diff --git a/examples/pyomo/tutorials/set.dat b/examples/pyomo/tutorials/set.dat index ab0d00b43cc..e2ad04122d8 100644 --- a/examples/pyomo/tutorials/set.dat +++ b/examples/pyomo/tutorials/set.dat @@ -16,3 +16,6 @@ set S[5] := 2 3; set T[2] := 1 3; set T[5] := 2 3; + +set X[2] := 1; +set X[5] := 2 3; \ No newline at end of file diff --git a/examples/pyomo/tutorials/set.out b/examples/pyomo/tutorials/set.out index 818977f6155..dd1ef2d4335 100644 --- a/examples/pyomo/tutorials/set.out +++ b/examples/pyomo/tutorials/set.out @@ -1,4 +1,4 @@ -23 Set Declarations +24 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {1, 2, 3} @@ -89,5 +89,9 @@ 2 : 1 : Any : 5 : {1, 3, 5, 7, 9} 3 : 1 : Any : 5 : {1, 4, 7, 10, 13} 4 : 1 : Any : 5 : {1, 5, 9, 13, 17} + X : Size=2, Index=B, Ordered=Insertion + Key : Dimen : Domain : Size : Members + 2 : 1 : S[2] : 1 : {1,} + 5 : 1 : S[5] : 2 : {2, 3} -23 Declarations: A B C D E F G H Hsub I J K K_2 L M N O P R S T U V +24 Declarations: A B C D E F G H Hsub I J K K_2 L M N O P R S X T U V diff --git a/examples/pyomo/tutorials/set.py b/examples/pyomo/tutorials/set.py index a14301484c9..c1ea60b48ad 100644 --- a/examples/pyomo/tutorials/set.py +++ b/examples/pyomo/tutorials/set.py @@ -171,6 +171,13 @@ def P_init(model, i, j): # model.S = Set(model.B, within=model.A) +# +# Validation of a set array can also be linked to another set array. If so, the +# elements under each index must also be found under the corresponding index in +# the validation set array: +# +model.X = Set(model.B, within=model.S) + # # Validation of set arrays can also be performed with the _validate_ option. diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index c0d427747c5..049775fd9dd 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -1932,7 +1932,8 @@ class Set(IndexedComponent): within : initialiser(set), optional A set that defines the valid values that can be contained - in this set + in this set. If the latter is indexed, the former can be indexed or + non-indexed, in which case it applies to all indices. domain : initializer(set), optional A set that defines the valid values that can be contained in this set @@ -2218,7 +2219,7 @@ def _getitem_when_not_present(self, index): domain = self._init_domain(_block, index, self) if domain is not None: - domain.construct() + domain.parent_component().construct() if _d is UnknownSetDimen and domain is not None and domain.dimen is not None: _d = domain.dimen diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index f62589a6873..2b0da8b861d 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -4543,9 +4543,11 @@ def test_construction(self): m.I = Set(initialize=[1, 2, 3]) m.J = Set(initialize=[4, 5, 6]) m.K = Set(initialize=[(1, 4), (2, 6), (3, 5)], within=m.I * m.J) + m.L = Set(initialize=[1, 3], within=m.I) m.II = Set([1, 2, 3], initialize={1: [0], 2: [1, 2], 3: range(3)}) m.JJ = Set([1, 2, 3], initialize={1: [0], 2: [1, 2], 3: range(3)}) m.KK = Set([1, 2], initialize=[], dimen=lambda m, i: i) + m.LL = Set([2, 3], within=m.II, initialize={2: [1, 2], 3: [1]}) output = StringIO() m.I.pprint(ostream=output) @@ -4569,6 +4571,8 @@ def test_construction(self): 'I': [-1, 0], 'II': {1: [10, 11], 3: [30]}, 'K': [-1, 4, -1, 6, 0, 5], + 'L': [-1], + 'LL': {3: [30]}, } } ) @@ -4576,6 +4580,7 @@ def test_construction(self): self.assertEqual(list(i.I), [-1, 0]) self.assertEqual(list(i.J), [4, 5, 6]) self.assertEqual(list(i.K), [(-1, 4), (-1, 6), (0, 5)]) + self.assertEqual(list(i.L), [-1]) self.assertEqual(list(i.II[1]), [10, 11]) self.assertEqual(list(i.II[3]), [30]) self.assertEqual(list(i.JJ[1]), [0]) @@ -4583,9 +4588,11 @@ def test_construction(self): self.assertEqual(list(i.JJ[3]), [0, 1, 2]) self.assertEqual(list(i.KK[1]), []) self.assertEqual(list(i.KK[2]), []) + self.assertEqual(list(i.LL[3]), [30]) # Implicitly-constructed set should fall back on initialize! self.assertEqual(list(i.II[2]), [1, 2]) + self.assertEqual(list(i.LL[2]), [1, 2]) # Additional tests for tuplize: i = m.create_instance(data={None: {'K': [(1, 4), (2, 6)], 'KK': [1, 4, 2, 6]}}) @@ -6388,3 +6395,209 @@ def test_issue_1112(self): self.assertEqual(len(vals), 1) self.assertIsInstance(vals[0], SetProduct_OrderedSet) self.assertIsNot(vals[0], cross) + + def test_issue_3284(self): + # test creating (indexed and non-indexed) sets using the within argument + # using concrete model and initialization + problem = ConcreteModel() + # non-indexed sets not using the within argument + problem.A = Set(initialize=[1, 2, 3]) + problem.B = Set(dimen=2, initialize=[(1, 2), (3, 4), (5, 6)]) + # non-indexed sets using within argument + problem.subset_A = Set(within=problem.A, initialize=[2, 3]) + problem.subset_B = Set(within=problem.B, dimen=2, initialize=[(1, 2), (5, 6)]) + # indexed sets not using the within argument + problem.C = Set(problem.A, initialize={1: [-1, 3], 2: [4, 7], 3: [3, 8]}) + problem.D = Set( + problem.B, initialize={(1, 2): [1, 5], (3, 4): [3], (5, 6): [6, 8, 9]} + ) + # indexed sets using an indexed set for the within argument + problem.subset_C = Set( + problem.A, within=problem.C, initialize={1: [-1], 2: [4], 3: [3, 8]} + ) + problem.subset_D = Set( + problem.B, + within=problem.D, + initialize={(1, 2): [1, 5], (3, 4): [], (5, 6): [6]}, + ) + # indexed sets using a non-indexed set for the within argument + problem.E = Set([0, 1], within=problem.A, initialize={0: [1, 2], 1: [3]}) + problem.F = Set( + [(1, 2, 3), (4, 5, 6)], + within=problem.B, + initialize={(1, 2, 3): [(1, 2)], (4, 5, 6): [(3, 4)]}, + ) + # check them + self.assertEqual(list(problem.A), [1, 2, 3]) + self.assertEqual(list(problem.B), [(1, 2), (3, 4), (5, 6)]) + self.assertEqual(list(problem.subset_A), [2, 3]) + self.assertEqual(list(problem.subset_B), [(1, 2), (5, 6)]) + self.assertEqual(list(problem.C[1]), [-1, 3]) + self.assertEqual(list(problem.C[2]), [4, 7]) + self.assertEqual(list(problem.C[3]), [3, 8]) + self.assertEqual(list(problem.D[(1, 2)]), [1, 5]) + self.assertEqual(list(problem.D[(3, 4)]), [3]) + self.assertEqual(list(problem.D[(5, 6)]), [6, 8, 9]) + self.assertEqual(list(problem.subset_C[1]), [-1]) + self.assertEqual(list(problem.subset_C[2]), [4]) + self.assertEqual(list(problem.subset_C[3]), [3, 8]) + self.assertEqual(list(problem.subset_D[(1, 2)]), [1, 5]) + self.assertEqual(list(problem.subset_D[(3, 4)]), []) + self.assertEqual(list(problem.subset_D[(5, 6)]), [6]) + self.assertEqual(list(problem.E[0]), [1, 2]) + self.assertEqual(list(problem.E[1]), [3]) + self.assertEqual(list(problem.F[(1, 2, 3)]), [(1, 2)]) + self.assertEqual(list(problem.F[(4, 5, 6)]), [(3, 4)]) + + # try adding elements to test the domains (1 compatible, 1 incompatible) + # set subset_A + problem.subset_A.add(1) + error_message = ( + "Cannot add value 4 to Set subset_A.\n\tThe value is not in the domain A" + ) + with self.assertRaisesRegex(ValueError, error_message): + problem.subset_A.add(4) + # set subset_B + problem.subset_B.add((3, 4)) + with self.assertRaisesRegex(ValueError, r".*Cannot add value \(7, 8\)"): + problem.subset_B.add((7, 8)) + # set subset_C + problem.subset_C[2].add(7) + with self.assertRaisesRegex(ValueError, ".*Cannot add value 8 to Set"): + problem.subset_C[2].add(8) + # set subset_D + problem.subset_D[(5, 6)].add(9) + with self.assertRaisesRegex(ValueError, ".*Cannot add value 2 to Set"): + problem.subset_D[(3, 4)].add(2) + # set E + problem.E[1].add(2) + with self.assertRaisesRegex(ValueError, ".*Cannot add value 4 to Set"): + problem.E[1].add(4) + # set F + problem.F[(1, 2, 3)].add((3, 4)) + with self.assertRaisesRegex(ValueError, r".*Cannot add value \(4, 3\)"): + problem.F[(4, 5, 6)].add((4, 3)) + # check them + self.assertEqual(list(problem.A), [1, 2, 3]) + self.assertEqual(list(problem.B), [(1, 2), (3, 4), (5, 6)]) + self.assertEqual(list(problem.subset_A), [2, 3, 1]) + self.assertEqual(list(problem.subset_B), [(1, 2), (5, 6), (3, 4)]) + self.assertEqual(list(problem.C[1]), [-1, 3]) + self.assertEqual(list(problem.C[2]), [4, 7]) + self.assertEqual(list(problem.C[3]), [3, 8]) + self.assertEqual(list(problem.D[(1, 2)]), [1, 5]) + self.assertEqual(list(problem.D[(3, 4)]), [3]) + self.assertEqual(list(problem.D[(5, 6)]), [6, 8, 9]) + self.assertEqual(list(problem.subset_C[1]), [-1]) + self.assertEqual(list(problem.subset_C[2]), [4, 7]) + self.assertEqual(list(problem.subset_C[3]), [3, 8]) + self.assertEqual(list(problem.subset_D[(1, 2)]), [1, 5]) + self.assertEqual(list(problem.subset_D[(3, 4)]), []) + self.assertEqual(list(problem.subset_D[(5, 6)]), [6, 9]) + self.assertEqual(list(problem.E[0]), [1, 2]) + self.assertEqual(list(problem.E[1]), [3, 2]) + self.assertEqual(list(problem.F[(1, 2, 3)]), [(1, 2), (3, 4)]) + self.assertEqual(list(problem.F[(4, 5, 6)]), [(3, 4)]) + + # using abstract model and no initialization + model = AbstractModel() + # non-indexed sets not using the within argument + model.A = Set() + model.B = Set(dimen=2) + # non-indexed sets using within argument + model.subset_A = Set(within=model.A) + model.subset_B = Set(within=model.B, dimen=2) + # indexed sets not using the within argument + model.C = Set(model.A) + model.D = Set(model.B) + # indexed sets using an indexed set for the within argument + model.subset_C = Set(model.A, within=model.C) + model.subset_D = Set(model.B, within=model.D) + # indexed sets using a non-indexed set for the within argument + model.E_index = Set() + model.F_index = Set() + model.E = Set(model.E_index, within=model.A) + model.F = Set(model.F_index, within=model.B) + problem = model.create_instance( + data={ + None: { + 'A': [3, 4, 5], + 'B': [(1, 2), (7, 8)], + 'subset_A': [3, 4], + 'subset_B': [(1, 2)], + 'C': {3: [3], 4: [4, 8], 5: [5, 6]}, + 'D': {(1, 2): [2], (7, 8): [0, 1]}, + 'subset_C': {3: [3], 4: [8], 5: []}, + 'subset_D': {(1, 2): [], (7, 8): [0, 1]}, + 'E_index': [0, 1], + 'F_index': [(1, 2, 3), (4, 5, 6)], + 'E': {0: [3, 4], 1: [5]}, + 'F': {(1, 2, 3): [(1, 2)], (4, 5, 6): [(7, 8)]}, + } + } + ) + + # check them + self.assertEqual(list(problem.A), [3, 4, 5]) + self.assertEqual(list(problem.B), [(1, 2), (7, 8)]) + self.assertEqual(list(problem.subset_A), [3, 4]) + self.assertEqual(list(problem.subset_B), [(1, 2)]) + self.assertEqual(list(problem.C[3]), [3]) + self.assertEqual(list(problem.C[4]), [4, 8]) + self.assertEqual(list(problem.C[5]), [5, 6]) + self.assertEqual(list(problem.D[(1, 2)]), [2]) + self.assertEqual(list(problem.D[(7, 8)]), [0, 1]) + self.assertEqual(list(problem.subset_C[3]), [3]) + self.assertEqual(list(problem.subset_C[4]), [8]) + self.assertEqual(list(problem.subset_C[5]), []) + self.assertEqual(list(problem.subset_D[(1, 2)]), []) + self.assertEqual(list(problem.subset_D[(7, 8)]), [0, 1]) + self.assertEqual(list(problem.E[0]), [3, 4]) + self.assertEqual(list(problem.E[1]), [5]) + self.assertEqual(list(problem.F[(1, 2, 3)]), [(1, 2)]) + self.assertEqual(list(problem.F[(4, 5, 6)]), [(7, 8)]) + + # try adding elements to test the domains (1 compatible, 1 incompatible) + # set subset_A + problem.subset_A.add(5) + with self.assertRaisesRegex(ValueError, ".*Cannot add value "): + problem.subset_A.add(6) + # set subset_B + problem.subset_B.add((7, 8)) + with self.assertRaisesRegex(ValueError, ".*Cannot add value "): + problem.subset_B.add((3, 4)) + # set subset_C + problem.subset_C[4].add(4) + with self.assertRaisesRegex(ValueError, ".*Cannot add value "): + problem.subset_C[4].add(9) + # set subset_D + problem.subset_D[(1, 2)].add(2) + with self.assertRaisesRegex(ValueError, ".*Cannot add value "): + problem.subset_D[(1, 2)].add(3) + # set E + problem.E[1].add(4) + with self.assertRaisesRegex(ValueError, ".*Cannot add value "): + problem.E[1].add(1) + # set F + problem.F[(1, 2, 3)].add((7, 8)) + with self.assertRaisesRegex(ValueError, ".*Cannot add value "): + problem.F[(4, 5, 6)].add((4, 3)) + # check them + self.assertEqual(list(problem.A), [3, 4, 5]) + self.assertEqual(list(problem.B), [(1, 2), (7, 8)]) + self.assertEqual(list(problem.subset_A), [3, 4, 5]) + self.assertEqual(list(problem.subset_B), [(1, 2), (7, 8)]) + self.assertEqual(list(problem.C[3]), [3]) + self.assertEqual(list(problem.C[4]), [4, 8]) + self.assertEqual(list(problem.C[5]), [5, 6]) + self.assertEqual(list(problem.D[(1, 2)]), [2]) + self.assertEqual(list(problem.D[(7, 8)]), [0, 1]) + self.assertEqual(list(problem.subset_C[3]), [3]) + self.assertEqual(list(problem.subset_C[4]), [8, 4]) + self.assertEqual(list(problem.subset_C[5]), []) + self.assertEqual(list(problem.subset_D[(1, 2)]), [2]) + self.assertEqual(list(problem.subset_D[(7, 8)]), [0, 1]) + self.assertEqual(list(problem.E[0]), [3, 4]) + self.assertEqual(list(problem.E[1]), [5, 4]) + self.assertEqual(list(problem.F[(1, 2, 3)]), [(1, 2), (7, 8)]) + self.assertEqual(list(problem.F[(4, 5, 6)]), [(7, 8)])