Skip to content

Commit

Permalink
Add tensor centralities (#600)
Browse files Browse the repository at this point in the history
* Added tensor methods

* added tensor centralities

* update

* add refs

* add ref

* added unit tests

* Update xgi/algorithms/centrality.py

Co-authored-by: Maxime Lucas <[email protected]>

* Update xgi/algorithms/centrality.py

Co-authored-by: Maxime Lucas <[email protected]>

* rename measures

* add centralities to nodestats

* add docs for tensor methods

* add notebook

* Update comparing_centralities.ipynb

* fix node_edge_centrality

* update notebook

* add notebook to docs

* removed assets

* change order of case studies and cookbook

* added to "see also"

* add docstrings and cleanup function

* Update tensor.py

* rename things

* added test to catch the array helper function

* added docstrings

* add test that the node-edge centrality converges

* add test

* Update test_centrality.py

---------

Co-authored-by: Maxime Lucas <[email protected]>
  • Loading branch information
nwlandry and maximelucas authored Nov 12, 2024
1 parent 85f74e7 commit 1bdba44
Show file tree
Hide file tree
Showing 13 changed files with 1,000 additions and 171 deletions.
21 changes: 21 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,24 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


---------------------------------

License

Tensor methods for nonuniform hypergraphs

* Tensor methods functionality for the CompleX Group Interactions library

Copyright 2023, 2024 Battelle Memorial Institute

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2 changes: 2 additions & 0 deletions docs/source/api/algorithms/xgi.algorithms.centrality.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ xgi.algorithms.centrality

.. autofunction:: clique_eigenvector_centrality
.. autofunction:: h_eigenvector_centrality
.. autofunction:: z_eigenvector_centrality
.. autofunction:: node_edge_centrality
.. autofunction:: line_vector_centrality
.. autofunction:: katz_centrality
.. autofunction:: uniform_h_eigenvector_centrality
8 changes: 5 additions & 3 deletions docs/source/api/stats/xgi.stats.nodestats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@

.. autofunction:: attrs
.. autofunction:: average_neighbor_degree
.. autofunction:: clique_eigenvector_centrality
.. autofunction:: clustering_coefficient
.. autofunction:: degree
.. autofunction:: clique_eigenvector_centrality
.. autofunction:: h_eigenvector_centrality
.. autofunction:: local_clustering_coefficient
.. autofunction:: z_eigenvector_centrality
.. autofunction:: katz_centrality
.. autofunction:: node_edge_centrality
.. autofunction:: clustering_coefficient
.. autofunction:: local_clustering_coefficient
.. autofunction:: two_node_clustering_coefficient
.. autofunction:: local_simplicial_fraction
.. autofunction:: local_edit_simpliciality
Expand Down
3 changes: 2 additions & 1 deletion docs/source/api/tutorials/case_studies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ Case studies
:maxdepth: 1

case_study_1
case_study_2
case_study_2
case_study_3
3 changes: 3 additions & 0 deletions docs/source/api/tutorials/case_study_3.nblink
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"path": "../../../../tutorials/case_studies/comparing_centralities.ipynb"
}
23 changes: 11 additions & 12 deletions docs/source/user_guides.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,39 +59,38 @@ User Guides
To the in-depth tutorials

.. grid::

.. grid-item-card::
:text-align: center

Cookbook
Case studies
^^^

Recipes to solve specific tasks in a few lines

To see how others have used XGI in their work
+++

.. button-ref:: api/tutorials/recipes
.. button-ref:: api/tutorials/case_studies
:expand:
:color: secondary
:click-parent:

To the cookbook
To the case studies
.. grid-item-card::
:text-align: center

Case studies
Cookbook
^^^

To see how others have used XGI in their work
Recipes to solve specific tasks in a few lines

+++

.. button-ref:: api/tutorials/case_studies
.. button-ref:: api/tutorials/recipes
:expand:
:color: secondary
:click-parent:

To the case studies

To the cookbook

For all specifications and options of a particular function, or to explore all existing functions, see the `API Reference <reference.html>`_.
5 changes: 5 additions & 0 deletions docs/source/using-xgi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ Published work
2024
----

Sinan G. Aksoy, Ilya Amburg, and Stephen J. Young, "Scalable Tensor Methods for Nonuniform Hypergraphs", *SIAM Journal on Mathematics of Data Science*, Vol. 6, Iss. 2, 481-503 (2024).

:bdg-link-primary-line:`Paper <https://doi.org/10.1137/23M1584472>`
:bdg-link-primary-line:`Code <https://github.com/pnnl/GENTTSV>`

