UKAEA FESTIM Workshop 2026
Kaelyn Dunnell & Tez Orr
June 29, 2026
The FESTIM team at MIT and beyond
University of Cambridge
Simula Research Laboratory

DFT
Multi-scale hydrogen transport

Y. Ferro et al 2023 Nucl. Fusion 63 036017
Length scale
Time scale
MD

Length scale
Time scale
DFT

potentials
Multi-scale hydrogen transport
Component scale modelling


Length scale
Time scale
MD
DFT


D, S, other coeffs.
Multi-scale hydrogen transport
Length scale
Time scale
MD
DFT


Component scale modelling


Fuel cycle modelling

Residency times, fluxes, ...
Multi-scale hydrogen transport
Length scale
Time scale
MD
DFT


Component scale modelling


Fuel cycle modelling

Abstraction
Multi-scale hydrogen transport
Tritium Transport in Materials
Diffusion
\( \varphi\): diffusion flux
\( D \): diffusion coefficient
\( c \): mobile hydrogen concentration
\( S\): source term
Fick's 1st law of diffusion
Fick's 2nd law of diffusion
Trapping

H
Potential energy
Distance
Diffusion barrier
Trap binding energy
Trapping energy
Common assumption:
\( E_k = E_D \)
Trapping
0D
Total concentration of traps
McNabb & Foster Model
Particle implantation
Ziegler et al. 2010. Nuclear Instruments and Methods in Physics Research Section B: Beam Interactions with Materials and Atoms, 268 (11): 1818–23. https://doi.org/10.1016/j.nimb.2010.02.091.
Implantation range

Implantation range & width and reflection coefficient can be computed with SRIM, SDTRIM...
Mutzke et al, SDTrimSP Version 6.00 2019
\(\Gamma_\mathrm{incident} \): incident flux (particle/m2/s)
\( f(x) \): Gaussian distribution (/m)
\(r \): reflection coefficient
or
Surface effects
H2 molecules
Metal lattice
Surface effects
Dissociation coefficient (H/m2/s/Pa)
Partial pressure of H (Pa)
Adsorbed H
Metal lattice
Surface effects
Metal lattice
Recombination coefficient (m4/s)
Concentration (H/m3)
Surface effects
Metal lattice
Waelbroeck model
Surface effects
Metal lattice
At equilibrium:
Sievert's law of solubility
Surface effects
Non-metallic liquid
At equilibrium:
Henry's law of solubility
Interfaces
Material 1
Material 2
Case 1:
Metal-Metal
Sievert's law
Flux continuity
Conservation of chemical potential
Interfaces
Material 1
Material 2
Case 2:
Non metal-non metal
Henry's law
Flux continuity
Conservation of chemical potential
Interfaces
Material 1
Material 2
Case 3:
Metal-Non metal
Sievert's law
Henry's law
McNabb & Foster model
Challenges
- Number of degrees of freedom
- Interface discontinuities
Governing equations
We can solve these numerically
Component modelling
Experimental analysis
3 main numerical methods
Finite Difference Method (FDM)
Finite Element Method (FEM)
Finite Volume Method (FVM)
Let's not bother
FESTIM today
See more FESTIM stats:
festim-dev.github.io/pose
See all FESTIM papers:
FESTIM is used worldwide

Docs users
Fusion machine developers
Research organisations
What changed in FESTIM2?
Upgrade from legacy-FEniCS to FEniCSx
Before
- Change of variable required - did not scale well
- Single solver method available
- No submesh coupling
- Limited mesh element types
Now
- Mixed-domain: no substitution needed
- Multiple solver methods (Penalty, Nitches)
- Submesh coupling: 1D to 2D problems
- Mixed topology meshes
- Improved parallel I/O
~11x faster
Before
- Monolithic approach
- Change of variable required
- Poor scaling
- Little control over interface equations
- Adding advection was tricky
Now
- Mixed-domain: no substitution needed
- No change of variable needed
- Improved performance
- Full control over interface conditions
- Very suitable for multi-physics
Multi-material handling
Multi-species and Reactions
Before
- Single mobile species only
- No multi-isotope support
- No multi-occupancy trapping
- Rigid trapping reactions
Now
- Arbitrary species
- Multi-isotope
- Multi-occupancy trapping
- Defect diffusion
- Flexible reactions
- Bulk and surface reactions
The mixed formulation is great for multiphysics


