Files
optimiz-rs/docs/source/algorithms/differential_evolution.md
T
ThotDjehuty b10263b4f2 docs: Rebrand OptimizR to Optimiz-rs throughout documentation
Updated Branding in ReadTheDocs:
-  All 'OptimizR' → 'Optimiz-rs' (17 files)
-  Project name in conf.py
-  HTML title and short title
-  All algorithm documentation
-  Getting started guide
-  Installation guide
-  Theory/mathematical foundations
-  Archive documentation

Documentation now consistently uses the new 'optimiz-rs' branding that
matches both PyPI and crates.io package names.

Note: Python module name 'optimizr' in import statements intentionally
unchanged (that's the actual module name).
2026-02-17 10:09:15 +01:00

523 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Differential Evolution
**Differential Evolution (DE)** is a population-based metaheuristic optimization algorithm
introduced by Storn and Price (1997). It is particularly effective for continuous, non-convex,
multimodal optimization problems where gradient information is unavailable or unreliable.
This module provides a high-performance Rust implementation with Python bindings, supporting
multiple mutation strategies, adaptive parameter control (jDE), and parallel evaluation.
---
## Mathematical Foundations
### Problem Formulation
DE solves unconstrained (or box-constrained) minimization problems:
$$
\min_{\mathbf{x} \in \mathbb{R}^D} f(\mathbf{x})
$$
subject to box constraints:
$$
x_j \in [l_j, u_j], \quad j = 1, \ldots, D
$$
**DE is well-suited when:**
- $f$ is continuous but non-differentiable
- Multiple local minima exist
- Gradient information is unavailable or expensive
- Problem dimension is moderate ($D < 100$)
---
### Population
DE maintains a population of $N_P$ candidate solutions:
$$
P_g = \{\mathbf{x}_{1,g}, \mathbf{x}_{2,g}, \ldots, \mathbf{x}_{N_P,g}\}
$$
where $g$ is the generation number and $\mathbf{x}_{i,g} \in \mathbb{R}^D$.
**Rule of thumb:** $N_P = 10 \times D$ where $D$ is the problem dimension.
---
### Main Loop
For each generation $g = 0, 1, 2, \ldots$:
1. **Mutation**: Create mutant vectors by combining existing solutions
2. **Crossover**: Mix mutant with target vector to form trial vector
3. **Selection**: Keep better solution (greedy selection)
---
## Mutation Strategies
The mutation operator creates a **mutant vector** $\mathbf{v}_{i,g+1}$ from existing
population members:
### DE/rand/1 (Classic Strategy)
$$
\mathbf{v}_{i,g+1} = \mathbf{x}_{r_1,g} + F \cdot (\mathbf{x}_{r_2,g} - \mathbf{x}_{r_3,g})
$$
where:
- $r_1, r_2, r_3 \in \{1, \ldots, N_P\}$ are randomly chosen, distinct, and $\neq i$
- $F \in (0, 2]$ is the **mutation factor** (typically 0.51.0)
**Interpretation:** Start from a random population member $\mathbf{x}_{r_1}$,
move in direction given by the difference $(\mathbf{x}_{r_2} - \mathbf{x}_{r_3})$,
scaled by $F$.
**Characteristics:** Most explorative, good for diverse populations.
### DE/best/1
$$
\mathbf{v}_{i,g+1} = \mathbf{x}_{\text{best},g} + F \cdot (\mathbf{x}_{r_1,g} - \mathbf{x}_{r_2,g})
$$
**Advantage:** Faster convergence toward the best-known solution.
**Disadvantage:** More likely to get stuck in local minima.
### DE/current-to-best/1
$$
\mathbf{v}_{i,g+1} = \mathbf{x}_{i,g} + F \cdot (\mathbf{x}_{\text{best},g} - \mathbf{x}_{i,g}) + F \cdot (\mathbf{x}_{r_1,g} - \mathbf{x}_{r_2,g})
$$
**Interpretation:** Move current solution toward the best while also exploring.
**Characteristics:** Balanced exploration/exploitation.
### DE/rand/2
$$
\mathbf{v}_{i,g+1} = \mathbf{x}_{r_1,g} + F \cdot (\mathbf{x}_{r_2,g} - \mathbf{x}_{r_3,g}) + F \cdot (\mathbf{x}_{r_4,g} - \mathbf{x}_{r_5,g})
$$
**Characteristics:** More disruptive, better for highly multimodal problems.
### DE/best/2
$$
\mathbf{v}_{i,g+1} = \mathbf{x}_{\text{best},g} + F \cdot (\mathbf{x}_{r_1,g} - \mathbf{x}_{r_2,g}) + F \cdot (\mathbf{x}_{r_3,g} - \mathbf{x}_{r_4,g})
$$
**Characteristics:** Aggressive convergence to the best solution.
---
## Crossover
After mutation, the **trial vector** $\mathbf{u}_{i,g+1}$ is formed by mixing
components from the mutant and the target vector.
### Binomial Crossover
For each component $j = 1, \ldots, D$:
$$
u_{i,j,g+1} = \begin{cases}
v_{i,j,g+1} & \text{if } \text{rand}(0,1) \leq CR \text{ or } j = j_{\text{rand}} \\
x_{i,j,g} & \text{otherwise}
\end{cases}
$$
where:
- $CR \in [0, 1]$ is the **crossover probability**
- $j_{\text{rand}} \in \{1, \ldots, D\}$ ensures at least one component comes from the mutant
**Effect:** $CR$ controls how much of the mutant vector is used.
| CR Value | Effect |
|----------|--------|
| Low (0.10.3) | Less information exchange, slower convergence. Better for separable problems |
| High (0.70.9) | More information exchange, faster convergence. Better for non-separable problems |
| 0.0 | Pure mutation (except $j_{\text{rand}}$) |
| 1.0 | Full crossover |
---
## Selection
Greedy selection (for minimization):
$$
\mathbf{x}_{i,g+1} = \begin{cases}
\mathbf{u}_{i,g+1} & \text{if } f(\mathbf{u}_{i,g+1}) \leq f(\mathbf{x}_{i,g}) \\
\mathbf{x}_{i,g} & \text{otherwise}
\end{cases}
$$
**Property:** Population quality never decreases:
$$
f(\mathbf{x}_{\text{best},g+1}) \leq f(\mathbf{x}_{\text{best},g})
$$
---
## Complete Algorithm
```
Algorithm: Differential Evolution
─────────────────────────────────
Input: objective f, bounds [l, u], pop_size N_P, F, CR, max_iter
1. Initialize population:
For i = 1 to N_P:
x_{i,0} = l + rand(0,1) · (u - l) # uniform in bounds
2. Evaluate fitness:
f_i = f(x_{i,0}) for all i
3. While g < max_iter and not converged:
a. For i = 1 to N_P:
i. Mutation:
Select r_1, r_2, r_3 distinct and ≠ i
v_{i,g+1} = x_{r_1,g} + F · (x_{r_2,g} - x_{r_3,g})
ii. Crossover:
j_rand = randint(1, D)
For j = 1 to D:
if rand(0,1) ≤ CR or j = j_rand:
u_{i,j,g+1} = v_{i,j,g+1}
else:
u_{i,j,g+1} = x_{i,j,g}
iii. Boundary handling:
Clip u_{i,g+1} to [l, u]
iv. Selection:
if f(u_{i,g+1}) ≤ f(x_{i,g}):
x_{i,g+1} = u_{i,g+1}
else:
x_{i,g+1} = x_{i,g}
b. g = g + 1
4. Return x_best and f(x_best)
```
---
## Parameter Selection Guidelines
### Population Size ($N_P$)
| Size Category | Range | Use Case |
|---------------|-------|----------|
| Small | < 4D | Faster convergence; risk premature convergence. Simple unimodal problems |
| Medium | 10D (default) | Good balance for most problems |
| Large | > 20D | Better exploration; slower convergence. Highly multimodal problems |
**Minimum:** $N_P \geq 4$ (needed for mutation with three distinct indices).
### Mutation Factor ($F$)
| F Value | Effect |
|---------|--------|
| Low (0.40.6) | Fine-tuning, local search. Safer, less disruptive |
| High (0.81.2) | Exploration, global search. Escape local minima |
**Typical range:** $F \in [0.4, 1.0]$, default 0.8.
### Crossover Probability ($CR$)
| CR Value | Effect |
|----------|--------|
| Low (0.10.3) | Best for separable problems |
| High (0.70.9) | Best for non-separable problems |
**Default:** 0.70.9 for most problems.
---
## Python API
### Basic Usage
```python
import numpy as np
from optimizr import differential_evolution
def rastrigin(x):
"""Multimodal benchmark function with many local minima."""
A = 10
return A * len(x) + sum(x**2 - A * np.cos(2 * np.pi * x))
best_x, best_fx = differential_evolution(
objective_fn=rastrigin,
bounds=[(-5.12, 5.12)] * 10, # 10-dimensional problem
strategy="best1",
popsize=20,
maxiter=500,
adaptive=True,
)
print(f"Best fitness: {best_fx:.6f}")
print(f"Best solution: {best_x}")
```
**Expected output:**
```
Best fitness: 0.000042
Best solution: [ 0.00012 -0.00023 0.00018 ... ]
```
### Configuration Options
```python
from optimizr import DifferentialEvolution
de = DifferentialEvolution(
bounds=[(-5, 5)] * 20,
strategy="rand1", # mutation strategy
popsize=200, # population size
maxiter=1000, # maximum generations
F=0.8, # mutation factor
CR=0.9, # crossover probability
tol=1e-8, # convergence tolerance
seed=42, # reproducibility
)
result = de.minimize(sphere_function)
print(f"Converged in {result.nit} iterations")
print(f"Function evaluations: {result.nfev}")
```
---
## Adaptive Control (jDE)
Optimiz-rs implements **jDE** (self-adaptive DE), where the parameters $F$ and $CR$
evolve with the population:
$$
F_{i,g+1} = \begin{cases}
F_l + \text{rand}(0,1) \cdot (F_u - F_l) & \text{if } \text{rand}(0,1) < \tau_1 \\
F_{i,g} & \text{otherwise}
\end{cases}
$$
$$
CR_{i,g+1} = \begin{cases}
\text{rand}(0,1) & \text{if } \text{rand}(0,1) < \tau_2 \\
CR_{i,g} & \text{otherwise}
\end{cases}
$$
**Enable jDE:**
```python
de = DifferentialEvolution(
bounds=[(-5, 5)] * 20,
adaptive=True, # enables jDE
tau_F=0.1, # probability of F mutation
tau_CR=0.1, # probability of CR mutation
)
```
**Advantages:**
- No need to manually tune $F$ and $CR$
- Adapts to problem landscape during optimization
- Generally robust across problem types
---
## Parallel Evaluation (Rust Backend)
For pure-Rust objectives or when Python callbacks are not needed, enable
data-parallel evaluation via Rayon:
```python
from optimizr import parallel_differential_evolution_rust
result = parallel_differential_evolution_rust(
objective="rastrigin", # built-in benchmark
dim=50,
bounds=(-5.12, 5.12),
popsize=500,
maxiter=2000,
n_threads=8,
)
print(f"Best fitness: {result.best_fitness:.8f}")
```
**Speedup:** Near-linear up to $N_P$ processors for expensive objectives.
---
## Convergence Analysis
### Theoretical Properties
**Global Convergence Theorem** (Lampinen, 2001):
Under these sufficient conditions:
- Population size $N_P > 3$
- Mutation factor $F > 0$
- At least one component crossed over ($j_{\text{rand}}$)
DE is a **global optimization method**: any point can be reached with positive probability.
### Diversity Measure
$$
D_g = \frac{1}{N_P D} \sum_{i=1}^{N_P} \sum_{j=1}^D |x_{i,j,g} - \bar{x}_{j,g}|
$$
| Diversity | Behavior |
|-----------|----------|
| High | Exploration (global search) |
| Low | Exploitation (local search) |
### Empirical Budget
**Rule of thumb:** Budget $10^4 \times D$ function evaluations for moderately difficult problems.
---
## Performance Comparison
| Algorithm | Gradient | Global | Constraints | Speed | Best For |
|-----------|----------|--------|-------------|-------|----------|
| **DE** | No | Yes | Box | Medium | Non-convex, continuous |
| Gradient Descent | Yes | No | Yes | Fast | Smooth, convex |
| Genetic Algorithm | No | Yes | Yes | Slow | Discrete, combinatorial |
| Particle Swarm | No | Yes | Box | Fast | Continuous, many dimensions |
| CMA-ES | No | Yes | Box | Fast | Continuous, noisy |
---
## Practical Tips
### 1. Start Simple
Use defaults: $N_P = 10D$, $F = 0.8$, $CR = 0.7$, `strategy="rand1"`.
### 2. Scale Variables
Normalize parameters to similar ranges for better performance.
### 3. Warm Start
If you have a good initial guess, seed the population around it.
### 4. Hybrid Approach
Use DE for global search, then a local optimizer for refinement:
```python
# Global search with DE
best_x, _ = differential_evolution(f, bounds, maxiter=200)
# Local refinement with L-BFGS-B
from scipy.optimize import minimize
result = minimize(f, best_x, method='L-BFGS-B', bounds=bounds)
```
### 5. Monitor Convergence
Plot:
- Best fitness vs. generation
- Average population fitness vs. generation
- Population diversity vs. generation
### 6. Restarts
If premature convergence detected, restart with new random population.
---
## Troubleshooting
| Symptom | Cause | Fix |
|---------|-------|-----|
| Slow convergence | $F$ or $CR$ too low | Increase $F$ to 0.8, $CR$ to 0.9 |
| Premature convergence | Population too small | Increase $N_P$ to 1520D |
| Oscillating fitness | $F$ too high | Decrease $F$ to 0.50.6 |
| Stuck in local minimum | Using `best1` strategy | Switch to `rand1` or `rand2` |
---
## Benchmark Results
Performance on standard test functions (D=30, $N_P=300$, 1000 generations):
| Function | Best Fitness | Iterations | Time (s) |
|----------|-------------|------------|----------|
| Sphere | 1.2e-28 | 412 | 0.8 |
| Rosenbrock | 2.4e-08 | 891 | 1.4 |
| Rastrigin | 4.1e-05 | 1000 | 2.1 |
| Ackley | 8.8e-15 | 623 | 1.2 |
| Griewank | 3.7e-12 | 548 | 1.0 |
---
## Advantages & Limitations
### Advantages
✅ No gradient information needed
✅ Handles non-convex, multimodal functions well
✅ Few parameters to tune
✅ Simple to implement and understand
✅ Robust across problem types
✅ Naturally handles box constraints
✅ Population maintains diversity
### Limitations
❌ Slower than gradient methods (when gradients are available)
❌ Scales poorly to high dimensions ($D > 100$)
❌ No convergence guarantees in finite time
❌ Requires many function evaluations
❌ Performance sensitive to parameter choices
---
## References
1. Storn, R. & Price, K. (1997). "Differential evolution A simple and efficient heuristic for global optimization over continuous spaces." *Journal of Global Optimization*, 11(4):341359.
2. Price, K., Storn, R.M. & Lampinen, J.A. (2005). *Differential Evolution: A Practical Approach to Global Optimization*. Springer.
3. Das, S. & Suganthan, P.N. (2011). "Differential evolution: A survey of the state-of-the-art." *IEEE Transactions on Evolutionary Computation*, 15(1):431.
4. Brest, J. et al. (2006). "Self-adapting control parameters in differential evolution." *IEEE Trans. Evolutionary Computation*, 10(6):646657. (jDE)
5. Tanabe, R. & Fukunaga, A. (2013). "Success-history based parameter adaptation for differential evolution." *IEEE CEC*, pp. 7178. (SHADE)
---
## Related Topics
- [Grid Search](grid_search.md) Exhaustive search for small parameter spaces
- [MCMC](mcmc.md) Sampling-based inference for Bayesian optimization
- [Mean Field Games](mean_field_games.md) Population dynamics optimization