Fix instability Jacobian diagnostic on in-place problems with an analytic Jacobian - #4191
Conversation
|
CI note: Nothing in this PR touches a compat bound. Format and spelling are green: |
|
Second CI note. Every red check so far is pre-existing, none of it touches this change:
The Green so far and relevant to this PR: Runic, runic-suggestions, Spell Check, |
|
Third CI note — the group this PR actually changes is green on all three Julia versions: Also green: Remaining reds, all verified pre-existing:
|
`get_fresh_jacobian` unconditionally delegated to the out-of-place `calc_J`, so on an in-place problem with an analytic `jac!` the diagnostic called `f.jac(uprev, p, t)` and threw instead of reporting the instability. Route mutable caches through `calc_J!` into a scratch copy of `cache.J`, and guard the whole Jacobian-analysis block so a diagnostic failure degrades to "Jacobian analysis unavailable" rather than converting a `retcode = Unstable` return into a thrown exception. Closes SciML#4155 Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
96fa972 to
728b074
Compare
Please ignore until reviewed by @ChrisRackauckas.
Closes #4155
What changed and why
get_fresh_jacobianunconditionally delegated to the out-of-placecalc_J, which callsf.jac(uprev, p, t). On an in-place problem with an analyticjac!(J, u, p, t)that throws, so the instability diagnostic replaced theretcode = Unstablereport with aFunctionWrappersWrappers.NoFunctionWrapperFoundError. Mutable caches now route throughcalc_J!into a scratch copy ofcache.J(constant caches keep usingcalc_J), preserving the existingnjacsrestore in both. Separately, the whole Jacobian-analysis block inlog_numerical_instabilityis now guarded: this is best-effort reporting on an already-failed solve, so a failure there degrades to aJacobian analysis unavailable: <reason>line and the instability warning still prints.Branch check on Rosenbrock and BDF
The
#rosenbrockbranch keys off_get_W(integrator), i.e.integrator.W— that field is the SDE noise process, which isnothingfor a pure ODE, not the W-matrix. So that branch never fires for an ODE solve and Rosenbrock caches (which do havecache.J) fall through to theget_fresh_jacobianbranch.Rodas5Pwas therefore broken by this bug too, which the tests confirm below. The BDF branch (cache.nlsolver.cache.J) reads an existing matrix and was genuinely unaffected —FBDFpasses before and after. I left the deadW.Jbranch alone rather than widening the diff; it looks like a separate latent bug worth its own issue.Verification
Base:
9d23061at the time of the reproduction, rebased onto4cb8020. Julia 1.12.6.Failing before (
src/changes stashed, tests kept)Passing after
The guard alone, isolated
With the dispatch fix applied but the
try/catchguard reverted, only the guard test fails — confirming the two parts are independently load-bearing:The diagnostic still works
The no-analytic-Jacobian case still emits all three sections, and the in-place analytic-Jacobian case now emits the identical report instead of throwing:
Test groups, format, spelling
Runic 1.5.1 and
typosare clean on every touched file.What I did NOT verify
integrator.Wis an actual noise process.jac_prototypes through the newcopy(cache.J)scratch.copywas chosen oversimilar/zeroprecisely to preserve sparsity structure, andcalc_J!already runsprepare_sparse_jac!for CSC prototypes, but I have no test covering an unstable sparse-Jacobian solve.Worth pushing back on
copy(cache.J)allocates a fresh Jacobian on the failure path. Writing intocache.Jdirectly would avoid it, since the cache is discarded immediately after; I chose the copy so the diagnostic has no side effect on solver state.try/catcharound_analyze_instability_jacobianonly, rethrowingInterruptException. It does not wrap the state or error-estimate analysis.W.Jdead-branch finding above — I did not change it, but someone should decide whether it was meant to read the W-matrix.The QA failure is pre-existing on master and unrelated to this PR. I reran
GROUP=QAon a detached checkout of4cb8020with no changes and got the identicalisempty([:SciMLBase, :SciMLLogging])failure (87 passed, 1 failed, 16.4s). A separate agent is bisecting it.Loggingis added to[extras]/[targets]becausePkg.test's restrictedLOAD_PATHdoes not expose unlisted stdlibs, and the new test usesTest.TestLogger+with_loggerto capture the warning text.