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.
Files changed (68) hide show
  1. pydiffgame-2.0.1/.github/workflows/python-publish.yml +132 -0
  2. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/.github/workflows/tests.yml +2 -2
  3. pydiffgame-2.0.1/CLAUDE.md +32 -0
  4. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/CONTRIBUTING.md +23 -0
  5. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/PKG-INFO +29 -16
  6. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/README.md +28 -15
  7. pydiffgame-2.0.1/docs/README.md +362 -0
  8. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/pyproject.toml +1 -1
  9. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/__init__.py +1 -1
  10. pydiffgame-2.0.1/tools/bump_version.py +78 -0
  11. pydiffgame-2.0.0/.github/workflows/python-publish.yml +0 -71
  12. pydiffgame-2.0.0/docs/README.md +0 -266
  13. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/.gitignore +0 -0
  14. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/.pre-commit-config.yaml +0 -0
  15. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/CITATIONS.bib +0 -0
  16. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/CODE_OF_CONDUCT.md +0 -0
  17. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/LICENSE +0 -0
  18. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/_config.yml +0 -0
  19. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/images/Logo_ISTRC_Green_English.png +0 -0
  20. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/images/logo.png +0 -0
  21. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/images/logo_abc.png +0 -0
  22. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/images/readme/masses_cost.png +0 -0
  23. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/images/readme/masses_game_vs_lqr.png +0 -0
  24. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/images/readme/masses_schematic.png +0 -0
  25. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/requirements.txt +0 -0
  26. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/_typing.py +0 -0
  27. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/base.py +0 -0
  28. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/comparison.py +0 -0
  29. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/continuous.py +0 -0
  30. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/discrete.py +0 -0
  31. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/InvertedPendulumComparison.py +0 -0
  32. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/MassesWithSpringsComparison.py +0 -0
  33. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/PVTOL.py +0 -0
  34. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/PVTOLComparison.py +0 -0
  35. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/QuadRotorControl.py +0 -0
  36. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/2/2-players_large_1.png +0 -0
  37. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/2/2-players_large_2.png +0 -0
  38. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/2/LQR_large_1.png +0 -0
  39. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/2/LQR_large_2.png +0 -0
  40. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/2/two_masses_tikz.png +0 -0
  41. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/4/4-players_large_1.png +0 -0
  42. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/4/4-players_large_2.png +0 -0
  43. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/4/LQR_large_1.png +0 -0
  44. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/4/LQR_large_2.png +0 -0
  45. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/8/8-players_large_1.png +0 -0
  46. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/8/8-players_large_2.png +0 -0
  47. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/8/LQR_large_1.png +0 -0
  48. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/8/LQR_large_2.png +0 -0
  49. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/PVTOL/PVTOL1.png +0 -0
  50. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/PVTOL/PVTOL10.png +0 -0
  51. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/PVTOL/PVTOL100.png +0 -0
  52. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/PVTOL/PVTOL1000.png +0 -0
  53. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/PVTOL0001.png +0 -0
  54. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/PVTOL001.png +0 -0
  55. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/PVTOL01.png +0 -0
  56. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/examples/figures/PVTOL1.png +0 -0
  57. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/lqr.py +0 -0
  58. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/objective.py +0 -0
  59. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/src/PyDiffGame/plotting.py +0 -0
  60. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/tests/conftest.py +0 -0
  61. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/tests/test_discrete.py +0 -0
  62. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/tests/test_examples.py +0 -0
  63. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/tests/test_game.py +0 -0
  64. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/tests/test_lqr.py +0 -0
  65. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/tests/test_objective.py +0 -0
  66. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/tests/test_simulation.py +0 -0
  67. {pydiffgame-2.0.0 → pydiffgame-2.0.1}/tools/generate_readme_figures.py +0 -0
  68. {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@v4
26
+ - uses: actions/checkout@v5
27
27
 
28
28
  - name: Install uv
29
- uses: astral-sh/setup-uv@v4
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.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
- Install the latest release from PyPI:
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
- pip install PyDiffGame
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
- pip install "PyDiffGame[examples]"
141
+ uv add "PyDiffGame[examples]"
141
142
  ```
142
143
 
143
- To work on the package itself, this project is managed with
144
- [**uv**](https://docs.astral.sh/uv/). Clone it and sync the locked development
145
- environment:
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`, `uv run python -m PyDiffGame.examples.MassesWithSpringsComparison`, …).
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
  &emsp;
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
- Install the latest release from PyPI:
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
- pip install PyDiffGame
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
- pip install "PyDiffGame[examples]"
82
+ uv add "PyDiffGame[examples]"
82
83
  ```
83
84
 
84
- To work on the package itself, this project is managed with
85
- [**uv**](https://docs.astral.sh/uv/). Clone it and sync the locked development
86
- environment:
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`, `uv run python -m PyDiffGame.examples.MassesWithSpringsComparison`, …).
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
  &emsp;
322
335
  <a href="https://in.bgu.ac.il/en/Pages/default.aspx">