Skip to content

Commit

Permalink
Updated GPU support for adj. robustness measures
Browse files Browse the repository at this point in the history
  • Loading branch information
safreita1 committed Feb 20, 2021
1 parent 98ec6a7 commit 8f4a76b
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 41 deletions.
4 changes: 2 additions & 2 deletions graph_tiger/graphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def watts_strogatz(n, m=4, p=0.05, seed=None):
return nx.generators.connected_watts_strogatz_graph(n=n, k=m, p=p, seed=seed)


def barbasi_albert(n, m=3, seed=None):
def barabasi_albert(n, m=3, seed=None):
"""
Returns a Barabasi Albert NetworkX graph
Expand Down Expand Up @@ -502,7 +502,7 @@ def two_c4_3_bridge():
models = {
'ER': erdos_reyni,
'WS': watts_strogatz,
'BA': barbasi_albert,
'BA': barabasi_albert,
'CSF': clustered_scale_free
}

Expand Down
65 changes: 33 additions & 32 deletions graph_tiger/measures.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


@stopit.threading_timeoutable()
def run_measure(graph, measure, k=np.inf):
def run_measure(graph, measure, k=np.inf, use_gpu=False):
"""
Evaluates graph robustness according to a specified measure
Expand All @@ -19,11 +19,7 @@ def run_measure(graph, measure, k=np.inf):
"""

try:
if k is not np.inf:
result = measures[measure](graph, k)
else:
result = measures[measure](graph)

result = measures[measure](graph, k=k, use_gpu=use_gpu)
return result

except stopit.TimeoutException:
Expand Down Expand Up @@ -62,7 +58,7 @@ def get_measures():
# return connected


def node_connectivity(graph):
def node_connectivity(graph, **kwargs):
"""
Measures the minimal number of vertices that can be removed to disconnect the graph.
Larger vertex (node) connectivity --> harder to disconnect graph
Expand All @@ -75,7 +71,7 @@ def node_connectivity(graph):
return nx.algorithms.node_connectivity(graph)


def edge_connectivity(graph):
def edge_connectivity(graph, **kwargs):
"""
Measures the minimal number of edges that can be removed to disconnect the graph.
Larger edge connectivity --> harder to disconnect graph -->
Expand All @@ -88,7 +84,7 @@ def edge_connectivity(graph):
return nx.algorithms.edge_connectivity(graph)


def avg_distance(graph):
def avg_distance(graph, **kwargs):
"""
The average distance between all pairs of nodes in the graph.
The smaller the average shortest path distance, the more robust the graph.
Expand All @@ -104,7 +100,7 @@ def avg_distance(graph):
return round(nx.average_shortest_path_length(graph), 2)


def avg_inverse_distance(graph):
def avg_inverse_distance(graph, **kwargs):
"""
The average inverse distance between all pairs of nodes in the graph.
The larger the average inverse shortest path distance, the more robust the graph.
Expand All @@ -120,7 +116,7 @@ def avg_inverse_distance(graph):
return round(nx.global_efficiency(graph), 2)


def diameter(graph):
def diameter(graph, **kwargs):
"""
The diameter of a connected graph is the longest shortest path between all pairs of nodes.
The smaller the diameter the more robust the graph i.e., smaller diameter -->
Expand All @@ -133,7 +129,7 @@ def diameter(graph):
return nx.diameter(graph)


def avg_vertex_betweenness(graph, k=np.inf):
def avg_vertex_betweenness(graph, k=np.inf, **kwargs):
"""
The average vertex betweenness of a graph is the summation of vertex betweenness for every node in the graph.
The smaller the average vertex betweenness, the more robust the graph.
Expand All @@ -151,7 +147,7 @@ def avg_vertex_betweenness(graph, k=np.inf):
return round(avg_betw, 2)


def avg_edge_betweenness(graph, k=np.inf):
def avg_edge_betweenness(graph, k=np.inf, **kwargs):
"""
Similar to vertex betweenness, edge betweenness is defined as the number of shortest paths
that pass through an edge *e* out of the total possible shortest paths.
Expand All @@ -173,7 +169,7 @@ def avg_edge_betweenness(graph, k=np.inf):
return 0


