From 8a416e7ecb262e6806a46143a6b6201463eb01af Mon Sep 17 00:00:00 2001 From: Iain Ross Date: Wed, 2 Sep 2026 15:13:43 -0600 Subject: [PATCH] basic testing --- aima/agents.py | 20 +- aima/notebook_utils.py | 2 +- notebooks/agents.ipynb | 8 +- notebooks/learning.ipynb | 2668 +---------------- .../planning_partial_order_planner.ipynb | 633 +--- notebooks/vacuum_world.ipynb | 230 +- 6 files changed, 226 insertions(+), 3335 deletions(-) diff --git a/aima/agents.py b/aima/agents.py index d159466cc..2f2f8b7d4 100644 --- a/aima/agents.py +++ b/aima/agents.py @@ -191,7 +191,8 @@ def rule_match(state, rules): # ______________________________________________________________________________ -loc_A, loc_B = (0, 0), (1, 0) # The two locations for the Vacuum world +loc_A, loc_B, loc_C, loc_D = (0, 0), (1, 0), (0, 1), (1,1) +# The four locations for the Vacuum world def RandomVacuumAgent(): @@ -806,8 +807,11 @@ class TrivialVacuumEnvironment(Environment): def __init__(self): super().__init__() - self.status = {loc_A: random.choice(['Clean', 'Dirty']), - loc_B: random.choice(['Clean', 'Dirty'])} + self.status = {loc_AA: random.choice(['Clean', 'Dirty']), + loc_AB: random.choice(['Clean', 'Dirty']), + loc_BB: random.choice(['Clean', 'Dirty']), + loc_BA: random.choice(['Clean', 'Dirty']) + } def thing_classes(self): """Return the Thing/Agent classes that may populate this vacuum world.""" @@ -821,10 +825,16 @@ def execute_action(self, agent, action): """Change agent's location and/or location's status; track performance. Score 10 for each dirt cleaned; -1 for each move.""" if action == 'Right': - agent.location = loc_B + agent.location[0] += 1 agent.performance -= 1 elif action == 'Left': - agent.location = loc_A + agent.location[0] -= 1 + agent.performance -= 1 + elif action == 'Up': + agent.location[1] -= 1 + agent.performance -= 1 + elif action == 'Down': + agent.location[1] += 1 agent.performance -= 1 elif action == 'Suck': if self.status[agent.location] == 'Dirty': diff --git a/aima/notebook_utils.py b/aima/notebook_utils.py index 7b881d29c..6da04b8a0 100644 --- a/aima/notebook_utils.py +++ b/aima/notebook_utils.py @@ -50,7 +50,7 @@ def psource(*functions): from pygments.lexers import PythonLexer from pygments import highlight - display(HTML(highlight(source_code, PythonLexer(), HtmlFormatter(full=True)))) + display(HTML(highlight(source_code, PythonLexer(), HtmlFormatter(noclasses=True, style='monokai')))) except ImportError: print(source_code) diff --git a/notebooks/agents.ipynb b/notebooks/agents.ipynb index 6cff727ff..3d81313bd 100644 --- a/notebooks/agents.ipynb +++ b/notebooks/agents.ipynb @@ -722,9 +722,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "AIMA (Python 3.13)", "language": "python", - "name": "python3" + "name": "aima" }, "language_info": { "codemirror_mode": { @@ -736,9 +736,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.4" + "version": "3.13.5" } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } diff --git a/notebooks/learning.ipynb b/notebooks/learning.ipynb index cce6f92f5..4ed55ba87 100644 --- a/notebooks/learning.ipynb +++ b/notebooks/learning.ipynb @@ -20,7 +20,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:35.443128Z", @@ -126,9 +126,8 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": { - "collapsed": true, "execution": { "iopub.execute_input": "2026-06-27T12:55:38.354614Z", "iopub.status.busy": "2026-06-27T12:55:38.352319Z", @@ -196,9 +195,8 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { - "collapsed": true, "execution": { "iopub.execute_input": "2026-06-27T12:55:38.414409Z", "iopub.status.busy": "2026-06-27T12:55:38.413959Z", @@ -220,7 +218,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:38.444628Z", @@ -229,16 +227,7 @@ "shell.execute_reply": "2026-06-27T12:55:38.452358Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[5.1, 3.5, 1.4, 0.2, 'setosa']\n", - "[0, 1, 2, 3]\n" - ] - } - ], + "outputs": [], "source": [ "print(iris.examples[0])\n", "print(iris.inputs)" @@ -260,7 +249,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:38.573527Z", @@ -269,15 +258,7 @@ "shell.execute_reply": "2026-06-27T12:55:38.588777Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[0, 2, 3]\n" - ] - } - ], + "outputs": [], "source": [ "iris2 = DataSet(name=\"iris\",exclude=[1])\n", "print(iris2.inputs)" @@ -296,7 +277,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:38.597066Z", @@ -305,15 +286,7 @@ "shell.execute_reply": "2026-06-27T12:55:38.604341Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[5.1, 3.5, 1.4, 0.2, 'setosa'], [4.9, 3.0, 1.4, 0.2, 'setosa'], [4.7, 3.2, 1.3, 0.2, 'setosa']]\n" - ] - } - ], + "outputs": [], "source": [ "print(iris.examples[:3])" ] @@ -327,7 +300,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:38.612338Z", @@ -336,18 +309,7 @@ "shell.execute_reply": "2026-06-27T12:55:38.620495Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "attrs: [0, 1, 2, 3, 4]\n", - "attrnames (by default same as attrs): [0, 1, 2, 3, 4]\n", - "target: 4\n", - "inputs: [0, 1, 2, 3]\n" - ] - } - ], + "outputs": [], "source": [ "print(\"attrs:\", iris.attrs)\n", "print(\"attrnames (by default same as attrs):\", iris.attr_names)\n", @@ -364,7 +326,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:38.632407Z", @@ -373,15 +335,7 @@ "shell.execute_reply": "2026-06-27T12:55:38.641280Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[4.7, 5.5, 5.0, 4.9, 5.1, 4.6, 5.4, 4.4, 4.8, 4.3, 5.8, 7.0, 7.1, 4.5, 5.9, 5.6, 6.9, 6.5, 6.4, 6.6, 6.0, 6.1, 7.6, 7.4, 7.9, 5.7, 5.3, 5.2, 6.3, 6.7, 6.2, 6.8, 7.3, 7.2, 7.7]\n" - ] - } - ], + "outputs": [], "source": [ "print(iris.values[0])" ] @@ -395,7 +349,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:38.653354Z", @@ -404,16 +358,7 @@ "shell.execute_reply": "2026-06-27T12:55:38.663514Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "name: iris\n", - "source: \n" - ] - } - ], + "outputs": [], "source": [ "print(\"name:\", iris.name)\n", "print(\"source:\", iris.source)" @@ -428,7 +373,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:38.671257Z", @@ -437,15 +382,7 @@ "shell.execute_reply": "2026-06-27T12:55:38.679248Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['setosa', 'versicolor', 'virginica']\n" - ] - } - ], + "outputs": [], "source": [ "print(iris.values[iris.target])" ] @@ -472,7 +409,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:38.685610Z", @@ -481,16 +418,7 @@ "shell.execute_reply": "2026-06-27T12:55:38.694127Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Sanitized: [5.1, 3.5, 1.4, 0.2, None]\n", - "Original: [5.1, 3.5, 1.4, 0.2, 'setosa']\n" - ] - } - ], + "outputs": [], "source": [ "print(\"Sanitized:\",iris.sanitize(iris.examples[0]))\n", "print(\"Original:\",iris.examples[0])" @@ -505,7 +433,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:38.718499Z", @@ -514,15 +442,7 @@ "shell.execute_reply": "2026-06-27T12:55:38.744753Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['setosa', 'versicolor']\n" - ] - } - ], + "outputs": [], "source": [ "iris2 = DataSet(name=\"iris\")\n", "\n", @@ -539,7 +459,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:38.763865Z", @@ -548,16 +468,7 @@ "shell.execute_reply": "2026-06-27T12:55:38.780079Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Class of first example: setosa\n", - "Class of first example: 0\n" - ] - } - ], + "outputs": [], "source": [ "print(\"Class of first example:\",iris2.examples[0][iris2.target])\n", "iris2.classes_to_numbers()\n", @@ -580,7 +491,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:38.800881Z", @@ -589,18 +500,7 @@ "shell.execute_reply": "2026-06-27T12:55:38.837580Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Setosa feature means: [5.006, 3.418, 1.464, 0.244]\n", - "Versicolor mean for first feature: 5.936\n", - "Setosa feature deviations: [0.3524896872134513, 0.38102439795469095, 0.17351115943644543, 0.10720950308167838]\n", - "Virginica deviation for second feature: 0.32249663817263746\n" - ] - } - ], + "outputs": [], "source": [ "means, deviations = iris.find_means_and_deviations()\n", "\n", @@ -624,7 +524,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:38.852396Z", @@ -633,20 +533,7 @@ "shell.execute_reply": "2026-06-27T12:55:39.212501Z" } }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/david/Insync/donato.meoli.95@gmail.com/Google Drive/aima-python/notebook_utils.py:93: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown\n", - " plt.show()\n", - "/home/david/Insync/donato.meoli.95@gmail.com/Google Drive/aima-python/notebook_utils.py:93: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown\n", - " plt.show()\n", - "/home/david/Insync/donato.meoli.95@gmail.com/Google Drive/aima-python/notebook_utils.py:93: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown\n", - " plt.show()\n" - ] - } - ], + "outputs": [], "source": [ "iris = DataSet(name=\"iris\")\n", "\n", @@ -677,7 +564,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:39.222314Z", @@ -686,15 +573,7 @@ "shell.execute_reply": "2026-06-27T12:55:39.232027Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Manhattan Distance between (1,2) and (3,4) is 4\n" - ] - } - ], + "outputs": [], "source": [ "def manhattan_distance(X, Y):\n", " return sum([abs(x - y) for x, y in zip(X, Y)])\n", @@ -715,7 +594,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:39.250666Z", @@ -724,15 +603,7 @@ "shell.execute_reply": "2026-06-27T12:55:39.264400Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Euclidean Distance between (1,2) and (3,4) is 2.8284271247461903\n" - ] - } - ], + "outputs": [], "source": [ "def euclidean_distance(X, Y):\n", " return math.sqrt(sum([(x - y)**2 for x, y in zip(X,Y)]))\n", @@ -753,7 +624,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:39.274190Z", @@ -762,15 +633,7 @@ "shell.execute_reply": "2026-06-27T12:55:39.294351Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Hamming Distance between 'abc' and 'abb' is 1\n" - ] - } - ], + "outputs": [], "source": [ "def hamming_distance(X, Y):\n", " return sum(x != y for x, y in zip(X, Y))\n", @@ -791,7 +654,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:39.302076Z", @@ -800,15 +663,7 @@ "shell.execute_reply": "2026-06-27T12:55:39.315861Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Mean Boolean Error Distance between (1,2,3) and (1,4,5) is 0.6666666666666666\n" - ] - } - ], + "outputs": [], "source": [ "def mean_boolean_error(X, Y):\n", " return mean(int(x != y) for x, y in zip(X, Y))\n", @@ -829,7 +684,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:39.328793Z", @@ -838,15 +693,7 @@ "shell.execute_reply": "2026-06-27T12:55:39.342477Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Mean Error Distance between (1,0,5) and (3,10,5) is 4\n" - ] - } - ], + "outputs": [], "source": [ "def mean_error(X, Y):\n", " return mean([abs(x - y) for x, y in zip(X, Y)])\n", @@ -867,7 +714,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:39.353032Z", @@ -876,15 +723,7 @@ "shell.execute_reply": "2026-06-27T12:55:39.366279Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Mean Square Distance between (1,0,5) and (3,10,5) is 34.666666666666664\n" - ] - } - ], + "outputs": [], "source": [ "def ms_error(X, Y):\n", " return mean([(x - y)**2 for x, y in zip(X, Y)])\n", @@ -905,7 +744,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:39.372060Z", @@ -914,15 +753,7 @@ "shell.execute_reply": "2026-06-27T12:55:39.387901Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Root of Mean Error Distance between (1,0,5) and (3,10,5) is 5.887840577551898\n" - ] - } - ], + "outputs": [], "source": [ "def rms_error(X, Y):\n", " return math.sqrt(ms_error(X, Y))\n", @@ -960,9 +791,8 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": { - "collapsed": true, "execution": { "iopub.execute_input": "2026-06-27T12:55:39.399384Z", "iopub.status.busy": "2026-06-27T12:55:39.398967Z", @@ -970,132 +800,7 @@ "shell.execute_reply": "2026-06-27T12:55:39.520880Z" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
def PluralityLearner(dataset):\n",
-       "    """\n",
-       "    A very dumb algorithm: always pick the result that was most popular\n",
-       "    in the training data. Makes a baseline for comparison.\n",
-       "    """\n",
-       "    most_popular = mode([e[dataset.target] for e in dataset.examples])\n",
-       "\n",
-       "    def predict(example):\n",
-       "        """Always return same result: the most popular from the training set."""\n",
-       "        return most_popular\n",
-       "\n",
-       "    return predict\n",
-       "
\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "psource(PluralityLearner)" ] @@ -1120,7 +825,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:39.528793Z", @@ -1129,15 +834,7 @@ "shell.execute_reply": "2026-06-27T12:55:39.537742Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "mammal\n" - ] - } - ], + "outputs": [], "source": [ "zoo = DataSet(name=\"zoo\")\n", "\n", @@ -1190,9 +887,8 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": { - "collapsed": true, "execution": { "iopub.execute_input": "2026-06-27T12:55:39.550609Z", "iopub.status.busy": "2026-06-27T12:55:39.549998Z", @@ -1200,129 +896,7 @@ "shell.execute_reply": "2026-06-27T12:55:39.564510Z" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
def NearestNeighborLearner(dataset, k=1):\n",
-       "    """k-NearestNeighbor: the k nearest neighbors vote."""\n",
-       "\n",
-       "    def predict(example):\n",
-       "        """Find the k closest items, and have them vote for the best."""\n",
-       "        best = heapq.nsmallest(k, ((dataset.distance(e, example), e) for e in dataset.examples))\n",
-       "        return mode(e[dataset.target] for (d, e) in best)\n",
-       "\n",
-       "    return predict\n",
-       "
\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "psource(NearestNeighborLearner)" ] @@ -1347,7 +921,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:39.572104Z", @@ -1356,15 +930,7 @@ "shell.execute_reply": "2026-06-27T12:55:39.587958Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "setosa\n" - ] - } - ], + "outputs": [], "source": [ "iris = DataSet(name=\"iris\")\n", "\n", @@ -1414,7 +980,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:39.596196Z", @@ -1423,36 +989,7 @@ "shell.execute_reply": "2026-06-27T12:55:39.714362Z" } }, - "outputs": [ - { - "data": { - "text/markdown": [ - "### AIMA3e\n", - "__function__ DECISION-TREE-LEARNING(_examples_, _attributes_, _parent\\_examples_) __returns__ a tree \n", - " __if__ _examples_ is empty __then return__ PLURALITY\\-VALUE(_parent\\_examples_) \n", - " __else if__ all _examples_ have the same classification __then return__ the classification \n", - " __else if__ _attributes_ is empty __then return__ PLURALITY\\-VALUE(_examples_) \n", - " __else__ \n", - "   _A_ ← argmax_a_ ∈ _attributes_ IMPORTANCE(_a_, _examples_) \n", - "   _tree_ ← a new decision tree with root test _A_ \n", - "   __for each__ value _vk_ of _A_ __do__ \n", - "     _exs_ ← \\{ _e_ : _e_ ∈ _examples_ __and__ _e_._A_ = _vk_ \\} \n", - "     _subtree_ ← DECISION-TREE-LEARNING(_exs_, _attributes_ − _A_, _examples_) \n", - "     add a branch to _tree_ with label \\(_A_ = _vk_\\) and subtree _subtree_ \n", - "   __return__ _tree_ \n", - "\n", - "---\n", - "__Figure ??__ The decision\\-tree learning algorithm. The function IMPORTANCE is described in Section __??__. The function PLURALITY\\-VALUE selects the most common output value among a set of examples, breaking ties randomly." - ], - "text/plain": [ - "" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "pseudocode(\"Decision Tree Learning\")" ] @@ -1467,7 +1004,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:39.724691Z", @@ -1476,155 +1013,7 @@ "shell.execute_reply": "2026-06-27T12:55:39.859525Z" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
class DecisionFork:\n",
-       "    """\n",
-       "    A fork of a decision tree holds an attribute to test, and a dict\n",
-       "    of branches, one for each of the attribute's values.\n",
-       "    """\n",
-       "\n",
-       "    def __init__(self, attr, attr_name=None, default_child=None, branches=None):\n",
-       "        """Initialize by saying what attribute this node tests."""\n",
-       "        self.attr = attr\n",
-       "        self.attr_name = attr_name or attr\n",
-       "        self.default_child = default_child\n",
-       "        self.branches = branches or {}\n",
-       "\n",
-       "    def __call__(self, example):\n",
-       "        """Given an example, classify it using the attribute and the branches."""\n",
-       "        attr_val = example[self.attr]\n",
-       "        if attr_val in self.branches:\n",
-       "            return self.branches[attr_val](example)\n",
-       "        else:\n",
-       "            # return default class when attribute is unknown\n",
-       "            return self.default_child(example)\n",
-       "\n",
-       "    def add(self, val, subtree):\n",
-       "        """Add a branch. If self.attr = val, go to the given subtree."""\n",
-       "        self.branches[val] = subtree\n",
-       "\n",
-       "    def display(self, indent=0):\n",
-       "        name = self.attr_name\n",
-       "        print('Test', name)\n",
-       "        for (val, subtree) in self.branches.items():\n",
-       "            print(' ' * 4 * indent, name, '=', val, '==>', end=' ')\n",
-       "            subtree.display(indent + 1)\n",
-       "\n",
-       "    def __repr__(self):\n",
-       "        return 'DecisionFork({0!r}, {1!r}, {2!r})'.format(self.attr, self.attr_name, self.branches)\n",
-       "
\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "psource(DecisionFork)" ] @@ -1638,9 +1027,8 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "metadata": { - "collapsed": true, "execution": { "iopub.execute_input": "2026-06-27T12:55:39.877242Z", "iopub.status.busy": "2026-06-27T12:55:39.874487Z", @@ -1648,134 +1036,7 @@ "shell.execute_reply": "2026-06-27T12:55:39.967338Z" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
class DecisionLeaf:\n",
-       "    """A leaf of a decision tree holds just a result."""\n",
-       "\n",
-       "    def __init__(self, result):\n",
-       "        self.result = result\n",
-       "\n",
-       "    def __call__(self, example):\n",
-       "        return self.result\n",
-       "\n",
-       "    def display(self):\n",
-       "        print('RESULT =', self.result)\n",
-       "\n",
-       "    def __repr__(self):\n",
-       "        return repr(self.result)\n",
-       "
\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "psource(DecisionLeaf)" ] @@ -1789,9 +1050,8 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": null, "metadata": { - "collapsed": true, "execution": { "iopub.execute_input": "2026-06-27T12:55:39.982966Z", "iopub.status.busy": "2026-06-27T12:55:39.982541Z", @@ -1799,175 +1059,7 @@ "shell.execute_reply": "2026-06-27T12:55:40.009042Z" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
def DecisionTreeLearner(dataset):\n",
-       "    """[Figure 18.5]"""\n",
-       "\n",
-       "    target, values = dataset.target, dataset.values\n",
-       "\n",
-       "    def decision_tree_learning(examples, attrs, parent_examples=()):\n",
-       "        if len(examples) == 0:\n",
-       "            return plurality_value(parent_examples)\n",
-       "        if all_same_class(examples):\n",
-       "            return DecisionLeaf(examples[0][target])\n",
-       "        if len(attrs) == 0:\n",
-       "            return plurality_value(examples)\n",
-       "        A = choose_attribute(attrs, examples)\n",
-       "        tree = DecisionFork(A, dataset.attr_names[A], plurality_value(examples))\n",
-       "        for (v_k, exs) in split_by(A, examples):\n",
-       "            subtree = decision_tree_learning(exs, remove_all(A, attrs), examples)\n",
-       "            tree.add(v_k, subtree)\n",
-       "        return tree\n",
-       "\n",
-       "    def plurality_value(examples):\n",
-       "        """\n",
-       "        Return the most popular target value for this set of examples.\n",
-       "        (If target is binary, this is the majority; otherwise plurality).\n",
-       "        """\n",
-       "        popular = argmax_random_tie(values[target], key=lambda v: count(target, v, examples))\n",
-       "        return DecisionLeaf(popular)\n",
-       "\n",
-       "    def count(attr, val, examples):\n",
-       "        """Count the number of examples that have example[attr] = val."""\n",
-       "        return sum(e[attr] == val for e in examples)\n",
-       "\n",
-       "    def all_same_class(examples):\n",
-       "        """Are all these examples in the same target class?"""\n",
-       "        class0 = examples[0][target]\n",
-       "        return all(e[target] == class0 for e in examples)\n",
-       "\n",
-       "    def choose_attribute(attrs, examples):\n",
-       "        """Choose the attribute with the highest information gain."""\n",
-       "        return argmax_random_tie(attrs, key=lambda a: information_gain(a, examples))\n",
-       "\n",
-       "    def information_gain(attr, examples):\n",
-       "        """Return the expected reduction in entropy from splitting by attr."""\n",
-       "\n",
-       "        def I(examples):\n",
-       "            return information_content([count(target, v, examples) for v in values[target]])\n",
-       "\n",
-       "        n = len(examples)\n",
-       "        remainder = sum((len(examples_i) / n) * I(examples_i) for (v, examples_i) in split_by(attr, examples))\n",
-       "        return I(examples) - remainder\n",
-       "\n",
-       "    def split_by(attr, examples):\n",
-       "        """Return a list of (val, examples) pairs for each val of attr."""\n",
-       "        return [(v, [e for e in examples if e[attr] == v]) for v in values[attr]]\n",
-       "\n",
-       "    return decision_tree_learning(dataset.examples, dataset.inputs)\n",
-       "
\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "psource(DecisionTreeLearner)" ] @@ -1996,7 +1088,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:40.020013Z", @@ -2005,15 +1097,7 @@ "shell.execute_reply": "2026-06-27T12:55:40.046557Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "setosa\n" - ] - } - ], + "outputs": [], "source": [ "iris = DataSet(name=\"iris\")\n", "\n", @@ -2064,7 +1148,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:40.063393Z", @@ -2073,142 +1157,7 @@ "shell.execute_reply": "2026-06-27T12:55:40.082955Z" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
def RandomForest(dataset, n=5):\n",
-       "    """An ensemble of Decision Trees trained using bagging and feature bagging."""\n",
-       "\n",
-       "    def data_bagging(dataset, m=0):\n",
-       "        """Sample m examples with replacement"""\n",
-       "        n = len(dataset.examples)\n",
-       "        return weighted_sample_with_replacement(m or n, dataset.examples, [1] * n)\n",
-       "\n",
-       "    def feature_bagging(dataset, p=0.7):\n",
-       "        """Feature bagging with probability p to retain an attribute"""\n",
-       "        inputs = [i for i in dataset.inputs if probability(p)]\n",
-       "        return inputs or dataset.inputs\n",
-       "\n",
-       "    def predict(example):\n",
-       "        print([predictor(example) for predictor in predictors])\n",
-       "        return mode(predictor(example) for predictor in predictors)\n",
-       "\n",
-       "    predictors = [DecisionTreeLearner(DataSet(examples=data_bagging(dataset), attrs=dataset.attrs,\n",
-       "                                              attr_names=dataset.attr_names, target=dataset.target,\n",
-       "                                              inputs=feature_bagging(dataset))) for _ in range(n)]\n",
-       "\n",
-       "    return predict\n",
-       "
\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "psource(RandomForest)" ] @@ -2227,7 +1176,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:40.091994Z", @@ -2236,16 +1185,7 @@ "shell.execute_reply": "2026-06-27T12:55:40.148387Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['setosa', 'setosa', 'setosa', 'versicolor', 'setosa']\n", - "setosa\n" - ] - } - ], + "outputs": [], "source": [ "iris = DataSet(name=\"iris\")\n", "\n", @@ -2368,7 +1308,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:40.155239Z", @@ -2377,16 +1317,7 @@ "shell.execute_reply": "2026-06-27T12:55:40.172313Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.3333333333333333\n", - "0.10588235294117647\n" - ] - } - ], + "outputs": [], "source": [ "dataset = iris\n", "\n", @@ -2417,7 +1348,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:40.177520Z", @@ -2426,15 +1357,7 @@ "shell.execute_reply": "2026-06-27T12:55:40.184360Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "setosa\n" - ] - } - ], + "outputs": [], "source": [ "def predict(example):\n", " def class_probability(targetval):\n", @@ -2456,9 +1379,8 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": { - "collapsed": true, "execution": { "iopub.execute_input": "2026-06-27T12:55:40.191002Z", "iopub.status.busy": "2026-06-27T12:55:40.189990Z", @@ -2466,149 +1388,7 @@ "shell.execute_reply": "2026-06-27T12:55:40.207715Z" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
def NaiveBayesDiscrete(dataset):\n",
-       "    """\n",
-       "    Just count how many times each value of each input attribute\n",
-       "    occurs, conditional on the target value. Count the different\n",
-       "    target values too.\n",
-       "    """\n",
-       "\n",
-       "    target_vals = dataset.values[dataset.target]\n",
-       "    target_dist = CountingProbDist(target_vals)\n",
-       "    attr_dists = {(gv, attr): CountingProbDist(dataset.values[attr]) for gv in target_vals for attr in dataset.inputs}\n",
-       "    for example in dataset.examples:\n",
-       "        target_val = example[dataset.target]\n",
-       "        target_dist.add(target_val)\n",
-       "        for attr in dataset.inputs:\n",
-       "            attr_dists[target_val, attr].add(example[attr])\n",
-       "\n",
-       "    def predict(example):\n",
-       "        """\n",
-       "        Predict the target value for example. Consider each possible value,\n",
-       "        and pick the most likely by looking at each attribute independently.\n",
-       "        """\n",
-       "\n",
-       "        def class_probability(target_val):\n",
-       "            return (target_dist[target_val] * product(attr_dists[target_val, attr][example[attr]]\n",
-       "                                                      for attr in dataset.inputs))\n",
-       "\n",
-       "        return max(target_vals, key=class_probability)\n",
-       "\n",
-       "    return predict\n",
-       "
\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "psource(NaiveBayesDiscrete)" ] @@ -2624,7 +1404,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:40.219592Z", @@ -2633,16 +1413,7 @@ "shell.execute_reply": "2026-06-27T12:55:40.239243Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[5.006, 3.418, 1.464, 0.244]\n", - "[0.5161711470638634, 0.3137983233784114, 0.46991097723995795, 0.19775268000454405]\n" - ] - } - ], + "outputs": [], "source": [ "means, deviations = dataset.find_means_and_deviations()\n", "\n", @@ -2667,7 +1438,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:40.247667Z", @@ -2676,15 +1447,7 @@ "shell.execute_reply": "2026-06-27T12:55:40.264593Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "setosa\n" - ] - } - ], + "outputs": [], "source": [ "def predict(example):\n", " def class_probability(targetval):\n", @@ -2708,9 +1471,8 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": null, "metadata": { - "collapsed": true, "execution": { "iopub.execute_input": "2026-06-27T12:55:40.271469Z", "iopub.status.busy": "2026-06-27T12:55:40.271088Z", @@ -2718,143 +1480,7 @@ "shell.execute_reply": "2026-06-27T12:55:40.289892Z" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
def NaiveBayesContinuous(dataset):\n",
-       "    """\n",
-       "    Count how many times each target value occurs.\n",
-       "    Also, find the means and deviations of input attribute values for each target value.\n",
-       "    """\n",
-       "    means, deviations = dataset.find_means_and_deviations()\n",
-       "\n",
-       "    target_vals = dataset.values[dataset.target]\n",
-       "    target_dist = CountingProbDist(target_vals)\n",
-       "\n",
-       "    def predict(example):\n",
-       "        """Predict the target value for example. Consider each possible value,\n",
-       "        and pick the most likely by looking at each attribute independently."""\n",
-       "\n",
-       "        def class_probability(target_val):\n",
-       "            prob = target_dist[target_val]\n",
-       "            for attr in dataset.inputs:\n",
-       "                prob *= gaussian(means[target_val][attr], deviations[target_val][attr], example[attr])\n",
-       "            return prob\n",
-       "\n",
-       "        return max(target_vals, key=class_probability)\n",
-       "\n",
-       "    return predict\n",
-       "
\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "psource(NaiveBayesContinuous)" ] @@ -2874,9 +1500,8 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": null, "metadata": { - "collapsed": true, "execution": { "iopub.execute_input": "2026-06-27T12:55:40.303006Z", "iopub.status.busy": "2026-06-27T12:55:40.302569Z", @@ -2884,141 +1509,7 @@ "shell.execute_reply": "2026-06-27T12:55:40.330606Z" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
def NaiveBayesSimple(distribution):\n",
-       "    """\n",
-       "    A simple naive bayes classifier that takes as input a dictionary of\n",
-       "    CountingProbDist objects and classifies items according to these distributions.\n",
-       "    The input dictionary is in the following form:\n",
-       "        (ClassName, ClassProb): CountingProbDist\n",
-       "    """\n",
-       "    target_dist = {c_name: prob for c_name, prob in distribution.keys()}\n",
-       "    attr_dists = {c_name: count_prob for (c_name, _), count_prob in distribution.items()}\n",
-       "\n",
-       "    def predict(example):\n",
-       "        """Predict the target value for example. Calculate probabilities for each\n",
-       "        class and pick the max."""\n",
-       "\n",
-       "        def class_probability(target_val):\n",
-       "            attr_dist = attr_dists[target_val]\n",
-       "            return target_dist[target_val] * product(attr_dist[a] for a in example)\n",
-       "\n",
-       "        return max(target_dist.keys(), key=class_probability)\n",
-       "\n",
-       "    return predict\n",
-       "
\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "psource(NaiveBayesSimple)" ] @@ -3041,7 +1532,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:40.340479Z", @@ -3050,23 +1541,7 @@ "shell.execute_reply": "2026-06-27T12:55:40.354533Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Discrete Classifier\n", - "setosa\n", - "setosa\n", - "setosa\n", - "\n", - "Continuous Classifier\n", - "setosa\n", - "versicolor\n", - "virginica\n" - ] - } - ], + "outputs": [], "source": [ "nBD = NaiveBayesLearner(iris, continuous=False)\n", "print(\"Discrete Classifier\")\n", @@ -3095,9 +1570,8 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": null, "metadata": { - "collapsed": true, "execution": { "iopub.execute_input": "2026-06-27T12:55:40.362294Z", "iopub.status.busy": "2026-06-27T12:55:40.361867Z", @@ -3124,9 +1598,8 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": null, "metadata": { - "collapsed": true, "execution": { "iopub.execute_input": "2026-06-27T12:55:40.377137Z", "iopub.status.busy": "2026-06-27T12:55:40.376766Z", @@ -3149,7 +1622,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:40.390374Z", @@ -3158,17 +1631,7 @@ "shell.execute_reply": "2026-06-27T12:55:40.406624Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "First\n", - "Second\n", - "Third\n" - ] - } - ], + "outputs": [], "source": [ "print(nBS('aab')) # We can handle strings\n", "print(nBS(['b', 'b'])) # And lists!\n", @@ -3219,9 +1682,8 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": null, "metadata": { - "collapsed": true, "execution": { "iopub.execute_input": "2026-06-27T12:55:40.419072Z", "iopub.status.busy": "2026-06-27T12:55:40.418702Z", @@ -3229,140 +1691,7 @@ "shell.execute_reply": "2026-06-27T12:55:40.439149Z" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
def PerceptronLearner(dataset, learning_rate=0.01, epochs=100):\n",
-       "    """Logistic Regression, NO hidden layer"""\n",
-       "    i_units = len(dataset.inputs)\n",
-       "    o_units = len(dataset.values[dataset.target])\n",
-       "    hidden_layer_sizes = []\n",
-       "    raw_net = network(i_units, hidden_layer_sizes, o_units)\n",
-       "    learned_net = BackPropagationLearner(dataset, raw_net, learning_rate, epochs)\n",
-       "\n",
-       "    def predict(example):\n",
-       "        o_nodes = learned_net[1]\n",
-       "\n",
-       "        # forward pass\n",
-       "        for node in o_nodes:\n",
-       "            in_val = dot_product(example, node.weights)\n",
-       "            node.value = node.activation(in_val)\n",
-       "\n",
-       "        # hypothesis\n",
-       "        return find_max_node(o_nodes)\n",
-       "\n",
-       "    return predict\n",
-       "
\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "psource(PerceptronLearner)" ] @@ -3387,7 +1716,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:40.450973Z", @@ -3396,15 +1725,7 @@ "shell.execute_reply": "2026-06-27T12:55:41.305276Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0\n" - ] - } - ], + "outputs": [], "source": [ "iris = DataSet(name=\"iris\")\n", "iris.classes_to_numbers()\n", @@ -3440,7 +1761,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:41.321375Z", @@ -3449,159 +1770,7 @@ "shell.execute_reply": "2026-06-27T12:55:41.366214Z" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
def LinearLearner(dataset, learning_rate=0.01, epochs=100):\n",
-       "    """\n",
-       "    [Section 18.6.3]\n",
-       "    Linear classifier with hard threshold.\n",
-       "    """\n",
-       "    idx_i = dataset.inputs\n",
-       "    idx_t = dataset.target\n",
-       "    examples = dataset.examples\n",
-       "    num_examples = len(examples)\n",
-       "\n",
-       "    # X transpose: the actual value of each input feature across the examples\n",
-       "    X_col = [[example[i] for example in examples] for i in idx_i]  # vertical columns of X\n",
-       "\n",
-       "    # add dummy\n",
-       "    ones = [1 for _ in range(len(examples))]\n",
-       "    X_col = [ones] + X_col\n",
-       "\n",
-       "    # initialize random weights\n",
-       "    num_weights = len(idx_i) + 1\n",
-       "    w = random_weights(min_value=-0.5, max_value=0.5, num_weights=num_weights)\n",
-       "\n",
-       "    for epoch in range(epochs):\n",
-       "        err = []\n",
-       "        # pass over all examples\n",
-       "        for example in examples:\n",
-       "            x = [1] + [example[i] for i in idx_i]\n",
-       "            y = np.dot(w, x)\n",
-       "            t = example[idx_t]\n",
-       "            err.append(t - y)\n",
-       "\n",
-       "        # update weights\n",
-       "        for i in range(len(w)):\n",
-       "            w[i] = w[i] + learning_rate * (np.dot(err, X_col[i]) / num_examples)\n",
-       "\n",
-       "    def predict(example):\n",
-       "        x = [1] + [example[i] for i in idx_i]\n",
-       "        return np.dot(w, x)\n",
-       "\n",
-       "    return predict\n",
-       "
\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "psource(LinearLearner)" ] @@ -3619,7 +1788,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:41.377922Z", @@ -3628,15 +1797,7 @@ "shell.execute_reply": "2026-06-27T12:55:41.534600Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "-0.30907513960973826\n" - ] - } - ], + "outputs": [], "source": [ "iris = DataSet(name=\"iris\")\n", "iris.classes_to_numbers()\n", @@ -3675,7 +1836,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:41.546856Z", @@ -3684,132 +1845,7 @@ "shell.execute_reply": "2026-06-27T12:55:41.556249Z" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
def EnsembleLearner(learners):\n",
-       "    """Given a list of learning algorithms, have them vote."""\n",
-       "\n",
-       "    def train(dataset):\n",
-       "        predictors = [learner(dataset) for learner in learners]\n",
-       "\n",
-       "        def predict(example):\n",
-       "            return mode(predictor(example) for predictor in predictors)\n",
-       "\n",
-       "        return predict\n",
-       "\n",
-       "    return train\n",
-       "
\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "psource(EnsembleLearner)" ] @@ -3832,9 +1868,8 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": null, "metadata": { - "collapsed": true, "execution": { "iopub.execute_input": "2026-06-27T12:55:41.564293Z", "iopub.status.busy": "2026-06-27T12:55:41.563983Z", @@ -3858,7 +1893,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:41.582307Z", @@ -3867,16 +1902,7 @@ "shell.execute_reply": "2026-06-27T12:55:41.605011Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Error ratio for Discrete: 0.033333333333333326\n", - "Error ratio for Continuous: 0.040000000000000036\n" - ] - } - ], + "outputs": [], "source": [ "nBD = NaiveBayesLearner(iris, continuous=False)\n", "print(\"Error ratio for Discrete:\", err_ratio(nBD, iris))\n", @@ -3903,7 +1929,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:41.609363Z", @@ -3912,36 +1938,7 @@ "shell.execute_reply": "2026-06-27T12:55:43.663781Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Error ratio for k=1: 0.0\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Error ratio for k=3: 0.06000000000000005\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Error ratio for k=5: 0.1266666666666667\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Error ratio for k=7: 0.19999999999999996\n" - ] - } - ], + "outputs": [], "source": [ "kNN_1 = NearestNeighborLearner(iris, k=1)\n", "kNN_3 = NearestNeighborLearner(iris, k=3)\n", @@ -3974,7 +1971,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:43.672457Z", @@ -3983,15 +1980,7 @@ "shell.execute_reply": "2026-06-27T12:55:44.370195Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Error ratio for Perceptron: 0.31333333333333335\n" - ] - } - ], + "outputs": [], "source": [ "iris2 = DataSet(name=\"iris\")\n", "iris2.classes_to_numbers()\n", @@ -4039,7 +2028,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:44.385682Z", @@ -4048,140 +2037,7 @@ "shell.execute_reply": "2026-06-27T12:55:44.419458Z" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
def ada_boost(dataset, L, K):\n",
-       "    """[Figure 18.34]"""\n",
-       "\n",
-       "    examples, target = dataset.examples, dataset.target\n",
-       "    n = len(examples)\n",
-       "    eps = 1 / (2 * n)\n",
-       "    w = [1 / n] * n\n",
-       "    h, z = [], []\n",
-       "    for k in range(K):\n",
-       "        h_k = L(dataset, w)\n",
-       "        h.append(h_k)\n",
-       "        error = sum(weight for example, weight in zip(examples, w) if example[target] != h_k(example))\n",
-       "        # avoid divide-by-0 from either 0% or 100% error rates\n",
-       "        error = np.clip(error, eps, 1 - eps)\n",
-       "        for j, example in enumerate(examples):\n",
-       "            if example[target] == h_k(example):\n",
-       "                w[j] *= error / (1 - error)\n",
-       "        w = normalize(w)\n",
-       "        z.append(np.log((1 - error) / error))\n",
-       "    return weighted_majority(h, z)\n",
-       "
\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "psource(ada_boost)" ] @@ -4198,9 +2054,8 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": null, "metadata": { - "collapsed": true, "execution": { "iopub.execute_input": "2026-06-27T12:55:44.430078Z", "iopub.status.busy": "2026-06-27T12:55:44.429620Z", @@ -4208,131 +2063,7 @@ "shell.execute_reply": "2026-06-27T12:55:44.442762Z" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
def WeightedLearner(unweighted_learner):\n",
-       "    """\n",
-       "    [Page 749 footnote 14]\n",
-       "    Given a learner that takes just an unweighted dataset, return\n",
-       "    one that takes also a weight for each example.\n",
-       "    """\n",
-       "\n",
-       "    def train(dataset, weights):\n",
-       "        return unweighted_learner(replicated_dataset(dataset, weights))\n",
-       "\n",
-       "    return train\n",
-       "
\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "psource(WeightedLearner)" ] @@ -4355,9 +2086,8 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": null, "metadata": { - "collapsed": true, "execution": { "iopub.execute_input": "2026-06-27T12:55:44.450641Z", "iopub.status.busy": "2026-06-27T12:55:44.450204Z", @@ -4372,7 +2102,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:44.461594Z", @@ -4381,18 +2111,7 @@ "shell.execute_reply": "2026-06-27T12:55:49.885324Z" } }, - "outputs": [ - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 57, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "iris2 = DataSet(name=\"iris\")\n", "iris2.classes_to_numbers()\n", @@ -4411,7 +2130,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:49.903548Z", @@ -4420,15 +2139,7 @@ "shell.execute_reply": "2026-06-27T12:55:50.003020Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Error ratio for adaboost: 0.013333333333333308\n" - ] - } - ], + "outputs": [], "source": [ "print(\"Error ratio for adaboost: \", err_ratio(adaboost, iris2))" ] @@ -4460,7 +2171,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:50.031572Z", @@ -4469,151 +2180,7 @@ "shell.execute_reply": "2026-06-27T12:55:50.100632Z" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
def cross_validation(learner, dataset, size=None, k=10, trials=1):\n",
-       "    """\n",
-       "    Do k-fold cross_validate and return their mean.\n",
-       "    That is, keep out 1/k of the examples for testing on each of k runs.\n",
-       "    Shuffle the examples first; if trials > 1, average over several shuffles.\n",
-       "    Returns Training error, Validation error\n",
-       "    """\n",
-       "    k = k or len(dataset.examples)\n",
-       "    if trials > 1:\n",
-       "        trial_errT = 0\n",
-       "        trial_errV = 0\n",
-       "        for t in range(trials):\n",
-       "            errT, errV = cross_validation(learner, dataset, size, k, trials)\n",
-       "            trial_errT += errT\n",
-       "            trial_errV += errV\n",
-       "        return trial_errT / trials, trial_errV / trials\n",
-       "    else:\n",
-       "        fold_errT = 0\n",
-       "        fold_errV = 0\n",
-       "        n = len(dataset.examples)\n",
-       "        examples = dataset.examples\n",
-       "        random.shuffle(dataset.examples)\n",
-       "        for fold in range(k):\n",
-       "            train_data, val_data = train_test_split(dataset, fold * (n // k), (fold + 1) * (n // k))\n",
-       "            dataset.examples = train_data\n",
-       "            h = learner(dataset, size)\n",
-       "            fold_errT += err_ratio(h, dataset, train_data)\n",
-       "            fold_errV += err_ratio(h, dataset, val_data)\n",
-       "            # reverting back to original once test is completed\n",
-       "            dataset.examples = examples\n",
-       "        return fold_errT / k, fold_errV / k\n",
-       "
\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "psource(cross_validation)" ] @@ -4627,7 +2194,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T12:55:50.115975Z", @@ -4636,16 +2203,7 @@ "shell.execute_reply": "2026-06-27T12:55:54.601766Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "training error: 0.090\n", - "validation error: 0.287\n" - ] - } - ], + "outputs": [], "source": [ "# cross-validate a k-NN learner on the iris dataset.\n", "# cross_validation calls learner(dataset, size), so we let `size` be the\n", diff --git a/notebooks/planning_partial_order_planner.ipynb b/notebooks/planning_partial_order_planner.ipynb index 7c8578604..1cf5fe122 100644 --- a/notebooks/planning_partial_order_planner.ipynb +++ b/notebooks/planning_partial_order_planner.ipynb @@ -30,7 +30,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T10:32:40.235651Z", @@ -47,7 +47,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T10:32:48.621379Z", @@ -56,496 +56,7 @@ "shell.execute_reply": "2026-06-27T10:32:49.670955Z" } }, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
class PartialOrderPlanner:\n",
-       "    """\n",
-       "    [Section 10.13] PARTIAL-ORDER-PLANNER\n",
-       "\n",
-       "    Partially ordered plans are created by a search through the space of plans\n",
-       "    rather than a search through the state space. It views planning as a refinement of partially ordered plans.\n",
-       "    A partially ordered plan is defined by a set of actions and a set of constraints of the form A < B,\n",
-       "    which denotes that action A has to be performed before action B.\n",
-       "    To summarize the working of a partial order planner,\n",
-       "    1. An open precondition is selected (a sub-goal that we want to achieve).\n",
-       "    2. An action that fulfils the open precondition is chosen.\n",
-       "    3. Temporal constraints are updated.\n",
-       "    4. Existing causal links are protected. Protection is a method that checks if the causal links conflict\n",
-       "       and if they do, temporal constraints are added to fix the threats.\n",
-       "    5. The set of open preconditions is updated.\n",
-       "    6. Temporal constraints of the selected action and the next action are established.\n",
-       "    7. A new causal link is added between the selected action and the owner of the open precondition.\n",
-       "    8. The set of new causal links is checked for threats and if found, the threat is removed by either promotion or\n",
-       "       demotion. If promotion or demotion is unable to solve the problem, the planning problem cannot be solved with\n",
-       "       the current sequence of actions or it may not be solvable at all.\n",
-       "    9. These steps are repeated until the set of open preconditions is empty.\n",
-       "    """\n",
-       "\n",
-       "    def __init__(self, planning_problem):\n",
-       "        self.tries = 1\n",
-       "        # safety bounds for the backtracking search in execute(): the maximum\n",
-       "        # number of actions a plan may contain (iterative-deepening target) and\n",
-       "        # the maximum number of node expansions per deepening level\n",
-       "        self._max_plan_actions = 12\n",
-       "        self._max_expansions = 20000\n",
-       "        self.planning_problem = planning_problem\n",
-       "        self.causal_links = []\n",
-       "        self.start = Action('Start', [], self.planning_problem.initial)\n",
-       "        self.finish = Action('Finish', self.planning_problem.goals, [])\n",
-       "        self.actions = set()\n",
-       "        self.actions.add(self.start)\n",
-       "        self.actions.add(self.finish)\n",
-       "        self.constraints = set()\n",
-       "        self.constraints.add((self.start, self.finish))\n",
-       "        self.agenda = set()\n",
-       "        for precond in self.finish.precond:\n",
-       "            self.agenda.add((precond, self.finish))\n",
-       "        self.expanded_actions = planning_problem.expand_actions()\n",
-       "\n",
-       "    def find_open_precondition(self):\n",
-       "        """\n",
-       "        Find the open precondition with the least number of achieving actions\n",
-       "        (a most-constrained-variable heuristic). Returns the triple\n",
-       "        (precondition, action_that_needs_it, [achieving_actions]). Iteration is\n",
-       "        ordered deterministically so the search does not depend on set/hash\n",
-       "        ordering. Returns (None, None, None) when some open precondition has no\n",
-       "        achiever at all, which is a dead end for the current partial plan.\n",
-       "        """\n",
-       "        possible_actions = list(self.actions) + self.expanded_actions\n",
-       "        number_of_ways = dict()\n",
-       "        actions_for_precondition = dict()\n",
-       "        for open_precondition, act in sorted(self.agenda, key=str):\n",
-       "            if open_precondition in number_of_ways:\n",
-       "                continue\n",
-       "            achievers = [action for action in possible_actions\n",
-       "                         if any(effect == open_precondition for effect in action.effect)]\n",
-       "            if not achievers:\n",
-       "                return None, None, None\n",
-       "            number_of_ways[open_precondition] = len(achievers)\n",
-       "            actions_for_precondition[open_precondition] = achievers\n",
-       "\n",
-       "        if not number_of_ways:\n",
-       "            return None, None, None\n",
-       "\n",
-       "        chosen = min(number_of_ways, key=lambda p: (number_of_ways[p], str(p)))\n",
-       "        act1 = next(act for precond, act in sorted(self.agenda, key=str) if precond == chosen)\n",
-       "        return chosen, act1, actions_for_precondition[chosen]\n",
-       "\n",
-       "    def find_action_for_precondition(self, oprec):\n",
-       "        """Find action for a given precondition"""\n",
-       "\n",
-       "        # either\n",
-       "        #   choose act0 E Actions such that act0 achieves G\n",
-       "        for action in self.actions:\n",
-       "            for effect in action.effect:\n",
-       "                if effect == oprec:\n",
-       "                    return action, 0\n",
-       "\n",
-       "        # or\n",
-       "        #   choose act0 E Actions such that act0 achieves G\n",
-       "        for action in self.planning_problem.actions:\n",
-       "            for effect in action.effect:\n",
-       "                if effect.op == oprec.op:\n",
-       "                    bindings = unify_mm(effect, oprec)\n",
-       "                    if bindings is None:\n",
-       "                        break\n",
-       "                    return action, bindings\n",
-       "\n",
-       "    def generate_expr(self, clause, bindings):\n",
-       "        """Generate atomic expression from generic expression given variable bindings"""\n",
-       "\n",
-       "        new_args = []\n",
-       "        for arg in clause.args:\n",
-       "            if arg in bindings:\n",
-       "                new_args.append(bindings[arg])\n",
-       "            else:\n",
-       "                new_args.append(arg)\n",
-       "\n",
-       "        try:\n",
-       "            return Expr(str(clause.name), *new_args)\n",
-       "        except:\n",
-       "            return Expr(str(clause.op), *new_args)\n",
-       "\n",
-       "    def generate_action_object(self, action, bindings):\n",
-       "        """Generate action object given a generic action and variable bindings"""\n",
-       "\n",
-       "        # if bindings is 0, it means the action already exists in self.actions\n",
-       "        if bindings == 0:\n",
-       "            return action\n",
-       "\n",
-       "        # bindings cannot be None\n",
-       "        else:\n",
-       "            new_expr = self.generate_expr(action, bindings)\n",
-       "            new_preconds = []\n",
-       "            for precond in action.precond:\n",
-       "                new_precond = self.generate_expr(precond, bindings)\n",
-       "                new_preconds.append(new_precond)\n",
-       "            new_effects = []\n",
-       "            for effect in action.effect:\n",
-       "                new_effect = self.generate_expr(effect, bindings)\n",
-       "                new_effects.append(new_effect)\n",
-       "            return Action(new_expr, new_preconds, new_effects)\n",
-       "\n",
-       "    def cyclic(self, graph):\n",
-       "        """Check cyclicity of a directed graph"""\n",
-       "\n",
-       "        new_graph = dict()\n",
-       "        for element in graph:\n",
-       "            if element[0] in new_graph:\n",
-       "                new_graph[element[0]].append(element[1])\n",
-       "            else:\n",
-       "                new_graph[element[0]] = [element[1]]\n",
-       "\n",
-       "        path = set()\n",
-       "\n",
-       "        def visit(vertex):\n",
-       "            path.add(vertex)\n",
-       "            for neighbor in new_graph.get(vertex, ()):\n",
-       "                if neighbor in path or visit(neighbor):\n",
-       "                    return True\n",
-       "            path.remove(vertex)\n",
-       "            return False\n",
-       "\n",
-       "        value = any(visit(v) for v in new_graph)\n",
-       "        return value\n",
-       "\n",
-       "    def add_const(self, constraint, constraints):\n",
-       "        """Add the constraint to constraints if the resulting graph is acyclic"""\n",
-       "\n",
-       "        if constraint[0] == self.finish or constraint[1] == self.start:\n",
-       "            return constraints\n",
-       "\n",
-       "        new_constraints = set(constraints)\n",
-       "        new_constraints.add(constraint)\n",
-       "\n",
-       "        if self.cyclic(new_constraints):\n",
-       "            return constraints\n",
-       "        return new_constraints\n",
-       "\n",
-       "    def is_a_threat(self, precondition, effect):\n",
-       "        """Check if effect is a threat to precondition"""\n",
-       "\n",
-       "        if (str(effect.op) == 'Not' + str(precondition.op)) or ('Not' + str(effect.op) == str(precondition.op)):\n",
-       "            if effect.args == precondition.args:\n",
-       "                return True\n",
-       "        return False\n",
-       "\n",
-       "    def protect(self, causal_link, action, constraints):\n",
-       "        """Check and resolve threats by promotion or demotion"""\n",
-       "\n",
-       "        threat = False\n",
-       "        for effect in action.effect:\n",
-       "            if self.is_a_threat(causal_link[1], effect):\n",
-       "                threat = True\n",
-       "                break\n",
-       "\n",
-       "        if action != causal_link[0] and action != causal_link[2] and threat:\n",
-       "            # try promotion\n",
-       "            new_constraints = set(constraints)\n",
-       "            new_constraints.add((action, causal_link[0]))\n",
-       "            if not self.cyclic(new_constraints):\n",
-       "                constraints = self.add_const((action, causal_link[0]), constraints)\n",
-       "            else:\n",
-       "                # try demotion\n",
-       "                new_constraints = set(constraints)\n",
-       "                new_constraints.add((causal_link[2], action))\n",
-       "                if not self.cyclic(new_constraints):\n",
-       "                    constraints = self.add_const((causal_link[2], action), constraints)\n",
-       "                else:\n",
-       "                    # both promotion and demotion fail\n",
-       "                    print('Unable to resolve a threat caused by', action, 'onto', causal_link)\n",
-       "                    return\n",
-       "        return constraints\n",
-       "\n",
-       "    def convert(self, constraints):\n",
-       "        """Convert constraints into a dict of Action to set orderings"""\n",
-       "\n",
-       "        graph = dict()\n",
-       "        for constraint in constraints:\n",
-       "            if constraint[0] in graph:\n",
-       "                graph[constraint[0]].add(constraint[1])\n",
-       "            else:\n",
-       "                graph[constraint[0]] = set()\n",
-       "                graph[constraint[0]].add(constraint[1])\n",
-       "        return graph\n",
-       "\n",
-       "    def toposort(self, graph):\n",
-       "        """Generate topological ordering of constraints"""\n",
-       "\n",
-       "        if len(graph) == 0:\n",
-       "            return\n",
-       "\n",
-       "        graph = graph.copy()\n",
-       "\n",
-       "        for k, v in graph.items():\n",
-       "            v.discard(k)\n",
-       "\n",
-       "        extra_elements_in_dependencies = _reduce(set.union, graph.values()) - set(graph.keys())\n",
-       "\n",
-       "        graph.update({element: set() for element in extra_elements_in_dependencies})\n",
-       "        while True:\n",
-       "            ordered = set(element for element, dependency in graph.items() if len(dependency) == 0)\n",
-       "            if not ordered:\n",
-       "                break\n",
-       "            yield ordered\n",
-       "            graph = {element: (dependency - ordered)\n",
-       "                     for element, dependency in graph.items()\n",
-       "                     if element not in ordered}\n",
-       "        if len(graph) != 0:\n",
-       "            raise ValueError('The graph is not acyclic and cannot be linearly ordered')\n",
-       "\n",
-       "    def display_plan(self):\n",
-       "        """Display causal links, constraints and the plan"""\n",
-       "\n",
-       "        print('Causal Links')\n",
-       "        for causal_link in self.causal_links:\n",
-       "            print(causal_link)\n",
-       "\n",
-       "        print('\\n_constraints')\n",
-       "        for constraint in self.constraints:\n",
-       "            print(constraint[0], '<', constraint[1])\n",
-       "\n",
-       "        print('\\n_partial Order Plan')\n",
-       "        print(list(reversed(list(self.toposort(self.convert(self.constraints))))))\n",
-       "\n",
-       "    def execute(self, display=True):\n",
-       "        """\n",
-       "        Execute the algorithm with backtracking, using iterative deepening on the\n",
-       "        number of actions in the plan. The original greedy version committed to\n",
-       "        the first achiever it happened to iterate over and could not recover when\n",
-       "        that action's own preconditions turned out to be unsatisfiable, so it\n",
-       "        depended on hash ordering and often printed 'Probably Wrong' / "Couldn't\n",
-       "        find a solution". Backtracking over both action choices and threat\n",
-       "        resolution (promotion vs demotion), together with the deterministic\n",
-       "        selection in find_open_precondition and a smallest-plan-first deepening\n",
-       "        bound, makes the planner solve the standard problems reproducibly and\n",
-       "        return a short, valid plan.\n",
-       "        """\n",
-       "        pristine = self._snapshot()\n",
-       "        for limit in range(1, self._max_plan_actions + 1):\n",
-       "            self._restore(pristine)\n",
-       "            if self._search([self._max_expansions], limit):\n",
-       "                if display:\n",
-       "                    self.display_plan()\n",
-       "                else:\n",
-       "                    return self.constraints, self.causal_links\n",
-       "                return\n",
-       "        print("Couldn't find a solution")\n",
-       "        if not display:\n",
-       "            return None, None\n",
-       "\n",
-       "    def _reachable(self, source, target):\n",
-       "        """True if target is forced to come after source by the ordering constraints"""\n",
-       "\n",
-       "        stack, seen = [source], set()\n",
-       "        while stack:\n",
-       "            node = stack.pop()\n",
-       "            if node == target:\n",
-       "                return True\n",
-       "            if node in seen:\n",
-       "                continue\n",
-       "            seen.add(node)\n",
-       "            stack.extend(b for a, b in self.constraints if a == node)\n",
-       "        return False\n",
-       "\n",
-       "    def _open_threat(self):\n",
-       "        """\n",
-       "        Return an (action, causal_link) threat that is not yet resolved by the\n",
-       "        ordering constraints, or None if every causal link is protected. A\n",
-       "        causal link (a0, p, a1) is threatened by an action whose effect negates p\n",
-       "        unless the action is already ordered before a0 (promotion) or after a1\n",
-       "        (demotion).\n",
-       "        """\n",
-       "        for a0, p, a1 in self.causal_links:\n",
-       "            for action in self.actions:\n",
-       "                if action == a0 or action == a1:\n",
-       "                    continue\n",
-       "                if any(self.is_a_threat(p, effect) for effect in action.effect):\n",
-       "                    if not (self._reachable(action, a0) or self._reachable(a1, action)):\n",
-       "                        return action, (a0, p, a1)\n",
-       "        return None\n",
-       "\n",
-       "    def _snapshot(self):\n",
-       "        return set(self.actions), set(self.constraints), list(self.causal_links), set(self.agenda)\n",
-       "\n",
-       "    def _restore(self, snapshot):\n",
-       "        self.actions, self.constraints, self.causal_links, self.agenda = (\n",
-       "            set(snapshot[0]), set(snapshot[1]), list(snapshot[2]), set(snapshot[3]))\n",
-       "\n",
-       "    def _search(self, budget, limit):\n",
-       "        """\n",
-       "        Recursively complete the partial plan, backtracking on failure. Three\n",
-       "        kinds of choice points are explored: which action satisfies an open\n",
-       "        precondition, how each threat is resolved (promotion vs demotion), and -\n",
-       "        bounded by 'limit' - whether to introduce a new action at all. Returns\n",
-       "        True and leaves the solution in self.* on success.\n",
-       "        """\n",
-       "        if budget[0] <= 0:\n",
-       "            return False\n",
-       "        budget[0] -= 1\n",
-       "\n",
-       "        # first, resolve any outstanding threat to a causal link (choice point)\n",
-       "        threat = self._open_threat()\n",
-       "        if threat is not None:\n",
-       "            action, (a0, p, a1) = threat\n",
-       "            snapshot = self._snapshot()\n",
-       "            for ordering in ((action, a0), (a1, action)):  # promotion, then demotion\n",
-       "                new_constraints = self.add_const(ordering, self.constraints)\n",
-       "                if ordering in new_constraints:  # ordering was consistent (acyclic and allowed)\n",
-       "                    self.constraints = new_constraints\n",
-       "                    if self._search(budget, limit):\n",
-       "                        return True\n",
-       "                self._restore(snapshot)\n",
-       "            return False\n",
-       "\n",
-       "        # no open threats: a plan with an empty agenda is a complete solution\n",
-       "        if not self.agenda:\n",
-       "            return True\n",
-       "\n",
-       "        # select <G, act1> from the agenda (most-constrained precondition first)\n",
-       "        G, act1, possible_actions = self.find_open_precondition()\n",
-       "        if G is None:  # an open precondition has no achiever -> dead end\n",
-       "            return False\n",
-       "\n",
-       "        # number of actions already introduced, excluding the dummy Start/Finish\n",
-       "        introduced = len(self.actions) - 2\n",
-       "        snapshot = self._snapshot()\n",
-       "        # try each achiever deterministically, reusing existing actions first\n",
-       "        for act0 in sorted(set(possible_actions), key=lambda a: (a not in self.actions, str(a))):\n",
-       "            is_new = act0 not in self.actions\n",
-       "            if is_new and introduced >= limit:  # deepening bound on plan size\n",
-       "                continue\n",
-       "            self.agenda.discard((G, act1))\n",
-       "            self.actions.add(act0)\n",
-       "            self.constraints = self.add_const((self.start, act0), self.constraints)\n",
-       "            self.constraints = self.add_const((act0, act1), self.constraints)\n",
-       "            # the causal link act0 --G--> act1 requires act0 strictly before act1\n",
-       "            # (and after start); add_const drops an ordering that would create a\n",
-       "            # cycle, so reject the choice when the required ordering is not enforced\n",
-       "            if ((act0 == act1 or self._reachable(act0, act1)) and\n",
-       "                    (act0 == self.start or self._reachable(self.start, act0))):\n",
-       "                if (act0, G, act1) not in self.causal_links:\n",
-       "                    self.causal_links.append((act0, G, act1))\n",
-       "                if is_new:  # a freshly introduced action contributes its own preconditions\n",
-       "                    for precondition in act0.precond:\n",
-       "                        self.agenda.add((precondition, act0))\n",
-       "                if self._search(budget, limit):\n",
-       "                    return True\n",
-       "            # undo and try the next achiever\n",
-       "            self._restore(snapshot)\n",
-       "        return False\n",
-       "
\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "psource(PartialOrderPlanner)" ] @@ -666,7 +177,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T10:32:50.028987Z", @@ -675,37 +186,7 @@ "shell.execute_reply": "2026-06-27T10:32:50.087372Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Causal Links\n", - "(PutOn(Spare, Axle), At(Spare, Axle), Finish)\n", - "(Start, Tire(Spare), PutOn(Spare, Axle))\n", - "(Remove(Flat, Axle), NotAt(Flat, Axle), PutOn(Spare, Axle))\n", - "(Start, Tire(Flat), Remove(Flat, Axle))\n", - "(Start, At(Flat, Axle), Remove(Flat, Axle))\n", - "(Remove(Spare, Trunk), At(Spare, Ground), PutOn(Spare, Axle))\n", - "(Start, At(Spare, Trunk), Remove(Spare, Trunk))\n", - "(Start, Tire(Spare), Remove(Spare, Trunk))\n", - "(Remove(Flat, Axle), At(Flat, Ground), Finish)\n", - "\n", - "_constraints\n", - "Start < Remove(Flat, Axle)\n", - "PutOn(Spare, Axle) < Finish\n", - "Remove(Flat, Axle) < Finish\n", - "Remove(Flat, Axle) < PutOn(Spare, Axle)\n", - "Start < PutOn(Spare, Axle)\n", - "Remove(Spare, Trunk) < PutOn(Spare, Axle)\n", - "Start < Remove(Spare, Trunk)\n", - "Start < Finish\n", - "\n", - "_partial Order Plan\n", - "[{Start}, {Remove(Flat, Axle), Remove(Spare, Trunk)}, {PutOn(Spare, Axle)}, {Finish}]\n" - ] - } - ], + "outputs": [], "source": [ "st = spare_tire()\n", "pop = PartialOrderPlanner(st)\n", @@ -723,7 +204,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T10:32:50.102953Z", @@ -732,39 +213,7 @@ "shell.execute_reply": "2026-06-27T10:32:50.140058Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Causal Links\n", - "(FromTable(B, A), On(B, A), Finish)\n", - "(FromTable(C, B), On(C, B), Finish)\n", - "(ToTable(A, B), Clear(B), FromTable(B, A))\n", - "(Start, On(A, B), ToTable(A, B))\n", - "(Start, Clear(A), FromTable(B, A))\n", - "(Start, Clear(A), ToTable(A, B))\n", - "(ToTable(A, B), Clear(B), FromTable(C, B))\n", - "(Start, Clear(C), FromTable(C, B))\n", - "(Start, OnTable(B), FromTable(B, A))\n", - "(Start, OnTable(C), FromTable(C, B))\n", - "\n", - "_constraints\n", - "Start < FromTable(B, A)\n", - "Start < FromTable(C, B)\n", - "Start < ToTable(A, B)\n", - "ToTable(A, B) < FromTable(B, A)\n", - "Start < Finish\n", - "FromTable(B, A) < FromTable(C, B)\n", - "FromTable(C, B) < Finish\n", - "ToTable(A, B) < FromTable(C, B)\n", - "FromTable(B, A) < Finish\n", - "\n", - "_partial Order Plan\n", - "[{Start}, {ToTable(A, B)}, {FromTable(B, A)}, {FromTable(C, B)}, {Finish}]\n" - ] - } - ], + "outputs": [], "source": [ "sbw = simple_blocks_world()\n", "pop = PartialOrderPlanner(sbw)\n", @@ -782,7 +231,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T10:32:50.154083Z", @@ -791,33 +240,7 @@ "shell.execute_reply": "2026-06-27T10:32:50.173844Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Causal Links\n", - "(LeftShoe, LeftShoeOn, Finish)\n", - "(LeftSock, LeftSockOn, LeftShoe)\n", - "(RightShoe, RightShoeOn, Finish)\n", - "(RightSock, RightSockOn, RightShoe)\n", - "\n", - "_constraints\n", - "LeftSock < LeftShoe\n", - "Start < LeftSock\n", - "Start < RightSock\n", - "Start < RightShoe\n", - "RightSock < RightShoe\n", - "Start < Finish\n", - "LeftShoe < Finish\n", - "Start < LeftShoe\n", - "RightShoe < Finish\n", - "\n", - "_partial Order Plan\n", - "[{Start}, {LeftSock, RightSock}, {RightShoe, LeftShoe}, {Finish}]\n" - ] - } - ], + "outputs": [], "source": [ "ss = socks_and_shoes()\n", "pop = PartialOrderPlanner(ss)\n", @@ -849,7 +272,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T10:32:50.186142Z", @@ -865,7 +288,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T10:32:50.217506Z", @@ -874,15 +297,7 @@ "shell.execute_reply": "2026-06-27T10:32:54.754693Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "529 μs ± 70.2 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n" - ] - } - ], + "outputs": [], "source": [ "%%timeit\n", "GraphPlan(ss).execute()" @@ -890,7 +305,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T10:32:54.758067Z", @@ -899,15 +314,7 @@ "shell.execute_reply": "2026-06-27T10:33:04.566335Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1.17 ms ± 123 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n" - ] - } - ], + "outputs": [], "source": [ "%%timeit\n", "Linearize(ss).execute()" @@ -915,7 +322,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": { "execution": { "iopub.execute_input": "2026-06-27T10:33:04.572932Z", @@ -924,15 +331,7 @@ "shell.execute_reply": "2026-06-27T10:33:15.532043Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1.24 ms ± 186 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n" - ] - } - ], + "outputs": [], "source": [ "%%timeit\n", "PartialOrderPlanner(ss).execute(display=False)" diff --git a/notebooks/vacuum_world.ipynb b/notebooks/vacuum_world.ipynb index 8fa52dffc..54e3d5229 100644 --- a/notebooks/vacuum_world.ipynb +++ b/notebooks/vacuum_world.ipynb @@ -2,9 +2,18 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 44, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/iain/.venvs/AIMA/lib/python3.13/site-packages/nbformat/validator.py:434: MissingIDFieldWarning: Cell is missing an id field, this will become a hard error in future nbformat versions. You may want to use `normalize()` on your notebooks before validations (available since nbformat 5.1.4). Previous versions of nbformat are fixing this issue transparently, and will stop doing so in the future.\n", + " _validate(nbdict, ref, version, version_minor, relax_add_props)\n" + ] + } + ], "source": [ "%run bootstrap.ipynb" ] @@ -82,7 +91,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 45, "metadata": {}, "outputs": [], "source": [ @@ -99,138 +108,52 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "

\n", - "\n", - "
class TrivialVacuumEnvironment(Environment):\n",
+       "
class TrivialVacuumEnvironment(Environment):\n",
+       "    \"\"\"This environment has two locations, A and B. Each can be Dirty\n",
+       "    or Clean. The agent perceives its location and the location's\n",
+       "    status. This serves as an example of how to implement a simple\n",
+       "    Environment.\"\"\"\n",
        "\n",
-       "    """This environment has two locations, A and B. Each can be Dirty\n",
-       "    or Clean. The agent perceives its location and the location's\n",
-       "    status. This serves as an example of how to implement a simple\n",
-       "    Environment."""\n",
+       "    def __init__(self):\n",
+       "        super().__init__()\n",
+       "        self.status = {loc_AA: random.choice(['Clean', 'Dirty']),\n",
+       "                       loc_AB: random.choice(['Clean', 'Dirty']),\n",
+       "                       loc_BB: random.choice(['Clean', 'Dirty']),\n",
+       "                       loc_BA: random.choice(['Clean', 'Dirty'])\n",
+       "                       }\n",
        "\n",
-       "    def __init__(self):\n",
-       "        super().__init__()\n",
-       "        self.status = {loc_A: random.choice(['Clean', 'Dirty']),\n",
-       "                       loc_B: random.choice(['Clean', 'Dirty'])}\n",
+       "    def thing_classes(self):\n",
+       "        \"\"\"Return the Thing/Agent classes that may populate this vacuum world.\"\"\"\n",
+       "        return [Wall, Dirt, ReflexVacuumAgent, RandomVacuumAgent, TableDrivenVacuumAgent, ModelBasedVacuumAgent]\n",
        "\n",
-       "    def thing_classes(self):\n",
-       "        return [Wall, Dirt, ReflexVacuumAgent, RandomVacuumAgent,\n",
-       "                TableDrivenVacuumAgent, ModelBasedVacuumAgent]\n",
+       "    def percept(self, agent):\n",
+       "        \"\"\"Returns the agent's location, and the location status (Dirty/Clean).\"\"\"\n",
+       "        return agent.location, self.status[agent.location]\n",
        "\n",
-       "    def percept(self, agent):\n",
-       "        """Returns the agent's location, and the location status (Dirty/Clean)."""\n",
-       "        return (agent.location, self.status[agent.location])\n",
+       "    def execute_action(self, agent, action):\n",
+       "        \"\"\"Change agent's location and/or location's status; track performance.\n",
+       "        Score 10 for each dirt cleaned; -1 for each move.\"\"\"\n",
+       "        if action == 'Right':\n",
+       "            agent.location = loc_B\n",
+       "            agent.performance -= 1\n",
+       "        elif action == 'Left':\n",
+       "            agent.location = loc_A\n",
+       "            agent.performance -= 1\n",
+       "        elif action == 'Suck':\n",
+       "            if self.status[agent.location] == 'Dirty':\n",
+       "                agent.performance += 10\n",
+       "            self.status[agent.location] = 'Clean'\n",
        "\n",
-       "    def execute_action(self, agent, action):\n",
-       "        """Change agent's location and/or location's status; track performance.\n",
-       "        Score 10 for each dirt cleaned; -1 for each move."""\n",
-       "        if action == 'Right':\n",
-       "            agent.location = loc_B\n",
-       "            agent.performance -= 1\n",
-       "        elif action == 'Left':\n",
-       "            agent.location = loc_A\n",
-       "            agent.performance -= 1\n",
-       "        elif action == 'Suck':\n",
-       "            if self.status[agent.location] == 'Dirty':\n",
-       "                agent.performance += 10\n",
-       "            self.status[agent.location] = 'Clean'\n",
-       "\n",
-       "    def default_location(self, thing):\n",
-       "        """Agents start in either location at random."""\n",
-       "        return random.choice([loc_A, loc_B])\n",
-       "
\n", - "\n", - "\n" + " def default_location(self, thing):\n", + " \"\"\"Agents start in either location at random.\"\"\"\n", + " return random.choice([loc_A, loc_B])\n", + "
\n" ], "text/plain": [ "" @@ -246,24 +169,24 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "State of the Environment: {(0, 0): 'Clean', (1, 0): 'Dirty'}.\n" + ">\n", + "State of the Environment: {(0, 0): 'Dirty', (1, 0): 'Clean'}.\n" ] } ], "source": [ "# These are the two locations for the two-state environment\n", - "loc_A, loc_B = (0, 0), (1, 0)\n", + "loc_A, loc_B= (0, 0), (0, 1)\n", "\n", "# Initialize the two-state environment\n", "trivial_vacuum_env = TrivialVacuumEnvironment()\n", - "\n", "# Check the initial state of the environment\n", "print(\"State of the Environment: {}.\".format(trivial_vacuum_env.status))" ] @@ -277,12 +200,12 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create the random agent\n", - "random_agent = Agent(program=RandomAgentProgram(['Right', 'Left', 'Suck', 'NoOp']))" + "random_agent = Agent(program=RandomAgentProgram(['Right', 'Left', 'Up', 'Down', 'Suck', 'NoOp']))" ] }, { @@ -294,14 +217,14 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "RandomVacuumAgent is located at (1, 0).\n" + "RandomVacuumAgent is located at (0, 0).\n" ] } ], @@ -321,15 +244,15 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "State of the Environment: {(0, 0): 'Clean', (1, 0): 'Dirty'}.\n", - "RandomVacuumAgent is located at (1, 0).\n" + "State of the Environment: {(0, 0): 'Dirty', (1, 0): 'Dirty'}.\n", + "RandomVacuumAgent is located at (0, 0).\n" ] } ], @@ -355,7 +278,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 27, "metadata": {}, "outputs": [], "source": [ @@ -381,7 +304,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 28, "metadata": {}, "outputs": [], "source": [ @@ -398,7 +321,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 29, "metadata": {}, "outputs": [], "source": [ @@ -407,7 +330,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 30, "metadata": {}, "outputs": [ { @@ -427,7 +350,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 31, "metadata": {}, "outputs": [ { @@ -435,7 +358,7 @@ "output_type": "stream", "text": [ "State of the Environment: {(0, 0): 'Clean', (1, 0): 'Dirty'}.\n", - "TableDrivenVacuumAgent is located at (1, 0).\n" + "TableDrivenVacuumAgent is located at (0, 0).\n" ] } ], @@ -471,7 +394,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 32, "metadata": {}, "outputs": [], "source": [ @@ -488,7 +411,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 33, "metadata": {}, "outputs": [], "source": [ @@ -522,7 +445,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 34, "metadata": {}, "outputs": [ { @@ -541,7 +464,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 35, "metadata": {}, "outputs": [ { @@ -584,7 +507,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 36, "metadata": {}, "outputs": [], "source": [ @@ -601,20 +524,21 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "ModelBasedVacuumAgent is located at (0, 0).\n" + "ModelBasedVacuumAgent is located at (1, 0).\n" ] } ], "source": [ "# TODO: Implement this function for the two-dimensional environment\n", "def update_state(state, action, percept, model):\n", + " \n", " pass\n", "\n", "# Create a model-based reflex agent\n", @@ -628,7 +552,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 38, "metadata": {}, "outputs": [ { @@ -636,7 +560,7 @@ "output_type": "stream", "text": [ "State of the Environment: {(0, 0): 'Clean', (1, 0): 'Clean'}.\n", - "ModelBasedVacuumAgent is located at (1, 0).\n" + "ModelBasedVacuumAgent is located at (0, 0).\n" ] } ], @@ -688,7 +612,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "AIMA (3.13.5.final.0)", "language": "python", "name": "python3" }, @@ -702,7 +626,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.4" + "version": "3.13.5" } }, "nbformat": 4,