Skip to content

Commit 5bc059a

Browse files
committed
Remove mathml2sympy
For spline handling, replace `mathml2sympy` by sbmlmath functions. Related to #2146. Also fix potential code printing issues resulting in unintended integer division specifically for splines.
1 parent 84b9d8d commit 5bc059a

File tree

7 files changed

+38
-77
lines changed

7 files changed

+38
-77
lines changed

pytest.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ filterwarnings =
2828
ignore:divide by zero encountered in log:RuntimeWarning
2929
# ignore jax deprecation warnings
3030
ignore:jax.* is deprecated:DeprecationWarning
31-
31+
# swig 4.3 issues
32+
ignore:builtin type .* has no __module__ attribute:DeprecationWarning
3233

3334
norecursedirs = .git amici_models build doc documentation models ThirdParty amici sdist examples *build* petab_test_problems pysb_test_models

python/sdist/amici/exporters/sundials/de_export.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -838,21 +838,19 @@ def _get_create_splines_body(self):
838838
body = ["return {"]
839839
for ispl, spline in enumerate(self.model._splines):
840840
if isinstance(spline.nodes, splines.UniformGrid):
841-
nodes = (
842-
f"{ind8}{{{spline.nodes.start}, {spline.nodes.stop}}}, "
843-
)
841+
start = self._code_printer.doprint(spline.nodes.start)
842+
stop = self._code_printer.doprint(spline.nodes.stop)
843+
nodes = f"{ind8}{{{start}, {stop}}}, "
844844
else:
845-
nodes = f"{ind8}{{{', '.join(map(str, spline.nodes))}}}, "
845+
nodes = f"{ind8}{{{', '.join(map(self._code_printer.doprint, spline.nodes))}}}, "
846846

847847
# vector with the node values
848-
values = (
849-
f"{ind8}{{{', '.join(map(str, spline.values_at_nodes))}}}, "
850-
)
848+
values = f"{ind8}{{{', '.join(map(self._code_printer.doprint, spline.values_at_nodes))}}}, "
851849
# vector with the slopes
852850
if spline.derivatives_by_fd:
853851
slopes = f"{ind8}{{}},"
854852
else:
855-
slopes = f"{ind8}{{{', '.join(map(str, spline.derivatives_at_nodes))}}},"
853+
slopes = f"{ind8}{{{', '.join(map(self._code_printer.doprint, spline.derivatives_at_nodes))}}},"
856854

