Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Symbolic FEM and Adjoint Forms

Australian National University
Photo by Logan Voss / Unsplash

In this article, we will see how the underworld3 sympy formulation combined with the PETSc finite element, strong-form framework builds model derivatives which can solve non-linear problems using a Newton method. This requires us to calculate the local derivative of the model with respect to the unknowns so that we know the direction to move towards a better solution.

If we differentiate the prediction a model makes with respect to one of its parameters, then we can determine how sensitive the prediction is to changes in that parameter and therefore whether the model can be used to constrain the value of that parameter. Similarly, model derivatives are used in data assimilation to determine how to adjust a model’s parameters so it better fits the observations we have 12.

The forward problem, solved by Newton

Underworld3 starts from the strong form of the equations which is usually how they are written in a textbook or a paper. These are symbolic, sympy objects that pass directly to compiled objects in PETSc 34: everything inside a divergence term goes into the flux F1\mathbf F_1, everything else into the force F0F_0. The discrete solution is written in the finite-element basis, u=∑jujϕju = \sum_j u_j \phi_j, and the weak form is one equation for each basis function ϕi\phi_i: the integral of the two templates against it and its gradient,

Ri(u; m)  =  ∫ΩF0(u,∇u; m) ϕi  +  F1(u,∇u; m)⋅∇ϕi  =  0.R_i(u;\, m) \;=\; \int_\Omega F_0(u, \nabla u;\, m)\,\phi_i \;+\; \mathbf F_1(u, \nabla u;\, m) \cdot \nabla\phi_i \;=\; 0 .

RR, the residual (1), is the out-of-balance force: what the assembled system has left over when the current uu is put into the governing equation. A solution is a uu that makes it vanish. Here mm is a set of parameters of the model (for example a Rayleigh number, or a constitutive modulus) that the user typically would adjust to run an example.

A Newton non-linear solver needs the derivative of RR with respect to the unknowns 5:

G0=∂F0∂u,G1=∂F0∂(∇u),G2=∂F1∂u,G3=∂F1∂(∇u),G_0 = \frac{\partial F_0}{\partial u}, \quad G_1 = \frac{\partial F_0}{\partial (\nabla u)}, \quad G_2 = \frac{\partial \mathbf F_1}{\partial u}, \quad G_3 = \frac{\partial \mathbf F_1}{\partial (\nabla u)} ,

which underworld computes directly from sympy, compiles and registers with PETSc. The Jacobian is the derivative of each equation RiR_i with respect to each nodal unknown uju_j. Since ∂u/∂uj=ϕj\partial u/\partial u_j = \phi_j and ∂(∇u)/∂uj=∇ϕj\partial (\nabla u)/\partial u_j = \nabla\phi_j, the chain rule turns the four blocks into Kij=∂Ri/∂ujK_{ij} = \partial R_i/\partial u_j, assembled element by element,

Kij=∫Ωϕi G0 ϕj  +  ϕi G1⋅∇ϕj  +  ∇ϕi⋅G2 ϕj  +  ∇ϕi⋅G3⋅∇ϕj.K_{ij} = \int_\Omega \phi_i \, G_0 \, \phi_j \;+\; \phi_i \, G_1 \cdot \nabla\phi_j \;+\; \nabla\phi_i \cdot G_2 \, \phi_j \;+\; \nabla\phi_i \cdot G_3 \cdot \nabla\phi_j .

K=∂R/∂uK = \partial R/\partial u, assembled by (3), is the tangent stiffness matrix. For a linear problem it is just the stiffness matrix, and

R=Ku−fR = Ku - f

For a nonlinear problem, K(u)K(u) is the stiffness of the material at the current solution state. Newton uses it to reduce the out-of-balance force: expanding R(u+δu)≈R(u)+K δuR(u + \delta u) \approx R(u) + K\,\delta u and asking for the left side to vanish gives the correction

K δu=−R(u),u←u+δu,K\,\delta u = -R(u), \qquad u \leftarrow u + \delta u ,

repeated until RR is zero to the tolerance asked for. For a linear problem (4) makes the expansion exact, and one step gives the solution.

