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

feat: added complete_hypergraph #337

Merged
merged 7 commits into from
Apr 17, 2023
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
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

nwlandry marked this conversation as resolved.
Show resolved Hide resolved
# 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).
maximelucas marked this conversation as resolved.
Show resolved Hide resolved
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