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,20 +1,40 @@
|
|
|
1
|
+
"""Planar vertical take-off and landing (PVTOL) aircraft: differential game vs. LQR.
|
|
2
|
+
|
|
3
|
+
The PVTOL is a classic 6-state planar aircraft model with horizontal position
|
|
4
|
+
``x``, vertical position ``y`` and pitch angle ``theta`` (plus their velocities).
|
|
5
|
+
The two physical inputs are the thrust and the rolling moment. The physical
|
|
6
|
+
input space is decomposed along the ``x``, ``y`` and ``theta`` channels so that
|
|
7
|
+
each becomes one player of a differential game, and the result is compared
|
|
8
|
+
against a single LQR controller acting on the whole system.
|
|
9
|
+
|
|
10
|
+
Run directly to solve a small instance and report the costs::
|
|
11
|
+
|
|
12
|
+
python -m PyDiffGame.examples.PVTOLComparison
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
1
17
|
import numpy as np
|
|
2
|
-
from typing import Optional
|
|
3
18
|
|
|
4
|
-
from PyDiffGame.
|
|
5
|
-
from PyDiffGame.
|
|
6
|
-
from PyDiffGame.
|
|
19
|
+
from PyDiffGame.base import PyDiffGame
|
|
20
|
+
from PyDiffGame.comparison import PyDiffGameLQRComparison
|
|
21
|
+
from PyDiffGame.objective import GameObjective, LQRObjective
|
|
22
|
+
from PyDiffGame.plotting import show
|
|
7
23
|
|
|
8
24
|
|
|
9
25
|
class PVTOLComparison(PyDiffGameLQRComparison):
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
26
|
+
"""Compare a per-channel differential game with an LQR for the PVTOL aircraft."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
x_0: np.ndarray | None = None,
|
|
31
|
+
x_T: np.ndarray | None = None,
|
|
32
|
+
T_f: float | None = None,
|
|
33
|
+
epsilon_x: float = PyDiffGame.epsilon_x_default,
|
|
34
|
+
epsilon_P: float = PyDiffGame.epsilon_P_default,
|
|
35
|
+
L: int = PyDiffGame.L_default,
|
|
36
|
+
eta: int = PyDiffGame.eta_default,
|
|
37
|
+
) -> None:
|
|
18
38
|
m = 4 # mass of aircraft
|
|
19
39
|
J = 0.0475 # inertia around pitch axis
|
|
20
40
|
r = 0.25 # distance to center of force
|
|
@@ -22,23 +42,13 @@ class PVTOLComparison(PyDiffGameLQRComparison):
|
|
|
22
42
|
|
|
23
43
|
A_11 = np.zeros((3, 3))
|
|
24
44
|
A_12 = np.eye(3)
|
|
25
|
-
A_21 = np.array([[0, 0, -PyDiffGame.g],
|
|
26
|
-
[0, 0, 0],
|
|
27
|
-
[0, 0, 0]])
|
|
45
|
+
A_21 = np.array([[0, 0, -PyDiffGame.g], [0, 0, 0], [0, 0, 0]])
|
|
28
46
|
A_22 = np.diag([-c / m, -c / m, 0])
|
|
29
47
|
|
|
30
|
-
A = np.block([[A_11, A_12],
|
|
31
|
-
[A_21, A_22]])
|
|
48
|
+
A = np.block([[A_11, A_12], [A_21, A_22]])
|
|
32
49
|
|
|
33
50
|
# 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
|
-
)
|
|
51
|
+
B = np.array([[0, 0], [0, 0], [0, 0], [1 / m, 0], [0, 1 / m], [r / J, 0]])
|
|
42
52
|
|
|
43
53
|
psi = 1
|
|
44
54
|
|
|
@@ -47,9 +57,6 @@ class PVTOLComparison(PyDiffGameLQRComparison):
|
|
|
47
57
|
M_theta = [1, 0]
|
|
48
58
|
|
|
49
59
|
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
60
|
|
|
54
61
|
Q_x_diag = [psi, 0, 0]
|
|
55
62
|
Q_y_diag = [0, 1, 0]
|
|
@@ -60,52 +67,51 @@ class PVTOLComparison(PyDiffGameLQRComparison):
|
|
|
60
67
|
r_theta = 1
|
|
61
68
|
|
|
62
69
|
Qs = [np.diag(Q_i_diag * 2) for Q_i_diag in [Q_x_diag, Q_y_diag, Q_theta_diag]]
|
|
63
|
-
Rs = [
|
|
64
|
-
|
|
65
|
-
variables = ['x', 'y', '\\theta']
|
|
70
|
+
Rs = [r_x, r_y, r_theta]
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
variables = ["x", "y", r"\theta"]
|
|
73
|
+
state_variables_names = [rf"{v}(t)" for v in variables] + [rf"\dot{{{v}}}(t)" for v in variables]
|
|
68
74
|
|
|
69
75
|
Q_lqr = sum(Qs)
|
|
70
76
|
R_lqr = np.diag([r_x + r_theta, r_y])
|
|
71
77
|
|
|
72
|
-
lqr_objective = [LQRObjective(Q=Q_lqr,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
games_objectives=games_objectives)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if __name__ == '__main__':
|
|
97
|
-
epsilon_x = epsilon_P = 10 ** (-8)
|
|
78
|
+
lqr_objective = [LQRObjective(Q=Q_lqr, R=R_lqr)]
|
|
79
|
+
game_objectives = [GameObjective(Q=Q_i, R=R_i, M=M_i) for Q_i, R_i, M_i in zip(Qs, Rs, Ms)]
|
|
80
|
+
|
|
81
|
+
super().__init__(
|
|
82
|
+
A=A,
|
|
83
|
+
B=B,
|
|
84
|
+
games_objectives=[lqr_objective, game_objectives],
|
|
85
|
+
continuous=True,
|
|
86
|
+
x_0=x_0,
|
|
87
|
+
x_T=x_T,
|
|
88
|
+
T_f=T_f,
|
|
89
|
+
L=L,
|
|
90
|
+
eta=eta,
|
|
91
|
+
epsilon_x=epsilon_x,
|
|
92
|
+
epsilon_P=epsilon_P,
|
|
93
|
+
state_variables_names=state_variables_names,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def main(*, plot: bool = True, save_figure: bool = False) -> None:
|
|
98
|
+
"""Solve a single PVTOL comparison and print the costs."""
|
|
98
99
|
|
|
99
100
|
x_0 = np.zeros((6,))
|
|
100
101
|
x_d = y_d = 50
|
|
101
|
-
x_T = np.array([x_d, y_d] + [0] * 4)
|
|
102
|
-
T_f = 25
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
102
|
+
x_T = np.array([x_d, y_d] + [0] * 4, dtype=np.float64)
|
|
103
|
+
T_f = 25.0
|
|
104
|
+
|
|
105
|
+
comparison = PVTOLComparison(x_0=x_0, x_T=x_T, T_f=T_f, L=300)
|
|
106
|
+
comparison.run(plot_state_spaces=plot, save_figure=save_figure)
|
|
107
|
+
|
|
108
|
+
lqr_cost, game_cost = comparison.costs()
|
|
109
|
+
print(f"LQR cost = {lqr_cost:.4g}")
|
|
110
|
+
print(f"Game cost = {game_cost:.4g}")
|
|
111
|
+
print(f"All games controllable: {comparison.are_all_controllable()}")
|
|
112
|
+
if plot:
|
|
113
|
+
show()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
main()
|