Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
81 changes: 81 additions & 0 deletions petab/v2/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import tempfile
import traceback
from abc import abstractmethod
from collections import OrderedDict
from collections.abc import Sequence
from enum import Enum
from itertools import chain
Expand Down Expand Up @@ -1691,6 +1692,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 +2279,65 @@ def get_measurements_for_experiment(
if measurement.experiment_id == experiment.id
]

def get_output_parameters(
self, observables: bool = True, noise: bool = True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the docstring, I think this would be more consistent. i.e. "collect free symbols from observable and noise formulas", rather than "collect free symbols from noise formulas and observables".

Suggested change
self, observables: bool = True, noise: bool = True
self, observable: bool = True, noise: bool = True

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Will differ from petab.v1.observables.get_output_parameters then, but fine with me.

) -> 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 observables:
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 observables:
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 = OrderedDict()

# 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[candidate] = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine as is but the spec currently states that mapping.model_id exists in the model, so the self.model.symbol_allowed_in_observable_formula check is redundant with linting that occurs elsewhere and can be assumed true here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the spec currently states that mapping.model_id exists in the model

Hm, then the spec needs to be fixed. That would be incompatible with the intended use of the mapping table for general annotations.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's OK since it can be left empty. Here, the mapping.model_id is not None takes care of checking that it's non-empty (and thereby in the model according to the spec). Annotations would be in other columns as I recall.

A globally unique identifier defined in any model, or empty if the entity is not present in any model.


return list(output_parameters.keys())


class ModelFile(BaseModel):
"""A file in the PEtab problem configuration."""
Expand Down
67 changes: 3 additions & 64 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 @@ -1007,8 +1007,7 @@ def append_overrides(overrides):
{"noise": True, "observables": True},
),
):
output_parameters = get_output_parameters(
problem,
output_parameters = problem.get_output_parameters(
**formula_type,
)
placeholders = get_placeholders(
Expand All @@ -1034,66 +1033,6 @@ 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,
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(observables=True, noise=True)
== ["p1", "p3", "p4", "p5"]
)
assert petab_problem.get_output_parameters(
observables=True, noise=False
) == ["p1", "p3", "p4"]
assert petab_problem.get_output_parameters(
observables=False, noise=True
) == ["p1", "p3", "p5"]