The adjoint problem

Now suppose we want to choose the parameters mm so that the model reproduces some observations as well as it can. The observations are values diobsd_i^{\mathrm{obs}} — a surface velocity at a station, or a stress at a point — and the model predicts the same quantities, di(u)d_i(u), from its solution. The quality of the fit is the misfit

J(u)=12∑i(di(u)−diobs)2,J(u) = \frac12 \sum_i \big(d_i(u) - d_i^{\mathrm{obs}}\big)^2 ,

and the task is to minimise this by adjusting mm, under the constraint that uu always solves the model: R(u; m)=0R(u;\, m) = 0 at every mm we try. To do that we need the gradient dJ/dm\mathrm dJ/\mathrm dm (which way, and how far, to move each parameter) and the adjoint approach provides this for all the parameters at once - at the expense of one additional linear solve.

A bowl-shaped surface drawn as a grid above the parameter plane with axes m_1 to the right, m_2 towards the rear, and J upward. A red dot on the surface labelled J(m) marks the current parameters, with a dashed vertical line down to the plane beneath it. The tangent plane touches the surface at the dot, and a rust arrow labelled minus grad J points down the slope from it. A dashed line with small open circles runs from the dot down the bowl to a black dot at its lowest point, labelled m-star.

Figure 1:The misfit JJ as a surface over two parameters (in this case). A forward solve locates the model at one point on this surface. The adjoint solve gives the tangent plane at that location. Following the gradient downhill reaches m∗m^*, where the model best fits the observations.

The constraint is added to the minimisation with a Lagrange multiplier μ\mu 6:

L(u,μ; m)=J(u; m)+μTR(u; m),\mathcal L(u, \mu;\, m) = J(u;\, m) + \mu^{T} R(u;\, m) ,

which equals JJ for any μ\mu whenever the model is solved, because R=0R = 0 there. We are free to choose μ\mu, and we choose it so that L\mathcal L does not change, to first order, when the solution changes: ∂L/∂u=∂J/∂u+μTK=0\partial\mathcal L/\partial u = \partial J/\partial u + \mu^{T}K = 0. Transposed, that is the adjoint problem,

KTμ=−(∂J∂u)T,K^{T}\mu = -\Big(\frac{\partial J}{\partial u}\Big)^{T},

a linear solve for a field μ\mu on the same mesh as uu. It is linear because uu is already known: KK and ∂J/∂u\partial J/\partial u are evaluated at the converged solution and held fixed, and μ\mu appears only multiplied by them. The operator is the tangent stiffness KK at that solution, transposed. The force is the misfit’s derivative with respect to the solution,

∂J∂uj=∑i(di(u)−diobs) ∂di∂uj,\frac{\partial J}{\partial u_j} = \sum_i \big(d_i(u) - d_i^{\mathrm{obs}}\big)\,\frac{\partial d_i}{\partial u_j} ,

one entry per nodal unknown: each misfit di−diobsd_i - d_i^{\mathrm{obs}}, placed at the nodes its prediction depends on. This is the misfit written as a force, assembled like any finite-element force vector, and it is the only place the observations enter. With μ\mu chosen this way, the solution’s response to mm drops out of dL/dm\mathrm d\mathcal L/\mathrm dm, and what is left is the gradient for each parameter,

dJdmk=∂J∂mk+μT∂R∂mk,\frac{\mathrm d J}{\mathrm d m_k} = \frac{\partial J}{\partial m_k} + \mu^{T}\frac{\partial R}{\partial m_k} ,

where ∂R/∂mk\partial R/\partial m_k is a pseudo-force: the out-of-balance force that appears when mkm_k is nudged and the solution held still. It is an integral for each parameter, not a solve.

The appendix reaches (8) and (10) a second way, by differentiating the constraint directly, which shows what each term means.

Reducing JJ is then a loop, Figure 2: (1) solve the model, (2) solve the adjoint problem, (3) form the gradient for every parameter, (4) update the parameters and go round again. In practice a quasi-Newton method such as L-BFGS chooses the step α\alpha and builds up curvature from successive gradients.

Four rounded boxes in a square, numbered 1 to 4 and joined by arrows running clockwise. Top left, 1. Solve the model, R(u; m) = 0 by Newton. An arrow labelled K leads right to 2. Solve the adjoint, K-transpose mu equals minus the transpose of dJ/du, into which a heavier rust-coloured arrow labelled dJ/du comes down from the words observations d-obs above. A teal arrow labelled mu leads down to 3. Form the gradient, dJ/dm_k as an integral. An arrow labelled dJ/dm leads left to 4. Update the parameters, m becomes m minus alpha grad J, and an arrow labelled m leads up to close the loop. A dashed rust-coloured arrow labelled dR/dm_k runs diagonally from the model box to the gradient box.

Figure 2:One pass of an inversion. Teal carries a field, rust a force, black a quantity handed on. The tangent stiffness KK assembled by the forward solve is the operator of the adjoint solve, and the pseudo-force ∂R/∂mk\partial R/\partial m_k comes from the same residual; the observations enter only as the adjoint’s load.

Each pass costs one nonlinear solve and one linear one, irrespective of the number of parameters there are, and the fit improves until the gradient vanishes, at a minimum of JJ where no small change of mm brings the model closer to the observations. The minimum may be a local one: the gradient describes the surface only where the model is. In Underworld3, with the misfit J written as an integrand over the mesh, steps 2 and 3 are a single call,

dJdm = stokes.gradient(J, parameters=[m1, m2, m3])["parameters"]
dJdm[m1]                       # dJ/dm1

and step 4 is whatever optimiser one prefers.

Re-using the forward solve

The adjoint problem (8) asks for two things: the transposed stiffness KTK^{T}, and the pseudo-force ∂R/∂m\partial R/\partial m. Neither is a new object. Both are the blocks (2) that Newton already uses, read differently.

The transposed stiffness is a relabelling. KTK^{T} is the bilinear form of (3) with trial and test exchanged. Exchanging ϕi\phi_i and ϕj\phi_j leaves the two diagonal terms in place, transposed on their paired indices, and swaps the two mixed ones — the G1G_1 term becomes a G2G_2 term and the other way about, with their components exchanged:

G0  →  G0T,G3  →  G3T,G1  →  G2T,G2  →  G1T.G_0 \;\rightarrow\; G_0^{T}, \qquad G_3 \;\rightarrow\; G_3^{T}, \qquad G_1 \;\rightarrow\; G_2^{T}, \qquad G_2 \;\rightarrow\; G_1^{T} .

So the adjoint operator is assembled from the same four compiled kernels, registered in a different arrangement. We do that wherever the kernels carry the whole operator, rather than transposing an assembled matrix, which matters for Stokes, where the (u,p)(u,p) and (p,u)(p,u) blocks exchange places by the same rule and the composite operator stays a composite operator the Krylov solver can be preconditioned against. Nothing is differentiated a second time, and the assembled operator agrees with the explicit transpose to machine precision.

The parameter derivative is the same diff, on a different symbol. A named quantity mm — a friction coefficient, a reference viscosity, a continuation parameter — survives into F0F_0 and F1\mathbf F_1 as a symbol, because the solver carries the constitutive model’s own expression rather than the number it currently holds. Differentiating (1) with respect to mm instead of uu is the same operation that produced (2):

∂R∂m  =  ∫Ω∂F0∂m ϕ  +  ∂F1∂m⋅∇ϕ.\frac{\partial R}{\partial m} \;=\; \int_\Omega \frac{\partial F_0}{\partial m}\,\phi \;+\; \frac{\partial \mathbf F_1}{\partial m}\cdot\nabla\phi .

Accuracy of the gradient

Three things decide the accuracy of the gradient. The forward solve must be converged before the adjoint is taken, because μ\mu is the multiplier for the constraint R=0R = 0 and has meaning only where that holds. The adjoint solve itself can be run to a loose tolerance: every pass re-solves the forward problem and recomputes JJ, so a direction that is slightly off costs an extra iteration and the next misfit shows whether it helped. And the matrix must be the true derivative ∂R/∂u\partial R/\partial u. With a frozen-viscosity Picard matrix, μ\mu solves its own equation exactly and the gradient it gives belongs to no function, so the optimiser can stop where the true gradient is still large. The tangent is a property of the code rather than of the iterate, so it is checked once, by finite differences, and assembling it symbolically from the same residual is what makes it right by construction.

