Skip to content

Commit 6629df6

Browse files
BSnellingdilpath
andauthored
SciML linting - allow nn outputs not in params table (#511)
* linting - allow nn outputs not in params table * rm try - except hiding import errors * update wording * Apply suggestion from @dilpath Co-authored-by: Dilan Pathirana <59329744+dilpath@users.noreply.github.com> * fix ruff --------- Co-authored-by: Dilan Pathirana <59329744+dilpath@users.noreply.github.com>
1 parent f1c7679 commit 6629df6

3 files changed

Lines changed: 61 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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,8 @@ def append_overrides(overrides):
11271127
parameter_ids -= condition_targets
11281128

11291129
if problem.extensions.sciml is not None:
1130+
from .extensions.sciml_lint import get_nn_entity_petab_ids
1131+
11301132
hybridization_targets = {
11311133
hyb.target_id for hyb in problem.extensions.sciml.hybridizations
11321134
}
@@ -1137,6 +1139,10 @@ def append_overrides(overrides):
11371139
}
11381140
parameter_ids -= hybridization_target_values
11391141

1142+
# NN outputs should not appear in the parameters table.
1143+
_, nn_outputs, _ = get_nn_entity_petab_ids(problem)
1144+
parameter_ids -= set(nn_outputs)
1145+
11401146
return parameter_ids
11411147

11421148

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 should not appear in the parameter table, and can appear in
416+
observable formulas."""
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)