@@ -447,31 +447,31 @@ def run(self, problem: Problem) -> ValidationIssue | None:
447447
448448 # check for uniqueness of all primary keys
449449 counter = Counter (c .id for c in problem .conditions )
450- duplicates = { id_ for id_ , count in counter .items () if count > 1 }
450+ duplicates = sorted ( id_ for id_ , count in counter .items () if count > 1 )
451451
452452 if duplicates :
453453 return ValidationError (
454454 f"Condition table contains duplicate IDs: { duplicates } "
455455 )
456456
457457 counter = Counter (o .id for o in problem .observables )
458- duplicates = { id_ for id_ , count in counter .items () if count > 1 }
458+ duplicates = sorted ( id_ for id_ , count in counter .items () if count > 1 )
459459
460460 if duplicates :
461461 return ValidationError (
462462 f"Observable table contains duplicate IDs: { duplicates } "
463463 )
464464
465465 counter = Counter (e .id for e in problem .experiments )
466- duplicates = { id_ for id_ , count in counter .items () if count > 1 }
466+ duplicates = sorted ( id_ for id_ , count in counter .items () if count > 1 )
467467
468468 if duplicates :
469469 return ValidationError (
470470 f"Experiment table contains duplicate IDs: { duplicates } "
471471 )
472472
473473 counter = Counter (p .id for p in problem .parameters )
474- duplicates = { id_ for id_ , count in counter .items () if count > 1 }
474+ duplicates = sorted ( id_ for id_ , count in counter .items () if count > 1 )
475475
476476 if duplicates :
477477 return ValidationError (
@@ -510,7 +510,11 @@ def run(self, problem: Problem) -> ValidationIssue | None:
510510 for experiment in problem .experiments :
511511 # Check that there are no duplicate timepoints
512512 counter = Counter (period .time for period in experiment .periods )
513- duplicates = {time for time , count in counter .items () if count > 1 }
513+ duplicates = sorted (
514+ time
515+ for time , count in counter .items ()
516+ if count > 1
517+ )
514518 if duplicates :
515519 messages .append (
516520 f"Experiment { experiment .id } contains duplicate "
@@ -905,38 +909,47 @@ def run(self, problem: Problem) -> ValidationIssue | None:
905909
906910 # Mapping table is optional
907911 if problem .mappings :
908- # Check that each id only occurs once
909- counter = Counter (
912+ # Check that each id, across both the petabEntityId and
913+ # modelEntityId columns, occurs only once
914+ must_be_unique_ids = []
915+ for mapping in problem .mappings :
916+ petab_id := getattr (mapping , "petab_id" ):
917+ model_id := getattr (mapping , "model_id" ):
918+
919+ if petab_id :
920+ must_be_unique_ids .append (petab_id )
921+ # Duplicates for annotation-only rows (identity mappings)
922+ # are permitted.
923+ if petab_id == model_id :
924+ continue
925+ if model_id :
926+ must_be_unique_ids .append (model_id )
927+
928+ non_unique_ids = sorted (
910929 id_
911- for mapping in problem .mappings
912- for id_ in {
913- getattr (mapping , attr )
914- for attr in ["petab_id" , "model_id" ]
915- if getattr (mapping , attr )
916- }
917- )
918- non_unique = sorted (
919- {id_ for id_ , count in counter .items () if count > 1 }
930+ for id_ , count in Counter (must_be_unique_ids ).items ()
931+ if count > 1
920932 )
921- if non_unique :
933+ if non_unique_ids :
922934 return ValidationError (
923- f"Mapping table contains non-unique IDs: { non_unique } ."
935+ f"Mapping table contains non-unique IDs: { non_unique_ids } ."
924936 )
925937
926938 # petabEntityId is not defined elsewhere in the PEtab problem
927- petab_ids_mapping = {
939+ new_petab_ids = {
928940 m .petab_id
929941 for m in problem .mappings
942+ # Ignore identity mappings used for annotation
930943 if m .petab_id != m .model_id
931944 }
932- defined_petab_ids = (
945+ old_petab_ids = (
933946 {c .id for c in problem .conditions }
934947 | {e .id for e in problem .experiments }
935948 | {o .id for o in problem .observables }
936949 )
937- if petab_ids_mapping & defined_petab_ids :
950+ if overdefined_ids := sorted ( new_petab_ids & old_petab_ids ) :
938951 messages .append (
939- f"PEtab IDs `{ petab_ids_mapping & defined_petab_ids } ` are "
952+ f"PEtab IDs `{ overdefined_ids } ` are "
940953 "defined in the mapping table but also defined through "
941954 "other PEtab tables."
942955 )
@@ -1040,6 +1053,9 @@ def get_valid_parameters_for_parameter_table(
10401053 )
10411054
10421055 # Add petab ids from mapping table if they are used for aliasing
1056+ # FIXME only add mapping.petab_id to allowed parameter IDs list if it
1057+ # aliases an invalid PEtab ID? See
1058+ # https://github.com/PEtab-dev/libpetab-python/pull/482#discussion_r3420762034
10431059 for mapping in problem .mappings :
10441060 if mapping .petab_id not in invalid :
10451061 parameter_ids [mapping .petab_id ] = None
0 commit comments