Metadata-Version: 2.5
Name: amm-optimizer
Version: 0.3.0
Summary: Adaptive Multi Mode (AMM): budget-aware routing of derivative-free optimizers by online landscape probes (import amm_optimizer)
Project-URL: Homepage, https://github.com/BjBodner/amm-optimizer
Project-URL: Source, https://github.com/BjBodner/amm-optimizer
Project-URL: Issues, https://github.com/BjBodner/amm-optimizer/issues
Project-URL: Changelog, https://github.com/BjBodner/amm-optimizer/blob/main/CHANGELOG.md
Author: Benjy Bodner
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: BBOB,CMA-ES,COCO,algorithm selection,black-box optimization,derivative-free
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: numpy>=2.0
Requires-Dist: scipy>=1.18
Provides-Extra: bench
Requires-Dist: coco-experiment; extra == 'bench'
Requires-Dist: cocopp; extra == 'bench'
Provides-Extra: dev
Requires-Dist: coco-experiment; extra == 'dev'
Requires-Dist: hypothesis; extra == 'dev'
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Description-Content-Type: text/markdown

# Adaptive Multi Mode (AMM): budget-aware routing of optimizers by online landscape probes

AMM is a derivative-free optimizer for continuous black-box problems with a **known evaluation
budget** and a **known box**. It does not run one algorithm: it spends a small, counted part of the
budget on *online probes* (curvature, plateau, separability, kink, basin and fractal readings of the
objective near the incumbent) and uses their outcomes to route the rest of the budget among
quasi-Newton (SLSQP, L-BFGS-B), trust-region (COBYQA), coordinate (STEP), multistart (MLSL) and
population (CMA-ES family, the ATM sampler) engines. A probe that does not fire leaves the run on
a general path, so a misreading costs evaluations, not the run. Every rule reads only the
dimension, the budget, the box and evaluated function values.

The method, its development protocol and its evaluation on COCO/bbob are described in the paper
(citation below). The method grew out of the Adaptive Two Mode (ATM) sampler, which is one of its
engines; some internal names (`ATMConfig`, preset codes such as `c46`) still carry that history.

## Install

```bash
pip install amm-optimizer            # NumPy and SciPy only; Python 3.12 or newer
uv add amm-optimizer                 # or: uv pip install amm-optimizer
pip install -e ".[dev]"              # from a checkout; add ",bench" for the COCO/bbob tooling
```

Source, issues and the changelog: <https://github.com/BjBodner/amm-optimizer>. Upgrading from 0.2.0: the import
name changed to `amm_optimizer` (`import amm_optimizer as amm`); see [CHANGELOG.md](CHANGELOG.md).

## Quick start (the configuration frozen for the paper)

```python
import numpy as np
import amm_optimizer as amm

def f(x):
    return float(np.sum((x - 1.0) ** 2))

result = amm.minimize(
    f,
    x0=np.zeros(10),
    budget=100 * 10,                        # maximum number of function evaluations
    seed=0,                                 # runs are deterministic given the seed
    bounds=(-5, 5),                         # required: the box (scalars or one value per coordinate)
    config=amm.presets.get("c46"),          # the frozen configuration; see docs/FREEZE_2026-10-22.md
)
print(result.x, result.fun, result.nfev)
```

`bounds` is required by this preset (it raises `ValueError` without it). The box is used, not
enforced as a constraint: samples are folded into it before the function is called. The objective
must be deterministic. Tested on noiseless, continuous, box-constrained problems with budgets of
10 to 1000 x the dimension.

**Reproducing the paper.** The configuration hash of `amm.presets.get("c46").config_hash()` is `2153948ebed51150`; [docs/FREEZE_2026-10-22.md](docs/FREEZE_2026-10-22.md) records what was frozen. The pre-registrations, batch notes and result files of the development are kept in a separate research archive.

