Skip to content

Commit e72d162

Browse files
alecarraroschillic
andauthored
Add overapproximate for matrix zonotope exponential (#4000)
* add conversion from `IntervalMatrix` to `MatrixZonotope` * add mz exp overapproxination * Apply suggestions from code review - part 1 Co-authored-by: Christian Schilling <[email protected]> * apply suggestions from code review -part 2 * fix tests and docstring * Update test/Approximations/overapproximate_expmap.jl Co-authored-by: Christian Schilling <[email protected]> * Update docs/src/lib/conversion.md Co-authored-by: Christian Schilling <[email protected]> --------- Co-authored-by: Christian Schilling <[email protected]>
1 parent 7959e74 commit e72d162

File tree

7 files changed

+156
-1
lines changed

7 files changed

+156
-1
lines changed

docs/src/lib/conversion.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,11 @@ convert(::Type{SimpleSparsePolynomialZonotope}, ::AbstractSparsePolynomialZonoto
4343
convert(::Type{SparsePolynomialZonotope}, ::AbstractZonotope{N}) where {N}
4444
convert(::Type{SparsePolynomialZonotope}, ::SimpleSparsePolynomialZonotope{N}) where {N}
4545
```
46+
47+
```@meta
48+
CurrentModule = LazySets.MatrixZonotopeModule
49+
```
50+
51+
```@docs
52+
convert(::Type{MatrixZonotope}, ::IntervalMatrices.IntervalMatrix)
53+
```

src/Approximations/overapproximate_expmap.jl

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,59 @@ function load_intervalmatrices_overapproximation_expmap()
156156
res = overapproximate(E * Z, Zonotope)
157157
return res
158158
end
159+
160+
"""
161+
overapproximate(expA::MatrixZonotopeExp{N,T}, ::Type{<:MatrixZonotope},
162+
k::Int=2) where {N,T<:AbstractMatrixZonotope{N}}
163+
164+
Overapproximate the matrix zonotope exponential ``exp(\\mathcal{A})``
165+
166+
### Input
167+
168+
- `expA` -- `MatrixZonotopeExp`
169+
- `MatrixZonotope` -- target type
170+
- `k` -- (default: `2`) the order of the Taylor expansion
171+
172+
### Output
173+
174+
A matrix zonotope overapproximating the matrix zonotope exponential
175+
176+
### Algorithm
177+
178+
The expansion
179+
180+
```math
181+
exp(\\mathcal{A}) ⊆ \\sum_i^k \\frac{\\mathcal{A}^i}{i!} + E_k
182+
```
183+
184+
is computed by overapproximating the matrix zonotope powers ``A^i``
185+
for ``i=0, …, k``.
186+
The remainder term ``E_k`` is computed through interval arithmetic
187+
following Proposition 4.1 by [AlthoffKS11](@citet).
188+
"""
189+
function overapproximate(expA::MatrixZonotopeExp{N,T}, ::Type{<:MatrixZonotope},
190+
k::Int=2) where {N,T<:AbstractMatrixZonotope{N}}
191+
# overapproximate the exponent, which can be a product A*B*…
192+
MZP = MatrixZonotopeProduct(expA.M)
193+
MZ = overapproximate(MZP, MatrixZonotope)
194+
195+
# compute the Taylor expansion
196+
powers = Vector{typeof(MZ)}(undef, k)
197+
powers[1] = MZ
198+
@inbounds for i in 2:k
199+
term = overapproximate(MZ * powers[i - 1], MatrixZonotope)
200+
powers[i] = scale(1 / i, term)
201+
end
202+
W = reduce(minkowski_sum, powers)
203+
W = MatrixZonotope(center(W) + Matrix{N}(I, size(W)), generators(W))
204+
205+
# overapproximate mat zon by interval matrix and overapproximate remainder
206+
IM = overapproximate(MZ, IntervalMatrix)
207+
E = IntervalMatrices._exp_remainder(IM, N(1), k)
208+
209+
res = minkowski_sum(W, convert(MatrixZonotope, E))
210+
return remove_redundant_generators(res)
211+
end
159212
end
160213
end
161214

src/MatrixSets/MatrixZonotopeModule.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module MatrixZonotopeModule
22

3-
using Reexport
3+
using Reexport, Requires
44

55
using Random: AbstractRNG, GLOBAL_RNG
66
using ReachabilityBase.Distribution: reseed!
@@ -26,10 +26,13 @@ include("minkowski_sum.jl")
2626
include("reduce_order.jl")
2727
include("remove_redundant_generators.jl")
2828
include("reshape.jl")
29+
include("convert.jl")
2930

3031
include("generators.jl")
3132
include("indexvector.jl")
3233
include("ngens.jl")
3334
include("order.jl")
3435

36+
include("init.jl")
37+
3538
end # module

src/MatrixSets/convert.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
function load_intervalmatrices_conversion()
2+
return quote
3+
using .IntervalMatrices: IntervalMatrix, mid, sup, radius
4+
5+
"""
6+
convert(::Type{MatrixZonotope}, IM::IntervalMatrix)
7+
8+
Convert an interval matrix to a matrix zonotope
9+
10+
### Input
11+
12+
- `MatrixZonotope` -- target type
13+
- `IM` -- an interval matrix
14+
15+
### Output
16+
17+
A matrix zonotope with one generator
18+
19+
### Example
20+
21+
```jldoctest
22+
julia> using LazySets, IntervalMatrices
23+
24+
julia> IM = IntervalMatrix([interval(-1.1, -0.9) interval(-4.1, -3.9);
25+
interval(3.9, 4.1) interval(-1.1, -0.9)])
26+
2×2 IntervalMatrix{Float64, IntervalArithmetic.Interval{Float64}, Matrix{IntervalArithmetic.Interval{Float64}}}:
27+
[-1.10001, -0.9] [-4.1, -3.89999]
28+
[3.89999, 4.1] [-1.10001, -0.9]
29+
30+
julia> MZ = convert(MatrixZonotope, IM)
31+
MatrixZonotope{Float64, Matrix{Float64}}([-1.0 -4.0; 4.0 -1.0], [[0.10000000000000009 0.10000000000000009; 0.10000000000000009 0.10000000000000009]], [1])
32+
```
33+
"""
34+
function Base.convert(::Type{MatrixZonotope}, IM::IntervalMatrix)
35+
c = mid(IM)
36+
G = [radius(IM)]
37+
return MatrixZonotope(c, G)
38+
end
39+
end
40+
end

src/MatrixSets/init.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function __init__()
2+
@require IntervalMatrices = "5c1f47dc-42dd-5697-8aaa-4d102d140ba9" eval(load_intervalmatrices_conversion())
3+
end

test/Approximations/overapproximate_expmap.jl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using LazySets, Test, SparseArrays
2+
using LazySets.MatrixZonotopeModule: vectorize
23
if !isdefined(@__MODULE__, Symbol("@tN"))
34
macro tN(v)
45
return v
@@ -51,6 +52,22 @@ for N in @tN([Float32, Float64])
5152
pts = sample(Pex, 10; sampler=sampler)
5253
@test all(p Zex for p in pts)
5354

55+
# overapproximate MatrixZonotopeExp
56+
57+
# test degenerate case
58+
c = N[1 -2; 2 -1]
59+
A = MatrixZonotope(c, [zeros(N, 2, 2)])
60+
expA = MatrixZonotopeExp(A)
61+
res = overapproximate(expA, MatrixZonotope, 20) #for large k it converges to exp(c)
62+
@test isapprox(center(res), exp(c))
63+
@test ngens(res)== 0
64+
65+
# degenerate case + product
66+
B = MatrixZonotope(N[1 0; 0 1], Matrix{N}[])
67+
expAB = MatrixZonotopeExp(A * B)
68+
res2 = overapproximate(expA, MatrixZonotope, 20)
69+
@test isapprox(center(res), center(res2))
70+
5471
@static if isdefined(@__MODULE__, :ExponentialUtilities) || isdefined(@__MODULE__, :Expokit)
5572
# matrix
5673
mzexp = SparseMatrixExp(sparse(M))
@@ -61,3 +78,16 @@ for N in @tN([Float32, Float64])
6178
end
6279
end
6380
end
81+
82+
for N in @tN([Float64])
83+
# This test is only run with Float64 since it is expensive
84+
# and requires high Taylor order `k` to pass on Float32
85+
@static if isdefined(@__MODULE__, :IntervalMatrices)
86+
#inclusion
87+
C = MatrixZonotope(N[1 -2; 2 -1], [N[0.1 0.05; 0 0.1]])
88+
expC = MatrixZonotopeExp(C)
89+
res = reduce_order(overapproximate(expC, MatrixZonotope, 4), 1)
90+
res2 = reduce_order(overapproximate(expC, MatrixZonotope, 8), 1)
91+
@test vectorize(res2) vectorize(res)
92+
end
93+
end

test/MatrixSets/MatrixZonotope.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using LazySets, Test
2+
IA = LazySets.IA
3+
using LazySets.IA: interval
24
using LazySets.MatrixZonotopeModule: vectorize, matrixize
35
if !isdefined(@__MODULE__, Symbol("@tN"))
46
macro tN(v)
@@ -122,6 +124,22 @@ for N in @tN([Float64, Float32, Rational{Int}])
122124
@test size(expMZ) == size(A_mz)
123125
@test_throws AssertionError MatrixZonotopeExp(B_mz)
124126

127+
# convert IM to MZ
128+
@static if isdefined(@__MODULE__, :IntervalMatrices)
129+
using IntervalMatrices: IntervalMatrix
130+
IM = IntervalMatrix([interval(-N(1.1), -N(0.9)) interval(-N(4.1), -N(3.9));
131+
interval(N(3.9), N(4.1)) interval(-N(1.1), -N(0.9))])
132+
MZ = convert(MatrixZonotope, IM)
133+
if N==Rational{Int} # isapprox is sensitive to minor rounding using Rational{Int}
134+
T = Float64
135+
@test isapprox(convert(Matrix{T}, center(MZ)), T[-1.0 -4.0; 4.0 -1.0])
136+
@test isapprox(convert(Vector{Matrix{T}}, generators(MZ)), [T[0.1 0.1; 0.1 0.1]])
137+
else
138+
@test isapprox(center(MZ), N[-1.0 -4.0; 4.0 -1.0])
139+
@test isapprox(generators(MZ), [N[0.1 0.1; 0.1 0.1]])
140+
end
141+
end
142+
125143
# vectorize and matrixize
126144
c = N[1 0; 0 3]
127145
gens = [N[1 -1; 0 2]]

0 commit comments

Comments
 (0)