Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a few igraph algorithms to run via scripts/bench.py #54

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ dependencies:
- numba
- python-suitesparse-graphblas
- pyyaml
# python-graphblas extra dependencies
- fast_matrix_market
- packaging
# networkx default dependencies
- matplotlib
- pandas
Expand All @@ -54,3 +57,9 @@ dependencies:
- ipython
# For type annotations
- mypy
# For benchmark comparisons (optional; uncomment as desired)
# - igraph
# - python-igraph
# - networkit
# - graph-tool
# - xorg-libxcursor # for graph-tool
36 changes: 31 additions & 5 deletions scripts/bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import scipy.sparse

import graphblas_algorithms as ga
import igraph_impl
import scipy_impl
from graphblas_algorithms.interface import Dispatcher

Expand Down Expand Up @@ -56,13 +57,20 @@ def readfile(filepath, is_symmetric, backend):
return ga.Graph(A)
return ga.DiGraph(A)
a = scipy.io.mmread(filepath)
if backend == "networkx":
if backend in {"networkx", "igraph"}:
create_using = nx.Graph if is_symmetric else nx.DiGraph
return nx.from_scipy_sparse_array(a, create_using=create_using)
G = nx.from_scipy_sparse_array(a, create_using=create_using)
if backend == "networkx":
return G
if backend == "igraph":
# TODO: is there a better way for igraph to read MM files or scipy.sparse arrays?
Copy link

@szhorvat szhorvat Mar 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, how is one supposed to load Matrix Market files into an igraph Graph? Going from *.mtx file to scipy.sparse to networkx to igraph seems less than ideal.

You can use igraph.Graph.Adjacency to convert from a SciPy sparse matrix.

There is no direct support for reading MM files in igraph.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just what I was looking for, thanks!

import igraph

return igraph.Graph.from_networkx(G)
if backend == "scipy":
return scipy.sparse.csr_array(a)
raise ValueError(
f"Backend {backend!r} not understood; must be 'graphblas', 'networkx', or 'scipy'"
f"Backend {backend!r} not understood; must be 'graphblas', 'networkx', 'igraph', or 'scipy'"
)


Expand Down Expand Up @@ -126,6 +134,8 @@ def getfunction(functionname, backend):
return getattr(Dispatcher, functionname)
if backend == "scipy":
return getattr(scipy_impl, functionname)
if backend == "igraph":
return getattr(igraph_impl, functionname)
if functionname in functionpaths:
func = nx
for attr in functionpaths[functionname].split("."):
Expand All @@ -149,7 +159,15 @@ def getgraph(dataname, backend="graphblas", functionname=None):


def main(
dataname, backend, functionname, time=3.0, n=None, extra=None, display=True, enable_gc=False
dataname,
backend,
functionname,
time=3.0,
n=None,
min_n=None,
extra=None,
display=True,
enable_gc=False,
):
G = getgraph(dataname, backend, functionname)
func = getfunction(functionname, backend)
Expand Down Expand Up @@ -193,6 +211,8 @@ def main(
n = 1
elif n is None:
n = 2 ** max(0, int(np.ceil(np.log2(time / first_time))))
if min_n is not None:
n = max(n, min_n)
if display:
print("Number of runs:", n)
print("first: ", stime(first_time))
Expand Down Expand Up @@ -222,7 +242,7 @@ def main(
description=f"Example usage: python {sys.argv[0]} -b graphblas -f pagerank -d amazon0302"
)
parser.add_argument(
"-b", "--backend", choices=["graphblas", "networkx", "scipy"], default="graphblas"
"-b", "--backend", choices=["graphblas", "networkx", "scipy", "igraph"], default="graphblas"
)
parser.add_argument(
"-t", "--time", type=float, default=3.0, help="Target minimum time to run benchmarks"
Expand All @@ -232,6 +252,11 @@ def main(
type=int,
help="The number of times to run the benchmark (the default is to run according to time)",
)
parser.add_argument(
"--min-n",
type=int,
help="The minimum number of times to run the benchmark",
)
parser.add_argument(
"-d",
"--data",
Expand Down Expand Up @@ -260,6 +285,7 @@ def main(
args.func,
time=args.time,
n=args.n,
min_n=args.min_n,
extra=args.extra,
display=not args.json,
enable_gc=args.gc,
Expand Down
51 changes: 51 additions & 0 deletions scripts/igraph_impl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
def overall_reciprocity(G):
return G.reciprocity()


def pagerank(
G,
alpha=0.85,
personalization=None,
max_iter=100,
tol=1e-06,
nstart=None,
weight="weight",
dangling=None,
*,
vertices=None,
directed=True,
arpack_options=None,
implementation="prpack",
):
if personalization is not None:
raise NotImplementedError

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personalization is provided through the reset and reset_vertices parameters of personalized_pagerank()

https://python.igraph.org/en/stable/api/igraph.GraphBase.html#personalized_pagerank

if nstart is not None:
raise NotImplementedError
if dangling is not None:
raise NotImplementedError

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just confirming that the behaviour is the same as with dangling=None in NetworkX.

rv = G.pagerank(
vertices=vertices,
directed=directed,
damping=alpha,
weights=weight,
arpack_options=arpack_options,
implementation=implementation,
)
return rv


def transitivity(G):
return G.transitivity_undirected()


def average_clustering(G, nodes=None, weight=None, count_zeros=True):
if nodes is not None:
raise NotImplementedError
# TODO: check results when `count_zeros=False`
mode = "zero" if count_zeros else "nan"
Comment on lines +44 to +45

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct.

return G.transitivity_avglocal_undirected(mode=mode, weights=weight)


def clustering(G, nodes=None, weight=None):
mode = "zero" # or "nan"
return G.transitivity_local_undirected(vertices=nodes, mode=mode, weights=weight)