Features of dolfin-adjoint

Generality

dolfin-adjoint works for both steady and time-dependent models, and for both linear and nonlinear models. The user interface is exactly the same in both cases. For an example of adjoining a nonlinear time-dependent model, see the tutorial.

Ease of use

dolfin-adjoint has been carefully designed to try to make its use as easy as possible. In many cases the only change to the forward model is to add

from dolfin_adjoint import *

at the top of the model. For example, deriving the adjoint of the tutorial example requires adding precisely two lines to the forward model; implementing a checkpointing scheme requires adding another two. dolfin-adjoint also makes it extremely easy to verify the correctness of the adjoint model. It offers a powerful syntax for expressing general functionals.

Efficiency

Efficiency of the resulting model is absolutely crucial for real applications. The efficiency of an adjoint model is measured as (time for forward and adjoint run)/(time for forward run). Naumann (2011) states that a typical value for this ratio when using algorithmic differentiation tools is in the range 3–30. By contrast, dolfin-adjoint is extremely efficient; consider the following examples from the papers:

PDE Theoretical optimum Achieved efficiency
Cahn-Hilliard 1.2 1.22
Stokes 2.0 1.86
Viscoelasticity 2.0 2.029
Gross-Pitaevskii 1.5 1.54
Gray-Scott 2.0 2.04
Navier-Stokes 1.33 1.41
Mathematical programming with equilibrium constraints 1.125 1.126
Shallow water 1.125 1.125
Wetting and drying 1.5 1.55

Parallelism

Parallelism is ubiquitous in modern computational science. However, applying algorithmic differentiation to parallel codes is still a major research challenge. Algorithmic differentiation tools must be specially modified to understand MPI and OpenMP directives, and translate them into their parallel equivalents. By contrast, because of the high-level abstraction taken in libadjoint, the problem of parallelism simply disappears. In fact, there is no code whatsoever in either dolfin-adjoint or libadjoint to handle parallelism; by deriving the adjoint at the right level of abstraction, the problem no longer exists. If the forward model runs in parallel, the adjoint model also runs in parallel, with no modification.

For more details, see the manual section on parallelism and the dolfin-adjoint paper.

Checkpointing

The adjoint model is a linearisation of the forward model. If the forward model is nonlinear, then the solution of that forward model must be available to linearise the forward model. By default, dolfin-adjoint stores every variable computed in memory, as this is the fastest and most straightforward option; however, this may not be feasible for large runs, or for runs with very many timesteps.

The solution to this problem is to employ a checkpointing scheme. Rather than store every variable during the forward run, checkpoints are stored at strategically chosen intervals, from which the model may recompute the missing solutions. During the adjoint run, if a forward variable is necessary and unavailable, the forward model is restarted from the nearest available checkpoint to compute the missing solutions; once these are available, the adjoint run continues.

Thus, to employ a checkpointing scheme, the control flow of the adjoint run must seamlessly jump between assembling and solving the adjoint equations, and assembling and solving parts of the forward run. Coding a checkpointing scheme is quite complicated, and so most hand-coded adjoint models do not use them. However, the libadjoint library underlying dolfin-adjoint embeds the excellent revolve library of Griewank and Walther, and can automatically employ optimal checkpointing schemes for almost no marginal user effort.

For more details, see the manual section on checkpointing and the dolfin-adjoint paper.

Optimisation with PDE constraints

Many computational problems in engineering and science can be formulated as optimisation problems in which a system of partial differential equation occur as a constraint. To solve these problems efficiently, the use of gradient based optimisation algorithms is essential.

The fact that dolfin-adjoint has direct access to the gradient information made it possible to directly interface dolfin-adjoint to a range of powerful optimisation algorithms. That means, that an existing FEniCS forward model can be easily used in the context of an optimisation problem with minimal development effort.

For more details, see the manual section on optimisation.

Generalised stability analysis

Generalised stability analysis is an extension of linear stability analysis with two important features: it allows for the stability analysis of non-normal systems that permit transient perturbations that grow in magnitude before decaying, and it allows for the analysis of the stability of time-dependent base solutions.

The core computation involved in conducting a generalised stability analysis is the singular value decomposition of the propagator: each action of the propagator requires the solution of the tangent linear system, and each adjoint action (for the singular value decomposition) requires the solution of the adjoint system. Since dolfin-adjoint automates the solution of these systems, it can also automate generalised stability analysis by embedding these computations in a matrix-free Krylov–Schur algorithm for computing the SVD of the propagator.

For more details, see the manual section on generalised stability analysis.