Autodiff code generator for Fortran
- Works seamlessly with existing Fortran code
- Performs source-to-source transformations
- Minimizes the need to modify the original files
- Optimized for computational efficiency
- Supports MPI, OpenMP, and SIMD parallelism
- Trades extra arithmetic for reduced memory traffic
This source-to-source tool parses existing Fortran programs and emits new modules
with _ad suffixes, preserving the original structure while providing forward
and reverse mode derivatives. Documentation of the supported Fortran constructs
can be found in docs/fortran_support.md.
Basic C preprocessor macros are also recognized; see docs/preprocessor.md for
details.
The parser relies on the fparser package (version 0.2.0 or later).
Install fautodiff and its dependencies with:
pip install .For simple examples the fautodiff.generator module can create a forward or reverse mode automatic differentiation version of a Fortran source file.
The generator parses the input via :mod:fautodiff.parser (which internally relies on fparser2) and retains the original structure with _ad appended to
routine names.
Integer variables are treated as constants, so no _ad variables are generated for them.
The generated module is named <module>_ad and provides both a forward (<name>_fwd_ad) and a reverse (<name>_rev_ad) version of each original routine.
After installation the fautodiff console script is available on your PATH.
When working directly from a source checkout you can invoke the same entry point
with python -m fautodiff.cli or run ./bin/fautodiff.
Generate the AD code for an example file examples/simple_math.f90:
# print to standard output
fautodiff examples/simple_math.f90
# or write to a file
fautodiff examples/simple_math.f90 -o examples/simple_math_ad.f90Example of the original code in examples/simple_math.f90:
work = a + b
c = a + 1.0
c = c + 2.0 + workGenerated forward mode AD code in examples/simple_math_ad.f90:
work_ad = a_ad + b_ad ! work = a + b
work = a + b
c_ad = a_ad ! c = a + 1.0
c = a + 1.0
c_ad = c_ad + work_ad ! c = c + 2.0 + work
c = c + 2.0 + workGenerated reverse mode AD code in examples/simple_math_ad.f90:
work_ad = c_ad ! c = c + 2.0 + work
a_ad = c_ad ! c = a + 1.0
c_ad = 0.0 ! c = a + 1.0
a_ad = work_ad + a_ad ! work = a + b
b_ad = work_ad ! work = a + bAdditional examples are available under the directory examples.
You can select the differentiation mode with --mode.
Pass forward for forward mode only or reverse for reverse mode only.
The default both generates both sets of routines.
Generate forward mode only:
fautodiff --mode forward examples/simple_math.f90Generate reverse mode only:
fautodiff --mode reverse examples/simple_math.f90Suppress warnings about unsupported derivatives with --no-warn:
fautodiff --no-warn examples/simple_math.f90Disable processing of any !$FAD directives in the source:
fautodiff --disable-directives examples/directives.f90Keep OpenMP scatter stores as-is instead of rewriting them to gathers with
--disable-scatter-to-gather:
fautodiff --disable-scatter-to-gather examples/omp_loops.f90Generate a validation driver template alongside the AD output with
--emit-validation-driver. The flag accepts an optional file name; when
omitted, run_<module>_validation.f90 is emitted next to the AD output. The
driver performs both the forward finite-difference vs. AD comparison and a
transpose consistency check, printing the individual norms in addition to the
difference. Array workspaces are declared with placeholder extent variables and
TODO comments so you can quickly fill in problem-specific sizes. Validation
drivers require both forward and reverse derivatives, so combine the flag with
--mode both (the default):
# emit run_simple_math_validation.f90 next to the AD file
fautodiff examples/simple_math.f90 -o examples/simple_math_ad.f90 \
--emit-validation-driver
# emit a custom driver name
fautodiff examples/simple_math.f90 -o examples/simple_math_ad.f90 \
--emit-validation-driver simple_math_validate.f90Each module's routine signatures are also written to a <module>.fadmod file when AD code is generated.
These JSON files can be loaded when differentiating another file that uses the module.
Add search directories with -I (repeat as needed), choose the output directory with -M DIR (defaults to the current directory), and disable writing with --no-fadmod:
# write ``cross_mod_a.fadmod`` under ``examples``
fautodiff -M examples examples/cross_mod_a.f90
# load that file when differentiating another module
fautodiff -I examples examples/cross_mod_b.f90The structure of these files is documented in docs/fadmod.md.
Generate code programmatically from Python:
from pathlib import Path
from fautodiff.generator import generate_ad
src_path = Path("examples/simple_math.f90")
ad_code = generate_ad(src_path.read_text(), str(src_path))
print(ad_code)Forward-mode subroutines execute the original computations and return each intent(out) variable along with its derivative.
Arguments consist of the original inputs followed by their derivative counterparts:
interface
subroutine foo(x, y)
real, intent(in) :: x
real, intent(out) :: y
end subroutine foo
end interface
real :: x, x_ad, y, y_ad
x = 2.0
x_ad = 1.0
call foo_fwd_ad(x, x_ad, y, y_ad) ! x: in, x_ad: in, y: out, y_ad: outReverse-mode subroutines accept the original inputs and the derivatives of any
intent(out) results. They return derivatives for intent(in) arguments and
update the derivatives of intent(inout) and intent(out) variables in
place:
real :: x, x_ad, y_ad
x = 2.0
y_ad = 1.0
call foo_rev_ad(x, x_ad, y_ad) ! x: in, x_ad: out, y_ad: inoutSee docs/directives.md for a description of optional directives that can control how AD code is generated. Details on how module variable assignments are handled in reverse mode are in the Module variables in reverse mode section.
See docs/fortran_support.md for a description of supported Fortran constructs.
OpenMP directives are preserved in the generated code. Supported constructs
include parallel, parallel do (and related forms such as do,
parallel do simd, and do simd), sections, parallel sections, and
single blocks, as well as stand‑alone directives like barrier, flush,
taskwait, and taskyield. Clauses that list variables—e.g. private,
firstprivate, or reduction—automatically receive the corresponding
derivative variables so that both the primal and _ad versions participate.
Loops with cross‑iteration dependencies are detected during reverse-mode
generation; in these cases the OpenMP directive is dropped and the loop runs
sequentially. When the dependency arises from scatter-style updates (e.g.
stencil kernels) the generator rewrites the reverse loop to gather
contributions instead. The OpenMP directive is preserved in those cases and a
diagnostic message notes that the loop was rewritten for thread-safety. Pass
--disable-scatter-to-gather to skip this rewrite when you would rather keep
the original scatter stores.
The helper module fautodiff_stack stores data that must persist between the forward and reverse sweeps.
It defines stack types for real, double precision, logical and integer values and pointers.
Four default stacks (fautodiff_stack_r4, fautodiff_stack_r8, fautodiff_stack_l, fautodiff_stack_i, and fautodiff_stack_p) are provided for convenience. Each stack allocates memory in pages and the size of a page can be changed by modifying the page_size` component before the first push.
Run the included tests with:
python tests/test_generator.py
python tests/test_fortran_runtime.py
python tests/test_fortran_adcode.pyAutomatic differentiation output is built from a tree of nodes defined in fautodiff.code_tree.
Each node represents a Fortran construct such as a Block, Assignment or IfBlock and provides a render method that returns formatted source lines.
The generator populates this tree while walking the parsed input and then renders it to produce the final AD code.
Contributors adding new features should rely on these new node classes instead of creating raw strings so that the tree remains consistent.