Metadata-Version: 2.4
Name: defoundry
Version: 0.2.0
Summary: Composable Differential Evolution optimizer
Author: Honza
License-Expression: MIT
Project-URL: Repository, https://github.com/vrbaj/differential_evolution
Project-URL: Issues, https://github.com/vrbaj/differential_evolution/issues
Keywords: optimization,differential-evolution,global-optimization
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Operating System :: OS Independent
Classifier: Typing :: Typed
Classifier: Topic :: Scientific/Engineering :: Mathematics
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Requires-Dist: mypy>=1.10; extra == "test"
Requires-Dist: ruff>=0.6; extra == "test"
Provides-Extra: release
Requires-Dist: build>=1.2; extra == "release"
Requires-Dist: twine>=6; extra == "release"
Provides-Extra: paper
Requires-Dist: numpy==2.5.4; extra == "paper"
Requires-Dist: scipy==1.18.1; extra == "paper"
Requires-Dist: pymoo==0.6.2; extra == "paper"
Requires-Dist: coco-experiment==2.8.2; extra == "paper"
Requires-Dist: matplotlib==3.11.2; extra == "paper"
Requires-Dist: coverage==7.16.2; extra == "paper"
Provides-Extra: docs
Requires-Dist: Sphinx<9,>=8; extra == "docs"
Requires-Dist: sphinxcontrib-bibtex<3,>=2.6; extra == "docs"
Requires-Dist: sphinx-rtd-theme<4,>=3; extra == "docs"
Dynamic: license-file

# 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`, and `testing_functions.py` remain as compatibility shims
- Full `SHADE` and `LSHADE` optimizers 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`.

```bash
python3 -m pip install defoundry
```

For development, install from a source checkout with `python3 -m pip install -e .`.
Maintainers can follow the [release guide](https://github.com/vrbaj/differential_evolution/blob/master/RELEASE.md)
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:

1. population initialization
2. donor-vector mutation
3. target/donor crossover
4. boundary handling
5. objective evaluation
6. greedy one-to-one selection

## Built-in mutation strategies

- `Rand1`
- `Rand2`
- `Best1`
- `Best2`
- `CurrentToBest1`
- `CurrentToBest2`
- `CurrentToRand1`
- `CurrentToRand2`
- `TrigonometricMutation`
- `DirectedMutation`
- `NeighborhoodSearchMutation`

## Built-in scale-factor controllers

- `ConstantScaleFactor`
- `RandomizedScaleFactor`
- `AdaptiveScaleFactor`

## Built-in crossover-rate controllers

- `ConstantCrossoverRate`
- `AdaptiveCrossoverRate`

These can be passed anywhere a mutation strategy expects `scale` or `difference_scale`.

```python
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:

```python
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

- `BinomialCrossover`
- `ExponentialCrossover`
- `IdentityCrossover`

`IdentityCrossover` is useful for canonical current-to-rand style runs where no extra crossover is desired.

## Built-in diversity measures

- `PopulationDiameter`
- `PopulationRadius`
- `AverageDistanceAroundPopulationCenter`
- `AverageDistanceAroundAllIndividuals`
- `PopulationCoherence`
- `DimensionalVariance`
- `AggregatedDistribution`
- `AveragePairwiseDistance`

You can disable diversity tracking by leaving `diversity_measures=None`, or track several measures at once:

```python
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

- `RandomInitializer`
- `TentInitializer`
- `OppositionInitializer`
- `QuasiOppositionInitializer`
- `SobolInitializer`

`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](https://github.com/vrbaj/differential_evolution/blob/master/docs/algorithm_reference.rst#initialization).

## Population reduction

The optimizer can optionally reduce population size during the run:

- `LinearPopulationReduction`
- `HyperbolicTangentPopulationReduction`

```python
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:

- `SHADE`
- `LSHADE`

`SHADE` includes:

- `current-to-pbest/1`
- external archive
- Cauchy sampling of `F`
- Gaussian sampling of `CR`
- success-history memories `M_F` and `M_CR`

`LSHADE` extends `SHADE` with linear population size reduction.

```python
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:

```python
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

```python
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.

```python
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:

```python
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](https://github.com/vrbaj/differential_evolution/blob/master/MIGRATION.md).

## Verification

- algorithm audit: [ALGORITHM_AUDIT.md](https://github.com/vrbaj/differential_evolution/blob/master/ALGORITHM_AUDIT.md)
- migration notes: [MIGRATION.md](https://github.com/vrbaj/differential_evolution/blob/master/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`, and `python3 -m examples.population_initialization`

### Review corrections and compatibility

See [review fixes](https://github.com/vrbaj/differential_evolution/blob/master/docs/review_fixes.md) 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](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.
