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.
@@ -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.PyDiffGame import PyDiffGame
5
- from PyDiffGame.PyDiffGameLQRComparison import PyDiffGameLQRComparison
6
- from PyDiffGame.Objective import GameObjective, LQRObjective
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
- 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):
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 = [np.array([r_i]) for r_i in [r_x, r_y, r_theta]]
64
-
65
- variables = ['x', 'y', '\\theta']
70
+ Rs = [r_x, r_y, r_theta]
66
71
 
67
- self.state_variables_names = [f'{v}(t)' for v in variables] + ['\\dot{' + str(v) + '}(t)' for v in variables]
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
- 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)
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
- 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)
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()