Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PetabProblem class for handling PEtab-defined simulation con… #2255

Merged
merged 15 commits into from
Jan 16, 2024
355 changes: 92 additions & 263 deletions python/examples/example_petab/petab.ipynb

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions python/sdist/amici/petab/conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,11 @@ def create_edatas(
if PREEQUILIBRATION_CONDITION_ID in simulation_conditions:
measurement_groupvar.append(petab.PREEQUILIBRATION_CONDITION_ID)
measurement_dfs = dict(
list(petab_problem.measurement_df.groupby(measurement_groupvar))
list(
petab_problem.measurement_df.fillna(
{PREEQUILIBRATION_CONDITION_ID: ""}
).groupby(measurement_groupvar)
)
)

edatas = []
Expand All @@ -405,10 +409,11 @@ def create_edatas(
if PREEQUILIBRATION_CONDITION_ID in condition:
measurement_index = (
condition.get(SIMULATION_CONDITION_ID),
condition.get(PREEQUILIBRATION_CONDITION_ID),
condition.get(PREEQUILIBRATION_CONDITION_ID) or "",
)
else:
measurement_index = (condition.get(SIMULATION_CONDITION_ID),)

edata = create_edata_for_condition(
condition=condition,
amici_model=amici_model,
Expand Down
277 changes: 277 additions & 0 deletions python/sdist/amici/petab/petab_problem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
"""PEtab-problem based simulations."""
import copy
from typing import Optional, Union

import amici
import pandas as pd
import petab
from petab.C import PREEQUILIBRATION_CONDITION_ID, SIMULATION_CONDITION_ID

from .conditions import create_edatas, fill_in_parameters
from .parameter_mapping import create_parameter_mapping


class PetabProblem:

Check warning on line 14 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L14

Added line #L14 was not covered by tests
Copy link
Member

Choose a reason for hiding this comment

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

why is codecov not reporting any coverage here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oops. Because I started putting tests in a separate PEtab directory which was not included in the pytest invocation.

"""Manage experimental conditions based on a PEtab problem definition.

Create :class:`ExpData` objects from a PEtab problem definition, and handle
parameter scales and parameter mapping.

:param petab_problem: PEtab problem definition.
:param amici_model: AMICI model
:param problem_parameters: Problem parameters to use for simulation
(default: PEtab nominal values and model values).
:param scaled_parameters: Whether the provided parameters are on PEtab
`parameterScale` or not.
:param simulation_conditions: Simulation conditions to use for simulation.
It can be used to subset the conditions in the PEtab problem.
All subsequent operations will only be performed on that subset.
By default, all conditions are used.
:param store_edatas: Whether to create and store all `ExpData` objects for
all conditions upfront. If set to ``False``, `ExpData` objects will be
created and disposed of on the fly during simulation. The latter saves
memory if the given PEtab problem comprises many simulation conditions.
"""

def __init__(

Check warning on line 36 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L36

Added line #L36 was not covered by tests
self,
petab_problem: petab.Problem,
amici_model: Optional[amici.Model] = None,
problem_parameters: Optional[dict[str, float]] = None,
scaled_parameters: Optional[bool] = False,
simulation_conditions: Union[pd.DataFrame, list[dict]] = None,
store_edatas: bool = True,
):
self._petab_problem = copy.deepcopy(petab_problem)

Check warning on line 45 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L45

Added line #L45 was not covered by tests

if amici_model is not None:
self._amici_model = amici_model

Check warning on line 48 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L47-L48

Added lines #L47 - L48 were not covered by tests
else:
from .petab_import import import_petab_problem

self._amici_model = import_petab_problem(petab_problem)

self._scaled_parameters = scaled_parameters

Check warning on line 54 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L54

Added line #L54 was not covered by tests

self._simulation_conditions = simulation_conditions or (

Check warning on line 56 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L56

Added line #L56 was not covered by tests
petab_problem.get_simulation_conditions_from_measurement_df()
)
if not isinstance(self._simulation_conditions, pd.DataFrame):
self._simulation_conditions = pd.DataFrame(

Check warning on line 60 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L59-L60

Added lines #L59 - L60 were not covered by tests
self._simulation_conditions
)
if (

Check warning on line 63 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L63

Added line #L63 was not covered by tests
preeq_id := PREEQUILIBRATION_CONDITION_ID
) in self._simulation_conditions:
self._simulation_conditions[

Check warning on line 66 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L66

Added line #L66 was not covered by tests
preeq_id
] = self._simulation_conditions[preeq_id].fillna("")

if problem_parameters is None:

Check warning on line 70 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L70

Added line #L70 was not covered by tests
# Use PEtab nominal values as default
self._problem_parameters = self._default_parameters()
if scaled_parameters is True:

Check warning on line 73 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L72-L73

Added lines #L72 - L73 were not covered by tests
raise NotImplementedError(
"scaled_parameters=True in combination with default "
"parameters is not implemented yet."
)
scaled_parameters = False

Check warning on line 78 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L78

Added line #L78 was not covered by tests
Copy link
Member

Choose a reason for hiding this comment

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

remove?

else:
self._problem_parameters = problem_parameters
self._scaled_parameters = scaled_parameters

Check warning on line 81 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L80-L81

Added lines #L80 - L81 were not covered by tests

if store_edatas:
self._parameter_mapping = create_parameter_mapping(

Check warning on line 84 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L83-L84

Added lines #L83 - L84 were not covered by tests
petab_problem=self._petab_problem,
simulation_conditions=self._simulation_conditions,
scaled_parameters=self._scaled_parameters,
amici_model=self._amici_model,
)
self._create_edatas()

Check warning on line 90 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L90

Added line #L90 was not covered by tests
else:
self._parameter_mapping = None
self._edatas = None

Check warning on line 93 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L92-L93

Added lines #L92 - L93 were not covered by tests

def set_parameters(

Check warning on line 95 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L95

Added line #L95 was not covered by tests
self,
problem_parameters: dict[str, float],
scaled_parameters: bool = False,
):
"""Set problem parameters.

:param problem_parameters: Problem parameters to use for simulation.
This may be a subset of all parameters.
:param scaled_parameters: Whether the provided parameters are on PEtab
`parameterScale` or not.
"""
if scaled_parameters != self._scaled_parameters:

Check warning on line 107 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L107

Added line #L107 was not covered by tests
dweindl marked this conversation as resolved.
Show resolved Hide resolved
# redo parameter mapping if scale changed
self._parameter_mapping = create_parameter_mapping(

Check warning on line 109 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L109

Added line #L109 was not covered by tests
petab_problem=self._petab_problem,
simulation_conditions=self._simulation_conditions,
scaled_parameters=scaled_parameters,
amici_model=self._amici_model,
)

if set(self._problem_parameters) - set(problem_parameters):

Check warning on line 116 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L116

Added line #L116 was not covered by tests
# not all parameters are provided - update
# bring previously set parameters to the same scale if necessary
if scaled_parameters and not self._scaled_parameters:
self._problem_parameters = (

Check warning on line 120 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L119-L120

Added lines #L119 - L120 were not covered by tests
self._petab_problem.scale_parameters(
self._problem_parameters,
)
)
elif not scaled_parameters and self._scaled_parameters:
self._problem_parameters = (

Check warning on line 126 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L125-L126

Added lines #L125 - L126 were not covered by tests
self._petab_problem.unscale_parameters(
self._problem_parameters,
)
)
self._problem_parameters |= problem_parameters

Check warning on line 131 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L131

Added line #L131 was not covered by tests
else:
self._problem_parameters = problem_parameters

Check warning on line 133 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L133

Added line #L133 was not covered by tests

self._scaled_parameters = scaled_parameters

Check warning on line 135 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L135

Added line #L135 was not covered by tests

if self._edatas:
fill_in_parameters(

Check warning on line 138 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L137-L138

Added lines #L137 - L138 were not covered by tests
edatas=self._edatas,
problem_parameters=self._problem_parameters,
scaled_parameters=self._scaled_parameters,
parameter_mapping=self._parameter_mapping,
amici_model=self._amici_model,
)

def get_edata(

Check warning on line 146 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L146

Added line #L146 was not covered by tests
self, condition_id: str, preequilibration_condition_id: str = None
) -> amici.ExpData:
"""Get ExpData object for a given condition.

NOTE: If ``store_edatas=True`` was passed to the constructor and the
returned object is modified, the changes will be reflected in the
internal `ExpData` objects. Also, if parameter values of
`PetabProblem` are changed, all `ExpData` objects will be updated.
Create a deep copy if you want to avoid this.

:param condition_id: PEtab condition ID
:param preequilibration_condition_id: PEtab preequilibration condition ID
:return: ExpData object
"""
# exists or has to be created?
if self._edatas:
edata_id = condition_id
if preequilibration_condition_id:
edata_id += "+" + preequilibration_condition_id

Check warning on line 165 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L162-L165

Added lines #L162 - L165 were not covered by tests

for edata in self._edatas:
if edata.id == edata_id:
return edata

Check warning on line 169 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L167-L169

Added lines #L167 - L169 were not covered by tests

return self._create_edata(condition_id, preequilibration_condition_id)

Check warning on line 171 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L171

Added line #L171 was not covered by tests

def get_edatas(self):

Check warning on line 173 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L173

Added line #L173 was not covered by tests
"""Get all ExpData objects.

NOTE: If ``store_edatas=True`` was passed to the constructor and the
returned objects are modified, the changes will be reflected in the
internal `ExpData` objects. Also, if parameter values of
`PetabProblem` are changed, all `ExpData` objects will be updated.
Create a deep copy if you want to avoid this.

:return: List of ExpData objects
"""
if self._edatas:

Check warning on line 184 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L184

Added line #L184 was not covered by tests
# shallow copy
return self._edatas.copy()

Check warning on line 186 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L186

Added line #L186 was not covered by tests

# not storing edatas - create and return
self._parameter_mapping = create_parameter_mapping(

Check warning on line 189 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L189

Added line #L189 was not covered by tests
petab_problem=self._petab_problem,
simulation_conditions=self._simulation_conditions,
scaled_parameters=self._scaled_parameters,
amici_model=self._amici_model,
)
self._create_edatas()
result = self._edatas
self._edatas = []
return result

Check warning on line 198 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L195-L198

Added lines #L195 - L198 were not covered by tests

def _create_edata(

Check warning on line 200 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L200

Added line #L200 was not covered by tests
self, condition_id: str, preequilibration_condition_id: str
) -> amici.ExpData:
"""Create ExpData object for a given condition.

:param condition_id: PEtab condition ID
:param preequilibration_condition_id: PEtab preequilibration condition ID
:return: ExpData object
"""
simulation_condition = pd.DataFrame(

Check warning on line 209 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L209

Added line #L209 was not covered by tests
[
{
SIMULATION_CONDITION_ID: condition_id,
PREEQUILIBRATION_CONDITION_ID: preequilibration_condition_id
or None,
}
]
)
edatas = create_edatas(

Check warning on line 218 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L218

Added line #L218 was not covered by tests
amici_model=self._amici_model,
petab_problem=self._petab_problem,
simulation_conditions=simulation_condition,
)
parameter_mapping = create_parameter_mapping(

Check warning on line 223 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L223

Added line #L223 was not covered by tests
petab_problem=self._petab_problem,
simulation_conditions=simulation_condition,
scaled_parameters=self._scaled_parameters,
amici_model=self._amici_model,
)

# Fill parameters in ExpDatas (in-place)
fill_in_parameters(

Check warning on line 231 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L231

Added line #L231 was not covered by tests
edatas=edatas,
problem_parameters={
p: self._problem_parameters[p]
for p in parameter_mapping.free_symbols
if p in self._problem_parameters
},
scaled_parameters=self._scaled_parameters,
parameter_mapping=parameter_mapping,
amici_model=self._amici_model,
)

if len(edatas) != 1:

Check warning on line 243 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L243

Added line #L243 was not covered by tests
raise AssertionError("Expected exactly one ExpData object.")
return edatas[0]

Check warning on line 245 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L245

Added line #L245 was not covered by tests

def _create_edatas(

Check warning on line 247 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L247

Added line #L247 was not covered by tests
self,
):
"""Create ExpData objects from PEtab problem definition."""
self._edatas = create_edatas(

Check warning on line 251 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L251

Added line #L251 was not covered by tests
amici_model=self._amici_model,
petab_problem=self._petab_problem,
simulation_conditions=self._simulation_conditions,
)

fill_in_parameters(

Check warning on line 257 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L257

Added line #L257 was not covered by tests
edatas=self._edatas,
problem_parameters=self._problem_parameters,
scaled_parameters=self._scaled_parameters,
parameter_mapping=self._parameter_mapping,
amici_model=self._amici_model,
)

def _default_parameters(self) -> dict[str, float]:

Check warning on line 265 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L265

Added line #L265 was not covered by tests
"""Get unscaled default parameters."""
return {

Check warning on line 267 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L267

Added line #L267 was not covered by tests
t.Index: getattr(t, petab.NOMINAL_VALUE)
for t in self._petab_problem.parameter_df[
self._petab_problem.parameter_df[petab.ESTIMATE] == 1
].itertuples()
}

@property
def model(self) -> amici.Model:

Check warning on line 275 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L274-L275

Added lines #L274 - L275 were not covered by tests
"""AMICI model."""
return self._amici_model

Check warning on line 277 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L277

Added line #L277 was not covered by tests
Loading