cpomdp-workshop 0.1.0__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 (38) hide show
  1. cpomdp_workshop-0.1.0/.gitignore +9 -0
  2. cpomdp_workshop-0.1.0/LICENSE +21 -0
  3. cpomdp_workshop-0.1.0/PKG-INFO +108 -0
  4. cpomdp_workshop-0.1.0/README.md +77 -0
  5. cpomdp_workshop-0.1.0/assets/fig_world_rock.png +0 -0
  6. cpomdp_workshop-0.1.0/assets/tile_task1.png +0 -0
  7. cpomdp_workshop-0.1.0/assets/tile_task2.png +0 -0
  8. cpomdp_workshop-0.1.0/assets/tile_task3.png +0 -0
  9. cpomdp_workshop-0.1.0/delivery/hints_chat.txt +69 -0
  10. cpomdp_workshop-0.1.0/notebooks/build_a_curious_rocket.ipynb +1808 -0
  11. cpomdp_workshop-0.1.0/pyproject.toml +115 -0
  12. cpomdp_workshop-0.1.0/scripts/build_notebook.py +232 -0
  13. cpomdp_workshop-0.1.0/scripts/lesson.py +718 -0
  14. cpomdp_workshop-0.1.0/scripts/render_assets.py +42 -0
  15. cpomdp_workshop-0.1.0/src/cpomdp_workshop/__init__.py +161 -0
  16. cpomdp_workshop-0.1.0/src/cpomdp_workshop/_seams.py +46 -0
  17. cpomdp_workshop-0.1.0/src/cpomdp_workshop/build.py +171 -0
  18. cpomdp_workshop-0.1.0/src/cpomdp_workshop/check.py +287 -0
  19. cpomdp_workshop-0.1.0/src/cpomdp_workshop/checkpoints.py +55 -0
  20. cpomdp_workshop-0.1.0/src/cpomdp_workshop/planner.py +112 -0
  21. cpomdp_workshop-0.1.0/src/cpomdp_workshop/plot.py +396 -0
  22. cpomdp_workshop-0.1.0/src/cpomdp_workshop/selfguided.py +68 -0
  23. cpomdp_workshop-0.1.0/src/cpomdp_workshop/sim.py +139 -0
  24. cpomdp_workshop-0.1.0/src/cpomdp_workshop/solutions.py +97 -0
  25. cpomdp_workshop-0.1.0/src/cpomdp_workshop/symptoms.py +123 -0
  26. cpomdp_workshop-0.1.0/src/cpomdp_workshop/world.py +107 -0
  27. cpomdp_workshop-0.1.0/tests/conftest.py +4 -0
  28. cpomdp_workshop-0.1.0/tests/test_api_contract.py +160 -0
  29. cpomdp_workshop-0.1.0/tests/test_build.py +98 -0
  30. cpomdp_workshop-0.1.0/tests/test_check.py +56 -0
  31. cpomdp_workshop-0.1.0/tests/test_measured_claims.py +442 -0
  32. cpomdp_workshop-0.1.0/tests/test_notebook.py +267 -0
  33. cpomdp_workshop-0.1.0/tests/test_planner.py +84 -0
  34. cpomdp_workshop-0.1.0/tests/test_plot.py +117 -0
  35. cpomdp_workshop-0.1.0/tests/test_sim.py +87 -0
  36. cpomdp_workshop-0.1.0/tests/test_solutions.py +44 -0
  37. cpomdp_workshop-0.1.0/tests/test_symptoms.py +28 -0
  38. cpomdp_workshop-0.1.0/tests/test_world.py +73 -0
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .ipynb_checkpoints/
7
+
8
+ .claude/
9
+ artifacts/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DanBoringName
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.5
2
+ Name: cpomdp-workshop
3
+ Version: 0.1.0
4
+ Summary: Build a curious rocket — the AII 2026 symposium workshop for cpomdp.
5
+ Project-URL: Repository, https://github.com/inferogenesis/cpomdp-workshop
6
+ Author: Inferogenesis
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: active-inference,cpomdp,teaching,workshop
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Education
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
16
+ Requires-Python: >=3.11
17
+ Requires-Dist: cpomdp==0.5.0
18
+ Requires-Dist: matplotlib>=3.5
19
+ Requires-Dist: numpy>=1.24
20
+ Provides-Extra: dev
21
+ Requires-Dist: nbclient>=0.10; extra == 'dev'
22
+ Requires-Dist: nbformat>=5.10; extra == 'dev'
23
+ Requires-Dist: pre-commit>=4.0; extra == 'dev'
24
+ Requires-Dist: pytest>=8.0; extra == 'dev'
25
+ Requires-Dist: ruff>=0.15; extra == 'dev'
26
+ Requires-Dist: ty>=0.0.1a1; extra == 'dev'
27
+ Provides-Extra: nb
28
+ Requires-Dist: ipykernel>=6.29; extra == 'nb'
29
+ Requires-Dist: jupyter>=1.1; extra == 'nb'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # Build a curious rocket
33
+
34
+ A two-hour hands-on workshop on [cpomdp](https://github.com/inferogenesis/cpomdp),
35
+ continuous active inference in Python, for the 6th Applied Active Inference Symposium.
36
+ You build three rockets. The first is the 1960 controller, a Kalman filter with a goal,
37
+ and the library reports that no move can teach it more than another. The second reads its
38
+ position through a sensor that depends on where it is, and bends its path toward a
39
+ beacon. In the third, three rockets fly past a rock none of them was told about.
40
+
41
+ ## Open it
42
+
43
+ The live session runs on Colab. There is nothing to install. A browser and a Google
44
+ account are enough. Edits vanish when the tab closes unless you save a copy to Drive.
45
+
46
+ [Open the notebook in Colab](https://colab.research.google.com/github/inferogenesis/cpomdp-workshop/blob/main/notebooks/build_a_curious_rocket.ipynb)
47
+
48
+ Please write the lines yourself rather than asking a generative AI tool for them. The
49
+ point of the session is the two lines you work out. The hints and the reference cells
50
+ are there for when you are stuck. Using them is fine.
51
+
52
+ ## Run it locally
53
+
54
+ With [uv](https://docs.astral.sh/uv/):
55
+
56
+ ```sh
57
+ git clone https://github.com/inferogenesis/cpomdp-workshop
58
+ cd cpomdp-workshop
59
+ uv sync --extra nb
60
+ uv run jupyter lab notebooks/build_a_curious_rocket.ipynb
61
+ ```
62
+
63
+ Python 3.11 or newer. The workshop pins the cpomdp release it was measured on, and the
64
+ first cell stops if a different one is installed.
65
+
66
+ Do not name a file in the folder you run from after a standard-library module, such as
67
+ `types.py`, `dis.py` or `random.py`. Python imports yours instead. The error that
68
+ follows points nowhere near the cause.
69
+
70
+ ## What is in the repo
71
+
72
+ | Path | What it holds |
73
+ | --- | --- |
74
+ | `src/cpomdp_workshop/` | The package the notebook imports: the world, the two rockets, the flight loop, the checks, the figures |
75
+ | `notebooks/` | The notebook, generated and run by `scripts/build_notebook.py`. Never edited by hand |
76
+ | `scripts/lesson.py` | What the notebook says, cell by cell |
77
+ | `delivery/hints_chat.txt` | The hint lines pasted into chat. The notebook's hint and reference cells are built from it |
78
+ | `assets/` | Figures, regenerated from the package by `scripts/render_assets.py` |
79
+ | `tests/test_measured_claims.py` | One test per number the notebook or the deck shows |
80
+ | `tests/test_api_contract.py` | The cpomdp forms the workshop is written against |
81
+
82
+ ## Working on it
83
+
84
+ Every command runs from the repo root.
85
+
86
+ ```sh
87
+ uv sync --extra dev --extra nb
88
+ uv run --no-sync pytest -q -m "not slow" # fast tests
89
+ uv run --no-sync pytest -q # everything, the notebook run included
90
+ uv run --no-sync pre-commit run --all-files # the gate CI runs
91
+ uv run --no-sync python scripts/build_notebook.py
92
+ uv run --no-sync python scripts/render_assets.py
93
+ ```
94
+
95
+ A change to the lesson, the hints or the package means rebuilding the notebook. A stale
96
+ notebook fails a test.
97
+
98
+ ## Releasing
99
+
100
+ The notebook's first cell installs the exact version it was built from, so bump
101
+ `version` in `pyproject.toml`, rebuild the notebook and merge. Then publish a GitHub
102
+ release tagged `v` plus that version. `.github/workflows/publish.yml` runs the tests,
103
+ installs the wheel into a clean environment and uploads to PyPI through Trusted
104
+ Publishing.
105
+
106
+ ## Licence
107
+
108
+ MIT. See `LICENSE`.
@@ -0,0 +1,77 @@
1
+ # Build a curious rocket
2
+
3
+ A two-hour hands-on workshop on [cpomdp](https://github.com/inferogenesis/cpomdp),
4
+ continuous active inference in Python, for the 6th Applied Active Inference Symposium.
5
+ You build three rockets. The first is the 1960 controller, a Kalman filter with a goal,
6
+ and the library reports that no move can teach it more than another. The second reads its
7
+ position through a sensor that depends on where it is, and bends its path toward a
8
+ beacon. In the third, three rockets fly past a rock none of them was told about.
9
+
10
+ ## Open it
11
+
12
+ The live session runs on Colab. There is nothing to install. A browser and a Google
13
+ account are enough. Edits vanish when the tab closes unless you save a copy to Drive.
14
+
15
+ [Open the notebook in Colab](https://colab.research.google.com/github/inferogenesis/cpomdp-workshop/blob/main/notebooks/build_a_curious_rocket.ipynb)
16
+
17
+ Please write the lines yourself rather than asking a generative AI tool for them. The
18
+ point of the session is the two lines you work out. The hints and the reference cells
19
+ are there for when you are stuck. Using them is fine.
20
+
21
+ ## Run it locally
22
+
23
+ With [uv](https://docs.astral.sh/uv/):
24
+
25
+ ```sh
26
+ git clone https://github.com/inferogenesis/cpomdp-workshop
27
+ cd cpomdp-workshop
28
+ uv sync --extra nb
29
+ uv run jupyter lab notebooks/build_a_curious_rocket.ipynb
30
+ ```
31
+
32
+ Python 3.11 or newer. The workshop pins the cpomdp release it was measured on, and the
33
+ first cell stops if a different one is installed.
34
+
35
+ Do not name a file in the folder you run from after a standard-library module, such as
36
+ `types.py`, `dis.py` or `random.py`. Python imports yours instead. The error that
37
+ follows points nowhere near the cause.
38
+
39
+ ## What is in the repo
40
+
41
+ | Path | What it holds |
42
+ | --- | --- |
43
+ | `src/cpomdp_workshop/` | The package the notebook imports: the world, the two rockets, the flight loop, the checks, the figures |
44
+ | `notebooks/` | The notebook, generated and run by `scripts/build_notebook.py`. Never edited by hand |
45
+ | `scripts/lesson.py` | What the notebook says, cell by cell |
46
+ | `delivery/hints_chat.txt` | The hint lines pasted into chat. The notebook's hint and reference cells are built from it |
47
+ | `assets/` | Figures, regenerated from the package by `scripts/render_assets.py` |
48
+ | `tests/test_measured_claims.py` | One test per number the notebook or the deck shows |
49
+ | `tests/test_api_contract.py` | The cpomdp forms the workshop is written against |
50
+
51
+ ## Working on it
52
+
53
+ Every command runs from the repo root.
54
+
55
+ ```sh
56
+ uv sync --extra dev --extra nb
57
+ uv run --no-sync pytest -q -m "not slow" # fast tests
58
+ uv run --no-sync pytest -q # everything, the notebook run included
59
+ uv run --no-sync pre-commit run --all-files # the gate CI runs
60
+ uv run --no-sync python scripts/build_notebook.py
61
+ uv run --no-sync python scripts/render_assets.py
62
+ ```
63
+
64
+ A change to the lesson, the hints or the package means rebuilding the notebook. A stale
65
+ notebook fails a test.
66
+
67
+ ## Releasing
68
+
69
+ The notebook's first cell installs the exact version it was built from, so bump
70
+ `version` in `pyproject.toml`, rebuild the notebook and merge. Then publish a GitHub
71
+ release tagged `v` plus that version. `.github/workflows/publish.yml` runs the tests,
72
+ installs the wheel into a clean environment and uploads to PyPI through Trusted
73
+ Publishing.
74
+
75
+ ## Licence
76
+
77
+ MIT. See `LICENSE`.
@@ -0,0 +1,69 @@
1
+ Pasted into the chat on the timer. The notebook's hint and reference cells are built
2
+ from this file, so what is pasted and what is in the notebook never differ. A hint is
3
+ one line of code that replaces the line of the same name in the attempt cell. A nudge
4
+ is the step before its hint, pasted first. In the notebook it shows under the hint's
5
+ title, with the hint's line behind Show code.
6
+
7
+ [session 1, nudge 1]
8
+ `observation_matrix` describes how the rocket's position (x, y) turns into a sensor reading (x, y).
9
+ Here the reading is simply the position itself, so this is the identity matrix.
10
+ In JAX, you can create an n-by-n identity matrix with `jnp.eye(n)`.
11
+ We need n = 2: one dimension for x and one for y.
12
+
13
+ [session 1, hint 1]
14
+ observation_matrix = jnp.eye(2)
15
+
16
+ [session 1, nudge 2]
17
+ `observation_noise` (R) describes how far each reading tends to stray from the true position.
18
+ It's a covariance matrix: the variance for each axis sits on the diagonal, with zeros everywhere else.
19
+ In this task, the variance is 0.05 on both axes.
20
+ Multiplying a matrix by a number scales every entry, so `v * jnp.eye(n)` puts v along the diagonal.
21
+
22
+ [session 1, hint 2]
23
+ observation_noise = 0.05 * jnp.eye(2)
24
+
25
+ [session 1, reference]
26
+ Reference for task 1: the collapsed cell under hint 2. Run it, then the checkpoint.
27
+
28
+ [session 2, nudge 1]
29
+ `pull` is the extra noise the beacon adds at position x.
30
+ It's k multiplied by `distance_squared`, with the squared distance capped first so it never goes above the cap.
31
+ `jnp.minimum(a, b)` returns whichever of a and b is smaller, which is a handy way to cap a at b.
32
+ You'll find k in `params["beacon_gain"]` and the cap in `params["beacon_cap"]`.
33
+
34
+ [session 2, hint 1]
35
+ pull = params["beacon_gain"] * jnp.minimum(distance_squared, params["beacon_cap"])
36
+
37
+ [session 2, nudge 2]
38
+ `k` controls how quickly the reading gets noisier as the rocket moves away from the beacon.
39
+ With a small k, the path stays nearly straight.
40
+ With a large k, staying near the beacon becomes more appealing than reaching the pad, and the rocket stops short.
41
+ Try raising k from 0.08 in steps of about 0.1 and watch how high the path climbs.
42
+
43
+ [session 2, hint 2]
44
+ beacon_gain = 0.4
45
+
46
+ [session 2, reference]
47
+ Reference for task 2: the collapsed cell under hint 2. Run it, then the checkpoint.
48
+
49
+ [session 3, nudge 1]
50
+ `blindness` is the extra noise the blackout adds at position x.
51
+ It's strongest at the centre of the blackout and fades with distance, in a bell shape.
52
+ `jnp.exp(-d / w)` equals 1 when d is 0 and falls towards 0 as d grows, more slowly when w is larger.
53
+ Here, d is `from_blackout`, w is `width`, and `height` sets how strong the peak is.
54
+
55
+ [session 3, hint 1]
56
+ blindness = height * jnp.exp(-from_blackout / width)
57
+
58
+ [session 3, nudge 2]
59
+ A backend is the engine that updates the rocket's belief after each reading.
60
+ `KalmanBackend(model)` is the classic Kalman filter.
61
+ cpomdp's other engine passes messages on a graph, the form it uses for models made of several linked parts.
62
+ `one_node` rewrites a model as a graph with just one node, so the same rocket can run on that engine.
63
+ Use `model`, the 1960 rocket's model from Task 1, which the checkpoint has restored for you.
64
+
65
+ [session 3, hint 2]
66
+ backend = one_node(model)
67
+
68
+ [session 3, reference]
69
+ Reference for task 3: the collapsed cell under hint 2. Run it, then the checkpoint.