diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cf951da22..e082d09bb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,6 +11,7 @@ env: up_tamer_commit: "ddcb02f3457b097965d9d4596a972b5bf8f495ff" up_pyperplan_commit: "124482387cdc5c7a5673ac6ab47002b4ae2fd682" up_fast_downward_commit: "99dd5bce8f996a9557296644de38bdf94f1dbed8" + up_enhsp_commit: "e0c1567c30fe9eb6ec33bd8fdff24411c817cdee" up_fmap_commit: "d0e3c0c920b3a515486ae7526c0fba51c75361e1" jobs: @@ -419,6 +420,22 @@ jobs: - name: Install up-fast-downward run: python3 -m pip install up-fast-downward/ + - name: Setup java for ENHSP + uses: actions/setup-java@v2 + with: + distribution: "microsoft" + java-version: "17" + + - name: Checkout up-enhsp + uses: actions/checkout@v2 + with: + repository: aiplan4eu/up-enhsp + path: up-enhsp + ref: ${{env.up_enhsp_commit}} + + - name: Install up-enhsp + run: python3 -m pip install up-enhsp/ + - name: Install up-aries run: pip3 install up-aries diff --git a/docs/examples.rst b/docs/examples.rst index 9ecf82932..5262e03cc 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -28,6 +28,26 @@ In particular we will go through the following steps: * call multiple planners in parallel. + +Numeric Planning +---------------- + + +.. image:: https://img.shields.io/badge/see-Github-579aca?logo=github + :target: https:///github.com/aiplan4eu/unified-planning/blob/master/docs/notebooks/01-numeric-planning.ipynb + :alt: Open In GitHub + + +.. image:: https://colab.research.google.com/assets/colab-badge.svg + :target: https://colab.research.google.com/github/aiplan4eu/unified-planning/blob/master/docs/notebooks/01-numeric-planning.ipynb + :alt: Open In Colab + + +In this notebook we show how to model a numeric problem. + +We will define a simple problem with counters that must be incremented. + + Optimal Planning ---------------- diff --git a/docs/notebooks/01-numeric-planning.ipynb b/docs/notebooks/01-numeric-planning.ipynb new file mode 100644 index 000000000..3375061a1 --- /dev/null +++ b/docs/notebooks/01-numeric-planning.ipynb @@ -0,0 +1,584 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "vXUqFpLObzhb" + }, + "source": [ + "#Encoding a numeric planning problem\n", + "\n", + "This python notebook shows how to use the unified planning library to model a simple numeric planning problem.\n", + "We start by doing some preliminary installation that is needed to use a Java planner\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "VdTlwR0-DDCa", + "outputId": "0bb3ef4d-8f75-4f67-c601-41cda8be325b", + "scrolled": false, + "tags": [ + "remove_from_CI" + ] + }, + "outputs": [], + "source": [ + "!apt-get install openjdk-17-jdk" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "t8dCcpf7mivV" + }, + "source": [ + "## Setup the library\n", + "\n", + "We install (from github) the unified planning library." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "BoqALxJWdfl8", + "outputId": "299282e7-07f5-46df-e607-47da781dca50", + "scrolled": true, + "tags": [ + "remove_from_CI" + ] + }, + "outputs": [], + "source": [ + "!pip install --pre unified-planning[enhsp]" + ] + }, + { + "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": "xn5l-SVxufFA" + }, + "source": [ + "We start importing the shortcuts." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "otZVSku3idJC" + }, + "outputs": [], + "source": [ + "from unified_planning.shortcuts import *" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "M9DCTuoSu2vh" + }, + "source": [ + "Now we start to model a problem involving three numeric variables $c_0$, $c_1$ and $c_2$ that can be increased and decreased. The goal of this problem is to change the variables values such that $c_0 < c_1 < c_2$. We name with value the lifted fluent that lets us access to the value of a given counter $c$.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "nBaUofy3Ko7V" + }, + "source": [ + "\n", + "### Creating the fluent\n", + "\n", + "First, we define the `UserTypes` and the `Fluents`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "e5BWi6wyuqyB" + }, + "outputs": [], + "source": [ + "Counter = UserType('Counter')\n", + "\n", + "value = Fluent('value', IntType(), m=Counter)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "5sJwsWGy3-K5" + }, + "source": [ + "### Creating the actions\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "J-cU6CvL0-Pv" + }, + "outputs": [], + "source": [ + "inc = InstantaneousAction('increment',c=Counter)\n", + "c = inc.parameter('c')\n", + "inc.add_precondition(LE(value(c), 10))\n", + "inc.add_increase_effect(value(c), 1)\n", + "\n", + "dec = InstantaneousAction('decrement',c=Counter)\n", + "c = dec.parameter('c')\n", + "dec.add_precondition(GT(value(c), 0))\n", + "dec.add_decrease_effect(value(c),1)\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. Note here that we do not need to specify all values for each object. These are set to 0 using the default intial value parameter.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "vEDxcqkLvm9d", + "outputId": "aae981f7-dac8-4a2e-e02a-0619edefd271" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "problem name = problem\n", + "\n", + "types = [Counter]\n", + "\n", + "fluents = [\n", + " integer value[m=Counter]\n", + "]\n", + "\n", + "actions = [\n", + " action increment(Counter c) {\n", + " preconditions = [\n", + " (value(c) <= 10)\n", + " ]\n", + " effects = [\n", + " value(c) += 1\n", + " ]\n", + " }\n", + " action decrement(Counter c) {\n", + " preconditions = [\n", + " (0 < value(c))\n", + " ]\n", + " effects = [\n", + " value(c) -= 1\n", + " ]\n", + " }\n", + "]\n", + "\n", + "objects = [\n", + " Counter: [c0, c1, c2]\n", + "]\n", + "\n", + "initial fluents default = [\n", + " integer value[m=Counter] := 0\n", + "]\n", + "\n", + "initial values = [\n", + "]\n", + "\n", + "goals = [\n", + " (((value(c1) + 1) <= value(c2)) and ((value(c0) + 1) <= value(c1)))\n", + "]\n" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "problem = Problem('problem')\n", + "\n", + "problem.add_fluent(value, default_initial_value=0)\n", + "C0 = Object('c0', Counter)\n", + "C1 = Object('c1', Counter)\n", + "C2 = Object('c2', Counter)\n", + "problem.add_object(C0)\n", + "problem.add_object(C1)\n", + "problem.add_object(C2)\n", + "problem.add_action(inc)\n", + "problem.add_action(dec)\n", + "problem.add_goal(And( GE(value(C2),Plus(value(C1),1)), GE(value(C1),Plus(value(C0),1))))\n", + "problem\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Pfo5MK-4FIs4" + }, + "source": [ + "\n", + "Now we see how we can generate another, larger problem, much more compactly using a more programmatic definition\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "v62jmabZCyZr", + "outputId": "e44cd067-6ceb-4db1-b939-34c79248f64c" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "problem name = Large_problems\n", + "\n", + "types = [Counter]\n", + "\n", + "fluents = [\n", + " integer value[m=Counter]\n", + "]\n", + "\n", + "actions = [\n", + " action increment(Counter c) {\n", + " preconditions = [\n", + " (value(c) <= 10)\n", + " ]\n", + " effects = [\n", + " value(c) += 1\n", + " ]\n", + " }\n", + " action decrement(Counter c) {\n", + " preconditions = [\n", + " (0 < value(c))\n", + " ]\n", + " effects = [\n", + " value(c) -= 1\n", + " ]\n", + " }\n", + "]\n", + "\n", + "objects = [\n", + " Counter: [c0, c1, c2, c3, c4, c5, c6, c7, c8]\n", + "]\n", + "\n", + "initial fluents default = [\n", + " integer value[m=Counter] := 0\n", + "]\n", + "\n", + "initial values = [\n", + "]\n", + "\n", + "goals = [\n", + " ((value(c0) + 1) <= value(c1))\n", + " ((value(c1) + 1) <= value(c2))\n", + " ((value(c2) + 1) <= value(c3))\n", + " ((value(c3) + 1) <= value(c4))\n", + " ((value(c4) + 1) <= value(c5))\n", + " ((value(c5) + 1) <= value(c6))\n", + " ((value(c6) + 1) <= value(c7))\n", + " ((value(c7) + 1) <= value(c8))\n", + "]\n" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "N = 9 #This is the number of counters\n", + "\n", + "p2 = Problem('Large_problems')\n", + "\n", + "p2.add_fluent(value, default_initial_value=0)\n", + "p2.add_objects([Object(f'c{i}',Counter) for i in range(N)])\n", + "p2.add_action(inc)\n", + "p2.add_action(dec)\n", + "\n", + "for i in range(N-1):\n", + " p2.add_goal(GE(value(p2.object(f'c{i+1}')),Plus(value(p2.object(f'c{i}')),1)))\n", + "\n", + "p2" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "z3l0y9kaKMII" + }, + "source": [ + "### Solving the small and the parametric problem\n", + "\n", + "The unified_planning can either select among the available planners one which is suited for the task at hand (looking at the problem kind), or use the user defined planning. In what follows we first attempt to solve the small problem with three counters and ask the UP to use a specific planning system (ENHSP), and then one with N=9 counters (problem p2) asking the UP to automatically select the engine\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "frcEeD-oKO2m", + "outputId": "1ea9a630-db3f-47d8-b011-85e97be31203" + }, + "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 ``, \u001b[0m\u001b[96myou are using the following planning engine:\n", + "\u001b[0m\u001b[96m * Engine name: ENHSP\n", + " * Developers: Enrico Scala\n", + "\u001b[0m\u001b[96m * Description: \u001b[0m\u001b[96mExpressive Numeric Heuristic Search Planner.\u001b[0m\u001b[96m\n", + "\u001b[0m\u001b[96m\n", + "\u001b[0mSAT-enhsp returned:\n", + "SequentialPlan:\n", + " increment(c2)\n", + " increment(c1)\n", + " increment(c2)\n" + ] + } + ], + "source": [ + "with OneshotPlanner(name='enhsp') as planner:\n", + " result = planner.solve(problem)\n", + " plan = result.plan\n", + " if plan is not None:\n", + " print(\"%s returned:\" % planner.name)\n", + " print(plan)\n", + " else:\n", + " print(\"No plan found.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "1Xz-IqYoE56M", + "outputId": "8a4b48ec-06e7-41f4-837d-c4c2c0564d14" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[96m *** Credits ***\n", + "\u001b[0m\u001b[96m * In operation mode `OneshotPlanner` at line 1 of ``, \u001b[0m\u001b[96myou are using the following planning engine:\n", + "\u001b[0m\u001b[96m * Engine name: ENHSP\n", + " * Developers: Enrico Scala\n", + "\u001b[0m\u001b[96m * Description: \u001b[0m\u001b[96mExpressive Numeric Heuristic Search Planner.\u001b[0m\u001b[96m\n", + "\u001b[0m\u001b[96m\n", + "\u001b[0mSAT-enhsp returned:\n", + "SequentialPlan:\n", + " increment(c8)\n", + " increment(c7)\n", + " increment(c8)\n", + " increment(c8)\n", + " increment(c7)\n", + " increment(c6)\n", + " increment(c8)\n", + " increment(c5)\n", + " increment(c6)\n", + " increment(c7)\n", + " increment(c8)\n", + " increment(c4)\n", + " increment(c5)\n", + " increment(c6)\n", + " increment(c7)\n", + " increment(c8)\n", + " increment(c8)\n", + " increment(c3)\n", + " increment(c4)\n", + " increment(c5)\n", + " increment(c7)\n", + " increment(c6)\n", + " increment(c7)\n", + " increment(c8)\n", + " increment(c6)\n", + " increment(c5)\n", + " increment(c4)\n", + " increment(c2)\n", + " increment(c3)\n", + " increment(c7)\n", + " increment(c6)\n", + " increment(c5)\n", + " increment(c4)\n", + " increment(c3)\n", + " increment(c1)\n", + " increment(c2)\n" + ] + } + ], + "source": [ + "with OneshotPlanner(problem_kind=problem.kind) as planner:\n", + " result = planner.solve(p2)\n", + " plan = result.plan\n", + " if plan is not None:\n", + " print(\"%s returned:\" % planner.name)\n", + " print(plan)\n", + " else:\n", + " print(\"No plan found.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "YGfAogQpHZY-" + }, + "source": [ + "Now let us create a problem medium-sized problem, set up a minimisation function as minimize the number of actions, and see how this can be solved optimally." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "v41WrIcmH1eJ", + "outputId": "e80e8b7b-dfb1-4d88-84a2-856c0ba9a50f" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[96m *** Credits ***\n", + "\u001b[0m\u001b[96m * In operation mode `OneshotPlanner` at line 17 of ``, \u001b[0m\u001b[96myou are using the following planning engine:\n", + "\u001b[0m\u001b[96m * Engine name: ENHSP\n", + " * Developers: Enrico Scala\n", + "\u001b[0m\u001b[96m * Description: \u001b[0m\u001b[96mExpressive Numeric Heuristic Search Planner.\u001b[0m\u001b[96m\n", + "\u001b[0m\u001b[96m\n", + "\u001b[0mOPT-enhsp returned:\n", + "SequentialPlan:\n", + " increment(c6)\n", + " increment(c5)\n", + " increment(c4)\n", + " increment(c3)\n", + " increment(c6)\n", + " increment(c5)\n", + " increment(c4)\n", + " increment(c3)\n", + " increment(c6)\n", + " increment(c5)\n", + " increment(c4)\n", + " increment(c6)\n", + " increment(c5)\n", + " increment(c2)\n", + " increment(c6)\n", + " increment(c6)\n", + " increment(c5)\n", + " increment(c4)\n", + " increment(c3)\n", + " increment(c2)\n", + " increment(c1)\n" + ] + } + ], + "source": [ + "from unified_planning.model.metrics import MinimizeSequentialPlanLength\n", + "\n", + "N = 7 #This is the number of counters\n", + "\n", + "mediumSizeProblem = Problem('Medium_sized_problem')\n", + "\n", + "mediumSizeProblem.add_fluent(value, default_initial_value=0)\n", + "mediumSizeProblem.add_objects([Object(f'c{i}',Counter) for i in range(N)])\n", + "mediumSizeProblem.add_action(inc)\n", + "mediumSizeProblem.add_action(dec)\n", + "metric = MinimizeSequentialPlanLength()\n", + "mediumSizeProblem.add_quality_metric(metric)\n", + "\n", + "for i in range(N-1):\n", + " mediumSizeProblem.add_goal(GE(value(p2.object(f'c{i+1}')),Plus(value(p2.object(f'c{i}')),1)))\n", + "\n", + "with OneshotPlanner(problem_kind=problem.kind,optimality_guarantee=True) as planner:\n", + " result = planner.solve(mediumSizeProblem)\n", + " plan = result.plan\n", + " if plan is not None:\n", + " print(\"%s returned:\" % planner.name)\n", + " print(plan)\n", + " else:\n", + " print(\"No plan found.\")\n" + ] + } + ], + "metadata": { + "celltoolbar": "Tags", + "colab": { + "name": "Numeric 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" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/docs/notebooks/engines/README.md b/docs/notebooks/engines/README.md index fc4f1b69b..7b88bfeb3 100644 --- a/docs/notebooks/engines/README.md +++ b/docs/notebooks/engines/README.md @@ -20,6 +20,9 @@ Classical Planning Numeric Planning ---------------- +- In this notebook we show how to model a numeric problem: + + [![Open In GitHub](https://img.shields.io/badge/see-Github-579aca?logo=github)](../01-numeric-planning.ipynb) [![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/01-numeric-planning.ipynb) Temporal Planning diff --git a/setup.py b/setup.py index 0810633bf..10c7ef769 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ "tarski": ["tarski[arithmetic]"], "pyperplan": ["up-pyperplan==1.0.0"], "tamer": ["up-tamer==1.0.0"], - "enhsp": ["up-enhsp==0.0.16"], + "enhsp": ["up-enhsp==0.0.17"], "fast-downward": ["up-fast-downward==0.2.3"], "lpg": ["up-lpg==0.0.7"], "fmap": ["up-fmap==0.0.7"], @@ -35,7 +35,7 @@ "tarski[arithmetic]", "up-pyperplan==1.0.0", "up-tamer==1.0.0", - "up-enhsp==0.0.16", + "up-enhsp==0.0.17", "up-fast-downward==0.2.3", "up-lpg==0.0.7", "up-fmap==0.0.7",