Skip to content
Merged
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
43 changes: 16 additions & 27 deletions petab/v2/petab1to2.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,20 +134,23 @@ def petab_files_1to2(yaml_config: Path | str | dict, output_dir: Path | str):
# records for the experiment table to be created
experiments = []

def v2_condition_id(cond_id: str) -> str:
"""Get the condition ID to use in the v2 experiment table.

Conditions without any changes (all-NaN rows) are not included in the
v2 condition table. Periods applying such a condition get an empty
condition ID ("use the model as is").
"""
if petab_problem.condition_df.loc[cond_id].notna().any():
return cond_id
return ""

def create_experiment_id(sim_cond_id: str, preeq_cond_id: str) -> str:
if not sim_cond_id and not preeq_cond_id:
return ""
# check whether the conditions will exist in the v2 condition table
sim_cond_exists = (
petab_problem.condition_df.loc[sim_cond_id].notna().any()
)
preeq_cond_exists = (
preeq_cond_id
and petab_problem.condition_df.loc[preeq_cond_id].notna().any()
)
if not sim_cond_exists and not preeq_cond_exists:
# if we have only all-NaN conditions, we don't create a new
# experiment
if not preeq_cond_id and not v2_condition_id(sim_cond_id):
# without pre-equilibration and without any changes, there is
# nothing to be described by an experiment
return ""

if preeq_cond_id:
Expand Down Expand Up @@ -179,14 +182,14 @@ def create_experiment_id(sim_cond_id: str, preeq_cond_id: str) -> str:
{
v2.C.EXPERIMENT_ID: exp_id,
v2.C.TIME: v2.C.TIME_PREEQUILIBRATION,
v2.C.CONDITION_ID: preeq_cond_id,
v2.C.CONDITION_ID: v2_condition_id(preeq_cond_id),
}
)
experiments.append(
{
v2.C.EXPERIMENT_ID: exp_id,
v2.C.TIME: 0,
v2.C.CONDITION_ID: sim_cond_id,
v2.C.CONDITION_ID: v2_condition_id(sim_cond_id),
}
)
if experiments:
Expand Down Expand Up @@ -217,20 +220,6 @@ def create_experiment_id(sim_cond_id: str, preeq_cond_id: str) -> str:
else:
measurement_df[v1.C.PREEQUILIBRATION_CONDITION_ID] = ""

if (
petab_problem.condition_df is not None
and len(
set(petab_problem.condition_df.columns) - {v1.C.CONDITION_NAME}
)
== 0
):
# we can't have "empty" conditions with no overrides in v2,
# therefore, we drop the respective condition ID completely
# TODO: or can we?
# TODO: this needs to be checked condition-wise, not globally
measurement_df[v1.C.SIMULATION_CONDITION_ID] = ""
if v1.C.PREEQUILIBRATION_CONDITION_ID in measurement_df.columns:
measurement_df[v1.C.PREEQUILIBRATION_CONDITION_ID] = ""
# condition IDs to experiment IDs
measurement_df.insert(
0,
Expand Down
129 changes: 129 additions & 0 deletions tests/v2/test_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pytest

from petab import v1, v2
from petab.v1.models.sbml_model import SbmlModel
from petab.v2 import Problem
from petab.v2.petab1to2 import petab1to2, v1v2_observable_df

Expand Down Expand Up @@ -41,6 +42,134 @@ def test_petab1to2_remote():
assert len(problem.measurements)


def _petab1to2_with_conditions(
tmp_path, condition_df: pd.DataFrame, conditions: list[tuple[str, str]]
) -> Problem:
"""Convert a minimal PEtab v1 problem with the given condition table.

``conditions`` are (preequilibrationConditionId, simulationConditionId)
pairs, one measurement per pair.
"""
problem = v1.Problem(
model=SbmlModel.from_antimony(
"A = 1; B = 0; k1 = 0.5; A -> B; k1 * A"
),
condition_df=v1.get_condition_df(condition_df),
observable_df=v1.get_observable_df(
pd.DataFrame(
{
v1.C.OBSERVABLE_ID: ["obs_B"],
v1.C.OBSERVABLE_FORMULA: ["B"],
v1.C.NOISE_FORMULA: [0.1],
}
)
),
measurement_df=v1.get_measurement_df(
pd.DataFrame(
{
v1.C.OBSERVABLE_ID: "obs_B",
v1.C.PREEQUILIBRATION_CONDITION_ID: [
preeq for preeq, _ in conditions
],
v1.C.SIMULATION_CONDITION_ID: [
sim for _, sim in conditions
],
v1.C.TIME: 1.0,
v1.C.MEASUREMENT: 0.5,
}
)
),
parameter_df=v1.get_parameter_df(
pd.DataFrame(
{
v1.C.PARAMETER_ID: ["k1"],
v1.C.PARAMETER_SCALE: [v1.C.LIN],
v1.C.LOWER_BOUND: [1e-3],
v1.C.UPPER_BOUND: [1e3],
v1.C.NOMINAL_VALUE: [0.5],
v1.C.ESTIMATE: [1],
}
)
),
)
return petab1to2(problem.to_files_generic(tmp_path))


def _measured_periods(
problem: Problem,
) -> list[list[tuple[float, list[str]]] | None]:
"""The (time, conditionIds) of the experiment periods per measurement."""
return [
None
if m.experiment_id is None
else [
(p.time, p.condition_ids) for p in problem[m.experiment_id].periods
]
for m in problem.measurements
]


def test_petab1to2_empty_conditions_preequilibration(tmp_path):
"""Pre-equilibration is kept if the condition table sets nothing."""
problem = _petab1to2_with_conditions(
tmp_path,
pd.DataFrame({v1.C.CONDITION_ID: ["c0", "steady"]}),
[("steady", "c0")],
)

assert problem.conditions == []
assert _measured_periods(problem) == [
[(v2.C.TIME_PREEQUILIBRATION, []), (0, [])]
]
assert problem.experiments[0].has_preequilibration


def test_petab1to2_mixed_empty_conditions(tmp_path):
"""Conditions that set nothing are handled condition-wise."""
problem = _petab1to2_with_conditions(
tmp_path,
pd.DataFrame(
{
v1.C.CONDITION_ID: ["c0", "c1", "preeq_set", "preeq_empty"],
"A": [1.0, None, 2.0, None],
}
),
[
("preeq_set", "c0"),
("preeq_set", "c1"),
("preeq_empty", "c0"),
("preeq_empty", "c1"),
("", "c0"),
("", "c1"),
],
)

assert {c.id for c in problem.conditions} == {"c0", "preeq_set"}
preeq = v2.C.TIME_PREEQUILIBRATION
assert _measured_periods(problem) == [
[(preeq, ["preeq_set"]), (0, ["c0"])],
[(preeq, ["preeq_set"]), (0, [])],
[(preeq, []), (0, ["c0"])],
[(preeq, []), (0, [])],
[(0, ["c0"])],
# no pre-equilibration, no changes -> no experiment
None,
]


def test_petab1to2_empty_conditions_no_preequilibration(tmp_path):
"""Without pre-equilibration, conditions that set nothing don't need an
experiment."""
problem = _petab1to2_with_conditions(
tmp_path,
pd.DataFrame({v1.C.CONDITION_ID: ["c0"]}),
[("", "c0")],
)

assert problem.experiments == []
assert _measured_periods(problem) == [None]


try:
import benchmark_models_petab

Expand Down
Loading