PyDiffGame 0.1.1__py3-none-any.whl → 1.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/ContinuousPyDiffGame.py +2 -2
- PyDiffGame/DiscretePyDiffGame.py +8 -9
- PyDiffGame/PyDiffGame.py +19 -12
- PyDiffGame/PyDiffGameLQRComparison.py +9 -10
- PyDiffGame/examples/InvertedPendulumComparison.py +257 -0
- PyDiffGame/examples/MassesWithSpringsComparison.py +218 -0
- PyDiffGame/examples/PVTOL.py +222 -0
- PyDiffGame/examples/PVTOLComparison.py +111 -0
- PyDiffGame/examples/QuadRotorControl.py +548 -0
- PyDiffGame/examples/figures/2/2-players_large_1.png +0 -0
- PyDiffGame/examples/figures/2/2-players_large_2.png +0 -0
- PyDiffGame/examples/figures/2/LQR_large_1.png +0 -0
- PyDiffGame/examples/figures/2/LQR_large_2.png +0 -0
- PyDiffGame/examples/figures/2/two_masses_tikz.png +0 -0
- PyDiffGame/examples/figures/4/4-players_large_1.png +0 -0
- PyDiffGame/examples/figures/4/4-players_large_2.png +0 -0
- PyDiffGame/examples/figures/4/LQR_large_1.png +0 -0
- PyDiffGame/examples/figures/4/LQR_large_2.png +0 -0
- PyDiffGame/examples/figures/8/8-players_large_1.png +0 -0
- PyDiffGame/examples/figures/8/8-players_large_2.png +0 -0
- PyDiffGame/examples/figures/8/LQR_large_1.png +0 -0
- PyDiffGame/examples/figures/8/LQR_large_2.png +0 -0
- PyDiffGame/examples/figures/PVTOL/PVTOL1.png +0 -0
- PyDiffGame/examples/figures/PVTOL/PVTOL10.png +0 -0
- PyDiffGame/examples/figures/PVTOL/PVTOL100.png +0 -0
- PyDiffGame/examples/figures/PVTOL/PVTOL1000.png +0 -0
- PyDiffGame/examples/figures/PVTOL0001.png +0 -0
- PyDiffGame/examples/figures/PVTOL001.png +0 -0
- PyDiffGame/examples/figures/PVTOL01.png +0 -0
- PyDiffGame/examples/figures/PVTOL1.png +0 -0
- {PyDiffGame-0.1.1.dist-info → pydiffgame-1.0.0.dist-info}/METADATA +46 -35
- pydiffgame-1.0.0.dist-info/RECORD +37 -0
- {PyDiffGame-0.1.1.dist-info → pydiffgame-1.0.0.dist-info}/WHEEL +1 -2
- {PyDiffGame-0.1.1.dist-info → pydiffgame-1.0.0.dist-info/licenses}/LICENSE +1 -1
- PyDiffGame-0.1.1.dist-info/RECORD +0 -12
- PyDiffGame-0.1.1.dist-info/top_level.txt +0 -1
|
@@ -4,7 +4,7 @@ import sys
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
from scipy.integrate import odeint
|
|
6
6
|
from numpy.linalg import eigvals, inv
|
|
7
|
-
from typing import Sequence, Optional
|
|
7
|
+
from typing import Sequence, Optional, Union
|
|
8
8
|
|
|
9
9
|
from PyDiffGame.PyDiffGame import PyDiffGame
|
|
10
10
|
from PyDiffGame.Objective import Objective
|
|
@@ -30,7 +30,7 @@ class ContinuousPyDiffGame(PyDiffGame):
|
|
|
30
30
|
x_0: Optional[np.array] = None,
|
|
31
31
|
x_T: Optional[np.array] = None,
|
|
32
32
|
T_f: Optional[float] = None,
|
|
33
|
-
P_f: Optional[Sequence[np.array]
|
|
33
|
+
P_f: Optional[Union[Sequence[np.array], np.array]] = None,
|
|
34
34
|
show_legend: Optional[bool] = True,
|
|
35
35
|
state_variables_names: Optional[Sequence[str]] = None,
|
|
36
36
|
epsilon_x: Optional[float] = PyDiffGame._epsilon_x_default,
|
PyDiffGame/DiscretePyDiffGame.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import scipy as sp
|
|
3
|
-
|
|
4
|
-
from typing import Sequence, Optional
|
|
3
|
+
|
|
4
|
+
from typing import Sequence, Optional, Union
|
|
5
5
|
|
|
6
6
|
from PyDiffGame.PyDiffGame import PyDiffGame
|
|
7
7
|
from PyDiffGame.Objective import Objective
|
|
@@ -37,14 +37,13 @@ class DiscretePyDiffGame(PyDiffGame):
|
|
|
37
37
|
x_0: Optional[np.array] = None,
|
|
38
38
|
x_T: Optional[np.array] = None,
|
|
39
39
|
T_f: Optional[float] = None,
|
|
40
|
-
P_f: Optional[Sequence[np.array]
|
|
40
|
+
P_f: Optional[Union[Sequence[np.array], np.array]] = None,
|
|
41
41
|
show_legend: Optional[bool] = True,
|
|
42
42
|
state_variables_names: Optional[Sequence] = None,
|
|
43
43
|
epsilon_x: Optional[float] = PyDiffGame._epsilon_x_default,
|
|
44
44
|
epsilon_P: Optional[float] = PyDiffGame._epsilon_P_default,
|
|
45
45
|
L: Optional[int] = PyDiffGame._L_default,
|
|
46
46
|
eta: Optional[int] = PyDiffGame._eta_default,
|
|
47
|
-
force_finite_horizon: Optional[bool] = False,
|
|
48
47
|
debug: Optional[bool] = False):
|
|
49
48
|
|
|
50
49
|
super().__init__(A=A,
|
|
@@ -64,7 +63,6 @@ class DiscretePyDiffGame(PyDiffGame):
|
|
|
64
63
|
epsilon_P=epsilon_P,
|
|
65
64
|
L=L,
|
|
66
65
|
eta=eta,
|
|
67
|
-
force_finite_horizon=force_finite_horizon,
|
|
68
66
|
debug=debug
|
|
69
67
|
)
|
|
70
68
|
|
|
@@ -134,12 +132,13 @@ class DiscretePyDiffGame(PyDiffGame):
|
|
|
134
132
|
"""
|
|
135
133
|
|
|
136
134
|
A_tilda = np.exp(self._A * self._delta)
|
|
137
|
-
e_AT =
|
|
138
|
-
|
|
139
|
-
|
|
135
|
+
e_AT = sp.integrate.quad(lambda T: np.array([
|
|
136
|
+
np.exp(t * self._A) for t in T]).swapaxes(0, 2).swapaxes(0, 1),
|
|
137
|
+
a=0,
|
|
138
|
+
b=self._delta)[0]
|
|
140
139
|
|
|
141
140
|
self._A = A_tilda
|
|
142
|
-
B_tilda = e_AT
|
|
141
|
+
B_tilda = np.array(e_AT)
|
|
143
142
|
self._Bs = [B_tilda @ B_i for B_i in self._Bs]
|
|
144
143
|
self._Q = [Q_i * self._delta for Q_i in self._Q]
|
|
145
144
|
self._R = [R_i / self._delta for R_i in self._Rs]
|
PyDiffGame/PyDiffGame.py
CHANGED
|
@@ -9,7 +9,7 @@ import matplotlib.pyplot as plt
|
|
|
9
9
|
import scipy as sp
|
|
10
10
|
import sympy
|
|
11
11
|
import warnings
|
|
12
|
-
from typing import Callable, Final, ClassVar, Optional, Sequence
|
|
12
|
+
from typing import Callable, Final, ClassVar, Optional, Sequence, Union
|
|
13
13
|
from abc import ABC, abstractmethod
|
|
14
14
|
|
|
15
15
|
from PyDiffGame.Objective import Objective, GameObjective, LQRObjective
|
|
@@ -122,7 +122,7 @@ class PyDiffGame(ABC, Callable, Sequence):
|
|
|
122
122
|
x_0: Optional[np.array] = None,
|
|
123
123
|
x_T: Optional[np.array] = None,
|
|
124
124
|
T_f: Optional[float] = None,
|
|
125
|
-
P_f: Optional[Sequence[np.array]
|
|
125
|
+
P_f: Optional[Union[Sequence[np.array], np.array]] = None,
|
|
126
126
|
show_legend: Optional[bool] = True,
|
|
127
127
|
state_variables_names: Optional[Sequence[str]] = None,
|
|
128
128
|
epsilon_x: Optional[float] = _epsilon_x_default,
|
|
@@ -162,7 +162,6 @@ class PyDiffGame(ABC, Callable, Sequence):
|
|
|
162
162
|
self._M_inv = np.linalg.inv(self._M)
|
|
163
163
|
except np.linalg.LinAlgError:
|
|
164
164
|
self._M_inv = np.linalg.pinv(self._M)
|
|
165
|
-
print(np.linalg.matrix_rank(self._M_inv))
|
|
166
165
|
l = 0
|
|
167
166
|
self._Bs = []
|
|
168
167
|
|
|
@@ -445,7 +444,15 @@ class PyDiffGame(ABC, Callable, Sequence):
|
|
|
445
444
|
self._fig = plt.figure(dpi=150)
|
|
446
445
|
self._fig.set_size_inches(8, 6)
|
|
447
446
|
plt.plot(t[:temporal_variables.shape[0]], temporal_variables)
|
|
448
|
-
plt.xlabel('Time $[s]$')
|
|
447
|
+
plt.xlabel('Time $[s]$', fontsize=18)
|
|
448
|
+
plt.xticks(fontsize=18)
|
|
449
|
+
plt.yticks(fontsize=18)
|
|
450
|
+
plt.subplots_adjust(wspace=0)
|
|
451
|
+
|
|
452
|
+
if not is_P and max(np.max(temporal_variables, axis=0)) > 1e3:
|
|
453
|
+
plt.ticklabel_format(style='sci', axis='y', scilimits=(0, 0))
|
|
454
|
+
plt.gca().yaxis.get_offset_text().set_size(18)
|
|
455
|
+
|
|
449
456
|
|
|
450
457
|
if title:
|
|
451
458
|
plt.title(title)
|
|
@@ -467,10 +474,10 @@ class PyDiffGame(ABC, Callable, Sequence):
|
|
|
467
474
|
|
|
468
475
|
plt.legend(labels=labels,
|
|
469
476
|
loc='upper left' if is_P else 'right',
|
|
470
|
-
ncol=2,
|
|
471
|
-
prop={'size':
|
|
472
|
-
bbox_to_anchor=(1, 0.
|
|
473
|
-
|
|
477
|
+
ncol=4 if self._n > 8 else 2,
|
|
478
|
+
prop={'size': 26},
|
|
479
|
+
bbox_to_anchor=(1, 0.55),
|
|
480
|
+
framealpha=0.3
|
|
474
481
|
)
|
|
475
482
|
|
|
476
483
|
plt.grid()
|
|
@@ -529,7 +536,7 @@ class PyDiffGame(ABC, Callable, Sequence):
|
|
|
529
536
|
self.__plot_temporal_variables(t=self._forward_time,
|
|
530
537
|
temporal_variables=state_variables,
|
|
531
538
|
is_P=False,
|
|
532
|
-
title=self.__get_temporal_state_variables_title(linear_system=linear_system),
|
|
539
|
+
# title=self.__get_temporal_state_variables_title(linear_system=linear_system),
|
|
533
540
|
output_variables_names=output_variables_names)
|
|
534
541
|
|
|
535
542
|
def __plot_x(self):
|
|
@@ -543,7 +550,7 @@ class PyDiffGame(ABC, Callable, Sequence):
|
|
|
543
550
|
C: np.array,
|
|
544
551
|
output_variables_names: Optional[Sequence[str]] = None,
|
|
545
552
|
save_figure: Optional[bool] = False,
|
|
546
|
-
figure_path: Optional[str
|
|
553
|
+
figure_path: Optional[Union[str, Path]] = _default_figures_filename,
|
|
547
554
|
figure_filename: Optional[str] = _default_figures_filename):
|
|
548
555
|
"""
|
|
549
556
|
Plots an output vector y = C x^T wth respect to time
|
|
@@ -572,7 +579,7 @@ class PyDiffGame(ABC, Callable, Sequence):
|
|
|
572
579
|
|
|
573
580
|
@_post_convergence
|
|
574
581
|
def __save_figure(self,
|
|
575
|
-
figure_path: Optional[str
|
|
582
|
+
figure_path: Optional[Union[str, Path]] = _default_figures_filename,
|
|
576
583
|
figure_filename: Optional[str] = _default_figures_filename):
|
|
577
584
|
"""
|
|
578
585
|
Saves the current figure
|
|
@@ -1150,7 +1157,7 @@ class PyDiffGame(ABC, Callable, Sequence):
|
|
|
1150
1157
|
M: Optional[np.array] = None,
|
|
1151
1158
|
output_variables_names: Optional[Sequence[str]] = None,
|
|
1152
1159
|
save_figure: Optional[bool] = False,
|
|
1153
|
-
figure_path: Optional[str
|
|
1160
|
+
figure_path: Optional[Union[str, Path]] = _default_figures_path,
|
|
1154
1161
|
figure_filename: Optional[str] = _default_figures_filename,
|
|
1155
1162
|
print_characteristic_polynomials: Optional[bool] = False,
|
|
1156
1163
|
print_eigenvalues: Optional[bool] = False):
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
from tqdm import tqdm
|
|
3
3
|
from time import time
|
|
4
|
-
from termcolor import colored
|
|
5
4
|
from pathlib import Path
|
|
6
5
|
import itertools
|
|
7
6
|
from concurrent.futures import ProcessPoolExecutor
|
|
8
7
|
|
|
9
8
|
import inspect
|
|
10
|
-
from typing import Sequence, Any, Optional, Callable
|
|
9
|
+
from typing import Sequence, Any, Optional, Callable, Union
|
|
11
10
|
from abc import ABC
|
|
12
11
|
|
|
13
12
|
from PyDiffGame.PyDiffGame import PyDiffGame
|
|
@@ -35,9 +34,9 @@ class PyDiffGameLQRComparison(ABC, Callable, Sequence):
|
|
|
35
34
|
def __init__(self,
|
|
36
35
|
args: dict[str, Any],
|
|
37
36
|
games_objectives: Sequence[Sequence[GameObjective]],
|
|
38
|
-
M: np.array,
|
|
37
|
+
M: np.array = None,
|
|
39
38
|
continuous: bool = True):
|
|
40
|
-
|
|
39
|
+
game_class = ContinuousPyDiffGame if continuous else DiscretePyDiffGame
|
|
41
40
|
|
|
42
41
|
self.__args = args
|
|
43
42
|
self.__verify_input()
|
|
@@ -47,7 +46,7 @@ class PyDiffGameLQRComparison(ABC, Callable, Sequence):
|
|
|
47
46
|
self.__M = M
|
|
48
47
|
|
|
49
48
|
for i, game_i_objectives in enumerate(games_objectives):
|
|
50
|
-
game_i =
|
|
49
|
+
game_i = game_class(**self.__args | {'objectives': [o for o in game_i_objectives]})
|
|
51
50
|
self._games[i] = game_i
|
|
52
51
|
|
|
53
52
|
if game_i.is_LQR():
|
|
@@ -110,8 +109,9 @@ class PyDiffGameLQRComparison(ABC, Callable, Sequence):
|
|
|
110
109
|
plot_Mx: Optional[bool] = False,
|
|
111
110
|
output_variables_names: Optional[Sequence[str]] = None,
|
|
112
111
|
save_figure: Optional[bool] = False,
|
|
113
|
-
figure_path: Optional[str
|
|
114
|
-
figure_filename: Optional[str
|
|
112
|
+
figure_path: Optional[Union[str, Path]] = PyDiffGame.default_figures_path,
|
|
113
|
+
figure_filename: Optional[Union[str, Callable[[PyDiffGame], str]]] =
|
|
114
|
+
PyDiffGame.default_figures_filename,
|
|
115
115
|
run_animations: Optional[bool] = True,
|
|
116
116
|
print_characteristic_polynomials: Optional[bool] = False,
|
|
117
117
|
print_eigenvalues: Optional[bool] = False):
|
|
@@ -134,7 +134,7 @@ class PyDiffGameLQRComparison(ABC, Callable, Sequence):
|
|
|
134
134
|
self.__run_animation(i=i)
|
|
135
135
|
|
|
136
136
|
@staticmethod
|
|
137
|
-
def run_multiprocess(multiprocess_worker_function: Callable
|
|
137
|
+
def run_multiprocess(multiprocess_worker_function: Callable,
|
|
138
138
|
values: Sequence[Sequence]):
|
|
139
139
|
t_start = time()
|
|
140
140
|
combos = list(itertools.product(*values))
|
|
@@ -147,8 +147,7 @@ class PyDiffGameLQRComparison(ABC, Callable, Sequence):
|
|
|
147
147
|
|
|
148
148
|
for combo, submittal in tqdm(iterable=submittals.items(), total=len(submittals)):
|
|
149
149
|
values = combo[1:-1].split(delimiter)
|
|
150
|
-
print_str = f"
|
|
151
|
-
color='blue')}"""
|
|
150
|
+
print_str = f"{delimiter.join([f'{n}={v}' for n, v in zip(names, values)])}"
|
|
152
151
|
print(print_str)
|
|
153
152
|
submittal.result()
|
|
154
153
|
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import scipy as sp
|
|
5
|
+
from time import time
|
|
6
|
+
import matplotlib
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from PyDiffGame.PyDiffGame import PyDiffGame
|
|
12
|
+
from PyDiffGame.PyDiffGameLQRComparison import PyDiffGameLQRComparison
|
|
13
|
+
from PyDiffGame.Objective import GameObjective, LQRObjective
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class InvertedPendulumComparison(PyDiffGameLQRComparison):
|
|
17
|
+
def __init__(self,
|
|
18
|
+
m_c: float,
|
|
19
|
+
m_p: float,
|
|
20
|
+
p_L: float,
|
|
21
|
+
q: float,
|
|
22
|
+
r: Optional[float] = 1,
|
|
23
|
+
x_0: Optional[np.array] = None,
|
|
24
|
+
x_T: Optional[np.array] = None,
|
|
25
|
+
T_f: Optional[float] = None,
|
|
26
|
+
epsilon_x: Optional[float] = PyDiffGame.epsilon_x_default,
|
|
27
|
+
epsilon_P: Optional[float] = PyDiffGame.epsilon_P_default,
|
|
28
|
+
L: Optional[int] = PyDiffGame.L_default,
|
|
29
|
+
eta: Optional[int] = PyDiffGame.eta_default):
|
|
30
|
+
self.__m_c = m_c
|
|
31
|
+
self.__m_p = m_p
|
|
32
|
+
self.__p_L = p_L
|
|
33
|
+
self.__l = self.__p_L / 2 # CoM of uniform rod
|
|
34
|
+
self.__I = 1 / 12 * self.__m_p * self.__p_L ** 2 # center mass moment of inertia of uniform rod
|
|
35
|
+
|
|
36
|
+
# # original linear system
|
|
37
|
+
linearized_D = self.__m_c * self.__m_p * self.__l ** 2 + self.__I * (self.__m_c + self.__m_p)
|
|
38
|
+
a32 = self.__m_p * PyDiffGame.g * self.__l ** 2 / linearized_D
|
|
39
|
+
a42 = self.__m_p * PyDiffGame.g * self.__l * (self.__m_c + self.__m_p) / linearized_D
|
|
40
|
+
A = np.array([[0, 0, 1, 0],
|
|
41
|
+
[0, 0, 0, 1],
|
|
42
|
+
[0, a32, 0, 0],
|
|
43
|
+
[0, a42, 0, 0]])
|
|
44
|
+
|
|
45
|
+
b21 = (m_p * self.__l ** 2 + self.__I) / linearized_D
|
|
46
|
+
b31 = m_p * self.__l / linearized_D
|
|
47
|
+
b22 = b31
|
|
48
|
+
b32 = (m_c + m_p) / linearized_D
|
|
49
|
+
B = np.array([[0, 0],
|
|
50
|
+
[0, 0],
|
|
51
|
+
[b21, b22],
|
|
52
|
+
[b31, b32]])
|
|
53
|
+
|
|
54
|
+
M1 = B[2, :].reshape(1, 2)
|
|
55
|
+
M2 = B[3, :].reshape(1, 2)
|
|
56
|
+
Ms = [M1, M2]
|
|
57
|
+
|
|
58
|
+
Q_x = q * np.array([[1, 0, 2, 0],
|
|
59
|
+
[0, 0, 0, 0],
|
|
60
|
+
[2, 0, 4, 0],
|
|
61
|
+
[0, 0, 0, 0]])
|
|
62
|
+
Q_theta = q * np.array([[0, 0, 0, 0],
|
|
63
|
+
[0, 1, 0, 2],
|
|
64
|
+
[0, 0, 0, 0],
|
|
65
|
+
[0, 2, 0, 4]])
|
|
66
|
+
Q_lqr = Q_theta + Q_x
|
|
67
|
+
Qs = [Q_x, Q_theta]
|
|
68
|
+
|
|
69
|
+
R_lqr = np.diag([r] * 2)
|
|
70
|
+
Rs = [np.array([r])] * 2
|
|
71
|
+
|
|
72
|
+
self.__origin = (0.0, 0.0)
|
|
73
|
+
|
|
74
|
+
state_variables_names = ['x',
|
|
75
|
+
'\\theta',
|
|
76
|
+
'\\dot{x}',
|
|
77
|
+
'\\dot{\\theta}']
|
|
78
|
+
|
|
79
|
+
args = {'A': A,
|
|
80
|
+
'B': B,
|
|
81
|
+
'x_0': x_0,
|
|
82
|
+
'x_T': x_T,
|
|
83
|
+
'T_f': T_f,
|
|
84
|
+
'state_variables_names': state_variables_names,
|
|
85
|
+
'epsilon_x': epsilon_x,
|
|
86
|
+
'epsilon_P': epsilon_P,
|
|
87
|
+
'L': L,
|
|
88
|
+
'eta': eta,
|
|
89
|
+
'force_finite_horizon': T_f is not None}
|
|
90
|
+
|
|
91
|
+
lqr_objective = [LQRObjective(Q=Q_lqr, R_ii=R_lqr)]
|
|
92
|
+
game_objectives = [GameObjective(Q=Q, R_ii=R, M_i=M_i) for Q, R, M_i in zip(Qs, Rs, Ms)]
|
|
93
|
+
games_objectives = [lqr_objective, game_objectives]
|
|
94
|
+
|
|
95
|
+
super().__init__(args=args,
|
|
96
|
+
games_objectives=games_objectives,
|
|
97
|
+
continuous=True)
|
|
98
|
+
|
|
99
|
+
def __simulate_non_linear_system(self,
|
|
100
|
+
i: int,
|
|
101
|
+
plot: bool = False) -> np.array:
|
|
102
|
+
game = self._games[i]
|
|
103
|
+
K = game.K
|
|
104
|
+
x_T = game.x_T
|
|
105
|
+
|
|
106
|
+
def nonlinear_state_space(_, x_t: np.array) -> np.array:
|
|
107
|
+
x_t = x_t - x_T
|
|
108
|
+
|
|
109
|
+
if game.is_LQR():
|
|
110
|
+
u_t = - K[0] @ x_t
|
|
111
|
+
F_t, M_t = u_t.T
|
|
112
|
+
else:
|
|
113
|
+
K_x, K_theta = K
|
|
114
|
+
v_x = - K_x @ x_t
|
|
115
|
+
v_theta = - K_theta @ x_t
|
|
116
|
+
v = np.array([v_x, v_theta])
|
|
117
|
+
F_t, M_t = game.M_inv @ v
|
|
118
|
+
|
|
119
|
+
x, theta, x_dot, theta_dot = x_t
|
|
120
|
+
|
|
121
|
+
theta_ddot = 1 / (
|
|
122
|
+
self.__m_p * self.__l ** 2 + self.__I - (self.__m_p * self.__l) ** 2 * np.cos(theta) ** 2 /
|
|
123
|
+
(self.__m_p + self.__m_c)) * (M_t - self.__m_p * self.__l *
|
|
124
|
+
(np.cos(theta) / (self.__m_p + self.__m_c) *
|
|
125
|
+
(F_t + self.__m_p * self.__l * np.sin(theta)
|
|
126
|
+
* theta_dot ** 2) + PyDiffGame.g * np.sin(theta)))
|
|
127
|
+
x_ddot = 1 / (self.__m_p + self.__m_c) * (F_t + self.__m_p * self.__l * (np.sin(theta) * theta_dot ** 2 -
|
|
128
|
+
np.cos(theta) * theta_ddot))
|
|
129
|
+
if isinstance(theta_ddot, np.ndarray):
|
|
130
|
+
theta_ddot = theta_ddot[0]
|
|
131
|
+
x_ddot = x_ddot[0]
|
|
132
|
+
|
|
133
|
+
non_linear_x = np.array([x_dot, theta_dot, x_ddot, theta_ddot],
|
|
134
|
+
dtype=float)
|
|
135
|
+
|
|
136
|
+
return non_linear_x
|
|
137
|
+
|
|
138
|
+
pendulum_state = sp.integrate.solve_ivp(fun=nonlinear_state_space,
|
|
139
|
+
t_span=[0.0, game.T_f],
|
|
140
|
+
y0=game.x_0,
|
|
141
|
+
t_eval=game.forward_time,
|
|
142
|
+
rtol=game.epsilon)
|
|
143
|
+
|
|
144
|
+
Y = pendulum_state.y
|
|
145
|
+
|
|
146
|
+
if plot:
|
|
147
|
+
game.plot_state_variables(state_variables=Y.T,
|
|
148
|
+
linear_system=False)
|
|
149
|
+
|
|
150
|
+
return Y
|
|
151
|
+
|
|
152
|
+
def __run_animation(self,
|
|
153
|
+
i: int) -> (matplotlib.lines.Line2D, matplotlib.patches.Rectangle):
|
|
154
|
+
game = self._games[i]
|
|
155
|
+
game._x_non_linear = self.__simulate_non_linear_system(i=i,
|
|
156
|
+
plot=True)
|
|
157
|
+
x_t, theta_t, x_dot_t, theta_dot_t = game._x_non_linear
|
|
158
|
+
|
|
159
|
+
pendulumArm = matplotlib.lines.Line2D(xdata=self.__origin,
|
|
160
|
+
ydata=self.__origin,
|
|
161
|
+
color='r')
|
|
162
|
+
cart = matplotlib.patches.Rectangle(xy=self.__origin,
|
|
163
|
+
width=0.5,
|
|
164
|
+
height=0.15,
|
|
165
|
+
color='b')
|
|
166
|
+
|
|
167
|
+
fig = plt.figure()
|
|
168
|
+
x_max = max(abs(max(x_t)), abs(min(x_t)))
|
|
169
|
+
square_side = 1.1 * min(max(self.__p_L, x_max), 3 * self.__p_L)
|
|
170
|
+
|
|
171
|
+
ax = fig.add_subplot(111,
|
|
172
|
+
aspect='equal',
|
|
173
|
+
xlim=(-square_side, square_side),
|
|
174
|
+
ylim=(-square_side, square_side),
|
|
175
|
+
title=f"Inverted Pendulum {'LQR' if game.is_LQR() else 'Game'} Simulation")
|
|
176
|
+
|
|
177
|
+
def init() -> (matplotlib.lines.Line2D, matplotlib.patches.Rectangle):
|
|
178
|
+
ax.add_patch(cart)
|
|
179
|
+
ax.add_line(pendulumArm)
|
|
180
|
+
|
|
181
|
+
return pendulumArm, cart
|
|
182
|
+
|
|
183
|
+
def animate(i: int) -> (matplotlib.lines.Line2D, matplotlib.patches.Rectangle):
|
|
184
|
+
x_i, theta_i = x_t[i], theta_t[i]
|
|
185
|
+
pendulum_x_coordinates = [x_i, x_i + self.__p_L * np.sin(theta_i)]
|
|
186
|
+
pendulum_y_coordinates = [0, - self.__p_L * np.cos(theta_i)]
|
|
187
|
+
pendulumArm.set_xdata(x=pendulum_x_coordinates)
|
|
188
|
+
pendulumArm.set_ydata(y=pendulum_y_coordinates)
|
|
189
|
+
|
|
190
|
+
cart_x_y = [x_i - cart.get_width() / 2, - cart.get_height()]
|
|
191
|
+
cart.set_xy(xy=cart_x_y)
|
|
192
|
+
|
|
193
|
+
return pendulumArm, cart
|
|
194
|
+
|
|
195
|
+
ax.grid()
|
|
196
|
+
t0 = time()
|
|
197
|
+
animate(0)
|
|
198
|
+
t1 = time()
|
|
199
|
+
|
|
200
|
+
frames = game.L
|
|
201
|
+
interval = game.T_f - (t1 - t0)
|
|
202
|
+
|
|
203
|
+
anim = matplotlib.animationFuncAnimation(fig=fig,
|
|
204
|
+
func=animate,
|
|
205
|
+
init_func=init,
|
|
206
|
+
frames=frames,
|
|
207
|
+
interval=interval,
|
|
208
|
+
blit=True)
|
|
209
|
+
plt.show()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def multiprocess_worker_function(x_T: float,
|
|
213
|
+
theta_0: float,
|
|
214
|
+
m_c: float,
|
|
215
|
+
m_p: float,
|
|
216
|
+
p_L: float,
|
|
217
|
+
q: float,
|
|
218
|
+
epsilon_x: float,
|
|
219
|
+
epsilon_P: float) -> int:
|
|
220
|
+
x_T = np.array([x_T, # x
|
|
221
|
+
theta_0, # theta
|
|
222
|
+
0, # x_dot
|
|
223
|
+
0] # theta_dot
|
|
224
|
+
)
|
|
225
|
+
x_0 = np.zeros_like(x_T)
|
|
226
|
+
|
|
227
|
+
inverted_pendulum_comparison = \
|
|
228
|
+
InvertedPendulumComparison(m_c=m_c,
|
|
229
|
+
m_p=m_p,
|
|
230
|
+
p_L=p_L,
|
|
231
|
+
q=q,
|
|
232
|
+
x_0=x_0,
|
|
233
|
+
x_T=x_T,
|
|
234
|
+
epsilon_x=epsilon_x,
|
|
235
|
+
epsilon_P=epsilon_P) # game class
|
|
236
|
+
is_max_lqr = \
|
|
237
|
+
inverted_pendulum_comparison(plot_state_spaces=False,
|
|
238
|
+
run_animations=False
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# inverted_pendulum_comparison.plot_two_state_spaces(non_linear=True)
|
|
242
|
+
return int(is_max_lqr)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
if __name__ == '__main__':
|
|
246
|
+
x_Ts = [10 ** p for p in [2]]
|
|
247
|
+
theta_Ts = [np.pi / 2 + np.pi / n for n in [10]]
|
|
248
|
+
m_cs = [10 ** p for p in [1, 2]]
|
|
249
|
+
m_ps = [10 ** p for p in [0, 1, 2]]
|
|
250
|
+
p_Ls = [10 ** p for p in [0, 1]]
|
|
251
|
+
qs = [10 ** p for p in [-2, -1, 0, 1]]
|
|
252
|
+
epsilon_xs = [10 ** (-7)]
|
|
253
|
+
epsilon_Ps = [10 ** (-3)]
|
|
254
|
+
params = [x_Ts, theta_Ts, m_cs, m_ps, p_Ls, qs, epsilon_xs, epsilon_Ps]
|
|
255
|
+
|
|
256
|
+
PyDiffGameLQRComparison.run_multiprocess(multiprocess_worker_function=multiprocess_worker_function,
|
|
257
|
+
values=params)
|