Gonzalo Contreras-Aso, Regino Criado, and Miguel Romance, "Beyond directed hypergraphs: heterogeneous hypergraphs and spectral centralities", *Journal of Complex Networks*, Volume 12, Issue 4, cnae037 (2024).

:bdg-link-primary-line:`Paper <https://doi.org/10.1093/comnet/cnae037>`
Expand Down
191 changes: 150 additions & 41 deletions tests/algorithms/test_centrality.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,47 +28,54 @@ def test_clique_eigenvector_centrality():
H = xgi.sunflower(3, 1, 3)
c = H.nodes.clique_eigenvector_centrality.asnumpy()
assert norm(c[1:] - c[1]) < 1e-4
assert abs(c[0] / c[1] - ratio(3, 3, kind="CEC")) < 1e-4
assert abs(c[0] / c[1] - _ratio(3, 3, kind="CEC")) < 1e-4

H = xgi.sunflower(5, 1, 7)
c = H.nodes.clique_eigenvector_centrality.asnumpy()
assert norm(c[1:] - c[1]) < 1e-4
assert abs(c[0] / c[1] - ratio(5, 7, kind="CEC")) < 1e-4
assert abs(c[0] / c[1] - _ratio(5, 7, kind="CEC")) < 1e-4


@pytest.mark.slow
def test_h_eigenvector_centrality():
def test_uniform_h_eigenvector_centrality():
# test empty hypergraph
H = xgi.Hypergraph()
c = xgi.h_eigenvector_centrality(H)
c = xgi.uniform_h_eigenvector_centrality(H)
assert c == dict()

# Test no edges
H.add_nodes_from([0, 1, 2])
hec = xgi.h_eigenvector_centrality(H)
hec = xgi.uniform_h_eigenvector_centrality(H)
for i in hec:
assert np.isnan(hec[i])

# test disconnected
H.add_edge([0, 1])
hec = xgi.h_eigenvector_centrality(H)
hec = xgi.uniform_h_eigenvector_centrality(H)
assert set(hec) == {0, 1, 2}
for i in hec:
assert np.isnan(hec[i])

H = xgi.sunflower(3, 1, 5)
c = H.nodes.h_eigenvector_centrality(max_iter=1000).asnumpy()
c = xgi.uniform_h_eigenvector_centrality(H, max_iter=1000)
c = np.array(list(c.values()))
assert norm(c[1:] - c[1]) < 1e-4
assert abs(c[0] / c[1] - ratio(3, 5, kind="HEC")) < 1e-4
assert abs(c[0] / c[1] - _ratio(3, 5, kind="HEC")) < 1e-4

H = xgi.sunflower(5, 1, 7)
c = H.nodes.h_eigenvector_centrality(max_iter=1000).asnumpy()
c = xgi.uniform_h_eigenvector_centrality(H, max_iter=1000)
c = np.array(list(c.values()))
assert norm(c[1:] - c[1]) < 1e-4
assert abs(c[0] / c[1] - ratio(5, 7, kind="HEC")) < 1e-4
assert abs(c[0] / c[1] - _ratio(5, 7, kind="HEC")) < 1e-4

with pytest.raises(XGIError):
H = xgi.Hypergraph([[1, 2], [2, 3, 4]])
H.nodes.h_eigenvector_centrality.asnumpy()
xgi.uniform_h_eigenvector_centrality(H)

# non-convergence
with pytest.raises(XGIError):
H = xgi.Hypergraph([[1, 2], [2, 3, 4]])
xgi.uniform_h_eigenvector_centrality(H, max_iter=2)


def test_node_edge_centrality():
Expand Down Expand Up @@ -105,6 +112,11 @@ def test_node_edge_centrality():
c = H.edges.node_edge_centrality.asnumpy()
assert abs(c[0] - c[1]) < 1e-6

H = xgi.load_xgi_data("email-enron").cleanup()
c = xgi.node_edge_centrality(H)
assert len(c[0]) == H.num_nodes
assert len(c[1]) == H.num_edges


def test_line_vector_centrality():
H = xgi.Hypergraph()
Expand All @@ -128,36 +140,6 @@ def test_line_vector_centrality():
xgi.line_vector_centrality(H)


def ratio(r, m, kind="CEC"):
"""Generate the ratio between largest and second largest centralities
for the sunflower hypergraph with one core node.
Parameters
----------
r : int
Number of petals
m : int
Size of edges
kind : str, default: "CEC"
"CEC" or "HEC"
Returns
-------
float
Ratio
References
----------
Three Hypergraph Eigenvector Centralities,
Austin R. Benson,
https://doi.org/10.1137/18M1203031
"""
if kind == "CEC":
return 2 * r * (m - 1) / (np.sqrt(m**2 + 4 * (m - 1) * (r - 1)) + m - 2)
elif kind == "HEC":
return r ** (1.0 / m)


