diff --git a/docs/src/lib/interfaces.md b/docs/src/lib/interfaces.md index c6d497c5a9..92f14364f8 100644 --- a/docs/src/lib/interfaces.md +++ b/docs/src/lib/interfaces.md @@ -156,6 +156,7 @@ addconstraint!(::AbstractHPolygon{N}, ::LinearConstraint{N}) where {N<:Real} addconstraint!(::Vector{LinearConstraint{N}}, ::LinearConstraint{N}) where {N<:Real} constraints_list(::AbstractHPolygon{N}) where {N<:Real} vertices_list(::AbstractHPolygon{N}, ::Bool=false, ::Bool=true) where {N<:Real} +isbounded(::AbstractHPolygon, ::Bool=true) ``` ### Centrally symmetric polytope diff --git a/docs/src/lib/representations.md b/docs/src/lib/representations.md index 0ea6527c93..d884f4f5e4 100644 --- a/docs/src/lib/representations.md +++ b/docs/src/lib/representations.md @@ -328,7 +328,6 @@ Inherited from [`LazySet`](@ref): * [`diameter`](@ref diameter(::LazySet, ::Real)) Inherited from [`AbstractPolytope`](@ref): -* [`isbounded`](@ref isbounded(::AbstractPolytope)) * [`isempty`](@ref isempty(::AbstractPolytope)) * [`singleton_list`](@ref singleton_list(::AbstractPolytope{N}) where {N<:Real}) * [`linear_map`](@ref linear_map(::AbstractMatrix{N}, ::AbstractPolytope{N}) where {N<:Real}) @@ -342,6 +341,7 @@ Inherited from [`AbstractHPolygon`](@ref): * [`vertices_list`](@ref vertices_list(::AbstractHPolygon{N}, ::Bool=false, ::Bool=true) where {N<:Real}) * [`tohrep`](@ref tohrep(::HPOLYGON) where {HPOLYGON<:AbstractHPolygon}) * [`tovrep`](@ref tovrep(::AbstractHPolygon{N}) where {N<:Real}) +* [`isbounded`](@ref isbounded(::AbstractHPolygon, ::Bool=true)) * [`addconstraint!`](@ref addconstraint!(::AbstractHPolygon{N}, ::LinearConstraint{N}) where {N<:Real}) * [`constraints_list`](@ref constraints_list(::AbstractHPolygon{N}) where {N<:Real}) @@ -357,7 +357,6 @@ Inherited from [`LazySet`](@ref): * [`diameter`](@ref diameter(::LazySet, ::Real)) Inherited from [`AbstractPolytope`](@ref): -* [`isbounded`](@ref isbounded(::AbstractPolytope)) * [`isempty`](@ref isempty(::AbstractPolytope)) * [`singleton_list`](@ref singleton_list(::AbstractPolytope{N}) where {N<:Real}) * [`linear_map`](@ref linear_map(::AbstractMatrix{N}, ::AbstractPolytope{N}) where {N<:Real}) @@ -371,6 +370,7 @@ Inherited from [`AbstractHPolygon`](@ref): * [`vertices_list`](@ref vertices_list(::AbstractHPolygon{N}, ::Bool=false, ::Bool=true) where {N<:Real}) * [`tohrep`](@ref tohrep(::HPOLYGON) where {HPOLYGON<:AbstractHPolygon}) * [`tovrep`](@ref tovrep(::AbstractHPolygon{N}) where {N<:Real}) +* [`isbounded`](@ref isbounded(::AbstractHPolygon, ::Bool=true)) * [`addconstraint!`](@ref addconstraint!(::AbstractHPolygon{N}, ::LinearConstraint{N}) where {N<:Real}) * [`constraints_list`](@ref constraints_list(::AbstractHPolygon{N}) where {N<:Real}) @@ -460,10 +460,10 @@ The following methods are specific for `HPolytope`. ```@docs rand(::Type{HPolytope}) vertices_list(::HPolytope{N}) where {N<:Real} +isbounded(::HPolytope, ::Bool=true) ``` Inherited from [`AbstractPolytope`](@ref): -* [`isbounded`](@ref isbounded(::AbstractPolytope)) * [`singleton_list`](@ref singleton_list(::AbstractPolytope{N}) where {N<:Real}) #### Polyhedra diff --git a/src/AbstractHPolygon.jl b/src/AbstractHPolygon.jl index 31f131e785..985a6f386e 100644 --- a/src/AbstractHPolygon.jl +++ b/src/AbstractHPolygon.jl @@ -5,7 +5,8 @@ export AbstractHPolygon, an_element, addconstraint!, vertices_list, - constraints_list + constraints_list, + isbounded # This constant marks the threshold for the number of constraints of a polygon # above which we use a binary search to find the relevant constraint in a @@ -378,3 +379,31 @@ function binary_search_constraints(d::AbstractVector{N}, return upper end end + +""" + isbounded(P::AbstractHPolygon, [use_type_assumption]::Bool=true)::Bool + +Determine whether a polygon in constraint representation is bounded. + +### Input + +- `P` -- polygon in constraint representation +- `use_type_assumption` -- (optional, default: `true`) flag for ignoring the + type assumption that polygons are bounded + +### Output + +`true` if `use_type_assumption` is activated. +Otherwise, `true` iff `P` is bounded. + +### Algorithm + +If `!use_type_assumption`, we convert `P` to an `HPolyhedron` `P2` and then use +`isbounded(P2)`. +""" +function isbounded(P::AbstractHPolygon, use_type_assumption::Bool=true)::Bool + if use_type_assumption + return true + end + return isbounded(HPolyhedron(P.constraints)) +end diff --git a/src/HPolygon.jl b/src/HPolygon.jl index 1f22c0a90e..30ad1e64e4 100644 --- a/src/HPolygon.jl +++ b/src/HPolygon.jl @@ -10,7 +10,13 @@ are sorted in counter-clockwise fashion with respect to their normal directions. ### Fields -- `constraints` -- list of linear constraints, sorted by the angle +- `constraints` -- list of linear constraints, sorted by the angle +- `sort_constraints` -- (optional, default: `true`) flag for sorting the + constraints (sortedness is a running assumption of this + type) +- `check_boundedness` -- (optional, default: `false`) flag for checking if the + constraints make the polygon bounded; (boundedness is a + running assumption of this type) ### Notes @@ -22,31 +28,37 @@ Use `addconstraint!` to iteratively add the edges in a sorted way. -- default constructor - `HPolygon()` -- constructor with no constraints -- `HPolygon(S::LazySet)` -- constructor from another set """ struct HPolygon{N<:Real} <: AbstractHPolygon{N} constraints::Vector{LinearConstraint{N}} # default constructor that applies sorting of the given constraints function HPolygon{N}(constraints::Vector{LinearConstraint{N}}; - sort_constraints::Bool=true) where {N<:Real} + sort_constraints::Bool=true, + check_boundedness::Bool=false) where {N<:Real} if sort_constraints sorted_constraints = Vector{LinearConstraint{N}}() sizehint!(sorted_constraints, length(constraints)) for ci in constraints addconstraint!(sorted_constraints, ci) end - return new{N}(sorted_constraints) + P = new{N}(sorted_constraints) else - return new{N}(constraints) + P = new{N}(constraints) end + @assert (!check_boundedness || + isbounded(P, false)) "the polygon is not bounded" + return P end end # convenience constructor without type parameter HPolygon(constraints::Vector{LinearConstraint{N}}; - sort_constraints::Bool=true) where {N<:Real} = - HPolygon{N}(constraints; sort_constraints=sort_constraints) + sort_constraints::Bool=true, + check_boundedness::Bool=false) where {N<:Real} = + HPolygon{N}(constraints; + sort_constraints=sort_constraints, + check_boundedness=check_boundedness) # constructor for an HPolygon with no constraints HPolygon{N}() where {N<:Real} = HPolygon{N}(Vector{LinearConstraint{N}}()) @@ -54,9 +66,6 @@ HPolygon{N}() where {N<:Real} = HPolygon{N}(Vector{LinearConstraint{N}}()) # constructor for an HPolygon with no constraints of type Float64 HPolygon() = HPolygon{Float64}() -# conversion constructor -HPolygon(S::LazySet) = convert(HPolygon, S) - # --- LazySet interface functions --- diff --git a/src/HPolygonOpt.jl b/src/HPolygonOpt.jl index e33d05cce2..505e89b3ce 100644 --- a/src/HPolygonOpt.jl +++ b/src/HPolygonOpt.jl @@ -11,9 +11,15 @@ This is a refined version of `HPolygon`. ### Fields -- `constraints` -- list of linear constraints -- `ind` -- index in the list of constraints to begin the search to evaluate the - support function +- `constraints` -- list of linear constraints +- `ind` -- index in the list of constraints to begin the search + to evaluate the support function +- `sort_constraints` -- (optional, default: `true`) flag for sorting the + constraints (sortedness is a running assumption of this + type) +- `check_boundedness` -- (optional, default: `false`) flag for checking if the + constraints make the polygon bounded; (boundedness is a + running assumption of this type) ### Notes @@ -26,9 +32,8 @@ The default constructor assumes that the given list of edges is sorted. It *does not perform* any sorting. Use `addconstraint!` to iteratively add the edges in a sorted way. -- `HPolygonOpt(constraints::Vector{LinearConstraint{<:Real}}, [ind]::Int)` +- `HPolygonOpt(constraints::Vector{LinearConstraint{<:Real}}, [ind]::Int=1)` -- default constructor with optional index -- `HPolygonOpt(S::LazySet)` -- constructor from another set """ mutable struct HPolygonOpt{N<:Real} <: AbstractHPolygon{N} constraints::Vector{LinearConstraint{N}} @@ -37,36 +42,40 @@ mutable struct HPolygonOpt{N<:Real} <: AbstractHPolygon{N} # default constructor that applies sorting of the given constraints function HPolygonOpt{N}(constraints::Vector{LinearConstraint{N}}, ind::Int=1; - sort_constraints::Bool=true) where {N<:Real} + sort_constraints::Bool=true, + check_boundedness::Bool=false) where {N<:Real} if sort_constraints sorted_constraints = Vector{LinearConstraint{N}}() sizehint!(sorted_constraints, length(constraints)) for ci in constraints addconstraint!(sorted_constraints, ci) end - return new{N}(sorted_constraints, ind) + P = new{N}(sorted_constraints, ind) else - return new{N}(constraints, ind) + P = new{N}(constraints, ind) end + @assert (!check_boundedness || + isbounded(P, false)) "the polygon is not bounded" + return P end end # convenience constructor without type parameter HPolygonOpt(constraints::Vector{LinearConstraint{N}}, - ind::Int=1) where {N<:Real} = - HPolygonOpt{N}(constraints, ind) - -# constructor for an HPolygon with no constraints -HPolygonOpt{N}() where {N<:Real} = - HPolygonOpt{N}(Vector{LinearConstraint{N}}()) - -# constructor for an HPolygon with no constraints of type Float64 + ind::Int=1; + sort_constraints::Bool=true, + check_boundedness::Bool=false) where {N<:Real} = + HPolygonOpt{N}(constraints, + ind; + sort_constraints=sort_constraints, + check_boundedness=check_boundedness) + +# constructor for an HPolygonOpt with no constraints +HPolygonOpt{N}() where {N<:Real} = HPolygonOpt{N}(Vector{LinearConstraint{N}}()) + +# constructor for an HPolygonOpt with no constraints of type Float64 HPolygonOpt() = HPolygonOpt{Float64}() -# conversion constructor -HPolygonOpt(S::LazySet) = convert(HPolygonOpt, S) - - # --- LazySet interface functions --- diff --git a/src/HPolytope.jl b/src/HPolytope.jl index 11086867b6..e530985734 100644 --- a/src/HPolytope.jl +++ b/src/HPolytope.jl @@ -1,7 +1,8 @@ import Base.rand export HPolytope, - vertices_list + vertices_list, + isbounded """ HPolytope{N<:Real} <: AbstractPolytope{N} @@ -10,7 +11,10 @@ Type that represents a convex polytope in H-representation. ### Fields -- `constraints` -- vector of linear constraints +- `constraints` -- vector of linear constraints +- `check_boundedness` -- (optional, default: `false`) flag for checking if the + constraints make the polytope bounded; (boundedness is + a running assumption of this type) ### Note @@ -19,28 +23,43 @@ assumption in this type. """ struct HPolytope{N<:Real} <: AbstractPolytope{N} constraints::Vector{LinearConstraint{N}} + + function HPolytope{N}(constraints::Vector{LinearConstraint{N}}; + check_boundedness::Bool=false + ) where {N<:Real} + P = new{N}(constraints) + @assert (!check_boundedness || + isbounded(P, false)) "the polytope is not bounded" + return P + end end +# convenience constructor without type parameter +HPolytope(constraints::Vector{LinearConstraint{N}}; + check_boundedness::Bool=false) where {N<:Real} = + HPolytope{N}(constraints; check_boundedness=check_boundedness) + # constructor for an HPolytope with no constraints HPolytope{N}() where {N<:Real} = HPolytope{N}(Vector{LinearConstraint{N}}()) # constructor for an HPolytope with no constraints of type Float64 HPolytope() = HPolytope{Float64}() -# conversion constructor -HPolytope(S::LazySet) = convert(HPolytope, S) - # constructor for an HPolytope from a simple H-representation -function HPolytope(A::AbstractMatrix{N}, b::AbstractVector{N}) where {N<:Real} +function HPolytope(A::AbstractMatrix{N}, b::AbstractVector{N}; + check_boundedness::Bool=false) where {N<:Real} m = size(A, 1) constraints = LinearConstraint{N}[] @inbounds for i in 1:m push!(constraints, LinearConstraint(A[i, :], b[i])) end - return HPolytope(constraints) + return HPolytope(constraints; check_boundedness=check_boundedness) end -HPolytope{N}(A::AbstractMatrix{N}, b::AbstractVector{N}) where {N<:Real} = HPolytope(A, b) +HPolytope{N}(A::AbstractMatrix{N}, b::AbstractVector{N}; + check_boundedness::Bool=false) where {N<:Real} = + HPolytope(A, b; check_boundedness=check_boundedness) + # --- LazySet interface functions --- @@ -87,6 +106,35 @@ function rand(::Type{HPolytope}; return convert(HPolytope, vpolytope) end +""" + isbounded(P::HPolytope, [use_type_assumption]::Bool=true)::Bool + +Determine whether a polytope in constraint representation is bounded. + +### Input + +- `P` -- polytope in constraint representation +- `use_type_assumption` -- (optional, default: `true`) flag for ignoring the + type assumption that polytopes are bounded + +### Output + +`true` if `use_type_assumption` is activated. +Otherwise, `true` iff `P` is bounded. + +### Algorithm + +If `!use_type_assumption`, we convert `P` to an `HPolyhedron` `P2` and then use +`isbounded(P2)`. +""" +function isbounded(P::HPolytope, use_type_assumption::Bool=true)::Bool + if use_type_assumption + return true + end + return isbounded(HPolyhedron(P.constraints)) +end + + # --- functions that use Polyhedra.jl --- diff --git a/test/unit_Polygon.jl b/test/unit_Polygon.jl index 5d8bf82faa..073239e3a0 100644 --- a/test/unit_Polygon.jl +++ b/test/unit_Polygon.jl @@ -22,28 +22,42 @@ for N in [Float64, Float32, Rational{Int}] @test p.constraints[4] == c4 # conversion to optimized polygon - po = HPolygonOpt(p) + po = convert(HPolygonOpt, p) # conversion back - HPolygon(po) + convert(HPolygon, po) # conversion from HPolytope polytope = HPolytope{N}() addconstraint!(polytope, c1) addconstraint!(polytope, c2) addconstraint!(polytope, c3) addconstraint!(polytope, c4) - HPolygon(polytope) - HPolygonOpt(polytope) + convert(HPolygon, polytope) + convert(HPolygonOpt, polytope) # conversion to HPolytope - HPolytope(p) - HPolytope(po) + HPolytope(constraints_list(p)) + HPolytope(constraints_list(po)) + + # conversion from other set type + H = Hyperrectangle(low=N[-1, -1], high=N[1, 1]) + HPolygon(constraints_list(H)) + HPolygonOpt(constraints_list(H)) + # check boundedness after conversion + HPolygon(constraints_list(H); check_boundedness=true) + HPolygonOpt(constraints_list(H); check_boundedness=true) # support vector of polygon with no constraints @test_throws AssertionError σ(N[0], HPolygon{N}()) - @test_throws AssertionError σ(N[0], HPolygonOpt(HPolygon{N}())) + @test_throws AssertionError σ(N[0], HPolygonOpt{N}()) # boundedness - @test isbounded(p) - @test isbounded(po) + @test isbounded(p) && isbounded(po) + @test isbounded(p, false) && isbounded(po, false) + @test !isbounded(HPolygon{N}(), false) && + !isbounded(HPolygonOpt{N}(), false) + @test_throws AssertionError HPolygon(LinearConstraint{N}[]; + check_boundedness=true) + @test_throws AssertionError HPolygonOpt(LinearConstraint{N}[]; + check_boundedness=true) # isempty @test !isempty(p) diff --git a/test/unit_Polytope.jl b/test/unit_Polytope.jl index 093d162875..decde1efcd 100644 --- a/test/unit_Polytope.jl +++ b/test/unit_Polytope.jl @@ -21,6 +21,8 @@ for N in [Float64, Rational{Int}, Float32] @test c isa Vector{LinearConstraint{N}} @test c[1].a == N[1, 2] && c[1].b == N(1) @test c[2].a == N[-1, 1] && c[2].b == N(2) + @test_throws AssertionError HPolytope(N[1 0; 0 1], N[1, 1]; + check_boundedness=true) # convert back to matrix and vector A2, b2 = tosimplehrep(p) @@ -51,7 +53,9 @@ for N in [Float64, Rational{Int}, Float32] @test_throws ErrorException σ(N[0], HPolytope{N}()) # boundedness - @test isbounded(p) + @test isbounded(p) && isbounded(p, false) + p2 = HPolytope{N}() + @test isbounded(p2) && !isbounded(p2, false) # membership @test ∈(N[5 / 4, 7 / 4], p) @@ -79,6 +83,8 @@ for N in [Float64, Rational{Int}, Float32] P = convert(HPolytope, H) vlist = vertices_list(P) @test ispermutation(vlist, [N[3, 3], N[3, -1], N[-1, -1], N[-1, 3]]) + # check boundedness after conversion + HPolytope(constraints_list(H); check_boundedness=true) # isempty @test !isempty(p)