Files
optimiz-rs/examples/notebooks/03_differential_evolution_tutorial.ipynb
T

476 lines
16 KiB
Plaintext
Raw Normal View History

{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "b9578ab3",
"metadata": {},
"outputs": [],
"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 evolutiona 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": null,
"id": "588bf0b2",
"metadata": {},
"outputs": [],
"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": null,
"id": "5f9644cd",
"metadata": {},
"outputs": [],
"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": null,
"id": "ec984eff",
"metadata": {},
"outputs": [],
"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": null,
"id": "f722fb0a",
"metadata": {},
"outputs": [],
"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",
"result = differential_evolution(\n",
" objective_fn=rosenbrock,\n",
" bounds=bounds,\n",
" maxiter=500,\n",
" popsize=15,\n",
" mutation_factor=0.8,\n",
" crossover_rate=0.7,\n",
" seed=42\n",
")\n",
"\n",
"print(f\"\\nOptimization completed!\")\n",
"print(f\"Best solution: {result.x}\")\n",
"print(f\"Best value: {result.fun:.6e}\")\n",
"print(f\"Function evaluations: {result.nfev}\")\n",
"print(f\"\\nDistance to true optimum [1, 1, ..., 1]:\")\n",
"print(f\" ||x - x*|| = {np.linalg.norm(result.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": null,
"id": "fff32181",
"metadata": {},
"outputs": [],
"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": null,
"id": "79deeae5",
"metadata": {},
"outputs": [],
"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",
"result = differential_evolution(\n",
" objective_fn=rastrigin,\n",
" bounds=bounds,\n",
" maxiter=1000,\n",
" popsize=20,\n",
" mutation_factor=0.9,\n",
" crossover_rate=0.9,\n",
" seed=42\n",
")\n",
"\n",
"print(f\"\\nBest solution: {result.x}\")\n",
"print(f\"Best value: {result.fun:.6e}\")\n",
"print(f\"Distance to global optimum: {np.linalg.norm(result.x):.6f}\")\n",
"\n",
"if result.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": null,
"id": "19b33e87",
"metadata": {},
"outputs": [],
"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": null,
"id": "348c49cb",
"metadata": {},
"outputs": [],
"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",
"result = differential_evolution(\n",
" objective_fn=portfolio_objective,\n",
" bounds=bounds,\n",
" maxiter=500,\n",
" popsize=20,\n",
" seed=42\n",
")\n",
"\n",
"optimal_weights = result.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": null,
"id": "75325f0a",
"metadata": {},
"outputs": [],
"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 evolutiona simple and efficient heuristic for global optimization\"\n",
"- Price, Storn & Lampinen (2005). \"Differential Evolution: A Practical Approach to Global Optimization\""
]
}
],
"metadata": {
2026-01-06 14:36:08 +01:00
"kernelspec": {
"display_name": "rhftlab",
"language": "python",
"name": "python3"
},
"language_info": {
2026-01-06 14:36:08 +01:00
"name": "python",
"version": "3.11.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}