def read_graphs():
    signals = []
    Gs = []
    graphs_mat = loadmat(DATASET_PATH)
    g_sizes = []
    for i, A in enumerate(graphs_mat['cell_A']):
        if len(signals) >= MAX_SIGNALS:
            break
        G = Graph(A[0])
        if G.N < MIN_SIZE or not G.is_connected():
            continue

        signal = gs.DeterministicGS(G, graphs_mat['cell_X'][i][0][:, ATTR])
        if np.linalg.norm(signal.x) == 0:
            continue
        #signal.normalize()
        #signal.signal_to_0_1_interval()
        signal.to_unit_norm()
        G.compute_fourier_basis()
        Gs.append(G)
        g_sizes.append(G.N)
        signals.append(signal)

    print('Graphs readed:', len(Gs), 'from:', i, 'mean size:',
          np.mean(g_sizes))
    return Gs, signals
示例#2
0
 def build(self, A=None, **kwargs):
     super().build(A, **kwargs)
     M = self.A(A).copy()
     offset = np.array(M.sum(0))[0][0]
     print(offset)
     M.setdiag(0)
     M = abs(M)
     M.eliminate_zeros()
     g = Graph(M)
     g.estimate_lmax()
     f = Filter(g, lambda x: 1 / (1 + offset*x))
     #self._G = g
     self.__solve = f.filter
     self.built = True
     return self
示例#3
0
def read_graphs(dataset_path,
                attr,
                min_size=50,
                max_signals=100,
                to_0_1=False,
                center=False,
                max_size=None,
                max_smooth=None,
                max_bl_err=None):
    signals = []
    Gs = []
    g_sizes = []
    gs_mat = loadmat(dataset_path)

    for i, A in enumerate(gs_mat['cell_A']):
        if len(signals) >= max_signals:
            break
        G = Graph(A[0])
        G.compute_fourier_basis()
        if G.N < min_size or not G.is_connected():
            continue
        if max_size and G.N > max_size:
            continue

        if gs_mat['cell_X'][0][0].shape[1] == 1:
            signal = ds.DeterministicGS(G, gs_mat['cell_X'][i][0][:, 0])
        else:
            signal = ds.DeterministicGS(G, gs_mat['cell_X'][i][0][:, attr - 1])

        if np.linalg.norm(signal.x) == 0:
            continue

        if center:
            signal.center()
        if to_0_1:
            signal.to_0_1_interval()
        signal.to_unit_norm()

        if max_smooth and signal.smoothness() > max_smooth:
            continue

        if max_bl_err and signal.check_bl_err(coefs=0.25,
                                              firsts=True) > max_bl_err:
            continue

        G.compute_fourier_basis()
        G.set_coordinates('spring')
        Gs.append(G)
        g_sizes.append(G.N)
        signals.append(signal)

    print('Graphs read:', len(Gs), 'from:', i, 'mean size:', np.mean(g_sizes))
    return Gs, signals
示例#4
0
def tree_depths(A, root):
    r"""Empty docstring. TODO."""
    if not Graph(A=A).is_connected():
        raise ValueError('Graph is not connected')

    N = np.shape(A)[0]
    assigned = root - 1
    depths = np.zeros((N))
    parents = np.zeros((N))

    next_to_expand = np.array([root])
    current_depth = 1

    while len(assigned) < N:
        new_entries_whole_round = []
        for i in range(len(next_to_expand)):
            neighbors = np.where(A[next_to_expand[i]])[0]
            new_entries = np.setdiff1d(neighbors, assigned)
            parents[new_entries] = next_to_expand[i]
            depths[new_entries] = current_depth
            assigned = np.concatenate((assigned, new_entries))
            new_entries_whole_round = np.concatenate((new_entries_whole_round,
                                                      new_entries))

        current_depth = current_depth + 1
        next_to_expand = new_entries_whole_round

    return depths, parents
示例#5
0
def assemble(g1, g2, margin=0.2):
    """
    Merge two graphs together
    """
    W = block_diag((g1.W, g2.W))
    margin = 0.2
    new_coords = g2.coords
    new_coords[:, 0] = new_coords[:, 0] + margin + np.max(g1.coords)
    coords = np.concatenate((g1.coords, new_coords))
    return Graph(W, coords=coords, plotting=g1.plotting)
