{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "b9578ab3",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:11:56.183162Z",
     "iopub.status.busy": "2026-02-16T16:11:56.182871Z",
     "iopub.status.idle": "2026-02-16T16:11:56.814215Z",
     "shell.execute_reply": "2026-02-16T16:11:56.812868Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OptimizR Differential Evolution Module Loaded!\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from mpl_toolkits.mplot3d import Axes3D\n",
    "from optimizr import differential_evolution\n",
    "import time\n",
    "\n",
    "np.random.seed(42)\n",
    "print(\"OptimizR Differential Evolution Module Loaded!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "78cfac35",
   "metadata": {},
   "source": [
    "# Differential Evolution Tutorial - Global Optimization\n",
    "\n",
    "## Introduction\n",
    "\n",
    "**Differential Evolution (DE)** is a powerful population-based stochastic optimization algorithm designed for global optimization of non-convex, non-differentiable, and multimodal problems.\n",
    "\n",
    "### Why Differential Evolution?\n",
    "\n",
    "Unlike gradient-based methods that can get stuck in local minima, DE:\n",
    "- ✅ **Global search capability** - Explores entire parameter space\n",
    "- ✅ **No gradient required** - Works with black-box functions\n",
    "- ✅ **Few hyperparameters** - Mutation factor F and crossover rate CR\n",
    "- ✅ **Robust** - Handles noisy and discontinuous functions\n",
    "- ✅ **Parallelizable** - Population members can be evaluated independently\n",
    "\n",
    "### Applications\n",
    "- Portfolio optimization\n",
    "- Hyperparameter tuning in ML\n",
    "- Engineering design optimization\n",
    "- Physics parameter fitting\n",
    "- Control system design\n",
    "\n",
    "## Algorithm Overview\n",
    "\n",
    "### The DE/rand/1/bin Strategy\n",
    "\n",
    "Given a population of $N_p$ candidate solutions $\\mathbf{x}_i$, DE iterates:\n",
    "\n",
    "**1. Mutation** - Create mutant vector:\n",
    "$$\\mathbf{v}_i = \\mathbf{x}_{r1} + F \\cdot (\\mathbf{x}_{r2} - \\mathbf{x}_{r3})$$\n",
    "\n",
    "where $r1, r2, r3$ are random distinct indices, and $F \\in [0, 2]$ is the mutation factor.\n",
    "\n",
    "**2. Crossover** - Create trial vector:\n",
    "$$u_{i,j} = \\begin{cases}\n",
    "v_{i,j} & \\text{if } \\text{rand}() < CR \\text{ or } j = j_{rand} \\\\\n",
    "x_{i,j} & \\text{otherwise}\n",
    "\\end{cases}$$\n",
    "\n",
    "where $CR \\in [0, 1]$ is the crossover probability.\n",
    "\n",
    "**3. Selection** - Greedy selection:\n",
    "$$\\mathbf{x}_i^{t+1} = \\begin{cases}\n",
    "\\mathbf{u}_i & \\text{if } f(\\mathbf{u}_i) < f(\\mathbf{x}_i^t) \\\\\n",
    "\\mathbf{x}_i^t & \\text{otherwise}\n",
    "\\end{cases}$$\n",
    "\n",
    "### Convergence\n",
    "\n",
    "Under mild conditions, DE converges to the global optimum with probability 1:\n",
    "$$\\lim_{t \\to \\infty} P\\left(\\|\\mathbf{x}^*_t - \\mathbf{x}^*\\| < \\epsilon\\right) = 1$$\n",
    "\n",
    "where $\\mathbf{x}^*$ is the global optimum.\n",
    "\n",
    "### Complexity\n",
    "\n",
    "- **Time:** $O(N_p \\cdot d \\cdot T)$ where $d$ is dimension, $T$ is iterations\n",
    "- **Space:** $O(N_p \\cdot d)$ for population storage\n",
    "\n",
    "## References\n",
    "\n",
    "- Storn, R., & Price, K. (1997). \"Differential evolution–a simple and efficient heuristic for global optimization over continuous spaces.\" *Journal of global optimization*, 11(4), 341-359.\n",
    "- Das, S., & Suganthan, P. N. (2011). \"Differential evolution: A survey of the state-of-the-art.\" *IEEE transactions on evolutionary computation*, 15(1), 4-31."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "588bf0b2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:11:56.817581Z",
     "iopub.status.busy": "2026-02-16T16:11:56.817168Z",
     "iopub.status.idle": "2026-02-16T16:11:56.822872Z",
     "shell.execute_reply": "2026-02-16T16:11:56.821072Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OptimizR Differential Evolution Loaded!\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from mpl_toolkits.mplot3d import Axes3D\n",
    "from optimizr import differential_evolution\n",
    "\n",
    "np.random.seed(42)\n",
    "print(\"OptimizR Differential Evolution Loaded!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3ba7fb3a",
   "metadata": {},
   "source": [
    "## Example 1: Rosenbrock Function (Banana Valley)\n",
    "\n",
    "$$f(\\mathbf{x}) = \\sum_{i=1}^{n-1} \\left[100(x_{i+1} - x_i^2)^2 + (1 - x_i)^2\\right]$$\n",
    "\n",
    "Global minimum: $f(1, 1, \\ldots, 1) = 0$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "5f9644cd",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:11:56.826257Z",
     "iopub.status.busy": "2026-02-16T16:11:56.825957Z",
     "iopub.status.idle": "2026-02-16T16:11:56.831830Z",
     "shell.execute_reply": "2026-02-16T16:11:56.830572Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "f([1, 1, 1]): 0.0\n",
      "f([0, 0, 0]): 2.0\n"
     ]
    }
   ],
   "source": [
    "def rosenbrock(x):\n",
    "    \"\"\"N-dimensional Rosenbrock function.\"\"\"\n",
    "    return sum(100 * (x[i+1] - x[i]**2)**2 + (1 - x[i])**2 \n",
    "              for i in range(len(x) - 1))\n",
    "\n",
    "# Test function\n",
    "print(f\"f([1, 1, 1]): {rosenbrock([1.0, 1.0, 1.0])}\")\n",
    "print(f\"f([0, 0, 0]): {rosenbrock([0.0, 0.0, 0.0])}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dda42ec7",
   "metadata": {},
   "source": [
    "### Visualize 2D Rosenbrock"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "ec984eff",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:11:56.834783Z",
     "iopub.status.busy": "2026-02-16T16:11:56.834496Z",
     "iopub.status.idle": "2026-02-16T16:11:58.524532Z",
     "shell.execute_reply": "2026-02-16T16:11:58.523304Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "<>:16: SyntaxWarning: invalid escape sequence '\\l'\n",
      "<>:27: SyntaxWarning: invalid escape sequence '\\l'\n",
      "<>:16: SyntaxWarning: invalid escape sequence '\\l'\n",
      "<>:27: SyntaxWarning: invalid escape sequence '\\l'\n",
      "/var/folders/ns/tb9t1knx50z780g06d68yfth0000gp/T/ipykernel_22373/2058947432.py:16: SyntaxWarning: invalid escape sequence '\\l'\n",
      "  ax1.set_zlabel('$\\log_{10}(f + 1)$', fontsize=11)\n",
      "/var/folders/ns/tb9t1knx50z780g06d68yfth0000gp/T/ipykernel_22373/2058947432.py:27: SyntaxWarning: invalid escape sequence '\\l'\n",
      "  plt.colorbar(contour, ax=ax2, label='$\\log_{10}(f + 1)$')\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 675780 characters]",
      "text/plain": [
       "<Figure size 1400x600 with 3 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Create meshgrid\n",
    "x1 = np.linspace(-2, 2, 200)\n",
    "x2 = np.linspace(-1, 3, 200)\n",
    "X1, X2 = np.meshgrid(x1, x2)\n",
    "Z = np.array([[rosenbrock([x1_val, x2_val]) for x1_val, x2_val in zip(x1_row, x2_row)] \n",
    "             for x1_row, x2_row in zip(X1, X2)])\n",
    "\n",
    "fig = plt.figure(figsize=(14, 6))\n",
    "\n",
    "# 3D surface\n",
    "ax1 = fig.add_subplot(121, projection='3d')\n",
    "surf = ax1.plot_surface(X1, X2, np.log10(Z + 1), cmap='viridis', alpha=0.8)\n",
    "ax1.scatter([1], [1], [0], c='red', s=200, marker='*', edgecolors='black', linewidths=2, label='Global min')\n",
    "ax1.set_xlabel('$x_1$', fontsize=11)\n",
    "ax1.set_ylabel('$x_2$', fontsize=11)\n",
    "ax1.set_zlabel('$\\log_{10}(f + 1)$', fontsize=11)\n",
    "ax1.set_title('Rosenbrock Function (3D)', fontsize=13, fontweight='bold')\n",
    "\n",
    "# 2D contour\n",
    "ax2 = fig.add_subplot(122)\n",
    "contour = ax2.contour(X1, X2, np.log10(Z + 1), levels=20, cmap='viridis')\n",
    "ax2.scatter([1], [1], c='red', s=200, marker='*', edgecolors='black', linewidths=2, label='Global min', zorder=5)\n",
    "ax2.set_xlabel('$x_1$', fontsize=11)\n",
    "ax2.set_ylabel('$x_2$', fontsize=11)\n",
    "ax2.set_title('Rosenbrock Function (Contour)', fontsize=13, fontweight='bold')\n",
    "ax2.legend()\n",
    "plt.colorbar(contour, ax=ax2, label='$\\log_{10}(f + 1)$')\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "812d80ae",
   "metadata": {},
   "source": [
    "### Optimize with Differential Evolution"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "f722fb0a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:11:58.531497Z",
     "iopub.status.busy": "2026-02-16T16:11:58.531199Z",
     "iopub.status.idle": "2026-02-16T16:11:59.276354Z",
     "shell.execute_reply": "2026-02-16T16:11:59.274987Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimizing 10D Rosenbrock function...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Optimization completed!\n",
      "Best solution: [ 9.40856038e-01  9.02723698e-01  8.52156614e-01  7.21181862e-01\n",
      "  4.78495137e-01  2.33698512e-01  4.66375701e-02 -9.09126619e-04\n",
      "  1.80505507e-02 -1.14071552e-02]\n",
      "Best value: 4.247611e+00\n",
      "\n",
      "Distance to true optimum [1, 1, ..., 1]:\n",
      "  ||x - x*|| = 2.206724\n"
     ]
    }
   ],
   "source": [
    "# 10-dimensional Rosenbrock\n",
    "n_dims = 10\n",
    "bounds = [(-5, 5)] * n_dims\n",
    "\n",
    "print(f\"Optimizing {n_dims}D Rosenbrock function...\")\n",
    "x, fun = differential_evolution(\n",
    "    objective_fn=rosenbrock,\n",
    "    bounds=bounds,\n",
    "    maxiter=500,\n",
    "    popsize=15,\n",
    "    f=0.8,\n",
    "    cr=0.7,\n",
    "    seed=42\n",
    ")\n",
    "\n",
    "print(f\"\\nOptimization completed!\")\n",
    "print(f\"Best solution: {x}\")\n",
    "print(f\"Best value: {fun:.6e}\")\n",
    "print(f\"\\nDistance to true optimum [1, 1, ..., 1]:\")\n",
    "print(f\"  ||x - x*|| = {np.linalg.norm(x - np.ones(n_dims)):.6f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "89fde8e9",
   "metadata": {},
   "source": [
    "## Example 2: Rastrigin Function (Many Local Minima)\n",
    "\n",
    "$$f(\\mathbf{x}) = 10n + \\sum_{i=1}^n \\left[x_i^2 - 10\\cos(2\\pi x_i)\\right]$$\n",
    "\n",
    "Global minimum: $f(0, 0, \\ldots, 0) = 0$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "fff32181",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:11:59.279477Z",
     "iopub.status.busy": "2026-02-16T16:11:59.279189Z",
     "iopub.status.idle": "2026-02-16T16:12:00.923846Z",
     "shell.execute_reply": "2026-02-16T16:12:00.922152Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 369708 characters]",
      "text/plain": [
       "<Figure size 1400x600 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Note: Rastrigin has MANY local minima (visible as the peaks in the plot)\n"
     ]
    }
   ],
   "source": [
    "def rastrigin(x):\n",
    "    \"\"\"Rastrigin function with many local minima.\"\"\"\n",
    "    n = len(x)\n",
    "    return 10 * n + sum(xi**2 - 10 * np.cos(2 * np.pi * xi) for xi in x)\n",
    "\n",
    "# Visualize 2D\n",
    "x1 = np.linspace(-5.12, 5.12, 200)\n",
    "x2 = np.linspace(-5.12, 5.12, 200)\n",
    "X1, X2 = np.meshgrid(x1, x2)\n",
    "Z = np.array([[rastrigin([x1_val, x2_val]) for x1_val, x2_val in zip(x1_row, x2_row)]\n",
    "             for x1_row, x2_row in zip(X1, X2)])\n",
    "\n",
    "fig, axes = plt.subplots(1, 2, figsize=(14, 6))\n",
    "\n",
    "# 3D plot\n",
    "ax1 = fig.add_subplot(121, projection='3d')\n",
    "ax1.plot_surface(X1, X2, Z, cmap='plasma', alpha=0.8)\n",
    "ax1.scatter([0], [0], [0], c='red', s=200, marker='*', edgecolors='black', linewidths=2)\n",
    "ax1.set_xlabel('$x_1$', fontsize=11)\n",
    "ax1.set_ylabel('$x_2$', fontsize=11)\n",
    "ax1.set_zlabel('$f(x)$', fontsize=11)\n",
    "ax1.set_title('Rastrigin Function (3D)', fontsize=13, fontweight='bold')\n",
    "\n",
    "# Contour plot\n",
    "contour = axes[1].contourf(X1, X2, Z, levels=30, cmap='plasma')\n",
    "axes[1].scatter([0], [0], c='red', s=200, marker='*', edgecolors='black', linewidths=2, label='Global min', zorder=5)\n",
    "axes[1].set_xlabel('$x_1$', fontsize=11)\n",
    "axes[1].set_ylabel('$x_2$', fontsize=11)\n",
    "axes[1].set_title('Rastrigin Function (Contour)', fontsize=13, fontweight='bold')\n",
    "axes[1].legend()\n",
    "plt.colorbar(contour, ax=axes[1])\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(\"Note: Rastrigin has MANY local minima (visible as the peaks in the plot)\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "79deeae5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:12:00.928890Z",
     "iopub.status.busy": "2026-02-16T16:12:00.928558Z",
     "iopub.status.idle": "2026-02-16T16:12:05.991238Z",
     "shell.execute_reply": "2026-02-16T16:12:05.989880Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimizing 10D Rastrigin function...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Best solution: [-0.02184215 -2.1508949   0.15564753  0.86569676  0.02828005 -0.11939509\n",
      "  0.91321362  0.0694459   0.94685828 -0.03271556]\n",
      "Best value: 2.517248e+01\n",
      "Distance to global optimum: 2.674309\n",
      "\n",
      "⚠ Stuck in local minimum (try increasing popsize or maxiter)\n"
     ]
    }
   ],
   "source": [
    "# Optimize Rastrigin\n",
    "n_dims = 10\n",
    "bounds = [(-5.12, 5.12)] * n_dims\n",
    "\n",
    "print(f\"Optimizing {n_dims}D Rastrigin function...\")\n",
    "x, fun = differential_evolution(\n",
    "    objective_fn=rastrigin,\n",
    "    bounds=bounds,\n",
    "    maxiter=1000,\n",
    "    popsize=20,\n",
    "    f=0.9,\n",
    "    cr=0.9,\n",
    "    seed=42\n",
    ")\n",
    "\n",
    "print(f\"\\nBest solution: {x}\")\n",
    "print(f\"Best value: {fun:.6e}\")\n",
    "print(f\"Distance to global optimum: {np.linalg.norm(x):.6f}\")\n",
    "\n",
    "if fun < 1.0:\n",
    "    print(\"\\n✓ Successfully found global minimum!\")\n",
    "else:\n",
    "    print(\"\\n⚠ Stuck in local minimum (try increasing popsize or maxiter)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb324ced",
   "metadata": {},
   "source": [
    "## Example 3: Real-World Application - Portfolio Optimization\n",
    "\n",
    "Minimize portfolio variance with expected return constraint.\n",
    "\n",
    "$$\\min_{\\mathbf{w}} \\quad \\mathbf{w}^T \\Sigma \\mathbf{w}$$\n",
    "$$\\text{s.t.} \\quad \\mathbf{w}^T \\boldsymbol{\\mu} \\geq r_{\\text{target}}$$\n",
    "$$\\sum_i w_i = 1, \\quad w_i \\geq 0$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "19b33e87",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:12:05.994582Z",
     "iopub.status.busy": "2026-02-16T16:12:05.994297Z",
     "iopub.status.idle": "2026-02-16T16:12:06.380622Z",
     "shell.execute_reply": "2026-02-16T16:12:06.379482Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Portfolio with 10 assets\n",
      "Expected returns (daily): [ 0.00157133 -0.00010693  0.00277879  0.00141902  0.00154478  0.00056306\n",
      "  0.00144421  0.00249818  0.00340539  0.00288016]\n",
      "Annualized returns: [ 0.39597642 -0.0269462   0.70025534  0.35759412  0.38928546  0.14189035\n",
      "  0.36394002  0.62954258  0.85815941  0.72579955]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 245384 characters]",
      "text/plain": [
       "<Figure size 1200x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Generate synthetic asset data\n",
    "n_assets = 10\n",
    "n_periods = 252  # 1 year of daily data\n",
    "\n",
    "# Simulate correlated returns\n",
    "np.random.seed(42)\n",
    "mean_returns = np.random.uniform(0.0005, 0.002, n_assets)  # Daily returns\n",
    "returns = np.random.multivariate_normal(\n",
    "    mean=mean_returns,\n",
    "    cov=np.diag(np.random.uniform(0.01, 0.03, n_assets)**2),\n",
    "    size=n_periods\n",
    ")\n",
    "\n",
    "# Compute statistics\n",
    "mu = returns.mean(axis=0)  # Expected returns\n",
    "Sigma = np.cov(returns.T)  # Covariance matrix\n",
    "\n",
    "print(f\"Portfolio with {n_assets} assets\")\n",
    "print(f\"Expected returns (daily): {mu}\")\n",
    "print(f\"Annualized returns: {mu * 252}\")\n",
    "\n",
    "# Visualize returns\n",
    "plt.figure(figsize=(12, 6))\n",
    "cumulative_returns = np.cumprod(1 + returns, axis=0) - 1\n",
    "for i in range(n_assets):\n",
    "    plt.plot(cumulative_returns[:, i], alpha=0.6, label=f'Asset {i+1}')\n",
    "plt.xlabel('Days', fontsize=11)\n",
    "plt.ylabel('Cumulative Return', fontsize=11)\n",
    "plt.title('Simulated Asset Returns', fontsize=13, fontweight='bold')\n",
    "plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')\n",
    "plt.grid(alpha=0.3)\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "348c49cb",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:12:06.384302Z",
     "iopub.status.busy": "2026-02-16T16:12:06.384003Z",
     "iopub.status.idle": "2026-02-16T16:12:08.123468Z",
     "shell.execute_reply": "2026-02-16T16:12:08.122370Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimizing portfolio allocation...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Optimal Portfolio:\n",
      "Weights: [0.52655741 0.54272521 0.6364651  0.40590176 0.03434282 0.41495685\n",
      " 0.73742443 0.8492516  0.13127889 0.0032521 ]\n",
      "Sum of weights: 4.282156\n",
      "\n",
      "Expected daily return: 0.007044 (177.50% annualized)\n",
      "Daily volatility: 0.031613 (50.18% annualized)\n",
      "Sharpe ratio (assuming 0% risk-free): 0.2228\n"
     ]
    }
   ],
   "source": [
    "def portfolio_objective(weights):\n",
    "    \"\"\"\n",
    "    Minimize: variance + penalty for constraint violations.\n",
    "    \"\"\"\n",
    "    # Portfolio variance\n",
    "    variance = weights @ Sigma @ weights\n",
    "    \n",
    "    # Constraints (penalize violations)\n",
    "    target_return = 0.0015  # Target daily return\n",
    "    return_constraint = max(0, target_return - weights @ mu)\n",
    "    sum_constraint = abs(weights.sum() - 1.0)\n",
    "    negative_constraint = max(0, -weights.min())\n",
    "    \n",
    "    # Penalize constraint violations heavily\n",
    "    penalty = 1000 * (return_constraint + sum_constraint + negative_constraint)\n",
    "    \n",
    "    return variance + penalty\n",
    "\n",
    "# Optimize\n",
    "bounds = [(0, 1)] * n_assets  # Weights between 0 and 1\n",
    "\n",
    "print(\"Optimizing portfolio allocation...\")\n",
    "x, fun = differential_evolution(\n",
    "    objective_fn=portfolio_objective,\n",
    "    bounds=bounds,\n",
    "    maxiter=500,\n",
    "    popsize=20,\n",
    "    seed=42\n",
    ")\n",
    "\n",
    "optimal_weights = x\n",
    "optimal_return = optimal_weights @ mu\n",
    "optimal_volatility = np.sqrt(optimal_weights @ Sigma @ optimal_weights)\n",
    "\n",
    "print(f\"\\nOptimal Portfolio:\")\n",
    "print(f\"Weights: {optimal_weights}\")\n",
    "print(f\"Sum of weights: {optimal_weights.sum():.6f}\")\n",
    "print(f\"\\nExpected daily return: {optimal_return:.6f} ({optimal_return * 252:.2%} annualized)\")\n",
    "print(f\"Daily volatility: {optimal_volatility:.6f} ({optimal_volatility * np.sqrt(252):.2%} annualized)\")\n",
    "print(f\"Sharpe ratio (assuming 0% risk-free): {optimal_return / optimal_volatility:.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "75325f0a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-02-16T16:12:08.127014Z",
     "iopub.status.busy": "2026-02-16T16:12:08.126714Z",
     "iopub.status.idle": "2026-02-16T16:12:08.530507Z",
     "shell.execute_reply": "2026-02-16T16:12:08.529120Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 74396 characters]",
      "text/plain": [
       "<Figure size 1400x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Visualize allocation\n",
    "fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
    "\n",
    "# Bar chart\n",
    "axes[0].bar(range(n_assets), optimal_weights, color='steelblue', edgecolor='black')\n",
    "axes[0].set_xlabel('Asset', fontsize=11)\n",
    "axes[0].set_ylabel('Weight', fontsize=11)\n",
    "axes[0].set_title('Optimal Portfolio Allocation', fontsize=13, fontweight='bold')\n",
    "axes[0].grid(alpha=0.3, axis='y')\n",
    "\n",
    "# Pie chart\n",
    "nonzero_weights = optimal_weights[optimal_weights > 0.01]\n",
    "nonzero_assets = [f'Asset {i+1}' for i in range(n_assets) if optimal_weights[i] > 0.01]\n",
    "axes[1].pie(nonzero_weights, labels=nonzero_assets, autopct='%1.1f%%', startangle=90)\n",
    "axes[1].set_title('Portfolio Composition', fontsize=13, fontweight='bold')\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ed601b8",
   "metadata": {},
   "source": [
    "## Key Takeaways\n",
    "\n",
    "1. **DE is excellent for non-convex, multimodal problems** where gradient-based methods fail\n",
    "2. **Population-based approach** explores solution space thoroughly\n",
    "3. **Few hyperparameters** - typically F ∈ [0.5, 1], CR ∈ [0.7, 1]\n",
    "4. **Robust** - works well on wide variety of problems\n",
    "5. **OptimizR provides 50-100x speedup** over pure Python\n",
    "6. **Real-world applications** - engineering, ML, finance, science\n",
    "\n",
    "## Further Reading\n",
    "\n",
    "- Storn & Price (1997). \"Differential evolution–a simple and efficient heuristic for global optimization\"\n",
    "- Price, Storn & Lampinen (2005). \"Differential Evolution: A Practical Approach to Global Optimization\""
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "rhftlab",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
