Skip to content
This repository was archived by the owner on May 15, 2025. It is now read-only.

Commit 9312115

Browse files
committed
Started to implement DFSane
1 parent 95db958 commit 9312115

File tree

4 files changed

+205
-13
lines changed

4 files changed

+205
-13
lines changed

src/SimpleNonlinearSolve.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,14 @@ include("klement.jl")
2424
include("trustRegion.jl")
2525
include("ridder.jl")
2626
include("brent.jl")
27+
include("dfsane.jl")
2728
include("ad.jl")
2829

2930
import SnoopPrecompile
3031

3132
SnoopPrecompile.@precompile_all_calls begin for T in (Float32, Float64)
3233
prob_no_brack = NonlinearProblem{false}((u, p) -> u .* u .- p, T(0.1), T(2))
33-
for alg in (SimpleNewtonRaphson, Broyden, Klement, SimpleTrustRegion)
34+
for alg in (SimpleNewtonRaphson, Broyden, Klement, SimpleTrustRegion, SimpleDFSane)
3435
solve(prob_no_brack, alg(), abstol = T(1e-2))
3536
end
3637

@@ -51,7 +52,7 @@ SnoopPrecompile.@precompile_all_calls begin for T in (Float32, Float64)
5152
end end
5253

5354
# DiffEq styled algorithms
54-
export Bisection, Brent, Broyden, Falsi, Klement, Ridder, SimpleNewtonRaphson,
55+
export Bisection, Brent, Broyden, SimpleDFSane, Falsi, Klement, Ridder, SimpleNewtonRaphson,
5556
SimpleTrustRegion
5657

5758
end # module

src/ad.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,16 @@ end
3131
function SciMLBase.solve(prob::NonlinearProblem{<:Union{Number, StaticArraysCore.SVector},
3232
iip,
3333
<:Dual{T, V, P}},
34-
alg::Union{SimpleNewtonRaphson, SimpleTrustRegion},
34+
alg::Union{SimpleNewtonRaphson, SimpleTrustRegion, SimpleDFSane}, # TODO make this to one variable
3535
args...; kwargs...) where {iip, T, V, P}
3636
sol, partials = scalar_nlsolve_ad(prob, alg, args...; kwargs...)
3737
return SciMLBase.build_solution(prob, alg, Dual{T, V, P}(sol.u, partials), sol.resid;
3838
retcode = sol.retcode)
3939
end
40-
function SciMLBase.solve(prob::NonlinearProblem{<:Union{Number, StaticArraysCore.SVector},
40+
function SciMLBase.solve(prob::NonlinearProblem{<:Union{Number, StaticArraysCore.SVector}, # TODO make this to one variable
4141
iip,
4242
<:AbstractArray{<:Dual{T, V, P}}},
43-
alg::Union{SimpleNewtonRaphson, SimpleTrustRegion}, args...;
43+
alg::Union{SimpleNewtonRaphson, SimpleTrustRegion, SimpleDFSane}, args...;
4444
kwargs...) where {iip, T, V, P}
4545
sol, partials = scalar_nlsolve_ad(prob, alg, args...; kwargs...)
4646
return SciMLBase.build_solution(prob, alg, Dual{T, V, P}(sol.u, partials), sol.resid;

src/dfsane.jl

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"""
2+
```julia
3+
SimpleDFSane(; σ_min::Real = 1e-10, σ_0::Real = 1.0, M::Int = 10,
4+
γ::Real = 1e-4, τ_min::Real = 0.1, τ_max::Real = 0.5,
5+
nexp::Int = 2)
6+
```
7+
8+
A low-overhead implementation of the df-sane method. For more information, see [1].
9+
References:
10+
W LaCruz, JM Martinez, and M Raydan (2006), Spectral residual mathod without gradient information for solving large-scale nonlinear systems of equations, Mathematics of Computation, 75, 1429-1448.
11+
### Keyword Arguments
12+
13+
14+
- `σ_min`: the minimum value of `σ_k`. Defaults to `1e-10`. # TODO write about this...
15+
- `σ_0`: the initial value of `σ_k`. Defaults to `1.0`. # TODO write about this...
16+
- `M`: the value of `M` in the paper. Defaults to `10`. # TODO write about this...
17+
- `γ`: the value of `γ` in the paper. Defaults to `1e-4`. # TODO write about this...
18+
- `τ_min`: the minimum value of `τ_k`. Defaults to `0.1`. # TODO write about this...
19+
- `τ_max`: the maximum value of `τ_k`. Defaults to `0.5`. # TODO write about this...
20+
- `nexp`: the value of `nexp` in the paper. Defaults to `2`. # TODO write about this...
21+
22+
"""
23+
struct SimpleDFSane{T} <: AbstractSimpleNonlinearSolveAlgorithm
24+
σ_min::T
25+
σ_0::T
26+
M::Int
27+
γ::T
28+
τ_min::T
29+
τ_max::T
30+
nexp::Int
31+
32+
function SimpleDFSane(; σ_min::Real = 1e-10, σ_0::Real = 1.0, M::Int = 10,
33+
γ::Real = 1e-4, τ_min::Real = 0.1, τ_max::Real = 0.5,
34+
nexp::Int = 2)
35+
new{typeof(σ_min)}(σ_min, σ_0, M, γ, τ_min, τ_max, nexp)
36+
end
37+
end
38+
39+
function SciMLBase.__solve(prob::NonlinearProblem,
40+
alg::SimpleDFSane, args...; abstol = nothing,
41+
reltol = nothing,
42+
maxiters = 1000, kwargs...)
43+
f = Base.Fix2(prob.f, prob.p)
44+
x = float(prob.u0)
45+
T = eltype(x)
46+
σ_min = float(alg.σ_min)
47+
σ_k = float(alg.σ_0)
48+
M = alg.M
49+
γ = float(alg.γ)
50+
τ_min = float(alg.τ_min)
51+
τ_max = float(alg.τ_max)
52+
nexp = alg.nexp
53+
54+
if SciMLBase.isinplace(prob)
55+
error("SimpleDFSane currently only supports out-of-place nonlinear problems")
56+
end
57+
58+
atol = abstol !== nothing ? abstol :
59+
real(oneunit(eltype(T))) * (eps(real(one(eltype(T)))))^(4 // 5)
60+
rtol = reltol !== nothing ? reltol : eps(real(one(eltype(T))))^(4 // 5)
61+
62+
function ff(x)
63+
F = f(x)
64+
f_k = norm(F)^nexp
65+
return f_k, F
66+
end
67+
68+
f_k, F_k = ff(x)
69+
α_0 = convert(T, 1.0)
70+
f_0 = f_k
71+
prev_fs = fill(f_k, M)
72+
73+
for k in 1:maxiters
74+
iszero(F_k) &&
75+
return SciMLBase.build_solution(prob, alg, x, F_k;
76+
retcode = ReturnCode.Success)
77+
78+
# Control spectral parameter
79+
if abs(σ_k) > 1 / σ_min
80+
σ_k = 1 / σ_min * sign(σ_k)
81+
elseif abs(σ_k) < σ_min
82+
σ_k = σ_min
83+
end
84+
85+
# Line search direction
86+
d = -σ_k * F_k
87+
88+
# Nonmonotone line search
89+
η = f_0 / k^2
90+
91+
f_bar = maximum(prev_fs)
92+
α_p = α_0
93+
α_m = α_0
94+
xp = x + α_p * d
95+
fp, Fp = ff(xp)
96+
while true
97+
if fp f_bar + η - γ * α_p^2 * f_k
98+
break
99+
end
100+
101+
α_tp = α_p^2 * f_k / (fp + (2 * α_p - 1) * f_k)
102+
xp = x - α_m * d
103+
fp, Fp = ff(xp)
104+
105+
if fp f_bar + η - γ * α_m^2 * f_k
106+
break
107+
end
108+
109+
α_tm = α_m^2 * f_k / (fp + (2 * α_m - 1) * f_k)
110+
α_p = min(τ_max * α_p, max(α_tp, τ_min * α_p))
111+
α_m = min(τ_max * α_m, max(α_tm, τ_min * α_m))
112+
xp = x + α_p * d
113+
fp, Fp = ff(xp)
114+
end
115+
116+
if isapprox(xp, x, atol = atol, rtol = rtol)
117+
return SciMLBase.build_solution(prob, alg, xp, Fp;
118+
retcode = ReturnCode.Success)
119+
end
120+
# Update spectral parameter
121+
s_k = xp - x
122+
y_k = Fp - F_k
123+
σ_k = dot(s_k, s_k) / dot(s_k, y_k)
124+
125+
# Take step
126+
x = xp
127+
F_k = Fp
128+
f_k = fp
129+
130+
# Store function value
131+
idx_to_replace = k % M + 1
132+
prev_fs[idx_to_replace] = fp
133+
end
134+
return SciMLBase.build_solution(prob, alg, x, F_k; retcode = ReturnCode.MaxIters)
135+
end

test/basictests.jl

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,37 +62,49 @@ sol = benchmark_scalar(sf, csu0)
6262
@test sol.retcode === ReturnCode.Success
6363
@test sol.u * sol.u - 2 < 1e-9
6464

65+
# SimpleDFSane
66+
function benchmark_scalar(f, u0)
67+
probN = NonlinearProblem{false}(f, u0)
68+
sol = (solve(probN, SimpleDFSane()))
69+
end
70+
71+
sol = benchmark_scalar(sf, csu0)
72+
@test sol.retcode === ReturnCode.Success
73+
@test sol.u * sol.u - 2 < 1e-9
74+
6575
# AD Tests
6676
using ForwardDiff
6777

6878
# Immutable
6979
f, u0 = (u, p) -> u .* u .- p, @SVector[1.0, 1.0]
7080

71-
for alg in (SimpleNewtonRaphson(), Broyden(), Klement(), SimpleTrustRegion())
81+
for alg in (SimpleNewtonRaphson(), Broyden(), Klement(), SimpleTrustRegion(),
82+
SimpleDFSane())
7283
g = function (p)
7384
probN = NonlinearProblem{false}(f, csu0, p)
7485
sol = solve(probN, alg, abstol = 1e-9)
7586
return sol.u[end]
7687
end
7788

7889
for p in 1.1:0.1:100.0
79-
@test g(p) sqrt(p)
80-
@test ForwardDiff.derivative(g, p) 1 / (2 * sqrt(p))
90+
@test abs.(g(p)) sqrt(p)
91+
@test abs.(ForwardDiff.derivative(g, p)) 1 / (2 * sqrt(p))
8192
end
8293
end
8394

8495
# Scalar
8596
f, u0 = (u, p) -> u * u - p, 1.0
86-
for alg in (SimpleNewtonRaphson(), Broyden(), Klement(), SimpleTrustRegion())
97+
for alg in (SimpleNewtonRaphson(), Broyden(), Klement(), SimpleTrustRegion(),
98+
SimpleDFSane())
8799
g = function (p)
88100
probN = NonlinearProblem{false}(f, oftype(p, u0), p)
89101
sol = solve(probN, alg)
90102
return sol.u
91103
end
92104

93105
for p in 1.1:0.1:100.0
94-
@test g(p) sqrt(p)
95-
@test ForwardDiff.derivative(g, p) 1 / (2 * sqrt(p))
106+
@test abs(g(p)) sqrt(p)
107+
@test abs(ForwardDiff.derivative(g, p)) 1 / (2 * sqrt(p))
96108
end
97109
end
98110

@@ -148,7 +160,7 @@ for alg in [Bisection(), Falsi(), Ridder(), Brent()]
148160
@test ForwardDiff.jacobian(g, p) ForwardDiff.jacobian(t, p)
149161
end
150162

151-
for alg in (SimpleNewtonRaphson(), Broyden(), Klement(), SimpleTrustRegion())
163+
for alg in (SimpleNewtonRaphson(), Broyden(), Klement(), SimpleTrustRegion(), SimpleDFSane())
152164
global g, p
153165
g = function (p)
154166
probN = NonlinearProblem{false}(f, 0.5, p)
@@ -169,6 +181,7 @@ probN = NonlinearProblem(f, u0)
169181
@test solve(probN, SimpleTrustRegion(; autodiff = false)).u[end] sqrt(2.0)
170182
@test solve(probN, Broyden()).u[end] sqrt(2.0)
171183
@test solve(probN, Klement()).u[end] sqrt(2.0)
184+
@test solve(probN, SimpleDFSane()).u[end] sqrt(2.0)
172185

173186
for u0 in [1.0, [1, 1.0]]
174187
local f, probN, sol
@@ -185,8 +198,8 @@ for u0 in [1.0, [1, 1.0]]
185198
@test solve(probN, SimpleTrustRegion(; autodiff = false)).u sol
186199

187200
@test solve(probN, Broyden()).u sol
188-
189201
@test solve(probN, Klement()).u sol
202+
@test solve(probN, SimpleDFSane()).u sol
190203
end
191204

192205
# Bisection Tests
@@ -299,3 +312,46 @@ for options in list_of_options
299312
sol = solve(probN, alg)
300313
@test all(abs.(f(u, p)) .< 1e-10)
301314
end
315+
316+
# # Test that `SimpleDFSane` passes a test that `SimpleNewtonRaphson` fails on.
317+
# u0 = [-10.0, -1.0, 1.0, 2.0, 3.0, 4.0, 10.0]
318+
# global g, f
319+
# f = (u, p) -> 0.010000000000000002 .+
320+
# 10.000000000000002 ./ (1 .+
321+
# (0.21640425613334457 .+
322+
# 216.40425613334457 ./ (1 .+
323+
# (0.21640425613334457 .+
324+
# 216.40425613334457 ./
325+
# (1 .+ 0.0006250000000000001(u .^ 2.0))) .^ 2.0)) .^ 2.0) .-
326+
# 0.0011552453009332421u .- p
327+
# g = function (p)
328+
# probN = NonlinearProblem{false}(f, u0, p)
329+
# sol = solve(probN, SimpleDFSane())
330+
# return sol.u
331+
# end
332+
# p = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
333+
# u = g(p)
334+
# f(u, p)
335+
# @test all(abs.(f(u, p)) .< 1e-10)
336+
337+
# # Test kwars in `SimpleDFSane`
338+
339+
340+
# list_of_options = zip(max_trust_radius, initial_trust_radius, step_threshold,
341+
# shrink_threshold, expand_threshold, shrink_factor,
342+
# expand_factor, max_shrink_times)
343+
# for options in list_of_options
344+
# local probN, sol, alg
345+
# alg = SimpleDFSane(max_trust_radius = options[1],
346+
# initial_trust_radius = options[2],
347+
# step_threshold = options[3],
348+
# shrink_threshold = options[4],
349+
# expand_threshold = options[5],
350+
# shrink_factor = options[6],
351+
# expand_factor = options[7],
352+
# max_shrink_times = options[8])
353+
354+
# probN = NonlinearProblem(f, u0, p)
355+
# sol = solve(probN, alg)
356+
# @test all(abs.(f(u, p)) .< 1e-10)
357+
# end

0 commit comments

Comments
 (0)