-
Notifications
You must be signed in to change notification settings - Fork 71
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
Constructing intervals #36
Comments
Thinking about it, I think julia> x = 1..2
[1, 2]
julia> x * 0.3 Here, |
However, we still want some way of making the smallest interval around 0.3. |
Thanks to bring this up again; perfect timing! The idea of this comment is to exploit dispatch rather than conversion. I think that such an approach may be better and "more julian". Just to recall it, we should have two types, one which is "safe" (in the sense of checks and correct rounding), and the other that is "fast". Both should be subtypes of From what you propose above, I think I like the idea of using different short-forms for each type, for example, |
Refactor of |
I'm playing with the idea above, or having a fast constructor for intervals ( |
On master: julia> using BenchmarkTools
julia> @btime Interval(1, 2)
7.994 ns (0 allocations: 0 bytes) On julia> @btime Interval(1, 2) # without checks
1.393 ns (0 allocations: 0 bytes)
julia> @btime interval(1, 2) # with checks
8.908 ns (0 allocations: 0 bytes) We should actually make a proper benchmark suite. |
(Edited above to add |
Note that any benchmark that includes arithmetic will currently be dominated by those arithmetic operations, since they are currently really slow! That is what the |
Having said that, it is no longer clear to me that removing the checks from the constructor really has that much influence on the overall time for a real calculation, which will indeed be dominated by other costs. |
For example, julia> f1(N) = sum(Interval(1) / (i^2) for i in 1:N)
f1 (generic function with 1 method)
julia> f2(N) = sum(interval(1) / (i^2) for i in 1:N)
f2 (generic function with 1 method)
julia> f1(10)
[1.54976, 1.54977]
julia> f2(10)
[1.54976, 1.54977]
julia> @btime f1(100)
3.627 μs (3 allocations: 96 bytes)
[1.63498, 1.63499]
julia> @btime f2(100)
4.399 μs (3 allocations: 96 bytes)
[1.63498, 1.63499] There is a 20% increase in time due to the extra checks in the constructor. |
This is what I get: julia> @btime Interval(pi, 7pi)
11.307 ns (0 allocations: 0 bytes)
[3.14159, 21.9912]
julia> @btime @interval(pi, 7pi)
1.122 μs (7 allocations: 192 bytes)
[3.14159, 21.9912]
julia> @btime FastInterval(pi, 7pi)
8.488 ns (0 allocations: 0 bytes)
IntervalArithmetic.FastInterval{Float64}(3.141592653589793, 21.991148575128552)
Let me advance further and then I push it to GitHub. |
Among others, I still have to adapt the arithmetic definitions... |
There is some time being spent in The fact that you have to adapt the arithmetic definitions is one reason why this doesn't seem to me to be an attractive direction to take! |
The julia> @btime Interval(1, 2)
11.307 ns (0 allocations: 0 bytes)
[1, 2]
julia> @btime FastInterval(1, 2)
8.487 ns (0 allocations: 0 bytes)
IntervalArithmetic.FastInterval{Float64}(1.0, 2.0)
julia> @btime @interval(1, 2)
756.105 ns (5 allocations: 128 bytes)
[1, 2] |
Regarding adapting the operations, the idea is to exploit that both |
Not related to julia> @btime convert(Interval{Float64}, 0.1)
303.565 ns (3 allocations: 128 bytes)
[0.0999999, 0.100001]
julia> @btime @interval 0.1
675.000 ns (5 allocations: 192 bytes)
[0.0999999, 0.100001]
|
Yes, that is interesting -- it seems to be due to this: julia> @macroexpand @interval 0.1
:((IntervalArithmetic.Interval)((IntervalArithmetic.convert)((IntervalArithmetic.Interval){IntervalArithmetic.parameters.precision_type}, 0.1))) i.e. it must do a dynamic dispatch (i.e. at runtime) to |
Note the following (with
|
I just pushed the Here are some benchmarks using Julia v0.6.0-rc2, as we outlined above: julia> using IntervalArithmetic, BenchmarkTools
julia> @btime Interval(1, 2)
11.307 ns (0 allocations: 0 bytes)
[1, 2]
julia> @btime FastInterval(1, 2) # no checks
8.173 ns (0 allocations: 0 bytes)
IntervalArithmetic.FastInterval{Float64}(1.0, 2.0)
julia> f1(N) = sum(Interval(1) / (i^2) for i in 1:N)
f1 (generic function with 1 method)
julia> f2(N) = sum(FastInterval(1) / (i^2) for i in 1:N)
f2 (generic function with 1 method)
julia> setformat(:full)
6
julia> f1(10)
Interval(1.5497677311665397, 1.549767731166541)
julia> f2(10)
IntervalArithmetic.FastInterval{Float64}(1.5497677311665408, 1.5497677311665408)
julia> @btime f1(100) # uses Interval; slow
17.795 μs (3 allocations: 96 bytes)
Interval(1.634983900184882, 1.6349839001849027)
julia> @btime f2(100) # uses FastInterval
2.876 μs (3 allocations: 96 bytes)
IntervalArithmetic.FastInterval{Float64}(1.6349839001848923, 1.6349839001848923) |
I was just about to write that I didn't understand why |
The amazing thing about the fastrounding branch is that doing correct rounding is almost as fast as no rounding at all. There is one bug left to fix before it is ready. |
You are right, it is doing no rounding. The subtlety is related to |
You could change |
I thought that changing |
I rewrote |
I don't like my solution to include rounding, but it's up there. Currently, I get the following julia> using IntervalArithmetic, BenchmarkTools
julia> @btime Interval(1, 2)
11.307 ns (0 allocations: 0 bytes)
[1, 2]
julia> @btime FastInterval(1, 2)
8.173 ns (0 allocations: 0 bytes)
IntervalArithmetic.FastInterval{Float64}(1.0, 2.0)
julia> f1(N) = sum(Interval(1) / (i^2) for i in 1:N)
f1 (generic function with 1 method)
julia> f2(N) = sum(FastInterval(1) / (i^2) for i in 1:N)
f2 (generic function with 1 method)
julia> setformat(:full)
6
julia> f1(10), f2(10)
(Interval(1.5497677311665397, 1.549767731166541), IntervalArithmetic.FastInterval{Float64}(1.5497677311665397, 1.549767731166541))
julia> @btime f1(100) # uses Interval; slow
17.632 μs (3 allocations: 96 bytes)
Interval(1.634983900184882, 1.6349839001849027)
julia> @btime f2(100) # uses FastInterval
16.094 μs (3 allocations: 96 bytes)
IntervalArithmetic.FastInterval{Float64}(1.634983900184882, 1.6349839001849027) From this, it seems to me that the whole issue is not so much to check the bounds, but to round faster. And currently we don't do it. |
Yes, I completely agree -- this is all pointless with the current slooow rounding. (Actually that branch is not the problem; there are two remaining bugs to fix in the packages that it will depend on.) |
I partially agree with you: I think there are two points here:
I'll take a look on #25. |
I'm not sure what you mean by "not quite". (Or by "revelant".) |
What I mean by "not quite" is that using |
You can try mixing your branch and
|
While I managed to merge both branches (only needed to correct two conflicts) I am having problems with FastRounding (actually on Nemo)... |
I'm not sure what you mean by "on nemo". I guess youve done Pkg.add("FastRounding")? |
I'm not sure what was I doing wrong... Anyway, here are the new benchmarks: julia> using IntervalArithmetic, BenchmarkTools
julia> setrounding(Interval, :errorfree)
julia> @btime Interval(1, 2)
10.992 ns (0 allocations: 0 bytes)
[1, 2]
julia> @btime FastInterval(1, 2)
8.173 ns (0 allocations: 0 bytes)
IntervalArithmetic.FastInterval{Float64}(1.0, 2.0)
julia> f1(N) = sum(Interval(1) / (i^2) for i in 1:N)
f1 (generic function with 1 method)
julia> f2(N) = sum(FastInterval(1) / (i^2) for i in 1:N)
f2 (generic function with 1 method)
julia> setformat(:full)
6
julia> f1(10), f2(10)
(Interval(1.5497677311665397, 1.549767731166541), IntervalArithmetic.FastInterval{Float64}(1.5497677311665397, 1.549767731166541))
julia> @btime f1(100)
8.307 μs (3 allocations: 96 bytes)
Interval(1.634983900184882, 1.6349839001849027)
julia> @btime f2(100)
5.963 μs (3 allocations: 96 bytes)
IntervalArithmetic.FastInterval{Float64}(1.634983900184882, 1.6349839001849027)
So, All our tests pass; ITF1788 tests also pass, except for two tests related with |
That's a bit disappointing -- I was expecting a better speedup. Those two tests are from a (very) subtle bug that is on my radar: JeffreySarnoff/FastRounding.jl#11 |
Although the code for |
These function provide something like the inverse for addition and subtraction; I included them because they are in the standard. The complicated programing was the way I found to get the tests passing... |
I just pushed another commit where |
I can make a new PR with the sole correction of |
That would be great, yes! And |
Yes: I will prepare a separate PR to handle this. |
Is there anything left to do here @lbenet ? |
I must admit that I completely forgot about this discussion. I think the discussion is quite outdated and a lot has changed since we were talking about this: For instance, we have now Maybe your question was related to |
The current behaviour is now roughly what was sketched at the start of the thread and is documented in the docs. |
I suggest that we should have the following mechanisms for constructing intervals:
interval(a, b)
(with a smalli
): the currentInterval(a, b)
-- includes the checks at the moment of construction; constructs an interval from exactly the given value ofa
to the given value ofb
; fasta..b
: Gives a guaranteed enclosure of the interval, including botha
andb
; see Speed upa..b
#34; fast.Does not try to do anything clever with floats; just uses
prevfloat
andnextfloat
convert(Interval, a)
: Gives the smallest possible enclosure of the numbera
as an interval; does fancy things (rationalize
orparse(string(x))
) to interpret e.g.0.1
as the real number0.1
; slow (same as currently)@interval(a, b)
: The most general method, but slowest.a ± b
: Only for numbersa
andb
(not intervals)cc @lbenet
The text was updated successfully, but these errors were encountered: