Skip to content

Commit b853842

Browse files
Merge pull request #303 from avik-pal/ap/better_init
Improvements for Broyden and Klement
2 parents 1fbf7af + c4bde4d commit b853842

File tree

11 files changed

+482
-155
lines changed

11 files changed

+482
-155
lines changed

src/NonlinearSolve.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ function SciMLBase.reinit!(cache::AbstractNonlinearSolveCache{iip}, u0 = get_u(c
8181
get_u(cache), p, get_fu(cache), Val(iip))
8282
end
8383

84-
hasfield(typeof(cache), :uf) && (cache.uf.p = p)
84+
hasfield(typeof(cache), :uf) && cache.uf !== nothing && (cache.uf.p = p)
8585

8686
cache.abstol = abstol
8787
cache.reltol = reltol
@@ -117,6 +117,7 @@ function Base.show(io::IO, alg::AbstractNonlinearSolveAlgorithm)
117117
push!(modifiers, "linesearch = $(nameof(typeof(alg.linesearch)))()")
118118
end
119119
end
120+
append!(modifiers, __alg_print_modifiers(alg))
120121
if __getproperty(alg, Val(:radius_update_scheme)) !== nothing
121122
push!(modifiers, "radius_update_scheme = $(alg.radius_update_scheme)")
122123
end
@@ -125,6 +126,8 @@ function Base.show(io::IO, alg::AbstractNonlinearSolveAlgorithm)
125126
return nothing
126127
end
127128

129+
__alg_print_modifiers(_) = String[]
130+
128131
function SciMLBase.__solve(prob::Union{NonlinearProblem, NonlinearLeastSquaresProblem},
129132
alg::AbstractNonlinearSolveAlgorithm, args...; kwargs...)
130133
cache = init(prob, alg, args...; kwargs...)

src/broyden.jl

Lines changed: 146 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,78 @@
11
# Sadly `Broyden` is taken up by SimpleNonlinearSolve.jl
22
"""
3-
GeneralBroyden(; max_resets = 3, linesearch = nothing, reset_tolerance = nothing)
3+
GeneralBroyden(; max_resets = 100, linesearch = nothing, reset_tolerance = nothing,
4+
init_jacobian::Val = Val(:identity), autodiff = nothing, alpha = nothing)
45
56
An implementation of `Broyden` with resetting and line search.
67
78
## Arguments
89
9-
- `max_resets`: the maximum number of resets to perform. Defaults to `3`.
10+
- `max_resets`: the maximum number of resets to perform. Defaults to `100`.
11+
1012
- `reset_tolerance`: the tolerance for the reset check. Defaults to
1113
`sqrt(eps(real(eltype(u))))`.
1214
- `linesearch`: the line search algorithm to use. Defaults to [`LineSearch()`](@ref),
1315
which means that no line search is performed. Algorithms from `LineSearches.jl` can be
1416
used here directly, and they will be converted to the correct `LineSearch`. It is
1517
recommended to use [LiFukushimaLineSearch](@ref) -- a derivative free linesearch
1618
specifically designed for Broyden's method.
19+
- `alpha`: If `init_jacobian` is set to `Val(:identity)`, then the initial Jacobian
20+
inverse is set to be `(αI)⁻¹`. Defaults to `nothing` which implies
21+
`α = max(norm(u), 1) / (2 * norm(fu))`.
22+
- `init_jacobian`: the method to use for initializing the jacobian. Defaults to
23+
`Val(:identity)`. Choices include:
24+
25+
+ `Val(:identity)`: Identity Matrix.
26+
+ `Val(:true_jacobian)`: True Jacobian. This is a good choice for differentiable
27+
problems.
28+
- `autodiff`: determines the backend used for the Jacobian. Note that this argument is
29+
ignored if an analytical Jacobian is passed, as that will be used instead. Defaults to
30+
`nothing` which means that a default is selected according to the problem specification!
31+
Valid choices are types from ADTypes.jl. (Used if `init_jacobian = Val(:true_jacobian)`)
32+
- `update_rule`: Update Rule for the Jacobian. Choices are:
33+
34+
+ `Val(:good_broyden)`: Good Broyden's Update Rule
35+
+ `Val(:bad_broyden)`: Bad Broyden's Update Rule
36+
+ `Val(:diagonal)`: Only update the diagonal of the Jacobian. This algorithm may be
37+
useful for specific problems, but whether it will work may depend strongly on the
38+
problem.
1739
"""
18-
@concrete struct GeneralBroyden <: AbstractNewtonAlgorithm{false, Nothing}
40+
@concrete struct GeneralBroyden{IJ, UR, CJ, AD} <: AbstractNewtonAlgorithm{CJ, AD}
41+
ad::AD
1942
max_resets::Int
2043
reset_tolerance
2144
linesearch
45+
alpha
46+
end
47+
48+
function __alg_print_modifiers(alg::GeneralBroyden{IJ, UR}) where {IJ, UR}
49+
modifiers = String[]
50+
IJ !== :identity && push!(modifiers, "init_jacobian = Val(:$(IJ))")
51+
UR !== :good_broyden && push!(modifiers, "update_rule = Val(:$(UR))")
52+
alg.alpha !== nothing && push!(modifiers, "alpha = $(alg.alpha)")
53+
return modifiers
54+
end
55+
56+
function set_ad(alg::GeneralBroyden{IJ, UR, CJ}, ad) where {IJ, UR, CJ}
57+
return GeneralBroyden{IJ, UR, CJ}(ad, alg.max_resets, alg.reset_tolerance,
58+
alg.linesearch, alg.alpha)
2259
end
2360

24-
function GeneralBroyden(; max_resets = 3, linesearch = nothing,
25-
reset_tolerance = nothing)
61+
function GeneralBroyden(; max_resets = 100, linesearch = nothing, reset_tolerance = nothing,
62+
init_jacobian::Val = Val(:identity), autodiff = nothing, alpha = nothing,
63+
update_rule = Val(:good_broyden))
64+
UR = _unwrap_val(update_rule)
65+
@assert UR (:good_broyden, :bad_broyden, :diagonal)
66+
IJ = _unwrap_val(init_jacobian)
67+
@assert IJ (:identity, :true_jacobian)
2668
linesearch = linesearch isa LineSearch ? linesearch : LineSearch(; method = linesearch)
27-
return GeneralBroyden(max_resets, reset_tolerance, linesearch)
69+
CJ = IJ === :true_jacobian
70+
return GeneralBroyden{IJ, UR, CJ}(autodiff, max_resets, reset_tolerance, linesearch,
71+
alpha)
2872
end
2973

30-
@concrete mutable struct GeneralBroydenCache{iip} <: AbstractNonlinearSolveCache{iip}
74+
@concrete mutable struct GeneralBroydenCache{iip, IJ, UR} <:
75+
AbstractNonlinearSolveCache{iip}
3176
f
3277
alg
3378
u
@@ -37,8 +82,12 @@ end
3782
fu_cache
3883
dfu
3984
p
85+
uf
4086
J⁻¹
87+
J⁻¹_cache
4188
J⁻¹dfu
89+
inv_alpha
90+
alpha_initial
4291
force_stop::Bool
4392
resets::Int
4493
max_resets::Int
@@ -49,46 +98,85 @@ end
4998
reltol
5099
reset_tolerance
51100
reset_check
101+
jac_cache
52102
prob
53103
stats::NLStats
54104
ls_cache
55105
tc_cache
56106
trace
57107
end
58108

59-
function SciMLBase.__init(prob::NonlinearProblem{uType, iip}, alg::GeneralBroyden, args...;
60-
alias_u0 = false, maxiters = 1000, abstol = nothing, reltol = nothing,
109+
function SciMLBase.__init(prob::NonlinearProblem{uType, iip}, alg_::GeneralBroyden{IJ, UR},
110+
args...; alias_u0 = false, maxiters = 1000, abstol = nothing, reltol = nothing,
61111
termination_condition = nothing, internalnorm::F = DEFAULT_NORM,
62-
kwargs...) where {uType, iip, F}
112+
kwargs...) where {uType, iip, F, IJ, UR}
63113
@unpack f, u0, p = prob
64114
u = __maybe_unaliased(u0, alias_u0)
65115
fu = evaluate_f(prob, u)
66116
@bb du = copy(u)
67-
J⁻¹ = __init_identity_jacobian(u, fu)
117+
118+
inv_alpha = __initial_inv_alpha(alg_.alpha, u, fu, internalnorm)
119+
120+
if IJ === :true_jacobian
121+
alg = get_concrete_algorithm(alg_, prob)
122+
uf, _, J, fu_cache, jac_cache, du = jacobian_caches(alg, f, u, p, Val(iip);
123+
lininit = Val(false))
124+
if UR === :diagonal
125+
J⁻¹_cache = J
126+
J⁻¹ = __diag(J)
127+
else
128+
J⁻¹_cache = nothing
129+
J⁻¹ = J
130+
end
131+
elseif IJ === :identity
132+
alg = alg_
133+
@bb du = similar(u)
134+
uf, fu_cache, jac_cache, J⁻¹_cache = nothing, nothing, nothing, nothing
135+
if UR === :diagonal
136+
J⁻¹ = one.(fu)
137+
@bb J⁻¹ .*= inv_alpha
138+
else
139+
J⁻¹ = __init_identity_jacobian(u, fu, inv_alpha)
140+
end
141+
end
142+
68143
reset_tolerance = alg.reset_tolerance === nothing ? sqrt(eps(real(eltype(u)))) :
69144
alg.reset_tolerance
70145
reset_check = x -> abs(x) reset_tolerance
71146

72147
@bb u_cache = copy(u)
73-
@bb fu_cache = copy(fu)
74-
@bb dfu = similar(fu)
148+
@bb dfu = copy(fu)
75149
@bb J⁻¹dfu = similar(u)
76150

77151
abstol, reltol, tc_cache = init_termination_cache(abstol, reltol, fu, u,
78152
termination_condition)
79-
trace = init_nonlinearsolve_trace(alg, u, fu, J⁻¹, du; uses_jac_inverse = Val(true),
80-
kwargs...)
153+
trace = init_nonlinearsolve_trace(alg, u, fu, ApplyArray(__zero, J⁻¹), du;
154+
uses_jac_inverse = Val(true), kwargs...)
81155

82-
return GeneralBroydenCache{iip}(f, alg, u, u_cache, du, fu, fu_cache, dfu, p,
83-
J⁻¹, J⁻¹dfu, false, 0, alg.max_resets, maxiters, internalnorm, ReturnCode.Default,
84-
abstol, reltol, reset_tolerance, reset_check, prob, NLStats(1, 0, 0, 0, 0),
156+
return GeneralBroydenCache{iip, IJ, UR}(f, alg, u, u_cache, du, fu, fu_cache, dfu, p,
157+
uf, J⁻¹, J⁻¹_cache, J⁻¹dfu, inv_alpha, alg.alpha, false, 0, alg.max_resets,
158+
maxiters, internalnorm, ReturnCode.Default, abstol, reltol, reset_tolerance,
159+
reset_check, jac_cache, prob, NLStats(1, 0, 0, 0, 0),
85160
init_linesearch_cache(alg.linesearch, f, u, p, fu, Val(iip)), tc_cache, trace)
86161
end
87162

88-
function perform_step!(cache::GeneralBroydenCache{iip}) where {iip}
163+
function perform_step!(cache::GeneralBroydenCache{iip, IJ, UR}) where {iip, IJ, UR}
89164
T = eltype(cache.u)
90165

91-
@bb cache.du = cache.J⁻¹ × vec(cache.fu)
166+
if IJ === :true_jacobian && cache.stats.nsteps == 0
167+
if UR === :diagonal
168+
cache.J⁻¹_cache = __safe_inv(jacobian!!(cache.J⁻¹_cache, cache))
169+
cache.J⁻¹ = __get_diagonal!!(cache.J⁻¹, cache.J⁻¹_cache)
170+
else
171+
cache.J⁻¹ = __safe_inv(jacobian!!(cache.J⁻¹, cache))
172+
end
173+
end
174+
175+
if UR === :diagonal
176+
@bb @. cache.du = cache.J⁻¹ * cache.fu
177+
else
178+
@bb cache.du = cache.J⁻¹ × vec(cache.fu)
179+
end
92180
α = perform_linesearch!(cache.ls_cache, cache.u, cache.du)
93181
@bb axpy!(-α, cache.du, cache.u)
94182

@@ -100,33 +188,62 @@ function perform_step!(cache::GeneralBroydenCache{iip}) where {iip}
100188
cache.force_stop && return nothing
101189

102190
# Update the inverse jacobian
103-
@bb @. cache.dfu = cache.fu - cache.fu_cache
191+
@bb @. cache.dfu = cache.fu - cache.dfu
104192

105193
if all(cache.reset_check, cache.du) || all(cache.reset_check, cache.dfu)
106194
if cache.resets cache.max_resets
107195
cache.retcode = ReturnCode.ConvergenceFailure
108196
cache.force_stop = true
109197
return nothing
110198
end
111-
cache.J⁻¹ = __reinit_identity_jacobian!!(cache.J⁻¹)
199+
if IJ === :true_jacobian
200+
if UR === :diagonal
201+
cache.J⁻¹_cache = __safe_inv(jacobian!!(cache.J⁻¹_cache, cache))
202+
cache.J⁻¹ = __get_diagonal!!(cache.J⁻¹, cache.J⁻¹_cache)
203+
else
204+
cache.J⁻¹ = __safe_inv(jacobian!!(cache.J⁻¹, cache))
205+
end
206+
else
207+
cache.inv_alpha = __initial_inv_alpha(cache.inv_alpha, cache.alpha_initial,
208+
cache.u, cache.fu, cache.internalnorm)
209+
cache.J⁻¹ = __reinit_identity_jacobian!!(cache.J⁻¹, cache.inv_alpha)
210+
end
112211
cache.resets += 1
113212
else
114213
@bb cache.du .*= -1
115-
@bb cache.J⁻¹dfu = cache.J⁻¹ × vec(cache.dfu)
116-
@bb cache.u_cache = transpose(cache.J⁻¹) × vec(cache.du)
117-
denom = dot(cache.du, cache.J⁻¹dfu)
118-
@bb @. cache.du = (cache.du - cache.J⁻¹dfu) / ifelse(iszero(denom), T(1e-5), denom)
119-
@bb cache.J⁻¹ += vec(cache.du) × transpose(_vec(cache.u_cache))
214+
if UR === :good_broyden
215+
@bb cache.J⁻¹dfu = cache.J⁻¹ × vec(cache.dfu)
216+
@bb cache.u_cache = transpose(cache.J⁻¹) × vec(cache.du)
217+
denom = dot(cache.du, cache.J⁻¹dfu)
218+
@bb @. cache.du = (cache.du - cache.J⁻¹dfu) /
219+
ifelse(iszero(denom), T(1e-5), denom)
220+
@bb cache.J⁻¹ += vec(cache.du) × transpose(_vec(cache.u_cache))
221+
elseif UR === :bad_broyden
222+
@bb cache.J⁻¹dfu = cache.J⁻¹ × vec(cache.dfu)
223+
dfu_norm = cache.internalnorm(cache.dfu)^2
224+
@bb @. cache.du = (cache.du - cache.J⁻¹dfu) /
225+
ifelse(iszero(dfu_norm), T(1e-5), dfu_norm)
226+
@bb cache.J⁻¹ += vec(cache.du) × transpose(_vec(cache.dfu))
227+
elseif UR === :diagonal
228+
@bb @. cache.J⁻¹dfu = cache.du * cache.J⁻¹ * cache.dfu
229+
denom = sum(cache.J⁻¹dfu)
230+
@bb @. cache.J⁻¹ += (cache.du - cache.J⁻¹ * cache.dfu) * cache.du * cache.J⁻¹ /
231+
ifelse(iszero(denom), T(1e-5), denom)
232+
else
233+
error("update_rule = Val(:$(UR)) is not implemented for Broyden.")
234+
end
120235
end
121236

122-
@bb copyto!(cache.fu_cache, cache.fu)
237+
@bb copyto!(cache.dfu, cache.fu)
123238
@bb copyto!(cache.u_cache, cache.u)
124239

125240
return nothing
126241
end
127242

128243
function __reinit_internal!(cache::GeneralBroydenCache; kwargs...)
129-
cache.J⁻¹ = __reinit_identity_jacobian!!(cache.J⁻¹)
244+
cache.inv_alpha = __initial_inv_alpha(cache.inv_alpha, cache.alpha_initial, cache.u,
245+
cache.fu, cache.internalnorm)
246+
cache.J⁻¹ = __reinit_identity_jacobian!!(cache.J⁻¹, cache.inv_alpha)
130247
cache.resets = 0
131248
return nothing
132249
end

src/default.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,9 @@ function FastShortcutNonlinearPolyalg(; concrete_jac = nothing, linsolve = nothi
248248
TrustRegion(; concrete_jac, linsolve, precs,
249249
radius_update_scheme = RadiusUpdateSchemes.Bastin, adkwargs...))
250250
else
251-
algs = (GeneralKlement(; linsolve, precs),
252-
GeneralBroyden(),
251+
algs = (GeneralBroyden(),
252+
GeneralBroyden(; init_jacobian = Val(:true_jacobian)),
253+
GeneralKlement(; linsolve, precs),
253254
NewtonRaphson(; concrete_jac, linsolve, precs, adkwargs...),
254255
NewtonRaphson(; concrete_jac, linsolve, precs, linesearch = BackTracking(),
255256
adkwargs...),

src/jacobian.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ function jacobian!!(::Number, cache)
5858
cache.stats.njacs += 1
5959
return last(value_derivative(cache.uf, cache.u))
6060
end
61+
6162
# Build Jacobian Caches
6263
function jacobian_caches(alg::AbstractNonlinearSolveAlgorithm, f::F, u, p, ::Val{iip};
6364
linsolve_kwargs = (;), lininit::Val{linsolve_init} = Val(true),

0 commit comments

Comments
 (0)