Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Python package and PyO3 bindings

The fcmaes-rust Python distribution installs the public fcmaes_rust package. Its private fcmaes_rust._fcmaes_ext PyO3 module exposes the native optimizer core; fcmaes_rust.native provides explicit access to that low-level surface. SciPy result adapters and plotting are not bundled.

Build and import

Using maturin in a virtual environment:

python -m venv .venv
.venv/bin/python -m pip install "maturin[patchelf]>=1.7,<2"
env -u CONDA_PREFIX VIRTUAL_ENV="$PWD/.venv" \
  PATH="$PWD/.venv/bin:$PATH" \
  .venv/bin/maturin develop --release \
  --manifest-path crates/fcmaes-py/Cargo.toml
.venv/bin/python -c \
  'import fcmaes_rust; print(fcmaes_rust.__version__); print(fcmaes_rust.phase1_build_info())'

For an installed release:

python -m pip install fcmaes-rust

The facade exports the optimizer functions and classes documented below. They return low-level tuples, dictionaries, and NumPy arrays. The binary extension remains private so future facade-level result adapters can evolve without changing its import location.

Every public callable, stateful method, and property has a runtime docstring. Use Python’s normal inspection tools for the exact installed version:

import fcmaes_rust

help(fcmaes_rust.optimize_de)
help(fcmaes_rust.MODE)
help(fcmaes_rust.Archive.optimize_map_elites)

CI inspects the installed extension and rejects newly exposed callables or descriptors without docstrings. The guide supplies workflow context; runtime docstrings supply callback contracts, array shapes, parameter meaning, result layouts, stopping behavior, raised exceptions, and parallelism notes at the API entry point.

Type checking and editor support

The distribution ships a PEP 561 py.typed marker and a _fcmaes_ext.pyi stub, so editors and type checkers resolve the native surface without a plugin. No configuration is needed.

The stub also records defaults that inspect.signature cannot show. PyO3 renders any non-literal Rust default as Ellipsis in __text_signature__, and CPython rejects a text signature that spells -inf as a default, so the real value cannot be surfaced through introspection at all. Seventeen parameters across ten entry points are affected:

ParameterReal defaultShown by inspect.signature
stop_fitness-infEllipsis
value_limitinfEllipsis
stop_hist-1.0Ellipsis
update_gap-1Ellipsis

Read the stub, not help(), when a default matters. test_type_stubs.py compares the stub against the built extension — names, parameter order, and every default — so the two cannot drift apart silently.

Runnable Python example

examples/python/test_cma.py adapts the Rosenbrock tests from the original fcmaes.testfun and fcmaes.test_cma modules. It demonstrates both the one-shot optimize_acma function and the ACMA ask/tell class:

.venv/bin/python examples/python/test_cma.py
.venv/bin/python -m pytest examples/python/test_cma.py

The objective monitor verifies that the result returned by Rust matches the best point observed by Python and that the evaluation counts agree.

Optimizer surface

AlgorithmOne-shot functionStateful class
Differential Evolutionoptimize_deDE
Active CMA-ESoptimize_acmaACMA
CR-FM-NESoptimize_crfmnesCRFMNES
PGPEoptimize_pgpePGPE
Dual Annealingoptimize_da
BiteOptoptimize_biteBite
MODEAsk/tell onlyMODE
MAP-Elites / DiversifierArchive methodsArchive

Stateful scalar classes expose ask, tell, population, and result where the underlying optimizer supports them. ACMA also exposes tell_x. BiteOpt enforces pending-batch call order and exact feedback lengths. The authoritative callable signatures and docstrings are generated from the #[pyo3(signature = ...)] declarations and Rust documentation comments under crates/fcmaes-py/src/.

MODE and quality diversity

MODE.ask() returns a (popsize, dim) matrix. tell() accepts a (popsize, nobj + ncon) matrix in which minimized objectives precede constraints; constraints are feasible at values less than or equal to zero. tell_switch() changes the update mode for one batch, and set_population() installs validated decision and objective matrices.

The native QD Archive constructor receives decision bounds, descriptor bounds, capacity, CVT sampling density, and a seed. For two descriptors, samples_per_niche=0 selects constant-time grid lookup; positive values build CVT centers.

Archive.optimize_map_elites() runs the SBX/mutation or Iso+LineDD emitter and optional CMA emitter generations. Archive.diversify() runs the CMA-ME-style improvement search. xs(), ys(), and descriptors() expose archive arrays; occupied, best_y, and qd_score expose summary values.

Persistence, archive joins, shared-memory statistics, and plotting are not part of this binding crate.

Retry surface

The extension exports minimize_retry, minimize_advanced_retry, and minimize_moretry. Returned dictionaries contain:

  • x, fun, nfev, nit, and success;
  • retry_xs and retry_ys for retained results;
  • improvements for completed-retry best-value samples.

The advanced-retry optimizer callback receives local bounds, an optional guess, step-size information, its evaluation budget, and an independently spawned seed. The moretry callback receives sampled scalarization weights and retains the original vector-valued evaluations.

GTOP surface

The extension exposes:

  • gtop_gtoc1
  • gtop_cassini1 and gtop_cassini1_minlp
  • gtop_cassini2 and gtop_cassini2_minlp
  • gtop_messenger and gtop_messengerfull
  • gtop_rosetta
  • gtop_sagas
  • gtop_tandem and gtop_tandem_unconstrained

Wrong input dimensions return the GTOP penalty result instead of accessing outside native arrays.

GIL and parallelism

Optimizer loops execute under py.allow_threads. Each Python objective call constructs a NumPy vector and reacquires the GIL. Consequently:

  • Native Rust objectives scale across retry workers without the GIL.
  • Python objectives dominated by an extension may scale if that extension releases the GIL.
  • Cheap Python callbacks are normally callback/GIL limited.
  • Combining retry-level workers with population-level workers can oversubscribe the machine and should be measured.