From 6a2bccb004ea98d19138de506fd695cc2d5ce619 Mon Sep 17 00:00:00 2001 From: Isuru Fernando <isuruf@gmail.com> Date: Tue, 21 May 2019 18:58:23 -0500 Subject: [PATCH] New API for PDE --- sumpy/expansion/__init__.py | 189 ++------------------------------- sumpy/expansion/pde_utils.py | 200 +++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+), 182 deletions(-) create mode 100644 sumpy/expansion/pde_utils.py diff --git a/sumpy/expansion/__init__.py b/sumpy/expansion/__init__.py index 02ba9fec..888ba26d 100644 --- a/sumpy/expansion/__init__.py +++ b/sumpy/expansion/__init__.py @@ -29,8 +29,9 @@ import logging from pytools import memoize_method import sumpy.symbolic as sym from collections import defaultdict -from sumpy.tools import CoeffIdentifier, add_mi, nth_root_assume_positive - +from sumpy.tools import add_mi +from .pde_utils import (make_pde_syms, process_pde, laplacian, div, curl, grad, + PDE) __doc__ = """ .. autoclass:: ExpansionBase @@ -399,7 +400,6 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): from six import iteritems from sumpy.tools import nullspace, solve_symbolic - tol = 1e-13 stored_identifiers = [] from pytools import ProcessLogger @@ -469,7 +469,7 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): coeff_matrix = defaultdict(list) for i in range(s.shape[0]): for j in range(s.shape[1]): - if np.abs(s[i, j]) > tol: + if s[i, j] != 0: coeff_matrix[j].append((i, s[i, j])) plog.done() @@ -580,181 +580,6 @@ class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): return rows -class PDE(object): - r""" - Represents a system of PDEs of dimension `dim`. It is represented by a - list of dictionaries with each dictionary representing a single PDE. - Each dictionary maps a :class:`CoeffIdentifier` object to a value, - - .. math:: - - \sum_{(mi, ident), c \text{pde\_dict}} - \frac{\partial^{\sum(mi)}}{\partial u^mi} c = 0, - - where :math:`u` is the solution vector of the PDE. - """ - def __init__(self, dim, eqs): - """ - :arg dim: dimension of the PDE - :arg eqs: list of dictionaries mapping a :class:`CoeffIdentifier` to a - value. - """ - self.dim = dim - self.eqs = eqs - - def __mul__(self, param): - eqs = [] - for eq in self.eqs: - new_eq = dict() - for k, v in eq.items(): - new_eq[k] = eq[k] * param - eqs.append(new_eq) - return PDE(self.dim, eqs=eqs) - - __rmul__ = __mul__ - - def __add__(self, other_pde): - assert self.dim == other_pde.dim - assert len(self.eqs) == len(other_pde.eqs) - eqs = [] - for eq1, eq2 in zip(self.eqs, other_pde.eqs): - eq = defaultdict(lambda: 0) - for k, v in eq1.items(): - eq[k] += v - for k, v in eq2.items(): - eq[k] += v - eqs.append(dict(eq)) - return PDE(self.dim, eqs=eqs) - - __radd__ = __add__ - - def __sub__(self, other_pde): - return self + (-1)*other_pde - - def laplacian(self): - p = PDE(self.dim, eqs=[]) - for j in range(len(self.eqs)): - p = p | self[j].grad().div() - return p - - def __or__(self, other_pde): - assert self.dim == other_pde.dim - eqs = self.eqs + other_pde.eqs - return PDE(self.dim, eqs=eqs) - - def __getitem__(self, key): - eqs = self.eqs.__getitem__(key) - if not isinstance(eqs, list): - eqs = [eqs] - return PDE(self.dim, eqs=eqs) - - def grad(self): - assert len(self.eqs) == 1 - eqs = [] - for d in range(self.dim): - new_eq = defaultdict(lambda: 0) - for ident, v in self.eqs[0].items(): - mi = list(ident.mi) - mi[d] += 1 - new_ident = CoeffIdentifier(tuple(mi), ident.iexpr) - new_eq[new_ident] += v - eqs.append(dict(new_eq)) - return PDE(self.dim, eqs=eqs) - - def diff(self, mi): - eqs = [] - for eq in self.eqs: - new_eq = defaultdict(lambda: 0) - for ident, v in eq.items(): - new_mi = add_mi(ident.mi, mi) - new_ident = CoeffIdentifier(tuple(new_mi), ident.iexpr) - new_eq[new_ident] += v - eqs.append(dict(new_eq)) - return PDE(self.dim, eqs=eqs) - - def curl(self): - assert len(self.eqs) == self.dim - if self.dim == 2: - f1, f2 = self[0], self[1] - return f2.diff((1, 0)) - f1.diff((0, 1)) - - assert self.dim == 3 - eqs = None - for d in range(3): - f1, f2 = self[(d+1) % 3], self[(d+2) % 3] - mi1 = [0, 0, 0] - mi1[(d+1) % 3] = 1 - mi2 = [0, 0, 0] - mi2[(d+2) % 3] = 1 - new_eqs = f2.diff(mi1) - f1.diff(mi2) - if eqs is None: - eqs = new_eqs - else: - eqs = eqs | new_eqs - - return eqs - - def div(self): - result = defaultdict(lambda: 0) - for d, eq in enumerate(self.eqs): - for ident, v in eq.items(): - mi = list(ident.mi) - mi[d] += 1 - new_ident = CoeffIdentifier(tuple(mi), ident.iexpr) - result[new_ident] += v - return PDE(self.dim, eqs=[dict(result)]) - - def __repr__(self): - return repr(self.eqs) - - -def process_pde(pde): - """ - Process a PDE object to return a PDE and a multiplier such that - the sum of multiplier ** order * derivative * coefficient gives the - original PDE `pde`. - """ - multiplier = None - for eq in pde.eqs: - for ident1, val1 in eq.items(): - for ident2, val2 in eq.items(): - s1 = sum(ident1.mi) - s2 = sum(ident2.mi) - if s1 == s2: - continue - m = nth_root_assume_positive(val1/val2, s2 - s1) - if multiplier is None and not isinstance(m, (int, sym.Integer)): - multiplier = m - - if multiplier is None: - return pde, 1 - eqs = [] - for eq in pde.eqs: - new_eq = dict() - for i, (k, v) in enumerate(eq.items()): - new_eq[k] = v * multiplier**sum(k.mi) - if i == 0: - val = new_eq[k] - new_eq[k] /= sym.sympify(val) - if isinstance(new_eq[k], sym.Integer): - new_eq[k] = int(new_eq[k]) - eqs.append(new_eq) - return PDE(pde.dim, eqs=eqs), multiplier - - -def make_pde_syms(dim, nexprs): - """ - Returns a list of expressions of size `nexprs` to create a PDE - of dimension `dim`. - """ - eqs = [] - for iexpr in range(nexprs): - mi = [0]*dim - eq = dict() - eq[CoeffIdentifier(tuple(mi), iexpr)] = 1 - eqs.append(eq) - return PDE(dim, eqs=eqs) - class LaplaceExpansionTermsWrangler(LinearPDEBasedExpansionTermsWrangler): @@ -766,7 +591,7 @@ class LaplaceExpansionTermsWrangler(LinearPDEBasedExpansionTermsWrangler): def get_pdes(self): w = make_pde_syms(self.dim, 1) - return w.laplacian(), 0, 1 + return laplacian(w), 0, 1 def _get_reduced_coeffs(self, nullspace): idx = [] @@ -790,7 +615,7 @@ class HelmholtzExpansionTermsWrangler(LinearPDEBasedExpansionTermsWrangler): def get_pdes(self, **kwargs): w = make_pde_syms(self.dim, 1) k = sym.Symbol(self.helmholtz_k_name) - return (w.laplacian() + k**2 * w), 0, 1 + return (laplacian(w) + k**2 * w), 0, 1 def _get_reduced_coeffs(self, nullspace): idx = [] @@ -817,7 +642,7 @@ class StokesExpansionTermsWrangler(LinearPDEBasedExpansionTermsWrangler): mu = sym.Symbol(self.viscosity_mu_name) u = w[:self.dim] p = w[-1] - pdes = (mu * u.laplacian() - p.grad() | u.div()) + pdes = PDE(self.dim, mu * laplacian(u) - grad(p), div(u)) return pdes, self.icomp, self.dim+1 # }}} diff --git a/sumpy/expansion/pde_utils.py b/sumpy/expansion/pde_utils.py new file mode 100644 index 00000000..1b26e038 --- /dev/null +++ b/sumpy/expansion/pde_utils.py @@ -0,0 +1,200 @@ +from __future__ import division, absolute_import + +__copyright__ = "Copyright (C) 2019 Isuru Fernando" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +from collections import defaultdict +from sumpy.tools import CoeffIdentifier, add_mi, nth_root_assume_positive +import sumpy.symbolic as sym + +class PDE(object): + r""" + Represents a system of PDEs of dimension `dim`. It is represented by a + list of dictionaries with each dictionary representing a single PDE. + Each dictionary maps a :class:`CoeffIdentifier` object to a value, + + .. math:: + + \sum_{(mi, ident), c \text{pde\_dict}} + \frac{\partial^{\sum(mi)}}{\partial u^mi} c = 0, + + where :math:`u` is the solution vector of the PDE. + """ + def __init__(self, dim, *eqs): + """ + :arg dim: dimension of the PDE + :arg eqs: list of dictionaries mapping a :class:`CoeffIdentifier` to a + value or PDE instance + """ + self.dim = dim + self.eqs = [] + for obj in eqs: + if isinstance(obj, PDE): + self.eqs.extend(obj.eqs) + else: + self.eqs.append(obj) + + def __mul__(self, param): + eqs = [] + for eq in self.eqs: + new_eq = dict() + for k, v in eq.items(): + new_eq[k] = eq[k] * param + eqs.append(new_eq) + return PDE(self.dim, *eqs) + + __rmul__ = __mul__ + + def __add__(self, other_pde): + assert self.dim == other_pde.dim + assert len(self.eqs) == len(other_pde.eqs) + eqs = [] + for eq1, eq2 in zip(self.eqs, other_pde.eqs): + eq = defaultdict(lambda: 0) + for k, v in eq1.items(): + eq[k] += v + for k, v in eq2.items(): + eq[k] += v + eqs.append(dict(eq)) + return PDE(self.dim, *eqs) + + __radd__ = __add__ + + def __sub__(self, other_pde): + return self + (-1)*other_pde + + def __getitem__(self, key): + eqs = self.eqs.__getitem__(key) + if not isinstance(eqs, list): + eqs = [eqs] + return PDE(self.dim, *eqs) + + def __repr__(self): + return repr(self.eqs) + + +def laplacian(pde): + p = PDE(pde.dim) + for j in range(len(pde.eqs)): + p.eqs.append(div(grad(pde[j])).eqs[0]) + return p + + +def diff(pde, mi): + eqs = [] + for eq in pde.eqs: + new_eq = defaultdict(lambda: 0) + for ident, v in eq.items(): + new_mi = add_mi(ident.mi, mi) + new_ident = CoeffIdentifier(tuple(new_mi), ident.iexpr) + new_eq[new_ident] += v + eqs.append(dict(new_eq)) + return PDE(pde.dim, *eqs) + + +def grad(pde): + assert len(pde.eqs) == 1 + eqs = [] + for d in range(pde.dim): + mi = [0]*pde.dim + mi[d] += 1 + eqs.append(diff(pde, mi).eqs[0]) + return PDE(pde.dim, *eqs) + + +def curl(pde): + assert len(pde.eqs) == pde.dim + if pde.dim == 2: + f1, f2 = pde[0], pde[1] + return diff(f2, (1, 0)) - diff(f1, (0, 1)) + + assert pde.dim == 3 + eqs = [] + for d in range(3): + f1, f2 = pde[(d+1) % 3], pde[(d+2) % 3] + mi1 = [0, 0, 0] + mi1[(d+1) % 3] = 1 + mi2 = [0, 0, 0] + mi2[(d+2) % 3] = 1 + new_eqs = diff(f2, mi1) - diff(f1, mi2) + eqs.extend(new_eqs.eqs) + return PDE(pde.dim, *eqs) + + +def div(pde): + result = defaultdict(lambda: 0) + for d, eq in enumerate(pde.eqs): + for ident, v in eq.items(): + mi = list(ident.mi) + mi[d] += 1 + new_ident = CoeffIdentifier(tuple(mi), ident.iexpr) + result[new_ident] += v + return PDE(pde.dim, dict(result)) + + +def process_pde(pde): + """ + Process a PDE object to return a PDE and a multiplier such that + the sum of multiplier ** order * derivative * coefficient gives the + original PDE `pde`. + """ + multiplier = None + for eq in pde.eqs: + for ident1, val1 in eq.items(): + for ident2, val2 in eq.items(): + s1 = sum(ident1.mi) + s2 = sum(ident2.mi) + if s1 == s2: + continue + m = nth_root_assume_positive(val1/val2, s2 - s1) + if multiplier is None and not isinstance(m, (int, sym.Integer)): + multiplier = m + + if multiplier is None: + return pde, 1 + eqs = [] + for eq in pde.eqs: + new_eq = dict() + for i, (k, v) in enumerate(eq.items()): + new_eq[k] = v * multiplier**sum(k.mi) + if i == 0: + val = new_eq[k] + new_eq[k] /= sym.sympify(val) + if isinstance(new_eq[k], sym.Integer): + new_eq[k] = int(new_eq[k]) + eqs.append(new_eq) + return PDE(pde.dim, *eqs), multiplier + + +def make_pde_syms(dim, nexprs): + """ + Returns a list of expressions of size `nexprs` to create a PDE + of dimension `dim`. + """ + eqs = [] + for iexpr in range(nexprs): + mi = [0]*dim + eq = dict() + eq[CoeffIdentifier(tuple(mi), iexpr)] = 1 + eqs.append(eq) + return PDE(dim, *eqs) + -- GitLab