From 47cf4705d09a1e7a44ea5af6cbb26bb7cab1da20 Mon Sep 17 00:00:00 2001 From: schillic Date: Sat, 26 Jan 2019 17:57:46 +0100 Subject: [PATCH 1/6] add AbstractPolyhedron interface --- docs/src/lib/interfaces.md | 25 ++++++++++++++-------- src/AbstractPolyhedron.jl | 32 +++++++++++++++++++++++++++++ src/AbstractPolyhedron_functions.jl | 0 src/AbstractPolytope.jl | 10 ++++----- src/HPolyhedron.jl | 6 ++++-- src/HalfSpace.jl | 4 ++-- src/Hyperplane.jl | 4 ++-- src/LazySet.jl | 8 ++------ src/LazySets.jl | 2 ++ src/Line.jl | 4 ++-- 10 files changed, 68 insertions(+), 27 deletions(-) create mode 100644 src/AbstractPolyhedron.jl create mode 100644 src/AbstractPolyhedron_functions.jl diff --git a/docs/src/lib/interfaces.md b/docs/src/lib/interfaces.md index 2138fd7c68..094f7ca6b5 100644 --- a/docs/src/lib/interfaces.md +++ b/docs/src/lib/interfaces.md @@ -100,10 +100,19 @@ an_element(::AbstractCentrallySymmetric{N}) where {N<:Real} isempty(::AbstractCentrallySymmetric) ``` -## Polytope +## Polyhedron -A polytope has finitely many vertices (*V-representation*) resp. facets -(*H-representation*). +A polyhedron has finitely many facets (*H-representation*) and is not +necessarily bounded. + +```@docs +AbstractPolyhedron +``` + +### Polytope + +A polytope is a bounded set with finitely many vertices (*V-representation*) +resp. facets (*H-representation*). Note that there is a special interface combination [Centrally symmetric polytope](@ref). @@ -122,7 +131,7 @@ RecipesBase.apply_recipe(::Dict{Symbol,Any}, ::AbstractPolytope) RecipesBase.apply_recipe(::Dict{Symbol,Any}, ::Vector{S}) where {S<:AbstractPolytope} ``` -### Polygon +#### Polygon A polygon is a two-dimensional polytope. @@ -137,7 +146,7 @@ dim(P::AbstractPolygon) linear_map(::AbstractMatrix{N}, P::AbstractPolygon{N}) where {N<:Real} ``` -#### HPolygon +##### HPolygon An HPolygon is a polygon in H-representation (or constraint representation). @@ -160,7 +169,7 @@ vertices_list(::AbstractHPolygon{N}, ::Bool=false, ::Bool=true) where {N<:Real} isbounded(::AbstractHPolygon, ::Bool=true) ``` -### Centrally symmetric polytope +#### Centrally symmetric polytope A centrally symmetric polytope is a combination of two other interfaces: [Centrally symmetric set](@ref) and [Polytope](@ref). @@ -177,7 +186,7 @@ an_element(::AbstractCentrallySymmetricPolytope{N}) where {N<:Real} isempty(::AbstractCentrallySymmetricPolytope) ``` -#### Hyperrectangle +##### Hyperrectangle A hyperrectangle is a special centrally symmetric polytope with axis-aligned facets. @@ -199,7 +208,7 @@ high(::AbstractHyperrectangle{N}) where {N<:Real} low(::AbstractHyperrectangle{N}) where {N<:Real} ``` -#### Singleton +##### Singleton A singleton is a special hyperrectangle consisting of only one point. diff --git a/src/AbstractPolyhedron.jl b/src/AbstractPolyhedron.jl new file mode 100644 index 0000000000..db82762e86 --- /dev/null +++ b/src/AbstractPolyhedron.jl @@ -0,0 +1,32 @@ +export AbstractPolyhedron + +""" + AbstractPolyhedron{N<:Real} <: LazySet{N} + +Abstract type for polyhedral sets, i.e., sets with finitely many flat facets, or +equivalently, sets defined as an intersection of a finite number of halfspaces. + +### Notes + +Every concrete `AbstractPolyhedron` must define the following functions: +- `constraints_list(::AbstractPolyhedron{N})::Vector{LinearConstraint{N}}` -- + return a list of all facet constraints + +```jldoctest +julia> subtypes(AbstractPolyhedron) +5-element Array{Any,1}: + AbstractPolytope + HPolyhedron + HalfSpace + Hyperplane + Line +``` +""" +abstract type AbstractPolyhedron{N<:Real} <: LazySet{N} end + + +# --- common AbstractPolyhedron functions --- + + +# To account for the compilation order, functions are defined in the file +# AbstractPolyhedron_functions.jl diff --git a/src/AbstractPolyhedron_functions.jl b/src/AbstractPolyhedron_functions.jl new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/AbstractPolytope.jl b/src/AbstractPolytope.jl index bbc074597c..e241f1924d 100644 --- a/src/AbstractPolytope.jl +++ b/src/AbstractPolytope.jl @@ -7,11 +7,11 @@ export AbstractPolytope, isempty """ - AbstractPolytope{N<:Real} <: LazySet{N} + AbstractPolytope{N<:Real} <: AbstractPolyhedron{N} -Abstract type for polytopic sets, i.e., sets with finitely many flat facets, or -equivalently, sets defined as an intersection of a finite number of halfspaces, -or equivalently, sets with finitely many vertices. +Abstract type for polytopic sets, i.e., bounded sets with finitely many flat +facets, or equivalently, bounded sets defined as an intersection of a finite +number of halfspaces, or equivalently, bounded sets with finitely many vertices. ### Notes @@ -30,7 +30,7 @@ julia> subtypes(AbstractPolytope) VPolytope ``` """ -abstract type AbstractPolytope{N<:Real} <: LazySet{N} end +abstract type AbstractPolytope{N<:Real} <: AbstractPolyhedron{N} end # --- common AbstractPolytope functions --- diff --git a/src/HPolyhedron.jl b/src/HPolyhedron.jl index e583ed2e3a..40d4007e11 100644 --- a/src/HPolyhedron.jl +++ b/src/HPolyhedron.jl @@ -20,7 +20,7 @@ export HPolyhedron, constrained_dimensions """ - HPolyhedron{N<:Real} <: LazySet{N} + HPolyhedron{N<:Real} <: AbstractPolyhedron{N} Type that represents a convex polyhedron in H-representation. @@ -28,7 +28,7 @@ Type that represents a convex polyhedron in H-representation. - `constraints` -- vector of linear constraints """ -struct HPolyhedron{N<:Real} <: LazySet{N} +struct HPolyhedron{N<:Real} <: AbstractPolyhedron{N} constraints::Vector{LinearConstraint{N}} end @@ -53,8 +53,10 @@ HPolyhedron{N}(A::AbstractMatrix{N}, b::AbstractVector{N}) where {N<:Real} = HPo # convenience union type const HPoly{N} = Union{HPolytope{N}, HPolyhedron{N}} + # --- LazySet interface functions --- + """ dim(P::HPoly{N})::Int where {N<:Real} diff --git a/src/HalfSpace.jl b/src/HalfSpace.jl index 5c5553be76..d1167fdd60 100644 --- a/src/HalfSpace.jl +++ b/src/HalfSpace.jl @@ -10,7 +10,7 @@ export HalfSpace, LinearConstraint, linear_map """ - HalfSpace{N<:Real} <: LazySet{N} + HalfSpace{N<:Real} <: AbstractPolyhedron{N} Type that represents a (closed) half-space of the form ``a⋅x ≤ b``. @@ -28,7 +28,7 @@ julia> HalfSpace([0, -1.], 0.) HalfSpace{Float64}([0.0, -1.0], 0.0) ``` """ -struct HalfSpace{N<:Real} <: LazySet{N} +struct HalfSpace{N<:Real} <: AbstractPolyhedron{N} a::AbstractVector{N} b::N end diff --git a/src/Hyperplane.jl b/src/Hyperplane.jl index e6af438c6d..aff708d6ec 100644 --- a/src/Hyperplane.jl +++ b/src/Hyperplane.jl @@ -6,7 +6,7 @@ export Hyperplane, an_element """ - Hyperplane{N<:Real} <: LazySet{N} + Hyperplane{N<:Real} <: AbstractPolyhedron{N} Type that represents a hyperplane of the form ``a⋅x = b``. @@ -24,7 +24,7 @@ julia> Hyperplane([0, 1.], 0.) Hyperplane{Float64}([0.0, 1.0], 0.0) ``` """ -struct Hyperplane{N<:Real} <: LazySet{N} +struct Hyperplane{N<:Real} <: AbstractPolyhedron{N} a::AbstractVector{N} b::N end diff --git a/src/LazySet.jl b/src/LazySet.jl index cb0ba26b72..6b04550123 100644 --- a/src/LazySet.jl +++ b/src/LazySet.jl @@ -34,9 +34,9 @@ Every concrete `LazySet` must define the following functions: ```jldoctest julia> subtypes(LazySet) -19-element Array{Any,1}: +15-element Array{Any,1}: AbstractCentrallySymmetric - AbstractPolytope + AbstractPolyhedron CacheMinkowskiSum CartesianProduct CartesianProductArray @@ -45,12 +45,8 @@ julia> subtypes(LazySet) EmptySet ExponentialMap ExponentialProjectionMap - HPolyhedron - HalfSpace - Hyperplane Intersection IntersectionArray - Line LinearMap MinkowskiSum MinkowskiSumArray diff --git a/src/LazySets.jl b/src/LazySets.jl index 526255c242..372cd082c2 100644 --- a/src/LazySets.jl +++ b/src/LazySets.jl @@ -20,7 +20,9 @@ include("macros.jl") # Abstract set types # ================== include("LazySet.jl") +include("AbstractPolyhedron.jl") include("HalfSpace.jl") # must be here to make LinearConstraint available +include("AbstractPolyhedron_functions.jl") include("AbstractPolytope.jl") include("AbstractCentrallySymmetric.jl") include("AbstractCentrallySymmetricPolytope.jl") diff --git a/src/Line.jl b/src/Line.jl index 30894a044d..bb03cf2cce 100644 --- a/src/Line.jl +++ b/src/Line.jl @@ -6,7 +6,7 @@ export Line, an_element """ - Line{N<:Real, VN<:AbstractVector{N}} <: LazySet{N} + Line{N<:Real, VN<:AbstractVector{N}} <: AbstractPolyhedron{N} Type that represents a line in 2D of the form ``a⋅x = b`` (i.e., a special case of a `Hyperplane`). @@ -25,7 +25,7 @@ julia> Line([1., 1.], 1.) Line{Float64,Array{Float64,1}}([1.0, 1.0], 1.0) ``` """ -struct Line{N<:Real, VN<:AbstractVector{N}} <: LazySet{N} +struct Line{N<:Real, VN<:AbstractVector{N}} <: AbstractPolyhedron{N} a::VN b::N From 705986d2b9c594b060deb52931b0387eb8120d91 Mon Sep 17 00:00:00 2001 From: schillic Date: Sat, 26 Jan 2019 18:13:14 +0100 Subject: [PATCH 2/6] outsource 'in' method --- docs/src/lib/interfaces.md | 6 ++++++ docs/src/lib/representations.md | 4 +++- src/AbstractPolyhedron_functions.jl | 31 +++++++++++++++++++++++++++++ src/HPolyhedron.jl | 31 ----------------------------- 4 files changed, 40 insertions(+), 32 deletions(-) diff --git a/docs/src/lib/interfaces.md b/docs/src/lib/interfaces.md index 094f7ca6b5..bbdbf9ef40 100644 --- a/docs/src/lib/interfaces.md +++ b/docs/src/lib/interfaces.md @@ -109,6 +109,12 @@ necessarily bounded. AbstractPolyhedron ``` +This interface defines the following functions: + +```@docs +∈(::AbstractVector{N}, ::AbstractPolyhedron{N}) where {N<:Real} +``` + ### Polytope A polytope is a bounded set with finitely many vertices (*V-representation*) diff --git a/docs/src/lib/representations.md b/docs/src/lib/representations.md index 4f8100b7d3..d6ee9d367f 100644 --- a/docs/src/lib/representations.md +++ b/docs/src/lib/representations.md @@ -437,7 +437,6 @@ The following methods are shared between `HPolytope` and `HPolyhedron`. dim(::HPoly{N}) where {N<:Real} ρ(::AbstractVector{N}, ::HPoly{N}) where {N<:Real} σ(::AbstractVector{N}, ::HPoly{N}) where {N<:Real} -∈(::AbstractVector{N}, ::HPoly{N}) where {N<:Real} addconstraint!(::HPoly{N}, ::LinearConstraint{N}) where {N<:Real} constraints_list(::HPoly{N}) where {N<:Real} tohrep(::HPoly{N}) where {N<:Real} @@ -455,6 +454,9 @@ Inherited from [`LazySet`](@ref): * [`radius`](@ref radius(::LazySet, ::Real)) * [`diameter`](@ref diameter(::LazySet, ::Real)) +Inherited from [`AbstractPolyhedron`](@ref): +* [`∈`](@ref ∈(::AbstractVector{N}, ::AbstractPolyhedron{N}) where {N<:Real}) + #### Polytopes in constraint representation The following methods are specific for `HPolytope`. diff --git a/src/AbstractPolyhedron_functions.jl b/src/AbstractPolyhedron_functions.jl index e69de29bb2..9d1bee0475 100644 --- a/src/AbstractPolyhedron_functions.jl +++ b/src/AbstractPolyhedron_functions.jl @@ -0,0 +1,31 @@ +import Base.∈ + +""" + ∈(x::AbstractVector{N}, P::AbstractPolyhedron{N})::Bool where {N<:Real} + +Check whether a given point is contained in a polyhedron. + +### Input + +- `x` -- point/vector +- `P` -- polyhedron + +### Output + +`true` iff ``x ∈ P``. + +### Algorithm + +This implementation checks if the point lies inside each defining half-space. +""" +function ∈(x::AbstractVector{N}, P::AbstractPolyhedron{N})::Bool where {N<:Real} + @assert length(x) == dim(P) "a $(length(x))-dimensional point cannot be " * + "an element of a $(dim(P))-dimensional set" + + for c in constraints_list(P) + if dot(c.a, x) > c.b + return false + end + end + return true +end diff --git a/src/HPolyhedron.jl b/src/HPolyhedron.jl index 40d4007e11..855f610d86 100644 --- a/src/HPolyhedron.jl +++ b/src/HPolyhedron.jl @@ -217,37 +217,6 @@ function isbounded(P::HPolyhedron)::Bool return isbounded_unit_dimensions(P) end -""" - ∈(x::AbstractVector{N}, P::HPoly{N})::Bool where {N<:Real} - -Check whether a given point is contained in a polyhedron in constraint -representation. - -### Input - -- `x` -- vector with the coordinates of the point -- `P` -- polyhedron in constraint representation - -### Output - -`true` iff ``x ∈ P``. - -### Algorithm - -This implementation checks if the point lies on the outside of each hyperplane. -This is equivalent to checking if the point lies in each half-space. -""" -function ∈(x::AbstractVector{N}, P::HPoly{N})::Bool where {N<:Real} - @assert length(x) == dim(P) - - for c in P.constraints - if dot(c.a, x) > c.b - return false - end - end - return true -end - """ rand(::Type{HPolyhedron}; [N]::Type{<:Real}=Float64, [dim]::Int=2, [rng]::AbstractRNG=GLOBAL_RNG, [seed]::Union{Int, Nothing}=nothing From bfb2f17d0e485b6667f023289327e0cb72f5c1fd Mon Sep 17 00:00:00 2001 From: schillic Date: Sat, 26 Jan 2019 18:16:26 +0100 Subject: [PATCH 3/6] outsource 'constrained_dimensions' method --- docs/src/lib/interfaces.md | 1 + docs/src/lib/representations.md | 2 +- src/AbstractPolyhedron_functions.jl | 32 +++++++++++++++++++++++++++++ src/HPolyhedron.jl | 30 +-------------------------- 4 files changed, 35 insertions(+), 30 deletions(-) diff --git a/docs/src/lib/interfaces.md b/docs/src/lib/interfaces.md index bbdbf9ef40..bcc892a289 100644 --- a/docs/src/lib/interfaces.md +++ b/docs/src/lib/interfaces.md @@ -113,6 +113,7 @@ This interface defines the following functions: ```@docs ∈(::AbstractVector{N}, ::AbstractPolyhedron{N}) where {N<:Real} +constrained_dimensions(::AbstractPolyhedron{N}) where {N<:Real} ``` ### Polytope diff --git a/docs/src/lib/representations.md b/docs/src/lib/representations.md index d6ee9d367f..128353192d 100644 --- a/docs/src/lib/representations.md +++ b/docs/src/lib/representations.md @@ -456,6 +456,7 @@ Inherited from [`LazySet`](@ref): Inherited from [`AbstractPolyhedron`](@ref): * [`∈`](@ref ∈(::AbstractVector{N}, ::AbstractPolyhedron{N}) where {N<:Real}) +* [`constrained_dimensions`](@ref constrained_dimensions(::AbstractPolyhedron{N}) where {N<:Real}) #### Polytopes in constraint representation @@ -479,7 +480,6 @@ rand(::Type{HPolyhedron}) isbounded(::HPolyhedron) vertices_list(::HPolyhedron{N}) where {N<:Real} singleton_list(::HPolyhedron{N}) where {N<:Real} -constrained_dimensions(::HPolyhedron{N}) where {N<:Real} ``` ### Vertex representation diff --git a/src/AbstractPolyhedron_functions.jl b/src/AbstractPolyhedron_functions.jl index 9d1bee0475..ab587c4b53 100644 --- a/src/AbstractPolyhedron_functions.jl +++ b/src/AbstractPolyhedron_functions.jl @@ -1,5 +1,7 @@ import Base.∈ +export constrained_dimensions + """ ∈(x::AbstractVector{N}, P::AbstractPolyhedron{N})::Bool where {N<:Real} @@ -29,3 +31,33 @@ function ∈(x::AbstractVector{N}, P::AbstractPolyhedron{N})::Bool where {N<:Rea end return true end + +""" + constrained_dimensions(P::AbstractPolyhedron{N})::Vector{Int} + where {N<:Real} + +Return the indices in which a polyhedron is constrained. + +### Input + +- `P` -- polyhedron + +### Output + +A vector of ascending indices `i` such that the polyhedron is constrained in +dimension `i`. + +### Examples + +A 2D polyhedron with constraint ``x1 ≥ 0`` is constrained in dimension 1 only. +""" +function constrained_dimensions(P::AbstractPolyhedron{N} + )::Vector{Int} where {N<:Real} + zero_indices = zeros(Int, dim(P)) + for constraint in constraints_list(P) + for i in constrained_dimensions(constraint) + zero_indices[i] = i + end + end + return filter(x -> x != 0, zero_indices) +end diff --git a/src/HPolyhedron.jl b/src/HPolyhedron.jl index 855f610d86..1c4b2b49d3 100644 --- a/src/HPolyhedron.jl +++ b/src/HPolyhedron.jl @@ -259,40 +259,12 @@ function rand(::Type{HPolyhedron}; return HPolyhedron(constraints_Q) end -""" - constrained_dimensions(P::HPolyhedron{N})::Vector{Int} where {N<:Real} - -Return the indices in which a polyhedron in constraint representation is -constrained. - -### Input - -- `P` -- polyhedron in constraint representation - -### Output - -A vector of ascending indices `i` such that the polyhedron is constrained in -dimension `i`. - -### Examples - -A 2D polyhedron with constraint ``x1 ≥ 0`` is constrained in dimension 1 only. -""" -function constrained_dimensions(P::HPolyhedron{N})::Vector{Int} where {N<:Real} - zero_indices = zeros(Int, dim(P)) - for constraint in P.constraints - for i in constrained_dimensions(constraint) - zero_indices[i] = i - end - end - return filter(x -> x != 0, zero_indices) -end - # =========================================== # HPolyhedron and HPolytope's shared methods # =========================================== + """ addconstraint!(P::HPoly{N}, constraint::LinearConstraint{N})::Nothing where {N<:Real} From 1903cdbd70bd1fc72fbc9f4c220ba73d1c82d8c6 Mon Sep 17 00:00:00 2001 From: schillic Date: Sat, 26 Jan 2019 18:34:40 +0100 Subject: [PATCH 4/6] outsource 'tosimplehrep' method --- src/AbstractPolyhedron_functions.jl | 37 ++++++++++++++++++++++++++++- src/HPolyhedron.jl | 32 ------------------------- src/LazySet.jl | 13 +++++----- 3 files changed, 42 insertions(+), 40 deletions(-) diff --git a/src/AbstractPolyhedron_functions.jl b/src/AbstractPolyhedron_functions.jl index ab587c4b53..2b6ab81ea7 100644 --- a/src/AbstractPolyhedron_functions.jl +++ b/src/AbstractPolyhedron_functions.jl @@ -1,6 +1,7 @@ import Base.∈ -export constrained_dimensions +export constrained_dimensions, + tosimplehrep """ ∈(x::AbstractVector{N}, P::AbstractPolyhedron{N})::Bool where {N<:Real} @@ -61,3 +62,37 @@ function constrained_dimensions(P::AbstractPolyhedron{N} end return filter(x -> x != 0, zero_indices) end + +""" + tosimplehrep(constraints::AbstractVector{LinearConstraint{N}}) + where {N<:Real} + +Return the simple H-representation ``Ax ≤ b`` from a list of linear constraints. + +### Input + +- `constraints` -- a list of linear constraints + +### Output + +The tuple `(A, b)` where `A` is the matrix of normal directions and `b` is the +vector of offsets. +""" +function tosimplehrep(constraints::AbstractVector{LinearConstraint{N}} + ) where {N<:Real} + n = length(constraints) + if n == 0 + A = Matrix{N}(undef, 0, 0) + b = Vector{N}(undef, 0) + return (A, b) + end + A = zeros(N, n, dim(first(constraints))) + b = zeros(N, n) + @inbounds begin + for (i, Pi) in enumerate(constraints) + A[i, :] = Pi.a + b[i] = Pi.b + end + end + return (A, b) +end diff --git a/src/HPolyhedron.jl b/src/HPolyhedron.jl index 1c4b2b49d3..2d45d95eb1 100644 --- a/src/HPolyhedron.jl +++ b/src/HPolyhedron.jl @@ -310,38 +310,6 @@ function constraints_list(P::HPoly{N} return P.constraints end -""" - tosimplehrep(constraints::AbstractVector{LinearConstraint{N}}) where {N<:Real} - -Return the simple H-representation ``Ax ≤ b`` of a list of constraints. - -### Input - -- `constraints` -- a list of constraints - -### Output - -The tuple `(A, b)` where `A` is the matrix of normal directions and `b` are the -offsets. -""" -function tosimplehrep(constraints::AbstractVector{LinearConstraint{N}}) where {N<:Real} - n = length(constraints) - if n == 0 - A = Matrix{N}(undef, 0, 0) - b = Vector{N}(undef, 0) - return (A, b) - end - A = zeros(N, n, dim(first(constraints))) - b = zeros(N, n) - @inbounds begin - for (i, Pi) in enumerate(constraints) - A[i, :] = Pi.a - b[i] = Pi.b - end - end - return (A, b) -end - """ tohrep(P::HPoly{N}) where {N<:Real} diff --git a/src/LazySet.jl b/src/LazySet.jl index 6b04550123..8dea2c3a3d 100644 --- a/src/LazySet.jl +++ b/src/LazySet.jl @@ -321,7 +321,7 @@ copy(S::LazySet) = deepcopy(S) """ tosimplehrep(S::LazySet) -Return the simple H-representation ``Ax ≤ b`` of a set from its list of +Return the simple H-representation ``Ax ≤ b`` of a set from its list of linear constraints. ### Input @@ -330,14 +330,13 @@ constraints. ### Output -The tuple `(A, b)` where `A` is the matrix of normal directions and `b` are the -offsets. +The tuple `(A, b)` where `A` is the matrix of normal directions and `b` is the +vector of offsets. ### Notes -This function uses `constraints_list(S)`. It is a fallback implementation that -works only for those sets that can be represented exactly by a list of linear -constraints, which is available through the `constraints_list(S)` -function. +This function only works for sets that can be represented exactly by a finite +list of linear constraints. +This fallback implementation relies on `constraints_list(S)`. """ tosimplehrep(S::LazySet) = tosimplehrep(constraints_list(S)) From 9e46c3f323085d029624d259fe02c13ae7e80269 Mon Sep 17 00:00:00 2001 From: schillic Date: Sat, 26 Jan 2019 19:27:11 +0100 Subject: [PATCH 5/6] outsource 'remove_redundant_constraints' methods --- docs/src/lib/representations.md | 10 +- src/AbstractPolyhedron_functions.jl | 119 +++++++++++++++++++- src/HPolyhedron.jl | 168 +++++++--------------------- 3 files changed, 163 insertions(+), 134 deletions(-) diff --git a/docs/src/lib/representations.md b/docs/src/lib/representations.md index 128353192d..e652865665 100644 --- a/docs/src/lib/representations.md +++ b/docs/src/lib/representations.md @@ -174,8 +174,10 @@ constraints_list(::HalfSpace{N}) where {N<:Real} constrained_dimensions(::HalfSpace{N}) where {N<:Real} halfspace_left(::AbstractVector{N}, ::AbstractVector{N}) where {N<:Real} halfspace_right(::AbstractVector{N}, ::AbstractVector{N}) where {N<:Real} -tosimplehrep(::AbstractVector{HalfSpace{N}}) where {N<:Real} linear_map(::AbstractMatrix{N}, ::HalfSpace{N}) where {N} +tosimplehrep(::AbstractVector{HalfSpace{N}}) where {N<:Real} +remove_redundant_constraints(::AbstractVector{LinearConstraint{N}}) where {N<:Real} +remove_redundant_constraints!(::AbstractVector{LinearConstraint{N}}) where {N<:Real} ``` Inherited from [`LazySet`](@ref): * [`norm`](@ref norm(::LazySet, ::Real)) @@ -442,11 +444,11 @@ constraints_list(::HPoly{N}) where {N<:Real} tohrep(::HPoly{N}) where {N<:Real} isempty(::HPoly{N}, ::Bool=false) where {N<:Real} cartesian_product(::HPoly{N}, ::HPoly{N}) where {N<:Real} -linear_map(M::AbstractMatrix{N}, P::PT) where {N<:Real, PT<:HPoly{N}} +linear_map(::AbstractMatrix{N}, ::PT) where {N<:Real, PT<:HPoly{N}} tovrep(::HPoly{N}) where {N<:Real} polyhedron(::HPoly{N}) where {N<:Real} -remove_redundant_constraints -remove_redundant_constraints! +remove_redundant_constraints(::PT) where {N<:Real, PT<:HPoly{N}} +remove_redundant_constraints!(::HPoly{N}) where {N<:Real} ``` Inherited from [`LazySet`](@ref): diff --git a/src/AbstractPolyhedron_functions.jl b/src/AbstractPolyhedron_functions.jl index 2b6ab81ea7..765e009de0 100644 --- a/src/AbstractPolyhedron_functions.jl +++ b/src/AbstractPolyhedron_functions.jl @@ -1,7 +1,9 @@ import Base.∈ export constrained_dimensions, - tosimplehrep + tosimplehrep, + remove_redundant_constraints, + remove_redundant_constraints! """ ∈(x::AbstractVector{N}, P::AbstractPolyhedron{N})::Bool where {N<:Real} @@ -96,3 +98,118 @@ function tosimplehrep(constraints::AbstractVector{LinearConstraint{N}} end return (A, b) end + +""" + remove_redundant_constraints!( + constraints::AbstractVector{LinearConstraint{N}}; + [backend]=GLPKSolverLP())::Bool where {N<:Real} + +Remove the redundant constraints of a given list of linear constraints; the list +is updated in-place. + +### Input + +- `constraints` -- list of constraints +- `backend` -- (optional, default: `GLPKSolverLP`) numeric LP solver backend + +### Output + +`true` if the function was successful and the list of constraints `constraints` +is modified by removing the redundant constraints, and `false` only if the +constraints are infeasible. + +### Notes + +Note that the result may be `true` even if the constraints are infeasible. +For example, ``x ≤ 0 && x ≥ 1`` will return `true` without removing any +constraint. +To check if the constraints are infeasible, use +`isempty(HPolyhedron(constraints)`. + +### Algorithm + +If there are `m` constraints in `n` dimensions, this function checks one by one +if each of the `m` constraints is implied by the remaining ones. + +To check if the `k`-th constraint is redundant, an LP is formulated using the +constraints that have not yet being removed. +If, at an intermediate step, it is detected that a subgroup of the constraints +is infeasible, this function returns `false`. +If the calculation finished successfully, this function returns `true`. + +For details, see [Fukuda's Polyhedra +FAQ](https://www.cs.mcgill.ca/~fukuda/soft/polyfaq/node24.html). +""" +function remove_redundant_constraints!(constraints::AbstractVector{LinearConstraint{N}}; + backend=GLPKSolverLP() + )::Bool where {N<:Real} + + A, b = tosimplehrep(constraints) + m, n = size(A) + non_redundant_indices = 1:m + + i = 1 # counter over reduced constraints + + for j in 1:m # loop over original constraints + α = A[j, :] + Ar = A[non_redundant_indices, :] + br = b[non_redundant_indices] + br[i] = b[j] + one(N) + lp = linprog(-α, Ar, '<', br, -Inf, Inf, backend) + if lp.status == :Infeasible + # the polyhedron is empty + return false + elseif lp.status == :Optimal + objval = -lp.objval + if objval <= b[j] + # the constraint is redundant + non_redundant_indices = setdiff(non_redundant_indices, j) + else + # the constraint is not redundant + i = i+1 + end + else + error("LP is not optimal; the status of the LP is $(lp.status)") + end + end + + deleteat!(constraints, setdiff(1:m, non_redundant_indices)) + return true +end + +""" + remove_redundant_constraints( + constraints::AbstractVector{LinearConstraint{N}}; + backend=GLPKSolverLP())::Union{AbstractVector{LinearConstraint{N}}, + EmptySet{N}} where {N<:Real} + +Remove the redundant constraints of a given list of linear constraints. + +### Input + +- `constraints` -- list of constraints +- `backend` -- (optional, default: `GLPKSolverLP`) numeric LP solver backend + +### Output + +The list of constraints with the redundant ones removed, or an empty set if the +constraints are infeasible. + +### Algorithm + +See +[`remove_redundant_constraints!(::AbstractVector{LinearConstraint{<:Real}})`](@ref) +for details. +""" +function remove_redundant_constraints(constraints::AbstractVector{LinearConstraint{N}}; + backend=GLPKSolverLP() + )::Union{AbstractVector{LinearConstraint{N}}, + EmptySet{N} + } where {N<:Real} + constraints_copy = copy(constraints) + if remove_redundant_constraints!(constraints_copy, backend=backend) + return constraints_copy + else # the constraints are infeasible + return EmptySet{N}() + end +end diff --git a/src/HPolyhedron.jl b/src/HPolyhedron.jl index 2d45d95eb1..798b801621 100644 --- a/src/HPolyhedron.jl +++ b/src/HPolyhedron.jl @@ -48,7 +48,8 @@ function HPolyhedron(A::AbstractMatrix{N}, b::AbstractVector{N}) where {N<:Real} return HPolyhedron(constraints) end -HPolyhedron{N}(A::AbstractMatrix{N}, b::AbstractVector{N}) where {N<:Real} = HPolyhedron(A, b) +HPolyhedron{N}(A::AbstractMatrix{N}, b::AbstractVector{N}) where {N<:Real} = + HPolyhedron(A, b) # convenience union type const HPoly{N} = Union{HPolytope{N}, HPolyhedron{N}} @@ -166,7 +167,8 @@ end function σ_helper(d::AbstractVector{N}, P::HPoly{N}) where {N<:Real} - # let c = -d as a Vector, since GLPK doesn't accept sparse vectors (see #1011) + # let c = -d as a Vector, since GLPK doesn't accept sparse vectors + # (see #1011) c = _to_minus_vector(d) (A, b) = tosimplehrep(P) @@ -330,7 +332,9 @@ end """ remove_redundant_constraints(P::PT; - backend=GLPKSolverLP())::Union{PT, EmptySet{N}} where {N, PT<:HPoly{N}} + backend=GLPKSolverLP() + )::Union{PT, EmptySet{N}} where {N<:Real, + PT<:HPoly{N}} Remove the redundant constraints in a polyhedron in H-representation. @@ -341,17 +345,20 @@ Remove the redundant constraints in a polyhedron in H-representation. ### Output -A polyhedron equivalent to `P` but with no redundant constraints, or an empty set -if `P` is detected to be empty, which may happen if the constraints are infeasible. +A polyhedron equivalent to `P` but with no redundant constraints, or an empty +set if `P` is detected to be empty, which may happen if the constraints are +infeasible. ### Algorithm See -[`remove_redundant_constraints!(::Vector{LinearConstraint{N}}) where {N<:Real}`](@ref) +[`remove_redundant_constraints!(::Vector{LinearConstraint{<:Real}})`](@ref) for details. """ function remove_redundant_constraints(P::PT; - backend=GLPKSolverLP())::Union{PT, EmptySet{N}} where {N, PT<:HPoly{N}} + backend=GLPKSolverLP() + )::Union{PT, EmptySet{N}} where {N<:Real, + PT<:HPoly{N}} Pred = copy(P) if remove_redundant_constraints!(Pred, backend=backend) return Pred @@ -361,11 +368,11 @@ function remove_redundant_constraints(P::PT; end """ - remove_redundant_constraints!(P::PT; - backend=GLPKSolverLP())::Bool where {N, PT<:HPoly{N}} + remove_redundant_constraints!(P::HPoly{N}; + backend=GLPKSolverLP())::Bool where {N<:Real} -Remove the redundant constraints in a polyhedron in H-representation; the polyhedron -is updated in-place. +Remove the redundant constraints in a polyhedron in H-representation; the +polyhedron is updated in-place. ### Input @@ -381,117 +388,15 @@ which may happen if the constraints are infeasible. ### Algorithm See -[`remove_redundant_constraints!(::Vector{LinearConstraint{N}}) where {N<:Real}`](@ref) +[`remove_redundant_constraints!(::Vector{LinearConstraint{<:Real}})`](@ref) for details. """ -function remove_redundant_constraints!(P::PT; - backend=GLPKSolverLP())::Bool where {N, PT<:HPoly{N}} +function remove_redundant_constraints!(P::HPoly{N}; + backend=GLPKSolverLP() + )::Bool where {N<:Real} remove_redundant_constraints!(P.constraints, backend=backend) end -""" - remove_redundant_constraints!(constraints::Vector{LinearConstraint{N}}; - backend=GLPKSolverLP())::Bool where {N} - -Remove the redundant constraints of a given list of linear constraints; the list -is updated in-place. - -### Input - -- `constraints` -- list of constraints -- `backend` -- (optional, default: `GLPKSolverLP`) the numeric LP solver backend - -### Output - -`true` if the method was successful and the list of constraints `constraints` is -modified by removing the redundant constraints, and `false` if the constraints -are infeasible. - -### Algorithm - -If there are `m` constraints in `n` dimensions, this function checks one by one -if each of the `m` constraints is implied by the remaining ones. - -To check if the `k`-th constraint is redundant, an LP is formulated using the -constraints that have not yet being removed. If, at an intermediate step, it is -detected that a subgruop of the constraints is infeasible, this function returns -`false`; if the calculation finished successfully it returns `true`. - -Note that the constraints being infeasible does not imply that `false` is returned. -For example, `x <= 0 && x >= 1` will return `true` without removing any constraint. -To check if the constraints are infeasible use `isempty(HPolyhedron(constraints)`. - -For details, see [Fukuda's Polyhedra -FAQ](https://www.cs.mcgill.ca/~fukuda/soft/polyfaq/node24.html). -""" -function remove_redundant_constraints!(constraints::AbstractVector{LinearConstraint{N}}; - backend=GLPKSolverLP())::Bool where {N} - - A, b = tosimplehrep(constraints) - m, n = size(A) - non_redundant_indices = 1:m - - i = 1 # counter over reduced constraints - - for j in 1:m # loop over original constraints - α = A[j, :] - Ar = A[non_redundant_indices, :] - br = b[non_redundant_indices] - br[i] = b[j] + one(N) - lp = linprog(-α, Ar, '<', br, -Inf, Inf, backend) - if lp.status == :Infeasible - # the polyhedron is empty - return false - elseif lp.status == :Optimal - objval = -lp.objval - if objval <= b[j] - # the constraint is redundant - non_redundant_indices = setdiff(non_redundant_indices, j) - else - # the constraint is not redundant - i = i+1 - end - else - error("LP is not optimal; the status of the LP is $(lp.status)") - end - end - - deleteat!(constraints, setdiff(1:m, non_redundant_indices)) - return true -end - -""" - remove_redundant_constraints(constraints::AbstractVector{LinearConstraint{N}}; - backend=GLPKSolverLP())::Union{AbstractVector{LinearConstraint{N}}, EmptySet{N}} where {N} - -Remove the redundant constraints of a given list of linear constraints. - -### Input - -- `constraints` -- list of constraints -- `backend` -- (optional, default: `GLPKSolverLP`) the numeric LP solver backend - -### Output - -The list of constraints with the redundant ones removed, or an empty set -if the given constraints are infeasible. - -### Algorithm - -See -[`remove_redundant_constraints!(::AbstractVector{LinearConstraint{N}}) where {N<:Real}`](@ref) -for details. -""" -function remove_redundant_constraints(constraints::AbstractVector{LinearConstraint{N}}; - backend=GLPKSolverLP())::Union{AbstractVector{LinearConstraint{N}}, EmptySet{N}} where {N} - constraints_copy = copy(constraints) - if remove_redundant_constraints!(constraints_copy, backend=backend) - return constraints_copy - else # the constraints are infeasible - return EmptySet{N}() - end -end - """ linear_map(M::AbstractMatrix{N}, P::PT; [cond_tol=DEFAULT_COND_TOL]::Number) where {N<:Real, PT<:HPoly{N}} @@ -528,7 +433,8 @@ function linear_map(M::AbstractMatrix{N}, end # matrix is invertible invM = inv(M) - constraints = Vector{LinearConstraint{N}}(undef, length(constraints_list(P))) + constraints = Vector{LinearConstraint{N}}(undef, + length(constraints_list(P))) @inbounds for (i, c) in enumerate(constraints_list(P)) constraints[i] = LinearConstraint(vec(c.a' * invM), c.b) end @@ -568,9 +474,10 @@ For further information on the supported backends see function convex_hull(P1::HPoly{N}, P2::HPoly{N}; backend=default_polyhedra_backend(P1, N)) where {N} - @assert isdefined(@__MODULE__, :Polyhedra) "the function `convex_hull` needs " * - "the package 'Polyhedra' to be loaded" - Pch = convexhull(polyhedron(P1; backend=backend), polyhedron(P2; backend=backend)) + @assert isdefined(@__MODULE__, :Polyhedra) "the function `convex_hull` "* + "needs the package 'Polyhedra'" + Pch = convexhull(polyhedron(P1; backend=backend), + polyhedron(P2; backend=backend)) removehredundancy!(Pch) return convert(typeof(P1), Pch) end @@ -602,9 +509,10 @@ function cartesian_product(P1::HPoly{N}, P2::HPoly{N}; backend=default_polyhedra_backend(P1, N) ) where {N<:Real} - @assert isdefined(@__MODULE__, :Polyhedra) "the function `cartesian_product` " * - "needs the package 'Polyhedra' to be loaded" - Pcp = hcartesianproduct(polyhedron(P1; backend=backend), polyhedron(P2; backend=backend)) + @assert isdefined(@__MODULE__, :Polyhedra) "the function " * + "`cartesian_product` needs the package 'Polyhedra'" + Pcp = hcartesianproduct(polyhedron(P1; backend=backend), + polyhedron(P2; backend=backend)) return convert(typeof(P1), Pcp) end @@ -635,7 +543,7 @@ For further information on the supported backends see function tovrep(P::HPoly{N}; backend=default_polyhedra_backend(P, N)) where {N<:Real} @assert isdefined(@__MODULE__, :Polyhedra) "the function `tovrep` needs " * - "the package 'Polyhedra' to be loaded" + "the package 'Polyhedra'" P = polyhedron(P; backend=backend) return VPolytope(P) end @@ -664,8 +572,9 @@ julia> P_as_polytope = convert(HPolytope, P); ``` """ function vertices_list(P::HPolyhedron{N}) where {N<:Real} - throw(ArgumentError("the list of vertices of a (possibly unbounded) polyhedron is not defined; " * - "if the polyhedron is bounded, try converting to `HPolytope` first")) + throw(ArgumentError("the list of vertices of a (possibly unbounded) " * + "polyhedron is not defined; if the polyhedron is bounded, try " * + "converting to `HPolytope` first")) end """ @@ -683,8 +592,9 @@ This function returns an error because the polyhedron is possibly unbounded. If `P` is known to be bounded, try converting to `HPolytope` first. """ function singleton_list(P::HPolyhedron{N}) where {N<:Real} - throw(ArgumentError("the list of singletons of a (possibly unbounded) polyhedron is not defined; " * - "if the polyhedron is bounded, try converting to `HPolytope` first")) + throw(ArgumentError("the list of singletons of a (possibly unbounded) " * + "polyhedron is not defined; if the polyhedron is bounded, try " * + "converting to `HPolytope` first")) end """ From 7c33894f624f8ee1d27008416e3fb2d7f60105f0 Mon Sep 17 00:00:00 2001 From: schillic Date: Sun, 27 Jan 2019 10:04:10 +0100 Subject: [PATCH 6/6] update picture of interface hierarchy --- docs/src/assets/interfaces.graphml | 192 +++++++++++++---------------- docs/src/assets/interfaces.png | Bin 14120 -> 18375 bytes src/AbstractPolyhedron.jl | 2 +- src/AbstractPolytope.jl | 3 +- 4 files changed, 88 insertions(+), 109 deletions(-) diff --git a/docs/src/assets/interfaces.graphml b/docs/src/assets/interfaces.graphml index 5b14bdb273..ab3d66ae18 100644 --- a/docs/src/assets/interfaces.graphml +++ b/docs/src/assets/interfaces.graphml @@ -1,6 +1,6 @@ - + @@ -13,180 +13,147 @@ - + - + - LazySet - - - - - - - - - + LazySet + + + - + - + - AbstractPolytope - - - - - - - - - + AbstractPolytope + + + - + - + - AbstractCentrallySymmetric - - - - - - - - - + AbstractCentrallySymmetric + + + - + - + - AbstractCentrallySymmetricPolytope - - - - - - - - - + AbstractCentrallySymmetricPolytope + + + - + - + - AbstractHyperrectangle - - - - - - - - - + AbstractHyperrectangle + + + - + - + - AbstractPolygon - - - - - - - - - + AbstractPolygon + + + - + - + - AbstractHPolygon - - - - - - - - - + AbstractHPolygon + + + - + - + - AbstractSingleton - - - - - - - - - + AbstractSingleton + + + - - + + + + + + + + AbstractPolyhedron + + + + + + + + + - + @@ -194,7 +161,7 @@ - + @@ -205,7 +172,7 @@ - + @@ -216,7 +183,7 @@ - + @@ -227,7 +194,7 @@ - + @@ -238,7 +205,7 @@ - + @@ -249,7 +216,7 @@ - + @@ -260,7 +227,7 @@ - + @@ -270,6 +237,17 @@ + + + + + + + + + + + diff --git a/docs/src/assets/interfaces.png b/docs/src/assets/interfaces.png index 3d27c9505b202d1e55cb20f46f434b577c0abe7a..ff464d78d08b9ddec25ee486a3274827306d08db 100644 GIT binary patch literal 18375 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 bLfz6xa9IhepMf9fc_1tx&R@i<@%euMS73d= literal 14120 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 Au>b%7 diff --git a/src/AbstractPolyhedron.jl b/src/AbstractPolyhedron.jl index db82762e86..4e9e81947d 100644 --- a/src/AbstractPolyhedron.jl +++ b/src/AbstractPolyhedron.jl @@ -4,7 +4,7 @@ export AbstractPolyhedron AbstractPolyhedron{N<:Real} <: LazySet{N} Abstract type for polyhedral sets, i.e., sets with finitely many flat facets, or -equivalently, sets defined as an intersection of a finite number of halfspaces. +equivalently, sets defined as an intersection of a finite number of half-spaces. ### Notes diff --git a/src/AbstractPolytope.jl b/src/AbstractPolytope.jl index e241f1924d..37d6d0e4d2 100644 --- a/src/AbstractPolytope.jl +++ b/src/AbstractPolytope.jl @@ -11,7 +11,8 @@ export AbstractPolytope, Abstract type for polytopic sets, i.e., bounded sets with finitely many flat facets, or equivalently, bounded sets defined as an intersection of a finite -number of halfspaces, or equivalently, bounded sets with finitely many vertices. +number of half-spaces, or equivalently, bounded sets with finitely many +vertices. ### Notes