Reputation: 267
Firstly, this may not be the best forum, so apologies if that's the case. I am creating a Pyomo model, where I have created a binary matrix as follows:
model.binMat = Var(range(6),range(6),domain=Binary)
My model solves for this matrix, with a typical output like this:
binaryMatrix = [[0 1 0 1 0 0]
[1 0 1 0 0 0]
[0 1 0 0 0 1]
[1 0 0 0 1 0]
[0 0 0 1 0 1]
[0 0 1 0 1 0]]
Results are interpreted as the coordinates of the 1's, i.e. (1,2),(1,4),(2,1),(2,3),(3,2),(3,6),(4,1),(4,5),(5,4),(5,6),(6,3),(6,5) in this example.
This is then thought of in terms of groups of connected elements. In this case, there would only be 1 unique group: (1,2,3,4,5,6).
I would like help to create a new constraint to only allow 2 unique groups that are equally sized by referencing the values in model.binMat.
An example of what these final groups could look like is: (1,5,6) and (2,3,4). The corresponding coordinates for this could be: (1,5),(1,6),(2,3),(2,4),(3,2),(3,4),(4,2),(4,3),(5,1),(5,6),(6,1),(6,5)
I am currently attempting to solve this using Pyomo sets, but as these are new to me, I haven't had any luck.
For those interested in alternative approaches to the same problem, I also posted this here
Upvotes: 2
Views: 566
Reputation: 1336
There may be a simpler way, but the best way I could think of is to add binary constraints to check each possible such set and force one of those sets of equally sized unique components to be chosen. Note, this approach results in an exponential number of constraints so it's not a good solution for larger problems.
import pyomo.environ as pyo
import itertools
nodes = set(range(6))
# the possible sets of components of length 3
full_comp_list = [(set(i),nodes-set(i)) for i in itertools.combinations(nodes,3)]
# only take the first half because it's symmetric with six nodes and equal size
comp_list = full_comp_list[:int(len(full_comp_list)/2)]
num_poss_component_sets = len(comp_list)
#%% Build model
model = pyo.ConcreteModel()
model.binMat = pyo.Var(nodes,nodes,domain=pyo.Binary)
#%% Additional Variables
# binaries to track if each component connected
model.comp1_connected= pyo.Var(range(num_poss_component_sets),within=pyo.Binary)
model.comp2_connected= pyo.Var(range(num_poss_component_sets),within=pyo.Binary)
# tracks if the two components are disjoint
model.comps_disjoint = pyo.Var(range(num_poss_component_sets),within=pyo.Binary)
# tracks if the criteria met for any set of components
model.meet_criteria = pyo.Var(range(num_poss_component_sets),within=pyo.Binary)
#%% Additional constraints
def is_comp1_connected_rule(model,comp_num):
''' The component is complete iff the number of (directed) edges between ==6 (all three undirected edges selected)'''
return(sum(model.binMat[i,j] for i,j in itertools.combinations(comp_list[comp_num][0],2))
>=3*model.comp1_connected[comp_num])
model.comp1_connected_constraint = pyo.Constraint(range(num_poss_component_sets),
rule=is_comp1_connected_rule)
# Check if each component set is a complete graph
def is_comp2_connected_rule(model,comp_num):
''' The component is complete iff the number of (directed) edges between == 6 (all three undirected edges selected)'''
return(sum(model.binMat[i,j] for i,j in itertools.combinations(comp_list[comp_num][1],2))
>= 3*model.comp2_connected[comp_num])
model.comp2_connected_constraint = pyo.Constraint(range(num_poss_component_sets),
rule=is_comp2_connected_rule)
# Check if components are separate from each other (no edges between)
def are_both_disjoint_rule(model,comp_num):
'''Disjoint if no edges between any nodes in different component
If there are ANY edges between, then not disjoint (model.both_comps_connected must be 0)
'''
return(sum([model.binMat[i,j] for i in comp_list[comp_num][0] for j in comp_list[comp_num][1]])
<= 9 * (1-model.comps_disjoint[comp_num]))
model.comps_disjoint_constraint = pyo.Constraint(range(num_poss_component_sets),
rule=are_both_disjoint_rule)
# Determines if a given set of components meet the rule
def meet_criteria_rule(model,comp_num):
'''Rule met if both components are connected and separate from each other'''
return(model.comp1_connected[comp_num] + model.comp2_connected[comp_num]
+ model.comps_disjoint[comp_num] >= 3 * model.meet_criteria[comp_num])
model.comp_meets_criteria_constraint = pyo.Constraint(range(num_poss_component_sets),
rule=meet_criteria_rule)
# at least one component must meet rule that theyre separate connected components
model.must_meet_criteria_constraint = pyo.Constraint(expr = sum(model.meet_criteria[comp_num]
for comp_num in range(num_poss_component_sets)) >= 1)
### New constraint to make adjacency matrix symmetric (binMat_{i,j} == binMat_{j,i})
def edges_symmetric_rule(model,node1,node2):
'''Rule requiring both directions for edges to be the same'''
return(model.binMat[node1,node2] == model.binMat[node2,node1])
model.edges_symmetric_constraint = pyo.Constraint(nodes,nodes,rule=edges_symmetric_rule)
#%% Add objective and solve
des_edges = [(4,0),(1,2),(1,3),(2,1),(2,3),(3,1),(3,2)]
pos_c_dict = {e:1 for e in des_edges}
c = [[pos_c_dict.get((i,j),-1) for i in nodes] for j in nodes]
model.obj = pyo.Objective(expr = sum([c[i][j]*model.binMat[i,j] for i in nodes for j in nodes]),
sense=pyo.maximize)
solver = pyo.SolverFactory('glpk')
res = solver.solve(model)
# get the components and the index for what's chosen
[comp_list[i] for i in range(len(comp_list)) if pyo.value(model.meet_criteria[i])]
# [({0, 4, 5}, {1, 2, 3})]
[i for i in range(len(comp_list)) if pyo.value(model.meet_criteria[i])]
# 9
# View the final binMat
final_binMat = pd.DataFrame({'source':list(nodes)*len(nodes),
'target':[j for i in nodes for j in [i]*len(nodes)]})
final_binMat['value'] = [pyo.value(model.binMat[i,j]) for i,j in final_binMat.values]
final_binMat['cost'] = [c[i][j] for i,j in final_binMat[['source','target']].values]
final_binMat_wide = pd.pivot(data=final_binMat,index='source',columns='target',values='value')
# target 0 1 2 3 4 5
# source
# 0 0.0 0.0 0.0 0.0 1.0 1.0
# 1 0.0 0.0 1.0 1.0 0.0 0.0
# 2 0.0 1.0 0.0 1.0 0.0 0.0
# 3 0.0 1.0 1.0 0.0 0.0 0.0
# 4 1.0 0.0 0.0 0.0 0.0 1.0
# 5 1.0 0.0 0.0 0.0 1.0 0.0
Upvotes: 3