@@ -11,7 +11,7 @@ Firedrake offers highly flexible capabilities for interpolating expressions
1111(functions of space) into finite element :py:class: `~.Function `\s .
1212Interpolation is often used to set up initial conditions and/or boundary
1313conditions. Mathematically, if :math: `e(x)` is a function of space and
14- :math: `V` is a finite element functionspace then
14+ :math: `V` is a finite element function space then
1515:math: `\operatorname {interpolate}(e, V)` is the :py:class: `~.Function `
1616:math: `v_i \phi _i\in V` such that:
1717
@@ -46,6 +46,9 @@ The basic syntax for interpolation is:
4646 :start-after: [test_interpolate_operator 1]
4747 :end-before: [test_interpolate_operator 2]
4848
49+ Here, the :py:func: `~.interpolate ` function returned a **symbolic ** UFL _ :py:class: `~ufl.Interpolate `
50+ expression. To calculate a concrete numerical result, we need to call :py:func: `~.assemble ` on this expression.
51+
4952It is also possible to interpolate an expression directly into an existing
5053:py:class: `~.Function `:
5154
@@ -89,8 +92,7 @@ Here is an example demonstrating some of these features:
8992 :start-after: [test_interpolate_operator 7]
9093 :end-before: [test_interpolate_operator 8]
9194
92- This also works as expected when interpolating into a a space defined on the facets
93- of the mesh:
95+ This also works when interpolating into a space defined on the facets of the mesh:
9496
9597.. literalinclude :: ../../tests/firedrake/regression/test_interpolation_manual.py
9698 :language: python3
@@ -105,19 +107,170 @@ of the mesh:
105107 interpolate into spaces defined by higher-continuity elements such as
106108 Argyris and Hermite.
107109
110+
111+ Semantics of symbolic interpolation
112+ --------------------------------------------
113+
114+ Let :math: `U` and :math: `V` be finite element spaces with DoFs :math: `\{\psi ^{*}_{i}\}` and :math: `\{\phi ^{*}_{i}\}`
115+ and basis functions :math: `\{\psi _{i}\}` and :math: `\{\phi _{i}\}`, respectively.
116+ The interpolation operator between :math: `U` and :math: `V` is defined
117+
118+ .. math ::
119+
120+ \mathcal {I}_{V} : U &\to V \\ \mathcal {I}_{V}(u)(x) &= \phi ^{*}_{i}(u)\phi _{i}(x).
121+
122+ We define the following bilinear form
123+
124+ .. math ::
125+
126+ I : U \times V^{*} &\to \mathbb {R} \\ I(u, v^*) &= v^{*}(u)
127+
128+ where :math: `v^{*}\in V^{*}` is a linear functional in the dual space to :math: `V`, extended so that
129+ it can act on functions in :math: `U`. If we choose :math: `v^{*} = \phi ^{*}_{i}` then
130+ :math: `I(u, \phi ^{*}_{i}) = \phi ^{*}_{i}(u)` gives the coefficients of the interpolation of :math: `u` into :math: `V`.
131+ This allows us to represent the interpolation as a form in UFL _. This is exactly the
132+ :py:class: `~ufl.Interpolate ` UFL _ object. Note that this differs from typical bilinear forms since one of the
133+ arguments is in a dual space. For more information on dual spaces in Firedrake,
134+ see :ref: `the relevant section of the manual <duals >`.
135+
136+ Interpolation operators
137+ ~~~~~~~~~~~~~~~~~~~~~~~
138+
139+ 2-forms are assembled into matrices, and we can do the same with the interpolation form.
140+ If we let :math: `u` be a ``TrialFunction(U) `` (i.e. an argument in slot 1) and :math: `v^*` be a
141+ ``TestFunction(V.dual()) `` (i.e. a :py:class: `~ufl.Coargument ` in slot 0) then
142+
143+ .. math ::
144+
145+ I(u, v^*) = I(\psi _{j},\phi _{i}^*)=\phi _{i}^*(\psi _{j})=:A_{ij}
146+
147+ The matrix :math: `A` is the interpolation matrix from :math: `U` to :math: `V`. In Firedrake, we can
148+ assemble this matrix by doing
149+
150+ .. literalinclude :: ../../tests/firedrake/regression/test_interpolation_manual.py
151+ :language: python3
152+ :dedent:
153+ :start-after: [test_interpolate_operator 11]
154+ :end-before: [test_interpolate_operator 12]
155+
156+ Passing a :py:class: `~.FunctionSpace ` into the dual slot of :py:func: `~.interpolate ` is
157+ syntactic sugar for ``TestFunction(V.dual()) ``.
158+
159+ If :math: `g\in U` is a :py:class: `~.Function `, then we can write it as :math: `g = g_j \psi _j` for
160+ some coefficients :math: `g_j`. Interpolating :math: `g` into :math: `V` gives
161+
162+ .. math ::
163+
164+ I(g, v^*) = \phi ^{*}_{i}(g_j \psi _j)= A_{ij} g_j,
165+
166+ so we can multiply the vector of coefficients of :math: `g` by the interpolation matrix to obtain the
167+ coefficients of the interpolated function. In Firedrake, we can do this by
168+
169+ .. literalinclude :: ../../tests/firedrake/regression/test_interpolation_manual.py
170+ :language: python3
171+ :dedent:
172+ :start-after: [test_interpolate_operator 12]
173+ :end-before: [test_interpolate_operator 13]
174+
175+ :math: `h` is a :py:class: `~.Function ` in :math: `V` representing the interpolation of :math: `g` into :math: `V`.
176+
177+ .. note ::
178+
179+ When interpolating a :py:class: `~.Function ` directly, for example
180+
181+ .. code-block :: python3
182+
183+ assemble(interpolate(Function(U), V))
184+
185+ Firedrake does not explicitly assemble the interpolation matrix. Instead, the interpolation
186+ is performed matrix-free.
187+
188+ Adjoint interpolation
189+ ~~~~~~~~~~~~~~~~~~~~~
190+ The adjoint of the interpolation operator is defined as
191+
192+ .. math ::
193+
194+ \mathcal {I}_{V}^{*} : V^{*} \to U^{*}.
195+
196+ This operator interpolates :py:class: `~.Cofunction `\s in the dual space :math: `V^{*}` into
197+ the dual space :math: `U^{*}`. The associated form is
198+
199+ .. math ::
200+
201+ I^{*} : V^{*} \times U \to \mathbb {R}.
202+
203+ So to obtain the adjoint interpolation operator, we swap the arguments of the :py:class: `~ufl.Interpolate `
204+ form. In Firedrake, we can accomplish this in two ways. The first is to swap the argument numbers to the form:
205+
206+ .. literalinclude :: ../../tests/firedrake/regression/test_interpolation_manual.py
207+ :language: python3
208+ :dedent:
209+ :start-after: [test_interpolate_operator 14]
210+ :end-before: [test_interpolate_operator 15]
211+
212+ The second way is to use UFL _'s :py:func: `~ufl.adjoint ` operator, which takes a form and returns its adjoint:
213+
214+ .. literalinclude :: ../../tests/firedrake/regression/test_interpolation_manual.py
215+ :language: python3
216+ :dedent:
217+ :start-after: [test_interpolate_operator 15]
218+ :end-before: [test_interpolate_operator 16]
219+
220+ If :math: `g^*` is a :py:class: `~.Cofunction ` in :math: `V^{*}` then we can interpolate it into :math: `U^{*}` by doing
221+
222+ .. math ::
223+
224+ I^{*}(g^*, u) = g^*_i \phi _i^*(\psi _j) = g^*_i A_{ij}.
225+
226+ This is the product of the adjoint interpolation matrix :math: `A^{*}` and the coefficients of :math: `g^*`.
227+ In Firedrake, we can do this by
228+
229+ .. literalinclude :: ../../tests/firedrake/regression/test_interpolation_manual.py
230+ :language: python3
231+ :dedent:
232+ :start-after: [test_interpolate_operator 16]
233+ :end-before: [test_interpolate_operator 17]
234+
235+ Again, Firedrake does not explicitly assemble the adjoint interpolation matrix, but performs the
236+ interpolation matrix-free. To perform the interpolation with the assembled adjoint interpolation operator,
237+ we can take the :py:func: `~ufl.action ` of the operator on the :py:class: `~.Cofunction `:
238+
239+ .. literalinclude :: ../../tests/firedrake/regression/test_interpolation_manual.py
240+ :language: python3
241+ :dedent:
242+ :start-after: [test_interpolate_operator 17]
243+ :end-before: [test_interpolate_operator 18]
244+
245+ The final case is when we interpolate a :py:class: `~.Function ` into :py:class: `~.Cofunction `:
246+
247+ .. literalinclude :: ../../tests/firedrake/regression/test_interpolation_manual.py
248+ :language: python3
249+ :dedent:
250+ :start-after: [test_interpolate_operator 19]
251+ :end-before: [test_interpolate_operator 20]
252+
253+ This interpolation has zero arguments and hence is assembled into a number. Mathematically, we have
254+
255+ .. math ::
256+
257+ I^{*}(g^*, u) = g^*_i \phi _i^*(u_{j}\psi _j) = g^*_i A_{ij} u_j.
258+
259+ which indeed contracts into a number.
260+
108261Interpolation across meshes
109262---------------------------
110263
111264The interpolation API supports interpolation between meshes where the target
112265function space has finite elements (as given in the list of
113266:ref: `supported elements <supported_elements >`)
114267
115- * **Lagrange/CG ** (also known a Continuous Galerkin or P elements),
268+ * **Lagrange/CG ** (also known as Continuous Galerkin or P elements),
116269* **Q ** (i.e. Lagrange/CG on lines, quadrilaterals and hexahedra),
117270* **Discontinuous Lagrange/DG ** (also known as Discontinuous Galerkin or DP elements) and
118271* **DQ ** (i.e. Discontinuous Lagrange/DG on lines, quadrilaterals and hexahedra).
119272
120- Vector, tensor and mixed function spaces can also be interpolated into from
273+ Vector, tensor, and mixed function spaces can also be interpolated into from
121274other meshes as long as they are constructed from these spaces.
122275
123276.. note ::
@@ -142,7 +295,7 @@ of the source mesh. Volume, surface and line integrals can therefore be
142295calculated by interpolating onto the mesh or
143296:ref: `immersed manifold <immersed_manifolds >` which defines the volume,
144297surface or line of interest in the domain. The integral itself is calculated
145- by calling :py:func: `~.assemble ` on an approriate form over the target mesh
298+ by calling :py:func: `~.assemble ` on an appropriate form over the target mesh
146299function space:
147300
148301.. literalinclude :: ../../tests/firedrake/regression/test_interpolation_manual.py
@@ -178,7 +331,7 @@ interpolation will raise a :py:class:`~.DofNotDefinedError`.
178331 :start-after: [test_cross_mesh 3]
179332 :end-before: [test_cross_mesh 4]
180333
181- This can be overriden with the optional ``allow_missing_dofs `` keyword
334+ This can be overridden with the optional ``allow_missing_dofs `` keyword
182335argument:
183336
184337.. literalinclude :: ../../tests/firedrake/regression/test_interpolation_manual.py
@@ -248,7 +401,7 @@ Interpolation from external data
248401
249402Unfortunately, UFL interpolation is not applicable if some of the
250403source data is not yet available as a Firedrake :py:class: `~.Function `
251- or UFL expression. Here we describe a recipe for moving external to
404+ or UFL expression. Here we describe a recipe for moving external data to
252405Firedrake fields.
253406
254407Let us assume that there is some function ``mydata(X) `` which takes as
0 commit comments