|
43 | 43 | "CheckUnusedConditions", |
44 | 44 | "CheckPriorDistribution", |
45 | 45 | "CheckUndefinedExperiments", |
| 46 | + "CheckInitialChangeSymbols", |
46 | 47 | "lint_problem", |
47 | 48 | "default_validation_tasks", |
48 | 49 | ] |
@@ -736,6 +737,62 @@ def run(self, problem: Problem) -> ValidationIssue | None: |
736 | 737 | return None |
737 | 738 |
|
738 | 739 |
|
| 740 | +class CheckInitialChangeSymbols(ValidationTask): |
| 741 | + """ |
| 742 | + Check that changes of any first period of any experiment only refers to |
| 743 | + allowed symbols. |
| 744 | +
|
| 745 | + The only allowed symbols are those that are present in the parameter table. |
| 746 | + """ |
| 747 | + |
| 748 | + def run(self, problem: Problem) -> ValidationIssue | None: |
| 749 | + if not problem.experiments: |
| 750 | + return None |
| 751 | + |
| 752 | + if not problem.conditions: |
| 753 | + return None |
| 754 | + |
| 755 | + allowed_symbols = {p.id for p in problem.parameters} |
| 756 | + allowed_symbols.add(TIME_SYMBOL) |
| 757 | + # IDs of conditions that have already been checked |
| 758 | + valid_conditions = set() |
| 759 | + id_to_condition = {c.id: c for c in problem.conditions} |
| 760 | + |
| 761 | + messages = [] |
| 762 | + for experiment in problem.experiments: |
| 763 | + if not experiment.periods: |
| 764 | + continue |
| 765 | + |
| 766 | + first_period = experiment.sorted_periods[0] |
| 767 | + for condition_id in first_period.condition_ids: |
| 768 | + if condition_id in valid_conditions: |
| 769 | + continue |
| 770 | + |
| 771 | + # we assume that all referenced condition IDs are valid |
| 772 | + condition = id_to_condition[condition_id] |
| 773 | + |
| 774 | + used_symbols = { |
| 775 | + str(sym) |
| 776 | + for change in condition.changes |
| 777 | + for sym in change.target_value.free_symbols |
| 778 | + } |
| 779 | + invalid_symbols = used_symbols - allowed_symbols |
| 780 | + if invalid_symbols: |
| 781 | + messages.append( |
| 782 | + f"Condition {condition.id} is applied at the start of " |
| 783 | + f"experiment {experiment.id}, and thus, its " |
| 784 | + f"target value expressions must only contain " |
| 785 | + f"symbols from the parameter table, or `time`. " |
| 786 | + "However, it contains additional symbols: " |
| 787 | + f"{invalid_symbols}. " |
| 788 | + ) |
| 789 | + |
| 790 | + if messages: |
| 791 | + return ValidationError("\n".join(messages)) |
| 792 | + |
| 793 | + return None |
| 794 | + |
| 795 | + |
739 | 796 | class CheckPriorDistribution(ValidationTask): |
740 | 797 | """A task to validate the prior distribution of a PEtab problem.""" |
741 | 798 |
|
@@ -1082,10 +1139,7 @@ def get_placeholders( |
1082 | 1139 | CheckValidParameterInConditionOrParameterTable(), |
1083 | 1140 | CheckUnusedExperiments(), |
1084 | 1141 | CheckUnusedConditions(), |
1085 | | - # TODO: atomize checks, update to long condition table, re-enable |
1086 | | - # TODO validate mapping table |
1087 | | - CheckValidParameterInConditionOrParameterTable(), |
1088 | | - CheckAllParametersPresentInParameterTable(), |
1089 | | - CheckValidConditionTargets(), |
1090 | 1142 | CheckPriorDistribution(), |
| 1143 | + CheckInitialChangeSymbols(), |
| 1144 | + # TODO validate mapping table |
1091 | 1145 | ] |
0 commit comments