Skip to content

Commit c3cadaa

Browse files
committed
linting - allow nn outputs not in params table
1 parent f1c7679 commit c3cadaa

3 files changed

Lines changed: 65 additions & 3 deletions

File tree

petab/v2/extensions/sciml_lint.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"CheckNeuralNetworkModel",
2828
"CheckSciMLConditionTable",
2929
"CheckSciMLParameterTable",
30+
"get_nn_entity_petab_ids",
3031
]
3132

3233
#: Placeholder used in messages when a neural network has no ID.
@@ -67,7 +68,7 @@ def _nn_ids(problem: core.Problem) -> set[str]:
6768
return ids
6869

6970

70-
def _nn_entity_petab_ids(
71+
def get_nn_entity_petab_ids(
7172
problem: core.Problem,
7273
) -> tuple[dict[str, str], dict[str, str], dict[str, str]]:
7374
"""Classify NN entities referenced in the mapping table.
@@ -209,7 +210,7 @@ def run(self, problem: core.Problem) -> lint.ValidationIssue | None:
209210
condition_targets = {
210211
c.target_id for ct in problem.conditions for c in ct.changes
211212
}
212-
nn_inputs, nn_outputs, nn_params = _nn_entity_petab_ids(problem)
213+
nn_inputs, nn_outputs, nn_params = get_nn_entity_petab_ids(problem)
213214
array_input_ids = _array_input_ids(problem)
214215
array_param_layers = _array_parameter_layers(problem)
215216
array_param_petab_ids = {
@@ -333,7 +334,7 @@ class CheckSciMLConditionTable(lint.ValidationTask):
333334
def run(self, problem: core.Problem) -> lint.ValidationIssue | None:
334335
messages = []
335336

336-
nn_inputs, nn_outputs, nn_params = _nn_entity_petab_ids(problem)
337+
nn_inputs, nn_outputs, nn_params = get_nn_entity_petab_ids(problem)
337338
array_input_ids = _array_input_ids(problem)
338339
array_param_layers = _array_parameter_layers(problem)
339340
array_param_petab_ids = {

petab/v2/lint.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,16 @@ def append_overrides(overrides):
11371137
}
11381138
parameter_ids -= hybridization_target_values
11391139

1140+
# NN outputs can be used in observable and noise formulas without
1141+
# appearing in the parameter table.
1142+
try:
1143+
from .extensions.sciml_lint import get_nn_entity_petab_ids
1144+
except ImportError:
1145+
pass
1146+
else:
1147+
_, nn_outputs, _ = get_nn_entity_petab_ids(problem)
1148+
parameter_ids -= set(nn_outputs)
1149+
11401150
return parameter_ids
11411151

11421152

tests/v2/test_sciml.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,57 @@ def test_parameter_posterior_requires_bounds_or_prior():
401401
assert "net1_ps" in issue.message
402402

403403

404+
def _add_observable_consuming_nn_output(problem):
405+
"""Add an observable whose formula references an NN output directly."""
406+
problem.add_mapping("net1_output2", "net1.outputs[0][1]")
407+
problem.add_observable("fitness_obs", "net1_output2", noise_formula="0.05")
408+
problem.add_measurement(
409+
"fitness_obs", time=1, measurement=1, experiment_id="e1"
410+
)
411+
return problem
412+
413+
414+
def test_nn_output_in_observable_formula_not_required_parameter():
415+
"""NN outputs consumed by an observable formula are not parameter table
416+
entries -- PEtab SciML explicitly allows an NN output in a formula."""
417+
from petab.v2.lint import get_required_parameters_for_parameter_table
418+
419+
problem = _add_observable_consuming_nn_output(_get_test_problem())
420+
421+
assert "net1_output2" not in get_required_parameters_for_parameter_table(
422+
problem
423+
)
424+
assert problem.validate() == []
425+
426+
427+
def test_nn_output_in_noise_formula_not_required_parameter():
428+
"""Same for noise formulas."""
429+
from petab.v2.lint import get_required_parameters_for_parameter_table
430+
431+
problem = _get_test_problem()
432+
problem.add_mapping("net1_output2", "net1.outputs[0][1]")
433+
problem.observable_tables[0]["B_obs"].noise_formula = "net1_output2"
434+
435+
assert "net1_output2" not in get_required_parameters_for_parameter_table(
436+
problem
437+
)
438+
assert problem.validate() == []
439+
440+
441+
def test_genuinely_missing_output_parameter_still_reported():
442+
"""The NN-output carve-out does not mask real missing parameters."""
443+
problem = _add_observable_consuming_nn_output(_get_test_problem())
444+
# `scale` is not an NN entity and is not in the parameter table.
445+
problem.observable_tables[0][
446+
"fitness_obs"
447+
].formula = "scale * net1_output2"
448+
449+
results = problem.validate()
450+
assert results.has_errors()
451+
assert any("scale" in issue.message for issue in results)
452+
assert not any("net1_output2" in issue.message for issue in results)
453+
454+
404455
# ---------------------------------------------------------------------------
405456
# Full-problem integration
406457
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)