Differential Evolution
Build Differential Evolution from scratch: differential mutation, binomial crossover, greedy selection, bounds, and budget-matched benchmarks on Rastrigin.
Machine Learning from ScratchAll chapters Differential EvolutionFull chapter · Sections in this chapter1 / 10 ↓
Reading tools Definitions, links & notes
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Differential Evolution
A simulation may return a cost for each proposed design without providing a usable gradient. If several basins contain good designs, repeatedly improving one starting point can miss a better region. Gradient Descent and Numerical Optimization gave us local updates, Local Search and Simulated Annealing introduced stochastic search, and Genetic Algorithms used a population of discrete candidates. We now build a population search directly for continuous decision variables.
Differential Evolution (DE) forms a perturbation from the difference between two population members, scales it by a factor , and adds it to a third member. That difference supplies a direction and length from the current population geometry. It contains no objective values and is not a gradient estimate.
Step lengths depend on the population: widely separated donors can produce long differences, and a contracting population produces shorter differences. This self-scaling mechanism needs no explicit cooling schedule, but contraction can occur around a poor solution as well as a good one.
This chapter develops DE/rand/1/bin from differential mutation, binomial crossover, greedy one-to-one replacement, and box-bound handling. We compare it with uniform random search and bounded multistart local optimization on a small synthetic objective. Every method receives the same number of candidate evaluations, including initialization. That controls evaluation effort; it does not imply equal runtime or a universal ranking.
Advantages
DE obtains its perturbation scale from population differences rather than requiring a separate fixed standard deviation for every decision variable. The factor , crossover rate , population size, initialization, and bounds still require choices. We will test some of those choices instead of assuming that a single setting works for every objective.
DE only needs objective values for proposed real-valued vectors, so its evolutionary phase does not require derivatives. This is useful for simulation outputs or discontinuous losses. A population can retain candidates in several regions at once, although it need not cover every basin and can lose that diversity.
In a deferred generation, every trial is formed from the same old population. Independent objective evaluations can then be batched or distributed before selection. The primitive implementation below evaluates trials serially so that each candidate has an explicit budget index. At a generation boundary, the population, its objective values, and the archived best retain the candidate history used by the next sweep. Reproducing that sweep also requires the objective, bounds, operator settings, remaining budget, and RNG state.
Disadvantages
A population of size requires candidate evaluations before mutation begins, and a complete generation requires another . This can be expensive when one evaluation runs a long simulation. Derivative-based local methods remain useful baselines when derivatives are available; their cost and result depend on the objective, starting point, and stopping rule.
A finite DE run does not certify a global optimum. Its population can collapse around a suboptimal region and leave little variation for further steps. A local polish may refine its best candidate, but those additional evaluations must be counted. Population spread and best-so-far cost help diagnose a run; neither is an optimality certificate.
Coordinate-wise mutation differences scale with the coordinates themselves. With matched bounds and initial populations, changing units by a diagonal affine transformation does not inherently freeze a small-scale coordinate. Numerical precision, stopping tolerances, or a local solver can still make units matter. Binomial crossover selects coordinate axes, so rotation is a different issue: rotating the variables can change the composite search.
Formula
We need a representation and initialization, a mutation rule, a crossover mask, and a selection rule. The standard DE/rand/1/bin construction below follows Storn and Price (1997). We will distinguish the rule itself from the particular choices made in our implementation.
Continuous populations and differential mutation
We start with the object we are manipulating. The population at generation is an indexed collection of real vectors for , where is the number of decision variables and is the scalar objective we want to minimize.
Before we can mutate anything we need somewhere to start, so the first ingredient is initialization. Drawing each coordinate uniformly inside the box constraints gives
where:
- : the -th coordinate of population member at generation
- : lower bound of the box constraint for coordinate
- : upper bound of the box constraint for coordinate
- : an independent draw from the uniform distribution on
Uniform initialization uses only the stated box bounds; it does not ensure that every region is sampled. The minimum population size for this variant is , because each target needs three mutually distinct donor indices that also exclude the target.
With a population in hand, the central question is how to generate a new candidate. Gradient descent would answer with ; simulated annealing would answer with a perturbation whose size we had to choose. DE answers differently: it takes a displacement that the population already contains. For each target , pick three indices uniformly at random, mutually distinct and all different from , and form the mutant
where:
- : the mutant vector for target at generation
- : the base vector, a randomly selected population member
- : two other distinct population members whose difference forms the mutation direction
- : the mutation factor scaling the difference vector
This single line contains the whole idea, so it is worth reading slowly.
The quantity in parentheses is a difference of two population members. In coordinate it equals , so its sign tells us which of the two members is larger in that coordinate, and its magnitude is how far apart they are. The full vector is therefore a displacement from to , a direction together with a length. Multiplying by rescales that displacement. Adding the scaled displacement to a third member places the mutant at the point we would reach by standing at and stepping times the way from toward .
The mutation factor multiplies the donor difference. Our implementation accepts and uses in the main comparison. At the mutant is just the base donor. Increasing lengthens a fixed nonzero difference; it does not guarantee a better proposal. The parameter sweep later tests several values under a fixed budget.
We can quantify self-scaling under an idealized model. Suppose two donor vectors are independent draws from a distribution with mean and covariance . This assumption is a model for the calculation, not a claim that evolved population members remain independent. The expected difference is zero:
where:
- : independent draws from the population distribution
- : the mean of that distribution
Thus the idealized difference has zero mean. This describes a displacement around its base donor, not an unbiased estimate of a direction that reduces the objective. Its covariance is:
where:
- : independent draws from the population distribution
- : the covariance matrix of that distribution
Under this independent-draw model, coordinate of the scaled difference has standard deviation , where is the distribution standard deviation. This is not exactly twice the empirical covariance of a fixed finite donor pool sampled without replacement. For a pool of eligible vectors with covariance computed using divisor , the difference covariance is times that covariance. The model explains why population spread influences step size without promising contraction or improvement.
In matrix notation the same generation can be written compactly. Let be the matrix whose -th row is , and let be index vectors of length that encode, row by row, which members were drawn as donors:
Here selects rows. For each target row, its three selected donor indices must be distinct and exclude that target. With scalar , the expression is an ordinary scalar-matrix product. It describes a whole generation compactly; our teaching implementation uses a loop over targets while freezing the donor population. SciPy also supports a mutation interval that samples one per generation, as discussed below.
The geometry can be checked on explicit donor coordinates. The dashed arrow shows the unscaled difference; the solid arrow attaches its scaled copy to the base donor. A later crossover chooses coordinates from the mutant and target, so a trial can be a corner of their axis-aligned rectangle rather than a point on the line segment joining them.
PythonIn [3]Expand
geometry_donors = np.array([[-1.5, -0.5], [1.5, 1.5], [-0.5, 1.0]])
geometry_F = 0.7
geometry_difference = geometry_donors[1] - geometry_donors[2]
geometry_mutant = geometry_donors[0] + geometry_F * geometry_differenceVisualizationOut [4]Expand

