Skip to content

NonlinearSolveAlg: give inner NonlinearFunction W's structure as jac_prototype#3819

Draft
ChrisRackauckas-Claude wants to merge 2 commits into
SciML:masterfrom
ChrisRackauckas-Claude:nsa-inner-jac-prototype
Draft

NonlinearSolveAlg: give inner NonlinearFunction W's structure as jac_prototype#3819
ChrisRackauckas-Claude wants to merge 2 commits into
SciML:masterfrom
ChrisRackauckas-Claude:nsa-inner-jac-prototype

Conversation

@ChrisRackauckas-Claude

Copy link
Copy Markdown
Contributor

Please ignore until reviewed by @ChrisRackauckas. Draft opened by an agent.

Bug

Under W-reuse (#3693/#3753) the inner NonlinearFunction gets the analytic-jac closure copyto!(J_out, W) but no jac_prototype. NonlinearSolveBase allocates a dense J for analytic-jac functions without a prototype (jacobian.jl: safe_similar(fu, n, n)), so for sparse/structured W:

  • with the ODE's sparse-only linsolve piped in (_nlalg_with_linsolve): hard crashFBDF(linsolve = KLUFactorization(), nlsolve = NonlinearSolveAlg(NewtonRaphson())) on a sparse-jac_prototype brusselator dies with FieldError: Matrix has no field nzval;
  • with the default linsolve: silent dense LU on a sparse system.

Fix

Pass jac_prototype = similar(W_for_reuse) so the inner Jacobian mirrors W's type and structure; the copyto! closure is then structure-preserving and sparse factorizations work.

Verified on the 512-unknown sparse brusselator (before → after): inner J Matrix{Float64}SparseMatrixCSC (nnz=3072), KLU crash → clean solve. Combined with the J/W-update split and SciML/NonlinearSolve.jl#1003, FBDF+NSA+KLU reaches 1.00× of FBDF+NLNewton+KLU wall time (220.3 vs 219.5 ms) — from a crash/3.9× before.

Tests

New nsa_sparse_tests.jl: asserts the inner NonlinearSolve J is SparseMatrixCSC under W-reuse, and that FBDF/TRBDF2 + NSA + KLUFactorization solve a sparse-jac_prototype problem to the reference (6/6 pass locally). Local suite runs on this branch (Julia 1.12): Newton 6/6, Linear Nonlinear 36/36, W-Operator Prototype, NSA Sparse 6/6, Sparse Algebraic 12/12, Sparse DAE Init 27/27, Linear Solver 113/113, Split ODE 10/10 — no failures (full Core group exceeds a 1h local window; remaining suites left to CI).

Part of a series: #3816 (nsolve stat), #3818 (recompute_jacobian on W===nothing paths), SciML/NonlinearSolve.jl#1003 (refactorization reuse), and a J/W-update-split PR to follow.

🤖 Generated with Claude Code

…prototype

Under W-reuse the inner NonlinearSolve builds its Jacobian buffer without a
jac_prototype, and NonlinearSolveBase allocates a dense matrix for analytic-jac
functions. A sparse/structured W then degrades to dense LU, and sparse-only
linear solvers piped in from the ODE algorithm crash outright
(KLUFactorization: `FieldError: Matrix has no field nzval`).

Pass `jac_prototype = similar(W_for_reuse)` so the inner Jacobian mirrors W's
type and sparsity; `copyto!(J_out, W)` in the jac closure is then
structure-preserving and sparse factorizations work.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas-Claude

Copy link
Copy Markdown
Contributor Author

CI triage: the failing lanes here are master-baseline, not from this change — StochasticDiffEq QA/downgrade + GPU lanes fail on all concurrent PRs including the 2-line #3816; the OrdinaryDiffEqNonlinearSolve Core lane failure is the pre-existing Dependent Mass Matrix Tests error (mass_matrix_tests.jl:283, OOP NLNewton + non-concrete operator W, 221 pass/2 error/4 broken — the limitation #3796 documents), reproduced identically on unmodified master locally. The MTK nlstep lane failures are the two order-test failures that #3818 fixes.

The jac_prototype fix surfaced a latent resize hazard: the build-time jac
closure captured the original W array, and the build-time jac_prototype kept
its original size, so after `resize!(integrator, n)` the length-mismatch
rebuild in initialize! produced an inner Jacobian/linear cache at the stale
size (CI Cache Tests: `DimensionMismatch: B has leading dimension 2, but
needs 1`; on master the same stale closure was masked because the dense inner
J was sized from u instead of the prototype).

Replace the closure with a `WReuseJac` callable struct holding a
`Ref{typeof(W)}`. Its type depends only on W's type, not on a captured
binding, so when resize_J_W! replaces the W array the length-mismatch branch
just swaps the Ref target and remakes the NonlinearFunction with a same-typed
`similar(cache.W)` prototype — cache.prob's concrete type is preserved.

Adds a growing-cell resize test (mirroring ode_cache_tests) asserting the
resize completes without dimension errors for TRBDF2/KenCarp4/ImplicitEuler
and a Success retcode for TRBDF2. Retcodes for the other methods on this
marginally-stable scenario are method-sensitive on master too
(ImplicitEuler+NLNewton is Unstable on the deterministic variant), so they
are deliberately not asserted.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas-Claude

Copy link
Copy Markdown
Contributor Author

The Integrators_II Cache Tests failures on the previous push were real — the jac_prototype fix surfaced a latent resize hazard (build-time jac closure captured the original W array; build-time prototype kept its size → DimensionMismatch after resize!(integrator, n)). Fixed in the latest push by replacing the closure with a WReuseJac Ref-holding callable (type stable across resizes) and remaking the inner NonlinearFunction with a same-typed similar(cache.W) prototype in the length-mismatch branch. Added a growing-cell resize regression test mirroring ode_cache_tests. Local: sparse+resize+Newton+linear-nonlinear suites all pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants