Skip to content

Commit

Permalink
Merge pull request #2982 from jsiirola/lp-writer-warn-suffixes
Browse files Browse the repository at this point in the history
LP writer: warn user for ignored suffixes
  • Loading branch information
mrmundt authored Aug 31, 2023
2 parents a8c8f09 + e228786 commit 39f95bc
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 24 deletions.
6 changes: 2 additions & 4 deletions pyomo/core/kernel/suffix.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,11 @@ def __init__(self, *args, **kwds):
# Interface
#

@property
def export_enabled(self):
"""Returns :const:`True` when this suffix is enabled
for export to solvers."""
return bool(self._direction & suffix.EXPORT)

@property
def import_enabled(self):
"""Returns :const:`True` when this suffix is enabled
for import from solutions."""
Expand Down Expand Up @@ -233,7 +231,7 @@ def export_suffix_generator(blk, datatype=_noarg, active=True, descend_into=True
"""
for suf in filter(
lambda x: (
x.export_enabled and ((datatype is _noarg) or (x.datatype is datatype))
x.export_enabled() and ((datatype is _noarg) or (x.datatype is datatype))
),
blk.components(ctype=suffix._ctype, active=active, descend_into=descend_into),
):
Expand Down Expand Up @@ -267,7 +265,7 @@ def import_suffix_generator(blk, datatype=_noarg, active=True, descend_into=True
"""
for suf in filter(
lambda x: (
x.import_enabled and ((datatype is _noarg) or (x.datatype is datatype))
x.import_enabled() and ((datatype is _noarg) or (x.datatype is datatype))
),
blk.components(ctype=suffix._ctype, active=active, descend_into=descend_into),
):
Expand Down
16 changes: 8 additions & 8 deletions pyomo/core/tests/unit/kernel/test_suffix.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,20 @@ def test_import_export_enabled(self):
s = suffix()
s.direction = suffix.LOCAL
self.assertEqual(s.direction, suffix.LOCAL)
self.assertEqual(s.export_enabled, False)
self.assertEqual(s.import_enabled, False)
self.assertEqual(s.export_enabled(), False)
self.assertEqual(s.import_enabled(), False)
s.direction = suffix.IMPORT
self.assertEqual(s.direction, suffix.IMPORT)
self.assertEqual(s.export_enabled, False)
self.assertEqual(s.import_enabled, True)
self.assertEqual(s.export_enabled(), False)
self.assertEqual(s.import_enabled(), True)
s.direction = suffix.EXPORT
self.assertEqual(s.direction, suffix.EXPORT)
self.assertEqual(s.export_enabled, True)
self.assertEqual(s.import_enabled, False)
self.assertEqual(s.export_enabled(), True)
self.assertEqual(s.import_enabled(), False)
s.direction = suffix.IMPORT_EXPORT
self.assertEqual(s.direction, suffix.IMPORT_EXPORT)
self.assertEqual(s.export_enabled, True)
self.assertEqual(s.import_enabled, True)
self.assertEqual(s.export_enabled(), True)
self.assertEqual(s.import_enabled(), True)
with self.assertRaises(ValueError):
s.direction = 'export'

Expand Down
24 changes: 24 additions & 0 deletions pyomo/repn/plugins/lp_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,30 @@ def write(self, model):

timer.toc('Initialized column order', level=logging.DEBUG)

# We don't export any suffix information to the LP file
#
if component_map[Suffix]:
suffixesByName = {}
for block in component_map[Suffix]:
for suffix in block.component_objects(
Suffix, active=True, descend_into=False, sort=sorter
):
if not suffix.export_enabled() or not suffix:
continue
name = suffix.local_name
if name in suffixesByName:
suffixesByName[name].append(suffix)
else:
suffixesByName[name] = [suffix]
for name, suffixes in suffixesByName.items():
n = len(suffixes)
plural = 's' if n > 1 else ''
logger.warning(
f"EXPORT Suffix '{name}' found on {n} block{plural}:\n "
+ "\n ".join(s.name for s in suffixes)
+ "\nLP writer cannot export suffixes to LP files. Skipping."
)

ostream.write(f"\\* Source Pyomo model name={model.name} *\\\n\n")

#
Expand Down
75 changes: 75 additions & 0 deletions pyomo/repn/tests/cpxlp/test_lpv2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2022
# National Technology and Engineering Solutions of Sandia, LLC
# Under the terms of Contract DE-NA0003525 with National Technology and
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
# rights in this software.
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________

from io import StringIO

import pyomo.common.unittest as unittest

from pyomo.common.log import LoggingIntercept
from pyomo.environ import ConcreteModel, Block, Constraint, Var, Objective, Suffix

from pyomo.repn.plugins.lp_writer import LPWriter


class TestLPv2(unittest.TestCase):
def test_warn_export_suffixes(self):
m = ConcreteModel()
m.x = Var()
m.obj = Objective(expr=m.x)
m.con = Constraint(expr=m.x >= 2)
m.b = Block()
m.ignored = Suffix(direction=Suffix.IMPORT)
m.duals = Suffix(direction=Suffix.IMPORT_EXPORT)
m.b.duals = Suffix(direction=Suffix.IMPORT_EXPORT)
m.b.scaling = Suffix(direction=Suffix.EXPORT)

# Empty suffixes are ignored
writer = LPWriter()
with LoggingIntercept() as LOG:
writer.write(m, StringIO())
self.assertEqual(LOG.getvalue(), "")

# Import are ignored, export and import/export are warned
m.duals[m.con] = 5
m.ignored[m.x] = 6
m.b.scaling[m.x] = 7

writer = LPWriter()
with LoggingIntercept() as LOG:
writer.write(m, StringIO())
self.assertEqual(
LOG.getvalue(),
"""EXPORT Suffix 'duals' found on 1 block:
duals
LP writer cannot export suffixes to LP files. Skipping.
EXPORT Suffix 'scaling' found on 1 block:
b.scaling
LP writer cannot export suffixes to LP files. Skipping.
""",
)

# Counting works correctly
m.b.duals[m.x] = 7

writer = LPWriter()
with LoggingIntercept() as LOG:
writer.write(m, StringIO())
self.assertEqual(
LOG.getvalue(),
"""EXPORT Suffix 'duals' found on 2 blocks:
duals
b.duals
LP writer cannot export suffixes to LP files. Skipping.
EXPORT Suffix 'scaling' found on 1 block:
b.scaling
LP writer cannot export suffixes to LP files. Skipping.
""",
)
14 changes: 2 additions & 12 deletions pyomo/solvers/tests/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,7 @@ def save_current_solution(self, filename, **kwds):
(suffix, getattr(model, suffix)) for suffix in kwds.pop('suffixes', [])
)
for suf in suffixes.values():
if isinstance(self.model, IBlock):
assert isinstance(suf, pmo.suffix)
assert suf.import_enabled
else:
assert isinstance(suf, Suffix)
assert suf.import_enabled()
assert suf.import_enabled()

with open(filename, 'w') as f:
#
Expand Down Expand Up @@ -197,12 +192,7 @@ def validate_current_solution(self, **kwds):
)
exclude = kwds.pop('exclude_suffixes', set())
for suf in suffixes.values():
if isinstance(self.model, IBlock):
assert isinstance(suf, pmo.suffix)
assert suf.import_enabled
else:
assert isinstance(suf, Suffix)
assert suf.import_enabled()
assert suf.import_enabled()
solution = None
error_str = (
"Difference in solution for {0}.{1}:\n\tBaseline - {2}\n\tCurrent - {3}"
Expand Down

0 comments on commit 39f95bc

Please sign in to comment.