From 33977f154f79b1ca63fdde19dc9254ac6a0d730a Mon Sep 17 00:00:00 2001 From: Sebastian Quintero Date: Tue, 2 Dec 2025 17:22:31 -0500 Subject: [PATCH 1/2] Fixes the Python SDK tutorial code --- .../highs/nextmv-ified/README.md | 39 +++++++++++++++--- .../highs/nextmv-ified/app3.py | 9 +--- .../highs/nextmv-ified/app4.py | 18 ++++---- .../highs/nextmv-ified/app5.py | 16 ++------ .../highs/nextmv-ified/app6.py | 21 +++------- .../highs/nextmv-ified/app7.py | 37 +++++------------ .../highs/nextmv-ified/app8.py | 29 +++++++++++++ .../pyomo/nextmv-ified/README.md | 39 +++++++++++++++--- .../pyomo/nextmv-ified/app3.py | 9 +--- .../pyomo/nextmv-ified/app4.py | 18 ++++---- .../pyomo/nextmv-ified/app5.py | 20 ++------- .../pyomo/nextmv-ified/app6.py | 25 +++++------ .../pyomo/nextmv-ified/app7.py | 41 +++++-------------- .../pyomo/nextmv-ified/app8.py | 33 +++++++++++++++ 14 files changed, 194 insertions(+), 160 deletions(-) create mode 100644 connect-your-model-python/highs/nextmv-ified/app8.py create mode 100644 connect-your-model-python/pyomo/nextmv-ified/app8.py diff --git a/connect-your-model-python/highs/nextmv-ified/README.md b/connect-your-model-python/highs/nextmv-ified/README.md index 9829e60..07dbc43 100644 --- a/connect-your-model-python/highs/nextmv-ified/README.md +++ b/connect-your-model-python/highs/nextmv-ified/README.md @@ -30,20 +30,49 @@ HiGHS][highs-example] turned into a Nextmv Application. python app2.py ``` - * `app3.py`: syncs the local application runs to a Nextmv Cloud - application. Requires a valid Nextmv Cloud API key. + * Export your Nextmv Cloud API key for convenience, as it is required in the upcoming + scripts. ```bash export NEXTMV_API_KEY="" + ``` + + * `app3.py`: creates a new Nextmv Cloud Application. Requires a valid Nextmv + Cloud API key. + + ```bash python app3.py ``` - * `app4.py`: pushes the local executable code to a Nextmv Cloud Application. - Requires a valid Nextmv Cloud API key. + * `app4.py`: syncs the local application runs to a Nextmv Cloud + application. ```bash - export NEXTMV_API_KEY="" python app4.py ``` + * `app5.py`: pushes the local executable code to a Nextmv Cloud Application. + + ```bash + python app5.py + ``` + + * `app6.py`: runs the Nextmv Cloud application, polling for results. + + ```bash + python app6.py + ``` + + * `app7.py`: creates an input set from the last runs. + + ```bash + python app7.py + ``` + + * `app8.py`: creates a scenario test using the input set. + + ```bash + python app8.py + ``` + [highs-example]: https://github.com/ERGO-Code/HiGHS/blob/master/examples/network_flow.py diff --git a/connect-your-model-python/highs/nextmv-ified/app3.py b/connect-your-model-python/highs/nextmv-ified/app3.py index 0ad545e..86b8e67 100644 --- a/connect-your-model-python/highs/nextmv-ified/app3.py +++ b/connect-your-model-python/highs/nextmv-ified/app3.py @@ -1,9 +1,6 @@ import os -from nextmv import cloud, local - -# Instantiate the local application. -local_app = local.Application(src=".") +from nextmv import cloud # Instantiate the cloud application. client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY")) @@ -13,6 +10,4 @@ id="test-highs-app", exist_ok=True, ) - -# Sync the local app's runs to the cloud app. -local_app.sync(target=cloud_app, verbose=True) +print("Cloud application created:", cloud_app.id) diff --git a/connect-your-model-python/highs/nextmv-ified/app4.py b/connect-your-model-python/highs/nextmv-ified/app4.py index 581a8d9..91e4d65 100644 --- a/connect-your-model-python/highs/nextmv-ified/app4.py +++ b/connect-your-model-python/highs/nextmv-ified/app4.py @@ -1,17 +1,13 @@ import os -import nextmv -from nextmv import cloud +from nextmv import cloud, local + +# Instantiate the local application. +local_app = local.Application(src=".") # Instantiate the cloud application. client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY")) -cloud_app = cloud.Application.new( - client=client, - name="test-highs-app", - id="test-highs-app", - exist_ok=True, -) +cloud_app = cloud.Application.new(client=client, id="test-highs-app") -# Push the app. -manifest = nextmv.Manifest.from_yaml(dirpath=".") -cloud_app.push(manifest=manifest, app_dir=".", verbose=True) +# Sync the local app's runs to the cloud app. +local_app.sync(target=cloud_app, verbose=True) diff --git a/connect-your-model-python/highs/nextmv-ified/app5.py b/connect-your-model-python/highs/nextmv-ified/app5.py index 6407b66..9ade247 100644 --- a/connect-your-model-python/highs/nextmv-ified/app5.py +++ b/connect-your-model-python/highs/nextmv-ified/app5.py @@ -5,16 +5,8 @@ # Instantiate the cloud application. client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY")) -cloud_app = cloud.Application.new( - client=client, - name="test-highs-app", - id="test-highs-app", - exist_ok=True, -) +cloud_app = cloud.Application.new(client=client, id="test-highs-app") -# Run the app. -run_result = cloud_app.new_run_with_result( - input_dir_path="./inputs", # Data is loaded from a dir. - run_options={"duration": "3"}, -) -nextmv.write(run_result) +# Push the app. +manifest = nextmv.Manifest.from_yaml(dirpath=".") +cloud_app.push(manifest=manifest, app_dir=".", verbose=True) diff --git a/connect-your-model-python/highs/nextmv-ified/app6.py b/connect-your-model-python/highs/nextmv-ified/app6.py index 7fa9bac..c1babf2 100644 --- a/connect-your-model-python/highs/nextmv-ified/app6.py +++ b/connect-your-model-python/highs/nextmv-ified/app6.py @@ -1,24 +1,15 @@ import os -from datetime import datetime, timedelta, timezone import nextmv from nextmv import cloud # Instantiate the cloud application. client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY")) -cloud_app = cloud.Application.new( - client=client, - name="test-highs-app", - id="test-highs-app", - exist_ok=True, -) +cloud_app = cloud.Application.new(client=client, id="test-highs-app") -# Create the input set. -input_set = cloud_app.new_input_set( - id="input-set-2", - name="Input Set 2", - instance_id="latest", - start_time=datetime.now(timezone.utc) - timedelta(days=1), - end_time=datetime.now(timezone.utc), +# Run the app. +run_result = cloud_app.new_run_with_result( + input_dir_path="./inputs", # Data is loaded from a dir. + run_options={"duration": "3"}, ) -nextmv.write(input_set) +nextmv.write(run_result) diff --git a/connect-your-model-python/highs/nextmv-ified/app7.py b/connect-your-model-python/highs/nextmv-ified/app7.py index 640ebcc..cd66c9a 100644 --- a/connect-your-model-python/highs/nextmv-ified/app7.py +++ b/connect-your-model-python/highs/nextmv-ified/app7.py @@ -1,34 +1,19 @@ import os +from datetime import datetime, timedelta, timezone +import nextmv from nextmv import cloud # Instantiate the cloud application. client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY")) -cloud_app = cloud.Application.new( - client=client, - name="test-highs-app", - id="test-highs-app", - exist_ok=True, -) +cloud_app = cloud.Application.new(client=client, id="test-highs-app") -# Create the scenario test. -scenario_test_id = cloud_app.new_scenario_test( - id="scenario-test-2", - name="Scenario Test 2", - scenarios=[ - cloud.Scenario( - scenario_input=cloud.ScenarioInput( - scenario_input_type=cloud.ScenarioInputType.INPUT_SET, - scenario_input_data="input-set-2", - ), - instance_id="latest", - configuration=[ - cloud.ScenarioConfiguration( - name="duration", - values=["1", "3", "5"], - ), - ], - ), - ], +# Create the input set. +input_set = cloud_app.new_input_set( + id="input-set-2", + name="Input Set 2", + instance_id="latest", + start_time=datetime.now(timezone.utc) - timedelta(days=1), + end_time=datetime.now(timezone.utc), ) -print(f"Created scenario test with ID: {scenario_test_id}") +nextmv.write(input_set) diff --git a/connect-your-model-python/highs/nextmv-ified/app8.py b/connect-your-model-python/highs/nextmv-ified/app8.py new file mode 100644 index 0000000..7156d88 --- /dev/null +++ b/connect-your-model-python/highs/nextmv-ified/app8.py @@ -0,0 +1,29 @@ +import os + +from nextmv import cloud + +# Instantiate the cloud application. +client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY")) +cloud_app = cloud.Application.new(client=client, id="test-highs-app") + +# Create the scenario test. +scenario_test_id = cloud_app.new_scenario_test( + id="scenario-test-2", + name="Scenario Test 2", + scenarios=[ + cloud.Scenario( + scenario_input=cloud.ScenarioInput( + scenario_input_type=cloud.ScenarioInputType.INPUT_SET, + scenario_input_data="input-set-2", + ), + instance_id="latest", + configuration=[ + cloud.ScenarioConfiguration( + name="duration", + values=["1", "3", "5"], + ), + ], + ), + ], +) +print(f"Created scenario test with ID: {scenario_test_id}") diff --git a/connect-your-model-python/pyomo/nextmv-ified/README.md b/connect-your-model-python/pyomo/nextmv-ified/README.md index 97f6061..8de513b 100644 --- a/connect-your-model-python/pyomo/nextmv-ified/README.md +++ b/connect-your-model-python/pyomo/nextmv-ified/README.md @@ -30,20 +30,49 @@ Pyomo][pyomo-example] turned into a Nextmv Application. python app2.py ``` - * `app3.py`: syncs the local application runs to a Nextmv Cloud - application. Requires a valid Nextmv Cloud API key. + * Export your Nextmv Cloud API key for convenience, as it is required in the upcoming + scripts. ```bash export NEXTMV_API_KEY="" + ``` + + * `app3.py`: creates a new Nextmv Cloud Application. Requires a valid Nextmv + Cloud API key. + + ```bash python app3.py ``` - * `app4.py`: pushes the local executable code to a Nextmv Cloud Application. - Requires a valid Nextmv Cloud API key. + * `app4.py`: syncs the local application runs to a Nextmv Cloud + application. ```bash - export NEXTMV_API_KEY="" python app4.py ``` + * `app5.py`: pushes the local executable code to a Nextmv Cloud Application. + + ```bash + python app5.py + ``` + + * `app6.py`: runs the Nextmv Cloud application, polling for results. + + ```bash + python app6.py + ``` + + * `app7.py`: creates an input set from the last runs. + + ```bash + python app7.py + ``` + + * `app8.py`: creates a scenario test using the input set. + + ```bash + python app8.py + ``` + [pyomo-example]: https://github.com/Pyomo/pyomo-gallery/blob/main/transport/transport.py diff --git a/connect-your-model-python/pyomo/nextmv-ified/app3.py b/connect-your-model-python/pyomo/nextmv-ified/app3.py index a6559d6..d612fb2 100644 --- a/connect-your-model-python/pyomo/nextmv-ified/app3.py +++ b/connect-your-model-python/pyomo/nextmv-ified/app3.py @@ -1,9 +1,6 @@ import os -from nextmv import cloud, local - -# Instantiate the local application. -local_app = local.Application(src=".") +from nextmv import cloud # Instantiate the cloud application. client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY")) @@ -13,6 +10,4 @@ id="test-pyomo-app", exist_ok=True, ) - -# Sync the local app's runs to the cloud app. -local_app.sync(target=cloud_app, verbose=True) +print("Cloud application created:", cloud_app.id) diff --git a/connect-your-model-python/pyomo/nextmv-ified/app4.py b/connect-your-model-python/pyomo/nextmv-ified/app4.py index d15414f..3eeb052 100644 --- a/connect-your-model-python/pyomo/nextmv-ified/app4.py +++ b/connect-your-model-python/pyomo/nextmv-ified/app4.py @@ -1,17 +1,13 @@ import os -import nextmv -from nextmv import cloud +from nextmv import cloud, local + +# Instantiate the local application. +local_app = local.Application(src=".") # Instantiate the cloud application. client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY")) -cloud_app = cloud.Application.new( - client=client, - name="test-pyomo-app", - id="test-pyomo-app", - exist_ok=True, -) +cloud_app = cloud.Application(client=client, id="test-pyomo-app") -# Push the app. -manifest = nextmv.Manifest.from_yaml(dirpath=".") -cloud_app.push(manifest=manifest, app_dir=".", verbose=True) +# Sync the local app's runs to the cloud app. +local_app.sync(target=cloud_app, verbose=True) diff --git a/connect-your-model-python/pyomo/nextmv-ified/app5.py b/connect-your-model-python/pyomo/nextmv-ified/app5.py index 27b6568..c251d29 100644 --- a/connect-your-model-python/pyomo/nextmv-ified/app5.py +++ b/connect-your-model-python/pyomo/nextmv-ified/app5.py @@ -5,20 +5,8 @@ # Instantiate the cloud application. client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY")) -cloud_app = cloud.Application.new( - client=client, - name="test-pyomo-app", - id="test-pyomo-app", - exist_ok=True, -) +cloud_app = cloud.Application(client=client, id="test-pyomo-app") -# Run the app. -input = nextmv.load(path=os.path.join("inputs", "problem.json")) -run_result = cloud_app.new_run_with_result( - input=input.data, # Data is loaded from memory. - run_options={ - "duration": "3", - "solver": "scip", - }, -) -nextmv.write(run_result) +# Push the app. +manifest = nextmv.Manifest.from_yaml(dirpath=".") +cloud_app.push(manifest=manifest, app_dir=".", verbose=True) diff --git a/connect-your-model-python/pyomo/nextmv-ified/app6.py b/connect-your-model-python/pyomo/nextmv-ified/app6.py index 99eba8b..f2bcdd4 100644 --- a/connect-your-model-python/pyomo/nextmv-ified/app6.py +++ b/connect-your-model-python/pyomo/nextmv-ified/app6.py @@ -1,24 +1,19 @@ import os -from datetime import datetime, timedelta, timezone import nextmv from nextmv import cloud # Instantiate the cloud application. client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY")) -cloud_app = cloud.Application.new( - client=client, - name="test-pyomo-app", - id="test-pyomo-app", - exist_ok=True, -) +cloud_app = cloud.Application(client=client, id="test-pyomo-app") -# Create the input set. -input_set = cloud_app.new_input_set( - id="input-set-2", - name="Input Set 2", - instance_id="latest", - start_time=datetime.now(timezone.utc) - timedelta(days=1), - end_time=datetime.now(timezone.utc), +# Run the app. +input = nextmv.load(path=os.path.join("inputs", "problem.json")) +run_result = cloud_app.new_run_with_result( + input=input.data, # Data is loaded from memory. + run_options={ + "duration": "3", + "solver": "scip", + }, ) -nextmv.write(input_set) +nextmv.write(run_result) diff --git a/connect-your-model-python/pyomo/nextmv-ified/app7.py b/connect-your-model-python/pyomo/nextmv-ified/app7.py index 893abc9..f8e0174 100644 --- a/connect-your-model-python/pyomo/nextmv-ified/app7.py +++ b/connect-your-model-python/pyomo/nextmv-ified/app7.py @@ -1,38 +1,19 @@ import os +from datetime import datetime, timedelta, timezone +import nextmv from nextmv import cloud # Instantiate the cloud application. client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY")) -cloud_app = cloud.Application.new( - client=client, - name="test-pyomo-app", - id="test-pyomo-app", - exist_ok=True, -) +cloud_app = cloud.Application(client=client, id="test-pyomo-app") -# Create the scenario test. -scenario_test_id = cloud_app.new_scenario_test( - id="scenario-test-2", - name="Scenario Test 2", - scenarios=[ - cloud.Scenario( - scenario_input=cloud.ScenarioInput( - scenario_input_type=cloud.ScenarioInputType.INPUT_SET, - scenario_input_data="input-set-2", - ), - instance_id="latest", - configuration=[ - cloud.ScenarioConfiguration( - name="duration", - values=["1", "3", "5"], - ), - cloud.ScenarioConfiguration( - name="solver", - values=["glpk", "scip", "cbc"], - ), - ], - ), - ], +# Create the input set. +input_set = cloud_app.new_input_set( + id="input-set-2", + name="Input Set 2", + instance_id="latest", + start_time=datetime.now(timezone.utc) - timedelta(days=1), + end_time=datetime.now(timezone.utc), ) -print(f"Created scenario test with ID: {scenario_test_id}") +nextmv.write(input_set) diff --git a/connect-your-model-python/pyomo/nextmv-ified/app8.py b/connect-your-model-python/pyomo/nextmv-ified/app8.py new file mode 100644 index 0000000..6a4ecb7 --- /dev/null +++ b/connect-your-model-python/pyomo/nextmv-ified/app8.py @@ -0,0 +1,33 @@ +import os + +from nextmv import cloud + +# Instantiate the cloud application. +client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY")) +cloud_app = cloud.Application(client=client, id="test-pyomo-app") + +# Create the scenario test. +scenario_test_id = cloud_app.new_scenario_test( + id="scenario-test-2", + name="Scenario Test 2", + scenarios=[ + cloud.Scenario( + scenario_input=cloud.ScenarioInput( + scenario_input_type=cloud.ScenarioInputType.INPUT_SET, + scenario_input_data="input-set-2", + ), + instance_id="latest", + configuration=[ + cloud.ScenarioConfiguration( + name="duration", + values=["1", "3", "5"], + ), + cloud.ScenarioConfiguration( + name="solver", + values=["glpk", "scip", "cbc"], + ), + ], + ), + ], +) +print(f"Created scenario test with ID: {scenario_test_id}") From a59f1c1c44a211c14a7b299f2a778533cfac9cd4 Mon Sep 17 00:00:00 2001 From: Sebastian Quintero Date: Wed, 3 Dec 2025 13:37:07 -0500 Subject: [PATCH 2/2] Adds the explore new model dir --- .gitignore | 1 + README.md | 2 + .../highs/nextmv-ified/README.md | 26 ++ .../ortools/nextmv-ified/README.md | 26 ++ explore-new-model-cli/README.md | 51 +++ explore-new-model-cli/app1.sh | 1 + explore-new-model-cli/app2.sh | 1 + explore-new-model-cli/app3.sh | 1 + explore-new-model-cli/app4.sh | 2 + explore-new-model-cli/app5.sh | 2 + explore-new-model-cli/app6.sh | 1 + .../python-highs-knapsack/LICENSE | 201 ++++++++++ .../python-highs-knapsack/README.md | 29 ++ .../python-highs-knapsack/app.yaml | 16 + .../python-highs-knapsack/input.json | 60 +++ .../python-highs-knapsack/main.ipynb | 342 ++++++++++++++++++ .../python-highs-knapsack/main.py | 83 +++++ .../python-highs-knapsack/requirements.txt | 2 + 18 files changed, 847 insertions(+) create mode 100644 explore-new-model-cli/README.md create mode 100644 explore-new-model-cli/app1.sh create mode 100644 explore-new-model-cli/app2.sh create mode 100644 explore-new-model-cli/app3.sh create mode 100644 explore-new-model-cli/app4.sh create mode 100644 explore-new-model-cli/app5.sh create mode 100644 explore-new-model-cli/app6.sh create mode 100644 explore-new-model-cli/python-highs-knapsack/LICENSE create mode 100644 explore-new-model-cli/python-highs-knapsack/README.md create mode 100644 explore-new-model-cli/python-highs-knapsack/app.yaml create mode 100644 explore-new-model-cli/python-highs-knapsack/input.json create mode 100644 explore-new-model-cli/python-highs-knapsack/main.ipynb create mode 100644 explore-new-model-cli/python-highs-knapsack/main.py create mode 100644 explore-new-model-cli/python-highs-knapsack/requirements.txt diff --git a/.gitignore b/.gitignore index 6645923..523f09b 100644 --- a/.gitignore +++ b/.gitignore @@ -209,6 +209,7 @@ __marimo__/ # Nextmv stuff .nextmv/ outputs/ +output.json # HiGHS stuff connect-your-model-cli/highs/*/HiGHS/ diff --git a/README.md b/README.md index 2ad65cf..0e5c62a 100644 --- a/README.md +++ b/README.md @@ -6,5 +6,7 @@ Tutorials for getting up and running with the Nextmv Platform. decision model to the Nextmv platform using the Python SDK. 1. [Connect your model - CLI](./connect-your-model-cli/): bring an external decision model to the Nextmv platform using the Nextmv CLI. +1. [Explore new model - CLI](./explore-new-model-cli/): explore a new decision + model using a Nextmv community app and the Nextmv CLI. 1. [Free local experience](./free-local-experience/): run Nextmv Applications locally using the Python SDK. diff --git a/connect-your-model-cli/highs/nextmv-ified/README.md b/connect-your-model-cli/highs/nextmv-ified/README.md index 3169838..38a3b43 100644 --- a/connect-your-model-cli/highs/nextmv-ified/README.md +++ b/connect-your-model-cli/highs/nextmv-ified/README.md @@ -52,4 +52,30 @@ HiGHS][highs-example] turned into a Nextmv Application. cat inputs/problem.json | LD_LIBRARY_PATH=./HiGHS/build/lib ./main ``` +* Run the scripts individually. + + * `app1.sh`: Create a Nextmv Cloud Application. + + ```bash + bash app1.sh + ``` + + * `app2.sh`: Push the local executable code to a Nextmv Cloud Application. + + ```bash + bash app2.sh + ``` + + * `app3.sh`: Run the Nextmv Cloud application. + + ```bash + bash app3.sh + ``` + + * `app4.sh`: Create an input set. + + ```bash + bash app4.sh + ``` + [highs-example]: https://github.com/ERGO-Code/HiGHS/blob/master/examples/call_highs_from_cpp.cpp diff --git a/connect-your-model-cli/ortools/nextmv-ified/README.md b/connect-your-model-cli/ortools/nextmv-ified/README.md index 8ca937e..e5c3286 100644 --- a/connect-your-model-cli/ortools/nextmv-ified/README.md +++ b/connect-your-model-cli/ortools/nextmv-ified/README.md @@ -15,4 +15,30 @@ Deliveries authored by OR-Tools][ortools-example] turned into a Nextmv Applicati java -jar main.jar ``` +* Run the scripts individually. + + * `app1.sh`: Create a Nextmv Cloud Application. + + ```bash + bash app1.sh + ``` + + * `app2.sh`: Push the local executable code to a Nextmv Cloud Application. + + ```bash + bash app2.sh + ``` + + * `app3.sh`: Run the Nextmv Cloud application. + + ```bash + bash app3.sh + ``` + + * `app4.sh`: Create an input set. + + ```bash + bash app4.sh + ``` + [ortools-example]: https://developers.google.com/optimization/routing/pickup_delivery#complete_programs diff --git a/explore-new-model-cli/README.md b/explore-new-model-cli/README.md new file mode 100644 index 0000000..7d9aa74 --- /dev/null +++ b/explore-new-model-cli/README.md @@ -0,0 +1,51 @@ +# Explore new model - CLI + +Code for the explore new model (CLI) tutorial. The following [community +app][community-apps] is used: + +* [Python HiGHS Knapsack][python-highs-knapsack-comm-app]: use the HiGHS solver + to solve a knapsack problem defined in JSON format. + +Go into the [`python-highs-knapsack`](./python-highs-knapsack/) directory for +instructions on how to run the app itself. + +* Run the scripts individually. + + * `app1.sh`: List available community apps. + + ```bash + bash app1.sh + ``` + + * `app2.sh`: Clone the `python-highs-knapsack` community app locally. + + ```bash + bash app2.sh + ``` + + * `app3.sh`: Create a Nextmv Cloud Application. + + ```bash + bash app3.sh + ``` + + * `app4.sh`: Push the local executable code to a Nextmv Cloud Application. + + ```bash + bash app4.sh + ``` + + * `app5.sh`: Run the Nextmv Cloud application. + + ```bash + bash app5.sh + ``` + + * `app6.sh`: Create an input set. + + ```bash + bash app6.sh + ``` + +[community-apps]: https://github.com/nextmv-io/community-apps +[python-highs-knapsack-comm-app]: https://github.com/nextmv-io/community-apps/tree/develop/python-highs-knapsack diff --git a/explore-new-model-cli/app1.sh b/explore-new-model-cli/app1.sh new file mode 100644 index 0000000..088c310 --- /dev/null +++ b/explore-new-model-cli/app1.sh @@ -0,0 +1 @@ +nextmv community list diff --git a/explore-new-model-cli/app2.sh b/explore-new-model-cli/app2.sh new file mode 100644 index 0000000..800b9ec --- /dev/null +++ b/explore-new-model-cli/app2.sh @@ -0,0 +1 @@ +nextmv community clone -a python-highs-knapsack diff --git a/explore-new-model-cli/app3.sh b/explore-new-model-cli/app3.sh new file mode 100644 index 0000000..82a8c76 --- /dev/null +++ b/explore-new-model-cli/app3.sh @@ -0,0 +1 @@ +nextmv app create -n test-community-app -a test-community-app diff --git a/explore-new-model-cli/app4.sh b/explore-new-model-cli/app4.sh new file mode 100644 index 0000000..bfa203f --- /dev/null +++ b/explore-new-model-cli/app4.sh @@ -0,0 +1,2 @@ +# Run this command from the root of the app, this is, where the app.yaml file is located. +nextmv app push -a test-community-app diff --git a/explore-new-model-cli/app5.sh b/explore-new-model-cli/app5.sh new file mode 100644 index 0000000..253cb1c --- /dev/null +++ b/explore-new-model-cli/app5.sh @@ -0,0 +1,2 @@ +# Run this command from the root of the app, this is, where the app.yaml file is located. +nextmv app run -a test-community-app -i input.json diff --git a/explore-new-model-cli/app6.sh b/explore-new-model-cli/app6.sh new file mode 100644 index 0000000..506e656 --- /dev/null +++ b/explore-new-model-cli/app6.sh @@ -0,0 +1 @@ +nextmv experiment input-set create -a test-community-app -i latest -s input-set-2 -n "Input set 2" diff --git a/explore-new-model-cli/python-highs-knapsack/LICENSE b/explore-new-model-cli/python-highs-knapsack/LICENSE new file mode 100644 index 0000000..2c27ec7 --- /dev/null +++ b/explore-new-model-cli/python-highs-knapsack/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022-2024 nextmv.io inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/explore-new-model-cli/python-highs-knapsack/README.md b/explore-new-model-cli/python-highs-knapsack/README.md new file mode 100644 index 0000000..7c897b6 --- /dev/null +++ b/explore-new-model-cli/python-highs-knapsack/README.md @@ -0,0 +1,29 @@ +# Nextmv Python HiGHS Knapsack + +Example for running a Python application on the Nextmv Platform using the HiGHS +solver. We solve a knapsack Mixed Integer Programming problem. + +1. Install packages. + + ```bash + pip3 install -r requirements.txt + ``` + +1. Run the app. + + ```bash + 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. +* Visit our [docs][docs] and [blog][blog]. Need more assistance? + [Contact][contact] us! + +[docs]: https://docs.nextmv.io +[blog]: https://www.nextmv.io/blog +[contact]: https://www.nextmv.io/contact diff --git a/explore-new-model-cli/python-highs-knapsack/app.yaml b/explore-new-model-cli/python-highs-knapsack/app.yaml new file mode 100644 index 0000000..4aab122 --- /dev/null +++ b/explore-new-model-cli/python-highs-knapsack/app.yaml @@ -0,0 +1,16 @@ +# This manifest holds the information the app needs to run on the Nextmv Cloud. +type: python +runtime: ghcr.io/nextmv-io/runtime/python:3.11 +python: + # All listed packages will get bundled with the app. + pip-requirements: requirements.txt + +# List all files/directories that should be included in the app. Globbing +# (e.g.: configs/*.json) is supported. +files: + - main.py + +configuration: + # Specify the format the app reads and writes. + content: + format: "json" # Read JSON from stdin and write JSON to stdout. diff --git a/explore-new-model-cli/python-highs-knapsack/input.json b/explore-new-model-cli/python-highs-knapsack/input.json new file mode 100644 index 0000000..98eee19 --- /dev/null +++ b/explore-new-model-cli/python-highs-knapsack/input.json @@ -0,0 +1,60 @@ +{ + "items": [ + { + "id": "cat", + "value": 100, + "weight": 20 + }, + { + "id": "dog", + "value": 20, + "weight": 45 + }, + { + "id": "water", + "value": 40, + "weight": 2 + }, + { + "id": "phone", + "value": 6, + "weight": 1 + }, + { + "id": "book", + "value": 63, + "weight": 10 + }, + { + "id": "rx", + "value": 81, + "weight": 1 + }, + { + "id": "tablet", + "value": 28, + "weight": 8 + }, + { + "id": "coat", + "value": 44, + "weight": 9 + }, + { + "id": "laptop", + "value": 51, + "weight": 13 + }, + { + "id": "keys", + "value": 92, + "weight": 1 + }, + { + "id": "nuts", + "value": 18, + "weight": 4 + } + ], + "weight_capacity": 50 +} diff --git a/explore-new-model-cli/python-highs-knapsack/main.ipynb b/explore-new-model-cli/python-highs-knapsack/main.ipynb new file mode 100644 index 0000000..0f0a4b4 --- /dev/null +++ b/explore-new-model-cli/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.Option(\"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.36.0\"\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/explore-new-model-cli/python-highs-knapsack/main.py b/explore-new-model-cli/python-highs-knapsack/main.py new file mode 100644 index 0000000..b602d87 --- /dev/null +++ b/explore-new-model-cli/python-highs-knapsack/main.py @@ -0,0 +1,83 @@ +import time +from importlib.metadata import version + +import highspy +import nextmv + + +def main() -> None: + """Entry point for the program.""" + + options = nextmv.Options( + nextmv.Option("input", str, "", "Path to input file. Default is stdin.", False), + nextmv.Option("output", str, "", "Path to output file. Default is stdout.", False), + nextmv.Option("duration", int, 30, "Max runtime duration (in seconds).", False), + ) + + input = nextmv.load(options=options, path=options.input) + + nextmv.log("Solving knapsack problem:") + nextmv.log(f" - items: {len(input.data.get('items', []))}") + nextmv.log(f" - capacity: {input.data.get('weight_capacity', 0)}") + + model = DecisionModel() + output = model.solve(input) + nextmv.write(output, path=options.output) + + +class DecisionModel(nextmv.Model): + def solve(self, input: nextmv.Input) -> nextmv.Output: + """Solves the given problem and returns the solution.""" + + start_time = time.time() + + # Creates the solver. + solver = highspy.Highs() + solver.silent() # Solver output ignores stdout redirect, silence it. + solver.setOptionValue("time_limit", input.options.duration) + + # Initializes the linear sums. + weights = 0.0 + values = 0.0 + + # Creates the decision variables and adds them to the linear sums. + items = [] + for item in input.data["items"]: + item_variable = solver.addVariable(0.0, 1.0, item["value"]) + items.append({"item": item, "variable": item_variable}) + weights += item_variable * item["weight"] + values += item_variable * item["value"] + + # This constraint ensures the weight capacity of the knapsack will not be + # exceeded. + solver.addConstr(weights <= input.data["weight_capacity"]) + + # Sets the objective function: maximize the value of the chosen items. + status = solver.maximize(values) + + # Determines which items were chosen. + chosen_items = [item["item"] for item in items if solver.val(item["variable"]) > 0.9] + + input.options.version = version("highspy") + + statistics = nextmv.Statistics( + run=nextmv.RunStatistics(duration=time.time() - start_time), + result=nextmv.ResultStatistics( + value=sum(item["value"] for item in chosen_items), + custom={ + "status": str(status), + "variables": solver.numVariables, + "constraints": solver.numConstrs, + }, + ), + ) + + return nextmv.Output( + options=input.options, + solution={"items": chosen_items}, + statistics=statistics, + ) + + +if __name__ == "__main__": + main() diff --git a/explore-new-model-cli/python-highs-knapsack/requirements.txt b/explore-new-model-cli/python-highs-knapsack/requirements.txt new file mode 100644 index 0000000..c0a10e8 --- /dev/null +++ b/explore-new-model-cli/python-highs-knapsack/requirements.txt @@ -0,0 +1,2 @@ +highspy==1.9.0 +nextmv==0.36.0