Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jupyter notebooks for showcasing Apps from Models experience #90

Merged
merged 1 commit into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading