PyDiffGame 2.0.0__tar.gz → 2.0.2__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.2/.github/workflows/python-publish.yml +173 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/.github/workflows/tests.yml +2 -2
- pydiffgame-2.0.2/CLAUDE.md +42 -0
- pydiffgame-2.0.2/CONTRIBUTING.md +92 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/PKG-INFO +29 -16
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/README.md +28 -15
- pydiffgame-2.0.2/docs/README.md +362 -0
- pydiffgame-2.0.2/images/logo.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/pyproject.toml +1 -1
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/__init__.py +1 -1
- pydiffgame-2.0.2/tools/bump_version.py +78 -0
- pydiffgame-2.0.2/tools/render_logo.py +295 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/uv.lock +1 -1
- pydiffgame-2.0.0/.github/workflows/python-publish.yml +0 -71
- pydiffgame-2.0.0/CONTRIBUTING.md +0 -44
- pydiffgame-2.0.0/docs/README.md +0 -266
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/.gitignore +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/.pre-commit-config.yaml +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/CITATIONS.bib +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/CODE_OF_CONDUCT.md +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/LICENSE +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/_config.yml +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/images/Logo_ISTRC_Green_English.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/images/logo_abc.png +0 -0
- /pydiffgame-2.0.0/images/logo.png → /pydiffgame-2.0.2/images/logo_source.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/images/readme/masses_cost.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/images/readme/masses_game_vs_lqr.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/images/readme/masses_schematic.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/requirements.txt +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/_typing.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/base.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/comparison.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/continuous.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/discrete.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/InvertedPendulumComparison.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/MassesWithSpringsComparison.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/PVTOL.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/PVTOLComparison.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/QuadRotorControl.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/2/2-players_large_1.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/2/2-players_large_2.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/2/LQR_large_1.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/2/LQR_large_2.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/2/two_masses_tikz.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/4/4-players_large_1.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/4/4-players_large_2.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/4/LQR_large_1.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/4/LQR_large_2.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/8/8-players_large_1.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/8/8-players_large_2.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/8/LQR_large_1.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/8/LQR_large_2.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/PVTOL/PVTOL1.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/PVTOL/PVTOL10.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/PVTOL/PVTOL100.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/PVTOL/PVTOL1000.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/PVTOL0001.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/PVTOL001.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/PVTOL01.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/examples/figures/PVTOL1.png +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/lqr.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/objective.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/src/PyDiffGame/plotting.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/tests/conftest.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/tests/test_discrete.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/tests/test_examples.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/tests/test_game.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/tests/test_lqr.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/tests/test_objective.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/tests/test_simulation.py +0 -0
- {pydiffgame-2.0.0 → pydiffgame-2.0.2}/tools/generate_readme_figures.py +0 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Publishes the package to PyPI and creates the matching GitHub Release,
|
|
2
|
+
# auto-incrementing the version when source files change on master.
|
|
3
|
+
#
|
|
4
|
+
# Triggers:
|
|
5
|
+
# - push to master that touches SOURCE files (the package or its packaging
|
|
6
|
+
# metadata) — docs / tests / tooling / CI changes do not cut a release.
|
|
7
|
+
# - workflow_dispatch: manual trigger remains available for on-demand re-runs
|
|
8
|
+
# (use this if you ever need to publish despite no source-file changes).
|
|
9
|
+
#
|
|
10
|
+
# Loop prevention: the workflow itself commits a "chore: bump version" commit
|
|
11
|
+
# back to master. Three independent guards stop that commit from re-triggering
|
|
12
|
+
# the workflow:
|
|
13
|
+
# 1. GitHub Actions natively skips push events whose head commit message
|
|
14
|
+
# contains "[skip ci]" (which the bump commit always does).
|
|
15
|
+
# 2. The bump-version job is guarded with an explicit `if:` so it only runs
|
|
16
|
+
# for human commits / manual dispatch, never for github-actions[bot].
|
|
17
|
+
# 3. The bump commit message itself contains "[skip ci]" so even if (1) and
|
|
18
|
+
# (2) ever break, the message still signals "do not republish".
|
|
19
|
+
#
|
|
20
|
+
# Concurrency: serialize runs so two pushes close together don't race on the
|
|
21
|
+
# bump commit's git push.
|
|
22
|
+
#
|
|
23
|
+
# Flow per run:
|
|
24
|
+
# 1. bump-version : increment the version (carry-at-9 via tools/bump_version.py),
|
|
25
|
+
# commit it back to master with [skip ci].
|
|
26
|
+
# 2. release-build : build the dists from the bumped master.
|
|
27
|
+
# 3. pypi-publish : upload to PyPI via Trusted Publishing (OIDC, no tokens).
|
|
28
|
+
# 4. github-release: create the v<version> GitHub Release with notes + dists.
|
|
29
|
+
#
|
|
30
|
+
# Versions roll over at 9: 2.0.9 -> 2.1.0, 2.9.9 -> 3.0.0.
|
|
31
|
+
|
|
32
|
+
name: Upload Python Package
|
|
33
|
+
|
|
34
|
+
on:
|
|
35
|
+
push:
|
|
36
|
+
branches: [master]
|
|
37
|
+
# Only publish when source files change. Docs/tests/tooling commits do not
|
|
38
|
+
# cut a release; mixed commits do (the filter matches if ANY changed file
|
|
39
|
+
# matches).
|
|
40
|
+
paths:
|
|
41
|
+
- 'src/PyDiffGame/**'
|
|
42
|
+
- 'pyproject.toml'
|
|
43
|
+
workflow_dispatch:
|
|
44
|
+
|
|
45
|
+
permissions:
|
|
46
|
+
contents: read
|
|
47
|
+
|
|
48
|
+
concurrency:
|
|
49
|
+
group: publish-master
|
|
50
|
+
cancel-in-progress: false
|
|
51
|
+
|
|
52
|
+
jobs:
|
|
53
|
+
bump-version:
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
# Defense-in-depth loop guard: only release for human commits or manual
|
|
56
|
+
# dispatch — never for the workflow's own [skip ci] bump commit.
|
|
57
|
+
if: >-
|
|
58
|
+
${{
|
|
59
|
+
github.event_name == 'workflow_dispatch' ||
|
|
60
|
+
(
|
|
61
|
+
github.actor != 'github-actions[bot]' &&
|
|
62
|
+
!contains(github.event.head_commit.message, '[skip ci]')
|
|
63
|
+
)
|
|
64
|
+
}}
|
|
65
|
+
permissions:
|
|
66
|
+
contents: write
|
|
67
|
+
outputs:
|
|
68
|
+
version: ${{ steps.bump.outputs.version }}
|
|
69
|
+
steps:
|
|
70
|
+
- uses: actions/checkout@v5
|
|
71
|
+
with:
|
|
72
|
+
ref: master
|
|
73
|
+
|
|
74
|
+
- name: Increment the version (carry-at-9)
|
|
75
|
+
id: bump
|
|
76
|
+
run: |
|
|
77
|
+
new="$(python3 tools/bump_version.py)"
|
|
78
|
+
echo "version=$new" >> "$GITHUB_OUTPUT"
|
|
79
|
+
echo "Bumped to $new"
|
|
80
|
+
|
|
81
|
+
- name: Commit and push the bump
|
|
82
|
+
run: |
|
|
83
|
+
git config user.name "github-actions[bot]"
|
|
84
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
85
|
+
git add pyproject.toml src/PyDiffGame/__init__.py
|
|
86
|
+
git commit -m "chore: bump version to ${{ steps.bump.outputs.version }} [skip ci]"
|
|
87
|
+
git push origin HEAD:master
|
|
88
|
+
|
|
89
|
+
release-build:
|
|
90
|
+
needs: bump-version
|
|
91
|
+
runs-on: ubuntu-latest
|
|
92
|
+
steps:
|
|
93
|
+
- uses: actions/checkout@v5
|
|
94
|
+
with:
|
|
95
|
+
ref: master # the bumped commit
|
|
96
|
+
|
|
97
|
+
- name: Install uv
|
|
98
|
+
uses: astral-sh/setup-uv@v6
|
|
99
|
+
|
|
100
|
+
- name: Build release distributions
|
|
101
|
+
run: uv build
|
|
102
|
+
|
|
103
|
+
- name: Upload distributions
|
|
104
|
+
uses: actions/upload-artifact@v5
|
|
105
|
+
with:
|
|
106
|
+
name: release-dists
|
|
107
|
+
path: dist/
|
|
108
|
+
|
|
109
|
+
pypi-publish:
|
|
110
|
+
needs:
|
|
111
|
+
- release-build
|
|
112
|
+
runs-on: ubuntu-latest
|
|
113
|
+
permissions:
|
|
114
|
+
# IMPORTANT: this permission is mandatory for trusted publishing
|
|
115
|
+
id-token: write
|
|
116
|
+
|
|
117
|
+
environment:
|
|
118
|
+
name: pypi
|
|
119
|
+
url: https://pypi.org/project/PyDiffGame/
|
|
120
|
+
|
|
121
|
+
steps:
|
|
122
|
+
- name: Retrieve release distributions
|
|
123
|
+
uses: actions/download-artifact@v5
|
|
124
|
+
with:
|
|
125
|
+
name: release-dists
|
|
126
|
+
path: dist/
|
|
127
|
+
|
|
128
|
+
# Authentication is via PyPI Trusted Publishing (OIDC) — no token needed.
|
|
129
|
+
# Trusted publisher on PyPI: owner krichelj, repo PyDiffGame,
|
|
130
|
+
# workflow python-publish.yml, environment pypi.
|
|
131
|
+
- name: Publish release distributions to PyPI
|
|
132
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
133
|
+
with:
|
|
134
|
+
packages-dir: dist/
|
|
135
|
+
# Don't fail if this version is already on PyPI (idempotent re-runs).
|
|
136
|
+
skip-existing: true
|
|
137
|
+
|
|
138
|
+
github-release:
|
|
139
|
+
needs:
|
|
140
|
+
- bump-version
|
|
141
|
+
- pypi-publish
|
|
142
|
+
runs-on: ubuntu-latest
|
|
143
|
+
permissions:
|
|
144
|
+
# Needed to create the release and its tag.
|
|
145
|
+
contents: write
|
|
146
|
+
|
|
147
|
+
steps:
|
|
148
|
+
- uses: actions/checkout@v5
|
|
149
|
+
with:
|
|
150
|
+
ref: master
|
|
151
|
+
|
|
152
|
+
- name: Retrieve release distributions
|
|
153
|
+
uses: actions/download-artifact@v5
|
|
154
|
+
with:
|
|
155
|
+
name: release-dists
|
|
156
|
+
path: dist/
|
|
157
|
+
|
|
158
|
+
- name: Create or update the GitHub Release
|
|
159
|
+
env:
|
|
160
|
+
GH_TOKEN: ${{ github.token }}
|
|
161
|
+
VERSION: ${{ needs.bump-version.outputs.version }}
|
|
162
|
+
run: |
|
|
163
|
+
tag="v${VERSION}"
|
|
164
|
+
echo "Releasing ${tag}"
|
|
165
|
+
if gh release view "$tag" >/dev/null 2>&1; then
|
|
166
|
+
echo "Release ${tag} already exists - refreshing its assets."
|
|
167
|
+
gh release upload "$tag" dist/* --clobber
|
|
168
|
+
else
|
|
169
|
+
gh release create "$tag" dist/* \
|
|
170
|
+
--title "$tag" \
|
|
171
|
+
--generate-notes \
|
|
172
|
+
--target master
|
|
173
|
+
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,42 @@
|
|
|
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
|
+
- **Continuous deployment, gated on source changes.** Every commit to `master`
|
|
20
|
+
that touches `src/PyDiffGame/**` or `pyproject.toml` automatically triggers
|
|
21
|
+
the publish workflow. Docs / tests / tooling / CI changes do **not** trigger
|
|
22
|
+
a release on their own; mixed commits do.
|
|
23
|
+
- The workflow auto-increments the version (`tools/bump_version.py`), commits
|
|
24
|
+
the bump to `master` as `chore: bump version to X.Y.Z [skip ci]`, builds with
|
|
25
|
+
`uv build`, publishes to PyPI via Trusted Publishing (OIDC, no tokens), and
|
|
26
|
+
creates the matching `v<version>` GitHub Release with notes and the built
|
|
27
|
+
dists attached. It is idempotent (`skip-existing`).
|
|
28
|
+
- Manual on-demand publish stays available via
|
|
29
|
+
`Actions -> Upload Python Package -> Run workflow` (`workflow_dispatch`).
|
|
30
|
+
- Three independent guards prevent the bump commit from re-triggering the
|
|
31
|
+
workflow (an infinite loop): `[skip ci]` in the message (which GitHub Actions
|
|
32
|
+
natively honors), an explicit job-level `if:` filtering out
|
|
33
|
+
`github-actions[bot]`, and `paths:` filter scoping to source files.
|
|
34
|
+
|
|
35
|
+
## Docs
|
|
36
|
+
- `README.md` is the single canonical readme and is also the PyPI long-description
|
|
37
|
+
(`pyproject.toml: readme = "README.md"`), so its image/file links must be **absolute**
|
|
38
|
+
(`raw.githubusercontent.com/.../master/...` for images, `github.com/.../blob/master/...`
|
|
39
|
+
for files) so they render on PyPI.
|
|
40
|
+
- Keep `docs/README.md` identical to `README.md`.
|
|
41
|
+
- README figures are generated from the live solver:
|
|
42
|
+
`uv run python tools/generate_readme_figures.py`.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Contribution Guidelines
|
|
2
|
+
|
|
3
|
+
This repo is part of a research conducted at Ben Gurion University,
|
|
4
|
+
and is thus open source. We would love to receive your input!
|
|
5
|
+
|
|
6
|
+
Please ensure your pull request adheres to the following guidelines:
|
|
7
|
+
|
|
8
|
+
- Search previous suggestions before making a new one, as yours may be a duplicate.
|
|
9
|
+
- Make an individual pull request for each suggestion.
|
|
10
|
+
- New categories or improvements to the existing categorization are welcome.
|
|
11
|
+
- Check your spelling and grammar.
|
|
12
|
+
- Make sure your text editor is set to remove trailing whitespace.
|
|
13
|
+
- The pull request and commit should have a useful title.
|
|
14
|
+
|
|
15
|
+
## Development setup
|
|
16
|
+
|
|
17
|
+
PyDiffGame requires **Python >= 3.11** and uses [uv](https://docs.astral.sh/uv/)
|
|
18
|
+
for environment and dependency management. Install uv, sync the locked
|
|
19
|
+
development environment, and enable the pre-commit hooks so the code-quality
|
|
20
|
+
tools run automatically on every commit:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# install uv: https://docs.astral.sh/uv/getting-started/installation/
|
|
24
|
+
uv sync --extra dev
|
|
25
|
+
uv run pre-commit install
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`uv sync` creates the virtual environment and installs the exact, locked
|
|
29
|
+
dependencies. Run the tooling through `uv run`:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv run ruff format src/PyDiffGame tests # auto-format (black-compatible)
|
|
33
|
+
uv run ruff check src/PyDiffGame tests # lint
|
|
34
|
+
uv run mypy src/PyDiffGame # type-check
|
|
35
|
+
uv run pytest # test suite
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Continuous integration runs the formatter check, the linter, the type checker
|
|
39
|
+
and the full suite (all via uv) across Python 3.11–3.14, so please make sure
|
|
40
|
+
they pass locally.
|
|
41
|
+
|
|
42
|
+
## Releasing
|
|
43
|
+
|
|
44
|
+
The project is on **continuous deployment for source changes**: every commit
|
|
45
|
+
to `master` that touches source files automatically cuts a new release.
|
|
46
|
+
|
|
47
|
+
A commit triggers a release when it changes any of:
|
|
48
|
+
|
|
49
|
+
- `src/PyDiffGame/**` — the package itself
|
|
50
|
+
- `pyproject.toml` — packaging metadata, dependencies, classifiers
|
|
51
|
+
|
|
52
|
+
Docs (`*.md`, `docs/**`), tests (`tests/**`), tooling (`tools/**`, `.github/**`,
|
|
53
|
+
`.pre-commit-config.yaml`), images and the lock file do **not** trigger a
|
|
54
|
+
release on their own. A mixed commit (e.g. `src/foo.py` + `README.md`) does
|
|
55
|
+
trigger one — the path filter matches if any changed file matches.
|
|
56
|
+
|
|
57
|
+
When a release-triggering commit lands on `master`, the publish workflow:
|
|
58
|
+
|
|
59
|
+
1. **Increments the version** with `tools/bump_version.py`, which rolls each
|
|
60
|
+
component over at 9 (`2.0.9 -> 2.1.0`, `2.9.9 -> 3.0.0`), updates both
|
|
61
|
+
`pyproject.toml` and `src/PyDiffGame/__init__.py`, and commits the bump back
|
|
62
|
+
to `master` as `chore: bump version to X.Y.Z [skip ci]`.
|
|
63
|
+
2. Builds the distributions with `uv build`.
|
|
64
|
+
3. Uploads them to PyPI via
|
|
65
|
+
[Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (OIDC — no
|
|
66
|
+
tokens or secrets).
|
|
67
|
+
4. Creates a `v<version>` GitHub Release with auto-generated notes and the built
|
|
68
|
+
wheels/sdist attached.
|
|
69
|
+
|
|
70
|
+
The workflow can also be triggered manually from **Actions -> Upload Python
|
|
71
|
+
Package -> Run workflow** if you ever need to re-run it.
|
|
72
|
+
|
|
73
|
+
You normally never edit the version by hand. To bump it locally (e.g. to test),
|
|
74
|
+
run `uv run python tools/bump_version.py` (`--dry-run` to preview, `--current`
|
|
75
|
+
to print the current version). The PyPI upload is idempotent (`skip-existing`),
|
|
76
|
+
so re-running the workflow is safe.
|
|
77
|
+
|
|
78
|
+
### What stops an infinite loop?
|
|
79
|
+
|
|
80
|
+
The publish workflow itself commits the version bump back to `master`. Three
|
|
81
|
+
independent guards stop that commit from re-triggering the workflow:
|
|
82
|
+
|
|
83
|
+
1. The bump commit message contains `[skip ci]`, which GitHub Actions natively
|
|
84
|
+
honors by **not creating a workflow run at all** for that push.
|
|
85
|
+
2. The `bump-version` job has an explicit `if:` that skips when the actor is
|
|
86
|
+
`github-actions[bot]` or the head-commit message contains `[skip ci]`.
|
|
87
|
+
3. `bump_version.py` is the single source of truth for the version, so a
|
|
88
|
+
tampered bump commit still wouldn't double-bump.
|
|
89
|
+
|
|
90
|
+
Thank you for your contribution!
|
|
91
|
+
|
|
92
|
+
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.2
|
|
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">
|