Skip to content

Commit eb0c05f

Browse files
committed
Port logic and csp notebooks to the JupyterLite companion (#1072)
Add two more in-browser PoC notebooks: - logic.ipynb: propositional model checking / DPLL and first-order forward chaining (the 'West is a criminal' KB). - csp.ipynb: map colouring with AC-3 / backtracking and 8-queens with min-conflicts. Both modules only need pure-Python packages Pyodide already ships (networkx for logic, sortedcontainers for csp), which are preloaded in jupyter-lite.json. To let them import in Pyodide, make ipythonblocks a lazy import in agents.py (logic/csp pull in aima.agents): BlockGrid is only used by GraphicEnvironment, so import it inside __init__ with a clear error message instead of at module top level. This also makes aima.agents usable in any environment without the optional GUI dependency. Verified: the lite site builds with all five notebooks and the lazy-import fix is in the bundled wheel; in a Pyodide proxy env (numpy/networkx/ sortedcontainers/ipython, no ipythonblocks) both notebooks import and run.
1 parent 266e024 commit eb0c05f

4 files changed

Lines changed: 198 additions & 6 deletions

File tree

aima/agents.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232

3333
from aima.utils import distance_squared, turn_heading
3434
from statistics import mean
35-
from ipythonblocks import BlockGrid
3635
from IPython.display import HTML, display, clear_output
3736
from time import sleep
3837

@@ -635,6 +634,13 @@ def __init__(self, width=10, height=10, boundary=True, color={}, display=False):
635634
"""Define all the usual XYEnvironment characteristics,
636635
but initialise a BlockGrid for GUI too."""
637636
super().__init__(width, height)
637+
# imported lazily so the rest of aima.agents (and modules that import it,
638+
# e.g. logic/csp) can be used without the optional ipythonblocks GUI dep
639+
try:
640+
from ipythonblocks import BlockGrid
641+
except ImportError as e:
642+
raise ImportError("GraphicEnvironment needs the optional 'ipythonblocks' "
643+
"package; install it with `pip install ipythonblocks`.") from e
638644
self.grid = BlockGrid(width, height, fill=(200, 200, 200))
639645
if display:
640646
self.grid.show()

lite/README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,22 @@ cannot run here. The `aima` wheel is therefore installed with `deps=False` and
2121
the demo notebooks are restricted to modules that import only Pyodide-provided
2222
packages.
2323

24-
The proof-of-concept ships two such notebooks:
24+
The proof-of-concept ships these notebooks:
2525

2626
- `content/search.ipynb` — BFS / A* on the Romania map (`aima.search`, numpy only).
2727
- `content/games.ipynb` — minimax / alpha-beta on Tic-Tac-Toe (`aima.games`, numpy only).
28+
- `content/logic.ipynb` — propositional model checking / DPLL and first-order
29+
forward chaining (`aima.logic`, needs `networkx`).
30+
- `content/csp.ipynb` — map colouring with AC-3 / backtracking and N-queens with
31+
min-conflicts (`aima.csp`, needs `sortedcontainers`).
2832

29-
`content/Welcome.ipynb` shows the one-cell install pattern used by every notebook.
33+
`networkx` and `sortedcontainers` are pure-Python and ship with Pyodide; they are
34+
preloaded for the kernel in `jupyter-lite.json`. `content/Welcome.ipynb` shows the
35+
one-cell install pattern used by every notebook.
36+
37+
Note: `aima.logic`/`aima.csp` pull in `aima.agents`, whose only GUI dependency
38+
(`ipythonblocks`) is imported lazily, so these modules import cleanly in Pyodide
39+
without it.
3040

3141
## Build locally
3242

@@ -48,7 +58,6 @@ companion). Remaining work:
4858
- Verify in-browser execution across browsers (Pyodide runs only in a real
4959
browser, so this cannot be checked in headless CI — the CI job only proves the
5060
static site *builds*).
51-
- Port more lightweight notebooks (logic, csp, planning, probability) once their
52-
in-browser behaviour is confirmed; csp needs `sortedcontainers`, logic needs
53-
`networkx` (both pure-Python, installable in Pyodide).
61+
- Port more lightweight notebooks (planning, probability) once their in-browser
62+
behaviour is confirmed.
5463
- Decide whether to grow this into a full MyST / Jupyter Book textbook companion.