**Citation.** See `CITATION.cff` (the paper's reference will be added when it is public).

## Legacy ATM presets

The sections below document the original ATM sampler and the older presets
(`ATMConfig()`, `composite()`, `improved()`); they are kept because every recorded result and test
depends on them. This repository is a clean, tested re-implementation of the original ATM that
reproduces its results exactly (see [docs/DEVIATIONS.md](docs/DEVIATIONS.md)).

```python
import numpy as np
import amm_optimizer as amm

def sphere(x):
    return np.sum((x - 2) ** 2)

result = amm.minimize(sphere, x0=np.zeros(10), budget=2000, seed=0)
print(result.x, result.fun, result.nfev)
```

`budget` is the maximum number of function evaluations, and ATM uses all of them (the
best point found is kept even if the budget ends mid-iteration). Pass `seed=` (or an
`rng=np.random.RandomState`) for reproducible runs. A starting point where the function is
NaN or infinite raises `ValueError`; NaN or infinite values elsewhere count as failed
evaluations and never win.

The defaults reproduce the original ATM exactly (see `docs/DEVIATIONS.md`);
`ATMConfig(strict_budget=False)` also restores the original's looser budget accounting.

## Better results: the improved preset

If you know the search box, `ATMConfig.improved()` gets far more out of the same budget:

```python
result = amm.minimize(
    sphere,
    x0=np.zeros(10),
    budget=2000,
    seed=0,
    bounds=(-5, 5),                      # scalars or one value per coordinate
    config=amm.ATMConfig.improved(),
)
print(result.fun)
```

The box is used, not enforced as a constraint: samples are folded back into it before the
function is called, so the function only ever sees points inside. Without `bounds=` the
preset raises `ValueError`. The objective must be deterministic.

**This preset is a hybrid, not just ATM.** It takes ATM's sampler with five improvements
(success-rate control of the search width, a uniform first pool over the box, bounds folded
with a 25 % corner clip, a pool sized from the budget, no re-evaluation of the incumbent) and
adds local-search stages: a simplex search in up to 4 dimensions and a (1+1)-ES above, for the
first part of small budgets; a learned covariance up to 10 dimensions; and, up to 10
dimensions, a simplex search over the last half of the budget. Each option is documented in
`docs/DEVIATIONS.md`.

Presets, from simplest to largest:

| Preset | What it is | Evidence |
|---|---|---|
| `ATMConfig()` | the original ATM, bit for bit | `tests/golden`, 400-case parity sweep |
| `ATMConfig.composite()` | ATM's sampler plus the five improvements; stays pure ATM | +.101, +.097, +.099 on three untouched holdouts |
| `ATMConfig.improved()` | the composite plus the local stages above (recommended) | +.196 over the original, +.096 over the composite on a fourth holdout |
| `ATMConfig.improved_wave6()` | an earlier version, kept for reproducibility; loses on multimodal functions at medium budgets | Tier 4 |

`tests/golden/presets.json` pins the exact cost traces of all of them.

**What to expect from `improved()`**, measured on COCO bbob (2021 suite) instances 96-100 that
were not used to design it (18,000 runs per arm, raw COCO evaluation):

* ECDF value (fraction of targets reached) improves by **+.196** [95 % CI +.180, +.212] over
  the defaults, averaged over budgets of 30, 100 and 300 x dimension, and by **+.096**
  [+.085, +.108] over `composite()`.
* The gain shrinks as the dimension grows: over the defaults +.412 at D = 2, +.197 at D = 5,
  +.127 at D = 10, +.082 at D = 20, +.055 at D = 40; over `composite()` +.215 at D = 2 down to
  +.017 at D = 40, where the two are almost the same algorithm.
* It does **not** beat everything: a plain (1+1)-ES with the 1/5 rule is still ahead at 40
  dimensions (-.031) and level at 20, and CMA-ES is far ahead of every ATM variant at large
  budgets (on the Tier 4 instances: .597 against .504 for the closest hybrid at 1000 x D).
* Only noiseless, continuous, box-constrained problems with budgets of 10 to 1000 x D were
  tested. The three-algorithm design means its gain should not be described as an improvement
  of ATM's sampler alone; use `composite()` if you want that.

## Development

```bash
pytest -m "not slow"      # fast suite, includes golden-master and parity tests
pytest -m slow            # 400 random cases against the frozen original in reference/
python tools/record_golden.py   # re-record golden masters (only for deliberate changes)
python benchmarks/perf.py       # repeatable speed benchmark, original vs this package
python benchmarks/block_profile.py   # where the time goes, per algorithm block
```

| Path | What it is |
|------|------------|
| `src/amm_optimizer/` | the package |
| `reference/` | byte-identical copy of the original module, used only by tests |
| `tests/golden/` | recorded trajectories from the original |
| `docs/DEVIATIONS.md` | every difference from the original |
| `benchmarks/` | speed harness (`perf.py`), per-block timing (`block_profile.py`), COCO/bbob harness (`run_bbob.py`, `compare_bbob.py`, `postprocess_bbob.py`) and stored results |

## Continuous integration

`.github/workflows/ci.yml` runs lint, type checks, the research-tier tests on Windows (Python 3.13), Linux (3.12, 3.14)
and macOS (3.12), and a packaging job (build, `twine check`, an sdist size gate, and a smoke test of the installed wheel
and sdist). The bit-exact snapshot tests only hold in the environment that recorded them and skip themselves elsewhere;
a Windows 3.12 leg pinned to that environment reports but does not gate, because GitHub's runners differ in CPU generation
and so in the last bits. Run the same checks locally with `python tools/ci.py` (fast gate), `python tools/ci.py --tier merge`
or `--tier full`; see [CONTRIBUTING.md](CONTRIBUTING.md). Releases are built in `release.yml` and published with Trusted Publishing.
Security reports: [SECURITY.md](SECURITY.md).

Licensed under Apache-2.0.
