@@ -446,31 +446,31 @@ def run(self, problem: Problem) -> ValidationIssue | None:
446446
447447 # check for uniqueness of all primary keys
448448 counter = Counter (c .id for c in problem .conditions )
449- duplicates = { id_ for id_ , count in counter .items () if count > 1 }
449+ duplicates = sorted ( id_ for id_ , count in counter .items () if count > 1 )
450450
451451 if duplicates :
452452 return ValidationError (
453453 f"Condition table contains duplicate IDs: { duplicates } "
454454 )
455455
456456 counter = Counter (o .id for o in problem .observables )
457- duplicates = { id_ for id_ , count in counter .items () if count > 1 }
457+ duplicates = sorted ( id_ for id_ , count in counter .items () if count > 1 )
458458
459459 if duplicates :
460460 return ValidationError (
461461 f"Observable table contains duplicate IDs: { duplicates } "
462462 )
463463
464464 counter = Counter (e .id for e in problem .experiments )
465- duplicates = { id_ for id_ , count in counter .items () if count > 1 }
465+ duplicates = sorted ( id_ for id_ , count in counter .items () if count > 1 )
466466
467467 if duplicates :
468468 return ValidationError (
469469 f"Experiment table contains duplicate IDs: { duplicates } "
470470 )
471471
472472 counter = Counter (p .id for p in problem .parameters )
473- duplicates = { id_ for id_ , count in counter .items () if count > 1 }
473+ duplicates = sorted ( id_ for id_ , count in counter .items () if count > 1 )
474474
475475 if duplicates :
476476 return ValidationError (
@@ -509,7 +509,11 @@ def run(self, problem: Problem) -> ValidationIssue | None:
509509 for experiment in problem .experiments :
510510 # Check that there are no duplicate timepoints
511511 counter = Counter (period .time for period in experiment .periods )
512- duplicates = {time for time , count in counter .items () if count > 1 }
512+ duplicates = sorted (
513+ time
514+ for time , count in counter .items ()
515+ if count > 1
516+ )
513517 if duplicates :
514518 messages .append (
515519 f"Experiment { experiment .id } contains duplicate "
@@ -904,31 +908,47 @@ def run(self, problem: Problem) -> ValidationIssue | None:
904908
905909 # Mapping table is optional
906910 if problem .mappings :
907- # Check that each id only occurs once
908- counter = Counter (
909- [
910- getattr (mapping , attr )
911- for mapping in problem .mappings
912- for attr in ["petab_id" , "model_id" ]
913- if getattr (mapping , attr )
914- ]
911+ # Check that each id, across both the petabEntityId and
912+ # modelEntityId columns, occurs only once
913+ must_be_unique_ids = []
914+ for mapping in problem .mappings :
915+ petab_id := getattr (mapping , "petab_id" ):
916+ model_id := getattr (mapping , "model_id" ):
917+
918+ if petab_id :
919+ must_be_unique_ids .append (petab_id )
920+ # Duplicates for annotation-only rows (identity mappings)
921+ # are permitted.
922+ if petab_id == model_id :
923+ continue
924+ if model_id :
925+ must_be_unique_ids .append (model_id )
926+
927+ non_unique_ids = sorted (
928+ id_
929+ for id_ , count in Counter (must_be_unique_ids ).items ()
930+ if count > 1
915931 )
916- non_unique = {id_ for id_ , count in counter .items () if count > 1 }
917- if non_unique :
932+ if non_unique_ids :
918933 return ValidationError (
919- f"Mapping table contains non-unique IDs: { non_unique } ."
934+ f"Mapping table contains non-unique IDs: { non_unique_ids } ."
920935 )
921936
922937 # petabEntityId is not defined elsewhere in the PEtab problem
923- petab_ids_mapping = {m .petab_id for m in problem .mappings }
924- defined_petab_ids = (
938+ new_petab_ids = {
939+ m .petab_id
940+ for m in problem .mappings
941+ # Ignore identity mappings used for annotation
942+ if m .petab_id != m .model_id
943+ }
944+ old_petab_ids = (
925945 {c .id for c in problem .conditions }
926946 | {e .id for e in problem .experiments }
927947 | {o .id for o in problem .observables }
928948 )
929- if petab_ids_mapping & defined_petab_ids :
949+ if overdefined_ids := sorted ( new_petab_ids & old_petab_ids ) :
930950 messages .append (
931- f"PEtab IDs `{ petab_ids_mapping & defined_petab_ids } ` are "
951+ f"PEtab IDs `{ overdefined_ids } ` are "
932952 "defined in the mapping table but also defined through "
933953 "other PEtab tables."
934954 )
@@ -989,6 +1009,9 @@ def get_valid_parameters_for_parameter_table(
9891009 )
9901010
9911011 # Add petab ids from mapping table if they are used for aliasing
1012+ # FIXME only add mapping.petab_id to allowed parameter IDs list if it
1013+ # aliases an invalid PEtab ID? See
1014+ # https://github.com/PEtab-dev/libpetab-python/pull/482#discussion_r3420762034
9921015 for mapping in problem .mappings :
9931016 if mapping .model_id and mapping .model_id in parameter_ids .keys ():
9941017 parameter_ids [mapping .petab_id ] = None
0 commit comments