From 1ed0b952603204dfb4c91f4a97b187860b527b32 Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Fri, 31 Jul 2026 15:59:28 +0200 Subject: [PATCH 1/7] chore: changes GAMS cplex usage to scip --- solvers/gams/qubo/solver_lp_qubo.py | 5 +++-- solvers/gams/unsplittable-mcf/solver_classical.py | 2 +- .../kit/provideq/toolbox/qubo/solvers/GamsQuboSolver.java | 4 ++-- .../solvers/GamsUnsplittableMcfClassicalSolver.java | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/solvers/gams/qubo/solver_lp_qubo.py b/solvers/gams/qubo/solver_lp_qubo.py index 7c8650b6..e1d6f073 100644 --- a/solvers/gams/qubo/solver_lp_qubo.py +++ b/solvers/gams/qubo/solver_lp_qubo.py @@ -71,7 +71,8 @@ def parse_lp_to_df(filepath="unsplittable_model.lp"): ) print("Solving model natively...") -qubo_model.solve(solver="CPLEX", output=sys.stdout, options=gp.Options(time_limit=60)) +qubo_model.solve(solver="SCIP", solver_options={"lp/solver": '"highs"'}, + output=sys.stdout, options=gp.Options(time_limit=60)) print(f"\n--- Solver Status: {qubo_model.status} ---") print(f"--- Objective Value: {qubo_model.objective_value} ---") @@ -88,4 +89,4 @@ def parse_lp_to_df(filepath="unsplittable_model.lp"): with open(output_path, "w") as f: json.dump(solution_dict, f, indent=4) -print(f"--- Extracted {len(solution_dict)} active variables to {output_path} ---") \ No newline at end of file +print(f"--- Extracted {len(solution_dict)} active variables to {output_path} ---") diff --git a/solvers/gams/unsplittable-mcf/solver_classical.py b/solvers/gams/unsplittable-mcf/solver_classical.py index 55457e8f..c61687b0 100644 --- a/solvers/gams/unsplittable-mcf/solver_classical.py +++ b/solvers/gams/unsplittable-mcf/solver_classical.py @@ -26,4 +26,4 @@ model.build_equations_and_model() print("\n--- Solving Classical MIP with CPLEX ---") -model.solve_classical(solver="CPLEX", output_html=output_path) +model.solve_classical(solver="SCIP", solver_options={"lp/solver": '"highs"'}, output_html=output_path) diff --git a/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/GamsQuboSolver.java b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/GamsQuboSolver.java index 960195df..49522b69 100644 --- a/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/GamsQuboSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/GamsQuboSolver.java @@ -33,13 +33,13 @@ public GamsQuboSolver( @Override public String getName() { - return "(GAMS) CPLEX Solver for QUBOs"; + return "(GAMS) SCIP Solver for QUBOs"; } @Override public String getDescription() { return "Solves QUBO problems by transforming it into a MIP," - + "which is then solved with CPLEX. Implementation is done in GAMS"; + + "which is then solved with SCIP. Implementation is done in GAMS"; } @Override diff --git a/src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfClassicalSolver.java b/src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfClassicalSolver.java index 6e298aca..4daa51cf 100644 --- a/src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfClassicalSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfClassicalSolver.java @@ -34,12 +34,12 @@ public GamsUnsplittableMcfClassicalSolver( @Override public String getName() { - return "(GAMS) CPLEX Classical MCF Solver"; + return "(GAMS) SCIP Classical MCF Solver"; } @Override public String getDescription() { - return "Solves the Unsplittable Multi Commodity Flow problem using GAMSPy with CPLEX. " + return "Solves the Unsplittable Multi Commodity Flow problem using GAMSPy and SCIP. " + "Builds a time-expanded network model and finds optimal flow routes minimizing " + "delay and slack penalties."; } From 7791d3e00f17e53d1ef5e26757cd975310663ead Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Thu, 6 Aug 2026 18:50:59 +0200 Subject: [PATCH 2/7] add: gamspy licence install for new solvers --- .gitignore | 1 + scripts/install-python-dependencies.py | 44 +++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f20019d1..10037ddd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ HELP.md *.DS_Store .gradle +.gamspy_license build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ diff --git a/scripts/install-python-dependencies.py b/scripts/install-python-dependencies.py index 1f8ad00e..97f9191f 100644 --- a/scripts/install-python-dependencies.py +++ b/scripts/install-python-dependencies.py @@ -14,6 +14,23 @@ os.path.join(root_dir, 'solvers'), ] +def get_gamspy_license(): + # 1. Environment variable (GitHub Actions) + license_key = os.environ.get("GAMSPY_LICENSE") + if license_key: + return license_key.strip() + + # 2. Local file (ignored by git) + license_file = os.path.join(root_dir, ".gamspy_license") + if os.path.exists(license_file): + with open(license_file) as f: + return f.read().strip() + + raise RuntimeError( + "No GAMSPY license found. " + "Set the GAMSPY_LICENSE environment variable or create a .gamspy_license file." + ) + exitCode = 0 for base_dir in base_dirs: for root, dirs, files in os.walk(base_dir): @@ -24,6 +41,7 @@ # Iterate over problem directory (knapsack, tsp, etc.) for solver_name in os.listdir(framework_dir): solver_dir = os.path.join(framework_dir, solver_name) + # If folder filter is specified, only process matching folders if folder_filter and folder_filter not in solver_dir: continue @@ -32,17 +50,41 @@ if os.path.exists(req_file): venv_name = f"{os.path.basename(root)}_{framework_name}_{solver_name}" print(f"Setting up virtual environment '{venv_name}' for {solver_dir}...") + try: venv_path = os.path.join('venv', venv_name) subprocess.run(['python', '-m', 'venv', venv_path], check=True) if platform.system() == 'Windows': pip_executable = os.path.join(venv_path, 'Scripts', 'pip.exe') + python_executable = os.path.join(venv_path, 'Scripts', 'python.exe') else: pip_executable = os.path.join(venv_path, 'bin', 'pip') + python_executable = os.path.join(venv_path, 'bin', 'python') + + # install dependencies from requirements.txt subprocess.run([pip_executable, 'install', '-r', req_file], check=True) + + # install GAMSPy license if this is a GAMS environment + if "gams" in venv_name.lower(): + license_key = get_gamspy_license() + + print("Installing GAMSPy license...") + subprocess.run( + [ + python_executable, + "-m", + "gamspy", + "install", + "license", + license_key, + ], + check=True, + ) + print("GAMSPy license activated in '%s'" % venv_name) + except subprocess.CalledProcessError as e: print(f"Error setting up virtual environment for {solver_dir}: {e}") exitCode = 1 # let pipeline fail if there was an error in the venv setup. -exit(exitCode) \ No newline at end of file +exit(exitCode) From 7803e6519e21d3f2290b79dc59de9a3af70e6fc7 Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Fri, 7 Aug 2026 13:23:30 +0200 Subject: [PATCH 3/7] fix: added missing env GAMSPY LICENSE to yaml workflow --- .github/workflows/ci-cd.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 321b2dc4..b8ad59ab 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -40,6 +40,8 @@ jobs: venv-${{ runner.os }}- - name: Install solver dependencies + env: + GAMSPY_LICENSE: ${{ secrets.GAMSPY_LICENSE }} run: ./scripts/ci-setup-solvers.sh - name: Run checks @@ -81,4 +83,4 @@ jobs: command_timeout: 60m script: | echo "Deploying ${{ github.event.repository.name }} to '${{ github.ref_name }}' environment" - sh /root/ProvideQ/${{ github.event.repository.name }}/deploy.sh ${{ github.ref_name }} \ No newline at end of file + sh /root/ProvideQ/${{ github.event.repository.name }}/deploy.sh ${{ github.ref_name }} From f3ae131201123a77515064164fe76c5b1746063f Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Fri, 7 Aug 2026 13:37:50 +0200 Subject: [PATCH 4/7] fix: added gamspy dependency to generals gams venv (that is used by the .gms files). This fixes current script issues but the gams directory setup in our toolbox should be refactored in the future. --- solvers/gams/python/requirements.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/solvers/gams/python/requirements.txt b/solvers/gams/python/requirements.txt index b1b37424..c2608687 100644 --- a/solvers/gams/python/requirements.txt +++ b/solvers/gams/python/requirements.txt @@ -1,3 +1,4 @@ -# This file describes the python package requirements for all GAMS solvers. -# We only build one venv for all of GAMS, not for every GAMS solver. +# This file contains of the python dependencies required in the .gms files +# GamsPy solvers are handles extra. networkx +gamspy == 1.23.1 From cfa72dc0ff559f4f012fc42e48b3786e108c140b Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Fri, 7 Aug 2026 15:18:24 +0200 Subject: [PATCH 5/7] fix: added scip install, improved debugging output --- scripts/install-python-dependencies.py | 31 +++++++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/scripts/install-python-dependencies.py b/scripts/install-python-dependencies.py index 97f9191f..3e2a0733 100644 --- a/scripts/install-python-dependencies.py +++ b/scripts/install-python-dependencies.py @@ -49,11 +49,11 @@ def get_gamspy_license(): req_file = os.path.join(solver_dir, 'requirements.txt') if os.path.exists(req_file): venv_name = f"{os.path.basename(root)}_{framework_name}_{solver_name}" - print(f"Setting up virtual environment '{venv_name}' for {solver_dir}...") + print(f"Setting up virtual environment '{venv_name}'") try: venv_path = os.path.join('venv', venv_name) - subprocess.run(['python', '-m', 'venv', venv_path], check=True) + subprocess.run(['python', '-m', 'venv', venv_path], check=True, capture_output=True) if platform.system() == 'Windows': pip_executable = os.path.join(venv_path, 'Scripts', 'pip.exe') python_executable = os.path.join(venv_path, 'Scripts', 'python.exe') @@ -62,13 +62,13 @@ def get_gamspy_license(): python_executable = os.path.join(venv_path, 'bin', 'python') # install dependencies from requirements.txt - subprocess.run([pip_executable, 'install', '-r', req_file], check=True) + subprocess.run([pip_executable, 'install', '-r', req_file], check=True, capture_output=True) # install GAMSPy license if this is a GAMS environment if "gams" in venv_name.lower(): license_key = get_gamspy_license() - print("Installing GAMSPy license...") + print("Installing GAMSPy license and scip solver ...") subprocess.run( [ python_executable, @@ -79,11 +79,30 @@ def get_gamspy_license(): license_key, ], check=True, + capture_output=True, + ) + + subprocess.run( + [ + python_executable, + "-m", + "gamspy", + "install", + "solver", + "scip" + ], + check=True, + capture_output=True, ) - print("GAMSPy license activated in '%s'" % venv_name) except subprocess.CalledProcessError as e: - print(f"Error setting up virtual environment for {solver_dir}: {e}") + print(f"Error setting up virtual environment '{venv_name}' for {solver_dir}:") + if e.stdout: + print("STDOUT:") + print(e.stdout.decode() if isinstance(e.stdout, bytes) else e.stdout) + if e.stderr: + print("STDERR:") + print(e.stderr.decode() if isinstance(e.stderr, bytes) else e.stderr) exitCode = 1 # let pipeline fail if there was an error in the venv setup. From 1895473ddbf2a2e7e94aab4dfbea7565bc853bcb Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Fri, 7 Aug 2026 15:29:17 +0200 Subject: [PATCH 6/7] fix: removed info flag from gradle tests to reduce overhead in ci runners --- .github/workflows/ci-cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index b8ad59ab..8bdffa0d 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -48,7 +48,7 @@ jobs: shell: bash # required for the source command run: | # change to "./gradlew check --info" for more debugging output. source /opt/conda/bin/activate gams - ./gradlew check --info + ./gradlew check - name: Archive test reports if: always() From 8c4640de627218951950e42f11f6063103eaafe6 Mon Sep 17 00:00:00 2001 From: Domenik Eichhorn Date: Fri, 7 Aug 2026 15:55:28 +0200 Subject: [PATCH 7/7] fix: resolved wrong call in mcfp solver. Improved test output by gradle. --- build.gradle | 6 ++++++ solvers/gams/unsplittable-mcf/solver_classical.py | 2 +- .../java/edu/kit/provideq/toolbox/api/ApiTestHelper.java | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 510fe67d..15a41002 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,12 @@ jar { // inject JUnit into test task tasks.named('test') { useJUnitPlatform() + + // Show test output (System.out.println, etc) + testLogging { + showStandardStreams = true + events "passed", "skipped", "failed" + } } tasks.withType(JavaCompile).configureEach { diff --git a/solvers/gams/unsplittable-mcf/solver_classical.py b/solvers/gams/unsplittable-mcf/solver_classical.py index c61687b0..3f14552f 100644 --- a/solvers/gams/unsplittable-mcf/solver_classical.py +++ b/solvers/gams/unsplittable-mcf/solver_classical.py @@ -26,4 +26,4 @@ model.build_equations_and_model() print("\n--- Solving Classical MIP with CPLEX ---") -model.solve_classical(solver="SCIP", solver_options={"lp/solver": '"highs"'}, output_html=output_path) +model.solve_classical(solver="SCIP", output_html=output_path) diff --git a/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java b/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java index 2d2abb1f..b33b80f0 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java @@ -209,6 +209,7 @@ public static ProblemDto trySolveFor( // print error output if something went wrong if (problemDto.getState() != ProblemState.SOLVED || problemDto.getSolution().getStatus() != SolutionStatus.SOLVED) { + System.out.println("Testcase failed. Printing debugging info:"); System.out.println(builder); }