Donor sampling without replacement and exclusion of the target are defining conditions of this variant. DE/rand/1/bin names a random base donor, one difference vector, and binomial crossover. Other strategies change these ingredients; their performance must be checked on the intended problem rather than inferred from the name.
Crossover and greedy selection
A mutant contains donor-derived values in every coordinate. Binomial crossover chooses between those values and the target values, coordinate by coordinate. It does not inspect which coordinate choices improve the objective; only the completed trial is evaluated:
where:
- : the -th coordinate of the trial vector for target
- : the -th coordinate of the mutant vector
- : the -th coordinate of the target vector
- : an independent draw from the uniform distribution on
- : the crossover rate in
- : a uniformly drawn index from that is always inherited from the mutant
For a non-forced coordinate, is its probability of being selected from the mutant. At every coordinate is selected from the mutant; at exactly the forced coordinate is selected. The forced index is drawn independently and uniformly for each target.
The forced coordinate guarantees donor inheritance, not numerical novelty. If target and mutant coordinates coincide, or repair maps them to the same values, the trial can equal its target. Duplicate trials still consume evaluations. The random-threshold and forced-index events can overlap: their union probability for one coordinate is . Equivalently, the forced coordinate contributes one selected entry, and the other coordinates each contribute in expectation:
Here is one when the mask selects the mutant coordinate and zero otherwise. For , the expected selected counts are at and at . These count selected entries, not accidental numerical equalities. For , a smaller selects fewer mutant coordinates in expectation. At , the forced coordinate keeps the selected count at one for every . Coordinate dependence alone does not supply a universal best setting.
In matrix form, let be the zero-one mask with when coordinate of row is inherited from the mutant, including the forced column. Then
where:
- : the matrix of trial vectors
- : the binary mask selecting mutant coordinates
- : the matrix of mutant vectors
- : the matrix of target vectors
- : elementwise (Hadamard) product
- : the all-ones matrix, so selects the target coordinates
Writing it this way makes clear that crossover is a selection of entries, not an arithmetic blend: no new coordinate values are created, only copied.
Selection is then greedy and one-to-one. Each trial competes only with its own target:
where:
- : the target vector for slot at the next generation
- : the trial vector for slot
- : the current target vector
- : the objective function to minimize
This rule compares a trial only with its own target. Our <= convention accepts ties; a strictly worse trial is discarded. For a fixed deterministic objective, every retained slot value is non-increasing, so the population minimum is non-increasing too. This property does not ensure diversity or a global optimum.
In a deferred generation, every donor and target comes from generation , and the accepted replacements are installed together. Our code freezes that population explicitly. An immediate variant allows later proposals in a sweep to use earlier replacements. With fixed update order and random-number stream either can be reproducible, but the trajectories can differ; there is no general faster-convergence guarantee.
Bounds, constraints, and stopping criteria
Initialization respects the box, but a mutant can leave it. This can happen even for , depending on the base and donor difference. We will clip mutants before crossover; other choices change the proposal distribution:
- Clipping maps a coordinate to . It enforces its interval but can concentrate proposals on the boundaries. It does not enforce coupled non-box constraints.
- Reflection sends a lower-bound violation to or an upper-bound violation to . Large overshoots need repeated reflection or an equivalent folding rule.
- Reinitialization samples a violating coordinate again inside its interval, discarding that coordinate's differential step. These are different search choices, not interchangeable guarantees.
For non-box constraints, a penalty, a repair procedure, or a feasible reparametrization needs its own validation. A penalty alone does not certify feasibility. Count every objective evaluation performed while repairing or comparing candidates, and validate every reported solution against the original constraints.
Our comparison stops at a fixed candidate-evaluation budget. Population spread, changes in the incumbent, or a generation limit can also be stopping rules without knowing the optimum. A tolerance on objective spread measures agreement among current candidates; it is not a test of distance to the global optimum. Every stopping rule and its evaluation cost should be reported.
Mathematical Properties
For a fixed deterministic objective, let . Each selected slot either retains its old value or takes a value no larger, so and therefore . An archived minimum over all evaluated candidates is also non-increasing. Neither statement implies strict progress; with noisy evaluations it applies to the stored observations, not necessarily to true expected costs.
The mutation operator is translation and rotation equivariant: if we rotate every population member by an orthogonal matrix , the mutant rotates by as well, because
Binomial crossover is not rotation equivariant because it chooses entries in a fixed coordinate basis. Diagonal affine changes of units are different: for with diagonal having positive entries, mutation gives . The same crossover mask commutes with this coordinate-wise transformation. With matching bounds, initialization, and arithmetic, this preserves the transformed search; it does not imply rotation invariance or immunity to numerical tolerances.
These update rules provide no finite-budget global-optimum guarantee. A small spread can explain short mutation steps, but the population may have contracted in the wrong basin. We do not assume a geometric convergence rate or identify this mechanism with an annealing theorem.
Example
We work through half a generation of DE/rand/1/bin by hand on a two-variable problem with a known answer, so that every step of the machinery above becomes a concrete number. The objective is
whose unique minimum is at . The box is , the mutation factor is , and the crossover rate is . Our population of four members, with their objective values, is:
Let us verify one of these values to fix the arithmetic. For we get and , so . Repeating: , , and . So the current best is with .
Take the first target and donors , , . The difference vector is
which is a step of two units left and two units up, a displacement the population happens to contain. Scaling by gives , and adding it to gives the mutant
Now crossover with . Suppose the uniform draws are for the coordinate and for the coordinate, and the forced index is , the coordinate. For : , so we take the mutant value (and it would have been forced anyway). For : and , so we keep the target value . The trial is
Since , the greedy rule accepts and slot 1 becomes . The mutation displacement points left from its base to the mutant . Relative to the target , however, crossover moves rightward to and leaves unchanged. The squared error drops from to , accounting for the entire cost decrease. A donor-difference direction must be read from its base, not from the target.
Now take the second target with donors , , , using generation-0 values for all members. The difference vector is now , scaled to , and the mutant is
With draws and and forced index , the coordinate is inherited from the mutant () and the coordinate is kept from the target (), giving . Its value is
and , so the trial is discarded and survives unchanged. This is the greedy rule doing its job: the rejected trial has , whereas has ; the trial's inherited coordinate of is farther from the optimum than the target's , so changing alone left the trial worse than the target.
Both trials cost an objective evaluation, including the rejected trial. A rejection means this completed crossover trial was worse than its target; it does not isolate the mutation direction as the cause. Both hand calculations use generation-0 donors, including the old value of slot 1 after its replacement has been proposed. That is the deferred convention.
We can check the arithmetic with a helper that takes explicit donor vectors, uniform draws, and the forced-coordinate index. We compute the four initial objective values and two trial values once, then reuse those values for display and plotting. These six hand-example candidate evaluations are separate from the later Rastrigin search budgets.
PythonIn [5]Expand
def f_example(X):
"""Vectorized objective f(x, y) = (x - 1)^2 + (y + 2)^2 for a 2D array of points."""
X = np.atleast_2d(X)
return (X[:, 0] - 1.0) ** 2 + (X[:, 1] + 2.0) ** 2
def de_trial_manual(target, r1, r2, r3, F, CR, j_rand, rand):
"""One DE/rand/1/bin trial with all randomness supplied by the caller (1-based j_rand)."""
mutant = r1 + F * (r2 - r3)
mask = rand < CR
mask[j_rand - 1] = True
trial = np.where(mask, mutant, target)
return mutant, mask, trial
pop0 = np.array(
[
[-3.0, 2.0], # x1
[4.0, 1.0], # x2
[0.0, -4.0], # x3
[2.0, 3.0],
]
) # x4
f0 = f_example(pop0)
F, CR = 0.8, 0.5
v1, m1, u1 = de_trial_manual(
pop0[0], pop0[2], pop0[3], pop0[1], F, CR, 1, np.array([0.3, 0.9])
)
v3, m3, u3 = de_trial_manual(
pop0[2], pop0[0], pop0[1], pop0[3], F, CR, 1, np.array([0.2, 0.8])
)
trial_values = f_example(np.vstack([u1, u3]))
accepted_values = trial_values <= f0[[0, 2]]ConsoleOut [6]Expand
objective values of generation 0: [32. 18. 5. 26.] target i=1 mutant : [-1.6 -2.4] mask : [ True False] trial : [-1.6 2. ] f(trial) : 22.7600 f(target) = 32.0000 -> accept target i=3 mutant : [-1.4 0.4] mask : [ True False] trial : [-1.4 -4. ] f(trial) : 9.7600 f(target) = 5.0000 -> reject
The displayed values match the hand calculation: the first trial has cost and is accepted against ; the second trial has cost and is rejected against . If values disagree, check the arithmetic, formulas, indexing, and code together. Agreement on these two updates is a focused consistency check, not a proof for every input. The masks show which coordinates are selected from the mutant.
The printed values already give us the coordinates of both updates; plotting them makes the selection outcome visible. The fixed example uses hand-supplied random draws, so every point is the deterministic result of the hand-chosen donors and crossover draws.
The preparation cell collects the existing targets, mutants, trials, and stored selection outcomes for plotting. It introduces no new random draws or objective evaluations.
VisualizationOut [8]Expand

