Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@
- [ ] The PEtab problem author(s) are assigned to the GitHub issue
- [ ] The README has been updated with `bmp-create-overview --update` (requires `pip install -e src/python` from the repository root)
- [ ] The new PEtab problem row in the generated table has the correct reference (and other entries)
- [ ] If this PR results in a problem having both `v1/` and `v2/` encodings, I've confirmed they satisfy the v1/v2 equivalence policy in [CONTRIBUTING.md](../CONTRIBUTING.md#v1v2-equivalence)
20 changes: 16 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@ New submissions are very welcome! Please check our [pull request template](.gith

Simply create a new branch and open a pull request with your files. We will then check your model and merge it into the collection.

Each problem lives under `problems/<ProblemID>/`:
- `v1/` contains the PEtab problem itself: `problem.yaml`, `model.xml`, `conditions.tsv`,
`measurements.tsv`, `observables.tsv`, `parameters.tsv`, and optionally `simulations.tsv`
and `visualizations.tsv`.
Each problem lives under `problems/<ProblemID>/`, with at least one of `v1/`/`v2/` present:
- `v1/` holds the problem in PEtab v1 format: `problem.yaml`, `model.xml`,
`conditions.tsv`, `measurements.tsv`, `observables.tsv`, `parameters.tsv`, and optionally
`simulations.tsv` and `visualizations.tsv`.
- `v2/` holds the same problem in PEtab v2 format.
- `resources/` (optional) holds anything else related to the problem that isn't part of the
PEtab files themselves (e.g. raw data, scripts, notebooks, figures). There's no prescribed
structure — organize it however fits your problem.

## v1/v2 equivalence

A problem's `v1/` and `v2/` encodings must represent *exactly* the same mathematical problem:
same model, same data, same objective.
Comment on lines +16 to +17

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will probably need some refinement. How do we want to deal with the whole parameter scaling topic? Encode it in the model or condition table, ignore it, ...?


This is a constraint on the math, not the encoding: `v2/` files may (and often should) look
structurally different from a mechanical v1-to-v2 port to make idiomatic use of v2 features
(experiments table, v2 prior/distribution syntax, mapping tables, ...) where they fit better.
They just have to evaluate to the same log-likelihood / log-posterior / gradient at nominal
parameters as `v1/`.
1 change: 1 addition & 0 deletions src/python/benchmark_models_petab/C.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

# layout of a single problem directory (`<MODELS_DIR>/<ProblemID>/...`)
V1_DIRNAME: str = "v1"
V2_DIRNAME: str = "v2"
PROBLEM_FILENAME: str = "problem.yaml"
SIMULATIONS_FILENAME: str = "simulations.tsv"

Expand Down
3 changes: 2 additions & 1 deletion src/python/benchmark_models_petab/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
Python tool to access the model collection.
"""

from .base import get_problem, get_problem_yaml_path, get_simulation_df
from . import v1, v2
from .v1 import get_problem, get_problem_yaml_path, get_simulation_df
from .C import MODEL_DIRS, MODELS, MODELS_DIR
from .overview import get_overview_df
from importlib.metadata import PackageNotFoundError, version
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Get a petab problem from the collection."""
"""Get a PEtab v1 problem from the collection."""

from pathlib import Path

Expand Down
65 changes: 65 additions & 0 deletions src/python/benchmark_models_petab/v2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Get a PEtab v2 problem from the collection."""

from pathlib import Path

import pandas as pd
import petab.v2 as petab

from .C import MODELS_DIR, PROBLEM_FILENAME, SIMULATIONS_FILENAME, V2_DIRNAME


def get_problem_yaml_path(id_: str) -> Path:
"""Get the path to the PEtab v2 problem YAML file.

Parameters
----------
id_: Problem name, as in `benchmark_models_petab.MODELS`.

Returns
-------
The path to the PEtab v2 problem YAML file.
"""
yaml_path = Path(MODELS_DIR, id_, V2_DIRNAME, PROBLEM_FILENAME)
if not yaml_path.exists():
raise ValueError(
f"Could not find a v2 YAML for problem with ID `{id_}`. "
"Most problems in this collection do not have a v2 encoding "
"yet; see `benchmark_models_petab.v1` for the v1 problem."
)
return yaml_path


def get_problem(id_: str) -> petab.Problem:
"""Read the PEtab v2 problem from the benchmark collection by name.

Parameters
----------
id_: Problem name, as in `benchmark_models_petab.MODELS`.

Returns
-------
The PEtab v2 problem.
"""
yaml_file = get_problem_yaml_path(id_)
return petab.Problem.from_yaml(yaml_file)


def get_simulation_df(id_: str) -> pd.DataFrame | None:
"""Get the simulation dataframe for the v2 encoding of the benchmark
collection problem with the given name.

Parameters
----------
id_: Problem name, as in `benchmark_models_petab.MODELS`.

Returns
-------
The simulation dataframe if it exists, else None.
"""
path = Path(MODELS_DIR, id_, V2_DIRNAME, SIMULATIONS_FILENAME)
if path.is_file():
return pd.read_csv(
path, sep="\t", index_col=None, float_precision="round_trip"
)

return None
7 changes: 7 additions & 0 deletions src/python/test/test_base.py → src/python/test/test_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,10 @@ def test_get_problem():
def test_get_simulation_df():
assert models.get_simulation_df("Elowitz_Nature2000").empty is False
assert models.get_simulation_df("not a problem name") is None


def test_v1_submodule_matches_top_level():
"""The top-level functions are the v1 ones, unchanged."""
assert models.get_problem is models.v1.get_problem
assert models.get_problem_yaml_path is models.v1.get_problem_yaml_path
assert models.get_simulation_df is models.v1.get_simulation_df
25 changes: 25 additions & 0 deletions src/python/test/test_v2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Tests for the v2 accessors.

No problem in the collection has a `v2/` encoding yet, so only the
"not found" error path can be tested here. Once real `problems/<id>/v2/`
directories exist, add a happy-path test (`get_problem`/`get_simulation_df`
on an actual v2 problem) alongside these.
"""

import pytest

import benchmark_models_petab as models


def test_get_problem_yaml_path_raises_for_missing_v2():
with pytest.raises(ValueError, match="v2"):
models.v2.get_problem_yaml_path(models.MODELS[0])


def test_get_problem_raises_for_missing_v2():
with pytest.raises(ValueError, match="v2"):
models.v2.get_problem(models.MODELS[0])


def test_get_simulation_df_returns_none_for_missing_v2():
assert models.v2.get_simulation_df(models.MODELS[0]) is None
Loading