From faf1626122eda9658b117032ffef1be4b41643ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Jun 2025 03:28:17 +0000 Subject: [PATCH 1/7] Initial plan for issue From 30d413356c77c44cd75ac606d79e9fea67ba4961 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Jun 2025 03:40:31 +0000 Subject: [PATCH 2/7] Add complete HiGHS solver support Co-authored-by: ryanjoneil <6748953+ryanjoneil@users.noreply.github.com> --- HiGHS.log | 204 ++++++++++++++++++++++++++++++++ docs/03-solver-configuration.md | 16 +++ lib/rams/model.rb | 2 + lib/rams/solvers/highs.rb | 92 ++++++++++++++ tests/test_solver_paths.rb | 15 ++- 5 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 HiGHS.log create mode 100644 lib/rams/solvers/highs.rb diff --git a/HiGHS.log b/HiGHS.log new file mode 100644 index 0000000..e21a452 --- /dev/null +++ b/HiGHS.log @@ -0,0 +1,204 @@ +Running HiGHS 1.11.0 (git hash: 364c83a51): Copyright (c) 2025 HiGHS under MIT licence terms +Set option log_file to "HiGHS.log" +Set option solution_file to "/tmp/20250624-5677-ohtpru.lp.sol" +MIP 20250624-5677-ohtpru has 2 rows; 3 cols; 5 nonzeros; 3 integer variables (3 binary) +Coefficient ranges: + Matrix [1e+00, 1e+00] + Cost [1e+00, 3e+00] + Bound [1e+00, 1e+00] + RHS [1e+00, 2e+00] +Presolving model +2 rows, 3 cols, 5 nonzeros 0s +1 rows, 2 cols, 2 nonzeros 0s +0 rows, 0 cols, 0 nonzeros 0s +Presolve: Optimal + +Src: B => Branching; C => Central rounding; F => Feasibility pump; J => Feasibility jump; + H => Heuristic; L => Sub-MIP; P => Empty MIP; R => Randomized rounding; Z => ZI Round; + I => Shifting; S => Solve LP; T => Evaluate node; U => Unbounded; X => User solution; + z => Trivial zero; l => Trivial lower; u => Trivial upper; p => Trivial point + + Nodes | B&B Tree | Objective Bounds | Dynamic Constraints | Work +Src Proc. InQueue | Leaves Expl. | BestBound BestSol Gap | Cuts InLp Confl. | LpIters Time + + 0 0 0 0.00% 4 4 0.00% 0 0 0 0 0.0s + +Solving report + Model 20250624-5677-ohtpru + Status Optimal + Primal bound 4 + Dual bound 4 + Gap 0% (tolerance: 0.01%) + P-D integral 0 + Solution status feasible + 4 (objective) + 0 (bound viol.) + 0 (int. viol.) + 0 (row viol.) + Timing 0.00 (total) + 0.00 (presolve) + 0.00 (solve) + 0.00 (postsolve) + Max sub-MIP depth 0 + Nodes 0 + Repair LPs 0 (0 feasible; 0 iterations) + LP iterations 0 (total) + 0 (strong br.) + 0 (separation) + 0 (heuristics) +Writing the solution to /tmp/20250624-5677-ohtpru.lp.sol +Running HiGHS 1.11.0 (git hash: 364c83a51): Copyright (c) 2025 HiGHS under MIT licence terms +Set option log_file to "HiGHS.log" +Set option solution_file to "/tmp/debug.sol" +LP debug has 2 rows; 3 cols; 5 nonzeros +Coefficient ranges: + Matrix [1e+00, 1e+00] + Cost [1e+00, 3e+00] + Bound [0e+00, 0e+00] + RHS [1e+00, 2e+00] +Presolving model +2 rows, 3 cols, 5 nonzeros 0s +0 rows, 1 cols, 0 nonzeros 0s +0 rows, 0 cols, 0 nonzeros 0s +Presolve : Reductions: rows 0(-2); columns 0(-3); elements 0(-5) - Reduced to empty +Solving the original LP from the solution after postsolve +Model name : debug +Model status : Optimal +Objective value : 4.0000000000e+00 +P-D objective error : 0.0000000000e+00 +HiGHS run time : 0.00 +Writing the solution to /tmp/debug.sol +Running HiGHS 1.11.0 (git hash: 364c83a51): Copyright (c) 2025 HiGHS under MIT licence terms +Set option log_file to "HiGHS.log" +Set option solution_file to "/tmp/20250624-5685-fty4ru.lp.sol" +LP 20250624-5685-fty4ru has 2 rows; 3 cols; 5 nonzeros +Coefficient ranges: + Matrix [1e+00, 1e+00] + Cost [1e+00, 3e+00] + Bound [0e+00, 0e+00] + RHS [1e+00, 2e+00] +Presolving model +2 rows, 3 cols, 5 nonzeros 0s +0 rows, 1 cols, 0 nonzeros 0s +0 rows, 0 cols, 0 nonzeros 0s +Presolve : Reductions: rows 0(-2); columns 0(-3); elements 0(-5) - Reduced to empty +Solving the original LP from the solution after postsolve +Model name : 20250624-5685-fty4ru +Model status : Optimal +Objective value : 4.0000000000e+00 +P-D objective error : 0.0000000000e+00 +HiGHS run time : 0.00 +Writing the solution to /tmp/20250624-5685-fty4ru.lp.sol +Running HiGHS 1.11.0 (git hash: 364c83a51): Copyright (c) 2025 HiGHS under MIT licence terms +Set option log_file to "HiGHS.log" +Set option solution_file to "/tmp/debug.sol" +LP debug has 2 rows; 3 cols; 5 nonzeros +Coefficient ranges: + Matrix [1e+00, 1e+00] + Cost [1e+00, 3e+00] + Bound [0e+00, 0e+00] + RHS [1e+00, 2e+00] +Presolving model +2 rows, 3 cols, 5 nonzeros 0s +0 rows, 1 cols, 0 nonzeros 0s +0 rows, 0 cols, 0 nonzeros 0s +Presolve : Reductions: rows 0(-2); columns 0(-3); elements 0(-5) - Reduced to empty +Solving the original LP from the solution after postsolve +Model name : debug +Model status : Optimal +Objective value : 4.0000000000e+00 +P-D objective error : 0.0000000000e+00 +HiGHS run time : 0.00 +Writing the solution to /tmp/debug.sol +Running HiGHS 1.11.0 (git hash: 364c83a51): Copyright (c) 2025 HiGHS under MIT licence terms +Set option log_file to "HiGHS.log" +Set option solution_file to "/tmp/20250624-5703-fbex79.lp.sol" +LP 20250624-5703-fbex79 has 2 rows; 3 cols; 5 nonzeros +Coefficient ranges: + Matrix [1e+00, 1e+00] + Cost [1e+00, 3e+00] + Bound [0e+00, 0e+00] + RHS [1e+00, 2e+00] +Presolving model +2 rows, 3 cols, 5 nonzeros 0s +0 rows, 1 cols, 0 nonzeros 0s +0 rows, 0 cols, 0 nonzeros 0s +Presolve : Reductions: rows 0(-2); columns 0(-3); elements 0(-5) - Reduced to empty +Solving the original LP from the solution after postsolve +Model name : 20250624-5703-fbex79 +Model status : Optimal +Objective value : 4.0000000000e+00 +P-D objective error : 0.0000000000e+00 +HiGHS run time : 0.00 +Writing the solution to /tmp/20250624-5703-fbex79.lp.sol +Running HiGHS 1.11.0 (git hash: 364c83a51): Copyright (c) 2025 HiGHS under MIT licence terms +Set option log_file to "HiGHS.log" +Set option solution_file to "/tmp/20250624-5715-oukmal.lp.sol" +MIP 20250624-5715-oukmal has 2 rows; 3 cols; 5 nonzeros; 3 integer variables (3 binary) +Coefficient ranges: + Matrix [1e+00, 1e+00] + Cost [1e+00, 3e+00] + Bound [1e+00, 1e+00] + RHS [1e+00, 2e+00] +Presolving model +2 rows, 3 cols, 5 nonzeros 0s +1 rows, 2 cols, 2 nonzeros 0s +0 rows, 0 cols, 0 nonzeros 0s +Presolve: Optimal + +Src: B => Branching; C => Central rounding; F => Feasibility pump; J => Feasibility jump; + H => Heuristic; L => Sub-MIP; P => Empty MIP; R => Randomized rounding; Z => ZI Round; + I => Shifting; S => Solve LP; T => Evaluate node; U => Unbounded; X => User solution; + z => Trivial zero; l => Trivial lower; u => Trivial upper; p => Trivial point + + Nodes | B&B Tree | Objective Bounds | Dynamic Constraints | Work +Src Proc. InQueue | Leaves Expl. | BestBound BestSol Gap | Cuts InLp Confl. | LpIters Time + + 0 0 0 0.00% 4 4 0.00% 0 0 0 0 0.0s + +Solving report + Model 20250624-5715-oukmal + Status Optimal + Primal bound 4 + Dual bound 4 + Gap 0% (tolerance: 0.01%) + P-D integral 0 + Solution status feasible + 4 (objective) + 0 (bound viol.) + 0 (int. viol.) + 0 (row viol.) + Timing 0.00 (total) + 0.00 (presolve) + 0.00 (solve) + 0.00 (postsolve) + Max sub-MIP depth 0 + Nodes 0 + Repair LPs 0 (0 feasible; 0 iterations) + LP iterations 0 (total) + 0 (strong br.) + 0 (separation) + 0 (heuristics) +Writing the solution to /tmp/20250624-5715-oukmal.lp.sol +Running HiGHS 1.11.0 (git hash: 364c83a51): Copyright (c) 2025 HiGHS under MIT licence terms +Set option solver to "simplex" +Set option log_file to "HiGHS.log" +Set option solution_file to "/tmp/20250624-5789-b4mrd2.lp.sol" +LP 20250624-5789-b4mrd2 has 2 rows; 3 cols; 5 nonzeros +Coefficient ranges: + Matrix [1e+00, 1e+00] + Cost [1e+00, 3e+00] + Bound [0e+00, 0e+00] + RHS [1e+00, 2e+00] +Presolving model +2 rows, 3 cols, 5 nonzeros 0s +0 rows, 1 cols, 0 nonzeros 0s +0 rows, 0 cols, 0 nonzeros 0s +Presolve : Reductions: rows 0(-2); columns 0(-3); elements 0(-5) - Reduced to empty +Solving the original LP from the solution after postsolve +Model name : 20250624-5789-b4mrd2 +Model status : Optimal +Objective value : 4.0000000000e+00 +P-D objective error : 0.0000000000e+00 +HiGHS run time : 0.00 +Writing the solution to /tmp/20250624-5789-b4mrd2.lp.sol diff --git a/docs/03-solver-configuration.md b/docs/03-solver-configuration.md index 65f0e1d..40486de 100644 --- a/docs/03-solver-configuration.md +++ b/docs/03-solver-configuration.md @@ -29,6 +29,7 @@ m.solver = :cbc # or m.solver = :clp # or m.solver = :cplex # or m.solver = :glpk # or +m.solver = :highs # or m.solver = :scip ``` @@ -40,6 +41,7 @@ By default, RAMS assumes that solvers are available in your system's PATH with t - `RAMS_SOLVER_PATH_CLP` - Override path for CLP (defaults to `clp`) - `RAMS_SOLVER_PATH_CPLEX` - Override path for CPLEX (defaults to `cplex`) - `RAMS_SOLVER_PATH_GLPK` - Override path for GLPK (defaults to `glpsol`) +- `RAMS_SOLVER_PATH_HIGHS` - Override path for HiGHS (defaults to `highs`) - `RAMS_SOLVER_PATH_SCIP` - Override path for SCIP (defaults to `scip`) For example, if you have GLPK installed in a custom location: @@ -54,6 +56,12 @@ Or if you want to use a specific version of CBC: export RAMS_SOLVER_PATH_CBC=/usr/local/bin/cbc-2.10 ``` +Or if you have HiGHS installed in a custom location: + +```bash +export RAMS_SOLVER_PATH_HIGHS=/opt/highs/bin/highs +``` + These environment variables are particularly useful when you have multiple versions of solvers installed or when solvers are installed in non-standard locations. ## Solver Arguments @@ -89,4 +97,12 @@ m.args = ['-c', 'set presolving maxrounds 0'] m.solve ``` +Similarly, if you are using HiGHS, you can set a time limit or choose a specific algorithm: + +```ruby +m.solver = :highs +m.args = ['--time_limit', '10', '--solver', 'simplex'] +m.solve +``` + Every solver has different options, so check the manual to see what command line flags are available to you. diff --git a/lib/rams/model.rb b/lib/rams/model.rb index 88d32dd..996970c 100644 --- a/lib/rams/model.rb +++ b/lib/rams/model.rb @@ -5,6 +5,7 @@ require_relative 'solvers/clp' require_relative 'solvers/cplex' require_relative 'solvers/glpk' +require_relative 'solvers/highs' require_relative 'solvers/scip' require_relative 'variable' @@ -40,6 +41,7 @@ class Model clp: RAMS::Solvers::CLP.new, cplex: RAMS::Solvers::CPLEX.new, glpk: RAMS::Solvers::GLPK.new, + highs: RAMS::Solvers::HiGHS.new, scip: RAMS::Solvers::SCIP.new }.freeze diff --git a/lib/rams/solvers/highs.rb b/lib/rams/solvers/highs.rb new file mode 100644 index 0000000..d34dca1 --- /dev/null +++ b/lib/rams/solvers/highs.rb @@ -0,0 +1,92 @@ +require_relative 'solver' + +module RAMS + module Solvers + # Interface to HiGHS solver + class HiGHS < Solver + def solver_command(model_path, solution_path, args) + [solver_executable('highs', 'highs'), model_path, '--solution_file', solution_path] + args + end + + private + + def parse_status(_model, lines) + status_idx = lines.index { |l| l =~ /^Model status/ } + return :undefined unless status_idx + + # Status is on the next line after "Model status" + status = lines[status_idx + 1] + return :undefined unless status + + return :optimal if status =~ /optimal/i + return :feasible if status =~ /feasible/i + return :infeasible if status =~ /infeasible/i + return :unbounded if status =~ /unbounded/i + :undefined + end + + def parse_objective(_model, lines) + objective_line = lines.find { |l| l =~ /^Objective/ } + return nil unless objective_line + objective_line.split.last.to_f + end + + def parse_primal(model, lines) + # Find the primal section + start_idx = lines.index { |l| l =~ /^# Columns/ } + return {} unless start_idx + + num_columns = lines[start_idx].split.last.to_i + primal_values = {} + + # Parse variable values + (1..num_columns).each do |i| + line = lines[start_idx + i] + next unless line + + parts = line.split + next unless parts.length >= 2 + + var_name = parts[0] + var_value = parts[1].to_f + variable = model.variables[var_name] + primal_values[variable] = var_value if variable + end + + primal_values + end + + def parse_dual(model, lines) + # Find the dual section + dual_start_idx = lines.index { |l| l =~ /^# Dual solution values/ } + return {} unless dual_start_idx + + # Check if dual solution is available + return {} if lines[dual_start_idx + 1] =~ /^None/ + + # Find the rows section within dual values + rows_idx = lines.index { |l| l =~ /^# Rows/ } + return {} unless rows_idx + + num_rows = lines[rows_idx].split.last.to_i + dual_values = {} + + # Parse constraint dual values + (1..num_rows).each do |i| + line = lines[rows_idx + i] + next unless line + + parts = line.split + next unless parts.length >= 2 + + constraint_name = parts[0] + dual_value = parts[1].to_f + constraint = model.constraints[constraint_name] + dual_values[constraint] = dual_value if constraint + end + + dual_values + end + end + end +end \ No newline at end of file diff --git a/tests/test_solver_paths.rb b/tests/test_solver_paths.rb index 164fa5e..0042199 100644 --- a/tests/test_solver_paths.rb +++ b/tests/test_solver_paths.rb @@ -6,7 +6,7 @@ class TestSolverPaths < Test::Unit::TestCase def setup # Clear any existing environment variables @old_env_vars = {} - %w[CBC CLP SCIP CPLEX GLPK].each do |solver| + %w[CBC CLP SCIP CPLEX GLPK HIGHS].each do |solver| env_var = "RAMS_SOLVER_PATH_#{solver}" @old_env_vars[env_var] = ENV[env_var] ENV.delete(env_var) @@ -88,4 +88,17 @@ def test_glpk_custom_solver_command command = solver.solver_command('/path/to/model.lp', '/path/to/solution.sol', []) assert_equal '/custom/path/to/glpsol', command[0] end + + def test_highs_default_solver_command + solver = RAMS::Solvers::HiGHS.new + command = solver.solver_command('/path/to/model.lp', '/path/to/solution.sol', []) + assert_equal 'highs', command[0] + end + + def test_highs_custom_solver_command + ENV['RAMS_SOLVER_PATH_HIGHS'] = '/custom/path/to/highs' + solver = RAMS::Solvers::HiGHS.new + command = solver.solver_command('/path/to/model.lp', '/path/to/solution.sol', []) + assert_equal '/custom/path/to/highs', command[0] + end end \ No newline at end of file From eb8c07ec7c82512a49c911b6646015f846fae089 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Jun 2025 03:40:54 +0000 Subject: [PATCH 3/7] Add HiGHS.log to .gitignore and remove from repository --- .gitignore | 1 + HiGHS.log | 204 ----------------------------------------------------- 2 files changed, 1 insertion(+), 204 deletions(-) delete mode 100644 HiGHS.log diff --git a/.gitignore b/.gitignore index 722d5e7..93e278b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .vscode +HiGHS.log diff --git a/HiGHS.log b/HiGHS.log deleted file mode 100644 index e21a452..0000000 --- a/HiGHS.log +++ /dev/null @@ -1,204 +0,0 @@ -Running HiGHS 1.11.0 (git hash: 364c83a51): Copyright (c) 2025 HiGHS under MIT licence terms -Set option log_file to "HiGHS.log" -Set option solution_file to "/tmp/20250624-5677-ohtpru.lp.sol" -MIP 20250624-5677-ohtpru has 2 rows; 3 cols; 5 nonzeros; 3 integer variables (3 binary) -Coefficient ranges: - Matrix [1e+00, 1e+00] - Cost [1e+00, 3e+00] - Bound [1e+00, 1e+00] - RHS [1e+00, 2e+00] -Presolving model -2 rows, 3 cols, 5 nonzeros 0s -1 rows, 2 cols, 2 nonzeros 0s -0 rows, 0 cols, 0 nonzeros 0s -Presolve: Optimal - -Src: B => Branching; C => Central rounding; F => Feasibility pump; J => Feasibility jump; - H => Heuristic; L => Sub-MIP; P => Empty MIP; R => Randomized rounding; Z => ZI Round; - I => Shifting; S => Solve LP; T => Evaluate node; U => Unbounded; X => User solution; - z => Trivial zero; l => Trivial lower; u => Trivial upper; p => Trivial point - - Nodes | B&B Tree | Objective Bounds | Dynamic Constraints | Work -Src Proc. InQueue | Leaves Expl. | BestBound BestSol Gap | Cuts InLp Confl. | LpIters Time - - 0 0 0 0.00% 4 4 0.00% 0 0 0 0 0.0s - -Solving report - Model 20250624-5677-ohtpru - Status Optimal - Primal bound 4 - Dual bound 4 - Gap 0% (tolerance: 0.01%) - P-D integral 0 - Solution status feasible - 4 (objective) - 0 (bound viol.) - 0 (int. viol.) - 0 (row viol.) - Timing 0.00 (total) - 0.00 (presolve) - 0.00 (solve) - 0.00 (postsolve) - Max sub-MIP depth 0 - Nodes 0 - Repair LPs 0 (0 feasible; 0 iterations) - LP iterations 0 (total) - 0 (strong br.) - 0 (separation) - 0 (heuristics) -Writing the solution to /tmp/20250624-5677-ohtpru.lp.sol -Running HiGHS 1.11.0 (git hash: 364c83a51): Copyright (c) 2025 HiGHS under MIT licence terms -Set option log_file to "HiGHS.log" -Set option solution_file to "/tmp/debug.sol" -LP debug has 2 rows; 3 cols; 5 nonzeros -Coefficient ranges: - Matrix [1e+00, 1e+00] - Cost [1e+00, 3e+00] - Bound [0e+00, 0e+00] - RHS [1e+00, 2e+00] -Presolving model -2 rows, 3 cols, 5 nonzeros 0s -0 rows, 1 cols, 0 nonzeros 0s -0 rows, 0 cols, 0 nonzeros 0s -Presolve : Reductions: rows 0(-2); columns 0(-3); elements 0(-5) - Reduced to empty -Solving the original LP from the solution after postsolve -Model name : debug -Model status : Optimal -Objective value : 4.0000000000e+00 -P-D objective error : 0.0000000000e+00 -HiGHS run time : 0.00 -Writing the solution to /tmp/debug.sol -Running HiGHS 1.11.0 (git hash: 364c83a51): Copyright (c) 2025 HiGHS under MIT licence terms -Set option log_file to "HiGHS.log" -Set option solution_file to "/tmp/20250624-5685-fty4ru.lp.sol" -LP 20250624-5685-fty4ru has 2 rows; 3 cols; 5 nonzeros -Coefficient ranges: - Matrix [1e+00, 1e+00] - Cost [1e+00, 3e+00] - Bound [0e+00, 0e+00] - RHS [1e+00, 2e+00] -Presolving model -2 rows, 3 cols, 5 nonzeros 0s -0 rows, 1 cols, 0 nonzeros 0s -0 rows, 0 cols, 0 nonzeros 0s -Presolve : Reductions: rows 0(-2); columns 0(-3); elements 0(-5) - Reduced to empty -Solving the original LP from the solution after postsolve -Model name : 20250624-5685-fty4ru -Model status : Optimal -Objective value : 4.0000000000e+00 -P-D objective error : 0.0000000000e+00 -HiGHS run time : 0.00 -Writing the solution to /tmp/20250624-5685-fty4ru.lp.sol -Running HiGHS 1.11.0 (git hash: 364c83a51): Copyright (c) 2025 HiGHS under MIT licence terms -Set option log_file to "HiGHS.log" -Set option solution_file to "/tmp/debug.sol" -LP debug has 2 rows; 3 cols; 5 nonzeros -Coefficient ranges: - Matrix [1e+00, 1e+00] - Cost [1e+00, 3e+00] - Bound [0e+00, 0e+00] - RHS [1e+00, 2e+00] -Presolving model -2 rows, 3 cols, 5 nonzeros 0s -0 rows, 1 cols, 0 nonzeros 0s -0 rows, 0 cols, 0 nonzeros 0s -Presolve : Reductions: rows 0(-2); columns 0(-3); elements 0(-5) - Reduced to empty -Solving the original LP from the solution after postsolve -Model name : debug -Model status : Optimal -Objective value : 4.0000000000e+00 -P-D objective error : 0.0000000000e+00 -HiGHS run time : 0.00 -Writing the solution to /tmp/debug.sol -Running HiGHS 1.11.0 (git hash: 364c83a51): Copyright (c) 2025 HiGHS under MIT licence terms -Set option log_file to "HiGHS.log" -Set option solution_file to "/tmp/20250624-5703-fbex79.lp.sol" -LP 20250624-5703-fbex79 has 2 rows; 3 cols; 5 nonzeros -Coefficient ranges: - Matrix [1e+00, 1e+00] - Cost [1e+00, 3e+00] - Bound [0e+00, 0e+00] - RHS [1e+00, 2e+00] -Presolving model -2 rows, 3 cols, 5 nonzeros 0s -0 rows, 1 cols, 0 nonzeros 0s -0 rows, 0 cols, 0 nonzeros 0s -Presolve : Reductions: rows 0(-2); columns 0(-3); elements 0(-5) - Reduced to empty -Solving the original LP from the solution after postsolve -Model name : 20250624-5703-fbex79 -Model status : Optimal -Objective value : 4.0000000000e+00 -P-D objective error : 0.0000000000e+00 -HiGHS run time : 0.00 -Writing the solution to /tmp/20250624-5703-fbex79.lp.sol -Running HiGHS 1.11.0 (git hash: 364c83a51): Copyright (c) 2025 HiGHS under MIT licence terms -Set option log_file to "HiGHS.log" -Set option solution_file to "/tmp/20250624-5715-oukmal.lp.sol" -MIP 20250624-5715-oukmal has 2 rows; 3 cols; 5 nonzeros; 3 integer variables (3 binary) -Coefficient ranges: - Matrix [1e+00, 1e+00] - Cost [1e+00, 3e+00] - Bound [1e+00, 1e+00] - RHS [1e+00, 2e+00] -Presolving model -2 rows, 3 cols, 5 nonzeros 0s -1 rows, 2 cols, 2 nonzeros 0s -0 rows, 0 cols, 0 nonzeros 0s -Presolve: Optimal - -Src: B => Branching; C => Central rounding; F => Feasibility pump; J => Feasibility jump; - H => Heuristic; L => Sub-MIP; P => Empty MIP; R => Randomized rounding; Z => ZI Round; - I => Shifting; S => Solve LP; T => Evaluate node; U => Unbounded; X => User solution; - z => Trivial zero; l => Trivial lower; u => Trivial upper; p => Trivial point - - Nodes | B&B Tree | Objective Bounds | Dynamic Constraints | Work -Src Proc. InQueue | Leaves Expl. | BestBound BestSol Gap | Cuts InLp Confl. | LpIters Time - - 0 0 0 0.00% 4 4 0.00% 0 0 0 0 0.0s - -Solving report - Model 20250624-5715-oukmal - Status Optimal - Primal bound 4 - Dual bound 4 - Gap 0% (tolerance: 0.01%) - P-D integral 0 - Solution status feasible - 4 (objective) - 0 (bound viol.) - 0 (int. viol.) - 0 (row viol.) - Timing 0.00 (total) - 0.00 (presolve) - 0.00 (solve) - 0.00 (postsolve) - Max sub-MIP depth 0 - Nodes 0 - Repair LPs 0 (0 feasible; 0 iterations) - LP iterations 0 (total) - 0 (strong br.) - 0 (separation) - 0 (heuristics) -Writing the solution to /tmp/20250624-5715-oukmal.lp.sol -Running HiGHS 1.11.0 (git hash: 364c83a51): Copyright (c) 2025 HiGHS under MIT licence terms -Set option solver to "simplex" -Set option log_file to "HiGHS.log" -Set option solution_file to "/tmp/20250624-5789-b4mrd2.lp.sol" -LP 20250624-5789-b4mrd2 has 2 rows; 3 cols; 5 nonzeros -Coefficient ranges: - Matrix [1e+00, 1e+00] - Cost [1e+00, 3e+00] - Bound [0e+00, 0e+00] - RHS [1e+00, 2e+00] -Presolving model -2 rows, 3 cols, 5 nonzeros 0s -0 rows, 1 cols, 0 nonzeros 0s -0 rows, 0 cols, 0 nonzeros 0s -Presolve : Reductions: rows 0(-2); columns 0(-3); elements 0(-5) - Reduced to empty -Solving the original LP from the solution after postsolve -Model name : 20250624-5789-b4mrd2 -Model status : Optimal -Objective value : 4.0000000000e+00 -P-D objective error : 0.0000000000e+00 -HiGHS run time : 0.00 -Writing the solution to /tmp/20250624-5789-b4mrd2.lp.sol From bfe6668226736dc3da0ff19b592604263018e032 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Jun 2025 03:53:08 +0000 Subject: [PATCH 4/7] Add HiGHS solver support to GitHub Actions workflow and tests Co-authored-by: ryanjoneil <6748953+ryanjoneil@users.noreply.github.com> --- .github/workflows/test.yml | 3 ++- tests/test_model.rb | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 36a5442..13a4d51 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,6 +15,7 @@ jobs: RAMS_TEST_CBC: true RAMS_TEST_CLP: true RAMS_TEST_GLPK: true + RAMS_TEST_HIGHS: true RAMS_TEST_SCIP: true steps: @@ -32,7 +33,7 @@ jobs: - name: Install optimization solvers run: | - dnf install -y coin-or-Cbc coin-or-Clp glpk-utils scip + dnf install -y coin-or-Cbc coin-or-Clp coin-or-HiGHS glpk-utils scip - name: Run tests run: bundle exec rake test diff --git a/tests/test_model.rb b/tests/test_model.rb index 331b682..cb31e66 100644 --- a/tests/test_model.rb +++ b/tests/test_model.rb @@ -9,12 +9,14 @@ def test_simple run_test_simple :clp if ENV['RAMS_TEST_CLP'] run_test_simple :cplex if ENV['RAMS_TEST_CPLEX'] run_test_simple :glpk if ENV['RAMS_TEST_GLPK'] + run_test_simple :highs if ENV['RAMS_TEST_HIGHS'] end def test_binary run_test_binary :cbc if ENV['RAMS_TEST_CBC'] run_test_binary :cplex if ENV['RAMS_TEST_CPLEX'] run_test_binary :glpk if ENV['RAMS_TEST_GLPK'] + run_test_binary :highs if ENV['RAMS_TEST_HIGHS'] run_test_binary :scip if ENV['RAMS_TEST_SCIP'] end @@ -22,6 +24,7 @@ def test_integer run_test_integer :cbc if ENV['RAMS_TEST_CBC'] run_test_integer :cplex if ENV['RAMS_TEST_CPLEX'] run_test_integer :glpk if ENV['RAMS_TEST_GLPK'] + run_test_integer :highs if ENV['RAMS_TEST_HIGHS'] run_test_integer :scip if ENV['RAMS_TEST_SCIP'] end @@ -30,6 +33,7 @@ def test_infeasible run_test_infeasible :clp if ENV['RAMS_TEST_CLP'] run_test_infeasible :cplex if ENV['RAMS_TEST_CPLEX'] run_test_infeasible :glpk if ENV['RAMS_TEST_GLPK'] + run_test_infeasible :highs if ENV['RAMS_TEST_HIGHS'] run_test_infeasible :scip if ENV['RAMS_TEST_SCIP'] end @@ -38,6 +42,7 @@ def test_unbounded run_test_unbounded :clp if ENV['RAMS_TEST_CLP'] run_test_unbounded :cplex if ENV['RAMS_TEST_CPLEX'] run_test_unbounded :glpk if ENV['RAMS_TEST_GLPK'] + run_test_unbounded :highs if ENV['RAMS_TEST_HIGHS'] run_test_unbounded :scip if ENV['RAMS_TEST_SCIP'] end @@ -45,6 +50,7 @@ def test_implication run_test_implication :cbc if ENV['RAMS_TEST_CBC'] run_test_implication :cplex if ENV['RAMS_TEST_CPLEX'] run_test_implication :glpk if ENV['RAMS_TEST_GLPK'] + run_test_implication :highs if ENV['RAMS_TEST_HIGHS'] run_test_implication :scip if ENV['RAMS_TEST_SCIP'] end From bdf648945dc43a243f9de87bcdd3e7ee42dba6ba Mon Sep 17 00:00:00 2001 From: Ryan O'Neil <6748953+ryanjoneil@users.noreply.github.com> Date: Mon, 23 Jun 2025 23:57:30 -0400 Subject: [PATCH 5/7] Version bump --- rams.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rams.gemspec b/rams.gemspec index 353413a..e98a608 100644 --- a/rams.gemspec +++ b/rams.gemspec @@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) Gem::Specification.new do |spec| spec.name = 'rams' - spec.version = '0.1.7' + spec.version = '0.1.8' spec.authors = ["Ryan J. O'Neil"] spec.email = ['ryanjoneil@gmail.com'] spec.summary = 'Ruby Algebraic Modeling System' From 18e8095e97edba8198e1830e4a2a86960975a964 Mon Sep 17 00:00:00 2001 From: Ryan O'Neil <6748953+ryanjoneil@users.noreply.github.com> Date: Tue, 24 Jun 2025 11:57:55 -0400 Subject: [PATCH 6/7] Fix HiGHS solution parsing --- lib/rams/solvers/highs.rb | 24 ++++++++++--------- tests/test_model.rb | 49 ++++++++++++++++++++++++++++++++------- 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/lib/rams/solvers/highs.rb b/lib/rams/solvers/highs.rb index d34dca1..94bfe5d 100644 --- a/lib/rams/solvers/highs.rb +++ b/lib/rams/solvers/highs.rb @@ -13,13 +13,13 @@ def solver_command(model_path, solution_path, args) def parse_status(_model, lines) status_idx = lines.index { |l| l =~ /^Model status/ } return :undefined unless status_idx - + # Status is on the next line after "Model status" status = lines[status_idx + 1] return :undefined unless status - + return :optimal if status =~ /optimal/i - return :feasible if status =~ /feasible/i + return :feasible if status =~ /^feasible/i return :infeasible if status =~ /infeasible/i return :unbounded if status =~ /unbounded/i :undefined @@ -38,21 +38,21 @@ def parse_primal(model, lines) num_columns = lines[start_idx].split.last.to_i primal_values = {} - + # Parse variable values (1..num_columns).each do |i| line = lines[start_idx + i] next unless line - + parts = line.split next unless parts.length >= 2 - + var_name = parts[0] var_value = parts[1].to_f variable = model.variables[var_name] primal_values[variable] = var_value if variable end - + primal_values end @@ -61,7 +61,9 @@ def parse_dual(model, lines) dual_start_idx = lines.index { |l| l =~ /^# Dual solution values/ } return {} unless dual_start_idx + # Check if dual solution is available + lines = lines[dual_start_idx..-1] return {} if lines[dual_start_idx + 1] =~ /^None/ # Find the rows section within dual values @@ -70,21 +72,21 @@ def parse_dual(model, lines) num_rows = lines[rows_idx].split.last.to_i dual_values = {} - + # Parse constraint dual values (1..num_rows).each do |i| line = lines[rows_idx + i] next unless line - + parts = line.split next unless parts.length >= 2 - + constraint_name = parts[0] dual_value = parts[1].to_f constraint = model.constraints[constraint_name] dual_values[constraint] = dual_value if constraint end - + dual_values end end diff --git a/tests/test_model.rb b/tests/test_model.rb index cb31e66..ef2fc3d 100644 --- a/tests/test_model.rb +++ b/tests/test_model.rb @@ -4,14 +4,24 @@ # RAMS::Model tests # rubocop:disable ClassLength class TestModel < Test::Unit::TestCase - def test_simple - run_test_simple :cbc if ENV['RAMS_TEST_CBC'] - run_test_simple :clp if ENV['RAMS_TEST_CLP'] - run_test_simple :cplex if ENV['RAMS_TEST_CPLEX'] - run_test_simple :glpk if ENV['RAMS_TEST_GLPK'] - run_test_simple :highs if ENV['RAMS_TEST_HIGHS'] + def test_simple_primal + run_test_simple_primal :cbc if ENV['RAMS_TEST_CBC'] + run_test_simple_primal :clp if ENV['RAMS_TEST_CLP'] + run_test_simple_primal :cplex if ENV['RAMS_TEST_CPLEX'] + run_test_simple_primal :glpk if ENV['RAMS_TEST_GLPK'] + run_test_simple_primal :highs if ENV['RAMS_TEST_HIGHS'] + run_test_simple_primal :scip if ENV['RAMS_TEST_SCIP'] end + def test_simple_dual + run_test_simple_dual :cbc if ENV['RAMS_TEST_CBC'] + run_test_simple_dual :clp if ENV['RAMS_TEST_CLP'] + run_test_simple_dual :cplex if ENV['RAMS_TEST_CPLEX'] + run_test_simple_dual :glpk if ENV['RAMS_TEST_GLPK'] + run_test_simple_dual :highs if ENV['RAMS_TEST_HIGHS'] + end + + def test_binary run_test_binary :cbc if ENV['RAMS_TEST_CBC'] run_test_binary :cplex if ENV['RAMS_TEST_CPLEX'] @@ -55,7 +65,7 @@ def test_implication end # rubocop:disable MethodLength - def run_test_simple(solver, args = []) + def run_test_simple_primal(solver, args = []) m = RAMS::Model.new m.solver = solver m.args = args @@ -63,7 +73,7 @@ def run_test_simple(solver, args = []) x1 = m.variable low: 0.5 x2 = m.variable - c = m.constrain(x1 + x2 <= 1) + m.constrain(x1 + x2 <= 1) m.sense = :max m.objective = x1 + (2 * x2) @@ -73,6 +83,27 @@ def run_test_simple(solver, args = []) assert_in_delta 1.5, solution.objective, 10e-7 assert_in_delta 0.5, solution[x1], 10e-7 assert_in_delta 0.5, solution[x2], 10e-7 + end + # rubocop:enable MethodLength + + + # rubocop:disable MethodLength + def run_test_simple_dual(solver, args = []) + m = RAMS::Model.new + m.solver = solver + m.args = args + + x1 = m.variable low: 0.5 + x2 = m.variable + + c = m.constrain(x1 + x2 <= 1) + + m.sense = :max + m.objective = x1 + (2 * x2) + solution = m.solve + + assert_equal :optimal, solution.status + assert_in_delta 1.5, solution.objective, 10e-7 assert_in_delta 2.0, solution.dual[c], 10e-7 end # rubocop:enable MethodLength @@ -153,7 +184,7 @@ def run_test_unbounded(solver, args = []) m.objective = x solution = m.solve - assert_includes [:unbounded, :undefined], solution.status + assert_includes [:infeasible, :unbounded, :undefined], solution.status end # rubocop:disable MethodLength From 9f128db069a5460dddc12d38809aa8e711ac037a Mon Sep 17 00:00:00 2001 From: Ryan O'Neil <6748953+ryanjoneil@users.noreply.github.com> Date: Tue, 24 Jun 2025 12:02:03 -0400 Subject: [PATCH 7/7] Removes non-OSS solvers --- README.md | 6 ++-- docs/01-quickstart.md | 6 ++-- docs/02-modeling-primitives.md | 6 +++- docs/03-solver-configuration.md | 3 +- lib/rams/model.rb | 2 -- lib/rams/solvers/cplex.rb | 55 --------------------------------- tests/test_model.rb | 7 ----- tests/test_solver_paths.rb | 15 +-------- 8 files changed, 14 insertions(+), 86 deletions(-) delete mode 100644 lib/rams/solvers/cplex.rb diff --git a/README.md b/README.md index e6e5d42..436beba 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Ruby Algebraic Modeling System -RAMS is a library for formulating and solving [Mixed Integer Linear Programs](https://en.wikipedia.org/wiki/Integer_programming) in Ruby. Currently it supports the following solvers: +RAMS is a library for formulating and solving [Mixed Integer Linear Programs](https://en.wikipedia.org/wiki/Integer_programming) in Ruby. Currently it supports the following open source solvers: * [CLP](https://www.coin-or.org/Clp/) * [CBC](https://www.coin-or.org/Cbc/) -* [CPLEX](https://www-01.ibm.com/software/commerce/optimization/cplex-optimizer/) * [GNU Linear Programming Kit](https://www.gnu.org/software/glpk/) -* [SCIP](http://scip.zib.de) +* [HiGHS](https://highs.dev/) +* [SCIP](https://www.scipopt.org/) ## Documentation diff --git a/docs/01-quickstart.md b/docs/01-quickstart.md index 1de1f2f..0aae770 100644 --- a/docs/01-quickstart.md +++ b/docs/01-quickstart.md @@ -2,8 +2,8 @@ ## Installation -RAMS assumes you have the solver you're using in your `PATH`. The default solver is [GLPK]((https://www.gnu.org/software/glpk/)), but you can also use [CLP](https://www.coin-or.org/Clp/), [CBC](https://www.coin-or.org/Cbc/), [CPLEX](https://www-01.ibm.com/software/commerce/optimization/cplex-optimizer/), -and [SCIP](http://scip.zib.de). +RAMS assumes you have the solver you're using in your `PATH`. The default solver is [GLPK]((https://www.gnu.org/software/glpk/)), but you can also use [CLP](https://www.coin-or.org/Clp/), [CBC](https://www.coin-or.org/Cbc/), [HiGHS](https://highs.dev), +and [SCIP](https://scipopt.org). First make sure you have the latest RAMS installed. @@ -14,11 +14,13 @@ gem install rams Now install GLPK or whatever solver you wish. ### Ubuntu + ``` sudo apt-get install glpk-utils ``` ### Mac OSX + ``` brew install glpk ``` diff --git a/docs/02-modeling-primitives.md b/docs/02-modeling-primitives.md index 14123e3..132cf0c 100644 --- a/docs/02-modeling-primitives.md +++ b/docs/02-modeling-primitives.md @@ -23,6 +23,7 @@ By default, a continuous variable has a lower bound of `0` and an upper bound of ```ruby puts "#{m.variables.values.map { |x| [x.low, x.high ]}}" ``` + ``` [[0.0, nil], [0.0, nil], [0.0, nil]] ``` @@ -33,11 +34,12 @@ To set a variable's lower bound to negative infinity, pass a `low: inf` keyword x4 = m.variable(type: :integer, low: nil, high: 10) ``` -The binary variables may appear to have an upper bound of positive infinity, but that becomes `1` when it is written to the solver. To see a model the way it is passed to a solver, use the `to_lp` method. This returns the model in [LP format](http://lpsolve.sourceforge.net/5.0/CPLEX-format.htm). Note that the variable names are different in the `to_lp` output. +The binary variables may appear to have an upper bound of positive infinity, but that becomes `1` when it is written to the solver. To see a model the way it is passed to a solver, use the `to_lp` method. This returns the model in [LP format](https://lpsolve.sourceforge.net/5.0/CPLEX-format.htm). Note that the variable names are different in the `to_lp` output. ```ruby puts m.to_lp ``` + ``` max obj: 0 v1 @@ -75,6 +77,7 @@ puts <<-HERE #{c3.lhs[x2]} * x2 + #{c3.lhs[x3]} #{c3.sense} #{c3.rhs} HERE ``` + ``` 2.0 * x1 + 0.5 * x2 <= 5.0 1.0 * x2 + 1.0 * x3 + 1.0 * x4 >= 2.0 @@ -101,6 +104,7 @@ x = #{[x1, x2, x3, x4].map { |x| solution[x] }} y = #{[c1, c2, c3].map { |c| solution.dual[c] }} HERE ``` + ``` z = 10.0 x = [2.0, 2.0, 1.0, -1.0] diff --git a/docs/03-solver-configuration.md b/docs/03-solver-configuration.md index 40486de..27c9892 100644 --- a/docs/03-solver-configuration.md +++ b/docs/03-solver-configuration.md @@ -27,7 +27,6 @@ If you want to switch to a different solver, install that solver onto your syste ```ruby m.solver = :cbc # or m.solver = :clp # or -m.solver = :cplex # or m.solver = :glpk # or m.solver = :highs # or m.solver = :scip @@ -39,7 +38,6 @@ By default, RAMS assumes that solvers are available in your system's PATH with t - `RAMS_SOLVER_PATH_CBC` - Override path for CBC (defaults to `coin.cbc`) - `RAMS_SOLVER_PATH_CLP` - Override path for CLP (defaults to `clp`) -- `RAMS_SOLVER_PATH_CPLEX` - Override path for CPLEX (defaults to `cplex`) - `RAMS_SOLVER_PATH_GLPK` - Override path for GLPK (defaults to `glpsol`) - `RAMS_SOLVER_PATH_HIGHS` - Override path for HiGHS (defaults to `highs`) - `RAMS_SOLVER_PATH_SCIP` - Override path for SCIP (defaults to `scip`) @@ -72,6 +70,7 @@ Additional solver arguments can be passed as though they are command line flags. m.args = ['--dfs', '--bib'] m.solve ``` + ``` GLPSOL: GLPK LP/MIP Solver, v4.60 Parameter(s) specified in the command line: diff --git a/lib/rams/model.rb b/lib/rams/model.rb index 996970c..c41a950 100644 --- a/lib/rams/model.rb +++ b/lib/rams/model.rb @@ -3,7 +3,6 @@ require_relative 'formatters/lp' require_relative 'solvers/cbc' require_relative 'solvers/clp' -require_relative 'solvers/cplex' require_relative 'solvers/glpk' require_relative 'solvers/highs' require_relative 'solvers/scip' @@ -39,7 +38,6 @@ class Model SOLVERS = { cbc: RAMS::Solvers::CBC.new, clp: RAMS::Solvers::CLP.new, - cplex: RAMS::Solvers::CPLEX.new, glpk: RAMS::Solvers::GLPK.new, highs: RAMS::Solvers::HiGHS.new, scip: RAMS::Solvers::SCIP.new diff --git a/lib/rams/solvers/cplex.rb b/lib/rams/solvers/cplex.rb deleted file mode 100644 index 70c38b1..0000000 --- a/lib/rams/solvers/cplex.rb +++ /dev/null @@ -1,55 +0,0 @@ -require 'nokogiri' -require_relative 'solver' - -module RAMS - module Solvers - # Interface to CPLEX - class CPLEX < Solver - def solve_and_parse(model, model_path, solution_path) - call_solver model, model_path, solution_path - return RAMS::Solution.new(:infeasible, nil, {}, {}) unless File.exist? solution_path - parse_solution model, File.read(solution_path) - end - - def solver_command(model_path, solution_path, args) - [solver_executable('cplex', 'cplex'), '-c', "read #{model_path}"] + args + ['optimize', "write #{solution_path}"] - end - - private - - def parse_solution(model, solution_text) - xml_doc = Nokogiri::XML solution_text - RAMS::Solution.new( - parse_status(model, xml_doc), - parse_objective(model, xml_doc), - parse_primal(model, xml_doc), - parse_dual(model, xml_doc) - ) - end - - def parse_status(_model, xml_doc) - status = xml_doc.css('CPLEXSolution').css('header').first['solutionStatusString'] - return :optimal if status =~ /optimal/i - return :feasible if status =~ /feasible/i - return :unbounded if status =~ /unbounded/i - :unknown - end - - def parse_objective(_model, xml_doc) - xml_doc.css('CPLEXSolution').css('header').first['objectiveValue'].to_f - end - - def parse_primal(model, xml_doc) - xml_doc.css('CPLEXSolution').css('variables').css('variable').map do |v| - [model.variables[v['name']], v['value'].to_f] - end.to_h - end - - def parse_dual(model, xml_doc) - xml_doc.css('CPLEXSolution').css('linearConstraints').css('constraint').map do |c| - [model.constraints[c['name']], c['dual'].to_f] - end.to_h - end - end - end -end diff --git a/tests/test_model.rb b/tests/test_model.rb index ef2fc3d..0192317 100644 --- a/tests/test_model.rb +++ b/tests/test_model.rb @@ -7,7 +7,6 @@ class TestModel < Test::Unit::TestCase def test_simple_primal run_test_simple_primal :cbc if ENV['RAMS_TEST_CBC'] run_test_simple_primal :clp if ENV['RAMS_TEST_CLP'] - run_test_simple_primal :cplex if ENV['RAMS_TEST_CPLEX'] run_test_simple_primal :glpk if ENV['RAMS_TEST_GLPK'] run_test_simple_primal :highs if ENV['RAMS_TEST_HIGHS'] run_test_simple_primal :scip if ENV['RAMS_TEST_SCIP'] @@ -16,7 +15,6 @@ def test_simple_primal def test_simple_dual run_test_simple_dual :cbc if ENV['RAMS_TEST_CBC'] run_test_simple_dual :clp if ENV['RAMS_TEST_CLP'] - run_test_simple_dual :cplex if ENV['RAMS_TEST_CPLEX'] run_test_simple_dual :glpk if ENV['RAMS_TEST_GLPK'] run_test_simple_dual :highs if ENV['RAMS_TEST_HIGHS'] end @@ -24,7 +22,6 @@ def test_simple_dual def test_binary run_test_binary :cbc if ENV['RAMS_TEST_CBC'] - run_test_binary :cplex if ENV['RAMS_TEST_CPLEX'] run_test_binary :glpk if ENV['RAMS_TEST_GLPK'] run_test_binary :highs if ENV['RAMS_TEST_HIGHS'] run_test_binary :scip if ENV['RAMS_TEST_SCIP'] @@ -32,7 +29,6 @@ def test_binary def test_integer run_test_integer :cbc if ENV['RAMS_TEST_CBC'] - run_test_integer :cplex if ENV['RAMS_TEST_CPLEX'] run_test_integer :glpk if ENV['RAMS_TEST_GLPK'] run_test_integer :highs if ENV['RAMS_TEST_HIGHS'] run_test_integer :scip if ENV['RAMS_TEST_SCIP'] @@ -41,7 +37,6 @@ def test_integer def test_infeasible run_test_infeasible :cbc if ENV['RAMS_TEST_CBC'] run_test_infeasible :clp if ENV['RAMS_TEST_CLP'] - run_test_infeasible :cplex if ENV['RAMS_TEST_CPLEX'] run_test_infeasible :glpk if ENV['RAMS_TEST_GLPK'] run_test_infeasible :highs if ENV['RAMS_TEST_HIGHS'] run_test_infeasible :scip if ENV['RAMS_TEST_SCIP'] @@ -50,7 +45,6 @@ def test_infeasible def test_unbounded run_test_unbounded :cbc if ENV['RAMS_TEST_CBC'] run_test_unbounded :clp if ENV['RAMS_TEST_CLP'] - run_test_unbounded :cplex if ENV['RAMS_TEST_CPLEX'] run_test_unbounded :glpk if ENV['RAMS_TEST_GLPK'] run_test_unbounded :highs if ENV['RAMS_TEST_HIGHS'] run_test_unbounded :scip if ENV['RAMS_TEST_SCIP'] @@ -58,7 +52,6 @@ def test_unbounded def test_implication run_test_implication :cbc if ENV['RAMS_TEST_CBC'] - run_test_implication :cplex if ENV['RAMS_TEST_CPLEX'] run_test_implication :glpk if ENV['RAMS_TEST_GLPK'] run_test_implication :highs if ENV['RAMS_TEST_HIGHS'] run_test_implication :scip if ENV['RAMS_TEST_SCIP'] diff --git a/tests/test_solver_paths.rb b/tests/test_solver_paths.rb index 0042199..d932dfc 100644 --- a/tests/test_solver_paths.rb +++ b/tests/test_solver_paths.rb @@ -6,7 +6,7 @@ class TestSolverPaths < Test::Unit::TestCase def setup # Clear any existing environment variables @old_env_vars = {} - %w[CBC CLP SCIP CPLEX GLPK HIGHS].each do |solver| + %w[CBC CLP SCIP GLPK HIGHS].each do |solver| env_var = "RAMS_SOLVER_PATH_#{solver}" @old_env_vars[env_var] = ENV[env_var] ENV.delete(env_var) @@ -63,19 +63,6 @@ def test_scip_custom_solver_command assert_equal '/custom/path/to/scip', command[0] end - def test_cplex_default_solver_command - solver = RAMS::Solvers::CPLEX.new - command = solver.solver_command('/path/to/model.lp', '/path/to/solution.sol', []) - assert_equal 'cplex', command[0] - end - - def test_cplex_custom_solver_command - ENV['RAMS_SOLVER_PATH_CPLEX'] = '/custom/path/to/cplex' - solver = RAMS::Solvers::CPLEX.new - command = solver.solver_command('/path/to/model.lp', '/path/to/solution.sol', []) - assert_equal '/custom/path/to/cplex', command[0] - end - def test_glpk_default_solver_command solver = RAMS::Solvers::GLPK.new command = solver.solver_command('/path/to/model.lp', '/path/to/solution.sol', [])