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

_component_data_iter now uses sorted_robust #1852

Merged
merged 6 commits into from
Mar 10, 2021
Merged
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
3 changes: 2 additions & 1 deletion pyomo/core/base/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from pyomo.core.base.indexed_component import (
ActiveIndexedComponent, UnindexedComponent_set,
)
from pyomo.core.base.misc import sorted_robust

from pyomo.opt.base import ProblemFormat, guess_format
from pyomo.opt import WriterFactory
Expand Down Expand Up @@ -1358,7 +1359,7 @@ def _component_data_iter(self, ctype=None, active=None, sort=False):
_items = ((None, comp),)

if _sort_indices:
_items = sorted(_items, key=itemgetter(0))
_items = sorted_robust(_items, key=itemgetter(0))
if active is None or not isinstance(comp, ActiveIndexedComponent):
for idx, compData in _items:
yield (name, idx), compData
Expand Down
20 changes: 14 additions & 6 deletions pyomo/core/base/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import sys
import types

from pyomo.core.expr import native_numeric_types

logger = logging.getLogger('pyomo.core')


Expand Down Expand Up @@ -110,8 +112,11 @@ class _robust_sort_keyfcn(object):
_typemap without resorting to global variables.
"""
def __init__(self):
self._typemap = {tuple: (3, tuple.__name__)}
def __init__(self, key=None):
# sort all native numeric types as if they were floats
self._typemap = {t: (1, float.__name__) for t in native_numeric_types}
self._typemap[tuple] =(3, tuple.__name__)
self._key = key

def __call__(self, val):
"""Generate a tuple ( str(type_name), val ) for sorting the value.
Expand All @@ -122,6 +127,9 @@ def __call__(self, val):
argument of the sort key.
"""
if self._key is not None:
val = self._key(val)

try:
i, _typename = self._typemap[val.__class__]
except KeyError:
Expand Down Expand Up @@ -155,7 +163,7 @@ def __call__(self, val):
return _typename, id(val)


def sorted_robust(arg):
def sorted_robust(arg, key=None, reverse=False):
"""Utility to sort an arbitrary iterable.
This returns the sorted(arg) in a consistent order by first tring
Expand All @@ -166,16 +174,16 @@ def sorted_robust(arg):
"""
# It is possible that arg is a generator. We need to cache the
# elements returned by the generator in case 'sorted' raises an
# exception (this ensures we don't loose any elements). Howevver,
# exception (this ensures we don't lose any elements). However,
# if we were passed a list, we do not want to make an unnecessary
# copy. Tuples are OK because tuple(a) will not copy a if it is
# already a tuple.
if type(arg) is not list:
arg = tuple(arg)
try:
return sorted(arg)
return sorted(arg, key=key, reverse=reverse)
except:
return sorted(arg, key=_robust_sort_keyfcn())
return sorted(arg, key=_robust_sort_keyfcn(key), reverse=reverse)


def _to_ustr(obj):
Expand Down
31 changes: 18 additions & 13 deletions pyomo/core/tests/unit/test_base_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ def test_unicode_table(self):
# Test that an embedded unicode character does not foul up the
# table alignment
os = StringIO()
data = {1: ("a", 1), (2,3): ("∧", 2)}
tabular_writer(os, "", data.items(), ["s", "val"], lambda k,v: v)
data = {1: ("a", 1), (2, 3): ("∧", 2)}
tabular_writer(os, "", data.items(), ["s", "val"], lambda k, v: v)
ref = u"""
Key : s : val
1 : a : 1
Expand All @@ -35,21 +35,26 @@ def test_unicode_table(self):
class TestSortedRobust(unittest.TestCase):
def test_sorted_robust(self):
# Note: as types are sorted by name, int < str < tuple
a = sorted_robust([3,2,1])
self.assertEqual(a, [1,2,3])
a = sorted_robust([3, 2, 1])
self.assertEqual(a, [1, 2, 3])

a = sorted_robust([3,'2',1])
self.assertEqual(a, [1,3,'2'])
# Testthat ints and floats are sorted as "numbers"
a = sorted_robust([3, 2.1, 1])
self.assertEqual(a, [1, 2.1, 3])

a = sorted_robust([('str1','str1'), (1, 'str2')])
self.assertEqual(a, [(1, 'str2'), ('str1','str1')])
a = sorted_robust([3, '2', 1])
self.assertEqual(a, [1, 3, '2'])

a = sorted_robust([((1,), 'str2'), ('str1','str1')])
self.assertEqual(a, [('str1','str1'), ((1,), 'str2')])
a = sorted_robust([('str1', 'str1'), (1, 'str2')])
self.assertEqual(a, [(1, 'str2'), ('str1', 'str1')])

a = sorted_robust([((1,), 'str2'), ('str1', 'str1')])
self.assertEqual(a, [('str1', 'str1'), ((1,), 'str2')])

a = sorted_robust([('str1', 'str1'), ((1,), 'str2')])
self.assertEqual(a, [('str1', 'str1'), ((1,), 'str2')])

a = sorted_robust([('str1','str1'), ((1,), 'str2')])
self.assertEqual(a, [('str1','str1'), ((1,), 'str2')])

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

9 changes: 9 additions & 0 deletions pyomo/core/tests/unit/test_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,15 @@ def test_Block(self):
self.assertEqual(sorted([id(comp) for comp in model.block_data_objects(sort=False)]),
sorted([id(comp) for comp in [model,]+model.component_data_lists[Block]]))

def test_mixed_index_type(self):
m = ConcreteModel()
m.I = Set(initialize=[1,'1',3.5,4])
m.x = Var(m.I)
v = list(m.component_data_objects(Var, sort=True))
self.assertEqual(len(v), 4)
for a,b in zip([m.x[1], m.x[3.5], m.x[4], m.x['1']], v):
self.assertIs(a, b)


class HierarchicalModel(object):
def __init__(self):
Expand Down