Contents

# Defining variational problems¶

Firedrake uses a high-level language, UFL, to describe variational
problems. To do this, we need a number of pieces. We need a
representation of the domain we’re solving the PDE on: Firedrake uses a
`Mesh()`

for this. On top of this mesh,
we build `FunctionSpace`

s which
define the space in which the solutions to our equation live. Finally
we define `Function`

s in those
function spaces to actually hold the solutions.

## Constructing meshes¶

Firedrake can read meshes in Gmsh, triangle, CGNS, and
Exodus formats. To build a mesh one uses the `Mesh()`

constructor, passing the name of the file as an argument, which see
for more details. The mesh type is determined by the file extension,
for example if the provided filename is `coastline.msh`

the mesh is
assumed to be in Gmsh format, in which case you can construct a mesh
object like so:

```
coastline = Mesh("coastline.msh")
```

This works in both serial and parallel, Firedrake takes care of decomposing the mesh among processors transparently.

### Reordering meshes for better performance¶

Most mesh generators produce badly numbered meshes (with bad data
locality) which can reduce the performance of assembling and solving
finite element problems. By default then, Firedrake reorders input
meshes to improve data locality by performing reverse Cuthill-McKee
reordering on the adjacency matrix of the input mesh. If you know
your mesh has a good numbering (perhaps your mesh generator uses space
filling curves to number entities) then you can switch off this
reordering by passing `reorder=False`

to the appropriate
`Mesh()`

constructor. You can control Firedrake’s default
behaviour in reordering meshes with the `"reorder_meshes"`

parameter. For example, to turn off mesh reordering globally:

```
from firedrake import *
parameters["reorder_meshes"] = False
```

The parameter passed in to the mesh constructor overrides this default value.

Note

Firedrake numbers degrees of freedom in a function space by visiting each cell in order and performing a depth first numbering of all degrees of freedom on that cell. Hence, if your mesh has a good numbering, the degrees of freedom will too.

### Utility mesh functions¶

As well as offering the ability to read mesh information from a file,
Firedrake also provides a number of built in mesh types for a number
of standard shapes. 1-dimensional intervals may be constructed with
`IntervalMesh()`

; 2-dimensional rectangles with
`RectangleMesh()`

; and 3-dimensional boxes with
`BoxMesh()`

. There are also more specific constructors (for
example to build unit square meshes). See
`utility_meshes`

for full details.

### Immersed manifolds¶

In addition to the simple meshes described above, Firedrake also has
support for solving problems on orientable immersed manifolds. That is, meshes in which the entities are
*immersed* in a higher dimensional space. For example, the surface of
a sphere in 3D.

If your mesh is such an immersed manifold, you need to tell Firedrake that the geometric dimension of the coordinate field (defining where the points in mesh are) is not the same as the topological dimension of the mesh entities. This is done by passing an optional second argument to the mesh constructor which specifies the geometric dimension. For example, for the surface of a sphere embedded in 3D we use:

```
sphere_mesh = Mesh('sphere_mesh.node', dim=3)
```

Firedrake provides utility meshes for the surfaces of spheres immersed
in 3D that are approximated using an icosahedral mesh. You can
either build a mesh of the unit sphere with
`UnitIcosahedralSphereMesh()`

, or a mesh of a
sphere with specified radius using
`IcosahedralSphereMesh()`

. The meshes are
constructed by recursively refining a regular icosahedron, you can specify the refinement level by passing a
non-zero `refinement_level`

to the constructor. For example, to
build a sphere mesh that approximates the surface of the Earth (with a
radius of 6371 km) that has subdivided the original icosahedron 7
times we would write:

```
earth = IcosahedralSphereMesh(radius=6371, refinement_level=7)
```

#### Ensuring consistent cell orientations¶

Variational forms that include particular function spaces (those
requiring a *contravariant Piola transform*), require information
about the orientation of the cells. For normal meshes, this can be
deduced automatically. However, when using immersed meshes, Firedrake
needs extra information to calculate the orientation of each cell
relative to some global orientation. This
is used by Firedrake to ensure that the cell normal on,
say, the surface of a sphere, uniformly points outwards. To do this,
after constructing an immersed mesh, we must initialise the cell
orientation information. This is carried out with the function
`init_cell_orientations()`

, which
takes a UFL expression or an `Expression`

used to produce
the reference normal direction. For example, on the sphere mesh of
the earth defined above we can initialise the cell orientations
relative to vector pointing out from the origin:

