Skip to content

Commit 2f32be5

Browse files
authored
Add has_path, inter_community_edges, intra_community_edges (#22)
* Add `has_path`, `inter_community_edges, `intra_community_edges` * Add katz_centrality * eigenvector_centrality * Add `normalize` and `is_converged` helper functions * Add degree centralities * HITS * Rename `mark_nx_tests` to `on_start_tests` * Add NoteMap and NodeSet to make vectors look like sets and dicts * change `has_path` to use bidirectional BFS
1 parent bdc7cec commit 2f32be5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+783
-116
lines changed

.github/dependabot.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: 'github-actions'
4+
directory: '/'
5+
schedule:
6+
interval: 'weekly'

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
conda install -c conda-forge python-graphblas scipy pandas \
3434
pytest-cov pytest-randomly black flake8-comprehensions flake8-bugbear
3535
# matplotlib lxml pygraphviz pydot sympy # Extra networkx deps we don't need yet
36-
pip install git+https://github.com/jim22k/networkx.git@nx-sparse --no-deps
36+
pip install git+https://github.com/mriduls/networkx.git@nx-sparse --no-deps
3737
pip install -e . --no-deps
3838
- name: Style checks
3939
run: |

.pre-commit-config.yaml

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@
22
#
33
# Before first use: `pre-commit install`
44
# To run: `pre-commit run --all-files`
5+
fail_fast: true
56
repos:
67
- repo: https://github.com/pre-commit/pre-commit-hooks
7-
rev: v4.2.0
8+
rev: v4.3.0
89
hooks:
910
- id: check-added-large-files
11+
- id: check-ast
12+
- id: check-toml
1013
- id: check-yaml
1114
- id: debug-statements
1215
- id: end-of-file-fixer
1316
- id: mixed-line-ending
1417
- id: trailing-whitespace
1518
- repo: https://github.com/myint/autoflake
16-
rev: v1.4
19+
rev: v1.7.7
1720
hooks:
1821
- id: autoflake
1922
args: [--in-place]
@@ -23,34 +26,42 @@ repos:
2326
- id: isort
2427
language_version: python3
2528
- repo: https://github.com/asottile/pyupgrade
26-
rev: v2.32.0
29+
rev: v3.1.0
2730
hooks:
2831
- id: pyupgrade
2932
args: [--py38-plus]
33+
- repo: https://github.com/MarcoGorelli/auto-walrus
34+
rev: v0.2.1
35+
hooks:
36+
- id: auto-walrus
3037
- repo: https://github.com/psf/black
31-
rev: 22.3.0
38+
rev: 22.10.0
3239
hooks:
3340
- id: black
3441
language_version: python3
3542
args: [--target-version=py38]
3643
- repo: https://github.com/PyCQA/flake8
37-
rev: 4.0.1
44+
rev: 5.0.4
3845
hooks:
3946
- id: flake8
4047
additional_dependencies: &flake8_dependencies
41-
- flake8==4.0.1
42-
- flake8-comprehensions==3.8.0
43-
- flake8-bugbear==22.3.23
48+
- flake8==5.0.4
49+
- flake8-comprehensions==3.10.0
50+
- flake8-bugbear==22.9.23
4451
- repo: https://github.com/asottile/yesqa
45-
rev: v1.3.0
52+
rev: v1.4.0
4653
hooks:
4754
- id: yesqa
4855
additional_dependencies: *flake8_dependencies
4956
- repo: https://github.com/codespell-project/codespell
50-
rev: v2.1.0
57+
rev: v2.2.2
5158
hooks:
5259
- id: codespell
5360
types_or: [python, rst, markdown]
5461
files: ^(graphblas_algorithms|docs)/
5562
# args: ["--ignore-words-list=coo,ba"]
63+
- repo: https://github.com/pre-commit/pre-commit-hooks
64+
rev: v4.3.0
65+
hooks:
66+
- id: no-commit-to-branch # no commit directly to main
5667
# Maybe: black-jupyter, blacken-docs, blackdoc mypy, velin

graphblas_algorithms/_utils.py

Lines changed: 0 additions & 46 deletions
This file was deleted.

graphblas_algorithms/algorithms/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from . import exceptions
22
from .boundary import *
3+
from .centrality import *
34
from .cluster import *
5+
from .community import *
46
from .core import *
57
from .cuts import *
68
from .dag import *
@@ -9,6 +11,7 @@
911
from .link_analysis import *
1012
from .reciprocity import *
1113
from .regular import *
14+
from .shortest_paths import *
1215
from .simple_paths import *
1316
from .smetric import *
1417
from .structuralholes import *
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from graphblas import binary, monoid, unary
2+
3+
4+
def normalize(x, how):
5+
how = how.lower()
6+
if how == "l1":
7+
denom = x.reduce().get(0)
8+
elif how == "l2":
9+
denom = (x @ x).get(0) ** 0.5
10+
elif how == "linf":
11+
denom = x.reduce(monoid.max).get(0)
12+
else:
13+
raise ValueError(f"Unknown normalization method: {how}")
14+
try:
15+
x *= 1.0 / denom
16+
except ZeroDivisionError: # pragma: no cover
17+
pass
18+
return x
19+
20+
21+
def is_converged(xprev, x, tol):
22+
"""Check convergence, L1 norm: err = sum(abs(xprev - x)); err < N * tol
23+
24+
This modifies `xprev`.
25+
"""
26+
xprev << binary.minus(xprev | x)
27+
xprev << unary.abs(xprev)
28+
err = xprev.reduce().get(0)
29+
return err < xprev.size * tol

graphblas_algorithms/algorithms/boundary.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@ def node_boundary(G, nbunch1, *, mask=None):
2525
mask = ~nbunch1.S
2626
else:
2727
mask = mask & (~nbunch1.S)
28-
bdy = any_pair(G._A.T @ nbunch1).new(mask=mask, name="boundary")
28+
bdy = any_pair(nbunch1 @ G._A).new(mask=mask, name="boundary")
2929
return bdy
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .degree_alg import *
2+
from .eigenvector import *
3+
from .katz import *
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from graphblas import Vector
2+
3+
__all__ = ["degree_centrality", "in_degree_centrality", "out_degree_centrality"]
4+
5+
6+
def _degree_centrality(G, degrees, name):
7+
N = len(G)
8+
rv = Vector(float, size=N, name=name)
9+
if N <= 1:
10+
rv << 1
11+
else:
12+
s = 1 / (N - 1)
13+
rv << s * degrees
14+
return rv
15+
16+
17+
def degree_centrality(G, *, name="degree_centrality"):
18+
if G.is_directed():
19+
degrees = G.get_property("total_degrees+")
20+
else:
21+
degrees = G.get_property("degrees+")
22+
return _degree_centrality(G, degrees, name)
23+
24+
25+
def in_degree_centrality(G, *, name="in_degree_centrality"):
26+
degrees = G.get_property("column_degrees+")
27+
return _degree_centrality(G, degrees, name)
28+
29+
30+
def out_degree_centrality(G, *, name="out_degree_centrality"):
31+
degrees = G.get_property("row_degrees+")
32+
return _degree_centrality(G, degrees, name)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from graphblas import Vector
2+
3+
from graphblas_algorithms.algorithms._helpers import is_converged, normalize
4+
from graphblas_algorithms.algorithms.exceptions import (
5+
ConvergenceFailure,
6+
GraphBlasAlgorithmException,
7+
PointlessConcept,
8+
)
9+
10+
__all__ = ["eigenvector_centrality"]
11+
12+
13+
def eigenvector_centrality(G, max_iter=100, tol=1.0e-6, nstart=None, name="eigenvector_centrality"):
14+
N = len(G)
15+
if N == 0:
16+
raise PointlessConcept("cannot compute centrality for the null graph")
17+
x = Vector(float, N, name="x")
18+
if nstart is None:
19+
x << 1.0 / N
20+
else:
21+
x << nstart
22+
denom = x.reduce().get(0) # why not use L2 norm?
23+
if denom == 0:
24+
raise GraphBlasAlgorithmException("initial vector cannot have all zero values")
25+
x *= 1.0 / denom
26+
27+
# Power iteration: make up to max_iter iterations
28+
A = G._A
29+
xprev = Vector(float, N, name="x_prev")
30+
for _ in range(max_iter):
31+
xprev << x
32+
x += x @ A
33+
normalize(x, "L2")
34+
if is_converged(xprev, x, tol): # sum(abs(xprev - x)) < N * tol
35+
x.name = name
36+
return x
37+
raise ConvergenceFailure(max_iter)

0 commit comments

Comments
 (0)