Skip to content

Commit 0016acf

Browse files
committed
Add piecewise non-linear dynamics
1 parent 1680792 commit 0016acf

3 files changed

+251
-3
lines changed

src/IntervalMDPAbstractions.jl

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ include("dynamics/base.jl")
2424
include("dynamics/additive_noise.jl")
2525
include("dynamics/AffineAdditiveNoiseDynamics.jl")
2626
include("dynamics/NonlinearAdditiveNoiseDynamics.jl")
27+
include("dynamics/PiecewiseNonlinearAdditiveNoiseDynamics.jl")
2728
include("dynamics/UncertainPWAAdditiveNoiseDynamics.jl")
2829
include("dynamics/StochasticSwitchedDynamics.jl")
2930
include("dynamics/AbstractedGaussianProcess.jl")

src/dynamics/NonlinearAdditiveNoiseDynamics.jl

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ export NonlinearAdditiveNoiseDynamics
44
"""
55
NonlinearAdditiveNoiseDynamics
66
7-
A struct representing dynamics with additive noise.
8-
That is, ``x_{k+1} = f(x_k, u_k) + w_k``, where ``w_k \\sim p_w`` and ``p_w`` is multivariate probability distribution.
7+
A struct representing continuous non-linear dynamics with additive noise.
8+
That is, ``x_{k+1} = f(x_k, u_k) + w_k``, where ``f(\\cdot, u_k)`` is continuously differentiable function for each ``u_k \\in U`` and
9+
``w_k \\sim p_w`` and ``p_w`` is multivariate probability distribution.
910
1011
!!! note
1112
The nominal dynamics of this class are _assumed_ to be infinitely differentiable, i.e.
@@ -35,7 +36,7 @@ That is, ``x_{k+1} = f(x_k, u_k) + w_k``, where ``w_k \\sim p_w`` and ``p_w`` is
3536
f(x, u) = [x[1] + x[2] * τ, x[2] + (-x[1] + (1 - x[1])^2 * x[2]) * τ]
3637
3738
w_stddev = [0.1, 0.1]
38-
w = AdditiveDiagonalUniformNoise(w_stddev)
39+
w = AdditiveCentralUniformNoise(w_stddev)
3940
4041
dyn = NonlinearAdditiveNoiseDynamics(f, 2, 0, w)
4142
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
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

Comments
 (0)