Functionality in this subpackage helps import and export data to/from other pieces of software, typically PDE solvers.
.. automodule:: meshmode.interop.nodal_dg
Users wishing to interact with :mod:`meshmode` from :mod:`firedrake` 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`. It is not recommended to create a :class:`~meshmode.interop.firedrake.connection.FiredrakeConnection` directly.
.. automodule:: meshmode.interop.firedrake.connection
.. automodule:: meshmode.interop.firedrake.mesh
.. automodule:: meshmode.interop.firedrake.reference_cell
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` :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 the PETSc documentation on 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.
- :mod:`meshmode` has discontinuous polynomial function spaces which may use different unit nodes than :mod:`firedrake`.
- :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` and :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 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 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 A applied to the reference element which implements this permutation of barycentric coordinates. In this case, 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:`~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 g\circ A\circ \text{Resampling} \circ f^{-1}).
In Firedrake, meshes and function spaces have a close relationship. In particular, this is due to some structure described in this 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.
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:
- 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 triangle is different than specified in :mod:`modepy`.
- A ~firedrake.mesh.MeshTopology which holds information about connectivity and other topological properties, but nothing about geometry/coordinates etc.
- A class :class:`~firedrake.functionspaceimpl.FunctionSpace` created from a :mod:`finat` element and a ~firedrake.mesh.MeshTopology which allows us to define functions mapping the nodes (defined by the :mod:`finat` element) of each element in the ~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` and :class:`~firedrake.functionspaceimpl.WithGeometry` (see (6)).
- A ~firedrake.function.CoordinatelessFunction (in the sense that its domain has no coordinates) which is a function in a :class:`~firedrake.functionspaceimpl.FunctionSpace`.
- A ~firedrake.mesh.MeshGeometry created from a :class:`~firedrake.functionspaceimpl.FunctionSpace` and a ~firedrake.function.CoordinatelessFunction in that :class:`~firedrake.functionspaceimpl.FunctionSpace` which maps each dof to its geometric coordinates.
- A :class:`~firedrake.functionspaceimpl.WithGeometry` which is a :class:`~firedrake.functionspaceimpl.FunctionSpace` together with a ~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`.
- A :class:`~firedrake.function.Function` is defined on a :class:`~firedrake.functionspaceimpl.WithGeometry`.
Thus, by the coordinates of a mesh geometry we mean
- On the hidden back-end: a ~firedrake.function.CoordinatelessFunction f on some function space defined only on the mesh topology.
- 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 ~firedrake.mesh.MeshGeometry f defines.
Basically, it's this picture (where a\to b if b depends on a)
Warning
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.
.. 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; }