{"id":19828962,"url":"https://github.com/krichelj/pydiffgame","last_synced_at":"2025-10-07T17:27:50.963Z","repository":{"id":39699491,"uuid":"257912224","full_name":"krichelj/PyDiffGame","owner":"krichelj","description":"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","archived":false,"fork":false,"pushed_at":"2025-02-19T07:15:33.000Z","size":7772,"stargazers_count":50,"open_issues_count":0,"forks_count":10,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-30T12:06:50.460Z","etag":null,"topics":["artificial-intelligence-algorithms","bellman-equation","computer-science","control-theory","differential-equations","differential-game","dynamical-systems","game-theory","hamilton-jacobi-bellman","hamiltonian","inverted-pendulum","matrix-equations","multi-objective-optimization","nash-equilibrium","numerical-methods","object-oriented-programming","optimization-algorithms","pontryagin-maximum-principle","quadrotor","riccati-equation"],"latest_commit_sha":null,"homepage":"https://krichelj.github.io/PyDiffGame/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/krichelj.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":"CITATIONS.bib","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-22T13:38:13.000Z","updated_at":"2025-02-19T07:14:54.000Z","dependencies_parsed_at":"2024-01-21T22:25:39.137Z","dependency_job_id":"ba9ef82a-0b83-44fa-b040-79a61f581358","html_url":"https://github.com/krichelj/PyDiffGame","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krichelj%2FPyDiffGame","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krichelj%2FPyDiffGame/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krichelj%2FPyDiffGame/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krichelj%2FPyDiffGame/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/krichelj","download_url":"https://codeload.github.com/krichelj/PyDiffGame/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247509237,"owners_count":20950232,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["artificial-intelligence-algorithms","bellman-equation","computer-science","control-theory","differential-equations","differential-game","dynamical-systems","game-theory","hamilton-jacobi-bellman","hamiltonian","inverted-pendulum","matrix-equations","multi-objective-optimization","nash-equilibrium","numerical-methods","object-oriented-programming","optimization-algorithms","pontryagin-maximum-principle","quadrotor","riccati-equation"],"created_at":"2024-11-12T11:17:25.815Z","updated_at":"2025-10-07T17:27:45.924Z","avatar_url":"https://github.com/krichelj.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n    \u003cimg alt=\"Logo\" src=\"https://raw.githubusercontent.com/krichelj/PyDiffGame/master/images/logo.png\"/\u003e\n\u003c/p\u003e\n\n[![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)\n\n\n  * [What is this?](#what-is-this)\n  * [Installation](#installation)\n  * [Input Parameters](#input-parameters)\n  * [Tutorial](#tutorial)\n  * [Authors](#authors)\n  * [Acknowledgments](#acknowledgments)\n\n# What is this?\n\n[`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\\\nThe method relies on the formulation given in:\n\n - The thesis work \"_Differential Games for Compositional Handling of Competing Control Tasks_\"\n   ([Research Gate](https://www.researchgate.net/publication/359819808_Differential_Games_for_Compositional_Handling_of_Competing_Control_Tasks))\n\n - The conference article \"_Composition of Dynamic Control Objectives Based on Differential Games_\" \n([IEEE](https://ieeexplore.ieee.org/document/9480269) | \n[Research Gate](https://www.researchgate.net/publication/353452024_Composition_of_Dynamic_Control_Objectives_Based_on_Differential_Games))\n\nThe package was tested for Python \u003e= 3.10.\n\n# Installation\n\nTo install this package run this from the command prompt:\n```\npip install PyDiffGame\n```\n\nThe package was tested for Python \u003e= 3.10, along with the listed packages versions in [`requirments.txt`](https://github.com/krichelj/PyDiffGame/blob/master/requirements.txt)\n\n# Input Parameters\n\nThe 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.\nThe input parameters to instantiate a `PyDiffGame` object are:\n\n* `A` : `np.array` of shape $(n,n)$\n\u003eSystem dynamics matrix\n* `B` : `np.array` of shape $(n, m_1 + ... + m_N)$, optional\n\u003eInput matrix for all virtual control objectives\n* `Bs` : `Sequence` of `np.array` objects of len $(N)$, each array $B_i$ of shape $(n,m_i)$, optional\n\u003eInput matrices for each virtual control objective\n* `Qs` : `Sequence` of `np.array` objects of len $(N)$, each array $Q_i$ of shape $(n,n)$, optional\n\u003eState weight matrices for each virtual control objective\n* `Rs` : `Sequence` of `np.array` objects of len $(N)$, each array $R_i$ of shape $(m_i,m_i)$, optional\n\u003eInput weight matrices for each virtual control objective\n* `Ms` : `Sequence` of `np.array` objects of len $(N)$, each array $M_i$ of shape $(m_i,m)$, optional\n\u003eDecomposition matrices for each virtual control objective\n* `objectives` : `Sequence` of `Objective` objects of len $(N)$, each $O_i$ specifying $Q_i, R_i$ and $M_i$, optional\n\u003eDesired objectives for the game\n* `x_0` : `np.array` of len $(n)$, optional\n\u003eInitial state vector\n* `x_T` : `np.array` of len $(n)$, optional\n\u003eFinal state vector, in case of signal tracking\n* `T_f` : positive `float`, optional\n\u003eSystem dynamics horizon. Should be given in the case of finite horizon\n* `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`\n\u003e\n\u003eFinal condition for the Riccati equation array. Should be given in the case of finite horizon\n* `state_variables_names` : `Sequence` of `str` objects of len $(N)$, optional\n\u003eThe state variables' names to display when plotting\n* `show_legend` : `boolean`, optional\n\u003eIndicates whether to display a legend in the plots\n* `state_variables_names` : `Sequence` of `str` objects of len $(n)$, optional\n\u003eThe state variables' names to display\n* `epsilon_x` : `float` in the interval $(0,1)$, optional\n\u003eNumerical convergence threshold for the state vector of the system\n* `epsilon_P` : `float` in the interval $(0,1)$, optional\n\u003eNumerical convergence threshold for the matrices P_i\n* `L` : positive `int`, optional\n\u003eNumber of data points\n* `eta` : positive `int`, optional\n\u003eThe number of last matrix norms to consider for convergence\n* `debug` : `boolean`, optional\n\u003eIndicates whether to display debug information\n\n\n# Tutorial\n\nTo demonstrate the use of the package, we provide a few running examples.\nConsider the following system of masses and springs:\n\n\n\u003cp align=\"center\"\u003e\n    \u003cimg align=top src=\"https://raw.githubusercontent.com/krichelj/PyDiffGame/master/src/PyDiffGame/examples/figures/2/two_masses_tikz.png\" width=\"797\" height=\"130\"/\u003e\n\u003c/p\u003e\n\nThe 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.\nAs 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:\n\n```python\nimport numpy as np\nfrom PyDiffGame.examples.MassesWithSpringsComparison import MassesWithSpringsComparison\n\nN = 2\nk = 10\nm = 50\nr = 1\nepsilon_x = 10e-8\nepsilon_P = 10e-8\nq = [[500, 2000], [500, 250]]\n\nx_0 = np.array([10 * i for i in range(1, N + 1)] + [0] * N)\nx_T = x_0 * 10 if N == 2 else np.array([(10 * i) ** 3 for i in range(1, N + 1)] + [0] * N)\nT_f = 25\n\nmasses_with_springs = MassesWithSpringsComparison(N=N,\n                                                  m=m,\n                                                  k=k,\n                                                  q=q,\n                                                  r=r,\n                                                  x_0=x_0,\n                                                  x_T=x_T,\n                                                  T_f=T_f,\n                                                  epsilon_x=epsilon_x,\n                                                  epsilon_P=epsilon_P)\n```\n\nConsider the constructor of the class `MassesWithSpringsComparison`:\n\n```python\nimport numpy as np\nfrom typing import Sequence, Optional\n\nfrom PyDiffGame.PyDiffGame import PyDiffGame\nfrom PyDiffGame.PyDiffGameLQRComparison import PyDiffGameLQRComparison\nfrom PyDiffGame.Objective import GameObjective, LQRObjective\n\n\nclass MassesWithSpringsComparison(PyDiffGameLQRComparison):\n    def __init__(self,\n                 N: int,\n                 m: float,\n                 k: float,\n                 q: float | Sequence[float] | Sequence[Sequence[float]],\n                 r: float,\n                 Ms: Optional[Sequence[np.array]] = None,\n                 x_0: Optional[np.array] = None,\n                 x_T: Optional[np.array] = None,\n                 T_f: Optional[float] = None,\n                 epsilon_x: Optional[float] = PyDiffGame.epsilon_x_default,\n                 epsilon_P: Optional[float] = PyDiffGame.epsilon_P_default,\n                 L: Optional[int] = PyDiffGame.L_default,\n                 eta: Optional[int] = PyDiffGame.eta_default):\n        I_N = np.eye(N)\n        Z_N = np.zeros((N, N))\n\n        M_masses = m * I_N\n        K = k * (2 * I_N - np.array([[int(abs(i - j) == 1) for j in range(N)] for i in range(N)]))\n        M_masses_inv = np.linalg.inv(M_masses)\n\n        M_inv_K = M_masses_inv @ K\n\n        if Ms is None:\n            eigenvectors = np.linalg.eig(M_inv_K)[1]\n            Ms = [eigenvector.reshape(1, N) for eigenvector in eigenvectors]\n\n        A = np.block([[Z_N, I_N],\n                      [-M_inv_K, Z_N]])\n        B = np.block([[Z_N],\n                      [M_masses_inv]])\n\n        Qs = [np.diag([0.0] * i + [q] + [0.0] * (N - 1) + [q] + [0.0] * (N - i - 1))\n              if isinstance(q, (int, float)) else\n              np.diag([0.0] * i + [q[i]] + [0.0] * (N - 1) + [q[i]] + [0.0] * (N - i - 1)) for i in range(N)]\n\n        M = np.concatenate(Ms,\n                           axis=0)\n\n        assert np.all(np.abs(np.linalg.inv(M) - M.T) \u003c 10e-12)\n\n        Q_mat = np.kron(a=np.eye(2),\n                        b=M)\n\n        Qs = [Q_mat.T @ Q @ Q_mat for Q in Qs]\n\n        Rs = [np.array([r])] * N\n        R_lqr = 1 / 4 * r * I_N\n        Q_lqr = q * np.eye(2 * N) if isinstance(q, (int, float)) else np.diag(2 * q)\n\n        state_variables_names = ['x_{' + str(i) + '}' for i in range(1, N + 1)] + \\\n                                ['\\\\dot{x}_{' + str(i) + '}' for i in range(1, N + 1)]\n        args = {'A': A,\n                'B': B,\n                'x_0': x_0,\n                'x_T': x_T,\n                'T_f': T_f,\n                'state_variables_names': state_variables_names,\n                'epsilon_x': epsilon_x,\n                'epsilon_P': epsilon_P,\n                'L': L,\n                'eta': eta}\n\n        lqr_objective = [LQRObjective(Q=Q_lqr,\n                                      R_ii=R_lqr)]\n        game_objectives = [GameObjective(Q=Q,\n                                         R_ii=R,\n                                         M_i=M_i) for Q, R, M_i in zip(Qs, Rs, Ms)]\n        games_objectives = [lqr_objective,\n                            game_objectives]\n\n        super().__init__(args=args,\n                         M=M,\n                         games_objectives=games_objectives,\n                         continuous=True)\n```\n\nFinally, consider calling the `masses_with_springs` object as follows:\n\n```python\noutput_variables_names = ['$\\\\frac{x_1 + x_2}{\\\\sqrt{2}}$',\n                          '$\\\\frac{x_2 - x_1}{\\\\sqrt{2}}$',\n                          '$\\\\frac{\\\\dot{x}_1 + \\\\dot{x}_2}{\\\\sqrt{2}}$',\n                          '$\\\\frac{\\\\dot{x}_2 - \\\\dot{x}_1}{\\\\sqrt{2}}$']\n\nmasses_with_springs(plot_state_spaces=True,\n                    plot_Mx=True,\n                    output_variables_names=output_variables_names,\n                    save_figure=True)\n```\n\nThis will result in the following plot that compares the two systems performance for a differential game vs an LQR:\n\n\u003cp align=\"center\"\u003e\n    \u003cimg align=top src=\"https://raw.githubusercontent.com/krichelj/PyDiffGame/master/src/PyDiffGame/examples/figures/2/2-players_large_1.png\" width=\"400\" height=\"300\"/\u003e\n    \u003cimg align=top src=\"https://raw.githubusercontent.com/krichelj/PyDiffGame/master/src/PyDiffGame/examples/figures/2/LQR_large_1.png\" width=\"400\" height=\"300\"/\u003e\n\u003c/p\u003e\n\n\nAnd when tweaking the weights by setting\n\n```python\nqs = [[500, 5000]]\n```\n\nwe have: \n\n\u003cp align=\"center\"\u003e\n    \u003cimg align=top src=\"https://raw.githubusercontent.com/krichelj/PyDiffGame/master/src/PyDiffGame/examples/figures/2/2-players_large_2.png\" width=\"400\" height=\"300\"/\u003e\n    \u003cimg align=top src=\"https://raw.githubusercontent.com/krichelj/PyDiffGame/master/src/PyDiffGame/examples/figures/2/LQR_large_2.png\" width=\"400\" height=\"300\"/\u003e\n\u003c/p\u003e\n\n# Authors\n\nIf you use this work, please cite our paper:\n```\n@inproceedings{pydiffgame_paper,\n    author={Kricheli, Joshua Shay and Sadon, Aviran and Arogeti, Shai and Regev, Shimon and Weiss, Gera},\n    booktitle={29th Mediterranean Conference on Control and Automation (MED 2021)},\n    title={{Composition of Dynamic Control Objectives Based on Differential Games}},\n    year={2021},\n    volume={},\n    number={},\n    pages={298-304},\n    doi={10.1109/MED51440.2021.9480269}}\n```\n\nFurther details can be found in the [citation document](CITATIONS.bib).\n\n# Acknowledgments\n\nThis 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.\nThis research was also supported by The '_Israeli Smart Transportation Research Center_' ('ISTRC') by The Technion and Bar-Ilan Universities, Israel.\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://istrc.net.technion.ac.il/\"\u003e\n\u003cimg src=\"https://github.com/krichelj/PyDiffGame/blob/master/images/Logo_ISTRC_Green_English.png?raw=true\" width=\"233\"  alt=\"\"/\u003e\n\u003c/a\u003e\n\u0026emsp;\n\u003ca href=\"https://in.bgu.ac.il/en/Pages/default.aspx\"\u003e\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTZ8GbtJiX8lNUygX7-inRBuWESK438jWbRjQ\u0026s\"\u003e\n  \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://tamrur.bgu.ac.il/restore/BGU.sig.png\"\u003e\n  \u003cimg alt=\"BGU Logo\" src=\"https://tamrur.bgu.ac.il/restore/BGU.sig.png\" width=\"233\" class=\"bgu-logo-dark\"\u003e\n\u003c/picture\u003e\n\u003c/a\u003e\n\u0026emsp;\n\u0026emsp;\n\u003ca href=\"https://helmsleytrust.org/\"\u003e\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://helmsleytrust.org/wp-content/themes/helmsley/assets/img/helmsley-charitable-trust-logo-white.png\"\u003e\n  \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://helmsleytrust.org/wp-content/themes/helmsley/assets/img/helmsley-charitable-trust-logo-blue.png\"\u003e\n  \u003cimg alt=\"Helmsley Charitable Trust\" src=\"https://helmsleytrust.org/wp-content/themes/helmsley/assets/img/helmsley-charitable-trust-logo-blue.png\" width=\"233\"\u003e\n\u003c/picture\u003e\n\u003c/a\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkrichelj%2Fpydiffgame","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkrichelj%2Fpydiffgame","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkrichelj%2Fpydiffgame/lists"}