From 43d21d84093215568822fcc83aaec70eb0ef03c4 Mon Sep 17 00:00:00 2001 From: Isuru Fernando Date: Mon, 27 Jun 2022 14:36:22 -0500 Subject: [PATCH] Add SpatialConstant (#86) * Add SpatialConstant * Add docs * Improve docs around SpatialConstant, ExpressionKernel Co-authored-by: Andreas Kloeckner --- doc/conf.py | 4 +++ doc/misc.rst | 2 ++ sumpy/kernel.py | 63 ++++++++++++++++++++++++++++------------------- sumpy/symbolic.py | 52 +++++++++++++++++++++++++++++++++++--- 4 files changed, 93 insertions(+), 28 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 5af7433c..5dcae4e4 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -27,3 +27,7 @@ intersphinx_mapping = { "https://docs.sympy.org/latest/": None, "https://matplotlib.org/stable/": None, } + +nitpick_ignore_regex = [ + ["py:class", r"symengine\.(.+)"], # :cry: + ] diff --git a/doc/misc.rst b/doc/misc.rst index b281d378..10b5042d 100644 --- a/doc/misc.rst +++ b/doc/misc.rst @@ -3,6 +3,8 @@ Misc Tools .. automodule:: sumpy.tools +.. automodule:: sumpy.symbolic + Installation ============ diff --git a/sumpy/kernel.py b/sumpy/kernel.py index ccc30bcc..05efd4bb 100644 --- a/sumpy/kernel.py +++ b/sumpy/kernel.py @@ -24,10 +24,10 @@ THE SOFTWARE. import loopy as lp import numpy as np from pymbolic.mapper import IdentityMapper, CSECachingMapperMixin -from sumpy.symbolic import pymbolic_real_norm_2 +from sumpy.symbolic import pymbolic_real_norm_2, SpatialConstant import sumpy.symbolic as sym from pymbolic.primitives import make_sym_vector -from pymbolic import var, parse +from pymbolic import var from pytools import memoize_method from collections import defaultdict @@ -286,27 +286,37 @@ class Kernel: class ExpressionKernel(Kernel): - is_complex_valued = False + r""" + .. attribute:: expression + + A :mod:`pymbolic` expression depending on + variables *d_1* through *d_N* where *N* equals *dim*. + (These variables match what is returned from + :func:`pymbolic.primitives.make_sym_vector` with + argument `"d"`.) Any variable that is not *d* or + a :class:`~sumpy.symbolic.SpatialConstant` will be + viewed as potentially spatially varying. + + .. attribute:: global_scaling_const + + A constant :mod:`pymbolic` expression for the + global scaling of the kernel. Typically, this ensures that + the kernel is scaled so that :math:`\mathcal L(G)(x)=C\delta(x)` + with a constant of 1, where :math:`\mathcal L` is the PDE + operator associated with the kernel. Not to be confused with + *rscale*, which keeps expansion coefficients benignly scaled. + + .. attribute:: is_complex_valued + + .. automethod:: __init__ + .. automethod:: get_expression + """ init_arg_names = ("dim", "expression", "global_scaling_const", "is_complex_valued") def __init__(self, dim, expression, global_scaling_const, is_complex_valued): - r""" - :arg expression: A :mod:`pymbolic` expression depending on - variables *d_1* through *d_N* where *N* equals *dim*. - (These variables match what is returned from - :func:`pymbolic.primitives.make_sym_vector` with - argument `"d"`.) - :arg global_scaling_const: A constant :mod:`pymbolic` expression for the - global scaling of the kernel. Typically, this ensures that - the kernel is scaled so that :math:`\mathcal L(G)(x)=C\delta(x)` - with a constant of 1, where :math:`\mathcal L` is the PDE - operator associated with the kernel. Not to be confused with - *rscale*, which keeps expansion coefficients benignly scaled. - """ - # expression and global_scaling_const are pymbolic objects because # those pickle cleanly. D'oh, sympy! @@ -324,6 +334,8 @@ class ExpressionKernel(Kernel): return f"ExprKnl{self.dim}D" def get_expression(self, scaled_dist_vec): + """Return :attr:`expression` as a :class:`sumpy.symbolic.Basic`.""" + from sumpy.symbolic import PymbolicToSympyMapperWithSymbols expr = PymbolicToSympyMapperWithSymbols()(self.expression) @@ -339,7 +351,8 @@ class ExpressionKernel(Kernel): return expr def get_global_scaling_const(self): - """Return a global scaling of the kernel.""" + """Return a global scaling of the kernel as a :class:`sumpy.symbolic.Basic`. + """ from sumpy.symbolic import PymbolicToSympyMapperWithSymbols return PymbolicToSympyMapperWithSymbols()( @@ -488,7 +501,7 @@ class HelmholtzKernel(ExpressionKernel): :arg helmholtz_k_name: The argument name to use for the Helmholtz parameter when generating functions to evaluate this kernel. """ - k = var(helmholtz_k_name) + k = SpatialConstant(helmholtz_k_name) # Guard against code using the old positional interface. assert isinstance(allow_evanescent, bool) @@ -565,7 +578,7 @@ class YukawaKernel(ExpressionKernel): :arg yukawa_lambda_name: The argument name to use for the Yukawa parameter when generating functions to evaluate this kernel. """ - lam = var(yukawa_lambda_name) + lam = SpatialConstant(yukawa_lambda_name) # NOTE: The Yukawa kernel is given by [1] # -1/(2 pi)**(n/2) * (lam/r)**(n/2-1) * K(n/2-1, lam r) @@ -658,11 +671,11 @@ class ElasticityKernel(ExpressionKernel): evaluate this kernel. Can also be a numeric value. """ if isinstance(viscosity_mu, str): - mu = parse(viscosity_mu) + mu = SpatialConstant(viscosity_mu) else: mu = viscosity_mu if isinstance(poisson_ratio, str): - nu = parse(poisson_ratio) + nu = SpatialConstant(poisson_ratio) else: nu = poisson_ratio @@ -769,7 +782,7 @@ class StressletKernel(ExpressionKernel): """ # mu is unused but kept for consistency with the Stokeslet. if isinstance(viscosity_mu, str): - mu = parse(viscosity_mu) + mu = SpatialConstant(viscosity_mu) else: mu = viscosity_mu @@ -854,11 +867,11 @@ class LineOfCompressionKernel(ExpressionKernel): evaluate this kernel. Can also be a numeric value. """ if isinstance(viscosity_mu, str): - mu = parse(viscosity_mu) + mu = SpatialConstant(viscosity_mu) else: mu = viscosity_mu if isinstance(poisson_ratio, str): - nu = parse(poisson_ratio) + nu = SpatialConstant(poisson_ratio) else: nu = poisson_ratio diff --git a/sumpy/symbolic.py b/sumpy/symbolic.py index 53f73e3c..4e1234ae 100644 --- a/sumpy/symbolic.py +++ b/sumpy/symbolic.py @@ -20,6 +20,19 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +__doc__ = """ + + Symbolic Tools + ============== + + .. class:: Basic + + The expression base class for the "heavy-duty" computer algebra toolkit + in use. Either :class:`sympy.core.basic.Basic` or :class:`symengine.Basic`. + + .. autoclass:: SpatialConstant +""" + import numpy as np from pymbolic.mapper import IdentityMapper as IdentityMapperBase @@ -69,12 +82,13 @@ _find_symbolic_backend() if USE_SYMENGINE: import symengine as sym from pymbolic.interop.symengine import ( - PymbolicToSymEngineMapper as PymbolicToSympyMapper, - SymEngineToPymbolicMapper as SympyToPymbolicMapper) + PymbolicToSymEngineMapper as PymbolicToSympyMapperBase, + SymEngineToPymbolicMapper as SympyToPymbolicMapperBase) else: import sympy as sym from pymbolic.interop.sympy import ( - PymbolicToSympyMapper, SympyToPymbolicMapper) + PymbolicToSympyMapper as PymbolicToSympyMapperBase, + SympyToPymbolicMapper as SympyToPymbolicMapperBase) # Symbolic API common to SymEngine and sympy. # Before adding a function here, make sure it's present in both modules. @@ -241,6 +255,38 @@ def find_power_of(base, prod): return result[power] +class SpatialConstant(prim.Variable): + """A symbolic constant to represent a symbolic variable that + is spatially constant, like for example the wave-number :math:`k` + in the setting of a constant-coefficient Helmholtz problem. + For use in :attr:`sumpy.kernel.ExpressionKernel.expression`. + Any variable occurring there that is not a :class:`SpatialConstant` + is assumed to have a spatial dependency. + """ + + prefix = "_spatial_constant_" + mapper_method = "map_spatial_constant" + + def as_sympy(self): + return sym.Symbol(f"{self.prefix}{self.name}") + + @classmethod + def from_sympy(cls, expr): + return cls(expr.name[len(cls.prefix):]) + + +class PymbolicToSympyMapper(PymbolicToSympyMapperBase): + def map_spatial_constant(self, expr): + return expr.as_sympy() + + +class SympyToPymbolicMapper(SympyToPymbolicMapperBase): + def map_Symbol(self, expr): # noqa + if expr.name.startswith(SpatialConstant.prefix): + return SpatialConstant.from_sympy(expr) + return SympyToPymbolicMapperBase.map_Symbol(self, expr) + + class PymbolicToSympyMapperWithSymbols(PymbolicToSympyMapper): def map_variable(self, expr): if expr.name == "I": -- GitLab