From d06979fc1a343fb43e2ed5fe2562c802a3da7b62 Mon Sep 17 00:00:00 2001 From: Alexandru Fikl Date: Fri, 4 Sep 2026 20:43:43 +0300 Subject: [PATCH 1/4] feat: rework sympy to pymbolic mappers --- sumpy/codegen.py | 25 +--- sumpy/e2p.py | 8 +- sumpy/kernel.py | 4 +- sumpy/qbx.py | 4 +- sumpy/symbolic.py | 246 +++++++++++++++++++++++++------------ sumpy/test/test_codegen.py | 5 +- sumpy/test/test_misc.py | 7 +- sumpy/tools.py | 7 +- 8 files changed, 184 insertions(+), 122 deletions(-) diff --git a/sumpy/codegen.py b/sumpy/codegen.py index bc72e8d14..c953f5a3e 100644 --- a/sumpy/codegen.py +++ b/sumpy/codegen.py @@ -64,11 +64,9 @@ __doc__ = """ - Conversion of :mod:`sympy` expressions to :mod:`loopy` ------------------------------------------------------ -.. autoclass:: SympyToPymbolicMapper .. autofunction:: to_loopy_insns """ @@ -78,26 +76,6 @@ def wrap_in_cse(expr: Expression, return prim.make_common_subexpression(expr, prefix, wrap_vars=False) -# {{{ sympy -> pymbolic mapper - -_SPECIAL_FUNCTION_NAMES = frozenset(dir(sym.functions)) - - -class SympyToPymbolicMapper(sym.SympyToPymbolicMapper): - @override - def not_supported(self, expr: object) -> Expression: - if isinstance(expr, int): - return expr - elif getattr(expr, "is_Function", False): - func_name = sym.SympyToPymbolicMapper.function_name(self, expr) - return prim.Variable(func_name)( - *tuple(self.rec(arg) for arg in expr.args)) - else: - return sym.SympyToPymbolicMapper.not_supported(self, expr) - -# }}} - - # {{{ bessel -> loopy codegen BESSEL_PREAMBLE = """//CL// @@ -758,8 +736,7 @@ def to_loopy_insns( assignments = list(assignments) # convert from sympy - sympy_conv = SympyToPymbolicMapper() - pymbolic_assignments = [(name, sympy_conv(expr)) for name, expr in assignments] + pymbolic_assignments = [(name, sym.to_pymbolic(expr)) for name, expr in assignments] flat = FlattenMapper() bdr = BesselDerivativeReplacer() diff --git a/sumpy/e2p.py b/sumpy/e2p.py index bd46441a5..aa53985d9 100644 --- a/sumpy/e2p.py +++ b/sumpy/e2p.py @@ -107,12 +107,12 @@ def get_loopy_args(self): return gather_loopy_arguments((self.expansion, *tuple(self.kernels))) def get_kernel_scaling_assignment(self): - from sumpy.symbolic import SympyToPymbolicMapper - sympy_conv = SympyToPymbolicMapper() + from sumpy.symbolic import to_pymbolic return [lp.Assignment(id="kernel_scaling", assignee="kernel_scaling", - expression=sympy_conv( - self.expansion.kernel.get_global_scaling_const()), + expression=to_pymbolic( + self.expansion.kernel.get_global_scaling_const(), + ), temp_var_type=lp.Optional(None), )] # }}} diff --git a/sumpy/kernel.py b/sumpy/kernel.py index 821f9993e..366345baf 100644 --- a/sumpy/kernel.py +++ b/sumpy/kernel.py @@ -580,7 +580,7 @@ def __str__(self) -> str: @override def get_expression(self, dist_vec: sym.Matrix) -> sym.Expr: - expr = sym.PymbolicToSympyMapperWithSymbols().to_expr(self.expression) + expr = sym.to_symbolic_expr(self.expression, symbols=True) if self.dim != len(dist_vec): raise ValueError( @@ -594,7 +594,7 @@ def get_expression(self, dist_vec: sym.Matrix) -> sym.Expr: @override def get_global_scaling_const(self) -> sym.Expr: - return sym.PymbolicToSympyMapperWithSymbols().to_expr(self.global_scaling_const) + return sym.to_symbolic_expr(self.global_scaling_const, symbols=True) @override def get_derivative_taker( diff --git a/sumpy/qbx.py b/sumpy/qbx.py index edbe5519c..a638d833a 100644 --- a/sumpy/qbx.py +++ b/sumpy/qbx.py @@ -111,9 +111,7 @@ def get_cache_key(self): tuple(self.value_dtypes)) def _expand(self, sac, avec, bvec, rscale, isrc): - from sumpy.symbolic import PymbolicToSympyMapper - conv = PymbolicToSympyMapper() - strengths = [conv.to_expr(self.get_strength_or_not(isrc, idx)) + strengths = [sym.to_symbolic_expr(self.get_strength_or_not(isrc, idx)) for idx in range(len(self.source_kernels))] return self.expansion.coefficients_from_source_vec( self.source_kernels, avec, bvec, rscale=rscale, weights=strengths, diff --git a/sumpy/symbolic.py b/sumpy/symbolic.py index 851c18631..8412dce1d 100644 --- a/sumpy/symbolic.py +++ b/sumpy/symbolic.py @@ -39,7 +39,7 @@ import logging import math -from typing import TYPE_CHECKING, ClassVar, cast +from typing import TYPE_CHECKING, Any, ClassVar, cast from typing_extensions import override @@ -57,19 +57,21 @@ logger = logging.getLogger(__name__) USE_SYMENGINE = False +HAS_SYMENGINE = False # {{{ symbolic backend def _find_symbolic_backend(): global USE_SYMENGINE + global HAS_SYMENGINE try: import symengine # ruff:ignore[unused-import] - symengine_found = True + HAS_SYMENGINE = True symengine_error = None except ImportError as import_error: - symengine_found = False + HAS_SYMENGINE = False symengine_error = import_error allowed_backends = ("sympy", "symengine") @@ -85,12 +87,12 @@ def _find_symbolic_backend(): ", ".join(f"'{val}'" for val in allowed_backends)) ) - if backend == "symengine" and not symengine_found: + if backend == "symengine" and not HAS_SYMENGINE: raise RuntimeError(f"could not find SymEngine: {symengine_error}") USE_SYMENGINE = (backend == "symengine") # pyright: ignore[reportConstantRedefinition] else: - USE_SYMENGINE = symengine_found # pyright: ignore[reportConstantRedefinition] + USE_SYMENGINE = HAS_SYMENGINE # pyright: ignore[reportConstantRedefinition] _find_symbolic_backend() @@ -100,19 +102,23 @@ def _find_symbolic_backend(): # {{{ symbolic expressions + if TYPE_CHECKING or not USE_SYMENGINE: import sympy as sym - - from pymbolic.interop.sympy import ( - PymbolicToSympyMapper as PymbolicToSympyMapperBase, - SympyToPymbolicMapper as SympyToPymbolicMapperBase, - ) else: import symengine as sym + +from pymbolic.interop.sympy import ( + PymbolicToSympyMapper as PymbolicToSympyMapperBase, + SympyToPymbolicMapper as SympyToPymbolicMapperBase, +) + + +if HAS_SYMENGINE: from pymbolic.interop.symengine import ( - PymbolicToSymEngineMapper as PymbolicToSympyMapperBase, - SymEngineToPymbolicMapper as SympyToPymbolicMapperBase, + PymbolicToSymEngineMapper as PymbolicToSymEngineMapperBase, + SymEngineToPymbolicMapper as SymEngineToPymbolicMapperBase, ) # Symbolic API common to SymEngine and sympy. @@ -201,7 +207,6 @@ def _get_assignments_in_maxima( from pymbolic.interop.maxima import MaximaStringifyMapper mstr = MaximaStringifyMapper() - s2p = SympyToPymbolicMapper() dkill = _DerivativeKiller() result: list[str] = [] @@ -216,7 +221,7 @@ def write_assignment(name: str) -> None: write_assignment(symb.name) result.append("{}{} : {};".format( - prefix, name, mstr(dkill(s2p( + prefix, name, mstr(dkill(to_pymbolic( assignments[name].subs(prefix_subst_dict)))))) written_assignments.add(name) @@ -404,97 +409,102 @@ def from_sympy(cls, expr: Symbol) -> SpatialConstant: # {{{ sympy <-> pymbolic interop -class PymbolicToSympyMapper(PymbolicToSympyMapperBase): - def map_spatial_constant(self, expr: SpatialConstant) -> Basic: - return expr.as_sympy() +class PymbolicToSympyLikeMixin: + symbols: bool -class SympyToPymbolicMapper(SympyToPymbolicMapperBase): - @override - def map_Symbol(self, expr: Symbol) -> Expression: - try: - return SpatialConstant.from_sympy(expr) - except ValueError: - return SympyToPymbolicMapperBase.map_Symbol(self, expr) - - @override - def map_Pow(self, expr: Pow) -> Expression: - if expr.exp == -1: - return 1 / self.rec_arith(expr.base) - else: - return SympyToPymbolicMapperBase.map_Pow(self, expr) - - @override - def map_Mul(self, expr: Mul) -> Expression: - num_args: list[ArithmeticExpression] = [] - den_args: list[ArithmeticExpression] = [] - for child in expr.args: - if (isinstance(child, Pow) - and isinstance(child.exp, Integer) - and child.exp < 0): - den_args.append(self.rec_arith(child.base)**(-self.rec_arith(child.exp))) - else: - num_args.append(self.rec_arith(child)) - - return math.prod(num_args) / math.prod(den_args) - + def __init__(self, *, symbols: bool = False) -> None: + self.symbols = symbols -class PymbolicToSympyMapperWithSymbols(PymbolicToSympyMapper): - @override def map_variable(self, expr: prim.Variable) -> Basic: + if not self.symbols: + return super().map_variable(expr) + if expr.name == "I": - return I + return self.sym.I elif expr.name == "pi": - return pi + return self.sym.pi else: - return PymbolicToSympyMapper.map_variable(self, expr) + return super().map_variable(expr) - @override def map_subscript(self, expr: prim.Subscript) -> sym.Basic: + if not self.symbols: + return super().map_subscript(expr) + if isinstance(expr.aggregate, prim.Variable) and isinstance(expr.index, int): - return Symbol(f"{expr.aggregate.name}{expr.index}") + # NOTE: this matches how make_sym_vector creates indexed vectors + return self.sym.Symbol(f"{expr.aggregate.name}{expr.index}") else: self.raise_conversion_error(expr) - @override def map_call(self, expr: prim.Call) -> sym.Basic: + if not self.symbols: + return super().map_call(expr) + function = expr.function if isinstance(function, prim.Variable): if function.name == "hankel_1": args = [self.rec(param) for param in expr.parameters] - args.append(sympify(0)) + args.append(self.sym.sympify(0)) return Hankel1(*args) elif function.name == "bessel_j": args = [self.rec(param) for param in expr.parameters] - args.append(sympify(0)) + args.append(self.sym.sympify(0)) return BesselJ(*args) - return PymbolicToSympyMapper.map_call(self, expr) + return super().map_call(expr) -class SympyToPymbolicMapperWithSymbols(SympyToPymbolicMapper): - if USE_SYMENGINE: - @override - def map_Constant(self, expr: object) -> Expression: - if expr is pi: - return prim.Variable("pi") - elif expr is I: - return prim.Variable("I") - else: - return super().map_Constant(expr) - else: - @override - def map_NumberSymbol(self, expr: sym.NumberSymbol) -> Expression: - if expr is pi: - return prim.Variable("pi") - elif expr is I: - return prim.Variable("I") +class PymbolicToSympyMapper(PymbolicToSympyLikeMixin, PymbolicToSympyMapperBase): + def map_spatial_constant(self, expr: SpatialConstant) -> Basic: + # NOTE: do not be tempted to use SpatialConstant.as_sympy() here: that + # uses the current symbolic backend, not necessarily sympy + return self.sym.Symbol(f"{expr.prefix}{expr.name}") + + +if HAS_SYMENGINE: + class PymbolicToSymEngineMapper(PymbolicToSympyLikeMixin, + PymbolicToSymEngineMapperBase): + def map_spatial_constant(self, expr: SpatialConstant) -> Basic: + return self.sym.Symbol(f"{expr.prefix}{expr.name}") + + +class SympyLikeToPymbolicMixin: + symbols: bool + + def __init__(self, *, symbols: bool = False) -> None: + self.symbols = symbols + + def map_Symbol(self, expr: Symbol) -> Expression: # ruff: ignore[invalid-function-name] + # NOTE: do not be tempted to use SpatialConstant.from_sympy() here: that + # uses the current symbolic backend, not necessarily sympy + if (isinstance(expr, self.sym.Symbol) + and expr.name.startswith(SpatialConstant.prefix)): + return SpatialConstant(expr.name[len(SpatialConstant.prefix):]) + + return super().map_Symbol(expr) + + def map_Pow(self, expr: Pow) -> Expression: # ruff: ignore[invalid-function-name] + if expr.exp == -1: + return 1 / self.rec_arith(expr.base) + else: + return super().map_Pow(expr) + + def map_Mul(self, expr: Mul) -> Expression: # ruff: ignore[invalid-function-name] + num_args: list[ArithmeticExpression] = [] + den_args: list[ArithmeticExpression] = [] + for child in expr.args: + if (isinstance(child, self.sym.Pow) + and isinstance(child.exp, self.sym.Integer) + and child.exp < 0): + den_args.append(self.rec_arith(child.base)**(-self.rec_arith(child.exp))) else: - return super().map_NumberSymbol(expr) + num_args.append(self.rec_arith(child)) + + return math.prod(num_args) / math.prod(den_args) - @override def not_supported(self, expr: object) -> Expression: - if getattr(expr, "is_Function", False): + if self.symbols and getattr(expr, "is_Function", False): function_name = self.function_name(expr) if function_name in {"Hankel1", "BesselJ"}: order, arg, nderivs = expr.args @@ -506,6 +516,88 @@ def not_supported(self, expr: object) -> Expression: return super().not_supported(expr) + +class SympyToPymbolicMapper(SympyLikeToPymbolicMixin, + SympyToPymbolicMapperBase): + @property + def sym(self) -> Any: + import sympy as sp + return sp + + @override + def map_NumberSymbol(self, expr: sym.NumberSymbol) -> Expression: + if not self.symbols: + return super().map_NumberSymbol(expr) + + if expr is self.sym.pi: + return prim.Variable("pi") + elif expr is self.sym.I: + return prim.Variable("I") + else: + return super().map_NumberSymbol(expr) + + +if HAS_SYMENGINE: + class SymEngineToPymbolicMapper(SympyLikeToPymbolicMixin, + SymEngineToPymbolicMapperBase): + @property + def sym(self) -> Any: + import symengine as sp + return sp + + @override + def map_Constant(self, expr: object) -> Expression: + if not self.symbols: + return super().map_Constant(expr) + + if expr is self.sym.pi: + return prim.Variable("pi") + elif expr is self.sym.I: + return prim.Variable("I") + else: + return super().map_Constant(expr) + + +def to_pymbolic(expr: Basic, *, symbols: bool = False) -> Expression: + r"""Convert the symbolic expression *expr* to :mod:`pymbolic`. + + :arg symbols: if *True*, this will also convert known symbols (e.g. :math:`\pi`) + to variables. + """ + if USE_SYMENGINE: + p2s = SymEngineToPymbolicMapper(symbols=symbols) + else: + p2s = SympyToPymbolicMapper(symbols=symbols) + + return p2s(expr) + + +def to_symbolic(expr: Expression, *, symbols: bool = False) -> Basic: + r"""Convert from :mod:`pymbolic` to a symbolic object. + + :arg symbols: if *True*, this will also convert known symbols (e.g. :math:`\pi`) + to their respective constants in the symbolic backend. + """ + if USE_SYMENGINE: + s2p = PymbolicToSymEngineMapper(symbols=symbols) + else: + s2p = PymbolicToSympyMapper(symbols=symbols) + + return s2p(expr) + + +def to_symbolic_expr(expr: Expression, *, symbols: bool = False) -> Expr: + r"""Convert from :mod:`pymbolic` to a symbolic expression. + + This is similar to :func:`to_symbolic`, but will raise an exception if the + result is not an algebraic expression. + """ + result = to_symbolic(expr, symbols=symbols) + if not isinstance(result, Expr): + raise TypeError(f"result is not an expression: {type(result)}") + + return result + # }}} diff --git a/sumpy/test/test_codegen.py b/sumpy/test/test_codegen.py index 4eb45b094..1e374aa9e 100644 --- a/sumpy/test/test_codegen.py +++ b/sumpy/test/test_codegen.py @@ -62,7 +62,7 @@ def test_line_taylor_coeff_growth(): from sumpy.expansion.local import LineTaylorLocalExpansion from sumpy.kernel import LaplaceKernel - from sumpy.symbolic import SympyToPymbolicMapper, make_sym_vector + from sumpy.symbolic import make_sym_vector, to_pymbolic order = 10 expn = LineTaylorLocalExpansion(LaplaceKernel(2), order) @@ -70,8 +70,7 @@ def test_line_taylor_coeff_growth(): bvec = make_sym_vector("b", 2) coeffs = expn.coefficients_from_source(expn.kernel, avec, bvec, rscale=1) - sym2pymbolic = SympyToPymbolicMapper() - coeffs_pymbolic = [sym2pymbolic(c) for c in coeffs] + coeffs_pymbolic = [to_pymbolic(c) for c in coeffs] from pymbolic.mapper.flop_counter import FlopCounter flop_counter = FlopCounter() diff --git a/sumpy/test/test_misc.py b/sumpy/test/test_misc.py index 703a783fc..528910b2f 100644 --- a/sumpy/test/test_misc.py +++ b/sumpy/test/test_misc.py @@ -1002,16 +1002,13 @@ def test_system_kernel_pickle(dim: int, cls: type[SystemKernel]) -> None: def test_symbolic_roundtrip_with_symbols() -> None: from pymbolic.primitives import Variable - s2p = sym.SympyToPymbolicMapperWithSymbols() - p2s = sym.PymbolicToSympyMapperWithSymbols() - sympy_exprs = [ sym.pi, sym.Symbol("x"), sym.sin(sym.Symbol("x")), ] for expr in sympy_exprs: - back = p2s.to_expr(s2p(expr)) + back = sym.to_symbolic(sym.to_pymbolic(expr, symbols=True), symbols=True) assert sym.sympify(expr) == sym.sympify(back) pymbolic_exprs = [ @@ -1022,7 +1019,7 @@ def test_symbolic_roundtrip_with_symbols() -> None: Variable("bessel_j")(2, Variable("x")), ] for expr in pymbolic_exprs: - back = s2p(p2s.to_expr(expr)) + back = sym.to_pymbolic(sym.to_symbolic(expr, symbols=True), symbols=True) assert expr == back # }}} diff --git a/sumpy/tools.py b/sumpy/tools.py index 6a5d2f419..0e6e8750e 100644 --- a/sumpy/tools.py +++ b/sumpy/tools.py @@ -348,14 +348,13 @@ def default_name(self) -> str: pass def get_kernel_scaling_assignments(self): - from sumpy.symbolic import SympyToPymbolicMapper - sympy_conv = SympyToPymbolicMapper() - import loopy as lp return [ lp.Assignment(id=f"knl_{i}_scaling", assignee=f"knl_{i}_scaling", - expression=sympy_conv(kernel.get_global_scaling_const()), + expression=sym.to_pymbolic( + kernel.get_global_scaling_const() + ), temp_var_type=lp.Optional(dtype), tags=frozenset([ScalingAssignmentTag()])) for i, (kernel, dtype) in enumerate( From 65a0a284c823c6440168c61c09cf8fce3322091f Mon Sep 17 00:00:00 2001 From: Alexandru Fikl Date: Sat, 5 Sep 2026 11:32:17 +0300 Subject: [PATCH 2/4] fix: roundtrip imaginary I to sympy --- sumpy/symbolic.py | 17 +++++++++++++---- sumpy/test/test_misc.py | 2 ++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/sumpy/symbolic.py b/sumpy/symbolic.py index 8412dce1d..a4b4f7cfc 100644 --- a/sumpy/symbolic.py +++ b/sumpy/symbolic.py @@ -531,11 +531,16 @@ def map_NumberSymbol(self, expr: sym.NumberSymbol) -> Expression: if expr is self.sym.pi: return prim.Variable("pi") - elif expr is self.sym.I: - return prim.Variable("I") else: return super().map_NumberSymbol(expr) + @override + def map_ImaginaryUnit(self, expr: sym.ImaginaryUnit) -> Expression: + if not self.symbols: + return super().map_ImaginaryUnit(expr) + + return prim.Variable("I") + if HAS_SYMENGINE: class SymEngineToPymbolicMapper(SympyLikeToPymbolicMixin, @@ -552,11 +557,15 @@ def map_Constant(self, expr: object) -> Expression: if expr is self.sym.pi: return prim.Variable("pi") - elif expr is self.sym.I: - return prim.Variable("I") else: return super().map_Constant(expr) + def map_ImaginaryUnit(self, expr: object) -> Expression: # ruff: ignore[invalid-function-name] + if not self.symbols: + return super().map_Complex(expr) + + return prim.Variable("I") + def to_pymbolic(expr: Basic, *, symbols: bool = False) -> Expression: r"""Convert the symbolic expression *expr* to :mod:`pymbolic`. diff --git a/sumpy/test/test_misc.py b/sumpy/test/test_misc.py index 528910b2f..9c476cb17 100644 --- a/sumpy/test/test_misc.py +++ b/sumpy/test/test_misc.py @@ -1004,6 +1004,7 @@ def test_symbolic_roundtrip_with_symbols() -> None: sympy_exprs = [ sym.pi, + sym.I, sym.Symbol("x"), sym.sin(sym.Symbol("x")), ] @@ -1013,6 +1014,7 @@ def test_symbolic_roundtrip_with_symbols() -> None: pymbolic_exprs = [ Variable("pi"), + Variable("I"), Variable("x"), sym.SpatialConstant("k"), Variable("hankel_1")(0, Variable("x")), From f55b874e71a1b547af531b3e9e6760b1811512d5 Mon Sep 17 00:00:00 2001 From: Alexandru Fikl Date: Sat, 5 Sep 2026 11:30:18 +0300 Subject: [PATCH 3/4] chore: update baseline --- .basedpyright/baseline.json | 906 ++++++++++++++++++++++++++++++++++-- 1 file changed, 857 insertions(+), 49 deletions(-) diff --git a/.basedpyright/baseline.json b/.basedpyright/baseline.json index f6e487467..6d89a7e2d 100644 --- a/.basedpyright/baseline.json +++ b/.basedpyright/baseline.json @@ -365,30 +365,6 @@ } ], "./sumpy/codegen.py": [ - { - "code": "reportUnknownArgumentType", - "range": { - "startColumn": 36, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportUnknownMemberType", - "range": { - "startColumn": 52, - "endColumn": 61, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 57, - "endColumn": 61, - "lineCount": 1 - } - }, { "code": "reportArgumentType", "range": { @@ -11436,16 +11412,16 @@ { "code": "reportUnknownMemberType", "range": { - "startColumn": 34, - "endColumn": 58, + "startColumn": 42, + "endColumn": 66, "lineCount": 1 } }, { "code": "reportUnknownArgumentType", "range": { - "startColumn": 59, - "endColumn": 63, + "startColumn": 67, + "endColumn": 71, "lineCount": 1 } }, @@ -13147,6 +13123,22 @@ "lineCount": 1 } }, + { + "code": "reportConstantRedefinition", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportConstantRedefinition", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, { "code": "reportMissingTypeStubs", "range": { @@ -13420,74 +13412,890 @@ } }, { - "code": "reportReturnType", + "code": "reportUnknownMemberType", "range": { - "startColumn": 23, - "endColumn": 37, + "startColumn": 15, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 20, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 27, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 24, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 24, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 27, + "endColumn": 39, "lineCount": 1 } }, { "code": "reportReturnType", "range": { - "startColumn": 23, - "endColumn": 37, + "startColumn": 53, + "endColumn": 62, "lineCount": 1 } }, { - "code": "reportGeneralTypeIssues", + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 20, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 27, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 24, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", "range": { "startColumn": 12, - "endColumn": 24, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 17, + "endColumn": 39, "lineCount": 1 } }, { "code": "reportUnknownMemberType", "range": { - "startColumn": 23, - "endColumn": 43, + "startColumn": 15, + "endColumn": 27, "lineCount": 1 } }, { "code": "reportAttributeAccessIssue", "range": { - "startColumn": 31, - "endColumn": 43, + "startColumn": 20, + "endColumn": 27, "lineCount": 1 } }, { "code": "reportUnknownMemberType", "range": { - "startColumn": 38, - "endColumn": 47, + "startColumn": 19, + "endColumn": 35, "lineCount": 1 } }, { "code": "reportAttributeAccessIssue", "range": { - "startColumn": 43, - "endColumn": 47, + "startColumn": 27, + "endColumn": 35, "lineCount": 1 } }, { - "code": "reportUnknownArgumentType", + "code": "reportUnknownMemberType", "range": { - "startColumn": 47, - "endColumn": 52, + "startColumn": 24, + "endColumn": 32, "lineCount": 1 } }, { - "code": "reportUnknownArgumentType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 64, - "endColumn": 67, + "startColumn": 29, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 28, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 28, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 33, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 23, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 29, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 28, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 28, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 33, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 23, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 23, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportMissingSuperCall", + "range": { + "startColumn": 8, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportMissingSuperCall", + "range": { + "startColumn": 12, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 29, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 29, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 34, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 23, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 28, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 27, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 34, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 34, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 39, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 35, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 41, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 46, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 46, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 51, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 30, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 32, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 88, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 37, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 47, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 53, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 62, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 67, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 77, + "endColumn": 86, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 83, + "endColumn": 86, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 32, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 37, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 16, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 28, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 33, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 38, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 43, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 38, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 43, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 55, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 60, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 23, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 11, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 15, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 32, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 38, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 42, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 45, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 19, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 23, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 23, + "endColumn": 34, "lineCount": 1 } }, From eee22cd78a2bec98513172b412185a449811bd97 Mon Sep 17 00:00:00 2001 From: Alexandru Fikl Date: Sun, 6 Sep 2026 10:16:14 +0300 Subject: [PATCH 4/4] docs: explain symbol handling in sympy-pymbolic mappers --- sumpy/symbolic.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/sumpy/symbolic.py b/sumpy/symbolic.py index a4b4f7cfc..69fecd3a6 100644 --- a/sumpy/symbolic.py +++ b/sumpy/symbolic.py @@ -411,7 +411,24 @@ def from_sympy(cls, expr: Symbol) -> SpatialConstant: class PymbolicToSympyLikeMixin: + r"""Mixin for :class:`sumpy.interop.common.PymbolicToSympyLikeMapper` that + maps :mod:`sumpy` specific expressions to the backend. + + If :attr:`symbols` is *True*, it handles: + + * Converting variables (such as :math:`\pi` and the imaginary :math:`i`) to + the corresponding backend constants. Otherwise, these would be converted + to numbers (floating or complex). + * Converting subscripts to the ``{name}{index}`` convention used by :mod:`sumpy`. + * Converting special functions (such as ``hankel_1``) to custom backend + expressions. + """ + symbols: bool + """If *True*, convert known :mod:`sumpy` symbols to backend equivalents. + Otherwise, they are converted to generic variants (e.g. ``pi`` becomes a + float instead of a ``sympy.pi``). + """ def __init__(self, *, symbols: bool = False) -> None: self.symbols = symbols @@ -470,7 +487,14 @@ def map_spatial_constant(self, expr: SpatialConstant) -> Basic: class SympyLikeToPymbolicMixin: + """Mixin for :class:`sumpy.interop.SympyLikeToPymbolicMapper` that converts + backend expressions to :mod:`sumpy` specific variants. + + See :class:`PymbolicToSympyLikeMixin` for details on special symbol handling. + """ + symbols: bool + """If *True*, convert special backend symbols to custom :mod:`sumpy` expressions.""" def __init__(self, *, symbols: bool = False) -> None: self.symbols = symbols