Skip to content

Commit c223b21

Browse files
authored
fix oop dampen_jacobian!! for scalar damping (#1068)
1 parent d156439 commit c223b21

3 files changed

Lines changed: 60 additions & 1 deletion

File tree

lib/NonlinearSolveBase/src/descent/damped_newton.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ dampen_jacobian!!(::Any, J::Union{AbstractSciMLOperator, Number}, D) = J + D
344344

345345
# Scalar damping (identity-style `(1/α) I` damping) only touches the diagonal of `J`.
346346
function dampen_jacobian!!(J_cache, J::AbstractMatrix, D::Number)
347-
ArrayInterface.can_setindex(J_cache) || return J .+ D
347+
ArrayInterface.can_setindex(J_cache) || return J + D * LinearAlgebra.I
348348
J_cache !== J && copyto!(J_cache, J)
349349
if ArrayInterface.fast_scalar_indexing(J_cache)
350350
@simd ivdep for i in axes(J_cache, 1)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using NonlinearSolveBase
2+
using NonlinearSolveBase: dampen_jacobian!!
3+
using ArrayInterface
4+
using LinearAlgebra
5+
using StaticArrays
6+
using Test
7+
8+
# `dampen_jacobian!!` has a mutable path (`can_setindex(J_cache)`, damping written in place) and
9+
# an out-of-place fallback for Jacobians that cannot `setindex!` — `SMatrix`, and GPU sparse
10+
# matrices, which are mutable only through `nonzeros`. The two must agree: damping is defined by
11+
# the math, not by how the Jacobian happens to be stored. The scalar-`D` fallback used to read
12+
# `J .+ D`, which adds `D` to *every* entry rather than only the diagonal, so an `SMatrix`
13+
# Jacobian was silently damped wrong (a convergence bug, not a wrong root: damping does not move
14+
# the fixed point).
15+
16+
J = [1.0 2.0; 3.0 4.0]
17+
Jstatic = SMatrix{2, 2}(J)
18+
19+
@testset "scalar damping touches only the diagonal" begin
20+
D = 10.0
21+
expected = J + D * I # [11 2; 3 14]
22+
23+
@test dampen_jacobian!!(copy(J), J, D) == expected
24+
@test !ArrayInterface.can_setindex(Jstatic) # ensure we exercise the fallback
25+
@test Array(dampen_jacobian!!(Jstatic, Jstatic, D)) == expected
26+
27+
# The off-diagonal entries must survive untouched.
28+
damped = dampen_jacobian!!(Jstatic, Jstatic, D)
29+
@test damped[1, 2] == J[1, 2]
30+
@test damped[2, 1] == J[2, 1]
31+
end
32+
33+
@testset "diagonal damping touches only the diagonal" begin
34+
D = Diagonal([10.0, 20.0])
35+
expected = J + D
36+
37+
@test dampen_jacobian!!(copy(J), J, D) == expected
38+
@test Array(dampen_jacobian!!(Jstatic, Jstatic, D)) == expected
39+
end
40+
41+
@testset "matrix damping adds the full matrix" begin
42+
D = [10.0 100.0; 200.0 20.0]
43+
expected = J + D
44+
45+
@test dampen_jacobian!!(copy(J), J, D) == expected
46+
@test Array(dampen_jacobian!!(Jstatic, Jstatic, D)) == expected
47+
end
48+
49+
@testset "mutable and immutable paths agree" begin
50+
# The invariant behind all of the above, stated directly.
51+
for D in (10.0, Diagonal([10.0, 20.0]), [10.0 100.0; 200.0 20.0])
52+
@test Array(dampen_jacobian!!(Jstatic, Jstatic, D)) ==
53+
dampen_jacobian!!(copy(J), J, D)
54+
end
55+
end

lib/NonlinearSolveBase/test/runtests.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ run_tests(;
140140
@test_throws ErrorException cache(nothing)
141141
end
142142

143+
@safetestset "dampen_jacobian!! touches only the diagonal" include(
144+
"dampen_jacobian.jl"
145+
)
146+
143147
return @safetestset "Dense LU refactorization allocations" include(
144148
"lu_refactorization_allocs.jl"
145149
)

0 commit comments

Comments
 (0)