Skip to content

Commit 9ea3fb1

Browse files
authored
Merge pull request #165 from ProvideQ/fix/gams-scip
Changed GAMS MIP solver to SCIP and improved test output in CI pipeline.
2 parents 2815e33 + 8c4640d commit 9ea3fb1

10 files changed

Lines changed: 89 additions & 16 deletions

File tree

‎.github/workflows/ci-cd.yml‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,15 @@ jobs:
4040
venv-${{ runner.os }}-
4141
4242
- name: Install solver dependencies
43+
env:
44+
GAMSPY_LICENSE: ${{ secrets.GAMSPY_LICENSE }}
4345
run: ./scripts/ci-setup-solvers.sh
4446

4547
- name: Run checks
4648
shell: bash # required for the source command
4749
run: | # change to "./gradlew check --info" for more debugging output.
4850
source /opt/conda/bin/activate gams
49-
./gradlew check --info
51+
./gradlew check
5052
5153
- name: Archive test reports
5254
if: always()
@@ -81,4 +83,4 @@ jobs:
8183
command_timeout: 60m
8284
script: |
8385
echo "Deploying ${{ github.event.repository.name }} to '${{ github.ref_name }}' environment"
84-
sh /root/ProvideQ/${{ github.event.repository.name }}/deploy.sh ${{ github.ref_name }}
86+
sh /root/ProvideQ/${{ github.event.repository.name }}/deploy.sh ${{ github.ref_name }}

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
HELP.md
22
*.DS_Store
33
.gradle
4+
.gamspy_license
45
build/
56
!gradle/wrapper/gradle-wrapper.jar
67
!**/src/main/**/build/

‎build.gradle‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ jar {
3737
// inject JUnit into test task
3838
tasks.named('test') {
3939
useJUnitPlatform()
40+
41+
// Show test output (System.out.println, etc)
42+
testLogging {
43+
showStandardStreams = true
44+
events "passed", "skipped", "failed"
45+
}
4046
}
4147

4248
tasks.withType(JavaCompile).configureEach {

‎scripts/install-python-dependencies.py‎

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,23 @@
1414
os.path.join(root_dir, 'solvers'),
1515
]
1616

17+
def get_gamspy_license():
18+
# 1. Environment variable (GitHub Actions)
19+
license_key = os.environ.get("GAMSPY_LICENSE")
20+
if license_key:
21+
return license_key.strip()
22+
23+
# 2. Local file (ignored by git)
24+
license_file = os.path.join(root_dir, ".gamspy_license")
25+
if os.path.exists(license_file):
26+
with open(license_file) as f:
27+
return f.read().strip()
28+
29+
raise RuntimeError(
30+
"No GAMSPY license found. "
31+
"Set the GAMSPY_LICENSE environment variable or create a .gamspy_license file."
32+
)
33+
1734
exitCode = 0
1835
for base_dir in base_dirs:
1936
for root, dirs, files in os.walk(base_dir):
@@ -24,25 +41,69 @@
2441
# Iterate over problem directory (knapsack, tsp, etc.)
2542
for solver_name in os.listdir(framework_dir):
2643
solver_dir = os.path.join(framework_dir, solver_name)
44+
2745
# If folder filter is specified, only process matching folders
2846
if folder_filter and folder_filter not in solver_dir:
2947
continue
3048

3149
req_file = os.path.join(solver_dir, 'requirements.txt')
3250
if os.path.exists(req_file):
3351
venv_name = f"{os.path.basename(root)}_{framework_name}_{solver_name}"
34-
print(f"Setting up virtual environment '{venv_name}' for {solver_dir}...")
52+
53+
print(f"Setting up virtual environment '{venv_name}'")
3554
try:
3655
venv_path = os.path.join('venv', venv_name)
37-
subprocess.run(['python', '-m', 'venv', venv_path], check=True)
56+
subprocess.run(['python', '-m', 'venv', venv_path], check=True, capture_output=True)
3857
if platform.system() == 'Windows':
3958
pip_executable = os.path.join(venv_path, 'Scripts', 'pip.exe')
59+
python_executable = os.path.join(venv_path, 'Scripts', 'python.exe')
4060
else:
4161
pip_executable = os.path.join(venv_path, 'bin', 'pip')
42-
subprocess.run([pip_executable, 'install', '-r', req_file], check=True)
62+
python_executable = os.path.join(venv_path, 'bin', 'python')
63+
64+
# install dependencies from requirements.txt
65+
subprocess.run([pip_executable, 'install', '-r', req_file], check=True, capture_output=True)
66+
67+
# install GAMSPy license if this is a GAMS environment
68+
if "gams" in venv_name.lower():
69+
license_key = get_gamspy_license()
70+
71+
print("Installing GAMSPy license and scip solver ...")
72+
subprocess.run(
73+
[
74+
python_executable,
75+
"-m",
76+
"gamspy",
77+
"install",
78+
"license",
79+
license_key,
80+
],
81+
check=True,
82+
capture_output=True,
83+
)
84+
85+
subprocess.run(
86+
[
87+
python_executable,
88+
"-m",
89+
"gamspy",
90+
"install",
91+
"solver",
92+
"scip"
93+
],
94+
check=True,
95+
capture_output=True,
96+
)
97+
4398
except subprocess.CalledProcessError as e:
44-
print(f"Error setting up virtual environment for {solver_dir}: {e}")
99+
print(f"Error setting up virtual environment '{venv_name}' for {solver_dir}:")
100+
if e.stdout:
101+
print("STDOUT:")
102+
print(e.stdout.decode() if isinstance(e.stdout, bytes) else e.stdout)
103+
if e.stderr:
104+
print("STDERR:")
105+
print(e.stderr.decode() if isinstance(e.stderr, bytes) else e.stderr)
45106
exitCode = 1
46107

47108
# let pipeline fail if there was an error in the venv setup.
48-
exit(exitCode)
109+
exit(exitCode)
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
# This file describes the python package requirements for all GAMS solvers.
2-
# We only build one venv for all of GAMS, not for every GAMS solver.
1+
# This file contains of the python dependencies required in the .gms files
2+
# GamsPy solvers are handles extra.
33
networkx
4+
gamspy == 1.23.1

‎solvers/gams/qubo/solver_lp_qubo.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ def parse_lp_to_df(filepath="unsplittable_model.lp"):
7171
)
7272

