Repository navigation
meta(sciml): expose prepared operators to the SciML integrator stack #81
Description
Umbrella for handing MFO operators to OrdinaryDiffEq / LinearSolve. Nothing SciML exists today: no weak dep, no ext/…SciMLExt.jl, and every example hand-rolls its integrator. README.md no longer advertises OrdinaryDiffEq interop that is not there (#87 retracted it; README.md:150 now says there is no integration yet).
Status (2026-09-30): #86 and #87 are done. What remains is #65 first, then #73.
Sub-issues, in order
fix(linalg): prepared operators declare their traits — PreparedOperator supertype, PreparedComposed/PreparedAdjoint islinear/isconstant #86done (PR fix(linalg): prepared operators declare their traits #89) — prepared-operator trait hygiene —PreparedOperatorhas no supertype andPreparedComposed/PreparedAdjointdeclare noislinear/isconstant, so a wrapper forwarding traits gets thefalsedefault by accident. An invariant hazard independent of SciML; ship first.- feat(sciml): build the planned SciMLOperators jac_prototype adapter (DESIGN §7) #65 — next — the
jac_prototypeadapter (DESIGN §7). Enough thatODEFunction(f; jac_prototype = sciml(prepare(L)))works andKrylovJL_GMREScalls the zero-alloc flatmul!. The downstream forcing function is the microvascular model'sKenCarp47+ sparse-ILU detour. - feat(sciml): expose operators as (Split)ODEFunction and search integrators per model #73 — after feat(sciml): build the planned SciMLOperators jac_prototype adapter (DESIGN §7) #65 —
SplitODEFunctionfor the linear half plus a per-model work-precision search over IMEX / exponential integrators (a benchmark artifact, not a package feature). docs: retract the OrdinaryDiffEq interop claim and document field-level apply! as the explicit-stepping idiom #87done (PR docs: retract the OrdinaryDiffEq interop claim and document field-level apply! as the explicit-stepping idiom #90) — docs — fix the README claim, and document field-levelapply!as the explicit-stepping idiom (the preparedmul!leg pays 15–50% in flat copies that only Krylov needs).
Related, not parented: #63 (stacked multi-species operator) is what the reaction half of a split problem wants to look like; #62 (Robin) is what makes MultigridPreconditioner applicable to the downstream wall-coupled system.
Addendum questions (from the comment below)
- Q1 (what OrdinaryDiffEq requires of a state
u, and the minimal interface that makesFielda legal state) now lives in feat(sciml): expose operators as (Split)ODEFunction and search integrators per model #73's checklist. - Q3 (ghosts inline in a padded array vs. a flat interior with ghosts beside it in face buffers) is a DESIGN question, not a SciML one, and now lives as a checkbox in meta(distributed): multi-node and multi-GPU execution — backend API, IGG halo, tests, slab adjoint #79.
- Q2 (whether a Field-valued Krylov workspace beats the two staging copies per matvec, CPU and GPU separately) is still an open benchmark question here.
Constraints
- Extension only. SciMLOperators / OrdinaryDiffEq are weak deps, nothing enters
[deps](CLAUDE.md, DESIGN.md §7). - Do not route around the
mul!(y, ::AbstractOperator, x::AbstractVector)guard; a convenience constructor must still go throughprepare. - The affine
boundary_rhsterm needs a declared home in a split problem.
—
🤖 Claude tidied this one and moved the addendum questions to their proper homes.
claude-opus-5-5[1m] (high) · claude-code 2.1.285 · unreviewed
Activity
docs: retract the OrdinaryDiffEq interop claim, step examples via apply!
docs(design): explicit apply! idiom is a hand-rolled stepper, not ODE…
docs: retract the OrdinaryDiffEq interop claim, step examples via apply!
docs(design): explicit apply! idiom is a hand-rolled stepper, not ODE…
kylebeggs commentedon Sep 10, 2026
Addendum: are the staging copies actually necessary, or just the shape we ended up with?
#87 / #90 established that the flat
mul!path pays 15–50% inflat_to_interior!/interior_to_flat!staging, and steered explicit stepping to field-levelapply!to dodge it. That routes around the cost. It does not establish that the cost has to exist. Three questions surfaced reviewing that PR, in increasing order of scope — recording them here because #1 blocks this umbrella directly and #2/#3 are the layer underneath it.1. What actually stops a SciML explicit stepper from using
apply!?The RHS signature is a closure away, so the blocker is the state type, not the function. Worth establishing from scratch: what does OrdinaryDiffEq genuinely require of a state
u— which ofsimilar/copyto!/ broadcast /norm/length/ indexing, and which of those are needed only by adaptive stepping or by particular integrator families rather than by all of them?Fieldtoday definessimilarand nothing else (src/Fields.jl:207). So: what is the minimal interface that makesFielda legal state, what does implementing it over a padded array whose interior is a stridedviewcost, and does an interior-only broadcast/norm sit cleanly with the interior-only-DOF invariant or fight it?Whatever the answer, this is a gap that belongs on #73's list and currently isn't there.
2. Can the copies be removed from
mul!instead of routed around?Krylov.jl is generic over its vector type and lets you extend its kernel operations, so a Field-valued Krylov workspace is at least expressible. The question is whether it pays. Every workspace vector would become a padded array with a strided interior, so dots, axpys and norms trade contiguity (and GPU coalescing) for the saved copies, and GMRES(k) would carry k ghost rings it never fills. Two O(N) copies per matvec against a penalty on the 5–30 vector ops per iteration is not obviously a win in either direction — it wants a benchmark, not an argument.
The GPU case deserves separate measurement: there the staging is also two extra kernel launches, and on small grids launch overhead may swamp the bandwidth reasoning entirely.
3. Should the layout have been flat from the start?
That is, interior stored contiguously with ghosts held beside it in separate face buffers, rather than inline in a padded array. A solver vector could then alias the interior directly, and filling faces scales as O(surface) rather than two O(volume) passes. It is also plausibly the more natural fit for the #79 distributed work, since MPI wants contiguous face buffers regardless and inline ghosts have to be packed into them.
The cost lands on operator bodies. With ghosts inline, one broadcast over shifted slices is correct at every cell, boundary included — which is what the "leaf bodies stay array-level, device-agnostic, no per-backend code" structural rule rests on. With ghosts beside, the sweep splits into an interior core plus a boundary shell of up to 3ᴺ−1 regions, per leaf, per BC kind, and again for every declared adjoint and AD path. Whether that multiplier is as bad as it sounds is the open question, and it is worth checking against the one existing data point:
laplacian_7pt_noflux(DESIGN §5) is already a flat halo-free primitive, deliberately kept as a separate body, pinned to homogeneous no-flux, and locked to the halo-based leaf by a parity test. That precedent is evidence about how far the pattern generalizes — in either direction.Where the answers should land
🤖 Beep boop — Claude filed the questions, not the answers; Kyle gets to keep the fun part.