diff --git a/docs/src/lib/API.md b/docs/src/lib/API.md index 60396dbc29..5f18242f06 100644 --- a/docs/src/lib/API.md +++ b/docs/src/lib/API.md @@ -62,6 +62,7 @@ volume(::LazySet) ```@docs affine_map(::AbstractMatrix, ::LazySet, ::AbstractVector) +distance(::AbstractVector, ::LazySet) exponential_map(::AbstractMatrix, ::LazySet) ∈(::AbstractVector, ::LazySet) is_interior_point(::AbstractVector, ::LazySet) diff --git a/docs/src/lib/interfaces/AbstractHyperrectangle.md b/docs/src/lib/interfaces/AbstractHyperrectangle.md index 5e134983e1..74fb856475 100644 --- a/docs/src/lib/interfaces/AbstractHyperrectangle.md +++ b/docs/src/lib/interfaces/AbstractHyperrectangle.md @@ -118,7 +118,6 @@ CurrentModule = LazySets ``` ```@docs volume(::AbstractHyperrectangle) -distance(::AbstractVector, ::AbstractHyperrectangle{N}; ::Real=N(2)) where {N} ``` ```@meta CurrentModule = LazySets.API @@ -239,6 +238,7 @@ CurrentModule = LazySets.API * [`high`](@ref high(::LazySet, ::Int)) * [`low`](@ref low(::LazySet)) * [`low`](@ref low(::LazySet, ::Int)) +* [`distance`](@ref distance(::AbstractVector, ::LazySet)) * [`project`](@ref project(::LazySet, ::AbstractVector{Int})) * [`ρ`](@ref ρ(::AbstractVector, ::LazySet)) * [`cartesian_product`](@ref cartesian_product(::LazySet, ::LazySet)) diff --git a/docs/src/lib/sets/BallInf.md b/docs/src/lib/sets/BallInf.md index 83135396d4..0dd5c56cec 100644 --- a/docs/src/lib/sets/BallInf.md +++ b/docs/src/lib/sets/BallInf.md @@ -166,6 +166,7 @@ Inherited from [`AbstractZonotope`](@ref): Inherited from [`AbstractHyperrectangle`](@ref): * [`constraints_list`](@ref constraints_list(::AbstractHyperrectangle)) +* [`distance`](@ref distance(::AbstractVector, ::AbstractHyperrectangle)) * [`extrema`](@ref extrema(::AbstractHyperrectangle)) * [`extrema`](@ref extrema(::AbstractHyperrectangle, ::Int)) * [`generators`](@ref generators(::AbstractHyperrectangle)) diff --git a/docs/src/lib/sets/EmptySet.md b/docs/src/lib/sets/EmptySet.md index 05427c8b4c..1c627aeccb 100644 --- a/docs/src/lib/sets/EmptySet.md +++ b/docs/src/lib/sets/EmptySet.md @@ -76,6 +76,7 @@ Undocumented implementations: * [`vertices_list`](@ref vertices_list(::LazySet)) * [`vertices`](@ref vertices(::LazySet)) * [`volume`](@ref volume(::LazySet)) +* [`distance`](@ref distance(::AbstractVector, ::LazySet)) * [`exponential_map`](@ref exponential_map(::AbstractMatrix, ::LazySet)) * [`∈`](@ref ∈(::AbstractVector, ::LazySet)) * [`is_interior_point`](@ref is_interior_point(::AbstractVector, ::LazySet)) diff --git a/docs/src/lib/sets/HalfSpace.md b/docs/src/lib/sets/HalfSpace.md index b10ea7596c..102d8d79c2 100644 --- a/docs/src/lib/sets/HalfSpace.md +++ b/docs/src/lib/sets/HalfSpace.md @@ -77,7 +77,6 @@ rand(::Type{HalfSpace}) remove_redundant_constraints(::AbstractVector{<:HalfSpace}) remove_redundant_constraints!(::AbstractVector{<:HalfSpace}) tosimplehrep(::AbstractVector{<:HalfSpace}) -distance(::AbstractVector, ::HalfSpace) ``` ```@meta CurrentModule = LazySets.API @@ -170,6 +169,7 @@ Undocumented implementations: * [`isbounded`](@ref isbounded(::LazySet)) * [`isempty`](@ref isempty(::LazySet)) * [`isoperationtype`](@ref isoperationtype(::Type{LazySet})) +* [`distance`](@ref distance(::AbstractVector, ::LazySet)) * [`permute`](@ref permute(::LazySet, ::AbstractVector{Int})) ```@meta diff --git a/docs/src/lib/sets/Hyperplane.md b/docs/src/lib/sets/Hyperplane.md index 547e8233e7..5cc91b516b 100644 --- a/docs/src/lib/sets/Hyperplane.md +++ b/docs/src/lib/sets/Hyperplane.md @@ -67,7 +67,6 @@ CurrentModule = LazySets.HyperplaneModule ``` ```@docs rand(::Type{Hyperplane}) -distance(::AbstractVector, ::Hyperplane) ``` ```@meta CurrentModule = LazySets.API @@ -134,6 +133,7 @@ Undocumented implementations: * [`dim`](@ref dim(::LazySet)) * [`isempty`](@ref isempty(::LazySet)) * [`isoperationtype`](@ref isoperationtype(::Type{LazySet})) +* [`distance`](@ref distance(::AbstractVector, ::LazySet)) * [`project`](@ref project(::LazySet, ::AbstractVector{Int})) * [`isdisjoint`](@ref isdisjoint(::LazySet, ::LazySet)) diff --git a/docs/src/lib/sets/Hyperrectangle.md b/docs/src/lib/sets/Hyperrectangle.md index dfde02cb82..9fe483895a 100644 --- a/docs/src/lib/sets/Hyperrectangle.md +++ b/docs/src/lib/sets/Hyperrectangle.md @@ -146,7 +146,7 @@ Inherited from [`AbstractHyperrectangle`](@ref): * [`reflect`](@ref reflect(::AbstractHyperrectangle)) * [`vertices_list`](@ref vertices_list(::AbstractHyperrectangle)) * [`volume`](@ref volume(::AbstractHyperrectangle)) -* [`distance`](@ref distance(::AbstractVector, ::AbstractHyperrectangle{N}; ::Real=N(2)) where {N}) +* [`distance`](@ref distance(::AbstractVector, ::AbstractHyperrectangle)) * [`∈`](@ref ∈(::AbstractVector, ::AbstractHyperrectangle)) * [`project`](@ref project(::AbstractHyperrectangle, ::AbstractVector{Int})) * [`split`](@ref split(::AbstractHyperrectangle, ::AbstractVector{Int})) diff --git a/docs/src/lib/sets/Line.md b/docs/src/lib/sets/Line.md index 26595decf9..c146b3ca06 100644 --- a/docs/src/lib/sets/Line.md +++ b/docs/src/lib/sets/Line.md @@ -48,7 +48,6 @@ CurrentModule = LazySets.LineModule ``` ```@docs rand(::Type{Line}) -distance(::AbstractVector, ::Line; ::Real=2.0) ``` ```@meta CurrentModule = LazySets.API @@ -86,6 +85,7 @@ Undocumented implementations: * [`isbounded`](@ref isbounded(::LazySet)) * [`isempty`](@ref isempty(::LazySet)) * [`isoperationtype`](@ref isoperationtype(::Type{LazySet})) +* [`distance`](@ref distance(::AbstractVector, ::LazySet)) * [`project`](@ref project(::LazySet, ::AbstractVector{Int})) * [`ρ`](@ref ρ(::AbstractVector, ::LazySet)) * [`σ`](@ref σ(::AbstractVector, ::LazySet)) diff --git a/src/API/API.jl b/src/API/API.jl index 013b4d1ae0..867d8206c8 100644 --- a/src/API/API.jl +++ b/src/API/API.jl @@ -21,11 +21,11 @@ export low, norm, radius, rectify, reflect, surface, vertices_list, vertices, volume, # mixed set operations (typically with vectors or matrices) - affine_map, exponential_map, is_interior_point, linear_map, permute, - project, sample, scale!, scale, support_function, ρ, support_vector, σ, - translate!, translate, + affine_map, distance, exponential_map, is_interior_point, linear_map, + permute, project, sample, scale!, scale, support_function, ρ, + support_vector, σ, translate!, translate, # binary set operations - cartesian_product, difference, distance, exact_sum, intersection, + cartesian_product, difference, exact_sum, intersection, is_intersection_empty, isequivalent, ⊂, linear_combination, minkowski_difference, pontryagin_difference, minkowski_sum @@ -64,6 +64,7 @@ include("Unary/vertices.jl") include("Unary/volume.jl") include("Mixed/affine_map.jl") +include("Mixed/distance.jl") include("Mixed/exponential_map.jl") include("Mixed/in.jl") include("Mixed/is_interior_point.jl") diff --git a/src/API/Binary/distance.jl b/src/API/Binary/distance.jl index b63253911d..2f3a3ebefc 100644 --- a/src/API/Binary/distance.jl +++ b/src/API/Binary/distance.jl @@ -15,11 +15,11 @@ A real number representing the distance between `X` and `Y`. ### Notes -The standard distance is zero if the sets intersect and otherwise the ``p``-norm -of the shortest line segment between any pair of points. Formally, +The standard distance is zero if the sets intersect, and infinite if one of the sets is empty. +Otherwise, it is the ``p``-norm of the shortest line segment between any pair of points. Formally, ```math - \\inf_{x ∈ H_1, y ∈ H_2} \\{ d(x, y) \\}. + \\inf_{x ∈ X, y ∈ Y} \\{ d(x, y) \\}. ``` """ function distance(::LazySet, ::LazySet; p::Real=2) end diff --git a/src/API/Mixed/distance.jl b/src/API/Mixed/distance.jl new file mode 100644 index 0000000000..eab4a7607d --- /dev/null +++ b/src/API/Mixed/distance.jl @@ -0,0 +1,27 @@ +""" + distance(x::AbstractVector, X::LazySet; [p]::Real=2) + distance(X::LazySet, x::AbstractVector; [p]::Real=2) + +Compute the standard distance (induced by the ``p``-norm) between a point and a set. + +### Input + +- `x` -- point/vector +- `X` -- set +- `p` -- (optional; default: `2`) value of the ``p``-norm + +### Output + +A real number representing the distance between `x` and `X`. + +### Notes + +The standard distance is zero if the point lies inside the set, and infinite if the set is empty. +Otherwise, it is the ``p``-norm of the shortest line segment between the point and any other point +in the set. Formally, + +```math + \\inf_{y ∈ X} \\{ d(x, y) \\}. +``` +""" +function distance(::AbstractVector, ::LazySet; p::Real=2) end diff --git a/src/Interfaces/AbstractHyperrectangle.jl b/src/Interfaces/AbstractHyperrectangle.jl index fcdc910c3e..d14cab8a5f 100644 --- a/src/Interfaces/AbstractHyperrectangle.jl +++ b/src/Interfaces/AbstractHyperrectangle.jl @@ -591,24 +591,7 @@ function project(H::AbstractHyperrectangle, block::AbstractVector{Int}; return Hyperrectangle(πc, πr; check_bounds=false) end -""" - distance(x::AbstractVector, H::AbstractHyperrectangle{N}; - [p]::Real=N(2)) where {N} - -Compute the distance between a point `x` and a hyperrectangular set `H` with -respect to the given `p`-norm. - -### Input - -- `x` -- point/vector -- `H` -- hyperrectangular set - -### Output - -A scalar representing the distance between point `x` and hyperrectangle `H`. -""" -@commutative function distance(x::AbstractVector, H::AbstractHyperrectangle{N}; - p::Real=N(2)) where {N} +@commutative function distance(x::AbstractVector, H::AbstractHyperrectangle; p::Real=2) @assert length(x) == dim(H) "a vector of length $(length(x)) is " * "incompatible with a set of dimension $(dim(H))" @@ -631,6 +614,7 @@ A scalar representing the distance between point `x` and hyperrectangle `H`. if !outside # point is inside + N = promote_type(eltype(x), eltype(H)) return zero(N) end diff --git a/src/Sets/EmptySet/EmptySetModule.jl b/src/Sets/EmptySet/EmptySetModule.jl index ddd7bfc785..c52a714bae 100644 --- a/src/Sets/EmptySet/EmptySetModule.jl +++ b/src/Sets/EmptySet/EmptySetModule.jl @@ -4,6 +4,7 @@ using Reexport, Requires using ..LazySets: LazySet, ConvexSet, _witness_result_empty using Random: AbstractRNG, GLOBAL_RNG +using ReachabilityBase.Commutative: @commutative using ReachabilityBase.Comparison: _rtol using ReachabilityBase.Distribution: reseed! using ReachabilityBase.Iteration: EmptyIterator diff --git a/src/Sets/EmptySet/distance.jl b/src/Sets/EmptySet/distance.jl index 4c5f38f100..48f8d3033b 100644 --- a/src/Sets/EmptySet/distance.jl +++ b/src/Sets/EmptySet/distance.jl @@ -1,3 +1,14 @@ +# distance point <-> set + +@commutative function distance(x::AbstractVector, ∅::EmptySet; p::Real=2) + @assert length(x) == dim(∅) "incompatible dimensions $(length(x)) and $(dim(∅))" + + N = promote_type(eltype(x), eltype(∅)) + return N(Inf) +end + +# distance set <-> set + function distance(∅₁::EmptySet, ∅₂::EmptySet; p::Real=2.0) return _distance_emptyset(∅₁, ∅₂; p=p) end diff --git a/src/Sets/HalfSpace/distance.jl b/src/Sets/HalfSpace/distance.jl index 6bce85fe98..d6df9f2abd 100644 --- a/src/Sets/HalfSpace/distance.jl +++ b/src/Sets/HalfSpace/distance.jl @@ -1,19 +1,10 @@ -""" - distance(x::AbstractVector, H::HalfSpace) +@commutative function distance(x::AbstractVector, H::HalfSpace; p::Real=2) + @assert length(x) == dim(H) "incompatible dimensions $(length(x)) and $(dim(H))" -Compute the distance between point `x` and half-space `H` with respect to the -Euclidean norm. + if p != 2 + throw(ArgumentError("`distance` is only implemented for Euclidean norm")) + end -### Input - -- `x` -- vector -- `H` -- half-space - -### Output - -A scalar representing the distance between point `x` and half-space `H`. -""" -@commutative function distance(x::AbstractVector, H::HalfSpace) N = promote_type(eltype(x), eltype(H)) a, b = _normalize_halfspace(H, N(2)) return max(dot(x, a) - b, zero(N)) diff --git a/src/Sets/Hyperplane/distance.jl b/src/Sets/Hyperplane/distance.jl index 12682571b0..436e3d3b82 100644 --- a/src/Sets/Hyperplane/distance.jl +++ b/src/Sets/Hyperplane/distance.jl @@ -1,19 +1,10 @@ -""" - distance(x::AbstractVector, H::Hyperplane) +@commutative function distance(x::AbstractVector, H::Hyperplane; p::Real=2) + @assert length(x) == dim(H) "incompatible dimensions $(length(x)) and $(dim(H))" -Compute the distance between point `x` and hyperplane `H` with respect to the -Euclidean norm. + if p != 2 + throw(ArgumentError("`distance` is only implemented for Euclidean norm")) + end -### Input - -- `x` -- vector -- `H` -- hyperplane - -### Output - -A scalar representing the distance between point `x` and hyperplane `H`. -""" -@commutative function distance(x::AbstractVector, H::Hyperplane) N = promote_type(eltype(x), eltype(H)) a, b = _normalize_halfspace(H, N(2)) return abs(dot(x, a) - b) diff --git a/src/Sets/Line/distance.jl b/src/Sets/Line/distance.jl index 480a5e8384..9227fffb9a 100644 --- a/src/Sets/Line/distance.jl +++ b/src/Sets/Line/distance.jl @@ -1,21 +1,6 @@ -""" - distance(x::AbstractVector, L::Line; [p]::Real=2.0) +@commutative function distance(x::AbstractVector, L::Line; p::Real=2) + @assert length(x) == dim(L) "incompatible dimensions $(length(x)) and $(dim(L))" -Compute the distance between point `x` and the line with respect to the given -`p`-norm. - -### Input - -- `x` -- point/vector -- `L` -- line -- `p` -- (optional, default: `2.0`) the `p`-norm used; `p = 2.0` corresponds to - the usual Euclidean norm - -### Output - -A scalar representing the distance between `x` and the line `L`. -""" -@commutative function distance(x::AbstractVector, L::Line; p::Real=2.0) d = L.d # direction of the line t = dot(x - L.p, d) / dot(d, d) return distance(x, L.p + t * d; p=p) diff --git a/test/API.jl b/test/API.jl index 0fbec73712..697ec3bd55 100644 --- a/test/API.jl +++ b/test/API.jl @@ -43,7 +43,7 @@ end for f in (permute, project, translate!, translate) @test isnothing(f(X, v)) end - for f in (is_interior_point, ∈, support_function, ρ, support_vector, σ) + for f in (distance, is_interior_point, ∈, support_function, ρ, support_vector, σ) @test isnothing(f(v, X)) end M = hcat(1) diff --git a/test/Sets/EmptySet.jl b/test/Sets/EmptySet.jl index 6dc6e6889b..4ab91e5972 100644 --- a/test/Sets/EmptySet.jl +++ b/test/Sets/EmptySet.jl @@ -156,6 +156,13 @@ for N in [Float64, Rational{Int}, Float32] E2 = affine_map(ones(N, 3, 2), E, N[1, 1, 3]) @test E2 isa EmptySet{N} && dim(E2) == 3 + # distance (between point and set) + @test_throws AssertionError distance(E, N[0]) + x = N[0, 0] + for res in (distance(E, x), distance(E, x)) + @test res isa N && res == N(Inf) + end + # exponential_map / linear_map for f in (exponential_map, linear_map) @test_throws AssertionError f(ones(N, 2, 3), E) @@ -231,7 +238,7 @@ for N in [Float64, Rational{Int}, Float32] E2 = difference(B, E) @test E2 isa BallInf{N} && E2 == B - # distance + # distance (between sets) res = distance(E, E) @test res isa N && res == N(Inf) E2 = EmptySet{N}(3)