Skip to content

Differentiating through the code

IncompressibleNavierStokes is reverse-mode differentiable, which means that you can back-propagate gradients through a simulation, e.g. to optimize initial conditions, force parameters, or neural closure models. Two AD libraries are supported, both backed by the same hand-written adjoint kernels:

  • Zygote.jl, via ChainRulesCore rrules. Zygote does not support array mutation, so your simulation code must be composed of the non-mutating operator variants (e.g. divergence, timestep). These allocate intermediate fields that are kept in memory for the backward pass.

  • Enzyme.jl, via EnzymeCore rules. Enzyme differentiates the fast mutating variants (e.g. divergence!, right_hand_side!) and is typically the most efficient option when it applies.

Enzyme limitation: array returns

Enzyme's autodiff requires functions with scalar output. To compute pullbacks of array-valued functions, use a mutating function that returns nothing and stores its result in an argument wrapped in Duplicated.

For complete, runnable demonstrations of both approaches — computing the gradient of the final kinetic energy with respect to the initial conditions with Zygote, and differentiating the right-hand side with Enzyme — see the differentiability example.