Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion petab/v1/observables.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def get_output_parameters(
) -> list[str]:
"""Get output parameters

Returns IDs of parameters used in observable and noise formulas that are
Returns IDs of parameters used in observable or noise formulas that are
not defined in the model.

Arguments:
Expand Down
80 changes: 80 additions & 0 deletions petab/v2/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1691,6 +1691,27 @@ def get_x_nominal(self, free: bool = True, fixed: bool = True) -> list:

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

def get_x_nominal_dict(
self, free: bool = True, fixed: bool = True
) -> dict[str, float]:
"""Get parameter nominal values as dict.

:param free:
Whether to return free parameters, i.e. parameters to estimate.
:param fixed:
Whether to return fixed parameters, i.e. parameters not to
estimate.
:returns:
A dictionary mapping parameter IDs to their nominal values.
"""
return dict(
zip(
self.get_x_ids(free=free, fixed=fixed),
self.get_x_nominal(free=free, fixed=fixed),
strict=True,
)
)

@property
def x_nominal(self) -> list:
"""Parameter table nominal values"""
Expand Down Expand Up @@ -2257,6 +2278,65 @@ def get_measurements_for_experiment(
if measurement.experiment_id == experiment.id
]

def get_output_parameters(
self, observable: bool = True, noise: bool = True
) -> list[str]:
"""Get output parameters.

Returns IDs of symbols used in observable and noise formulas that are
not observables and that are not defined in the model.

:param observable:
Include parameters from observableFormulas
:param noise:
Include parameters from noiseFormulas
:returns:
List of output parameter IDs, including any placeholder parameters.
"""
# collect free symbols from observable and noise formulas,
# skipping observable IDs
candidates = set()
if observable:
candidates |= {
str_sym
for o in self.observables
if o.formula is not None
for sym in o.formula.free_symbols
if (str_sym := str(sym)) != o.id
}
if noise:
candidates |= {
str_sym
for o in self.observables
if o.noise_formula is not None
for sym in o.noise_formula.free_symbols
if (str_sym := str(sym)) != o.id
}

output_parameters = []

# filter out symbols that are defined in the model or mapped to
# such symbols
for candidate in sorted(candidates):
if self.model.symbol_allowed_in_observable_formula(candidate):
continue

# does it map to a model entity?
for mapping in self.mappings:
if (
mapping.petab_id == candidate
and mapping.model_id is not None
):
if self.model.symbol_allowed_in_observable_formula(
mapping.model_id
):
break
else:
# no mapping to a model entity, so it is an output parameter
output_parameters.append(candidate)

return output_parameters


class ModelFile(BaseModel):
"""A file in the PEtab problem configuration."""
Expand Down
81 changes: 10 additions & 71 deletions petab/v2/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ def run(self, problem: Problem) -> ValidationIssue | None:
if problem.model
else set()
)
allowed_targets |= set(get_output_parameters(problem))
allowed_targets |= set(problem.get_output_parameters())
allowed_targets |= {
m.petab_id for m in problem.mappings if m.model_id is not None
}
Expand Down Expand Up @@ -932,7 +932,7 @@ def get_valid_parameters_for_parameter_table(
parameter_ids[mapping.petab_id] = None

# add output parameters from observable table
output_parameters = get_output_parameters(problem)
output_parameters = problem.get_output_parameters()
for p in output_parameters:
if p not in invalid:
parameter_ids[p] = None
Expand Down Expand Up @@ -996,19 +996,18 @@ def append_overrides(overrides):
for formula_type, placeholder_sources in (
(
# Observable formulae
{"observables": True, "noise": False},
{"observable": True, "noise": False},
# can only contain observable placeholders
{"noise": False, "observables": True},
{"noise": False, "observable": True},
),
(
# Noise formulae
{"observables": False, "noise": True},
{"observable": False, "noise": True},
# can contain noise and observable placeholders
{"noise": True, "observables": True},
{"noise": True, "observable": True},
),
):
output_parameters = get_output_parameters(
problem,
output_parameters = problem.get_output_parameters(
**formula_type,
)
placeholders = get_placeholders(
Expand All @@ -1034,77 +1033,17 @@ def append_overrides(overrides):
return parameter_ids


def get_output_parameters(
problem: Problem,
observables: bool = True,
noise: bool = True,
) -> list[str]:
"""Get output parameters

Returns IDs of symbols used in observable and noise formulas that are
not observables and that are not defined in the model.

Arguments:
problem: The PEtab problem
observables: Include parameters from observableFormulas
noise: Include parameters from noiseFormulas

