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. amm_optimizer.minimize runs AMM by default. 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. Results, limits and how it works: https://bjbodner.github.io/amm-optimizer-site/ (the website has its own repository, BjBodner/amm-optimizer-site). Upgrading from 0.2.0: the import name changed to amm_optimizer (import amm_optimizer as amm). Upgrading from 0.3.0: amm.minimize now runs AMM by default and needs bounds=; the original ATM sampler is config=amm.ATMConfig(). See CHANGELOG.md.

Quick start

import numpy as np
import amm_optimizer as amm

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

result = amm.minimize(f, bounds=[(-5, 5)] * 10)
print(result.x, result.fun, result.nfev)

That is the whole call: the search box is the only thing you must give. Everything else has a default:

Argument Default Meaning
bounds required the search box: a list of (low, high) pairs (as in SciPy), (lower, upper) with scalars or arrays, or scipy.optimize.Bounds; finite limits
x0 the centre of the box the starting point
budget 100 x D evaluations (at least 100) the maximum number of calls of f; all are used. Pass what you can afford
seed a fresh seed a run is deterministic given the seed
anytime False True for a run whose budget is not known or that you will cut short, see docs/ANYTIME.md
config AMM (presets.get("c46")) amm.ATMConfig() runs the original ATM sampler alone and needs no box

The older, fully spelled-out call still works:

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),                         # the box (scalars or one value per coordinate)
)
print(result.fun, result.nfev)

Called without config=, amm.minimize runs AMM, the configuration frozen for the paper (amm.presets.get("c46"); passing it as config= gives a bit-identical run). It needs bounds=: without it, and without a config, the call raises ValueError. 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.

The original ATM sampler is one engine of AMM and is still available on its own: pass config=amm.ATMConfig() (no bounds= needed). amm_optimizer.api.minimize is the same engine without the AMM default, so a call to it with no config also runs the original sampler. Versions up to 0.3.0 ran that sampler when config was omitted; see the changelog.

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

The original ATM sampler and the older presets

The sections below document the original ATM sampler and the older presets (ATMConfig() and composite()); they are kept because every recorded result and test depends on them. None of them is the default of amm.minimize: pass them as config=. 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,
    config=amm.ATMConfig(),                 # the original ATM sampler; no bounds needed
)
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.

ATMConfig() reproduces the original ATM exactly, and so does amm_optimizer.api.minimize called without a config (see docs/DEVIATIONS.md); the default of amm.minimize is AMM, not the original. ATMConfig(strict_budget=False) also restores the original's looser budget accounting.

The composite preset

ATMConfig.composite() is ATM's own sampler plus 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). It is kept for reproducibility; AMM (c46, the default) is the configuration to use. If you know the search box, the composite gets more out of the same budget than the original sampler:

result = amm.minimize(
    sphere,
    x0=np.zeros(10),
    budget=2000,
    seed=0,
    bounds=(-5, 5),                      # scalars or one value per coordinate
    config=amm.ATMConfig.composite(),
)
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. Each option is documented in docs/DEVIATIONS.md.

Presets, from simplest to largest (c46 last, as the frozen AMM):

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
presets.get("c46") AMM, the default of amm.minimize docs/FREEZE_2026-10-22.md

tests/golden/presets.json pins the exact cost traces of the first two.

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 (default.py is the public minimize; api.py is the engine, whose default is the original sampler and which the tests call)
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.5.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.5.0
File Size Uploaded Provenance
amm_optimizer-0.5.0.tar.gz 1.9 MB With provenance Details

Built distribution (wheel)

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

Total release size: 2.1 MB

Release files / amm_optimizer-0.5.0.tar.gz

Download URL amm_optimizer-0.5.0.tar.gz
Size 1.9 MB
Tags Source
SHA-256 checksum
How to use checksums
b5af35c784a11e46b5d2aa968ce07aa9b6577390aebbe2c06eb38643d8bca79a
BLAKE2b-256 checksum
How to use checksums
975cf03e2cb0743b8749a5b39430ed85021215dd7fd1d73757e7d15df5f97bd0
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 25, 2026.

Transparency log

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

Download URL amm_optimizer-0.5.0-py3-none-any.whl
Size 161.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fff510b927af731be1904dd7805f1833b257713122afd02a3b62c13d69527d41
BLAKE2b-256 checksum
How to use checksums
d8ffedcc6b2f911d2e16695fc8287a74123c8ad7ff243a292e390efd57f89782
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 25, 2026.

Transparency log

Security for amm-optimizer 0.5.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.5.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 25, 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.5.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