diff --git a/pyomo/core/base/range.py b/pyomo/core/base/range.py index b0863f11207..f650680df26 100644 --- a/pyomo/core/base/range.py +++ b/pyomo/core/base/range.py @@ -208,7 +208,7 @@ def __contains__(self, value): return False if self.step: - _dir = math.copysign(1, self.step) + _dir = int(math.copysign(1, self.step)) _from_start = value - self.start return ( 0 <= _dir * _from_start <= _dir * (self.end - self.start) @@ -411,14 +411,13 @@ def _split_ranges(cnr, new_step): assert new_step >= abs(cnr.step) assert new_step % cnr.step == 0 - _dir = math.copysign(1, cnr.step) + _dir = int(math.copysign(1, cnr.step)) _subranges = [] for i in range(int(abs(new_step // cnr.step))): if _dir * (cnr.start + i * cnr.step) > _dir * cnr.end: # Once we walk past the end of the range, we are done # (all remaining offsets will be farther past the end) break - _subranges.append( NumericRange(cnr.start + i * cnr.step, cnr.end, _dir * new_step) ) @@ -458,7 +457,7 @@ def _step_lcm(self, other_ranges): else: # one of the steps was 0: add to preserve the non-zero step a += b - return abs(a) + return int(abs(a)) def _push_to_discrete_element(self, val, push_to_next_larger_value): if not self.step or val in _infinite: @@ -557,9 +556,14 @@ def range_difference(self, other_ranges): NumericRange(t.start, start, 0, (t.closed[0], False)) ) if s.step: # i.e., not a single point - for i in range(int(start // s.step), int(end // s.step)): + for i in range(int((end - start) // s.step)): _new_subranges.append( - NumericRange(i * s.step, (i + 1) * s.step, 0, '()') + NumericRange( + start + i * s.step, + start + (i + 1) * s.step, + 0, + '()', + ) ) if t.end > end: _new_subranges.append( @@ -605,7 +609,7 @@ def range_difference(self, other_ranges): ) elif t_max == s_max and t_c[1] and not s_c[1]: _new_subranges.append(NumericRange(t_max, t_max, 0)) - _this = _new_subranges + _this = _new_subranges return _this def range_intersection(self, other_ranges): diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 051922c4aaa..6dfc3f07427 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -1584,28 +1584,26 @@ def _to_0_based_index(self, item): # implementation does not guarantee that the index is valid (it # could be outside of abs(i) <= len(self)). try: - if item != int(item): - raise IndexError( - "%s indices must be integers, not %s" - % (self.name, type(item).__name__) - ) - item = int(item) + _item = int(item) + if item != _item: + raise IndexError() except: raise IndexError( - "%s indices must be integers, not %s" % (self.name, type(item).__name__) - ) - - if item >= 1: - return item - 1 - elif item < 0: - item += len(self) - if item < 0: - raise IndexError("%s index out of range" % (self.name,)) - return item + f"Set '{self.name}' positional indices must be integers, " + f"not {type(item).__name__}" + ) from None + + if _item >= 1: + return _item - 1 + elif _item < 0: + _item += len(self) + if _item < 0: + raise IndexError(f"{self.name} index out of range") + return _item else: raise IndexError( - "Pyomo Sets are 1-indexed: valid index values for Sets are " - "[1 .. len(Set)] or [-1 .. -len(Set)]" + "Accessing Pyomo Sets by position is 1-based: valid Set positional " + "index values are [1 .. len(Set)] or [-1 .. -len(Set)]" ) @@ -1683,7 +1681,7 @@ def at(self, index): try: return self._ordered_values[i] except IndexError: - raise IndexError("%s index out of range" % (self.name)) + raise IndexError(f"{self.name} index out of range") from None def ord(self, item): """ @@ -2023,7 +2021,7 @@ def __init__( filter=None, validate=None, name=None, - doc=None + doc=None, ): ... @@ -2545,7 +2543,7 @@ def at(self, index): try: return self._ref[i] except IndexError: - raise IndexError("%s index out of range" % (self.name)) + raise IndexError(f"{self.name} index out of range") from None def ord(self, item): # The bulk of single-value set members are stored as scalars. @@ -2686,7 +2684,7 @@ def at(self, index): if not idx: return ans idx -= 1 - raise IndexError("%s index out of range" % (self.name,)) + raise IndexError(f"{self.name} index out of range") def ord(self, item): if len(self._ranges) == 1: @@ -2861,7 +2859,7 @@ def __init__( filter=None, validate=None, name=None, - doc=None + doc=None, ): ... @@ -2878,7 +2876,7 @@ def __init__( filter=None, validate=None, name=None, - doc=None + doc=None, ): ... @@ -2892,7 +2890,7 @@ def __init__( filter=None, validate=None, name=None, - doc=None + doc=None, ): ... @@ -3505,7 +3503,7 @@ def at(self, index): if val not in self._sets[0]: idx -= 1 except StopIteration: - raise IndexError("%s index out of range" % (self.name,)) + raise IndexError(f"{self.name} index out of range") from None return val def ord(self, item): @@ -3642,7 +3640,7 @@ def at(self, index): idx -= 1 return next(_iter) except StopIteration: - raise IndexError("%s index out of range" % (self.name,)) + raise IndexError(f"{self.name} index out of range") from None def ord(self, item): """ @@ -3736,7 +3734,7 @@ def at(self, index): idx -= 1 return next(_iter) except StopIteration: - raise IndexError("%s index out of range" % (self.name,)) + raise IndexError(f"{self.name} index out of range") from None def ord(self, item): """ @@ -3846,7 +3844,7 @@ def at(self, index): idx -= 1 return next(_iter) except StopIteration: - raise IndexError("%s index out of range" % (self.name,)) + raise IndexError(f"{self.name} index out of range") from None def ord(self, item): """ @@ -4128,7 +4126,7 @@ def at(self, index): i -= 1 _ord[i], _idx = _idx % _ord[i], _idx // _ord[i] if _idx: - raise IndexError("%s index out of range" % (self.name,)) + raise IndexError(f"{self.name} index out of range") ans = tuple(s.at(i + 1) for s, i in zip(self._sets, _ord)) if FLATTEN_CROSS_PRODUCT and normalize_index.flatten and self.dimen != len(ans): return self._flatten_product(ans) diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index 4263bdef153..a1072e7156c 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -1530,8 +1530,8 @@ def test_ordered_setof(self): self.assertEqual(i[-1], 0) with self.assertRaisesRegex( IndexError, - "valid index values for Sets are " - r"\[1 .. len\(Set\)\] or \[-1 .. -len\(Set\)\]", + "Accessing Pyomo Sets by position is 1-based: valid Set positional " + r"index values are \[1 .. len\(Set\)\] or \[-1 .. -len\(Set\)\]", ): i[0] with self.assertRaisesRegex(IndexError, "OrderedSetOf index out of range"): @@ -1589,8 +1589,8 @@ def test_ordered_setof(self): self.assertEqual(i[-1], 0) with self.assertRaisesRegex( IndexError, - "valid index values for Sets are " - r"\[1 .. len\(Set\)\] or \[-1 .. -len\(Set\)\]", + "Accessing Pyomo Sets by position is 1-based: valid Set positional " + r"index values are \[1 .. len\(Set\)\] or \[-1 .. -len\(Set\)\]", ): i[0] with self.assertRaisesRegex(IndexError, "OrderedSetOf index out of range"): @@ -1752,8 +1752,8 @@ def test_ord_index(self): self.assertEqual(r[i + 1], v) with self.assertRaisesRegex( IndexError, - "valid index values for Sets are " - r"\[1 .. len\(Set\)\] or \[-1 .. -len\(Set\)\]", + "Accessing Pyomo Sets by position is 1-based: valid Set positional " + r"index values are \[1 .. len\(Set\)\] or \[-1 .. -len\(Set\)\]", ): r[0] with self.assertRaisesRegex( @@ -1769,8 +1769,8 @@ def test_ord_index(self): self.assertEqual(r[i + 1], v) with self.assertRaisesRegex( IndexError, - "valid index values for Sets are " - r"\[1 .. len\(Set\)\] or \[-1 .. -len\(Set\)\]", + "Accessing Pyomo Sets by position is 1-based: valid Set positional " + r"index values are \[1 .. len\(Set\)\] or \[-1 .. -len\(Set\)\]", ): r[0] with self.assertRaisesRegex( @@ -2647,6 +2647,34 @@ def test_infinite_setdifference(self): list(RangeSet(ranges=[NR(0, 2, 0, (True, False))]).ranges()), ) + x = RangeSet(0, 6, 0) - RangeSet(1, 5, 2) + self.assertIs(type(x), SetDifference_InfiniteSet) + self.assertFalse(x.isfinite()) + self.assertFalse(x.isordered()) + + self.assertIn(0, x) + self.assertNotIn(1, x) + self.assertIn(2, x) + self.assertNotIn(3, x) + self.assertIn(4, x) + self.assertNotIn(5, x) + self.assertIn(6, x) + self.assertNotIn(7, x) + + self.assertEqual( + list(x.ranges()), + list( + RangeSet( + ranges=[ + NR(0, 1, 0, (True, False)), + NR(1, 3, 0, (False, False)), + NR(3, 5, 0, (False, False)), + NR(5, 6, 0, (False, True)), + ] + ).ranges() + ), + ) + class TestSetSymmetricDifference(unittest.TestCase): def test_pickle(self): @@ -4191,10 +4219,12 @@ def test_indexing(self): m.I = [1, 3, 2] self.assertEqual(m.I[2], 3) with self.assertRaisesRegex( - IndexError, "I indices must be integers, not float" + IndexError, "Set 'I' positional indices must be integers, not float" ): m.I[2.5] - with self.assertRaisesRegex(IndexError, "I indices must be integers, not str"): + with self.assertRaisesRegex( + IndexError, "Set 'I' positional indices must be integers, not str" + ): m.I['a'] def test_add_filter_validate(self): diff --git a/pyomo/core/tests/unit/test_sets.py b/pyomo/core/tests/unit/test_sets.py index 47cc14ce181..90668a28e72 100644 --- a/pyomo/core/tests/unit/test_sets.py +++ b/pyomo/core/tests/unit/test_sets.py @@ -3395,7 +3395,9 @@ def test_getitem(self): with self.assertRaisesRegex(RuntimeError, ".*before it has been constructed"): a[0] a.construct() - with self.assertRaisesRegex(IndexError, "Pyomo Sets are 1-indexed"): + with self.assertRaisesRegex( + IndexError, "Accessing Pyomo Sets by position is 1-based" + ): a[0] self.assertEqual(a[1], 2)