import networkx as nx
import random as rnd
import matplotlib.pyplot as plt
from tabulate import tabulate
import math
from IPython.display import SVG
import pygraphviz as pgv
Definition Feasible matrix rounding
Original matrix $$\begin{array}{c|ccc} &17.91 & 16.79 & 22.32\\ \hline 23.61 & 9.13 & 9.75 & 4.73\\ 14.74 & 0.42 & 5.23 & 9.09\\ 18.67 & 8.36 & 1.81 & 8.5\\ \end{array}$$
Rounded matrix $$\begin{array}{c|ccc} & 17 & 16 & 22\\ \hline 23 & 9 & 10 & 4\\ 14 & 0 & 5 & 9\\ 18 & 8 & 1 & 9 \\ \end{array}$$
For any matrix $M$ there exists a feasible rounding
The random
package is used to generate a random matrix
numrows = 4
numcols = 5
matrix = [[round(rnd.random()*10,2) for x in range(numcols)] for x in range(numrows)]
sumcol = [0 for j in range(numcols)]
for j in range(numcols):
for i in range(numrows):
sumcol [j] += matrix [i][j]
sumrow = [0 for i in range(numrows)]
for i in range(numrows):
for j in range(numcols):
sumrow [i] += matrix[i][j]
print "Original matrix"
print tabulate(matrix, tablefmt='grid')
print
print "Matrix with columns and rows sum"
table = []
for i in range(numrows):
newrow = matrix[i][:]
newrow.insert(0,sumrow[i])
table.append(newrow)
print tabulate(table, headers = sumcol, tablefmt='grid')
G = nx.DiGraph()
G.add_node('s')
G.add_node('t')
nodecol = ['c'+ str(i) for i in range(numcols)]
noderow = ['r'+ str(i) for i in range(numrows)]
G.add_nodes_from(nodecol)
G.add_nodes_from(noderow)
for i in range(numrows):
G.add_edge ('s',noderow[i],lb=int (math.floor(sumrow[i])), ub= int(math.ceil(sumrow[i])))
for j in range(numcols):
G.add_edge (nodecol[j],'t',lb=int (math.floor(sumcol[j])), ub= int(math.ceil(sumcol[j])))
for i in range(numrows):
for j in range(numcols):
G.add_edge (noderow[i],nodecol[j],lb=int (math.floor(matrix[i][j])), ub= int(math.ceil(matrix[i][j])))
offset = 0.45
count = 0
lenghtcol = 135.0 * float(len(nodecol))
lenghtrow = 135.0 * float(len(noderow))
lenght = max (lenghtcol, lenghtrow)
offsetcol = lenght / len(nodecol)
offsetrow = lenght / len(noderow)
for i in noderow:
G.node[i]['pos'] = "%f,%f"%(200, offsetrow * count)
count += 1
count = 0
for j in nodecol:
G.node[j]['pos'] = "%f,%f"%(500, offsetcol * count)
count += 1
G.node['s']['pos'] = "%f,%f"%(0.0, offset * count * 300 / 2.0)
G.node['t']['pos'] = "%f,%f"%(700, offset * count * 300 /2.0)
for i in G.edges():
G[i[0]][i[1]]['xlabel'] = '%d,%d'%(G[i[0]][i[1]]['lb'],G[i[0]][i[1]]['ub'])
d = nx.to_agraph(G)
d.node_attr.update (fontsize='10', width=0.4, shape='circle')
d.edge_attr.update(fontsize='10', arrowhead='vee', penwidth=0.5)
#d.node_attr['shape']='circle
d.draw ('img.svg', prog='neato', args='-n2')
SVG(filename='img.svg')
#Image ('img.png')
Step 1. Generate a copy $H$ of the graph $G$
Remember The copy()
method makes a complete copy of the graph including all of the node or edge attributes.
H = G.copy()
Step 2. Capacity scaling: capacity of each arc of $H$ is scaled to $u_{ij} - l_{ij}$
for j in H.edges():
H[j[0]][j[1]]['capacity'] = \
H[j[0]][j[1]]['ub'] - H[j[0]][j[1]]['lb']
Step 3. Add an arc between $t$ and $s$ with infinite capacity (i.e., a capacity large enough)
Remark In this case you can assign to the arc $(t,s)$ a capacity equal to the maximum between $u(\delta^+(s))$ and $u(\delta^-(t))$
row_cap = 0
for i in noderow:
row_cap += H['s'][i]['ub']
col_cap = 0
for i in nodecol:
col_cap += H[i]['t']['ub']
H.add_edge ('t','s',capacity=max(row_cap,col_cap))
Step 4. Two extra nodes $s_1$ and $t_1$ are added to $H$
H.add_node('s1')
H.add_node('t1')
Step 5. For each node, the flow unbalance is evaluated
Remark Flow unbalance is stored in a dictionary and is evaluated on graph $G$. By constructions, the sum of flow unbalance is equal to 0
unbalance = {}
for i in G.nodes():
auxunb = 0
for j in G.in_edges(i):
auxunb += G[j[0]][j[1]]['lb']
for j in G.out_edges(i):
auxunb -= G[j[0]][j[1]]['lb']
unbalance[i] = auxunb
Step 6. Two set of arcs are added to $H$:
for i in unbalance:
if unbalance[i] > 0:
H.add_edge ('s1',i,capacity = unbalance[i])
if unbalance[i] < 0:
H.add_edge (i,'t1',capacity =- unbalance[i])
Step 7. Evaluate the maximum $s_1-t_1$ flow
A valid circulation always exists
value, flow = nx.maximum_flow(H,'s1','t1', 'capacity')
for i in H.out_edges('s1'):
print "Flow and capacity of arc (%s,%s):" % (i[0],i[1]), H[i[0]][i[1]]['capacity'], flow[i[0]][i[1]]
if H[i[0]][i[1]]['capacity'] != flow[i[0]][i[1]]:
print "Valid circulation not found"
break
Step 8. Print the rounded matrix
rounded_matrix = []
for i in range(numrows):
rounded_matrix.append([])
for j in range(numcols):
rounded_matrix[i].append(int(math.floor(matrix[i][j])) + \
flow[noderow[i]][nodecol[j]])
sumroundedcol = [0 for j in range(numcols)]
for j in range(numcols):
for i in range(numrows):
sumroundedcol [j] += rounded_matrix [i][j]
sumroundedrow = [0 for i in range(numrows)]
for i in range(numrows):
for j in range(numcols):
sumroundedrow [i] += rounded_matrix[i][j]
print "Original matrix"
table = []
for i in range(numrows):
newrow = matrix[i][:]
newrow.insert(0,sumrow[i])
table.append(newrow)
print tabulate(table, headers = sumcol, tablefmt="grid")
print
print "Rounded matrix"
table = []
for i in range(numrows):
newrow = rounded_matrix[i][:]
newrow.insert(0,sumroundedrow[i])
table.append(newrow)
print tabulate(table, headers = sumroundedcol, tablefmt="grid")