From 953607aa7c78cfd1e1f1a27dbe1fca4229a0d00a Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Fri, 11 Aug 2023 08:34:52 -0300 Subject: [PATCH 1/9] Adjusting mps writer to the correct structure regarding binary variables declaration --- pyomo/repn/plugins/mps.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyomo/repn/plugins/mps.py b/pyomo/repn/plugins/mps.py index 89420929778..5186f077153 100644 --- a/pyomo/repn/plugins/mps.py +++ b/pyomo/repn/plugins/mps.py @@ -515,10 +515,18 @@ def yield_all_constraints(): column_template = " %s %s %" + self._precision_string + "\n" output_file.write("COLUMNS\n") cnt = 0 + set_integer = False for vardata in variable_list: col_entries = column_data[variable_to_column[vardata]] cnt += 1 if len(col_entries) > 0: + if vardata.is_binary() and not set_integer: + set_integer = True + output_file.write(" %s %s %s\n" % ("INT", "'MARKER'", "'INTORG'")) + if not vardata.is_binary() and set_integer: + set_integer = False + output_file.write(" %s %s %s\n" % ("INTEND", "'MARKER'", "'INTEND'")) + var_label = variable_symbol_dictionary[id(vardata)] for i, (row_label, coef) in enumerate(col_entries): output_file.write( From 9ee408f26a54bb2bf8f50870f075943c285fd62c Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Fri, 11 Aug 2023 09:09:08 -0300 Subject: [PATCH 2/9] Changing is_binary() method to is_integer() to contemplate all integer variables --- pyomo/repn/plugins/mps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/repn/plugins/mps.py b/pyomo/repn/plugins/mps.py index 5186f077153..d3a324e2f50 100644 --- a/pyomo/repn/plugins/mps.py +++ b/pyomo/repn/plugins/mps.py @@ -520,10 +520,10 @@ def yield_all_constraints(): col_entries = column_data[variable_to_column[vardata]] cnt += 1 if len(col_entries) > 0: - if vardata.is_binary() and not set_integer: + if vardata.is_integer() and not set_integer: set_integer = True output_file.write(" %s %s %s\n" % ("INT", "'MARKER'", "'INTORG'")) - if not vardata.is_binary() and set_integer: + if not vardata.is_integer() and set_integer: set_integer = False output_file.write(" %s %s %s\n" % ("INTEND", "'MARKER'", "'INTEND'")) From a0b7840382e5db881e8a5ef8364967b5b2497616 Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Fri, 11 Aug 2023 15:44:09 -0300 Subject: [PATCH 3/9] Formatting code to black standard indentation --- pyomo/repn/plugins/mps.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyomo/repn/plugins/mps.py b/pyomo/repn/plugins/mps.py index d3a324e2f50..8ebd45f2327 100644 --- a/pyomo/repn/plugins/mps.py +++ b/pyomo/repn/plugins/mps.py @@ -522,10 +522,14 @@ def yield_all_constraints(): if len(col_entries) > 0: if vardata.is_integer() and not set_integer: set_integer = True - output_file.write(" %s %s %s\n" % ("INT", "'MARKER'", "'INTORG'")) + output_file.write( + " %s %s %s\n" % ("INT", "'MARKER'", "'INTORG'") + ) if not vardata.is_integer() and set_integer: set_integer = False - output_file.write(" %s %s %s\n" % ("INTEND", "'MARKER'", "'INTEND'")) + output_file.write( + " %s %s %s\n" % ("INTEND", "'MARKER'", "'INTEND'") + ) var_label = variable_symbol_dictionary[id(vardata)] for i, (row_label, coef) in enumerate(col_entries): From ff9fb5897f1c70357702ec6ffb09707b3b6af7e0 Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Sun, 10 Sep 2023 19:41:31 -0300 Subject: [PATCH 4/9] Creating tests to cover the adjustments --- .../integer_variable_declaration.mps.baseline | 27 +++++++++ ..._integer_variable_declaration.mps.baseline | 39 ++++++++++++ pyomo/repn/tests/mps/test_mps.py | 59 ++++++++++++++++++- 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 pyomo/repn/tests/mps/integer_variable_declaration.mps.baseline create mode 100644 pyomo/repn/tests/mps/knapsack_problem_integer_variable_declaration.mps.baseline diff --git a/pyomo/repn/tests/mps/integer_variable_declaration.mps.baseline b/pyomo/repn/tests/mps/integer_variable_declaration.mps.baseline new file mode 100644 index 00000000000..91901aad77e --- /dev/null +++ b/pyomo/repn/tests/mps/integer_variable_declaration.mps.baseline @@ -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 + INT 'MARKER' 'INTORG' + x1 obj 3 + x1 c_l_const1_ 4 + x1 c_u_const2_ 1 + INTEND '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 diff --git a/pyomo/repn/tests/mps/knapsack_problem_integer_variable_declaration.mps.baseline b/pyomo/repn/tests/mps/knapsack_problem_integer_variable_declaration.mps.baseline new file mode 100644 index 00000000000..cfaac8e7595 --- /dev/null +++ b/pyomo/repn/tests/mps/knapsack_problem_integer_variable_declaration.mps.baseline @@ -0,0 +1,39 @@ +* Source: Pyomo MPS Writer +* Format: Free MPS +* +NAME knapsack problem +OBJSENSE + MIN +ROWS + N obj + G c_l_const1_ +COLUMNS + INT '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 +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 diff --git a/pyomo/repn/tests/mps/test_mps.py b/pyomo/repn/tests/mps/test_mps.py index 44f3d93b75e..e2e5f7aa6ef 100644 --- a/pyomo/repn/tests/mps/test_mps.py +++ b/pyomo/repn/tests/mps/test_mps.py @@ -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__)) @@ -41,6 +51,7 @@ def _check_baseline(self, model, **kwds): io_options = {"symbolic_solver_labels": True} io_options.update(kwds) model.write(test_fname, format="mps", io_options=io_options) + self.assertTrue( cmp(test_fname, baseline_fname), msg="Files %s and %s differ" % (test_fname, baseline_fname), @@ -185,6 +196,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(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) + + def test_integer_variable_declaration(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) + if __name__ == "__main__": unittest.main() From f818de7351d1785c587c6ff8c340dedf04ac84eb Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Mon, 11 Sep 2023 12:20:39 -0300 Subject: [PATCH 5/9] Change mps.baseline name --- ... => knapsack_problem_binary_variable_declaration.mps.baseline} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pyomo/repn/tests/mps/{knapsack_problem_integer_variable_declaration.mps.baseline => knapsack_problem_binary_variable_declaration.mps.baseline} (100%) diff --git a/pyomo/repn/tests/mps/knapsack_problem_integer_variable_declaration.mps.baseline b/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration.mps.baseline similarity index 100% rename from pyomo/repn/tests/mps/knapsack_problem_integer_variable_declaration.mps.baseline rename to pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration.mps.baseline From 786f62282c425681e5f036121ee0d6ec474c502d Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Tue, 26 Sep 2023 15:33:59 -0300 Subject: [PATCH 6/9] Creating integer marker flag for mps file writer // Adjusting integer marker script // changing set_integer name to in_integer_section // adding marker to the end of integer section --- pyomo/core/base/block.py | 11 +++++++++-- pyomo/repn/plugins/mps.py | 33 +++++++++++++++++++++------------ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 596f52b1259..d5630b32be8 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -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. """ @@ -1968,7 +1975,7 @@ 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) + problem_writer = WriterFactory(format, int_marker=int_marker) if problem_writer is None: raise ValueError( "Cannot write model in format '%s': no model " diff --git a/pyomo/repn/plugins/mps.py b/pyomo/repn/plugins/mps.py index 8ebd45f2327..e187f101da3 100644 --- a/pyomo/repn/plugins/mps.py +++ b/pyomo/repn/plugins/mps.py @@ -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 @@ -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 @@ -515,21 +517,25 @@ def yield_all_constraints(): column_template = " %s %s %" + self._precision_string + "\n" output_file.write("COLUMNS\n") cnt = 0 - set_integer = False + 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 vardata.is_integer() and not set_integer: - set_integer = True - output_file.write( - " %s %s %s\n" % ("INT", "'MARKER'", "'INTORG'") - ) - if not vardata.is_integer() and set_integer: - set_integer = False - output_file.write( - " %s %s %s\n" % ("INTEND", "'MARKER'", "'INTEND'") - ) + 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 + 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): @@ -548,6 +554,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] From 38fb412a1cc181b485811e7c51a17aeef7edbe37 Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Tue, 26 Sep 2023 15:34:11 -0300 Subject: [PATCH 7/9] Adjusting tests --- ...r_variable_declaration_with_marker.mps.baseline} | 4 ++-- ...y_variable_declaration_with_marker.mps.baseline} | 3 ++- pyomo/repn/tests/mps/test_mps.py | 13 ++++++++----- 3 files changed, 12 insertions(+), 8 deletions(-) rename pyomo/repn/tests/mps/{integer_variable_declaration.mps.baseline => integer_variable_declaration_with_marker.mps.baseline} (85%) rename pyomo/repn/tests/mps/{knapsack_problem_binary_variable_declaration.mps.baseline => knapsack_problem_binary_variable_declaration_with_marker.mps.baseline} (91%) diff --git a/pyomo/repn/tests/mps/integer_variable_declaration.mps.baseline b/pyomo/repn/tests/mps/integer_variable_declaration_with_marker.mps.baseline similarity index 85% rename from pyomo/repn/tests/mps/integer_variable_declaration.mps.baseline rename to pyomo/repn/tests/mps/integer_variable_declaration_with_marker.mps.baseline index 91901aad77e..efd2293f8fd 100644 --- a/pyomo/repn/tests/mps/integer_variable_declaration.mps.baseline +++ b/pyomo/repn/tests/mps/integer_variable_declaration_with_marker.mps.baseline @@ -9,11 +9,11 @@ ROWS G c_l_const1_ L c_u_const2_ COLUMNS - INT 'MARKER' 'INTORG' + MARK0000 'MARKER' 'INTORG' x1 obj 3 x1 c_l_const1_ 4 x1 c_u_const2_ 1 - INTEND 'MARKER' 'INTEND' + MARK0000 'MARKER' 'INTEND' x2 obj 2 x2 c_l_const1_ 3 x2 c_u_const2_ 2 diff --git a/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration.mps.baseline b/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration_with_marker.mps.baseline similarity index 91% rename from pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration.mps.baseline rename to pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration_with_marker.mps.baseline index cfaac8e7595..3f3a642ea33 100644 --- a/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration.mps.baseline +++ b/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration_with_marker.mps.baseline @@ -8,7 +8,7 @@ ROWS N obj G c_l_const1_ COLUMNS - INT 'MARKER' 'INTORG' + MARK0000 'MARKER' 'INTORG' x(_1_) obj 3 x(_1_) c_l_const1_ 30 x(_2_) obj 2 @@ -25,6 +25,7 @@ COLUMNS x(_7_) c_l_const1_ 31 x(_8_) obj 1 x(_8_) c_l_const1_ 18 + MARK0000 'MARKER' 'INTEND' RHS RHS c_l_const1_ 60 BOUNDS diff --git a/pyomo/repn/tests/mps/test_mps.py b/pyomo/repn/tests/mps/test_mps.py index e2e5f7aa6ef..9be45a17870 100644 --- a/pyomo/repn/tests/mps/test_mps.py +++ b/pyomo/repn/tests/mps/test_mps.py @@ -46,11 +46,14 @@ 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), @@ -196,7 +199,7 @@ 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(self): + 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 @@ -224,9 +227,9 @@ def test_knapsack_problem_binary_variable_declaration(self): name="const", ) - self._check_baseline(model) + self._check_baseline(model, int_marker=True) - def test_integer_variable_declaration(self): + def test_integer_variable_declaration_with_marker(self): model = ConcreteModel("Example-mix-integer-linear-problem") # Define the decision variables @@ -240,7 +243,7 @@ def test_integer_variable_declaration(self): 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) + self._check_baseline(model, int_marker=True) if __name__ == "__main__": From b8ff42f101aee51b00ee97c7cbcb0542d0b01713 Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Tue, 26 Sep 2023 19:01:30 -0300 Subject: [PATCH 8/9] Adjusting int_marker flag logic --- pyomo/core/base/block.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index d5630b32be8..fd5322ba686 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -1975,7 +1975,8 @@ def write( "Filename '%s' likely does not match specified " "file format (%s)" % (filename, format) ) - problem_writer = WriterFactory(format, int_marker=int_marker) + 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 " From 14be2cb60e19d5ec942ebd788202d77a6f6d7aa3 Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Tue, 26 Sep 2023 19:19:13 -0300 Subject: [PATCH 9/9] Adjusting mps writer marker name logic --- pyomo/repn/plugins/mps.py | 1 + .../mps/integer_variable_declaration_with_marker.mps.baseline | 2 +- ...problem_binary_variable_declaration_with_marker.mps.baseline | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyomo/repn/plugins/mps.py b/pyomo/repn/plugins/mps.py index e187f101da3..f40c7666278 100644 --- a/pyomo/repn/plugins/mps.py +++ b/pyomo/repn/plugins/mps.py @@ -530,6 +530,7 @@ def yield_all_constraints(): 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" diff --git a/pyomo/repn/tests/mps/integer_variable_declaration_with_marker.mps.baseline b/pyomo/repn/tests/mps/integer_variable_declaration_with_marker.mps.baseline index efd2293f8fd..d455b38af0c 100644 --- a/pyomo/repn/tests/mps/integer_variable_declaration_with_marker.mps.baseline +++ b/pyomo/repn/tests/mps/integer_variable_declaration_with_marker.mps.baseline @@ -13,7 +13,7 @@ COLUMNS x1 obj 3 x1 c_l_const1_ 4 x1 c_u_const2_ 1 - MARK0000 'MARKER' 'INTEND' + MARK0001 'MARKER' 'INTEND' x2 obj 2 x2 c_l_const1_ 3 x2 c_u_const2_ 2 diff --git a/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration_with_marker.mps.baseline b/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration_with_marker.mps.baseline index 3f3a642ea33..d19c9285e5c 100644 --- a/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration_with_marker.mps.baseline +++ b/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration_with_marker.mps.baseline @@ -25,7 +25,7 @@ COLUMNS x(_7_) c_l_const1_ 31 x(_8_) obj 1 x(_8_) c_l_const1_ 18 - MARK0000 'MARKER' 'INTEND' + MARK0001 'MARKER' 'INTEND' RHS RHS c_l_const1_ 60 BOUNDS