Skip to content

Commit cd8f195

Browse files
authored
Misc petab.v2 fixes/cleanup (#445)
* Remove `Problem.from_dfs` which isn't really useful * Resolve some TODOs * Fix doctests * Fix `get_output_parameters` to not include observable IDs and make more efficient
1 parent 004be14 commit cd8f195

File tree

2 files changed

+59
-95
lines changed

2 files changed

+59
-95
lines changed

petab/v2/core.py

Lines changed: 21 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -992,16 +992,13 @@ def _validate(self) -> Self:
992992
"Estimated parameter must have lower and upper bounds set"
993993
)
994994

995-
# TODO: also if not estimated?
996-
if (
997-
self.estimate
998-
and self.lb is not None
999-
and self.ub is not None
1000-
and self.lb >= self.ub
1001-
):
1002-
raise ValueError("Lower bound must be less than upper bound.")
995+
if self.lb is not None and self.ub is not None and self.lb > self.ub:
996+
raise ValueError(
997+
"Lower bound must be less than or equal to upper bound."
998+
)
1003999

1004-
# TODO priorType, priorParameters
1000+
# NOTE: priorType and priorParameters are currently checked in
1001+
# `CheckPriorDistribution`
10051002

10061003
return self
10071004

@@ -1294,50 +1291,6 @@ def from_yaml(
12941291
mapping_tables=mapping_tables,
12951292
)
12961293

