diff --git a/petab/lint.py b/petab/lint.py index f31c8459..3f47779d 100644 --- a/petab/lint.py +++ b/petab/lint.py @@ -7,6 +7,8 @@ import re import copy import logging +import libsbml +import pandas as pd logger = logging.getLogger(__name__) @@ -360,6 +362,18 @@ def lint_problem(problem: 'core.Problem'): logger.error(e) errors_occurred = True + if problem.sbml_model is not None and problem.condition_df is not None \ + and problem.parameter_df is not None: + try: + assert_model_parameters_in_condition_or_parameter_table( + problem.sbml_model, + problem.condition_df, + problem.parameter_df + ) + except AssertionError as e: + logger.error(e) + errors_occurred = True + if errors_occurred: logger.error('Not OK') elif problem.measurement_df is None or problem.condition_df is None \ @@ -370,3 +384,39 @@ def lint_problem(problem: 'core.Problem'): logger.info('OK') return errors_occurred + + +def assert_model_parameters_in_condition_or_parameter_table( + sbml_model: libsbml.Model, + condition_df: pd.DataFrame, + parameter_df: pd.DataFrame): + """Model non-placeholder model parameters must be either specified in the + condition or parameter table, unless they are AssignmentRule target, in + which case they must not occur in either. + Check that. + + NOTE: SBML local parameters are ignored here""" + + for parameter in sbml_model.getListOfParameters(): + parameter_id = parameter.getId() + + is_assignee = \ + sbml_model.getAssignmentRuleByVariable(parameter_id) is not None + in_parameter_df = parameter_id in parameter_df.index + in_condition_df = parameter_id in condition_df.columns + + if is_assignee and (in_parameter_df or in_condition_df): + raise AssertionError(f"Model parameter '{parameter_id}' is target " + "of AssignmentRule, and thus, must not be " + "present in condition table or in parameter " + "table.") + + if not is_assignee and not in_parameter_df and not in_condition_df: + raise AssertionError(f"Model parameter '{parameter_id}' neither " + "present in condition table nor in parameter " + "table.") + + if in_parameter_df and in_condition_df: + raise AssertionError(f"Model parameter '{parameter_id}' present " + "in both condition table and parameter " + "table.") diff --git a/tests/test_lint.py b/tests/test_lint.py index f64c4250..92c86233 100644 --- a/tests/test_lint.py +++ b/tests/test_lint.py @@ -5,7 +5,7 @@ import pandas as pd sys.path.append(os.getcwd()) -from petab import lint # noqa: E402 +from petab import (lint, sbml) # noqa: E402 def test_assert_measured_observables_present_in_model(): @@ -147,3 +147,38 @@ def test_assert_no_leading_trailing_whitespace(): lint.assert_no_leading_trailing_whitespace( test_df['testNone'].values, "testNone") + + +def test_assert_model_parameters_in_condition_or_parameter_table(): + document = libsbml.SBMLDocument(3, 1) + model = document.createModel() + model.setTimeUnits("second") + model.setExtentUnits("mole") + model.setSubstanceUnits('mole') + sbml.add_global_parameter(model, 'parameter1') + + with pytest.raises(AssertionError): + lint.assert_model_parameters_in_condition_or_parameter_table( + model, pd.DataFrame(), pd.DataFrame()) + + lint.assert_model_parameters_in_condition_or_parameter_table( + model, pd.DataFrame(columns=['parameter1']), pd.DataFrame() + ) + + lint.assert_model_parameters_in_condition_or_parameter_table( + model, pd.DataFrame(), pd.DataFrame(index=['parameter1'])) + + with pytest.raises(AssertionError): + lint.assert_model_parameters_in_condition_or_parameter_table( + model, + pd.DataFrame(columns=['parameter1']), + pd.DataFrame(index=['parameter1'])) + + with pytest.raises(AssertionError): + lint.assert_model_parameters_in_condition_or_parameter_table( + model, pd.DataFrame(), pd.DataFrame()) + + sbml.create_assigment_rule(model, assignee_id='parameter1', + formula='parameter2') + lint.assert_model_parameters_in_condition_or_parameter_table( + model, pd.DataFrame(), pd.DataFrame())