示例#6
0
def perm_graph(A, coords, node_com, comm_sizes):
    N = A.shape[0]
    # Create permutation matrix
    P = np.zeros(A.shape)
    i = np.arange(N)
    j = np.random.permutation(N)
    P[i, j] = 1

    # Permute
    A_p = P.dot(A).dot(P.T)
    assert np.sum(np.diag(A_p)) == 0, 'Diagonal of permutated A is not 0'
    coords_p = P.dot(coords)
    node_com_p = P.dot(node_com)
    G = Graph(A_p)
    G.set_coordinates(coords_p)
    G.info = {
        'node_com': node_com_p,
        'comm_sizes': comm_sizes,
        'perm_matrix': P
    }
    return G
示例#7
0
def perturbated_graphs(g_params,
                       creat=5,
                       dest=5,
                       pct=True,
                       perm=False,
                       seed=None):
    """
    Create 2 closely related graphs. The first graph is created following the
    indicated model and the second is a perturbated version of the previous
    where links are created or destroid with a small probability.
    Arguments:
        - g_params: a dictionary containing all the parameters for creating
          the desired graph. The options are explained in the documentation
          of the function 'create_graph'
        - eps_c: probability for creating new edges
        - eps_d: probability for destroying existing edges
    """
    Gx = create_graph(g_params, seed)
    if pct:
        Ay = perturbate_percentage(Gx, creat, dest)
    else:
        Ay = perturbate_probability(Gx, creat, dest)
    coords_Gy = Gx.coords
    node_com_Gy = Gx.info['node_com']
    comm_sizes_Gy = Gx.info['comm_sizes']
    assert np.sum(np.diag(Ay)) == 0, 'Diagonal of A is not 0'

    if perm:
        Gy = perm_graph(Ay, coords_Gy, node_com_Gy, comm_sizes_Gy)
    else:
        Gy = Graph(Ay)
        Gy.set_coordinates(coords_Gy)
        Gy.info = {'node_com': node_com_Gy, 'comm_sizes': comm_sizes_Gy}
    assert Gy.is_connected(), 'Could not create connected graph Gy'
    return Gx, Gy
示例#8
0
 def plot_As(self, show=True):
     _, axes = plt.subplots(2, len(self.As))
     for i in range(len(self.As)):
         G = Graph(self.As[i])
         G.set_coordinates()
         axes[0, i].spy(self.As[i])
         G.plot(ax=axes[1, i])
     if show:
         plt.show()
示例#9
0
def nodes_perturbated_graphs(g_params, n_dest, perm=False, seed=None):
    Gx = create_graph(g_params, seed)
    Ax = Gx.A.todense()
    rm_nodes = np.random.choice(Gx.N, n_dest, replace=False)
    Ay = np.delete(Ax, rm_nodes, axis=0)
    Ay = np.delete(Ay, rm_nodes, axis=1)

    # Set Graph info
    coords_Gy = np.delete(Gx.coords, rm_nodes, axis=0)
    node_com_Gy = np.delete(Gx.info['node_com'], rm_nodes)
    comm_sizes_Gy = np.zeros(len(Gx.info['comm_sizes']))
    for i in range(len(Gx.info['comm_sizes'])):
        comm_sizes_Gy[i] = np.sum(node_com_Gy == i)

    if perm:
        Gy = perm_graph(Ay, coords_Gy, node_com_Gy, comm_sizes_Gy)
    else:
        Gy = Graph(Ay)
        Gy.set_coordinates(coords_Gy)
        Gy.info = {'node_com': node_com_Gy, 'comm_sizes': comm_sizes_Gy}
    Gy.info['rm_nodes'] = rm_nodes
    assert Gy.is_connected(), 'Could not create connected graph Gy'
    return Gx, Gy
