Skip to content

NewtonBisection: correct the Newton step at multiple roots - #1375

Draft
ChrisRackauckas wants to merge 11 commits into
masterfrom
newton-bisection-multiplicity
Draft

ChrisRackauckas wants to merge 11 commits into
masterfrom
newton-bisection-multiplicity

Conversation

@ChrisRackauckas

@ChrisRackauckas ChrisRackauckas commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

🚧 UNREVIEWED — awaiting review by @ChrisRackauckas. Filed by an AI agent running as @ChrisRackauckas; Chris has not seen this.
Harness: Codex CLI 0.163.0 · Model: gpt-6.1-sol
Conversation: local job 1377-codex (no session URL)

NewtonBisection corrects the Newton step at multiple roots, rejects undefined residuals before changing the bracket, uses an overflow-safe bisection safeguard, and normalizes integer brackets to floating point before initialization. Existing derivative-free Dual solves with a supplied jacobian use a plain-function wrapper and allocate zero bytes. The concrete Newton derivative wrapper and AD backend validation remain in the AD layer.

The original NewtonBisection branch belongs to the fork behind #1330, so this PR targets master; its dependents target the owned stack branches. Please ignore until reviewed by @ChrisRackauckas. Part of #1377 (#1377).

Failing before / passing after

The same committed safeguard tests ran through a runner that imports Test and includes newton_safeguard_tests.jl inside @testset "Newton safeguards":

julia +1.10 --project="$VALIDATION_ENV" "$REGRESSION_RUNNER" "$CHECKOUT"
Before: Newton safeguards | 20 passed, 113 failed, 0 errored, 0 broken
After:  Newton safeguards | 133 passed, 133 total

The derivative-free wrapper test uses the same 48 assertions before and after the guard. The runner imports Test and includes interval_wrapper_tests.jl; four zero-allocation assertions discriminate the bug.

julia +1.12 --project="$VALIDATION_ENV" "$WRAPPER_RUNNER" "$WRAPPER_TESTS"
Before: 44 passed, 4 failed, 0 errored, 0 broken
After: Interval wrapper inference and allocations | 48 48

Integer endpoint normalization is covered with positive, negative and reversed brackets, both default and zero absolute tolerance. The reference is signed sqrt(big(2)), computed independently of the solver. The same test file produces eight initialization errors before the fix and 32 passing assertions after it.

julia +1.12 --project="$VALIDATION_ENV" -e 'using Test, SciMLBase; include(ARGS[1])' "$INTEGER_TESTS"
Before: Integer brackets | 8 errored
After:  Integer brackets | 32 32

Verification

Current source and test files were checked byte-for-byte against the isolated snapshot used for the official groups below. The snapshot includes the repository project and documentation so QA performs its normal public API checks. Julia 1.10 environments add SciMLBase 3.58 first and develop the local sublibraries and root.

GROUP=Core julia +1.12 --project="$CHECKOUT/lib/BracketingNonlinearSolve" -e 'using Pkg; Pkg.instantiate(); Pkg.test()'
Test Summary:                              | Pass  Total  Time
Interval wrapper inference and allocations |   48     48  5.0s
Test Summary:                            | Pass  Total  Time
Derivative methods with integer brackets |   32     32  0.4s
     Testing BracketingNonlinearSolve tests passed 
GROUP=Core julia +1.10 --project="$LTS_VALIDATION_ENV" -e 'using Pkg; Pkg.test("BracketingNonlinearSolve")'
Test Summary:                              | Pass  Total  Time
Interval wrapper inference and allocations |   48     48  3.6s
Test Summary:                            | Pass  Total  Time
Derivative methods with integer brackets |   32     32  0.3s
     Testing BracketingNonlinearSolve tests passed 
GROUP=QA julia +1.12 --project="$CHECKOUT/lib/BracketingNonlinearSolve" -e 'using Pkg; Pkg.test()'
  [3f19e933] + p7zip_jll v17.7.0+0
        Info Packages marked with ⌅ have new versions available but compatibility constraints restrict them from upgrading. To see why use `status --outdated -m`
Test Summary: | Pass  Total   Time
QA            |   20     20  34.6s
     Testing BracketingNonlinearSolve tests passed 
GROUP=QA julia +1.10 --project="$ROOT_VALIDATION_ENV" -e 'using Pkg; Pkg.test("NonlinearSolve")'
  [3f19e933] + p7zip_jll v17.6.1+0
        Info Packages marked with ⌅ have new versions available but compatibility constraints restrict them from upgrading. To see why use `status --outdated -m`
Test Summary: | Pass  Total     Time
QA            |   26     26  3m12.4s
     Testing NonlinearSolve tests passed 