Derivation of the adjoint

The adjoint problem (8) and the gradient (10) also follow from differentiating the constraint directly, without the Lagrangian (7), and this route shows what each term means.

We explicitly wrote RR in terms of mm to demonstrate that there is an entire family of solutions to R=0R=0 that we can access by changing the model’s free parameters. If the model makes a prediction, J(u)J(u), we can ask how changing mm affects that prediction (the sensitivity of the prediction to mm). That is

dJdm\frac{\mathrm d J}{\mathrm d m}

If JJ is an underworld function then we can form a symbolic expression. First we note that uu and mm are not independent: the solution is constrained to the family of solutions of (1), R=0R = 0 holds for every mm and, because of this, dR/dmdR/dm is zero too

dRdm=∂R∂ududm+∂R∂m=0.\frac{\mathrm d R}{\mathrm d m} = \frac{\partial R}{\partial u}\frac{\mathrm d u}{\mathrm d m} + \frac{\partial R}{\partial m} = 0 .

The first factor is the tangent stiffness KK again, so

K dudm=−∂R∂m,sodudm=−K−1∂R∂m,K\,\frac{\mathrm d u}{\mathrm d m} = -\frac{\partial R}{\partial m}, \qquad \text{so} \qquad \frac{\mathrm d u}{\mathrm d m} = -K^{-1}\frac{\partial R}{\partial m},

∂R/∂m\partial R/\partial m is the pseudo-force of (10): the out-of-balance force that appears if the parameter is nudged and the solution is held still. It is assembled exactly as RR is, with ∂F0/∂m\partial F_0/\partial m and ∂F1/∂m\partial \mathbf F_1/\partial m in place of F0F_0 and F1\mathbf F_1.

We interpret (15) as: the sensitivity of the solution to a parameter is the response of the structure to that parameter’s pseudo-force.

The prediction depends on mm in the same two ways: directly, and through the solution. With ∂J/∂u\partial J/\partial u the row of derivatives ∂J/∂uj\partial J/\partial u_j with respect to the nodal unknowns — the prediction written as a force — the chain rule and (15) give

dJdm=∂J∂m+∂J∂ududm=∂J∂m−∂J∂u K−1∂R∂m.\frac{\mathrm d J}{\mathrm d m} = \frac{\partial J}{\partial m} + \frac{\partial J}{\partial u}\frac{\mathrm d u}{\mathrm d m} = \frac{\partial J}{\partial m} - \frac{\partial J}{\partial u}\,K^{-1}\frac{\partial R}{\partial m} .

Many parameters at once

A real model has many parameters, m1,…,mNm_1, \dots, m_N, and we want the sensitivity for all of them. We first write (16) like this:

dJdmk=∂J∂mk−∂J∂u K−1 ∂R∂mk,k=1,…,N.\frac{\mathrm d J}{\mathrm d m_k} = \frac{\partial J}{\partial m_k} - \frac{\partial J}{\partial u}\,K^{-1}\,\frac{\partial R}{\partial m_k}, \qquad k = 1, \dots, N .

These are NN equations that differ only in their last factor, the pseudo-force ∂R/∂mk\partial R/\partial m_k. The row in front of it, (∂J/∂u)K−1\big(\partial J/\partial u\big)K^{-1}, contains the prediction and the stiffness but no parameter; it is the same for every kk and we only need to compute it once.

Now define μ\mu through

μT=−∂J∂u K−1.\mu^{T} = -\frac{\partial J}{\partial u}\,K^{-1} .

We do not have access to K−1K^{-1} so we multiply both sides by KK on the right, take the transpose and substitute into (17) to recover (8):

KTμ=−(∂J∂u)T,dJdmk=∂J∂mk+μT∂R∂mk.K^{T}\mu = -\Big(\frac{\partial J}{\partial u}\Big)^{T}, \qquad \frac{\mathrm d J}{\mathrm d m_k} = \frac{\partial J}{\partial m_k} + \mu^{T}\frac{\partial R}{\partial m_k} .

