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,169 +0,0 @@
1
- import numpy as np
2
- from tqdm import tqdm
3
- from time import time
4
- from pathlib import Path
5
- import itertools
6
- from concurrent.futures import ProcessPoolExecutor
7
-
8
- import inspect
9
- from typing import Sequence, Any, Optional, Callable, Union
10
- from abc import ABC
11
-
12
- from PyDiffGame.PyDiffGame import PyDiffGame
13
- from PyDiffGame.ContinuousPyDiffGame import ContinuousPyDiffGame
14
- from PyDiffGame.DiscretePyDiffGame import DiscretePyDiffGame
15
- from PyDiffGame.Objective import GameObjective
16
-
17
-
18
- class PyDiffGameLQRComparison(ABC, Callable, Sequence):
19
- """
20
- Differential games comparison abstract base class
21
-
22
-
23
- Parameters
24
- ----------
25
-
26
- args: sequence of 2-d np.arrays of len(N), each array B_i of shape(n, m_i)
27
- System input matrices for each control objective
28
- games_objectives: Sequence of Sequences of GameObjective objects
29
- Each game objectives for the comparison
30
- continuous: boolean, optional
31
- Indicates whether to consider a continuous or discrete control design
32
- """
33
-
34
- def __init__(self,
35
- args: dict[str, Any],
36
- games_objectives: Sequence[Sequence[GameObjective]],
37
- M: np.array = None,
38
- continuous: bool = True):
39
- game_class = ContinuousPyDiffGame if continuous else DiscretePyDiffGame
40
-
41
- self.__args = args
42
- self.__verify_input()
43
-
44
- self._games = {}
45
- self._lqr_objective = None
46
- self.__M = M
47
-
48
- for i, game_i_objectives in enumerate(games_objectives):
49
- game_i = game_class(**self.__args | {'objectives': [o for o in game_i_objectives]})
50
- self._games[i] = game_i
51
-
52
- if game_i.is_LQR():
53
- self._lqr_objective = game_i_objectives[0]
54
-
55
- def __verify_input(self):
56
- """
57
- Input checking method
58
-
59
- Raises
60
- ------
61
-
62
- Case-specific errors
63
- """
64
-
65
- for mat in ['A', 'B']:
66
- mat_name = 'dynamics' if mat == 'A' else 'input'
67
- if mat not in self.__args.keys():
68
- raise ValueError(f"Passed arguments must contain the {mat_name} matrix {mat}")
69
- if isinstance(self.__args[mat], type(np.array)):
70
- raise ValueError(f'The {mat_name} matrix {mat} must be a numpy array')
71
-
72
- def are_fully_controllable(self):
73
- """
74
- Tests each game's full controllability as an LTI system
75
- """
76
-
77
- return all(game.is_fully_controllable() for game in self._games.values())
78
-
79
- def plot_two_state_spaces(self,
80
- i: Optional[int] = 0,
81
- j: Optional[int] = 1,
82
- non_linear: bool = False):
83
- """
84
- Plot the state spaces of two games
85
- """
86
-
87
- self._games[i].plot_two_state_spaces(other=self._games[j],
88
- non_linear=non_linear)
89
-
90
- def __simulate_non_linear_system(self,
91
- i: int,
92
- plot: bool = False) -> np.array:
93
- """
94
- Simulates the corresponding non-linear system's progression through time
95
- """
96
-
97
- pass
98
-
99
- def __run_animation(self,
100
- i: int):
101
- """
102
- Animates the state progression of the system
103
- """
104
-
105
- pass
106
-
107
- def __call__(self,
108
- plot_state_spaces: Optional[bool] = False,
109
- plot_Mx: Optional[bool] = False,
110
- output_variables_names: Optional[Sequence[str]] = None,
111
- save_figure: Optional[bool] = False,
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
- run_animations: Optional[bool] = True,
116
- print_characteristic_polynomials: Optional[bool] = False,
117
- print_eigenvalues: Optional[bool] = False):
118
- """
119
- Runs the comparison
120
- """
121
-
122
- for i, game_i in self._games.items():
123
- game_i(plot_state_space=plot_state_spaces,
124
- plot_Mx=plot_Mx,
125
- M=self.__M,
126
- output_variables_names=output_variables_names,
127
- save_figure=save_figure,
128
- figure_path=figure_path,
129
- figure_filename=figure_filename if isinstance(figure_filename, str) else figure_filename(game_i),
130
- print_characteristic_polynomials=print_characteristic_polynomials,
131
- print_eigenvalues=print_eigenvalues)
132
-
133
- if run_animations:
134
- self.__run_animation(i=i)
135
-
136
- @staticmethod
137
- def run_multiprocess(multiprocess_worker_function: Callable,
138
- values: Sequence[Sequence]):
139
- t_start = time()
140
- combos = list(itertools.product(*values))
141
-
142
- with ProcessPoolExecutor() as executor:
143
- submittals = {str(combo): executor.submit(multiprocess_worker_function, *combo) for combo in combos}
144
-
145
- delimiter = ', '
146
- names = inspect.signature(multiprocess_worker_function).parameters.keys()
147
-
148
- for combo, submittal in tqdm(iterable=submittals.items(), total=len(submittals)):
149
- values = combo[1:-1].split(delimiter)
150
- print_str = f"{delimiter.join([f'{n}={v}' for n, v in zip(names, values)])}"
151
- print(print_str)
152
- submittal.result()
153
-
154
- print(f'Total time: {time() - t_start}')
155
-
156
- def __getitem__(self,
157
- i: int) -> PyDiffGame:
158
- """
159
- Returns the i'th game
160
- """
161
-
162
- return self._games[i]
163
-
164
- def __len__(self) -> int:
165
- """
166
- We define the length of a comparison to be its number of games
167
- """
168
-
169
- return len(self._games)
@@ -1,306 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: PyDiffGame
3
- Version: 0.1.2
4
- Summary: PyDiffGame is a Python implementation of a Nash Equilibrium solution to Differential Games, based on a reduction of Game Hamilton-Bellman-Jacobi (GHJB) equations to Game Algebraic and Differential Riccati equations, associated with Multi-Objective Dynamical Control Systems
5
- Project-URL: Homepage, https://krichelj.github.io/PyDiffGame/
6
- Project-URL: Bug Tracker, https://github.com/krichelj/PyDiffGame/issues
7
- Author-email: Joshua Shay Kricheli <skricheli2@gmail.com>
8
- License-File: LICENSE
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Classifier: Programming Language :: Python :: 3
12
- Requires-Python: >=3.10
13
- Description-Content-Type: text/markdown
14
-
15
- <p align="center">
16
- <img alt="Logo" src="https://raw.githubusercontent.com/krichelj/PyDiffGame/master/images/logo.png"/>
17
- </p>
18
-
19
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Upload Python Package](https://github.com/krichelj/PyDiffGame/actions/workflows/python-publish.yml/badge.svg)](https://github.com/krichelj/PyDiffGame/actions/workflows/python-publish.yml) [![pages-build-deployment](https://github.com/krichelj/PyDiffGame/actions/workflows/pages/pages-build-deployment/badge.svg)](https://github.com/krichelj/PyDiffGame/actions/workflows/pages/pages-build-deployment)
20
-
21
-
22
- * [What is this?](#what-is-this)
23
- * [Installation](#installation)
24
- * [Input Parameters](#input-parameters)
25
- * [Tutorial](#tutorial)
26
- * [Authors](#authors)
27
- * [Acknowledgments](#acknowledgments)
28
-
29
- # What is this?
30
-
31
- [`PyDiffGame`](https://github.com/krichelj/PyDiffGame) is a Python implementation of a Nash Equilibrium solution to Differential Games, based on a reduction of Game Hamilton-Bellman-Jacobi (GHJB) equations to Game Algebraic and Differential Riccati equations, associated with Multi-Objective Dynamical Control Systems\
32
- The method relies on the formulation given in:
33
-
34
- - The thesis work "_Differential Games for Compositional Handling of Competing Control Tasks_"
35
- ([Research Gate](https://www.researchgate.net/publication/359819808_Differential_Games_for_Compositional_Handling_of_Competing_Control_Tasks))
36
-
37
- - The conference article "_Composition of Dynamic Control Objectives Based on Differential Games_"
38
- ([IEEE](https://ieeexplore.ieee.org/document/9480269) |
39
- [Research Gate](https://www.researchgate.net/publication/353452024_Composition_of_Dynamic_Control_Objectives_Based_on_Differential_Games))
40
-
41
- The package was tested for Python >= 3.10.
42
-
43
- # Installation
44
-
45
- To install this package run this from the command prompt:
46
- ```
47
- pip install PyDiffGame
48
- ```
49
-
50
- The package was tested for Python >= 3.10, along with the listed packages versions in [`requirments.txt`](https://github.com/krichelj/PyDiffGame/blob/master/requirements.txt)
51
-
52
- # Input Parameters
53
-
54
- The package defines an abstract class [`PyDiffGame.py`](https://github.com/krichelj/PyDiffGame/blob/master/src/PyDiffGame/PyDiffGame.py). An object of this class represents an instance of differential game.
55
- The input parameters to instantiate a `PyDiffGame` object are:
56
-
57
- * `A` : `np.array` of shape $(n,n)$
58
- >System dynamics matrix
59
- * `B` : `np.array` of shape $(n, m_1 + ... + m_N)$, optional
60
- >Input matrix for all virtual control objectives
61
- * `Bs` : `Sequence` of `np.array` objects of len $(N)$, each array $B_i$ of shape $(n,m_i)$, optional
62
- >Input matrices for each virtual control objective
63
- * `Qs` : `Sequence` of `np.array` objects of len $(N)$, each array $Q_i$ of shape $(n,n)$, optional
64
- >State weight matrices for each virtual control objective
65
- * `Rs` : `Sequence` of `np.array` objects of len $(N)$, each array $R_i$ of shape $(m_i,m_i)$, optional
66
- >Input weight matrices for each virtual control objective
67
- * `Ms` : `Sequence` of `np.array` objects of len $(N)$, each array $M_i$ of shape $(m_i,m)$, optional
68
- >Decomposition matrices for each virtual control objective
69
- * `objectives` : `Sequence` of `Objective` objects of len $(N)$, each $O_i$ specifying $Q_i, R_i$ and $M_i$, optional
70
- >Desired objectives for the game
71
- * `x_0` : `np.array` of len $(n)$, optional
72
- >Initial state vector
73
- * `x_T` : `np.array` of len $(n)$, optional
74
- >Final state vector, in case of signal tracking
75
- * `T_f` : positive `float`, optional
76
- >System dynamics horizon. Should be given in the case of finite horizon
77
- * `P_f` : `list` of `np.array` objects of len $(N)$, each array $P_{f_i}$ of shape $(n,n)$, optional, default = uncoupled solution of `scipy's solve_are`
78
- >
79
- >Final condition for the Riccati equation array. Should be given in the case of finite horizon
80
- * `state_variables_names` : `Sequence` of `str` objects of len $(N)$, optional
81
- >The state variables' names to display when plotting
82
- * `show_legend` : `boolean`, optional
83
- >Indicates whether to display a legend in the plots
84
- * `state_variables_names` : `Sequence` of `str` objects of len $(n)$, optional
85
- >The state variables' names to display
86
- * `epsilon_x` : `float` in the interval $(0,1)$, optional
87
- >Numerical convergence threshold for the state vector of the system
88
- * `epsilon_P` : `float` in the interval $(0,1)$, optional
89
- >Numerical convergence threshold for the matrices P_i
90
- * `L` : positive `int`, optional
91
- >Number of data points
92
- * `eta` : positive `int`, optional
93
- >The number of last matrix norms to consider for convergence
94
- * `debug` : `boolean`, optional
95
- >Indicates whether to display debug information
96
-
97
-
98
- # Tutorial
99
-
100
- To demonstrate the use of the package, we provide a few running examples.
101
- Consider the following system of masses and springs:
102
-
103
-
104
- <p align="center">
105
- <img align=top src="https://raw.githubusercontent.com/krichelj/PyDiffGame/master/src/PyDiffGame/examples/figures/2/two_masses_tikz.png" width="797" height="130"/>
106
- </p>
107
-
108
- The performance of the system under the use of the suggested method is compared with that of a Linear Quadratic Regulator (LQR). For that purpose, class named [`PyDiffGameLQRComparison`](https://github.com/krichelj/PyDiffGame/blob/master/src/PyDiffGame/PyDiffGameLQRComparison.py) is defined. A comparison of a system should subclass this class.
109
- As an example, for the masses and springs system, consider the following instantiation of an [`MassesWithSpringsComparison`](https://github.com/krichelj/PyDiffGame/blob/master/src/PyDiffGame/examples/MassesWithSpringsComparison.py) object:
110
-
111
- ```python
112
- import numpy as np
113
- from PyDiffGame.examples.MassesWithSpringsComparison import MassesWithSpringsComparison
114
-
115
- N = 2
116
- k = 10
117
- m = 50
118
- r = 1
119
- epsilon_x = 10e-8
120
- epsilon_P = 10e-8
121
- q = [[500, 2000], [500, 250]]
122
-
123
- x_0 = np.array([10 * i for i in range(1, N + 1)] + [0] * N)
124
- x_T = x_0 * 10 if N == 2 else np.array([(10 * i) ** 3 for i in range(1, N + 1)] + [0] * N)
125
- T_f = 25
126
-
127
- masses_with_springs = MassesWithSpringsComparison(N=N,
128
- m=m,
129
- k=k,
130
- q=q,
131
- r=r,
132
- x_0=x_0,
133
- x_T=x_T,
134
- T_f=T_f,
135
- epsilon_x=epsilon_x,
136
- epsilon_P=epsilon_P)
137
- ```
138
-
139
- Consider the constructor of the class `MassesWithSpringsComparison`:
140
-
141
- ```python
142
- import numpy as np
143
- from typing import Sequence, Optional
144
-
145
- from PyDiffGame.PyDiffGame import PyDiffGame
146
- from PyDiffGame.PyDiffGameLQRComparison import PyDiffGameLQRComparison
147
- from PyDiffGame.Objective import GameObjective, LQRObjective
148
-
149
-
150
- class MassesWithSpringsComparison(PyDiffGameLQRComparison):
151
- def __init__(self,
152
- N: int,
153
- m: float,
154
- k: float,
155
- q: float | Sequence[float] | Sequence[Sequence[float]],
156
- r: float,
157
- Ms: Optional[Sequence[np.array]] = None,
158
- x_0: Optional[np.array] = None,
159
- x_T: Optional[np.array] = None,
160
- T_f: Optional[float] = None,
161
- epsilon_x: Optional[float] = PyDiffGame.epsilon_x_default,
162
- epsilon_P: Optional[float] = PyDiffGame.epsilon_P_default,
163
- L: Optional[int] = PyDiffGame.L_default,
164
- eta: Optional[int] = PyDiffGame.eta_default):
165
- I_N = np.eye(N)
166
- Z_N = np.zeros((N, N))
167
-
168
- M_masses = m * I_N
169
- K = k * (2 * I_N - np.array([[int(abs(i - j) == 1) for j in range(N)] for i in range(N)]))
170
- M_masses_inv = np.linalg.inv(M_masses)
171
-
172
- M_inv_K = M_masses_inv @ K
173
-
174
- if Ms is None:
175
- eigenvectors = np.linalg.eig(M_inv_K)[1]
176
- Ms = [eigenvector.reshape(1, N) for eigenvector in eigenvectors]
177
-
178
- A = np.block([[Z_N, I_N],
179
- [-M_inv_K, Z_N]])
180
- B = np.block([[Z_N],
181
- [M_masses_inv]])
182
-
183
- Qs = [np.diag([0.0] * i + [q] + [0.0] * (N - 1) + [q] + [0.0] * (N - i - 1))
184
- if isinstance(q, (int, float)) else
185
- np.diag([0.0] * i + [q[i]] + [0.0] * (N - 1) + [q[i]] + [0.0] * (N - i - 1)) for i in range(N)]
186
-
187
- M = np.concatenate(Ms,
188
- axis=0)
189
-
190
- assert np.all(np.abs(np.linalg.inv(M) - M.T) < 10e-12)
191
-
192
- Q_mat = np.kron(a=np.eye(2),
193
- b=M)
194
-
195
- Qs = [Q_mat.T @ Q @ Q_mat for Q in Qs]
196
-
197
- Rs = [np.array([r])] * N
198
- R_lqr = 1 / 4 * r * I_N
199
- Q_lqr = q * np.eye(2 * N) if isinstance(q, (int, float)) else np.diag(2 * q)
200
-
201
- state_variables_names = ['x_{' + str(i) + '}' for i in range(1, N + 1)] + \
202
- ['\\dot{x}_{' + str(i) + '}' for i in range(1, N + 1)]
203
- args = {'A': A,
204
- 'B': B,
205
- 'x_0': x_0,
206
- 'x_T': x_T,
207
- 'T_f': T_f,
208
- 'state_variables_names': state_variables_names,
209
- 'epsilon_x': epsilon_x,
210
- 'epsilon_P': epsilon_P,
211
- 'L': L,
212
- 'eta': eta}
213
-
214
- lqr_objective = [LQRObjective(Q=Q_lqr,
215
- R_ii=R_lqr)]
216
- game_objectives = [GameObjective(Q=Q,
217
- R_ii=R,
218
- M_i=M_i) for Q, R, M_i in zip(Qs, Rs, Ms)]
219
- games_objectives = [lqr_objective,
220
- game_objectives]
221
-
222
- super().__init__(args=args,
223
- M=M,
224
- games_objectives=games_objectives,
225
- continuous=True)
226
- ```
227
-
228
- Finally, consider calling the `masses_with_springs` object as follows:
229
-
230
- ```python
231
- output_variables_names = ['$\\frac{x_1 + x_2}{\\sqrt{2}}$',
232
- '$\\frac{x_2 - x_1}{\\sqrt{2}}$',
233
- '$\\frac{\\dot{x}_1 + \\dot{x}_2}{\\sqrt{2}}$',
234
- '$\\frac{\\dot{x}_2 - \\dot{x}_1}{\\sqrt{2}}$']
235
-
236
- masses_with_springs(plot_state_spaces=True,
237
- plot_Mx=True,
238
- output_variables_names=output_variables_names,
239
- save_figure=True)
240
- ```
241
-
242
- This will result in the following plot that compares the two systems performance for a differential game vs an LQR:
243
-
244
- <p align="center">
245
- <img align=top src="https://raw.githubusercontent.com/krichelj/PyDiffGame/master/src/PyDiffGame/examples/figures/2/2-players_large_1.png" width="400" height="300"/>
246
- <img align=top src="https://raw.githubusercontent.com/krichelj/PyDiffGame/master/src/PyDiffGame/examples/figures/2/LQR_large_1.png" width="400" height="300"/>
247
- </p>
248
-
249
-
250
- And when tweaking the weights by setting
251
-
252
- ```python
253
- qs = [[500, 5000]]
254
- ```
255
-
256
- we have:
257
-
258
- <p align="center">
259
- <img align=top src="https://raw.githubusercontent.com/krichelj/PyDiffGame/master/src/PyDiffGame/examples/figures/2/2-players_large_2.png" width="400" height="300"/>
260
- <img align=top src="https://raw.githubusercontent.com/krichelj/PyDiffGame/master/src/PyDiffGame/examples/figures/2/LQR_large_2.png" width="400" height="300"/>
261
- </p>
262
-
263
- # Authors
264
-
265
- If you use this work, please cite our paper:
266
- ```
267
- @inproceedings{pydiffgame_paper,
268
- author={Kricheli, Joshua Shay and Sadon, Aviran and Arogeti, Shai and Regev, Shimon and Weiss, Gera},
269
- booktitle={29th Mediterranean Conference on Control and Automation (MED 2021)},
270
- title={{Composition of Dynamic Control Objectives Based on Differential Games}},
271
- year={2021},
272
- volume={},
273
- number={},
274
- pages={298-304},
275
- doi={10.1109/MED51440.2021.9480269}}
276
- ```
277
-
278
- Further details can be found in the [citation document](CITATIONS.bib).
279
-
280
- # Acknowledgments
281
-
282
- This research was supported in part by the Leona M. and Harry B. Helmsley Charitable Trust through the '_Agricultural, Biological and Cognitive Robotics Initiative_' ('ABC') and by the Marcus Endowment Fund both at Ben-Gurion University of the Negev, Israel.
283
- This research was also supported by The '_Israeli Smart Transportation Research Center_' ('ISTRC') by The Technion and Bar-Ilan Universities, Israel.
284
-
285
- <p align="center">
286
- <a href="https://istrc.net.technion.ac.il/">
287
- <img src="https://github.com/krichelj/PyDiffGame/blob/master/images/Logo_ISTRC_Green_English.png?raw=true" width="233" alt=""/>
288
- </a>
289
- &emsp;
290
- <a href="https://in.bgu.ac.il/en/Pages/default.aspx">
291
- <picture>
292
- <source media="(prefers-color-scheme: dark)" srcset="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTZ8GbtJiX8lNUygX7-inRBuWESK438jWbRjQ&s">
293
- <source media="(prefers-color-scheme: light)" srcset="https://tamrur.bgu.ac.il/restore/BGU.sig.png">
294
- <img alt="BGU Logo" src="https://tamrur.bgu.ac.il/restore/BGU.sig.png" width="233" class="bgu-logo-dark">
295
- </picture>
296
- </a>
297
- &emsp;
298
- &emsp;
299
- <a href="https://helmsleytrust.org/">
300
- <picture>
301
- <source media="(prefers-color-scheme: dark)" srcset="https://helmsleytrust.org/wp-content/themes/helmsley/assets/img/helmsley-charitable-trust-logo-white.png">
302
- <source media="(prefers-color-scheme: light)" srcset="https://helmsleytrust.org/wp-content/themes/helmsley/assets/img/helmsley-charitable-trust-logo-blue.png">
303
- <img alt="Helmsley Charitable Trust" src="https://helmsleytrust.org/wp-content/themes/helmsley/assets/img/helmsley-charitable-trust-logo-blue.png" width="233">
304
- </picture>
305
- </a>
306
- </p>