```
earth.init_cell_orientations(SpatialCoordinate(earth))
```

However, a more complicated expression would be needed to initialise the cell orientations on a toroidal mesh.

### Semi-structured extruded meshes¶

Firedrake has special support for solving PDEs on high-aspect ratio
domains, such as in the ocean or atmosphere, where the numerics
dictate that the “short” dimension should be structured. These are
termed *extruded meshes* and have a separate section in the manual.

## Building function spaces¶

Now that we have a mesh of our domain, we need to build the function
spaces the solution to our PDE
will live in, along with the spaces for the trial and test functions.
To do so, we use the `FunctionSpace()`

constructor.
This is the only way to obtain a function space for a scalar variable,
such as pressure, which has a single value at each point in the
domain.

To construct a function space, you must specify its family and polynomial degree. To build a scalar-valued function space of continuous piecewise-cubic polynomials, we write:

```
V = FunctionSpace(mesh, "Lagrange", 3)
```

There are three main routes to obtaining a function space for a
vector-valued variable such as velocity. Firstly, you can pass the
`FunctionSpace()`

constructor a natively *vector-valued*
family such as `"Raviart-Thomas"`

. Secondly, you may use the
`VectorFunctionSpace()`

constructor with a *scalar-valued*
family, which gives a vector-valued space where each component is
identical to the appropriate scalar-valued
`FunctionSpace`

. Thirdly, you can create a
`VectorElement`

directly (which is itself
*vector-valued* and pass that to the `FunctionSpace()`

constructor).

To build a vector-valued function space using the lowest-order
`Raviart-Thomas`

elements, we write

```
V = FunctionSpace(mesh, "Raviart-Thomas", 1)
```

To build a vector-valued function space for which each component is a discontinuous piecewise-quadratic polynomial, we can write either

```
V = VectorFunctionSpace(mesh, "Discontinuous Lagrange", 2)
```

or

```
Vele = VectorElement("Discontinuous Lagrange", cell=mesh.ufl_cell(), degree=2)
V = FunctionSpace(mesh, Vele)
```

Firedrake supports the use of all function spaces generated by FIAT.

### Advanced usage of `VectorFunctionSpace`

¶

By default, the number of components of a
`VectorFunctionSpace()`

is the geometric dimension of the
mesh (e.g. 3, if the mesh is 3D). However, sometimes we might want
the number of components in the vector to differ from the geometric
dimension of the mesh. We can do this by passing a value for the
`dim`

argument to the `VectorFunctionSpace()`

constructor.
For example, if we wanted a vector-valued function space on the surface
of a unit sphere mesh with only 2 components, we might write:

```
mesh = UnitIcosahedralSphereMesh(refinement_level=3)
V = VectorFunctionSpace(mesh, "Lagrange", 1, dim=2)
```

### Mixed function spaces¶

Many PDEs are posed in terms
of multiple, coupled, variables. The variational problem for such a
PDE uses a so-called *mixed* function space. In Firedrake, this is
represented by a `MixedFunctionSpace`

. We can either
build such a space by invoking the `constructor directly`

, or, more readably, by taking existing function
spaces and multiplying them together using the `*`

operator. For
example:

```
V = FunctionSpace(mesh, 'RT', 1)
Q = FunctionSpace(mesh, 'DG', 0)
W = V*Q
```

is equivalent to:

```
V = FunctionSpace(mesh, 'RT', 1)
Q = FunctionSpace(mesh, 'DG', 0)
W = MixedFunctionSpace([V, Q])
```

### Function spaces on extruded meshes¶

On extruded meshes, we build function spaces by taking a tensor product of the base (“horizontal”) space and the extruded (“vertical”) space. Firedrake allows us to separately choose the horizontal and vertical spaces when building a function space on an extruded mesh. We refer the reader to the manual section on extrusion for details.

## Expressing a variational problem¶

Firedrake uses the UFL language to express variational problems. For complete documentation, we refer the reader to the UFL package documentation and the description of the language in TOMS. We present a brief overview of the syntax here, for a more didactic introduction, we refer the reader to the Firedrake tutorial examples.

### Building test and trial spaces¶

Now that we have function spaces that our solution will live in, the
next step is to actually write down the variational form of the
problem we wish to solve. To do this, we will need a test function in
an appropriate space along with a function to hold the solution and
perhaps a trial function. Test functions are obtained via a call to
`TestFunction`