1297-
@staticmethod
1298-
def from_dfs(
1299-
model: Model = None,
1300-
condition_df: pd.DataFrame = None,
1301-
experiment_df: pd.DataFrame = None,
1302-
measurement_df: pd.DataFrame = None,
1303-
parameter_df: pd.DataFrame = None,
1304-
observable_df: pd.DataFrame = None,
1305-
mapping_df: pd.DataFrame = None,
1306-
config: ProblemConfig = None,
1307-
):
1308-
"""
1309-
Construct a PEtab problem from dataframes.
1310-
1311-
Parameters:
1312-
condition_df: PEtab condition table
1313-
experiment_df: PEtab experiment table
1314-
measurement_df: PEtab measurement table
1315-
parameter_df: PEtab parameter table
1316-
observable_df: PEtab observable table
1317-
mapping_df: PEtab mapping table
1318-
model: The underlying model
1319-
config: The PEtab problem configuration
1320-
"""
1321-
# TODO: do we really need this?
1322-
1323-
observable_table = ObservableTable.from_df(observable_df)
1324-
condition_table = ConditionTable.from_df(condition_df)
1325-
experiment_table = ExperimentTable.from_df(experiment_df)
1326-
measurement_table = MeasurementTable.from_df(measurement_df)
1327-
mapping_table = MappingTable.from_df(mapping_df)
1328-
parameter_table = ParameterTable.from_df(parameter_df)
1329-
1330-
return Problem(
1331-
models=[model],
1332-
condition_tables=[condition_table],
1333-
experiment_tables=[experiment_table],
1334-
observable_tables=[observable_table],
1335-
measurement_tables=[measurement_table],
1336-
parameter_tables=[parameter_table],
1337-
mapping_tables=[mapping_table],
1338-
config=config,
1339-
)
1340-
13411294
@staticmethod
13421295
def from_combine(filename: Path | str) -> Problem:
13431296
"""Read PEtab COMBINE archive (http://co.mbine.org/documents/archive).
@@ -2235,6 +2188,7 @@ def model_dump(self, **kwargs) -> dict[str, Any]:
22352188
'experiment_files': [],
22362189
'extensions': {},
22372190
'format_version': '2.0.0',
2191+
'id': None,
22382192
'mapping_files': [],
22392193
'measurement_files': [],
22402194
'model_files': {},
@@ -2343,19 +2297,25 @@ class ProblemConfig(BaseModel):
23432297
#: The problem ID.
23442298
id: str | None = None
23452299

2346-
#: The path to the parameter file, relative to ``base_path``.
2347-
# TODO https://github.com/PEtab-dev/PEtab/pull/641:
2348-
# rename to parameter_files in yaml for consistency with other files?
2349-
# always a list?
2350-
parameter_files: list[AnyUrl | Path] = Field(
2351-
default=[], alias=C.PARAMETER_FILES
2352-
)
2353-
2300+
#: The paths to the parameter tables.
2301+
# Absolute or relative to `base_path`.
2302+
parameter_files: list[AnyUrl | Path] = []
2303+
#: The model IDs and files used by the problem (`id->ModelFile`).
23542304
model_files: dict[str, ModelFile] | None = {}
2305+
#: The paths to the measurement tables.
2306+
# Absolute or relative to `base_path`.
23552307
measurement_files: list[AnyUrl | Path] = []
2308+
#: The paths to the condition tables.
2309+
# Absolute or relative to `base_path`.
23562310
condition_files: list[AnyUrl | Path] = []
2311+
#: The paths to the experiment tables.
2312+
# Absolute or relative to `base_path`.
23572313
experiment_files: list[AnyUrl | Path] = []
2314+
#: The paths to the observable tables.
2315+
# Absolute or relative to `base_path`.
23582316
observable_files: list[AnyUrl | Path] = []
2317+
#: The paths to the mapping tables.
2318+
# Absolute or relative to `base_path`.
23592319
mapping_files: list[AnyUrl | Path] = []
23602320

23612321
#: Extensions used by the problem.

petab/v2/lint.py

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -992,11 +992,6 @@ def append_overrides(overrides):
992992
append_overrides(m.observable_parameters)
993993
append_overrides(m.noise_parameters)
994994

995-
# TODO remove `observable_ids` when
996-
# `get_output_parameters` is updated for PEtab v2/v1.1, where
997-
# observable IDs are allowed in observable formulae
998-
observable_ids = {o.id for o in problem.observables}
999-
1000995
# Add output parameters except for placeholders
1001996
for formula_type, placeholder_sources in (
1002997
(
@@ -1021,9 +1016,7 @@ def append_overrides(overrides):
10211016
**placeholder_sources,
10221017
)
10231018
parameter_ids.update(
1024-
p
1025-
for p in output_parameters
1026-
if p not in placeholders and p not in observable_ids
1019+
p for p in output_parameters if p not in placeholders
10271020
)
10281021

10291022
# Add condition table parametric overrides unless already defined in the
@@ -1048,44 +1041,55 @@ def get_output_parameters(
10481041
) -> list[str]:
10491042
"""Get output parameters
10501043
1051-
Returns IDs of parameters used in observable and noise formulas that are
1052-
not defined in the model.
1044+
Returns IDs of symbols used in observable and noise formulas that are
1045+
not observables and that are not defined in the model.
10531046
10541047
Arguments:
10551048
problem: The PEtab problem
10561049
observables: Include parameters from observableFormulas
10571050
noise: Include parameters from noiseFormulas
10581051
10591052
Returns:
1060-
List of output parameter IDs
1053+
List of output parameter IDs, including any placeholder parameters.
10611054
"""
1062-
formulas = []
1055+
# collect free symbols from observable and noise formulas,
1056+
# skipping observable IDs
1057+
candidates = set()
10631058
if observables:
1064-
formulas.extend(o.formula for o in problem.observables)
1059+
candidates |= {
1060+
str_sym
1061+
for o in problem.observables
1062+
if o.formula is not None
1063+
for sym in o.formula.free_symbols
1064+
if (str_sym := str(sym)) != o.id
1065+
}
10651066
if noise:
1066-
formulas.extend(o.noise_formula for o in problem.observables)
1067-
output_parameters = OrderedDict()
1067+
candidates |= {
1068+
str_sym
1069+
for o in problem.observables
1070+
if o.noise_formula is not None
1071+
for sym in o.noise_formula.free_symbols
1072+
if (str_sym := str(sym)) != o.id
1073+
}
10681074

1069-
for formula in formulas:
1070-
free_syms = sorted(
1071-
formula.free_symbols,
1072-
key=lambda symbol: symbol.name,
1073-
)
1074-
for free_sym in free_syms:
1075-
sym = str(free_sym)
1076-
if problem.model.symbol_allowed_in_observable_formula(sym):
1077-
continue
1075+
output_parameters = OrderedDict()
10781076

1079-
# does it map to a model entity?
1080-
for mapping in problem.mappings:
1081-
if mapping.petab_id == sym and mapping.model_id is not None:
1082-
if problem.model.symbol_allowed_in_observable_formula(
1083-
mapping.model_id
1084-
):
1085-
break
1086-
else:
1087-
# no mapping to a model entity, so it is an output parameter
1088-
output_parameters[sym] = None
1077+
# filter out symbols that are defined in the model or mapped to
1078+
# such symbols
1079+
for candidate in sorted(candidates):
1080+
if problem.model.symbol_allowed_in_observable_formula(candidate):
1081+
continue
1082+
1083+
# does it map to a model entity?
1084+
for mapping in problem.mappings:
1085+
if mapping.petab_id == candidate and mapping.model_id is not None:
1086+
if problem.model.symbol_allowed_in_observable_formula(
1087+
mapping.model_id
1088+
):
1089+
break
1090+
else:
1091+
# no mapping to a model entity, so it is an output parameter
1092+
output_parameters[candidate] = None
10891093

10901094
return list(output_parameters.keys())
10911095

0 commit comments

Comments
 (0)