lite/content/csp.ipynb

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Constraint satisfaction in the browser\n",
8+
"\n",
9+
"Map colouring and N-queens from chapter 6, running fully in your browser with\n",
10+
"`aima.csp`."
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": null,
16+
"metadata": {},
17+
"outputs": [],
18+
"source": [
19+
"import piplite\n",
20+
"await piplite.install(\"aima\", deps=False)"
21+
]
22+
},
23+
{
24+
"cell_type": "markdown",
25+
"metadata": {},
26+
"source": [
27+
"## Map colouring\n",
28+
"\n",
29+
"Colour the map of Australia with three colours so that no two neighbouring\n",
30+
"regions share one. First we enforce arc consistency (AC-3), then solve by\n",
31+
"backtracking search."
32+
]
33+
},
34+
{
35+
"cell_type": "code",
36+
"execution_count": null,
37+
"metadata": {},
38+
"outputs": [],
39+
"source": [
40+
"from aima.csp import australia_csp, AC3, backtracking_search\n",
41+
"\n",
42+
"consistent, checks = AC3(australia_csp)\n",
43+
"print(\"AC-3 arc-consistent:\", consistent, \" (\", checks, \"constraint checks )\")\n",
44+
"print(\"backtracking solution:\", backtracking_search(australia_csp))"
45+
]
46+
},
47+
{
48+
"cell_type": "markdown",
49+
"metadata": {},
50+
"source": [
51+
"## N-queens\n",
52+
"\n",
53+
"Place 8 non-attacking queens by local search (min-conflicts). The solution\n",
54+
"maps each column to the row of its queen."
55+
]
56+
},
57+
{
58+
"cell_type": "code",
59+
"execution_count": null,
60+
"metadata": {},
61+
"outputs": [],
62+
"source": [
63+
"import random\n",
64+
"from aima.csp import NQueensCSP, min_conflicts\n",
65+
"\n",
66+
"random.seed(0)\n",
67+
"solution = min_conflicts(NQueensCSP(8), max_steps=1000)\n",
68+
"print(\"8-queens (column -> row):\", solution)"
69+
]
70+
}
71+
],
72+
"metadata": {
73+
"kernelspec": {
74+
"display_name": "Python (Pyodide)",
75+
"language": "python",
76+
"name": "python"
77+
},
78+
"language_info": {
79+
"name": "python"
80+
}
81+
},
82+
"nbformat": 4,
83+
"nbformat_minor": 5
84+
}

lite/content/logic.ipynb

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Logic in the browser\n",
8+
"\n",
9+
"Propositional and first-order reasoning from chapters 7-9, running fully in\n",
10+
"your browser with `aima.logic`."
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": null,
16+
"metadata": {},
17+
"outputs": [],
18+
"source": [
19+
"import piplite\n",
20+
"await piplite.install(\"aima\", deps=False)"
21+
]
22+
},
23+
{
24+
"cell_type": "markdown",
25+
"metadata": {},
26+
"source": [
27+
"## Propositional logic\n",
28+
"\n",
29+
"Model checking by truth-table enumeration, and satisfiability via DPLL."
30+
]
31+
},
32+
{
33+
"cell_type": "code",
34+
"execution_count": null,
35+
"metadata": {},
36+
"outputs": [],
37+
"source": [
38+
"from aima.logic import expr, tt_entails, dpll_satisfiable\n",
39+
"\n",
40+
"# modus ponens: does P & (P => Q) entail Q ?\n",
41+
"print(\"(P & (P ==> Q)) |= Q :\", tt_entails(expr(\"(P & (P ==> Q))\"), expr(\"Q\")))\n",
42+
"\n",
43+
"# satisfiability\n",
44+
"print(\"P | ~P satisfiable :\", bool(dpll_satisfiable(expr(\"P | ~P\"))))\n",
45+
"print(\"P & ~P satisfiable :\", bool(dpll_satisfiable(expr(\"P & ~P\"))))"
46+
]
47+
},
48+
{
49+
"cell_type": "markdown",
50+
"metadata": {},
51+
"source": [
52+
"## First-order logic\n",
53+
"\n",
54+
"The classic \"West is a criminal\" knowledge base (section 9.3), solved by\n",
55+
"forward chaining."
56+
]
57+
},
58+
{
59+
"cell_type": "code",
60+
"execution_count": null,
61+
"metadata": {},
62+
"outputs": [],
63+
"source": [
64+
"from aima.logic import expr, FolKB, fol_fc_ask\n",
65+
"\n",
66+
"clauses = [\n",
67+
" \"(American(x) & Weapon(y) & Sells(x, y, z) & Hostile(z)) ==> Criminal(x)\",\n",
68+
" \"Owns(Nono, M1)\",\n",
69+
" \"Missile(M1)\",\n",
70+
" \"(Missile(x) & Owns(Nono, x)) ==> Sells(West, x, Nono)\",\n",
71+
" \"Missile(x) ==> Weapon(x)\",\n",
72+
" \"Enemy(x, America) ==> Hostile(x)\",\n",
73+
" \"American(West)\",\n",
74+
" \"Enemy(Nono, America)\",\n",
75+
"]\n",
76+
"kb = FolKB([expr(c) for c in clauses])\n",
77+
"print(\"Who is a criminal? \", list(fol_fc_ask(kb, expr(\"Criminal(x)\"))))"
78+
]
79+
}
80+
],
81+
"metadata": {
82+
"kernelspec": {
83+
"display_name": "Python (Pyodide)",
84+
"language": "python",
85+
"name": "python"
86+
},
87+
"language_info": {
88+
"name": "python"
89+
}
90+
},
91+
"nbformat": 4,
92+
"nbformat_minor": 5
93+
}

0 commit comments

Comments
 (0)