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) |
|
2 |
Eastern English Channel |
|
14 |
Baltic Sea |
|
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¶
Python engine (recommended — no Java required)¶
A config is a plain dict[str, str] produced by the config reader. Hand it to
PythonEngine, which has two run modes:
from pathlib import Path
from osmose.config.reader import OsmoseConfigReader
from osmose.engine import PythonEngine
config = OsmoseConfigReader().read(Path("data/minimal/osm_all-parameters.csv"))
engine = PythonEngine() # backend="numpy" by default
# (a) In memory — no files written; returns an OsmoseResults you can query directly.
results = engine.run_in_memory(config=config, seed=42)
# (b) To disk — writes the usual OSMOSE CSV/NetCDF tree; returns a RunResult.
run = engine.run(config=config, output_dir=Path("output"), seed=42)
print(run.status, run.returncode) # -> ok 0
Reproducible ensembles over seeds:
runs = engine.run_ensemble(config=config, output_dir=Path("output_ens"), n=5, base_seed=0)
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 |
|---|---|
|
standing stock over time |
|
catch over time |
|
mortality (by cause) |
|
community indicators |
|
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 |
|---|---|---|
|
model biomass vs ICES SAG envelopes (in-range / overshoot) |
|
|
community size spectrum (slope, LFI, mean size) |
|
|
fishing-vs-natural mortality (F/M) per species |
|
|
structured config parse diagnostics |
|
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 |
|
|
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 |
|
The Baltic example config and where its parameters come from |
|
Model F/biomass vs ICES advice |
|
Python-vs-Java port status |
|
Short API reference (engine, config I/O, results, calibration) |
|
Per-release change history |