Share ordinary solver paths with Reactant - #4436
ChrisRackauckas-Claude wants to merge 30 commits into
Conversation
|
Implementation/CI plan:
|
5e7c058 to
fcef277
Compare
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Codex <noreply@openai.com> Agent-Harness: Codex CLI 0.151.0 Agent-Model: unknown Agent-Session: local session ID 01a04f92-0d12-7990-926c-f3e5a23f3a31
fcef277 to
9f78a3c
Compare
| if ReactantCore.within_compile() | ||
| isnothing(dt) && integrator.opts.adaptive && auto_dt_reset!(integrator) | ||
| return nothing | ||
| end |
There was a problem hiding this comment.
This isn't doing the tdir flip? Why not do this the other way around and just if not in a compile, do the error messages?
There was a problem hiding this comment.
Addressed in 5ad0c97: handle_dt!(integrator, dt) always runs auto_dt_reset! (when needed) and the tdir sign fix via abs(dt) * tdir on the shared path. Only the host diagnostics (sign/isnan error messages) are gated behind !ReactantCore.within_compile(), because those predicates cannot drive ordinary if on traced values. Adaptive Reactant suite is 208/208 after this change (previously failed on TracedRNumber{Bool} in the old dt > 0 branch).
| if ReactantCore.within_compile() | ||
| return integrator.opts.adaptive ? | ||
| _traced_adaptive_solve!(integrator, integrator.controller_cache) : | ||
| _traced_fixed_step_solve!(integrator) | ||
| end |
There was a problem hiding this comment.
Why is this treated as separate code instead of integrating it?
There was a problem hiding this comment.
Agreed — the separate _traced_adaptive_solve! / _traced_fixed_step_solve! entry points are gone. SciMLBase.solve! is now one shared loop: ReactantCore.@trace while around _solve_step!, with compile-only finalization (_traced_finalize_solution) after the traced loop. Host-only pieces (error checks, handle_tstop!, postamble) stay behind within_compile() gates rather than a second solver.
| throw(ArgumentError("implicit algorithms are not supported inside Reactant compilation")) | ||
| !adaptive && isnothing(dt) && | ||
| throw(ArgumentError("dt is required for fixed-step solves inside Reactant compilation")) | ||
| isempty(saveat) || throw(ArgumentError("saveat is not supported inside Reactant compilation")) |
There was a problem hiding this comment.
doesn't reactant want a constant size output? That would require using saveat
There was a problem hiding this comment.
Yes — a constant-size compiled output would naturally be driven by saveat (or a fixed save_everystep count). Under compile today we force save_everystep=false / save_end=true so the traced solution is a single endpoint, which is statically sized without needing a dynamic save queue. Supporting saveat is the right next step once we can materialize a fixed-length traced timeseries from a compile-time-known grid; leaving the throw until that lands rather than half-supporting a growing BinaryHeap.
There was a problem hiding this comment.
we really need saveat for reactant
There was a problem hiding this comment.
Tracked in #4657 together with the other options still rejected under compilation (save_everystep, tstops, callbacks, implicit algorithms, etc.). This PR keeps the explicit ArgumentError for saveat inside Reactant compilation.
Cursor Agent CLI 2026.09.26-dd393fe, model auto.
| !adaptive && isnothing(dt) && | ||
| throw(ArgumentError("dt is required for fixed-step solves inside Reactant compilation")) |
There was a problem hiding this comment.
why is this different? dt with tstops is the only other way, but tstops aren't supported, so this is an unnecessary check?
There was a problem hiding this comment.
Kept on purpose: under compile, fixed-step still needs a concrete dt because auto-dt only runs for adaptive algorithms, and tstops/d_discontinuities are also rejected in the same gate. Without dt, a non-adaptive compiled solve has no step size to advance. Happy to drop the check once fixed-step can take a compile-time-constant alternative (e.g. a static tstop grid), but today it is not redundant with the tstops rejection.
| isimplicit(alg) && | ||
| throw(ArgumentError("implicit algorithms are not supported inside Reactant compilation")) |
There was a problem hiding this comment.
Implicit algorithms still need nonlinear solves / W-factorization paths that are not yet exercised on the shared traced step. The gate fails fast at _ode_init rather than tracing into unsupported caches. Explicit adaptive/fixed-step (Tsit5 etc.) is what the Reactant suite covers; lifting this is follow-up once OrdinaryDiffEqNonlinearSolve / W construction is Reactant-safe end-to-end.
Route compiled solves through the ordinary step loop, initial-step estimator, FSAL update, and I/PI controllers. Promote loop-carried state for tracing and retain narrow adapters for static output and endpoint handling. Cover custom controllers, zero dynamics, in-place fixed steps, and rejected first steps. Merge current master to resolve the test-group configuration and retain upstream fixes. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Codex <noreply@openai.com> Agent-Harness: Codex CLI 0.153.4 Agent-Model: gpt-6-astra Agent-Session: local transcript /home/crackauc/.codex/sessions/2026/09/05/rollout-2026-09-05T07-40-10-01a0715e-73c9-7e21-a18b-c5b0504a4bf3.jsonl
Keep the ordinary integrator's active FSAL aliases and select cache buffers at the tracing boundary. Composite cache initialization buffers need not belong to the active solver. The existing composite tests fail 2 of 16 checks without this correction and pass all 16 with it. Reactant passes 178/178 and QA passes 109/109. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Codex <noreply@openai.com> Agent-Harness: Codex CLI 0.153.4 Agent-Model: gpt-6-astra Agent-Session: local transcript /home/crackauc/.codex/sessions/2026/09/05/rollout-2026-09-05T07-40-10-01a0715e-73c9-7e21-a18b-c5b0504a4bf3.jsonl
|
Reworked the implementation to use the ordinary solver loop, initial-step estimator, controllers, acceptance/rejection, and FSAL update. Removed the separate traced solver and initial-step implementations. The remaining compilation checks handle traced state, buffer aliases, endpoint stops, and output/error boundaries. Local verification: Reactant 178/178; QA 109/109; full InterfaceI passed in 2367.4 seconds; targeted composite tests 16/16; docs build, Runic, and typos passed. The new IController regression errors on the original implementation and passes with this revision; the updated body includes the code and output. Commits: The PR remains a draft and should be ignored until reviewed by @ChrisRackauckas. CI is running. 🤖 Generated with Codex CLI 0.153.4 (model: gpt-6-astra). Session: local transcript |
| if isinplace(integrator.sol.prob) | ||
| recursivecopy!(integrator.fsalfirst, integrator.fsallast) | ||
| fsalfirst, fsallast = if ReactantCore.within_compile() | ||
| get_fsalfirstlast(integrator.cache, integrator.u) |
There was a problem hiding this comment.
why would this be a function call only within compile?
There was a problem hiding this comment.
Both execution modes now use the same FSAL-buffer accessor and copy. Composite/default/constant caches retain their active integrator buffers because their initialization accessor does not identify the current runtime buffers.
Updated in abb04b3. Clean local Reactant suite: 208/208; QA: 109/109.
| if ReactantCore.within_compile() | ||
| dt = integrator.opts.adaptive ? integrator.dt : integrator.dtcache | ||
| integrator.dt = integrator.tdir * min( | ||
| abs(dt), abs(first_tstop(integrator) - integrator.tdir * integrator.t) | ||
| ) | ||
| return nothing | ||
| end |
There was a problem hiding this comment.
why is clamping ignored in reactant compile?
There was a problem hiding this comment.
Removed the compilation-only timestep-clipping implementation. Both modes now use the normal stop flags, clipping, roundoff tolerance, and restoration of the proposed timestep.
Updated in abb04b3. Clean local Reactant suite: 208/208; QA: 109/109.
| integrator.accept_step = ( | ||
| !integrator.isout && | ||
| !integrator.isout & | ||
| accept_step_controller( |
There was a problem hiding this comment.
The built-in acceptance predicates only inspect controller state, but eager evaluation was unnecessary. The shared path now evaluates the predicate only when the candidate is in-domain, preserving short-circuit behavior for custom controllers as well.
Updated in abb04b3. Clean local Reactant suite: 208/208; QA: 109/109.
| # because it also checks if partials are NaN | ||
| # https://discourse.julialang.org/t/incorporating-forcing-functions-in-the-ode-model/70133/26 | ||
| if isnan(d₁) | ||
| if !ReactantCore.within_compile() && isnan(d₁) |
There was a problem hiding this comment.
why would reactant compile not do a NaN check?
There was a problem hiding this comment.
The NaN checks and fallback are now part of the common initial-step control flow, including compiled solves. Added native/compiled fallback parity tests for both RHS forms. The IIP path also checks the derivative directly because the fast-math norm can hide NaNs from a later scalar check; Float32/Float64 native regressions cover that fallback.
Updated in abb04b3. Clean local Reactant suite: 208/208; QA: 109/109.
| end | ||
| return tdir * max(dtmin, min(100dt₀, dt₁, dtmax_tdir)) | ||
| result_dt = tdir * max(dtmin, min(100dt₀, dt₁, dtmax_tdir)) | ||
| if ReactantCore.within_compile() |
There was a problem hiding this comment.
What exactly is this doing?
There was a problem hiding this comment.
That deferred correction was compensating for skipped early decisions during tracing. Removed it: NaN and constant-derivative decisions now occur in the shared initial-step path.
Updated in abb04b3. Clean local Reactant suite: 208/208; QA: 109/109.
| # Function wrappers hide types from Reactant and provide no compile-time reuse inside `@jit`. | ||
| SciMLBase.specialization(::ODEFunction{iip, SciMLBase.AutoSpecialize}) where {iip} = | ||
| ReactantCore.within_compile() ? SciMLBase.FullSpecialize : SciMLBase.AutoSpecialize |
There was a problem hiding this comment.
This is piracy, should go to SciMLBase
There was a problem hiding this comment.
Moved specialization dispatch to a ReactantCore extension owned by SciMLBase, with instance/type and outside-compilation tests: SciML/SciMLBase.jl#1564. OrdinaryDiffEq no longer defines those methods.
Updated in abb04b3. Clean local Reactant suite: 208/208; QA: 109/109.
| if ReactantCore.within_compile() && adaptive && | ||
| !(controller_cache isa Union{IControllerCache, PIControllerCache}) | ||
| throw(ArgumentError("only IController and PIController are supported inside Reactant compilation")) |
There was a problem hiding this comment.
Removed the I/PI whitelist. PID now uses the common controller implementation with a traceable error history, and is covered by the compiled solve tests.
Updated in abb04b3. Clean local Reactant suite: 208/208; QA: 109/109.
Use shared FSAL access, stop clipping, controller acceptance, and initial-step control flow. Support PID tuple history and keep specialization in SciMLBase. Cover compiled clipping, PID and NaN parity plus native controller reinit and NaN initial-step regressions. Merge current upstream solver changes. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Codex <noreply@openai.com> Agent-Harness: Codex CLI 0.153.4 Agent-Model: gpt-6-astra Agent-Session: local transcript /home/crackauc/.codex/sessions/2026/09/05/rollout-2026-09-05T07-40-10-01a0715e-73c9-7e21-a18b-c5b0504a4bf3.jsonl
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Codex <noreply@openai.com> Agent-Harness: Codex CLI 0.153.4 Agent-Model: gpt-6-astra Agent-Session: local transcript /home/crackauc/.codex/sessions/2026/09/05/rollout-2026-09-05T07-40-10-01a0715e-73c9-7e21-a18b-c5b0504a4bf3.jsonl
Resolve the Project.toml test-target conflict by keeping master's FunctionWrappersWrappers entry and this branch's Reactant extras. Raise the SciMLBase floor to 3.56 for the released parametric-retcode work, drop the SciMLBase PR branch pin, point the Reactant test group at EnzymeAD/Reactant.jl main, and keep handle_dt!/stats paths Reactant-compatible after the merge. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Cursor Agent <noreply@cursor.com> Agent-Harness: Cursor Agent CLI 2026.09.26-dd393fe Agent-Model: auto Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-cursor/log.txt on amdci2.julia.csail.mit.edu
Traced enums landed on Reactant main but are not released yet (latest registry is still v0.2.288). Resolve Reactant/ReactantCore from main so the Reactant group exercises the merged enum design. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Cursor Agent <noreply@cursor.com> Agent-Harness: Cursor Agent CLI 2026.09.26-dd393fe Agent-Model: auto Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-cursor/log.txt on amdci2.julia.csail.mit.edu
Avoid traced `dt > 0` / `iszero(dt)` as ordinary `if` conditions during compilation. Keep auto_dt_reset! and the tdir sign correction on the shared path; restrict host-only error/NaN diagnostics to non-compile runs, matching the review request on the compile early-return. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Cursor Agent <noreply@cursor.com> Agent-Harness: Cursor Agent CLI 2026.09.26-dd393fe Agent-Model: auto Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-cursor/log.txt on amdci2.julia.csail.mit.edu
Move the initial-dt epsilon warning into the non-NaN branch so JET no longer sees an undefined dt₀ after ReactantCore.@trace if, and teach InterfaceIII/StochasticDiffEq CustomArray tests DiffEqBase.NAN_CHECK. Drop root Project.toml Reactant main sources (GROUP=Reactant still pins via runtests.jl) so Downgrade does not resolve Reactant from git. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Cursor Agent <noreply@cursor.com> Agent-Harness: Cursor Agent CLI 2026.09.26-dd393fe Agent-Model: auto Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-cursor/log.txt on amdci2.julia.csail.mit.edu
Bring in Trapezoid non-diagonal mass-matrix support (SciML#4631), BDF JET FullSpecialize (SciML#4627), and other master fixes needed for Sublibrary CI.
…acks update_fsal! was routing through get_fsalfirstlast, which returns throwaway zero(...) for RKM/ExpRK and wiped inplace FSAL (order ~0). Restore integrator.fsalfirst/fsallast copies. Match DelayDiffEq's tstop_tol via eps(magnitude). Handle ScalarOperator in _mmdiag. Treat empty CallbackSet as no callbacks for GlobalErrorEstimation (DiffEqBase Julia≥1.12 erasure). Co-authored-by: Cursor <cursoragent@cursor.com>
Prefer stable cache-owned FSAL buffers after Reactant dealiasing while keeping throwaway get_fsalfirstlast fallbacks for ExpRK; specialize _mmdiag for UniformScaling; path-source SDIRK for NonlinearSolve tests; and avoid non-public DiffEqBase.has_callbacks in GlobalDiffEq QA. Co-authored-by: Cursor <cursoragent@cursor.com>
Use NAN_CHECK only when a method exists so master's CustomArray fixture works again; select FSAL via integrator buffers on the host (cache fields only under Reactant when owned by the cache); bootstrap Reactant at the group boundary for GROUP=Everything; add an ExpRK FSAL allocation guard. Co-authored-by: Cursor <cursoragent@cursor.com>
Pkg.test runs with --check-bounds=yes, under which ETDRK2 step! allocates a constant ~64 bytes on master and head alike, so the absolute step! == 0 assertion failed in CI on both revisions. Guard size-independence instead (step! allocations at n=10 equal those at n=10000) and keep the update_fsal! == 0 assertions, which discriminate: they fail on 080d4c1 with 576/320384 bytes. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: opencode <noreply@opencode.ai> Agent-Harness: opencode 1.18.31 Agent-Model: opencode/longcat-2.5-preview-free Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-opencode/log.txt on amdci2.julia.csail.mit.edu
Some caches return an owned first buffer and a fresh last one via get_fsalfirstlast; accepting the pair when either buffer is owned could bind perform_step! to a throwaway. Require both to be cache-owned and keep host behavior unchanged. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: opencode <noreply@opencode.ai> Agent-Harness: opencode 1.18.31 Agent-Model: opencode/longcat-2.5-preview-free Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-opencode/log.txt on amdci2.julia.csail.mit.edu
The broader sibling [sources] block made the downgrade job resolve local Core/BDF/FIRK/Rosenbrock/Differentiation/DevTools/DiffEqBase (PR head code) against downgraded registry deps, and nsa_stats_tests.jl:91 failed under Julia 1.11 (1.28235 <= 1.26195). Master's NonlinearSolve Project.toml has no [sources] and that downgrade stays green. Path-source only SDIRK, which the Trapezoid DAE tests need for in-tree SciML#4631 before that release is in General; nsa_stats passes 39/39 locally with this package selection. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: opencode <noreply@opencode.ai> Agent-Harness: opencode 1.18.31 Agent-Model: opencode/longcat-2.5-preview-free Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-opencode/log.txt on amdci2.julia.csail.mit.edu
NAN_CHECK on Vector{<:Dual} only sees NaN values, not NaN Dual partials;
that is DiffEqBase's behavior, not something this code relies on.
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: opencode <noreply@opencode.ai>
Agent-Harness: opencode 1.18.31
Agent-Model: opencode/longcat-2.5-preview-free
Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-opencode/log.txt on amdci2.julia.csail.mit.edu
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: opencode <noreply@opencode.ai> Agent-Harness: opencode 1.18.31 Agent-Model: opencode/longcat-2.5-preview-free Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-opencode/log.txt on amdci2.julia.csail.mit.edu
This reverts commit 3ff6120.
Master's NonlinearSolve Project.toml has no [sources] block; the block added in 080d4c1 makes the downgrade job resolve local siblings against downgraded deps and fails nsa_stats_tests.jl:91 under Julia 1.11, and narrowing it to SDIRK makes the downgrade job unresolvable (local SDIRK 2.9.7 vs downgrade-pinned BDF 2.0.0). With no [sources] the downgrade job resolves registry packages as on master. The regular NonlinearSolve Core job then uses registry SDIRK 2.9.6, which lacks SciML#4631; the Trapezoid mass-matrix and nsa_noinit failures that produces are master-latent (see CI triage in the PR body). Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: opencode <noreply@opencode.ai> Agent-Harness: opencode 1.18.31 Agent-Model: opencode/longcat-2.5-preview-free Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-opencode/log.txt on amdci2.julia.csail.mit.edu
Release versioning is a maintainer call; this PR does not bump versions. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: opencode <noreply@opencode.ai> Agent-Harness: opencode 1.18.31 Agent-Model: opencode/longcat-2.5-preview-free Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-opencode/log.txt on amdci2.julia.csail.mit.edu
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: opencode <noreply@opencode.ai> Agent-Harness: opencode 1.18.31 Agent-Model: opencode/longcat-2.5-preview-free Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-opencode/log.txt on amdci2.julia.csail.mit.edu
| # DiffEqBase.has_callbacks is not public (ExplicitImports QA). Mirror its | ||
| # semantics with the public `isempty(::CallbackSet)` API so empty erasure | ||
| # CallbackSets injected on Julia ≥1.12 are not treated as real callbacks. |
There was a problem hiding this comment.
Done in 00b2ab0 (docs placement fix in 399ec3b): DiffEqBase.has_callbacks is now public, documented next to merge_problem_kwargs on the DiffEqBase developer API page, covered by an ispublic + empty-CallbackSet unit test, and GlobalDiffEq calls DiffEqBase.has_callbacks directly (the local _has_nonzero_callbacks workaround is gone).
Cursor Agent CLI 2026.09.26-dd393fe, model auto.
| Reactant = "3c362404-f566-11ee-1572-e11a4b42c853" | ||
| ReactantCore = "a3311ec8-5e00-46d5-b541-4f83e724a433" |
There was a problem hiding this comment.
if reactant is a separate test group, is this needed here?
There was a problem hiding this comment.
Removed in 1648dcd: Reactant/ReactantCore are no longer in the root [extras]/[targets]/[compat]. The Reactant group bootstraps itself in reactant_group() with the released packages that include traced enums (Reactant 0.2.289 and ReactantCore 0.1.23), replacing the previous pin to EnzymeAD/Reactant.jl main.
Cursor Agent CLI 2026.09.26-dd393fe, model auto.
Document the helper on the DiffEqBase API pages and drop the local _has_nonzero_callbacks workaround that mirrored its empty-CallbackSet semantics for ExplicitImports QA. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Cursor Agent <noreply@cursor.com> Agent-Harness: Cursor Agent CLI 2026.09.26-dd393fe Agent-Model: auto Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-cursor/log.txt on amdci2.julia.csail.mit.edu
Drop Reactant/ReactantCore from the root extras/targets/compat now that the Reactant group bootstraps the released packages with traced enums. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Cursor Agent <noreply@cursor.com> Agent-Harness: Cursor Agent CLI 2026.09.26-dd393fe Agent-Model: auto Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-cursor/log.txt on amdci2.julia.csail.mit.edu
Avoid a Documenter duplicate with the user-facing DiffEqBase page; the helper lives next to merge_problem_kwargs in the developer extension API. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Cursor Agent <noreply@cursor.com> Agent-Harness: Cursor Agent CLI 2026.09.26-dd393fe Agent-Model: auto Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-cursor/log.txt on amdci2.julia.csail.mit.edu
Resolve initdt conflicts by keeping Reactant-traced NaN/control flow while adopting master's SciML#4601 tiny-first-guess fallback (refine before falling back). Keep both the Reactant NaN and Float32/Float64 initdt regression tests. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Cursor Agent <noreply@cursor.com> Agent-Harness: Cursor Agent CLI 2026.09.26-dd393fe Agent-Model: auto Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-cursor/log.txt on amdci2.julia.csail.mit.edu
Merge of master's always-refine + tiny fallback into the Reactant @trace path: use bitwise tiny_first, refine always, then ifelse for the fallback so nested @trace/SSA issues do not break IIP adaptive compiles. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Cursor Agent <noreply@cursor.com> Agent-Harness: Cursor Agent CLI 2026.09.28-64d2043 Agent-Model: auto Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-cursor/log.txt on amdci2.julia.csail.mit.edu
The Reactant-safe `&`/`|` tiny-dt fallback always evaluates `eps(::Type)`, which MethodErrors on DynamicQuantities.Quantity and broke Downstream. Unwrap via SciMLBase.value like dtmin. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Cursor Agent <noreply@cursor.com> Agent-Harness: Cursor Agent CLI Agent-Model: auto
Re-emit master's :dt_epsilon message on the IIP path, drop the OOP tiny-dt fallback (SciML#4601 is IIP-only), and reject non-constant ScalarOperator mass matrices in ESDIRK instead of returning a wrong constant answer. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Cursor Agent <noreply@cursor.com> Agent-Harness: Cursor Agent CLI 2026.10.01-14929f9 Agent-Model: auto Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-cursor/log.txt on amdci2.julia.csail.mit.edu
Restore OrdinaryDiffEqSDIRK and the Differentiation ScalarOperator mass-matrix test to match master. ESDIRK λI/ScalarOperator support belongs in a separate follow-up, not this Reactant PR. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Cursor Agent <noreply@cursor.com> Agent-Harness: Cursor Agent CLI 2026.10.01-14929f9 Agent-Model: auto Agent-Session: local session, transcript at /home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-cursor/log.txt on amdci2.julia.csail.mit.edu
Please ignore until reviewed by @ChrisRackauckas.
What changed and why
Explicit ODE solves inside Reactant use the ordinary solver loop, step acceptance/rejection, FSAL updates, initial-step estimator, and I/PI/PID controllers on a shared path (no separate Reactant solver). This tip addresses Chris's 2026-09-27 review comments that are in-scope for this PR, plus Round-6/7 host-parity fixes:
DiffEqBase.has_callbacksis public (00b2ab09d, docs placement399ec3b92).1648dcd8a): group bootstraps released Reactant 0.2.289 / ReactantCore 0.1.23.origin/master(033d5949c) keeping master's Float32: initdt falls back to dt=1e-6 via absolute eps guard, +27% steps on Lorenz #4601 tiny-dt fallback and this PR's Reactant@trace/NaN path (4bf5da8cd); unitful eps unwrap inbb9ec861e.9801a0ddb): restore IIP:dt_epsilonmessage; drop OOP tiny-dt fallback (master's Float32: initdt falls back to dt=1e-6 via absolute eps guard, +27% steps on Lorenz #4601 is IIP-only).c364243b6): restoreOrdinaryDiffEqSDIRKand the Differentiation ScalarOperator mass-matrix test to matchorigin/masterexactly. ESDIRKλI/ScalarOperatorsupport is a separate follow-up.Follow-ups (separate PRs):
λI/ScalarOperatormass matrices (against the Fix jacobian2W! DimensionMismatch for ScalarOperator mass matrix (#3915) #3916 Differentiation test; master regression from only allow diagonal M for ESDIRK #4560).Part of #4436.
Round-7 review (CHANGES → fixed in
c364243b6)_mmdiag_values, theisconstantguard, and theUniformScalingimport from SDIRK.git diff origin/master -- lib/OrdinaryDiffEqSDIRKis empty. Reactant does not need that path.sol_t.u[end][1] ≈ 0.5 atol = 1e-5. Differentiation Core fails on this tip the same way as on master (diag(::ScalarOperator)MethodError from only allow diagonal M for ESDIRK #4560).Verification (tip
c364243b6)CI triage
Differentiation Core is pre-existing on master from #4560 (
diag(mass_matrix)in ESDIRK). The test is the correct #3916 physics assertion; it is not edited or skipped here. Tracked in #4661/#4582.Other reds previously listed on
bb9ec861e(SciMLSensitivity Core1, downgrade DiffEqBase, DelayDiffEq Waltman Inf/NaN on master+PR, DiffEqBase lts, GPU, NonlinearSolve Core, Rosenbrock QA / open #4668) remain master-caused.Risk assessment
9801a0ddb.origin/master.What was NOT verified
c364243b6(CI pending after push).saveat/ other Reactant compilation: remaining unsupported solve options (saveat, callbacks, implicit, tstops, ...) #4657 options under Reactant.Please ignore this draft until reviewed by @ChrisRackauckas.
Cursor Agent CLI, model auto. Transcript under
/home/crackauc/sandbox/agent-jobs/OrdinaryDiffEq.jl/jobs/4436-cursor/on amdci2.julia.csail.mit.edu.