|
38 | 38 | "CheckObservablesDoNotShadowModelEntities", |
39 | 39 | "CheckUnusedConditions", |
40 | 40 | "CheckPriorDistribution", |
| 41 | + "CheckInitialChangeSymbols", |
41 | 42 | "lint_problem", |
42 | 43 | "default_validation_tasks", |
43 | 44 | ] |
@@ -709,6 +710,62 @@ def run(self, problem: Problem) -> ValidationIssue | None: |
709 | 710 | return None |
710 | 711 |
|
711 | 712 |
|
| 713 | +class CheckInitialChangeSymbols(ValidationTask): |
| 714 | + """ |
| 715 | + Check that changes of any first period of any experiment only refers to |
| 716 | + allowed symbols. |
| 717 | +
|
| 718 | + The only allowed symbols are those that are present in the parameter table. |
| 719 | + """ |
| 720 | + |
| 721 | + def run(self, problem: Problem) -> ValidationIssue | None: |
| 722 | + if not problem.experiments: |
| 723 | + return None |
| 724 | + |
| 725 | + if not problem.conditions: |
| 726 | + return None |
| 727 | + |
| 728 | + allowed_symbols = {p.id for p in problem.parameters} |
| 729 | + allowed_symbols.add(TIME_SYMBOL) |
| 730 | + # IDs of conditions that have already been checked |
| 731 | + valid_conditions = set() |
| 732 | + id_to_condition = {c.id: c for c in problem.conditions} |
| 733 | + |
| 734 | + messages = [] |
| 735 | + for experiment in problem.experiments: |
| 736 | + if not experiment.periods: |
| 737 | + continue |
| 738 | + |
| 739 | + first_period = experiment.sorted_periods[0] |
| 740 | + for condition_id in first_period.condition_ids: |
| 741 | + if condition_id in valid_conditions: |
| 742 | + continue |
| 743 | + |
| 744 | + # we assume that all referenced condition IDs are valid |
| 745 | + condition = id_to_condition[condition_id] |
| 746 | + |
| 747 | + used_symbols = { |
| 748 | + str(sym) |
| 749 | + for change in condition.changes |
| 750 | + for sym in change.target_value.free_symbols |
| 751 | + } |
| 752 | + invalid_symbols = used_symbols - allowed_symbols |
| 753 | + if invalid_symbols: |
| 754 | + messages.append( |
| 755 | + f"Condition {condition.id} is applied at the start of " |
| 756 | + f"experiment {experiment.id}, and thus, its " |
| 757 | + f"target value expressions must only contain " |
| 758 | + f"symbols from the parameter table, or `time`. " |
| 759 | + "However, it contains additional symbols: " |
| 760 | + f"{invalid_symbols}. " |
| 761 | + ) |
| 762 | + |
| 763 | + if messages: |
| 764 | + return ValidationError("\n".join(messages)) |
| 765 | + |
| 766 | + return None |
| 767 | + |
| 768 | + |
712 | 769 | class CheckPriorDistribution(ValidationTask): |
713 | 770 | """A task to validate the prior distribution of a PEtab problem.""" |
714 | 771 |
|
@@ -1022,10 +1079,7 @@ def get_placeholders( |
1022 | 1079 | CheckValidParameterInConditionOrParameterTable(), |
1023 | 1080 | CheckUnusedExperiments(), |
1024 | 1081 | CheckUnusedConditions(), |
1025 | | - # TODO: atomize checks, update to long condition table, re-enable |
1026 | | - # TODO validate mapping table |
1027 | | - CheckValidParameterInConditionOrParameterTable(), |
1028 | | - CheckAllParametersPresentInParameterTable(), |
1029 | | - CheckValidConditionTargets(), |
1030 | 1082 | CheckPriorDistribution(), |
| 1083 | + CheckInitialChangeSymbols(), |
| 1084 | + # TODO validate mapping table |
1031 | 1085 | ] |
0 commit comments