velocity, temperature, turbulent viscosity...
tritium production
Heat source
Temperature
Neutronics
Thermo-hydraulics
Tritium transport




Examples

Using FESTIM for estimating tritium contamination in a heat exchanger







ARC Breeding Blanket
ARC blanket geometry
T source
Heat source
Velocity
Temperature
Turbulent viscosity
Tritium concentration
TDS analysis: codeposits

- Simulation of W codeposited layers
- Influence of partial pressure
- 10 different traps!
Thank you for your attention!
Any questions?

FESTIM Fellowship programme commenced in 2026

"Even though it's quite nice to be the FESTIM v2 expert on site (!!!), I would absolutely recommend the programme to my colleagues."
First FESTIM fellow - Tez Orr (UKAEA)
Hands-on-learning - Tez worked directly with the dev team on UKAEA cases and fixed a bug in v2.0
Immediate impact - Became UKAEA's resident FESTIM expert, contributing to both the LIBRTI and STEP programmes

Monoblock




CAD based simulations
Read a mesh from GMSH
mesh_data = gmshio.read_from_msh(
"gmsh/mesh3D.msh", MPI.COMM_WORLD, 0, gdim=3
)
mesh = mesh_data.mesh
facet_tags = mesh_data.facet_tags
cell_tags = mesh_data.cell_tagsCan also import meshes from other sources thanks to io4dolfinx

Heat transfer simulation
Run heat conduction simulation
heat_transfer_problem = F.HeatTransferProblem()
...
heat_flux_top = F.HeatFluxBC(subdomain=top_surface, value=10e6)
convective_flux_coolant = F.HeatFluxBC(
subdomain=cooling_surface, value=lambda T: h_convective * (T_coolant - T)
)
...
heat_transfer_problem.run()Convective cooling
H transport problem
Pass the temperature field to the H transport problem
my_model = F.HydrogenTransportProblemDiscontinuous()
my_model.temperature = heat_transfer_problem.u
my_model.method_interface = "penalty"
my_model.subdomains = all_subdomains
H = F.Species("H", subdomains=my_model.volume_subdomains)
my_model.species = [H]
my_model.mesh = mesh
my_model.surface_to_volume = {
top_surface: tungsten_volume,
cooling_surface: cucrzr_volume,
poloidal_gap_w: tungsten_volume,
poloidal_gap_cu: copper_volume,
poloidal_gap_cucrzr: cucrzr_volume,
toroidal_gap: tungsten_volume,
bottom: tungsten_volume,
}
penalty_term = 1e20
my_model.interfaces = [
F.Interface(
id=16, subdomains=(tungsten_volume, copper_volume), penalty_term=penalty_term
),
F.Interface(
id=17, subdomains=(copper_volume, cucrzr_volume), penalty_term=penalty_term
),
]
import ufl
# Plasma implantation flux BC
phi = 1.61e22
R_p = 9.52e-10
implantation_flux_top = F.FixedConcentrationBC(
subdomain=top_surface,
value=lambda T: phi * R_p / (tungsten.D_0 * ufl.exp(-tungsten.E_D / F.k_B / T)),
species=H,
)
# Instantaneous molecular recombination flux BCs at all other surfaces (fixed concentration of 0)
recombination_fluxes = [
F.FixedConcentrationBC(subdomain=surf, value=0, species=H)
for surf in [
toroidal_gap,
bottom,
poloidal_gap_w,
poloidal_gap_cu,
poloidal_gap_cucrzr,
cooling_surface,
]
]
my_model.boundary_conditions = [implantation_flux_top] + recombination_fluxes
exports = {
"poloidal_gap_cu_flux": F.SurfaceFlux(surface=poloidal_gap_cu, field=H),
"poloidal_gap_cucrzr_flux": F.SurfaceFlux(surface=poloidal_gap_cucrzr, field=H),
"poloidal_gap_w_flux": F.SurfaceFlux(surface=poloidal_gap_w, field=H),
"toroidal_gap_flux": F.SurfaceFlux(surface=toroidal_gap, field=H),
"bottom_flux": F.SurfaceFlux(surface=bottom, field=H),
"inventory_w": F.TotalVolume(field=H, volume=tungsten_volume),
"inventory_cu": F.TotalVolume(field=H, volume=copper_volume),
"inventory_cucrzr": F.TotalVolume(field=H, volume=cucrzr_volume),
}
my_model.exports = list(exports.values())
my_model.settings = F.Settings(
atol=1e-8,
rtol=1e-10,
transient=False,
max_iterations=10,
)
my_model.initialise()
my_model.run()