Core passed 21,327 assertions on each Julia version. Runic, staged git diff --check, and typos --config .typos.toml on the staged diff exited zero. No docstrings, docs entries, dependencies, or tolerances changed in these focused guard fixes. Integer brackets now reach the same floating-point iteration as floating brackets.

Not verified

GPU, downstream, prerelease, other platforms, and a complete downgrade matrix were not rerun locally. The SCC downgrade issue remains dependent on the open prerequisite fix; no tests were weakened or silenced. Full study wall-time benchmarks were not rerun.

Reviewer decisions

Chris retains the public API decision, acceptance of the two MIT dependencies in the AD layer, the value of adding ModABNewton, and how the original NewtonBisection branch should integrate master. No changes are pushed to that original author's branch.

Independent review

Fresh Codex CLI 0.163.0 / gpt-6.1-sol review at 9e93e479 returned CHANGES, medium risk (M1). It independently reproduced Core 21,327 on each version, sub QA 20/root QA 26, root Core LTS 1,572 passes with 91 existing broken assertions, both safeguard/allocation negative controls, 824 additional analytic assertions at this head, and 96 identical existing solution/trace comparisons.

The remaining compatibility blocker is SCC: the review runs the selected SciMLBase 3.57 configuration and observes 11 passes/1 failure at isconcretetype(eltype(scc_sol_debug.original)); the base with 3.46 passes 12/12. Passing root Core and QA does not clear this separate failure. The compatible prerequisite and a refreshed downgrade check are required.

The review also found that the dependent revisions fetched at its start (b91696f3 / 1eb61948) lacked the integer fix. That integration has since landed through normal merges at 695446ab and 377e506d, with AD 64 and combined 128 integer assertions passing locally. The fresh AD review independently tested both final heads, including Core 24,719 on each version at ModABNewton, and confirms the integer and cache-handoff defects are resolved; its only blocker is SCC. Review summary. This is not presented as a clean stack verdict. Its stale-body concern is addressed with the current output and count above.

A non-blocking shared follow-up remains: the sensitivity secant for a function capturing Dual state can sample outside a very narrow original bracket. The reviewer reproduces it for NewtonBisection and existing ModAB. It is tracked in the handoff and is not claimed fixed here.

Downstream CI classification

All three current heads fail ModelingToolkit InterfaceI at the same causal-connection equation orientation assertion, lib/ModelingToolkitBase/test/components.jl:446: multiplicity, AD, ModABNewton. Each actual log selects 207f39e2, the merge of SciML/ModelingToolkit.jl#5295. ModelingToolkit's own master CI fails the identical assertion without this stack. The older passing NonlinearSolve base integration uses an earlier downstream revision and Symbolics/SymbolicUtils versions. All comparison logs were read.

CI rechecked 2026-10-21 22:40 UTC: ModABNewton and its actual AD base each have 56 successful checks and the same single ModelingToolkit failure; multiplicity has 80 successes, the ModelingToolkit and SCC failures, and three skips. No checks remain pending. The latest ModelingToolkit master InterfaceI run at fe189a7e still fails the identical assertion (1,590 passed, one failed, three broken); its actual log was read. The routed CHANGES verdict remains blocked on the separate, unmerged SCC prerequisite. No new source commit or push is claimed for this audit. AD Ubuntu LTS Core reports success on the current AD head. No causal-connection test is changed. This upstream failure is separate from the SCC downgrade compatibility regression exposed by the raised SciMLBase minimum; neither is claimed fixed.

Links

Risk assessment

  • Risk: medium
  • Blast radius: new derivative-using scalar bracketing methods and interval Dual handling; public API requires Chris's decision.
  • Evidence: executed negative controls and local test output above.
  • Independent review: Codex CLI 0.163.0 / gpt-6.1-sol rated head 9e93e47 medium (M1), confidence not separately stated; CHANGES for SCC and the then-unpropagated integer fix. The latter now has normal merges and passing regressions; the fresh AD review independently confirms the integration, and the dedicated top review also confirms final numerical fixes and reports only SCC as a blocker. Review summary.
  • Merge: blocked on the compatible SCC prerequisite and refreshed downgrade check; Chris retains the public API decision.

🤖 Posted by an AI agent — harness: Codex CLI 0.163.0 · model: gpt-6.1-sol
Conversation: local job 1377-codex (no session URL)

albertomercurio and others added 7 commits October 1, 2026 22:15
NewtonBisection is Newton's method safeguarded by bisection, for interval
problems that give the derivative of `f` as the `jac` of their
IntervalNonlinearFunction. It bisects when a Newton step leaves the
bracket or does not shrink fast enough (the rule of `rtsafe`). Once the
Newton step is within the tolerance, it evaluates just beyond the Newton
point on the other side of the root, doubling the distance until the sign
changes, so that both ends converge and a zero tolerance ends with
adjacent floats. With a cheap derivative it needs fewer evaluations of `f`
than ModAB, for example when locating the events of continuous callbacks.

