From 8fe139a95a80fa70eba45736db25f0e3573a11cd Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Fri, 11 Jul 2025 00:48:38 +0200 Subject: [PATCH 1/8] Relaying all parameters into the inner model, only looking for model automatically, updated readme --- python-hexaly-generic/README.md | 30 ++++++++---- python-hexaly-generic/main.py | 82 ++++++++++++++++++++------------- 2 files changed, 71 insertions(+), 41 deletions(-) diff --git a/python-hexaly-generic/README.md b/python-hexaly-generic/README.md index 18b026a..17675f2 100644 --- a/python-hexaly-generic/README.md +++ b/python-hexaly-generic/README.md @@ -20,17 +20,17 @@ 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=`). 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 ``` 1. If above steps were successful, you can push the app to the Nextmv Platform. @@ -40,6 +40,20 @@ multi knapsack Mixed Integer Programming problem. nextmv push --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 \ + --input inputs/ \ + --content-type multi-file \ + --secret-collection-id \ + --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. diff --git a/python-hexaly-generic/main.py b/python-hexaly-generic/main.py index e95c755..0228baf 100644 --- a/python-hexaly-generic/main.py +++ b/python-hexaly-generic/main.py @@ -1,52 +1,68 @@ import os +import sys import nextmv from hexaly.modeler import HexalyModeler 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 = 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) + + # 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, - ) + # 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() -> dict[str, str]: + """ + Parses all arguments so that they can be submitted to the model. + """ + options = {} + for arg in sys.argv[1:]: + if arg.startswith("--"): + arg = arg[2:] + elif arg.startswith("-"): + arg = arg[1:] + if "=" in arg: + key, value = arg.split("=", 1) + options[key] = value + else: + options[arg] = True + return options def find_file(path: str, extensions: list[str]) -> str: From 067427f9e73138e6136d18c24046831d96bd32a6 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Fri, 11 Jul 2025 00:51:58 +0200 Subject: [PATCH 2/8] Removing commented code --- python-hexaly-generic/main.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/python-hexaly-generic/main.py b/python-hexaly-generic/main.py index 0228baf..3549598 100644 --- a/python-hexaly-generic/main.py +++ b/python-hexaly-generic/main.py @@ -34,16 +34,6 @@ def main() -> None: *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.") From 8cda091d012c8f5b9be1428278b635152b421106 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Fri, 11 Jul 2025 00:53:26 +0200 Subject: [PATCH 3/8] Updating readme commands --- .nextmv/readme/python-hexaly-generic/1.sh | 2 +- .nextmv/readme/python-hexaly-generic/3.sh | 8 +++++--- .nextmv/readme/python-hexaly-generic/4.sh | 3 +++ .nextmv/readme/workflow-configuration.yml | 2 ++ 4 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 .nextmv/readme/python-hexaly-generic/4.sh diff --git a/.nextmv/readme/python-hexaly-generic/1.sh b/.nextmv/readme/python-hexaly-generic/1.sh index b3c4c49..6c2e312 100644 --- a/.nextmv/readme/python-hexaly-generic/1.sh +++ b/.nextmv/readme/python-hexaly-generic/1.sh @@ -1 +1 @@ -python3 main.py -duration 30 +python3 main.py inFileName=inputs/input.dat solFileName=outputs/solutions/output.txt diff --git a/.nextmv/readme/python-hexaly-generic/3.sh b/.nextmv/readme/python-hexaly-generic/3.sh index f062c46..c102959 100644 --- a/.nextmv/readme/python-hexaly-generic/3.sh +++ b/.nextmv/readme/python-hexaly-generic/3.sh @@ -1,3 +1,5 @@ -docker run -i --rm \ --v $(pwd):/app ghcr.io/nextmv-io/runtime/hexaly:latest \ -sh -c 'python3 /app/main.py' +nextmv app run --app-id \ + --input inputs/ \ + --content-type multi-file \ + --secret-collection-id \ + --options 'inFileName=inputs/input.dat,solFileName=outputs/solutions/output.txt' diff --git a/.nextmv/readme/python-hexaly-generic/4.sh b/.nextmv/readme/python-hexaly-generic/4.sh new file mode 100644 index 0000000..f062c46 --- /dev/null +++ b/.nextmv/readme/python-hexaly-generic/4.sh @@ -0,0 +1,3 @@ +docker run -i --rm \ +-v $(pwd):/app ghcr.io/nextmv-io/runtime/hexaly:latest \ +sh -c 'python3 /app/main.py' diff --git a/.nextmv/readme/workflow-configuration.yml b/.nextmv/readme/workflow-configuration.yml index d739455..d67b68e 100644 --- a/.nextmv/readme/workflow-configuration.yml +++ b/.nextmv/readme/workflow-configuration.yml @@ -132,6 +132,8 @@ apps: skip: true - name: 3.sh skip: true + - name: 4.sh + skip: true - name: python-highs-knapsack scripts: - name: 0.sh From 1f67d70f03498dce482d0e056d9abaa865c13c5b Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Fri, 11 Jul 2025 01:49:17 +0200 Subject: [PATCH 4/8] Allow unnesting --- python-hexaly-generic/.gitignore | 2 ++ python-hexaly-generic/README.md | 10 ++++++++ python-hexaly-generic/main.py | 40 ++++++++++++++++++++++++++++---- 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 python-hexaly-generic/.gitignore diff --git a/python-hexaly-generic/.gitignore b/python-hexaly-generic/.gitignore new file mode 100644 index 0000000..4b1638f --- /dev/null +++ b/python-hexaly-generic/.gitignore @@ -0,0 +1,2 @@ +model.hxm +input.dat diff --git a/python-hexaly-generic/README.md b/python-hexaly-generic/README.md index 17675f2..ce0aec0 100644 --- a/python-hexaly-generic/README.md +++ b/python-hexaly-generic/README.md @@ -33,6 +33,16 @@ multi knapsack Mixed Integer Programming problem. 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]: diff --git a/python-hexaly-generic/main.py b/python-hexaly-generic/main.py index 3549598..b7b6caf 100644 --- a/python-hexaly-generic/main.py +++ b/python-hexaly-generic/main.py @@ -1,15 +1,20 @@ 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: """Entry point for the program.""" # Parse options from command line arguments. - options = parse_options() + options, un_nest = parse_options() nextmv.log("Options:") for key, value in options.items(): nextmv.log(f" - {key}: {value}") @@ -17,6 +22,12 @@ def main() -> None: # 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}") @@ -37,22 +48,43 @@ def main() -> None: nextmv.log("Done.") -def parse_options() -> dict[str, str]: +def parse_options() -> tuple[dict[str, str], bool]: """ - Parses all arguments so that they can be submitted to the model. + 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 + 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: From 5b3739cfaab06f40392437ee9c9bc288b1524758 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Fri, 11 Jul 2025 01:58:20 +0200 Subject: [PATCH 5/8] Updating generic hexaly readme commands --- .nextmv/readme/python-hexaly-generic/2.sh | 2 +- .nextmv/readme/python-hexaly-generic/3.sh | 6 +----- .nextmv/readme/python-hexaly-generic/4.sh | 8 +++++--- .nextmv/readme/python-hexaly-generic/5.sh | 3 +++ .nextmv/readme/workflow-configuration.yml | 2 ++ 5 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 .nextmv/readme/python-hexaly-generic/5.sh diff --git a/.nextmv/readme/python-hexaly-generic/2.sh b/.nextmv/readme/python-hexaly-generic/2.sh index 13da292..aef8acc 100644 --- a/.nextmv/readme/python-hexaly-generic/2.sh +++ b/.nextmv/readme/python-hexaly-generic/2.sh @@ -1 +1 @@ -nextmv push --app-id +python3 main.py inFileName=input.dat solFileName=outputs/solutions/output.txt unNest=true diff --git a/.nextmv/readme/python-hexaly-generic/3.sh b/.nextmv/readme/python-hexaly-generic/3.sh index c102959..13da292 100644 --- a/.nextmv/readme/python-hexaly-generic/3.sh +++ b/.nextmv/readme/python-hexaly-generic/3.sh @@ -1,5 +1 @@ -nextmv app run --app-id \ - --input inputs/ \ - --content-type multi-file \ - --secret-collection-id \ - --options 'inFileName=inputs/input.dat,solFileName=outputs/solutions/output.txt' +nextmv push --app-id diff --git a/.nextmv/readme/python-hexaly-generic/4.sh b/.nextmv/readme/python-hexaly-generic/4.sh index f062c46..c102959 100644 --- a/.nextmv/readme/python-hexaly-generic/4.sh +++ b/.nextmv/readme/python-hexaly-generic/4.sh @@ -1,3 +1,5 @@ -docker run -i --rm \ --v $(pwd):/app ghcr.io/nextmv-io/runtime/hexaly:latest \ -sh -c 'python3 /app/main.py' +nextmv app run --app-id \ + --input inputs/ \ + --content-type multi-file \ + --secret-collection-id \ + --options 'inFileName=inputs/input.dat,solFileName=outputs/solutions/output.txt' diff --git a/.nextmv/readme/python-hexaly-generic/5.sh b/.nextmv/readme/python-hexaly-generic/5.sh new file mode 100644 index 0000000..f062c46 --- /dev/null +++ b/.nextmv/readme/python-hexaly-generic/5.sh @@ -0,0 +1,3 @@ +docker run -i --rm \ +-v $(pwd):/app ghcr.io/nextmv-io/runtime/hexaly:latest \ +sh -c 'python3 /app/main.py' diff --git a/.nextmv/readme/workflow-configuration.yml b/.nextmv/readme/workflow-configuration.yml index d67b68e..441fddc 100644 --- a/.nextmv/readme/workflow-configuration.yml +++ b/.nextmv/readme/workflow-configuration.yml @@ -134,6 +134,8 @@ apps: skip: true - name: 4.sh skip: true + - name: 5.sh + skip: true - name: python-highs-knapsack scripts: - name: 0.sh From a5d062cdd26202acc67de794c06f40fb5fb2e2d3 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Fri, 11 Jul 2025 02:26:45 +0200 Subject: [PATCH 6/8] Addressing linter complaint --- python-hexaly-generic/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python-hexaly-generic/README.md b/python-hexaly-generic/README.md index ce0aec0..2e2398e 100644 --- a/python-hexaly-generic/README.md +++ b/python-hexaly-generic/README.md @@ -40,7 +40,10 @@ multi knapsack Mixed Integer Programming problem. 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 + 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. From 406da72b66d7db79883d2db74f130df135b7d551 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Fri, 11 Jul 2025 02:28:28 +0200 Subject: [PATCH 7/8] Updating command --- .nextmv/readme/python-hexaly-generic/2.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.nextmv/readme/python-hexaly-generic/2.sh b/.nextmv/readme/python-hexaly-generic/2.sh index aef8acc..9a59ab3 100644 --- a/.nextmv/readme/python-hexaly-generic/2.sh +++ b/.nextmv/readme/python-hexaly-generic/2.sh @@ -1 +1,4 @@ -python3 main.py inFileName=input.dat solFileName=outputs/solutions/output.txt unNest=true +python3 main.py \ + inFileName=input.dat \ + solFileName=outputs/solutions/output.txt \ + unNest=true From 1f5491351f62566cae6a8cad986925bbc3612118 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Fri, 11 Jul 2025 02:44:57 +0200 Subject: [PATCH 8/8] Addressing type issue --- python-hexaly-generic/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-hexaly-generic/main.py b/python-hexaly-generic/main.py index b7b6caf..a4e2c46 100644 --- a/python-hexaly-generic/main.py +++ b/python-hexaly-generic/main.py @@ -70,7 +70,7 @@ def parse_options() -> tuple[dict[str, str], bool]: continue options[key] = value else: - options[arg] = True + options[arg] = "true" return options, un_nest