Skip to article content

DG Methods for Advection-Diffusion in DOLFINx

A mild introduction to suffering | How far does the rabbit hole go?

The Advection-Diffusion Equation

Advection-diffusion equations describe how a quantity is transported through a medium by two competing mechanisms: advection (bulk transport carried along by a flow) and diffusion (spreading down a concentration gradient). In this work the quantity of interest is a species concentration in a fluid, specifically hydrogen, but the same equation governs heat transport, pollutant dispersion, and many other physical problems.

We consider the steady-state form for a scalar quantity uu (m3^{-3}):

(wu)(Du)=fin Ω\nabla \cdot (\mathbf{w} u) - \nabla \cdot (D \nabla u) = f \quad \text{in } \Omega

where w\mathbf{w} is a prescribed advective velocity field (m\,s1^{-1}), DD is the diffusion coefficient (m2^{2}\,s1^{-1}), ff is a volumetric source term (m3^{-3}\,s1^{-1}), and ΩRd\Omega \subset \mathbb{R}^d is the computational domain.

The relative importance of the two transport mechanisms is captured by the cell Péclet number:

Pe=wh2D\text{Pe} = \frac{|\mathbf{w}| h}{2D}

where hh is the local mesh element size. The Péclet number is a dimensionless ratio: large w|\mathbf{w}| or small DD pushes it up, and small w|\mathbf{w}| or large DD pushes it down.

Numerical Challenges and the Case for DG

Standard continuous Galerkin (CG) finite element methods work well when diffusion dominates, but as the Péclet number grows the solution develops oscillations that are entirely numerical in origin and have no physical meaning. To understand why, it helps to think about how information travels in an advection-dominated problem. If a fluid is moving to the right, the concentration at a point is determined by what is upstream, not downstream. A numerical scheme that respects this directionality is said to have upwinding: it biases the treatment of the advection term towards information coming from the upstream direction. Standard CG treats information from both sides of an element face symmetrically, which is appropriate for diffusion but wrong for advection, and this mismatch is what produces the spurious oscillations.

Several stabilisation strategies have been developed to recover stability within the CG framework:

These methods can be effective, but all require tuning parameters, add complexity to the formulation, and are fundamentally workarounds for a framework not designed with advection-dominated transport in mind.

DG methods address the root cause rather than patching the symptom. In DG the approximate solution is allowed to be discontinuous across element boundaries, and information is passed between elements through numerical flux functions defined on each shared face. The choice of flux naturally encodes upwinding for the advection term, without any add-on stabilisation. This gives DG several structural advantages:

The trade-off is a larger global system (DG has more degrees of freedom than CG for the same mesh) and the need to choose a penalty parameter for the diffusion term carefully. Both are manageable in practice.

Derivation of the DG Weak Form

Notation

The domain Ω\Omega is divided into a mesh Th={K}\mathcal{T}_h = \{K\}: a collection of non-overlapping elements KK, triangles in 2D or tetrahedra in 3D, that together cover Ω\Omega. The subscript hh refers to the typical element size.

Within this mesh, every shared boundary between two adjacent elements is an interior face. The set of all interior faces is FI\mathcal{F}_I, and the set of faces on the outer boundary of Ω\Omega is FB\mathcal{F}_B.

In DG, the approximate solution is allowed to be a different polynomial on each element and may be discontinuous across element boundaries. The function space that encodes this is:

Vh={vL2(Ω):vKPp(K), KTh}V_h = \{v \in L^2(\Omega) : v|_K \in \mathbb{P}_p(K),\ \forall K \in \mathcal{T}_h\}

This says: VhV_h contains all functions that, when restricted to any element KK, are a polynomial of degree at most pp, with no continuity requirement between elements.

Because solutions in VhV_h can be discontinuous, each interior face FFIF \in \mathcal{F}_I has two values, one from each neighbouring element K0K_0 and K1K_1. Let n=n0\mathbf{n} = \mathbf{n}_0 be the outward unit normal from K0K_0 (pointing into K1K_1). For a quantity qq with values q0q_0 and q1q_1 on each side, we define the average and jump operators:

q:=q0+q12(average),q:=q0q1(jump)\langle q \rangle := \frac{q_0 + q_1}{2} \quad \text{(average)}, \qquad \llbracket q \rrbracket := q_0 - q_1 \quad \text{(jump)}

