{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "vXUqFpLObzhb" }, "source": [ "## Plan Parsing and Conversion\n", "\n", "This python notebook shows how to use the unified planning library to parse a plan and how to convert plans from one type to another. \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/12-plan-parsing-conversion.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/12-plan-parsing-conversion.ipynb)\n" ] }, { "cell_type": "markdown", "metadata": { "id": "t8dCcpf7mivV" }, "source": [ "### Setup\n", "\n", "For this example there are no planners needed, so the library can be installed" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "BoqALxJWdfl8", "outputId": "299282e7-07f5-46df-e607-47da781dca50", "scrolled": false, "tags": [ "remove_from_CI" ] }, "outputs": [], "source": [ "%pip install unified-planning" ] }, { "cell_type": "markdown", "metadata": { "id": "iNHFHxQKnKIp" }, "source": [ "We are now ready to use the Unified-Planning library and to modify plans!" ] }, { "cell_type": "markdown", "metadata": { "id": "xI2BGgmvdsek" }, "source": [ "### Demo\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 create a problem that models a world where there are a number of locations to be fixed. To fix a location, both a worker and a supervisor are needed.\n", "\n", "The workers and the supervisors can move between locations freely and independently.\n", "\n", "The idea of the deordering is that the order in which the supervisors and the workers move in-between locations does not matter, they just have to arrive both in a location before it can be fixed." ] }, { "cell_type": "markdown", "metadata": { "id": "nBaUofy3Ko7V" }, "source": [ "\n", "#### Creating the fluents\n", "\n", "First, we define the `UserTypes`, and the `Fluents`." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "id": "e5BWi6wyuqyB" }, "outputs": [], "source": [ "Supervisor = UserType('Supervisor')\n", "Worker = UserType('Worker')\n", "\n", "Location = UserType('Location')\n", "\n", "is_supervisor_at = Fluent('is_supervisor_at', s=Supervisor, pos=Location)\n", "is_worker_at = Fluent('is_worker_at', w=Worker, pos=Location)\n", "\n", "is_fixed = Fluent('is_fixed', l=Location)" ] }, { "cell_type": "markdown", "metadata": { "id": "5sJwsWGy3-K5" }, "source": [ "#### Creating the actions\n", "\n", "One to move a Supervisor, one to move a Worker and one to fix a Location." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "id": "J-cU6CvL0-Pv" }, "outputs": [], "source": [ "move_supervisor = InstantaneousAction('move_supervisor', supervisor=Supervisor, l_from=Location, l_to=Location)\n", "supervisor = move_supervisor.supervisor\n", "l_from = move_supervisor.l_from\n", "l_to = move_supervisor.l_to\n", "move_supervisor.add_precondition(Not(Equals(l_from, l_to)))\n", "move_supervisor.add_precondition(is_supervisor_at(supervisor, l_from))\n", "move_supervisor.add_effect(is_supervisor_at(supervisor, l_from), False)\n", "move_supervisor.add_effect(is_supervisor_at(supervisor, l_to), True)\n", "\n", "move_worker = InstantaneousAction('move_worker', worker=Worker, l_from=Location, l_to=Location)\n", "worker = move_worker.worker\n", "l_from = move_worker.l_from\n", "l_to = move_worker.l_to\n", "move_worker.add_precondition(Not(Equals(l_from, l_to)))\n", "move_worker.add_precondition(is_worker_at(worker, l_from))\n", "move_worker.add_effect(is_worker_at(worker, l_from), False)\n", "move_worker.add_effect(is_worker_at(worker, l_to), True)\n", "\n", "fix_location = InstantaneousAction('fix_location', loc=Location, supervisor=Supervisor, worker=Worker)\n", "loc = fix_location.loc\n", "supervisor = fix_location.supervisor\n", "worker = fix_location.worker\n", "fix_location.add_precondition(is_supervisor_at(supervisor, loc))\n", "fix_location.add_precondition(is_worker_at(worker, loc))\n", "fix_location.add_effect(is_fixed(loc), True)\n" ] }, { "cell_type": "markdown", "metadata": { "id": "aMMtRDVovvuM" }, "source": [ "Finally, we can create a `Problem` that encompasses the fluents and the actions, and puts them together with concrete objects, an initial state and a goal. \n", "\n", "The goal is that every location is fixed at the end.\n", "\n", "Both the supervisor and the worker start at location 'l0'.\n" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "vEDxcqkLvm9d", "outputId": "aae981f7-dac8-4a2e-e02a-0619edefd271", "scrolled": true }, "outputs": [], "source": [ "problem = Problem('problem')\n", "\n", "problem.add_fluent(is_supervisor_at, default_initial_value=False)\n", "problem.add_fluent(is_worker_at, default_initial_value=False)\n", "problem.add_fluent(is_fixed, default_initial_value=False)\n", "\n", "s0 = Object('s0', Supervisor)\n", "w0 = Object('w0', Worker)\n", "l0 = Object('l0', Location)\n", "l1 = Object('l1', Location)\n", "l2 = Object('l2', Location)\n", "\n", "problem.add_objects((s0, w0, l0, l1, l2))\n", "\n", "problem.add_action(move_supervisor)\n", "problem.add_action(move_worker)\n", "problem.add_action(fix_location)\n", "\n", "problem.set_initial_value(is_supervisor_at(s0, l0), True)\n", "problem.set_initial_value(is_worker_at(w0, l0), True)\n", "\n", "var_loc = Variable('l', Location)\n", "problem.add_goal(Forall(is_fixed(var_loc), var_loc))" ] }, { "cell_type": "markdown", "metadata": { "id": "Pfo5MK-4FIs4" }, "source": [ "\n", "Now we specify a valid plan in a format parsable by the PDDLReader; the accepted formats are in the PDDLReader.parse_plan (or parse_plan_string) methods" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "v62jmabZCyZr", "outputId": "e44cd067-6ceb-4db1-b939-34c79248f64c" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Parsed plan: SequentialPlan:\n", " fix_location(l0, s0, w0)\n", " move_supervisor(s0, l0, l1)\n", " move_worker(w0, l0, l1)\n", " fix_location(l1, s0, w0)\n", " move_supervisor(s0, l1, l2)\n", " move_worker(w0, l1, l2)\n", " fix_location(l2, s0, w0)\n" ] } ], "source": [ "from unified_planning.io import PDDLReader\n", "\n", "plan_str = \"\"\"(fix_location l0 s0 w0)\n", "(move_supervisor s0 l0 l1)\n", "(move_worker w0 l0 l1)\n", "(fix_location l1 s0 w0)\n", "(move_supervisor s0 l1 l2)\n", "(move_worker w0 l1 l2)\n", "(fix_location l2 s0 w0)\n", "\"\"\"\n", "\n", "reader = PDDLReader()\n", "sequential_plan = reader.parse_plan_string(problem, plan_str)\n", "print(\"Parsed plan: \", sequential_plan)" ] }, { "cell_type": "markdown", "metadata": { "id": "z3l0y9kaKMII" }, "source": [ "#### Converting the SequentialPlan to a PartialOrderPlan\n", "\n", "Now we use the `Plan.convert_to` method, that allows conversion from a `PlanKind` to another `PlanKind`.\n", "\n", "Few conversions are implemented at the moment, also because some conversions do not make sense at all!\n", "\n", "The most interesting currently implemented is the conversion between `Sequential` an `PartialOrder` plans; it's important to specify that the conversion is not the most general possible." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "frcEeD-oKO2m", "outputId": "1ea9a630-db3f-47d8-b011-85e97be31203" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "PartialOrderPlan:\n", " actions:\n", " 0) fix_location(l0, s0, w0)\n", " 1) move_supervisor(s0, l0, l1)\n", " 2) move_worker(w0, l0, l1)\n", " 3) fix_location(l1, s0, w0)\n", " 4) move_worker(w0, l1, l2)\n", " 5) move_supervisor(s0, l1, l2)\n", " 6) fix_location(l2, s0, w0)\n", " constraints:\n", " 0 < 1 ,2\n", " 1 < 3\n", " 2 < 3\n", " 3 < 4 ,5\n", " 5 < 6\n", " 4 < 6\n" ] } ], "source": [ "from unified_planning.plans import PlanKind\n", "\n", "partial_order_plan = sequential_plan.convert_to(PlanKind.PARTIAL_ORDER_PLAN, problem)\n", "\n", "print(partial_order_plan)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see from the print above, location `l0` must be fixed before the supervisor and the worker both move to location `l1`, (constraint `0 < 1, 2`) but both the move actions must be performed before fixing the location `l1` (constraints `1 < 3` and `2 < 3`). Same for `l1` and `l2`.\n", "\n", "The following code shows a special feature of the `PartialOrderPlan`, where we can get an Iterator over all the `SequentialPlans` that follow the ordering enforced by the `PartialOrderPlan` constraints." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "1Xz-IqYoE56M", "outputId": "8a4b48ec-06e7-41f4-837d-c4c2c0564d14" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "SequentialPlan:\n", " fix_location(l0, s0, w0)\n", " move_worker(w0, l0, l1)\n", " move_supervisor(s0, l0, l1)\n", " fix_location(l1, s0, w0)\n", " move_supervisor(s0, l1, l2)\n", " move_worker(w0, l1, l2)\n", " fix_location(l2, s0, w0)\n", "1\n", "SequentialPlan:\n", " fix_location(l0, s0, w0)\n", " move_worker(w0, l0, l1)\n", " move_supervisor(s0, l0, l1)\n", " fix_location(l1, s0, w0)\n", " move_worker(w0, l1, l2)\n", " move_supervisor(s0, l1, l2)\n", " fix_location(l2, s0, w0)\n", "2\n", "SequentialPlan:\n", " fix_location(l0, s0, w0)\n", " move_supervisor(s0, l0, l1)\n", " move_worker(w0, l0, l1)\n", " fix_location(l1, s0, w0)\n", " move_supervisor(s0, l1, l2)\n", " move_worker(w0, l1, l2)\n", " fix_location(l2, s0, w0)\n", "3\n", "SequentialPlan:\n", " fix_location(l0, s0, w0)\n", " move_supervisor(s0, l0, l1)\n", " move_worker(w0, l0, l1)\n", " fix_location(l1, s0, w0)\n", " move_worker(w0, l1, l2)\n", " move_supervisor(s0, l1, l2)\n", " fix_location(l2, s0, w0)\n" ] } ], "source": [ "for i, sorted_plan in enumerate(partial_order_plan.all_sequential_plans()):\n", " print(i)\n", " print(sorted_plan)" ] }, { "cell_type": "markdown", "metadata": { "id": "YGfAogQpHZY-" }, "source": [ "In the 4 plans above the order of the move_supervisor and move_worker between the 3 locations are mixed up!" ] } ], "metadata": { "celltoolbar": "Tags", "colab": { "name": "Plan Parsing and Conversion", "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.12" } }, "nbformat": 4, "nbformat_minor": 1 }