Source code for petab.v1.math.sympify

"""PEtab math to sympy conversion."""

import numpy as np
import sympy as sp
from antlr4 import CommonTokenStream, InputStream
from antlr4.error.ErrorListener import ErrorListener

from . import petab_math_str
from ._generated.PetabMathExprLexer import PetabMathExprLexer
from ._generated.PetabMathExprParser import PetabMathExprParser
from .SympyVisitor import MathVisitorSympy, bool2num

__all__ = ["sympify_petab"]


[docs] def sympify_petab( expr: str | int | float | sp.Basic, evaluate: bool = True ) -> sp.Expr | sp.Basic: """Convert PEtab math expression to sympy expression. Args: expr: PEtab math expression. evaluate: Whether to evaluate the expression. Raises: ValueError: Upon lexer/parser errors or if the expression is otherwise invalid. Returns: The sympy expression corresponding to `expr`. Boolean values are converted to numeric values. .. note:: All symbols in the returned expression will have the ``real=True`` assumption. :example: >>> from petab.v1.math import sympify_petab >>> sympify_petab("sin(0)") 0 >>> sympify_petab("sin(0)", evaluate=False) sin(0.0) >>> sympify_petab("sin(0)", evaluate=True) 0 >>> sympify_petab("1 + 2", evaluate=True) 3.00000000000000 >>> sympify_petab("1 + 2", evaluate=False) 1.0 + 2.0 >>> sympify_petab("piecewise(1, 1 > 2, 0)", evaluate=True) 0.0 >>> sympify_petab("piecewise(1, 1 > 2, 0)", evaluate=False) Piecewise((1.0, 1.0 > 2.0), (0.0, True)) >>> # currently, boolean values are converted to numeric values >>> # independent of the `evaluate` flag >>> sympify_petab("true", evaluate=True) 1.00000000000000 >>> sympify_petab("true", evaluate=False) 1.00000000000000 >>> # ... and integer values are converted to floats >>> sympify_petab("2", evaluate=True) 2.00000000000000 """ if isinstance(expr, sp.Basic): return sympify_petab(petab_math_str(expr)) if isinstance(expr, int) or isinstance(expr, np.integer): return sp.Integer(expr) if isinstance(expr, float) or isinstance(expr, np.floating): return sp.Float(expr) try: input_stream = InputStream(expr) except TypeError as e: raise TypeError(f"Error parsing {expr!r}: {e.args[0]}") from e lexer = PetabMathExprLexer(input_stream) # Set error listeners lexer.removeErrorListeners() lexer.addErrorListener(MathErrorListener()) stream = CommonTokenStream(lexer) parser = PetabMathExprParser(stream) parser.removeErrorListeners() parser.addErrorListener(MathErrorListener()) # Parse expression try: tree = parser.petabExpression() except ValueError as e: raise ValueError(f"Error parsing {expr!r}: {e.args[0]}") from None # Convert to sympy expression visitor = MathVisitorSympy(evaluate=evaluate) expr = visitor.visit(tree) expr = bool2num(expr) try: # check for `False`, we'll accept both `True` and `None` if expr.is_extended_real is False: raise ValueError(f"Expression {expr} is not real-valued.") except AttributeError as e: # work-around for `sp.sec(0, evaluate=False).is_extended_real` error if str(e) not in ( "'One' object has no attribute '_eval_is_extended_real'", "'Float' object has no attribute '_eval_is_extended_real'", ): raise return expr
class MathErrorListener(ErrorListener): """Error listener for math expression parser/lexer.""" def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e): # noqa N803 raise ValueError(f"Syntax error at {line}:{column}: {msg}")