The average is the mean value across the face; the jump measures the size of the discontinuity, which is zero if the solution is continuous at that face. For a vector τ\boldsymbol{\tau}:

τ:=τ0+τ12,τ:=τ0n0+τ1n1\langle \boldsymbol{\tau} \rangle := \frac{\boldsymbol{\tau}_0 + \boldsymbol{\tau}_1}{2}, \qquad \llbracket \boldsymbol{\tau} \rrbracket := \boldsymbol{\tau}_0 \cdot \mathbf{n}_0 + \boldsymbol{\tau}_1 \cdot \mathbf{n}_1

where τ=(τ0τ1)n\llbracket \boldsymbol{\tau} \rrbracket = (\boldsymbol{\tau}_0 - \boldsymbol{\tau}_1) \cdot \mathbf{n} is the scalar normal jump. On a boundary face there is only one element, so q=q\langle q \rangle = q and q=q\llbracket q \rrbracket = q.

From Strong Form to Weak Form

The strong form of the PDE is an equation that must hold pointwise everywhere in Ω\Omega, requiring uu to be smooth enough to be differentiated twice. On a mesh where the solution can be discontinuous, this is too strict.

The weak form relaxes this by multiplying the PDE by a test function vv and integrating over the domain. Rather than demanding the equation hold at every point, we demand it holds in an average sense against every possible vv. The test function is chosen from the same space VhV_h as the solution, which is called the Galerkin approach.

We derive the weak form element by element. Multiplying by vv and integrating over a single element KK gives:

K(wu)vdxK(Du)vdx=Kfvdx\int_K \nabla \cdot (\mathbf{w} u)\, v\, \mathrm{d}x - \int_K \nabla \cdot (D\nabla u)\, v\, \mathrm{d}x = \int_K f\, v\, \mathrm{d}x

Applying the divergence theorem (the multi-dimensional analogue of integration by parts) moves one derivative off uu and onto vv, reducing the smoothness requirement on uu. It also produces boundary integrals on K\partial K, which represent the flux of information through the faces of element KK:

K(wu)vdxadvection, bulk+K(wnK)uvdsadvection, face flux+KDuvdxdiffusion, bulkKD(unK)vdsdiffusion, face flux=Kfvdx\begin{align} &\underbrace{-\int_K (\mathbf{w} u) \cdot \nabla v\, \mathrm{d}x}_{\text{advection, bulk}} + \underbrace{\int_{\partial K} (\mathbf{w} \cdot \mathbf{n}_K)\, u\, v\, \mathrm{d}s}_{\text{advection, face flux}} \\ &+ \underbrace{\int_K D\nabla u \cdot \nabla v\, \mathrm{d}x}_{\text{diffusion, bulk}} - \underbrace{\int_{\partial K} D(\nabla u \cdot \mathbf{n}_K)\, v\, \mathrm{d}s}_{\text{diffusion, face flux}} = \int_K f\, v\, \mathrm{d}x \end{align}

To form the global problem we sum over all elements KThK \in \mathcal{T}_h. The face integrals on K\partial K are shared between neighbouring elements and must be assembled carefully using the average and jump operators. The treatment of the advection and diffusion face terms differs, and is described in the two sections below.

Diffusion Term: SIPG

When we sum the diffusion face integrals over all elements, each interior face FF is counted twice, once from each side. Using the average and jump operators this collapses to:

KKD(unK)vds=FFIF(Dunv+Duv)ds+FFBFD(un)vds\sum_K \int_{\partial K} D(\nabla u \cdot \mathbf{n}_K)\, v\, \mathrm{d}s = \sum_{F \in \mathcal{F}_I} \int_F \left( \langle D\nabla u \rangle \cdot \mathbf{n}\, \llbracket v \rrbracket + \llbracket D\nabla u \rrbracket\, \langle v \rangle \right) \mathrm{d}s + \sum_{F \in \mathcal{F}_B} \int_F D(\nabla u \cdot \mathbf{n})\, v\, \mathrm{d}s