Returns:
List of output parameter IDs, including any placeholder parameters.
"""
# collect free symbols from observable and noise formulas,
# skipping observable IDs
candidates = set()
if observables:
candidates |= {
str_sym
for o in problem.observables
if o.formula is not None
for sym in o.formula.free_symbols
if (str_sym := str(sym)) != o.id
}
if noise:
candidates |= {
str_sym
for o in problem.observables
if o.noise_formula is not None
for sym in o.noise_formula.free_symbols
if (str_sym := str(sym)) != o.id
}

output_parameters = OrderedDict()

# filter out symbols that are defined in the model or mapped to
# such symbols
for candidate in sorted(candidates):
if problem.model.symbol_allowed_in_observable_formula(candidate):
continue

# does it map to a model entity?
for mapping in problem.mappings:
if mapping.petab_id == candidate and mapping.model_id is not None:
if problem.model.symbol_allowed_in_observable_formula(
mapping.model_id
):
break
else:
# no mapping to a model entity, so it is an output parameter
output_parameters[candidate] = None

return list(output_parameters.keys())


def get_placeholders(
problem: Problem,
observables: bool = True,
observable: bool = True,
noise: bool = True,
) -> list[str]:
"""Get all placeholder parameters from observable table observableFormulas
and noiseFormulas.

Arguments:
problem: The PEtab problem
observables: Include parameters from observableFormulas
observable: Include parameters from observableFormulas
noise: Include parameters from noiseFormulas

Returns:
Expand All @@ -1115,7 +1054,7 @@ def get_placeholders(
# {observable,noise}Parameters
placeholders = []
for o in problem.observables:
if observables:
if observable:
placeholders.extend(map(str, o.observable_placeholders))
if noise:
placeholders.extend(map(str, o.noise_placeholders))
Expand Down
74 changes: 74 additions & 0 deletions tests/v2/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -739,3 +739,77 @@ def make_yaml(id_line: str) -> str:
f.write(make_yaml(""))
problem = Problem.from_yaml(filepath)
assert problem.id is None


def test_parameter_accessors(): # pylint: disable=W0621
"""
Test the petab.Problem functions to get parameter values.
"""
petab_problem = Problem()
petab_problem += Parameter(
id="par1", lb=0, ub=100, nominal_value=7, estimate=True
)
petab_problem += Parameter(
id="par2", lb=0.1, ub=100, nominal_value=8, estimate=True
)
petab_problem += Parameter(
id="par3", lb=0.1, ub=200, nominal_value=9, estimate=False
)

assert petab_problem.x_ids == ["par1", "par2", "par3"]
assert petab_problem.x_free_ids == ["par1", "par2"]
assert petab_problem.x_fixed_ids == ["par3"]
assert petab_problem.lb == [0, 0.1, 0.1]
assert petab_problem.ub == [100, 100, 200]
assert petab_problem.x_nominal == [7, 8, 9]
assert petab_problem.x_nominal_free == [7, 8]
assert petab_problem.x_nominal_fixed == [9]

assert (
petab_problem.get_x_nominal_dict()
== petab_problem.get_x_nominal_dict(free=True, fixed=True)
== {
"par1": 7,
"par2": 8,
"par3": 9,
}
)
assert petab_problem.get_x_nominal_dict(free=True, fixed=False) == {
"par1": 7,
"par2": 8,
}
assert petab_problem.get_x_nominal_dict(free=False, fixed=True) == {
"par3": 9,
}


def test_get_output_parameters():
"""Test Problem.get_output_parameters"""
petab_problem = Problem()
assert petab_problem.get_output_parameters() == []

petab_problem += Parameter(id="p1", lb=0, ub=100, estimate=True)
petab_problem.models.append(SbmlModel.from_antimony("p2 = 1"))
assert petab_problem.get_output_parameters() == []

petab_problem += Observable(
id="obs1", formula="p1 + p2", noise_formula="p1 * p2"
)
assert petab_problem.get_output_parameters() == ["p1"]

petab_problem += Observable(
id="obs1",
formula="p3 + p4",
noise_formula="p3 * p5",
)
assert (
petab_problem.get_output_parameters()
== petab_problem.get_output_parameters(observable=True, noise=True)
== ["p1", "p3", "p4", "p5"]
)
assert petab_problem.get_output_parameters(
observable=True, noise=False
) == ["p1", "p3", "p4"]
assert petab_problem.get_output_parameters(
observable=False, noise=True
) == ["p1", "p3", "p5"]