def test_katz_centrality(edgelist1, edgelist8):
# test hypergraph with no edge
H = xgi.Hypergraph()
Expand Down Expand Up @@ -195,3 +177,130 @@ def test_katz_centrality(edgelist1, edgelist8):
}
for n in c:
assert np.allclose(c[n], expected_c[n])


@pytest.mark.slow
def test_h_eigenvector_centrality():
# test empty hypergraph
H = xgi.Hypergraph()
c = xgi.h_eigenvector_centrality(H)
assert c == dict()

# Test no edges
H.add_nodes_from([0, 1, 2])
hec = xgi.h_eigenvector_centrality(H)
for i in hec:
assert np.isnan(hec[i])

# test disconnected
H.add_edge([0, 1])
hec = xgi.h_eigenvector_centrality(H)
assert set(hec) == {0, 1, 2}
for i in hec:
assert np.isnan(hec[i])

H = xgi.sunflower(3, 1, 5)
c = xgi.h_eigenvector_centrality(H, max_iter=1000)
assert (
max([abs(c[0] / c[i + 1] - _ratio(3, 5, kind="HEC")) for i in range(12)]) < 1e-4
)

H = xgi.sunflower(5, 1, 7)
print(H.num_nodes)
c = xgi.h_eigenvector_centrality(H, max_iter=1000)
assert (
max([abs(c[0] / c[i + 1] - _ratio(5, 7, kind="HEC")) for i in range(29)]) < 1e-4
)

H = xgi.Hypergraph([[1, 2], [2, 3, 4]])
c = xgi.h_eigenvector_centrality(H)
true_c = {
1: 0.24458437592396465,
2: 0.3014043407819482,
3: 0.22700561916516002,
4: 0.22700566412892714,
}
for i in c:
assert np.allclose(c[i], true_c[i])

H = xgi.load_xgi_data("email-enron")
H.cleanup(relabel=False)
c = xgi.h_eigenvector_centrality(H)
assert sorted(c) == sorted(H.nodes)


@pytest.mark.slow
def test_z_eigenvector_centrality():
# test empty hypergraph
H = xgi.Hypergraph()
c = xgi.z_eigenvector_centrality(H)
assert c == dict()

# Test no edges
H.add_nodes_from([0, 1, 2])
hec = xgi.z_eigenvector_centrality(H)
for i in hec:
assert np.isnan(hec[i])

# test disconnected
H.add_edge([0, 1])
hec = xgi.z_eigenvector_centrality(H)
assert set(hec) == {0, 1, 2}
for i in hec:
assert np.isnan(hec[i])

H = xgi.sunflower(3, 1, 5)
c = H.nodes.z_eigenvector_centrality(max_iter=1000).asdict()
assert (
max([abs(c[0] / c[i + 1] - _ratio(3, 5, kind="ZEC")) for i in range(12)]) < 1e-4
)

H = xgi.sunflower(5, 1, 7)
print(H.num_nodes)
c = xgi.z_eigenvector_centrality(H, max_iter=1000)
assert (
max([abs(c[0] / c[i + 1] - _ratio(5, 7, kind="ZEC")) for i in range(29)]) < 1e-4
)

H = xgi.Hypergraph([[1, 2], [2, 3, 4]])
c = xgi.z_eigenvector_centrality(H, max_iter=10000)
true_c = {
1: 0.45497398635982933,
2: 0.45900452108663403,
3: 0.04301074627676834,
4: 0.04301074627676829,
}
for i in c:
assert np.allclose(c[i], true_c[i])


def _ratio(r, m, kind="CEC"):
"""Generate the _ratio between largest and second largest centralities
for the sunflower hypergraph with one core node.
Parameters
----------
r : int
Number of petals
m : int
Size of edges
kind : str, default: "CEC"
"CEC" or "HEC"
Returns
-------
float
Ratio
References
----------
Three Hypergraph Eigenvector Centralities,
Austin R. Benson,
https://doi.org/10.1137/18M1203031
"""
if kind == "CEC":
return 2 * r * (m - 1) / (np.sqrt(m**2 + 4 * (m - 1) * (r - 1)) + m - 2)
elif kind == "HEC":
return r ** (1.0 / m)
elif kind == "ZEC":
return r**0.5
Loading

0 comments on commit 1bdba44

Please sign in to comment.