For a smooth exact solution, the normal gradient is the same on both sides of every face, so Du=0\llbracket D\nabla u \rrbracket = 0 and the second interior term vanishes. We can drop it without changing which solutions satisfy the equations, a property called consistency. After dropping it, however, the resulting form is not symmetric and is not yet stable. The Symmetric Interior Penalty Galerkin (SIPG) method adds two further face terms to fix both issues:

Fdiff(u;v)=KKDuvdxbulk diffusionFFIFDunvdsconsistencyFFIFDvnudssymmetry+FFIFαDhuvdspenalty\begin{align} F_\text{diff}(u; v) &= \underbrace{\sum_K \int_K D\, \nabla u \cdot \nabla v\, \mathrm{d}x}_{\text{bulk diffusion}} \\ &\quad - \underbrace{\sum_{F \in \mathcal{F}_I} \int_F \langle D\nabla u \rangle \cdot \mathbf{n}\, \llbracket v \rrbracket\, \mathrm{d}s}_{\text{consistency}} \\ &\quad - \underbrace{\sum_{F \in \mathcal{F}_I} \int_F \langle D\nabla v \rangle \cdot \mathbf{n}\, \llbracket u \rrbracket\, \mathrm{d}s}_{\text{symmetry}} \\ &\quad + \underbrace{\sum_{F \in \mathcal{F}_I} \int_F \frac{\alpha D}{h}\, \llbracket u \rrbracket \llbracket v \rrbracket\, \mathrm{d}s}_{\text{penalty}} \end{align}

The role of each term:

Advection Term: Upwind Flux

When the advection face integrals are summed over all elements, each interior face FF produces a term involving uu evaluated from both sides. Since uu is discontinuous, we must choose which value to use, and this is where upwinding is applied.

The upwind value of uu on face FF is:

