Skip to content

VAMOS Cookbook

Common recipes and patterns for using VAMOS.

The optimize(...) API is the fastest way to run experiments in Python. See the API decision guide in docs/guide/getting-started.md if you need explicit config objects.

from vamos import optimize
from vamos.ux.api import result_summary_text

result = optimize("zdt1", algorithm="nsgaii", max_evaluations=5000, pop_size=100, seed=0)
print(result_summary_text(result))

1. Custom Problem Definition

Define a problem by implementing ProblemProtocol (attributes plus evaluate):

import numpy as np


class MyProblem:
    def __init__(self) -> None:
        self.n_var = 2
        self.n_obj = 2
        self.n_constraints = 0
        self.xl = np.array([0.0, 0.0])
        self.xu = np.array([1.0, 1.0])
        self.encoding = "real"

    def evaluate(self, X: np.ndarray, out: dict[str, np.ndarray]) -> None:
        f1 = X[:, 0]
        f2 = (1.0 + X[:, 1]) / X[:, 0]
        out["F"] = np.column_stack([f1, f2])


problem = MyProblem()

2. Handling Constraints

Box constraints are handled via xl and xu. For other constraints, fill out["G"]. VAMOS expects g(x) <= 0 for feasible solutions. Set n_constraints to the number of constraints if you want the count explicitly tracked.

    def evaluate(self, X: np.ndarray, out: dict[str, np.ndarray]) -> None:
        # ... calculate F ...

        # Constraint: x[0] + x[1] <= 1.5
        g1 = (X[:, 0] + X[:, 1]) - 1.5
        out["G"] = g1.reshape(-1, 1)

Constraint DSL (symbolic)

When you want a reusable constraint evaluator, build it symbolically:

import numpy as np
from vamos.foundation.constraints.dsl import constraint_model, build_constraint_evaluator

with constraint_model(n_vars=2) as cm:
    x0, x1 = cm.vars("x0", "x1")
    cm.add(x0 + x1 <= 1.0)
    cm.add(x0 >= 0.0)

eval_constraints = build_constraint_evaluator(cm)
G = eval_constraints(np.array([[0.2, 0.3], [0.9, 0.4]]))

Notes: - Constants must be scalar values (including 0-d arrays like np.array(1.0)). - Vector constants are not supported; expand them into separate constraints.

3. Visualization Callback

Use live_viz to see progress or save frames.

from vamos import optimize

class MyCallback:
    def __call__(self, algorithm):
        print(f"Gen {algorithm.n_gen}: {len(algorithm.pop)} solutions")
        # Access population: algorithm.pop.get("F")

optimize("zdt1", algorithm="nsgaii", max_evaluations=2000, live_viz=MyCallback())

4. Re-using Algorithm State (Checkpointing)

VAMOS algorithms are stateful. You can resume() them if you manually stepped them, or pickle them (ensure backends are pickleable).

Note: Full checkpointing support is in active development.

5. Using Numba for Performance

Select a backend via optimize(..., engine=...).

from vamos import optimize

result = optimize("zdt1", algorithm="nsgaii", engine="numba", max_evaluations=5000)

6. Comparing Algorithms

Run multiple algorithms and plot their fronts together.

import matplotlib.pyplot as plt
from vamos import optimize

res_nsga2 = optimize("zdt1", algorithm="nsgaii", max_evaluations=4000)
res_moead = optimize("zdt1", algorithm="moead", max_evaluations=4000)

plt.scatter(res_nsga2.F[:, 0], res_nsga2.F[:, 1], label="NSGA-II")
plt.scatter(res_moead.F[:, 0], res_moead.F[:, 1], label="MOEA/D")
plt.legend()
plt.show()

7. Inspect Auto-Resolved Defaults

See which top-level settings were inferred vs provided.

from vamos import optimize

result = optimize("zdt1")
print(result.explain_defaults())

The output includes:

  • resolved_spec: canonical problem, algorithm, operators, backend, termination, seed, and population details
  • default_sources: which values were inferred (auto) vs set explicitly

8. Operator Facade Access

Import common operators directly from vamos.engine.operators.

import numpy as np
from vamos.engine.operators import SBXCrossover, PolynomialMutation

xl = np.zeros(30)
xu = np.ones(30)
crossover = SBXCrossover(prob_crossover=0.9, eta=15.0, lower=xl, upper=xu)
mutation = PolynomialMutation(prob=1 / 30, eta=20.0, lower=xl, upper=xu)

9. Multi-Seed Studies

Pass a list of seeds to run a small study in one call.

from vamos import optimize
from vamos.ux.api import result_summary_text

