Skip to content

Commit 8e4fbae

Browse files
authored
Merge pull request #67 from smoia/fix/degree
Fix computation of degree matrix (from normal matrix to its adjacency)
2 parents 059532f + 2f841be commit 8e4fbae

2 files changed

Lines changed: 45 additions & 47 deletions

File tree

nigsp/operations/laplacian.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,6 @@ def compute_laplacian(mtx, negval="absolute", selfloops=False):
7070
f'Behaviour "{negval}" to deal with negative values is not supported'
7171
)
7272

73-
degree = mtx.sum(axis=1) # This is fixed to across columns
74-
7573
adjacency = deepcopy(mtx)
7674

7775
if selfloops is False:
@@ -89,15 +87,16 @@ def compute_laplacian(mtx, negval="absolute", selfloops=False):
8987
f"but specified matrix has {mtx.shape[0]} diagonal elements."
9088
)
9189
adjacency[np.diag_indices(adjacency.shape[0])] = selfloops
92-
degree = degree + selfloops
9390
elif selfloops == "degree":
94-
adjacency[np.diag_indices(adjacency.shape[0])] = degree
95-
degree = degree * 2
91+
adjacency[np.diag_indices(adjacency.shape[0])] = 0
92+
adjacency[np.diag_indices(adjacency.shape[0])] = adjacency.sum(axis=1)
9693
else:
9794
raise NotImplementedError(
9895
f'Value "{selfloops}" for self-loops settings is not supported'
9996
)
10097

98+
degree = adjacency.sum(axis=1) # This is fixed to across columns
99+
101100
degree_mat = np.zeros_like(mtx)
102101
degree_mat[np.diag_indices(degree_mat.shape[0])] = degree
103102

