Skip to content
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
2 changes: 1 addition & 1 deletion .nextmv/readme/python-hexaly-generic/1.sh
Original file line number Diff line number Diff line change
@@ -1 +1 @@
python3 main.py -duration 30
python3 main.py inFileName=inputs/input.dat solFileName=outputs/solutions/output.txt
5 changes: 4 additions & 1 deletion .nextmv/readme/python-hexaly-generic/2.sh
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
nextmv push --app-id <your-app-id>
python3 main.py \
inFileName=input.dat \
solFileName=outputs/solutions/output.txt \
unNest=true
4 changes: 1 addition & 3 deletions .nextmv/readme/python-hexaly-generic/3.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
docker run -i --rm \
-v $(pwd):/app ghcr.io/nextmv-io/runtime/hexaly:latest \
sh -c 'python3 /app/main.py'
nextmv push --app-id <your-app-id>
5 changes: 5 additions & 0 deletions .nextmv/readme/python-hexaly-generic/4.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
nextmv app run --app-id <your-app-id> \
--input inputs/ \
--content-type multi-file \
--secret-collection-id <your-secret-collection> \
--options 'inFileName=inputs/input.dat,solFileName=outputs/solutions/output.txt'
3 changes: 3 additions & 0 deletions .nextmv/readme/python-hexaly-generic/5.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
docker run -i --rm \
-v $(pwd):/app ghcr.io/nextmv-io/runtime/hexaly:latest \
sh -c 'python3 /app/main.py'
4 changes: 4 additions & 0 deletions .nextmv/readme/workflow-configuration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ apps:
skip: true
- name: 3.sh
skip: true
- name: 4.sh
skip: true
- name: 5.sh
skip: true
- name: python-highs-knapsack
scripts:
- name: 0.sh
Expand Down
2 changes: 2 additions & 0 deletions python-hexaly-generic/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
model.hxm
input.dat
43 changes: 35 additions & 8 deletions python-hexaly-generic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,53 @@ multi knapsack Mixed Integer Programming problem.
pip3 install -r requirements.txt
```

1. Put your model file and input data in the `inputs/` directory. The model file
should have the extension `.hxm` and the input data file should have the
extension `.dat`. See the example files in the `inputs/` directory for
reference.
1. Put your model file and any other necessary files in the `inputs/` directory.
The _model file_ should have the extension `.hxm`. All other files need to be
either referenced by your model code or specified as input arguments via
options (e.g., `-data=<file>`). See the example files in the `inputs/`
directory for reference.
- The model automatically loads the first `.hxm` file (alternatively, the
first `.lsp` file) and the first `.dat` file it finds in the input
directory. Use the `-model` and `-data` flags to specify specific files.
1. Run the app.
first `.lsp` file) it finds in the input directory.
1. Run the app locally.

```bash
python3 main.py -duration 30
python3 main.py inFileName=inputs/input.dat solFileName=outputs/solutions/output.txt
```

- If your app expects inputs files to be in the same directory as the model
file, you can use the `unNest=true` option. The app will then copy all
files from the `inputs/` directory to the current working directory before
running the model. Even though it is not necessary for this example, you
can test this by running (note the path to the data file):

```bash
python3 main.py \
inFileName=input.dat \
solFileName=outputs/solutions/output.txt \
unNest=true
```

1. If above steps were successful, you can push the app to the Nextmv Platform.
E.g., using the [Nextmv CLI][install-cli]:

```bash
nextmv push --app-id <your-app-id>
```

1. You can then run the app on the Nextmv Platform by using the CLI (note that
you need to have the license file defined as a [secret][secret] in your
Nextmv Application):

```bash
nextmv app run --app-id <your-app-id> \
--input inputs/ \
--content-type multi-file \
--secret-collection-id <your-secret-collection> \
--options 'inFileName=inputs/input.dat,solFileName=outputs/solutions/output.txt'
```

Or you can run it via the [Nextmv Console][console].

## Mirror running on Nextmv Cloud locally

Docker needs to be installed.
Expand Down
104 changes: 71 additions & 33 deletions python-hexaly-generic/main.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,90 @@
import os
import shutil
import sys

import nextmv
from hexaly.modeler import HexalyModeler

# Name of the option that makes the app copy all files from the `inputs/` directory to the
# current working directory before running the model.
OPTION_UN_NEST = "unNest"


def main() -> None:
options = nextmv.Options(
nextmv.Option("input", str, "inputs/", "input path", False),
nextmv.Option("model", str, "", "model file path", False),
nextmv.Option("data", str, "", "data file path", False),
nextmv.Option("output", str, "outputs/solutions/", "output path", False),
nextmv.Option("duration", int, 30, "max runtime in seconds", False),
)

os.makedirs(options.output, exist_ok=True)

# Determine model and data files.
if options.model:
model_path = os.path.join(options.input, options.model)
else:
model_path = find_file(options.input, [".hxm", ".lsp"])
if options.data:
data_path = os.path.join(options.input, options.data)
else:
data_path = find_file(options.input, [".dat"])
nextmv.log(f"Using model file: {model_path}")
nextmv.log(f"Using data file: {data_path}")
"""Entry point for the program."""

# Parse options from command line arguments.
options, un_nest = parse_options()
nextmv.log("Options:")
for key, value in options.items():
nextmv.log(f" - {key}: {value}")

# Make sure the output directory exists.
os.makedirs(os.path.join("outputs", "solutions"), exist_ok=True)

# If the `unNest=true` option is set, copy all files from the `inputs/` directory to
# the current working directory.
if un_nest:
nextmv.log("Using unNest option, copying files from inputs/ to current directory.")
unnest_directory("inputs")

# Find the model file in the specified path.
model_path = find_file("inputs", [".hxm", ".lsp"])
nextmv.log(f"Model file found: {model_path}")

# Prepare options for consumption by the model.
options_list = [f"{key}={value}" for key, value in options.items()]

# Load and solve the model.
nextmv.log("Loading and solving the model...")
with HexalyModeler() as modeler:
optimizer = modeler.create_optimizer()
module = modeler.load_module("model", model_path)
module.run(
optimizer,
f"inFileName={data_path}",
f"solFileName={options.output}/output.txt",
f"hxTimeLimit={options.duration}",
*options_list,
)

with open(f"{options.output}/output.txt") as f:
nextmv.write(
nextmv.Output(
solution=f.read(),
options=options.to_dict(),
output_format=nextmv.OutputFormat.MULTI_FILE,
),
path=options.output,
)
nextmv.log("Done.")


def parse_options() -> tuple[dict[str, str], bool]:
"""
Parses all arguments so that they can be submitted to the model. Returns a dictionary
of options and a boolean indicating whether the inputs directory should be un-nested.
"""
un_nest = False
options = {}
for arg in sys.argv[1:]:
if arg.startswith("--"):
arg = arg[2:]
elif arg.startswith("-"):
arg = arg[1:]
if arg == OPTION_UN_NEST:
un_nest = True
continue
if "=" in arg:
key, value = arg.split("=", 1)
if key == OPTION_UN_NEST:
un_nest = True
continue
options[key] = value
else:
options[arg] = "true"
return options, un_nest


def unnest_directory(source_directory: str) -> None:
"""
Copies all files from the source directory to the current working directory.
"""
# Iterate over all the items in the source directory
for root, _, files in os.walk(source_directory):
for file in files:
# Construct the full file path
source_file_path = os.path.join(root, file)
# Copy the file to the current directory
shutil.copy2(source_file_path, ".")


def find_file(path: str, extensions: list[str]) -> str:
Expand Down
Loading