From 11f3a2871af755f92481529ba5909de2f898ddd1 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Mon, 25 May 2020 11:12:46 -0500 Subject: [PATCH 001/221] Set up basic interface for object conversion --- doc/conf.py | 3 +- meshmode/interop/__init__.py | 57 ++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 meshmode/interop/__init__.py diff --git a/doc/conf.py b/doc/conf.py index 86665362..8ff4e0bb 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -280,5 +280,6 @@ intersphinx_mapping = { 'https://documen.tician.de/pyopencl': None, 'https://documen.tician.de/meshpy': None, 'https://documen.tician.de/modepy': None, - 'https://documen.tician.de/loopy': None + 'https://documen.tician.de/loopy': None, + 'https://firedrakeproject.org/' : None } diff --git a/meshmode/interop/__init__.py b/meshmode/interop/__init__.py new file mode 100644 index 00000000..e3ca48fc --- /dev/null +++ b/meshmode/interop/__init__.py @@ -0,0 +1,57 @@ +__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. +""" + +from abc import ABC + +__doc__ = """ +Development Interface +--------------------- +.. autoclass:: Interoperator +""" + + +class Interoperator(ABC): + """ + An object which handles the interoperation of an external + object with its closest meshmode representation + """ + + def __init__(self): + pass + + def as_meshmode(self): + pass + + def as_external(self): + pass + + def __hash__(self): + pass + + def __eq__(self, other): + pass + + def __neq__(self, other): + return not self.__eq__(other) + + +# vim: fdm=marker -- GitLab From bd5f322c94a687972b1eef02f81a6d69a477fc33 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Thu, 28 May 2020 11:27:53 -0500 Subject: [PATCH 002/221] Update interface to be unidirectional --- meshmode/interop/__init__.py | 82 ++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 13 deletions(-) diff --git a/meshmode/interop/__init__.py b/meshmode/interop/__init__.py index e3ca48fc..c08a36e1 100644 --- a/meshmode/interop/__init__.py +++ b/meshmode/interop/__init__.py @@ -25,33 +25,89 @@ from abc import ABC __doc__ = """ Development Interface --------------------- -.. autoclass:: Interoperator +.. autoclass:: ExternalTransporter +.. autoclass:: ExternalImporter +.. autoclass:: ExternalExporter """ +# {{{ Generic, most abstract class for transporting meshmode <-> external -class Interoperator(ABC): - """ - An object which handles the interoperation of an external - object with its closest meshmode representation +class ExternalTransporter(ABC): """ + .. attribute:: from_data + + The object which needs to be transported either to meshmode or + from meshmode - def __init__(self): - pass + .. attribute:: to_data - def as_meshmode(self): - pass + The "transported" object, i.e. the - def as_external(self): - pass + This attribute does not exist at instantiation time. + If exporting (resp. importing) from meshmode + then we are using an :class:`ExternalExporter` + (resp. :class:`ExternalImporter`) instance. :attr:`to_data` is + computed with a call to :fun:`ExternalExporter.export` + (resp. :fun:`ExternalImporter.import`). + + :raises ValueError: if :attr:`to_data` is accessed before creation. + :raises NotImplementedError: if :meth:`validate_to_data` is called + without an implementation. + """ + def __init__(self, from_data): + self.from_data = from_data + + def validate_to_data(self): + """ + Validate :attr:`to_data` + + :return: *True* if :attr:`to_data` has been computed and is valid + and *False* otherwise + """ + raise NotImplementedError("*validate_to_data* method not implemented " \ + "for object of type %s" % type(self)) def __hash__(self): - pass + return hash((type(self), self.from_data)) def __eq__(self, other): - pass + return isinstance(other, type(self)) and \ + isinstance(self, type(other)) and \ + self.from_data == other.from_data def __neq__(self, other): return not self.__eq__(other) + def __getattr__(self, attr): + if attr != 'to_data': + return super(ExternalTransporter, self).__getattr__(attr) + raise ValueError("Attribute *to_data* has not yet been computed. " \ + "An object of class *ExternalExporter* (resp. " \ + "*ExternalImporter*) must call *export()* " \ + "(resp. *import()*) to compute attribute *to_data*") + +# }}} + + +# {{{ Define specific classes for meshmode -> external and meshmode <- external + +class ExternalExporter(ExternalTransporter): + def export(self): + """ + Compute :attr:`to_data` from :attr:`from_data` + """ + raise NotImplementedError("*export* method not implemented " \ + "for type %s" % type(self)) + +class ExternalImporter(ExternalTransporter): + def import(self): + """ + Compute :attr:`to_data` from :attr:`from_data` + """ + raise NotImplementedError("*import* method not implemented " \ + "for type %s" % type(self)) + +# }}} + # vim: fdm=marker -- GitLab From 70657ca9e56c3fea77d3efd3b336797e8cd91ad2 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Thu, 28 May 2020 11:41:52 -0500 Subject: [PATCH 003/221] Added an importer for FIAT's simplex cell --- meshmode/interop/fiat/simplex_cell.py | 123 ++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 meshmode/interop/fiat/simplex_cell.py diff --git a/meshmode/interop/fiat/simplex_cell.py b/meshmode/interop/fiat/simplex_cell.py new file mode 100644 index 00000000..51c61a04 --- /dev/null +++ b/meshmode/interop/fiat/simplex_cell.py @@ -0,0 +1,123 @@ +from warnings import warn # noqa +import numpy as np +import numpy.linalg as la + +from meshmode.mesh.interop import ExternalImporter + + +__doc__ = """ +Interoperators +-------------- +.. autoclass:: SimplexCellAnalog + :members: +""" + +# {{{ Compute an affine mapping from given input/outputs + +def get_affine_mapping(reference_vects, vects): + r""" + Returns (mat, shift), + a matrix *mat* and vector *shift* which maps the + *i*th vector in *reference_vects* to the *i*th vector in *vects by + + ..math:: + + A ri + b -> vi, \qquad A = mat, b = shift + + :arg reference_vects: An np.array of *n* vectors of dimension *ref_dim* + :arg vects: An np.array of *n* vectors of dimension *dim*, with + *ref_dim* <= *dim*. + + NOTE : Should be shape *(ref_dim, nvectors)*, *(dim, nvectors)* respectively. + + *mat* will have shape *(dim, ref_dim)*, *shift* will have shape *(dim,)* + """ + # Make sure both have same number of vectors + ref_dim, num_vects = reference_vects.shape + assert num_vects == vects.shape[1] + + # Make sure d1 <= d2 (see docstring) + dim = vects.shape[0] + assert ref_dim <= dim + + # If there is only one vector, set M = I, b = vect - reference + if num_vects == 1: + mat = np.eye(dim, ref_dim) + shift = vects[:, 0] - np.matmul(mat, reference_vects[:, 0]) + else: + ref_span_vects = reference_vects[:, 1:] - reference_vects[:, 0, np.newaxis] + span_vects = vects[:, 1:] - vects[:, 0, np.newaxis] + mat = la.solve(ref_span_vects, span_vects) + shift = -np.matmul(mat, reference_vects[:, 0]) + vects[:, 0] + + return mat, shift + +# }}} + + +# {{{ Interoperator for FIAT's reference_element.Simplex + +class FIATSimplexCellImporter(ExternalImporter): + """ + Importer for a :mod:`FIAT` simplex cell. + There is no data created from the :attr:`from_data`. Instead, + the data is simply used to obtain FIAT's reference + nodes according to :mod:`modepy`'s reference coordinates + using :meth:`make_modepy_points`. + + .. attribute:: from_data + + An instance of :class:`fiat.FIAT.reference_element.Simplex`. + + .. attribute:: to_data + + :raises ValueError: If accessed. + """ + def __init__(self, cell): + """ + :arg cell: a :class:`fiat.FIAT.reference_element.Simplex`. + """ + # Ensure this cell is actually a simplex + from FIAT.reference_element import Simplex + assert isinstance(cell, Simplex) + + super(SimplexCellInteroperator, self).__init__(cell) + + # Stored as (dim, nunit_vertices) + from modepy.tools import unit_vertices + self._unit_vertices = unit_vertices(cell.get_dimension()).T + + # Maps FIAT reference vertices to :mod:`meshmode` + # unit vertices by x -> Ax + b, where A is :attr:`_mat` + # and b is :attr:`_shift` + reference_vertices = np.array(cell.vertices).T + self._mat, self._shift = get_affine_mapping(reference_vertices, + self._unit_vertices) + + def make_modepy_points(self, dim, entity_id, order): + """ + Constructs a lattice of points on the *entity_id*th facet + of dimension *dim*. + + Args are exactly as in + :meth:`fiat.FIAT.reference_element.Cell.make_points` + (see `FIAT docs `_), + but the unit nodes are (affinely) mapped to :mod:`modepy` + `unit coordinates `_. + + :arg dim: Dimension of the facet we are constructing points on. + :arg entity_id: identifier to determine which facet of + dimension *dim* to construct the points on. + :arg order: Number of points to include in each direction. + + :return: an *np.array* of shape *(dim, npoints)* holding the + coordinates of each of the ver + """ + points = self.from_data.make_points(dim, entity_id, order) + if not points: + return points + points = np.array(points) + # Points is (npoints, dim) so have to transpose + return (np.matmul(self._mat, points.T) + self._shift[:, np.newaxis]).T + +# }}} -- GitLab From 4ff6048f106e80514ecc9ffabc55258579e0a637 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Thu, 28 May 2020 11:42:44 -0500 Subject: [PATCH 004/221] Added an __init__.py file for FIAT directory --- meshmode/interop/fiat/__init__.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 meshmode/interop/fiat/__init__.py diff --git a/meshmode/interop/fiat/__init__.py b/meshmode/interop/fiat/__init__.py new file mode 100644 index 00000000..f8797739 --- /dev/null +++ b/meshmode/interop/fiat/__init__.py @@ -0,0 +1,24 @@ +from future import division + +__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. +""" + -- GitLab From 07d0f416ecf5dae25938fbd87affdd6147ecc143 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Thu, 28 May 2020 11:44:37 -0500 Subject: [PATCH 005/221] Fixed docstring --- meshmode/interop/fiat/simplex_cell.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/meshmode/interop/fiat/simplex_cell.py b/meshmode/interop/fiat/simplex_cell.py index 51c61a04..b582094a 100644 --- a/meshmode/interop/fiat/simplex_cell.py +++ b/meshmode/interop/fiat/simplex_cell.py @@ -6,10 +6,7 @@ from meshmode.mesh.interop import ExternalImporter __doc__ = """ -Interoperators --------------- -.. autoclass:: SimplexCellAnalog - :members: +.. autoclass:: FIATSimplexCellImporter """ # {{{ Compute an affine mapping from given input/outputs -- GitLab From 7491da5e2b930f06d7f005571ae9a1b3ca50f053 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Thu, 28 May 2020 11:58:19 -0500 Subject: [PATCH 006/221] Some flake8 fixes --- meshmode/interop/__init__.py | 36 ++++++++++++++++++++----------- meshmode/interop/fiat/__init__.py | 3 --- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/meshmode/interop/__init__.py b/meshmode/interop/__init__.py index c08a36e1..91ddfd76 100644 --- a/meshmode/interop/__init__.py +++ b/meshmode/interop/__init__.py @@ -30,6 +30,7 @@ Development Interface .. autoclass:: ExternalExporter """ + # {{{ Generic, most abstract class for transporting meshmode <-> external class ExternalTransporter(ABC): @@ -45,10 +46,10 @@ class ExternalTransporter(ABC): This attribute does not exist at instantiation time. If exporting (resp. importing) from meshmode - then we are using an :class:`ExternalExporter` - (resp. :class:`ExternalImporter`) instance. :attr:`to_data` is - computed with a call to :fun:`ExternalExporter.export` - (resp. :fun:`ExternalImporter.import`). + then we are using an :class:`ExternalExporter` + (resp. :class:`ExternalImporter`) instance. :attr:`to_data` is + computed with a call to :fun:`ExternalExporter.export_data` + (resp. :fun:`ExternalImporter.import_data`). :raises ValueError: if :attr:`to_data` is accessed before creation. :raises NotImplementedError: if :meth:`validate_to_data` is called @@ -64,7 +65,7 @@ class ExternalTransporter(ABC): :return: *True* if :attr:`to_data` has been computed and is valid and *False* otherwise """ - raise NotImplementedError("*validate_to_data* method not implemented " \ + raise NotImplementedError("*validate_to_data* method not implemented " "for object of type %s" % type(self)) def __hash__(self): @@ -81,10 +82,10 @@ class ExternalTransporter(ABC): def __getattr__(self, attr): if attr != 'to_data': return super(ExternalTransporter, self).__getattr__(attr) - raise ValueError("Attribute *to_data* has not yet been computed. " \ - "An object of class *ExternalExporter* (resp. " \ - "*ExternalImporter*) must call *export()* " \ - "(resp. *import()*) to compute attribute *to_data*") + raise ValueError("Attribute *to_data* has not yet been computed. " + "An object of class *ExternalExporter* (resp. " + "*ExternalImporter*) must call *export_data()* " + "(resp. *import_data()*) to compute attribute *to_data*") # }}} @@ -92,19 +93,28 @@ class ExternalTransporter(ABC): # {{{ Define specific classes for meshmode -> external and meshmode <- external class ExternalExporter(ExternalTransporter): - def export(self): + """ + Subclass of :class:`ExternalTransporter` for meshmode -> external + data transfer + """ + def export_data(self): """ Compute :attr:`to_data` from :attr:`from_data` """ - raise NotImplementedError("*export* method not implemented " \ + raise NotImplementedError("*export_data* method not implemented " "for type %s" % type(self)) + class ExternalImporter(ExternalTransporter): - def import(self): + """ + Subclass of :class:`ExternalTransporter` for external -> meshmode + data transfer + """ + def import_data(self): """ Compute :attr:`to_data` from :attr:`from_data` """ - raise NotImplementedError("*import* method not implemented " \ + raise NotImplementedError("*import_data* method not implemented " "for type %s" % type(self)) # }}} diff --git a/meshmode/interop/fiat/__init__.py b/meshmode/interop/fiat/__init__.py index f8797739..908c7cd2 100644 --- a/meshmode/interop/fiat/__init__.py +++ b/meshmode/interop/fiat/__init__.py @@ -1,5 +1,3 @@ -from future import division - __copyright__ = "Copyright (C) 2014 Andreas Kloeckner" __license__ = """ @@ -21,4 +19,3 @@ 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. """ - -- GitLab From fc9baf080fe6bc838c292e9427a6b4f56df72153 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Thu, 28 May 2020 12:26:22 -0500 Subject: [PATCH 007/221] Some more minor flake8 changes --- meshmode/interop/fiat/__init__.py | 4 ++++ meshmode/interop/fiat/simplex_cell.py | 22 ++++++++++++++-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/meshmode/interop/fiat/__init__.py b/meshmode/interop/fiat/__init__.py index 908c7cd2..3536a1ea 100644 --- a/meshmode/interop/fiat/__init__.py +++ b/meshmode/interop/fiat/__init__.py @@ -19,3 +19,7 @@ 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 meshmode.interop.fiat.simplex_cell import FIATSimplexCellImporter + +__all__ = ['FIATSimplexCellImporter'] diff --git a/meshmode/interop/fiat/simplex_cell.py b/meshmode/interop/fiat/simplex_cell.py index b582094a..f2b8366b 100644 --- a/meshmode/interop/fiat/simplex_cell.py +++ b/meshmode/interop/fiat/simplex_cell.py @@ -1,14 +1,14 @@ -from warnings import warn # noqa import numpy as np import numpy.linalg as la -from meshmode.mesh.interop import ExternalImporter +from meshmode.interop import ExternalImporter __doc__ = """ .. autoclass:: FIATSimplexCellImporter """ + # {{{ Compute an affine mapping from given input/outputs def get_affine_mapping(reference_vects, vects): @@ -57,17 +57,23 @@ def get_affine_mapping(reference_vects, vects): class FIATSimplexCellImporter(ExternalImporter): """ Importer for a :mod:`FIAT` simplex cell. - There is no data created from the :attr:`from_data`. Instead, - the data is simply used to obtain FIAT's reference + + There is no obvious meshmode counterpart for :attr:`from_data`. + The data is simply used to obtain FIAT's reference nodes according to :mod:`modepy`'s reference coordinates - using :meth:`make_modepy_points`. + using :meth:`make_points`. + + In particular, methods + :meth:`import_data` and :meth:`validate_to_data` + are *NOT* implemented + and there is no :attr:`to_data` to be computed. .. attribute:: from_data An instance of :class:`fiat.FIAT.reference_element.Simplex`. .. attribute:: to_data - + :raises ValueError: If accessed. """ def __init__(self, cell): @@ -78,7 +84,7 @@ class FIATSimplexCellImporter(ExternalImporter): from FIAT.reference_element import Simplex assert isinstance(cell, Simplex) - super(SimplexCellInteroperator, self).__init__(cell) + super(FIATSimplexCellImporter, self).__init__(cell) # Stored as (dim, nunit_vertices) from modepy.tools import unit_vertices @@ -91,7 +97,7 @@ class FIATSimplexCellImporter(ExternalImporter): self._mat, self._shift = get_affine_mapping(reference_vertices, self._unit_vertices) - def make_modepy_points(self, dim, entity_id, order): + def make_points(self, dim, entity_id, order): """ Constructs a lattice of points on the *entity_id*th facet of dimension *dim*. -- GitLab From d67546125459dc7a10561610d58bbfe2651f4052 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Thu, 28 May 2020 12:26:43 -0500 Subject: [PATCH 008/221] 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 `_ + 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 From ec0715190f029d07e9c1f88d55377c94720a0ed1 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Thu, 28 May 2020 15:28:21 -0500 Subject: [PATCH 009/221] Added a mesh topology import handler --- meshmode/interop/firedrake/mesh_topology.py | 200 ++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 meshmode/interop/firedrake/mesh_topology.py diff --git a/meshmode/interop/firedrake/mesh_topology.py b/meshmode/interop/firedrake/mesh_topology.py new file mode 100644 index 00000000..d4ec055e --- /dev/null +++ b/meshmode/interop/firedrake/mesh_topology.py @@ -0,0 +1,200 @@ +from warnings import warn # noqa +import numpy as np + +from meshmode.interop import ExternalImportHandler + + +# {{{ ImportHandler for firedrake's MeshTopology class + +class FiredrakeMeshTopologyImporter(ExternalImportHandler): + """ + An Importer for :class:`firedrake.mesh.MeshTopology`. + Holds the topological (as opposed to geometric) information + about a mesh. + """ + + def __init__(self, mesh, cells_to_use=None): + """ + :param mesh: An instance :mod:`firedrake` :class:`MeshTopology` or + :class:`MeshGeometry`. If an instance of + :class:`MeshGeometry`, uses its underlying topology. + :param cells_to_use: Either + + * *None*, in which case this argument is ignored + * An array of cell ids, in which case those are the + only cells for which information is gathered/converted + + We require that :param:`mesh` have co-dimesnion + of 0 or 1. + Moreover, if :param:`mesh` is a 2-surface embedded in 3-space, + we _require_ that :function:`init_cell_orientations` + has been called already. + + :raises TypeError: If :param:`mesh` is not of :mod:`firedrake` + :class:`MeshTopology` or :class:`MeshGeometry` + """ + top = mesh.topological # convert geometric to topological + + # {{{ Check input types + from firedrake.mesh import MeshTopology + if not isinstance(top, MeshTopology): + raise TypeError(":param:`mesh` must be of type " + ":class:`firedrake.mesh.MeshTopology` or " + ":class:`firedrake.mesh.MeshGeometry`") + # }}} + + super(FiredrakeMeshTopologyImporter, self).__init__(top) + + # Ensure has simplex-type elements + if not top.ufl_cell().is_simplex(): + raise ValueError(":param:`mesh` must have simplex type elements, " + "%s is not a simplex" % (mesh.ufl_cell())) + + # Ensure dimensions are in appropriate ranges + supported_dims = [1, 2, 3] + if self.cell_dimension() not in supported_dims: + raise ValueError("Cell dimension is %s. Cell dimension must be one of" + " %s" % (self.cell_dimension(), supported_dims)) + + self._nodal_adjacency = None + self.icell_to_fd = cells_to_use # Map cell index -> fd cell index + self.fd_to_icell = None # Map fd cell index -> cell index + + # perform checks on :param:`cells_to_use` if not *None* + if self.icell_to_fd is not None: + assert np.unique(self.icell_to_fd).shape == self.icell_to_fd.shape + self.fd_to_icell = dict(zip(self.icell_to_fd, + np.arange(self.icell_to_fd.shape[0], + dtype=np.int32) + )) + + @property + def topology_importer(self): + """ + A reference to *self*, for compatability with mesh geometry + importers + """ + return self + + @property + def topological_importer(self): + """ + A reference to *self*, for compatability with mesh geometry + importers + """ + return self + + def cell_dimension(self): + """ + Return the dimension of the cells used by this topology + """ + return self.data.cell_dimension() + + def nelements(self): + """ + Return the number of cells in this topology + """ + if self.icell_to_fd is None: + num_cells = self.data.num_cells() + else: + num_cells = self.icell_to_fd.shape[0] + + return num_cells + + def nunit_vertices(self): + """ + Return the number of unit vertices on the reference element + """ + return self.data.ufl_cell().num_vertices() + + def bdy_tags(self): + """ + Return a tuple of bdy tags as requested in + the construction of a :mod:`meshmode` :class:`Mesh` + + The tags used are :class:`meshmode.mesh.BTAG_ALL`, + :class:`meshmode.mesh.BTAG_REALLY_ALL`, and + any markers in the mesh topology's exterior facets + (see :attr:`firedrake.mesh.MeshTopology.exterior_facets.unique_markers`) + """ + from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL + bdy_tags = [BTAG_ALL, BTAG_REALLY_ALL] + + unique_markers = self.data.exterior_facets.unique_markers + if unique_markers is not None: + bdy_tags += list(unique_markers) + + return tuple(bdy_tags) + + def nodal_adjacency(self): + """ + Returns a :class:`meshmode.mesh.NodalAdjacency` object + representing the nodal adjacency of this mesh + """ + if self._nodal_adjacency is None: + # TODO... not sure how to get around the private access + plex = self.data._plex + + # variable names follow a dmplex naming convention for + # cell Start/end and vertex Start/end. + cStart, cEnd = plex.getHeightStratum(0) + vStart, vEnd = plex.getDepthStratum(0) + + # TODO... not sure how to get around the private access + to_fd_id = np.vectorize(self.data._cell_numbering.getOffset)( + np.arange(cStart, cEnd, dtype=np.int32)) + + element_to_neighbors = {} + verts_checked = set() # dmplex ids of vertex checked + + # If using all cells, loop over them all + if self.icell_to_fd is None: + range_ = range(cStart, cEnd) + # Otherwise, just the ones you're using + else: + isin = np.isin(to_fd_id, self.icell_to_fd) + range_ = np.arange(cStart, cEnd, dtype=np.int32)[isin] + + # For each cell + for cell_id in range_: + # For each vertex touching the cell (that haven't already seen) + for vert_id in plex.getTransitiveClosure(cell_id)[0]: + if vStart <= vert_id < vEnd and vert_id not in verts_checked: + verts_checked.add(vert_id) + cells = [] + # Record all cells touching that vertex + support = plex.getTransitiveClosure(vert_id, + useCone=False)[0] + for other_cell_id in support: + if cStart <= other_cell_id < cEnd: + cells.append(to_fd_id[other_cell_id - cStart]) + + # If only using some cells, clean out extraneous ones + # and relabel them to new id + cells = set(cells) + if self.fd_to_icell is not None: + cells = set([self.fd_to_icell[fd_ndx] + for fd_ndx in cells + if fd_ndx in self.fd_to_icell]) + + # mark cells as neighbors + for cell_one in cells: + element_to_neighbors.setdefault(cell_one, set()) + element_to_neighbors[cell_one] |= cells + + # Create neighbors_starts and neighbors + neighbors = [] + neighbors_starts = np.zeros(self.nelements() + 1, dtype=np.int32) + for iel in range(len(element_to_neighbors)): + elt_neighbors = element_to_neighbors[iel] + neighbors += list(elt_neighbors) + neighbors_starts[iel+1] = len(neighbors) + + neighbors = np.array(neighbors, dtype=np.int32) + + from meshmode.mesh import NodalAdjacency + self._nodal_adjacency = NodalAdjacency(neighbors_starts=neighbors_starts, + neighbors=neighbors) + return self._nodal_adjacency + +# }}} -- GitLab From 4dd0d6adf1a139a28a3ec4dde156a69eef1968c7 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Mon, 1 Jun 2020 11:15:54 -0500 Subject: [PATCH 010/221] Added coordinateless functions implementation --- .../coordinateless_functions_impl.py | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 meshmode/interop/firedrake/coordinateless_functions_impl.py diff --git a/meshmode/interop/firedrake/coordinateless_functions_impl.py b/meshmode/interop/firedrake/coordinateless_functions_impl.py new file mode 100644 index 00000000..0a4e7bc4 --- /dev/null +++ b/meshmode/interop/firedrake/coordinateless_functions_impl.py @@ -0,0 +1,195 @@ +from warnings import warn # noqa + +from meshmode.interop import ExternalImportHandler +from meshmode.interop.firedrake.mesh_topology import \ + FiredrakeMeshTopologyImporter +from meshmode.interop.FInAT import FinatLagrangeElementImporter + + +# {{{ Function space for coordinateless functions to live on + + +class FiredrakeFunctionSpaceImporter(ExternalImportHandler): + """ + This is a counterpart of :class:`firedrake.functionspaceimpl.FunctionSpace`, + + This is not what usually results from a call to + :func:`firedrake.functionspace.FunctionSpace`. + When someone calls :func:`firedrake.functionspace.FunctionSpace` + from the user side, they are usually talking about a + :class:`firedrake.functionspaceimpl.WithGeometry`. + + Generally, this class is here to match Firedrake's design + principles, i.e. so that we have something to put CoordinatelessFunction + import handlers on. + + Just like we have "topological" and "geometric" + meshes, one can think of think of this as a "topological" function space + whose counterpart is a "WithGeometry" + + .. attribute:: data + + An instance of :class:`firedrake.functionspaceimpl.FunctionSpace`, i.e. + a function space built to hold coordinateless function (functions + whose domain is just a set of points with some topology, but no + coordinates) + + .. attribute:: finat_element_importer + + An instance of + :class:`meshmode.interop.FInAT.FinatLagrangeElementImporter` which + is an importer for the *finat_element* of :attr:`data`. + """ + def __init__(self, function_space, mesh_importer, finat_element_importer): + """ + :param function_space: A :mod:`firedrake` + :class:`firedrake.functionspaceimpl.FunctionSpace` or + :class:`firedrake.functionspaceimpl.WithGeometry`. In the + latter case, the underlying ``FunctionSpace`` is extracted + from the ``WithGeometry``. + :param mesh_importer: An instance + of :class:`FiredrakeMeshTopology` created from the topological + mesh of :param:`function_space` + :param finat_element_importer: An instance + of :class:`FinatLagrangeElementIMporter` created from the + finat_element of :param:`function_space` + + :raises TypeError: If any of the arguments are the wrong type + :raises ValueError: If :param:`mesh_importer` or + :param:`finat_element_importer` are importing + a different mesh or finat element than the provided + :param:`function_space` is built on. + """ + # {{{ Some type-checking + from firedrake.functionspaceimpl import FunctionSpace, WithGeometry + + if not isinstance(function_space, (FunctionSpace, WithGeometry)): + raise TypeError(":param:`function_space` must be of type " + "``firedrake.functionspaceimpl.FunctionSpace`` " + "or ``firedrake.functionspaceimpl.WithGeometry`` ", + "not %s" % type(function_space)) + + if not isinstance(mesh_importer, FiredrakeMeshTopologyImporter): + raise TypeError(":param:`mesh_importer` must be either *None* " + "or of type :class:`meshmode.interop.firedrake." + "FiredrakeMeshTopologyImporter`") + if not function_space.mesh() == mesh_importer.data: + raise ValueError(":param:`mesh_importer`'s *data* attribute " + "must be the same mesh as returned by " + ":param:`function_space`'s *mesh()* method.") + + if not isinstance(finat_element_importer, FinatLagrangeElementImporter): + raise TypeError(":param:`finat_element_importer` must be either " + "*None* " + "or of type :class:`meshmode.interop.FInAT." + "FinatLagragneElementImporter`") + if not function_space.finat_element == finat_element_importer.data: + raise ValueError(":param:`finat_element_importer`'s *data* " + "attribute " + "must be the same finat element as " + ":param:`function_space`'s *finat_element*" + " attribute.") + + # }}} + + # We want to ignore any geometry and then finish initialization + function_space = function_space.topological + super(FiredrakeFunctionSpaceImporter, self).__init__(function_space) + + self._mesh_importer = mesh_importer + self.finat_element_importer = finat_element_importer + + @property + def topological_importer(self): + """ + A reference to self for compatability with 'geometrical' function spaces + """ + return self + + def mesh_importer(self): + """ + Return this object's mesh importer + """ + return self._mesh_importer + +# }}} + + +# {{{ Container to hold the coordinateless functions themselves + + +class FiredrakeCoordinatelessFunctionImporter(ExternalImportHandler): + """ + A coordinateless function, i.e. a function defined on a set of + points which only have an associated topology, no associated + geometry. + + .. attribute:: data + + An instance of :mod:`firedrake` class + :class:`firedrake.function.CoordinatelessFunction`. + Note that a coordinateless function object in firedrake + references concrete, but mutable data. + """ + def __init__(self, function, function_space_importer): + """ + :param function: The instance of + :class:`firedrake.function.CoordinatelessFunction` + which this object is importing. Becomes the + :attr:`data` attribute. + + :param function_space_importer: An instance of + :class:`FiredrakeFunctionSpaceImporter` + which is importing the topological function space that + :param:`function` is built on. + + :raises TypeError: If either parameter is the wrong type + :raises ValueError: If :param:`function_space_importer` is an + importer for a firedrake function space which is not + identical to ``function.topological.function_space()`` + """ + # {{{ Some type-checking + + from firedrake.function import Function, CoordinatelessFunction + if not isinstance(function, (CoordinatelessFunction, Function)): + raise TypeError(":param:`function` must be of type " + "`firedrake.function.CoordinatelessFunction` " + " or `firedrdake.function.Function`") + + if not isinstance(function_space_importer, + FiredrakeFunctionSpaceImporter): + raise TypeError(":param:`function_space_importer` must be of type " + "`meshmode.interop.firedrake." + "FiredrakeFunctionSpaceImporter`.") + + if not function_space_importer.data == function.function_space(): + raise ValueError(":param:`function_space_importer`'s *data* " + "attribute and ``function.function_space()`` " + "must be identical.") + + function = function.topological + function_space_importer = function_space_importer.topological_importer + + super(FiredrakeCoordinatelessFunctionImporter, self).__init__(function) + + self._function_space_importer = function_space_importer + + def function_space_importer(self): + """ + Return the + :class:`meshmode.interop.firedrake.FiredrakeFunctionSpaceImporter` + instance being used to import the function space + of the underlying firedrake function this object represents. + """ + return self._function_space_importer + + @property + def topological_importer(self): + """ + A reference to self for compatability with functions that have + coordinates. + """ + return self + + +# }}} -- GitLab From aa9bd6d93a5e74839f09eec554b9035c0b43f45c Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Mon, 1 Jun 2020 11:16:04 -0500 Subject: [PATCH 011/221] Forgot to add __init__.py --- meshmode/interop/firedrake/__init__.py | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 meshmode/interop/firedrake/__init__.py diff --git a/meshmode/interop/firedrake/__init__.py b/meshmode/interop/firedrake/__init__.py new file mode 100644 index 00000000..67712b23 --- /dev/null +++ b/meshmode/interop/firedrake/__init__.py @@ -0,0 +1,33 @@ +__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. +""" + +__doc__ = """ +.. autoclass:: FiredrakeConnection +""" + +from meshmode.discretization.connection import DiscretizationConnection + + +# TODO +class FiredrakeConnection(DiscretizationConnection): + def __init__(self, cl_ctx, fdrake_function_space): + pass -- GitLab From 1c3b93bf70be68a579e8243b0bba68d345380860 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Mon, 1 Jun 2020 11:16:46 -0500 Subject: [PATCH 012/221] Changed name of underlying structure from transporter to handler --- meshmode/interop/FInAT/__init__.py | 2 + meshmode/interop/FInAT/lagrange_element.py | 18 ++--- meshmode/interop/__init__.py | 80 ++++++---------------- meshmode/interop/fiat/simplex_cell.py | 24 ++----- 4 files changed, 34 insertions(+), 90 deletions(-) diff --git a/meshmode/interop/FInAT/__init__.py b/meshmode/interop/FInAT/__init__.py index f53a2135..3708afa4 100644 --- a/meshmode/interop/FInAT/__init__.py +++ b/meshmode/interop/FInAT/__init__.py @@ -20,4 +20,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +from meshmode.interop.FInAT.lagrange_element import FinatLagrangeElementImporter + __all__ = ['FinatLagrangeElementImporter'] diff --git a/meshmode/interop/FInAT/lagrange_element.py b/meshmode/interop/FInAT/lagrange_element.py index c74b2e53..ce71d6b7 100644 --- a/meshmode/interop/FInAT/lagrange_element.py +++ b/meshmode/interop/FInAT/lagrange_element.py @@ -2,7 +2,7 @@ import numpy as np import numpy.linalg as la import six -from meshmode.interop import ExternalImporter +from meshmode.interop import ExternalImportHandler from meshmode.interop.fiat import FIATSimplexCellImporter @@ -12,16 +12,10 @@ __doc__ = """ """ -class FinatLagrangeElementImporter(ExternalImporter): +class FinatLagrangeElementImporter(ExternalImportHandler): """ 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): """ @@ -76,11 +70,11 @@ class FinatLagrangeElementImporter(ExternalImporter): # Get unit nodes for dim, element_nrs in six.iteritems( - self.analog().entity_support_dofs()): + self.data.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) + dim, element_nr, self.data.degree) # Record any new nodes i = 0 for node_nr in node_list: @@ -106,7 +100,7 @@ class FinatLagrangeElementImporter(ExternalImporter): """ :return: The dimension of the FInAT element's cell """ - return self.cell_importer.from_data.get_dimension() + return self.cell_importer.data.get_dimension() def unit_vertex_indices(self): """ @@ -162,7 +156,7 @@ class FinatLagrangeElementImporter(ExternalImporter): from modepy import resampling_matrix, simplex_best_available_basis flip_matrix = resampling_matrix( - simplex_best_available_basis(self.dim(), self.analog().degree), + simplex_best_available_basis(self.dim(), self.data.degree), flipped_unit_nodes, self.unit_nodes()) flip_matrix[np.abs(flip_matrix) < 1e-15] = 0 diff --git a/meshmode/interop/__init__.py b/meshmode/interop/__init__.py index 91ddfd76..bd6d9257 100644 --- a/meshmode/interop/__init__.py +++ b/meshmode/interop/__init__.py @@ -25,97 +25,59 @@ from abc import ABC __doc__ = """ Development Interface --------------------- -.. autoclass:: ExternalTransporter -.. autoclass:: ExternalImporter -.. autoclass:: ExternalExporter +.. autoclass:: ExternalDataHandler +.. autoclass:: ExternalExportHandler +.. autoclass:: ExternalImportHandler """ # {{{ Generic, most abstract class for transporting meshmode <-> external -class ExternalTransporter(ABC): +class ExternalDataHandler(ABC): """ - .. attribute:: from_data + A data handler takes data from meshmode and facilitates its use + in another package or the reverse: takes data from another package + and facilitates its use in meshmode. - The object which needs to be transported either to meshmode or - from meshmode + .. attribute:: data - .. attribute:: to_data - - The "transported" object, i.e. the - - This attribute does not exist at instantiation time. - If exporting (resp. importing) from meshmode - then we are using an :class:`ExternalExporter` - (resp. :class:`ExternalImporter`) instance. :attr:`to_data` is - computed with a call to :fun:`ExternalExporter.export_data` - (resp. :fun:`ExternalImporter.import_data`). - - :raises ValueError: if :attr:`to_data` is accessed before creation. - :raises NotImplementedError: if :meth:`validate_to_data` is called - without an implementation. + The object which needs to be interfaced either into meshmode or + out of meshmode. + Should not be modified after creation. """ - def __init__(self, from_data): - self.from_data = from_data - - def validate_to_data(self): - """ - Validate :attr:`to_data` - - :return: *True* if :attr:`to_data` has been computed and is valid - and *False* otherwise - """ - raise NotImplementedError("*validate_to_data* method not implemented " - "for object of type %s" % type(self)) + def __init__(self, data): + self.data = data def __hash__(self): - return hash((type(self), self.from_data)) + return hash((type(self), self.data)) def __eq__(self, other): return isinstance(other, type(self)) and \ isinstance(self, type(other)) and \ - self.from_data == other.from_data + self.data == other.data def __neq__(self, other): return not self.__eq__(other) - def __getattr__(self, attr): - if attr != 'to_data': - return super(ExternalTransporter, self).__getattr__(attr) - raise ValueError("Attribute *to_data* has not yet been computed. " - "An object of class *ExternalExporter* (resp. " - "*ExternalImporter*) must call *export_data()* " - "(resp. *import_data()*) to compute attribute *to_data*") - # }}} # {{{ Define specific classes for meshmode -> external and meshmode <- external -class ExternalExporter(ExternalTransporter): +class ExternalExportHandler(ExternalDataHandler): """ - Subclass of :class:`ExternalTransporter` for meshmode -> external + Subclass of :class:`ExternalDataHandler` for meshmode -> external data transfer """ - def export_data(self): - """ - Compute :attr:`to_data` from :attr:`from_data` - """ - raise NotImplementedError("*export_data* method not implemented " - "for type %s" % type(self)) + pass -class ExternalImporter(ExternalTransporter): +class ExternalImportHandler(ExternalDataHandler): """ - Subclass of :class:`ExternalTransporter` for external -> meshmode + Subclass of :class:`ExternalDataHandler` for external -> meshmode data transfer """ - def import_data(self): - """ - Compute :attr:`to_data` from :attr:`from_data` - """ - raise NotImplementedError("*import_data* method not implemented " - "for type %s" % type(self)) + pass # }}} diff --git a/meshmode/interop/fiat/simplex_cell.py b/meshmode/interop/fiat/simplex_cell.py index f2b8366b..bdcabd83 100644 --- a/meshmode/interop/fiat/simplex_cell.py +++ b/meshmode/interop/fiat/simplex_cell.py @@ -1,7 +1,7 @@ import numpy as np import numpy.linalg as la -from meshmode.interop import ExternalImporter +from meshmode.interop import ExternalImportHandler __doc__ = """ @@ -54,27 +54,13 @@ def get_affine_mapping(reference_vects, vects): # {{{ Interoperator for FIAT's reference_element.Simplex -class FIATSimplexCellImporter(ExternalImporter): +class FIATSimplexCellImporter(ExternalImportHandler): """ - Importer for a :mod:`FIAT` simplex cell. + Import handler for a :mod:`FIAT` simplex cell. - There is no obvious meshmode counterpart for :attr:`from_data`. - The data is simply used to obtain FIAT's reference - nodes according to :mod:`modepy`'s reference coordinates - using :meth:`make_points`. - - In particular, methods - :meth:`import_data` and :meth:`validate_to_data` - are *NOT* implemented - and there is no :attr:`to_data` to be computed. - - .. attribute:: from_data + .. attribute:: data An instance of :class:`fiat.FIAT.reference_element.Simplex`. - - .. attribute:: to_data - - :raises ValueError: If accessed. """ def __init__(self, cell): """ @@ -116,7 +102,7 @@ class FIATSimplexCellImporter(ExternalImporter): :return: an *np.array* of shape *(dim, npoints)* holding the coordinates of each of the ver """ - points = self.from_data.make_points(dim, entity_id, order) + points = self.data.make_points(dim, entity_id, order) if not points: return points points = np.array(points) -- GitLab From 30f04b46261b34d7575cdfe02addfa815b182943 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Mon, 1 Jun 2020 11:51:08 -0500 Subject: [PATCH 013/221] Added an importer for mesh geometries --- meshmode/interop/firedrake/mesh_geometry.py | 408 ++++++++++++++++++++ 1 file changed, 408 insertions(+) create mode 100644 meshmode/interop/firedrake/mesh_geometry.py diff --git a/meshmode/interop/firedrake/mesh_geometry.py b/meshmode/interop/firedrake/mesh_geometry.py new file mode 100644 index 00000000..313f7c3e --- /dev/null +++ b/meshmode/interop/firedrake/mesh_geometry.py @@ -0,0 +1,408 @@ +from warnings import warn # noqa +from collections import defaultdict +import numpy as np + +from meshmode.interop import ExternalImportHandler +from meshmode.interop.firedrake.coordinateless_functions_impl import \ + FiredrakeCoordinatelessFunctionImporter + + +class FiredrakeMeshGeometryImporter(ExternalImportHandler): + """ + This takes a :mod:`firedrake` :class:`MeshGeometry` + and converts its data so that :mod:`meshmode` can handle it. + + .. attribute:: data + + A :mod:`firedrake` :class:`MeshGeometry` instance + """ + + def __init__(self, + mesh, + coordinates_importer, + normals=None, + no_normals_warn=True): + """ + :param mesh: A :mod:`firedrake` :class:`MeshGeometry`. + We require that :aram:`mesh` have co-dimesnion + of 0 or 1. + Moreover, if :param:`mesh` is a 2-surface embedded in 3-space, + we _require_ that :function:`init_cell_orientations` + has been called already. + + :param coordinates_importer: An instance of + class :class:`FiredrakeCoordinatelessFunctionImporter` + to use, mapping the points of the topological mesh + to their coordinates (The function is coordinateless + in the sense that its *domain* has no coordinates) + + For other params see :meth:`orientations` + """ + super(FiredrakeMeshGeometryImporter, self).__init__(mesh) + + # {{{ Make sure input data is valid + + # Ensure is not a topological mesh + if mesh.topological == mesh: + raise TypeError(":param:`mesh` must be of type" + " :class:`firedrake.mesh.MeshGeometry`") + + # Ensure dimensions are in appropriate ranges + supported_dims = [1, 2, 3] + if mesh.geometric_dimension() not in supported_dims: + raise ValueError("Geometric dimension is %s. Geometric " + " dimension must be one of range %s" + % (mesh.geometric_dimension(), supported_dims)) + + # Raise warning if co-dimension is not 0 or 1 + co_dimension = mesh.geometric_dimension() - mesh.topological_dimension() + if co_dimension not in [0, 1]: + raise ValueError("Codimension is %s, but must be 0 or 1." % + (co_dimension)) + + # Ensure coordinates are coordinateless + if not isinstance(coordinates_importer, + FiredrakeCoordinatelessFunctionImporter): + raise ValueError(":param:`coordinates_importer` must be of type" + " FiredrakeCoordinatelessFunctionImporter") + + fspace_importer = coordinates_importer.function_space_importer() + topology_importer = fspace_importer.mesh_importer() + + if topology_importer != mesh.topology: + raise ValueError("Topology :param:`coordinates` lives on must be " + "the same " + "topology that :param:`mesh` lives on") + + # }}} + + # For sharing data like in firedrake + self._shared_data_cache = defaultdict(dict) + + # Store input information + self._coordinates_importer = coordinates_importer + self._topology_importer = topology_importer + + self._normals = normals + self._no_normals_warn = no_normals_warn + + # To be computed later + self._vertex_indices = None + self._vertices = None + self._nodes = None + self._group = None + self._orient = None + self._facial_adjacency_groups = None + self._meshmode_mesh = None + + def callback(cl_ctx): + """ + Finish initialization by creating a coordinates function importer + for public access on this mesh which is not "coordinateless" (i.e. + its domain has coordinates) + """ + from meshmode.interop.firedrake.function import \ + FiredrakeFunctionImporter + from meshmode.interop.firedrake.functionspace import \ + FiredrakeWithGeometryImporter + from firedrake import Function + + coordinates_fs = self.data.coordinates.function_space() + coordinates_fs_importer = \ + self._coordinates_importer.function_space_importer() + + V_importer = FiredrakeWithGeometryImporter(cl_ctx, + coordinates_fs, + coordinates_fs_importer, + self) + f = Function(V_importer.data, val=self._coordinates_a.data) + self._coordinates_function_importer = \ + FiredrakeFunctionImporter(f, V_importer) + + del self._callback + + self._callback = callback + + def initialized(self): + return not hasattr(self, '_callback') + + def init(self, cl_ctx): + if not self.initialized(): + self._callback(cl_ctx) + + def __getattr__(self, attr): + """ + If can't find an attribute, look in the underlying + topological mesh + (Done like :class:`firedrake.function.MeshGeometry`) + """ + return getattr(self._topology_importer, attr) + + @property + def coordinates_importer(self): + """ + Return coordinates as a function + + PRECONDITION: Have called :meth:`init` + """ + try: + return self._coordinates_function_importer + except AttributeError: + raise AttributeError("No coordinates function, have you finished" + " initializing this object" + " (i.e. have you called :meth:`init`)?") + + def _compute_vertex_indices_and_vertices(self): + if self._vertex_indices is None: + fspace_importer = self.coordinates_importer.function_space_importer() + finat_element_importer = fspace_importer.finat_element_importer + + # Convert cell node list of mesh to vertex list + unit_vertex_indices = finat_element_importer.unit_vertex_indices() + cfspace = self.data.coordinates.function_space() + if self.icell_to_fd is not None: + cell_node_list = cfspace.cell_node_list[self.icell_to_fd] + else: + cell_node_list = cfspace.cell_node_list + + vertex_indices = cell_node_list[:, unit_vertex_indices] + + # Get maps newnumbering->old and old->new (new numbering comes + # from removing the non-vertex + # nodes) + vert_ndx_to_fd_ndx = np.unique(vertex_indices.flatten()) + fd_ndx_to_vert_ndx = dict(zip(vert_ndx_to_fd_ndx, + np.arange(vert_ndx_to_fd_ndx.shape[0], + dtype=np.int32) + )) + # Get vertices array + vertices = np.real( + self.data.coordinates.dat.data[vert_ndx_to_fd_ndx]) + + #:mod:`meshmode` wants shape to be [ambient_dim][nvertices] + if len(vertices.shape) == 1: + # 1 dim case, (note we're about to transpose) + vertices = vertices.reshape(vertices.shape[0], 1) + vertices = vertices.T.copy() + + # Use new numbering on vertex indices + vertex_indices = np.vectorize(fd_ndx_to_vert_ndx.get)(vertex_indices) + + # store vertex indices and vertices + self._vertex_indices = vertex_indices + self._vertices = vertices + + def vertex_indices(self): + """ + A numpy array of shape *(nelements, nunitvertices)* + holding the vertex indices associated to each element + """ + self._compute_vertex_indices_and_vertices() + return self._vertex_indices + + def vertices(self): + """ + Return the mesh vertices as a numpy array of shape + *(dim, nvertices)* + """ + self._compute_vertex_indices_and_vertices() + return self._vertices + + def nodes(self): + """ + Return the mesh nodes as a numpy array of shape + *(dim, nelements, nunitnodes)* + """ + if self._nodes is None: + coords = self.data.coordinates.dat.data + cfspace = self.data.coordinates.function_space() + + if self.icell_to_fd is not None: + cell_node_list = cfspace.cell_node_list[self.icell_to_fd] + else: + cell_node_list = cfspace.cell_node_list + self._nodes = np.real(coords[cell_node_list]) + + # reshape for 1D so that [nelements][nunit_nodes][dim] + if len(self._nodes.shape) != 3: + self._nodes = np.reshape(self._nodes, self._nodes.shape + (1,)) + + # Change shape to [dim][nelements][nunit_nodes] + self._nodes = np.transpose(self._nodes, (2, 0, 1)) + + return self._nodes + + def group(self): + """ + Return an instance of :class:meshmode.mesh.SimplexElementGroup` + corresponding to the mesh :attr:`data` + """ + if self._group is None: + from meshmode.mesh import SimplexElementGroup + from meshmode.mesh.processing import flip_simplex_element_group + + fspace_importer = self.coordinates_importer.function_space_importer() + finat_element_importer = fspace_importer.finat_element_importer + + # IMPORTANT that set :attr:`_group` because + # :meth:`orientations` may call :meth:`group` + self._group = SimplexElementGroup( + finat_element_importer.data.degree, + self.vertex_indices(), + self.nodes(), + dim=self.cell_dimension(), + unit_nodes=finat_element_importer.unit_nodes()) + + self._group = flip_simplex_element_group(self.vertices(), self._group, + self.orientations() < 0) + + return self._group + + def orientations(self): + """ + Return the orientations of the mesh elements: + an array, the *i*th element is > 0 if the *ith* element + is positively oriented, < 0 if negatively oriented + + :param normals: _Only_ used if :param:`mesh` is a 1-surface + embedded in 2-space. In this case, + - If *None* then + all elements are assumed to be positively oriented. + - Else, should be a list/array whose *i*th entry + is the normal for the *i*th element (*i*th + in :param:`mesh`*.coordinate.function_space()*'s + :attribute:`cell_node_list`) + + :param no_normals_warn: If *True*, raises a warning + if :param:`mesh` is a 1-surface embedded in 2-space + and :param:`normals` is *None*. + """ + if self._orient is None: + # compute orientations + tdim = self.data.topological_dimension() + gdim = self.data.geometric_dimension() + + orient = None + if gdim == tdim: + # We use :mod:`meshmode` to check our orientations + from meshmode.mesh.processing import \ + find_volume_mesh_element_group_orientation + + orient = \ + find_volume_mesh_element_group_orientation(self.vertices(), + self.group()) + + if tdim == 1 and gdim == 2: + # In this case we have a 1-surface embedded in 2-space + orient = np.ones(self.nelements()) + if self._normals: + for i, (normal, vertices) in enumerate(zip( + np.array(self._normals), self.vertices())): + if np.cross(normal, vertices) < 0: + orient[i] = -1.0 + elif self._no_normals_warn: + warn("Assuming all elements are positively-oriented.") + + elif tdim == 2 and gdim == 3: + # In this case we have a 2-surface embedded in 3-space + orient = self.data.cell_orientations().dat.data + r""" + Convert (0 \implies negative, 1 \implies positive) to + (-1 \implies negative, 1 \implies positive) + """ + orient *= 2 + orient -= np.ones(orient.shape, dtype=orient.dtype) + + self._orient = orient + #Make sure the mesh fell into one of the cases + """ + NOTE : This should be guaranteed by previous checks, + but is here anyway in case of future development. + """ + assert self._orient is not None + + return self._orient + + def face_vertex_indices_to_tags(self): + """ + Returns a dict mapping frozensets + of vertex indices (which identify faces) to a + list of boundary tags + """ + finat_element = self.data.coordinates.function_space().finat_element + exterior_facets = self.data.exterior_facets + + # fvi_to_tags maps frozenset(vertex indices) to tags + fvi_to_tags = {} + # maps faces to local vertex indices + connectivity = finat_element.cell.connectivity[(self.cell_dimension()-1, 0)] + + # Compatability for older versions of firedrake + try: + local_fac_number = exterior_facets.local_facet_number + except AttributeError: + local_fac_number = exterior_facets.local_facet_dat.data + + for i, (icells, ifacs) in enumerate(zip(exterior_facets.facet_cell, + local_fac_number)): + # Compatability for older versions of firedrake + try: + iter(ifacs) + except TypeError: + ifacs = [ifacs] + + for icell, ifac in zip(icells, ifacs): + # If necessary, convert to new cell numbering + if self.fd_to_icell is not None: + if icell not in self.fd_to_icell: + continue + else: + icell = self.fd_to_icell[icell] + + # record face vertex indices to tag map + cell_vertices = self.vertex_indices()[icell] + facet_indices = connectivity[ifac] + fvi = frozenset(cell_vertices[list(facet_indices)]) + fvi_to_tags.setdefault(fvi, []) + fvi_to_tags[fvi].append(exterior_facets.markers[i]) + + # }}} + + return fvi_to_tags + + def facial_adjacency_groups(self): + """ + Return a list of :mod:`meshmode` :class:`FacialAdjacencyGroups` + as used in the construction of a :mod:`meshmode` :class:`Mesh` + """ + # {{{ Compute facial adjacency groups if not already done + + if self._facial_adjacency_groups is None: + from meshmode.mesh import _compute_facial_adjacency_from_vertices + + self._facial_adjacency_groups = _compute_facial_adjacency_from_vertices( + [self.group()], + self.bdy_tags(), + np.int32, np.int8, + face_vertex_indices_to_tags=self.face_vertex_indices_to_tags()) + + # }}} + + return self._facial_adjacency_groups + + def meshmode_mesh(self): + """ + PRECONDITION: Have called :meth:`init` + """ + if self._meshmode_mesh is None: + assert self.initialized(), \ + "Must call :meth:`init` before :meth:`meshmode_mesh`" + + from meshmode.mesh import Mesh + self._meshmode_mesh = \ + Mesh(self.vertices(), [self.group()], + boundary_tags=self.bdy_tags(), + nodal_adjacency=self.nodal_adjacency(), + facial_adjacency_groups=self.facial_adjacency_groups()) + + return self._meshmode_mesh -- GitLab From a2ab04f23ff534bd85e72e0ac8b0c08f26e2da7e Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Mon, 1 Jun 2020 12:12:14 -0500 Subject: [PATCH 014/221] Added function space shared data --- ...ns_impl.py => function_space_coordless.py} | 0 .../firedrake/function_space_shared_data.py | 243 ++++++++++++++++++ meshmode/interop/firedrake/mesh_geometry.py | 2 +- 3 files changed, 244 insertions(+), 1 deletion(-) rename meshmode/interop/firedrake/{coordinateless_functions_impl.py => function_space_coordless.py} (100%) create mode 100644 meshmode/interop/firedrake/function_space_shared_data.py diff --git a/meshmode/interop/firedrake/coordinateless_functions_impl.py b/meshmode/interop/firedrake/function_space_coordless.py similarity index 100% rename from meshmode/interop/firedrake/coordinateless_functions_impl.py rename to meshmode/interop/firedrake/function_space_coordless.py diff --git a/meshmode/interop/firedrake/function_space_shared_data.py b/meshmode/interop/firedrake/function_space_shared_data.py new file mode 100644 index 00000000..e5032b9b --- /dev/null +++ b/meshmode/interop/firedrake/function_space_shared_data.py @@ -0,0 +1,243 @@ +import numpy as np +import numpy.linalg as la +from decorator import decorator +from firedrake.functionspacedata import FunctionSpaceData +from meshmode.discretization import Discretization + +from meshmode.interop import ExternalImportHandler +from meshmode.interop.FInAT.lagrange_element import \ + FinatLagrangeElementImporter + + +@decorator +def cached(f, mesh_analog, key, *args, **kwargs): + """ + Exactly :func:`firedrake.functionspacedata.cached`, but + caching on mesh Geometry instead + + :param f: The function to cache. + :param mesh_analog: The mesh_analog to cache on (should have a + ``_shared_data_cache`` object). + :param key: The key to the cache. + :args args: Additional arguments to ``f``. + :kwargs kwargs: Additional keyword arguments to ``f``.""" + assert hasattr(mesh_analog, "_shared_data_cache") + cache = mesh_analog._shared_data_cache[f.__name__] + try: + return cache[key] + except KeyError: + result = f(mesh_analog, key, *args, **kwargs) + cache[key] = result + return result + + +def reorder_nodes(orient, nodes, flip_matrix, unflip=False): + """ + :param orient: An array of shape (nelements) of orientations, + >0 for positive, <0 for negative + :param nodes: a (nelements, nunit_nodes) or (dim, nelements, nunit_nodes) + shaped array of nodes + :param flip_matrix: The matrix used to flip each negatively-oriented + element + :param unflip: If *True*, use transpose of :param:`flip_matrix` to + flip negatively-oriented elements + + flips :param:`nodes` + """ + # reorder nodes (Code adapted from + # meshmode.mesh.processing.flip_simplex_element_group) + + # ( round to int bc applying on integers) + flip_mat = np.rint(flip_matrix) + if unflip: + flip_mat = flip_mat.T + + # flipping twice should be identity + assert la.norm( + np.dot(flip_mat, flip_mat) + - np.eye(len(flip_mat))) < 1e-13 + + # }}} + + # {{{ flip nodes that need to be flipped, note that this point we act + # like we are in a DG space + + # if a vector function space, nodes array is shaped differently + if len(nodes.shape) > 2: + nodes[orient < 0] = np.einsum( + "ij,ejk->eik", + flip_mat, nodes[orient < 0]) + # Reshape to [nodes][vector dims] + nodes = nodes.reshape( + nodes.shape[0] * nodes.shape[1], nodes.shape[2]) + # pytential wants [vector dims][nodes] not [nodes][vector dims] + nodes = nodes.T.copy() + else: + nodes[orient < 0] = np.einsum( + "ij,ej->ei", + flip_mat, nodes[orient < 0]) + # convert from [element][unit_nodes] to + # global node number + nodes = nodes.flatten() + + +@cached +def reordering_array(mesh_importer, key, fspace_data): + """ + :param key: A tuple (finat_element_anlog, firedrake_to_meshmode) + where *firedrake_to_meshmode* is a *bool*, *True* indicating + firedrake->meshmode reordering, *False* meshmode->firedrake + + :param fspace_data: A :mod:`firedrake` instance of shared + function space data, i.e. + :class:`firedrake.functionspacedata.FunctionSpaceData` + + :returns: an *np.array* that can reorder the data by composition, + see :meth:`fd2mm.function_space.FunctionSpaceAnalog.reorder_nodes` + """ + finat_element_importer, firedrake_to_meshmode = key + assert isinstance(finat_element_importer, FinatLagrangeElementImporter) + + cell_node_list = fspace_data.entity_node_lists[mesh_importer.data.cell_set] + if mesh_importer.icell_to_fd is not None: + cell_node_list = cell_node_list[mesh_importer.icell_to_fd] + + num_fd_nodes = fspace_data.node_set.size + + nelements = cell_node_list.shape[0] + nunit_nodes = cell_node_list.shape[1] + num_mm_nodes = nelements * nunit_nodes + + if firedrake_to_meshmode: + nnodes = num_fd_nodes + else: + nnodes = num_mm_nodes + order = np.arange(nnodes) + + # Put into cell-node list if firedrake-to meshmode (so can apply + # flip-mat) + if firedrake_to_meshmode: + new_order = order[cell_node_list] + # else just need to reshape new_order so that can apply flip-mat + else: + new_order = order.reshape( + (order.shape[0]//nunit_nodes, nunit_nodes) + order.shape[1:]) + + flip_mat = finat_element_importer.flip_matrix() + reorder_nodes(mesh_importer.orientations(), new_order, flip_mat, + unflip=firedrake_to_meshmode) + new_order = new_order.flatten() + + # Resize new_order if going meshmode->firedrake and meshmode + # has duplicate nodes (e.g if used a CG fspace) + # + # TODO: this is done VERY LAZILY (NOT GOOD) + if not firedrake_to_meshmode and num_fd_nodes != num_mm_nodes: + newnew_order = np.zeros(num_fd_nodes, dtype=np.int32) + pyt_ndx = 0 + for nodes in cell_node_list: + for fd_index in nodes: + newnew_order[fd_index] = new_order[pyt_ndx] + pyt_ndx += 1 + + new_order = newnew_order + + # }}} + + return new_order + + +@cached +def get_factory(mesh_analog, degree): + from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory + return InterpolatoryQuadratureSimplexGroupFactory(degree) + + +@cached +def get_discretization(mesh_analog, key): + finat_element_importer, cl_ctx = key + assert isinstance(finat_element_importer, FinatLagrangeElementImporter) + + degree = finat_element_importer.data.degree + discretization = Discretization(cl_ctx, + mesh_analog.meshmode_mesh(), + get_factory(mesh_analog, degree)) + + return discretization + + +class FunctionSpaceDataAnalog(ExternalImportHandler): + """ + This is not *quite* the usual thought of a + :class:`ExternalImportHandler`. + + It handles an "import" in the sense that a mesh & finat element + define a lot of the data of a function space that + can be shared between multiple function spaces on the + same mesh, so we use + this object to store data based on the mesh and finat element, + cached on the mesh geometry. + In contrast to firedrake's shared data, we need the mesh + geometry + + .. attribute:: data + + A tuple whose first entry is an instance of + class :class:`FiredrakeMeshGeometryImporter` and second + entry is an instance of class + :class:`FinatLagrangeElementImporter` + """ + # FIXME: Give two finat elts + def __init__(self, cl_ctx, mesh_importer, finat_element_importer): + """ + Note that :param:`mesh_analog` and :param:`finat_element_analog` + are used for analog-checking + """ + if mesh_importer.topological_a == mesh_importer: + raise TypeError(":param:`mesh_importer` is a " + "FiredrakeMeshTopologyImporter, but " + " must be a FiredrakeMeshGeometryImporter") + importer = (mesh_importer.importer(), finat_element_importer.importer()) + super(FunctionSpaceDataAnalog, self).__init__(importer) + + self._fspace_data = FunctionSpaceData(mesh_importer.importer(), + finat_element_importer.importer()) + + self._cl_ctx = cl_ctx + self._mesh_importer = mesh_importer + self._finat_element_importer = finat_element_importer + self._discretization = None + + def firedrake_to_meshmode(self): + """ + See :func:`reordering_array` + """ + return reordering_array(self._mesh_importer, + (self._finat_element_importer, True), + self._fspace_data) + + def meshmode_to_firedrake(self): + """ + See :func:`reordering_array` + """ + return reordering_array(self._mesh_importer, + (self._finat_element_importer, False), + self._fspace_data) + + def discretization(self): + """ + Creates a discretization on the mesh returned by this object's + mesh importer's :func:`meshmode_mesh()` + """ + return get_discretization(self._mesh_importer, + (self._finat_element_importer, self._cl_ctx)) + + def factory(self): + """ + Returns a :mod:`meshmode` + :class:`InterpolatoryQuadratureSimplexGroupFactory` + of the same degree as this object's finat element. + """ + degree = self._finat_element_importer.data.degree + return get_factory(self._mesh_importer, degree) diff --git a/meshmode/interop/firedrake/mesh_geometry.py b/meshmode/interop/firedrake/mesh_geometry.py index 313f7c3e..23d803a7 100644 --- a/meshmode/interop/firedrake/mesh_geometry.py +++ b/meshmode/interop/firedrake/mesh_geometry.py @@ -3,7 +3,7 @@ from collections import defaultdict import numpy as np from meshmode.interop import ExternalImportHandler -from meshmode.interop.firedrake.coordinateless_functions_impl import \ +from meshmode.interop.firedrake.function_space_coordless import \ FiredrakeCoordinatelessFunctionImporter -- GitLab From eedefb9a48261a127f0f33d69cf503dd24aa58fd Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Mon, 1 Jun 2020 13:02:59 -0500 Subject: [PATCH 015/221] Added a function space importer --- meshmode/interop/firedrake/function_space.py | 184 ++++++++++++++++++ .../firedrake/function_space_shared_data.py | 2 +- meshmode/interop/firedrake/mesh_geometry.py | 2 +- 3 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 meshmode/interop/firedrake/function_space.py diff --git a/meshmode/interop/firedrake/function_space.py b/meshmode/interop/firedrake/function_space.py new file mode 100644 index 00000000..ca4bfd63 --- /dev/null +++ b/meshmode/interop/firedrake/function_space.py @@ -0,0 +1,184 @@ +from warnings import warn +import numpy as np + +from meshmode.interop import ExternalImportHandler +from meshmode.interop.firedrake.mesh_geometry import \ + FiredrakeMeshGeometryImporter +from meshmode.interop.firedrake.function_space_coordless import \ + FiredrakeFunctionSpaceImporter + +from meshmode.interop.firedrake.function_space_shared_data import \ + FiredrakeFunctionSpaceDataImporter + + +class FiredrakeWithGeometryImporter(ExternalImportHandler): + def __init__(self, + cl_ctx, + function_space, + function_space_importer, + mesh_importer): + """ + :param cl_ctx: A pyopencl context + + :param function_space: A firedrake :class:`WithGeometry` + + :param function_space_importer: An instance of class + :class:`FiredrakeFunctionSpaceImporter` for the + underlying topological function space of :param`function_space` + + :param mesh_importer: An instance of class + :class:`FiredrakeMeshGeometryImporter` for the mesh + that :param:`function_space` is built on + """ + # FIXME use on bdy + # {{{ Check input + from firedrake.functionspaceimpl import WithGeometry + if not isinstance(function_space, WithGeometry): + raise TypeError(":arg:`function_space` must be of type" + " :class:`firedrake.functionspaceimpl.WithGeometry") + + if not isinstance(function_space_importer, + FiredrakeFunctionSpaceImporter): + raise TypeError(":arg:`function_space_importer` must be of type" + " FiredrakeFunctionSpaceImporter") + + if not isinstance(mesh_importer, FiredrakeMeshGeometryImporter): + raise TypeError(":arg:`mesh_importer` must be of type" + " FiredrakeMeshGeometryImporter") + + # Make sure importers are good for given function space + assert function_space_importer.data == function_space.topological + assert mesh_importer.data == function_space.mesh() + + # }}} + + # Initialize as Analog + super(FiredrakeWithGeometryImporter, self).__init__(function_space) + self._topology_importer = function_space_importer + self._mesh_importer = mesh_importer + self._cl_ctx = cl_ctx + + finat_element_importer = function_space_importer.finat_element_importer + self._shared_data = \ + FiredrakeFunctionSpaceDataImporter(cl_ctx, + mesh_importer, + finat_element_importer) + + mesh_order = mesh_importer.data.coordinates.\ + function_space().finat_element.degree + if mesh_order > self.data.degree: + warn("Careful! When the mesh order is higher than the element" + " order. Conversion MIGHT work, but maybe not..." + " To be honest I really don't know.") + + # Used to convert between refernce node sets + self._resampling_mat_fd2mm = None + self._resampling_mat_mm2fd = None + + def __getattr__(self, attr): + return getattr(self._topology_a, attr) + + def mesh_importer(self): + return self._mesh_importer + + def _reordering_array(self, firedrake_to_meshmode): + if firedrake_to_meshmode: + return self._shared_data.firedrake_to_meshmode() + return self._shared_data.meshmode_to_firedrake() + + def factory(self): + return self._shared_data.factory() + + def discretization(self): + return self._shared_data.discretization() + + def resampling_mat(self, firedrake_to_meshmode): + if self._resampling_mat_fd2mm is None: + element_grp = self.discretization().groups[0] + self._resampling_mat_fd2mm = \ + self.finat_element_a.make_resampling_matrix(element_grp) + + self._resampling_mat_mm2fd = np.linalg.inv(self._resampling_mat_fd2mm) + + # return the correct resampling matrix + if firedrake_to_meshmode: + return self._resampling_mat_fd2mm + return self._resampling_mat_mm2fd + + def reorder_nodes(self, nodes, firedrake_to_meshmode=True): + """ + :arg nodes: An array representing function values at each of the + dofs, if :arg:`firedrake_to_meshmode` is *True*, should + be of shape (ndofs) or (ndofs, xtra_dims). + If *False*, should be of shape (ndofs) or (xtra_dims, ndofs) + :arg firedrake_to_meshmode: *True* iff firedrake->meshmode, *False* + if reordering meshmode->firedrake + """ + # {{{ Case where shape is (ndofs,), just apply reordering + + if len(nodes.shape) == 1: + return nodes[self._reordering_array(firedrake_to_meshmode)] + + # }}} + + # {{{ Else we have (xtra_dims, ndofs) or (ndofs, xtra_dims): + + # Make sure we have (xtra_dims, ndofs) ordering + if firedrake_to_meshmode: + nodes = nodes.T + + reordered_nodes = nodes[:, self._reordering_array(firedrake_to_meshmode)] + + # if converting mm->fd, change to (ndofs, xtra_dims) + if not firedrake_to_meshmode: + reordered_nodes = reordered_nodes.T + + return reordered_nodes + + # }}} + + def convert_function(self, function): + """ + Convert a firedrake function defined on this function space + to a meshmode form, reordering data as necessary + and resampling to the unit nodes in meshmode + + :param function: A firedrake function or a + :class:`FiredrakeFunctionImporter` instance + + :returns: A numpy array + """ + from meshmode.interop.firedrake.function import FiredrakeFunctionImporter + if isinstance(function, FiredrakeFunctionImporter): + function = function.data + + # FIXME: Check that function can be converted! Maybe check the + # shared data? We can just do the check below + # but it's a little too stiff + #assert function.function_space() == self.data + + nodes = function.dat.data + + # handle vector function spaces differently, hence the shape checks + + # {{{ Reorder the nodes to have positive orientation + # (and if a vector, now have meshmode [dims][nnodes] + # instead of firedrake [nnodes][dims] shape) + + if len(nodes.shape) > 1: + new_nodes = [self.reorder_nodes(nodes.T[i], True) for i in + range(nodes.shape[1])] + nodes = np.array(new_nodes) + else: + nodes = self.reorder_nodes(nodes, True) + + # }}} + + # {{{ Now convert to meshmode reference nodes + node_view = self.discretization().groups[0].view(nodes) + # Multiply each row (repping an element) by the resampler + np.matmul(node_view, self.resampling_mat(True).T, out=node_view) + + # }}} + + return nodes diff --git a/meshmode/interop/firedrake/function_space_shared_data.py b/meshmode/interop/firedrake/function_space_shared_data.py index e5032b9b..9644f0f1 100644 --- a/meshmode/interop/firedrake/function_space_shared_data.py +++ b/meshmode/interop/firedrake/function_space_shared_data.py @@ -167,7 +167,7 @@ def get_discretization(mesh_analog, key): return discretization -class FunctionSpaceDataAnalog(ExternalImportHandler): +class FiredrakeFunctionSpaceDataImporter(ExternalImportHandler): """ This is not *quite* the usual thought of a :class:`ExternalImportHandler`. diff --git a/meshmode/interop/firedrake/mesh_geometry.py b/meshmode/interop/firedrake/mesh_geometry.py index 23d803a7..cd5c1d43 100644 --- a/meshmode/interop/firedrake/mesh_geometry.py +++ b/meshmode/interop/firedrake/mesh_geometry.py @@ -103,7 +103,7 @@ class FiredrakeMeshGeometryImporter(ExternalImportHandler): """ from meshmode.interop.firedrake.function import \ FiredrakeFunctionImporter - from meshmode.interop.firedrake.functionspace import \ + from meshmode.interop.firedrake.function_space import \ FiredrakeWithGeometryImporter from firedrake import Function -- GitLab From e8f56cb89269dfca74127d975b7fda61dac69af4 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Mon, 1 Jun 2020 13:17:19 -0500 Subject: [PATCH 016/221] Added a function importer --- meshmode/interop/firedrake/function.py | 96 ++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 meshmode/interop/firedrake/function.py diff --git a/meshmode/interop/firedrake/function.py b/meshmode/interop/firedrake/function.py new file mode 100644 index 00000000..ce601ec7 --- /dev/null +++ b/meshmode/interop/firedrake/function.py @@ -0,0 +1,96 @@ +import numpy as np + +from firedrake import Function + +from meshmode.interop import ExternalImportHandler +from meshmode.interop.firedrake.function_space import FiredrakeWithGeometryImporter +from meshmode.interop.firedrake.function_space_coordless import \ + FiredrakeCoordinatelessFunctionImporter + + +class FiredrakeFunctionImporter(ExternalImportHandler): + """ + An import handler for :mod:`firedrake` functions. + + .. attribute:: data + + A :mod:`firedrake` function + """ + def __init__(self, function, function_space_importer): + """ + :param function: A firedrake :class:`Function` or a + :class:`FiredrakeCoordinatelessFunctionImporter` + + :param function_space_importer: An instance of + :class:`FiredrakeWithGeometryImporter` + compatible with the function + space this function is built on + """ + # {{{ Check types + if not isinstance(function_space_importer, + FiredrakeWithGeometryImporter): + raise TypeError(":param:`function_space_importer` must be of " + "type FiredrakeWithGeometryImporter") + + if not isinstance(function, (Function, + FiredrakeCoordinatelessFunctionImporter)): + raise TypeError(":param:`function` must be one of " + "(:class:`firedrake.function.Function`," + " FiredrakeCoordinatelessFunctionImporter)") + # }}} + + super(FiredrakeFunctionImporter, self).__init__(function) + + self._function_space_importer = function_space_importer + if isinstance(function, Function): + self._topological_importer = \ + FiredrakeCoordinatelessFunctionImporter(function, + function_space_importer) + else: + self._topological_importer = function + + def function_space_importer(self): + """ + :returns: the :class:`FiredrakeWithGeometryImporter` instance + used for the function space this object's :attr:`data` lives on + """ + return self._function_space_importer + + @property + def topological_importer(self): + """ + :returns: The topological version of this object, i.e. the underlying + coordinateless importer (an instance of + :class:`FiredrakeCoordinatelessFunctionImporter`) + """ + return self._topological_importer + + def as_field(self): + """ + :returns: A numpy array holding the data of this function as a + field on the corresponding meshmode mesh + """ + return self.function_space_importer().convert_function(self) + + def set_from_field(self, field): + """ + Set function :attr:`data`'s data + from a field on the corresponding meshmode mesh. + + :param field: A numpy array representing a field on the corresponding + meshmode mesh + """ + # Handle 1-D case + if len(self.data.dat.data.shape) == 1 and len(field.shape) > 1: + field = field.reshape(field.shape[1]) + + # resample from nodes + group = self.function_space_importer().discretization().groups[0] + resampled = np.copy(field) + resampled_view = group.view(resampled) + resampling_mat = self.function_space_importer().resampling_mat(False) + np.matmul(resampled_view, resampling_mat.T, out=resampled_view) + + # reorder data + self.data.dat.data[:] = self.function_space_importer().reorder_nodes( + resampled, firedrake_to_meshmode=False)[:] -- GitLab From 75e6337a83a3562d0b2540c98eb041d123ea4ffe Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Mon, 1 Jun 2020 13:21:05 -0500 Subject: [PATCH 017/221] Some leftover changes from fd2mm --- meshmode/interop/firedrake/function_space.py | 2 +- .../firedrake/function_space_shared_data.py | 26 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/meshmode/interop/firedrake/function_space.py b/meshmode/interop/firedrake/function_space.py index ca4bfd63..95f4e297 100644 --- a/meshmode/interop/firedrake/function_space.py +++ b/meshmode/interop/firedrake/function_space.py @@ -52,7 +52,7 @@ class FiredrakeWithGeometryImporter(ExternalImportHandler): # }}} - # Initialize as Analog + # Initialize as importer super(FiredrakeWithGeometryImporter, self).__init__(function_space) self._topology_importer = function_space_importer self._mesh_importer = mesh_importer diff --git a/meshmode/interop/firedrake/function_space_shared_data.py b/meshmode/interop/firedrake/function_space_shared_data.py index 9644f0f1..c58bbcd1 100644 --- a/meshmode/interop/firedrake/function_space_shared_data.py +++ b/meshmode/interop/firedrake/function_space_shared_data.py @@ -10,23 +10,23 @@ from meshmode.interop.FInAT.lagrange_element import \ @decorator -def cached(f, mesh_analog, key, *args, **kwargs): +def cached(f, mesh_importer, key, *args, **kwargs): """ Exactly :func:`firedrake.functionspacedata.cached`, but caching on mesh Geometry instead :param f: The function to cache. - :param mesh_analog: The mesh_analog to cache on (should have a + :param mesh_importer: The mesh_importer to cache on (should have a ``_shared_data_cache`` object). :param key: The key to the cache. :args args: Additional arguments to ``f``. :kwargs kwargs: Additional keyword arguments to ``f``.""" - assert hasattr(mesh_analog, "_shared_data_cache") - cache = mesh_analog._shared_data_cache[f.__name__] + assert hasattr(mesh_importer, "_shared_data_cache") + cache = mesh_importer._shared_data_cache[f.__name__] try: return cache[key] except KeyError: - result = f(mesh_analog, key, *args, **kwargs) + result = f(mesh_importer, key, *args, **kwargs) cache[key] = result return result @@ -93,7 +93,7 @@ def reordering_array(mesh_importer, key, fspace_data): :class:`firedrake.functionspacedata.FunctionSpaceData` :returns: an *np.array* that can reorder the data by composition, - see :meth:`fd2mm.function_space.FunctionSpaceAnalog.reorder_nodes` + see :meth:`reorder_nodes` in class :class:`FiredrakeWithGeometryImporter` """ finat_element_importer, firedrake_to_meshmode = key assert isinstance(finat_element_importer, FinatLagrangeElementImporter) @@ -148,21 +148,21 @@ def reordering_array(mesh_importer, key, fspace_data): @cached -def get_factory(mesh_analog, degree): +def get_factory(mesh_importer, degree): from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory return InterpolatoryQuadratureSimplexGroupFactory(degree) @cached -def get_discretization(mesh_analog, key): +def get_discretization(mesh_importer, key): finat_element_importer, cl_ctx = key assert isinstance(finat_element_importer, FinatLagrangeElementImporter) degree = finat_element_importer.data.degree discretization = Discretization(cl_ctx, - mesh_analog.meshmode_mesh(), - get_factory(mesh_analog, degree)) + mesh_importer.meshmode_mesh(), + get_factory(mesh_importer, degree)) return discretization @@ -191,15 +191,15 @@ class FiredrakeFunctionSpaceDataImporter(ExternalImportHandler): # FIXME: Give two finat elts def __init__(self, cl_ctx, mesh_importer, finat_element_importer): """ - Note that :param:`mesh_analog` and :param:`finat_element_analog` - are used for analog-checking + Note that :param:`mesh_importer` and :param:`finat_element_importer` + are used for checking """ if mesh_importer.topological_a == mesh_importer: raise TypeError(":param:`mesh_importer` is a " "FiredrakeMeshTopologyImporter, but " " must be a FiredrakeMeshGeometryImporter") importer = (mesh_importer.importer(), finat_element_importer.importer()) - super(FunctionSpaceDataAnalog, self).__init__(importer) + super(FiredrakeFunctionSpaceDataImporter, self).__init__(importer) self._fspace_data = FunctionSpaceData(mesh_importer.importer(), finat_element_importer.importer()) -- GitLab From 5a213f3340a0e2eafd1e174600474b48b99f16ed Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Mon, 1 Jun 2020 15:31:56 -0500 Subject: [PATCH 018/221] Finished FromFiredrakeConnection --- meshmode/interop/firedrake/__init__.py | 231 ++++++++++++++++++++++++- 1 file changed, 226 insertions(+), 5 deletions(-) diff --git a/meshmode/interop/firedrake/__init__.py b/meshmode/interop/firedrake/__init__.py index 67712b23..11902b75 100644 --- a/meshmode/interop/firedrake/__init__.py +++ b/meshmode/interop/firedrake/__init__.py @@ -21,13 +21,234 @@ THE SOFTWARE. """ __doc__ = """ -.. autoclass:: FiredrakeConnection +.. autoclass:: FromFiredrakeConnection +.. autofunction:: import_firedrake_mesh +.. autofunction:: import_firedrake_function_space """ -from meshmode.discretization.connection import DiscretizationConnection +import numpy as np -# TODO -class FiredrakeConnection(DiscretizationConnection): +# {{{ Helper functions to construct importers for firedrake objects + + +def _compute_cells_near_bdy(mesh, bdy_id): + """ + Returns an array of the cell ids with >= 1 vertex on the + given bdy_id + """ + cfspace = mesh.coordinates.function_space() + cell_node_list = cfspace.cell_node_list + + boundary_nodes = cfspace.boundary_nodes(bdy_id, 'topological') + # Reduce along each cell: Is a vertex of the cell in boundary nodes? + cell_is_near_bdy = np.any(np.isin(cell_node_list, boundary_nodes), axis=1) + + return np.arange(cell_node_list.shape[0], dtype=np.int32)[cell_is_near_bdy] + + +def import_firedrake_mesh(fdrake_mesh, near_bdy=None): + """ + :param fdrake_mesh: an instance of :mod:`firedrake`'s mesh, i.e. + of type :class:`firedrake.mesh.MeshGeometry`. + + :param near_bdy: If *None* does nothing. Otherwise should be a + boundary id. In this case, only cell ids + with >= 1 vertex on the given bdy id are used. + + :returns: A :class:`FiredrakeMeshGeometryImporter` object which + is created appropriately from :param:`fdrake_mesh` + + :raises TypeError: if :param:`fdrake_mesh` is of the wrong type + """ + # {{{ Make sure have an initialized firedrake mesh + + from firedrake.mesh import MeshGeometry, MeshTopology + if not isinstance(fdrake_mesh, MeshGeometry): + if isinstance(fdrake_mesh, MeshTopology): + raise TypeError(":param:`fdrake_mesh` must have an associated" + " geometry, but is of type MeshTopology") + raise TypeError(":param:`fdrake_mesh` must be of type" + "`firedrake.mesh.MeshGeometry`, not %s " + % type(fdrake_mesh)) + + fdrake_mesh.init() + + # }}} + + # {{{ Use the coordinates function space to built an importer + + coords_fspace = fdrake_mesh.coordinates.function_space() + cells_to_use = None + if near_bdy is not None: + cells_to_use = _compute_cells_near_bdy(fdrake_mesh, near_bdy) + + from meshmode.interop.FInAT.lagrange_element import \ + FinatLagrangeElementImporter + from meshmode.interop.firedrake.mesh_topology import \ + FiredrakeMeshTopologyImporter + topology_importer = FiredrakeMeshTopologyImporter(fdrake_mesh, + cells_to_use=cells_to_use) + finat_elt_importer = FinatLagrangeElementImporter(coords_fspace.finat_element) + + from meshmode.interop.firedrake.function_space_coordless import \ + FiredrakeFunctionSpaceImporter, FiredrakeCoordinatelessFunctionImporter + + coords_fspace_importer = FiredrakeFunctionSpaceImporter(coords_fspace, + topology_importer, + finat_elt_importer) + coordinates_importer = \ + FiredrakeCoordinatelessFunctionImporter(fdrake_mesh.coordinates, + coords_fspace_importer) + + # }}} + + # FIXME Allow for normals and no_normals_warn? as in the constructor + # for FiredrakeMeshGeometryImporter s + from meshmode.interop.firedrake.mesh_geometry import \ + FiredrakeMeshGeometryImporter + return FiredrakeMeshGeometryImporter(fdrake_mesh, coordinates_importer) + + +def import_firedrake_function_space(cl_ctx, fdrake_fspace, mesh_importer): + """ + Builds a + :class:`FiredrakeWithGeometryImporter` built from the given + :mod:`firedrake` function space :param:`fdrake_fspace` + + :param cl_ctx: A pyopencl computing context. This input + is not checked. + + :param:`fdrake_fspace` An instance of class :mod:`firedrake`'s + :class:`WithGeometry` class, representing a function + space defined on a concrete mesh + + :param mesh_importer: An instance of class + :class:`FiredrakeMeshGeometryImporter` defined on the + mesh underlying :param:`fdrake_fspace`. + + :returns: An instance of class + :mod :`meshmode.interop.firedrake.function_space` + :class:`FiredrakeWithGeometryImporter` built from the given + :param:`fdrake_fspace` + + :raises TypeError: If any input is the wrong type + :raises ValueError: If :param:`mesh_importer` is built on a mesh + different from the one underlying :param:`fdrake_fspace`. + """ + # {{{ Input checking + + from firedrake.functionspaceimpl import WithGeometry + if not isinstance(fdrake_fspace, WithGeometry): + raise TypeError(":param:`fdrake_fspace` must be of type " + ":class:`firedrake.functionspaceimpl.WithGeometry`, " + "not %s " % type(fdrake_fspace)) + + from meshmode.interop.firedrake.mesh_geometry import \ + FiredrakeMeshGeometryImporter + if not isinstance(mesh_importer, FiredrakeMeshGeometryImporter): + raise TypeError(":param:`mesh_importer` must be of type " + ":class:`FiredrakeMeshGeometryImporter` not `%s`." + % type(mesh_importer)) + + if mesh_importer.data != fdrake_fspace.mesh(): + raise ValueError("``mesh_importer.data`` and ``fdrake_fspace.mesh()`` " + "must be identical") + + # }}} + + # {{{ Make an importer for the topological function space + + from meshmode.interop.FInAT import FinatLagrangeElementImporter + from meshmode.interop.firedrake.function_space_coordless import \ + FiredrakeFunctionSpaceImporter + mesh_importer.init(cl_ctx) + finat_elt_importer = FinatLagrangeElementImporter(fdrake_fspace.finat_elt) + topological_importer = FiredrakeFunctionSpaceImporter(fdrake_fspace, + mesh_importer, + finat_elt_importer) + + # }}} + + # now we can make the full importer + from meshmode.interop.firedrake.function_space import \ + FiredrakeWithGeometryImporter + return FiredrakeWithGeometryImporter(cl_ctx, + fdrake_fspace, + topological_importer, + mesh_importer) + +# }}} + + +class FromFiredrakeConnection: + """ + Creates a method of transporting functions on a Firedrake mesh + back and forth between firedrake and meshmode + + .. attribute:: with_geometry_importer + + An instance of class :class:`FiredrakeWithGeometryImporter` + that converts the firedrake function space into + meshmode and allows for field conversion. + """ def __init__(self, cl_ctx, fdrake_function_space): - pass + """ + :param cl_ctx: A computing context + :param fdrake_function_space: A firedrake function space (an instance of + class :class:`WithGeometry`) + """ + mesh_importer = import_firedrake_mesh(fdrake_function_space.mesh()) + self.with_geometry_importer = \ + import_firedrake_function_space(cl_ctx, + fdrake_function_space, + mesh_importer) + + def from_function_space(self): + """ + :returns: the firedrake function space this object was created from + """ + return self.with_geometry_importer.data + + def to_factory(self): + """ + :returns: An InterpolatoryQuadratureSimplexGroupFactory + of the appropriate degree to use + """ + return self.with_geometry_importer.factory() + + def to_discr(self): + """ + :returns: A meshmode discretization corresponding to the + firedrake function space + """ + return self.with_geometry_importer.discretization() + + def from_firedrake(self, fdrake_function): + """ + Convert a firedrake function on this function space to a numpy + array + + :param queue: A CommandQueue + :param fdrake_function: A firedrake function on the function space + of this connection + + :returns: A numpy array holding the data of this function as + a field for the corresponding meshmode mesh + """ + return self.with_geometry_importer.convert_function(fdrake_function) + + def from_meshmode(self, field, fdrake_function): + """ + Store a numpy array holding a field on the meshmode mesh into a + firedrake function + + :param field: A numpy array representing a field on the meshmode version + of this mesh + :param fdrake_function: The firedrake function to store the data in + """ + from meshmode.interop.firedrake.function import \ + FiredrakeFunctionImporter + importer = FiredrakeFunctionImporter(fdrake_function, + self.with_geometry_importer) + importer.set_from_field(field) -- GitLab From 947668b8fe18ba5d61a4071aaaf366f4ac6dee81 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Mon, 1 Jun 2020 16:57:00 -0500 Subject: [PATCH 019/221] Fixed copyrights --- meshmode/interop/FInAT/__init__.py | 2 +- meshmode/interop/FInAT/lagrange_element.py | 22 +++++++++++++++++++ meshmode/interop/__init__.py | 2 +- meshmode/interop/fiat/__init__.py | 2 +- meshmode/interop/fiat/simplex_cell.py | 22 +++++++++++++++++++ meshmode/interop/firedrake/__init__.py | 2 +- meshmode/interop/firedrake/function.py | 22 +++++++++++++++++++ meshmode/interop/firedrake/function_space.py | 22 +++++++++++++++++++ .../firedrake/function_space_coordless.py | 22 +++++++++++++++++++ .../firedrake/function_space_shared_data.py | 22 +++++++++++++++++++ meshmode/interop/firedrake/mesh_geometry.py | 22 +++++++++++++++++++ meshmode/interop/firedrake/mesh_topology.py | 22 +++++++++++++++++++ 12 files changed, 180 insertions(+), 4 deletions(-) diff --git a/meshmode/interop/FInAT/__init__.py b/meshmode/interop/FInAT/__init__.py index 3708afa4..a79ad2ae 100644 --- a/meshmode/interop/FInAT/__init__.py +++ b/meshmode/interop/FInAT/__init__.py @@ -1,4 +1,4 @@ -__copyright__ = "Copyright (C) 2014 Andreas Kloeckner" +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/meshmode/interop/FInAT/lagrange_element.py b/meshmode/interop/FInAT/lagrange_element.py index ce71d6b7..6a80ed9f 100644 --- a/meshmode/interop/FInAT/lagrange_element.py +++ b/meshmode/interop/FInAT/lagrange_element.py @@ -1,3 +1,25 @@ +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" + +__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. +""" + import numpy as np import numpy.linalg as la import six diff --git a/meshmode/interop/__init__.py b/meshmode/interop/__init__.py index bd6d9257..089e4471 100644 --- a/meshmode/interop/__init__.py +++ b/meshmode/interop/__init__.py @@ -1,4 +1,4 @@ -__copyright__ = "Copyright (C) 2014 Andreas Kloeckner" +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/meshmode/interop/fiat/__init__.py b/meshmode/interop/fiat/__init__.py index 3536a1ea..57a42bfe 100644 --- a/meshmode/interop/fiat/__init__.py +++ b/meshmode/interop/fiat/__init__.py @@ -1,4 +1,4 @@ -__copyright__ = "Copyright (C) 2014 Andreas Kloeckner" +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/meshmode/interop/fiat/simplex_cell.py b/meshmode/interop/fiat/simplex_cell.py index bdcabd83..cf0c1622 100644 --- a/meshmode/interop/fiat/simplex_cell.py +++ b/meshmode/interop/fiat/simplex_cell.py @@ -1,3 +1,25 @@ +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" + +__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. +""" + import numpy as np import numpy.linalg as la diff --git a/meshmode/interop/firedrake/__init__.py b/meshmode/interop/firedrake/__init__.py index 11902b75..47c56e6a 100644 --- a/meshmode/interop/firedrake/__init__.py +++ b/meshmode/interop/firedrake/__init__.py @@ -1,4 +1,4 @@ -__copyright__ = "Copyright (C) 2014 Andreas Kloeckner" +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/meshmode/interop/firedrake/function.py b/meshmode/interop/firedrake/function.py index ce601ec7..14ef3a46 100644 --- a/meshmode/interop/firedrake/function.py +++ b/meshmode/interop/firedrake/function.py @@ -1,3 +1,25 @@ +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" + +__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. +""" + import numpy as np from firedrake import Function diff --git a/meshmode/interop/firedrake/function_space.py b/meshmode/interop/firedrake/function_space.py index 95f4e297..998bed3b 100644 --- a/meshmode/interop/firedrake/function_space.py +++ b/meshmode/interop/firedrake/function_space.py @@ -1,3 +1,25 @@ +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" + +__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 warnings import warn import numpy as np diff --git a/meshmode/interop/firedrake/function_space_coordless.py b/meshmode/interop/firedrake/function_space_coordless.py index 0a4e7bc4..3d785bc9 100644 --- a/meshmode/interop/firedrake/function_space_coordless.py +++ b/meshmode/interop/firedrake/function_space_coordless.py @@ -1,3 +1,25 @@ +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" + +__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 warnings import warn # noqa from meshmode.interop import ExternalImportHandler diff --git a/meshmode/interop/firedrake/function_space_shared_data.py b/meshmode/interop/firedrake/function_space_shared_data.py index c58bbcd1..1682ffdd 100644 --- a/meshmode/interop/firedrake/function_space_shared_data.py +++ b/meshmode/interop/firedrake/function_space_shared_data.py @@ -1,3 +1,25 @@ +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" + +__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. +""" + import numpy as np import numpy.linalg as la from decorator import decorator diff --git a/meshmode/interop/firedrake/mesh_geometry.py b/meshmode/interop/firedrake/mesh_geometry.py index cd5c1d43..715e2336 100644 --- a/meshmode/interop/firedrake/mesh_geometry.py +++ b/meshmode/interop/firedrake/mesh_geometry.py @@ -1,3 +1,25 @@ +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" + +__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 warnings import warn # noqa from collections import defaultdict import numpy as np diff --git a/meshmode/interop/firedrake/mesh_topology.py b/meshmode/interop/firedrake/mesh_topology.py index d4ec055e..1b16df9f 100644 --- a/meshmode/interop/firedrake/mesh_topology.py +++ b/meshmode/interop/firedrake/mesh_topology.py @@ -1,3 +1,25 @@ +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" + +__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 warnings import warn # noqa import numpy as np -- GitLab From 5ed91ce0f3694f4db35c918cd17d6eeece1156e1 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Mon, 1 Jun 2020 18:03:16 -0500 Subject: [PATCH 020/221] renamed to fix flake8 errors --- meshmode/interop/firedrake/mesh_geometry.py | 13 +++++++------ meshmode/interop/firedrake/mesh_topology.py | 19 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/meshmode/interop/firedrake/mesh_geometry.py b/meshmode/interop/firedrake/mesh_geometry.py index 715e2336..735bbc6a 100644 --- a/meshmode/interop/firedrake/mesh_geometry.py +++ b/meshmode/interop/firedrake/mesh_geometry.py @@ -133,13 +133,14 @@ class FiredrakeMeshGeometryImporter(ExternalImportHandler): coordinates_fs_importer = \ self._coordinates_importer.function_space_importer() - V_importer = FiredrakeWithGeometryImporter(cl_ctx, - coordinates_fs, - coordinates_fs_importer, - self) - f = Function(V_importer.data, val=self._coordinates_a.data) + fspace_importer = \ + FiredrakeWithGeometryImporter(cl_ctx, + coordinates_fs, + coordinates_fs_importer, + self) + f = Function(fspace_importer.data, val=self._coordinates_a.data) self._coordinates_function_importer = \ - FiredrakeFunctionImporter(f, V_importer) + FiredrakeFunctionImporter(f, fspace_importer) del self._callback diff --git a/meshmode/interop/firedrake/mesh_topology.py b/meshmode/interop/firedrake/mesh_topology.py index 1b16df9f..3a5d7a2c 100644 --- a/meshmode/interop/firedrake/mesh_topology.py +++ b/meshmode/interop/firedrake/mesh_topology.py @@ -157,39 +157,38 @@ class FiredrakeMeshTopologyImporter(ExternalImportHandler): # TODO... not sure how to get around the private access plex = self.data._plex - # variable names follow a dmplex naming convention for - # cell Start/end and vertex Start/end. - cStart, cEnd = plex.getHeightStratum(0) - vStart, vEnd = plex.getDepthStratum(0) + # dmplex cell Start/end and vertex Start/end. + c_start, c_end = plex.getHeightStratum(0) + v_start, v_end = plex.getDepthStratum(0) # TODO... not sure how to get around the private access to_fd_id = np.vectorize(self.data._cell_numbering.getOffset)( - np.arange(cStart, cEnd, dtype=np.int32)) + np.arange(c_start, c_end, dtype=np.int32)) element_to_neighbors = {} verts_checked = set() # dmplex ids of vertex checked # If using all cells, loop over them all if self.icell_to_fd is None: - range_ = range(cStart, cEnd) + range_ = range(c_start, c_end) # Otherwise, just the ones you're using else: isin = np.isin(to_fd_id, self.icell_to_fd) - range_ = np.arange(cStart, cEnd, dtype=np.int32)[isin] + range_ = np.arange(c_start, c_end, dtype=np.int32)[isin] # For each cell for cell_id in range_: # For each vertex touching the cell (that haven't already seen) for vert_id in plex.getTransitiveClosure(cell_id)[0]: - if vStart <= vert_id < vEnd and vert_id not in verts_checked: + if v_start <= vert_id < v_end and vert_id not in verts_checked: verts_checked.add(vert_id) cells = [] # Record all cells touching that vertex support = plex.getTransitiveClosure(vert_id, useCone=False)[0] for other_cell_id in support: - if cStart <= other_cell_id < cEnd: - cells.append(to_fd_id[other_cell_id - cStart]) + if c_start <= other_cell_id < c_end: + cells.append(to_fd_id[other_cell_id - c_start]) # If only using some cells, clean out extraneous ones # and relabel them to new id -- GitLab From 4e97cc81220e1ad1ed30baa80e0696261a96ee8f Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Mon, 1 Jun 2020 18:53:13 -0500 Subject: [PATCH 021/221] Added some docs about interop --- doc/images/firedrake_mesh_design.gv | 25 ++++++++ doc/images/firedrake_mesh_design.png | Bin 0 -> 43833 bytes doc/index.rst | 1 + doc/interop.rst | 91 +++++++++++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100755 doc/images/firedrake_mesh_design.gv create mode 100755 doc/images/firedrake_mesh_design.png create mode 100644 doc/interop.rst diff --git a/doc/images/firedrake_mesh_design.gv b/doc/images/firedrake_mesh_design.gv new file mode 100755 index 00000000..b5465390 --- /dev/null +++ b/doc/images/firedrake_mesh_design.gv @@ -0,0 +1,25 @@ +// created with graphviz2.38 dot + +digraph{ + // NODES + + top [label="Topological\nMesh"]; + ref [label="Reference\nElement"]; + fspace [label="Function Space"]; + coordless [label="Coordinateless\nFunction"]; + geo [label="Geometric\nMesh"]; + withgeo [label="With\nGeometry"]; + + // EDGES + + top -> fspace; + ref -> fspace; + + fspace -> coordless; + + top -> geo; + coordless -> geo [label="Mesh\nCoordinates"]; + + fspace -> withgeo; + geo -> withgeo; +} diff --git a/doc/images/firedrake_mesh_design.png b/doc/images/firedrake_mesh_design.png new file mode 100755 index 0000000000000000000000000000000000000000..f394f9e7f3d56138ff15850311e6bcd6dcdd7bbe GIT binary patch literal 43833 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

>8izt!`;($TvHB=k8rvy|e?> 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. + ``fd2mm`` mimics this firedrake design style. + + In short, it is the idea + that every function space should have a mesh, and the coordinates of the mesh + should be representable as a function on that same mesh, which must live + on some function space on the mesh... etc. + Under the hood, we divide between topological and geometric objects, + roughly as so + + (1) A reference element defined using :mod:`FInAT` and :mod:`fiat` + is used to define what meshmode calls the unit nodes and unit + vertices. It is worth noting that :mod:`firedrake` does + not require a positive orientation of elements and that its + reference traingle is different than specified in :mod:`modepy`. + + (2) A ``MeshTopology`` which holds information about connectivity + and other topological properties, but nothing about geometry/coordinates + etc. + + (3) A ``FunctionSpace`` created from a ``FInAT`` element and a + ``MeshTopology`` which allows us to define functions + mapping the nodes (defined by the ``FInAT`` element) of + each element in the ``MeshTopology`` to some values + + (4) A ``CoordinatelessFunction`` (in the sense that its + *domain* has no coordinates) which is a function + in a ``FunctionSpace`` + + (5) A ``MeshGeometry`` created from a ``FunctionSpace`` + and a ``CoordinatelessFunction`` in that ``FunctionSpace`` + which maps each dof to its geometric coordinates. + + (6) A ``WithGeometry`` which is a ``FunctionSpace`` together + with a ``MeshGeometry``. This is the object returned + usually returned to the user by a call + to the :mod:`firedrake` function :func:`FunctionSpace`. + + (7) A ``Function`` is defined on a ``WithGeometry`` + + Thus, by the coordinates of a mesh geometry we mean + + (a) On the hidden back-end: a ``CoordinatelessFunction`` *f* on some function + space defined only on the mesh topology + (b) On the front-end: A ``Function`` with the values of *f* but defined + on a ``WithGeometry`` created from the ``FunctionSpace`` *f* lives in + and the ``MeshGeometry`` *f* defines. + + Basically, it's this picture (where a->b if b depends on a) + + .. warning:: + + In general, the ``FunctionSpace`` of the coordinates function + of a ``WithGeometry`` may not be the same ``FunctionSpace`` + as for functions which live in the ``WithGeometry``. + This picture + only shows how the class definitions depend on each other. + + + .. image:: images/firedrake_mesh_design.png -- GitLab From 2167d8d98c1b6200a9a67cefe3ba8d7bee06393f Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Tue, 2 Jun 2020 11:37:11 -0500 Subject: [PATCH 022/221] Finished adding firedrake mesh design to the docs --- doc/interop.rst | 133 +++++++++++++++++++++++++----------------------- 1 file changed, 68 insertions(+), 65 deletions(-) diff --git a/doc/interop.rst b/doc/interop.rst index f621b49b..41ad3b63 100644 --- a/doc/interop.rst +++ b/doc/interop.rst @@ -19,73 +19,76 @@ FInAT Firedrake --------- +TODO include this + + +Implementation Details +---------------------- + Firedrake Function Space Design ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. note:: - *(If you are just an end-user, you probably don't need to know this)* - - In firedrake, meshes and function spaces have a close relationship. - In particular, due to some structure described in this - `firedrake pull request `_. - ``fd2mm`` mimics this firedrake design style. - - In short, it is the idea - that every function space should have a mesh, and the coordinates of the mesh - should be representable as a function on that same mesh, which must live - on some function space on the mesh... etc. - Under the hood, we divide between topological and geometric objects, - roughly as so - - (1) A reference element defined using :mod:`FInAT` and :mod:`fiat` - is used to define what meshmode calls the unit nodes and unit - vertices. It is worth noting that :mod:`firedrake` does - not require a positive orientation of elements and that its - reference traingle is different than specified in :mod:`modepy`. - - (2) A ``MeshTopology`` which holds information about connectivity - and other topological properties, but nothing about geometry/coordinates - etc. - - (3) A ``FunctionSpace`` created from a ``FInAT`` element and a - ``MeshTopology`` which allows us to define functions - mapping the nodes (defined by the ``FInAT`` element) of - each element in the ``MeshTopology`` to some values - - (4) A ``CoordinatelessFunction`` (in the sense that its - *domain* has no coordinates) which is a function - in a ``FunctionSpace`` - - (5) A ``MeshGeometry`` created from a ``FunctionSpace`` - and a ``CoordinatelessFunction`` in that ``FunctionSpace`` - which maps each dof to its geometric coordinates. - - (6) A ``WithGeometry`` which is a ``FunctionSpace`` together - with a ``MeshGeometry``. This is the object returned - usually returned to the user by a call - to the :mod:`firedrake` function :func:`FunctionSpace`. - - (7) A ``Function`` is defined on a ``WithGeometry`` - - Thus, by the coordinates of a mesh geometry we mean - - (a) On the hidden back-end: a ``CoordinatelessFunction`` *f* on some function - space defined only on the mesh topology - (b) On the front-end: A ``Function`` with the values of *f* but defined - on a ``WithGeometry`` created from the ``FunctionSpace`` *f* lives in - and the ``MeshGeometry`` *f* defines. - - Basically, it's this picture (where a->b if b depends on a) - - .. warning:: - - In general, the ``FunctionSpace`` of the coordinates function - of a ``WithGeometry`` may not be the same ``FunctionSpace`` - as for functions which live in the ``WithGeometry``. - This picture - only shows how the class definitions depend on each other. - - - .. image:: images/firedrake_mesh_design.png +In firedrake, meshes and function spaces have a close relationship. +In particular, due to some structure described in this +`firedrake pull request `_. +``fd2mm`` mimics this firedrake design style. + +In short, it is the idea +that every function space should have a mesh, and the coordinates of the mesh +should be representable as a function on that same mesh, which must live +on some function space on the mesh... etc. +Under the hood, we divide between topological and geometric objects, +roughly as so + +(1) A reference element defined using :mod:`FInAT` and :mod:`fiat` + is used to define what meshmode calls the unit nodes and unit + vertices. It is worth noting that :mod:`firedrake` does + not require a positive orientation of elements and that its + reference traingle is different than specified in :mod:`modepy`. + +(2) A ``MeshTopology`` which holds information about connectivity + and other topological properties, but nothing about geometry/coordinates + etc. + +(3) A ``FunctionSpace`` created from a ``FInAT`` element and a + ``MeshTopology`` which allows us to define functions + mapping the nodes (defined by the ``FInAT`` element) of + each element in the ``MeshTopology`` to some values + +(4) A ``CoordinatelessFunction`` (in the sense that its + *domain* has no coordinates) which is a function + in a ``FunctionSpace`` + +(5) A ``MeshGeometry`` created from a ``FunctionSpace`` + and a ``CoordinatelessFunction`` in that ``FunctionSpace`` + which maps each dof to its geometric coordinates. + +(6) A ``WithGeometry`` which is a ``FunctionSpace`` together + with a ``MeshGeometry``. This is the object returned + usually returned to the user by a call + to the :mod:`firedrake` function :func:`FunctionSpace`. + +(7) A ``Function`` is defined on a ``WithGeometry`` + +Thus, by the coordinates of a mesh geometry we mean + +(a) On the hidden back-end: a ``CoordinatelessFunction`` *f* on some function + space defined only on the mesh topology +(b) On the front-end: A ``Function`` with the values of *f* but defined + on a ``WithGeometry`` created from the ``FunctionSpace`` *f* lives in + and the ``MeshGeometry`` *f* defines. + +Basically, it's this picture (where a->b if b depends on a) + +.. warning:: + + In general, the ``FunctionSpace`` of the coordinates function + of a ``WithGeometry`` may not be the same ``FunctionSpace`` + as for functions which live in the ``WithGeometry``. + This picture + only shows how the class definitions depend on each other. + + +.. image:: images/firedrake_mesh_design.png -- GitLab From 5d9f5d8eeb6652a15ac3722b9e48e5d7c8e1a776 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Tue, 2 Jun 2020 13:09:44 -0500 Subject: [PATCH 023/221] Some bug fixes (mostly from fd2mm conversion) revealed by testing --- meshmode/interop/firedrake/__init__.py | 3 ++- meshmode/interop/firedrake/function_space.py | 6 +++--- .../firedrake/function_space_coordless.py | 16 ++++++++++------ .../firedrake/function_space_shared_data.py | 8 ++++---- meshmode/interop/firedrake/mesh_geometry.py | 4 ++-- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/meshmode/interop/firedrake/__init__.py b/meshmode/interop/firedrake/__init__.py index 47c56e6a..33daeb39 100644 --- a/meshmode/interop/firedrake/__init__.py +++ b/meshmode/interop/firedrake/__init__.py @@ -163,7 +163,8 @@ def import_firedrake_function_space(cl_ctx, fdrake_fspace, mesh_importer): from meshmode.interop.firedrake.function_space_coordless import \ FiredrakeFunctionSpaceImporter mesh_importer.init(cl_ctx) - finat_elt_importer = FinatLagrangeElementImporter(fdrake_fspace.finat_elt) + finat_elt_importer = \ + FinatLagrangeElementImporter(fdrake_fspace.finat_element) topological_importer = FiredrakeFunctionSpaceImporter(fdrake_fspace, mesh_importer, finat_elt_importer) diff --git a/meshmode/interop/firedrake/function_space.py b/meshmode/interop/firedrake/function_space.py index 998bed3b..85b3d6c2 100644 --- a/meshmode/interop/firedrake/function_space.py +++ b/meshmode/interop/firedrake/function_space.py @@ -88,7 +88,7 @@ class FiredrakeWithGeometryImporter(ExternalImportHandler): mesh_order = mesh_importer.data.coordinates.\ function_space().finat_element.degree - if mesh_order > self.data.degree: + if mesh_order > self.data.finat_element.degree: warn("Careful! When the mesh order is higher than the element" " order. Conversion MIGHT work, but maybe not..." " To be honest I really don't know.") @@ -98,7 +98,7 @@ class FiredrakeWithGeometryImporter(ExternalImportHandler): self._resampling_mat_mm2fd = None def __getattr__(self, attr): - return getattr(self._topology_a, attr) + return getattr(self._topology_importer, attr) def mesh_importer(self): return self._mesh_importer @@ -118,7 +118,7 @@ class FiredrakeWithGeometryImporter(ExternalImportHandler): if self._resampling_mat_fd2mm is None: element_grp = self.discretization().groups[0] self._resampling_mat_fd2mm = \ - self.finat_element_a.make_resampling_matrix(element_grp) + self.finat_element_importer.make_resampling_matrix(element_grp) self._resampling_mat_mm2fd = np.linalg.inv(self._resampling_mat_fd2mm) diff --git a/meshmode/interop/firedrake/function_space_coordless.py b/meshmode/interop/firedrake/function_space_coordless.py index 3d785bc9..e6fdec6c 100644 --- a/meshmode/interop/firedrake/function_space_coordless.py +++ b/meshmode/interop/firedrake/function_space_coordless.py @@ -82,6 +82,10 @@ class FiredrakeFunctionSpaceImporter(ExternalImportHandler): a different mesh or finat element than the provided :param:`function_space` is built on. """ + # We want to ignore any geomery + function_space = function_space.topological + mesh_importer = mesh_importer.topological_importer + # {{{ Some type-checking from firedrake.functionspaceimpl import FunctionSpace, WithGeometry @@ -95,7 +99,7 @@ class FiredrakeFunctionSpaceImporter(ExternalImportHandler): raise TypeError(":param:`mesh_importer` must be either *None* " "or of type :class:`meshmode.interop.firedrake." "FiredrakeMeshTopologyImporter`") - if not function_space.mesh() == mesh_importer.data: + if not function_space.mesh().topological == mesh_importer.data: raise ValueError(":param:`mesh_importer`'s *data* attribute " "must be the same mesh as returned by " ":param:`function_space`'s *mesh()* method.") @@ -114,8 +118,7 @@ class FiredrakeFunctionSpaceImporter(ExternalImportHandler): # }}} - # We want to ignore any geometry and then finish initialization - function_space = function_space.topological + # finish initialization super(FiredrakeFunctionSpaceImporter, self).__init__(function_space) self._mesh_importer = mesh_importer @@ -170,6 +173,10 @@ class FiredrakeCoordinatelessFunctionImporter(ExternalImportHandler): importer for a firedrake function space which is not identical to ``function.topological.function_space()`` """ + # Throw out geometric information if there is any + function = function.topological + function_space_importer = function_space_importer.topological_importer + # {{{ Some type-checking from firedrake.function import Function, CoordinatelessFunction @@ -189,9 +196,6 @@ class FiredrakeCoordinatelessFunctionImporter(ExternalImportHandler): "attribute and ``function.function_space()`` " "must be identical.") - function = function.topological - function_space_importer = function_space_importer.topological_importer - super(FiredrakeCoordinatelessFunctionImporter, self).__init__(function) self._function_space_importer = function_space_importer diff --git a/meshmode/interop/firedrake/function_space_shared_data.py b/meshmode/interop/firedrake/function_space_shared_data.py index 1682ffdd..9bd03f27 100644 --- a/meshmode/interop/firedrake/function_space_shared_data.py +++ b/meshmode/interop/firedrake/function_space_shared_data.py @@ -216,15 +216,15 @@ class FiredrakeFunctionSpaceDataImporter(ExternalImportHandler): Note that :param:`mesh_importer` and :param:`finat_element_importer` are used for checking """ - if mesh_importer.topological_a == mesh_importer: + if mesh_importer.topological_importer == mesh_importer: raise TypeError(":param:`mesh_importer` is a " "FiredrakeMeshTopologyImporter, but " " must be a FiredrakeMeshGeometryImporter") - importer = (mesh_importer.importer(), finat_element_importer.importer()) + importer = (mesh_importer.data, finat_element_importer.data) super(FiredrakeFunctionSpaceDataImporter, self).__init__(importer) - self._fspace_data = FunctionSpaceData(mesh_importer.importer(), - finat_element_importer.importer()) + self._fspace_data = FunctionSpaceData(mesh_importer.data, + finat_element_importer.data) self._cl_ctx = cl_ctx self._mesh_importer = mesh_importer diff --git a/meshmode/interop/firedrake/mesh_geometry.py b/meshmode/interop/firedrake/mesh_geometry.py index 735bbc6a..216cb7b1 100644 --- a/meshmode/interop/firedrake/mesh_geometry.py +++ b/meshmode/interop/firedrake/mesh_geometry.py @@ -91,7 +91,7 @@ class FiredrakeMeshGeometryImporter(ExternalImportHandler): fspace_importer = coordinates_importer.function_space_importer() topology_importer = fspace_importer.mesh_importer() - if topology_importer != mesh.topology: + if topology_importer.data != mesh.topology: raise ValueError("Topology :param:`coordinates` lives on must be " "the same " "topology that :param:`mesh` lives on") @@ -138,7 +138,7 @@ class FiredrakeMeshGeometryImporter(ExternalImportHandler): coordinates_fs, coordinates_fs_importer, self) - f = Function(fspace_importer.data, val=self._coordinates_a.data) + f = Function(fspace_importer.data, val=self._coordinates_importer.data) self._coordinates_function_importer = \ FiredrakeFunctionImporter(f, fspace_importer) -- GitLab From c6099406caa241b8a2b39c16701dc7aa3e1d0322 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Tue, 2 Jun 2020 13:10:07 -0500 Subject: [PATCH 024/221] Added an initial test which makes sure conversion is idempotent --- test/test_firedrake_interop.py | 121 +++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 test/test_firedrake_interop.py diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py new file mode 100644 index 00000000..9661a548 --- /dev/null +++ b/test/test_firedrake_interop.py @@ -0,0 +1,121 @@ +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" + +__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. +""" + +import pyopencl as cl +import numpy as np + +from pyopencl.tools import ( # noqa + pytest_generate_tests_for_pyopencl + as pytest_generate_tests) + +from meshmode.interop.firedrake import FromFiredrakeConnection + +import pytest + +import logging +logger = logging.getLogger(__name__) + +# skip testing this module if cannot import firedrake +firedrake = pytest.importorskip("firedrake") + +from firedrake import ( + UnitIntervalMesh, UnitSquareMesh, UnitCubeMesh, + FunctionSpace, VectorFunctionSpace, Function, + SpatialCoordinate, sin, exp, pi, as_vector) + + +CLOSE_ATOL = 10**-12 + + +@pytest.fixture(params=[1, 2, 3], ids=["1D", "2D", "3D"]) +def fdrake_mesh(request): + dim = request.param + if dim == 1: + return UnitIntervalMesh(100) + if dim == 2: + return UnitSquareMesh(10, 10) + if dim == 3: + return UnitCubeMesh(5, 5, 5) + return None + + +@pytest.fixture(params=[1, 2, 3], ids=["P^1", "P^2", "P^3"]) +def fdrake_degree(request): + return request.param + + +# {{{ Idempotency tests fd->mm->fd and (fd->)mm->fd->mm for connection + +def check_idempotency(fdrake_connection, fdrake_function): + """ + Make sure fd->mm->fd and mm->fd->mm are identity for DG spaces + """ + fdrake_fspace = fdrake_connection.from_function_space() + + # Test for idempotency fd->mm->fd + mm_field = fdrake_connection.from_firedrake(fdrake_function) + fdrake_function_copy = Function(fdrake_fspace) + fdrake_connection.from_meshmode(mm_field, fdrake_function_copy) + + print(np.sum(np.abs(fdrake_function.dat.data - fdrake_function_copy.dat.data))) + print(type(fdrake_function.dat.data)) + np.testing.assert_allclose(fdrake_function_copy.dat.data, + fdrake_function.dat.data, + atol=CLOSE_ATOL) + + # Test for idempotency (fd->)mm->fd->mm + mm_field_copy = fdrake_connection.from_firedrake(fdrake_function_copy) + np.testing.assert_allclose(mm_field_copy, mm_field, atol=CLOSE_ATOL) + + +def test_scalar_idempotency(ctx_getter, fdrake_mesh, fdrake_degree): + """ + Make sure fd->mm->fd and mm->fd->mm are identity for scalar DG spaces + """ + fdrake_fspace = FunctionSpace(fdrake_mesh, 'DG', fdrake_degree) + + # Make a function with unique values at each node + fdrake_unique = Function(fdrake_fspace) + fdrake_unique.dat.data[:] = np.arange(fdrake_unique.dat.data.shape[0]) + + # test idempotency + cl_ctx = ctx_getter() + fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) + check_idempotency(fdrake_connection, fdrake_unique) + + +def test_vector_idempotency(ctx_getter, fdrake_mesh, fdrake_degree): + """ + Make sure fd->mm->fd and mm->fd->mm are identity for vector DG spaces + """ + fdrake_vfspace = VectorFunctionSpace(fdrake_mesh, 'DG', fdrake_degree) + + # Make a function with unique values at each node + xx = SpatialCoordinate(fdrake_vfspace.mesh()) + fdrake_unique = Function(fdrake_vfspace).interpolate(xx) + + # test idempotency + cl_ctx = ctx_getter() + fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_vfspace) + check_idempotency(fdrake_connection, fdrake_unique) + +# }}} -- GitLab From 30a19e717711f695f90bf86207d4a19983b32cf5 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Tue, 2 Jun 2020 13:14:05 -0500 Subject: [PATCH 025/221] Some flake8 fixes, ctx_getter -> ctx_factory --- test/test_firedrake_interop.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 9661a548..c1f94b39 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -20,7 +20,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import pyopencl as cl import numpy as np from pyopencl.tools import ( # noqa @@ -40,7 +39,7 @@ firedrake = pytest.importorskip("firedrake") from firedrake import ( UnitIntervalMesh, UnitSquareMesh, UnitCubeMesh, FunctionSpace, VectorFunctionSpace, Function, - SpatialCoordinate, sin, exp, pi, as_vector) + SpatialCoordinate) CLOSE_ATOL = 10**-12 @@ -87,7 +86,7 @@ def check_idempotency(fdrake_connection, fdrake_function): np.testing.assert_allclose(mm_field_copy, mm_field, atol=CLOSE_ATOL) -def test_scalar_idempotency(ctx_getter, fdrake_mesh, fdrake_degree): +def test_scalar_idempotency(ctx_factory, fdrake_mesh, fdrake_degree): """ Make sure fd->mm->fd and mm->fd->mm are identity for scalar DG spaces """ @@ -98,12 +97,12 @@ def test_scalar_idempotency(ctx_getter, fdrake_mesh, fdrake_degree): fdrake_unique.dat.data[:] = np.arange(fdrake_unique.dat.data.shape[0]) # test idempotency - cl_ctx = ctx_getter() + cl_ctx = ctx_factory() fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) check_idempotency(fdrake_connection, fdrake_unique) -def test_vector_idempotency(ctx_getter, fdrake_mesh, fdrake_degree): +def test_vector_idempotency(ctx_factory, fdrake_mesh, fdrake_degree): """ Make sure fd->mm->fd and mm->fd->mm are identity for vector DG spaces """ @@ -114,7 +113,7 @@ def test_vector_idempotency(ctx_getter, fdrake_mesh, fdrake_degree): fdrake_unique = Function(fdrake_vfspace).interpolate(xx) # test idempotency - cl_ctx = ctx_getter() + cl_ctx = ctx_factory() fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_vfspace) check_idempotency(fdrake_connection, fdrake_unique) -- GitLab From d2871c40a9f0fe36cb6a1e34a049d52e6a282184 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Tue, 2 Jun 2020 13:47:46 -0500 Subject: [PATCH 026/221] Removed dependency on abc --- meshmode/interop/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/meshmode/interop/__init__.py b/meshmode/interop/__init__.py index 089e4471..1421458c 100644 --- a/meshmode/interop/__init__.py +++ b/meshmode/interop/__init__.py @@ -20,8 +20,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from abc import ABC - __doc__ = """ Development Interface --------------------- @@ -33,7 +31,7 @@ Development Interface # {{{ Generic, most abstract class for transporting meshmode <-> external -class ExternalDataHandler(ABC): +class ExternalDataHandler: """ A data handler takes data from meshmode and facilitates its use in another package or the reverse: takes data from another package -- GitLab From 7af97f693866c5838037230daf8dd6763ff1e608 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Mon, 22 Jun 2020 12:56:16 -0500 Subject: [PATCH 027/221] Added a test to check some basic consistency --- test/test_firedrake_interop.py | 48 ++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index c1f94b39..eec00304 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -62,6 +62,54 @@ def fdrake_degree(request): return request.param +# {{{ Basic conversion tests + +def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): + """ + While nodes may change, vertex conversion should be *identical* up to + reordering, ensure this is the case. Also ensure the + meshes have the same basic properties and the function space/discretization + agree across firedrake vs meshmode + """ + # get fdrake_verts (shaped like (nverts, dim)) + fdrake_verts = fdrake_mesh.coordinates.dat.data + if fdrake_mesh.geometric_dimension() == 1: + fdrake_verts = fdrake_verts[:, np.newaxis] + + # Get meshmode vertices (shaped like (dim, nverts)) + fdrake_fspace = FunctionSpace(fdrake_mesh, 'DG', fdrake_degree) + cl_ctx = ctx_factory() + fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) + to_discr = fdrake_connection.to_discr() + meshmode_verts = to_discr.mesh.vertices + + # Ensure the meshmode mesh has one group and make sure both + # meshes agree on some basic properties + assert len(to_discr.mesh.groups) == 1 + fdrake_mesh_fspace = fdrake_mesh.coordinates.function_space() + fdrake_mesh_order = fdrake_mesh_fspace.finat_element.degree + assert to_discr.mesh.groups[0].order == fdrake_mesh_order + assert to_discr.mesh.groups[0].nelements == fdrake_mesh.num_cells() + assert to_discr.mesh.nvertices == fdrake_mesh.num_vertices() + + # Ensure that the vertex sets are identical up to reordering + # Nb: I got help on this from stack overflow: + # https://stackoverflow.com/questions/38277143/sort-2d-numpy-array-lexicographically # noqa: E501 + lex_sorted_mm_verts = meshmode_verts[:, np.lexsort(meshmode_verts)] + lex_sorted_fdrake_verts = fdrake_verts[np.lexsort(fdrake_verts.T)] + np.testing.assert_array_equal(lex_sorted_mm_verts, lex_sorted_fdrake_verts.T) + + # Ensure the discretization and the firedrake function space agree on + # some basic properties + finat_elt = fdrake_fspace.finat_element + assert len(to_discr.groups) == 1 + assert to_discr.groups[0].order == finat_elt.degree + assert to_discr.groups[0].nunit_nodes == finat_elt.space_dimension() + assert to_discr.nnodes == fdrake_fspace.node_count + +# }}} + + # {{{ Idempotency tests fd->mm->fd and (fd->)mm->fd->mm for connection def check_idempotency(fdrake_connection, fdrake_function): -- GitLab From 6a7a21e7897977b043331bbd54b654d1e43e5395 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Mon, 22 Jun 2020 12:57:07 -0500 Subject: [PATCH 028/221] Fixed the unit node coordinates routine --- meshmode/interop/FInAT/lagrange_element.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/meshmode/interop/FInAT/lagrange_element.py b/meshmode/interop/FInAT/lagrange_element.py index 6a80ed9f..858bbf01 100644 --- a/meshmode/interop/FInAT/lagrange_element.py +++ b/meshmode/interop/FInAT/lagrange_element.py @@ -59,11 +59,16 @@ class FinatLagrangeElementImporter(ExternalImportHandler): # Check types from finat.fiat_elements import DiscontinuousLagrange, Lagrange - if not isinstance(finat_element, (Lagrange, DiscontinuousLagrange)): + from finat.spectral import GaussLegendre, GaussLobattoLegendre + valid_types = (Lagrange, DiscontinuousLagrange, + GaussLegendre, GaussLobattoLegendre) + if not isinstance(finat_element, valid_types): raise TypeError(":param:`finat_element` must be of type" - " `finat.fiat_elements.Lagrange` or" - " `finat.fiat_elements.DiscontinuousLagrange`", - " not type `%s`" % type(finat_element)) + " `finat.fiat_elements.Lagrange`," + " `finat.fiat_elements.DiscontinuousLagrange`," + " or `finat.spectral.GaussLegendre` or" + " `finat.spectral.GaussLobattoLegendre` in 1D," + " but is instead of type `%s`" % type(finat_element)) if finat_element.mapping != 'affine': raise ValueError("FInAT element must use affine mappings" @@ -90,6 +95,9 @@ class FinatLagrangeElementImporter(ExternalImportHandler): node_nr_to_coords = {} unit_vertex_indices = [] + # FIXME : This works, but is very ad-hoc. It is probably better + # to get permission from the FInAT people to reach into + # the fiat element and get the nodes explicitly # Get unit nodes for dim, element_nrs in six.iteritems( self.data.entity_support_dofs()): @@ -100,7 +108,7 @@ class FinatLagrangeElementImporter(ExternalImportHandler): # Record any new nodes i = 0 for node_nr in node_list: - if node_nr not in node_nr_to_coords: + if node_nr not in node_nr_to_coords and i < len(pts_on_element): node_nr_to_coords[node_nr] = pts_on_element[i] i += 1 # If is a vertex, store the index -- GitLab From 46d9fca3b43a7fcbe210ae3f6147765d54e93b26 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Mon, 22 Jun 2020 14:56:34 -0500 Subject: [PATCH 029/221] flake8 + fixed careless bug by preventing memoized functions from both calling each other --- meshmode/interop/FInAT/lagrange_element.py | 3 +- .../firedrake/function_space_shared_data.py | 1 + meshmode/interop/firedrake/mesh_geometry.py | 80 ++++++++++++------- 3 files changed, 54 insertions(+), 30 deletions(-) diff --git a/meshmode/interop/FInAT/lagrange_element.py b/meshmode/interop/FInAT/lagrange_element.py index 858bbf01..b3d4c50f 100644 --- a/meshmode/interop/FInAT/lagrange_element.py +++ b/meshmode/interop/FInAT/lagrange_element.py @@ -108,7 +108,8 @@ class FinatLagrangeElementImporter(ExternalImportHandler): # Record any new nodes i = 0 for node_nr in node_list: - if node_nr not in node_nr_to_coords and i < len(pts_on_element): + if node_nr not in node_nr_to_coords and \ + i < len(pts_on_element): node_nr_to_coords[node_nr] = pts_on_element[i] i += 1 # If is a vertex, store the index diff --git a/meshmode/interop/firedrake/function_space_shared_data.py b/meshmode/interop/firedrake/function_space_shared_data.py index 9bd03f27..52c0e6d2 100644 --- a/meshmode/interop/firedrake/function_space_shared_data.py +++ b/meshmode/interop/firedrake/function_space_shared_data.py @@ -146,6 +146,7 @@ def reordering_array(mesh_importer, key, fspace_data): (order.shape[0]//nunit_nodes, nunit_nodes) + order.shape[1:]) flip_mat = finat_element_importer.flip_matrix() + print("ORIENT=", mesh_importer._orient) reorder_nodes(mesh_importer.orientations(), new_order, flip_mat, unflip=firedrake_to_meshmode) new_order = new_order.flatten() diff --git a/meshmode/interop/firedrake/mesh_geometry.py b/meshmode/interop/firedrake/mesh_geometry.py index 216cb7b1..8ce88f26 100644 --- a/meshmode/interop/firedrake/mesh_geometry.py +++ b/meshmode/interop/firedrake/mesh_geometry.py @@ -255,50 +255,72 @@ class FiredrakeMeshGeometryImporter(ExternalImportHandler): return self._nodes - def group(self): + def _get_unflipped_group(self): """ Return an instance of :class:meshmode.mesh.SimplexElementGroup` - corresponding to the mesh :attr:`data` + but with elements which are not guaranteed to have a positive + orientation. + + This is important because :meth:`group` requires + the result of :meth:`orientations` to guarantee each + element has positive orientations, and + :meth:`orientations` (usually) needs an unflipped group to compute + the orientations, so we need this function to prevent a recursive + loop of (orientations calling group calling orientations calling...etc.) """ - if self._group is None: + # Cache the group we create since :meth:`group` and :meth:`orientations` + # may both use it. One should note that once :meth:`group` terminates, + # this attribute is deleted (once :attr:`_group` is computed then + # :attr:`_orient` must also have been computed, so we no longer have any + # use for :attr:`_unflipped_group`) + if not hasattr(self, "_unflipped_group"): from meshmode.mesh import SimplexElementGroup - from meshmode.mesh.processing import flip_simplex_element_group fspace_importer = self.coordinates_importer.function_space_importer() finat_element_importer = fspace_importer.finat_element_importer - # IMPORTANT that set :attr:`_group` because - # :meth:`orientations` may call :meth:`group` - self._group = SimplexElementGroup( + self._unflipped_group = SimplexElementGroup( finat_element_importer.data.degree, self.vertex_indices(), self.nodes(), dim=self.cell_dimension(), unit_nodes=finat_element_importer.unit_nodes()) - self._group = flip_simplex_element_group(self.vertices(), self._group, + return self._unflipped_group + + def group(self): + """ + Return an instance of :class:meshmode.mesh.SimplexElementGroup` + corresponding to the mesh :attr:`data` + """ + if self._group is None: + from meshmode.mesh.processing import flip_simplex_element_group + self._group = flip_simplex_element_group(self.vertices(), + self._get_unflipped_group(), self.orientations() < 0) + # We don't need this anymore + del self._unflipped_group return self._group def orientations(self): """ - Return the orientations of the mesh elements: - an array, the *i*th element is > 0 if the *ith* element - is positively oriented, < 0 if negatively oriented - - :param normals: _Only_ used if :param:`mesh` is a 1-surface - embedded in 2-space. In this case, - - If *None* then - all elements are assumed to be positively oriented. - - Else, should be a list/array whose *i*th entry - is the normal for the *i*th element (*i*th - in :param:`mesh`*.coordinate.function_space()*'s - :attribute:`cell_node_list`) - - :param no_normals_warn: If *True*, raises a warning - if :param:`mesh` is a 1-surface embedded in 2-space - and :param:`normals` is *None*. + Return the orientations of the mesh elements: + an array, the *i*th element is > 0 if the *ith* element + is positively oriented, < 0 if negatively oriented + + :param normals: _Only_ used if :param:`mesh` is a 1-surface + embedded in 2-space. In this case, + - If *None* then + all elements are assumed to be positively oriented. + - Else, should be a list/array whose *i*th entry + is the normal for the *i*th element (*i*th + in :param:`mesh`*.coordinate.function_space()*'s + :attribute:`cell_node_list`) + + :param no_normals_warn: If *True*, raises a warning + if :param:`mesh` is a 1-surface embedded in 2-space + and :param:`normals` is *None*. """ if self._orient is None: # compute orientations @@ -311,9 +333,9 @@ class FiredrakeMeshGeometryImporter(ExternalImportHandler): from meshmode.mesh.processing import \ find_volume_mesh_element_group_orientation - orient = \ - find_volume_mesh_element_group_orientation(self.vertices(), - self.group()) + orient = find_volume_mesh_element_group_orientation( + self.vertices(), + self._get_unflipped_group()) if tdim == 1 and gdim == 2: # In this case we have a 1-surface embedded in 2-space @@ -339,8 +361,8 @@ class FiredrakeMeshGeometryImporter(ExternalImportHandler): self._orient = orient #Make sure the mesh fell into one of the cases """ - NOTE : This should be guaranteed by previous checks, - but is here anyway in case of future development. + NOTE : This should be guaranteed by previous checks, + but is here anyway in case of future development. """ assert self._orient is not None -- GitLab From 910303e3e89c29ddeae95b9113d5626060e9e62d Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 23 Jun 2020 10:44:14 -0500 Subject: [PATCH 030/221] Added some function transfer tests --- test/test_firedrake_interop.py | 75 +++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index eec00304..f3337c4a 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -21,6 +21,7 @@ THE SOFTWARE. """ import numpy as np +import pyopencl as cl from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl @@ -39,7 +40,7 @@ firedrake = pytest.importorskip("firedrake") from firedrake import ( UnitIntervalMesh, UnitSquareMesh, UnitCubeMesh, FunctionSpace, VectorFunctionSpace, Function, - SpatialCoordinate) + SpatialCoordinate, Constant) CLOSE_ATOL = 10**-12 @@ -57,17 +58,22 @@ def fdrake_mesh(request): return None +@pytest.fixture(params=["CG", "DG"]) +def fdrake_family(request): + return request.param + + @pytest.fixture(params=[1, 2, 3], ids=["P^1", "P^2", "P^3"]) def fdrake_degree(request): return request.param -# {{{ Basic conversion tests +# {{{ Basic conversion checks for the function space def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): """ While nodes may change, vertex conversion should be *identical* up to - reordering, ensure this is the case. Also ensure the + reordering, ensure this is the case for DG spaces. Also ensure the meshes have the same basic properties and the function space/discretization agree across firedrake vs meshmode """ @@ -110,6 +116,67 @@ def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): # }}} +# {{{ Double check functions are being transported correctly + +def alternating_sum_fd(spat_coord): + """ + Return an expression x1 - x2 + x3 -+... + """ + return sum( + [(-1)**i * spat_coord for i, spat_coord in enumerate(spat_coord)] + ) + + +def alternating_sum_mm(nodes): + """ + Take the *(dim, nnodes)* array nodes and return an array + holding the alternating sum of the coordinates of each node + """ + alternator = np.ones(nodes.shape[0]) + alternator[1::2] *= -1 + return np.matmul(alternator, nodes) + + +# In 1D/2D/3D check constant 1, +# projection to x1, x1/x1+x2/x1+x2+x3, and x1/x1-x2/x1-x2+x3. +# This should show that projection to any coordinate in 1D/2D/3D +# transfers correctly. +test_functions = [ + (lambda spat_coord: Constant(1.0), lambda nodes: np.ones(nodes.shape[1])), + (lambda spat_coord: spat_coord[0], lambda nodes: nodes[0, :]), + (sum, lambda nodes: np.sum(nodes, axis=0)), + (alternating_sum_fd, alternating_sum_mm) +] + + +@pytest.mark.parametrize("fdrake_f_expr,meshmode_f_eval", test_functions) +def test_function_transfer(ctx_factory, + fdrake_mesh, fdrake_family, fdrake_degree, + fdrake_f_expr, meshmode_f_eval): + """ + Make sure creating a function then transporting it is the same + (up to resampling error) as creating a function on the transported + mesh + """ + fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fdrake_degree) + spat_coord = SpatialCoordinate(fdrake_mesh) + + fdrake_f = Function(fdrake_fspace).interpolate(fdrake_f_expr(spat_coord)) + + cl_ctx = ctx_factory() + fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) + transported_f = fdrake_connection.from_firedrake(fdrake_f) + + to_discr = fdrake_connection.to_discr() + with cl.CommandQueue(cl_ctx) as queue: + nodes = to_discr.nodes().get(queue=queue) + meshmode_f = meshmode_f_eval(nodes) + + np.testing.assert_allclose(transported_f, meshmode_f, atol=CLOSE_ATOL) + +# }}} + + # {{{ Idempotency tests fd->mm->fd and (fd->)mm->fd->mm for connection def check_idempotency(fdrake_connection, fdrake_function): @@ -123,8 +190,6 @@ def check_idempotency(fdrake_connection, fdrake_function): fdrake_function_copy = Function(fdrake_fspace) fdrake_connection.from_meshmode(mm_field, fdrake_function_copy) - print(np.sum(np.abs(fdrake_function.dat.data - fdrake_function_copy.dat.data))) - print(type(fdrake_function.dat.data)) np.testing.assert_allclose(fdrake_function_copy.dat.data, fdrake_function.dat.data, atol=CLOSE_ATOL) -- GitLab From 41d1ea91785d6da61a7a9c8b817d466caed22f1a Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 23 Jun 2020 10:46:06 -0500 Subject: [PATCH 031/221] Redid cell and coordinate handling a bit to account for 1D bug/get correct unit nodes --- meshmode/interop/FInAT/lagrange_element.py | 79 +++++++++---------- meshmode/interop/fiat/simplex_cell.py | 30 +++---- .../firedrake/function_space_shared_data.py | 1 - meshmode/interop/firedrake/mesh_geometry.py | 8 ++ 4 files changed, 55 insertions(+), 63 deletions(-) diff --git a/meshmode/interop/FInAT/lagrange_element.py b/meshmode/interop/FInAT/lagrange_element.py index b3d4c50f..4b3d977c 100644 --- a/meshmode/interop/FInAT/lagrange_element.py +++ b/meshmode/interop/FInAT/lagrange_element.py @@ -22,7 +22,6 @@ THE SOFTWARE. import numpy as np import numpy.linalg as la -import six from meshmode.interop import ExternalImportHandler from meshmode.interop.fiat import FIATSimplexCellImporter @@ -44,6 +43,8 @@ class FinatLagrangeElementImporter(ExternalImportHandler): :param finat_element: A FInAT element of type :class:`finat.fiat_elements.Lagrange` or :class:`finat.fiat_elements.DiscontinuousLagrange` + (or :class:`finat.spectral.GaussLegendre` or + :class:`finat.spectral.GaussLobattoLegendre` in 1D) which uses affine mapping of the basis functions (i.e. ``finat_element.mapping`` must be ``"affine"``) @@ -51,6 +52,7 @@ class FinatLagrangeElementImporter(ExternalImportHandler): :raises TypeError: If :param:`finat_element` is not of type :class:`finat.fiat_elements.Lagrange` or :class:`finat.fiat_elements.DiscontinuousLagrange` + (or the 1D classes listed above) :raises ValueError: If :param:`finat_element` does not use affine mappings of the basis functions @@ -91,39 +93,33 @@ class FinatLagrangeElementImporter(ExternalImportHandler): if they have not already been computed. """ if self._unit_nodes is None or self._unit_vertex_indices is None: + # FIXME : This should work, but uses some private info # {{{ Compute unit nodes - node_nr_to_coords = {} - unit_vertex_indices = [] - - # FIXME : This works, but is very ad-hoc. It is probably better - # to get permission from the FInAT people to reach into - # the fiat element and get the nodes explicitly - # Get unit nodes - for dim, element_nrs in six.iteritems( - self.data.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.data.degree) - # Record any new nodes - i = 0 - for node_nr in node_list: - if node_nr not in node_nr_to_coords and \ - i < len(pts_on_element): - 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() + + # each point evaluator is a function p(f) evaluates f at a node, + # so we need to evaluate each point evaluator at the identity to + # recover the nodes + point_evaluators = self.data._element.dual.nodes + unit_nodes = [p(lambda x: x) for p in point_evaluators] + unit_nodes = np.array(unit_nodes).T + self._unit_nodes = \ + self.cell_importer.affinely_map_firedrake_to_meshmode(unit_nodes) + + # Is this safe?, I think so bc on a reference element + close = 1e-8 + # Get vertices as (dim, nunit_vertices) + unit_vertices = np.array(self.data.cell.vertices).T + unit_vertices = \ + self.cell_importer.affinely_map_firedrake_to_meshmode(unit_vertices) + self._unit_vertex_indices = [] + for n_ndx in range(self._unit_nodes.shape[1]): + for v_ndx in range(unit_vertices.shape[1]): + diff = self._unit_nodes[:, n_ndx] - unit_vertices[:, v_ndx] + if np.max(np.abs(diff)) < close: + self._unit_vertex_indices.append(n_ndx) + break + + self._unit_vertex_indices = np.array(self._unit_vertex_indices) # }}} @@ -135,9 +131,10 @@ class FinatLagrangeElementImporter(ExternalImportHandler): def unit_vertex_indices(self): """ - :return: An array of shape *(dim+1,)* of indices + :return: A numpy integer array of indices so that *self.unit_nodes()[self.unit_vertex_indices()]* - are the vertices of the reference element. + are the nodes of the reference element which coincide + with its vertices (this is possibly empty). """ self._compute_unit_vertex_indices_and_nodes() return self._unit_vertex_indices @@ -203,12 +200,12 @@ class FinatLagrangeElementImporter(ExternalImportHandler): 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) + :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), \ diff --git a/meshmode/interop/fiat/simplex_cell.py b/meshmode/interop/fiat/simplex_cell.py index cf0c1622..c6c0ca2c 100644 --- a/meshmode/interop/fiat/simplex_cell.py +++ b/meshmode/interop/fiat/simplex_cell.py @@ -105,30 +105,18 @@ class FIATSimplexCellImporter(ExternalImportHandler): self._mat, self._shift = get_affine_mapping(reference_vertices, self._unit_vertices) - def make_points(self, dim, entity_id, order): + def affinely_map_firedrake_to_meshmode(self, points): """ - Constructs a lattice of points on the *entity_id*th facet - of dimension *dim*. - - Args are exactly as in - :meth:`fiat.FIAT.reference_element.Cell.make_points` - (see `FIAT docs `_), - but the unit nodes are (affinely) mapped to :mod:`modepy` + Map points on the firedrake reference simplex to + :mod:`modepy` `unit coordinates `_. - :arg dim: Dimension of the facet we are constructing points on. - :arg entity_id: identifier to determine which facet of - dimension *dim* to construct the points on. - :arg order: Number of points to include in each direction. - - :return: an *np.array* of shape *(dim, npoints)* holding the - coordinates of each of the ver + :arg points: *n* points on the reference simplex + as a numpy array of shape *(dim, n)* + :return: A numpy array of shape *(dim, n)* wherein the + firedrake refernece simplex has been affinely mapped + to the modepy reference simplex """ - points = self.data.make_points(dim, entity_id, order) - if not points: - return points - points = np.array(points) - # Points is (npoints, dim) so have to transpose - return (np.matmul(self._mat, points.T) + self._shift[:, np.newaxis]).T + return np.matmul(self._mat, points) + self._shift[:, np.newaxis] # }}} diff --git a/meshmode/interop/firedrake/function_space_shared_data.py b/meshmode/interop/firedrake/function_space_shared_data.py index 52c0e6d2..9bd03f27 100644 --- a/meshmode/interop/firedrake/function_space_shared_data.py +++ b/meshmode/interop/firedrake/function_space_shared_data.py @@ -146,7 +146,6 @@ def reordering_array(mesh_importer, key, fspace_data): (order.shape[0]//nunit_nodes, nunit_nodes) + order.shape[1:]) flip_mat = finat_element_importer.flip_matrix() - print("ORIENT=", mesh_importer._orient) reorder_nodes(mesh_importer.orientations(), new_order, flip_mat, unflip=firedrake_to_meshmode) new_order = new_order.flatten() diff --git a/meshmode/interop/firedrake/mesh_geometry.py b/meshmode/interop/firedrake/mesh_geometry.py index 8ce88f26..da965cbe 100644 --- a/meshmode/interop/firedrake/mesh_geometry.py +++ b/meshmode/interop/firedrake/mesh_geometry.py @@ -180,6 +180,14 @@ class FiredrakeMeshGeometryImporter(ExternalImportHandler): fspace_importer = self.coordinates_importer.function_space_importer() finat_element_importer = fspace_importer.finat_element_importer + from finat.fiat_elements import Lagrange + from finat.spectral import GaussLobattoLegendre + if not isinstance(finat_element_importer.data, + (Lagrange, GaussLobattoLegendre)): + raise TypeError("Coordinates must live in a continuous space " + " (Lagrange or GaussLobattoLegendre), not %s" + % type(finat_element_importer.data)) + # Convert cell node list of mesh to vertex list unit_vertex_indices = finat_element_importer.unit_vertex_indices() cfspace = self.data.coordinates.function_space() -- GitLab From d2e843a0d3f8fc4a04b63378fbb52ac70f5b3035 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 23 Jun 2020 14:25:52 -0500 Subject: [PATCH 032/221] Some documentation fixes --- .../interop/firedrake/function_space_shared_data.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/meshmode/interop/firedrake/function_space_shared_data.py b/meshmode/interop/firedrake/function_space_shared_data.py index 9bd03f27..c7b316a7 100644 --- a/meshmode/interop/firedrake/function_space_shared_data.py +++ b/meshmode/interop/firedrake/function_space_shared_data.py @@ -55,16 +55,16 @@ def cached(f, mesh_importer, key, *args, **kwargs): def reorder_nodes(orient, nodes, flip_matrix, unflip=False): """ - :param orient: An array of shape (nelements) of orientations, + flips :param:`nodes` according to :param:`orient` + + :param orient: An array of shape *(nelements)* of orientations, >0 for positive, <0 for negative - :param nodes: a (nelements, nunit_nodes) or (dim, nelements, nunit_nodes) - shaped array of nodes + :param nodes: a *(nelements, nunit_nodes)* or + *(dim, nelements, nunit_nodes)* shaped array of nodes :param flip_matrix: The matrix used to flip each negatively-oriented element :param unflip: If *True*, use transpose of :param:`flip_matrix` to flip negatively-oriented elements - - flips :param:`nodes` """ # reorder nodes (Code adapted from # meshmode.mesh.processing.flip_simplex_element_group) @@ -106,7 +106,7 @@ def reorder_nodes(orient, nodes, flip_matrix, unflip=False): @cached def reordering_array(mesh_importer, key, fspace_data): """ - :param key: A tuple (finat_element_anlog, firedrake_to_meshmode) + :param key: A tuple (finat_element_importer, firedrake_to_meshmode) where *firedrake_to_meshmode* is a *bool*, *True* indicating firedrake->meshmode reordering, *False* meshmode->firedrake -- GitLab From 7dad1fcbe5ac3a9ff5bda350384e093313a5ba31 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 23 Jun 2020 14:26:17 -0500 Subject: [PATCH 033/221] Removed unused/unsafe __eq__/__hash__ from handlers --- meshmode/interop/__init__.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/meshmode/interop/__init__.py b/meshmode/interop/__init__.py index 1421458c..2c1bf072 100644 --- a/meshmode/interop/__init__.py +++ b/meshmode/interop/__init__.py @@ -46,17 +46,6 @@ class ExternalDataHandler: def __init__(self, data): self.data = data - def __hash__(self): - return hash((type(self), self.data)) - - def __eq__(self, other): - return isinstance(other, type(self)) and \ - isinstance(self, type(other)) and \ - self.data == other.data - - def __neq__(self, other): - return not self.__eq__(other) - # }}} -- GitLab From 35a71f8699396fb546d800b5291748eaedd1101a Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 24 Jun 2020 16:31:02 -0500 Subject: [PATCH 034/221] Moved useful methods from FInAT and FIAT classes into functions in reference_cell.py --- meshmode/interop/firedrake/reference_cell.py | 134 +++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 meshmode/interop/firedrake/reference_cell.py diff --git a/meshmode/interop/firedrake/reference_cell.py b/meshmode/interop/firedrake/reference_cell.py new file mode 100644 index 00000000..100f426f --- /dev/null +++ b/meshmode/interop/firedrake/reference_cell.py @@ -0,0 +1,134 @@ +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" + +__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. +""" + +import numpy as np +import numpy.linalg as la + + +__doc__ = """ +.. autofunction:: get_affine_reference_simplex_mapping +.. autofunction:: get_finat_element_unit_nodes +""" + + +# {{{ Map between reference simplices + +def get_affine_reference_simplex_mapping(self, spat_dim, firedrake_to_meshmode=True): + """ + Returns a function which takes a numpy array points + on one reference cell and maps each + point to another using a positive affine map. + + :param spat_dim: The spatial dimension + :param firedrake_to_meshmode: If true, the returned function maps from + the firedrake reference element to + meshmode, if false maps from + meshmode to firedrake. More specifically, + :mod:`firedrake` uses the standard :mod:`FIAT` + simplex and :mod:`meshmode` uses + :mod:`modepy`'s + `unit coordinates `_. + :return: A function which takes a numpy array of *n* points with + shape *(dim, n)* on one reference cell and maps + each point to another using a positive affine map. + Note that the returned function performs + no input validation. + """ + # validate input + assert isinstance(spat_dim, int) + assert spat_dim >= 0 + assert isinstance(firedrake_to_meshmode, bool) + + from FIAT.reference_element import ufc_simplex + from modepy.tools import unit_vertices + # Get the unit vertices from each system, + # each stored with shape *(dim, nunit_vertices)* + firedrake_unit_vertices = np.array(ufc_simplex(spat_dim).vertices).T + modepy_unit_vertices = unit_vertices(spat_dim).T + + if firedrake_to_meshmode: + from_verts = firedrake_unit_vertices + to_verts = modepy_unit_vertices + else: + from_verts = modepy_unit_vertices + to_verts = firedrake_unit_vertices + + # Compute matrix A and vector b so that A f_i + b -> t_i + # for each "from" vertex f_i and corresponding "to" vertex t_i + assert from_verts.shape == to_verts.shape + dim, nvects = from_verts.shape + + # If only have on vertex, have A = I and b = to_vert - from_vert + if nvects == 1: + shift = to_verts[:, 0] - from_verts[:, 0] + + def affine_map(points): + return points + shift[:, np.newaxis] + # Otherwise, we have to solve for A and b + else: + # span verts: v1 - v0, v2 - v0, ... + from_span_verts = from_verts[:, 1:] - from_verts[:, 0, np.newaxis] + to_span_verts = to_verts[:, 1:] - to_verts[:, 0, np.newaxis] + # mat maps (fj - f0) -> (tj - t0), our "A" + mat = la.solve(from_span_verts, to_span_verts) + # A f0 + b -> t0 so b = t0 - A f0 + shift = to_verts[:, 0] - np.matmul(mat, from_verts[:, 0]) + + # Explicitly ensure A is positive + if la.det(mat) < 0: + from meshmode.mesh.processing import get_simplex_element_flip_matrix + flip_matrix = get_simplex_element_flip_matrix(1, to_verts) + mat = np.matmul(flip_matrix, mat) + + def affine_map(points): + return np.matmul(mat, points) + shift[:, np.newaxis] + + return affine_map + +# }}} + + +# {{{ Get firedrake unit nodes + +def get_finat_element_unit_nodes(finat_element): + """ + Returns the unit nodes used by the FInAT element in firedrake's + (equivalently, FInAT/FIAT's) reference coordinates + + :param finat_element: A :class:`finat.finiteelementbase.FiniteElementBase` + instance (i.e. a firedrake function space's reference element) + :return: A numpy array of shape *(dim, nunit_nodes)* holding the unit + nodes used by this element. *dim* is the dimension spanned + by the finat element's reference element + (see its ``cell`` attribute) + """ + # point evaluators is a list of functions *p_0,...,p_{n-1}*. + # *p_i(f)* evaluates function *f* at node *i* (stored as a tuple), + # so to recover node *i* we need to evaluate *p_i* at the identity + # function + point_evaluators = finat_element._element.dual.nodes + unit_nodes = [p(lambda x: x) for p in point_evaluators] + unit_nodes = np.array(unit_nodes).T + + return unit_nodes + +# }}} -- GitLab From e3081538f71c9eba1002bd996910e4be1f3dd498 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 24 Jun 2020 16:31:23 -0500 Subject: [PATCH 035/221] Moved useful methods from mesh_topology.py into mesh.py --- meshmode/interop/firedrake/mesh.py | 158 +++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 meshmode/interop/firedrake/mesh.py diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py new file mode 100644 index 00000000..d067fb27 --- /dev/null +++ b/meshmode/interop/firedrake/mesh.py @@ -0,0 +1,158 @@ +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" + +__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. +""" + +__doc__ = """ +.. autofunction:: get_firedrake_nodal_adjacency_group +.. autofunction:: get_firedrake_boundary_tags +""" + +from warnings import warn # noqa +import numpy as np + + +# {{{ functions to extract information from Mesh Topology + +def get_firedrake_nodal_adjacency_group(fdrake_mesh, cells_to_use=None): + """ + Create a nodal adjacency object + representing the nodal adjacency of :param:`fdrake_mesh` + + :param fdrake_mesh: A firedrake mesh (:class:`MeshTopology` + or :class:`MeshGeometry`) + :param cells_to_use: Either + + * *None*, in which case this argument is is ignored + * A numpy array of firedrake cell indices, in which case + any cell with indices not in the array :param:`cells_to_use` + is ignored. + This induces a new order on the cells. + The *i*th element in the returned :class:`NodalAdjacency` + object corresponds to the ``cells_to_use[i]``th cell + in the firedrake mesh. + + This feature has been used when only part of the mesh + needs to be converted, since firedrake has no concept + of a "sub-mesh". + + :return: A :class:`meshmode.mesh.NodalAdjacency` instance + representing the nodal adjacency of :param:`fdrake_mesh` + """ + mesh_topology = fdrake_mesh.topology + # TODO... not sure how to get around the private access + plex = mesh_topology._plex + + # dmplex cell Start/end and vertex Start/end. + c_start, c_end = plex.getHeightStratum(0) + v_start, v_end = plex.getDepthStratum(0) + + # TODO... not sure how to get around the private access + # This maps the dmplex index of a cell to its firedrake index + to_fd_id = np.vectorize(mesh_topology._cell_numbering.getOffset)( + np.arange(c_start, c_end, dtype=np.int32)) + + element_to_neighbors = {} + verts_checked = set() # dmplex ids of vertex checked + + # If using all cells, loop over them all + if cells_to_use is None: + range_ = range(c_start, c_end) + # Otherwise, just the ones you're using + else: + assert isinstance(cells_to_use, np.ndarray) + assert np.size(cells_to_use) == np.size(np.unique(cells_to_use)), \ + "cells_to_use must have unique values" + assert len(np.shape(cells_to_use)) == 1 and len(cells_to_use) > 0 + isin = np.isin(to_fd_id, cells_to_use) + range_ = np.arange(c_start, c_end, dtype=np.int32)[isin] + + # For each cell + for cell_id in range_: + # For each vertex touching the cell (that haven't already seen) + for vert_id in plex.getTransitiveClosure(cell_id)[0]: + if v_start <= vert_id < v_end and vert_id not in verts_checked: + verts_checked.add(vert_id) + cells = [] + # Record all cells touching that vertex + support = plex.getTransitiveClosure(vert_id, useCone=False)[0] + for other_cell_id in support: + if c_start <= other_cell_id < c_end: + cells.append(to_fd_id[other_cell_id - c_start]) + + # If only using some cells, clean out extraneous ones + # and relabel them to new id + cells = set(cells) + if cells_to_use is not None: + cells = set([cells_to_use[fd_ndx] for fd_ndx in cells + if fd_ndx in cells_to_use]) + + # mark cells as neighbors + for cell_one in cells: + element_to_neighbors.setdefault(cell_one, set()) + element_to_neighbors[cell_one] |= cells + + # Count the number of cells + if cells_to_use is None: + nelements = mesh_topology.num_cells() + else: + nelements = cells_to_use.shape[0] + + # Create neighbors_starts and neighbors + neighbors = [] + # FIXME : Is this the right integer type to choose? + neighbors_starts = np.zeros(nelements + 1, dtype=np.int32) + for iel in range(len(element_to_neighbors)): + elt_neighbors = element_to_neighbors[iel] + neighbors += list(elt_neighbors) + neighbors_starts[iel+1] = len(neighbors) + + neighbors = np.array(neighbors, dtype=np.int32) + + from meshmode.mesh import NodalAdjacency + return NodalAdjacency(neighbors_starts=neighbors_starts, + neighbors=neighbors) + + +def get_firedrake_boundary_tags(fdrake_mesh): + """ + Return a tuple of bdy tags as requested in + the construction of a :mod:`meshmode` :class:`Mesh` + + The tags used are :class:`meshmode.mesh.BTAG_ALL`, + :class:`meshmode.mesh.BTAG_REALLY_ALL`, and + any markers in the mesh topology's exterior facets + (see :attr:`firedrake.mesh.MeshTopology.exterior_facets.unique_markers`) + + :param fdrake_mesh: A :mod:`firedrake` :class:`MeshTopology` or + :class:`MeshGeometry` + + :return: A tuple of boundary tags + """ + from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL + bdy_tags = [BTAG_ALL, BTAG_REALLY_ALL] + + unique_markers = fdrake_mesh.topology.exterior_facets.unique_markers + if unique_markers is not None: + bdy_tags += list(unique_markers) + + return tuple(bdy_tags) + +# }}} -- GitLab From 8831b0d3357368034d19e9fcfe2e717d6beade0e Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 25 Jun 2020 16:24:53 -0500 Subject: [PATCH 036/221] Redid mesh to be functional and use dmplex/cell_closure properly --- meshmode/interop/firedrake/mesh.py | 462 +++++++++++++++---- meshmode/interop/firedrake/reference_cell.py | 8 +- meshmode/mesh/processing.py | 61 ++- 3 files changed, 422 insertions(+), 109 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index d067fb27..7ca5ea8f 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -22,116 +22,107 @@ THE SOFTWARE. __doc__ = """ .. autofunction:: get_firedrake_nodal_adjacency_group -.. autofunction:: get_firedrake_boundary_tags +.. autofunction:: get_firedrake_vertex_indices """ from warnings import warn # noqa import numpy as np +import six # {{{ functions to extract information from Mesh Topology -def get_firedrake_nodal_adjacency_group(fdrake_mesh, cells_to_use=None): + +def _get_firedrake_nodal_info(fdrake_mesh_topology): + # FIXME: do docs """ - Create a nodal adjacency object - representing the nodal adjacency of :param:`fdrake_mesh` - - :param fdrake_mesh: A firedrake mesh (:class:`MeshTopology` - or :class:`MeshGeometry`) - :param cells_to_use: Either - - * *None*, in which case this argument is is ignored - * A numpy array of firedrake cell indices, in which case - any cell with indices not in the array :param:`cells_to_use` - is ignored. - This induces a new order on the cells. - The *i*th element in the returned :class:`NodalAdjacency` - object corresponds to the ``cells_to_use[i]``th cell - in the firedrake mesh. - - This feature has been used when only part of the mesh - needs to be converted, since firedrake has no concept - of a "sub-mesh". - - :return: A :class:`meshmode.mesh.NodalAdjacency` instance - representing the nodal adjacency of :param:`fdrake_mesh` + Get nodal adjacency, and vertex indices from a firedrake mesh topology """ - mesh_topology = fdrake_mesh.topology + top = fdrake_mesh_topology.topology + + # If you don't understand dmplex, look at the PETSc reference + # here: https://cse.buffalo.edu/~knepley/classes/caam519/CSBook.pdf + # used to get topology info # TODO... not sure how to get around the private access - plex = mesh_topology._plex + plex = top._plex - # dmplex cell Start/end and vertex Start/end. + # Get range of dmplex ids for cells, facets, and vertices c_start, c_end = plex.getHeightStratum(0) + f_start, f_end = plex.getHeightStratum(1) v_start, v_end = plex.getDepthStratum(0) - # TODO... not sure how to get around the private access - # This maps the dmplex index of a cell to its firedrake index - to_fd_id = np.vectorize(mesh_topology._cell_numbering.getOffset)( - np.arange(c_start, c_end, dtype=np.int32)) - - element_to_neighbors = {} - verts_checked = set() # dmplex ids of vertex checked - - # If using all cells, loop over them all - if cells_to_use is None: - range_ = range(c_start, c_end) - # Otherwise, just the ones you're using - else: - assert isinstance(cells_to_use, np.ndarray) - assert np.size(cells_to_use) == np.size(np.unique(cells_to_use)), \ - "cells_to_use must have unique values" - assert len(np.shape(cells_to_use)) == 1 and len(cells_to_use) > 0 - isin = np.isin(to_fd_id, cells_to_use) - range_ = np.arange(c_start, c_end, dtype=np.int32)[isin] - - # For each cell - for cell_id in range_: - # For each vertex touching the cell (that haven't already seen) - for vert_id in plex.getTransitiveClosure(cell_id)[0]: - if v_start <= vert_id < v_end and vert_id not in verts_checked: - verts_checked.add(vert_id) - cells = [] - # Record all cells touching that vertex - support = plex.getTransitiveClosure(vert_id, useCone=False)[0] - for other_cell_id in support: - if c_start <= other_cell_id < c_end: - cells.append(to_fd_id[other_cell_id - c_start]) - - # If only using some cells, clean out extraneous ones - # and relabel them to new id - cells = set(cells) - if cells_to_use is not None: - cells = set([cells_to_use[fd_ndx] for fd_ndx in cells - if fd_ndx in cells_to_use]) - - # mark cells as neighbors - for cell_one in cells: - element_to_neighbors.setdefault(cell_one, set()) - element_to_neighbors[cell_one] |= cells - - # Count the number of cells - if cells_to_use is None: - nelements = mesh_topology.num_cells() - else: - nelements = cells_to_use.shape[0] - - # Create neighbors_starts and neighbors + # TODO... not sure how to get around the private accesses + # Maps dmplex cell id -> firedrake cell index + def cell_id_dmp_to_fd(ndx): + return top._cell_numbering.getOffset(ndx) + + # Maps dmplex vert id -> firedrake vert index + def vert_id_dmp_to_fd(ndx): + return top._vertex_numbering.getOffset(ndx) + + # FIXME : Is this the right integer type? + # We will fill in the values as we go + vertex_indices = -np.ones((top.num_cells(), top.ufl_cell().num_vertices()), + dtype=np.int32) + # This will map fd cell ndx -> list of fd cell indices which share a vertex + cell_to_nodal_neighbors = {} + # This will map dmplex facet id -> list of adjacent + # (fd cell ndx, firedrake local fac num) + facet_to_cells = {} + # This will map dmplex vert id -> list of fd cell + # indices which touch this vertex, + # Primarily used to construct cell_to_nodal_neighbors + vert_to_cells = {} + + # Loop through each cell (cell closure is all the dmplex ids for any + # verts, faces, etc. associated with the cell) + for fd_cell_ndx, closure_dmp_ids in enumerate(top.cell_closure): + # Store the vertex indices + dmp_verts = closure_dmp_ids[np.logical_and(v_start <= closure_dmp_ids, + closure_dmp_ids < v_end)] + fd_verts = np.array([vert_id_dmp_to_fd(dmp_vert) + for dmp_vert in dmp_verts]) + vertex_indices[fd_cell_ndx][:] = fd_verts[:] + + # Record this cell as touching the facet and remember its local + # facet number (the order it appears) + dmp_fac_ids = closure_dmp_ids[np.logical_and(f_start <= closure_dmp_ids, + closure_dmp_ids < f_end)] + for loc_fac_nr, dmp_fac_id in enumerate(dmp_fac_ids): + # make sure there is a list to append to and append + facet_to_cells.setdefault(dmp_fac_id, []) + facet_to_cells[dmp_fac_id].append((fd_cell_ndx, loc_fac_nr)) + + # Record this vertex as touching the cell, and mark this cell + # as nodally adjacent (in cell_to_nodal_neighbors) to any + # cells already documented as touching this cell + cell_to_nodal_neighbors[fd_cell_ndx] = [] + for dmp_vert_id in dmp_verts: + vert_to_cells.setdefault(dmp_vert_id, []) + for other_cell_ndx in vert_to_cells[dmp_vert_id]: + cell_to_nodal_neighbors[fd_cell_ndx].append(other_cell_ndx) + cell_to_nodal_neighbors[other_cell_ndx].append(fd_cell_ndx) + vert_to_cells[dmp_vert_id].append(fd_cell_ndx) + + # Next go ahead and compute nodal adjacency by creating + # neighbors and neighbor_starts as specified by :class:`NodalAdjacency` neighbors = [] # FIXME : Is this the right integer type to choose? - neighbors_starts = np.zeros(nelements + 1, dtype=np.int32) - for iel in range(len(element_to_neighbors)): - elt_neighbors = element_to_neighbors[iel] - neighbors += list(elt_neighbors) + neighbors_starts = np.zeros(top.num_cells() + 1, dtype=np.int32) + for iel in range(len(cell_to_nodal_neighbors)): + neighbors += cell_to_nodal_neighbors[iel] neighbors_starts[iel+1] = len(neighbors) neighbors = np.array(neighbors, dtype=np.int32) from meshmode.mesh import NodalAdjacency - return NodalAdjacency(neighbors_starts=neighbors_starts, - neighbors=neighbors) + nodal_adjacency = NodalAdjacency(neighbors_starts=neighbors_starts, + neighbors=neighbors) + + return (vertex_indices, nodal_adjacency) -def get_firedrake_boundary_tags(fdrake_mesh): +def _get_firedrake_boundary_tags(fdrake_mesh): """ Return a tuple of bdy tags as requested in the construction of a :mod:`meshmode` :class:`Mesh` @@ -155,4 +146,303 @@ def get_firedrake_boundary_tags(fdrake_mesh): return tuple(bdy_tags) + +def _get_firedrake_facial_adjacency_groups(fdrake_mesh): + # FIXME: do docs + top = fdrake_mesh.topology + # We only need one group + # for interconnectivity and one for boundary connectivity. + # The tricky part is moving from firedrake local facet numbering + # (ordered lexicographically by the vertex excluded from the face) + # and meshmode's facet ordering: obtained from a simplex element + # group + from meshmode.mesh import SimplexElementGroup + mm_simp_group = SimplexElementGroup(1, None, None, + dim=top.cell_dimension()) + mm_face_vertex_indices = mm_simp_group.face_vertex_indices() + # map firedrake local face number to meshmode local face number + fd_loc_fac_nr_to_mm = {} + # Figure out which vertex is excluded to get the corresponding + # firedrake local index + for mm_loc_fac_nr, face in enumerate(mm_face_vertex_indices): + for fd_loc_fac_nr in range(top.ufl_cell().num_vertices()): + if fd_loc_fac_nr not in face: + fd_loc_fac_nr_to_mm[fd_loc_fac_nr] = mm_loc_fac_nr + break + + # First do the interconnectivity group + + # Get the firedrake cells associated to each interior facet + int_facet_cell = top.interior_facets.facet_cell + # Get the firedrake local facet numbers and map them to the + # meshmode local facet numbers + int_fac_loc_nr = top.interior_facets.local_facet_dat.data + int_fac_loc_nr = \ + np.array([[fd_loc_fac_nr_to_mm[fac_nr] for fac_nr in fac_nrs] + for fac_nrs in int_fac_loc_nr]) + # elements neighbors element_faces neighbor_faces are as required + # for a :class:`FacialAdjacencyGroup`. + from meshmode.mesh import Mesh + + int_elements = int_facet_cell.flatten() + int_neighbors = np.concatenate((int_facet_cell[:, 1], int_facet_cell[:, 0])) + int_element_faces = int_fac_loc_nr.flatten().astype(Mesh.face_id_dtype) + int_neighbor_faces = np.concatenate((int_fac_loc_nr[:, 1], + int_fac_loc_nr[:, 0])) + int_neighbor_faces = int_neighbor_faces.astype(Mesh.face_id_dtype) + + from meshmode.mesh import FacialAdjacencyGroup + interconnectivity_grp = FacialAdjacencyGroup(igroup=0, ineighbor_group=0, + elements=int_elements, + neighbors=int_neighbors, + element_faces=int_element_faces, + neighbor_faces=int_neighbor_faces) + + # Now look at exterior facets + + # We can get the elements directly from exterior facets + ext_elements = top.exterior_facets.facet_cell.flatten() + + ext_element_faces = top.exterior_facets.local_facet_dat.data + ext_element_faces = ext_element_faces.astype(Mesh.face_id_dtype) + ext_neighbor_faces = np.zeros(ext_element_faces.shape, dtype=np.int32) + ext_neighbor_faces = ext_neighbor_faces.astype(Mesh.face_id_dtype) + + # Now we need to tag the boundary + bdy_tags = _get_firedrake_boundary_tags(top) + boundary_tag_to_index = {bdy_tag: i for i, bdy_tag in enumerate(bdy_tags)} + + def boundary_tag_bit(boundary_tag): + try: + return 1 << boundary_tag_to_index[boundary_tag] + except KeyError: + raise 0 + + from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL + ext_neighbors = np.zeros(ext_elements.shape, dtype=np.int32) + for ifac, marker in enumerate(top.exterior_facets.markers): + ext_neighbors[ifac] = boundary_tag_bit(BTAG_ALL) \ + | boundary_tag_bit(BTAG_REALLY_ALL) \ + | boundary_tag_bit(marker) + + exterior_grp = FacialAdjacencyGroup(igroup=0, ineighbor=None, + elements=ext_elements, + element_faces=ext_element_faces, + neighbors=ext_neighbors, + neighbor_faces=ext_neighbor_faces) + + return [{0: interconnectivity_grp, None: exterior_grp}] + # }}} + + +# {{{ Orientation computation + +def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, + normals=None, no_normals_warn=True): + # FIXME : Fix docs + """ + Return the orientations of the mesh elements: + an array, the *i*th element is > 0 if the *ith* element + is positively oriented, < 0 if negatively oriented. + Mesh must have co-dimension 0 or 1. + + :param normals: _Only_ used if :param:`mesh` is a 1-surface + embedded in 2-space. In this case, + - If *None* then + all elements are assumed to be positively oriented. + - Else, should be a list/array whose *i*th entry + is the normal for the *i*th element (*i*th + in :param:`mesh`*.coordinate.function_space()*'s + :attribute:`cell_node_list`) + + :param no_normals_warn: If *True*, raises a warning + if :param:`mesh` is a 1-surface embedded in 2-space + and :param:`normals` is *None*. + """ + # compute orientations + tdim = fdrake_mesh.topological_dimension() + gdim = fdrake_mesh.geometric_dimension() + + orient = None + if gdim == tdim: + # We use :mod:`meshmode` to check our orientations + from meshmode.mesh.processing import \ + find_volume_mesh_element_group_orientation + + orient = find_volume_mesh_element_group_orientation(vertices, + unflipped_group) + + if tdim == 1 and gdim == 2: + # In this case we have a 1-surface embedded in 2-space + orient = np.ones(fdrake_mesh.num_cells()) + if normals: + for i, (normal, vertices) in enumerate(zip(np.array(normals), + vertices)): + if np.cross(normal, vertices) < 0: + orient[i] = -1.0 + elif no_normals_warn: + warn("Assuming all elements are positively-oriented.") + + elif tdim == 2 and gdim == 3: + # In this case we have a 2-surface embedded in 3-space + orient = fdrake_mesh.cell_orientations().dat.data + r""" + Convert (0 \implies negative, 1 \implies positive) to + (-1 \implies negative, 1 \implies positive) + """ + orient *= 2 + orient -= np.ones(orient.shape, dtype=orient.dtype) + #Make sure the mesh fell into one of the cases + """ + NOTE : This should be guaranteed by previous checks, + but is here anyway in case of future development. + """ + assert orient is not None, "something went wrong, contact the developer" + return orient + +# }}} + + +# {{{ Mesh conversion + +def import_firedrake_mesh(fdrake_mesh): + # FIXME : docs + # Type validation + from firedrake.mesh import MeshGeometry + if not isinstance(fdrake_mesh, MeshGeometry): + raise TypeError(":param:`fdrake_mesh_topology` must be a " + ":mod:`firedrake` :class:`MeshGeometry`, " + "not %s." % type(fdrake_mesh)) + assert fdrake_mesh.ufl_cell().is_simplex(), "Mesh must use simplex cells" + gdim = fdrake_mesh.geometric_dimension() + tdim = fdrake_mesh.topological_dimension() + assert gdim - tdim in [0, 1], "Mesh co-dimension must be 0 or 1" + fdrake_mesh.init() + + # Get all the nodal information we can from the topology + bdy_tags = _get_firedrake_boundary_tags(fdrake_mesh) + vertex_indices, nodal_adjacency = _get_firedrake_nodal_info(fdrake_mesh) + + # Grab the mesh reference element and cell dimension + coord_finat_elt = fdrake_mesh.coordinates.function_space().finat_element + cell_dim = fdrake_mesh.cell_dimension() + + # Get finat unit nodes and map them onto the meshmode reference simplex + from meshmode.interop.firedrake.reference_cell import ( + get_affine_reference_simplex_mapping, get_finat_element_unit_nodes) + finat_unit_nodes = get_finat_element_unit_nodes(coord_finat_elt) + fd_ref_to_mm = get_affine_reference_simplex_mapping(cell_dim, True) + finat_unit_nodes = fd_ref_to_mm(finat_unit_nodes) + + # Now grab the nodes + coords = fdrake_mesh.coordinates + cell_node_list = coords.function_space().cell_node_list + nodes = np.real(coords.dat.data[cell_node_list]) + # Add extra dim in 1D so that have [nelements][nunit_nodes][dim] + if len(nodes.shape) == 2: + nodes = np.reshape(nodes, nodes.shape + (1,)) + # Now we want the nodes to actually have shape [dim][nelements][nunit_nodes] + nodes = np.transpose(nodes, (2, 0, 1)) + + # make a group (possibly with some elements that need to be flipped) + from meshmode.mesh import SimplexElementGroup + unflipped_group = SimplexElementGroup(coord_finat_elt.degree, + vertex_indices, + nodes, + dim=cell_dim, + unit_nodes=finat_unit_nodes) + + # Next get the vertices (we'll need these for the orientations) + + coord_finat = fdrake_mesh.coordinates.function_space().finat_element + # unit_vertex_indices are the element-local indices of the nodes + # which coincide with the vertices, i.e. for element *i*, + # vertex 0's coordinates would be nodes[i][unit_vertex_indices[0]]. + # This assumes each vertex has some node which coincides with it... + # which is normally fine to assume for firedrake meshes. + unit_vertex_indices = [] + # iterate through the dofs associated to each vertex on the + # reference element + for _, dofs in sorted(six.iteritems(coord_finat.entity_dofs()[0])): + assert len(dofs) == 1, \ + "The function space of the mesh coordinates must have" \ + " exactly one degree of freedom associated with " \ + " each vertex in order to determine vertex coordinates" + dof, = dofs + unit_vertex_indices.append(dof) + + # Now get the vertex coordinates + vertices = {} + for icell, cell_vertex_indices in enumerate(vertex_indices): + for local_vert_id, global_vert_id in enumerate(cell_vertex_indices): + if global_vert_id in vertices: + continue + local_node_nr = unit_vertex_indices[local_vert_id] + vertices[global_vert_id] = nodes[:, icell, local_node_nr] + # Stuff the vertices in a *(dim, nvertices)*-shaped numpy array + vertices = np.array([vertices[i] for i in range(len(vertices))]).T + + # Use the vertices to compute the orientations and flip the group + # FIXME : Allow for passing in normals/no normals warn + orient = _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices) + from meshmode.mesh.processing import flip_simplex_element_group + group = flip_simplex_element_group(vertices, unflipped_group, orient < 0) + + # Now, any flipped element had its 0 vertex and 1 vertex exchanged. + # This changes the local facet nr, so we need to create and then + # fix our facial adjacency groups. To do that, we need to figure + # out which local facet numbers switched. + from meshmode.mesh import SimplexElementGroup + mm_simp_group = SimplexElementGroup(1, None, None, + dim=fdrake_mesh.cell_dimension()) + face_vertex_indices = mm_simp_group.face_vertex_indices() + # face indices of the faces not containing vertex 0 and not + # containing vertex 1, respectively + no_zero_face_ndx, no_one_face_ndx = None, None + for iface, face in enumerate(face_vertex_indices): + if 0 not in face: + no_zero_face_ndx = iface + elif 1 not in face: + no_one_face_ndx = iface + + unflipped_facial_adjacency_groups = \ + _get_firedrake_facial_adjacency_groups(fdrake_mesh) + + def flip_local_face_indices(arr, elements): + arr = np.copy(arr) + to_no_one = np.logical_and(orient[elements] < 0, arr == no_zero_face_ndx) + to_no_zero = np.logical_and(orient[elements] < 0, arr == no_one_face_ndx) + arr[to_no_one], arr[to_no_zero] = no_one_face_ndx, no_zero_face_ndx + return arr + + facial_adjacency_groups = [] + from meshmode.mesh import FacialAdjacencyGroup + for igroup, fagrps in enumerate(unflipped_facial_adjacency_groups): + facial_adjacency_groups.append({}) + for ineighbor_group, fagrp in six.iteritems(fagrps): + new_element_faces = flip_local_face_indices(fagrp.element_faces, + fagrp.elements) + new_neighbor_faces = flip_local_face_indices(fagrp.neighbor_faces, + fagrp.neighbors) + new_fagrp = FacialAdjacencyGroup(igroup=igroup, + ineighbor_group=ineighbor_group, + elements=fagrp.elements, + element_faces=new_element_faces, + neighbors=fagrp.neighbors, + neighbor_faces=new_neighbor_faces) + facial_adjacency_groups[igroup][ineighbor_group] = new_fagrp + + from meshmode.mesh import Mesh + return Mesh(vertices, [group], + boundary_tags=bdy_tags, + nodal_adjacency=nodal_adjacency, + facial_adjacency_groups=facial_adjacency_groups) + +# }}} + + +from firedrake import UnitSquareMesh +m = UnitSquareMesh(10, 10) +m.init() +mm_mesh = import_firedrake_mesh(m) diff --git a/meshmode/interop/firedrake/reference_cell.py b/meshmode/interop/firedrake/reference_cell.py index 100f426f..b8509582 100644 --- a/meshmode/interop/firedrake/reference_cell.py +++ b/meshmode/interop/firedrake/reference_cell.py @@ -32,7 +32,7 @@ __doc__ = """ # {{{ Map between reference simplices -def get_affine_reference_simplex_mapping(self, spat_dim, firedrake_to_meshmode=True): +def get_affine_reference_simplex_mapping(spat_dim, firedrake_to_meshmode=True): """ Returns a function which takes a numpy array points on one reference cell and maps each @@ -115,12 +115,16 @@ def get_finat_element_unit_nodes(finat_element): (equivalently, FInAT/FIAT's) reference coordinates :param finat_element: A :class:`finat.finiteelementbase.FiniteElementBase` - instance (i.e. a firedrake function space's reference element) + instance (i.e. a firedrake function space's reference element). + The refernce element of the finat element *MUST* be a simplex :return: A numpy array of shape *(dim, nunit_nodes)* holding the unit nodes used by this element. *dim* is the dimension spanned by the finat element's reference element (see its ``cell`` attribute) """ + from FIAT.reference_element import Simplex + assert isinstance(finat_element.cell, Simplex), \ + "Reference element of the finat element MUST be a simplex" # point evaluators is a list of functions *p_0,...,p_{n-1}*. # *p_i(f)* evaluates function *f* at node *i* (stored as a tuple), # so to recover node *i* we need to evaluate *p_i* at the identity diff --git a/meshmode/mesh/processing.py b/meshmode/mesh/processing.py index 97b9bbda..36e2505e 100644 --- a/meshmode/mesh/processing.py +++ b/meshmode/mesh/processing.py @@ -363,36 +363,36 @@ def test_volume_mesh_element_orientations(mesh): # {{{ flips -def flip_simplex_element_group(vertices, grp, grp_flip_flags): - from modepy.tools import barycentric_to_unit, unit_to_barycentric - - from meshmode.mesh import SimplexElementGroup - - if not isinstance(grp, SimplexElementGroup): - raise NotImplementedError("flips only supported on " - "exclusively SimplexElementGroup-based meshes") - - # Swap the first two vertices on elements to be flipped. - new_vertex_indices = grp.vertex_indices.copy() - new_vertex_indices[grp_flip_flags, 0] \ - = grp.vertex_indices[grp_flip_flags, 1] - new_vertex_indices[grp_flip_flags, 1] \ - = grp.vertex_indices[grp_flip_flags, 0] - - # Generate a resampling matrix that corresponds to the - # first two barycentric coordinates being swapped. +def get_simplex_element_flip_matrix(order, unit_nodes): + """ + Generate a resampling matrix that corresponds to the + first two barycentric coordinates being swapped. + + :param order: The order of the function space on the simplex, + (see second argument in + :fun:`modepy.simplex_best_available_basis`) + :param unit_nodes: A np array of unit nodes with shape + *(dim, nunit_nodes)* + + :return: A numpy array of shape *(dim, dim)* which, when applied + to the matrix of nodes (shaped *(dim, nunit_nodes)*) + corresponds to the first two barycentric coordinates + being swapped + """ + from modepy.tools import barycentric_to_unit, unit_to_barycentric - bary_unit_nodes = unit_to_barycentric(grp.unit_nodes) + bary_unit_nodes = unit_to_barycentric(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) + dim = unit_nodes.shape[0] flip_matrix = mp.resampling_matrix( - mp.simplex_best_available_basis(grp.dim, grp.order), - flipped_unit_nodes, grp.unit_nodes) + mp.simplex_best_available_basis(dim, order), + flipped_unit_nodes, unit_nodes) flip_matrix[np.abs(flip_matrix) < 1e-15] = 0 @@ -401,7 +401,26 @@ def flip_simplex_element_group(vertices, grp, grp_flip_flags): np.dot(flip_matrix, flip_matrix) - np.eye(len(flip_matrix))) < 1e-13 + return flip_matrix + + +def flip_simplex_element_group(vertices, grp, grp_flip_flags): + from meshmode.mesh import SimplexElementGroup + + if not isinstance(grp, SimplexElementGroup): + raise NotImplementedError("flips only supported on " + "exclusively SimplexElementGroup-based meshes") + + # Swap the first two vertices on elements to be flipped. + + new_vertex_indices = grp.vertex_indices.copy() + new_vertex_indices[grp_flip_flags, 0] \ + = grp.vertex_indices[grp_flip_flags, 1] + new_vertex_indices[grp_flip_flags, 1] \ + = grp.vertex_indices[grp_flip_flags, 0] + # Apply the flip matrix to the nodes. + flip_matrix = get_simplex_element_flip_matrix(grp.order, grp.unit_nodes) new_nodes = grp.nodes.copy() new_nodes[:, grp_flip_flags] = np.einsum( "ij,dej->dei", -- GitLab From 427c2761d1235845ebd4aa242ff8cee6c8b6a3e2 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Fri, 26 Jun 2020 09:25:35 -0500 Subject: [PATCH 037/221] Fixed neighbors on boundary to be negative --- meshmode/interop/firedrake/mesh.py | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 7ca5ea8f..00177590 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -221,9 +221,9 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh): from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL ext_neighbors = np.zeros(ext_elements.shape, dtype=np.int32) for ifac, marker in enumerate(top.exterior_facets.markers): - ext_neighbors[ifac] = boundary_tag_bit(BTAG_ALL) \ - | boundary_tag_bit(BTAG_REALLY_ALL) \ - | boundary_tag_bit(marker) + ext_neighbors[ifac] = -(boundary_tag_bit(BTAG_ALL) \ + | boundary_tag_bit(BTAG_REALLY_ALL) \ + | boundary_tag_bit(marker)) exterior_grp = FacialAdjacencyGroup(igroup=0, ineighbor=None, elements=ext_elements, @@ -409,12 +409,14 @@ def import_firedrake_mesh(fdrake_mesh): unflipped_facial_adjacency_groups = \ _get_firedrake_facial_adjacency_groups(fdrake_mesh) - def flip_local_face_indices(arr, elements): - arr = np.copy(arr) - to_no_one = np.logical_and(orient[elements] < 0, arr == no_zero_face_ndx) - to_no_zero = np.logical_and(orient[elements] < 0, arr == no_one_face_ndx) - arr[to_no_one], arr[to_no_zero] = no_one_face_ndx, no_zero_face_ndx - return arr + def flip_local_face_indices(faces, elements): + faces = np.copy(faces) + to_no_one = np.logical_and(orient[elements] < 0, + faces == no_zero_face_ndx) + to_no_zero = np.logical_and(orient[elements] < 0, + faces == no_one_face_ndx) + faces[to_no_one], faces[to_no_zero] = no_one_face_ndx, no_zero_face_ndx + return faces facial_adjacency_groups = [] from meshmode.mesh import FacialAdjacencyGroup @@ -423,15 +425,19 @@ def import_firedrake_mesh(fdrake_mesh): for ineighbor_group, fagrp in six.iteritems(fagrps): new_element_faces = flip_local_face_indices(fagrp.element_faces, fagrp.elements) - new_neighbor_faces = flip_local_face_indices(fagrp.neighbor_faces, - fagrp.neighbors) + if ineighbor_group is None: + new_neighbor_faces = fagrp.neighbor_faces + else: + new_neighbor_faces = \ + flip_local_face_indices(fagrp.neighbor_faces, + fagrp.neighbors) new_fagrp = FacialAdjacencyGroup(igroup=igroup, ineighbor_group=ineighbor_group, elements=fagrp.elements, element_faces=new_element_faces, neighbors=fagrp.neighbors, neighbor_faces=new_neighbor_faces) - facial_adjacency_groups[igroup][ineighbor_group] = new_fagrp + facial_adjacency_groups[igroup][ineighbor_group] = new_fagrp from meshmode.mesh import Mesh return Mesh(vertices, [group], @@ -440,9 +446,3 @@ def import_firedrake_mesh(fdrake_mesh): facial_adjacency_groups=facial_adjacency_groups) # }}} - - -from firedrake import UnitSquareMesh -m = UnitSquareMesh(10, 10) -m.init() -mm_mesh = import_firedrake_mesh(m) -- GitLab From ec692e12c8634a44b4ef11df6106b501c89c885a Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sun, 28 Jun 2020 11:09:58 -0500 Subject: [PATCH 038/221] Added a new functional connection implementation, began debugging with testing --- meshmode/interop/firedrake/connection.py | 256 +++++++++++++++++++++++ meshmode/interop/firedrake/mesh.py | 16 +- test/test_firedrake_interop.py | 8 +- 3 files changed, 272 insertions(+), 8 deletions(-) create mode 100644 meshmode/interop/firedrake/connection.py diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py new file mode 100644 index 00000000..4fa6e807 --- /dev/null +++ b/meshmode/interop/firedrake/connection.py @@ -0,0 +1,256 @@ +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" + +__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. +""" + +__doc__ = """ +.. autoclass:: FromFiredrakeConnection +""" + +import numpy as np +import numpy.linalg as la + +from meshmode.interop.firedrake.mesh import import_firedrake_mesh +from meshmode.interop.firedrake.reference_cell import ( + get_affine_reference_simplex_mapping, get_finat_element_unit_nodes) + +from meshmode.mesh.processing import get_simplex_element_flip_matrix + +from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory +from meshmode.discretization import Discretization + +from modepy import resampling_matrix + + +def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): + """ + flips :param:`nodes` in place according to :param:`orient` + + :param orient: An array of shape *(nelements)* of orientations, + >0 for positive, <0 for negative + :param nodes: a *(nelements, nunit_nodes)* or shaped array of nodes + :param flip_matrix: The matrix used to flip each negatively-oriented + element + :param unflip: If *True*, use transpose of :param:`flip_matrix` to + flip negatively-oriented elements + """ + # reorder nodes (Code adapted from + # meshmode.mesh.processing.flip_simplex_element_group) + + # ( round to int bc applying on integers) + flip_mat = np.rint(flip_matrix) + if unflip: + flip_mat = flip_mat.T + + # flipping twice should be identity + assert la.norm( + np.dot(flip_mat, flip_mat) + - np.eye(len(flip_mat))) < 1e-13 + + # flip nodes that need to be flipped, note that this point we act + # like we are in a DG space + nodes[orient < 0] = np.einsum( + "ij,ej->ei", + flip_mat, nodes[orient < 0]) + + +class FromFiredrakeConnection: + # FIXME : docs + """ + A connection created from a :mod:`firedrake` + ``"CG"`` or ``"DG"`` function space which creates a corresponding + meshmode discretization and allows + transfer of functions to and from :mod:`firedrake`. + + .. attribute:: to_discr + + The discretization corresponding to the firedrake function + space (DESCRIBE HERE) + """ + def __init__(self, cl_ctx, fdrake_fspace): + # FIXME : docs + # Ensure fdrake_fspace is a function space with appropriate reference + # element. + from firedrake.functionspaceimpl import WithGeometry + if not isinstance(fdrake_fspace, WithGeometry): + raise TypeError(":param:`fdrake_fspace` must be of firedrake type " + ":class:`WithGeometry`, not `%s`." + % type(fdrake_fspace)) + ufl_elt = fdrake_fspace.ufl_element() + + if ufl_elt.family() not in ('Lagrange', 'Discontinuous Lagrange'): + raise ValueError("the ``ufl_element().family()`` of " + ":param:`fdrake_fspace` must " + "be ``'Lagrange'`` or " + "``'Discontinuous Lagrange'``, not %s." + % ufl_elt.family()) + + # Create to_discr + mm_mesh, orient = import_firedrake_mesh(fdrake_fspace.mesh()) + factory = InterpolatoryQuadratureSimplexGroupFactory(ufl_elt.degree()) + self.to_discr = Discretization(cl_ctx, mm_mesh, factory) + + # Get meshmode unit nodes + element_grp = self.to_discr.groups[0] + mm_unit_nodes = element_grp.unit_nodes + # get firedrake unit nodes and map onto meshmode reference element + dim = fdrake_fspace.mesh().topological_dimension() + fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(dim, True) + fd_unit_nodes = get_finat_element_unit_nodes(fdrake_fspace.finat_element) + fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) + + # compute resampling matrices + self._resampling_mat_fd2mm = resampling_matrix(element_grp.basis(), + new_nodes=mm_unit_nodes, + old_nodes=fd_unit_nodes) + self._resampling_mat_mm2fd = resampling_matrix(element_grp.basis(), + new_nodes=fd_unit_nodes, + old_nodes=mm_unit_nodes) + + # handle reordering fd->mm + flip_mat = get_simplex_element_flip_matrix(ufl_elt.degree(), + fd_unit_nodes) + fd_cell_node_list = fdrake_fspace.cell_node_list + _reorder_nodes(orient, fd_cell_node_list, flip_mat, unflip=False) + self._reordering_arr_fd2mm = fd_cell_node_list.flatten() + + # handle reordering mm->fd (this only works in the discontinuous + # case) + nnodes = self.to_discr.nnodes + mm_cell_node_list = self.to_discr.groups[0].view(np.arange(nnodes)) + _reorder_nodes(orient, mm_cell_node_list, flip_mat, unflip=True) + self._reordering_arr_mm2fd = mm_cell_node_list.flatten() + + # Store things that we need for *from_fspace* + self._ufl_element = ufl_elt + self._mesh_geometry = fdrake_fspace.mesh() + self._fspace_cache = {} # map vector dim -> firedrake fspace + + def from_fspace(self, dim=None): + """ + Return a firedrake function space of the appropriate vector dimension + + :param dim: Either *None*, in which case a function space which maps + to scalar values is returned, or a positive integer *n*, + in which case a function space which maps into *\\R^n* + is returned + :return: A :mod:`firedrake` :class:`WithGeometry` which corresponds to + :attr:`to_discr` of the appropriate vector dimension + """ + # Cache the function spaces created to avoid high overhead. + # Note that firedrake is smart about re-using shared information, + # so this is not duplicating mesh/reference element information + if dim not in self._fspace_cache: + assert (isinstance(dim, int) and dim > 0) or dim is None + if dim is None: + from firedrake import FunctionSpace + self._fspace_cache[dim] = \ + FunctionSpace(self._mesh_geometry, + self._ufl_element.family(), + degree=self._ufl_element.degree()) + else: + from firedrake import VectorFunctionSpace + self._fspace_cache[dim] = \ + VectorFunctionSpace(self._mesh_geometry, + self._ufl_element.family(), + degree=self._ufl_element.degree(), + dim=dim) + return self._fspace_cache[dim] + + def from_firedrake(self, function, out=None): + """transport fiiredrake function""" + # make sure function is a firedrake function in an appropriate + # function space + from firedrake.function import Function + assert isinstance(function, Function), \ + ":param:`function` must be a :mod:`firedrake` Function" + assert function.function_space().ufl_element().family() \ + == self._ufl_element.family() and \ + function.function_space().ufl_element().degree() \ + == self._ufl_element.degree(), \ + ":param:`function` must live in a function space with the " \ + "same family and degree as ``self.from_fspace()``" + assert function.function_space().mesh() is self._mesh_geometry, \ + ":param:`function` mesh must be the same mesh as used by " \ + "``self.from_fspace().mesh()``" + + # Get function data as shape [dims][nnodes] or [nnodes] + function_data = function.dat.data.T + + if out is None: + shape = (self.to_discr.nnodes,) + if len(function_data.shape) > 1: + shape = (function_data.shape[1],) + shape + out = np.ndarray(shape, dtype=function_data.dtype) + # Reorder nodes + if len(out.shape) > 1: + out[:] = function_data.T[:, self._reordering_arr_fd2mm] + else: + out[:] = function_data[self._reordering_arr_fd2mm] + + # Resample at the appropriate nodes + out_view = self.to_discr.groups[0].view(out) + np.matmul(out_view, self._resampling_mat_fd2mm.T, out=out_view) + return out + + def from_meshmode(self, mm_field, out=None): + """transport meshmode field""" + if self._ufl_element.family() == 'Lagrange': + raise ValueError("Cannot convert functions from discontinuous " + " space (meshmode) to continuous firedrake " + " space (reference element family %s)." + % type(self._ufl_element.family())) + # make sure out is a firedrake function in an appropriate + # function space + if out is not None: + from firedrake.function import Function + assert isinstance(out, Function), \ + ":param:`out` must be a :mod:`firedrake` Function or *None*" + assert out.function_space().ufl_element().family() \ + == self._ufl_element.family() and \ + out.function_space().ufl_element().degree() \ + == self._ufl_element.degree(), \ + ":param:`out` must live in a function space with the " \ + "same family and degree as ``self.from_fspace()``" + assert out.function_space().mesh() is self._mesh_geometry, \ + ":param:`out` mesh must be the same mesh as used by " \ + "``self.from_fspace().mesh()`` or *None*" + else: + if len(mm_field.shape) == 1: + dim = None + else: + dim = mm_field.shape[0] + out = Function(self.from_fspace(dim)) + + # Handle 1-D case + if len(out.dat.data.shape) == 1 and len(mm_field.shape) > 1: + mm_field = mm_field.reshape(mm_field.shape[1]) + + # resample from nodes + resampled_view = self.to_discr.groups[0].view(out.dat.data.T) + np.matmul(resampled_view, self._resampling_mat_mm2fd.T, out=resampled_view) + # reorder data + if len(out.dat.data.shape) == 1: + out.dat.data[:] = out.dat.data[self._reordering_arr_mm2fd] + else: + out.dat.data[:] = out.dat.data[self._reordering_arr_mm2fd, :] + + return out diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 00177590..69e29447 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -308,6 +308,13 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, def import_firedrake_mesh(fdrake_mesh): # FIXME : docs + """ + :return: A tuple (meshmode mesh, firedrake_orient). + firedrake_orient < 0 is True for any negatively + oriented firedrake cell (which was flipped by meshmode) + and False for any positively oriented firedrake cell + (whcih was not flipped by meshmode) + """ # Type validation from firedrake.mesh import MeshGeometry if not isinstance(fdrake_mesh, MeshGeometry): @@ -440,9 +447,10 @@ def import_firedrake_mesh(fdrake_mesh): facial_adjacency_groups[igroup][ineighbor_group] = new_fagrp from meshmode.mesh import Mesh - return Mesh(vertices, [group], - boundary_tags=bdy_tags, - nodal_adjacency=nodal_adjacency, - facial_adjacency_groups=facial_adjacency_groups) + return (Mesh(vertices, [group], + boundary_tags=bdy_tags, + nodal_adjacency=nodal_adjacency, + facial_adjacency_groups=facial_adjacency_groups), + orient) # }}} diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index f3337c4a..cdd5cf25 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -27,7 +27,7 @@ from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) -from meshmode.interop.firedrake import FromFiredrakeConnection +from meshmode.interop.firedrake.connection import FromFiredrakeConnection import pytest @@ -86,7 +86,7 @@ def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): fdrake_fspace = FunctionSpace(fdrake_mesh, 'DG', fdrake_degree) cl_ctx = ctx_factory() fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) - to_discr = fdrake_connection.to_discr() + to_discr = fdrake_connection.to_discr meshmode_verts = to_discr.mesh.vertices # Ensure the meshmode mesh has one group and make sure both @@ -167,7 +167,7 @@ def test_function_transfer(ctx_factory, fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) transported_f = fdrake_connection.from_firedrake(fdrake_f) - to_discr = fdrake_connection.to_discr() + to_discr = fdrake_connection.to_discr with cl.CommandQueue(cl_ctx) as queue: nodes = to_discr.nodes().get(queue=queue) meshmode_f = meshmode_f_eval(nodes) @@ -183,7 +183,7 @@ def check_idempotency(fdrake_connection, fdrake_function): """ Make sure fd->mm->fd and mm->fd->mm are identity for DG spaces """ - fdrake_fspace = fdrake_connection.from_function_space() + fdrake_fspace = fdrake_connection.from_fspace() # Test for idempotency fd->mm->fd mm_field = fdrake_connection.from_firedrake(fdrake_function) -- GitLab From 6c45d61b9efd61780188c8c77ac6c6aeb97591d1 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sun, 28 Jun 2020 11:31:35 -0500 Subject: [PATCH 039/221] get function transfer to pass tests --- meshmode/interop/firedrake/connection.py | 14 ++++++++------ test/test_firedrake_interop.py | 5 ++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 4fa6e807..1e695e74 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -192,8 +192,8 @@ class FromFiredrakeConnection: ":param:`function` mesh must be the same mesh as used by " \ "``self.from_fspace().mesh()``" - # Get function data as shape [dims][nnodes] or [nnodes] - function_data = function.dat.data.T + # Get function data as shape [nnodes][dims] or [nnodes] + function_data = function.dat.data if out is None: shape = (self.to_discr.nnodes,) @@ -245,12 +245,14 @@ class FromFiredrakeConnection: mm_field = mm_field.reshape(mm_field.shape[1]) # resample from nodes - resampled_view = self.to_discr.groups[0].view(out.dat.data.T) - np.matmul(resampled_view, self._resampling_mat_mm2fd.T, out=resampled_view) + # FIXME : we're still allocating new memory when :param:`out` is supplied + resampled = np.copy(mm_field) + by_cell_view = self.to_discr.groups[0].view(resampled) + np.matmul(by_cell_view, self._resampling_mat_mm2fd.T, out=by_cell_view) # reorder data if len(out.dat.data.shape) == 1: - out.dat.data[:] = out.dat.data[self._reordering_arr_mm2fd] + out.dat.data[:] = resampled[self._reordering_arr_mm2fd] else: - out.dat.data[:] = out.dat.data[self._reordering_arr_mm2fd, :] + out.dat.data[:] = resampled.T[self._reordering_arr_mm2fd, :] return out diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index cdd5cf25..69fffee7 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -183,7 +183,10 @@ def check_idempotency(fdrake_connection, fdrake_function): """ Make sure fd->mm->fd and mm->fd->mm are identity for DG spaces """ - fdrake_fspace = fdrake_connection.from_fspace() + vdim = None + if len(fdrake_function.dat.data.shape) > 1: + vdim = fdrake_function.dat.data.shape[1] + fdrake_fspace = fdrake_connection.from_fspace(dim=vdim) # Test for idempotency fd->mm->fd mm_field = fdrake_connection.from_firedrake(fdrake_function) -- GitLab From b54c33870b7a18ad8746e3d0bb43b9b27937f66a Mon Sep 17 00:00:00 2001 From: benSepanski Date: Mon, 29 Jun 2020 09:14:21 -0500 Subject: [PATCH 040/221] Add docs, move over to newer functional interface --- meshmode/interop/firedrake/__init__.py | 218 +-------- meshmode/interop/firedrake/connection.py | 39 +- meshmode/interop/firedrake/function.py | 118 ----- meshmode/interop/firedrake/function_space.py | 206 -------- .../firedrake/function_space_coordless.py | 221 --------- .../firedrake/function_space_shared_data.py | 265 ---------- meshmode/interop/firedrake/mesh.py | 97 +++- meshmode/interop/firedrake/mesh_geometry.py | 461 ------------------ meshmode/interop/firedrake/mesh_topology.py | 221 --------- 9 files changed, 116 insertions(+), 1730 deletions(-) delete mode 100644 meshmode/interop/firedrake/function.py delete mode 100644 meshmode/interop/firedrake/function_space.py delete mode 100644 meshmode/interop/firedrake/function_space_coordless.py delete mode 100644 meshmode/interop/firedrake/function_space_shared_data.py delete mode 100644 meshmode/interop/firedrake/mesh_geometry.py delete mode 100644 meshmode/interop/firedrake/mesh_topology.py diff --git a/meshmode/interop/firedrake/__init__.py b/meshmode/interop/firedrake/__init__.py index 33daeb39..890dbaf7 100644 --- a/meshmode/interop/firedrake/__init__.py +++ b/meshmode/interop/firedrake/__init__.py @@ -20,16 +20,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -__doc__ = """ -.. autoclass:: FromFiredrakeConnection -.. autofunction:: import_firedrake_mesh -.. autofunction:: import_firedrake_function_space -""" import numpy as np +from meshmode.interop.firedrake.connection import FromFiredrakeConnection +from meshmode.interop.firedrake.mesh import import_firedrake_mesh - -# {{{ Helper functions to construct importers for firedrake objects +__all__ = ["FromFiredrakeConnection", "import_firedrake_mesh"] def _compute_cells_near_bdy(mesh, bdy_id): @@ -45,211 +41,3 @@ def _compute_cells_near_bdy(mesh, bdy_id): cell_is_near_bdy = np.any(np.isin(cell_node_list, boundary_nodes), axis=1) return np.arange(cell_node_list.shape[0], dtype=np.int32)[cell_is_near_bdy] - - -def import_firedrake_mesh(fdrake_mesh, near_bdy=None): - """ - :param fdrake_mesh: an instance of :mod:`firedrake`'s mesh, i.e. - of type :class:`firedrake.mesh.MeshGeometry`. - - :param near_bdy: If *None* does nothing. Otherwise should be a - boundary id. In this case, only cell ids - with >= 1 vertex on the given bdy id are used. - - :returns: A :class:`FiredrakeMeshGeometryImporter` object which - is created appropriately from :param:`fdrake_mesh` - - :raises TypeError: if :param:`fdrake_mesh` is of the wrong type - """ - # {{{ Make sure have an initialized firedrake mesh - - from firedrake.mesh import MeshGeometry, MeshTopology - if not isinstance(fdrake_mesh, MeshGeometry): - if isinstance(fdrake_mesh, MeshTopology): - raise TypeError(":param:`fdrake_mesh` must have an associated" - " geometry, but is of type MeshTopology") - raise TypeError(":param:`fdrake_mesh` must be of type" - "`firedrake.mesh.MeshGeometry`, not %s " - % type(fdrake_mesh)) - - fdrake_mesh.init() - - # }}} - - # {{{ Use the coordinates function space to built an importer - - coords_fspace = fdrake_mesh.coordinates.function_space() - cells_to_use = None - if near_bdy is not None: - cells_to_use = _compute_cells_near_bdy(fdrake_mesh, near_bdy) - - from meshmode.interop.FInAT.lagrange_element import \ - FinatLagrangeElementImporter - from meshmode.interop.firedrake.mesh_topology import \ - FiredrakeMeshTopologyImporter - topology_importer = FiredrakeMeshTopologyImporter(fdrake_mesh, - cells_to_use=cells_to_use) - finat_elt_importer = FinatLagrangeElementImporter(coords_fspace.finat_element) - - from meshmode.interop.firedrake.function_space_coordless import \ - FiredrakeFunctionSpaceImporter, FiredrakeCoordinatelessFunctionImporter - - coords_fspace_importer = FiredrakeFunctionSpaceImporter(coords_fspace, - topology_importer, - finat_elt_importer) - coordinates_importer = \ - FiredrakeCoordinatelessFunctionImporter(fdrake_mesh.coordinates, - coords_fspace_importer) - - # }}} - - # FIXME Allow for normals and no_normals_warn? as in the constructor - # for FiredrakeMeshGeometryImporter s - from meshmode.interop.firedrake.mesh_geometry import \ - FiredrakeMeshGeometryImporter - return FiredrakeMeshGeometryImporter(fdrake_mesh, coordinates_importer) - - -def import_firedrake_function_space(cl_ctx, fdrake_fspace, mesh_importer): - """ - Builds a - :class:`FiredrakeWithGeometryImporter` built from the given - :mod:`firedrake` function space :param:`fdrake_fspace` - - :param cl_ctx: A pyopencl computing context. This input - is not checked. - - :param:`fdrake_fspace` An instance of class :mod:`firedrake`'s - :class:`WithGeometry` class, representing a function - space defined on a concrete mesh - - :param mesh_importer: An instance of class - :class:`FiredrakeMeshGeometryImporter` defined on the - mesh underlying :param:`fdrake_fspace`. - - :returns: An instance of class - :mod :`meshmode.interop.firedrake.function_space` - :class:`FiredrakeWithGeometryImporter` built from the given - :param:`fdrake_fspace` - - :raises TypeError: If any input is the wrong type - :raises ValueError: If :param:`mesh_importer` is built on a mesh - different from the one underlying :param:`fdrake_fspace`. - """ - # {{{ Input checking - - from firedrake.functionspaceimpl import WithGeometry - if not isinstance(fdrake_fspace, WithGeometry): - raise TypeError(":param:`fdrake_fspace` must be of type " - ":class:`firedrake.functionspaceimpl.WithGeometry`, " - "not %s " % type(fdrake_fspace)) - - from meshmode.interop.firedrake.mesh_geometry import \ - FiredrakeMeshGeometryImporter - if not isinstance(mesh_importer, FiredrakeMeshGeometryImporter): - raise TypeError(":param:`mesh_importer` must be of type " - ":class:`FiredrakeMeshGeometryImporter` not `%s`." - % type(mesh_importer)) - - if mesh_importer.data != fdrake_fspace.mesh(): - raise ValueError("``mesh_importer.data`` and ``fdrake_fspace.mesh()`` " - "must be identical") - - # }}} - - # {{{ Make an importer for the topological function space - - from meshmode.interop.FInAT import FinatLagrangeElementImporter - from meshmode.interop.firedrake.function_space_coordless import \ - FiredrakeFunctionSpaceImporter - mesh_importer.init(cl_ctx) - finat_elt_importer = \ - FinatLagrangeElementImporter(fdrake_fspace.finat_element) - topological_importer = FiredrakeFunctionSpaceImporter(fdrake_fspace, - mesh_importer, - finat_elt_importer) - - # }}} - - # now we can make the full importer - from meshmode.interop.firedrake.function_space import \ - FiredrakeWithGeometryImporter - return FiredrakeWithGeometryImporter(cl_ctx, - fdrake_fspace, - topological_importer, - mesh_importer) - -# }}} - - -class FromFiredrakeConnection: - """ - Creates a method of transporting functions on a Firedrake mesh - back and forth between firedrake and meshmode - - .. attribute:: with_geometry_importer - - An instance of class :class:`FiredrakeWithGeometryImporter` - that converts the firedrake function space into - meshmode and allows for field conversion. - """ - def __init__(self, cl_ctx, fdrake_function_space): - """ - :param cl_ctx: A computing context - :param fdrake_function_space: A firedrake function space (an instance of - class :class:`WithGeometry`) - """ - mesh_importer = import_firedrake_mesh(fdrake_function_space.mesh()) - self.with_geometry_importer = \ - import_firedrake_function_space(cl_ctx, - fdrake_function_space, - mesh_importer) - - def from_function_space(self): - """ - :returns: the firedrake function space this object was created from - """ - return self.with_geometry_importer.data - - def to_factory(self): - """ - :returns: An InterpolatoryQuadratureSimplexGroupFactory - of the appropriate degree to use - """ - return self.with_geometry_importer.factory() - - def to_discr(self): - """ - :returns: A meshmode discretization corresponding to the - firedrake function space - """ - return self.with_geometry_importer.discretization() - - def from_firedrake(self, fdrake_function): - """ - Convert a firedrake function on this function space to a numpy - array - - :param queue: A CommandQueue - :param fdrake_function: A firedrake function on the function space - of this connection - - :returns: A numpy array holding the data of this function as - a field for the corresponding meshmode mesh - """ - return self.with_geometry_importer.convert_function(fdrake_function) - - def from_meshmode(self, field, fdrake_function): - """ - Store a numpy array holding a field on the meshmode mesh into a - firedrake function - - :param field: A numpy array representing a field on the meshmode version - of this mesh - :param fdrake_function: The firedrake function to store the data in - """ - from meshmode.interop.firedrake.function import \ - FiredrakeFunctionImporter - importer = FiredrakeFunctionImporter(fdrake_function, - self.with_geometry_importer) - importer.set_from_field(field) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 1e695e74..e3d950d3 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -73,7 +73,6 @@ def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): class FromFiredrakeConnection: - # FIXME : docs """ A connection created from a :mod:`firedrake` ``"CG"`` or ``"DG"`` function space which creates a corresponding @@ -83,10 +82,16 @@ class FromFiredrakeConnection: .. attribute:: to_discr The discretization corresponding to the firedrake function - space (DESCRIBE HERE) + space created with a + :class:`InterpolatoryQuadratureSimplexElementGroup`. """ def __init__(self, cl_ctx, fdrake_fspace): - # FIXME : docs + """ + :param cl_ctx: A :mod:`pyopencl` computing context + :param fdrake_fspace: A :mod:`firedrake` ``"CG"`` or ``"DG"`` + function space (of class :class:`WithGeometry`) built on + a mesh which is importable by :func:`import_firedrake_mesh`. + """ # Ensure fdrake_fspace is a function space with appropriate reference # element. from firedrake.functionspaceimpl import WithGeometry @@ -176,7 +181,19 @@ class FromFiredrakeConnection: return self._fspace_cache[dim] def from_firedrake(self, function, out=None): - """transport fiiredrake function""" + """ + transport firedrake function onto :attr:`to_discr` + + :param function: A :mod:`firedrake` function to transfer onto + :attr:`to_discr`. Its function space must have + the same family, degree, and mesh as ``self.from_fspace()``. + :param out: If *None* then ignored, otherwise a numpy array of the + shape *function.dat.data.shape.T* (i.e. + *(dim, nnodes)* or *(nnodes,)* in which :param:`function`'s + transported data is stored. + + :return: a numpy array holding the transported function + """ # make sure function is a firedrake function in an appropriate # function space from firedrake.function import Function @@ -212,7 +229,19 @@ class FromFiredrakeConnection: return out def from_meshmode(self, mm_field, out=None): - """transport meshmode field""" + """ + transport meshmode field from :attr:`to_discr` into an + appropriate firedrake function space. + + :param mm_field: A numpy array of shape *(nnodes,)* or *(dim, nnodes)* + representing a function on :attr:`to_distr`. + :param out: If *None* then ignored, otherwise a :mod:`firedrake` + function of the right function space for the transported data + to be stored in. + + :return: a :mod:`firedrake` :class:`Function` holding the transported + data. + """ if self._ufl_element.family() == 'Lagrange': raise ValueError("Cannot convert functions from discontinuous " " space (meshmode) to continuous firedrake " diff --git a/meshmode/interop/firedrake/function.py b/meshmode/interop/firedrake/function.py deleted file mode 100644 index 14ef3a46..00000000 --- a/meshmode/interop/firedrake/function.py +++ /dev/null @@ -1,118 +0,0 @@ -__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" - -__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. -""" - -import numpy as np - -from firedrake import Function - -from meshmode.interop import ExternalImportHandler -from meshmode.interop.firedrake.function_space import FiredrakeWithGeometryImporter -from meshmode.interop.firedrake.function_space_coordless import \ - FiredrakeCoordinatelessFunctionImporter - - -class FiredrakeFunctionImporter(ExternalImportHandler): - """ - An import handler for :mod:`firedrake` functions. - - .. attribute:: data - - A :mod:`firedrake` function - """ - def __init__(self, function, function_space_importer): - """ - :param function: A firedrake :class:`Function` or a - :class:`FiredrakeCoordinatelessFunctionImporter` - - :param function_space_importer: An instance of - :class:`FiredrakeWithGeometryImporter` - compatible with the function - space this function is built on - """ - # {{{ Check types - if not isinstance(function_space_importer, - FiredrakeWithGeometryImporter): - raise TypeError(":param:`function_space_importer` must be of " - "type FiredrakeWithGeometryImporter") - - if not isinstance(function, (Function, - FiredrakeCoordinatelessFunctionImporter)): - raise TypeError(":param:`function` must be one of " - "(:class:`firedrake.function.Function`," - " FiredrakeCoordinatelessFunctionImporter)") - # }}} - - super(FiredrakeFunctionImporter, self).__init__(function) - - self._function_space_importer = function_space_importer - if isinstance(function, Function): - self._topological_importer = \ - FiredrakeCoordinatelessFunctionImporter(function, - function_space_importer) - else: - self._topological_importer = function - - def function_space_importer(self): - """ - :returns: the :class:`FiredrakeWithGeometryImporter` instance - used for the function space this object's :attr:`data` lives on - """ - return self._function_space_importer - - @property - def topological_importer(self): - """ - :returns: The topological version of this object, i.e. the underlying - coordinateless importer (an instance of - :class:`FiredrakeCoordinatelessFunctionImporter`) - """ - return self._topological_importer - - def as_field(self): - """ - :returns: A numpy array holding the data of this function as a - field on the corresponding meshmode mesh - """ - return self.function_space_importer().convert_function(self) - - def set_from_field(self, field): - """ - Set function :attr:`data`'s data - from a field on the corresponding meshmode mesh. - - :param field: A numpy array representing a field on the corresponding - meshmode mesh - """ - # Handle 1-D case - if len(self.data.dat.data.shape) == 1 and len(field.shape) > 1: - field = field.reshape(field.shape[1]) - - # resample from nodes - group = self.function_space_importer().discretization().groups[0] - resampled = np.copy(field) - resampled_view = group.view(resampled) - resampling_mat = self.function_space_importer().resampling_mat(False) - np.matmul(resampled_view, resampling_mat.T, out=resampled_view) - - # reorder data - self.data.dat.data[:] = self.function_space_importer().reorder_nodes( - resampled, firedrake_to_meshmode=False)[:] diff --git a/meshmode/interop/firedrake/function_space.py b/meshmode/interop/firedrake/function_space.py deleted file mode 100644 index 85b3d6c2..00000000 --- a/meshmode/interop/firedrake/function_space.py +++ /dev/null @@ -1,206 +0,0 @@ -__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" - -__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 warnings import warn -import numpy as np - -from meshmode.interop import ExternalImportHandler -from meshmode.interop.firedrake.mesh_geometry import \ - FiredrakeMeshGeometryImporter -from meshmode.interop.firedrake.function_space_coordless import \ - FiredrakeFunctionSpaceImporter - -from meshmode.interop.firedrake.function_space_shared_data import \ - FiredrakeFunctionSpaceDataImporter - - -class FiredrakeWithGeometryImporter(ExternalImportHandler): - def __init__(self, - cl_ctx, - function_space, - function_space_importer, - mesh_importer): - """ - :param cl_ctx: A pyopencl context - - :param function_space: A firedrake :class:`WithGeometry` - - :param function_space_importer: An instance of class - :class:`FiredrakeFunctionSpaceImporter` for the - underlying topological function space of :param`function_space` - - :param mesh_importer: An instance of class - :class:`FiredrakeMeshGeometryImporter` for the mesh - that :param:`function_space` is built on - """ - # FIXME use on bdy - # {{{ Check input - from firedrake.functionspaceimpl import WithGeometry - if not isinstance(function_space, WithGeometry): - raise TypeError(":arg:`function_space` must be of type" - " :class:`firedrake.functionspaceimpl.WithGeometry") - - if not isinstance(function_space_importer, - FiredrakeFunctionSpaceImporter): - raise TypeError(":arg:`function_space_importer` must be of type" - " FiredrakeFunctionSpaceImporter") - - if not isinstance(mesh_importer, FiredrakeMeshGeometryImporter): - raise TypeError(":arg:`mesh_importer` must be of type" - " FiredrakeMeshGeometryImporter") - - # Make sure importers are good for given function space - assert function_space_importer.data == function_space.topological - assert mesh_importer.data == function_space.mesh() - - # }}} - - # Initialize as importer - super(FiredrakeWithGeometryImporter, self).__init__(function_space) - self._topology_importer = function_space_importer - self._mesh_importer = mesh_importer - self._cl_ctx = cl_ctx - - finat_element_importer = function_space_importer.finat_element_importer - self._shared_data = \ - FiredrakeFunctionSpaceDataImporter(cl_ctx, - mesh_importer, - finat_element_importer) - - mesh_order = mesh_importer.data.coordinates.\ - function_space().finat_element.degree - if mesh_order > self.data.finat_element.degree: - warn("Careful! When the mesh order is higher than the element" - " order. Conversion MIGHT work, but maybe not..." - " To be honest I really don't know.") - - # Used to convert between refernce node sets - self._resampling_mat_fd2mm = None - self._resampling_mat_mm2fd = None - - def __getattr__(self, attr): - return getattr(self._topology_importer, attr) - - def mesh_importer(self): - return self._mesh_importer - - def _reordering_array(self, firedrake_to_meshmode): - if firedrake_to_meshmode: - return self._shared_data.firedrake_to_meshmode() - return self._shared_data.meshmode_to_firedrake() - - def factory(self): - return self._shared_data.factory() - - def discretization(self): - return self._shared_data.discretization() - - def resampling_mat(self, firedrake_to_meshmode): - if self._resampling_mat_fd2mm is None: - element_grp = self.discretization().groups[0] - self._resampling_mat_fd2mm = \ - self.finat_element_importer.make_resampling_matrix(element_grp) - - self._resampling_mat_mm2fd = np.linalg.inv(self._resampling_mat_fd2mm) - - # return the correct resampling matrix - if firedrake_to_meshmode: - return self._resampling_mat_fd2mm - return self._resampling_mat_mm2fd - - def reorder_nodes(self, nodes, firedrake_to_meshmode=True): - """ - :arg nodes: An array representing function values at each of the - dofs, if :arg:`firedrake_to_meshmode` is *True*, should - be of shape (ndofs) or (ndofs, xtra_dims). - If *False*, should be of shape (ndofs) or (xtra_dims, ndofs) - :arg firedrake_to_meshmode: *True* iff firedrake->meshmode, *False* - if reordering meshmode->firedrake - """ - # {{{ Case where shape is (ndofs,), just apply reordering - - if len(nodes.shape) == 1: - return nodes[self._reordering_array(firedrake_to_meshmode)] - - # }}} - - # {{{ Else we have (xtra_dims, ndofs) or (ndofs, xtra_dims): - - # Make sure we have (xtra_dims, ndofs) ordering - if firedrake_to_meshmode: - nodes = nodes.T - - reordered_nodes = nodes[:, self._reordering_array(firedrake_to_meshmode)] - - # if converting mm->fd, change to (ndofs, xtra_dims) - if not firedrake_to_meshmode: - reordered_nodes = reordered_nodes.T - - return reordered_nodes - - # }}} - - def convert_function(self, function): - """ - Convert a firedrake function defined on this function space - to a meshmode form, reordering data as necessary - and resampling to the unit nodes in meshmode - - :param function: A firedrake function or a - :class:`FiredrakeFunctionImporter` instance - - :returns: A numpy array - """ - from meshmode.interop.firedrake.function import FiredrakeFunctionImporter - if isinstance(function, FiredrakeFunctionImporter): - function = function.data - - # FIXME: Check that function can be converted! Maybe check the - # shared data? We can just do the check below - # but it's a little too stiff - #assert function.function_space() == self.data - - nodes = function.dat.data - - # handle vector function spaces differently, hence the shape checks - - # {{{ Reorder the nodes to have positive orientation - # (and if a vector, now have meshmode [dims][nnodes] - # instead of firedrake [nnodes][dims] shape) - - if len(nodes.shape) > 1: - new_nodes = [self.reorder_nodes(nodes.T[i], True) for i in - range(nodes.shape[1])] - nodes = np.array(new_nodes) - else: - nodes = self.reorder_nodes(nodes, True) - - # }}} - - # {{{ Now convert to meshmode reference nodes - node_view = self.discretization().groups[0].view(nodes) - # Multiply each row (repping an element) by the resampler - np.matmul(node_view, self.resampling_mat(True).T, out=node_view) - - # }}} - - return nodes diff --git a/meshmode/interop/firedrake/function_space_coordless.py b/meshmode/interop/firedrake/function_space_coordless.py deleted file mode 100644 index e6fdec6c..00000000 --- a/meshmode/interop/firedrake/function_space_coordless.py +++ /dev/null @@ -1,221 +0,0 @@ -__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" - -__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 warnings import warn # noqa - -from meshmode.interop import ExternalImportHandler -from meshmode.interop.firedrake.mesh_topology import \ - FiredrakeMeshTopologyImporter -from meshmode.interop.FInAT import FinatLagrangeElementImporter - - -# {{{ Function space for coordinateless functions to live on - - -class FiredrakeFunctionSpaceImporter(ExternalImportHandler): - """ - This is a counterpart of :class:`firedrake.functionspaceimpl.FunctionSpace`, - - This is not what usually results from a call to - :func:`firedrake.functionspace.FunctionSpace`. - When someone calls :func:`firedrake.functionspace.FunctionSpace` - from the user side, they are usually talking about a - :class:`firedrake.functionspaceimpl.WithGeometry`. - - Generally, this class is here to match Firedrake's design - principles, i.e. so that we have something to put CoordinatelessFunction - import handlers on. - - Just like we have "topological" and "geometric" - meshes, one can think of think of this as a "topological" function space - whose counterpart is a "WithGeometry" - - .. attribute:: data - - An instance of :class:`firedrake.functionspaceimpl.FunctionSpace`, i.e. - a function space built to hold coordinateless function (functions - whose domain is just a set of points with some topology, but no - coordinates) - - .. attribute:: finat_element_importer - - An instance of - :class:`meshmode.interop.FInAT.FinatLagrangeElementImporter` which - is an importer for the *finat_element* of :attr:`data`. - """ - def __init__(self, function_space, mesh_importer, finat_element_importer): - """ - :param function_space: A :mod:`firedrake` - :class:`firedrake.functionspaceimpl.FunctionSpace` or - :class:`firedrake.functionspaceimpl.WithGeometry`. In the - latter case, the underlying ``FunctionSpace`` is extracted - from the ``WithGeometry``. - :param mesh_importer: An instance - of :class:`FiredrakeMeshTopology` created from the topological - mesh of :param:`function_space` - :param finat_element_importer: An instance - of :class:`FinatLagrangeElementIMporter` created from the - finat_element of :param:`function_space` - - :raises TypeError: If any of the arguments are the wrong type - :raises ValueError: If :param:`mesh_importer` or - :param:`finat_element_importer` are importing - a different mesh or finat element than the provided - :param:`function_space` is built on. - """ - # We want to ignore any geomery - function_space = function_space.topological - mesh_importer = mesh_importer.topological_importer - - # {{{ Some type-checking - from firedrake.functionspaceimpl import FunctionSpace, WithGeometry - - if not isinstance(function_space, (FunctionSpace, WithGeometry)): - raise TypeError(":param:`function_space` must be of type " - "``firedrake.functionspaceimpl.FunctionSpace`` " - "or ``firedrake.functionspaceimpl.WithGeometry`` ", - "not %s" % type(function_space)) - - if not isinstance(mesh_importer, FiredrakeMeshTopologyImporter): - raise TypeError(":param:`mesh_importer` must be either *None* " - "or of type :class:`meshmode.interop.firedrake." - "FiredrakeMeshTopologyImporter`") - if not function_space.mesh().topological == mesh_importer.data: - raise ValueError(":param:`mesh_importer`'s *data* attribute " - "must be the same mesh as returned by " - ":param:`function_space`'s *mesh()* method.") - - if not isinstance(finat_element_importer, FinatLagrangeElementImporter): - raise TypeError(":param:`finat_element_importer` must be either " - "*None* " - "or of type :class:`meshmode.interop.FInAT." - "FinatLagragneElementImporter`") - if not function_space.finat_element == finat_element_importer.data: - raise ValueError(":param:`finat_element_importer`'s *data* " - "attribute " - "must be the same finat element as " - ":param:`function_space`'s *finat_element*" - " attribute.") - - # }}} - - # finish initialization - super(FiredrakeFunctionSpaceImporter, self).__init__(function_space) - - self._mesh_importer = mesh_importer - self.finat_element_importer = finat_element_importer - - @property - def topological_importer(self): - """ - A reference to self for compatability with 'geometrical' function spaces - """ - return self - - def mesh_importer(self): - """ - Return this object's mesh importer - """ - return self._mesh_importer - -# }}} - - -# {{{ Container to hold the coordinateless functions themselves - - -class FiredrakeCoordinatelessFunctionImporter(ExternalImportHandler): - """ - A coordinateless function, i.e. a function defined on a set of - points which only have an associated topology, no associated - geometry. - - .. attribute:: data - - An instance of :mod:`firedrake` class - :class:`firedrake.function.CoordinatelessFunction`. - Note that a coordinateless function object in firedrake - references concrete, but mutable data. - """ - def __init__(self, function, function_space_importer): - """ - :param function: The instance of - :class:`firedrake.function.CoordinatelessFunction` - which this object is importing. Becomes the - :attr:`data` attribute. - - :param function_space_importer: An instance of - :class:`FiredrakeFunctionSpaceImporter` - which is importing the topological function space that - :param:`function` is built on. - - :raises TypeError: If either parameter is the wrong type - :raises ValueError: If :param:`function_space_importer` is an - importer for a firedrake function space which is not - identical to ``function.topological.function_space()`` - """ - # Throw out geometric information if there is any - function = function.topological - function_space_importer = function_space_importer.topological_importer - - # {{{ Some type-checking - - from firedrake.function import Function, CoordinatelessFunction - if not isinstance(function, (CoordinatelessFunction, Function)): - raise TypeError(":param:`function` must be of type " - "`firedrake.function.CoordinatelessFunction` " - " or `firedrdake.function.Function`") - - if not isinstance(function_space_importer, - FiredrakeFunctionSpaceImporter): - raise TypeError(":param:`function_space_importer` must be of type " - "`meshmode.interop.firedrake." - "FiredrakeFunctionSpaceImporter`.") - - if not function_space_importer.data == function.function_space(): - raise ValueError(":param:`function_space_importer`'s *data* " - "attribute and ``function.function_space()`` " - "must be identical.") - - super(FiredrakeCoordinatelessFunctionImporter, self).__init__(function) - - self._function_space_importer = function_space_importer - - def function_space_importer(self): - """ - Return the - :class:`meshmode.interop.firedrake.FiredrakeFunctionSpaceImporter` - instance being used to import the function space - of the underlying firedrake function this object represents. - """ - return self._function_space_importer - - @property - def topological_importer(self): - """ - A reference to self for compatability with functions that have - coordinates. - """ - return self - - -# }}} diff --git a/meshmode/interop/firedrake/function_space_shared_data.py b/meshmode/interop/firedrake/function_space_shared_data.py deleted file mode 100644 index c7b316a7..00000000 --- a/meshmode/interop/firedrake/function_space_shared_data.py +++ /dev/null @@ -1,265 +0,0 @@ -__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" - -__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. -""" - -import numpy as np -import numpy.linalg as la -from decorator import decorator -from firedrake.functionspacedata import FunctionSpaceData -from meshmode.discretization import Discretization - -from meshmode.interop import ExternalImportHandler -from meshmode.interop.FInAT.lagrange_element import \ - FinatLagrangeElementImporter - - -@decorator -def cached(f, mesh_importer, key, *args, **kwargs): - """ - Exactly :func:`firedrake.functionspacedata.cached`, but - caching on mesh Geometry instead - - :param f: The function to cache. - :param mesh_importer: The mesh_importer to cache on (should have a - ``_shared_data_cache`` object). - :param key: The key to the cache. - :args args: Additional arguments to ``f``. - :kwargs kwargs: Additional keyword arguments to ``f``.""" - assert hasattr(mesh_importer, "_shared_data_cache") - cache = mesh_importer._shared_data_cache[f.__name__] - try: - return cache[key] - except KeyError: - result = f(mesh_importer, key, *args, **kwargs) - cache[key] = result - return result - - -def reorder_nodes(orient, nodes, flip_matrix, unflip=False): - """ - flips :param:`nodes` according to :param:`orient` - - :param orient: An array of shape *(nelements)* of orientations, - >0 for positive, <0 for negative - :param nodes: a *(nelements, nunit_nodes)* or - *(dim, nelements, nunit_nodes)* shaped array of nodes - :param flip_matrix: The matrix used to flip each negatively-oriented - element - :param unflip: If *True*, use transpose of :param:`flip_matrix` to - flip negatively-oriented elements - """ - # reorder nodes (Code adapted from - # meshmode.mesh.processing.flip_simplex_element_group) - - # ( round to int bc applying on integers) - flip_mat = np.rint(flip_matrix) - if unflip: - flip_mat = flip_mat.T - - # flipping twice should be identity - assert la.norm( - np.dot(flip_mat, flip_mat) - - np.eye(len(flip_mat))) < 1e-13 - - # }}} - - # {{{ flip nodes that need to be flipped, note that this point we act - # like we are in a DG space - - # if a vector function space, nodes array is shaped differently - if len(nodes.shape) > 2: - nodes[orient < 0] = np.einsum( - "ij,ejk->eik", - flip_mat, nodes[orient < 0]) - # Reshape to [nodes][vector dims] - nodes = nodes.reshape( - nodes.shape[0] * nodes.shape[1], nodes.shape[2]) - # pytential wants [vector dims][nodes] not [nodes][vector dims] - nodes = nodes.T.copy() - else: - nodes[orient < 0] = np.einsum( - "ij,ej->ei", - flip_mat, nodes[orient < 0]) - # convert from [element][unit_nodes] to - # global node number - nodes = nodes.flatten() - - -@cached -def reordering_array(mesh_importer, key, fspace_data): - """ - :param key: A tuple (finat_element_importer, firedrake_to_meshmode) - where *firedrake_to_meshmode* is a *bool*, *True* indicating - firedrake->meshmode reordering, *False* meshmode->firedrake - - :param fspace_data: A :mod:`firedrake` instance of shared - function space data, i.e. - :class:`firedrake.functionspacedata.FunctionSpaceData` - - :returns: an *np.array* that can reorder the data by composition, - see :meth:`reorder_nodes` in class :class:`FiredrakeWithGeometryImporter` - """ - finat_element_importer, firedrake_to_meshmode = key - assert isinstance(finat_element_importer, FinatLagrangeElementImporter) - - cell_node_list = fspace_data.entity_node_lists[mesh_importer.data.cell_set] - if mesh_importer.icell_to_fd is not None: - cell_node_list = cell_node_list[mesh_importer.icell_to_fd] - - num_fd_nodes = fspace_data.node_set.size - - nelements = cell_node_list.shape[0] - nunit_nodes = cell_node_list.shape[1] - num_mm_nodes = nelements * nunit_nodes - - if firedrake_to_meshmode: - nnodes = num_fd_nodes - else: - nnodes = num_mm_nodes - order = np.arange(nnodes) - - # Put into cell-node list if firedrake-to meshmode (so can apply - # flip-mat) - if firedrake_to_meshmode: - new_order = order[cell_node_list] - # else just need to reshape new_order so that can apply flip-mat - else: - new_order = order.reshape( - (order.shape[0]//nunit_nodes, nunit_nodes) + order.shape[1:]) - - flip_mat = finat_element_importer.flip_matrix() - reorder_nodes(mesh_importer.orientations(), new_order, flip_mat, - unflip=firedrake_to_meshmode) - new_order = new_order.flatten() - - # Resize new_order if going meshmode->firedrake and meshmode - # has duplicate nodes (e.g if used a CG fspace) - # - # TODO: this is done VERY LAZILY (NOT GOOD) - if not firedrake_to_meshmode and num_fd_nodes != num_mm_nodes: - newnew_order = np.zeros(num_fd_nodes, dtype=np.int32) - pyt_ndx = 0 - for nodes in cell_node_list: - for fd_index in nodes: - newnew_order[fd_index] = new_order[pyt_ndx] - pyt_ndx += 1 - - new_order = newnew_order - - # }}} - - return new_order - - -@cached -def get_factory(mesh_importer, degree): - from meshmode.discretization.poly_element import \ - InterpolatoryQuadratureSimplexGroupFactory - return InterpolatoryQuadratureSimplexGroupFactory(degree) - - -@cached -def get_discretization(mesh_importer, key): - finat_element_importer, cl_ctx = key - assert isinstance(finat_element_importer, FinatLagrangeElementImporter) - - degree = finat_element_importer.data.degree - discretization = Discretization(cl_ctx, - mesh_importer.meshmode_mesh(), - get_factory(mesh_importer, degree)) - - return discretization - - -class FiredrakeFunctionSpaceDataImporter(ExternalImportHandler): - """ - This is not *quite* the usual thought of a - :class:`ExternalImportHandler`. - - It handles an "import" in the sense that a mesh & finat element - define a lot of the data of a function space that - can be shared between multiple function spaces on the - same mesh, so we use - this object to store data based on the mesh and finat element, - cached on the mesh geometry. - In contrast to firedrake's shared data, we need the mesh - geometry - - .. attribute:: data - - A tuple whose first entry is an instance of - class :class:`FiredrakeMeshGeometryImporter` and second - entry is an instance of class - :class:`FinatLagrangeElementImporter` - """ - # FIXME: Give two finat elts - def __init__(self, cl_ctx, mesh_importer, finat_element_importer): - """ - Note that :param:`mesh_importer` and :param:`finat_element_importer` - are used for checking - """ - if mesh_importer.topological_importer == mesh_importer: - raise TypeError(":param:`mesh_importer` is a " - "FiredrakeMeshTopologyImporter, but " - " must be a FiredrakeMeshGeometryImporter") - importer = (mesh_importer.data, finat_element_importer.data) - super(FiredrakeFunctionSpaceDataImporter, self).__init__(importer) - - self._fspace_data = FunctionSpaceData(mesh_importer.data, - finat_element_importer.data) - - self._cl_ctx = cl_ctx - self._mesh_importer = mesh_importer - self._finat_element_importer = finat_element_importer - self._discretization = None - - def firedrake_to_meshmode(self): - """ - See :func:`reordering_array` - """ - return reordering_array(self._mesh_importer, - (self._finat_element_importer, True), - self._fspace_data) - - def meshmode_to_firedrake(self): - """ - See :func:`reordering_array` - """ - return reordering_array(self._mesh_importer, - (self._finat_element_importer, False), - self._fspace_data) - - def discretization(self): - """ - Creates a discretization on the mesh returned by this object's - mesh importer's :func:`meshmode_mesh()` - """ - return get_discretization(self._mesh_importer, - (self._finat_element_importer, self._cl_ctx)) - - def factory(self): - """ - Returns a :mod:`meshmode` - :class:`InterpolatoryQuadratureSimplexGroupFactory` - of the same degree as this object's finat element. - """ - degree = self._finat_element_importer.data.degree - return get_factory(self._mesh_importer, degree) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 69e29447..26dbed76 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -21,8 +21,7 @@ THE SOFTWARE. """ __doc__ = """ -.. autofunction:: get_firedrake_nodal_adjacency_group -.. autofunction:: get_firedrake_vertex_indices +.. autofunction:: import_firedrake_mesh """ from warnings import warn # noqa @@ -34,9 +33,25 @@ import six def _get_firedrake_nodal_info(fdrake_mesh_topology): - # FIXME: do docs """ - Get nodal adjacency, and vertex indices from a firedrake mesh topology + Get nodal adjacency and vertex indices corresponding + to a firedrake mesh topology. Note that as we do not use + geometric information, there is no guarantee that elements + have a positive orientation. + + The elements (in firedrake lingo, the cells) + are guaranteed to have the same numbering in :mod:`meshmode` + as :mod:`firedrdake` + + :param fdrake_mesh_topology: A :mod:`firedrake` instance of class + :class:`MeshTopology` or :class:`MeshGeometry`. + + :return: Returns *vertex_indices* as a numpy array of shape + *(nelements, ref_element.nvertices)* (as described by + the ``vertex_indices`` attribute of a :class:`MeshElementGroup`) + and a :class:`NodalAdjacency` constructed from + :param:`fdrake_mesh_topology` + as a tuple *(vertex_indices, nodal_adjacency)*. """ top = fdrake_mesh_topology.topology @@ -147,9 +162,19 @@ def _get_firedrake_boundary_tags(fdrake_mesh): return tuple(bdy_tags) -def _get_firedrake_facial_adjacency_groups(fdrake_mesh): - # FIXME: do docs - top = fdrake_mesh.topology +def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology): + """ + Return facial_adjacency_groups corresponding to + the given firedrake mesh topology. Note that as we do not + have geometric information, elements may need to be + flipped later. + + :param fdrake_mesh_topology: A :mod:`firedrake` instance of class + :class:`MeshTopology` or :class:`MeshGeometry`. + :return: A list of maps to :class:`FacialAdjacencyGroup`s as required + by a :mod:`meshmode` :class:`Mesh` + """ + top = fdrake_mesh_topology.topology # We only need one group # for interconnectivity and one for boundary connectivity. # The tricky part is moving from firedrake local facet numbering @@ -221,8 +246,8 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh): from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL ext_neighbors = np.zeros(ext_elements.shape, dtype=np.int32) for ifac, marker in enumerate(top.exterior_facets.markers): - ext_neighbors[ifac] = -(boundary_tag_bit(BTAG_ALL) \ - | boundary_tag_bit(BTAG_REALLY_ALL) \ + ext_neighbors[ifac] = -(boundary_tag_bit(BTAG_ALL) + | boundary_tag_bit(BTAG_REALLY_ALL) | boundary_tag_bit(marker)) exterior_grp = FacialAdjacencyGroup(igroup=0, ineighbor=None, @@ -240,12 +265,16 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh): def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, normals=None, no_normals_warn=True): - # FIXME : Fix docs """ Return the orientations of the mesh elements: - an array, the *i*th element is > 0 if the *ith* element - is positively oriented, < 0 if negatively oriented. - Mesh must have co-dimension 0 or 1. + + :param fdrake_mesh: A :mod:`firedrake` instance of :class:`MeshGeometry` + + :param unflipped_group: A :class:`SimplexElementGroup` instance with + (potentially) some negatively oriented elements. + + :param vertices: The vertex coordinates as a numpy array of shape + *(ambient_dim, nvertices)* :param normals: _Only_ used if :param:`mesh` is a 1-surface embedded in 2-space. In this case, @@ -259,6 +288,10 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, :param no_normals_warn: If *True*, raises a warning if :param:`mesh` is a 1-surface embedded in 2-space and :param:`normals` is *None*. + + :return: A numpy array, the *i*th element is > 0 if the *ith* element + is positively oriented, < 0 if negatively oriented. + Mesh must have co-dimension 0 or 1. """ # compute orientations tdim = fdrake_mesh.topological_dimension() @@ -309,11 +342,38 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, def import_firedrake_mesh(fdrake_mesh): # FIXME : docs """ - :return: A tuple (meshmode mesh, firedrake_orient). - firedrake_orient < 0 is True for any negatively - oriented firedrake cell (which was flipped by meshmode) - and False for any positively oriented firedrake cell - (whcih was not flipped by meshmode) + Create a :mod:`meshmode` :class:`Mesh` from a :mod:`firedrake` + :class:`MeshGeometry` with the same cells/elements, vertices, nodes, + mesh order, and facial adjacency. + + The vertex and node coordinates will be the same, as well + as the cell/element ordering. However, :mod:`firedrake` + does not require elements to be positively oriented, + so any negative elements are flipped + as in :func:`meshmode.processing.flip_simplex_element_group`. + + The flipped cells/elements are identified by the returned + *firedrake_orient* array + + :param fdrake_mesh: A :mod:`firedrake` :class:`MeshGeometry`. + This mesh **must** be in a space of ambient dimension + 1, 2, or 3 and have co-dimension of 0 or 1. + It must use a simplex as a reference element. + + Finally, its ``coordinates`` attribute must have a function + space whose *finat_element* associates a degree + of freedom with each vertex. In particular, + this means that the vertices of the mesh must have well-defined + coordinates. + For those unfamiliar with :mod:`firedrake`, you can + verify this by looking at the ``[0]`` entry of + ``fdrake_mesh.coordinates.function_space().finat_element.entity_dofs()``. + + :return: A tuple *(meshmode mesh, firedrake_orient)*. + ``firedrake_orient < 0`` is *True* for any negatively + oriented firedrake cell (which was flipped by meshmode) + and False for any positively oriented firedrake cell + (whcih was not flipped by meshmode). """ # Type validation from firedrake.mesh import MeshGeometry @@ -324,6 +384,7 @@ def import_firedrake_mesh(fdrake_mesh): assert fdrake_mesh.ufl_cell().is_simplex(), "Mesh must use simplex cells" gdim = fdrake_mesh.geometric_dimension() tdim = fdrake_mesh.topological_dimension() + assert gdim in [1, 2, 3], "Mesh must be in space of ambient dim 1, 2, or 3" assert gdim - tdim in [0, 1], "Mesh co-dimension must be 0 or 1" fdrake_mesh.init() diff --git a/meshmode/interop/firedrake/mesh_geometry.py b/meshmode/interop/firedrake/mesh_geometry.py deleted file mode 100644 index da965cbe..00000000 --- a/meshmode/interop/firedrake/mesh_geometry.py +++ /dev/null @@ -1,461 +0,0 @@ -__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" - -__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 warnings import warn # noqa -from collections import defaultdict -import numpy as np - -from meshmode.interop import ExternalImportHandler -from meshmode.interop.firedrake.function_space_coordless import \ - FiredrakeCoordinatelessFunctionImporter - - -class FiredrakeMeshGeometryImporter(ExternalImportHandler): - """ - This takes a :mod:`firedrake` :class:`MeshGeometry` - and converts its data so that :mod:`meshmode` can handle it. - - .. attribute:: data - - A :mod:`firedrake` :class:`MeshGeometry` instance - """ - - def __init__(self, - mesh, - coordinates_importer, - normals=None, - no_normals_warn=True): - """ - :param mesh: A :mod:`firedrake` :class:`MeshGeometry`. - We require that :aram:`mesh` have co-dimesnion - of 0 or 1. - Moreover, if :param:`mesh` is a 2-surface embedded in 3-space, - we _require_ that :function:`init_cell_orientations` - has been called already. - - :param coordinates_importer: An instance of - class :class:`FiredrakeCoordinatelessFunctionImporter` - to use, mapping the points of the topological mesh - to their coordinates (The function is coordinateless - in the sense that its *domain* has no coordinates) - - For other params see :meth:`orientations` - """ - super(FiredrakeMeshGeometryImporter, self).__init__(mesh) - - # {{{ Make sure input data is valid - - # Ensure is not a topological mesh - if mesh.topological == mesh: - raise TypeError(":param:`mesh` must be of type" - " :class:`firedrake.mesh.MeshGeometry`") - - # Ensure dimensions are in appropriate ranges - supported_dims = [1, 2, 3] - if mesh.geometric_dimension() not in supported_dims: - raise ValueError("Geometric dimension is %s. Geometric " - " dimension must be one of range %s" - % (mesh.geometric_dimension(), supported_dims)) - - # Raise warning if co-dimension is not 0 or 1 - co_dimension = mesh.geometric_dimension() - mesh.topological_dimension() - if co_dimension not in [0, 1]: - raise ValueError("Codimension is %s, but must be 0 or 1." % - (co_dimension)) - - # Ensure coordinates are coordinateless - if not isinstance(coordinates_importer, - FiredrakeCoordinatelessFunctionImporter): - raise ValueError(":param:`coordinates_importer` must be of type" - " FiredrakeCoordinatelessFunctionImporter") - - fspace_importer = coordinates_importer.function_space_importer() - topology_importer = fspace_importer.mesh_importer() - - if topology_importer.data != mesh.topology: - raise ValueError("Topology :param:`coordinates` lives on must be " - "the same " - "topology that :param:`mesh` lives on") - - # }}} - - # For sharing data like in firedrake - self._shared_data_cache = defaultdict(dict) - - # Store input information - self._coordinates_importer = coordinates_importer - self._topology_importer = topology_importer - - self._normals = normals - self._no_normals_warn = no_normals_warn - - # To be computed later - self._vertex_indices = None - self._vertices = None - self._nodes = None - self._group = None - self._orient = None - self._facial_adjacency_groups = None - self._meshmode_mesh = None - - def callback(cl_ctx): - """ - Finish initialization by creating a coordinates function importer - for public access on this mesh which is not "coordinateless" (i.e. - its domain has coordinates) - """ - from meshmode.interop.firedrake.function import \ - FiredrakeFunctionImporter - from meshmode.interop.firedrake.function_space import \ - FiredrakeWithGeometryImporter - from firedrake import Function - - coordinates_fs = self.data.coordinates.function_space() - coordinates_fs_importer = \ - self._coordinates_importer.function_space_importer() - - fspace_importer = \ - FiredrakeWithGeometryImporter(cl_ctx, - coordinates_fs, - coordinates_fs_importer, - self) - f = Function(fspace_importer.data, val=self._coordinates_importer.data) - self._coordinates_function_importer = \ - FiredrakeFunctionImporter(f, fspace_importer) - - del self._callback - - self._callback = callback - - def initialized(self): - return not hasattr(self, '_callback') - - def init(self, cl_ctx): - if not self.initialized(): - self._callback(cl_ctx) - - def __getattr__(self, attr): - """ - If can't find an attribute, look in the underlying - topological mesh - (Done like :class:`firedrake.function.MeshGeometry`) - """ - return getattr(self._topology_importer, attr) - - @property - def coordinates_importer(self): - """ - Return coordinates as a function - - PRECONDITION: Have called :meth:`init` - """ - try: - return self._coordinates_function_importer - except AttributeError: - raise AttributeError("No coordinates function, have you finished" - " initializing this object" - " (i.e. have you called :meth:`init`)?") - - def _compute_vertex_indices_and_vertices(self): - if self._vertex_indices is None: - fspace_importer = self.coordinates_importer.function_space_importer() - finat_element_importer = fspace_importer.finat_element_importer - - from finat.fiat_elements import Lagrange - from finat.spectral import GaussLobattoLegendre - if not isinstance(finat_element_importer.data, - (Lagrange, GaussLobattoLegendre)): - raise TypeError("Coordinates must live in a continuous space " - " (Lagrange or GaussLobattoLegendre), not %s" - % type(finat_element_importer.data)) - - # Convert cell node list of mesh to vertex list - unit_vertex_indices = finat_element_importer.unit_vertex_indices() - cfspace = self.data.coordinates.function_space() - if self.icell_to_fd is not None: - cell_node_list = cfspace.cell_node_list[self.icell_to_fd] - else: - cell_node_list = cfspace.cell_node_list - - vertex_indices = cell_node_list[:, unit_vertex_indices] - - # Get maps newnumbering->old and old->new (new numbering comes - # from removing the non-vertex - # nodes) - vert_ndx_to_fd_ndx = np.unique(vertex_indices.flatten()) - fd_ndx_to_vert_ndx = dict(zip(vert_ndx_to_fd_ndx, - np.arange(vert_ndx_to_fd_ndx.shape[0], - dtype=np.int32) - )) - # Get vertices array - vertices = np.real( - self.data.coordinates.dat.data[vert_ndx_to_fd_ndx]) - - #:mod:`meshmode` wants shape to be [ambient_dim][nvertices] - if len(vertices.shape) == 1: - # 1 dim case, (note we're about to transpose) - vertices = vertices.reshape(vertices.shape[0], 1) - vertices = vertices.T.copy() - - # Use new numbering on vertex indices - vertex_indices = np.vectorize(fd_ndx_to_vert_ndx.get)(vertex_indices) - - # store vertex indices and vertices - self._vertex_indices = vertex_indices - self._vertices = vertices - - def vertex_indices(self): - """ - A numpy array of shape *(nelements, nunitvertices)* - holding the vertex indices associated to each element - """ - self._compute_vertex_indices_and_vertices() - return self._vertex_indices - - def vertices(self): - """ - Return the mesh vertices as a numpy array of shape - *(dim, nvertices)* - """ - self._compute_vertex_indices_and_vertices() - return self._vertices - - def nodes(self): - """ - Return the mesh nodes as a numpy array of shape - *(dim, nelements, nunitnodes)* - """ - if self._nodes is None: - coords = self.data.coordinates.dat.data - cfspace = self.data.coordinates.function_space() - - if self.icell_to_fd is not None: - cell_node_list = cfspace.cell_node_list[self.icell_to_fd] - else: - cell_node_list = cfspace.cell_node_list - self._nodes = np.real(coords[cell_node_list]) - - # reshape for 1D so that [nelements][nunit_nodes][dim] - if len(self._nodes.shape) != 3: - self._nodes = np.reshape(self._nodes, self._nodes.shape + (1,)) - - # Change shape to [dim][nelements][nunit_nodes] - self._nodes = np.transpose(self._nodes, (2, 0, 1)) - - return self._nodes - - def _get_unflipped_group(self): - """ - Return an instance of :class:meshmode.mesh.SimplexElementGroup` - but with elements which are not guaranteed to have a positive - orientation. - - This is important because :meth:`group` requires - the result of :meth:`orientations` to guarantee each - element has positive orientations, and - :meth:`orientations` (usually) needs an unflipped group to compute - the orientations, so we need this function to prevent a recursive - loop of (orientations calling group calling orientations calling...etc.) - """ - # Cache the group we create since :meth:`group` and :meth:`orientations` - # may both use it. One should note that once :meth:`group` terminates, - # this attribute is deleted (once :attr:`_group` is computed then - # :attr:`_orient` must also have been computed, so we no longer have any - # use for :attr:`_unflipped_group`) - if not hasattr(self, "_unflipped_group"): - from meshmode.mesh import SimplexElementGroup - - fspace_importer = self.coordinates_importer.function_space_importer() - finat_element_importer = fspace_importer.finat_element_importer - - self._unflipped_group = SimplexElementGroup( - finat_element_importer.data.degree, - self.vertex_indices(), - self.nodes(), - dim=self.cell_dimension(), - unit_nodes=finat_element_importer.unit_nodes()) - - return self._unflipped_group - - def group(self): - """ - Return an instance of :class:meshmode.mesh.SimplexElementGroup` - corresponding to the mesh :attr:`data` - """ - if self._group is None: - from meshmode.mesh.processing import flip_simplex_element_group - self._group = flip_simplex_element_group(self.vertices(), - self._get_unflipped_group(), - self.orientations() < 0) - # We don't need this anymore - del self._unflipped_group - - return self._group - - def orientations(self): - """ - Return the orientations of the mesh elements: - an array, the *i*th element is > 0 if the *ith* element - is positively oriented, < 0 if negatively oriented - - :param normals: _Only_ used if :param:`mesh` is a 1-surface - embedded in 2-space. In this case, - - If *None* then - all elements are assumed to be positively oriented. - - Else, should be a list/array whose *i*th entry - is the normal for the *i*th element (*i*th - in :param:`mesh`*.coordinate.function_space()*'s - :attribute:`cell_node_list`) - - :param no_normals_warn: If *True*, raises a warning - if :param:`mesh` is a 1-surface embedded in 2-space - and :param:`normals` is *None*. - """ - if self._orient is None: - # compute orientations - tdim = self.data.topological_dimension() - gdim = self.data.geometric_dimension() - - orient = None - if gdim == tdim: - # We use :mod:`meshmode` to check our orientations - from meshmode.mesh.processing import \ - find_volume_mesh_element_group_orientation - - orient = find_volume_mesh_element_group_orientation( - self.vertices(), - self._get_unflipped_group()) - - if tdim == 1 and gdim == 2: - # In this case we have a 1-surface embedded in 2-space - orient = np.ones(self.nelements()) - if self._normals: - for i, (normal, vertices) in enumerate(zip( - np.array(self._normals), self.vertices())): - if np.cross(normal, vertices) < 0: - orient[i] = -1.0 - elif self._no_normals_warn: - warn("Assuming all elements are positively-oriented.") - - elif tdim == 2 and gdim == 3: - # In this case we have a 2-surface embedded in 3-space - orient = self.data.cell_orientations().dat.data - r""" - Convert (0 \implies negative, 1 \implies positive) to - (-1 \implies negative, 1 \implies positive) - """ - orient *= 2 - orient -= np.ones(orient.shape, dtype=orient.dtype) - - self._orient = orient - #Make sure the mesh fell into one of the cases - """ - NOTE : This should be guaranteed by previous checks, - but is here anyway in case of future development. - """ - assert self._orient is not None - - return self._orient - - def face_vertex_indices_to_tags(self): - """ - Returns a dict mapping frozensets - of vertex indices (which identify faces) to a - list of boundary tags - """ - finat_element = self.data.coordinates.function_space().finat_element - exterior_facets = self.data.exterior_facets - - # fvi_to_tags maps frozenset(vertex indices) to tags - fvi_to_tags = {} - # maps faces to local vertex indices - connectivity = finat_element.cell.connectivity[(self.cell_dimension()-1, 0)] - - # Compatability for older versions of firedrake - try: - local_fac_number = exterior_facets.local_facet_number - except AttributeError: - local_fac_number = exterior_facets.local_facet_dat.data - - for i, (icells, ifacs) in enumerate(zip(exterior_facets.facet_cell, - local_fac_number)): - # Compatability for older versions of firedrake - try: - iter(ifacs) - except TypeError: - ifacs = [ifacs] - - for icell, ifac in zip(icells, ifacs): - # If necessary, convert to new cell numbering - if self.fd_to_icell is not None: - if icell not in self.fd_to_icell: - continue - else: - icell = self.fd_to_icell[icell] - - # record face vertex indices to tag map - cell_vertices = self.vertex_indices()[icell] - facet_indices = connectivity[ifac] - fvi = frozenset(cell_vertices[list(facet_indices)]) - fvi_to_tags.setdefault(fvi, []) - fvi_to_tags[fvi].append(exterior_facets.markers[i]) - - # }}} - - return fvi_to_tags - - def facial_adjacency_groups(self): - """ - Return a list of :mod:`meshmode` :class:`FacialAdjacencyGroups` - as used in the construction of a :mod:`meshmode` :class:`Mesh` - """ - # {{{ Compute facial adjacency groups if not already done - - if self._facial_adjacency_groups is None: - from meshmode.mesh import _compute_facial_adjacency_from_vertices - - self._facial_adjacency_groups = _compute_facial_adjacency_from_vertices( - [self.group()], - self.bdy_tags(), - np.int32, np.int8, - face_vertex_indices_to_tags=self.face_vertex_indices_to_tags()) - - # }}} - - return self._facial_adjacency_groups - - def meshmode_mesh(self): - """ - PRECONDITION: Have called :meth:`init` - """ - if self._meshmode_mesh is None: - assert self.initialized(), \ - "Must call :meth:`init` before :meth:`meshmode_mesh`" - - from meshmode.mesh import Mesh - self._meshmode_mesh = \ - Mesh(self.vertices(), [self.group()], - boundary_tags=self.bdy_tags(), - nodal_adjacency=self.nodal_adjacency(), - facial_adjacency_groups=self.facial_adjacency_groups()) - - return self._meshmode_mesh diff --git a/meshmode/interop/firedrake/mesh_topology.py b/meshmode/interop/firedrake/mesh_topology.py deleted file mode 100644 index 3a5d7a2c..00000000 --- a/meshmode/interop/firedrake/mesh_topology.py +++ /dev/null @@ -1,221 +0,0 @@ -__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" - -__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 warnings import warn # noqa -import numpy as np - -from meshmode.interop import ExternalImportHandler - - -# {{{ ImportHandler for firedrake's MeshTopology class - -class FiredrakeMeshTopologyImporter(ExternalImportHandler): - """ - An Importer for :class:`firedrake.mesh.MeshTopology`. - Holds the topological (as opposed to geometric) information - about a mesh. - """ - - def __init__(self, mesh, cells_to_use=None): - """ - :param mesh: An instance :mod:`firedrake` :class:`MeshTopology` or - :class:`MeshGeometry`. If an instance of - :class:`MeshGeometry`, uses its underlying topology. - :param cells_to_use: Either - - * *None*, in which case this argument is ignored - * An array of cell ids, in which case those are the - only cells for which information is gathered/converted - - We require that :param:`mesh` have co-dimesnion - of 0 or 1. - Moreover, if :param:`mesh` is a 2-surface embedded in 3-space, - we _require_ that :function:`init_cell_orientations` - has been called already. - - :raises TypeError: If :param:`mesh` is not of :mod:`firedrake` - :class:`MeshTopology` or :class:`MeshGeometry` - """ - top = mesh.topological # convert geometric to topological - - # {{{ Check input types - from firedrake.mesh import MeshTopology - if not isinstance(top, MeshTopology): - raise TypeError(":param:`mesh` must be of type " - ":class:`firedrake.mesh.MeshTopology` or " - ":class:`firedrake.mesh.MeshGeometry`") - # }}} - - super(FiredrakeMeshTopologyImporter, self).__init__(top) - - # Ensure has simplex-type elements - if not top.ufl_cell().is_simplex(): - raise ValueError(":param:`mesh` must have simplex type elements, " - "%s is not a simplex" % (mesh.ufl_cell())) - - # Ensure dimensions are in appropriate ranges - supported_dims = [1, 2, 3] - if self.cell_dimension() not in supported_dims: - raise ValueError("Cell dimension is %s. Cell dimension must be one of" - " %s" % (self.cell_dimension(), supported_dims)) - - self._nodal_adjacency = None - self.icell_to_fd = cells_to_use # Map cell index -> fd cell index - self.fd_to_icell = None # Map fd cell index -> cell index - - # perform checks on :param:`cells_to_use` if not *None* - if self.icell_to_fd is not None: - assert np.unique(self.icell_to_fd).shape == self.icell_to_fd.shape - self.fd_to_icell = dict(zip(self.icell_to_fd, - np.arange(self.icell_to_fd.shape[0], - dtype=np.int32) - )) - - @property - def topology_importer(self): - """ - A reference to *self*, for compatability with mesh geometry - importers - """ - return self - - @property - def topological_importer(self): - """ - A reference to *self*, for compatability with mesh geometry - importers - """ - return self - - def cell_dimension(self): - """ - Return the dimension of the cells used by this topology - """ - return self.data.cell_dimension() - - def nelements(self): - """ - Return the number of cells in this topology - """ - if self.icell_to_fd is None: - num_cells = self.data.num_cells() - else: - num_cells = self.icell_to_fd.shape[0] - - return num_cells - - def nunit_vertices(self): - """ - Return the number of unit vertices on the reference element - """ - return self.data.ufl_cell().num_vertices() - - def bdy_tags(self): - """ - Return a tuple of bdy tags as requested in - the construction of a :mod:`meshmode` :class:`Mesh` - - The tags used are :class:`meshmode.mesh.BTAG_ALL`, - :class:`meshmode.mesh.BTAG_REALLY_ALL`, and - any markers in the mesh topology's exterior facets - (see :attr:`firedrake.mesh.MeshTopology.exterior_facets.unique_markers`) - """ - from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL - bdy_tags = [BTAG_ALL, BTAG_REALLY_ALL] - - unique_markers = self.data.exterior_facets.unique_markers - if unique_markers is not None: - bdy_tags += list(unique_markers) - - return tuple(bdy_tags) - - def nodal_adjacency(self): - """ - Returns a :class:`meshmode.mesh.NodalAdjacency` object - representing the nodal adjacency of this mesh - """ - if self._nodal_adjacency is None: - # TODO... not sure how to get around the private access - plex = self.data._plex - - # dmplex cell Start/end and vertex Start/end. - c_start, c_end = plex.getHeightStratum(0) - v_start, v_end = plex.getDepthStratum(0) - - # TODO... not sure how to get around the private access - to_fd_id = np.vectorize(self.data._cell_numbering.getOffset)( - np.arange(c_start, c_end, dtype=np.int32)) - - element_to_neighbors = {} - verts_checked = set() # dmplex ids of vertex checked - - # If using all cells, loop over them all - if self.icell_to_fd is None: - range_ = range(c_start, c_end) - # Otherwise, just the ones you're using - else: - isin = np.isin(to_fd_id, self.icell_to_fd) - range_ = np.arange(c_start, c_end, dtype=np.int32)[isin] - - # For each cell - for cell_id in range_: - # For each vertex touching the cell (that haven't already seen) - for vert_id in plex.getTransitiveClosure(cell_id)[0]: - if v_start <= vert_id < v_end and vert_id not in verts_checked: - verts_checked.add(vert_id) - cells = [] - # Record all cells touching that vertex - support = plex.getTransitiveClosure(vert_id, - useCone=False)[0] - for other_cell_id in support: - if c_start <= other_cell_id < c_end: - cells.append(to_fd_id[other_cell_id - c_start]) - - # If only using some cells, clean out extraneous ones - # and relabel them to new id - cells = set(cells) - if self.fd_to_icell is not None: - cells = set([self.fd_to_icell[fd_ndx] - for fd_ndx in cells - if fd_ndx in self.fd_to_icell]) - - # mark cells as neighbors - for cell_one in cells: - element_to_neighbors.setdefault(cell_one, set()) - element_to_neighbors[cell_one] |= cells - - # Create neighbors_starts and neighbors - neighbors = [] - neighbors_starts = np.zeros(self.nelements() + 1, dtype=np.int32) - for iel in range(len(element_to_neighbors)): - elt_neighbors = element_to_neighbors[iel] - neighbors += list(elt_neighbors) - neighbors_starts[iel+1] = len(neighbors) - - neighbors = np.array(neighbors, dtype=np.int32) - - from meshmode.mesh import NodalAdjacency - self._nodal_adjacency = NodalAdjacency(neighbors_starts=neighbors_starts, - neighbors=neighbors) - return self._nodal_adjacency - -# }}} -- GitLab From d2b8c563a61db8a997a1d5f61881658d6eab001d Mon Sep 17 00:00:00 2001 From: benSepanski Date: Mon, 29 Jun 2020 09:17:08 -0500 Subject: [PATCH 041/221] updated test file --- test/test_firedrake_interop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 69fffee7..31e4a4ba 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -27,7 +27,7 @@ from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) -from meshmode.interop.firedrake.connection import FromFiredrakeConnection +from meshmode.interop.firedrake import FromFiredrakeConnection import pytest -- GitLab From f6cbea638ab64db193dbeaba5a60a4680160a713 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Mon, 29 Jun 2020 09:23:40 -0500 Subject: [PATCH 042/221] Removed old DataHandlers --- meshmode/interop/FInAT/__init__.py | 25 --- meshmode/interop/FInAT/lagrange_element.py | 218 --------------------- meshmode/interop/__init__.py | 51 ----- meshmode/interop/fiat/__init__.py | 25 --- meshmode/interop/fiat/simplex_cell.py | 122 ------------ 5 files changed, 441 deletions(-) delete mode 100644 meshmode/interop/FInAT/__init__.py delete mode 100644 meshmode/interop/FInAT/lagrange_element.py delete mode 100644 meshmode/interop/fiat/__init__.py delete mode 100644 meshmode/interop/fiat/simplex_cell.py diff --git a/meshmode/interop/FInAT/__init__.py b/meshmode/interop/FInAT/__init__.py deleted file mode 100644 index a79ad2ae..00000000 --- a/meshmode/interop/FInAT/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" - -__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 meshmode.interop.FInAT.lagrange_element import FinatLagrangeElementImporter - -__all__ = ['FinatLagrangeElementImporter'] diff --git a/meshmode/interop/FInAT/lagrange_element.py b/meshmode/interop/FInAT/lagrange_element.py deleted file mode 100644 index 4b3d977c..00000000 --- a/meshmode/interop/FInAT/lagrange_element.py +++ /dev/null @@ -1,218 +0,0 @@ -__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" - -__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. -""" - -import numpy as np -import numpy.linalg as la - -from meshmode.interop import ExternalImportHandler -from meshmode.interop.fiat import FIATSimplexCellImporter - - -__doc__ = """ -.. autoclass:: FinatLagrangeElementImporter - :members: -""" - - -class FinatLagrangeElementImporter(ExternalImportHandler): - """ - An importer for a FInAT element, usually instantiated from - ``some_instantiated_firedrake_function_space.finat_element`` - """ - def __init__(self, finat_element): - """ - :param finat_element: A FInAT element of type - :class:`finat.fiat_elements.Lagrange` or - :class:`finat.fiat_elements.DiscontinuousLagrange` - (or :class:`finat.spectral.GaussLegendre` or - :class:`finat.spectral.GaussLobattoLegendre` in 1D) - 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` - (or the 1D classes listed above) - - :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 - from finat.spectral import GaussLegendre, GaussLobattoLegendre - valid_types = (Lagrange, DiscontinuousLagrange, - GaussLegendre, GaussLobattoLegendre) - if not isinstance(finat_element, valid_types): - raise TypeError(":param:`finat_element` must be of type" - " `finat.fiat_elements.Lagrange`," - " `finat.fiat_elements.DiscontinuousLagrange`," - " or `finat.spectral.GaussLegendre` or" - " `finat.spectral.GaussLobattoLegendre` in 1D," - " but is instead of 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: - # FIXME : This should work, but uses some private info - # {{{ Compute unit nodes - - # each point evaluator is a function p(f) evaluates f at a node, - # so we need to evaluate each point evaluator at the identity to - # recover the nodes - point_evaluators = self.data._element.dual.nodes - unit_nodes = [p(lambda x: x) for p in point_evaluators] - unit_nodes = np.array(unit_nodes).T - self._unit_nodes = \ - self.cell_importer.affinely_map_firedrake_to_meshmode(unit_nodes) - - # Is this safe?, I think so bc on a reference element - close = 1e-8 - # Get vertices as (dim, nunit_vertices) - unit_vertices = np.array(self.data.cell.vertices).T - unit_vertices = \ - self.cell_importer.affinely_map_firedrake_to_meshmode(unit_vertices) - self._unit_vertex_indices = [] - for n_ndx in range(self._unit_nodes.shape[1]): - for v_ndx in range(unit_vertices.shape[1]): - diff = self._unit_nodes[:, n_ndx] - unit_vertices[:, v_ndx] - if np.max(np.abs(diff)) < close: - self._unit_vertex_indices.append(n_ndx) - break - - self._unit_vertex_indices = np.array(self._unit_vertex_indices) - - # }}} - - def dim(self): - """ - :return: The dimension of the FInAT element's cell - """ - return self.cell_importer.data.get_dimension() - - def unit_vertex_indices(self): - """ - :return: A numpy integer array of indices - so that *self.unit_nodes()[self.unit_vertex_indices()]* - are the nodes of the reference element which coincide - with its vertices (this is possibly empty). - """ - 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 `_ - 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.data.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()) diff --git a/meshmode/interop/__init__.py b/meshmode/interop/__init__.py index 2c1bf072..ba465860 100644 --- a/meshmode/interop/__init__.py +++ b/meshmode/interop/__init__.py @@ -19,54 +19,3 @@ 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. """ - -__doc__ = """ -Development Interface ---------------------- -.. autoclass:: ExternalDataHandler -.. autoclass:: ExternalExportHandler -.. autoclass:: ExternalImportHandler -""" - - -# {{{ Generic, most abstract class for transporting meshmode <-> external - -class ExternalDataHandler: - """ - A data handler takes data from meshmode and facilitates its use - in another package or the reverse: takes data from another package - and facilitates its use in meshmode. - - .. attribute:: data - - The object which needs to be interfaced either into meshmode or - out of meshmode. - Should not be modified after creation. - """ - def __init__(self, data): - self.data = data - -# }}} - - -# {{{ Define specific classes for meshmode -> external and meshmode <- external - -class ExternalExportHandler(ExternalDataHandler): - """ - Subclass of :class:`ExternalDataHandler` for meshmode -> external - data transfer - """ - pass - - -class ExternalImportHandler(ExternalDataHandler): - """ - Subclass of :class:`ExternalDataHandler` for external -> meshmode - data transfer - """ - pass - -# }}} - - -# vim: fdm=marker diff --git a/meshmode/interop/fiat/__init__.py b/meshmode/interop/fiat/__init__.py deleted file mode 100644 index 57a42bfe..00000000 --- a/meshmode/interop/fiat/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" - -__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 meshmode.interop.fiat.simplex_cell import FIATSimplexCellImporter - -__all__ = ['FIATSimplexCellImporter'] diff --git a/meshmode/interop/fiat/simplex_cell.py b/meshmode/interop/fiat/simplex_cell.py deleted file mode 100644 index c6c0ca2c..00000000 --- a/meshmode/interop/fiat/simplex_cell.py +++ /dev/null @@ -1,122 +0,0 @@ -__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" - -__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. -""" - -import numpy as np -import numpy.linalg as la - -from meshmode.interop import ExternalImportHandler - - -__doc__ = """ -.. autoclass:: FIATSimplexCellImporter -""" - - -# {{{ Compute an affine mapping from given input/outputs - -def get_affine_mapping(reference_vects, vects): - r""" - Returns (mat, shift), - a matrix *mat* and vector *shift* which maps the - *i*th vector in *reference_vects* to the *i*th vector in *vects by - - ..math:: - - A ri + b -> vi, \qquad A = mat, b = shift - - :arg reference_vects: An np.array of *n* vectors of dimension *ref_dim* - :arg vects: An np.array of *n* vectors of dimension *dim*, with - *ref_dim* <= *dim*. - - NOTE : Should be shape *(ref_dim, nvectors)*, *(dim, nvectors)* respectively. - - *mat* will have shape *(dim, ref_dim)*, *shift* will have shape *(dim,)* - """ - # Make sure both have same number of vectors - ref_dim, num_vects = reference_vects.shape - assert num_vects == vects.shape[1] - - # Make sure d1 <= d2 (see docstring) - dim = vects.shape[0] - assert ref_dim <= dim - - # If there is only one vector, set M = I, b = vect - reference - if num_vects == 1: - mat = np.eye(dim, ref_dim) - shift = vects[:, 0] - np.matmul(mat, reference_vects[:, 0]) - else: - ref_span_vects = reference_vects[:, 1:] - reference_vects[:, 0, np.newaxis] - span_vects = vects[:, 1:] - vects[:, 0, np.newaxis] - mat = la.solve(ref_span_vects, span_vects) - shift = -np.matmul(mat, reference_vects[:, 0]) + vects[:, 0] - - return mat, shift - -# }}} - - -# {{{ Interoperator for FIAT's reference_element.Simplex - -class FIATSimplexCellImporter(ExternalImportHandler): - """ - Import handler for a :mod:`FIAT` simplex cell. - - .. attribute:: data - - An instance of :class:`fiat.FIAT.reference_element.Simplex`. - """ - def __init__(self, cell): - """ - :arg cell: a :class:`fiat.FIAT.reference_element.Simplex`. - """ - # Ensure this cell is actually a simplex - from FIAT.reference_element import Simplex - assert isinstance(cell, Simplex) - - super(FIATSimplexCellImporter, self).__init__(cell) - - # Stored as (dim, nunit_vertices) - from modepy.tools import unit_vertices - self._unit_vertices = unit_vertices(cell.get_dimension()).T - - # Maps FIAT reference vertices to :mod:`meshmode` - # unit vertices by x -> Ax + b, where A is :attr:`_mat` - # and b is :attr:`_shift` - reference_vertices = np.array(cell.vertices).T - self._mat, self._shift = get_affine_mapping(reference_vertices, - self._unit_vertices) - - def affinely_map_firedrake_to_meshmode(self, points): - """ - Map points on the firedrake reference simplex to - :mod:`modepy` - `unit coordinates `_. - - :arg points: *n* points on the reference simplex - as a numpy array of shape *(dim, n)* - :return: A numpy array of shape *(dim, n)* wherein the - firedrake refernece simplex has been affinely mapped - to the modepy reference simplex - """ - return np.matmul(self._mat, points) + self._shift[:, np.newaxis] - -# }}} -- GitLab From ea3c949f72b37ef0b2ce502a0a47b41da51c0ec5 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Mon, 29 Jun 2020 09:33:48 -0500 Subject: [PATCH 043/221] cleaned up some FIXMEs with integer types and avoiding copies --- meshmode/interop/firedrake/connection.py | 13 +++++++------ meshmode/interop/firedrake/mesh.py | 3 --- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index e3d950d3..9916b8c9 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -274,14 +274,15 @@ class FromFiredrakeConnection: mm_field = mm_field.reshape(mm_field.shape[1]) # resample from nodes - # FIXME : we're still allocating new memory when :param:`out` is supplied - resampled = np.copy(mm_field) - by_cell_view = self.to_discr.groups[0].view(resampled) - np.matmul(by_cell_view, self._resampling_mat_mm2fd.T, out=by_cell_view) + by_cell_field_view = self.to_discr.groups[0].view(mm_field) + by_cell_out_view = self.to_discr.groups[0].view(out.dat.data.T) + np.matmul(by_cell_field_view, self._resampling_mat_mm2fd.T, + out=by_cell_out_view) + # reorder data if len(out.dat.data.shape) == 1: - out.dat.data[:] = resampled[self._reordering_arr_mm2fd] + out.dat.data[:] = out.dat.data[self._reordering_arr_mm2fd] else: - out.dat.data[:] = resampled.T[self._reordering_arr_mm2fd, :] + out.dat.data[:] = out.dat.data.T[self._reordering_arr_mm2fd, :] return out diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 26dbed76..470cd724 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -75,7 +75,6 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology): def vert_id_dmp_to_fd(ndx): return top._vertex_numbering.getOffset(ndx) - # FIXME : Is this the right integer type? # We will fill in the values as we go vertex_indices = -np.ones((top.num_cells(), top.ufl_cell().num_vertices()), dtype=np.int32) @@ -122,7 +121,6 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology): # Next go ahead and compute nodal adjacency by creating # neighbors and neighbor_starts as specified by :class:`NodalAdjacency` neighbors = [] - # FIXME : Is this the right integer type to choose? neighbors_starts = np.zeros(top.num_cells() + 1, dtype=np.int32) for iel in range(len(cell_to_nodal_neighbors)): neighbors += cell_to_nodal_neighbors[iel] @@ -340,7 +338,6 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, # {{{ Mesh conversion def import_firedrake_mesh(fdrake_mesh): - # FIXME : docs """ Create a :mod:`meshmode` :class:`Mesh` from a :mod:`firedrake` :class:`MeshGeometry` with the same cells/elements, vertices, nodes, -- GitLab From 2e1a00fb7196896809e1d72acca0a83884c04a2e Mon Sep 17 00:00:00 2001 From: benSepanski Date: Mon, 29 Jun 2020 09:35:33 -0500 Subject: [PATCH 044/221] missed a transpose in a hasty commit --- meshmode/interop/firedrake/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 9916b8c9..b9cefaec 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -283,6 +283,6 @@ class FromFiredrakeConnection: if len(out.dat.data.shape) == 1: out.dat.data[:] = out.dat.data[self._reordering_arr_mm2fd] else: - out.dat.data[:] = out.dat.data.T[self._reordering_arr_mm2fd, :] + out.dat.data[:] = out.dat.data[self._reordering_arr_mm2fd, :] return out -- GitLab From 389c51745a4fbca7820366dfafb548dd28ba7314 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Mon, 29 Jun 2020 13:16:40 -0500 Subject: [PATCH 045/221] Added support for conversion from meshmode into a continuous space --- meshmode/interop/firedrake/connection.py | 117 ++++++++++++++++++----- meshmode/interop/firedrake/mesh.py | 49 ++++++---- test/test_firedrake_interop.py | 20 ++-- 3 files changed, 136 insertions(+), 50 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index b9cefaec..61977330 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -26,6 +26,9 @@ __doc__ = """ import numpy as np import numpy.linalg as la +import six + +from modepy import resampling_matrix from meshmode.interop.firedrake.mesh import import_firedrake_mesh from meshmode.interop.firedrake.reference_cell import ( @@ -37,8 +40,6 @@ from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory from meshmode.discretization import Discretization -from modepy import resampling_matrix - def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): """ @@ -130,19 +131,45 @@ class FromFiredrakeConnection: new_nodes=fd_unit_nodes, old_nodes=mm_unit_nodes) - # handle reordering fd->mm + # Flipping negative elements corresponds to reordering the nodes. + # We handle reordering by storing the permutation explicitly as + # a numpy array + + # Get the reordering fd->mm. + # + # One should note there is something a bit more subtle going on + # in the continuous case. All meshmode discretizations use + # are discontinuous, so nodes are associated with elements(cells) + # not vertices. In a continuous firedrake space, some nodes + # are shared between multiple cells. In particular, while the + # below "reordering" is indeed a permutation if the firedrake space + # is discontinuous, if the firedrake space is continuous then + # some firedrake nodes correspond to nodes on multiple meshmode + # elements, i.e. those nodes appear multiple times + # in the "reordering" array flip_mat = get_simplex_element_flip_matrix(ufl_elt.degree(), fd_unit_nodes) fd_cell_node_list = fdrake_fspace.cell_node_list _reorder_nodes(orient, fd_cell_node_list, flip_mat, unflip=False) self._reordering_arr_fd2mm = fd_cell_node_list.flatten() - # handle reordering mm->fd (this only works in the discontinuous - # case) - nnodes = self.to_discr.nnodes - mm_cell_node_list = self.to_discr.groups[0].view(np.arange(nnodes)) - _reorder_nodes(orient, mm_cell_node_list, flip_mat, unflip=True) - self._reordering_arr_mm2fd = mm_cell_node_list.flatten() + # Now handle the possibility of duplicate nodes + unique_fd_nodes, counts = np.unique(self._reordering_arr_fd2mm, + return_counts=True) + # self._duplicate_nodes + # maps firedrake nodes associated to more than 1 meshmode node + # to all associated meshmode nodes. + self._duplicate_nodes = {} + if ufl_elt.family() == 'Discontinuous Lagrange': + assert np.all(counts == 1), \ + "This error should never happen, some nodes in a firedrake " \ + "discontinuous space were duplicated. Contact the developer " + else: + dup_fd_nodes = set(unique_fd_nodes[counts > 1]) + for mm_inode, fd_inode in enumerate(self._reordering_arr_fd2mm): + if fd_inode in dup_fd_nodes: + self._duplicate_nodes.setdefault(fd_inode, []) + self._duplicate_nodes[fd_inode].append(mm_inode) # Store things that we need for *from_fspace* self._ufl_element = ufl_elt @@ -228,7 +255,9 @@ class FromFiredrakeConnection: np.matmul(out_view, self._resampling_mat_fd2mm.T, out=out_view) return out - def from_meshmode(self, mm_field, out=None): + def from_meshmode(self, mm_field, out=None, + assert_fdrake_discontinuous=True, + continuity_tolerance=None): """ transport meshmode field from :attr:`to_discr` into an appropriate firedrake function space. @@ -238,15 +267,26 @@ class FromFiredrakeConnection: :param out: If *None* then ignored, otherwise a :mod:`firedrake` function of the right function space for the transported data to be stored in. + :param assert_fdrake_discontinuous: If *True*, + disallows conversion to a continuous firedrake function space + (i.e. this function checks that ``self.from_fspace()`` is + discontinuous and raises a *ValueError* otherwise) + :param continuity_tolerance: If converting to a continuous firedrake + function space (i.e. if ``self.from_fspace()`` is continuous), + assert that at any two meshmode nodes corresponding to the + same firedrake node (meshmode is a discontinuous space, so this + situation will almost certainly happen), the function being transported + has values at most :param:`continuity_tolerance` distance + apart. If *None*, no checks are performed. :return: a :mod:`firedrake` :class:`Function` holding the transported data. """ - if self._ufl_element.family() == 'Lagrange': - raise ValueError("Cannot convert functions from discontinuous " - " space (meshmode) to continuous firedrake " - " space (reference element family %s)." - % type(self._ufl_element.family())) + if self._ufl_element.family() == 'Lagrange' \ + and assert_fdrake_discontinuous: + raise ValueError("Trying to convert to continuous function space " + " with :param:`assert_fdrake_discontinuous` set " + " to *True*") # make sure out is a firedrake function in an appropriate # function space if out is not None: @@ -273,16 +313,47 @@ class FromFiredrakeConnection: if len(out.dat.data.shape) == 1 and len(mm_field.shape) > 1: mm_field = mm_field.reshape(mm_field.shape[1]) - # resample from nodes + # resample from nodes on reordered view. Have to do this in + # a bit of a roundabout way to be careful about duplicated + # firedrake nodes. by_cell_field_view = self.to_discr.groups[0].view(mm_field) - by_cell_out_view = self.to_discr.groups[0].view(out.dat.data.T) - np.matmul(by_cell_field_view, self._resampling_mat_mm2fd.T, - out=by_cell_out_view) - - # reorder data if len(out.dat.data.shape) == 1: - out.dat.data[:] = out.dat.data[self._reordering_arr_mm2fd] + reordered_outdata = out.dat.data[self._reordering_arr_fd2mm] else: - out.dat.data[:] = out.dat.data[self._reordering_arr_mm2fd, :] + reordered_outdata = out.dat.data.T[:, self._reordering_arr_fd2mm] + by_cell_reordered_view = self.to_discr.groups[0].view(reordered_outdata) + np.matmul(by_cell_field_view, self._resampling_mat_mm2fd.T, + out=by_cell_reordered_view) + out.dat.data[self._reordering_arr_fd2mm] = reordered_outdata.T + + # Continuity checks + if self._ufl_element.family() == 'Lagrange' \ + and continuity_tolerance is not None: + assert isinstance(continuity_tolerance, float) + assert continuity_tolerance >= 0 + # Check each firedrake node which has been duplicated + # that all associated values are within the continuity + # tolerance + for fd_inode, duplicated_mm_nodes in \ + six.iteritems(self._duplicate_nodes): + mm_inode = duplicated_mm_nodes[0] + # Make sure to compare using reordered_outdata not mm_field, + # because two meshmode nodes associated to the same firedrake + # nodes may have been resampled to distinct nodes on different + # elements. reordered_outdata has undone that resampling. + for dup_mm_inode in duplicated_mm_nodes[1:]: + if len(reordered_outdata.shape) > 1: + dist = la.norm(reordered_outdata[:, mm_inode] + - reordered_outdata[:, dup_mm_inode]) + else: + dist = la.norm(reordered_outdata[mm_inode] + - reordered_outdata[dup_mm_inode]) + if dist >= continuity_tolerance: + raise ValueError("Meshmode nodes %s and %s represent " + "the same firedrake node %s, but " + ":param:`mm_field`'s values are " + " %s > %s apart)" + % (mm_inode, dup_mm_inode, fd_inode, + dist, continuity_tolerance)) return out diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 470cd724..d9060b9b 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -267,25 +267,13 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, Return the orientations of the mesh elements: :param fdrake_mesh: A :mod:`firedrake` instance of :class:`MeshGeometry` - :param unflipped_group: A :class:`SimplexElementGroup` instance with (potentially) some negatively oriented elements. - :param vertices: The vertex coordinates as a numpy array of shape *(ambient_dim, nvertices)* - - :param normals: _Only_ used if :param:`mesh` is a 1-surface - embedded in 2-space. In this case, - - If *None* then - all elements are assumed to be positively oriented. - - Else, should be a list/array whose *i*th entry - is the normal for the *i*th element (*i*th - in :param:`mesh`*.coordinate.function_space()*'s - :attribute:`cell_node_list`) - - :param no_normals_warn: If *True*, raises a warning - if :param:`mesh` is a 1-surface embedded in 2-space - and :param:`normals` is *None*. + :param normals: As described in the kwargs of :func:`import_firedrake_mesh` + :param no_normals_warn: As described in the kwargs of + :func:`import_firedrake_mesh` :return: A numpy array, the *i*th element is > 0 if the *ith* element is positively oriented, < 0 if negatively oriented. @@ -337,7 +325,7 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, # {{{ Mesh conversion -def import_firedrake_mesh(fdrake_mesh): +def import_firedrake_mesh(fdrake_mesh, **kwargs): """ Create a :mod:`meshmode` :class:`Mesh` from a :mod:`firedrake` :class:`MeshGeometry` with the same cells/elements, vertices, nodes, @@ -357,7 +345,14 @@ def import_firedrake_mesh(fdrake_mesh): 1, 2, or 3 and have co-dimension of 0 or 1. It must use a simplex as a reference element. - Finally, its ``coordinates`` attribute must have a function + In the case of a 2-dimensional mesh embedded in 3-space, + the method ``fdrake_mesh.init_cell_orientations`` must + have been called. + + In the case of a 1-dimensional mesh embedded in 2-space, + see the keyword arguments below. + + Finally, the ``coordinates`` attribute must have a function space whose *finat_element* associates a degree of freedom with each vertex. In particular, this means that the vertices of the mesh must have well-defined @@ -366,6 +361,19 @@ def import_firedrake_mesh(fdrake_mesh): verify this by looking at the ``[0]`` entry of ``fdrake_mesh.coordinates.function_space().finat_element.entity_dofs()``. + :Keyword Arguments: + * *normals*: _Only_ used if :param:`fdrake_mesh` is a 1-surface + embedded in 2-space. In this case, + - If *None* then + all elements are assumed to be positively oriented. + - Else, should be a list/array whose *i*th entry + is the normal for the *i*th element (*i*th + in :param:`mesh`*.coordinate.function_space()*'s + :attribute:`cell_node_list`) + * *no_normals_warn: If *True* (the default), raises a warning + if :param:`fdrake_mesh` is a 1-surface embedded in 2-space + and :param:`normals` is *None*. + :return: A tuple *(meshmode mesh, firedrake_orient)*. ``firedrake_orient < 0`` is *True* for any negatively oriented firedrake cell (which was flipped by meshmode) @@ -449,8 +457,11 @@ def import_firedrake_mesh(fdrake_mesh): vertices = np.array([vertices[i] for i in range(len(vertices))]).T # Use the vertices to compute the orientations and flip the group - # FIXME : Allow for passing in normals/no normals warn - orient = _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices) + normals = kwargs.get('normals', None) + no_normals_warn = kwargs.get('no_normals_warn', True) + orient = _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, + normals=normals, + no_normals_warn=no_normals_warn) from meshmode.mesh.processing import flip_simplex_element_group group = flip_simplex_element_group(vertices, unflipped_group, orient < 0) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 31e4a4ba..7074b58e 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -181,7 +181,7 @@ def test_function_transfer(ctx_factory, def check_idempotency(fdrake_connection, fdrake_function): """ - Make sure fd->mm->fd and mm->fd->mm are identity for DG spaces + Make sure fd->mm->fd and mm->fd->mm are identity """ vdim = None if len(fdrake_function.dat.data.shape) > 1: @@ -191,7 +191,9 @@ def check_idempotency(fdrake_connection, fdrake_function): # Test for idempotency fd->mm->fd mm_field = fdrake_connection.from_firedrake(fdrake_function) fdrake_function_copy = Function(fdrake_fspace) - fdrake_connection.from_meshmode(mm_field, fdrake_function_copy) + fdrake_connection.from_meshmode(mm_field, fdrake_function_copy, + assert_fdrake_discontinuous=False, + continuity_tolerance=1e-8) np.testing.assert_allclose(fdrake_function_copy.dat.data, fdrake_function.dat.data, @@ -202,11 +204,12 @@ def check_idempotency(fdrake_connection, fdrake_function): np.testing.assert_allclose(mm_field_copy, mm_field, atol=CLOSE_ATOL) -def test_scalar_idempotency(ctx_factory, fdrake_mesh, fdrake_degree): +def test_scalar_idempotency(ctx_factory, fdrake_mesh, + fdrake_family, fdrake_degree): """ - Make sure fd->mm->fd and mm->fd->mm are identity for scalar DG spaces + Make sure fd->mm->fd and mm->fd->mm are identity for scalar spaces """ - fdrake_fspace = FunctionSpace(fdrake_mesh, 'DG', fdrake_degree) + fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fdrake_degree) # Make a function with unique values at each node fdrake_unique = Function(fdrake_fspace) @@ -218,11 +221,12 @@ def test_scalar_idempotency(ctx_factory, fdrake_mesh, fdrake_degree): check_idempotency(fdrake_connection, fdrake_unique) -def test_vector_idempotency(ctx_factory, fdrake_mesh, fdrake_degree): +def test_vector_idempotency(ctx_factory, fdrake_mesh, + fdrake_family, fdrake_degree): """ - Make sure fd->mm->fd and mm->fd->mm are identity for vector DG spaces + Make sure fd->mm->fd and mm->fd->mm are identity for vector spaces """ - fdrake_vfspace = VectorFunctionSpace(fdrake_mesh, 'DG', fdrake_degree) + fdrake_vfspace = VectorFunctionSpace(fdrake_mesh, fdrake_family, fdrake_degree) # Make a function with unique values at each node xx = SpatialCoordinate(fdrake_vfspace.mesh()) -- GitLab From 05fe1dc675bae6e7473bb6c582ed1914d078c2dd Mon Sep 17 00:00:00 2001 From: benSepanski Date: Mon, 29 Jun 2020 14:35:33 -0500 Subject: [PATCH 046/221] Improved docs --- doc/interop.rst | 60 +++++++++++++++----- meshmode/interop/firedrake/connection.py | 53 ++++++++--------- meshmode/interop/firedrake/mesh.py | 55 +++++++++--------- meshmode/interop/firedrake/reference_cell.py | 6 +- 4 files changed, 104 insertions(+), 70 deletions(-) diff --git a/doc/interop.rst b/doc/interop.rst index 41ad3b63..e80fb8e8 100644 --- a/doc/interop.rst +++ b/doc/interop.rst @@ -4,36 +4,66 @@ interop Interfacing with data outside of :mod:`meshmode`. -fiat ----- - -.. automodule:: meshmode.interop.fiat.simplex_cell +Firedrake +--------- +Function Spaces/Discretizations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -FInAT ------ +.. automodule:: meshmode.interop.firedrake.connection -.. automodule:: meshmode.interop.FInAT.lagrange_element +Meshes +^^^^^^ +.. automodule:: meshmode.interop.firedrake.mesh -Firedrake ---------- +Reference Cells +^^^^^^^^^^^^^^^ -TODO include this +.. automodule:: meshmode.interop.firedrake.reference_cell Implementation Details ----------------------- +^^^^^^^^^^^^^^^^^^^^^^ +Converting between :mod:`firedrake` and :mod:`meshmode` is in general +straightforward. Some language is different: -Firedrake Function Space Design -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +* In a mesh, a :mod:`meshmode` "element" is a :mod:`firedrake` "cell" +* A :mod:`meshmode` :class:`Discretization` is a :mod:`firedrake` + :class:`WithGeometry` created, usually + created to using the function :func:`FunctionSpace` and referred + to as a "function space" +* In a mesh, any vertices, faces, cells, etc. are :mod:`firedrake` + "entities" (see `dmplex `_ + for more info on how topological mesh information is stored + in :mod:`firedrake`). + +Other than carefully tabulating how and which vertices/faces +correspond to other vertices/faces/cells, there are two main difficulties. + +1. :mod:`meshmode` requires that all mesh elements be positively oriented, + :mod:`firedrake` does not. +2. :mod:`meshmode` has discontinuous polynomial function spaces + which use different nodes than :mod:`firedrake`. + +Consequently, any :mod:`firedrake` :class:`firedrake.function.Function` +whose data is converted onto a corresponding :class:`Discretization` +using a :class:`FromFiredrakeConnection` instance is +first reordered (as the converted mesh was reordered to have +positively oriented elements) and then resampled at the :mod:`meshmode` +nodes. + +For Developers: Firedrake Function Space Design Crash Course +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In firedrake, meshes and function spaces have a close relationship. -In particular, due to some structure described in this +In particular, this is due to some structure described in this `firedrake pull request `_. -``fd2mm`` mimics this firedrake design style. +If you wish to develop on / add to the implementation of conversion +between :mod:`meshmode` and :mod:`firedrake`, you will need +to understand their design style. Below is a crash course. In short, it is the idea that every function space should have a mesh, and the coordinates of the mesh diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 61977330..936e8732 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -22,6 +22,7 @@ THE SOFTWARE. __doc__ = """ .. autoclass:: FromFiredrakeConnection + :members: """ import numpy as np @@ -43,14 +44,14 @@ from meshmode.discretization import Discretization def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): """ - flips :param:`nodes` in place according to :param:`orient` + flips *nodes* in place according to *orient* - :param orient: An array of shape *(nelements)* of orientations, + :arg orient: An array of shape *(nelements)* of orientations, >0 for positive, <0 for negative - :param nodes: a *(nelements, nunit_nodes)* or shaped array of nodes - :param flip_matrix: The matrix used to flip each negatively-oriented + :arg nodes: a *(nelements, nunit_nodes)* or shaped array of nodes + :arg flip_matrix: The matrix used to flip each negatively-oriented element - :param unflip: If *True*, use transpose of :param:`flip_matrix` to + :arg unflip: If *True*, use transpose of *flip_matrix* to flip negatively-oriented elements """ # reorder nodes (Code adapted from @@ -88,8 +89,8 @@ class FromFiredrakeConnection: """ def __init__(self, cl_ctx, fdrake_fspace): """ - :param cl_ctx: A :mod:`pyopencl` computing context - :param fdrake_fspace: A :mod:`firedrake` ``"CG"`` or ``"DG"`` + :arg cl_ctx: A :mod:`pyopencl` computing context + :arg fdrake_fspace: A :mod:`firedrake` ``"CG"`` or ``"DG"`` function space (of class :class:`WithGeometry`) built on a mesh which is importable by :func:`import_firedrake_mesh`. """ @@ -97,14 +98,14 @@ class FromFiredrakeConnection: # element. from firedrake.functionspaceimpl import WithGeometry if not isinstance(fdrake_fspace, WithGeometry): - raise TypeError(":param:`fdrake_fspace` must be of firedrake type " + raise TypeError(":arg:`fdrake_fspace` must be of firedrake type " ":class:`WithGeometry`, not `%s`." % type(fdrake_fspace)) ufl_elt = fdrake_fspace.ufl_element() if ufl_elt.family() not in ('Lagrange', 'Discontinuous Lagrange'): raise ValueError("the ``ufl_element().family()`` of " - ":param:`fdrake_fspace` must " + ":arg:`fdrake_fspace` must " "be ``'Lagrange'`` or " "``'Discontinuous Lagrange'``, not %s." % ufl_elt.family()) @@ -180,7 +181,7 @@ class FromFiredrakeConnection: """ Return a firedrake function space of the appropriate vector dimension - :param dim: Either *None*, in which case a function space which maps + :arg dim: Either *None*, in which case a function space which maps to scalar values is returned, or a positive integer *n*, in which case a function space which maps into *\\R^n* is returned @@ -211,12 +212,12 @@ class FromFiredrakeConnection: """ transport firedrake function onto :attr:`to_discr` - :param function: A :mod:`firedrake` function to transfer onto + :arg function: A :mod:`firedrake` function to transfer onto :attr:`to_discr`. Its function space must have the same family, degree, and mesh as ``self.from_fspace()``. - :param out: If *None* then ignored, otherwise a numpy array of the + :arg out: If *None* then ignored, otherwise a numpy array of the shape *function.dat.data.shape.T* (i.e. - *(dim, nnodes)* or *(nnodes,)* in which :param:`function`'s + *(dim, nnodes)* or *(nnodes,)* in which *function*'s transported data is stored. :return: a numpy array holding the transported function @@ -225,15 +226,15 @@ class FromFiredrakeConnection: # function space from firedrake.function import Function assert isinstance(function, Function), \ - ":param:`function` must be a :mod:`firedrake` Function" + ":arg:`function` must be a :mod:`firedrake` Function" assert function.function_space().ufl_element().family() \ == self._ufl_element.family() and \ function.function_space().ufl_element().degree() \ == self._ufl_element.degree(), \ - ":param:`function` must live in a function space with the " \ + ":arg:`function` must live in a function space with the " \ "same family and degree as ``self.from_fspace()``" assert function.function_space().mesh() is self._mesh_geometry, \ - ":param:`function` mesh must be the same mesh as used by " \ + ":arg:`function` mesh must be the same mesh as used by " \ "``self.from_fspace().mesh()``" # Get function data as shape [nnodes][dims] or [nnodes] @@ -262,21 +263,21 @@ class FromFiredrakeConnection: transport meshmode field from :attr:`to_discr` into an appropriate firedrake function space. - :param mm_field: A numpy array of shape *(nnodes,)* or *(dim, nnodes)* + :arg mm_field: A numpy array of shape *(nnodes,)* or *(dim, nnodes)* representing a function on :attr:`to_distr`. - :param out: If *None* then ignored, otherwise a :mod:`firedrake` + :arg out: If *None* then ignored, otherwise a :mod:`firedrake` function of the right function space for the transported data to be stored in. - :param assert_fdrake_discontinuous: If *True*, + :arg assert_fdrake_discontinuous: If *True*, disallows conversion to a continuous firedrake function space (i.e. this function checks that ``self.from_fspace()`` is discontinuous and raises a *ValueError* otherwise) - :param continuity_tolerance: If converting to a continuous firedrake + :arg continuity_tolerance: If converting to a continuous firedrake function space (i.e. if ``self.from_fspace()`` is continuous), assert that at any two meshmode nodes corresponding to the same firedrake node (meshmode is a discontinuous space, so this situation will almost certainly happen), the function being transported - has values at most :param:`continuity_tolerance` distance + has values at most *continuity_tolerance* distance apart. If *None*, no checks are performed. :return: a :mod:`firedrake` :class:`Function` holding the transported @@ -285,22 +286,22 @@ class FromFiredrakeConnection: if self._ufl_element.family() == 'Lagrange' \ and assert_fdrake_discontinuous: raise ValueError("Trying to convert to continuous function space " - " with :param:`assert_fdrake_discontinuous` set " + " with :arg:`assert_fdrake_discontinuous` set " " to *True*") # make sure out is a firedrake function in an appropriate # function space if out is not None: from firedrake.function import Function assert isinstance(out, Function), \ - ":param:`out` must be a :mod:`firedrake` Function or *None*" + ":arg:`out` must be a :mod:`firedrake` Function or *None*" assert out.function_space().ufl_element().family() \ == self._ufl_element.family() and \ out.function_space().ufl_element().degree() \ == self._ufl_element.degree(), \ - ":param:`out` must live in a function space with the " \ + ":arg:`out` must live in a function space with the " \ "same family and degree as ``self.from_fspace()``" assert out.function_space().mesh() is self._mesh_geometry, \ - ":param:`out` mesh must be the same mesh as used by " \ + ":arg:`out` mesh must be the same mesh as used by " \ "``self.from_fspace().mesh()`` or *None*" else: if len(mm_field.shape) == 1: @@ -351,7 +352,7 @@ class FromFiredrakeConnection: if dist >= continuity_tolerance: raise ValueError("Meshmode nodes %s and %s represent " "the same firedrake node %s, but " - ":param:`mm_field`'s values are " + ":arg:`mm_field`'s values are " " %s > %s apart)" % (mm_inode, dup_mm_inode, fd_inode, dist, continuity_tolerance)) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index d9060b9b..a0c8cb0d 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -1,4 +1,4 @@ -__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" +arg = "Copyright (C) 2020 Benjamin Sepanski" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -43,14 +43,14 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology): are guaranteed to have the same numbering in :mod:`meshmode` as :mod:`firedrdake` - :param fdrake_mesh_topology: A :mod:`firedrake` instance of class + :arg fdrake_mesh_topology: A :mod:`firedrake` instance of class :class:`MeshTopology` or :class:`MeshGeometry`. :return: Returns *vertex_indices* as a numpy array of shape *(nelements, ref_element.nvertices)* (as described by the ``vertex_indices`` attribute of a :class:`MeshElementGroup`) and a :class:`NodalAdjacency` constructed from - :param:`fdrake_mesh_topology` + *fdrake_mesh_topology* as a tuple *(vertex_indices, nodal_adjacency)*. """ top = fdrake_mesh_topology.topology @@ -145,7 +145,7 @@ def _get_firedrake_boundary_tags(fdrake_mesh): any markers in the mesh topology's exterior facets (see :attr:`firedrake.mesh.MeshTopology.exterior_facets.unique_markers`) - :param fdrake_mesh: A :mod:`firedrake` :class:`MeshTopology` or + :arg fdrake_mesh: A :mod:`firedrake` :class:`MeshTopology` or :class:`MeshGeometry` :return: A tuple of boundary tags @@ -167,7 +167,7 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology): have geometric information, elements may need to be flipped later. - :param fdrake_mesh_topology: A :mod:`firedrake` instance of class + :arg fdrake_mesh_topology: A :mod:`firedrake` instance of class :class:`MeshTopology` or :class:`MeshGeometry`. :return: A list of maps to :class:`FacialAdjacencyGroup`s as required by a :mod:`meshmode` :class:`Mesh` @@ -266,13 +266,13 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, """ Return the orientations of the mesh elements: - :param fdrake_mesh: A :mod:`firedrake` instance of :class:`MeshGeometry` - :param unflipped_group: A :class:`SimplexElementGroup` instance with + :arg fdrake_mesh: A :mod:`firedrake` instance of :class:`MeshGeometry` + :arg unflipped_group: A :class:`SimplexElementGroup` instance with (potentially) some negatively oriented elements. - :param vertices: The vertex coordinates as a numpy array of shape + :arg vertices: The vertex coordinates as a numpy array of shape *(ambient_dim, nvertices)* - :param normals: As described in the kwargs of :func:`import_firedrake_mesh` - :param no_normals_warn: As described in the kwargs of + :arg normals: As described in the kwargs of :func:`import_firedrake_mesh` + :arg no_normals_warn: As described in the kwargs of :func:`import_firedrake_mesh` :return: A numpy array, the *i*th element is > 0 if the *ith* element @@ -325,7 +325,7 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, # {{{ Mesh conversion -def import_firedrake_mesh(fdrake_mesh, **kwargs): +def import_firedrake_mesh(fdrake_mesh, normals=None, no_normals_warn=None): """ Create a :mod:`meshmode` :class:`Mesh` from a :mod:`firedrake` :class:`MeshGeometry` with the same cells/elements, vertices, nodes, @@ -340,7 +340,7 @@ def import_firedrake_mesh(fdrake_mesh, **kwargs): The flipped cells/elements are identified by the returned *firedrake_orient* array - :param fdrake_mesh: A :mod:`firedrake` :class:`MeshGeometry`. + :arg fdrake_mesh: A :mod:`firedrake` :class:`MeshGeometry`. This mesh **must** be in a space of ambient dimension 1, 2, or 3 and have co-dimension of 0 or 1. It must use a simplex as a reference element. @@ -350,7 +350,7 @@ def import_firedrake_mesh(fdrake_mesh, **kwargs): have been called. In the case of a 1-dimensional mesh embedded in 2-space, - see the keyword arguments below. + see parameters *normals* and *no_normals_warn*. Finally, the ``coordinates`` attribute must have a function space whose *finat_element* associates a degree @@ -358,21 +358,26 @@ def import_firedrake_mesh(fdrake_mesh, **kwargs): this means that the vertices of the mesh must have well-defined coordinates. For those unfamiliar with :mod:`firedrake`, you can - verify this by looking at the ``[0]`` entry of - ``fdrake_mesh.coordinates.function_space().finat_element.entity_dofs()``. + verify this by looking at - :Keyword Arguments: - * *normals*: _Only_ used if :param:`fdrake_mesh` is a 1-surface - embedded in 2-space. In this case, + .. code-block:: python + + import six + coords_fspace = fdrake_mesh.coordinates.function_space() + vertex_entity_dofs = coords_fspace.finat_element.entity_dofs()[0] + for entity, dof_list in six.iteritems(vertex_entity_dofs): + assert len(dof_list) > 0 + :arg normals: **Only** used if *fdrake_mesh* is a 1-surface + embedded in 2-space. In this case, - If *None* then all elements are assumed to be positively oriented. - Else, should be a list/array whose *i*th entry is the normal for the *i*th element (*i*th - in :param:`mesh`*.coordinate.function_space()*'s - :attribute:`cell_node_list`) - * *no_normals_warn: If *True* (the default), raises a warning - if :param:`fdrake_mesh` is a 1-surface embedded in 2-space - and :param:`normals` is *None*. + in *mesh.coordinate.function_space()*'s + :attr:`cell_node_list`) + :arg no_normals_warn: If *True* (the default), raises a warning + if *fdrake_mesh* is a 1-surface embedded in 2-space + and *normals* is *None*. :return: A tuple *(meshmode mesh, firedrake_orient)*. ``firedrake_orient < 0`` is *True* for any negatively @@ -383,7 +388,7 @@ def import_firedrake_mesh(fdrake_mesh, **kwargs): # Type validation from firedrake.mesh import MeshGeometry if not isinstance(fdrake_mesh, MeshGeometry): - raise TypeError(":param:`fdrake_mesh_topology` must be a " + raise TypeError(":arg:`fdrake_mesh_topology` must be a " ":mod:`firedrake` :class:`MeshGeometry`, " "not %s." % type(fdrake_mesh)) assert fdrake_mesh.ufl_cell().is_simplex(), "Mesh must use simplex cells" @@ -457,8 +462,6 @@ def import_firedrake_mesh(fdrake_mesh, **kwargs): vertices = np.array([vertices[i] for i in range(len(vertices))]).T # Use the vertices to compute the orientations and flip the group - normals = kwargs.get('normals', None) - no_normals_warn = kwargs.get('no_normals_warn', True) orient = _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, normals=normals, no_normals_warn=no_normals_warn) diff --git a/meshmode/interop/firedrake/reference_cell.py b/meshmode/interop/firedrake/reference_cell.py index b8509582..153c5667 100644 --- a/meshmode/interop/firedrake/reference_cell.py +++ b/meshmode/interop/firedrake/reference_cell.py @@ -38,8 +38,8 @@ def get_affine_reference_simplex_mapping(spat_dim, firedrake_to_meshmode=True): on one reference cell and maps each point to another using a positive affine map. - :param spat_dim: The spatial dimension - :param firedrake_to_meshmode: If true, the returned function maps from + :arg spat_dim: The spatial dimension + :arg firedrake_to_meshmode: If true, the returned function maps from the firedrake reference element to meshmode, if false maps from meshmode to firedrake. More specifically, @@ -114,7 +114,7 @@ def get_finat_element_unit_nodes(finat_element): Returns the unit nodes used by the FInAT element in firedrake's (equivalently, FInAT/FIAT's) reference coordinates - :param finat_element: A :class:`finat.finiteelementbase.FiniteElementBase` + :arg finat_element: A :class:`finat.finiteelementbase.FiniteElementBase` instance (i.e. a firedrake function space's reference element). The refernce element of the finat element *MUST* be a simplex :return: A numpy array of shape *(dim, nunit_nodes)* holding the unit -- GitLab From 4ac9ad5e52b3fd46d0032ef6e5e3ef7018542d3f Mon Sep 17 00:00:00 2001 From: benSepanski Date: Mon, 29 Jun 2020 16:03:05 -0500 Subject: [PATCH 047/221] Added a mesh exporter, set up beginning of exporting connection --- meshmode/interop/firedrake/connection.py | 22 ++++++ meshmode/interop/firedrake/mesh.py | 90 +++++++++++++++++++++--- 2 files changed, 101 insertions(+), 11 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 936e8732..e2f202a0 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -74,6 +74,9 @@ def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): flip_mat, nodes[orient < 0]) +# {{{ Create connection from firedrake into meshmode + + class FromFiredrakeConnection: """ A connection created from a :mod:`firedrake` @@ -358,3 +361,22 @@ class FromFiredrakeConnection: dist, continuity_tolerance)) return out + +# }}} + + +# {{{ Create connection to firedrake from meshmode + + +# TODO : implement this (should be easy using export_mesh_to_firedrake +# and similar styles) +class ToFiredrakeConnection: + def __init__(self, discr, group_nr=None): + """ + Create a connection from a firedrake discretization + into firedrake. Create a corresponding "DG" function + space and allow for conversion back and forth + by resampling at the nodes. + """ + +# }}} diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index a0c8cb0d..49e87408 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -28,6 +28,13 @@ from warnings import warn # noqa import numpy as np import six +from modepy import resampling_matrix + +from meshmode.mesh import (BTAG_ALL, BTAG_REALLY_ALL, FacialAdjacencyGroup, + Mesh, NodalAdjacency, SimplexElementGroup) +from meshmode.interop.firedrake.reference_cell import ( + get_affine_reference_simplex_mapping, get_finat_element_unit_nodes) + # {{{ functions to extract information from Mesh Topology @@ -128,7 +135,6 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology): neighbors = np.array(neighbors, dtype=np.int32) - from meshmode.mesh import NodalAdjacency nodal_adjacency = NodalAdjacency(neighbors_starts=neighbors_starts, neighbors=neighbors) @@ -150,7 +156,6 @@ def _get_firedrake_boundary_tags(fdrake_mesh): :return: A tuple of boundary tags """ - from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL bdy_tags = [BTAG_ALL, BTAG_REALLY_ALL] unique_markers = fdrake_mesh.topology.exterior_facets.unique_markers @@ -179,7 +184,6 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology): # (ordered lexicographically by the vertex excluded from the face) # and meshmode's facet ordering: obtained from a simplex element # group - from meshmode.mesh import SimplexElementGroup mm_simp_group = SimplexElementGroup(1, None, None, dim=top.cell_dimension()) mm_face_vertex_indices = mm_simp_group.face_vertex_indices() @@ -205,7 +209,6 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology): for fac_nrs in int_fac_loc_nr]) # elements neighbors element_faces neighbor_faces are as required # for a :class:`FacialAdjacencyGroup`. - from meshmode.mesh import Mesh int_elements = int_facet_cell.flatten() int_neighbors = np.concatenate((int_facet_cell[:, 1], int_facet_cell[:, 0])) @@ -214,7 +217,6 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology): int_fac_loc_nr[:, 0])) int_neighbor_faces = int_neighbor_faces.astype(Mesh.face_id_dtype) - from meshmode.mesh import FacialAdjacencyGroup interconnectivity_grp = FacialAdjacencyGroup(igroup=0, ineighbor_group=0, elements=int_elements, neighbors=int_neighbors, @@ -241,7 +243,6 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology): except KeyError: raise 0 - from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL ext_neighbors = np.zeros(ext_elements.shape, dtype=np.int32) for ifac, marker in enumerate(top.exterior_facets.markers): ext_neighbors[ifac] = -(boundary_tag_bit(BTAG_ALL) @@ -323,7 +324,7 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, # }}} -# {{{ Mesh conversion +# {{{ Mesh importing from firedrake def import_firedrake_mesh(fdrake_mesh, normals=None, no_normals_warn=None): """ @@ -424,7 +425,6 @@ def import_firedrake_mesh(fdrake_mesh, normals=None, no_normals_warn=None): nodes = np.transpose(nodes, (2, 0, 1)) # make a group (possibly with some elements that need to be flipped) - from meshmode.mesh import SimplexElementGroup unflipped_group = SimplexElementGroup(coord_finat_elt.degree, vertex_indices, nodes, @@ -472,7 +472,6 @@ def import_firedrake_mesh(fdrake_mesh, normals=None, no_normals_warn=None): # This changes the local facet nr, so we need to create and then # fix our facial adjacency groups. To do that, we need to figure # out which local facet numbers switched. - from meshmode.mesh import SimplexElementGroup mm_simp_group = SimplexElementGroup(1, None, None, dim=fdrake_mesh.cell_dimension()) face_vertex_indices = mm_simp_group.face_vertex_indices() @@ -498,7 +497,6 @@ def import_firedrake_mesh(fdrake_mesh, normals=None, no_normals_warn=None): return faces facial_adjacency_groups = [] - from meshmode.mesh import FacialAdjacencyGroup for igroup, fagrps in enumerate(unflipped_facial_adjacency_groups): facial_adjacency_groups.append({}) for ineighbor_group, fagrp in six.iteritems(fagrps): @@ -518,7 +516,6 @@ def import_firedrake_mesh(fdrake_mesh, normals=None, no_normals_warn=None): neighbor_faces=new_neighbor_faces) facial_adjacency_groups[igroup][ineighbor_group] = new_fagrp - from meshmode.mesh import Mesh return (Mesh(vertices, [group], boundary_tags=bdy_tags, nodal_adjacency=nodal_adjacency, @@ -526,3 +523,74 @@ def import_firedrake_mesh(fdrake_mesh, normals=None, no_normals_warn=None): orient) # }}} + + +# {{{ Mesh exporting to firedrake + +def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): + """ + Create a firedrake mesh corresponding to one :class:`Mesh`'s + :class:`SimplexElementGroup`. + + :param mesh: A :class:`meshmode.mesh.Mesh` to convert with + at least one :class:`SimplexElementGroup` + :param group_nr: The group number to be converted into a firedrake + mesh. The corresponding group must be of type + :class:`SimplexElementGroup`. If *None* and + *mesh* has only one group, that group is used. Otherwise, + a *ValueError* is raised. + :param comm: The communicator to build the dmplex mesh on + """ + if not isinstance(mesh, Mesh): + raise TypeError(":arg:`mesh` must of type :class:`meshmode.mesh.Mesh`," + " not %s." % type(mesh)) + if group_nr is None: + if len(mesh.groups) != 1: + raise ValueError(":arg:`group_nr` is *None* but :arg:`mesh` has " + "more than one group.") + else: + group_nr = 0 + assert group_nr >= 0 and group_nr < len(mesh.groups) + assert isinstance(mesh.groups[group_nr], SimplexElementGroup) + assert mesh.vertices is not None + + # Get a dmplex object and then a mesh topology + group = mesh.groups[group_nr] + mm2fd_indices = np.unique(group.vertex_indices.flatten()) + coords = mesh.vertices[:, mm2fd_indices].T + cells = mm2fd_indices[group.vertex_indices] + + if comm is None: + from pyop2.mpi import COMM_WORLD + comm = COMM_WORLD + # FIXME : this is a private member... + import firedrake.mesh as fd_mesh + plex = fd_mesh._from_cell_list(group.dim, cells, coords, comm) + + topology = fd_mesh.Mesh(plex, dim=mesh.ambient_dim, reorder=False) + + # Now make a coordinates function + from firedrake import VectorFunctionSpace, Function + coords_fspace = VectorFunctionSpace(topology, 'CG', group.order, + dim=mesh.ambient_dim) + coords = Function(coords_fspace) + + # get firedrake unit nodes and map onto meshmode reference element + fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(group.dim, True) + fd_unit_nodes = get_finat_element_unit_nodes(coords_fspace.finat_element) + fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) + + from meshmode.discretization.poly_element import ( + InterpolatoryQuadratureSimplexElementGroup) + el_group = InterpolatoryQuadratureSimplexElementGroup(group, group.order, + group.node_nr_base) + resampling_mat = resampling_matrix(el_group.basis(), + new_nodes=fd_unit_nodes, + old_nodes=group.unit_nodes) + # nodes is shaped *(ambient dim, nelements, nunit nodes) + coords.dat.data[coords_fspace.cell_node_list, :] = \ + np.matmul(group.nodes, resampling_mat.T).transpose((1, 2, 0)) + + return fd_mesh.Mesh(coords, reorder=False) + +# }}} -- GitLab From 359d8719856a231e8ea9a6107eb16b0d1e55a70c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Tue, 30 Jun 2020 06:07:14 +0200 Subject: [PATCH 048/221] Shot in the dark at Firedrake interop CI script --- .gitlab-ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 34ebe33a..04aae962 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -85,6 +85,20 @@ Python 3 POCL Examples: except: - tags +Python 3 POCL Firedrake: + tags: + - "docker-runner" + image: "firedrakeproject/firedrake" + script: + - source ~/firedrake/bin/activate + - pip install -r requirements.txt + - pip install pytest + - cd test + - python -m pytest --tb=native --junitxml=pytest.xml -rxsw test_firedrake_interop.py + artifacts: + reports: + junit: test/pytest.xml + Documentation: script: - EXTRA_INSTALL="pybind11 cython numpy" -- GitLab From 78ee5444ab622f5366e8a1d9918ecd4458f851e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Tue, 30 Jun 2020 06:15:42 +0200 Subject: [PATCH 049/221] Install POCL in Firedrake interop --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 04aae962..de58fb9e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -90,6 +90,8 @@ Python 3 POCL Firedrake: - "docker-runner" image: "firedrakeproject/firedrake" script: + - sudo apt update + - sudo apt install pocl-opencl-icd ocl-icd-opencl-dev - source ~/firedrake/bin/activate - pip install -r requirements.txt - pip install pytest -- GitLab From 098f8f02fac2d0670c52fec9d7f3315d239e5d30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Tue, 30 Jun 2020 06:35:27 +0200 Subject: [PATCH 050/221] Firedrake CI: Convince apt to actually install --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index de58fb9e..5d8ce4e9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -91,7 +91,7 @@ Python 3 POCL Firedrake: image: "firedrakeproject/firedrake" script: - sudo apt update - - sudo apt install pocl-opencl-icd ocl-icd-opencl-dev + - sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev - source ~/firedrake/bin/activate - pip install -r requirements.txt - pip install pytest -- GitLab From 9aab8aef87b02acdaf20d863cdf31057166f4dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Tue, 30 Jun 2020 06:55:11 +0200 Subject: [PATCH 051/221] Firedrake CI: Actually also install meshmode --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5d8ce4e9..025048ac 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -95,6 +95,7 @@ Python 3 POCL Firedrake: - source ~/firedrake/bin/activate - pip install -r requirements.txt - pip install pytest + - pip install . - cd test - python -m pytest --tb=native --junitxml=pytest.xml -rxsw test_firedrake_interop.py artifacts: -- GitLab From 2a701c0ce4471d48047458b537fa4043b983a61a Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 30 Jun 2020 10:02:41 -0500 Subject: [PATCH 052/221] Some doc fixes, added intersphinx links --- doc/conf.py | 3 +- doc/images/firedrake_mesh_design.gv | 25 ------- doc/images/firedrake_mesh_design.png | Bin 43833 -> 0 bytes doc/interop.rst | 101 +++++++++++++++++++-------- 4 files changed, 75 insertions(+), 54 deletions(-) delete mode 100755 doc/images/firedrake_mesh_design.gv delete mode 100755 doc/images/firedrake_mesh_design.png diff --git a/doc/conf.py b/doc/conf.py index 8ff4e0bb..bcfef766 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -33,6 +33,7 @@ extensions = [ 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.mathjax', + 'sphinx.ext.graphviz', ] # Add any paths that contain templates here, relative to this directory. @@ -281,5 +282,5 @@ intersphinx_mapping = { 'https://documen.tician.de/meshpy': None, 'https://documen.tician.de/modepy': None, 'https://documen.tician.de/loopy': None, - 'https://firedrakeproject.org/' : None + 'https://firedrakeproject.org/': None } diff --git a/doc/images/firedrake_mesh_design.gv b/doc/images/firedrake_mesh_design.gv deleted file mode 100755 index b5465390..00000000 --- a/doc/images/firedrake_mesh_design.gv +++ /dev/null @@ -1,25 +0,0 @@ -// created with graphviz2.38 dot - -digraph{ - // NODES - - top [label="Topological\nMesh"]; - ref [label="Reference\nElement"]; - fspace [label="Function Space"]; - coordless [label="Coordinateless\nFunction"]; - geo [label="Geometric\nMesh"]; - withgeo [label="With\nGeometry"]; - - // EDGES - - top -> fspace; - ref -> fspace; - - fspace -> coordless; - - top -> geo; - coordless -> geo [label="Mesh\nCoordinates"]; - - fspace -> withgeo; - geo -> withgeo; -} diff --git a/doc/images/firedrake_mesh_design.png b/doc/images/firedrake_mesh_design.png deleted file mode 100755 index f394f9e7f3d56138ff15850311e6bcd6dcdd7bbe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43833 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

>8izt!`;($TvHB=k8rvy|e?> zfPavJCKo2{z)D1Hvi-bY0|Mo4EChPZDx?>vb4Xp+7UajIPC3Dv3Y}09LvG`ObT~6X zL&L)uq}(165kdBQT)O8a(h+%a3-*GzcVIGwsTtz?+6lMTs7@cio{26298N{S+oB-Pa#wDI)P!~;@%zq` z$BEtr3n*zZ;YNf#4EGz}X$4i}#RB(kn;L33jh||8P(unQWCBRs0Z8?zvphKaL9k5Q zkxJz@h@}uJL>(C&9bG^s!VqgLuqLMSGtk6t+jC+AjFtE=*i36@=S`Sk>W=?#+`%FH zeQq|`5KMh#;GALCUS??}_Tg1c=u;7z=X}rhVW6XquqwB1iJtf4b0?>p@F-v_tI+P_ z{A%G9NM#pB86Q|Y4T4Ju2)GJefrUL@hZ5vE@UqD7qyV67E$Q{SxWwfbSWng1l-#1h-2)XAa(Md1Pd^>mW&r>4hbu@8t#Vf&8#%1 z(Jw;+f<@i+&x$wMEWEZ19ZM~^^bcPZUqA=J%V++jVy1uBFJBJ*1nL8N@&Jey@@gkV z?f^7!Xs#M^R(-1mRqqNn7@mCqROz=5d0UC+3r2y6om-Ep!4X1uKjPmb>PpNCgZeJg zzLk`gHUZr^YQ*2b8{XXJewaxDqGA3f9IVJk$a0{&b2zRTC_yy0|NG~+Jt#?PimQjm zj=ziV{Qr$zIf{a&*^H5mDezX4c~baZ@0O@|@726nvl zAdbmE1bo*7tZcwOVo8|op}ZoLp`ux$$(|xx`&7|Ef0C61Vs~jF)rv<`*tG9u_k04vPLFHW;-5$z%tlDh|K(}VwxW8 z$|Pt%J5U)V0}`iR*VL>6KcI%-#k^b~RIsX-FV_$uca}zr^h-E+{jfA0J>R$m=V%at z1U9c*LUstylREuf159px-@iv9VUzxZ0IG3E-Tk)6(4f9al_NYd0G##7lVNyW!u_Yl z4=f^}6@6dJ>JSYPTGNRoS2-m zcH%8Y)D*mr>-lpn*oR?1QZ@2(K$ld%D2>0W`}FD9gK^j}-M=4fewF;Pm`#R57uIP$>uxMBO=a59bs%GR(MH0>eC z=UOLDe}pzjKohU&3nHvpenVt75K2DQj5a#+MHG?BmV4WUEcu`2NPhmnDS0i*kIyUHIX zCnaqTR0Oj_9j~d1&`>AG6F~WD$IojLc4P!CsUz*qg?C@*n3L1f^(b+W<=bNr!{mKL z@IOtVXC$+nr)0`(dwX-EhC@;7qTvULh_Zmdie(c$-r4K%IhQt2Q4t>7aJ;PW=zR3x z7|klGrq{2p3gzQfGUVf%OyB%EfAagPzyOpkt?~U}vhFWOHVb?#_)rm!cmI4H8x!u$ z9W-?{qj^y^o~ab^MTg3q=i{ie z^=rXKN>09@$$dWJBm)=7C^FJV!YCak8e^@XXvmrz7 zKyr6y2dwaBIJ|2h0R)brlU1_Gu7%GZi})Wf=p0}F?VCq|L)`2|vrjLyt4FXu5zh{F ze+nDSU2A43|Nm5V=3zOmUH`vBBq2i-LNZjQ%tM4w#wcYjB}1YRB?_T3lrp7^A(5Fh zP$ZJVCK;khRM;U@WGH$+EBpB!$L~G%AMb;@ulu^r>pa)`PV2#(Cod~dMZ-y=_Ho<*jvGY5m0qnz34ylbI;__OY zk6d};Nc7YpMwWdOqHN6O@?(|4PU!?omT`Dr`0P_l5m z*{_D%J9F@>D>@n(&9ZmaYt{9~9!kf_#i?P4*Y}b2vB=SJ@|qmy*YOjtta}sqYTM-U zb=7ZwztYYNG`;yQe%2M^vWFi>M){x8y*e}JaI#IDp}zRdPbsfgX(%_l0cDmWyh%fs6m9xjm%ipZgfq_%g{%CAJw`hD5 zqy*q=u!kqV#3HTpSzlGy4x;eJt(>`<`#}xl5TWGt{g{u8L8xDene+WPXXlG0` zWRe=d+QSH(SfVPa8qR!))%3%)KLC9WHNr@p$}r^1-&X&cqtW$8bIj}wD7wMAtsITe z!#&E#xJgb;^Xx{+k+tg8tM~PDemnZdX=#7nQLdueBwluuPUWW&r725Kr_fD=@jU@OO1+-mR(OuPQ554B9 zS-X5D1od`&)E<7o_v0)e201`iu8cORxG;uyt^eZ8ZgMsp?fUaC>7~DpwEv&4B!{jxBS0GE*#J+N&rnN5GMQ89u3y{zo-um~Ok%7dyMGlC}=0n$HC?L|rmD;WAvbb&{lb zJlZ%6bc_$J2c4_Bc%(+D$F)Xzj=&60vp;=O(SfwP33=N*vnCIyO&j%Q&B8DCZ_akE zA<-rDE&3n3d4ZbI+?kQ$>xt&_9TH+VmxUB zG_nH+R}@~}c%hbP+>i%$V6o~{@|;sCoR*Ux#G(k+($tLpl}qcpC$wliu>=z45K(gY z)v)THjX4(UQKI?A_E}vvFE`$;0XT)Kv)N%fGbOM`Kaze-65ND3p$-^#sI+3<>{Y4* zE^k4x^)^TY`W5$0*FNKJq12`YiS(ikXqGo$oe`nc=0WKdic{53Nzqtpz_9FIiIK-RB0V zX5BE@6tMO7{_6Ml_jli&6X(Z zp;(WDw&<5!N2_jd$=J<|8m$9fwTwrP+SAThPj4+@g2g#;J?}nv!2dd};<&c%_!&7d z-=>?zzJ8m(G||t+^S*xn^=B_HE{*+e8=t_2A;x$*kl@S{;F+sGoQvC&X*=yw4{Z$~ zf^ERHg@942iyi({{S581uo0zaR-x%fMRkJXfsl}skK93(m0!KNp}!Em{!pT6vdxN} zx#_K!UekG2x7-pC7c@{>XZ#FSi%J3!_o7O+n(1j{TDc{;DxF$$lA4-Y^IywZ1t)Lk z<{tF=nqkJ`dv+wa&#OR}&E|(+-_JI+b-wO&;?qMcm};7a-uP$P-f1suc_ZP}i^-do z^6-nbq&sZF(&^i%C1rGbX{wNhRzb9>E;f7CM7c+89W*0H?w4O$QMvQlnESw}A0Hs29Zr8=dBgf`8 z^~+ropQ~(nBwt4cRpW|x2e-Ua!>JdV}liHWxQ&IbPTZU$eA@tdWm zt*GCifv(Sv{?E?^KdZZWR#=N_o1oaA$38xIe%g8QFl`(Exa$-0LPP7hd}bU>^z`B$C5yNTjmMr=8n+Bj7XhDmU}srY zlHE^?qcm>e+Vw$^LXn@iy1Lql|KI$;^md7=Gvin4n~WT3fxB=|1%>n$Rsz2Djp^^E z9CGlpp9w;{NmR~2@SFh#79Tf+j5i?ATz`pg_tkuPWzcvvT3HEY$8Fqnk9VK(cmBW8XAf2_;Dgc_qrdi>#;l4X3GOcOG3xHnGVrlb?9LcSuD;x zdy{u}1Fd9-!Gl{3`~9^UG=Zbp;@yW1Y18&v%$hacXxsrdRSO6VaSnYOH07mN`9!E= z-v4Hzp%LkZG@#_MS*_@mcc1FD^k%(wNu2N^QJ09h{KW0tu#CV=9mI#DSUr44v%kOB zo|ngrl+}MOWOm&R8bPnW9F2%bqeYu77spbb)7|(^@6%oR(rB4Bff=KsRY$xo`OnwC zzZ^-=>X`b$kOP5j#q3Rj@r;|(?6FgAB<^C}&o?czEfm+fRdto;ah%fq76=pt35cx^z7}cQaV8ZC$sl>rTLM(q{Q9bo_(2&!U3fE~I6?1V zEKAeZ|C!Rg!S#-wwc=*K_@5TQ(=)i;spP~(X9}(?Elo*HmE5!F!sz#$@hT9=bcvH^ z&sHM>|3CYf(+ZmzCp^zYMh?frW@2I@G0_B9h_`zBjdep=xh6)3XO|FC!DeQ;JMUDh zsb8LX^Q=2hI;Z%O=AUbMdRCNKIS)=f(i; zJ6^H&%FwZ_PE_m5iywCqw3F?6BjE`2g@I5gt+@hI0I@}>R-g?AkeWJTP9+(&jWACjtp7s8(R`=jEB37fI#=D=@3 z|7tjH>O0Z%h%!K~j}4|KF2ukZoJ$DLOY`ry$hMHLIgz+XJ9Zec4DcLF2D(~fBI4r; z$tc1?atA;rBW~#&F(XJu$ln|S&s;6tiIh6<*W&C!b0#iy!V3X5Bwph`Revv!FLv1$ z*7x}Q*AQk{@&Rr+Bb`XAprgc1{fgEkx=_!`+&u6~S`|qmIHAp0GrT9{*aO`R3zQIrwYL!23*2O!owYcuDmdqBrK?$_%)I0HD6ZNjevAQ=t(ZRdz;fhRDhhF zjk~`2V=Gh_t~xPnkc5ByPc(SvkrA67ot)I}-sjDOLXI|4$K2Zlms8L0OZmia z^7vDA)x!vo0|-lnaJbuiCdnUI$r=b(O3oy1KkP!@(0&91oSGc~1~XcQYTUp7H&CMY z5XbhDGN_BYq9YTi*_zdi}aqut&6OYoGDc>`=v z`)H{gI9lg8C`V zUVPyu8o#QO9C$w#I6FV+v1|&5b}gZ6fu!XvD;d}w3e~R-u^Y=?4L>N!fO}j{5fMNw zKp)bqWj@v8@?I^lJu_~E_kL7|oj=?#3l?k=XC(~>|8eW=?d%i)_==wXvCy$U+=$0O zgWvZAG~T7mX;<)jgk$UPQSUZ$c0|`Um5{C6T^*E|NF!9+{WlGF66zsqO7yIoSG^ z>be0J*H*gY47z;T%g;iIu6iTIFJEdd-0+t4G7`PH#L%`&xs@0rk7`FJ15DY3nmz?zCQNN0c#leL~RuB}@K! z_(dz_37QjV%4JX`vdLkWoe(r{$r7O!GXgVe@qv#>R}i5~)rUFNO?#e6H4G?|4jxtz zxkq&P7<=U#2L=-@j|6xVxdz!5nkQ0UXZG1UyVewqiyQMNZrAKpi>Km8NcgyQ(epw- z1)%RLmc%@w;>c~4*wn|$!^0!#?%n#;znia%ZHi@)R#_7JwSrQ2$9L-3 zF*@Fv`oq5@;XS#!K2TPbdAnpwtVvSTVb(y~c@Sfmj*{r)tv|K)p731VWCxg3VmnRE z7w=R)|Xe7e;=Ve63N)m09G9D!R+xl(OeSv_@HHJK15{t9o1mFuZt^JPn7Z*olC!SZVS z^`X&cJ7~!t?}k;Ma&0$g-}%DwpQ5HtNbvN3cYe-|M#f=1%iZ_{5sB1o*s6|+3arOA zC>BP?C@0;Y`x(2hi3*cDHuAmh+j6JLE&>5C8tF@EBCI zj1su__dd%DMk$gw%ax9#uB#4b+hCs|+(d4d6$h_;GGb0Gsxa2R>m7~7cN;|G1}jLN zFDSVpTJ!p4?3xPk57%wfW(x}iz>9MzYU$EA(UlYb6zw1{LA_a5mgz`gOibUA=;%G} zZA>4+nci->)PLJHL#L;a{o~&GSv578nX3@?+vhLeDQqnU72g~6+)fa|h-fam*KuB( zWNipEYDPA>W7@tu`@jkl1oYH&^ysah%l>RZehd>!Y&yai9w@$10^HN56N4WG$4FkjvRnyWTjPe*xk&f<6!E-FxGO3ro*b?m0O1NS8(rc(@VTN7)Ru z6iGNjFsu6$-xKAw?3wzsgGzRon+h77&sCr3qIaUm{edviqit>5LYpaFyEX>Yrpvdx zc06XGr&+czz}?%vdFB# zA$3xFvqcm+FiGj@lbr4~$bO+^tlJ-p4_s}#IcvXZU}QCw6Umi`2HiL0Dte7XzxzQK z6az}ooCR9?jEkaBfBbf1hkGZ^_H!EDVduST3s+A`X$GLY&pj(Uy8wMl3M_9xjqrRl&oe-&Iw&{WmFkp6pAS-Ox|(44Q6TMnoS8)Xn+uMwOJthD%=#xdMVf zbH$?zCNlXymz1MhYnKSUvHxM4pW3-}&wA)GjSZchfejs1*UkI)$DbZ%TlNi1wEKxM zE-z2!R~g6oouh?$T2MUOBIILYf$Ps-zqTa=bZj~Mbw$sh7`J^3RFYRpT8&n(xpkJi zx^@r|b75pOltwLYs{E#8e$?_Ny}`U>BpI=sdXSbJ)o3mVc^!jH?PP2-l{!N4Qo;+x z3q-eP&2?Bmk4hxXr4RczIXqy`tDlSD^)P8KI$ewAoUdtdztW; z3564DVq*Kp*mc+}>vGQM-RLqN2kMOewr*U&J(0NfGBj)tb&8lQVr&bq8NTO+&OW+( z$Qa7&W{QHjFZPvOQG3w11^Fv=6wsp0haphP?6AV~`~0DAOG@$I{K@W#*A1xW zf*a!#ryZRf8Ks=hP$zdaeH5(4(d0Y6B3gL_q-)JY6!SPYfNVw2${&LS2J)^xH!Npg z$v}EQ%kS%}f3H&Ph(G_x>%cT(qI)0^){#{EGY)PcF>cn&_XNJzZlM)Zirb(mM_;Mg z8q@tnTE@>~GO9ktcXxa^JTBVd+6vTkFJ3?A$ta?x`Imu|es%AQ zs>A`Y75SVyo_5(1IQe^w8-kA-6||qMxoLL_OKWWX__;xgEwNZX=Ahy~N`hH=CsxO$ zVjEBz?vyq5)5307KiZkaEp8aM*BK8=LXP`hY63-c!r}90hrCaW{lo!k<>I8A8Iim) z3*o^Q%H4eoPXii7usnZ^d$D@ijf^fV1Iw0Ut~x8%+VC_bO}Jgh)}KmQE2sRAz$PYK z)cRZfiENi*3G~c4%t_v7KEx%uNF}T~{`s_yMKXf8u`te9QdLmF0J_r9N(&SavyE{7x=S-C-rnp(rlv&~i67DO6@X*}L-q+Wc$f4~<@2 z_Z(nc`LR#-a>%;uiY5OcTQ&2pwuNqUGwk*LAJ7>*b+>6T{4b?uYA#WN6I3I+sfkxV>Ir!H6LYL3S7G0$f3g!DeR z%j7U%;ohV~C3_@UZ#K;u4UN_LSC{Sg1z1W9gHSS14;*P}iJ>Vg7gKdBl2nbKtT;#T z0M*9}GN*wyFJcGJ-l=J-b7gEk#q^rKn1CdH3J>eQ`8U>YUa(@Grd#r%*JrZzv^j4h z%1cW{*iOMx9}OIU$D_d8hOQ|ZUe>K6k|}^|Vx|C<9A4k@MJO|OilJfNIJQK|QQYC$ zbkUMMjYcYCZ~?G;ja`u2Mug^eZrjcSM2vtK-g9=gL(~&?)bGGX!GpwuhyZbtk!1)e z^ldOP5za93#=76sMtaf;WMo)WY4RH-+uFz=+zz)Gkt=_jC9Ts0i!G-~p^=aJu&u~F}&e0MfyIyrvbEb3ts1{~F(}C6sWfFax9Uqf+|9-gksVAMy50Y)`-#^Yi zeAk+~+`SXpKoDpZy^kK%k@Pksx7j-f`=%D!(;T0BxB*Ysc9PB`a}CA>R1IDKr%mm; zYDa3iKCR&1c1a4Lts{uHB)ewDS8yTH>&$g*DkUR~FS3suz-KoJ+PTXlDZ1L^l{>&( zITbPzRxOn@4qR^q?^JYzWk7csL`vQ^{rRNqB%`37XZ&3d1a7AMH1TPE#dXgA3+7=* zSAthg;f-Wc7XV*j<^Fc!jJ(0v3gs7JnA3}xyXO=+h7z0P5X~A3kCKv?Ur&8w&+U+o zNosQbr;+gCdFw6ZI2A7 zq+{0av^bB37H+HltNE(}0YTr43W!4EP+K8C^pLFIFgo&zsbM6n)@&Ru#kV_+-@Bzx zzrKAhW(*ptvg-QQL$65*J7~N%KF@8LJOYHhuoZf}W>gZJ)XO=$uR;vIYzY$wH3{#= zVgwqQM|9TUO|$w%#`NsH-0F3=8(0Dba{(GcGY7eYpju54+TVHqk4wBeOjNNNa-q?a z--1VRR}BVr@Hew6xons`hDkE|LZN{?k_@KG-RRxGC@2-oeoOAuM{EJ)rRneszYx7l z0q`19sbCiGD}Zt7$E-edjMrR^s53PU?G)Hn>}c81ch~_iFz(e4O|cA-oj>1q#HPAi zFVr_YT>?~}#DTbT|Ne1Cq2k&>Wf{z2nG#z01XCg}^Ck|HHQzrqMB+Le2_oeU*A6)KjiJ9)IyOcp0jOFG#BwiZT|eJ zTq+?>zz0rEj3!uabYFo(TB;*hEG%7@B zbPy|%ZF7L%5o0j+E_d}KjyE?dBlW(?9=OPC5#_(BbIR@8cIk(Q3ipBP&zHAZP)vL{ zte)F|-(qBx^cmvNWT=k-%1f3Qf+k}d;6K6bMd0|`eegLV!D;eo#7rq5A#n);?v9P{w#l)o}o}6W*oBSs|qg_DhWvykZ20A zb_1GQ-W_|ch?qGYE(c0W!V}pYqh_Ln7Pj-y`--`P7qYf~tgrrDZO^0r?~^h` zrVG!BAMiEIpDb#am|FBAGFKHVoB($vp-+Lvbm=u{vQ-*4mZ`Sf??xMjEBFtJ2OGnk*hz<%wBHY#y+>O^;rb4|uKmgx1q)OA3>y=^Gq zMZmt`A5E@LGF{p22a}mFMYL2o|oiH*urT#-EkKPXZBN+jJOk5O&{pJ>Dc6?jOB?qW+n?lj) ziBtw(hOp?2tFpMOKVNH+1X2z+@~&*k=wkx%*kjcm03a7c+dUtcuJE?Hs<@gD^4tw%yRk%VS!nMoLQ7* zX=B04OL0coqvf7{X|6I0Tjtx;qX>x(*SCg&Mek*`W`U(Zy|ekBFaxq*{V`V62c&khWgDqx9`$r^UM~l$oR8bqh>6Jv>ma&1zK85bFUoTvo!6*@#BC9 z^{CVMkDrH4_8!yGGQt1W`@LDbHjvoflqDDiI(>TnsXn`<^}0)I`iC?MH{?j<3~a@& z|3$cF#96PM!6c4QThQmQg+YQm0D~8BikSjV{Q?YO8!54!g0&Q|I083Aj_5Fn8Jsp+ zS3eh?j_Ke*Z2mjl_e>R(TnG0@FJJ*+S0Mz)=~p1wU?!!6f!t1M>v8qYN8WRgu~T2v z%3xXkLJGE9A&sCd)vz&0B_+`c9etT#aT5)zxsfU4_rm59D}o334((E~t9uAHl2_J& zKRoBs2lLRrpq3gM_5ihb`j&A^t*rJuE&mUJ=y41kz!4Nx@4q3o;%}9|l$|}8S_lf! z0Q^?be%z|tlQB%vCJLdM@?Jo==Q$0`=lUS%|&;C^4 z{8x;;Bioh^G6(Zq9q*?fz$WS|3W|%z%KxL|Bih?`9~>)l*LAwQEC^11HyIrKwf?s& z9T8ZAx}|(avK;ZLYce~ybdHQ24z>bo;7sw%QS|GO?HJMOdxL8)6W zU+z<)sd`CV7B<{FI=3!SmTahw|d0G+MSS1gc5BQT=-<E4l(;3ow;t>vMgZlqQuYBq~m1I zf;p*2)fiov##HDQHO!@H0;{*EfKfAW)#Am7j2;Fs?y+Xgt-QSJQ1(EnLsL&~wIx{X8QF_zWKCGqo}n z{%D|GiOnm~LkARA97R0|(={Gxz(^xiouc{6-}x@%W=QS2~U_32UI;~wrAdyAtW?MJj%XCE7-pC38@X%5(qq>LmbCAsw4`I7Sb667YA z%p8Nw?g#r5L3QFju4%Te^ld|*=nNh_`2QL4x6PI?7KD%nb3T$5qBTgK8O$9~{ZF@` zcc6wFZ{#+F!%#G$bH#`j z9llblW=LW|CBZ}IT)O@|h`js$**!24HZM1yW`>~*T%jKygAFSWfp1OvXLvFTKmH#q zXFYANNzf?|@8-k{ADM@;etmU|@l9*>+O z8Y6v%y*ue`xBtpq!Z@-bKHzM4fP) zYDSeqO3lG+wfRdPP~9``AhLhUB}x~wdTae8uBan@e|uV+=MGCx<;+dsEk=&qc5Yu2tB zIIICzLi@?n-G+F_hy(&4WAKP6(?c5Rw#309=@v*f6P|@uY4us4*pWEAb*oke>t^pI zBUWj+e})k-bKNhl5of89JAfT<+R@`1Tvj{Ufo~@&=pU#i8R;_K$UG$Py63cWzdkEv zZcb0jAQ$_{$&;dtdW@Hj1E9b^4u-HK@ZKvwt6d>sMX?nN175hzr?<5es<1O?? zw9e~U(!D?UdACGUf|hfRu04GQ2*DqWX(v+DLp)ZQt|c?Vxdl(Z57=TcgTS@+xZ96M zMs9K6GcEJl71x$iJ2mJNXH)Kc_1PtkzKFd;ifkKfjZm-uGyn8d=aMOh8mvQzDTAS8 z0GXKWY*xKK|0c4*x2lG7t!Cn-kXd7Nxt(jsgO#nmhs`LvWiV~e)t&u!<@C#sz3%38 zb(7L6nG5J;xFaSV%y(Z&3L(=_zFu8$o}xM+zbd;dnpT*>Dd9SbqDI*P2k65p=9fXcr$OnAwxS zlxrJ-*#H2DK5fKJk^BDZYw<{ts5`gpmd13@$THS$>^9S47TYOn4}xeg66d2%k=@#0IK2@u8?{{1nM z4&D3K9nRW^CfY{dREUZ;Rpb}cZKy(h-1|l(tgY?5J@L`MkY(OuV%?H|b z(dn6i$mYdaOV)pKkB^p?78V;A7UTd0N-cbg=?KdRGGnqvxY^fd_iOHOR+iY&q2qljedA86p#lNTZtZG56K98?o5KT%%Txof@l|osEQcu!wT38AWZYTp+>9c z03;*mJPJ)8w_-zsb8~L2lP@X_7~YLz0HzcWeh@+;qdFcDkr5b~NeIgqDGPztphH0x zQ&_t)+KP~(JsO_%aV8<^nv&CGuwb1}Cj%4Wv)ce%0k&G3L@a)4I!Xj-wEK5m*c!LX zxN#;dHx#_fS&s~9Qe&{-E$~wT+p?QjXX00od`QZL7@OT8>@nw%xx8X6OB|pmcmb4O zV^@fdr^X3=# zXDNwU3IYgS3L>7`TzV?~orLX)LK8(xw1dHH5{(JdR43c%-+vt?n-JP3RXV!&?^OxD zV2faAo%;et*b#GxGW~I2**&QOBEVgB2M=zAJIahmACz+-mA6X-aFHQLlH*ODB(lcv zgB8!L!KHu&z{W)H0Q4ny2z2`<{VjEQvRoD~8*IR%z%#MPF&^DmzuxQHvuRGAH(P*C z735I80N7dP@z#9ZJ{tsEj zG0mE~1*7g|cmRA)RLeZi>`MvTdCw#)5#(TAOJ^-pZBmc@8!V*Jm04}1Vf^u18fLr} ztXCNh6MEzpp%IiF>eYiF8W5_W;%mpY_UWnbHIyNOy~xBeef=i+5>JuHuhZ?)r5;y= z3BT@_vIb5g{nUUpHe2z>>Sg5;oTy3MZ=~NtTKJN zo?HJz1ow#RZ|>}A)1JRK#tn0Xy&IMdTu#7d3Ovqqrxl0W^Jk?SIDs_L0r^2_J$-MVN{Qra65}EffFKHdn}R;>3SNrZ zI(0Z}B})J(;m4UDvj;TzzV!$%oj~GSw~h_zq4$}9PH1j%eghIG{Hd#@59B3BP~y+G zt;xUk0r@GetD-|t)y^NsoG0zSX_xX#WiOB4j%{9G?izw*r53=^lkEZ8 zMlGmGl9(-(vNv$gZp4OmB8qoNLmsL2hISkrh_pvyJ%`d-#=dTq4W3!r?QkV}u56f;V$KBq;A1qfcm}8wzpMR!jkuid{*A zlpIX=#4fX_1`s-Dlsx=GuN2z$4paxoj>Mmm9!#FvI5O1!<6EdyyK*|7sK-er$5-uk z?b=E18CrycY#}bI+#AaaGc=RKM?AP9T_dOFC^7(`GMQDx{k4w|0mEe{P|$47_diiJ zjaiql&9V>i2;`p>-$c9Hq*beJfIs#k=q!x%VFQv>5Jm(Y(H{Lto@U#JY+@4cC;Se~ z)}z3Y+RxXg6S!-((}>ITyU7u7alwODGO3SKCAx4jVo>IPhPq@Os(rDiZ7;R*IdY>I z)jP_2@56M0pqjSM~GZ& zPV%T`U&qOD-75sKO^&|B&`uf?fun0Q#(}p+o0{HY$cMX{FMxx6=u~nd9i+#>Urz)M zvL&WCJ>_{dLR1+YuGgGsLCq5reK>h*p$;nG@k#)Y_SjF9&f6%A3k6G;Fc&j>3#%b>e6p%vI|UlOnh5}RzO{iwP+Zq>UH6T^PCOUi$3+{yLSnIpP{QS);E#UL#0h8JuPR(s|()^GK zB6x|YJbz!yjn}2s&^A;2YK5uy){}?Qa*o#qByz8abeSE}X9>6iiI#~~@1WpD=ruRr zl62D8%Oq_FGUFloTjtpIvD(_UYzHDq2yvaXR$s)kI(l zaeyj7Rg*FO)Pxv67?6M4W9{BOZpB|a29M)sb*k$C_#D3%Wo5GYLoF4Z_lqF>?Sc1Cm2CKOZXrM5jyTB${e(eR{;CA3i(mD+%I5J3%;vH*fOM z%a@xa`6rfQYgsY-kSpEj+D}h1Gf^!Xi6Ka1>hZzN<)@0@zrQ76Jye^8FrUHS*VZ2H z>1jlvGw|&BrE8TF0cORtMB@CI6pd4jKaziuDWESt~yvXpQM`|AAj9If9djya}%MHEAcBNs}*8_1V)q38EEnwebf4|cx zb_`lF?xNkN&t(^fQqFrR9d$sVlTTZ1s(Dy^Q5juFnfRjn5YN+R z&-%bA*w3$xl(wT%Olg#`CZ~3mGwgwb>d=kTH-tqx6Q!z>W zIkd^jpoae?L(_G3u+!vM%}o3zLT$D4`l1ArM_OOr2mPy9U~=6)uota#cQ-LJtHytQMVEl!hWIX-K8-(75;U1H4;|$-z272M<=-)|EFrk30dQb- zpmrt6jOtK4L6G-@=S`U+x8TlgnueOxd?@lH|49l+ceEdHpOBDShZJ>cTHm|n+CP7% z>1`Wwgs%u4-jK6)!v4Nv|3temiHd?jR5>7kN&Yd5FhwaT*mUgzj&e(yZXLC*b`$>; zhH;LBM(2`cL^Ow3T22_+Ho|G8_=N^Ww0Sct>uP?JvF|s^^Y2XNd1B=!BN^9#o;bOoYCUL5CSAwpCJ;=a6^TpvwU3~O91&QP!>qEljugSmEG$qjW3tdFKU>r z^`G*vYRTE9&$0`!hxhz}m%SG?V(_!!d^yiouZ@JBAHjW5O+B0eIcoV^N zq`-7|yw>#c{HDE?NEy*3bPc)$k5ekB31d%&+ovl*UrRwHsq?g-k`jq@pKz)LPjikX zb(yzuEF-n(qXCYV(K$o$+fhf(4(az=3pXo8h75WKZ#s9{g=&%m|yoJ`>d0 zPelm+@bFO4v5U1Ob4&f3eV;M&z4TMU=Z*9uk_rdIZ{{^|&8;r;YF$ABAiBql<(?SLCv zW&QrGAEw?A&A%^|*j!XNEUJBm_U(|wu#Fjie@?6#9lq1mz;jUGP&SXUvg#valoU|y zvVN?z48wq4*Q)i z9EgJ7%A97^sdMLw!Dbe8G8a}TeO*1&jcE8=q-yPhJ#6NmvdK8;7gyL`w5s*md3jU3 z#(apCs05g5EOi;qp2$38kS<6sf|Rtl@g19TBr#Oc;80zY4m%}_7If8T?mu@A|9Cd^ z7rQDcV=_w-#Ufq&`lJ<+<2S1}lq40v!WtN&WrgAkwj2gxU7oh;K1hm^!sO`1oC@@h zj19V%oVc)>aGGmXGuOIRLeQ{8gm0w&(f*ddsN0O`(_aAu(lOt`;81tfW6iY%?R_F2 z!DIkxmrv)&dL9v=qUfxy67TH4b*sJPjm1F@-d|5N)%EzrYyOwxM{h2+Ka74R3Me;y-Cd!fX$k)w}pT7O3Pqx3fH@S)q{DNKxj}cJ#JngZb5a^O{2*y;+=A zF@#q`@4DY$RTUMvRAS!GuN=k(;ADW4n-Y&rZFWfS_mVun`byU_it7mz0s#6OP{u;p&RpC1l9vv*3X~Bb`^P&zO(hALxdDpqw6S^Rh z4#ie@Q<9X8*`7vC1bbS1JFQL7m%x05LN#jas1a7ZvF4`OWj6@jd2Cy?rm}27-Hdm~WW0rPZzn`}*usKDYHP@YMT$CiRXabs2@j zz*t-Hg|c(m?D|?kn1};VT7Ao|`GMXHz6ZA@?IG1oU!mAJ#)s&RpC-CsJeZFEjX804 z@Z{9}8Y2BAR@7?nMt`cGes5mC)(PtoI-Y-p_duQYC&~8gr46J+)=~ha4m$7hFVN1F z9`>UInHUpc8F6{*`(=|G>&$D82T&p>4`y=A@NrKUYtO{dE`u|XW0$wu%>L0{Uw`X| z-lk-gwPq1V+Q%=)n+>*uzdx=wSl244YPV}p_zEFGKHCvLD~eiu(* zq_hj&6M9U`Gohq0C0nNKkVE{Z7CQ4LbO-Tw&WiHg9ZMu&aF0F%38QVrUh)DZi(j&f_v&>ir<6wM<=%o{F;V3(-ES5@xtDn+xcm0VE4C%WOTtrKH*a%h z!CVF|THSi+g>xR}6DK~{@iHVsDkTgX)2VD9|CT3z37zVlcjbd-+>c#oXpr-~JVnz% zc&+#oOK?>=d|a<|Rx>neS+MWQIVFu3_4VV99~-Yhyi;X+w|mmZtOMm$yuI>|?E zlKEc$+`G38nbM?)k`^2+W@poEA$_|Mv{roa0q4!n?Hw4U_WZjjAIiEcud4pHP0EL_ zY9Ed+X&t#XYr8?`&L^HfIC`Ut2LYDUYJT7AHX6BbVV7B;uR<7kvI`w~c(k802R<#M zO8xpH2wo6@27;UO9Y8m^_yKsah@g1P2uA;kWS}w;vy9 z9({+_Ys|-vmM-#iBSx1qB4&;Ax+~-(k~j&17(Vn(qy?>!gyce^K+*dh2k| zOTIB|moM>VUaEGz*Aa3nGb~;tt~EkIBiiS#UC%VJ*PzIEN zqrQ&s_E?_LpVFZ9NtlLYv=c)!i0!P;$et#14#^SQWR!rj->wRJuMB96wbrRb#n{gbJW2+BC|`0n$xYSj4c zxf6&~M4OFL-52%s&9pQ}tDK`!^Y`xC_cjHV#Lx5lb@``wg@oc|3elAwOHHni+2w~u)li<)yzosl%&nB_&m1Ohm+Et5F2AhpNkvHO{!Sp`CW~f6EU;xW+14Sq` zyRaBk0`&mMe}%@5uW7DWafNfLf3+0R4v4e>-k~(<+?TCTe&_E`@f65A@z1ZtfatQc zxR-K&==Cm91cAH-tZw*q^E4mz2&rTVBthf&3U-$Ynt-D^psV0*=`@NX^_oi~N}I<} zl+kdGNN*K;R*)l`=5G<@oZd(h@d5eIXiY9%b z29;k9mRb*j@8NrzCm*$$>_2$Jw$g?pOylTNr(I!BOhLVg`#jxk>%o8Dzc)bvDs)tBP>TPs60?Z>kogFf$^Knak zZYzMkOvi@Y5|$2{9R!?@^`((}KEI--6Tbw=DV+s%2@t@B$$E9LGLh*cY5|$ni`H7Q zU;yWyziA{!K%(iX*`f4+#lHcH_$49f1o6&hkXXM!HPdN-rQ(D0XbM8_(?7!2N7`*JYm8>2zE?a_1FOI z7#Ac3>C*L;v&pIdoqJTH8V4l zDr_UzYt?*vdnHgUVR{Juogh`s-@R z0K+}6qyW-jPXq|`9o3<@ybb%&&xW_TM2f<)Nm@~p+UOn%cD*L+CtxvSyw&|v%33<` zlMNNNRlRn1E6T~qAqb^?bsfdE>7N)!(+&VcCWgnveDl#BTSu-V&1b}vuIeUfbw=`_ for more info on how topological mesh information is stored @@ -78,47 +78,92 @@ roughly as so not require a positive orientation of elements and that its reference traingle is different than specified in :mod:`modepy`. -(2) A ``MeshTopology`` which holds information about connectivity +(2) A :class:`firedrake.mesh.MeshTopology` + which holds information about connectivity and other topological properties, but nothing about geometry/coordinates etc. -(3) A ``FunctionSpace`` created from a ``FInAT`` element and a - ``MeshTopology`` which allows us to define functions - mapping the nodes (defined by the ``FInAT`` element) of - each element in the ``MeshTopology`` to some values - -(4) A ``CoordinatelessFunction`` (in the sense that its - *domain* has no coordinates) which is a function - in a ``FunctionSpace`` - -(5) A ``MeshGeometry`` created from a ``FunctionSpace`` - and a ``CoordinatelessFunction`` in that ``FunctionSpace`` +(3) A class :class:`firedrake.functionspaceimpl.FunctionSpace` + created from a :mod:`FInAT` element and a + :class:`firedrake.mesh.MeshTopology` which allows us to + define functions mapping the nodes (defined by the + :mod:`FInAT` element) of each element in the + :class:`firedrake.mesh.MeshTopology` to some values. + Note that the function :func:`firedrake.functionspace.FunctionSpace` + in the firedrake API is used to create objects of class + :class:`firedrake.functionspaceimpl.FunctionSpace` s + and :class:`firedrake.functionspaceimpl.WithGeometry` (see + (6)). + +(4) A :class:`firedrake.function.CoordinatelessFunction` + (in the sense that its *domain* has no coordinates) + which is a function in a + :class:`firedrake.functionspaceimpl.FunctionSpace` + +(5) A :class:`firedrake.mesh.MeshGeometry` created from a + :class:`firedrake.functionspaceimpl.FunctionSpace` + and a :class:`firedrake.function.CoordinatelessFunction` + in that :class:`firedrake.functionspaceimpl.FunctionSpace` which maps each dof to its geometric coordinates. -(6) A ``WithGeometry`` which is a ``FunctionSpace`` together - with a ``MeshGeometry``. This is the object returned +(6) A :class:`firedrake.functionspaceimpl.WithGeometry` which is a + :class:`firedrake.functionspaceimpl.FunctionSpace` together + with a :class:`firedrake.mesh.MeshGeometry`. + This is the object returned usually returned to the user by a call - to the :mod:`firedrake` function :func:`FunctionSpace`. + to the :mod:`firedrake` function + :func:`firedrake.functionspace.FunctionSpace`. -(7) A ``Function`` is defined on a ``WithGeometry`` +(7) A :class:`firedrake.function.Function` is defined on a + :class:`firedrake.functionspaceimpl.WithGeometry` Thus, by the coordinates of a mesh geometry we mean -(a) On the hidden back-end: a ``CoordinatelessFunction`` *f* on some function - space defined only on the mesh topology -(b) On the front-end: A ``Function`` with the values of *f* but defined - on a ``WithGeometry`` created from the ``FunctionSpace`` *f* lives in - and the ``MeshGeometry`` *f* defines. +(a) On the hidden back-end: a :class:`firedrake.function.CoordinatelessFunction` + *f* on some function space defined only on the mesh topology +(b) On the front-end: A :class:`firedrake.function.Function` + with the values of *f* but defined + on a :class:`firedrake.functionspaceimpl.WithGeometry` + created from the :class:`firedrake.functionspaceimpl.FunctionSpace` + *f* lives in and the :class:`firedrake.mesh.MeshGeometry` *f* defines. Basically, it's this picture (where a->b if b depends on a) .. warning:: - In general, the ``FunctionSpace`` of the coordinates function - of a ``WithGeometry`` may not be the same ``FunctionSpace`` - as for functions which live in the ``WithGeometry``. + In general, the :class:`firedrake.functionspaceimpl.FunctionSpace` + of the coordinates function + of a :class:`firedrake.functionspaceimpl.WithGeometry` may not be the same + :class:`firedrake.functionspaceimpl.FunctionSpace` + as for functions which live in the + :class:`firedrake.functionspaceimpl.WithGeometry`. This picture only shows how the class definitions depend on each other. -.. image:: images/firedrake_mesh_design.png +.. graphviz:: + + digraph{ + // created with graphviz2.38 dot + // NODES + + top [label="Topological\nMesh"]; + ref [label="Reference\nElement"]; + fspace [label="Function Space"]; + coordless [label="Coordinateless\nFunction"]; + geo [label="Geometric\nMesh"]; + withgeo [label="With\nGeometry"]; + + // EDGES + + top -> fspace; + ref -> fspace; + + fspace -> coordless; + + top -> geo; + coordless -> geo [label="Mesh\nCoordinates"]; + + fspace -> withgeo; + geo -> withgeo; + } -- GitLab From baa8a85d7266c01281d2af32409ca591c2fa79f1 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Tue, 30 Jun 2020 17:03:09 +0200 Subject: [PATCH 053/221] Apply 1 suggestion(s) to 1 file(s) --- doc/interop.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/interop.rst b/doc/interop.rst index 0f6b0003..0af206c5 100644 --- a/doc/interop.rst +++ b/doc/interop.rst @@ -1,7 +1,8 @@ -interop -======= +Interoperability with Other Discretization Packages +=================================================== -Interfacing with data outside of :mod:`meshmode`. +Functionality in this subpackage helps import and export data to/from other +pieces of software, typically PDE solvers. Firedrake -- GitLab From 88a5e2d3341c7aafe64efa7557b478ef448cd02e Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Tue, 30 Jun 2020 17:03:14 +0200 Subject: [PATCH 054/221] Apply 1 suggestion(s) to 1 file(s) --- doc/interop.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/interop.rst b/doc/interop.rst index 0af206c5..b07ac095 100644 --- a/doc/interop.rst +++ b/doc/interop.rst @@ -31,7 +31,7 @@ Converting between :mod:`firedrake` and :mod:`meshmode` is in general straightforward. Some language is different: * In a mesh, a :mod:`meshmode` "element" is a :mod:`firedrake` "cell" -* A :mod:`meshmode` :class:`Discretization` is a :mod:`firedrake` +* A :class:`meshmode.Discretization` is a :mod:`firedrake` :class:`WithGeometry`, usually created by calling the function :func:`firedrake.functionspace.FunctionSpace` and referred to as a "function space" -- GitLab From c5c7e6580228197e8991db4b91a9289593e18f0e Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Tue, 30 Jun 2020 17:03:22 +0200 Subject: [PATCH 055/221] Apply 1 suggestion(s) to 1 file(s) --- meshmode/interop/firedrake/connection.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index e2f202a0..a73ec5ef 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -380,3 +380,5 @@ class ToFiredrakeConnection: """ # }}} + +# vim: foldmethod=marker -- GitLab From c7e09c3286604a59bbc7aaf3fb9db04133a9f11b Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Tue, 30 Jun 2020 17:03:27 +0200 Subject: [PATCH 056/221] Apply 1 suggestion(s) to 1 file(s) --- meshmode/interop/firedrake/mesh.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 49e87408..1718fb4e 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -594,3 +594,5 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): return fd_mesh.Mesh(coords, reorder=False) # }}} + +# vim: foldmethod=marker -- GitLab From 50ad638fdff8d41ec5610d42b01d374d2e69dba7 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Tue, 30 Jun 2020 17:03:33 +0200 Subject: [PATCH 057/221] Apply 1 suggestion(s) to 1 file(s) --- test/test_firedrake_interop.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 7074b58e..a267938e 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -238,3 +238,5 @@ def test_vector_idempotency(ctx_factory, fdrake_mesh, check_idempotency(fdrake_connection, fdrake_unique) # }}} + +# vim: foldmethod=marker -- GitLab From df53a818cfd6dee725a228cffaad93c2b1a23aa1 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Tue, 30 Jun 2020 17:03:58 +0200 Subject: [PATCH 058/221] Apply 1 suggestion(s) to 1 file(s) --- meshmode/interop/firedrake/reference_cell.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meshmode/interop/firedrake/reference_cell.py b/meshmode/interop/firedrake/reference_cell.py index 153c5667..a102a939 100644 --- a/meshmode/interop/firedrake/reference_cell.py +++ b/meshmode/interop/firedrake/reference_cell.py @@ -136,3 +136,5 @@ def get_finat_element_unit_nodes(finat_element): return unit_nodes # }}} + +# vim: foldmethod=marker -- GitLab From d966b7c27a7fa3b76338c251cc692bcbe9454490 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 30 Jun 2020 10:10:23 -0500 Subject: [PATCH 059/221] spat_coord -> spatial_coord --- test/test_firedrake_interop.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 7074b58e..8bafdadf 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -118,12 +118,13 @@ def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): # {{{ Double check functions are being transported correctly -def alternating_sum_fd(spat_coord): +def alternating_sum_fd(spatial_coord): """ Return an expression x1 - x2 + x3 -+... """ return sum( - [(-1)**i * spat_coord for i, spat_coord in enumerate(spat_coord)] + [(-1)**i * spatial_coord + for i, spatial_coord in enumerate(spatial_coord)] ) @@ -142,8 +143,8 @@ def alternating_sum_mm(nodes): # This should show that projection to any coordinate in 1D/2D/3D # transfers correctly. test_functions = [ - (lambda spat_coord: Constant(1.0), lambda nodes: np.ones(nodes.shape[1])), - (lambda spat_coord: spat_coord[0], lambda nodes: nodes[0, :]), + (lambda spatial_coord: Constant(1.0), lambda nodes: np.ones(nodes.shape[1])), + (lambda spatial_coord: spatial_coord[0], lambda nodes: nodes[0, :]), (sum, lambda nodes: np.sum(nodes, axis=0)), (alternating_sum_fd, alternating_sum_mm) ] @@ -159,9 +160,9 @@ def test_function_transfer(ctx_factory, mesh """ fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fdrake_degree) - spat_coord = SpatialCoordinate(fdrake_mesh) + spatial_coord = SpatialCoordinate(fdrake_mesh) - fdrake_f = Function(fdrake_fspace).interpolate(fdrake_f_expr(spat_coord)) + fdrake_f = Function(fdrake_fspace).interpolate(fdrake_f_expr(spatial_coord)) cl_ctx = ctx_factory() fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) -- GitLab From 5da7244322151ff8d56ba31a9d0ef16dbfbafaf6 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 30 Jun 2020 10:46:46 -0500 Subject: [PATCH 060/221] Added some blob meshes --- test/test_firedrake_interop.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 3f37e4af..94174760 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -46,16 +46,29 @@ from firedrake import ( CLOSE_ATOL = 10**-12 -@pytest.fixture(params=[1, 2, 3], ids=["1D", "2D", "3D"]) +@pytest.fixture(params=["FiredrakeUnitIntervalMesh", + "FiredrakeUnitSquareMesh", + "FiredrakeUnitCubeMesh", + "annulus.msh", + "blob2d-order1-h4e-2.msh", + "blob2d-order1-h6e-2.msh", + "blob2d-order1-h8e-2.msh", + ]) def fdrake_mesh(request): - dim = request.param - if dim == 1: + mesh_name = request.param + if mesh_name == "FiredrakeUnitIntervalMesh": return UnitIntervalMesh(100) - if dim == 2: + if mesh_name == "FiredrakeUnitSquareMesh": return UnitSquareMesh(10, 10) - if dim == 3: + if mesh_name == "FiredrakeUnitCubeMesh": return UnitCubeMesh(5, 5, 5) - return None + + # Firedrake can't read in higher order meshes from gmsh, + # so we can only use the order1 blobs + from firedrake import Mesh + fd_mesh = Mesh(mesh_name) + fd_mesh.init() + return fd_mesh @pytest.fixture(params=["CG", "DG"]) -- GitLab From cfdca1d105269d3885c0317de9d3b201f7e81c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Wed, 1 Jul 2020 02:27:51 +0200 Subject: [PATCH 061/221] Firedrake CI: Do not install loopy over Firedrake loopy --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 19514568..6933ae1b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -75,7 +75,8 @@ Python 3 POCL Firedrake: - sudo apt update - sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev - source ~/firedrake/bin/activate - - pip install -r requirements.txt + - "grep -v loopy requirements.txt > myreq.txt" + - pip install -r myreq.txt - pip install pytest - pip install . - cd test -- GitLab From fcfb51104c09971d8fc329f6b665cbee9fca6a0e Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 2 Jul 2020 10:41:22 -0500 Subject: [PATCH 062/221] Made FiredrakeConnection class from node mapping to avoid code duplication --- meshmode/interop/firedrake/connection.py | 447 +++++++++++++++-------- test/test_firedrake_interop.py | 30 +- 2 files changed, 321 insertions(+), 156 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index a73ec5ef..6a11c70e 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -21,6 +21,8 @@ THE SOFTWARE. """ __doc__ = """ +.. autoclass:: FiredrakeConnection + :members: .. autoclass:: FromFiredrakeConnection :members: """ @@ -31,14 +33,16 @@ import six from modepy import resampling_matrix -from meshmode.interop.firedrake.mesh import import_firedrake_mesh +from meshmode.interop.firedrake.mesh import ( + import_firedrake_mesh, export_mesh_to_firedrake) from meshmode.interop.firedrake.reference_cell import ( get_affine_reference_simplex_mapping, get_finat_element_unit_nodes) from meshmode.mesh.processing import get_simplex_element_flip_matrix from meshmode.discretization.poly_element import \ - InterpolatoryQuadratureSimplexGroupFactory + InterpolatoryQuadratureSimplexGroupFactory, \ + InterpolatoryQuadratureSimplexElementGroup from meshmode.discretization import Discretization @@ -74,52 +78,109 @@ def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): flip_mat, nodes[orient < 0]) -# {{{ Create connection from firedrake into meshmode - +# {{{ Most basic connection between a fd function space and mm discretization -class FromFiredrakeConnection: +class FiredrakeConnection: """ - A connection created from a :mod:`firedrake` - ``"CG"`` or ``"DG"`` function space which creates a corresponding - meshmode discretization and allows - transfer of functions to and from :mod:`firedrake`. + A connection between one group of + a meshmode discretization and a firedrake "CG" or "DG" + function space. + + .. autoattribute:: discr + + A meshmode discretization - .. attribute:: to_discr + .. autoattribute:: group_nr - The discretization corresponding to the firedrake function - space created with a - :class:`InterpolatoryQuadratureSimplexElementGroup`. + The group number identifying which element group of + :attr:`discr` is being connected to a firedrake function space + + .. autoattribute:: mm2fd_node_mapping + + A numpy array of shape *(self.discr.groups[group_nr].nnodes,)* + whose *i*th entry is the :mod:`firedrake` node index associated + to the *i*th node in *self.discr.groups[group_nr]*. + It is important to note that, due to :mod:`meshmode` + and :mod:`firedrake` using different unit nodes, a :mod:`firedrake` + node associated to a :mod:`meshmode` may have different coordinates. + However, after resampling to the other system's unit nodes, + two associated nodes should have identical coordinates. """ - def __init__(self, cl_ctx, fdrake_fspace): + def __init__(self, discr, fdrake_fspace, mm2fd_node_mapping, group_nr=None): """ - :arg cl_ctx: A :mod:`pyopencl` computing context - :arg fdrake_fspace: A :mod:`firedrake` ``"CG"`` or ``"DG"`` - function space (of class :class:`WithGeometry`) built on - a mesh which is importable by :func:`import_firedrake_mesh`. + :param discr: A :mod:`meshmode` :class:`Discretization` + :param fdrake_fspace: A :mod:`firedrake` + :class:`firedrake.functionspaceimpl.WithGeometry`. + Must have ufl family ``'Lagrange'`` or + ``'Discontinuous Lagrange'``. + :param mm2fd_node_mapping: Used as attribute :attr:`mm2fd_node_mapping`. + A numpy integer array with the same dtype as + ``fdrake_fspace.cell_node_list.dtype`` + :param group_nr: The index of the group in *discr* which is + being connected to *fdrake_fspace*. The group must be + a :class:`InterpolatoryQuadratureSimplexElementGroup` + of the same topological dimension as *fdrake_fspace*. + If *discr* has only one group, *group_nr=None* may be supplied. + + :raises TypeError: If any input arguments are of the wrong type, + if the designated group is of the wrong type, + or if *fdrake_fspace* is of the wrong family. + :raises ValueError: If: + * *mm2fd_node_mapping* is of the wrong shape + or dtype, if *group_nr* is an invalid index + * If *group_nr* is *None* when *discr* has more than one group. """ - # Ensure fdrake_fspace is a function space with appropriate reference - # element. + # {{{ Validate input + if not isinstance(discr, Discretization): + raise TypeError(":param:`discr` must be of type " + ":class:`meshmode.discretization.Discretization`, " + "not :class:`%s`." % type(discr)) from firedrake.functionspaceimpl import WithGeometry if not isinstance(fdrake_fspace, WithGeometry): - raise TypeError(":arg:`fdrake_fspace` must be of firedrake type " - ":class:`WithGeometry`, not `%s`." - % type(fdrake_fspace)) - ufl_elt = fdrake_fspace.ufl_element() - - if ufl_elt.family() not in ('Lagrange', 'Discontinuous Lagrange'): - raise ValueError("the ``ufl_element().family()`` of " - ":arg:`fdrake_fspace` must " - "be ``'Lagrange'`` or " - "``'Discontinuous Lagrange'``, not %s." - % ufl_elt.family()) - - # Create to_discr - mm_mesh, orient = import_firedrake_mesh(fdrake_fspace.mesh()) - factory = InterpolatoryQuadratureSimplexGroupFactory(ufl_elt.degree()) - self.to_discr = Discretization(cl_ctx, mm_mesh, factory) + raise TypeError(":param:`fdrake_fspace` must be of type " + ":class:`firedrake.functionspaceimpl.WithGeometry`, " + "not :class:`%s`." % type(fdrake_fspace)) + if not isinstance(mm2fd_node_mapping, np.ndarray): + raise TypeError(":param:`mm2fd_node_mapping` must be of type " + ":class:`np.ndarray`, " + "not :class:`%s`." % type(mm2fd_node_mapping)) + if not isinstance(group_nr, int) and group_nr is not None: + raise TypeError(":param:`group_nr` must be of type *int* or be " + "*None*, not of type %s." % type(group_nr)) + # Convert group_nr to an integer if *None* + if group_nr is None: + if len(discr.groups) != 1: + raise ValueError(":param:`group_nr` is *None* but :param:`discr` " + "has %s != 1 groups." % len(discr.groups)) + group_nr = 0 + if group_nr < 0 or group_nr >= len(discr.groups): + raise ValueError(":param:`group_nr` has value %s, which an invalid " + "index into list *discr.groups* of length %s." + % (group_nr, len(discr.gropus))) + if not isinstance(discr.groups[group_nr], + InterpolatoryQuadratureSimplexElementGroup): + raise TypeError("*discr.groups[group_nr]* must be of type " + ":class:`InterpolatoryQuadratureSimplexElementGroup`" + ", not :class:`%s`." % type(discr.groups[group_nr])) + allowed_families = ('Discontinuous Lagrange', 'Lagrange') + if fdrake_fspace.ufl_element().family() not in allowed_families: + raise TypeError(":param:`fdrake_fspace` must have ufl family " + "be one of %s, not %s." + % (allowed_families, + fdrake_fspace.ufl_element().family())) + if mm2fd_node_mapping.shape != (discr.groups[group_nr].nnodes,): + raise ValueError(":param:`mm2fd_node_mapping` must be of shape ", + "(%s,), not %s" + % ((discr.groups[group_nr].nnodes,), + mm2fd_node_mapping.shape)) + if mm2fd_node_mapping.dtype != fdrake_fspace.cell_node_list.dtype: + raise ValueError(":param:`mm2fd_node_mapping` must have dtype " + "%s, not %s" % (fdrake_fspace.cell_node_list.dtype, + mm2fd_node_mapping.dtype)) + # }}} # Get meshmode unit nodes - element_grp = self.to_discr.groups[0] + element_grp = discr.groups[group_nr] mm_unit_nodes = element_grp.unit_nodes # get firedrake unit nodes and map onto meshmode reference element dim = fdrake_fspace.mesh().topological_dimension() @@ -127,7 +188,7 @@ class FromFiredrakeConnection: fd_unit_nodes = get_finat_element_unit_nodes(fdrake_fspace.finat_element) fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) - # compute resampling matrices + # compute and store resampling matrices self._resampling_mat_fd2mm = resampling_matrix(element_grp.basis(), new_nodes=mm_unit_nodes, old_nodes=fd_unit_nodes) @@ -135,101 +196,98 @@ class FromFiredrakeConnection: new_nodes=fd_unit_nodes, old_nodes=mm_unit_nodes) - # Flipping negative elements corresponds to reordering the nodes. - # We handle reordering by storing the permutation explicitly as - # a numpy array - - # Get the reordering fd->mm. - # - # One should note there is something a bit more subtle going on - # in the continuous case. All meshmode discretizations use - # are discontinuous, so nodes are associated with elements(cells) - # not vertices. In a continuous firedrake space, some nodes - # are shared between multiple cells. In particular, while the - # below "reordering" is indeed a permutation if the firedrake space - # is discontinuous, if the firedrake space is continuous then - # some firedrake nodes correspond to nodes on multiple meshmode - # elements, i.e. those nodes appear multiple times - # in the "reordering" array - flip_mat = get_simplex_element_flip_matrix(ufl_elt.degree(), - fd_unit_nodes) - fd_cell_node_list = fdrake_fspace.cell_node_list - _reorder_nodes(orient, fd_cell_node_list, flip_mat, unflip=False) - self._reordering_arr_fd2mm = fd_cell_node_list.flatten() - - # Now handle the possibility of duplicate nodes - unique_fd_nodes, counts = np.unique(self._reordering_arr_fd2mm, + # Now handle the possibility of multiple meshmode nodes being associated + # to the same firedrake node + unique_fd_nodes, counts = np.unique(mm2fd_node_mapping, return_counts=True) # self._duplicate_nodes # maps firedrake nodes associated to more than 1 meshmode node # to all associated meshmode nodes. self._duplicate_nodes = {} - if ufl_elt.family() == 'Discontinuous Lagrange': - assert np.all(counts == 1), \ - "This error should never happen, some nodes in a firedrake " \ - "discontinuous space were duplicated. Contact the developer " - else: - dup_fd_nodes = set(unique_fd_nodes[counts > 1]) - for mm_inode, fd_inode in enumerate(self._reordering_arr_fd2mm): - if fd_inode in dup_fd_nodes: - self._duplicate_nodes.setdefault(fd_inode, []) - self._duplicate_nodes[fd_inode].append(mm_inode) - - # Store things that we need for *from_fspace* - self._ufl_element = ufl_elt + dup_fd_nodes = set(unique_fd_nodes[counts > 1]) + for mm_inode, fd_inode in enumerate(mm2fd_node_mapping): + if fd_inode in dup_fd_nodes: + self._duplicate_nodes.setdefault(fd_inode, []) + self._duplicate_nodes[fd_inode].append(mm_inode) + + # Store input + self.discr = discr + self.group_nr = group_nr + self.mm2fd_node_mapping = mm2fd_node_mapping self._mesh_geometry = fdrake_fspace.mesh() - self._fspace_cache = {} # map vector dim -> firedrake fspace + self._ufl_element = fdrake_fspace.ufl_element() + # Cache firedrake function spaces of each vector dimension to + # avoid overhead. Firedrake takes care of avoiding memory + # duplication. + self._fspace_cache = {} - def from_fspace(self, dim=None): + def firedrake_fspace(self, vdim=None): """ - Return a firedrake function space of the appropriate vector dimension - - :arg dim: Either *None*, in which case a function space which maps - to scalar values is returned, or a positive integer *n*, - in which case a function space which maps into *\\R^n* - is returned + Return a firedrake function space that + *self.discr.groups[self.group_nr]* is connected to + of the appropriate vector dimension + + :arg vdim: Either *None*, in which case a function space which maps + to scalar values is returned, a positive integer *n*, + in which case a function space which maps into *\\R^n* + is returned, or a tuple of integers defining + the shape of values in a tensor function space, + in which case a tensor function space is returned :return: A :mod:`firedrake` :class:`WithGeometry` which corresponds to - :attr:`to_discr` of the appropriate vector dimension + *self.discr.groups[self.group_nr]* of the appropriate vector + dimension + + :raises TypeError: If *vdim* is of the wrong type """ # Cache the function spaces created to avoid high overhead. # Note that firedrake is smart about re-using shared information, # so this is not duplicating mesh/reference element information - if dim not in self._fspace_cache: - assert (isinstance(dim, int) and dim > 0) or dim is None - if dim is None: + if vdim not in self._fspace_cache: + if vdim is None: from firedrake import FunctionSpace - self._fspace_cache[dim] = \ + self._fspace_cache[vdim] = \ FunctionSpace(self._mesh_geometry, self._ufl_element.family(), degree=self._ufl_element.degree()) - else: + elif isinstance(vdim, int): from firedrake import VectorFunctionSpace - self._fspace_cache[dim] = \ + self._fspace_cache[vdim] = \ VectorFunctionSpace(self._mesh_geometry, self._ufl_element.family(), degree=self._ufl_element.degree(), - dim=dim) - return self._fspace_cache[dim] + dim=vdim) + elif isinstance(vdim, tuple): + from firedrake import TensorFunctionSpace + self._fspace_cache[vdim] = \ + TensorFunctionSpace(self._mesh_geometry, + self._ufl_element.family(), + degree=self._ufl_element.degree(), + shape=vdim) + else: + raise TypeError(":param:`vdim` must be *None*, an integer, " + " or a tuple of integers, not of type %s." + % type(vdim)) + return self._fspace_cache[vdim] def from_firedrake(self, function, out=None): """ - transport firedrake function onto :attr:`to_discr` + transport firedrake function onto :attr:`discr` :arg function: A :mod:`firedrake` function to transfer onto - :attr:`to_discr`. Its function space must have + :attr:`discr`. Its function space must have the same family, degree, and mesh as ``self.from_fspace()``. :arg out: If *None* then ignored, otherwise a numpy array of the - shape *function.dat.data.shape.T* (i.e. - *(dim, nnodes)* or *(nnodes,)* in which *function*'s - transported data is stored. + shape (i.e. + *(..., num meshmode nodes)* or *(num meshmode nodes,)* and of the + same dtype in which *function*'s transported data will be stored :return: a numpy array holding the transported function """ - # make sure function is a firedrake function in an appropriate - # function space + # Check function is convertible from firedrake.function import Function - assert isinstance(function, Function), \ - ":arg:`function` must be a :mod:`firedrake` Function" + if not isinstance(function, Function): + raise TypeError(":arg:`function` must be a :mod:`firedrake` " + "Function") assert function.function_space().ufl_element().family() \ == self._ufl_element.family() and \ function.function_space().ufl_element().degree() \ @@ -240,22 +298,28 @@ class FromFiredrakeConnection: ":arg:`function` mesh must be the same mesh as used by " \ "``self.from_fspace().mesh()``" - # Get function data as shape [nnodes][dims] or [nnodes] + # Get function data as shape *(nnodes, ...)* or *(nnodes,)* function_data = function.dat.data - if out is None: - shape = (self.to_discr.nnodes,) - if len(function_data.shape) > 1: - shape = (function_data.shape[1],) + shape - out = np.ndarray(shape, dtype=function_data.dtype) - # Reorder nodes - if len(out.shape) > 1: - out[:] = function_data.T[:, self._reordering_arr_fd2mm] + # Check that out is supplied correctly, or create out if it is + # not supplied + shape = (self.discr.groups[self.group_nr].nnodes,) + if len(function_data.shape) > 1: + shape = function_data.shape[1:] + shape + if out is not None: + if not isinstance(out, np.ndarray): + raise TypeError(":param:`out` must of type *np.ndarray* or " + "be *None*") + assert out.shape == shape, \ + ":param:`out` must have shape %s." % shape + assert out.dtype == function.dat.data.dtype else: - out[:] = function_data[self._reordering_arr_fd2mm] + out = np.ndarray(shape, dtype=function_data.dtype) + # Reorder nodes + out[:] = np.moveaxis(function_data, 0, -1)[..., self.mm2fd_node_mapping] # Resample at the appropriate nodes - out_view = self.to_discr.groups[0].view(out) + out_view = self.discr.groups[self.group_nr].view(out) np.matmul(out_view, self._resampling_mat_fd2mm.T, out=out_view) return out @@ -263,20 +327,20 @@ class FromFiredrakeConnection: assert_fdrake_discontinuous=True, continuity_tolerance=None): """ - transport meshmode field from :attr:`to_discr` into an + transport meshmode field from :attr:`discr` into an appropriate firedrake function space. - :arg mm_field: A numpy array of shape *(nnodes,)* or *(dim, nnodes)* + :arg mm_field: A numpy array of shape *(nnodes,)* or *(..., nnodes)* representing a function on :attr:`to_distr`. :arg out: If *None* then ignored, otherwise a :mod:`firedrake` function of the right function space for the transported data to be stored in. :arg assert_fdrake_discontinuous: If *True*, disallows conversion to a continuous firedrake function space - (i.e. this function checks that ``self.from_fspace()`` is + (i.e. this function checks that ``self.firedrake_fspace()`` is discontinuous and raises a *ValueError* otherwise) :arg continuity_tolerance: If converting to a continuous firedrake - function space (i.e. if ``self.from_fspace()`` is continuous), + function space (i.e. if ``self.firedrake_fspace()`` is continuous), assert that at any two meshmode nodes corresponding to the same firedrake node (meshmode is a discontinuous space, so this situation will almost certainly happen), the function being transported @@ -302,16 +366,18 @@ class FromFiredrakeConnection: out.function_space().ufl_element().degree() \ == self._ufl_element.degree(), \ ":arg:`out` must live in a function space with the " \ - "same family and degree as ``self.from_fspace()``" + "same family and degree as ``self.firedrake_fspace()``" assert out.function_space().mesh() is self._mesh_geometry, \ ":arg:`out` mesh must be the same mesh as used by " \ - "``self.from_fspace().mesh()`` or *None*" + "``self.firedrake_fspace().mesh()`` or *None*" else: if len(mm_field.shape) == 1: - dim = None + vdim = None + elif len(mm_field.shape) == 2: + vdim = mm_field.shape[0] else: - dim = mm_field.shape[0] - out = Function(self.from_fspace(dim)) + vdim = mm_field.shape[:-1] + out = Function(self.firedrake_fspace(vdim)) # Handle 1-D case if len(out.dat.data.shape) == 1 and len(mm_field.shape) > 1: @@ -320,17 +386,21 @@ class FromFiredrakeConnection: # resample from nodes on reordered view. Have to do this in # a bit of a roundabout way to be careful about duplicated # firedrake nodes. - by_cell_field_view = self.to_discr.groups[0].view(mm_field) - if len(out.dat.data.shape) == 1: - reordered_outdata = out.dat.data[self._reordering_arr_fd2mm] - else: - reordered_outdata = out.dat.data.T[:, self._reordering_arr_fd2mm] - by_cell_reordered_view = self.to_discr.groups[0].view(reordered_outdata) + el_group = self.discr.groups[self.group_nr] + by_cell_field_view = el_group.view(mm_field) + + # Get firedrake data from out into meshmode ordering and view by cell + reordered_outdata = \ + np.moveaxis(out.dat.data, 0, -1)[..., self.mm2fd_node_mapping] + by_cell_reordered_view = el_group.view(reordered_outdata) + # Resample this reordered data np.matmul(by_cell_field_view, self._resampling_mat_mm2fd.T, out=by_cell_reordered_view) - out.dat.data[self._reordering_arr_fd2mm] = reordered_outdata.T + # Now store the resampled data back in the firedrake order + out.dat.data[self.mm2fd_node_mapping] = \ + np.moveaxis(reordered_outdata, -1, 0) - # Continuity checks + # Continuity checks if requested if self._ufl_element.family() == 'Lagrange' \ and continuity_tolerance is not None: assert isinstance(continuity_tolerance, float) @@ -346,12 +416,8 @@ class FromFiredrakeConnection: # nodes may have been resampled to distinct nodes on different # elements. reordered_outdata has undone that resampling. for dup_mm_inode in duplicated_mm_nodes[1:]: - if len(reordered_outdata.shape) > 1: - dist = la.norm(reordered_outdata[:, mm_inode] - - reordered_outdata[:, dup_mm_inode]) - else: - dist = la.norm(reordered_outdata[mm_inode] - - reordered_outdata[dup_mm_inode]) + dist = la.norm(reordered_outdata[..., mm_inode] + - reordered_outdata[..., dup_mm_inode]) if dist >= continuity_tolerance: raise ValueError("Meshmode nodes %s and %s represent " "the same firedrake node %s, but " @@ -365,19 +431,114 @@ class FromFiredrakeConnection: # }}} +# {{{ Create connection from firedrake into meshmode + +class FromFiredrakeConnection(FiredrakeConnection): + """ + A connection created from a :mod:`firedrake` + ``"CG"`` or ``"DG"`` function space which creates a corresponding + meshmode discretization and allows + transfer of functions to and from :mod:`firedrake`. + """ + def __init__(self, cl_ctx, fdrake_fspace): + """ + :arg cl_ctx: A :mod:`pyopencl` computing context + :arg fdrake_fspace: A :mod:`firedrake` ``"CG"`` or ``"DG"`` + function space (of class :class:`WithGeometry`) built on + a mesh which is importable by :func:`import_firedrake_mesh`. + """ + # Ensure fdrake_fspace is a function space with appropriate reference + # element. + from firedrake.functionspaceimpl import WithGeometry + if not isinstance(fdrake_fspace, WithGeometry): + raise TypeError(":arg:`fdrake_fspace` must be of firedrake type " + ":class:`WithGeometry`, not `%s`." + % type(fdrake_fspace)) + ufl_elt = fdrake_fspace.ufl_element() + + if ufl_elt.family() not in ('Lagrange', 'Discontinuous Lagrange'): + raise ValueError("the ``ufl_element().family()`` of " + ":arg:`fdrake_fspace` must " + "be ``'Lagrange'`` or " + "``'Discontinuous Lagrange'``, not %s." + % ufl_elt.family()) + + # Create to_discr + mm_mesh, orient = import_firedrake_mesh(fdrake_fspace.mesh()) + factory = InterpolatoryQuadratureSimplexGroupFactory(ufl_elt.degree()) + to_discr = Discretization(cl_ctx, mm_mesh, factory) + + # get firedrake unit nodes and map onto meshmode reference element + group = to_discr.groups[0] + fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(group.dim, + True) + fd_unit_nodes = get_finat_element_unit_nodes(fdrake_fspace.finat_element) + fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) + # Flipping negative elements corresponds to reordering the nodes. + # We handle reordering by storing the permutation explicitly as + # a numpy array + + # Get the reordering fd->mm. + # + # One should note there is something a bit more subtle going on + # in the continuous case. All meshmode discretizations use + # are discontinuous, so nodes are associated with elements(cells) + # not vertices. In a continuous firedrake space, some nodes + # are shared between multiple cells. In particular, while the + # below "reordering" is indeed a permutation if the firedrake space + # is discontinuous, if the firedrake space is continuous then + # some firedrake nodes correspond to nodes on multiple meshmode + # elements, i.e. those nodes appear multiple times + # in the "reordering" array + flip_mat = get_simplex_element_flip_matrix(ufl_elt.degree(), + fd_unit_nodes) + fd_cell_node_list = fdrake_fspace.cell_node_list + _reorder_nodes(orient, fd_cell_node_list, flip_mat, unflip=False) + mm2fd_node_mapping = fd_cell_node_list.flatten() + + super(FromFiredrakeConnection, self).__init__(to_discr, + fdrake_fspace, + mm2fd_node_mapping) + if fdrake_fspace.ufl_element().family() == 'Discontinuous Lagrange': + assert len(self._duplicate_nodes) == 0, \ + "Somehow a firedrake node in a 'DG' space got duplicated..." \ + "contact the developer." + +# }}} + + # {{{ Create connection to firedrake from meshmode -# TODO : implement this (should be easy using export_mesh_to_firedrake -# and similar styles) -class ToFiredrakeConnection: - def __init__(self, discr, group_nr=None): +class ToFiredrakeConnection(FiredrakeConnection): + """ + Create a connection from a firedrake discretization + into firedrake. Create a corresponding "DG" function + space and allow for conversion back and forth + by resampling at the nodes. + """ + def __init__(self, discr, group_nr=None, comm=None): """ - Create a connection from a firedrake discretization - into firedrake. Create a corresponding "DG" function - space and allow for conversion back and forth - by resampling at the nodes. + :param discr: A :class:`Discretization` to intialize the connection with + :param group_nr: The group number of the discretization to convert. + If *None* there must be only one group. The selected group + must be of type :class:`InterpolatoryQuadratureSimplexElementGroup` + :param comm: Communicator to build a dmplex object on for the created + firedrake mesh """ + if group_nr is None: + assert len(discr.groups) == 1, ":arg:`group_nr` is *None*, but " \ + ":arg:`discr` has %s != 1 groups." % len(discr.groups) + group_nr = 0 + el_group = discr.groups[group_nr] + + from firedrake.functionspace import FunctionSpace + fd_mesh = export_mesh_to_firedrake(discr.mesh, group_nr, comm) + fspace = FunctionSpace(fd_mesh, 'DG', el_group.order) + super(ToFiredrakeConnection, self).__init__(discr, + fspace, + np.arange(el_group.nnodes), + group_nr=group_nr) # }}} diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 94174760..b3740f24 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -81,6 +81,9 @@ def fdrake_degree(request): return request.param +# TODO : make some tests to verify boundary tagging + + # {{{ Basic conversion checks for the function space def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): @@ -99,17 +102,17 @@ def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): fdrake_fspace = FunctionSpace(fdrake_mesh, 'DG', fdrake_degree) cl_ctx = ctx_factory() fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) - to_discr = fdrake_connection.to_discr - meshmode_verts = to_discr.mesh.vertices + discr = fdrake_connection.discr + meshmode_verts = discr.mesh.vertices # Ensure the meshmode mesh has one group and make sure both # meshes agree on some basic properties - assert len(to_discr.mesh.groups) == 1 + assert len(discr.mesh.groups) == 1 fdrake_mesh_fspace = fdrake_mesh.coordinates.function_space() fdrake_mesh_order = fdrake_mesh_fspace.finat_element.degree - assert to_discr.mesh.groups[0].order == fdrake_mesh_order - assert to_discr.mesh.groups[0].nelements == fdrake_mesh.num_cells() - assert to_discr.mesh.nvertices == fdrake_mesh.num_vertices() + assert discr.mesh.groups[0].order == fdrake_mesh_order + assert discr.mesh.groups[0].nelements == fdrake_mesh.num_cells() + assert discr.mesh.nvertices == fdrake_mesh.num_vertices() # Ensure that the vertex sets are identical up to reordering # Nb: I got help on this from stack overflow: @@ -121,10 +124,11 @@ def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): # Ensure the discretization and the firedrake function space agree on # some basic properties finat_elt = fdrake_fspace.finat_element - assert len(to_discr.groups) == 1 - assert to_discr.groups[0].order == finat_elt.degree - assert to_discr.groups[0].nunit_nodes == finat_elt.space_dimension() - assert to_discr.nnodes == fdrake_fspace.node_count + assert len(discr.groups) == 1 + assert discr.groups[0].order == finat_elt.degree + assert discr.groups[0].nunit_nodes == finat_elt.space_dimension() + assert discr.nnodes == fdrake_fspace.node_count + # }}} @@ -181,9 +185,9 @@ def test_function_transfer(ctx_factory, fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) transported_f = fdrake_connection.from_firedrake(fdrake_f) - to_discr = fdrake_connection.to_discr + discr = fdrake_connection.discr with cl.CommandQueue(cl_ctx) as queue: - nodes = to_discr.nodes().get(queue=queue) + nodes = discr.nodes().get(queue=queue) meshmode_f = meshmode_f_eval(nodes) np.testing.assert_allclose(transported_f, meshmode_f, atol=CLOSE_ATOL) @@ -200,7 +204,7 @@ def check_idempotency(fdrake_connection, fdrake_function): vdim = None if len(fdrake_function.dat.data.shape) > 1: vdim = fdrake_function.dat.data.shape[1] - fdrake_fspace = fdrake_connection.from_fspace(dim=vdim) + fdrake_fspace = fdrake_connection.firedrake_fspace(vdim=vdim) # Test for idempotency fd->mm->fd mm_field = fdrake_connection.from_firedrake(fdrake_function) -- GitLab From 0d9fcb2c4896d59e1d6e87235274b93f25447436 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 2 Jul 2020 10:41:45 -0500 Subject: [PATCH 063/221] _plex -> _topology_dm --- meshmode/interop/firedrake/mesh.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 1718fb4e..d42265d2 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -65,15 +65,15 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology): # If you don't understand dmplex, look at the PETSc reference # here: https://cse.buffalo.edu/~knepley/classes/caam519/CSBook.pdf # used to get topology info - # TODO... not sure how to get around the private access - plex = top._plex + # FIXME... not sure how to get around the private access + dm = top._topology_dm # Get range of dmplex ids for cells, facets, and vertices - c_start, c_end = plex.getHeightStratum(0) - f_start, f_end = plex.getHeightStratum(1) - v_start, v_end = plex.getDepthStratum(0) + c_start, c_end = dm.getHeightStratum(0) + f_start, f_end = dm.getHeightStratum(1) + v_start, v_end = dm.getDepthStratum(0) - # TODO... not sure how to get around the private accesses + # FIXME... not sure how to get around the private accesses # Maps dmplex cell id -> firedrake cell index def cell_id_dmp_to_fd(ndx): return top._cell_numbering.getOffset(ndx) @@ -527,6 +527,7 @@ def import_firedrake_mesh(fdrake_mesh, normals=None, no_normals_warn=None): # {{{ Mesh exporting to firedrake +# TODO : Keep boundary tagging def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): """ Create a firedrake mesh corresponding to one :class:`Mesh`'s @@ -567,12 +568,18 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): import firedrake.mesh as fd_mesh plex = fd_mesh._from_cell_list(group.dim, cells, coords, comm) - topology = fd_mesh.Mesh(plex, dim=mesh.ambient_dim, reorder=False) + # TODO : Allow reordering? This makes the connection slower + # but (in principle) firedrake is reordering nodes + # to speed up cache access for their computations. + # Users might want to be able to choose whether + # or not to reorder based on if caching/conversion + # is bottle-necking + topology = fd_mesh.Mesh(plex, reorder=False) # Now make a coordinates function from firedrake import VectorFunctionSpace, Function coords_fspace = VectorFunctionSpace(topology, 'CG', group.order, - dim=mesh.ambient_dim) + dim=mesh.ambient_dim) coords = Function(coords_fspace) # get firedrake unit nodes and map onto meshmode reference element -- GitLab From ce192ab4a498c2678d19b677b7a4b06d90becca0 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 2 Jul 2020 10:52:01 -0500 Subject: [PATCH 064/221] Some doc updates --- meshmode/interop/firedrake/connection.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 6a11c70e..1f32848c 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -25,6 +25,8 @@ __doc__ = """ :members: .. autoclass:: FromFiredrakeConnection :members: +.. autoclass:: ToFiredrakeConnection + :members: """ import numpy as np @@ -86,6 +88,9 @@ class FiredrakeConnection: a meshmode discretization and a firedrake "CG" or "DG" function space. + Users should instantiate this using a + :class:`FromFiredrakeConnection` or :class:`ToFiredrakeConnection`. + .. autoattribute:: discr A meshmode discretization @@ -105,6 +110,8 @@ class FiredrakeConnection: node associated to a :mod:`meshmode` may have different coordinates. However, after resampling to the other system's unit nodes, two associated nodes should have identical coordinates. + + .. automethod:: __init__ """ def __init__(self, discr, fdrake_fspace, mm2fd_node_mapping, group_nr=None): """ @@ -125,10 +132,10 @@ class FiredrakeConnection: :raises TypeError: If any input arguments are of the wrong type, if the designated group is of the wrong type, or if *fdrake_fspace* is of the wrong family. - :raises ValueError: If: - * *mm2fd_node_mapping* is of the wrong shape - or dtype, if *group_nr* is an invalid index - * If *group_nr* is *None* when *discr* has more than one group. + :raises ValueError: If + *mm2fd_node_mapping* is of the wrong shape + or dtype, if *group_nr* is an invalid index, or + if *group_nr* is *None* when *discr* has more than one group. """ # {{{ Validate input if not isinstance(discr, Discretization): @@ -516,6 +523,8 @@ class ToFiredrakeConnection(FiredrakeConnection): into firedrake. Create a corresponding "DG" function space and allow for conversion back and forth by resampling at the nodes. + + .. automethod:: __init__ """ def __init__(self, discr, group_nr=None, comm=None): """ -- GitLab From 9d8786edca6b0b94df992383ea81841889bbe383 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 2 Jul 2020 13:46:38 -0500 Subject: [PATCH 065/221] First stab at a cells_to_use argument (for later use in FromBdy connection) --- meshmode/interop/firedrake/connection.py | 6 + meshmode/interop/firedrake/mesh.py | 197 +++++++++++++++++------ 2 files changed, 154 insertions(+), 49 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 1f32848c..2ef06da0 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -337,6 +337,11 @@ class FiredrakeConnection: transport meshmode field from :attr:`discr` into an appropriate firedrake function space. + If *out* is *None*, values at any firedrake + nodes associated to NO meshmode nodes are zeroed out. + If *out* is supplied, values at nodes associated to NO meshmode nodes + are not modified. + :arg mm_field: A numpy array of shape *(nnodes,)* or *(..., nnodes)* representing a function on :attr:`to_distr`. :arg out: If *None* then ignored, otherwise a :mod:`firedrake` @@ -385,6 +390,7 @@ class FiredrakeConnection: else: vdim = mm_field.shape[:-1] out = Function(self.firedrake_fspace(vdim)) + out.dat.data[:] = 0.0 # Handle 1-D case if len(out.dat.data.shape) == 1 and len(mm_field.shape) > 1: diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index d42265d2..89576497 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -30,8 +30,8 @@ import six from modepy import resampling_matrix -from meshmode.mesh import (BTAG_ALL, BTAG_REALLY_ALL, FacialAdjacencyGroup, - Mesh, NodalAdjacency, SimplexElementGroup) +from meshmode.mesh import (BTAG_ALL, BTAG_REALLY_ALL, BTAG_NO_BOUNDARY, + FacialAdjacencyGroup, Mesh, NodalAdjacency, SimplexElementGroup) from meshmode.interop.firedrake.reference_cell import ( get_affine_reference_simplex_mapping, get_finat_element_unit_nodes) @@ -39,7 +39,7 @@ from meshmode.interop.firedrake.reference_cell import ( # {{{ functions to extract information from Mesh Topology -def _get_firedrake_nodal_info(fdrake_mesh_topology): +def _get_firedrake_nodal_info(fdrake_mesh_topology, cells_to_use=None): """ Get nodal adjacency and vertex indices corresponding to a firedrake mesh topology. Note that as we do not use @@ -53,12 +53,22 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology): :arg fdrake_mesh_topology: A :mod:`firedrake` instance of class :class:`MeshTopology` or :class:`MeshGeometry`. + :arg cells_to_use: Ignored if *None*. Otherwise, assumed to be + a numpy array holding the firedrake cell indices to use. + Any cells not on this array are ignored. + Cells used are assumed to appear exactly once in the array. + The cell's element index in the nodal adjacency will be its + index in *cells_to_use*. + :return: Returns *vertex_indices* as a numpy array of shape *(nelements, ref_element.nvertices)* (as described by the ``vertex_indices`` attribute of a :class:`MeshElementGroup`) and a :class:`NodalAdjacency` constructed from *fdrake_mesh_topology* as a tuple *(vertex_indices, nodal_adjacency)*. + + Even if *cells_to_use* is not *None*, the vertex indices + are still the global firedrake vertex indices. """ top = fdrake_mesh_topology.topology @@ -66,26 +76,27 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology): # here: https://cse.buffalo.edu/~knepley/classes/caam519/CSBook.pdf # used to get topology info # FIXME... not sure how to get around the private access - dm = top._topology_dm + top_dm = top._topology_dm # Get range of dmplex ids for cells, facets, and vertices - c_start, c_end = dm.getHeightStratum(0) - f_start, f_end = dm.getHeightStratum(1) - v_start, v_end = dm.getDepthStratum(0) + f_start, f_end = top_dm.getHeightStratum(1) + v_start, v_end = top_dm.getDepthStratum(0) # FIXME... not sure how to get around the private accesses - # Maps dmplex cell id -> firedrake cell index - def cell_id_dmp_to_fd(ndx): - return top._cell_numbering.getOffset(ndx) - # Maps dmplex vert id -> firedrake vert index - def vert_id_dmp_to_fd(ndx): - return top._vertex_numbering.getOffset(ndx) + vert_id_dmp_to_fd = top._vertex_numbering.getOffset # We will fill in the values as we go - vertex_indices = -np.ones((top.num_cells(), top.ufl_cell().num_vertices()), + if cells_to_use is None: + num_cells = top.num_cells() + else: + num_cells = np.size(cells_to_use) + vertex_indices = -np.ones((num_cells, top.ufl_cell().num_vertices()), dtype=np.int32) - # This will map fd cell ndx -> list of fd cell indices which share a vertex + # This will map fd cell ndx (or its new index as dictated by + # *cells_to_use* if *cells_to_use* + # is not *None*) + # -> list of fd cell indices which share a vertex cell_to_nodal_neighbors = {} # This will map dmplex facet id -> list of adjacent # (fd cell ndx, firedrake local fac num) @@ -97,7 +108,10 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology): # Loop through each cell (cell closure is all the dmplex ids for any # verts, faces, etc. associated with the cell) - for fd_cell_ndx, closure_dmp_ids in enumerate(top.cell_closure): + cell_closure = top.cell_closure + if cells_to_use is not None: + cell_closure = cell_closure[cells_to_use, :] + for fd_cell_ndx, closure_dmp_ids in enumerate(cell_closure): # Store the vertex indices dmp_verts = closure_dmp_ids[np.logical_and(v_start <= closure_dmp_ids, closure_dmp_ids < v_end)] @@ -141,7 +155,7 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology): return (vertex_indices, nodal_adjacency) -def _get_firedrake_boundary_tags(fdrake_mesh): +def _get_firedrake_boundary_tags(fdrake_mesh, no_boundary=False): """ Return a tuple of bdy tags as requested in the construction of a :mod:`meshmode` :class:`Mesh` @@ -153,10 +167,13 @@ def _get_firedrake_boundary_tags(fdrake_mesh): :arg fdrake_mesh: A :mod:`firedrake` :class:`MeshTopology` or :class:`MeshGeometry` + :arg no_boundary: If *True*, include :class:`BTAG_NO_BOUNDARY` :return: A tuple of boundary tags """ bdy_tags = [BTAG_ALL, BTAG_REALLY_ALL] + if no_boundary: + bdy_tags.append(BTAG_NO_BOUNDARY) unique_markers = fdrake_mesh.topology.exterior_facets.unique_markers if unique_markers is not None: @@ -165,7 +182,8 @@ def _get_firedrake_boundary_tags(fdrake_mesh): return tuple(bdy_tags) -def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology): +def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, + cells_to_use=None): """ Return facial_adjacency_groups corresponding to the given firedrake mesh topology. Note that as we do not @@ -174,8 +192,20 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology): :arg fdrake_mesh_topology: A :mod:`firedrake` instance of class :class:`MeshTopology` or :class:`MeshGeometry`. + :arg cells_to_use: If *None*, then this argument is ignored. + Otherwise, assumed to be a numpy array of unique firedrake + cell ids indicating which cells of the mesh to include, + as well as inducing a new cell index for those cells. + Also, in this case boundary faces are tagged + with :class:`BTAG_NO_BOUNDARY` if they are not a boundary + face in *fdrake_mesh_topology* but become a boundary + because the opposite cell is not in *cells_to_use*. + Boundary faces in *fdrake_mesh_topology* are marked + with :class:`BTAG_ALL`. Both are marked with + :class:`BTAG_REALLY_ALL`. + :return: A list of maps to :class:`FacialAdjacencyGroup`s as required - by a :mod:`meshmode` :class:`Mesh` + by a :mod:`meshmode` :class:`Mesh`. """ top = fdrake_mesh_topology.topology # We only need one group @@ -197,7 +227,19 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology): fd_loc_fac_nr_to_mm[fd_loc_fac_nr] = mm_loc_fac_nr break - # First do the interconnectivity group + # We need boundary tags to tag the boundary + no_boundary = False + if cells_to_use is not None: + no_boundary = True + bdy_tags = _get_firedrake_boundary_tags(top, no_boundary=no_boundary) + boundary_tag_to_index = {bdy_tag: i for i, bdy_tag in enumerate(bdy_tags)} + + def boundary_tag_bit(boundary_tag): + try: + return 1 << boundary_tag_to_index[boundary_tag] + except KeyError: + raise 0 + # Now do the interconnectivity group # Get the firedrake cells associated to each interior facet int_facet_cell = top.interior_facets.facet_cell @@ -214,8 +256,29 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology): int_neighbors = np.concatenate((int_facet_cell[:, 1], int_facet_cell[:, 0])) int_element_faces = int_fac_loc_nr.flatten().astype(Mesh.face_id_dtype) int_neighbor_faces = np.concatenate((int_fac_loc_nr[:, 1], - int_fac_loc_nr[:, 0])) + int_fac_loc_nr[:, 0])) int_neighbor_faces = int_neighbor_faces.astype(Mesh.face_id_dtype) + # If only using some of the cells + if cells_to_use is not None: + to_keep = np.isin(int_elements, cells_to_use) + cells_to_use_inv = dict(zip(cells_to_use, + np.arange(np.size(cells_to_use)))) + + # Only keep cells that using, and change to new cell index + int_elements = [cells_to_use_inv[icell] + for icell in int_elements[to_keep]] + int_element_faces = int_element_faces[to_keep] + int_neighbors = int_elements[to_keep] + int_neighbor_faces = int_element_faces[to_keep] + # For neighbor cells, change to new cell index or mark + # as a new boundary (if the neighbor cell is not being used) + for ndx, icell in enumerate(int_neighbors): + try: + int_neighbors[ndx] = cells_to_use_inv[icell] + except KeyError: + int_neighbors[ndx] = -(boundary_tag_bit(BTAG_REALLY_ALL) + | boundary_tag_bit(BTAG_NO_BOUNDARY)) + int_neighbor_faces[ndx] = 0.0 interconnectivity_grp = FacialAdjacencyGroup(igroup=0, ineighbor_group=0, elements=int_elements, @@ -223,7 +286,7 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology): element_faces=int_element_faces, neighbor_faces=int_neighbor_faces) - # Now look at exterior facets + # First look at exterior facets # We can get the elements directly from exterior facets ext_elements = top.exterior_facets.facet_cell.flatten() @@ -232,17 +295,17 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology): ext_element_faces = ext_element_faces.astype(Mesh.face_id_dtype) ext_neighbor_faces = np.zeros(ext_element_faces.shape, dtype=np.int32) ext_neighbor_faces = ext_neighbor_faces.astype(Mesh.face_id_dtype) - - # Now we need to tag the boundary - bdy_tags = _get_firedrake_boundary_tags(top) - boundary_tag_to_index = {bdy_tag: i for i, bdy_tag in enumerate(bdy_tags)} - - def boundary_tag_bit(boundary_tag): - try: - return 1 << boundary_tag_to_index[boundary_tag] - except KeyError: - raise 0 - + # If only using some of the cells, throw away unused cells and + # move to new cell index + if cells_to_use is not None: + to_keep = np.isin(ext_elements, cells_to_use) + ext_elements = [cells_to_use_inv[icell] + for icell in ext_elements[to_keep]] + ext_element_faces = ext_element_faces[to_keep] + ext_neighbors = ext_elements[to_keep] + ext_neighbor_faces = ext_element_faces[to_keep] + + # tag the boundary ext_neighbors = np.zeros(ext_elements.shape, dtype=np.int32) for ifac, marker in enumerate(top.exterior_facets.markers): ext_neighbors[ifac] = -(boundary_tag_bit(BTAG_ALL) @@ -263,6 +326,7 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology): # {{{ Orientation computation def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, + cells_to_use, normals=None, no_normals_warn=True): """ Return the orientations of the mesh elements: @@ -271,14 +335,19 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, :arg unflipped_group: A :class:`SimplexElementGroup` instance with (potentially) some negatively oriented elements. :arg vertices: The vertex coordinates as a numpy array of shape - *(ambient_dim, nvertices)* + *(ambient_dim, nvertices)* (the vertices of *unflipped_group*) :arg normals: As described in the kwargs of :func:`import_firedrake_mesh` :arg no_normals_warn: As described in the kwargs of :func:`import_firedrake_mesh` + :arg cells_to_use: If *None*, then ignored. Otherwise, a numpy array + of unique firedrake cell indices. In this case, *cells_to_use* should + have also been used in the creation *unflipped_group*. - :return: A numpy array, the *i*th element is > 0 if the *ith* element + :return: A numpy array, the *i*th element is > 0 if the *i*th element is positively oriented, < 0 if negatively oriented. - Mesh must have co-dimension 0 or 1. + Mesh must have co-dimension 0 or 1. If *cells_to_use* is not + *None*, then the *i*th entry corresponds to the + *cells_to_use[i]*th element. """ # compute orientations tdim = fdrake_mesh.topological_dimension() @@ -295,11 +364,16 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, if tdim == 1 and gdim == 2: # In this case we have a 1-surface embedded in 2-space - orient = np.ones(fdrake_mesh.num_cells()) + if cells_to_use is None: + num_cells = fdrake_mesh.num_cells() + else: + num_cells = np.size(cells_to_use) + orient = np.ones(num_cells) if normals: - for i, (normal, vertices) in enumerate(zip(np.array(normals), - vertices)): - if np.cross(normal, vertices) < 0: + for i, (normal, vert_indices) in enumerate( + zip(np.array(normals), unflipped_group.vertex_indices)): + edge = vertices[:, vert_indices[1]] - vertices[:, vert_indices[0]] + if np.cross(normal, edge) < 0: orient[i] = -1.0 elif no_normals_warn: warn("Assuming all elements are positively-oriented.") @@ -307,17 +381,17 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, elif tdim == 2 and gdim == 3: # In this case we have a 2-surface embedded in 3-space orient = fdrake_mesh.cell_orientations().dat.data + if cells_to_use is not None: + orient = orient[cells_to_use] r""" Convert (0 \implies negative, 1 \implies positive) to (-1 \implies negative, 1 \implies positive) """ orient *= 2 - orient -= np.ones(orient.shape, dtype=orient.dtype) + orient -= 1 #Make sure the mesh fell into one of the cases - """ - NOTE : This should be guaranteed by previous checks, - but is here anyway in case of future development. - """ + #Nb : This should be guaranteed by previous checks, + # but is here anyway in case of future development. assert orient is not None, "something went wrong, contact the developer" return orient @@ -326,7 +400,8 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, # {{{ Mesh importing from firedrake -def import_firedrake_mesh(fdrake_mesh, normals=None, no_normals_warn=None): +def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, + normals=None, no_normals_warn=None): """ Create a :mod:`meshmode` :class:`Mesh` from a :mod:`firedrake` :class:`MeshGeometry` with the same cells/elements, vertices, nodes, @@ -368,6 +443,13 @@ def import_firedrake_mesh(fdrake_mesh, normals=None, no_normals_warn=None): vertex_entity_dofs = coords_fspace.finat_element.entity_dofs()[0] for entity, dof_list in six.iteritems(vertex_entity_dofs): assert len(dof_list) > 0 + :arg cells_to_use: Either *None*, in which case this argument is ignored, + or a numpy array of unique firedrake cell indexes. In the latter case, + only cells whose index appears in *cells_to_use* are included + in the resultant mesh, and their index in *cells_to_use* + becomes the element index in the resultant mesh element group. + Any faces or vertices which do not touch a cell in + *cells_to_use* are also ignored. :arg normals: **Only** used if *fdrake_mesh* is a 1-surface embedded in 2-space. In this case, - If *None* then @@ -392,6 +474,15 @@ def import_firedrake_mesh(fdrake_mesh, normals=None, no_normals_warn=None): raise TypeError(":arg:`fdrake_mesh_topology` must be a " ":mod:`firedrake` :class:`MeshGeometry`, " "not %s." % type(fdrake_mesh)) + if cells_to_use is not None: + if not isinstance(cells_to_use, np.ndarray): + raise TypeError(":arg:`cells_to_use` must be a np.ndarray or " + "*None*") + assert len(cells_to_use.shape) == 1 + assert np.size(np.unique(cells_to_use)) == np.size(cells_to_use), \ + ":arg:`cells_to_use` must have unique entries" + assert np.all(np.logical_and(cells_to_use >= 0, + cells_to_use < fdrake_mesh.num_cells())) assert fdrake_mesh.ufl_cell().is_simplex(), "Mesh must use simplex cells" gdim = fdrake_mesh.geometric_dimension() tdim = fdrake_mesh.topological_dimension() @@ -400,8 +491,12 @@ def import_firedrake_mesh(fdrake_mesh, normals=None, no_normals_warn=None): fdrake_mesh.init() # Get all the nodal information we can from the topology - bdy_tags = _get_firedrake_boundary_tags(fdrake_mesh) - vertex_indices, nodal_adjacency = _get_firedrake_nodal_info(fdrake_mesh) + no_boundary = False + if cells_to_use is not None: + no_boundary = True + bdy_tags = _get_firedrake_boundary_tags(fdrake_mesh, no_boundary=no_boundary) + vertex_indices, nodal_adjacency = \ + _get_firedrake_nodal_info(fdrake_mesh, cells_to_use=cells_to_use) # Grab the mesh reference element and cell dimension coord_finat_elt = fdrake_mesh.coordinates.function_space().finat_element @@ -417,6 +512,8 @@ def import_firedrake_mesh(fdrake_mesh, normals=None, no_normals_warn=None): # Now grab the nodes coords = fdrake_mesh.coordinates cell_node_list = coords.function_space().cell_node_list + if cells_to_use is not None: + cell_node_list = cell_node_list[cells_to_use] nodes = np.real(coords.dat.data[cell_node_list]) # Add extra dim in 1D so that have [nelements][nunit_nodes][dim] if len(nodes.shape) == 2: @@ -463,6 +560,7 @@ def import_firedrake_mesh(fdrake_mesh, normals=None, no_normals_warn=None): # Use the vertices to compute the orientations and flip the group orient = _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, + cells_to_use=cells_to_use, normals=normals, no_normals_warn=no_normals_warn) from meshmode.mesh.processing import flip_simplex_element_group @@ -485,7 +583,8 @@ def import_firedrake_mesh(fdrake_mesh, normals=None, no_normals_warn=None): no_one_face_ndx = iface unflipped_facial_adjacency_groups = \ - _get_firedrake_facial_adjacency_groups(fdrake_mesh) + _get_firedrake_facial_adjacency_groups(fdrake_mesh, + cells_to_use=cells_to_use) def flip_local_face_indices(faces, elements): faces = np.copy(faces) -- GitLab From b3fd9cbf6a5fa96bab8067da6cc1e871709aa424 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 2 Jul 2020 14:21:41 -0500 Subject: [PATCH 066/221] Implement FromBdyFiredrakeConnection --- meshmode/interop/firedrake/connection.py | 81 ++++++++++++++++++++++++ meshmode/interop/firedrake/mesh.py | 8 ++- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 2ef06da0..01fa368e 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -517,6 +517,87 @@ class FromFiredrakeConnection(FiredrakeConnection): "Somehow a firedrake node in a 'DG' space got duplicated..." \ "contact the developer." + +def _compute_cells_near_bdy(mesh, bdy_id): + """ + Returns an array of the cell ids with >= 1 vertex on the + given bdy_id + """ + cfspace = mesh.coordinates.function_space() + cell_node_list = cfspace.cell_node_list + + boundary_nodes = cfspace.boundary_nodes(bdy_id, 'topological') + # Reduce along each cell: Is a vertex of the cell in boundary nodes? + cell_is_near_bdy = np.any(np.isin(cell_node_list, boundary_nodes), axis=1) + + return np.arange(cell_node_list.shape[0], dtype=np.int32)[cell_is_near_bdy] + + +class FromBdyFiredrakeConnection(FiredrakeConnection): + """ + A connection created from a :mod:`firedrake` + ``"CG"`` or ``"DG"`` function space which creates a + meshmode discretization corresponding to all cells with a face on + the given boundary and allows + transfer of functions to and from :mod:`firedrake`. + """ + def __init__(self, cl_ctx, fdrake_fspace, bdy_id): + """ + :arg cl_ctx: A :mod:`pyopencl` computing context + :arg fdrake_fspace: A :mod:`firedrake` ``"CG"`` or ``"DG"`` + function space (of class :class:`WithGeometry`) built on + a mesh which is importable by :func:`import_firedrake_mesh`. + :arg bdy_id: A boundary marker of *fdrake_fspace.mesh()* as accepted by + the *boundary_nodes* method of a firedrake + :class:`firedrake.functionspaceimpl.WithGeometry`. + """ + # Ensure fdrake_fspace is a function space with appropriate reference + # element. + from firedrake.functionspaceimpl import WithGeometry + if not isinstance(fdrake_fspace, WithGeometry): + raise TypeError(":arg:`fdrake_fspace` must be of firedrake type " + ":class:`WithGeometry`, not `%s`." + % type(fdrake_fspace)) + ufl_elt = fdrake_fspace.ufl_element() + + if ufl_elt.family() not in ('Lagrange', 'Discontinuous Lagrange'): + raise ValueError("the ``ufl_element().family()`` of " + ":arg:`fdrake_fspace` must " + "be ``'Lagrange'`` or " + "``'Discontinuous Lagrange'``, not %s." + % ufl_elt.family()) + + # Create to_discr + cells_to_use = _compute_cells_near_bdy(fdrake_fspace.mesh(), bdy_id) + mm_mesh, orient = import_firedrake_mesh(fdrake_fspace.mesh(), + cells_to_use=cells_to_use) + factory = InterpolatoryQuadratureSimplexGroupFactory(ufl_elt.degree()) + to_discr = Discretization(cl_ctx, mm_mesh, factory) + + # get firedrake unit nodes and map onto meshmode reference element + group = to_discr.groups[0] + fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(group.dim, + True) + fd_unit_nodes = get_finat_element_unit_nodes(fdrake_fspace.finat_element) + fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) + + # Get the reordering fd->mm, see the note in + # :class:`FromFiredrakeConnection` for a comment on what this is + # doing in continuous spaces. + flip_mat = get_simplex_element_flip_matrix(ufl_elt.degree(), + fd_unit_nodes) + fd_cell_node_list = fdrake_fspace.cell_node_list[cells_to_use] + _reorder_nodes(orient, fd_cell_node_list, flip_mat, unflip=False) + mm2fd_node_mapping = fd_cell_node_list.flatten() + + super(FromBdyFiredrakeConnection, self).__init__(to_discr, + fdrake_fspace, + mm2fd_node_mapping) + if fdrake_fspace.ufl_element().family() == 'Discontinuous Lagrange': + assert len(self._duplicate_nodes) == 0, \ + "Somehow a firedrake node in a 'DG' space got duplicated..." \ + "contact the developer." + # }}} diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 89576497..e82ac8dd 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -340,8 +340,7 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, :arg no_normals_warn: As described in the kwargs of :func:`import_firedrake_mesh` :arg cells_to_use: If *None*, then ignored. Otherwise, a numpy array - of unique firedrake cell indices. In this case, *cells_to_use* should - have also been used in the creation *unflipped_group*. + of unique firedrake cell indices indicating which cells to use. :return: A numpy array, the *i*th element is > 0 if the *i*th element is positively oriented, < 0 if negatively oriented. @@ -450,6 +449,11 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, becomes the element index in the resultant mesh element group. Any faces or vertices which do not touch a cell in *cells_to_use* are also ignored. + Note that in this later case, some faces that are not + boundaries in *fdrake_mesh* may become boundaries in the + returned mesh. These "induced" boundaries are marked with + :class:`BTAG_NO_BOUNDARY` (and :class:`BTAG_REALLY_ALL`) + instead of :class:`BTAG_ALL`. :arg normals: **Only** used if *fdrake_mesh* is a 1-surface embedded in 2-space. In this case, - If *None* then -- GitLab From 4bc3ff7dc836701800c63897a2803d18737b1e59 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Fri, 3 Jul 2020 11:07:28 -0500 Subject: [PATCH 067/221] Add testing for boundary tagging --- test/test_firedrake_interop.py | 79 +++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index b3740f24..0fe9005c 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -27,7 +27,8 @@ from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) -from meshmode.interop.firedrake import FromFiredrakeConnection +from meshmode.interop.firedrake import ( + FromFiredrakeConnection, import_firedrake_mesh) import pytest @@ -133,6 +134,82 @@ def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): # }}} +# {{{ Boundary tags checking + +bdy_tests = [(UnitSquareMesh(10, 10), + [1, 2, 3, 4], + [0, 0, 1, 1], + [0.0, 1.0, 0.0, 1.0]), + (UnitCubeMesh(5, 5, 5), + [1, 2, 3, 4, 5, 6], + [0, 0, 1, 1, 2, 2], + [0.0, 1.0, 0.0, 1.0, 0.0, 1.0]), + ] + + +@pytest.mark.parametrize("square_or_cube_mesh,bdy_ids,coord_indices,coord_values", + bdy_tests) +def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values): + """ + Make sure the given boundary ids cover the converted mesh. + Make sure that the given coordinate have the given value for the + corresponding boundary tag (see :mod:`firedrake`'s documentation + to see how the boundary tags for its utility meshes are defined) + """ + from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL, check_bc_coverage + mm_mesh, orient = import_firedrake_mesh(square_or_cube_mesh) + # Ensure meshmode required boundary tags are there + assert set([BTAG_ALL, BTAG_REALLY_ALL]) <= set(mm_mesh.boundary_tags) + # Check disjoint coverage of bdy ids and BTAG_ALL + check_bc_coverage(mm_mesh, [BTAG_ALL]) + check_bc_coverage(mm_mesh, bdy_ids) + + # count number of times the boundary tag appears in the meshmode mesh, + # should be the same as in the firedrake mesh + bdy_id_to_mm_count = {} + # Now make sure we have identified the correct faces + face_vertex_indices = mm_mesh.groups[0].face_vertex_indices() + ext_grp = mm_mesh.facial_adjacency_groups[0][None] + for iel, ifac, bdy_flags in zip( + ext_grp.elements, ext_grp.element_faces, ext_grp.neighbors): + el_vert_indices = mm_mesh.groups[0].vertex_indices[iel] + # numpy nb: have to have comma to use advanced indexing + face_vert_indices = el_vert_indices[face_vertex_indices[ifac], ] + # shape: *(ambient dim, num vertices on face)* + face_verts = mm_mesh.vertices[:, face_vert_indices] + print("Face vertex indices=", face_vertex_indices) + print("iel=", iel) + print("ifac=", ifac) + print("el verts =", mm_mesh.groups[0].vertex_indices[iel]) + print("orient=", orient[iel]) + print() + # Figure out which coordinate should have a fixed value, and what + # that value is. Also, count how many times each boundary tag appears + coord_index, val = None, None + for bdy_id_index, bdy_id in enumerate(bdy_ids): + if mm_mesh.boundary_tag_bit(bdy_id) & -bdy_flags: + bdy_id_to_mm_count.setdefault(bdy_id, 0) + bdy_id_to_mm_count[bdy_id] += 1 + coord_index = coord_indices[bdy_id_index] + val = coord_values[bdy_id_index] + break + print('vert indices =', face_vert_indices) + print("Verts=", face_verts) + print("val=", val) + print("coord=", coord_index) + assert np.max(np.abs(face_verts[coord_index, :] - val)) < CLOSE_ATOL + + # Verify that the number of meshes tagged with a boundary tag + # is the same in meshmode and firedrake for each tag in *bdy_ids* + fdrake_bdy_ids, fdrake_counts = \ + np.unique(square_or_cube_mesh.exterior_facets.markers, return_counts=True) + assert set(fdrake_bdy_ids) == set(bdy_ids) + for bdy_id, fdrake_count in zip(fdrake_bdy_ids, fdrake_counts): + assert fdrake_count == bdy_id_to_mm_count[bdy_id] + +# }}} + + # {{{ Double check functions are being transported correctly def alternating_sum_fd(spatial_coord): -- GitLab From 2027ac199d492440556a9673521bc66c9b15c03b Mon Sep 17 00:00:00 2001 From: benSepanski Date: Fri, 3 Jul 2020 11:08:01 -0500 Subject: [PATCH 068/221] Some doc fixes --- meshmode/interop/firedrake/connection.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 01fa368e..9c5c8eb8 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -24,9 +24,8 @@ __doc__ = """ .. autoclass:: FiredrakeConnection :members: .. autoclass:: FromFiredrakeConnection - :members: +.. autoclass:: FromBdyFiredrakeConnection .. autoclass:: ToFiredrakeConnection - :members: """ import numpy as np @@ -537,8 +536,8 @@ class FromBdyFiredrakeConnection(FiredrakeConnection): """ A connection created from a :mod:`firedrake` ``"CG"`` or ``"DG"`` function space which creates a - meshmode discretization corresponding to all cells with a face on - the given boundary and allows + meshmode discretization corresponding to all cells with at + least one vertex on the given boundary and allows transfer of functions to and from :mod:`firedrake`. """ def __init__(self, cl_ctx, fdrake_fspace, bdy_id): -- GitLab From 5baef9af851d04c768529a3facc0fcb0f346b5da Mon Sep 17 00:00:00 2001 From: benSepanski Date: Fri, 3 Jul 2020 11:08:25 -0500 Subject: [PATCH 069/221] Doc fixes + remember to convert fd local facet nrs -> meshmode --- meshmode/interop/firedrake/mesh.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index e82ac8dd..5ce3a674 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -211,7 +211,11 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, # We only need one group # for interconnectivity and one for boundary connectivity. # The tricky part is moving from firedrake local facet numbering - # (ordered lexicographically by the vertex excluded from the face) + # (ordered lexicographically by the vertex excluded from the face, + # search for "local facet number" in the following paper for + # a reference on this... + # https://spiral.imperial.ac.uk/bitstream/10044/1/28819/2/mlange-firedrake-dmplex-accepted.pdf # noqa : E501 + # ) # and meshmode's facet ordering: obtained from a simplex element # group mm_simp_group = SimplexElementGroup(1, None, None, @@ -239,6 +243,7 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, return 1 << boundary_tag_to_index[boundary_tag] except KeyError: raise 0 + # Now do the interconnectivity group # Get the firedrake cells associated to each interior facet @@ -286,12 +291,13 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, element_faces=int_element_faces, neighbor_faces=int_neighbor_faces) - # First look at exterior facets + # Then look at exterior facets # We can get the elements directly from exterior facets ext_elements = top.exterior_facets.facet_cell.flatten() - ext_element_faces = top.exterior_facets.local_facet_dat.data + ext_element_faces = np.array([fd_loc_fac_nr_to_mm[fac_nr] for fac_nr in + top.exterior_facets.local_facet_dat.data]) ext_element_faces = ext_element_faces.astype(Mesh.face_id_dtype) ext_neighbor_faces = np.zeros(ext_element_faces.shape, dtype=np.int32) ext_neighbor_faces = ext_neighbor_faces.astype(Mesh.face_id_dtype) @@ -574,9 +580,7 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, # This changes the local facet nr, so we need to create and then # fix our facial adjacency groups. To do that, we need to figure # out which local facet numbers switched. - mm_simp_group = SimplexElementGroup(1, None, None, - dim=fdrake_mesh.cell_dimension()) - face_vertex_indices = mm_simp_group.face_vertex_indices() + face_vertex_indices = group.face_vertex_indices() # face indices of the faces not containing vertex 0 and not # containing vertex 1, respectively no_zero_face_ndx, no_one_face_ndx = None, None @@ -590,6 +594,9 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, _get_firedrake_facial_adjacency_groups(fdrake_mesh, cells_to_use=cells_to_use) + # applied below to take elements and element_faces + # (or neighbors and neighbor_faces) and flip in any faces that need to + # be flipped. def flip_local_face_indices(faces, elements): faces = np.copy(faces) to_no_one = np.logical_and(orient[elements] < 0, @@ -599,6 +606,7 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, faces[to_no_one], faces[to_no_zero] = no_one_face_ndx, no_zero_face_ndx return faces + # Create new facial adjacency groups that have been flipped facial_adjacency_groups = [] for igroup, fagrps in enumerate(unflipped_facial_adjacency_groups): facial_adjacency_groups.append({}) -- GitLab From bbc88920bba8e0234c485581a5e9bc85e8698477 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sat, 4 Jul 2020 09:14:21 -0500 Subject: [PATCH 070/221] Improve docs for frombdy related args --- meshmode/interop/firedrake/connection.py | 4 ++++ meshmode/interop/firedrake/mesh.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 9c5c8eb8..b63dfc9a 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -539,6 +539,10 @@ class FromBdyFiredrakeConnection(FiredrakeConnection): meshmode discretization corresponding to all cells with at least one vertex on the given boundary and allows transfer of functions to and from :mod:`firedrake`. + + Use the same bdy_id as one would for a + :class:`firedrake.bcs.DirichletBC`. + ``"on_boundary"`` corresponds to the entire boundary. """ def __init__(self, cl_ctx, fdrake_fspace, bdy_id): """ diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 5ce3a674..569a3b9f 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -460,6 +460,9 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, returned mesh. These "induced" boundaries are marked with :class:`BTAG_NO_BOUNDARY` (and :class:`BTAG_REALLY_ALL`) instead of :class:`BTAG_ALL`. + + This argument is primarily intended for use by a + :class:`meshmode.interop.firedrake.FromBdyFiredrakeConnection`. :arg normals: **Only** used if *fdrake_mesh* is a 1-surface embedded in 2-space. In this case, - If *None* then -- GitLab From a95a8dcf48ccce3ba0983e7d19b8497e4a75026e Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sun, 5 Jul 2020 09:51:10 -0500 Subject: [PATCH 071/221] Add testing for FromBdyConnection --- test/test_firedrake_interop.py | 77 ++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 0fe9005c..2963bc43 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -27,8 +27,10 @@ from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) +from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL, check_bc_coverage from meshmode.interop.firedrake import ( - FromFiredrakeConnection, import_firedrake_mesh) + FromFiredrakeConnection, FromBdyFiredrakeConnection, import_firedrake_mesh) +from meshmode.interop.firedrake.connection import _compute_cells_near_bdy import pytest @@ -95,6 +97,8 @@ def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): agree across firedrake vs meshmode """ # get fdrake_verts (shaped like (nverts, dim)) + # Nb : Mesh must be order 1 for these to be vertices + assert fdrake_mesh.coordinates.function_space().finat_element.degree == 1 fdrake_verts = fdrake_mesh.coordinates.dat.data if fdrake_mesh.geometric_dimension() == 1: fdrake_verts = fdrake_verts[:, np.newaxis] @@ -111,6 +115,7 @@ def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): assert len(discr.mesh.groups) == 1 fdrake_mesh_fspace = fdrake_mesh.coordinates.function_space() fdrake_mesh_order = fdrake_mesh_fspace.finat_element.degree + assert discr.mesh.groups[0].dim == fdrake_mesh.topological_dimension() assert discr.mesh.groups[0].order == fdrake_mesh_order assert discr.mesh.groups[0].nelements == fdrake_mesh.num_cells() assert discr.mesh.nvertices == fdrake_mesh.num_vertices() @@ -134,6 +139,65 @@ def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): # }}} +# {{{ Now check the FromBdyFiredrakeConnection + +def test_from_bdy_consistency(ctx_factory, + fdrake_mesh, + fdrake_family, + fdrake_degree): + """ + Make basic checks that FiredrakeFromBdyConnection is not doing something + obviouisly wrong, i.e. that it has proper tagging, that it has + the right number of cells, etc. + """ + fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fdrake_degree) + cl_ctx = ctx_factory() + frombdy_conn = FromBdyFiredrakeConnection(cl_ctx, + fdrake_fspace, + "on_boundary") + + # Ensure the meshmode mesh has one group and make sure both + # meshes agree on some basic properties + discr = frombdy_conn.discr + assert len(discr.mesh.groups) == 1 + fdrake_mesh_fspace = fdrake_mesh.coordinates.function_space() + fdrake_mesh_order = fdrake_mesh_fspace.finat_element.degree + assert discr.mesh.groups[0].dim == fdrake_mesh.topological_dimension() + assert discr.mesh.groups[0].order == fdrake_mesh_order + + # get fdrake_verts (shaped like (nverts, dim)) + # Nb : Mesh must be order 1 for these to be vertices + assert fdrake_mesh.coordinates.function_space().finat_element.degree == 1 + fdrake_verts = fdrake_mesh.coordinates.dat.data + if fdrake_mesh.geometric_dimension() == 1: + fdrake_verts = fdrake_verts[:, np.newaxis] + # only look at cells "near" bdy (with >= 1 vertex on) + cells_near_bdy = _compute_cells_near_bdy(fdrake_mesh, 'on_boundary') + verts_near_bdy = np.unique( + fdrake_mesh_fspace.cell_node_list[cells_near_bdy, :].flatten()) + fdrake_verts = fdrake_verts[verts_near_bdy, :] + # Get meshmode vertices (shaped like (dim, nverts)) + meshmode_verts = discr.mesh.vertices + + # Ensure that the vertices of firedrake elements on + # the boundary are identical to the resultant meshes' vertices up to + # reordering + # Nb: I got help on this from stack overflow: + # https://stackoverflow.com/questions/38277143/sort-2d-numpy-array-lexicographically # noqa: E501 + lex_sorted_mm_verts = meshmode_verts[:, np.lexsort(meshmode_verts)] + lex_sorted_fdrake_verts = fdrake_verts[np.lexsort(fdrake_verts.T)] + np.testing.assert_array_equal(lex_sorted_mm_verts, lex_sorted_fdrake_verts.T) + + # Ensure the discretization and the firedrake function space reference element + # agree on some basic properties + finat_elt = fdrake_fspace.finat_element + assert len(discr.groups) == 1 + assert discr.groups[0].order == finat_elt.degree + assert discr.groups[0].nunit_nodes == finat_elt.space_dimension() + +# }}} + + # {{{ Boundary tags checking bdy_tests = [(UnitSquareMesh(10, 10), @@ -149,15 +213,20 @@ bdy_tests = [(UnitSquareMesh(10, 10), @pytest.mark.parametrize("square_or_cube_mesh,bdy_ids,coord_indices,coord_values", bdy_tests) -def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values): +@pytest.mark.parametrize("only_convert_bdy", (True, False)) +def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values, + only_convert_bdy): """ Make sure the given boundary ids cover the converted mesh. Make sure that the given coordinate have the given value for the corresponding boundary tag (see :mod:`firedrake`'s documentation to see how the boundary tags for its utility meshes are defined) """ - from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL, check_bc_coverage - mm_mesh, orient = import_firedrake_mesh(square_or_cube_mesh) + cells_to_use = None + if only_convert_bdy: + cells_to_use = _compute_cells_near_bdy(square_or_cube_mesh, 'on_boundary') + mm_mesh, orient = import_firedrake_mesh(square_or_cube_mesh, + cells_to_use=cells_to_use) # Ensure meshmode required boundary tags are there assert set([BTAG_ALL, BTAG_REALLY_ALL]) <= set(mm_mesh.boundary_tags) # Check disjoint coverage of bdy ids and BTAG_ALL -- GitLab From 8fd2ac02a5cff7d01ee3b42eb72289e4a1863082 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sun, 5 Jul 2020 09:51:46 -0500 Subject: [PATCH 072/221] Some bug fixes to allow for FromBdyConnection, be more careful with flipping local facet nrs --- meshmode/interop/firedrake/__init__.py | 7 ++- meshmode/interop/firedrake/connection.py | 2 +- meshmode/interop/firedrake/mesh.py | 55 ++++++++++++++---------- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/meshmode/interop/firedrake/__init__.py b/meshmode/interop/firedrake/__init__.py index 890dbaf7..6ebacd91 100644 --- a/meshmode/interop/firedrake/__init__.py +++ b/meshmode/interop/firedrake/__init__.py @@ -22,10 +22,13 @@ THE SOFTWARE. import numpy as np -from meshmode.interop.firedrake.connection import FromFiredrakeConnection +from meshmode.interop.firedrake.connection import ( + FromBdyFiredrakeConnection, FromFiredrakeConnection) from meshmode.interop.firedrake.mesh import import_firedrake_mesh -__all__ = ["FromFiredrakeConnection", "import_firedrake_mesh"] +__all__ = ["FromBdyFiredrakeConnection", "FromFiredrakeConnection", + "import_firedrake_mesh", + ] def _compute_cells_near_bdy(mesh, bdy_id): diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index b63dfc9a..0501bc65 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -529,7 +529,7 @@ def _compute_cells_near_bdy(mesh, bdy_id): # Reduce along each cell: Is a vertex of the cell in boundary nodes? cell_is_near_bdy = np.any(np.isin(cell_node_list, boundary_nodes), axis=1) - return np.arange(cell_node_list.shape[0], dtype=np.int32)[cell_is_near_bdy] + return np.nonzero(cell_is_near_bdy)[0].astype(np.int32) class FromBdyFiredrakeConnection(FiredrakeConnection): diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 569a3b9f..59a72ac4 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -142,7 +142,11 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology, cells_to_use=None): # Next go ahead and compute nodal adjacency by creating # neighbors and neighbor_starts as specified by :class:`NodalAdjacency` neighbors = [] - neighbors_starts = np.zeros(top.num_cells() + 1, dtype=np.int32) + if cells_to_use is None: + num_cells = top.num_cells() + else: + num_cells = np.size(cells_to_use) + neighbors_starts = np.zeros(num_cells + 1, dtype=np.int32) for iel in range(len(cell_to_nodal_neighbors)): neighbors += cell_to_nodal_neighbors[iel] neighbors_starts[iel+1] = len(neighbors) @@ -270,11 +274,11 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, np.arange(np.size(cells_to_use)))) # Only keep cells that using, and change to new cell index - int_elements = [cells_to_use_inv[icell] - for icell in int_elements[to_keep]] + int_elements = np.vectorize(cells_to_use_inv.__getitem__)( + int_elements[to_keep]) int_element_faces = int_element_faces[to_keep] - int_neighbors = int_elements[to_keep] - int_neighbor_faces = int_element_faces[to_keep] + int_neighbors = int_neighbors[to_keep] + int_neighbor_faces = int_neighbor_faces[to_keep] # For neighbor cells, change to new cell index or mark # as a new boundary (if the neighbor cell is not being used) for ndx, icell in enumerate(int_neighbors): @@ -299,17 +303,16 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, ext_element_faces = np.array([fd_loc_fac_nr_to_mm[fac_nr] for fac_nr in top.exterior_facets.local_facet_dat.data]) ext_element_faces = ext_element_faces.astype(Mesh.face_id_dtype) - ext_neighbor_faces = np.zeros(ext_element_faces.shape, dtype=np.int32) - ext_neighbor_faces = ext_neighbor_faces.astype(Mesh.face_id_dtype) + ext_neighbor_faces = np.zeros(ext_element_faces.shape, + dtype=Mesh.face_id_dtype) # If only using some of the cells, throw away unused cells and # move to new cell index if cells_to_use is not None: to_keep = np.isin(ext_elements, cells_to_use) - ext_elements = [cells_to_use_inv[icell] - for icell in ext_elements[to_keep]] + ext_elements = np.vectorize(cells_to_use_inv.__getitem__)( + ext_elements[to_keep]) ext_element_faces = ext_element_faces[to_keep] - ext_neighbors = ext_elements[to_keep] - ext_neighbor_faces = ext_element_faces[to_keep] + ext_neighbor_faces = ext_neighbor_faces[to_keep] # tag the boundary ext_neighbors = np.zeros(ext_elements.shape, dtype=np.int32) @@ -510,6 +513,15 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, bdy_tags = _get_firedrake_boundary_tags(fdrake_mesh, no_boundary=no_boundary) vertex_indices, nodal_adjacency = \ _get_firedrake_nodal_info(fdrake_mesh, cells_to_use=cells_to_use) + # If only using some cells, vertices may need new indices as many + # will be removed + if cells_to_use is not None: + vert_ndx_new2old = np.unique(vertex_indices.flatten()) + vert_ndx_old2new = dict(zip(vert_ndx_new2old, + np.arange(np.size(vert_ndx_new2old), + dtype=vertex_indices.dtype))) + vertex_indices = \ + np.vectorize(vert_ndx_old2new.__getitem__)(vertex_indices) # Grab the mesh reference element and cell dimension coord_finat_elt = fdrake_mesh.coordinates.function_space().finat_element @@ -602,11 +614,14 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, # be flipped. def flip_local_face_indices(faces, elements): faces = np.copy(faces) - to_no_one = np.logical_and(orient[elements] < 0, - faces == no_zero_face_ndx) - to_no_zero = np.logical_and(orient[elements] < 0, - faces == no_one_face_ndx) - faces[to_no_one], faces[to_no_zero] = no_one_face_ndx, no_zero_face_ndx + neg_elements = np.full(elements.shape, False) + # To handle neighbor case, we only need to flip at elements + # who have a neighbor, i.e. where neighbors is not a negative + # bitmask of bdy tags + neg_elements[elements >= 0] = (orient[elements[elements >= 0]] < 0) + no_zero = np.logical_and(neg_elements, faces == no_zero_face_ndx) + no_one = np.logical_and(neg_elements, faces == no_one_face_ndx) + faces[no_zero], faces[no_one] = no_one_face_ndx, no_zero_face_ndx return faces # Create new facial adjacency groups that have been flipped @@ -616,12 +631,8 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, for ineighbor_group, fagrp in six.iteritems(fagrps): new_element_faces = flip_local_face_indices(fagrp.element_faces, fagrp.elements) - if ineighbor_group is None: - new_neighbor_faces = fagrp.neighbor_faces - else: - new_neighbor_faces = \ - flip_local_face_indices(fagrp.neighbor_faces, - fagrp.neighbors) + new_neighbor_faces = flip_local_face_indices(fagrp.neighbor_faces, + fagrp.neighbors) new_fagrp = FacialAdjacencyGroup(igroup=igroup, ineighbor_group=ineighbor_group, elements=fagrp.elements, -- GitLab From d828024a74740bd2010f91d2c2ea5cd969680c5d Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sun, 5 Jul 2020 10:14:28 -0500 Subject: [PATCH 073/221] Added FromBdy to test_function_transfer, added some TODOs --- test/test_firedrake_interop.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 2963bc43..0d9891cb 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -84,9 +84,6 @@ def fdrake_degree(request): return request.param -# TODO : make some tests to verify boundary tagging - - # {{{ Basic conversion checks for the function space def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): @@ -314,9 +311,11 @@ test_functions = [ @pytest.mark.parametrize("fdrake_f_expr,meshmode_f_eval", test_functions) +@pytest.mark.parametrize("only_convert_bdy", (False, True)) def test_function_transfer(ctx_factory, fdrake_mesh, fdrake_family, fdrake_degree, - fdrake_f_expr, meshmode_f_eval): + fdrake_f_expr, meshmode_f_eval, + only_convert_bdy): """ Make sure creating a function then transporting it is the same (up to resampling error) as creating a function on the transported @@ -328,7 +327,12 @@ def test_function_transfer(ctx_factory, fdrake_f = Function(fdrake_fspace).interpolate(fdrake_f_expr(spatial_coord)) cl_ctx = ctx_factory() - fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) + if only_convert_bdy: + fdrake_connection = FromBdyFiredrakeConnection(cl_ctx, fdrake_fspace, + 'on_boundary') + else: + fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) + transported_f = fdrake_connection.from_firedrake(fdrake_f) discr = fdrake_connection.discr @@ -340,6 +344,9 @@ def test_function_transfer(ctx_factory, # }}} +# TODO : Add idempotency test for FromBdyFiredrakeConnection +# TODO : Add idempotency test for tensor function spaces + # {{{ Idempotency tests fd->mm->fd and (fd->)mm->fd->mm for connection @@ -348,8 +355,10 @@ def check_idempotency(fdrake_connection, fdrake_function): Make sure fd->mm->fd and mm->fd->mm are identity """ vdim = None - if len(fdrake_function.dat.data.shape) > 1: + if len(fdrake_function.dat.data.shape) == 2: vdim = fdrake_function.dat.data.shape[1] + elif len(fdrake_function.dat.data.shape) > 2: + vdim = fdrake_function.dat.data.shape[1:] fdrake_fspace = fdrake_connection.firedrake_fspace(vdim=vdim) # Test for idempotency fd->mm->fd -- GitLab From f012bcd98a74e2cd3388e717697358bd5b1db047 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Mon, 6 Jul 2020 11:30:47 -0500 Subject: [PATCH 074/221] Added tensor + frombdy idempotency tests, combined into one test_idempotency function, minor bug fix --- meshmode/interop/firedrake/connection.py | 2 +- test/test_firedrake_interop.py | 107 ++++++++++++----------- 2 files changed, 57 insertions(+), 52 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 0501bc65..7127e730 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -368,8 +368,8 @@ class FiredrakeConnection: " to *True*") # make sure out is a firedrake function in an appropriate # function space + from firedrake.function import Function if out is not None: - from firedrake.function import Function assert isinstance(out, Function), \ ":arg:`out` must be a :mod:`firedrake` Function or *None*" assert out.function_space().ufl_element().family() \ diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 0d9891cb..b747498b 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -42,8 +42,8 @@ firedrake = pytest.importorskip("firedrake") from firedrake import ( UnitIntervalMesh, UnitSquareMesh, UnitCubeMesh, - FunctionSpace, VectorFunctionSpace, Function, - SpatialCoordinate, Constant) + FunctionSpace, VectorFunctionSpace, TensorFunctionSpace, + Function, SpatialCoordinate, Constant, as_tensor) CLOSE_ATOL = 10**-12 @@ -136,7 +136,7 @@ def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): # }}} -# {{{ Now check the FromBdyFiredrakeConnection +# {{{ Now check the FromBdyFiredrakeConnection consistency def test_from_bdy_consistency(ctx_factory, fdrake_mesh, @@ -345,71 +345,76 @@ def test_function_transfer(ctx_factory, # }}} # TODO : Add idempotency test for FromBdyFiredrakeConnection -# TODO : Add idempotency test for tensor function spaces # {{{ Idempotency tests fd->mm->fd and (fd->)mm->fd->mm for connection -def check_idempotency(fdrake_connection, fdrake_function): + +@pytest.mark.parametrize("fspace_type", ("scalar", "vector", "tensor")) +@pytest.mark.parametrize("only_convert_bdy", (False, True)) +def test_idempotency(ctx_factory, + fdrake_mesh, fdrake_family, fdrake_degree, + fspace_type, only_convert_bdy): """ Make sure fd->mm->fd and mm->fd->mm are identity """ - vdim = None - if len(fdrake_function.dat.data.shape) == 2: - vdim = fdrake_function.dat.data.shape[1] - elif len(fdrake_function.dat.data.shape) > 2: - vdim = fdrake_function.dat.data.shape[1:] - fdrake_fspace = fdrake_connection.firedrake_fspace(vdim=vdim) + # Make a function space and a function with unique values at each node + if fspace_type == "scalar": + fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fdrake_degree) + # Just use the node nr + fdrake_unique = Function(fdrake_fspace) + fdrake_unique.dat.data[:] = np.arange(fdrake_unique.dat.data.shape[0]) + elif fspace_type == "vector": + fdrake_fspace = VectorFunctionSpace(fdrake_mesh, fdrake_family, + fdrake_degree) + # use the coordinates + xx = SpatialCoordinate(fdrake_fspace.mesh()) + fdrake_unique = Function(fdrake_fspace).interpolate(xx) + elif fspace_type == "tensor": + fdrake_fspace = TensorFunctionSpace(fdrake_mesh, + fdrake_family, + fdrake_degree) + # use the coordinates, duplicated into the right tensor shape + xx = SpatialCoordinate(fdrake_fspace.mesh()) + dim = fdrake_fspace.mesh().geometric_dimension() + unique_expr = as_tensor([xx for _ in range(dim)]) + fdrake_unique = Function(fdrake_fspace).interpolate(unique_expr) + + # Make connection + cl_ctx = ctx_factory() + + # If only converting boundary, first go ahead and do one round of + # fd->mm->fd. This will zero out any degrees of freedom absent in + # the meshmode mesh (because they are not associated to cells + # with >= 1 node on the boundary) + # + # Otherwise, just continue as normal + if only_convert_bdy: + fdrake_connection = FromBdyFiredrakeConnection(cl_ctx, fdrake_fspace, + 'on_boundary') + temp = fdrake_connection.from_firedrake(fdrake_unique) + fdrake_unique = \ + fdrake_connection.from_meshmode(temp, + assert_fdrake_discontinuous=False, + continuity_tolerance=1e-8) + else: + fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) # Test for idempotency fd->mm->fd - mm_field = fdrake_connection.from_firedrake(fdrake_function) - fdrake_function_copy = Function(fdrake_fspace) - fdrake_connection.from_meshmode(mm_field, fdrake_function_copy, + mm_field = fdrake_connection.from_firedrake(fdrake_unique) + fdrake_unique_copy = Function(fdrake_fspace) + fdrake_connection.from_meshmode(mm_field, fdrake_unique_copy, assert_fdrake_discontinuous=False, continuity_tolerance=1e-8) - np.testing.assert_allclose(fdrake_function_copy.dat.data, - fdrake_function.dat.data, + np.testing.assert_allclose(fdrake_unique_copy.dat.data, + fdrake_unique.dat.data, atol=CLOSE_ATOL) # Test for idempotency (fd->)mm->fd->mm - mm_field_copy = fdrake_connection.from_firedrake(fdrake_function_copy) + mm_field_copy = fdrake_connection.from_firedrake(fdrake_unique_copy) np.testing.assert_allclose(mm_field_copy, mm_field, atol=CLOSE_ATOL) - -def test_scalar_idempotency(ctx_factory, fdrake_mesh, - fdrake_family, fdrake_degree): - """ - Make sure fd->mm->fd and mm->fd->mm are identity for scalar spaces - """ - fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fdrake_degree) - - # Make a function with unique values at each node - fdrake_unique = Function(fdrake_fspace) - fdrake_unique.dat.data[:] = np.arange(fdrake_unique.dat.data.shape[0]) - - # test idempotency - cl_ctx = ctx_factory() - fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) - check_idempotency(fdrake_connection, fdrake_unique) - - -def test_vector_idempotency(ctx_factory, fdrake_mesh, - fdrake_family, fdrake_degree): - """ - Make sure fd->mm->fd and mm->fd->mm are identity for vector spaces - """ - fdrake_vfspace = VectorFunctionSpace(fdrake_mesh, fdrake_family, fdrake_degree) - - # Make a function with unique values at each node - xx = SpatialCoordinate(fdrake_vfspace.mesh()) - fdrake_unique = Function(fdrake_vfspace).interpolate(xx) - - # test idempotency - cl_ctx = ctx_factory() - fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_vfspace) - check_idempotency(fdrake_connection, fdrake_unique) - # }}} # vim: foldmethod=marker -- GitLab From dc09101e5ccc74c93b701bfdd8c9e12829bb380d Mon Sep 17 00:00:00 2001 From: benSepanski Date: Mon, 6 Jul 2020 17:00:49 -0500 Subject: [PATCH 075/221] Update flip_matrix so that can produce arbitrary permutation --- meshmode/mesh/processing.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/meshmode/mesh/processing.py b/meshmode/mesh/processing.py index 70c17461..39fe5677 100644 --- a/meshmode/mesh/processing.py +++ b/meshmode/mesh/processing.py @@ -365,18 +365,25 @@ def test_volume_mesh_element_orientations(mesh): # {{{ flips -def get_simplex_element_flip_matrix(order, unit_nodes): +def get_simplex_element_flip_matrix(order, unit_nodes, permutation=None): """ - Generate a resampling matrix that corresponds to the - first two barycentric coordinates being swapped. + Generate a resampling matrix that corresponds to a + permutation of the barycentric coordinates being applied. + The default permutation is to swap the + first two barycentric coordinates. :param order: The order of the function space on the simplex, (see second argument in :fun:`modepy.simplex_best_available_basis`) :param unit_nodes: A np array of unit nodes with shape *(dim, nunit_nodes)* + :param permutation: Either *None*, or a tuple of shape + storing a permutation: + the *i*th barycentric coordinate gets mapped to + the *permutation[i]*th coordinate. - :return: A numpy array of shape *(dim, dim)* which, when applied + :return: A numpy array of shape *(nunit_nodes, nunit_nodes)* + which, when applied to the matrix of nodes (shaped *(dim, nunit_nodes)*) corresponds to the first two barycentric coordinates being swapped @@ -386,8 +393,11 @@ def get_simplex_element_flip_matrix(order, unit_nodes): bary_unit_nodes = unit_to_barycentric(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, :] + if permutation is None: + flipped_bary_unit_nodes[0, :] = bary_unit_nodes[1, :] + flipped_bary_unit_nodes[1, :] = bary_unit_nodes[0, :] + else: + flipped_bary_unit_nodes[:] = bary_unit_nodes[permutation, :] flipped_unit_nodes = barycentric_to_unit(flipped_bary_unit_nodes) dim = unit_nodes.shape[0] @@ -398,9 +408,10 @@ def get_simplex_element_flip_matrix(order, 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 + if permutation is None: + assert la.norm( + np.dot(flip_matrix, flip_matrix) + - np.eye(len(flip_matrix))) < 1e-13 return flip_matrix -- GitLab From aaed3638b9cfb624b3d744de28f02aad13f0d6eb Mon Sep 17 00:00:00 2001 From: benSepanski Date: Mon, 6 Jul 2020 17:01:11 -0500 Subject: [PATCH 076/221] Fix ToFiredrakeConnection to handle arbitrary local vertex nr permutations --- meshmode/interop/firedrake/__init__.py | 4 +- meshmode/interop/firedrake/connection.py | 63 ++++++++++--- meshmode/interop/firedrake/mesh.py | 114 +++++++++++++++++++---- 3 files changed, 147 insertions(+), 34 deletions(-) diff --git a/meshmode/interop/firedrake/__init__.py b/meshmode/interop/firedrake/__init__.py index 6ebacd91..7a223534 100644 --- a/meshmode/interop/firedrake/__init__.py +++ b/meshmode/interop/firedrake/__init__.py @@ -23,11 +23,11 @@ THE SOFTWARE. import numpy as np from meshmode.interop.firedrake.connection import ( - FromBdyFiredrakeConnection, FromFiredrakeConnection) + FromBdyFiredrakeConnection, FromFiredrakeConnection, ToFiredrakeConnection) from meshmode.interop.firedrake.mesh import import_firedrake_mesh __all__ = ["FromBdyFiredrakeConnection", "FromFiredrakeConnection", - "import_firedrake_mesh", + "ToFiredrakeConnection", "import_firedrake_mesh", ] diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 7127e730..9720751a 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -103,7 +103,8 @@ class FiredrakeConnection: A numpy array of shape *(self.discr.groups[group_nr].nnodes,)* whose *i*th entry is the :mod:`firedrake` node index associated - to the *i*th node in *self.discr.groups[group_nr]*. + to the *i*th node in *self.discr.groups[group_nr]* + (where *i* is the group-local node index). It is important to note that, due to :mod:`meshmode` and :mod:`firedrake` using different unit nodes, a :mod:`firedrake` node associated to a :mod:`meshmode` may have different coordinates. @@ -282,10 +283,16 @@ class FiredrakeConnection: :arg function: A :mod:`firedrake` function to transfer onto :attr:`discr`. Its function space must have the same family, degree, and mesh as ``self.from_fspace()``. - :arg out: If *None* then ignored, otherwise a numpy array of the - shape (i.e. + :arg out: Either *None* or a numpy array of + shape *(..., num meshmode nodes)* or *(num meshmode nodes,)* and of the - same dtype in which *function*'s transported data will be stored + same dtype as *function*. + *function*'s transported data will be stored in *out* + and *out* will be returned. + Note that number of nodes referenced here is + the number of nodes in the whole discretization. + If *out* is *None*, then a numpy array of the correct size + filled with zeros is created to take its place. :return: a numpy array holding the transported function """ @@ -309,21 +316,23 @@ class FiredrakeConnection: # Check that out is supplied correctly, or create out if it is # not supplied - shape = (self.discr.groups[self.group_nr].nnodes,) + shape = (self.discr.nnodes,) if len(function_data.shape) > 1: shape = function_data.shape[1:] + shape if out is not None: if not isinstance(out, np.ndarray): raise TypeError(":param:`out` must of type *np.ndarray* or " "be *None*") - assert out.shape == shape, \ - ":param:`out` must have shape %s." % shape - assert out.dtype == function.dat.data.dtype + assert out.shape == shape, \ + ":param:`out` must have shape %s." % shape + assert out.dtype == function.dat.data.dtype else: - out = np.ndarray(shape, dtype=function_data.dtype) + out = np.zeros(shape, dtype=function_data.dtype) # Reorder nodes - out[:] = np.moveaxis(function_data, 0, -1)[..., self.mm2fd_node_mapping] + group = self.discr.groups[self.group_nr] + out[..., group.node_nr_base:group.nnodes] = \ + np.moveaxis(function_data, 0, -1)[..., self.mm2fd_node_mapping] # Resample at the appropriate nodes out_view = self.discr.groups[self.group_nr].view(out) np.matmul(out_view, self._resampling_mat_fd2mm.T, out=out_view) @@ -342,7 +351,10 @@ class FiredrakeConnection: are not modified. :arg mm_field: A numpy array of shape *(nnodes,)* or *(..., nnodes)* - representing a function on :attr:`to_distr`. + representing a function on :attr:`to_distr` + (where nnodes is the number of nodes in *self.discr*. Note + that only data from group number *self.group_nr* will be + transported). :arg out: If *None* then ignored, otherwise a :mod:`firedrake` function of the right function space for the transported data to be stored in. @@ -632,11 +644,36 @@ class ToFiredrakeConnection(FiredrakeConnection): el_group = discr.groups[group_nr] from firedrake.functionspace import FunctionSpace - fd_mesh = export_mesh_to_firedrake(discr.mesh, group_nr, comm) + fd_mesh, fd_cell_order, perm2cells = \ + export_mesh_to_firedrake(discr.mesh, group_nr, comm) fspace = FunctionSpace(fd_mesh, 'DG', el_group.order) + # To get the meshmode to firedrake node assocation, we need to handle + # local vertex reordering and cell reordering. + # + # Get a copy of cell_node_list with local vertex reordering undone + reordered_cell_node_list = np.copy(fspace.cell_node_list) + for perm, cells in six.iteritems(perm2cells): + perm_inv = tuple(np.argsort(perm)) + flip_mat = get_simplex_element_flip_matrix(el_group.order, + el_group.unit_nodes, + perm_inv) + reordered_cell_node_list[cells] = \ + np.einsum("ij,jk->ik", + fspace.cell_node_list[cells], + np.rint(flip_mat)) + + # making reordering_arr using the discr then assign using a view of it + reordering_arr = np.arange(discr.nnodes, + dtype=fspace.cell_node_list.dtype) + by_cell_view = el_group.view(reordering_arr) + by_cell_view = reordered_cell_node_list[fd_cell_order] # noqa : F841 + # we only want to keep the part relevant to el_group, the rest of the + # array was just there so we could use *el_group.view* + reordering_arr = reordering_arr[el_group.node_nr_base:el_group.nnodes] + super(ToFiredrakeConnection, self).__init__(discr, fspace, - np.arange(el_group.nnodes), + reordering_arr, group_nr=group_nr) # }}} diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 59a72ac4..928ba1ba 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -659,13 +659,36 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): :class:`SimplexElementGroup`. :param mesh: A :class:`meshmode.mesh.Mesh` to convert with - at least one :class:`SimplexElementGroup` + at least one :class:`SimplexElementGroup`. :param group_nr: The group number to be converted into a firedrake mesh. The corresponding group must be of type :class:`SimplexElementGroup`. If *None* and *mesh* has only one group, that group is used. Otherwise, a *ValueError* is raised. :param comm: The communicator to build the dmplex mesh on + + :return: A tuple *(fdrake_mesh, fdrake_cell_ordering, perm2cell)* + where + + * *fdrake_mesh* is a :mod:`firedrake` + :class:`firedrake.mesh.MeshGeometry` corresponding to + *mesh* + * *fdrake_cell_ordering* is a numpy array: the *i*th + element in *mesh* (i.e. the *i*th element in + *mesh.groups[group_nr].vertex_indices*) corresponds to the + *fdrake_cell_ordering[i]*th :mod:`firedrake` cell + * *perm2cell* is a dictionary, mapping tuples to + lists of meshmode element indices. Each meshmode element index + appears in exactly one of these lists. The corresponding + tuple describes how firedrake reordered the local vertex + indices on that cell. In particular, if *c* + is in the list *perm2cell[p]* for a tuple *p*, then + the *p[i]*th local vertex of the *fdrake_cell_ordering[c]*th + firedrake cell corresponds to the *i*th local vertex + of the *c*th meshmode element. + + .. warning:: + Currently, no boundary tags are exported along with the mesh. """ if not isinstance(mesh, Mesh): raise TypeError(":arg:`mesh` must of type :class:`meshmode.mesh.Mesh`," @@ -680,30 +703,70 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): assert isinstance(mesh.groups[group_nr], SimplexElementGroup) assert mesh.vertices is not None - # Get a dmplex object and then a mesh topology + # Get the vertices and vertex indices of the requested group group = mesh.groups[group_nr] - mm2fd_indices = np.unique(group.vertex_indices.flatten()) - coords = mesh.vertices[:, mm2fd_indices].T - cells = mm2fd_indices[group.vertex_indices] + fd2mm_indices = np.unique(group.vertex_indices.flatten()) + coords = mesh.vertices[:, fd2mm_indices].T + mm2fd_indices = dict(zip(fd2mm_indices, np.arange(np.size(fd2mm_indices)))) + cells = np.vectorize(mm2fd_indices.__getitem__)(group.vertex_indices) + # Get a dmplex object and then a mesh topology if comm is None: from pyop2.mpi import COMM_WORLD comm = COMM_WORLD - # FIXME : this is a private member... + # FIXME : this is a private member..., and there are some below import firedrake.mesh as fd_mesh plex = fd_mesh._from_cell_list(group.dim, cells, coords, comm) - - # TODO : Allow reordering? This makes the connection slower - # but (in principle) firedrake is reordering nodes - # to speed up cache access for their computations. - # Users might want to be able to choose whether - # or not to reorder based on if caching/conversion - # is bottle-necking - topology = fd_mesh.Mesh(plex, reorder=False) + # Nb : One might be tempted to pass reorder=False and thereby save some + # hassle in exchange for forcing firedrake to have slightly + # less efficient caching. Unfortunately, that only prevents + # the cells from being reordered, and does not prevent the + # vertices from being (locally) reordered on each cell... + # the tl;dr is we don't actually save any hassle + top = fd_mesh.Mesh(plex, dim=mesh.ambient_dim) # mesh topology + top.init() + + # Get new element ordering: + c_start, c_end = top._topology_dm.getHeightStratum(0) + cell_index_mm2fd = np.vectorize(top._cell_numbering.getOffset)( + np.arange(c_start, c_end)) + v_start, v_end = top._topology_dm.getDepthStratum(0) + + # Firedrake goes crazy reordering local vertex numbers, + # we've got to work to figure out what changes they made. + # + # *perm2cells* will map each permutation of local vertex numbers to + # a list of the meshmode cells to which that permutation + # has been applied + perm2cells = {} + for mm_cell_id, dmp_ids in enumerate(top.cell_closure[cell_index_mm2fd]): + # look at order of vertices in firedrake cell + vert_dmp_ids = dmp_ids[np.logical_and(v_start <= dmp_ids, dmp_ids < v_end)] + fdrake_order = vert_dmp_ids - v_start + # get original order + mm_order = mesh.groups[group_nr].vertex_indices[mm_cell_id] + # want permutation p so that mm_order[p] = fdrake_order + # To do so, look at permutations acting by composition. + # + # mm_order \circ argsort(mm_order) = + # fdrake_order \circ argsort(fdrake_order) + # so + # mm_order \circ argsort(mm_order) \circ inv(argsort(fdrake_order)) + # = fdrake_order + # + # argsort acts as an inverse, so the desired permutation is: + perm = tuple(np.argsort(mm_order)[np.argsort(np.argsort(fdrake_order))]) + perm2cells.setdefault(perm, []) + perm2cells[perm].append(mm_cell_id) # Now make a coordinates function from firedrake import VectorFunctionSpace, Function - coords_fspace = VectorFunctionSpace(topology, 'CG', group.order, + if mesh.is_conforming: + family = 'CG' + else: + warn("Non-conforming meshes are untested,... I think they should work") + family = 'DG' + coords_fspace = VectorFunctionSpace(top, family, group.order, dim=mesh.ambient_dim) coords = Function(coords_fspace) @@ -719,11 +782,24 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): resampling_mat = resampling_matrix(el_group.basis(), new_nodes=fd_unit_nodes, old_nodes=group.unit_nodes) - # nodes is shaped *(ambient dim, nelements, nunit nodes) - coords.dat.data[coords_fspace.cell_node_list, :] = \ - np.matmul(group.nodes, resampling_mat.T).transpose((1, 2, 0)) - return fd_mesh.Mesh(coords, reorder=False) + # nodes is shaped *(ambient dim, nelements, nunit nodes)* + # flip nodes + from meshmode.mesh.processing import get_simplex_element_flip_matrix + flipped_group_nodes = np.copy(group.nodes) + for perm, cells in six.iteritems(perm2cells): + flip_mat = get_simplex_element_flip_matrix(group.order, + group.unit_nodes, + perm) + flipped_group_nodes[:, cells, :] = \ + np.einsum("ijk,ke->ije", group.nodes[:, cells, :], flip_mat) + + # reorder to firedrake cell ordering + reordered_cell_node_list = coords_fspace.cell_node_list[cell_index_mm2fd] + coords.dat.data[reordered_cell_node_list, :] = \ + np.matmul(flipped_group_nodes, resampling_mat.T).transpose((1, 2, 0)) + + return fd_mesh.Mesh(coords), cell_index_mm2fd, perm2cells # }}} -- GitLab From 335b70e014af2346da8c39f6006f044ba8322c9d Mon Sep 17 00:00:00 2001 From: benSepanski Date: Mon, 6 Jul 2020 17:01:29 -0500 Subject: [PATCH 077/221] Remove extra prints, add tests, add some TODOs --- test/test_firedrake_interop.py | 91 +++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 34 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index b747498b..2e50365f 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -27,9 +27,15 @@ from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) +from meshmode.discretization import Discretization +from meshmode.discretization.poly_element import ( + InterpolatoryQuadratureSimplexGroupFactory) + from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL, check_bc_coverage + from meshmode.interop.firedrake import ( - FromFiredrakeConnection, FromBdyFiredrakeConnection, import_firedrake_mesh) + FromFiredrakeConnection, FromBdyFiredrakeConnection, ToFiredrakeConnection, + import_firedrake_mesh) from meshmode.interop.firedrake.connection import _compute_cells_near_bdy import pytest @@ -49,6 +55,16 @@ from firedrake import ( CLOSE_ATOL = 10**-12 +@pytest.fixture(params=["annulus.msh", + "blob2d-order1-h4e-2.msh", + "blob2d-order1-h6e-2.msh", + "blob2d-order1-h8e-2.msh", + ]) +def mm_mesh(request): + from meshmode.mesh.io import read_gmsh + return read_gmsh(request.param) + + @pytest.fixture(params=["FiredrakeUnitIntervalMesh", "FiredrakeUnitSquareMesh", "FiredrakeUnitCubeMesh", @@ -80,19 +96,20 @@ def fdrake_family(request): @pytest.fixture(params=[1, 2, 3], ids=["P^1", "P^2", "P^3"]) -def fdrake_degree(request): +def fspace_degree(request): return request.param # {{{ Basic conversion checks for the function space -def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): +def check_consistency(fdrake_fspace, discr, group_nr=0): """ While nodes may change, vertex conversion should be *identical* up to reordering, ensure this is the case for DG spaces. Also ensure the meshes have the same basic properties and the function space/discretization agree across firedrake vs meshmode """ + fdrake_mesh = fdrake_fspace.mesh() # get fdrake_verts (shaped like (nverts, dim)) # Nb : Mesh must be order 1 for these to be vertices assert fdrake_mesh.coordinates.function_space().finat_element.degree == 1 @@ -100,11 +117,6 @@ def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): if fdrake_mesh.geometric_dimension() == 1: fdrake_verts = fdrake_verts[:, np.newaxis] - # Get meshmode vertices (shaped like (dim, nverts)) - fdrake_fspace = FunctionSpace(fdrake_mesh, 'DG', fdrake_degree) - cl_ctx = ctx_factory() - fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) - discr = fdrake_connection.discr meshmode_verts = discr.mesh.vertices # Ensure the meshmode mesh has one group and make sure both @@ -112,9 +124,9 @@ def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): assert len(discr.mesh.groups) == 1 fdrake_mesh_fspace = fdrake_mesh.coordinates.function_space() fdrake_mesh_order = fdrake_mesh_fspace.finat_element.degree - assert discr.mesh.groups[0].dim == fdrake_mesh.topological_dimension() - assert discr.mesh.groups[0].order == fdrake_mesh_order - assert discr.mesh.groups[0].nelements == fdrake_mesh.num_cells() + assert discr.mesh.groups[group_nr].dim == fdrake_mesh.topological_dimension() + assert discr.mesh.groups[group_nr].order == fdrake_mesh_order + assert discr.mesh.groups[group_nr].nelements == fdrake_mesh.num_cells() assert discr.mesh.nvertices == fdrake_mesh.num_vertices() # Ensure that the vertex sets are identical up to reordering @@ -128,11 +140,33 @@ def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): # some basic properties finat_elt = fdrake_fspace.finat_element assert len(discr.groups) == 1 - assert discr.groups[0].order == finat_elt.degree - assert discr.groups[0].nunit_nodes == finat_elt.space_dimension() + assert discr.groups[group_nr].order == finat_elt.degree + assert discr.groups[group_nr].nunit_nodes == finat_elt.space_dimension() assert discr.nnodes == fdrake_fspace.node_count +def test_fd2mm_consistency(ctx_factory, fdrake_mesh, fspace_degree): + """ + Check basic consistency with a FromFiredrakeConnection + """ + # make discretization from firedrake + fdrake_fspace = FunctionSpace(fdrake_mesh, 'DG', fspace_degree) + cl_ctx = ctx_factory() + fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) + discr = fdrake_connection.discr + # Check consistency + check_consistency(fdrake_fspace, discr) + + +def test_mm2fd_consistency(ctx_factory, mm_mesh, fspace_degree): + cl_ctx = ctx_factory() + factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) + discr = Discretization(cl_ctx, mm_mesh, factory) + fdrake_connection = ToFiredrakeConnection(discr) + fdrake_fspace = fdrake_connection.firedrake_fspace() + # Check consistency + check_consistency(fdrake_fspace, discr) + # }}} @@ -141,13 +175,13 @@ def test_discretization_consistency(ctx_factory, fdrake_mesh, fdrake_degree): def test_from_bdy_consistency(ctx_factory, fdrake_mesh, fdrake_family, - fdrake_degree): + fspace_degree): """ Make basic checks that FiredrakeFromBdyConnection is not doing something obviouisly wrong, i.e. that it has proper tagging, that it has the right number of cells, etc. """ - fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fdrake_degree) + fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fspace_degree) cl_ctx = ctx_factory() frombdy_conn = FromBdyFiredrakeConnection(cl_ctx, fdrake_fspace, @@ -243,12 +277,6 @@ def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values, face_vert_indices = el_vert_indices[face_vertex_indices[ifac], ] # shape: *(ambient dim, num vertices on face)* face_verts = mm_mesh.vertices[:, face_vert_indices] - print("Face vertex indices=", face_vertex_indices) - print("iel=", iel) - print("ifac=", ifac) - print("el verts =", mm_mesh.groups[0].vertex_indices[iel]) - print("orient=", orient[iel]) - print() # Figure out which coordinate should have a fixed value, and what # that value is. Also, count how many times each boundary tag appears coord_index, val = None, None @@ -259,10 +287,6 @@ def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values, coord_index = coord_indices[bdy_id_index] val = coord_values[bdy_id_index] break - print('vert indices =', face_vert_indices) - print("Verts=", face_verts) - print("val=", val) - print("coord=", coord_index) assert np.max(np.abs(face_verts[coord_index, :] - val)) < CLOSE_ATOL # Verify that the number of meshes tagged with a boundary tag @@ -276,6 +300,8 @@ def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values, # }}} +# TODO : Add function transfer test for ToFiredrakeConnection +# TODO : Add idempotency test for ToFiredrakeConnection # {{{ Double check functions are being transported correctly def alternating_sum_fd(spatial_coord): @@ -313,7 +339,7 @@ test_functions = [ @pytest.mark.parametrize("fdrake_f_expr,meshmode_f_eval", test_functions) @pytest.mark.parametrize("only_convert_bdy", (False, True)) def test_function_transfer(ctx_factory, - fdrake_mesh, fdrake_family, fdrake_degree, + fdrake_mesh, fdrake_family, fspace_degree, fdrake_f_expr, meshmode_f_eval, only_convert_bdy): """ @@ -321,7 +347,7 @@ def test_function_transfer(ctx_factory, (up to resampling error) as creating a function on the transported mesh """ - fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fdrake_degree) + fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fspace_degree) spatial_coord = SpatialCoordinate(fdrake_mesh) fdrake_f = Function(fdrake_fspace).interpolate(fdrake_f_expr(spatial_coord)) @@ -344,36 +370,33 @@ def test_function_transfer(ctx_factory, # }}} -# TODO : Add idempotency test for FromBdyFiredrakeConnection - # {{{ Idempotency tests fd->mm->fd and (fd->)mm->fd->mm for connection - @pytest.mark.parametrize("fspace_type", ("scalar", "vector", "tensor")) @pytest.mark.parametrize("only_convert_bdy", (False, True)) def test_idempotency(ctx_factory, - fdrake_mesh, fdrake_family, fdrake_degree, + fdrake_mesh, fdrake_family, fspace_degree, fspace_type, only_convert_bdy): """ Make sure fd->mm->fd and mm->fd->mm are identity """ # Make a function space and a function with unique values at each node if fspace_type == "scalar": - fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fdrake_degree) + fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fspace_degree) # Just use the node nr fdrake_unique = Function(fdrake_fspace) fdrake_unique.dat.data[:] = np.arange(fdrake_unique.dat.data.shape[0]) elif fspace_type == "vector": fdrake_fspace = VectorFunctionSpace(fdrake_mesh, fdrake_family, - fdrake_degree) + fspace_degree) # use the coordinates xx = SpatialCoordinate(fdrake_fspace.mesh()) fdrake_unique = Function(fdrake_fspace).interpolate(xx) elif fspace_type == "tensor": fdrake_fspace = TensorFunctionSpace(fdrake_mesh, fdrake_family, - fdrake_degree) + fspace_degree) # use the coordinates, duplicated into the right tensor shape xx = SpatialCoordinate(fdrake_fspace.mesh()) dim = fdrake_fspace.mesh().geometric_dimension() -- GitLab From afa2a4a70f95c3cf2300e8b2095831a1af049dd5 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 7 Jul 2020 10:34:26 -0500 Subject: [PATCH 078/221] Fixed ToFiredrakeConnection to use permutation based on fd unit nodes, not mm --- meshmode/interop/firedrake/connection.py | 51 +++++++++++++++--------- meshmode/interop/firedrake/mesh.py | 17 ++++---- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 9720751a..e0de6fc9 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -53,7 +53,7 @@ def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): :arg orient: An array of shape *(nelements)* of orientations, >0 for positive, <0 for negative - :arg nodes: a *(nelements, nunit_nodes)* or shaped array of nodes + :arg nodes: a *(nelements, nunit_nodes)* shaped array of nodes :arg flip_matrix: The matrix used to flip each negatively-oriented element :arg unflip: If *True*, use transpose of *flip_matrix* to @@ -647,29 +647,42 @@ class ToFiredrakeConnection(FiredrakeConnection): fd_mesh, fd_cell_order, perm2cells = \ export_mesh_to_firedrake(discr.mesh, group_nr, comm) fspace = FunctionSpace(fd_mesh, 'DG', el_group.order) + # get firedrake unit nodes and map onto meshmode reference element + dim = fspace.mesh().topological_dimension() + fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(dim, True) + fd_unit_nodes = get_finat_element_unit_nodes(fspace.finat_element) + fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) + # To get the meshmode to firedrake node assocation, we need to handle # local vertex reordering and cell reordering. # - # Get a copy of cell_node_list with local vertex reordering undone - reordered_cell_node_list = np.copy(fspace.cell_node_list) + # **_cell_node holds the node nrs in shape *(ncells, nunit_nodes)* + fd_cell_node = fspace.cell_node_list + mm_cell_node = el_group.view(np.arange(discr.nnodes)) + reordering_arr = np.arange(el_group.nnodes, dtype=fd_cell_node.dtype) for perm, cells in six.iteritems(perm2cells): - perm_inv = tuple(np.argsort(perm)) + # reordering_arr[i] should be the fd node corresponding to meshmode + # node i + # + # The jth meshmode cell corresponds to the fd_cell_order[j]th + # firedrake cell. If *nodeperm* is the permutation of local nodes + # applied to the *j*th meshmode cell, the firedrake node + # fd_cell_node[fd_cell_order[j]][k] corresponds to the + # mm_cell_node[j, nodeperm[k]]th meshmode node. + # + # Note that the permutation on the unit nodes may not be the + # same as the permutation on the barycentric coordinates (*perm*). + # Importantly, the permutation is derived from getting a flip + # matrix from the Firedrake unit nodes, not necessarily the meshmode + # unit nodes + # flip_mat = get_simplex_element_flip_matrix(el_group.order, - el_group.unit_nodes, - perm_inv) - reordered_cell_node_list[cells] = \ - np.einsum("ij,jk->ik", - fspace.cell_node_list[cells], - np.rint(flip_mat)) - - # making reordering_arr using the discr then assign using a view of it - reordering_arr = np.arange(discr.nnodes, - dtype=fspace.cell_node_list.dtype) - by_cell_view = el_group.view(reordering_arr) - by_cell_view = reordered_cell_node_list[fd_cell_order] # noqa : F841 - # we only want to keep the part relevant to el_group, the rest of the - # array was just there so we could use *el_group.view* - reordering_arr = reordering_arr[el_group.node_nr_base:el_group.nnodes] + fd_unit_nodes, + np.argsort(perm)) + flip_mat = np.rint(flip_mat).astype(np.int32) + fd_permuted_cell_node = np.matmul(fd_cell_node[fd_cell_order[cells]], + flip_mat.T) + reordering_arr[mm_cell_node[cells]] = fd_permuted_cell_node super(ToFiredrakeConnection, self).__init__(discr, fspace, diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 928ba1ba..25304e8f 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -75,14 +75,14 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology, cells_to_use=None): # If you don't understand dmplex, look at the PETSc reference # here: https://cse.buffalo.edu/~knepley/classes/caam519/CSBook.pdf # used to get topology info - # FIXME... not sure how to get around the private access + # FIXME : not sure how to get around the private access top_dm = top._topology_dm # Get range of dmplex ids for cells, facets, and vertices f_start, f_end = top_dm.getHeightStratum(1) v_start, v_end = top_dm.getDepthStratum(0) - # FIXME... not sure how to get around the private accesses + # FIXME : not sure how to get around the private accesses # Maps dmplex vert id -> firedrake vert index vert_id_dmp_to_fd = top._vertex_numbering.getOffset @@ -678,8 +678,9 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): *mesh.groups[group_nr].vertex_indices*) corresponds to the *fdrake_cell_ordering[i]*th :mod:`firedrake` cell * *perm2cell* is a dictionary, mapping tuples to - lists of meshmode element indices. Each meshmode element index - appears in exactly one of these lists. The corresponding + 1-D numpy arrays of meshmode element indices. + Each meshmode element index + appears in exactly one of these arrays. The corresponding tuple describes how firedrake reordered the local vertex indices on that cell. In particular, if *c* is in the list *perm2cell[p]* for a tuple *p*, then @@ -714,7 +715,7 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): if comm is None: from pyop2.mpi import COMM_WORLD comm = COMM_WORLD - # FIXME : this is a private member..., and there are some below + # FIXME : not sure how to get around the private accesses import firedrake.mesh as fd_mesh plex = fd_mesh._from_cell_list(group.dim, cells, coords, comm) # Nb : One might be tempted to pass reorder=False and thereby save some @@ -758,6 +759,8 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): perm = tuple(np.argsort(mm_order)[np.argsort(np.argsort(fdrake_order))]) perm2cells.setdefault(perm, []) perm2cells[perm].append(mm_cell_id) + perm2cells = {perm: np.array(cells) + for perm, cells in six.iteritems(perm2cells)} # Now make a coordinates function from firedrake import VectorFunctionSpace, Function @@ -791,8 +794,8 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): flip_mat = get_simplex_element_flip_matrix(group.order, group.unit_nodes, perm) - flipped_group_nodes[:, cells, :] = \ - np.einsum("ijk,ke->ije", group.nodes[:, cells, :], flip_mat) + flipped_group_nodes[:, cells, :] = np.matmul(group.nodes[:, cells, :], + flip_mat.T) # reorder to firedrake cell ordering reordered_cell_node_list = coords_fspace.cell_node_list[cell_index_mm2fd] -- GitLab From 4bbddf805588120e120b3856d643553ac696a620 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 7 Jul 2020 10:35:33 -0500 Subject: [PATCH 079/221] flipped_bary_unit should be the one indexed by permutation --- meshmode/mesh/processing.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/meshmode/mesh/processing.py b/meshmode/mesh/processing.py index 39fe5677..96a948df 100644 --- a/meshmode/mesh/processing.py +++ b/meshmode/mesh/processing.py @@ -383,10 +383,9 @@ def get_simplex_element_flip_matrix(order, unit_nodes, permutation=None): the *permutation[i]*th coordinate. :return: A numpy array of shape *(nunit_nodes, nunit_nodes)* - which, when applied - to the matrix of nodes (shaped *(dim, nunit_nodes)*) - corresponds to the first two barycentric coordinates - being swapped + which, when its transpose is right-applied + to the matrix of nodes (shaped *(dim, nunit_nodes)*), + corresponds to the permutation being applied """ from modepy.tools import barycentric_to_unit, unit_to_barycentric @@ -397,7 +396,7 @@ def get_simplex_element_flip_matrix(order, unit_nodes, permutation=None): flipped_bary_unit_nodes[0, :] = bary_unit_nodes[1, :] flipped_bary_unit_nodes[1, :] = bary_unit_nodes[0, :] else: - flipped_bary_unit_nodes[:] = bary_unit_nodes[permutation, :] + flipped_bary_unit_nodes[permutation, :] = bary_unit_nodes flipped_unit_nodes = barycentric_to_unit(flipped_bary_unit_nodes) dim = unit_nodes.shape[0] -- GitLab From 6f20676fae0c15f23e1c3a45405b2d664fd9e74a Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 7 Jul 2020 10:36:43 -0500 Subject: [PATCH 080/221] Added function transfer test for ToFiredrakeConnection --- test/test_firedrake_interop.py | 58 ++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 2e50365f..57c62723 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -300,8 +300,8 @@ def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values, # }}} -# TODO : Add function transfer test for ToFiredrakeConnection # TODO : Add idempotency test for ToFiredrakeConnection +# TODO : Add test for ToFiredrakeConnection where group_nr != 0 # {{{ Double check functions are being transported correctly def alternating_sum_fd(spatial_coord): @@ -338,20 +338,22 @@ test_functions = [ @pytest.mark.parametrize("fdrake_f_expr,meshmode_f_eval", test_functions) @pytest.mark.parametrize("only_convert_bdy", (False, True)) -def test_function_transfer(ctx_factory, - fdrake_mesh, fdrake_family, fspace_degree, - fdrake_f_expr, meshmode_f_eval, - only_convert_bdy): +def test_from_fd_transfer(ctx_factory, + fdrake_mesh, fdrake_family, fspace_degree, + fdrake_f_expr, meshmode_f_eval, + only_convert_bdy): """ Make sure creating a function then transporting it is the same (up to resampling error) as creating a function on the transported mesh """ + # make function space and function fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fspace_degree) spatial_coord = SpatialCoordinate(fdrake_mesh) fdrake_f = Function(fdrake_fspace).interpolate(fdrake_f_expr(spatial_coord)) + # build connection cl_ctx = ctx_factory() if only_convert_bdy: fdrake_connection = FromBdyFiredrakeConnection(cl_ctx, fdrake_fspace, @@ -359,14 +361,56 @@ def test_function_transfer(ctx_factory, else: fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) - transported_f = fdrake_connection.from_firedrake(fdrake_f) + # transport fdrake function + fd2mm_f = fdrake_connection.from_firedrake(fdrake_f) + # build same function in meshmode discr = fdrake_connection.discr with cl.CommandQueue(cl_ctx) as queue: nodes = discr.nodes().get(queue=queue) meshmode_f = meshmode_f_eval(nodes) - np.testing.assert_allclose(transported_f, meshmode_f, atol=CLOSE_ATOL) + # fd -> mm should be same as creating in meshmode + np.testing.assert_allclose(fd2mm_f, meshmode_f, atol=CLOSE_ATOL) + + if not only_convert_bdy: + # now transport mm -> fd + mm2fd_f = \ + fdrake_connection.from_meshmode(meshmode_f, + assert_fdrake_discontinuous=False, + continuity_tolerance=1e-8) + # mm -> fd should be same as creating in firedrake + np.testing.assert_allclose(fdrake_f.dat.data, mm2fd_f.dat.data, + atol=CLOSE_ATOL) + + +@pytest.mark.parametrize("fdrake_f_expr,meshmode_f_eval", test_functions) +def test_to_fd_transfer(ctx_factory, mm_mesh, fspace_degree, + fdrake_f_expr, meshmode_f_eval): + """ + Make sure creating a function then transporting it is the same + (up to resampling error) as creating a function on the transported + mesh + """ + # Make discr and evaluate function in meshmode + cl_ctx = ctx_factory() + factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) + discr = Discretization(cl_ctx, mm_mesh, factory) + + with cl.CommandQueue(cl_ctx) as queue: + nodes = discr.nodes().get(queue=queue) + meshmode_f = meshmode_f_eval(nodes) + + # connect to firedrake and evaluate expr in firedrake + fdrake_connection = ToFiredrakeConnection(discr) + fdrake_fspace = fdrake_connection.firedrake_fspace() + spatial_coord = SpatialCoordinate(fdrake_fspace.mesh()) + fdrake_f = Function(fdrake_fspace).interpolate(fdrake_f_expr(spatial_coord)) + + # transport to firedrake and make sure this is the same + mm2fd_f = fdrake_connection.from_meshmode(meshmode_f) + np.testing.assert_allclose(mm2fd_f.dat.data, fdrake_f.dat.data, + atol=CLOSE_ATOL) # }}} -- GitLab From 2b0d8662890330b90afebdf1cf0bd853d24d6805 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 7 Jul 2020 10:45:08 -0500 Subject: [PATCH 081/221] Added ToFiredrakeConnection idempotency tests --- test/test_firedrake_interop.py | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 57c62723..2e8b8ec3 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -300,7 +300,6 @@ def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values, # }}} -# TODO : Add idempotency test for ToFiredrakeConnection # TODO : Add test for ToFiredrakeConnection where group_nr != 0 # {{{ Double check functions are being transported correctly @@ -419,11 +418,11 @@ def test_to_fd_transfer(ctx_factory, mm_mesh, fspace_degree, @pytest.mark.parametrize("fspace_type", ("scalar", "vector", "tensor")) @pytest.mark.parametrize("only_convert_bdy", (False, True)) -def test_idempotency(ctx_factory, - fdrake_mesh, fdrake_family, fspace_degree, - fspace_type, only_convert_bdy): +def test_from_fd_idempotency(ctx_factory, + fdrake_mesh, fdrake_family, fspace_degree, + fspace_type, only_convert_bdy): """ - Make sure fd->mm->fd and mm->fd->mm are identity + Make sure fd->mm->fd and (fd->)->mm->fd->mm are identity """ # Make a function space and a function with unique values at each node if fspace_type == "scalar": @@ -482,6 +481,31 @@ def test_idempotency(ctx_factory, mm_field_copy = fdrake_connection.from_firedrake(fdrake_unique_copy) np.testing.assert_allclose(mm_field_copy, mm_field, atol=CLOSE_ATOL) + +def test_to_fd_idempotency(ctx_factory, mm_mesh, fspace_degree): + """ + Make sure mm->fd->mm and (mm->)->fd->mm->fd are identity + """ + # Make a function space and a function with unique values at each node + cl_ctx = ctx_factory() + factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) + discr = Discretization(cl_ctx, mm_mesh, factory) + fdrake_connection = ToFiredrakeConnection(discr) + mm_unique = np.arange(discr.nnodes, dtype=np.float64) + mm_unique_copy = np.copy(mm_unique) + + # Test for idempotency mm->fd->mm + fdrake_unique = fdrake_connection.from_meshmode(mm_unique) + fdrake_connection.from_firedrake(fdrake_unique, mm_unique_copy) + + np.testing.assert_allclose(mm_unique_copy, mm_unique, atol=CLOSE_ATOL) + + # Test for idempotency (mm->)fd->mm->fd + fdrake_unique_copy = fdrake_connection.from_meshmode(mm_unique_copy) + np.testing.assert_allclose(fdrake_unique_copy.dat.data, + fdrake_unique.dat.data, + atol=CLOSE_ATOL) + # }}} # vim: foldmethod=marker -- GitLab From 210f96090865b53762e400eda20e386de5b45393 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 7 Jul 2020 10:51:05 -0500 Subject: [PATCH 082/221] Some doc updates --- meshmode/interop/firedrake/mesh.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 25304e8f..6e425ba6 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -652,7 +652,6 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, # {{{ Mesh exporting to firedrake -# TODO : Keep boundary tagging def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): """ Create a firedrake mesh corresponding to one :class:`Mesh`'s @@ -689,7 +688,9 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): of the *c*th meshmode element. .. warning:: - Currently, no boundary tags are exported along with the mesh. + Currently, no custom boundary tags are exported along with the mesh. + :mod:`firedrake` seems to only allow one marker on each facet, whereas + :mod:`meshmode` allows many. """ if not isinstance(mesh, Mesh): raise TypeError(":arg:`mesh` must of type :class:`meshmode.mesh.Mesh`," -- GitLab From b5d50231fb2a9953ce48a1558fd048382d1e7407 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 7 Jul 2020 11:20:15 -0500 Subject: [PATCH 083/221] Removed duplicate function from __init__.py --- meshmode/interop/firedrake/__init__.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/meshmode/interop/firedrake/__init__.py b/meshmode/interop/firedrake/__init__.py index 7a223534..1ad51ae1 100644 --- a/meshmode/interop/firedrake/__init__.py +++ b/meshmode/interop/firedrake/__init__.py @@ -21,7 +21,6 @@ THE SOFTWARE. """ -import numpy as np from meshmode.interop.firedrake.connection import ( FromBdyFiredrakeConnection, FromFiredrakeConnection, ToFiredrakeConnection) from meshmode.interop.firedrake.mesh import import_firedrake_mesh @@ -29,18 +28,3 @@ from meshmode.interop.firedrake.mesh import import_firedrake_mesh __all__ = ["FromBdyFiredrakeConnection", "FromFiredrakeConnection", "ToFiredrakeConnection", "import_firedrake_mesh", ] - - -def _compute_cells_near_bdy(mesh, bdy_id): - """ - Returns an array of the cell ids with >= 1 vertex on the - given bdy_id - """ - cfspace = mesh.coordinates.function_space() - cell_node_list = cfspace.cell_node_list - - boundary_nodes = cfspace.boundary_nodes(bdy_id, 'topological') - # Reduce along each cell: Is a vertex of the cell in boundary nodes? - cell_is_near_bdy = np.any(np.isin(cell_node_list, boundary_nodes), axis=1) - - return np.arange(cell_node_list.shape[0], dtype=np.int32)[cell_is_near_bdy] -- GitLab From 8c2da5f81d2267fb07728211dce31fc285ead5e9 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 7 Jul 2020 11:20:32 -0500 Subject: [PATCH 084/221] np.int32 -> pyop2.datatypes.IntType to handle possibility of int64 --- meshmode/interop/firedrake/connection.py | 14 ++++++++------ meshmode/interop/firedrake/mesh.py | 19 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index e0de6fc9..8ce7a4e2 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -541,7 +541,8 @@ def _compute_cells_near_bdy(mesh, bdy_id): # Reduce along each cell: Is a vertex of the cell in boundary nodes? cell_is_near_bdy = np.any(np.isin(cell_node_list, boundary_nodes), axis=1) - return np.nonzero(cell_is_near_bdy)[0].astype(np.int32) + from pyop2.datatypes import IntType + return np.nonzero(cell_is_near_bdy)[0].astype(IntType) class FromBdyFiredrakeConnection(FiredrakeConnection): @@ -653,13 +654,14 @@ class ToFiredrakeConnection(FiredrakeConnection): fd_unit_nodes = get_finat_element_unit_nodes(fspace.finat_element) fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) - # To get the meshmode to firedrake node assocation, we need to handle - # local vertex reordering and cell reordering. - # # **_cell_node holds the node nrs in shape *(ncells, nunit_nodes)* fd_cell_node = fspace.cell_node_list mm_cell_node = el_group.view(np.arange(discr.nnodes)) - reordering_arr = np.arange(el_group.nnodes, dtype=fd_cell_node.dtype) + + # To get the meshmode to firedrake node assocation, we need to handle + # local vertex reordering and cell reordering. + from pyop2.datatypes import IntType + reordering_arr = np.arange(el_group.nnodes, dtype=IntType) for perm, cells in six.iteritems(perm2cells): # reordering_arr[i] should be the fd node corresponding to meshmode # node i @@ -679,7 +681,7 @@ class ToFiredrakeConnection(FiredrakeConnection): flip_mat = get_simplex_element_flip_matrix(el_group.order, fd_unit_nodes, np.argsort(perm)) - flip_mat = np.rint(flip_mat).astype(np.int32) + flip_mat = np.rint(flip_mat).astype(IntType) fd_permuted_cell_node = np.matmul(fd_cell_node[fd_cell_order[cells]], flip_mat.T) reordering_arr[mm_cell_node[cells]] = fd_permuted_cell_node diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 6e425ba6..d2d32e26 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -86,13 +86,14 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology, cells_to_use=None): # Maps dmplex vert id -> firedrake vert index vert_id_dmp_to_fd = top._vertex_numbering.getOffset - # We will fill in the values as we go + # We will fill in the values of vertex indices as we go if cells_to_use is None: num_cells = top.num_cells() else: num_cells = np.size(cells_to_use) + from pyop2.datatypes import IntType vertex_indices = -np.ones((num_cells, top.ufl_cell().num_vertices()), - dtype=np.int32) + dtype=IntType) # This will map fd cell ndx (or its new index as dictated by # *cells_to_use* if *cells_to_use* # is not *None*) @@ -146,12 +147,12 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology, cells_to_use=None): num_cells = top.num_cells() else: num_cells = np.size(cells_to_use) - neighbors_starts = np.zeros(num_cells + 1, dtype=np.int32) + neighbors_starts = np.zeros(num_cells + 1, dtype=IntType) for iel in range(len(cell_to_nodal_neighbors)): neighbors += cell_to_nodal_neighbors[iel] neighbors_starts[iel+1] = len(neighbors) - neighbors = np.array(neighbors, dtype=np.int32) + neighbors = np.array(neighbors, dtype=IntType) nodal_adjacency = NodalAdjacency(neighbors_starts=neighbors_starts, neighbors=neighbors) @@ -268,10 +269,12 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, int_fac_loc_nr[:, 0])) int_neighbor_faces = int_neighbor_faces.astype(Mesh.face_id_dtype) # If only using some of the cells + from pyop2.datatypes import IntType if cells_to_use is not None: to_keep = np.isin(int_elements, cells_to_use) cells_to_use_inv = dict(zip(cells_to_use, - np.arange(np.size(cells_to_use)))) + np.arange(np.size(cells_to_use), + dtype=IntType))) # Only keep cells that using, and change to new cell index int_elements = np.vectorize(cells_to_use_inv.__getitem__)( @@ -301,8 +304,8 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, ext_elements = top.exterior_facets.facet_cell.flatten() ext_element_faces = np.array([fd_loc_fac_nr_to_mm[fac_nr] for fac_nr in - top.exterior_facets.local_facet_dat.data]) - ext_element_faces = ext_element_faces.astype(Mesh.face_id_dtype) + top.exterior_facets.local_facet_dat.data], + dtype=Mesh.face_id_dtype) ext_neighbor_faces = np.zeros(ext_element_faces.shape, dtype=Mesh.face_id_dtype) # If only using some of the cells, throw away unused cells and @@ -315,7 +318,7 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, ext_neighbor_faces = ext_neighbor_faces[to_keep] # tag the boundary - ext_neighbors = np.zeros(ext_elements.shape, dtype=np.int32) + ext_neighbors = np.zeros(ext_elements.shape, dtype=IntType) for ifac, marker in enumerate(top.exterior_facets.markers): ext_neighbors[ifac] = -(boundary_tag_bit(BTAG_ALL) | boundary_tag_bit(BTAG_REALLY_ALL) -- GitLab From 5a2cca71fed5ac6918a19f62a3d5c3179991fb6e Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 8 Jul 2020 09:54:18 -0500 Subject: [PATCH 085/221] Added implementation details to docs to clarify FiredrakeConnection.mm2fd_node_mapping --- doc/interop.rst | 105 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 92 insertions(+), 13 deletions(-) diff --git a/doc/interop.rst b/doc/interop.rst index b07ac095..02c78fb2 100644 --- a/doc/interop.rst +++ b/doc/interop.rst @@ -11,6 +11,14 @@ Firedrake Function Spaces/Discretizations ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Users wishing to interact with :mod:`meshmode` from :mod:`firedrake` +will primarily interact with the :class:`FromFiredrakeConnection` and +:class:`FromBdyFiredrakeConnection` classes, while users wishing +to interact with :mod:`firedrake` from :mod:`meshmode` will use +the :class:`ToFiredrakeConnection` class. All of these classes inherit from +the :class:`FiredrakeConnection` class, which provides the interface. +It is not recommended to create a :class:`FiredrakeConnection` directly. + .. automodule:: meshmode.interop.firedrake.connection Meshes @@ -31,8 +39,8 @@ Converting between :mod:`firedrake` and :mod:`meshmode` is in general straightforward. Some language is different: * In a mesh, a :mod:`meshmode` "element" is a :mod:`firedrake` "cell" -* A :class:`meshmode.Discretization` is a :mod:`firedrake` - :class:`WithGeometry`, usually +* A :class:`meshmode.discretization.Discretization` is a :mod:`firedrake` + :class:`firedrake.functionspaceimpl.WithGeometry`, usually created by calling the function :func:`firedrake.functionspace.FunctionSpace` and referred to as a "function space" * In a mesh, any vertices, faces, cells, etc. are :mod:`firedrake` @@ -43,22 +51,93 @@ straightforward. Some language is different: Other than carefully tabulating how and which vertices/faces correspond to other vertices/faces/cells, there are two main difficulties. -1. :mod:`meshmode` requires that all mesh elements be positively oriented, - :mod:`firedrake` does not. -2. :mod:`meshmode` has discontinuous polynomial function spaces - which use different nodes than :mod:`firedrake`. +1. :mod:`meshmode` has discontinuous polynomial function spaces + which use different unit nodes than :mod:`firedrake`. +2. :mod:`meshmode` requires that all mesh elements be positively oriented, + :mod:`firedrake` does not. Meanwhile, when :mod:`firedrake` creates + a mesh, it changes the element ordering and the local vertex ordering. + +(1.) is easily handled by insisting that the :mod:`firedrake` +:class:`firedrake.functionspaceimpl.WithGeometry` uses polynomial elements +and that the group of the :class:`meshmode.discretization.Discretization` +being converted is a +:class:`meshmode.discretization.poly_element.InterpolatoryQuadratureSimplexElementGroup` +of the same order. Then, on each element, the function space being +represented is the same in :mod:`firedrake` or :mod:`meshmode`. +We may simply resample to one system or another's unit nodes. + +To handle (2.), +once we associate a :mod:`meshmode` +element to the correct :mod:`firedrake` cell, we have something +like this picture: + +.. graphviz:: + + digraph{ + // created with graphviz2.38 dot + // NODES -Consequently, any :mod:`firedrake` :class:`firedrake.function.Function` -whose data is converted onto a corresponding :class:`Discretization` -using a :class:`FromFiredrakeConnection` instance is -first reordered (as the converted mesh was reordered to have -positively oriented elements) and then resampled at the :mod:`meshmode` -nodes. + mmNodes [label="Meshmode\nnodes"]; + mmRef [label="Meshmode\nunit nodes"]; + fdRef [label="Firedrake\nunit nodes"]; + fdNodes [label="Firedrake\nnodes"]; + + // EDGES + + mmRef -> mmNodes [label=" f "]; + fdRef -> fdNodes [label=" g "]; + } + +(Assume we have already +ensured that :mod:`meshmode` and :mod:`firedrake` use the +same reference element by mapping :mod:`firedrake`'s reference +element onto :mod:`meshmode`'s). +If :math:`f=g`, then we can resample function values from +one node set to the other. However, if :mod:`firedrake` +has reordered the vertices or if we flipped their order to +ensure :mod:`meshmode` has positively-oriented elements, +there is some map :math:`A` applied to the reference element +which implements this permutation of barycentric coordinates. +In this case, :math:`f=g\circ A`. Now, we have a connected diagram: + +.. graphviz:: + + digraph{ + // created with graphviz2.38 dot + // NODES + + mmNodes [label="Meshmode\nnodes"]; + mmRef [label="Meshmode\nunit nodes"]; + fdRef [label="Firedrake\nunit nodes"]; + fdRef2 [label="Firedrake\nunit nodes"]; + fdNodes [label="Firedrake\nnodes"]; + + // EDGES + + {rank=same; mmRef; fdRef;} + {rank=same; mmNodes; fdNodes;} + mmRef -> fdRef [label="Resampling", dir="both"]; + mmRef -> mmNodes [label=" f "]; + fdRef -> fdRef2 [label=" A "]; + fdRef2 -> fdNodes [label=" g "]; + } + +In short, once we reorder the :mod:`firedrake` nodes so +that the mapping from the :mod:`meshmode` and :mod:`firedrake` +reference elements are the same, we can resample function values +at nodes from one set of unit nodes to another (and then undo +the reordering if converting function values +from :mod:`meshmode` to :mod:`firedrake`). The +information for this whole reordering process is +stored in :attr:`FiredrakeConnection.mm2fd_node_mapping`, +an array which associates each :mod:`meshmode` node +to the :mod:`firedrake` node found by tracing the +above diagram (i.e. it stores +:math:`g\circ A\circ Resampling \circ f^{-1}`). For Developers: Firedrake Function Space Design Crash Course ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - In firedrake, meshes and function spaces have a close relationship. In particular, this is due to some structure described in this `firedrake pull request `_. -- GitLab From 8f0ac40f737a258b1136f99991875c987311783c Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 9 Jul 2020 10:23:58 -0500 Subject: [PATCH 086/221] Added type check to from_meshmode --- meshmode/interop/firedrake/connection.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 8ce7a4e2..0d83aeea 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -378,6 +378,9 @@ class FiredrakeConnection: raise ValueError("Trying to convert to continuous function space " " with :arg:`assert_fdrake_discontinuous` set " " to *True*") + if not isinstance(mm_field, np.ndarray): + raise TypeError(":param:`mm_field` must be of type " + ":class:`np.ndarray`, not %s." % type(mm_field)) # make sure out is a firedrake function in an appropriate # function space from firedrake.function import Function -- GitLab From 8026737471e95a036174b28075de55ef8c5c4340 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 9 Jul 2020 10:24:10 -0500 Subject: [PATCH 087/221] Added to-firedrake example --- examples/firedrake_connection.py | 114 +++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 examples/firedrake_connection.py diff --git a/examples/firedrake_connection.py b/examples/firedrake_connection.py new file mode 100644 index 00000000..bb2abca4 --- /dev/null +++ b/examples/firedrake_connection.py @@ -0,0 +1,114 @@ +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" + +__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. +""" + + +import numpy as np +import pyopencl as cl + + +# Nb: Some of the initial setup was adapted from meshmode/examplse/simple-dg.py +# written by Andreas Klockner: +# https://gitlab.tiker.net/inducer/meshmode/-/blob/7826fa5e13854bf1dae425b4226865acc10ee01f/examples/simple-dg.py # noqa : E501 +def main(): + # For this example, imagine we wish to solve the Laplace equation + # on a meshmode mesh with some given Dirichlet boundary conditions, + # and decide to use firedrake. + # + # To verify this is working, we use a solution to the wave equation + # (see :func:`bump`) to get our boundary conditions + + # {{{ First we make a discretization in meshmode and get our bcs + + cl_ctx = cl.create_some_context() + queue = cl.CommandQueue(cl_ctx) + + nel_1d = 16 + from meshmode.mesh.generation import generate_regular_rect_mesh + mesh = generate_regular_rect_mesh( + a=(-0.5, -0.5), + b=(0.5, 0.5), + n=(nel_1d, nel_1d)) + + order = 3 + + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory + group_factory = InterpolatoryQuadratureSimplexGroupFactory(order=order) + discr = Discretization(cl_ctx, mesh, group_factory) + + # Get our solution: we will use + # Real(e^z) = Real(e^{x+iy}) + # = e^x Real(e^{iy}) + # = e^x cos(y) + nodes = discr.nodes().with_queue(queue).get(queue=queue) + candidate_sol = np.exp(nodes[0, :]) * np.cos(nodes[1, :]) + + # }}} + + # {{{ Now send candidate_sol into firedrake and use it for boundary conditions + + from meshmode.interop.firedrake import ToFiredrakeConnection + fd_connection = ToFiredrakeConnection(discr, group_nr=0) + # convert candidate_sol to firedrake + fd_candidate_sol = fd_connection.from_meshmode(candidate_sol) + # get the firedrake function space + fd_fspace = fd_connection.firedrake_fspace() + + # set up dirichlet laplace problem in fd and solve + from firedrake import ( + FunctionSpace, TrialFunction, TestFunction, Function, inner, grad, dx, + Constant, project, DirichletBC, solve) + + # because it's easier to write down the variational problem, + # we're going to project from our "DG" space + # into a continuous one. + cfd_fspace = FunctionSpace(fd_fspace.mesh(), 'CG', order) + u = TrialFunction(cfd_fspace) + v = TestFunction(cfd_fspace) + sol = Function(cfd_fspace) + + a = inner(grad(u), grad(v)) * dx + L = Constant(0.0) * v * dx + bc_value = project(fd_candidate_sol, cfd_fspace) + bc = DirichletBC(cfd_fspace, bc_value, 'on_boundary') + params = {'ksp_monitor': None} + solve(a == L, sol, bcs=[bc], solver_parameters=params) + + # project back into our "DG" space + sol = project(sol, fd_fspace) + + # }}} + + # {{{ Take the solution from firedrake and compare it to candidate_sol + + true_sol = fd_connection.from_firedrake(sol) + print("l^2 difference between candidate solution and firedrake solution=", + np.linalg.norm(true_sol - candidate_sol)) + + # }}} + + +if __name__ == "__main__": + main() + +# vim: foldmethod=marker -- GitLab From cdcd4db59b8f1d071c8e52851160bfd713188f56 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 9 Jul 2020 10:27:39 -0500 Subject: [PATCH 088/221] Fixed comment --- examples/firedrake_connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/firedrake_connection.py b/examples/firedrake_connection.py index bb2abca4..a7bffdb1 100644 --- a/examples/firedrake_connection.py +++ b/examples/firedrake_connection.py @@ -34,7 +34,7 @@ def main(): # and decide to use firedrake. # # To verify this is working, we use a solution to the wave equation - # (see :func:`bump`) to get our boundary conditions + # to get our boundary conditions # {{{ First we make a discretization in meshmode and get our bcs -- GitLab From 1ddde33490aedb697b1de9ec4712982bfdc05229 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 9 Jul 2020 10:59:04 -0500 Subject: [PATCH 089/221] Add guard on firedrake_connection example in case firedrake cannot be imported --- examples/firedrake_connection.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/firedrake_connection.py b/examples/firedrake_connection.py index a7bffdb1..2621de4e 100644 --- a/examples/firedrake_connection.py +++ b/examples/firedrake_connection.py @@ -29,6 +29,12 @@ import pyopencl as cl # written by Andreas Klockner: # https://gitlab.tiker.net/inducer/meshmode/-/blob/7826fa5e13854bf1dae425b4226865acc10ee01f/examples/simple-dg.py # noqa : E501 def main(): + # If can't import firedrake, do nothing + try: + import firedrake + except ImportError: + return 0 + # For this example, imagine we wish to solve the Laplace equation # on a meshmode mesh with some given Dirichlet boundary conditions, # and decide to use firedrake. -- GitLab From dfcaccdf94d9b13e1c3874180a1ba7bcfcc98dd0 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 9 Jul 2020 11:00:19 -0500 Subject: [PATCH 090/221] flake8 fix --- examples/firedrake_connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/firedrake_connection.py b/examples/firedrake_connection.py index 2621de4e..20878e0b 100644 --- a/examples/firedrake_connection.py +++ b/examples/firedrake_connection.py @@ -31,7 +31,7 @@ import pyopencl as cl def main(): # If can't import firedrake, do nothing try: - import firedrake + import firedrake # noqa : F401 except ImportError: return 0 -- GitLab From e4b0f9a9871e6b5c7707e056a0f1443f3b7337f5 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 9 Jul 2020 12:33:40 -0500 Subject: [PATCH 091/221] Added a FromFiredrake example that makes some plots --- examples/from_firedrake.py | 105 ++++++++++++++++++ ...iredrake_connection.py => to_firedrake.py} | 0 2 files changed, 105 insertions(+) create mode 100644 examples/from_firedrake.py rename examples/{firedrake_connection.py => to_firedrake.py} (100%) diff --git a/examples/from_firedrake.py b/examples/from_firedrake.py new file mode 100644 index 00000000..a70fb945 --- /dev/null +++ b/examples/from_firedrake.py @@ -0,0 +1,105 @@ +__copyright__ = "Copyright (C) 2020 Benjamin Sepanski" + +__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. +""" + +import pyopencl as cl + + +# This example provides a brief template for bringing information in +# from firedrake and makes some plots to help you better understand +# what a FromBdyFiredrakeConnection does +def main(): + # If can't import firedrake, do nothing + try: + import firedrake # noqa : F401 + except ImportError: + return 0 + + from meshmode.interop.firedrake import ( + FromFiredrakeConnection, FromBdyFiredrakeConnection) + from firedrake import ( + UnitSquareMesh, FunctionSpace, SpatialCoordinate, Function, cos + ) + + # Create a firedrake mesh and interpolate cos(x+y) onto it + fd_mesh = UnitSquareMesh(10, 10) + fd_fspace = FunctionSpace(fd_mesh, 'DG', 2) + spatial_coord = SpatialCoordinate(fd_mesh) + fd_fntn = Function(fd_fspace).interpolate(cos(sum(spatial_coord))) + + # Make connections + cl_ctx = cl.create_some_context() + fd_connection = FromFiredrakeConnection(cl_ctx, fd_fspace) + fd_bdy_connection = FromBdyFiredrakeConnection(cl_ctx, + fd_fspace, + 'on_boundary') + + # Plot the meshmode meshes that the connections connect to + import matplotlib.pyplot as plt + from meshmode.mesh.visualization import draw_2d_mesh + fig, (ax1, ax2) = plt.subplots(1, 2) + ax1.set_title("FromFiredrakeConnection") + plt.sca(ax1) + draw_2d_mesh(fd_connection.discr.mesh, + draw_vertex_numbers=False, + draw_element_numbers=False, + set_bounding_box=True) + ax2.set_title("FromBdyFiredrakeConnection") + plt.sca(ax2) + draw_2d_mesh(fd_bdy_connection.discr.mesh, + draw_vertex_numbers=False, + draw_element_numbers=False, + set_bounding_box=True) + plt.show() + + # Plot fd_fntn using FromFiredrakeConnection + from meshmode.discretization.visualization import make_visualizer + queue = cl.CommandQueue(cl_ctx) + discr = fd_connection.discr + vis = make_visualizer(queue, discr, discr.groups[0].order+3) + field = fd_connection.from_firedrake(fd_fntn) + field = cl.array.to_device(queue, field) + + fig = plt.figure() + ax1 = fig.add_subplot(1, 2, 1, projection='3d') + ax1.set_title("cos(x+y) in\nFromFiredrakeConnection") + vis.show_scalar_in_matplotlib_3d(field, do_show=False) + + # Now repeat using FromBdyFiredrakeConnection + bdy_discr = fd_bdy_connection.discr + bdy_vis = make_visualizer(queue, bdy_discr, bdy_discr.groups[0].order+3) + bdy_field = fd_bdy_connection.from_firedrake(fd_fntn) + bdy_field = cl.array.to_device(queue, bdy_field) + + ax2 = fig.add_subplot(1, 2, 2, projection='3d') + plt.sca(ax2) + ax2.set_title("cos(x+y) in\nFromBdyFiredrakeConnection") + bdy_vis.show_scalar_in_matplotlib_3d(bdy_field, do_show=False) + + import matplotlib.cm as cm + fig.colorbar(cm.ScalarMappable()) + plt.show() + + +if __name__ == "__main__": + main() + +# vim: foldmethod=marker diff --git a/examples/firedrake_connection.py b/examples/to_firedrake.py similarity index 100% rename from examples/firedrake_connection.py rename to examples/to_firedrake.py -- GitLab From c7624e299b1ce8862084ce39d09802dcf62ca0a6 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 14 Jul 2020 13:19:14 -0500 Subject: [PATCH 092/221] Make sure high order mesh uses fd nodes for permutation --- meshmode/interop/firedrake/mesh.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index d2d32e26..5a29c140 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -784,27 +784,31 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): from meshmode.discretization.poly_element import ( InterpolatoryQuadratureSimplexElementGroup) - el_group = InterpolatoryQuadratureSimplexElementGroup(group, group.order, + el_group = InterpolatoryQuadratureSimplexElementGroup(group, + group.order, group.node_nr_base) resampling_mat = resampling_matrix(el_group.basis(), new_nodes=fd_unit_nodes, old_nodes=group.unit_nodes) + # Store the meshmode data resampled to firedrake unit nodes + # (but still in meshmode order) + resampled_group_nodes = np.matmul(group.nodes, resampling_mat.T) + # Now put the nodes in the right local order # nodes is shaped *(ambient dim, nelements, nunit nodes)* - # flip nodes from meshmode.mesh.processing import get_simplex_element_flip_matrix - flipped_group_nodes = np.copy(group.nodes) for perm, cells in six.iteritems(perm2cells): flip_mat = get_simplex_element_flip_matrix(group.order, - group.unit_nodes, + fd_unit_nodes, perm) - flipped_group_nodes[:, cells, :] = np.matmul(group.nodes[:, cells, :], - flip_mat.T) + flip_mat = np.rint(flip_mat).astype(np.int32) + resampled_group_nodes[:, cells, :] = \ + np.matmul(resampled_group_nodes[:, cells, :], flip_mat.T) - # reorder to firedrake cell ordering + # store resampled data in right cell ordering reordered_cell_node_list = coords_fspace.cell_node_list[cell_index_mm2fd] coords.dat.data[reordered_cell_node_list, :] = \ - np.matmul(flipped_group_nodes, resampling_mat.T).transpose((1, 2, 0)) + resampled_group_nodes.transpose((1, 2, 0)) return fd_mesh.Mesh(coords), cell_index_mm2fd, perm2cells -- GitLab From e120940edbae1bb0e1d42022e23feedf5cadbfd6 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 14 Jul 2020 13:19:46 -0500 Subject: [PATCH 093/221] Some doc fixes + discr order >= mesh order for ToFiredrakeConnection until we find the bug? --- meshmode/interop/firedrake/connection.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 0d83aeea..9385f9fc 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -368,7 +368,8 @@ class FiredrakeConnection: same firedrake node (meshmode is a discontinuous space, so this situation will almost certainly happen), the function being transported has values at most *continuity_tolerance* distance - apart. If *None*, no checks are performed. + apart. If *None*, no checks are performed. Does nothing if + the firedrake function space is discontinuous :return: a :mod:`firedrake` :class:`Function` holding the transported data. @@ -637,7 +638,9 @@ class ToFiredrakeConnection(FiredrakeConnection): :param discr: A :class:`Discretization` to intialize the connection with :param group_nr: The group number of the discretization to convert. If *None* there must be only one group. The selected group - must be of type :class:`InterpolatoryQuadratureSimplexElementGroup` + must be of type :class:`InterpolatoryQuadratureSimplexElementGroup`. + The mesh group ``discr.mesh.groups[group_nr]`` must have + order less than or equal to the order of ``discr.groups[group_nr]``. :param comm: Communicator to build a dmplex object on for the created firedrake mesh """ @@ -645,6 +648,10 @@ class ToFiredrakeConnection(FiredrakeConnection): assert len(discr.groups) == 1, ":arg:`group_nr` is *None*, but " \ ":arg:`discr` has %s != 1 groups." % len(discr.groups) group_nr = 0 + if discr.groups[group_nr].order < discr.mesh.groups[group_nr].order: + raise ValueError("Discretization group order must be greater than " + "or equal to the corresponding mesh group's " + "order.") el_group = discr.groups[group_nr] from firedrake.functionspace import FunctionSpace @@ -693,6 +700,9 @@ class ToFiredrakeConnection(FiredrakeConnection): fspace, reordering_arr, group_nr=group_nr) + assert len(self._duplicate_nodes) == 0, \ + "Somehow a firedrake node in a 'DG' space got duplicated..." \ + "contact the developer." # }}} -- GitLab From 89e6a06d358b5ceeba7e2d73a21a514b872d723a Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 14 Jul 2020 13:20:05 -0500 Subject: [PATCH 094/221] add high order meshes, grab vertices properly --- test/test_firedrake_interop.py | 67 ++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 2e8b8ec3..e98e3c6f 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -22,6 +22,7 @@ THE SOFTWARE. import numpy as np import pyopencl as cl +import six from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl @@ -59,6 +60,9 @@ CLOSE_ATOL = 10**-12 "blob2d-order1-h4e-2.msh", "blob2d-order1-h6e-2.msh", "blob2d-order1-h8e-2.msh", + "blob2d-order4-h4e-2.msh", + "blob2d-order4-h6e-2.msh", + "blob2d-order4-h8e-2.msh", ]) def mm_mesh(request): from meshmode.mesh.io import read_gmsh @@ -67,6 +71,7 @@ def mm_mesh(request): @pytest.fixture(params=["FiredrakeUnitIntervalMesh", "FiredrakeUnitSquareMesh", + "FiredrakeUnitSquareMesh-order2", "FiredrakeUnitCubeMesh", "annulus.msh", "blob2d-order1-h4e-2.msh", @@ -79,6 +84,12 @@ def fdrake_mesh(request): return UnitIntervalMesh(100) if mesh_name == "FiredrakeUnitSquareMesh": return UnitSquareMesh(10, 10) + if mesh_name == "FiredrakeUnitSquareMesh-order2": + m = UnitSquareMesh(10, 10) + V = VectorFunctionSpace(m, 'CG', 2) + coords = Function(V).interpolate(SpatialCoordinate(m)) + from firedrake.mesh import Mesh + return Mesh(coords) if mesh_name == "FiredrakeUnitCubeMesh": return UnitCubeMesh(5, 5, 5) @@ -95,7 +106,7 @@ def fdrake_family(request): return request.param -@pytest.fixture(params=[1, 2, 3], ids=["P^1", "P^2", "P^3"]) +@pytest.fixture(params=[1, 3], ids=["P^1", "P^3"]) def fspace_degree(request): return request.param @@ -109,11 +120,19 @@ def check_consistency(fdrake_fspace, discr, group_nr=0): meshes have the same basic properties and the function space/discretization agree across firedrake vs meshmode """ + # Get the unit vertex indices (in each cell) fdrake_mesh = fdrake_fspace.mesh() - # get fdrake_verts (shaped like (nverts, dim)) - # Nb : Mesh must be order 1 for these to be vertices - assert fdrake_mesh.coordinates.function_space().finat_element.degree == 1 - fdrake_verts = fdrake_mesh.coordinates.dat.data + cfspace = fdrake_mesh.coordinates.function_space() + entity_dofs = cfspace.finat_element.entity_dofs()[0] + fdrake_unit_vert_indices = [] + for _, local_node_nrs in sorted(six.iteritems(entity_dofs)): + assert len(local_node_nrs) == 1 + fdrake_unit_vert_indices.append(local_node_nrs[0]) + + # get the firedrake vertices, in no particular order + fdrake_vert_indices = cfspace.cell_node_list[:, fdrake_unit_vert_indices] + fdrake_vert_indices = np.unique(fdrake_vert_indices) + fdrake_verts = fdrake_mesh.coordinates.dat.data[fdrake_vert_indices, ...] if fdrake_mesh.geometric_dimension() == 1: fdrake_verts = fdrake_verts[:, np.newaxis] @@ -134,7 +153,8 @@ def check_consistency(fdrake_fspace, discr, group_nr=0): # https://stackoverflow.com/questions/38277143/sort-2d-numpy-array-lexicographically # noqa: E501 lex_sorted_mm_verts = meshmode_verts[:, np.lexsort(meshmode_verts)] lex_sorted_fdrake_verts = fdrake_verts[np.lexsort(fdrake_verts.T)] - np.testing.assert_array_equal(lex_sorted_mm_verts, lex_sorted_fdrake_verts.T) + np.testing.assert_allclose(lex_sorted_mm_verts, lex_sorted_fdrake_verts.T, + atol=1e-15) # Ensure the discretization and the firedrake function space agree on # some basic properties @@ -159,6 +179,7 @@ def test_fd2mm_consistency(ctx_factory, fdrake_mesh, fspace_degree): def test_mm2fd_consistency(ctx_factory, mm_mesh, fspace_degree): + fspace_degree += mm_mesh.groups[0].order cl_ctx = ctx_factory() factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) discr = Discretization(cl_ctx, mm_mesh, factory) @@ -196,17 +217,27 @@ def test_from_bdy_consistency(ctx_factory, assert discr.mesh.groups[0].dim == fdrake_mesh.topological_dimension() assert discr.mesh.groups[0].order == fdrake_mesh_order - # get fdrake_verts (shaped like (nverts, dim)) - # Nb : Mesh must be order 1 for these to be vertices - assert fdrake_mesh.coordinates.function_space().finat_element.degree == 1 - fdrake_verts = fdrake_mesh.coordinates.dat.data - if fdrake_mesh.geometric_dimension() == 1: - fdrake_verts = fdrake_verts[:, np.newaxis] + # Get the unit vertex indices (in each cell) + fdrake_mesh = fdrake_fspace.mesh() + cfspace = fdrake_mesh.coordinates.function_space() + entity_dofs = cfspace.finat_element.entity_dofs()[0] + fdrake_unit_vert_indices = [] + for _, local_node_nrs in sorted(six.iteritems(entity_dofs)): + assert len(local_node_nrs) == 1 + fdrake_unit_vert_indices.append(local_node_nrs[0]) + fdrake_unit_vert_indices = np.array(fdrake_unit_vert_indices) + # only look at cells "near" bdy (with >= 1 vertex on) cells_near_bdy = _compute_cells_near_bdy(fdrake_mesh, 'on_boundary') - verts_near_bdy = np.unique( - fdrake_mesh_fspace.cell_node_list[cells_near_bdy, :].flatten()) - fdrake_verts = fdrake_verts[verts_near_bdy, :] + # get the firedrake vertices of cells near the boundary, + # in no particular order + fdrake_vert_indices = \ + cfspace.cell_node_list[cells_near_bdy, + fdrake_unit_vert_indices[:, np.newaxis]] + fdrake_vert_indices = np.unique(fdrake_vert_indices) + fdrake_verts = fdrake_mesh.coordinates.dat.data[fdrake_vert_indices, ...] + if fdrake_mesh.geometric_dimension() == 1: + fdrake_verts = fdrake_verts[:, np.newaxis] # Get meshmode vertices (shaped like (dim, nverts)) meshmode_verts = discr.mesh.vertices @@ -217,7 +248,8 @@ def test_from_bdy_consistency(ctx_factory, # https://stackoverflow.com/questions/38277143/sort-2d-numpy-array-lexicographically # noqa: E501 lex_sorted_mm_verts = meshmode_verts[:, np.lexsort(meshmode_verts)] lex_sorted_fdrake_verts = fdrake_verts[np.lexsort(fdrake_verts.T)] - np.testing.assert_array_equal(lex_sorted_mm_verts, lex_sorted_fdrake_verts.T) + np.testing.assert_allclose(lex_sorted_mm_verts, lex_sorted_fdrake_verts.T, + atol=CLOSE_ATOL) # Ensure the discretization and the firedrake function space reference element # agree on some basic properties @@ -391,6 +423,7 @@ def test_to_fd_transfer(ctx_factory, mm_mesh, fspace_degree, (up to resampling error) as creating a function on the transported mesh """ + fspace_degree += mm_mesh.groups[0].order # Make discr and evaluate function in meshmode cl_ctx = ctx_factory() factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) @@ -408,6 +441,7 @@ def test_to_fd_transfer(ctx_factory, mm_mesh, fspace_degree, # transport to firedrake and make sure this is the same mm2fd_f = fdrake_connection.from_meshmode(meshmode_f) + np.testing.assert_allclose(mm2fd_f.dat.data, fdrake_f.dat.data, atol=CLOSE_ATOL) @@ -487,6 +521,7 @@ def test_to_fd_idempotency(ctx_factory, mm_mesh, fspace_degree): Make sure mm->fd->mm and (mm->)->fd->mm->fd are identity """ # Make a function space and a function with unique values at each node + fspace_degree += mm_mesh.groups[0].order cl_ctx = ctx_factory() factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) discr = Discretization(cl_ctx, mm_mesh, factory) -- GitLab From ca978f709384af5dbb94c4a028c13e00957e3945 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 14 Jul 2020 13:58:19 -0500 Subject: [PATCH 095/221] flake8 fix : V -> fspace --- test/test_firedrake_interop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index e98e3c6f..505daf39 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -86,8 +86,8 @@ def fdrake_mesh(request): return UnitSquareMesh(10, 10) if mesh_name == "FiredrakeUnitSquareMesh-order2": m = UnitSquareMesh(10, 10) - V = VectorFunctionSpace(m, 'CG', 2) - coords = Function(V).interpolate(SpatialCoordinate(m)) + fspace = VectorFunctionSpace(m, 'CG', 2) + coords = Function(fspace).interpolate(SpatialCoordinate(m)) from firedrake.mesh import Mesh return Mesh(coords) if mesh_name == "FiredrakeUnitCubeMesh": -- GitLab From a4ffacbc096e8cb4e2523cbed032ac366ad28ccd Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 14 Jul 2020 17:15:32 -0500 Subject: [PATCH 096/221] Initial changes to be compatible with new DOF interface, committing to save work (only halfway done) --- meshmode/interop/firedrake/connection.py | 308 ++++++++++++------- meshmode/interop/firedrake/mesh.py | 10 +- meshmode/interop/firedrake/reference_cell.py | 12 +- 3 files changed, 215 insertions(+), 115 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 9385f9fc..0a6ab626 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -37,7 +37,7 @@ from modepy import resampling_matrix from meshmode.interop.firedrake.mesh import ( import_firedrake_mesh, export_mesh_to_firedrake) from meshmode.interop.firedrake.reference_cell import ( - get_affine_reference_simplex_mapping, get_finat_element_unit_nodes) + get_affine_reference_simplex_mapping, get_finat_element_unit_dofs) from meshmode.mesh.processing import get_simplex_element_flip_matrix @@ -101,15 +101,24 @@ class FiredrakeConnection: .. autoattribute:: mm2fd_node_mapping - A numpy array of shape *(self.discr.groups[group_nr].nnodes,)* - whose *i*th entry is the :mod:`firedrake` node index associated - to the *i*th node in *self.discr.groups[group_nr]* - (where *i* is the group-local node index). - It is important to note that, due to :mod:`meshmode` - and :mod:`firedrake` using different unit nodes, a :mod:`firedrake` - node associated to a :mod:`meshmode` may have different coordinates. - However, after resampling to the other system's unit nodes, - two associated nodes should have identical coordinates. + Letting *element_grp = self.discr.groups[self.group_nr]*, + *mm2fd_node_mapping* is a numpy array of shape + *(element_grp.nelements, element_grp.nunit_dofs)* + whose *(i, j)*th entry is the :mod:`firedrake` node + index associated to the *j*th degree of freedom of the + *i*th element in *element_grp*. + + degrees of freedom should be associated so that + the implicit mapping from a reference element to element *i* + which maps meshmode unit dofs *0,1,...,n-1* to actual + dofs *(i, 0), (i, 1), ..., (i, n-1)* + is the same mapping which maps firedrake unit dofs + *0,1,...,n-1* to firedrake dofs + *mm2fd_node_mapping[i,0], mm2fd_node_mapping[i,1],..., + mm2fd_node_mapping[i,n-1]*. + + (See :mod:`meshmode.interop.firedrake.reference_cell` to + obtain firedrake unit dofs on the meshmode reference cell) .. automethod:: __init__ """ @@ -121,7 +130,7 @@ class FiredrakeConnection: Must have ufl family ``'Lagrange'`` or ``'Discontinuous Lagrange'``. :param mm2fd_node_mapping: Used as attribute :attr:`mm2fd_node_mapping`. - A numpy integer array with the same dtype as + A 2-D numpy integer array with the same dtype as ``fdrake_fspace.cell_node_list.dtype`` :param group_nr: The index of the group in *discr* which is being connected to *fdrake_fspace*. The group must be @@ -160,25 +169,29 @@ class FiredrakeConnection: raise ValueError(":param:`group_nr` is *None* but :param:`discr` " "has %s != 1 groups." % len(discr.groups)) group_nr = 0 + # store element_grp as variable for convenience + element_grp = discr.groups[group_nr] + if group_nr < 0 or group_nr >= len(discr.groups): raise ValueError(":param:`group_nr` has value %s, which an invalid " "index into list *discr.groups* of length %s." - % (group_nr, len(discr.gropus))) - if not isinstance(discr.groups[group_nr], + % (group_nr, len(discr.groups))) + if not isinstance(element_grp, InterpolatoryQuadratureSimplexElementGroup): raise TypeError("*discr.groups[group_nr]* must be of type " ":class:`InterpolatoryQuadratureSimplexElementGroup`" - ", not :class:`%s`." % type(discr.groups[group_nr])) + ", not :class:`%s`." % type(element_grp)) allowed_families = ('Discontinuous Lagrange', 'Lagrange') if fdrake_fspace.ufl_element().family() not in allowed_families: raise TypeError(":param:`fdrake_fspace` must have ufl family " "be one of %s, not %s." % (allowed_families, fdrake_fspace.ufl_element().family())) - if mm2fd_node_mapping.shape != (discr.groups[group_nr].nnodes,): + if mm2fd_node_mapping.shape != (element_grp.nelements, + element_grp.nunit_dofs): raise ValueError(":param:`mm2fd_node_mapping` must be of shape ", "(%s,), not %s" - % ((discr.groups[group_nr].nnodes,), + % ((discr.groups[group_nr].ndofs,), mm2fd_node_mapping.shape)) if mm2fd_node_mapping.dtype != fdrake_fspace.cell_node_list.dtype: raise ValueError(":param:`mm2fd_node_mapping` must have dtype " @@ -187,12 +200,11 @@ class FiredrakeConnection: # }}} # Get meshmode unit nodes - element_grp = discr.groups[group_nr] - mm_unit_nodes = element_grp.unit_nodes + mm_unit_nodes = element_grp.unit_nodes() # get firedrake unit nodes and map onto meshmode reference element - dim = fdrake_fspace.mesh().topological_dimension() - fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(dim, True) - fd_unit_nodes = get_finat_element_unit_nodes(fdrake_fspace.finat_element) + tdim = fdrake_fspace.mesh().topological_dimension() + fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(tdim, True) + fd_unit_nodes = get_finat_element_unit_dofs(fdrake_fspace.finat_element) fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) # compute and store resampling matrices @@ -228,13 +240,13 @@ class FiredrakeConnection: # duplication. self._fspace_cache = {} - def firedrake_fspace(self, vdim=None): + def firedrake_fspace(self, shape=None): """ Return a firedrake function space that *self.discr.groups[self.group_nr]* is connected to of the appropriate vector dimension - :arg vdim: Either *None*, in which case a function space which maps + :arg shape: Either *None*, in which case a function space which maps to scalar values is returned, a positive integer *n*, in which case a function space which maps into *\\R^n* is returned, or a tuple of integers defining @@ -244,98 +256,195 @@ class FiredrakeConnection: *self.discr.groups[self.group_nr]* of the appropriate vector dimension - :raises TypeError: If *vdim* is of the wrong type + :raises TypeError: If *shape* is of the wrong type """ # Cache the function spaces created to avoid high overhead. # Note that firedrake is smart about re-using shared information, # so this is not duplicating mesh/reference element information - if vdim not in self._fspace_cache: - if vdim is None: + if shape not in self._fspace_cache: + if shape is None: from firedrake import FunctionSpace - self._fspace_cache[vdim] = \ + self._fspace_cache[shape] = \ FunctionSpace(self._mesh_geometry, self._ufl_element.family(), degree=self._ufl_element.degree()) - elif isinstance(vdim, int): + elif isinstance(shape, int): from firedrake import VectorFunctionSpace - self._fspace_cache[vdim] = \ + self._fspace_cache[shape] = \ VectorFunctionSpace(self._mesh_geometry, self._ufl_element.family(), degree=self._ufl_element.degree(), - dim=vdim) - elif isinstance(vdim, tuple): + dim=shape) + elif isinstance(shape, tuple): from firedrake import TensorFunctionSpace - self._fspace_cache[vdim] = \ + self._fspace_cache[shape] = \ TensorFunctionSpace(self._mesh_geometry, self._ufl_element.family(), degree=self._ufl_element.degree(), - shape=vdim) + shape=shape) else: - raise TypeError(":param:`vdim` must be *None*, an integer, " + raise TypeError(":param:`shape` must be *None*, an integer, " " or a tuple of integers, not of type %s." - % type(vdim)) - return self._fspace_cache[vdim] + % type(shape)) + return self._fspace_cache[shape] - def from_firedrake(self, function, out=None): + def _validate_function(function, function_name, shape=None, dtype=None): + """ + Handy helper function to validate that a firedrake function + is convertible (or can be the recipient of a conversion). + Raises error messages if wrong types, shape, dtype found + etc. + """ + # Validate that *function* is convertible + from firedrake.function import Function + if not isinstance(function, Function): + raise TypeError(function_name + " must be a :mod:`firedrake` " + "Function") + ufl_elt = function.function_space().ufl_element() + if ufl_elt.family() != self._ufl_element.family(): + raise ValueError(function_name + "'s function_space's ufl element" + " must have family %s, not %s" + % (self._ufl_element.family(), ufl_elt.family())) + if ufl_elt.degree() != self._ufl_element.degree(): + raise ValueError(function_name + "'s function_space's ufl element" + " must have degree %s, not %s" + % (self._ufl_element.degree(), ufl_elt.degree()) + if function.function_space().mesh() is not self._mesh_geometry: + raise ValueError(function_name + "'s mesh must be the same as " + "`self.from_fspace().mesh()``") + if dtype is not None and function.dat.data.dtype != dtype: + raise ValueError(function_name + ".dat.dtype must be %s, not %s." + % (dtype, function.dat.data.dtype)) + if shape is not None and function.function_space().shape != shape: + raise ValueError(function_name + ".function_space().shape must be " + "%s, not %s" % (shape, + function.function_space().shape)) + + def _validate_field(field, field_name, shape=None, dtype=None): + """ + Handy helper function to validate that a meshmode field + is convertible (or can be the recipient of a conversion). + Raises error messages if wrong types, shapes, dtypes found + etc. + """ + from meshmode.dof_array import DOFArray + element_grp = self.discr.groups[self.group_nr] + group_shape = (element_grp.nelements, element_grp.nunit_dofs) + + def check_dof_array(arr, arr_name): + if not isinstance(arr, DOFArray): + raise TypeError(arr_name + " must be of type " + ":class:`meshmode.dof_array.DOFArray`, " + "not :class:`%s`." % type(arr)) + if arr.shape != self.discr.groups.shape: + raise ValueError(arr_name + " shape must be %s, not %s." + % (self.discr.groups.shape, arr.shape)) + if arr[self.group_nr].shape != group_shape: + raise VaueError(arr_name + "[%s].shape must be %s, not %s" + % (self.group_nr, + group_shape, + arr[self.group_nr].shape)) + if dtype is not None and arr.entry_dtype != dtype: + raise ValueError(arr_name + ".entry_dtype must be %s, not %s." + % (dtype, arr.entry_dtype)) + + if isinstance(field, DOFArray): + if shape is not None and shape != tuple(): + raise ValueError("shape != tuple() and %s is of type DOFArray" + " instead of np.ndarray." % field_name) + check_dof_array(field, field_name) + elif isinstance(field, np.ndarray): + if shape is not None and field.shape != shape: + raise ValueError(field_name + ".shape must be %s, not %s" + % (shape, field.shape)) + for i, arr in np.flatten(field): + arr_name = "%s[%s]" % (field_name, np.unravel_index(i, shape)) + check_dof_array(arr, arr_name) + else: + raise TypeError("field must be of type DOFArray or np.ndarray", + "not %s." % type(field)) + + def from_firedrake(self, function, out=None, actx=None): """ transport firedrake function onto :attr:`discr` :arg function: A :mod:`firedrake` function to transfer onto :attr:`discr`. Its function space must have the same family, degree, and mesh as ``self.from_fspace()``. - :arg out: Either *None* or a numpy array of - shape - *(..., num meshmode nodes)* or *(num meshmode nodes,)* and of the - same dtype as *function*. - *function*'s transported data will be stored in *out* - and *out* will be returned. - Note that number of nodes referenced here is - the number of nodes in the whole discretization. - If *out* is *None*, then a numpy array of the correct size - filled with zeros is created to take its place. - - :return: a numpy array holding the transported function + :arg out: Either + 1.) A :class:`meshmode.dof_array.DOFArray` + 2.) A :class:`np.ndarray` object array, each of whose + entries is a :class:`meshmode.dof_array.DOFArray` + 3.) *None* + In the case of (1.), *function* must be in a + scalar function space + (i.e. `function.function_space().shape == (,)`). + In the case of (2.), the shape of *out* must match + `function.function_space().shape`. + + In either case, each `DOFArray` must be a `DOFArray` + defined on :attr:`discr` (as described in + the documentation for :class:`meshmode.dof_array.DOFArray`). + Also, each `DOFArray`'s *entry_dtype* must match the + *function.dat.data.dtype*, and be of shape + *(nelements, nunit_dofs)*. + + In case (3.), an array is created satisfying + the above requirements. + + The data in *function* is transported to :attr:`discr` + and stored in *out*, which is then returned. + :arg actx: + * If *out* is *None*, then *actx* is a + :class:`meshmode.array_context.ArrayContext` on which + to create the :class:`DOFArray` + * If *out* is not *None*, *actx* must be *None* or *out*'s + *array_context*. + + :return: *out*, with the converted data from *function* stored in it """ - # Check function is convertible - from firedrake.function import Function - if not isinstance(function, Function): - raise TypeError(":arg:`function` must be a :mod:`firedrake` " - "Function") - assert function.function_space().ufl_element().family() \ - == self._ufl_element.family() and \ - function.function_space().ufl_element().degree() \ - == self._ufl_element.degree(), \ - ":arg:`function` must live in a function space with the " \ - "same family and degree as ``self.from_fspace()``" - assert function.function_space().mesh() is self._mesh_geometry, \ - ":arg:`function` mesh must be the same mesh as used by " \ - "``self.from_fspace().mesh()``" - + self._validate_function(function, "function") # Get function data as shape *(nnodes, ...)* or *(nnodes,)* function_data = function.dat.data + # get the shape needed in each DOFArray and the data shape + element_grp = self.discr.groups[self.group_nr] + group_shape = (element_grp.nelements, element_grp.nunit_dofs) + fspace_shape = function.function_space().shape - # Check that out is supplied correctly, or create out if it is - # not supplied - shape = (self.discr.nnodes,) - if len(function_data.shape) > 1: - shape = function_data.shape[1:] + shape + # Handle :arg:`out` if out is not None: - if not isinstance(out, np.ndarray): - raise TypeError(":param:`out` must of type *np.ndarray* or " - "be *None*") - assert out.shape == shape, \ - ":param:`out` must have shape %s." % shape - assert out.dtype == function.dat.data.dtype + self._validate_field(out, "out", fspace_shape, function_data.dtype) + # If out is supplied, check type, shape, and dtype + assert actx in (None, out.array_context), \ + "If :param:`out` is not *None*, :param:`actx` must be" \ + " *None* or *out.array_context*". else: - out = np.zeros(shape, dtype=function_data.dtype) - - # Reorder nodes - group = self.discr.groups[self.group_nr] - out[..., group.node_nr_base:group.nnodes] = \ - np.moveaxis(function_data, 0, -1)[..., self.mm2fd_node_mapping] - # Resample at the appropriate nodes - out_view = self.discr.groups[self.group_nr].view(out) - np.matmul(out_view, self._resampling_mat_fd2mm.T, out=out_view) + # If `out` is not supplied, create it + from meshmode.array_context import ArrayContext + assert actx isinstance(actx, ArrayContext) + if fspace_shape == tuple(): + out = self.discr.zeros(actx, dtype=function_data.dtype) + else: + out = \ + np.array([self.discr.zeros(actx, dtype=function_data.dtype) + for _ in np.prod(fspace_shape)] + ).reshape(fspace_shape) + + def reorder_and_resample(dof_array, fd_data): + dof_array[self.group_nr] = fd_data[self.mm2fd_node_mapping] + np.matmul(dof_array[self.group_nr], self._resampling_mat_fd2mm.T, + out=dof_array[self.group_nr] + # If scalar, just reorder and resample out + if fspace_shape == tuple(): + reorder_and_resample(out, function_data) + else: + # otherwise, have to grab each dofarray and the corresponding + # data from *function_data* + with np.nditer(out, op_flags=['readwrite', 'multi_index'] as it: + for dof_array in it: + fd_data = function_data[:, it.multi_index] + reorder_and_resample(dof_array, function_data) + return out def from_meshmode(self, mm_field, out=None, @@ -379,32 +488,25 @@ class FiredrakeConnection: raise ValueError("Trying to convert to continuous function space " " with :arg:`assert_fdrake_discontinuous` set " " to *True*") - if not isinstance(mm_field, np.ndarray): - raise TypeError(":param:`mm_field` must be of type " - ":class:`np.ndarray`, not %s." % type(mm_field)) + dtype = self.firedrake_fspace().mesh().coordinates.dat.dat.dtype + self._validate_field(mm_field, "mm_field", dtype=dtype) # make sure out is a firedrake function in an appropriate # function space - from firedrake.function import Function if out is not None: - assert isinstance(out, Function), \ - ":arg:`out` must be a :mod:`firedrake` Function or *None*" - assert out.function_space().ufl_element().family() \ - == self._ufl_element.family() and \ - out.function_space().ufl_element().degree() \ - == self._ufl_element.degree(), \ - ":arg:`out` must live in a function space with the " \ - "same family and degree as ``self.firedrake_fspace()``" - assert out.function_space().mesh() is self._mesh_geometry, \ - ":arg:`out` mesh must be the same mesh as used by " \ - "``self.firedrake_fspace().mesh()`` or *None*" + if isinstance(mm_field, np.ndarray): + shape = mm_field.shape + else: + shape = tuple() + self._validate_function(out, "out", shape, dtype) else: + from firedrake.function import Function if len(mm_field.shape) == 1: - vdim = None + shape = None elif len(mm_field.shape) == 2: - vdim = mm_field.shape[0] + shape = mm_field.shape[0] else: - vdim = mm_field.shape[:-1] - out = Function(self.firedrake_fspace(vdim)) + shape = mm_field.shape[:-1] + out = Function(self.firedrake_fspace(shape)) out.dat.data[:] = 0.0 # Handle 1-D case diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 5a29c140..1ce8702a 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -33,7 +33,7 @@ from modepy import resampling_matrix from meshmode.mesh import (BTAG_ALL, BTAG_REALLY_ALL, BTAG_NO_BOUNDARY, FacialAdjacencyGroup, Mesh, NodalAdjacency, SimplexElementGroup) from meshmode.interop.firedrake.reference_cell import ( - get_affine_reference_simplex_mapping, get_finat_element_unit_nodes) + get_affine_reference_simplex_mapping, get_finat_element_unit_dofs) # {{{ functions to extract information from Mesh Topology @@ -532,8 +532,8 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, # Get finat unit nodes and map them onto the meshmode reference simplex from meshmode.interop.firedrake.reference_cell import ( - get_affine_reference_simplex_mapping, get_finat_element_unit_nodes) - finat_unit_nodes = get_finat_element_unit_nodes(coord_finat_elt) + get_affine_reference_simplex_mapping, get_finat_element_unit_dofs) + finat_unit_nodes = get_finat_element_unit_dofs(coord_finat_elt) fd_ref_to_mm = get_affine_reference_simplex_mapping(cell_dim, True) finat_unit_nodes = fd_ref_to_mm(finat_unit_nodes) @@ -779,14 +779,14 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): # get firedrake unit nodes and map onto meshmode reference element fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(group.dim, True) - fd_unit_nodes = get_finat_element_unit_nodes(coords_fspace.finat_element) + fd_unit_nodes = get_finat_element_unit_dofs(coords_fspace.finat_element) fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) from meshmode.discretization.poly_element import ( InterpolatoryQuadratureSimplexElementGroup) el_group = InterpolatoryQuadratureSimplexElementGroup(group, group.order, - group.node_nr_base) + group_nr) resampling_mat = resampling_matrix(el_group.basis(), new_nodes=fd_unit_nodes, old_nodes=group.unit_nodes) diff --git a/meshmode/interop/firedrake/reference_cell.py b/meshmode/interop/firedrake/reference_cell.py index a102a939..d2a00194 100644 --- a/meshmode/interop/firedrake/reference_cell.py +++ b/meshmode/interop/firedrake/reference_cell.py @@ -26,7 +26,7 @@ import numpy.linalg as la __doc__ = """ .. autofunction:: get_affine_reference_simplex_mapping -.. autofunction:: get_finat_element_unit_nodes +.. autofunction:: get_finat_element_unit_dofs """ @@ -109,7 +109,7 @@ def get_affine_reference_simplex_mapping(spat_dim, firedrake_to_meshmode=True): # {{{ Get firedrake unit nodes -def get_finat_element_unit_nodes(finat_element): +def get_finat_element_unit_dofs(finat_element): """ Returns the unit nodes used by the FInAT element in firedrake's (equivalently, FInAT/FIAT's) reference coordinates @@ -117,7 +117,7 @@ def get_finat_element_unit_nodes(finat_element): :arg finat_element: A :class:`finat.finiteelementbase.FiniteElementBase` instance (i.e. a firedrake function space's reference element). The refernce element of the finat element *MUST* be a simplex - :return: A numpy array of shape *(dim, nunit_nodes)* holding the unit + :return: A numpy array of shape *(dim, nunit_dofs)* holding the unit nodes used by this element. *dim* is the dimension spanned by the finat element's reference element (see its ``cell`` attribute) @@ -130,10 +130,8 @@ def get_finat_element_unit_nodes(finat_element): # so to recover node *i* we need to evaluate *p_i* at the identity # function point_evaluators = finat_element._element.dual.nodes - unit_nodes = [p(lambda x: x) for p in point_evaluators] - unit_nodes = np.array(unit_nodes).T - - return unit_nodes + unit_dofs = [p(lambda x: x) for p in point_evaluators] + return np.array(unit_dofs).T # }}} -- GitLab From c756da009edf85c58a4d96d4efcde52b1257a0a5 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 14 Jul 2020 18:46:26 -0500 Subject: [PATCH 097/221] Began fixing tests to use ArrayContext s --- test/test_firedrake_interop.py | 63 +++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 505daf39..1ea0567b 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -1,3 +1,9 @@ +# TODO: +# * Make sure from_meshmode and from_firedrake receive +# DOFArrays +# * Make sure output of from_firedrake is treated as +# DOFArray +# * Run tests and debug __copyright__ = "Copyright (C) 2020 Benjamin Sepanski" __license__ = """ @@ -28,6 +34,8 @@ from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) +from meshmode.array_tools import PyOpenCLArrayContext + from meshmode.discretization import Discretization from meshmode.discretization.poly_element import ( InterpolatoryQuadratureSimplexGroupFactory) @@ -171,8 +179,12 @@ def test_fd2mm_consistency(ctx_factory, fdrake_mesh, fspace_degree): """ # make discretization from firedrake fdrake_fspace = FunctionSpace(fdrake_mesh, 'DG', fspace_degree) + cl_ctx = ctx_factory() - fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) + queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) + + fdrake_connection = FromFiredrakeConnection(actx, fdrake_fspace) discr = fdrake_connection.discr # Check consistency check_consistency(fdrake_fspace, discr) @@ -180,9 +192,13 @@ def test_fd2mm_consistency(ctx_factory, fdrake_mesh, fspace_degree): def test_mm2fd_consistency(ctx_factory, mm_mesh, fspace_degree): fspace_degree += mm_mesh.groups[0].order + cl_ctx = ctx_factory() + queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) + factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) - discr = Discretization(cl_ctx, mm_mesh, factory) + discr = Discretization(actx, mm_mesh, factory) fdrake_connection = ToFiredrakeConnection(discr) fdrake_fspace = fdrake_connection.firedrake_fspace() # Check consistency @@ -203,8 +219,12 @@ def test_from_bdy_consistency(ctx_factory, the right number of cells, etc. """ fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fspace_degree) + cl_ctx = ctx_factory() - frombdy_conn = FromBdyFiredrakeConnection(cl_ctx, + queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) + + frombdy_conn = FromBdyFiredrakeConnection(actx, fdrake_fspace, "on_boundary") @@ -386,19 +406,21 @@ def test_from_fd_transfer(ctx_factory, # build connection cl_ctx = ctx_factory() + queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) + if only_convert_bdy: - fdrake_connection = FromBdyFiredrakeConnection(cl_ctx, fdrake_fspace, + fdrake_connection = FromBdyFiredrakeConnection(actx, fdrake_fspace, 'on_boundary') else: - fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) + fdrake_connection = FromFiredrakeConnection(actx, fdrake_fspace) # transport fdrake function fd2mm_f = fdrake_connection.from_firedrake(fdrake_f) # build same function in meshmode discr = fdrake_connection.discr - with cl.CommandQueue(cl_ctx) as queue: - nodes = discr.nodes().get(queue=queue) + nodes = discr.nodes().get(queue=queue) meshmode_f = meshmode_f_eval(nodes) # fd -> mm should be same as creating in meshmode @@ -409,7 +431,7 @@ def test_from_fd_transfer(ctx_factory, mm2fd_f = \ fdrake_connection.from_meshmode(meshmode_f, assert_fdrake_discontinuous=False, - continuity_tolerance=1e-8) + continuity_tolerance=1e-8) # mm -> fd should be same as creating in firedrake np.testing.assert_allclose(fdrake_f.dat.data, mm2fd_f.dat.data, atol=CLOSE_ATOL) @@ -426,11 +448,13 @@ def test_to_fd_transfer(ctx_factory, mm_mesh, fspace_degree, fspace_degree += mm_mesh.groups[0].order # Make discr and evaluate function in meshmode cl_ctx = ctx_factory() + queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) + factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) - discr = Discretization(cl_ctx, mm_mesh, factory) + discr = Discretization(actx, mm_mesh, factory) - with cl.CommandQueue(cl_ctx) as queue: - nodes = discr.nodes().get(queue=queue) + nodes = discr.nodes().get(queue=queue) meshmode_f = meshmode_f_eval(nodes) # connect to firedrake and evaluate expr in firedrake @@ -482,6 +506,8 @@ def test_from_fd_idempotency(ctx_factory, # Make connection cl_ctx = ctx_factory() + queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) # If only converting boundary, first go ahead and do one round of # fd->mm->fd. This will zero out any degrees of freedom absent in @@ -490,7 +516,7 @@ def test_from_fd_idempotency(ctx_factory, # # Otherwise, just continue as normal if only_convert_bdy: - fdrake_connection = FromBdyFiredrakeConnection(cl_ctx, fdrake_fspace, + fdrake_connection = FromBdyFiredrakeConnection(actx, fdrake_fspace, 'on_boundary') temp = fdrake_connection.from_firedrake(fdrake_unique) fdrake_unique = \ @@ -498,7 +524,7 @@ def test_from_fd_idempotency(ctx_factory, assert_fdrake_discontinuous=False, continuity_tolerance=1e-8) else: - fdrake_connection = FromFiredrakeConnection(cl_ctx, fdrake_fspace) + fdrake_connection = FromFiredrakeConnection(actx, fdrake_fspace) # Test for idempotency fd->mm->fd mm_field = fdrake_connection.from_firedrake(fdrake_unique) @@ -520,11 +546,16 @@ def test_to_fd_idempotency(ctx_factory, mm_mesh, fspace_degree): """ Make sure mm->fd->mm and (mm->)->fd->mm->fd are identity """ - # Make a function space and a function with unique values at each node - fspace_degree += mm_mesh.groups[0].order cl_ctx = ctx_factory() + queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) + + # make sure degree is higher order than mesh + fspace_degree += mm_mesh.groups[0].order + + # Make a function space and a function with unique values at each node factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) - discr = Discretization(cl_ctx, mm_mesh, factory) + discr = Discretization(actx, mm_mesh, factory) fdrake_connection = ToFiredrakeConnection(discr) mm_unique = np.arange(discr.nnodes, dtype=np.float64) mm_unique_copy = np.copy(mm_unique) -- GitLab From 86c6fc70e4664796c058f27cc273a1a8458f2503 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 14 Jul 2020 18:47:12 -0500 Subject: [PATCH 098/221] Minor bug fixes + complete conversion/validation routines with DOFArrays --- meshmode/interop/firedrake/connection.py | 165 +++++++++++-------- meshmode/interop/firedrake/mesh.py | 8 +- meshmode/interop/firedrake/reference_cell.py | 8 +- 3 files changed, 99 insertions(+), 82 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 0a6ab626..b13d6fa2 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -37,7 +37,7 @@ from modepy import resampling_matrix from meshmode.interop.firedrake.mesh import ( import_firedrake_mesh, export_mesh_to_firedrake) from meshmode.interop.firedrake.reference_cell import ( - get_affine_reference_simplex_mapping, get_finat_element_unit_dofs) + get_affine_reference_simplex_mapping, get_finat_element_unit_nodes) from meshmode.mesh.processing import get_simplex_element_flip_matrix @@ -204,7 +204,7 @@ class FiredrakeConnection: # get firedrake unit nodes and map onto meshmode reference element tdim = fdrake_fspace.mesh().topological_dimension() fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(tdim, True) - fd_unit_nodes = get_finat_element_unit_dofs(fdrake_fspace.finat_element) + fd_unit_nodes = get_finat_element_unit_nodes(fdrake_fspace.finat_element) fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) # compute and store resampling matrices @@ -288,7 +288,8 @@ class FiredrakeConnection: % type(shape)) return self._fspace_cache[shape] - def _validate_function(function, function_name, shape=None, dtype=None): + def _validate_function(self, function, function_name, + shape=None, dtype=None): """ Handy helper function to validate that a firedrake function is convertible (or can be the recipient of a conversion). @@ -308,7 +309,7 @@ class FiredrakeConnection: if ufl_elt.degree() != self._ufl_element.degree(): raise ValueError(function_name + "'s function_space's ufl element" " must have degree %s, not %s" - % (self._ufl_element.degree(), ufl_elt.degree()) + % (self._ufl_element.degree(), ufl_elt.degree())) if function.function_space().mesh() is not self._mesh_geometry: raise ValueError(function_name + "'s mesh must be the same as " "`self.from_fspace().mesh()``") @@ -320,7 +321,7 @@ class FiredrakeConnection: "%s, not %s" % (shape, function.function_space().shape)) - def _validate_field(field, field_name, shape=None, dtype=None): + def _validate_field(self, field, field_name, shape=None, dtype=None): """ Handy helper function to validate that a meshmode field is convertible (or can be the recipient of a conversion). @@ -340,10 +341,10 @@ class FiredrakeConnection: raise ValueError(arr_name + " shape must be %s, not %s." % (self.discr.groups.shape, arr.shape)) if arr[self.group_nr].shape != group_shape: - raise VaueError(arr_name + "[%s].shape must be %s, not %s" - % (self.group_nr, - group_shape, - arr[self.group_nr].shape)) + raise ValueError(arr_name + "[%s].shape must be %s, not %s" + % (self.group_nr, + group_shape, + arr[self.group_nr].shape)) if dtype is not None and arr.entry_dtype != dtype: raise ValueError(arr_name + ".entry_dtype must be %s, not %s." % (dtype, arr.entry_dtype)) @@ -404,11 +405,8 @@ class FiredrakeConnection: :return: *out*, with the converted data from *function* stored in it """ self._validate_function(function, "function") - # Get function data as shape *(nnodes, ...)* or *(nnodes,)* + # get function data and shape of values function_data = function.dat.data - # get the shape needed in each DOFArray and the data shape - element_grp = self.discr.groups[self.group_nr] - group_shape = (element_grp.nelements, element_grp.nunit_dofs) fspace_shape = function.function_space().shape # Handle :arg:`out` @@ -417,11 +415,11 @@ class FiredrakeConnection: # If out is supplied, check type, shape, and dtype assert actx in (None, out.array_context), \ "If :param:`out` is not *None*, :param:`actx` must be" \ - " *None* or *out.array_context*". + " *None* or *out.array_context*" else: # If `out` is not supplied, create it from meshmode.array_context import ArrayContext - assert actx isinstance(actx, ArrayContext) + assert isinstance(actx, ArrayContext) if fspace_shape == tuple(): out = self.discr.zeros(actx, dtype=function_data.dtype) else: @@ -433,17 +431,18 @@ class FiredrakeConnection: def reorder_and_resample(dof_array, fd_data): dof_array[self.group_nr] = fd_data[self.mm2fd_node_mapping] np.matmul(dof_array[self.group_nr], self._resampling_mat_fd2mm.T, - out=dof_array[self.group_nr] + out=dof_array[self.group_nr]) + # If scalar, just reorder and resample out if fspace_shape == tuple(): reorder_and_resample(out, function_data) else: # otherwise, have to grab each dofarray and the corresponding # data from *function_data* - with np.nditer(out, op_flags=['readwrite', 'multi_index'] as it: + with np.nditer(out, op_flags=['readwrite', 'multi_index']) as it: for dof_array in it: fd_data = function_data[:, it.multi_index] - reorder_and_resample(dof_array, function_data) + reorder_and_resample(dof_array, fd_data) return out @@ -459,18 +458,29 @@ class FiredrakeConnection: If *out* is supplied, values at nodes associated to NO meshmode nodes are not modified. - :arg mm_field: A numpy array of shape *(nnodes,)* or *(..., nnodes)* - representing a function on :attr:`to_distr` - (where nnodes is the number of nodes in *self.discr*. Note - that only data from group number *self.group_nr* will be - transported). + :arg mm_field: Either + * A :class:`meshmode.dof_array.DOFArray` representing + a field of shape *tuple()* on :attr:`discr` + * A :class:`numpy.ndarray` of :class:`meshmode.dof_array.DOFArray`s + representing a field of shape *mm_field.shape* + on :attr:`discr` + + See :class:`meshmode.dof.DOFArray` for further requirements. + The :attr:`group_nr`th entry of each :class:`DOFArray` + must be of shape *(nelements, nunit_dofs)* and + the *element_dtype* must match that used for + :mod:`firedrake` :class:`Function`s + :arg out: If *None* then ignored, otherwise a :mod:`firedrake` function of the right function space for the transported data - to be stored in. + to be stored in. The shape of its function space must + match the shape of *mm_field* + :arg assert_fdrake_discontinuous: If *True*, disallows conversion to a continuous firedrake function space (i.e. this function checks that ``self.firedrake_fspace()`` is discontinuous and raises a *ValueError* otherwise) + :arg continuity_tolerance: If converting to a continuous firedrake function space (i.e. if ``self.firedrake_fspace()`` is continuous), assert that at any two meshmode nodes corresponding to the @@ -481,58 +491,67 @@ class FiredrakeConnection: the firedrake function space is discontinuous :return: a :mod:`firedrake` :class:`Function` holding the transported - data. + data (*out*, if *out* was not *None*) """ if self._ufl_element.family() == 'Lagrange' \ and assert_fdrake_discontinuous: raise ValueError("Trying to convert to continuous function space " " with :arg:`assert_fdrake_discontinuous` set " " to *True*") - dtype = self.firedrake_fspace().mesh().coordinates.dat.dat.dtype + # All firedrake functions are the same dtype + dtype = self.firedrake_fspace().mesh().coordinates.dat.data.dtype self._validate_field(mm_field, "mm_field", dtype=dtype) + + # get the shape of mm_field + if isinstance(mm_field, np.ndarray): + fspace_shape = mm_field.shape + else: + fspace_shape = tuple() + # make sure out is a firedrake function in an appropriate # function space if out is not None: - if isinstance(mm_field, np.ndarray): - shape = mm_field.shape - else: - shape = tuple() - self._validate_function(out, "out", shape, dtype) + self._validate_function(out, "out", fspace_shape, dtype) else: from firedrake.function import Function - if len(mm_field.shape) == 1: + # Translate shape so that don't always get a TensorFunctionSpace, + # but instead get FunctionSpace or VectorFunctionSpace when + # reasonable + shape = fspace_shape + if shape == tuple(): shape = None - elif len(mm_field.shape) == 2: - shape = mm_field.shape[0] - else: - shape = mm_field.shape[:-1] + elif len(shape) == 1: + shape = shape[0] + # make a function filled with zeros out = Function(self.firedrake_fspace(shape)) out.dat.data[:] = 0.0 # Handle 1-D case - if len(out.dat.data.shape) == 1 and len(mm_field.shape) > 1: - mm_field = mm_field.reshape(mm_field.shape[1]) - - # resample from nodes on reordered view. Have to do this in - # a bit of a roundabout way to be careful about duplicated - # firedrake nodes. - el_group = self.discr.groups[self.group_nr] - by_cell_field_view = el_group.view(mm_field) - - # Get firedrake data from out into meshmode ordering and view by cell - reordered_outdata = \ - np.moveaxis(out.dat.data, 0, -1)[..., self.mm2fd_node_mapping] - by_cell_reordered_view = el_group.view(reordered_outdata) - # Resample this reordered data - np.matmul(by_cell_field_view, self._resampling_mat_mm2fd.T, - out=by_cell_reordered_view) - # Now store the resampled data back in the firedrake order - out.dat.data[self.mm2fd_node_mapping] = \ - np.moveaxis(reordered_outdata, -1, 0) + if len(out.dat.data.shape) == 1 and isinstance(mm_field, np.ndarray): + mm_field = mm_field[0] + + def resample_and_reorder(fd_data, dof_array): + # Resample to firedrake + resampled_dof_arr = np.matmul(dof_array[self.group_nr], + self._resampling_mat_mm2fd.T) + # store using correct ordering + fd_data[self.mm2fd_node_mapping] = resampled_dof_arr + + # If scalar, just reorder and resample out + if fspace_shape == tuple(): + resample_and_reorder(out.dat.data, mm_field) + else: + # otherwise, have to grab each dofarray and the corresponding + # data from *function_data* + with np.nditer(mm_field, op_flags=['readwrite', 'multi_index']) as it: + for dof_array in it: + fd_data = out.dat.data[:, it.multi_index] + resample_and_reorder(fd_data, dof_array) # Continuity checks if requested if self._ufl_element.family() == 'Lagrange' \ and continuity_tolerance is not None: + raise NotImplementedError("This got harder with DOFArrays") assert isinstance(continuity_tolerance, float) assert continuity_tolerance >= 0 # Check each firedrake node which has been duplicated @@ -541,13 +560,13 @@ class FiredrakeConnection: for fd_inode, duplicated_mm_nodes in \ six.iteritems(self._duplicate_nodes): mm_inode = duplicated_mm_nodes[0] - # Make sure to compare using reordered_outdata not mm_field, + # Make sure not to compare using mm_field, # because two meshmode nodes associated to the same firedrake # nodes may have been resampled to distinct nodes on different - # elements. reordered_outdata has undone that resampling. + # elements. for dup_mm_inode in duplicated_mm_nodes[1:]: - dist = la.norm(reordered_outdata[..., mm_inode] - - reordered_outdata[..., dup_mm_inode]) + dist = la.norm(mm_field[..., mm_inode] + - mm_field[..., dup_mm_inode]) if dist >= continuity_tolerance: raise ValueError("Meshmode nodes %s and %s represent " "the same firedrake node %s, but " @@ -570,9 +589,9 @@ class FromFiredrakeConnection(FiredrakeConnection): meshmode discretization and allows transfer of functions to and from :mod:`firedrake`. """ - def __init__(self, cl_ctx, fdrake_fspace): + def __init__(self, actx, fdrake_fspace): """ - :arg cl_ctx: A :mod:`pyopencl` computing context + :arg actx: A :class:`meshmode.array_context.ArrayContext` :arg fdrake_fspace: A :mod:`firedrake` ``"CG"`` or ``"DG"`` function space (of class :class:`WithGeometry`) built on a mesh which is importable by :func:`import_firedrake_mesh`. @@ -596,7 +615,7 @@ class FromFiredrakeConnection(FiredrakeConnection): # Create to_discr mm_mesh, orient = import_firedrake_mesh(fdrake_fspace.mesh()) factory = InterpolatoryQuadratureSimplexGroupFactory(ufl_elt.degree()) - to_discr = Discretization(cl_ctx, mm_mesh, factory) + to_discr = Discretization(actx, mm_mesh, factory) # get firedrake unit nodes and map onto meshmode reference element group = to_discr.groups[0] @@ -623,12 +642,12 @@ class FromFiredrakeConnection(FiredrakeConnection): flip_mat = get_simplex_element_flip_matrix(ufl_elt.degree(), fd_unit_nodes) fd_cell_node_list = fdrake_fspace.cell_node_list + # flip fd_cell_node_list _reorder_nodes(orient, fd_cell_node_list, flip_mat, unflip=False) - mm2fd_node_mapping = fd_cell_node_list.flatten() super(FromFiredrakeConnection, self).__init__(to_discr, fdrake_fspace, - mm2fd_node_mapping) + fd_cell_node_list) if fdrake_fspace.ufl_element().family() == 'Discontinuous Lagrange': assert len(self._duplicate_nodes) == 0, \ "Somehow a firedrake node in a 'DG' space got duplicated..." \ @@ -663,9 +682,9 @@ class FromBdyFiredrakeConnection(FiredrakeConnection): :class:`firedrake.bcs.DirichletBC`. ``"on_boundary"`` corresponds to the entire boundary. """ - def __init__(self, cl_ctx, fdrake_fspace, bdy_id): + def __init__(self, actx, fdrake_fspace, bdy_id): """ - :arg cl_ctx: A :mod:`pyopencl` computing context + :arg actx: A :class:`meshmode.array_context.ArrayContext` :arg fdrake_fspace: A :mod:`firedrake` ``"CG"`` or ``"DG"`` function space (of class :class:`WithGeometry`) built on a mesh which is importable by :func:`import_firedrake_mesh`. @@ -694,7 +713,7 @@ class FromBdyFiredrakeConnection(FiredrakeConnection): mm_mesh, orient = import_firedrake_mesh(fdrake_fspace.mesh(), cells_to_use=cells_to_use) factory = InterpolatoryQuadratureSimplexGroupFactory(ufl_elt.degree()) - to_discr = Discretization(cl_ctx, mm_mesh, factory) + to_discr = Discretization(actx, mm_mesh, factory) # get firedrake unit nodes and map onto meshmode reference element group = to_discr.groups[0] @@ -709,12 +728,12 @@ class FromBdyFiredrakeConnection(FiredrakeConnection): flip_mat = get_simplex_element_flip_matrix(ufl_elt.degree(), fd_unit_nodes) fd_cell_node_list = fdrake_fspace.cell_node_list[cells_to_use] + # flip fd_cell_node_list _reorder_nodes(orient, fd_cell_node_list, flip_mat, unflip=False) - mm2fd_node_mapping = fd_cell_node_list.flatten() super(FromBdyFiredrakeConnection, self).__init__(to_discr, fdrake_fspace, - mm2fd_node_mapping) + fd_cell_node_list) if fdrake_fspace.ufl_element().family() == 'Discontinuous Lagrange': assert len(self._duplicate_nodes) == 0, \ "Somehow a firedrake node in a 'DG' space got duplicated..." \ @@ -768,12 +787,12 @@ class ToFiredrakeConnection(FiredrakeConnection): # **_cell_node holds the node nrs in shape *(ncells, nunit_nodes)* fd_cell_node = fspace.cell_node_list - mm_cell_node = el_group.view(np.arange(discr.nnodes)) # To get the meshmode to firedrake node assocation, we need to handle # local vertex reordering and cell reordering. from pyop2.datatypes import IntType - reordering_arr = np.arange(el_group.nnodes, dtype=IntType) + mm2fd_node_mapping = np.ndarray((el_group.nelements, el_group.nunit_dofs), + dtype=IntType) for perm, cells in six.iteritems(perm2cells): # reordering_arr[i] should be the fd node corresponding to meshmode # node i @@ -796,11 +815,11 @@ class ToFiredrakeConnection(FiredrakeConnection): flip_mat = np.rint(flip_mat).astype(IntType) fd_permuted_cell_node = np.matmul(fd_cell_node[fd_cell_order[cells]], flip_mat.T) - reordering_arr[mm_cell_node[cells]] = fd_permuted_cell_node + mm2fd_node_mapping[cells] = fd_permuted_cell_node super(ToFiredrakeConnection, self).__init__(discr, fspace, - reordering_arr, + mm2fd_node_mapping, group_nr=group_nr) assert len(self._duplicate_nodes) == 0, \ "Somehow a firedrake node in a 'DG' space got duplicated..." \ diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 1ce8702a..2b73c856 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -33,7 +33,7 @@ from modepy import resampling_matrix from meshmode.mesh import (BTAG_ALL, BTAG_REALLY_ALL, BTAG_NO_BOUNDARY, FacialAdjacencyGroup, Mesh, NodalAdjacency, SimplexElementGroup) from meshmode.interop.firedrake.reference_cell import ( - get_affine_reference_simplex_mapping, get_finat_element_unit_dofs) + get_affine_reference_simplex_mapping, get_finat_element_unit_nodes) # {{{ functions to extract information from Mesh Topology @@ -531,9 +531,7 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, cell_dim = fdrake_mesh.cell_dimension() # Get finat unit nodes and map them onto the meshmode reference simplex - from meshmode.interop.firedrake.reference_cell import ( - get_affine_reference_simplex_mapping, get_finat_element_unit_dofs) - finat_unit_nodes = get_finat_element_unit_dofs(coord_finat_elt) + finat_unit_nodes = get_finat_element_unit_nodes(coord_finat_elt) fd_ref_to_mm = get_affine_reference_simplex_mapping(cell_dim, True) finat_unit_nodes = fd_ref_to_mm(finat_unit_nodes) @@ -779,7 +777,7 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): # get firedrake unit nodes and map onto meshmode reference element fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(group.dim, True) - fd_unit_nodes = get_finat_element_unit_dofs(coords_fspace.finat_element) + fd_unit_nodes = get_finat_element_unit_nodes(coords_fspace.finat_element) fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) from meshmode.discretization.poly_element import ( diff --git a/meshmode/interop/firedrake/reference_cell.py b/meshmode/interop/firedrake/reference_cell.py index d2a00194..6ad6761b 100644 --- a/meshmode/interop/firedrake/reference_cell.py +++ b/meshmode/interop/firedrake/reference_cell.py @@ -26,7 +26,7 @@ import numpy.linalg as la __doc__ = """ .. autofunction:: get_affine_reference_simplex_mapping -.. autofunction:: get_finat_element_unit_dofs +.. autofunction:: get_finat_element_unit_nodes """ @@ -109,7 +109,7 @@ def get_affine_reference_simplex_mapping(spat_dim, firedrake_to_meshmode=True): # {{{ Get firedrake unit nodes -def get_finat_element_unit_dofs(finat_element): +def get_finat_element_unit_nodes(finat_element): """ Returns the unit nodes used by the FInAT element in firedrake's (equivalently, FInAT/FIAT's) reference coordinates @@ -130,8 +130,8 @@ def get_finat_element_unit_dofs(finat_element): # so to recover node *i* we need to evaluate *p_i* at the identity # function point_evaluators = finat_element._element.dual.nodes - unit_dofs = [p(lambda x: x) for p in point_evaluators] - return np.array(unit_dofs).T + unit_nodes = [p(lambda x: x) for p in point_evaluators] + return np.array(unit_nodes).T # }}} -- GitLab From 847e0ed90d7bfe5e19f9b23518429f4a2225af8c Mon Sep 17 00:00:00 2001 From: benSepanski Date: Fri, 17 Jul 2020 10:22:05 -0500 Subject: [PATCH 099/221] Handle firedrake programs along with kernels --- meshmode/array_context.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/meshmode/array_context.py b/meshmode/array_context.py index 88d3ecf4..8071bb96 100644 --- a/meshmode/array_context.py +++ b/meshmode/array_context.py @@ -276,8 +276,13 @@ class PyOpenCLArrayContext(ArrayContext): def call_loopy(self, program, **kwargs): program = self.transform_loopy_program(program) - assert program.options.return_dict - assert program.options.no_numpy + + try: + options = program.options + except AttributeError: + options = program.root_kernel.options + assert options.return_dict + assert options.no_numpy evt, result = program(self.queue, **kwargs, allocator=self.allocator) return result @@ -295,7 +300,10 @@ class PyOpenCLArrayContext(ArrayContext): def transform_loopy_program(self, program): # FIXME: This could be much smarter. import loopy as lp - all_inames = program.all_inames() + try: + all_inames = program.all_inames() + except AttributeError: + all_inames = program.root_kernel.all_inames() inner_iname = None if "iel" not in all_inames and "i0" in all_inames: -- GitLab From f20d57bac8d707b3ea249a3caff44a67d2b24981 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Fri, 17 Jul 2020 10:22:20 -0500 Subject: [PATCH 100/221] Make sure using DOFArrays for meshmode fields --- test/test_firedrake_interop.py | 81 ++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 27 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 1ea0567b..2b112a9f 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -34,12 +34,14 @@ from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) -from meshmode.array_tools import PyOpenCLArrayContext +from meshmode.array_context import PyOpenCLArrayContext from meshmode.discretization import Discretization from meshmode.discretization.poly_element import ( InterpolatoryQuadratureSimplexGroupFactory) +from meshmode.dof_array import DOFArray + from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL, check_bc_coverage from meshmode.interop.firedrake import ( @@ -169,8 +171,8 @@ def check_consistency(fdrake_fspace, discr, group_nr=0): finat_elt = fdrake_fspace.finat_element assert len(discr.groups) == 1 assert discr.groups[group_nr].order == finat_elt.degree - assert discr.groups[group_nr].nunit_nodes == finat_elt.space_dimension() - assert discr.nnodes == fdrake_fspace.node_count + assert discr.groups[group_nr].nunit_dofs == finat_elt.space_dimension() + assert discr.ndofs == fdrake_fspace.node_count def test_fd2mm_consistency(ctx_factory, fdrake_mesh, fspace_degree): @@ -276,7 +278,7 @@ def test_from_bdy_consistency(ctx_factory, finat_elt = fdrake_fspace.finat_element assert len(discr.groups) == 1 assert discr.groups[0].order == finat_elt.degree - assert discr.groups[0].nunit_nodes == finat_elt.space_dimension() + assert discr.groups[0].nunit_dofs == finat_elt.space_dimension() # }}} @@ -355,6 +357,7 @@ def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values, # TODO : Add test for ToFiredrakeConnection where group_nr != 0 # {{{ Double check functions are being transported correctly + def alternating_sum_fd(spatial_coord): """ Return an expression x1 - x2 + x3 -+... @@ -365,14 +368,15 @@ def alternating_sum_fd(spatial_coord): ) -def alternating_sum_mm(nodes): +def alternating_sum_mm(group_nodes): """ - Take the *(dim, nnodes)* array nodes and return an array - holding the alternating sum of the coordinates of each node + Take the *(dim, nelements, nunit_dofs)* np array group_nodes and return + an *(nelements, nunit_dofs)* + array holding the alternating sum of the coordinates of each node """ - alternator = np.ones(nodes.shape[0]) + alternator = np.ones(group_nodes.shape[0]) alternator[1::2] *= -1 - return np.matmul(alternator, nodes) + return np.einsum("i,ijk->jk", alternator, group_nodes) # In 1D/2D/3D check constant 1, @@ -380,9 +384,11 @@ def alternating_sum_mm(nodes): # This should show that projection to any coordinate in 1D/2D/3D # transfers correctly. test_functions = [ - (lambda spatial_coord: Constant(1.0), lambda nodes: np.ones(nodes.shape[1])), - (lambda spatial_coord: spatial_coord[0], lambda nodes: nodes[0, :]), - (sum, lambda nodes: np.sum(nodes, axis=0)), + (lambda spatial_coord: Constant(1.0), + lambda grp_nodes: np.ones(grp_nodes.shape[1:])), + (lambda spatial_coord: spatial_coord[0], + lambda grp_nodes: grp_nodes[0, ...]), + (sum, lambda grp_nodes: np.sum(grp_nodes, axis=0)), (alternating_sum_fd, alternating_sum_mm) ] @@ -415,21 +421,28 @@ def test_from_fd_transfer(ctx_factory, else: fdrake_connection = FromFiredrakeConnection(actx, fdrake_fspace) - # transport fdrake function - fd2mm_f = fdrake_connection.from_firedrake(fdrake_f) + # transport fdrake function and put in numpy + fd2mm_f = fdrake_connection.from_firedrake(fdrake_f, actx=actx) + fd2mm_f = actx.to_numpy(fd2mm_f[0]) # build same function in meshmode discr = fdrake_connection.discr - nodes = discr.nodes().get(queue=queue) - meshmode_f = meshmode_f_eval(nodes) + # nodes is np array (ambient_dim,) of DOFArray (ngroups,) + # of arrays (nelements, nunit_dofs), we want a single np array + # of shape (ambient_dim, nelements, nunit_dofs) + nodes = discr.nodes() + group_nodes = np.array([actx.to_numpy(dof_arr[0]) for dof_arr in nodes]) + meshmode_f = meshmode_f_eval(group_nodes) # fd -> mm should be same as creating in meshmode np.testing.assert_allclose(fd2mm_f, meshmode_f, atol=CLOSE_ATOL) if not only_convert_bdy: # now transport mm -> fd + meshmode_f_dofarr = discr.zeros(actx) + meshmode_f_dofarr[0][:] = meshmode_f mm2fd_f = \ - fdrake_connection.from_meshmode(meshmode_f, + fdrake_connection.from_meshmode(meshmode_f_dofarr, assert_fdrake_discontinuous=False, continuity_tolerance=1e-8) # mm -> fd should be same as creating in firedrake @@ -454,8 +467,10 @@ def test_to_fd_transfer(ctx_factory, mm_mesh, fspace_degree, factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) discr = Discretization(actx, mm_mesh, factory) - nodes = discr.nodes().get(queue=queue) - meshmode_f = meshmode_f_eval(nodes) + nodes = discr.nodes() + group_nodes = np.array([actx.to_numpy(dof_arr[0]) for dof_arr in nodes]) + meshmode_f = discr.zeros(actx) + meshmode_f[0][:] = meshmode_f_eval(group_nodes) # connect to firedrake and evaluate expr in firedrake fdrake_connection = ToFiredrakeConnection(discr) @@ -518,7 +533,7 @@ def test_from_fd_idempotency(ctx_factory, if only_convert_bdy: fdrake_connection = FromBdyFiredrakeConnection(actx, fdrake_fspace, 'on_boundary') - temp = fdrake_connection.from_firedrake(fdrake_unique) + temp = fdrake_connection.from_firedrake(fdrake_unique, actx=actx) fdrake_unique = \ fdrake_connection.from_meshmode(temp, assert_fdrake_discontinuous=False, @@ -527,9 +542,10 @@ def test_from_fd_idempotency(ctx_factory, fdrake_connection = FromFiredrakeConnection(actx, fdrake_fspace) # Test for idempotency fd->mm->fd - mm_field = fdrake_connection.from_firedrake(fdrake_unique) + mm_field = fdrake_connection.from_firedrake(fdrake_unique, actx=actx) fdrake_unique_copy = Function(fdrake_fspace) - fdrake_connection.from_meshmode(mm_field, fdrake_unique_copy, + fdrake_connection.from_meshmode(mm_field, + out=fdrake_unique_copy, assert_fdrake_discontinuous=False, continuity_tolerance=1e-8) @@ -538,8 +554,18 @@ def test_from_fd_idempotency(ctx_factory, atol=CLOSE_ATOL) # Test for idempotency (fd->)mm->fd->mm - mm_field_copy = fdrake_connection.from_firedrake(fdrake_unique_copy) - np.testing.assert_allclose(mm_field_copy, mm_field, atol=CLOSE_ATOL) + mm_field_copy = fdrake_connection.from_firedrake(fdrake_unique_copy, + actx=actx) + if fspace_type == "scalar": + np.testing.assert_allclose(actx.to_numpy(mm_field_copy[0]), + actx.to_numpy(mm_field[0]), + atol=CLOSE_ATOL) + else: + for dof_arr_cp, dof_arr in zip(mm_field_copy.flatten(), + mm_field.flatten()): + np.testing.assert_allclose(actx.to_numpy(dof_arr_cp[0]), + actx.to_numpy(dof_arr[0]), + atol=CLOSE_ATOL) def test_to_fd_idempotency(ctx_factory, mm_mesh, fspace_degree): @@ -557,12 +583,13 @@ def test_to_fd_idempotency(ctx_factory, mm_mesh, fspace_degree): factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) discr = Discretization(actx, mm_mesh, factory) fdrake_connection = ToFiredrakeConnection(discr) - mm_unique = np.arange(discr.nnodes, dtype=np.float64) - mm_unique_copy = np.copy(mm_unique) + mm_unique = discr.zeros(actx, dtype=np.float64) + mm_unique[0][:] = np.arange(np.size(mm_unique[0])) + mm_unique_copy = actx.np.copy(mm_unique) # Test for idempotency mm->fd->mm fdrake_unique = fdrake_connection.from_meshmode(mm_unique) - fdrake_connection.from_firedrake(fdrake_unique, mm_unique_copy) + fdrake_connection.from_firedrake(fdrake_unique, out=mm_unique_copy) np.testing.assert_allclose(mm_unique_copy, mm_unique, atol=CLOSE_ATOL) -- GitLab From 7bc33af7742b056a9f8fe10bd4aae4a98202fb53 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Fri, 17 Jul 2020 10:22:37 -0500 Subject: [PATCH 101/221] Be more careful with indexing, handling meshmode fields --- meshmode/interop/firedrake/connection.py | 173 ++++++++++++++--------- 1 file changed, 104 insertions(+), 69 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index b13d6fa2..15abd028 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -200,7 +200,7 @@ class FiredrakeConnection: # }}} # Get meshmode unit nodes - mm_unit_nodes = element_grp.unit_nodes() + mm_unit_nodes = element_grp.unit_nodes # get firedrake unit nodes and map onto meshmode reference element tdim = fdrake_fspace.mesh().topological_dimension() fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(tdim, True) @@ -219,15 +219,25 @@ class FiredrakeConnection: # to the same firedrake node unique_fd_nodes, counts = np.unique(mm2fd_node_mapping, return_counts=True) - # self._duplicate_nodes - # maps firedrake nodes associated to more than 1 meshmode node + # map firedrake nodes associated to more than 1 meshmode node # to all associated meshmode nodes. - self._duplicate_nodes = {} + # fd node index -> (meshmode cell index, meshmode local node index) + fd_to_dupes = {} dup_fd_nodes = set(unique_fd_nodes[counts > 1]) - for mm_inode, fd_inode in enumerate(mm2fd_node_mapping): - if fd_inode in dup_fd_nodes: - self._duplicate_nodes.setdefault(fd_inode, []) - self._duplicate_nodes[fd_inode].append(mm_inode) + for icell, cell in enumerate(mm2fd_node_mapping): + for local_mm_inode, fd_inode in enumerate(cell): + if fd_inode in dup_fd_nodes: + fd_to_dupes.setdefault(fd_inode, []) + fd_to_dupes[fd_inode] = (icell, local_mm_inode) + # A list of tuples, each tuple represents an equivalence class + # of meshmode nodes + # (represented as tuples + # *(meshmode_cell_index, meshmode local node index)*) + # which associate to the same firedrake node under + # *mm2fd_node_mapping*. Only equivalence classes of size > 1 + # are included. + self._mm_node_equiv_classes = [tuple(equiv_class) for equiv_class + in six.itervalues(fd_to_dupes)] # Store input self.discr = discr @@ -337,9 +347,9 @@ class FiredrakeConnection: raise TypeError(arr_name + " must be of type " ":class:`meshmode.dof_array.DOFArray`, " "not :class:`%s`." % type(arr)) - if arr.shape != self.discr.groups.shape: + if arr.shape != tuple([len(self.discr.groups)]): raise ValueError(arr_name + " shape must be %s, not %s." - % (self.discr.groups.shape, arr.shape)) + % (tuple([len(self.discr.groups)]), arr.shape)) if arr[self.group_nr].shape != group_shape: raise ValueError(arr_name + "[%s].shape must be %s, not %s" % (self.group_nr, @@ -358,9 +368,25 @@ class FiredrakeConnection: if shape is not None and field.shape != shape: raise ValueError(field_name + ".shape must be %s, not %s" % (shape, field.shape)) - for i, arr in np.flatten(field): - arr_name = "%s[%s]" % (field_name, np.unravel_index(i, shape)) - check_dof_array(arr, arr_name) + for i, arr in enumerate(field.flatten()): + arr_name = "%s[%s]" % (field_name, np.unravel_index(i, field.shape)) + try: + check_dof_array(arr, arr_name) + except TypeError as e: + msg = e.args[0] + prefix = "%s is a numpy array of shape %s, which is " \ + "interpreted as a mapping into a space of shape " \ + "%s. For each multi-index *mi*, the " \ + "*mi*th coordinate values of %s should be " \ + "represented as a DOFArray stored in %s[mi]. If you " \ + "are not trying to represent a mapping into a space " \ + "of shape %s, look at the documentation for " \ + "FiredrakeConnection.from_meshmode or " \ + "FiredrakeConnection.from_firedrake to see how " \ + "fields in a discretization are represented." \ + % (field_name, field.shape, field.shape, field_name, + field_name, field.shape) + raise TypeError(prefix + "\n" + msg) else: raise TypeError("field must be of type DOFArray or np.ndarray", "not %s." % type(field)) @@ -423,26 +449,34 @@ class FiredrakeConnection: if fspace_shape == tuple(): out = self.discr.zeros(actx, dtype=function_data.dtype) else: - out = \ - np.array([self.discr.zeros(actx, dtype=function_data.dtype) - for _ in np.prod(fspace_shape)] - ).reshape(fspace_shape) + out = np.ndarray(fspace_shape, dtype=np.object) + for multi_index in np.ndindex(fspace_shape): + out[multi_index] = \ + self.discr.zeros(actx, dtype=function_data.dtype) def reorder_and_resample(dof_array, fd_data): - dof_array[self.group_nr] = fd_data[self.mm2fd_node_mapping] - np.matmul(dof_array[self.group_nr], self._resampling_mat_fd2mm.T, - out=dof_array[self.group_nr]) + # put the firedrake data in meshmode order and then resample, + # storing in dof_array + dof_array[self.group_nr].set( + np.matmul(fd_data[self.mm2fd_node_mapping], + self._resampling_mat_fd2mm.T) + ) # If scalar, just reorder and resample out if fspace_shape == tuple(): reorder_and_resample(out, function_data) else: + # firedrake drops extra dimensions + if len(function_data.shape) != 1 + len(fspace_shape): + shape = (function_data.shape[0],) + fspace_shape + function_data = function_data.reshape(shape) # otherwise, have to grab each dofarray and the corresponding # data from *function_data* - with np.nditer(out, op_flags=['readwrite', 'multi_index']) as it: - for dof_array in it: - fd_data = function_data[:, it.multi_index] - reorder_and_resample(dof_array, fd_data) + for multi_index in np.ndindex(fspace_shape): + dof_array = out[multi_index] + index = (np.s_[:],) + multi_index + fd_data = function_data[index] + reorder_and_resample(dof_array, fd_data) return out @@ -486,7 +520,8 @@ class FiredrakeConnection: assert that at any two meshmode nodes corresponding to the same firedrake node (meshmode is a discontinuous space, so this situation will almost certainly happen), the function being transported - has values at most *continuity_tolerance* distance + has values less than *continuity_tolerance* distance (in + :math:`\\ell^\\infty` distance) apart. If *None*, no checks are performed. Does nothing if the firedrake function space is discontinuous @@ -503,7 +538,8 @@ class FiredrakeConnection: self._validate_field(mm_field, "mm_field", dtype=dtype) # get the shape of mm_field - if isinstance(mm_field, np.ndarray): + from meshmode.dof_array import DOFArray + if not isinstance(mm_field, DOFArray): fspace_shape = mm_field.shape else: fspace_shape = tuple() @@ -526,54 +562,53 @@ class FiredrakeConnection: out = Function(self.firedrake_fspace(shape)) out.dat.data[:] = 0.0 - # Handle 1-D case - if len(out.dat.data.shape) == 1 and isinstance(mm_field, np.ndarray): - mm_field = mm_field[0] + out_data = out.dat.data + # Handle firedrake dropping dimensions + if len(out.dat.data.shape) != 1 + len(fspace_shape): + shape = (out.dat.data.shape[0],) + fspace_shape + out_data = out_data.reshape(shape) def resample_and_reorder(fd_data, dof_array): - # Resample to firedrake - resampled_dof_arr = np.matmul(dof_array[self.group_nr], - self._resampling_mat_mm2fd.T) - # store using correct ordering - fd_data[self.mm2fd_node_mapping] = resampled_dof_arr + # pull data into numpy + dof_np = dof_array.array_context.to_numpy(dof_array[self.group_nr]) + # resample the data data (keep this for continuity checks) + resampled_data = np.matmul(dof_np, self._resampling_mat_mm2fd.T) + # store resampled data in firedrake ordering + fd_data[self.mm2fd_node_mapping] = resampled_data + + # Continuity checks if requested + if self._ufl_element.family() == 'Lagrange' \ + and continuity_tolerance is not None: + assert isinstance(continuity_tolerance, float) + assert continuity_tolerance >= 0 + # Make sure not to compare using mm_field, + # because two meshmode nodes associated to the same firedrake + # nodes may have been resampled to distinct nodes on different + # elements. + for mm_equiv_class in self._mm_node_equiv_classes: + l_inf = np.ptp(resampled_data[mm_equiv_class]) + if l_inf >= continuity_tolerance: + fd_inode = self.mm2fd_node_mapping[mm_equiv_class[0]] + raise ValueError("Meshmode nodes %s (written as " + " *(element index, local node index)*)" + " represent the same firedrake node %s" + ", but :arg:`mm_field`'s resampled " + " values are %s > %s apart)" + % (mm_equiv_class, fd_inode, + l_inf, continuity_tolerance)) # If scalar, just reorder and resample out if fspace_shape == tuple(): - resample_and_reorder(out.dat.data, mm_field) + resample_and_reorder(out_data, mm_field) else: # otherwise, have to grab each dofarray and the corresponding # data from *function_data* - with np.nditer(mm_field, op_flags=['readwrite', 'multi_index']) as it: - for dof_array in it: - fd_data = out.dat.data[:, it.multi_index] - resample_and_reorder(fd_data, dof_array) - - # Continuity checks if requested - if self._ufl_element.family() == 'Lagrange' \ - and continuity_tolerance is not None: - raise NotImplementedError("This got harder with DOFArrays") - assert isinstance(continuity_tolerance, float) - assert continuity_tolerance >= 0 - # Check each firedrake node which has been duplicated - # that all associated values are within the continuity - # tolerance - for fd_inode, duplicated_mm_nodes in \ - six.iteritems(self._duplicate_nodes): - mm_inode = duplicated_mm_nodes[0] - # Make sure not to compare using mm_field, - # because two meshmode nodes associated to the same firedrake - # nodes may have been resampled to distinct nodes on different - # elements. - for dup_mm_inode in duplicated_mm_nodes[1:]: - dist = la.norm(mm_field[..., mm_inode] - - mm_field[..., dup_mm_inode]) - if dist >= continuity_tolerance: - raise ValueError("Meshmode nodes %s and %s represent " - "the same firedrake node %s, but " - ":arg:`mm_field`'s values are " - " %s > %s apart)" - % (mm_inode, dup_mm_inode, fd_inode, - dist, continuity_tolerance)) + for multi_index in np.ndindex(fspace_shape): + # have to be careful to take view and not copy + index = (np.s_[:],) + multi_index + fd_data = out_data[index] + dof_array = mm_field[multi_index] + resample_and_reorder(fd_data, dof_array) return out @@ -649,7 +684,7 @@ class FromFiredrakeConnection(FiredrakeConnection): fdrake_fspace, fd_cell_node_list) if fdrake_fspace.ufl_element().family() == 'Discontinuous Lagrange': - assert len(self._duplicate_nodes) == 0, \ + assert len(self._mm_node_equiv_classes) == 0, \ "Somehow a firedrake node in a 'DG' space got duplicated..." \ "contact the developer." @@ -735,7 +770,7 @@ class FromBdyFiredrakeConnection(FiredrakeConnection): fdrake_fspace, fd_cell_node_list) if fdrake_fspace.ufl_element().family() == 'Discontinuous Lagrange': - assert len(self._duplicate_nodes) == 0, \ + assert len(self._mm_node_equiv_classes) == 0, \ "Somehow a firedrake node in a 'DG' space got duplicated..." \ "contact the developer." @@ -821,7 +856,7 @@ class ToFiredrakeConnection(FiredrakeConnection): fspace, mm2fd_node_mapping, group_nr=group_nr) - assert len(self._duplicate_nodes) == 0, \ + assert len(self._mm_node_equiv_classes) == 0, \ "Somehow a firedrake node in a 'DG' space got duplicated..." \ "contact the developer." -- GitLab From b27d9d890e517d609700cb97a5d676deda42407d Mon Sep 17 00:00:00 2001 From: benSepanski Date: Fri, 17 Jul 2020 13:28:25 -0500 Subject: [PATCH 102/221] updated to_fd_idempotency test to DOFArray s --- test/test_firedrake_interop.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 2b112a9f..88e9ed72 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -1,9 +1,3 @@ -# TODO: -# * Make sure from_meshmode and from_firedrake receive -# DOFArrays -# * Make sure output of from_firedrake is treated as -# DOFArray -# * Run tests and debug __copyright__ = "Copyright (C) 2020 Benjamin Sepanski" __license__ = """ @@ -583,15 +577,21 @@ def test_to_fd_idempotency(ctx_factory, mm_mesh, fspace_degree): factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) discr = Discretization(actx, mm_mesh, factory) fdrake_connection = ToFiredrakeConnection(discr) - mm_unique = discr.zeros(actx, dtype=np.float64) - mm_unique[0][:] = np.arange(np.size(mm_unique[0])) - mm_unique_copy = actx.np.copy(mm_unique) + fdrake_mesh = fdrake_connection.firedrake_fspace().mesh() + dtype = fdrake_mesh.coordinates.dat.data.dtype + + mm_unique = discr.zeros(actx, dtype=dtype) + unique_vals = np.arange(np.size(mm_unique[0]), dtype=dtype) + mm_unique[0].set(unique_vals.reshape(mm_unique[0].shape)) + mm_unique_copy = DOFArray.from_list(actx, [mm_unique[0].copy()]) # Test for idempotency mm->fd->mm fdrake_unique = fdrake_connection.from_meshmode(mm_unique) fdrake_connection.from_firedrake(fdrake_unique, out=mm_unique_copy) - np.testing.assert_allclose(mm_unique_copy, mm_unique, atol=CLOSE_ATOL) + np.testing.assert_allclose(actx.to_numpy(mm_unique_copy[0]), + actx.to_numpy(mm_unique[0]), + atol=CLOSE_ATOL) # Test for idempotency (mm->)fd->mm->fd fdrake_unique_copy = fdrake_connection.from_meshmode(mm_unique_copy) -- GitLab From 6e00fe4a7d4985055be03d1493943ec3414dba6c Mon Sep 17 00:00:00 2001 From: benSepanski Date: Fri, 17 Jul 2020 13:51:03 -0500 Subject: [PATCH 103/221] Updated examples to fit DOFArray infrastructure --- examples/from_firedrake.py | 19 ++++++++++--------- examples/to_firedrake.py | 17 +++++++++++++---- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/examples/from_firedrake.py b/examples/from_firedrake.py index a70fb945..ddc6108a 100644 --- a/examples/from_firedrake.py +++ b/examples/from_firedrake.py @@ -47,8 +47,12 @@ def main(): # Make connections cl_ctx = cl.create_some_context() - fd_connection = FromFiredrakeConnection(cl_ctx, fd_fspace) - fd_bdy_connection = FromBdyFiredrakeConnection(cl_ctx, + queue = cl.CommandQueue(cl_ctx) + from meshmode.array_context import PyOpenCLArrayContext + actx = PyOpenCLArrayContext(queue) + + fd_connection = FromFiredrakeConnection(actx, fd_fspace) + fd_bdy_connection = FromBdyFiredrakeConnection(actx, fd_fspace, 'on_boundary') @@ -72,11 +76,9 @@ def main(): # Plot fd_fntn using FromFiredrakeConnection from meshmode.discretization.visualization import make_visualizer - queue = cl.CommandQueue(cl_ctx) discr = fd_connection.discr - vis = make_visualizer(queue, discr, discr.groups[0].order+3) - field = fd_connection.from_firedrake(fd_fntn) - field = cl.array.to_device(queue, field) + vis = make_visualizer(actx, discr, discr.groups[0].order+3) + field = fd_connection.from_firedrake(fd_fntn, actx=actx) fig = plt.figure() ax1 = fig.add_subplot(1, 2, 1, projection='3d') @@ -85,9 +87,8 @@ def main(): # Now repeat using FromBdyFiredrakeConnection bdy_discr = fd_bdy_connection.discr - bdy_vis = make_visualizer(queue, bdy_discr, bdy_discr.groups[0].order+3) - bdy_field = fd_bdy_connection.from_firedrake(fd_fntn) - bdy_field = cl.array.to_device(queue, bdy_field) + bdy_vis = make_visualizer(actx, bdy_discr, bdy_discr.groups[0].order+3) + bdy_field = fd_bdy_connection.from_firedrake(fd_fntn, actx=actx) ax2 = fig.add_subplot(1, 2, 2, projection='3d') plt.sca(ax2) diff --git a/examples/to_firedrake.py b/examples/to_firedrake.py index 20878e0b..122fb691 100644 --- a/examples/to_firedrake.py +++ b/examples/to_firedrake.py @@ -46,6 +46,8 @@ def main(): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) + from meshmode.array_context import PyOpenCLArrayContext + actx = PyOpenCLArrayContext(queue) nel_1d = 16 from meshmode.mesh.generation import generate_regular_rect_mesh @@ -60,14 +62,18 @@ def main(): from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory group_factory = InterpolatoryQuadratureSimplexGroupFactory(order=order) - discr = Discretization(cl_ctx, mesh, group_factory) + discr = Discretization(actx, mesh, group_factory) # Get our solution: we will use # Real(e^z) = Real(e^{x+iy}) # = e^x Real(e^{iy}) # = e^x cos(y) - nodes = discr.nodes().with_queue(queue).get(queue=queue) - candidate_sol = np.exp(nodes[0, :]) * np.cos(nodes[1, :]) + nodes = discr.nodes() + from meshmode.dof_array import thaw + for i in range(len(nodes)): + nodes[i] = thaw(actx, nodes[i]) + # First index is dimension + candidate_sol = actx.np.exp(nodes[0]) * actx.np.cos(nodes[1]) # }}} @@ -107,7 +113,10 @@ def main(): # {{{ Take the solution from firedrake and compare it to candidate_sol - true_sol = fd_connection.from_firedrake(sol) + true_sol = fd_connection.from_firedrake(sol, actx=actx) + # pull back into numpy + true_sol = actx.to_numpy(true_sol[0]) + candidate_sol = actx.to_numpy(candidate_sol[0]) print("l^2 difference between candidate solution and firedrake solution=", np.linalg.norm(true_sol - candidate_sol)) -- GitLab From 96dde2e4582e50599e55077e431209038dbc3ae2 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Fri, 17 Jul 2020 13:51:36 -0500 Subject: [PATCH 104/221] Added error check for frozen DOFArray s --- meshmode/interop/firedrake/connection.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 15abd028..88de2555 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -347,6 +347,9 @@ class FiredrakeConnection: raise TypeError(arr_name + " must be of type " ":class:`meshmode.dof_array.DOFArray`, " "not :class:`%s`." % type(arr)) + if arr.array_context is None: + raise ValueError(arr_name + " must have a non-*None* " + "array_context") if arr.shape != tuple([len(self.discr.groups)]): raise ValueError(arr_name + " shape must be %s, not %s." % (tuple([len(self.discr.groups)]), arr.shape)) -- GitLab From a480e18a3b400fbf5757e590a76d3e93a0372261 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 21 Jul 2020 10:39:12 -0500 Subject: [PATCH 105/221] Added export_mesh_to_firedrake to export list of interop.firedrake --- meshmode/interop/firedrake/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/meshmode/interop/firedrake/__init__.py b/meshmode/interop/firedrake/__init__.py index 1ad51ae1..19581c84 100644 --- a/meshmode/interop/firedrake/__init__.py +++ b/meshmode/interop/firedrake/__init__.py @@ -23,8 +23,10 @@ THE SOFTWARE. from meshmode.interop.firedrake.connection import ( FromBdyFiredrakeConnection, FromFiredrakeConnection, ToFiredrakeConnection) -from meshmode.interop.firedrake.mesh import import_firedrake_mesh +from meshmode.interop.firedrake.mesh import ( + import_firedrake_mesh, export_mesh_to_firedrake) __all__ = ["FromBdyFiredrakeConnection", "FromFiredrakeConnection", "ToFiredrakeConnection", "import_firedrake_mesh", + "export_mesh_to_firedrake" ] -- GitLab From 383207332284d8f8e16e55e8e2370f8551cc014e Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 21 Jul 2020 10:45:33 -0500 Subject: [PATCH 106/221] Handle possibility of exterior_facets.markers being *None* --- meshmode/interop/firedrake/mesh.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 2b73c856..4625cb9b 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -317,12 +317,19 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, ext_element_faces = ext_element_faces[to_keep] ext_neighbor_faces = ext_neighbor_faces[to_keep] - # tag the boundary - ext_neighbors = np.zeros(ext_elements.shape, dtype=IntType) - for ifac, marker in enumerate(top.exterior_facets.markers): - ext_neighbors[ifac] = -(boundary_tag_bit(BTAG_ALL) - | boundary_tag_bit(BTAG_REALLY_ALL) - | boundary_tag_bit(marker)) + # tag the boundary, making sure to record custom tags + # (firedrake "markers") if present + if top.exterior_facets.markers is not None: + ext_neighbors = np.zeros(ext_elements.shape, dtype=IntType) + for ifac, marker in enumerate(top.exterior_facets.markers): + ext_neighbors[ifac] = -(boundary_tag_bit(BTAG_ALL) + | boundary_tag_bit(BTAG_REALLY_ALL) + | boundary_tag_bit(marker)) + else: + ext_neighbors = np.full(ext_elements.shape, + -(boundary_tag_bit(BTAG_ALL) + | boundary_tag_bit(BTAG_REALLY_ALL)), + dtype=IntType) exterior_grp = FacialAdjacencyGroup(igroup=0, ineighbor=None, elements=ext_elements, -- GitLab From eb5e52894b049c8dd9ae8b7bc5ed8887d17b8fcf Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 21 Jul 2020 12:13:08 -0500 Subject: [PATCH 107/221] ooops... make sure NOT to change immutable things --- meshmode/interop/firedrake/connection.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 88de2555..a749695a 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -49,7 +49,7 @@ from meshmode.discretization import Discretization def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): """ - flips *nodes* in place according to *orient* + return a flipped copy of *nodes* according to *orient* :arg orient: An array of shape *(nelements)* of orientations, >0 for positive, <0 for negative @@ -74,10 +74,13 @@ def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): # flip nodes that need to be flipped, note that this point we act # like we are in a DG space - nodes[orient < 0] = np.einsum( + flipped_nodes = np.copy(nodes) + flipped_nodes[orient < 0] = np.einsum( "ij,ej->ei", flip_mat, nodes[orient < 0]) + return flipped_nodes + # {{{ Most basic connection between a fd function space and mm discretization @@ -681,11 +684,14 @@ class FromFiredrakeConnection(FiredrakeConnection): fd_unit_nodes) fd_cell_node_list = fdrake_fspace.cell_node_list # flip fd_cell_node_list - _reorder_nodes(orient, fd_cell_node_list, flip_mat, unflip=False) + flipped_cell_node_list = _reorder_nodes(orient, + fd_cell_node_list, + flip_mat, + unflip=False) super(FromFiredrakeConnection, self).__init__(to_discr, fdrake_fspace, - fd_cell_node_list) + flipped_cell_node_list) if fdrake_fspace.ufl_element().family() == 'Discontinuous Lagrange': assert len(self._mm_node_equiv_classes) == 0, \ "Somehow a firedrake node in a 'DG' space got duplicated..." \ @@ -767,11 +773,14 @@ class FromBdyFiredrakeConnection(FiredrakeConnection): fd_unit_nodes) fd_cell_node_list = fdrake_fspace.cell_node_list[cells_to_use] # flip fd_cell_node_list - _reorder_nodes(orient, fd_cell_node_list, flip_mat, unflip=False) + flipped_cell_node_list = _reorder_nodes(orient, + fd_cell_node_list, + flip_mat, + unflip=False) super(FromBdyFiredrakeConnection, self).__init__(to_discr, fdrake_fspace, - fd_cell_node_list) + flipped_cell_node_list) if fdrake_fspace.ufl_element().family() == 'Discontinuous Lagrange': assert len(self._mm_node_equiv_classes) == 0, \ "Somehow a firedrake node in a 'DG' space got duplicated..." \ -- GitLab From ea052b7e4cfce01b9ee396248dda7e0bb7129481 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 21 Jul 2020 12:31:28 -0500 Subject: [PATCH 108/221] Remove mesh order <= group order restriction --- meshmode/interop/firedrake/connection.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index a749695a..522b5042 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -807,8 +807,6 @@ class ToFiredrakeConnection(FiredrakeConnection): :param group_nr: The group number of the discretization to convert. If *None* there must be only one group. The selected group must be of type :class:`InterpolatoryQuadratureSimplexElementGroup`. - The mesh group ``discr.mesh.groups[group_nr]`` must have - order less than or equal to the order of ``discr.groups[group_nr]``. :param comm: Communicator to build a dmplex object on for the created firedrake mesh """ @@ -816,10 +814,6 @@ class ToFiredrakeConnection(FiredrakeConnection): assert len(discr.groups) == 1, ":arg:`group_nr` is *None*, but " \ ":arg:`discr` has %s != 1 groups." % len(discr.groups) group_nr = 0 - if discr.groups[group_nr].order < discr.mesh.groups[group_nr].order: - raise ValueError("Discretization group order must be greater than " - "or equal to the corresponding mesh group's " - "order.") el_group = discr.groups[group_nr] from firedrake.functionspace import FunctionSpace -- GitLab From 61476f52840624bf76b5e8e593d7bc3a6d65a0e5 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 21 Jul 2020 12:35:16 -0500 Subject: [PATCH 109/221] Changed transfer tests to convergence checks --- test/test_firedrake_interop.py | 298 ++++++++++++++++++++------------- 1 file changed, 184 insertions(+), 114 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 88e9ed72..7fcca3a1 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -54,7 +54,7 @@ firedrake = pytest.importorskip("firedrake") from firedrake import ( UnitIntervalMesh, UnitSquareMesh, UnitCubeMesh, FunctionSpace, VectorFunctionSpace, TensorFunctionSpace, - Function, SpatialCoordinate, Constant, as_tensor) + Function, SpatialCoordinate, as_tensor) CLOSE_ATOL = 10**-12 @@ -110,7 +110,7 @@ def fdrake_family(request): return request.param -@pytest.fixture(params=[1, 3], ids=["P^1", "P^3"]) +@pytest.fixture(params=[1, 4], ids=["P^1", "P^4"]) def fspace_degree(request): return request.param @@ -352,131 +352,201 @@ def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values, # {{{ Double check functions are being transported correctly -def alternating_sum_fd(spatial_coord): +@pytest.mark.parametrize("fdrake_mesh_name,fdrake_mesh_pars,dim", + [("UnitInterval", [10, 20, 30], 1), + ("UnitSquare", [10, 20, 30], 2), + ("UnitCube", [10, 20, 30], 3), + ("blob2d-order1", ["8e-2", "6e-2", "4e-2"], 2), + ("blob2d-order4", ["8e-2", "6e-2", "4e-2"], 2), + ("warp", [10, 20, 30], 2), + ("warp", [10, 20, 30], 3), + ]) +@pytest.mark.parametrize("fdrake_family", ['DG', 'CG']) +@pytest.mark.parametrize("only_convert_bdy", [False, True]) +def test_from_fd_transfer(ctx_factory, fspace_degree, + fdrake_mesh_name, fdrake_mesh_pars, dim, + fdrake_family, only_convert_bdy): """ - Return an expression x1 - x2 + x3 -+... + Make sure creating a function which projects onto + one dimension then transports it is the same + (up to resampling error) as projecting to one + dimension on the transported mesh """ - return sum( - [(-1)**i * spatial_coord - for i, spatial_coord in enumerate(spatial_coord)] - ) - - -def alternating_sum_mm(group_nodes): - """ - Take the *(dim, nelements, nunit_dofs)* np array group_nodes and return - an *(nelements, nunit_dofs)* - array holding the alternating sum of the coordinates of each node - """ - alternator = np.ones(group_nodes.shape[0]) - alternator[1::2] *= -1 - return np.einsum("i,ijk->jk", alternator, group_nodes) - - -# In 1D/2D/3D check constant 1, -# projection to x1, x1/x1+x2/x1+x2+x3, and x1/x1-x2/x1-x2+x3. -# This should show that projection to any coordinate in 1D/2D/3D -# transfers correctly. -test_functions = [ - (lambda spatial_coord: Constant(1.0), - lambda grp_nodes: np.ones(grp_nodes.shape[1:])), - (lambda spatial_coord: spatial_coord[0], - lambda grp_nodes: grp_nodes[0, ...]), - (sum, lambda grp_nodes: np.sum(grp_nodes, axis=0)), - (alternating_sum_fd, alternating_sum_mm) -] - - -@pytest.mark.parametrize("fdrake_f_expr,meshmode_f_eval", test_functions) -@pytest.mark.parametrize("only_convert_bdy", (False, True)) -def test_from_fd_transfer(ctx_factory, - fdrake_mesh, fdrake_family, fspace_degree, - fdrake_f_expr, meshmode_f_eval, - only_convert_bdy): - """ - Make sure creating a function then transporting it is the same - (up to resampling error) as creating a function on the transported - mesh - """ - # make function space and function - fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fspace_degree) - spatial_coord = SpatialCoordinate(fdrake_mesh) - - fdrake_f = Function(fdrake_fspace).interpolate(fdrake_f_expr(spatial_coord)) + # build estimate-of-convergence recorder + from pytools.convergence import EOCRecorder + # (fd -> mm ? True : False, dimension projecting onto) + eoc_recorders = {(True, d): EOCRecorder() for d in range(dim)} + if not only_convert_bdy: + for d in range(dim): + eoc_recorders[(False, d)] = EOCRecorder() - # build connection + # make a computing context cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) - if only_convert_bdy: - fdrake_connection = FromBdyFiredrakeConnection(actx, fdrake_fspace, - 'on_boundary') - else: - fdrake_connection = FromFiredrakeConnection(actx, fdrake_fspace) - - # transport fdrake function and put in numpy - fd2mm_f = fdrake_connection.from_firedrake(fdrake_f, actx=actx) - fd2mm_f = actx.to_numpy(fd2mm_f[0]) - - # build same function in meshmode - discr = fdrake_connection.discr - # nodes is np array (ambient_dim,) of DOFArray (ngroups,) - # of arrays (nelements, nunit_dofs), we want a single np array - # of shape (ambient_dim, nelements, nunit_dofs) - nodes = discr.nodes() - group_nodes = np.array([actx.to_numpy(dof_arr[0]) for dof_arr in nodes]) - meshmode_f = meshmode_f_eval(group_nodes) - - # fd -> mm should be same as creating in meshmode - np.testing.assert_allclose(fd2mm_f, meshmode_f, atol=CLOSE_ATOL) - - if not only_convert_bdy: - # now transport mm -> fd - meshmode_f_dofarr = discr.zeros(actx) - meshmode_f_dofarr[0][:] = meshmode_f - mm2fd_f = \ - fdrake_connection.from_meshmode(meshmode_f_dofarr, - assert_fdrake_discontinuous=False, - continuity_tolerance=1e-8) - # mm -> fd should be same as creating in firedrake - np.testing.assert_allclose(fdrake_f.dat.data, mm2fd_f.dat.data, - atol=CLOSE_ATOL) - - -@pytest.mark.parametrize("fdrake_f_expr,meshmode_f_eval", test_functions) -def test_to_fd_transfer(ctx_factory, mm_mesh, fspace_degree, - fdrake_f_expr, meshmode_f_eval): + # Get each refinements of the meshmeshes, do conversions, + # and record errors + for mesh_par in fdrake_mesh_pars: + if fdrake_mesh_name == "UnitInterval": + assert dim == 1 + n = mesh_par + fdrake_mesh = UnitIntervalMesh(n) + h = 1/n + elif fdrake_mesh_name == "UnitSquare": + assert dim == 2 + n = mesh_par + fdrake_mesh = UnitSquareMesh(n, n) + h = 1/n + elif fdrake_mesh_name == "UnitCube": + assert dim == 3 + n = mesh_par + fdrake_mesh = UnitCubeMesh(n, n, n) + h = 1/n + elif fdrake_mesh_name in ("blob2d-order1", "blob2d-order4"): + assert dim == 2 + if fdrake_mesh_name == "blob2d-order1": + from firedrake import Mesh + fdrake_mesh = Mesh("%s-h%s.msh" % (fdrake_mesh_name, mesh_par), + dim=dim) + else: + from meshmode.mesh.io import read_gmsh + from meshmode.interop.firedrake import export_mesh_to_firedrake + mm_mesh = read_gmsh("%s-h%s.msh" % (fdrake_mesh_name, mesh_par), + force_ambient_dim=dim) + fdrake_mesh, _, _ = export_mesh_to_firedrake(mm_mesh) + h = float(mesh_par) + elif fdrake_mesh_name == "warp": + from meshmode.mesh.generation import generate_warped_rect_mesh + from meshmode.interop.firedrake import export_mesh_to_firedrake + mm_mesh = generate_warped_rect_mesh(dim, order=4, n=mesh_par) + fdrake_mesh, _, _ = export_mesh_to_firedrake(mm_mesh) + h = 1/mesh_par + else: + raise ValueError("fdrake_mesh_name not recognized") + + # make function space and build connection + fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fspace_degree) + if only_convert_bdy: + fdrake_connection = FromBdyFiredrakeConnection(actx, fdrake_fspace, + 'on_boundary') + else: + fdrake_connection = FromFiredrakeConnection(actx, fdrake_fspace) + # get this for making functions in firedrake + spatial_coord = SpatialCoordinate(fdrake_mesh) + + # get nodes in handier format for making meshmode functions + discr = fdrake_connection.discr + # nodes is np array (ambient_dim,) of DOFArray (ngroups,) + # of arrays (nelements, nunit_dofs), we want a single np array + # of shape (ambient_dim, nelements, nunit_dofs) + nodes = discr.nodes() + group_nodes = np.array([actx.to_numpy(dof_arr[0]) for dof_arr in nodes]) + + # Now, for each coordinate d, test transferring the function + # x -> dth component of x + for d in range(dim): + fdrake_f = Function(fdrake_fspace).interpolate(spatial_coord[d]) + # transport fdrake function and put in numpy + fd2mm_f = fdrake_connection.from_firedrake(fdrake_f, actx=actx) + fd2mm_f = actx.to_numpy(fd2mm_f[0]) + meshmode_f = group_nodes[d, :, :] + + # record fd -> mm error + err = np.max(np.abs(fd2mm_f - meshmode_f)) + eoc_recorders[(True, d)].add_data_point(h, err) + + if not only_convert_bdy: + # now transport mm -> fd + meshmode_f_dofarr = discr.zeros(actx) + meshmode_f_dofarr[0][:] = meshmode_f + mm2fd_f = fdrake_connection.from_meshmode( + meshmode_f_dofarr, + assert_fdrake_discontinuous=False, + continuity_tolerance=1e-8) + # record mm -> fd error + err = np.max(np.abs(fdrake_f.dat.data - mm2fd_f.dat.data)) + eoc_recorders[(False, d)].add_data_point(h, err) + + # assert that order is correct or error is "low enough" + for ((fd2mm, d), eoc_rec) in six.iteritems(eoc_recorders): + print("\nfiredrake -> meshmode: %s\nvector *x* -> *x[%s]*\n" + % (fd2mm, d), eoc_rec) + assert ( + eoc_rec.order_estimate() >= fspace_degree + or eoc_rec.max_error() < 1e-14) + + +@pytest.mark.parametrize("mesh_name,mesh_pars,dim", + [("blob2d-order1", ["8e-2", "6e-2", "4e-2"], 2), + ("blob2d-order4", ["8e-2", "6e-2", "4e-2"], 2), + ("warp", [10, 20, 30], 2), + ("warp", [10, 20, 30], 3), + ]) +def test_to_fd_transfer(ctx_factory, fspace_degree, mesh_name, mesh_pars, dim): """ - Make sure creating a function then transporting it is the same - (up to resampling error) as creating a function on the transported - mesh + Make sure creating a function which projects onto + one dimension then transports it is the same + (up to resampling error) as projecting to one + dimension on the transported mesh """ - fspace_degree += mm_mesh.groups[0].order - # Make discr and evaluate function in meshmode + # build estimate-of-convergence recorder + from pytools.convergence import EOCRecorder + # dimension projecting onto -> EOCRecorder + eoc_recorders = {d: EOCRecorder() for d in range(dim)} + + # make a computing context cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) - factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) - discr = Discretization(actx, mm_mesh, factory) - - nodes = discr.nodes() - group_nodes = np.array([actx.to_numpy(dof_arr[0]) for dof_arr in nodes]) - meshmode_f = discr.zeros(actx) - meshmode_f[0][:] = meshmode_f_eval(group_nodes) - - # connect to firedrake and evaluate expr in firedrake - fdrake_connection = ToFiredrakeConnection(discr) - fdrake_fspace = fdrake_connection.firedrake_fspace() - spatial_coord = SpatialCoordinate(fdrake_fspace.mesh()) - fdrake_f = Function(fdrake_fspace).interpolate(fdrake_f_expr(spatial_coord)) - - # transport to firedrake and make sure this is the same - mm2fd_f = fdrake_connection.from_meshmode(meshmode_f) - - np.testing.assert_allclose(mm2fd_f.dat.data, fdrake_f.dat.data, - atol=CLOSE_ATOL) + # Get each of the refinements of the meshmeshes and record + # conversions errors + for mesh_par in mesh_pars: + if mesh_name in ("blob2d-order1", "blob2d-order4"): + assert dim == 2 + from meshmode.mesh.io import read_gmsh + mm_mesh = read_gmsh("%s-h%s.msh" % (mesh_name, mesh_par), + force_ambient_dim=dim) + h = float(mesh_par) + elif mesh_name == "warp": + from meshmode.mesh.generation import generate_warped_rect_mesh + mm_mesh = generate_warped_rect_mesh(dim, order=4, n=mesh_par) + h = 1/mesh_par + else: + raise ValueError("mesh_name not recognized") + + # Make discr and connect it to firedrake + factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) + discr = Discretization(actx, mm_mesh, factory) + + fdrake_connection = ToFiredrakeConnection(discr) + fdrake_fspace = fdrake_connection.firedrake_fspace() + spatial_coord = SpatialCoordinate(fdrake_fspace.mesh()) + + # get the group's nodes in a numpy array + nodes = discr.nodes() + group_nodes = np.array([actx.to_numpy(dof_arr[0]) for dof_arr in nodes]) + + for d in range(dim): + meshmode_f = discr.zeros(actx) + meshmode_f[0][:] = group_nodes[d, :, :] + + # connect to firedrake and evaluate expr in firedrake + fdrake_f = Function(fdrake_fspace).interpolate(spatial_coord[d]) + + # transport to firedrake and record error + mm2fd_f = fdrake_connection.from_meshmode(meshmode_f) + + err = np.max(np.abs(fdrake_f.dat.data - mm2fd_f.dat.data)) + eoc_recorders[d].add_data_point(h, err) + + # assert that order is correct or error is "low enough" + for d, eoc_rec in six.iteritems(eoc_recorders): + print("\nvector *x* -> *x[%s]*\n" % d, eoc_rec) + assert ( + eoc_rec.order_estimate() >= fspace_degree + or eoc_rec.max_error() < 2e-14) # }}} -- GitLab From fddf22d40b73cbb27c51d66c9f8316518fdab0b7 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 21 Jul 2020 12:39:41 -0500 Subject: [PATCH 110/221] Rename functions to be consistent with connection names --- test/test_firedrake_interop.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 7fcca3a1..75bbcccc 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -57,7 +57,7 @@ from firedrake import ( Function, SpatialCoordinate, as_tensor) -CLOSE_ATOL = 10**-12 +CLOSE_ATOL = 1e-14 @pytest.fixture(params=["annulus.msh", @@ -169,7 +169,7 @@ def check_consistency(fdrake_fspace, discr, group_nr=0): assert discr.ndofs == fdrake_fspace.node_count -def test_fd2mm_consistency(ctx_factory, fdrake_mesh, fspace_degree): +def test_from_fd_consistency(ctx_factory, fdrake_mesh, fspace_degree): """ Check basic consistency with a FromFiredrakeConnection """ @@ -186,7 +186,7 @@ def test_fd2mm_consistency(ctx_factory, fdrake_mesh, fspace_degree): check_consistency(fdrake_fspace, discr) -def test_mm2fd_consistency(ctx_factory, mm_mesh, fspace_degree): +def test_to_fd_consistency(ctx_factory, mm_mesh, fspace_degree): fspace_degree += mm_mesh.groups[0].order cl_ctx = ctx_factory() -- GitLab From 18426ae0f23eac24c3eeb4150db7fa2a753246ec Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 21 Jul 2020 12:46:22 -0500 Subject: [PATCH 111/221] 1e-14 back to 1e-12 --- test/test_firedrake_interop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 75bbcccc..1d6347c3 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -57,7 +57,7 @@ from firedrake import ( Function, SpatialCoordinate, as_tensor) -CLOSE_ATOL = 1e-14 +CLOSE_ATOL = 1e-12 @pytest.fixture(params=["annulus.msh", -- GitLab From 39c9ef75bb1a9fa16c82163b3bf859a47262984e Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 22 Jul 2020 13:22:02 -0500 Subject: [PATCH 112/221] Improved the sphinx links --- doc/interop.rst | 62 ++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/doc/interop.rst b/doc/interop.rst index 02c78fb2..93433628 100644 --- a/doc/interop.rst +++ b/doc/interop.rst @@ -40,8 +40,8 @@ straightforward. Some language is different: * In a mesh, a :mod:`meshmode` "element" is a :mod:`firedrake` "cell" * A :class:`meshmode.discretization.Discretization` is a :mod:`firedrake` - :class:`firedrake.functionspaceimpl.WithGeometry`, usually - created by calling the function :func:`firedrake.functionspace.FunctionSpace` + :class:`~firedrake.functionspaceimpl.WithGeometry`, usually + created by calling the function :func:`~firedrake.functionspace.FunctionSpace` and referred to as a "function space" * In a mesh, any vertices, faces, cells, etc. are :mod:`firedrake` "entities" (see `dmplex `_ @@ -58,7 +58,7 @@ correspond to other vertices/faces/cells, there are two main difficulties. a mesh, it changes the element ordering and the local vertex ordering. (1.) is easily handled by insisting that the :mod:`firedrake` -:class:`firedrake.functionspaceimpl.WithGeometry` uses polynomial elements +:class:`~firedrake.functionspaceimpl.WithGeometry` uses polynomial elements and that the group of the :class:`meshmode.discretization.Discretization` being converted is a :class:`meshmode.discretization.poly_element.InterpolatoryQuadratureSimplexElementGroup` @@ -158,65 +158,65 @@ roughly as so not require a positive orientation of elements and that its reference traingle is different than specified in :mod:`modepy`. -(2) A :class:`firedrake.mesh.MeshTopology` +(2) A :class:`~firedrake.mesh.MeshTopology` which holds information about connectivity and other topological properties, but nothing about geometry/coordinates etc. -(3) A class :class:`firedrake.functionspaceimpl.FunctionSpace` +(3) A class :class:`~firedrake.functionspaceimpl.FunctionSpace` created from a :mod:`FInAT` element and a - :class:`firedrake.mesh.MeshTopology` which allows us to + :class:`~firedrake.mesh.MeshTopology` which allows us to define functions mapping the nodes (defined by the :mod:`FInAT` element) of each element in the - :class:`firedrake.mesh.MeshTopology` to some values. - Note that the function :func:`firedrake.functionspace.FunctionSpace` + :class:`~firedrake.mesh.MeshTopology` to some values. + Note that the function :func:`~firedrake.functionspace.FunctionSpace` in the firedrake API is used to create objects of class - :class:`firedrake.functionspaceimpl.FunctionSpace` s - and :class:`firedrake.functionspaceimpl.WithGeometry` (see + :class:`~firedrake.functionspaceimpl.FunctionSpace` s + and :class:`~firedrake.functionspaceimpl.WithGeometry` (see (6)). -(4) A :class:`firedrake.function.CoordinatelessFunction` +(4) A :class:`~firedrake.function.CoordinatelessFunction` (in the sense that its *domain* has no coordinates) which is a function in a - :class:`firedrake.functionspaceimpl.FunctionSpace` + :class:`~firedrake.functionspaceimpl.FunctionSpace` -(5) A :class:`firedrake.mesh.MeshGeometry` created from a - :class:`firedrake.functionspaceimpl.FunctionSpace` - and a :class:`firedrake.function.CoordinatelessFunction` - in that :class:`firedrake.functionspaceimpl.FunctionSpace` +(5) A :class:`~firedrake.mesh.MeshGeometry` created from a + :class:`~firedrake.functionspaceimpl.FunctionSpace` + and a :class:`~firedrake.function.CoordinatelessFunction` + in that :class:`~firedrake.functionspaceimpl.FunctionSpace` which maps each dof to its geometric coordinates. -(6) A :class:`firedrake.functionspaceimpl.WithGeometry` which is a - :class:`firedrake.functionspaceimpl.FunctionSpace` together - with a :class:`firedrake.mesh.MeshGeometry`. +(6) A :class:`~firedrake.functionspaceimpl.WithGeometry` which is a + :class:`~firedrake.functionspaceimpl.FunctionSpace` together + with a :class:`~firedrake.mesh.MeshGeometry`. This is the object returned usually returned to the user by a call to the :mod:`firedrake` function - :func:`firedrake.functionspace.FunctionSpace`. + :func:`~firedrake.functionspace.FunctionSpace`. -(7) A :class:`firedrake.function.Function` is defined on a - :class:`firedrake.functionspaceimpl.WithGeometry` +(7) A :class:`~firedrake.function.Function` is defined on a + :class:`~firedrake.functionspaceimpl.WithGeometry` Thus, by the coordinates of a mesh geometry we mean -(a) On the hidden back-end: a :class:`firedrake.function.CoordinatelessFunction` +(a) On the hidden back-end: a :class:`~firedrake.function.CoordinatelessFunction` *f* on some function space defined only on the mesh topology -(b) On the front-end: A :class:`firedrake.function.Function` +(b) On the front-end: A :class:`~firedrake.function.Function` with the values of *f* but defined - on a :class:`firedrake.functionspaceimpl.WithGeometry` - created from the :class:`firedrake.functionspaceimpl.FunctionSpace` - *f* lives in and the :class:`firedrake.mesh.MeshGeometry` *f* defines. + on a :class:`~firedrake.functionspaceimpl.WithGeometry` + created from the :class:`~firedrake.functionspaceimpl.FunctionSpace` + *f* lives in and the :class:`~firedrake.mesh.MeshGeometry` *f* defines. Basically, it's this picture (where a->b if b depends on a) .. warning:: - In general, the :class:`firedrake.functionspaceimpl.FunctionSpace` + In general, the :class:`~firedrake.functionspaceimpl.FunctionSpace` of the coordinates function - of a :class:`firedrake.functionspaceimpl.WithGeometry` may not be the same - :class:`firedrake.functionspaceimpl.FunctionSpace` + of a :class:`~firedrake.functionspaceimpl.WithGeometry` may not be the same + :class:`~firedrake.functionspaceimpl.FunctionSpace` as for functions which live in the - :class:`firedrake.functionspaceimpl.WithGeometry`. + :class:`~firedrake.functionspaceimpl.WithGeometry`. This picture only shows how the class definitions depend on each other. -- GitLab From 12383b6eb328b88d93f8306b437e7f9b3adca9a5 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 22 Jul 2020 13:22:45 -0500 Subject: [PATCH 113/221] Improved GroupFactories to use recursive nodes, and allow the user to choose --- meshmode/interop/firedrake/connection.py | 190 ++++++++++++----------- test/test_firedrake_interop.py | 9 +- 2 files changed, 105 insertions(+), 94 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 522b5042..e08f3c4d 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -41,10 +41,11 @@ from meshmode.interop.firedrake.reference_cell import ( from meshmode.mesh.processing import get_simplex_element_flip_matrix -from meshmode.discretization.poly_element import \ - InterpolatoryQuadratureSimplexGroupFactory, \ - InterpolatoryQuadratureSimplexElementGroup -from meshmode.discretization import Discretization +from meshmode.discretization.poly_element import ( + InterpolatoryQuadratureSimplexGroupFactory, + PolynomialRecursiveNodesGroupFactory) +from meshmode.discretization import ( + Discretization, InterpolatoryElementGroupBase, ElementGroupFactory) def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): @@ -136,8 +137,8 @@ class FiredrakeConnection: A 2-D numpy integer array with the same dtype as ``fdrake_fspace.cell_node_list.dtype`` :param group_nr: The index of the group in *discr* which is - being connected to *fdrake_fspace*. The group must be - a :class:`InterpolatoryQuadratureSimplexElementGroup` + being connected to *fdrake_fspace*. The group must be a + :class:`~meshmode.discretization.InterpolatoryElementGroupBase` of the same topological dimension as *fdrake_fspace*. If *discr* has only one group, *group_nr=None* may be supplied. @@ -152,7 +153,7 @@ class FiredrakeConnection: # {{{ Validate input if not isinstance(discr, Discretization): raise TypeError(":param:`discr` must be of type " - ":class:`meshmode.discretization.Discretization`, " + ":class:`~meshmode.discretization.Discretization`, " "not :class:`%s`." % type(discr)) from firedrake.functionspaceimpl import WithGeometry if not isinstance(fdrake_fspace, WithGeometry): @@ -179,10 +180,9 @@ class FiredrakeConnection: raise ValueError(":param:`group_nr` has value %s, which an invalid " "index into list *discr.groups* of length %s." % (group_nr, len(discr.groups))) - if not isinstance(element_grp, - InterpolatoryQuadratureSimplexElementGroup): + if not isinstance(element_grp, InterpolatoryElementGroupBase): raise TypeError("*discr.groups[group_nr]* must be of type " - ":class:`InterpolatoryQuadratureSimplexElementGroup`" + ":class:`InterpolatoryElementGroupBase`" ", not :class:`%s`." % type(element_grp)) allowed_families = ('Discontinuous Lagrange', 'Lagrange') if fdrake_fspace.ufl_element().family() not in allowed_families: @@ -348,7 +348,7 @@ class FiredrakeConnection: def check_dof_array(arr, arr_name): if not isinstance(arr, DOFArray): raise TypeError(arr_name + " must be of type " - ":class:`meshmode.dof_array.DOFArray`, " + ":class:`~meshmode.dof_array.DOFArray`, " "not :class:`%s`." % type(arr)) if arr.array_context is None: raise ValueError(arr_name + " must have a non-*None* " @@ -405,9 +405,9 @@ class FiredrakeConnection: :attr:`discr`. Its function space must have the same family, degree, and mesh as ``self.from_fspace()``. :arg out: Either - 1.) A :class:`meshmode.dof_array.DOFArray` + 1.) A :class:`~meshmode.dof_array.DOFArray` 2.) A :class:`np.ndarray` object array, each of whose - entries is a :class:`meshmode.dof_array.DOFArray` + entries is a :class:`~meshmode.dof_array.DOFArray` 3.) *None* In the case of (1.), *function* must be in a scalar function space @@ -417,7 +417,7 @@ class FiredrakeConnection: In either case, each `DOFArray` must be a `DOFArray` defined on :attr:`discr` (as described in - the documentation for :class:`meshmode.dof_array.DOFArray`). + the documentation for :class:`~meshmode.dof_array.DOFArray`). Also, each `DOFArray`'s *entry_dtype* must match the *function.dat.data.dtype*, and be of shape *(nelements, nunit_dofs)*. @@ -429,7 +429,7 @@ class FiredrakeConnection: and stored in *out*, which is then returned. :arg actx: * If *out* is *None*, then *actx* is a - :class:`meshmode.array_context.ArrayContext` on which + :class:`~meshmode.array_context.ArrayContext` on which to create the :class:`DOFArray` * If *out* is not *None*, *actx* must be *None* or *out*'s *array_context*. @@ -499,13 +499,13 @@ class FiredrakeConnection: are not modified. :arg mm_field: Either - * A :class:`meshmode.dof_array.DOFArray` representing + * A :class:`~meshmode.dof_array.DOFArray` representing a field of shape *tuple()* on :attr:`discr` - * A :class:`numpy.ndarray` of :class:`meshmode.dof_array.DOFArray`s + * A :class:`numpy.ndarray` of :class:`~meshmode.dof_array.DOFArray`s representing a field of shape *mm_field.shape* on :attr:`discr` - See :class:`meshmode.dof.DOFArray` for further requirements. + See :class:`~meshmode.dof.DOFArray` for further requirements. The :attr:`group_nr`th entry of each :class:`DOFArray` must be of shape *(nelements, nunit_dofs)* and the *element_dtype* must match that used for @@ -623,19 +623,33 @@ class FiredrakeConnection: # {{{ Create connection from firedrake into meshmode + class FromFiredrakeConnection(FiredrakeConnection): """ A connection created from a :mod:`firedrake` ``"CG"`` or ``"DG"`` function space which creates a corresponding meshmode discretization and allows transfer of functions to and from :mod:`firedrake`. + + .. automethod:: __init__ """ - def __init__(self, actx, fdrake_fspace): + def __init__(self, actx, fdrake_fspace, grp_factory=None): """ - :arg actx: A :class:`meshmode.array_context.ArrayContext` + :arg actx: A :class:`~meshmode.array_context.ArrayContext` :arg fdrake_fspace: A :mod:`firedrake` ``"CG"`` or ``"DG"`` function space (of class :class:`WithGeometry`) built on a mesh which is importable by :func:`import_firedrake_mesh`. + :arg grp_factory: (optional) If not *None*, should be + a :class:`~meshmode.discretization.ElementGroupFactory` + whose group class is a subclass of + :class:`~meshmode.discretization.InterpolatoryElementGroupBase`. + If *None*, uses + + * A :class:`~meshmode.discretization.poly_element.\ + PolynomialRecursiveNodesGroupFactory` if :mod:`recursivenodes` is + installed + * A :class:`~meshmode.discretization.poly_element.\ + PolynomialWarpAndBlendGroupFactory` """ # Ensure fdrake_fspace is a function space with appropriate reference # element. @@ -652,11 +666,39 @@ class FromFiredrakeConnection(FiredrakeConnection): "be ``'Lagrange'`` or " "``'Discontinuous Lagrange'``, not %s." % ufl_elt.family()) - + # Make sure grp_factory is the right type if provided, and + # uses an interpolatory class. + if grp_factory is not None: + if not isinstance(grp_factory, ElementGroupFactory): + raise TypeError(":arg:`grp_factory` must inherit from " + ":class:`meshmode.discretization." + "ElementGroupFactory`, but is instead of type " + "%s." % type(grp_factory)) + if not issubclass(grp_factory.group_class, + InterpolatoryElementGroupBase): + raise TypeError(":arg:`grp_factory` must use a *group_class" + "* which inherits from" + ":class:`meshmode.discretization." + "InterpolatoryElementGroupBase, but instead uses" + " *group_class* of type %s." + % type(grp_factory.group_class)) + # If not provided, make one + else: + degree = ufl_elt.degree() + try: + import recursivenodes # noqa : F401 + family = 'lgl' # L-G-Legendre + grp_factory = PolynomialRecursiveNodesGroupFactory(degree, family) + except ImportError: + grp_factory = InterpolatoryQuadratureSimplexGroupFactory(degree) + + # In case this class is really a FromBdyFiredrakeConnection, + # get *cells_to_use* + cells_to_use = self._get_cells_to_use(fdrake_fspace.mesh()) # Create to_discr - mm_mesh, orient = import_firedrake_mesh(fdrake_fspace.mesh()) - factory = InterpolatoryQuadratureSimplexGroupFactory(ufl_elt.degree()) - to_discr = Discretization(actx, mm_mesh, factory) + mm_mesh, orient = import_firedrake_mesh(fdrake_fspace.mesh(), + cells_to_use=cells_to_use) + to_discr = Discretization(actx, mm_mesh, grp_factory) # get firedrake unit nodes and map onto meshmode reference element group = to_discr.groups[0] @@ -683,6 +725,8 @@ class FromFiredrakeConnection(FiredrakeConnection): flip_mat = get_simplex_element_flip_matrix(ufl_elt.degree(), fd_unit_nodes) fd_cell_node_list = fdrake_fspace.cell_node_list + if cells_to_use is not None: + fd_cell_node_list = fd_cell_node_list[cells_to_use] # flip fd_cell_node_list flipped_cell_node_list = _reorder_nodes(orient, fd_cell_node_list, @@ -697,24 +741,14 @@ class FromFiredrakeConnection(FiredrakeConnection): "Somehow a firedrake node in a 'DG' space got duplicated..." \ "contact the developer." - -def _compute_cells_near_bdy(mesh, bdy_id): - """ - Returns an array of the cell ids with >= 1 vertex on the - given bdy_id - """ - cfspace = mesh.coordinates.function_space() - cell_node_list = cfspace.cell_node_list - - boundary_nodes = cfspace.boundary_nodes(bdy_id, 'topological') - # Reduce along each cell: Is a vertex of the cell in boundary nodes? - cell_is_near_bdy = np.any(np.isin(cell_node_list, boundary_nodes), axis=1) - - from pyop2.datatypes import IntType - return np.nonzero(cell_is_near_bdy)[0].astype(IntType) + def _get_cells_to_use(self, mesh): + """ + For compatability with :class:`FromFiredrakeBdyConnection` + """ + return None -class FromBdyFiredrakeConnection(FiredrakeConnection): +class FromBdyFiredrakeConnection(FromFiredrakeConnection): """ A connection created from a :mod:`firedrake` ``"CG"`` or ``"DG"`` function space which creates a @@ -725,66 +759,40 @@ class FromBdyFiredrakeConnection(FiredrakeConnection): Use the same bdy_id as one would for a :class:`firedrake.bcs.DirichletBC`. ``"on_boundary"`` corresponds to the entire boundary. + + .. attribute:: bdy_id + + the boundary id of the boundary being connecting from + + .. automethod:: __init__ """ - def __init__(self, actx, fdrake_fspace, bdy_id): + def __init__(self, actx, fdrake_fspace, bdy_id, grp_factory=None): """ - :arg actx: A :class:`meshmode.array_context.ArrayContext` - :arg fdrake_fspace: A :mod:`firedrake` ``"CG"`` or ``"DG"`` - function space (of class :class:`WithGeometry`) built on - a mesh which is importable by :func:`import_firedrake_mesh`. :arg bdy_id: A boundary marker of *fdrake_fspace.mesh()* as accepted by the *boundary_nodes* method of a firedrake :class:`firedrake.functionspaceimpl.WithGeometry`. - """ - # Ensure fdrake_fspace is a function space with appropriate reference - # element. - from firedrake.functionspaceimpl import WithGeometry - if not isinstance(fdrake_fspace, WithGeometry): - raise TypeError(":arg:`fdrake_fspace` must be of firedrake type " - ":class:`WithGeometry`, not `%s`." - % type(fdrake_fspace)) - ufl_elt = fdrake_fspace.ufl_element() - if ufl_elt.family() not in ('Lagrange', 'Discontinuous Lagrange'): - raise ValueError("the ``ufl_element().family()`` of " - ":arg:`fdrake_fspace` must " - "be ``'Lagrange'`` or " - "``'Discontinuous Lagrange'``, not %s." - % ufl_elt.family()) - - # Create to_discr - cells_to_use = _compute_cells_near_bdy(fdrake_fspace.mesh(), bdy_id) - mm_mesh, orient = import_firedrake_mesh(fdrake_fspace.mesh(), - cells_to_use=cells_to_use) - factory = InterpolatoryQuadratureSimplexGroupFactory(ufl_elt.degree()) - to_discr = Discretization(actx, mm_mesh, factory) + Other arguments are as in + :class:`~meshmode.interop.firedrake.FromFiredrakeConnection`. + """ + self.bdy_id = bdy_id + super(FromBdyFiredrakeConnection, self).__init__(actx, fdrake_fspace, + grp_factory=grp_factory) - # get firedrake unit nodes and map onto meshmode reference element - group = to_discr.groups[0] - fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(group.dim, - True) - fd_unit_nodes = get_finat_element_unit_nodes(fdrake_fspace.finat_element) - fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) + def _get_cells_to_use(self, mesh): + """ + Returns an array of the cell ids with >= 1 vertex on the + given bdy_id + """ + cfspace = mesh.coordinates.function_space() + cell_node_list = cfspace.cell_node_list - # Get the reordering fd->mm, see the note in - # :class:`FromFiredrakeConnection` for a comment on what this is - # doing in continuous spaces. - flip_mat = get_simplex_element_flip_matrix(ufl_elt.degree(), - fd_unit_nodes) - fd_cell_node_list = fdrake_fspace.cell_node_list[cells_to_use] - # flip fd_cell_node_list - flipped_cell_node_list = _reorder_nodes(orient, - fd_cell_node_list, - flip_mat, - unflip=False) + boundary_nodes = cfspace.boundary_nodes(self.bdy_id, 'topological') + # Reduce along each cell: Is a vertex of the cell in boundary nodes? + cell_is_near_bdy = np.any(np.isin(cell_node_list, boundary_nodes), axis=1) - super(FromBdyFiredrakeConnection, self).__init__(to_discr, - fdrake_fspace, - flipped_cell_node_list) - if fdrake_fspace.ufl_element().family() == 'Discontinuous Lagrange': - assert len(self._mm_node_equiv_classes) == 0, \ - "Somehow a firedrake node in a 'DG' space got duplicated..." \ - "contact the developer." + from pyop2.datatypes import IntType + return np.nonzero(cell_is_near_bdy)[0].astype(IntType) # }}} diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 1d6347c3..b50e0b47 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -41,7 +41,6 @@ from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL, check_bc_coverage from meshmode.interop.firedrake import ( FromFiredrakeConnection, FromBdyFiredrakeConnection, ToFiredrakeConnection, import_firedrake_mesh) -from meshmode.interop.firedrake.connection import _compute_cells_near_bdy import pytest @@ -244,7 +243,7 @@ def test_from_bdy_consistency(ctx_factory, fdrake_unit_vert_indices = np.array(fdrake_unit_vert_indices) # only look at cells "near" bdy (with >= 1 vertex on) - cells_near_bdy = _compute_cells_near_bdy(fdrake_mesh, 'on_boundary') + cells_near_bdy = frombdy_conn._get_cells_to_use(fdrake_mesh) # get the firedrake vertices of cells near the boundary, # in no particular order fdrake_vert_indices = \ @@ -303,7 +302,11 @@ def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values, """ cells_to_use = None if only_convert_bdy: - cells_to_use = _compute_cells_near_bdy(square_or_cube_mesh, 'on_boundary') + # make a dummy connection which just has a bdy_id + class DummyConnection(FromBdyFiredrakeConnection): + def __init__(self): + self.bdy_id = 'on_boundary' + cells_to_use = DummyConnection()._get_cells_to_use(square_or_cube_mesh) mm_mesh, orient = import_firedrake_mesh(square_or_cube_mesh, cells_to_use=cells_to_use) # Ensure meshmode required boundary tags are there -- GitLab From caf028ba653f183069838282317def58b1609673 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 22 Jul 2020 13:22:59 -0500 Subject: [PATCH 114/221] just use the best available basis for mesh ndoes --- meshmode/interop/firedrake/mesh.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 4625cb9b..20999313 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -28,7 +28,7 @@ from warnings import warn # noqa import numpy as np import six -from modepy import resampling_matrix +from modepy import resampling_matrix, simplex_best_available_basis from meshmode.mesh import (BTAG_ALL, BTAG_REALLY_ALL, BTAG_NO_BOUNDARY, FacialAdjacencyGroup, Mesh, NodalAdjacency, SimplexElementGroup) @@ -787,12 +787,8 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): fd_unit_nodes = get_finat_element_unit_nodes(coords_fspace.finat_element) fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) - from meshmode.discretization.poly_element import ( - InterpolatoryQuadratureSimplexElementGroup) - el_group = InterpolatoryQuadratureSimplexElementGroup(group, - group.order, - group_nr) - resampling_mat = resampling_matrix(el_group.basis(), + basis = simplex_best_available_basis(group.dim, group.order) + resampling_mat = resampling_matrix(basis, new_nodes=fd_unit_nodes, old_nodes=group.unit_nodes) # Store the meshmode data resampled to firedrake unit nodes -- GitLab From 5b570f0a7863d507fe9d81a6b209d72d35b31e6e Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 23 Jul 2020 14:37:34 -0500 Subject: [PATCH 115/221] Make sure vertices is stored as a contiguous array --- meshmode/interop/firedrake/mesh.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 20999313..4ed1d7ca 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -580,16 +580,15 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, dof, = dofs unit_vertex_indices.append(dof) - # Now get the vertex coordinates - vertices = {} + # Now get the vertex coordinates as *(dim, nvertices)*-shaped array + vertices = np.array((gdim, fdrake_mesh.num_vertices()), dtype=nodes.dtype) + recorded_verts = set() for icell, cell_vertex_indices in enumerate(vertex_indices): for local_vert_id, global_vert_id in enumerate(cell_vertex_indices): - if global_vert_id in vertices: - continue - local_node_nr = unit_vertex_indices[local_vert_id] - vertices[global_vert_id] = nodes[:, icell, local_node_nr] - # Stuff the vertices in a *(dim, nvertices)*-shaped numpy array - vertices = np.array([vertices[i] for i in range(len(vertices))]).T + if global_vert_id not in recorded_verts: + recorded_verts.add(global_vert_id) + local_node_nr = unit_vertex_indices[local_vert_id] + vertices[:, global_vert_id] = nodes[:, icell, local_node_nr] # Use the vertices to compute the orientations and flip the group orient = _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, -- GitLab From 06376de5a970b5c7f8b5e3db15613e5e23d85454 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 23 Jul 2020 14:40:59 -0500 Subject: [PATCH 116/221] array -> ndarray --- meshmode/interop/firedrake/mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 4ed1d7ca..42745a19 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -581,7 +581,7 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, unit_vertex_indices.append(dof) # Now get the vertex coordinates as *(dim, nvertices)*-shaped array - vertices = np.array((gdim, fdrake_mesh.num_vertices()), dtype=nodes.dtype) + vertices = np.ndarray((gdim, fdrake_mesh.num_vertices()), dtype=nodes.dtype) recorded_verts = set() for icell, cell_vertex_indices in enumerate(vertex_indices): for local_vert_id, global_vert_id in enumerate(cell_vertex_indices): -- GitLab From 0cfcedd32e89581614f816e9d0f4d5b80f7339f6 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Mon, 27 Jul 2020 16:05:34 -0500 Subject: [PATCH 117/221] improved error msg --- meshmode/interop/firedrake/connection.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index e08f3c4d..5757f693 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -451,7 +451,8 @@ class FiredrakeConnection: else: # If `out` is not supplied, create it from meshmode.array_context import ArrayContext - assert isinstance(actx, ArrayContext) + assert isinstance(actx, ArrayContext), "If :arg:`out` is *None*, " \ + ":arg:`actx` must be of type ArrayContext, not %s." % type(actx) if fspace_shape == tuple(): out = self.discr.zeros(actx, dtype=function_data.dtype) else: -- GitLab From 8e110f3164d35deb8b12954344cff835e2ca4a7c Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 30 Jul 2020 13:15:20 -0500 Subject: [PATCH 118/221] Import ElementGroupFactory from poly_element not __init__ --- meshmode/interop/firedrake/connection.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 5757f693..fa4ead42 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -43,9 +43,10 @@ from meshmode.mesh.processing import get_simplex_element_flip_matrix from meshmode.discretization.poly_element import ( InterpolatoryQuadratureSimplexGroupFactory, - PolynomialRecursiveNodesGroupFactory) + PolynomialRecursiveNodesGroupFactory, + ElementGroupFactory) from meshmode.discretization import ( - Discretization, InterpolatoryElementGroupBase, ElementGroupFactory) + Discretization, InterpolatoryElementGroupBase) def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): -- GitLab From 37fe06622fae3aebd2db7bff2942e67290081e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Mon, 10 Aug 2020 23:40:57 +0200 Subject: [PATCH 119/221] Install dataclasses fallback in Firedrake Gitlab CI --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3c6b530e..63122baf 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -59,7 +59,9 @@ Python 3 POCL Firedrake: - source ~/firedrake/bin/activate - "grep -v loopy requirements.txt > myreq.txt" - pip install -r myreq.txt - - pip install pytest + # The Firedrake container is based on Py3.6 as of 2020-10-10, which + # doesn't have dataclasses. + - pip install pytest dataclasses - pip install . - cd test - python -m pytest --tb=native --junitxml=pytest.xml -rxsw test_firedrake_interop.py -- GitLab From 6c6aa1b27cb2fe49309315084761fb08c703409c Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 10 Aug 2020 16:56:23 -0500 Subject: [PATCH 120/221] Add github CI for Firedrake --- .github/workflows/ci.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e1afede..a908c923 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,33 @@ jobs: curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh . ./build-and-test-py-project-within-miniconda.sh + pytest3: + name: Pytest Firedrake + runs-on: ubuntu-latest + steps: + - uses: docker://firedrakeproject/firedrake + - uses: actions/checkout@v2 + - name: "APT dependencies" + run: | + sudo apt update + sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev + - name: "PIP dependencies" + run: | + source ~/firedrake/bin/activate + "grep -v loopy requirements.txt > myreq.txt" + pip install -r myreq.txt + # The Firedrake container is based on Py3.6 as of 2020-10-10, which + # doesn't have dataclasses. + pip install pytest dataclasses + pip install . + - name: "Test" + run: | + cd test + python -m pytest --tb=native --junitxml=pytest.xml -rxsw test_firedrake_interop.py + CONDA_ENVIRONMENT=.test-conda-env-py3.yml + curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh + . ./build-and-test-py-project-within-miniconda.sh + examples3: name: Examples Conda Py3 runs-on: ubuntu-latest -- GitLab From c18aa17a5936a2b8f71ada16cae22b08d0f7917a Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 10 Aug 2020 17:09:48 -0500 Subject: [PATCH 121/221] Rename github CI firedrake job --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc01ada3..4e2f2f69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh . ./build-and-test-py-project-within-miniconda.sh - pytest3: + firedrake: name: Pytest Firedrake runs-on: ubuntu-latest steps: -- GitLab From be800faf841d57bb1a30b2b928841fbe31be9c0c Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 10 Aug 2020 17:15:21 -0500 Subject: [PATCH 122/221] Install correct loopy branch for Fdrake CI --- .github/workflows/ci.yml | 1 + .gitlab-ci.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e2f2f69..4029e894 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,7 @@ jobs: source ~/firedrake/bin/activate "grep -v loopy requirements.txt > myreq.txt" pip install -r myreq.txt + pip install git+https://github.com/benSepanski/loopy.git@firedrake-reinstate_program_executor_cache # The Firedrake container is based on Py3.6 as of 2020-10-10, which # doesn't have dataclasses. pip install pytest dataclasses diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 63122baf..4396f139 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -59,6 +59,7 @@ Python 3 POCL Firedrake: - source ~/firedrake/bin/activate - "grep -v loopy requirements.txt > myreq.txt" - pip install -r myreq.txt + - pip install git+https://github.com/benSepanski/loopy.git@firedrake-reinstate_program_executor_cache # The Firedrake container is based on Py3.6 as of 2020-10-10, which # doesn't have dataclasses. - pip install pytest dataclasses -- GitLab From 4822c551173fec02f719582e8a626b5efab79898 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 10 Aug 2020 17:43:03 -0500 Subject: [PATCH 123/221] Fix Firedrake venv path in Github CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4029e894..d2024c57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev - name: "PIP dependencies" run: | - source ~/firedrake/bin/activate + source /home/firedrake/firedrake/bin/activate "grep -v loopy requirements.txt > myreq.txt" pip install -r myreq.txt pip install git+https://github.com/benSepanski/loopy.git@firedrake-reinstate_program_executor_cache -- GitLab From 570492d8ba730e6a5d7c2da42252af4dc88baab1 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 10 Aug 2020 17:59:53 -0500 Subject: [PATCH 124/221] Next attempt at Fdrake Github CI --- .github/workflows/ci.yml | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2024c57..8d5d49ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,29 +37,26 @@ jobs: name: Pytest Firedrake runs-on: ubuntu-latest steps: - - uses: docker://firedrakeproject/firedrake - uses: actions/checkout@v2 - - name: "APT dependencies" + - name: "Dependencies" + uses: docker://firedrakeproject/firedrake run: | - sudo apt update - sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev - - name: "PIP dependencies" - run: | - source /home/firedrake/firedrake/bin/activate - "grep -v loopy requirements.txt > myreq.txt" - pip install -r myreq.txt - pip install git+https://github.com/benSepanski/loopy.git@firedrake-reinstate_program_executor_cache - # The Firedrake container is based on Py3.6 as of 2020-10-10, which - # doesn't have dataclasses. - pip install pytest dataclasses - pip install . + sudo apt update + sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev + source /home/firedrake/firedrake/bin/activate + "grep -v loopy requirements.txt > myreq.txt" + pip install -r myreq.txt + pip install git+https://github.com/benSepanski/loopy.git@firedrake-reinstate_program_executor_cache + # The Firedrake container is based on Py3.6 as of 2020-10-10, which + # doesn't have dataclasses. + pip install pytest dataclasses + pip install . - name: "Test" + - name: "Dependencies" + uses: docker://firedrakeproject/firedrake run: | - cd test - python -m pytest --tb=native --junitxml=pytest.xml -rxsw test_firedrake_interop.py - CONDA_ENVIRONMENT=.test-conda-env-py3.yml - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh - . ./build-and-test-py-project-within-miniconda.sh + cd test + python -m pytest --tb=native --junitxml=pytest.xml -rxsw test_firedrake_interop.py examples3: name: Examples Conda Py3 -- GitLab From 1fe3ea865cef355706effd79f144c74cbecb72ba Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 10 Aug 2020 18:03:52 -0500 Subject: [PATCH 125/221] Remove duplicate key from Github CI config --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d5d49ab..05d05d60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,6 @@ jobs: pip install pytest dataclasses pip install . - name: "Test" - - name: "Dependencies" uses: docker://firedrakeproject/firedrake run: | cd test -- GitLab From 780d9990cfa1e79f8af0885388440c55908d3037 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 10 Aug 2020 18:12:47 -0500 Subject: [PATCH 126/221] Tweak Github CI docker syntax for Firedrake --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05d05d60..a7e02573 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,11 +35,12 @@ jobs: firedrake: name: Pytest Firedrake - runs-on: ubuntu-latest + runs: + using: 'docker' + image: 'docker://firedrakeproject/firedrake' steps: - uses: actions/checkout@v2 - name: "Dependencies" - uses: docker://firedrakeproject/firedrake run: | sudo apt update sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev @@ -52,7 +53,6 @@ jobs: pip install pytest dataclasses pip install . - name: "Test" - uses: docker://firedrakeproject/firedrake run: | cd test python -m pytest --tb=native --junitxml=pytest.xml -rxsw test_firedrake_interop.py -- GitLab From c3e434d5ff35f793a674db54f0d4f5fc0c99405a Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 10 Aug 2020 18:20:55 -0500 Subject: [PATCH 127/221] Tweak Github CI docker syntax for Firedrake --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7e02573..b7270f71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,9 +35,9 @@ jobs: firedrake: name: Pytest Firedrake - runs: - using: 'docker' - image: 'docker://firedrakeproject/firedrake' + runs-on: ubuntu-latest + container: + image: 'firedrakeproject/firedrake' steps: - uses: actions/checkout@v2 - name: "Dependencies" -- GitLab From b0162564b182e7195a93c9411678039c2d7aba95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Mon, 10 Aug 2020 18:37:06 -0500 Subject: [PATCH 128/221] Use actions/checkout@v1 in Github Firedrake CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7270f71..9f24fd95 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: container: image: 'firedrakeproject/firedrake' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v1 - name: "Dependencies" run: | sudo apt update -- GitLab From 17ca8e1606020240edc65c05b15a9ba38147d580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Mon, 10 Aug 2020 18:42:10 -0500 Subject: [PATCH 129/221] Shell tweaks to Github CI Firedrake job --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f24fd95..5fb7bf20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,8 +44,8 @@ jobs: run: | sudo apt update sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev - source /home/firedrake/firedrake/bin/activate - "grep -v loopy requirements.txt > myreq.txt" + . /home/firedrake/firedrake/bin/activate + grep -v loopy requirements.txt > myreq.txt pip install -r myreq.txt pip install git+https://github.com/benSepanski/loopy.git@firedrake-reinstate_program_executor_cache # The Firedrake container is based on Py3.6 as of 2020-10-10, which -- GitLab From 918d4b767d7735d8aab3f354b5072e295c045588 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 10 Aug 2020 18:51:33 -0500 Subject: [PATCH 130/221] Dance around home directory mismatch on Firedrake container on Github CI --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5fb7bf20..f9d0f552 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,9 +44,10 @@ jobs: run: | sudo apt update sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev + . /home/firedrake/firedrake/bin/activate - grep -v loopy requirements.txt > myreq.txt - pip install -r myreq.txt + grep -v loopy requirements.txt > /tmp/myreq.txt + pip install -r /tmp/myreq.txt pip install git+https://github.com/benSepanski/loopy.git@firedrake-reinstate_program_executor_cache # The Firedrake container is based on Py3.6 as of 2020-10-10, which # doesn't have dataclasses. @@ -54,6 +55,7 @@ jobs: pip install . - name: "Test" run: | + . /home/firedrake/firedrake/bin/activate cd test python -m pytest --tb=native --junitxml=pytest.xml -rxsw test_firedrake_interop.py -- GitLab From 5d5172c313a0473a76bd4da6fdb008d6cbcbe2a4 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 10 Aug 2020 19:01:36 -0500 Subject: [PATCH 131/221] Firedrake Github CI: Don't attempt to write JUnit XML --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9d0f552..e6b60918 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: run: | . /home/firedrake/firedrake/bin/activate cd test - python -m pytest --tb=native --junitxml=pytest.xml -rxsw test_firedrake_interop.py + python -m pytest --tb=native -rxsw test_firedrake_interop.py examples3: name: Examples Conda Py3 -- GitLab From f64d02c7fefebb911995802a3ba57ec471d456c7 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 10 Aug 2020 19:13:07 -0500 Subject: [PATCH 132/221] Github Firedrake CI: Fix ownership of /github/home --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6b60918..cce344cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,6 +44,7 @@ jobs: run: | sudo apt update sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev + sudo chown -R /github/home $(whoami) . /home/firedrake/firedrake/bin/activate grep -v loopy requirements.txt > /tmp/myreq.txt -- GitLab From 155f4e63c5576a57a778cbea9a30252583bfa345 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 10 Aug 2020 20:21:45 -0500 Subject: [PATCH 133/221] Github Firedrake CI: Actually fix ownership of /github/home --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cce344cd..78bd0655 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,7 @@ jobs: run: | sudo apt update sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev - sudo chown -R /github/home $(whoami) + sudo chown -R $(whoami) /github/home . /home/firedrake/firedrake/bin/activate grep -v loopy requirements.txt > /tmp/myreq.txt -- GitLab From 930210efccd0ce0dac5e386920489a38a2c58cd5 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 10:30:40 -0500 Subject: [PATCH 134/221] Make sure to count the right number of vertices when only converting near boundary --- meshmode/interop/firedrake/mesh.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 42745a19..b07f09ad 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -22,6 +22,7 @@ THE SOFTWARE. __doc__ = """ .. autofunction:: import_firedrake_mesh +.. autofunction:: export_mesh_to_firedrake """ from warnings import warn # noqa @@ -581,7 +582,11 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, unit_vertex_indices.append(dof) # Now get the vertex coordinates as *(dim, nvertices)*-shaped array - vertices = np.ndarray((gdim, fdrake_mesh.num_vertices()), dtype=nodes.dtype) + if cells_to_use is not None: + nvertices = np.size(vert_ndx_new2old) + else: + nvertices = fdrake_mesh.num_vertices() + vertices = np.ndarray((gdim, nvertices), dtype=nodes.dtype) recorded_verts = set() for icell, cell_vertex_indices in enumerate(vertex_indices): for local_vert_id, global_vert_id in enumerate(cell_vertex_indices): -- GitLab From 34ba750f4c009d5b8d5f561883e01ea3cdf9337b Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 10:39:15 -0500 Subject: [PATCH 135/221] Fixed docs so that they build --- meshmode/interop/firedrake/connection.py | 19 +++++++++++-------- meshmode/interop/firedrake/mesh.py | 4 ++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index fa4ead42..085ef628 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -95,16 +95,16 @@ class FiredrakeConnection: Users should instantiate this using a :class:`FromFiredrakeConnection` or :class:`ToFiredrakeConnection`. - .. autoattribute:: discr + .. attribute:: discr A meshmode discretization - .. autoattribute:: group_nr + .. attribute:: group_nr The group number identifying which element group of :attr:`discr` is being connected to a firedrake function space - .. autoattribute:: mm2fd_node_mapping + .. attribute:: mm2fd_node_mapping Letting *element_grp = self.discr.groups[self.group_nr]*, *mm2fd_node_mapping* is a numpy array of shape @@ -406,10 +406,12 @@ class FiredrakeConnection: :attr:`discr`. Its function space must have the same family, degree, and mesh as ``self.from_fspace()``. :arg out: Either - 1.) A :class:`~meshmode.dof_array.DOFArray` - 2.) A :class:`np.ndarray` object array, each of whose - entries is a :class:`~meshmode.dof_array.DOFArray` - 3.) *None* + + 1. A :class:`~meshmode.dof_array.DOFArray` + 2. A :class:`np.ndarray` object array, each of whose + entries is a :class:`~meshmode.dof_array.DOFArray` + 3. *None* + In the case of (1.), *function* must be in a scalar function space (i.e. `function.function_space().shape == (,)`). @@ -501,6 +503,7 @@ class FiredrakeConnection: are not modified. :arg mm_field: Either + * A :class:`~meshmode.dof_array.DOFArray` representing a field of shape *tuple()* on :attr:`discr` * A :class:`numpy.ndarray` of :class:`~meshmode.dof_array.DOFArray`s @@ -521,7 +524,7 @@ class FiredrakeConnection: :arg assert_fdrake_discontinuous: If *True*, disallows conversion to a continuous firedrake function space (i.e. this function checks that ``self.firedrake_fspace()`` is - discontinuous and raises a *ValueError* otherwise) + discontinuous and raises a *ValueError* otherwise) :arg continuity_tolerance: If converting to a continuous firedrake function space (i.e. if ``self.firedrake_fspace()`` is continuous), diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index b07f09ad..6b2e30c2 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -462,6 +462,7 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, vertex_entity_dofs = coords_fspace.finat_element.entity_dofs()[0] for entity, dof_list in six.iteritems(vertex_entity_dofs): assert len(dof_list) > 0 + :arg cells_to_use: Either *None*, in which case this argument is ignored, or a numpy array of unique firedrake cell indexes. In the latter case, only cells whose index appears in *cells_to_use* are included @@ -477,14 +478,17 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, This argument is primarily intended for use by a :class:`meshmode.interop.firedrake.FromBdyFiredrakeConnection`. + :arg normals: **Only** used if *fdrake_mesh* is a 1-surface embedded in 2-space. In this case, + - If *None* then all elements are assumed to be positively oriented. - Else, should be a list/array whose *i*th entry is the normal for the *i*th element (*i*th in *mesh.coordinate.function_space()*'s :attr:`cell_node_list`) + :arg no_normals_warn: If *True* (the default), raises a warning if *fdrake_mesh* is a 1-surface embedded in 2-space and *normals* is *None*. -- GitLab From 43f35337627478e331ff8346b097d31609dd0076 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 11:05:14 -0500 Subject: [PATCH 136/221] Fixed sphinx links --- doc/interop.rst | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/doc/interop.rst b/doc/interop.rst index 93433628..953fe015 100644 --- a/doc/interop.rst +++ b/doc/interop.rst @@ -12,12 +12,17 @@ Function Spaces/Discretizations ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Users wishing to interact with :mod:`meshmode` from :mod:`firedrake` -will primarily interact with the :class:`FromFiredrakeConnection` and -:class:`FromBdyFiredrakeConnection` classes, while users wishing +will primarily interact with the +:class:`~meshmode.interop.firedrake.connection.FromFiredrakeConnection` and +:class:`~meshmode.interop.firedrake.connection.FromBdyFiredrakeConnection` +classes, while users wishing to interact with :mod:`firedrake` from :mod:`meshmode` will use -the :class:`ToFiredrakeConnection` class. All of these classes inherit from -the :class:`FiredrakeConnection` class, which provides the interface. -It is not recommended to create a :class:`FiredrakeConnection` directly. +the :class:`~meshmode.interop.firedrake.connection.ToFiredrakeConnection` class. +All of these classes inherit from +the :class:`~meshmode.interop.firedrake.connection.FiredrakeConnection` +class, which provides the interface. +It is not recommended to create a +:class:`~meshmode.interop.firedrake.connection.FiredrakeConnection` directly. .. automodule:: meshmode.interop.firedrake.connection @@ -39,7 +44,7 @@ Converting between :mod:`firedrake` and :mod:`meshmode` is in general straightforward. Some language is different: * In a mesh, a :mod:`meshmode` "element" is a :mod:`firedrake` "cell" -* A :class:`meshmode.discretization.Discretization` is a :mod:`firedrake` +* A :class:`~meshmode.discretization.Discretization` is a :mod:`firedrake` :class:`~firedrake.functionspaceimpl.WithGeometry`, usually created by calling the function :func:`~firedrake.functionspace.FunctionSpace` and referred to as a "function space" @@ -61,7 +66,7 @@ correspond to other vertices/faces/cells, there are two main difficulties. :class:`~firedrake.functionspaceimpl.WithGeometry` uses polynomial elements and that the group of the :class:`meshmode.discretization.Discretization` being converted is a -:class:`meshmode.discretization.poly_element.InterpolatoryQuadratureSimplexElementGroup` +:class:`~meshmode.discretization.poly_element.InterpolatoryQuadratureSimplexElementGroup` of the same order. Then, on each element, the function space being represented is the same in :mod:`firedrake` or :mod:`meshmode`. We may simply resample to one system or another's unit nodes. @@ -129,7 +134,8 @@ at nodes from one set of unit nodes to another (and then undo the reordering if converting function values from :mod:`meshmode` to :mod:`firedrake`). The information for this whole reordering process is -stored in :attr:`FiredrakeConnection.mm2fd_node_mapping`, +stored in +:attr:`~meshmode.interop.firedrake.FiredrakeConnection.mm2fd_node_mapping`, an array which associates each :mod:`meshmode` node to the :mod:`firedrake` node found by tracing the above diagram (i.e. it stores -- GitLab From 85f05a68cd5033d5a04a4954a9733db25b80d32e Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 11:08:03 -0500 Subject: [PATCH 137/221] FromBdyFiredrakeConnection -> FromBoundaryFiredrakeConnection --- doc/interop.rst | 2 +- examples/from_firedrake.py | 12 +++++----- meshmode/interop/firedrake/__init__.py | 5 +++-- meshmode/interop/firedrake/connection.py | 10 ++++----- meshmode/interop/firedrake/mesh.py | 2 +- test/test_firedrake_interop.py | 28 +++++++++++++----------- 6 files changed, 31 insertions(+), 28 deletions(-) diff --git a/doc/interop.rst b/doc/interop.rst index 953fe015..0b737a73 100644 --- a/doc/interop.rst +++ b/doc/interop.rst @@ -14,7 +14,7 @@ Function Spaces/Discretizations Users wishing to interact with :mod:`meshmode` from :mod:`firedrake` will primarily interact with the :class:`~meshmode.interop.firedrake.connection.FromFiredrakeConnection` and -:class:`~meshmode.interop.firedrake.connection.FromBdyFiredrakeConnection` +:class:`~meshmode.interop.firedrake.connection.FromBoundaryFiredrakeConnection` classes, while users wishing to interact with :mod:`firedrake` from :mod:`meshmode` will use the :class:`~meshmode.interop.firedrake.connection.ToFiredrakeConnection` class. diff --git a/examples/from_firedrake.py b/examples/from_firedrake.py index ddc6108a..b3f99641 100644 --- a/examples/from_firedrake.py +++ b/examples/from_firedrake.py @@ -25,7 +25,7 @@ import pyopencl as cl # This example provides a brief template for bringing information in # from firedrake and makes some plots to help you better understand -# what a FromBdyFiredrakeConnection does +# what a FromBoundaryFiredrakeConnection does def main(): # If can't import firedrake, do nothing try: @@ -34,7 +34,7 @@ def main(): return 0 from meshmode.interop.firedrake import ( - FromFiredrakeConnection, FromBdyFiredrakeConnection) + FromFiredrakeConnection, FromBoundaryFiredrakeConnection) from firedrake import ( UnitSquareMesh, FunctionSpace, SpatialCoordinate, Function, cos ) @@ -52,7 +52,7 @@ def main(): actx = PyOpenCLArrayContext(queue) fd_connection = FromFiredrakeConnection(actx, fd_fspace) - fd_bdy_connection = FromBdyFiredrakeConnection(actx, + fd_bdy_connection = FromBoundaryFiredrakeConnection(actx, fd_fspace, 'on_boundary') @@ -66,7 +66,7 @@ def main(): draw_vertex_numbers=False, draw_element_numbers=False, set_bounding_box=True) - ax2.set_title("FromBdyFiredrakeConnection") + ax2.set_title("FromBoundaryFiredrakeConnection") plt.sca(ax2) draw_2d_mesh(fd_bdy_connection.discr.mesh, draw_vertex_numbers=False, @@ -85,14 +85,14 @@ def main(): ax1.set_title("cos(x+y) in\nFromFiredrakeConnection") vis.show_scalar_in_matplotlib_3d(field, do_show=False) - # Now repeat using FromBdyFiredrakeConnection + # Now repeat using FromBoundaryFiredrakeConnection bdy_discr = fd_bdy_connection.discr bdy_vis = make_visualizer(actx, bdy_discr, bdy_discr.groups[0].order+3) bdy_field = fd_bdy_connection.from_firedrake(fd_fntn, actx=actx) ax2 = fig.add_subplot(1, 2, 2, projection='3d') plt.sca(ax2) - ax2.set_title("cos(x+y) in\nFromBdyFiredrakeConnection") + ax2.set_title("cos(x+y) in\nFromBoundaryFiredrakeConnection") bdy_vis.show_scalar_in_matplotlib_3d(bdy_field, do_show=False) import matplotlib.cm as cm diff --git a/meshmode/interop/firedrake/__init__.py b/meshmode/interop/firedrake/__init__.py index 19581c84..f86d9de7 100644 --- a/meshmode/interop/firedrake/__init__.py +++ b/meshmode/interop/firedrake/__init__.py @@ -22,11 +22,12 @@ THE SOFTWARE. from meshmode.interop.firedrake.connection import ( - FromBdyFiredrakeConnection, FromFiredrakeConnection, ToFiredrakeConnection) + FromBoundaryFiredrakeConnection, FromFiredrakeConnection, + ToFiredrakeConnection) from meshmode.interop.firedrake.mesh import ( import_firedrake_mesh, export_mesh_to_firedrake) -__all__ = ["FromBdyFiredrakeConnection", "FromFiredrakeConnection", +__all__ = ["FromBoundaryFiredrakeConnection", "FromFiredrakeConnection", "ToFiredrakeConnection", "import_firedrake_mesh", "export_mesh_to_firedrake" ] diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 085ef628..8c9b67f6 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -24,7 +24,7 @@ __doc__ = """ .. autoclass:: FiredrakeConnection :members: .. autoclass:: FromFiredrakeConnection -.. autoclass:: FromBdyFiredrakeConnection +.. autoclass:: FromBoundaryFiredrakeConnection .. autoclass:: ToFiredrakeConnection """ @@ -697,7 +697,7 @@ class FromFiredrakeConnection(FiredrakeConnection): except ImportError: grp_factory = InterpolatoryQuadratureSimplexGroupFactory(degree) - # In case this class is really a FromBdyFiredrakeConnection, + # In case this class is really a FromBoundaryFiredrakeConnection, # get *cells_to_use* cells_to_use = self._get_cells_to_use(fdrake_fspace.mesh()) # Create to_discr @@ -753,7 +753,7 @@ class FromFiredrakeConnection(FiredrakeConnection): return None -class FromBdyFiredrakeConnection(FromFiredrakeConnection): +class FromBoundaryFiredrakeConnection(FromFiredrakeConnection): """ A connection created from a :mod:`firedrake` ``"CG"`` or ``"DG"`` function space which creates a @@ -781,8 +781,8 @@ class FromBdyFiredrakeConnection(FromFiredrakeConnection): :class:`~meshmode.interop.firedrake.FromFiredrakeConnection`. """ self.bdy_id = bdy_id - super(FromBdyFiredrakeConnection, self).__init__(actx, fdrake_fspace, - grp_factory=grp_factory) + super(FromBoundaryFiredrakeConnection, self).__init__( + actx, fdrake_fspace, grp_factory=grp_factory) def _get_cells_to_use(self, mesh): """ diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 6b2e30c2..e2df4dfa 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -477,7 +477,7 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, instead of :class:`BTAG_ALL`. This argument is primarily intended for use by a - :class:`meshmode.interop.firedrake.FromBdyFiredrakeConnection`. + :class:`meshmode.interop.firedrake.FromBoundaryFiredrakeConnection`. :arg normals: **Only** used if *fdrake_mesh* is a 1-surface embedded in 2-space. In this case, diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index b50e0b47..552f68c9 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -39,8 +39,8 @@ from meshmode.dof_array import DOFArray from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL, check_bc_coverage from meshmode.interop.firedrake import ( - FromFiredrakeConnection, FromBdyFiredrakeConnection, ToFiredrakeConnection, - import_firedrake_mesh) + FromFiredrakeConnection, FromBoundaryFiredrakeConnection, + ToFiredrakeConnection, import_firedrake_mesh) import pytest @@ -202,15 +202,15 @@ def test_to_fd_consistency(ctx_factory, mm_mesh, fspace_degree): # }}} -# {{{ Now check the FromBdyFiredrakeConnection consistency +# {{{ Now check the FromBoundaryFiredrakeConnection consistency def test_from_bdy_consistency(ctx_factory, fdrake_mesh, fdrake_family, fspace_degree): """ - Make basic checks that FiredrakeFromBdyConnection is not doing something - obviouisly wrong, i.e. that it has proper tagging, that it has + Make basic checks that FiredrakeFromBoundaryConnection is not doing + something obviously wrong, i.e. that it has proper tagging, that it has the right number of cells, etc. """ fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fspace_degree) @@ -219,9 +219,9 @@ def test_from_bdy_consistency(ctx_factory, queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) - frombdy_conn = FromBdyFiredrakeConnection(actx, - fdrake_fspace, - "on_boundary") + frombdy_conn = FromBoundaryFiredrakeConnection(actx, + fdrake_fspace, + "on_boundary") # Ensure the meshmode mesh has one group and make sure both # meshes agree on some basic properties @@ -303,7 +303,7 @@ def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values, cells_to_use = None if only_convert_bdy: # make a dummy connection which just has a bdy_id - class DummyConnection(FromBdyFiredrakeConnection): + class DummyConnection(FromBoundaryFiredrakeConnection): def __init__(self): self.bdy_id = 'on_boundary' cells_to_use = DummyConnection()._get_cells_to_use(square_or_cube_mesh) @@ -431,8 +431,9 @@ def test_from_fd_transfer(ctx_factory, fspace_degree, # make function space and build connection fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fspace_degree) if only_convert_bdy: - fdrake_connection = FromBdyFiredrakeConnection(actx, fdrake_fspace, - 'on_boundary') + fdrake_connection = FromBoundaryFiredrakeConnection(actx, + fdrake_fspace, + 'on_boundary') else: fdrake_connection = FromFiredrakeConnection(actx, fdrake_fspace) # get this for making functions in firedrake @@ -598,8 +599,9 @@ def test_from_fd_idempotency(ctx_factory, # # Otherwise, just continue as normal if only_convert_bdy: - fdrake_connection = FromBdyFiredrakeConnection(actx, fdrake_fspace, - 'on_boundary') + fdrake_connection = FromBoundaryFiredrakeConnection(actx, + fdrake_fspace, + 'on_boundary') temp = fdrake_connection.from_firedrake(fdrake_unique, actx=actx) fdrake_unique = \ fdrake_connection.from_meshmode(temp, -- GitLab From 19cb7ecb677f7d87a0e22112a81d3e839f6d2ea2 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 11:16:14 -0500 Subject: [PATCH 138/221] Use loopy branch benSepanski/firedrake-usable_for_potentials --- .github/workflows/ci.yml | 2 +- .gitlab-ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78bd0655..2aad7fb2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: . /home/firedrake/firedrake/bin/activate grep -v loopy requirements.txt > /tmp/myreq.txt pip install -r /tmp/myreq.txt - pip install git+https://github.com/benSepanski/loopy.git@firedrake-reinstate_program_executor_cache + pip install git+https://github.com/benSepanski/loopy.git@firedrake-usable_for_potentials # The Firedrake container is based on Py3.6 as of 2020-10-10, which # doesn't have dataclasses. pip install pytest dataclasses diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4396f139..ef7d70f3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -59,7 +59,7 @@ Python 3 POCL Firedrake: - source ~/firedrake/bin/activate - "grep -v loopy requirements.txt > myreq.txt" - pip install -r myreq.txt - - pip install git+https://github.com/benSepanski/loopy.git@firedrake-reinstate_program_executor_cache + - pip install git+https://github.com/benSepanski/loopy.git@firedrake-usable_for_potentials # The Firedrake container is based on Py3.6 as of 2020-10-10, which # doesn't have dataclasses. - pip install pytest dataclasses -- GitLab From 14f41c55969559db7f9574e3fa3dc289c91d8088 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Wed, 12 Aug 2020 11:28:35 -0500 Subject: [PATCH 139/221] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit documentation typos/improvements in interop.rst Co-authored-by: Andreas Klöckner --- doc/interop.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/interop.rst b/doc/interop.rst index 0b737a73..63aafca1 100644 --- a/doc/interop.rst +++ b/doc/interop.rst @@ -49,7 +49,7 @@ straightforward. Some language is different: created by calling the function :func:`~firedrake.functionspace.FunctionSpace` and referred to as a "function space" * In a mesh, any vertices, faces, cells, etc. are :mod:`firedrake` - "entities" (see `dmplex `_ + "entities" (see `the PETSc documentation on dmplex `__ for more info on how topological mesh information is stored in :mod:`firedrake`). @@ -57,7 +57,7 @@ Other than carefully tabulating how and which vertices/faces correspond to other vertices/faces/cells, there are two main difficulties. 1. :mod:`meshmode` has discontinuous polynomial function spaces - which use different unit nodes than :mod:`firedrake`. + which may use different unit nodes than :mod:`firedrake`. 2. :mod:`meshmode` requires that all mesh elements be positively oriented, :mod:`firedrake` does not. Meanwhile, when :mod:`firedrake` creates a mesh, it changes the element ordering and the local vertex ordering. @@ -68,7 +68,7 @@ and that the group of the :class:`meshmode.discretization.Discretization` being converted is a :class:`~meshmode.discretization.poly_element.InterpolatoryQuadratureSimplexElementGroup` of the same order. Then, on each element, the function space being -represented is the same in :mod:`firedrake` or :mod:`meshmode`. +represented is the same in :mod:`firedrake` and :mod:`meshmode`. We may simply resample to one system or another's unit nodes. To handle (2.), @@ -139,14 +139,14 @@ stored in an array which associates each :mod:`meshmode` node to the :mod:`firedrake` node found by tracing the above diagram (i.e. it stores -:math:`g\circ A\circ Resampling \circ f^{-1}`). +:math:`g\circ A\circ \text{Resampling} \circ f^{-1}`). For Developers: Firedrake Function Space Design Crash Course ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In firedrake, meshes and function spaces have a close relationship. +In Firedrake, meshes and function spaces have a close relationship. In particular, this is due to some structure described in this -`firedrake pull request `_. +`Firedrake pull request `_. If you wish to develop on / add to the implementation of conversion between :mod:`meshmode` and :mod:`firedrake`, you will need to understand their design style. Below is a crash course. @@ -156,7 +156,7 @@ that every function space should have a mesh, and the coordinates of the mesh should be representable as a function on that same mesh, which must live on some function space on the mesh... etc. Under the hood, we divide between topological and geometric objects, -roughly as so +roughly as so: (1) A reference element defined using :mod:`FInAT` and :mod:`fiat` is used to define what meshmode calls the unit nodes and unit @@ -213,7 +213,7 @@ Thus, by the coordinates of a mesh geometry we mean created from the :class:`~firedrake.functionspaceimpl.FunctionSpace` *f* lives in and the :class:`~firedrake.mesh.MeshGeometry` *f* defines. -Basically, it's this picture (where a->b if b depends on a) +Basically, it's this picture (where :math:`a\to b` if :math:`b` depends on :math:`a`) .. warning:: -- GitLab From bf636338e2fa8fd33161bcd576939131b5dcb29c Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Wed, 12 Aug 2020 11:30:19 -0500 Subject: [PATCH 140/221] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make accommodating loopy with and without kernel callables more clear Co-authored-by: Andreas Klöckner --- meshmode/array_context.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meshmode/array_context.py b/meshmode/array_context.py index e0b284f6..a995f67c 100644 --- a/meshmode/array_context.py +++ b/meshmode/array_context.py @@ -277,6 +277,7 @@ class PyOpenCLArrayContext(ArrayContext): def call_loopy(self, program, **kwargs): program = self.transform_loopy_program(program) + # accommodate loopy with and without kernel callables try: options = program.options except AttributeError: @@ -305,6 +306,7 @@ class PyOpenCLArrayContext(ArrayContext): def transform_loopy_program(self, program): # FIXME: This could be much smarter. import loopy as lp + # accommodate loopy with and without kernel callables try: all_inames = program.all_inames() except AttributeError: -- GitLab From 551da9cd0f9ee5bc142b127c428df4437a333153 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 13:26:19 -0500 Subject: [PATCH 141/221] Improved intersphinx links --- doc/interop.rst | 12 ++--- meshmode/interop/firedrake/connection.py | 57 ++++++++++++-------- meshmode/interop/firedrake/mesh.py | 30 ++++++----- meshmode/interop/firedrake/reference_cell.py | 2 +- 4 files changed, 59 insertions(+), 42 deletions(-) diff --git a/doc/interop.rst b/doc/interop.rst index 63aafca1..738a5423 100644 --- a/doc/interop.rst +++ b/doc/interop.rst @@ -64,7 +64,7 @@ correspond to other vertices/faces/cells, there are two main difficulties. (1.) is easily handled by insisting that the :mod:`firedrake` :class:`~firedrake.functionspaceimpl.WithGeometry` uses polynomial elements -and that the group of the :class:`meshmode.discretization.Discretization` +and that the group of the :class:`~meshmode.discretization.Discretization` being converted is a :class:`~meshmode.discretization.poly_element.InterpolatoryQuadratureSimplexElementGroup` of the same order. Then, on each element, the function space being @@ -135,7 +135,7 @@ the reordering if converting function values from :mod:`meshmode` to :mod:`firedrake`). The information for this whole reordering process is stored in -:attr:`~meshmode.interop.firedrake.FiredrakeConnection.mm2fd_node_mapping`, +:attr:`~meshmode.interop.firedrake.connection.FiredrakeConnection.mm2fd_node_mapping`, an array which associates each :mod:`meshmode` node to the :mod:`firedrake` node found by tracing the above diagram (i.e. it stores @@ -177,14 +177,14 @@ roughly as so: :class:`~firedrake.mesh.MeshTopology` to some values. Note that the function :func:`~firedrake.functionspace.FunctionSpace` in the firedrake API is used to create objects of class - :class:`~firedrake.functionspaceimpl.FunctionSpace` s + :class:`~firedrake.functionspaceimpl.FunctionSpace` and :class:`~firedrake.functionspaceimpl.WithGeometry` (see (6)). (4) A :class:`~firedrake.function.CoordinatelessFunction` (in the sense that its *domain* has no coordinates) which is a function in a - :class:`~firedrake.functionspaceimpl.FunctionSpace` + :class:`~firedrake.functionspaceimpl.FunctionSpace`. (5) A :class:`~firedrake.mesh.MeshGeometry` created from a :class:`~firedrake.functionspaceimpl.FunctionSpace` @@ -201,12 +201,12 @@ roughly as so: :func:`~firedrake.functionspace.FunctionSpace`. (7) A :class:`~firedrake.function.Function` is defined on a - :class:`~firedrake.functionspaceimpl.WithGeometry` + :class:`~firedrake.functionspaceimpl.WithGeometry`. Thus, by the coordinates of a mesh geometry we mean (a) On the hidden back-end: a :class:`~firedrake.function.CoordinatelessFunction` - *f* on some function space defined only on the mesh topology + *f* on some function space defined only on the mesh topology. (b) On the front-end: A :class:`~firedrake.function.Function` with the values of *f* but defined on a :class:`~firedrake.functionspaceimpl.WithGeometry` diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 8c9b67f6..e259bfa7 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -129,7 +129,8 @@ class FiredrakeConnection: """ def __init__(self, discr, fdrake_fspace, mm2fd_node_mapping, group_nr=None): """ - :param discr: A :mod:`meshmode` :class:`Discretization` + :param discr: A :mod:`meshmode` + :class:`~meshmode.discretization.Discretization` :param fdrake_fspace: A :mod:`firedrake` :class:`firedrake.functionspaceimpl.WithGeometry`. Must have ufl family ``'Lagrange'`` or @@ -163,7 +164,7 @@ class FiredrakeConnection: "not :class:`%s`." % type(fdrake_fspace)) if not isinstance(mm2fd_node_mapping, np.ndarray): raise TypeError(":param:`mm2fd_node_mapping` must be of type " - ":class:`np.ndarray`, " + ":class:`numpy.ndarray`, " "not :class:`%s`." % type(mm2fd_node_mapping)) if not isinstance(group_nr, int) and group_nr is not None: raise TypeError(":param:`group_nr` must be of type *int* or be " @@ -266,8 +267,9 @@ class FiredrakeConnection: is returned, or a tuple of integers defining the shape of values in a tensor function space, in which case a tensor function space is returned - :return: A :mod:`firedrake` :class:`WithGeometry` which corresponds to - *self.discr.groups[self.group_nr]* of the appropriate vector + :return: A :mod:`firedrake` + :class:`~firedrake.functionspaceimpl.WithGeometry` which corresponds + to *self.discr.groups[self.group_nr]* of the appropriate vector dimension :raises TypeError: If *shape* is of the wrong type @@ -408,7 +410,7 @@ class FiredrakeConnection: :arg out: Either 1. A :class:`~meshmode.dof_array.DOFArray` - 2. A :class:`np.ndarray` object array, each of whose + 2. A :class:`numpy.ndarray` object array, each of whose entries is a :class:`~meshmode.dof_array.DOFArray` 3. *None* @@ -418,11 +420,12 @@ class FiredrakeConnection: In the case of (2.), the shape of *out* must match `function.function_space().shape`. - In either case, each `DOFArray` must be a `DOFArray` + In either case, each :class:`~meshmode.dof_array.DOFArray` + must be a :class:`~meshmode.dof_array.DOFArray` defined on :attr:`discr` (as described in the documentation for :class:`~meshmode.dof_array.DOFArray`). - Also, each `DOFArray`'s *entry_dtype* must match the - *function.dat.data.dtype*, and be of shape + Also, each :class:`~meshmode.dof_array.DOFArray`'s *entry_dtype* must + match the *function.dat.data.dtype*, and be of shape *(nelements, nunit_dofs)*. In case (3.), an array is created satisfying @@ -433,7 +436,7 @@ class FiredrakeConnection: :arg actx: * If *out* is *None*, then *actx* is a :class:`~meshmode.array_context.ArrayContext` on which - to create the :class:`DOFArray` + to create the :class:`~meshmode.dof_array.DOFArray` * If *out* is not *None*, *actx* must be *None* or *out*'s *array_context*. @@ -506,12 +509,14 @@ class FiredrakeConnection: * A :class:`~meshmode.dof_array.DOFArray` representing a field of shape *tuple()* on :attr:`discr` - * A :class:`numpy.ndarray` of :class:`~meshmode.dof_array.DOFArray`s + * A :class:`numpy.ndarray` with + entries of class :class:`~meshmode.dof_array.DOFArray` representing a field of shape *mm_field.shape* on :attr:`discr` See :class:`~meshmode.dof.DOFArray` for further requirements. - The :attr:`group_nr`th entry of each :class:`DOFArray` + The :attr:`group_nr` entry of each + :class:`~meshmode.dof_array.DOFArray` must be of shape *(nelements, nunit_dofs)* and the *element_dtype* must match that used for :mod:`firedrake` :class:`Function`s @@ -642,19 +647,21 @@ class FromFiredrakeConnection(FiredrakeConnection): """ :arg actx: A :class:`~meshmode.array_context.ArrayContext` :arg fdrake_fspace: A :mod:`firedrake` ``"CG"`` or ``"DG"`` - function space (of class :class:`WithGeometry`) built on - a mesh which is importable by :func:`import_firedrake_mesh`. + function space (of class + :class:`~firedrake.functionspaceimpl.WithGeometry`) built on + a mesh which is importable by + :func:`~meshmode.interop.firedrake.mesh.import_firedrake_mesh`. :arg grp_factory: (optional) If not *None*, should be - a :class:`~meshmode.discretization.ElementGroupFactory` + a :class:`~meshmode.discretization.poly_element.ElementGroupFactory` whose group class is a subclass of :class:`~meshmode.discretization.InterpolatoryElementGroupBase`. If *None*, uses * A :class:`~meshmode.discretization.poly_element.\ - PolynomialRecursiveNodesGroupFactory` if :mod:`recursivenodes` is +PolynomialRecursiveNodesGroupFactory` if :mod:`recursivenodes` is installed * A :class:`~meshmode.discretization.poly_element.\ - PolynomialWarpAndBlendGroupFactory` +PolynomialWarpAndBlendGroupFactory` """ # Ensure fdrake_fspace is a function space with appropriate reference # element. @@ -761,8 +768,8 @@ class FromBoundaryFiredrakeConnection(FromFiredrakeConnection): least one vertex on the given boundary and allows transfer of functions to and from :mod:`firedrake`. - Use the same bdy_id as one would for a - :class:`firedrake.bcs.DirichletBC`. + Use the same bdy_id as one would for a :mod:`firedrake` + :class:`~firedrake.bcs.DirichletBC` instance. ``"on_boundary"`` corresponds to the entire boundary. .. attribute:: bdy_id @@ -774,11 +781,11 @@ class FromBoundaryFiredrakeConnection(FromFiredrakeConnection): def __init__(self, actx, fdrake_fspace, bdy_id, grp_factory=None): """ :arg bdy_id: A boundary marker of *fdrake_fspace.mesh()* as accepted by - the *boundary_nodes* method of a firedrake - :class:`firedrake.functionspaceimpl.WithGeometry`. + the *boundary_nodes* method of a :mod:`firedrake` + :class:`~firedrake.functionspaceimpl.WithGeometry`. Other arguments are as in - :class:`~meshmode.interop.firedrake.FromFiredrakeConnection`. + :class:`~meshmode.interop.firedrake.connection.FromFiredrakeConnection`. """ self.bdy_id = bdy_id super(FromBoundaryFiredrakeConnection, self).__init__( @@ -816,10 +823,14 @@ class ToFiredrakeConnection(FiredrakeConnection): """ def __init__(self, discr, group_nr=None, comm=None): """ - :param discr: A :class:`Discretization` to intialize the connection with + :param discr: A :class:`~meshmode.discretization.Discretization` + to intialize the connection with :param group_nr: The group number of the discretization to convert. If *None* there must be only one group. The selected group - must be of type :class:`InterpolatoryQuadratureSimplexElementGroup`. + must be of type + :class:`~meshmode.discretization.poly_element.\ +InterpolatoryQuadratureSimplexElementGroup`. + :param comm: Communicator to build a dmplex object on for the created firedrake mesh """ diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index e2df4dfa..c6a07dd7 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -422,8 +422,10 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, normals=None, no_normals_warn=None): """ - Create a :mod:`meshmode` :class:`Mesh` from a :mod:`firedrake` - :class:`MeshGeometry` with the same cells/elements, vertices, nodes, + Create a :mod:`meshmode` :class:`~meshmode.mesh.Mesh` + from a :mod:`firedrake` + :class:`~firedrake.mesh.MeshGeometry` + with the same cells/elements, vertices, nodes, mesh order, and facial adjacency. The vertex and node coordinates will be the same, as well @@ -435,7 +437,8 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, The flipped cells/elements are identified by the returned *firedrake_orient* array - :arg fdrake_mesh: A :mod:`firedrake` :class:`MeshGeometry`. + :arg fdrake_mesh: A :mod:`firedrake` + :class:`~firedrake.mesh.MeshGeometry`. This mesh **must** be in a space of ambient dimension 1, 2, or 3 and have co-dimension of 0 or 1. It must use a simplex as a reference element. @@ -473,11 +476,13 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, Note that in this later case, some faces that are not boundaries in *fdrake_mesh* may become boundaries in the returned mesh. These "induced" boundaries are marked with - :class:`BTAG_NO_BOUNDARY` (and :class:`BTAG_REALLY_ALL`) - instead of :class:`BTAG_ALL`. + :class:`~meshmode.mesh.BTAG_NO_BOUNDARY` + (and :class:`~meshmode.mesh.BTAG_REALLY_ALL`) + instead of :class:`~meshmode.mesh.BTAG_ALL`. This argument is primarily intended for use by a - :class:`meshmode.interop.firedrake.FromBoundaryFiredrakeConnection`. + :class:`~meshmode.interop.firedrake.connection.\ +FromBoundaryFiredrakeConnection`. :arg normals: **Only** used if *fdrake_mesh* is a 1-surface embedded in 2-space. In this case, @@ -670,14 +675,15 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): """ - Create a firedrake mesh corresponding to one :class:`Mesh`'s - :class:`SimplexElementGroup`. + Create a firedrake mesh corresponding to one + :class:`~meshmode.mesh.Mesh`'s + :class:`~meshmode.mesh.SimplexElementGroup`. - :param mesh: A :class:`meshmode.mesh.Mesh` to convert with - at least one :class:`SimplexElementGroup`. + :param mesh: A :class:`~meshmode.mesh.Mesh` to convert with + at least one :class:`~meshmode.mesh.SimplexElementGroup`. :param group_nr: The group number to be converted into a firedrake mesh. The corresponding group must be of type - :class:`SimplexElementGroup`. If *None* and + :class:`~meshmode.mesh.SimplexElementGroup`. If *None* and *mesh* has only one group, that group is used. Otherwise, a *ValueError* is raised. :param comm: The communicator to build the dmplex mesh on @@ -686,7 +692,7 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): where * *fdrake_mesh* is a :mod:`firedrake` - :class:`firedrake.mesh.MeshGeometry` corresponding to + :class:`~firedrake.mesh.MeshGeometry` corresponding to *mesh* * *fdrake_cell_ordering* is a numpy array: the *i*th element in *mesh* (i.e. the *i*th element in diff --git a/meshmode/interop/firedrake/reference_cell.py b/meshmode/interop/firedrake/reference_cell.py index 6ad6761b..9f1531b1 100644 --- a/meshmode/interop/firedrake/reference_cell.py +++ b/meshmode/interop/firedrake/reference_cell.py @@ -114,7 +114,7 @@ def get_finat_element_unit_nodes(finat_element): Returns the unit nodes used by the FInAT element in firedrake's (equivalently, FInAT/FIAT's) reference coordinates - :arg finat_element: A :class:`finat.finiteelementbase.FiniteElementBase` + :arg finat_element: A :class:`~finat.finiteelementbase.FiniteElementBase` instance (i.e. a firedrake function space's reference element). The refernce element of the finat element *MUST* be a simplex :return: A numpy array of shape *(dim, nunit_dofs)* holding the unit -- GitLab From 5eca136edf2bcec8e1b38cb9d89e86d2a3f989d5 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 13:30:17 -0500 Subject: [PATCH 142/221] Removed unnecessary asserts --- meshmode/array_context.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/meshmode/array_context.py b/meshmode/array_context.py index a995f67c..84dd8101 100644 --- a/meshmode/array_context.py +++ b/meshmode/array_context.py @@ -282,8 +282,6 @@ class PyOpenCLArrayContext(ArrayContext): options = program.options except AttributeError: options = program.root_kernel.options - assert options.return_dict - assert options.no_numpy if not (options.return_dict and options.no_numpy): raise ValueError("Loopy program passed to call_loopy must " "have return_dict and no_numpy options set. " -- GitLab From 2b17d44d7f9b7fc373118d43dc74654e386c630c Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 14:57:43 -0500 Subject: [PATCH 143/221] Try --upgrade for pip install since firedrake has loopy pre-installed --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2aad7fb2..b3be4aa5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: . /home/firedrake/firedrake/bin/activate grep -v loopy requirements.txt > /tmp/myreq.txt pip install -r /tmp/myreq.txt - pip install git+https://github.com/benSepanski/loopy.git@firedrake-usable_for_potentials + pip install --upgrade git+https://github.com/benSepanski/loopy.git@firedrake-usable_for_potentials # The Firedrake container is based on Py3.6 as of 2020-10-10, which # doesn't have dataclasses. pip install pytest dataclasses -- GitLab From 78d173b18235ecd2ea92cac5b0982e492179ab54 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 15:19:26 -0500 Subject: [PATCH 144/221] pip install --force-reinstall the right version of loopy for firedrake tests --- .github/workflows/ci.yml | 2 +- .gitlab-ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3be4aa5..9457918a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: . /home/firedrake/firedrake/bin/activate grep -v loopy requirements.txt > /tmp/myreq.txt pip install -r /tmp/myreq.txt - pip install --upgrade git+https://github.com/benSepanski/loopy.git@firedrake-usable_for_potentials + pip install --force-reinstall git+https://github.com/benSepanski/loopy.git@firedrake-usable_for_potentials # The Firedrake container is based on Py3.6 as of 2020-10-10, which # doesn't have dataclasses. pip install pytest dataclasses diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ef7d70f3..c3324b75 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -59,7 +59,7 @@ Python 3 POCL Firedrake: - source ~/firedrake/bin/activate - "grep -v loopy requirements.txt > myreq.txt" - pip install -r myreq.txt - - pip install git+https://github.com/benSepanski/loopy.git@firedrake-usable_for_potentials + - pip install --force-reinstall git+https://github.com/benSepanski/loopy.git@firedrake-usable_for_potentials # The Firedrake container is based on Py3.6 as of 2020-10-10, which # doesn't have dataclasses. - pip install pytest dataclasses -- GitLab From 6b3bd456de11c1e1b9edcddb69d28fa0cecb29cb Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 15:24:30 -0500 Subject: [PATCH 145/221] Add FIAT to intersphinx library --- doc/conf.py | 1 + doc/interop.rst | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index 4f15c9c2..04e723f2 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -284,4 +284,5 @@ intersphinx_mapping = { 'https://documen.tician.de/loopy': None, 'https://firedrakeproject.org/': None, 'https://tisaac.gitlab.io/recursivenodes/': None, + 'https://fenics.readthedocs.io/projects/fiat/en/latest/': None, } diff --git a/doc/interop.rst b/doc/interop.rst index 738a5423..6b42287f 100644 --- a/doc/interop.rst +++ b/doc/interop.rst @@ -158,7 +158,7 @@ on some function space on the mesh... etc. Under the hood, we divide between topological and geometric objects, roughly as so: -(1) A reference element defined using :mod:`FInAT` and :mod:`fiat` +(1) A reference element defined using :mod:`FInAT` and :mod:`FIAT` is used to define what meshmode calls the unit nodes and unit vertices. It is worth noting that :mod:`firedrake` does not require a positive orientation of elements and that its -- GitLab From 014667936e5475c987d35143796980aa2d376236 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 15:58:20 -0500 Subject: [PATCH 146/221] Assert that all vertex_indices are set in _get_firedrake_nodal_info --- meshmode/interop/firedrake/mesh.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index c6a07dd7..e0f8d949 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -141,6 +141,9 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology, cells_to_use=None): cell_to_nodal_neighbors[other_cell_ndx].append(fd_cell_ndx) vert_to_cells[dmp_vert_id].append(fd_cell_ndx) + # make sure that no -1s remain in vertex_indices (i.e. none are left unset) + assert np.all(vertex_indices >= 0) + # Next go ahead and compute nodal adjacency by creating # neighbors and neighbor_starts as specified by :class:`NodalAdjacency` neighbors = [] -- GitLab From 816a41650a4cb5ca68dfaabc52b43806e005f8ae Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 16:00:46 -0500 Subject: [PATCH 147/221] Some typos + renaming from @inducer's code review --- meshmode/interop/firedrake/reference_cell.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/meshmode/interop/firedrake/reference_cell.py b/meshmode/interop/firedrake/reference_cell.py index 9f1531b1..60d4e968 100644 --- a/meshmode/interop/firedrake/reference_cell.py +++ b/meshmode/interop/firedrake/reference_cell.py @@ -32,13 +32,13 @@ __doc__ = """ # {{{ Map between reference simplices -def get_affine_reference_simplex_mapping(spat_dim, firedrake_to_meshmode=True): +def get_affine_reference_simplex_mapping(ambient_dim, firedrake_to_meshmode=True): """ Returns a function which takes a numpy array points on one reference cell and maps each point to another using a positive affine map. - :arg spat_dim: The spatial dimension + :arg ambient_dim: The spatial dimension :arg firedrake_to_meshmode: If true, the returned function maps from the firedrake reference element to meshmode, if false maps from @@ -54,16 +54,16 @@ def get_affine_reference_simplex_mapping(spat_dim, firedrake_to_meshmode=True): no input validation. """ # validate input - assert isinstance(spat_dim, int) - assert spat_dim >= 0 + assert isinstance(ambient_dim, int) + assert ambient_dim >= 0 assert isinstance(firedrake_to_meshmode, bool) from FIAT.reference_element import ufc_simplex from modepy.tools import unit_vertices # Get the unit vertices from each system, # each stored with shape *(dim, nunit_vertices)* - firedrake_unit_vertices = np.array(ufc_simplex(spat_dim).vertices).T - modepy_unit_vertices = unit_vertices(spat_dim).T + firedrake_unit_vertices = np.array(ufc_simplex(ambient_dim).vertices).T + modepy_unit_vertices = unit_vertices(ambient_dim).T if firedrake_to_meshmode: from_verts = firedrake_unit_vertices @@ -77,7 +77,7 @@ def get_affine_reference_simplex_mapping(spat_dim, firedrake_to_meshmode=True): assert from_verts.shape == to_verts.shape dim, nvects = from_verts.shape - # If only have on vertex, have A = I and b = to_vert - from_vert + # If only have one vertex, have A = I and b = to_vert - from_vert if nvects == 1: shift = to_verts[:, 0] - from_verts[:, 0] @@ -116,7 +116,7 @@ def get_finat_element_unit_nodes(finat_element): :arg finat_element: A :class:`~finat.finiteelementbase.FiniteElementBase` instance (i.e. a firedrake function space's reference element). - The refernce element of the finat element *MUST* be a simplex + The reference element of the finat element *MUST* be a simplex. :return: A numpy array of shape *(dim, nunit_dofs)* holding the unit nodes used by this element. *dim* is the dimension spanned by the finat element's reference element -- GitLab From 9885d96e7d4975e192352d01186034596b52b75a Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 16:13:26 -0500 Subject: [PATCH 148/221] Changed asserts to if ... raise for user input validation --- meshmode/interop/firedrake/mesh.py | 20 +++++++++++++++----- meshmode/interop/firedrake/reference_cell.py | 18 +++++++++++++----- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index e0f8d949..1de248f3 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -724,11 +724,21 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): if len(mesh.groups) != 1: raise ValueError(":arg:`group_nr` is *None* but :arg:`mesh` has " "more than one group.") - else: - group_nr = 0 - assert group_nr >= 0 and group_nr < len(mesh.groups) - assert isinstance(mesh.groups[group_nr], SimplexElementGroup) - assert mesh.vertices is not None + group_nr = 0 + if not isinstance(group_nr, int): + raise TypeError("Expecting :arg:`group_nr` to be of type *int*, not " + f"{type(group_nr)}") + if group_nr < 0 or group_nr >= len(mesh.groups): + raise ValueError(":arg:`group_nr` is an invalid group index:" + f" {group_nr} fails to satisfy " + f"0 <= {group_nr} < {len(mesh.groups)}") + if not isinstance(mesh.groups[group_nr], SimplexElementGroup): + raise TypeError("Expecting *mesh.groups[group_nr] to be of type " + ":class:`meshmode.mesh.SimplexElementGroup`, not " + f"{type(mesh.groups[group_nr])}") + if mesh.vertices is None: + raise ValueError(":arg:`mesh` has no vertices " + "(*mesh.vertices* is *None*)") # Get the vertices and vertex indices of the requested group group = mesh.groups[group_nr] diff --git a/meshmode/interop/firedrake/reference_cell.py b/meshmode/interop/firedrake/reference_cell.py index 60d4e968..a6fdd2b7 100644 --- a/meshmode/interop/firedrake/reference_cell.py +++ b/meshmode/interop/firedrake/reference_cell.py @@ -54,9 +54,14 @@ def get_affine_reference_simplex_mapping(ambient_dim, firedrake_to_meshmode=True no input validation. """ # validate input - assert isinstance(ambient_dim, int) - assert ambient_dim >= 0 - assert isinstance(firedrake_to_meshmode, bool) + if not isinstance(ambient_dim, int): + raise TypeError(":arg:`ambient_dim` must be of type *int*, not " + f"{type(ambient_dim)}") + if ambient_dim < 0: + raise ValueError(":arg:`ambient_dim` must be non-negative") + if not isinstance(firedrake_to_meshmode, bool): + raise TypeError(":arg:`firedrake_to_meshmode` must be of type *bool*, not " + f"{type(firedrake_to_meshmode)}") from FIAT.reference_element import ufc_simplex from modepy.tools import unit_vertices @@ -123,8 +128,11 @@ def get_finat_element_unit_nodes(finat_element): (see its ``cell`` attribute) """ from FIAT.reference_element import Simplex - assert isinstance(finat_element.cell, Simplex), \ - "Reference element of the finat element MUST be a simplex" + if not isinstance(finat_element.cell, Simplex): + raise TypeError("Reference element of the finat element MUST be a" + " simplex, i.e. :arg:`finat_element`'s *cell* attribute must" + " be of type :class:`FIAT.reference_element.Simplex`, not " + f"{type(finat_element.cell)}") # point evaluators is a list of functions *p_0,...,p_{n-1}*. # *p_i(f)* evaluates function *f* at node *i* (stored as a tuple), # so to recover node *i* we need to evaluate *p_i* at the identity -- GitLab From 5fa3ac2848e0731bde006626b6748c648dc7c8d2 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Wed, 12 Aug 2020 16:43:30 -0500 Subject: [PATCH 149/221] Comment changes / making code more pythonic from @inducer's code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andreas Klöckner --- meshmode/interop/firedrake/mesh.py | 50 +++++++++++++----------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 1de248f3..68fc4fcf 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -117,18 +117,15 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology, cells_to_use=None): # Store the vertex indices dmp_verts = closure_dmp_ids[np.logical_and(v_start <= closure_dmp_ids, closure_dmp_ids < v_end)] - fd_verts = np.array([vert_id_dmp_to_fd(dmp_vert) - for dmp_vert in dmp_verts]) - vertex_indices[fd_cell_ndx][:] = fd_verts[:] + vertex_indices[fd_cell_ndx][:] = np.array([ + vert_id_dmp_to_fd(dmp_vert) for dmp_vert in dmp_verts]) # Record this cell as touching the facet and remember its local - # facet number (the order it appears) + # facet number (the index at which it appears) dmp_fac_ids = closure_dmp_ids[np.logical_and(f_start <= closure_dmp_ids, closure_dmp_ids < f_end)] for loc_fac_nr, dmp_fac_id in enumerate(dmp_fac_ids): - # make sure there is a list to append to and append - facet_to_cells.setdefault(dmp_fac_id, []) - facet_to_cells[dmp_fac_id].append((fd_cell_ndx, loc_fac_nr)) + facet_to_cells.setdefault(dmp_fac_id, []).append((fd_cell_ndx, loc_fac_nr)) # Record this vertex as touching the cell, and mark this cell # as nodally adjacent (in cell_to_nodal_neighbors) to any @@ -161,7 +158,7 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology, cells_to_use=None): nodal_adjacency = NodalAdjacency(neighbors_starts=neighbors_starts, neighbors=neighbors) - return (vertex_indices, nodal_adjacency) + return vertex_indices, nodal_adjacency def _get_firedrake_boundary_tags(fdrake_mesh, no_boundary=False): @@ -186,7 +183,7 @@ def _get_firedrake_boundary_tags(fdrake_mesh, no_boundary=False): unique_markers = fdrake_mesh.topology.exterior_facets.unique_markers if unique_markers is not None: - bdy_tags += list(unique_markers) + bdy_tags.extend(unique_markers) return tuple(bdy_tags) @@ -241,10 +238,7 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, break # We need boundary tags to tag the boundary - no_boundary = False - if cells_to_use is not None: - no_boundary = True - bdy_tags = _get_firedrake_boundary_tags(top, no_boundary=no_boundary) + bdy_tags = _get_firedrake_boundary_tags(top, no_boundary=cells_to_use is not None) boundary_tag_to_index = {bdy_tag: i for i, bdy_tag in enumerate(bdy_tags)} def boundary_tag_bit(boundary_tag): @@ -253,7 +247,7 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, except KeyError: raise 0 - # Now do the interconnectivity group + # {{{ build the FacialAdjacencyGroup for internal connectivity # Get the firedrake cells associated to each interior facet int_facet_cell = top.interior_facets.facet_cell @@ -302,7 +296,9 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, element_faces=int_element_faces, neighbor_faces=int_neighbor_faces) - # Then look at exterior facets + # }}} + + # {{{ build the FacialAdjacencyGroup for boundary faces # We can get the elements directly from exterior facets ext_elements = top.exterior_facets.facet_cell.flatten() @@ -341,6 +337,8 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, neighbors=ext_neighbors, neighbor_faces=ext_neighbor_faces) + # }}} + return [{0: interconnectivity_grp, None: exterior_grp}] # }}} @@ -360,8 +358,7 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, :arg vertices: The vertex coordinates as a numpy array of shape *(ambient_dim, nvertices)* (the vertices of *unflipped_group*) :arg normals: As described in the kwargs of :func:`import_firedrake_mesh` - :arg no_normals_warn: As described in the kwargs of - :func:`import_firedrake_mesh` + :arg no_normals_warn: As described in :func:`import_firedrake_mesh` :arg cells_to_use: If *None*, then ignored. Otherwise, a numpy array of unique firedrake cell indices indicating which cells to use. @@ -414,7 +411,7 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, #Make sure the mesh fell into one of the cases #Nb : This should be guaranteed by previous checks, # but is here anyway in case of future development. - assert orient is not None, "something went wrong, contact the developer" + assert orient is not None return orient # }}} @@ -425,9 +422,8 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, normals=None, no_normals_warn=None): """ - Create a :mod:`meshmode` :class:`~meshmode.mesh.Mesh` - from a :mod:`firedrake` - :class:`~firedrake.mesh.MeshGeometry` + Create a :class:`meshmode.mesh.Mesh` + from a :class:`firedrake.mesh.MeshGeometry` with the same cells/elements, vertices, nodes, mesh order, and facial adjacency. @@ -440,8 +436,7 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, The flipped cells/elements are identified by the returned *firedrake_orient* array - :arg fdrake_mesh: A :mod:`firedrake` - :class:`~firedrake.mesh.MeshGeometry`. + :arg fdrake_mesh: :class:`firedrake.mesh.MeshGeometry`. This mesh **must** be in a space of ambient dimension 1, 2, or 3 and have co-dimension of 0 or 1. It must use a simplex as a reference element. @@ -530,10 +525,7 @@ FromBoundaryFiredrakeConnection`. fdrake_mesh.init() # Get all the nodal information we can from the topology - no_boundary = False - if cells_to_use is not None: - no_boundary = True - bdy_tags = _get_firedrake_boundary_tags(fdrake_mesh, no_boundary=no_boundary) + bdy_tags = _get_firedrake_boundary_tags(fdrake_mesh, no_boundary=cells_to_use is not None) vertex_indices, nodal_adjacency = \ _get_firedrake_nodal_info(fdrake_mesh, cells_to_use=cells_to_use) # If only using some cells, vertices may need new indices as many @@ -561,10 +553,10 @@ FromBoundaryFiredrakeConnection`. if cells_to_use is not None: cell_node_list = cell_node_list[cells_to_use] nodes = np.real(coords.dat.data[cell_node_list]) - # Add extra dim in 1D so that have [nelements][nunit_nodes][dim] + # Add extra dim in 1D for shape (nelements, nunit_nodes, dim) if len(nodes.shape) == 2: nodes = np.reshape(nodes, nodes.shape + (1,)) - # Now we want the nodes to actually have shape [dim][nelements][nunit_nodes] + # Transpose nodes to have shape (dim, nelements, nunit_nodes) nodes = np.transpose(nodes, (2, 0, 1)) # make a group (possibly with some elements that need to be flipped) -- GitLab From a1c0842aeb18cc143430cbed0d7d7f43e17c8517 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 16:46:31 -0500 Subject: [PATCH 150/221] boundary_tags is a list now, not a tuple --- meshmode/interop/firedrake/mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 68fc4fcf..d2d1e9ae 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -185,7 +185,7 @@ def _get_firedrake_boundary_tags(fdrake_mesh, no_boundary=False): if unique_markers is not None: bdy_tags.extend(unique_markers) - return tuple(bdy_tags) + return bdy_tags def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, -- GitLab From ede4055166419770d34d7af9fc47e084240b5a9c Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 16:51:48 -0500 Subject: [PATCH 151/221] flake8 fixes + determine mm->fd facet indices using python set ops --- meshmode/interop/firedrake/mesh.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index d2d1e9ae..c9615004 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -125,7 +125,8 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology, cells_to_use=None): dmp_fac_ids = closure_dmp_ids[np.logical_and(f_start <= closure_dmp_ids, closure_dmp_ids < f_end)] for loc_fac_nr, dmp_fac_id in enumerate(dmp_fac_ids): - facet_to_cells.setdefault(dmp_fac_id, []).append((fd_cell_ndx, loc_fac_nr)) + facet_to_cells.setdefault(dmp_fac_id, []).append((fd_cell_ndx, + loc_fac_nr)) # Record this vertex as touching the cell, and mark this cell # as nodally adjacent (in cell_to_nodal_neighbors) to any @@ -231,14 +232,14 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, fd_loc_fac_nr_to_mm = {} # Figure out which vertex is excluded to get the corresponding # firedrake local index - for mm_loc_fac_nr, face in enumerate(mm_face_vertex_indices): - for fd_loc_fac_nr in range(top.ufl_cell().num_vertices()): - if fd_loc_fac_nr not in face: - fd_loc_fac_nr_to_mm[fd_loc_fac_nr] = mm_loc_fac_nr - break + all_local_facet_nrs = set(range(top.ufl_cell().num_vertices())) + for mm_local_facet_nr, face in enumerate(mm_face_vertex_indices): + fd_local_facet_nr = all_local_facet_nrs - set(face) + fd_loc_fac_nr_to_mm[fd_local_facet_nr] = mm_local_facet_nr # We need boundary tags to tag the boundary - bdy_tags = _get_firedrake_boundary_tags(top, no_boundary=cells_to_use is not None) + bdy_tags = _get_firedrake_boundary_tags(top, + no_boundary=cells_to_use is not None) boundary_tag_to_index = {bdy_tag: i for i, bdy_tag in enumerate(bdy_tags)} def boundary_tag_bit(boundary_tag): @@ -297,7 +298,7 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, neighbor_faces=int_neighbor_faces) # }}} - + # {{{ build the FacialAdjacencyGroup for boundary faces # We can get the elements directly from exterior facets @@ -338,7 +339,7 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, neighbor_faces=ext_neighbor_faces) # }}} - + return [{0: interconnectivity_grp, None: exterior_grp}] # }}} @@ -525,7 +526,8 @@ FromBoundaryFiredrakeConnection`. fdrake_mesh.init() # Get all the nodal information we can from the topology - bdy_tags = _get_firedrake_boundary_tags(fdrake_mesh, no_boundary=cells_to_use is not None) + bdy_tags = _get_firedrake_boundary_tags(fdrake_mesh, + no_boundary=cells_to_use is not None) vertex_indices, nodal_adjacency = \ _get_firedrake_nodal_info(fdrake_mesh, cells_to_use=cells_to_use) # If only using some cells, vertices may need new indices as many -- GitLab From f842197514e044b4f2ea97b48fafd4e56cc505aa Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 16:54:31 -0500 Subject: [PATCH 152/221] Make sure to get the actual face out of set({face}) --- meshmode/interop/firedrake/mesh.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index c9615004..1a952e8d 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -235,6 +235,8 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, all_local_facet_nrs = set(range(top.ufl_cell().num_vertices())) for mm_local_facet_nr, face in enumerate(mm_face_vertex_indices): fd_local_facet_nr = all_local_facet_nrs - set(face) + assert len(fd_local_facet_nr) == 1 + (fd_local_facet_nr,) = fd_local_facet_nr # extract id from set({id}) fd_loc_fac_nr_to_mm[fd_local_facet_nr] = mm_local_facet_nr # We need boundary tags to tag the boundary -- GitLab From 0ffde6662f26651c0b08832ae7871bff970cb9f8 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 17:08:51 -0500 Subject: [PATCH 153/221] Use meshmode.mesh._boundary_tag_bit, store a lookup table for markers --- meshmode/interop/firedrake/mesh.py | 38 ++++++++++++++++++------------ 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 1a952e8d..7c915b84 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -239,16 +239,22 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, (fd_local_facet_nr,) = fd_local_facet_nr # extract id from set({id}) fd_loc_fac_nr_to_mm[fd_local_facet_nr] = mm_local_facet_nr - # We need boundary tags to tag the boundary + # build a look-up table from firedrake markers to the appropriate values + # in the neighbors array for the external and internal facial adjacency + # groups bdy_tags = _get_firedrake_boundary_tags(top, no_boundary=cells_to_use is not None) boundary_tag_to_index = {bdy_tag: i for i, bdy_tag in enumerate(bdy_tags)} - - def boundary_tag_bit(boundary_tag): - try: - return 1 << boundary_tag_to_index[boundary_tag] - except KeyError: - raise 0 + marker_to_neighbor_value = {} + from meshmode.mesh import _boundary_tag_bit + # None for no marker + marker_to_neighbor_value[None] = \ + -(_boundary_tag_bit(bdy_tags, boundary_tag_to_index, BTAG_REALLY_ALL) + | _boundary_tag_bit(bdy_tags, boundary_tag_to_index, BTAG_ALL)) + for marker in top.exterior_facets.unique_markers: + marker_to_neighbor_value[marker] = \ + -(_boundary_tag_bit(bdy_tags, boundary_tag_to_index, marker) + | -marker_to_neighbor_value[None]) # {{{ build the FacialAdjacencyGroup for internal connectivity @@ -285,13 +291,18 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, int_neighbor_faces = int_neighbor_faces[to_keep] # For neighbor cells, change to new cell index or mark # as a new boundary (if the neighbor cell is not being used) + no_bdy_neighbor_tag = -(_boundary_tag_bit(bdy_tags, + boundary_tag_to_index, + BTAG_REALLY_ALL) + | _boundary_tag_bit(bdy_tags, + boundary_tag_to_index, + BTAG_NO_BOUNDARY)) for ndx, icell in enumerate(int_neighbors): try: int_neighbors[ndx] = cells_to_use_inv[icell] except KeyError: - int_neighbors[ndx] = -(boundary_tag_bit(BTAG_REALLY_ALL) - | boundary_tag_bit(BTAG_NO_BOUNDARY)) - int_neighbor_faces[ndx] = 0.0 + int_neighbors[ndx] = no_bdy_neighbor_tag + int_neighbor_faces[ndx] = 0 interconnectivity_grp = FacialAdjacencyGroup(igroup=0, ineighbor_group=0, elements=int_elements, @@ -325,13 +336,10 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, if top.exterior_facets.markers is not None: ext_neighbors = np.zeros(ext_elements.shape, dtype=IntType) for ifac, marker in enumerate(top.exterior_facets.markers): - ext_neighbors[ifac] = -(boundary_tag_bit(BTAG_ALL) - | boundary_tag_bit(BTAG_REALLY_ALL) - | boundary_tag_bit(marker)) + ext_neighbors[ifac] = marker_to_neighbor_value[marker] else: ext_neighbors = np.full(ext_elements.shape, - -(boundary_tag_bit(BTAG_ALL) - | boundary_tag_bit(BTAG_REALLY_ALL)), + marker_to_neighbor_value[None], dtype=IntType) exterior_grp = FacialAdjacencyGroup(igroup=0, ineighbor=None, -- GitLab From 5a7037e5408802addcbb0c68bc7f059bb0dd89ba Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 17:51:00 -0500 Subject: [PATCH 154/221] no_boundary -> include_no_boundary --- meshmode/interop/firedrake/mesh.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 7c915b84..44df2fcb 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -162,7 +162,7 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology, cells_to_use=None): return vertex_indices, nodal_adjacency -def _get_firedrake_boundary_tags(fdrake_mesh, no_boundary=False): +def _get_firedrake_boundary_tags(fdrake_mesh, include_no_boundary=False): """ Return a tuple of bdy tags as requested in the construction of a :mod:`meshmode` :class:`Mesh` @@ -174,12 +174,12 @@ def _get_firedrake_boundary_tags(fdrake_mesh, no_boundary=False): :arg fdrake_mesh: A :mod:`firedrake` :class:`MeshTopology` or :class:`MeshGeometry` - :arg no_boundary: If *True*, include :class:`BTAG_NO_BOUNDARY` + :arg include_no_boundary: If *True*, include :class:`BTAG_NO_BOUNDARY` :return: A tuple of boundary tags """ bdy_tags = [BTAG_ALL, BTAG_REALLY_ALL] - if no_boundary: + if include_no_boundary: bdy_tags.append(BTAG_NO_BOUNDARY) unique_markers = fdrake_mesh.topology.exterior_facets.unique_markers @@ -242,8 +242,10 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, # build a look-up table from firedrake markers to the appropriate values # in the neighbors array for the external and internal facial adjacency # groups - bdy_tags = _get_firedrake_boundary_tags(top, - no_boundary=cells_to_use is not None) + include_no_boundary = cells_to_use is not None + bdy_tags = \ + _get_firedrake_boundary_tags(top, + include_no_boundary=include_no_boundary) boundary_tag_to_index = {bdy_tag: i for i, bdy_tag in enumerate(bdy_tags)} marker_to_neighbor_value = {} from meshmode.mesh import _boundary_tag_bit @@ -536,8 +538,10 @@ FromBoundaryFiredrakeConnection`. fdrake_mesh.init() # Get all the nodal information we can from the topology - bdy_tags = _get_firedrake_boundary_tags(fdrake_mesh, - no_boundary=cells_to_use is not None) + include_no_boundary = cells_to_use is not None + bdy_tags = \ + _get_firedrake_boundary_tags(fdrake_mesh, + include_no_boundary=include_no_boundary) vertex_indices, nodal_adjacency = \ _get_firedrake_nodal_info(fdrake_mesh, cells_to_use=cells_to_use) # If only using some cells, vertices may need new indices as many -- GitLab From 8b5c7f2c6fe30066a207f0c4a328a4f142194b59 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 17:52:11 -0500 Subject: [PATCH 155/221] grammar fix + remove hanging indentation --- meshmode/interop/firedrake/mesh.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 44df2fcb..5119cbba 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -285,9 +285,10 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, np.arange(np.size(cells_to_use), dtype=IntType))) - # Only keep cells that using, and change to new cell index + # Keep the cells that we are using and change old cell index + # to new cell index int_elements = np.vectorize(cells_to_use_inv.__getitem__)( - int_elements[to_keep]) + int_elements[to_keep]) int_element_faces = int_element_faces[to_keep] int_neighbors = int_neighbors[to_keep] int_neighbor_faces = int_neighbor_faces[to_keep] -- GitLab From 5dbc06342da222d0345649e3f339cdac741b73b5 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 18:07:34 -0500 Subject: [PATCH 156/221] add explanation for initialization firedrake mesh --- meshmode/interop/firedrake/mesh.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 5119cbba..efb16d2c 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -536,6 +536,15 @@ FromBoundaryFiredrakeConnection`. tdim = fdrake_mesh.topological_dimension() assert gdim in [1, 2, 3], "Mesh must be in space of ambient dim 1, 2, or 3" assert gdim - tdim in [0, 1], "Mesh co-dimension must be 0 or 1" + # firedrake meshes are not guaranteed be fully instantiated until + # the .init() method is called. In particular, the coordinates function + # may not be accessible if we do not call init(). If the mesh has + # already been initialized, nothing will change. For more details + # on why we need a second initialization, see + # this pull request: + # https://github.com/firedrakeproject/firedrake/pull/627 + # which details how Firedrake implements a mesh's coordinates + # as a function on that very same mesh fdrake_mesh.init() # Get all the nodal information we can from the topology -- GitLab From 210814137222cc05b79f1e7fdc976b52d0ce2ac8 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 12 Aug 2020 18:10:04 -0500 Subject: [PATCH 157/221] Check topological dimension explicitly --- meshmode/interop/firedrake/mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index efb16d2c..72c46443 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -580,7 +580,7 @@ FromBoundaryFiredrakeConnection`. cell_node_list = cell_node_list[cells_to_use] nodes = np.real(coords.dat.data[cell_node_list]) # Add extra dim in 1D for shape (nelements, nunit_nodes, dim) - if len(nodes.shape) == 2: + if tdim == 1: nodes = np.reshape(nodes, nodes.shape + (1,)) # Transpose nodes to have shape (dim, nelements, nunit_nodes) nodes = np.transpose(nodes, (2, 0, 1)) -- GitLab From 37e4995d0520c0e80499f25bdfd4eee744735b7a Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 08:40:05 -0500 Subject: [PATCH 158/221] 1e-14 tol -> 2e-14 tol --- test/test_firedrake_interop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 552f68c9..5ed2d08d 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -478,7 +478,7 @@ def test_from_fd_transfer(ctx_factory, fspace_degree, % (fd2mm, d), eoc_rec) assert ( eoc_rec.order_estimate() >= fspace_degree - or eoc_rec.max_error() < 1e-14) + or eoc_rec.max_error() < 2e-14) @pytest.mark.parametrize("mesh_name,mesh_pars,dim", -- GitLab From 389eb00ed1a19c57ea66e79d8f3ba18f9662ad4a Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 09:02:35 -0500 Subject: [PATCH 159/221] Improve comments in _get_firedrake_orientations --- meshmode/interop/firedrake/mesh.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 72c46443..264d8b71 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -366,12 +366,12 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, """ Return the orientations of the mesh elements: - :arg fdrake_mesh: A :mod:`firedrake` instance of :class:`MeshGeometry` + :arg fdrake_mesh: As described in :func:`import_firedrake_mesh` :arg unflipped_group: A :class:`SimplexElementGroup` instance with (potentially) some negatively oriented elements. :arg vertices: The vertex coordinates as a numpy array of shape *(ambient_dim, nvertices)* (the vertices of *unflipped_group*) - :arg normals: As described in the kwargs of :func:`import_firedrake_mesh` + :arg normals: As described in :func:`import_firedrake_mesh` :arg no_normals_warn: As described in :func:`import_firedrake_mesh` :arg cells_to_use: If *None*, then ignored. Otherwise, a numpy array of unique firedrake cell indices indicating which cells to use. @@ -388,15 +388,19 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, orient = None if gdim == tdim: - # We use :mod:`meshmode` to check our orientations + # If the co-dimension is 0, :mod:`meshmode` has a convenient + # function to compute cell orientations from meshmode.mesh.processing import \ find_volume_mesh_element_group_orientation orient = find_volume_mesh_element_group_orientation(vertices, unflipped_group) - if tdim == 1 and gdim == 2: - # In this case we have a 1-surface embedded in 2-space + elif tdim == 1 and gdim == 2: + # In this case we have a 1-surface embedded in 2-space. + # Firedrake does not provide any convenient way of + # letting the user set cell orientations in this case, so we + # have to ask the user for cell normals directly. if cells_to_use is None: num_cells = fdrake_mesh.num_cells() else: @@ -412,7 +416,14 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, warn("Assuming all elements are positively-oriented.") elif tdim == 2 and gdim == 3: - # In this case we have a 2-surface embedded in 3-space + # In this case we have a 2-surface embedded in 3-space. + # In this case, we assume the user has called + # :func:`firedrake.mesh.MeshGeometry.init_cell_orientations`, see + # https://www.firedrakeproject.org/variational-problems.html#ensuring-consistent-cell-orientations # noqa : E501 + # for a tutorial on how these are usually initialized. + # + # Unfortunately, *init_cell_orientations* is currently only implemented + # in 3D, so we can't use this in the 1/2 case. orient = fdrake_mesh.cell_orientations().dat.data if cells_to_use is not None: orient = orient[cells_to_use] @@ -422,9 +433,9 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, """ orient *= 2 orient -= 1 - #Make sure the mesh fell into one of the cases - #Nb : This should be guaranteed by previous checks, - # but is here anyway in case of future development. + # Make sure the mesh fell into one of the cases + # Nb : This should be guaranteed by previous checks, + # but is here anyway in case of future development. assert orient is not None return orient -- GitLab From db0e090c9ae35c5470fa170d92fdcd1ba4434ac8 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 09:10:42 -0500 Subject: [PATCH 160/221] remove unnecessary six as we are py3 now --- meshmode/interop/firedrake/connection.py | 5 ++--- meshmode/interop/firedrake/mesh.py | 12 +++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index e259bfa7..8d739c93 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -30,7 +30,6 @@ __doc__ = """ import numpy as np import numpy.linalg as la -import six from modepy import resampling_matrix @@ -242,7 +241,7 @@ class FiredrakeConnection: # *mm2fd_node_mapping*. Only equivalence classes of size > 1 # are included. self._mm_node_equiv_classes = [tuple(equiv_class) for equiv_class - in six.itervalues(fd_to_dupes)] + in fd_to_dupes.values()] # Store input self.discr = discr @@ -858,7 +857,7 @@ InterpolatoryQuadratureSimplexElementGroup`. from pyop2.datatypes import IntType mm2fd_node_mapping = np.ndarray((el_group.nelements, el_group.nunit_dofs), dtype=IntType) - for perm, cells in six.iteritems(perm2cells): + for perm, cells in perm2cells.items(): # reordering_arr[i] should be the fd node corresponding to meshmode # node i # diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 264d8b71..6615d704 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -27,7 +27,6 @@ __doc__ = """ from warnings import warn # noqa import numpy as np -import six from modepy import resampling_matrix, simplex_best_available_basis @@ -483,10 +482,9 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, .. code-block:: python - import six coords_fspace = fdrake_mesh.coordinates.function_space() vertex_entity_dofs = coords_fspace.finat_element.entity_dofs()[0] - for entity, dof_list in six.iteritems(vertex_entity_dofs): + for entity, dof_list in vertex_entity_dofs.items(): assert len(dof_list) > 0 :arg cells_to_use: Either *None*, in which case this argument is ignored, @@ -614,7 +612,7 @@ FromBoundaryFiredrakeConnection`. unit_vertex_indices = [] # iterate through the dofs associated to each vertex on the # reference element - for _, dofs in sorted(six.iteritems(coord_finat.entity_dofs()[0])): + for _, dofs in sorted(coord_finat.entity_dofs()[0].items()): assert len(dofs) == 1, \ "The function space of the mesh coordinates must have" \ " exactly one degree of freedom associated with " \ @@ -681,7 +679,7 @@ FromBoundaryFiredrakeConnection`. facial_adjacency_groups = [] for igroup, fagrps in enumerate(unflipped_facial_adjacency_groups): facial_adjacency_groups.append({}) - for ineighbor_group, fagrp in six.iteritems(fagrps): + for ineighbor_group, fagrp in fagrps.items(): new_element_faces = flip_local_face_indices(fagrp.element_faces, fagrp.elements) new_neighbor_faces = flip_local_face_indices(fagrp.neighbor_faces, @@ -825,7 +823,7 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): perm2cells.setdefault(perm, []) perm2cells[perm].append(mm_cell_id) perm2cells = {perm: np.array(cells) - for perm, cells in six.iteritems(perm2cells)} + for perm, cells in perm2cells.items()} # Now make a coordinates function from firedrake import VectorFunctionSpace, Function @@ -854,7 +852,7 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): # Now put the nodes in the right local order # nodes is shaped *(ambient dim, nelements, nunit nodes)* from meshmode.mesh.processing import get_simplex_element_flip_matrix - for perm, cells in six.iteritems(perm2cells): + for perm, cells in perm2cells.items(): flip_mat = get_simplex_element_flip_matrix(group.order, fd_unit_nodes, perm) -- GitLab From 0d72b6bfd289810f14be9b14bbecadcb9ba6c943 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Thu, 13 Aug 2020 10:08:01 -0500 Subject: [PATCH 161/221] Some typos / comment improvements / minor code improvements from @inducer's code review suggestions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andreas Klöckner --- meshmode/interop/firedrake/connection.py | 49 ++++++++++++------------ 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index e259bfa7..d00893d4 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -22,7 +22,6 @@ THE SOFTWARE. __doc__ = """ .. autoclass:: FiredrakeConnection - :members: .. autoclass:: FromFiredrakeConnection .. autoclass:: FromBoundaryFiredrakeConnection .. autoclass:: ToFiredrakeConnection @@ -97,7 +96,7 @@ class FiredrakeConnection: .. attribute:: discr - A meshmode discretization + A :class:`meshmode.discretization.Discretization`. .. attribute:: group_nr @@ -113,7 +112,7 @@ class FiredrakeConnection: index associated to the *j*th degree of freedom of the *i*th element in *element_grp*. - degrees of freedom should be associated so that + Degrees of freedom should be associated so that the implicit mapping from a reference element to element *i* which maps meshmode unit dofs *0,1,...,n-1* to actual dofs *(i, 0), (i, 1), ..., (i, n-1)* @@ -129,8 +128,7 @@ class FiredrakeConnection: """ def __init__(self, discr, fdrake_fspace, mm2fd_node_mapping, group_nr=None): """ - :param discr: A :mod:`meshmode` - :class:`~meshmode.discretization.Discretization` + :param discr: A :class:`meshmode.discretization.Discretization` :param fdrake_fspace: A :mod:`firedrake` :class:`firedrake.functionspaceimpl.WithGeometry`. Must have ufl family ``'Lagrange'`` or @@ -220,6 +218,8 @@ class FiredrakeConnection: new_nodes=fd_unit_nodes, old_nodes=mm_unit_nodes) + # {{{ compute meshmode node equivalence classes for continuity check + # Now handle the possibility of multiple meshmode nodes being associated # to the same firedrake node unique_fd_nodes, counts = np.unique(mm2fd_node_mapping, @@ -244,6 +244,8 @@ class FiredrakeConnection: self._mm_node_equiv_classes = [tuple(equiv_class) for equiv_class in six.itervalues(fd_to_dupes)] + # }}} + # Store input self.discr = discr self.group_nr = group_nr @@ -369,8 +371,8 @@ class FiredrakeConnection: % (dtype, arr.entry_dtype)) if isinstance(field, DOFArray): - if shape is not None and shape != tuple(): - raise ValueError("shape != tuple() and %s is of type DOFArray" + if shape is not None and shape != (): + raise ValueError("shape != () and '%s' is of type DOFArray" " instead of np.ndarray." % field_name) check_dof_array(field, field_name) elif isinstance(field, np.ndarray): @@ -397,12 +399,12 @@ class FiredrakeConnection: field_name, field.shape) raise TypeError(prefix + "\n" + msg) else: - raise TypeError("field must be of type DOFArray or np.ndarray", + raise TypeError("field must be of type DOFArray or a numpy object array of those", "not %s." % type(field)) def from_firedrake(self, function, out=None, actx=None): """ - transport firedrake function onto :attr:`discr` + Transport firedrake function onto :attr:`discr` :arg function: A :mod:`firedrake` function to transfer onto :attr:`discr`. Its function space must have @@ -438,7 +440,7 @@ class FiredrakeConnection: :class:`~meshmode.array_context.ArrayContext` on which to create the :class:`~meshmode.dof_array.DOFArray` * If *out* is not *None*, *actx* must be *None* or *out*'s - *array_context*. + :attr:`~meshmode.dof_array.DOFArray.array_context`. :return: *out*, with the converted data from *function* stored in it """ @@ -476,7 +478,7 @@ class FiredrakeConnection: ) # If scalar, just reorder and resample out - if fspace_shape == tuple(): + if fspace_shape == (): reorder_and_resample(out, function_data) else: # firedrake drops extra dimensions @@ -496,8 +498,8 @@ class FiredrakeConnection: def from_meshmode(self, mm_field, out=None, assert_fdrake_discontinuous=True, continuity_tolerance=None): - """ - transport meshmode field from :attr:`discr` into an + r""" + Transport meshmode field from :attr:`discr` into an appropriate firedrake function space. If *out* is *None*, values at any firedrake @@ -509,7 +511,7 @@ class FiredrakeConnection: * A :class:`~meshmode.dof_array.DOFArray` representing a field of shape *tuple()* on :attr:`discr` - * A :class:`numpy.ndarray` with + * A :class:`numpy.ndarray` of dtype "object" with entries of class :class:`~meshmode.dof_array.DOFArray` representing a field of shape *mm_field.shape* on :attr:`discr` @@ -519,7 +521,7 @@ class FiredrakeConnection: :class:`~meshmode.dof_array.DOFArray` must be of shape *(nelements, nunit_dofs)* and the *element_dtype* must match that used for - :mod:`firedrake` :class:`Function`s + :mod:`firedrake` :class:`Function`\ s :arg out: If *None* then ignored, otherwise a :mod:`firedrake` function of the right function space for the transported data @@ -558,7 +560,7 @@ class FiredrakeConnection: if not isinstance(mm_field, DOFArray): fspace_shape = mm_field.shape else: - fspace_shape = tuple() + fspace_shape = () # make sure out is a firedrake function in an appropriate # function space @@ -570,7 +572,7 @@ class FiredrakeConnection: # but instead get FunctionSpace or VectorFunctionSpace when # reasonable shape = fspace_shape - if shape == tuple(): + if shape == (): shape = None elif len(shape) == 1: shape = shape[0] @@ -614,7 +616,7 @@ class FiredrakeConnection: l_inf, continuity_tolerance)) # If scalar, just reorder and resample out - if fspace_shape == tuple(): + if fspace_shape == (): resample_and_reorder(out_data, mm_field) else: # otherwise, have to grab each dofarray and the corresponding @@ -658,8 +660,7 @@ class FromFiredrakeConnection(FiredrakeConnection): If *None*, uses * A :class:`~meshmode.discretization.poly_element.\ -PolynomialRecursiveNodesGroupFactory` if :mod:`recursivenodes` is - installed +PolynomialRecursiveNodesGroupFactory` * A :class:`~meshmode.discretization.poly_element.\ PolynomialWarpAndBlendGroupFactory` """ @@ -750,12 +751,11 @@ PolynomialWarpAndBlendGroupFactory` flipped_cell_node_list) if fdrake_fspace.ufl_element().family() == 'Discontinuous Lagrange': assert len(self._mm_node_equiv_classes) == 0, \ - "Somehow a firedrake node in a 'DG' space got duplicated..." \ - "contact the developer." + "A firedrake node in a 'DG' space got duplicated" def _get_cells_to_use(self, mesh): """ - For compatability with :class:`FromFiredrakeBdyConnection` + For compatibility with :class:`FromFiredrakeBdyConnection` """ return None @@ -887,8 +887,7 @@ InterpolatoryQuadratureSimplexElementGroup`. mm2fd_node_mapping, group_nr=group_nr) assert len(self._mm_node_equiv_classes) == 0, \ - "Somehow a firedrake node in a 'DG' space got duplicated..." \ - "contact the developer." + "A firedrake node in a 'DG' space got duplicated" # }}} -- GitLab From 266c3ef7b47a7142ae17dbe16a736e04315e1afa Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 10:10:22 -0500 Subject: [PATCH 162/221] flake8 fixes --- meshmode/interop/firedrake/connection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 2cf58f72..2bc9cebe 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -243,7 +243,7 @@ class FiredrakeConnection: self._mm_node_equiv_classes = [tuple(equiv_class) for equiv_class in fd_to_dupes.values()] - # }}} + # }}} # Store input self.discr = discr @@ -398,8 +398,8 @@ class FiredrakeConnection: field_name, field.shape) raise TypeError(prefix + "\n" + msg) else: - raise TypeError("field must be of type DOFArray or a numpy object array of those", - "not %s." % type(field)) + raise TypeError("field must be of type DOFArray or a numpy object array " + "of those not %s." % type(field)) def from_firedrake(self, function, out=None, actx=None): """ -- GitLab From 1de8b4894032aeca2aa2c1892cc1abec9ebe481a Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 10:14:28 -0500 Subject: [PATCH 163/221] Improved test docs and tested sin(x[i]) instead of x[i] --- test/test_firedrake_interop.py | 38 ++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 5ed2d08d..c4c1d4d3 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -53,7 +53,7 @@ firedrake = pytest.importorskip("firedrake") from firedrake import ( UnitIntervalMesh, UnitSquareMesh, UnitCubeMesh, FunctionSpace, VectorFunctionSpace, TensorFunctionSpace, - Function, SpatialCoordinate, as_tensor) + Function, SpatialCoordinate, as_tensor, sin) CLOSE_ATOL = 1e-12 @@ -85,16 +85,19 @@ def fdrake_mesh(request): mesh_name = request.param if mesh_name == "FiredrakeUnitIntervalMesh": return UnitIntervalMesh(100) - if mesh_name == "FiredrakeUnitSquareMesh": + elif mesh_name == "FiredrakeUnitSquareMesh": return UnitSquareMesh(10, 10) - if mesh_name == "FiredrakeUnitSquareMesh-order2": + elif mesh_name == "FiredrakeUnitSquareMesh-order2": m = UnitSquareMesh(10, 10) fspace = VectorFunctionSpace(m, 'CG', 2) coords = Function(fspace).interpolate(SpatialCoordinate(m)) from firedrake.mesh import Mesh return Mesh(coords) - if mesh_name == "FiredrakeUnitCubeMesh": + elif mesh_name == "FiredrakeUnitCubeMesh": return UnitCubeMesh(5, 5, 5) + elif mesh_name not in ("annulus.msh", "blob2d-order1-h4e-2.msh", + "blob2d-order1-h6e-2.msh", "blob2d-order1-h8e-2.msh"): + raise ValueError("Unexpected value for request.param") # Firedrake can't read in higher order meshes from gmsh, # so we can only use the order1 blobs @@ -204,14 +207,18 @@ def test_to_fd_consistency(ctx_factory, mm_mesh, fspace_degree): # {{{ Now check the FromBoundaryFiredrakeConnection consistency -def test_from_bdy_consistency(ctx_factory, - fdrake_mesh, - fdrake_family, - fspace_degree): +def test_from_boundary_consistency(ctx_factory, + fdrake_mesh, + fdrake_family, + fspace_degree): """ Make basic checks that FiredrakeFromBoundaryConnection is not doing - something obviously wrong, i.e. that it has proper tagging, that it has - the right number of cells, etc. + something obviously wrong, + i.e. that the firedrake boundary tags partition the converted meshmode mesh, + that the firedrake boundary tags correspond to the same physical + regions in the converted meshmode mesh as in the original firedrake mesh, + and that each boundary tag is associated to the same number of facets + in the converted meshmode mesh as in the original firedrake mesh. """ fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fspace_degree) @@ -297,8 +304,9 @@ def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values, """ Make sure the given boundary ids cover the converted mesh. Make sure that the given coordinate have the given value for the - corresponding boundary tag (see :mod:`firedrake`'s documentation - to see how the boundary tags for its utility meshes are defined) + corresponding boundary tag (see :mod:`firedrake.utility_meshes`'s + documentation to see how the boundary tags for its utility meshes are + defined) """ cells_to_use = None if only_convert_bdy: @@ -448,13 +456,13 @@ def test_from_fd_transfer(ctx_factory, fspace_degree, group_nodes = np.array([actx.to_numpy(dof_arr[0]) for dof_arr in nodes]) # Now, for each coordinate d, test transferring the function - # x -> dth component of x + # x -> sin(dth component of x) for d in range(dim): - fdrake_f = Function(fdrake_fspace).interpolate(spatial_coord[d]) + fdrake_f = Function(fdrake_fspace).interpolate(sin(spatial_coord[d])) # transport fdrake function and put in numpy fd2mm_f = fdrake_connection.from_firedrake(fdrake_f, actx=actx) fd2mm_f = actx.to_numpy(fd2mm_f[0]) - meshmode_f = group_nodes[d, :, :] + meshmode_f = np.sin(group_nodes[d, :, :]) # record fd -> mm error err = np.max(np.abs(fd2mm_f - meshmode_f)) -- GitLab From 299884d94abe2ecf91992acc594afcfdffc8a277 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 10:58:27 -0500 Subject: [PATCH 164/221] Change error messages to not use ReST syntax --- meshmode/interop/firedrake/connection.py | 167 +++++++++---------- meshmode/interop/firedrake/mesh.py | 32 ++-- meshmode/interop/firedrake/reference_cell.py | 16 +- 3 files changed, 106 insertions(+), 109 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 2bc9cebe..e120cadc 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -151,53 +151,53 @@ class FiredrakeConnection: """ # {{{ Validate input if not isinstance(discr, Discretization): - raise TypeError(":param:`discr` must be of type " - ":class:`~meshmode.discretization.Discretization`, " - "not :class:`%s`." % type(discr)) + raise TypeError("'discr' must be of type " + "meshmode.discretization.Discretization, " + "not '%s'`." % type(discr)) from firedrake.functionspaceimpl import WithGeometry if not isinstance(fdrake_fspace, WithGeometry): - raise TypeError(":param:`fdrake_fspace` must be of type " - ":class:`firedrake.functionspaceimpl.WithGeometry`, " - "not :class:`%s`." % type(fdrake_fspace)) + raise TypeError("'fdrake_fspace' must be of type " + "firedrake.functionspaceimpl.WithGeometry, " + "not '%s'." % type(fdrake_fspace)) if not isinstance(mm2fd_node_mapping, np.ndarray): - raise TypeError(":param:`mm2fd_node_mapping` must be of type " - ":class:`numpy.ndarray`, " - "not :class:`%s`." % type(mm2fd_node_mapping)) + raise TypeError("'mm2fd_node_mapping' must be of type " + "numpy.ndarray, " + "not '%s'." % type(mm2fd_node_mapping)) if not isinstance(group_nr, int) and group_nr is not None: - raise TypeError(":param:`group_nr` must be of type *int* or be " - "*None*, not of type %s." % type(group_nr)) + raise TypeError("'group_nr' must be of type int or be " + "*None*, not of type '%s'." % type(group_nr)) # Convert group_nr to an integer if *None* if group_nr is None: if len(discr.groups) != 1: - raise ValueError(":param:`group_nr` is *None* but :param:`discr` " - "has %s != 1 groups." % len(discr.groups)) + raise ValueError("'group_nr' is *None* but 'discr' " + "has '%s' != 1 groups." % len(discr.groups)) group_nr = 0 # store element_grp as variable for convenience element_grp = discr.groups[group_nr] if group_nr < 0 or group_nr >= len(discr.groups): - raise ValueError(":param:`group_nr` has value %s, which an invalid " - "index into list *discr.groups* of length %s." + raise ValueError("'group_nr' has value '%s', which an invalid " + "index into list 'discr.groups' of length '%s'." % (group_nr, len(discr.groups))) if not isinstance(element_grp, InterpolatoryElementGroupBase): - raise TypeError("*discr.groups[group_nr]* must be of type " - ":class:`InterpolatoryElementGroupBase`" - ", not :class:`%s`." % type(element_grp)) + raise TypeError("'discr.groups[group_nr]' must be of type " + "InterpolatoryElementGroupBase" + ", not '%s'." % type(element_grp)) allowed_families = ('Discontinuous Lagrange', 'Lagrange') if fdrake_fspace.ufl_element().family() not in allowed_families: - raise TypeError(":param:`fdrake_fspace` must have ufl family " - "be one of %s, not %s." + raise TypeError("'fdrake_fspace' must have ufl family " + "be one of %s, not '%s'." % (allowed_families, fdrake_fspace.ufl_element().family())) if mm2fd_node_mapping.shape != (element_grp.nelements, element_grp.nunit_dofs): - raise ValueError(":param:`mm2fd_node_mapping` must be of shape ", - "(%s,), not %s" + raise ValueError("'mm2fd_node_mapping' must be of shape ", + "(%s,), not '%s'" % ((discr.groups[group_nr].ndofs,), mm2fd_node_mapping.shape)) if mm2fd_node_mapping.dtype != fdrake_fspace.cell_node_list.dtype: - raise ValueError(":param:`mm2fd_node_mapping` must have dtype " - "%s, not %s" % (fdrake_fspace.cell_node_list.dtype, + raise ValueError("'mm2fd_node_mapping' must have dtype " + "%s, not '%s'" % (fdrake_fspace.cell_node_list.dtype, mm2fd_node_mapping.dtype)) # }}} @@ -300,8 +300,8 @@ class FiredrakeConnection: degree=self._ufl_element.degree(), shape=shape) else: - raise TypeError(":param:`shape` must be *None*, an integer, " - " or a tuple of integers, not of type %s." + raise TypeError("'shape' must be *None*, an integer, " + " or a tuple of integers, not of type '%s'." % type(shape)) return self._fspace_cache[shape] @@ -316,27 +316,27 @@ class FiredrakeConnection: # Validate that *function* is convertible from firedrake.function import Function if not isinstance(function, Function): - raise TypeError(function_name + " must be a :mod:`firedrake` " - "Function") + raise TypeError(f"'{function_name} must be a firedrake Function" + f" but is of unexpected type '{type(function)}'") ufl_elt = function.function_space().ufl_element() if ufl_elt.family() != self._ufl_element.family(): - raise ValueError(function_name + "'s function_space's ufl element" - " must have family %s, not %s" - % (self._ufl_element.family(), ufl_elt.family())) + raise ValueError(f"'{function_name}.function_space().ufl_element()" + f".family()' must be {self._ufl_element.family()}" + f", not '{type(ufl_elt.family())}'") if ufl_elt.degree() != self._ufl_element.degree(): - raise ValueError(function_name + "'s function_space's ufl element" - " must have degree %s, not %s" - % (self._ufl_element.degree(), ufl_elt.degree())) + raise ValueError(f"'{function_name}.function_space().ufl_element()" + f".degree()' must be {self._ufl_element.degree()}" + f", not '{ufl_elt.degree()}'") if function.function_space().mesh() is not self._mesh_geometry: - raise ValueError(function_name + "'s mesh must be the same as " - "`self.from_fspace().mesh()``") + raise ValueError(f"'{function_name}.function_space().mesh()' must" + " be the same mesh used by this connection") if dtype is not None and function.dat.data.dtype != dtype: - raise ValueError(function_name + ".dat.dtype must be %s, not %s." - % (dtype, function.dat.data.dtype)) + raise ValueError(f"'{function_name}.dat.dtype' must be " + f"{dtype}, not '{function.dat.data.dtype}'") if shape is not None and function.function_space().shape != shape: - raise ValueError(function_name + ".function_space().shape must be " - "%s, not %s" % (shape, - function.function_space().shape)) + raise ValueError("'{function_name}.function_space().shape' must be" + " {shape}, not '{function.function_space().shape}" + "'") def _validate_field(self, field, field_name, shape=None, dtype=None): """ @@ -351,23 +351,23 @@ class FiredrakeConnection: def check_dof_array(arr, arr_name): if not isinstance(arr, DOFArray): - raise TypeError(arr_name + " must be of type " - ":class:`~meshmode.dof_array.DOFArray`, " - "not :class:`%s`." % type(arr)) + raise TypeError(f"'{arr_name}' must be of type " + f"meshmode.dof_array.DOFArray, not " + f"{type(arr)}") if arr.array_context is None: - raise ValueError(arr_name + " must have a non-*None* " + raise ValueError(f"'{arr_name}' must have a non-*None* " "array_context") if arr.shape != tuple([len(self.discr.groups)]): - raise ValueError(arr_name + " shape must be %s, not %s." - % (tuple([len(self.discr.groups)]), arr.shape)) + raise ValueError(f"'{arr_name}' shape must be " + f"{tuple([len(self.discr.groups)])}, not " + f"'{arr.shape}'") if arr[self.group_nr].shape != group_shape: - raise ValueError(arr_name + "[%s].shape must be %s, not %s" - % (self.group_nr, - group_shape, - arr[self.group_nr].shape)) + raise ValueError(f"'{arr_name}[{self.group_nr}].shape' must be" + f" {group_shape}, not " + f"'{arr[self.group_nr].shape}'") if dtype is not None and arr.entry_dtype != dtype: - raise ValueError(arr_name + ".entry_dtype must be %s, not %s." - % (dtype, arr.entry_dtype)) + raise ValueError(f"'{arr_name}.entry_dtype' must be {dtype}," + f" not '{arr.entry_dtype}'") if isinstance(field, DOFArray): if shape is not None and shape != (): @@ -376,26 +376,25 @@ class FiredrakeConnection: check_dof_array(field, field_name) elif isinstance(field, np.ndarray): if shape is not None and field.shape != shape: - raise ValueError(field_name + ".shape must be %s, not %s" - % (shape, field.shape)) + raise ValueError(f"'{field_name}.shape' must be {shape}, not " + f"'{field.shape}'") for i, arr in enumerate(field.flatten()): arr_name = "%s[%s]" % (field_name, np.unravel_index(i, field.shape)) try: check_dof_array(arr, arr_name) - except TypeError as e: - msg = e.args[0] - prefix = "%s is a numpy array of shape %s, which is " \ - "interpreted as a mapping into a space of shape " \ - "%s. For each multi-index *mi*, the " \ - "*mi*th coordinate values of %s should be " \ - "represented as a DOFArray stored in %s[mi]. If you " \ - "are not trying to represent a mapping into a space " \ - "of shape %s, look at the documentation for " \ - "FiredrakeConnection.from_meshmode or " \ + except TypeError as type_err: + msg = type_err.args[0] + prefix = f"{field_name} is a numpy array of shape " \ + f"{field.shape}, which is interpreted as a mapping" \ + f" into a space of sahpe {field.shape}. For each " \ + " multi-index *mi*, the *mi*th coordinate values " \ + f" of {field_name} should be represented as a " \ + f"DOFArray stored in {field_name}[mi]. If you are " \ + " not trying to represent a mapping into a space of " \ + f" shape {field.shape}, look at the documentation " \ + " for FiredrakeConnection.from_meshmode or " \ "FiredrakeConnection.from_firedrake to see how " \ - "fields in a discretization are represented." \ - % (field_name, field.shape, field.shape, field_name, - field_name, field.shape) + "fields in a discretization are represented." raise TypeError(prefix + "\n" + msg) else: raise TypeError("field must be of type DOFArray or a numpy object array " @@ -548,7 +547,7 @@ class FiredrakeConnection: if self._ufl_element.family() == 'Lagrange' \ and assert_fdrake_discontinuous: raise ValueError("Trying to convert to continuous function space " - " with :arg:`assert_fdrake_discontinuous` set " + " with 'assert_fdrake_discontinuous' set " " to *True*") # All firedrake functions are the same dtype dtype = self.firedrake_fspace().mesh().coordinates.dat.data.dtype @@ -609,7 +608,7 @@ class FiredrakeConnection: raise ValueError("Meshmode nodes %s (written as " " *(element index, local node index)*)" " represent the same firedrake node %s" - ", but :arg:`mm_field`'s resampled " + ", but 'mm_field's resampled " " values are %s > %s apart)" % (mm_equiv_class, fd_inode, l_inf, continuity_tolerance)) @@ -667,32 +666,30 @@ PolynomialWarpAndBlendGroupFactory` # element. from firedrake.functionspaceimpl import WithGeometry if not isinstance(fdrake_fspace, WithGeometry): - raise TypeError(":arg:`fdrake_fspace` must be of firedrake type " - ":class:`WithGeometry`, not `%s`." + raise TypeError("'fdrake_fspace' must be of firedrake type " + "WithGeometry, not '%s'." % type(fdrake_fspace)) ufl_elt = fdrake_fspace.ufl_element() if ufl_elt.family() not in ('Lagrange', 'Discontinuous Lagrange'): - raise ValueError("the ``ufl_element().family()`` of " - ":arg:`fdrake_fspace` must " - "be ``'Lagrange'`` or " - "``'Discontinuous Lagrange'``, not %s." + raise ValueError("the 'fdrake_fspace.ufl_element().family()' of " + "must be be 'Lagrange' or " + "'Discontinuous Lagrange', not '%s'." % ufl_elt.family()) # Make sure grp_factory is the right type if provided, and # uses an interpolatory class. if grp_factory is not None: if not isinstance(grp_factory, ElementGroupFactory): - raise TypeError(":arg:`grp_factory` must inherit from " - ":class:`meshmode.discretization." - "ElementGroupFactory`, but is instead of type " - "%s." % type(grp_factory)) + raise TypeError("'grp_factory' must inherit from " + "meshmode.discretization.ElementGroupFactory," + "but is instead of type " + "'%s'." % type(grp_factory)) if not issubclass(grp_factory.group_class, InterpolatoryElementGroupBase): - raise TypeError(":arg:`grp_factory` must use a *group_class" - "* which inherits from" - ":class:`meshmode.discretization." - "InterpolatoryElementGroupBase, but instead uses" - " *group_class* of type %s." + raise TypeError("'grp_factory.group_class' must inherit from" + "meshmode.discretization." + "InterpolatoryElementGroupBase, but" + " is instead of type '%s'" % type(grp_factory.group_class)) # If not provided, make one else: diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 6615d704..8e5bb1a0 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -528,12 +528,12 @@ FromBoundaryFiredrakeConnection`. # Type validation from firedrake.mesh import MeshGeometry if not isinstance(fdrake_mesh, MeshGeometry): - raise TypeError(":arg:`fdrake_mesh_topology` must be a " - ":mod:`firedrake` :class:`MeshGeometry`, " - "not %s." % type(fdrake_mesh)) + raise TypeError("'fdrake_mesh_topology' must be an instance of " + "firedrake.mesh.MeshGeometry, " + "not '%s'." % type(fdrake_mesh)) if cells_to_use is not None: if not isinstance(cells_to_use, np.ndarray): - raise TypeError(":arg:`cells_to_use` must be a np.ndarray or " + raise TypeError("'cells_to_use' must be a np.ndarray or " "*None*") assert len(cells_to_use.shape) == 1 assert np.size(np.unique(cells_to_use)) == np.size(cells_to_use), \ @@ -745,27 +745,27 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): :mod:`meshmode` allows many. """ if not isinstance(mesh, Mesh): - raise TypeError(":arg:`mesh` must of type :class:`meshmode.mesh.Mesh`," - " not %s." % type(mesh)) + raise TypeError("'mesh' must of type meshmode.mesh.Mesh," + " not '%s'." % type(mesh)) if group_nr is None: if len(mesh.groups) != 1: - raise ValueError(":arg:`group_nr` is *None* but :arg:`mesh` has " + raise ValueError("'group_nr' is *None* but 'mesh' has " "more than one group.") group_nr = 0 if not isinstance(group_nr, int): - raise TypeError("Expecting :arg:`group_nr` to be of type *int*, not " - f"{type(group_nr)}") + raise TypeError("Expecting 'group_nr' to be of type int, not " + f"'{type(group_nr)}'") if group_nr < 0 or group_nr >= len(mesh.groups): - raise ValueError(":arg:`group_nr` is an invalid group index:" - f" {group_nr} fails to satisfy " + raise ValueError("'group_nr' is an invalid group index:" + f" '{group_nr}' fails to satisfy " f"0 <= {group_nr} < {len(mesh.groups)}") if not isinstance(mesh.groups[group_nr], SimplexElementGroup): - raise TypeError("Expecting *mesh.groups[group_nr] to be of type " - ":class:`meshmode.mesh.SimplexElementGroup`, not " - f"{type(mesh.groups[group_nr])}") + raise TypeError("Expecting 'mesh.groups[group_nr]' to be of type " + "meshmode.mesh.SimplexElementGroup, not " + f"'{type(mesh.groups[group_nr])}'") if mesh.vertices is None: - raise ValueError(":arg:`mesh` has no vertices " - "(*mesh.vertices* is *None*)") + raise ValueError("'mesh' has no vertices " + "('mesh.vertices' is *None*)") # Get the vertices and vertex indices of the requested group group = mesh.groups[group_nr] diff --git a/meshmode/interop/firedrake/reference_cell.py b/meshmode/interop/firedrake/reference_cell.py index a6fdd2b7..d03babcd 100644 --- a/meshmode/interop/firedrake/reference_cell.py +++ b/meshmode/interop/firedrake/reference_cell.py @@ -55,13 +55,13 @@ def get_affine_reference_simplex_mapping(ambient_dim, firedrake_to_meshmode=True """ # validate input if not isinstance(ambient_dim, int): - raise TypeError(":arg:`ambient_dim` must be of type *int*, not " - f"{type(ambient_dim)}") + raise TypeError("'ambient_dim' must be an int, not " + f"'{type(ambient_dim)}'") if ambient_dim < 0: - raise ValueError(":arg:`ambient_dim` must be non-negative") + raise ValueError("'ambient_dim' must be non-negative") if not isinstance(firedrake_to_meshmode, bool): - raise TypeError(":arg:`firedrake_to_meshmode` must be of type *bool*, not " - f"{type(firedrake_to_meshmode)}") + raise TypeError("'firedrake_to_meshmode' must be a bool, not " + f"'{type(firedrake_to_meshmode)}'") from FIAT.reference_element import ufc_simplex from modepy.tools import unit_vertices @@ -130,9 +130,9 @@ def get_finat_element_unit_nodes(finat_element): from FIAT.reference_element import Simplex if not isinstance(finat_element.cell, Simplex): raise TypeError("Reference element of the finat element MUST be a" - " simplex, i.e. :arg:`finat_element`'s *cell* attribute must" - " be of type :class:`FIAT.reference_element.Simplex`, not " - f"{type(finat_element.cell)}") + " simplex, i.e. 'finat_element's *cell* attribute must" + " be of type FIAT.reference_element.Simplex, not " + f"'{type(finat_element.cell)}'") # point evaluators is a list of functions *p_0,...,p_{n-1}*. # *p_i(f)* evaluates function *f* at node *i* (stored as a tuple), # so to recover node *i* we need to evaluate *p_i* at the identity -- GitLab From 8ab8f09458a5108865522ac68200b847ce907810 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 11:04:37 -0500 Subject: [PATCH 165/221] Improve intersphinx links for firedrake objects --- meshmode/interop/firedrake/connection.py | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index e120cadc..d7d2a086 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -128,7 +128,7 @@ class FiredrakeConnection: def __init__(self, discr, fdrake_fspace, mm2fd_node_mapping, group_nr=None): """ :param discr: A :class:`meshmode.discretization.Discretization` - :param fdrake_fspace: A :mod:`firedrake` + :param fdrake_fspace: A :class:`firedrake.functionspaceimpl.WithGeometry`. Must have ufl family ``'Lagrange'`` or ``'Discontinuous Lagrange'``. @@ -268,10 +268,9 @@ class FiredrakeConnection: is returned, or a tuple of integers defining the shape of values in a tensor function space, in which case a tensor function space is returned - :return: A :mod:`firedrake` - :class:`~firedrake.functionspaceimpl.WithGeometry` which corresponds - to *self.discr.groups[self.group_nr]* of the appropriate vector - dimension + :return: A :class:`firedrake.functionspaceimpl.WithGeometry` which + corresponds to *self.discr.groups[self.group_nr]* of the appropriate + vector dimension :raises TypeError: If *shape* is of the wrong type """ @@ -404,7 +403,7 @@ class FiredrakeConnection: """ Transport firedrake function onto :attr:`discr` - :arg function: A :mod:`firedrake` function to transfer onto + :arg function: A :class:`firedrake.function.Function` to transfer onto :attr:`discr`. Its function space must have the same family, degree, and mesh as ``self.from_fspace()``. :arg out: Either @@ -519,10 +518,11 @@ class FiredrakeConnection: :class:`~meshmode.dof_array.DOFArray` must be of shape *(nelements, nunit_dofs)* and the *element_dtype* must match that used for - :mod:`firedrake` :class:`Function`\ s + :class:`firedrake.function.Function`\ s - :arg out: If *None* then ignored, otherwise a :mod:`firedrake` - function of the right function space for the transported data + :arg out: If *None* then ignored, otherwise a + :class:`firedrake.function.Function` + of the right function space for the transported data to be stored in. The shape of its function space must match the shape of *mm_field* @@ -541,7 +541,7 @@ class FiredrakeConnection: apart. If *None*, no checks are performed. Does nothing if the firedrake function space is discontinuous - :return: a :mod:`firedrake` :class:`Function` holding the transported + :return: a :class:`firedrake.function.Function` holding the transported data (*out*, if *out* was not *None*) """ if self._ufl_element.family() == 'Lagrange' \ @@ -764,8 +764,8 @@ class FromBoundaryFiredrakeConnection(FromFiredrakeConnection): least one vertex on the given boundary and allows transfer of functions to and from :mod:`firedrake`. - Use the same bdy_id as one would for a :mod:`firedrake` - :class:`~firedrake.bcs.DirichletBC` instance. + Use the same bdy_id as one would for a + :class:`firedrake.bcs.DirichletBC` instance. ``"on_boundary"`` corresponds to the entire boundary. .. attribute:: bdy_id @@ -777,8 +777,8 @@ class FromBoundaryFiredrakeConnection(FromFiredrakeConnection): def __init__(self, actx, fdrake_fspace, bdy_id, grp_factory=None): """ :arg bdy_id: A boundary marker of *fdrake_fspace.mesh()* as accepted by - the *boundary_nodes* method of a :mod:`firedrake` - :class:`~firedrake.functionspaceimpl.WithGeometry`. + the *boundary_nodes* method of a + :class:`firedrake.functionspaceimpl.WithGeometry`. Other arguments are as in :class:`~meshmode.interop.firedrake.connection.FromFiredrakeConnection`. @@ -810,7 +810,7 @@ class FromBoundaryFiredrakeConnection(FromFiredrakeConnection): class ToFiredrakeConnection(FiredrakeConnection): """ - Create a connection from a firedrake discretization + Create a connection from a meshmode discretization into firedrake. Create a corresponding "DG" function space and allow for conversion back and forth by resampling at the nodes. -- GitLab From 8da2bfc8af220ca829941812531f7bb44456709e Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 12:00:44 -0500 Subject: [PATCH 166/221] Use @memoize_method --- meshmode/interop/firedrake/connection.py | 57 ++++++++++-------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index d7d2a086..1b5cd50a 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -46,6 +46,8 @@ from meshmode.discretization.poly_element import ( from meshmode.discretization import ( Discretization, InterpolatoryElementGroupBase) +from pytools import memoize_method + def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): """ @@ -251,11 +253,8 @@ class FiredrakeConnection: self.mm2fd_node_mapping = mm2fd_node_mapping self._mesh_geometry = fdrake_fspace.mesh() self._ufl_element = fdrake_fspace.ufl_element() - # Cache firedrake function spaces of each vector dimension to - # avoid overhead. Firedrake takes care of avoiding memory - # duplication. - self._fspace_cache = {} + @memoize_method def firedrake_fspace(self, shape=None): """ Return a firedrake function space that @@ -274,35 +273,27 @@ class FiredrakeConnection: :raises TypeError: If *shape* is of the wrong type """ - # Cache the function spaces created to avoid high overhead. - # Note that firedrake is smart about re-using shared information, - # so this is not duplicating mesh/reference element information - if shape not in self._fspace_cache: - if shape is None: - from firedrake import FunctionSpace - self._fspace_cache[shape] = \ - FunctionSpace(self._mesh_geometry, - self._ufl_element.family(), - degree=self._ufl_element.degree()) - elif isinstance(shape, int): - from firedrake import VectorFunctionSpace - self._fspace_cache[shape] = \ - VectorFunctionSpace(self._mesh_geometry, - self._ufl_element.family(), - degree=self._ufl_element.degree(), - dim=shape) - elif isinstance(shape, tuple): - from firedrake import TensorFunctionSpace - self._fspace_cache[shape] = \ - TensorFunctionSpace(self._mesh_geometry, - self._ufl_element.family(), - degree=self._ufl_element.degree(), - shape=shape) - else: - raise TypeError("'shape' must be *None*, an integer, " - " or a tuple of integers, not of type '%s'." - % type(shape)) - return self._fspace_cache[shape] + if shape is None: + from firedrake import FunctionSpace + return FunctionSpace(self._mesh_geometry, + self._ufl_element.family(), + degree=self._ufl_element.degree()) + elif isinstance(shape, int): + from firedrake import VectorFunctionSpace + return VectorFunctionSpace(self._mesh_geometry, + self._ufl_element.family(), + degree=self._ufl_element.degree(), + dim=shape) + elif isinstance(shape, tuple): + from firedrake import TensorFunctionSpace + return TensorFunctionSpace(self._mesh_geometry, + self._ufl_element.family(), + degree=self._ufl_element.degree(), + shape=shape) + else: + raise TypeError("'shape' must be *None*, an integer, " + " or a tuple of integers, not of type '%s'." + % type(shape)) def _validate_function(self, function, function_name, shape=None, dtype=None): -- GitLab From aadd621fadd45df9e44f70042878607799dd7726 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 12:06:53 -0500 Subject: [PATCH 167/221] Check user input with if ... raise not assert --- meshmode/interop/firedrake/connection.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 1b5cd50a..4c908793 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -441,15 +441,16 @@ class FiredrakeConnection: if out is not None: self._validate_field(out, "out", fspace_shape, function_data.dtype) # If out is supplied, check type, shape, and dtype - assert actx in (None, out.array_context), \ - "If :param:`out` is not *None*, :param:`actx` must be" \ - " *None* or *out.array_context*" + if actx not in (None, out.array_context): + raise ValueError("If 'out' is not *None*, 'actx' must be" + " *None* or 'out.array_context'") else: - # If `out` is not supplied, create it + # If 'out' is not supplied, create it from meshmode.array_context import ArrayContext - assert isinstance(actx, ArrayContext), "If :arg:`out` is *None*, " \ - ":arg:`actx` must be of type ArrayContext, not %s." % type(actx) - if fspace_shape == tuple(): + if not isinstance(actx, ArrayContext): + raise TypeError("If 'out' is *None*, 'actx' must be of type " + "ArrayContext, not '%s'." % type(actx)) + if fspace_shape == (): out = self.discr.zeros(actx, dtype=function_data.dtype) else: out = np.ndarray(fspace_shape, dtype=np.object) @@ -822,8 +823,9 @@ InterpolatoryQuadratureSimplexElementGroup`. firedrake mesh """ if group_nr is None: - assert len(discr.groups) == 1, ":arg:`group_nr` is *None*, but " \ - ":arg:`discr` has %s != 1 groups." % len(discr.groups) + if len(discr.groups) != 1: + raise ValueError("'group_nr' is *None*, but 'discr' has '%s' " + "!= 1 groups." % len(discr.groups)) group_nr = 0 el_group = discr.groups[group_nr] -- GitLab From f9dd851a57bd8ab293c1430d1a0015b4976d1ac0 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 12:17:16 -0500 Subject: [PATCH 168/221] Refactor mesh construction in test into function --- test/test_firedrake_interop.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index c4c1d4d3..074a608e 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -396,9 +396,7 @@ def test_from_fd_transfer(ctx_factory, fspace_degree, queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) - # Get each refinements of the meshmeshes, do conversions, - # and record errors - for mesh_par in fdrake_mesh_pars: + def get_fdrake_mesh_and_h_from_par(mesh_par): if fdrake_mesh_name == "UnitInterval": assert dim == 1 n = mesh_par @@ -436,6 +434,11 @@ def test_from_fd_transfer(ctx_factory, fspace_degree, else: raise ValueError("fdrake_mesh_name not recognized") + return (fdrake_mesh, h) + + # Record error for each refinement of each mesh + for mesh_par in fdrake_mesh_pars: + fdrake_mesh, h = get_fdrake_mesh_and_h_from_par(mesh_par) # make function space and build connection fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fspace_degree) if only_convert_bdy: -- GitLab From 2c1239397394156244eaf40a88386187e0d176ba Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 12:29:25 -0500 Subject: [PATCH 169/221] Improve comment for perm2cells --- meshmode/interop/firedrake/mesh.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 8e5bb1a0..a5a6e204 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -799,9 +799,19 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): # Firedrake goes crazy reordering local vertex numbers, # we've got to work to figure out what changes they made. # - # *perm2cells* will map each permutation of local vertex numbers to - # a list of the meshmode cells to which that permutation - # has been applied + # *perm2cells* will map permutations of local vertex numbers to + # the list of all the meshmode cells + # which firedrake reordered according to that permutation + # + # Permutations on *n* vertices are stored as a tuple + # containing all of the integers *0*, *1*, *2*, ..., *n-1* + # exactly once. A permutation *p* + # represents relabeling the *i*th local vertex + # of a meshmode element as the *p[i]*th local vertex + # in the corresponding firedrake cell. + # + # *perm2cells[p]* is a list of all the meshmode element indices + # for which *p* represents the reordering applied by firedrake perm2cells = {} for mm_cell_id, dmp_ids in enumerate(top.cell_closure[cell_index_mm2fd]): # look at order of vertices in firedrake cell @@ -822,6 +832,8 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): perm = tuple(np.argsort(mm_order)[np.argsort(np.argsort(fdrake_order))]) perm2cells.setdefault(perm, []) perm2cells[perm].append(mm_cell_id) + + # Make perm2cells map to numpy arrays instead of lists perm2cells = {perm: np.array(cells) for perm, cells in perm2cells.items()} -- GitLab From 2c0a1ba9055dc7f56d69c901c223dccf4d835e9c Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 12:33:52 -0500 Subject: [PATCH 170/221] Make sure the np array is an object array --- meshmode/interop/firedrake/connection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 4c908793..1e75fda4 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -364,7 +364,7 @@ class FiredrakeConnection: raise ValueError("shape != () and '%s' is of type DOFArray" " instead of np.ndarray." % field_name) check_dof_array(field, field_name) - elif isinstance(field, np.ndarray): + elif isinstance(field, np.ndarray) and field.dtype == np.object: if shape is not None and field.shape != shape: raise ValueError(f"'{field_name}.shape' must be {shape}, not " f"'{field.shape}'") @@ -387,8 +387,8 @@ class FiredrakeConnection: "fields in a discretization are represented." raise TypeError(prefix + "\n" + msg) else: - raise TypeError("field must be of type DOFArray or a numpy object array " - "of those not %s." % type(field)) + raise TypeError("'field' must be of type DOFArray or a numpy object " + "array of those, not '%s'." % type(field)) def from_firedrake(self, function, out=None, actx=None): """ -- GitLab From 65d98f91d62574e6f1ffd2bdfb1f3343d00178f3 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 12:35:23 -0500 Subject: [PATCH 171/221] enumerate(...flatten()) -> np.ndenumerate --- meshmode/interop/firedrake/connection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 1e75fda4..50a96c55 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -368,8 +368,8 @@ class FiredrakeConnection: if shape is not None and field.shape != shape: raise ValueError(f"'{field_name}.shape' must be {shape}, not " f"'{field.shape}'") - for i, arr in enumerate(field.flatten()): - arr_name = "%s[%s]" % (field_name, np.unravel_index(i, field.shape)) + for multi_index, arr in np.ndenumerate(field): + arr_name = "%s[%s]" % (field_name, multi_index) try: check_dof_array(arr, arr_name) except TypeError as type_err: -- GitLab From 0a960e53178654a631bd9a070dd3f2011ed281e1 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 12:36:16 -0500 Subject: [PATCH 172/221] Improve error message with single quotes --- meshmode/interop/firedrake/connection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 50a96c55..da71d04b 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -374,12 +374,12 @@ class FiredrakeConnection: check_dof_array(arr, arr_name) except TypeError as type_err: msg = type_err.args[0] - prefix = f"{field_name} is a numpy array of shape " \ + prefix = f"'{field_name}' is a numpy array of shape " \ f"{field.shape}, which is interpreted as a mapping" \ f" into a space of sahpe {field.shape}. For each " \ " multi-index *mi*, the *mi*th coordinate values " \ - f" of {field_name} should be represented as a " \ - f"DOFArray stored in {field_name}[mi]. If you are " \ + f" of '{field_name}' should be represented as a " \ + f"DOFArray stored in '{field_name}[mi]'. If you are " \ " not trying to represent a mapping into a space of " \ f" shape {field.shape}, look at the documentation " \ " for FiredrakeConnection.from_meshmode or " \ -- GitLab From 2d5be6c1bf871d259ac2b2fdabc29a60dd78de98 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 12:41:36 -0500 Subject: [PATCH 173/221] Avoid cost of zero-filling, zeros -> empty --- meshmode/interop/firedrake/connection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index da71d04b..30a35fe6 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -451,12 +451,12 @@ class FiredrakeConnection: raise TypeError("If 'out' is *None*, 'actx' must be of type " "ArrayContext, not '%s'." % type(actx)) if fspace_shape == (): - out = self.discr.zeros(actx, dtype=function_data.dtype) + out = self.discr.empty(actx, dtype=function_data.dtype) else: out = np.ndarray(fspace_shape, dtype=np.object) for multi_index in np.ndindex(fspace_shape): out[multi_index] = \ - self.discr.zeros(actx, dtype=function_data.dtype) + self.discr.empty(actx, dtype=function_data.dtype) def reorder_and_resample(dof_array, fd_data): # put the firedrake data in meshmode order and then resample, -- GitLab From 2205ac97ef218a9d74c5a9b7d3b400a926385247 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 13:16:11 -0500 Subject: [PATCH 174/221] Added ProcessLogger to log importing/exporting meshes to/from firedrake --- meshmode/interop/firedrake/mesh.py | 71 +++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index a5a6e204..b87e91de 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -20,12 +20,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -__doc__ = """ -.. autofunction:: import_firedrake_mesh -.. autofunction:: export_mesh_to_firedrake -""" - from warnings import warn # noqa +import logging import numpy as np from modepy import resampling_matrix, simplex_best_available_basis @@ -35,6 +31,16 @@ from meshmode.mesh import (BTAG_ALL, BTAG_REALLY_ALL, BTAG_NO_BOUNDARY, from meshmode.interop.firedrake.reference_cell import ( get_affine_reference_simplex_mapping, get_finat_element_unit_nodes) +from pytools import ProcessLogger + +__doc__ = """ +.. autofunction:: import_firedrake_mesh +.. autofunction:: export_mesh_to_firedrake +""" + + +logger = logging.getLogger(__name__) + # {{{ functions to extract information from Mesh Topology @@ -523,7 +529,7 @@ FromBoundaryFiredrakeConnection`. ``firedrake_orient < 0`` is *True* for any negatively oriented firedrake cell (which was flipped by meshmode) and False for any positively oriented firedrake cell - (whcih was not flipped by meshmode). + (which was not flipped by meshmode). """ # Type validation from firedrake.mesh import MeshGeometry @@ -561,8 +567,13 @@ FromBoundaryFiredrakeConnection`. bdy_tags = \ _get_firedrake_boundary_tags(fdrake_mesh, include_no_boundary=include_no_boundary) + + nodal_info_logger = ProcessLogger(logger, "Retrieving vertex indices and " + "computing NodalAdjacency from " + "firedrake mesh") vertex_indices, nodal_adjacency = \ _get_firedrake_nodal_info(fdrake_mesh, cells_to_use=cells_to_use) + # If only using some cells, vertices may need new indices as many # will be removed if cells_to_use is not None: @@ -573,6 +584,12 @@ FromBoundaryFiredrakeConnection`. vertex_indices = \ np.vectorize(vert_ndx_old2new.__getitem__)(vertex_indices) + nodal_info_logger.done() + + unflipped_group_logger = ProcessLogger(logger, "Building (possibly) " + "unflipped SimplexElementGroup " + "from firedrake unit nodes/nodes") + # Grab the mesh reference element and cell dimension coord_finat_elt = fdrake_mesh.coordinates.function_space().finat_element cell_dim = fdrake_mesh.cell_dimension() @@ -601,7 +618,10 @@ FromBoundaryFiredrakeConnection`. dim=cell_dim, unit_nodes=finat_unit_nodes) + unflipped_group_logger.done() + # Next get the vertices (we'll need these for the orientations) + vertices_logger = ProcessLogger(logger, "Obtaining vertex coordinates") coord_finat = fdrake_mesh.coordinates.function_space().finat_element # unit_vertex_indices are the element-local indices of the nodes @@ -634,13 +654,20 @@ FromBoundaryFiredrakeConnection`. local_node_nr = unit_vertex_indices[local_vert_id] vertices[:, global_vert_id] = nodes[:, icell, local_node_nr] + vertices_logger.done() + # Use the vertices to compute the orientations and flip the group + orientation_logger = ProcessLogger(logger, "Computing cell orientations") orient = _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, cells_to_use=cells_to_use, normals=normals, no_normals_warn=no_normals_warn) + orientation_logger.done() + + flipped_grp_logger = ProcessLogger(logger, "Flipping group") from meshmode.mesh.processing import flip_simplex_element_group group = flip_simplex_element_group(vertices, unflipped_group, orient < 0) + flipped_grp_logger.done() # Now, any flipped element had its 0 vertex and 1 vertex exchanged. # This changes the local facet nr, so we need to create and then @@ -656,9 +683,12 @@ FromBoundaryFiredrakeConnection`. elif 1 not in face: no_one_face_ndx = iface + adj_grps_logger = ProcessLogger(logger, "Building (possibly) unflipped " + "FacialAdjacencyGroups") unflipped_facial_adjacency_groups = \ _get_firedrake_facial_adjacency_groups(fdrake_mesh, cells_to_use=cells_to_use) + adj_grps_logger.done() # applied below to take elements and element_faces # (or neighbors and neighbor_faces) and flip in any faces that need to @@ -676,6 +706,9 @@ FromBoundaryFiredrakeConnection`. return faces # Create new facial adjacency groups that have been flipped + flip_adj_grps_logger = ProcessLogger(logger, + "Flipping FacialAdjacencyGroups") + facial_adjacency_groups = [] for igroup, fagrps in enumerate(unflipped_facial_adjacency_groups): facial_adjacency_groups.append({}) @@ -692,6 +725,8 @@ FromBoundaryFiredrakeConnection`. neighbor_faces=new_neighbor_faces) facial_adjacency_groups[igroup][ineighbor_group] = new_fagrp + flip_adj_grps_logger.done() + return (Mesh(vertices, [group], boundary_tags=bdy_tags, nodal_adjacency=nodal_adjacency, @@ -768,13 +803,18 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): "('mesh.vertices' is *None*)") # Get the vertices and vertex indices of the requested group + vertices_logger = ProcessLogger(logger, + "Obtaining vertices from selected group") group = mesh.groups[group_nr] fd2mm_indices = np.unique(group.vertex_indices.flatten()) coords = mesh.vertices[:, fd2mm_indices].T mm2fd_indices = dict(zip(fd2mm_indices, np.arange(np.size(fd2mm_indices)))) cells = np.vectorize(mm2fd_indices.__getitem__)(group.vertex_indices) + vertices_logger.done() + # Get a dmplex object and then a mesh topology + top_logger = ProcessLogger(logger, "Building dmplex object and MeshTopology") if comm is None: from pyop2.mpi import COMM_WORLD comm = COMM_WORLD @@ -790,7 +830,12 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): top = fd_mesh.Mesh(plex, dim=mesh.ambient_dim) # mesh topology top.init() + top_logger.done() + # Get new element ordering: + perm_logger = ProcessLogger(logger, "Determining permutations applied" + " to local vertex numbers") + c_start, c_end = top._topology_dm.getHeightStratum(0) cell_index_mm2fd = np.vectorize(top._cell_numbering.getOffset)( np.arange(c_start, c_end)) @@ -837,7 +882,11 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): perm2cells = {perm: np.array(cells) for perm, cells in perm2cells.items()} + perm_logger.done() + # Now make a coordinates function + fspace_logger = ProcessLogger(logger, "Building firedrake function " + "space for mesh coordinates") from firedrake import VectorFunctionSpace, Function if mesh.is_conforming: family = 'CG' @@ -847,6 +896,7 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): coords_fspace = VectorFunctionSpace(top, family, group.order, dim=mesh.ambient_dim) coords = Function(coords_fspace) + fspace_logger.done() # get firedrake unit nodes and map onto meshmode reference element fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(group.dim, True) @@ -863,6 +913,9 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): # Now put the nodes in the right local order # nodes is shaped *(ambient dim, nelements, nunit nodes)* + undo_perm_logger = ProcessLogger(logger, "Storing meshmode mesh coordinates" + " in firedrake nodal order") + from meshmode.mesh.processing import get_simplex_element_flip_matrix for perm, cells in perm2cells.items(): flip_mat = get_simplex_element_flip_matrix(group.order, @@ -872,11 +925,17 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): resampled_group_nodes[:, cells, :] = \ np.matmul(resampled_group_nodes[:, cells, :], flip_mat.T) + undo_perm_logger.done() + # store resampled data in right cell ordering + resample_logger = ProcessLogger(logger, "resampling mesh coordinates to " + "firedrake unit nodes") reordered_cell_node_list = coords_fspace.cell_node_list[cell_index_mm2fd] coords.dat.data[reordered_cell_node_list, :] = \ resampled_group_nodes.transpose((1, 2, 0)) + resample_logger.done() + return fd_mesh.Mesh(coords), cell_index_mm2fd, perm2cells # }}} -- GitLab From 31bb494078699f46d349fc63494d65c87289e441 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 13 Aug 2020 13:21:14 -0500 Subject: [PATCH 175/221] np.matmul -> einsum for readability --- meshmode/interop/firedrake/connection.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 30a35fe6..d5bbe70c 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -462,9 +462,10 @@ class FiredrakeConnection: # put the firedrake data in meshmode order and then resample, # storing in dof_array dof_array[self.group_nr].set( - np.matmul(fd_data[self.mm2fd_node_mapping], - self._resampling_mat_fd2mm.T) - ) + np.einsum("ij,kj->ik", + fd_data[self.mm2fd_node_mapping], + self._resampling_mat_fd2mm) + ) # If scalar, just reorder and resample out if fspace_shape == (): -- GitLab From 9fed3cb402b9585b622f24cf13ead5c3d929bcd2 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Fri, 14 Aug 2020 10:03:46 -0500 Subject: [PATCH 176/221] Added/fixed finat intersphinx links --- doc/conf.py | 1 + doc/interop.rst | 6 +++--- meshmode/interop/firedrake/reference_cell.py | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 04e723f2..cb25d8fe 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -285,4 +285,5 @@ intersphinx_mapping = { 'https://firedrakeproject.org/': None, 'https://tisaac.gitlab.io/recursivenodes/': None, 'https://fenics.readthedocs.io/projects/fiat/en/latest/': None, + 'https://finat.github.io/FInAT/': None, } diff --git a/doc/interop.rst b/doc/interop.rst index 6b42287f..78caab70 100644 --- a/doc/interop.rst +++ b/doc/interop.rst @@ -158,7 +158,7 @@ on some function space on the mesh... etc. Under the hood, we divide between topological and geometric objects, roughly as so: -(1) A reference element defined using :mod:`FInAT` and :mod:`FIAT` +(1) A reference element defined using :mod:`finat` and :mod:`FIAT` is used to define what meshmode calls the unit nodes and unit vertices. It is worth noting that :mod:`firedrake` does not require a positive orientation of elements and that its @@ -170,10 +170,10 @@ roughly as so: etc. (3) A class :class:`~firedrake.functionspaceimpl.FunctionSpace` - created from a :mod:`FInAT` element and a + created from a :mod:`finat` element and a :class:`~firedrake.mesh.MeshTopology` which allows us to define functions mapping the nodes (defined by the - :mod:`FInAT` element) of each element in the + :mod:`finat` element) of each element in the :class:`~firedrake.mesh.MeshTopology` to some values. Note that the function :func:`~firedrake.functionspace.FunctionSpace` in the firedrake API is used to create objects of class diff --git a/meshmode/interop/firedrake/reference_cell.py b/meshmode/interop/firedrake/reference_cell.py index d03babcd..bf642993 100644 --- a/meshmode/interop/firedrake/reference_cell.py +++ b/meshmode/interop/firedrake/reference_cell.py @@ -116,8 +116,8 @@ def get_affine_reference_simplex_mapping(ambient_dim, firedrake_to_meshmode=True def get_finat_element_unit_nodes(finat_element): """ - Returns the unit nodes used by the FInAT element in firedrake's - (equivalently, FInAT/FIAT's) reference coordinates + Returns the unit nodes used by the :mod:`finat` element in firedrake's + (equivalently, :mod:`finat`/:mod:`FIAT`'s) reference coordinates :arg finat_element: A :class:`~finat.finiteelementbase.FiniteElementBase` instance (i.e. a firedrake function space's reference element). -- GitLab From 51270f9abc1b82b17c31442d6cb57bd95efb6174 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Fri, 14 Aug 2020 10:16:22 -0500 Subject: [PATCH 177/221] assume recursivenodes is installed --- meshmode/interop/firedrake/connection.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index d5bbe70c..77bff518 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -649,11 +649,8 @@ class FromFiredrakeConnection(FiredrakeConnection): whose group class is a subclass of :class:`~meshmode.discretization.InterpolatoryElementGroupBase`. If *None*, uses - - * A :class:`~meshmode.discretization.poly_element.\ -PolynomialRecursiveNodesGroupFactory` - * A :class:`~meshmode.discretization.poly_element.\ -PolynomialWarpAndBlendGroupFactory` + a :class:`~meshmode.discretization.poly_element.\ +PolynomialRecursiveNodesGroupFactory` with ``'lgl'`` nodes """ # Ensure fdrake_fspace is a function space with appropriate reference # element. @@ -687,12 +684,8 @@ PolynomialWarpAndBlendGroupFactory` # If not provided, make one else: degree = ufl_elt.degree() - try: - import recursivenodes # noqa : F401 - family = 'lgl' # L-G-Legendre - grp_factory = PolynomialRecursiveNodesGroupFactory(degree, family) - except ImportError: - grp_factory = InterpolatoryQuadratureSimplexGroupFactory(degree) + family = 'lgl' # L-G-Legendre + grp_factory = PolynomialRecursiveNodesGroupFactory(degree, family) # In case this class is really a FromBoundaryFiredrakeConnection, # get *cells_to_use* -- GitLab From 139d2d6299a825aa55284e7ddaac96c20c9733ca Mon Sep 17 00:00:00 2001 From: benSepanski Date: Fri, 14 Aug 2020 11:11:45 -0500 Subject: [PATCH 178/221] Change error on test fail to reflect we are testing sin(x) transfer, not x transfer --- test/test_firedrake_interop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 074a608e..7a678136 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -485,7 +485,7 @@ def test_from_fd_transfer(ctx_factory, fspace_degree, # assert that order is correct or error is "low enough" for ((fd2mm, d), eoc_rec) in six.iteritems(eoc_recorders): - print("\nfiredrake -> meshmode: %s\nvector *x* -> *x[%s]*\n" + print("\nfiredrake -> meshmode: %s\nvector *x* -> *sin(x[%s])*\n" % (fd2mm, d), eoc_rec) assert ( eoc_rec.order_estimate() >= fspace_degree -- GitLab From 80e26b76f48a7a570b910c21767bb60916e676c7 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Fri, 14 Aug 2020 15:05:18 -0500 Subject: [PATCH 179/221] Go back to checking for recursivenodes, update alternative to warp and blend --- meshmode/interop/firedrake/connection.py | 26 +++++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 77bff518..48a75ced 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -40,7 +40,7 @@ from meshmode.interop.firedrake.reference_cell import ( from meshmode.mesh.processing import get_simplex_element_flip_matrix from meshmode.discretization.poly_element import ( - InterpolatoryQuadratureSimplexGroupFactory, + PolynomialWarpAndBlendGroupFactory, PolynomialRecursiveNodesGroupFactory, ElementGroupFactory) from meshmode.discretization import ( @@ -674,18 +674,24 @@ PolynomialRecursiveNodesGroupFactory` with ``'lgl'`` nodes "meshmode.discretization.ElementGroupFactory," "but is instead of type " "'%s'." % type(grp_factory)) - if not issubclass(grp_factory.group_class, - InterpolatoryElementGroupBase): - raise TypeError("'grp_factory.group_class' must inherit from" - "meshmode.discretization." - "InterpolatoryElementGroupBase, but" - " is instead of type '%s'" - % type(grp_factory.group_class)) + if not issubclass(grp_factory.group_class, + InterpolatoryElementGroupBase): + raise TypeError("'grp_factory.group_class' must inherit from" + "meshmode.discretization." + "InterpolatoryElementGroupBase, but" + " is instead of type '%s'" + % type(grp_factory.group_class)) # If not provided, make one else: degree = ufl_elt.degree() - family = 'lgl' # L-G-Legendre - grp_factory = PolynomialRecursiveNodesGroupFactory(degree, family) + try: + # recursivenodes is only importable in Python 3.8, so still have + # to check + import recursivenodes # noqa : F401 + family = 'lgl' # L-G-Legendre + grp_factory = PolynomialRecursiveNodesGroupFactory(degree, family) + except ImportError: + grp_factory = PolynomialWarpAndBlendGroupFactory(degree) # In case this class is really a FromBoundaryFiredrakeConnection, # get *cells_to_use* -- GitLab From dad514d871c90c2da8c5798de8bbdf52899cafc2 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Fri, 14 Aug 2020 16:38:20 -0500 Subject: [PATCH 180/221] Clarify role of actx in instantiating a FiredrakeConnection from firedrake --- meshmode/interop/firedrake/connection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 48a75ced..a9450d05 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -639,6 +639,7 @@ class FromFiredrakeConnection(FiredrakeConnection): def __init__(self, actx, fdrake_fspace, grp_factory=None): """ :arg actx: A :class:`~meshmode.array_context.ArrayContext` + used to instantiate :attr:`FiredrakeConnection.discr`. :arg fdrake_fspace: A :mod:`firedrake` ``"CG"`` or ``"DG"`` function space (of class :class:`~firedrake.functionspaceimpl.WithGeometry`) built on -- GitLab From 3d7dfe75b796d4bc3fdeef4561ede93d6a727d63 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Fri, 14 Aug 2020 16:41:59 -0500 Subject: [PATCH 181/221] Improved comments/documentations explaining which group factory is used --- meshmode/interop/firedrake/connection.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index a9450d05..82ef53a4 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -649,9 +649,14 @@ class FromFiredrakeConnection(FiredrakeConnection): a :class:`~meshmode.discretization.poly_element.ElementGroupFactory` whose group class is a subclass of :class:`~meshmode.discretization.InterpolatoryElementGroupBase`. - If *None*, uses + If *None*, and :mod:`recursivenodes` can be imported, a :class:`~meshmode.discretization.poly_element.\ -PolynomialRecursiveNodesGroupFactory` with ``'lgl'`` nodes +PolynomialRecursiveNodesGroupFactory` with ``'lgl'`` nodes is used. + Note that :mod:`recursivenodes` may not be importable + as it uses :func:`math.comb`, which is new in Python 3.8. + In the case that :mod:`recursivenodes` cannot be successfully + imported, a :class:`~meshmode.discretization.poly_element.\ +PolynomialWarpAndBlendGroupFactory` is used. """ # Ensure fdrake_fspace is a function space with appropriate reference # element. @@ -686,12 +691,14 @@ PolynomialRecursiveNodesGroupFactory` with ``'lgl'`` nodes else: degree = ufl_elt.degree() try: - # recursivenodes is only importable in Python 3.8, so still have - # to check + # recursivenodes is only importable in Python 3.8 since + # it uses :func:`math.comb`, so need to check if it can + # be imported import recursivenodes # noqa : F401 family = 'lgl' # L-G-Legendre grp_factory = PolynomialRecursiveNodesGroupFactory(degree, family) except ImportError: + # If cannot be imported, uses warp-and-blend nodes grp_factory = PolynomialWarpAndBlendGroupFactory(degree) # In case this class is really a FromBoundaryFiredrakeConnection, -- GitLab From ef26177f85aa5d88fa80a9107f69f48005758822 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sat, 15 Aug 2020 13:25:42 -0500 Subject: [PATCH 182/221] Remove support for continuous spaces --- meshmode/interop/firedrake/connection.py | 126 ++++-------------- .../interop/firedrake/laplace-order1.hdf5 | Bin 0 -> 11896 bytes test/test_firedrake_interop.py | 39 ++---- 3 files changed, 35 insertions(+), 130 deletions(-) create mode 100644 meshmode/interop/firedrake/laplace-order1.hdf5 diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 82ef53a4..057dd911 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -113,6 +113,10 @@ class FiredrakeConnection: index associated to the *j*th degree of freedom of the *i*th element in *element_grp*. + :attr:`mm2fd_node_mapping` must encode an embedding + into the :mod:`firedrake` mesh, i.e. no two :mod:`meshmode` nodes + may be associated to the same :mod:`firedrake` node + Degrees of freedom should be associated so that the implicit mapping from a reference element to element *i* which maps meshmode unit dofs *0,1,...,n-1* to actual @@ -132,8 +136,7 @@ class FiredrakeConnection: :param discr: A :class:`meshmode.discretization.Discretization` :param fdrake_fspace: A :class:`firedrake.functionspaceimpl.WithGeometry`. - Must have ufl family ``'Lagrange'`` or - ``'Discontinuous Lagrange'``. + Must use ufl family ``'Discontinuous Lagrange'``. :param mm2fd_node_mapping: Used as attribute :attr:`mm2fd_node_mapping`. A 2-D numpy integer array with the same dtype as ``fdrake_fspace.cell_node_list.dtype`` @@ -185,12 +188,10 @@ class FiredrakeConnection: raise TypeError("'discr.groups[group_nr]' must be of type " "InterpolatoryElementGroupBase" ", not '%s'." % type(element_grp)) - allowed_families = ('Discontinuous Lagrange', 'Lagrange') - if fdrake_fspace.ufl_element().family() not in allowed_families: - raise TypeError("'fdrake_fspace' must have ufl family " - "be one of %s, not '%s'." - % (allowed_families, - fdrake_fspace.ufl_element().family())) + if fdrake_fspace.ufl_element().family() != 'Discontinuous Lagrange': + raise TypeError("'fdrake_fspace.ufl_element().family()' must be" + "'Discontinuous Lagrange', not " + f"'{fdrake_fspace.ufl_element().family()}'") if mm2fd_node_mapping.shape != (element_grp.nelements, element_grp.nunit_dofs): raise ValueError("'mm2fd_node_mapping' must be of shape ", @@ -201,6 +202,10 @@ class FiredrakeConnection: raise ValueError("'mm2fd_node_mapping' must have dtype " "%s, not '%s'" % (fdrake_fspace.cell_node_list.dtype, mm2fd_node_mapping.dtype)) + if np.size(np.unique(mm2fd_node_mapping)) != np.size(mm2fd_node_mapping): + raise ValueError("'mm2fd_node_mapping' must have unique entries; " + "no two meshmode nodes may be associated to the " + "same Firedrake node") # }}} # Get meshmode unit nodes @@ -219,34 +224,6 @@ class FiredrakeConnection: new_nodes=fd_unit_nodes, old_nodes=mm_unit_nodes) - # {{{ compute meshmode node equivalence classes for continuity check - - # Now handle the possibility of multiple meshmode nodes being associated - # to the same firedrake node - unique_fd_nodes, counts = np.unique(mm2fd_node_mapping, - return_counts=True) - # map firedrake nodes associated to more than 1 meshmode node - # to all associated meshmode nodes. - # fd node index -> (meshmode cell index, meshmode local node index) - fd_to_dupes = {} - dup_fd_nodes = set(unique_fd_nodes[counts > 1]) - for icell, cell in enumerate(mm2fd_node_mapping): - for local_mm_inode, fd_inode in enumerate(cell): - if fd_inode in dup_fd_nodes: - fd_to_dupes.setdefault(fd_inode, []) - fd_to_dupes[fd_inode] = (icell, local_mm_inode) - # A list of tuples, each tuple represents an equivalence class - # of meshmode nodes - # (represented as tuples - # *(meshmode_cell_index, meshmode local node index)*) - # which associate to the same firedrake node under - # *mm2fd_node_mapping*. Only equivalence classes of size > 1 - # are included. - self._mm_node_equiv_classes = [tuple(equiv_class) for equiv_class - in fd_to_dupes.values()] - - # }}} - # Store input self.discr = discr self.group_nr = group_nr @@ -485,9 +462,7 @@ class FiredrakeConnection: return out - def from_meshmode(self, mm_field, out=None, - assert_fdrake_discontinuous=True, - continuity_tolerance=None): + def from_meshmode(self, mm_field, out=None): r""" Transport meshmode field from :attr:`discr` into an appropriate firedrake function space. @@ -519,29 +494,9 @@ class FiredrakeConnection: to be stored in. The shape of its function space must match the shape of *mm_field* - :arg assert_fdrake_discontinuous: If *True*, - disallows conversion to a continuous firedrake function space - (i.e. this function checks that ``self.firedrake_fspace()`` is - discontinuous and raises a *ValueError* otherwise) - - :arg continuity_tolerance: If converting to a continuous firedrake - function space (i.e. if ``self.firedrake_fspace()`` is continuous), - assert that at any two meshmode nodes corresponding to the - same firedrake node (meshmode is a discontinuous space, so this - situation will almost certainly happen), the function being transported - has values less than *continuity_tolerance* distance (in - :math:`\\ell^\\infty` distance) - apart. If *None*, no checks are performed. Does nothing if - the firedrake function space is discontinuous - :return: a :class:`firedrake.function.Function` holding the transported data (*out*, if *out* was not *None*) """ - if self._ufl_element.family() == 'Lagrange' \ - and assert_fdrake_discontinuous: - raise ValueError("Trying to convert to continuous function space " - " with 'assert_fdrake_discontinuous' set " - " to *True*") # All firedrake functions are the same dtype dtype = self.firedrake_fspace().mesh().coordinates.dat.data.dtype self._validate_field(mm_field, "mm_field", dtype=dtype) @@ -580,31 +535,10 @@ class FiredrakeConnection: def resample_and_reorder(fd_data, dof_array): # pull data into numpy dof_np = dof_array.array_context.to_numpy(dof_array[self.group_nr]) - # resample the data data (keep this for continuity checks) - resampled_data = np.matmul(dof_np, self._resampling_mat_mm2fd.T) + # resample the data and store in firedrake ordering # store resampled data in firedrake ordering - fd_data[self.mm2fd_node_mapping] = resampled_data - - # Continuity checks if requested - if self._ufl_element.family() == 'Lagrange' \ - and continuity_tolerance is not None: - assert isinstance(continuity_tolerance, float) - assert continuity_tolerance >= 0 - # Make sure not to compare using mm_field, - # because two meshmode nodes associated to the same firedrake - # nodes may have been resampled to distinct nodes on different - # elements. - for mm_equiv_class in self._mm_node_equiv_classes: - l_inf = np.ptp(resampled_data[mm_equiv_class]) - if l_inf >= continuity_tolerance: - fd_inode = self.mm2fd_node_mapping[mm_equiv_class[0]] - raise ValueError("Meshmode nodes %s (written as " - " *(element index, local node index)*)" - " represent the same firedrake node %s" - ", but 'mm_field's resampled " - " values are %s > %s apart)" - % (mm_equiv_class, fd_inode, - l_inf, continuity_tolerance)) + fd_data[self.mm2fd_node_mapping] = \ + np.einsum("ij,kj->ik", dof_np, self._resampling_mat_mm2fd) # If scalar, just reorder and resample out if fspace_shape == (): @@ -667,9 +601,9 @@ PolynomialWarpAndBlendGroupFactory` is used. % type(fdrake_fspace)) ufl_elt = fdrake_fspace.ufl_element() - if ufl_elt.family() not in ('Lagrange', 'Discontinuous Lagrange'): + if ufl_elt.family() != 'Discontinuous Lagrange': raise ValueError("the 'fdrake_fspace.ufl_element().family()' of " - "must be be 'Lagrange' or " + "must be be " "'Discontinuous Lagrange', not '%s'." % ufl_elt.family()) # Make sure grp_factory is the right type if provided, and @@ -720,17 +654,6 @@ PolynomialWarpAndBlendGroupFactory` is used. # a numpy array # Get the reordering fd->mm. - # - # One should note there is something a bit more subtle going on - # in the continuous case. All meshmode discretizations use - # are discontinuous, so nodes are associated with elements(cells) - # not vertices. In a continuous firedrake space, some nodes - # are shared between multiple cells. In particular, while the - # below "reordering" is indeed a permutation if the firedrake space - # is discontinuous, if the firedrake space is continuous then - # some firedrake nodes correspond to nodes on multiple meshmode - # elements, i.e. those nodes appear multiple times - # in the "reordering" array flip_mat = get_simplex_element_flip_matrix(ufl_elt.degree(), fd_unit_nodes) fd_cell_node_list = fdrake_fspace.cell_node_list @@ -742,12 +665,12 @@ PolynomialWarpAndBlendGroupFactory` is used. flip_mat, unflip=False) + assert np.size(np.unique(flipped_cell_node_list)) == \ + np.size(flipped_cell_node_list), \ + "A firedrake node in a 'DG' space got duplicated" super(FromFiredrakeConnection, self).__init__(to_discr, fdrake_fspace, flipped_cell_node_list) - if fdrake_fspace.ufl_element().family() == 'Discontinuous Lagrange': - assert len(self._mm_node_equiv_classes) == 0, \ - "A firedrake node in a 'DG' space got duplicated" def _get_cells_to_use(self, mesh): """ @@ -879,12 +802,13 @@ InterpolatoryQuadratureSimplexElementGroup`. flip_mat.T) mm2fd_node_mapping[cells] = fd_permuted_cell_node + assert np.size(np.unique(mm2fd_node_mapping)) == \ + np.size(mm2fd_node_mapping), \ + "A firedrake node in a 'DG' space got duplicated" super(ToFiredrakeConnection, self).__init__(discr, fspace, mm2fd_node_mapping, group_nr=group_nr) - assert len(self._mm_node_equiv_classes) == 0, \ - "A firedrake node in a 'DG' space got duplicated" # }}} diff --git a/meshmode/interop/firedrake/laplace-order1.hdf5 b/meshmode/interop/firedrake/laplace-order1.hdf5 new file mode 100644 index 0000000000000000000000000000000000000000..403234a909e7a20f65256a5755bb9717423e44a7 GIT binary patch literal 11896 zcmeHNJ!~9B6rQtVbBWKye}NDPIQ$r=K?g+O0?8N2j;^qXl^-co>-F6duVUX`_RiLY z21`(w4p9LT5@n!DOJ&MP5dI1XB0^9g(jd7+h+yXDeYf6snpVO)-b2R@3H4;N;(+MWO}y6WX*^>nz`>uPMuy-D!RsBSk&zk|?Tb*}Ho{l+1`?v9X{^he1 zDuNMr!xXp8{lVfGcBC-w0b3rJzR$>2X@4i>fwf*EkM)R;l4AdN57_d++Y~>T|JktY zh0e3vmIp>DZjlFg9K((j#yw!m1DAS?T!Z#^QXcrd-^izVAnxD&g4y!GyA(h0!<4t= z%{%#R%L93eTjT*A$FL)XaSvp~kX{phuf0L{+EF@JCFOy;4;sB`9*FyQzhKA%H)z7k z1glS(MotuD1w~ax21QPu;!AGLbG%9^DEpPm&aA|YT%Qae6GH_}zNj$>%y0B^{#O^>vJKftzTp>_C>$$R|^)ylIP3@4QF2cWRhE~dccqN*8tbEVYP%+Pc#qMGeq;4 z4gJy;NBeQZLfGb2CWCTfn*4ZJjL04BmfdRIU-B@kqjEsy1Et6KA}h|Eo;p7%X&262 zR6sM}CymQjq_V0!_Z}7RVRFBkhO9 zxh8+2y&U{L^JIIuF=9_Y(Ozzt*oDX2%gON9&+P}=%WV&LZQSTx8~^!*J0E`+{I>e# zt;U&*eaeeb5IS^WpkHH~#1=z~6t+2OJLnVsz|3$aRx~KS_>5 z=m%Y$jns3GI?g={@R?_&p5!1m^~hcglN|iXDD$ zw%@-lkze|tQU^+FoZu#8;%KMe@#>5aWw-7s8y14H=Tw4l!JYSC^JF1}o-1Bhkskt7 zO(OJzuth{xdc4pr)%~F2=o;aYS5lpI+pm=UlBabkU#cf>K;k=Te}6<805`v0`IAOP zv1_9-t?RYCk-cke)cbgSG^!(+`cJ%0TbuJ=ZD03`&dKrowrdkj2X5;D+_N~u{^E4t zNJ_vt#QluZcM8XS&o&-5sf~{~w(&Wg{)W)}-zTCUa`;;WaPvo1(Ub=rkUnQx`H`r~_xjnC=#C+OqysDE-ga8nhv`v;$MAfE&WHfi5rg8WbdKgpgX{*eUzC>KHx xeSU4mi#g3l{-Z;9laGEjNQV!r{KEO5MBTP literal 0 HcmV?d00001 diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 7a678136..338bd812 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -107,11 +107,6 @@ def fdrake_mesh(request): return fd_mesh -@pytest.fixture(params=["CG", "DG"]) -def fdrake_family(request): - return request.param - - @pytest.fixture(params=[1, 4], ids=["P^1", "P^4"]) def fspace_degree(request): return request.param @@ -209,7 +204,6 @@ def test_to_fd_consistency(ctx_factory, mm_mesh, fspace_degree): def test_from_boundary_consistency(ctx_factory, fdrake_mesh, - fdrake_family, fspace_degree): """ Make basic checks that FiredrakeFromBoundaryConnection is not doing @@ -220,7 +214,7 @@ def test_from_boundary_consistency(ctx_factory, and that each boundary tag is associated to the same number of facets in the converted meshmode mesh as in the original firedrake mesh. """ - fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fspace_degree) + fdrake_fspace = FunctionSpace(fdrake_mesh, 'DG', fspace_degree) cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) @@ -372,11 +366,10 @@ def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values, ("warp", [10, 20, 30], 2), ("warp", [10, 20, 30], 3), ]) -@pytest.mark.parametrize("fdrake_family", ['DG', 'CG']) @pytest.mark.parametrize("only_convert_bdy", [False, True]) def test_from_fd_transfer(ctx_factory, fspace_degree, fdrake_mesh_name, fdrake_mesh_pars, dim, - fdrake_family, only_convert_bdy): + only_convert_bdy): """ Make sure creating a function which projects onto one dimension then transports it is the same @@ -440,7 +433,7 @@ def test_from_fd_transfer(ctx_factory, fspace_degree, for mesh_par in fdrake_mesh_pars: fdrake_mesh, h = get_fdrake_mesh_and_h_from_par(mesh_par) # make function space and build connection - fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fspace_degree) + fdrake_fspace = FunctionSpace(fdrake_mesh, 'DG', fspace_degree) if only_convert_bdy: fdrake_connection = FromBoundaryFiredrakeConnection(actx, fdrake_fspace, @@ -475,10 +468,7 @@ def test_from_fd_transfer(ctx_factory, fspace_degree, # now transport mm -> fd meshmode_f_dofarr = discr.zeros(actx) meshmode_f_dofarr[0][:] = meshmode_f - mm2fd_f = fdrake_connection.from_meshmode( - meshmode_f_dofarr, - assert_fdrake_discontinuous=False, - continuity_tolerance=1e-8) + mm2fd_f = fdrake_connection.from_meshmode(meshmode_f_dofarr) # record mm -> fd error err = np.max(np.abs(fdrake_f.dat.data - mm2fd_f.dat.data)) eoc_recorders[(False, d)].add_data_point(h, err) @@ -571,27 +561,24 @@ def test_to_fd_transfer(ctx_factory, fspace_degree, mesh_name, mesh_pars, dim): @pytest.mark.parametrize("fspace_type", ("scalar", "vector", "tensor")) @pytest.mark.parametrize("only_convert_bdy", (False, True)) def test_from_fd_idempotency(ctx_factory, - fdrake_mesh, fdrake_family, fspace_degree, + fdrake_mesh, fspace_degree, fspace_type, only_convert_bdy): """ Make sure fd->mm->fd and (fd->)->mm->fd->mm are identity """ # Make a function space and a function with unique values at each node if fspace_type == "scalar": - fdrake_fspace = FunctionSpace(fdrake_mesh, fdrake_family, fspace_degree) + fdrake_fspace = FunctionSpace(fdrake_mesh, 'DG', fspace_degree) # Just use the node nr fdrake_unique = Function(fdrake_fspace) fdrake_unique.dat.data[:] = np.arange(fdrake_unique.dat.data.shape[0]) elif fspace_type == "vector": - fdrake_fspace = VectorFunctionSpace(fdrake_mesh, fdrake_family, - fspace_degree) + fdrake_fspace = VectorFunctionSpace(fdrake_mesh, 'DG', fspace_degree) # use the coordinates xx = SpatialCoordinate(fdrake_fspace.mesh()) fdrake_unique = Function(fdrake_fspace).interpolate(xx) elif fspace_type == "tensor": - fdrake_fspace = TensorFunctionSpace(fdrake_mesh, - fdrake_family, - fspace_degree) + fdrake_fspace = TensorFunctionSpace(fdrake_mesh, 'DG', fspace_degree) # use the coordinates, duplicated into the right tensor shape xx = SpatialCoordinate(fdrake_fspace.mesh()) dim = fdrake_fspace.mesh().geometric_dimension() @@ -614,20 +601,14 @@ def test_from_fd_idempotency(ctx_factory, fdrake_fspace, 'on_boundary') temp = fdrake_connection.from_firedrake(fdrake_unique, actx=actx) - fdrake_unique = \ - fdrake_connection.from_meshmode(temp, - assert_fdrake_discontinuous=False, - continuity_tolerance=1e-8) + fdrake_unique = fdrake_connection.from_meshmode(temp) else: fdrake_connection = FromFiredrakeConnection(actx, fdrake_fspace) # Test for idempotency fd->mm->fd mm_field = fdrake_connection.from_firedrake(fdrake_unique, actx=actx) fdrake_unique_copy = Function(fdrake_fspace) - fdrake_connection.from_meshmode(mm_field, - out=fdrake_unique_copy, - assert_fdrake_discontinuous=False, - continuity_tolerance=1e-8) + fdrake_connection.from_meshmode(mm_field, out=fdrake_unique_copy) np.testing.assert_allclose(fdrake_unique_copy.dat.data, fdrake_unique.dat.data, -- GitLab From ebecb103908032934c90c9125ee399d96b408bc0 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sat, 15 Aug 2020 13:26:07 -0500 Subject: [PATCH 183/221] First stab at running examples during CI --- .github/workflows/ci.yml | 12 ++++++++++++ .gitlab-ci.yml | 9 +++++++++ examples/from_firedrake.py | 3 +++ examples/to_firedrake.py | 3 +++ 4 files changed, 27 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9457918a..b4b268a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,6 +59,18 @@ jobs: . /home/firedrake/firedrake/bin/activate cd test python -m pytest --tb=native -rxsw test_firedrake_interop.py + - name: "Examples" + run: | + . /home/firedrake/firedrake/bin/activate + cd examples + for i in $(find . -name '*firedrake*.py' -exec grep -q __main__ '{}' \; -print ); do + echo "-----------------------------------------------------------------------" + echo "RUNNING $i" + echo "-----------------------------------------------------------------------" + dn=$(dirname "$i") + bn=$(basename "$i") + (cd $dn; time ${PY_EXE} "$bn") + done examples3: name: Examples Conda Py3 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c3324b75..d2a06263 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -66,6 +66,15 @@ Python 3 POCL Firedrake: - pip install . - cd test - python -m pytest --tb=native --junitxml=pytest.xml -rxsw test_firedrake_interop.py + - cd ../examples + - for i in $(find . -name '*firedrake*.py' -exec grep -q __main__ '{}' \; -print ); do \ + echo "-----------------------------------------------------------------------" \ + echo "RUNNING $i" \ + echo "-----------------------------------------------------------------------" \ + dn=$(dirname "$i") \ + bn=$(basename "$i") \ + (cd $dn; time ${PY_EXE} "$bn") \ + done artifacts: reports: junit: test/pytest.xml diff --git a/examples/from_firedrake.py b/examples/from_firedrake.py index b3f99641..87bcbd9c 100644 --- a/examples/from_firedrake.py +++ b/examples/from_firedrake.py @@ -28,6 +28,9 @@ import pyopencl as cl # what a FromBoundaryFiredrakeConnection does def main(): # If can't import firedrake, do nothing + # + # filename MUST include "firedrake" (i.e. match *firedrake*.py) in order + # to be run during CI try: import firedrake # noqa : F401 except ImportError: diff --git a/examples/to_firedrake.py b/examples/to_firedrake.py index 122fb691..208a9054 100644 --- a/examples/to_firedrake.py +++ b/examples/to_firedrake.py @@ -30,6 +30,9 @@ import pyopencl as cl # https://gitlab.tiker.net/inducer/meshmode/-/blob/7826fa5e13854bf1dae425b4226865acc10ee01f/examples/simple-dg.py # noqa : E501 def main(): # If can't import firedrake, do nothing + # + # filename MUST include "firedrake" (i.e. match *firedrake*.py) in order + # to be run during CI try: import firedrake # noqa : F401 except ImportError: -- GitLab From 9be6d305412c7388ea109d226b952ec4c17cda6e Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sat, 15 Aug 2020 13:27:34 -0500 Subject: [PATCH 184/221] Add line continuation to github workflow --- .github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4b268a1..5addd5d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,13 +63,13 @@ jobs: run: | . /home/firedrake/firedrake/bin/activate cd examples - for i in $(find . -name '*firedrake*.py' -exec grep -q __main__ '{}' \; -print ); do - echo "-----------------------------------------------------------------------" - echo "RUNNING $i" - echo "-----------------------------------------------------------------------" - dn=$(dirname "$i") - bn=$(basename "$i") - (cd $dn; time ${PY_EXE} "$bn") + for i in $(find . -name '*firedrake*.py' -exec grep -q __main__ '{}' \; -print ); do \ + echo "-----------------------------------------------------------------------" \ + echo "RUNNING $i" \ + echo "-----------------------------------------------------------------------" \ + dn=$(dirname "$i") \ + bn=$(basename "$i") \ + (cd $dn; time ${PY_EXE} "$bn") \ done examples3: -- GitLab From 2b8b298d885345937196765c5e91667c49e34bd0 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sat, 15 Aug 2020 13:32:25 -0500 Subject: [PATCH 185/221] Try echo -e on workflow --- .github/workflows/ci.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5addd5d1..cda8effe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,14 +63,15 @@ jobs: run: | . /home/firedrake/firedrake/bin/activate cd examples - for i in $(find . -name '*firedrake*.py' -exec grep -q __main__ '{}' \; -print ); do \ - echo "-----------------------------------------------------------------------" \ - echo "RUNNING $i" \ - echo "-----------------------------------------------------------------------" \ - dn=$(dirname "$i") \ - bn=$(basename "$i") \ - (cd $dn; time ${PY_EXE} "$bn") \ - done + echo -e + 'for i in $(find . -name "*firedrake*.py" -exec grep -q __main__ "{}" \; -print ); do + echo "-----------------------------------------------------------------------" + echo "RUNNING $i" + echo "-----------------------------------------------------------------------" + dn=$(dirname "$i") + bn=$(basename "$i") + (cd $dn; time ${PY_EXE} "$bn") + done' examples3: name: Examples Conda Py3 -- GitLab From 1d21dfa70605c138f9dd834257fd5c4b03a864d2 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sat, 15 Aug 2020 13:34:13 -0500 Subject: [PATCH 186/221] change gitlab ci to echo -e --- .gitlab-ci.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d2a06263..8534e81c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -67,14 +67,15 @@ Python 3 POCL Firedrake: - cd test - python -m pytest --tb=native --junitxml=pytest.xml -rxsw test_firedrake_interop.py - cd ../examples - - for i in $(find . -name '*firedrake*.py' -exec grep -q __main__ '{}' \; -print ); do \ - echo "-----------------------------------------------------------------------" \ - echo "RUNNING $i" \ - echo "-----------------------------------------------------------------------" \ - dn=$(dirname "$i") \ - bn=$(basename "$i") \ - (cd $dn; time ${PY_EXE} "$bn") \ - done + - echo -e + 'for i in $(find . -name "*firedrake*.py" -exec grep -q __main__ "{}" \; -print ); do + echo "-----------------------------------------------------------------------" + echo "RUNNING $i" + echo "-----------------------------------------------------------------------" + dn=$(dirname "$i") + bn=$(basename "$i") + (cd $dn; time ${PY_EXE} "$bn") + done' artifacts: reports: junit: test/pytest.xml -- GitLab From 540a5934ed11a31291c0e61806c41f36898e8215 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sat, 15 Aug 2020 15:33:16 -0500 Subject: [PATCH 187/221] Next try at for loop in github CI --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cda8effe..8001643a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,15 +63,15 @@ jobs: run: | . /home/firedrake/firedrake/bin/activate cd examples - echo -e - 'for i in $(find . -name "*firedrake*.py" -exec grep -q __main__ "{}" \; -print ); do + > + for i in $(find . -name '*firedrake*.py' -exec grep -q __main__ '{}' \; -print ); do echo "-----------------------------------------------------------------------" echo "RUNNING $i" echo "-----------------------------------------------------------------------" dn=$(dirname "$i") bn=$(basename "$i") (cd $dn; time ${PY_EXE} "$bn") - done' + done examples3: name: Examples Conda Py3 -- GitLab From a140fcf9b147319222d8ef06312dd8d18815de48 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sat, 15 Aug 2020 16:44:38 -0500 Subject: [PATCH 188/221] one more shot at getting the github ci to work --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8001643a..dd4bf8fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,8 +63,8 @@ jobs: run: | . /home/firedrake/firedrake/bin/activate cd examples - > - for i in $(find . -name '*firedrake*.py' -exec grep -q __main__ '{}' \; -print ); do + for i in $(find . -name '*firedrake*.py' -exec grep -q __main__ '{}' \; -print ) + do echo "-----------------------------------------------------------------------" echo "RUNNING $i" echo "-----------------------------------------------------------------------" -- GitLab From 0d6773abdd10f982e6079e3d1419482173303b36 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sat, 15 Aug 2020 16:45:17 -0500 Subject: [PATCH 189/221] Move examples before test so that I can debug faster --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd4bf8fa..802dad49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,11 +54,6 @@ jobs: # doesn't have dataclasses. pip install pytest dataclasses pip install . - - name: "Test" - run: | - . /home/firedrake/firedrake/bin/activate - cd test - python -m pytest --tb=native -rxsw test_firedrake_interop.py - name: "Examples" run: | . /home/firedrake/firedrake/bin/activate @@ -72,6 +67,11 @@ jobs: bn=$(basename "$i") (cd $dn; time ${PY_EXE} "$bn") done + - name: "Test" + run: | + . /home/firedrake/firedrake/bin/activate + cd test + python -m pytest --tb=native -rxsw test_firedrake_interop.py examples3: name: Examples Conda Py3 -- GitLab From ca32658d13f739781c415101150e06303b9ea72a Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sat, 15 Aug 2020 17:13:57 -0500 Subject: [PATCH 190/221] Try ci using ci-support --- .github/workflows/ci.yml | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 802dad49..5105f434 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,17 +56,10 @@ jobs: pip install . - name: "Examples" run: | + curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/ci-support.sh + source ci-support.sh . /home/firedrake/firedrake/bin/activate - cd examples - for i in $(find . -name '*firedrake*.py' -exec grep -q __main__ '{}' \; -print ) - do - echo "-----------------------------------------------------------------------" - echo "RUNNING $i" - echo "-----------------------------------------------------------------------" - dn=$(dirname "$i") - bn=$(basename "$i") - (cd $dn; time ${PY_EXE} "$bn") - done + run_examples # from ci-support - name: "Test" run: | . /home/firedrake/firedrake/bin/activate -- GitLab From a5b1bbf341f91fa50e771a805332df6d87d1aada Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sat, 15 Aug 2020 17:28:15 -0500 Subject: [PATCH 191/221] Try making our own .sh file to run examples --- .github/workflows/ci.yml | 4 +--- .github/workflows/run_firedrake_examples.sh | 9 +++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/run_firedrake_examples.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5105f434..36083af3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,10 +56,8 @@ jobs: pip install . - name: "Examples" run: | - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/ci-support.sh - source ci-support.sh . /home/firedrake/firedrake/bin/activate - run_examples # from ci-support + source run_firedrake_examples.sh - name: "Test" run: | . /home/firedrake/firedrake/bin/activate diff --git a/.github/workflows/run_firedrake_examples.sh b/.github/workflows/run_firedrake_examples.sh new file mode 100644 index 00000000..d5f59f7a --- /dev/null +++ b/.github/workflows/run_firedrake_examples.sh @@ -0,0 +1,9 @@ +cd examples +for i in $(find . -name '*firedrake*.py' -exec grep -q __main__ '{}' \; -print ); do + echo "-----------------------------------------------------------------------" + echo "RUNNING $i" + echo "-----------------------------------------------------------------------" + dn=$(dirname "$i") + bn=$(basename "$i") + (cd $dn; time python3 "$bn") +done -- GitLab From ef320858bbe4724001b42576297244e90482cef8 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sat, 15 Aug 2020 17:50:24 -0500 Subject: [PATCH 192/221] source begone --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36083af3..c377241f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: - name: "Examples" run: | . /home/firedrake/firedrake/bin/activate - source run_firedrake_examples.sh + . ./run_firedrake_examples.sh - name: "Test" run: | . /home/firedrake/firedrake/bin/activate -- GitLab From 2a6fafe6e27e75e270f09273ef13749c30d461b5 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sat, 15 Aug 2020 18:17:27 -0500 Subject: [PATCH 193/221] Fix run_firedrake_examples.sh link --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c377241f..9bbd8f59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: - name: "Examples" run: | . /home/firedrake/firedrake/bin/activate - . ./run_firedrake_examples.sh + . ./.github/workflows/run_firedrake_examples.sh - name: "Test" run: | . /home/firedrake/firedrake/bin/activate -- GitLab From 6e43115df4b1ff5783c954b77fc3b5bd44d9276b Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sat, 15 Aug 2020 18:41:49 -0500 Subject: [PATCH 194/221] install time --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9bbd8f59..eb136d86 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,7 @@ jobs: - name: "Dependencies" run: | sudo apt update + sudo apt upgrade -y sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev sudo chown -R $(whoami) /github/home @@ -56,6 +57,7 @@ jobs: pip install . - name: "Examples" run: | + sudo apt install time . /home/firedrake/firedrake/bin/activate . ./.github/workflows/run_firedrake_examples.sh - name: "Test" -- GitLab From c4304e2f3d29315bb76cf2768e2a0c63114ce70a Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sat, 15 Aug 2020 18:54:31 -0500 Subject: [PATCH 195/221] Set up gitlab ci to work like github --- .gitlab-ci.yml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8534e81c..4014ec40 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -55,6 +55,8 @@ Python 3 POCL Firedrake: image: "firedrakeproject/firedrake" script: - sudo apt update + - sudo apt upgrade + - sudo apt install time - sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev - source ~/firedrake/bin/activate - "grep -v loopy requirements.txt > myreq.txt" @@ -64,18 +66,11 @@ Python 3 POCL Firedrake: # doesn't have dataclasses. - pip install pytest dataclasses - pip install . + # run examples + - . ./.github/workflows/run_firedrake_examples.sh + # run tests - cd test - python -m pytest --tb=native --junitxml=pytest.xml -rxsw test_firedrake_interop.py - - cd ../examples - - echo -e - 'for i in $(find . -name "*firedrake*.py" -exec grep -q __main__ "{}" \; -print ); do - echo "-----------------------------------------------------------------------" - echo "RUNNING $i" - echo "-----------------------------------------------------------------------" - dn=$(dirname "$i") - bn=$(basename "$i") - (cd $dn; time ${PY_EXE} "$bn") - done' artifacts: reports: junit: test/pytest.xml -- GitLab From 5664f3d2beea8e9e2d1c40af97a1360ecfcc8d82 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sun, 16 Aug 2020 08:46:40 -0500 Subject: [PATCH 196/221] First stab at changing From/ToFiredrakeConnection to factory functions --- doc/interop.rst | 14 +- examples/from_firedrake.py | 24 +- examples/to_firedrake.py | 4 +- meshmode/interop/firedrake/__init__.py | 8 +- meshmode/interop/firedrake/connection.py | 424 +++++++++++------------ test/test_firedrake_interop.py | 36 +- 6 files changed, 242 insertions(+), 268 deletions(-) diff --git a/doc/interop.rst b/doc/interop.rst index 78caab70..c56665e5 100644 --- a/doc/interop.rst +++ b/doc/interop.rst @@ -12,15 +12,15 @@ Function Spaces/Discretizations ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Users wishing to interact with :mod:`meshmode` from :mod:`firedrake` -will primarily interact with the -:class:`~meshmode.interop.firedrake.connection.FromFiredrakeConnection` and -:class:`~meshmode.interop.firedrake.connection.FromBoundaryFiredrakeConnection` -classes, while users wishing +will create a +:class:`~meshmode.interop.firedrake.connection.FiredrakeConnection` +using :func:`~meshmode.interop.firedrake.connection.build_connection_from_firedrake`, +while users wishing to interact with :mod:`firedrake` from :mod:`meshmode` will use +will create a +:class:`~meshmode.interop.firedrake.connection.FiredrakeConnection` +using :func:`~meshmode.interop.firedrake.connection.build_connection_to_firedrake`. the :class:`~meshmode.interop.firedrake.connection.ToFiredrakeConnection` class. -All of these classes inherit from -the :class:`~meshmode.interop.firedrake.connection.FiredrakeConnection` -class, which provides the interface. It is not recommended to create a :class:`~meshmode.interop.firedrake.connection.FiredrakeConnection` directly. diff --git a/examples/from_firedrake.py b/examples/from_firedrake.py index 87bcbd9c..624d1dc4 100644 --- a/examples/from_firedrake.py +++ b/examples/from_firedrake.py @@ -36,8 +36,7 @@ def main(): except ImportError: return 0 - from meshmode.interop.firedrake import ( - FromFiredrakeConnection, FromBoundaryFiredrakeConnection) + from meshmode.interop.firedrake import build_connection_from_firedrake from firedrake import ( UnitSquareMesh, FunctionSpace, SpatialCoordinate, Function, cos ) @@ -54,22 +53,23 @@ def main(): from meshmode.array_context import PyOpenCLArrayContext actx = PyOpenCLArrayContext(queue) - fd_connection = FromFiredrakeConnection(actx, fd_fspace) - fd_bdy_connection = FromBoundaryFiredrakeConnection(actx, - fd_fspace, - 'on_boundary') + fd_connection = build_connection_from_firedrake(actx, fd_fspace) + fd_bdy_connection = \ + build_connection_from_firedrake(actx, + fd_fspace, + restrict_to_boundary='on_boundary') # Plot the meshmode meshes that the connections connect to import matplotlib.pyplot as plt from meshmode.mesh.visualization import draw_2d_mesh fig, (ax1, ax2) = plt.subplots(1, 2) - ax1.set_title("FromFiredrakeConnection") + ax1.set_title("FiredrakeConnection") plt.sca(ax1) draw_2d_mesh(fd_connection.discr.mesh, draw_vertex_numbers=False, draw_element_numbers=False, set_bounding_box=True) - ax2.set_title("FromBoundaryFiredrakeConnection") + ax2.set_title("FiredrakeConnection 'on_boundary'") plt.sca(ax2) draw_2d_mesh(fd_bdy_connection.discr.mesh, draw_vertex_numbers=False, @@ -77,7 +77,7 @@ def main(): set_bounding_box=True) plt.show() - # Plot fd_fntn using FromFiredrakeConnection + # Plot fd_fntn using unrestricted FiredrakeConnection from meshmode.discretization.visualization import make_visualizer discr = fd_connection.discr vis = make_visualizer(actx, discr, discr.groups[0].order+3) @@ -85,17 +85,17 @@ def main(): fig = plt.figure() ax1 = fig.add_subplot(1, 2, 1, projection='3d') - ax1.set_title("cos(x+y) in\nFromFiredrakeConnection") + ax1.set_title("cos(x+y) in\nFiredrakeConnection") vis.show_scalar_in_matplotlib_3d(field, do_show=False) - # Now repeat using FromBoundaryFiredrakeConnection + # Now repeat using FiredrakeConnection restricted to 'on_boundary' bdy_discr = fd_bdy_connection.discr bdy_vis = make_visualizer(actx, bdy_discr, bdy_discr.groups[0].order+3) bdy_field = fd_bdy_connection.from_firedrake(fd_fntn, actx=actx) ax2 = fig.add_subplot(1, 2, 2, projection='3d') plt.sca(ax2) - ax2.set_title("cos(x+y) in\nFromBoundaryFiredrakeConnection") + ax2.set_title("cos(x+y) in\nFiredrakeConnection 'on_boundary'") bdy_vis.show_scalar_in_matplotlib_3d(bdy_field, do_show=False) import matplotlib.cm as cm diff --git a/examples/to_firedrake.py b/examples/to_firedrake.py index 208a9054..fb1a6bef 100644 --- a/examples/to_firedrake.py +++ b/examples/to_firedrake.py @@ -82,8 +82,8 @@ def main(): # {{{ Now send candidate_sol into firedrake and use it for boundary conditions - from meshmode.interop.firedrake import ToFiredrakeConnection - fd_connection = ToFiredrakeConnection(discr, group_nr=0) + from meshmode.interop.firedrake import build_connection_to_firedrake + fd_connection = build_connection_to_firedrake(discr, group_nr=0) # convert candidate_sol to firedrake fd_candidate_sol = fd_connection.from_meshmode(candidate_sol) # get the firedrake function space diff --git a/meshmode/interop/firedrake/__init__.py b/meshmode/interop/firedrake/__init__.py index f86d9de7..a4bc55a7 100644 --- a/meshmode/interop/firedrake/__init__.py +++ b/meshmode/interop/firedrake/__init__.py @@ -22,12 +22,12 @@ THE SOFTWARE. from meshmode.interop.firedrake.connection import ( - FromBoundaryFiredrakeConnection, FromFiredrakeConnection, - ToFiredrakeConnection) + build_connection_from_firedrake, build_connection_to_firedrake, + FiredrakeConnection) from meshmode.interop.firedrake.mesh import ( import_firedrake_mesh, export_mesh_to_firedrake) -__all__ = ["FromBoundaryFiredrakeConnection", "FromFiredrakeConnection", - "ToFiredrakeConnection", "import_firedrake_mesh", +__all__ = ["build_connection_from_firedrake", "build_connection_to_firedrake", + "FiredrakeConnection", "import_firedrake_mesh", "export_mesh_to_firedrake" ] diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 057dd911..9b77453d 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -22,9 +22,8 @@ THE SOFTWARE. __doc__ = """ .. autoclass:: FiredrakeConnection -.. autoclass:: FromFiredrakeConnection -.. autoclass:: FromBoundaryFiredrakeConnection -.. autoclass:: ToFiredrakeConnection +.. autofunction:: build_connection_to_firedrake +.. autofunction:: build_connection_from_firedrake """ import numpy as np @@ -89,11 +88,12 @@ def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): class FiredrakeConnection: """ A connection between one group of - a meshmode discretization and a firedrake "CG" or "DG" + a meshmode discretization and a firedrake "DG" function space. - Users should instantiate this using a - :class:`FromFiredrakeConnection` or :class:`ToFiredrakeConnection`. + Users should instantiate this using + :func:`build_connection_to_firedrake` + or :func:`build_connection_from_firedrake` .. attribute:: discr @@ -561,169 +561,143 @@ class FiredrakeConnection: # {{{ Create connection from firedrake into meshmode -class FromFiredrakeConnection(FiredrakeConnection): +def build_connection_from_firedrake(actx, fdrake_fspace, grp_factory=None, + restrict_to_boundary=None): + """ - A connection created from a :mod:`firedrake` - ``"CG"`` or ``"DG"`` function space which creates a corresponding - meshmode discretization and allows + Create a :class:`FiredrakeConnection` from a :mod:`firedrake` + ``"DG"`` function space by creates a corresponding + meshmode discretization and facilitating transfer of functions to and from :mod:`firedrake`. - .. automethod:: __init__ - """ - def __init__(self, actx, fdrake_fspace, grp_factory=None): - """ - :arg actx: A :class:`~meshmode.array_context.ArrayContext` - used to instantiate :attr:`FiredrakeConnection.discr`. - :arg fdrake_fspace: A :mod:`firedrake` ``"CG"`` or ``"DG"`` - function space (of class - :class:`~firedrake.functionspaceimpl.WithGeometry`) built on - a mesh which is importable by - :func:`~meshmode.interop.firedrake.mesh.import_firedrake_mesh`. - :arg grp_factory: (optional) If not *None*, should be - a :class:`~meshmode.discretization.poly_element.ElementGroupFactory` - whose group class is a subclass of - :class:`~meshmode.discretization.InterpolatoryElementGroupBase`. - If *None*, and :mod:`recursivenodes` can be imported, - a :class:`~meshmode.discretization.poly_element.\ + :arg actx: A :class:`~meshmode.array_context.ArrayContext` + used to instantiate :attr:`FiredrakeConnection.discr`. + :arg fdrake_fspace: A :mod:`firedrake` ``"DG"`` + function space (of class + :class:`~firedrake.functionspaceimpl.WithGeometry`) built on + a mesh which is importable by + :func:`~meshmode.interop.firedrake.mesh.import_firedrake_mesh`. + :arg grp_factory: (optional) If not *None*, should be + a :class:`~meshmode.discretization.poly_element.ElementGroupFactory` + whose group class is a subclass of + :class:`~meshmode.discretization.InterpolatoryElementGroupBase`. + If *None*, and :mod:`recursivenodes` can be imported, + a :class:`~meshmode.discretization.poly_element.\ PolynomialRecursiveNodesGroupFactory` with ``'lgl'`` nodes is used. - Note that :mod:`recursivenodes` may not be importable - as it uses :func:`math.comb`, which is new in Python 3.8. - In the case that :mod:`recursivenodes` cannot be successfully - imported, a :class:`~meshmode.discretization.poly_element.\ + Note that :mod:`recursivenodes` may not be importable + as it uses :func:`math.comb`, which is new in Python 3.8. + In the case that :mod:`recursivenodes` cannot be successfully + imported, a :class:`~meshmode.discretization.poly_element.\ PolynomialWarpAndBlendGroupFactory` is used. - """ - # Ensure fdrake_fspace is a function space with appropriate reference - # element. - from firedrake.functionspaceimpl import WithGeometry - if not isinstance(fdrake_fspace, WithGeometry): - raise TypeError("'fdrake_fspace' must be of firedrake type " - "WithGeometry, not '%s'." - % type(fdrake_fspace)) - ufl_elt = fdrake_fspace.ufl_element() - - if ufl_elt.family() != 'Discontinuous Lagrange': - raise ValueError("the 'fdrake_fspace.ufl_element().family()' of " - "must be be " - "'Discontinuous Lagrange', not '%s'." - % ufl_elt.family()) - # Make sure grp_factory is the right type if provided, and - # uses an interpolatory class. - if grp_factory is not None: - if not isinstance(grp_factory, ElementGroupFactory): - raise TypeError("'grp_factory' must inherit from " - "meshmode.discretization.ElementGroupFactory," - "but is instead of type " - "'%s'." % type(grp_factory)) - if not issubclass(grp_factory.group_class, - InterpolatoryElementGroupBase): - raise TypeError("'grp_factory.group_class' must inherit from" - "meshmode.discretization." - "InterpolatoryElementGroupBase, but" - " is instead of type '%s'" - % type(grp_factory.group_class)) - # If not provided, make one - else: - degree = ufl_elt.degree() - try: - # recursivenodes is only importable in Python 3.8 since - # it uses :func:`math.comb`, so need to check if it can - # be imported - import recursivenodes # noqa : F401 - family = 'lgl' # L-G-Legendre - grp_factory = PolynomialRecursiveNodesGroupFactory(degree, family) - except ImportError: - # If cannot be imported, uses warp-and-blend nodes - grp_factory = PolynomialWarpAndBlendGroupFactory(degree) - - # In case this class is really a FromBoundaryFiredrakeConnection, - # get *cells_to_use* - cells_to_use = self._get_cells_to_use(fdrake_fspace.mesh()) - # Create to_discr - mm_mesh, orient = import_firedrake_mesh(fdrake_fspace.mesh(), - cells_to_use=cells_to_use) - to_discr = Discretization(actx, mm_mesh, grp_factory) - - # get firedrake unit nodes and map onto meshmode reference element - group = to_discr.groups[0] - fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(group.dim, - True) - fd_unit_nodes = get_finat_element_unit_nodes(fdrake_fspace.finat_element) - fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) - # Flipping negative elements corresponds to reordering the nodes. - # We handle reordering by storing the permutation explicitly as - # a numpy array - - # Get the reordering fd->mm. - flip_mat = get_simplex_element_flip_matrix(ufl_elt.degree(), - fd_unit_nodes) - fd_cell_node_list = fdrake_fspace.cell_node_list - if cells_to_use is not None: - fd_cell_node_list = fd_cell_node_list[cells_to_use] - # flip fd_cell_node_list - flipped_cell_node_list = _reorder_nodes(orient, - fd_cell_node_list, - flip_mat, - unflip=False) - - assert np.size(np.unique(flipped_cell_node_list)) == \ - np.size(flipped_cell_node_list), \ - "A firedrake node in a 'DG' space got duplicated" - super(FromFiredrakeConnection, self).__init__(to_discr, - fdrake_fspace, - flipped_cell_node_list) - - def _get_cells_to_use(self, mesh): - """ - For compatibility with :class:`FromFiredrakeBdyConnection` - """ - return None - - -class FromBoundaryFiredrakeConnection(FromFiredrakeConnection): - """ - A connection created from a :mod:`firedrake` - ``"CG"`` or ``"DG"`` function space which creates a - meshmode discretization corresponding to all cells with at - least one vertex on the given boundary and allows - transfer of functions to and from :mod:`firedrake`. - - Use the same bdy_id as one would for a - :class:`firedrake.bcs.DirichletBC` instance. - ``"on_boundary"`` corresponds to the entire boundary. - - .. attribute:: bdy_id - - the boundary id of the boundary being connecting from - - .. automethod:: __init__ + :arg restrict_to_boundary: (optional) + If not *None*, then must be a valid boundary marker for + ``fdrake_fspace.mesh()``. In this case, creates a + :class:`~meshmode.discretization.Discretization` on a submesh + of ``fdrake_fspace.mesh()`` created from the cells with at least + one vertex on a facet marked with the marker + *restrict_to_boundary*. """ - def __init__(self, actx, fdrake_fspace, bdy_id, grp_factory=None): - """ - :arg bdy_id: A boundary marker of *fdrake_fspace.mesh()* as accepted by - the *boundary_nodes* method of a - :class:`firedrake.functionspaceimpl.WithGeometry`. - - Other arguments are as in - :class:`~meshmode.interop.firedrake.connection.FromFiredrakeConnection`. - """ - self.bdy_id = bdy_id - super(FromBoundaryFiredrakeConnection, self).__init__( - actx, fdrake_fspace, grp_factory=grp_factory) - - def _get_cells_to_use(self, mesh): - """ - Returns an array of the cell ids with >= 1 vertex on the - given bdy_id - """ - cfspace = mesh.coordinates.function_space() + # Ensure fdrake_fspace is a function space with appropriate reference + # element. + from firedrake.functionspaceimpl import WithGeometry + if not isinstance(fdrake_fspace, WithGeometry): + raise TypeError("'fdrake_fspace' must be of firedrake type " + "WithGeometry, not '%s'." + % type(fdrake_fspace)) + ufl_elt = fdrake_fspace.ufl_element() + + if ufl_elt.family() != 'Discontinuous Lagrange': + raise ValueError("the 'fdrake_fspace.ufl_element().family()' of " + "must be be " + "'Discontinuous Lagrange', not '%s'." + % ufl_elt.family()) + # Make sure grp_factory is the right type if provided, and + # uses an interpolatory class. + if grp_factory is not None: + if not isinstance(grp_factory, ElementGroupFactory): + raise TypeError("'grp_factory' must inherit from " + "meshmode.discretization.ElementGroupFactory," + "but is instead of type " + "'%s'." % type(grp_factory)) + if not issubclass(grp_factory.group_class, + InterpolatoryElementGroupBase): + raise TypeError("'grp_factory.group_class' must inherit from" + "meshmode.discretization." + "InterpolatoryElementGroupBase, but" + " is instead of type '%s'" + % type(grp_factory.group_class)) + # If not provided, make one + else: + degree = ufl_elt.degree() + try: + # recursivenodes is only importable in Python 3.8 since + # it uses :func:`math.comb`, so need to check if it can + # be imported + import recursivenodes # noqa : F401 + family = 'lgl' # L-G-Legendre + grp_factory = PolynomialRecursiveNodesGroupFactory(degree, family) + except ImportError: + # If cannot be imported, uses warp-and-blend nodes + grp_factory = PolynomialWarpAndBlendGroupFactory(degree) + if restrict_to_boundary is not None: + uniq_markers = fdrake_fspace.mesh().exterior_facets.unique_markers + allowable_bdy_ids = list(uniq_markers) + ["on_boundary"] + if restrict_to_boundary not in allowable_bdy_ids: + raise ValueError("'restrict_to_boundary' must be one of" + " the following allowable boundary ids: " + f"{allowable_bdy_ids}, not " + f"'{restrict_to_boundary}'") + + # If only converting a portion of the mesh near the boundary, get + # *cells_to_use* as described in + # :func:`meshmode.interop.firedrake.mesh.import_firedrake_mesh` + cells_to_use = None + if restrict_to_boundary is not None: + cfspace = fdrake_fspace.mesh().coordinates.function_space() cell_node_list = cfspace.cell_node_list - boundary_nodes = cfspace.boundary_nodes(self.bdy_id, 'topological') + boundary_nodes = cfspace.boundary_nodes(restrict_to_boundary, + 'topological') # Reduce along each cell: Is a vertex of the cell in boundary nodes? cell_is_near_bdy = np.any(np.isin(cell_node_list, boundary_nodes), axis=1) from pyop2.datatypes import IntType - return np.nonzero(cell_is_near_bdy)[0].astype(IntType) + cells_to_use = np.nonzero(cell_is_near_bdy)[0].astype(IntType) + + # Create to_discr + mm_mesh, orient = import_firedrake_mesh(fdrake_fspace.mesh(), + cells_to_use=cells_to_use) + to_discr = Discretization(actx, mm_mesh, grp_factory) + + # get firedrake unit nodes and map onto meshmode reference element + group = to_discr.groups[0] + fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(group.dim, + True) + fd_unit_nodes = get_finat_element_unit_nodes(fdrake_fspace.finat_element) + fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) + # Flipping negative elements corresponds to reordering the nodes. + # We handle reordering by storing the permutation explicitly as + # a numpy array + + # Get the reordering fd->mm. + flip_mat = get_simplex_element_flip_matrix(ufl_elt.degree(), + fd_unit_nodes) + fd_cell_node_list = fdrake_fspace.cell_node_list + if cells_to_use is not None: + fd_cell_node_list = fd_cell_node_list[cells_to_use] + # flip fd_cell_node_list + flipped_cell_node_list = _reorder_nodes(orient, + fd_cell_node_list, + flip_mat, + unflip=False) + + assert np.size(np.unique(flipped_cell_node_list)) == \ + np.size(flipped_cell_node_list), \ + "A firedrake node in a 'DG' space got duplicated" + + return FiredrakeConnection(to_discr, + fdrake_fspace, + flipped_cell_node_list) # }}} @@ -731,84 +705,80 @@ class FromBoundaryFiredrakeConnection(FromFiredrakeConnection): # {{{ Create connection to firedrake from meshmode -class ToFiredrakeConnection(FiredrakeConnection): +def build_connection_to_firedrake(discr, group_nr=None, comm=None): """ Create a connection from a meshmode discretization into firedrake. Create a corresponding "DG" function space and allow for conversion back and forth by resampling at the nodes. - .. automethod:: __init__ - """ - def __init__(self, discr, group_nr=None, comm=None): - """ - :param discr: A :class:`~meshmode.discretization.Discretization` - to intialize the connection with - :param group_nr: The group number of the discretization to convert. - If *None* there must be only one group. The selected group - must be of type - :class:`~meshmode.discretization.poly_element.\ + :param discr: A :class:`~meshmode.discretization.Discretization` + to intialize the connection with + :param group_nr: The group number of the discretization to convert. + If *None* there must be only one group. The selected group + must be of type + :class:`~meshmode.discretization.poly_element.\ InterpolatoryQuadratureSimplexElementGroup`. - :param comm: Communicator to build a dmplex object on for the created - firedrake mesh - """ - if group_nr is None: - if len(discr.groups) != 1: - raise ValueError("'group_nr' is *None*, but 'discr' has '%s' " - "!= 1 groups." % len(discr.groups)) - group_nr = 0 - el_group = discr.groups[group_nr] - - from firedrake.functionspace import FunctionSpace - fd_mesh, fd_cell_order, perm2cells = \ - export_mesh_to_firedrake(discr.mesh, group_nr, comm) - fspace = FunctionSpace(fd_mesh, 'DG', el_group.order) - # get firedrake unit nodes and map onto meshmode reference element - dim = fspace.mesh().topological_dimension() - fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(dim, True) - fd_unit_nodes = get_finat_element_unit_nodes(fspace.finat_element) - fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) - - # **_cell_node holds the node nrs in shape *(ncells, nunit_nodes)* - fd_cell_node = fspace.cell_node_list - - # To get the meshmode to firedrake node assocation, we need to handle - # local vertex reordering and cell reordering. - from pyop2.datatypes import IntType - mm2fd_node_mapping = np.ndarray((el_group.nelements, el_group.nunit_dofs), - dtype=IntType) - for perm, cells in perm2cells.items(): - # reordering_arr[i] should be the fd node corresponding to meshmode - # node i - # - # The jth meshmode cell corresponds to the fd_cell_order[j]th - # firedrake cell. If *nodeperm* is the permutation of local nodes - # applied to the *j*th meshmode cell, the firedrake node - # fd_cell_node[fd_cell_order[j]][k] corresponds to the - # mm_cell_node[j, nodeperm[k]]th meshmode node. - # - # Note that the permutation on the unit nodes may not be the - # same as the permutation on the barycentric coordinates (*perm*). - # Importantly, the permutation is derived from getting a flip - # matrix from the Firedrake unit nodes, not necessarily the meshmode - # unit nodes - # - flip_mat = get_simplex_element_flip_matrix(el_group.order, - fd_unit_nodes, - np.argsort(perm)) - flip_mat = np.rint(flip_mat).astype(IntType) - fd_permuted_cell_node = np.matmul(fd_cell_node[fd_cell_order[cells]], - flip_mat.T) - mm2fd_node_mapping[cells] = fd_permuted_cell_node - - assert np.size(np.unique(mm2fd_node_mapping)) == \ - np.size(mm2fd_node_mapping), \ - "A firedrake node in a 'DG' space got duplicated" - super(ToFiredrakeConnection, self).__init__(discr, - fspace, - mm2fd_node_mapping, - group_nr=group_nr) + :param comm: Communicator to build a dmplex object on for the created + firedrake mesh + """ + if group_nr is None: + if len(discr.groups) != 1: + raise ValueError("'group_nr' is *None*, but 'discr' has '%s' " + "!= 1 groups." % len(discr.groups)) + group_nr = 0 + el_group = discr.groups[group_nr] + + from firedrake.functionspace import FunctionSpace + fd_mesh, fd_cell_order, perm2cells = \ + export_mesh_to_firedrake(discr.mesh, group_nr, comm) + fspace = FunctionSpace(fd_mesh, 'DG', el_group.order) + # get firedrake unit nodes and map onto meshmode reference element + dim = fspace.mesh().topological_dimension() + fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(dim, True) + fd_unit_nodes = get_finat_element_unit_nodes(fspace.finat_element) + fd_unit_nodes = fd_ref_cell_to_mm(fd_unit_nodes) + + # **_cell_node holds the node nrs in shape *(ncells, nunit_nodes)* + fd_cell_node = fspace.cell_node_list + + # To get the meshmode to firedrake node assocation, we need to handle + # local vertex reordering and cell reordering. + from pyop2.datatypes import IntType + mm2fd_node_mapping = np.ndarray((el_group.nelements, el_group.nunit_dofs), + dtype=IntType) + for perm, cells in perm2cells.items(): + # reordering_arr[i] should be the fd node corresponding to meshmode + # node i + # + # The jth meshmode cell corresponds to the fd_cell_order[j]th + # firedrake cell. If *nodeperm* is the permutation of local nodes + # applied to the *j*th meshmode cell, the firedrake node + # fd_cell_node[fd_cell_order[j]][k] corresponds to the + # mm_cell_node[j, nodeperm[k]]th meshmode node. + # + # Note that the permutation on the unit nodes may not be the + # same as the permutation on the barycentric coordinates (*perm*). + # Importantly, the permutation is derived from getting a flip + # matrix from the Firedrake unit nodes, not necessarily the meshmode + # unit nodes + # + flip_mat = get_simplex_element_flip_matrix(el_group.order, + fd_unit_nodes, + np.argsort(perm)) + flip_mat = np.rint(flip_mat).astype(IntType) + fd_permuted_cell_node = np.matmul(fd_cell_node[fd_cell_order[cells]], + flip_mat.T) + mm2fd_node_mapping[cells] = fd_permuted_cell_node + + assert np.size(np.unique(mm2fd_node_mapping)) == \ + np.size(mm2fd_node_mapping), \ + "A firedrake node in a 'DG' space got duplicated" + return FiredrakeConnection(discr, + fspace, + mm2fd_node_mapping, + group_nr=group_nr) # }}} diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 338bd812..06e6c661 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -39,8 +39,8 @@ from meshmode.dof_array import DOFArray from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL, check_bc_coverage from meshmode.interop.firedrake import ( - FromFiredrakeConnection, FromBoundaryFiredrakeConnection, - ToFiredrakeConnection, import_firedrake_mesh) + build_connection_from_firedrake, build_connection_to_firedrake, + import_firedrake_mesh) import pytest @@ -168,7 +168,7 @@ def check_consistency(fdrake_fspace, discr, group_nr=0): def test_from_fd_consistency(ctx_factory, fdrake_mesh, fspace_degree): """ - Check basic consistency with a FromFiredrakeConnection + Check basic consistency with a FiredrakeConnection built from firedrake """ # make discretization from firedrake fdrake_fspace = FunctionSpace(fdrake_mesh, 'DG', fspace_degree) @@ -177,7 +177,7 @@ def test_from_fd_consistency(ctx_factory, fdrake_mesh, fspace_degree): queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) - fdrake_connection = FromFiredrakeConnection(actx, fdrake_fspace) + fdrake_connection = build_connection_from_firedrake(actx, fdrake_fspace) discr = fdrake_connection.discr # Check consistency check_consistency(fdrake_fspace, discr) @@ -192,7 +192,7 @@ def test_to_fd_consistency(ctx_factory, mm_mesh, fspace_degree): factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) discr = Discretization(actx, mm_mesh, factory) - fdrake_connection = ToFiredrakeConnection(discr) + fdrake_connection = build_connection_to_firedrake(discr) fdrake_fspace = fdrake_connection.firedrake_fspace() # Check consistency check_consistency(fdrake_fspace, discr) @@ -353,7 +353,9 @@ def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values, # }}} -# TODO : Add test for ToFiredrakeConnection where group_nr != 0 +# TODO : Add test for FiredrakeConnection built from meshmode +# where group_nr != 0 + # {{{ Double check functions are being transported correctly @@ -435,11 +437,12 @@ def test_from_fd_transfer(ctx_factory, fspace_degree, # make function space and build connection fdrake_fspace = FunctionSpace(fdrake_mesh, 'DG', fspace_degree) if only_convert_bdy: - fdrake_connection = FromBoundaryFiredrakeConnection(actx, - fdrake_fspace, - 'on_boundary') + fdrake_connection = \ + build_connection_from_firedrake(actx, + fdrake_fspace, + restrict_to_boundary='on_boundary') else: - fdrake_connection = FromFiredrakeConnection(actx, fdrake_fspace) + fdrake_connection = build_connection_from_firedrake(actx, fdrake_fspace) # get this for making functions in firedrake spatial_coord = SpatialCoordinate(fdrake_mesh) @@ -525,7 +528,7 @@ def test_to_fd_transfer(ctx_factory, fspace_degree, mesh_name, mesh_pars, dim): factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) discr = Discretization(actx, mm_mesh, factory) - fdrake_connection = ToFiredrakeConnection(discr) + fdrake_connection = build_connection_to_firedrake(discr) fdrake_fspace = fdrake_connection.firedrake_fspace() spatial_coord = SpatialCoordinate(fdrake_fspace.mesh()) @@ -597,13 +600,14 @@ def test_from_fd_idempotency(ctx_factory, # # Otherwise, just continue as normal if only_convert_bdy: - fdrake_connection = FromBoundaryFiredrakeConnection(actx, - fdrake_fspace, - 'on_boundary') + fdrake_connection = \ + build_connection_from_firedrake(actx, + fdrake_fspace, + restrict_to_boundary='on_boundary') temp = fdrake_connection.from_firedrake(fdrake_unique, actx=actx) fdrake_unique = fdrake_connection.from_meshmode(temp) else: - fdrake_connection = FromFiredrakeConnection(actx, fdrake_fspace) + fdrake_connection = build_connection_from_firedrake(actx, fdrake_fspace) # Test for idempotency fd->mm->fd mm_field = fdrake_connection.from_firedrake(fdrake_unique, actx=actx) @@ -643,7 +647,7 @@ def test_to_fd_idempotency(ctx_factory, mm_mesh, fspace_degree): # Make a function space and a function with unique values at each node factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) discr = Discretization(actx, mm_mesh, factory) - fdrake_connection = ToFiredrakeConnection(discr) + fdrake_connection = build_connection_to_firedrake(discr) fdrake_mesh = fdrake_connection.firedrake_fspace().mesh() dtype = fdrake_mesh.coordinates.dat.data.dtype -- GitLab From 243405256c1de2cff77f7b911fa910e72d6f0b2d Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sun, 16 Aug 2020 16:20:32 -0500 Subject: [PATCH 197/221] Flake8 fixes + separate _get_cells_to_use into a function --- meshmode/interop/firedrake/connection.py | 46 ++++++++++++++++-------- test/test_firedrake_interop.py | 22 ++++++------ 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 9b77453d..5d560bb0 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -73,8 +73,7 @@ def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): np.dot(flip_mat, flip_mat) - np.eye(len(flip_mat))) < 1e-13 - # flip nodes that need to be flipped, note that this point we act - # like we are in a DG space + # flip nodes that need to be flipped flipped_nodes = np.copy(nodes) flipped_nodes[orient < 0] = np.einsum( "ij,ej->ei", @@ -561,6 +560,35 @@ class FiredrakeConnection: # {{{ Create connection from firedrake into meshmode +def _get_cells_to_use(fdrake_mesh, bdy_id): + """ + Return the cell indices of 'fdrake_mesh' which have at least one vertex + coinciding with a facet which is marked with firedrake marker + 'bdy_id'. + + If 'bdy_id' is *None*, returns *None* + + Separated into a function for testing purposes + + :param fdrake_mesh: A mesh as in + :func:`~meshmode.interop.firedrake.mesh.import_firedrake_mesh` + :param bdy_id: As the argument 'restrict_to_boundary' in + :func:`build_connection_from_firedrake` + """ + if bdy_id is None: + return None + + cfspace = fdrake_mesh.coordinates.function_space() + cell_node_list = cfspace.cell_node_list + + boundary_nodes = cfspace.boundary_nodes(bdy_id, 'topological') + # Reduce along each cell: Is a vertex of the cell in boundary nodes? + cell_is_near_bdy = np.any(np.isin(cell_node_list, boundary_nodes), axis=1) + + from pyop2.datatypes import IntType + return np.nonzero(cell_is_near_bdy)[0].astype(IntType) + + def build_connection_from_firedrake(actx, fdrake_fspace, grp_factory=None, restrict_to_boundary=None): @@ -651,18 +679,8 @@ PolynomialWarpAndBlendGroupFactory` is used. # If only converting a portion of the mesh near the boundary, get # *cells_to_use* as described in # :func:`meshmode.interop.firedrake.mesh.import_firedrake_mesh` - cells_to_use = None - if restrict_to_boundary is not None: - cfspace = fdrake_fspace.mesh().coordinates.function_space() - cell_node_list = cfspace.cell_node_list - - boundary_nodes = cfspace.boundary_nodes(restrict_to_boundary, - 'topological') - # Reduce along each cell: Is a vertex of the cell in boundary nodes? - cell_is_near_bdy = np.any(np.isin(cell_node_list, boundary_nodes), axis=1) - - from pyop2.datatypes import IntType - cells_to_use = np.nonzero(cell_is_near_bdy)[0].astype(IntType) + cells_to_use = _get_cells_to_use(fdrake_fspace.mesh(), + restrict_to_boundary) # Create to_discr mm_mesh, orient = import_firedrake_mesh(fdrake_fspace.mesh(), diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 06e6c661..446d6eee 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -200,13 +200,14 @@ def test_to_fd_consistency(ctx_factory, mm_mesh, fspace_degree): # }}} -# {{{ Now check the FromBoundaryFiredrakeConnection consistency +# {{{ Now check the FiredrakeConnection consistency when restricted to bdy def test_from_boundary_consistency(ctx_factory, fdrake_mesh, fspace_degree): """ - Make basic checks that FiredrakeFromBoundaryConnection is not doing + Make basic checks that FiredrakeConnection restricted to cells + near the boundary is not doing something obviously wrong, i.e. that the firedrake boundary tags partition the converted meshmode mesh, that the firedrake boundary tags correspond to the same physical @@ -220,9 +221,10 @@ def test_from_boundary_consistency(ctx_factory, queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) - frombdy_conn = FromBoundaryFiredrakeConnection(actx, - fdrake_fspace, - "on_boundary") + frombdy_conn = \ + build_connection_from_firedrake(actx, + fdrake_fspace, + restrict_to_boundary="on_boundary") # Ensure the meshmode mesh has one group and make sure both # meshes agree on some basic properties @@ -244,7 +246,8 @@ def test_from_boundary_consistency(ctx_factory, fdrake_unit_vert_indices = np.array(fdrake_unit_vert_indices) # only look at cells "near" bdy (with >= 1 vertex on) - cells_near_bdy = frombdy_conn._get_cells_to_use(fdrake_mesh) + from meshmode.interop.firedrake.connection import _get_cells_to_use + cells_near_bdy = _get_cells_to_use(fdrake_mesh, 'on_boundary') # get the firedrake vertices of cells near the boundary, # in no particular order fdrake_vert_indices = \ @@ -304,11 +307,8 @@ def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values, """ cells_to_use = None if only_convert_bdy: - # make a dummy connection which just has a bdy_id - class DummyConnection(FromBoundaryFiredrakeConnection): - def __init__(self): - self.bdy_id = 'on_boundary' - cells_to_use = DummyConnection()._get_cells_to_use(square_or_cube_mesh) + from meshmode.interop.firedrake.connection import _get_cells_to_use + cells_to_use = _get_cells_to_use(square_or_cube_mesh, 'on_boundary') mm_mesh, orient = import_firedrake_mesh(square_or_cube_mesh, cells_to_use=cells_to_use) # Ensure meshmode required boundary tags are there -- GitLab From a16d1c17c0d60c49fcd7f4c6c8b1b628a4c38e98 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sun, 16 Aug 2020 18:07:43 -0500 Subject: [PATCH 198/221] Make sure that any interior facets which become exterior are recorded in the boundary connectiveness grp --- meshmode/interop/firedrake/mesh.py | 41 +++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index b87e91de..1b4d6498 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -297,7 +297,7 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, int_element_faces = int_element_faces[to_keep] int_neighbors = int_neighbors[to_keep] int_neighbor_faces = int_neighbor_faces[to_keep] - # For neighbor cells, change to new cell index or mark + # For neighbor cells, change to new cell index or record # as a new boundary (if the neighbor cell is not being used) no_bdy_neighbor_tag = -(_boundary_tag_bit(bdy_tags, boundary_tag_to_index, @@ -305,12 +305,31 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, | _boundary_tag_bit(bdy_tags, boundary_tag_to_index, BTAG_NO_BOUNDARY)) + newly_created_exterior_facs = [] for ndx, icell in enumerate(int_neighbors): try: int_neighbors[ndx] = cells_to_use_inv[icell] except KeyError: - int_neighbors[ndx] = no_bdy_neighbor_tag - int_neighbor_faces[ndx] = 0 + newly_created_exterior_facs.append(ndx) + # Make boolean array: 1 if a newly created exterior facet, 0 if + # remains an interior facet + newly_created_exterior_facs = np.isin(np.arange(np.size(int_elements)), + newly_created_exterior_facs) + new_ext_elements = int_elements[newly_created_exterior_facs] + new_ext_element_faces = int_element_faces[newly_created_exterior_facs] + new_ext_neighbors = np.full(new_ext_elements.shape, + no_bdy_neighbor_tag, + dtype=IntType) + new_ext_neighbor_faces = np.full(new_ext_elements.shape, + 0, + dtype=Mesh.face_id_dtype) + # Remove any (previously) interior facets that have become exterior + # facets + remaining_int_facs = np.logical_not(newly_created_exterior_facs) + int_elements = int_elements[remaining_int_facs] + int_element_faces = int_element_faces[remaining_int_facs] + int_neighbors = int_neighbors[remaining_int_facs] + int_neighbor_faces = int_neighbor_faces[remaining_int_facs] interconnectivity_grp = FacialAdjacencyGroup(igroup=0, ineighbor_group=0, elements=int_elements, @@ -335,7 +354,7 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, if cells_to_use is not None: to_keep = np.isin(ext_elements, cells_to_use) ext_elements = np.vectorize(cells_to_use_inv.__getitem__)( - ext_elements[to_keep]) + ext_elements[to_keep]) ext_element_faces = ext_element_faces[to_keep] ext_neighbor_faces = ext_neighbor_faces[to_keep] @@ -350,6 +369,17 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, marker_to_neighbor_value[None], dtype=IntType) + # If not using all the cells, some interior facets may have become + # interior facets: + if cells_to_use is not None: + # Record any newly created exterior facets + ext_elements = np.concatenate((ext_elements, new_ext_elements)) + ext_element_faces = np.concatenate((ext_element_faces, + new_ext_element_faces)) + ext_neighbor_faces = np.concatenate((ext_neighbor_faces, + new_ext_neighbor_faces)) + ext_neighbors = np.concatenate((ext_neighbors, new_ext_neighbors)) + exterior_grp = FacialAdjacencyGroup(igroup=0, ineighbor=None, elements=ext_elements, element_faces=ext_element_faces, @@ -690,6 +720,9 @@ FromBoundaryFiredrakeConnection`. cells_to_use=cells_to_use) adj_grps_logger.done() + # TODO: Here the problem is that we moved interior facets to exterior facets, + # so the shapes don't line up.... + # applied below to take elements and element_faces # (or neighbors and neighbor_faces) and flip in any faces that need to # be flipped. -- GitLab From 93415ea15aa5b135449f6d65cff9c92002304253 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sun, 16 Aug 2020 18:17:22 -0500 Subject: [PATCH 199/221] Fix test to handle BTAG_NO_BOUNDARY and flake8 --- examples/to_firedrake.py | 4 ++-- test/test_firedrake_interop.py | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/examples/to_firedrake.py b/examples/to_firedrake.py index fb1a6bef..30e86bcb 100644 --- a/examples/to_firedrake.py +++ b/examples/to_firedrake.py @@ -103,11 +103,11 @@ def main(): sol = Function(cfd_fspace) a = inner(grad(u), grad(v)) * dx - L = Constant(0.0) * v * dx + rhs = Constant(0.0) * v * dx bc_value = project(fd_candidate_sol, cfd_fspace) bc = DirichletBC(cfd_fspace, bc_value, 'on_boundary') params = {'ksp_monitor': None} - solve(a == L, sol, bcs=[bc], solver_parameters=params) + solve(a == rhs, sol, bcs=[bc], solver_parameters=params) # project back into our "DG" space sol = project(sol, fd_fspace) diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 446d6eee..d2b526f0 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -36,7 +36,9 @@ from meshmode.discretization.poly_element import ( from meshmode.dof_array import DOFArray -from meshmode.mesh import BTAG_ALL, BTAG_REALLY_ALL, check_bc_coverage +from meshmode.mesh import ( + BTAG_ALL, BTAG_REALLY_ALL, BTAG_NO_BOUNDARY, check_bc_coverage + ) from meshmode.interop.firedrake import ( build_connection_from_firedrake, build_connection_to_firedrake, @@ -325,6 +327,17 @@ def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values, ext_grp = mm_mesh.facial_adjacency_groups[0][None] for iel, ifac, bdy_flags in zip( ext_grp.elements, ext_grp.element_faces, ext_grp.neighbors): + # try: if mm_mesh has boundaries flagged as not boundaries we need to + # skip them + # catch: if mm_mesh does not use BTAG_NO_BOUNDARY flag we get a + # ValueError + try: + # If this facet is flagged as not really a boundary, skip it + if mm_mesh.boundary_tag_bit(BTAG_NO_BOUNDARY) & -bdy_flags: + continue + except ValueError: + pass + el_vert_indices = mm_mesh.groups[0].vertex_indices[iel] # numpy nb: have to have comma to use advanced indexing face_vert_indices = el_vert_indices[face_vertex_indices[ifac], ] -- GitLab From 4c444e363244d43098922df9c34eae5ecfcfb9af Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sun, 16 Aug 2020 18:30:20 -0500 Subject: [PATCH 200/221] Insist meshes are conforming and improve export_mesh_to_firedrake docs --- meshmode/interop/firedrake/mesh.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 1b4d6498..32e4e7cc 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -779,6 +779,9 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): :param mesh: A :class:`~meshmode.mesh.Mesh` to convert with at least one :class:`~meshmode.mesh.SimplexElementGroup`. + 'mesh.is_conforming' must evaluate to *True*. + 'mesh' must have vertices supplied, i.e. + 'mesh.vertices' must not be *None*. :param group_nr: The group number to be converted into a firedrake mesh. The corresponding group must be of type :class:`~meshmode.mesh.SimplexElementGroup`. If *None* and @@ -834,6 +837,10 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): if mesh.vertices is None: raise ValueError("'mesh' has no vertices " "('mesh.vertices' is *None*)") + if not mesh.is_conforming: + raise ValueError(f"'mesh.is_conforming' is {mesh.is_conforming} " + "instead of *True*. Converting non-conforming " + " meshes to Firedrake is not supported") # Get the vertices and vertex indices of the requested group vertices_logger = ProcessLogger(logger, @@ -921,12 +928,7 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): fspace_logger = ProcessLogger(logger, "Building firedrake function " "space for mesh coordinates") from firedrake import VectorFunctionSpace, Function - if mesh.is_conforming: - family = 'CG' - else: - warn("Non-conforming meshes are untested,... I think they should work") - family = 'DG' - coords_fspace = VectorFunctionSpace(top, family, group.order, + coords_fspace = VectorFunctionSpace(top, 'CG', group.order, dim=mesh.ambient_dim) coords = Function(coords_fspace) fspace_logger.done() -- GitLab From c29cc044d4150659091307d2f4ac3c47854881ab Mon Sep 17 00:00:00 2001 From: benSepanski Date: Sun, 16 Aug 2020 18:43:36 -0500 Subject: [PATCH 201/221] Insist on using CiarletElement to avoid non-nodal dofs --- meshmode/interop/firedrake/reference_cell.py | 23 ++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/meshmode/interop/firedrake/reference_cell.py b/meshmode/interop/firedrake/reference_cell.py index bf642993..8d9ee652 100644 --- a/meshmode/interop/firedrake/reference_cell.py +++ b/meshmode/interop/firedrake/reference_cell.py @@ -119,20 +119,39 @@ def get_finat_element_unit_nodes(finat_element): Returns the unit nodes used by the :mod:`finat` element in firedrake's (equivalently, :mod:`finat`/:mod:`FIAT`'s) reference coordinates - :arg finat_element: A :class:`~finat.finiteelementbase.FiniteElementBase` - instance (i.e. a firedrake function space's reference element). + :arg finat_element: A :class:`finat.finiteelementbase.FiniteElementBase` + instance whose :mod:`FIAT` element is of type + :class:`FIAT.finite_element.CiarletElement` + (i.e. a certain type of reference element used by + firedrake with dofs guaranteed to be nodal) The reference element of the finat element *MUST* be a simplex. :return: A numpy array of shape *(dim, nunit_dofs)* holding the unit nodes used by this element. *dim* is the dimension spanned by the finat element's reference element (see its ``cell`` attribute) """ + from finat.finiteelementbase import FiniteElementBase + from FIAT.finite_element import CiarletElement from FIAT.reference_element import Simplex + if not isinstance(finat_element, FiniteElementBase): + raise TypeError("'finat_element' is of unexpected type " + f"{type(finat_element)}. 'finat_element' must be an " + "instance of finat.finiteelementbase.FiniteElementBase") + if not isinstance(finat_element._element, CiarletElement): + raise TypeError("'finat_element._element' is of unexpected type " + f"{type(finat_element._element)}. " + "'finat_element._element' must be an " + "instance of FIAT.finite_element.CiarletElement") if not isinstance(finat_element.cell, Simplex): raise TypeError("Reference element of the finat element MUST be a" " simplex, i.e. 'finat_element's *cell* attribute must" " be of type FIAT.reference_element.Simplex, not " f"'{type(finat_element.cell)}'") + # We insisted 'finat_element._element' was a + # FIAT.finite_element.CiarletElement, + # so the finat_element._element.dual.nodes ought to represent + # nodal dofs + # # point evaluators is a list of functions *p_0,...,p_{n-1}*. # *p_i(f)* evaluates function *f* at node *i* (stored as a tuple), # so to recover node *i* we need to evaluate *p_i* at the identity -- GitLab From fc7d4d92480707557c45e1009814957f6a4aa3d3 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 18 Aug 2020 14:28:51 -0500 Subject: [PATCH 202/221] Separate firedrake examples into separate job --- .github/workflows/ci.yml | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb136d86..9e11970c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: image: 'firedrakeproject/firedrake' steps: - uses: actions/checkout@v1 - - name: "Dependencies" + - name: "Dependencies" &setup_firedrake_dependencies run: | sudo apt update sudo apt upgrade -y @@ -55,17 +55,26 @@ jobs: # doesn't have dataclasses. pip install pytest dataclasses pip install . - - name: "Examples" - run: | - sudo apt install time - . /home/firedrake/firedrake/bin/activate - . ./.github/workflows/run_firedrake_examples.sh - name: "Test" run: | . /home/firedrake/firedrake/bin/activate cd test python -m pytest --tb=native -rxsw test_firedrake_interop.py + firedrake_examples: + name: Firedrake Examples + runs-on: ubuntu-latest + container: + image: 'firedrakeproject/firedrake' + steps: + - uses: actions/checkout@v1 + - *setup_firedrake_dependencies + - name: "Examples" + run: | + sudo apt install time + . /home/firedrake/firedrake/bin/activate + . ./.github/workflows/run_firedrake_examples.sh + examples3: name: Examples Conda Py3 runs-on: ubuntu-latest -- GitLab From 6dbf4d115f9239cbeb7bd02ee5a98107b1dd9088 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 18 Aug 2020 14:32:11 -0500 Subject: [PATCH 203/221] Try to fix github yaml anchor --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e11970c..c7f9a30e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,8 @@ jobs: image: 'firedrakeproject/firedrake' steps: - uses: actions/checkout@v1 - - name: "Dependencies" &setup_firedrake_dependencies + - &setup_firedrake_dependencies + name: "Dependencies" run: | sudo apt update sudo apt upgrade -y -- GitLab From 3944e47a80e6d80aa8759f5e82c77714d05056ba Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 18 Aug 2020 14:49:37 -0500 Subject: [PATCH 204/221] Fix sphinx errors with inline * --- meshmode/interop/firedrake/mesh.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 32e4e7cc..c7af6f6a 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -772,7 +772,7 @@ FromBoundaryFiredrakeConnection`. # {{{ Mesh exporting to firedrake def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): - """ + r""" Create a firedrake mesh corresponding to one :class:`~meshmode.mesh.Mesh`'s :class:`~meshmode.mesh.SimplexElementGroup`. @@ -795,10 +795,10 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): * *fdrake_mesh* is a :mod:`firedrake` :class:`~firedrake.mesh.MeshGeometry` corresponding to *mesh* - * *fdrake_cell_ordering* is a numpy array: the *i*th - element in *mesh* (i.e. the *i*th element in + * *fdrake_cell_ordering* is a numpy array whose *i*\ th + element in *mesh* (i.e. the *i*\ th element in *mesh.groups[group_nr].vertex_indices*) corresponds to the - *fdrake_cell_ordering[i]*th :mod:`firedrake` cell + *fdrake_cell_ordering[i]*\ th :mod:`firedrake` cell * *perm2cell* is a dictionary, mapping tuples to 1-D numpy arrays of meshmode element indices. Each meshmode element index @@ -806,9 +806,9 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): tuple describes how firedrake reordered the local vertex indices on that cell. In particular, if *c* is in the list *perm2cell[p]* for a tuple *p*, then - the *p[i]*th local vertex of the *fdrake_cell_ordering[c]*th - firedrake cell corresponds to the *i*th local vertex - of the *c*th meshmode element. + the *p[i]*\ th local vertex of the *fdrake_cell_ordering[c]*\ th + firedrake cell corresponds to the *i*\ th local vertex + of the *c*\ th meshmode element. .. warning:: Currently, no custom boundary tags are exported along with the mesh. -- GitLab From 8e6cfa937ec68ab52810ab1c5b84af3cf9f59dbc Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 18 Aug 2020 14:52:27 -0500 Subject: [PATCH 205/221] improve sphinx documentation by fixing inline emphases --- meshmode/interop/firedrake/connection.py | 12 ++++++------ meshmode/interop/firedrake/mesh.py | 18 +++++++++--------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 5d560bb0..7fa349cc 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -85,7 +85,7 @@ def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): # {{{ Most basic connection between a fd function space and mm discretization class FiredrakeConnection: - """ + r""" A connection between one group of a meshmode discretization and a firedrake "DG" function space. @@ -108,9 +108,9 @@ class FiredrakeConnection: Letting *element_grp = self.discr.groups[self.group_nr]*, *mm2fd_node_mapping* is a numpy array of shape *(element_grp.nelements, element_grp.nunit_dofs)* - whose *(i, j)*th entry is the :mod:`firedrake` node - index associated to the *j*th degree of freedom of the - *i*th element in *element_grp*. + whose *(i, j)*\ th entry is the :mod:`firedrake` node + index associated to the *j*\ th degree of freedom of the + *i*\ th element in *element_grp*. :attr:`mm2fd_node_mapping` must encode an embedding into the :mod:`firedrake` mesh, i.e. no two :mod:`meshmode` nodes @@ -353,7 +353,7 @@ class FiredrakeConnection: prefix = f"'{field_name}' is a numpy array of shape " \ f"{field.shape}, which is interpreted as a mapping" \ f" into a space of sahpe {field.shape}. For each " \ - " multi-index *mi*, the *mi*th coordinate values " \ + r" multi-index *mi*, the *mi*\ th coordinate values " \ f" of '{field_name}' should be represented as a " \ f"DOFArray stored in '{field_name}[mi]'. If you are " \ " not trying to represent a mapping into a space of " \ @@ -772,7 +772,7 @@ InterpolatoryQuadratureSimplexElementGroup`. # # The jth meshmode cell corresponds to the fd_cell_order[j]th # firedrake cell. If *nodeperm* is the permutation of local nodes - # applied to the *j*th meshmode cell, the firedrake node + # applied to the *j*\ th meshmode cell, the firedrake node # fd_cell_node[fd_cell_order[j]][k] corresponds to the # mm_cell_node[j, nodeperm[k]]th meshmode node. # diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index c7af6f6a..e695db24 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -398,7 +398,7 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, cells_to_use, normals=None, no_normals_warn=True): - """ + r""" Return the orientations of the mesh elements: :arg fdrake_mesh: As described in :func:`import_firedrake_mesh` @@ -411,11 +411,11 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, :arg cells_to_use: If *None*, then ignored. Otherwise, a numpy array of unique firedrake cell indices indicating which cells to use. - :return: A numpy array, the *i*th element is > 0 if the *i*th element + :return: A numpy array, the *i*\ th element is > 0 if the *i*\ th element is positively oriented, < 0 if negatively oriented. Mesh must have co-dimension 0 or 1. If *cells_to_use* is not - *None*, then the *i*th entry corresponds to the - *cells_to_use[i]*th element. + *None*, then the *i*\ th entry corresponds to the + *cells_to_use[i]*\ th element. """ # compute orientations tdim = fdrake_mesh.topological_dimension() @@ -481,7 +481,7 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, normals=None, no_normals_warn=None): - """ + r""" Create a :class:`meshmode.mesh.Mesh` from a :class:`firedrake.mesh.MeshGeometry` with the same cells/elements, vertices, nodes, @@ -546,8 +546,8 @@ FromBoundaryFiredrakeConnection`. - If *None* then all elements are assumed to be positively oriented. - - Else, should be a list/array whose *i*th entry - is the normal for the *i*th element (*i*th + - Else, should be a list/array whose *i*\ th entry + is the normal for the *i*\ th element (*i*\ th in *mesh.coordinate.function_space()*'s :attr:`cell_node_list`) @@ -891,8 +891,8 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): # Permutations on *n* vertices are stored as a tuple # containing all of the integers *0*, *1*, *2*, ..., *n-1* # exactly once. A permutation *p* - # represents relabeling the *i*th local vertex - # of a meshmode element as the *p[i]*th local vertex + # represents relabeling the *i*\ th local vertex + # of a meshmode element as the *p[i]*\ th local vertex # in the corresponding firedrake cell. # # *perm2cells[p]* is a list of all the meshmode element indices -- GitLab From a2b245177ad6826c54a94daef36055425dd1d7cf Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 18 Aug 2020 15:00:04 -0500 Subject: [PATCH 206/221] Fix doc string --- meshmode/interop/firedrake/mesh.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index e695db24..8c1d1802 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -481,7 +481,7 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, normals=None, no_normals_warn=None): - r""" + """ Create a :class:`meshmode.mesh.Mesh` from a :class:`firedrake.mesh.MeshGeometry` with the same cells/elements, vertices, nodes, @@ -538,16 +538,16 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, instead of :class:`~meshmode.mesh.BTAG_ALL`. This argument is primarily intended for use by a - :class:`~meshmode.interop.firedrake.connection.\ -FromBoundaryFiredrakeConnection`. + :func:`~meshmode.interop.firedrake.connection.\ +build_connection_from_firedrake`. :arg normals: **Only** used if *fdrake_mesh* is a 1-surface embedded in 2-space. In this case, - If *None* then all elements are assumed to be positively oriented. - - Else, should be a list/array whose *i*\ th entry - is the normal for the *i*\ th element (*i*\ th + - Else, should be a list/array whose *i*\\ th entry + is the normal for the *i*\\ th element (*i*\\ th in *mesh.coordinate.function_space()*'s :attr:`cell_node_list`) -- GitLab From dfe431a00b06962629c2537855f85fb22f4f10e1 Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Tue, 18 Aug 2020 15:00:39 -0500 Subject: [PATCH 207/221] Improve comment in meshmode/interop/firedrake/reference_cell.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andreas Klöckner --- meshmode/interop/firedrake/reference_cell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshmode/interop/firedrake/reference_cell.py b/meshmode/interop/firedrake/reference_cell.py index 8d9ee652..993240e7 100644 --- a/meshmode/interop/firedrake/reference_cell.py +++ b/meshmode/interop/firedrake/reference_cell.py @@ -82,7 +82,7 @@ def get_affine_reference_simplex_mapping(ambient_dim, firedrake_to_meshmode=True assert from_verts.shape == to_verts.shape dim, nvects = from_verts.shape - # If only have one vertex, have A = I and b = to_vert - from_vert + # If we only have one vertex, have A = I and b = to_vert - from_vert if nvects == 1: shift = to_verts[:, 0] - from_verts[:, 0] -- GitLab From 0ef0f85bb69f31890942f8504537605d425f4baf Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Tue, 18 Aug 2020 15:00:56 -0500 Subject: [PATCH 208/221] Improve comment in meshmode/interop/firedrake/connection.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andreas Klöckner --- meshmode/interop/firedrake/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index 5d560bb0..6c45073f 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -50,7 +50,7 @@ from pytools import memoize_method def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): """ - return a flipped copy of *nodes* according to *orient* + Return a flipped copy of *nodes* according to *orient*. :arg orient: An array of shape *(nelements)* of orientations, >0 for positive, <0 for negative -- GitLab From 6c2ed346e8be1af7bd87d681b12180eddda34f6f Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 18 Aug 2020 15:06:44 -0500 Subject: [PATCH 209/221] improve comments / names, remove binary file --- meshmode/interop/firedrake/connection.py | 2 +- meshmode/interop/firedrake/laplace-order1.hdf5 | Bin 11896 -> 0 bytes meshmode/interop/firedrake/mesh.py | 14 +++++++------- 3 files changed, 8 insertions(+), 8 deletions(-) delete mode 100644 meshmode/interop/firedrake/laplace-order1.hdf5 diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index afc804f5..4c61113b 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -82,7 +82,7 @@ def _reorder_nodes(orient, nodes, flip_matrix, unflip=False): return flipped_nodes -# {{{ Most basic connection between a fd function space and mm discretization +# {{{ Connection between a fd function space and mm discretization class FiredrakeConnection: r""" diff --git a/meshmode/interop/firedrake/laplace-order1.hdf5 b/meshmode/interop/firedrake/laplace-order1.hdf5 deleted file mode 100644 index 403234a909e7a20f65256a5755bb9717423e44a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11896 zcmeHNJ!~9B6rQtVbBWKye}NDPIQ$r=K?g+O0?8N2j;^qXl^-co>-F6duVUX`_RiLY z21`(w4p9LT5@n!DOJ&MP5dI1XB0^9g(jd7+h+yXDeYf6snpVO)-b2R@3H4;N;(+MWO}y6WX*^>nz`>uPMuy-D!RsBSk&zk|?Tb*}Ho{l+1`?v9X{^he1 zDuNMr!xXp8{lVfGcBC-w0b3rJzR$>2X@4i>fwf*EkM)R;l4AdN57_d++Y~>T|JktY zh0e3vmIp>DZjlFg9K((j#yw!m1DAS?T!Z#^QXcrd-^izVAnxD&g4y!GyA(h0!<4t= z%{%#R%L93eTjT*A$FL)XaSvp~kX{phuf0L{+EF@JCFOy;4;sB`9*FyQzhKA%H)z7k z1glS(MotuD1w~ax21QPu;!AGLbG%9^DEpPm&aA|YT%Qae6GH_}zNj$>%y0B^{#O^>vJKftzTp>_C>$$R|^)ylIP3@4QF2cWRhE~dccqN*8tbEVYP%+Pc#qMGeq;4 z4gJy;NBeQZLfGb2CWCTfn*4ZJjL04BmfdRIU-B@kqjEsy1Et6KA}h|Eo;p7%X&262 zR6sM}CymQjq_V0!_Z}7RVRFBkhO9 zxh8+2y&U{L^JIIuF=9_Y(Ozzt*oDX2%gON9&+P}=%WV&LZQSTx8~^!*J0E`+{I>e# zt;U&*eaeeb5IS^WpkHH~#1=z~6t+2OJLnVsz|3$aRx~KS_>5 z=m%Y$jns3GI?g={@R?_&p5!1m^~hcglN|iXDD$ zw%@-lkze|tQU^+FoZu#8;%KMe@#>5aWw-7s8y14H=Tw4l!JYSC^JF1}o-1Bhkskt7 zO(OJzuth{xdc4pr)%~F2=o;aYS5lpI+pm=UlBabkU#cf>K;k=Te}6<805`v0`IAOP zv1_9-t?RYCk-cke)cbgSG^!(+`cJ%0TbuJ=ZD03`&dKrowrdkj2X5;D+_N~u{^E4t zNJ_vt#QluZcM8XS&o&-5sf~{~w(&Wg{)W)}-zTCUa`;;WaPvo1(Ub=rkUnQx`H`r~_xjnC=#C+OqysDE-ga8nhv`v;$MAfE&WHfi5rg8WbdKgpgX{*eUzC>KHx xeSU4mi#g3l{-Z;9laGEjNQV!r{KEO5MBTP diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 8c1d1802..b5271af5 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -299,12 +299,6 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, int_neighbor_faces = int_neighbor_faces[to_keep] # For neighbor cells, change to new cell index or record # as a new boundary (if the neighbor cell is not being used) - no_bdy_neighbor_tag = -(_boundary_tag_bit(bdy_tags, - boundary_tag_to_index, - BTAG_REALLY_ALL) - | _boundary_tag_bit(bdy_tags, - boundary_tag_to_index, - BTAG_NO_BOUNDARY)) newly_created_exterior_facs = [] for ndx, icell in enumerate(int_neighbors): try: @@ -317,8 +311,14 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, newly_created_exterior_facs) new_ext_elements = int_elements[newly_created_exterior_facs] new_ext_element_faces = int_element_faces[newly_created_exterior_facs] + new_ext_neighbor_tag = -(_boundary_tag_bit(bdy_tags, + boundary_tag_to_index, + BTAG_REALLY_ALL) + | _boundary_tag_bit(bdy_tags, + boundary_tag_to_index, + BTAG_NO_BOUNDARY)) new_ext_neighbors = np.full(new_ext_elements.shape, - no_bdy_neighbor_tag, + new_ext_neighbor_tag, dtype=IntType) new_ext_neighbor_faces = np.full(new_ext_elements.shape, 0, -- GitLab From a6c87321eb44279e227c09944ba03d85696faa26 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 18 Aug 2020 15:19:09 -0500 Subject: [PATCH 210/221] Use ProcessLogger as a context manager --- meshmode/interop/firedrake/mesh.py | 422 ++++++++++++++--------------- 1 file changed, 196 insertions(+), 226 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index b5271af5..05f8a7d0 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -598,106 +598,97 @@ build_connection_from_firedrake`. _get_firedrake_boundary_tags(fdrake_mesh, include_no_boundary=include_no_boundary) - nodal_info_logger = ProcessLogger(logger, "Retrieving vertex indices and " - "computing NodalAdjacency from " - "firedrake mesh") - vertex_indices, nodal_adjacency = \ - _get_firedrake_nodal_info(fdrake_mesh, cells_to_use=cells_to_use) - - # If only using some cells, vertices may need new indices as many - # will be removed - if cells_to_use is not None: - vert_ndx_new2old = np.unique(vertex_indices.flatten()) - vert_ndx_old2new = dict(zip(vert_ndx_new2old, - np.arange(np.size(vert_ndx_new2old), - dtype=vertex_indices.dtype))) - vertex_indices = \ - np.vectorize(vert_ndx_old2new.__getitem__)(vertex_indices) - - nodal_info_logger.done() - - unflipped_group_logger = ProcessLogger(logger, "Building (possibly) " - "unflipped SimplexElementGroup " - "from firedrake unit nodes/nodes") - - # Grab the mesh reference element and cell dimension - coord_finat_elt = fdrake_mesh.coordinates.function_space().finat_element - cell_dim = fdrake_mesh.cell_dimension() - - # Get finat unit nodes and map them onto the meshmode reference simplex - finat_unit_nodes = get_finat_element_unit_nodes(coord_finat_elt) - fd_ref_to_mm = get_affine_reference_simplex_mapping(cell_dim, True) - finat_unit_nodes = fd_ref_to_mm(finat_unit_nodes) - - # Now grab the nodes - coords = fdrake_mesh.coordinates - cell_node_list = coords.function_space().cell_node_list - if cells_to_use is not None: - cell_node_list = cell_node_list[cells_to_use] - nodes = np.real(coords.dat.data[cell_node_list]) - # Add extra dim in 1D for shape (nelements, nunit_nodes, dim) - if tdim == 1: - nodes = np.reshape(nodes, nodes.shape + (1,)) - # Transpose nodes to have shape (dim, nelements, nunit_nodes) - nodes = np.transpose(nodes, (2, 0, 1)) - - # make a group (possibly with some elements that need to be flipped) - unflipped_group = SimplexElementGroup(coord_finat_elt.degree, - vertex_indices, - nodes, - dim=cell_dim, - unit_nodes=finat_unit_nodes) - - unflipped_group_logger.done() + with ProcessLogger(logger, "Retrieving vertex indices and computing " + "NodalAdjacency from firedrake mesh"): + vertex_indices, nodal_adjacency = \ + _get_firedrake_nodal_info(fdrake_mesh, cells_to_use=cells_to_use) + + # If only using some cells, vertices may need new indices as many + # will be removed + if cells_to_use is not None: + vert_ndx_new2old = np.unique(vertex_indices.flatten()) + vert_ndx_old2new = dict(zip(vert_ndx_new2old, + np.arange(np.size(vert_ndx_new2old), + dtype=vertex_indices.dtype))) + vertex_indices = \ + np.vectorize(vert_ndx_old2new.__getitem__)(vertex_indices) + + with ProcessLogger(logger, "Building (possibly) unflipped " + "SimplexElementGroup from firedrake unit nodes/nodes"): + + # Grab the mesh reference element and cell dimension + coord_finat_elt = fdrake_mesh.coordinates.function_space().finat_element + cell_dim = fdrake_mesh.cell_dimension() + + # Get finat unit nodes and map them onto the meshmode reference simplex + finat_unit_nodes = get_finat_element_unit_nodes(coord_finat_elt) + fd_ref_to_mm = get_affine_reference_simplex_mapping(cell_dim, True) + finat_unit_nodes = fd_ref_to_mm(finat_unit_nodes) + + # Now grab the nodes + coords = fdrake_mesh.coordinates + cell_node_list = coords.function_space().cell_node_list + if cells_to_use is not None: + cell_node_list = cell_node_list[cells_to_use] + nodes = np.real(coords.dat.data[cell_node_list]) + # Add extra dim in 1D for shape (nelements, nunit_nodes, dim) + if tdim == 1: + nodes = np.reshape(nodes, nodes.shape + (1,)) + # Transpose nodes to have shape (dim, nelements, nunit_nodes) + nodes = np.transpose(nodes, (2, 0, 1)) + + # make a group (possibly with some elements that need to be flipped) + unflipped_group = SimplexElementGroup(coord_finat_elt.degree, + vertex_indices, + nodes, + dim=cell_dim, + unit_nodes=finat_unit_nodes) # Next get the vertices (we'll need these for the orientations) - vertices_logger = ProcessLogger(logger, "Obtaining vertex coordinates") - - coord_finat = fdrake_mesh.coordinates.function_space().finat_element - # unit_vertex_indices are the element-local indices of the nodes - # which coincide with the vertices, i.e. for element *i*, - # vertex 0's coordinates would be nodes[i][unit_vertex_indices[0]]. - # This assumes each vertex has some node which coincides with it... - # which is normally fine to assume for firedrake meshes. - unit_vertex_indices = [] - # iterate through the dofs associated to each vertex on the - # reference element - for _, dofs in sorted(coord_finat.entity_dofs()[0].items()): - assert len(dofs) == 1, \ - "The function space of the mesh coordinates must have" \ - " exactly one degree of freedom associated with " \ - " each vertex in order to determine vertex coordinates" - dof, = dofs - unit_vertex_indices.append(dof) - - # Now get the vertex coordinates as *(dim, nvertices)*-shaped array - if cells_to_use is not None: - nvertices = np.size(vert_ndx_new2old) - else: - nvertices = fdrake_mesh.num_vertices() - vertices = np.ndarray((gdim, nvertices), dtype=nodes.dtype) - recorded_verts = set() - for icell, cell_vertex_indices in enumerate(vertex_indices): - for local_vert_id, global_vert_id in enumerate(cell_vertex_indices): - if global_vert_id not in recorded_verts: - recorded_verts.add(global_vert_id) - local_node_nr = unit_vertex_indices[local_vert_id] - vertices[:, global_vert_id] = nodes[:, icell, local_node_nr] - - vertices_logger.done() + with ProcessLogger(logger, "Obtaining vertex coordinates"): + coord_finat = fdrake_mesh.coordinates.function_space().finat_element + # unit_vertex_indices are the element-local indices of the nodes + # which coincide with the vertices, i.e. for element *i*, + # vertex 0's coordinates would be nodes[i][unit_vertex_indices[0]]. + # This assumes each vertex has some node which coincides with it... + # which is normally fine to assume for firedrake meshes. + unit_vertex_indices = [] + # iterate through the dofs associated to each vertex on the + # reference element + for _, dofs in sorted(coord_finat.entity_dofs()[0].items()): + assert len(dofs) == 1, \ + "The function space of the mesh coordinates must have" \ + " exactly one degree of freedom associated with " \ + " each vertex in order to determine vertex coordinates" + dof, = dofs + unit_vertex_indices.append(dof) + + # Now get the vertex coordinates as *(dim, nvertices)*-shaped array + if cells_to_use is not None: + nvertices = np.size(vert_ndx_new2old) + else: + nvertices = fdrake_mesh.num_vertices() + vertices = np.ndarray((gdim, nvertices), dtype=nodes.dtype) + recorded_verts = set() + for icell, cell_vertex_indices in enumerate(vertex_indices): + for local_vert_id, global_vert_id in enumerate(cell_vertex_indices): + if global_vert_id not in recorded_verts: + recorded_verts.add(global_vert_id) + local_node_nr = unit_vertex_indices[local_vert_id] + vertices[:, global_vert_id] = nodes[:, icell, local_node_nr] # Use the vertices to compute the orientations and flip the group - orientation_logger = ProcessLogger(logger, "Computing cell orientations") - orient = _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, - cells_to_use=cells_to_use, - normals=normals, - no_normals_warn=no_normals_warn) - orientation_logger.done() - - flipped_grp_logger = ProcessLogger(logger, "Flipping group") - from meshmode.mesh.processing import flip_simplex_element_group - group = flip_simplex_element_group(vertices, unflipped_group, orient < 0) - flipped_grp_logger.done() + with ProcessLogger(logger, "Computing cell orientations"): + orient = _get_firedrake_orientations(fdrake_mesh, + unflipped_group, + vertices, + cells_to_use=cells_to_use, + normals=normals, + no_normals_warn=no_normals_warn) + + with ProcessLogger(logger, "Flipping group"): + from meshmode.mesh.processing import flip_simplex_element_group + group = flip_simplex_element_group(vertices, unflipped_group, orient < 0) # Now, any flipped element had its 0 vertex and 1 vertex exchanged. # This changes the local facet nr, so we need to create and then @@ -713,15 +704,11 @@ build_connection_from_firedrake`. elif 1 not in face: no_one_face_ndx = iface - adj_grps_logger = ProcessLogger(logger, "Building (possibly) unflipped " - "FacialAdjacencyGroups") - unflipped_facial_adjacency_groups = \ - _get_firedrake_facial_adjacency_groups(fdrake_mesh, - cells_to_use=cells_to_use) - adj_grps_logger.done() - - # TODO: Here the problem is that we moved interior facets to exterior facets, - # so the shapes don't line up.... + with ProcessLogger(logger, "Building (possibly) unflipped " + "FacialAdjacencyGroups"): + unflipped_facial_adjacency_groups = \ + _get_firedrake_facial_adjacency_groups(fdrake_mesh, + cells_to_use=cells_to_use) # applied below to take elements and element_faces # (or neighbors and neighbor_faces) and flip in any faces that need to @@ -739,26 +726,22 @@ build_connection_from_firedrake`. return faces # Create new facial adjacency groups that have been flipped - flip_adj_grps_logger = ProcessLogger(logger, - "Flipping FacialAdjacencyGroups") - - facial_adjacency_groups = [] - for igroup, fagrps in enumerate(unflipped_facial_adjacency_groups): - facial_adjacency_groups.append({}) - for ineighbor_group, fagrp in fagrps.items(): - new_element_faces = flip_local_face_indices(fagrp.element_faces, - fagrp.elements) - new_neighbor_faces = flip_local_face_indices(fagrp.neighbor_faces, - fagrp.neighbors) - new_fagrp = FacialAdjacencyGroup(igroup=igroup, - ineighbor_group=ineighbor_group, - elements=fagrp.elements, - element_faces=new_element_faces, - neighbors=fagrp.neighbors, - neighbor_faces=new_neighbor_faces) - facial_adjacency_groups[igroup][ineighbor_group] = new_fagrp - - flip_adj_grps_logger.done() + with ProcessLogger(logger, "Flipping FacialAdjacencyGroups"): + facial_adjacency_groups = [] + for igroup, fagrps in enumerate(unflipped_facial_adjacency_groups): + facial_adjacency_groups.append({}) + for ineighbor_group, fagrp in fagrps.items(): + new_element_faces = flip_local_face_indices(fagrp.element_faces, + fagrp.elements) + new_neighbor_faces = flip_local_face_indices(fagrp.neighbor_faces, + fagrp.neighbors) + new_fagrp = FacialAdjacencyGroup(igroup=igroup, + ineighbor_group=ineighbor_group, + elements=fagrp.elements, + element_faces=new_element_faces, + neighbors=fagrp.neighbors, + neighbor_faces=new_neighbor_faces) + facial_adjacency_groups[igroup][ineighbor_group] = new_fagrp return (Mesh(vertices, [group], boundary_tags=bdy_tags, @@ -843,95 +826,87 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): " meshes to Firedrake is not supported") # Get the vertices and vertex indices of the requested group - vertices_logger = ProcessLogger(logger, - "Obtaining vertices from selected group") - group = mesh.groups[group_nr] - fd2mm_indices = np.unique(group.vertex_indices.flatten()) - coords = mesh.vertices[:, fd2mm_indices].T - mm2fd_indices = dict(zip(fd2mm_indices, np.arange(np.size(fd2mm_indices)))) - cells = np.vectorize(mm2fd_indices.__getitem__)(group.vertex_indices) - - vertices_logger.done() + with ProcessLogger(logger, "Obtaining vertices from selected group"): + group = mesh.groups[group_nr] + fd2mm_indices = np.unique(group.vertex_indices.flatten()) + coords = mesh.vertices[:, fd2mm_indices].T + mm2fd_indices = dict(zip(fd2mm_indices, np.arange(np.size(fd2mm_indices)))) + cells = np.vectorize(mm2fd_indices.__getitem__)(group.vertex_indices) # Get a dmplex object and then a mesh topology - top_logger = ProcessLogger(logger, "Building dmplex object and MeshTopology") - if comm is None: - from pyop2.mpi import COMM_WORLD - comm = COMM_WORLD - # FIXME : not sure how to get around the private accesses - import firedrake.mesh as fd_mesh - plex = fd_mesh._from_cell_list(group.dim, cells, coords, comm) - # Nb : One might be tempted to pass reorder=False and thereby save some - # hassle in exchange for forcing firedrake to have slightly - # less efficient caching. Unfortunately, that only prevents - # the cells from being reordered, and does not prevent the - # vertices from being (locally) reordered on each cell... - # the tl;dr is we don't actually save any hassle - top = fd_mesh.Mesh(plex, dim=mesh.ambient_dim) # mesh topology - top.init() - - top_logger.done() + with ProcessLogger(logger, "Building dmplex object and MeshTopology"): + if comm is None: + from pyop2.mpi import COMM_WORLD + comm = COMM_WORLD + # FIXME : not sure how to get around the private accesses + import firedrake.mesh as fd_mesh + plex = fd_mesh._from_cell_list(group.dim, cells, coords, comm) + # Nb : One might be tempted to pass reorder=False and thereby save some + # hassle in exchange for forcing firedrake to have slightly + # less efficient caching. Unfortunately, that only prevents + # the cells from being reordered, and does not prevent the + # vertices from being (locally) reordered on each cell... + # the tl;dr is we don't actually save any hassle + top = fd_mesh.Mesh(plex, dim=mesh.ambient_dim) # mesh topology + top.init() # Get new element ordering: - perm_logger = ProcessLogger(logger, "Determining permutations applied" - " to local vertex numbers") - - c_start, c_end = top._topology_dm.getHeightStratum(0) - cell_index_mm2fd = np.vectorize(top._cell_numbering.getOffset)( - np.arange(c_start, c_end)) - v_start, v_end = top._topology_dm.getDepthStratum(0) - - # Firedrake goes crazy reordering local vertex numbers, - # we've got to work to figure out what changes they made. - # - # *perm2cells* will map permutations of local vertex numbers to - # the list of all the meshmode cells - # which firedrake reordered according to that permutation - # - # Permutations on *n* vertices are stored as a tuple - # containing all of the integers *0*, *1*, *2*, ..., *n-1* - # exactly once. A permutation *p* - # represents relabeling the *i*\ th local vertex - # of a meshmode element as the *p[i]*\ th local vertex - # in the corresponding firedrake cell. - # - # *perm2cells[p]* is a list of all the meshmode element indices - # for which *p* represents the reordering applied by firedrake - perm2cells = {} - for mm_cell_id, dmp_ids in enumerate(top.cell_closure[cell_index_mm2fd]): - # look at order of vertices in firedrake cell - vert_dmp_ids = dmp_ids[np.logical_and(v_start <= dmp_ids, dmp_ids < v_end)] - fdrake_order = vert_dmp_ids - v_start - # get original order - mm_order = mesh.groups[group_nr].vertex_indices[mm_cell_id] - # want permutation p so that mm_order[p] = fdrake_order - # To do so, look at permutations acting by composition. + with ProcessLogger(logger, "Determining permutations applied" + " to local vertex numbers"): + c_start, c_end = top._topology_dm.getHeightStratum(0) + cell_index_mm2fd = np.vectorize(top._cell_numbering.getOffset)( + np.arange(c_start, c_end)) + v_start, v_end = top._topology_dm.getDepthStratum(0) + + # Firedrake goes crazy reordering local vertex numbers, + # we've got to work to figure out what changes they made. # - # mm_order \circ argsort(mm_order) = - # fdrake_order \circ argsort(fdrake_order) - # so - # mm_order \circ argsort(mm_order) \circ inv(argsort(fdrake_order)) - # = fdrake_order + # *perm2cells* will map permutations of local vertex numbers to + # the list of all the meshmode cells + # which firedrake reordered according to that permutation # - # argsort acts as an inverse, so the desired permutation is: - perm = tuple(np.argsort(mm_order)[np.argsort(np.argsort(fdrake_order))]) - perm2cells.setdefault(perm, []) - perm2cells[perm].append(mm_cell_id) - - # Make perm2cells map to numpy arrays instead of lists - perm2cells = {perm: np.array(cells) - for perm, cells in perm2cells.items()} - - perm_logger.done() + # Permutations on *n* vertices are stored as a tuple + # containing all of the integers *0*, *1*, *2*, ..., *n-1* + # exactly once. A permutation *p* + # represents relabeling the *i*\ th local vertex + # of a meshmode element as the *p[i]*\ th local vertex + # in the corresponding firedrake cell. + # + # *perm2cells[p]* is a list of all the meshmode element indices + # for which *p* represents the reordering applied by firedrake + perm2cells = {} + for mm_cell_id, dmp_ids in enumerate(top.cell_closure[cell_index_mm2fd]): + # look at order of vertices in firedrake cell + vert_dmp_ids = \ + dmp_ids[np.logical_and(v_start <= dmp_ids, dmp_ids < v_end)] + fdrake_order = vert_dmp_ids - v_start + # get original order + mm_order = mesh.groups[group_nr].vertex_indices[mm_cell_id] + # want permutation p so that mm_order[p] = fdrake_order + # To do so, look at permutations acting by composition. + # + # mm_order \circ argsort(mm_order) = + # fdrake_order \circ argsort(fdrake_order) + # so + # mm_order \circ argsort(mm_order) \circ inv(argsort(fdrake_order)) + # = fdrake_order + # + # argsort acts as an inverse, so the desired permutation is: + perm = tuple(np.argsort(mm_order)[np.argsort(np.argsort(fdrake_order))]) + perm2cells.setdefault(perm, []) + perm2cells[perm].append(mm_cell_id) + + # Make perm2cells map to numpy arrays instead of lists + perm2cells = {perm: np.array(cells) + for perm, cells in perm2cells.items()} # Now make a coordinates function - fspace_logger = ProcessLogger(logger, "Building firedrake function " - "space for mesh coordinates") - from firedrake import VectorFunctionSpace, Function - coords_fspace = VectorFunctionSpace(top, 'CG', group.order, - dim=mesh.ambient_dim) - coords = Function(coords_fspace) - fspace_logger.done() + with ProcessLogger(logger, "Building firedrake function " + "space for mesh coordinates"): + from firedrake import VectorFunctionSpace, Function + coords_fspace = VectorFunctionSpace(top, 'CG', group.order, + dim=mesh.ambient_dim) + coords = Function(coords_fspace) # get firedrake unit nodes and map onto meshmode reference element fd_ref_cell_to_mm = get_affine_reference_simplex_mapping(group.dim, True) @@ -948,28 +923,23 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): # Now put the nodes in the right local order # nodes is shaped *(ambient dim, nelements, nunit nodes)* - undo_perm_logger = ProcessLogger(logger, "Storing meshmode mesh coordinates" - " in firedrake nodal order") - - from meshmode.mesh.processing import get_simplex_element_flip_matrix - for perm, cells in perm2cells.items(): - flip_mat = get_simplex_element_flip_matrix(group.order, - fd_unit_nodes, - perm) - flip_mat = np.rint(flip_mat).astype(np.int32) - resampled_group_nodes[:, cells, :] = \ - np.matmul(resampled_group_nodes[:, cells, :], flip_mat.T) - - undo_perm_logger.done() + with ProcessLogger(logger, "Storing meshmode mesh coordinates" + " in firedrake nodal order"): + from meshmode.mesh.processing import get_simplex_element_flip_matrix + for perm, cells in perm2cells.items(): + flip_mat = get_simplex_element_flip_matrix(group.order, + fd_unit_nodes, + perm) + flip_mat = np.rint(flip_mat).astype(np.int32) + resampled_group_nodes[:, cells, :] = \ + np.matmul(resampled_group_nodes[:, cells, :], flip_mat.T) # store resampled data in right cell ordering - resample_logger = ProcessLogger(logger, "resampling mesh coordinates to " - "firedrake unit nodes") - reordered_cell_node_list = coords_fspace.cell_node_list[cell_index_mm2fd] - coords.dat.data[reordered_cell_node_list, :] = \ - resampled_group_nodes.transpose((1, 2, 0)) - - resample_logger.done() + with ProcessLogger(logger, "resampling mesh coordinates to " + "firedrake unit nodes"): + reordered_cell_node_list = coords_fspace.cell_node_list[cell_index_mm2fd] + coords.dat.data[reordered_cell_node_list, :] = \ + resampled_group_nodes.transpose((1, 2, 0)) return fd_mesh.Mesh(coords), cell_index_mm2fd, perm2cells -- GitLab From 2de258f9b2677b4a8c09c4bcd1a2662e6bb7ac8b Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 18 Aug 2020 15:23:41 -0500 Subject: [PATCH 211/221] Duplicate firedrake setup in github yaml --- .github/workflows/ci.yml | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7f9a30e..3a76e942 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,8 +40,10 @@ jobs: image: 'firedrakeproject/firedrake' steps: - uses: actions/checkout@v1 - - &setup_firedrake_dependencies - name: "Dependencies" + # This Dependencies setup is the same setup used for running firedrake + # examples, but had to be copied since github doesn't support yaml + # anchors. Changes made here should also be made there + - name: "Dependencies" run: | sudo apt update sudo apt upgrade -y @@ -69,10 +71,26 @@ jobs: image: 'firedrakeproject/firedrake' steps: - uses: actions/checkout@v1 - - *setup_firedrake_dependencies + # This Dependencies setup is the same setup used for running firedrake + # tests, but had to be copied since github doesn't support yaml + # anchors. Changes made here should also be made there + - name: "Dependencies" + run: | + sudo apt update + sudo apt upgrade -y + sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev + sudo chown -R $(whoami) /github/home + + . /home/firedrake/firedrake/bin/activate + grep -v loopy requirements.txt > /tmp/myreq.txt + pip install -r /tmp/myreq.txt + pip install --force-reinstall git+https://github.com/benSepanski/loopy.git@firedrake-usable_for_potentials + # The Firedrake container is based on Py3.6 as of 2020-10-10, which + # doesn't have dataclasses. + pip install pytest dataclasses + pip install . - name: "Examples" run: | - sudo apt install time . /home/firedrake/firedrake/bin/activate . ./.github/workflows/run_firedrake_examples.sh -- GitLab From 9b1d21b67df7a758d693715933155871b2a0f130 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 18 Aug 2020 15:31:47 -0500 Subject: [PATCH 212/221] Separate examples from tests for gitlab, fix names --- .github/workflows/ci.yml | 2 +- .gitlab-ci.yml | 27 +++++++++++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a76e942..b92efb43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,7 +65,7 @@ jobs: python -m pytest --tb=native -rxsw test_firedrake_interop.py firedrake_examples: - name: Firedrake Examples + name: Examples Firedrake runs-on: ubuntu-latest container: image: 'firedrakeproject/firedrake' diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4014ec40..2bb1b39a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -55,8 +55,7 @@ Python 3 POCL Firedrake: image: "firedrakeproject/firedrake" script: - sudo apt update - - sudo apt upgrade - - sudo apt install time + - sudo apt upgrade -y - sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev - source ~/firedrake/bin/activate - "grep -v loopy requirements.txt > myreq.txt" @@ -66,8 +65,6 @@ Python 3 POCL Firedrake: # doesn't have dataclasses. - pip install pytest dataclasses - pip install . - # run examples - - . ./.github/workflows/run_firedrake_examples.sh # run tests - cd test - python -m pytest --tb=native --junitxml=pytest.xml -rxsw test_firedrake_interop.py @@ -75,6 +72,28 @@ Python 3 POCL Firedrake: reports: junit: test/pytest.xml +Python 3 POCL Firedrake Examples: + tags: + - "docker-runner" + image: "firedrakeproject/firedrake" + script: + - sudo apt update + - sudo apt upgrade -y + - sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev + - source ~/firedrake/bin/activate + - "grep -v loopy requirements.txt > myreq.txt" + - pip install -r myreq.txt + - pip install --force-reinstall git+https://github.com/benSepanski/loopy.git@firedrake-usable_for_potentials + # The Firedrake container is based on Py3.6 as of 2020-10-10, which + # doesn't have dataclasses. + - pip install pytest dataclasses + - pip install . + # run examples + - . ./.github/workflows/run_firedrake_examples.sh + artifacts: + reports: + junit: test/pytest.xml + Python 3 Conda: script: | CONDA_ENVIRONMENT=.test-conda-env-py3.yml -- GitLab From 7f4239349d1185ac2d0998fb8b71a46db5abaf91 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 18 Aug 2020 15:37:51 -0500 Subject: [PATCH 213/221] Install time again since using sh not bash --- .github/workflows/ci.yml | 2 ++ .gitlab-ci.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b92efb43..94eb5fae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,7 @@ jobs: run: | sudo apt update sudo apt upgrade -y + sudo apt install time sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev sudo chown -R $(whoami) /github/home @@ -78,6 +79,7 @@ jobs: run: | sudo apt update sudo apt upgrade -y + sudo apt install time sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev sudo chown -R $(whoami) /github/home diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2bb1b39a..6695c4e1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,6 +56,7 @@ Python 3 POCL Firedrake: script: - sudo apt update - sudo apt upgrade -y + - sudo apt install time - sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev - source ~/firedrake/bin/activate - "grep -v loopy requirements.txt > myreq.txt" @@ -79,6 +80,7 @@ Python 3 POCL Firedrake Examples: script: - sudo apt update - sudo apt upgrade -y + - sudo apt install time - sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev - source ~/firedrake/bin/activate - "grep -v loopy requirements.txt > myreq.txt" -- GitLab From 79179faab794f658a835a02225a40b87df4f483b Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 18 Aug 2020 16:02:46 -0500 Subject: [PATCH 214/221] Try using anchors for gitlab --- .gitlab-ci.yml | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6695c4e1..83f54480 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -54,18 +54,19 @@ Python 3 POCL Firedrake: - "docker-runner" image: "firedrakeproject/firedrake" script: - - sudo apt update - - sudo apt upgrade -y - - sudo apt install time - - sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev - - source ~/firedrake/bin/activate - - "grep -v loopy requirements.txt > myreq.txt" - - pip install -r myreq.txt - - pip install --force-reinstall git+https://github.com/benSepanski/loopy.git@firedrake-usable_for_potentials - # The Firedrake container is based on Py3.6 as of 2020-10-10, which - # doesn't have dataclasses. - - pip install pytest dataclasses - - pip install . + - &firedrake_setup + sudo apt update + sudo apt upgrade -y + sudo apt install time + sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev + source ~/firedrake/bin/activate + "grep -v loopy requirements.txt > myreq.txt" + pip install -r myreq.txt + pip install --force-reinstall git+https://github.com/benSepanski/loopy.git@firedrake-usable_for_potentials + # The Firedrake container is based on Py3.6 as of 2020-10-10, which + # doesn't have dataclasses. + pip install pytest dataclasses + pip install . # run tests - cd test - python -m pytest --tb=native --junitxml=pytest.xml -rxsw test_firedrake_interop.py @@ -78,18 +79,7 @@ Python 3 POCL Firedrake Examples: - "docker-runner" image: "firedrakeproject/firedrake" script: - - sudo apt update - - sudo apt upgrade -y - - sudo apt install time - - sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev - - source ~/firedrake/bin/activate - - "grep -v loopy requirements.txt > myreq.txt" - - pip install -r myreq.txt - - pip install --force-reinstall git+https://github.com/benSepanski/loopy.git@firedrake-usable_for_potentials - # The Firedrake container is based on Py3.6 as of 2020-10-10, which - # doesn't have dataclasses. - - pip install pytest dataclasses - - pip install . + - *firedrake_setup # run examples - . ./.github/workflows/run_firedrake_examples.sh artifacts: -- GitLab From e253d210fd1c4e9258186a98cdad3a0b9de13153 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 18 Aug 2020 16:04:42 -0500 Subject: [PATCH 215/221] Fix gitlab yml syntax error --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 83f54480..2fb77a41 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -54,7 +54,7 @@ Python 3 POCL Firedrake: - "docker-runner" image: "firedrakeproject/firedrake" script: - - &firedrake_setup + - &firedrake_setup | sudo apt update sudo apt upgrade -y sudo apt install time -- GitLab From c8559fdb9184ab36068c17ffac2f6f2295d3c723 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Tue, 18 Aug 2020 16:13:22 -0500 Subject: [PATCH 216/221] Try fixing gitlab ci --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2fb77a41..715fded3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -60,7 +60,7 @@ Python 3 POCL Firedrake: sudo apt install time sudo apt install -y pocl-opencl-icd ocl-icd-opencl-dev source ~/firedrake/bin/activate - "grep -v loopy requirements.txt > myreq.txt" + grep -v loopy requirements.txt > myreq.txt pip install -r myreq.txt pip install --force-reinstall git+https://github.com/benSepanski/loopy.git@firedrake-usable_for_potentials # The Firedrake container is based on Py3.6 as of 2020-10-10, which -- GitLab From 5c80b14bb9e5fd894a515be8f13f6eca7a748a9e Mon Sep 17 00:00:00 2001 From: Ben Sepanski Date: Wed, 19 Aug 2020 11:36:04 -0500 Subject: [PATCH 217/221] interior -> exterior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andreas Klöckner --- meshmode/interop/firedrake/mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 05f8a7d0..87b5567f 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -370,7 +370,7 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, dtype=IntType) # If not using all the cells, some interior facets may have become - # interior facets: + # exterior facets: if cells_to_use is not None: # Record any newly created exterior facets ext_elements = np.concatenate((ext_elements, new_ext_elements)) -- GitLab From a2fe1d5249a2f4cc252aa10c81eee833c90c01e1 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Wed, 19 Aug 2020 12:10:12 -0500 Subject: [PATCH 218/221] Use new BTAG_INDUCED_BOUNDARY instead of BTAG_NO_BOUNDARY --- meshmode/interop/firedrake/__init__.py | 4 +- meshmode/interop/firedrake/mesh.py | 75 +++++++++++++++++--------- meshmode/mesh/__init__.py | 15 +++++- test/test_firedrake_interop.py | 8 +-- 4 files changed, 70 insertions(+), 32 deletions(-) diff --git a/meshmode/interop/firedrake/__init__.py b/meshmode/interop/firedrake/__init__.py index a4bc55a7..7c29dd3e 100644 --- a/meshmode/interop/firedrake/__init__.py +++ b/meshmode/interop/firedrake/__init__.py @@ -25,9 +25,9 @@ from meshmode.interop.firedrake.connection import ( build_connection_from_firedrake, build_connection_to_firedrake, FiredrakeConnection) from meshmode.interop.firedrake.mesh import ( - import_firedrake_mesh, export_mesh_to_firedrake) + import_firedrake_mesh, export_mesh_to_firedrake, BTAG_INDUCED_BOUNDARY) __all__ = ["build_connection_from_firedrake", "build_connection_to_firedrake", "FiredrakeConnection", "import_firedrake_mesh", - "export_mesh_to_firedrake" + "export_mesh_to_firedrake", "BTAG_INDUCED_BOUNDARY", ] diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 87b5567f..1395d219 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -26,7 +26,7 @@ import numpy as np from modepy import resampling_matrix, simplex_best_available_basis -from meshmode.mesh import (BTAG_ALL, BTAG_REALLY_ALL, BTAG_NO_BOUNDARY, +from meshmode.mesh import (BTAG_ALL, BTAG_REALLY_ALL, FacialAdjacencyGroup, Mesh, NodalAdjacency, SimplexElementGroup) from meshmode.interop.firedrake.reference_cell import ( get_affine_reference_simplex_mapping, get_finat_element_unit_nodes) @@ -36,12 +36,40 @@ from pytools import ProcessLogger __doc__ = """ .. autofunction:: import_firedrake_mesh .. autofunction:: export_mesh_to_firedrake + +Predefined Boundary tags +------------------------ + +.. autoclass:: BTAG_INDUCED_BOUNDARY """ logger = logging.getLogger(__name__) +# {{{ Predefined boundary tags + +class BTAG_INDUCED_BOUNDARY(object): # noqa + """A boundary tag for a mesh imported from :mod:`firedrake` indicating that + this facet was not a boundary facet in the original :mod:`firedrake` + mesh. + This tag is used in place of :class:`BTAG_ALL`. + + More precisely, if two cells in a :mod:`firedrake` mesh + share a facet, but exactly one of those cells is present + in the *cells_to_use* argument of :func:`import_firedrake_mesh`, + that facet becomes a boundary facet. We call this an "induced" + boundary facet. + In order to distinguish induced boundary facets + from other boundary facets, we mark induced facets with + :class:`BTAG_INDUCED_BOUNDARY` instead of + :class:`~meshmode.mesh.BTAG_ALL`. + """ + pass + +# }}} + + # {{{ functions to extract information from Mesh Topology @@ -167,7 +195,7 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology, cells_to_use=None): return vertex_indices, nodal_adjacency -def _get_firedrake_boundary_tags(fdrake_mesh, include_no_boundary=False): +def _get_firedrake_boundary_tags(fdrake_mesh, include_induced_boundary=False): """ Return a tuple of bdy tags as requested in the construction of a :mod:`meshmode` :class:`Mesh` @@ -179,13 +207,14 @@ def _get_firedrake_boundary_tags(fdrake_mesh, include_no_boundary=False): :arg fdrake_mesh: A :mod:`firedrake` :class:`MeshTopology` or :class:`MeshGeometry` - :arg include_no_boundary: If *True*, include :class:`BTAG_NO_BOUNDARY` + :arg include_induced_boundary: If *True*, include + :class:`BTAG_INDUCED_BOUNDARY` :return: A tuple of boundary tags """ bdy_tags = [BTAG_ALL, BTAG_REALLY_ALL] - if include_no_boundary: - bdy_tags.append(BTAG_NO_BOUNDARY) + if include_induced_boundary: + bdy_tags.append(BTAG_INDUCED_BOUNDARY) unique_markers = fdrake_mesh.topology.exterior_facets.unique_markers if unique_markers is not None: @@ -209,7 +238,7 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, cell ids indicating which cells of the mesh to include, as well as inducing a new cell index for those cells. Also, in this case boundary faces are tagged - with :class:`BTAG_NO_BOUNDARY` if they are not a boundary + with :class:`BTAG_INDUCED_BOUNDARY` if they are not a boundary face in *fdrake_mesh_topology* but become a boundary because the opposite cell is not in *cells_to_use*. Boundary faces in *fdrake_mesh_topology* are marked @@ -247,10 +276,8 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, # build a look-up table from firedrake markers to the appropriate values # in the neighbors array for the external and internal facial adjacency # groups - include_no_boundary = cells_to_use is not None - bdy_tags = \ - _get_firedrake_boundary_tags(top, - include_no_boundary=include_no_boundary) + bdy_tags = _get_firedrake_boundary_tags( + top, include_induced_boundary=cells_to_use is not None) boundary_tag_to_index = {bdy_tag: i for i, bdy_tag in enumerate(bdy_tags)} marker_to_neighbor_value = {} from meshmode.mesh import _boundary_tag_bit @@ -316,7 +343,7 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, BTAG_REALLY_ALL) | _boundary_tag_bit(bdy_tags, boundary_tag_to_index, - BTAG_NO_BOUNDARY)) + BTAG_INDUCED_BOUNDARY)) new_ext_neighbors = np.full(new_ext_elements.shape, new_ext_neighbor_tag, dtype=IntType) @@ -523,24 +550,26 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, for entity, dof_list in vertex_entity_dofs.items(): assert len(dof_list) > 0 - :arg cells_to_use: Either *None*, in which case this argument is ignored, - or a numpy array of unique firedrake cell indexes. In the latter case, + :arg cells_to_use: *cells_to_use* is primarily intended for use + internally by :func:`~meshmode.interop.firedrake.connection.\ +build_connection_from_firedrake`. + *cells_to_use* must be either + + 1. *None*, in which case this argument is ignored, or + 2. a numpy array of unique firedrake cell indexes. + + In case (2.), only cells whose index appears in *cells_to_use* are included in the resultant mesh, and their index in *cells_to_use* becomes the element index in the resultant mesh element group. Any faces or vertices which do not touch a cell in *cells_to_use* are also ignored. - Note that in this later case, some faces that are not + Note that in this latter case, some faces that are not boundaries in *fdrake_mesh* may become boundaries in the returned mesh. These "induced" boundaries are marked with - :class:`~meshmode.mesh.BTAG_NO_BOUNDARY` - (and :class:`~meshmode.mesh.BTAG_REALLY_ALL`) + :class:`~meshmode.interop.firedrake.mesh.BTAG_INDUCED_BOUNDARY` instead of :class:`~meshmode.mesh.BTAG_ALL`. - This argument is primarily intended for use by a - :func:`~meshmode.interop.firedrake.connection.\ -build_connection_from_firedrake`. - :arg normals: **Only** used if *fdrake_mesh* is a 1-surface embedded in 2-space. In this case, @@ -593,10 +622,8 @@ build_connection_from_firedrake`. fdrake_mesh.init() # Get all the nodal information we can from the topology - include_no_boundary = cells_to_use is not None - bdy_tags = \ - _get_firedrake_boundary_tags(fdrake_mesh, - include_no_boundary=include_no_boundary) + bdy_tags = _get_firedrake_boundary_tags( + fdrake_mesh, include_induced_boundary=cells_to_use is not None) with ProcessLogger(logger, "Retrieving vertex indices and computing " "NodalAdjacency from firedrake mesh"): diff --git a/meshmode/mesh/__init__.py b/meshmode/mesh/__init__.py index c5d7ecd8..138dda26 100644 --- a/meshmode/mesh/__init__.py +++ b/meshmode/mesh/__init__.py @@ -67,7 +67,12 @@ class BTAG_ALL(object): # noqa """A boundary tag representing the entire boundary or volume. In the case of the boundary, :class:`BTAG_ALL` does not include rank boundaries, - or, more generally, anything tagged with :class:`BTAG_NO_BOUNDARY`.""" + or, more generally, anything tagged with :class:`BTAG_NO_BOUNDARY`. + + In the case of a mesh imported from :mod:`firedrake`, :class:`BTAG_ALL` does + not include induced boundaries, or, more generally, anything tagged with + :class:`meshmode.interop.firedrake.mesh.BTAG_INDUCED_BOUNDARY`. + """ pass @@ -75,7 +80,13 @@ class BTAG_REALLY_ALL(object): # noqa """A boundary tag representing the entire boundary. Unlike :class:`BTAG_ALL`, this includes rank boundaries, - or, more generally, everything tagged with :class:`BTAG_NO_BOUNDARY`.""" + or, more generally, everything tagged with :class:`BTAG_NO_BOUNDARY`. + + In the case of a mesh imported from :mod:`firedrake`, + this includes induced boundaries, or, more generally, + everything tagged with + :class:`meshmode.interop.firedrake.mesh.BTAG_INDUCED_BOUNDARY` + """ pass diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index d2b526f0..15fad369 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -37,12 +37,12 @@ from meshmode.discretization.poly_element import ( from meshmode.dof_array import DOFArray from meshmode.mesh import ( - BTAG_ALL, BTAG_REALLY_ALL, BTAG_NO_BOUNDARY, check_bc_coverage + BTAG_ALL, BTAG_REALLY_ALL, check_bc_coverage ) from meshmode.interop.firedrake import ( build_connection_from_firedrake, build_connection_to_firedrake, - import_firedrake_mesh) + import_firedrake_mesh, BTAG_INDUCED_BOUNDARY) import pytest @@ -329,11 +329,11 @@ def test_bdy_tags(square_or_cube_mesh, bdy_ids, coord_indices, coord_values, ext_grp.elements, ext_grp.element_faces, ext_grp.neighbors): # try: if mm_mesh has boundaries flagged as not boundaries we need to # skip them - # catch: if mm_mesh does not use BTAG_NO_BOUNDARY flag we get a + # catch: if mm_mesh does not use BTAG_INDUCED_BOUNDARY flag we get a # ValueError try: # If this facet is flagged as not really a boundary, skip it - if mm_mesh.boundary_tag_bit(BTAG_NO_BOUNDARY) & -bdy_flags: + if mm_mesh.boundary_tag_bit(BTAG_INDUCED_BOUNDARY) & -bdy_flags: continue except ValueError: pass -- GitLab From 9b25af3675e646271f36bce5483057c85ecdb886 Mon Sep 17 00:00:00 2001 From: benSepanski Date: Thu, 20 Aug 2020 12:21:21 -0500 Subject: [PATCH 219/221] Improve user-input validation for reference_cell.py --- meshmode/interop/firedrake/reference_cell.py | 33 ++++++++++---------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/meshmode/interop/firedrake/reference_cell.py b/meshmode/interop/firedrake/reference_cell.py index 993240e7..327f9cd4 100644 --- a/meshmode/interop/firedrake/reference_cell.py +++ b/meshmode/interop/firedrake/reference_cell.py @@ -119,29 +119,30 @@ def get_finat_element_unit_nodes(finat_element): Returns the unit nodes used by the :mod:`finat` element in firedrake's (equivalently, :mod:`finat`/:mod:`FIAT`'s) reference coordinates - :arg finat_element: A :class:`finat.finiteelementbase.FiniteElementBase` - instance whose :mod:`FIAT` element is of type - :class:`FIAT.finite_element.CiarletElement` - (i.e. a certain type of reference element used by - firedrake with dofs guaranteed to be nodal) - The reference element of the finat element *MUST* be a simplex. + :arg finat_element: An instance of one of the following :mod:`finat` + elements + + * :class:`finat.fiat_elements.Lagrange` + * :class:`finat.fiat_elements.DiscontinuousLagrange` + * :class:`finat.fiat_elements.CrouzeixRaviart` + * :class:`finat.spectral.GaussLobattoLegendre` + * :class:`finat.spectral.GaussLegendre` + :return: A numpy array of shape *(dim, nunit_dofs)* holding the unit nodes used by this element. *dim* is the dimension spanned by the finat element's reference element (see its ``cell`` attribute) """ - from finat.finiteelementbase import FiniteElementBase - from FIAT.finite_element import CiarletElement + from finat.fiat_elements import ( + Lagrange, DiscontinuousLagrange, CrouzeixRaviart) + from finat.spectral import GaussLobattoLegendre, GaussLegendre from FIAT.reference_element import Simplex - if not isinstance(finat_element, FiniteElementBase): + allowed_finat_elts = (Lagrange, DiscontinuousLagrange, CrouzeixRaviart, + GaussLobattoLegendre, GaussLegendre) + if not isinstance(finat_element, allowed_finat_elts): raise TypeError("'finat_element' is of unexpected type " - f"{type(finat_element)}. 'finat_element' must be an " - "instance of finat.finiteelementbase.FiniteElementBase") - if not isinstance(finat_element._element, CiarletElement): - raise TypeError("'finat_element._element' is of unexpected type " - f"{type(finat_element._element)}. " - "'finat_element._element' must be an " - "instance of FIAT.finite_element.CiarletElement") + f"{type(finat_element)}. 'finat_element' must be of " + "one of the following types: {allowed_finat_elts}") if not isinstance(finat_element.cell, Simplex): raise TypeError("Reference element of the finat element MUST be a" " simplex, i.e. 'finat_element's *cell* attribute must" -- GitLab From d3966c31b03641a11db5df6b0f89a1e99e859d2a Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 20 Aug 2020 17:52:34 -0500 Subject: [PATCH 220/221] BTAG_INDUCED_BOUNDARY: improve docs, promote to system tag --- meshmode/interop/firedrake/mesh.py | 42 +++++------------------------- meshmode/mesh/__init__.py | 37 +++++++++++++++++--------- 2 files changed, 32 insertions(+), 47 deletions(-) diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 1395d219..db2f0a2f 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -26,7 +26,7 @@ import numpy as np from modepy import resampling_matrix, simplex_best_available_basis -from meshmode.mesh import (BTAG_ALL, BTAG_REALLY_ALL, +from meshmode.mesh import (BTAG_ALL, BTAG_REALLY_ALL, BTAG_INDUCED_BOUNDARY, FacialAdjacencyGroup, Mesh, NodalAdjacency, SimplexElementGroup) from meshmode.interop.firedrake.reference_cell import ( get_affine_reference_simplex_mapping, get_finat_element_unit_nodes) @@ -36,40 +36,12 @@ from pytools import ProcessLogger __doc__ = """ .. autofunction:: import_firedrake_mesh .. autofunction:: export_mesh_to_firedrake - -Predefined Boundary tags ------------------------- - -.. autoclass:: BTAG_INDUCED_BOUNDARY """ logger = logging.getLogger(__name__) -# {{{ Predefined boundary tags - -class BTAG_INDUCED_BOUNDARY(object): # noqa - """A boundary tag for a mesh imported from :mod:`firedrake` indicating that - this facet was not a boundary facet in the original :mod:`firedrake` - mesh. - This tag is used in place of :class:`BTAG_ALL`. - - More precisely, if two cells in a :mod:`firedrake` mesh - share a facet, but exactly one of those cells is present - in the *cells_to_use* argument of :func:`import_firedrake_mesh`, - that facet becomes a boundary facet. We call this an "induced" - boundary facet. - In order to distinguish induced boundary facets - from other boundary facets, we mark induced facets with - :class:`BTAG_INDUCED_BOUNDARY` instead of - :class:`~meshmode.mesh.BTAG_ALL`. - """ - pass - -# }}} - - # {{{ functions to extract information from Mesh Topology @@ -195,7 +167,7 @@ def _get_firedrake_nodal_info(fdrake_mesh_topology, cells_to_use=None): return vertex_indices, nodal_adjacency -def _get_firedrake_boundary_tags(fdrake_mesh, include_induced_boundary=False): +def _get_firedrake_boundary_tags(fdrake_mesh, tag_induced_boundary=False): """ Return a tuple of bdy tags as requested in the construction of a :mod:`meshmode` :class:`Mesh` @@ -207,13 +179,13 @@ def _get_firedrake_boundary_tags(fdrake_mesh, include_induced_boundary=False): :arg fdrake_mesh: A :mod:`firedrake` :class:`MeshTopology` or :class:`MeshGeometry` - :arg include_induced_boundary: If *True*, include - :class:`BTAG_INDUCED_BOUNDARY` + :arg tag_induced_boundary: If *True*, tag induced boundary with + :class:`~meshmode.mesh.BTAG_INDUCED_BOUNDARY` :return: A tuple of boundary tags """ bdy_tags = [BTAG_ALL, BTAG_REALLY_ALL] - if include_induced_boundary: + if tag_induced_boundary: bdy_tags.append(BTAG_INDUCED_BOUNDARY) unique_markers = fdrake_mesh.topology.exterior_facets.unique_markers @@ -277,7 +249,7 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, # in the neighbors array for the external and internal facial adjacency # groups bdy_tags = _get_firedrake_boundary_tags( - top, include_induced_boundary=cells_to_use is not None) + top, tag_induced_boundary=cells_to_use is not None) boundary_tag_to_index = {bdy_tag: i for i, bdy_tag in enumerate(bdy_tags)} marker_to_neighbor_value = {} from meshmode.mesh import _boundary_tag_bit @@ -623,7 +595,7 @@ build_connection_from_firedrake`. # Get all the nodal information we can from the topology bdy_tags = _get_firedrake_boundary_tags( - fdrake_mesh, include_induced_boundary=cells_to_use is not None) + fdrake_mesh, tag_induced_boundary=cells_to_use is not None) with ProcessLogger(logger, "Retrieving vertex indices and computing " "NodalAdjacency from firedrake mesh"): diff --git a/meshmode/mesh/__init__.py b/meshmode/mesh/__init__.py index 138dda26..532e5e39 100644 --- a/meshmode/mesh/__init__.py +++ b/meshmode/mesh/__init__.py @@ -53,44 +53,46 @@ Predefined Boundary tags .. autoclass:: BTAG_REALLY_ALL .. autoclass:: BTAG_NO_BOUNDARY .. autoclass:: BTAG_PARTITION +.. autoclass:: BTAG_INDUCED_BOUNDARY """ # {{{ element tags -class BTAG_NONE(object): # noqa +class BTAG_NONE(object): # noqa: N801 """A boundary tag representing an empty boundary or volume.""" pass -class BTAG_ALL(object): # noqa +class BTAG_ALL(object): # noqa: N801 """A boundary tag representing the entire boundary or volume. In the case of the boundary, :class:`BTAG_ALL` does not include rank boundaries, or, more generally, anything tagged with :class:`BTAG_NO_BOUNDARY`. - In the case of a mesh imported from :mod:`firedrake`, :class:`BTAG_ALL` does - not include induced boundaries, or, more generally, anything tagged with - :class:`meshmode.interop.firedrake.mesh.BTAG_INDUCED_BOUNDARY`. + In the case of a mesh representing an element-wise subset of another, + :class:`BTAG_ALL` does not include boundaries induced by taking the subset. + Instead, these boundaries will be tagged with + :class:`BTAG_INDUCED_BOUNDARY`. """ pass -class BTAG_REALLY_ALL(object): # noqa +class BTAG_REALLY_ALL(object): # noqa: N801 """A boundary tag representing the entire boundary. Unlike :class:`BTAG_ALL`, this includes rank boundaries, or, more generally, everything tagged with :class:`BTAG_NO_BOUNDARY`. - In the case of a mesh imported from :mod:`firedrake`, - this includes induced boundaries, or, more generally, + In the case of a mesh representing an element-wise subset of another, + this tag includes boundaries induced by taking the subset, or, more generally, everything tagged with - :class:`meshmode.interop.firedrake.mesh.BTAG_INDUCED_BOUNDARY` + :class:`BTAG_INDUCED_BOUNDARY` """ pass -class BTAG_NO_BOUNDARY(object): # noqa +class BTAG_NO_BOUNDARY(object): # noqa: N801 """A boundary tag indicating that this edge should not fall under :class:`BTAG_ALL`. Among other things, this is used to keep rank boundaries out of :class:`BTAG_ALL`. @@ -98,7 +100,7 @@ class BTAG_NO_BOUNDARY(object): # noqa pass -class BTAG_PARTITION(object): # noqa +class BTAG_PARTITION(object): # noqa: N801 """ A boundary tag indicating that this edge is adjacent to an element of another :class:`Mesh`. The partition number of the adjacent mesh @@ -127,8 +129,19 @@ class BTAG_PARTITION(object): # noqa return "<%s(%s)>" % (type(self).__name__, repr(self.part_nr)) +class BTAG_INDUCED_BOUNDARY(BTAG_NO_BOUNDARY): # noqa: N801 + """When a :class:`Mesh` is created as an element-by-element subset of another + (as, for example, when calling + :func:`meshmode.interop.firedrake.build_connection_from_firedrake` + while passing *restrict_to_boundary*), boundaries may arise where there + were none in the original mesh. This boundary tag is used to indicate + such boundaries. + """ + pass + + SYSTEM_TAGS = set([BTAG_NONE, BTAG_ALL, BTAG_REALLY_ALL, BTAG_NO_BOUNDARY, - BTAG_PARTITION]) + BTAG_PARTITION, BTAG_INDUCED_BOUNDARY]) # }}} -- GitLab From 29665f8e10aad8be4a1df565ddcbf45bd60d414f Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 20 Aug 2020 18:01:19 -0500 Subject: [PATCH 221/221] Fix BTAG_INDUCED_BOUNDARY imports --- meshmode/interop/firedrake/__init__.py | 4 ++-- test/test_firedrake_interop.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/meshmode/interop/firedrake/__init__.py b/meshmode/interop/firedrake/__init__.py index 7c29dd3e..727e2c67 100644 --- a/meshmode/interop/firedrake/__init__.py +++ b/meshmode/interop/firedrake/__init__.py @@ -25,9 +25,9 @@ from meshmode.interop.firedrake.connection import ( build_connection_from_firedrake, build_connection_to_firedrake, FiredrakeConnection) from meshmode.interop.firedrake.mesh import ( - import_firedrake_mesh, export_mesh_to_firedrake, BTAG_INDUCED_BOUNDARY) + import_firedrake_mesh, export_mesh_to_firedrake) __all__ = ["build_connection_from_firedrake", "build_connection_to_firedrake", "FiredrakeConnection", "import_firedrake_mesh", - "export_mesh_to_firedrake", "BTAG_INDUCED_BOUNDARY", + "export_mesh_to_firedrake", ] diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 15fad369..17cabade 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -37,12 +37,12 @@ from meshmode.discretization.poly_element import ( from meshmode.dof_array import DOFArray from meshmode.mesh import ( - BTAG_ALL, BTAG_REALLY_ALL, check_bc_coverage + BTAG_ALL, BTAG_REALLY_ALL, BTAG_INDUCED_BOUNDARY, check_bc_coverage ) from meshmode.interop.firedrake import ( build_connection_from_firedrake, build_connection_to_firedrake, - import_firedrake_mesh, BTAG_INDUCED_BOUNDARY) + import_firedrake_mesh) import pytest -- GitLab