def average_clustering_coefficient(graph):
def average_clustering_coefficient(graph, **kwargs):
"""
The global clustering coefficient is based on the number of triplets of nodes in the graph,
and provides an indication of how well nodes tend to cluster together.
Expand All @@ -186,7 +182,7 @@ def average_clustering_coefficient(graph):
return round(nx.average_clustering(graph), 2)


def largest_connected_component(graph):
def largest_connected_component(graph, **kwargs):
"""
This measure provides an indication of a graph's connectivity by measuring the fraction
of nodes contained in the largest connected component. The larger the value, the more robust the graph.
Expand All @@ -203,24 +199,25 @@ def largest_connected_component(graph):
"""


def spectral_radius(graph):
def spectral_radius(graph, use_gpu=False, **kwargs):
"""
The largest eigenvalue :math:`\lambda_1` of an adjacency matrix **A** is called the spectral radius.
The larger the spectral radius, the more robust the graph. This can be viewed from its close relationship to the
"path" or "loop" capacity in a network :cite:`chen2015node,tong2010vulnerability`.
:param graph: undirected NetworkX graph
:param use_gpu: defaults to False; set to True to use GPU (if available)
:return: a float
"""
lam = get_adjacency_spectrum(graph, k=1, which='LA', eigvals_only=True)
lam = get_adjacency_spectrum(graph, k=1, which='LA', eigvals_only=True, use_gpu=use_gpu)

idx = lam.argsort()[::-1] # sort descending algebraic
lam = lam[idx]

return round(lam[0], 2)


def spectral_gap(graph):
def spectral_gap(graph, use_gpu=False, **kwargs):
"""
The difference between the largest and second largest eigenvalues of the adjacency matrix
(:math:`\lambda_1 - \lambda_2`) is called the spectral gap :math:`\lambda_d`.
Expand All @@ -229,26 +226,28 @@ def spectral_gap(graph):
undesirable bridges in the network :cite:`chan2016optimizing,malliaros2012fast`.
:param graph: undirected NetworkX graph
:param use_gpu: defaults to False; set to True to use GPU (if available)
:return: a float
"""
lam = get_adjacency_spectrum(graph, k=2, which='LA', eigvals_only=True)
lam = get_adjacency_spectrum(graph, k=2, which='LA', eigvals_only=True, use_gpu=use_gpu)

idx = lam.argsort()[::-1] # sort descending algebraic
lam = lam[idx]

return round(lam[0] - lam[1], 2)


def natural_connectivity(graph, k=np.inf):
def natural_connectivity(graph, k=np.inf, use_gpu=False, **kwargs):
"""
Natural connectivity has a physical and structural interpretation that is tied to the connectivity properties
of a network, identifying alternative pathways in a network through the weighted number of closed walks.
The larger the natural connectivity (average eigenvalue of adjacency matrix), the more robust the graph :cite:`chan2014make`.
:param graph: undirected NetworkX graph
:param use_gpu: defaults to False; set to True to use GPU (if available)
:return: a float
"""
lam = get_adjacency_spectrum(graph, k=k, which='LA', eigvals_only=True)
lam = get_adjacency_spectrum(graph, k=k, which='LA', eigvals_only=True, use_gpu=use_gpu)

idx = lam.argsort()[::-1] # sort descending algebraic
lam = lam[idx]
Expand All @@ -274,15 +273,16 @@ def odd_subgraph_centrality(i, lam, u):
return sc


def spectral_scaling(graph, k=np.inf):
def spectral_scaling(graph, k=np.inf, use_gpu=False, **kwargs):
"""
Spectral scaling is a combination of the spectral gap and subgraph centrality. Spectral scaling takes into account
if a graph has many bridges. The smaller the value, the more robust the graph :cite:`estrada2006network`.
:param graph: undirected NetworkX graph
:param use_gpu: defaults to False; set to True to use GPU (if available)
:return: a float
"""
lam, u = get_adjacency_spectrum(graph, k=k, which='LM', eigvals_only=False)
lam, u = get_adjacency_spectrum(graph, k=k, which='LM', eigvals_only=False, use_gpu=use_gpu)

idx = np.abs(lam).argsort()[::-1] # sort descending magnitude
lam = lam[idx]
Expand All @@ -300,24 +300,25 @@ def spectral_scaling(graph, k=np.inf):
return sc


def generalized_robustness_index(graph, k=30):
def generalized_robustness_index(graph, k=30, use_gpu=False, **kwargs):
"""
This can be considered a fast approximation of spectral scaling. The smaller the value, the more robust the graph.
Also helps determine if a graph has many bridges (bad for robustness) :cite:`malliaros2012fast`.
:param graph: undirected NetworkX graph
:param use_gpu: defaults to False; set to True to use GPU (if available)
:return: a float
"""
return spectral_scaling(graph, k=k)
return spectral_scaling(graph, k=k, use_gpu=use_gpu, kwargs=kwargs)


'''
Laplacian Spectral Measures
'''


def algebraic_connectivity(graph):
"""
def algebraic_connectivity(graph, **kwargs):
r"""
The larger the algebraic connectivity, the more robust the graph.
This is due to it's close connection to edge connectivity, where it serves as a lower bound:
0 < :math:`u_2` < node connectivity < edge connectivity. This means that a network with larger algebraic connectivity
Expand All @@ -326,14 +327,14 @@ def algebraic_connectivity(graph):
:param graph: undirected NetworkX graph
:return: a float
"""
lam = get_laplacian_spectrum(graph, k=2)
lam = get_laplacian_spectrum(graph, k=2, use_gpu=kwargs['use_gpu'])

