Skip to content

Commit 13bc215

Browse files
Kolarudpsanders
authored andcommitted
Implement Krawczyk method (#57)
* Krawczyk contractor implementation * Add tests and fix complex roots for Krawczyk * Fix requested changes - Uniformized tests - Uniformized contraction for NewtonLike contractors - Add a bit of documentation
1 parent 2570253 commit 13bc215

File tree

3 files changed

+140
-61
lines changed

3 files changed

+140
-61
lines changed

src/contractors.jl

Lines changed: 79 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1-
# contractors:
2-
"""
1+
export Bisection, Newton, Krawczyk
2+
3+
Base.isinf(X::IntervalBox) = any(isinf.(X))
4+
IntervalArithmetic.mid(X::IntervalBox, α) = mid.(X, α)
5+
6+
doc"""
37
Contractor{F}
48
59
Abstract type for contractors.
610
"""
711
abstract type Contractor{F} end
812

9-
export Bisection, Newton
13+
doc"""
14+
Bisection{F} <: Contractor{F}
1015
11-
# bisection
16+
Contractor type for the bisection method.
17+
"""
1218
struct Bisection{F} <: Contractor{F}
1319
f::F
1420
end
@@ -23,38 +29,54 @@ function (contractor::Bisection)(X, tol)
2329
return :unknown, X
2430
end
2531

26-
# Newton
27-
struct Newton{F,FP} <: Contractor{F}
28-
f::F
29-
f′::FP # use \prime<TAB> for ′
30-
end
32+
doc"""
33+
newtonlike_contract(op, X, tol)
3134
32-
Base.isinf(X::IntervalBox) = any(isinf.(X))
35+
Contraction operation for contractors using the first derivative of the
36+
function. This contraction use a bisection scheme to refine the intervals
37+
with `:unkown` status.
3338
34-
function (C::Newton)(X, tol)
39+
Currently `Newton` and `Krawczyk` contractors uses this.
40+
"""
41+
function newtonlike_contract(op, C, X, tol)
3542
# use Bisection contractor for this:
3643
if !(contains_zero(C.f(X)))
3744
return :empty, X
3845
end
3946

4047
# given that have the Jacobian, can also do mean value form
4148

42-
NX = 𝒩(C.f, C.f′, X) X
49+
NX = op(C.f, C.f′, X) X
4350

4451
isempty(NX) && return :empty, X
45-
46-
if isinf(X)
47-
return :unknown, NX # force bisection
48-
end
52+
isinf(X) && return :unknown, NX # force bisection
4953

5054
if NX X # isinterior; know there's a unique root inside
51-
NX = refine(X -> 𝒩(C.f, C.f′, X), NX, tol)
55+
NX = refine(X -> op(C.f, C.f′, X), NX, tol)
5256
return :unique, NX
5357
end
5458

5559
return :unknown, NX
5660
end
5761

62+
doc"""
63+
Newton{F, FP} <: Contractor{F}
64+
65+
Contractor type for the Newton method.
66+
67+
# Fields
68+
- `f::F`: function whose roots are searched
69+
- `f::FP`: derivative or jacobian of `f`
70+
"""
71+
struct Newton{F,FP} <: Contractor{F}
72+
f::F
73+
f′::FP # use \prime<TAB> for ′
74+
end
75+
76+
function (C::Newton)(X, tol)
77+
newtonlike_contract(𝒩, C, X, tol)
78+
end
79+
5880

5981
doc"""
6082
Single-variable Newton operator
@@ -77,20 +99,57 @@ function 𝒩{T}(f, X::Interval{T}, dX::Interval{T})
7799
m - (f(m) / dX)
78100
end
79101

80-
IntervalArithmetic.mid(X::IntervalBox, α) = mid.(X, α)
81-
82102
doc"""
83103
Multi-variable Newton operator.
84104
"""
85105
function 𝒩(f::Function, jacobian::Function, X::IntervalBox) # multidimensional Newton operator
86-
87106
m = IntervalBox(Interval.(mid(X, where_bisect)))
88107
J = jacobian(SVector(X))
89108

90109
return IntervalBox(m - (J \ f(m)))
91110
end
92111

93112

113+
doc"""
114+
Krawczyk{F, FP} <: Contractor{F}
115+
116+
Contractor type for the Krawczyk method.
117+
118+
# Fields
119+
- `f::F`: function whose roots are searched
120+
- `f::FP`: derivative or jacobian of `f`
121+
"""
122+
struct Krawczyk{F, FP} <: Contractor{F}
123+
f::F
124+
f′::FP # use \prime<TAB> for ′
125+
end
126+
127+
function (C::Krawczyk)(X, tol)
128+
newtonlike_contract(𝒦, C, X, tol)
129+
end
130+
131+
132+
doc"""
133+
Single-variable Krawczyk operator
134+
"""
135+
function 𝒦(f, f′, X::Interval{T}) where {T}
136+
m = Interval(mid(X))
137+
Y = 1/f′(m)
138+
139+
m - Y*f(m) + (1 - Y*f′(X))*(X - m)
140+
end
141+
142+
doc"""
143+
Multi-variable Krawczyk operator
144+
"""
145+
function 𝒦(f, jacobian, X::IntervalBox{T}) where {T}
146+
m = mid(X)
147+
J = jacobian(X)
148+
Y = inv(jacobian(m))
149+
m = IntervalBox(Interval.(m))
150+
151+
IntervalBox(m - Y*f(m) + (I - Y*J)*(X - m))
152+
end
94153

95154
"""
96155
Generic refine operation for Krawczyk and Newton.

src/roots.jl

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ function as the contractor.
8282
8383
Inputs:
8484
- `X`: `Interval` or `IntervalBox`
85-
- `contractor`: function that determines the status of a given box `X`. It
85+
- `contract`: function that determines the status of a given box `X`. It
8686
returns the new box and a symbol indicating the status. Current possible
8787
values are of type `Bisection` or `Newton`
8888
@@ -116,9 +116,10 @@ contains_zero(X::IntervalBox) = all(contains_zero.(X))
116116

117117

118118
IntervalLike{T} = Union{Interval{T}, IntervalBox{T}}
119+
NewtonLike = Union{Type{Newton}, Type{Krawczyk}}
119120

120121
"""
121-
roots(f, X, contractor, tol=1e-3)
122+
roots(f, X, contractor, tol=1e-3 ; deriv=nothing)
122123
123124
Uses a generic branch and prune routine to find in principle all isolated roots of a function
124125
`f:R^n → R^n` in a box `X`, or a vector of boxes.
@@ -128,53 +129,57 @@ Inputs:
128129
- `X`: `Interval` or `IntervalBox`
129130
- `contractor`: function that, when applied to the function `f`, determines
130131
the status of a given box `X`. It returns the new box and a symbol indicating
131-
the status. Current possible values are `Bisection` and `Newton`.
132+
the status. Current possible values are `Bisection`, `Newton` and `Krawczyk`
133+
- `deriv` ; explicit derivative of `f` for `Newton` and `Krawczyk`
132134
133135
"""
134136
# Contractor specific `roots` functions
135137
function roots(f, X::IntervalLike{T}, ::Type{Bisection}, tol::Float64=1e-3; deriv = nothing) where {T}
136138
branch_and_prune(X, Bisection(f), tol)
137139
end
138140

139-
function roots(f, X::Interval{T}, ::Type{Newton}, tol::Float64=1e-3; deriv = nothing) where {T}
141+
function roots(f, X::Interval{T}, C::NewtonLike, tol::Float64=1e-3; deriv = nothing) where {T}
140142

141143
if deriv == nothing
142144
deriv = x -> ForwardDiff.derivative(f, x)
143145
end
144146

145-
branch_and_prune(X, Newton(f, deriv), tol)
147+
branch_and_prune(X, C(f, deriv), tol)
146148
end
147149

148-
function roots(f, X::IntervalBox{T}, ::Type{Newton}, tol::Float64=1e-3; deriv = nothing) where {T}
150+
function roots(f, X::IntervalBox{T}, C::NewtonLike, tol::Float64=1e-3; deriv = nothing) where {T}
149151

150152
if deriv == nothing
151153
deriv = x -> ForwardDiff.jacobian(f, x)
152154
end
153155

154-
branch_and_prune(X, Newton(f, deriv), tol)
156+
branch_and_prune(X, C(f, deriv), tol)
155157
end
156158

157-
158159
roots(f, r::Root, contractor::Type{C}, tol::Float64=1e-3; deriv = nothing) where {C<:Contractor} = roots(f, r.interval, contractor, tol; deriv = deriv)
159160

161+
160162
# Acting on a Vector:
161163

162164
# TODO: Use previous status information about roots:
163-
roots(f, V::Vector{Root{T}}, contractor::Type{C}, tol::Float64=1e-3; deriv = nothing) where {T, C<:Contractor} = vcat(roots.(f, V, contractor, tol; deriv = deriv)...)
165+
roots(f, V::Vector{Root{T}}, contractor::Type{C}, tol::Float64=1e-3;
166+
deriv = nothing) where {T, C<:Contractor} = vcat(roots.(f, V, contractor, tol; deriv = deriv)...)
164167

165168

166169

167170
# Complex:
168171

169-
function roots(f, Xc::Complex{Interval{T}}, contractor::Type{C}, tol::Float64=1e-3) where {T, C<:Contractor}
172+
function roots(f, Xc::Complex{Interval{T}}, contractor::Type{C},
173+
tol::Float64=1e-3) where {T, C<:Contractor}
174+
170175
g = realify(f)
171176
Y = IntervalBox(reim(Xc))
172177
rts = roots(g, Y, contractor, tol)
173178

174179
return [Root(Complex(root.interval...), root.status) for root in rts]
175180
end
176181

177-
function roots(f, Xc::Complex{Interval{T}}, ::Type{Newton}, tol::Float64=1e-15;
182+
function roots(f, Xc::Complex{Interval{T}}, C::NewtonLike, tol::Float64=1e-3;
178183
deriv = nothing) where {T}
179184

180185
g = realify(f)
@@ -186,7 +191,7 @@ function roots(f, Xc::Complex{Interval{T}}, ::Type{Newton}, tol::Float64=1e-15;
186191
end
187192

188193
Y = IntervalBox(reim(Xc))
189-
rts = roots(g, Y, Newton, tol; deriv=g_prime)
194+
rts = roots(g, Y, C, tol; deriv=g_prime)
190195

191196
return [Root(Complex(root.interval...), root.status) for root in rts]
192197
end

test/roots.jl

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,38 @@
22
using IntervalArithmetic, IntervalRootFinding, StaticArrays
33
using Base.Test
44

5+
function all_unique(rts)
6+
all(root_status.(rts) .== :unique)
7+
end
8+
9+
function test_newtonlike(f, X, method, nsol, tol=1e-3; deriv=nothing)
10+
rts = roots(f, X, method)
11+
@test length(rts) == nsol
12+
@test all_unique(rts)
13+
@test rts == roots(f, X, method; deriv = deriv)
14+
end
15+
16+
newtonlike_methods = [Newton, Krawczyk]
17+
518
@testset "1D roots" begin
19+
# Default
620
rts = roots(sin, -5..5)
721
@test length(rts) == 3
8-
@test length(find(x->x==:unique, [root.status for root in rts])) == 3
22+
@test all_unique(rts)
923

24+
# Bisection
1025
rts = roots(sin, -5..6, Bisection)
1126
@test length(rts) == 3
27+
28+
# Refinement
1229
rts = roots(sin, rts, Newton)
13-
@test all(root.status == :unique for root in rts)
30+
@test all_unique(rts)
1431

15-
rts = roots(sin, -5..5, Newton)
16-
@test rts == roots(sin, -5..5, Newton; deriv = cos)
32+
for method in newtonlike_methods
33+
test_newtonlike(sin, -5..5, method, 3; deriv=cos)
34+
end
1735

36+
# Infinite interval
1837
rts = roots(x -> x^2 - 2, -..∞)
1938
@test length(rts) == 2
2039
end
@@ -25,19 +44,18 @@ end
2544
f(X) = f(X...)
2645
X = (-6..6) × (-6..6)
2746

47+
# Bisection
2848
rts = roots(f, X, Bisection, 1e-3)
2949
@test length(rts) == 4
3050

31-
rts = roots(f, rts, Newton)
32-
@test length(rts) == 2
33-
34-
rts = roots(f, X, Newton)
35-
@test rts == roots(f, X, Newton; deriv = xx -> ForwardDiff.jacobian(f, xx))
51+
for method in newtonlike_methods
52+
test_newtonlike(f, X, method, 2; deriv = xx -> ForwardDiff.jacobian(f, xx))
53+
end
3654

55+
# Infinite interval
3756
X = IntervalBox(-..∞, 2)
3857
rts = roots(f, X, Newton)
3958
@test length(rts) == 2
40-
4159
end
4260

4361

@@ -55,9 +73,13 @@ end
5573

5674
X = (-5..5)
5775
XX = IntervalBox(X, 3)
58-
rts = roots(g, XX, Newton)
59-
@test length(rts) == 4
60-
@test rts == roots(g, XX, Newton; deriv = xx -> ForwardDiff.jacobian(g, xx))
76+
77+
for method in newtonlike_methods
78+
rts = roots(g, XX, method)
79+
@test length(rts) == 4
80+
@test all_unique(rts)
81+
@test rts == roots(g, XX, method; deriv = xx -> ForwardDiff.jacobian(g, xx))
82+
end
6183
end
6284

6385
@testset "Stationary points" begin
@@ -66,34 +88,27 @@ end
6688
XX = IntervalBox(-5..6, 2)
6789
tol = 1e-5
6890

69-
rts = roots(gradf, XX, Newton, tol)
70-
@test length(rts) == 25
71-
@test rts == roots(gradf, XX, Newton, tol; deriv = xx -> ForwardDiff.jacobian(gradf, xx))
91+
for method in newtonlike_methods
92+
test_newtonlike(gradf, XX, method, 25, tol; deriv = xx -> ForwardDiff.jacobian(gradf, xx))
93+
end
7294
end
7395

7496
@testset "Complex roots" begin
7597
X = -5..5
7698
Xc = Complex(X, X)
7799
f(z) = z^3 - 1
78100

79-
rts = roots(f, Xc, Bisection, 1e-3)
80-
@test length(rts) == 7
81-
rts = roots(f, rts, Newton)
82-
@test length(rts) == 3
101+
# Default
83102
rts = roots(f, Xc)
84103
@test length(rts) == 3
85104

86-
rts = roots(f, Xc, Newton)
87-
rts2 = roots(f, Xc, Newton; deriv = z -> 3*z^2)
88-
89-
intervals = [reim(rt.interval) for rt in rts]
90-
intervals2 = [reim(rt.interval) for rt in rts2]
105+
# Bisection
106+
rts = roots(f, Xc, Bisection, 1e-3)
107+
@test length(rts) == 7
91108

92-
d = []
93-
for (I, I2) in zip(sort(intervals), sort(intervals2))
94-
append!(d, abs.(I .- I2))
109+
for method in newtonlike_methods
110+
test_newtonlike(f, Xc, method, 3; deriv = z -> 3*z^2)
95111
end
96-
@test all(d .< 1e-15)
97112
end
98113

99114
@testset "RootSearch interface" begin

0 commit comments

Comments
 (0)