uup={u0if wn>0(flow from K0 to K1)u1if wn0(flow from K1 to K0)u_\text{up} = \begin{cases} u_0 & \text{if } \mathbf{w} \cdot \mathbf{n} > 0 \quad (\text{flow from } K_0 \text{ to } K_1) \\ u_1 & \text{if } \mathbf{w} \cdot \mathbf{n} \leq 0 \quad (\text{flow from } K_1 \text{ to } K_0) \end{cases}

Always take uu from the element the flow is coming from. This is the DG implementation of the upwinding principle, and it can be written compactly as:

(wn)uup=wun+12wnu(\mathbf{w} \cdot \mathbf{n})\, u_\text{up} = \langle \mathbf{w} u \rangle \cdot \mathbf{n} + \tfrac{1}{2}|\mathbf{w} \cdot \mathbf{n}|\, \llbracket u \rrbracket

The interior advection contribution is then:

Fadv(u;v)=KK(wu)vdxbulk advection+FFIF(wn)uupvdsinterior upwind flux\begin{align} F_\text{adv}(u; v) &= \underbrace{-\sum_K \int_K (\mathbf{w} u) \cdot \nabla v\, \mathrm{d}x}_{\text{bulk advection}} \\ &\quad + \underbrace{\sum_{F \in \mathcal{F}_I} \int_F (\mathbf{w} \cdot \mathbf{n})\, u_\text{up}\, \llbracket v \rrbracket\, \mathrm{d}s}_{\text{interior upwind flux}} \end{align}

The bulk advection term moves information within each element; the interior upwind flux passes information between elements using the upstream value. Together they give DG its natural stability for advection without any additional tuning parameter.

Note: the compact identity for uupu_\text{up} assumes wn\mathbf{w}\cdot\mathbf{n} is single-valued on FF. This holds when w\mathbf{w} is prescribed analytically or lives in a continuous FE space, but requires care when the velocity is interpolated from an external solver, as discussed in the section on coupling with an external velocity field.

Boundary contributions depend on the flow direction and are treated in the next section.

Boundary Conditions

Each type of boundary condition enters the formulation by modifying the face integrals on FB\mathcal{F}_B that appeared during integration by parts. On a boundary face there is only one element, so there is no “other side” to take values from. Instead, the boundary data closes the equations.

Diffusion

Dirichlet (u=gDu = g_D on ΓD\Gamma_D):

A Dirichlet condition prescribes the solution value directly. In a classical finite element method this would mean enforcing u=gDu = g_D at shared nodes. In DG, where there are no shared nodes between elements, the condition is instead imposed weakly through the face integrals, by treating the boundary face exactly like an interior SIPG face but with gDg_D playing the role of the solution on the outside. Written as a residual contribution in terms of the error (ugD)(u - g_D):

FdiffD(u;v)=ΓDD(un)vdsflux consistencyΓDD(vn)(ugD)dssymmetry+ΓDαDh(ugD)vdspenalty\begin{align} F_\text{diff}^D(u; v) &= \underbrace{- \int_{\Gamma_D} D(\nabla u \cdot \mathbf{n})\, v\, \mathrm{d}s}_{\text{flux consistency}} \\ &\quad - \underbrace{\int_{\Gamma_D} D(\nabla v \cdot \mathbf{n})\, (u - g_D)\, \mathrm{d}s}_{\text{symmetry}} \\ &\quad + \underbrace{\int_{\Gamma_D} \frac{\alpha D}{h}\, (u - g_D)\, v\, \mathrm{d}s}_{\text{penalty}} \end{align}

When u=gDu = g_D exactly, the symmetry and penalty terms vanish and only the flux consistency term remains, confirming the condition is satisfied.

Neumann (Dun=gND\nabla u \cdot \mathbf{n} = g_N on ΓN\Gamma_N):

A Neumann condition prescribes the flux through the boundary rather than the value. The diffusion face flux integral that arose from integration by parts is simply replaced by the prescribed value:

ΓND(un)vdsΓNgNvds-\int_{\Gamma_N} D(\nabla u \cdot \mathbf{n})\, v\, \mathrm{d}s \longrightarrow -\int_{\Gamma_N} g_N\, v\, \mathrm{d}s

No penalty term is needed because we are specifying the gradient, not enforcing a value of uu. The homogeneous case gN=0g_N = 0 (zero flux, such as an insulating or symmetry boundary) is the natural boundary condition for this formulation: it requires no additional terms at all, and is automatically satisfied if the boundary face integrals are simply omitted.

Robin (Dun=gRβuD\nabla u \cdot \mathbf{n} = g_R - \beta u on ΓR\Gamma_R, β0\beta \geq 0):

A Robin condition is a linear combination of the flux and the solution value. It arises in heat transfer (convective cooling at a surface) and in mass transfer (surface recombination of species). Substituting the Robin condition into the flux integral splits it into one term depending on uu and one depending on the data gRg_R:

ΓRD(un)vds=ΓRβuvdsu-dependentΓRgRvdsdata-\int_{\Gamma_R} D(\nabla u \cdot \mathbf{n})\, v\, \mathrm{d}s = \underbrace{\int_{\Gamma_R} \beta\, u\, v\, \mathrm{d}s}_{u\text{-dependent}} - \underbrace{\int_{\Gamma_R} g_R\, v\, \mathrm{d}s}_{\text{data}}

Advection

The advection term also produces boundary face integrals, and their treatment depends on whether flow is entering or leaving the domain at each face.

Inflow (wn<0\mathbf{w} \cdot \mathbf{n} < 0 on Γin\Gamma_\text{in}):

The flow is entering the domain, so the upwind value lies outside the domain. We prescribe it as ging_\text{in}, which moves the term to the right-hand side:

Γin(wn)ginvds(data term)-\int_{\Gamma_\text{in}} (\mathbf{w} \cdot \mathbf{n})\, g_\text{in}\, v\, \mathrm{d}s \quad \text{(data term)}

The minus sign and the fact that wn<0\mathbf{w} \cdot \mathbf{n} < 0 on Γin\Gamma_\text{in} mean this contributes positively for positive inflow data, which is physically correct: inflow adds concentration to the domain.

Outflow (wn>0\mathbf{w} \cdot \mathbf{n} > 0 on Γout\Gamma_\text{out}):

The flow is leaving the domain, so the upwind value is the interior trace uu. No data needs to be prescribed; the term is uu-dependent and enters the residual as:

Γout(wn)uvds(u-dependent)\int_{\Gamma_\text{out}} (\mathbf{w} \cdot \mathbf{n})\, u\, v\, \mathrm{d}s \quad (u\text{-dependent})

Wall / Symmetry (wn=0\mathbf{w} \cdot \mathbf{n} = 0 on Γw\Gamma_w):

No advective flux crosses the boundary. The face integral is identically zero, so no term needs to be added.

The Complete Weak Formulation

In practice, FESTIM uses a nonlinear solver (Newton/SNES) even for problems that are formally linear, so the formulation is assembled as a single residual F(u;v)F(u; v) rather than as separate bilinear and linear forms. This has the added benefit that the same formulation extends directly to nonlinear problems, for example with a concentration-dependent diffusivity D(u)D(u) or a nonlinear boundary flux, without any structural change.

The residual form of the problem is: find uVhu \in V_h such that F(u;v)=0F(u; v) = 0 for all vVhv \in V_h, where FF is the sum of all contributions derived above:

F(u;v)=KKDuvdxbulk diffusionFFIΓDFDunvdsdiffusion consistencyFFIΓDFDvnugDdsdiffusion symmetry+FFIΓDFαDhugDvdsdiffusion penalty+ΓRβuvdsRobinKK(wu)vdxbulk advection+FFIF(wn)uupvdsinterior upwind flux+Γout(wn)uvdsoutflow BCΩfvdxsourceΓNgNvdsNeumann BCΓRgRvdsRobin BCΓin(wn)ginvdsinflow BC=0\begin{align} F(u; v) &= \underbrace{\sum_K \int_K D\, \nabla u \cdot \nabla v\, \mathrm{d}x}_{\text{bulk diffusion}} \\ &\quad - \underbrace{\sum_{F \in \mathcal{F}_I \cup \Gamma_D} \int_F \langle D\nabla u \rangle \cdot \mathbf{n}\, \llbracket v \rrbracket\, \mathrm{d}s}_{\text{diffusion consistency}} \\ &\quad - \underbrace{\sum_{F \in \mathcal{F}_I \cup \Gamma_D} \int_F \langle D\nabla v \rangle \cdot \mathbf{n}\, \llbracket u - g_D \rrbracket\, \mathrm{d}s}_{\text{diffusion symmetry}} \\ &\quad + \underbrace{\sum_{F \in \mathcal{F}_I \cup \Gamma_D} \int_F \frac{\alpha D}{h}\, \llbracket u - g_D \rrbracket \llbracket v \rrbracket\, \mathrm{d}s}_{\text{diffusion penalty}} \\ &\quad + \underbrace{\int_{\Gamma_R} \beta\, u\, v\, \mathrm{d}s}_{\text{Robin}} \\ &\quad - \underbrace{\sum_K \int_K (\mathbf{w} u) \cdot \nabla v\, \mathrm{d}x}_{\text{bulk advection}} \\ &\quad + \underbrace{\sum_{F \in \mathcal{F}_I} \int_F (\mathbf{w} \cdot \mathbf{n})\, u_\text{up}\, \llbracket v \rrbracket\, \mathrm{d}s}_{\text{interior upwind flux}} \\ &\quad + \underbrace{\int_{\Gamma_\text{out}} (\mathbf{w} \cdot \mathbf{n})\, u\, v\, \mathrm{d}s}_{\text{outflow BC}} \\ &\quad - \underbrace{\int_\Omega f\, v\, \mathrm{d}x}_{\text{source}} \\ &\quad - \underbrace{\int_{\Gamma_N} g_N\, v\, \mathrm{d}s}_{\text{Neumann BC}} \\ &\quad - \underbrace{\int_{\Gamma_R} g_R\, v\, \mathrm{d}s}_{\text{Robin BC}} \\ &\quad - \underbrace{\int_{\Gamma_\text{in}} (\mathbf{w} \cdot \mathbf{n})\, g_\text{in}\, v\, \mathrm{d}s}_{\text{inflow BC}} \\[4pt] &= 0 \end{align}

On interior faces the jump ugD=u\llbracket u - g_D \rrbracket = \llbracket u \rrbracket since gD=0g_D = 0 there; on ΓD\Gamma_D faces it reduces to (ugD)(u - g_D), penalising the departure from the prescribed value.

Coupling with an External Velocity Field

The formulation above assumes the velocity field w\mathbf{w} is either prescribed analytically or lives in a continuous finite element space, is divergence-free (i.e. w=0\nabla\cdot\mathbf{w} = 0) in the discrete sense, and has exactly zero normal component on walls. When w\mathbf{w} is supplied by an external solver such as OpenFOAM, each of these assumptions requires care. The issues divide into three groups.

Single-valued face flux

In the interior advection term, we used the compact identity

(wn)uup=wun+12wnu(\mathbf{w} \cdot \mathbf{n})\, u_\text{up} = \langle \mathbf{w} u \rangle \cdot \mathbf{n} + \tfrac{1}{2}|\mathbf{w} \cdot \mathbf{n}|\, \llbracket u \rrbracket

which assumes wn\mathbf{w}\cdot\mathbf{n} is the same on both sides of every interior face, i.e. wn=0\llbracket \mathbf{w}\cdot\mathbf{n} \rrbracket = 0. OpenFOAM satisfies this naturally: it stores face-normal mass fluxes ϕF=F(ρw)nds\phi_F = \int_F (\rho \mathbf{w}) \cdot \mathbf{n}\,\mathrm{d}s that are single-valued per face by construction. However, if the cell-centred velocity is interpolated into a DG space on the transport mesh and wn\mathbf{w}\cdot\mathbf{n} is reconstructed from it, the two sides of each interior face will in general give different values, breaking the identity. Two fixes are available:

  1. Replace the compact identity with an explicit upwind flux that does not assume single-valuedness:

    f^adv=12(w0nu0+w1nu1)+12wnu\hat{f}_\text{adv} = \tfrac{1}{2} \bigl( \mathbf{w}_0\cdot\mathbf{n}\, u_0 + \mathbf{w}_1\cdot\mathbf{n}\, u_1 \bigr) + \tfrac{1}{2} \bigl| \langle \mathbf{w}\rangle\cdot\mathbf{n} \bigr|\, \llbracket u \rrbracket
  2. Transfer the face-flux field ϕF\phi_F directly from OpenFOAM and use it as the advective transport coefficient on each face, replacing wn\mathbf{w}\cdot\mathbf{n} by ϕF/(ρF)\phi_F / (\rho\, |F|) (or by ϕF/F\phi_F / |F| for a volumetric flux). The upwind branch is selected on the sign of ϕF\phi_F. This option preserves OpenFOAM’s flux conservation exactly and is generally preferred.

In either case the boundary face integrals on Γin\Gamma_\text{in} and Γout\Gamma_\text{out} must use the same face flux consistently, not a reconstructed wn\mathbf{w}\cdot\mathbf{n}, so that the inflow and outflow classification remains consistent with the interior treatment.

Discrete divergence

A divergence-free velocity field satisfies w=0\nabla\cdot\mathbf{w} = 0, meaning there is no net creation or destruction of fluid volume at any point. The physical flow is divergence-free, but an interpolated velocity field wh\mathbf{w}_h on the transport mesh generally is not. This matters because the two advection forms

(wu)andwu\nabla\cdot(\mathbf{w} u) \quad\text{and}\quad \mathbf{w}\cdot\nabla u

differ by (w)u(\nabla\cdot\mathbf{w})\, u, which is zero in the continuous problem but acts as a spurious source when wh0\nabla\cdot\mathbf{w}_h \ne 0. Two options are available:

When the face-flux form (option 2 above) is used, conservation is enforced through ϕF\phi_F directly and this divergence issue does not arise in the same way.

Wall boundary flux

On no-slip or symmetry walls the physical condition is wn=0\mathbf{w}\cdot\mathbf{n} = 0, and the corresponding advection face integral vanishes. An interpolated velocity field rarely satisfies this exactly, and a small residual normal component acts as a spurious inflow or outflow. The safest fix is to enforce wn=0\mathbf{w}\cdot\mathbf{n} = 0 explicitly on wall faces, either by zeroing the integrand or by using the face-flux field ϕF\phi_F, which is identically zero on OpenFOAM wall patches and therefore introduces no spurious flux.

Mesh transfer

All of the above assumes that velocity data defined on the OpenFOAM mesh has already been transferred to the transport mesh. Mesh-to-mesh interpolation is itself a source of error, particularly near boundaries and in regions with large velocity gradients, and its treatment is outside the scope of this document. We assume that either a face-flux field ϕF\phi_F or a cell-centred velocity field has been made available on the transport mesh by an appropriate interpolation method.

Further Reading