Implantation from plasma
Examples: multi-species framework
Multi-species
H = F.Species("H")
my_model.species = [H]Defining species
Multi-species
H = F.Species("H")
D = F.Species("D")
my_model.species = [H, D]Defining species
Multi-species
H = F.Species("H")
empty_traps = F.Species("empty", mobile=False)
trapped_H = F.Species("trapped_H", mobile=False)
my_model.species = [H, empty_traps, trapped_H]Adding a reaction
trapping = F.Reaction(
reactant=[H, empty_traps],
product=[trapped_H],
k_0=...,
E_k=...,
p_0=...,
E_p=...,
)
my_model.reactions = [trapping]Multi-species
H = F.Species("H")
empty_traps = F.Species("empty", mobile=True)
trapped_H = F.Species("trapped_H", mobile=False)
my_model.species = [H, empty_traps, trapped_H]Mobile traps?
trapping = F.Reaction(
reactant=[H, empty_traps],
product=[trapped_H],
k_0=...,
E_k=...,
p_0=...,
E_p=...,
)
my_model.reactions = [trapping]Multi-species
H = F.Species("H")
empty_traps = F.Species("empty", mobile=False)
empty_traps2 = F.Species("empty2", mobile=False)
trapped_H = F.Species("trapped_H", mobile=False)
trapped_H2 = F.Species("trapped_H2", mobile=False)
my_model.species = [
H, empty_traps, trapped_H,
empty_traps2, trapped_H2
]2 traps
trapping = F.Reaction(
reactant=[H, empty_traps],
product=[trapped_H],
...
)
trapping2 = F.Reaction(
reactant=[H, empty_traps2],
product=[trapped_H2],
...
)
my_model.reactions = [trapping, trapping2]Multi-species
H = F.Species("H")
D = F.Species("D")
empty_traps = F.Species("empty", mobile=False)
trapped_H = F.Species("trapped_H", mobile=False)
trapped_D = F.Species("trapped_D", mobile=False)
my_model.species = [
H, D, empty_traps,
trapped_H, trapped_D
]1 trap, 2 isotopes
trapping = F.Reaction(
reactant=[H, empty_traps],
product=[trapped_H],
...
)
trapping2 = F.Reaction(
reactant=[D, empty_traps],
product=[trapped_D],
...
)
my_model.reactions = [trapping, trapping2]Multi-species
H = F.Species("H")
empty_traps = F.Species("empty", mobile=False)
trapped_H = F.Species("trapped_H", mobile=False)
trapped_H2 = F.Species("trapped_H2", mobile=False)
my_model.species = [
H, empty_traps,
trapped_H, trapped_H2
]Multi-occupancy trapping
trapping = F.Reaction(
reactant=[H, empty_traps],
product=[trapped_H],
...
)
trapping2 = F.Reaction(
reactant=[H, empty_traps2],
product=[trapped_H2],
...
)
my_model.reactions = [trapping, trapping2]Multi-species
A = F.Species("A")
B = F.Species("B")
C = F.Species("C")
D = F.Species("D")
my_model.species = [A, B, C, D]"Species" can be anything...
Multi-species
A = F.Species("A")
B = F.Species("B")
C = F.Species("C")
D = F.Species("D")
my_model.species = [A, B, C, D]reac = F.Reaction(
reactant=[A, B],
product=[C, D],
...
)Arbitrary numbers of reactants and products
Multi-species
A = F.Species("A")
B = F.Species("B")
my_model.species = [A, B]reac = F.Reaction(
reactant=[A, B],
product=[],
...
)Arbitrary numbers of reactants and products
Can be used for:
- Vacancy-Interstitial anhiliation
- Radioactive decay
- Defect annealing
- ...
Multi-species
H = F.Species("H")
my_model.species = [H]
reac = F.SurfaceReactionBC(
reactant=[H, H],
pressure=10,
...
)
my_model.boundary_conditions = [reac]Surface reactions
Soon: support for dynamically computed gas pressures (enclosures)
Examples: Multiphysics coupling
Integrate results from external solvers into FESTIM
Coupling CFD to FESTIM using foam2dolfinx


