From 45fa8d5f486a9de42d3613d78ee5dbb3f901e184 Mon Sep 17 00:00:00 2001
From: Isuru Fernando <isuruf@gmail.com>
Date: Tue, 2 Apr 2019 21:00:20 -0500
Subject: [PATCH] Make PDE spec cute and symbolic

---
 sumpy/expansion/__init__.py | 139 +++++++++++++++++++++++++-----------
 1 file changed, 99 insertions(+), 40 deletions(-)

diff --git a/sumpy/expansion/__init__.py b/sumpy/expansion/__init__.py
index c4e8e9ac..e2513e3c 100644
--- a/sumpy/expansion/__init__.py
+++ b/sumpy/expansion/__init__.py
@@ -373,7 +373,7 @@ class LinearRecurrenceBasedExpansionTermsWrangler(ExpansionTermsWrangler):
         offset = len(mis)
 
         for mi in mis:
-            for pde_dict in pdes:
+            for pde_dict in pdes.eqs:
                 eq = [0]*(len(mis)*nexpr)
                 for ident, coeff in iteritems(pde_dict):
                     c = tuple(add_mi(ident.mi, mi))
@@ -501,6 +501,96 @@ class LinearRecurrenceBasedExpansionTermsWrangler(ExpansionTermsWrangler):
         return rows
 
 
+class PDE(object):
+    def __init__(self, dim, nexprs=None, eqs=None):
+        if nexprs is not None:
+            eqs = []
+            for iexpr in range(nexprs):
+                mi = [0]*dim
+                eq = dict()
+                eq[CoeffIdentifier(tuple(mi), iexpr)] = 1
+                eqs.append(eq)
+        self.dim = dim
+        self.eqs = eqs
+
+    def __mul__(self, param):
+        eqs = self.eqs.copy()
+        for eq in eqs:
+            for k, v in eq.items():
+                eq[k] = eq[k] * param
+        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):
+        eqs = []
+        for eq in self.eqs:
+            new_eq = defaultdict(lambda: 0)
+            for ident, v in eq.items():
+                for d in range(self.dim):
+                    mi = list(ident.mi)
+                    mi[d] += 2
+                    new_ident = CoeffIdentifier(tuple(mi), ident.iexpr)
+                    new_eq[new_ident] += v
+            eqs.append(dict(new_eq))
+        return PDE(self.dim, eqs=eqs)
+
+    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 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)
+
+
 class LaplaceExpansionTermsWrangler(LinearRecurrenceBasedExpansionTermsWrangler):
 
     init_arg_names = ("order", "dim", "max_mi")
@@ -510,12 +600,8 @@ class LaplaceExpansionTermsWrangler(LinearRecurrenceBasedExpansionTermsWrangler)
             deriv_multiplier=1, max_mi=max_mi)
 
     def get_pdes(self):
-        pde_dict = {}
-        for d in range(self.dim):
-            mi = [0]*self.dim
-            mi[d] = 2
-            pde_dict[CoeffIdentifier(tuple(mi), 0)] = 1
-        return [pde_dict], 0, 1
+        w = PDE(dim=self.dim, nexprs=1)
+        return w.laplacian(), 0, 1
 
     def _get_reduced_coeffs(self, nullspace):
         idx = []
@@ -539,13 +625,8 @@ class HelmholtzExpansionTermsWrangler(LinearRecurrenceBasedExpansionTermsWrangle
             deriv_multiplier=multiplier, max_mi=max_mi)
 
     def get_pdes(self, **kwargs):
-        pde_dict = {}
-        for d in range(self.dim):
-            mi = [0]*self.dim
-            mi[d] = 2
-            pde_dict[CoeffIdentifier(tuple(mi), 0)] = 1
-        pde_dict[CoeffIdentifier(tuple([0]*self.dim), 0)] = 1
-        return [pde_dict], 0, 1
+        w = PDE(dim=self.dim, nexprs=1)
+        return (w.laplacian() + w), 0, 1
 
     def _get_reduced_coeffs(self, nullspace):
         idx = []
@@ -569,32 +650,10 @@ class StokesExpansionTermsWrangler(LinearRecurrenceBasedExpansionTermsWrangler):
             deriv_multiplier=multiplier, max_mi=max_mi)
 
     def get_pdes(self, **kwargs):
-        pdes = []
-        # velocity
-        for eq in range(self.dim):
-            pde_dict = {}
-            for d in range(self.dim):
-                mi = [0]*self.dim
-                mi[d] = 2
-                pde_dict[CoeffIdentifier(tuple(mi), eq)] = 1
-            mi = [0]*self.dim
-            mi[eq] = 1
-            pde_dict[CoeffIdentifier(tuple(mi), self.dim)] = -1
-            pdes.append(pde_dict)
-        # pressure
-        pde_dict = {}
-        for d in range(self.dim):
-            mi = [0]*self.dim
-            mi[d] = 2
-            pde_dict[CoeffIdentifier(tuple(mi), self.dim)] = 1
-        pdes.append(pde_dict)
-        # divergence
-        pde_dict = {}
-        for d in range(self.dim):
-            mi = [0]*self.dim
-            mi[d] = 1
-            pde_dict[CoeffIdentifier(tuple(mi), d)] = 1
-        pdes.append(pde_dict)
+        w = PDE(dim=self.dim, nexprs=self.dim+1)
+        u = w[:self.dim]
+        p = w[-1]
+        pdes = (u.laplacian() - p.grad() | u.div())
         return pdes, self.icomp, self.dim+1
 # }}}
 
-- 
GitLab