def run(**kwargs):
    "\n    Run training example.\n\n    By default this function writes results to the current working directory. To override this use the ``output``\n    keyword argument.\n\n    This function reproduces figure 1(a) of [#l2g]_ if called as ``run(dims=[2**i for i in range(1, 8)], plot=True)``.\n\n\n    Keyword Args:\n        data: Name of data set to load (one of {``'Cora'``, ``'PubMed'``, ``'AMZ_computers'``, ``'AMZ_photo'``}) (default: ``'Cora'``)\n        no_features: If ``True``, discard features and use node identity. (default: ``False``)\n        num_epochs: Number of training epochs (default: ``200``)\n        runs: Number of training runs (keep best result) (default: ``1``)\n        dims: list of embedding dimensions (default: ``[2]``)\n        hidden_multiplier: Hidden dimension is ``hidden_multiplier * dim``\n        target_patch_degree: Target patch degree for resistance sparsification. (default: ``4``)\n        min_overlap: Minimum target patch overlap (default: ``max(dims) + 1``)\n        target_overlap: Target patch overlap (default: ``2 * max(dims)``)\n        gamma: Value of 'gamma' for RMST sparsification (default: ``0``)\n        sparsify: Sparsification method to use (one of {``'resistance'``, ``'none'``, ``'rmst'``})\n                  (default: ``'resistance'``)\n        cluster: Clustering method to use (one of {``'louvain'``, ``'fennel'`` , ``'distributed'``, ``'metis'``})\n                 (default: ``'metis'``)\n        num_clusters: Target number of clusters for distributed, fennel, or metis.\n        num_iters: Maximum iterations for distributed or fennel\n        lr: Learning rate\n        dist: If ``True``, use distance decoder instead of inner product decoder (default: ``False``)\n        output: output folder (default: ``'.'``)\n        device: Device used for training e.g., 'cpu', 'cuda' (defaults to ``'cuda'`` if available else ``'cpu'``)\n        plot: If ``True``, plot embedding performance (default: ``False``)\n        verbose: If ``True``, show progress info (default: ``False``)\n\n    This function only accepts keyword arguments and is also exposed as a command-line interface.\n\n    .. rubric:: References\n\n    .. [#l2g] L. G. S. Jeub et al.\n              “Local2Global: Scaling global representation learning on graphs via local training”.\n              DLG-KDD’21. 2021. `arXiv:2107.12224 [cs.LG] <https://arxiv.org/abs/2107.12224>`_.\n\n    "
    args = _parser.parse_args([])
    for (key, value) in kwargs.items():
        if (key in args):
            setattr(args, key, value)
        else:
            raise TypeError(f'Unknown argument {key}')
    output_folder = Path(args.output)
    data = load_data(args.data)
    neg_edges = tg.utils.negative_sampling(data.edge_index, data.num_nodes)
    graph = TGraph(data.edge_index, data.edge_attr)
    basename = args.data
    dims = args.dims
    num_epochs = args.num_epochs
    runs = args.runs
    min_overlap = (args.min_overlap if (args.min_overlap is not None) else (max(dims) + 1))
    target_overlap = (args.target_overlap if (args.target_overlap is not None) else (2 * max(dims)))
    if args.no_features:
        data.x = None
        basename += '_no_features'
    if args.dist:
        basename += '_dist'
    if (args.sparsify == 'resistance'):
        sp_string = f'resistance_deg{args.target_patch_degree}'
    elif (args.sparsify == 'rmst'):
        sp_string = f'rmst_gamma{args.gamma}'
    elif (args.sparsify == 'none'):
        sp_string = 'no_sparsify'
    else:
        raise RuntimeError(f"Unknown sparsification method '{args.sparsify}'.")
    if (args.cluster == 'louvain'):
        cluster_fun = (lambda : louvain_clustering(graph))
        cluster_string = 'louvain'
    elif (args.cluster == 'distributed'):
        cluster_fun = (lambda : distributed_clustering(graph, args.beta, rounds=args.num_iters))
        cluster_string = f'distributed_beta{args.beta}_it{args.num_iters}'
    elif (args.cluster == 'fennel'):
        cluster_fun = (lambda : fennel_clustering(graph, num_clusters=args.num_clusters, randomise_order=True, num_iters=args.num_iters))
        cluster_string = f'fennel_n{args.num_clusters}_it{args.num_iters}'
    elif (args.cluster == 'metis'):
        cluster_fun = (lambda : metis_clustering(graph, num_clusters=args.num_clusters))
        cluster_string = f'metis_n{args.num_clusters}'
    else:
        raise RuntimeError(f"Unknown cluster method '{args.cluster}'.")
    cluster_file = (output_folder / f'{args.data}_{cluster_string}_clusters.pt')
    if cluster_file.is_file():
        clusters = torch.load(cluster_file)
    else:
        clusters = cluster_fun()
        torch.save(clusters, cluster_file)
    patch_folder = (output_folder / f'{args.data}_{cluster_string}_{sp_string}_mo{min_overlap}_to{target_overlap}_patches')
    (patch_data, patch_graph) = prepare_patches(output_folder=patch_folder, data=data, partition_tensor=clusters, min_overlap=min_overlap, target_overlap=target_overlap, sparsify_method=args.sparsify, gamma=args.gamma, target_patch_degree=args.target_patch_degree, verbose=args.verbose)
    if args.verbose:
        print(f'total edges: {data.num_edges}')
        print(f'total patch edges: {sum((c.num_edges for c in patch_data))}')
    if args.no_features:
        data.x = speye(data.num_nodes)
    baseline_file = (output_folder / f'{basename}_full_info.json')
    training_args = {'lr': args.lr, 'num_epochs': args.num_epochs, 'hidden_multiplier': args.hidden_multiplier}
    if baseline_file.is_file():
        baseline_data = ResultsDict.load(baseline_file)
    else:
        baseline_data = ResultsDict()
    for d in dims:
        r = baseline_data.runs(d)
        if (r < runs):
            if args.verbose:
                print(f'training full model for {(runs - r)} runs and d={d}')
            for r_it in range(r, runs):
                if args.verbose:
                    print(f'full model (d={d}) run {(r_it + 1)} of {runs}')
                data = data.to(args.device)
                model = train(data, VGAE_model(d, (d * args.hidden_multiplier), data.num_features, dist=args.dist).to(args.device), loss_fun=VGAE_loss, num_epochs=num_epochs, lr=args.lr, verbose=args.verbose)
                coords = embedding(model, data)
                auc = reconstruction_auc(coords, data, dist=args.dist)
                if (auc > baseline_data.max_auc(d)):
                    if args.verbose:
                        print(f'new best (auc={auc})')
                    torch.save(model.state_dict(), (output_folder / f'{basename}_full_d{d}_best_model.pt'))
                    torch.save(coords, (output_folder / f'{basename}_full_d{d}_best_coords.pt'))
                baseline_data.update_dim(d, [auc], training_args)
                baseline_data.save(baseline_file)
    results_file = (patch_folder / f'{basename}_l2g_info.json')
    nt_results_file = (patch_folder / f'{basename}_nt_info.json')
    if results_file.is_file():
        results = ResultsDict.load(results_file, replace=True)
    else:
        results = ResultsDict(replace=True)
    if nt_results_file.is_file():
        nt_results = ResultsDict.load(nt_results_file, replace=True)
    else:
        nt_results = ResultsDict(replace=True)
    for d in dims:
        patch_list = []
        update_aligned_embedding = False
        for (p_ind, patch) in enumerate(patch_data):
            patch_result_file = (patch_folder / f'{basename}_patch{p_ind}_info.json')
            if patch_result_file.is_file():
                patch_results = ResultsDict.load(patch_result_file)
            else:
                patch_results = ResultsDict()
            coords_file = (patch_folder / f'{basename}_patch{p_ind}_d{d}_best_coords.pt')
            if coords_file.is_file():
                best_coords = torch.load(coords_file)
            r = patch_results.runs(d)
            if args.no_features:
                patch.x = speye(patch.num_nodes)
            if (r < runs):
                if args.verbose:
                    print(f'training patch{p_ind} for {(runs - r)} runs and d={d}')
                patch = patch.to(args.device)
                for r_it in range(r, runs):
                    if args.verbose:
                        print(f'patch{p_ind} (d={d}) run {(r_it + 1)} of {runs}')
                    model = train(patch, VGAE_model(d, (d * args.hidden_multiplier), patch.num_features, dist=args.dist).to(args.device), loss_fun=VGAE_loss, num_epochs=num_epochs, lr=args.lr)
                    coords = embedding(model, patch)
                    auc = reconstruction_auc(coords, patch, dist=args.dist)
                    if (auc > patch_results.max_auc(d)):
                        if args.verbose:
                            print(f'new best (auc={auc})')
                        best_coords = coords
                        torch.save(model.state_dict(), (patch_folder / f'{basename}_patch{p_ind}_d{d}_best_model.pt'))
                        torch.save(best_coords, coords_file)
                        update_aligned_embedding = True
                    patch_results.update_dim(d, [auc], training_args)
                    patch_results.save(patch_result_file)
            patch_list.append(l2g.Patch(patch.nodes.cpu().numpy(), best_coords.cpu().numpy()))
        patched_embedding_file = (patch_folder / f'{basename}_d{d}_coords.pt')
        patched_embedding_file_nt = (patch_folder / f'{basename}_d{d}_ntcoords.pt')
        if (update_aligned_embedding or (not patched_embedding_file.is_file())):
            prob = l2g.WeightedAlignmentProblem(patch_list, patch_edges=patch_graph.edges())
            ntcoords = prob.mean_embedding()
            coords = prob.get_aligned_embedding()
            torch.save(coords, patched_embedding_file)
            torch.save(ntcoords, patched_embedding_file_nt)
            results.update_dim(d, [reconstruction_auc(torch.as_tensor(coords), data, neg_edges, dist=args.dist)])
            nt_results.update_dim(d, [reconstruction_auc(torch.as_tensor(ntcoords), data, neg_edges, dist=args.dist)])
            results.save(results_file)
            nt_results.save(nt_results_file)
    baseline_data = baseline_data.reduce_to_dims(dims)
    results = results.reduce_to_dims(dims)
    nt_results = nt_results.reduce_to_dims(dims)
    if args.plot:
        plt.figure()
        plt.plot(dims, [max(v) for v in baseline_data['auc']], label='full, inner product', marker='o', color='tab:blue')
        plt.plot(dims, results['auc'], '--', label='l2g, inner product', marker='>', color='tab:blue')
        plt.plot(dims, nt_results['auc'], ':', label='no-trans, inner product', color='tab:blue', linewidth=1)
        plt.xscale('log')
        plt.xticks(dims, dims)
        plt.minorticks_off()
        plt.xlabel('embedding dimension')
        plt.ylabel('AUC')
        plt.legend()
        oversampling_ratio = (sum((p.num_edges for p in patch_data)) / data.num_edges)
        plt.title(f'oversampling ratio: {oversampling_ratio:.2}, #patches: {len(patch_data)}')
        plt.savefig((output_folder / f'{basename}_{cluster_string}_{sp_string}_mo{min_overlap}_to{target_overlap}.pdf'))
        plt.show()
