From 4a8b9085b79f2acc15120084b68749866ea7be8a Mon Sep 17 00:00:00 2001 From: Marcus Reaiche Date: Mon, 30 Sep 2024 22:22:50 -0400 Subject: [PATCH 1/6] Add pulp/tests/utilities --- pulp/tests/utilities.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 pulp/tests/utilities.py diff --git a/pulp/tests/utilities.py b/pulp/tests/utilities.py new file mode 100644 index 00000000..76e719c5 --- /dev/null +++ b/pulp/tests/utilities.py @@ -0,0 +1,24 @@ +import re + + +def read_command_line_from_log_file(logPath: str) -> str: + """ + 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}") + + +def extract_option_from_command_line( + command_line: str, option: str, prefix: str = "-", grp_pattern: str = "[a-zA-Z]+" +) -> str: + 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 From 958e02325330f4783226a785d1e795a4006e12d5 Mon Sep 17 00:00:00 2001 From: Marcus Reaiche Date: Mon, 30 Sep 2024 22:25:57 -0400 Subject: [PATCH 2/6] Add unit tests to pulp/tests/test_pulp --- pulp/tests/test_pulp.py | 164 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/pulp/tests/test_pulp.py b/pulp/tests/test_pulp.py index d50de9e1..a7fcfc81 100644 --- a/pulp/tests/test_pulp.py +++ b/pulp/tests/test_pulp.py @@ -9,6 +9,10 @@ from pulp import LpVariable, LpProblem, lpSum, LpConstraintVar, LpFractionConstraint from pulp import constants as const from pulp.tests.bin_packing_problem import create_bin_packing_problem +from pulp.tests.utilities import ( + read_command_line_from_log_file, + extract_option_from_command_line, +) from pulp.utilities import makeDict import functools import unittest @@ -1032,6 +1036,166 @@ def test_logPath(self): if not os.path.getsize(logFilename): raise PulpError(f"Test failed for solver: {self.solver}") + 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 + if self.solver.name in [ + "PULP_CBC_CMD", + ]: + 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}") + command_line = read_command_line_from_log_file(logFilename) + option_value = 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 + if self.solver.name in [ + "PULP_CBC_CMD", + ]: + 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}") + command_line = read_command_line_from_log_file(logFilename) + gomory_value = extract_option_from_command_line( + command_line, option="gomory" + ) + knapsack_value = extract_option_from_command_line( + command_line, option="knapsack", prefix="" + ) + probing_value = 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 in PULP_CBC_CMD 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 + if self.solver.name in [ + "PULP_CBC_CMD", + ]: + 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}") + command_line = read_command_line_from_log_file(logFilename) + option_value = extract_option_from_command_line( + command_line, option="cuts" + ) + self.assertEqual("off", option_value) + + def test_strong(self): + """ + Test if setting strong=10 in PULP_CBC_CMD 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 + if self.solver.name in [ + "PULP_CBC_CMD", + ]: + 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}") + command_line = read_command_line_from_log_file(logFilename) + option_value = extract_option_from_command_line( + command_line, option="strong", grp_pattern="\d+" + ) + self.assertEqual("10", option_value) + def test_makeDict_behavior(self): """ Test if makeDict is returning the expected value. From 17456580fd682c166e708b770fbc43d136077fa0 Mon Sep 17 00:00:00 2001 From: Marcus Reaiche Date: Mon, 30 Sep 2024 22:34:42 -0400 Subject: [PATCH 3/6] Fix command passed to cbc application from PULP_CBC_CMD parameters --- pulp/apis/coin_api.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/pulp/apis/coin_api.py b/pulp/apis/coin_api.py index 62191f9d..e0459fd5 100644 --- a/pulp/apis/coin_api.py +++ b/pulp/apis/coin_api.py @@ -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. """ @@ -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 + " " @@ -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 {}", ) From 29ab059aec314ff9479056edddeb401e1ae2354d Mon Sep 17 00:00:00 2001 From: Marcus Reaiche Date: Wed, 2 Oct 2024 22:19:40 -0400 Subject: [PATCH 4/6] Remove type annotations and add docstring --- pulp/tests/utilities.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/pulp/tests/utilities.py b/pulp/tests/utilities.py index 76e719c5..23d26830 100644 --- a/pulp/tests/utilities.py +++ b/pulp/tests/utilities.py @@ -1,7 +1,7 @@ import re -def read_command_line_from_log_file(logPath: str) -> str: +def read_command_line_from_log_file(logPath): """ Read from log file the command line executed. """ @@ -13,8 +13,28 @@ def read_command_line_from_log_file(logPath: str) -> str: def extract_option_from_command_line( - command_line: str, option: str, prefix: str = "-", grp_pattern: str = "[a-zA-Z]+" -) -> str: + 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" + >>> extract_option_from_command_line(cmd, "presolve") + 'off' + + >>> cmd = "cbc model.mps -strong 101 -timeMode elapsed -branch" + >>> 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: From 44fd3480d782809a9c3be73892326e54b1c591a1 Mon Sep 17 00:00:00 2001 From: Marcus Reaiche Date: Fri, 4 Oct 2024 21:31:14 -0400 Subject: [PATCH 5/6] Move tests to PULP_CBC_CMDTest --- pulp/tests/test_pulp.py | 304 +++++++++++++++++++--------------------- 1 file changed, 144 insertions(+), 160 deletions(-) diff --git a/pulp/tests/test_pulp.py b/pulp/tests/test_pulp.py index a7fcfc81..82f23688 100644 --- a/pulp/tests/test_pulp.py +++ b/pulp/tests/test_pulp.py @@ -1036,166 +1036,6 @@ def test_logPath(self): if not os.path.getsize(logFilename): raise PulpError(f"Test failed for solver: {self.solver}") - 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 - if self.solver.name in [ - "PULP_CBC_CMD", - ]: - 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}") - command_line = read_command_line_from_log_file(logFilename) - option_value = 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 - if self.solver.name in [ - "PULP_CBC_CMD", - ]: - 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}") - command_line = read_command_line_from_log_file(logFilename) - gomory_value = extract_option_from_command_line( - command_line, option="gomory" - ) - knapsack_value = extract_option_from_command_line( - command_line, option="knapsack", prefix="" - ) - probing_value = 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 in PULP_CBC_CMD 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 - if self.solver.name in [ - "PULP_CBC_CMD", - ]: - 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}") - command_line = read_command_line_from_log_file(logFilename) - option_value = extract_option_from_command_line( - command_line, option="cuts" - ) - self.assertEqual("off", option_value) - - def test_strong(self): - """ - Test if setting strong=10 in PULP_CBC_CMD 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 - if self.solver.name in [ - "PULP_CBC_CMD", - ]: - 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}") - command_line = read_command_line_from_log_file(logFilename) - option_value = extract_option_from_command_line( - command_line, option="strong", grp_pattern="\d+" - ) - self.assertEqual("10", option_value) - def test_makeDict_behavior(self): """ Test if makeDict is returning the expected value. @@ -1604,6 +1444,150 @@ def test_multiply_nan_values(self): class PULP_CBC_CMDTest(BaseSolverTest.PuLPTest): solveInst = PULP_CBC_CMD + 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 = read_command_line_from_log_file(logFilename) + option_value = 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 = read_command_line_from_log_file(logFilename) + gomory_value = extract_option_from_command_line(command_line, option="gomory") + knapsack_value = extract_option_from_command_line( + command_line, option="knapsack", prefix="" + ) + probing_value = 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 = read_command_line_from_log_file(logFilename) + option_value = 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 = read_command_line_from_log_file(logFilename) + option_value = 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 From c3f1eb017062972b8e35f28e2cc396700aeb9947 Mon Sep 17 00:00:00 2001 From: Marcus Reaiche Date: Mon, 7 Oct 2024 19:16:00 -0400 Subject: [PATCH 6/6] Move functions in tests/utilities to PULP_CBC_CMDTest --- pulp/tests/test_pulp.py | 74 +++++++++++++++++++++++++++++++++-------- pulp/tests/utilities.py | 44 ------------------------ 2 files changed, 60 insertions(+), 58 deletions(-) delete mode 100644 pulp/tests/utilities.py diff --git a/pulp/tests/test_pulp.py b/pulp/tests/test_pulp.py index 82f23688..ffc05eee 100644 --- a/pulp/tests/test_pulp.py +++ b/pulp/tests/test_pulp.py @@ -9,11 +9,8 @@ from pulp import LpVariable, LpProblem, lpSum, LpConstraintVar, LpFractionConstraint from pulp import constants as const from pulp.tests.bin_packing_problem import create_bin_packing_problem -from pulp.tests.utilities import ( - read_command_line_from_log_file, - extract_option_from_command_line, -) from pulp.utilities import makeDict +import re import functools import unittest @@ -1444,6 +1441,49 @@ 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 @@ -1474,8 +1514,10 @@ def test_presolve_off(self): if not os.path.getsize(logFilename): raise PulpError(f"Test failed for solver: {self.solver}") # Extract option_value from command line - command_line = read_command_line_from_log_file(logFilename) - option_value = extract_option_from_command_line(command_line, option="presolve") + 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): @@ -1508,12 +1550,14 @@ def test_cuts_on(self): if not os.path.getsize(logFilename): raise PulpError(f"Test failed for solver: {self.solver}") # Extract option values from command line - command_line = read_command_line_from_log_file(logFilename) - gomory_value = extract_option_from_command_line(command_line, option="gomory") - knapsack_value = extract_option_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 = extract_option_from_command_line( + probing_value = PULP_CBC_CMDTest.extract_option_from_command_line( command_line, option="probing", prefix="" ) self.assertListEqual( @@ -1549,8 +1593,10 @@ def test_cuts_off(self): if not os.path.getsize(logFilename): raise PulpError(f"Test failed for solver: {self.solver}") # Extract option value from the command line - command_line = read_command_line_from_log_file(logFilename) - option_value = extract_option_from_command_line(command_line, option="cuts") + 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): @@ -1582,8 +1628,8 @@ def test_strong(self): if not os.path.getsize(logFilename): raise PulpError(f"Test failed for solver: {self.solver}") # Extract option value from command line - command_line = read_command_line_from_log_file(logFilename) - option_value = extract_option_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) diff --git a/pulp/tests/utilities.py b/pulp/tests/utilities.py deleted file mode 100644 index 23d26830..00000000 --- a/pulp/tests/utilities.py +++ /dev/null @@ -1,44 +0,0 @@ -import re - - -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}") - - -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" - >>> extract_option_from_command_line(cmd, "presolve") - 'off' - - >>> cmd = "cbc model.mps -strong 101 -timeMode elapsed -branch" - >>> 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