Implementation in NumPy and SciPy
The primitive implementation uses NumPy arrays for the population and objective values. We compare its result with scipy.optimize.differential_evolution, then evaluate it alongside uniform random search and bounded multistart Nelder-Mead under the stated budget.
The two-dimensional Rastrigin objective adds a cosine ripple to a quadratic bowl:
where:
- : the number of decision variables
- : the -th coordinate of
Rewrite the objective as . Every summand is nonnegative, and zero is attained only at the origin, which lies inside our box. This proves the known global minimum used to interpret this synthetic example. The ripples create local minima with different objective values. This smooth benchmark illustrates search behavior; it does not establish performance on arbitrary black-box or non-differentiable objectives.
PythonIn [9]Expand
def rastrigin(X):
"""Vectorized Rastrigin function; accepts a (n, d) array and returns (n,) values."""
X = np.atleast_2d(X)
return np.sum(X**2 + 10.0 * (1.0 - np.cos(2.0 * np.pi * X)), axis=1)
BOUNDS = np.array([[-5.12, 5.12], [-5.12, 5.12]])
def de_rand_1_bin(
f, bounds, popsize=20, F=0.7, CR=0.9, max_nfev=3000, seed=0, record=False
):
"""Synchronous DE/rand/1/bin; count candidate evaluations, including initialization."""
bounds = np.asarray(bounds, dtype=float)
if (
bounds.ndim != 2
or bounds.shape[1] != 2
or not np.isfinite(bounds).all()
):
raise ValueError("bounds must be a finite (dimension, 2) array")
lo, hi = bounds[:, 0], bounds[:, 1]
if lo.size == 0 or np.any(lo >= hi):
raise ValueError("each lower bound must be less than its upper bound")
if not isinstance(popsize, (int, np.integer)) or popsize < 4:
raise ValueError("DE/rand/1 requires at least four population members")
if not isinstance(max_nfev, (int, np.integer)) or max_nfev < popsize:
raise ValueError("the budget must cover the initial population")
if not 0 <= F < 2 or not 0 <= CR <= 1:
raise ValueError("require 0 <= F < 2 and 0 <= CR <= 1")
rng = np.random.default_rng(seed)
d = lo.size
pop = rng.uniform(lo, hi, size=(popsize, d))
fit = np.asarray(f(pop), dtype=float)
if fit.shape != (popsize,) or not np.isfinite(fit).all():
raise ValueError(
"the batch objective must return one finite value per member"
)
nfev = popsize
best = int(np.argmin(fit))
best_x, best_f = pop[best].copy(), float(fit[best])
trace_best = np.minimum.accumulate(fit).tolist()
snaps = [pop.copy()] if record else None
snap_evaluations = [nfev] if record else None
while nfev < max_nfev:
# Every donor and target comes from the same frozen generation.
old_pop, old_fit = pop.copy(), fit.copy()
next_pop, next_fit = old_pop.copy(), old_fit.copy()
for i in range(popsize):
if nfev >= max_nfev:
break # A partial final generation spends exactly the remaining budget.
pool = np.delete(np.arange(popsize), i)
r1, r2, r3 = rng.choice(pool, size=3, replace=False)
mutant = np.clip(
old_pop[r1] + F * (old_pop[r2] - old_pop[r3]), lo, hi
)
mask = rng.random(d) < CR
mask[rng.integers(d)] = True
trial = np.where(mask, mutant, old_pop[i])
trial_values = np.asarray(f(trial[None, :]), dtype=float)
if trial_values.shape != (1,) or not np.isfinite(trial_values[0]):
raise ValueError(
"the objective must return one finite trial value"
)
ft = float(trial_values[0])
nfev += 1 # Rejected and numerically duplicate trials also cost one evaluation.
if ft <= old_fit[i]:
next_pop[i], next_fit[i] = trial, ft
if ft < best_f:
best_f, best_x = ft, trial.copy()
trace_best.append(best_f)
pop, fit = next_pop, next_fit
if record:
snaps.append(pop.copy())
snap_evaluations.append(nfev)
return dict(
best_x=best_x,
best_f=best_f,
nfev=nfev,
trace_nfev=np.arange(1, nfev + 1),
trace_best=np.asarray(trace_best),
snaps=snaps,
snap_evaluations=snap_evaluations,
population=pop,
)The old population is copied before each sweep, so every trial uses the same generation of donors and targets. Every initialized individual and every trial contributes one candidate evaluation, including ties, repeats, and rejected trials. The final sweep can be partial: a budget of 21 with a population of 20 permits one trial, not a second full population. The trace records the incumbent after every candidate, including initialization. We retain the best candidate separately from population membership.
Now we run it, and also run the SciPy reference with a comparable budget.
ConsoleOut [11]Expand
our DE/rand/1/bin : best f = 3.346e-15 at [ 0. -0.] using 3000 evaluations scipy reference : best f = 9.950e-01 at [0. 0.995] using 3000 evaluations difference in best value: 9.95e-01
The reference uses the same mutation factor, crossover rate, uniform initialization scheme, and deferred convention, but its random draws need not match our implementation. In this two-variable setup, SciPy's popsize=10 means 20 members; 149 generations plus initialization allow at most scalar evaluations when polishing is disabled. Zero tolerances can still stop at exactly zero population-energy spread. We display the actual counts and results. A single agreement or disagreement cannot prove an implementation correct; the worked updates, budget tests, and feasibility checks are separate checks. These API conventions are documented in the version-matched SciPy source.
Key Parameters
These are the choices used in the chapter. Their effects depend on the objective and the evaluation budget:
popsize: our function takes the actual number of members, at least four. Initialization spends that many evaluations; a larger population leaves fewer generations under a fixed budget. SciPy instead uses a dimension-dependent multiplier unless an explicit initial population is supplied.F: scales a donor difference. We use 0.7 and test other values below. A fixed does not imply a fixed absolute step length. SciPy's mutation interval samples one value per generation, not per mutant.CR: controls non-forced coordinate inheritance. We use 0.9, while forcing one selected coordinate at every setting. The sweep is an example rather than a universal tuning rule.max_nfev: includes all initialization and trial evaluations. Our function rejects a budget smaller than the initial population and stops partway through a sweep when necessary.strategy: this chapter implementsrand1bin; alternatives change donor or crossover rules and require their own evaluation.
Key Methods
The reference and teaching functions have different purposes:
differential_evolution(): Runs the full DE loop, including initialization, mutation, crossover, and selection. Use it when a reference implementation or built-in bound and polishing options are needed.de_rand_1_bin(): The chapter's NumPy implementation, useful for inspecting the four ingredients and for budget-matched experiments.
Visualizing Differential Evolution
When we call scipy.optimize.differential_evolution, a few of its arguments map onto the mechanics we derived:
init="random"uses random box initialization. SciPy also offers Latin hypercube initialization or an explicit initial population.polish=Falsedisables the additional local-polishing phase in our reference comparison. If polishing is enabled, count its evaluations as well.tolandatoltest population-energy spread against , not against the best value or distance to the optimum. Setting both to zero still allows termination when the spread is exactly zero.updating="deferred"uses the old population for the generation. Parallel or vectorized evaluation requires this convention.rngcontrols SciPy's random-number generator. Reproducibility also depends on the runtime, objective, and execution settings.
For seeds 0 through 19, all three methods evaluate exactly 3000 candidates. A vectorized objective call evaluating 20 rows counts as 20 candidate evaluations, not one Python invocation. The Nelder-Mead baseline uses the same box bounds, limits each local restart to at most 200 evaluations, and keeps a global incumbent across restarts. This restart cap is a disclosed baseline choice, not a tuned guarantee.
VisualizationOut [13]Expand