示例#10
0
def create_graph(ps, seed=None):
    """
    Create a random graph using the parameters specified in the dictionary ps.
    The keys that this dictionary should nclude are:
        - type: model for the graph. Options are SBM (Stochastic Block Model)
          or ER (Erdos-Renyi)
        - N: number of nodes
        - k: number of communities (for SBM only)
        - p: edge probability for nodes in the same community
        - q: edge probability for nodes in different communities (for SBM only)
        - type_z: specify how to assigns nodes to communities (for SBM only).
          Options are CONT (continous), ALT (alternating) and RAND (random)
    """
    if ps['type'] == SBM:
        if ps['type_z'] == CONT:
            z = assign_nodes_to_comms(ps['N'], ps['k'])
        elif ps['type_z'] == ALT:
            z = np.array(
                list(range(ps['k'])) * int(ps['N'] / ps['k']) +
                list(range(ps['N'] % ps['k'])))
        elif ps['type_z'] == RAND:
            z = assign_nodes_to_comms(ps['N'], ps['k'])
            np.random.shuffle(z)
        else:
            z = None
        G = StochasticBlockModel(N=ps['N'],
                                 k=ps['k'],
                                 p=ps['p'],
                                 z=z,
                                 q=ps['q'],
                                 connected=True,
                                 seed=seed,
                                 max_iter=MAX_RETRIES)
    elif ps['type'] == ER:
        G = ErdosRenyi(N=ps['N'],
                       p=ps['p'],
                       connected=True,
                       seed=seed,
                       max_iter=MAX_RETRIES)
    elif ps['type'] == BA:
        G = BarabasiAlbert(N=ps['N'], m=ps['m'], m0=ps['m0'], seed=seed)
        G.info = {
            'comm_sizes': np.array([ps['N']]),
            'node_com': np.zeros((ps['N'], ), dtype=int)
        }
    elif ps['type'] == SW:
        # ps['k'] < 1 means the proportion of desired links is indicated
        k = ps['k'] * (ps['N'] - 1) if ps['k'] < 1 else ps['k']
        G = nx.connected_watts_strogatz_graph(n=ps['N'],
                                              k=int(k),
                                              p=ps['p'],
                                              tries=MAX_RETRIES,
                                              seed=seed)
        A = nx.to_numpy_array(G)
        G = Graph(A)
    elif ps['type'] == REG:
        # ps['d'] < 1 means the proportion of desired links is indicated
        d = ps['d'] * (ps['N'] - 1) if ps['d'] < 1 else ps['d']
        G = nx.random_regular_graph(n=ps['N'], d=int(d), seed=seed)
        A = nx.to_numpy_array(G)
        G = Graph(A)
    elif ps['type'] == PLC:
        # ps['m'] < 1 means the proportion of desired links is indicated
        m = ps['m'] * (ps['N'] - 1) if ps['m'] < 1 else ps['m']
        G = nx.powerlaw_cluster_graph(n=ps['N'],
                                      m=int(m),
                                      p=ps['p'],
                                      seed=seed)
        A = nx.to_numpy_array(G)
        G = Graph(A)
    elif ps['type'] == CAVE:
        k = int(ps['N'] / ps['l'])
        G = nx.connected_caveman_graph(ps['l'], k=k)
        A = nx.to_numpy_array(G)
        G = Graph(A)
    else:
        raise RuntimeError('Unknown graph type')

    assert G.is_connected(), 'Graph is not connected'

    G.set_coordinates('spring')
    # G.compute_fourier_basis()
    return G
