Skip to content

Commit f056682

Browse files
authored
Merge branch 'lint_mapping_table' into sciml_lint
2 parents 730ddd2 + d1f1c3d commit f056682

2 files changed

Lines changed: 39 additions & 23 deletions

File tree

petab/v2/lint.py

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -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

tests/v2/test_lint.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def test_validate_initial_change_symbols():
112112
def test_check_mapping_table():
113113
"""Test checks related to the mapping table."""
114114
problem = Problem()
115-
# SBML model with mappings
115+
# FIXME see https://github.com/PEtab-dev/libpetab-python/pull/482#discussion_r3431330125
116116
problem.model = SbmlModel.from_antimony("a.mean = 1")
117117
problem.add_mapping(
118118
petab_id="a_m",

0 commit comments

Comments
 (0)