Пример #1
0
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))
Пример #2
0
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
Пример #3
0
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()
Пример #4
0
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()
Пример #5
0
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