Skip to content

Commit

Permalink
Add simpliciality measures
Browse files Browse the repository at this point in the history
  • Loading branch information
nwlandry committed Sep 8, 2024
1 parent 5ff749e commit b875fa6
Show file tree
Hide file tree
Showing 13 changed files with 841 additions and 9 deletions.
3 changes: 2 additions & 1 deletion docs/source/api/algorithms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ algorithms package
~xgi.algorithms.centrality
~xgi.algorithms.clustering
~xgi.algorithms.connected
~xgi.algorithms.properties
~xgi.algorithms.shortest_path
~xgi.algorithms.properties
~xgi.algorithms.simpliciality
14 changes: 14 additions & 0 deletions docs/source/api/algorithms/xgi.algorithms.simpliciality.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
xgi.algorithms.simpliciality
============================

.. currentmodule:: xgi.algorithms.simpliciality

.. automodule:: xgi.algorithms.simpliciality

.. rubric:: Functions

.. autofunction:: edit_simpliciality
.. autofunction:: simplicial_edit_distance
.. autofunction:: face_edit_simpliciality
.. autofunction:: mean_face_edit_distance
.. autofunction:: simplicial_fraction
5 changes: 4 additions & 1 deletion docs/source/api/stats/xgi.stats.nodestats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@
.. autofunction:: h_eigenvector_centrality
.. autofunction:: local_clustering_coefficient
.. autofunction:: node_edge_centrality
.. autofunction:: two_node_clustering_coefficient
.. autofunction:: two_node_clustering_coefficient
.. autofunction:: local_simplicial_fraction
.. autofunction:: local_edit_simpliciality
.. autofunction:: local_face_edit_simpliciality
255 changes: 255 additions & 0 deletions tests/algorithms/test_simpliciality.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
import xgi
import numpy as np


def test_edit_simpliciality(
sc1_with_singletons,
h_missing_one_singleton,
h_missing_one_link,
h_links_and_triangles2,
h1,
):
# simplicial complex
es = xgi.edit_simpliciality(sc1_with_singletons)
assert es == 1.0

es = xgi.edit_simpliciality(sc1_with_singletons, min_size=1)
assert es == 1.0

es = xgi.edit_simpliciality(sc1_with_singletons, min_size=1, exclude_min_size=False)
assert es == 1.0

# h1
es = xgi.edit_simpliciality(h_missing_one_singleton)
assert es == 1.0

es = xgi.edit_simpliciality(h_missing_one_singleton, min_size=1)
assert np.allclose(es, 6 / 7)

es = xgi.edit_simpliciality(
h_missing_one_singleton, min_size=1, exclude_min_size=False
)
assert np.allclose(es, 6 / 7)

# h2
es = xgi.edit_simpliciality(h_missing_one_link)
assert np.allclose(es, 3 / 4)

es = xgi.edit_simpliciality(h_missing_one_link, min_size=1)
assert np.allclose(es, 6 / 7)

# links and triangles 2
es = xgi.edit_simpliciality(h_links_and_triangles2)
assert np.allclose(es, 3 / 4)

es = xgi.edit_simpliciality(h_links_and_triangles2, min_size=1)
assert np.allclose(es, 1 / 2)

es = xgi.edit_simpliciality(h_links_and_triangles2, exclude_min_size=False)
assert np.allclose(es, 3 / 4)

# test h1
es = xgi.edit_simpliciality(h1)
s = 4
m = 4 + 10
assert np.allclose(es, s / (m + s))

es = xgi.edit_simpliciality(h1, min_size=1)
s = 4
m = 4 + 10 + 7
assert np.allclose(es, s / (m + s))

es = xgi.edit_simpliciality(h1, exclude_min_size=False)
s = 4
m = 4 + 10
assert np.allclose(es, s / (m + s))


def test_face_edit_simpliciality(
sc1_with_singletons,
h_missing_one_singleton,
h_missing_one_link,
h_links_and_triangles2,
):
# simplicial complex
fes = xgi.face_edit_simpliciality(sc1_with_singletons)
assert fes == 1.0

fes = xgi.face_edit_simpliciality(sc1_with_singletons, min_size=1)
assert fes == 1.0

fes = xgi.face_edit_simpliciality(
sc1_with_singletons, min_size=1, exclude_min_size=False
)
assert fes == 1.0

# h1
fes = xgi.face_edit_simpliciality(h_missing_one_singleton)
assert fes == 1.0

fes = xgi.face_edit_simpliciality(h_missing_one_singleton, min_size=1)
assert np.allclose(fes, 5 / 6)

fes = xgi.face_edit_simpliciality(
h_missing_one_singleton, min_size=1, exclude_min_size=False
)
assert np.allclose(fes, 5 / 6)

# h2
fes = xgi.face_edit_simpliciality(h_missing_one_link)
assert np.allclose(fes, 2 / 3)

fes = xgi.face_edit_simpliciality(h_missing_one_link, min_size=1)
assert np.allclose(fes, 5 / 6)

# links and triangles 2
fes = xgi.face_edit_simpliciality(h_links_and_triangles2)
assert np.allclose(fes, 2 / 3)

fes = xgi.face_edit_simpliciality(h_links_and_triangles2, min_size=1)
assert np.allclose(fes, 2 / 9)

fes = xgi.face_edit_simpliciality(h_links_and_triangles2, exclude_min_size=False)
assert np.allclose(fes, 7 / 9)


def test_simplicial_fraction(
sc1_with_singletons, h_missing_one_singleton, h_missing_one_link
):
# simplicial complex
sf = xgi.simplicial_fraction(sc1_with_singletons)
assert sf == 1.0

sf = xgi.simplicial_fraction(sc1_with_singletons, min_size=1)
assert sf == 1.0

sf = xgi.simplicial_fraction(
sc1_with_singletons, min_size=1, exclude_min_size=False
)
assert sf == 1.0

# h1
sf = xgi.simplicial_fraction(h_missing_one_singleton)
assert sf == 1.0

sf = xgi.simplicial_fraction(h_missing_one_singleton, min_size=1)
assert sf == 1 / 4

sf = xgi.simplicial_fraction(
h_missing_one_singleton, min_size=1, exclude_min_size=False
)
assert sf == 0.5

# h2
sf = xgi.simplicial_fraction(h_missing_one_link)
assert sf == 0

sf = xgi.simplicial_fraction(h_missing_one_link, min_size=1)
assert sf == 2 / 3


def test_is_simplex(sc1_with_singletons, h_missing_one_singleton):
t = xgi.Trie()
edges = sc1_with_singletons.edges.members()
t.build_trie(edges)

is_simplex = xgi.algorithms.simpliciality._is_simplex

assert is_simplex(t, {1, 2, 3}, min_size=2)
assert is_simplex(t, {1, 2, 3}, min_size=1)
assert is_simplex(t, {1, 2}, min_size=1)

t = xgi.Trie()
edges = h_missing_one_singleton.edges.members()
t.build_trie(edges)

assert is_simplex(t, {1, 2, 3})
assert not is_simplex(t, {1, 2, 3}, min_size=1)
assert not is_simplex(t, {2, 3}, min_size=1)
assert is_simplex(t, {1, 2}, min_size=1)


def test_count_simplices(sc1_with_singletons, h_missing_one_singleton):
count_simplices = xgi.algorithms.simpliciality._count_simplices

ns = count_simplices(sc1_with_singletons)
assert ns == 1

ns = count_simplices(sc1_with_singletons, min_size=1)
assert ns == 4

ns = count_simplices(sc1_with_singletons, min_size=1, exclude_min_size=False)
assert ns == 7

ns = count_simplices(h_missing_one_singleton, min_size=1)
assert ns == 1


def test_potential_simplices(sc1_with_singletons, h_missing_one_link):
potential_simplices = xgi.algorithms.simpliciality._potential_simplices