7373
print("Solving model natively...")
74-
qubo_model.solve(solver="CPLEX", output=sys.stdout, options=gp.Options(time_limit=60))
74+
qubo_model.solve(solver="SCIP", solver_options={"lp/solver": '"highs"'},
75+
output=sys.stdout, options=gp.Options(time_limit=60))
7576

7677
print(f"\n--- Solver Status: {qubo_model.status} ---")
7778
print(f"--- Objective Value: {qubo_model.objective_value} ---")
@@ -88,4 +89,4 @@ def parse_lp_to_df(filepath="unsplittable_model.lp"):
8889
with open(output_path, "w") as f:
8990
json.dump(solution_dict, f, indent=4)
9091

91-
print(f"--- Extracted {len(solution_dict)} active variables to {output_path} ---")
92+
print(f"--- Extracted {len(solution_dict)} active variables to {output_path} ---")

‎solvers/gams/unsplittable-mcf/solver_classical.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@
2626
model.build_equations_and_model()
2727

2828
print("\n--- Solving Classical MIP with CPLEX ---")
29-
model.solve_classical(solver="CPLEX", output_html=output_path)
29+
model.solve_classical(solver="SCIP", output_html=output_path)

‎src/main/java/edu/kit/provideq/toolbox/qubo/solvers/GamsQuboSolver.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ public GamsQuboSolver(
3333

3434
@Override
3535
public String getName() {
36-
return "(GAMS) CPLEX Solver for QUBOs";
36+
return "(GAMS) SCIP Solver for QUBOs";
3737
}
3838

3939
@Override
4040
public String getDescription() {
4141
return "Solves QUBO problems by transforming it into a MIP,"
42-
+ "which is then solved with CPLEX. Implementation is done in GAMS";
42+
+ "which is then solved with SCIP. Implementation is done in GAMS";
4343
}
4444

4545
@Override

‎src/main/java/edu/kit/provideq/toolbox/unsplittablemcf/solvers/GamsUnsplittableMcfClassicalSolver.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@ public GamsUnsplittableMcfClassicalSolver(
3434

3535
@Override
3636
public String getName() {
37-
return "(GAMS) CPLEX Classical MCF Solver";
37+
return "(GAMS) SCIP Classical MCF Solver";
3838
}
3939

4040
@Override
4141
public String getDescription() {
42-
return "Solves the Unsplittable Multi Commodity Flow problem using GAMSPy with CPLEX. "
42+
return "Solves the Unsplittable Multi Commodity Flow problem using GAMSPy and SCIP. "
4343
+ "Builds a time-expanded network model and finds optimal flow routes minimizing "
4444
+ "delay and slack penalties.";
4545
}

‎src/test/java/edu/kit/provideq/toolbox/api/ApiTestHelper.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ public static <InputT, ResultT> ProblemDto<InputT, ResultT> trySolveFor(
209209
// print error output if something went wrong
210210
if (problemDto.getState() != ProblemState.SOLVED
211211
|| problemDto.getSolution().getStatus() != SolutionStatus.SOLVED) {
212+
System.out.println("Testcase failed. Printing debugging info:");
212213
System.out.println(builder);
213214
}
214215

0 commit comments

Comments
 (0)