Differentiable Fortran with LFortran and Enzyme - Tesseract Blog
Contents
Menu
Expand
Light mode
Dark mode
Auto light/dark, in light mode
Auto light/dark, in dark mode
Skip to content
Introduction
Installation
Get Started
Tesseract User Forums
Changelog
Creating Tesseracts
Creating Tesseracts
Design Patterns
Tips for Defining Tesseract APIs
Using LLMs to Create Tesseracts
Deploying Tesseracts
Using Tesseracts
Interacting with Tesseracts
Array Encodings
Advanced Usage
Demos & Tutorials
Demos & Tutorials<br>4D-Variational Data Assimilation for a Chaotic Dynamical System
Building the JAX Solver Tesseract for Lorenz-96
Gradient-Based Optimization of Fluid Flows
Parametric Shape Optimization with Differentiable FEM Simulation
Inverse Heat Transfer with Automatic Differentiation
Learned Closure: Training a Neural Viscosity Model Through a PDE Solver (PyTorch)
JAX Rosenbrock Minimization
PyTorch Rosenbrock Minimization
JAX RBF Fitting
Example Gallery: Building Blocks<br>HelloWorld
VectorAdd
Univariate Rosenbrock function
Wrapping Compiled Code (Fortran Example)
Differentiating Compiled Code (Enzyme + Fortran)
Wrapping MATLAB Code
Packaging local files into a Tesseract
Custom build steps: PyVista on ARM64
Installing local Python modules into a Tesseract
Out-of-core data loading
File IO with InputPath / OutputPath
Finite Difference Gradients
Deriving Gradient Endpoints from Each Other
Example Gallery: Integrating with Ansys Products<br>Wrapping SpaceClaim as a Tesseract
Wrapping MAPDL as a Tesseract
Guides
Differentiable Programming Basics
Debugging Guide
Performance Trade-offs & Optimization
API Reference — SDK
CLI Reference (tesseract)
Python API (tesseract_core)
Configuration (tesseract_config.yaml)
API Reference — Runtime
Endpoints (tesseract_api.py)
CLI Reference (tesseract-runtime)
Python API (tesseract_core.runtime)
Back to top
View this page
← Tesseract Blog
2026-07-09 · @dionhaefner
Differentiable Fortran with LFortran and Enzyme¶
What if you could backpropagate through existing Fortran, C, or C++ simulation code, embed it into JAX and torch, and use it as a high-performance differentiable physics engine? Turns out, you can — if you’re brave enough…
Decades of validated physics code in CFD, climate, aerospace, and nuclear sit behind a wall that modern ML pipelines can’t cross, because they don’t expose gradients. The usual answer is to rewrite it all in JAX or PyTorch. The alternative we explore here is to leave the code where it is and get exact gradients out anyway, thanks to some LLVM-level magic. This is possible because Enzyme applies autodiff at the LLVM IR level, so we can differentiate any code that compiles to LLVM!
All we need is to duct-tape LFortran, LLVM, and Enzyme together, point the result at a Fortran thermal solver, and get exact gradients out the other end. From there, Tesseract wraps the result as a custom JAX primitive, so a Fortran solver becomes a differentiable layer in arbitrary JAX code.
This is all pretty experimental, so be prepared to spend some time chasing a gradient that returned NaN and manually compare LLVM IR diffs to make it work. But if you put in the labor, you’ll get gradients through the entire multi-step time loop that match an analytic answer, and it’s amazing to see that a stack combining some of the oldest and newest technologies can work together at all.
But let’s start at the beginning. What follows is the full story, including the compilation pipeline, the sharp edges we hit, and application on a challenging inverse problem (that wouldn’t be possible without AD).
The problem with getting gradients out of legacy code¶
If you work in scientific computing, you might have run into this: you have a simulation written in Fortran (or C, or C++), and now someone needs gradients (the derivatives of the simulation’s outputs with respect to its inputs). Maybe it’s for optimization, maybe inverse problems, maybe plugging the sim into an ML pipeline. Now your options are:
Hand-written adjoints. Boils down to hand-implementing the derivative of every line of code, which means months of expert effort. Error-prone and a maintenance nightmare that slowly drifts out of sync with the forward code.
Finite differences. Perturb every input and difference the outputs. Slow (\(O(n)\) evaluations for n parameters), inaccurate, and poorly conditioned for stiff problems.
Rewrite in JAX or PyTorch and use autodiff. Sure, if you want to rewrite tens of thousands of lines of validated physics.
What if you could just compile the derivatives automatically, from the existing source? This is what it actually looks like when you try.
An example Fortran heat solver¶
Our test subject is thermal_2d.f90, about 220 lines of vanilla Fortran 90 that we wrote for this experiment. It solves 2D transient heat conduction with temperature-dependent conductivity:
\[\rho \, c_p \frac{\partial T}{\partial t} = \nabla \cdot...