Skip to content

Commit eb85bf5

Browse files
committed
Add v2.Problem.{get_output_parameters,get_x_nominal_dict}
* Move `get_output_parameters` from v2.lint to v2.Problem * Add `Problem.get_x_nominal_dict` * Test
1 parent 6f0891e commit eb85bf5

File tree

3 files changed

+160
-64
lines changed

3 files changed

+160
-64
lines changed

petab/v2/core.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import tempfile
99
import traceback
1010
from abc import abstractmethod
11+
from collections import OrderedDict
1112
from collections.abc import Sequence
1213
from enum import Enum
1314
from itertools import chain
@@ -1691,6 +1692,27 @@ def get_x_nominal(self, free: bool = True, fixed: bool = True) -> list:
16911692

16921693
return self._apply_mask(v, free=free, fixed=fixed)
16931694

1695+
def get_x_nominal_dict(
1696+
self, free: bool = True, fixed: bool = True
1697+
) -> dict[str, float]:
1698+
"""Get parameter nominal values as dict.
1699+
1700+
:param free:
1701+
Whether to return free parameters, i.e. parameters to estimate.
1702+
:param fixed:
1703+
Whether to return fixed parameters, i.e. parameters not to
1704+
estimate.
1705+
:returns:
1706+
A dictionary mapping parameter IDs to their nominal values.
1707+
"""
1708+
return dict(
1709+
zip(
1710+
self.get_x_ids(free=free, fixed=fixed),
1711+
self.get_x_nominal(free=free, fixed=fixed),
1712+
strict=True,
1713+
)
1714+
)
1715+
16941716
@property
16951717
def x_nominal(self) -> list:
16961718
"""Parameter table nominal values"""
@@ -2257,6 +2279,67 @@ def get_measurements_for_experiment(
22572279
if measurement.experiment_id == experiment.id
22582280
]
22592281

2282+
def get_output_parameters(
2283+
self, observables: bool = True, noise: bool = True
2284+
) -> list[str]:
2285+
"""Get output parameters
2286+
2287+
Returns IDs of symbols used in observable and noise formulas that are
2288+
not observables and that are not defined in the model.
2289+
2290+
:param problem:
2291+
The PEtab problem
2292+
:param observables:
2293+
Include parameters from observableFormulas
2294+
:param noise:
2295+
Include parameters from noiseFormulas
2296+
:returns:
2297+
List of output parameter IDs, including any placeholder parameters.
2298+
"""
2299+
# collect free symbols from observable and noise formulas,
2300+
# skipping observable IDs
2301+
candidates = set()
2302+
if observables:
2303+
candidates |= {
2304+
str_sym
2305+
for o in self.observables
2306+
if o.formula is not None
2307+
for sym in o.formula.free_symbols
2308+
if (str_sym := str(sym)) != o.id
2309+
}
2310+
if noise:
2311+
candidates |= {
2312+
str_sym
2313+
for o in self.observables
2314+
if o.noise_formula is not None
2315+
for sym in o.noise_formula.free_symbols
2316+
if (str_sym := str(sym)) != o.id
2317+
}
2318+
2319+
output_parameters = OrderedDict()
2320+
2321+
# filter out symbols that are defined in the model or mapped to
2322+
# such symbols
2323+
for candidate in sorted(candidates):
2324+
if self.model.symbol_allowed_in_observable_formula(candidate):
2325+
continue
2326+
2327+
# does it map to a model entity?
2328+
for mapping in self.mappings:
2329+
if (
2330+
mapping.petab_id == candidate
2331+
and mapping.model_id is not None
2332+
):
2333+
if self.model.symbol_allowed_in_observable_formula(
2334+
mapping.model_id
2335+
):
2336+
break
2337+
else:
2338+
# no mapping to a model entity, so it is an output parameter
2339+
output_parameters[candidate] = None
2340+
2341+
return list(output_parameters.keys())
2342+
22602343

22612344
class ModelFile(BaseModel):
22622345
"""A file in the PEtab problem configuration."""

petab/v2/lint.py

