From 7ccdba2729038aeb59739dad1f99df9c56e5645a Mon Sep 17 00:00:00 2001
From: Alexandru Fikl <alexfikl@gmail.com>
Date: Thu, 23 Apr 2020 21:16:58 -0500
Subject: [PATCH] another attempt at making 1D work nicely

The problem is that in 1D faces are 0D and so carry exactly no
useful information about which side of the element they may be
on.

To hack around that, this commit introduces a hacky operator
that hacks it. Less ambiguously, we just use the connection to
figure out which face we're on on set the normal to `+1` or `-1`
based on that
---
 examples/advection/weak.py           |  2 --
 examples/dagrt-fusion.py             |  2 +-
 grudge/execution.py                  | 15 +++++++++
 grudge/symbolic/dofdesc_inference.py |  1 +
 grudge/symbolic/mappers/__init__.py  |  3 ++
 grudge/symbolic/operators.py         |  7 -----
 grudge/symbolic/primitives.py        | 46 +++++++++++++++++++++-------
 7 files changed, 55 insertions(+), 21 deletions(-)

diff --git a/examples/advection/weak.py b/examples/advection/weak.py
index 85545303..6e30094d 100644
--- a/examples/advection/weak.py
+++ b/examples/advection/weak.py
@@ -67,9 +67,7 @@ def main(ctx_factory, dim=2, order=4, visualize=True):
 
     from grudge import DGDiscretizationWithBoundaries
     discr = DGDiscretizationWithBoundaries(cl_ctx, mesh, order=order)
-
     volume_discr = discr.discr_from_dd(sym.DD_VOLUME)
-    faces_discr = discr.discr_from_dd(sym.FACE_RESTR_INTERIOR)
 
     # }}}
 
diff --git a/examples/dagrt-fusion.py b/examples/dagrt-fusion.py
index 27e35abd..bfb6f761 100755
--- a/examples/dagrt-fusion.py
+++ b/examples/dagrt-fusion.py
@@ -159,7 +159,7 @@ def transcribe_phase(dag, field_var_name, field_components, phase_name,
             "<dt>": sym.var("input_dt", sym.DD_SCALAR),
             f"<state>{field_var_name}": sym.make_sym_array(
                 f"input_{field_var_name}", field_components),
-            f"<p>residual": sym.make_sym_array(
+            "<p>residual": sym.make_sym_array(
                 "input_residual", field_components),
     }
 
diff --git a/grudge/execution.py b/grudge/execution.py
index 948be131..fade7fca 100644
--- a/grudge/execution.py
+++ b/grudge/execution.py
@@ -283,6 +283,21 @@ class ExecutionMapper(mappers.Evaluator,
 
     # }}}
 
+    def map_signed_face_ones(self, expr):
+        face_discr = self.discrwb.discr_from_dd(expr.dd)
+        assert face_discr.dim == 0
+
+        all_faces_conn = self.discrwb.connection_from_dds("vol", expr.dd)
+        field = face_discr.empty(self.queue, allocator=self.bound_op.allocator)
+        field.fill(1)
+
+        for grp in all_faces_conn.groups:
+            for batch in grp.batches:
+                i = batch.to_element_indices.with_queue(self.queue)
+                field[i] = (2.0 * (batch.to_element_face % 2) - 1.0) * field[i]
+
+        return field
+
     # }}}
 
     # {{{ instruction execution functions
diff --git a/grudge/symbolic/dofdesc_inference.py b/grudge/symbolic/dofdesc_inference.py
index c44a7940..38c77512 100644
--- a/grudge/symbolic/dofdesc_inference.py
+++ b/grudge/symbolic/dofdesc_inference.py
@@ -183,6 +183,7 @@ class DOFDescInferenceMapper(RecursiveMapper, CSECachingMapperMixin):
         return expr.dd
 
     map_node_coordinate_component = map_ones
+    map_signed_face_ones = map_ones
 
     def map_call(self, expr):
         arg_dds = [
diff --git a/grudge/symbolic/mappers/__init__.py b/grudge/symbolic/mappers/__init__.py
index 0e9b8e02..b277c4c9 100644
--- a/grudge/symbolic/mappers/__init__.py
+++ b/grudge/symbolic/mappers/__init__.py
@@ -213,6 +213,7 @@ class IdentityMapperMixin(LocalOpReducerMixin, FluxOpReducerMixin):
 
     map_function_symbol = map_grudge_variable
     map_ones = map_grudge_variable
+    map_signed_face_ones = map_grudge_variable
     map_node_coordinate_component = map_grudge_variable
 
     # }}}
@@ -266,6 +267,7 @@ class DependencyMapper(
         return set()
 
     map_ones = _map_leaf
+    map_signed_face_ones = _map_leaf
     map_node_coordinate_component = _map_leaf
 
 
@@ -1235,6 +1237,7 @@ class CollectorMixin(OperatorReducerMixin, LocalOpReducerMixin, FluxOpReducerMix
     map_function_symbol = map_constant
 
     map_ones = map_grudge_variable
+    map_signed_face_ones = map_grudge_variable
     map_node_coordinate_component = map_grudge_variable
 
     map_operator = map_grudge_variable
diff --git a/grudge/symbolic/operators.py b/grudge/symbolic/operators.py
index 0cbb6eda..f1997b1c 100644
--- a/grudge/symbolic/operators.py
+++ b/grudge/symbolic/operators.py
@@ -536,13 +536,6 @@ class RefFaceMassOperator(ElementwiseLinearOperator):
                     volgrp.order,
                     face_vertices)
 
-        if afgrp.dim == 0 and volgrp.dim == 1:
-            # NOTE: This complements a choice in `parametrization_derivative`
-            # where we don't really get a sign for the 0-dim case, so we add
-            # signs here in the hope that it'll only be used for fluxes where
-            # this makes sense
-            matrix[0, 0, 0] = -matrix[0, 0, 0]
-
         # np.set_printoptions(linewidth=200, precision=3)
         # matrix[np.abs(matrix) < 1e-13] = 0
         # print(matrix)
diff --git a/grudge/symbolic/primitives.py b/grudge/symbolic/primitives.py
index 80dd8413..875a7bff 100644
--- a/grudge/symbolic/primitives.py
+++ b/grudge/symbolic/primitives.py
@@ -426,6 +426,20 @@ class Ones(HasDOFDesc, ExpressionBase):
     mapper_method = intern("map_ones")
 
 
+class _SignedFaceOnes(HasDOFDesc, ExpressionBase):
+    """Sets the face to :math:`-1` if its odd and :math:`+1` if its even.
+    This is based on the face order of
+    :meth:`meshmode.mesh.MeshElementGroup.face_vertex_indices`.
+    """
+
+    def __init__(self, dd):
+        dd = as_dofdesc(dd)
+        assert dd.is_trace()
+        super(_SignedFaceOnes, self).__init__(dd)
+
+    mapper_method = intern("map_signed_face_ones")
+
+
 # {{{ geometry data
 
 class DiscretizationProperty(HasDOFDesc, ExpressionBase):
@@ -507,8 +521,7 @@ def parametrization_derivative(ambient_dim, dim=None, dd=None):
         dim = ambient_dim
 
     if dim == 0:
-        inner_dd = (DD_VOLUME if dd is None else dd).with_qtag(QTAG_NONE)
-        return MultiVector(np.array([Ones(dd=inner_dd)]))
+        return MultiVector(np.array([_SignedFaceOnes(dd)]))
 
     from pytools import product
     return product(
@@ -607,18 +620,29 @@ def mv_normal(dd, ambient_dim, dim=None):
     if dim is None:
         dim = ambient_dim - 1
 
-    # Don't be tempted to add a sign here. As it is, it produces
+    # NOTE: Don't be tempted to add a sign here. As it is, it produces
     # exterior normals for positively oriented curves.
 
-    pder = (
-            pseudoscalar(ambient_dim, dim, dd=dd)
-            /  # noqa: W504
-            area_element(ambient_dim, dim, dd=dd))
+    pder = pseudoscalar(ambient_dim, dim, dd=dd) \
+            / area_element(ambient_dim, dim, dd=dd)
 
-    return cse(
-            # Dorst Section 3.7.2
-            pder if dim == 0 else pder << pder.I.inv(),
-            "normal", cse_scope.DISCRETIZATION)
+    # Dorst Section 3.7.2
+    mv = pder << pder.I.inv()
+
+    if dim == 0:
+        # NOTE: when the mesh is 0D, we do not have a clear notion of
+        # `exterior normal`, so we just take the tangent of the 1D element,
+        # interpolate it to the element faces and make it signed
+        tangent = parametrization_derivative(
+                ambient_dim, dim=1, dd=DD_VOLUME)
+        tangent = tangent / sqrt(tangent.norm_squared())
+
+        interp = _sym().interp(DD_VOLUME, dd)
+        mv = MultiVector(np.array([
+            mv.as_scalar() * interp(t) for t in tangent.as_vector()
+            ]))
+
+    return cse(mv, "normal", cse_scope.DISCRETIZATION)
 
 
 def normal(dd, ambient_dim, dim=None, quadrature_tag=None):
-- 
GitLab