Skip to content

Commit 396cd76

Browse files
committed
Implement cross mesh interpolation
This implements cross-mesh interpolation for interpolating into function spaces which have point evaluation nodes. Full documentation is added to the manual. Two new classes are added to interpolation.py: CrossMeshInterpolator and SameMeshInterpolator, whilst Interpolator is made an abstract base class. To maintain the API of interpolate and Interpolator, the __new__ method of Interpolator is overridden to return an instance of the appropriate subclass. Two new keyword arguments are added to interpolate and Interpolator to allow for target meshes which extend outside the source mesh domain:: see their docstrings for details. Docstrings are also added for some undocumented keyword arguments of Interpolator and interpolate. A full suite of tests is found in tests/regression/test_interpolate_cross_mesh.py Note that as part of this, I have changed the error by VertexOnlyMesh when points are outside the domain from a ValueError to a VertexOnlyMeshMissingPointsError Details of how this works from the PR description: We use VertexOnlyMesh as an intermediary for the global locations of the point evaluation nodes of the target function space: 1. We get the point evaluation node locations using the method described in the interpolation from external data section of the manual. This will have the parallel domain decomposition of the target mesh. 2. Next we create a VertexOnlyMesh (A) at those locations within the source mesh such that we inherit the source mesh's parallel domain decomposition. 3. We interpolate our expression in our source function space onto a P0DG function space on VertexOnlyMesh (A), which has the effect of point evaluating at the target function space node locations. 4. This VertexOnlyMesh (A) has an input_ordering VertexOnlyMesh (B) whose vertices have the ordering and parallel domain decomposition of the target function space global node locations. We interpolate from P0DG on (A) onto P0DG on (B). Under the hood, this is an SF reduce operation which moves the point evaluations from (A) to (B). 5. We now have a Function on the input_ordering VertexOnlyMesh (B) which has point evaluations from our source mesh function space at the target mesh function space node locations. These are in the correct order and have the correct domain decomposition. We can therefore safely set the dat of a function in our target function space to the values of this function. For this to work for the general case, we would need to create a VertexOnlyMesh at the global quadrature points of the target function space, which is rather more complicated than the work I've done here. Some important notes: - This does not require one mesh to be a structured refinement of the other. This, for example, should allow you to solve a PDE on two different unstructured meshes, one of which is finer than the other, and directly check the difference in solutions by interpolating from one mesh to the other within Firedrake. - Crucially, this is entirely parallel compatible! - Since we can interpolate onto immersed manifolds we can perform line, surface and volume integrals by interpolating onto a mesh which has the domain of integration as an immersed manifold. This is demonstrated in the test_interpolate_cross_mesh.py test suite. Other notes: - The VertexOnlyMesh required is stored in the Interpolator, as are the underlying Interpolators - For interpolation between mixed spaces, I create sub_interpolators for each space and evaluate them as necessary when calling interpolate - Interpolation into a mixed space therefore requires the function space being interpolated from to be another mixed space. I throw a NotImplementedError if not. Regarding the manual: Note that not all the comments in the manual file are included in the literalinclude text of the manual, instead they are approximately rewritten as prose in the manual.
1 parent 2ddd133 commit 396cd76

11 files changed

+1550
-40
lines changed

docs/source/interpolation.rst

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,120 @@ in the dual space to `V` to assembled 1-forms in the dual space to
108108
`W`.
109109

110110

