PyDiffGame 2.0.0__tar.gz → 2.0.1__tar.gz
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-2.0.1/.github/workflows/python-publish.yml +132 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/.github/workflows/tests.yml +2 -2
- pydiffgame-2.0.1/CLAUDE.md +32 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/CONTRIBUTING.md +23 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/PKG-INFO +29 -16
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/README.md +28 -15
- pydiffgame-2.0.1/docs/README.md +362 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/pyproject.toml +1 -1
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/__init__.py +1 -1
- pydiffgame-2.0.1/tools/bump_version.py +78 -0
- pydiffgame-2.0.0/.github/workflows/python-publish.yml +0 -71
- pydiffgame-2.0.0/docs/README.md +0 -266
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/.gitignore +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/.pre-commit-config.yaml +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/CITATIONS.bib +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/CODE_OF_CONDUCT.md +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/LICENSE +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/_config.yml +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/images/Logo_ISTRC_Green_English.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/images/logo.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/images/logo_abc.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/images/readme/masses_cost.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/images/readme/masses_game_vs_lqr.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/images/readme/masses_schematic.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/requirements.txt +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/_typing.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/base.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/comparison.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/continuous.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/discrete.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/InvertedPendulumComparison.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/MassesWithSpringsComparison.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/PVTOL.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/PVTOLComparison.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/QuadRotorControl.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/2/2-players_large_1.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/2/2-players_large_2.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/2/LQR_large_1.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/2/LQR_large_2.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/2/two_masses_tikz.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/4/4-players_large_1.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/4/4-players_large_2.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/4/LQR_large_1.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/4/LQR_large_2.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/8/8-players_large_1.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/8/8-players_large_2.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/8/LQR_large_1.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/8/LQR_large_2.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/PVTOL/PVTOL1.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/PVTOL/PVTOL10.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/PVTOL/PVTOL100.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/PVTOL/PVTOL1000.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/PVTOL0001.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/PVTOL001.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/PVTOL01.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/PVTOL1.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/lqr.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/objective.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/plotting.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/tests/conftest.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/tests/test_discrete.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/tests/test_examples.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/tests/test_game.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/tests/test_lqr.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/tests/test_objective.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/tests/test_simulation.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/tools/generate_readme_figures.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.1}/uv.lock +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Publishes the package to PyPI and creates the matching GitHub Release,
|
|
2
|
+
# auto-incrementing the version each run.
|
|
3
|
+
#
|
|
4
|
+
# Flow (just run it — Actions -> Upload Python Package -> Run workflow):
|
|
5
|
+
# 1. bump-version : increment the version (carry-at-9 via tools/bump_version.py),
|
|
6
|
+
# commit it back to master.
|
|
7
|
+
# 2. release-build : build the dists from the bumped master.
|
|
8
|
+
# 3. pypi-publish : upload to PyPI via Trusted Publishing (OIDC, no tokens).
|
|
9
|
+
# 4. github-release: create the v<version> GitHub Release with notes + dists.
|
|
10
|
+
#
|
|
11
|
+
# Versions roll over at 9: 2.0.9 -> 2.1.0, 2.9.9 -> 3.0.0.
|
|
12
|
+
|
|
13
|
+
name: Upload Python Package
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
workflow_dispatch:
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
bump-version:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
permissions:
|
|
25
|
+
contents: write
|
|
26
|
+
outputs:
|
|
27
|
+
version: ${{ steps.bump.outputs.version }}
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v5
|
|
30
|
+
with:
|
|
31
|
+
ref: master
|
|
32
|
+
|
|
33
|
+
- name: Increment the version (carry-at-9)
|
|
34
|
+
id: bump
|
|
35
|
+
run: |
|
|
36
|
+
new="$(python3 tools/bump_version.py)"
|
|
37
|
+
echo "version=$new" >> "$GITHUB_OUTPUT"
|
|
38
|
+
echo "Bumped to $new"
|
|
39
|
+
|
|
40
|
+
- name: Commit and push the bump
|
|
41
|
+
run: |
|
|
42
|
+
git config user.name "github-actions[bot]"
|
|
43
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
44
|
+
git add pyproject.toml src/PyDiffGame/__init__.py
|
|
45
|
+
git commit -m "chore: bump version to ${{ steps.bump.outputs.version }} [skip ci]"
|
|
46
|
+
git push origin HEAD:master
|
|
47
|
+
|
|
48
|
+
release-build:
|
|
49
|
+
needs: bump-version
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/checkout@v5
|
|
53
|
+
with:
|
|
54
|
+
ref: master # the bumped commit
|
|
55
|
+
|
|
56
|
+
- name: Install uv
|
|
57
|
+
uses: astral-sh/setup-uv@v6
|
|
58
|
+
|
|
59
|
+
- name: Build release distributions
|
|
60
|
+
run: uv build
|
|
61
|
+
|
|
62
|
+
- name: Upload distributions
|
|
63
|
+
uses: actions/upload-artifact@v5
|
|
64
|
+
with:
|
|
65
|
+
name: release-dists
|
|
66
|
+
path: dist/
|
|
67
|
+
|
|
68
|
+
pypi-publish:
|
|
69
|
+
needs:
|
|
70
|
+
- release-build
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
permissions:
|
|
73
|
+
# IMPORTANT: this permission is mandatory for trusted publishing
|
|
74
|
+
id-token: write
|
|
75
|
+
|
|
76
|
+
environment:
|
|
77
|
+
name: pypi
|
|
78
|
+
url: https://pypi.org/project/PyDiffGame/
|
|
79
|
+
|
|
80
|
+
steps:
|
|
81
|
+
- name: Retrieve release distributions
|
|
82
|
+
uses: actions/download-artifact@v5
|
|
83
|
+
with:
|
|
84
|
+
name: release-dists
|
|
85
|
+
path: dist/
|
|
86
|
+
|
|
87
|
+
# Authentication is via PyPI Trusted Publishing (OIDC) — no token needed.
|
|
88
|
+
# Trusted publisher on PyPI: owner krichelj, repo PyDiffGame,
|
|
89
|
+
# workflow python-publish.yml, environment pypi.
|
|
90
|
+
- name: Publish release distributions to PyPI
|
|
91
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
92
|
+
with:
|
|
93
|
+
packages-dir: dist/
|
|
94
|
+
# Don't fail if this version is already on PyPI (idempotent re-runs).
|
|
95
|
+
skip-existing: true
|
|
96
|
+
|
|
97
|
+
github-release:
|
|
98
|
+
needs:
|
|
99
|
+
- bump-version
|
|
100
|
+
- pypi-publish
|
|
101
|
+
runs-on: ubuntu-latest
|
|
102
|
+
permissions:
|
|
103
|
+
# Needed to create the release and its tag.
|
|
104
|
+
contents: write
|
|
105
|
+
|
|
106
|
+
steps:
|
|
107
|
+
- uses: actions/checkout@v5
|
|
108
|
+
with:
|
|
109
|
+
ref: master
|
|
110
|
+
|
|
111
|
+
- name: Retrieve release distributions
|
|
112
|
+
uses: actions/download-artifact@v5
|
|
113
|
+
with:
|
|
114
|
+
name: release-dists
|
|
115
|
+
path: dist/
|
|
116
|
+
|
|
117
|
+
- name: Create or update the GitHub Release
|
|
118
|
+
env:
|
|
119
|
+
GH_TOKEN: ${{ github.token }}
|
|
120
|
+
VERSION: ${{ needs.bump-version.outputs.version }}
|
|
121
|
+
run: |
|
|
122
|
+
tag="v${VERSION}"
|
|
123
|
+
echo "Releasing ${tag}"
|
|
124
|
+
if gh release view "$tag" >/dev/null 2>&1; then
|
|
125
|
+
echo "Release ${tag} already exists - refreshing its assets."
|
|
126
|
+
gh release upload "$tag" dist/* --clobber
|
|
127
|
+
else
|
|
128
|
+
gh release create "$tag" dist/* \
|
|
129
|
+
--title "$tag" \
|
|
130
|
+
--generate-notes \
|
|
131
|
+
--target master
|
|
132
|
+
fi
|
|
@@ -23,10 +23,10 @@ jobs:
|
|
|
23
23
|
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
24
24
|
|
|
25
25
|
steps:
|
|
26
|
-
- uses: actions/checkout@
|
|
26
|
+
- uses: actions/checkout@v5
|
|
27
27
|
|
|
28
28
|
- name: Install uv
|
|
29
|
-
uses: astral-sh/setup-uv@
|
|
29
|
+
uses: astral-sh/setup-uv@v6
|
|
30
30
|
with:
|
|
31
31
|
enable-cache: true
|
|
32
32
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# PyDiffGame — repository guide for Claude
|
|
2
|
+
|
|
3
|
+
## Python tooling (uv-first)
|
|
4
|
+
- Use **uv** for everything; pip is only a documented fallback.
|
|
5
|
+
- `uv sync --extra dev`, `uv run pytest`, `uv run ruff check`, `uv run ruff format`,
|
|
6
|
+
`uv run mypy src/PyDiffGame`, `uv build`.
|
|
7
|
+
- Keep the quality gates green before committing: ruff format, ruff check, mypy on
|
|
8
|
+
`src/PyDiffGame`, and the pytest suite — all via `uv run`.
|
|
9
|
+
|
|
10
|
+
## Versioning (carry-at-9)
|
|
11
|
+
- The version is `X.Y.Z` with single-digit components that roll over at 9:
|
|
12
|
+
`2.0.9 -> 2.1.0`, `2.9.9 -> 3.0.0` (the major keeps growing).
|
|
13
|
+
- Increment **only** via `tools/bump_version.py`, which updates the version in both
|
|
14
|
+
`pyproject.toml` and `src/PyDiffGame/__init__.py`. Never hand-edit version strings.
|
|
15
|
+
- The version is bumped **automatically** by the publish workflow on each release, so
|
|
16
|
+
do not bump it in ordinary PRs — the release run does it.
|
|
17
|
+
|
|
18
|
+
## Releasing
|
|
19
|
+
- `Actions -> Upload Python Package -> Run workflow` (on `master`). The workflow
|
|
20
|
+
auto-increments the version, commits it to `master`, builds with `uv build`,
|
|
21
|
+
publishes to PyPI via Trusted Publishing (OIDC, no tokens), and creates the matching
|
|
22
|
+
`v<version>` GitHub Release with notes and the built dists attached. It is idempotent
|
|
23
|
+
(`skip-existing`).
|
|
24
|
+
|
|
25
|
+
## Docs
|
|
26
|
+
- `README.md` is the single canonical readme and is also the PyPI long-description
|
|
27
|
+
(`pyproject.toml: readme = "README.md"`), so its image/file links must be **absolute**
|
|
28
|
+
(`raw.githubusercontent.com/.../master/...` for images, `github.com/.../blob/master/...`
|
|
29
|
+
for files) so they render on PyPI.
|
|
30
|
+
- Keep `docs/README.md` identical to `README.md`.
|
|
31
|
+
- README figures are generated from the live solver:
|
|
32
|
+
`uv run python tools/generate_readme_figures.py`.
|
|
@@ -39,6 +39,29 @@ Continuous integration runs the formatter check, the linter, the type checker
|
|
|
39
39
|
and the full suite (all via uv) across Python 3.11–3.14, so please make sure
|
|
40
40
|
they pass locally.
|
|
41
41
|
|
|
42
|
+
## Releasing
|
|
43
|
+
|
|
44
|
+
Publishing a new version is a single automated step — just run the publish
|
|
45
|
+
workflow: **Actions -> Upload Python Package -> Run workflow** (on `master`).
|
|
46
|
+
|
|
47
|
+
The run automatically:
|
|
48
|
+
|
|
49
|
+
1. **Increments the version** with `tools/bump_version.py`, which rolls each
|
|
50
|
+
component over at 9 (`2.0.9 -> 2.1.0`, `2.9.9 -> 3.0.0`), updating both
|
|
51
|
+
`pyproject.toml` and `src/PyDiffGame/__init__.py`, and commits the bump to
|
|
52
|
+
`master`.
|
|
53
|
+
2. Builds the distributions with `uv build`.
|
|
54
|
+
3. Uploads them to PyPI via
|
|
55
|
+
[Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (OIDC — no
|
|
56
|
+
tokens or secrets).
|
|
57
|
+
4. Creates a `v<version>` GitHub Release with auto-generated notes and the built
|
|
58
|
+
wheels/sdist attached.
|
|
59
|
+
|
|
60
|
+
You normally never edit the version by hand. To bump it locally (e.g. to test),
|
|
61
|
+
run `uv run python tools/bump_version.py` (`--dry-run` to preview, `--current`
|
|
62
|
+
to print the current version). The PyPI upload is idempotent (`skip-existing`),
|
|
63
|
+
so re-running the workflow is safe.
|
|
64
|
+
|
|
42
65
|
Thank you for your contribution!
|
|
43
66
|
|
|
44
67
|
Joshua Shay Kricheli
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyDiffGame
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Summary: Nash-equilibrium solutions to linear-quadratic differential games, via a reduction of the Game Hamilton-Jacobi-Bellman equations to coupled algebraic and differential Riccati equations for multi-objective dynamical control systems.
|
|
5
5
|
Project-URL: Homepage, https://krichelj.github.io/PyDiffGame/
|
|
6
6
|
Project-URL: Repository, https://github.com/krichelj/PyDiffGame
|
|
@@ -58,7 +58,7 @@ Requires-Dist: control>=0.10; extra == 'examples'
|
|
|
58
58
|
Description-Content-Type: text/markdown
|
|
59
59
|
|
|
60
60
|
<p align="center">
|
|
61
|
-
<img alt="PyDiffGame logo" src="images/logo.png" width="420"/>
|
|
61
|
+
<img alt="PyDiffGame logo" src="https://raw.githubusercontent.com/krichelj/PyDiffGame/master/images/logo.png" width="420"/>
|
|
62
62
|
</p>
|
|
63
63
|
|
|
64
64
|
<p align="center">
|
|
@@ -127,22 +127,32 @@ accounting on a common yardstick, and ready-made plotting.
|
|
|
127
127
|
|
|
128
128
|
# Installation
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
PyDiffGame is published on [PyPI](https://pypi.org/project/PyDiffGame/) and is
|
|
131
|
+
managed with [**uv**](https://docs.astral.sh/uv/). Add it to your project:
|
|
131
132
|
|
|
132
133
|
```bash
|
|
133
|
-
|
|
134
|
+
uv add PyDiffGame
|
|
134
135
|
```
|
|
135
136
|
|
|
136
137
|
To run the bundled examples (which additionally need
|
|
137
138
|
[`python-control`](https://python-control.readthedocs.io/)):
|
|
138
139
|
|
|
139
140
|
```bash
|
|
140
|
-
|
|
141
|
+
uv add "PyDiffGame[examples]"
|
|
141
142
|
```
|
|
142
143
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
<details>
|
|
145
|
+
<summary><b>Prefer pip?</b> It works as a fallback.</summary>
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
pip install PyDiffGame
|
|
149
|
+
pip install "PyDiffGame[examples]" # with the examples extra
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
</details>
|
|
153
|
+
|
|
154
|
+
To work on the package itself, clone it and sync the locked development
|
|
155
|
+
environment with uv:
|
|
146
156
|
|
|
147
157
|
```bash
|
|
148
158
|
git clone https://github.com/krichelj/PyDiffGame.git
|
|
@@ -151,7 +161,10 @@ uv sync --extra dev # creates .venv with the exact locked dependencies
|
|
|
151
161
|
uv run pre-commit install # enable the formatting / lint / type-check hooks
|
|
152
162
|
```
|
|
153
163
|
|
|
154
|
-
Then run anything through `uv run` (`uv run pytest`,
|
|
164
|
+
Then run anything through `uv run` (`uv run pytest`,
|
|
165
|
+
`uv run python -m PyDiffGame.examples.MassesWithSpringsComparison`, …). Pip users
|
|
166
|
+
can instead `pip install -e ".[dev]"`, though uv is recommended for the exact
|
|
167
|
+
locked environment.
|
|
155
168
|
|
|
156
169
|
# Quick start
|
|
157
170
|
|
|
@@ -242,7 +255,7 @@ To show the package in action we compare a differential game against an LQR on a
|
|
|
242
255
|
masses connected by springs — a textbook coupled, oscillatory system:
|
|
243
256
|
|
|
244
257
|
<p align="center">
|
|
245
|
-
<img alt="Two masses connected by springs between two walls" src="images/readme/masses_schematic.png" width="760"/>
|
|
258
|
+
<img alt="Two masses connected by springs between two walls" src="https://raw.githubusercontent.com/krichelj/PyDiffGame/master/images/readme/masses_schematic.png" width="760"/>
|
|
246
259
|
</p>
|
|
247
260
|
|
|
248
261
|
The physical input space is decomposed along the **modal** directions of $M^{-1}K$, so each
|
|
@@ -304,11 +317,11 @@ monolithic optimum **to numerical precision**: the two state trajectories coinci
|
|
|
304
317
|
(they differ by ~10⁻⁷) and the costs are equal:
|
|
305
318
|
|
|
306
319
|
<p align="center">
|
|
307
|
-
<img alt="State trajectories: the decomposed game reproduces the monolithic LQR" src="images/readme/masses_game_vs_lqr.png" width="860"/>
|
|
320
|
+
<img alt="State trajectories: the decomposed game reproduces the monolithic LQR" src="https://raw.githubusercontent.com/krichelj/PyDiffGame/master/images/readme/masses_game_vs_lqr.png" width="860"/>
|
|
308
321
|
</p>
|
|
309
322
|
|
|
310
323
|
<p align="center">
|
|
311
|
-
<img alt="Cost comparison: the modal game recovers the LQR optimum" src="images/readme/masses_cost.png" width="440"/>
|
|
324
|
+
<img alt="Cost comparison: the modal game recovers the LQR optimum" src="https://raw.githubusercontent.com/krichelj/PyDiffGame/master/images/readme/masses_cost.png" width="440"/>
|
|
312
325
|
</p>
|
|
313
326
|
|
|
314
327
|
For this modally-decoupled system the decomposition is **lossless** — and it buys
|
|
@@ -316,7 +329,7 @@ For this modally-decoupled system the decomposition is **lossless** — and it b
|
|
|
316
329
|
player, without re-tuning one monolithic cost matrix.
|
|
317
330
|
|
|
318
331
|
> The figures above are regenerated from the live solver by
|
|
319
|
-
> [`tools/generate_readme_figures.py`](tools/generate_readme_figures.py)
|
|
332
|
+
> [`tools/generate_readme_figures.py`](https://github.com/krichelj/PyDiffGame/blob/master/tools/generate_readme_figures.py)
|
|
320
333
|
> (`uv run python tools/generate_readme_figures.py`), so they always match the current code.
|
|
321
334
|
|
|
322
335
|
# More examples
|
|
@@ -347,7 +360,7 @@ uv run mypy src/PyDiffGame # type-check
|
|
|
347
360
|
```
|
|
348
361
|
|
|
349
362
|
Continuous integration runs the formatter check, linter, type checker and full suite on
|
|
350
|
-
Python 3.11–3.14. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
363
|
+
Python 3.11–3.14. See [CONTRIBUTING.md](https://github.com/krichelj/PyDiffGame/blob/master/CONTRIBUTING.md) for details.
|
|
351
364
|
|
|
352
365
|
# Citing
|
|
353
366
|
|
|
@@ -363,7 +376,7 @@ If you use this work, please cite our paper:
|
|
|
363
376
|
doi={10.1109/MED51440.2021.9480269}}
|
|
364
377
|
```
|
|
365
378
|
|
|
366
|
-
Further details can be found in the [citation document](CITATIONS.bib).
|
|
379
|
+
Further details can be found in the [citation document](https://github.com/krichelj/PyDiffGame/blob/master/CITATIONS.bib).
|
|
367
380
|
|
|
368
381
|
# Acknowledgments
|
|
369
382
|
|
|
@@ -375,7 +388,7 @@ and Bar-Ilan Universities, Israel.
|
|
|
375
388
|
|
|
376
389
|
<p align="center">
|
|
377
390
|
<a href="https://istrc.net.technion.ac.il/">
|
|
378
|
-
<img src="images/Logo_ISTRC_Green_English.png" height="80" alt="ISTRC"/>
|
|
391
|
+
<img src="https://raw.githubusercontent.com/krichelj/PyDiffGame/master/images/Logo_ISTRC_Green_English.png" height="80" alt="ISTRC"/>
|
|
379
392
|
</a>
|
|
380
393
|
 
|
|
381
394
|
<a href="https://in.bgu.ac.il/en/Pages/default.aspx">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img alt="PyDiffGame logo" src="images/logo.png" width="420"/>
|
|
2
|
+
<img alt="PyDiffGame logo" src="https://raw.githubusercontent.com/krichelj/PyDiffGame/master/images/logo.png" width="420"/>
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
@@ -68,22 +68,32 @@ accounting on a common yardstick, and ready-made plotting.
|
|
|
68
68
|
|
|
69
69
|
# Installation
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
PyDiffGame is published on [PyPI](https://pypi.org/project/PyDiffGame/) and is
|
|
72
|
+
managed with [**uv**](https://docs.astral.sh/uv/). Add it to your project:
|
|
72
73
|
|
|
73
74
|
```bash
|
|
74
|
-
|
|
75
|
+
uv add PyDiffGame
|
|
75
76
|
```
|
|
76
77
|
|
|
77
78
|
To run the bundled examples (which additionally need
|
|
78
79
|
[`python-control`](https://python-control.readthedocs.io/)):
|
|
79
80
|
|
|
80
81
|
```bash
|
|
81
|
-
|
|
82
|
+
uv add "PyDiffGame[examples]"
|
|
82
83
|
```
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
<details>
|
|
86
|
+
<summary><b>Prefer pip?</b> It works as a fallback.</summary>
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pip install PyDiffGame
|
|
90
|
+
pip install "PyDiffGame[examples]" # with the examples extra
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
</details>
|
|
94
|
+
|
|
95
|
+
To work on the package itself, clone it and sync the locked development
|
|
96
|
+
environment with uv:
|
|
87
97
|
|
|
88
98
|
```bash
|
|
89
99
|
git clone https://github.com/krichelj/PyDiffGame.git
|
|
@@ -92,7 +102,10 @@ uv sync --extra dev # creates .venv with the exact locked dependencies
|
|
|
92
102
|
uv run pre-commit install # enable the formatting / lint / type-check hooks
|
|
93
103
|
```
|
|
94
104
|
|
|
95
|
-
Then run anything through `uv run` (`uv run pytest`,
|
|
105
|
+
Then run anything through `uv run` (`uv run pytest`,
|
|
106
|
+
`uv run python -m PyDiffGame.examples.MassesWithSpringsComparison`, …). Pip users
|
|
107
|
+
can instead `pip install -e ".[dev]"`, though uv is recommended for the exact
|
|
108
|
+
locked environment.
|
|
96
109
|
|
|
97
110
|
# Quick start
|
|
98
111
|
|
|
@@ -183,7 +196,7 @@ To show the package in action we compare a differential game against an LQR on a
|
|
|
183
196
|
masses connected by springs — a textbook coupled, oscillatory system:
|
|
184
197
|
|
|
185
198
|
<p align="center">
|
|
186
|
-
<img alt="Two masses connected by springs between two walls" src="images/readme/masses_schematic.png" width="760"/>
|
|
199
|
+
<img alt="Two masses connected by springs between two walls" src="https://raw.githubusercontent.com/krichelj/PyDiffGame/master/images/readme/masses_schematic.png" width="760"/>
|
|
187
200
|
</p>
|
|
188
201
|
|
|
189
202
|
The physical input space is decomposed along the **modal** directions of $M^{-1}K$, so each
|
|
@@ -245,11 +258,11 @@ monolithic optimum **to numerical precision**: the two state trajectories coinci
|
|
|
245
258
|
(they differ by ~10⁻⁷) and the costs are equal:
|
|
246
259
|
|
|
247
260
|
<p align="center">
|
|
248
|
-
<img alt="State trajectories: the decomposed game reproduces the monolithic LQR" src="images/readme/masses_game_vs_lqr.png" width="860"/>
|
|
261
|
+
<img alt="State trajectories: the decomposed game reproduces the monolithic LQR" src="https://raw.githubusercontent.com/krichelj/PyDiffGame/master/images/readme/masses_game_vs_lqr.png" width="860"/>
|
|
249
262
|
</p>
|
|
250
263
|
|
|
251
264
|
<p align="center">
|
|
252
|
-
<img alt="Cost comparison: the modal game recovers the LQR optimum" src="images/readme/masses_cost.png" width="440"/>
|
|
265
|
+
<img alt="Cost comparison: the modal game recovers the LQR optimum" src="https://raw.githubusercontent.com/krichelj/PyDiffGame/master/images/readme/masses_cost.png" width="440"/>
|
|
253
266
|
</p>
|
|
254
267
|
|
|
255
268
|
For this modally-decoupled system the decomposition is **lossless** — and it buys
|
|
@@ -257,7 +270,7 @@ For this modally-decoupled system the decomposition is **lossless** — and it b
|
|
|
257
270
|
player, without re-tuning one monolithic cost matrix.
|
|
258
271
|
|
|
259
272
|
> The figures above are regenerated from the live solver by
|
|
260
|
-
> [`tools/generate_readme_figures.py`](tools/generate_readme_figures.py)
|
|
273
|
+
> [`tools/generate_readme_figures.py`](https://github.com/krichelj/PyDiffGame/blob/master/tools/generate_readme_figures.py)
|
|
261
274
|
> (`uv run python tools/generate_readme_figures.py`), so they always match the current code.
|
|
262
275
|
|
|
263
276
|
# More examples
|
|
@@ -288,7 +301,7 @@ uv run mypy src/PyDiffGame # type-check
|
|
|
288
301
|
```
|
|
289
302
|
|
|
290
303
|
Continuous integration runs the formatter check, linter, type checker and full suite on
|
|
291
|
-
Python 3.11–3.14. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
304
|
+
Python 3.11–3.14. See [CONTRIBUTING.md](https://github.com/krichelj/PyDiffGame/blob/master/CONTRIBUTING.md) for details.
|
|
292
305
|
|
|
293
306
|
# Citing
|
|
294
307
|
|
|
@@ -304,7 +317,7 @@ If you use this work, please cite our paper:
|
|
|
304
317
|
doi={10.1109/MED51440.2021.9480269}}
|
|
305
318
|
```
|
|
306
319
|
|
|
307
|
-
Further details can be found in the [citation document](CITATIONS.bib).
|
|
320
|
+
Further details can be found in the [citation document](https://github.com/krichelj/PyDiffGame/blob/master/CITATIONS.bib).
|
|
308
321
|
|
|
309
322
|
# Acknowledgments
|
|
310
323
|
|
|
@@ -316,7 +329,7 @@ and Bar-Ilan Universities, Israel.
|
|
|
316
329
|
|
|
317
330
|
<p align="center">
|
|
318
331
|
<a href="https://istrc.net.technion.ac.il/">
|
|
319
|
-
<img src="images/Logo_ISTRC_Green_English.png" height="80" alt="ISTRC"/>
|
|
332
|
+
<img src="https://raw.githubusercontent.com/krichelj/PyDiffGame/master/images/Logo_ISTRC_Green_English.png" height="80" alt="ISTRC"/>
|
|
320
333
|
</a>
|
|
321
334
|
 
|
|
322
335
|
<a href="https://in.bgu.ac.il/en/Pages/default.aspx">
|