Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix pulp cbc cmd solver params #778

Merged
merged 6 commits into from
Oct 22, 2024
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
22 changes: 17 additions & 5 deletions pulp/apis/coin_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ def __init__(
:param bool keepFiles: if True, files are saved in the current directory and not deleted after solving
:param str path: path to the solver binary
:param str logPath: path to the log file
:param bool presolve: if True, adds presolve on
:param bool cuts: if True, adds gomory on knapsack on probing on
:param bool strong: if True, adds strong
:param bool presolve: if True, adds presolve on, if False, adds presolve off
:param bool cuts: if True, adds gomory on knapsack on probing on, if False adds cuts off
:param int strong: number of variables to look at in strong branching (range is 0 to 2147483647)
:param str timeMode: "elapsed": count wall-time to timeLimit; "cpu": count cpu-time
:param int maxNodes: max number of nodes during branching. Stops the solving when reached.
"""
Expand Down Expand Up @@ -142,6 +142,20 @@ def solve_CBC(self, lp, use_mps=True):
cmds += f"-mips {tmpMst} "
if self.timeLimit is not None:
cmds += f"-sec {self.timeLimit} "
if self.optionsDict.get("presolve") is not None:
if self.optionsDict["presolve"]:
# presolve is True: add 'presolve on'
cmds += f"-presolve on "
else:
# presolve is False: add 'presolve off'
cmds += f"-presolve off "
if self.optionsDict.get("cuts") is not None:
if self.optionsDict["cuts"]:
# activate gomory, knapsack, and probing cuts
cmds += f"-gomory on knapsack on probing on "
else:
# turn off all cuts
cmds += f"-cuts off "
options = self.options + self.getOptions()
for option in options:
cmds += "-" + option + " "
Expand Down Expand Up @@ -209,9 +223,7 @@ def getOptions(self):
gapRel="ratio {}",
gapAbs="allow {}",
threads="threads {}",
presolve="presolve on",
strong="strong {}",
cuts="gomory on knapsack on probing on",
timeMode="timeMode {}",
maxNodes="maxNodes {}",
)
Expand Down
194 changes: 194 additions & 0 deletions pulp/tests/test_pulp.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from pulp import constants as const
from pulp.tests.bin_packing_problem import create_bin_packing_problem
from pulp.utilities import makeDict
import re
import functools
import unittest

Expand Down Expand Up @@ -1440,6 +1441,199 @@ def test_multiply_nan_values(self):
class PULP_CBC_CMDTest(BaseSolverTest.PuLPTest):
solveInst = PULP_CBC_CMD

@staticmethod
def read_command_line_from_log_file(logPath):
"""
Read from log file the command line executed.
"""
with open(logPath) as fp:
for row in fp.readlines():
if row.startswith("command line "):
return row
raise ValueError(f"Unable to find the command line in {logPath}")

@staticmethod
def extract_option_from_command_line(
command_line, option, prefix="-", grp_pattern="[a-zA-Z]+"
):
"""
Extract option value from command line string.

:param command_line: str that we extract the option value from
:param option: str representing the option name (e.g., presolve, sec, etc)
:param prefix: str (default: '-')
:param grp_pattern: str (default: '[a-zA-Z]+') - regex to capture option value

:return: option value captured (str); otherwise, None

example:

>>> cmd = "cbc model.mps -presolve off -timeMode elapsed -branch"
>>> PULP_CBC_CMDTest.extract_option_from_command_line(cmd, "presolve")
'off'

>>> cmd = "cbc model.mps -strong 101 -timeMode elapsed -branch"
>>> PULP_CBC_CMDTest.extract_option_from_command_line(cmd, "strong", grp_pattern="\d+")
'101'
"""
pattern = re.compile(rf"{prefix}{option}\s+({grp_pattern})\s*")
m = pattern.search(command_line)
if not m:
print(f"{option} not found in {command_line}")
return None
option_value = m.groups()[0]
return option_value

def test_presolve_off(self):
"""
Test if setting presolve=False in PULP_CBC_CMD adds presolve off to the
command line.
"""
name = self._testMethodName
prob = LpProblem(name, const.LpMinimize)
x = LpVariable("x", 0, 4)
y = LpVariable("y", -1, 1)
z = LpVariable("z", 0)
w = LpVariable("w", 0)
prob += x + 4 * y + 9 * z, "obj"
prob += x + y <= 5, "c1"
prob += x + z >= 10, "c2"
prob += -y + z == 7, "c3"
prob += w >= 0, "c4"
logFilename = name + ".log"
self.solver.optionsDict["logPath"] = logFilename
self.solver.optionsDict["presolve"] = False
pulpTestCheck(
prob,
self.solver,
[const.LpStatusOptimal],
{x: 4, y: -1, z: 6, w: 0},
)
if not os.path.exists(logFilename):
raise PulpError(f"Test failed for solver: {self.solver}")
if not os.path.getsize(logFilename):
raise PulpError(f"Test failed for solver: {self.solver}")
# Extract option_value from command line
command_line = PULP_CBC_CMDTest.read_command_line_from_log_file(logFilename)
option_value = PULP_CBC_CMDTest.extract_option_from_command_line(
command_line, option="presolve"
)
self.assertEqual("off", option_value)

def test_cuts_on(self):
"""
Test if setting cuts=True in PULP_CBC_CMD adds "gomory on knapsack on
probing on" to the command line.
"""
name = self._testMethodName
prob = LpProblem(name, const.LpMinimize)
x = LpVariable("x", 0, 4)
y = LpVariable("y", -1, 1)
z = LpVariable("z", 0)
w = LpVariable("w", 0)
prob += x + 4 * y + 9 * z, "obj"
prob += x + y <= 5, "c1"
prob += x + z >= 10, "c2"
prob += -y + z == 7, "c3"
prob += w >= 0, "c4"
logFilename = name + ".log"
self.solver.optionsDict["logPath"] = logFilename
self.solver.optionsDict["cuts"] = True
pulpTestCheck(
prob,
self.solver,
[const.LpStatusOptimal],
{x: 4, y: -1, z: 6, w: 0},
)
if not os.path.exists(logFilename):
raise PulpError(f"Test failed for solver: {self.solver}")
if not os.path.getsize(logFilename):
raise PulpError(f"Test failed for solver: {self.solver}")
# Extract option values from command line
command_line = PULP_CBC_CMDTest.read_command_line_from_log_file(logFilename)
gomory_value = PULP_CBC_CMDTest.extract_option_from_command_line(
command_line, option="gomory"
)
knapsack_value = PULP_CBC_CMDTest.extract_option_from_command_line(
command_line, option="knapsack", prefix=""
)
probing_value = PULP_CBC_CMDTest.extract_option_from_command_line(
command_line, option="probing", prefix=""
)
self.assertListEqual(
["on", "on", "on"], [gomory_value, knapsack_value, probing_value]
)

def test_cuts_off(self):
"""
Test if setting cuts=False adds cuts off to the command line.
"""
name = self._testMethodName
prob = LpProblem(name, const.LpMinimize)
x = LpVariable("x", 0, 4)
y = LpVariable("y", -1, 1)
z = LpVariable("z", 0)
w = LpVariable("w", 0)
prob += x + 4 * y + 9 * z, "obj"
prob += x + y <= 5, "c1"
prob += x + z >= 10, "c2"
prob += -y + z == 7, "c3"
prob += w >= 0, "c4"
logFilename = name + ".log"
self.solver.optionsDict["logPath"] = logFilename
self.solver.optionsDict["cuts"] = False
pulpTestCheck(
prob,
self.solver,
[const.LpStatusOptimal],
{x: 4, y: -1, z: 6, w: 0},
)
if not os.path.exists(logFilename):
raise PulpError(f"Test failed for solver: {self.solver}")
if not os.path.getsize(logFilename):
raise PulpError(f"Test failed for solver: {self.solver}")
# Extract option value from the command line
command_line = PULP_CBC_CMDTest.read_command_line_from_log_file(logFilename)
option_value = PULP_CBC_CMDTest.extract_option_from_command_line(
command_line, option="cuts"
)
self.assertEqual("off", option_value)

def test_strong(self):
"""
Test if setting strong=10 adds strong 10 to the command line.
"""
name = self._testMethodName
prob = LpProblem(name, const.LpMinimize)
x = LpVariable("x", 0, 4)
y = LpVariable("y", -1, 1)
z = LpVariable("z", 0)
w = LpVariable("w", 0)
prob += x + 4 * y + 9 * z, "obj"
prob += x + y <= 5, "c1"
prob += x + z >= 10, "c2"
prob += -y + z == 7, "c3"
prob += w >= 0, "c4"
logFilename = name + ".log"
self.solver.optionsDict["logPath"] = logFilename
self.solver.optionsDict["strong"] = 10
pulpTestCheck(
prob,
self.solver,
[const.LpStatusOptimal],
{x: 4, y: -1, z: 6, w: 0},
)
if not os.path.exists(logFilename):
raise PulpError(f"Test failed for solver: {self.solver}")
if not os.path.getsize(logFilename):
raise PulpError(f"Test failed for solver: {self.solver}")
# Extract option value from command line
command_line = PULP_CBC_CMDTest.read_command_line_from_log_file(logFilename)
option_value = PULP_CBC_CMDTest.extract_option_from_command_line(
command_line, option="strong", grp_pattern="\d+"
)
self.assertEqual("10", option_value)


class CPLEX_CMDTest(BaseSolverTest.PuLPTest):
solveInst = CPLEX_CMD
Expand Down