Development documentation — 0.3.0a4 / main
This is the development branch, not a published stable release. Use the source installation guide. Functional checks cover Ubuntu, Windows and macOS; interpreter, artifact and optional-feature limits still apply. Read support, release gates and known limitations before relying on results.
Extend the library
Documentation index · Contributing
First choose the smallest extension
- New optimization problem: implement a decoder and objective.
- Different settings: use fixed overrides or adaptive TOMLs.
- Different refinement/perturbation: use the existing operator protocols.
- New search strategy: subclass
MetaheuristicBaseand implement the lifecycle.
Do not copy an entire algorithm to change a parameter or a local-search operator.
Replace an operator
Advanced kernel constructors, explicitly imported from implementation modules,
accept LocalSearchOperator and, for SA/ILS/VNS,
ShakingOperator. A local-search callable accepts
(keys, decoder, cost, *, pool) and returns (keys, objective). The pool= keyword
is part of the contract. It may be None. A shaking callable accepts
(keys, beta_min, beta_max) and returns the perturbed NumPy vector.
Use the same callback contract as the owning algorithm and return keys consistent with the objective value. Preserve bounds and dimension; do not assume that mutating an argument is harmless. Use owned RNGs if the operator is stochastic. Do not silently suppress callback exceptions or report uncomputed objectives.
Public algorithm solver objects share RKO's constructor and .solve(...) contract,
not every low-level kernel argument. Operator overrides remain an advanced kernel
feature, with the lifecycle limitations documented in
advanced kernel integration.
Implement a new metaheuristic
Read src/rko_solver/metaheuristics/base.py and a comparable implementation first.
A concrete class must:
- Inherit
MetaheuristicBase, retaindecoder,cost,tam_solutionand its pool, and validate fixed and adaptive settings before searching. - Implement
solve(limit_time)with a timer, cooperative stop checks and consistent minimization comparisons. Respect cancellation and callback exceptions. - Return the low-level
(extra, best_keys, best_cost)tuple, retaining a valid incumbent when interrupted. Publish detached improvements to the pool withpool.insert((cost, keys), self.name). - Use
RandomStreamsfor library randomness rather than reseeding global generators. - Describe strategy, pseudocode, acceptance/stopping rules, inputs, outputs and practical complexity in the module header; document every class/function with types, parameters, returns and applicable exceptions.
Use an existing algorithm module as a maintained implementation example rather than a minimal skeleton that omits validation, RNG or stopping behavior.
To integrate with the configured high-level API, update the single MH_SPECS
registry, canonical aliases if needed, parameter-file mapping/axis schema,
packaged TOML defaults and public exports. To offer a dedicated managed public
solver, add a single-algorithm RKO subclass and its lazy package export; reuse
RKO.solve rather than copying execution logic. Registry membership alone does not
create that public solver class, and arbitrary subclasses are not auto-registered.
There is no third-party plugin-registration API in this change.
Keep the three engines aligned
The Python, Rust and C++ engines share one public contract: the same RKO(...) settings, the
same errors and the same result metadata. A setting or an algorithm that only one engine
understands is a defect, not a feature, so extend all three or reject it explicitly in the
others with a ValueError that says so.
Where each piece lives:
| Piece | Python | Rust | C++ |
|---|---|---|---|
| Algorithm | metaheuristics/ |
engines/rust/rko-core/src/mh/ |
the vendored reference headers, driven by engines/cpp/cpp/rko_cpp.cpp |
| Search budget, history, cost checks | orchestration/search.py |
rko-core/src/search.rs |
Decoder in rko_cpp.cpp |
| Settings and errors to Python | solver.py, orchestration/preparation.py |
engines/rust/rko-py/src/lib.rs |
engines/cpp/src/ffi.rs |
The C++ headers are vendored unchanged and carry the MIT license; behavior is added around them in the driver, never by editing them.
When you add a setting or an algorithm:
- Read the setting once in
orchestration/preparation.pyso every engine validates it the same way, and pass it down to the Rust and C++ calls. - Add the algorithm to
Algorithm::all()inrko-core/src/mh/mod.rs; the C++ driver identifies algorithms by their index in that list, so keep the order. - Report it in the per-search metadata with the same field names and stop reasons
(
time_limit,max_evaluations,completed,cancelled,not_started). - Pin it in the parity contract (
tests/test_engine_parity_contract.py): run it on every available engine and compare reports and error messages exactly. - If it changes what an algorithm computes, refresh the golden runs
(
test_golden_runs.py) and say why in the pull request; they are the only frozen results.
What the contract pins today: parameters and overrides (including a custom TOML directory),
adaptive grids and controls, the report fields and stop vocabulary, bounded incumbent history,
and the error for a non-finite cost (ValueError, one message). The differential tests
(test_engine_differential.py) additionally check that the reported cost is recomputed from the
reported keys with independent code.
Differences that are intended, and documented, not bugs:
verbose: the Python engine prints live progress; Rust and C++ print only a start and a done line, because progress would mean calling back into Python from the search loop.- C++ startup: the reference always fills its pool first, so
initializationis reported even withoutprefill_pool. - Workers and seeds:
n_jobsdoes not limit the C++ engine (one thread per algorithm), and its seeds are not reproducible (see native engines).
Before claiming parity for anything new, run the same solve on each engine and compare what comes back; a probe like that is how the history and exception differences were found.
Required tests and documentation
- Constructor and factory invalid scalar/joint-domain cases.
- Finite keys/cost consistency and short-budget incumbent preservation.
- Fixed and supported adaptive paths, RNG isolation, callback failures.
- Pool interaction and operator substitution where applicable.
- The common constructor/solve contract, signatures/defaults and result metadata.
- The same behavior on every engine, through the parity contract above.
- Public examples and parameter reference updated with the implementation.
Run from this repository root:
uv run --locked ruff check .
uv run --locked ruff format --check .
uv run --locked pytest tests -q
For public behavior changes, explain compatibility and any seeded-trajectory or callback-count changes. Passing unit tests does not establish scientific parity, optimization quality, or production release readiness.