111+
Interpolation across meshes
112+
---------------------------
113+
114+
The interpolation API supports interpolation between meshes where the target
115+
function space has finite elements (as given in the list of
116+
:ref:`supported elements <supported_elements>`)
117+
118+
* **Lagrange/CG** (also known a Continuous Galerkin or P elements),
119+
* **Q** (i.e. Lagrange/CG on lines, quadrilaterals and hexahedra),
120+
* **Discontinuous Lagrange/DG** (also known as Discontinuous Galerkin or DP elements) and
121+
* **DQ** (i.e. Discontinuous Lagrange/DG on lines, quadrilaterals and hexahedra).
122+
123+
Vector, tensor and mixed function spaces can also be interpolated into from
124+
other meshes as long as they are constructed from these spaces.
125+
126+
.. note::
127+
128+
The list of supported elements above is only for *target* function spaces.
129+
Function spaces on the *source* mesh can be built from most of the supported
130+
elements.
131+
132+
There are few constraints on the meshes involved: the target mesh can have a
133+
different cell shape, topological dimension, or resolution to the source mesh.
134+
There are many use cases for this: For example, two solutions to the same
135+
problem calculated on meshes with different resolutions or cell shapes can be
136+
interpolated onto one another, or onto a third, finer mesh, and be directly
137+
compared.
138+
139+
140+
Interpolating onto sub-domain meshes
141+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
142+
143+
The target mesh for a cross-mesh interpolation need not cover the full domain
144+
of the source mesh. Volume, surface and line integrals can therefore be
145+
calculated by interpolating onto the mesh or
146+
:ref:`immersed manifold <immersed_manifolds>` which defines the volume,
147+
surface or line of interest in the domain. The integral itself is calculated
148+
by calling :py:func:`~.assemble` on an approriate form over the target mesh
149+
function space:
150+
151+
.. literalinclude:: ../../tests/regression/test_interpolation_manual.py
152+
:language: python3
153+
:dedent:
154+
:lines: 11-18, 31-40
155+
156+
For more on forms, see :ref:`this section of the manual <more_complicated_forms>`.
157+
158+
159+
Interpolating onto other meshes
160+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
161+
162+
.. note::
163+
164+
Interpolation *from* :ref:`immersed manifolds <immersed_manifolds>` and
165+
:ref:`high-order meshes <changing_coordinate_fs>` is currently not supported.
166+
167+
If the target mesh extends outside the source mesh domain, then cross-mesh
168+
interpolation will raise a :py:class:`~.DofNotDefinedError`.
169+
170+
.. literalinclude:: ../../tests/regression/test_interpolation_manual.py
171+
:language: python3
172+
:dedent:
173+
:lines: 46-58, 64-65
174+
175+
This can be overriden with the optional ``allow_missing_dofs`` keyword
176+
argument:
177+
178+
.. literalinclude:: ../../tests/regression/test_interpolation_manual.py
179+
:language: python3
180+
:dedent:
181+
:lines: 72-73, 80
182+
183+
By default the missing degrees of freedom (DoFs, the global basis function
184+
coefficients which could not be set) are zero:
185+
186+
.. literalinclude:: ../../tests/regression/test_interpolation_manual.py
187+
:language: python3
188+
:dedent:
189+
:lines: 85
190+
191+
We can optionally specify a value to use for our missing DoFs. Here
192+
we set them to be ``nan`` ('not a number') for easy identification:
193+
194+
.. literalinclude:: ../../tests/regression/test_interpolation_manual.py
195+
:language: python3
196+
:dedent:
197+
:lines: 90-93
198+
199+
When using :py:class:`~.Interpolator`\s, the ``allow_missing_dofs`` keyword
200+
argument is set at construction:
201+
202+
.. literalinclude:: ../../tests/regression/test_interpolation_manual.py
203+
:language: python3
204+
:dedent:
205+
:lines: 100
206+
207+
The ``default_missing_val`` keyword argument is then set whenever we call
208+
:py:meth:`~.Interpolator.interpolate`:
209+
210+
.. literalinclude:: ../../tests/regression/test_interpolation_manual.py
211+
:language: python3
212+
:dedent:
213+
:lines: 103
214+
215+
If we supply an output :py:class:`~.Function` and don't set
216+
``default_missing_val`` then any missing DoFs are left as they were prior to
217+
interpolation:
218+
219+
.. literalinclude:: ../../tests/regression/test_interpolation_manual.py
220+
:language: python3
221+
:dedent:
222+
:lines: 107-110, 113-115, 124-126
223+
224+
111225
Interpolation from external data
112226
--------------------------------
113227

docs/source/mesh-coordinates.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ This can also be used if `f` is a solution to a PDE.
3636
This will be fixed in a future Firedrake update.
3737

3838

39+
.. _changing_coordinate_fs:
40+
3941
Changing the coordinate function space
4042
--------------------------------------
4143

