-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Jupyter notebooks for shwocasing Apps from Models experience
- Loading branch information
1 parent
2b671d6
commit 1ed6cba
Showing
6 changed files
with
1,232 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.