Skip to content

Commit 3d85a52

Browse files
Merge pull request #214 from axla-io/al/dfsane
WIP: Add DFSane method
2 parents 7890cef + 0df8aaf commit 3d85a52

File tree

4 files changed

+536
-1
lines changed

4 files changed

+536
-1
lines changed

src/NonlinearSolve.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ include("raphson.jl")
6666
include("trustRegion.jl")
6767
include("levenberg.jl")
6868
include("gaussnewton.jl")
69+
include("dfsane.jl")
6970
include("jacobian.jl")
7071
include("ad.jl")
7172
include("default.jl")
@@ -94,7 +95,7 @@ end
9495

9596
export RadiusUpdateSchemes
9697

97-
export NewtonRaphson, TrustRegion, LevenbergMarquardt, GaussNewton
98+
export NewtonRaphson, TrustRegion, LevenbergMarquardt, DFSane, GaussNewton
9899
export LeastSquaresOptimJL, FastLevenbergMarquardtJL
99100
export RobustMultiNewton, FastShortcutNonlinearPolyalg
100101

src/dfsane.jl

Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
"""
2+
DFSane(; σ_min::Real = 1e-10, σ_max::Real = 1e10, σ_1::Real = 1.0,
3+
M::Int = 10, γ::Real = 1e-4, τ_min::Real = 0.1, τ_max::Real = 0.5,
4+
n_exp::Int = 2, η_strategy::Function = (fn_1, n, x_n, f_n) -> fn_1 / n^2,
5+
max_inner_iterations::Int = 1000)
6+
7+
A low-overhead and allocation-free implementation of the df-sane method for solving large-scale nonlinear
8+
systems of equations. For in depth information about all the parameters and the algorithm,
9+
see the paper: [W LaCruz, JM Martinez, and M Raydan (2006), Spectral residual mathod without
10+
gradient information for solving large-scale nonlinear systems of equations, Mathematics of
11+
Computation, 75, 1429-1448.](https://www.researchgate.net/publication/220576479_Spectral_Residual_Method_without_Gradient_Information_for_Solving_Large-Scale_Nonlinear_Systems_of_Equations)
12+
13+
See also the implementation in [SimpleNonlinearSolve.jl](https://github.com/SciML/SimpleNonlinearSolve.jl/blob/main/src/dfsane.jl)
14+
15+
### Keyword Arguments
16+
17+
- `σ_min`: the minimum value of the spectral coefficient `σₙ` which is related to the step
18+
size in the algorithm. Defaults to `1e-10`.
19+
- `σ_max`: the maximum value of the spectral coefficient `σₙ` which is related to the step
20+
size in the algorithm. Defaults to `1e10`.
21+
- `σ_1`: the initial value of the spectral coefficient `σₙ` which is related to the step
22+
size in the algorithm.. Defaults to `1.0`.
23+
- `M`: The monotonicity of the algorithm is determined by a this positive integer.
24+
A value of 1 for `M` would result in strict monotonicity in the decrease of the L2-norm
25+
of the function `f`. However, higher values allow for more flexibility in this reduction.
26+
Despite this, the algorithm still ensures global convergence through the use of a
27+
non-monotone line-search algorithm that adheres to the Grippo-Lampariello-Lucidi
28+
condition. Values in the range of 5 to 20 are usually sufficient, but some cases may call
29+
for a higher value of `M`. The default setting is 10.
30+
- `γ`: a parameter that influences if a proposed step will be accepted. Higher value of `γ`
31+
will make the algorithm more restrictive in accepting steps. Defaults to `1e-4`.
32+
- `τ_min`: if a step is rejected the new step size will get multiplied by factor, and this
33+
parameter is the minimum value of that factor. Defaults to `0.1`.
34+
- `τ_max`: if a step is rejected the new step size will get multiplied by factor, and this
35+
parameter is the maximum value of that factor. Defaults to `0.5`.
36+
- `n_exp`: the exponent of the loss, i.e. ``f_n=||F(x_n)||^{n_exp}``. The paper uses
37+
`n_exp ∈ {1,2}`. Defaults to `2`.
38+
- `η_strategy`: function to determine the parameter `η`, which enables growth
39+
of ``||f_n||^2``. Called as ``η = η_strategy(fn_1, n, x_n, f_n)`` with `fn_1` initialized as
40+
``fn_1=||f(x_1)||^{n_exp}``, `n` is the iteration number, `x_n` is the current `x`-value and
41+
`f_n` the current residual. Should satisfy ``η > 0`` and ``∑ₖ ηₖ < ∞``. Defaults to
42+
``fn_1 / n^2``.
43+
- `max_inner_iterations`: the maximum number of iterations allowed for the inner loop of the
44+
algorithm. Defaults to `1000`.
45+
"""
46+
47+
struct DFSane{T, F} <: AbstractNonlinearSolveAlgorithm
48+
σ_min::T
49+
σ_max::T
50+
σ_1::T
51+
M::Int
52+
γ::T
53+
τ_min::T
54+
τ_max::T
55+
n_exp::Int
56+
η_strategy::F
57+
max_inner_iterations::Int
58+
end
59+
60+
function DFSane(; σ_min = 1e-10,
61+
σ_max = 1e+10,
62+
σ_1 = 1.0,
63+
M = 10,
64+
γ = 1e-4,
65+
τ_min = 0.1,
66+
τ_max = 0.5,
67+
n_exp = 2,
68+
η_strategy = (fn_1, n, x_n, f_n) -> fn_1 / n^2,
69+
max_inner_iterations = 1000)
70+
return DFSane{typeof(σ_min), typeof(η_strategy)}(σ_min,
71+
σ_max,
72+
σ_1,
73+
M,
74+
γ,
75+
τ_min,
76+
τ_max,
77+
n_exp,
78+
η_strategy,
79+
max_inner_iterations)
80+
end
81+
mutable struct DFSaneCache{iip, algType, uType, resType, T, pType,
82+
INType,
83+
tolType,
84+
probType}
85+
f::Function
86+
alg::algType
87+
uₙ::uType
88+
uₙ₋₁::uType
89+
fuₙ::resType
90+
fuₙ₋₁::resType
91+
𝒹::uType
92+
::Vector{T}
93+
f₍ₙₒᵣₘ₎ₙ₋₁::T
94+
f₍ₙₒᵣₘ₎₀::T
95+
M::Int
96+
σₙ::T
97+
σₘᵢₙ::T
98+
σₘₐₓ::T
99+
α₁::T
100+
γ::T
101+
τₘᵢₙ::T
102+
τₘₐₓ::T
103+
nₑₓₚ::Int
104+
p::pType
105+
force_stop::Bool
106+
maxiters::Int
107+
internalnorm::INType
108+
retcode::SciMLBase.ReturnCode.T
109+
abstol::tolType
110+
prob::probType
111+
stats::NLStats
112+
function DFSaneCache{iip}(f::Function, alg::algType, uₙ::uType, uₙ₋₁::uType,
113+
fuₙ::resType, fuₙ₋₁::resType, 𝒹::uType, ℋ::Vector{T},
114+
f₍ₙₒᵣₘ₎ₙ₋₁::T, f₍ₙₒᵣₘ₎₀::T, M::Int, σₙ::T, σₘᵢₙ::T, σₘₐₓ::T,
115+
α₁::T, γ::T, τₘᵢₙ::T, τₘₐₓ::T, nₑₓₚ::Int, p::pType,
116+
force_stop::Bool, maxiters::Int, internalnorm::INType,
117+
retcode::SciMLBase.ReturnCode.T, abstol::tolType,
118+
prob::probType,
119+
stats::NLStats) where {iip, algType, uType,
120+
resType, T, pType, INType,
121+
tolType,
122+
probType
123+
}
124+
new{iip, algType, uType, resType, T, pType, INType, tolType,
125+
probType
126+
}(f, alg, uₙ, uₙ₋₁, fuₙ, fuₙ₋₁, 𝒹, ℋ, f₍ₙₒᵣₘ₎ₙ₋₁, f₍ₙₒᵣₘ₎₀, M, σₙ,
127+
σₘᵢₙ, σₘₐₓ, α₁, γ, τₘᵢₙ,
128+
τₘₐₓ, nₑₓₚ, p, force_stop, maxiters, internalnorm,
129+
retcode,
130+
abstol, prob, stats)
131+
end
132+
end
133+
134+
function SciMLBase.__init(prob::NonlinearProblem{uType, iip}, alg::DFSane,
135+
args...;
136+
alias_u0 = false,
137+
maxiters = 1000,
138+
abstol = 1e-6,
139+
internalnorm = DEFAULT_NORM,
140+
kwargs...) where {uType, iip}
141+
if alias_u0
142+
uₙ = prob.u0
143+
else
144+
uₙ = deepcopy(prob.u0)
145+
end
146+
147+
p = prob.p
148+
T = eltype(uₙ)
149+
σₘᵢₙ, σₘₐₓ, γ, τₘᵢₙ, τₘₐₓ = T(alg.σ_min), T(alg.σ_max), T(alg.γ), T(alg.τ_min),
150+
T(alg.τ_max)
151+
α₁ = one(T)
152+
γ = T(alg.γ)
153+
f₍ₙₒᵣₘ₎ₙ₋₁ = α₁
154+
σₙ = T(alg.σ_1)
155+
M = alg.M
156+
nₑₓₚ = alg.n_exp
157+
𝒹, uₙ₋₁, fuₙ, fuₙ₋₁ = copy(uₙ), copy(uₙ), copy(uₙ), copy(uₙ)
158+
159+
if iip
160+
f = (dx, x) -> prob.f(dx, x, p)
161+
f(fuₙ₋₁, uₙ₋₁)
162+
else
163+
f = (x) -> prob.f(x, p)
164+
fuₙ₋₁ = f(uₙ₋₁)
165+
end
166+
167+
f₍ₙₒᵣₘ₎ₙ₋₁ = norm(fuₙ₋₁)^nₑₓₚ
168+
f₍ₙₒᵣₘ₎₀ = f₍ₙₒᵣₘ₎ₙ₋₁
169+
170+
= fill(f₍ₙₒᵣₘ₎ₙ₋₁, M)
171+
return DFSaneCache{iip}(f, alg, uₙ, uₙ₋₁, fuₙ, fuₙ₋₁, 𝒹, ℋ, f₍ₙₒᵣₘ₎ₙ₋₁, f₍ₙₒᵣₘ₎₀,
172+
M, σₙ, σₘᵢₙ, σₘₐₓ, α₁, γ, τₘᵢₙ,
173+
τₘₐₓ, nₑₓₚ, p, false, maxiters,
174+
internalnorm, ReturnCode.Default, abstol, prob,
175+
NLStats(1, 0, 0, 0, 0))
176+
end
177+
178+
function perform_step!(cache::DFSaneCache{true})
179+
@unpack f, alg, f₍ₙₒᵣₘ₎ₙ₋₁, f₍ₙₒᵣₘ₎₀,
180+
σₙ, σₘᵢₙ, σₘₐₓ, α₁, γ, τₘᵢₙ, τₘₐₓ, nₑₓₚ, M = cache
181+
182+
T = eltype(cache.uₙ)
183+
n = cache.stats.nsteps
184+
185+
# Spectral parameter range check
186+
σₙ = sign(σₙ) * clamp(abs(σₙ), σₘᵢₙ, σₘₐₓ)
187+
188+
# Line search direction
189+
@. cache.𝒹 = -σₙ * cache.fuₙ₋₁
190+
191+
η = alg.η_strategy(f₍ₙₒᵣₘ₎₀, n, cache.uₙ₋₁, cache.fuₙ₋₁)
192+
193+
= maximum(cache.ℋ)
194+
α₊ = α₁
195+
α₋ = α₁
196+
@. cache.uₙ = cache.uₙ₋₁ + α₊ * cache.𝒹
197+
198+
f(cache.fuₙ, cache.uₙ)
199+
f₍ₙₒᵣₘ₎ₙ = norm(cache.fuₙ)^nₑₓₚ
200+
for _ in 1:(cache.alg.max_inner_iterations)
201+
𝒸 =+ η - γ * α₊^2 * f₍ₙₒᵣₘ₎ₙ₋₁
202+
203+
f₍ₙₒᵣₘ₎ₙ 𝒸 && break
204+
205+
α₊ = clamp(α₊^2 * f₍ₙₒᵣₘ₎ₙ₋₁ /
206+
(f₍ₙₒᵣₘ₎ₙ + (T(2) * α₊ - T(1)) * f₍ₙₒᵣₘ₎ₙ₋₁),
207+
τₘᵢₙ * α₊,
208+
τₘₐₓ * α₊)
209+
@. cache.uₙ = cache.uₙ₋₁ - α₋ * cache.𝒹
210+
211+
f(cache.fuₙ, cache.uₙ)
212+
f₍ₙₒᵣₘ₎ₙ = norm(cache.fuₙ)^nₑₓₚ
213+
214+
f₍ₙₒᵣₘ₎ₙ .≤ 𝒸 && break
215+
216+
α₋ = clamp(α₋^2 * f₍ₙₒᵣₘ₎ₙ₋₁ / (f₍ₙₒᵣₘ₎ₙ + (T(2) * α₋ - T(1)) * f₍ₙₒᵣₘ₎ₙ₋₁),
217+
τₘᵢₙ * α₋,
218+
τₘₐₓ * α₋)
219+
220+
@. cache.uₙ = cache.uₙ₋₁ + α₊ * cache.𝒹
221+
f(cache.fuₙ, cache.uₙ)
222+
f₍ₙₒᵣₘ₎ₙ = norm(cache.fuₙ)^nₑₓₚ
223+
end
224+
225+
if cache.internalnorm(cache.fuₙ) < cache.abstol
226+
cache.force_stop = true
227+
end
228+
229+
# Update spectral parameter
230+
@. cache.uₙ₋₁ = cache.uₙ - cache.uₙ₋₁
231+
@. cache.fuₙ₋₁ = cache.fuₙ - cache.fuₙ₋₁
232+
233+
α₊ = sum(abs2, cache.uₙ₋₁)
234+
@. cache.uₙ₋₁ = cache.uₙ₋₁ * cache.fuₙ₋₁
235+
α₋ = sum(cache.uₙ₋₁)
236+
cache.σₙ = α₊ / α₋
237+
238+
# Spectral parameter bounds check
239+
if abs(cache.σₙ) > σₘₐₓ || abs(cache.σₙ) < σₘᵢₙ
240+
test_norm = sqrt(sum(abs2, cache.fuₙ₋₁))
241+
cache.σₙ = clamp(1.0 / test_norm, 1, 1e5)
242+
end
243+
244+
# Take step
245+
@. cache.uₙ₋₁ = cache.uₙ
246+
@. cache.fuₙ₋₁ = cache.fuₙ
247+
cache.f₍ₙₒᵣₘ₎ₙ₋₁ = f₍ₙₒᵣₘ₎ₙ
248+
249+
# Update history
250+
cache.ℋ[n % M + 1] = f₍ₙₒᵣₘ₎ₙ
251+
cache.stats.nf += 1
252+
return nothing
253+
end
254+
255+
function perform_step!(cache::DFSaneCache{false})
256+
@unpack f, alg, f₍ₙₒᵣₘ₎ₙ₋₁, f₍ₙₒᵣₘ₎₀,
257+
σₙ, σₘᵢₙ, σₘₐₓ, α₁, γ, τₘᵢₙ, τₘₐₓ, nₑₓₚ, M = cache
258+
259+
T = eltype(cache.uₙ)
260+
n = cache.stats.nsteps
261+
262+
# Spectral parameter range check
263+
σₙ = sign(σₙ) * clamp(abs(σₙ), σₘᵢₙ, σₘₐₓ)
264+
265+
# Line search direction
266+
cache.𝒹 = -σₙ * cache.fuₙ₋₁
267+
268+
η = alg.η_strategy(f₍ₙₒᵣₘ₎₀, n, cache.uₙ₋₁, cache.fuₙ₋₁)
269+
270+
= maximum(cache.ℋ)
271+
α₊ = α₁
272+
α₋ = α₁
273+
cache.uₙ = cache.uₙ₋₁ + α₊ * cache.𝒹
274+
275+
cache.fuₙ = f(cache.uₙ)
276+
f₍ₙₒᵣₘ₎ₙ = norm(cache.fuₙ)^nₑₓₚ
277+
for _ in 1:(cache.alg.max_inner_iterations)
278+
𝒸 =+ η - γ * α₊^2 * f₍ₙₒᵣₘ₎ₙ₋₁
279+
280+
f₍ₙₒᵣₘ₎ₙ 𝒸 && break
281+
282+
α₊ = clamp(α₊^2 * f₍ₙₒᵣₘ₎ₙ₋₁ /
283+
(f₍ₙₒᵣₘ₎ₙ + (T(2) * α₊ - T(1)) * f₍ₙₒᵣₘ₎ₙ₋₁),
284+
τₘᵢₙ * α₊,
285+
τₘₐₓ * α₊)
286+
cache.uₙ = @. cache.uₙ₋₁ - α₋ * cache.𝒹
287+
288+
cache.fuₙ = f(cache.uₙ)
289+
f₍ₙₒᵣₘ₎ₙ = norm(cache.fuₙ)^nₑₓₚ
290+
291+
f₍ₙₒᵣₘ₎ₙ .≤ 𝒸 && break
292+
293+
α₋ = clamp(α₋^2 * f₍ₙₒᵣₘ₎ₙ₋₁ / (f₍ₙₒᵣₘ₎ₙ + (T(2) * α₋ - T(1)) * f₍ₙₒᵣₘ₎ₙ₋₁),
294+
τₘᵢₙ * α₋,
295+
τₘₐₓ * α₋)
296+
297+
cache.uₙ = @. cache.uₙ₋₁ + α₊ * cache.𝒹
298+
cache.fuₙ = f(cache.uₙ)
299+
f₍ₙₒᵣₘ₎ₙ = norm(cache.fuₙ)^nₑₓₚ
300+
end
301+
302+
if cache.internalnorm(cache.fuₙ) < cache.abstol
303+
cache.force_stop = true
304+
end
305+
306+
# Update spectral parameter
307+
cache.uₙ₋₁ = @. cache.uₙ - cache.uₙ₋₁
308+
cache.fuₙ₋₁ = @. cache.fuₙ - cache.fuₙ₋₁
309+
310+
α₊ = sum(abs2, cache.uₙ₋₁)
311+
cache.uₙ₋₁ = @. cache.uₙ₋₁ * cache.fuₙ₋₁
312+
α₋ = sum(cache.uₙ₋₁)
313+
cache.σₙ = α₊ / α₋
314+
315+
# Spectral parameter bounds check
316+
if abs(cache.σₙ) > σₘₐₓ || abs(cache.σₙ) < σₘᵢₙ
317+
test_norm = sqrt(sum(abs2, cache.fuₙ₋₁))
318+
cache.σₙ = clamp(1.0 / test_norm, 1, 1e5)
319+
end
320+
321+
# Take step
322+
cache.uₙ₋₁ = cache.uₙ
323+
cache.fuₙ₋₁ = cache.fuₙ
324+
cache.f₍ₙₒᵣₘ₎ₙ₋₁ = f₍ₙₒᵣₘ₎ₙ
325+
326+
# Update history
327+
cache.ℋ[n % M + 1] = f₍ₙₒᵣₘ₎ₙ
328+
cache.stats.nf += 1
329+
return nothing
330+
end
331+
332+
function SciMLBase.solve!(cache::DFSaneCache)
333+
while !cache.force_stop && cache.stats.nsteps < cache.maxiters
334+
cache.stats.nsteps += 1
335+
perform_step!(cache)
336+
end
337+
338+
if cache.stats.nsteps == cache.maxiters
339+
cache.retcode = ReturnCode.MaxIters
340+
else
341+
cache.retcode = ReturnCode.Success
342+
end
343+
344+
SciMLBase.build_solution(cache.prob, cache.alg, cache.uₙ, cache.fuₙ;
345+
retcode = cache.retcode, stats = cache.stats)
346+
end
347+
348+
function SciMLBase.reinit!(cache::DFSaneCache{iip}, u0 = cache.uₙ; p = cache.p,
349+
abstol = cache.abstol, maxiters = cache.maxiters) where {iip}
350+
cache.p = p
351+
if iip
352+
recursivecopy!(cache.uₙ, u0)
353+
recursivecopy!(cache.uₙ₋₁, u0)
354+
cache.f = (dx, x) -> cache.prob.f(dx, x, p)
355+
cache.f(cache.fuₙ, cache.uₙ)
356+
cache.f(cache.fuₙ₋₁, cache.uₙ)
357+
else
358+
cache.uₙ = u0
359+
cache.uₙ₋₁ = u0
360+
cache.f = (x) -> cache.prob.f(x, p)
361+
cache.fuₙ = cache.f(cache.uₙ)
362+
cache.fuₙ₋₁ = cache.f(cache.uₙ)
363+
end
364+
365+
cache.f₍ₙₒᵣₘ₎ₙ₋₁ = norm(cache.fuₙ₋₁)^cache.nₑₓₚ
366+
cache.f₍ₙₒᵣₘ₎₀ = cache.f₍ₙₒᵣₘ₎ₙ₋₁
367+
fill!(cache.ℋ, cache.f₍ₙₒᵣₘ₎ₙ₋₁)
368+
369+
T = eltype(cache.uₙ)
370+
cache.σₙ = T(cache.alg.σ_1)
371+
372+
cache.abstol = abstol
373+
cache.maxiters = maxiters
374+
cache.stats.nf = 1
375+
cache.stats.nsteps = 1
376+
cache.force_stop = false
377+
cache.retcode = ReturnCode.Default
378+
return cache
379+
end

0 commit comments

Comments
 (0)