The ForwardDiff extension handles NewtonBisection like the other methods,
and keeps the derivative when it strips Duals from the endpoints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It has the `jac` field that NewtonBisection reads. Also bump to 1.13.0 for the new
exported method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It doubles at every step of the end game, and as an `Int` it could overflow and change
sign after 63 doublings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…osed

NewtonBisection now evaluates the derivative at every point right after `f`, also at the
two ends of the bracket, so a cache of the last point lets them share work. It also skips
the derivative at the last point, which no step uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It evaluates the derivative about as often as `f`, so it needs a derivative that costs
a small fraction of `f`, as when `f` is an integral and the derivative its integrand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test gives all methods the derivative, which only NewtonBisection uses, and checks it
is also evaluated inside the bracket.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Near a root of multiplicity m Newton's method converges linearly: each
step shrinks by (m - 1)/m, which rtsafe's acceptance rule (step at most
half the step before the previous one) lets through for m >= 3. So on
(u - 1)^3 over (0, 3) at abstol = 0 NewtonBisection took 88 evaluations
of f, against 55 for ModAB.

When two successive Newton steps from the same end of the bracket shrink
by a ratio rho > 1/2, the step is now multiplied by m = round(1/(1 - rho))
(at most 16), if that point stays inside the bracket. Evaluations of f at
abstol = 0: 4 on (u - 1)^3, 5 on (u - 1)^5, 12 on sin(u - pi/4)^3, 15 on
(exp(u) - 2)^3 (before: 88, 102, 89, 88). On the nine standard functions
the total goes from 90 to 91.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Agent-Harness: Claude Code cloud
Agent-Model: withheld (the harness forbids model identifiers in commits)
Agent-Session: https://claude.ai/code/session_01HJLdhiuK5ih88KyZciWTEH

Copy link
Copy Markdown
Member Author

