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

Commit 310f84b

Browse files
Merge pull request #47 from yash2798/ys/halley
Implemented a simple version of Halley's Method for scalar functions along with some tests
2 parents 9ebee9c + fb6880c commit 310f84b

File tree

3 files changed

+125
-4
lines changed

3 files changed

+125
-4
lines changed

src/SimpleNonlinearSolve.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,13 @@ include("ridder.jl")
3737
include("brent.jl")
3838
include("dfsane.jl")
3939
include("ad.jl")
40+
include("halley.jl")
4041

4142
import SnoopPrecompile
4243

4344
SnoopPrecompile.@precompile_all_calls begin for T in (Float32, Float64)
4445
prob_no_brack = NonlinearProblem{false}((u, p) -> u .* u .- p, T(0.1), T(2))
45-
for alg in (SimpleNewtonRaphson, Broyden, Klement, SimpleTrustRegion, SimpleDFSane)
46+
for alg in (SimpleNewtonRaphson, Halley, Broyden, Klement, SimpleTrustRegion, SimpleDFSane)
4647
solve(prob_no_brack, alg(), abstol = T(1e-2))
4748
end
4849

@@ -63,7 +64,7 @@ SnoopPrecompile.@precompile_all_calls begin for T in (Float32, Float64)
6364
end end
6465

6566
# DiffEq styled algorithms
66-
export Bisection, Brent, Broyden, LBroyden, SimpleDFSane, Falsi, Klement,
67+
export Bisection, Brent, Broyden, LBroyden, SimpleDFSane, Falsi, Halley, Klement,
6768
Ridder, SimpleNewtonRaphson, SimpleTrustRegion
6869

6970
end # module

