Skip to content

Commit 880741a

Browse files
dweindldilpath
andauthored
Add v2.Problem.{get_output_parameters,get_x_nominal_dict} (#447)
* 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 Co-authored-by: Dilan Pathirana <[email protected]>
1 parent 1c3b759 commit 880741a

File tree

4 files changed

+165
-72
lines changed

4 files changed

+165
-72
lines changed

petab/v1/observables.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def get_output_parameters(
8484
) -> list[str]:
8585
"""Get output parameters
8686
87-
Returns IDs of parameters used in observable and noise formulas that are
87+
Returns IDs of parameters used in observable or noise formulas that are
8888
not defined in the model.
8989
9090
Arguments:

petab/v2/core.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,6 +1693,27 @@ def get_x_nominal(self, free: bool = True, fixed: bool = True) -> list:
16931693

16941694
return self._apply_mask(v, free=free, fixed=fixed)
16951695

1696+
def get_x_nominal_dict(
1697+
self, free: bool = True, fixed: bool = True
1698+
) -> dict[str, float]:
1699+
"""Get parameter nominal values as dict.
1700+
1701+
:param free:
1702+
Whether to return free parameters, i.e. parameters to estimate.
1703+
:param fixed:
1704+
Whether to return fixed parameters, i.e. parameters not to
1705+
estimate.
1706+
:returns:
1707+
A dictionary mapping parameter IDs to their nominal values.
1708+
"""
1709+
return dict(
1710+
zip(
1711+
self.get_x_ids(free=free, fixed=fixed),
1712+
self.get_x_nominal(free=free, fixed=fixed),
1713+
strict=True,
1714+
)
1715+
)
1716+
16961717
@property
16971718
def x_nominal(self) -> list:
16981719
"""Parameter table nominal values"""
@@ -2259,6 +2280,65 @@ def get_measurements_for_experiment(
22592280
if measurement.experiment_id == experiment.id
22602281
]
22612282

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

22632343
class ModelFile(BaseModel):
22642344
"""A file in the PEtab problem configuration."""

petab/v2/lint.py

Lines changed: 10 additions & 71 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
@@ -996,19 +996,18 @@ def append_overrides(overrides):
996996
for formula_type, placeholder_sources in (
997997
(
998998
# Observable formulae
999-
{"observables": True, "noise": False},
999+
{"observable": True, "noise": False},
10001000
# can only contain observable placeholders
1001-
{"noise": False, "observables": True},
1001+
{"noise": False, "observable": True},
10021002
),
10031003
(
10041004
# Noise formulae
1005-
{"observables": False, "noise": True},
1005+
{"observable": False, "noise": True},
10061006
# can contain noise and observable placeholders
1007-
{"noise": True, "observables": True},
1007+
{"noise": True, "observable": 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,77 +1033,17 @@ 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,
1099-
observables: bool = True,
1038+
observable: bool = True,
11001039
noise: bool = True,
11011040
) -> list[str]:
11021041
"""Get all placeholder parameters from observable table observableFormulas
11031042
and noiseFormulas.
11041043
11051044
Arguments:
11061045
problem: The PEtab problem
1107-
observables: Include parameters from observableFormulas
1046+
observable: Include parameters from observableFormulas
11081047
noise: Include parameters from noiseFormulas
11091048
11101049
Returns:
@@ -1115,7 +1054,7 @@ def get_placeholders(
11151054
# {observable,noise}Parameters
11161055
placeholders = []
11171056
for o in problem.observables:
1118-
if observables:
1057+
if observable:
11191058
placeholders.extend(map(str, o.observable_placeholders))
11201059
if noise:
11211060
placeholders.extend(map(str, o.noise_placeholders))

tests/v2/test_core.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,3 +762,77 @@ def make_yaml(id_line: str) -> str:
762762
f.write(make_yaml(""))
763763
problem = Problem.from_yaml(filepath)
764764
assert problem.id is None
765+
766+
767+
def test_parameter_accessors(): # pylint: disable=W0621
768+
"""
769+
Test the petab.Problem functions to get parameter values.
770+
"""
771+
petab_problem = Problem()
772+
petab_problem += Parameter(
773+
id="par1", lb=0, ub=100, nominal_value=7, estimate=True
774+
)
775+
petab_problem += Parameter(
776+
id="par2", lb=0.1, ub=100, nominal_value=8, estimate=True
777+
)
778+
petab_problem += Parameter(
779+
id="par3", lb=0.1, ub=200, nominal_value=9, estimate=False
780+
)
781+
782+
assert petab_problem.x_ids == ["par1", "par2", "par3"]
783+
assert petab_problem.x_free_ids == ["par1", "par2"]
784+
assert petab_problem.x_fixed_ids == ["par3"]
785+
assert petab_problem.lb == [0, 0.1, 0.1]
786+
assert petab_problem.ub == [100, 100, 200]
787+
assert petab_problem.x_nominal == [7, 8, 9]
788+
assert petab_problem.x_nominal_free == [7, 8]
789+
assert petab_problem.x_nominal_fixed == [9]
790+
791+
assert (
792+
petab_problem.get_x_nominal_dict()
793+
== petab_problem.get_x_nominal_dict(free=True, fixed=True)
794+
== {
795+
"par1": 7,
796+
"par2": 8,
797+
"par3": 9,
798+
}
799+
)
800+
assert petab_problem.get_x_nominal_dict(free=True, fixed=False) == {
801+
"par1": 7,
802+
"par2": 8,
803+
}
804+
assert petab_problem.get_x_nominal_dict(free=False, fixed=True) == {
805+
"par3": 9,
806+
}
807+
808+
809+
def test_get_output_parameters():
810+
"""Test Problem.get_output_parameters"""
811+
petab_problem = Problem()
812+
assert petab_problem.get_output_parameters() == []
813+
814+
petab_problem += Parameter(id="p1", lb=0, ub=100, estimate=True)
815+
petab_problem.models.append(SbmlModel.from_antimony("p2 = 1"))
816+
assert petab_problem.get_output_parameters() == []
817+
818+
petab_problem += Observable(
819+
id="obs1", formula="p1 + p2", noise_formula="p1 * p2"
820+
)
821+
assert petab_problem.get_output_parameters() == ["p1"]
822+
823+
petab_problem += Observable(
824+
id="obs1",
825+
formula="p3 + p4",
826+
noise_formula="p3 * p5",
827+
)
828+
assert (
829+
petab_problem.get_output_parameters()
830+
== petab_problem.get_output_parameters(observable=True, noise=True)
831+
== ["p1", "p3", "p4", "p5"]
832+
)
833+
assert petab_problem.get_output_parameters(
834+
observable=True, noise=False
835+
) == ["p1", "p3", "p4"]
836+
assert petab_problem.get_output_parameters(
837+
observable=False, noise=True
838+
) == ["p1", "p3", "p5"]

0 commit comments

Comments
 (0)