From ac6daef27cbfd1e71c9fa98fa2b8c54aa78df636 Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Wed, 31 May 2023 13:30:31 +0100 Subject: [PATCH 1/3] reads objective sense from MPS file --- pulp/mps_lp.py | 19 ++++++++++++++++++- pulp/pulp.py | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/pulp/mps_lp.py b/pulp/mps_lp.py index e5ef2bed..f8fc1f58 100644 --- a/pulp/mps_lp.py +++ b/pulp/mps_lp.py @@ -7,6 +7,7 @@ import re from . import constants as const +CORE_FILE_OBJSENSE_MODE = "OBJSENSE" CORE_FILE_ROW_MODE = "ROWS" CORE_FILE_COL_MODE = "COLUMNS" CORE_FILE_RHS_MODE = "RHS" @@ -34,7 +35,9 @@ def readMPS(path, sense, dropConsNames=False): This dictionary can be used to generate an LpProblem :param path: path of mps file - :param sense: 1 for minimize, -1 for maximize + :param sense: 1 for minimize, -1 for maximize, can be None. If None and the + MPS file doesn't set the sense, then the objective sense defaults to + minimize. :param dropConsNames: if True, do not store the names of constraints :return: a dictionary with all the problem data """ @@ -83,6 +86,12 @@ def readMPS(path, sense, dropConsNames=False): mode = CORE_FILE_BOUNDS_MODE_NAME_GIVEN else: mode = CORE_FILE_BOUNDS_MODE_NO_NAME + elif parameters['sense'] is None and line[0] == CORE_FILE_OBJSENSE_MODE: + if len(line) > 1: + parameters['sense'] = const.LpMaximize if line[1] == 'MAX'\ + else const.LpMinimize + else: + mode = CORE_FILE_OBJSENSE_MODE # here we query the mode variable elif mode == CORE_FILE_ROW_MODE: @@ -142,12 +151,20 @@ def readMPS(path, sense, dropConsNames=False): readMPSSetBounds(line, variable_info) if line[1] not in bnd_names: bnd_names.append(line[1]) + elif mode == CORE_FILE_OBJSENSE_MODE: + parameters['sense'] = const.LpMaximize if line[1] == 'MAX'\ + else const.LpMinimize constraints = list(constraints.values()) if dropConsNames: for c in constraints: c["name"] = None objective["name"] = None variable_info = list(variable_info.values()) + + # if the objective sense has not been read. Then it defaults to minimize + if parameters['sense'] is None: + parameters['sense'] = const.LpMinimize + return dict( parameters=parameters, objective=objective, diff --git a/pulp/pulp.py b/pulp/pulp.py index 9aef8d12..8f9c68dd 100644 --- a/pulp/pulp.py +++ b/pulp/pulp.py @@ -1540,7 +1540,7 @@ def fromJson(cls, filename): from_json = fromJson @classmethod - def fromMPS(cls, filename, sense=const.LpMinimize, **kwargs): + def fromMPS(cls, filename, sense=None, **kwargs): data = mpslp.readMPS(filename, sense=sense, **kwargs) return cls.fromDict(data) From ddaa10d13d87659b93e8798ad844264337f8005f Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Wed, 31 May 2023 13:42:34 +0100 Subject: [PATCH 2/3] fixes error in line index for objective sense --- pulp/mps_lp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pulp/mps_lp.py b/pulp/mps_lp.py index f8fc1f58..c123d352 100644 --- a/pulp/mps_lp.py +++ b/pulp/mps_lp.py @@ -152,7 +152,7 @@ def readMPS(path, sense, dropConsNames=False): if line[1] not in bnd_names: bnd_names.append(line[1]) elif mode == CORE_FILE_OBJSENSE_MODE: - parameters['sense'] = const.LpMaximize if line[1] == 'MAX'\ + parameters['sense'] = const.LpMaximize if line[0] == 'MAX'\ else const.LpMinimize constraints = list(constraints.values()) if dropConsNames: From afb229cfc07dacfd65e9d17168c0b2ca32c1ae95 Mon Sep 17 00:00:00 2001 From: "Stephen J. Maher" Date: Wed, 31 May 2023 14:07:29 +0100 Subject: [PATCH 3/3] adds tests for parsing the objective sense in MPS --- pulp/tests/test_pulp.py | 90 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/pulp/tests/test_pulp.py b/pulp/tests/test_pulp.py index 0ce442d7..d1de560e 100644 --- a/pulp/tests/test_pulp.py +++ b/pulp/tests/test_pulp.py @@ -1149,6 +1149,96 @@ def test_importMPS_RHS_fields56(self): # for k, v in _vars.items(): # print(k, v.value()) + def test_importMPS_read_objsense_maximize(self): + name = self._testMethodName + prob = LpProblem(name, const.LpMaximize) + 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" + filename = name + ".mps" + prob.writeMPS(filename, with_objsense = True) + _vars, prob2 = LpProblem.fromMPS(filename) + print("\t Testing reading objective sense MPS files - maximize") + self.assertEqual(prob.sense, prob2.sense) + + def test_importMPS_read_objsense_minimize(self): + 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" + filename = name + ".mps" + prob.writeMPS(filename, with_objsense = True) + _vars, prob2 = LpProblem.fromMPS(filename) + print("\t Testing reading objective sense MPS files - minimize") + self.assertEqual(prob.sense, prob2.sense) + + def test_importMPS_read_objsense_none(self): + 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" + filename = name + ".mps" + prob.writeMPS(filename) + _vars, prob2 = LpProblem.fromMPS(filename) + print("\t Testing reading objective sense MPS files - none") + self.assertEqual(prob.sense, prob2.sense) + + def test_importMPS_read_objsense_override_maximize(self): + 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" + filename = name + ".mps" + prob.writeMPS(filename, with_objsense = True) + _vars, prob2 = LpProblem.fromMPS(filename, sense=const.LpMaximize) + print("\t Testing reading objective sense MPS files - override maximize") + self.assertEqual(const.LpMaximize, prob2.sense) + + def test_importMPS_read_objsense_override_minimize(self): + name = self._testMethodName + prob = LpProblem(name, const.LpMaximize) + 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" + filename = name + ".mps" + prob.writeMPS(filename, with_objsense = True) + _vars, prob2 = LpProblem.fromMPS(filename, sense=const.LpMinimize) + print("\t Testing reading objective sense MPS files - override minimize") + self.assertEqual(const.LpMinimize, prob2.sense) + def test_unset_objective_value__is_valid(self): """Given a valid problem that does not converge, assert that it is still categorised as valid.