Skip to content

Commit 47fd640

Browse files
gwaterdpsanders
authored andcommitted
Implement Base.Iterator interface for branch and prune routine (#60)
* implement Iterator interface for branch and prune routine * add example application for RootSearch iterator * fix implementations of optional RootSearch methods * add test cases for optional RootSearch methods * remove stray letter from iterator eltype method
1 parent beb47c1 commit 47fd640

File tree

3 files changed

+93
-31
lines changed

3 files changed

+93
-31
lines changed

examples/roots.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ rts = roots(f, Xc, Bisection)
2222
rts = roots(f, rts, Newton)
2323
rts = roots(f, Xc)
2424

25+
# track the number of working intervals during the iteration:
26+
f(x) = sin(x)
27+
contractor = Newton(f, x -> ForwardDiff.derivative(f, x))
28+
search = RootSearch(-10..10, contractor, 1e-3)
29+
for state in search
30+
print(length(state.working), " ")
31+
end
32+
println("done.")
33+
2534
# From R docs:
2635

2736
# https://www.rdocumentation.org/packages/pracma/versions/1.9.9/topics/broyden

src/roots.jl

Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,78 @@
11

22
import IntervalArithmetic: diam, isinterior
3+
import Base: start, next, done, copy, eltype, iteratorsize
34

4-
export branch_and_prune, Bisection, Newton
5+
export branch_and_prune, Bisection, Newton, RootSearch
6+
export start, next, done, copy, step!, eltype, iteratorsize
57

68
diam(x::Root) = diam(x.interval)
79

810
Base.size(x::Interval) = (1,)
911

1012
isinterior{N}(X::IntervalBox{N}, Y::IntervalBox{N}) = all(isinterior.(X, Y))
1113

14+
struct RootSearchState{T <: Union{Interval,IntervalBox}}
15+
working::Vector{T}
16+
outputs::Vector{Root{T}}
17+
end
18+
RootSearchState{T<:Union{Interval,IntervalBox}}(region::T) =
19+
RootSearchState([region], Root{T}[])
20+
21+
copy(state::RootSearchState) =
22+
RootSearchState(copy(state.working), copy(state.outputs))
1223

24+
"""
25+
RootSearch{R <: Union{Interval,IntervalBox}, S <: Contractor, T <: Real}
26+
27+
Type implementing the `Base.Iterator` interface to the branch and prune routine.
28+
Returns the `RootSearchState` at each iteration. Note: Each iteration mutates
29+
the `RootSearchState`. Use `copy(state::RootSearchState)` to create an
30+
independent instance if necessary.
31+
"""
32+
struct RootSearch{R <: Union{Interval,IntervalBox}, S <: Contractor, T <: Real}
33+
region::R
34+
contractor::S
35+
tolerance::T
36+
end
37+
38+
eltype{R, T <: RootSearch{R}}(::Type{T}) = RootSearchState{R}
39+
iteratorsize{T <: RootSearch}(::Type{T}) = Base.SizeUnknown()
40+
41+
function start(iter::RootSearch)
42+
state = RootSearchState(iter.region)
43+
sizehint!(state.outputs, 100)
44+
sizehint!(state.working, 1000)
45+
return state
46+
end
47+
48+
"""
49+
step!(state::RootSearchState, contractor, tolerance)
50+
51+
Progress `state` by treating one of its `working` regions. Note: `state.working`
52+
is always modified. If a root is found, it is added to `state.outputs`.
53+
"""
54+
function step!(state::RootSearchState, contractor, tolerance)
55+
X = pop!(state.working)
56+
status, output = contractor(X, tolerance)
57+
if status == :empty
58+
return nothing
59+
elseif status == :unique
60+
push!(state.outputs, Root(output, :unique))
61+
elseif diam(output) < tolerance
62+
push!(state.outputs, Root(output, :unknown))
63+
else # branch
64+
X1, X2 = bisect(X)
65+
push!(state.working, X1, X2)
66+
end
67+
return nothing
68+
end
69+
70+
function next(iter::RootSearch, state::RootSearchState)
71+
step!(state, iter.contractor, iter.tolerance)
72+
return state, state
73+
end
74+
75+
done(iter::RootSearch, state::RootSearchState) = isempty(state.working)
1376

1477
"""
1578
branch_and_prune(X, contract, tol=1e-3)
@@ -25,38 +88,13 @@ Inputs:
2588
2689
"""
2790
function branch_and_prune(X, contractor, tol=1e-3)
28-
working = [X]
29-
outputs = Root{typeof(X)}[]
30-
31-
sizehint!(outputs, 100)
32-
sizehint!(working, 1000)
33-
34-
while !isempty(working)
35-
# @show working
36-
X = pop!(working)
37-
38-
status, output = contractor(X, tol)
39-
40-
if status == :empty
41-
continue
42-
43-
elseif status == :unique
44-
push!(outputs, Root(output, :unique))
45-
46-
elseif diam(output) < tol
47-
push!(outputs, Root(output, :unknown))
48-
49-
else # branch
50-
X1, X2 = bisect(X)
51-
52-
push!(working, X1, X2)
53-
end
54-
end
55-
56-
return outputs
91+
iter = RootSearch(X, contractor, tol)
92+
local state
93+
# complete iteration
94+
for state in iter end
95+
return state.outputs
5796
end
5897

59-
6098
export recursively_branch_and_prune
6199

62100
function recursively_branch_and_prune(h, X, contractor=BisectionContractor, final_tol=1e-14)

test/roots.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,18 @@ end
9595
end
9696
@test all(d .< 1e-15)
9797
end
98+
99+
@testset "RootSearch interface" begin
100+
contractor = Newton(sin, cos)
101+
search = RootSearch(-10..10, contractor, 1e-3)
102+
state = start(search)
103+
104+
# check that original and copy are independent
105+
state_copy = copy(state)
106+
pop!(state_copy.working) # mutate copy
107+
@test length(state.working) != length(state_copy.working)
108+
109+
# cover optional iterator methods
110+
@test eltype(search) != Any
111+
@test_nowarn iteratorsize(search)
112+
end

0 commit comments

Comments
 (0)