(AI-generated, Claude Code cloud session: https://claude.ai/code/session_01HJLdhiuK5ih88KyZciWTEH)

The red Downgrade / Downgrade Tests - Core check is not from this PR:


Generated by Claude Code

Copy link
Copy Markdown
Member Author

(AI-generated, Claude Code cloud session: https://claude.ai/code/session_01HJLdhiuK5ih88KyZciWTEH)

The red downgrade-sublibraries / test (lib/SCCNonlinearSolve) check is not from this PR either:

  • The failure: lib/SCCNonlinearSolve/test/core_tests__item4.jl:100, isconcretetype(eltype(scc_sol_debug.original)) is false. It fails the same way on Add NewtonBisection, a bracketing Newton method that uses the derivative #1330's head 73f9b745, and this PR does not touch SCCNonlinearSolve.
  • The cause is SciMLBase: I ran the test locally from master's code (dad173fa). It passes with SciMLBase 3.56.0 and fails with 3.58.0, so it comes from a SciMLBase change in 3.57+.
  • Why only this PR's CI shows it: master's downgrade job resolves SciMLBase 3.46 and passes, and master hasn't run the regular SCCNonlinearSolve tests against the newest SciMLBase recently. Add NewtonBisection, a bracketing Newton method that uses the derivative #1330 raises BracketingNonlinearSolve's SciMLBase floor to 3.57, so the downgrade job here resolves 3.57 and hits it.
  • Fix: none exists yet. I'm narrowing it to the SciMLBase commit and will link a fix here.

Generated by Claude Code

Copy link
Copy Markdown
Member Author

Follow-up on the downgrade-sublibraries / test (lib/SCCNonlinearSolve) failure (core_tests__item4.jl:100):

The same failure shows on #1376 and #1377 for the same reason.

AI-generated (Claude Code cloud harness, on behalf of @ChrisRackauckas). Session: https://claude.ai/code/session_01HJLdhiuK5ih88KyZciWTEH


Generated by Claude Code

…ltiplicity

Picks up #1347 (PolyAlgorithm solve! retcode type assert for Julia 1.10
inference), fixing the lts Tests-Core and Downgrade-Tests-Core failures.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Devin <noreply@cognition.ai>
Agent-Harness: Devin CLI 3000.11.92
Agent-Model: swe-2-max
Agent-Session: local Devin CLI session oss-fix/SciML-NonlinearSolve.jl-1375
@ChrisRackauckas

Copy link
Copy Markdown
Member Author

🤖 Automated comment from an AI agent running as @ChrisRackauckas — not written or reviewed by Chris.

Merged master (2275450e) into this branch as fc902b05 — a clean merge with no conflicts and no changes to this PR's own diff (still +330/−12, all inside lib/BracketingNonlinearSolve plus the docs entries).

What was wrong

Three red checks, two upstream causes, none in this PR's code:

  1. tests / Core (julia lts, ubuntu/macos) and Downgrade / Downgrade Tests - Core: solve!() inference errored at test/Core/core_tests__item3.jl:27 on Julia 1.10 — solve! inferred a partial UnionAll NonlinearSolution{...} missing the RC parameter that SciMLBase 3.57 added. Fixed on master by Polyalgorithm solve!: assert retcode type for Julia 1.10 inference #1347 (dad173fa, the retcode::ReturnCode.T assert in lib/NonlinearSolveBase/src/solve.jl); this branch simply hadn't picked it up. The merge resolves these checks.

  2. downgrade-sublibraries / test (lib/SCCNonlinearSolve): isconcretetype(eltype(scc_sol_debug.original)) fails at lib/SCCNonlinearSolve/test/core_tests__item4.jl:100. That job installed SciMLBase 3.57.0 — the floor raised by Add NewtonBisection, a bracketing Newton method that uses the derivative #1330's BracketingNonlinearSolve compat bump propagates through the dev'd root package. Under SciMLBase ≥ 3.57 NonlinearSolution declares 11 type parameters, so the 10-parameter spelling at lib/SCCNonlinearSolve/src/SCCNonlinearSolve.jl:139 is a UnionAll and isconcretetype returns false. That spelled type is identical on master; master's downgrade job is green only because it resolves SciMLBase 3.46 (confirmed on run 38983068269 at dad173fa). The fix is the still-open Keep spelled NonlinearSolution element type concrete across SciMLBase layouts #1332 — deliberately not ported here, to keep this PR scoped to NewtonBisection and to avoid colliding with Keep spelled NonlinearSolution element type concrete across SciMLBase layouts #1332 when it lands. This check will remain red until Keep spelled NonlinearSolution element type concrete across SciMLBase layouts #1332 (or equivalent) merges; it fails identically on NewtonBisection: opt-in automatic differentiation #1376 and Add ModABNewton: ModAB's bisection phase, then NewtonBisection's steps #1377.

Validation (run on the pushed merge fc902b05)

  • GROUP=Core JULIA_NUM_THREADS=4 JULIA_NUM_PRECOMPILE_TASKS=4 julia +lts test/runtests.jl on Julia 1.10.12, in an env that Pkg.develops all lib/* dependencies plus the root (SciMLBase 3.58.0 resolved — newer than the 3.57.0 that failed in CI): all 82 testsets pass, exit 0, including PolyAlgorithm Type Inference 16/16 — the exact testset that errored in CI.
  • Reproduction check: with the Polyalgorithm solve!: assert retcode type for Julia 1.10 inference #1347 hunk reverted (retcode::ReturnCode.T → retcode), the same env reproduces the CI failure verbatim (solve!() inference: Error During Test at core_tests__item3.jl:27, inferred partial UnionAll missing SciMLBase.ReturnCode.T); restoring the line makes it pass 8/8 again. Working tree left clean.
  • cd lib/BracketingNonlinearSolve && NONLINEARSOLVE_TEST_GROUP=Core julia +1.12 --project=. -e 'using Pkg; Pkg.test()' on Julia 1.12.7: all pass — NewtonBisection 2057/2057, NewtonBisection at multiple roots 24/24.
  • GROUP=QA julia +1.12 --project=. -e 'using Pkg; Pkg.test()': 28/28 pass.
  • julia +release --project=@runic -m Runic --inplace on the 7 changed .jl files: no changes needed.
  • typos over the full diff: clean (one false positive on a git blob hash).
  • Confirmed under SciMLBase 3.58.0: NonlinearSolution declares 11 parameters; the 10-parameter spelling used in SCCNonlinearSolve.jl:139 is a UnionAll — a latent incompatibility in code this PR does not touch.

Skipped

  • Docs build not run locally; the merge only pulled already-merged master doc changes, and the docstring change here built fine on the previous CI.
  • Julia pre and the Adjoint group not run locally.

🤖 Posted by an AI agent — harness: Devin CLI 3000.11.92 · model: swe-2-max
Conversation: local Devin CLI session oss-fix/SciML-NonlinearSolve.jl-1375

ChrisRackauckas and others added 3 commits October 20, 2026 23:08
…iduals

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Codex <noreply@openai.com>
Agent-Harness: Codex CLI 0.163.0
Agent-Model: gpt-6.1-sol
Agent-Session: local job 1377-codex (no session URL)
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Codex <noreply@openai.com>
Agent-Harness: Codex CLI 0.163.0
Agent-Model: gpt-6.1-sol
Agent-Session: local job 1377-codex (no session URL)
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Codex <noreply@openai.com>
Agent-Harness: Codex CLI 0.163.0
Agent-Model: gpt-6.1-sol
Agent-Session: local job 1377-codex (no session URL)

This branch has not been deployed

No deployments
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.

3 participants