FESTIM: a framework for hydrogen isotope transport on top of FEniCS
Rémi Delaporte-Mathurin, on behalf of the whole FESTIM community
Simula 25 Years · FEniCS Workshop | Oslo | September 2026




The problem (no nuclear physics)
Hydrogen isotopes in a solid: mobile, drifting, and repeatedly trapped in defects.
- Many species at once, and stiff
- Concentration jumps at interfaces
- 1D slabs to CAD-meshed components
↑ drift: thermal, electric, advective
one equation per trap population
Why we care: tritium is the fuel, it is scarce, radioactive, and it leaks through everything.
Who we build for
Our users
- Know their physics
- Can write Python
- Have never assembled a bilinear form
- Will not debug a solver
So we abstract:
- FESTIM owns the vocabulary: materials, traps, species, reactions
- FEniCSx owns the mathematics
- Users never see a form
We try to encapsulate every FEniCSx feature we adopt.
Two eras
2019 – 2024 · FESTIM 1
- Legacy FEniCS
- One mobile species + traps
- Chemical-potential change of variable
- Validated against experiments
2024 – today · FESTIM 2
- DOLFINx
- Arbitrary species
- Submeshes & mixed function spaces
- ~11× faster, MPI-native
FEniCSx made so many things possible...
The thing that shaped the code
At a material interface the concentration is discontinuous. Two conditions hold across it:
chemical potential continuous
flux continuous
Legacy workaround
Solve for θ = c/K: one continuous field, one space.
The cost: every source, BC, export and reaction rate rewritten in θ. Nonlinear in T. Didn't scale well.
What we wanted
One field per material, each on its own submesh.
Coupled by a condition we choose, not one baked into the function space.
What DOLFINx gave us
Submeshes, MixedFunctionSpace, assembly over mixed domains. One field per subdomain, one block system:
… and the only thing tying the subdomains together is one term on the interface:
- γ is a penalty parameter; the Nitsche variant adds consistency terms and drops the tuning
- No change of variable required
Our users write none of this.
Weak coupling is a feature
Once the interface condition lives in the form rather than in the function space:
Continuity of chemical potential is just one choice; a flux condition, a kinetic law or a surface reaction work too
Which matters, because equilibrium is sometimes simply wrong.
Delaporte-Mathurin & Dark, When chemical potential continuity fails, arXiv:2608.26231
A subdomain doesn't need to be the same dimension as its parent mesh.

NEW SINCE JULY
Subdomains that are manifolds
A grain boundary is a fast path through a slow bulk. Meshing it as a thin volume is hopeless.
gb = F.VolumeSubdomain(
id=2,
material=F.Material(D_0=D_gb, E_D=0.0),
dim=1, # codimension 1 in a 2D mesh
locator=lambda x: np.isclose(x[0], 0.5),
)
- One extra argument:
dim. Everything else is the subdomain API users already know. - Under the hood, the same story as before: its own submesh, its own field, same mixed system.
- Diffusion along the manifold ~100× faster than through the bulk.
Coupling across dimensions
Bulk on Ω (dim n), manifold on Γ (dim n−1), each carrying its own field:
↑ tangential gradient along the manifold
- The flux leaving the bulk is the source entering the manifold: conservative by construction
- It nests: a coolant channel is a 1D manifold on a 2D wall, and its inlet and outlet are dim = 0
Hydrogen takes the fast lane
A real polycrystalline microstructure. Hydrogen enters at the top.
Two fields solved together:c_b in the grains (dim=2)c_gb on the boundaries (dim=1)
It reaches depth through the boundary network long before the bulk front arrives.
NEW SINCE JULY
An enclosure is one number
Hydrogen permeates out of a wall into a sealed volume. The pressure obeys an ODE fed by the surface flux, and feeds straight back into the wall’s boundary condition.
One scalar unknown, coupled both ways to a PDE. Our previous answer was an operator splitting and a hand-rolled ODE loop.
We didn’t need one. DOLFINx has real-space elements: P becomes a single global degree of freedom in the same mixed space as the fields, and Newton solves the lot monolithically.
For the user: F.GasSpecies and F.Enclosure. Sieverts’ law then sets c from a pressure with no nodal values, so we impose it weakly, with Nitsche.
An enclosure to simulate a pump
Two membranes with a pumped gap. The 1D mesh is discontiguous: no cell spans the gap, so the gas enclosure is the only path across.
c = c0
c = 0
layer 1
gap ↓ pump
layer 2
- Both faces exchange with one
Enclosurevia a surface reaction - A
Pumpis an opening on it; a purge stream is the same object
Pump on: enclosure pressure 25 Pa → 0, and the concentration reaching the second membrane falls by ~5 orders of magnitude.
Permeation flux
Drifts!
Hydrogen does not only follow its own gradient. It drifts: down temperature gradients, under electric fields, and with a moving coolant.
Soret / thermodiffusion; Q_star is the heat of transport
Several drift terms may act on one species; the velocities simply add. SoretTerm, ElectromigrationTerm, AdvectionTerm.
Because the residual is built in UFL, a drift term is literally one more term in the form. No new solver, no new time integrator, and it composes with submeshes and manifolds for free.
NEW SINCE AUGUST
NEW SINCE SEPT
Submeshes broke our I/O
Every feature above multiplies the number of meshes and fields. Users got a directory of .bp files and no way to restart.
F.SpeciesExport("state.bp", field=H, format="checkpoint")
state = F.read_function_from_file("state.bp", name="H", timestamp=10.0)
- io4dolfinx (evolution of adios4dolfinx): N-to-M checkpointing, multiple backends
Three codes, one field
OpenMC: tritium source
OpenFOAM: velocity
FESTIM: tritium field
openmc2dolfinx and foam2dolfinx hand over DOLFINx functions, not files, so they compose with submeshes, manifolds and drift terms for free.
ARC breeding blanket · ~243 mg tritium inventory · Dark, Sircar, Bae, Dokken & Delaporte-Mathurin, arXiv:2608.05398
How do we know any of it is right?
Verification
- Manufactured solutions: 10 cases, convergence orders checked
- Manufactured exact solutions: 6 cases
- The TMAP benchmark suite
Validation
- 8 experimental campaigns
- Plasma-driven permeation, deuterium retention in damaged tungsten, Be co-deposition, protium in titanium, EUROFER implantation
Every case is a runnable script. The report rebuilds from them, in public.
festim-vv-report.readthedocs.io
Independently cross-compared against MHIMS and mHIT (Nuclear Materials and Energy)
What it looks like in anger
- Tritium contamination in a heat exchanger: CFD-driven, multi-material, transient
- Divertor monoblock: CAD mesh, coupled heat transfer, three materials, two interfaces
What we want next
On our side
- DG for advection-dominated transport
- Hydride formation: moving phase boundaries
- Better diagnostics when Newton stalls
On our wishlist from you
- End-to-end mixed-topology meshes: it seems there currently are limits in UFL
- A higher-level mixed-dimensional API
- GPU assembly for the big transients
The project
30+
contributors
27+
institutions
8k
forum views / month
Apache 2.0
NumFOCUS affiliated
conda install -c conda-forge festim
Monthly dev meetings, open to anyone. A workshop with runnable tutorials for everything in this talk.
If you maintain a FEniCS-based code and want to compare notes on hiding FEM from users, come find me. 😉
Thank you
Happy 25th, Simula, and thank you for DOLFINx.
github.com/festim-dev/FESTIM
festim.readthedocs.io · festim-workshop.readthedocs.io
Questions?

Simula Anniversary FESTIM
By Remi Delaporte-Mathurin
Simula Anniversary FESTIM
- 108