These are median incumbent traces on one synthetic two-dimensional objective. Read the curve together with the final-value distribution: an early improvement in a median trace does not mean every seed reached that value. The random-search baseline continues drawing across the box, while the restarted local baseline divides its budget among local searches. Their relative outcomes describe this particular box, objective, restart cap, and budget.
ConsoleOut [14]Expand
method min/max evaluations median best worst best success rate (<1e-6)
DE/rand/1/bin 3000/3000 1.228e-20 9.950e-01 9.000e-01
Random search 3000/3000 1.118e+00 2.041e+00 0.000e+00
Multistart Nelder-Mead 3000/3000 9.950e-01 3.980e+00 5.000e-02The table reports actual per-seed evaluation counts, median and worst observed final values, and the fraction below the prespecified threshold. That threshold means a small objective value on this example, not an optimality certificate. A worst observed value is the maximum among these seeds, not a bound on future runs.
Individual final values expose the variation hidden by a median. The plot uses the same twenty seeds and 3000-candidate budget as the comparison. Values below the display floor share a plotted level even when their computed values differ.
VisualizationOut [16]Expand

The next four snapshots show one recorded seed at initialization, after ten and thirty generations, and at the end. They use the same axes so contraction remains visible; a small display margin outside the contour box prevents boundary markers from being clipped. This is one trajectory, not a claim that every seed follows the same basins or timing. The 300-by-300 contour grid requires 90,000 visualization-only candidate evaluations. Those values are not available to any search method and are separate from every search budget.
VisualizationsOut [18]Expand




Compare the four snapshots with the recorded evaluation indices. If the points concentrate, their donor differences become shorter without changing . The contour background supplies objective context, but the snapshots alone do not identify which proposal caused an improvement or establish a general convergence rate.
The final heatmap varies and at a fixed budget of 1500 candidates, including initialization. Every cell is a median over seeds 0 through 4 on the same synthetic objective. It shows local sensitivity to these choices; it does not estimate a universal best setting.
VisualizationOut [20]Expand

The cells summarize measured outcomes, not an assumed explanation for them. A small median difference across five seeds is weak evidence for a ranking. A poor result can reflect the starting population, finite budget, bounds, and operator settings together; this sweep does not isolate a universal causal rule.
Practical Implications
DE is a candidate when continuous decisions can be scored but derivatives are unavailable or inconvenient, provided a population fits the evaluation budget. The example here uses finite deterministic values and box constraints. An application with solver failures, non-box feasibility conditions, or expensive evaluations needs explicit handling of those cases and a comparison with simpler methods.
Before choosing parameters, make the representation and evaluation protocol explicit:
- Choose meaningful bounds and units. Normalizing variables can simplify tolerance choices and baseline step sizes, even though matched diagonal scaling does not inherently change differential mutation and coordinate crossover.
- Validate any reparametrization in the original decision space. For strictly positive variables, use log coordinates with the inverse map . For a positive simplex vector of dimension , softmax uses . For finite real , these softmax entries are positive and sum to one in exact arithmetic. Both maps can exclude boundary solutions with zero coordinates. They do not automatically supply a bounded transformed search domain.
- Disclose warm starts. Prior fits or grid searches can inform initialization, but their evaluation cost and the remaining random population must be stated rather than treated as free knowledge.
Inspect the incumbent trace together with population spread, repeated candidates, and bound hits. A flat objective trace with large spread does not establish duplicate collapse; separated regions may simply have similar costs. A small spread can indicate lost exploration, but neither diagnostic alone identifies a unique remedy. Test restarts or different settings under a reported additional budget.
Scaling, noisy objectives, and budgeted comparisons
The two-dimensional example does not establish a high-dimensional population-size rule or a cutoff beyond which another optimizer wins. More variables change coverage, coordinate interactions, and evaluation cost. Reassess population size and budget on the application. Methods that learn search-distribution covariance are a possible later extension; a full CMA-ES derivation is outside this chapter.
With noisy evaluations, a trial can win because its observation was lucky. The stored best observation can improve while the true expected objective does not. Repeated comparisons, larger simulation averages, or an improvement margin can change that risk, but they offer no automatic guarantee. Common random numbers reduce comparison variance when they induce positive covariance between competing estimates. Re-evaluating both target and trial on paired draws, and every additional simulation replicate, has a cost that must be reported and budgeted.
A matched candidate-evaluation budget controls one source of comparison differences. Also report preprocessing, initialization, runtime, feasibility, objective noise, tuning effort, and stopping rules when they matter. Count finite-difference, repair, restart, and polishing evaluations; repeat across seeds; and include simple baselines. A good result on one seed or one objective does not establish general superiority.
Summary
Differential Evolution builds a continuous population search from donor differences. DE/rand/1/bin draws three distinct donors excluding the target, scales one difference, adds it to a random base donor, and chooses trial coordinates with a binomial mask. A forced mask entry ensures mutant inheritance, although the resulting trial can still duplicate the target.
For a fixed deterministic objective, greedy replacement with our tie-accepting convention makes retained slot costs non-increasing. Freezing the old population gives the deferred implementation used here. Neither a contracting population nor a flat incumbent trace certifies a global optimum; box repair also does not establish feasibility for other constraints.
Our synthetic comparison counts every candidate, including initialization and rejected proposals, stops exactly at its budget, validates final box membership, and reports repeated-run outcomes. The known Rastrigin minimum helps interpret those outcomes; it does not turn DE into an optimality proof. Parameter choices, local-search baselines, noise handling, and the costs of any polishing or warm start remain part of an honest comparison.
Quiz
Ready to test your understanding? Take this quick quiz to reinforce what you've learned about Differential Evolution, including mutation, crossover, selection, bounds, and budgeted comparison.
Differential Evolution Quiz
Join the discussionAsk a question or share what you learned.
Keep exploring
Reference
Citation details
Cite or share this article.


Comments
Add to the conversation
A question, a correction, or an example from your own work.
No comments yet. Be the first to share your thoughts!