Skip to content

Commit 6b9bff5

Browse files
committed
Functions for adding conditions/observables/parameter to Problem
Will simplify writing test cases and interactively assembling petab problems. To be extended. Related to #220.
1 parent d3e4006 commit 6b9bff5

File tree

7 files changed

+497
-90
lines changed

7 files changed

+497
-90
lines changed

petab/v1/mapping.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Functionality related to the PEtab entity mapping table"""
2+
# TODO: Move to petab.v2.mapping
23
from pathlib import Path
34

45
import pandas as pd

petab/v1/problem.py

Lines changed: 176 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33

44
import os
55
import tempfile
6-
from collections.abc import Iterable
6+
from collections.abc import Iterable, Sequence
77
from math import nan
8+
from numbers import Number
89
from pathlib import Path, PurePosixPath
910
from typing import TYPE_CHECKING
1011
from warnings import warn
@@ -1005,3 +1006,177 @@ def n_priors(self) -> int:
10051006
return 0
10061007

10071008
return self.parameter_df[OBJECTIVE_PRIOR_PARAMETERS].notna().sum()
1009+
1010+
def add_condition(self, id_: str, name: str = None, **kwargs):
1011+
"""Add a simulation condition to the problem.
1012+
1013+
Arguments:
1014+
id_: The condition id
1015+
name: The condition name
1016+
kwargs: Parameter, value pairs to add to the condition table.
1017+
"""
1018+
record = {CONDITION_ID: [id_], **kwargs}
1019+
if name is not None:
1020+
record[CONDITION_NAME] = name
1021+
tmp_df = pd.DataFrame(record).set_index([CONDITION_ID])
1022+
if self.condition_df is None:
1023+
self.condition_df = tmp_df
1024+
else:
1025+
self.condition_df = pd.concat([self.condition_df, tmp_df])
1026+
1027+
def add_observable(
1028+
self,
1029+
id_: str,
1030+
formula: str | float | int,
1031+
noise_formula: str | float | int = None,
1032+
noise_distribution: str = None,
1033+
transform: str = None,
1034+
name: str = None,
1035+
**kwargs,
1036+
):
1037+
"""Add an observable to the problem.
1038+
1039+
Arguments:
1040+
id_: The observable id
1041+
formula: The observable formula
1042+
noise_formula: The noise formula
1043+
noise_distribution: The noise distribution
1044+
transform: The observable transformation
1045+
name: The observable name
1046+
kwargs: additional columns/values to add to the observable table
1047+
1048+
"""
1049+
record = {
1050+
OBSERVABLE_ID: [id_],
1051+
OBSERVABLE_FORMULA: [formula],
1052+
}
1053+
if name is not None:
1054+
record[OBSERVABLE_NAME] = [name]
1055+
if noise_formula is not None:
1056+
record[NOISE_FORMULA] = [noise_formula]
1057+
if noise_distribution is not None:
1058+
record[NOISE_DISTRIBUTION] = [noise_distribution]
1059+
if transform is not None:
1060+
record[OBSERVABLE_TRANSFORMATION] = [transform]
1061+
record.update(kwargs)
1062+
1063+
tmp_df = pd.DataFrame(record).set_index([OBSERVABLE_ID])
1064+
if self.observable_df is None:
1065+
self.observable_df = tmp_df
1066+
else:
1067+
self.observable_df = pd.concat([self.observable_df, tmp_df])
1068+
1069+
def add_parameter(
1070+
self,
1071+
id_: str,
1072+
estimated: bool | str | int = True,
1073+
nominal_value=None,
1074+
scale: str = None,
1075+
lb: Number = None,
1076+
ub: Number = None,
1077+
init_prior_type: str = None,
1078+
init_prior_pars: str | Sequence = None,
1079+
obj_prior_type: str = None,
1080+
obj_prior_pars: str | Sequence = None,
1081+
**kwargs,
1082+
):
1083+
"""Add a parameter to the problem.
1084+
1085+
Arguments:
1086+
id_: The parameter id
1087+
estimated: Whether the parameter is estimated
1088+
nominal_value: The nominal value of the parameter
1089+
scale: The parameter scale
1090+
lb: The lower bound of the parameter
1091+
ub: The upper bound of the parameter
1092+
init_prior_type: The type of the initialization prior distribution
1093+
init_prior_pars: The parameters of the initialization prior
1094+
distribution
1095+
obj_prior_type: The type of the objective prior distribution
1096+
obj_prior_pars: The parameters of the objective prior distribution
1097+
kwargs: additional columns/values to add to the parameter table
1098+
"""
1099+
record = {
1100+
PARAMETER_ID: [id_],
1101+
}
1102+
if estimated is not None:
1103+
record[ESTIMATE] = [
1104+
int(estimated)
1105+
if isinstance(estimated, bool | int)
1106+
else estimated
1107+
]
1108+
if nominal_value is not None:
1109+
record[NOMINAL_VALUE] = [nominal_value]
1110+
if scale is not None:
1111+
record[PARAMETER_SCALE] = [scale]
1112+
if lb is not None:
1113+
record[LOWER_BOUND] = [lb]
1114+
if ub is not None:
1115+
record[UPPER_BOUND] = [ub]
1116+
if init_prior_type is not None:
1117+
record[INITIALIZATION_PRIOR_TYPE] = [init_prior_type]
1118+
if init_prior_pars is not None:
1119+
if not isinstance(init_prior_pars, str):
1120+
init_prior_pars = PARAMETER_SEPARATOR.join(
1121+
map(str, init_prior_pars)
1122+
)
1123+
record[INITIALIZATION_PRIOR_PARAMETERS] = [init_prior_pars]
1124+
if obj_prior_type is not None:
1125+
record[OBJECTIVE_PRIOR_TYPE] = [obj_prior_type]
1126+
if obj_prior_pars is not None:
1127+
if not isinstance(obj_prior_pars, str):
1128+
obj_prior_pars = PARAMETER_SEPARATOR.join(
1129+
map(str, obj_prior_pars)
1130+
)
1131+
record[OBJECTIVE_PRIOR_PARAMETERS] = [obj_prior_pars]
1132+
record.update(kwargs)
1133+
1134+
tmp_df = pd.DataFrame(record).set_index([PARAMETER_ID])
1135+
if self.parameter_df is None:
1136+
self.parameter_df = tmp_df
1137+
else:
1138+
self.parameter_df = pd.concat([self.parameter_df, tmp_df])
1139+
1140+
def add_measurement(
1141+
self,
1142+
obs_id: str,
1143+
sim_cond_id: str,
1144+
time: float,
1145+
measurement: float,
1146+
observable_parameters: Sequence[str] = None,
1147+
noise_parameters: Sequence[str] = None,
1148+
preeq_cond_id: str = None,
1149+
):
1150+
"""Add a measurement to the problem.
1151+
1152+
Arguments:
1153+
obs_id: The observable ID
1154+
sim_cond_id: The simulation condition ID
1155+
time: The measurement time
1156+
measurement: The measurement value
1157+
observable_parameters: The observable parameters
1158+
noise_parameters: The noise parameters
1159+
preeq_cond_id: The pre-equilibration condition ID
1160+
"""
1161+
record = {
1162+
OBSERVABLE_ID: [obs_id],
1163+
SIMULATION_CONDITION_ID: [sim_cond_id],
1164+
TIME: [time],
1165+
MEASUREMENT: [measurement],
1166+
}
1167+
if observable_parameters is not None:
1168+
record[OBSERVABLE_PARAMETERS] = [
1169+
PARAMETER_SEPARATOR.join(observable_parameters)
1170+
]
1171+
if noise_parameters is not None:
1172+
record[NOISE_PARAMETERS] = [
1173+
PARAMETER_SEPARATOR.join(noise_parameters)
1174+
]
1175+
if preeq_cond_id is not None:
1176+
record[PREEQUILIBRATION_CONDITION_ID] = [preeq_cond_id]
1177+
1178+
tmp_df = pd.DataFrame(record)
1179+
if self.measurement_df is None:
1180+
self.measurement_df = tmp_df
1181+
else:
1182+
self.measurement_df = pd.concat([self.measurement_df, tmp_df])

petab/v2/petab1to2.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ def _copy_file(src: Path | str, dest: Path | str):
136136
src = str(src)
137137
dest = str(dest)
138138

139+
if src == dest:
140+
return
141+
139142
if is_url(src):
140143
with get_handle(src, mode="r") as src_handle:
141144
with open(dest, "w") as dest_handle:

0 commit comments

Comments
 (0)