Skip to content

Commit

Permalink
Merge pull request Pyomo#2946 from Raphael-D-F-Gomes/bugfix/adjust-mp…
Browse files Browse the repository at this point in the history
…s-structure

Adjusting mps writer to the correct structure regarding integer variables declaration
  • Loading branch information
blnicho authored Nov 9, 2023
2 parents ce37c84 + 410e557 commit 71787b7
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 5 deletions.
12 changes: 10 additions & 2 deletions pyomo/core/base/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -1934,7 +1934,14 @@ def valid_problem_types(self):
Model object."""
return [ProblemFormat.pyomo]

def write(self, filename=None, format=None, solver_capability=None, io_options={}):
def write(
self,
filename=None,
format=None,
solver_capability=None,
io_options={},
int_marker=False,
):
"""
Write the model to a file, with a given format.
"""
Expand Down Expand Up @@ -1968,7 +1975,8 @@ def write(self, filename=None, format=None, solver_capability=None, io_options={
"Filename '%s' likely does not match specified "
"file format (%s)" % (filename, format)
)
problem_writer = WriterFactory(format)
int_marker_kwds = {"int_marker": int_marker} if int_marker else {}
problem_writer = WriterFactory(format, **int_marker_kwds)
if problem_writer is None:
raise ValueError(
"Cannot write model in format '%s': no model "
Expand Down
24 changes: 23 additions & 1 deletion pyomo/repn/plugins/mps.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def _get_bound(exp):

@WriterFactory.register('mps', 'Generate the corresponding MPS file')
class ProblemWriter_mps(AbstractProblemWriter):
def __init__(self):
def __init__(self, int_marker=False):
AbstractProblemWriter.__init__(self, ProblemFormat.mps)

# the MPS writer is responsible for tracking which variables are
Expand All @@ -78,6 +78,8 @@ def __init__(self):
# the number's sign.
self._precision_string = '.17g'

self._int_marker = int_marker

def __call__(self, model, output_filename, solver_capability, io_options):
# Make sure not to modify the user's dictionary,
# they may be reusing it outside of this call
Expand Down Expand Up @@ -515,10 +517,27 @@ def yield_all_constraints():
column_template = " %s %s %" + self._precision_string + "\n"
output_file.write("COLUMNS\n")
cnt = 0
in_integer_section = False
mark_cnt = 0
for vardata in variable_list:
col_entries = column_data[variable_to_column[vardata]]
cnt += 1
if len(col_entries) > 0:
if self._int_marker:
if vardata.is_integer():
if not in_integer_section:
output_file.write(
f" MARK{mark_cnt:04d} 'MARKER' 'INTORG'\n"
)
in_integer_section = True
mark_cnt += 1
elif in_integer_section: # and not vardata.is_integer()
output_file.write(
f" MARK{mark_cnt:04d} 'MARKER' 'INTEND'\n"
)
in_integer_section = False
mark_cnt += 1

var_label = variable_symbol_dictionary[id(vardata)]
for i, (row_label, coef) in enumerate(col_entries):
output_file.write(
Expand All @@ -536,6 +555,9 @@ def yield_all_constraints():
var_label = variable_symbol_dictionary[id(vardata)]
output_file.write(column_template % (var_label, objective_label, 0))

if self._int_marker and in_integer_section:
output_file.write(f" MARK{mark_cnt:04d} 'MARKER' 'INTEND'\n")

assert cnt == len(column_data) - 1
if len(column_data[-1]) > 0:
col_entries = column_data[-1]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
* Source: Pyomo MPS Writer
* Format: Free MPS
*
NAME Example-mix-integer-linear-problem
OBJSENSE
MIN
ROWS
N obj
G c_l_const1_
L c_u_const2_
COLUMNS
MARK0000 'MARKER' 'INTORG'
x1 obj 3
x1 c_l_const1_ 4
x1 c_u_const2_ 1
MARK0001 'MARKER' 'INTEND'
x2 obj 2
x2 c_l_const1_ 3
x2 c_u_const2_ 2
RHS
RHS c_l_const1_ 10
RHS c_u_const2_ 7
BOUNDS
LI BOUND x1 0
UI BOUND x1 10E20
LO BOUND x2 0
ENDATA
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
* Source: Pyomo MPS Writer
* Format: Free MPS
*
NAME knapsack problem
OBJSENSE
MIN
ROWS
N obj
G c_l_const1_
COLUMNS
MARK0000 'MARKER' 'INTORG'
x(_1_) obj 3
x(_1_) c_l_const1_ 30
x(_2_) obj 2
x(_2_) c_l_const1_ 24
x(_3_) obj 2
x(_3_) c_l_const1_ 11
x(_4_) obj 4
x(_4_) c_l_const1_ 35
x(_5_) obj 5
x(_5_) c_l_const1_ 29
x(_6_) obj 4
x(_6_) c_l_const1_ 8
x(_7_) obj 3
x(_7_) c_l_const1_ 31
x(_8_) obj 1
x(_8_) c_l_const1_ 18
MARK0001 'MARKER' 'INTEND'
RHS
RHS c_l_const1_ 60
BOUNDS
BV BOUND x(_1_)
BV BOUND x(_2_)
BV BOUND x(_3_)
BV BOUND x(_4_)
BV BOUND x(_5_)
BV BOUND x(_6_)
BV BOUND x(_7_)
BV BOUND x(_8_)
ENDATA
64 changes: 62 additions & 2 deletions pyomo/repn/tests/mps/test_mps.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,17 @@
from filecmp import cmp
import pyomo.common.unittest as unittest

from pyomo.environ import ConcreteModel, Var, Objective, Constraint, ComponentMap
from pyomo.environ import (
ConcreteModel,
Var,
Objective,
Constraint,
ComponentMap,
minimize,
Binary,
NonNegativeReals,
NonNegativeIntegers,
)

thisdir = os.path.dirname(os.path.abspath(__file__))

Expand All @@ -36,11 +46,15 @@ def _get_fnames(self):
return prefix + ".mps.baseline", prefix + ".mps.out"

def _check_baseline(self, model, **kwds):
int_marker = kwds.pop("int_marker", False)
baseline_fname, test_fname = self._get_fnames()
self._cleanup(test_fname)
io_options = {"symbolic_solver_labels": True}
io_options.update(kwds)
model.write(test_fname, format="mps", io_options=io_options)
model.write(
test_fname, format="mps", io_options=io_options, int_marker=int_marker
)

self.assertTrue(
cmp(test_fname, baseline_fname),
msg="Files %s and %s differ" % (test_fname, baseline_fname),
Expand Down Expand Up @@ -185,6 +199,52 @@ def test_row_ordering(self):
row_order[model.con4[2]] = -1
self._check_baseline(model, row_order=row_order)

def test_knapsack_problem_binary_variable_declaration_with_marker(self):
elements_size = [30, 24, 11, 35, 29, 8, 31, 18]
elements_weight = [3, 2, 2, 4, 5, 4, 3, 1]
capacity = 60

model = ConcreteModel("knapsack problem")
var_names = [f"{i + 1}" for i in range(len(elements_size))]

model.x = Var(var_names, within=Binary)

model.obj = Objective(
expr=sum(
model.x[var_names[i]] * elements_weight[i]
for i in range(len(elements_size))
),
sense=minimize,
name="obj",
)

model.const1 = Constraint(
expr=sum(
model.x[var_names[i]] * elements_size[i]
for i in range(len(elements_size))
)
>= capacity,
name="const",
)

self._check_baseline(model, int_marker=True)

def test_integer_variable_declaration_with_marker(self):
model = ConcreteModel("Example-mix-integer-linear-problem")

# Define the decision variables
model.x1 = Var(within=NonNegativeIntegers) # Integer variable
model.x2 = Var(within=NonNegativeReals) # Continuous variable

# Define the objective function
model.obj = Objective(expr=3 * model.x1 + 2 * model.x2, sense=minimize)

# Define the constraints
model.const1 = Constraint(expr=4 * model.x1 + 3 * model.x2 >= 10)
model.const2 = Constraint(expr=model.x1 + 2 * model.x2 <= 7)

self._check_baseline(model, int_marker=True)


if __name__ == "__main__":
unittest.main()

0 comments on commit 71787b7

Please sign in to comment.