Why Differential Evolution Matters
Suppose you must tune two real-valued settings for a system whose quality can be measured but whose derivatives are unavailable. Trying a dense grid becomes expensive as the number of settings grows, while a single local search may explore only one region. Differential evolution, or DE, maintains several candidate vectors and uses differences between them to construct new proposals at a scale suggested by the current population.
The distinctive idea is simple: take a base vector, add a scaled difference of two other vectors, mix the result with the current target, and keep the trial only when its objective is no worse. Here we minimize. The algorithm can explore a bounded continuous domain without a gradient, but a finite run normally returns the best point it evaluated rather than an optimality certificate.
We will implement DE/rand/1/bin with a supplied population, three distinct donor indices excluding the target, coordinate clipping, forced binomial crossover, and deferred generation replacement. The worked function is a shifted sphere with a known answer, allowing independent verification. Python, C++, and Java will produce identical sample output and the same recorded numerical decisions.
Differential evolution searches a continuous domain by proposing $\mathbf v=\mathbf x_a+F(\mathbf x_b-\mathbf x_c)$, crossing that vector with a target, and retaining the trial when its objective is no worse. The population supplies the search differences; greedy target-wise selection preserves progress without proving that a global optimum has been reached.
Core Concepts and Terminology
Let each candidate be a real vector $\mathbf x\in\mathbb R^d$ inside a box with bounds $\ell_j\le x_j\le h_j$. The population $P_g=(\mathbf x_{0,g},\ldots,\mathbf x_{N-1,g})$ is an ordered list of $N$ such vectors, and $f(\mathbf x)$ is a deterministic objective to minimize. The vectors may coincide, but their indices remain distinct.
For target $i$, choose ordered indices $a,b,c$ that are mutually distinct and different from $i$. Thus this variant requires $N\ge4$. Its raw mutant or donor vector is
$$\mathbf v_{i,g}=\mathbf x_{a,g}+F(\mathbf x_{b,g}-\mathbf x_{c,g}),\qquad F\gt0.$$
“rand” means the base index $a$ is sampled rather than fixed at the current best. “1” means one difference vector is used; it does not mean one-dimensional optimization. “bin” means coordinate-wise, binomial-style crossover. The scale $F$ is not a mutation probability: the unbounded differential displacement has length $F\lVert\mathbf x_b-\mathbf x_c\rVert_2$. Clipping and crossover can subsequently change its direction and length relative to the target.
The worked objective is
$$f(x,y)=(x-1)^2+(y+1)^2,\qquad -5\le x,y\le5.$$
Its unique real-valued minimum is $f(1,-1)=0$. The dashed path in the diagram connects successive best discoveries, which can belong to different population slots; DE does not have a single search trajectory or a fixed step size. The two-generation run stops at objective $2$, so it has improved substantially without reaching the known optimum.
| Term | Worked example | Role |
|---|---|---|
| Target vector | $\mathbf x_0=(-4,3)$, with $f=41$ | Incumbent that a trial must compete against |
| Population size | $N=4$ | Supplies the target and three other distinct indices |
| Ordered donor indices | $(a,b,c)=(3,2,1)$ | Choose base and the direction of the difference |
| Differential scale | $F=0.5$ | Scales the difference $(4,6)$ to $(2,3)$ |
| Raw mutant | $(5,-2)+0.5(4,6)=(7,1)$ | Differential proposal before box repair |
| Repaired mutant | $(5,1)$ after clipping | Keeps any inherited donor coordinates feasible |
| Crossover rate | $CR=m/1000$, with $m=500$ | Controls donor use at non-forced coordinates |
| Forced coordinate | $j_{\mathrm{rand}}=0$ | Guarantees at least one donor-sourced coordinate |
| Trial vector | $\mathbf u_0=(5,1)$, with $f=20$ | Competes only with target $0$ |
| Deferred update | Every donor is read from $P_g$ | New winners enter the breeding population together |
| Archive | First evaluated point achieving the smallest score seen | Retains a reproducible best-ever witness |
How Differential Evolution Works, Step by Step
The input fixes the initial population, lower and upper bounds, objective center, seed, generation limit $G$, differential scale $F$, crossover threshold $m$, and evaluation budget $B$. The programs use the deterministic shifted-sphere objective
$$f(\mathbf x)=\sum_{j=0}^{d-1}(x_j-c_j)^2.$$
- Validate and initialize. Copy all input vectors. Require enough budget to evaluate all $N$ initial members; otherwise return
EVAL_LIMITwith no evaluated point and no random draws. Evaluate every initial member, even duplicates, and archive the earliest minimum. Record generation $0$. - Check limits before a generation. If $G$ generations have completed, return
GEN_LIMIT. Otherwise require budget for $N$ trial evaluations; if it is insufficient, returnEVAL_LIMITbefore drawing randomness. No partial generation is produced. - Choose three donors without replacement. Exclude the target index, then draw a rank among the remaining indices. Remove that chosen index and repeat twice. The code maps each rank past at most three sorted exclusions, avoiding repeated whole-population shuffling or rejection of duplicate indices. Under uniform rank draws, this samples ordered donor triples uniformly.
- Draw the crossover choices. Draw a forced coordinate $j_{\mathrm{rand}}$ in $0,\ldots,d-1$, then one integer $r_j$ in $0,\ldots,999$ for every coordinate. These draws are consumed even for the forced coordinate and at crossover rates $0$ and $1$.
- Form and repair the mutant. Compute each difference, multiply by $F$, and add the base coordinate in that order. Clip every raw mutant coordinate into its interval:
$$\widehat v_j=\min\bigl(h_j,\max(\ell_j,v_j)\bigr).$$
The code implements clipping with comparisons so an in-range signed zero is retained consistently across languages. The clip counter includes every out-of-range raw coordinate, even if crossover later discards that coordinate.
- Build the trial. Use the repaired mutant coordinate if its crossover draw is below $m$ or if it is the forced coordinate; otherwise retain the target coordinate:
$$u_j=\begin{cases}\widehat v_j,&j=j_{\mathrm{rand}}\text{ or }r_j\lt m,\\x_{i,g,j},&\text{otherwise}.\end{cases}$$
- Evaluate and select. Evaluate the trial once, including when it equals its target. Accept when $f(\mathbf u_i)\le f(\mathbf x_{i,g})$. Accepting a tie permits neutral movement. A rejected trial leaves the target and its stored objective unchanged. Update the archive only on a strictly smaller objective.
- Commit the generation. Repeat for every target using the same frozen old population, storing winners in a separate next population. After all $N$ trials, replace the population and its score array together, record a snapshot, and repeat.
At least one coordinate is donor-sourced, but the trial can still equal the target numerically. This happens with duplicate vectors, fixed coordinates, zero differences, clipping, or rounding. With ideal independent crossover draws, the expected number of donor-sourced coordinates is $1+(d-1)CR$; it is not necessarily the number of changed values. At $CR=0$ only the forced coordinate uses the donor, and at $CR=1$ every coordinate does. For $d=1$, crossover rate does not affect the chosen value.
flowchart TD
A["Validate bounds and copy population"] --> B{"Budget covers initialization?"}
B -->|No| Z["Return no evaluated point"]
B -->|Yes| C["Evaluate population and archive best"]
C --> D{"Generation limit reached?"}
D -->|Yes| Y["Return completed population and archive"]
D -->|No| E{"Budget covers N trials?"}
E -->|No| Y
E -->|Yes| F["Begin separate next population"]
F --> H["Choose three distinct old donors excluding target"]
H --> I["Mutate, clip, and cross with forced coordinate"]
I --> J{"Trial objective no worse than target?"}
J -->|Yes| K["Store trial and update archive if better"]
J -->|No| L["Store unchanged target and objective"]
K --> M{"More targets?"}
L --> M
M -->|Yes| H
M -->|No| N["Commit generation and record snapshot"]
N --> D
Worked Example with a Full Trace
Use the box $[-5,5]^2$, center $(1,-1)$, population size $N=4$, seed $3$, scale $F=0.5$, crossover threshold $m=500$, generation limit $G=2$, and budget $B=100$. Initialize in this exact order:
| Slot | Point | Objective |
|---|---|---|
| $0$ | $(-4,3)$ | $25+16=41$ |
| $1$ | $(-1,-4)$ | $4+9=13$ |
| $2$ | $(3,2)$ | $4+9=13$ |
| $3$ | $(5,-2)$ | $16+1=17$ |
The first tied minimum, slot $1$, initializes the archive. The portable generator uses
$$z_{k+1}=48271z_k\bmod2147483647,\qquad z_0=3.$$
Raw states lie in $1,\ldots,2147483646$. To draw an integer below bound $b$, set $R=2147483646$, let $v=z-1$, and accept only if $v\lt R-(R\bmod b)$; return $v\bmod b$ after acceptance and retry otherwise. Under a uniform raw-input model this avoids modulo bias. The seeded generator itself is deterministic and does not supply independent mathematical randomness.
For each target the mapped draw order is three donor ranks, the forced coordinate, and two crossover draws. Even the last donor rank, whose bound is $1$ when $N=4$, consumes a draw. For example, the first raw state is $144813$, and $(144813-1)\bmod3=2$, selecting the third eligible index for target $0$: donor $3$. The donor-rank bounds are $3,2,1$; the forced-coordinate bound is $2$; crossover bounds are $1000$.
The table records every proposal and selection. In a/b/c, $a$ is the base and $b-c$ defines the difference. j is the forced zero-based coordinate. Targets, donor vectors, and incumbent scores always come from the preceding completed generation.
| Gen / target | Donors a/b/c | Forced j / crossover draws | Raw mutant → repaired | Trial / objective | Old objective / decision |
|---|---|---|---|---|---|
| $1/0$ | 3,2,1 | $0$ / 828,48 | (7,1) → (5,1) | (5,1) / $20$ | $41$ / Accept |
| $1/1$ | 2,3,0 | $0$ / 112,973 | (7.5,-0.5) → (5,-0.5) | (5,-4) / $25$ | $13$ / Reject |
| $1/2$ | 1,0,3 | $1$ / 612,87 | (-5.5,-1.5) → (-5,-1.5) | (3,-1.5) / $4.25$ | $13$ / Accept |
| $1/3$ | 0,1,2 | $1$ / 445,613 | (-6,0) → (-5,0) | (-5,0) / $37$ | $17$ / Reject |
| $2/0$ | 1,3,2 | $1$ / 330,40 | (0,-4.25) → (0,-4.25) | (0,-4.25) / $11.5625$ | $20$ / Accept |
| $2/1$ | 0,2,3 | $0$ / 160,806 | (4,1.25) → (4,1.25) | (4,-4) / $18$ | $13$ / Reject |
| $2/2$ | 0,3,1 | $0$ / 138,138 | (8,2) → (5,2) | (5,2) / $25$ | $4.25$ / Reject |
| $2/3$ | 1,0,2 | $0$ / 312,903 | (0,-2.75) → (0,-2.75) | (0,-2) / $2$ | $17$ / Accept |
For the first row, $\mathbf x_2-\mathbf x_1=(4,6)$, so the raw mutant is $(5,-2)+0.5(4,6)=(7,1)$. Clip it to $(5,1)$. Coordinate $0$ is forced even though draw $828$ is not below $500$; coordinate $1$ uses the donor because draw $48$ is below $500$. The trial objective is $20$, so it replaces target objective $41$ while the archive remains $13$.
In generation $1$, target $2$ has raw mutant $(-5.5,-1.5)$, clipped to $(-5,-1.5)$. Its first crossover draw $612$ retains the target's first coordinate $3$, so the clipped $-5$ is not used. The final trial $(3,-1.5)$ has objective $4.25$ and becomes the archive. Generation $1$ still uses old slot $0=(-4,3)$ when forming its later trials; the accepted $(5,1)$ is not available as a donor until generation $2$.
Every mapped random choice and each child-end generator state is recorded here:
| Gen / target | Donor ranks | Forced j | Crossover draws | End state / cumulative raw draws |
|---|---|---|---|---|
| $1/0$ | 2,1,0 | $0$ | 828,48 | $1222067049/6$ |
| $1/1$ | 1,1,0 | $0$ | 112,973 | $1462800974/12$ |
| $1/2$ | 1,0,0 | $1$ | 612,87 | $2115816088/18$ |
| $1/3$ | 0,0,0 | $1$ | 445,613 | $2059428614/24$ |
| $2/0$ | 0,1,0 | $1$ | 330,40 | $1894249041/30$ |
| $2/1$ | 0,0,0 | $0$ | 160,806 | $793180807/36$ |
| $2/2$ | 0,1,0 | $0$ | 138,138 | $637327139/42$ |
| $2/3$ | 1,0,0 | $0$ | 312,903 | $1236041904/48$ |
There are no rejection retries in this sample. Each trial consumes $3+1+2=6$ raw draws, so each generation consumes $24$ and both generations consume $48$. The accepted populations are:
| Generation | Ordered population | Objectives by slot | Archive / objective | Evaluations / raw draws |
|---|---|---|---|---|
| $0$ | (-4,3),(-1,-4),(3,2),(5,-2) | 41,13,13,17 | (-1,-4) / $13$ | $4/0$ |
| $1$ | (5,1),(-1,-4),(3,-1.5),(5,-2) | 20,13,4.25,17 | (3,-1.5) / $4.25$ | $8/24$ |
| $2$ | (0,-4.25),(-1,-4),(3,-1.5),(0,-2) | 11.5625,13,4.25,2 | (0,-2) / $2$ | $12/48$ |
The final result is GEN_LIMIT, best $(0,-2)$ with objective $2$, $12$ evaluations, $8$ trials, $4$ acceptances, $4$ strict target improvements, $4$ accepted point changes, $5$ clipped raw coordinates, and final generator state $1236041904$. The archive is consistent with every evaluated value, yet remains above the analytic minimum $0$.
Trace It by Hand
Predict the final update: generation $2$, target $3=(5,-2)$ with objective $17$. The old population is [(5,1),(-1,-4),(3,-1.5),(5,-2)]. Donors are $(a,b,c)=(1,0,2)$, the forced coordinate is $0$, and crossover draws are $(312,903)$. Use $F=0.5$ and $m=500$.
Reveal: the difference is $(2,2.5)$, giving mutant $(-1,-4)+0.5(2,2.5)=(0,-2.75)$. It is already feasible. The first coordinate comes from the mutant; the second retains target value $-2$ because draw $903$ is above the threshold and it is not forced. Trial $(0,-2)$ has objective $1+1=2$, so it replaces objective $17$ and improves the archive from $4.25$ to $2$.
Implementation in Python, C++, and Java
The three panels are complete programs implementing the same policy. Use Python $3.10+$, C++17, or Java $17+$. For reproducible C++ floating-point decisions, compile with g++ -std=c++17 -O2 -ffp-contract=off main.cpp -o de and avoid fast-math transformations. Save the Java panel as Main.java.
Inputs are an ordered $N\times d$ initial population; bound and center vectors of length $d$; a seed in $1,\ldots,2147483646$; $0\le G\le200$; finite $0\lt F\le2$; integer $0\le m\le1000$; and $0\le B\le10^6$. Require $1\le d\le16$, $4\le N\le32$, finite bounds and center coordinates of magnitude at most $10^6$, ordered bounds, and finite initial points inside their box. Equal lower and upper bounds are allowed. The center may lie outside the box. Python accepts bounded integer or float coordinates but rejects booleans; integer control parameters must be actual integers. Java arrays must be non-null, and Python inputs must be ordinary finite sequences.
Validation occurs before searching. The numeric limits keep raw differential coordinates and the sphere's squared differences finite. Operations are performed in binary64 in a fixed order: subtract, multiply by $F$, add the base; for the objective, subtract the center, square, and accumulate coordinates left to right. The test corpus compared full binary64 traces across languages; this does not make general floating-point arithmetic exact. Tiny differences or products can round to zero, and close real-valued objectives can compare equal.
Result contains status, has_value, best-ever vector and score, final population and scores, completed generations, evaluations, trials, acceptances, strict target improvements, accepted point changes, clipped coordinates, generator statistics, generation snapshots, and per-trial events. An accepted tie need not count as an improvement or as a changed point. A clipped coordinate is counted even when its donor component is discarded by crossover. Snapshots and result arrays are copied, preserving their correspondence with scores and preventing later mutation from rewriting history.
When $B\lt N$, the result has has_value=false, no population, no archive vector, no history, and zero evaluations and draws; the zero score is a dummy field. A request for zero generations still evaluates the initial population when its budget permits. After initialization, GEN_LIMIT takes precedence when both checks would stop. Budget $7$ can evaluate the four initial points but cannot fund another four trials, so it stops at generation $0$ without consuming any randomness. All trials, including unchanged points, are evaluated; unchanged rejected targets retain cached scores.
The supplied objective is deliberately simple and has a known reference answer. For a general deterministic evaluator, replace sphere, check every objective result is finite before selection, and review its cost, constraints, and numerical behavior. Real DE implementations also need explicit policies for failed, noisy, or time-varying evaluations. The sample does not claim to implement an arbitrary objective callback, nonlinear constraints, polishing, or a library's default settings.
The last sample has a fixed one-dimensional box $[2,2]$ and center $0$. Every trial equals $2$, so all four ties are accepted with no changed points. That case correctly returns the box-constrained optimum $4$; it is separate from the suboptimal collapsed-population counterexample below.
Expected standard output, identical in all three languages:
sphereg=0 best=(-1.000000,-4.000000) f=13.000000 eval=4 draws=0g=1 best=(3.000000,-1.500000) f=4.250000 eval=8 draws=24g=2 best=(0.000000,-2.000000) f=2.000000 eval=12 draws=48status=GEN_LIMIT best=(0.000000,-2.000000):2.000000 generations=2 eval=12 trials=8 accepted=4 improved=4 changed=4 clips=5 draws=48 rejections=0 rng=1236041904budgetg=0 best=(-1.000000,-4.000000) f=13.000000 eval=4 draws=0status=EVAL_LIMIT best=(-1.000000,-4.000000):13.000000 generations=0 eval=4 trials=0 accepted=0 improved=0 changed=0 clips=0 draws=0 rejections=0 rng=3zero-budgetstatus=EVAL_LIMIT best=NONE generations=0 eval=0 trials=0 accepted=0 improved=0 changed=0 clips=0 draws=0 rejections=0 rng=3fixedg=0 best=(2.000000) f=4.000000 eval=4 draws=0g=1 best=(2.000000) f=4.000000 eval=8 draws=20status=GEN_LIMIT best=(2.000000):4.000000 generations=1 eval=8 trials=4 accepted=4 improved=0 changed=0 clips=0 draws=20 rejections=0 rng=1645572783
Complexity, Correctness, and Invariants
Let $g$ be completed generations. Initialization costs $N$ objective evaluations and every full generation costs another $N$, so for $B\ge N$,
$$g=\min\left(G,\left\lfloor\frac{B-N}{N}\right\rfloor\right),\qquad E=N(g+1),\qquad T=gN,$$
where $T$ is the number of trials. If $B\lt N$, no objective is evaluated. Because this implementation has no early convergence exit, every objective landscape uses the same evaluation count for fixed $N,G,B$. Best, average, and worst solution quality can differ widely even when the work count is identical.
Choosing donors maps ranks past at most three exclusions, so it costs constant work per trial rather than scanning or shuffling all $N$ members. Mutation, clipping, crossover, score evaluation, and trace copying cost $\Theta(d)$ per target for the sphere. If $Q$ raw generator draws include $J$ rejection retries, then
$$Q=T(4+d)+J.$$
With this specific generator and mapped bounds no larger than $1000$, a rejected raw state is followed by an accepted one: a rejected state is $2147483647-k$ for $1\le k\le999$, whose successor $2147483647-48271k$ is outside all these rejection intervals. Thus each mapped draw takes at most two raw draws. Including input validation and copies, best-, average-, and worst-case runtime for this bounded implementation is $\Theta((g+1)Nd)$. Replacing the generator changes that particular retry guarantee; independent uniform raw draws give expected constant mapping cost instead.
For an external evaluator costing $C_f$ per vector, expected search work is $O(NC_f+gN(d+C_f))$, plus input construction and validation. Expensive simulations often dominate variation. No generic polynomial bound follows for the generations needed to approach a global optimum. Runtime to a fixed evaluation budget is a different question from runtime to a requested quality.
Two live populations and their scores use $O(Nd)$ memory. The supplied programs keep full history: snapshots and trial events retain $\Theta((g+1)Nd)$ auxiliary words, including raw mutants, repaired mutants, trials, and crossover draws. Retaining only the current population and archive reduces storage to $O(Nd)$ plus evaluator state. Printing a full high-dimensional trace also has a real cost.
Correctness here means maintaining feasible candidates, valid objective associations, budget accounting, and monotonic progress in the computed deterministic objective. The proof is an induction. Initial points are validated and evaluated. Every donor index exists, differs from the target, and differs from the other donors. Repair places every donor coordinate inside its interval, and crossover chooses between two feasible values, so each trial is feasible. Selection pairs each next vector with the score actually computed for that vector, retaining an old pair on rejection. Finally,
$$f(\mathbf x_{i,g+1})=\min\bigl(f(\mathbf x_{i,g}),f(\mathbf u_{i,g})\bigr)\le f(\mathbf x_{i,g})$$
for every fixed slot $i$. Consequently population minimum, mean, and maximum objective values cannot increase in exact arithmetic over those computed values. Any newly evaluated global record is necessarily accepted, because it is better than every current target; therefore the archive's score equals the population minimum at each completed generation. The archived vector need not still occupy a slot if equal-fitness replacements later change the witnesses. Strict archive updates retain the first evaluated witness of each new record.
Requiring a full generation's evaluation budget before any random draws ensures the result ends with a consistent completed population. None of these facts proves global convergence. For instance, if all population vectors equal a feasible suboptimal point, all differences are zero and this variant remains there forever, regardless of the positive scale or crossover rate.
| Method | Work for a stated budget | Live search storage | Guarantee or useful assumption |
|---|---|---|---|
| DE/rand/1/bin | $O(NC_f+gN(d+C_f))$ expected search work | $O(Nd)$ excluding history | Each target's deterministic score never worsens; no finite global certificate |
| Binary genetic algorithm | $O(NC_f+gN(L+C_f))$ for $L$-bit genomes | $O(NL)$ | Representation-specific recombination; preserving best may require explicit elitism |
| Simulated annealing | $O(K(d+C_f))$ for $K$ proposals with full vector construction | $O(d)$ plus archive | Can accept worse current points; finite-run global optimality is not generic |
| Gradient descent | $O(K(d+C_{\nabla f}))$ for $K$ steps | $O(d)$ | Requires suitable derivatives; convergence claims depend on smoothness, convexity, and step policy |
| Uniform random search | $O(K(d+C_f))$ for $K$ full-box samples | $O(d)$ plus archive | Independent exploration baseline without population differences |
| Tensor grid search | $\Theta(M^d(d+C_f))$ for $M$ samples per coordinate | $O(d)$ plus archive | Exact best on the sampled grid; continuous optimum needs additional reasoning |
Every population vector is feasible and paired with its deterministic computed objective, and each slot's objective can only decrease or tie after greedy replacement. The archive retains the smallest evaluated score, while complete-generation budget checks keep returned state consistent; these preservation guarantees do not certify global optimality.
Variants, Pitfalls, and Optimizations
Donor indices must be distinct and exclude the target. Accidentally selecting $b=c$ makes the differential term zero; allowing repeated indices changes the intended DE/rand/1 policy. Distinct indices can still hold identical vectors, so index correctness alone does not ensure diversity. A donor pool that is too small must be rejected rather than handled by an endless resampling loop.
Force one donor source. Without the forced coordinate, ideal independent crossover takes every coordinate from the target with probability $(1-CR)^d$. At $CR=0$, this happens every time. The forced coordinate prevents that all-target mask, but not a numerically identical trial: a collapsed population or a fixed coordinate can make donor and target values equal.
Use one update policy consistently. Deferred updates read all parents from the old generation and write winners separately. Immediate updates allow later trials to use earlier winners; that is a legitimate different variant but produces different trajectories and affects parallel reproducibility. A best-ever archive may update during this deferred implementation because its values are never used to construct donors. In a best/1 strategy, when the best base becomes available is itself part of the algorithm.
Treat boundary handling as an algorithmic choice. Clipping, reflection, random resampling, and transformed coordinates create different proposal distributions. Clipping can concentrate vectors on box faces and erase variation. It does not enforce a nonlinear feasibility constraint such as a budget or a collision-free route. Specify when repair happens; here it occurs on the mutant before crossover. Do not evaluate an unrepaired point and store its score alongside a repaired point.
Watch population collapse and numerical stagnation. If every vector is $(4,4)$ in the worked box, all donor differences vanish and every trial stays there with objective $34$, although $(1,-1)$ is feasible with objective $0$. Zero spread or unchanged fitness is therefore not an optimality certificate. Very small $F$, cancellation, or floating-point rounding can also suppress steps. Restarts, random immigrants, or adaptation can restore exploration, but they change the method and consume additional evaluations.
Choose the representation and scales deliberately. Bound widths may differ by orders of magnitude. Normalizing coordinates to $[0,1]$ can simplify initialization, tolerances, and variation; return to physical units before evaluating. Axis-wise crossover is not invariant to an arbitrary rotation of the coordinate system, even though differential mutation has a simple vector form. Integer and categorical settings need their own decoding, repair, or an alternative method.
Separate strategy from defaults. best/1 uses the best vector as its base; rand/2 uses two differences and requires enough distinct indices; exponential crossover copies a contiguous run of coordinates rather than making independent coordinate choices. Dithering varies $F$, while adaptive schemes update search parameters from observed outcomes. The SciPy reference documents several strategies and immediate/deferred updating. Its defaults, boundary policy, stopping criteria, random source, and optional polishing differ from this teaching implementation, so matching a strategy name alone does not imply matching traces.
Assess evaluations and uncertainty. Use several independent seeds, compare at equal objective budgets, validate final candidates independently, and include random search and a suitable local method as baselines. If fitness is noisy, accepting one apparently better score and caching it indefinitely can be misleading; use a stated reevaluation or statistical policy. Parallel evaluation fits deferred generations well, but assign randomness and results by target index so completion order does not silently change the run. A smooth convex sphere is a verification case, not evidence that DE is the most efficient solver for that objective.
Use three distinct non-target donor indices, guarantee one donor-sourced coordinate, and keep parent generations and objective caches consistent. Boundary repair, update timing, diversity loss, and finite precision can materially change the search; a small population spread or a stalled best value never establishes global optimality on its own.
Practice Problems and Real-World Use
Begin with the shifted sphere and its analytic reference answer. For a box-constrained sphere with arbitrary center, the exact real-valued minimizer clips each center coordinate into its interval; this follows because the squared-coordinate terms minimize independently. Compare that reference point with the algorithm's returned point and objective, allowing for finite-precision evaluation. Then try nonseparable or multimodal functions where the known answer remains available for checking.
In a practical study, document bounds and units, initialization, population size, donor strategy, differential scale, crossover rule, repair policy, update timing, stopping limits, random streams, evaluator failures, and any post-search polishing. Report objective evaluations and variation across seeds as well as the best result. A continuous optimizer can propose useful calibration or engineering parameters, but the evaluator and feasibility checks must reflect the real system.
| Problem or application | Exercise | Main idea tested | Difficulty |
|---|---|---|---|
| Shifted sphere in a box | Reproduce the trace and compare with clipped center | Mutation arithmetic, objective caching, and analytic verification | Easy |
| Forced-coordinate laboratory | Try $CR=0$, $CR=1$, and $d=1$ | Source masks versus actual changed values | Easy |
| Boundary and collapse tests | Start near faces or make every vector identical | Clipping bias, lost diversity, and false convergence | Medium |
| Rosenbrock benchmark | Compare coordinate scaling and initial populations | Coupled variables and narrow curved valleys | Medium |
| Rastrigin benchmark | Compare equal-budget repeated-seed runs | Multimodality, exploration, and empirical success rates | Medium |
| Model or sensor calibration | Fit bounded continuous parameters to residual data | Evaluator cost, validation data, and meaningful parameter bounds | Hard |
| Engineering design simulation | Decode candidate parameters and verify constraints | Feasibility policies, failed evaluations, and independent validation | Hard |
| Deferred parallel experiment | Evaluate targets concurrently with fixed random streams | Reproducibility, result ordering, and evaluation accounting | Advanced |