/
ResetMap.jl
340 lines (253 loc) · 7.84 KB
/
ResetMap.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
import Base: isempty
export ResetMap,
get_A,
get_b
"""
ResetMap{N<:Real, S<:LazySet{N}} <: LazySet{N}
Type that represents a lazy reset map.
A reset map is a special case of an affine map ``A x + b, x ∈ X`` where the
linear map ``A`` is the identity matrix with zero entries in all reset
dimensions, and the translation vector ``b`` is zero in all other dimensions.
### Fields
- `X` -- convex set
- `resets` -- resets (a mapping from an index to a new value)
### Example
```jldoctest resetmap
julia> X = BallInf([2.0, 2.0, 2.0], 1.0);
julia> r = Dict(1 => 4.0, 3 => 0.0);
julia> rm = ResetMap(X, r);
```
Here `rm` modifies the set `X` such that `x1` is reset to 4 and `x3` is reset to
0, while `x2` is not modified.
Hence `rm` is equivalent to the set
`Hyperrectangle([4.0, 2.0, 0.0], [0.0, 1.0, 0.0])`, i.e., an axis-aligned line
segment embedded in 3D.
The corresponding affine map ``A x + b`` would be:
```math
\begin{pmatrix} 0 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 0 \end{pmatrix} x +
\begin{pmatrix} 4 & 0 & 0 \end{pmatrix}
```
Use the function `get_A` (resp. `get_b`) to create the matrix `A` (resp. vector
`b`) corresponding to a given reset map.
The (in this case unique) support vector of `rm` in direction `ones(3)` is:
```jldoctest resetmap
julia> σ(ones(3), rm)
3-element Array{Float64,1}:
4.0
3.0
0.0
```
"""
struct ResetMap{N<:Real, S<:LazySet{N}} <: LazySet{N}
X::S
resets::Dict{Int, N}
end
"""
get_A(rm::ResetMap{N}) where {N<:Real}
Return the ``A`` matrix of the affine map ``A x + b, x ∈ X`` represented by a
reset map.
### Input
- `rm` -- reset map
### Output
The (sparse) square matrix for the affine map ``A x + b, x ∈ X`` represented by
the reset map.
### Algorithm
We construct the identity matrix and set all entries in the reset dimensions to
zero.
"""
function get_A(rm::ResetMap{N}) where {N<:Real}
n = dim(rm)
A = sparse(N(1)*I, n, n)
for i in keys(rm.resets)
A[i, i] = zero(N)
end
return A
end
"""
get_b(rm::ResetMap{N}) where {N<:Real}
Return the ``b`` vector of the affine map ``A x + b, x ∈ X`` represented by a
reset map.
### Input
- `rm` -- reset map
### Output
The (sparse) vector for the affine map ``A x + b, x ∈ X`` represented by the
reset map.
The vector contains the reset value for all reset dimensions, and is zero for
all other dimensions.
"""
function get_b(rm::ResetMap{N}) where {N<:Real}
n = dim(rm)
b = sparsevec(Int[], N[], n)
for (i, val) in rm.resets
b[i] = val
end
return b
end
# --- LazySet interface functions ---
"""
dim(rm::ResetMap)
Return the dimension of a reset map.
### Input
- `rm` -- reset map
### Output
The dimension of a reset map.
"""
function dim(rm::ResetMap)::Int
return dim(rm.X)
end
"""
σ(d::AbstractVector{N}, rm::ResetMap{N}) where {N<:Real}
Return the support vector of a reset map.
### Input
- `d` -- direction
- `rm` -- reset map
### Output
The support vector in the given direction.
If the direction has norm zero, the result depends on the wrapped set.
"""
function σ(d::AbstractVector{N}, rm::ResetMap{N}) where {N<:Real}
d_reset = copy(d)
for var in keys(rm.resets)
d_reset[var] = zero(N)
end
return substitute(rm.resets, σ(d_reset, rm.X))
end
"""
ρ(d::AbstractVector{N}, rm::ResetMap{N}) where {N<:Real}
Return the support function of a reset map.
### Input
- `d` -- direction
- `rm` -- reset map
### Output
The support function in the given direction.
### Notes
We use the usual dot-product definition, but for unbounded sets we redefine the
product between ``0`` and ``±∞`` as ``0``; Julia returns `NaN` here.
```jldoctest
julia> Inf * 0.0
NaN
```
See the discussion
[here](https://math.stackexchange.com/questions/28940/why-is-infty-cdot-0-not-clearly-equal-to-0).
"""
function ρ(d::AbstractVector{N}, rm::ResetMap{N}) where {N<:Real}
return dot_zero(d, σ(d, rm))
end
"""
an_element(rm::ResetMap)
Return some element of a reset map.
### Input
- `rm` -- reset map
### Output
An element in the reset map.
It relies on the `an_element` function of the wrapped set.
"""
function an_element(rm::ResetMap)
return substitute(rm.resets, an_element(rm.X))
end
"""
isempty(rm::ResetMap)::Bool
Return if a reset map is empty or not.
### Input
- `rm` -- reset map
### Output
`true` iff the wrapped set is empty.
"""
function isempty(rm::ResetMap)::Bool
return isempty(rm.X)
end
"""
constraints_list(rm::ResetMap{N}) where {N<:Real}
Return the list of constraints of a polytopic reset map.
### Input
- `rm` -- reset map of a polytope
### Output
The list of constraints of the reset map.
### Notes
We assume that the underlying set `X` is a polytope, i.e., is bounded and offers
a method `constraints_list(X)`.
### Algorithm
We fall back to `constraints_list` of a `LinearMap` of the `A`-matrix in the
affine-map view of a reset map.
Each reset dimension ``i`` is projected to zero, expressed by two constraints
for each reset dimension.
Then it remains to shift these constraints to the new value.
For instance, if the dimension ``5`` was reset to ``4``, then there will be
constraints ``x₅ ≤ 0`` and ``-x₅ ≤ 0``.
We then modify the right-hand side of these constraints to ``x₅ ≤ 4`` and
``-x₅ ≤ -4``, respectively.
"""
function constraints_list(rm::ResetMap{N}) where {N<:Real}
# if `vector` has exactly one non-zero entry, return its index
# otherwise return 0
function find_unique_nonzero_entry(vector::AbstractVector{N})
res = 0
for (i, v) in enumerate(vector)
if v != zero(N)
if res != 0
# at least two non-zero entries
return 0
else
# first non-zero entry so far
res = i
end
end
end
return res
end
constraints = copy(constraints_list(LinearMap(get_A(rm), rm.X)))
for (i, c) in enumerate(constraints)
constrained_dim = find_unique_nonzero_entry(c.a)
if constrained_dim > 0 # constraint in only one dimension
if !haskey(rm.resets, constrained_dim)
continue # not a dimension we are interested in
end
new_value = rm.resets[constrained_dim]
if new_value == zero(N)
@assert c.b == zero(N)
continue # a reset to 0 needs not create a new constraint
end
if c.a[constrained_dim] < zero(N)
# change sign for lower bound
new_value = -new_value
end
constraints[i] = HalfSpace(c.a, new_value)
end
end
return constraints
end
"""
constraints_list(rm::ResetMap{N, S}) where
{N<:Real, S<:AbstractHyperrectangle}
Return the list of constraints of a hyperrectangular reset map.
### Input
- `rm` -- reset map of a hyperrectangular set
### Output
The list of constraints of the reset map.
### Algorithm
We iterate through all dimensions.
If there is a reset, we construct the corresponding (flat) constraints.
Otherwise, we construct the corresponding constraints of the underlying set.
"""
function constraints_list(rm::ResetMap{N, S}
) where {N<:Real, S<:AbstractHyperrectangle}
H = rm.X
n = dim(H)
constraints = Vector{LinearConstraint{N}}(undef, 2*n)
j = 1
for i in 1:n
ei = LazySets.Approximations.UnitVector(i, n, one(N))
if haskey(rm.resets, i)
# reset dimension => add flat constraints
v = rm.resets[i]
constraints[j] = HalfSpace(ei, v)
constraints[j+1] = HalfSpace(-ei, -v)
else
# non-reset dimension => use the hyperrectangle's constraints
constraints[j] = HalfSpace(ei, high(H, i))
constraints[j+1] = HalfSpace(-ei, -low(H, i))
end
j += 2
end
return constraints
end