CoolFace
Apppublic

YuWang0103/LGGM-Text2Graph

sourceHugging Facemitupdated 2y agoView on Hugging Face
1likes
spectre_utils.py929 linesDownload Raw Back to analysis
1###############################################################################2#3# Adapted from https://github.com/lrjconan/GRAN/ which in turn is adapted from https://github.com/JiaxuanYou/graph-generation4#5###############################################################################6# import graph_tool.all as gt7##Navigate to the ./util/orca directory and compile orca.cpp8# g++ -O2 -std=c++11 -o orca orca.cpp9import os10import copy11import torch12import torch.nn as nn13import numpy as np14import networkx as nx15import subprocess as sp16import concurrent.futures17 18import pygsp as pg19import secrets20from string import ascii_uppercase, digits21from datetime import datetime22from scipy.linalg import eigvalsh23from scipy.stats import chi224from analysis.dist_helper import compute_mmd, gaussian_emd, gaussian, emd, gaussian_tv, disc25from torch_geometric.utils import to_networkx26import wandb27from collections import defaultdict28 29 30PRINT_TIME = False31__all__ = ['degree_stats', 'clustering_stats', 'orbit_stats_all', 'spectral_stats', 'eval_acc_lobster_graph']32 33 34def degree_worker(G):35    return np.array(nx.degree_histogram(G))36 37 38def degree_stats(graph_ref_list, graph_pred_list, is_parallel=True, compute_emd=False):39    ''' Compute the distance between the degree distributions of two unordered sets of graphs.40        Args:41            graph_ref_list, graph_target_list: two lists of networkx graphs to be evaluated42        '''43    sample_ref = []44    sample_pred = []45    # in case an empty graph is generated46    graph_pred_list_remove_empty = [47        G for G in graph_pred_list if not G.number_of_nodes() == 048    ]49 50    prev = datetime.now()51    if is_parallel:52        with concurrent.futures.ThreadPoolExecutor() as executor:53            for deg_hist in executor.map(degree_worker, graph_ref_list):54                sample_ref.append(deg_hist)55        with concurrent.futures.ThreadPoolExecutor() as executor:56            for deg_hist in executor.map(degree_worker, graph_pred_list_remove_empty):57                sample_pred.append(deg_hist)58    else:59        for i in range(len(graph_ref_list)):60            degree_temp = np.array(nx.degree_histogram(graph_ref_list[i]))61            sample_ref.append(degree_temp)62        for i in range(len(graph_pred_list_remove_empty)):63            degree_temp = np.array(64                nx.degree_histogram(graph_pred_list_remove_empty[i]))65            sample_pred.append(degree_temp)66 67    # mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=gaussian_emd)68    # mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=emd)69    if compute_emd:70        # EMD option uses the same computation as GraphRNN, the alternative is MMD as computed by GRAN71        # mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=emd)72        mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=gaussian_emd)73    else:74        mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=gaussian_tv)75    # mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=gaussian)76 77    elapsed = datetime.now() - prev78    if PRINT_TIME:79        print('Time computing degree mmd: ', elapsed)80    return mmd_dist81 82 83###############################################################################84 85def spectral_worker(G, n_eigvals=-1):86    # eigs = nx.laplacian_spectrum(G)87    try:88        eigs = eigvalsh(nx.normalized_laplacian_matrix(G).todense())89    except:90        eigs = np.zeros(G.number_of_nodes())91    if n_eigvals > 0:92        eigs = eigs[1:n_eigvals + 1]93    spectral_pmf, _ = np.histogram(eigs, bins=200, range=(-1e-5, 2), density=False)94    spectral_pmf = spectral_pmf / spectral_pmf.sum()95    return spectral_pmf96 97 98def get_spectral_pmf(eigs, max_eig):99    spectral_pmf, _ = np.histogram(np.clip(eigs, 0, max_eig), bins=200, range=(-1e-5, max_eig), density=False)100    spectral_pmf = spectral_pmf / spectral_pmf.sum()101    return spectral_pmf102 103 104def eigval_stats(eig_ref_list, eig_pred_list, max_eig=20, is_parallel=True, compute_emd=False):105    ''' Compute the distance between the degree distributions of two unordered sets of graphs.106        Args:107            graph_ref_list, graph_target_list: two lists of networkx graphs to be evaluated108        '''109    sample_ref = []110    sample_pred = []111 112    prev = datetime.now()113    if is_parallel:114        with concurrent.futures.ThreadPoolExecutor() as executor:115            for spectral_density in executor.map(get_spectral_pmf, eig_ref_list,116                                                 [max_eig for i in range(len(eig_ref_list))]):117                sample_ref.append(spectral_density)118        with concurrent.futures.ThreadPoolExecutor() as executor:119            for spectral_density in executor.map(get_spectral_pmf, eig_pred_list,120                                                 [max_eig for i in range(len(eig_ref_list))]):121                sample_pred.append(spectral_density)122    else:123        for i in range(len(eig_ref_list)):124            spectral_temp = get_spectral_pmf(eig_ref_list[i])125            sample_ref.append(spectral_temp)126        for i in range(len(eig_pred_list)):127            spectral_temp = get_spectral_pmf(eig_pred_list[i])128            sample_pred.append(spectral_temp)129 130    # mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=gaussian_emd)131    if compute_emd:132        mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=emd)133    else:134        mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=gaussian_tv)135    # mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=gaussian)136 137    elapsed = datetime.now() - prev138    if PRINT_TIME:139        print('Time computing eig mmd: ', elapsed)140    return mmd_dist141 142 143def eigh_worker(G):144    L = nx.normalized_laplacian_matrix(G).todense()145    try:146        eigvals, eigvecs = np.linalg.eigh(L)147    except:148        eigvals = np.zeros(L[0, :].shape)149        eigvecs = np.zeros(L.shape)150    return (eigvals, eigvecs)151 152 153def compute_list_eigh(graph_list, is_parallel=False):154    eigval_list = []155    eigvec_list = []156    if is_parallel:157        with concurrent.futures.ThreadPoolExecutor() as executor:158            for e_U in executor.map(eigh_worker, graph_list):159                eigval_list.append(e_U[0])160                eigvec_list.append(e_U[1])161    else:162        for i in range(len(graph_list)):163            e_U = eigh_worker(graph_list[i])164            eigval_list.append(e_U[0])165            eigvec_list.append(e_U[1])166    return eigval_list, eigvec_list167 168 169def get_spectral_filter_worker(eigvec, eigval, filters, bound=1.4):170    ges = filters.evaluate(eigval)171    linop = []172    for ge in ges:173        linop.append(eigvec @ np.diag(ge) @ eigvec.T)174    linop = np.array(linop)175    norm_filt = np.sum(linop ** 2, axis=2)176    hist_range = [0, bound]177    hist = np.array([np.histogram(x, range=hist_range, bins=100)[0] for x in norm_filt])  # NOTE: change number of bins178    return hist.flatten()179 180 181def spectral_filter_stats(eigvec_ref_list, eigval_ref_list, eigvec_pred_list, eigval_pred_list, is_parallel=False,182                          compute_emd=False):183    ''' Compute the distance between the eigvector sets.184        Args:185            graph_ref_list, graph_target_list: two lists of networkx graphs to be evaluated186        '''187    prev = datetime.now()188 189    class DMG(object):190        """Dummy Normalized Graph"""191        lmax = 2192 193    n_filters = 12194    filters = pg.filters.Abspline(DMG, n_filters)195    bound = np.max(filters.evaluate(np.arange(0, 2, 0.01)))196    sample_ref = []197    sample_pred = []198    if is_parallel:199        with concurrent.futures.ThreadPoolExecutor() as executor:200            for spectral_density in executor.map(get_spectral_filter_worker, eigvec_ref_list, eigval_ref_list,201                                                 [filters for i in range(len(eigval_ref_list))],202                                                 [bound for i in range(len(eigval_ref_list))]):203                sample_ref.append(spectral_density)204        with concurrent.futures.ThreadPoolExecutor() as executor:205            for spectral_density in executor.map(get_spectral_filter_worker, eigvec_pred_list, eigval_pred_list,206                                                 [filters for i in range(len(eigval_ref_list))],207                                                 [bound for i in range(len(eigval_ref_list))]):208                sample_pred.append(spectral_density)209    else:210        for i in range(len(eigval_ref_list)):211            try:212                spectral_temp = get_spectral_filter_worker(eigvec_ref_list[i], eigval_ref_list[i], filters, bound)213                sample_ref.append(spectral_temp)214            except:215                pass216        for i in range(len(eigval_pred_list)):217            try:218                spectral_temp = get_spectral_filter_worker(eigvec_pred_list[i], eigval_pred_list[i], filters, bound)219                sample_pred.append(spectral_temp)220            except:221                pass222 223    if compute_emd:224        # EMD option uses the same computation as GraphRNN, the alternative is MMD as computed by GRAN225        # mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=emd)226        mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=gaussian_emd)227    else:228        mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=gaussian_tv)229 230    elapsed = datetime.now() - prev231    if PRINT_TIME:232        print('Time computing spectral filter stats: ', elapsed)233    return mmd_dist234 235 236def spectral_stats(graph_ref_list, graph_pred_list, is_parallel=True, n_eigvals=-1, compute_emd=False):237    ''' Compute the distance between the degree distributions of two unordered sets of graphs.238        Args:239            graph_ref_list, graph_target_list: two lists of networkx graphs to be evaluated240        '''241    sample_ref = []242    sample_pred = []243    # in case an empty graph is generated244    graph_pred_list_remove_empty = [245        G for G in graph_pred_list if not G.number_of_nodes() == 0246    ]247 248    prev = datetime.now()249    if is_parallel:250        with concurrent.futures.ThreadPoolExecutor() as executor:251            for spectral_density in executor.map(spectral_worker, graph_ref_list, [n_eigvals for i in graph_ref_list]):252                sample_ref.append(spectral_density)253        with concurrent.futures.ThreadPoolExecutor() as executor:254            for spectral_density in executor.map(spectral_worker, graph_pred_list_remove_empty,255                                                 [n_eigvals for i in graph_ref_list]):256                sample_pred.append(spectral_density)257    else:258        for i in range(len(graph_ref_list)):259            spectral_temp = spectral_worker(graph_ref_list[i], n_eigvals)260            sample_ref.append(spectral_temp)261        for i in range(len(graph_pred_list_remove_empty)):262            spectral_temp = spectral_worker(graph_pred_list_remove_empty[i], n_eigvals)263            sample_pred.append(spectral_temp)264 265    # mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=gaussian_emd)266    # mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=emd)267    if compute_emd:268        # EMD option uses the same computation as GraphRNN, the alternative is MMD as computed by GRAN269        # mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=emd)270        mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=gaussian_emd)271    else:272        mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=gaussian_tv)273    # mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=gaussian)274 275    elapsed = datetime.now() - prev276    if PRINT_TIME:277        print('Time computing degree mmd: ', elapsed)278    return mmd_dist279 280 281###############################################################################282 283def clustering_worker(param):284    G, bins = param285    clustering_coeffs_list = list(nx.clustering(G).values())286    hist, _ = np.histogram(287        clustering_coeffs_list, bins=bins, range=(0.0, 1.0), density=False)288    return hist289 290 291def clustering_stats(graph_ref_list,292                     graph_pred_list,293                     bins=100,294                     is_parallel=True, compute_emd=False):295    sample_ref = []296    sample_pred = []297    graph_pred_list_remove_empty = [298        G for G in graph_pred_list if not G.number_of_nodes() == 0299    ]300 301    prev = datetime.now()302    if is_parallel:303        with concurrent.futures.ThreadPoolExecutor() as executor:304            for clustering_hist in executor.map(clustering_worker,305                                                [(G, bins) for G in graph_ref_list]):306                sample_ref.append(clustering_hist)307        with concurrent.futures.ThreadPoolExecutor() as executor:308            for clustering_hist in executor.map(309                    clustering_worker, [(G, bins) for G in graph_pred_list_remove_empty]):310                sample_pred.append(clustering_hist)311 312        # check non-zero elements in hist313        # total = 0314        # for i in range(len(sample_pred)):315        #    nz = np.nonzero(sample_pred[i])[0].shape[0]316        #    total += nz317        # print(total)318    else:319        for i in range(len(graph_ref_list)):320            clustering_coeffs_list = list(nx.clustering(graph_ref_list[i]).values())321            hist, _ = np.histogram(322                clustering_coeffs_list, bins=bins, range=(0.0, 1.0), density=False)323            sample_ref.append(hist)324 325        for i in range(len(graph_pred_list_remove_empty)):326            clustering_coeffs_list = list(327                nx.clustering(graph_pred_list_remove_empty[i]).values())328            hist, _ = np.histogram(329                clustering_coeffs_list, bins=bins, range=(0.0, 1.0), density=False)330            sample_pred.append(hist)331 332    if compute_emd:333        # EMD option uses the same computation as GraphRNN, the alternative is MMD as computed by GRAN334        # mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=emd, sigma=1.0 / 10)335        mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=gaussian_emd, sigma=1.0 / 10, distance_scaling=bins)336    else:337        mmd_dist = compute_mmd(sample_ref, sample_pred, kernel=gaussian_tv, sigma=1.0 / 10)338 339    elapsed = datetime.now() - prev340    if PRINT_TIME:341        print('Time computing clustering mmd: ', elapsed)342    return mmd_dist343 344 345# maps motif/orbit name string to its corresponding list of indices from orca output346motif_to_indices = {347    '3path': [1, 2],348    '4cycle': [8],349}350COUNT_START_STR = 'orbit counts:'351 352 353def edge_list_reindexed(G):354    idx = 0355    id2idx = dict()356    for u in G.nodes():357        id2idx[str(u)] = idx358        idx += 1359 360    edges = []361    for (u, v) in G.edges():362        edges.append((id2idx[str(u)], id2idx[str(v)]))363    return edges364 365 366def orca(graph):367    # tmp_fname = f'analysis/orca/tmp_{"".join(secrets.choice(ascii_uppercase + digits) for i in range(8))}.txt'368    tmp_fname = f'orca/tmp_{"".join(secrets.choice(ascii_uppercase + digits) for i in range(8))}.txt'369    tmp_fname = os.path.join(os.path.dirname(os.path.realpath(__file__)), tmp_fname)370    # print(tmp_fname, flush=True)371    f = open(tmp_fname, 'w')372    f.write(373        str(graph.number_of_nodes()) + ' ' + str(graph.number_of_edges()) + '\n')374    for (u, v) in edge_list_reindexed(graph):375        f.write(str(u) + ' ' + str(v) + '\n')376    f.close()377    output = sp.check_output(378        [str(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'orca/orca')), 'node', '4', tmp_fname, 'std'])379    output = output.decode('utf8').strip()380    idx = output.find(COUNT_START_STR) + len(COUNT_START_STR) + 2381    output = output[idx:]382    node_orbit_counts = np.array([383        list(map(int,384                 node_cnts.strip().split(' ')))385        for node_cnts in output.strip('\n').split('\n')386    ])387 388    try:389        os.remove(tmp_fname)390    except OSError:391        pass392 393    return node_orbit_counts394 395 396def motif_stats(graph_ref_list, graph_pred_list, motif_type='4cycle', ground_truth_match=None,397                bins=100, compute_emd=False):398    # graph motif counts (int for each graph)399    # normalized by graph size400    total_counts_ref = []401    total_counts_pred = []402 403    num_matches_ref = []404    num_matches_pred = []405 406    graph_pred_list_remove_empty = [G for G in graph_pred_list if not G.number_of_nodes() == 0]407    indices = motif_to_indices[motif_type]408 409    for G in graph_ref_list:410        orbit_counts = orca(G)411        motif_counts = np.sum(orbit_counts[:, indices], axis=1)412 413        if ground_truth_match is not None:414            match_cnt = 0415            for elem in motif_counts:416                if elem == ground_truth_match:417                    match_cnt += 1418            num_matches_ref.append(match_cnt / G.number_of_nodes())419 420        # hist, _ = np.histogram(421        #        motif_counts, bins=bins, density=False)422        motif_temp = np.sum(motif_counts) / G.number_of_nodes()423        total_counts_ref.append(motif_temp)424 425    for G in graph_pred_list_remove_empty:426        orbit_counts = orca(G)427        motif_counts = np.sum(orbit_counts[:, indices], axis=1)428 429        if ground_truth_match is not None:430            match_cnt = 0431            for elem in motif_counts:432                if elem == ground_truth_match:433                    match_cnt += 1434            num_matches_pred.append(match_cnt / G.number_of_nodes())435 436        motif_temp = np.sum(motif_counts) / G.number_of_nodes()437        total_counts_pred.append(motif_temp)438 439    total_counts_ref = np.array(total_counts_ref)[:, None]440    total_counts_pred = np.array(total_counts_pred)[:, None]441 442 443    if compute_emd:444        # EMD option uses the same computation as GraphRNN, the alternative is MMD as computed by GRAN445        # mmd_dist = compute_mmd(total_counts_ref, total_counts_pred, kernel=emd, is_hist=False)446        mmd_dist = compute_mmd(total_counts_ref, total_counts_pred, kernel=gaussian, is_hist=False)447    else:448        mmd_dist = compute_mmd(total_counts_ref, total_counts_pred, kernel=gaussian, is_hist=False)449    return mmd_dist450 451 452def orbit_stats_all(graph_ref_list, graph_pred_list, compute_emd=False):453    total_counts_ref = []454    total_counts_pred = []455 456    graph_pred_list_remove_empty = [457        G for G in graph_pred_list if not G.number_of_nodes() == 0458    ]459 460    for G in graph_ref_list:461        orbit_counts = orca(G)462        orbit_counts_graph = np.sum(orbit_counts, axis=0) / G.number_of_nodes()463        total_counts_ref.append(orbit_counts_graph)464 465    for G in graph_pred_list:466        orbit_counts = orca(G)467        orbit_counts_graph = np.sum(orbit_counts, axis=0) / G.number_of_nodes()468        total_counts_pred.append(orbit_counts_graph)469 470    total_counts_ref = np.array(total_counts_ref)471    total_counts_pred = np.array(total_counts_pred)472 473    # mmd_dist = compute_mmd(474    #     total_counts_ref,475    #     total_counts_pred,476    #     kernel=gaussian,477    #     is_hist=False,478    #     sigma=30.0)479 480    # mmd_dist = compute_mmd(481    #         total_counts_ref,482    #         total_counts_pred,483    #         kernel=gaussian_tv,484    #         is_hist=False,485    #         sigma=30.0)  486 487    if compute_emd:488        # mmd_dist = compute_mmd(total_counts_ref, total_counts_pred, kernel=emd, sigma=30.0)489        # EMD option uses the same computation as GraphRNN, the alternative is MMD as computed by GRAN490        mmd_dist = compute_mmd(total_counts_ref, total_counts_pred, kernel=gaussian, is_hist=False, sigma=30.0)491    else:492        mmd_dist = compute_mmd(total_counts_ref, total_counts_pred, kernel=gaussian_tv, is_hist=False, sigma=30.0)493    return mmd_dist494 495 496def eval_acc_lobster_graph(G_list):497    G_list = [copy.deepcopy(gg) for gg in G_list]498    count = 0499    for gg in G_list:500        if is_lobster_graph(gg):501            count += 1502    return count / float(len(G_list))503 504 505def eval_acc_tree_graph(G_list):506    count = 0507    for gg in G_list:508        if nx.is_tree(gg):509            count += 1510    return count / float(len(G_list))511 512 513def eval_acc_grid_graph(G_list, grid_start=10, grid_end=20):514    count = 0515    for gg in G_list:516        if is_grid_graph(gg):517            count += 1518    return count / float(len(G_list))519 520 521def eval_acc_sbm_graph(G_list, p_intra=0.3, p_inter=0.005, strict=True, refinement_steps=1000, is_parallel=True):522    count = 0.0523    if is_parallel:524        with concurrent.futures.ThreadPoolExecutor() as executor:525            for prob in executor.map(is_sbm_graph,526                                     [gg for gg in G_list], [p_intra for i in range(len(G_list))],527                                     [p_inter for i in range(len(G_list))],528                                     [strict for i in range(len(G_list))],529                                     [refinement_steps for i in range(len(G_list))]):530                count += prob531    else:532        for gg in G_list:533            count += is_sbm_graph(gg, p_intra=p_intra, p_inter=p_inter, strict=strict,534                                  refinement_steps=refinement_steps)535    return count / float(len(G_list))536 537 538def eval_acc_planar_graph(G_list):539    count = 0540    for gg in G_list:541        if is_planar_graph(gg):542            count += 1543    return count / float(len(G_list))544 545 546def is_planar_graph(G):547    return nx.is_connected(G) and nx.check_planarity(G)[0]548 549 550def is_lobster_graph(G):551    """552        Check a given graph is a lobster graph or not553 554        Removing leaf nodes twice:555 556        lobster -> caterpillar -> path557 558    """559    ### Check if G is a tree560    if nx.is_tree(G):561        G = G.copy()562        ### Check if G is a path after removing leaves twice563        leaves = [n for n, d in G.degree() if d == 1]564        G.remove_nodes_from(leaves)565 566        leaves = [n for n, d in G.degree() if d == 1]567        G.remove_nodes_from(leaves)568 569        num_nodes = len(G.nodes())570        num_degree_one = [d for n, d in G.degree() if d == 1]571        num_degree_two = [d for n, d in G.degree() if d == 2]572 573        if sum(num_degree_one) == 2 and sum(num_degree_two) == 2 * (num_nodes - 2):574            return True575        elif sum(num_degree_one) == 0 and sum(num_degree_two) == 0:576            return True577        else:578            return False579    else:580        return False581 582 583def is_grid_graph(G):584    """585    Check if the graph is grid, by comparing with all the real grids with the same node count586    """587    all_grid_file = f"data/all_grids.pt"588    if os.path.isfile(all_grid_file):589        all_grids = torch.load(all_grid_file)590    else:591        all_grids = {}592        for i in range(2, 20):593            for j in range(2, 20):594                G_grid = nx.grid_2d_graph(i, j)595                n_nodes = f"{len(G_grid.nodes())}"596                all_grids[n_nodes] = all_grids.get(n_nodes, []) + [G_grid]597        torch.save(all_grids, all_grid_file)598 599    n_nodes = f"{len(G.nodes())}"600    if n_nodes in all_grids:601        for G_grid in all_grids[n_nodes]:602            if nx.faster_could_be_isomorphic(G, G_grid):603                if nx.is_isomorphic(G, G_grid):604                    return True605        return False606    else:607        return False608 609 610# def is_sbm_graph(G, p_intra=0.3, p_inter=0.005, strict=True, refinement_steps=1000):611#     """612#     Check if how closely given graph matches a SBM with given probabilites by computing mean probability of Wald test statistic for each recovered parameter613#     """614 615#     adj = nx.adjacency_matrix(G).toarray()616#     idx = adj.nonzero()617#     g = gt.Graph()618#     g.add_edge_list(np.transpose(idx))619#     try:620#         state = gt.minimize_blockmodel_dl(g)621#     except ValueError:622#         if strict:623#             return False624#         else:625#             return 0.0626 627#     # Refine using merge-split MCMC628#     for i in range(refinement_steps):629#         state.multiflip_mcmc_sweep(beta=np.inf, niter=10)630 631#     b = state.get_blocks()632#     b = gt.contiguous_map(state.get_blocks())633#     state = state.copy(b=b)634#     e = state.get_matrix()635#     n_blocks = state.get_nonempty_B()636#     node_counts = state.get_nr().get_array()[:n_blocks]637#     edge_counts = e.todense()[:n_blocks, :n_blocks]638#     if strict:639#         if (node_counts > 40).sum() > 0 or (node_counts < 20).sum() > 0 or n_blocks > 5 or n_blocks < 2:640#             return False641 642#     max_intra_edges = node_counts * (node_counts - 1)643#     est_p_intra = np.diagonal(edge_counts) / (max_intra_edges + 1e-6)644 645#     max_inter_edges = node_counts.reshape((-1, 1)) @ node_counts.reshape((1, -1))646#     np.fill_diagonal(edge_counts, 0)647#     est_p_inter = edge_counts / (max_inter_edges + 1e-6)648 649#     W_p_intra = (est_p_intra - p_intra) ** 2 / (est_p_intra * (1 - est_p_intra) + 1e-6)650#     W_p_inter = (est_p_inter - p_inter) ** 2 / (est_p_inter * (1 - est_p_inter) + 1e-6)651 652#     W = W_p_inter.copy()653#     np.fill_diagonal(W, W_p_intra)654#     p = 1 - chi2.cdf(abs(W), 1)655#     p = p.mean()656#     if strict:657#         return p > 0.9  # p value < 10 %658#     else:659#         return p660 661 662def eval_fraction_isomorphic(fake_graphs, train_graphs):663    count = 0664    for fake_g in fake_graphs:665        for train_g in train_graphs:666            if nx.faster_could_be_isomorphic(fake_g, train_g):667                if nx.is_isomorphic(fake_g, train_g):668                    count += 1669                    break670    return count / float(len(fake_graphs))671 672 673def eval_fraction_unique(fake_graphs, precise=False):674    count_non_unique = 0675    fake_evaluated = []676    for fake_g in fake_graphs:677        unique = True678        if not fake_g.number_of_nodes() == 0:679            for fake_old in fake_evaluated:680                if precise:681                    if nx.faster_could_be_isomorphic(fake_g, fake_old):682                        if nx.is_isomorphic(fake_g, fake_old):683                            count_non_unique += 1684                            unique = False685                            break686                else:687                    if nx.faster_could_be_isomorphic(fake_g, fake_old):688                        if nx.could_be_isomorphic(fake_g, fake_old):689                            count_non_unique += 1690                            unique = False691                            break692            if unique:693                fake_evaluated.append(fake_g)694 695    frac_unique = (float(len(fake_graphs)) - count_non_unique) / float(696        len(fake_graphs))  # Fraction of distinct isomorphism classes in the fake graphs697 698    return frac_unique699 700 701def eval_fraction_unique_non_isomorphic_valid(fake_graphs, train_graphs, validity_func=(lambda x: True)):702    count_valid = 0703    count_isomorphic = 0704    count_non_unique = 0705    fake_evaluated = []706    for fake_g in fake_graphs:707        unique = True708 709        for fake_old in fake_evaluated:710            if nx.faster_could_be_isomorphic(fake_g, fake_old):711                if nx.is_isomorphic(fake_g, fake_old):712                    count_non_unique += 1713                    unique = False714                    break715        if unique:716            fake_evaluated.append(fake_g)717            non_isomorphic = True718            for train_g in train_graphs:719                if nx.faster_could_be_isomorphic(fake_g, train_g):720                    if nx.is_isomorphic(fake_g, train_g):721                        count_isomorphic += 1722                        non_isomorphic = False723                        break724            if non_isomorphic:725                if validity_func(fake_g):726                    count_valid += 1727 728    frac_unique = (float(len(fake_graphs)) - count_non_unique) / float(729        len(fake_graphs))  # Fraction of distinct isomorphism classes in the fake graphs730    frac_unique_non_isomorphic = (float(len(fake_graphs)) - count_non_unique - count_isomorphic) / float(731        len(fake_graphs))  # Fraction of distinct isomorphism classes in the fake graphs that are not in the training set732    frac_unique_non_isomorphic_valid = count_valid / float(733        len(fake_graphs))  # Fraction of distinct isomorphism classes in the fake graphs that are not in the training set and are valid734    return frac_unique, frac_unique_non_isomorphic, frac_unique_non_isomorphic_valid735 736 737class SpectreSamplingMetrics(nn.Module):738    def __init__(self, data_loaders, compute_emd, metrics_list):739        super().__init__()740 741        self.train_graphs = self.loader_to_nx(data_loaders['train'])742        self.val_graphs = self.loader_to_nx(data_loaders['val'])743        self.test_graphs = self.loader_to_nx(data_loaders['test'])744        self.num_graphs_test = len(self.test_graphs)745        self.num_graphs_val = len(self.val_graphs)746        self.compute_emd = compute_emd747        self.metrics_list = metrics_list748 749    def loader_to_nx(self, loader):750        networkx_graphs = []751        for i, batch in enumerate(loader):752            data_list = batch.to_data_list()753            for j, data in enumerate(data_list):754                networkx_graphs.append(to_networkx(data, node_attrs=None, edge_attrs=None, to_undirected=True,755                                                   remove_self_loops=True))756        return networkx_graphs757 758    def forward(self, generated_graphs: list, local_rank, test=False):759        reference_graphs = self.test_graphs if test else self.val_graphs760        if local_rank == 0:761            print(f"Computing sampling metrics between {len(generated_graphs)} generated graphs and {len(reference_graphs)}"762                  f" test graphs -- emd computation: {self.compute_emd}")763        networkx_graphs = []764        adjacency_matrices = []765        if local_rank == 0:766            print("Building networkx graphs...")767        for graph in generated_graphs:768            node_types, edge_types = graph769            A = edge_types.bool().cpu().numpy()770            adjacency_matrices.append(A)771 772            nx_graph = nx.from_numpy_array(A)773            networkx_graphs.append(nx_graph)774 775        np.savez('generated_adjs.npz', *adjacency_matrices)776 777        to_log = {}778        if 'degree' in self.metrics_list:779            if local_rank == 0:780                print("Computing degree stats..")781            degree = degree_stats(reference_graphs, networkx_graphs, is_parallel=True,782                                  compute_emd=self.compute_emd)783            784            to_log['degree'] = degree785 786            if wandb.run:787                wandb.run.summary['degree'] = degree788 789        # val_eigvals = [graph["eigval"][1:self.k + 1].cpu().detach().numpy() for graph in self.val]790        # train_eigvals = [graph["eigval"][1:self.k + 1].cpu().detach().numpy() for graph in self.train]791 792        # eigval_stats(eig_ref_list, eig_pred_list, max_eig=20, is_parallel=True, compute_emd=False)793        # spectral_filter_stats(eigvec_ref_list, eigval_ref_list, eigvec_pred_list, eigval_pred_list, is_parallel=False,794        #                       compute_emd=False)          # This is the one called wavelet795        796 797        if 'spectre' in self.metrics_list:798            if local_rank == 0:799                print("Computing spectre stats...")800            spectre = spectral_stats(reference_graphs, networkx_graphs, is_parallel=True, n_eigvals=-1,801                                     compute_emd=self.compute_emd)802 803            to_log['spectre'] = spectre804            if wandb.run:805              wandb.run.summary['spectre'] = spectre806 807        if 'clustering' in self.metrics_list:808            if local_rank == 0:809                print("Computing clustering stats...")810            clustering = clustering_stats(reference_graphs, networkx_graphs, bins=100, is_parallel=True,811                                          compute_emd=self.compute_emd)812            to_log['clustering'] = clustering813            if wandb.run:814                wandb.run.summary['clustering'] = clustering815 816        if 'motif' in self.metrics_list:817            if local_rank == 0:818                print("Computing motif stats")819            motif = motif_stats(reference_graphs, networkx_graphs, motif_type='4cycle', ground_truth_match=None, bins=100,820                                compute_emd=self.compute_emd)821            to_log['motif'] = motif822            if wandb.run:823                wandb.run.summary['motif'] = motif824 825        if 'orbit' in self.metrics_list:826            if local_rank == 0:827                print("Computing orbit stats...")828            orbit = orbit_stats_all(reference_graphs, networkx_graphs, compute_emd=self.compute_emd)829            to_log['orbit'] = orbit830            if wandb.run:831                wandb.run.summary['orbit'] = orbit832 833        if 'sbm' in self.metrics_list:834            if local_rank == 0:835                print("Computing accuracy...")836            acc = eval_acc_sbm_graph(networkx_graphs, refinement_steps=100, strict=True)837            to_log['sbm_acc'] = acc838            if wandb.run:839                wandb.run.summary['sbmacc'] = acc840 841        if 'planar' in self.metrics_list:842            if local_rank ==0:843                print('Computing planar accuracy...')844            planar_acc = eval_acc_planar_graph(networkx_graphs)845            to_log['planar_acc'] = planar_acc846            if wandb.run:847                wandb.run.summary['planar_acc'] = planar_acc848 849        if 'sbm' or 'planar' in self.metrics_list:850            if local_rank == 0:851                print("Computing all fractions...")852            frac_unique, frac_unique_non_isomorphic, fraction_unique_non_isomorphic_valid = eval_fraction_unique_non_isomorphic_valid(853                networkx_graphs, self.train_graphs, is_sbm_graph if 'sbm' in self.metrics_list else is_planar_graph)854            frac_non_isomorphic = 1.0 - eval_fraction_isomorphic(networkx_graphs, self.train_graphs)855            to_log.update({'sampling/frac_unique': frac_unique,856                           'sampling/frac_unique_non_iso': frac_unique_non_isomorphic,857                           'sampling/frac_unic_non_iso_valid': fraction_unique_non_isomorphic_valid,858                           'sampling/frac_non_iso': frac_non_isomorphic})859 860        if local_rank == 0:861            print("Sampling statistics", to_log)862        if wandb.run:863            wandb.log(to_log, commit=False)864 865    def reset(self):866        pass867 868 869def loader_to_nx(loader):870    networkx_graphs = {}871    for i, batch in enumerate(loader):872        data_list = batch.to_data_list()873        for j, data in enumerate(data_list):874            networkx_graphs[data.prompt_id.squeeze(0).item()] = [to_networkx(data, node_attrs=None, edge_attrs=None, to_undirected=True, remove_self_loops=True)]875 876    return networkx_graphs877 878def compute_metrics(generated_graphs, referenced_graphs):879    networkx_graphs = defaultdict(list)880    adjacency_matrices = defaultdict(list)881    for key in generated_graphs:882        for graph in generated_graphs[key]:883            node_types, edge_types = graph884            A = edge_types.bool().cpu().numpy()885            nx_graph = nx.from_numpy_array(A)886            887            networkx_graphs[key].append(nx_graph)888            adjacency_matrices[key].append(A)889 890    new_referenced_graphs = []891    for key in referenced_graphs:892        new_referenced_graphs.extend(referenced_graphs[key])893    referenced_graphs = new_referenced_graphs894    895    nx_graphs = []896    for key in networkx_graphs:897        nx_graphs.extend(networkx_graphs[key])898    899    return nx_graphs    900 901 902 903 904class Comm20SamplingMetrics(SpectreSamplingMetrics):905    def __init__(self, data_loaders):906        super().__init__(data_loaders=data_loaders,907                         compute_emd=True,908                         metrics_list=['degree', 'clustering', 'orbit'])909 910 911class PlanarSamplingMetrics(SpectreSamplingMetrics):912    def __init__(self, data_loaders):913        super().__init__(data_loaders=data_loaders,914                         compute_emd=False,915                         metrics_list=['degree', 'clustering', 'orbit', 'spectre', 'planar'])916 917 918class SBMSamplingMetrics(SpectreSamplingMetrics):919    def __init__(self, data_loaders):920        super().__init__(data_loaders=data_loaders,921                         compute_emd=False,922                         metrics_list=['degree', 'clustering', 'orbit', 'spectre', 'sbm'])923 924class CrossDomainSamplingMetrics(SpectreSamplingMetrics):925    def __init__(self, data_loaders):926        super().__init__(data_loaders=data_loaders,927                         compute_emd=False,928                         metrics_list=['degree', 'clustering', 'orbit', 'spectre'])929