PyDiffGame 0.1.0__py3-none-any.whl → 0.1.2__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.
Files changed (36) hide show
  1. PyDiffGame/ContinuousPyDiffGame.py +2 -2
  2. PyDiffGame/DiscretePyDiffGame.py +8 -9
  3. PyDiffGame/PyDiffGame.py +19 -12
  4. PyDiffGame/PyDiffGameLQRComparison.py +9 -10
  5. PyDiffGame/examples/InvertedPendulumComparison.py +257 -0
  6. PyDiffGame/examples/MassesWithSpringsComparison.py +218 -0
  7. PyDiffGame/examples/PVTOL.py +222 -0
  8. PyDiffGame/examples/PVTOLComparison.py +111 -0
  9. PyDiffGame/examples/QuadRotorControl.py +548 -0
  10. PyDiffGame/examples/figures/2/2-players_large_1.png +0 -0
  11. PyDiffGame/examples/figures/2/2-players_large_2.png +0 -0
  12. PyDiffGame/examples/figures/2/LQR_large_1.png +0 -0
  13. PyDiffGame/examples/figures/2/LQR_large_2.png +0 -0
  14. PyDiffGame/examples/figures/2/two_masses_tikz.png +0 -0
  15. PyDiffGame/examples/figures/4/4-players_large_1.png +0 -0
  16. PyDiffGame/examples/figures/4/4-players_large_2.png +0 -0
  17. PyDiffGame/examples/figures/4/LQR_large_1.png +0 -0
  18. PyDiffGame/examples/figures/4/LQR_large_2.png +0 -0
  19. PyDiffGame/examples/figures/8/8-players_large_1.png +0 -0
  20. PyDiffGame/examples/figures/8/8-players_large_2.png +0 -0
  21. PyDiffGame/examples/figures/8/LQR_large_1.png +0 -0
  22. PyDiffGame/examples/figures/8/LQR_large_2.png +0 -0
  23. PyDiffGame/examples/figures/PVTOL/PVTOL1.png +0 -0
  24. PyDiffGame/examples/figures/PVTOL/PVTOL10.png +0 -0
  25. PyDiffGame/examples/figures/PVTOL/PVTOL100.png +0 -0
  26. PyDiffGame/examples/figures/PVTOL/PVTOL1000.png +0 -0
  27. PyDiffGame/examples/figures/PVTOL0001.png +0 -0
  28. PyDiffGame/examples/figures/PVTOL001.png +0 -0
  29. PyDiffGame/examples/figures/PVTOL01.png +0 -0
  30. PyDiffGame/examples/figures/PVTOL1.png +0 -0
  31. {PyDiffGame-0.1.0.dist-info → pydiffgame-0.1.2.dist-info}/METADATA +65 -54
  32. pydiffgame-0.1.2.dist-info/RECORD +37 -0
  33. {PyDiffGame-0.1.0.dist-info → pydiffgame-0.1.2.dist-info}/WHEEL +1 -2
  34. {PyDiffGame-0.1.0.dist-info → pydiffgame-0.1.2.dist-info/licenses}/LICENSE +1 -1
  35. PyDiffGame-0.1.0.dist-info/RECORD +0 -12
  36. PyDiffGame-0.1.0.dist-info/top_level.txt +0 -1
