diff --git a/docs/03-solver-configuration.md b/docs/03-solver-configuration.md index ea137d2..65f0e1d 100644 --- a/docs/03-solver-configuration.md +++ b/docs/03-solver-configuration.md @@ -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. diff --git a/lib/rams/solvers/cbc.rb b/lib/rams/solvers/cbc.rb index 2909012..1719046 100644 --- a/lib/rams/solvers/cbc.rb +++ b/lib/rams/solvers/cbc.rb @@ -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 diff --git a/lib/rams/solvers/clp.rb b/lib/rams/solvers/clp.rb index de37d41..baf5ec7 100644 --- a/lib/rams/solvers/clp.rb +++ b/lib/rams/solvers/clp.rb @@ -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 diff --git a/lib/rams/solvers/cplex.rb b/lib/rams/solvers/cplex.rb index 8913c04..70c38b1 100644 --- a/lib/rams/solvers/cplex.rb +++ b/lib/rams/solvers/cplex.rb @@ -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 diff --git a/lib/rams/solvers/glpk.rb b/lib/rams/solvers/glpk.rb index 41addbe..b817a7d 100644 --- a/lib/rams/solvers/glpk.rb +++ b/lib/rams/solvers/glpk.rb @@ -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 diff --git a/lib/rams/solvers/scip.rb b/lib/rams/solvers/scip.rb index fcd5022..c687656 100644 --- a/lib/rams/solvers/scip.rb +++ b/lib/rams/solvers/scip.rb @@ -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 diff --git a/lib/rams/solvers/solver.rb b/lib/rams/solvers/solver.rb index 2ded3ce..303f543 100644 --- a/lib/rams/solvers/solver.rb +++ b/lib/rams/solvers/solver.rb @@ -60,6 +60,11 @@ def solver_command(_model_file, _solution_path, _args) raise NotImplementedError end + # 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( diff --git a/tests/test_solver_paths.rb b/tests/test_solver_paths.rb new file mode 100644 index 0000000..164fa5e --- /dev/null +++ b/tests/test_solver_paths.rb @@ -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 \ No newline at end of file