docs/source/point-evaluation.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,10 @@ duplication.
217217
Points outside the domain
218218
~~~~~~~~~~~~~~~~~~~~~~~~~
219219

220-
Be default points outside the domain by more than the :ref:`specified
221-
tolerance <tolerance>` will generate a ``ValueError``. This can be switched
222-
to a warning or switched off entirely:
220+
Be default points outside the domain by more than the :ref:`specified
221+
tolerance <tolerance>` will generate
222+
a :class:`~.VertexOnlyMeshMissingPointsError`. This can be switched to a
223+
warning or switched off entirely:
223224

224225
.. literalinclude:: ../../tests/vertexonly/test_vertex_only_manual.py
225226
:language: python3

docs/source/variational-problems.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ of standard shapes. 1-dimensional intervals may be constructed with
7777
example to build unit square meshes). See
7878
:mod:`~firedrake.utility_meshes` for full details.
7979

80+
.. _immersed_manifolds:
8081

8182
Immersed manifolds
8283
~~~~~~~~~~~~~~~~~~
@@ -260,6 +261,9 @@ the horizontal and vertical spaces when building a function space on
260261
an extruded mesh. We refer the reader to the :doc:`manual section on
261262
extrusion <extruded-meshes>` for details.
262263

264+
265+
.. _supported_elements:
266+
263267
Supported finite elements
264268
-------------------------
265269

@@ -632,6 +636,8 @@ we can write:
632636
t += dt
633637
c.assign(t)
634638
639+
.. _more_complicated_forms:
640+
635641
More complicated forms
636642
----------------------
637643

firedrake/function.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -367,14 +367,47 @@ def vector(self):
367367
return vector.Vector(self)
368368

369369
@PETSc.Log.EventDecorator()
370-
def interpolate(self, expression, subset=None, ad_block_tag=None):
370+
def interpolate(
371+
self,
372+
expression,
373+
subset=None,
374+
allow_missing_dofs=False,
375+
default_missing_val=None,
376+
ad_block_tag=None,
377+
):
371378
r"""Interpolate an expression onto this :class:`Function`.
372379
373380
:param expression: a UFL expression to interpolate
374-
:param ad_block_tag: string for tagging the resulting block on the Pyadjoint tape
381+
:kwarg subset: An optional :class:`pyop2.types.set.Subset` to apply the
382+
interpolation over. Cannot, at present, be used when interpolating
383+
across meshes unless the target mesh is a :func:`.VertexOnlyMesh`.
384+
:kwarg allow_missing_dofs: For interpolation across meshes: allow
385+
degrees of freedom (aka DoFs/nodes) in the target mesh that cannot be
386+
defined on the source mesh. For example, where nodes are point
387+
evaluations, points in the target mesh that are not in the source mesh.
388+
When ``False`` this raises a ``ValueError`` should this occur. When
389+
``True`` the corresponding values are set to zero or to the value
390+
``default_missing_val`` if given. Ignored if interpolating within the
391+
same mesh or onto a :func:`.VertexOnlyMesh` (the behaviour of a
392+
:func:`.VertexOnlyMesh` in this scenario is, at present, set when
393+
it is created).
394+
:kwarg default_missing_val: For interpolation across meshes: the optional
395+
value to assign to DoFs in the target mesh that are outside the source
396+
mesh. If this is not set then zero is used. Ignored if interpolating
397+
within the same mesh or onto a :func:`.VertexOnlyMesh`.
398+
:kwarg ad_block_tag: An optional string for tagging the resulting block on
399+
the Pyadjoint tape.
375400
:returns: this :class:`Function` object"""
376401
from firedrake import interpolation
377-
return interpolation.interpolate(expression, self, subset=subset, ad_block_tag=ad_block_tag)
402+
403+
return interpolation.interpolate(
404+
expression,
405+
self,
406+
subset=subset,
407+
allow_missing_dofs=allow_missing_dofs,
408+
default_missing_val=default_missing_val,
409+
ad_block_tag=ad_block_tag,
410+
)
378411

379412
def zero(self, subset=None):
380413
"""Set all values to zero.

0 commit comments

Comments
 (0)