Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/03-solver-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,30 @@ m.solver = :glpk # or
m.solver = :scip
```

## Solver Path Customization

By default, RAMS assumes that solvers are available in your system's PATH with their standard names. However, you can customize the path or name for any solver using environment variables:

- `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_SCIP` - Override path for SCIP (defaults to `scip`)

For example, if you have GLPK installed in a custom location:

```bash
export RAMS_SOLVER_PATH_GLPK=/opt/glpk/bin/glpsol
```

Or if you want to use a specific version of CBC:

```bash
export RAMS_SOLVER_PATH_CBC=/usr/local/bin/cbc-2.10
```

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

Additional solver arguments can be passed as though they are command line flags. The following adds both `--dfs` and `--bib` arguments to the GLPK invocation.
Expand Down
2 changes: 1 addition & 1 deletion lib/rams/solvers/cbc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Solvers
# Interface to COIN-OR Branch-and-Cut
class CBC < Solver
def solver_command(model_path, solution_path, args)
['coin.cbc', model_path] + args + ['printingOptions', 'all', 'solve', 'solution', solution_path]
[solver_executable('coin.cbc', 'cbc'), model_path] + args + ['printingOptions', 'all', 'solve', 'solution', solution_path]
end

private
Expand Down
2 changes: 1 addition & 1 deletion lib/rams/solvers/clp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Solvers
# Interface to COIN-OR Linear Programming
class CLP < Solver
def solver_command(model_path, solution_path, args)
['clp', model_path] + args + ['printingOptions', 'all', 'solve', 'solution', solution_path]
[solver_executable('clp', 'clp'), model_path] + args + ['printingOptions', 'all', 'solve', 'solution', solution_path]
end

private
Expand Down
2 changes: 1 addition & 1 deletion lib/rams/solvers/cplex.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def solve_and_parse(model, model_path, solution_path)
end

def solver_command(model_path, solution_path, args)
['cplex', '-c', "read #{model_path}"] + args + ['optimize', "write #{solution_path}"]
[solver_executable('cplex', 'cplex'), '-c', "read #{model_path}"] + args + ['optimize', "write #{solution_path}"]
end

private
Expand Down
2 changes: 1 addition & 1 deletion lib/rams/solvers/glpk.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Solvers
# Interface to the GNU Linear Programming Kit
class GLPK < Solver
def solver_command(model_path, solution_path, args)
['glpsol', '--lp', model_path, '--output', solution_path] + args
[solver_executable('glpsol', 'glpk'), '--lp', model_path, '--output', solution_path] + args
end

private
Expand Down
2 changes: 1 addition & 1 deletion lib/rams/solvers/scip.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def solve_and_parse(model, model_path, solution_path)
end

def solver_command(model_path, _solution_path, args)
['scip', '-c', "read #{model_path}"] + args +
[solver_executable('scip', 'scip'), '-c', "read #{model_path}"] + args +
['-c', 'optimize', '-c', 'display solution', '-c', 'display dualsolution', '-c', 'quit']
end

Expand Down
5 changes: 5 additions & 0 deletions lib/rams/solvers/solver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ def solver_command(_model_file, _solution_path, _args)
raise NotImplementedError
end

Copy link

Copilot AI Jun 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The helper method solver_executable is currently public. Consider moving it under a private section so it isn’t exposed as part of the public API.

Suggested change
private
def write_model_file(model)
model_file = Tempfile.new ['', '.lp']
model_file.write model.to_lp
model_file.close
model_file
end
def get_solution(model, model_path)
solution_path = model_path + '.sol'
begin
solve_and_parse model, model_path, solution_path
ensure
File.delete(solution_path) if File.exist?(solution_path)
end
end
def solve_and_parse(model, model_path, solution_path)
call_solver model, model_path, solution_path
return RAMS::Solution.new(:unknown, nil, {}, {}) unless File.exist? solution_path
parse_solution model, File.read(solution_path)
end
# rubocop:disable MethodLength
def call_solver(model, model_path, solution_path)
command = solver_command(model_path, solution_path, model.args)
_, stdout, stderr, exit_code = Open3.popen3(*command)
begin
output = stdout.gets(nil) || ''
error = output + (stderr.gets(nil) || '')
puts output if model.verbose && output != ''
raise error unless exit_code.value == 0
return output
ensure
stdout.close
stderr.close
end
end
# rubocop:enable MethodLength
def solver_command(_model_file, _solution_path, _args)
raise NotImplementedError
end

Copilot uses AI. Check for mistakes.
# Get solver executable name, checking environment variable first
def solver_executable(default_name, solver_name)
ENV["RAMS_SOLVER_PATH_#{solver_name.upcase}"] || default_name
end

def parse_solution(model, solution_text)
lines = solution_text.split "\n"
RAMS::Solution.new(
Expand Down
91 changes: 91 additions & 0 deletions tests/test_solver_paths.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
require './lib/rams.rb'
require 'test/unit'

# Test for solver path environment variable customization
class TestSolverPaths < Test::Unit::TestCase
def setup
# Clear any existing environment variables
@old_env_vars = {}
%w[CBC CLP SCIP CPLEX GLPK].each do |solver|
env_var = "RAMS_SOLVER_PATH_#{solver}"
@old_env_vars[env_var] = ENV[env_var]
ENV.delete(env_var)
end
end

def teardown
# Restore original environment variables
@old_env_vars.each do |key, value|
if value
ENV[key] = value
else
ENV.delete(key)
end
end
end

def test_cbc_default_solver_command
solver = RAMS::Solvers::CBC.new
command = solver.solver_command('/path/to/model.lp', '/path/to/solution.sol', [])
assert_equal 'coin.cbc', command[0]
end

def test_cbc_custom_solver_command
ENV['RAMS_SOLVER_PATH_CBC'] = '/custom/path/to/cbc'
solver = RAMS::Solvers::CBC.new
command = solver.solver_command('/path/to/model.lp', '/path/to/solution.sol', [])
assert_equal '/custom/path/to/cbc', command[0]
end

def test_clp_default_solver_command
solver = RAMS::Solvers::CLP.new
command = solver.solver_command('/path/to/model.lp', '/path/to/solution.sol', [])
assert_equal 'clp', command[0]
end

def test_clp_custom_solver_command
ENV['RAMS_SOLVER_PATH_CLP'] = '/custom/path/to/clp'
solver = RAMS::Solvers::CLP.new
command = solver.solver_command('/path/to/model.lp', '/path/to/solution.sol', [])
assert_equal '/custom/path/to/clp', command[0]
end

def test_scip_default_solver_command
solver = RAMS::Solvers::SCIP.new
command = solver.solver_command('/path/to/model.lp', '/path/to/solution.sol', [])
assert_equal 'scip', command[0]
end

def test_scip_custom_solver_command
ENV['RAMS_SOLVER_PATH_SCIP'] = '/custom/path/to/scip'
solver = RAMS::Solvers::SCIP.new
command = solver.solver_command('/path/to/model.lp', '/path/to/solution.sol', [])
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', [])
assert_equal 'glpsol', command[0]
end

def test_glpk_custom_solver_command
ENV['RAMS_SOLVER_PATH_GLPK'] = '/custom/path/to/glpsol'
solver = RAMS::Solvers::GLPK.new
command = solver.solver_command('/path/to/model.lp', '/path/to/solution.sol', [])
assert_equal '/custom/path/to/glpsol', command[0]
end
end