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

Adjusting mps writer to the correct structure regarding integer variables declaration #2946

Merged
merged 21 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
953607a
Adjusting mps writer to the correct structure regarding binary variab…
gomesraphael7d Aug 11, 2023
9ee408f
Changing is_binary() method to is_integer() to contemplate all intege…
gomesraphael7d Aug 11, 2023
a0b7840
Formatting code to black standard indentation
gomesraphael7d Aug 11, 2023
ff9fb58
Creating tests to cover the adjustments
gomesraphael7d Sep 10, 2023
c1913cd
Merge branch 'main' into bugfix/adjust-mps-structure
Raphael-D-F-Gomes Sep 11, 2023
f818de7
Change mps.baseline name
gomesraphael7d Sep 11, 2023
3458ef3
Merge remote-tracking branch 'origin/bugfix/adjust-mps-structure' int…
gomesraphael7d Sep 11, 2023
ec0a2e9
Merge branch 'main' into bugfix/adjust-mps-structure
Raphael-D-F-Gomes Sep 20, 2023
786f622
Creating integer marker flag for mps file writer // Adjusting integer…
gomesraphael7d Sep 26, 2023
38fb412
Adjusting tests
gomesraphael7d Sep 26, 2023
c93b34c
Merge remote-tracking branch 'origin/bugfix/adjust-mps-structure' int…
gomesraphael7d Sep 26, 2023
de4d455
Merge branch 'main' into bugfix/adjust-mps-structure
Raphael-D-F-Gomes Sep 26, 2023
b8ff42f
Adjusting int_marker flag logic
gomesraphael7d Sep 26, 2023
d1aa233
Merge remote-tracking branch 'origin/bugfix/adjust-mps-structure' int…
gomesraphael7d Sep 26, 2023
14be2cb
Adjusting mps writer marker name logic
gomesraphael7d Sep 26, 2023
cfcb06e
Merge branch 'main' into bugfix/adjust-mps-structure
jsiirola Oct 4, 2023
89f9499
Merge branch 'main' into bugfix/adjust-mps-structure
blnicho Oct 17, 2023
b795481
Merge branch 'main' into bugfix/adjust-mps-structure
blnicho Oct 24, 2023
9e6f996
Merge branch 'main' into bugfix/adjust-mps-structure
Raphael-D-F-Gomes Oct 31, 2023
8d741d4
Merge branch 'main' into bugfix/adjust-mps-structure
Raphael-D-F-Gomes Nov 1, 2023
410e557
Merge branch 'main' into bugfix/adjust-mps-structure
mrmundt Nov 8, 2023
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
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()