Skip to content

Commit

Permalink
Add AmiciPetabProblem class for handling PEtab-defined simulation con…
Browse files Browse the repository at this point in the history
…ditions

Makes it a bit easier to work with PEtab problems interactively or when implementing some PEtab-based objective function (#962).
  • Loading branch information
dweindl committed Jan 3, 2024
1 parent da93984 commit 0574214
Showing 1 changed file with 253 additions and 0 deletions.
253 changes: 253 additions & 0 deletions python/sdist/amici/petab/petab_problem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
"""PEtab-problem based simulations."""
import copy
from typing import Optional, Sequence, Union

import amici
import pandas as pd
import petab
from petab.C import (
DATASET_ID,
NOISE_PARAMETERS,
OBSERVABLE_ID,
PREEQUILIBRATION_CONDITION_ID,
SIMULATION,
SIMULATION_CONDITION_ID,
TIME,
)

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


class AmiciPetabProblem:

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L22 was not covered by tests
"""Manage experimental conditions based on a PEtab problem definition.
Create :class:`ExpData` objects from a PEtab problem definition, handle
parameter scales and parameter mapping.
:param petab_problem: PEtab problem definition.
:param amici_model: AMICI model
:param amici_solver: AMICI solver (Solver with default options will be
used if not provided).
: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 based 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. This can save
memory if many conditions are simulated.
"""

def __init__(

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L46 was not covered by tests
self,
petab_problem: petab.Problem,
amici_model: amici.Model,
problem_parameters: Optional[dict[str, float]] = None,
# move to a separate AmiciPetabProblemSimulator class?
amici_solver: Optional[amici.Solver] = None,
scaled_parameters: Optional[bool] = False,
simulation_conditions: Union[pd.DataFrame, list[dict]] = None,
store_edatas: bool = True,
):
self._petab_problem = petab_problem
self._amici_model = amici_model
self._amici_solver = amici_solver
self._scaled_parameters = scaled_parameters

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#L57-L60

Added lines #L57 - L60 were not covered by tests

self._simulation_conditions = simulation_conditions or (

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L62 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 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#L65-L66

Added lines #L65 - L66 were not covered by tests
self._simulation_conditions
)
if (

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

View check run for this annotation

Codecov / codecov/patch

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

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

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#L73

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

if problem_parameters is None:

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L77 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 80 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L79 - L80 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 85 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

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

Added line #L85 was not covered by tests
else:
self._problem_parameters = problem_parameters
self._scaled_parameters = scaled_parameters

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

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L87-L88

Added lines #L87 - L88 were not covered by tests

if store_edatas:
self._parameter_mapping = create_parameter_mapping(

Check warning on line 91 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-L91

Added lines #L90 - L91 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 98 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

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

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

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

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L100-L101

Added lines #L100 - L101 were not covered by tests

def set_parameters(

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L103 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.
:param scaled_parameters: Whether the provided parameters are on PEtab
`parameterScale` or not.
"""
if scaled_parameters != self._scaled_parameters:

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L114 was not covered by tests
# redo parameter mapping if scale changed
self._parameter_mapping = create_parameter_mapping(

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
petab_problem=self._petab_problem,
simulation_conditions=self._simulation_conditions,
scaled_parameters=scaled_parameters,
amici_model=self._amici_model,
)

self._problem_parameters = problem_parameters
self._scaled_parameters = scaled_parameters

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

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L123-L124

Added lines #L123 - L124 were not covered by tests

if self._edatas:
fill_in_parameters(

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L126 - L127 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 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
self, condition_id: str, preequilibration_condition_id: str
) -> 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
AmiciPetabProblem 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 154 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L151-L154

Added lines #L151 - L154 were not covered by tests

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

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

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L156-L158

Added lines #L156 - L158 were not covered by tests

return self._create_edata(condition_id, preequilibration_condition_id)

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L160 was not covered by tests

def get_edatas(self):

Check warning on line 162 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

Added line #L162 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
AmiciPetabProblem 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 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
# shallow copy
return self._edatas.copy()

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L175 was not covered by tests

# not storing edatas - create and return
self._create_edatas()
result = self._edatas
self._edatas = []
return result

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

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L178-L181

Added lines #L178 - L181 were not covered by tests

def _create_edata(

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L183 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 192 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

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

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L201 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 206 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

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

Added line #L206 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 214 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

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

Added line #L214 was not covered by tests
edatas=edatas,
problem_parameters=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 222 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

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

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L224 was not covered by tests

@property
def solver(self):

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

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L226-L227

Added lines #L226 - L227 were not covered by tests
"""Get the solver."""
return self._amici_solver or self._amici_model.getSolver()

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L229 was not covered by tests

def _create_edatas(

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
self,
):
"""Create ExpData objects from PEtab problem definition."""
self._edatas = create_edatas(

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L235 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 241 in python/sdist/amici/petab/petab_problem.py

View check run for this annotation

Codecov / codecov/patch

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

Added line #L241 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]:
return {

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

View check run for this annotation

Codecov / codecov/patch

python/sdist/amici/petab/petab_problem.py#L249-L250

Added lines #L249 - L250 were not covered by tests
t.Index: getattr(t, petab.NOMINAL_VALUE)
for t in self._petab_problem.parameter_df.itertuples()
}

0 comments on commit 0574214

Please sign in to comment.