diff --git a/petab/v1/models/sbml_model.py b/petab/v1/models/sbml_model.py index 6b15a02e..030d0ab0 100644 --- a/petab/v1/models/sbml_model.py +++ b/petab/v1/models/sbml_model.py @@ -8,7 +8,7 @@ import libsbml import sympy as sp -from sympy.abc import _clash +from sbmlmath import sbml_math_to_sympy from ..._utils import _generate_path from ..sbml import ( @@ -285,28 +285,7 @@ def sympify_sbml(sbml_obj: libsbml.ASTNode | libsbml.SBase) -> sp.Expr: ------- The sympy expression corresponding to ``sbml_obj``. """ - ast_node = ( - sbml_obj - if isinstance(sbml_obj, libsbml.ASTNode) - else sbml_obj.getMath() - ) - - parser_settings = libsbml.L3ParserSettings( - ast_node.getParentSBMLObject().getModel(), - libsbml.L3P_PARSE_LOG_AS_LOG10, - libsbml.L3P_EXPAND_UNARY_MINUS, - libsbml.L3P_NO_UNITS, - libsbml.L3P_AVOGADRO_IS_CSYMBOL, - libsbml.L3P_COMPARE_BUILTINS_CASE_INSENSITIVE, - None, - libsbml.L3P_MODULO_IS_PIECEWISE, - ) - - formula_str = libsbml.formulaToL3StringWithSettings( - ast_node, parser_settings - ) - - return sp.sympify(formula_str, locals=_clash) + return sbml_math_to_sympy(sbml_obj) def antimony2sbml(ant_model: str | Path) -> str: diff --git a/pyproject.toml b/pyproject.toml index e700d0c4..f1683d93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ dependencies = [ "jsonschema", "antlr4-python3-runtime==4.13.1", "pydantic>=2.10", + "sbmlmath>=0.4.0", ] license = "MIT" authors = [ @@ -65,7 +66,6 @@ doc = [ "ipython>=7.21.0, !=8.7.0", "pysb", "antimony>=2.14.0", - "sbmlmath>=0.4.0", ] vis = [ "matplotlib>=3.6.0", diff --git a/tests/v1/test_sbml.py b/tests/v1/test_sbml.py index 37524514..5cbb1568 100644 --- a/tests/v1/test_sbml.py +++ b/tests/v1/test_sbml.py @@ -5,7 +5,7 @@ import pandas as pd import pytest -from petab.v1.models.sbml_model import SbmlModel +from petab.v1.models.sbml_model import SbmlModel, sympify_sbml sys.path.append(os.getcwd()) import petab @@ -150,3 +150,22 @@ def test_sbml_from_to_ant(): # convert back to antimony assert "R1: S1 -> S2; k1*S1" in petab_model.to_antimony() + + +def test_sympify_sbml_modulo(): + """SBML's ``%`` follows C semantics (result has the sign of the + dividend), unlike Python's ``%`` (result has the sign of the divisor). + + Naively converting the SBML math to a string and handing it to + ``sympy.sympify`` therefore silently produces a wrong result (gh-283). + """ + sbml_document = libsbml.SBMLDocument(3, 2) + sbml_model = sbml_document.createModel() + parameter = sbml_model.createParameter() + parameter.setId("e") + parameter.setConstant(False) + initial_assignment = sbml_model.createInitialAssignment() + initial_assignment.setSymbol("e") + initial_assignment.setMath(libsbml.parseL3Formula("-5 % 3")) + + assert sympify_sbml(initial_assignment).doit() == -2