@@ -0,0 +1,218 @@
1
+ import numpy as np
2
+ from typing import Sequence, Optional, Union
3
+
4
+ from PyDiffGame.PyDiffGame import PyDiffGame
5
+ from PyDiffGame.PyDiffGameLQRComparison import PyDiffGameLQRComparison
6
+ from PyDiffGame.Objective import GameObjective, LQRObjective
7
+
8
+
9
+ class MassesWithSpringsComparison(PyDiffGameLQRComparison):
10
+ def __init__(self,
11
+ N: int,
12
+ m: float,
13
+ k: float,
14
+ q: Union[float, Sequence[float], Sequence[Sequence[float]]],
15
+ r: float,
16
+ Ms: Optional[Sequence[np.array]] = None,
17
+ x_0: Optional[np.array] = None,
18
+ x_T: Optional[np.array] = None,
19
+ T_f: Optional[float] = None,
20
+ epsilon_x: Optional[float] = PyDiffGame.epsilon_x_default,
21
+ epsilon_P: Optional[float] = PyDiffGame.epsilon_P_default,
22
+ L: Optional[int] = PyDiffGame.L_default,
23
+ eta: Optional[int] = PyDiffGame.eta_default):
24
+ I_N = np.eye(N)
25
+ Z_N = np.zeros((N, N))
26
+
27
+ M_masses = m * I_N
28
+ K = k * (2 * I_N - np.array([[int(abs(i - j) == 1) for j in range(N)] for i in range(N)]))
29
+ M_masses_inv = np.linalg.inv(M_masses)
30
+
31
+ M_inv_K = M_masses_inv @ K
32
+
33
+ if Ms is None:
34
+ eigenvectors = np.linalg.eig(M_inv_K)[1]
35
+ Ms = [eigenvector.reshape(1, N) for eigenvector in eigenvectors]
36
+
37
+ A = np.block([[Z_N, I_N],
38
+ [-M_inv_K, Z_N]])
39
+ B = np.block([[Z_N],
40
+ [M_masses_inv]])
41
+
42
+ Qs = [np.diag([0.0] * i + [q] + [0.0] * (N - 1) + [q] + [0.0] * (N - i - 1))
43
+ if isinstance(q, (int, float)) else
44
+ np.diag([0.0] * i + [q[i]] + [0.0] * (N - 1) + [q[i]] + [0.0] * (N - i - 1)) for i in range(N)]
45
+
46
+ M = np.concatenate(Ms,
47
+ axis=0)
48
+
49
+ assert np.all(np.abs(np.linalg.inv(M) - M.T) < 10e-12)
50
+
51
+ Q_mat = np.kron(a=np.eye(2),
52
+ b=M)
53
+
54
+ Qs = [Q_mat.T @ Q @ Q_mat for Q in Qs]
55
+
56
+ Rs = [np.array([r])] * N
57
+ R_lqr = 1 / 4 * r * I_N
58
+ Q_lqr = q * np.eye(2 * N) if isinstance(q, (int, float)) else np.diag(2 * q)
59
+
60
+ state_variables_names = ['x_{' + str(i) + '}' for i in range(1, N + 1)] + \
61
+ ['\\dot{x}_{' + str(i) + '}' for i in range(1, N + 1)]
62
+ args = {'A': A,
63
+ 'B': B,
64
+ 'x_0': x_0,
65
+ 'x_T': x_T,
66
+ 'T_f': T_f,
67
+ 'state_variables_names': state_variables_names,
68
+ 'epsilon_x': epsilon_x,
69
+ 'epsilon_P': epsilon_P,
70
+ 'L': L,
71
+ 'eta': eta}
72
+
73
+ lqr_objective = [LQRObjective(Q=Q_lqr,
74
+ R_ii=R_lqr)]
75
+ game_objectives = [GameObjective(Q=Q,
76
+ R_ii=R,
77
+ M_i=M_i) for Q, R, M_i in zip(Qs, Rs, Ms)]
78
+ games_objectives = [lqr_objective,
79
+ game_objectives]
80
+
81
+ self.figure_filename_generator = lambda g: ('LQR' if g.is_LQR() else f'{N}-players') + \
82
+ f'_large_{1 if q[0] > q[1] else 2}'
83
+
84
+ super().__init__(args=args,
85
+ M=M,
86
+ games_objectives=games_objectives,
87
+ continuous=True)
88
+
89
+ @staticmethod
90
+ def are_all_orthogonal(vectors: Sequence[np.array]) -> bool:
91
+ """
92
+ Check if a set of vectors are all orthogonal to each other.
93
+
94
+ Parameters
95
+ ----------
96
+
97
+ vectors: sequence of vectors of len(n)
98
+ A sequence of numpy arrays representing the vectors
99
+
100
+ Returns
101
+ ----------
102
+
103
+ all_orthogonal: bool
104
+ True if all the vectors are orthogonal to each other, False otherwise
105
+ """
106
+
107
+ all_orthogonal = not any([any([abs(vectors[i] @ vectors[j].T) > 1e-10 for j in range(i + 1, len(vectors))])
108
+ for i in range(len(vectors))])
109
+
110
+ return all_orthogonal
111
+
112
+ @staticmethod
113
+ def are_all_unit_vectors(vectors: Sequence[np.array]) -> bool:
114
+ """
115
+ Check if a set of vectors are all of length 1.
116
+
117
+ Parameters
118
+ ----------
119
+
120
+ vectors: sequence of vectors of len(n)
121
+ A sequence of numpy arrays representing the vectors
122
+
123
+ Returns
124
+ ----------
125
+
126
+ all_orthogonal: bool
127
+ True if all the vectors are orthogonal to each other, False otherwise
128
+ """
129
+
130
+ all_unit_vectors = all([abs(np.linalg.norm(v) - 1) < 1e-10 for v in vectors])
131
+
132
+ return all_unit_vectors
133
+
134
+
135
+ N2_output_variables_names = ['$\\frac{x_1 + x_2}{\\sqrt{2}}$',
136
+ '$\\frac{x_2 - x_1}{\\sqrt{2}}$',
137
+ '$\\frac{\\dot{x}_1 + \\dot{x}_2}{\\sqrt{2}}$',
138
+ '$\\frac{\\dot{x}_2 - \\dot{x}_1}{\\sqrt{2}}$']
139
+
140
+
141
+ def multiprocess_worker_function(N: int,
142
+ k: float,
143
+ m: float,
144
+ q: float,
145
+ r: float,
146
+ epsilon_x: float,
147
+ epsilon_P: float):
148
+ x_0 = np.array([10 * i for i in range(1, N + 1)] + [0] * N)
149
+ x_T = x_0 * 10 if N == 2 else np.array([(10 * i) ** 3 for i in range(1, N + 1)] + [0] * N)
150
+
151
+ output_variables_names = N2_output_variables_names \
152
+ if N == 2 else [f'$q_{i}$' for i in range(1, N + 1)] + ['$\\dot{q}_{' + str(i) + '}$' for i in range(1, N + 1)]
153
+
154
+ T_f = 25
155
+ masses_with_springs = MassesWithSpringsComparison(N=N,
156
+ m=m,
157
+ k=k,
158
+ q=q,
159
+ r=r,
160
+ x_0=x_0,
161
+ x_T=x_T,
162
+ T_f=T_f,
163
+ epsilon_x=epsilon_x,
164
+ epsilon_P=epsilon_P)
165
+
166
+ masses_with_springs(plot_state_spaces=True,
167
+ plot_Mx=True,
168
+ output_variables_names=output_variables_names,
169
+ save_figure=True,
170
+ figure_filename=masses_with_springs.figure_filename_generator
171
+ )
172
+
173
+
174
+ if __name__ == '__main__':
175
+ d = 0.2
176
+ N = 8
177
+
178
+ Ns = [N]
179
+ ks = [10]
180
+ ms = [50]
181
+ rs = [1]
182
+ epsilon_xs = [10e-8]
183
+ epsilon_Ps = [10e-8]
184
+ qs = [[500, 2000], [500, 250]] if N == 2 else \
185
+ [[500 * (1 + d) ** i for i in range(N)], [500 * (1 - d) ** i for i in range(N)]]
186
+
187
+ # N, k, m, r, epsilon_x, epsilon_P = Ns[0], ks[0], ms[0], rs[0], epsilon_xs[0], epsilon_Ps[0]
188
+ # q = [500, 2000] if N == 2 else [500 * (1 + d) ** i for i in range(N)]
189
+ # x_0 = np.array([10 * i for i in range(1, N + 1)] + [0] * N)
190
+ # x_T = x_0 * 10 if N == 2 else np.array([(10 * i) ** 3 for i in range(1, N + 1)] + [0] * N)
191
+ #
192
+ # masses_with_springs = MassesWithSpringsComparison(N=N,
193
+ # m=m,
194
+ # k=k,
195
+ # q=q,
196
+ # r=r,
197
+ # x_0=x_0,
198
+ # x_T=x_T,
199
+ # T_f=25,
200
+ # epsilon_x=epsilon_x,
201
+ # epsilon_P=epsilon_P)
202
+ #
203
+ # output_variables_names = N2_output_variables_names \
204
+ # if N == 2 else [f'$q_{i}$' for i in range(1, N + 1)] + ['$\\dot{q}_{' + str(i) + '}$' for i in range(1, N + 1)]
205
+ #
206
+ # masses_with_springs(plot_state_spaces=True,
207
+ # plot_Mx=True,
208
+ # output_variables_names=output_variables_names,
209
+ # save_figure=True,
210
+ # figure_filename=masses_with_springs.figure_filename_generator
211
+ # )
212
+
213
+ #
214
+
215
+ params = [Ns, ks, ms, qs, rs, epsilon_xs, epsilon_Ps]
216
+ PyDiffGameLQRComparison.run_multiprocess(multiprocess_worker_function=multiprocess_worker_function,
217
+ values=params)
218
+
@@ -0,0 +1,222 @@
1
+ # pvtol_lqr.m - LQR design for vectored thrust aircraft
2
+ # RMM, 14 Jan 03
3
+ #
4
+ # This file works through an LQR based design problem, using the
5
+ # planar vertical takeoff and landing (PVTOLComparison.py) aircraft example from
6
+ # Astrom and Murray, Chapter 5. It is intended to demonstrate the
7
+ # basic functionality of the python-control package.
8
+ #
9
+
10
+ import os
11
+ import numpy as np
12
+ import matplotlib.pyplot as plt # MATLAB-like plotting functions
13
+ import control as ct
14
+
15
+ #
16
+ # System dynamics
17
+ #
18
+ # These are the dynamics for the PVTOLComparison.py system, written in state space
19
+ # form.
20
+ #
21
+
22
+ # System parameters
23
+ m = 4 # mass of aircraft
24
+ J = 0.0475 # inertia around pitch axis
25
+ r = 0.25 # distance to center of force
26
+ g = 9.8 # gravitational constant
27
+ c = 0.05 # damping factor (estimated)
28
+
29
+ # State space dynamics
30
+ xe = [0, 0, 0, 0, 0, 0] # equilibrium point of interest
31
+ ue = [0, m * g] # (note these are lists, not matrices)
32
+
33
+ # This will involve re-working the subsequent equations as the shapes
34
+ # See below.
35
+
36
+ A = np.array(
37
+ [[0, 0, 0, 1, 0, 0],
38
+ [0, 0, 0, 0, 1, 0],
39
+ [0, 0, 0, 0, 0, 1],
40
+ [0, 0, (-ue[0]*np.sin(xe[2]) - ue[1]*np.cos(xe[2]))/m, -c/m, 0, 0],
41
+ [0, 0, (ue[0]*np.cos(xe[2]) - ue[1]*np.sin(xe[2]))/m, 0, -c/m, 0],
42
+ [0, 0, 0, 0, 0, 0]]
43
+ )
44
+
45
+ # Input matrix
46
+ B = np.array(
47
+ [[0, 0], [0, 0], [0, 0],
48
+ [np.cos(xe[2])/m, -np.sin(xe[2])/m],
49
+ [np.sin(xe[2])/m, np.cos(xe[2])/m],
50
+ [r/J, 0]]
51
+ )
52
+
53
+ # Output matrix
54
+ C = np.array([[1, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0]])
55
+ D = np.array([[0, 0], [0, 0]])
56
+
57
+ #
58
+ # Construct inputs and outputs corresponding to steps in xy position
59
+ #
60
+ # The vectors xd and yd correspond to the states that are the desired
61
+ # equilibrium states for the system. The matrices Cx and Cy are the
62
+ # corresponding outputs.
63
+ #
64
+ # The way these vectors are used is to compute the closed loop system
65
+ # dynamics as
66
+ #
67
+ # xdot = Ax + B u => xdot = (A-BK)x + K xd
68
+ # u = -K(x - xd) y = Cx
69
+ #
70
+ # The closed loop dynamics can be simulated using the "step" command,
71
+ # with K*xd as the input vector (assumes that the "input" is unit size,
72
+ # so that xd corresponds to the desired steady state.
73
+ #
74
+
75
+ xd = np.array([[1], [0], [0], [0], [0], [0]])
76
+ yd = np.array([[0], [1], [0], [0], [0], [0]])
77
+
78
+ #
79
+ # Extract the relevant dynamics for use with SISO library
80
+ #
81
+ # The current python-control library only supports SISO transfer
82
+ # functions, so we have to modify some parts of the original MATLAB
83
+ # code to extract out SISO systems. To do this, we define the 'lat' and
84
+ # 'alt' index vectors to consist of the states that are are relevant
85
+ # to the lateral (x) and vertical (y) dynamics.
86
+ #
87
+
88
+ # Indices for the parts of the state that we want
89
+ lat = (0, 2, 3, 5)
90
+ alt = (1, 4)
91
+
92
+ # Decoupled dynamics
93
+ Ax = A[np.ix_(lat, lat)]
94
+ Bx = B[np.ix_(lat, [0])]
95
+ Cx = C[np.ix_([0], lat)]
96
+ Dx = D[np.ix_([0], [0])]
97
+
98
+ Ay = A[np.ix_(alt, alt)]
99
+ By = B[np.ix_(alt, [1])]
100
+ Cy = C[np.ix_([1], alt)]
101
+ Dy = D[np.ix_([1], [1])]
102
+
103
+ # Label the plot
104
+ plt.clf()
105
+ plt.suptitle("LQR controllers for vectored thrust aircraft (pvtol-lqr)")
106
+
107
+ #
108
+ # LQR design
109
+ #
110
+
111
+ # Start with a diagonal weighting
112
+ Qx1 = np.diag([1, 1, 1, 1, 1, 1])
113
+ Qu1a = np.diag([1, 1])
114
+ K1a, X, E = ct.lqr(A, B, Qx1, Qu1a)
115
+
116
+ # Close the loop: xdot = Ax - B K (x-xd)
117
+ #
118
+ # Note: python-control requires we do this 1 input at a time
119
+ # H1a = ss(A-B*K1a, B*K1a*concatenate((xd, yd), axis=1), C, D);
120
+ # (T, Y) = step_response(H1a, T=np.linspace(0,10,100));
121
+ #
122
+
123
+ # Step response for the first input
124
+ H1ax = ct.ss(Ax - Bx @ K1a[np.ix_([0], lat)],
125
+ Bx @ K1a[np.ix_([0], lat)] @ xd[lat, :], Cx, Dx)
126
+ Tx, Yx = ct.step_response(H1ax, T=np.linspace(0, 10, 100))
127
+
128
+ # Step response for the second input
129
+ H1ay = ct.ss(Ay - By @ K1a[np.ix_([1], alt)],
130
+ By @ K1a[np.ix_([1], alt)] @ yd[alt, :], Cy, Dy)
131
+ Ty, Yy = ct.step_response(H1ay, T=np.linspace(0, 10, 100))
132
+
133
+ plt.subplot(221)
134
+ plt.title("Identity weights")
135
+ # plt.plot(T, Y[:,1, 1], '-', T, Y[:,2, 2], '--')
136
+ plt.plot(Tx.T, Yx.T, '-', Ty.T, Yy.T, '--')
137
+ plt.plot([0, 10], [1, 1], 'k-')
138
+
139
+ plt.axis([0, 10, -0.1, 1.4])
140
+ plt.ylabel('position')
141
+ plt.legend(('x', 'y'), loc='lower right')
142
+
143
+ # Look at different input weightings
144
+ Qu1a = np.diag([1, 1])
145
+ K1a, X, E = ct.lqr(A, B, Qx1, Qu1a)
146
+ H1ax = ct.ss(Ax - Bx @ K1a[np.ix_([0], lat)],
147
+ Bx @ K1a[np.ix_([0], lat)] @ xd[lat, :], Cx, Dx)
148
+
149
+ Qu1b = (40 ** 2)*np.diag([1, 1])
150
+ K1b, X, E = ct.lqr(A, B, Qx1, Qu1b)
151
+ H1bx = ct.ss(Ax - Bx @ K1b[np.ix_([0], lat)],
152
+ Bx @ K1b[np.ix_([0], lat)] @ xd[lat, :], Cx, Dx)
153
+
154
+ Qu1c = (200 ** 2)*np.diag([1, 1])
155
+ K1c, X, E = ct.lqr(A, B, Qx1, Qu1c)
156
+ H1cx = ct.ss(Ax - Bx @ K1c[np.ix_([0], lat)],
157
+ Bx @ K1c[np.ix_([0], lat)] @ xd[lat, :], Cx, Dx)
158
+
159
+ T1, Y1 = ct.step_response(H1ax, T=np.linspace(0, 10, 100))
160
+ T2, Y2 = ct.step_response(H1bx, T=np.linspace(0, 10, 100))
161
+ T3, Y3 = ct.step_response(H1cx, T=np.linspace(0, 10, 100))
162
+
163
+ plt.subplot(222)
164
+ plt.title("Effect of input weights")
165
+ plt.plot(T1.T, Y1.T, 'b-')
166
+ plt.plot(T2.T, Y2.T, 'b-')
167
+ plt.plot(T3.T, Y3.T, 'b-')
168
+ plt.plot([0, 10], [1, 1], 'k-')
169
+
170
+ plt.axis([0, 10, -0.1, 1.4])
171
+
172
+ # arcarrow([1.3, 0.8], [5, 0.45], -6)
173
+ plt.text(5.3, 0.4, r'$\rho$')
174
+
175
+ # Output weighting - change Qx to use outputs
176
+ Qx2 = C.T @ C
177
+ Qu2 = 0.1 * np.diag([1, 1])
178
+ K2, X, E = ct.lqr(A, B, Qx2, Qu2)
179
+
180
+ H2x = ct.ss(Ax - Bx @ K2[np.ix_([0], lat)],
181
+ Bx @ K2[np.ix_([0], lat)] @ xd[lat, :], Cx, Dx)
182
+ H2y = ct.ss(Ay - By @ K2[np.ix_([1], alt)],
183
+ By @ K2[np.ix_([1], alt)] @ yd[alt, :], Cy, Dy)
184
+
185
+ plt.subplot(223)
186
+ plt.title("Output weighting")
187
+ T2x, Y2x = ct.step_response(H2x, T=np.linspace(0, 10, 100))
188
+ T2y, Y2y = ct.step_response(H2y, T=np.linspace(0, 10, 100))
189
+ plt.plot(T2x.T, Y2x.T, T2y.T, Y2y.T)
190
+ plt.ylabel('position')
191
+ plt.xlabel('time')
192
+ plt.ylabel('position')
193
+ plt.legend(('x', 'y'), loc='lower right')
194
+
195
+ #
196
+ # Physically motivated weighting
197
+ #
198
+ # Shoot for 1 cm error in x, 10 cm error in y. Try to keep the angle
199
+ # less than 5 degrees in making the adjustments. Penalize side forces
200
+ # due to loss in efficiency.
201
+ #
202
+
203
+ Qx3 = np.diag([100, 10, 2*np.pi/5, 0, 0, 0])
204
+ Qu3 = 0.1*np.diag([1, 10])
205
+ K3, X, E = ct.lqr(A, B, Qx3, Qu3)
206
+
207
+ H3x = ct.ss(Ax - Bx @ K3[np.ix_([0], lat)],
208
+ Bx @ K3[np.ix_([0], lat)] @ xd[lat, :], Cx, Dx)
209
+ H3y = ct.ss(Ay - By @ K3[np.ix_([1], alt)],
210
+ By @ K3[np.ix_([1], alt)] @ yd[alt, :], Cy, Dy)
211
+ plt.subplot(224)
212
+ # step_response(H3x, H3y, 10)
213
+ T3x, Y3x = ct.step_response(H3x, T=np.linspace(0, 10, 100))
214
+ T3y, Y3y = ct.step_response(H3y, T=np.linspace(0, 10, 100))
215
+ plt.plot(T3x.T, Y3x.T, T3y.T, Y3y.T)
216
+ plt.title("Physically motivated weights")
217
+ plt.xlabel('time')
218
+ plt.legend(('x', 'y'), loc='lower right')
219
+ plt.tight_layout()
220
+
221
+ if 'PYCONTROL_TEST_EXAMPLES' not in os.environ:
222
+ plt.show()
@@ -0,0 +1,111 @@
1
+ import numpy as np
2
+ from typing import Optional
3
+
4
+ from PyDiffGame.PyDiffGame import PyDiffGame
5
+ from PyDiffGame.PyDiffGameLQRComparison import PyDiffGameLQRComparison
6
+ from PyDiffGame.Objective import GameObjective, LQRObjective
7
+
8
+
9
+ class PVTOLComparison(PyDiffGameLQRComparison):
10
+ def __init__(self,
11
+ x_0: Optional[np.array] = None,
12
+ x_T: Optional[np.array] = None,
13
+ T_f: Optional[float] = None,
14
+ epsilon_x: Optional[float] = PyDiffGame.epsilon_x_default,
15
+ epsilon_P: Optional[float] = PyDiffGame.epsilon_P_default,
16
+ L: Optional[int] = PyDiffGame.L_default,
17
+ eta: Optional[int] = PyDiffGame.eta_default):
18
+ m = 4 # mass of aircraft
19
+ J = 0.0475 # inertia around pitch axis
20
+ r = 0.25 # distance to center of force
21
+ c = 0.05 # damping factor (estimated)
22
+
23
+ A_11 = np.zeros((3, 3))
24
+ A_12 = np.eye(3)
25
+ A_21 = np.array([[0, 0, -PyDiffGame.g],
26
+ [0, 0, 0],
27
+ [0, 0, 0]])
28
+ A_22 = np.diag([-c / m, -c / m, 0])
29
+
30
+ A = np.block([[A_11, A_12],
31
+ [A_21, A_22]])
32
+
33
+ # Input matrix
34
+ B = np.array(
35
+ [[0, 0],
36
+ [0, 0],
37
+ [0, 0],
38
+ [1 / m, 0],
39
+ [0, 1 / m],
40
+ [r / J, 0]]
41
+ )
42
+
43
+ psi = 1
44
+
45
+ M_x = [1, 0]
46
+ M_y = [0, 1]
47
+ M_theta = [1, 0]
48
+
49
+ Ms = [np.array(M_i).reshape(1, 2) for M_i in [M_x, M_y, M_theta]]
50
+ M = np.concatenate(Ms, axis=0)
51
+
52
+ self.figure_filename_generator = lambda g: ('LQR_' if g.is_LQR() else '') + f"PVTOL{str(psi).replace('.', '')}"
53
+
54
+ Q_x_diag = [psi, 0, 0]
55
+ Q_y_diag = [0, 1, 0]
56
+ Q_theta_diag = [0, 0, 1]
57
+
58
+ r_x = 1
59
+ r_y = 1
60
+ r_theta = 1
61
+
62
+ Qs = [np.diag(Q_i_diag * 2) for Q_i_diag in [Q_x_diag, Q_y_diag, Q_theta_diag]]
63
+ Rs = [np.array([r_i]) for r_i in [r_x, r_y, r_theta]]
64
+
65
+ variables = ['x', 'y', '\\theta']
66
+
67
+ self.state_variables_names = [f'{v}(t)' for v in variables] + ['\\dot{' + str(v) + '}(t)' for v in variables]
68
+
69
+ Q_lqr = sum(Qs)
70
+ R_lqr = np.diag([r_x + r_theta, r_y])
71
+
72
+ lqr_objective = [LQRObjective(Q=Q_lqr,
73
+ R_ii=R_lqr)]
74
+ game_objectives = [GameObjective(Q=Q_i,
75
+ R_ii=R_i,
76
+ M_i=M_i) for Q_i, R_i, M_i in zip(Qs, Rs, Ms)]
77
+ games_objectives = [lqr_objective,
78
+ game_objectives]
79
+
80
+ args = {'A': A,
81
+ 'B': B,
82
+ 'x_0': x_0,
83
+ 'x_T': x_T,
84
+ 'T_f': T_f,
85
+ 'state_variables_names': self.state_variables_names,
86
+ 'epsilon_x': epsilon_x,
87
+ 'epsilon_P': epsilon_P,
88
+ 'L': L,
89
+ 'eta': eta}
90
+
91
+ super().__init__(args=args,
92
+ M=M,
93
+ games_objectives=games_objectives)
94
+
95
+
96
+ if __name__ == '__main__':
97
+ epsilon_x = epsilon_P = 10 ** (-8)
98
+
99
+ x_0 = np.zeros((6,))
100
+ x_d = y_d = 50
101
+ x_T = np.array([x_d, y_d] + [0] * 4)
102
+ T_f = 25
103
+
104
+ pvtol = PVTOLComparison(x_0=x_0,
105
+ x_T=x_T,
106
+ T_f=T_f,
107
+ epsilon_x=epsilon_x,
108
+ epsilon_P=epsilon_P)
109
+ pvtol(plot_state_spaces=True,
110
+ save_figure=True,
111
+ figure_filename=pvtol.figure_filename_generator)