Skip to main content

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

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.

Quick start (the configuration frozen for the paper)

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 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).

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:

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

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. Releases are built in release.yml and published with Trusted Publishing. Security reports: SECURITY.md.

Licensed under Apache-2.0.

Metadata

Release files for amm-optimizer 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for amm-optimizer 0.3.0
File Size Uploaded Provenance
amm_optimizer-0.3.0.tar.gz 2.0 MB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for amm-optimizer 0.3.0
File Interpreter ABI Platform
amm_optimizer-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 2.1 MB

Release files / amm_optimizer-0.3.0.tar.gz

Download URL amm_optimizer-0.3.0.tar.gz
Size 2.0 MB
Tags Source
SHA-256 checksum
How to use checksums
23ced601a8ba69d9ba7764ecdff42cb94d5af1915e701e83a4714a35817c3e11
BLAKE2b-256 checksum
How to use checksums
57ca10944fe4ea37a9c32e5f3783422a34850812883fbba170dc422ff26b7349
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 23, 2026.

Transparency log

Release files / amm_optimizer-0.3.0-py3-none-any.whl

Download URL amm_optimizer-0.3.0-py3-none-any.whl
Size 149.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
baf816f653c820750cb2da1fa2bf46f9cb9124e4c1e1ffc7299144f032748042
BLAKE2b-256 checksum
How to use checksums
c9ed1722bce30ec3d92009b5978702653648ed1407cf63afe573d2276bae0d82
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 23, 2026.

Transparency log

Security for amm-optimizer 0.3.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.

Select from 2 files

Attestation for amm_optimizer-0.3.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 23, 2026.

Transparency log

A 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.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page