Skip to content

Commit 1a6d600

Browse files
committed
Add Halley's method via descent API
1 parent e3238a7 commit 1a6d600

File tree

4 files changed

+104
-2
lines changed

4 files changed

+104
-2
lines changed

Project.toml

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ SimpleNonlinearSolve = "727e6d20-b764-4bd8-a329-72de5adea6c7"
2727
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
2828
SparseDiffTools = "47a9eef4-7e08-11e9-0b38-333d64bd3804"
2929
StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"
30+
TaylorDiff = "b36ab563-344f-407b-a36a-4f200bebf99c"
3031
TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
3132

3233
[weakdeps]

src/NonlinearSolve.jl

+4-2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ include("timer_outputs.jl")
4848
include("internal/helpers.jl")
4949

5050
include("descent/newton.jl")
51+
include("descent/halley.jl")
5152
include("descent/steepest.jl")
5253
include("descent/dogleg.jl")
5354
include("descent/damped_newton.jl")
@@ -80,6 +81,7 @@ include("algorithms/gauss_newton.jl")
8081
include("algorithms/levenberg_marquardt.jl")
8182
include("algorithms/trust_region.jl")
8283
include("algorithms/extension_algs.jl")
84+
include("algorithms/halley.jl")
8385

8486
include("utils.jl")
8587
include("default.jl")
@@ -141,7 +143,7 @@ include("default.jl")
141143
end
142144

143145
# Core Algorithms
144-
export NewtonRaphson, PseudoTransient, Klement, Broyden, LimitedMemoryBroyden, DFSane
146+
export NewtonRaphson, PseudoTransient, Klement, Broyden, LimitedMemoryBroyden, DFSane, Halley
145147
export GaussNewton, LevenbergMarquardt, TrustRegion
146148
export NonlinearSolvePolyAlgorithm, RobustMultiNewton, FastShortcutNonlinearPolyalg,
147149
FastShortcutNLLSPolyalg
@@ -154,7 +156,7 @@ export LeastSquaresOptimJL, FastLevenbergMarquardtJL, CMINPACK, NLsolveJL, NLSol
154156
export GeneralizedFirstOrderAlgorithm, ApproximateJacobianSolveAlgorithm, GeneralizedDFSane
155157

156158
# Descent Algorithms
157-
export NewtonDescent, SteepestDescent, Dogleg, DampedNewtonDescent, GeodesicAcceleration
159+
export NewtonDescent, SteepestDescent, Dogleg, DampedNewtonDescent, GeodesicAcceleration, HalleyDescent
158160

159161
# Globalization
160162
## Line Search Algorithms

src/algorithms/halley.jl

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""
2+
Halley(; concrete_jac = nothing, linsolve = nothing, linesearch = NoLineSearch(),
3+
precs = DEFAULT_PRECS, autodiff = nothing)
4+
5+
An experimental Halley's method implementation.
6+
"""
7+
function Halley(; concrete_jac = nothing, linsolve = nothing,
8+
linesearch = NoLineSearch(), precs = DEFAULT_PRECS, autodiff = nothing)
9+
descent = HalleyDescent(; linsolve, precs)
10+
return GeneralizedFirstOrderAlgorithm(;
11+
concrete_jac, name = :Halley, linesearch, descent, jacobian_ad = autodiff)
12+
end

src/descent/halley.jl

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""
2+
HalleyDescent(; linsolve = nothing, precs = DEFAULT_PRECS)
3+
4+
Compute the descent direction as ``J δu = -fu``. For non-square Jacobian problems, this is
5+
commonly referred to as the Gauss-Newton Descent.
6+
7+
See also [`Dogleg`](@ref), [`SteepestDescent`](@ref), [`DampedNewtonDescent`](@ref).
8+
"""
9+
@kwdef @concrete struct HalleyDescent <: AbstractDescentAlgorithm
10+
linsolve = nothing
11+
precs = DEFAULT_PRECS
12+
end
13+
14+
using TaylorDiff: derivative
15+
16+
function Base.show(io::IO, d::HalleyDescent)
17+
modifiers = String[]
18+
d.linsolve !== nothing && push!(modifiers, "linsolve = $(d.linsolve)")
19+
d.precs !== DEFAULT_PRECS && push!(modifiers, "precs = $(d.precs)")
20+
print(io, "HalleyDescent($(join(modifiers, ", ")))")
21+
end
22+
23+
supports_line_search(::HalleyDescent) = true
24+
25+
@concrete mutable struct HalleyDescentCache{pre_inverted} <:
26+
AbstractDescentCache
27+
f
28+
p
29+
δu
30+
δus
31+
b
32+
lincache
33+
timer
34+
end
35+
36+
@internal_caches HalleyDescentCache :lincache
37+
38+
function __internal_init(
39+
prob::NonlinearProblem, alg::HalleyDescent, J, fu, u; shared::Val{N} = Val(1),
40+
pre_inverted::Val{INV} = False, linsolve_kwargs = (;), abstol = nothing,
41+
reltol = nothing, timer = get_timer_output(), kwargs...) where {INV, N}
42+
@bb δu = similar(u)
43+
@bb b = similar(u)
44+
δus = N 1 ? nothing : map(2:N) do i
45+
@bb δu_ = similar(u)
46+
end
47+
INV && return HalleyDescentCache{true}(prob.f, prob.p, δu, δus, b, nothing, timer)
48+
lincache = LinearSolverCache(
49+
alg, alg.linsolve, J, _vec(fu), _vec(u); abstol, reltol, linsolve_kwargs...)
50+
return HalleyDescentCache{false}(prob.f, prob.p, δu, δus, b, lincache, timer)
51+
end
52+
53+
function __internal_solve!(
54+
cache::HalleyDescentCache{INV}, J, fu, u, idx::Val = Val(1);
55+
skip_solve::Bool = false, new_jacobian::Bool = true, kwargs...) where {INV}
56+
δu = get_du(cache, idx)
57+
skip_solve && return δu, true, (;)
58+
if INV
59+
@assert J!==nothing "`J` must be provided when `pre_inverted = Val(true)`."
60+
@bb δu = J × vec(fu)
61+
else
62+
@static_timeit cache.timer "linear solve 1" begin
63+
δu = cache.lincache(;
64+
A = J, b = _vec(fu), kwargs..., linu = _vec(δu), du = _vec(δu),
65+
reuse_A_if_factorization = !new_jacobian || (idx !== Val(1)))
66+
δu = _restructure(get_du(cache, idx), δu)
67+
end
68+
end
69+
b = cache.b
70+
# compute the hessian-vector-vector product
71+
hvvp = derivative(x -> cache.f(x, cache.p), u, δu, 2)
72+
# second linear solve, reuse factorization if possible
73+
if INV
74+
@bb b = J × vec(hvvp)
75+
else
76+
@static_timeit cache.timer "linear solve 2" begin
77+
b = cache.lincache(;
78+
A = J, b = _vec(hvvp), kwargs..., linu = _vec(b), du = _vec(b),
79+
reuse_A_if_factorization = true)
80+
b = _restructure(cache.b, b)
81+
end
82+
end
83+
@bb @. δu = δu * δu / (b / 2 - δu)
84+
set_du!(cache, δu, idx)
85+
cache.b = b
86+
return δu, true, (;)
87+
end

0 commit comments

Comments
 (0)