Skip to content

Commit 14d97bf

Browse files
Kolarudpsanders
authored andcommitted
Explicitely give derivative to Newton contractor. (#51)
* Explicitely give derivative to Newton contractor. * Refactor and methods. * Refactor roots and branch_and_prune methods. * Fix notation and simplify Newton type
1 parent e235900 commit 14d97bf

File tree

3 files changed

+156
-125
lines changed

3 files changed

+156
-125
lines changed

src/branch_and_prune.jl

Lines changed: 116 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,89 @@ export branch_and_prune, Bisection, Newton
55

66
diam(x::Root) = diam(x.interval)
77

8-
98
Base.size(x::Interval) = (1,)
109

1110
isinterior{N}(X::IntervalBox{N}, Y::IntervalBox{N}) = all(isinterior.(X, Y))
1211

12+
# contractors:
13+
"""
14+
Contractor{F}
1315
16+
Abstract type for contractors.
1417
"""
15-
branch_and_prune(f, X, contractor, tol=1e-3)
18+
abstract type Contractor{F} end
1619

17-
Generic branch and prune routine for finding isolated roots of a function
18-
`f:R^n → R^n` in a box.
20+
export Bisection, Newton
1921

20-
Inputs:
21-
- `f`: function whose roots will be found
22-
- `X`: `Interval` or `IntervalBox`
23-
- `contractor`: function that, when applied to the function `f`, determines
24-
the status of a given box `X`. It returns the new box and a symbol indicating
25-
the status. Current possible values are `Bisection` and `Newton`.
22+
struct Bisection{F} <: Contractor{F}
23+
f::F
24+
end
2625

27-
"""
28-
function branch_and_prune(f, X, contractor, tol=1e-3)
26+
function (contractor::Bisection)(X)
27+
image = contractor.f(X)
2928

30-
input_dim = length(X)
31-
output_dim = length(X)
29+
if !(contains_zero(image))
30+
return :empty, X
31+
end
32+
33+
return :unknown, X
34+
end
3235

33-
# @show input_dim
34-
# @show output_dim
3536

36-
# if !(input_dim == output_dim)
37-
# throw(ArgumentError("Input dimension ($input_dim) and output dimension ($output_dim) must be the same."))
38-
# end
37+
struct Newton{F,FP,O} <: Contractor{F}
38+
f::F
39+
f_prime::FP
40+
op::O
41+
end
3942

40-
contract = contractor(Val{input_dim}, f)
43+
function Newton(f::Function, f_prime::Function)
44+
Newton(f, f_prime, N)
45+
end
4146

42-
# main algorithm:
47+
function Newton(f_prime::Function)
48+
return NewtonConstructor(f_prime)
49+
end
4350

51+
function (C::Newton)(X)
52+
# use Bisection contractor for this:
53+
if !(contains_zero(IntervalBox(C.f(X))))
54+
return :empty, X
55+
end
56+
57+
# given that have the Jacobian, can also do mean value form
58+
59+
NX = C.op(C.f, C.f_prime, X) X
60+
61+
isempty(NX) && return :empty, X
62+
63+
if NX X # isinterior; know there's a unique root inside
64+
NX = refine(X -> C.op(C.f, C.f_prime, X), NX)
65+
return :unique, NX
66+
end
67+
68+
return :unknown, NX
69+
end
70+
71+
72+
struct NewtonConstructor{FP}
73+
f_prime::FP
74+
end
75+
76+
77+
"""
78+
branch_and_prune(X, contract, tol=1e-3)
79+
80+
Generic branch and prune routine for finding isolated roots using the `contract`
81+
function as the contractor.
82+
83+
Inputs:
84+
- `X`: `Interval` or `IntervalBox`
85+
- `contractor`: function that determines the status of a given box `X`. It
86+
returns the new box and a symbol indicating the status. Current possible
87+
values are of type `Bisection` or `Newton`
88+
89+
"""
90+
function branch_and_prune(X, contractor, tol=1e-3)
4491
working = [X]
4592
outputs = Root{typeof(X)}[]
4693

@@ -51,7 +98,7 @@ function branch_and_prune(f, X, contractor, tol=1e-3)
5198
# @show working
5299
X = pop!(working)
53100

54-
status, output = contract(X)
101+
status, output = contractor(X)
55102

56103
if status == :empty
57104
continue
@@ -67,23 +114,11 @@ function branch_and_prune(f, X, contractor, tol=1e-3)
67114

68115
push!(working, X1, X2)
69116
end
70-
71117
end
72118

73119
return outputs
74120
end
75121

76-
branch_and_prune(f, X::Root, contractor, tol=1e-3) =
77-
branch_and_prune(f, X.interval, contractor, tol)
78-
79-
80-
function branch_and_prune(f, V::Vector{Root{T}}, contractor, tol=1e-3) where {T}
81-
reduce(append!, Root{T}[], [branch_and_prune(f, X.interval, contractor, tol) for X in V])
82-
end
83-
84-
function branch_and_prune(f, V::Vector{T}, contractor, tol=1e-3) where {T}
85-
reduce(append!, Root{T}[], [branch_and_prune(f, X, contractor, tol) for X in V])
86-
end
87122

88123
export recursively_branch_and_prune
89124

@@ -99,54 +134,12 @@ function recursively_branch_and_prune(h, X, contractor=BisectionContractor, fina
99134
return roots
100135
end
101136

102-
"""
103-
If the input interval is complex, treat `f` as a complex function, currently of one complex variable `z`.
104-
"""
105-
function branch_and_prune{T}(f, Xc::Complex{Interval{T}}, contractor, tol=1e-3)
106-
107-
g = realify(f)
108-
Y = IntervalBox(reim(Xc))
109-
110-
roots = branch_and_prune(g, Y, contractor, tol)
111-
112-
# @show roots
113-
114-
return [Root(Complex(root.interval...), root.status) for root in roots]
115-
end
116-
117-
118137

119138
contains_zero{T}(X::Interval{T}) = zero(T) X
120139
contains_zero(X::SVector) = all(contains_zero.(X))
121140
contains_zero(X::IntervalBox) = all(contains_zero(X[i]) for i in 1:length(X))
122141

123142

124-
# contractors:
125-
126-
abstract type Contractor{F} end
127-
128-
export Bisection, Newton
129-
130-
struct Bisection{F} <: Contractor{F}
131-
dimension::Int
132-
f::F
133-
end
134-
135-
Bisection{n}(::Type{Val{n}}, f) = Bisection(n, f)
136-
137-
138-
function (contractor::Bisection)(X)
139-
image = contractor.f(X)
140-
141-
if !(contains_zero(image))
142-
return :empty, X
143-
end
144-
145-
return :unknown, X
146-
end
147-
148-
149-
150143
"""
151144
Generic refine operation for Krawczyk and Newton.
152145
This assumes that it is already known that `X` contains a unique root.
@@ -166,49 +159,7 @@ function refine(op, X)
166159
end
167160

168161

169-
170-
struct Newton{F,FP,O} <: Contractor{F}
171-
dimension::Int
172-
f::F
173-
fp::FP
174-
op::O
175-
end
176-
177-
function Newton(::Type{Val{1}}, f::Function)
178-
f_prime = x -> ForwardDiff.derivative(f, x)
179-
Newton(1, f, f_prime, N)
180-
end
181-
182-
function Newton{n}(::Type{Val{n}}, f::Function)
183-
f_prime = x -> ForwardDiff.jacobian(f, x)
184-
Newton(n, f, f_prime, N)
185-
end
186-
187-
188-
function (C::Newton)(X)
189-
190-
# use Bisection contractor for this:
191-
if !(contains_zero(IntervalBox(C.f(X))))
192-
return :empty, X
193-
end
194-
195-
# given that have the Jacobian, can also do mean value form
196-
197-
198-
NX = C.op(C.f, C.fp, X) X
199-
200-
isempty(NX) && return :empty, X
201-
202-
203-
if NX X # isinterior; know there's a unique root inside
204-
NX = refine(X -> C.op(C.f, C.fp, X), NX)
205-
return :unique, NX
206-
end
207-
208-
209-
return :unknown, NX
210-
end
211-
162+
IntervalLike{T} = Union{Interval{T}, IntervalBox{T}}
212163

213164
"""
214165
roots(f, X, contractor, tol=1e-3)
@@ -221,9 +172,52 @@ Inputs:
221172
- `X`: `Interval` or `IntervalBox`
222173
- `contractor`: function that, when applied to the function `f`, determines
223174
the status of a given box `X`. It returns the new box and a symbol indicating
224-
the status. Current possible values are `Bisection` and `Newton`.
175+
the status. Current possible values are `Bisection`, `Newton` and
176+
`Newton(f_prime)` where `f_prime` is the derivative or jacobian of `f`.
225177
226178
"""
227-
roots{C<:Contractor}(f, X, contractor::Type{C}, tol::Float64=1e-3) = branch_and_prune(f, X, contractor, tol)
179+
# Contractor specific `roots` functions
180+
function roots(f, X::IntervalLike{T}, ::Type{Bisection}, tol::Float64=1e-3) where {T}
181+
branch_and_prune(X, Bisection(f), tol)
182+
end
183+
184+
function roots(f, X::Interval{T}, ::Type{Newton}, tol::Float64=1e-3) where {T}
185+
branch_and_prune(X, Newton(f, x -> ForwardDiff.derivative(f, x)), tol)
186+
end
187+
188+
function roots(f, X::IntervalBox{T}, ::Type{Newton}, tol::Float64=1e-3) where {T}
189+
branch_and_prune(X, Newton(f, x -> ForwardDiff.jacobian(f, x)), tol)
190+
end
191+
192+
function roots{T}(f, X::IntervalLike{T}, nc::NewtonConstructor, tol::Float64=1e-3)
193+
branch_and_prune(X, Newton(f, nc.f_prime), tol)
194+
end
195+
196+
# `roots` function for cases where `X` is not an `Interval` or `IntervalBox`
197+
function roots(f, V::Vector{Root{T}}, contractor::Type{C}, tol::Float64=1e-3) where {T, C<:Contractor}
198+
reduce(append!, Root{T}[], [roots(f, X.interval, contractor, tol) for X in V])
199+
end
200+
201+
function roots(f, V::Vector{T}, contractor::Type{C}, tol::Float64=1e-3) where {T, C<:Contractor}
202+
reduce(append!, Root{T}[], [roots(f, X, contractor, tol) for X in V])
203+
end
204+
205+
function roots(f, Xc::Complex{Interval{T}}, contractor::Type{C}, tol::Float64=1e-3) where {T, C<:Contractor}
206+
g = realify(f)
207+
Y = IntervalBox(reim(Xc))
208+
rts = roots(g, Y, contractor, tol)
209+
210+
return [Root(Complex(root.interval...), root.status) for root in rts]
211+
end
212+
213+
function roots(f, Xc::Complex{Interval{T}}, nc::NewtonConstructor, tol::Float64=1e-3) where {T}
214+
g = realify(f)
215+
g_prime = realify_derivative(nc.f_prime)
216+
Y = IntervalBox(reim(Xc))
217+
rts = roots(g, Y, Newton(g_prime), tol)
218+
219+
return [Root(Complex(root.interval...), root.status) for root in rts]
220+
end
228221

229-
roots(f, X, tol::Float64=1e-3) = branch_and_prune(f, X, Newton, tol)
222+
# Default
223+
roots(f, X, tol::Float64=1e-3) = roots(f, X, Newton, tol)

src/complex.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,16 @@ function realify(f)
3838
return g
3939

4040
end
41+
42+
"""
43+
Takes the derivative of a complex function and returns the real jacobian
44+
that implements it.
45+
"""
46+
function realify_derivative(fp)
47+
function g_jac(x)
48+
z = Compl(x[1], x[2])
49+
fpz = fp(z)
50+
SMatrix{2, 2}(fpz.re, fpz.im, -fpz.im, fpz.re)
51+
end
52+
return g_jac
53+
end

test/roots.jl

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,25 @@ using Base.Test
1111
@test length(rts) == 3
1212
rts = roots(sin, rts, Newton)
1313
@test all(root.status == :unique for root in rts)
14+
15+
rts = roots(sin, -5..5, Newton)
16+
@test rts == roots(sin, -5..5, Newton(x -> ForwardDiff.derivative(sin, x)))
1417
end
1518

1619

1720
@testset "2D roots" begin
1821
f(x, y) = SVector(x^2 + y^2 - 1, y - 2x)
1922
f(X) = f(X...)
23+
X = (-6..6) × (-6..6)
2024

21-
rts = roots(f, (-6..6) × (-6..6), Bisection, 1e-3)
25+
rts = roots(f, X, Bisection, 1e-3)
2226
@test length(rts) == 4
2327

2428
rts = roots(f, rts, Newton)
2529
@test length(rts) == 2
30+
31+
rts = roots(f, X, Newton)
32+
@test rts == roots(f, X, Newton(xx -> ForwardDiff.jacobian(f, xx)))
2633
end
2734

2835
@testset "Complex roots" begin
@@ -36,6 +43,16 @@ end
3643
@test length(rts) == 3
3744
rts = roots(f, Xc)
3845
@test length(rts) == 3
46+
47+
rts = roots(f, Xc, Newton)
48+
rts2 = roots(f, Xc, Newton(z -> 3*z^2))
49+
intervals = [reim(rt.interval) for rt in rts]
50+
intervals2 = [reim(rt.interval) for rt in rts2]
51+
d = []
52+
for (I, I2) in zip(sort(intervals), sort(intervals2))
53+
append!(d, abs.(I .- I2))
54+
end
55+
@test all(d .< 1e-15)
3956
end
4057

4158
# From R docs:
@@ -51,12 +68,19 @@ end
5168
end
5269

5370
X = (-5..5)
54-
rts = roots(g, IntervalBox(X, 3))
71+
XX = IntervalBox(X, 3)
72+
rts = roots(g, XX, Newton)
5573
@test length(rts) == 4
74+
@test rts == roots(g, XX, Newton(xx -> ForwardDiff.jacobian(g, xx)))
5675
end
5776

5877
@testset "Stationary points" begin
5978
f(xx) = ( (x, y) = xx; sin(x) * sin(y) )
60-
rts = roots((f), IntervalBox(-5..6, 2), Newton, 1e-5)
79+
gradf = (f)
80+
XX = IntervalBox(-5..6, 2)
81+
tol = 1e-5
82+
83+
rts = roots(gradf, XX, Newton, tol)
6184
@test length(rts) == 25
85+
@test rts == roots(gradf, XX, Newton(xx -> ForwardDiff.jacobian(gradf, xx)), tol)
6286
end

0 commit comments

Comments
 (0)