857855
body.extend(
858856
[

python/sdist/amici/sbml_import.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1607,7 +1607,10 @@ def _process_rule_assignment(self, rule: libsbml.AssignmentRule):
16071607
):
16081608
raise NotImplementedError(
16091609
"AMICI at the moment does not support splines "
1610-
"whose evaluation point is not the model time."
1610+
"whose evaluation point is not the model time. "
1611+
f"'evaluate_at' was {spline.evaluate_at} "
1612+
f"({type(spline.evaluate_at)}) in spline for "
1613+
f"symbol {sym_id}."
16111614
)
16121615
self.splines.append(spline)
16131616
return
@@ -2274,6 +2277,9 @@ def _make_initial(
22742277
raise NotImplementedError(
22752278
"AMICI at the moment does not support splines "
22762279
"whose evaluation point is not the model time."
2280+
f"'evaluate_at' was {spline.evaluate_at} "
2281+
f"({type(spline.evaluate_at)}) in spline for "
2282+
f"symbol {var}."
22772283
)
22782284
sym_math = sym_math.subs(
22792285
var, spline.evaluate(sbml_time_symbol)

python/sdist/amici/sbml_utils.py

Lines changed: 1 addition & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
import sympy as sp
1212

1313
if TYPE_CHECKING:
14-
from typing import Any
15-
1614
SbmlID = str | sp.Symbol
1715

1816
import xml.dom.minidom
@@ -23,8 +21,6 @@
2321

2422
from .import_utils import (
2523
SBMLException,
26-
_check_unsupported_functions,
27-
_parse_special_functions,
2824
amici_time_symbol,
2925
sbml_time_symbol,
3026
)
@@ -399,6 +395,7 @@ def pretty_xml(ugly_xml: str) -> str:
399395
return pretty_xml[pretty_xml.index("\n") + 1 :]
400396

401397

398+
# TODO: replace by sbmlmath.SBMLMathMLPrinter
402399
class MathMLSbmlPrinter(MathMLContentPrinter):
403400
"""Prints a SymPy expression to a MathML expression parsable by libSBML.
404401
@@ -485,57 +482,3 @@ def set_sbml_math(obj: libsbml.SBase, expr, **kwargs) -> None:
485482
f"expression:\n{expr}\n"
486483
f"MathML:\n{pretty_xml(mathml)}"
487484
)
488-
489-
490-
# TODO: replace by `sbmlmath` functions
491-
def mathml2sympy(
492-
mathml: str,
493-
*,
494-
evaluate: bool = False,
495-
locals: dict[str, Any] | None = None,
496-
expression_type: str = "mathml2sympy",
497-
) -> sp.Basic:
498-
ast = libsbml.readMathMLFromString(mathml)
499-
if ast is None:
500-
raise ValueError(
501-
f"libSBML could not parse MathML string:\n{pretty_xml(mathml)}"
502-
)
503-
504-
formula = _parse_logical_operators(libsbml.formulaToL3String(ast))
505-
506-
if evaluate:
507-
expr = sp.sympify(formula, locals=locals)
508-
else:
509-
with sp.core.parameters.evaluate(False):
510-
expr = sp.sympify(formula, locals=locals)
511-
512-
expr = _parse_special_functions(expr)
513-
514-
if expression_type is not None:
515-
_check_unsupported_functions(expr, expression_type)
516-
517-
return expr
518-
519-
520-
# TODO: remove after getting rid of `mathml2sympy`
521-
def _parse_logical_operators(
522-
math_str: str | float | None,
523-
) -> str | float | None:
524-
"""
525-
Parses a math string in order to replace logical operators by a form
526-
parsable for sympy
527-
528-
:param math_str:
529-
str with mathematical expression
530-
:param math_str:
531-
parsed math_str
532-
"""
533-
if not isinstance(math_str, str):
534-
return math_str
535-
536-
if " xor(" in math_str or " Xor(" in math_str:
537-
raise SBMLException(
538-
"Xor is currently not supported as logical operation."
539-
)
540-
541-
return (math_str.replace("&&", "&")).replace("||", "|")

python/sdist/amici/splines.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
add_assignment_rule,
4949
add_parameter,
5050
get_sbml_units,
51-
mathml2sympy,
5251
pretty_xml,
5352
sbml_mathml,
5453
)
@@ -1216,9 +1215,9 @@ def from_annotation(
12161215
"""Create a spline object from a SBML annotation.
12171216
12181217
This function extracts annotation and children from the XML annotation
1219-
and gives them to the ``_fromAnnotation`` function for parsing.
1220-
Subclass behaviour should be implemented by extending
1221-
``_fromAnnotation``.
1218+
and gives them to the ``_from_annotation`` function for parsing.
1219+
Subclass behavior should be implemented by extending
1220+
``_from_annotation``.
12221221
However, the mapping between method strings and subclasses
12231222
must be hard-coded into this function here (at the moment).
12241223
"""
@@ -1240,6 +1239,12 @@ def from_annotation(
12401239
value = False
12411240
attributes[key] = value
12421241

1242+
from sbmlmath import SBMLMathMLParser, TimeSymbol
1243+
1244+
mathml_parser = SBMLMathMLParser(
1245+
symbol_kwargs={"real": True}, ignore_units=True, evaluate=False
1246+
)
1247+
12431248
children = {}
12441249
for child in annotation:
12451250
if not child.tag.startswith(f"{{{annotation_namespace}}}"):
@@ -1248,11 +1253,8 @@ def from_annotation(
12481253
)
12491254
key = child.tag[len(annotation_namespace) + 2 :]
12501255
value = [
1251-
mathml2sympy(
1252-
ET.tostring(gc).decode(),
1253-
evaluate=False,
1254-
locals=locals_,
1255-
expression_type="Rule",
1256+
mathml_parser.parse_str(ET.tostring(gc).decode()).replace(
1257+
TimeSymbol, lambda *args: sbml_time_symbol
12561258
)
12571259
for gc in child
12581260
]

python/tests/test_cxxcodeprinter.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,13 @@ def test_min_max():
6262
assert cp.doprint(sp.Max(a, b)) == "std::max(a, b)"
6363
assert cp.doprint(sp.Min(a, b, c)) == "std::min({a, b, c})"
6464
assert cp.doprint(sp.Max(a, b, c)) == "std::max({a, b, c})"
65+
66+
67+
@skip_on_valgrind
68+
def test_float_arithmetic():
69+
"""
70+
Check that AmiciCxxCodePrinter produces code that uses float arithmetic.
71+
"""
72+
cp = AmiciCxxCodePrinter()
73+
assert cp.doprint(sp.Rational(1, 2)) == "1.0/2.0"
74+
assert cp.doprint(sp.Integer(1) / sp.Integer(2)) == "1.0/2.0"

tests/performance/test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pathlib import Path
99

1010
import amici
11+
import amici.exporters.sundials.de_export
1112
import petab.v1 as petab
1213
from amici.petab.petab_import import import_model_sbml
1314

0 commit comments

Comments
 (0)