Repository navigation
Description
Status
Already scoped, not built. README.md:164-166 and DESIGN.md §7 describe a thin FunctionOperator-style adapter as the jac_prototype hook for implicit OrdinaryDiffEq, in a future ext/…SciMLExt.jl. This issue is to give it a real downstream forcing function rather than to propose it fresh.
The trait prerequisite has landed: #89 made PreparedOperator/PreparedForest declare islinear/isconstant/isselfadjoint/isdiagonal explicitly (src/linalg.jl:75-82) and gave PreparedComposed/PreparedAdjoint their trait methods (src/linalg.jl:308-309, :337-338), so the wrapper can forward traits from the prepared operator without falling through to a default it did not earn.
Why now
The microvascular transport model marches its coupled system with KenCarp47 + KrylovJL_GMRES. To hand OrdinaryDiffEq a matrix-free Jacobian it needs a SciMLOperators-shaped object, and MFO's LinearizedOp / EnzymeLinearizedOp is exactly that object wearing the wrong interface. Without the adapter, the downstream package is pushed into one of two bad options, and has taken both:
AutoFiniteDiffeverywhere, because its preallocatedFloat64work buffers rejectForwardDiff.Duals — so it gets finite-difference Jacobians even though MFO can supply exact ones via Enzyme.- Assemble a sparse
W = I − γJand ILU(0) it, with a hand-built Jacobian sparsity pattern and a Curtis–Powell–Reid colouring, purely to get a preconditioner intoLinearSolve. That is a full sparse-matrix detour bolted onto a matrix-free stack, and it exists only because the matrix-free path couldn't reach the integrator.
Both disappear if prepare/LinearizedOp can be presented as a SciML operator.
Minimum useful shape
jac_prototype only, as §7 already scopes it — enough that ODEFunction(f; jac_prototype = sciml(prepare(L))) works and KrylovJL_GMRES calls straight into the zero-alloc flat mul!. Lazy algebra and adjoints through the SciML interface can wait; the integrator hook is what unblocks.
Two pieces of the SciML contract are on that path, though, and are in scope here (moved from #73, which keeps the SplitODEFunction + solver-search layer above this one):
-
linearize!exposed asupdate_coefficients!. An implicit integrator refreshes the Jacobian at the current state throughupdate_coefficients!(J, u, p, t). The package already has exactly those semantics inlinearize!(J, u)(src/operators/linearize.jl:110-113forLinearizedOp,ext/MatrixFreeOperatorsEnzymeExt.jl:66-69forEnzymeLinearizedOp): it overwritesJ.u0.datain place and keeps the existingpreparevalid — refresh without re-prepare. The adapter'supdate_coefficients!should forward to it rather than reinvent it. -
ldiv!onMultigridPreconditioner. It exposes onlysize/eltype/mul!(src/multigrid.jl:378-395), which is Krylov.jl's preconditioner convention; LinearSolve'sPl/Pr(and therefore theprecsargument) wantldiv!. Without it the multigrid V-cycle cannot stand in for the downstream ILU.
Pairs naturally with #62 — a Robin-aware operator_diagonal is what makes MultigridPreconditioner usable as the precs argument, and between the two the downstream ILU path can be retired rather than ported.
—
🤖 Claude gave this issue two more chores from #73.
claude-opus-5-5[1m] (high) · claude-code 2.1.285 · unreviewed
Activity
docs: retract the OrdinaryDiffEq interop claim, step examples via apply!
docs: retract the OrdinaryDiffEq interop claim, step examples via apply!