Skip to content

Commit b61c1ff

Browse files
authored
Merge pull request #64 from smoia/feat/self-loops
Add settings to consider self-loops when computing Laplacians
2 parents 51e4492 + b6f30a6 commit b61c1ff

File tree

3 files changed

+56
-5
lines changed

3 files changed

+56
-5
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ NiGSP
88
[![See the documentation at: https://nigsp.readthedocs.io](https://img.shields.io/badge/docs-read%20latest-informational?style=flat&logo=readthedocs)](https://nigsp.readthedocs.io/en/latest/?badge=latest)
99
[![Latest DOI](https://zenodo.org/badge/446805866.svg?style=flat&logo=zenodo)](https://zenodo.org/badge/latestdoi/446805866)
1010
<!-- For badge only:
11-
[![Latest DOI](https://img.shields.io/badge/DOI-<NEWDOI>%2Fzenodo.7842386-blue?style=for-the-badge&logo=zenodo)](https://img.shields.io/badge/DOI-<NEWDOI>%2Fzenodo.7842386-blue?style=for-the-badge&logo=zenodo)
11+
[![Latest DOI](https://img.shields.io/badge/DOI-<NEWDOI>%2Fzenodo.7842386-blue?style=for-the-badge&logo=zenodo)](https://img.shields.io/badge/DOI-<NEWDOI>%2Fzenodo.7842386-blue?style=for-the-badge&logo=zenodo)
1212
-->
1313
[![Licensed Apache 2.0](https://img.shields.io/github/license/MIPLabCH/nigsp?style=flat&logo=apache)](https://github.com/MIPLabCH/nigsp/blob/master/LICENSE)
1414

nigsp/operations/laplacian.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
LGR = logging.getLogger(__name__)
1717

1818

19-
def compute_laplacian(mtx, negval="absolute"):
19+
def compute_laplacian(mtx, negval="absolute", selfloops=False):
2020
"""
2121
Compute Laplacian (L) matrix from a square matrix.
2222
@@ -28,18 +28,24 @@ def compute_laplacian(mtx, negval="absolute"):
2828
mtx : numpy.ndarray
2929
A square matrix
3030
negval : "absolute", "remove", or "rescale"
31-
The intended behaviour to deal with negative values:
31+
The intended behaviour to deal with negative values in matrix:
3232
- "absolute" will take absolute values of the matrix
3333
- "remove" will set all negative elements to 0
3434
- "rescale" will rescale the matrix between 0 and 1.
3535
Default is "absolute".
36+
selfloops : "degree", bool, or numpy.ndarray
37+
Allow or remove self-loops in input matrix. A numpy array can be used to specify
38+
particular loops directly in the adjacency matrix.
39+
The degree matrix of the Adjacency matrix can also be used instead.
40+
In the last two cases, the degree matrix will be updated accordingly.
41+
Default is to remove self loops (False).
3642
3743
Returns
3844
-------
3945
numpy.ndarray
4046
The laplacian of mtx
4147
numpy.ndarray
42-
The degree matrix of mtx as a (mtx.ndim-1)D array
48+
The degree matrix of mtx as a (mtx.ndim-1)D array, updated with selfloops in case.
4349
4450
See Also
4551
--------
@@ -49,6 +55,7 @@ def compute_laplacian(mtx, negval="absolute"):
4955
------
5056
NotImplementedError
5157
If negval is not "absolute", "remove", or "rescale"
58+
If selfloop
5259
"""
5360
mtx = deepcopy(mtx)
5461
if mtx.min() < 0:
@@ -66,7 +73,30 @@ def compute_laplacian(mtx, negval="absolute"):
6673
degree = mtx.sum(axis=1) # This is fixed to across columns
6774

6875
adjacency = deepcopy(mtx)
69-
adjacency[np.diag_indices(adjacency.shape[0])] = 0
76+
77+
if selfloops is False:
78+
adjacency[np.diag_indices(adjacency.shape[0])] = 0
79+
elif selfloops is True:
80+
pass
81+
elif type(selfloops) == np.ndarray:
82+
if selfloops.ndim > 1:
83+
raise NotImplementedError(
84+
"Multidimensional arrays are not implemented to specify self-loops"
85+
)
86+
if selfloops.shape[0] != mtx.shape[0]:
87+
raise ValueError(
88+
f"Array specified for self-loops has {selfloops.shape[0]} elements, "
89+
f"but specified matrix has {mtx.shape[0]} diagonal elements."
90+
)
91+
adjacency[np.diag_indices(adjacency.shape[0])] = selfloops
92+
degree = degree + selfloops
93+
elif selfloops == "degree":
94+
adjacency[np.diag_indices(adjacency.shape[0])] = degree
95+
degree = degree * 2
96+
else:
97+
raise NotImplementedError(
98+
f'Value "{selfloops}" for self-loops settings is not supported'
99+
)
70100

71101
degree_mat = np.zeros_like(mtx)
72102
degree_mat[np.diag_indices(degree_mat.shape[0])] = degree

nigsp/tests/test_laplacian.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,27 @@ def glap(mtx):
2727
assert (lapl == L).all()
2828
assert (degree == deg).all()
2929

30+
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()
34+
35+
rn_deg = np.random.rand(4)
36+
lapl, degree = laplacian.compute_laplacian(mtx, selfloops=rn_deg)
37+
updeg = deg + rn_deg
38+
adj = dc(mtx)
39+
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()
43+
44+
lapl, degree = laplacian.compute_laplacian(mtx, selfloops="degree")
45+
updeg = deg + deg
46+
adj[np.diag_indices(mtx.shape[0])] = deg
47+
Lsl = np.diag(updeg) - adj
48+
assert (lapl == Lsl).all()
49+
assert (degree == updeg).all()
50+
3051
mtx = mtx - mtx.mean()
3152

3253
mtx_abs = abs(mtx)

0 commit comments

Comments
 (0)