-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow control over the way the intervals are selected to be processed #72
Conversation
src/roots.jl
Outdated
outputs::Vector{Root{T}} | ||
struct RootSearchState{V, VR} | ||
working::V # Should be a container of the form CONT{T} | ||
outputs::VR # Should ba a container of root of the form CONT{Root{T}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's necessary for outputs
to be a fancy container -- isn't a standard list (vector) good enough, since there won't be complicated manipulations here?
This is interesting, thanks! Note that as far as I know, standard Julia arrays can actually be thought of as |
I don't think you can actually do this in 0.7 either? |
Despite the claim of better performance by the
Well I think I saw something along that line, but I can't find it again so I may have just dreamed of it. |
Okay after checking it may rather be that it can be implemented using |
src/roots.jl
Outdated
sizehint!(state.outputs, 100) | ||
sizehint!(state.working, 1000) | ||
return state | ||
return iter.state |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This implementation does not conform to the Base.Iterator
specification: start(iter)
should return the initial state of the iterator; not the last state that iterator was in. I can imagine this leading to problems down the line when someone tries to restart a RootSearch
after it has run for a few iterations. Ideally, start()
should create a fresh state. (Resetting state would probably also lead to some confusion because of side effects.)
More generally, let's discuss why |
The reason I put struct SearchStrategy
constructor::Function
retrieve!::Function
store!::Function
end
If I get it correctly, this should solve your concern about the Finally implementing a |
@Kolaru That sounds like an elegant solution to my concerns, thanks! |
Codecov Report
@@ Coverage Diff @@
## master #72 +/- ##
==========================================
- Coverage 64.97% 64.42% -0.55%
==========================================
Files 11 12 +1
Lines 551 565 +14
==========================================
+ Hits 358 364 +6
- Misses 193 201 +8
Continue to review full report at Codecov.
|
I finally got the time to implement this. The example now looks much simpler: using IntervalArithmetic
using IntervalRootFinding
using DataStructures
f(x) = x^2 - 2
region = -4..4
contractor = Newton(f, x -> ForwardDiff.derivative(f, x))
tol = 1e-10
strat = SearchStrategy(R -> Deque{R}(), push!, shift!)
rootsearch = RootSearch(region, contractor, strat, tol)
global state
for state in rootsearch end
println(state.outputs) @gwater In your implementation you defined the |
@Kolaru I think it could because we lose type stability in the loop. Can you check what I think the |
I'm thinking of something like
(untested) |
@gwater Looks like your last message and my commit just crossed, but we arrived at (mostly) the same conclusion. Except from the nomenclature, the main difference is that I defined the Concerning |
343f7de
to
5fccaac
Compare
So here is the changes brought by the last commit:
Note that the ability to give a Quick benchmark using the Smiley 2.2 example show no difference in performance between this PR and current master. |
I finally got a round to play with this version and I think it works fine (as the tests demonstrate). It's very cool to see how depth-first vs breadth-first affects the number of working intervals. I noticed a few issues, which I will annotate in the code. |
@@ -119,100 +54,162 @@ IntervalLike{T} = Union{Interval{T}, IntervalBox{T}} | |||
NewtonLike = Union{Type{Newton}, Type{Krawczyk}} | |||
|
|||
""" | |||
roots(f, X, contractor, tol=1e-3) | |||
roots(f, deriv, X, contractor, tol=1e-3) | |||
roots(f, X, contractor=Newton, strategy=BreadthFirstSearch, tol=1e-15) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In line L80 the same value (for tolerance) is defined as a constant. It might be worth moving the constant up to reuse it here (and on the following line).
is always modified. If a root is found, it is added to `state.outputs`. | ||
""" | ||
function step!(state::RootSearchState, contractor, searchstrat, tolerance) | ||
X = searchstrat.retrieve!(state.working) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line introduces a type instability because julia
cannot infer the return type of retrieve!()
for some reason. Three options:
- put the remaining code into a separate function which is then fully inferred
- ignore the issue for now because the heavy lifting is done in
contractor()
which should be fully inferrible - somehow fix type inferrence for
retrieve!()
(any ideas?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For your convenience; this is how I tested option 1:
function step_inner!(X, working, outputs, store!, contractor, tolerance)
status, output = contractor(X, tolerance)
if status == :empty
return nothing
elseif status == :unique
store!(outputs, Root(output, :unique))
elseif diam(output) < tolerance
store!(outputs, Root(output, :unknown))
else # branch
X1, X2 = bisect(X)
store!(working, X1, X2)
end
return nothing
end
"""
step!(state::RootSearchState, contractor, tolerance)
Progress `state` by treating one of its `working` regions. Note: `state.working`
is always modified. If a root is found, it is added to `state.outputs`.
"""
function step!(state::RootSearchState, contractor, searchstrat, tolerance)
X = searchstrat.retrieve!(state.working)
step_inner!(X, state.working, state.outputs, searchstrat.store!, contractor,
tolerance)
return nothing
end
Here, step!()
is still unstable but step_inner!()
is fully inferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch! Thinking about it, this is probably why each function has its own type: parametrization on function seems to allow to infer returned type in struct body. Consider the following
struct A
func::Function
end
struct B{F}
func::F
end
a = A(x -> x^2)
b = B(x -> x^2)
test(cont, x) = cont.func(x)
In the case of test(a, 3.2)
the return type can not be inferred, but for test(b, 3.2)
it can. So this is the way to go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, functions inside struct
s must "always" be parametrized.
The changes also affect one example in
|
Allow to have type predictability for the full iteration cycle.
- Introduce default strategies - Rework interface to allow using them - Changes break tests for complex Krawczyk method which expectation has been lowered
5fccaac
to
3eadb00
Compare
Sorry for the delay in getting to this. I will have a detailed look. |
This looks great, thanks a lot! Is it ready to merge? Two general comments:
|
It seems to me that the Or, rather, it should be some kind of tree object that reflects how a subinterval(box) is related to a bigger interval(box). |
Thanks ! It is ready to merged if we are still targeting Julia 0.6, but it is still untested with the recent revision of
As far as I can see, this would come down to promoting the
Actually, the
That's interesting, the output container could indeed convey more informations. Since intervals and intervals boxes are spatially arrange relative to each other, representing them as a network of neighbors could be more useful than representing them as a tree (for example to help merge interval boxes with no solution when plotting them). Do you have any particular use case in mind for a tree-like structure ? |
Superseeded by #97. |
This PR introduces the
SearchStrategy
type that store the methods used to store and retrieve the intervals inroots
and modifies theRootSearchState
type to allow for any type of containers. This should in principle allow to have any strategy and thus lay the foundation of what is needed to fix #22 .Here is a small working example using a
Deque
to implement a bread-first strategy:One problem is that due to the current type system (it will change in 0.7 if I get it correctly) it is not easy to write something like
For now the
RootSearchState
is therefore more general than needed and no check is performed to see if the two fields are compatible.Another problem is that the syntax is yet very verbose. One of the reason for that is that the
outputs
set must be provided. In principle, its type can be inferred from the type of theworking
set, howeverDataStructures
do not provide a nice way of doing as far as I know (Base
provides thesimilar
function which does exactly that). I do not know what is the best strategy for us, requesting both fields or implementing thesimilar
function (or something similar... ^^') for supported container types.I post this here as a work in progress as I would appreciate feedback and be sure that we can agree on this design before going further to implement what is missing:
roots
function