Skip to content

Commit e235900

Browse files
eeshan9815dpsanders
authored andcommitted
Interval Newton Method (#39)
* newton1d for 0 not in f_prime(X) * add other cases * Add review changes * add extended interval division * add tests * minor changes * fix AD bug
1 parent fc55d1f commit e235900

File tree

4 files changed

+185
-1
lines changed

4 files changed

+185
-1
lines changed

src/IntervalRootFinding.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export
1515
derivative, jacobian, # reexport derivative from ForwardDiff
1616
Root, is_unique,
1717
roots, find_roots,
18-
bisect
18+
bisect, newton1d
1919

2020
import Base: , show
2121

@@ -62,6 +62,8 @@ include("newton.jl")
6262
include("krawczyk.jl")
6363
include("complex.jl")
6464
include("branch_and_prune.jl")
65+
include("newton1d.jl")
66+
6567

6668

6769
gradient(f) = X -> ForwardDiff.gradient(f, SVector(X))

src/newton1d.jl

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
"""`newton1d` performs the interval Newton method on the given function `f`
2+
with its derivative `f′` and initial interval `x`.
3+
Optional keyword arguments give the tolerances `reltol` and `abstol`.
4+
`reltol` is the tolerance on the relative error whereas `abstol` is the tolerance on |f(X)|,
5+
and a `debug` boolean argument that prints out diagnostic information."""
6+
7+
function newton1d{T}(f::Function, f′::Function, x::Interval{T};
8+
reltol=eps(T), abstol=eps(T), debug=false)
9+
10+
L = Interval{T}[]
11+
12+
R = Root{Interval{T}}[]
13+
14+
push!(L, x)
15+
16+
while !isempty(L)
17+
X = pop!(L)
18+
m = mid(X)
19+
if (isempty(X))
20+
continue
21+
end
22+
23+
if 0 f′(X)
24+
while true
25+
m = mid(X)
26+
N = m - (f(Interval(m)) / f′(X))
27+
X = X N
28+
29+
if isempty(X)
30+
break
31+
32+
elseif 0 f(Interval(prevfloat(m), nextfloat(m)))
33+
push!(R, Root(X, :unique))
34+
break
35+
end
36+
end
37+
38+
else
39+
# 0 ∈ f'(X)
40+
expansion_pt = Inf
41+
# expansion point for the newton step might be m, X.lo or X.hi according to some conditions
42+
43+
if 0 f(Interval(mid(X)))
44+
# 0 ∈ fⁱ(x)
45+
# Step 7
46+
47+
if 0 f(Interval(X.lo))
48+
expansion_pt = X.lo
49+
50+
elseif 0 f(Interval(X.hi))
51+
expansion_pt = X.hi
52+
53+
else
54+
x1 = mid(Interval(X.lo, mid(X)))
55+
x2 = mid(Interval(mid(X), X.hi))
56+
if 0 f(Interval(x1)) || 0 f(Interval(x2))
57+
push!(L, Interval(X.lo, m))
58+
push!(L, Interval(m, X.hi))
59+
continue
60+
61+
else
62+
push!(R, Root(X, :unique))
63+
continue
64+
end
65+
end
66+
67+
else
68+
# 0 ∉ fⁱ(x)
69+
70+
if (diam(X)/mag(X)) < reltol && diam(f(X)) < abstol
71+
push!(R, Root(X, :unknown))
72+
continue
73+
end
74+
end
75+
# Step 8
76+
77+
if isinf(expansion_pt)
78+
expansion_pt = mid(X)
79+
end
80+
81+
initial_width = diam(X)
82+
83+
a = f(Interval(expansion_pt))
84+
b = f′(X)
85+
86+
if 0 < b.hi && 0 > b.lo && 0 a
87+
if a.hi < 0
88+
push!(L, X (expansion_pt - Interval(-Inf, a.hi / b.hi)))
89+
push!(L, X (expansion_pt - Interval(a.hi / b.lo, Inf)))
90+
91+
elseif a.lo > 0
92+
push!(L, X (expansion_pt - Interval(-Inf, a.lo / b.lo)))
93+
push!(L, X (expansion_pt - Interval(a.lo / b.hi, Inf)))
94+
95+
end
96+
97+
continue
98+
99+
else
100+
N = expansion_pt - (f(Interval(expansion_pt))/f′(X))
101+
X = X N
102+
m = mid(X)
103+
104+
if isempty(X)
105+
continue
106+
end
107+
end
108+
109+
if diam(X) > initial_width/2
110+
push!(L, Interval(m, X.hi))
111+
X = Interval(X.lo, m)
112+
end
113+
114+
push!(L, X)
115+
end
116+
end
117+
118+
return R
119+
end
120+
121+
122+
"""`newton1d` performs the interval Newton method on the given function `f` and initial interval `x`.
123+
Optional keyword arguments give the tolerances `reltol` and `abstol`.
124+
`reltol` is the tolerance on the relative error whereas `abstol` is the tolerance on |f(X)|,
125+
and a `debug` boolean argument that prints out diagnostic information."""
126+
127+
newton1d{T}(f::Function, x::Interval{T}; args...) =
128+
newton1d(f, x->D(f,x), x; args...)

test/newton1d.jl

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
2+
using IntervalArithmetic, IntervalRootFinding
3+
using ForwardDiff
4+
using Base.Test
5+
6+
const D = IntervalRootFinding.derivative
7+
8+
setprecision(Interval, Float64)
9+
float_pi = @interval(pi)
10+
11+
setprecision(Interval, 10000)
12+
big_pi = @interval(pi)
13+
# Using precision "only" 256 leads to overestimation of the true roots for `cos`
14+
# i.e the Newton method gives more accurate results!
15+
16+
half_pi = big_pi / 2
17+
three_halves_pi = 3*big_pi/2
18+
19+
@testset "Testing newton1d" begin
20+
21+
f(x) = exp(x^2) - cos(x)
22+
f′(x) = 2*x*exp(x^2) + sin(x)
23+
f1(x) = x^4 - 10x^3 + 35x^2 - 50x + 24
24+
f1′(x) = 4x^3 - 30x^2 + 70x - 50
25+
26+
for autodiff in (false, true)
27+
if autodiff
28+
rts1 = newton1d(sin, -5..5)
29+
rts2 = newton1d(f, -..∞)
30+
rts3 = newton1d(f1, -10..10)
31+
32+
else
33+
rts1 = newton1d(sin, cos, -5..5)
34+
rts2 = newton1d(f, f′, -..∞)
35+
rts3 = newton1d(f1, f1′, -10..10)
36+
end
37+
38+
@test length(rts1) == 3
39+
L = [(-π.. -π), (0..0), (π..π)]
40+
for i = 1:length(rts1)
41+
@test L[i] == rts1[i].interval && :unique == rts1[i].status
42+
end
43+
44+
@test length(rts2) == 1
45+
@test (0..0) == rts2[1].interval && :unique == rts2[1].status
46+
47+
@test length(rts3) == 4
48+
L = [1, 2, 3, 4]
49+
for i = 1:length(rts3)
50+
@test L[i] in rts3[i].interval && :unique == rts3[i].status
51+
end
52+
end
53+
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ using Base.Test
44
include("bisect.jl")
55
include("findroots.jl")
66
include("roots.jl")
7+
include("newton1d.jl")

0 commit comments

Comments
 (0)