Source code for xgi.core.views

"""View classes for hypergraphs.

A View class allows for inspection and querying of an underlying object but does not
allow modification.  This module provides View classes for nodes and edges of a hypergraph.
Views are automatically updaed when the hypergraph changes.

"""

from collections import defaultdict
from collections.abc import Mapping, Set
from functools import reduce

from ..exception import IDNotFound, XGIError
from ..stats import IDStat, dispatch_many_stats, dispatch_stat

__all__ = [
    "NodeView",
    "EdgeView",
    "DiNodeView",
    "DiEdgeView",
]


[docs]class IDView(Mapping, Set): """Base View class for accessing the ids (nodes or edges) of a Hypergraph. Can optionally keep track of a subset of ids. By default all node ids or all edge ids are kept track of. Parameters ---------- network : Hypergraph or Simplicial Complex The underlying network ids : iterable A subset of the keys in id_dict to keep track of. Raises ------ XGIError If ids is not a subset of the keys of id_dict. """ _id_kind = None __slots__ = ( "_net", "_ids", ) def __getstate__(self): """Function that allows pickling. Returns ------- dict The keys access the IDs and their attributes respectively and the values are dictionarys from the Hypergraph class. """ return { "_net": self._net, "_ids": self._ids, } def __setstate__(self, state): """Function that allows unpickling. Parameters ---------- dict The keys access the IDs and their attributes respectively and the values are dictionarys from the Hypergraph class. """ self._net = state["_net"] self._id_kind = state["_id_kind"] def __init__(self, network, ids=None): self._net = network if self._id_kind in {"node", "dinode"}: self._id_dict = None if self._net is None else network._node self._id_attr = None if self._net is None else network._node_attr self._bi_id_dict = None if self._net is None else network._edge self._bi_id_attr = None if self._net is None else network._edge_attr elif self._id_kind in {"edge", "diedge"}: self._id_dict = None if self._net is None else network._edge self._id_attr = None if self._net is None else network._edge_attr self._bi_id_dict = None if self._net is None else network._node self._bi_id_attr = None if self._net is None else network._node_attr if ids is None: self._ids = self._id_dict else: self._ids = ids def __getattr__(self, attr): stat = dispatch_stat(self._id_kind, self._net, self, attr) self.__dict__[attr] = stat return stat def multi(self, names): return dispatch_many_stats(self._id_kind, self._net, self, names) @property def ids(self): """The ids in this view. Notes ----- Do not use this property for membership check. Instead of `x in view.ids`, always use `x in view`. The latter is always faster. """ return set(self._ids) def __len__(self): """The number of IDs.""" return len(self._ids) def __iter__(self): """Returns an iterator over the IDs.""" return iter(self._ids) def __getitem__(self, idx): """Get the attributes of the ID. Parameters ---------- idx : hashable node or edge ID Returns ------- dict attributes associated to the ID. Raises ------ XGIError If the id is not being kept track of by this view, or if id is not in the hypergraph, or if id is not hashable. """ if idx not in self: raise IDNotFound(f"The ID {idx} is not in this view") return self._id_attr[idx] def __contains__(self, idx): """Checks whether the ID is in the hypergraph""" return idx in self._ids def __str__(self): """Returns a string of the list of IDs.""" return str(list(self)) def __repr__(self): """Returns a summary of the class""" return f"{self.__class__.__name__}({tuple(self)})" def __call__(self, bunch): """Filter to the given bunch. Parameters ---------- bunch : Iterable Iterable of IDs Returns ------- IDView A new view that keeps track only of the IDs in the bunch. """ return self.from_view(self, bunch)
[docs] def filterby(self, stat, val, mode="eq"): """Filter the IDs in this view by a statistic. Parameters ---------- stat : str or :class:`xgi.stats.NodeStat`/:class:`xgi.stats.EdgeStat` `NodeStat`/`EdgeStat` object, or name of a `NodeStat`/`EdgeStat`. val : Any Value of the statistic. Usually a single numeric value. When mode is 'between', must be a tuple of exactly two values. mode : str or function, optional How to compare each value to `val`. Can be one of the following. * 'eq' (default): Return IDs whose value is exactly equal to `val`. * 'neq': Return IDs whose value is not equal to `val`. * 'lt': Return IDs whose value is less than `val`. * 'gt': Return IDs whose value is greater than `val`. * 'leq': Return IDs whose value is less than or equal to `val`. * 'geq': Return IDs whose value is greater than or equal to `val`. * 'between': In this mode, `val` must be a tuple `(val1, val2)`. Return IDs whose value `v` satisfies `val1 <= v <= val2`. * function, must be able to call `mode(statistic, val)` and have it map to a bool. See Also -------- IDView.filterby_attr : For more details, see the `tutorial <https://xgi.readthedocs.io/en/stable/api/tutorials/focus_6.html>`_. Examples -------- By default, return the IDs whose value of the statistic is exactly equal to `val`. >>> import xgi >>> H = xgi.Hypergraph([[1, 2, 3], [2, 3, 4, 5], [3, 4, 5]]) >>> n = H.nodes >>> n.filterby('degree', 2) NodeView((2, 4, 5)) Can choose other comparison methods via `mode`. >>> n.filterby('degree', 2, 'eq') NodeView((2, 4, 5)) >>> n.filterby('degree', 2, 'neq') NodeView((1, 3)) >>> n.filterby('degree', 2, 'lt') NodeView((1,)) >>> n.filterby('degree', 2, 'gt') NodeView((3,)) >>> n.filterby('degree', 2, 'leq') NodeView((1, 2, 4, 5)) >>> n.filterby('degree', 2, 'geq') NodeView((2, 3, 4, 5)) >>> n.filterby('degree', (2, 3), 'between') NodeView((2, 3, 4, 5)) Can also pass a :class:`NodeStat` object. >>> n.filterby(n.degree(order=2), 2) NodeView((3,)) """ if not isinstance(stat, IDStat): try: stat = getattr(self, stat) except AttributeError as e: raise AttributeError(f'Statistic with name "{stat}" not found') from e values = stat.asdict() if mode == "eq": bunch = [idx for idx in self if values[idx] == val] elif mode == "neq": bunch = [idx for idx in self if values[idx] != val] elif mode == "lt": bunch = [idx for idx in self if values[idx] < val] elif mode == "gt": bunch = [idx for idx in self if values[idx] > val] elif mode == "leq": bunch = [idx for idx in self if values[idx] <= val] elif mode == "geq": bunch = [idx for idx in self if values[idx] >= val] elif mode == "between": bunch = [node for node in self if val[0] <= values[node] <= val[1]] elif callable(mode): bunch = [idx for idx in self if mode(values[idx], val)] else: raise ValueError( f"Unrecognized mode {mode}. mode must be one of " "'eq', 'neq', 'lt', 'gt', 'leq', 'geq', or 'between'." ) return type(self).from_view(self, bunch)
[docs] def filterby_attr(self, attr, val, mode="eq", missing=None): """Filter the IDs in this view by an attribute. Parameters ---------- attr : string The name of the attribute val : Any A single value or, in the case of 'between', a list of length 2 mode : str or function, optional Comparison mode. Valid options are 'eq' (default), 'neq', 'lt', 'gt', 'leq', 'geq', or 'between'. If a function, must be able to call `mode(attribute, val)` and have it map to a bool. missing : Any, optional The default value if the attribute is missing. If None (default), ignores those IDs. See Also -------- IDView.filterby : Identical method. For more details, see the `tutorial <https://xgi.readthedocs.io/en/stable/api/tutorials/focus_6.html>`_. Notes ----- Beware of using comparison modes ("lt", "gt", "leq", "geq") when the attribute is a string. For example, the string comparison `'10' < '9'` evaluates to `True`. """ attrs = dispatch_stat(self._id_kind, self._net, self, "attrs") values = attrs(attr, missing).asdict() if mode == "eq": bunch = [ idx for idx in self if values[idx] is not None and values[idx] == val ] elif mode == "neq": bunch = [ idx for idx in self if values[idx] is not None and values[idx] != val ] elif mode == "lt": bunch = [ idx for idx in self if values[idx] is not None and values[idx] < val ] elif mode == "gt": bunch = [ idx for idx in self if values[idx] is not None and values[idx] > val ] elif mode == "leq": bunch = [ idx for idx in self if values[idx] is not None and values[idx] <= val ] elif mode == "geq": bunch = [ idx for idx in self if values[idx] is not None and values[idx] >= val ] elif mode == "between": bunch = [ idx for idx in self if values[idx] is not None and val[0] <= values[idx] <= val[1] ] elif callable(mode): bunch = [ idx for idx in self if values[idx] is not None and mode(values[idx], val) ] else: raise ValueError( f"Unrecognized mode {mode}. mode must be one of " "'eq', 'neq', 'lt', 'gt', 'leq', 'geq', or 'between'." ) return type(self).from_view(self, bunch)
[docs] def neighbors(self, idx, s=1): """Find the neighbors of an ID. The neighbors of an ID are those IDs that share at least one bipartite ID. Parameters ---------- idx : hashable ID to find neighbors of. s : int, optional The intersection size s for two edges or nodes to be considered neighbors. By default, 1. Returns ------- set A set of the neighboring IDs See Also -------- ~xgi.algorithms.properties.edge_neighborhood Examples -------- >>> import xgi >>> hyperedge_list = [[1, 2], [2, 3, 4]] >>> H = xgi.Hypergraph(hyperedge_list) >>> H.nodes.neighbors(1) {2} >>> H.nodes.neighbors(2) {1, 3, 4} """ if s == 1: return { i for n in self._id_dict[idx] for i in self._bi_id_dict[n] }.difference({idx}) else: return { i for n in self._id_dict[idx] for i in self._bi_id_dict[n] if len(self._id_dict[idx].intersection(self._id_dict[i])) >= s }.difference({idx})
[docs] def duplicates(self): """Find IDs that have a duplicate. An ID has a 'duplicate' if there exists another ID with the same bipartite neighbors. Returns ------- IDView A view containing only those IDs with a duplicate. Raises ------ TypeError When IDs are of different types. For example, ("a", 1). Notes ----- The IDs returned are in an arbitrary order, that is duplicates are not guaranteed to be consecutive. For IDs with the same bipartite neighbors, only the first ID added is not a duplicate. See Also -------- IDView.lookup Examples -------- >>> import xgi >>> H = xgi.Hypergraph([[0, 1, 2], [3, 4, 2], [0, 1, 2]]) >>> H.edges.duplicates() EdgeView((2,)) Order does not matter: >>> H = xgi.Hypergraph([[2, 1, 0], [0, 1, 2]]) >>> H.edges.duplicates() EdgeView((1,)) Repetitions matter: >>> H = xgi.Hypergraph([[0, 1], [1, 0]]) >>> H.edges.duplicates() EdgeView((1,)) """ dups = [] hashes = defaultdict(list) for idx, members in self._id_dict.items(): hashes[frozenset(members)].append(idx) for _, edges in hashes.items(): if len(edges) > 1: try: dups.extend(sorted(edges)[1:]) except TypeError: dups.extend(edges[1:]) return self.__class__.from_view(self, bunch=dups)
[docs] def lookup(self, neighbors): """Find IDs with the specified bipartite neighbors. Parameters ---------- neighbors : Iterable An iterable of IDs. Returns ------- IDView A view containing only those IDs whose bipartite neighbors match `neighbors`. See Also -------- IDView.duplicates Examples -------- >>> import xgi >>> H = xgi.Hypergraph([[0, 1, 2], [3, 4], [3, 4, 2]]) >>> H.edges.lookup([3, 4]) EdgeView((1,)) >>> H.add_edge([3, 4]) >>> H.edges.lookup([3, 4]) EdgeView((1, 3)) Can be used as a boolean check for edge existence: >>> if H.edges.lookup([3, 4]): print('An edge with members [3, 4] exists') An edge with members [3, 4] exists Can also be used to check for nodes that belong to a particular set of edges: >>> H = xgi.Hypergraph([['a', 'b', 'c'], ['a', 'd', 'e'], ['c', 'd', 'e']]) >>> H.nodes.lookup([0, 1]) NodeView(('a',)) """ sought = set(neighbors) found = [idx for idx, neighbors in self._id_dict.items() if neighbors == sought] return self.__class__.from_view(self, bunch=found)
[docs] @classmethod def from_view(cls, view, bunch=None): """Create a view from another view. Allows to create a view with the same underlying data but with a different bunch. Parameters ---------- view : IDView The view used to initialize the new object bunch : iterable IDs the new view will keep track of Returns ------- IDView A view that is identical to `view` but keeps track of different IDs. """ newview = cls(None) newview._net = view._net newview._id_dict = view._id_dict newview._id_attr = view._id_attr newview._bi_id_dict = view._bi_id_dict newview._bi_id_attr = view._bi_id_attr all_ids = set(view._id_dict) if bunch is None: newview._ids = all_ids else: bunch = set(bunch) wrong = bunch - all_ids if wrong: raise IDNotFound(f"IDs {wrong} not in the hypergraph") newview._ids = [i for i in view._id_dict if i in bunch] return newview
def _from_iterable(self, it): """Construct an instance of the class from any iterable input. This overrides collections.abc.Set._from_iterable, which is in turn used to implement set operations such as &, |, ^, -. """ return self.from_view(self, it)
[docs]class NodeView(IDView): """An IDView that keeps track of node ids. Parameters ---------- hypergraph : Hypergraph The hypergraph whose nodes this view will keep track of. bunch : optional iterable, default None The node ids to keep track of. If None (default), keep track of all node ids. See Also -------- IDView Notes ----- In addition to the methods listed in this page, other methods defined in the `stats` package are also accessible via the `NodeView` class. For more details, see the `tutorial <https://xgi.readthedocs.io/en/stable/api/tutorials/focus_6.html>`_. """ _id_kind = "node" def __init__(self, H, bunch=None): if H is None: super().__init__(None, bunch) else: super().__init__(H, bunch)
[docs] def memberships(self, n=None): """Get the edge ids of which a node is a member. Gets all the node memberships for all nodes in the view if n not specified. Parameters ---------- n : hashable, optional Node ID. By default, None. Returns ------- dict of sets if n is None, otherwise a set Edge memberships. Raises ------ XGIError If `n` is not hashable or if it is not in the hypergraph. """ return ( {key: self._id_dict[key].copy() for key in self} if n is None else self._id_dict[n].copy() )
[docs] def isolates(self, ignore_singletons=False): """Nodes that belong to no edges. When ignore_singletons is True, 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 (default), a node is isolated only when it is a member of exactly zero edges, including singletons. Parameters ---------- ignore_singletons : bool, optional Whether to consider singleton edges. By default, False. 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)
[docs]class EdgeView(IDView): """An IDView that keeps track of edge ids. Parameters ---------- hypergraph : Hypergraph The hypergraph whose edges this view will keep track of. bunch : optional iterable, default None The edge ids to keep track of. If None (default), keep track of all edge ids. See Also -------- IDView Notes ----- In addition to the methods listed in this page, other methods defined in the `stats` package are also accessible via the `EdgeView` class. For more details, see the `tutorial <https://xgi.readthedocs.io/en/stable/api/tutorials/focus_6.html>`_. """ _id_kind = "edge" def __init__(self, H, bunch=None): if H is None: super().__init__(None, bunch) else: super().__init__(H, bunch)
[docs] def members(self, e=None, dtype=list): """Get the node ids that are members of an edge. Parameters ---------- e : hashable, optional Edge ID. By default, None. dtype : {list, dict}, optional Specify the type of the return value. By default, list. Returns ------- list (if dtype is list, default) Edge members. dict (if dtype is dict) Edge members. set (if e is not None) Members of edge e. Raises ------ TypeError If `e` is not None or a hashable XGIError If `dtype` is not dict or list IDNotFound If `e` does not exist in the hypergraph """ if e is None: if dtype is dict: return {key: self._id_dict[key].copy() for key in self} elif dtype is list: return [self._id_dict[key].copy() for key in self] else: raise XGIError(f"Unrecognized dtype {dtype}") if e not in self: raise IDNotFound(f'ID "{e}" not in this view') return self._id_dict[e].copy()
[docs] 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)
[docs] def empty(self): """Edges that contain no nodes. Returns ------- EdgeView containing the empty edges. See Also -------- :meth:`NodeView.isolates` """ return self.filterby("size", 0)
[docs] def maximal(self, strict=False): """Returns the maximal edges as an EdgeView. Maximal edges are those that are not subsets of any other edges in the hypergraph. The strict keyword determines whether the subsets are strict or non-strict. Parameters ---------- strict : bool, optional Whether maximal edges must strictly include all of its subsets (`strict=True`) or whether maximal multiedges are permitted (`strict=False`), by default False. See Notes for more details. Returns ------- EdgeView The maximal edges Notes ----- This function implements two definitions of maximal hyperedges: strict and non-strict. For the strict case (`strict=True`), we enforce that a maximal edge must strictly include all of its subsets and by this definition, multiedges can't be included. For the non-strict case (`strict=False`), then we add all the maximal multiedges with non-strict inclusion. There are methods for eliminating these duplicates by running `H.cleanup()` or `H.remove_edges_from(H.edges.duplicates())` References ---------- https://stackoverflow.com/questions/14106121/efficient-algorithm-for-finding-all-maximal-subsets Example ------- >>> import xgi >>> H = xgi.Hypergraph([{1, 2, 3},{1, 2}, {2, 3}, {2}, {2}, {3, 4}, {1, 2, 3}]) >>> H.edges.maximal() EdgeView((0, 5, 6)) >>> H.edges.maximal().members() [{1, 2, 3}, {3, 4}, {1, 2, 3}] """ edges = self._id_dict nodes = self._bi_id_dict max_edges = set() if strict: for i, e in edges.items(): if reduce(lambda x, y: x & y, (nodes[n] for n in e)) == {i}: max_edges.add(i) else: # This data structure so that the algorithm can handle multi-edges dups = defaultdict(list) for idx, members in edges.items(): dups[frozenset(members)].append(idx) for i, e in edges.items(): # If a multi-edge has already been added to the set of # maximal edges, we don't need to check. if i not in max_edges: if reduce(lambda x, y: x & y, (nodes[n] for n in e)) == set( dups[frozenset(e)] ): max_edges.update(dups[frozenset(e)]) return self.from_view(self, bunch=max_edges)
[docs]class DiNodeView(IDView): """A IDView that keeps track of node ids. .. warning:: This is currently an experimental feature. Parameters ---------- hypergraph : DiHypergraph The hypergraph whose nodes this view will keep track of. bunch : optional iterable, default None The node ids to keep track of. If None (default), keep track of all node ids. See Also -------- IDView Notes ----- In addition to the methods listed in this page, other methods defined in the `stats` package are also accessible via the `NodeView` class. For more details, see the `tutorial <https://xgi.readthedocs.io/en/stable/api/tutorials/focus_6.html>`_. """ _id_kind = "dinode" def __init__(self, H, bunch=None): if H is None: super().__init__(None, bunch) else: super().__init__(H, bunch)
[docs] def dimemberships(self, n=None): """Get the edge ids of which a node is a member. Gets all the node memberships for all nodes in the view if n not specified. Parameters ---------- n : hashable, optional Node ID. By default, None. Returns ------- dict of directed node memberships if n is None, otherwise the directed memberships of a single node. Raises ------ XGIError If `n` is not hashable or if it is not in the hypergraph. """ return ( { key: (self._id_dict[key]["in"].copy(), self._id_dict[key]["out"].copy()) for key in self } if n is None else (self._id_dict[n]["in"].copy(), self._id_dict[n]["out"].copy()) )
[docs] def memberships(self, n=None): """Get the edge ids of which a node is a member. Gets all the node memberships for all nodes in the view if n not specified. Parameters ---------- n : hashable, optional Node ID. By default, None. Returns ------- dict of sets if n is None, otherwise a set Node memberships, regardless of whether that node is a sender or receiver. Raises ------ XGIError If `n` is not hashable or if it is not in the dihypergraph. """ return ( { key: set(self._id_dict[key]["in"].union(self._id_dict[key]["out"])) for key in self } if n is None else set(self._id_dict[n]["in"].union(self._id_dict[n]["out"])) )
[docs] def isolates(self): """Nodes that belong to no edges. When ignore_singletons is True, 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 (default), a node is isolated only when it is a member of exactly zero edges, including singletons. Returns ------- NodeView containing the isolated nodes. See Also -------- :meth:`EdgeView.singletons` """ return self.filterby("degree", 0)
[docs]class DiEdgeView(IDView): """An IDView that keeps track of edge ids. .. warning:: This is currently an experimental feature. Parameters ---------- hypergraph : DiHypergraph The hypergraph whose edges this view will keep track of. bunch : optional iterable, default None The edge ids to keep track of. If None (default), keep track of all edge ids. See Also -------- IDView Notes ----- In addition to the methods listed in this page, other methods defined in the `stats` package are also accessible via the `EdgeView` class. For more details, see the `tutorial <https://xgi.readthedocs.io/en/stable/api/tutorials/focus_6.html>`_. """ _id_kind = "diedge" def __init__(self, H, bunch=None): if H is None: super().__init__(None, bunch) else: super().__init__(H, bunch)
[docs] def dimembers(self, e=None, dtype=list): """Get the node ids that are members of an edge. Parameters ---------- e : hashable, optional Edge ID. By default, None. dtype : {list, dict}, optional Specify the type of the return value. By default, list. Returns ------- list (if dtype is list, default) Directed edges. dict (if dtype is dict) Directed edges. set (if e is not None) A single directed edge. In all of these cases, a directed edge is a 2-tuple of sets, where the first entry is the tail, and the second entry is the head. Raises ------ TypeError If `e` is not None or a hashable XGIError If `dtype` is not dict or list IDNotFound If `e` does not exist in the hypergraph """ if e is None: if dtype is dict: return { key: ( self._id_dict[key]["in"].copy(), self._id_dict[key]["out"].copy(), ) for key in self } elif dtype is list: return [ (self._id_dict[key]["in"].copy(), self._id_dict[key]["out"].copy()) for key in self ] else: raise XGIError(f"Unrecognized dtype {dtype}") if e not in self: raise IDNotFound(f'ID "{e}" not in this view') return (self._id_dict[e]["in"].copy(), self._id_dict[e]["out"].copy())
[docs] def members(self, e=None, dtype=list): """Get the edges of a directed hypergraph. Parameters ---------- e : hashable, optional Edge ID. By default, None. dtype : {list, dict}, optional Specify the type of the return value. By default, list. Returns ------- list (if dtype is list, default) Edge members. dict (if dtype is dict) Edge members. set (if e is not None) Members of edge e. The members of an edge are the union of its head and tail sets. Raises ------ TypeError If `e` is not None or a hashable XGIError If `dtype` is not dict or list IDNotFound If `e` does not exist in the hypergraph """ if e is None: if dtype is dict: return { key: set(self._id_dict[key]["in"].union(self._id_dict[key]["out"])) for key in self } elif dtype is list: return [ set(self._id_dict[key]["in"].union(self._id_dict[key]["out"])) for key in self ] else: raise XGIError(f"Unrecognized dtype {dtype}") if e not in self: raise IDNotFound(f'ID "{e}" not in this view') return set(self._id_dict[e]["in"].union(self._id_dict[e]["out"]))
[docs] def head(self, e=None, dtype=list): """Get the node ids that are in the head of a directed edge. Parameters ---------- e : hashable, optional Edge ID. By default, None. dtype : {list, dict}, optional Specify the type of the return value. By default, list. Returns ------- list (if dtype is list, default) Head members. dict (if dtype is dict) Head members. set (if e is not None) Members of the head of edge e. Raises ------ TypeError If `e` is not None or a hashable XGIError If `dtype` is not dict or list IDNotFound If `e` does not exist in the hypergraph """ if e is None: if dtype is dict: return {key: self._id_dict[key]["out"].copy() for key in self} elif dtype is list: return [self._id_dict[key]["out"].copy() for key in self] else: raise XGIError(f"Unrecognized dtype {dtype}") if e not in self: raise IDNotFound(f'ID "{e}" not in this view') return self._id_dict[e]["out"].copy()
[docs] def tail(self, e=None, dtype=list): """Get the node ids that are in the tail of a directed edge. Parameters ---------- e : hashable, optional Edge ID. By default, None. dtype : {list, dict}, optional Specify the type of the return value. By default, list. Returns ------- list (if dtype is list, default) Tail members. dict (if dtype is dict) Tail members. set (if e is not None) Tail members of edge e. Raises ------ TypeError If `e` is not None or a hashable XGIError If `dtype` is not dict or list IDNotFound If `e` does not exist in the hypergraph """ if e is None: if dtype is dict: return {key: self._id_dict[key]["in"].copy() for key in self} elif dtype is list: return [self._id_dict[key]["in"].copy() for key in self] else: raise XGIError(f"Unrecognized dtype {dtype}") if e not in self: raise IDNotFound(f'ID "{e}" not in this view') return self._id_dict[e]["in"].copy()
[docs] def sources(self, e=None, dtype=list): """Get the nodes that are sources (senders) in the directed edges. See Also -------- tail: identical method """ return self.tail(e=e, dtype=dtype)
[docs] def targets(self, e=None, dtype=list): """Get the nodes that are sources (senders) in the directed edges. See Also -------- head: identical method """ return self.head(e=e, dtype=dtype)
[docs] def empty(self): """Edges with no nodes in the head or the tail. Returns ------- DiEdgeView containing the empty edges. See Also -------- :meth:`EdgeView.empty` """ return self.filterby("size", 0)