Differentiable analysis
The entire pipeline has a pure functional path: extract a ModelState (positions and sections as plain data), evaluate, differentiate. Loading Zygote (or anything ChainRules-aware) activates Asap's rule extension automatically; a handful of small rules cover the sparse construction and the linear solve, and everything else differentiates natively.
The example differentiates the compliance of a small portal frame model with respect to every node coordinate:
using Zygote
solve!(model) # builds the analysis cache
state = extract_state(model)
# gradient of compliance w.r.t. every node coordinate — a 3 × n sensitivity field:
g = Zygote.gradient(state.X) do X
compliance(model, ModelState{Float64}(X, state.sections))
end[1]3×4 Matrix{Float64}:
-0.00149367 0.00124859 0.00259249 -0.0023474
-1.57914e-19 2.0817e-19 1.9245e-19 -2.42707e-19
-0.000569496 0.000580041 0.00236257 -0.00237311Gradients flow with respect to node positions, section properties, and semi-rigid connection stiffnesses. For design-variable bookkeeping (areas, coupled symmetric geometry, bounds) and optimization-ready objectives, use AsapOptim — a thin layer over this path, verified against the original published implementation to 13 digits (see its docs/AD_VERIFICATION_AND_BENCHMARKS.md).
Forward-mode AD (ForwardDiff, Enzyme forward) and Mooncake are supported through the same path via their own package extensions — load the AD package and differentiate; no further setup.