From fd013396a4a5bdece05ab688eb6a747284beb747 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Tue, 20 Oct 2026 08:10:29 +0200 Subject: [PATCH] Add `petab_symbol` Add `petab.v1.math.petab_symbol` (also available from `petab.v2.math`), which creates a sympy symbol with the same assumptions as the symbols created by `sympify_petab` (currently `real=True`). Use it in the parser and in `calculate`, instead of repeating `real=True`. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 3 +++ petab/v1/calculate.py | 10 ++++------ petab/v1/math/SympyVisitor.py | 16 ++++++++++++++-- petab/v1/math/__init__.py | 1 + petab/v1/math/sympify.py | 2 +- petab/v2/calculate.py | 8 ++++---- tests/v1/math/test_math.py | 6 +++++- 7 files changed, 32 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1398e4e..230e675a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,9 @@ * `petab.v2.Problem.get_unused_id` returns an identifier that is not yet used in the problem or its model (by @dweindl in https://github.com/PEtab-dev/libpetab-python/pull/561) +* `petab.v1.math.petab_symbol` (also in `petab.v2.math`) creates sympy + symbols with the same assumptions as `sympify_petab` + (by @dweindl in https://github.com/PEtab-dev/libpetab-python/pull/563) ## 0.9 series diff --git a/petab/v1/calculate.py b/petab/v1/calculate.py index 46fdcb91..f8c58f2a 100644 --- a/petab/v1/calculate.py +++ b/petab/v1/calculate.py @@ -11,7 +11,7 @@ import petab.v1 as petab from .C import * -from .math import sympify_petab +from .math import petab_symbol, sympify_petab __all__ = [ "calculate_chi2", @@ -202,18 +202,16 @@ def evaluate_noise_formula( ) # fill in measurement specific parameters overrides = { - sp.Symbol( - f"noiseParameter{i_obs_par + 1}_{observable_id}", real=True - ): obs_par + petab_symbol(f"noiseParameter{i_obs_par + 1}_{observable_id}"): obs_par for i_obs_par, obs_par in enumerate(observable_parameter_overrides) } # fill in observables - overrides[sp.Symbol(observable_id, real=True)] = simulation + overrides[petab_symbol(observable_id)] = simulation # fill in general parameters for row in parameter_df.itertuples(): - overrides[sp.Symbol(row.Index, real=True)] = row.nominalValue + overrides[petab_symbol(row.Index)] = row.nominalValue # replace parametric measurement specific parameters for key, value in overrides.items(): diff --git a/petab/v1/math/SympyVisitor.py b/petab/v1/math/SympyVisitor.py index 92563d80..ddf0891b 100644 --- a/petab/v1/math/SympyVisitor.py +++ b/petab/v1/math/SympyVisitor.py @@ -6,7 +6,7 @@ from ._generated.PetabMathExprParser import PetabMathExprParser from ._generated.PetabMathExprParserVisitor import PetabMathExprParserVisitor -__all__ = ["MathVisitorSympy"] +__all__ = ["MathVisitorSympy", "petab_symbol"] # Mappings of PEtab math functions to sympy functions @@ -65,6 +65,18 @@ } +def petab_symbol(name: str) -> sp.Symbol: + """Create a sympy symbol for a PEtab identifier. + + The symbol has the same assumptions as the symbols created by + :func:`sympify_petab`. + + :param name: The identifier. + :return: The symbol. + """ + return sp.Symbol(name, real=True) + + class MathVisitorSympy(PetabMathExprParserVisitor): """ ANTLR4 visitor for PEtab-math-to-sympy conversion. @@ -101,7 +113,7 @@ def visitVar(self, ctx: PetabMathExprParser.VarContext) -> sp.Symbol: """Convert identifier to sympy Symbol.""" if ctx.getText().lower() in _reserved_names: raise ValueError(f"Use of reserved name {ctx.getText()!r}") - return sp.Symbol(ctx.getText(), real=True) + return petab_symbol(ctx.getText()) def visitMultExpr( self, ctx: PetabMathExprParser.MultExprContext diff --git a/petab/v1/math/__init__.py b/petab/v1/math/__init__.py index b85ca07b..3bf2244b 100644 --- a/petab/v1/math/__init__.py +++ b/petab/v1/math/__init__.py @@ -2,3 +2,4 @@ from .printer import PetabStrPrinter, petab_math_str # noqa: F401 from .sympify import sympify_petab # noqa: F401 +from .SympyVisitor import petab_symbol # noqa: F401 diff --git a/petab/v1/math/sympify.py b/petab/v1/math/sympify.py index adfee2f9..da129e04 100644 --- a/petab/v1/math/sympify.py +++ b/petab/v1/math/sympify.py @@ -34,7 +34,7 @@ def sympify_petab( .. note:: All symbols in the returned expression will have the ``real=True`` - assumption. + assumption, as created by :func:`petab_symbol`. :example: >>> from petab.v1.math import sympify_petab diff --git a/petab/v2/calculate.py b/petab/v2/calculate.py index 1e811974..4f843355 100644 --- a/petab/v2/calculate.py +++ b/petab/v2/calculate.py @@ -11,7 +11,7 @@ from petab.v1 import is_empty, split_parameter_replacement_list from .C import * -from .math import sympify_petab +from .math import petab_symbol, sympify_petab __all__ = [ "calculate_chi2", @@ -226,7 +226,7 @@ def evaluate_noise_formula( # fill in measurement specific parameters overrides = { - sp.Symbol(placeholder, real=True): override + petab_symbol(placeholder): override for placeholder, override in zip( [ p.strip() @@ -240,11 +240,11 @@ def evaluate_noise_formula( } # fill in observables - overrides[sp.Symbol(observable_id, real=True)] = simulation + overrides[petab_symbol(observable_id)] = simulation # fill in general parameters for row in parameter_df.itertuples(): - overrides[sp.Symbol(row.Index, real=True)] = row.nominalValue + overrides[petab_symbol(row.Index)] = row.nominalValue # replace parametric measurement specific parameters for key, value in overrides.items(): diff --git a/tests/v1/math/test_math.py b/tests/v1/math/test_math.py index 5708066d..019a6163 100644 --- a/tests/v1/math/test_math.py +++ b/tests/v1/math/test_math.py @@ -8,7 +8,7 @@ from sympy.abc import _clash from sympy.logic.boolalg import Boolean, BooleanFalse, BooleanTrue -from petab.v1.math import petab_math_str, sympify_petab +from petab.v1.math import petab_math_str, petab_symbol, sympify_petab def test_sympify_numpy(): @@ -36,6 +36,10 @@ def test_assumptions(): # non-real symbols are changed to real assert sympify_petab(sp.Symbol("x", real=False)).is_real + # `petab_symbol` creates the same symbols as the parser + assert petab_symbol("x") == sympify_petab("x") + assert petab_symbol("x") != sp.Symbol("x") + def test_printer(): a, b, c, d = sp.symbols("a b c d", real=True)