Hydrogen transport simulations made easy
Webinar | May 15th, 2026
Presented by:
James Dark & Remi Delaporte-Mathurin
2023
2023
2024
User friendly - Intuitive, Python API easy to use
Apache 2.0 license - Suitable for academic and commercial
Easy install - conda install -c conda-forge festim
Online tutorials - Dozens of tutorials
Online documentation - Auto-generated
3,000+ forum views/month - Public, searchable Q&A
27+ institutions - Adopted by universities, labs and industry
30+ contributors - Open to new developers and users
Monthly dev meetings - Community-driven roadmap
130+ Slack users - Active developer and user support
Built on FEniCSx - Proven FEM backend, 1.5M+ downloads
5 team members at MIT - more around the world
On GitHub - Transparent, version-controlled development
NumFOCUS affiliation - Backed by a sustainability non-profit
CI/CD workflows - Rigorous testing and streamlined releases
Performance
FEniCSx - JIT to fast C++ rapid assembly
High-performance solvers - PETSc back-end
Built for HPC - Native MPI, runs on 1000’s of cores
Tuned control - Custom meshes, solvers, finite elements
Fast I/O - Parallel mesh loading, low memory footprint
Usability
Sustainability
Community
"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
See more FESTIM stats:
festim-dev.github.io/pose
See all FESTIM papers:
Docs users
Are you using FESTIM?
Fill in this form!
github.com/festim-dev/FESTIM/issues
Before
Now
~11x faster
Before
Now
Before
Now
FESTIM in action!
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
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
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
Complete tutorials available at
festim-workshop.readthedocs.io/en/latest/content/post_process/intro.html
Writes a time-dependent field to a file
New in FESTIM2!
Compute quantities obtained from the solution
Thermo-Desorption Spectroscopy tutorial
festim-workshop.readthedocs.io/en/latest/content/applications/task02.html
left_flux = F.SurfaceFlux(surface=left_boundary, field=H)
right_flux = F.SurfaceFlux(surface=right_boundary, field=H)total_trapped_H1 = F.TotalVolume(field=trapped_H1, volume=volume_subdomain)
contribution_trap_1 = -np.diff(total_trapped_H1.data) / np.diff(total_trapped_H1.t)CustomQuantityH = F.Species("H")
my_model.species = [H]Defining species
H = F.Species("H")
D = F.Species("D")
my_model.species = [H, D]Defining 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]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]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]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]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]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...
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
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:
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)
Integrate results from external solvers into FESTIM
foam2dolfinxUsing foam2dolfinx we can pass velocity fields calculated from OpenFOAM to FESTIM
foam2dolfinxfrom 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
Tets
Hexes
Support for:
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")...
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
openmc2dolfinxUsing 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")
openmc2dolfinxRead .vtk file produced by OpenMC
Tets
Hexes
Support for:
...
my_model.sources = [
F.ParticleSource(value=source_from_openmc, species=T, subdomain=volume)
]
...Input into FESTIM as a source term
Integrate FESTIM in complex workflows
1. Parametric FESTIM model
2. Produce training data
3. Train surrogate/emulator
Goal: find the trapping parameters to reproduce an experimental thermo-desorption spectrum
PathView/PathSim: a python package for system modelling
Can be seamlessly coupled with FESTIM for multi-fidelity fuel cycle simulations
Everything in one place!
festim.readthedocs.io
Fundamentals: Core physics and FESTIM building blocks.
Visit the tutorial
festim-workshop.readthedocs.io
Misc: troubleshooting, tips & tricks, "build FESTIM from scratch"
Visit the tutorial
festim-workshop.readthedocs.io
Applications: Full application cases like monoblock, TDS, multi-physics, machine learning...
Visit the tutorial
festim-workshop.readthedocs.io
A selective, project-based
programme for experienced FESTIM users to develop advanced simulation expertise working directly alongside the MIT development team.
Fall 2026 Intake - Applications open now | Deadline: August 1st Apply: festim.readthedocs.io/en/latest/fellowship.html
~10 weeks, flexible, Remote or in person at MIT PSFC. Fellows bring a focused project and get guided technical support, code reviews, and hands-on collaboration.
Become an advanced FESTIM user. Capable of building complex models independently, contributing to the codebase, and supporting colleagues with FESTIM.
Slack Channel
Discourse
Monthly Dev meetings
Where we share updates on FESTIM activities and collaborate day-to-day. The best place to get involved and stay connected
Our public forum for questions, troubleshooting and discussion. Post any issue, and the team will repsond
Open Development meetings of the last Thursday of each month.
Next one May 28th!