示例#11
0
def tree_multiresolution(G, Nlevel, reduction_method='resistance_distance',
                         compute_full_eigen=False, root=None):
    r"""
    Compute a multiresolution of trees

    Parameters
    ----------
    G : Graph
        Graph structure of a tree.
    Nlevel : Number of times to downsample and coarsen the tree
    root : int
        The index of the root of the tree. (default = 1)
    reduction_method : str
        The graph reduction method (default = 'resistance_distance')
    compute_full_eigen : bool
        To also compute the graph Laplacian eigenvalues for every tree in the sequence

    Returns
    -------
    Gs : ndarray
        Ndarray, with each element containing a graph structure represent a reduced tree.
    subsampled_vertex_indices : ndarray
        Indices of the vertices of the previous tree that are kept for the subsequent tree.

    """
    from pygsp.graphs import Graph

    if not root:
        if hasattr(G, 'root'):
            root = G.root
        else:
            root = 1

    Gs = [G]

    if compute_full_eigen:
        Gs[0].compute_fourier_basis()

    subsampled_vertex_indices = []
    depths, parents = tree_depths(G.A, root)
    old_W = G.W

    for lev in range(Nlevel):
        # Identify the vertices in the even depths of the current tree
        down_odd = round(depths) % 2
        down_even = np.ones((Gs[lev].N)) - down_odd
        keep_inds = np.where(down_even == 1)[0]
        subsampled_vertex_indices.append(keep_inds)

        # There will be one undirected edge in the new graph connecting each
        # non-root subsampled vertex to its new parent. Here, we find the new
        # indices of the new parents
        non_root_keep_inds, new_non_root_inds = np.setdiff1d(keep_inds, root)
        old_parents_of_non_root_keep_inds = parents[non_root_keep_inds]
        old_grandparents_of_non_root_keep_inds = parents[old_parents_of_non_root_keep_inds]
        # TODO new_non_root_parents = dsearchn(keep_inds, old_grandparents_of_non_root_keep_inds)

        old_W_i_inds, old_W_j_inds, old_W_weights = sparse.find(old_W)
        i_inds = np.concatenate((new_non_root_inds, new_non_root_parents))
        j_inds = np.concatenate((new_non_root_parents, new_non_root_inds))
        new_N = np.sum(down_even)

        if reduction_method == "unweighted":
            new_weights = np.ones(np.shape(i_inds))

        elif reduction_method == "sum":
            # TODO old_weights_to_parents_inds = dsearchn([old_W_i_inds,old_W_j_inds], [non_root_keep_inds, old_parents_of_non_root_keep_inds]);
            old_weights_to_parents = old_W_weights[old_weights_to_parents_inds]
            # old_W(non_root_keep_inds,old_parents_of_non_root_keep_inds);
            # TODO old_weights_parents_to_grandparents_inds = dsearchn([old_W_i_inds, old_W_j_inds], [old_parents_of_non_root_keep_inds, old_grandparents_of_non_root_keep_inds])
            old_weights_parents_to_grandparents = old_W_weights[old_weights_parents_to_grandparents_inds]
            # old_W(old_parents_of_non_root_keep_inds,old_grandparents_of_non_root_keep_inds);
            new_weights = old_weights_to_parents + old_weights_parents_to_grandparents
            new_weights = np.concatenate((new_weights. new_weights))

        elif reduction_method == "resistance_distance":
            # TODO old_weights_to_parents_inds = dsearchn([old_W_i_inds, old_W_j_inds], [non_root_keep_inds, old_parents_of_non_root_keep_inds])
            old_weights_to_parents = old_W_weight[sold_weights_to_parents_inds]
            # old_W(non_root_keep_inds,old_parents_of_non_root_keep_inds);
            # TODO old_weights_parents_to_grandparents_inds = dsearchn([old_W_i_inds, old_W_j_inds], [old_parents_of_non_root_keep_inds, old_grandparents_of_non_root_keep_inds])
            old_weights_parents_to_grandparents = old_W_weights[old_weights_parents_to_grandparents_inds]
            # old_W(old_parents_of_non_root_keep_inds,old_grandparents_of_non_root_keep_inds);
            new_weights = 1./(1./old_weights_to_parents + 1./old_weights_parents_to_grandparents)
            new_weights = np.concatenate(([new_weights, new_weights]))

        else:
            raise ValueError('Unknown graph reduction method.')

        new_W = sparse.csc_matrix((new_weights, (i_inds, j_inds)),
                                  shape=(new_N, new_N))
        # Update parents
        new_root = np.where(keep_inds == root)[0]
        parents = np.zeros(np.shape(keep_inds)[0], np.shape(keep_inds)[0])
        parents[:new_root - 1, new_root:] = new_non_root_parents

        # Update depths
        depths = depths[keep_inds]
        depths = depths/2.

        # Store new tree
        Gtemp = Graph(new_W, coords=Gs[lev].coords[keep_inds], limits=G.limits, gtype='tree', root=new_root)
        Gs[lev].copy_graph_attributes(Gtemp, False)

        if compute_full_eigen:
            Gs[lev + 1].compute_fourier_basis()

        # Replace current adjacency matrix and root
        Gs.append(Gtemp)

        old_W = new_W
        root = new_root

    return Gs, subsampled_vertex_indices
示例#12
0
def kron_reduction(G, ind):
    r"""
    Compute the kron reduction.

    This function perform the Kron reduction of the weight matrix in the
    graph *G*, with boundary nodes labeled by *ind*. This function will
    create a new graph with a weight matrix Wnew that contain only boundary
    nodes and is computed as the Schur complement of the original matrix
    with respect to the selected indices.

    Parameters
    ----------
    G : Graph or sparse matrix
        Graph structure or weight matrix
    ind : list
        indices of the nodes to keep

    Returns
    -------
    Gnew : Graph or sparse matrix
        New graph structure or weight matrix


    References
    ----------
    See :cite:`dorfler2013kron`

    """
    if isinstance(G, Graph):
        if hasattr(G, 'lap_type'):
            if not G.lap_type == 'combinatorial':
                message = 'Unknwon reduction for {} laplacian.'.format(G.lap_type)
                raise NotImplementedError(message)

        if G.directed:
            message = 'This method only work for undirected graphs.'
            raise NotImplementedError(message)

        if not hasattr(G, 'L'):
            G.compute_laplacian()
        L = G.L

    else:
        L = G

    N = np.shape(L)[0]
    ind_comp = np.setdiff1d(np.arange(N, dtype=int), ind)

    L_red = L[np.ix_(ind, ind)]
    L_in_out = L[np.ix_(ind, ind_comp)]
    L_out_in = L[np.ix_(ind_comp, ind)].tocsc()
    L_comp = L[np.ix_(ind_comp, ind_comp)].tocsc()

    Lnew = L_red - L_in_out.dot(spsolve(L_comp, L_out_in))

    # Make the laplacian symmetric if it is almost symmetric!
    if np.abs(Lnew - Lnew.T).sum() < np.spacing(1) * np.abs(Lnew).sum():
        Lnew = (Lnew + Lnew.T) / 2.

    if isinstance(G, Graph):
        # Suppress the diagonal ? This is a good question?
        Wnew = sparse.diags(Lnew.diagonal(), 0) - Lnew
        Snew = Lnew.diagonal() - np.ravel(Wnew.sum(0))
        if np.linalg.norm(Snew, 2) >= np.spacing(1000):
            Wnew = Wnew + sparse.diags(Snew, 0)

        # Removing diagonal for stability
        Wnew = Wnew - Wnew.diagonal()

        coords = G.coords[ind, :] if len(G.coords.shape) else np.ndarray(None)
        Gnew = Graph(W=Wnew, coords=coords, lap_type=G.lap_type,
                     plotting=G.plotting, gtype='Kron reduction')
    else:
        Gnew = Lnew

    return Gnew