Lines changed: 3 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ def run(self, problem: Problem) -> ValidationIssue | None:
396396
if problem.model
397397
else set()
398398
)
399-
allowed_targets |= set(get_output_parameters(problem))
399+
allowed_targets |= set(problem.get_output_parameters())
400400
allowed_targets |= {
401401
m.petab_id for m in problem.mappings if m.model_id is not None
402402
}
@@ -932,7 +932,7 @@ def get_valid_parameters_for_parameter_table(
932932
parameter_ids[mapping.petab_id] = None
933933

934934
# add output parameters from observable table
935-
output_parameters = get_output_parameters(problem)
935+
output_parameters = problem.get_output_parameters()
936936
for p in output_parameters:
937937
if p not in invalid:
938938
parameter_ids[p] = None
@@ -1007,8 +1007,7 @@ def append_overrides(overrides):
10071007
{"noise": True, "observables": True},
10081008
),
10091009
):
1010-
output_parameters = get_output_parameters(
1011-
problem,
1010+
output_parameters = problem.get_output_parameters(
10121011
**formula_type,
10131012
)
10141013
placeholders = get_placeholders(
@@ -1034,66 +1033,6 @@ def append_overrides(overrides):
10341033
return parameter_ids
10351034

10361035

1037-
def get_output_parameters(
1038-
problem: Problem,
1039-
observables: bool = True,
1040-
noise: bool = True,
1041-
) -> list[str]:
1042-
"""Get output parameters
1043-
1044-
Returns IDs of symbols used in observable and noise formulas that are
1045-
not observables and that are not defined in the model.
1046-
1047-
Arguments:
1048-
problem: The PEtab problem
1049-
observables: Include parameters from observableFormulas
1050-
noise: Include parameters from noiseFormulas
1051-
1052-
Returns:
1053-
List of output parameter IDs, including any placeholder parameters.
1054-
"""
1055-
# collect free symbols from observable and noise formulas,
1056-
# skipping observable IDs
1057-
candidates = set()
1058-
if 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-
}
1066-
if noise:
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-
}
1074-
1075-
output_parameters = OrderedDict()
1076-
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
1093-
1094-
return list(output_parameters.keys())
1095-
1096-
10971036
def get_placeholders(
10981037
problem: Problem,
10991038
observables: bool = True,

tests/v2/test_core.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,3 +739,77 @@ def make_yaml(id_line: str) -> str:
739739
f.write(make_yaml(""))
740740
problem = Problem.from_yaml(filepath)
741741
assert problem.id is None
742+
743+
744+
def test_parameter_accessors(): # pylint: disable=W0621
745+
"""
746+
Test the petab.Problem functions to get parameter values.
747+
"""
748+
petab_problem = Problem()
749+
petab_problem += Parameter(
750+
id="par1", lb=0, ub=100, nominal_value=7, estimate=True
751+
)
752+
petab_problem += Parameter(
753+
id="par2", lb=0.1, ub=100, nominal_value=8, estimate=True
754+
)
755+
petab_problem += Parameter(
756+
id="par3", lb=0.1, ub=200, nominal_value=9, estimate=False
757+
)
758+
759+
assert petab_problem.x_ids == ["par1", "par2", "par3"]
760+
assert petab_problem.x_free_ids == ["par1", "par2"]
761+
assert petab_problem.x_fixed_ids == ["par3"]
762+
assert petab_problem.lb == [0, 0.1, 0.1]
763+
assert petab_problem.ub == [100, 100, 200]
764+
assert petab_problem.x_nominal == [7, 8, 9]
765+
assert petab_problem.x_nominal_free == [7, 8]
766+
assert petab_problem.x_nominal_fixed == [9]
767+
768+
assert (
769+
petab_problem.get_x_nominal_dict()
770+
== petab_problem.get_x_nominal_dict(free=True, fixed=True)
771+
== {
772+
"par1": 7,
773+
"par2": 8,
774+
"par3": 9,
775+
}
776+
)
777+
assert petab_problem.get_x_nominal_dict(free=True, fixed=False) == {
778+
"par1": 7,
779+
"par2": 8,
780+
}
781+
assert petab_problem.get_x_nominal_dict(free=False, fixed=True) == {
782+
"par3": 9,
783+
}
784+
785+
786+
def test_get_output_parameters():
787+
"""Test Problem.get_output_parameters"""
788+
petab_problem = Problem()
789+
assert petab_problem.get_output_parameters() == []
790+
791+
petab_problem += Parameter(id="p1", lb=0, ub=100, estimate=True)
792+
petab_problem.models.append(SbmlModel.from_antimony("p2 = 1"))
793+
assert petab_problem.get_output_parameters() == []
794+
795+
petab_problem += Observable(
796+
id="obs1", formula="p1 + p2", noise_formula="p1 * p2"
797+
)
798+
assert petab_problem.get_output_parameters() == ["p1"]
799+
800+
petab_problem += Observable(
801+
id="obs1",
802+
formula="p3 + p4",
803+
noise_formula="p3 * p5",
804+
)
805+
assert (
806+
petab_problem.get_output_parameters()
807+
== petab_problem.get_output_parameters(observables=True, noise=True)
808+
== ["p1", "p3", "p4", "p5"]
809+
)
810+
assert petab_problem.get_output_parameters(
811+
observables=True, noise=False
812+
) == ["p1", "p3", "p4"]
813+
assert petab_problem.get_output_parameters(
814+
observables=False, noise=True
815+
) == ["p1", "p3", "p5"]

0 commit comments

Comments
 (0)