Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
24 changes: 24 additions & 0 deletions petab/v2/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"CheckObservablesDoNotShadowModelEntities",
"CheckUnusedConditions",
"CheckPriorDistribution",
"CheckUndefinedExperiments",
"lint_problem",
"default_validation_tasks",
]
Expand Down Expand Up @@ -691,6 +692,28 @@ def run(self, problem: Problem) -> ValidationIssue | None:
return None


class CheckUndefinedExperiments(ValidationTask):
"""A task to check for experiments that are used in the measurement
table but not defined in the experiment table."""

def run(self, problem: Problem) -> ValidationIssue | None:
used_experiments = {
m.experiment_id
for m in problem.measurements
if m.experiment_id is not None
}
available_experiments = {e.id for e in problem.experiments}

if undefined_experiments := used_experiments - available_experiments:
return ValidationWarning(
f"Experiments {undefined_experiments} are used in the "
"measurements table but are not defined in the experiments "
"table."
)

return None


class CheckUnusedConditions(ValidationTask):
"""A task to check for conditions that are not used in the experiment
table."""
Expand Down Expand Up @@ -1053,6 +1076,7 @@ def get_placeholders(
CheckValidConditionTargets(),
CheckExperimentTable(),
CheckExperimentConditionsExist(),
CheckUndefinedExperiments(),
CheckObservablesDoNotShadowModelEntities(),
CheckAllParametersPresentInParameterTable(),
CheckValidParameterInConditionOrParameterTable(),
Expand Down
18 changes: 18 additions & 0 deletions tests/v2/test_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,21 @@ def test_invalid_model_id_in_measurements():
# Use a valid model ID
problem.measurements[0].model_id = "model1"
assert (error := check.run(problem)) is None, error


def test_undefined_experiment_id_in_measurements():
"""Test that measurements with an undefined experiment ID are caught."""
problem = Problem()
problem.add_experiment("e1", 0, "c1")
problem.add_observable("obs1", "A")
problem.add_measurement("obs1", experiment_id="e1", time=0, measurement=1)

check = CheckUndefinedExperiments()

# Valid experiment ID
assert (error := check.run(problem)) is None, error

# Invalid experiment ID
problem.measurements[0].experiment_id = "invalid_experiment_id"
assert (error := check.run(problem)) is not None
assert "not defined" in error.message
Loading