@@ -147,38 +146,38 @@ def normalisation(lapl, degree, norm="symmetric", fix_zeros=True):
147146
--------
148147
https://en.wikipedia.org/wiki/Laplacian_matrix
149148
"""
150-
degree = deepcopy(degree)
151-
if lapl.ndim - degree.ndim > 1:
149+
deg = deepcopy(degree)
150+
if lapl.ndim - deg.ndim > 1:
152151
raise NotImplementedError(
153-
f"The provided degree matrix is {degree.ndim}D while the "
152+
f"The provided degree matrix is {deg.ndim}D while the "
154153
f"provided laplacian matrix is {lapl.ndim}D."
155154
)
156-
elif lapl.ndim == degree.ndim:
157-
if not (degree.diagonal() == degree.sum(axis=1)).all():
155+
elif lapl.ndim == deg.ndim:
156+
if not (deg.diagonal() == deg.sum(axis=1)).all():
158157
raise ValueError(
159158
"The provided degree matrix is not a diagonal matrix (or a stack of)."
160159
)
161-
degree = degree.diagonal()
160+
deg = deepcopy(deg.diagonal())
162161

163-
if degree.shape != lapl.shape[:-1]:
162+
if deg.shape != lapl.shape[:-1]:
164163
raise ValueError(
165-
f"The provided degree matrix has shape {degree.shape} while the "
164+
f"The provided degree matrix has shape {deg.shape} while the "
166165
f"provided matrix has shape {lapl.shape}."
167166
)
168167

169168
if fix_zeros:
170-
degree[degree == 0] = 1
169+
deg[deg == 0] = 1
171170

172171
d = np.zeros_like(lapl)
173172

174173
if norm in ["symmetric", "symm"]:
175-
d[np.diag_indices(d.shape[0])] = degree ** (-1 / 2)
174+
d[np.diag_indices(d.shape[0])] = deg ** (-1 / 2)
176175
return d @ lapl @ d
177176
elif norm in ["random walk", "rw", "random walk inflow", "rwi"]:
178-
d[np.diag_indices(d.shape[0])] = degree ** (-1)
177+
d[np.diag_indices(d.shape[0])] = deg ** (-1)
179178
return d @ lapl
180179
elif norm in ["random walk outflow", "rwo"]:
181-
d[np.diag_indices(d.shape[0])] = degree ** (-1)
180+
d[np.diag_indices(d.shape[0])] = deg ** (-1)
182181
return lapl @ d
183182
else:
184183
raise NotImplementedError(f'Normalisation type "{norm}" is not supported.')

nigsp/tests/test_laplacian.py

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,41 +12,41 @@
1212
def test_compute_laplacian():
1313
def glap(mtx):
1414
deg = mtx.sum(axis=1)
15-
adj = dc(mtx)
16-
adj[np.diag_indices(mtx.shape[0])] = 0
1715

18-
L = np.diag(deg) - adj
16+
L = np.diag(deg) - mtx
1917
return L, deg
2018

2119
mtx = np.random.rand(4, 4)
2220
mtx = (mtx + mtx.T) / 2
2321

24-
L, deg = glap(mtx)
22+
adj = dc(mtx)
23+
adj[np.diag_indices(mtx.shape[0])] = 0
24+
L, deg = glap(adj)
2525
lapl, degree = laplacian.compute_laplacian(mtx)
2626

27-
assert (lapl == L).all()
28-
assert (degree == deg).all()
27+
assert np.allclose(lapl, L)
28+
assert np.allclose(degree, deg)
2929

3030
lapl, degree = laplacian.compute_laplacian(mtx, selfloops=True)
31-
Lsl = np.diag(mtx.sum(axis=1)) - mtx
32-
assert (lapl == Lsl).all()
33-
assert (degree == deg).all()
31+
L, deg = glap(mtx)
32+
assert np.allclose(lapl, L)
33+
assert np.allclose(degree, deg)
3434

3535
rn_deg = np.random.rand(4)
3636
lapl, degree = laplacian.compute_laplacian(mtx, selfloops=rn_deg)
37-
updeg = deg + rn_deg
3837
adj = dc(mtx)
3938
adj[np.diag_indices(mtx.shape[0])] = rn_deg
40-
Lsl = np.diag(updeg) - adj
41-
assert (lapl == Lsl).all()
42-
assert (degree == updeg).all()
39+
L, deg = glap(adj)
40+
assert np.allclose(lapl, L)
41+
assert np.allclose(degree, deg)
4342

4443
lapl, degree = laplacian.compute_laplacian(mtx, selfloops="degree")
45-
updeg = deg + deg
44+
adj[np.diag_indices(mtx.shape[0])] = 0
45+
_, deg = glap(adj)
4646
adj[np.diag_indices(mtx.shape[0])] = deg
47-
Lsl = np.diag(updeg) - adj
48-
assert (lapl == Lsl).all()
49-
assert (degree == updeg).all()
47+
L, deg = glap(adj)
48+
assert np.allclose(lapl, L)
49+
assert np.allclose(degree, deg)
5050

5151
mtx = mtx - mtx.mean()
5252

@@ -56,22 +56,21 @@ def glap(mtx):
5656
mtx_res = (mtx - mtx.min()) / mtx.max()
5757

5858
L, deg = glap(mtx_abs)
59-
lapl, degree = laplacian.compute_laplacian(mtx, negval="absolute")
60-
assert (lapl == L).all()
61-
assert (degree == deg).all()
59+
lapl, degree = laplacian.compute_laplacian(mtx, negval="absolute", selfloops=True)
60+
assert np.allclose(lapl, L)
61+
assert np.allclose(degree, deg)
6262

6363
L, deg = glap(mtx_rem)
64-
lapl, degree = laplacian.compute_laplacian(mtx, negval="remove")
65-
assert (lapl == L).all()
66-
assert (degree == deg).all()
64+
lapl, degree = laplacian.compute_laplacian(mtx, negval="remove", selfloops=True)
65+
assert np.allclose(lapl, L)
66+
assert np.allclose(degree, deg)
6767

6868
L, deg = glap(mtx_res)
69-
lapl, degree = laplacian.compute_laplacian(mtx, negval="rescale")
70-
assert (lapl == L).all()
71-
assert (degree == deg).all()
69+
lapl, degree = laplacian.compute_laplacian(mtx, negval="rescale", selfloops=True)
70+
assert np.allclose(lapl, L)
71+
assert np.allclose(degree, deg)
7272

7373

74-
@mark.xfail
7574
def test_normalisation():
7675
L = np.random.rand(4, 4)
7776
L = (L + L.T) / 2
@@ -80,10 +79,10 @@ def test_normalisation():
8079

8180
lapl_symm = laplacian.normalisation(L, d, norm="symmetric")
8281

83-
d = np.diag(d)
84-
lapl_rwi = laplacian.normalisation(L, d, norm="random walk")
82+
deg = np.diag(d)
83+
lapl_rwi = laplacian.normalisation(L, deg, norm="random walk")
8584

86-
lapl_rwo = laplacian.normalisation(L, d, norm="rwo")
85+
lapl_rwo = laplacian.normalisation(L, deg, norm="rwo")
8786

8887
d[2] = 1
8988
d_symm = np.diag(d ** (-1 / 2))

0 commit comments

Comments
 (0)