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

Updated the return type of Katz centrality #530

Merged
merged 2 commits into from
Apr 4, 2024
Merged
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
57 changes: 30 additions & 27 deletions tests/algorithms/test_centrality.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,36 +158,39 @@ def ratio(r, m, kind="CEC"):
return r ** (1.0 / m)


def test_katz_centrality(edgelist1, edgelist3):

def test_katz_centrality(edgelist1, edgelist8):
# test hypergraph with no edge
H = xgi.Hypergraph()
H.add_nodes_from([1, 2, 3])
c, nodedict = xgi.katz_centrality(H, index=True)
expected_c = np.zeros(3)
assert np.allclose(c, expected_c)
c = xgi.katz_centrality(H)
expected_c = {1: 0, 2: 0, 3: 0}
assert c == expected_c

# test numerical values
H = xgi.Hypergraph(edgelist1)
c, nodedict = xgi.katz_centrality(H, index=True)
expected_c = np.array(
[
0.2519685,
0.2519685,
0.2519685,
0.0,
0.12647448,
0.37746639,
0.25246065,
0.25246065,
]
)
assert np.allclose(c, expected_c)

# test with no index
H = xgi.Hypergraph(edgelist3)
c = xgi.katz_centrality(H, index=False)
expected_c = np.array(
[0.34686161, 0.34686161, 0.51894799, 0.51894799, 0.34686161, 0.34686161]
)
assert np.allclose(c, expected_c)
c = xgi.katz_centrality(H)
expected_c = {
1: 0.1427771519858862,
2: 0.1427771519858862,
3: 0.1427771519858862,
4: 0.0,
5: 0.07166636106392883,
6: 0.21389013015822952,
8: 0.14305602641009146,
7: 0.14305602641009146,
}
assert c == expected_c

# test with difference cutoff
H = xgi.Hypergraph(edgelist8)
c = xgi.katz_centrality(H, cutoff=5)
expected_c = {
0: 0.21358389796604274,
1: 0.1779789506060754,
2: 0.17880254869172216,
3: 0.17880254869172216,
4: 0.14321435302156882,
5: 0.07147345362079142,
6: 0.03614424740207708,
}
assert c == expected_c
46 changes: 17 additions & 29 deletions xgi/algorithms/centrality.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ def line_vector_centrality(H):
return vc


def katz_centrality(H, index=False, cutoff=100):
def katz_centrality(H, cutoff=100):
r"""Returns the Katz-centrality vector of a non-empty hypergraph H.

The Katz-centrality measures the relative importance of a node by counting
Expand All @@ -327,21 +327,15 @@ def katz_centrality(H, index=False, cutoff=100):
----------
H : xgi.Hypergraph
Hypergraph on which to compute the Katz-centralities.
index : bool
If set to `True`, will return a dictionary mapping each vector index to a
node. Default value is `False`.
cutoff : int
Power at which to stop the series :math:`A + \alpha A^2 + \alpha^2 A^3 + \dots`
Default value is 100.

Returns
-------
c : np.ndarray
Vector of the node centralities, sorted by the node indexes.
nodedict : dict
If index is set to True, nodedict will contain the nodes ids, keyed by
their indice in vector `c`.
Thus, `c[key]` will be the centrality of node `nodedict[key]`.
c : dict
`c` is a dictionary with node IDs as keys and centrality values
as values. The centralities are 1-normalized.

Raises
------
Expand All @@ -355,7 +349,7 @@ def katz_centrality(H, index=False, cutoff=100):
.. math::
c = [(I - \alpha A^{t})^{-1} - I]{\bf 1},

where :math:`A` is the adjency matrix of the the (hyper)graph.
where :math:`A` is the adjacency matrix of the the (hyper)graph.
Since :math:`A^{t} = A` for undirected graphs (our case), we have:


Expand All @@ -371,7 +365,7 @@ def katz_centrality(H, index=False, cutoff=100):
& = I

And :math:`(I - \alpha A^{t})^{-1} = I + A + \alpha A^2 + \alpha^2 A^3 + \dots`
Thus we can use the power serie to compute the Katz-centrality.
Thus we can use the power series to compute the Katz-centrality.
[2] The Katz-centrality of isolated nodes (no hyperedges contains them) is
zero. The Katz-centrality of an empty hypergraph is not defined.

Expand All @@ -380,27 +374,21 @@ def katz_centrality(H, index=False, cutoff=100):
See https://en.wikipedia.org/wiki/Katz_centrality#Alpha_centrality (visited
May 20 2023) for a clear definition of Katz centrality.
"""
n = H.num_nodes
m = H.num_edges

