From 39148297ab864bfe1f94c70e71300a283bc537e9 Mon Sep 17 00:00:00 2001 From: Branwen Snelling Date: Tue, 16 Jun 2026 14:13:34 +0100 Subject: [PATCH 1/4] validation and handling of cond ids specified in array files --- petab/v2/converters.py | 6 ++++++ petab/v2/core.py | 8 ++++++++ petab/v2/lint.py | 16 ++++++++++++++++ tests/v2/test_sciml.py | 12 ++++++++---- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/petab/v2/converters.py b/petab/v2/converters.py index 1c9acfd8..507c46a3 100644 --- a/petab/v2/converters.py +++ b/petab/v2/converters.py @@ -249,6 +249,12 @@ def _convert_experiment(self, experiment: Experiment) -> None: # single-period experiments. if i_period == 0: exp_ind_id = self.get_experiment_indicator(experiment.id) + # If condition ids are set by array data, skip as importers handle this + if set(period.condition_ids).issubset( + self._new_problem._get_array_data_condition_ids() + ): + continue + for change in self._new_problem.get_changes_for_period(period): period0_assignments.setdefault( change.target_id, [] diff --git a/petab/v2/core.py b/petab/v2/core.py index 49822f96..cea226a9 100644 --- a/petab/v2/core.py +++ b/petab/v2/core.py @@ -2288,6 +2288,14 @@ def add_experiment(self, id_: str, *args): Experiment(id=id_, periods=periods) ) + def _get_array_data_condition_ids(self) -> set[str]: + """Get the set of condition IDs that are referenced in the array data files.""" + condition_ids = set() + for array_data in self.array_data_files: + for input in array_data.inputs.values(): + condition_ids.update(input.keys()) + return condition_ids + def __iadd__(self, other): """Add Observable, Parameter, Measurement, Condition, or Experiment""" from .core import ( diff --git a/petab/v2/lint.py b/petab/v2/lint.py index c6fef280..da63a341 100644 --- a/petab/v2/lint.py +++ b/petab/v2/lint.py @@ -532,6 +532,13 @@ class CheckExperimentConditionsExist(ValidationTask): def run(self, problem: Problem) -> ValidationIssue | None: messages = [] available_conditions = {c.id for c in problem.conditions} + if problem.array_data_files: + available_conditions |= { + key + for array_data in problem.array_data_files + for input_array in array_data.inputs.values() + for key in input_array.keys() + } for experiment in problem.experiments: missing_conditions = ( set( @@ -972,6 +979,14 @@ def run(self, problem: Problem) -> ValidationIssue | None: return None +class CheckExperimentalConditionsDefined(ValidationTask): + """A task to check that all conditions referenced by the experiments + are defined.""" + + def run(self, problem: Problem) -> ValidationIssue | None: + return None + + def get_valid_parameters_for_parameter_table( problem: Problem, ) -> set[str]: @@ -1189,4 +1204,5 @@ def get_placeholders( #: Validation tasks that should be run PEtab SciML problems sciml_validation_tasks = default_validation_tasks + [ CheckHybridizationTable(), + CheckExperimentalConditionsDefined(), ] diff --git a/tests/v2/test_sciml.py b/tests/v2/test_sciml.py index d743c082..390605ec 100644 --- a/tests/v2/test_sciml.py +++ b/tests/v2/test_sciml.py @@ -48,9 +48,9 @@ def _get_test_problem(): -> B; gamma_; end """) - problem.add_experiment("e1", 0, "") + problem.add_experiment("e1", 0, "cond1") problem.add_mapping("net1_input1", "net1.inputs[0][0]") - problem.add_mapping("net1_input2", "net1.inputs[0][1]") + problem.add_mapping("net1_input2", "net1.inputs[1]") problem.add_mapping("net1_output1", "net1.outputs[0][0]") problem.add_mapping("net1_ps", "net1.parameters") problem.add_measurement("B_obs", time=1, measurement=1, experiment_id="e1") @@ -62,7 +62,7 @@ def _get_test_problem(): "net1_ps", estimate=True, lb=-np.inf, ub=np.inf, nominal_value="array" ) problem.extensions.sciml.add_hybridization("net1_input1", "A") - problem.extensions.sciml.add_hybridization("net1_input2", "B") + problem.extensions.sciml.add_hybridization("net1_input2", "array") problem.extensions.sciml.add_hybridization("gamma_", "net1_output1") problem.extensions.sciml.add_neural_network_from_dict( "net1", @@ -106,7 +106,11 @@ def _get_test_problem(): problem.extensions.sciml.add_array_data_from_dict( { "metadata": {"pytorch_format": True}, - "inputs": {}, + "inputs": { + "net1_input2": { + "cond1": np.random.randn(2), + } + }, "parameters": { "net1": { "layer1": { From 0d26f302b7bed2b1b4f787fb1f1b4340533f5002 Mon Sep 17 00:00:00 2001 From: Branwen Snelling Date: Tue, 30 Jun 2026 10:49:34 +0100 Subject: [PATCH 2/4] allow for sciml problems setting cond ids in array files --- petab/v2/converters.py | 12 +++++++----- petab/v2/core.py | 8 -------- petab/v2/extensions/sciml.py | 9 +++++++++ petab/v2/lint.py | 4 ++-- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/petab/v2/converters.py b/petab/v2/converters.py index 507c46a3..d47412c3 100644 --- a/petab/v2/converters.py +++ b/petab/v2/converters.py @@ -249,11 +249,13 @@ def _convert_experiment(self, experiment: Experiment) -> None: # single-period experiments. if i_period == 0: exp_ind_id = self.get_experiment_indicator(experiment.id) - # If condition ids are set by array data, skip as importers handle this - if set(period.condition_ids).issubset( - self._new_problem._get_array_data_condition_ids() - ): - continue + # Skip if condition ids are set by array data + # importers handle this + if self._new_problem.extensions.sciml: + if set(period.condition_ids).issubset( + self._new_problem.extensions.sciml._get_array_data_condition_ids() + ): + continue for change in self._new_problem.get_changes_for_period(period): period0_assignments.setdefault( diff --git a/petab/v2/core.py b/petab/v2/core.py index cea226a9..49822f96 100644 --- a/petab/v2/core.py +++ b/petab/v2/core.py @@ -2288,14 +2288,6 @@ def add_experiment(self, id_: str, *args): Experiment(id=id_, periods=periods) ) - def _get_array_data_condition_ids(self) -> set[str]: - """Get the set of condition IDs that are referenced in the array data files.""" - condition_ids = set() - for array_data in self.array_data_files: - for input in array_data.inputs.values(): - condition_ids.update(input.keys()) - return condition_ids - def __iadd__(self, other): """Add Observable, Parameter, Measurement, Condition, or Experiment""" from .core import ( diff --git a/petab/v2/extensions/sciml.py b/petab/v2/extensions/sciml.py index 808b0643..58189279 100644 --- a/petab/v2/extensions/sciml.py +++ b/petab/v2/extensions/sciml.py @@ -303,3 +303,12 @@ def from_config( hybridization_tables=hybridization_tables, array_data_files=array_data_files, ) + + def _get_array_data_condition_ids(self) -> set[str]: + """Get the set of condition IDs that are referenced in the array data + files.""" + condition_ids = set() + for array_data in self.array_data_files: + for input in array_data.inputs.values(): + condition_ids.update(input.keys()) + return condition_ids diff --git a/petab/v2/lint.py b/petab/v2/lint.py index da63a341..e9b19f34 100644 --- a/petab/v2/lint.py +++ b/petab/v2/lint.py @@ -532,10 +532,10 @@ class CheckExperimentConditionsExist(ValidationTask): def run(self, problem: Problem) -> ValidationIssue | None: messages = [] available_conditions = {c.id for c in problem.conditions} - if problem.array_data_files: + if problem.extensions.sciml: available_conditions |= { key - for array_data in problem.array_data_files + for array_data in problem.extensions.sciml.array_data_files for input_array in array_data.inputs.values() for key in input_array.keys() } From 1e82bc342eee0a82b34fa619a7547de502948b86 Mon Sep 17 00:00:00 2001 From: Branwen Snelling Date: Tue, 30 Jun 2026 10:55:45 +0100 Subject: [PATCH 3/4] rm duplicate validation task --- petab/v2/lint.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/petab/v2/lint.py b/petab/v2/lint.py index e9b19f34..80f47b04 100644 --- a/petab/v2/lint.py +++ b/petab/v2/lint.py @@ -979,14 +979,6 @@ def run(self, problem: Problem) -> ValidationIssue | None: return None -class CheckExperimentalConditionsDefined(ValidationTask): - """A task to check that all conditions referenced by the experiments - are defined.""" - - def run(self, problem: Problem) -> ValidationIssue | None: - return None - - def get_valid_parameters_for_parameter_table( problem: Problem, ) -> set[str]: @@ -1204,5 +1196,4 @@ def get_placeholders( #: Validation tasks that should be run PEtab SciML problems sciml_validation_tasks = default_validation_tasks + [ CheckHybridizationTable(), - CheckExperimentalConditionsDefined(), ] From a2573228d20cac84a223bf1af5b2f19c1f30aeaa Mon Sep 17 00:00:00 2001 From: Branwen Snelling Date: Wed, 1 Jul 2026 10:30:04 +0100 Subject: [PATCH 4/4] calc array cond ids once --- petab/v2/converters.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/petab/v2/converters.py b/petab/v2/converters.py index d47412c3..ae19686d 100644 --- a/petab/v2/converters.py +++ b/petab/v2/converters.py @@ -88,6 +88,11 @@ def __init__(self, problem: Problem, default_priority: float = None): self._model: libsbml.Model = self._new_problem.model.sbml_model self._preeq_indicator = self.PREEQ_INDICATOR + sciml = self._new_problem.extensions.sciml + self._array_data_condition_ids = ( + sciml._get_array_data_condition_ids() if sciml else set() + ) + # The maximum event priority that was found in the unprocessed model. self._max_event_priority = None # The priority that will be used for the PEtab events. @@ -251,11 +256,10 @@ def _convert_experiment(self, experiment: Experiment) -> None: exp_ind_id = self.get_experiment_indicator(experiment.id) # Skip if condition ids are set by array data # importers handle this - if self._new_problem.extensions.sciml: - if set(period.condition_ids).issubset( - self._new_problem.extensions.sciml._get_array_data_condition_ids() - ): - continue + if self._array_data_condition_ids and set( + period.condition_ids + ).issubset(self._array_data_condition_ids): + continue for change in self._new_problem.get_changes_for_period(period): period0_assignments.setdefault(