Skip to content

Commit abd83fb

Browse files
crisiumnihschillic
andcommitted
Added JKS16 submethod for order reduction.
Co-authored-by: Christian Schilling <[email protected]>
1 parent 0cf0cc0 commit abd83fb

File tree

9 files changed

+260
-8
lines changed

9 files changed

+260
-8
lines changed

docs/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@ RecipesBase = "1"
3838
StaticArrays = "1"
3939
SymEngine = "0.7 - 0.12"
4040
Symbolics = "6.1"
41-
TaylorModels = "0.6 - 0.7"
41+
TaylorModels = "0.6 - 0.8"

docs/src/lib/interfaces/AbstractZonotope.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ LazySets.AbstractReductionMethod
243243
LazySets.ASB10
244244
LazySets.COMB03
245245
LazySets.GIR05
246+
LazySets.JKS16
246247
```
247248

248249
## Implementations

docs/src/refs.bib

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,20 @@ @article{AlthoffSB10
215215
doi={10.1016/j.nahs.2009.03.009}
216216
}
217217

218+
@article{SCOTT2016126,
219+
title = {Constrained zonotopes: A new tool for set-based estimation and fault detection},
220+
journal = {Automatica},
221+
volume = {69},
222+
pages = {126-136},
223+
year = {2016},
224+
issn = {0005-1098},
225+
doi = {https://doi.org/10.1016/j.automatica.2016.02.036},
226+
url = {https://www.sciencedirect.com/science/article/pii/S0005109816300772},
227+
author = {Joseph K. Scott and Davide M. Raimondo and Giuseppe Roberto Marseglia and Richard D. Braatz},
228+
keywords = {State estimation, Fault detection, Set-based computing, Zonotopes, Reachability analysis},
229+
abstract = {This article introduces a new class of sets, called constrained zonotopes, that can be used to enclose sets of interest for estimation and control. The numerical representation of these sets is sufficient to describe arbitrary convex polytopes when the complexity of the representation is not limited. At the same time, this representation permits the computation of exact projections, intersections, and Minkowski sums using very simple identities. Efficient and accurate methods for computing an enclosure of one constrained zonotope by another of lower complexity are provided. The advantages and disadvantages of these sets are discussed in comparison to ellipsoids, parallelotopes, zonotopes, and convex polytopes in halfspace and vertex representations. Moreover, extensive numerical comparisons demonstrate significant advantages over other classes of sets in the context of set-based state estimation and fault detection.}
230+
}
231+
218232
@inproceedings{FrehseGDCRLRGDM11,
219233
author = {Goran Frehse and
220234
Colas Le Guernic and

src/Approximations/overapproximate.jl

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,84 @@ function overapproximate(P::VPolygon, ::Type{<:LinearMap{N,<:Hyperrectangle}}) w
603603
return LinearMap(R, min_rectangle)
604604
end
605605

606+
"""
607+
overapproximate(P::SparsePolynomialZonotope{N}, ::Type{<:VPolytope}) where {N}
608+
609+
610+
Overapproximate a sparse polynomial zonotope with a polytope in vertex representation.
611+
612+
### Input
613+
614+
- `P` -- sparse polynomial zonotope
615+
- `VPolytope` -- target type
616+
617+
### Output
618+
619+
A `VPolytope` that overapproximates the sparse polynomial zonotope.
620+
621+
### Algorithm
622+
623+
This method implements [Kochdumper21a; Proposition 3.1.15](@citet).
624+
The idea is to split `P` into a linear and nonlinear part (such that `P = P₁ ⊕ P₂`).
625+
The nonlinear part is enclosed by a zonotope. Then we combine the vertices
626+
of both sets and finally apply a convex-hull algorithm.
627+
"""
628+
function overapproximate(P::SparsePolynomialZonotope{N}, ::Type{<:VPolytope}) where {N}
629+
c = center(P)
630+
G = genmat_dep(P)
631+
GI = genmat_indep(P)
632+
E = expmat(P)
633+
idx = P.idx
634+
635+
H = [j for j in 1:size(E, 2) if any(E[:, j] .> 1)]
636+
K = setdiff(1:size(E, 2), H)
637+
638+
if !isempty(H)
639+
SPZ₂ = SparsePolynomialZonotope(c, G[:, H], zeros(N, length(c), 0), E[:, H], idx)
640+
Z = overapproximate(SPZ₂, Zonotope)
641+
c_z = center(Z)
642+
GI_mod = hcat(GI, genmat(Z))
643+
else
644+
c_z = c
645+
GI_mod = GI
646+
end
647+
648+
G_mod = G[:, K]
649+
E_mod = E[:, K]
650+
651+
# P̄ = SparsePolynomialZonotope(c_z, G_mod, GI_mod, E_mod, idx)
652+
# Compute vertices of a Z-representation
653+
p = size(E, 1)
654+
dep_params = Iterators.product(fill([-one(N), one(N)], p)...)
655+
indep_params = Iterators.product(fill([-one(N), one(N)], size(GI_mod, 2))...)
656+
657+
V = Vector{Vector{N}}()
658+
for α in dep_params
659+
dep_term = zeros(N, size(c))
660+
for j in axes(G_mod, 2)
661+
prod = one(N)
662+
for k in 1:p
663+
if E_mod[k, j] == 1
664+
prod *= α[k]
665+
end
666+
end
667+
dep_term += prod * G_mod[:, j]
668+
end
669+
for β in indep_params
670+
indep_term = zeros(N, size(c))
671+
for j in axes(GI_mod, 2)
672+
indep_term += β[j] * GI_mod[:, j]
673+
end
674+
point = c_z + dep_term + indep_term
675+
push!(V, point)
676+
end
677+
end
678+
679+
convex_hull!(V)
680+
681+
return VPolytope(V)
682+
end
683+
606684
# function to be loaded by Requires
607685
function load_paving_overapproximation()
608686
return quote

src/Interfaces/AbstractZonotope.jl

Lines changed: 158 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,25 @@ Zonotope order-reduction method from [AlthoffSB10](@citet).
808808
"""
809809
struct ASB10 <: AbstractReductionMethod end
810810

811+
"""
812+
JKS16 <: AbstractReductionMethod
813+
814+
`JKS16` uses a greedy factorization and iterative generator reduction
815+
816+
### Algorithm
817+
818+
- The JKS16 method reorders the generator matrix using reduced row echelon form (rref) to form a `T`,
819+
then iteratively removes excess generators from `V` while updating `T`.
820+
- The default `JKS16()` uses `ϵ = 1e-6` (pivot threshold) and `δ = 1e-3` (volume threshold).
821+
Referenced from [SCOTT2016126](@citet).
822+
"""
823+
struct JKS16{N<:Number} <: AbstractReductionMethod
824+
ϵ::N
825+
δ::N
826+
827+
JKS16::N=1e-6, δ::N=1e-3) where {N<:Number} = new{N}(ϵ, δ)
828+
end
829+
811830
"""
812831
reduce_order(Z::AbstractZonotope, r::Real,
813832
[method]::AbstractReductionMethod=GIR05())
@@ -831,16 +850,18 @@ The available algorithms are:
831850
832851
```jldoctest; setup = :(using LazySets: subtypes, AbstractReductionMethod)
833852
julia> subtypes(AbstractReductionMethod)
834-
3-element Vector{Any}:
853+
4-element Vector{Any}:
835854
LazySets.ASB10
836855
LazySets.COMB03
837856
LazySets.GIR05
857+
LazySets.JKS16
838858
```
839859
840860
See the documentation of each algorithm for references. These methods split the
841861
given zonotopic set `Z` into two zonotopes, `K` and `L`, where `K` contains the
842862
most "representative" generators and `L` contains the generators that are
843-
reduced, `Lred`, using a box overapproximation. We follow the notation from
863+
reduced, `Lred`, using a box overapproximation. This methodology varies slightly
864+
for `JKS16` (for details, refer to the method). We follow the notation from
844865
[YangS18](@citet). See also [KopetzkiSA17](@citet).
845866
"""
846867
function reduce_order(Z::AbstractZonotope, r::Real,
@@ -852,7 +873,10 @@ function reduce_order(Z::AbstractZonotope, r::Real,
852873

853874
# if r is bigger than the order of Z => do not reduce
854875
(r * n >= p) && return Z
876+
return _reduce_order_zonotope_common(Z, r, n, p, method)
877+
end
855878

879+
function _reduce_order_zonotope_common(Z, r, n, p, method::Union{ASB10,COMB03,GIR05})
856880
c = center(Z)
857881
G = genmat(Z)
858882

@@ -878,6 +902,138 @@ function reduce_order(Z::AbstractZonotope, r::Real,
878902
return Zonotope(c, Gred)
879903
end
880904

905+
function _reduce_order_zonotope_common(Z, r, n, p, method::JKS16)
906+
c = center(Z)
907+
G = genmat(Z)
908+
909+
G, G✶ = _factorG(G, method.ϵ, method.δ)
910+
911+
T = G[:, 1:n]
912+
V = G[:, (n+1):end]
913+
R = G✶[:, (n+1):end]
914+
915+
m = floor(Int, n * r)
916+
917+
while p > m
918+
error_metric = [prod(1 .+ abs.(R[:, j])) - (1 + sum(abs.(R[:, j])))
919+
for j in 1:size(R, 2)]
920+
921+
j_min = argmin(error_metric)
922+
923+
r_j = R[:, j_min]
924+
925+
V = V[:, [1:(j_min-1); (j_min+1):end]]
926+
R = R[:, [1:(j_min-1); (j_min+1):end]]
927+
928+
diag_matrix = Diagonal(1 .+ abs.(r_j))
929+
T = T * diag_matrix
930+
if !isempty(R)
931+
R = inv(diag_matrix) * R
932+
end
933+
934+
p -= 1
935+
end
936+
937+
G_red = hcat(T, V)
938+
return Zonotope(c, G_red)
939+
end
940+
941+
# reorder the generator matrix G
942+
function _factorG(G::AbstractMatrix, ϵ::N, δ::N) where {N}
943+
G✶ = copy(G)
944+
n, ng = size(G)
945+
946+
# rref with column swap
947+
for k in 1:n
948+
# normalize rows k:n
949+
for i in k:n
950+
row_norm = norm(G✶[i, :], 1)
951+
if row_norm > ϵ
952+
G✶[i, :] ./= row_norm
953+
end
954+
end
955+
956+
submatrix = G✶[k:n, k:ng]
957+
max_val, idx = findmax(abs.(submatrix))
958+
i_max = k + idx[1] - 1
959+
j_max = k + idx[2] - 1
960+
961+
if abs(max_val) ϵ
962+
break
963+
end
964+
965+
# swap rows and columns
966+
if i_max != k
967+
G✶[[k, i_max], :] = G✶[[i_max, k], :]
968+
end
969+
970+
if j_max != k
971+
G[:, [k, j_max]] = G[:, [j_max, k]]
972+
G✶[:, [k, j_max]] = G✶[:, [j_max, k]]
973+
end
974+
975+
G✶ = _rref(G✶)
976+
end
977+
978+
# extra column swaps until all |g∗ij| ≤ 1 + δ
979+
while true
980+
(max_val, max_idx) = findmax(abs.(G✶))
981+
(max_val 1 + δ) && break
982+
k, j = Tuple(max_idx)
983+
984+
G[:,[k,j]] = G[:,[j,k]]
985+
G✶[:,[k,j]] = G✶[:,[j,k]]
986+
987+
pivot = G✶[k,k]
988+
for i in 1:n
989+
i == k && continue
990+
factor = G✶[i,k] / pivot
991+
G✶[i,:] -= factor .* G✶[k,:]
992+
end
993+
G✶[k,:] ./= pivot
994+
end
995+
996+
return G, G✶
997+
end
998+
999+
# reduced row echelon (rref)
1000+
function _rref(G::AbstractMatrix{N}) where {N}
1001+
A = copy(G)
1002+
nr, nc = size(A)
1003+
pivot = 1
1004+
1005+
for r in 1:nr
1006+
if pivot > nc
1007+
return A
1008+
end
1009+
1010+
i = r
1011+
while i nr && A[i, pivot] == 0
1012+
i += 1
1013+
if i > nr
1014+
i = r
1015+
pivot += 1
1016+
pivot > nc && return A
1017+
end
1018+
end
1019+
1020+
A[[i, r], :] = A[[r, i], :]
1021+
1022+
if A[r, pivot] != 0
1023+
A[r, :] ./= A[r, pivot]
1024+
end
1025+
1026+
for i in 1:nr
1027+
i == r && continue
1028+
A[i, :] -= A[i, pivot] * A[r, :]
1029+
end
1030+
1031+
pivot += 1
1032+
end
1033+
1034+
return A
1035+
end
1036+
8811037
# approximate with a box
8821038
function _approximate_reduce_order(c, G, indices, ::Union{COMB03,GIR05})
8831039
return _interval_hull(G, indices)

src/Plotting/plot_recipes.jl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -373,14 +373,12 @@ julia> plot(B, 1e-2) # faster but less accurate than the previous call
373373
else
374374
x, y = res
375375
if length(x) == 1 ||
376-
(length(x) == 2 && norm([x[1], y[1]] - [x[2], y[2]]) 0)
376+
(length(x) == 2 && isapproxzero(norm([x[1], y[1]] - [x[2], y[2]])))
377377
# single point
378378
seriestype := :scatter
379379
elseif length(x) == 2
380380
# flat line segment
381381
linecolor --> DEFAULT_COLOR
382-
markercolor --> DEFAULT_COLOR
383-
markershape --> :circle
384382
seriestype := :path
385383
else
386384
seriestype := :shape

test/Approximations/overapproximate.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ for N in [Float64, Rational{Int}, Float32]
147147
if N <: AbstractFloat
148148
@test isequivalent(P, R)
149149
end
150+
151+
#overapproximate a SparsePolynomialZonotope with a VPolytope
152+
S = SparsePolynomialZonotope(N[-0.5, -0.5], N[1. 1 1 1;1 0 -1 1], zeros(N, 2, 0), [1 0 1 2;0 1 1 0])
153+
P = overapproximate(S, VPolytope)
154+
@test ispermutation([N[-1.5, -2.5], N[2.5, -0.5], N[3.5, 0.5], N[-0.5, 2.5], N[-1.5, 1.5]], vertices_list(P))
150155
end
151156

152157
# tests that do not work with Rational{Int}

test/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,5 @@ SetProg = "0.3 - 0.4"
5555
StaticArrays = "0.12, 1"
5656
SymEngine = "0.7 - 0.12"
5757
Symbolics = "1 - 5.30, 6.1"
58-
TaylorModels = "0.0.1, 0.1 - 0.7"
58+
TaylorModels = "0.0.1, 0.1 - 0.8"
5959
WriteVTK = "1"

test/Sets/Zonotope.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ for N in [Float64, Rational{Int}, Float32]
141141
@test ispermutation(collect(generators(Z)), [genmat(Z)[:, j] for j in 1:ngens(Z)])
142142

143143
# test order reduction
144-
for method in [LazySets.ASB10(), LazySets.COMB03(), LazySets.GIR05()]
144+
for method in [LazySets.ASB10(), LazySets.COMB03(), LazySets.GIR05(), LazySets.JKS16()]
145145
Z = Zonotope(N[2, 1], N[-1//2 3//2 1//2 1; 1//2 3//2 1 1//2])
146146
Zred1 = reduce_order(Z, 1, method)
147147
@test ngens(Zred1) == 2

0 commit comments

Comments
 (0)