Skip to content

Commit 8f8d57d

Browse files
committed
#2739: addded ZonotopeMD and support for Cartesian product in normal form
1 parent 64df96d commit 8f8d57d

File tree

11 files changed

+272
-5
lines changed

11 files changed

+272
-5
lines changed

docs/make.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ makedocs(; sitename="LazySets.jl",
8888
"VPolygon" => "lib/sets/VPolygon.md",
8989
"VPolytope" => "lib/sets/VPolytope.md",
9090
"ZeroSet" => "lib/sets/ZeroSet.md",
91-
"Zonotope" => "lib/sets/Zonotope.md"
91+
"Zonotope" => "lib/sets/Zonotope.md",
92+
"ZonotopeMD" => "lib/sets/ZonotopeMD.md"
9293
#
9394
],
9495
"Lazy Operations" => [

docs/src/lib/sets/ZonotopeMD.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
```@meta
2+
CurrentModule = LazySets.ZonotopeModule
3+
```
4+
5+
# [Zonotope](@id def_ZonotopeMD)
6+
7+
```@docs
8+
ZonotopeMD
9+
```
10+
11+
## Operations
12+
```@docs
13+
genmat(::ZonotopeMD)
14+
cartesian_product(Z1::ZonotopeMD, Z2::ZonotopeMD)
15+
```
16+
17+
Undocumented implementations:
18+
19+
* [`center`](@ref center(::LazySet))
20+
* [`ngens`](@ref ngens(::AbstractZonotope))

src/LazySets.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,8 @@ include("Sets/Zonotope/ZonotopeModule.jl")
218218
@reexport using ..ZonotopeModule: Zonotope,
219219
remove_zero_generators,
220220
linear_map!,
221-
split!
221+
split!,
222+
ZonotopeMD
222223
using ..ZonotopeModule: _split
223224

224225
include("LazyOperations/UnionSet.jl") # must come before IntervalModule
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""
2+
struct ZonotopeMD{N, VN<:AbstractVector{N}, MN<:AbstractMatrix{N}, DN<:AbstractVector{N}} <: AbstractZonotope{N}
3+
4+
Type that represents a zonotope of order `k` in normal form.
5+
6+
### Fields
7+
8+
- `center::VN` — the center of the zonotope
9+
- `M::MN` — matrix of general (non-axis-aligned) generators
10+
- `d::DN` — vector of axis-aligned (diagonal) generators
11+
12+
### Notes
13+
A zonotope is of order `k` if it has `n * k` generators in `ℝⁿ`, where `n` is the ambient dimension.
14+
15+
A zonotope of order `k` in *normal form* is defined as the set
16+
17+
```math
18+
Z = \\left\\{ x ∈ ℝ^n : x = c + Mξ + d ⊙ η, ~~ ξ ∈ [-1, 1]^m, ~~ η ∈ [-1, 1]^n \\right\\},
19+
```
20+
21+
where `M ∈ ℝ^{n×m}` is a matrix of general generators with `m = n*(k -1)` and `d ∈ ℝⁿ` is a vector of axis-aligned generators.
22+
Equivalently, this can be seen as a zonotope with generator matrix `[M D]`, where `D` is the diagonal matrix
23+
formed from the vector `d`.
24+
ZonotopeMD can be constructed in two ways: by passing the full generator matrix `[M D]` in normal form
25+
or by passing `M` and a vector `d` separately.
26+
27+
### Examples
28+
29+
Constructing a zonotope in normal form from a center, general generator matrix `M`, and diagonal vector `d`:
30+
31+
```jldoctest zonotopemd_label
32+
julia> c = [0.0, 0.0];
33+
julia> M = [1.0 2.0; 3.0 1.0];
34+
julia> d = [0.1, 0.2];
35+
julia> Z = ZonotopeMD(c, M, d)
36+
ZonotopeMD{Float64, Vector{Float64}, Matrix{Float64}, Vector{Float64}}([0.0, 0.0], [1.0 0.0; 0.0 1.0], [0.1, 0.2])
37+
38+
julia> center(Z)
39+
2-element Vector{Float64}:
40+
0.0
41+
0.0
42+
43+
julia> genmat(Z)
44+
2×4 Matrix{Float64}:
45+
1.0 2.0 0.1 0.0
46+
3.0 1.0 0.0 0.2
47+
```
48+
49+
The generator matrix returned by `genmat` is the concatenation `[M D]`, where `D` is the diagonal matrix formed from `d`.
50+
51+
Constructing the same zonotope by passing the full generator matrix `[M D]` directly:
52+
53+
```jldoctest zonotopemd_label
54+
julia> G = [1.0 2.0 0.1 0.0;
55+
3.0 1.0 0.0 0.2];
56+
57+
julia> Z2 = ZonotopeMD([0.0, 0.0], G)
58+
ZonotopeMD{Float64, Vector{Float64}, Matrix{Float64}, Vector{Float64}}([0.0, 0.0], [1.0 2.0; 3.0 1.0], [0.1, 0.2])
59+
60+
julia> genmat(Z2) == G
61+
true
62+
```
63+
You can also convert back to a standard `Zonotope` if needed:
64+
65+
```jldoctest zonotopemd_label
66+
julia> Zstd = Zonotope(Z)
67+
Zonotope{Float64, Vector{Float64}, Matrix{Float64}}([0.0, 0.0], [1.0 2.0 0.1 0.0; 3.0 1.0 0.0 0.2])
68+
```
69+
70+
"""
71+
struct ZonotopeMD{N,VN<: AbstractVector{N}, MN<:AbstractMatrix{N},DN<:AbstractVector{N}} <: AbstractZonotope{N}
72+
center::VN
73+
M::MN
74+
d::DN
75+
76+
function ZonotopeMD(center::VN, M::MN, d::DN) where {N,
77+
VN<:AbstractVector{N},
78+
MN<:AbstractMatrix{N},
79+
DN<:AbstractVector{N}}
80+
@assert length(center) == size(M, 1) == length(d) "Dimensions must match"
81+
return new{N,VN,MN,DN}(center, M, d)
82+
end
83+
end
84+
85+
# constructor from generator matrix
86+
function ZonotopeMD(center::VN, G::AbstractMatrix{N}) where {N, VN<:AbstractVector{N}}
87+
n, p = size(G)
88+
@assert p % n == 0 "The generator matrix must contain a multiple of n columns"
89+
@assert p >= 2n "Expected at least order 2 zonotope"
90+
91+
M = G[:, 1:(p - n)]
92+
D = G[:, end - n + 1:end]
93+
94+
@assert isdiag(D) "The last n columns of the generator matrix must be diagonal"
95+
d = diag(D)
96+
return ZonotopeMD(center, M, d)
97+
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
"""
3+
cartesian_product(Z1::ZonotopeMD, Z2::ZonotopeMD)
4+
5+
Return the Cartesian product of two zonotopes in normal form (`ZonotopeMD`).
6+
7+
### Input
8+
9+
- `Z1`, `Z2` -- zonotopes in normal form (`ZonotopeMD`)
10+
11+
### Output
12+
13+
A new `ZonotopeMD` representing the Cartesian product `Z1 × Z2`.
14+
"""
15+
function cartesian_product(Z1::ZonotopeMD, Z2::ZonotopeMD)
16+
c = vcat(Z1.center, Z2.center)
17+
d = vcat(Z1.d, Z2.d)
18+
M = blockdiag(sparse(Z1.M), sparse(Z2.M))
19+
return ZonotopeMD(c, M, d)
20+
end
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function center(Z::ZonotopeMD)
2+
return Z.center
3+
end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Zonotope(Z::ZonotopeMD) = convert(Zonotope, Z)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""
2+
genmat(Z::ZonotopeMD)
3+
4+
Return the generator matrix of a ZonotopeMD.
5+
6+
### Input
7+
8+
- `Z` -- zonotope in normal form
9+
10+
### Output
11+
12+
A matrix where each column represents one generator of the zonotope `Z`.
13+
"""
14+
function genmat(Z::ZonotopeMD)
15+
D = spdiagm(0 => Z.d)
16+
return hcat(Z.M, D)
17+
end

src/Sets/Zonotope/ZonotopeMD/ngens.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ngens(Z::ZonotopeMD) = size(Z.M, 2) + length(Z.d)

src/Sets/Zonotope/ZonotopeModule.jl

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@ module ZonotopeModule
33
using Reexport, Requires
44

55
using ..LazySets: AbstractZonotope, generators_fallback, _scale_copy_inplace
6-
using LinearAlgebra: mul!
6+
using LinearAlgebra: mul!, Diagonal, isdiag, diag
7+
using SparseArrays: blockdiag, sparse, spdiagm
78
using Random: AbstractRNG, GLOBAL_RNG
89
using ReachabilityBase.Arrays: ismultiple, remove_zero_columns, to_matrix,
910
vector_type
1011
using ReachabilityBase.Distribution: reseed!
1112
using ReachabilityBase.Require: require
1213

1314
@reexport import ..API: center, high, isoperationtype, low, rand,
14-
permute, scale, scale!, translate!
15+
permute, scale, scale!, translate!, cartesian_product
1516
@reexport import ..LazySets: generators, genmat, ngens, reduce_order,
1617
remove_redundant_generators, togrep
1718
import Base: convert
@@ -20,7 +21,8 @@ import Base: convert
2021
export Zonotope,
2122
remove_zero_generators,
2223
linear_map!,
23-
split!
24+
split!,
25+
ZonotopeMD
2426

2527
include("Zonotope.jl")
2628

@@ -47,4 +49,12 @@ include("convert.jl")
4749

4850
include("init.jl")
4951

52+
#ZonotopeMD
53+
include("ZonotopeMD/ZonotopeMD.jl")
54+
include("ZonotopeMD/convert.jl")
55+
include("ZonotopeMD/center.jl")
56+
include("ZonotopeMD/genmat.jl")
57+
include("ZonotopeMD/cartesian_product.jl")
58+
include("ZonotopeMD/ngens.jl")
59+
5060
end # module

test/Sets/ZonotopeMD.jl

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
for N in [Float64, Float32, Rational{Int}]
2+
# For order 2:
3+
# center ∈ ℝ²,
4+
# M is 2×2 and d ∈ ℝ²
5+
6+
# Direct construction via (center, M, d)
7+
c = [N(1), N(2)]
8+
M = [N(1) N(0); N(0) N(1)]
9+
d = [N(1//10), N(2)]
10+
Zmd = ZonotopeMD(c, M, d)
11+
@test Zmd isa ZonotopeMD{N}
12+
@test center(Zmd) == c
13+
@test genmat(Zmd) == hcat(M, Diagonal(d))
14+
@test length(Zmd.center) == size(Zmd.M, 1) == length(Zmd.d)
15+
16+
@test length(center(Zmd)) == 2
17+
@test genmat(Zmd) !== nothing
18+
19+
# Construction from generator matrix
20+
G = hcat(M, Diagonal(d))
21+
Zmd2 = ZonotopeMD(c, G)
22+
@test Zmd2 == Zmd
23+
24+
# Generator matrix with wrong shape
25+
G_wrong = hcat(M, ones(N, 2, 1))
26+
@test_throws AssertionError ZonotopeMD(c, G_wrong)
27+
28+
# Convert to standard Zonotope
29+
Zstd = convert(Zonotope, Zmd)
30+
@test center(Zstd) == c
31+
@test genmat(Zstd) == G
32+
33+
# Cartesian product
34+
c2 = [N(3), N(4)]
35+
M2 = [N(2) N(0); N(0) N(2)]
36+
d2 = [N(3), N(4)]
37+
Zmd_2 = ZonotopeMD(c2, M2, d2)
38+
cp_md = cartesian_product(Zmd, Zmd_2)
39+
cp_std = cartesian_product(Zstd, Zonotope(Zmd_2))
40+
@test isequivalent(cp_md, cp_std)
41+
42+
# Apply a linear map
43+
A = [N(1) N(1); N(0) N(1)]
44+
Zlm = linear_map(A, Zmd)
45+
expected_c = A * c
46+
expected_genmat = A * hcat(M, Diagonal(d))
47+
@test center(Zlm) == expected_c
48+
@test genmat(Zlm) == expected_genmat
49+
50+
# For order 3:
51+
# center ∈ ℝ²,
52+
# M is 2×4 and d ∈ ℝ².
53+
54+
# Direct construction via (center, M, d)
55+
c = [N(1), N(2)]
56+
M1 = [N(1) N(0) N(2) N(0); N(0) N(1) N(3) N(0)]
57+
d = [N(1//10), N(2)]
58+
Zmd = ZonotopeMD(c, M1, d)
59+
@test Zmd isa ZonotopeMD{N}
60+
@test center(Zmd) == c
61+
@test genmat(Zmd) == hcat(M1, Diagonal(d))
62+
@test length(Zmd.center) == size(Zmd.M, 1) == length(Zmd.d)
63+
64+
@test length(center(Zmd)) == 2
65+
@test genmat(Zmd) !== nothing
66+
67+
# Construction from generator matrix
68+
G = hcat(M1, Diagonal(d))
69+
Zmd2 = ZonotopeMD(c, G)
70+
@test Zmd2 == Zmd
71+
72+
G_wrong = hcat(M1, ones(N, 2, 1)) # here p = 3 instead of 4 (2n)
73+
@test_throws AssertionError ZonotopeMD(c, G_wrong)
74+
75+
# Convert to standard Zonotope
76+
Zstd = convert(Zonotope, Zmd)
77+
@test center(Zstd) == c
78+
@test genmat(Zstd) == G
79+
80+
# Cartesian product
81+
c2 = [N(3), N(4)]
82+
M2 = [N(2) N(0) N(1) N(0); N(0) N(2) N(3) N(0)]
83+
d2 = [N(3), N(4)]
84+
Zmd_2 = ZonotopeMD(c2, M2, d2)
85+
cp_md = cartesian_product(Zmd, Zmd_2)
86+
cp_std = cartesian_product(Zstd, Zonotope(Zmd_2))
87+
@test isequivalent(cp_md, cp_std)
88+
89+
# Apply a linear map
90+
A = [N(1) N(1); N(0) N(1)]
91+
Zlm = linear_map(A, Zmd)
92+
expected_c = A * c
93+
expected_genmat = A * hcat(M1, Diagonal(d))
94+
@test center(Zlm) == expected_c
95+
@test genmat(Zlm) == expected_genmat
96+
end

0 commit comments

Comments
 (0)