From c3cadaaba6768ccd75716207371ca9ed3decabb4 Mon Sep 17 00:00:00 2001 From: Branwen Snelling Date: Thu, 13 Aug 2026 09:31:14 +0100 Subject: [PATCH 1/5] linting - allow nn outputs not in params table --- petab/v2/extensions/sciml_lint.py | 7 +++-- petab/v2/lint.py | 10 ++++++ tests/v2/test_sciml.py | 51 +++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/petab/v2/extensions/sciml_lint.py b/petab/v2/extensions/sciml_lint.py index e9cf27c1..ba98ad32 100644 --- a/petab/v2/extensions/sciml_lint.py +++ b/petab/v2/extensions/sciml_lint.py @@ -27,6 +27,7 @@ "CheckNeuralNetworkModel", "CheckSciMLConditionTable", "CheckSciMLParameterTable", + "get_nn_entity_petab_ids", ] #: Placeholder used in messages when a neural network has no ID. @@ -67,7 +68,7 @@ def _nn_ids(problem: core.Problem) -> set[str]: return ids -def _nn_entity_petab_ids( +def get_nn_entity_petab_ids( problem: core.Problem, ) -> tuple[dict[str, str], dict[str, str], dict[str, str]]: """Classify NN entities referenced in the mapping table. @@ -209,7 +210,7 @@ def run(self, problem: core.Problem) -> lint.ValidationIssue | None: condition_targets = { c.target_id for ct in problem.conditions for c in ct.changes } - nn_inputs, nn_outputs, nn_params = _nn_entity_petab_ids(problem) + nn_inputs, nn_outputs, nn_params = get_nn_entity_petab_ids(problem) array_input_ids = _array_input_ids(problem) array_param_layers = _array_parameter_layers(problem) array_param_petab_ids = { @@ -333,7 +334,7 @@ class CheckSciMLConditionTable(lint.ValidationTask): def run(self, problem: core.Problem) -> lint.ValidationIssue | None: messages = [] - nn_inputs, nn_outputs, nn_params = _nn_entity_petab_ids(problem) + nn_inputs, nn_outputs, nn_params = get_nn_entity_petab_ids(problem) array_input_ids = _array_input_ids(problem) array_param_layers = _array_parameter_layers(problem) array_param_petab_ids = { diff --git a/petab/v2/lint.py b/petab/v2/lint.py index b0a96d94..e8aa5244 100644 --- a/petab/v2/lint.py +++ b/petab/v2/lint.py @@ -1137,6 +1137,16 @@ def append_overrides(overrides): } parameter_ids -= hybridization_target_values + # NN outputs can be used in observable and noise formulas without + # appearing in the parameter table. + try: + from .extensions.sciml_lint import get_nn_entity_petab_ids + except ImportError: + pass + else: + _, nn_outputs, _ = get_nn_entity_petab_ids(problem) + parameter_ids -= set(nn_outputs) + return parameter_ids diff --git a/tests/v2/test_sciml.py b/tests/v2/test_sciml.py index 55554665..2eb17f45 100644 --- a/tests/v2/test_sciml.py +++ b/tests/v2/test_sciml.py @@ -401,6 +401,57 @@ def test_parameter_posterior_requires_bounds_or_prior(): assert "net1_ps" in issue.message +def _add_observable_consuming_nn_output(problem): + """Add an observable whose formula references an NN output directly.""" + problem.add_mapping("net1_output2", "net1.outputs[0][1]") + problem.add_observable("fitness_obs", "net1_output2", noise_formula="0.05") + problem.add_measurement( + "fitness_obs", time=1, measurement=1, experiment_id="e1" + ) + return problem + + +def test_nn_output_in_observable_formula_not_required_parameter(): + """NN outputs consumed by an observable formula are not parameter table + entries -- PEtab SciML explicitly allows an NN output in a formula.""" + from petab.v2.lint import get_required_parameters_for_parameter_table + + problem = _add_observable_consuming_nn_output(_get_test_problem()) + + assert "net1_output2" not in get_required_parameters_for_parameter_table( + problem + ) + assert problem.validate() == [] + + +def test_nn_output_in_noise_formula_not_required_parameter(): + """Same for noise formulas.""" + from petab.v2.lint import get_required_parameters_for_parameter_table + + problem = _get_test_problem() + problem.add_mapping("net1_output2", "net1.outputs[0][1]") + problem.observable_tables[0]["B_obs"].noise_formula = "net1_output2" + + assert "net1_output2" not in get_required_parameters_for_parameter_table( + problem + ) + assert problem.validate() == [] + + +def test_genuinely_missing_output_parameter_still_reported(): + """The NN-output carve-out does not mask real missing parameters.""" + problem = _add_observable_consuming_nn_output(_get_test_problem()) + # `scale` is not an NN entity and is not in the parameter table. + problem.observable_tables[0][ + "fitness_obs" + ].formula = "scale * net1_output2" + + results = problem.validate() + assert results.has_errors() + assert any("scale" in issue.message for issue in results) + assert not any("net1_output2" in issue.message for issue in results) + + # --------------------------------------------------------------------------- # Full-problem integration # --------------------------------------------------------------------------- From 958ca29dd836cabb16bf7ca3f81a7eb6e20d4e46 Mon Sep 17 00:00:00 2001 From: Branwen Snelling Date: Thu, 13 Aug 2026 11:28:59 +0100 Subject: [PATCH 2/5] rm try - except hiding import errors --- petab/v2/lint.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/petab/v2/lint.py b/petab/v2/lint.py index e8aa5244..c04ee2b8 100644 --- a/petab/v2/lint.py +++ b/petab/v2/lint.py @@ -1127,6 +1127,8 @@ def append_overrides(overrides): parameter_ids -= condition_targets if problem.extensions.sciml is not None: + from .extensions.sciml_lint import get_nn_entity_petab_ids + hybridization_targets = { hyb.target_id for hyb in problem.extensions.sciml.hybridizations } @@ -1139,13 +1141,8 @@ def append_overrides(overrides): # NN outputs can be used in observable and noise formulas without # appearing in the parameter table. - try: - from .extensions.sciml_lint import get_nn_entity_petab_ids - except ImportError: - pass - else: - _, nn_outputs, _ = get_nn_entity_petab_ids(problem) - parameter_ids -= set(nn_outputs) + _, nn_outputs, _ = get_nn_entity_petab_ids(problem) + parameter_ids -= set(nn_outputs) return parameter_ids From 25b3f11e3e166ff71a9da988e5b30defdd406399 Mon Sep 17 00:00:00 2001 From: Branwen Snelling Date: Thu, 13 Aug 2026 11:55:38 +0100 Subject: [PATCH 3/5] update wording --- petab/v2/lint.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/petab/v2/lint.py b/petab/v2/lint.py index c04ee2b8..064bc888 100644 --- a/petab/v2/lint.py +++ b/petab/v2/lint.py @@ -1139,8 +1139,7 @@ def append_overrides(overrides): } parameter_ids -= hybridization_target_values - # NN outputs can be used in observable and noise formulas without - # appearing in the parameter table. + # NN outputs should not appear in the parameters table. _, nn_outputs, _ = get_nn_entity_petab_ids(problem) parameter_ids -= set(nn_outputs) From dab518ba50d0f024b7f76fbb9dcb3f2e47931ec6 Mon Sep 17 00:00:00 2001 From: BSnelling Date: Thu, 13 Aug 2026 12:31:04 +0100 Subject: [PATCH 4/5] Apply suggestion from @dilpath Co-authored-by: Dilan Pathirana <59329744+dilpath@users.noreply.github.com> --- tests/v2/test_sciml.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/v2/test_sciml.py b/tests/v2/test_sciml.py index 2eb17f45..8564ef29 100644 --- a/tests/v2/test_sciml.py +++ b/tests/v2/test_sciml.py @@ -412,8 +412,7 @@ def _add_observable_consuming_nn_output(problem): def test_nn_output_in_observable_formula_not_required_parameter(): - """NN outputs consumed by an observable formula are not parameter table - entries -- PEtab SciML explicitly allows an NN output in a formula.""" + """NN outputs should not appear in the parameter table, and can appear in observable formulas.""" from petab.v2.lint import get_required_parameters_for_parameter_table problem = _add_observable_consuming_nn_output(_get_test_problem()) From 6727939ee21621e62268b84bcff9ec95ccd7c409 Mon Sep 17 00:00:00 2001 From: Branwen Snelling Date: Thu, 13 Aug 2026 12:34:47 +0100 Subject: [PATCH 5/5] fix ruff --- tests/v2/test_sciml.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/v2/test_sciml.py b/tests/v2/test_sciml.py index 8564ef29..65157e41 100644 --- a/tests/v2/test_sciml.py +++ b/tests/v2/test_sciml.py @@ -412,7 +412,8 @@ def _add_observable_consuming_nn_output(problem): def test_nn_output_in_observable_formula_not_required_parameter(): - """NN outputs should not appear in the parameter table, and can appear in observable formulas.""" + """NN outputs should not appear in the parameter table, and can appear in + observable formulas.""" from petab.v2.lint import get_required_parameters_for_parameter_table problem = _add_observable_consuming_nn_output(_get_test_problem())