# Possibility of indexing decision variables with 2 indices using a set of tuples in Pyomo

I am currently attempting to solve a network problem that is not fully connected.Thus, I have attempted to do some preprocessing of data so as to form a set of tuples, e.g. $$\{(a,b), (c,e),\ldots\}$$, i.e. from $$a$$ to $$b$$, from $$c$$ to $$e$$.

I am able to declare binary decision variables with keys such as $$(a,b)$$, $$(c,e)$$ via using the set of tuples for indexing.

However, when I tried to use rules to declare constraints, with decision variables such as x[i][j], errors are thrown stating that $$(a,b)$$ is an invalid index.

Hence, I would like to ask if tuples can be used as indices for decision variables.

If not, is there a way to only declare the only decision variables that are needed, rather than declaring all, and then setting those unneeded to 0.

Thank you!

It is possible to use tuples as the indices of your variables. If the tuple like $$(a_1, a_2)$$ is not defined in the index set, errors will be thrown but you can skip those undefined indices by using:

Constraint.Skip


checking if the tuple is defined or not. An example of implementation would be as follow:

model.cons = ConstraintList()
for i in model.nodes:
for j in model.nodes:
if [i,j] in tuples_list:
else:
Constraint.Skip

• Dear Oguz, thank you for your reply. I suppose what you have shown is for concrete models. Could you help me by showing one for abstract models? Thanm you!
– Mike
Jul 23 '20 at 1:27
• Thanks! I managed to resolve the issue by first iterating to create a set of tuples that will be used for indexing and then invoke the constraint rule via checking all tuples against the set of tuples created for indexing.
– Mike
Jul 24 '20 at 2:41
• Good job. When I mentioned tuple_list, I meant a predefined set of possible tuples... Jul 24 '20 at 2:46
• @Mike if my answers to your questions were useful you can upvote them. This way others with a similar questions can easily find appropriate answers. Jul 24 '20 at 2:49

Yes. Totally doable. Here are 2 examples using either tuples in a pyomo set or just making some up on-the-fly and passing them to a rule-based constraint to make the appropriate number of sparse constraints (seen in the result).

# subsets in tuples

import pyomo.environ as pyo

mdl = pyo.ConcreteModel()

# sets
mdl.I = pyo.Set(initialize=range(4))
mdl.J = pyo.Set(initialize=range(3))
mdl.select_combos = pyo.Set(within = mdl.I * mdl.J, initialize = [(1,2), (3,1)])

# vars
mdl.X = pyo.Var(mdl.I, mdl.J, domain=pyo.NonNegativeReals)

# constraint with rule and tuples from pyomo Set
def c1(self, i, j):
return mdl.X[i, j] <= 2
mdl.c1 = pyo.Constraint(mdl.select_combos, rule=c1)

# or make a set of tuples of interest on the fly
my_combos = {(i, j) for i in mdl.I for j in mdl.J if
i <=2 and
j >=2 }
def c2(self, i, j):
return mdl.X[i, j] >= 1
mdl.C2 = pyo.Constraint(my_combos, rule=c2)

mdl.pprint()


### Output:

6 Set Declarations
C2_index : Dim=0, Dimen=2, Size=3, Domain=None, Ordered=False, Bounds=None
[(0, 2), (1, 2), (2, 2)]
I : Dim=0, Dimen=1, Size=4, Domain=None, Ordered=False, Bounds=(0, 3)
[0, 1, 2, 3]
J : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(0, 2)
[0, 1, 2]
X_index : Dim=0, Dimen=2, Size=12, Domain=None, Ordered=False, Bounds=None
Virtual
select_combos : Dim=0, Dimen=2, Size=2, Domain=select_combos_domain, Ordered=False, Bounds=None
[(1, 2), (3, 1)]
select_combos_domain : Dim=0, Dimen=2, Size=12, Domain=None, Ordered=False, Bounds=None
Virtual