Run training example.

By default this function writes results to the current working directory. To override this use the ``output``
keyword argument.

This function reproduces figure 1(a) of [#l2g]_ if called as ``run(dims=[2**i for i in range(1, 8)], plot=True)``.


Keyword Args:
    data: Name of data set to load (one of {``'Cora'``, ``'PubMed'``, ``'AMZ_computers'``, ``'AMZ_photo'``}) (default: ``'Cora'``)
    no_features: If ``True``, discard features and use node identity. (default: ``False``)
    num_epochs: Number of training epochs (default: ``200``)
    runs: Number of training runs (keep best result) (default: ``1``)
    dims: list of embedding dimensions (default: ``[2]``)
    hidden_multiplier: Hidden dimension is ``hidden_multiplier * dim``
    target_patch_degree: Target patch degree for resistance sparsification. (default: ``4``)
    min_overlap: Minimum target patch overlap (default: ``max(dims) + 1``)
    target_overlap: Target patch overlap (default: ``2 * max(dims)``)
    gamma: Value of 'gamma' for RMST sparsification (default: ``0``)
    sparsify: Sparsification method to use (one of {``'resistance'``, ``'none'``, ``'rmst'``})
              (default: ``'resistance'``)
    cluster: Clustering method to use (one of {``'louvain'``, ``'fennel'`` , ``'distributed'``, ``'metis'``})
             (default: ``'metis'``)
    num_clusters: Target number of clusters for distributed, fennel, or metis.
    num_iters: Maximum iterations for distributed or fennel
    lr: Learning rate
    dist: If ``True``, use distance decoder instead of inner product decoder (default: ``False``)
    output: output folder (default: ``'.'``)
    device: Device used for training e.g., 'cpu', 'cuda' (defaults to ``'cuda'`` if available else ``'cpu'``)
    plot: If ``True``, plot embedding performance (default: ``False``)
    verbose: If ``True``, show progress info (default: ``False``)

This function only accepts keyword arguments and is also exposed as a command-line interface.

.. rubric:: References

.. [#l2g] L. G. S. Jeub et al.
          “Local2Global: Scaling global representation learning on graphs via local training”.
          DLG-KDD’21. 2021. `arXiv:2107.12224 [cs.LG] <https://arxiv.org/abs/2107.12224>`_.
def run(**kwargs):
    "\n    Run training example.\n\n    By default this function writes results to the current working directory. To override this use the ``output``\n    keyword argument.\n\n    This function reproduces figure 1(a) of [#l2g]_ if called as ``run(dims=[2**i for i in range(1, 8)], plot=True)``.\n\n\n    Keyword Args:\n        data: Name of data set to load (one of {``'Cora'``, ``'PubMed'``, ``'AMZ_computers'``, ``'AMZ_photo'``}) (default: ``'Cora'``)\n        no_features: If ``True``, discard features and use node identity. (default: ``False``)\n        num_epochs: Number of training epochs (default: ``200``)\n        runs: Number of training runs (keep best result) (default: ``1``)\n        dims: list of embedding dimensions (default: ``[2]``)\n        hidden_multiplier: Hidden dimension is ``hidden_multiplier * dim``\n        target_patch_degree: Target patch degree for resistance sparsification. (default: ``4``)\n        min_overlap: Minimum target patch overlap (default: ``max(dims) + 1``)\n        target_overlap: Target patch overlap (default: ``2 * max(dims)``)\n        gamma: Value of 'gamma' for RMST sparsification (default: ``0``)\n        sparsify: Sparsification method to use (one of {``'resistance'``, ``'none'``, ``'rmst'``})\n                  (default: ``'resistance'``)\n        cluster: Clustering method to use (one of {``'louvain'``, ``'fennel'`` , ``'distributed'``, ``'metis'``})\n                 (default: ``'metis'``)\n        num_clusters: Target number of clusters for distributed, fennel, or metis.\n        num_iters: Maximum iterations for distributed or fennel\n        lr: Learning rate\n        dist: If ``True``, use distance decoder instead of inner product decoder (default: ``False``)\n        output: output folder (default: ``'.'``)\n        device: Device used for training e.g., 'cpu', 'cuda' (defaults to ``'cuda'`` if available else ``'cpu'``)\n        plot: If ``True``, plot embedding performance (default: ``False``)\n        verbose: If ``True``, show progress info (default: ``False``)\n\n    This function only accepts keyword arguments and is also exposed as a command-line interface.\n\n    .. rubric:: References\n\n    .. [#l2g] L. G. S. Jeub et al.\n              “Local2Global: Scaling global representation learning on graphs via local training”.\n              DLG-KDD’21. 2021. `arXiv:2107.12224 [cs.LG] <https://arxiv.org/abs/2107.12224>`_.\n\n    "
    args = _parser.parse_args([])
    for (key, value) in kwargs.items():
        if (key in args):
            setattr(args, key, value)
        else:
            raise TypeError(f'Unknown argument {key}')
    output_folder = Path(args.output)
    data = load_data(args.data)
    neg_edges = tg.utils.negative_sampling(data.edge_index, data.num_nodes)
    graph = TGraph(data.edge_index, data.edge_attr)
    basename = args.data
    dims = args.dims
    num_epochs = args.num_epochs
    runs = args.runs
    min_overlap = (args.min_overlap if (args.min_overlap is not None) else (max(dims) + 1))
    target_overlap = (args.target_overlap if (args.target_overlap is not None) else (2 * max(dims)))
    if args.no_features:
        data.x = None
        basename += '_no_features'
    if args.dist:
        basename += '_dist'
    if (args.sparsify == 'resistance'):
        sp_string = f'resistance_deg{args.target_patch_degree}'
    elif (args.sparsify == 'rmst'):
        sp_string = f'rmst_gamma{args.gamma}'
    elif (args.sparsify == 'none'):
        sp_string = 'no_sparsify'
    else:
        raise RuntimeError(f"Unknown sparsification method '{args.sparsify}'.")
    if (args.cluster == 'louvain'):
        cluster_fun = (lambda : louvain_clustering(graph))
        cluster_string = 'louvain'
    elif (args.cluster == 'distributed'):
        cluster_fun = (lambda : distributed_clustering(graph, args.beta, rounds=args.num_iters))
        cluster_string = f'distributed_beta{args.beta}_it{args.num_iters}'
    elif (args.cluster == 'fennel'):
        cluster_fun = (lambda : fennel_clustering(graph, num_clusters=args.num_clusters, randomise_order=True, num_iters=args.num_iters))
        cluster_string = f'fennel_n{args.num_clusters}_it{args.num_iters}'
    elif (args.cluster == 'metis'):
        cluster_fun = (lambda : metis_clustering(graph, num_clusters=args.num_clusters))
        cluster_string = f'metis_n{args.num_clusters}'
    else:
        raise RuntimeError(f"Unknown cluster method '{args.cluster}'.")
    cluster_file = (output_folder / f'{args.data}_{cluster_string}_clusters.pt')
    if cluster_file.is_file():
        clusters = torch.load(cluster_file)
    else:
        clusters = cluster_fun()
        torch.save(clusters, cluster_file)
    patch_folder = (output_folder / f'{args.data}_{cluster_string}_{sp_string}_mo{min_overlap}_to{target_overlap}_patches')
    (patch_data, patch_graph) = prepare_patches(output_folder=patch_folder, data=data, partition_tensor=clusters, min_overlap=min_overlap, target_overlap=target_overlap, sparsify_method=args.sparsify, gamma=args.gamma, target_patch_degree=args.target_patch_degree, verbose=args.verbose)
    if args.verbose:
        print(f'total edges: {data.num_edges}')
        print(f'total patch edges: {sum((c.num_edges for c in patch_data))}')
    if args.no_features:
        data.x = speye(data.num_nodes)
    baseline_file = (output_folder / f'{basename}_full_info.json')
    training_args = {'lr': args.lr, 'num_epochs': args.num_epochs, 'hidden_multiplier': args.hidden_multiplier}
    if baseline_file.is_file():
        baseline_data = ResultsDict.load(baseline_file)
    else:
        baseline_data = ResultsDict()
    for d in dims:
        r = baseline_data.runs(d)
        if (r < runs):
            if args.verbose:
                print(f'training full model for {(runs - r)} runs and d={d}')
            for r_it in range(r, runs):
                if args.verbose:
                    print(f'full model (d={d}) run {(r_it + 1)} of {runs}')
                data = data.to(args.device)
                model = train(data, VGAE_model(d, (d * args.hidden_multiplier), data.num_features, dist=args.dist).to(args.device), loss_fun=VGAE_loss, num_epochs=num_epochs, lr=args.lr, verbose=args.verbose)
                coords = embedding(model, data)
                auc = reconstruction_auc(coords, data, dist=args.dist)
                if (auc > baseline_data.max_auc(d)):
                    if args.verbose:
                        print(f'new best (auc={auc})')
                    torch.save(model.state_dict(), (output_folder / f'{basename}_full_d{d}_best_model.pt'))
                    torch.save(coords, (output_folder / f'{basename}_full_d{d}_best_coords.pt'))
                baseline_data.update_dim(d, [auc], training_args)
                baseline_data.save(baseline_file)
    results_file = (patch_folder / f'{basename}_l2g_info.json')
    nt_results_file = (patch_folder / f'{basename}_nt_info.json')
    if results_file.is_file():
        results = ResultsDict.load(results_file, replace=True)
    else:
        results = ResultsDict(replace=True)
    if nt_results_file.is_file():
        nt_results = ResultsDict.load(nt_results_file, replace=True)
    else:
        nt_results = ResultsDict(replace=True)
    for d in dims:
        patch_list = []
        update_aligned_embedding = False
        for (p_ind, patch) in enumerate(patch_data):
            patch_result_file = (patch_folder / f'{basename}_patch{p_ind}_info.json')
            if patch_result_file.is_file():
                patch_results = ResultsDict.load(patch_result_file)
            else:
                patch_results = ResultsDict()
            coords_file = (patch_folder / f'{basename}_patch{p_ind}_d{d}_best_coords.pt')
            if coords_file.is_file():
                best_coords = torch.load(coords_file)
            r = patch_results.runs(d)
            if args.no_features:
                patch.x = speye(patch.num_nodes)
            if (r < runs):
                if args.verbose:
                    print(f'training patch{p_ind} for {(runs - r)} runs and d={d}')
                patch = patch.to(args.device)
                for r_it in range(r, runs):
                    if args.verbose:
                        print(f'patch{p_ind} (d={d}) run {(r_it + 1)} of {runs}')
                    model = train(patch, VGAE_model(d, (d * args.hidden_multiplier), patch.num_features, dist=args.dist).to(args.device), loss_fun=VGAE_loss, num_epochs=num_epochs, lr=args.lr)
                    coords = embedding(model, patch)
                    auc = reconstruction_auc(coords, patch, dist=args.dist)
                    if (auc > patch_results.max_auc(d)):
                        if args.verbose:
                            print(f'new best (auc={auc})')
                        best_coords = coords
                        torch.save(model.state_dict(), (patch_folder / f'{basename}_patch{p_ind}_d{d}_best_model.pt'))
                        torch.save(best_coords, coords_file)
                        update_aligned_embedding = True
                    patch_results.update_dim(d, [auc], training_args)
                    patch_results.save(patch_result_file)
            patch_list.append(l2g.Patch(patch.nodes.cpu().numpy(), best_coords.cpu().numpy()))
        patched_embedding_file = (patch_folder / f'{basename}_d{d}_coords.pt')
        patched_embedding_file_nt = (patch_folder / f'{basename}_d{d}_ntcoords.pt')
        if (update_aligned_embedding or (not patched_embedding_file.is_file())):
            prob = l2g.WeightedAlignmentProblem(patch_list, patch_edges=patch_graph.edges())
            ntcoords = prob.mean_embedding()
            coords = prob.get_aligned_embedding()
            torch.save(coords, patched_embedding_file)
            torch.save(ntcoords, patched_embedding_file_nt)
            results.update_dim(d, [reconstruction_auc(torch.as_tensor(coords), data, neg_edges, dist=args.dist)])
            nt_results.update_dim(d, [reconstruction_auc(torch.as_tensor(ntcoords), data, neg_edges, dist=args.dist)])
            results.save(results_file)
            nt_results.save(nt_results_file)
    baseline_data = baseline_data.reduce_to_dims(dims)
    results = results.reduce_to_dims(dims)
    nt_results = nt_results.reduce_to_dims(dims)
    if args.plot:
        plt.figure()
        plt.plot(dims, [max(v) for v in baseline_data['auc']], label='full, inner product', marker='o', color='tab:blue')
        plt.plot(dims, results['auc'], '--', label='l2g, inner product', marker='>', color='tab:blue')
        plt.plot(dims, nt_results['auc'], ':', label='no-trans, inner product', color='tab:blue', linewidth=1)
        plt.xscale('log')
        plt.xticks(dims, dims)
        plt.minorticks_off()
        plt.xlabel('embedding dimension')
        plt.ylabel('AUC')
        plt.legend()
        oversampling_ratio = (sum((p.num_edges for p in patch_data)) / data.num_edges)
        plt.title(f'oversampling ratio: {oversampling_ratio:.2}, #patches: {len(patch_data)}')
        plt.savefig((output_folder / f'{basename}_{cluster_string}_{sp_string}_mo{min_overlap}_to{target_overlap}.pdf'))
        plt.show()