Skip to content

Commit

Permalink
feat: added complete_hypergraph (#337)
Browse files Browse the repository at this point in the history
* feat: added complete hypergraph

* tests: added corresponding

* docs: added new function. style: black isort

* review comments

* review comments

* fix docs maths
  • Loading branch information
maximelucas authored Apr 17, 2023
1 parent 198d054 commit 1191b60
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 1 deletion.
3 changes: 2 additions & 1 deletion docs/source/api/generators/xgi.generators.classic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@

.. autofunction:: empty_hypergraph
.. autofunction:: empty_simplicial_complex
.. autofunction:: trivial_hypergraph
.. autofunction:: trivial_hypergraph
.. autofunction:: complete_hypergraph
52 changes: 52 additions & 0 deletions tests/generators/test_classic.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from math import comb

import pytest

import xgi


Expand All @@ -20,3 +24,51 @@ def test_trivial_hypergraph():

H = xgi.trivial_hypergraph(n=2)
assert (H.num_nodes, H.num_edges) == (2, 0)


def test_complete_hypergraph():

N = 4

# single order
H1 = xgi.complete_hypergraph(N=N, order=1)
H2 = xgi.complete_hypergraph(N=N, order=2)
H3 = xgi.complete_hypergraph(N=N, order=3)
H03 = xgi.complete_hypergraph(N=N, order=3, include_singletons=True)

assert H3._edge == H03._edge

assert H1.edges.members() == [{0, 1}, {0, 2}, {0, 3}, {1, 2}, {1, 3}, {2, 3}]
assert H2.edges.members() == [{0, 1, 2}, {0, 1, 3}, {0, 2, 3}, {1, 2, 3}]
assert H3.edges.members() == [{0, 1, 2, 3}]

assert xgi.unique_edge_sizes(H1) == [2]
assert xgi.unique_edge_sizes(H2) == [3]
assert xgi.unique_edge_sizes(H3) == [4]

assert H1.num_edges == comb(N, 2)
assert H2.num_edges == comb(N, 3)
assert H3.num_edges == comb(N, 4)

# max_order
H1 = xgi.complete_hypergraph(N=N, max_order=1)
H2 = xgi.complete_hypergraph(N=N, max_order=2)
H3 = xgi.complete_hypergraph(N=N, max_order=3)
H03 = xgi.complete_hypergraph(N=N, max_order=3, include_singletons=True)

assert xgi.unique_edge_sizes(H1) == [2]
assert xgi.unique_edge_sizes(H2) == [2, 3]
assert xgi.unique_edge_sizes(H3) == [2, 3, 4]
assert xgi.unique_edge_sizes(H03) == [1, 2, 3, 4]

assert H1.num_edges == comb(N, 2)
assert H2.num_edges == comb(N, 2) + comb(N, 3)
assert H3.num_edges == comb(N, 2) + comb(N, 3) + comb(N, 4)
assert H03.num_edges == comb(N, 2) + comb(N, 3) + comb(N, 4) + N

# errors
with pytest.raises(ValueError):
H1 = xgi.complete_hypergraph(N=N, order=1, max_order=2)

with pytest.raises(ValueError):
H1 = xgi.complete_hypergraph(N=N, order=None, max_order=None)
70 changes: 70 additions & 0 deletions xgi/generators/classic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
"""

from itertools import chain, combinations

__all__ = [
"empty_hypergraph",
"empty_simplicial_complex",
"trivial_hypergraph",
"complete_hypergraph",
]


Expand Down Expand Up @@ -154,3 +157,70 @@ def trivial_hypergraph(n=1, create_using=None, default=None):
H.add_nodes_from(nodes)

return H


def complete_hypergraph(N, order=None, max_order=None, include_singletons=False):
"""
Generate a complete hypergraph, i.e. one that contains all possible hyperdges
at a given `order` or up to a `max_order`.
Parameters
----------
N : int
Number of nodes
order : int or None
If not None (default), specifies the single order for which to generate hyperedges
max_order : int or None
If not None (default), specifies the maximum order for which to generate hyperedges
include_singletons : bool
Whether to include singleton edges (default: False). This argument is discarded
if max_order is None.
Return
------
Hypergraph object
A complete hypergraph with `N` nodes
Notes
----
Only one of `order` and `max_order` can be specified by and int (not None).
Additionally, at least one of either must be specified.
The number of possible edges grows exponentially as :math:`2^N` for large `N` and
quickly becomes impractically long to compute, especially when using `max_order`. For
example, `N=100` and `max_order=5` already yields :math:`10^8` edges. Increasing `N=1000`
makes it :math:`10^{13}`. `N=100` and with a larger `max_order=6` yields :math:`10^9` edges.
"""
# this import needs to happen when the function runs, not when the module is first
# imported, to avoid circular imports
import xgi

if (order is None) and (max_order is None):
raise ValueError(
"At least one among order and max_order must be specified (not None)"
)
if (order is not None) and (max_order is not None):
raise ValueError(
"Both order and max_order cannot be specified (not None) at the same time."
)

H = xgi.Hypergraph()

nodes = range(N)
H.add_nodes_from(nodes)

if order is not None:
edges = combinations(nodes, order + 1)
elif max_order is not None:
start = 1 if include_singletons else 2
end = max_order + 1

s = list(nodes)
edges = chain.from_iterable(combinations(s, r) for r in range(start, end + 1))

H.add_edges_from(edges)

return H

0 comments on commit 1191b60

Please sign in to comment.