Skip to content

Commit

Permalink
Cleanup hypergraph methods (#146)
Browse files Browse the repository at this point in the history
* move isolates() from Hypergraph to NodeView and singletons() from Hypergraph to EdgeView. 
* delete Hypergraph.remove_isolates as it is now equivalent to H.remove_nodes_from(H.nodes.isolates()) and Hypergraph.remove_singletons as it is now equivalent to H.remove_edges_from(H.edges.singletons())
  • Loading branch information
leotrs authored Aug 4, 2022
1 parent 9a6f20a commit 44b646e
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 103 deletions.
4 changes: 0 additions & 4 deletions docs/source/api/classes/xgi.classes.hypergraph.Hypergraph.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@
~Hypergraph.remove_nodes_from
~Hypergraph.remove_edges_from
~Hypergraph.remove_node_from_edge
~Hypergraph.remove_isolates
~Hypergraph.remove_singleton_edges
~Hypergraph.clear
~Hypergraph.clear_edges

Expand All @@ -56,7 +54,5 @@
.. autosummary::
:nosignatures:

~Hypergraph.isolates
~Hypergraph.duplicate_edges
~Hypergraph.has_edge
~Hypergraph.singleton_edges
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ xgi.classes.reportviews.EdgeView
:nosignatures:

~EdgeView.members
~EdgeView.singletons
~IDView.neighbors
~IDView.filterby
~IDView.filterby_attr
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ xgi.classes.reportviews.NodeView
:nosignatures:

~NodeView.memberships
~NodeView.isolates
~IDView.neighbors
~IDView.filterby
~IDView.filterby_attr
8 changes: 4 additions & 4 deletions docs/source/api/readwrite/xgi.readwrite.xgi_data.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
xgi.readwrite.xgi_data
==================
======================

.. currentmodule:: xgi.readwrite.xgi_data

.. automodule:: xgi.readwrite.xgi_data

.. rubric:: Functions
.. autofunction:: load_xgi_data

.. autofunction:: load_xgi_data
25 changes: 4 additions & 21 deletions tests/classes/test_hypergraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,32 +100,15 @@ def test_add_nodes_from(attr1, attr2, attr3):
assert H.nodes[2]["color"] == attr3["color"]


def test_singleton_edges(edgelist1, edgelist2):
H1 = xgi.Hypergraph(edgelist1)
H2 = xgi.Hypergraph(edgelist2)

assert len(H1.singleton_edges()) == 1
assert 1 in H1.singleton_edges()
assert len(H2.singleton_edges()) == 0


def test_remove_singleton_edges(edgelist1, edgelist2):
H1 = xgi.Hypergraph(edgelist1)
H2 = xgi.Hypergraph(edgelist2)

H1.remove_singleton_edges()
H2.remove_singleton_edges()

assert H1.singleton_edges() == {}
assert H2.singleton_edges() == {}
H1.remove_edges_from(H1.edges.singletons())
H2.remove_edges_from(H2.edges.singletons())


def test_isolates(edgelist1):
H = xgi.Hypergraph(edgelist1)
assert H.isolates(ignore_singletons=False) == set()
assert H.isolates() == {4}
H.remove_isolates()
assert 4 not in H
assert list(H1.edges.singletons()) == []
assert list(H2.edges.singletons()) == []


def test_add_node_attr(edgelist1):
Expand Down
6 changes: 3 additions & 3 deletions tests/classes/test_hypergraphviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ def test_subhypergraph(edgelist1):
new_H = xgi.classes.hypergraphviews.subhypergraph(H, edges=[1, 2])
assert list(new_H.nodes) == [1, 2, 3, 4, 5, 6, 7, 8]
assert list(new_H.edges) == [1, 2]
assert new_H.isolates(ignore_singletons=False) == {1, 2, 3, 7, 8}
assert list(new_H.nodes.isolates(ignore_singletons=False)) == [1, 2, 3, 7, 8]

new_H = xgi.classes.hypergraphviews.subhypergraph(
H, nodes=[1, 2, 3, 4, 5], edges=[1, 2]
)
assert list(new_H.nodes) == [1, 2, 3, 4, 5]
assert list(new_H.edges) == [1]
assert new_H.isolates(ignore_singletons=False) == {1, 2, 3, 5}
assert list(new_H.nodes.isolates(ignore_singletons=False)) == [1, 2, 3, 5]

new_H = xgi.classes.hypergraphviews.subhypergraph(H, nodes=[4], edges=[0, 1, 2])
assert list(new_H.nodes) == [4]
Expand All @@ -68,4 +68,4 @@ def test_subhypergraph(edgelist1):
new_H = xgi.classes.hypergraphviews.subhypergraph(H, nodes=[3, 4, 5, 6], edges=[2])
assert list(new_H.nodes) == [3, 4, 5, 6]
assert list(new_H.edges) == [2]
assert new_H.isolates(ignore_singletons=False) == {3, 4}
assert list(new_H.nodes.isolates(ignore_singletons=False)) == [3, 4]
16 changes: 16 additions & 0 deletions tests/classes/test_reportviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,19 @@ def test_call(edgelist1):
assert len(H.nodes([])) == 0
assert H.nodes(list(H.nodes)) == H.nodes
assert H.nodes(H.nodes) == H.nodes


def test_isolates(edgelist1):
H = xgi.Hypergraph(edgelist1)
assert list(H.nodes.isolates(ignore_singletons=False)) == []
assert list(H.nodes.isolates()) == [4]
H.remove_nodes_from(H.nodes.isolates())
assert 4 not in H


def test_singletons(edgelist1):
H = xgi.Hypergraph(edgelist1)
assert list(H.edges.singletons()) == [1]
H.remove_edge(1)
assert 1 not in H.edges
assert list(H.edges.singletons()) == []
71 changes: 0 additions & 71 deletions xgi/classes/hypergraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -978,73 +978,6 @@ def dual(self):

return dual

def singleton_edges(self):
"""Edges with a single member.
Returns
-------
EdgeView
View of the edges with a single member.
"""
return self.edges.filterby("order", 0)

def remove_singleton_edges(self):
"""Removes all singletons edges from the hypergraph"""
singleton_ids = [
id_ for id_, members in self._edge.items() if len(members) == 1
]
self.remove_edges_from(singleton_ids)

def isolates(self, ignore_singletons=True):
"""Nodes that belong to no edges.
When ignore_singletons is True (default), a node is considered isolated frmo the
rest of the hypergraph when it is included in no edges of size two or more. In
particular, whether the node is part of any singleton edges is irrelevant to
determine whether it is isolated.
When ignore_singletons is False, a node is isolated only when it is a member of
exactly zero edges, including singletons.
Parameters
----------
ignore_singletons : bool, default False
Whether to consider singleton edges.
Returns
-------
set
Isolated nodes.
See Also
--------
remove_isolates
"""
nodes_in_edges = set()
for idx in self.edges:
edge = self.edges.members(idx)
if ignore_singletons and len(edge) == 1:
continue
nodes_in_edges = nodes_in_edges.union(edge)
return set(self.nodes) - nodes_in_edges

def remove_isolates(self, ignore_singletons=True):
"""Remove all nodes that belong to no edges.
Parameters
----------
ignore_singletons : bool, default False
Whether to consider singleton edges when searching for isolated nodes.
See Also
--------
isolates
"""
self.remove_nodes_from(self.isolates(ignore_singletons))

def duplicate_edges(self):
"""A list of all duplicate edges.
Expand All @@ -1053,10 +986,6 @@ def duplicate_edges(self):
list
All edges with a duplicate.
See also
--------
remove_duplicates
"""
edges = [tuple(e) for e in self._edge.values()]
edges_unique, counts = np.unique(edges, return_counts=True)
Expand Down
50 changes: 50 additions & 0 deletions xgi/classes/reportviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,42 @@ def memberships(self, n=None):
"""
return self._id_dict.copy() if n is None else self._id_dict[n].copy()

def isolates(self, ignore_singletons=True):
"""Nodes that belong to no edges.
When ignore_singletons is True (default), a node is considered isolated from the
rest of the hypergraph when it is included in no edges of size two or more. In
particular, whether the node is part of any singleton edges is irrelevant to
determine whether it is isolated.
When ignore_singletons is False, a node is isolated only when it is a member of
exactly zero edges, including singletons.
Parameters
----------
ignore_singletons : bool, default False
Whether to consider singleton edges.
Returns
-------
NodeView containing the isolated nodes.
See Also
--------
:meth:`EdgeView.singletons`
"""
if ignore_singletons:
nodes_in_edges = set()
for members in self._bi_id_dict.values():
if len(members) == 1:
continue
nodes_in_edges = nodes_in_edges.union(members)
isolates = set(self._id_dict) - nodes_in_edges
return self.from_view(self, bunch=isolates)
else:
return self.filterby("degree", 0)


class EdgeView(IDView):
"""An IDView that keeps track of edge ids.
Expand Down Expand Up @@ -513,3 +549,17 @@ def members(self, e=None, dtype=list):
raise IDNotFound(f'ID "{e}" not in this view')

return self._id_dict[e].copy()

def singletons(self):
"""Edges that contain exactly one node.
Returns
-------
EdgeView containing the singleton edges.
See Also
--------
:meth:`NodeView.isolates`
"""
return self.filterby("size", 1)

0 comments on commit 44b646e

Please sign in to comment.