1515from .. import v2
1616from ..v1 .lint import (
1717 _check_df ,
18+ assert_measured_observables_defined ,
19+ assert_measurements_not_null ,
20+ assert_measurements_numeric ,
1821 assert_model_parameters_in_condition_or_parameter_table ,
1922 assert_no_leading_trailing_whitespace ,
2023 assert_parameter_bounds_are_numeric ,
2326 assert_parameter_prior_parameters_are_valid ,
2427 assert_parameter_prior_type_is_valid ,
2528 assert_parameter_scale_is_valid ,
29+ assert_unique_observable_ids ,
2630 assert_unique_parameter_ids ,
2731 check_ids ,
28- check_measurement_df ,
2932 check_observable_df ,
3033 check_parameter_bounds ,
3134)
32- from ..v1 .measurements import split_parameter_replacement_list
35+ from ..v1 .measurements import (
36+ assert_overrides_match_parameter_count ,
37+ split_parameter_replacement_list ,
38+ )
3339from ..v1 .observables import get_output_parameters , get_placeholders
3440from ..v1 .visualize .lint import validate_visualization_df
3541from ..v2 .C import *
@@ -102,6 +108,23 @@ class ValidationError(ValidationIssue):
102108 level : ValidationIssueSeverity = field (
103109 default = ValidationIssueSeverity .ERROR , init = False
104110 )
111+ task : str | None = None
112+
113+ def __post_init__ (self ):
114+ if self .task is None :
115+ self .task = self ._get_task_name ()
116+
117+ def _get_task_name (self ):
118+ """Get the name of the ValidationTask that raised this error."""
119+ import inspect
120+
121+ # walk up the stack until we find the ValidationTask.run method
122+ for frame_info in inspect .stack ():
123+ frame = frame_info .frame
124+ if "self" in frame .f_locals :
125+ task = frame .f_locals ["self" ]
126+ if isinstance (task , ValidationTask ):
127+ return task .__class__ .__name__
105128
106129
107130class ValidationResultList (list [ValidationIssue ]):
@@ -237,8 +260,51 @@ def run(self, problem: Problem) -> ValidationIssue | None:
237260 if problem .measurement_df is None :
238261 return
239262
263+ df = problem .measurement_df
240264 try :
241- check_measurement_df (problem .measurement_df , problem .observable_df )
265+ _check_df (df , MEASUREMENT_DF_REQUIRED_COLS , "measurement" )
266+
267+ for column_name in MEASUREMENT_DF_REQUIRED_COLS :
268+ if not np .issubdtype (df [column_name ].dtype , np .number ):
269+ assert_no_leading_trailing_whitespace (
270+ df [column_name ].values , column_name
271+ )
272+
273+ for column_name in MEASUREMENT_DF_OPTIONAL_COLS :
274+ if column_name in df and not np .issubdtype (
275+ df [column_name ].dtype , np .number
276+ ):
277+ assert_no_leading_trailing_whitespace (
278+ df [column_name ].values , column_name
279+ )
280+
281+ if problem .observable_df is not None :
282+ assert_measured_observables_defined (df , problem .observable_df )
283+ assert_overrides_match_parameter_count (
284+ df , problem .observable_df
285+ )
286+
287+ if OBSERVABLE_TRANSFORMATION in problem .observable_df :
288+ # Check for positivity of measurements in case of
289+ # log-transformation
290+ assert_unique_observable_ids (problem .observable_df )
291+ # If the above is not checked, in the following loop
292+ # trafo may become a pandas Series
293+ for measurement , obs_id in zip (
294+ df [MEASUREMENT ], df [OBSERVABLE_ID ], strict = True
295+ ):
296+ trafo = problem .observable_df .loc [
297+ obs_id , OBSERVABLE_TRANSFORMATION
298+ ]
299+ if measurement <= 0.0 and trafo in [LOG , LOG10 ]:
300+ raise ValueError (
301+ "Measurements with observable "
302+ f"transformation { trafo } must be "
303+ f"positive, but { measurement } <= 0."
304+ )
305+
306+ assert_measurements_not_null (df )
307+ assert_measurements_numeric (df )
242308 except AssertionError as e :
243309 return ValidationError (str (e ))
244310
@@ -247,46 +313,20 @@ def run(self, problem: Problem) -> ValidationIssue | None:
247313 # condition table should be an error if the measurement table refers
248314 # to conditions
249315
250- # check that measured experiments/conditions exist
251- # TODO: fully switch to experiment table and remove this:
252- if SIMULATION_CONDITION_ID in problem .measurement_df :
253- if problem .condition_df is None :
254- return
255- used_conditions = set (
256- problem .measurement_df [SIMULATION_CONDITION_ID ].dropna ().values
257- )
258- if PREEQUILIBRATION_CONDITION_ID in problem .measurement_df :
259- used_conditions |= set (
260- problem .measurement_df [PREEQUILIBRATION_CONDITION_ID ]
261- .dropna ()
262- .values
263- )
264- available_conditions = set (
265- problem .condition_df [CONDITION_ID ].unique ()
266- )
267- if missing_conditions := (used_conditions - available_conditions ):
268- return ValidationError (
269- "Measurement table references conditions that "
270- "are not specified in the condition table: "
271- + str (missing_conditions )
272- )
273- elif EXPERIMENT_ID in problem .measurement_df :
274- if problem .experiment_df is None :
275- return
276- used_experiments = set (
277- problem .measurement_df [EXPERIMENT_ID ].values
278- )
279- available_experiments = set (
280- problem .condition_df [CONDITION_ID ].unique ()
316+ # check that measured experiments
317+ if problem .experiment_df is None :
318+ return
319+
320+ used_experiments = set (problem .measurement_df [EXPERIMENT_ID ].values )
321+ available_experiments = set (
322+ problem .experiment_df [EXPERIMENT_ID ].unique ()
323+ )
324+ if missing_experiments := (used_experiments - available_experiments ):
325+ raise AssertionError (
326+ "Measurement table references experiments that "
327+ "are not specified in the experiments table: "
328+ + str (missing_experiments )
281329 )
282- if missing_experiments := (
283- used_experiments - available_experiments
284- ):
285- raise AssertionError (
286- "Measurement table references experiments that "
287- "are not specified in the experiments table: "
288- + str (missing_experiments )
289- )
290330
291331
292332class CheckConditionTable (ValidationTask ):
@@ -486,7 +526,7 @@ def run(self, problem: Problem) -> ValidationIssue | None:
486526 )
487527
488528 required_conditions = problem .experiment_df [CONDITION_ID ].unique ()
489- existing_conditions = problem .condition_df . index
529+ existing_conditions = problem .condition_df [ CONDITION_ID ]. unique ()
490530
491531 missing_conditions = set (required_conditions ) - set (
492532 existing_conditions
@@ -771,7 +811,8 @@ def append_overrides(overrides):
771811 )
772812
773813 # parameters that are overridden via the condition table are not allowed
774- parameter_ids -= set (problem .condition_df [TARGET_ID ].unique ())
814+ if problem .condition_df is not None :
815+ parameter_ids -= set (problem .condition_df [TARGET_ID ].unique ())
775816
776817 return parameter_ids
777818
0 commit comments