From 91c22452f6043640134b7e5a032b3b7d33ed3d8d Mon Sep 17 00:00:00 2001
From: Andreas Kloeckner <inform@tiker.net>
Date: Mon, 19 Oct 2015 00:25:01 -0500
Subject: [PATCH] Add, test geometric primitives

---
 grudge/symbolic/primitives.py | 89 +++++++++++++++++++++++++++++++----
 test/test_grudge.py           | 47 +++++++++++++++++-
 2 files changed, 124 insertions(+), 12 deletions(-)

diff --git a/grudge/symbolic/primitives.py b/grudge/symbolic/primitives.py
index e5c8c2e..5a17953 100644
--- a/grudge/symbolic/primitives.py
+++ b/grudge/symbolic/primitives.py
@@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 """
 
-from six.moves import range
+from six.moves import range, intern
 
 import numpy as np
 import pymbolic.primitives
@@ -73,6 +73,9 @@ Geometry data
 .. autofunction:: mv_nodes
 .. autofunction:: forward_metric_derivative
 .. autofunction:: inverse_metric_derivative
+.. autofunction:: forward_metric_derivative_mat
+.. autofunction:: inverse_metric_derivative_mat
+.. autofunction:: pseudoscalar
 .. autofunction:: area_element
 .. autofunction:: mv_normal
 .. autofunction:: normal
@@ -286,30 +289,96 @@ def forward_metric_derivative(xyz_axis, rst_axis, where=None,
             scope=cse_scope.DISCRETIZATION)
 
 
+def forward_metric_derivative_vector(ambient_dim, rst_axis, where=None,
+        quadrature_tag=None):
+    return make_obj_array([
+        forward_metric_derivative(
+            i, rst_axis, where=where, quadrature_tag=quadrature_tag)
+        for i in range(ambient_dim)])
+
+
+def forward_metric_derivative_mv(ambient_dim, rst_axis, where=None,
+        quadrature_tag=None):
+    return MultiVector(
+        forward_metric_derivative_vector(
+            ambient_dim, rst_axis, where=where, quadrature_tag=quadrature_tag))
+
+
 def parametrization_derivative(ambient_dim, dim=None, where=None,
         quadrature_tag=None):
     if dim is None:
         dim = ambient_dim
 
-    par_grad = np.zeros((ambient_dim, dim), np.object)
-    for i in range(ambient_dim):
-        for j in range(dim):
-            par_grad[i, j] = forward_metric_derivative(
-                    i, j, where=where, quadrature_tag=quadrature_tag)
-
     from pytools import product
-    return product(MultiVector(vec) for vec in par_grad.T)
+    return product(
+        forward_metric_derivative_mv(
+            ambient_dim, rst_axis, where, quadrature_tag)
+        for rst_axis in range(dim))
 
 
 def inverse_metric_derivative(rst_axis, xyz_axis, ambient_dim, dim=None,
-        quadrature_tag=None):
+        where=None, quadrature_tag=None):
     if dim is None:
         dim = ambient_dim
 
     if dim != ambient_dim:
         raise ValueError("not clear what inverse_metric_derivative means if "
                 "the derivative matrix is not square")
-    raise NotImplementedError()
+
+    par_vecs = [
+        forward_metric_derivative_mv(
+            ambient_dim, rst, where, quadrature_tag)
+        for rst in range(dim)]
+
+    # Yay Cramer's rule! (o rly?)
+    from functools import reduce, partial
+    from operator import xor as outerprod_op
+    outerprod = partial(reduce, outerprod_op)
+
+    def outprod_with_unit(i, at):
+        unit_vec = np.zeros(dim)
+        unit_vec[i] = 1
+
+        vecs = par_vecs[:]
+        vecs[at] = MultiVector(unit_vec)
+
+        return outerprod(vecs)
+
+    volume_pseudoscalar_inv = cse(outerprod(
+        forward_metric_derivative_mv(
+            ambient_dim, rst_axis, where, quadrature_tag)
+        for rst_axis in range(dim)).inv())
+
+    return (outprod_with_unit(xyz_axis, rst_axis)
+            * volume_pseudoscalar_inv
+            ).as_scalar()
+
+
+def forward_metric_derivative_mat(ambient_dim, dim=None,
+        where=None, quadrature_tag=None):
+    if dim is None:
+        dim = ambient_dim
+
+    result = np.zeros((ambient_dim, dim), dtype=np.object)
+    for j in range(dim):
+        result[:, j] = forward_metric_derivative_vector(ambient_dim, j,
+                where=where, quadrature_tag=quadrature_tag)
+    return result
+
+
+def inverse_metric_derivative_mat(ambient_dim, dim=None,
+        where=None, quadrature_tag=None):
+    if dim is None:
+        dim = ambient_dim
+
+    result = np.zeros((ambient_dim, dim), dtype=np.object)
+    for i in range(dim):
+        for j in range(ambient_dim):
+            result[i, j] = inverse_metric_derivative(
+                    i, j, ambient_dim, dim,
+                    where=where, quadrature_tag=quadrature_tag)
+
+    return result
 
 
 def pseudoscalar(ambient_dim, dim=None, where=None, quadrature_tag=None):
diff --git a/test/test_grudge.py b/test/test_grudge.py
index 060766a..83f7c78 100644
--- a/test/test_grudge.py
+++ b/test/test_grudge.py
@@ -37,9 +37,52 @@ from pyopencl.tools import (  # noqa
 import logging
 logger = logging.getLogger(__name__)
 
+from grudge import sym, bind, Discretization
 
-def test_nothing(ctx_getter):
-    pass
+
+@pytest.mark.parametrize("dims", [2, 3])
+def test_inverse_metric(ctx_getter, dims):
+    cl_ctx = cl.create_some_context()
+    queue = cl.CommandQueue(cl_ctx)
+
+    from meshmode.mesh.generation import generate_regular_rect_mesh
+    mesh = generate_regular_rect_mesh(a=(-0.5,)*dims, b=(0.5,)*dims,
+            n=(6,)*dims, order=4)
+
+    def m(x):
+        result = np.empty_like(x)
+        result[0] = (
+                1.5*x[0] + np.cos(x[0])
+                + 0.1*np.sin(10*x[1]))
+        result[1] = (
+                0.05*np.cos(10*x[0])
+                + 1.3*x[1] + np.sin(x[1]))
+        if len(x) == 3:
+            result[2] = x[2]
+        return result
+
+    from meshmode.mesh.processing import map_mesh
+    mesh = map_mesh(mesh, m)
+
+    discr = Discretization(cl_ctx, mesh, order=4)
+
+    sym_op = (
+            sym.forward_metric_derivative_mat(mesh.dim)
+            .dot(
+                sym.inverse_metric_derivative_mat(mesh.dim)
+                )
+            .reshape(-1))
+
+    op = bind(discr, sym_op)
+    mat = op(queue).reshape(mesh.dim, mesh.dim)
+
+    for i in range(mesh.dim):
+        for j in range(mesh.dim):
+            tgt = 1 if i == j else 0
+
+            err = np.max(np.abs((mat[i, j] - tgt).get(queue=queue)))
+            print(i, j, err)
+            assert err < 1e-12, (i, j, err)
 
 
 # You can test individual routines by typing
-- 
GitLab