if index:
A, nodedict = clique_motif_matrix(H, index=True)
else:
A = clique_motif_matrix(H, index=False)

N = len(H.nodes)
M = len(H.edges)
if N == 0: # no nodes
if n == 0: # no nodes
raise XGIError("The Katz-centrality of an empty hypergraph is not defined.")
elif M == 0:
c = np.zeros(N)
elif m == 0:
c = np.zeros(n)
else: # there is at least one edge, both N and M are non-zero
alpha = 1 / 2**N
A = clique_motif_matrix(H)
alpha = 1 / 2**n
mat = A
for power in range(1, cutoff):
mat = alpha * mat.dot(A) + A
u = 1 / N * np.ones(N)
u = 1 / n * np.ones(n)
c = mat.dot(u)

if index:
return c, nodedict
else:
return c
c *= 1 / norm(c, 1)
nodedict = dict(zip(range(n), H.nodes))
return {nodedict[idx]: c[idx] for idx in nodedict}
61 changes: 61 additions & 0 deletions xgi/stats/nodestats.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"clique_eigenvector_centrality",
"h_eigenvector_centrality",
"node_edge_centrality",
"katz_centrality",
]


Expand Down Expand Up @@ -432,3 +433,63 @@ def node_edge_centrality(
"""
c, _ = xgi.node_edge_centrality(net, f, g, phi, psi, max_iter, tol)
return {n: c[n] for n in c if n in bunch}


def katz_centrality(net, bunch, cutoff=100):
"""Compute the H-eigenvector centrality of a hypergraph.

Parameters
----------
net : xgi.Hypergraph
The hypergraph of interest.
bunch : Iterable
Nodes in `net`.
cutoff : int
Power at which to stop the series :math:`A + \alpha A^2 + \alpha^2 A^3 + \dots`
Default value is 100.

Returns
-------
dict
node IDs are keys and centrality values
are values. The centralities are 1-normalized.

Raises
------
XGIError
If the hypergraph is empty.

Notes
-----
[1] The Katz-centrality is defined as

.. math::
c = [(I - \alpha A^{t})^{-1} - I]{\bf 1},

where :math:`A` is the adjacency matrix of the the (hyper)graph.
Since :math:`A^{t} = A` for undirected graphs (our case), we have:


.. math::
&[I + A + \alpha A^2 + \alpha^2 A^3 + \dots](I - \alpha A^{t})

& = [I + A + \alpha A^2 + \alpha^2 A^3 + \dots](I - \alpha A)

& = (I + A + \alpha A^2 + \alpha^2 A^3 + \dots) - A - \alpha A^2

& - \alpha^2 A^3 - \alpha^3 A^4 - \dots

& = I

And :math:`(I - \alpha A^{t})^{-1} = I + A + \alpha A^2 + \alpha^2 A^3 + \dots`
Thus we can use the power series to compute the Katz-centrality.
[2] The Katz-centrality of isolated nodes (no hyperedges contains them) is
zero. The Katz-centrality of an empty hypergraph is not defined.

References
----------
See https://en.wikipedia.org/wiki/Katz_centrality#Alpha_centrality (visited
May 20 2023) for a clear definition of Katz centrality.
"""
c = xgi.katz_centrality(net, cutoff=cutoff)
return {n: c[n] for n in c if n in bunch}
Loading