Skip to content

Commit

Permalink
Speed up set difference of interval boxes (#456)
Browse files Browse the repository at this point in the history
* sped up set difference of interval boxes

* updated version with less allocations

* removed old functions

* implemented first suggestion (use static arrays and setindex)

* added one test with infinite interval boxes

* bumped patch version
  • Loading branch information
lucaferranti authored Apr 22, 2021
1 parent e0333ae commit 24a0b66
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 99 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "IntervalArithmetic"
uuid = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253"
repo = "https://github.com/JuliaIntervals/IntervalArithmetic.jl.git"
version = "0.18.0"
version = "0.18.1"

[deps]
CRlibm = "96374032-68de-5a5b-8d9e-752f78720389"
Expand Down
109 changes: 25 additions & 84 deletions src/multidim/setdiff.jl
Original file line number Diff line number Diff line change
@@ -1,38 +1,23 @@
"""
Returns a list of pairs (interval, label)
label is 1 if the interval is *excluded* from the setdiff
label is 0 if the interval is included in the setdiff
label is -1 if the intersection of the two intervals was empty
_setdiff(x::Interval{T}, y::Interval{T})
Computes the set difference x\\y and always returns a tuple of two intervals.
If the set difference is only one interval or is empty, then the returned tuple contains 1
or 2 empty intervals.
"""
function labelled_setdiff(x::Interval{T}, y::Interval{T}) where T
function _setdiff(x::Interval{T}, y::Interval{T}) where T
intersection = x y

isempty(intersection) && return [(x, -1)]
intersection == x && return [(x, 1)]
isempty(intersection) && return (x, emptyinterval(T))
intersection == x && return (emptyinterval(T), emptyinterval(T)) # x is subset of y; setdiff is empty

x.lo == intersection.lo && return [(intersection, 1), (Interval(intersection.hi, x.hi), 0)]
x.hi == intersection.hi && return [(intersection, 1), (Interval(x.lo, intersection.lo), 0)]
x.lo == intersection.lo && return (Interval(intersection.hi, x.hi), emptyinterval(T))
x.hi == intersection.hi && return (Interval(x.lo, intersection.lo), emptyinterval(T))

return [(y, 1),
(Interval(x.lo, y.lo), 0),
(Interval(y.hi, x.hi), 0)]
return (Interval(x.lo, y.lo), Interval(y.hi, x.hi))

end

# function setdiff{N,T}(A::IntervalBox{N,T}, B::IntervalBox{N,T})
# X = [labelled_setdiff(a,b) for (a, b) in zip(A, B)]
# lengths = map(length, X)
# index = ones(N)
#
# # index[j] represents which set we are looking at in direction j
#``
#
# while index[1] <= N
# current_direction = 1
# current_piece = [ X[1][index[1]] ]
#
# end
# end

"""
setdiff(A::IntervalBox{N,T}, B::IntervalBox{N,T})
Expand All @@ -44,65 +29,21 @@ Algorithm: Start from the total overlap (in all directions);
expand each direction in turn.
"""
function setdiff(A::IntervalBox{N,T}, B::IntervalBox{N,T}) where {N,T}
X = [labelled_setdiff(a, b) for (a, b) in zip(A.v, B.v)]
# ordered such that the first in each is the excluded interval

first = [ i[1] for i in X ]
labels = [i[2] for i in first]

any(labels .== -1) && return [A] # no overlap

# @assert all(labels .== 1)

excluded = [i[1] for i in first]

result_list = IntervalBox{N,T}[]

for dimension in N:-1:1
for which in X[dimension][2:end]
excluded[dimension] = which[1]
push!(result_list,
IntervalBox(excluded[1:dimension]..., A[dimension+1:N]...))
intersection = A B
isempty(intersection) && return [A]

result_list = fill(IntervalBox(emptyinterval(T), N), 2*N)
offset = 0
x = A.v
@inbounds for i = 1:N
tmp = _setdiff(A[i], B[i])
@inbounds for j = 1:2
x = setindex(x, tmp[j], i)
result_list[offset+j] = IntervalBox{N, T}(x)
end
offset += 2
x = setindex(x, intersection[i], i)
end

result_list

filter!(!isempty, result_list)
end


# """
# setdiff(A::IntervalBox{2,T}, B::IntervalBox{2,T})
#
# Returns a vector of `IntervalBox`es that are in the set difference `A \ B`,
# i.e. the set of `x` that are in `A` but not in `B`.
# """
# function setdiff{T}(A::IntervalBox{2,T}, B::IntervalBox{2,T})
# X = labelled_setdiff(A[1], B[1])
# Y = labelled_setdiff(A[2], B[2])
#
# results_list = typeof(A)[]
#
# for (x, label) in X
# label == -1 && return [A] # intersection in one direction empty, so total intersection empty
#
# if label == 0
# push!(results_list, IntervalBox(x, A[2]))
# continue
# end
#
# # label is 1 here, so there is some intersection in the x direction
# for (y, label) in Y
# label == -1 && return [A]
#
# if label == 0
# push!(results_list, IntervalBox(x, y))
# continue
# end
#
# # label == 1: exclude this box since all labels are 1
# end
# end
#
# return results_list
# end
34 changes: 20 additions & 14 deletions test/multidim_tests/multidim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -108,34 +108,34 @@ end
@testset "setdiff for IntervalBox" begin
X = IntervalBox(2..4, 3..5)
Y = IntervalBox(3..5, 4..6)
@test setdiff(X, Y) == [ IntervalBox(3..4, 3..4),
IntervalBox(2..3, 3..5) ]
@test Set(setdiff(X, Y)) == Set([ IntervalBox(3..4, 3..4),
IntervalBox(2..3, 3..5) ])

@test setdiff(X.v, Y) == [ IntervalBox(3..4, 3..4),
IntervalBox(2..3, 3..5) ]
@test Set(setdiff(X.v, Y)) == Set([ IntervalBox(3..4, 3..4),
IntervalBox(2..3, 3..5) ])

@test setdiff(X, Y.v) == [ IntervalBox(3..4, 3..4),
IntervalBox(2..3, 3..5) ]
@test Set(setdiff(X, Y.v)) == Set([ IntervalBox(3..4, 3..4),
IntervalBox(2..3, 3..5) ])

X = IntervalBox(2..5, 3..6)
Y = IntervalBox(-10..10, 4..5)
@test setdiff(X, Y) == [ IntervalBox(2..5, 3..4),
IntervalBox(2..5, 5..6) ]
@test Set(setdiff(X, Y)) == Set([ IntervalBox(2..5, 3..4),
IntervalBox(2..5, 5..6) ])


X = IntervalBox(2..5, 3..6)
Y = IntervalBox(4..6, 4..5)
@test setdiff(X, Y) == [ IntervalBox(4..5, 3..4),
@test Set(setdiff(X, Y)) == Set([ IntervalBox(4..5, 3..4),
IntervalBox(4..5, 5..6),
IntervalBox(2..4, 3..6) ]
IntervalBox(2..4, 3..6) ])


X = IntervalBox(2..5, 3..6)
Y = IntervalBox(3..4, 4..5)
@test setdiff(X, Y) == [ IntervalBox(3..4, 3..4),
@test Set(setdiff(X, Y)) == Set([ IntervalBox(3..4, 3..4),
IntervalBox(3..4, 5..6),
IntervalBox(2..3, 3..6),
IntervalBox(4..5, 3..6) ]
IntervalBox(4..5, 3..6) ])


X = IntervalBox(2..5, 3..6)
Expand All @@ -150,14 +150,20 @@ end

X = IntervalBox(1..4, 3..6, 7..10)
Y = IntervalBox(2..3, 4..5, 8..9)
@test setdiff(X, Y) == [ IntervalBox(2..3, 4..5, 7..8),
@test Set(setdiff(X, Y)) == Set([ IntervalBox(2..3, 4..5, 7..8),
IntervalBox(2..3, 4..5, 9..10),
IntervalBox(2..3, 3..4, 7..10),
IntervalBox(2..3, 5..6, 7..10),
IntervalBox(1..2, 3..6, 7..10),
IntervalBox(3..4, 3..6, 7..10) ]
IntervalBox(3..4, 3..6, 7..10) ])


X = IntervalBox(-Inf..Inf, 1..2)
Y = IntervalBox(1..2, -1..1.5)

@test Set(setdiff(X, Y)) == Set([IntervalBox(-Inf..1, 1..2),
IntervalBox(2..Inf, 1..2),
IntervalBox(1..2, 1.5..2)])
end

@testset "mid, diam, × for IntervalBox" begin
Expand Down

2 comments on commit 24a0b66

@dpsanders
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/35021

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.18.1 -m "<description of version>" 24a0b667fa1711387ea3ac4c2af175e1e93f23d6
git push origin v0.18.1

Please sign in to comment.