Skip to content

Commit

Permalink
Merge 7889ce5 into 1f3b13a
Browse files Browse the repository at this point in the history
  • Loading branch information
dinever committed Mar 5, 2020
2 parents 1f3b13a + 7889ce5 commit e421a8a
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 4 deletions.
29 changes: 25 additions & 4 deletions flipy/solvers/cbc_solver.py
Expand Up @@ -2,6 +2,7 @@
import tempfile
import platform
import subprocess
from typing import Optional

from flipy.lp_problem import LpProblem
from flipy.solvers.base_solver import SolutionStatus
Expand Down Expand Up @@ -30,9 +31,22 @@ class CBCSolver:
('Windows', '64bit'): 'bin/cbc-win64/cbc.exe',
}

def __init__(self) -> None:
""" Initialize the solver """
def __init__(self, mip_gap: float = 0.1, timeout: Optional[int] = None) -> None:
""" Initialize the solver
Parameters
----------
mip_gap: float
Relative MIP optimality gap.
The solver will terminate (with an optimal result) when the gap between the lower and upper objective bound
is less than MIPGap times the absolute value of the upper bound
This can save some time if all you want to do is find a heuristic solution.
timeout: int
The time allowed for solving in seconds
"""
self.bin_path = os.getenv('CBC_SOLVER_BIN', self._find_cbc_binary())
self.mip_gap = mip_gap
self.timeout = timeout

@classmethod
def _find_cbc_binary(cls) -> str:
Expand Down Expand Up @@ -87,7 +101,7 @@ def solve(self, lp_problem: LpProblem) -> SolutionStatus:
self.call_cbc(f.name, solution_file_path)

if not os.path.exists(solution_file_path):
raise SolverError("Error while trying to solve the problem")
return SolutionStatus.NotSolved

return self.read_solution(solution_file_path, lp_problem)

Expand All @@ -107,7 +121,14 @@ def call_cbc(self, lp_file_path: str, solution_file_path: str):
Where to record the solution
"""
pipe = open(os.devnull, 'w')
args = [self.bin_path, lp_file_path, 'branch', 'printingOptions', 'all', 'solution', solution_file_path]

args = [self.bin_path, lp_file_path]
if self.timeout:
args.extend(['sec', str(self.timeout)])
if self.mip_gap:
args.extend(['ratio', str(self.mip_gap)])
args.extend(['branch', 'printingOptions', 'all', 'solution', solution_file_path])

coin_proc = subprocess.Popen(args, stderr=pipe, stdout=pipe)
if coin_proc.wait() != 0:
raise SolverError(f"Error while trying to execute {self.bin_path}")
Expand Down
Empty file added tests/test_solvers/__init__.py
Empty file.
49 changes: 49 additions & 0 deletions tests/test_solvers/test_cbc_solver.py
@@ -0,0 +1,49 @@
import mock

import pytest

from flipy import CBCSolver


@pytest.fixture
def cbc_solver():
return CBCSolver()


class TestCBCSolver:

def test_init(self):
solver = CBCSolver()
assert solver.mip_gap == 0.1
assert solver.timeout is None

@mock.patch('platform.system')
@mock.patch('platform.architecture')
@pytest.mark.parametrize('system, arch, bin_path', [
('Linux', '64bit', 'cbc-linux64'),
('Linux', '64bit', 'bin/cbc-linux64/cbc'),
('Darwin', '64bit', 'bin/cbc-osx/cbc'),
('Windows', '32bit', 'bin/cbc-win32/cbc.exe'),
('Windows', '64bit', 'bin/cbc-win64/cbc.exe'),
])
def test_find_binary(self, mock_arch, mock_system, system, arch, bin_path, cbc_solver):
mock_arch.return_value = arch, None
mock_system.return_value = system
assert bin_path in cbc_solver._find_cbc_binary()

@mock.patch('platform.system')
@mock.patch('platform.architecture')
def test_unknown_sys(self, mock_arch, mock_system, cbc_solver):
mock_system.return_value = 'Plan9'
mock_arch.return_value = '32bit', None
with pytest.raises(Exception) as e:
cbc_solver._find_cbc_binary()
assert str(
e.value
) == "no CBC solver found for system Plan9 32bit, please set the solver path manually with " \
"environment variable 'CBC_SOLVER_BIN'"

def test_find_custom_binary(self, monkeypatch, cbc_solver):
monkeypatch.setenv('CBC_SOLVER_BIN', '/my_custom_cbc_binary')
assert cbc_solver._find_cbc_binary() == '/my_custom_cbc_binary'

0 comments on commit e421a8a

Please sign in to comment.