One linear solve for μ\mu, whatever NN is, and then one product for each parameter. Written as fields, with μ=∑iμiϕi\mu = \sum_i \mu_i \phi_i, that product is

μT∂R∂mk=∫Ω∂F0∂mk μ  +  ∂F1∂mk⋅∇μ,\mu^{T}\frac{\partial R}{\partial m_k} = \int_\Omega \frac{\partial F_0}{\partial m_k}\,\mu \;+\; \frac{\partial \mathbf F_1}{\partial m_k}\cdot\nabla\mu ,

which is the pseudo-force’s weak form with μ\mu in the place of the test function.

μ\mu, the adjoint state, is the response of the same structure to the misfit’s force: an influence field, in the sense of an influence line. It says how strongly an out-of-balance force applied anywhere in the domain would tell on the misfit. With it in hand, the derivative for a parameter is μT∂R/∂m\mu^{T}\partial R/\partial m — the virtual work that parameter’s pseudo-force does against the influence field — and that is an integral, not a solve.

Solving (15) for each du/dmk\mathrm d u/\mathrm d m_k and contracting it with ∂J/∂u\partial J/\partial u gives the same numbers, at the cost of NN solves. That the two agree is Maxwell–Betti reciprocity: the work done by one force through the displacements of another equals the work done by the second through the displacements of the first. The transpose is what makes it true for a problem whose stiffness is not symmetric.

So the adjoint is cheap in parameters and dear in misfits. One solve for ten parameters or a thousand; another solve for every additional misfit. That suits a model with many dials and one thing you care about, which is the usual shape of the problem.

One stiffness, three forces

The Newton update, the forward sensitivity and the adjoint are three linear solves with the same matrix and different right-hand sides:

K δu=−R,K dudm=−∂R∂m,KTμ=−(∂J∂u)T.K\,\delta u = -R, \qquad\quad K\,\frac{\mathrm d u}{\mathrm d m} = -\frac{\partial R}{\partial m}, \qquad\quad K^{T}\mu = -\Big(\frac{\partial J}{\partial u}\Big)^{T} .

Newton has already assembled and preconditioned that matrix, so the adjoint costs one more linear solve.

The Underworld project is supported by AuScope and the Australian Government through the National Collaborative Research Infrastructure Strategy (NCRIS). Source code: github.com/underworldcode/underworld3

Comments
Discussion of these notes happens in GitHub Discussions, so it stays with the source and is searchable alongside it.
References
  1. Bunge, H.-P., Hagelberg, C. R., & Travis, B. J. (2003). Mantle circulation models with variational data assimilation: inferring past mantle flow and structure from plate motion histories and seismic tomography. Geophysical Journal International, 152(2), 280–301. 10.1046/j.1365-246x.2003.01823.x
  2. Liu, L., & Gurnis, M. (2008). Simultaneous inversion of mantle properties and initial conditions using an adjoint of mantle convection. Journal of Geophysical Research: Solid Earth, 113(B8), B08405. 10.1029/2008jb005594
  3. Moresi, L., Mansour, J., Giordani, J., Knepley, M., Knight, B., Graciosa, J. C., Gollapalli, T., Lu, N., & Beucher, R. (2025). Underworld3: Mathematically Self-Describing Modelling in Python for Desktop, HPC and Cloud. Journal of Open Source Software, 10(112), 7831. 10.21105/joss.07831
  4. Knepley, M. G., Brown, J., Rupp, K., & Smith, B. F. (2013). Achieving High Performance with Unified Residual Evaluation. 10.48550/arXiv.1309.1204
  5. Moresi, L., Mansour, J., & Giordani, J. (2026). How Underworld3 Turns SymPy into C. figshare. 10.6084/m9.figshare.33193572
  6. Plessix, R.-E. (2006). A review of the adjoint-state method for computing the gradient of a functional with geophysical applications. Geophysical Journal International, 167(2), 495–503. 10.1111/j.1365-246X.2006.02978.x