Skip to content

Commit af50ba3

Browse files
committed
Add auto-formatting on commit
1 parent 75ee7bb commit af50ba3

File tree

6 files changed

+126
-79
lines changed

6 files changed

+126
-79
lines changed

format.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
3+
cd "$(dirname "$0")"
4+
./env/bin/black graphpca/*.py test/*.py

graphpca/__init__.py

Lines changed: 60 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ def reduce_graph(nx_graph, output_dim):
4242
return reduce_graph_efficiently(nx_graph, output_dim, add_supernode=True)
4343

4444

45-
def reduce_graph_efficiently(nx_graph, output_dim, add_supernode=False,
46-
eigendecomp_strategy='smart'):
45+
def reduce_graph_efficiently(
46+
nx_graph, output_dim, add_supernode=False, eigendecomp_strategy="smart"
47+
):
4748
"""
4849
Run PCA on the ETCD of the input NetworkX graph
4950
@@ -90,38 +91,38 @@ def reduce_graph_efficiently(nx_graph, output_dim, add_supernode=False,
9091
:class:`numpy.ndarray`
9192
The reduced data in output_dim dimensions
9293
"""
93-
LOG.debug('Entering reduce_graph')
94+
LOG.debug("Entering reduce_graph")
9495
assert output_dim < len(nx_graph)
95-
LOG.info('Calculating Laplacian L')
96+
LOG.info("Calculating Laplacian L")
9697
L = nx.laplacian_matrix(nx_graph)
97-
LOG.debug('L.shape: {}'.format(L.shape))
98+
LOG.debug("L.shape: {}".format(L.shape))
9899
if add_supernode:
99100
L = _add_supernode_to_laplacian(L)
100-
LOG.info('Calculating nullity of L as connected components of nx_graph')
101+
LOG.info("Calculating nullity of L as connected components of nx_graph")
101102
nullity = nx.number_connected_components(nx_graph)
102-
LOG.info('Calculating smallest eigenvalues of L & corresponding eigenvectors')
103-
(E, U) = _eigendecomp(eigendecomp_strategy, L, output_dim + nullity, which='SM')
104-
LOG.debug('Eigenvalues: {}'.format(E))
105-
LOG.info('Assembling PCA result')
103+
LOG.info("Calculating smallest eigenvalues of L & corresponding eigenvectors")
104+
(E, U) = _eigendecomp(eigendecomp_strategy, L, output_dim + nullity, which="SM")
105+
LOG.debug("Eigenvalues: {}".format(E))
106+
LOG.info("Assembling PCA result")
106107
# If we added a supernode, now remove it
107108
if add_supernode:
108109
# Remove data row
109110
U = U[:-1, :]
110111
# Remove eigenpair with negative value, which correspond to supernode
111112
neg_indexes = np.where(E < 0.0)
112-
LOG.debug('Neg indexes: {}'.format(neg_indexes))
113+
LOG.debug("Neg indexes: {}".format(neg_indexes))
113114
E = np.delete(E, neg_indexes)
114115
U = np.delete(U, neg_indexes, axis=1)
115116
# Remove the 0 eigenvalues and corresponding eigenvectors
116117
# Use tolerance value 10 x from numpy.linalg.matrix_rank
117118
tol = E.max() * max(L.shape) * np.finfo(float).eps * 10
118-
LOG.debug('Using tolerance {}'.format(tol))
119+
LOG.debug("Using tolerance {}".format(tol))
119120
zero_indexes = [i for i in range(len(E)) if abs(E[i]) < tol]
120121
E = np.delete(E, zero_indexes)
121122
U = np.delete(U, zero_indexes, axis=1)
122123
# Invert eigenvalues to get largest eigenvalues of L-pseudoinverse
123-
Ep = 1/E
124-
LOG.debug('Filtered & Inverted Eigenvalues: {}'.format(Ep))
124+
Ep = 1 / E
125+
LOG.debug("Filtered & Inverted Eigenvalues: {}".format(Ep))
125126
# Orient Eigenvectors
126127
_orient_eigenvectors(U)
127128
# Assemble into the right structure
@@ -132,7 +133,7 @@ def reduce_graph_efficiently(nx_graph, output_dim, add_supernode=False,
132133
return X
133134

134135

135-
def reduce_graph_naively(nx_graph, output_dim, eigendecomp_strategy='exact'):
136+
def reduce_graph_naively(nx_graph, output_dim, eigendecomp_strategy="exact"):
136137
"""
137138
Run PCA on the ETCD of a NetworkX graph using a slow but precise method
138139
@@ -165,17 +166,19 @@ def reduce_graph_naively(nx_graph, output_dim, eigendecomp_strategy='exact'):
165166
:class:`numpy.ndarray`
166167
The reduced data in output_dim dimensions
167168
"""
168-
LOG.debug('Entering naive_reduce_graph')
169+
LOG.debug("Entering naive_reduce_graph")
169170
L = nx.laplacian_matrix(nx_graph).todense()
170-
LOG.info('Calculating Moore-Penrose inverse of the Laplacian L')
171+
LOG.info("Calculating Moore-Penrose inverse of the Laplacian L")
171172
Li = np.linalg.pinv(L)
172-
LOG.info('Calculating largest eigenvalues of L-inverse & corresponding eigenvectors')
173-
(E, U) = _eigendecomp(eigendecomp_strategy, Li, output_dim, which='LM')
173+
LOG.info(
174+
"Calculating largest eigenvalues of L-inverse & corresponding eigenvectors"
175+
)
176+
(E, U) = _eigendecomp(eigendecomp_strategy, Li, output_dim, which="LM")
174177
# Flip so largest eigen first
175178
E = E[::-1]
176179
U = np.fliplr(U)
177-
LOG.debug('Eigenvalues: {}'.format(E))
178-
LOG.info('Assembling PCA result')
180+
LOG.debug("Eigenvalues: {}".format(E))
181+
LOG.info("Assembling PCA result")
179182
# Assemble into the right structure
180183
X = np.zeros((output_dim, len(nx_graph)))
181184
sqrtE = np.sqrt(E)
@@ -185,7 +188,7 @@ def reduce_graph_naively(nx_graph, output_dim, eigendecomp_strategy='exact'):
185188

186189

187190
def _add_supernode_to_laplacian(L):
188-
L_padded = np.ones([n+1 for n in L.shape])
191+
L_padded = np.ones([n + 1 for n in L.shape])
189192
L_padded[:-1, :-1] = L.todense()
190193
return L_padded
191194

@@ -195,9 +198,9 @@ def _orient_eigenvectors(U):
195198
for i in range(U.shape[1]):
196199
try:
197200
if next(u for u in U[:, i] if np.fabs(u) > threshold) < 0.0:
198-
U[:, i] = - U[:, i]
201+
U[:, i] = -U[:, i]
199202
except StopIteration:
200-
LOG.debug('Zero eigenvector at index {}'.format(i))
203+
LOG.debug("Zero eigenvector at index {}".format(i))
201204
continue
202205
return U
203206

@@ -235,9 +238,9 @@ def _eigendecomp(eigendecomp_strategy, M, output_dim, which, *args, **kwargs):
235238
The corresponding eigenvectors of M
236239
237240
"""
238-
if eigendecomp_strategy == 'exact':
241+
if eigendecomp_strategy == "exact":
239242
return _exact_eigendecomp(M, output_dim, which)
240-
elif eigendecomp_strategy == 'sparse':
243+
elif eigendecomp_strategy == "sparse":
241244
return _sparse_eigendecomp(M, output_dim, which, *args, **kwargs)
242245
else:
243246
if M.shape[0] < 1000:
@@ -247,50 +250,58 @@ def _eigendecomp(eigendecomp_strategy, M, output_dim, which, *args, **kwargs):
247250

248251

249252
def _exact_eigendecomp(M, output_dim, which):
250-
LOG.debug('Using _exact_eigendecomp')
253+
LOG.debug("Using _exact_eigendecomp")
251254
if scipy.sparse.issparse(M):
252255
M = M.todense()
253256
E, U = scipy.linalg.eigh(M)
254257
# Cut out eigenpairs
255-
if which == 'SM':
258+
if which == "SM":
256259
E = E[:output_dim]
257260
U = U[:, :output_dim]
258261
U = _orient_eigenvectors(U)
259-
elif which == 'LM':
260-
E = E[E.shape[0] - output_dim:]
261-
U = U[:, U.shape[1] - output_dim:]
262+
elif which == "LM":
263+
E = E[E.shape[0] - output_dim :]
264+
U = U[:, U.shape[1] - output_dim :]
262265
U = _orient_eigenvectors(U)
263266
else:
264-
raise NotImplementedError('Unknown setting for `which`: {}'.format(which))
267+
raise NotImplementedError("Unknown setting for `which`: {}".format(which))
265268
return E, U
266269

267270

268271
def _sparse_eigendecomp(M, output_dim, which, tol=0.000000001, _attempt=0, **kwargs):
269-
LOG.debug('Using _sparse_eigendecomp')
272+
LOG.debug("Using _sparse_eigendecomp")
270273
try:
271-
M = M.astype('d')
272-
if which == 'SM':
274+
M = M.astype("d")
275+
if which == "SM":
273276
# Use shift-invert method to calculate smallest eigenpairs.
274277
# Use very small sigma since `sigma=0.0` fails with
275278
# RuntimeError: Factor is exactly singular
276-
E, U = scipy.sparse.linalg.eigsh(M, output_dim, sigma=0.00001,
277-
which='LM', tol=tol, **kwargs)
279+
E, U = scipy.sparse.linalg.eigsh(
280+
M, output_dim, sigma=0.00001, which="LM", tol=tol, **kwargs
281+
)
278282
else:
279-
E, U = scipy.sparse.linalg.eigsh(M, output_dim, which=which, tol=tol, **kwargs)
283+
E, U = scipy.sparse.linalg.eigsh(
284+
M, output_dim, which=which, tol=tol, **kwargs
285+
)
280286
U = _orient_eigenvectors(U)
281287
return E, U
282288
except ArpackNoConvergence as e:
283289
if _attempt > 2:
284-
LOG.error('Eigendecomp did not converge. Bailing.')
285-
raise e
290+
LOG.error("Eigendecomp did not converge. Bailing.")
291+
raise e
286292
LOG.info(e)
287293
new_tol = tol * 10
288-
LOG.info('Eigendecomp failed to converge, retrying with tolerance {}'.format(new_tol))
289-
return _sparse_eigendecomp(M, output_dim, which=which, tol=new_tol, _attempt=_attempt+1)
294+
LOG.info(
295+
"Eigendecomp failed to converge, retrying with tolerance {}".format(new_tol)
296+
)
297+
return _sparse_eigendecomp(
298+
M, output_dim, which=which, tol=new_tol, _attempt=_attempt + 1
299+
)
290300

291301

292-
def plot_2d(pca_output_2d, colormap_name='winter'):
302+
def plot_2d(pca_output_2d, colormap_name="winter"):
293303
import matplotlib.pyplot as plt
304+
294305
x = pca_output_2d[0, :]
295306
y = pca_output_2d[1, :]
296307
colormap = plt.get_cmap(colormap_name)
@@ -312,9 +323,12 @@ def draw_graph(nx_graph):
312323
The graph to be plotted
313324
"""
314325
import matplotlib.pyplot as plt
326+
315327
reduced_2 = reduce_graph(nx_graph, 2)
316328
for edge in nx_graph.edges():
317-
plt.plot([reduced_2[0, edge[0]], reduced_2[0, edge[1]]],
318-
[reduced_2[1, edge[0]], reduced_2[1, edge[1]]],
319-
'b-')
329+
plt.plot(
330+
[reduced_2[0, edge[0]], reduced_2[0, edge[1]]],
331+
[reduced_2[1, edge[0]], reduced_2[1, edge[1]]],
332+
"b-",
333+
)
320334
plot_2d(reduced_2)

hooks/pre-commit

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
3+
$(git rev-parse --show-toplevel)/format.sh
4+
git update-index --again

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ matplotlib
88

99
# Development
1010
docutils
11+
black

setup.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
python3 -m venv env
44
./env/bin/pip install --upgrade pip
55
./env/bin/pip install -r requirements.txt
6+
ln -s ../../hooks/pre-commit .git/hooks/

test/test_graphpca.py

Lines changed: 56 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414

1515

1616
def get_fixture_mat(filename):
17-
return scipy.io.loadmat(os.path.dirname(os.path.realpath(__file__)) + "/" + filename)
17+
return scipy.io.loadmat(
18+
os.path.dirname(os.path.realpath(__file__)) + "/" + filename
19+
)
1820

19-
class TestGraphPCA(unittest.TestCase):
2021

22+
class TestGraphPCA(unittest.TestCase):
2123
def test_returns_plausible_results(self):
2224
g = nx.erdos_renyi_graph(100, 0.3)
2325
g_5 = graphpca.reduce_graph_efficiently(g, 5)
@@ -39,75 +41,96 @@ def test_ok_if_multiple_zero_eigens(self):
3941
max_val = max(abs(g_5[i]))
4042
self.assertGreater(max_val, 0.01)
4143

42-
@unittest.skip('This fails and I have no idea why')
44+
@unittest.skip("This fails and I have no idea why")
4345
def test_similar_output_to_naive_peterson(self):
4446
G = nx.petersen_graph()
4547
G2 = graphpca.reduce_graph_efficiently(G, 2)
4648
G2n = graphpca.reduce_graph_naively(G, 2)
47-
self.assertTrue(np.allclose(G2, G2n, rtol=1e-04, atol=1e-06),
48-
'Regular result:\n{}\nNaive result:\n{}\n'.format(G2, G2n))
49+
self.assertTrue(
50+
np.allclose(G2, G2n, rtol=1e-04, atol=1e-06),
51+
"Regular result:\n{}\nNaive result:\n{}\n".format(G2, G2n),
52+
)
4953

5054
def test_similar_output_to_naive_small(self):
5155
G = nx.erdos_renyi_graph(10, 0.5)
5256
G2 = graphpca.reduce_graph_efficiently(G, 2)
5357
G2n = graphpca.reduce_graph_naively(G, 2)
54-
self.assertTrue(np.allclose(G2, G2n, rtol=1e-04, atol=1e-06),
55-
'Regular result:\n{}\nNaive result:\n{}\n'.format(G2, G2n))
58+
self.assertTrue(
59+
np.allclose(G2, G2n, rtol=1e-04, atol=1e-06),
60+
"Regular result:\n{}\nNaive result:\n{}\n".format(G2, G2n),
61+
)
5662

5763
def test_similar_output_to_naive_mat_3(self):
58-
mat = get_fixture_mat('bcspwr01.mat')
64+
mat = get_fixture_mat("bcspwr01.mat")
5965
# I love the UFSMC (https://www.cise.ufl.edu/research/sparse/matrices/)
6066
# but wow they really buried the matrix in this .mat
61-
A = mat['Problem'][0][0][1].todense()
67+
A = mat["Problem"][0][0][1].todense()
6268
G = nx.from_numpy_matrix(A)
6369
G3 = graphpca.reduce_graph_efficiently(G, 3)
6470
G3n = graphpca.reduce_graph_naively(G, 3)
65-
self.assertTrue(np.allclose(G3, G3n, rtol=1e-04, atol=1e-06),
66-
'Regular result:\n{}\nNaive result:\n{}\n'.format(G3, G3n))
71+
self.assertTrue(
72+
np.allclose(G3, G3n, rtol=1e-04, atol=1e-06),
73+
"Regular result:\n{}\nNaive result:\n{}\n".format(G3, G3n),
74+
)
6775

6876
def test_similar_output_to_naive_big(self):
6977
G = nx.erdos_renyi_graph(1001, 0.02)
7078
G2 = graphpca.reduce_graph_efficiently(G, 2)
7179
G2n = graphpca.reduce_graph_naively(G, 2)
72-
self.assertTrue(np.allclose(G2, G2n, rtol=1e-03, atol=1e-05),
73-
'Regular result:\n{}\nNaive result:\n{}\n'.format(G2, G2n))
80+
self.assertTrue(
81+
np.allclose(G2, G2n, rtol=1e-03, atol=1e-05),
82+
"Regular result:\n{}\nNaive result:\n{}\n".format(G2, G2n),
83+
)
7484

7585
def test_add_supernode_similar_output_to_naive_small(self):
7686
G = nx.erdos_renyi_graph(10, 0.5)
7787
G2 = graphpca.reduce_graph_efficiently(G, 2, add_supernode=True)
7888
G2n = graphpca.reduce_graph_naively(G, 2)
79-
self.assertTrue(np.allclose(G2, G2n, rtol=1e-02, atol=1e-06),
80-
'Regular result:\n{}\nNaive result:\n{}\n'.format(G2, G2n))
89+
self.assertTrue(
90+
np.allclose(G2, G2n, rtol=1e-02, atol=1e-06),
91+
"Regular result:\n{}\nNaive result:\n{}\n".format(G2, G2n),
92+
)
8193

8294
def test_add_supernode_similar_output_to_naive_mat_3(self):
83-
mat = get_fixture_mat('bcspwr01.mat')
84-
A = mat['Problem'][0][0][1].todense()
95+
mat = get_fixture_mat("bcspwr01.mat")
96+
A = mat["Problem"][0][0][1].todense()
8597
G = nx.from_numpy_matrix(A)
8698
G3 = graphpca.reduce_graph_efficiently(G, 3, add_supernode=True)
8799
G3n = graphpca.reduce_graph_naively(G, 3)
88-
self.assertTrue(np.allclose(G3, G3n, rtol=1e-02, atol=1e-06),
89-
'Regular result:\n{}\nNaive result:\n{}\n'.format(G3, G3n))
100+
self.assertTrue(
101+
np.allclose(G3, G3n, rtol=1e-02, atol=1e-06),
102+
"Regular result:\n{}\nNaive result:\n{}\n".format(G3, G3n),
103+
)
90104

91105
def test_add_supernode_similar_output_to_naive_big(self):
92106
G = nx.watts_strogatz_graph(1001, 10, 0.05)
93107
G2 = graphpca.reduce_graph_efficiently(G, 2, add_supernode=True)
94108
G2n = graphpca.reduce_graph_naively(G, 2)
95-
self.assertTrue(np.allclose(G2, G2n, rtol=1e-01, atol=1e-02),
96-
'Regular result:\n{}\nNaive result:\n{}\n'.format(G2, G2n))
109+
self.assertTrue(
110+
np.allclose(G2, G2n, rtol=1e-01, atol=1e-02),
111+
"Regular result:\n{}\nNaive result:\n{}\n".format(G2, G2n),
112+
)
97113

98114
def test_exact_eigendomp_same_as_sparse(self):
99115
g = nx.erdos_renyi_graph(10, 0.5)
100-
l = nx.laplacian_matrix(g).astype('d')
116+
l = nx.laplacian_matrix(g).astype("d")
101117
# Test for smallest eigs
102-
Eb, Ub = graphpca._sparse_eigendecomp(l, 4, which='SM')
103-
Es, Us = graphpca._exact_eigendecomp(l, 4, which='SM')
104-
self.assertTrue(np.allclose(Eb, Es), 'Big vals: {}\nSmall vals: {}\n'.format(Eb, Es))
105-
self.assertTrue(np.allclose(Ub, Us, rtol=1e-09, atol=1e-09),
106-
'Big vecs:\n{}\nSmall vecs:\n{}\n'.format(Ub, Us))
118+
Eb, Ub = graphpca._sparse_eigendecomp(l, 4, which="SM")
119+
Es, Us = graphpca._exact_eigendecomp(l, 4, which="SM")
120+
self.assertTrue(
121+
np.allclose(Eb, Es), "Big vals: {}\nSmall vals: {}\n".format(Eb, Es)
122+
)
123+
self.assertTrue(
124+
np.allclose(Ub, Us, rtol=1e-09, atol=1e-09),
125+
"Big vecs:\n{}\nSmall vecs:\n{}\n".format(Ub, Us),
126+
)
107127
# Test for biggest eigs
108-
Eb, Ub = graphpca._sparse_eigendecomp(l, 4, which='LM')
109-
Es, Us = graphpca._exact_eigendecomp(l, 4, which='LM')
110-
self.assertTrue(np.allclose(Eb, Es), 'Big vals: {}\nSmall vals: {}\n'.format(Eb, Es))
111-
self.assertTrue(np.allclose(Ub, Us, rtol=1e-09, atol=1e-09),
112-
'Big vecs:\n{}\nSmall vecs:\n{}\n'.format(Ub, Us))
113-
128+
Eb, Ub = graphpca._sparse_eigendecomp(l, 4, which="LM")
129+
Es, Us = graphpca._exact_eigendecomp(l, 4, which="LM")
130+
self.assertTrue(
131+
np.allclose(Eb, Es), "Big vals: {}\nSmall vals: {}\n".format(Eb, Es)
132+
)
133+
self.assertTrue(
134+
np.allclose(Ub, Us, rtol=1e-09, atol=1e-09),
135+
"Big vecs:\n{}\nSmall vecs:\n{}\n".format(Ub, Us),
136+
)

0 commit comments

Comments
 (0)