SimpleNonlinearSolve: single branchless loops that trace under Reactant - #1213
SimpleNonlinearSolve: single branchless loops that trace under Reactant#1213ChrisRackauckas-Claude wants to merge 2 commits into
Conversation
cf6dad1 to
bb507c6
Compare
|
Re-stacked on the rebased #1197 (after #1072); NonlinearSolveBase compat is now 🤖 Posted by an AI agent (Claude Code, model claude-fable-5) on behalf of Chris Rackauckas. Session: https://claude.ai/code/session_016LsC6pp9z6s5EABX9DnVjE |
|
CI triage: the red 🤖 Posted by an AI agent (Claude Code, model claude-fable-5) on behalf of Chris Rackauckas. Session: https://claude.ai/code/session_016LsC6pp9z6s5EABX9DnVjE |
4601664 to
458b609
Compare
Run every algorithm's ordinary `solve` dispatch under `Reactant.@compile`. The shared solver loop is a `ReactantCore.@trace while` over `CommonSolve.step!`, return codes are traced enums (EnzymeAD/Reactant.jl#3232, SciMLBase#1563) so the solution is built by `SciMLBase.NonlinearSolution` on every path, and the loop-carried cache state is refreshed by one reflective `dealias_traced!` instead of per-cache field lists. Trust-region, Levenberg-Marquardt, geodesic and dogleg updates are written once with `ifelse`; polyalgorithms are traced as a chain of their members. Forward-mode `AutoEnzyme` is preferred during compilation. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude <noreply@anthropic.com> Agent-Harness: Claude Code 2.1.251 Agent-Model: claude-fable-5 Agent-Session: https://claude.ai/code/session_016LsC6pp9z6s5EABX9DnVjE
Write each simple solver iteration once as a `ReactantCore.@trace while` loop without early returns, so the same code runs on the host and under `Reactant.@compile`. Loop state is prepared by `Utils.init_loop_state`/ `Utils.fresh` and the solution by `Utils.simple_solution`; the Reactant-only shadow solution, copies and duplicated loops are removed. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude <noreply@anthropic.com> Agent-Harness: Claude Code 2.1.251 Agent-Model: claude-fable-5 Agent-Session: https://claude.ai/code/session_016LsC6pp9z6s5EABX9DnVjE
458b609 to
67e5133
Compare
What changed and why
Each SimpleNonlinearSolve iteration (
SimpleNewtonRaphson,SimpleTrustRegion,SimpleBroyden,SimpleKlement,SimpleHalley) is now written once as a branchless loop that runs unchanged on the host and underReactant.@compile:for _ in 1:maxiterswithsolved && returnbecomesReactantCore.@trace track_numbers = false while (!solved) & (iterations < maxiters); the "initial guess is already a root" early return is subsumed (the loop does not execute andsimple_solutionreportsSuccess).@traceis a compile-time no-op outside Reactant.returnor branch (SimpleHalley's factorization failure,SimpleTrustRegion's shrink-threshold exit and step acceptance,SimpleKlement's Jacobian reset) are loop conditions orifelseselections.dogleg_method!!keeps its two host early-outs behind!within_compile()with the selection as the fallthrough.Utils.init_loop_statepromotes the return code and counter withNonlinearSolveBase.maybe_tracedand makes the loop-carried arrays distinct;Utils.freshre-applies that aftercheck_termination(whose best-iterate result aliases the termination cache) and after@bb copyto!(which rebinds rather than copies for traced arrays). Both are no-ops on the host.Utils.simple_solutionbuilds theNonlinearSolutionfromsolvedwithifelse/select; the previous_build_simple_solution,_copy_for_reactant,reactant.jland thenonlinear_solution_new_algshadow-solution hack are removed, as is theBase.@noinlineon the safe-bestcheck_termination(its tests pass on 1.10 without it).SimpleTrustRegion's host loop now forms the candidate Jacobian on every iteration and selects it (previously only accepted steps recomputed it). The result-type assertions insimple_solutionexist becauseSimpleTrustRegionwithautodiff = nothinghas a run-time-typed Jacobian (prepare_jacobianreturns a non-concreteDIExtras), which is pre-existing but previously never reached the return type; with them@inferred solve(...)holds on 1.10 and 1.12.Diff on top of the core PR: 9 files, +237/−102.
Verification
Same environment as the core PR (Reactant#3232, SciMLBase#1563, DI
571fc178), Julia 1.12.4 unless noted.Reactant group with the full matrix (Simple solvers included):
SimpleNonlinearSolve's own groups with this branch's NonlinearSolveBase developed in:
Before the
select/simple_solutionresult-type assertions, the 1.10 Core group failed@inferred solve(prob, SimpleTrustRegion())in the root-finding snippet (return type NonlinearSolution{_A, _B, _C, _D, …}); with them it passes on both versions.Formatted with Runic 1.10.0;
typosclean.Not verified
SimpleDFSaneandSimpleLimitedMemoryBroydenare unchanged here (SimpleDFSane's line search branches on traced values and is still unsupported under Reactant; limited-memory Broyden already traced).GROUP=Everything.🤖 Generated with Claude Code (model: claude-fable-5) on behalf of Chris Rackauckas. Session: https://claude.ai/code/session_016LsC6pp9z6s5EABX9DnVjE