diff --git a/docs/src/lib/concrete_binary_operations/intersection.md b/docs/src/lib/concrete_binary_operations/intersection.md index 890e5e4c13..5f72205673 100644 --- a/docs/src/lib/concrete_binary_operations/intersection.md +++ b/docs/src/lib/concrete_binary_operations/intersection.md @@ -31,4 +31,5 @@ intersection(::LineSegment, ::Line2D) intersection(::AbstractZonotope{N}, ::HalfSpace{N}) where {N} intersection(::Star, ::HalfSpace) intersection!(::Star, ::HalfSpace) +LazySets._bound_intersect_2D(::Zonotope, ::Line2D) ``` diff --git a/docs/src/lib/sets/Zonotope.md b/docs/src/lib/sets/Zonotope.md index cc678a4b8e..ebb39939de 100644 --- a/docs/src/lib/sets/Zonotope.md +++ b/docs/src/lib/sets/Zonotope.md @@ -1,32 +1,41 @@ ```@meta -CurrentModule = LazySets +CurrentModule = LazySets.ZonotopeModule ``` # [Zonotope](@id def_Zonotope) ```@docs Zonotope +``` + +## Operations + +```@docs center(::Zonotope) -rand(::Type{Zonotope}) generators(::Zonotope) genmat(::Zonotope) -scale!(::Real, Z::Zonotope) -ngens(::Zonotope) -togrep(::Zonotope) -low(::Zonotope, ::Int) high(::Zonotope, ::Int) +low(::Zonotope, ::Int) +ngens(::Zonotope) +rand(::Type{Zonotope}) +remove_redundant_generators(Z::Zonotope{N}) where {N} remove_zero_generators(::Zonotope) +togrep(::Zonotope) linear_map!(::Zonotope, ::AbstractMatrix, ::Zonotope) -LazySets._bound_intersect_2D(::Zonotope, ::Line2D) -remove_redundant_generators(Z::Zonotope{N}) where {N} +scale!(::Real, Z::Zonotope) translate!(::Zonotope, ::AbstractVector) ``` + +```@meta +CurrentModule = LazySets +``` + Inherited from [`LazySet`](@ref): +* [`diameter`](@ref diameter(::LazySet, ::Real)) * [`high`](@ref high(::LazySet)) * [`low`](@ref low(::LazySet)) * [`norm`](@ref norm(::LazySet, ::Real)) * [`radius`](@ref radius(::LazySet, ::Real)) -* [`diameter`](@ref diameter(::LazySet, ::Real)) * [`singleton_list`](@ref singleton_list(::LazySet)) * [`translate`](@ref translate(::LazySet, ::AbstractVector)) @@ -36,20 +45,20 @@ Inherited from [`AbstractPolytope`](@ref): * [`volume`](@ref volume(::AbstractPolytope)) Inherited from [`AbstractCentrallySymmetricPolytope`](@ref): +* [`an_element`](@ref an_element(::AbstractCentrallySymmetricPolytope)) * [`dim`](@ref dim(::AbstractCentrallySymmetricPolytope)) * [`isempty`](@ref isempty(::AbstractCentrallySymmetricPolytope)) -* [`an_element`](@ref an_element(::AbstractCentrallySymmetricPolytope)) Inherited from [`AbstractZonotope`](@ref): -* [`ρ`](@ref ρ(::AbstractVector, ::AbstractZonotope)) -* [`σ`](@ref σ(::AbstractVector, ::AbstractZonotope)) -* [`∈`](@ref ∈(::AbstractVector, ::AbstractZonotope)) -* [`linear_map`](@ref linear_map(::AbstractMatrix, ::AbstractZonotope)) -* [`split`](@ref split(::AbstractZonotope, ::Int)) -* [`split`](@ref split(::AbstractZonotope, ::AbstractVector{Int}, ::AbstractVector{Int})) * [`constraints_list`](@ref constraints_list(::AbstractZonotope)) * [`constraints_list`](@ref constraints_list(::AbstractZonotope{N}; ::Bool=true) where {N<:AbstractFloat}) -* [`vertices_list`](@ref vertices_list(::AbstractZonotope)) * [`order`](@ref order(::AbstractZonotope)) -* [`reduce_order`](@ref reduce_order(::AbstractZonotope, ::Real, ::AbstractReductionMethod=GIR05())) * [`reflect`](@ref reflect(::AbstractZonotope)) +* [`vertices_list`](@ref vertices_list(::AbstractZonotope)) +* [`∈`](@ref ∈(::AbstractVector, ::AbstractZonotope)) +* [`linear_map`](@ref linear_map(::AbstractMatrix, ::AbstractZonotope)) +* [`reduce_order`](@ref reduce_order(::AbstractZonotope, ::Real, ::AbstractReductionMethod=GIR05())) +* [`split`](@ref split(::AbstractZonotope, ::Int)) +* [`split`](@ref split(::AbstractZonotope, ::AbstractVector{Int}, ::AbstractVector{Int})) +* [`ρ`](@ref ρ(::AbstractVector, ::AbstractZonotope)) +* [`σ`](@ref σ(::AbstractVector, ::AbstractZonotope)) diff --git a/src/ConcreteOperations/intersection.jl b/src/ConcreteOperations/intersection.jl index 2db01ac408..3f2d8f4bac 100644 --- a/src/ConcreteOperations/intersection.jl +++ b/src/ConcreteOperations/intersection.jl @@ -1116,3 +1116,61 @@ end PT<:Union{HPoly,HPolygon,HPolygonOpt}} return intersection!(copy(X), H) end + +""" + _bound_intersect_2D(Z::Zonotope, L::Line2D) + +Evaluate the support function in the direction [0, 1] of the intersection +between the given zonotope and line. + +### Input + +- `Z` -- zonotope +- `L` -- vertical 2D line + +### Output + +The support function in the direction [0, 1] of the intersection between the +given zonotope and line. + +### Notes + +The algorithm assumes that the given line is vertical and that the intersection +between the given sets is not empty. + +### Algorithm + +This function implements [Algorithm 8.2, 1]. + +[1] *Colas Le Guernic. Reachability Analysis of Hybrid Systems with Linear +Continuous Dynamics. Computer Science [cs]. Université Joseph-Fourier - Grenoble +I, 2009. English. fftel-00422569v2f* +""" +function _bound_intersect_2D(Z::Zonotope, L::Line2D) + c = center(Z) + P = copy(c) + G = genmat(Z) + r = ngens(Z) + g(x) = view(G, :, x) + for i in 1:r + gi = g(i) + if !isupwards(gi) + gi .= -gi + end + P .= P - gi + end + G = sortslices(G; dims=2, by=x -> atan(x[2], x[1])) # sort gens + if P[1] < L.b + G .= G[:, end:-1:1] + end + j = 1 + while isdisjoint(LineSegment(P, P + 2g(j)), L) + P .= P + 2g(j) + j += 1 + if j > size(G, 2) + error("got an unexpected error; check that the sets intersect") + end + end + singleton = intersection(LineSegment(P, P + 2g(j)), L) + return element(singleton)[2] +end diff --git a/src/Initialization/init_StaticArraysCore.jl b/src/Initialization/init_StaticArraysCore.jl index 935632dec2..2565d6e388 100644 --- a/src/Initialization/init_StaticArraysCore.jl +++ b/src/Initialization/init_StaticArraysCore.jl @@ -1,6 +1,4 @@ using StaticArraysCore: SMatrix, SVector, MMatrix, MVector -eval(load_split_static()) eval(load_genmat_2D_static()) eval(load_reduce_order_static()) -eval(load_reduce_order_static_zonotope()) diff --git a/src/Interfaces/AbstractZonotope.jl b/src/Interfaces/AbstractZonotope.jl index 45b45517d6..2bf0567855 100644 --- a/src/Interfaces/AbstractZonotope.jl +++ b/src/Interfaces/AbstractZonotope.jl @@ -6,8 +6,8 @@ export AbstractZonotope, ngens, order, togrep, - split!, - reduce_order + reduce_order, + remove_redundant_generators """ AbstractZonotope{N} <: AbstractCentrallySymmetricPolytope{N} diff --git a/src/LazySets.jl b/src/LazySets.jl index d0396404a7..146a2b479a 100644 --- a/src/LazySets.jl +++ b/src/LazySets.jl @@ -201,7 +201,12 @@ include("Sets/Tetrahedron/TetrahedronModule.jl") include("Sets/ZeroSet/ZeroSetModule.jl") @reexport using ..ZeroSetModule: ZeroSet -include("Sets/Zonotope.jl") +include("Sets/Zonotope/ZonotopeModule.jl") +@reexport using ..ZonotopeModule: Zonotope, + remove_zero_generators, + linear_map!, + split! +using ..ZonotopeModule: _split include("Sets/SparsePolynomialZonotope/SparsePolynomialZonotopeModule.jl") @reexport using ..SparsePolynomialZonotopeModule: SparsePolynomialZonotope, SPZ, diff --git a/src/Sets/Zonotope.jl b/src/Sets/Zonotope.jl deleted file mode 100644 index d151d8071d..0000000000 --- a/src/Sets/Zonotope.jl +++ /dev/null @@ -1,640 +0,0 @@ -export Zonotope, - reduce_order, - remove_zero_generators, - remove_redundant_generators - -""" - Zonotope{N, VN<:AbstractVector{N}, MN<:AbstractMatrix{N}} <: AbstractZonotope{N} - -Type that represents a zonotope. - -### Fields - -- `center` -- center of the zonotope -- `generators` -- matrix; each column is a generator of the zonotope - -### Notes - -Mathematically, a zonotope is defined as the set - -```math -Z = \\left\\{ x ∈ ℝ^n : x = c + ∑_{i=1}^p ξ_i g_i,~~ ξ_i ∈ [-1, 1]~~ ∀ i = 1,…, p \\right\\}, -``` -where ``c ∈ ℝ^n`` is its *center* and ``\\{g_i\\}_{i=1}^p``, -``g_i ∈ ℝ^n``, is the set of *generators*. -This characterization defines a zonotope as the finite Minkowski sum of line -segments. -Zonotopes can be equivalently described as the image of a unit infinity-norm -ball in ``ℝ^n`` by an affine transformation. - -Zonotopes can be constructed in two different ways: either passing the -generators as a matrix, where each column represents a generator, or passing a -list of vectors, where each vector represents a generator. Below we illustrate -both ways. - -### Examples - -A two-dimensional zonotope with given center and matrix of generators: - -```jldoctest zonotope_label -julia> Z = Zonotope([1.0, 0.0], [0.1 0.0; 0.0 0.1]) -Zonotope{Float64, Vector{Float64}, Matrix{Float64}}([1.0, 0.0], [0.1 0.0; 0.0 0.1]) - -julia> dim(Z) -2 - -julia> center(Z) -2-element Vector{Float64}: - 1.0 - 0.0 - -julia> genmat(Z) -2×2 Matrix{Float64}: - 0.1 0.0 - 0.0 0.1 -``` -Here, the first vector in the `Zonotope` constructor corresponds to the center -and each column of the second argument corresponds to a generator. The functions -`center` and `genmat` respectively return the center and the generator matrix of -a zonotope. - -We can collect the vertices using `vertices_list`: - -```jldoctest zonotope_label -julia> vertices_list(Z) -4-element Vector{Vector{Float64}}: - [1.1, 0.1] - [0.9, 0.1] - [0.9, -0.1] - [1.1, -0.1] -``` - -The support vector along a given direction can be computed using `σ` -(resp. the support function can be computed using `ρ`): - -```jldoctest zonotope_label -julia> σ([1.0, 1.0], Z) -2-element Vector{Float64}: - 1.1 - 0.1 -``` - -Zonotopes admit an alternative constructor that receives a list of -vectors, each vector representing a generator: - -```jldoctest -julia> Z = Zonotope(ones(2), [[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]]) -Zonotope{Float64, Vector{Float64}, Matrix{Float64}}([1.0, 1.0], [1.0 0.0 1.0; 0.0 1.0 1.0]) - -julia> genmat(Z) -2×3 Matrix{Float64}: - 1.0 0.0 1.0 - 0.0 1.0 1.0 -``` -""" -struct Zonotope{N,VN<:AbstractVector{N},MN<:AbstractMatrix{N}} <: AbstractZonotope{N} - center::VN - generators::MN - - function Zonotope(center::VN, - generators::MN) where {N, - VN<:AbstractVector{N}, - MN<:AbstractMatrix{N}} - @assert length(center) == size(generators, 1) "the dimension of the " * - "center ($(length(center))) and the generators " * - "($(size(generators, 1))) need to match" - return new{N,VN,MN}(center, generators) - end -end - -isoperationtype(::Type{<:Zonotope}) = false - -# constructor from center and list of generators -function Zonotope(center::VN, generators_list::AbstractVector{VN}) where {VN<:AbstractVector} - G = to_matrix(generators_list, length(center)) - return Zonotope(center, G) -end - -""" - remove_zero_generators(Z::Zonotope) - -Return a new zonotope removing the generators that are zero. - -### Input - -- `Z` -- zonotope - -### Output - -If there are no zero generators, the result is the original zonotope `Z`. -Otherwise the result is a new zonotope that has the center and generators as `Z` -except for those generators that are zero. -""" -function remove_zero_generators(Z::Zonotope) - G = Z.generators - G2 = remove_zero_columns(G) - if G === G2 - return Z - end - return Zonotope(Z.center, G2) -end - -""" - center(Z::Zonotope) - -Return the center of a zonotope. - -### Input - -- `Z` -- zonotope - -### Output - -The center of the zonotope. -""" -function center(Z::Zonotope) - return Z.center -end - -""" - togrep(Z::Zonotope) - -Return a generator representation of a zonotope. - -### Input - -- `Z` -- zonotope - -### Output - -The same set `Z`. -""" -function togrep(Z::Zonotope) - return Z -end - -""" - genmat(Z::Zonotope) - -Return the generator matrix of a zonotope. - -### Input - -- `Z` -- zonotope - -### Output - -A matrix where each column represents one generator of the zonotope `Z`. -""" -function genmat(Z::Zonotope) - return Z.generators -end - -""" - generators(Z::Zonotope) - -Return an iterator over the generators of a zonotope. - -### Input - -- `Z` -- zonotope - -### Output - -An iterator over the generators of `Z`. -""" -function generators(Z::Zonotope) - return generators_fallback(Z) -end - -""" - ngens(Z::Zonotope) - -Return the number of generators of a zonotope. - -### Input - -- `Z` -- zonotope - -### Output - -An integer representing the number of generators. -""" -ngens(Z::Zonotope) = size(Z.generators, 2) - -""" - rand(::Type{Zonotope}; [N]::Type{<:Real}=Float64, [dim]::Int=2, - [rng]::AbstractRNG=GLOBAL_RNG, [seed]::Union{Int, Nothing}=nothing) - -Create a random zonotope. - -### Input - -- `Zonotope` -- type for dispatch -- `N` -- (optional, default: `Float64`) numeric type -- `dim` -- (optional, default: 2) dimension -- `rng` -- (optional, default: `GLOBAL_RNG`) random number generator -- `seed` -- (optional, default: `nothing`) seed for reseeding -- `num_generators` -- (optional, default: `-1`) number of generators of the - zonotope (see the comment below) - -### Output - -A random zonotope. - -### Algorithm - -All numbers are normally distributed with mean 0 and standard deviation 1. - -The number of generators can be controlled with the argument `num_generators`. -For a negative value we choose a random number in the range `dim:2*dim` (except -if `dim == 1`, in which case we only create a single generator). -""" -function rand(::Type{Zonotope}; - N::Type{<:Real}=Float64, - dim::Int=2, - rng::AbstractRNG=GLOBAL_RNG, - seed::Union{Int,Nothing}=nothing, - num_generators::Int=-1) - rng = reseed!(rng, seed) - center = randn(rng, N, dim) - if num_generators < 0 - num_generators = (dim == 1) ? 1 : rand(rng, dim:(2 * dim)) - end - generators = randn(rng, N, dim, num_generators) - return Zonotope(center, generators) -end - -""" - scale!(α::Real, Z::Zonotope) - -Concrete scaling of a zonotope modifying `Z` in-place. - -### Input - -- `α` -- scalar -- `Z` -- zonotope - -### Output - -The zonotope obtained by applying the numerical scale to the center and -generators of ``Z``. -""" -function scale!(α::Real, Z::Zonotope) - Z.center .*= α - Z.generators .*= α - return Z -end - -function _split(Z::Zonotope, j::Int) - c, G = Z.center, Z.generators - - c₁ = similar(c) - G₁ = similar(G) - Z₁ = Zonotope(c₁, G₁) - - c₂ = similar(c) - G₂ = similar(G) - Z₂ = Zonotope(c₂, G₂) - - return split!(Z₁, Z₂, Z, j) -end - -function split!(Z₁::Zonotope, Z₂::Zonotope, Z::Zonotope, j::Int) - c, G = Z.center, Z.generators - n, p = size(G) - @assert 1 <= j <= p "cannot split a zonotope with $p generators along " * - "index $j" - - c₁, G₁ = Z₁.center, Z₁.generators - c₂, G₂ = Z₂.center, Z₂.generators - copyto!(G₁, G) - - @inbounds for i in 1:n - α = G[i, j] / 2 - c₁[i] = c[i] - α - c₂[i] = c[i] + α - G₁[i, j] = α - end - copyto!(G₂, G₁) - - return _split_ret(Z₁, Z₂) -end - -_split_ret(Z₁::Zonotope, Z₂::Zonotope) = (Z₁, Z₂) - -function load_split_static() - return quote - function _split_ret(Z₁::Zonotope{N,SV,SM}, - Z₂::Zonotope{N,SV,SM}) where {N,n,p,SV<:MVector{n,N},SM<:MMatrix{n,p,N}} - Z₁ = Zonotope(SVector{n}(Z₁.center), SMatrix{n,p}(Z₁.generators)) - Z₂ = Zonotope(SVector{n}(Z₂.center), SMatrix{n,p}(Z₂.generators)) - return Z₁, Z₂ - end - end -end # quote / load_split_static - -function _split(Z::Zonotope, gens::AbstractVector, n::AbstractVector) - p = length(gens) - @assert p == length(n) "the number of generators $(length(n)) does not " * - "match the number of indicated partitions $p" - - @assert p <= ngens(Z) "the number of generators to split ($p) is greater " * - "than the number of generators of the zonotope ($(ngens(Z)))" - - Zs = [Z] - for (i, g) in enumerate(gens) - for j in 1:n[i] - km = length(Zs) - for k in 1:km - append!(Zs, split(Zs[k], g)) - end - deleteat!(Zs, 1:km) - end - end - return Zs -end - -""" - linear_map!(Zout::Zonotope, M::AbstractMatrix, Z::Zonotope) - -Compute the concrete linear map of a zonotope, storing the result in `Zout`. - -### Input - -- `Zout` -- zonotope (output) -- `M` -- matrix -- `Z` -- zonotope - -### Output - -The zonotope `Zout`, which is modified in-place. -""" -function linear_map!(Zout::Zonotope, M::AbstractMatrix, Z::Zonotope) - mul!(Zout.center, M, Z.center) - mul!(Zout.generators, M, Z.generators) - return Zout -end - -""" - _bound_intersect_2D(Z::Zonotope, L::Line2D) - -Evaluate the support function in the direction [0, 1] of the intersection -between the given zonotope and line. - -### Input - -- `Z` -- zonotope -- `L` -- vertical 2D line - -### Output - -The support function in the direction [0, 1] of the intersection between the -given zonotope and line. - -### Notes - -The algorithm assumes that the given line is vertical and that the intersection -between the given sets is not empty. - -### Algorithm - -This function implements [Algorithm 8.2, 1]. - -[1] *Colas Le Guernic. Reachability Analysis of Hybrid Systems with Linear -Continuous Dynamics. Computer Science [cs]. Université Joseph-Fourier - Grenoble -I, 2009. English. fftel-00422569v2f* -""" -function _bound_intersect_2D(Z::Zonotope, L::Line2D) - c = center(Z) - P = copy(c) - G = genmat(Z) - r = ngens(Z) - g(x) = view(G, :, x) - for i in 1:r - gi = g(i) - if !isupwards(gi) - gi .= -gi - end - P .= P - gi - end - G = sortslices(G; dims=2, by=x -> atan(x[2], x[1])) # sort gens - if P[1] < L.b - G .= G[:, end:-1:1] - end - j = 1 - while isdisjoint(LineSegment(P, P + 2g(j)), L) - P .= P + 2g(j) - j += 1 - if j > size(G, 2) - error("got an unexpected error; check that the sets intersect") - end - end - singleton = intersection(LineSegment(P, P + 2g(j)), L) - return element(singleton)[2] -end - -# ==================================== -# Zonotope vertex enumeration methods -# ==================================== - -@inline function _angles(point::AbstractVector{N}) where {N} - return (atand(point[2], point[1]) + 360) % 360 -end - -function _single_quadrant_vertices_enum(G::AbstractMatrix{N}, sorted::Bool=false) where {N} - if !sorted - G = sortslices(G; dims=2, by=_angles) - end - return VPolygon(2 * cumsum(hcat(G, -G); dims=2) .- sum(G; dims=2)) -end - -function _vertices_list_2D(c::AbstractVector{N}, G::AbstractMatrix{N}; - apply_convex_hull::Bool) where {N} - if apply_convex_hull - return _vertices_list_iterative(c, G; apply_convex_hull=apply_convex_hull) - end - angles = mapslices(_angles, G; dims=1)[1, :] - perm = sortperm(angles) - sorted_angles = angles[perm] - sorted_G = G[:, perm] - polygons = Vector{VPolygon{N}}() - sizehint!(polygons, 4) - - @inbounds for i in zip(0:90:360, 90:90:360) - index = i[1] .<= sorted_angles .< i[2] - if sum(index) > 0 - push!(polygons, _single_quadrant_vertices_enum(sorted_G[:, index], true)) - end - end - - return vertices_list(translate(reduce(minkowski_sum, polygons), c)) -end - -""" - remove_redundant_generators(Z::Zonotope{N}) where {N} - -Remove all redundant (pairwise linearly dependent) generators of a zonotope. - -### Input - -- `Z` -- zonotope - -### Output - -A new zonotope with fewer generators, or the same zonotope if no generator could -be removed. - -### Algorithm - -For each generator ``g_j`` that has not been checked yet, we find all other -generators that are linearly dependent with ``g_j``. -Then we combine those generators into a single generator. - -For one-dimensional zonotopes we use a more efficient implementation where we -just take the absolute sum of all generators. -""" -function remove_redundant_generators(Z::Zonotope{N}) where {N} - if dim(Z) == 1 # more efficient implementation in 1D - return _remove_redundant_generators_1d(Z) - end - - G = genmat(Z) - G = remove_zero_columns(G) - p = size(G, 2) - removed_zero_generators = p < ngens(Z) - deleted = false - done = falses(p) - G_new = vector_type(typeof(G))[] # list of new column vectors - @inbounds for j1 in 1:p - if done[j1] # skip if the generator was already removed - continue - end - # "done[j1] = true" not needed because we will never look at it again - gj1 = G[:, j1] - for j2 in (j1 + 1):p # look at all generators to the right - if done[j2] # skip if the generator was already removed - continue - end - gj2 = G[:, j2] - answer, factor = ismultiple(gj1, gj2) - if answer - # column j2 is a multiple of column j1 - if factor > zero(N) - gj1 += gj2 - else - gj1 -= gj2 - end - done[j2] = true - deleted = true - end - end - push!(G_new, gj1) - end - - if deleted - G_new = reduce(hcat, G_new) # convert list of column vectors to matrix - return Zonotope(center(Z), G_new) - elseif removed_zero_generators - return Zonotope(center(Z), G) - end - return Z # return the original zonotope if no generator was removed -end - -function _remove_redundant_generators_1d(Z) - G = genmat(Z) - g = sum(abs, G) - return Zonotope(center(Z), hcat(g)) -end - -""" - low(Z::Zonotope, i::Int) - -Return the lower coordinate of a zonotope in a given dimension. - -### Input - -- `Z` -- zonotope -- `i` -- dimension of interest - -### Output - -The lower coordinate of the zonotope in the given dimension. -""" -function low(Z::Zonotope, i::Int) - G = genmat(Z) - v = center(Z, i) - @inbounds for j in 1:ngens(Z) - v -= abs(G[i, j]) - end - return v -end - -""" - high(Z::Zonotope, i::Int) - -Return the higher coordinate of a zonotope in a given dimension. - -### Input - -- `Z` -- zonotope -- `i` -- dimension of interest - -### Output - -The higher coordinate of the zonotope in the given dimension. -""" -function high(Z::Zonotope, i::Int) - G = genmat(Z) - v = center(Z, i) - @inbounds for j in 1:ngens(Z) - v += abs(G[i, j]) - end - return v -end - -function load_reduce_order_static_zonotope() - return quote - # conversion for static matrix - function reduce_order(Z::Zonotope{N,<:AbstractVector,<:MMatrix}, r::Real, - method::AbstractReductionMethod=GIR05()) where {N} - return reduce_order(Zonotope(center(Z), SMatrix(genmat(Z))), r, method) - end - end -end # quote / load_reduce_order_static_zonotope - -function permute(Z::Zonotope, p::AbstractVector{Int}) - c = Z.center[p] - G = Z.generators[p, :] - return Zonotope(c, G) -end - -""" - translate!(Z::Zonotope, v::AbstractVector) - -Translate (i.e., shift) a zonotope by a given vector in-place. - -### Input - -- `Z` -- zonotope -- `v` -- translation vector - -### Output - -A translated zonotope. - -### Notes - -See also [`translate(Z::LazySet, v::AbstractVector)`](@ref) for the -out-of-place version. - -### Algorithm - -We add the translation vector to the center of the zonotope. -""" -function translate!(Z::Zonotope, v::AbstractVector) - @assert length(v) == dim(Z) "cannot translate a $(dim(Z))-dimensional " * - "set by a $(length(v))-dimensional vector" - Z.center .+= v - return Z -end diff --git a/src/Sets/Zonotope/Zonotope.jl b/src/Sets/Zonotope/Zonotope.jl new file mode 100644 index 0000000000..762bd0697e --- /dev/null +++ b/src/Sets/Zonotope/Zonotope.jl @@ -0,0 +1,109 @@ +""" + Zonotope{N, VN<:AbstractVector{N}, MN<:AbstractMatrix{N}} <: AbstractZonotope{N} + +Type that represents a zonotope. + +### Fields + +- `center` -- center of the zonotope +- `generators` -- matrix; each column is a generator of the zonotope + +### Notes + +Mathematically, a zonotope is defined as the set + +```math +Z = \\left\\{ x ∈ ℝ^n : x = c + ∑_{i=1}^p ξ_i g_i,~~ ξ_i ∈ [-1, 1]~~ ∀ i = 1,…, p \\right\\}, +``` +where ``c ∈ ℝ^n`` is its *center* and ``\\{g_i\\}_{i=1}^p``, +``g_i ∈ ℝ^n``, is the set of *generators*. +This characterization defines a zonotope as the finite Minkowski sum of line +segments. +Zonotopes can be equivalently described as the image of a unit infinity-norm +ball in ``ℝ^n`` by an affine transformation. + +Zonotopes can be constructed in two different ways: either passing the +generators as a matrix, where each column represents a generator, or passing a +list of vectors, where each vector represents a generator. Below we illustrate +both ways. + +### Examples + +A two-dimensional zonotope with given center and matrix of generators: + +```jldoctest zonotope_label +julia> Z = Zonotope([1.0, 0.0], [0.1 0.0; 0.0 0.1]) +Zonotope{Float64, Vector{Float64}, Matrix{Float64}}([1.0, 0.0], [0.1 0.0; 0.0 0.1]) + +julia> dim(Z) +2 + +julia> center(Z) +2-element Vector{Float64}: + 1.0 + 0.0 + +julia> genmat(Z) +2×2 Matrix{Float64}: + 0.1 0.0 + 0.0 0.1 +``` +Here, the first vector in the `Zonotope` constructor corresponds to the center +and each column of the second argument corresponds to a generator. The functions +`center` and `genmat` respectively return the center and the generator matrix of +a zonotope. + +We can collect the vertices using `vertices_list`: + +```jldoctest zonotope_label +julia> vertices_list(Z) +4-element Vector{Vector{Float64}}: + [1.1, 0.1] + [0.9, 0.1] + [0.9, -0.1] + [1.1, -0.1] +``` + +The support vector along a given direction can be computed using `σ` +(resp. the support function can be computed using `ρ`): + +```jldoctest zonotope_label +julia> σ([1.0, 1.0], Z) +2-element Vector{Float64}: + 1.1 + 0.1 +``` + +Zonotopes admit an alternative constructor that receives a list of +vectors, each vector representing a generator: + +```jldoctest +julia> Z = Zonotope(ones(2), [[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]]) +Zonotope{Float64, Vector{Float64}, Matrix{Float64}}([1.0, 1.0], [1.0 0.0 1.0; 0.0 1.0 1.0]) + +julia> genmat(Z) +2×3 Matrix{Float64}: + 1.0 0.0 1.0 + 0.0 1.0 1.0 +``` +""" +struct Zonotope{N,VN<:AbstractVector{N},MN<:AbstractMatrix{N}} <: AbstractZonotope{N} + center::VN + generators::MN + + function Zonotope(center::VN, + generators::MN) where {N, + VN<:AbstractVector{N}, + MN<:AbstractMatrix{N}} + @assert length(center) == size(generators, 1) "the dimension of the " * + "center ($(length(center))) and the generators " * + "($(size(generators, 1))) need to match" + return new{N,VN,MN}(center, generators) + end +end + +# constructor from center and list of generators +function Zonotope(center::VN, generators_list::AbstractVector{VN}) where {VN<:AbstractVector} + G = to_matrix(generators_list, length(center)) + return Zonotope(center, G) +end diff --git a/src/Sets/Zonotope/ZonotopeModule.jl b/src/Sets/Zonotope/ZonotopeModule.jl new file mode 100644 index 0000000000..abfc017e28 --- /dev/null +++ b/src/Sets/Zonotope/ZonotopeModule.jl @@ -0,0 +1,49 @@ +module ZonotopeModule + +using Reexport, Requires + +using ..LazySets: AbstractZonotope, generators_fallback, + _vertices_list_zonotope_iterative +using Random: AbstractRNG, GLOBAL_RNG +using ReachabilityBase.Arrays: ismultiple, remove_zero_columns, to_matrix, + vector_type +using ReachabilityBase.Distribution: reseed! +using ReachabilityBase.Require: require +using LinearAlgebra: mul! + +@reexport import ..API: center, high, isoperationtype, low, rand, vertices_list, + permute, scale!, translate! +@reexport import ..LazySets: generators, genmat, ngens, reduce_order, + remove_redundant_generators, togrep +@reexport using ..API + +export Zonotope, + remove_zero_generators, + linear_map!, + split! + +include("Zonotope.jl") + +include("center.jl") +include("high.jl") +include("isoperationtype.jl") +include("low.jl") +include("rand.jl") +include("vertices_list.jl") +include("linear_map.jl") +include("permute.jl") +include("scale.jl") +include("translate.jl") + +include("generators.jl") +include("genmat.jl") +include("ngens.jl") +include("reduce_order.jl") +include("remove_redundant_generators.jl") +include("togrep.jl") +include("remove_zero_generators.jl") +include("split.jl") + +include("init.jl") + +end # module diff --git a/src/Sets/Zonotope/center.jl b/src/Sets/Zonotope/center.jl new file mode 100644 index 0000000000..3a4f0fe011 --- /dev/null +++ b/src/Sets/Zonotope/center.jl @@ -0,0 +1,16 @@ +""" + center(Z::Zonotope) + +Return the center of a zonotope. + +### Input + +- `Z` -- zonotope + +### Output + +The center of the zonotope. +""" +function center(Z::Zonotope) + return Z.center +end diff --git a/src/Sets/Zonotope/generators.jl b/src/Sets/Zonotope/generators.jl new file mode 100644 index 0000000000..7ad24c267a --- /dev/null +++ b/src/Sets/Zonotope/generators.jl @@ -0,0 +1,16 @@ +""" + generators(Z::Zonotope) + +Return an iterator over the generators of a zonotope. + +### Input + +- `Z` -- zonotope + +### Output + +An iterator over the generators of `Z`. +""" +function generators(Z::Zonotope) + return generators_fallback(Z) +end diff --git a/src/Sets/Zonotope/genmat.jl b/src/Sets/Zonotope/genmat.jl new file mode 100644 index 0000000000..6ac350f8a0 --- /dev/null +++ b/src/Sets/Zonotope/genmat.jl @@ -0,0 +1,16 @@ +""" + genmat(Z::Zonotope) + +Return the generator matrix of a zonotope. + +### Input + +- `Z` -- zonotope + +### Output + +A matrix where each column represents one generator of the zonotope `Z`. +""" +function genmat(Z::Zonotope) + return Z.generators +end diff --git a/src/Sets/Zonotope/high.jl b/src/Sets/Zonotope/high.jl new file mode 100644 index 0000000000..89b42deba1 --- /dev/null +++ b/src/Sets/Zonotope/high.jl @@ -0,0 +1,22 @@ +""" + high(Z::Zonotope, i::Int) + +Return the higher coordinate of a zonotope in a given dimension. + +### Input + +- `Z` -- zonotope +- `i` -- dimension of interest + +### Output + +The higher coordinate of the zonotope in the given dimension. +""" +function high(Z::Zonotope, i::Int) + G = genmat(Z) + v = center(Z, i) + @inbounds for j in 1:ngens(Z) + v += abs(G[i, j]) + end + return v +end diff --git a/src/Sets/Zonotope/init.jl b/src/Sets/Zonotope/init.jl new file mode 100644 index 0000000000..1867dd7a3d --- /dev/null +++ b/src/Sets/Zonotope/init.jl @@ -0,0 +1,4 @@ +function __init__() + @require LazySets = "b4f0291d-fe17-52bc-9479-3d1a343d9043" include("init_LazySets.jl") + @require StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" include("init_StaticArraysCore.jl") +end diff --git a/src/Sets/Zonotope/init_LazySets.jl b/src/Sets/Zonotope/init_LazySets.jl new file mode 100644 index 0000000000..bb2e14382f --- /dev/null +++ b/src/Sets/Zonotope/init_LazySets.jl @@ -0,0 +1 @@ +using .LazySets.VPolygonModule: VPolygon diff --git a/src/Sets/Zonotope/init_StaticArraysCore.jl b/src/Sets/Zonotope/init_StaticArraysCore.jl new file mode 100644 index 0000000000..80351aa77d --- /dev/null +++ b/src/Sets/Zonotope/init_StaticArraysCore.jl @@ -0,0 +1,4 @@ +using StaticArraysCore: SMatrix, SVector, MMatrix, MVector + +eval(load_split_static()) +eval(load_reduce_order_static_zonotope()) diff --git a/src/Sets/Zonotope/isoperationtype.jl b/src/Sets/Zonotope/isoperationtype.jl new file mode 100644 index 0000000000..eacd2f02e5 --- /dev/null +++ b/src/Sets/Zonotope/isoperationtype.jl @@ -0,0 +1 @@ +isoperationtype(::Type{<:Zonotope}) = false diff --git a/src/Sets/Zonotope/linear_map.jl b/src/Sets/Zonotope/linear_map.jl new file mode 100644 index 0000000000..463fe9225d --- /dev/null +++ b/src/Sets/Zonotope/linear_map.jl @@ -0,0 +1,20 @@ +""" + linear_map!(Zout::Zonotope, M::AbstractMatrix, Z::Zonotope) + +Compute the concrete linear map of a zonotope, storing the result in `Zout`. + +### Input + +- `Zout` -- zonotope (output) +- `M` -- matrix +- `Z` -- zonotope + +### Output + +The zonotope `Zout`, which is modified in-place. +""" +function linear_map!(Zout::Zonotope, M::AbstractMatrix, Z::Zonotope) + mul!(Zout.center, M, Z.center) + mul!(Zout.generators, M, Z.generators) + return Zout +end diff --git a/src/Sets/Zonotope/low.jl b/src/Sets/Zonotope/low.jl new file mode 100644 index 0000000000..e4a33f4bea --- /dev/null +++ b/src/Sets/Zonotope/low.jl @@ -0,0 +1,22 @@ +""" + low(Z::Zonotope, i::Int) + +Return the lower coordinate of a zonotope in a given dimension. + +### Input + +- `Z` -- zonotope +- `i` -- dimension of interest + +### Output + +The lower coordinate of the zonotope in the given dimension. +""" +function low(Z::Zonotope, i::Int) + G = genmat(Z) + v = center(Z, i) + @inbounds for j in 1:ngens(Z) + v -= abs(G[i, j]) + end + return v +end diff --git a/src/Sets/Zonotope/ngens.jl b/src/Sets/Zonotope/ngens.jl new file mode 100644 index 0000000000..8b8affa53e --- /dev/null +++ b/src/Sets/Zonotope/ngens.jl @@ -0,0 +1,14 @@ +""" + ngens(Z::Zonotope) + +Return the number of generators of a zonotope. + +### Input + +- `Z` -- zonotope + +### Output + +An integer representing the number of generators. +""" +ngens(Z::Zonotope) = size(Z.generators, 2) diff --git a/src/Sets/Zonotope/permute.jl b/src/Sets/Zonotope/permute.jl new file mode 100644 index 0000000000..ac9b75b6c3 --- /dev/null +++ b/src/Sets/Zonotope/permute.jl @@ -0,0 +1,5 @@ +function permute(Z::Zonotope, p::AbstractVector{Int}) + c = Z.center[p] + G = Z.generators[p, :] + return Zonotope(c, G) +end diff --git a/src/Sets/Zonotope/rand.jl b/src/Sets/Zonotope/rand.jl new file mode 100644 index 0000000000..43e8aacf0e --- /dev/null +++ b/src/Sets/Zonotope/rand.jl @@ -0,0 +1,42 @@ +""" + rand(::Type{Zonotope}; [N]::Type{<:Real}=Float64, [dim]::Int=2, + [rng]::AbstractRNG=GLOBAL_RNG, [seed]::Union{Int, Nothing}=nothing) + +Create a random zonotope. + +### Input + +- `Zonotope` -- type for dispatch +- `N` -- (optional, default: `Float64`) numeric type +- `dim` -- (optional, default: 2) dimension +- `rng` -- (optional, default: `GLOBAL_RNG`) random number generator +- `seed` -- (optional, default: `nothing`) seed for reseeding +- `num_generators` -- (optional, default: `-1`) number of generators of the + zonotope (see the comment below) + +### Output + +A random zonotope. + +### Algorithm + +All numbers are normally distributed with mean 0 and standard deviation 1. + +The number of generators can be controlled with the argument `num_generators`. +For a negative value we choose a random number in the range `dim:2*dim` (except +if `dim == 1`, in which case we only create a single generator). +""" +function rand(::Type{Zonotope}; + N::Type{<:Real}=Float64, + dim::Int=2, + rng::AbstractRNG=GLOBAL_RNG, + seed::Union{Int,Nothing}=nothing, + num_generators::Int=-1) + rng = reseed!(rng, seed) + center = randn(rng, N, dim) + if num_generators < 0 + num_generators = (dim == 1) ? 1 : rand(rng, dim:(2 * dim)) + end + generators = randn(rng, N, dim, num_generators) + return Zonotope(center, generators) +end diff --git a/src/Sets/Zonotope/reduce_order.jl b/src/Sets/Zonotope/reduce_order.jl new file mode 100644 index 0000000000..b6abc4c562 --- /dev/null +++ b/src/Sets/Zonotope/reduce_order.jl @@ -0,0 +1,11 @@ +function load_reduce_order_static_zonotope() + return quote + using ..LazySets: AbstractReductionMethod + + # conversion for static matrix + function reduce_order(Z::Zonotope{N,<:AbstractVector,<:MMatrix}, r::Real, + method::AbstractReductionMethod=GIR05()) where {N} + return reduce_order(Zonotope(center(Z), SMatrix(genmat(Z))), r, method) + end + end +end # quote / load_reduce_order_static_zonotope diff --git a/src/Sets/Zonotope/remove_redundant_generators.jl b/src/Sets/Zonotope/remove_redundant_generators.jl new file mode 100644 index 0000000000..57077dff91 --- /dev/null +++ b/src/Sets/Zonotope/remove_redundant_generators.jl @@ -0,0 +1,75 @@ +""" + remove_redundant_generators(Z::Zonotope{N}) where {N} + +Remove all redundant (pairwise linearly dependent) generators of a zonotope. + +### Input + +- `Z` -- zonotope + +### Output + +A new zonotope with fewer generators, or the same zonotope if no generator could +be removed. + +### Algorithm + +For each generator ``g_j`` that has not been checked yet, we find all other +generators that are linearly dependent with ``g_j``. +Then we combine those generators into a single generator. + +For one-dimensional zonotopes we use a more efficient implementation where we +just take the absolute sum of all generators. +""" +function remove_redundant_generators(Z::Zonotope{N}) where {N} + if dim(Z) == 1 # more efficient implementation in 1D + return _remove_redundant_generators_1d(Z) + end + + G = genmat(Z) + G = remove_zero_columns(G) + p = size(G, 2) + removed_zero_generators = p < ngens(Z) + deleted = false + done = falses(p) + G_new = vector_type(typeof(G))[] # list of new column vectors + @inbounds for j1 in 1:p + if done[j1] # skip if the generator was already removed + continue + end + # "done[j1] = true" not needed because we will never look at it again + gj1 = G[:, j1] + for j2 in (j1 + 1):p # look at all generators to the right + if done[j2] # skip if the generator was already removed + continue + end + gj2 = G[:, j2] + answer, factor = ismultiple(gj1, gj2) + if answer + # column j2 is a multiple of column j1 + if factor > zero(N) + gj1 += gj2 + else + gj1 -= gj2 + end + done[j2] = true + deleted = true + end + end + push!(G_new, gj1) + end + + if deleted + G_new = reduce(hcat, G_new) # convert list of column vectors to matrix + return Zonotope(center(Z), G_new) + elseif removed_zero_generators + return Zonotope(center(Z), G) + end + return Z # return the original zonotope if no generator was removed +end + +function _remove_redundant_generators_1d(Z) + G = genmat(Z) + g = sum(abs, G) + return Zonotope(center(Z), hcat(g)) +end diff --git a/src/Sets/Zonotope/remove_zero_generators.jl b/src/Sets/Zonotope/remove_zero_generators.jl new file mode 100644 index 0000000000..7626e95e1d --- /dev/null +++ b/src/Sets/Zonotope/remove_zero_generators.jl @@ -0,0 +1,23 @@ +""" + remove_zero_generators(Z::Zonotope) + +Return a new zonotope removing the generators that are zero. + +### Input + +- `Z` -- zonotope + +### Output + +If there are no zero generators, the result is the original zonotope `Z`. +Otherwise the result is a new zonotope that has the center and generators as `Z` +except for those generators that are zero. +""" +function remove_zero_generators(Z::Zonotope) + G = Z.generators + G2 = remove_zero_columns(G) + if G === G2 + return Z + end + return Zonotope(Z.center, G2) +end diff --git a/src/Sets/Zonotope/scale.jl b/src/Sets/Zonotope/scale.jl new file mode 100644 index 0000000000..fb84e35c07 --- /dev/null +++ b/src/Sets/Zonotope/scale.jl @@ -0,0 +1,20 @@ +""" + scale!(α::Real, Z::Zonotope) + +Concrete scaling of a zonotope modifying `Z` in-place. + +### Input + +- `α` -- scalar +- `Z` -- zonotope + +### Output + +The zonotope obtained by applying the numerical scale to the center and +generators of ``Z``. +""" +function scale!(α::Real, Z::Zonotope) + Z.center .*= α + Z.generators .*= α + return Z +end diff --git a/src/Sets/Zonotope/split.jl b/src/Sets/Zonotope/split.jl new file mode 100644 index 0000000000..1e74b0b7db --- /dev/null +++ b/src/Sets/Zonotope/split.jl @@ -0,0 +1,68 @@ +function _split(Z::Zonotope, j::Int) + c, G = Z.center, Z.generators + + c₁ = similar(c) + G₁ = similar(G) + Z₁ = Zonotope(c₁, G₁) + + c₂ = similar(c) + G₂ = similar(G) + Z₂ = Zonotope(c₂, G₂) + + return split!(Z₁, Z₂, Z, j) +end + +function split!(Z₁::Zonotope, Z₂::Zonotope, Z::Zonotope, j::Int) + c, G = Z.center, Z.generators + n, p = size(G) + @assert 1 <= j <= p "cannot split a zonotope with $p generators along " * + "index $j" + + c₁, G₁ = Z₁.center, Z₁.generators + c₂, G₂ = Z₂.center, Z₂.generators + copyto!(G₁, G) + + @inbounds for i in 1:n + α = G[i, j] / 2 + c₁[i] = c[i] - α + c₂[i] = c[i] + α + G₁[i, j] = α + end + copyto!(G₂, G₁) + + return _split_ret(Z₁, Z₂) +end + +_split_ret(Z₁::Zonotope, Z₂::Zonotope) = (Z₁, Z₂) + +function load_split_static() + return quote + function _split_ret(Z₁::Zonotope{N,SV,SM}, + Z₂::Zonotope{N,SV,SM}) where {N,n,p,SV<:MVector{n,N},SM<:MMatrix{n,p,N}} + Z₁ = Zonotope(SVector{n}(Z₁.center), SMatrix{n,p}(Z₁.generators)) + Z₂ = Zonotope(SVector{n}(Z₂.center), SMatrix{n,p}(Z₂.generators)) + return Z₁, Z₂ + end + end +end # quote / load_split_static + +function _split(Z::Zonotope, gens::AbstractVector, n::AbstractVector) + p = length(gens) + @assert p == length(n) "the number of generators $(length(n)) does not " * + "match the number of indicated partitions $p" + + @assert p <= ngens(Z) "the number of generators to split ($p) is greater " * + "than the number of generators of the zonotope ($(ngens(Z)))" + + Zs = [Z] + for (i, g) in enumerate(gens) + for j in 1:n[i] + km = length(Zs) + for k in 1:km + append!(Zs, split(Zs[k], g)) + end + deleteat!(Zs, 1:km) + end + end + return Zs +end diff --git a/src/Sets/Zonotope/togrep.jl b/src/Sets/Zonotope/togrep.jl new file mode 100644 index 0000000000..d18e2173e9 --- /dev/null +++ b/src/Sets/Zonotope/togrep.jl @@ -0,0 +1,16 @@ +""" + togrep(Z::Zonotope) + +Return a generator representation of a zonotope. + +### Input + +- `Z` -- zonotope + +### Output + +The same set `Z`. +""" +function togrep(Z::Zonotope) + return Z +end diff --git a/src/Sets/Zonotope/translate.jl b/src/Sets/Zonotope/translate.jl new file mode 100644 index 0000000000..f5b272fa8b --- /dev/null +++ b/src/Sets/Zonotope/translate.jl @@ -0,0 +1,29 @@ +""" + translate!(Z::Zonotope, v::AbstractVector) + +Translate (i.e., shift) a zonotope by a given vector in-place. + +### Input + +- `Z` -- zonotope +- `v` -- translation vector + +### Output + +A translated zonotope. + +### Notes + +See also [`LazySets.API.translate(Z::LazySet, v::AbstractVector)`](@ref) for the +out-of-place version. + +### Algorithm + +We add the translation vector to the center of the zonotope. +""" +function translate!(Z::Zonotope, v::AbstractVector) + @assert length(v) == dim(Z) "cannot translate a $(dim(Z))-dimensional " * + "set by a $(length(v))-dimensional vector" + Z.center .+= v + return Z +end diff --git a/src/Sets/Zonotope/vertices_list.jl b/src/Sets/Zonotope/vertices_list.jl new file mode 100644 index 0000000000..a4ec64db6f --- /dev/null +++ b/src/Sets/Zonotope/vertices_list.jl @@ -0,0 +1,35 @@ +function _vertices_list_2D(c::AbstractVector{N}, G::AbstractMatrix{N}; + apply_convex_hull::Bool) where {N} + if apply_convex_hull + return _vertices_list_zonotope_iterative(c, G; apply_convex_hull=apply_convex_hull) + end + + require(@__MODULE__, :LazySets; fun_name="_vertices_list_2D") + + angles = mapslices(_angles, G; dims=1)[1, :] + perm = sortperm(angles) + sorted_angles = angles[perm] + sorted_G = G[:, perm] + polygons = Vector{VPolygon{N}}() + sizehint!(polygons, 4) + + @inbounds for i in zip(0:90:360, 90:90:360) + index = i[1] .<= sorted_angles .< i[2] + if sum(index) > 0 + push!(polygons, _single_quadrant_vertices_enum(sorted_G[:, index], true)) + end + end + + return vertices_list(translate(reduce(minkowski_sum, polygons), c)) +end + +@inline function _angles(point::AbstractVector{N}) where {N} + return (atand(point[2], point[1]) + 360) % 360 +end + +function _single_quadrant_vertices_enum(G::AbstractMatrix{N}, sorted::Bool=false) where {N} + if !sorted + G = sortslices(G; dims=2, by=_angles) + end + return VPolygon(2 * cumsum(hcat(G, -G); dims=2) .- sum(G; dims=2)) +end diff --git a/test/Sets/Zonotope.jl b/test/Sets/Zonotope.jl index 98145a5d04..b28b5cf489 100644 --- a/test/Sets/Zonotope.jl +++ b/test/Sets/Zonotope.jl @@ -1,6 +1,11 @@ for N in [Float64, Rational{Int}, Float32] # random zonotope rand(Zonotope) + + # constructor from list of generators + Z = Zonotope(N[1, 1], [N[1, 2], N[3, 4]]) + @test Z isa Zonotope{N} && Z == Zonotope(N[1, 1], N[1 3; 2 4]) + # Test dimension assertion @test_throws AssertionError Zonotope(N[1, 1], [N[0 0]; N[1 // 2 0]; N[0 1 // 2]; N[0 0]]) @@ -346,6 +351,11 @@ for N in [Float64] vlistZ = vertices_list(Z) @test length(vlistZ) == 6 @test ispermutation(vlistZ, [N[-2, -2], N[0, -2], N[2, 0], N[2, 2], N[0, 2], N[-2, 0]]) + c, G = Z.center, Z.generators + vlist2 = LazySets.ZonotopeModule._vertices_list_2D(c, G; apply_convex_hull=true) + @test ispermutation(vlistZ, vlist2) + vlist3 = LazySets.ZonotopeModule._vertices_list_2D(c, G; apply_convex_hull=false) + @test ispermutation(vlistZ, vlist3) # test 3d zonotope vertex enumeration Z = Zonotope([0.0, 0.0, 0.0], [1.0 0.0 1.0; 0.0 1.0 1.0; 0.1 1.0 1.0]) @@ -432,3 +442,6 @@ for N in [Float64] HalfSpace(N[0, -1], N(1))]) @test isequivalent(intersection(H, Z), P) end + +# isoperationtype +@test !isoperationtype(Zonotope)