diff --git a/HI_THERE.txt b/HI_THERE.txt new file mode 100644 index 000000000..087258bcd --- /dev/null +++ b/HI_THERE.txt @@ -0,0 +1,4 @@ +this is a test message! +wow + +:) diff --git a/notebooks/vacuum_world.ipynb b/notebooks/vacuum_world.ipynb index 8fa52dffc..d70860f44 100644 --- a/notebooks/vacuum_world.ipynb +++ b/notebooks/vacuum_world.ipynb @@ -2,9 +2,18 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/anaconda3/2023.03/lib/python3.10/site-packages/nbformat/__init__.py:92: MissingIDFieldWarning: Code 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(nb)\n" + ] + } + ], "source": [ "%run bootstrap.ipynb" ] @@ -24,7 +33,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Before moving on, please review [agents.ipynb](https://github.com/aimacode/aima-python/blob/master/agents.ipynb)" + "Before moving on, please review [agents.ipynb](https://github.com/aimacode/aima-python/blob/master/notebooks/agents.ipynb)" ] }, { @@ -82,7 +91,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -99,7 +108,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -107,33 +116,44 @@ "text/html": [ "\n", - "\n", + "\n", "\n", "\n", " \n", " \n", " \n", + "\n", + "\n", + "

\n", + "\n", + "
def ModelBasedVacuumAgent():\n",
+       "    """An agent that keeps track of what locations are clean or dirty.\n",
+       "    >>> agent = ModelBasedVacuumAgent()\n",
+       "    >>> environment = TrivialVacuumEnvironment()\n",
+       "    >>> environment.add_thing(agent)\n",
+       "    >>> environment.run()\n",
+       "    >>> environment.status == {(1,0):'Clean' , (0,0) : 'Clean'}\n",
+       "    True\n",
+       "    """\n",
+       "    model = {loc_A: None, loc_B: None}\n",
+       "\n",
+       "    def program(percept):\n",
+       "        """Same as ReflexVacuumAgent, except if everything is clean, do NoOp."""\n",
+       "        location, status = percept\n",
+       "        model[location] = status  # Update the model here\n",
+       "        if model[loc_A] == model[loc_B] == 'Clean':\n",
+       "            return 'NoOp'\n",
+       "        elif status == 'Dirty':\n",
+       "            return 'Suck'\n",
+       "        elif location == loc_A:\n",
+       "            return 'Right'\n",
+       "        elif location == loc_B:\n",
+       "            return 'Left'\n",
+       "\n",
+       "    return Agent(program)\n",
+       "
\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, { "name": "stdout", "output_type": "stream", "text": [ - "ModelBasedVacuumAgent is located at (0, 0).\n" + "ModelBasedVacuumAgent is located at (1, 0).\n" ] } ], @@ -622,13 +778,12 @@ "\n", "# Add the agent to the environment\n", "trivial_vacuum_env.add_thing(model_based_reflex_agent)\n", - "\n", "print(\"ModelBasedVacuumAgent is located at {}.\".format(model_based_reflex_agent.location))" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 21, "metadata": {}, "outputs": [ { @@ -636,7 +791,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" ] } ], @@ -684,11 +839,252 @@ "**Figure 2.15** of the book sums up the components and their working: \n", "" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 2x2 Grid Work\n", + "Below is all of my added work, to create a 2x2 grid. I found it was easier to start from scratch than modify what was present." + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "metadata": {}, + "outputs": [], + "source": [ + "class DirectionType(type):\n", + " \"\"\"Fancy python trickery that allows us to use either\n", + " >> Direction.Right\n", + " or\n", + " >> Direction[\"Right\"]\n", + " and get the same outcome (1,0)\n", + " \"\"\"\n", + " Right = (1,0)\n", + " Left = (-1,0)\n", + " Down = (0,-1)\n", + " Up = (0,1)\n", + " \n", + " def __getitem__(self, key):\n", + " match key.lower():\n", + " case \"right\": return self.Right\n", + " case \"left\": return self.Left\n", + " case \"down\": return self.Down\n", + " case \"up\": return self.Up\n", + " case _: return (0,0) # default case\n", + "\n", + "class Directions(metaclass=DirectionType):\n", + " \"\"\"A simple helper class for moving around a 2d grid.\"\"\"\n", + " pass\n", + "\n", + "def add_direction(position, direction):\n", + " return (position[0] + direction[0], position[1] + direction[1])\n", + "\n", + "all_literal_directions = [\"Right\",\"Left\",\"Up\",\"Down\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 109, + "metadata": {}, + "outputs": [], + "source": [ + "grid2x2 = [\n", + " (x,y)\n", + " for x in range(2)\n", + " for y in range(2)\n", + "]\n", + "\n", + "class VacuumEnvironment2d(TrivialVacuumEnvironment):\n", + "\n", + " def __init__(self):\n", + " super().__init__()\n", + " # We're overriding the status they set here\n", + " self.status = {\n", + " location: random.choice(['Clean', 'Dirty'])\n", + " for location in grid2x2\n", + " }\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", + " location_x, location_y = agent.location\n", + " \n", + " if action in all_literal_directions:\n", + " agent.location = add_direction(agent.location, Directions[action])\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", + "\n", + " def default_location(self, thing):\n", + " \"\"\"Agents start in either location at random.\"\"\"\n", + " all_locations = list(self.status.keys())\n", + " return random.choice(all_locations)\n", + "\n", + "\n", + "new_2d_env = VacuumEnvironment2d()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simple Reflex Agent (2d)" + ] + }, + { + "cell_type": "code", + "execution_count": 163, + "metadata": {}, + "outputs": [], + "source": [ + "def SimpleReflexAgentProgram2d():\n", + " \"\"\"This agent takes action based solely on the percept. [Figure 2.10]\"\"\"\n", + " \n", + " def program(percept):\n", + " loc, status = percept\n", + " if status == \"Dirty\":\n", + " return \"Suck\"\n", + " \n", + " match loc:\n", + " case (0,0): return \"Right\"\n", + " case (1,0): return \"Up\"\n", + " case (1,1): return \"Left\"\n", + " case (0,1): return \"Down\"\n", + "\n", + " return program" + ] + }, + { + "cell_type": "code", + "execution_count": 164, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "State of the Environment Before: {(0, 0): 'Clean', (0, 1): 'Clean', (1, 0): 'Dirty', (1, 1): 'Clean'}.\n", + "State of the Environment After: {(0, 0): 'Clean', (0, 1): 'Clean', (1, 0): 'Clean', (1, 1): 'Clean'}.\n" + ] + }, + { + "ename": "AttributeError", + "evalue": "'function' object has no attribute 'performance'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[164], line 13\u001b[0m\n\u001b[1;32m 9\u001b[0m new_2d_env\u001b[38;5;241m.\u001b[39mrun()\n\u001b[1;32m 11\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mState of the Environment After: \u001b[39m\u001b[38;5;132;01m{}\u001b[39;00m\u001b[38;5;124m.\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;241m.\u001b[39mformat(new_2d_env\u001b[38;5;241m.\u001b[39mstatus))\n\u001b[0;32m---> 13\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mModelBasedVacuumAgent scored \u001b[39m\u001b[38;5;132;01m{}\u001b[39;00m\u001b[38;5;124m.\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;241m.\u001b[39mformat(\u001b[43msimple_vacuum_agent_2d\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mperformance\u001b[49m))\n", + "\u001b[0;31mAttributeError\u001b[0m: 'function' object has no attribute 'performance'" + ] + } + ], + "source": [ + "new_2d_env = VacuumEnvironment2d()\n", + "\n", + "program = SimpleReflexAgentProgram2d()\n", + "simple_reflex_agent = Agent(program)\n", + "\n", + "new_2d_env.add_thing(simple_reflex_agent)\n", + "\n", + "print(\"State of the Environment Before: {}.\".format(new_2d_env.status))\n", + "\n", + "new_2d_env.run()\n", + "\n", + "print(\"State of the Environment After: {}.\".format(new_2d_env.status))\n", + "\n", + "print(\"ModelBasedVacuumAgent scored {}.\".format(simple_reflex_agent.performance))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Model-Based Reflex Agent (2d)" + ] + }, + { + "cell_type": "code", + "execution_count": 112, + "metadata": {}, + "outputs": [], + "source": [ + "def ModelBasedVacuumAgent2d():\n", + " model = {\n", + " location: None\n", + " for location in grid2x2\n", + " }\n", + "\n", + " def program(percept):\n", + " \"\"\"Same as ReflexVacuumAgent, except if everything is clean, do NoOp.\"\"\"\n", + " location, status = percept\n", + " model[location] = status\n", + " \n", + " if status == \"Dirty\":\n", + " return \"Suck\"\n", + " \n", + " adjacent_locations = []\n", + " for direction in all_literal_directions:\n", + " adjacent_spot = add_direction(location, Directions[direction])\n", + " if adjacent_spot in model and model[adjacent_spot] != \"Clean\":\n", + " adjacent_locations.append(direction)\n", + " \n", + " if len(adjacent_locations) == 0:\n", + " return \"NoOp\"\n", + " else:\n", + " return adjacent_locations[0]\n", + " \n", + "\n", + " return Agent(program)" + ] + }, + { + "cell_type": "code", + "execution_count": 160, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "State of the Environment Before: {(0, 0): 'Dirty', (0, 1): 'Dirty', (1, 0): 'Clean', (1, 1): 'Dirty'}.\n", + "State of the Environment After: {(0, 0): 'Clean', (0, 1): 'Clean', (1, 0): 'Clean', (1, 1): 'Clean'}.\n", + "ModelBasedVacuumAgent scored 27.\n" + ] + } + ], + "source": [ + "new_2d_env = VacuumEnvironment2d()\n", + "\n", + "model_based_vacuum_agent_2d = ModelBasedVacuumAgent2d()\n", + "\n", + "new_2d_env.add_thing(model_based_vacuum_agent_2d)\n", + "\n", + "print(\"State of the Environment Before: {}.\".format(new_2d_env.status))\n", + "\n", + "new_2d_env.run()\n", + "\n", + "print(\"State of the Environment After: {}.\".format(new_2d_env.status))\n", + "\n", + "print(\"ModelBasedVacuumAgent scored {}.\".format(model_based_vacuum_agent_2d.performance))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -702,9 +1098,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.4" + "version": "3.10.12" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/requirements-new.txt b/requirements-new.txt new file mode 100644 index 000000000..ca1d36dc2 --- /dev/null +++ b/requirements-new.txt @@ -0,0 +1,16 @@ +graphviz +ipython +ipythonblocks +ipywidgets +jupyter +keras +matplotlib +networkx +numpy +opencv-contrib-python +pandas +pillow +pytest +pytest-cov +qpsolvers +scipy diff --git a/vacuum_world_TableAgent.ipynb b/vacuum_world_TableAgent.ipynb new file mode 100644 index 000000000..c9dea2760 --- /dev/null +++ b/vacuum_world_TableAgent.ipynb @@ -0,0 +1,1188 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "dr6k9g_eBgZq" + }, + "outputs": [], + "source": [ + "%run bootstrap.ipynb" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "V6Q8plqMBgZr" + }, + "source": [ + "# THE VACUUM WORLD \n", + "\n", + "In this notebook, we will be discussing **the structure of agents** through an example of the **vacuum agent**. The job of AI is to design an **agent program** that implements the agent function: the mapping from percepts to actions. We assume this program will run on some sort of computing device with physical sensors and actuators: we call this the **architecture**:\n", + "\n", + "

agent = architecture + program

" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Qla3jKR3BgZt" + }, + "source": [ + "Before moving on, please review [agents.ipynb](https://github.com/aimacode/aima-python/blob/master/agents.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "b_1oa3NaBgZt" + }, + "source": [ + "## CONTENTS\n", + "\n", + "* Agent\n", + "* Random Agent Program\n", + "* Table-Driven Agent Program\n", + "* Simple Reflex Agent Program\n", + "* Model-Based Reflex Agent Program\n", + "* Goal-Based Agent Program\n", + "* Utility-Based Agent Program\n", + "* Learning Agent" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "5XBrAeNvBgZt" + }, + "source": [ + "## AGENT PROGRAMS\n", + "\n", + "An agent program takes the current percept as input from the sensors and returns an action to the actuators. There is a difference between an agent program and an agent function: an agent program takes the current percept as input whereas an agent function takes the entire percept history.\n", + "\n", + "The agent program takes just the current percept as input because nothing more is available from the environment; if the agent's actions depend on the entire percept sequence, the agent will have to remember the percept.\n", + "\n", + "We'll discuss the following agent programs here with the help of the vacuum world example:\n", + "\n", + "* Random Agent Program\n", + "* Table-Driven Agent Program\n", + "* Simple Reflex Agent Program\n", + "* Model-Based Reflex Agent Program\n", + "* Goal-Based Agent Program\n", + "* Utility-Based Agent Program" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "PdOuqNSsBgZu" + }, + "source": [ + "## Random Agent Program\n", + "\n", + "A random agent program, as the name suggests, chooses an action at random, without taking into account the percepts. \n", + "Here, we will demonstrate a random vacuum agent for a trivial vacuum environment, that is, the two-state environment." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "EKSsPx9hBgZu" + }, + "source": [ + "Let's begin by importing all the functions from the agents module:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "8zRIS82NBgZv" + }, + "outputs": [], + "source": [ + "from aima.agents import *\n", + "from aima.notebook_utils import psource" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "NqChzxVmBgZv" + }, + "source": [ + "Let us first see how we define the TrivialVacuumEnvironment. Run the next cell to see how abstract class TrivialVacuumEnvironment is defined in agents module:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 684 + }, + "id": "VxNRHZjfBgZw", + "outputId": "062556cd-da6a-4213-a5e6-9ec311ed8433" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

\n", + "\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",
+              "    def __init__(self):\n",
+              "        super().__init__()\n",
+              "        self.status = {loc_A: random.choice(['Clean', 'Dirty']),\n",
+              "                       loc_B: random.choice(['Clean', 'Dirty'])}\n",
+              "\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 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 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" + ] + }, + "metadata": {} + } + ], + "source": [ + "psource(TrivialVacuumEnvironment)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "YbHxxP8uBgZw", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "1a8f0b19-24da-42de-d5db-378b48b8a376" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "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", + "\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))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "QdyUsbWFBgZx" + }, + "source": [ + "Let's create our agent now. This agent will choose any of the actions from 'Right', 'Left', 'Suck' and 'NoOp' (No Operation) randomly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "HIx8YRGIBgZx" + }, + "outputs": [], + "source": [ + "# Create the random agent\n", + "random_agent = Agent(program=RandomAgentProgram(['Right', 'Left', 'Suck', 'NoOp']))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BLkj_q0wBgZx" + }, + "source": [ + "We will now add our agent to the environment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "z60BkjNaBgZx", + "outputId": "5f9bfcca-b843-4a52-8cde-c3856f452ad7" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "RandomVacuumAgent is located at (1, 0).\n" + ] + } + ], + "source": [ + "# Add agent to the environment\n", + "trivial_vacuum_env.add_thing(random_agent)\n", + "\n", + "print(\"RandomVacuumAgent is located at {}.\".format(random_agent.location))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "k5WDRI8GBgZx" + }, + "source": [ + "Let's run our environment now." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "sd4lE6sUBgZy", + "outputId": "3dee4f06-4983-47fd-d88e-c2a9de48dff6" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "State of the Environment: {(0, 0): 'Dirty', (1, 0): 'Dirty'}.\n", + "RandomVacuumAgent is located at (1, 0).\n" + ] + } + ], + "source": [ + "# Running the environment\n", + "trivial_vacuum_env.step()\n", + "\n", + "# Check the current state of the environment\n", + "print(\"State of the Environment: {}.\".format(trivial_vacuum_env.status))\n", + "\n", + "print(\"RandomVacuumAgent is located at {}.\".format(random_agent.location))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "2J9P1QVtBgZy" + }, + "source": [ + "## TABLE-DRIVEN AGENT PROGRAM\n", + "\n", + "A table-driven agent program keeps track of the percept sequence and then uses it to index into a table of actions to decide what to do. The table represents explicitly the agent function that the agent program embodies. \n", + "In the two-state vacuum world, the table would consist of all the possible states of the agent." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "DQjCrzwEBgZy" + }, + "outputs": [], + "source": [ + "table = {((loc_A, 'Clean'),): 'Right',\n", + " ((loc_A, 'Dirty'),): 'Suck',\n", + " ((loc_B, 'Clean'),): 'Left',\n", + " ((loc_B, 'Dirty'),): 'Suck',\n", + " ((loc_A, 'Dirty'), (loc_A, 'Clean')): 'Right',\n", + " ((loc_A, 'Clean'), (loc_B, 'Dirty')): 'Suck',\n", + " ((loc_B, 'Clean'), (loc_A, 'Dirty')): 'Suck',\n", + " ((loc_B, 'Dirty'), (loc_B, 'Clean')): 'Left',\n", + " ((loc_A, 'Dirty'), (loc_A, 'Clean'), (loc_B, 'Dirty')): 'Suck',\n", + " ((loc_B, 'Dirty'), (loc_B, 'Clean'), (loc_A, 'Dirty')): 'Suck'\n", + " }" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# **I'll be working on building a 2x2 table space here.**" + ], + "metadata": { + "id": "9W0ayvBZcL_Z" + } + }, + { + "cell_type": "markdown", + "source": [ + "Environment\n" + ], + "metadata": { + "id": "JLMjhfD52VPD" + } + }, + { + "cell_type": "code", + "source": [], + "metadata": { + "id": "-YmCuPtR2UyO" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "eUDi2m532ALu" + }, + "outputs": [], + "source": [ + "class DirectionType(type):\n", + " \"\"\"Fancy python trickery that allows us to use either\n", + " >> Direction.Right\n", + " or\n", + " >> Direction[\"Right\"]\n", + " and get the same outcome (1,0)\n", + " \"\"\"\n", + " Right = (1,0)\n", + " Left = (-1,0)\n", + " Down = (0,-1)\n", + " Up = (0,1)\n", + "\n", + " def __getitem__(self, key):\n", + " match key.lower():\n", + " case \"right\": return self.Right\n", + " case \"left\": return self.Left\n", + " case \"down\": return self.Down\n", + " case \"up\": return self.Up\n", + " case _: return (0,0) # default case\n", + "\n", + "class Directions(metaclass=DirectionType):\n", + " \"\"\"A simple helper class for moving around a 2d grid.\"\"\"\n", + " pass\n", + "\n", + "def add_direction(position, direction):\n", + " return (position[0] + direction[0], position[1] + direction[1])\n", + "\n", + "all_literal_directions = [\"Right\",\"Left\",\"Up\",\"Down\"]" + ] + }, + { + "cell_type": "code", + "source": [], + "metadata": { + "id": "9wXTyZFm2cSf" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "WdIYvSkD2ALu" + }, + "outputs": [], + "source": [ + "grid2x2 = [\n", + " (x,y)\n", + " for x in range(2)\n", + " for y in range(2)\n", + "]\n", + "\n", + "class VacuumEnvironment2d(TrivialVacuumEnvironment):\n", + "\n", + " def __init__(self):\n", + " super().__init__()\n", + " # We're overriding the status they set here\n", + " self.status = {\n", + " location: random.choice(['Clean', 'Dirty'])\n", + " for location in grid2x2\n", + " }\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", + " location_x, location_y = agent.location\n", + "\n", + " if action in all_literal_directions:\n", + " agent.location = add_direction(agent.location, Directions[action])\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", + "\n", + " def default_location(self, thing):\n", + " \"\"\"Agents start in either location at random.\"\"\"\n", + " all_locations = list(self.status.keys())\n", + " return random.choice(all_locations)\n", + "\n", + "\n", + "new_2d_env = VacuumEnvironment2d()" + ] + }, + { + "cell_type": "code", + "source": [ + "loc_A, loc_B, loc_C, loc_D = (0, 0), (1, 0), (1, 1), (0, 1)\n" + ], + "metadata": { + "id": "XWnJtyyhcLW6" + }, + "execution_count": 2, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "#taking some time to understand the I structured the first 3 moves by hand\n", + "\n", + "fourSquareTbl_by_hand = {((loc_A, 'Clean'),): 'Right',\n", + " ((loc_A, 'Dirty'),): 'Suck',\n", + " ((loc_B, 'Clean'),): 'Up',\n", + " ((loc_B, 'Dirty'),): 'Suck',\n", + " ((loc_C, 'Clean'),): 'Left',\n", + " ((loc_C, 'Dirty'),): 'Suck',\n", + " ((loc_D, 'Clean'),): 'Down',\n", + " ((loc_D, 'Dirty'),): 'Suck',\n", + " #history len 2\n", + " ((loc_A, 'Dirty'), (loc_A, 'Clean')): 'Right',\n", + " ((loc_A, 'Clean'), (loc_B, 'Dirty')): 'Suck',\n", + " ((loc_B, 'Dirty'), (loc_B, 'Clean')): 'Up',\n", + " ((loc_B, 'Clean'), (loc_C, 'Dirty')): 'Suck',\n", + " ((loc_C, 'Dirty'), (loc_C, 'Clean')): 'Left',\n", + " ((loc_C, 'Clean'), (loc_D, 'Dirty')): 'Suck',\n", + " ((loc_D, 'Dirty'), (loc_D, 'Clean')): 'Down',\n", + " ((loc_D, 'Clean'), (loc_A, 'Dirty')): 'Suck',\n", + " #hlen2 locations both clean\n", + " ((loc_A, 'Clean'), (loc_B, 'Clean')): 'Up',\n", + " ((loc_B, 'Clean'), (loc_C, 'Clean')): 'Left',\n", + " ((loc_C, 'Clean'), (loc_D, 'Clean')): 'Down',\n", + " ((loc_D, 'Clean'), (loc_A, 'Clean')): 'Right',\n", + "\n", + " #hlen3 1st & 2nd location dirty\n", + " ((loc_A, 'Dirty'), (loc_A, 'Clean'), (loc_B, 'Dirty')): 'Suck',\n", + " ((loc_B, 'Dirty'), (loc_B, 'Clean'), (loc_C, 'Dirty')): 'Suck',\n", + " ((loc_C, 'Dirty'), (loc_C, 'Clean'), (loc_D, 'Dirty')): 'Suck',\n", + " ((loc_D, 'Dirty'), (loc_D, 'Clean'), (loc_A, 'Dirty')): 'Suck',\n", + "\n", + " #hlent3 1st location clean, 2nd location dirty\n", + " ((loc_A, 'Clean'), (loc_B, 'Dirty'), (loc_B, 'Clean')): 'Up',\n", + " ((loc_B, 'Clean'), (loc_C, 'Dirty'), (loc_C, 'Clean')): 'Left',\n", + " ((loc_C, 'Clean'), (loc_D, 'Dirty'), (loc_D, 'Clean')): 'Down',\n", + " ((loc_D, 'Clean'), (loc_A, 'Dirty'), (loc_A, 'Clean')): 'Right',\n", + "\n", + " #hlen3 1st location dirty, 2nd location clean\n", + " ((loc_A, 'Dirty'), (loc_A, 'Clean'), (loc_B, 'Clean')): 'Up',\n", + " ((loc_B, 'Dirty'), (loc_B, 'Clean'), (loc_C, 'Clean')): 'Left',\n", + " ((loc_C, 'Dirty'), (loc_C, 'Clean'), (loc_D, 'Clean')): 'Down',\n", + " ((loc_D, 'Dirty'), (loc_D, 'Clean'), (loc_A, 'Clean')): 'Right',\n", + "\n", + " #hlent3 2 locations clean, 3rd location dirty\n", + " ((loc_A, 'Clean'), (loc_B, 'Clean'), (loc_C, 'Dirty')): 'Suck',\n", + " ((loc_B, 'Clean'), (loc_C, 'Clean'), (loc_D, 'Dirty')): 'Suck',\n", + " ((loc_C, 'Clean'), (loc_D, 'Clean'), (loc_A, 'Dirty')): 'Suck',\n", + " ((loc_D, 'Clean'), (loc_A, 'Clean'), (loc_B, 'Dirty')): 'Suck',\n", + "\n", + " #hlen3 locations all clean\n", + " ((loc_A, 'Clean'), (loc_B, 'Clean'), (loc_C, 'Clean')): 'Left',\n", + " ((loc_B, 'Clean'), (loc_C, 'Clean'), (loc_D, 'Clean')): 'Down',\n", + " ((loc_C, 'Clean'), (loc_D, 'Clean'), (loc_A, 'Clean')): 'Right',\n", + " ((loc_D, 'Clean'), (loc_A, 'Clean'), (loc_B, 'Clean')): 'Up',\n", + "\n", + " #hlen4\n", + "\n", + " }\n", + " #produces 3 actions for the agent to complete. by no means full set of actions possible" + ], + "metadata": { + "id": "eopw5FvYS0Ht" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "#here I'll implement a function that can build a table to the full coverage depth for a 2x2 environment\n", + "#cumulative growth in rows of options per percept\n", + "#3 moves + 4 suck actions = 7 percepts\n", + "#Depth 7 costs 8 + 12 + 20 + 32 + 52 + 84 + 136 = 344\n", + "#number of rows per depth grows by the successive difference of the last size\n", + "#vacuum percept has 2 actions (tuple), move and suck\n", + "#those care about where the vacuum is and if the floor is dirty\n", + "#location, status = percept\n", + "#what are the locations?\n", + "#where does the agent move to\n", + "#when the agent is at location x where does it go next?\n", + "\n", + "#location list variable\n", + "#move list variable\n", + "#move-next list variable\n", + "\n", + "LOC = [loc_A, loc_B, loc_C, loc_D]\n", + "MOVE = {loc_A:'Right', loc_B:'Up', loc_C:'Left', loc_D:'Down'}\n", + "NEXT = {loc_A: loc_B, loc_B: loc_C, loc_C: loc_D, loc_D: loc_A}\n", + "\n", + "#I want the agent to suck up dirty loc & move past clean spots\n", + "#thats the perception turned into an action\n", + "def actionPerc(percept):\n", + " location, status = percept\n", + "\n", + " if status == 'Dirty':\n", + " return 'Suck'\n", + " else:\n", + " return MOVE[location]\n", + "\n", + "#I want the agent to branch from one percept to the next possible percepts\n", + "def nextPerc(percept, action):\n", + " location, status = percept\n", + "\n", + " if action == 'Suck':\n", + " return [(location, 'Clean')]\n", + " else:\n", + " return [(NEXT[location], 'Dirty'), (NEXT[location], 'Clean')]\n", + "\n", + "#table generation process:\n", + "#percept -> history -> action from newest percept\n", + "# -> next percept -> repeat\n", + "\n", + "#depth = 2n-1\n", + "depth = 2*len(LOC)-1\n", + "\n", + "#table trajectory:\n", + "#(A,Dirty):'Suck' -> (A, Clean):'Right' -> branch ->\n", + " # (B,Dirty):'Suck' & (B,Clean):'Up'\n", + "def buildTable(depth):\n", + " table = {}\n", + " histories = []\n", + "\n", + " #for each location in the grid space...\n", + " for location in LOC:\n", + " for status in ['Clean','Dirty']:\n", + " #create percept\n", + " percept = (location, status)\n", + " #create history\n", + " history = (percept,)\n", + " #create a starting history\n", + " histories.append(history)\n", + "\n", + " #determine action associated w/ history\n", + "\n", + "\n", + " #add history/action to table\n", + " for i in range(depth):\n", + " nextHist = []\n", + "\n", + " for history in histories:\n", + " percept = history[-1]\n", + " action = actionPerc(percept)\n", + " table[history] = action\n", + "\n", + " for newPerc in nextPerc(percept, action):\n", + " nextHist.append(history + (newPerc,))\n", + "\n", + " histories = nextHist\n", + "\n", + "\n", + " return table\n" + ], + "metadata": { + "id": "VyCftbD6YvKC" + }, + "execution_count": 65, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "print(len(buildTable(1)))\n", + "print(len(buildTable(2)))\n", + "print(len(buildTable(3)))\n", + "print(len(buildTable(4)))\n", + "print(len(buildTable(5)))\n", + "print(len(buildTable(6)))\n", + "print(len(buildTable(7)))\n", + "\n", + "\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "FZ5vcaKgU567", + "outputId": "4da35bd5-75a3-419f-8af7-91df02cb3cba" + }, + "execution_count": 66, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "8\n", + "20\n", + "40\n", + "72\n", + "124\n", + "208\n", + "344\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aiwBNS6uBgZy" + }, + "source": [ + "We will now create a table-driven agent program for our two-state environment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "VAnBSbOLBgZy" + }, + "outputs": [], + "source": [ + "# Create a table-driven agent\n", + "table_driven_agent = Agent(program=TableDrivenAgentProgram(table=table))" + ] + }, + { + "cell_type": "code", + "source": [ + "table_driven_agent = Agent(program=TableDrivenAgentProgram(table=fourSquare))" + ], + "metadata": { + "id": "8HwRZjuL4DfF" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "beKQ3DmlBgZy" + }, + "source": [ + "Since we are using the same environment, let's remove the previously added random agent from the environment to avoid confusion." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "m9yowhSIBgZz" + }, + "outputs": [], + "source": [ + "trivial_vacuum_env.delete_thing(random_agent)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "5gv69lDQBgZz", + "outputId": "1ad94f60-70af-4f2f-827b-ec60ee72e537" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "TableDrivenVacuumAgent is located at (1, 0).\n" + ] + } + ], + "source": [ + "# Add the table-driven agent to the environment\n", + "trivial_vacuum_env.add_thing(table_driven_agent)\n", + "\n", + "print(\"TableDrivenVacuumAgent is located at {}.\".format(table_driven_agent.location))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "biGRv-S3BgZz", + "outputId": "129d62b7-9411-44b8-b30f-69b91f895dab" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "State of the Environment: {(0, 0): 'Dirty', (1, 0): 'Clean'}.\n", + "TableDrivenVacuumAgent is located at (1, 0).\n" + ] + } + ], + "source": [ + "# Run the environment\n", + "trivial_vacuum_env.step()\n", + "\n", + "# Check the current state of the environment\n", + "print(\"State of the Environment: {}.\".format(trivial_vacuum_env.status))\n", + "\n", + "print(\"TableDrivenVacuumAgent is located at {}.\".format(table_driven_agent.location))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "viLRxHLSBgZz" + }, + "source": [ + "## SIMPLE REFLEX AGENT PROGRAM\n", + "\n", + "A simple reflex agent program selects actions on the basis of the *current* percept, ignoring the rest of the percept history. These agents work on a **condition-action rule** (also called **situation-action rule**, **production** or **if-then rule**), which tells the agent the action to trigger when a particular situation is encountered. \n", + "\n", + "The schematic diagram shown in **Figure 2.9** of the book will make this more clear:\n", + "\n", + "\"![simple reflex agent](images/simple_reflex_agent.jpg)\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "sHQlSuQqBgZz" + }, + "source": [ + "Let us now create a simple reflex agent for the environment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "oJV_7rtqBgZz" + }, + "outputs": [], + "source": [ + "# Delete the previously added table-driven agent\n", + "trivial_vacuum_env.delete_thing(table_driven_agent)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "5VZ3P5SJBgZz" + }, + "source": [ + "To create our agent, we need two functions: INTERPRET-INPUT function, which generates an abstracted description of the current state from the percerpt and the RULE-MATCH function, which returns the first rule in the set of rules that matches the given state description." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "3CdxdlRlBgZz" + }, + "outputs": [], + "source": [ + "\n", + "loc_A = (0, 0)\n", + "loc_B = (1, 0)\n", + "\n", + "\"\"\"We change the simpleReflexAgentProgram so that it doesn't make use of the Rule class\"\"\"\n", + "def SimpleReflexAgentProgram():\n", + " \"\"\"This agent takes action based solely on the percept. [Figure 2.10]\"\"\"\n", + "\n", + " def program(percept):\n", + " loc, status = percept\n", + " return ('Suck' if status == 'Dirty'\n", + " else'Right' if loc == loc_A\n", + " else'Left')\n", + " return program\n", + "\n", + "\n", + "# Create a simple reflex agent the two-state environment\n", + "program = SimpleReflexAgentProgram()\n", + "simple_reflex_agent = Agent(program)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "X77QkkAZBgZz" + }, + "source": [ + "Now add the agent to the environment:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "9DO3OPmmBgZz", + "outputId": "0a8c9e02-602b-4cbe-9053-c2d20908f7be" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "SimpleReflexVacuumAgent is located at (0, 0).\n" + ] + } + ], + "source": [ + "trivial_vacuum_env.add_thing(simple_reflex_agent)\n", + "\n", + "print(\"SimpleReflexVacuumAgent is located at {}.\".format(simple_reflex_agent.location))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "4qjdj-IhBgZ0", + "outputId": "5813d7b3-0140-4572-b551-a58f6ea486fc" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "State of the Environment: {(0, 0): 'Clean', (1, 0): 'Clean'}.\n", + "SimpleReflexVacuumAgent is located at (1, 0).\n" + ] + } + ], + "source": [ + "# Run the environment\n", + "trivial_vacuum_env.step()\n", + "\n", + "# Check the current state of the environment\n", + "print(\"State of the Environment: {}.\".format(trivial_vacuum_env.status))\n", + "\n", + "print(\"SimpleReflexVacuumAgent is located at {}.\".format(simple_reflex_agent.location))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ygE6khNKBgZ0" + }, + "source": [ + "## MODEL-BASED REFLEX AGENT PROGRAM\n", + "\n", + "A model-based reflex agent maintains some sort of **internal state** that depends on the percept history and thereby reflects at least some of the unobserved aspects of the current state. In addition to this, it also requires a **model** of the world, that is, knowledge about \"how the world works\".\n", + "\n", + "The schematic diagram shown in **Figure 2.11** of the book will make this more clear:\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zyksQ_h3BgZ0" + }, + "source": [ + "We will now create a model-based reflex agent for the environment:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "3A2Duk96BgZ0" + }, + "outputs": [], + "source": [ + "# Delete the previously added simple reflex agent\n", + "trivial_vacuum_env.delete_thing(simple_reflex_agent)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XtwrWWOTBgZ0" + }, + "source": [ + "We need another function UPDATE-STATE which will be responsible for creating a new state description." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "clzgegrqBgZ1", + "outputId": "96fe5330-c93b-442e-d229-84e34f330a51" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "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", + " pass\n", + "\n", + "# Create a model-based reflex agent\n", + "model_based_reflex_agent = ModelBasedVacuumAgent()\n", + "\n", + "# Add the agent to the environment\n", + "trivial_vacuum_env.add_thing(model_based_reflex_agent)\n", + "\n", + "print(\"ModelBasedVacuumAgent is located at {}.\".format(model_based_reflex_agent.location))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "1_gBNveWBgZ1", + "outputId": "f5fb1a28-d4b6-4553-cbf0-654a307a2411" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "State of the Environment: {(0, 0): 'Clean', (1, 0): 'Clean'}.\n", + "ModelBasedVacuumAgent is located at (0, 0).\n" + ] + } + ], + "source": [ + "# Run the environment\n", + "trivial_vacuum_env.step()\n", + "\n", + "# Check the current state of the environment\n", + "print(\"State of the Environment: {}.\".format(trivial_vacuum_env.status))\n", + "\n", + "print(\"ModelBasedVacuumAgent is located at {}.\".format(model_based_reflex_agent.location))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "u0_PtNesBgZ3" + }, + "source": [ + "## GOAL-BASED AGENT PROGRAM\n", + "\n", + "A goal-based agent needs some sort of **goal** information that describes situations that are desirable, apart from the current state description.\n", + "\n", + "**Figure 2.13** of the book shows a model-based, goal-based agent:\n", + "\n", + "\n", + "**Search** (Chapters 3 to 5) and **Planning** (Chapters 10 to 11) are the subfields of AI devoted to finding action sequences that achieve the agent's goals.\n", + "\n", + "## UTILITY-BASED AGENT PROGRAM\n", + "\n", + "A utility-based agent maximizes its **utility** using the agent's **utility function**, which is essentially an internalization of the agent's performance measure.\n", + "\n", + "**Figure 2.14** of the book shows a model-based, utility-based agent:\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "P0nWyfdCBgZ3" + }, + "source": [ + "## LEARNING AGENT\n", + "\n", + "Learning allows the agent to operate in initially unknown environments and to become more competent than its initial knowledge alone might allow. Here, we will breifly introduce the main ideas of learning agents. \n", + "\n", + "A learning agent can be divided into four conceptual components. The **learning element** is responsible for making improvements. It uses the feedback from the **critic** on how the agent is doing and determines how the performance element should be modified to do better in the future. The **performance element** is responsible for selecting external actions for the agent: it takes in percepts and decides on actions. The critic tells the learning element how well the agent is doing with respect to a fixed performance standard. It is necesaary because the percepts themselves provide no indication of the agent's success. The last component of the learning agent is the **problem generator**. It is responsible for suggesting actions that will lead to new and informative experiences. \n", + "\n", + "**Figure 2.15** of the book sums up the components and their working: \n", + "" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.13" + }, + "colab": { + "provenance": [] + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file