Skip to content

Commit 2c44a18

Browse files
committed
Add petab-compatible sympy string-printer
Add a sympy Printer to stringify sympy expressions in a petab-compatible way. For example, we need to avoid `str(sympy.sympify("x^2"))` -> `'x**2'`. Closes PEtab-dev#362.
1 parent 5b47448 commit 2c44a18

File tree

3 files changed

+47
-6
lines changed

3 files changed

+47
-6
lines changed

petab/v1/math/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
"""Functions for parsing and evaluating mathematical expressions."""
22

3+
from .printer import PetabStrPrinter, petab_math_str # noqa: F401
34
from .sympify import sympify_petab # noqa: F401

petab/v1/math/printer.py

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""A PEtab-compatible sympy string-printer."""
2+
3+
import sympy as sp
4+
from sympy.printing.str import StrPrinter
5+
6+
7+
class PetabStrPrinter(StrPrinter):
8+
"""A PEtab-compatible sympy string-printer."""
9+
10+
def _print_Pow(self, expr: sp.Pow):
11+
# Custom printing for the power operator
12+
base, exp = expr.as_base_exp()
13+
return f"{self._print(base)} ^ {self._print(exp)}"
14+
15+
def _print_Infinity(self, expr):
16+
# Custom printing for infinity
17+
return "inf"
18+
19+
def _print_NegativeInfinity(self, expr):
20+
# Custom printing for negative infinity
21+
return "-inf"
22+
23+
24+
def petab_math_str(expr: sp.Expr) -> str:
25+
"""Convert a sympy expression to a PEtab-compatible math expression string.
26+
27+
:example:
28+
>>> expr = sp.sympify("x**2 + sin(y)")
29+
>>> petab_math_str(expr)
30+
'x ^ 2 + sin(y)'
31+
"""
32+
33+
return PetabStrPrinter().doprint(expr)

tests/v1/math/test_math.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from sympy.abc import _clash
99
from sympy.logic.boolalg import Boolean
1010

11-
from petab.math import sympify_petab
11+
from petab.math import petab_math_str, sympify_petab
1212

1313

1414
def test_sympify_numpy():
@@ -55,20 +55,27 @@ def read_cases():
5555
@pytest.mark.parametrize("expr_str, expected", read_cases())
5656
def test_parse_cases(expr_str, expected):
5757
"""Test PEtab math expressions for the PEtab test suite."""
58-
result = sympify_petab(expr_str)
59-
if isinstance(result, Boolean):
60-
assert result == expected
58+
sym_expr = sympify_petab(expr_str)
59+
if isinstance(sym_expr, Boolean):
60+
assert sym_expr == expected
6161
else:
6262
try:
63-
result = float(result.evalf())
63+
result = float(sym_expr.evalf())
6464
assert np.isclose(result, expected), (
6565
f"{expr_str}: Expected {expected}, got {result}"
6666
)
6767
except TypeError:
68-
assert result == expected, (
68+
assert sym_expr == expected, (
6969
f"{expr_str}: Expected {expected}, got {result}"
7070
)
7171

72+
# test parsing, printing, and parsing again
73+
resympified = sympify_petab(petab_math_str(sym_expr))
74+
if sym_expr.is_number:
75+
assert np.isclose(float(resympified), float(sym_expr))
76+
else:
77+
assert resympified.equals(sym_expr), (sym_expr, resympified)
78+
7279

7380
def test_ids():
7481
"""Test symbols in expressions."""

0 commit comments

Comments
 (0)