From a1c1a503ba2f0ddf30c0d783aa790a15b2c61dea Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 14 Sep 2019 13:47:34 -0700 Subject: [PATCH 1/2] Define interval arithmetics --- src/IntervalSets.jl | 1 + src/interval.jl | 57 +++++++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 29 +++++++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/src/IntervalSets.jl b/src/IntervalSets.jl index 03734b5..9d2623d 100644 --- a/src/IntervalSets.jl +++ b/src/IntervalSets.jl @@ -3,6 +3,7 @@ module IntervalSets using Base: @pure import Base: eltype, convert, show, in, length, isempty, isequal, issubset, ==, hash, union, intersect, minimum, maximum, extrema, range, ⊇ +import Base.Broadcast: broadcasted using Statistics import Statistics: mean diff --git a/src/interval.jl b/src/interval.jl index f97ad7d..ae39c59 100644 --- a/src/interval.jl +++ b/src/interval.jl @@ -225,6 +225,63 @@ end ClosedInterval{T}(i::AbstractUnitRange{I}) where {T,I<:Integer} = ClosedInterval{T}(minimum(i), maximum(i)) ClosedInterval(i::AbstractUnitRange{I}) where {I<:Integer} = ClosedInterval{I}(minimum(i), maximum(i)) +for op in (+, -, *) + @eval begin + broadcasted(::typeof($op), i::AbstractInterval, x) = $op.(i, (x..x)) + broadcasted(::typeof($op), x, i::AbstractInterval) = $op.((x..x), i) + end +end + +broadcasted(::typeof(+), d1::AbstractInterval, d2::AbstractInterval) = + Interval{ + isleftclosed(d1) && isleftclosed(d2) ? :closed : :open, + isrightclosed(d1) && isrightclosed(d2) ? :closed : :open, + }((endpoints(d1) .+ endpoints(d2))...) + +broadcasted(::typeof(-), d1::AbstractInterval, d2::AbstractInterval) = + Interval{ + isleftclosed(d1) && isrightclosed(d2) ? :closed : :open, + isrightclosed(d1) && isleftclosed(d2) ? :closed : :open, + }((endpoints(d1) .- reverse(endpoints(d2)))...) + +@inline foldlargs(op, x) = x +@inline foldlargs(op, x1, x2, xs...) = foldlargs(op, op(x1, x2), xs...) +@inline extremaby(f, x, xs...) = + foldlargs((x, x), xs...) do (min, max), x + if f(min) > f(x) + (x, max) + elseif f(max) < f(x) + (min, x) + else + (min, max) + end + end + +_value(::Val{x}) where x = x + +function broadcasted(::typeof(*), d1::AbstractInterval, d2::AbstractInterval) + l1, r1 = endpoints(d1) + l2, r2 = endpoints(d2) + candidates = ( + (l1 * l2, Val(isleftclosed(d1) && isleftclosed(d2) ? :closed : :open)), + (l1 * r2, Val(isleftclosed(d1) && isrightclosed(d2) ? :closed : :open)), + (r1 * l2, Val(isrightclosed(d1) && isleftclosed(d2) ? :closed : :open)), + (r1 * r2, Val(isrightclosed(d1) && isrightclosed(d2) ? :closed : :open)), + ) + (left, L), (right, R) = extremaby(first, candidates...) + return Interval{_value(L), _value(R)}(left, right) +end + +broadcasted(::typeof(/), d1::AbstractInterval, d2::AbstractInterval) = + MethodError(broadcasted, (/, d1, d2)) +# Defining this to be a method error so that the `x` below is not of +# type `AbstractInterval`. + +broadcasted(::typeof(/), i::AbstractInterval, x) = + Interval{ + isleftclosed(i) ? :closed : :open, + isrightclosed(i) ? :closed : :open, + }((endpoints(i) ./ x)...) Base.promote_rule(::Type{Interval{L,R,T1}}, ::Type{Interval{L,R,T2}}) where {L,R,T1,T2} = Interval{L,R,promote_type(T1, T2)} diff --git a/test/runtests.jl b/test/runtests.jl index a5412df..c044f5e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -693,4 +693,33 @@ struct IncompleteInterval <: AbstractInterval{Int} end @test_throws ErrorException endpoints(I) @test_throws ErrorException closedendpoints(I) end + + @testset "Interval arithmetic" begin + @test (1..2) .+ 1 == 2..3 + @test 1 .+ (1..2) == 2..3 + @test (1..2) .+ (3..4) == 4..6 + @test (1..2) .+ (3..5) == 4..7 + @test Interval{:open,:closed}(1, 2) .+ Interval{:closed,:open}(3, 4) == + Interval{:open,:open}(4, 6) + + @test (1..2) .- 1 == 0..1 + @test 1 .- (1..2) == -1 .. 0 + @test (1..2) .- (3..4) == -3 .. -1 + @test (1..2) .- (3..5) == -4 .. -1 + @test Interval{:open,:closed}(1, 2) .- Interval{:closed,:open}(3, 4) == + Interval{:open,:closed}(-3, -1) + + @test 3 .* (1..2) == 3..6 + @test (1..2) .* 3 == 3..6 + @test (1 .. 2) .* (3 .. 4) == 3 .. 8 + @test (-1 .. 2) .* (3 .. 4) == -4 .. 8 + @test (1 .. -2) .* (3 .. 4) == -8 .. 4 + @test (1 .. 2) .* (-3 .. 4) == -6 .. 8 + @test (1 .. 2) .* (3 .. -4) == -8 .. 6 + @test Interval{:open,:closed}(1, 2) .* Interval{:closed,:open}(3, 4) == + Interval{:open,:open}(3, 8) + + @test (2..4) ./ 2 == 1..2 + @test Interval{:open,:closed}(2, 4) ./ 2 == Interval{:open,:closed}(1, 2) + end end From 0e16d731ccb726bb09ada2a715bfb8e38b4f0a46 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 19 Sep 2019 14:06:24 -0700 Subject: [PATCH 2/2] Fix: MethodError was not thrown --- src/interval.jl | 2 +- test/runtests.jl | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/interval.jl b/src/interval.jl index ae39c59..64eaf63 100644 --- a/src/interval.jl +++ b/src/interval.jl @@ -273,7 +273,7 @@ function broadcasted(::typeof(*), d1::AbstractInterval, d2::AbstractInterval) end broadcasted(::typeof(/), d1::AbstractInterval, d2::AbstractInterval) = - MethodError(broadcasted, (/, d1, d2)) + throw(MethodError(broadcasted, (/, d1, d2))) # Defining this to be a method error so that the `x` below is not of # type `AbstractInterval`. diff --git a/test/runtests.jl b/test/runtests.jl index c044f5e..3594e95 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -721,5 +721,6 @@ struct IncompleteInterval <: AbstractInterval{Int} end @test (2..4) ./ 2 == 1..2 @test Interval{:open,:closed}(2, 4) ./ 2 == Interval{:open,:closed}(1, 2) + @test_throws MethodError (2..4) ./ (1..2) end end