Skip to content

Commit 89e9fec

Browse files
authored
v2 linter: check measurement.experimentId (#435)
Check for undefined experiments referenced in the measurement table. Closes #434.
1 parent 3cf8dac commit 89e9fec

File tree

2 files changed

+42
-0
lines changed

2 files changed

+42
-0
lines changed

petab/v2/lint.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"CheckObservablesDoNotShadowModelEntities",
4343
"CheckUnusedConditions",
4444
"CheckPriorDistribution",
45+
"CheckUndefinedExperiments",
4546
"lint_problem",
4647
"default_validation_tasks",
4748
]
@@ -691,6 +692,28 @@ def run(self, problem: Problem) -> ValidationIssue | None:
691692
return None
692693

693694

695+
class CheckUndefinedExperiments(ValidationTask):
696+
"""A task to check for experiments that are used in the measurement
697+
table but not defined in the experiment table."""
698+
699+
def run(self, problem: Problem) -> ValidationIssue | None:
700+
used_experiments = {
701+
m.experiment_id
702+
for m in problem.measurements
703+
if m.experiment_id is not None
704+
}
705+
available_experiments = {e.id for e in problem.experiments}
706+
707+
if undefined_experiments := used_experiments - available_experiments:
708+
return ValidationWarning(
709+
f"Experiments {undefined_experiments} are used in the "
710+
"measurements table but are not defined in the experiments "
711+
"table."
712+
)
713+
714+
return None
715+
716+
694717
class CheckUnusedConditions(ValidationTask):
695718
"""A task to check for conditions that are not used in the experiment
696719
table."""
@@ -1053,6 +1076,7 @@ def get_placeholders(
10531076
CheckValidConditionTargets(),
10541077
CheckExperimentTable(),
10551078
CheckExperimentConditionsExist(),
1079+
CheckUndefinedExperiments(),
10561080
CheckObservablesDoNotShadowModelEntities(),
10571081
CheckAllParametersPresentInParameterTable(),
10581082
CheckValidParameterInConditionOrParameterTable(),

tests/v2/test_lint.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,21 @@ def test_invalid_model_id_in_measurements():
6464
# Use a valid model ID
6565
problem.measurements[0].model_id = "model1"
6666
assert (error := check.run(problem)) is None, error
67+
68+
69+
def test_undefined_experiment_id_in_measurements():
70+
"""Test that measurements with an undefined experiment ID are caught."""
71+
problem = Problem()
72+
problem.add_experiment("e1", 0, "c1")
73+
problem.add_observable("obs1", "A")
74+
problem.add_measurement("obs1", experiment_id="e1", time=0, measurement=1)
75+
76+
check = CheckUndefinedExperiments()
77+
78+
# Valid experiment ID
79+
assert (error := check.run(problem)) is None, error
80+
81+
# Invalid experiment ID
82+
problem.measurements[0].experiment_id = "invalid_experiment_id"
83+
assert (error := check.run(problem)) is not None
84+
assert "not defined" in error.message

0 commit comments

Comments
 (0)