, trial functions via
`TrialFunction`

and functions with
`Function`

. The former two are purely
symbolic objects, the latter contains storage for the coefficients of
the basis functions in the function space. We use them as follows:

```
u = TrialFunction(V)
v = TestFunction(V)
f = Function(V)
```

Note

A newly allocated `Function`

has
coefficients which are all zero.

If `V`

above were a
`MixedFunctionSpace`

, the test and
trial functions we obtain are for the combined mixed space. Often, we
would like to have test and trial functions for the subspaces of the
mixed space. We can do this by asking for
`TrialFunctions`

and
`TestFunctions`

, which return an ordered
tuple of test and trial functions for the underlying spaces. For
example, if we write:

```
V = FunctionSpace(mesh, 'RT', 1)
Q = FunctionSpace(mesh, 'DG', 0)
W = V * Q
u, p = TrialFunctions(W)
v, q = TestFunctions(W)
```

then `u`

and `v`

will be, respectively, trial and test
functions for `V`

, while `p`

and `q`

will be trial and test
functions for `Q`

.

Note

If we intend to build a variational problem on a mixed space, we cannot build the individual test and trial functions on the function spaces that were used to construct the mixed space directly. The functions that we build must “know” that they come from a mixed space or else Firedrake will not be able to assemble the correct system of equations.

### A first variational form¶

With our test and trial functions defined, we can write down our first variational form. Let us consider solving the identity equation:

where \(\Omega\) is the unit square, using piecewise linear polynomials for our solution. We start with a mesh and build a function space on it:

```
mesh = UnitSquareMesh(10, 10)
V = FunctionSpace(mesh, "CG", 1)
```

now we need a test function, and since `u`

is unknown, a trial
function:

```
u = TrialFunction(V)
v = TestFunction(V)
```

finally we need a function to hold the right hand side \(f\) which we will populate with the x component of the coordinate field.

```
f = Function(V)
x = SpatialCoordinate(mesh)
f.interpolate(x[0])
```

For details on how `interpolate()`

works, see the
appropriate section in the manual. The
variational problem is to find \(u \in V\) such that

we define the variational problem in UFL with:

```
a = u*v*dx
L = f*v*dx
```

Where the `dx`

indicates that the integration should be carried out
over the cells of the mesh. UFL can also express integrals over the
boundary of the domain, using `ds`

, and the interior facets of the
domain, using `dS`

.

How to solve such variational problems is the subject of the next section, but for completeness we show how to do it here. First we define a function to hold the solution

```
s = Function(V)
```

and call `solve()`

to solve the variational
problem:

```
solve(a == L, s)
```

## Forms with constant coefficients¶

Many PDEs will contain values that are constant over the whole mesh,
but may vary in time. For example, a time-varying diffusivity, or a
time-dependent forcing function. Although you can create a new form
for each new value of this constant, this will not be efficient, since
Firedrake must generate new code each time the value changes. A
better option is to use a `Constant`

coefficient. This
object behaves exactly like a `Function`

, except that it
has a single value over the whole mesh. One may assign a new value to
the `Constant`

using the `assign()`

method. As an example, let us consider a form which contains a time
varying constant which we wish to assemble in a time loop. We can use
a `Constant`

to do this:

```
...
t = 0
dt = 0.1
from math import exp
c = Constant(exp(-t))
# Exponentially decaying RHS
L = f*v*c*dx
while t < tend:
solve(a == L, ...)
t += dt
c.assign(exp(-t))
```

Warning

Although UFL supports computing the derivative of a form with
respect to a `Constant`

, the resulting form will have
an unknown in the reals, which is currently unsupported by
Firedrake.

## Incorporating boundary conditions¶

Boundary conditions enter the variational problem in one of two ways.
Natural (often termed Neumann or weak) boundary conditions,
which prescribe values of the derivative of the solution, are
incorporated into the variational form. Essential (often termed
Dirichlet or strong) boundary conditions, which prescribe values
of the solution, become prescriptions on the function space. In
Firedrake, the former are naturally expressed as part of the
formulation of the variational problem, the latter are represented as
`DirichletBC`

objects and are applied when
solving the variational problem. Construction of such a strong
boundary condition requires a function space (to impose the boundary
condition in), a value and a subdomain to apply the boundary condition
over:

```
bc = DirichletBC(V, value, subdomain_id)
```

The `subdomain_id`