ps = potential_simplices(sc1_with_singletons)
assert ps == 1

ps = potential_simplices(sc1_with_singletons, min_size=1)
assert ps == 4

ps = potential_simplices(sc1_with_singletons, min_size=1, exclude_min_size=False)
assert ps == 7

ps = potential_simplices(h_missing_one_link, min_size=1)
assert ps == 3


def test_powerset():
powerset = xgi.algorithms.simpliciality._powerset
a = {1, 2, 3}

# test default behavior
subsets = {frozenset(s) for s in powerset(a, min_size=1)}
assert subsets == {
frozenset({1}),
frozenset({2}),
frozenset({3}),
frozenset({1, 2}),
frozenset({1, 3}),
frozenset({2, 3}),
frozenset({1, 2, 3}),
}

subsets = {frozenset(s) for s in powerset(a, min_size=2)}
assert subsets == {
frozenset({1, 2}),
frozenset({1, 3}),
frozenset({2, 3}),
frozenset({1, 2, 3}),
}

subsets = {frozenset(s) for s in powerset(a, max_size=2)}
assert subsets == {
frozenset({1}),
frozenset({2}),
frozenset({3}),
frozenset({1, 2}),
frozenset({1, 3}),
frozenset({2, 3}),
}


def test_count_subfaces(h_missing_one_link):
count_subfaces = xgi.algorithms.simpliciality._count_subfaces
t = xgi.Trie()
t.build_trie(h_missing_one_link.edges.members())
assert count_subfaces(t, {1}, min_size=2) == 0
assert count_subfaces(t, {2, 3}, min_size=2) == 0
assert count_subfaces(t, {2, 3}) == 2
assert count_subfaces(t, {1, 2, 3}) == 5
assert count_subfaces(t, {1, 2, 3}, min_size=2) == 2


def test_max_number_of_subfaces():
max_number_of_subfaces = xgi.algorithms.simpliciality._max_number_of_subfaces
assert max_number_of_subfaces(1, 3) == 6
assert max_number_of_subfaces(2, 3) == 3
assert max_number_of_subfaces(1, 4) == 14
assert max_number_of_subfaces(2, 4) == 10
33 changes: 33 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,36 @@ def dihyperwithattrs(diedgelist2, attr0, attr1, attr2, attr3, attr4, attr5):
H.add_edges_from(diedgelist2)
H.set_edge_attributes({0: attr3, 1: attr4, 2: attr5})
return H


### Fixtures for simpliciality


@pytest.fixture
def sc1_with_singletons():
return xgi.Hypergraph([{1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}, {1, 2, 3}])


@pytest.fixture
def h_missing_one_singleton():
return xgi.Hypergraph([{1}, {2}, {1, 2}, {1, 3}, {2, 3}, {1, 2, 3}])


@pytest.fixture
def h_missing_one_link():
return xgi.Hypergraph([{1}, {2}, {3}, {1, 3}, {2, 3}, {1, 2, 3}])


@pytest.fixture
def h_links_and_triangles():
return xgi.Hypergraph([{1, 3}, {2, 3}, {1, 2, 3}])


@pytest.fixture
def h_links_and_triangles2():
return xgi.Hypergraph([{1, 3}, {2, 3}, {1, 2, 3}, {1, 4}, {2, 3, 4}, {2, 4}])


@pytest.fixture
def h1():
return xgi.Hypergraph([{1, 2, 3}, {2, 3, 4, 5}, {5, 6, 7}, {5, 6}])
11 changes: 10 additions & 1 deletion xgi/algorithms/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
from . import assortativity, centrality, clustering, connected, properties
from . import (
assortativity,
centrality,
clustering,
connected,
properties,
shortest_path,
simpliciality,
)
from .assortativity import *
from .centrality import *
from .clustering import *
from .connected import *
from .properties import *
from .shortest_path import *
from .simpliciality import *
Loading

0 comments on commit b875fa6

Please sign in to comment.