Skip to content

Commit

Permalink
Jupyter notebooks for shwocasing Apps from Models experience
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastian-quintero committed Jan 8, 2025
1 parent 2b671d6 commit 1ed6cba
Show file tree
Hide file tree
Showing 6 changed files with 1,232 additions and 0 deletions.
3 changes: 3 additions & 0 deletions python-gurobi-knapsack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
374 changes: 374 additions & 0 deletions python-gurobi-knapsack/main.ipynb
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
}
3 changes: 3 additions & 0 deletions python-highs-knapsack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 1ed6cba

Please sign in to comment.