|
10 | 10 | import numpy as np |
11 | 11 | import pandas as pd |
12 | 12 |
|
13 | | -from petab.v1 import ( |
14 | | - assert_model_parameters_in_condition_or_parameter_table, |
15 | | -) |
16 | | -from petab.v1.C import ( |
17 | | - ESTIMATE, |
18 | | - MODEL_ENTITY_ID, |
19 | | - NOISE_PARAMETERS, |
20 | | - NOMINAL_VALUE, |
21 | | - OBSERVABLE_PARAMETERS, |
22 | | - PARAMETER_DF_REQUIRED_COLS, |
23 | | - PARAMETER_ID, |
24 | | -) |
25 | 13 | from petab.v1.conditions import get_parametric_overrides |
26 | 14 | from petab.v1.lint import ( |
27 | 15 | _check_df, |
|
42 | 30 | get_valid_parameters_for_parameter_table, |
43 | 31 | ) |
44 | 32 | from petab.v1.visualize.lint import validate_visualization_df |
| 33 | +from petab.v2 import ( |
| 34 | + assert_model_parameters_in_condition_or_parameter_table, |
| 35 | +) |
| 36 | +from petab.v2.C import * |
45 | 37 |
|
46 | 38 | from ..v1 import ( |
47 | 39 | assert_measurement_conditions_present_in_condition_table, |
|
61 | 53 | "ValidationTask", |
62 | 54 | "CheckModel", |
63 | 55 | "CheckTableExists", |
| 56 | + "CheckValidPetabIdColumn", |
64 | 57 | "CheckMeasurementTable", |
65 | 58 | "CheckConditionTable", |
66 | 59 | "CheckObservableTable", |
67 | 60 | "CheckParameterTable", |
| 61 | + "CheckExperimentTable", |
| 62 | + "CheckExperimentConditionsExist", |
68 | 63 | "CheckAllParametersPresentInParameterTable", |
69 | 64 | "CheckValidParameterInConditionOrParameterTable", |
70 | 65 | "CheckVisualizationTable", |
@@ -214,6 +209,35 @@ def run(self, problem: Problem) -> ValidationIssue | None: |
214 | 209 | return ValidationError(f"{self.table_name} table is missing.") |
215 | 210 |
|
216 | 211 |
|
| 212 | +class CheckValidPetabIdColumn(ValidationTask): |
| 213 | + """A task to check that a given column contains only valid PEtab IDs.""" |
| 214 | + |
| 215 | + def __init__( |
| 216 | + self, table_name: str, column_name: str, required_column: bool = True |
| 217 | + ): |
| 218 | + self.table_name = table_name |
| 219 | + self.column_name = column_name |
| 220 | + self.required_column = required_column |
| 221 | + |
| 222 | + def run(self, problem: Problem) -> ValidationIssue | None: |
| 223 | + df = getattr(problem, f"{self.table_name}_df") |
| 224 | + if df is None: |
| 225 | + return |
| 226 | + |
| 227 | + if self.column_name not in df.columns: |
| 228 | + if self.required_column: |
| 229 | + return ValidationError( |
| 230 | + f"Column {self.column_name} is missing in " |
| 231 | + f"{self.table_name} table." |
| 232 | + ) |
| 233 | + return |
| 234 | + |
| 235 | + try: |
| 236 | + check_ids(df[self.column_name].values, kind=self.column_name) |
| 237 | + except ValueError as e: |
| 238 | + return ValidationError(str(e)) |
| 239 | + |
| 240 | + |
217 | 241 | class CheckMeasurementTable(ValidationTask): |
218 | 242 | """A task to validate the measurement table of a PEtab problem.""" |
219 | 243 |
|
@@ -356,6 +380,66 @@ def run(self, problem: Problem) -> ValidationIssue | None: |
356 | 380 | return ValidationError(str(e)) |
357 | 381 |
|
358 | 382 |
|
| 383 | +class CheckExperimentTable(ValidationTask): |
| 384 | + """A task to validate the experiment table of a PEtab problem.""" |
| 385 | + |
| 386 | + def run(self, problem: Problem) -> ValidationIssue | None: |
| 387 | + if problem.experiment_df is None: |
| 388 | + return |
| 389 | + |
| 390 | + df = problem.experiment_df |
| 391 | + |
| 392 | + try: |
| 393 | + _check_df(df, EXPERIMENT_DF_REQUIRED_COLS, "experiment") |
| 394 | + except AssertionError as e: |
| 395 | + return ValidationError(str(e)) |
| 396 | + |
| 397 | + # valid timepoints |
| 398 | + invalid = [] |
| 399 | + for time in df[TIME].values: |
| 400 | + try: |
| 401 | + time = float(time) |
| 402 | + if not np.isfinite(time) and time != -np.inf: |
| 403 | + invalid.append(time) |
| 404 | + except ValueError: |
| 405 | + invalid.append(time) |
| 406 | + if invalid: |
| 407 | + return ValidationError( |
| 408 | + f"Invalid timepoints in experiment table: {invalid}" |
| 409 | + ) |
| 410 | + |
| 411 | + |
| 412 | +class CheckExperimentConditionsExist(ValidationTask): |
| 413 | + """A task to validate that all conditions in the experiment table exist |
| 414 | + in the condition table.""" |
| 415 | + |
| 416 | + def run(self, problem: Problem) -> ValidationIssue | None: |
| 417 | + if problem.experiment_df is None: |
| 418 | + return |
| 419 | + |
| 420 | + if ( |
| 421 | + problem.condition_df is None |
| 422 | + and problem.experiment_df is not None |
| 423 | + and not problem.experiment_df.empty |
| 424 | + ): |
| 425 | + return ValidationError( |
| 426 | + "Experiment table is non-empty, " |
| 427 | + "but condition table is missing." |
| 428 | + ) |
| 429 | + |
| 430 | + required_conditions = problem.experiment_df[CONDITION_ID].unique() |
| 431 | + existing_conditions = problem.condition_df.index |
| 432 | + |
| 433 | + missing_conditions = set(required_conditions) - set( |
| 434 | + existing_conditions |
| 435 | + ) |
| 436 | + if missing_conditions: |
| 437 | + return ValidationError( |
| 438 | + f"Experiment table contains conditions that are not present " |
| 439 | + f"in the condition table: {missing_conditions}" |
| 440 | + ) |
| 441 | + |
| 442 | + |
359 | 443 | class CheckAllParametersPresentInParameterTable(ValidationTask): |
360 | 444 | """Ensure all required parameters are contained in the parameter table |
361 | 445 | with no additional ones.""" |
@@ -558,6 +642,10 @@ def append_overrides(overrides): |
558 | 642 | CheckModel(), |
559 | 643 | CheckMeasurementTable(), |
560 | 644 | CheckConditionTable(), |
| 645 | + CheckExperimentTable(), |
| 646 | + CheckValidPetabIdColumn("experiment", EXPERIMENT_ID), |
| 647 | + CheckValidPetabIdColumn("experiment", CONDITION_ID), |
| 648 | + CheckExperimentConditionsExist(), |
561 | 649 | CheckObservableTable(), |
562 | 650 | CheckObservablesDoNotShadowModelEntities(), |
563 | 651 | CheckParameterTable(), |
|
0 commit comments