alg_connect = round(lam[1], 2)

return alg_connect


def num_spanning_trees(graph, k=np.inf):
def num_spanning_trees(graph, k=np.inf, **kwargs):
"""
The number of spanning trees *T* is the number of unique spanning trees that can be found in a graph.
The larger the number of spanning trees, the more robust the graph.
Expand All @@ -343,14 +344,14 @@ def num_spanning_trees(graph, k=np.inf):
:param graph: undirected NetworkX graph
:return: a float
"""
lam = get_laplacian_spectrum(graph, k=k)
lam = get_laplacian_spectrum(graph, k=k, use_gpu=kwargs['use_gpu'])

num_trees = round(np.prod(lam[1:]) / len(graph), 2)

return num_trees


def effective_resistance(graph, k=np.inf):
def effective_resistance(graph, k=np.inf, **kwargs):
"""
This measure views a graph as an electrical circuit where an edge :math:`(i, j)`
corresponds to a resister of :math:`r_{ij} = 1` Ohm and a node *i* corresponds to a junction.
Expand All @@ -360,7 +361,7 @@ def effective_resistance(graph, k=np.inf):
:param graph: undirected NetworkX graph
:return: a float
"""
lam = get_laplacian_spectrum(graph, k=k)
lam = get_laplacian_spectrum(graph, k=k, use_gpu=kwargs['use_gpu'])

resistance = round(len(graph) * np.sum(1.0 / lam[1:]), 2)

Expand Down
19 changes: 12 additions & 7 deletions graph_tiger/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from scipy.sparse.linalg import eigsh


def use_gpu():
def gpu_available():
from pip._internal.utils.misc import get_installed_distributions

gpu = False
Expand All @@ -29,7 +29,7 @@ def get_sparse_graph(graph):
return nx.to_scipy_sparse_matrix(graph, format='csr', dtype=np.float, nodelist=graph.nodes)


def get_adjacency_spectrum(graph, k=np.inf, eigvals_only=False, which='LA'):
def get_adjacency_spectrum(graph, k=np.inf, eigvals_only=False, which='LA', use_gpu=False):
"""
Gets the top k eigenpairs of the adjacency matrix
Expand All @@ -48,24 +48,27 @@ def get_adjacency_spectrum(graph, k=np.inf, eigvals_only=False, which='LA'):
else:
A = nx.to_scipy_sparse_matrix(graph, format='csr', dtype=np.float, nodelist=graph.nodes)

if not use_gpu():
eigpairs = eigsh(A, k=min(k, len(graph) - 1), which=which, return_eigenvectors=not eigvals_only)
else:
if gpu_available() and use_gpu:
import cupy as cp
import cupyx.scipy.sparse.linalg as cp_linalg

A_gpu = cp.sparse.csr_matrix(A)
eigpairs = cp_linalg.eigsh(A_gpu, k=min(k, len(graph) - 1), which=which, return_eigenvectors=not eigvals_only)

if len(eigpairs) > 1:
if type(eigpairs) is tuple:
eigpairs = list(eigpairs)
eigpairs[0], eigpairs[1] = cp.asnumpy(eigpairs[0]), cp.asnumpy(eigpairs[1])
else:
eigpairs = cp.asnumpy(eigpairs)

else:
if use_gpu: print('Warning: GPU requested, but not available')
eigpairs = eigsh(A, k=min(k, len(graph) - 1), which=which, return_eigenvectors=not eigvals_only)

return eigpairs


def get_laplacian_spectrum(graph, k=np.inf, which='SM', tol=1E-2, eigvals_only=True):
def get_laplacian_spectrum(graph, k=np.inf, which='SM', tol=1E-2, eigvals_only=True, use_gpu=False):
"""
Gets the bottom k eigenpairs of the Laplacian matrix
Expand All @@ -78,6 +81,8 @@ def get_laplacian_spectrum(graph, k=np.inf, which='SM', tol=1E-2, eigvals_only=T
:return: the eigenpair information
"""

if use_gpu: print('Warning: GPU requested, but not available for Laplacian measures')

# get all eigenvalues for small graphs
if len(graph) < 100:
lam = nx.laplacian_spectrum(graph)
Expand Down

0 comments on commit 8f4a76b

Please sign in to comment.