1 Var Declarations
X : Size=12, Index=X_index
Key    : Lower : Value : Upper : Fixed : Stale : Domain
(0, 0) :     0 :  None :  None : False :  True : NonNegativeReals
(0, 1) :     0 :  None :  None : False :  True : NonNegativeReals
(0, 2) :     0 :  None :  None : False :  True : NonNegativeReals
(1, 0) :     0 :  None :  None : False :  True : NonNegativeReals
(1, 1) :     0 :  None :  None : False :  True : NonNegativeReals
(1, 2) :     0 :  None :  None : False :  True : NonNegativeReals
(2, 0) :     0 :  None :  None : False :  True : NonNegativeReals
(2, 1) :     0 :  None :  None : False :  True : NonNegativeReals
(2, 2) :     0 :  None :  None : False :  True : NonNegativeReals
(3, 0) :     0 :  None :  None : False :  True : NonNegativeReals
(3, 1) :     0 :  None :  None : False :  True : NonNegativeReals
(3, 2) :     0 :  None :  None : False :  True : NonNegativeReals

2 Constraint Declarations
C2 : Size=3, Index=C2_index, Active=True
Key    : Lower : Body   : Upper : Active
(0, 2) :   1.0 : X[0,2] :  +Inf :   True
(1, 2) :   1.0 : X[1,2] :  +Inf :   True
(2, 2) :   1.0 : X[2,2] :  +Inf :   True
c1 : Size=2, Index=select_combos, Active=True
Key    : Lower : Body   : Upper : Active
(1, 2) :  -Inf : X[1,2] :   2.0 :   True
(3, 1) :  -Inf : X[3,1] :   2.0 :   True

9 Declarations: I J select_combos_domain select_combos X_index X c1 C2_index C2
[Finished in 2.5s]

• I guess the initialized set in mdl.select_combos = pyo.Set(within = mdl.I * mdl.J, initialize = [(1,2), (3,1)]) would be similar to the tuple set generating for loop in my code.
– Mike
Jul 24 '20 at 6:22
• Great. If you found any of these answers helpful, it helps to "accept" them with the checkmark to show them as complete, including on the linked question on other site. Jul 24 '20 at 15:49
• Dear Jeff, I have already done so before your message. However, as my reputation points are too low, they are not reflected publicly. Thanks.
– Mike
Jul 24 '20 at 16:51
• All good. You should still see a shadowed "check mark" to accept the answer as the original asker. None of these answers appears to have been accepted yet. You only need points to up/down, not to "accept". And yeah, I did find out how that you could change username, so I upgraded....lol Jul 24 '20 at 16:53
• Dear Jeff, thank you for the reminder. Have done so by clicking on the checkmarks.
– Mike
Jul 24 '20 at 17:34

An example is as follows:

##First, create the set of tuples needed for filtering

#Op_Machine: set of (operation, machine) tuples created to avoid redundancy in decision variable declaration Op_Machine=list() for machine_id, op_proctime in Machine_Op_Time.items():
for op in op_proctime.keys():
print(Op_Machine)
print((op,machine_id))
Op_Machine.append((op,machine_id))
print(Op_Machine)

##Next, invoke the rule using the if statement to filter across all possible indices accepting those combinations that are aligned with the tuples within the set
##Use Constraint.Skip to Skip creating constraints that do not belong to the set of tuples

def F1_rule(model,i,k):
if (i,k) in Op_Machine:
##print(i,k)
return model.Cmax>=model.completion_time[i,k]
else:
return Constraint.Skip

#model.makespan= Constraint(model.op_set, model.mach_set, rule=Cmax_rule) model.F1= Constraint(Operation_Set, Machine_Set, rule=F1_rule)


Note that the sets Operation_Set, Machine_Set function as the universal set as it comprises all combinations of operations and machines. Hence the statement model.F1= Constraint(Operation_Set, Machine_Set, rule=F1_rule) can be thought a for loop that iterates over all combinations while the if statement within the def function acts a filter to generate the needed constraints.