diff --git a/python-gurobi-knapsack/README.md b/python-gurobi-knapsack/README.md index 0e42e36..3b54d39 100644 --- a/python-gurobi-knapsack/README.md +++ b/python-gurobi-knapsack/README.md @@ -19,6 +19,9 @@ key. Modify the `app.yaml` file to include the `gurobi.lic` in the files list. python3 main.py -input input.json -output output.json -duration 30 ``` +Alternatively, you may reference the `main.ipynb` Jupyter notebook which, in +addition to running locally, showcases how to push the app and run it remotely. + ## Next steps * Open `main.py` and modify the model. diff --git a/python-gurobi-knapsack/main.ipynb b/python-gurobi-knapsack/main.ipynb new file mode 100644 index 0000000..d148ee8 --- /dev/null +++ b/python-gurobi-knapsack/main.ipynb @@ -0,0 +1,374 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Apps from Models - Gurobi\n", + "\n", + "This notebook shows how to:\n", + "\n", + "1. Create a decision model that solves a knapsack problem with the\n", + " Gurobi solver.\n", + "2. Run it locally.\n", + "3. Push it to a Nextmv Cloud Application.\n", + "4. Run it remotely.\n", + "\n", + "Let’s dive right in! 🐰" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Dependencies\n", + "\n", + "Install the necessary Python packages.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install gurobipy\n", + "%pip install \"nextmv[all]\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Imports\n", + "\n", + "Add the necessary imports." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import json\n", + "import time\n", + "\n", + "import gurobipy as gp\n", + "from gurobipy import GRB\n", + "import nextmv\n", + "import nextmv.cloud" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 1. Create the decision model\n", + "\n", + "Use Gurobi to solve a classic MIP with the `nextmv.Model` class." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "STATUS = {\n", + " GRB.SUBOPTIMAL: \"suboptimal\",\n", + " GRB.INFEASIBLE: \"infeasible\",\n", + " GRB.OPTIMAL: \"optimal\",\n", + " GRB.UNBOUNDED: \"unbounded\",\n", + "}\n", + "\n", + "class DecisionModel(nextmv.Model):\n", + " def solve(self, input: nextmv.Input) -> nextmv.Output:\n", + " \"\"\"Solves the given problem and returns the solution.\"\"\"\n", + "\n", + " start_time = time.time()\n", + " nextmv.redirect_stdout() # Solver chatter is logged to stderr.\n", + "\n", + " # Creates the environment.\n", + " env = gp.Env(empty=True)\n", + "\n", + " # Read the license file, if available.\n", + " if os.path.isfile(\"gurobi.lic\"):\n", + " env.readParams(\"gurobi.lic\")\n", + "\n", + " # Creates the model.\n", + " env.start()\n", + " model = gp.Model(env=env)\n", + " model.Params.TimeLimit = input.options.duration\n", + "\n", + " # Initializes the linear sums.\n", + " weights = 0.0\n", + " values = 0.0\n", + "\n", + " # Creates the decision variables and adds them to the linear sums.\n", + " items = []\n", + " for item in input.data[\"items\"]:\n", + " item_variable = model.addVar(vtype=GRB.BINARY, name=item[\"id\"])\n", + " items.append({\"item\": item, \"variable\": item_variable})\n", + " weights += item_variable * item[\"weight\"]\n", + " values += item_variable * item[\"value\"]\n", + "\n", + " # This constraint ensures the weight capacity of the knapsack will not be\n", + " # exceeded.\n", + " model.addConstr(weights <= input.data[\"weight_capacity\"])\n", + "\n", + " # Sets the objective function: maximize the value of the chosen items.\n", + " model.setObjective(expr=values, sense=GRB.MAXIMIZE)\n", + "\n", + " # Solves the problem.\n", + " model.optimize()\n", + "\n", + " # Determines which items were chosen.\n", + " chosen_items = [item[\"item\"] for item in items if item[\"variable\"].X > 0.9]\n", + "\n", + " input.options.provider = \"gurobi\"\n", + " statistics = nextmv.Statistics(\n", + " run=nextmv.RunStatistics(duration=time.time() - start_time),\n", + " result=nextmv.ResultStatistics(\n", + " duration=model.Runtime,\n", + " value=model.ObjVal,\n", + " custom={\n", + " \"status\": STATUS.get(model.Status, \"unknown\"),\n", + " \"variables\": model.NumVars,\n", + " \"constraints\": model.NumConstrs,\n", + " },\n", + " ),\n", + " )\n", + "\n", + " return nextmv.Output(\n", + " options=input.options,\n", + " solution={\"items\": chosen_items},\n", + " statistics=statistics,\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 2. Run the model locally\n", + "\n", + "Define the options that the model needs." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "options = nextmv.Options(\n", + " nextmv.Parameter(\"duration\", int, 30, \"Max runtime duration (in seconds).\", False),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Instantiate the model." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "model = DecisionModel()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define some sample input data." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "sample_input = {\n", + " \"items\": [\n", + " {\n", + " \"id\": \"cat\",\n", + " \"value\": 100,\n", + " \"weight\": 20\n", + " },\n", + " {\n", + " \"id\": \"dog\",\n", + " \"value\": 20,\n", + " \"weight\": 45\n", + " },\n", + " {\n", + " \"id\": \"water\",\n", + " \"value\": 40,\n", + " \"weight\": 2\n", + " },\n", + " {\n", + " \"id\": \"phone\",\n", + " \"value\": 6,\n", + " \"weight\": 1\n", + " },\n", + " {\n", + " \"id\": \"book\",\n", + " \"value\": 63,\n", + " \"weight\": 10\n", + " },\n", + " {\n", + " \"id\": \"rx\",\n", + " \"value\": 81,\n", + " \"weight\": 1\n", + " },\n", + " {\n", + " \"id\": \"tablet\",\n", + " \"value\": 28,\n", + " \"weight\": 8\n", + " },\n", + " {\n", + " \"id\": \"coat\",\n", + " \"value\": 44,\n", + " \"weight\": 9\n", + " },\n", + " {\n", + " \"id\": \"laptop\",\n", + " \"value\": 51,\n", + " \"weight\": 13\n", + " },\n", + " {\n", + " \"id\": \"keys\",\n", + " \"value\": 92,\n", + " \"weight\": 1\n", + " },\n", + " {\n", + " \"id\": \"nuts\",\n", + " \"value\": 18,\n", + " \"weight\": 4\n", + " }\n", + " ],\n", + " \"weight_capacity\": 50\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run the model locally. First, write the file with the license info, then solve the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%writefile gurobi.lic\n", + "WLSACCESSID=REPLACE_ME\n", + "WLSSECRET=REPLACE_ME\n", + "LICENSEID=REPLACE_ME" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "input = nextmv.Input(data=sample_input, options=options)\n", + "output = model.solve(input)\n", + "print(json.dumps(output.solution, indent=2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 3. Push the model to Nextmv Cloud\n", + "\n", + "Convert the model to an application, hence the workflow name \"Apps from\n", + "Models\". Push the application to Nextmv Cloud.\n", + "\n", + "Every app is production-ready with a full-featured API." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "client = nextmv.cloud.Client(api_key=os.getenv(\"NEXTMV_API_KEY\"))\n", + "application = nextmv.cloud.Application(client=client, id=\"apps-from-models-gurobi\")\n", + "\n", + "model_configuration = nextmv.ModelConfiguration(\n", + " name=\"gurobi_model\",\n", + " requirements=[\n", + " \"gurobipy==11.0.0\",\n", + " \"nextmv==0.14.2\",\n", + " ],\n", + " options=options,\n", + ")\n", + "manifest = nextmv.cloud.Manifest.from_model_configuration(model_configuration)\n", + "manifest.files.append(\"gurobi.lic\")\n", + "\n", + "application.push(\n", + " model=model,\n", + " model_configuration=model_configuration,\n", + " manifest=manifest,\n", + " verbose=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 4. Run the model remotely\n", + "\n", + "Execute an app run. This remote run produces an output that should be the same as the local run." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result = application.new_run_with_result(input=sample_input, instance_id=\"devint\")\n", + "print(json.dumps(result.output, indent=2))" + ] + } + ], + "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.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/python-highs-knapsack/README.md b/python-highs-knapsack/README.md index 4452b76..7c897b6 100644 --- a/python-highs-knapsack/README.md +++ b/python-highs-knapsack/README.md @@ -15,6 +15,9 @@ solver. We solve a knapsack Mixed Integer Programming problem. python3 main.py -input input.json -output output.json -duration 30 ``` +Alternatively, you may reference the `main.ipynb` Jupyter notebook which, in +addition to running locally, showcases how to push the app and run it remotely. + ## Next steps * Open `main.py` and modify the model. diff --git a/python-highs-knapsack/main.ipynb b/python-highs-knapsack/main.ipynb new file mode 100644 index 0000000..643ec4b --- /dev/null +++ b/python-highs-knapsack/main.ipynb @@ -0,0 +1,342 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Apps from Models - HiGHS\n", + "\n", + "This notebook shows how to:\n", + "\n", + "1. Create a decision model that solves a knapsack problem with the\n", + " HiGHS solver.\n", + "2. Run it locally.\n", + "3. Push it to a Nextmv Cloud Application.\n", + "4. Run it remotely.\n", + "\n", + "Let’s dive right in! 🐰" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Dependencies\n", + "\n", + "Install the necessary Python packages.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install highspy\n", + "%pip install \"nextmv[all]\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Imports\n", + "\n", + "Add the necessary imports." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import json\n", + "import time\n", + "from importlib.metadata import version\n", + "\n", + "import highspy\n", + "import nextmv\n", + "import nextmv.cloud" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 1. Create the decision model\n", + "\n", + "Use HiGHS to solve a classic MIP with the `nextmv.Model` class." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "class DecisionModel(nextmv.Model):\n", + " def solve(self, input: nextmv.Input) -> nextmv.Output:\n", + " \"\"\"Solves the given problem and returns the solution.\"\"\"\n", + "\n", + " start_time = time.time()\n", + "\n", + " # Creates the solver.\n", + " solver = highspy.Highs()\n", + " solver.silent() # Solver output ignores stdout redirect, silence it.\n", + " solver.setOptionValue(\"time_limit\", input.options.duration)\n", + "\n", + " # Initializes the linear sums.\n", + " weights = 0.0\n", + " values = 0.0\n", + "\n", + " # Creates the decision variables and adds them to the linear sums.\n", + " items = []\n", + " for item in input.data[\"items\"]:\n", + " item_variable = solver.addVariable(0.0, 1.0, item[\"value\"])\n", + " items.append({\"item\": item, \"variable\": item_variable})\n", + " weights += item_variable * item[\"weight\"]\n", + " values += item_variable * item[\"value\"]\n", + "\n", + " # This constraint ensures the weight capacity of the knapsack will not be\n", + " # exceeded.\n", + " solver.addConstr(weights <= input.data[\"weight_capacity\"])\n", + "\n", + " # Sets the objective function: maximize the value of the chosen items.\n", + " status = solver.maximize(values)\n", + "\n", + " # Determines which items were chosen.\n", + " chosen_items = [item[\"item\"] for item in items if solver.val(item[\"variable\"]) > 0.9]\n", + "\n", + " input.options.version = version(\"highspy\")\n", + "\n", + " statistics = nextmv.Statistics(\n", + " run=nextmv.RunStatistics(duration=time.time() - start_time),\n", + " result=nextmv.ResultStatistics(\n", + " value=sum(item[\"value\"] for item in chosen_items),\n", + " custom={\n", + " \"status\": str(status),\n", + " \"variables\": solver.numVariables,\n", + " \"constraints\": solver.numConstrs,\n", + " },\n", + " ),\n", + " )\n", + "\n", + " return nextmv.Output(\n", + " options=input.options,\n", + " solution={\"items\": chosen_items},\n", + " statistics=statistics,\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 2. Run the model locally\n", + "\n", + "Define the options that the model needs." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "options = nextmv.Options(\n", + " nextmv.Parameter(\"duration\", int, 30, \"Max runtime duration (in seconds).\", False),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Instantiate the model." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "model = DecisionModel()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define some sample input data." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "sample_input = {\n", + " \"items\": [\n", + " {\n", + " \"id\": \"cat\",\n", + " \"value\": 100,\n", + " \"weight\": 20\n", + " },\n", + " {\n", + " \"id\": \"dog\",\n", + " \"value\": 20,\n", + " \"weight\": 45\n", + " },\n", + " {\n", + " \"id\": \"water\",\n", + " \"value\": 40,\n", + " \"weight\": 2\n", + " },\n", + " {\n", + " \"id\": \"phone\",\n", + " \"value\": 6,\n", + " \"weight\": 1\n", + " },\n", + " {\n", + " \"id\": \"book\",\n", + " \"value\": 63,\n", + " \"weight\": 10\n", + " },\n", + " {\n", + " \"id\": \"rx\",\n", + " \"value\": 81,\n", + " \"weight\": 1\n", + " },\n", + " {\n", + " \"id\": \"tablet\",\n", + " \"value\": 28,\n", + " \"weight\": 8\n", + " },\n", + " {\n", + " \"id\": \"coat\",\n", + " \"value\": 44,\n", + " \"weight\": 9\n", + " },\n", + " {\n", + " \"id\": \"laptop\",\n", + " \"value\": 51,\n", + " \"weight\": 13\n", + " },\n", + " {\n", + " \"id\": \"keys\",\n", + " \"value\": 92,\n", + " \"weight\": 1\n", + " },\n", + " {\n", + " \"id\": \"nuts\",\n", + " \"value\": 18,\n", + " \"weight\": 4\n", + " }\n", + " ],\n", + " \"weight_capacity\": 50\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run the model locally." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "input = nextmv.Input(data=sample_input, options=options)\n", + "output = model.solve(input)\n", + "print(json.dumps(output.solution, indent=2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 3. Push the model to Nextmv Cloud\n", + "\n", + "Convert the model to an application, hence the workflow name \"Apps from\n", + "Models\". Push the application to Nextmv Cloud.\n", + "\n", + "Every app is production-ready with a full-featured API." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "client = nextmv.cloud.Client(api_key=os.getenv(\"NEXTMV_API_KEY\"))\n", + "application = nextmv.cloud.Application(client=client, id=\"apps-from-models-highs-sample\")\n", + "\n", + "model_configuration = nextmv.ModelConfiguration(\n", + " name=\"highs_model\",\n", + " requirements=[\n", + " \"highspy==1.7.2\",\n", + " \"nextmv==0.14.21\"\n", + " ],\n", + " options=options,\n", + ")\n", + "manifest = nextmv.cloud.Manifest.from_model_configuration(model_configuration)\n", + "application.push(\n", + " manifest=manifest,\n", + " model=model,\n", + " model_configuration=model_configuration,\n", + " verbose=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 4. Run the model remotely\n", + "\n", + "Execute an app run. This remote run produces an output that should be the same as the local run." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result = application.new_run_with_result(input=sample_input, instance_id=\"devint\")\n", + "print(json.dumps(result.output, indent=2))\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.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/python-nextroute/README.md b/python-nextroute/README.md index 6f9104d..c8e90c4 100644 --- a/python-nextroute/README.md +++ b/python-nextroute/README.md @@ -16,6 +16,9 @@ Example for running a Python application on the Nextmv Platform using the -solve_duration 10 ``` +Alternatively, you may reference the `main.ipynb` Jupyter notebook which, in +addition to running locally, showcases how to push the app and run it remotely. + ## Next steps * Open `main.py` and modify the model. diff --git a/python-nextroute/main.ipynb b/python-nextroute/main.ipynb new file mode 100644 index 0000000..5ada276 --- /dev/null +++ b/python-nextroute/main.ipynb @@ -0,0 +1,507 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Apps from Models - Nextroute\n", + "\n", + "This notebook shows how to:\n", + "\n", + "1. Create a decision model that solves a Vehicle Routing Problem (VRP) with the\n", + " Nextroute engine.\n", + "2. Run it locally.\n", + "3. Push it to a Nextmv Cloud Application.\n", + "4. Run it remotely.\n", + "\n", + "Let’s dive right in! 🐰" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Dependencies\n", + "\n", + "Install the necessary Python packages.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%pip install nextroute\n", + "%pip install \"nextmv[all]\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Imports\n", + "\n", + "Add the necessary imports." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import json\n", + "import nextroute\n", + "import nextmv\n", + "import nextmv.cloud" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 1. Create the decision model\n", + "\n", + "Use Nextroute to solve a Vehicle Routing Problem (VRP) with the `nextmv.Model` class." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "class DecisionModel(nextmv.Model):\n", + " def solve(self, input: nextmv.Input) -> nextmv.Output:\n", + " \"\"\"Solves the given problem and returns the solution.\"\"\"\n", + "\n", + " nextroute_input = nextroute.schema.Input.from_dict(input.data)\n", + " nextroute_options = nextroute.Options.extract_from_dict(input.options.to_dict())\n", + " nextroute_output = nextroute.solve(nextroute_input, nextroute_options)\n", + "\n", + " return nextmv.Output(\n", + " options=input.options,\n", + " solution=nextroute_output.solutions[0].to_dict(),\n", + " statistics=nextroute_output.statistics.to_dict(),\n", + " )\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 2. Run the model locally\n", + "\n", + "Define the options that the model needs." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "parameters = []\n", + "\n", + "default_options = nextroute.Options()\n", + "for name, default_value in default_options.to_dict().items():\n", + " parameters.append(nextmv.Parameter(name.lower(), type(default_value), default_value, name, False))\n", + "\n", + "options = nextmv.Options(*parameters)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Instantiate the model." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "model = DecisionModel()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define some sample input data." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "sample_input = {\n", + " \"defaults\": {\n", + " \"vehicles\": {\n", + " \"capacity\": {\n", + " \"bunnies\": 20,\n", + " \"rabbits\": 10\n", + " },\n", + " \"start_location\": {\n", + " \"lat\": 35.791729813680874,\n", + " \"lon\": -78.7401685145487\n", + " },\n", + " \"end_location\": {\n", + " \"lat\": 35.791729813680874,\n", + " \"lon\": -78.7401685145487\n", + " },\n", + " \"speed\": 10\n", + " },\n", + " \"stops\": {\n", + " \"duration\": 300,\n", + " \"quantity\": {\n", + " \"bunnies\": -1,\n", + " \"rabbits\": -1\n", + " },\n", + " \"unplanned_penalty\": 200000,\n", + " \"target_arrival_time\": \"2023-01-01T10:00:00Z\",\n", + " \"early_arrival_time_penalty\": 1.5,\n", + " \"late_arrival_time_penalty\": 1.5\n", + " }\n", + " },\n", + " \"stops\": [\n", + " {\n", + " \"id\": \"s1\",\n", + " \"location\": {\n", + " \"lon\": -78.90919,\n", + " \"lat\": 35.72389\n", + " },\n", + " \"compatibility_attributes\": [\"premium\"]\n", + " },\n", + " {\n", + " \"id\": \"s2\",\n", + " \"location\": {\n", + " \"lon\": -78.813862,\n", + " \"lat\": 35.75712\n", + " },\n", + " \"compatibility_attributes\": [\"premium\"]\n", + " },\n", + " {\n", + " \"id\": \"s3\",\n", + " \"location\": {\n", + " \"lon\": -78.92996,\n", + " \"lat\": 35.932795\n", + " },\n", + " \"compatibility_attributes\": [\"premium\"]\n", + " },\n", + " {\n", + " \"id\": \"s4\",\n", + " \"location\": {\n", + " \"lon\": -78.505745,\n", + " \"lat\": 35.77772\n", + " },\n", + " \"compatibility_attributes\": [\"premium\"]\n", + " },\n", + " {\n", + " \"id\": \"s5\",\n", + " \"location\": {\n", + " \"lon\": -78.75084,\n", + " \"lat\": 35.732995\n", + " },\n", + " \"compatibility_attributes\": [\"premium\"]\n", + " },\n", + " {\n", + " \"id\": \"s6\",\n", + " \"location\": {\n", + " \"lon\": -78.788025,\n", + " \"lat\": 35.813025\n", + " },\n", + " \"compatibility_attributes\": [\"premium\"]\n", + " },\n", + " {\n", + " \"id\": \"s7\",\n", + " \"location\": {\n", + " \"lon\": -78.749391,\n", + " \"lat\": 35.74261\n", + " },\n", + " \"compatibility_attributes\": [\"premium\"]\n", + " },\n", + " {\n", + " \"id\": \"s8\",\n", + " \"location\": {\n", + " \"lon\": -78.94658,\n", + " \"lat\": 36.039135\n", + " },\n", + " \"compatibility_attributes\": [\"basic\"]\n", + " },\n", + " {\n", + " \"id\": \"s9\",\n", + " \"location\": {\n", + " \"lon\": -78.64972,\n", + " \"lat\": 35.64796\n", + " },\n", + " \"compatibility_attributes\": [\"basic\"]\n", + " },\n", + " {\n", + " \"id\": \"s10\",\n", + " \"location\": {\n", + " \"lon\": -78.747955,\n", + " \"lat\": 35.672955\n", + " },\n", + " \"compatibility_attributes\": [\"basic\"]\n", + " },\n", + " {\n", + " \"id\": \"s11\",\n", + " \"location\": {\n", + " \"lon\": -78.83403,\n", + " \"lat\": 35.77013\n", + " },\n", + " \"compatibility_attributes\": [\"basic\"]\n", + " },\n", + " {\n", + " \"id\": \"s12\",\n", + " \"location\": {\n", + " \"lon\": -78.864465,\n", + " \"lat\": 35.782855\n", + " },\n", + " \"compatibility_attributes\": [\"basic\"]\n", + " },\n", + " {\n", + " \"id\": \"s13\",\n", + " \"location\": {\n", + " \"lon\": -78.952142,\n", + " \"lat\": 35.88029\n", + " },\n", + " \"compatibility_attributes\": [\"basic\"]\n", + " },\n", + " {\n", + " \"id\": \"s14\",\n", + " \"location\": {\n", + " \"lon\": -78.52748,\n", + " \"lat\": 35.961465\n", + " },\n", + " \"compatibility_attributes\": [\"basic\"]\n", + " },\n", + " {\n", + " \"id\": \"s15\",\n", + " \"location\": {\n", + " \"lon\": -78.89832,\n", + " \"lat\": 35.83202\n", + " }\n", + " },\n", + " {\n", + " \"id\": \"s16\",\n", + " \"location\": {\n", + " \"lon\": -78.63216,\n", + " \"lat\": 35.83458\n", + " }\n", + " },\n", + " {\n", + " \"id\": \"s17\",\n", + " \"location\": {\n", + " \"lon\": -78.76063,\n", + " \"lat\": 35.67337\n", + " }\n", + " },\n", + " {\n", + " \"id\": \"s18\",\n", + " \"location\": {\n", + " \"lon\": -78.911485,\n", + " \"lat\": 36.009015\n", + " }\n", + " },\n", + " {\n", + " \"id\": \"s19\",\n", + " \"location\": {\n", + " \"lon\": -78.522705,\n", + " \"lat\": 35.93663\n", + " }\n", + " },\n", + " {\n", + " \"id\": \"s20\",\n", + " \"location\": {\n", + " \"lon\": -78.995162,\n", + " \"lat\": 35.97414\n", + " }\n", + " },\n", + " {\n", + " \"id\": \"s21\",\n", + " \"location\": {\n", + " \"lon\": -78.50509,\n", + " \"lat\": 35.7606\n", + " }\n", + " },\n", + " {\n", + " \"id\": \"s22\",\n", + " \"location\": {\n", + " \"lon\": -78.828547,\n", + " \"lat\": 35.962635\n", + " },\n", + " \"precedes\": [\"s16\", \"s23\"]\n", + " },\n", + " {\n", + " \"id\": \"s23\",\n", + " \"location\": {\n", + " \"lon\": -78.60914,\n", + " \"lat\": 35.84616\n", + " },\n", + " \"start_time_window\": [\n", + " \"2023-01-01T09:00:00-06:00\",\n", + " \"2023-01-01T09:30:00-06:00\"\n", + " ]\n", + " },\n", + " {\n", + " \"id\": \"s24\",\n", + " \"location\": {\n", + " \"lon\": -78.65521,\n", + " \"lat\": 35.740605\n", + " },\n", + " \"start_time_window\": [\n", + " \"2023-01-01T09:00:00-06:00\",\n", + " \"2023-01-01T09:30:00-06:00\"\n", + " ],\n", + " \"succeeds\": \"s25\"\n", + " },\n", + " {\n", + " \"id\": \"s25\",\n", + " \"location\": {\n", + " \"lon\": -78.92051,\n", + " \"lat\": 35.887575\n", + " },\n", + " \"start_time_window\": [\n", + " \"2023-01-01T09:00:00-06:00\",\n", + " \"2023-01-01T09:30:00-06:00\"\n", + " ],\n", + " \"precedes\": \"s26\"\n", + " },\n", + " {\n", + " \"id\": \"s26\",\n", + " \"location\": {\n", + " \"lon\": -78.84058,\n", + " \"lat\": 35.823865\n", + " },\n", + " \"start_time_window\": [\n", + " \"2023-01-01T09:00:00-06:00\",\n", + " \"2023-01-01T09:30:00-06:00\"\n", + " ]\n", + " }\n", + " ],\n", + " \"vehicles\": [\n", + " {\n", + " \"id\": \"vehicle-0\",\n", + " \"start_time\": \"2023-01-01T06:00:00-06:00\",\n", + " \"end_time\": \"2023-01-01T10:00:00-06:00\",\n", + " \"activation_penalty\": 4000,\n", + " \"compatibility_attributes\": [\"premium\"]\n", + " },\n", + " {\n", + " \"id\": \"vehicle-1\",\n", + " \"start_time\": \"2023-01-01T10:00:00-06:00\",\n", + " \"end_time\": \"2023-01-01T16:00:00-06:00\",\n", + " \"max_duration\": 21000,\n", + " \"compatibility_attributes\": [\"basic\"]\n", + " }\n", + " ]\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run the model locally." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "input = nextmv.Input(data=sample_input, options=options)\n", + "output = model.solve(input)\n", + "print(json.dumps(output.solution, indent=2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 3. Push the model to Nextmv Cloud\n", + "\n", + "Convert the model to an application, hence the workflow name \"Apps from\n", + "Models\". Push the application to Nextmv Cloud.\n", + "\n", + "Every app is production-ready with a full-featured API." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "client = nextmv.cloud.Client(api_key=os.getenv(\"NEXTMV_API_KEY\"))\n", + "application = nextmv.cloud.Application(client=client, id=\"apps-from-models-nextroute\")\n", + "\n", + "model_configuration = nextmv.ModelConfiguration(\n", + " name=\"nextroute_model\",\n", + " requirements=[\n", + " \"nextroute==1.11.0\",\n", + " \"nextmv==0.14.2\"\n", + " ],\n", + " options=options,\n", + ")\n", + "manifest = nextmv.cloud.Manifest.from_model_configuration(model_configuration)\n", + "application.push(\n", + " manifest=manifest,\n", + " model=model,\n", + " model_configuration=model_configuration,\n", + " verbose=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 4. Run the model remotely\n", + "\n", + "Execute an app run. This remote run produces an output that should be the same as the local run." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result = application.new_run_with_result(input=sample_input, instance_id=\"devint\")\n", + "print(json.dumps(result.output, indent=2))" + ] + } + ], + "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.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}