PyDiffGame 0.1.2__py3-none-any.whl → 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- PyDiffGame/__init__.py +50 -0
- PyDiffGame/_typing.py +25 -0
- PyDiffGame/base.py +468 -0
- PyDiffGame/comparison.py +121 -0
- PyDiffGame/continuous.py +223 -0
- PyDiffGame/discrete.py +211 -0
- PyDiffGame/examples/InvertedPendulumComparison.py +211 -236
- PyDiffGame/examples/MassesWithSpringsComparison.py +109 -208
- PyDiffGame/examples/PVTOL.py +143 -149
- PyDiffGame/examples/PVTOLComparison.py +75 -69
- PyDiffGame/examples/QuadRotorControl.py +394 -304
- PyDiffGame/lqr.py +30 -0
- PyDiffGame/objective.py +108 -0
- PyDiffGame/plotting.py +98 -0
- pydiffgame-2.0.0.dist-info/METADATA +408 -0
- {pydiffgame-0.1.2.dist-info → pydiffgame-2.0.0.dist-info}/RECORD +18 -16
- {pydiffgame-0.1.2.dist-info → pydiffgame-2.0.0.dist-info}/WHEEL +1 -1
- PyDiffGame/ContinuousPyDiffGame.py +0 -275
- PyDiffGame/DiscretePyDiffGame.py +0 -359
- PyDiffGame/LQR.py +0 -73
- PyDiffGame/Objective.py +0 -62
- PyDiffGame/PyDiffGame.py +0 -1273
- PyDiffGame/PyDiffGameLQRComparison.py +0 -169
- pydiffgame-0.1.2.dist-info/METADATA +0 -306
- {pydiffgame-0.1.2.dist-info → pydiffgame-2.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,17 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
"""Hierarchical quadrotor control via continuous differential games.
|
|
2
|
+
|
|
3
|
+
A quadrotor is stabilised by a two-layer controller:
|
|
4
|
+
|
|
5
|
+
* a *low-level* angular-rate controller, designed as a 3-player continuous
|
|
6
|
+
differential game on the body-rate dynamics (one player per body axis), and
|
|
7
|
+
* a *high-level* outflow/position controller, designed as a 2-player continuous
|
|
8
|
+
differential game on a 9-state outer-loop model.
|
|
9
|
+
|
|
10
|
+
Both layers solve their feedback Nash gains with
|
|
11
|
+
:class:`~PyDiffGame.continuous.ContinuousPyDiffGame` and the resulting commands
|
|
12
|
+
drive the full nonlinear rigid-body dynamics, integrated with
|
|
13
|
+
:func:`scipy.integrate.odeint`.
|
|
14
|
+
|
|
15
|
+
Run directly to simulate a short trajectory headless::
|
|
16
|
+
|
|
17
|
+
python -m PyDiffGame.examples.QuadRotorControl
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
2
21
|
|
|
3
22
|
import numpy as np
|
|
23
|
+
from numpy import arctan, cos, sin
|
|
4
24
|
from numpy.linalg import inv
|
|
5
|
-
import matplotlib.pyplot as plt
|
|
6
25
|
from scipy.integrate import odeint
|
|
7
|
-
from tqdm import tqdm
|
|
8
|
-
from math import cos, sin
|
|
9
26
|
from scipy.optimize import nnls
|
|
10
|
-
from numpy import sin, cos, arctan
|
|
11
|
-
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
|
|
12
|
-
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
|
|
13
27
|
|
|
14
|
-
from PyDiffGame.
|
|
28
|
+
from PyDiffGame.continuous import ContinuousPyDiffGame
|
|
29
|
+
from PyDiffGame.objective import GameObjective
|
|
30
|
+
from PyDiffGame.plotting import show
|
|
15
31
|
|
|
16
32
|
# Global Constants
|
|
17
33
|
|
|
@@ -21,7 +37,7 @@ Ixx = 7.5e-3
|
|
|
21
37
|
Iyy = 7.5e-3
|
|
22
38
|
Izz = 1.3e-2
|
|
23
39
|
m = 0.65
|
|
24
|
-
|
|
40
|
+
length = 0.23
|
|
25
41
|
Jr = 6e-5
|
|
26
42
|
b = 3.13e-5
|
|
27
43
|
d = 7.5e-7
|
|
@@ -32,18 +48,19 @@ a3 = (Izz - Ixx) / Iyy
|
|
|
32
48
|
a4 = Jr / Iyy
|
|
33
49
|
a5 = (Ixx - Iyy) / Izz
|
|
34
50
|
|
|
35
|
-
b1 =
|
|
36
|
-
b2 =
|
|
51
|
+
b1 = length / Ixx
|
|
52
|
+
b2 = length / Iyy
|
|
37
53
|
b3 = 1 / Izz
|
|
38
54
|
|
|
39
|
-
v_d_s_0_2 = 0
|
|
40
|
-
h_d_s_0_2 = 0
|
|
55
|
+
v_d_s_0_2 = 0.0
|
|
56
|
+
h_d_s_0_2 = 0.0
|
|
41
57
|
sum_theta = sum_theta_init
|
|
42
|
-
sum_theta_2 = 0
|
|
58
|
+
sum_theta_2 = 0.0
|
|
43
59
|
|
|
44
60
|
|
|
45
61
|
# Low-Level Control
|
|
46
62
|
|
|
63
|
+
|
|
47
64
|
def quad_rotor_state_diff_eqn_for_given_pqrT(X, _, p, q, r, T, Plast):
|
|
48
65
|
phi, dPhidt, theta, dThetadt, psi, dPsidt, z, dzdt, x, dxdt, y, dydt = X
|
|
49
66
|
u_x = cos(phi) * sin(theta) * cos(psi) + sin(phi) * sin(psi)
|
|
@@ -51,16 +68,15 @@ def quad_rotor_state_diff_eqn_for_given_pqrT(X, _, p, q, r, T, Plast):
|
|
|
51
68
|
|
|
52
69
|
K = cos(phi) * cos(theta) / m
|
|
53
70
|
U = low_level_angular_rate_controller([dPhidt, dThetadt, dPsidt], p, q, r, T, Plast)
|
|
54
|
-
omegas_squared_coeffs = np.array([[b] * 4,
|
|
55
|
-
[0, -b, 0, b],
|
|
56
|
-
[b, 0, -b, 0],
|
|
57
|
-
[-d, d, -d, d]
|
|
58
|
-
])
|
|
71
|
+
omegas_squared_coeffs = np.array([[b] * 4, [0, -b, 0, b], [b, 0, -b, 0], [-d, d, -d, d]])
|
|
59
72
|
u1, u2, u3, u4 = U
|
|
60
73
|
omegas_squared = nnls(omegas_squared_coeffs, np.array([u1, u2, u3, u4]))[0]
|
|
61
74
|
omegas = np.sqrt(omegas_squared)
|
|
62
75
|
|
|
63
|
-
|
|
76
|
+
# Residual rotor angular rate Omega_r = w1 - w2 + w3 - w4 (rad/s) driving the
|
|
77
|
+
# gyroscopic coupling. It must NOT be scaled by the drag coefficient d, which
|
|
78
|
+
# would shrink it by ~1e6 and zero out the gyroscopic terms.
|
|
79
|
+
u5 = omegas[0] - omegas[1] + omegas[2] - omegas[3]
|
|
64
80
|
|
|
65
81
|
dPhiddt = dThetadt * (dPsidt * a1 + u5 * a2) + b1 * u2
|
|
66
82
|
dThetaddt = dPhidt * (dPsidt * a3 - u5 * a4) + b2 * u3
|
|
@@ -69,22 +85,18 @@ def quad_rotor_state_diff_eqn_for_given_pqrT(X, _, p, q, r, T, Plast):
|
|
|
69
85
|
dxddt = u_x * u1 / m
|
|
70
86
|
dyddt = u_y * u1 / m
|
|
71
87
|
|
|
72
|
-
return np.array(
|
|
73
|
-
|
|
88
|
+
return np.array(
|
|
89
|
+
[dPhidt, dPhiddt, dThetadt, dThetaddt, dPsidt, dPsiddt, dzdt, dzddt, dxdt, dxddt, dydt, dyddt],
|
|
90
|
+
dtype=np.float64,
|
|
91
|
+
)
|
|
74
92
|
|
|
75
93
|
|
|
76
94
|
def low_level_angular_rate_controller(x, p, q, r, T, Plast):
|
|
77
|
-
B1 = np.array([[b1],
|
|
78
|
-
[0],
|
|
79
|
-
[0]])
|
|
95
|
+
B1 = np.array([[b1], [0], [0]])
|
|
80
96
|
|
|
81
|
-
B2 = np.array([[0],
|
|
82
|
-
[b2],
|
|
83
|
-
[0]])
|
|
97
|
+
B2 = np.array([[0], [b2], [0]])
|
|
84
98
|
|
|
85
|
-
B3 = np.array([[0],
|
|
86
|
-
[0],
|
|
87
|
-
[b3]])
|
|
99
|
+
B3 = np.array([[0], [0], [b3]])
|
|
88
100
|
|
|
89
101
|
R1 = np.array([[0.1]])
|
|
90
102
|
R2 = np.array([[0.1]])
|
|
@@ -95,9 +107,13 @@ def low_level_angular_rate_controller(x, p, q, r, T, Plast):
|
|
|
95
107
|
P_sol = Plast
|
|
96
108
|
reduced_X = np.array(x) - np.array([p, q, r])
|
|
97
109
|
reduced_X_tr = reduced_X.T
|
|
98
|
-
inv_Rs = [inv(
|
|
99
|
-
B_t = [
|
|
100
|
-
U_angular = np.array(
|
|
110
|
+
inv_Rs = [inv(r_i) for r_i in R]
|
|
111
|
+
B_t = [b_i.T for b_i in B]
|
|
112
|
+
U_angular = np.array(
|
|
113
|
+
[-r_i @ b_i @ p_i @ reduced_X_tr for r_i, b_i, p_i in zip(inv_Rs, B_t, P_sol)]
|
|
114
|
+
).reshape(
|
|
115
|
+
3,
|
|
116
|
+
)
|
|
101
117
|
u2, u3, u4 = U_angular
|
|
102
118
|
U = [T, u2, u3, u4]
|
|
103
119
|
|
|
@@ -105,56 +121,55 @@ def low_level_angular_rate_controller(x, p, q, r, T, Plast):
|
|
|
105
121
|
|
|
106
122
|
|
|
107
123
|
def get_P_quad_given_angular_rates(x, P_sol):
|
|
108
|
-
A = np.array(
|
|
109
|
-
|
|
110
|
-
|
|
124
|
+
A = np.array(
|
|
125
|
+
[
|
|
126
|
+
[0, (1 / 2) * a1 * x[2], (1 / 2) * a1 * x[1]],
|
|
127
|
+
[(1 / 2) * a3 * x[2], 0, (1 / 2) * a3 * x[0]],
|
|
128
|
+
[(1 / 2) * a5 * x[1], (1 / 2) * a5 * x[0], 0],
|
|
129
|
+
]
|
|
130
|
+
)
|
|
111
131
|
|
|
112
|
-
B1 = np.array([[b1],
|
|
113
|
-
[0],
|
|
114
|
-
[0]])
|
|
132
|
+
B1 = np.array([[b1], [0], [0]])
|
|
115
133
|
|
|
116
|
-
B2 = np.array([[0],
|
|
117
|
-
[b2],
|
|
118
|
-
[0]])
|
|
134
|
+
B2 = np.array([[0], [b2], [0]])
|
|
119
135
|
|
|
120
|
-
B3 = np.array([[0],
|
|
121
|
-
[0],
|
|
122
|
-
[b3]])
|
|
136
|
+
B3 = np.array([[0], [0], [b3]])
|
|
123
137
|
|
|
124
|
-
Q1 = np.array([[1000, 0, 0],
|
|
125
|
-
[0, 10, 0],
|
|
126
|
-
[0, 0, 10]])
|
|
138
|
+
Q1 = np.array([[1000, 0, 0], [0, 10, 0], [0, 0, 10]])
|
|
127
139
|
|
|
128
|
-
Q2 = np.array([[10, 0, 0],
|
|
129
|
-
[0, 1000, 0],
|
|
130
|
-
[0, 0, 10]])
|
|
140
|
+
Q2 = np.array([[10, 0, 0], [0, 1000, 0], [0, 0, 10]])
|
|
131
141
|
|
|
132
|
-
Q3 = np.array([[10, 0, 0],
|
|
133
|
-
[0, 10, 0],
|
|
134
|
-
[0, 0, 1000]])
|
|
142
|
+
Q3 = np.array([[10, 0, 0], [0, 10, 0], [0, 0, 1000]])
|
|
135
143
|
|
|
136
144
|
R1 = np.array([[0.1]])
|
|
137
145
|
R2 = np.array([[0.1]])
|
|
138
146
|
R3 = np.array([[0.1]])
|
|
139
147
|
|
|
140
|
-
|
|
148
|
+
Bs = [B1, B2, B3]
|
|
141
149
|
R = [R1, R2, R3]
|
|
142
150
|
Q = [Q1, Q2, Q3]
|
|
143
|
-
game = ContinuousPyDiffGame(A=A, Bs=B, Qs=Q, Rs=R, P_f=P_sol, show_legend=False)
|
|
144
|
-
game()
|
|
145
|
-
Plast = game.P[-1]
|
|
146
151
|
|
|
147
|
-
|
|
152
|
+
# Each player drives one body axis; the decomposition matrices are the rows
|
|
153
|
+
# of the identity so that ``concat(M) == I`` and ``B == hstack(Bs)``.
|
|
154
|
+
identity = np.eye(3)
|
|
155
|
+
Ms = [identity[i : i + 1, :] for i in range(3)]
|
|
156
|
+
objectives = [GameObjective(Q=Q_i, R=R_i, M=M_i) for Q_i, R_i, M_i in zip(Q, R, Ms)]
|
|
157
|
+
|
|
158
|
+
game = ContinuousPyDiffGame(A=A, objectives=objectives, Bs=Bs, P_f=P_sol, show_legend=False)
|
|
159
|
+
game.solve()
|
|
160
|
+
|
|
161
|
+
return game.P
|
|
148
162
|
|
|
149
163
|
|
|
150
164
|
# High-Level Control
|
|
151
165
|
|
|
166
|
+
|
|
152
167
|
def get_mf_numerator(F3, R11, F1, R31, a_y, R12, R32):
|
|
153
168
|
return F3 * R11 - F1 * R31 + a_y * (R12 * R31 - R11 * R32)
|
|
154
169
|
|
|
155
170
|
|
|
156
171
|
def get_mf_denominator(F2, R11, F1, R21, a_y, R22, R12):
|
|
157
|
-
return -
|
|
172
|
+
return -F2 * R11 + F1 * R21 + a_y * (R11 * R22 - R12 * R21)
|
|
158
173
|
|
|
159
174
|
|
|
160
175
|
def get_mc_numerator(mf_numerator, a_z, R31, R13, R11, R33):
|
|
@@ -179,19 +194,18 @@ def calculate_Bs(u_sizes, dividing_matrix, B):
|
|
|
179
194
|
|
|
180
195
|
last = 0
|
|
181
196
|
for u_size in u_sizes:
|
|
182
|
-
Bs += [block_matrix[:, last:u_size + last]]
|
|
197
|
+
Bs += [block_matrix[:, last : u_size + last]]
|
|
183
198
|
last = u_size
|
|
184
199
|
|
|
185
200
|
return Bs
|
|
186
201
|
|
|
187
202
|
|
|
188
203
|
def wall_punishment(wall_distance, a_y):
|
|
189
|
-
return 3 * (10
|
|
204
|
+
return 3 * (10**2) * (wall_distance / a_y) ** 2
|
|
190
205
|
|
|
191
206
|
|
|
192
207
|
def get_higher_level_control2(state, st, a_y):
|
|
193
208
|
global v_d_s_0_2, h_d_s_0_2, sum_theta, sum_theta_2
|
|
194
|
-
# a_y = 1
|
|
195
209
|
a_z = -2.5
|
|
196
210
|
|
|
197
211
|
x = state[8]
|
|
@@ -212,7 +226,7 @@ def get_higher_level_control2(state, st, a_y):
|
|
|
212
226
|
tanpsi = spsi / cpsi
|
|
213
227
|
|
|
214
228
|
vp_x = sectheta * (sphi * stheta - cphi * tanpsi)
|
|
215
|
-
vp_y = sectheta * (-
|
|
229
|
+
vp_y = sectheta * (-cphi * stheta - sphi * tanpsi)
|
|
216
230
|
|
|
217
231
|
R11 = ctheta * cpsi
|
|
218
232
|
R21 = cpsi * stheta * sphi - cphi * spsi
|
|
@@ -220,7 +234,7 @@ def get_higher_level_control2(state, st, a_y):
|
|
|
220
234
|
R12 = ctheta * spsi
|
|
221
235
|
R22 = spsi * stheta * sphi + cphi * cpsi
|
|
222
236
|
R32 = spsi * stheta * cphi - sphi * cpsi
|
|
223
|
-
R13 = -
|
|
237
|
+
R13 = -stheta
|
|
224
238
|
R23 = ctheta * sphi
|
|
225
239
|
R33 = ctheta * cphi
|
|
226
240
|
r = np.array([[R11, R21, R31], [R12, R22, R32], [R13, R23, R33]])
|
|
@@ -236,8 +250,12 @@ def get_higher_level_control2(state, st, a_y):
|
|
|
236
250
|
mfr = mfr_numerator / mfr_denominator
|
|
237
251
|
mfl = mfl_numerator / mfl_denominator
|
|
238
252
|
|
|
239
|
-
mcr = get_mc_numerator(mfr_numerator, a_z, R31, R13, R11, R33) / get_mc_denominator(
|
|
240
|
-
|
|
253
|
+
mcr = get_mc_numerator(mfr_numerator, a_z, R31, R13, R11, R33) / get_mc_denominator(
|
|
254
|
+
mfr_denominator, a_z, R11, R23
|
|
255
|
+
)
|
|
256
|
+
mcl = get_mc_numerator(mfl_numerator, a_z, R31, R13, R11, R33) / get_mc_denominator(
|
|
257
|
+
mfl_denominator, a_z, R11, R23
|
|
258
|
+
)
|
|
241
259
|
|
|
242
260
|
at_mcl = arctan(mcl)
|
|
243
261
|
at_mcr = arctan(mcr)
|
|
@@ -266,48 +284,54 @@ def get_higher_level_control2(state, st, a_y):
|
|
|
266
284
|
v_d_s_0_2 = v_d_s
|
|
267
285
|
h_d_s_0_2 = h_d_s
|
|
268
286
|
|
|
269
|
-
Q1 = np.array(
|
|
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
|
-
|
|
287
|
+
Q1 = np.array(
|
|
288
|
+
[
|
|
289
|
+
[1000, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
290
|
+
[0, 1000, 0, 0, 0, 0, 0, 0, 0],
|
|
291
|
+
[0, 0, 1000, 0, 0, 0, 0, 0, 0],
|
|
292
|
+
[0, 0, 0, 0.1, 0, 0, 0, 0, 0],
|
|
293
|
+
[0, 0, 0, 0, 0.1, 0, 0, 0, 0],
|
|
294
|
+
[0, 0, 0, 0, 0, 10, 0, 0, 0],
|
|
295
|
+
[0, 0, 0, 0, 0, 0, 0.05, 0, 0],
|
|
296
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
297
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
298
|
+
]
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
R1 = np.array([[10, 0, 0, 0], [0, 10, 0, 0], [0, 0, 10, 0], [0, 0, 0, 0.01]])
|
|
302
|
+
|
|
303
|
+
A = np.array(
|
|
304
|
+
[
|
|
305
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
306
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
307
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
308
|
+
[0, 0, 0, 0, 1, 0, 0, 0, 0],
|
|
309
|
+
[g, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
310
|
+
[0, 0, 0, 0, 0, 0, 1, 0, 0],
|
|
311
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
312
|
+
[0, 1, 0, 0, 0, 0, 0, 0, 0],
|
|
313
|
+
[0, 1, 0, 0, 0, 0, 0, 0, 0],
|
|
314
|
+
]
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
B = np.array(
|
|
318
|
+
[
|
|
319
|
+
[1, 0, 0, 0],
|
|
320
|
+
[0, 1, 0, 0],
|
|
321
|
+
[0, 0, 1, 0],
|
|
322
|
+
[0, 0, 0, 0],
|
|
323
|
+
[0, 0, 0, 0],
|
|
324
|
+
[0, 0, 0, 0],
|
|
325
|
+
[0, 0, 0, -1 / m],
|
|
326
|
+
[0, 0, 0, 0],
|
|
327
|
+
[0, 0, 0, 0],
|
|
328
|
+
]
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
dividing_matrix = np.array([[1, 0, 0, 0, 0], [0, 1, 0, 0, 1], [0, 0, 1, 0, 0], [0, 0, 0, 1, 0]])
|
|
308
332
|
|
|
309
333
|
u_sizes = [4, 1]
|
|
310
|
-
Ms = [dividing_matrix[:, :u_sizes[0]].T, dividing_matrix[:, u_sizes[0]:u_sizes[0] + u_sizes[1]].T]
|
|
334
|
+
Ms = [dividing_matrix[:, : u_sizes[0]].T, dividing_matrix[:, u_sizes[0] : u_sizes[0] + u_sizes[1]].T]
|
|
311
335
|
Bs = calculate_Bs(u_sizes, dividing_matrix, B)
|
|
312
336
|
|
|
313
337
|
R2 = np.array([[10]])
|
|
@@ -315,24 +339,32 @@ def get_higher_level_control2(state, st, a_y):
|
|
|
315
339
|
max_punishment = wall_punishment(a_y, a_y)
|
|
316
340
|
curr_punishment = min(max_punishment, wall_punishment(p_y_tilda[0], a_y))
|
|
317
341
|
|
|
318
|
-
Q_wall_0 = np.array(
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
342
|
+
Q_wall_0 = np.array(
|
|
343
|
+
[
|
|
344
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
345
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
346
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
347
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
348
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
349
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
350
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
351
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
352
|
+
[0, 0, 0, 0, 0, 0, 0, 0, curr_punishment],
|
|
353
|
+
]
|
|
354
|
+
)
|
|
355
|
+
Q_speed_up_0 = np.array(
|
|
356
|
+
[
|
|
357
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
358
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
359
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
360
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
361
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
362
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
363
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
364
|
+
[0, 0, 0, 0, 0, 0, 0, 1000, 0],
|
|
365
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
366
|
+
]
|
|
367
|
+
)
|
|
336
368
|
|
|
337
369
|
R = [R1, R2]
|
|
338
370
|
if abs(p_y_tilda[0] / a_y) < 0.5:
|
|
@@ -341,21 +373,19 @@ def get_higher_level_control2(state, st, a_y):
|
|
|
341
373
|
Q = [0.01 * Q1, 0.01 * Q_wall_0]
|
|
342
374
|
P_sol = [0.01 * Q1, 0.01 * Q1]
|
|
343
375
|
|
|
344
|
-
|
|
345
|
-
game()
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
M = 9
|
|
349
|
-
P_size = M ** 2
|
|
350
|
-
Plast = [(Plast[i * P_size:(i + 1) * P_size]).reshape(M, M) for i in range(N)]
|
|
376
|
+
objectives = [GameObjective(Q=Q_i, R=R_i, M=M_i) for Q_i, R_i, M_i in zip(Q, R, Ms)]
|
|
377
|
+
game = ContinuousPyDiffGame(A=A, objectives=objectives, Bs=Bs, P_f=P_sol, show_legend=False)
|
|
378
|
+
game.solve()
|
|
379
|
+
Plast = game.P
|
|
351
380
|
|
|
352
|
-
inv_Rs = [inv(
|
|
353
|
-
B_t = [
|
|
381
|
+
inv_Rs = [inv(r_i) for r_i in R]
|
|
382
|
+
B_t = [b_i.T for b_i in Bs]
|
|
354
383
|
|
|
355
|
-
|
|
356
|
-
[-phi_tilda[0], -vp_y, -vp_x, -p_y_tilda[0], -h_d[0], -p_z_tilda[0], -v_d[0], sum_theta, sum_theta_2]
|
|
357
|
-
|
|
358
|
-
|
|
384
|
+
reduced_state = np.array(
|
|
385
|
+
[-phi_tilda[0], -vp_y, -vp_x, -p_y_tilda[0], -h_d[0], -p_z_tilda[0], -v_d[0], sum_theta, sum_theta_2]
|
|
386
|
+
)
|
|
387
|
+
U_Agenda1 = -inv_Rs[0] @ B_t[0] @ Plast[0] @ reduced_state
|
|
388
|
+
U_Agenda2 = -inv_Rs[1] @ B_t[1] @ Plast[1] @ reduced_state
|
|
359
389
|
|
|
360
390
|
Us = [U_Agenda1, U_Agenda2]
|
|
361
391
|
|
|
@@ -363,7 +393,8 @@ def get_higher_level_control2(state, st, a_y):
|
|
|
363
393
|
p_r, q_r, r_r, t_r = U_all_Out
|
|
364
394
|
|
|
365
395
|
tilda_state = np.array(
|
|
366
|
-
[phi_tilda[0], vp_y, vp_x, p_y_tilda[0], h_d[0], p_z_tilda[0], v_d[0], sum_theta, sum_theta_2]
|
|
396
|
+
[phi_tilda[0], vp_y, vp_x, p_y_tilda[0], h_d[0], p_z_tilda[0], v_d[0], sum_theta, sum_theta_2]
|
|
397
|
+
)
|
|
367
398
|
sum_theta = sum_theta - vp_y * 0.1
|
|
368
399
|
sum_theta_2 = sum_theta_2 - vp_y * 0.1
|
|
369
400
|
|
|
@@ -372,177 +403,236 @@ def get_higher_level_control2(state, st, a_y):
|
|
|
372
403
|
|
|
373
404
|
# Simulation
|
|
374
405
|
|
|
375
|
-
Q1 = np.array([[1000, 0, 0],
|
|
376
|
-
[0, 10, 0],
|
|
377
|
-
[0, 0, 10]])
|
|
378
|
-
Q2 = np.array([[10, 0, 0],
|
|
379
|
-
[0, 1000, 0],
|
|
380
|
-
[0, 0, 10]])
|
|
381
|
-
Q3 = np.array([[10, 0, 0],
|
|
382
|
-
[0, 10, 0],
|
|
383
|
-
[0, 0, 1000]])
|
|
384
|
-
Q = [Q1, Q2, Q3]
|
|
385
|
-
M = 3
|
|
386
|
-
P_size = M ** 2
|
|
387
|
-
N = 3
|
|
388
|
-
tTotal = [0]
|
|
389
|
-
tTotal_low = [0]
|
|
390
|
-
a_ys = [0.55]
|
|
391
|
-
quad_rotor_state_PD_dynamic = {}
|
|
392
|
-
quad_rotor_omega_D_dynamic = {}
|
|
393
|
-
quad_rotor_state_PD_dynamic_low = {}
|
|
394
|
-
tilda_state_dynamic = {}
|
|
395
|
-
|
|
396
|
-
for a_y in a_ys:
|
|
397
|
-
|
|
398
|
-
T_start = 0
|
|
399
|
-
deltaTstate = 0.1
|
|
400
|
-
X_rotor_0 = np.array([0.1, 0, 0, 0, 0.1, 0, -1, 0, 0, 0, 0.3, 0])
|
|
401
|
-
omega_rotor_0 = np.array([0, 0, 0])
|
|
402
|
-
quad_rotor_state_PD_dynamic[a_y] = [X_rotor_0]
|
|
403
|
-
quad_rotor_omega_D_dynamic[a_y] = [omega_rotor_0]
|
|
404
|
-
quad_rotor_state_PD_dynamic_low[a_y] = [X_rotor_0]
|
|
405
|
-
tilda_state = np.array([0, 0, 0, 0, 0, 0, 0, sum_theta_init, 0])
|
|
406
|
-
tilda_state_dynamic[a_y] = [tilda_state]
|
|
407
|
-
|
|
408
|
-
X_rotor_0_PD = X_rotor_0
|
|
409
|
-
Plast = [Q1, Q2, Q3]
|
|
410
|
-
v_d_s_0_2 = 0
|
|
411
|
-
h_d_s_0_2 = 0
|
|
412
|
-
sum_theta = sum_theta_init
|
|
413
|
-
sum_theta_2 = 0
|
|
414
|
-
|
|
415
|
-
for i in tqdm(range(300)):
|
|
416
|
-
# p_r, q_r, r_r, t_r, tilda_state_l = get_higher_level_control(X_rotor_0_PD,T_start, a_y)
|
|
417
|
-
p_r2, q_r2, r_r2, t_r2, tilda_state_l2 = get_higher_level_control2(X_rotor_0_PD, T_start, a_y)
|
|
418
|
-
tilda_state = [tilda_state_l2]
|
|
419
|
-
tilda_state_dynamic[a_y] = np.append(tilda_state_dynamic[a_y], tilda_state, axis=0)
|
|
420
|
-
T_end = T_start + deltaTstate
|
|
421
|
-
data_points = 100
|
|
422
|
-
t = np.linspace(T_start, T_end, data_points)
|
|
423
|
-
Plast = get_P_quad_given_angular_rates([X_rotor_0_PD[1], X_rotor_0_PD[3], X_rotor_0_PD[5]], Plast)
|
|
424
|
-
Plast = [(Plast[k * P_size:(k + 1) * P_size]).reshape(M, M) for k in range(N)]
|
|
425
|
-
quad_rotor_state_PD = odeint(quad_rotor_state_diff_eqn_for_given_pqrT, X_rotor_0_PD, t,
|
|
426
|
-
args=(p_r2, q_r2, r_r2, t_r2, Plast))
|
|
427
|
-
omega_rotor_i = np.array([p_r2, q_r2, r_r2])
|
|
428
|
-
X_rotor_0_PD = quad_rotor_state_PD[-1]
|
|
429
|
-
T_start = T_end
|
|
430
|
-
quad_rotor_state_PD_dynamic[a_y] = np.append(quad_rotor_state_PD_dynamic[a_y],
|
|
431
|
-
quad_rotor_state_PD, axis=0)
|
|
432
|
-
quad_rotor_omega_D_dynamic[a_y] = np.append(quad_rotor_omega_D_dynamic[a_y],
|
|
433
|
-
[omega_rotor_i], axis=0)
|
|
434
|
-
quad_rotor_state_PD_dynamic_low[a_y] = np.append(quad_rotor_state_PD_dynamic_low[a_y],
|
|
435
|
-
[X_rotor_0_PD], axis=0)
|
|
436
|
-
if a_y == a_ys[0]:
|
|
437
|
-
tTotal = np.append(tTotal, t)
|
|
438
|
-
tTotal_low = np.append(tTotal_low, T_end)
|
|
439
|
-
|
|
440
|
-
angles = {'phi': [0, 0], 'theta': [2, 1], 'psi': [4, 2]}
|
|
441
|
-
positions = {'x': [8, 7], 'z': [6, 5], 'y': [10, 3]}
|
|
442
|
-
velocities = {'y_dot': [11, 4], 'z_dot': [7, 6], 'x_dot': 9}
|
|
443
406
|
|
|
407
|
+
def run_simulation(a_ys=(0.55,), n_steps=8):
|
|
408
|
+
"""Run the hierarchical closed-loop simulation for each wall distance ``a_y``.
|
|
409
|
+
|
|
410
|
+
Returns dictionaries of recorded trajectories keyed by ``a_y`` plus the
|
|
411
|
+
common time vectors.
|
|
412
|
+
"""
|
|
413
|
+
|
|
414
|
+
global v_d_s_0_2, h_d_s_0_2, sum_theta, sum_theta_2
|
|
415
|
+
|
|
416
|
+
tTotal = [0]
|
|
417
|
+
tTotal_low = [0]
|
|
418
|
+
quad_rotor_state_PD_dynamic = {}
|
|
419
|
+
quad_rotor_omega_D_dynamic = {}
|
|
420
|
+
quad_rotor_state_PD_dynamic_low = {}
|
|
421
|
+
tilda_state_dynamic = {}
|
|
422
|
+
|
|
423
|
+
for a_y in a_ys:
|
|
424
|
+
T_start = 0
|
|
425
|
+
deltaTstate = 0.1
|
|
426
|
+
X_rotor_0 = np.array([0.1, 0, 0, 0, 0.1, 0, -1, 0, 0, 0, 0.3, 0])
|
|
427
|
+
omega_rotor_0 = np.array([0, 0, 0])
|
|
428
|
+
quad_rotor_state_PD_dynamic[a_y] = [X_rotor_0]
|
|
429
|
+
quad_rotor_omega_D_dynamic[a_y] = [omega_rotor_0]
|
|
430
|
+
quad_rotor_state_PD_dynamic_low[a_y] = [X_rotor_0]
|
|
431
|
+
tilda_state = np.array([0, 0, 0, 0, 0, 0, 0, sum_theta_init, 0])
|
|
432
|
+
tilda_state_dynamic[a_y] = [tilda_state]
|
|
433
|
+
|
|
434
|
+
X_rotor_0_PD = X_rotor_0
|
|
435
|
+
Q1 = np.array([[1000, 0, 0], [0, 10, 0], [0, 0, 10]])
|
|
436
|
+
Q2 = np.array([[10, 0, 0], [0, 1000, 0], [0, 0, 10]])
|
|
437
|
+
Q3 = np.array([[10, 0, 0], [0, 10, 0], [0, 0, 1000]])
|
|
438
|
+
Plast = [Q1, Q2, Q3]
|
|
439
|
+
v_d_s_0_2 = 0.0
|
|
440
|
+
h_d_s_0_2 = 0.0
|
|
441
|
+
sum_theta = sum_theta_init
|
|
442
|
+
sum_theta_2 = 0.0
|
|
443
|
+
|
|
444
|
+
for _ in range(n_steps):
|
|
445
|
+
p_r2, q_r2, r_r2, t_r2, tilda_state_l2 = get_higher_level_control2(X_rotor_0_PD, T_start, a_y)
|
|
446
|
+
tilda_state = [tilda_state_l2]
|
|
447
|
+
tilda_state_dynamic[a_y] = np.append(tilda_state_dynamic[a_y], tilda_state, axis=0)
|
|
448
|
+
T_end = T_start + deltaTstate
|
|
449
|
+
data_points = 100
|
|
450
|
+
t = np.linspace(T_start, T_end, data_points)
|
|
451
|
+
Plast = get_P_quad_given_angular_rates([X_rotor_0_PD[1], X_rotor_0_PD[3], X_rotor_0_PD[5]], Plast)
|
|
452
|
+
quad_rotor_state_PD = odeint(
|
|
453
|
+
quad_rotor_state_diff_eqn_for_given_pqrT,
|
|
454
|
+
X_rotor_0_PD,
|
|
455
|
+
t,
|
|
456
|
+
args=(p_r2, q_r2, r_r2, t_r2, Plast),
|
|
457
|
+
)
|
|
458
|
+
omega_rotor_i = np.array([p_r2, q_r2, r_r2])
|
|
459
|
+
X_rotor_0_PD = quad_rotor_state_PD[-1]
|
|
460
|
+
T_start = T_end
|
|
461
|
+
quad_rotor_state_PD_dynamic[a_y] = np.append(
|
|
462
|
+
quad_rotor_state_PD_dynamic[a_y], quad_rotor_state_PD, axis=0
|
|
463
|
+
)
|
|
464
|
+
quad_rotor_omega_D_dynamic[a_y] = np.append(
|
|
465
|
+
quad_rotor_omega_D_dynamic[a_y], [omega_rotor_i], axis=0
|
|
466
|
+
)
|
|
467
|
+
quad_rotor_state_PD_dynamic_low[a_y] = np.append(
|
|
468
|
+
quad_rotor_state_PD_dynamic_low[a_y], [X_rotor_0_PD], axis=0
|
|
469
|
+
)
|
|
470
|
+
if a_y == a_ys[0]:
|
|
471
|
+
tTotal = np.append(tTotal, t)
|
|
472
|
+
tTotal_low = np.append(tTotal_low, T_end)
|
|
473
|
+
|
|
474
|
+
return (
|
|
475
|
+
quad_rotor_state_PD_dynamic,
|
|
476
|
+
quad_rotor_omega_D_dynamic,
|
|
477
|
+
quad_rotor_state_PD_dynamic_low,
|
|
478
|
+
tilda_state_dynamic,
|
|
479
|
+
np.asarray(tTotal),
|
|
480
|
+
np.asarray(tTotal_low),
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
# Plotting (guarded; only runs when explicitly enabled)
|
|
485
|
+
|
|
486
|
+
angles = {"phi": [0, 0], "theta": [2, 1], "psi": [4, 2]}
|
|
487
|
+
positions = {"x": [8, 7], "z": [6, 5], "y": [10, 3]}
|
|
488
|
+
velocities = {"y_dot": [11, 4], "z_dot": [7, 6], "x_dot": 9}
|
|
444
489
|
plot_vars = {**angles, **positions, **velocities}
|
|
445
490
|
|
|
446
491
|
|
|
447
|
-
def
|
|
448
|
-
|
|
492
|
+
def plot_results(
|
|
493
|
+
quad_rotor_state_PD_dynamic,
|
|
494
|
+
quad_rotor_omega_D_dynamic,
|
|
495
|
+
quad_rotor_state_PD_dynamic_low,
|
|
496
|
+
tilda_state_dynamic,
|
|
497
|
+
tTotal,
|
|
498
|
+
tTotal_low,
|
|
499
|
+
a_ys,
|
|
500
|
+
):
|
|
501
|
+
"""Produce the full set of diagnostic figures (not shown under Agg)."""
|
|
502
|
+
|
|
503
|
+
import matplotlib.pyplot as plt
|
|
504
|
+
from mpl_toolkits.axes_grid1.inset_locator import mark_inset, zoomed_inset_axes
|
|
505
|
+
|
|
506
|
+
def plot_var(var):
|
|
507
|
+
var_indices = plot_vars[var]
|
|
508
|
+
for a_y in quad_rotor_state_PD_dynamic_low.keys():
|
|
509
|
+
plt.figure(dpi=130)
|
|
510
|
+
plt.title(r"$a _y = \ $" + str(a_y) + r"$ \ [m]$", fontsize=16)
|
|
511
|
+
plt.plot(
|
|
512
|
+
tTotal_low[1:],
|
|
513
|
+
quad_rotor_state_PD_dynamic_low[a_y][1:, var_indices[0] if var != "x_dot" else var_indices],
|
|
514
|
+
)
|
|
515
|
+
if var in positions.keys():
|
|
516
|
+
plt.plot(tTotal_low[1:], tilda_state_dynamic[a_y][1:, var_indices[1]])
|
|
517
|
+
else:
|
|
518
|
+
plt.plot(tTotal_low[1:], -tilda_state_dynamic[a_y][1:, var_indices[1]])
|
|
519
|
+
if var in positions.keys():
|
|
520
|
+
plt.legend([r"$" + var + r" \\ [m]$", r"$\tilde{" + var + r"} \\ [m]$"])
|
|
521
|
+
else:
|
|
522
|
+
if "dot" in var:
|
|
523
|
+
plt.legend(
|
|
524
|
+
[
|
|
525
|
+
r"$\dot{" + var[0] + r"} \ \left[ \frac{m}{sec} \right]$",
|
|
526
|
+
r"$\tilde{\dot{" + var[0] + r"}} \ \left[ \frac{m}{sec} \right]$",
|
|
527
|
+
]
|
|
528
|
+
)
|
|
529
|
+
else:
|
|
530
|
+
plt.legend([r"$\\" + var + r" \ [rad]$", r"$\tilde{\\" + var + r"} \ [rad]$"])
|
|
531
|
+
plt.xlabel(r"$t \ [sec]$", fontsize=16)
|
|
532
|
+
plt.grid()
|
|
533
|
+
|
|
534
|
+
for curr_a_y, var in quad_rotor_state_PD_dynamic_low.items():
|
|
535
|
+
plt.figure(dpi=300)
|
|
536
|
+
plt.plot(tTotal_low[1:], var[1:, 9])
|
|
537
|
+
plt.plot(tTotal_low[1:], abs(tilda_state_dynamic[curr_a_y][1:, 3] / curr_a_y))
|
|
538
|
+
plt.plot(tTotal_low[1:], 0.5 * np.ones(len(tTotal_low[1:])))
|
|
539
|
+
plt.grid()
|
|
540
|
+
plt.xlabel(r"$t \ [sec]$", fontsize=12)
|
|
541
|
+
plt.legend(["Forward Velocity", "Wall Distance Measure"])
|
|
542
|
+
|
|
543
|
+
for var in ["x", "y", "y_dot", "z", "z_dot", "phi", "theta", "psi"]:
|
|
544
|
+
plot_var(var)
|
|
545
|
+
|
|
546
|
+
time_ratio1 = len(tTotal_low) / max(tTotal_low)
|
|
547
|
+
time_ratio2 = len(tTotal) / max(tTotal)
|
|
548
|
+
|
|
549
|
+
for a_y in quad_rotor_omega_D_dynamic.keys():
|
|
550
|
+
fig, ax = plt.subplots(1, dpi=150)
|
|
551
|
+
t01 = int(time_ratio1 * 0.8)
|
|
552
|
+
t1 = int(time_ratio1 * 1.1)
|
|
553
|
+
t02 = int(time_ratio2 * 0.8)
|
|
554
|
+
t2 = int(time_ratio2 * 1)
|
|
555
|
+
x1 = tTotal_low[t01:t1]
|
|
556
|
+
y1 = quad_rotor_omega_D_dynamic[a_y][t01:t1, 1]
|
|
557
|
+
x2 = tTotal[t02:t2]
|
|
558
|
+
y2 = quad_rotor_state_PD_dynamic[a_y][t02:t2, 3]
|
|
559
|
+
ax.step(x1, y1, linewidth=1)
|
|
560
|
+
ax.plot(x2, y2, linewidth=1)
|
|
561
|
+
plt.xlabel(r"$t \ [sec]$", fontsize=14)
|
|
562
|
+
plt.grid()
|
|
563
|
+
axins = zoomed_inset_axes(ax, 3.5, borderpad=3)
|
|
564
|
+
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5")
|
|
565
|
+
axins.set_xlim([0.898, 0.905])
|
|
566
|
+
axins.set_ylim([0.193, 0.205])
|
|
567
|
+
axins.step(x1, y1, linewidth=1)
|
|
568
|
+
axins.plot(x2, y2, linewidth=1)
|
|
569
|
+
plt.grid()
|
|
570
|
+
fig.legend(
|
|
571
|
+
[
|
|
572
|
+
r"$\dot{\theta}_d \ \left[ \frac{rad}{sec} \right]$",
|
|
573
|
+
r"$\dot{\theta} \ \left[ \frac{rad}{sec} \right]$",
|
|
574
|
+
],
|
|
575
|
+
bbox_to_anchor=(0.28, 0.35) if a_y != 0.6 else (0.3, 0.35),
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
for sol in quad_rotor_state_PD_dynamic_low.values():
|
|
579
|
+
plt.figure(dpi=300)
|
|
580
|
+
plt.plot(tTotal_low[0:], sol[0:, 0:6])
|
|
581
|
+
plt.xlabel(r"$t \ [sec]$", fontsize=14)
|
|
582
|
+
plt.legend(
|
|
583
|
+
[
|
|
584
|
+
r"$\phi[rad]$",
|
|
585
|
+
r"$\dot{\phi}\left[ \frac{rad}{sec} \right]$",
|
|
586
|
+
r"$\theta[rad]$",
|
|
587
|
+
r"$\dot{\theta}\left[ \frac{rad}{sec} \right]$",
|
|
588
|
+
r"$\psi[rad]$",
|
|
589
|
+
r"$\dot{\psi}\left[ \frac{rad}{sec} \right]$",
|
|
590
|
+
],
|
|
591
|
+
ncol=2,
|
|
592
|
+
loc="upper right",
|
|
593
|
+
)
|
|
594
|
+
plt.grid()
|
|
449
595
|
|
|
450
|
-
for
|
|
451
|
-
plt.figure(dpi=
|
|
596
|
+
for sol in quad_rotor_state_PD_dynamic_low.values():
|
|
597
|
+
plt.figure(dpi=300)
|
|
598
|
+
plt.plot(tTotal_low[0:], sol[0:, 6:8], tTotal_low[0:], sol[0:, 10:12])
|
|
599
|
+
plt.xlabel(r"$t \ [sec]$", fontsize=14)
|
|
600
|
+
plt.legend(
|
|
601
|
+
[
|
|
602
|
+
r"$z[m]$",
|
|
603
|
+
r"$\dot{z}\left[ \frac{m}{sec} \right]$",
|
|
604
|
+
r"$y[m]$",
|
|
605
|
+
r"$\dot{y}\left[ \frac{m}{sec} \right]$",
|
|
606
|
+
]
|
|
607
|
+
)
|
|
608
|
+
plt.grid()
|
|
452
609
|
|
|
453
|
-
plt.title('$a _y = \ $' + str(a_y) + '$ \ [m]$', fontsize=16)
|
|
454
610
|
|
|
455
|
-
|
|
456
|
-
|
|
611
|
+
def main(*, n_steps: int = 8, plot: bool = True) -> None:
|
|
612
|
+
"""Run a short hierarchical quadrotor simulation and report the result."""
|
|
457
613
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
614
|
+
a_ys = (0.55,)
|
|
615
|
+
(state_pd, omega_d, state_pd_low, tilda_state_dynamic, tTotal, tTotal_low) = run_simulation(
|
|
616
|
+
a_ys=a_ys, n_steps=n_steps
|
|
617
|
+
)
|
|
462
618
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if 'dot' in var:
|
|
467
|
-
plt.legend(['$\\dot{' + var[0] + '} \\ \\left[ \\frac{m}{sec} \\right]$',
|
|
468
|
-
'$\\tilde{\\dot{' + var[0] + '}} \\ \\left[ \\frac{m}{sec} \\right]$'])
|
|
469
|
-
else:
|
|
470
|
-
plt.legend(['$\\' + var + ' \\ [rad]$', '$\\tilde{\\' + var + '} \\ [rad]$'])
|
|
619
|
+
if plot:
|
|
620
|
+
plot_results(state_pd, omega_d, state_pd_low, tilda_state_dynamic, tTotal, tTotal_low, a_ys)
|
|
621
|
+
show()
|
|
471
622
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
for var in ['x', 'y', 'y_dot', 'z', 'z_dot', 'phi', 'theta', 'psi']:
|
|
490
|
-
plot_var(var)
|
|
491
|
-
|
|
492
|
-
time_ratio1 = len(tTotal_low) / max(tTotal_low)
|
|
493
|
-
time_ratio2 = len(tTotal) / max(tTotal)
|
|
494
|
-
|
|
495
|
-
for a_y in quad_rotor_omega_D_dynamic.keys():
|
|
496
|
-
fig, ax = plt.subplots(1, dpi=150)
|
|
497
|
-
|
|
498
|
-
t01 = int(time_ratio1 * 0.8)
|
|
499
|
-
t1 = int(time_ratio1 * 1.1)
|
|
500
|
-
t02 = int(time_ratio2 * 0.8)
|
|
501
|
-
t2 = int(time_ratio2 * 1)
|
|
502
|
-
|
|
503
|
-
x1 = tTotal_low[t01:t1]
|
|
504
|
-
y1 = quad_rotor_omega_D_dynamic[a_y][t01:t1, 1]
|
|
505
|
-
x2 = tTotal[t02:t2]
|
|
506
|
-
y2 = quad_rotor_state_PD_dynamic[a_y][t02:t2, 3]
|
|
507
|
-
|
|
508
|
-
ax.step(x1, y1, linewidth=1)
|
|
509
|
-
ax.plot(x2, y2, linewidth=1)
|
|
510
|
-
plt.xlabel('$t \ [sec]$', fontsize=14)
|
|
511
|
-
plt.grid()
|
|
512
|
-
|
|
513
|
-
axins = zoomed_inset_axes(ax, 3.5, borderpad=3)
|
|
514
|
-
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5")
|
|
515
|
-
|
|
516
|
-
xlim = [0.898, 0.905]
|
|
517
|
-
ylim = [0.193, 0.205]
|
|
518
|
-
|
|
519
|
-
axins.set_xlim(xlim)
|
|
520
|
-
axins.set_ylim(ylim)
|
|
521
|
-
axins.step(x1, y1, linewidth=1)
|
|
522
|
-
axins.plot(x2, y2, linewidth=1)
|
|
523
|
-
plt.grid()
|
|
524
|
-
|
|
525
|
-
fig.legend(['$\\dot{\\theta}_d \\ \\left[ \\frac{rad}{sec} \\right]$',
|
|
526
|
-
'$\\dot{\\theta} \\ \\left[ \\frac{rad}{sec} \\right]$'],
|
|
527
|
-
bbox_to_anchor=(0.28, 0.35) if a_y != 0.6 else (0.3, 0.35))
|
|
528
|
-
|
|
529
|
-
plt.show()
|
|
530
|
-
|
|
531
|
-
for sol in quad_rotor_state_PD_dynamic_low.values():
|
|
532
|
-
plt.figure(dpi=300)
|
|
533
|
-
plt.plot(tTotal_low[0:], sol[0:, 0:6])
|
|
534
|
-
plt.xlabel('$t \ [sec]$', fontsize=14)
|
|
535
|
-
plt.legend(['$\\phi[rad]$', '$\\dot{\phi}\\left[ \\frac{rad}{sec} \\right]$', '$\\theta[rad]$',
|
|
536
|
-
'$\\dot{\\theta}\\left[ \\frac{rad}{sec} \\right]$', '$\\psi[rad]$',
|
|
537
|
-
'$\\dot{\psi}\\left[ \\frac{rad}{sec} \\right]$'], ncol=2, loc='upper right')
|
|
538
|
-
plt.grid()
|
|
539
|
-
plt.show()
|
|
540
|
-
|
|
541
|
-
for sol in quad_rotor_state_PD_dynamic_low.values():
|
|
542
|
-
plt.figure(dpi=300)
|
|
543
|
-
plt.plot(tTotal_low[0:], sol[0:, 6:8], tTotal_low[0:], sol[0:, 10:12])
|
|
544
|
-
plt.xlabel('$t \ [sec]$', fontsize=14)
|
|
545
|
-
plt.legend(
|
|
546
|
-
['$z[m]$', '$\\dot{z}\\left[ \\frac{m}{sec} \\right]$', '$y[m]$', '$\\dot{y}\\left[ \\frac{m}{sec} \\right]$'])
|
|
547
|
-
plt.grid()
|
|
548
|
-
plt.show()
|
|
623
|
+
final_state = state_pd_low[a_ys[0]][-1]
|
|
624
|
+
print("Quadrotor hierarchical control simulation complete.")
|
|
625
|
+
print(f" wall distances a_y = {list(a_ys)}")
|
|
626
|
+
print(f" high/low-level game steps = {n_steps}")
|
|
627
|
+
print(f" simulated time = {float(tTotal_low[-1]):.2f} s")
|
|
628
|
+
print(
|
|
629
|
+
f" final position (x, y, z) = ({final_state[8]:.3f}, {final_state[10]:.3f}, {final_state[6]:.3f}) m"
|
|
630
|
+
)
|
|
631
|
+
print(
|
|
632
|
+
f" final angles (phi, theta, psi) = "
|
|
633
|
+
f"({final_state[0]:.3f}, {final_state[2]:.3f}, {final_state[4]:.3f}) rad"
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
if __name__ == "__main__":
|
|
638
|
+
main()
|