diff --git a/docs/src/lib/sets/Interval.md b/docs/src/lib/sets/Interval.md index 7686577da4..51d4009e02 100644 --- a/docs/src/lib/sets/Interval.md +++ b/docs/src/lib/sets/Interval.md @@ -26,13 +26,9 @@ chebyshev_center_radius(::Interval) constraints_list(::Interval) diameter(::Interval, ::Real=Inf) dim(::Interval) -high(::Interval) ∈(::AbstractVector, ::Interval) isflat(::Interval) linear_map(::AbstractMatrix, ::Interval) -low(::Interval) -min(::Interval) -max(::Interval) ngens(::Interval) radius_hyperrectangle(::Interval) radius_hyperrectangle(::Interval{N}, ::Int) where {N} @@ -52,6 +48,8 @@ isdisjoint(::Interval, ::Interval, ::Bool=false) minkowski_difference(::Interval, ::Interval) minkowski_sum(::Interval, ::Interval) plot_recipe(::Interval{N}, ::Any=zero(N)) where {N} +min(::Interval) +max(::Interval) -(::Interval, ::Interval) *(::Interval, ::Interval) ``` @@ -61,21 +59,65 @@ CurrentModule = LazySets.API ``` Undocumented implementations: +* [`affine_map`](@ref affine_map(::AbstractMatrix, ::LazySet, ::AbstractVector)) +* [`complement`](@ref complement(::LazySet)) +* [`exponential_map`](@ref exponential_map(::AbstractMatrix, ::LazySet)) +* [`extrema`](@ref extrema(::LazySet)) +* [`extrema`](@ref extrema(::LazySet, ::Int)) +* [`high`](@ref high(::LazySet, ::Int)) +* [`high`](@ref high(::LazySet)) +* [`isoperationtype`](@ref isoperationtype(::Type{<:LazySet})) +* [`low`](@ref low(::LazySet, ::Int)) +* [`low`](@ref low(::LazySet)) +* [`norm`](@ref norm(::LazySet, ::Real)) +* [`permute`](@ref permute(::LazySet, ::AbstractVector{Int})) +* [`project`](@ref project(::LazySet, ::AbstractVector{Int})) * [`radius`](@ref radius(::LazySet, ::Real)) +* [`volume`](@ref volume(::LazySet)) +* [`convex_hull`](@ref convex_hull(::LazySet, ::LazySet)) +* [`distance`](@ref distance(::LazySet, ::LazySet)) +* [`≈`](@ref ≈(::LazySet, ::LazySet)) +* [`isequivalent`](@ref isequivalent(::LazySet, ::LazySet)) +* [`⊂`](@ref ⊂(::LazySet, ::LazySet)) ```@meta CurrentModule = LazySets ``` Inherited from [`LazySet`](@ref): +* [`concretize`](@ref concretize(::LazySet)) +* [`constraints`](@ref constraints(::LazySet)) +* [`eltype`](@ref eltype(::Type{<:LazySet})) +* [`eltype`](@ref eltype(::LazySet)) +* [`isoperation`](@ref isoperation(::LazySet)) * [`singleton_list`](@ref singleton_list(::LazySet)) +* [`vertices`](@ref vertices(::LazySet)) +* [`is_interior_point`](@ref is_interior_point(::AbstractVector, ::LazySet)) +* [`sample`](@ref sample(::LazySet, ::Int)) +* [`==`](@ref ==(::LazySet, ::LazySet)) + +Inherited from [`AbstractPolyhedron`](@ref): +* [`is_polyhedral`](@ref is_polyhedral(::AbstractPolyhedron)) + +Inherited from [`ConvexSet`](@ref): + +```@meta +CurrentModule = LazySets.API +``` + +* [`linear_combination`](@ref linear_combination(::LazySet, ::LazySet)) + +```@meta +CurrentModule = LazySets +``` Inherited from [`AbstractPolytope`](@ref): * [`isbounded`](@ref isbounded(::AbstractPolytope)) -* [`isuniversal`](@ref isuniversal(::AbstractPolytope{N}, ::Bool=false) where {N}) +* [`isboundedtype`](@ref isboundedtype(::Type{<:AbstractPolytope})) Inherited from [`AbstractCentrallySymmetricPolytope`](@ref): * [`isempty`](@ref isempty(::AbstractCentrallySymmetricPolytope)) +* [`isuniversal`](@ref isuniversal(::AbstractCentrallySymmetricPolytope{N}, ::Bool=false) where {N}) Inherited from [`AbstractZonotope`](@ref): * [`order`](@ref order(::AbstractZonotope)) @@ -84,9 +126,8 @@ Inherited from [`AbstractZonotope`](@ref): Inherited from [`AbstractHyperrectangle`](@ref): * [`generators`](@ref generators(::AbstractHyperrectangle)) * [`genmat`](@ref genmat(::AbstractHyperrectangle)) -* [`high`](@ref high(::AbstractHyperrectangle, ::Int)) -* [`low`](@ref low(::AbstractHyperrectangle, ::Int)) -* [`norm`](@ref norm(::AbstractHyperrectangle, ::Real)) +* [`isconvextype`](@ref isconvextype(::Type{<:AbstractHyperrectangle})) +* [`cartesian_product`](@ref cartesian_product(::AbstractHyperrectangle, ::AbstractHyperrectangle)) Some additional functionality is available for `IntervalArithmetic.Interval`s: diff --git a/src/API/Binary/linear_combination.jl b/src/API/Binary/linear_combination.jl index 7781f7a7ac..5aa683d7b1 100644 --- a/src/API/Binary/linear_combination.jl +++ b/src/API/Binary/linear_combination.jl @@ -19,5 +19,7 @@ The linear combination of two sets ``X`` and ``Y`` is defined as ```math \\left\\{\\frac{1}{2}(1+λ)x + \\frac{1}{2}(1-λ)y \\mid x ∈ X, y ∈ Y, λ ∈ [-1, 1]\\right\\}. ``` + +If ``X`` and ``Y`` are convex, their linear combination is identical with their convex hull. """ function linear_combination(::LazySet, ::LazySet) end diff --git a/src/ConcreteOperations/linear_combination.jl b/src/ConcreteOperations/linear_combination.jl index 762cb581cf..e22edc91e1 100644 --- a/src/ConcreteOperations/linear_combination.jl +++ b/src/ConcreteOperations/linear_combination.jl @@ -39,3 +39,7 @@ function linear_combination(P1::SimpleSparsePolynomialZonotope, return SimpleSparsePolynomialZonotope(c, G, E) end + +function linear_combination(X::ConvexSet, Y::ConvexSet) + return convex_hull(X, Y) +end diff --git a/src/Interfaces/LazySet.jl b/src/Interfaces/LazySet.jl index 640be7fb1f..c6df0a83cd 100644 --- a/src/Interfaces/LazySet.jl +++ b/src/Interfaces/LazySet.jl @@ -487,6 +487,9 @@ end The default implementation applies the functions `exp` and `linear_map`. """ function exponential_map(M::AbstractMatrix, X::LazySet) + n = dim(X) + @assert size(M) == (n, n) "cannot apply an exponential map of dimension " * + "$(size(M)) to an $n-dimensional set" return linear_map(exp(M), X) end diff --git a/src/Sets/Interval/IntervalModule.jl b/src/Sets/Interval/IntervalModule.jl index b32ca3be44..1ebc741a40 100644 --- a/src/Sets/Interval/IntervalModule.jl +++ b/src/Sets/Interval/IntervalModule.jl @@ -12,11 +12,12 @@ using ReachabilityBase.Comparison using ReachabilityBase.Distribution: reseed! import IntervalArithmetic as IA -@reexport import ..API: an_element, center, constraints_list, diameter, dim, high, ∈, - isoperationtype, linear_map, low, rand, rectify, reflect, scale, ρ, σ, - translate, vertices_list, - difference, intersection, isdisjoint, ⊆, minkowski_difference, - minkowski_sum +@reexport import ..API: affine_map, an_element, center, complement, constraints_list, + convex_hull, diameter, dim, exponential_map, extrema, high, ∈, + isoperationtype, linear_map, low, norm, permute, project, rand, + rectify, reflect, scale, ρ, σ, translate, vertices_list, volume, + difference, distance, intersection, ≈, isdisjoint, isequivalent, + ⊂, ⊆, minkowski_difference, minkowski_sum @reexport import ..LazySets: chebyshev_center_radius, isflat, ngens, plot_recipe, radius_hyperrectangle, split import Base: convert, -, *, min, max @@ -25,20 +26,28 @@ export Interval include("Interval.jl") +include("affine_map.jl") include("an_element.jl") include("center.jl") include("chebyshev_center_radius.jl") +include("complement.jl") include("constraints_list.jl") include("convert.jl") +include("convex_hull.jl") include("diameter.jl") include("dim.jl") +include("exponential_map.jl") +include("extrema.jl") include("high.jl") include("in.jl") include("isflat.jl") +include("isoperationtype.jl") include("linear_map.jl") include("low.jl") -include("isoperationtype.jl") include("ngens.jl") +include("norm.jl") +include("permute.jl") +include("project.jl") include("radius.jl") include("radius_hyperrectangle.jl") include("rand.jl") @@ -50,10 +59,15 @@ include("support_vector.jl") include("support_function.jl") include("translate.jl") include("vertices_list.jl") +include("volume.jl") include("difference.jl") +include("distance.jl") include("intersection.jl") +include("isapprox.jl") include("isdisjoint.jl") +include("isequivalent.jl") +include("isstrictsubset.jl") include("issubset.jl") include("minkowski_difference.jl") include("minkowski_sum.jl") diff --git a/src/Sets/Interval/affine_map.jl b/src/Sets/Interval/affine_map.jl new file mode 100644 index 0000000000..df7cfa63c2 --- /dev/null +++ b/src/Sets/Interval/affine_map.jl @@ -0,0 +1,10 @@ +function affine_map(M, X::Interval, v::AbstractVector; kwargs...) + @assert size(M, 2) == 1 "cannot apply an affine map of dimension $(size(M, 2)) " * + "to an interval" + @assert size(M, 1) == length(v) "cannot apply an affine map of matrix dimension " * + "$(size(M, 1)) and translation dimension $(length(v))" + @inbounds if length(v) == 1 + return Interval(M[1, 1] * X.dat + v[1]) + end + return translate(linear_map(M, X; kwargs...), v) +end diff --git a/src/Sets/Interval/complement.jl b/src/Sets/Interval/complement.jl new file mode 100644 index 0000000000..9d4f919b1f --- /dev/null +++ b/src/Sets/Interval/complement.jl @@ -0,0 +1,6 @@ +function complement(X::Interval) + N = eltype(X) + L = HalfSpace(SingleEntryVector(1, 1, one(N)), min(X)) + H = HalfSpace(SingleEntryVector(1, 1, -one(N)), -max(X)) + return UnionSet(L, H) +end diff --git a/src/Sets/Interval/convex_hull.jl b/src/Sets/Interval/convex_hull.jl new file mode 100644 index 0000000000..46b39e5076 --- /dev/null +++ b/src/Sets/Interval/convex_hull.jl @@ -0,0 +1,3 @@ +function convex_hull(I1::Interval, I2::Interval) + return Interval(min(min(I1), min(I2)), max(max(I1), max(I2))) +end diff --git a/src/Sets/Interval/distance.jl b/src/Sets/Interval/distance.jl new file mode 100644 index 0000000000..bcd3392116 --- /dev/null +++ b/src/Sets/Interval/distance.jl @@ -0,0 +1,7 @@ +function distance(X::Interval, Y::Interval; p::Real=2) + d = max(min(X) - max(Y), min(Y) - max(X)) + if d < 0 + return zero(d) + end + return d +end diff --git a/src/Sets/Interval/exponential_map.jl b/src/Sets/Interval/exponential_map.jl new file mode 100644 index 0000000000..368cc68ad5 --- /dev/null +++ b/src/Sets/Interval/exponential_map.jl @@ -0,0 +1,6 @@ +function exponential_map(M::AbstractMatrix, X::Interval) + @assert size(M) == (1, 1) "cannot apply an exponential map of dimension " * + "$(size(M)) to an interval" + @inbounds e = exp(M[1, 1]) + return Interval(e * X.dat) +end diff --git a/src/Sets/Interval/extrema.jl b/src/Sets/Interval/extrema.jl new file mode 100644 index 0000000000..676819291d --- /dev/null +++ b/src/Sets/Interval/extrema.jl @@ -0,0 +1,12 @@ +# the implementations here are equivalent to the default implementation for +# `LazySet`, but more efficient than the more specific implementations for +# subtypes + +function extrema(X::Interval, i::Int) + @assert i == 1 "an interval has dimension 1, but the index is $i" + return (min(X), max(X)) +end + +function extrema(X::Interval) + return ([min(X)], [max(X)]) +end diff --git a/src/Sets/Interval/high.jl b/src/Sets/Interval/high.jl index 7b3dd7e382..ed40748d31 100644 --- a/src/Sets/Interval/high.jl +++ b/src/Sets/Interval/high.jl @@ -1,16 +1,8 @@ -""" - high(x::Interval) - -Return the higher coordinate of an interval set. - -### Input - -- `x` -- interval - -### Output +function high(X::Interval, i::Int) + @assert i == 1 "an interval has dimension 1, but the index is $i" + return max(X) +end -A vector with the higher coordinate of the interval. -""" -function high(x::Interval) - return [x.dat.hi] +function high(X::Interval) + return [max(X)] end diff --git a/src/Sets/Interval/isapprox.jl b/src/Sets/Interval/isapprox.jl new file mode 100644 index 0000000000..199eaaf835 --- /dev/null +++ b/src/Sets/Interval/isapprox.jl @@ -0,0 +1,3 @@ +function ≈(I1::Interval, I2::Interval) + return _isapprox(min(I1), min(I2)) && _isapprox(max(I1), max(I2)) +end diff --git a/src/Sets/Interval/isequivalent.jl b/src/Sets/Interval/isequivalent.jl new file mode 100644 index 0000000000..c0e2c7ad27 --- /dev/null +++ b/src/Sets/Interval/isequivalent.jl @@ -0,0 +1,3 @@ +function isequivalent(I1::Interval, I2::Interval) + return I1 ≈ I2 +end diff --git a/src/Sets/Interval/isstrictsubset.jl b/src/Sets/Interval/isstrictsubset.jl new file mode 100644 index 0000000000..ea4bcbe02a --- /dev/null +++ b/src/Sets/Interval/isstrictsubset.jl @@ -0,0 +1,11 @@ +function ⊂(X::Interval, Y::Interval, witness::Bool=false) + if min(X) < min(Y) || max(X) > max(Y) + return _witness_result_empty(witness, false, X, Y) + end + if min(X) > min(Y) + return witness ? (true, low(Y)) : true + elseif max(Y) > max(X) + return witness ? (true, high(Y)) : true + end + return _witness_result_empty(witness, false, X, Y) +end diff --git a/src/Sets/Interval/low.jl b/src/Sets/Interval/low.jl index 6f18a067d3..7d2012a92b 100644 --- a/src/Sets/Interval/low.jl +++ b/src/Sets/Interval/low.jl @@ -1,16 +1,8 @@ -""" - low(x::Interval) - -Return the lower coordinate of an interval set. - -### Input - -- `x` -- interval - -### Output +function low(X::Interval, i::Int) + @assert i == 1 "an interval has dimension 1, but the index is $i" + return min(X) +end -A vector with the lower coordinate of the interval. -""" -function low(x::Interval) - return [x.dat.lo] +function low(X::Interval) + return [min(X)] end diff --git a/src/Sets/Interval/norm.jl b/src/Sets/Interval/norm.jl new file mode 100644 index 0000000000..dc213f6edc --- /dev/null +++ b/src/Sets/Interval/norm.jl @@ -0,0 +1,3 @@ +function norm(X::Interval, p::Real=Inf) + return max(abs(min(X)), abs(max(X))) +end diff --git a/src/Sets/Interval/permute.jl b/src/Sets/Interval/permute.jl new file mode 100644 index 0000000000..b6b989562f --- /dev/null +++ b/src/Sets/Interval/permute.jl @@ -0,0 +1,4 @@ +function permute(X::Interval, p::AbstractVector{Int}) + @assert length(p) == 1 && p[1] == 1 "invalid permutation vector $p" + return X +end diff --git a/src/Sets/Interval/project.jl b/src/Sets/Interval/project.jl new file mode 100644 index 0000000000..5073c518f7 --- /dev/null +++ b/src/Sets/Interval/project.jl @@ -0,0 +1,4 @@ +function project(X::Interval, block::AbstractVector{Int}; kwargs...) + @assert length(block) == 1 && block[1] == 1 "invalid permutation vector $block" + return X +end diff --git a/src/Sets/Interval/volume.jl b/src/Sets/Interval/volume.jl new file mode 100644 index 0000000000..393a7475db --- /dev/null +++ b/src/Sets/Interval/volume.jl @@ -0,0 +1,3 @@ +function volume(X::Interval) + return max(X) - min(X) +end diff --git a/test/Sets/Interval.jl b/test/Sets/Interval.jl index 1dff143a32..4662245ad7 100644 --- a/test/Sets/Interval.jl +++ b/test/Sets/Interval.jl @@ -282,10 +282,79 @@ for N in [Float64, Float32, Rational{Int}] I13 = Interval(N(1), N(3)) I02 = Interval(N(0), N(2)) I24 = Interval(N(2), N(4)) + I03 = Interval(N(0), N(3)) + I14 = Interval(N(1), N(4)) for I in (I02, I24) res, w = ⊆(I13, I, true) @test !(⊆(I13, I)) && !res && w ∈ I13 && w ∉ I end res, w = ⊆(I13, I13, true) @test ⊆(I13, I13) && res && w == N[] + # isstrictsubset + for I in (I02, I24, I13) + res, w = ⊂(I13, I, true) + @test !(⊂(I13, I)) && !res && w == N[] + end + for I in (I03, I14) + res, w = ⊂(I13, I, true) + @test ⊂(I13, I) && res && w ∈ I && w ∉ I13 + end + + # permute + @test permute(x, [1]) == x + @test_throws AssertionError permute(x, Int[]) + @test_throws AssertionError permute(x, Int[2]) + @test_throws AssertionError permute(x, Int[1, 1]) + + # project + @test project(x, [1]) == x + @test_throws AssertionError project(x, Int[]) + @test_throws AssertionError project(x, Int[2]) + @test_throws AssertionError project(x, Int[1, 1]) + + # isapprox + @test x ≈ x ≈ translate(x, [1e-8]) + @test !(x ≈ translate(x, [1e-4])) + + # convex_hull & linear_combination + I1 = Interval(N(0), N(1)) + I2 = Interval(N(2), N(3)) + @test convex_hull(I1, I2) == linear_combination(I1, I2) == Interval(N(0), N(3)) + + # complement + C = complement(I2) + L = HalfSpace(N[1], N(2)) + H = HalfSpace(N[-1], N(-3)) + @test length(C) == 2 && ispermutation([C[1], C[2]], [L, H]) + + # low, high, extrema + @test extrema(I1) == (low(I1), high(I1)) == (N[0], N[1]) + @test extrema(I1, 1) == (low(I1, 1), high(I1, 1)) == (N(0), N(1)) + @test_throws AssertionError low(I1, 2) + @test_throws AssertionError high(I1, 2) + @test_throws AssertionError extrema(I1, 2) + + # norm + @test norm(I1) == N(1) + @test norm(Interval(N(-2), N(1))) == N(2) + + # volume + @test volume(I1) == 1 + @test volume(Interval(N(-2), N(1))) == N(3) + + # affine_map + M = hcat(N[2]) + v = N[-3] + @test affine_map(M, I1, v) == Interval(N(-3), N(-1)) + + # exponential_map + @test exponential_map(M, I1) == Interval(N(0), N(exp(N(2)))) + + # isequivalent + @test isequivalent(I1, I1) && !isequivalent(I1, I2) + + # distance + @test distance(I1, I2) == distance(I2, I1) == N(1) + I3 = Interval(N(1//2), N(2)) + @test distance(I1, I3) == distance(I2, I3) == distance(I1, I1) == N(0) end