From d67546125459dc7a10561610d58bbfe2651f4052 Mon Sep 17 00:00:00 2001
From: Ben Sepanski <ben_sepanski@baylor.edu>
Date: Thu, 28 May 2020 12:26:43 -0500
Subject: [PATCH] Added an importer for FInAT Lagrange elements

---
 meshmode/interop/FInAT/__init__.py         |  23 +++
 meshmode/interop/FInAT/lagrange_element.py | 196 +++++++++++++++++++++
 2 files changed, 219 insertions(+)
 create mode 100644 meshmode/interop/FInAT/__init__.py
 create mode 100644 meshmode/interop/FInAT/lagrange_element.py

diff --git a/meshmode/interop/FInAT/__init__.py b/meshmode/interop/FInAT/__init__.py
new file mode 100644
index 00000000..f53a2135
--- /dev/null
+++ b/meshmode/interop/FInAT/__init__.py
@@ -0,0 +1,23 @@
+__copyright__ = "Copyright (C) 2014 Andreas Kloeckner"
+
+__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.
+"""
+
+__all__ = ['FinatLagrangeElementImporter']
diff --git a/meshmode/interop/FInAT/lagrange_element.py b/meshmode/interop/FInAT/lagrange_element.py
new file mode 100644
index 00000000..c74b2e53
--- /dev/null
+++ b/meshmode/interop/FInAT/lagrange_element.py
@@ -0,0 +1,196 @@
+import numpy as np
+import numpy.linalg as la
+import six
+
+from meshmode.interop import ExternalImporter
+from meshmode.interop.fiat import FIATSimplexCellImporter
+
+
+__doc__ = """
+.. autoclass:: FinatLagrangeElementImporter
+    :members:
+"""
+
+
+class FinatLagrangeElementImporter(ExternalImporter):
+    """
+    An importer for a FInAT element, usually instantiated from
+    ``some_instantiated_firedrake_function_space.finat_element``
+
+    This importer does not have an obvious meshmode counterpart,
+    so is instead used for its method.
+    In particular, methods :meth:`import_data`
+    and :meth:`validate_to_data` are *NOT* implemented
+    and there is no :attr:`to_data` to be computed.
+    """
+    def __init__(self, finat_element):
+        """
+        :param finat_element: A FInAT element of type
+            :class:`finat.fiat_elements.Lagrange` or
+            :class:`finat.fiat_elements.DiscontinuousLagrange`
+            which uses affine mapping of the basis functions
+            (i.e. ``finat_element.mapping`` must be
+            ``"affine"``)
+
+        :raises TypeError: If :param:`finat_element` is not of type
+            :class:`finat.fiat_elements.Lagrange` or
+            :class:`finat.fiat_elements.DiscontinuousLagrange`
+
+        :raises ValueError: If :param:`finat_element` does not
+            use affine mappings of the basis functions
+        """
+        # {{{ Check and store input
+
+        # Check types
+        from finat.fiat_elements import DiscontinuousLagrange, Lagrange
+        if not isinstance(finat_element, (Lagrange, DiscontinuousLagrange)):
+            raise TypeError(":param:`finat_element` must be of type"
+                            " `finat.fiat_elements.Lagrange` or"
+                            " `finat.fiat_elements.DiscontinuousLagrange`",
+                            " not type `%s`" % type(finat_element))
+
+        if finat_element.mapping != 'affine':
+            raise ValueError("FInAT element must use affine mappings"
+                             " of the bases")
+        # }}}
+
+        super(FinatLagrangeElementImporter, self).__init__(finat_element)
+
+        self.cell_importer = FIATSimplexCellImporter(finat_element.cell)
+
+        # computed and stored once :meth:`unit_nodes`, :meth:`unit_vertices`,
+        # and :meth:`flip_matrix` are called
+        self._unit_nodes = None
+        self._unit_vertex_indices = None
+        self._flip_matrix = None
+
+    def _compute_unit_vertex_indices_and_nodes(self):
+        """
+        Compute the unit nodes, as well as the unit vertex indices,
+        if they have not already been computed.
+        """
+        if self._unit_nodes is None or self._unit_vertex_indices is None:
+            # {{{ Compute unit nodes
+            node_nr_to_coords = {}
+            unit_vertex_indices = []
+
+            # Get unit nodes
+            for dim, element_nrs in six.iteritems(
+                    self.analog().entity_support_dofs()):
+                for element_nr, node_list in six.iteritems(element_nrs):
+                    # Get the nodes on the element (in meshmode reference coords)
+                    pts_on_element = self.cell_importer.make_points(
+                        dim, element_nr, self.analog().degree)
+                    # Record any new nodes
+                    i = 0
+                    for node_nr in node_list:
+                        if node_nr not in node_nr_to_coords:
+                            node_nr_to_coords[node_nr] = pts_on_element[i]
+                            i += 1
+                            # If is a vertex, store the index
+                            if dim == 0:
+                                unit_vertex_indices.append(node_nr)
+
+            # store vertex indices
+            self._unit_vertex_indices = np.array(sorted(unit_vertex_indices))
+
+            # Convert unit_nodes to array, then change to (dim, nunit_nodes)
+            # from (nunit_nodes, dim)
+            unit_nodes = np.array([node_nr_to_coords[i] for i in
+                                   range(len(node_nr_to_coords))])
+            self._unit_nodes = unit_nodes.T.copy()
+
+            # }}}
+
+    def dim(self):
+        """
+        :return: The dimension of the FInAT element's cell
+        """
+        return self.cell_importer.from_data.get_dimension()
+
+    def unit_vertex_indices(self):
+        """
+        :return: An array of shape *(dim+1,)* of indices
+                 so that *self.unit_nodes()[self.unit_vertex_indices()]*
+                 are the vertices of the reference element.
+        """
+        self._compute_unit_vertex_indices_and_nodes()
+        return self._unit_vertex_indices
+
+    def unit_nodes(self):
+        """
+        :return: The unit nodes used by the FInAT element mapped
+                 onto the appropriate :mod:`modepy` `reference
+                 element <https://documen.tician.de/modepy/nodes.html>`_
+                 as an array of shape *(dim, nunit_nodes)*.
+        """
+        self._compute_unit_vertex_indices_and_nodes()
+        return self._unit_nodes
+
+    def nunit_nodes(self):
+        """
+        :return: The number of unit nodes.
+        """
+        return self.unit_nodes().shape[1]
+
+    def flip_matrix(self):
+        """
+        :return: The matrix which should be applied to the
+                 *(dim, nunitnodes)*-shaped array of nodes corresponding to
+                 an element in order to change orientation - <-> +.
+
+                 The matrix will be *(dim, dim)* and orthogonal with
+                 *np.float64* type entries.
+        """
+        if self._flip_matrix is None:
+            # This is very similar to :mod:`meshmode` in processing.py
+            # the function :function:`from_simplex_element_group`, but
+            # we needed to use firedrake nodes
+
+            from modepy.tools import barycentric_to_unit, unit_to_barycentric
+
+            # Generate a resampling matrix that corresponds to the
+            # first two barycentric coordinates being swapped.
+
+            bary_unit_nodes = unit_to_barycentric(self.unit_nodes())
+
+            flipped_bary_unit_nodes = bary_unit_nodes.copy()
+            flipped_bary_unit_nodes[0, :] = bary_unit_nodes[1, :]
+            flipped_bary_unit_nodes[1, :] = bary_unit_nodes[0, :]
+            flipped_unit_nodes = barycentric_to_unit(flipped_bary_unit_nodes)
+
+            from modepy import resampling_matrix, simplex_best_available_basis
+
+            flip_matrix = resampling_matrix(
+                simplex_best_available_basis(self.dim(), self.analog().degree),
+                flipped_unit_nodes, self.unit_nodes())
+
+            flip_matrix[np.abs(flip_matrix) < 1e-15] = 0
+
+            # Flipping twice should be the identity
+            assert la.norm(
+                np.dot(flip_matrix, flip_matrix)
+                - np.eye(len(flip_matrix))) < 1e-13
+
+            self._flip_matrix = flip_matrix
+
+        return self._flip_matrix
+
+    def make_resampling_matrix(self, element_grp):
+        """
+            :param element_grp: A
+                :class:`meshmode.discretization.InterpolatoryElementGroupBase` whose
+                basis functions span the same space as the FInAT element.
+            :return: A matrix which resamples a function sampled at
+                     the firedrake unit nodes to a function sampled at
+                     *element_grp.unit_nodes()* (by matrix multiplication)
+        """
+        from meshmode.discretization import InterpolatoryElementGroupBase
+        assert isinstance(element_grp, InterpolatoryElementGroupBase), \
+            "element group must be an interpolatory element group so that" \
+            " can redistribute onto its nodes"
+
+        from modepy import resampling_matrix
+        return resampling_matrix(element_grp.basis(),
+                                 new_nodes=element_grp.unit_nodes,
+                                 old_nodes=self.unit_nodes())
-- 
GitLab