Using foam2dolfinx we can pass velocity fields calculated from OpenFOAM to FESTIM
Reading OpenFOAM data using foam2dolfinx
from foam2dolfinx import OpenFOAMReader
reader = OpenFOAMReader(filename="my_foam_case.foam", cell_type=10)
# read fields
u = reader.create_dolfinx_function_with_point_data(t=100, name="U")
# read mesh
foam_mesh = reader.dolfinx_meshes_dict["default"]
# read meshtags
facet_mt = reader.create_facet_meshtags()
volume_mt = reader.create_cell_meshtags()Read a local OpenFOAM case file
- Specify time stamp of data
- Can extract any field type
- Extract dolfinx mesh
- Extract meshtags
Tets
Hexes
Support for:
Reading and writing using io4dolfinx
import io4dolfinx
checkpoint_file = Path("sim_checkpoint.bp")
io4dolfinx.write_mesh(checkpoint_file, msh)
io4dolfinx.write_function(checkpoint_file, u, time=100, name="U")
io4dolfinx.write_meshtags(checkpoint_file, msh, facet_mt, meshtag_name="facet_tags")
io4dolfinx.write_meshtags(checkpoint_file, msh, cell_mt, meshtag_name="cell_tags")
..
msh = io4dolfinx.read_mesh(checkpoint_file, COMM)
facet_mt = io4dolfinx.read_meshtags(checkpoint_file, msh, meshtag_name="facet_tags")
cell_mt = io4dolfinx.read_meshtags(checkpoint_file, msh, meshtag_name="cell_tags")
gdim = msh.geometry.dim
el = basix.ufl.element("Lagrange", msh.topology.cell_name(), 1, shape=(gdim,))
w = fem.Function(fem.functionspace(msh, el), name="U")
io4dolfinx.read_function(checkpoint_file, u, time=100, name="U")Using OpenFOAM data in FESTIM
...
my_model.mesh = foam_mesh
my_model.surface_meshtags = facet_meshtags
my_model.volume_meshtags = cell_meshtags
...
my_model.advection_terms = [
F.AdvectionTerm(velocity=u, species=H, subdomain=fluid)
]Pass mesh and meshtags to FESTIM
Pass velocity field using F.AdvectionTerm()
Adding advection to FESTIM means adding a term in the formulation

Coupling neutronics to FESTIM using openmc2dolfinx


Using openmc2dolfinx we can pass fields calculated from OpenMC to FESTIM
from openmc2dolfinx import UnstructuredMeshReader, StructuredMeshReader
# UnstructuredMeshReader for tets
reader = UnstructuredMeshReader(filename="out.vtk")
# StructuredMeshReader for hexs
reader = StructuredMeshReader(filename="out.vtk")
# read fields
source_from_openmc = reader.create_dolfinx_function(name="mean")
Reading OpenMC data using openmc2dolfinx
Read .vtk file produced by OpenMC
- Extract any mesh tally:
- Tritium production
- Heat generation
- He production ...
Tets
Hexes
Support for:
...
my_model.sources = [
F.ParticleSource(value=source_from_openmc, species=T, subdomain=volume)
]
...Using OpenMC data in FESTIM
Input into FESTIM as a source term
Examples: FESTIM in the loop
Integrate FESTIM in complex workflows
Machine Learning with FESTIM

1. Parametric FESTIM model
2. Produce training data


3. Train surrogate/emulator
Parametric optimisation with FESTIM


Goal: find the trapping parameters to reproduce an experimental thermo-desorption spectrum
Integrating FESTIM with fuel cycle models

PathView/PathSim: a python package for system modelling
Can be seamlessly coupled with FESTIM for multi-fidelity fuel cycle simulations

UKAEA FESTIM workshop 2026
By Remi Delaporte-Mathurin
UKAEA FESTIM workshop 2026
- 78
