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
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
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
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
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)
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
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
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()
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
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
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
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
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