From 174c8d0d337aaceb21b755dd2a39df3ea649a773 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Sun, 6 Jul 2025 00:30:28 +0200 Subject: [PATCH 01/15] Adding python multi-file with excel example --- .../LICENSE | 201 ++++++++++++++++++ .../README.md | 27 +++ .../app.yaml | 11 + .../inputs/input.xlsx | Bin 0 -> 6361 bytes .../main.py | 159 ++++++++++++++ .../outputs/.keep | 2 + .../requirements.txt | 2 + 7 files changed, 402 insertions(+) create mode 100644 python-ortools-multiknapsack-multiexcel/LICENSE create mode 100644 python-ortools-multiknapsack-multiexcel/README.md create mode 100644 python-ortools-multiknapsack-multiexcel/app.yaml create mode 100644 python-ortools-multiknapsack-multiexcel/inputs/input.xlsx create mode 100644 python-ortools-multiknapsack-multiexcel/main.py create mode 100644 python-ortools-multiknapsack-multiexcel/outputs/.keep create mode 100644 python-ortools-multiknapsack-multiexcel/requirements.txt diff --git a/python-ortools-multiknapsack-multiexcel/LICENSE b/python-ortools-multiknapsack-multiexcel/LICENSE new file mode 100644 index 00000000..2c27ec72 --- /dev/null +++ b/python-ortools-multiknapsack-multiexcel/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/python-ortools-multiknapsack-multiexcel/README.md b/python-ortools-multiknapsack-multiexcel/README.md new file mode 100644 index 00000000..da6bdf8b --- /dev/null +++ b/python-ortools-multiknapsack-multiexcel/README.md @@ -0,0 +1,27 @@ +# Nextmv Python OR-Tools Multi-Knapsack Multi-Excel + +Example for running a Python application on the Nextmv Platform using the +OR-Tools package with the multi-file input/output format and Excel I/O files. +We solve a multi-knapsack Mixed Integer Programming problem. + +1. Install packages. + + ```bash + pip3 install -r requirements.txt + ``` + +1. Run the app. + + ```bash + python3 main.py -duration 30 -provider SCIP + ``` + +## 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/python-ortools-multiknapsack-multiexcel/app.yaml b/python-ortools-multiknapsack-multiexcel/app.yaml new file mode 100644 index 00000000..f0de77f9 --- /dev/null +++ b/python-ortools-multiknapsack-multiexcel/app.yaml @@ -0,0 +1,11 @@ +# 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 +# List all files/directories that should be included in the app. Globbing +# (e.g.: configs/*.json) is supported. +files: + - main.py +python: + # Packages the app depends on need to be listed in a requirements.txt file + # that is referenced here. All listed packages will get bundled with the app. + pip-requirements: requirements.txt diff --git a/python-ortools-multiknapsack-multiexcel/inputs/input.xlsx b/python-ortools-multiknapsack-multiexcel/inputs/input.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d61b96a26ff847d2b5d99b431dc0a7e926752e95 GIT binary patch literal 6361 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 b*yG=@n6f+)ELY&*uwXA|7~?AtT|WI6UQa>t literal 0 HcmV?d00001 diff --git a/python-ortools-multiknapsack-multiexcel/main.py b/python-ortools-multiknapsack-multiexcel/main.py new file mode 100644 index 00000000..429f4cbc --- /dev/null +++ b/python-ortools-multiknapsack-multiexcel/main.py @@ -0,0 +1,159 @@ +import time + +import nextmv +import pandas as pd +from ortools.linear_solver import pywraplp + +# Status of the solver after optimizing. +STATUS = { + pywraplp.Solver.FEASIBLE: "suboptimal", + pywraplp.Solver.INFEASIBLE: "infeasible", + pywraplp.Solver.OPTIMAL: "optimal", + pywraplp.Solver.UNBOUNDED: "unbounded", + pywraplp.Solver.ABNORMAL: "abnormal", + pywraplp.Solver.NOT_SOLVED: "not_solved", + pywraplp.Solver.MODEL_INVALID: "model_invalid", +} + + +def main() -> None: + """Entry point for the program.""" + + options = nextmv.Options( + nextmv.Option("input", str, "inputs/", "Path to input file.", False), + nextmv.Option("output", str, "outputs/", "Path to output file.", False), + nextmv.Option("duration", int, 30, "Max runtime duration (in seconds).", False), + nextmv.Option("provider", str, "SCIP", "Solver provider.", False), + ) + + input = nextmv.load( + options=options, + path=options.input, + data_files=[ + nextmv.DataFile( + name="input.xlsx", + loader=lambda path: pd.read_excel(path, sheet_name="items"), + input_data_key="items", + ), + nextmv.DataFile( + name="input.xlsx", + loader=lambda path: pd.read_excel(path, sheet_name="knapsacks"), + input_data_key="knapsacks", + ), + ], + input_format=nextmv.InputFormat.MULTI_FILE, + ) + + nextmv.log("Solving multi-knapsack problem:") + nextmv.log(f" - items: {len(input.data.get('items', []))}") + nextmv.log(f" - knapsacks: {len(input.data.get('knapsacks', []))}") + + 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() + nextmv.redirect_stdout() # Solver chatter is logged to stderr. + + # Creates the solver. + solver = pywraplp.Solver.CreateSolver(input.options.provider) + solver.SetTimeLimit(input.options.duration * 1000) + + # Unpack the input data. + if "items" not in input.data or "knapsacks" not in input.data: + raise ValueError("Input data must contain items and knapsacks.") + items: pd.DataFrame = input.data["items"] + knapsacks: pd.DataFrame = input.data["knapsacks"] + + # Initialize variables. + assignments = {} + for _, knapsack in knapsacks.iterrows(): + for _, item in items.iterrows(): + # Create a binary variable for each item in each knapsack. + assignments[(knapsack["id"], item["id"])] = solver.IntVar(0, 1, f"{knapsack['id']}_{item['id']}") + + # Make sure the knapsacks' capacities are not exceeded. + for _, knapsack in knapsacks.iterrows(): + solver.Add( + solver.Sum(assignments[(knapsack["id"], item["id"])] * item["weight"] for _, item in items.iterrows()) + <= knapsack["capacity"] + ) + + # Maximize the total value of the items in the knapsacks. + solver.Maximize( + solver.Sum( + assignments[(knapsack["id"], item["id"])] * item["value"] + for _, knapsack in knapsacks.iterrows() + for _, item in items.iterrows() + ) + ) + + # Solves the problem. + status = solver.Solve() + + # Determines which items were chosen. + chosen_items = [ + (knapsack["id"], item["id"]) + for _, knapsack in knapsacks.iterrows() + for _, item in items.iterrows() + if assignments[(knapsack["id"], item["id"])].solution_value() > 0.5 + ] + + statistics = nextmv.Statistics( + run=nextmv.RunStatistics(duration=time.time() - start_time), + result=nextmv.ResultStatistics( + duration=solver.WallTime() / 1000, + value=solver.Objective().Value(), + custom={ + "status": STATUS.get(status, "unknown"), + "variables": solver.NumVariables(), + "constraints": solver.NumConstraints(), + }, + ), + ) + + df = pd.DataFrame( + { + "knapsack_id": [knapsack_id for knapsack_id, _ in chosen_items], + "item_id": [item_id for _, item_id in chosen_items], + } + ) + df.to_excel( + f"{input.options.output}/assignments.xlsx", + index=False, + sheet_name="assignments", + ) + + return nextmv.Output( + options=input.options, + solution_files=[ + nextmv.SolutionFile( + name="assignments.xlsx", + data=pd.DataFrame( + { + "knapsack_id": [knapsack_id for knapsack_id, _ in chosen_items], + "item_id": [item_id for _, item_id in chosen_items], + } + ), + writer=lambda path, data: data.to_excel(path, index=False, sheet_name="assignments"), + ), + nextmv.csv_solution_file( + "assignments", + data=[{"knapsack_id": knapsack_id, "item_id": item_id} for knapsack_id, item_id in chosen_items], + ), + ], + statistics=statistics, + output_format=nextmv.OutputFormat.MULTI_FILE, + ) + + +if __name__ == "__main__": + main() diff --git a/python-ortools-multiknapsack-multiexcel/outputs/.keep b/python-ortools-multiknapsack-multiexcel/outputs/.keep new file mode 100644 index 00000000..d55a9686 --- /dev/null +++ b/python-ortools-multiknapsack-multiexcel/outputs/.keep @@ -0,0 +1,2 @@ +This file is just here to make sure that an 'outputs' directory exists as the app expects it. +This directory is automatically created by the Nextmv Platform when running the app remotely. diff --git a/python-ortools-multiknapsack-multiexcel/requirements.txt b/python-ortools-multiknapsack-multiexcel/requirements.txt new file mode 100644 index 00000000..821bfeac --- /dev/null +++ b/python-ortools-multiknapsack-multiexcel/requirements.txt @@ -0,0 +1,2 @@ +ortools==9.12.4544 +nextmv==0.26.3 From 9357943c2923dd8b535070227dad2017af27a8ce Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Mon, 7 Jul 2025 04:12:55 +0200 Subject: [PATCH 02/15] Ignoring multi-file output dirs --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 556a1170..a2e5f09f 100644 --- a/.gitignore +++ b/.gitignore @@ -176,6 +176,7 @@ main.jar !Output.Java output.json output/ +outputs/ # Ignore app binary files go-highs-orderfulfillment/go-highs-orderfulfillment From d1977692534c5701f0021ee36f6c9cdcc03c644d Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Mon, 7 Jul 2025 04:33:23 +0200 Subject: [PATCH 03/15] Adding readme test --- .../0.sh | 1 + .../0.sh.golden | 44 +++++++++++++++++++ .../1.sh | 1 + .../1.sh.golden | 3 ++ .nextmv/readme/workflow-configuration.yml | 4 ++ .../requirements.txt | 4 +- 6 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 .nextmv/readme/python-ortools-multiknapsack-multiexcel/0.sh create mode 100644 .nextmv/readme/python-ortools-multiknapsack-multiexcel/0.sh.golden create mode 100644 .nextmv/readme/python-ortools-multiknapsack-multiexcel/1.sh create mode 100644 .nextmv/readme/python-ortools-multiknapsack-multiexcel/1.sh.golden diff --git a/.nextmv/readme/python-ortools-multiknapsack-multiexcel/0.sh b/.nextmv/readme/python-ortools-multiknapsack-multiexcel/0.sh new file mode 100644 index 00000000..bc88b5d1 --- /dev/null +++ b/.nextmv/readme/python-ortools-multiknapsack-multiexcel/0.sh @@ -0,0 +1 @@ +pip3 install -r requirements.txt diff --git a/.nextmv/readme/python-ortools-multiknapsack-multiexcel/0.sh.golden b/.nextmv/readme/python-ortools-multiknapsack-multiexcel/0.sh.golden new file mode 100644 index 00000000..2e75324e --- /dev/null +++ b/.nextmv/readme/python-ortools-multiknapsack-multiexcel/0.sh.golden @@ -0,0 +1,44 @@ +Collecting nextmv==0.28.5 (from -r requirements.txt (line 1)) + Using cached nextmv-0.28.5-py3-none-any.whl.metadata (15 kB) +Collecting ortools==9.14.6206 (from -r requirements.txt (line 2)) + Downloading ortools-9.14.6206-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (3.3 kB) +Requirement already satisfied: pydantic>=2.5.2 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from nextmv==0.28.5->-r requirements.txt (line 1)) (2.11.3) +Requirement already satisfied: pyyaml>=6.0.1 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from nextmv==0.28.5->-r requirements.txt (line 1)) (6.0.2) +Requirement already satisfied: requests>=2.31.0 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from nextmv==0.28.5->-r requirements.txt (line 1)) (2.32.3) +Requirement already satisfied: urllib3>=2.1.0 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from nextmv==0.28.5->-r requirements.txt (line 1)) (2.4.0) +Requirement already satisfied: absl-py>=2.0.0 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from ortools==9.14.6206->-r requirements.txt (line 2)) (2.2.2) +Requirement already satisfied: numpy>=1.13.3 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from ortools==9.14.6206->-r requirements.txt (line 2)) (2.2.5) +Requirement already satisfied: pandas>=2.0.0 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from ortools==9.14.6206->-r requirements.txt (line 2)) (2.2.3) +Collecting protobuf<6.32,>=6.31.1 (from ortools==9.14.6206->-r requirements.txt (line 2)) + Downloading protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl.metadata (593 bytes) +Requirement already satisfied: typing-extensions>=4.12 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from ortools==9.14.6206->-r requirements.txt (line 2)) (4.13.2) +Requirement already satisfied: immutabledict>=3.0.0 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from ortools==9.14.6206->-r requirements.txt (line 2)) (4.2.1) +Requirement already satisfied: python-dateutil>=2.8.2 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from pandas>=2.0.0->ortools==9.14.6206->-r requirements.txt (line 2)) (2.9.0.post0) +Requirement already satisfied: pytz>=2020.1 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from pandas>=2.0.0->ortools==9.14.6206->-r requirements.txt (line 2)) (2025.2) +Requirement already satisfied: tzdata>=2022.7 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from pandas>=2.0.0->ortools==9.14.6206->-r requirements.txt (line 2)) (2025.2) +Requirement already satisfied: annotated-types>=0.6.0 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from pydantic>=2.5.2->nextmv==0.28.5->-r requirements.txt (line 1)) (0.7.0) +Requirement already satisfied: pydantic-core==2.33.1 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from pydantic>=2.5.2->nextmv==0.28.5->-r requirements.txt (line 1)) (2.33.1) +Requirement already satisfied: typing-inspection>=0.4.0 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from pydantic>=2.5.2->nextmv==0.28.5->-r requirements.txt (line 1)) (0.4.0) +Requirement already satisfied: six>=1.5 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas>=2.0.0->ortools==9.14.6206->-r requirements.txt (line 2)) (1.17.0) +Requirement already satisfied: charset-normalizer<4,>=2 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from requests>=2.31.0->nextmv==0.28.5->-r requirements.txt (line 1)) (3.4.1) +Requirement already satisfied: idna<4,>=2.5 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from requests>=2.31.0->nextmv==0.28.5->-r requirements.txt (line 1)) (3.10) +Requirement already satisfied: certifi>=2017.4.17 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from requests>=2.31.0->nextmv==0.28.5->-r requirements.txt (line 1)) (2025.1.31) +Using cached nextmv-0.28.5-py3-none-any.whl (99 kB) +Downloading ortools-9.14.6206-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (27.7 MB) + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 27.7/27.7 MB 52.6 MB/s eta 0:00:00 +Downloading protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl (321 kB) +Installing collected packages: protobuf, ortools, nextmv + Attempting uninstall: protobuf + Found existing installation: protobuf 5.29.4 + Uninstalling protobuf-5.29.4: + Successfully uninstalled protobuf-5.29.4 + Attempting uninstall: ortools + Found existing installation: ortools 9.12.4544 + Uninstalling ortools-9.12.4544: + Successfully uninstalled ortools-9.12.4544 + Attempting uninstall: nextmv + Found existing installation: nextmv 0.29.0.dev0 + Uninstalling nextmv-0.29.0.dev0: + Successfully uninstalled nextmv-0.29.0.dev0 + +Successfully installed nextmv-0.28.5 ortools-9.14.6206 protobuf-6.31.1 diff --git a/.nextmv/readme/python-ortools-multiknapsack-multiexcel/1.sh b/.nextmv/readme/python-ortools-multiknapsack-multiexcel/1.sh new file mode 100644 index 00000000..814546b6 --- /dev/null +++ b/.nextmv/readme/python-ortools-multiknapsack-multiexcel/1.sh @@ -0,0 +1 @@ +python3 main.py -duration 30 -provider SCIP diff --git a/.nextmv/readme/python-ortools-multiknapsack-multiexcel/1.sh.golden b/.nextmv/readme/python-ortools-multiknapsack-multiexcel/1.sh.golden new file mode 100644 index 00000000..2194214e --- /dev/null +++ b/.nextmv/readme/python-ortools-multiknapsack-multiexcel/1.sh.golden @@ -0,0 +1,3 @@ +Solving multi-knapsack problem: + - items: 11 + - knapsacks: 2 diff --git a/.nextmv/readme/workflow-configuration.yml b/.nextmv/readme/workflow-configuration.yml index 7eb8251c..a374b94b 100644 --- a/.nextmv/readme/workflow-configuration.yml +++ b/.nextmv/readme/workflow-configuration.yml @@ -166,6 +166,10 @@ apps: silent: true - name: 2.sh skip: true + - name: python-ortools-multiknapsack-multiexcel + scripts: + - name: 0.sh + silent: true - name: python-ortools-region-allocation scripts: - name: 0.sh diff --git a/python-ortools-multiknapsack-multiexcel/requirements.txt b/python-ortools-multiknapsack-multiexcel/requirements.txt index 821bfeac..62814407 100644 --- a/python-ortools-multiknapsack-multiexcel/requirements.txt +++ b/python-ortools-multiknapsack-multiexcel/requirements.txt @@ -1,2 +1,2 @@ -ortools==9.12.4544 -nextmv==0.26.3 +nextmv==0.29.0.dev0 +ortools==9.14.6206 From 39376aa3f3311b58d2fe70ca0d5c867fb0f174b6 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Mon, 7 Jul 2025 04:51:17 +0200 Subject: [PATCH 04/15] Silencing expectation --- .../0.sh.golden | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/.nextmv/readme/python-ortools-multiknapsack-multiexcel/0.sh.golden b/.nextmv/readme/python-ortools-multiknapsack-multiexcel/0.sh.golden index 2e75324e..e69de29b 100644 --- a/.nextmv/readme/python-ortools-multiknapsack-multiexcel/0.sh.golden +++ b/.nextmv/readme/python-ortools-multiknapsack-multiexcel/0.sh.golden @@ -1,44 +0,0 @@ -Collecting nextmv==0.28.5 (from -r requirements.txt (line 1)) - Using cached nextmv-0.28.5-py3-none-any.whl.metadata (15 kB) -Collecting ortools==9.14.6206 (from -r requirements.txt (line 2)) - Downloading ortools-9.14.6206-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (3.3 kB) -Requirement already satisfied: pydantic>=2.5.2 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from nextmv==0.28.5->-r requirements.txt (line 1)) (2.11.3) -Requirement already satisfied: pyyaml>=6.0.1 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from nextmv==0.28.5->-r requirements.txt (line 1)) (6.0.2) -Requirement already satisfied: requests>=2.31.0 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from nextmv==0.28.5->-r requirements.txt (line 1)) (2.32.3) -Requirement already satisfied: urllib3>=2.1.0 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from nextmv==0.28.5->-r requirements.txt (line 1)) (2.4.0) -Requirement already satisfied: absl-py>=2.0.0 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from ortools==9.14.6206->-r requirements.txt (line 2)) (2.2.2) -Requirement already satisfied: numpy>=1.13.3 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from ortools==9.14.6206->-r requirements.txt (line 2)) (2.2.5) -Requirement already satisfied: pandas>=2.0.0 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from ortools==9.14.6206->-r requirements.txt (line 2)) (2.2.3) -Collecting protobuf<6.32,>=6.31.1 (from ortools==9.14.6206->-r requirements.txt (line 2)) - Downloading protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl.metadata (593 bytes) -Requirement already satisfied: typing-extensions>=4.12 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from ortools==9.14.6206->-r requirements.txt (line 2)) (4.13.2) -Requirement already satisfied: immutabledict>=3.0.0 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from ortools==9.14.6206->-r requirements.txt (line 2)) (4.2.1) -Requirement already satisfied: python-dateutil>=2.8.2 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from pandas>=2.0.0->ortools==9.14.6206->-r requirements.txt (line 2)) (2.9.0.post0) -Requirement already satisfied: pytz>=2020.1 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from pandas>=2.0.0->ortools==9.14.6206->-r requirements.txt (line 2)) (2025.2) -Requirement already satisfied: tzdata>=2022.7 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from pandas>=2.0.0->ortools==9.14.6206->-r requirements.txt (line 2)) (2025.2) -Requirement already satisfied: annotated-types>=0.6.0 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from pydantic>=2.5.2->nextmv==0.28.5->-r requirements.txt (line 1)) (0.7.0) -Requirement already satisfied: pydantic-core==2.33.1 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from pydantic>=2.5.2->nextmv==0.28.5->-r requirements.txt (line 1)) (2.33.1) -Requirement already satisfied: typing-inspection>=0.4.0 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from pydantic>=2.5.2->nextmv==0.28.5->-r requirements.txt (line 1)) (0.4.0) -Requirement already satisfied: six>=1.5 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas>=2.0.0->ortools==9.14.6206->-r requirements.txt (line 2)) (1.17.0) -Requirement already satisfied: charset-normalizer<4,>=2 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from requests>=2.31.0->nextmv==0.28.5->-r requirements.txt (line 1)) (3.4.1) -Requirement already satisfied: idna<4,>=2.5 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from requests>=2.31.0->nextmv==0.28.5->-r requirements.txt (line 1)) (3.10) -Requirement already satisfied: certifi>=2017.4.17 in /home/marius/.asdf/installs/python/3.13.3/lib/python3.13/site-packages (from requests>=2.31.0->nextmv==0.28.5->-r requirements.txt (line 1)) (2025.1.31) -Using cached nextmv-0.28.5-py3-none-any.whl (99 kB) -Downloading ortools-9.14.6206-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (27.7 MB) - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 27.7/27.7 MB 52.6 MB/s eta 0:00:00 -Downloading protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl (321 kB) -Installing collected packages: protobuf, ortools, nextmv - Attempting uninstall: protobuf - Found existing installation: protobuf 5.29.4 - Uninstalling protobuf-5.29.4: - Successfully uninstalled protobuf-5.29.4 - Attempting uninstall: ortools - Found existing installation: ortools 9.12.4544 - Uninstalling ortools-9.12.4544: - Successfully uninstalled ortools-9.12.4544 - Attempting uninstall: nextmv - Found existing installation: nextmv 0.29.0.dev0 - Uninstalling nextmv-0.29.0.dev0: - Successfully uninstalled nextmv-0.29.0.dev0 - -Successfully installed nextmv-0.28.5 ortools-9.14.6206 protobuf-6.31.1 From be9bb5208ba2972a1e3eb48b95e477fcbd8c54d3 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Mon, 7 Jul 2025 23:52:53 +0200 Subject: [PATCH 05/15] Iterate dfs only once --- .../main.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/python-ortools-multiknapsack-multiexcel/main.py b/python-ortools-multiknapsack-multiexcel/main.py index 429f4cbc..1f76fa2e 100644 --- a/python-ortools-multiknapsack-multiexcel/main.py +++ b/python-ortools-multiknapsack-multiexcel/main.py @@ -70,29 +70,29 @@ def solve(self, input: nextmv.Input) -> nextmv.Output: # Unpack the input data. if "items" not in input.data or "knapsacks" not in input.data: raise ValueError("Input data must contain items and knapsacks.") - items: pd.DataFrame = input.data["items"] - knapsacks: pd.DataFrame = input.data["knapsacks"] + items_df: pd.DataFrame = input.data["items"] + knapsacks_df: pd.DataFrame = input.data["knapsacks"] + items = items_df.to_dict("records") + knapsacks = knapsacks_df.to_dict("records") # Initialize variables. assignments = {} - for _, knapsack in knapsacks.iterrows(): - for _, item in items.iterrows(): + for knapsack in knapsacks: + for item in items: # Create a binary variable for each item in each knapsack. assignments[(knapsack["id"], item["id"])] = solver.IntVar(0, 1, f"{knapsack['id']}_{item['id']}") # Make sure the knapsacks' capacities are not exceeded. - for _, knapsack in knapsacks.iterrows(): + for knapsack in knapsacks: solver.Add( - solver.Sum(assignments[(knapsack["id"], item["id"])] * item["weight"] for _, item in items.iterrows()) + solver.Sum(assignments[(knapsack["id"], item["id"])] * item["weight"] for item in items) <= knapsack["capacity"] ) # Maximize the total value of the items in the knapsacks. solver.Maximize( solver.Sum( - assignments[(knapsack["id"], item["id"])] * item["value"] - for _, knapsack in knapsacks.iterrows() - for _, item in items.iterrows() + assignments[(knapsack["id"], item["id"])] * item["value"] for knapsack in knapsacks for item in items ) ) @@ -102,8 +102,8 @@ def solve(self, input: nextmv.Input) -> nextmv.Output: # Determines which items were chosen. chosen_items = [ (knapsack["id"], item["id"]) - for _, knapsack in knapsacks.iterrows() - for _, item in items.iterrows() + for knapsack in knapsacks + for item in items if assignments[(knapsack["id"], item["id"])].solution_value() > 0.5 ] From aef606098052c127913f391ba71683493c70f0a1 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Mon, 7 Jul 2025 23:58:33 +0200 Subject: [PATCH 06/15] Fixing item integrity --- python-ortools-multiknapsack-multiexcel/main.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/python-ortools-multiknapsack-multiexcel/main.py b/python-ortools-multiknapsack-multiexcel/main.py index 1f76fa2e..bceabd1c 100644 --- a/python-ortools-multiknapsack-multiexcel/main.py +++ b/python-ortools-multiknapsack-multiexcel/main.py @@ -89,6 +89,10 @@ def solve(self, input: nextmv.Input) -> nextmv.Output: <= knapsack["capacity"] ) + # Ensure that each item can only be assigned once. + for item in items: + solver.Add(solver.Sum(assignments[(knapsack["id"], item["id"])] for knapsack in knapsacks) <= 1) + # Maximize the total value of the items in the knapsacks. solver.Maximize( solver.Sum( @@ -120,18 +124,6 @@ def solve(self, input: nextmv.Input) -> nextmv.Output: ), ) - df = pd.DataFrame( - { - "knapsack_id": [knapsack_id for knapsack_id, _ in chosen_items], - "item_id": [item_id for _, item_id in chosen_items], - } - ) - df.to_excel( - f"{input.options.output}/assignments.xlsx", - index=False, - sheet_name="assignments", - ) - return nextmv.Output( options=input.options, solution_files=[ From 536907fa5bc2d5c8cebccdd02d601a0725c696a8 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Tue, 8 Jul 2025 00:17:09 +0200 Subject: [PATCH 07/15] Fixing item integrity and paths --- .../main/java/com/nextmv/example/Main.java | 52 ++++++++++++++----- .../main/java/com/nextmv/example/Options.java | 2 +- .../main/java/com/nextmv/example/Output.java | 18 +++++-- 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Main.java b/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Main.java index 0506f02b..883091a8 100644 --- a/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Main.java +++ b/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Main.java @@ -22,17 +22,8 @@ public static void main(String[] args) { System.err.println("Input directory does not exist: " + options.getInputPath()); System.exit(1); } - // Create output directory if it does not exist. - Path outputPath = Paths.get(options.getOutputPath()); - if (!outputPath.toFile().exists()) { - if (!outputPath.toFile().mkdirs()) { - System.err.println("Failed to create output directory: " + options.getOutputPath()); - System.exit(1); - } - } else if (!outputPath.toFile().isDirectory()) { - System.err.println("Output path is not a directory: " + options.getOutputPath()); - System.exit(1); - } + // Prepare output directory. + prepareOutputDirectory(options.getOutputPath()); // Load input. ExcelReader inputReader = new ExcelReader(); @@ -82,6 +73,15 @@ public static void main(String[] args) { model.addConstr(knapsackExpr, GRB.LESS_EQUAL, knapsack.getCapacity(), "capacity_" + knapsack.getId()); } + // Ensure that each item can only be assigned once. + for (int j = 0; j < inputItems.size(); ++j) { + GRBLinExpr itemExpr = new GRBLinExpr(); + for (int i = 0; i < input.getKnapsacks().size(); ++i) { + itemExpr.addTerm(1.0, variables.get(i * inputItems.size() + j)); + } + model.addConstr(itemExpr, GRB.LESS_EQUAL, 1.0, "item_assignment_" + inputItems.get(j).getId()); + } + // Create the objective function. GRBLinExpr objectiveExpr = new GRBLinExpr(); for (int i = 0; i < input.getKnapsacks().size(); ++i) { @@ -111,8 +111,8 @@ public static void main(String[] args) { // Write solution to Excel file. ExcelWriter outputWriter = new ExcelWriter(); try { - String outputFilePath = Paths.get(options.getOutputPath(), "output.xlsx").toString(); - outputWriter.writeSolutionToExcel(solution, outputFilePath); + String solutionPath = Paths.get(options.getOutputPath(), "solutions", "solution.xlsx").toString(); + outputWriter.writeSolutionToExcel(solution, solutionPath); } catch (Exception e) { System.err.println("Error writing output file: " + e.getMessage()); System.exit(1); @@ -128,7 +128,7 @@ public static void main(String[] args) { model.get(GRB.IntAttr.NumConstrs)); // Write output. - Output.write(output); + Output.write(output, options.getOutputPath()); // Dispose of model and environment. model.dispose(); @@ -140,6 +140,30 @@ public static void main(String[] args) { } } + private static void prepareOutputDirectory(String outputPath) { + // Prepare output directory if it does not exist. + Path solutionsPath = Paths.get(outputPath, "solutions"); + Path statisticsPath = Paths.get(outputPath, "statistics"); + if (!solutionsPath.toFile().exists()) { + if (!solutionsPath.toFile().mkdirs()) { + System.err.println("Failed to create solutions directory: " + solutionsPath.toString()); + System.exit(1); + } + } else if (!solutionsPath.toFile().isDirectory()) { + System.err.println("Solutions path is not a directory: " + solutionsPath.toString()); + System.exit(1); + } + if (!statisticsPath.toFile().exists()) { + if (!statisticsPath.toFile().mkdirs()) { + System.err.println("Failed to create statistics directory: " + statisticsPath.toString()); + System.exit(1); + } + } else if (!statisticsPath.toFile().isDirectory()) { + System.err.println("Statistics path is not a directory: " + statisticsPath.toString()); + System.exit(1); + } + } + public static String convertStatus(int status) { switch (status) { case GRB.Status.LOADED: diff --git a/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Options.java b/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Options.java index 6ee2ef4c..e9b3eb7a 100644 --- a/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Options.java +++ b/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Options.java @@ -29,7 +29,7 @@ public static Options fromArguments(String[] args) { // And all output files will get collected in the "solutions" directory, after // the run. String inputPath = "inputs/"; - String outputPath = "outputs/solutions/"; + String outputPath = "outputs/"; int duration = 30; for (int i = 0; i < args.length; ++i) { diff --git a/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Output.java b/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Output.java index 81c74348..eaa3f688 100644 --- a/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Output.java +++ b/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Output.java @@ -2,6 +2,8 @@ import java.util.List; import java.util.ArrayList; +import java.nio.file.Files; +import java.nio.file.Paths; import com.google.gson.Gson; @@ -49,10 +51,18 @@ public Output( this.statistics.result.custom.variables = variables; } - public static void write(Output output) { - // Always write to stdout. + public static void write(Output output, String outputPath) { + // Always write to {outputPath}/statistics/statistics.json + // as required by convention. Gson gson = new Gson(); - System.out.println(gson.toJson(output)); - return; + String json = gson.toJson(output); + java.nio.file.Path path = Paths.get(outputPath, "statistics", "statistics.json"); + try { + Files.createDirectories(path.getParent()); + Files.writeString(path, json); + } catch (java.io.IOException e) { + System.err.println("Failed to write output: " + e.getMessage()); + System.exit(1); + } } } From 6c1aa698b9db18d609c1439a812695e2029b1624 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Tue, 8 Jul 2025 18:07:15 +0200 Subject: [PATCH 08/15] Reduce nesting --- .../main.py | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/python-ortools-multiknapsack-multiexcel/main.py b/python-ortools-multiknapsack-multiexcel/main.py index bceabd1c..81578e76 100644 --- a/python-ortools-multiknapsack-multiexcel/main.py +++ b/python-ortools-multiknapsack-multiexcel/main.py @@ -124,24 +124,24 @@ def solve(self, input: nextmv.Input) -> nextmv.Output: ), ) + excel_sol_file = nextmv.SolutionFile( + name="assignments.xlsx", + data=pd.DataFrame( + { + "knapsack_id": [knapsack_id for knapsack_id, _ in chosen_items], + "item_id": [item_id for _, item_id in chosen_items], + } + ), + writer=lambda path, data: data.to_excel(path, index=False, sheet_name="assignments"), + ) + csv_sol_file = nextmv.csv_solution_file( + "assignments", + data=[{"knapsack_id": knapsack_id, "item_id": item_id} for knapsack_id, item_id in chosen_items], + ) + return nextmv.Output( options=input.options, - solution_files=[ - nextmv.SolutionFile( - name="assignments.xlsx", - data=pd.DataFrame( - { - "knapsack_id": [knapsack_id for knapsack_id, _ in chosen_items], - "item_id": [item_id for _, item_id in chosen_items], - } - ), - writer=lambda path, data: data.to_excel(path, index=False, sheet_name="assignments"), - ), - nextmv.csv_solution_file( - "assignments", - data=[{"knapsack_id": knapsack_id, "item_id": item_id} for knapsack_id, item_id in chosen_items], - ), - ], + solution_files=[excel_sol_file, csv_sol_file], statistics=statistics, output_format=nextmv.OutputFormat.MULTI_FILE, ) From bc5322a8db88023c8eb28f2fa9ecc0eefd369778 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Tue, 8 Jul 2025 18:09:20 +0200 Subject: [PATCH 09/15] Reducing nesting --- .../main.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/python-ortools-multiknapsack-multiexcel/main.py b/python-ortools-multiknapsack-multiexcel/main.py index 81578e76..5d02fb64 100644 --- a/python-ortools-multiknapsack-multiexcel/main.py +++ b/python-ortools-multiknapsack-multiexcel/main.py @@ -26,21 +26,21 @@ def main() -> None: nextmv.Option("provider", str, "SCIP", "Solver provider.", False), ) + items_input_file = nextmv.DataFile( + name="input.xlsx", + loader=lambda path: pd.read_excel(path, sheet_name="items"), + input_data_key="items", + ) + knapsacks_input_file = nextmv.DataFile( + name="input.xlsx", + loader=lambda path: pd.read_excel(path, sheet_name="knapsacks"), + input_data_key="knapsacks", + ) + input = nextmv.load( options=options, path=options.input, - data_files=[ - nextmv.DataFile( - name="input.xlsx", - loader=lambda path: pd.read_excel(path, sheet_name="items"), - input_data_key="items", - ), - nextmv.DataFile( - name="input.xlsx", - loader=lambda path: pd.read_excel(path, sheet_name="knapsacks"), - input_data_key="knapsacks", - ), - ], + data_files=[items_input_file, knapsacks_input_file], input_format=nextmv.InputFormat.MULTI_FILE, ) From a0de9fc75c723d13c65ed98e82ab2333fff85e98 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Tue, 8 Jul 2025 18:21:27 +0200 Subject: [PATCH 10/15] Bumping nextmv and nextpipe versions --- .nextmv/release/requirements.txt | 2 +- python-ampl-facilitylocation/requirements.txt | 2 +- python-ampl-knapsack/requirements.txt | 2 +- python-ampl-priceoptimization/requirements.txt | 2 +- python-gurobi-knapsack/main.ipynb | 2 +- python-gurobi-knapsack/requirements.txt | 2 +- python-gurobi-price-optimization/main.ipynb | 10 +++++----- python-hello-world/requirements.txt | 2 +- python-hexaly-generic/requirements.txt | 2 +- python-hexaly-knapsack/requirements.txt | 2 +- python-highs-knapsack/main.ipynb | 2 +- python-highs-knapsack/requirements.txt | 2 +- python-nextroute/main.ipynb | 2 +- python-nextroute/requirements.txt | 2 +- python-ortools-costflow/requirements.txt | 2 +- python-ortools-demandforecasting/requirements.txt | 2 +- python-ortools-knapsack-multicsv/requirements.txt | 2 +- python-ortools-knapsack/requirements.txt | 2 +- .../requirements.txt | 2 +- python-ortools-region-allocation/requirements.txt | 2 +- python-ortools-routing/requirements.txt | 2 +- python-ortools-shiftassignment/requirements.txt | 2 +- python-ortools-shiftplanning/requirements.txt | 2 +- python-pyomo-knapsack/requirements.txt | 2 +- python-pyomo-shiftassignment/requirements.txt | 2 +- python-pyomo-shiftplanning/requirements.txt | 2 +- python-pyoptinterface-knapsack/requirements.txt | 2 +- python-pyvroom-routing/requirements.txt | 2 +- python-tr-ortools-region-allocation/requirements.txt | 2 +- python-verso-routing/requirements.txt | 2 +- .../README.md | 4 ++-- .../avocado-price-optimizer.ipynb | 2 +- .../requirements.txt | 4 ++-- python-wf-databricks/README.md | 4 ++-- python-wf-databricks/hello-world-nextmv-app.ipynb | 2 +- python-wf-databricks/requirements.txt | 4 ++-- python-wf-ortools-region-allocation/requirements.txt | 4 ++-- python-xpress-knapsack/requirements.txt | 2 +- 38 files changed, 47 insertions(+), 47 deletions(-) diff --git a/.nextmv/release/requirements.txt b/.nextmv/release/requirements.txt index 042dce99..e72ad2e3 100644 --- a/.nextmv/release/requirements.txt +++ b/.nextmv/release/requirements.txt @@ -2,4 +2,4 @@ boto3>=1.34.33 pyyaml>=6.0.1 ruff>=0.1.7 requests>=2.26.0 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-ampl-facilitylocation/requirements.txt b/python-ampl-facilitylocation/requirements.txt index 8f721c72..e6b8b264 100644 --- a/python-ampl-facilitylocation/requirements.txt +++ b/python-ampl-facilitylocation/requirements.txt @@ -17,5 +17,5 @@ ampl-module-open==20250206 ampl-module-scip==20240724 ampl-module-xpress==20241227 -nextmv==0.26.3 +nextmv==0.29.0 pandas==2.2.2 diff --git a/python-ampl-knapsack/requirements.txt b/python-ampl-knapsack/requirements.txt index 3663a8b5..170b2246 100644 --- a/python-ampl-knapsack/requirements.txt +++ b/python-ampl-knapsack/requirements.txt @@ -17,4 +17,4 @@ ampl-module-open==20250206 ampl-module-scip==20240724 ampl-module-xpress==20241227 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-ampl-priceoptimization/requirements.txt b/python-ampl-priceoptimization/requirements.txt index 90ef2f63..ff7b27d4 100644 --- a/python-ampl-priceoptimization/requirements.txt +++ b/python-ampl-priceoptimization/requirements.txt @@ -17,5 +17,5 @@ ampl-module-open==20250206 ampl-module-scip==20240724 ampl-module-xpress==20241227 -nextmv==0.26.3 +nextmv==0.29.0 plotly==6.0.0 diff --git a/python-gurobi-knapsack/main.ipynb b/python-gurobi-knapsack/main.ipynb index 9a4fe2fb..36f4b3df 100644 --- a/python-gurobi-knapsack/main.ipynb +++ b/python-gurobi-knapsack/main.ipynb @@ -315,7 +315,7 @@ " name=\"gurobi_model\",\n", " requirements=[\n", " \"gurobipy==11.0.0\",\n", - " \"nextmv==0.26.3\",\n", + " \"nextmv==0.29.0\",\n", " ],\n", " options=options,\n", ")\n", diff --git a/python-gurobi-knapsack/requirements.txt b/python-gurobi-knapsack/requirements.txt index 9e8f0b56..6883087e 100644 --- a/python-gurobi-knapsack/requirements.txt +++ b/python-gurobi-knapsack/requirements.txt @@ -1,2 +1,2 @@ gurobipy==11.0.0 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-gurobi-price-optimization/main.ipynb b/python-gurobi-price-optimization/main.ipynb index 9cd6d328..16047a84 100644 --- a/python-gurobi-price-optimization/main.ipynb +++ b/python-gurobi-price-optimization/main.ipynb @@ -330,7 +330,7 @@ "model_configuration = nextmv.ModelConfiguration(\n", " name=reg_app_name,\n", " requirements=[\n", - " \"nextmv==0.26.3\",\n", + " \"nextmv==0.29.0\",\n", " \"statsmodels==0.14.4\",\n", " \"scikit-learn==1.6.1\",\n", " \"pandas==2.2.2\"\n", @@ -654,7 +654,7 @@ "model_configuration = nextmv.ModelConfiguration(\n", " name=\"avocado-price-optimizer\",\n", " requirements=[\n", - " \"nextmv==0.26.3\",\n", + " \"nextmv==0.29.0\",\n", " \"nextmv-gurobipy==0.3.0\",\n", " \"plotly==6.0.0\"\n", " ],\n", @@ -1070,8 +1070,8 @@ "source": [ "%%writefile workflow/requirements.txt\n", "pandas==2.2.2\n", - "nextmv==0.26.3\n", - "nextpipe==0.1.3" + "nextmv==0.29.0\n", + "nextpipe==0.3.1" ] }, { @@ -1176,7 +1176,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.1" + "version": "3.13.3" } }, "nbformat": 4, diff --git a/python-hello-world/requirements.txt b/python-hello-world/requirements.txt index 0e233293..c714e869 100644 --- a/python-hello-world/requirements.txt +++ b/python-hello-world/requirements.txt @@ -1,3 +1,3 @@ # Define the packages required by your project here. -nextmv==0.26.3 +nextmv==0.29.0 plotly==6.0.0 diff --git a/python-hexaly-generic/requirements.txt b/python-hexaly-generic/requirements.txt index 6b34be71..7c5af17e 100644 --- a/python-hexaly-generic/requirements.txt +++ b/python-hexaly-generic/requirements.txt @@ -3,4 +3,4 @@ --extra-index-url https://pypi.org/simple hexaly==13.0.20240712 -nextmv==0.29.0.dev0 +nextmv==0.29.0 diff --git a/python-hexaly-knapsack/requirements.txt b/python-hexaly-knapsack/requirements.txt index feb5f1ef..7c5af17e 100644 --- a/python-hexaly-knapsack/requirements.txt +++ b/python-hexaly-knapsack/requirements.txt @@ -3,4 +3,4 @@ --extra-index-url https://pypi.org/simple hexaly==13.0.20240712 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-highs-knapsack/main.ipynb b/python-highs-knapsack/main.ipynb index 895d9ddf..6cfc32b9 100644 --- a/python-highs-knapsack/main.ipynb +++ b/python-highs-knapsack/main.ipynb @@ -285,7 +285,7 @@ " name=\"highs_model\",\n", " requirements=[\n", " \"highspy==1.7.2\",\n", - " \"nextmv==0.26.3\"\n", + " \"nextmv==0.29.0\"\n", " ],\n", " options=options,\n", ")\n", diff --git a/python-highs-knapsack/requirements.txt b/python-highs-knapsack/requirements.txt index 342318b2..a486291a 100644 --- a/python-highs-knapsack/requirements.txt +++ b/python-highs-knapsack/requirements.txt @@ -1,2 +1,2 @@ highspy==1.9.0 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-nextroute/main.ipynb b/python-nextroute/main.ipynb index 0fddf8e3..aea98879 100644 --- a/python-nextroute/main.ipynb +++ b/python-nextroute/main.ipynb @@ -450,7 +450,7 @@ " name=\"nextroute_model\",\n", " requirements=[\n", " \"nextroute==1.11.0\",\n", - " \"nextmv==0.26.3\"\n", + " \"nextmv==0.29.0\"\n", " ],\n", " options=options,\n", ")\n", diff --git a/python-nextroute/requirements.txt b/python-nextroute/requirements.txt index d7387ceb..bb09b739 100644 --- a/python-nextroute/requirements.txt +++ b/python-nextroute/requirements.txt @@ -1,2 +1,2 @@ nextroute==1.11.4 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-ortools-costflow/requirements.txt b/python-ortools-costflow/requirements.txt index 821bfeac..78f34a56 100644 --- a/python-ortools-costflow/requirements.txt +++ b/python-ortools-costflow/requirements.txt @@ -1,2 +1,2 @@ ortools==9.12.4544 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-ortools-demandforecasting/requirements.txt b/python-ortools-demandforecasting/requirements.txt index 821bfeac..78f34a56 100644 --- a/python-ortools-demandforecasting/requirements.txt +++ b/python-ortools-demandforecasting/requirements.txt @@ -1,2 +1,2 @@ ortools==9.12.4544 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-ortools-knapsack-multicsv/requirements.txt b/python-ortools-knapsack-multicsv/requirements.txt index 821bfeac..78f34a56 100644 --- a/python-ortools-knapsack-multicsv/requirements.txt +++ b/python-ortools-knapsack-multicsv/requirements.txt @@ -1,2 +1,2 @@ ortools==9.12.4544 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-ortools-knapsack/requirements.txt b/python-ortools-knapsack/requirements.txt index 821bfeac..78f34a56 100644 --- a/python-ortools-knapsack/requirements.txt +++ b/python-ortools-knapsack/requirements.txt @@ -1,2 +1,2 @@ ortools==9.12.4544 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-ortools-multiknapsack-multiexcel/requirements.txt b/python-ortools-multiknapsack-multiexcel/requirements.txt index 62814407..273fe821 100644 --- a/python-ortools-multiknapsack-multiexcel/requirements.txt +++ b/python-ortools-multiknapsack-multiexcel/requirements.txt @@ -1,2 +1,2 @@ -nextmv==0.29.0.dev0 +nextmv==0.29.0 ortools==9.14.6206 diff --git a/python-ortools-region-allocation/requirements.txt b/python-ortools-region-allocation/requirements.txt index 821bfeac..78f34a56 100644 --- a/python-ortools-region-allocation/requirements.txt +++ b/python-ortools-region-allocation/requirements.txt @@ -1,2 +1,2 @@ ortools==9.12.4544 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-ortools-routing/requirements.txt b/python-ortools-routing/requirements.txt index 821bfeac..78f34a56 100644 --- a/python-ortools-routing/requirements.txt +++ b/python-ortools-routing/requirements.txt @@ -1,2 +1,2 @@ ortools==9.12.4544 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-ortools-shiftassignment/requirements.txt b/python-ortools-shiftassignment/requirements.txt index 821bfeac..78f34a56 100644 --- a/python-ortools-shiftassignment/requirements.txt +++ b/python-ortools-shiftassignment/requirements.txt @@ -1,2 +1,2 @@ ortools==9.12.4544 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-ortools-shiftplanning/requirements.txt b/python-ortools-shiftplanning/requirements.txt index 821bfeac..78f34a56 100644 --- a/python-ortools-shiftplanning/requirements.txt +++ b/python-ortools-shiftplanning/requirements.txt @@ -1,2 +1,2 @@ ortools==9.12.4544 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-pyomo-knapsack/requirements.txt b/python-pyomo-knapsack/requirements.txt index fbe114f8..200dd4fe 100644 --- a/python-pyomo-knapsack/requirements.txt +++ b/python-pyomo-knapsack/requirements.txt @@ -1,2 +1,2 @@ pyomo==6.8.0 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-pyomo-shiftassignment/requirements.txt b/python-pyomo-shiftassignment/requirements.txt index fbe114f8..200dd4fe 100644 --- a/python-pyomo-shiftassignment/requirements.txt +++ b/python-pyomo-shiftassignment/requirements.txt @@ -1,2 +1,2 @@ pyomo==6.8.0 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-pyomo-shiftplanning/requirements.txt b/python-pyomo-shiftplanning/requirements.txt index fbe114f8..200dd4fe 100644 --- a/python-pyomo-shiftplanning/requirements.txt +++ b/python-pyomo-shiftplanning/requirements.txt @@ -1,2 +1,2 @@ pyomo==6.8.0 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-pyoptinterface-knapsack/requirements.txt b/python-pyoptinterface-knapsack/requirements.txt index da70bf89..c86d2996 100644 --- a/python-pyoptinterface-knapsack/requirements.txt +++ b/python-pyoptinterface-knapsack/requirements.txt @@ -2,4 +2,4 @@ pyoptinterface[highs]==0.4.0 # Other packages. -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-pyvroom-routing/requirements.txt b/python-pyvroom-routing/requirements.txt index cc33e425..014d2537 100644 --- a/python-pyvroom-routing/requirements.txt +++ b/python-pyvroom-routing/requirements.txt @@ -1,2 +1,2 @@ pyvroom==1.14.0 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-tr-ortools-region-allocation/requirements.txt b/python-tr-ortools-region-allocation/requirements.txt index 821bfeac..78f34a56 100644 --- a/python-tr-ortools-region-allocation/requirements.txt +++ b/python-tr-ortools-region-allocation/requirements.txt @@ -1,2 +1,2 @@ ortools==9.12.4544 -nextmv==0.26.3 +nextmv==0.29.0 diff --git a/python-verso-routing/requirements.txt b/python-verso-routing/requirements.txt index 7a42e3cf..1c21b167 100644 --- a/python-verso-routing/requirements.txt +++ b/python-verso-routing/requirements.txt @@ -1,5 +1,5 @@ # Define the packages required by your project here. -nextmv==0.27.0 +nextmv==0.29.0 plotly==6.0.0 requests==2.32.4 polyline==1.4.0 diff --git a/python-wf-databricks-ml-gurobi-price-optimization/README.md b/python-wf-databricks-ml-gurobi-price-optimization/README.md index 16c75267..2b3a429f 100644 --- a/python-wf-databricks-ml-gurobi-price-optimization/README.md +++ b/python-wf-databricks-ml-gurobi-price-optimization/README.md @@ -13,8 +13,8 @@ The content of this worklflow is based on the [Gurobi example](https://colab.res - Nextmv API key - Databricks workspace access (with DATABRICKS_HOST and DATABRICKS_TOKEN) - The following Python packages (specified in `requirements.txt`): - - nextmv==0.26.3 - - nextpipe==0.1.3 + - nextmv==0.29.0 + - nextpipe==0.3.1 - pandas==2.2.3 - databricks-sdk diff --git a/python-wf-databricks-ml-gurobi-price-optimization/avocado-price-optimizer.ipynb b/python-wf-databricks-ml-gurobi-price-optimization/avocado-price-optimizer.ipynb index bad68ebb..1b27f0b6 100644 --- a/python-wf-databricks-ml-gurobi-price-optimization/avocado-price-optimizer.ipynb +++ b/python-wf-databricks-ml-gurobi-price-optimization/avocado-price-optimizer.ipynb @@ -474,7 +474,7 @@ "model_configuration = nextmv.ModelConfiguration(\n", " name=\"avocado-price-optimizer\",\n", " requirements=[\n", - " \"nextmv==0.26.3\",\n", + " \"nextmv==0.29.0\",\n", " \"nextmv-gurobipy==0.3.0\",\n", " \"plotly==6.0.0\"\n", " ],\n", diff --git a/python-wf-databricks-ml-gurobi-price-optimization/requirements.txt b/python-wf-databricks-ml-gurobi-price-optimization/requirements.txt index 761b1097..b3e1b689 100644 --- a/python-wf-databricks-ml-gurobi-price-optimization/requirements.txt +++ b/python-wf-databricks-ml-gurobi-price-optimization/requirements.txt @@ -1,4 +1,4 @@ -nextmv==0.26.3 -nextpipe==0.1.3 +nextmv==0.29.0 +nextpipe==0.3.1 pandas==2.2.3 databricks-sdk diff --git a/python-wf-databricks/README.md b/python-wf-databricks/README.md index d7f67ad5..5f2602e0 100644 --- a/python-wf-databricks/README.md +++ b/python-wf-databricks/README.md @@ -11,8 +11,8 @@ capabilities. - Nextmv API key - Databricks workspace access (with DATABRICKS_HOST and DATABRICKS_TOKEN) - The following Python packages (specified in `requirements.txt`): - - nextmv==0.26.3 - - nextpipe==0.1.3 + - nextmv==0.29.0 + - nextpipe==0.3.1 - pandas==2.2.3 - databricks-sdk diff --git a/python-wf-databricks/hello-world-nextmv-app.ipynb b/python-wf-databricks/hello-world-nextmv-app.ipynb index 7df00ab0..b1b50d69 100644 --- a/python-wf-databricks/hello-world-nextmv-app.ipynb +++ b/python-wf-databricks/hello-world-nextmv-app.ipynb @@ -656,7 +656,7 @@ "\n", "model_configuration = nextmv.ModelConfiguration(\n", " name=\"hello-world\",\n", - " requirements=[\"nextmv==0.26.3\", \"plotly==6.0.0\"],\n", + " requirements=[\"nextmv==0.29.0\", \"plotly==6.0.0\"],\n", " options=options,\n", ")\n", "manifest = nextmv.cloud.Manifest.from_model_configuration(model_configuration)\n", diff --git a/python-wf-databricks/requirements.txt b/python-wf-databricks/requirements.txt index 761b1097..b3e1b689 100644 --- a/python-wf-databricks/requirements.txt +++ b/python-wf-databricks/requirements.txt @@ -1,4 +1,4 @@ -nextmv==0.26.3 -nextpipe==0.1.3 +nextmv==0.29.0 +nextpipe==0.3.1 pandas==2.2.3 databricks-sdk diff --git a/python-wf-ortools-region-allocation/requirements.txt b/python-wf-ortools-region-allocation/requirements.txt index bff59745..20a7da4b 100644 --- a/python-wf-ortools-region-allocation/requirements.txt +++ b/python-wf-ortools-region-allocation/requirements.txt @@ -1,5 +1,5 @@ -nextpipe==0.2.1 -nextmv==0.26.3 +nextpipe==0.3.1 +nextmv==0.29.0 requests==2.32.4 plotly==6.1.0 pandas==2.2.3 diff --git a/python-xpress-knapsack/requirements.txt b/python-xpress-knapsack/requirements.txt index 8d707eca..15338588 100644 --- a/python-xpress-knapsack/requirements.txt +++ b/python-xpress-knapsack/requirements.txt @@ -1,2 +1,2 @@ xpress==9.4.2; platform_system != 'Darwin' or (platform_system == 'Darwin' and platform_machine != 'arm64') -nextmv==0.26.3 +nextmv==0.29.0 From 7abd755189a65dd14be35481397f12ef66d9b7ef Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Tue, 8 Jul 2025 18:25:00 +0200 Subject: [PATCH 11/15] Bumping nextmv gurobipy --- python-gurobi-price-optimization/main.ipynb | 2 +- python-nextmv-gurobipy-knapsack/main.ipynb | 2 +- python-nextmv-gurobipy-knapsack/requirements.txt | 2 +- .../avocado-price-optimizer.ipynb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python-gurobi-price-optimization/main.ipynb b/python-gurobi-price-optimization/main.ipynb index 16047a84..fadeaa71 100644 --- a/python-gurobi-price-optimization/main.ipynb +++ b/python-gurobi-price-optimization/main.ipynb @@ -655,7 +655,7 @@ " name=\"avocado-price-optimizer\",\n", " requirements=[\n", " \"nextmv==0.29.0\",\n", - " \"nextmv-gurobipy==0.3.0\",\n", + " \"nextmv-gurobipy==0.4.1\",\n", " \"plotly==6.0.0\"\n", " ],\n", " options=options,\n", diff --git a/python-nextmv-gurobipy-knapsack/main.ipynb b/python-nextmv-gurobipy-knapsack/main.ipynb index 269ac9be..a5ede59d 100644 --- a/python-nextmv-gurobipy-knapsack/main.ipynb +++ b/python-nextmv-gurobipy-knapsack/main.ipynb @@ -275,7 +275,7 @@ "model_configuration = nextmv.ModelConfiguration(\n", " name=\"gurobi_model\",\n", " requirements=[\n", - " \"nextmv-gurobipy==0.3.0\",\n", + " \"nextmv-gurobipy==0.4.1\",\n", " ],\n", " options=options,\n", ")\n", diff --git a/python-nextmv-gurobipy-knapsack/requirements.txt b/python-nextmv-gurobipy-knapsack/requirements.txt index a3ad91f2..9fb5ebb9 100644 --- a/python-nextmv-gurobipy-knapsack/requirements.txt +++ b/python-nextmv-gurobipy-knapsack/requirements.txt @@ -1 +1 @@ -nextmv-gurobipy==0.3.0 +nextmv-gurobipy==0.4.1 diff --git a/python-wf-databricks-ml-gurobi-price-optimization/avocado-price-optimizer.ipynb b/python-wf-databricks-ml-gurobi-price-optimization/avocado-price-optimizer.ipynb index 1b27f0b6..2b846e3f 100644 --- a/python-wf-databricks-ml-gurobi-price-optimization/avocado-price-optimizer.ipynb +++ b/python-wf-databricks-ml-gurobi-price-optimization/avocado-price-optimizer.ipynb @@ -475,7 +475,7 @@ " name=\"avocado-price-optimizer\",\n", " requirements=[\n", " \"nextmv==0.29.0\",\n", - " \"nextmv-gurobipy==0.3.0\",\n", + " \"nextmv-gurobipy==0.4.1\",\n", " \"plotly==6.0.0\"\n", " ],\n", " options=options,\n", From c47fb281e44f6f4ac98f9d0b57bf638d1e17d87d Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Tue, 8 Jul 2025 18:29:03 +0200 Subject: [PATCH 12/15] Adding missing dependencies --- python-ortools-multiknapsack-multiexcel/requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python-ortools-multiknapsack-multiexcel/requirements.txt b/python-ortools-multiknapsack-multiexcel/requirements.txt index 273fe821..911835a8 100644 --- a/python-ortools-multiknapsack-multiexcel/requirements.txt +++ b/python-ortools-multiknapsack-multiexcel/requirements.txt @@ -1,2 +1,4 @@ nextmv==0.29.0 ortools==9.14.6206 +openpyxl==3.1.5 +pandas==2.3.1 From 5a7f6cf879c1c4069f717658aaaa7e134c573e53 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Wed, 9 Jul 2025 00:40:16 +0200 Subject: [PATCH 13/15] Showing stderr on readme tests too --- .nextmv/readme/main_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/.nextmv/readme/main_test.go b/.nextmv/readme/main_test.go index c1d918be..b294f623 100644 --- a/.nextmv/readme/main_test.go +++ b/.nextmv/readme/main_test.go @@ -129,6 +129,7 @@ func TestGolden(t *testing.T) { testName, golden.BashConfig{ DisplayStdout: !scriptConfig.Silent, + DisplayStderr: !scriptConfig.Silent, WorkingDir: "../../" + app, OutputProcessConfig: golden.OutputProcessConfig{ VolatileRegexReplacements: replacements, From 4fd4d5a221ef8a4de9b643a244bb0531d1502330 Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Wed, 9 Jul 2025 02:42:41 +0200 Subject: [PATCH 14/15] Allowing capture of stderr, updating expecations --- .nextmv/readme/main_test.go | 3 ++- .../readme/python-highs-knapsack/1.sh.golden | 3 +++ .../python-ortools-costflow/1.sh.golden | 3 +++ .../1.sh.golden | 2 ++ .../python-ortools-knapsack/1.sh.golden | 3 +++ .../1.sh.golden | 5 ++++ .../readme/python-ortools-routing/1.sh.golden | 3 +++ .../1.sh.golden | 4 ++++ .../python-ortools-shiftplanning/1.sh.golden | 3 +++ .../readme/python-pyomo-knapsack/1.sh.golden | 3 +++ .../python-pyomo-shiftassignment/1.sh.golden | 4 ++++ .../python-pyomo-shiftplanning/1.sh.golden | 3 +++ .../1.sh.golden | 3 +++ .nextmv/readme/workflow-configuration.yml | 24 +++++++++++++++++++ 14 files changed, 65 insertions(+), 1 deletion(-) diff --git a/.nextmv/readme/main_test.go b/.nextmv/readme/main_test.go index b294f623..74b9b957 100644 --- a/.nextmv/readme/main_test.go +++ b/.nextmv/readme/main_test.go @@ -28,6 +28,7 @@ const configFile = "workflow-configuration.yml" type ScriptConfig struct { Name string `yaml:"name"` Silent bool `yaml:"silent"` + StdErr bool `yaml:"stderr"` Skip bool `yaml:"skip"` } @@ -129,7 +130,7 @@ func TestGolden(t *testing.T) { testName, golden.BashConfig{ DisplayStdout: !scriptConfig.Silent, - DisplayStderr: !scriptConfig.Silent, + DisplayStderr: scriptConfig.StdErr, WorkingDir: "../../" + app, OutputProcessConfig: golden.OutputProcessConfig{ VolatileRegexReplacements: replacements, diff --git a/.nextmv/readme/python-highs-knapsack/1.sh.golden b/.nextmv/readme/python-highs-knapsack/1.sh.golden index e69de29b..5941d252 100644 --- a/.nextmv/readme/python-highs-knapsack/1.sh.golden +++ b/.nextmv/readme/python-highs-knapsack/1.sh.golden @@ -0,0 +1,3 @@ +Solving knapsack problem: + - items: 11 + - capacity: 50 diff --git a/.nextmv/readme/python-ortools-costflow/1.sh.golden b/.nextmv/readme/python-ortools-costflow/1.sh.golden index e69de29b..84392db4 100644 --- a/.nextmv/readme/python-ortools-costflow/1.sh.golden +++ b/.nextmv/readme/python-ortools-costflow/1.sh.golden @@ -0,0 +1,3 @@ +Best value flow for project to worker assignment: + - projects: 5 + - workers: 3 diff --git a/.nextmv/readme/python-ortools-demandforecasting/1.sh.golden b/.nextmv/readme/python-ortools-demandforecasting/1.sh.golden index e69de29b..4a044bfb 100644 --- a/.nextmv/readme/python-ortools-demandforecasting/1.sh.golden +++ b/.nextmv/readme/python-ortools-demandforecasting/1.sh.golden @@ -0,0 +1,2 @@ +Solving demand forecasting problem: + - demands: 7300 diff --git a/.nextmv/readme/python-ortools-knapsack/1.sh.golden b/.nextmv/readme/python-ortools-knapsack/1.sh.golden index e69de29b..5941d252 100644 --- a/.nextmv/readme/python-ortools-knapsack/1.sh.golden +++ b/.nextmv/readme/python-ortools-knapsack/1.sh.golden @@ -0,0 +1,3 @@ +Solving knapsack problem: + - items: 11 + - capacity: 50 diff --git a/.nextmv/readme/python-ortools-region-allocation/1.sh.golden b/.nextmv/readme/python-ortools-region-allocation/1.sh.golden index e69de29b..09ccc52d 100644 --- a/.nextmv/readme/python-ortools-region-allocation/1.sh.golden +++ b/.nextmv/readme/python-ortools-region-allocation/1.sh.golden @@ -0,0 +1,5 @@ +Solving region allocation: + - regions: 189 + - hubs: 6 + - duration: 30 seconds + - provider: scip diff --git a/.nextmv/readme/python-ortools-routing/1.sh.golden b/.nextmv/readme/python-ortools-routing/1.sh.golden index e69de29b..8e18d346 100644 --- a/.nextmv/readme/python-ortools-routing/1.sh.golden +++ b/.nextmv/readme/python-ortools-routing/1.sh.golden @@ -0,0 +1,3 @@ +Solving routing problem: + - vehicles: 2 + - stops: 10 diff --git a/.nextmv/readme/python-ortools-shiftassignment/1.sh.golden b/.nextmv/readme/python-ortools-shiftassignment/1.sh.golden index e69de29b..fc59c22b 100644 --- a/.nextmv/readme/python-ortools-shiftassignment/1.sh.golden +++ b/.nextmv/readme/python-ortools-shiftassignment/1.sh.golden @@ -0,0 +1,4 @@ +Solving shift-assignment: + - shifts: 4 + - workers: 5 + - rules: 1 diff --git a/.nextmv/readme/python-ortools-shiftplanning/1.sh.golden b/.nextmv/readme/python-ortools-shiftplanning/1.sh.golden index e69de29b..86fdcd38 100644 --- a/.nextmv/readme/python-ortools-shiftplanning/1.sh.golden +++ b/.nextmv/readme/python-ortools-shiftplanning/1.sh.golden @@ -0,0 +1,3 @@ +Solving shift-planning: + - shifts-templates: 2 + - demands: 3 diff --git a/.nextmv/readme/python-pyomo-knapsack/1.sh.golden b/.nextmv/readme/python-pyomo-knapsack/1.sh.golden index e69de29b..5941d252 100644 --- a/.nextmv/readme/python-pyomo-knapsack/1.sh.golden +++ b/.nextmv/readme/python-pyomo-knapsack/1.sh.golden @@ -0,0 +1,3 @@ +Solving knapsack problem: + - items: 11 + - capacity: 50 diff --git a/.nextmv/readme/python-pyomo-shiftassignment/1.sh.golden b/.nextmv/readme/python-pyomo-shiftassignment/1.sh.golden index e69de29b..fc59c22b 100644 --- a/.nextmv/readme/python-pyomo-shiftassignment/1.sh.golden +++ b/.nextmv/readme/python-pyomo-shiftassignment/1.sh.golden @@ -0,0 +1,4 @@ +Solving shift-assignment: + - shifts: 4 + - workers: 5 + - rules: 1 diff --git a/.nextmv/readme/python-pyomo-shiftplanning/1.sh.golden b/.nextmv/readme/python-pyomo-shiftplanning/1.sh.golden index e69de29b..86fdcd38 100644 --- a/.nextmv/readme/python-pyomo-shiftplanning/1.sh.golden +++ b/.nextmv/readme/python-pyomo-shiftplanning/1.sh.golden @@ -0,0 +1,3 @@ +Solving shift-planning: + - shifts-templates: 2 + - demands: 3 diff --git a/.nextmv/readme/python-pyoptinterface-knapsack/1.sh.golden b/.nextmv/readme/python-pyoptinterface-knapsack/1.sh.golden index e69de29b..5941d252 100644 --- a/.nextmv/readme/python-pyoptinterface-knapsack/1.sh.golden +++ b/.nextmv/readme/python-pyoptinterface-knapsack/1.sh.golden @@ -0,0 +1,3 @@ +Solving knapsack problem: + - items: 11 + - capacity: 50 diff --git a/.nextmv/readme/workflow-configuration.yml b/.nextmv/readme/workflow-configuration.yml index a1a60eca..00cecfbb 100644 --- a/.nextmv/readme/workflow-configuration.yml +++ b/.nextmv/readme/workflow-configuration.yml @@ -136,6 +136,8 @@ apps: scripts: - name: 0.sh silent: true + - name: 1.sh + stderr: true - name: 2.sh skip: true - name: python-nextmv-gurobipy-knapsack @@ -154,18 +156,24 @@ apps: scripts: - name: 0.sh silent: true + - name: 1.sh + stderr: true - name: 2.sh skip: true - name: python-ortools-demandforecasting scripts: - name: 0.sh silent: true + - name: 1.sh + stderr: true - name: 2.sh skip: true - name: python-ortools-knapsack scripts: - name: 0.sh silent: true + - name: 1.sh + stderr: true - name: 2.sh skip: true - name: python-ortools-knapsack-multicsv @@ -184,48 +192,64 @@ apps: scripts: - name: 0.sh silent: true + - name: 1.sh + stderr: true - name: 2.sh skip: true - name: python-ortools-routing scripts: - name: 0.sh silent: true + - name: 1.sh + stderr: true - name: 2.sh skip: true - name: python-ortools-shiftassignment scripts: - name: 0.sh silent: true + - name: 1.sh + stderr: true - name: 2.sh skip: true - name: python-ortools-shiftplanning scripts: - name: 0.sh silent: true + - name: 1.sh + stderr: true - name: 2.sh skip: true - name: python-pyomo-knapsack scripts: - name: 0.sh silent: true + - name: 1.sh + stderr: true - name: 2.sh skip: true - name: python-pyomo-shiftassignment scripts: - name: 0.sh silent: true + - name: 1.sh + stderr: true - name: 2.sh skip: true - name: python-pyomo-shiftplanning scripts: - name: 0.sh silent: true + - name: 1.sh + stderr: true - name: 2.sh skip: true - name: python-pyoptinterface-knapsack scripts: - name: 0.sh silent: true + - name: 1.sh + stderr: true - name: 2.sh skip: true - name: python-pyvroom-routing From 25f63a9a6d726f33a9ca7356728e8e0a52bc22be Mon Sep 17 00:00:00 2001 From: Marius Merschformann Date: Wed, 9 Jul 2025 02:57:10 +0200 Subject: [PATCH 15/15] Enabling stderr capture on ortools multi expectation --- .nextmv/readme/workflow-configuration.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.nextmv/readme/workflow-configuration.yml b/.nextmv/readme/workflow-configuration.yml index 00cecfbb..d739455d 100644 --- a/.nextmv/readme/workflow-configuration.yml +++ b/.nextmv/readme/workflow-configuration.yml @@ -188,6 +188,8 @@ apps: scripts: - name: 0.sh silent: true + - name: 1.sh + stderr: true - name: python-ortools-region-allocation scripts: - name: 0.sh