DEFoundry — Differential Evolution
This repository now provides a small, explicit Differential Evolution package with independently composable mutation strategies and crossover operators.
What changed
- The optimizer is now a real Python package:
differential_evolution/ - Mutation and crossover are separate components
- Randomness is explicit and reproducible
- A deterministic test suite verifies formulas and DE invariants
- The original
main.py,population_initialization.py, andtesting_functions.pyremain as compatibility shims - Full
SHADEandLSHADEoptimizers are available for success-history adaptive DE
Installation
DEFoundry requires Python 3.11 or later and has no mandatory runtime dependencies.
The distribution name is defoundry; the Python import is differential_evolution.
python3 -m pip install defoundry
For development, install from a source checkout with python3 -m pip install -e ..
Maintainers can follow the release guide
to validate and publish distributions.
Core concepts
- target vector: the current population member being evolved
- donor vector: the output of a mutation strategy
- trial vector: the result of applying a crossover operator to the target and donor
The optimizer orchestrates:
- population initialization
- donor-vector mutation
- target/donor crossover
- boundary handling
- objective evaluation
- greedy one-to-one selection
Built-in mutation strategies
Rand1Rand2Best1Best2CurrentToBest1CurrentToBest2CurrentToRand1CurrentToRand2TrigonometricMutationDirectedMutationNeighborhoodSearchMutation
Built-in scale-factor controllers
ConstantScaleFactorRandomizedScaleFactorAdaptiveScaleFactor
Built-in crossover-rate controllers
ConstantCrossoverRateAdaptiveCrossoverRate
These can be passed anywhere a mutation strategy expects scale or difference_scale.
from differential_evolution import AdaptiveScaleFactor, Rand1
mutation = Rand1(scale=AdaptiveScaleFactor(initial=0.5, tau=0.1, lower=0.1, upper=0.9))
For the Brest et al. paper's full jDE control-parameter scheme, pair AdaptiveScaleFactor with AdaptiveCrossoverRate, or use the convenience helper:
from differential_evolution import DifferentialEvolution, jde_rand_1_bin, sphere_function
components = jde_rand_1_bin()
optimizer = DifferentialEvolution(
objective=sphere_function,
bounds=[(-5.0, 5.0), (-5.0, 5.0)],
population_size=20,
mutation=components.mutation,
crossover=components.crossover,
max_generations=100,
seed=123,
)
Built-in crossover operators
BinomialCrossoverExponentialCrossoverIdentityCrossover
IdentityCrossover is useful for canonical current-to-rand style runs where no extra crossover is desired.
Built-in diversity measures
PopulationDiameterPopulationRadiusAverageDistanceAroundPopulationCenterAverageDistanceAroundAllIndividualsPopulationCoherenceDimensionalVarianceAggregatedDistributionAveragePairwiseDistance
You can disable diversity tracking by leaving diversity_measures=None, or track several measures at once:
from differential_evolution import (
AveragePairwiseDistance,
DifferentialEvolution,
PopulationDiameter,
Rand1,
BinomialCrossover,
sphere_function,
)
optimizer = DifferentialEvolution(
objective=sphere_function,
bounds=[(-5.0, 5.0), (-5.0, 5.0)],
population_size=20,
mutation=Rand1(scale=0.8),
crossover=BinomialCrossover(crossover_rate=0.9),
diversity_measures=[
PopulationDiameter(),
AveragePairwiseDistance(),
"population_coherence",
],
max_generations=50,
seed=123,
)
result = optimizer.run()
print(result.diversity_history["population_diameter"])
PopulationCoherence compares the movement of the population center between consecutive generations with the average movement of individuals. Its first recorded value is 0.0 because there is no previous generation yet.
AggregatedDistribution is implemented here as a project-specific marginal dispersion statistic: each coordinate is normalized to [0, 1], binned, and summarized by the variance-to-mean ratio of occupancy counts.
Built-in initializers
RandomInitializerTentInitializerOppositionInitializerQuasiOppositionInitializerSobolInitializer
SobolInitializer supports up to 40 dimensions and 2**30 sequence points.
It applies a seed-dependent digital shift by default; pass scramble=False
for the unshifted sequence. See the current
algorithm reference.
Population reduction
The optimizer can optionally reduce population size during the run:
LinearPopulationReductionHyperbolicTangentPopulationReduction
from differential_evolution import (
BinomialCrossover,
DifferentialEvolution,
LinearPopulationReduction,
Rand1,
sphere_function,
)
optimizer = DifferentialEvolution(
objective=sphere_function,
bounds=[(-5.0, 5.0), (-5.0, 5.0)],
population_size=20,
mutation=Rand1(scale=0.8),
crossover=BinomialCrossover(crossover_rate=0.9),
population_schedule=LinearPopulationReduction(min_population_size=4),
max_evaluations=2000,
seed=123,
)
LinearPopulationReduction follows the L-SHADE idea of shrinking linearly with used function-evaluation budget when max_evaluations is provided. If max_evaluations is omitted, it falls back to generation progress. HyperbolicTangentPopulationReduction is a project-specific smooth nonlinear schedule.
Supply at least one stopping limit. max_generations defaults to None, so an evaluation budget alone runs until that budget is exhausted. If both limits are supplied, the first one reached stops the run.
When the population is reduced, the worst individuals by current fitness are removed. Users must choose a minimum population size that remains compatible with the mutation strategy in use.
SHADE and L-SHADE
The package also includes dedicated optimizers for the full success-history adaptive algorithms:
SHADELSHADE
SHADE includes:
current-to-pbest/1- external archive
- Cauchy sampling of
F - Gaussian sampling of
CR - success-history memories
M_FandM_CR
LSHADE extends SHADE with linear population size reduction.
from differential_evolution import LSHADE, sphere_function
optimizer = LSHADE(
objective=sphere_function,
bounds=[(-5.0, 5.0), (-5.0, 5.0)],
population_size=18,
min_population_size=4,
memory_size=6,
max_evaluations=2000,
seed=123,
)
result = optimizer.run()
Saving runs
For post-run investigation, enable record_snapshots=True and save the result:
result = optimizer.run()
result.save_json("shade_run.json")
result.save_pickle("shade_run.pkl")
Each snapshot stores the generation number, evaluation count, best solution, population size, optional population and fitness arrays, diversity values, and algorithm-specific extra state. For SHADE and LSHADE, the extra state includes the external archive and the parameter memories.
Example
from differential_evolution import (
BinomialCrossover,
DifferentialEvolution,
ExponentialCrossover,
Rand1,
Best1,
sphere_function,
)
optimizer = DifferentialEvolution(
objective=sphere_function,
bounds=[(-5.0, 5.0), (-5.0, 5.0)],
population_size=20,
mutation=Rand1(scale=0.8),
crossover=BinomialCrossover(crossover_rate=0.9),
max_generations=50,
seed=123,
)
result = optimizer.run()
optimizer_alt = DifferentialEvolution(
objective=sphere_function,
bounds=[(-5.0, 5.0), (-5.0, 5.0)],
population_size=20,
mutation=Best1(scale=0.5),
crossover=ExponentialCrossover(crossover_rate=0.8),
max_generations=50,
seed=123,
)
result_alt = optimizer_alt.run()
Custom components
Any callable with the right signature can be injected.
class MyMutation:
def __call__(self, context):
target = context.population[context.target_index]
return [value * 0.5 for value in target]
class MyCrossover:
def __call__(self, target_vector, donor_vector, rng):
trial = list(target_vector)
trial[0] = donor_vector[0]
return trial
Reproducibility
Use seed= or pass a custom RNG object. The optimizer does not use module-level global random state.
Compatibility
The original constructor remains available from a source checkout only:
from main import DifferentialEvolution
Installed users can use from differential_evolution.compat import DifferentialEvolution.
Both paths emit a deprecation warning and forward into the new implementation.
Details are in MIGRATION.md.
Verification
- algorithm audit: ALGORITHM_AUDIT.md
- migration notes: MIGRATION.md
- tests:
python3 -m unittest discover -s tests -v - examples:
python3 -m examples.basic_usage,python3 -m examples.custom_components,python3 -m examples.diversity_measures, andpython3 -m examples.population_initialization
Review corrections and compatibility
See review fixes for changes and the disposition of the
reported findings. Plain DE now clips to bounds by default; SHADE/L-SHADE use
parent-aware midpoint repair. Mutation and crossover components are copied per
optimizer; inspect adapted state on optimizer.mutation and optimizer.crossover.
run() is single-use. result.success means a usable objective value was found,
not numerical convergence; result.message reports the exhausted limit.
Sobol initialization uses a seeded digital shift by default. Use
SobolInitializer(scramble=False) for the original unshifted sequence. Fixed-dimensional benchmarks require exactly two coordinates; Ackley is now
n-dimensional in version 0.2.0.
Execution and provenance features (0.2.0)
Generic DE now defaults to binomial crossover with CR=0.9; explicitly use
IdentityCrossover() for donor-only mutation. SHADE/L-SHADE retain their fixed
current-to-pbest/1/bin trial rules: their mutation and crossover placeholders
are not extension points. Boundary repair, initialization, diagnostics and
compatible schedules remain configurable.
An optional batch_objective(candidates) can use an ordered external worker
pool or vectorized objective. See python -m examples.batch_evaluation and
MIGRATION.md for budget, failure and controller semantics.
Result metadata records initial configuration, seed, Python/package versions
and an initial state for random.Random. Arbitrary callable implementations
and datasets must still be saved separately. The new
BoundNormalizedDiversity(AveragePairwiseDistance()) wrapper removes coordinate
unit effects from distance diagnostics. Ackley now accepts any positive dimension.
Metadata
Release files for defoundry 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| defoundry-0.2.0.tar.gz | 107.0 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| defoundry-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 151.5 kB
Release files / defoundry-0.2.0.tar.gz
| Download URL | defoundry-0.2.0.tar.gz |
|---|---|
| Size | 107.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
512b4354dc46e1517146a23a2a8eb8e5e6189f71c4ebd8dd52568557c8f7ad50
|
|
BLAKE2b-256 checksum How to use checksums |
8cf14f665ccd04250eaf9b713d64ed4b46407ef8a4d17a50d9e5b42220130048
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / defoundry-0.2.0-py3-none-any.whl
| Download URL | defoundry-0.2.0-py3-none-any.whl |
|---|---|
| Size | 44.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7f4aac2192824b5716c704a399dd7ea94d419c96a2c4d51502ff94b5146176af
|
|
BLAKE2b-256 checksum How to use checksums |
effe38c64e64f33594ed68fc7bbfd82fc67b667663fafef421540484525bf523
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logSecurity for defoundry 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for defoundry-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.