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

Parsing objective sense in MPS file #656

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
19 changes: 18 additions & 1 deletion pulp/mps_lp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
"""
Expand Down Expand Up @@ -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
Comment on lines +91 to +92
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't you missing a mode=CORE_FILE_OBJSENSE_MODE here too?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in this case. This line is catching the case where you have the objective sense provided in the same line as the keyword OBJSENSE, such as

OBJSENSE MAX

In this case we don't need to enter the OBJSENSE mode.

else:
mode = CORE_FILE_OBJSENSE_MODE
Comment on lines +93 to +94
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why would we have the same mode regardless of the if clause. Maybe we should put the assignment before the len(line)>1 and take out the else clause? Or maybe I don't understand the idea.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following on from the previous comment, we have to enter the CORE_FILE_OBJSENSE_MODE is the objective sense is not given in the same line as the OBJSENSE keyword. This is catching the case

OBJSENSE
  MAX

The check of len(line) > 1 differentiates between these two cases.


# here we query the mode variable
elif mode == CORE_FILE_ROW_MODE:
Expand Down Expand Up @@ -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[0] == 'MAX'\
else const.LpMinimize
Comment on lines +154 to +156
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we test if parameters['sense'] is None? or if we provided an argument to the function (i.e., via the sense argument?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could put in a check here for whether parameters['sense'] is None. However, in the current code the check is not necessary since it is only possible to enter the CORE_FILE_OBJSENSE_MODE mode if parameters['sense'] is None.

We could put in an assert or throw an exception to protect against future code changes? Something like (after line 154)

if parameters['sense'] is not None
    raise Exception("The supplied objective sense will be overwritten by the MPS file objective sense")

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,
Expand Down
2 changes: 1 addition & 1 deletion pulp/pulp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
90 changes: 90 additions & 0 deletions pulp/tests/test_pulp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down