Skip to content

Commit 21df2f1

Browse files
Fix SCC residuals transfer for linear problems
Previously, when using the SCC algorithm with linear problems, residuals were not computed and passed as nothing, resulting in a vector with nothing values in the solution object. This fix checks if sol.resid is nothing (which it often is for LinearSolution) and computes residuals using the formula `resid = A*u - b` when needed, then properly transfers them to the solution object. Fixes #706 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 146c8d5 commit 21df2f1

File tree

3 files changed

+83
-18
lines changed

3 files changed

+83
-18
lines changed

Project.toml

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ NonlinearSolveSpectralMethods = "26075421-4e9a-44e1-8bd1-420ed7ad02b2"
2323
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
2424
Preferences = "21216c6a-2e73-6563-6e65-726566657250"
2525
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
26+
SCCNonlinearSolve = "9dfe8606-65a1-4bb3-9748-cb89d1561431"
2627
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
2728
SimpleNonlinearSolve = "727e6d20-b764-4bd8-a329-72de5adea6c7"
2829
StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"
@@ -43,23 +44,13 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
4344
SpeedMapping = "f1835b91-879b-4a3f-a438-e4baacf14412"
4445
Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"
4546

46-
[sources.BracketingNonlinearSolve]
47-
path = "lib/BracketingNonlinearSolve"
48-
49-
[sources.NonlinearSolveBase]
50-
path = "lib/NonlinearSolveBase"
51-
52-
[sources.NonlinearSolveFirstOrder]
53-
path = "lib/NonlinearSolveFirstOrder"
54-
55-
[sources.NonlinearSolveQuasiNewton]
56-
path = "lib/NonlinearSolveQuasiNewton"
57-
58-
[sources.NonlinearSolveSpectralMethods]
59-
path = "lib/NonlinearSolveSpectralMethods"
60-
61-
[sources.SimpleNonlinearSolve]
62-
path = "lib/SimpleNonlinearSolve"
47+
[sources]
48+
BracketingNonlinearSolve = {path = "lib/BracketingNonlinearSolve"}
49+
NonlinearSolveBase = {path = "lib/NonlinearSolveBase"}
50+
NonlinearSolveFirstOrder = {path = "lib/NonlinearSolveFirstOrder"}
51+
NonlinearSolveQuasiNewton = {path = "lib/NonlinearSolveQuasiNewton"}
52+
NonlinearSolveSpectralMethods = {path = "lib/NonlinearSolveSpectralMethods"}
53+
SimpleNonlinearSolve = {path = "lib/SimpleNonlinearSolve"}
6354

6455
[extensions]
6556
NonlinearSolveFastLevenbergMarquardtExt = "FastLevenbergMarquardt"

lib/SCCNonlinearSolve/src/SCCNonlinearSolve.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ function iteratively_build_sols(alg, sols, (prob, explicitfun), args...; kwargs.
4747
# `remake` to recalculate `A` and `b` based on updated parameters from `explicitfun`.
4848
# Pass `A` and `b` to avoid unnecessarily copying them.
4949
sol = SciMLBase.solve(SciMLBase.remake(prob; A, b), alg.linalg; kwargs...)
50+
# LinearSolution may have resid=nothing, so compute it: resid = A*u - b
51+
resid = isnothing(sol.resid) ? A * sol.u - b : sol.resid
5052
SciMLBase.build_linear_solution(
51-
alg.linalg, sol.u, nothing, nothing, retcode = sol.retcode)
53+
alg.linalg, sol.u, resid, nothing, retcode = sol.retcode)
5254
else
5355
sol = SciMLBase.solve(prob, alg.nlalg; kwargs...)
5456
SciMLBase.build_solution(

lib/SCCNonlinearSolve/test/core_tests.jl

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,75 @@ end
7575
scc_sol_default=solve(sccprob)
7676
@test sol manualscc scc_sol_default
7777
end
78+
79+
@testitem "SCC Residuals Transfer" setup=[CoreRootfindTesting] tags=[:core] begin
80+
using NonlinearSolveFirstOrder
81+
using LinearAlgebra
82+
83+
# Create a simple SCC problem with both nonlinear and linear components
84+
# to test that residuals are properly computed and transferred
85+
86+
# Nonlinear problem
87+
function f1(du, u, p)
88+
du[1] = u[1]^2 - 2.0
89+
du[2] = u[2] - u[1]
90+
end
91+
explicitfun1(p, sols) = nothing
92+
prob1 = NonlinearProblem(
93+
NonlinearFunction{true, SciMLBase.NoSpecialize}(f1), [1.0, 1.0], nothing)
94+
95+
# Linear problem
96+
A2 = [1.0 0.5; 0.5 1.0]
97+
b2 = [1.0, 2.0]
98+
prob2 = LinearProblem(A2, b2)
99+
explicitfun2(p, sols) = nothing
100+
101+
# Another nonlinear problem
102+
function f3(du, u, p)
103+
du[1] = u[1] + u[2] - 3.0
104+
du[2] = u[1] * u[2] - 2.0
105+
end
106+
explicitfun3(p, sols) = nothing
107+
prob3 = NonlinearProblem(
108+
NonlinearFunction{true, SciMLBase.NoSpecialize}(f3), [1.0, 2.0], nothing)
109+
110+
# Create SCC problem
111+
sccprob = SciMLBase.SCCNonlinearProblem((prob1, prob2, prob3),
112+
SciMLBase.Void{Any}.([explicitfun1, explicitfun2, explicitfun3]))
113+
114+
# Solve with SCCAlg
115+
using SCCNonlinearSolve
116+
scc_alg = SCCNonlinearSolve.SCCAlg(nlalg = NewtonRaphson(), linalg = nothing)
117+
scc_sol = solve(sccprob, scc_alg)
118+
119+
# Test that solution was successful
120+
@test SciMLBase.successful_retcode(scc_sol)
121+
122+
# Test that residuals are not nothing
123+
@test scc_sol.resid !== nothing
124+
@test !any(isnothing, scc_sol.resid)
125+
126+
# Test that residuals have the correct length
127+
expected_length = length(prob1.u0) + length(prob2.b) + length(prob3.u0)
128+
@test length(scc_sol.resid) == expected_length
129+
130+
# Test that residuals are small (near zero for converged solution)
131+
@test norm(scc_sol.resid) < 1e-10
132+
133+
# Manually compute residuals to verify correctness
134+
u1 = scc_sol.u[1:2]
135+
u2 = scc_sol.u[3:4]
136+
u3 = scc_sol.u[5:6]
137+
138+
# Compute residuals for each component
139+
resid1 = zeros(2)
140+
f1(resid1, u1, nothing)
141+
142+
resid2 = A2 * u2 - b2
143+
144+
resid3 = zeros(2)
145+
f3(resid3, u3, nothing)
146+
147+
expected_resid = vcat(resid1, resid2, resid3)
148+
@test scc_sol.resid expected_resid atol=1e-10
149+
end

0 commit comments

Comments
 (0)