OSMOSE Python — Usage Guide

Task-oriented recipes for working with OSMOSE Python outside the web UI — scripting runs, reading outputs, comparing scenarios, and calibrating from Python or the command line. New to the model? Start with the hands-on 30-minute tutorial; for install see the README.

Every Python snippet below runs against the bundled 2-species smoke config data/minimal/osm_all-parameters.csv (finishes in seconds). Swap in a real config for actual work:

Config

Master file

Species

Minimal (smoke)

data/minimal/osm_all-parameters.csv

2

Eastern English Channel

data/eec_full/eec_all-parameters.csv

14

Baltic Sea

data/baltic/baltic_all-parameters.csv

8 + 6 LTL

Run snippets from the repository root in the project venv (.venv/bin/python), where the editable install makes osmose importable and the scripts/ are on hand.


1. Run a simulation

Command line

The package installs an osmose console command:

osmose validate data/minimal/osm_all-parameters.csv      # config typo / range / reference check
osmose report  output/                                   # text summary of an output directory
osmose run     data/minimal/osm_all-parameters.csv --jar path/to/osmose.jar --output output/

osmose run is the Java path and requires --jar. To run the Python engine non-interactively, call PythonEngine from a short script (above) — there is no osmose run for the Python engine.

Java engine (requires an OSMOSE JAR)

OsmoseRunner.run drives the Java subprocess. It is a coroutine — await it (or wrap in asyncio.run), and it takes a config path, not a dict:

import asyncio
from pathlib import Path
from osmose.runner import OsmoseRunner

runner = OsmoseRunner(jar_path=Path("osmose-java/osmose.jar"))
run = asyncio.run(runner.run(
    config_path=Path("data/minimal/osm_all-parameters.csv"),
    output_dir=Path("output_java"),
))

See §6 for when to pick which engine.


2. Read outputs

OsmoseResults reads an output directory (or comes back from run_in_memory). Point it at the directory and match the filename prefix (osm for the minimal/EEC configs):

from pathlib import Path
from osmose.results import OsmoseResults

results = OsmoseResults(Path("output"), prefix="osm")
print(results.list_outputs()[:5])     # what's available

The time-series accessors return wide DataFrames — a Time column, one column per species, and a constant species label column:

bio = results.biomass()
#        Time       Anchovy        Hake species
# 0  0.000000    1833.333333  500.000000     all
# ...

Common accessors (all return a pandas.DataFrame):

Method

Content

biomass(), abundance()

standing stock over time

yield_biomass(), yield_abundance()

catch over time

mortality(), mortality_rate()

mortality (by cause)

mean_size(), mean_trophic_level()

community indicators

biomass_by_age(), biomass_by_size()

structured distributions

Selecting one species: pick the column — the species= argument filters the row-label column (which is "all" on the standard wide files) and will return an empty frame if you pass a species name:

bio = results.biomass()
anchovy = bio[["Time", "Anchovy"]]          # correct: select the column
# anchovy_mean = bio["Anchovy"].mean()

Spatial / NetCDF outputs (when output.spatial.enabled=true) load as xarray.Dataset:

ds = results.read_netcdf("osm_spatial_biomass_Simu0.nc")   # or results.spatial_biomass(...)

3. Compare two runs

run_delta ranks the per-species change between a baseline and a variant. It takes two OsmoseResults objects (not paths):

from osmose.config.reader import OsmoseConfigReader
from osmose.engine import PythonEngine
from osmose.analysis import run_delta, format_delta_report
from pathlib import Path

config = OsmoseConfigReader().read(Path("data/minimal/osm_all-parameters.csv"))
baseline = PythonEngine().run_in_memory(config=config, seed=1)
variant  = PythonEngine().run_in_memory(config=config, seed=2)

deltas = run_delta(baseline, variant, metric="biomass", window_years=5)
for d in deltas:
    print(d.species, d.baseline_mean, d.variant_mean, d.pct_delta)

print(format_delta_report(deltas, metric="biomass", window_years=5))   # markdown report

Each SpeciesDelta carries species, baseline_mean, variant_mean, abs_delta, pct_delta (None for a zero-baseline species), and from_zero. metric accepts "biomass", "yield", or "abundance".

From the command line, against two output directories:

python scripts/compare_runs.py --baseline output_a/ --variant output_b/ \
    --metric biomass --window-years 10 --report delta.md

4. Calibrate

The Baltic calibration driver wraps the optimizers (Differential Evolution, CMA-ES, GP-surrogate DE) behind one CLI. It is long-running and writes results to data/calibration_history/:

python scripts/calibrate_baltic.py --optimizer de --phase 12 --maxiter 100 --seeds 3

Read a finished run back into Python:

from osmose.calibration import list_runs, load_run

runs = list_runs()                       # list[dict] from data/calibration_history
if runs:
    data = load_run(Path(runs[-1]["path"]))
    print(data["results"]["best_objective"])

The Calibration tab in the web UI shows a live dashboard for an in-flight run (optimizer/phase/generation, a per-species ICES proxy table, convergence). For a custom calibration problem, osmose.calibration.OsmoseCalibrationProblem is the programmatic entry point (set use_java_engine=True for bit-exact Java evaluation — see §6).


5. Post-run diagnostics (CLI)

These read an existing output directory and emit a report / JSON / plot:

Script

Purpose

Key args

scripts/validate_outputs_vs_ices.py

model biomass vs ICES SAG envelopes (in-range / overshoot)

--results-dir, --ices-window YYYY-YYYY

scripts/compute_size_spectrum.py

community size spectrum (slope, LFI, mean size)

--results-dir, --lfi-threshold-cm

scripts/compute_mortality_balance.py

fishing-vs-natural mortality (F/M) per species

--results-dir, --species

scripts/check_config.py

structured config parse diagnostics

--config

python scripts/validate_outputs_vs_ices.py --results-dir output/ --report ices.md
python scripts/compute_size_spectrum.py    --results-dir output/ --plot spectrum.png

Pass --help to any script for its full argument list.


6. Choose an engine & reproduce results

Python engine

Java engine

Dependency

Python only

Java 17+ and an OSMOSE JAR

Entry point

PythonEngine

OsmoseRunner (async)

Use it for

everyday runs, calibration, the UI

bit-exact parity against upstream Java

Reproducibility. Setting simulation.rng.fixed=true makes a single config + seed reproducible across Python-engine runs. It does not make the Python engine bit-equal to the Java engine: NumPy uses PCG64, the Java engine uses MT19937 (java.util.Random), and the streams diverge at the first draw. Cross-engine numerical equivalence is “within 1 order of magnitude” per the parity suite (14/14 EEC, 8/8 Bay of Biscay). If you need byte-exact reproducibility against Java, run the Java path — for calibration, OsmoseCalibrationProblem(use_java_engine=True). (Full caveat in the osmose/engine/rng.py module docstring.)


Where to go next

Goal

Doc

Hands-on: run and perturb a Baltic ecosystem in 30 min

tutorials/30-minute-ecosystem.md

The Baltic example config and where its parameters come from

baltic_example.md

Model F/biomass vs ICES advice

baltic_ices_validation_2026-04-18.md

Python-vs-Java port status

parity-roadmap.md

Short API reference (engine, config I/O, results, calibration)

README → API sketch

Per-release change history

../CHANGELOG.md