study = optimize("zdt1", algorithm="nsgaii", max_evaluations=4000, seed=[0, 1, 2, 3])
for idx, res in enumerate(study.runs):
    print(idx, result_summary_text(res))
print(study.mean("evaluations"))

10. Algorithm Config Objects (Reproducible Runs)

Use a config object when you want every knob explicit.

from vamos import optimize
from vamos.algorithms import NSGAIIConfig

cfg = (
    NSGAIIConfig.builder()
    .pop_size(100)
    .offspring_size(100)
    .crossover("sbx", prob=1.0, eta=20.0)
    .mutation("pm", prob="1/n", eta=20.0)
    .selection("tournament", size=2)
    .build()
)

result = optimize("zdt1", algorithm="nsgaii", algorithm_config=cfg, max_evaluations=8000, seed=7)

11. Multiprocessing Evaluation

For expensive evaluations, use the multiprocessing backend explicitly.

from vamos import optimize
from vamos.foundation.eval.backends import MultiprocessingEvalBackend

backend = MultiprocessingEvalBackend(n_workers=4)
result = optimize("zdt1", algorithm="nsgaii", max_evaluations=6000, eval_strategy=backend)

12. Hypervolume-Based Early Stopping

Use the lower-level experiment runner when you need non-default termination logic.

from vamos.experiment.runner import run_experiment
from vamos.foundation.core.experiment_config import ExperimentConfig
from vamos.foundation.core.hv_stop import build_hv_stop_config

hv_cfg = build_hv_stop_config(hv_threshold=0.9, hv_reference_front=None, problem_key="zdt1")
hv_cfg["max_evaluations"] = 12000

metrics = run_experiment(
    problem="zdt1",
    algorithm="nsgaii",
    engine="numpy",
    config=ExperimentConfig(max_evaluations=12000, seed=3),
    termination=("hv", hv_cfg),
)

13. Save and Load a Run Artifact

Persist a complete v1 run and load its canonical numerical result later.

from vamos import load_result, load_run, optimize, save_result

result = optimize("zdt1", algorithm="nsgaii", max_evaluations=5000)
stored = save_result(result, "results/zdt1_nsgaii")

loaded = load_result(stored.root)
run = load_run(stored.root)
print(loaded.F.shape, run.manifest.run_id)

save_result is available from the top-level vamos facade only. See Save and load Python run artifacts for integrity, resource-limit, and non-destructive-write behavior.

14. Select a Single Solution from the Front

Pick a balanced normalized-sum solution or a simple min objective.

from vamos import optimize

result = optimize("zdt1", algorithm="nsgaii", max_evaluations=5000)
choice = result.best("balanced_sum")
print(choice["F"])

15. Load a Stored Run Without Re-executing It

Loading stored data is intentionally distinct from reproduction. The v1 core implemented here does not expose replay or reproduction; use load_result for arrays and load_run for the immutable manifest and environment.

from vamos import load_run

run = load_run("results/zdt1_nsgaii", verify="all")
print(run.status, run.result.F.shape)

16. Validate a Config File (CLI)

Check a YAML/JSON experiment spec before running:

vamos --config configs/experiment.yaml --validate-config

17. Convert Results to a DataFrame (pandas)

Export results for analysis in pandas (requires the analysis extra).

from vamos import optimize
from vamos.ux.api import result_to_dataframe

result = optimize("zdt1", algorithm="nsgaii", max_evaluations=4000)
df = result_to_dataframe(result)
df.to_csv("results/zdt1_nsgaii_front.csv", index=False)

18. Combine Fronts from Multiple Runs

Merge fronts from multiple runs and keep the non-dominated set.

import numpy as np

from vamos import optimize
from vamos.foundation.quality_indicators.pareto import pareto_filter

study = optimize("zdt1", algorithm="nsgaii", max_evaluations=4000, seed=[0, 1, 2])
combined = np.vstack([res.F for res in study.runs if res.F is not None])
front = pareto_filter(combined, return_indices=False)

20. Compute Hypervolume (2D)

Compute hypervolume for 2D minimization fronts.

import numpy as np

from vamos import optimize
from vamos.foundation.quality_indicators import compute_hypervolume

result = optimize("zdt1", algorithm="nsgaii", max_evaluations=4000)
F = np.asarray(result.F)
hv = compute_hypervolume(F, ref_point=[1.1, 1.1])
print(hv)

21. Normalized HV for ZDT Problems

Use the built-in reference front to compute normalized hypervolume on ZDT.

import numpy as np

from vamos import optimize
from vamos.foundation.quality_indicators import compute_normalized_hv

result = optimize("zdt1", algorithm="nsgaii", max_evaluations=4000)
hv_norm = compute_normalized_hv(np.asarray(result.F), "zdt1")
print(hv_norm)