示例#13
0
def graph_sparsify(M, epsilon, maxiter=10):
    r"""
    Sparsify a graph using Spielman-Srivastava algorithm.

    Parameters
    ----------
    M : Graph or sparse matrix
        Graph structure or a Laplacian matrix
    epsilon : int
        Sparsification parameter

    Returns
    -------
    Mnew : Graph or sparse matrix
        New graph structure or sparse matrix

    Notes
    -----
    Epsilon should be between 1/sqrt(N) and 1

    Examples
    --------
    >>> from pygsp import graphs, operators
    >>> G = graphs.Sensor(256, Nc=20, distribute=True)
    >>> epsilon = 0.4
    >>> G2 = operators.graph_sparsify(G, epsilon)

    References
    ----------
    See :cite:`spielman2011graph`, :cite:`rudelson1999random` and :cite:`rudelson2007sampling`.
    for more informations

    """
    # Test the input parameters
    if isinstance(M, Graph):
        if not M.lap_type == 'combinatorial':
            raise NotImplementedError
        L = M.L
    else:
        L = M

    N = np.shape(L)[0]

    if not 1./np.sqrt(N) <= epsilon < 1:
        raise ValueError('GRAPH_SPARSIFY: Epsilon out of required range')

    # Not sparse
    resistance_distances = resistance_distance(L).toarray()
    # Get the Weight matrix
    if isinstance(M, Graph):
        W = M.W
    else:
        W = np.diag(L.diagonal()) - L.toarray()
        W[W < 1e-10] = 0

    W = sparse.coo_matrix(W)
    W.data[W.data < 1e-10] = 0
    W = W.tocsc()
    W.eliminate_zeros()


    start_nodes, end_nodes, weights = sparse.find(sparse.tril(W))

    # Calculate the new weights.
    weights = np.maximum(0, weights)
    Re = np.maximum(0, resistance_distances[start_nodes, end_nodes])
    Pe = weights * Re
    Pe = Pe / np.sum(Pe)

    for i in range(maxiter):
        # Rudelson, 1996 Random Vectors in the Isotropic Position
        # (too hard to figure out actual C0)
        C0 = 1 / 30.
        # Rudelson and Vershynin, 2007, Thm. 3.1
        C = 4 * C0
        q = round(N * np.log(N) * 9 * C**2 / (epsilon**2))

        results = stats.rv_discrete(values=(np.arange(np.shape(Pe)[0]), Pe)).rvs(size=int(q))
        spin_counts = stats.itemfreq(results).astype(int)
        per_spin_weights = weights / (q * Pe)

        counts = np.zeros(np.shape(weights)[0])
        counts[spin_counts[:, 0]] = spin_counts[:, 1]
        new_weights = counts * per_spin_weights

        sparserW = sparse.csc_matrix((new_weights, (start_nodes, end_nodes)),
                                     shape=(N, N))
        sparserW = sparserW + sparserW.T
        sparserL = sparse.diags(sparserW.diagonal(), 0) - sparserW

        if Graph(W=sparserW).is_connected():
            break
        elif i == maxiter - 1:
            logger.warning('Despite attempts to reduce epsilon, sparsified graph is disconnected')
        else:
            epsilon -= (epsilon - 1/np.sqrt(N)) / 2.

    if isinstance(M, Graph):
        sparserW = sparse.diags(sparserL.diagonal(), 0) - sparserL
        if not M.directed:
            sparserW = (sparserW + sparserW.T) / 2.

        Mnew = Graph(W=sparserW)
        M.copy_graph_attributes(Mnew)
    else:
        Mnew = sparse.lil_matrix(sparserL)

    return Mnew