def GetStaticEigenvectorCentrality(t, model='SECOND'): """Computes eigenvector centralities of nodes in the second-order aggregate network, and aggregates eigenvector centralities to obtain the eigenvector centrality of nodes in the first-order network. @param t: The temporalnetwork instance to work on @param model: either C{"SECOND"} or C{"NULL"}, where C{"SECOND"} is the the default value.""" if (model is "SECOND" or "NULL") == False: raise ValueError("model must be one of \"SECOND\" or \"NULL\"") name_map = Utilities.firstOrderNameMap(t) if model == 'SECOND': g2 = t.igraphSecondOrder() else: g2 = t.igraphSecondOrderNull() # Compute eigenvector centrality in second-order network A = Utilities.getSparseAdjacencyMatrix(g2, attribute="weight", transposed=True) evcent_2 = Utilities.StationaryDistribution(A, False) # Aggregate to obtain first-order eigenvector centrality evcent_1 = np.zeros(len(name_map)) sep = t.separator for i in range(len(evcent_2)): # Get name of target node target = g2.vs()[i]["name"].split(sep)[1] evcent_1[name_map[target]] += np.real(evcent_2[i]) return np.real(evcent_1 / sum(evcent_1))
def RWDiffusion(g, samples=5, epsilon=0.01, max_iterations=100000): """Computes the average number of steps requires by a random walk process to fall below a total variation distance below epsilon (TVD computed between the momentary visitation probabilities \pi^t and the stationary distribution \pi = \pi^{\infty}. This time can be used to measure diffusion speed in a given (weighted and directed) network.""" avg_speed = 0 T = Utilities.RWTransitionMatrix(g) pi = Utilities.StationaryDistribution(T) n = len(g.vs()) for s in range(samples): x = np.zeros(n) seed = np.random.randint(n) x[seed] = 1 while Utilities.TVD(x, pi) > epsilon: avg_speed += 1 # NOTE x * T = (T^T * x^T)^T # NOTE T is already transposed to get the left EV x = (T.dot(x.transpose())).transpose() if avg_speed > max_iterations: Log.add("x[0:10] = " + str(x[0:10])) Log.add("pi[0:10] = " + str(pi[0:10])) raise RuntimeError( "Failed to converge within maximal number of iterations. Start of current x and pi are printed above" ) return avg_speed / samples
def exportDiffusionMovieFrames(g, file_prefix='diffusion', visual_style=None, steps=100, initial_index=-1): """Exports an animation showing the evolution of a diffusion process on the network""" T = Utilities.RWTransitionMatrix(g) if visual_style == None: visual_style = {} visual_style["vertex_color"] = "lightblue" visual_style["vertex_label"] = g.vs["name"] visual_style["edge_curved"] = .5 visual_style["vertex_size"] = 30 # lambda expression for the coloring of nodes according to some quantity p \in [0,1] # p = 1 ==> color red # p = 0 ==> color white color_p = lambda p: "rgb(255," + str(int( (1 - p) * 255)) + "," + str(int((1 - p) * 255)) + ")" # Initial state of random walker if initial_index < 0: initial_index = np.random.randint(0, len(g.vs())) x = np.zeros(len(g.vs())) x[initial_index] = 1 # compute stationary state pi = Utilities.StationaryDistribution(T) scale = np.mean(np.abs(x - pi)) # Plot network (useful as poster frame of video) igraph.plot(g, file_prefix + "_network.pdf", **visual_style) # Create frames for i in range(0, steps): visual_style["vertex_color"] = [color_p(p**0.1) for p in x] igraph.plot(g, file_prefix + "_frame_" + str(i).zfill(5) + ".png", **visual_style) if i % 10 == 0: Log.add('Step' + str(i) + '\tTVD = ' + str(Utilities.TVD(x, pi)), Severity.INFO) # NOTE x * T = (T^T * x^T)^T # NOTE T is already transposed to get the left EV x = (T.dot(x.transpose())).transpose()
def exportDiffusionMovieFramesFirstOrder(t, file_prefix='diffusion', visual_style=None, steps=100, initial_index=-1, model='SECOND', dynamic=False, NWframesPerRWStep=5): """Exports an animation showing the evolution of a diffusion process on the first-order aggregate network, where random walk dynamics either follows a first-order (mode='NULL') or second-order (model='SECOND') Markov model""" assert model == 'SECOND' or model == 'NULL' g1 = t.igraphFirstOrder() if model == 'SECOND': g2 = t.igraphSecondOrder() temporal = tn.TemporalNetwork.ShuffleTwoPaths(t, l=steps) elif model == 'NULL': g2 = t.igraphSecondOrderNull() temporal = tn.TemporalNetwork.ShuffleEdges(t, l=steps) T = Utilities.RWTransitionMatrix(g2) # visual style is for *first-order* aggregate network if visual_style == None: visual_style = {} visual_style["vertex_color"] = "lightblue" visual_style["vertex_label"] = g1.vs["name"] visual_style["edge_curved"] = .5 visual_style["vertex_size"] = 30 visual_style["edge_color"] = ["darkgrey"] * g1.ecount() visual_style["edge_width"] = [.5] * g1.ecount() # Initial state of random walker if initial_index < 0: initial_index = np.random.randint(0, len(g2.vs())) exp = 1.0 / .75 x = np.zeros(len(g2.vs())) x[initial_index] = 1 # This index allows to quickly map node names to indices in the first-order network map_name_to_id = {} for i in range(len(g1.vs())): map_name_to_id[g1.vs()['name'][i]] = i # Index to quickly map second-order node indices to first-order node indices map_2_to_1 = {} for j in range(len(g2.vs())): # j is index of node in *second-order* network # we first get the name of the *target* of the underlying edge node = g2.vs()["name"][j].split(t.separator)[1] # we map the target of second-order node j to the index of the *first-order* node map_2_to_1[j] = map_name_to_id[node] # compute stationary state of random walk process pi = Utilities.StationaryDistribution(T) scale = np.mean(np.abs(x - pi)) # lambda expression for the coloring of nodes according to some quantity p \in [0,1] # p = 1 ==> color red # p = 0 ==> color white color_p = lambda p: "rgb(255," + str(int( (1 - p) * 255)) + "," + str(int((1 - p) * 255)) + ")" visual_style["edge_color"] = ["darkgrey"] * g1.ecount() visual_style["edge_width"] = [.5] * g1.ecount() # Create video frames for i in range(0, steps): # based on visitation probabilities in *second-order* aggregate network, # we need to compute visitation probabilities of nodes in the *first-order* # aggregate network x_firstorder = np.zeros(len(g1.vs())) # j is the index of nodes in the *second-order* network, which we need to map # to nodes in the *first-order* network for j in range(len(x)): if x[j] > 0: x_firstorder[ map_2_to_1[j]] = x_firstorder[map_2_to_1[j]] + x[j] # Perform some reasonable color scaling visual_style["vertex_color"] = [ color_p(np.power((p - min(x)) / (max(x) - min(x)), exp)) for p in x_firstorder ] # Visualize illustrative network dynamics if dynamic == True: #slice = igraph.Graph(n=len(g1.vs())) #slice.vs["name"] = g1.vs["name"] L = len(temporal.ordered_times) for e in temporal.time[temporal.ordered_times[i % L]]: e_id = g1.get_eid(e[0], e[1]) visual_style["edge_width"][e_id] = 5 visual_style["edge_color"][e_id] = "black" #slice.add_edge(e[0], e[1]) igraph.plot(g1, file_prefix + "_frame_" + str(i).zfill(5) + ".png", **visual_style) else: igraph.plot(g1, file_prefix + "_frame_" + str(i).zfill(5) + ".png", **visual_style) # Plot first-order aggregate network after 30 RW steps (particularly useful as poster frame of video) if i == 30: visual_style["edge_color"] = "black" visual_style["edge_width"] = 1 igraph.plot(g1, file_prefix + "_network.pdf", **visual_style) # Reset edge color and width visual_style["edge_color"] = ["darkgrey"] * g1.ecount() visual_style["edge_width"] = [.5] * g1.ecount() if i % 50 == 0: Log.add('Frame ' + str(i) + '\tTVD = ' + str(Utilities.TVD(x, pi))) # every NWframesPerRWStep frames, perform one random walk step if i % NWframesPerRWStep == 0: # NOTE x * T = (T^T * x^T)^T # NOTE T is already transposed to get the left EV x = (T.dot(x.transpose())).transpose()
def EntropyGrowthRateRatio(t, mode='FIRSTORDER', method='MLE'): """Computes the ratio between the entropy growth rate ratio between the second-order and first-order model of a temporal network t. Ratios smaller than one indicate that the temporal network exhibits non-Markovian characteristics""" # NOTE to myself: most of the time here goes into computation of the # NOTE EV of the transition matrix for the bigger of the # NOTE two graphs below (either 2nd-order or 2nd-order null) assert method == 'MLE' or method == 'Miller' # Generate strongly connected component of second-order network g2 = t.igraphSecondOrder().components(mode="STRONG").giant() Log.add('Calculating entropy growth rate ratio ... ', Severity.INFO) # Compute entropy growth rate of observed transition matrix T2 = Utilities.RWTransitionMatrix(g2) T2_pi = Utilities.StationaryDistribution(T2) T2.data *= np.log2(T2.data) # For T2, we can apply a Miller correction to the entropy estimation if method == 'Miller': # K is the number of possible two-paths that can exist based on the # time-stamped edge sequence (or observed two paths) if len(t.tedges) > 0: edges = set((e[0], e[1]) for e in t.tedges) else: edges = [(tp[0], tp[1]) for tp in t.twopaths] for tp in t.twopaths: edges.append((tp[1], tp[2])) edges = set(edges) K = len(Utilities.getPossibleTwoPaths(edges)) #print('K = ', K) # N is the number of observations used to estimate the transition probabilities # in the second-order network. This corresponds to the total link weight in the # second-order network (accounting for the fractional couting of two-paths) N = np.sum(g2.es["weight"]) #print('N = ', N) H2 = np.sum(T2 * T2_pi) + (K - 1) / (2 * N) else: # simple MLE estimation H2 = -np.sum(T2 * T2_pi) H2 = np.absolute(H2) # Compute entropy rate of null model if mode == 'FIRSTORDER': g2n = t.igraphFirstOrder().components(mode="STRONG").giant() else: g2n = t.igraphSecondOrderNull().components(mode="STRONG").giant() # For the entropy rate of the null model, no Miller correction is needed # since we assume that transitions correspond to the true probabilities T2n = Utilities.RWTransitionMatrix(g2n) T2n_pi = Utilities.StationaryDistribution(T2n) T2n.data *= np.log2(T2n.data) H2n = -np.sum(T2n * T2n_pi) H2n = np.absolute(H2n) Log.add('finished.', Severity.INFO) # Return ratio return H2 / H2n