{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "vXUqFpLObzhb" }, "source": [ "# Temporal Planning\n", "\n", "This python notebook shows how to use the unified planning library to model temporal problems.\n", "\n", "[![Open In GitHub](https://img.shields.io/badge/see-Github-579aca?logo=github)](https:///github.com/aiplan4eu/unified-planning/blob/master/docs/notebooks/03-temporal-planning.ipynb)\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/aiplan4eu/unified-planning/blob/master/docs/notebooks/03-temporal-planning.ipynb)\n" ] }, { "cell_type": "markdown", "metadata": { "id": "t8dCcpf7mivV" }, "source": [ "## Setup\n", "\n", "We install the unified planning library." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "id": "BoqALxJWdfl8", "tags": [ "remove_from_CI" ] }, "outputs": [], "source": [ "!apt install graphviz graphviz-dev\n", "%pip install unified-planning[tamer,plot]" ] }, { "cell_type": "markdown", "metadata": { "id": "iNHFHxQKnKIp" }, "source": [ "We are now ready to use the Unified-Planning library!" ] }, { "cell_type": "markdown", "metadata": { "id": "xI2BGgmvdsek" }, "source": [ "## Demo\n" ] }, { "cell_type": "markdown", "metadata": { "id": "bu91rHtQ34Wh" }, "source": [ "In this demo we show how to model a temporal planning problem using the Unified Planning library.\n", "\n", "Temporal planning extends classical planning introducing durative actions, timed effects and timed goals.\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "xn5l-SVxufFA" }, "source": [ "We start importing the shortcuts." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "id": "otZVSku3idJC" }, "outputs": [], "source": [ "from unified_planning.shortcuts import *" ] }, { "cell_type": "markdown", "metadata": { "id": "M9DCTuoSu2vh" }, "source": [ "Now we start to model the MatchCellar problem.\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "5iISKqwryXFT" }, "source": [ "### Creating the problem" ] }, { "cell_type": "markdown", "metadata": { "id": "nBaUofy3Ko7V" }, "source": [ "#### Classical part\n", "\n", "First, we define the `UserTypes` and the `Fluents`." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "id": "e5BWi6wyuqyB" }, "outputs": [], "source": [ "Match = UserType('Match')\n", "Fuse = UserType('Fuse')\n", "\n", "handfree = Fluent('handfree')\n", "light = Fluent('light')\n", "match_used = Fluent('match_used', BoolType(), m=Match)\n", "fuse_mended = Fluent('fuse_mended', BoolType(), f=Fuse)" ] }, { "cell_type": "markdown", "metadata": { "id": "aMMtRDVovvuM" }, "source": [ "We create a `Problem`, we add the fluents to it and we set their initial values." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "id": "vEDxcqkLvm9d" }, "outputs": [], "source": [ "problem = Problem('MatchCellar')\n", "\n", "problem.add_fluent(handfree)\n", "problem.add_fluent(light)\n", "problem.add_fluent(match_used, default_initial_value=False)\n", "problem.add_fluent(fuse_mended, default_initial_value=False)\n", "\n", "problem.set_initial_value(light, False)\n", "problem.set_initial_value(handfree, True)" ] }, { "cell_type": "markdown", "metadata": { "id": "SIA9pRCiwEql" }, "source": [ "We create the objects and we add them to the problem." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "id": "b1Xnzzq7wOxw" }, "outputs": [], "source": [ "fuses = [Object(f'f{i}', Fuse) for i in range(3)]\n", "matches = [Object(f'm{i}', Match) for i in range(3)]\n", "\n", "problem.add_objects(fuses)\n", "problem.add_objects(matches)" ] }, { "cell_type": "markdown", "metadata": { "id": "vidUnHLh3gOz" }, "source": [ "#### Temporal part\n", "\n", "Now we start with the temporal aspects creating two durative actions.\n", "\n", "A durative action has a duration, a set of conditions associated to an interval/timing and a set of effects associated to a timing.\n", "\n", "We define the `light_match` action setting a fixed duration and defining a condition at its start and three effects at its end." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "id": "xl8dm2fZ3gpp" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "durative action light_match(Match m) {\n", " duration = [6, 6]\n", " conditions = [\n", " [start]:\n", " (not match_used(m))\n", " ]\n", " effects = [\n", " start:\n", " match_used(m) := true:\n", " light := true:\n", " end:\n", " light := false:\n", " ]\n", " simulated effects = [\n", " ]\n", " }\n" ] } ], "source": [ "light_match = DurativeAction('light_match', m=Match)\n", "m = light_match.parameter('m')\n", "light_match.set_fixed_duration(6)\n", "light_match.add_condition(StartTiming(), Not(match_used(m)))\n", "light_match.add_effect(StartTiming(), match_used(m), True)\n", "light_match.add_effect(StartTiming(), light, True)\n", "light_match.add_effect(EndTiming(), light, False)\n", "problem.add_action(light_match)\n", "print(light_match)" ] }, { "cell_type": "markdown", "metadata": { "id": "mjdfaQw537eI" }, "source": [ "Defining the `mend_fuse` action we defined also a condition over an interval." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "id": "8NFqgnnlyeAc" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "durative action mend_fuse(Fuse f) {\n", " duration = [5, 5]\n", " conditions = [\n", " [start]:\n", " handfree\n", " [start, end]:\n", " light\n", " ]\n", " effects = [\n", " start:\n", " handfree := false:\n", " end:\n", " fuse_mended(f) := true:\n", " handfree := true:\n", " ]\n", " simulated effects = [\n", " ]\n", " }\n" ] } ], "source": [ "mend_fuse = DurativeAction('mend_fuse', f=Fuse)\n", "f = mend_fuse.parameter('f')\n", "mend_fuse.set_fixed_duration(5)\n", "mend_fuse.add_condition(StartTiming(), handfree)\n", "mend_fuse.add_condition(ClosedTimeInterval(StartTiming(), EndTiming()), light)\n", "mend_fuse.add_effect(StartTiming(), handfree, False)\n", "mend_fuse.add_effect(EndTiming(), fuse_mended(f), True)\n", "mend_fuse.add_effect(EndTiming(), handfree, True)\n", "problem.add_action(mend_fuse)\n", "print(mend_fuse)" ] }, { "cell_type": "markdown", "metadata": { "id": "kLDlxvwL39dh" }, "source": [ "We conclude the modeling defining three goals at the end of the execution." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "id": "S4rT6f0A3qRJ" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "problem name = MatchCellar\n", "\n", "types = [Match, Fuse]\n", "\n", "fluents = [\n", " bool handfree\n", " bool light\n", " bool match_used[m=Match]\n", " bool fuse_mended[f=Fuse]\n", "]\n", "\n", "actions = [\n", " durative action light_match(Match m) {\n", " duration = [6, 6]\n", " conditions = [\n", " [start]:\n", " (not match_used(m))\n", " ]\n", " effects = [\n", " start:\n", " match_used(m) := true:\n", " light := true:\n", " end:\n", " light := false:\n", " ]\n", " simulated effects = [\n", " ]\n", " }\n", " durative action mend_fuse(Fuse f) {\n", " duration = [5, 5]\n", " conditions = [\n", " [start]:\n", " handfree\n", " [start, end]:\n", " light\n", " ]\n", " effects = [\n", " start:\n", " handfree := false:\n", " end:\n", " fuse_mended(f) := true:\n", " handfree := true:\n", " ]\n", " simulated effects = [\n", " ]\n", " }\n", "]\n", "\n", "objects = [\n", " Match: [m0, m1, m2]\n", " Fuse: [f0, f1, f2]\n", "]\n", "\n", "initial fluents default = [\n", " bool match_used[m=Match] := false\n", " bool fuse_mended[f=Fuse] := false\n", "]\n", "\n", "initial values = [\n", " light := false\n", " handfree := true\n", "]\n", "\n", "timed goals = [\n", " [end] :\n", " fuse_mended(f0)\n", " fuse_mended(f1)\n", " fuse_mended(f2)\n", "]\n", "\n", "goals = [\n", "]\n" ] } ], "source": [ "for f in fuses:\n", " problem.add_timed_goal(EndTiming(), fuse_mended(f))\n", "\n", "print(problem)" ] }, { "cell_type": "markdown", "metadata": { "id": "z3l0y9kaKMII" }, "source": [ "### Solving the problem\n", "\n", "The unified_planning can automatically select, among the available planners installed on the system, one that is able to handle the temporal features of the problem." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "id": "frcEeD-oKO2m" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001B[96m\u001B[1mNOTE: To disable printing of planning engine credits, add this line to your code: `up.shortcuts.get_environment().credits_stream = None`\n", "\u001B[0m\u001B[96m *** Credits ***\n", "\u001B[0m\u001B[96m * In operation mode `OneshotPlanner` at line 1 of `/tmp/ipykernel_153164/3087807287.py`, \u001B[0m\u001B[96myou are using the following planning engine:\n", "\u001B[0m\u001B[96m * Engine name: Tamer\n", " * Developers: FBK Tamer Development Team\n", "\u001B[0m\u001B[96m * Description: \u001B[0m\u001B[96mTamer offers the capability to generate a plan for classical, numerical and temporal problems.\n", " * For those kind of problems tamer also offers the possibility of validating a submitted plan.\u001B[0m\u001B[96m\n", "\u001B[0m\u001B[96m\n", "\u001B[0mTamer returned:\n", "0.0: light_match(m1) [6.0]\n", "0.01: mend_fuse(f1) [5.0]\n", "6.01: light_match(m2) [6.0]\n", "6.02: mend_fuse(f2) [5.0]\n", "12.02: light_match(m0) [6.0]\n", "12.03: mend_fuse(f0) [5.0]\n" ] } ], "source": [ "with OneshotPlanner(problem_kind=problem.kind) as planner:\n", " result = planner.solve(problem)\n", " plan = result.plan\n", " if plan is not None:\n", " print(f\"{planner.name} returned:\")\n", " for start, action, duration in plan.timed_actions:\n", " print(f\"{float(start)}: {action} [{float(duration)}]\")\n", " else:\n", " print(\"No plan found.\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from unified_planning.plot import plot_plan\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ignore the code below, it's used to make this notebook also runnable in the Countinuous Intergation." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Redefine the plot package methods imported above to print the plot to a temp file\n", "# if the exception \"could not locate runnable browser\" is raised. This usually happens\n", "# in the Continuous Integration.\n", "\n", "from inspect import getmembers, isfunction\n", "from unified_planning import plot\n", "from functools import partial\n", "import os, uuid, tempfile as tf\n", "\n", "# Define the function that will be executed instead\n", "def _function(original_function, *args, **kwargs):\n", " try:\n", " original_function(*args, **kwargs)\n", " except Exception as e:\n", " if \"could not locate runnable browser\" in str(e):\n", " original_function(*args, **kwargs,\n", " filename=f\"{os.path.join(tf.gettempdir(), str(uuid.uuid1()))}.png\"\n", " )\n", " else:\n", " raise e\n", "\n", "# Iterate over all the functions of the plot package\n", "for function_name, function in getmembers(plot, isfunction):\n", " # Override the original function with the new one\n", " globals()[function_name] = partial(_function, function)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "if plan is not None:\n", " plot_plan(plan)" ] } ], "metadata": { "celltoolbar": "Tags", "colab": { "collapsed_sections": [], "name": "Temporal Planning", "provenance": [] }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.10.6" }, "vscode": { "interpreter": { "hash": "fcfc934ecfdac8ddac62d6a80ba8d82faf47dc8d54fd6a313f0c016b85ebec0e" } } }, "nbformat": 4, "nbformat_minor": 1 }