is an integer indicating which section of the
mesh the boundary condition should be applied to. The subdomain ids
for the various utility meshes are
described in their respective constructor documentation. For
externally generated meshes, Firedrake just uses whichever ids the
mesh generator provided. The `value`

may be either a scalar, or
more generally an `Expression`

, `Function`

or
`Constant`

of the appropriate shape. You may also supply
an iterable of literal constants. Hence the following two are
equivalent:

```
bc1 = DirichletBC(V, Expression(('1.0', '2.0')), 1)
bc2 = DirichletBC(V, (1.0, 2.0), 1)
```

Strong boundary conditions are applied in the solve by passing a list of boundary condition objects:

```
solve(a == L, bcs=[bc])
```

See the next section for a more complete description of the interface Firedrake provides to solve PDEs. The details of how Firedrake applies strong boundary conditions are slightly involved and therefore have their own section in the manual.

### Specifying conditions on components of a space¶

When solving a problem defined on either a
`MixedFunctionSpace`

or a rank-1 `FunctionSpace`

, it is
common to want to specify boundary values for only some of the
components. In the former case, this is the only supported method of
setting boundary values, the latter also supports setting the value
for all components. In both cases, the syntax is the same. When
defining the `DirichletBC`

we must index the function space
used. For example, to specify that the third component of a
`VectorFunctionSpace()`

should take the boundary value 0, we write:

```
V = VectorFunctionSpace(mesh, ...)
bc = DirichletBC(V.sub(2), Constant(0), boundary_ids)
```

Note that when indexing a `MixedFunctionSpace`

in this
manner, one pulls out the indexed sub-space, rather than a component.
For example, to specify the velocity values in a Taylor-Hood
discretisation we write:

```
V = VectorFunctionSpace(mesh, "CG", 2)
P = FunctionSpace(mesh, "CG", 1)
W = V*P
bcv = DirichletBC(W.sub(0), Constant((0, 0)), boundary_ids)
```

If we only wanted to specify a single component, we would have to index twice. For example, specifying that the x-component of the velocity is zero, using the same function space definitions:

```
bcv_x = DirichletBC(W.sub(0).sub(0), Constant(0), boundary_ids)
```

Note

Extruded meshes have full support for indexing
`MixedFunctionSpace`

s, but currently do not support
indexing on `VectorFunctionSpace()`

s.

### Boundary conditions in discontinuous spaces¶

The default method Firedrake uses to determine where to apply strong
boundary conditions is `"topological"`

, meaning that nodes
topologically associated with a boundary facet will be included. In
discontinuous spaces, however, the nodes to be included do not all
live on boundary facets, in this case, you should use the
`"geometric"`

method for determining boundary condition
nodes. In this case, nodes associated with basis functions that do
not vanish on the boundary are included. This method can be used to
impose strong boundary conditions on discontinuous galerkin spaces, or
no-slip conditions on \(H(\textrm{div})\) spaces. To select the method used for
determining boundary condition nodes, use the `method`

argument to the `DirichletBC`

constructor. For example, to
select geometric boundary node determination we would write:

```
V = FunctionSpace(mesh, 'DG', 2)
bc = DirichletBC(V, 1.0, subdomain_id, method="geometric")
...
```

### Time dependent boundary conditions¶

Imposition of time-dependent boundary conditions can by carried out by
modifying the value in the appropriate `DirichletBC`

object. Note that if you use a literal value to initialise the
boundary condition object within the timestepping loop, this will
necessitate a recompilation of code every time the boundary condition
changes. For this reason we either recommend using a
`Constant`

if the boundary condition is spatially uniform,
or a UFL expression if it has both space and
time-dependence. For example, a purely time-varying boundary
condition might be implemented as:

```
c = Constant(sin(t))
bc = DirichletBC(V, c, 1)
while t < T:
solve(F == 0, bcs=[bc])
t += dt
c.assign(sin(t))
```

If the boundary condition instead has both space and time dependence we can write:

```
c = Constant(t)
e = sin(x[0]*c)
bc = DirichletBC(V, e, 1)
while t < T:
solve(F == 0, bcs=[bc])
t += dt
c.assign(t)
```

## More complicated forms¶

UFL is a fully-fledged language for expressing variational problems, and hence has operators for all appropriate vector calculus operations along with special support for discontinuous galerkin methods in the form of symbolic expressions for facet averages and jumps. For an introduction to these concepts we refer the user to the UFL manual as well as the Firedrake tutorials which cover a wider variety of different problems.