Skip to content

Commit 44a3690

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 computes residuals for linear problems as `resid = A*u - b` and 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 44a3690

File tree

2 files changed

+75
-1
lines changed

2 files changed

+75
-1
lines changed

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+
# Compute residuals for linear problem: resid = A*u - b
51+
resid = A * sol.u - b
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)