1
+ export PiecewiseNonlinearAdditiveNoiseDynamics, NonlinearDynamicsRegion
2
+
3
+ """
4
+ NonlinearDynamicsRegion
5
+
6
+ A struct representing a non-linear dynamics, valid over a region.
7
+ """
8
+ struct NonlinearDynamicsRegion{F<: Function , S<: LazySet }
9
+ f:: F
10
+ region:: S
11
+ end
12
+
13
+ function (dyn:: NonlinearDynamicsRegion )(X:: Hyperrectangle{Float64} , U:: Hyperrectangle{Float64} )
14
+ # Use the Taylor model to over-approximate the reachable set
15
+ active_region = intersection (X, dyn. region)
16
+ if isempty (active_region)
17
+ throw (ArgumentError (" The input region X does not intersect with the valid region of the dynamics" ))
18
+ end
19
+ active_region_box = box_approximation (active_region)
20
+
21
+ x0 = center (active_region_box)
22
+ u0 = center (U)
23
+ z0 = [x0; u0]
24
+ dom = IntervalBox ([low (active_region_box); low (U)], [high (active_region_box); high (U)])
25
+
26
+ # TaylorSeries.jl modifieds the global state - eww...
27
+ # Therefore, we prepare the global state before entering the threaded section.
28
+ # set_variables(Float64, "z"; order=order, numvars=LazySets.dim(X) + LazySets.dim(U))
29
+
30
+ order = 1
31
+ z = [TaylorModelN (i, order, IntervalBox (z0), dom) for i = 1 : LazySets. dim (X)+ LazySets. dim (U)]
32
+ x, u = (z- z0)[1 : LazySets. dim (X)], z[LazySets. dim (X)+ 1 : end ]
33
+
34
+ # Perform the Taylor expansion
35
+ y = dyn. f (x, u)
36
+
37
+ # Extract the linear and constant terms + the remainder
38
+ C = [constant_term (y[i]) for i = 1 : LazySets. dim (X)]
39
+ Clow = inf .(C)
40
+ Cupper = sup .(C)
41
+
42
+ AB = [
43
+ linear_polynomial (y[i])[1 ][j] for i = 1 : LazySets. dim (X),
44
+ j = 1 : LazySets. dim (X)+ LazySets. dim (U)
45
+ ]
46
+
47
+ A = AB[:, 1 : LazySets. dim (X)]
48
+ Alow = inf .(A)
49
+ Aupper = sup .(A)
50
+
51
+ B = AB[:, LazySets. dim (X)+ 1 : end ]
52
+ Blow = inf .(B)
53
+ Bupper = sup .(B)
54
+
55
+ D = [remainder (y[i]) for i = 1 : LazySets. dim (X)]
56
+ Dlower = inf .(D)
57
+ Dupper = sup .(D)
58
+
59
+ Y1 = Alow * Translation (active_region, - x0) + Blow * Translation (U, - u0) + Clow + Dlower
60
+ Y2 = Aupper * Translation (active_region, - x0) + Bupper * Translation (U, - u0) + Cupper + Dupper
61
+
62
+ Yconv = ConvexHull (Y1, Y2)
63
+
64
+ return Yconv
65
+ end
66
+
67
+
68
+ function (dyn:: NonlinearDynamicsRegion )(X:: Hyperrectangle{Float64} , U:: Singleton{Float64} )
69
+ return dyn (X, element (U))
70
+ end
71
+
72
+ function (dyn:: NonlinearDynamicsRegion )(X:: Hyperrectangle{Float64} , u:: AbstractVector{Float64} )
73
+ # Use the Taylor model to over-approximate the reachable set
74
+
75
+ active_region = intersection (X, dyn. region)
76
+ if isempty (active_region)
77
+ throw (ArgumentError (" The input region X does not intersect with the valid region of the dynamics" ))
78
+ end
79
+ active_region_box = box_approximation (active_region)
80
+
81
+ x0 = center (active_region_box)
82
+ dom = IntervalBox (low (active_region_box), high (active_region_box))
83
+
84
+ # TaylorSeries.jl modifieds the global state - eww...
85
+ # It also means that this function is not thread-safe!!
86
+
87
+ # We set 10 as the maximum order of the Taylor expansion
88
+ # set_variables(Float64, "x"; order=10, numvars=LazySets.dim(X))
89
+
90
+ order = 1
91
+ x = [TaylorModelN (i, order, IntervalBox (x0), dom) for i = 1 : LazySets. dim (X)]
92
+
93
+ # Perform the Taylor expansion
94
+ y = dyn. f (x, u)
95
+
96
+ # Extract the linear and constant terms + the remainder
97
+ C = [constant_term (y[i]) for i = 1 : LazySets. dim (X)]
98
+ Clow = inf .(C)
99
+ Cupper = sup .(C)
100
+
101
+ A = [linear_polynomial (y[i])[1 ][j] for i = 1 : LazySets. dim (X), j = 1 : LazySets. dim (X)]
102
+ Alow = inf .(A)
103
+ Aupper = sup .(A)
104
+
105
+ D = [remainder (y[i]) for i = 1 : LazySets. dim (X)]
106
+ Dlower = inf .(D)
107
+ Dupper = sup .(D)
108
+
109
+ Y1 = Alow * Translation (active_region, - x0) + Clow + Dlower
110
+ Y2 = Aupper * Translation (active_region, - x0) + Cupper + Dupper
111
+
112
+ Yconv = ConvexHull (Y1, Y2)
113
+
114
+ return Yconv
115
+ end
116
+
117
+ function (dyn:: NonlinearDynamicsRegion )(X:: Singleton{Float64} , U:: Singleton{Float64} )
118
+ x = element (X)
119
+ u = element (U)
120
+
121
+ y = dyn. f (x, u)
122
+
123
+ return Singleton (y)
124
+ end
125
+
126
+ function (dyn:: NonlinearDynamicsRegion )(x:: AbstractVector{Float64} , u:: AbstractVector{Float64} )
127
+ if x ∉ dyn. region
128
+ throw (ArgumentError (" The input x does not belong to the valid region of the dynamics" ))
129
+ end
130
+
131
+ return dyn. f (x, u)
132
+ end
133
+
134
+ """
135
+ PiecewiseNonlinearAdditiveNoiseDynamics
136
+
137
+ A struct representing non-linear dynamics with additive noise.
138
+ That is, ``x_{k+1} = f(x_k, u_k) + w_k``, where ``f(\\ cdot, u_k)`` is piecewise continuously differentiable function for each ``u_k \\ in U`` and
139
+ ``w_k \\ sim p_w`` and ``p_w`` is multivariate probability distribution.
140
+
141
+ !!! note
142
+ The nominal dynamics of this class are _assumed_ to be piecewise infinitely differentiable, i.e.
143
+ the Taylor expansion of the dynamics function `f` is well-defined. This is because to over-approximate
144
+ the one-step reachable set, we rely on Taylor models, which are Taylor expansions + a remainder term.
145
+ If you are dealing wit a non-differentiable dynamics function, consider using [`UncertainPWAAdditiveNoiseDynamics`](@ref) instead.
146
+ To obtain an `UncertainPWAAdditiveNoiseDynamics`, you can partitoned the state space and use Linear Bound Propagation
147
+ with each region (see [bound_propagation](https://github.com/Zinoex/bound_propagation)).
148
+
149
+ !!! warning
150
+ Before calling [`nominal`](@ref) with a `LazySet` as input, you must call [`prepare_nominal`](@ref).
151
+ This is because the `TaylorSeries.jl` package modifies its global state. If you are using multi-threading,
152
+ [`prepare_nominal`](@ref) must be called before entering the threaded section.
153
+
154
+ ### Fields
155
+ - `regions::Vector{<:NonlinearDynamicsRegion}`: A list of [`NonlinearDynamicsRegion`](@ref) to represent the piecewise dynamics.
156
+ - `nstate::Int`: The state dimension.
157
+ - `ninput::Int`: The input dimension.
158
+ - `w::AdditiveNoiseStructure`: The additive noise.
159
+
160
+ ### Examples
161
+
162
+ ```julia
163
+
164
+ τ = 0.1
165
+
166
+ region1 = Hyperrectangle(low=[-1.0, -1.0], high=[0.0, 1.0])
167
+ f(x, u) = [x[1] + x[2] * τ, x[2] + (-x[1] + (1 - x[1])^2 * x[2]) * τ]
168
+ dyn_reg1 = NonlinearDynamicsRegion(f, region1)
169
+
170
+ region2 = Hyperrectangle(low=[0.0, -1.0], high=[1.0, 1.0])
171
+ g(x, u) = [x[1] + x[2] * τ, x[2] + (-x[2] + (1 - x[2])^2 * x[1]) * τ]
172
+ dyn_reg2 = NonlinearDynamicsRegion(g, region2)
173
+
174
+ w_stddev = [0.1, 0.1]
175
+ w = AdditiveDiagonalGaussianNoise(w_stddev)
176
+
177
+ dyn = PiecewiseNonlinearAdditiveNoiseDynamics([dyn_reg1, dyn_reg2], 2, 0, w)
178
+ ```
179
+
180
+ """
181
+ struct PiecewiseNonlinearAdditiveNoiseDynamics{TW<: AdditiveNoiseStructure } < :
182
+ AdditiveNoiseDynamics
183
+ regions:: Vector{<:NonlinearDynamicsRegion}
184
+ nstate:: Int
185
+ ninput:: Int
186
+ w:: TW
187
+
188
+ function PiecewiseNonlinearAdditiveNoiseDynamics (
189
+ regions:: Vector{<:NonlinearDynamicsRegion} ,
190
+ nstate,
191
+ ninput,
192
+ w:: TW ,
193
+ ) where {TW<: AdditiveNoiseStructure }
194
+ if nstate != dim (w)
195
+ throw (ArgumentError (" The dimensionality of w must match the state dimension" ))
196
+ end
197
+
198
+ return new {TW} (regions, nstate, ninput, w)
199
+ end
200
+ end
201
+
202
+ function nominal (
203
+ dyn:: PiecewiseNonlinearAdditiveNoiseDynamics ,
204
+ X:: Hyperrectangle{Float64} ,
205
+ u,
206
+ )
207
+ reachable_set = EmptySet (dimstate (dyn))
208
+
209
+ for region in dyn. regions
210
+ if ! iszeromeasure (region. region, X)
211
+ reachable_set = ConvexHull (reachable_set, region (X, u))
212
+ end
213
+ end
214
+
215
+ return reachable_set
216
+ end
217
+
218
+
219
+ function nominal (
220
+ dyn:: PiecewiseNonlinearAdditiveNoiseDynamics ,
221
+ x:: AbstractVector{Float64} ,
222
+ u,
223
+ )
224
+ for region in dyn. regions
225
+ if x ∈ region. region
226
+ return region (x, u)
227
+ end
228
+ end
229
+ end
230
+
231
+ noise (dyn:: PiecewiseNonlinearAdditiveNoiseDynamics ) = dyn. w
232
+ dimstate (dyn:: PiecewiseNonlinearAdditiveNoiseDynamics ) = dyn. nstate
233
+ diminput (dyn:: PiecewiseNonlinearAdditiveNoiseDynamics ) = dyn. ninput
234
+
235
+ function prepare_nominal (dyn:: PiecewiseNonlinearAdditiveNoiseDynamics , input_abstraction)
236
+ n = dimstate (dyn)
237
+ if issetbased (input_abstraction)
238
+ m = diminput (dyn)
239
+ n += m
240
+ end
241
+
242
+ # Set the Taylor model variables
243
+ set_variables (Float64, " z" ; order = 2 , numvars = n)
244
+
245
+ return nothing
246
+ end
0 commit comments