Пример #1
0
def EigenValueGap(t):
    """Returns the eigenvalue gap in the second-order transition matrix of a 
    temporal network, as well as in the corresponding null model. Returns 
    the tuple (lambda(T2), lambda(T2_null))
    
    @param t: The temporalnetwork instance to work on
    """

    #NOTE to myself: most of the time goes for construction of the 2nd order
    #NOTE            null graph, then for the 2nd order null transition matrix

    g2 = t.igraphSecondOrder().components(mode="STRONG").giant()
    g2n = t.igraphSecondOrderNull().components(mode="STRONG").giant()

    Log.add('Calculating eigenvalue gap ... ', Severity.INFO)

    # Build transition matrices
    T2 = Utilities.RWTransitionMatrix(g2)
    T2n = Utilities.RWTransitionMatrix(g2n)

    # Compute eigenvector sequences
    # NOTE: ncv=13 sets additional auxiliary eigenvectors that are computed
    # NOTE: in order to be more confident to find the one with the largest
    # NOTE: magnitude, see
    # NOTE: https://github.com/scipy/scipy/issues/4987
    w2 = sla.eigs(T2, which="LM", k=2, ncv=13, return_eigenvectors=False)
    evals2_sorted = np.sort(-np.absolute(w2))

    w2n = sla.eigs(T2n, which="LM", k=2, ncv=13, return_eigenvectors=False)
    evals2n_sorted = np.sort(-np.absolute(w2n))

    Log.add('finished.', Severity.INFO)

    return (np.abs(evals2_sorted[1]), np.abs(evals2n_sorted[1]))
Пример #2
0
def SlowDownFactor(t):
    """Returns a factor S that indicates how much slower (S>1) or faster (S<1)
    a diffusion process in the temporal network evolves on a second-order model 
    compared to a first-order model. This value captures the effect of order
    correlations on a diffusion process in the temporal network.
    
    @param t: The temporalnetwork instance to work on
    """

    #NOTE to myself: most of the time goes for construction of the 2nd order
    #NOTE            null graph, then for the 2nd order null transition matrix

    g2 = t.igraphSecondOrder().components(mode="STRONG").giant()
    g2n = t.igraphSecondOrderNull().components(mode="STRONG").giant()

    Log.add('Calculating slow down factor ... ', Severity.INFO)

    # Build transition matrices
    T2 = Utilities.RWTransitionMatrix(g2)
    T2n = Utilities.RWTransitionMatrix(g2n)

    # Compute eigenvector sequences
    # NOTE: ncv=13 sets additional auxiliary eigenvectors that are computed
    # NOTE: in order to be more confident to find the one with the largest
    # NOTE: magnitude, see
    # NOTE: https://github.com/scipy/scipy/issues/4987
    w2 = sla.eigs(T2, which="LM", k=2, ncv=13, return_eigenvectors=False)
    evals2_sorted = np.sort(-np.absolute(w2))

    w2n = sla.eigs(T2n, which="LM", k=2, ncv=13, return_eigenvectors=False)
    evals2n_sorted = np.sort(-np.absolute(w2n))

    Log.add('finished.', Severity.INFO)

    return np.log(np.abs(evals2n_sorted[1])) / np.log(np.abs(evals2_sorted[1]))
Пример #3
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
Пример #4
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()
Пример #5
0
def Laplacian(temporalnet, model="SECOND"):
    """Returns the transposed Laplacian matrix corresponding to the the second-order (model=SECOND) or 
    the second-order null (model=NULL) model for a temporal network.
    
    @param temporalnet: 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\"")

    if model == "SECOND":
        network = temporalnet.igraphSecondOrder().components(
            mode="STRONG").giant()
    elif model == "NULL":
        network = temporalnet.igraphSecondOrderNull().components(
            mode="STRONG").giant()

    T2 = Utilities.RWTransitionMatrix(network)
    I = sparse.identity(len(network.vs()))

    return I - T2
Пример #6
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()
Пример #7
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