src/halley.jl

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""
2+
```julia
3+
Halley(; chunk_size = Val{0}(), autodiff = Val{true}(),
4+
diff_type = Val{:forward})
5+
```
6+
7+
A low-overhead implementation of Halley's Method. This method is non-allocating on scalar
8+
and static array problems.
9+
10+
!!! note
11+
12+
As part of the decreased overhead, this method omits some of the higher level error
13+
catching of the other methods. Thus, to see better error messages, use one of the other
14+
methods like `NewtonRaphson`
15+
16+
### Keyword Arguments
17+
18+
- `chunk_size`: the chunk size used by the internal ForwardDiff.jl automatic differentiation
19+
system. This allows for multiple derivative columns to be computed simultaneously,
20+
improving performance. Defaults to `0`, which is equivalent to using ForwardDiff.jl's
21+
default chunk size mechanism. For more details, see the documentation for
22+
[ForwardDiff.jl](https://juliadiff.org/ForwardDiff.jl/stable/).
23+
- `autodiff`: whether to use forward-mode automatic differentiation for the Jacobian.
24+
Note that this argument is ignored if an analytical Jacobian is passed; as that will be
25+
used instead. Defaults to `Val{true}`, which means ForwardDiff.jl is used by default.
26+
If `Val{false}`, then FiniteDiff.jl is used for finite differencing.
27+
- `diff_type`: the type of finite differencing used if `autodiff = false`. Defaults to
28+
`Val{:forward}` for forward finite differences. For more details on the choices, see the
29+
[FiniteDiff.jl](https://github.com/JuliaDiff/FiniteDiff.jl) documentation.
30+
"""
31+
struct Halley{CS, AD, FDT} <: AbstractNewtonAlgorithm{CS, AD, FDT}
32+
function Halley(; chunk_size = Val{0}(), autodiff = Val{true}(),
33+
diff_type = Val{:forward})
34+
new{SciMLBase._unwrap_val(chunk_size), SciMLBase._unwrap_val(autodiff),
35+
SciMLBase._unwrap_val(diff_type)}()
36+
end
37+
end
38+
39+
function SciMLBase.__solve(prob::NonlinearProblem,
40+
alg::Halley, args...; abstol = nothing,
41+
reltol = nothing,
42+
maxiters = 1000, kwargs...)
43+
f = Base.Fix2(prob.f, prob.p)
44+
x = float(prob.u0)
45+
fx = f(x)
46+
# fx = float(prob.u0)
47+
if !isa(fx, Number) || !isa(x, Number)
48+
error("Halley currently only supports scalar-valued single-variable functions")
49+
end
50+
T = typeof(x)
51+
52+
if SciMLBase.isinplace(prob)
53+
error("Halley currently only supports out-of-place nonlinear problems")
54+
end
55+
56+
atol = abstol !== nothing ? abstol :
57+
real(oneunit(eltype(T))) * (eps(real(one(eltype(T)))))^(4 // 5)
58+
rtol = reltol !== nothing ? reltol : eps(real(one(eltype(T))))^(4 // 5)
59+
60+
if typeof(x) <: Number
61+
xo = oftype(one(eltype(x)), Inf)
62+
else
63+
xo = map(x -> oftype(one(eltype(x)), Inf), x)
64+
end
65+
66+
for i in 1:maxiters
67+
if alg_autodiff(alg)
68+
fx = f(x)
69+
dfdx(x) = ForwardDiff.derivative(f, x)
70+
dfx = dfdx(x)
71+
d2fx = ForwardDiff.derivative(dfdx, x)
72+
else
73+
fx = f(x)
74+
dfx = FiniteDiff.finite_difference_derivative(f, x, diff_type(alg), eltype(x),
75+
fx)
76+
d2fx = FiniteDiff.finite_difference_derivative(x -> FiniteDiff.finite_difference_derivative(f, x),
77+
x, diff_type(alg), eltype(x), fx)
78+
end
79+
iszero(fx) &&
80+
return SciMLBase.build_solution(prob, alg, x, fx; retcode = ReturnCode.Success)
81+
Δx = (2*dfx^2 - fx*d2fx) \ (2fx*dfx)
82+
x -= Δx
83+
if isapprox(x, xo, atol = atol, rtol = rtol)
84+
return SciMLBase.build_solution(prob, alg, x, fx; retcode = ReturnCode.Success)
85+
end
86+
xo = x
87+
end
88+
89+
return SciMLBase.build_solution(prob, alg, x, fx; retcode = ReturnCode.MaxIters)
90+
end

test/basictests.jl

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,29 @@ if VERSION >= v"1.7"
2626
@test (@ballocated benchmark_scalar(sf, csu0)) == 0
2727
end
2828

29+
# Halley
30+
function benchmark_scalar(f, u0)
31+
probN = NonlinearProblem{false}(f, u0)
32+
sol = (solve(probN, Halley()))
33+
end
34+
35+
# function ff(u, p)
36+
# u .* u .- 2
37+
# end
38+
# const cu0 = @SVector[1.0, 1.0]
39+
function sf(u, p)
40+
u * u - 2
41+
end
42+
const csu0 = 1.0
43+
44+
sol = benchmark_scalar(sf, csu0)
45+
@test sol.retcode === ReturnCode.Success
46+
@test sol.u * sol.u - 2 < 1e-9
47+
48+
if VERSION >= v"1.7"
49+
@test (@ballocated benchmark_scalar(sf, csu0)) == 0
50+
end
51+
2952
# Broyden
3053
function benchmark_scalar(f, u0)
3154
probN = NonlinearProblem{false}(f, u0)
@@ -95,7 +118,7 @@ end
95118
# Scalar
96119
f, u0 = (u, p) -> u * u - p, 1.0
97120
for alg in (SimpleNewtonRaphson(), Broyden(), LBroyden(), Klement(), SimpleTrustRegion(),
98-
SimpleDFSane())
121+
SimpleDFSane(), Halley())
99122
g = function (p)
100123
probN = NonlinearProblem{false}(f, oftype(p, u0), p)
101124
sol = solve(probN, alg)
@@ -161,7 +184,7 @@ for alg in [Bisection(), Falsi(), Ridder(), Brent()]
161184
end
162185

163186
for alg in (SimpleNewtonRaphson(), Broyden(), LBroyden(), Klement(), SimpleTrustRegion(),
164-
SimpleDFSane())
187+
SimpleDFSane(), Halley())
165188
global g, p
166189
g = function (p)
167190
probN = NonlinearProblem{false}(f, 0.5, p)
@@ -185,6 +208,13 @@ probN = NonlinearProblem(f, u0)
185208
@test solve(probN, Klement()).u[end] sqrt(2.0)
186209
@test solve(probN, SimpleDFSane()).u[end] sqrt(2.0)
187210

211+
# Separate Error check for Halley; will be included in above error checks for the improved Halley
212+
f, u0 = (u, p) -> u * u - 2.0, 1.0
213+
probN = NonlinearProblem(f, u0)
214+
215+
@test solve(probN, Halley()).u sqrt(2.0)
216+
217+
188218
for u0 in [1.0, [1, 1.0]]
189219
local f, probN, sol
190220
f = (u, p) -> u .* u .- 2.0

0 commit comments

Comments
 (0)