In [118]:
from pylab import *
def one_move_find_best(A, A2, fixed, preserve_degrees=False):
most_wedges_order = argsort(A2.ravel())[::-1]
doorways = array(unravel_index(most_wedges_order, shape(A2)))
already_edges = A[doorways[0], doorways[1]].astype('bool')
self_links = doorways[0]==doorways[1]
doorways = doorways[:,(~self_links * ~already_edges)]
for doorway_index in range(doorways.shape[1]):
doorway = tuple(doorways[:,doorway_index])
# if A[doorway]: #There's already an edge there
# continue
# if doorway[0]==doorway[1]: #The proposed target is a self link
# continue
door = find_door(A, doorway, A2, fixed, preserve_degrees)
if door:
hinge, target, latch = door
A, fixed = swing_door(A, hinge, target, latch, fixed)
return A, hinge, target, latch
return None
def find_door(A, sides_of_doorway, A2,
fixed, preserve_degrees=False):
wedges_across_doorway = A2[sides_of_doorway]
def find_door_helper(hinge, latch, A, A2,
fixed, wedges_across_doorway, preserve_degrees):
latch_degree = A[latch].sum()
neighbors_of_hinge = A[hinge].astype('bool')
neighbors_degrees = A.sum(axis=1)
wedges_across_candidate_doors = A2[hinge]
candidate_doors_not_fixed = ~fixed[hinge]
candidate_doors = (neighbors_of_hinge *
(wedges_across_doorway > wedges_across_candidate_doors) *
candidate_doors_not_fixed
)
if preserve_degrees:
candidate_doors *= (neighbors_degrees == latch_degree + 1) #The node we're taking the link from has exactly one higher degree than the node we're giving the link to)
else:
candidate_doors *= (neighbors_degrees > latch_degree)
if any(candidate_doors):
best_door_for_this_hinge = ma.masked_array(A2[hinge], mask=~candidate_doors.astype('bool')).argmin()
return best_door_for_this_hinge
else:
return None
best_doors = [find_door_helper(sides_of_doorway[0], sides_of_doorway[1], A, A2,
fixed, wedges_across_doorway, preserve_degrees),
find_door_helper(sides_of_doorway[1], sides_of_doorway[0], A, A2,
fixed, wedges_across_doorway, preserve_degrees)]
if best_doors[0] is None and best_doors[1] is None:
return None
elif best_doors[0] is None:
best_door_index = 1
elif best_doors[1] is None:
best_door_index = 0
else:
best_door_index = argmin(A2[sides_of_doorway, best_doors])
hinge = sides_of_doorway[best_door_index]
target_neighbor = best_doors[best_door_index]
latch = sides_of_doorway[not(best_door_index)]
return hinge, target_neighbor, latch
def swing_door(A, hinge, target_neighbor, latch, fixed):
A[hinge, target_neighbor] = 0
A[target_neighbor, hinge] = 0
A[hinge, latch] = 1
A[latch, hinge] = 1
fixed[hinge, latch] = True
fixed[latch, hinge] = True
return A, fixed
def one_move_improve_worst(A, A2, fixed, preserve_degrees=False):
most_wedges_order = argsort(A2.ravel()) ##Sorted from least to most
doors = array(unravel_index(most_wedges_order, shape(A2)))
not_has_edge = ~A[doors[0], doors[1]].astype('bool')
self_links = doors[0]==doors[1]
already_fixed = fixed[doors[0], doors[1]].astype('bool')
doors = doors[:,(~self_links * ~not_has_edge * ~already_fixed)]
for door_index in range(doors.shape[1]):
door = tuple(doors[:,door_index])
# if not A[door]: #There's not an edge there
# continue
# if fixed[door]: #If the edge there has already been moved
# continue
# if door[0]==door[1]: #The proposed target is a self link
# continue
doorway = find_doorway(A, door, A2, fixed, preserve_degrees)
if doorway:
hinge, door_stop, latch = doorway
A, fixed = swing_door(A, hinge, door_stop, latch, fixed)
return A, hinge, door_stop, latch
return None
def find_doorway(A, sides_of_door, A2,
fixed, preserve_degrees=False):
wedges_across_door = A2[sides_of_door]
def find_doorway_helper(hinge, edge_of_door, A, A2,
fixed, wedges_across_door, preserve_degrees):
edge_of_door_degree = A[edge_of_door].sum()
neighbors_of_hinge = A[hinge].astype('bool')
neighbors_degrees = A.sum(axis=1)
wedges_across_candidate_doorways = A2[hinge]
candidate_doorways = (~neighbors_of_hinge *
(wedges_across_door < wedges_across_candidate_doorways)
)
if preserve_degrees:
candidate_doorways *= (neighbors_degrees + 1 == edge_of_door_degree) #The node we're taking the link from has exactly one higher degree than the node we're giving the link to)
else:
candidate_doorways *= (neighbors_degrees < edge_of_door_degree)
candidate_doorways[hinge] = False #Can't connect to itself!
if any(candidate_doorways):
best_doorway_for_this_hinge = ma.masked_array(A2[hinge], mask=~candidate_doorways.astype('bool')).argmax()
return best_doorway_for_this_hinge
else:
return None
best_doorways = [find_doorway_helper(sides_of_door[0], sides_of_door[1], A, A2,
fixed, wedges_across_door, preserve_degrees),
find_doorway_helper(sides_of_door[1], sides_of_door[0], A, A2,
fixed, wedges_across_door, preserve_degrees)]
if best_doorways[0] is None and best_doorways[1] is None:
return None
elif best_doorways[0] is None:
best_doorway_index = 1
elif best_doorways[1] is None:
best_doorway_index = 0
else:
best_doorway_index = argmin(A2[sides_of_door, best_doorways])
hinge = sides_of_door[best_doorway_index]
latch = best_doorways[best_doorway_index]
door_stop = sides_of_door[not(best_doorway_index)]
return hinge, door_stop, latch
def move_edge_A2(A2, A, hinge, doorstop, latch):
#Update the neighborhood of the hinge
A2[hinge] = A2[hinge] + A[latch] - A[doorstop]
A2[:,hinge] = A2[:, hinge] + A[:, latch] - A[:, doorstop]
#Update the neighborhood of the doorstop
A2[doorstop] -= A[hinge]
A2[:,doorstop] -= A[:, hinge]
#Update the neighborhood of the latch
A2[latch] += A[hinge]
A2[:,latch] += A[:, hinge]
#Update the degrees of the hinge, doorstop and latch, compensating for the changes just made
A2[hinge,hinge] -= 2*A[latch,hinge]
A2[doorstop,doorstop] -= 1
A2[latch,latch] -= A[hinge, latch]
#Finally, we have accidentally messed up the entires Aˆ2(j,k) and Aˆ2(k,j) in the row/column updates, so we need to compensate for that:
A2[doorstop, latch] += A[hinge, latch]
A2[latch, doorstop] += A[latch, hinge]
return A2
def number_of_triangles(A, A2):
from networkx import triangles, Graph
return sum(list(triangles(Graph(A)).values()))/3
def number_of_possible_triangles(A, A2):
import networkx as nx
contri=0 # 2 times number of connected triples
for v,d,t in nx.algorithms.cluster._triangles_and_degree_iter(nx.Graph(A)):
contri += d*(d-1)
return float(contri)/2.0
def number_of_triangles_update(nt, A, A2, hinge, doorstop, latch):
return nt + A2[hinge, latch] - A2[hinge,doorstop] + A[latch, doorstop]
def number_of_possible_triangles_update(np, A, A2, hinge, doorstop, latch):
return np + (sum(A[latch])-1) - sum(A[doorstop])
def cluster_rewire_graph(A,
percent_of_edges_to_rewire = 1,
n_trials = None,
rewire_function = one_move_find_best,
verbose = True,
verbose_count = 10,
property_functions = [(number_of_triangles,
number_of_triangles_update),
(number_of_possible_triangles,
number_of_possible_triangles_update)],
preserve_degrees = False):
A2 = array(matrix(A)**2)
A = array(A)
n_edges = A.sum()/2
fixed = zeros(shape(A), dtype=bool)
if property_functions:
n_properties = len(property_functions)
properties = [[] for i in range(n_properties)]
for nth_property in range(n_properties):
prop_fun = property_functions[nth_property]
if not callable(prop_fun):
prop_fun = prop_fun[0]
properties[nth_property].append(prop_fun(A,A2))
if n_trials is None:
n_trials = floor(percent_of_edges_to_rewire*n_edges)
if verbose:
print("Attempting %i edge rewires, out of %i edges"%(n_trials, n_edges))
### Rewire graph
for k in arange(n_trials):
if not k%verbose_count:
if verbose:
print("Rewiring %i out of %i"%(k,n_trials))
outputs = rewire_function(A, A2, fixed, preserve_degrees)
if not outputs:
if verbose:
print("Couldn't make a move!")
break
else:
A, hinge, doorstop, latch = outputs
A2 = move_edge_A2(A2, A, hinge, doorstop, latch)
if property_functions:
for nth_property in range(n_properties):
prop_fun = property_functions[nth_property]
if callable(prop_fun):
updated_property = prop_fun(A, A2)
else:
prop_update_fun = prop_fun[1]
previous_property = properties[nth_property][-1]
updated_property = prop_update_fun(previous_property, A, A2, hinge, doorstop, latch)
properties[nth_property].append(updated_property)
if verbose:
print("Rewired %.1f percent of edges"%(100*float(k)/n_trials))
if property_functions:
return A, properties
else:
return A
In [119]:
def nt_np(G):
triangles=0 # 6 times number of triangles
contri=0 # 2 times number of connected triples
for v,d,t in nx.algorithms.cluster._triangles_and_degree_iter(G):
contri += d*(d-1)
triangles += t
if triangles==0: # we had no triangles or possible triangles
return 0.0, float(contri)
else:
return triangles/6.0, float(contri)/2.0
Out[119]: