diff --git a/CITATION.cff b/CITATION.cff index 9a33333fb..cff06c4f3 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -7,6 +7,16 @@ authors: - family-names: Torres given-names: Leo + - + family-names: Iacopini + given-names: Iacopo + - + family-names: Lucas + given-names: Maxime + - + family-names: Petri + given-names: Giovanni + cff-version: "1.1.0" license: "BSD-3" message: "If you use this software, please cite it using these metadata." diff --git a/LICENSE.md b/LICENSE.md index 9d4acfebb..e08c83811 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,8 +1,11 @@ XGI is distributed with the 3-clause BSD license. -Copyright (C) 2021, Hypergraph Developers +Copyright (C) 2021, XGI Developers Nicholas Landry Leo Torres +Iacopo Iacopini +Maxime Lucas +Giovanni Petri All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/tests/classes/test_function.py b/tests/classes/test_function.py index 04d7b0fe6..818a35fd2 100644 --- a/tests/classes/test_function.py +++ b/tests/classes/test_function.py @@ -80,15 +80,15 @@ def test_create_empty_copy(edgelist1): assert E1.shape == (8, 0) for node in E1.nodes: - assert len(E1.nodes[node]) == 0 - assert E1.name == "" + assert len(E1.nodes.memberships(node)) == 0 assert E1._hypergraph == {} assert E2.shape == (8, 0) for node in E2.nodes: - assert len(E1.nodes[node]) == 0 + assert len(E1.nodes.memberships(node)) == 0 assert E2._hypergraph == {"name": "test", "timestamp": "Nov. 20"} - assert dict(E2.nodes.data()) == attr_dict + for n in H.nodes: + assert H.nodes[n]["name"] == attr_dict[n]["name"] def test_is_empty(): diff --git a/tests/classes/test_hypergraph.py b/tests/classes/test_hypergraph.py index 80dc31e94..9becc5b8c 100644 --- a/tests/classes/test_hypergraph.py +++ b/tests/classes/test_hypergraph.py @@ -1,4 +1,6 @@ +import pytest import xgi +from xgi.exception import XGIError def test_constructor(edgelist5, dict5, incidence5, dataframe5): @@ -21,20 +23,20 @@ def test_constructor(edgelist5, dict5, incidence5, dataframe5): == list(H_df.edges) ) assert ( - list(H_list.edges[0]) - == list(H_dict.edges[0]) - == list(H_mat.edges[0]) - == list(H_df.edges[0]) + list(H_list.edges.members(0)) + == list(H_dict.edges.members(0)) + == list(H_mat.edges.members(0)) + == list(H_df.edges.members(0)) ) -def test_name(): +def test_hypergraph_attrs(): H = xgi.Hypergraph() - assert H.name == "" - H.name = "test" - assert H.name == "test" + assert H._hypergraph == dict() + with pytest.raises(XGIError): + name = H["name"] H = xgi.Hypergraph(name="test") - assert H.name == "test" + assert H["name"] == "test" def test_contains(edgelist1): @@ -46,6 +48,12 @@ def test_contains(edgelist1): assert 0 not in H +def test_string(): + H1 = xgi.Hypergraph() + assert str(H1) == "Unnamed Hypergraph with 0 nodes and 0 hyperedges" + H2 = xgi.Hypergraph(name="test") + # H2["name"] = "test" + assert str(H2) == "Hypergraph named test with 0 nodes and 0 hyperedges" def test_len(edgelist1, edgelist2): el1 = edgelist1 @@ -93,7 +101,7 @@ def test_dual(edgelist1, edgelist2, edgelist4): assert D3.shape == (3, 5) -def test_max_edge_order(edgelist1, edgelist4,edgelist5): +def test_max_edge_order(edgelist1, edgelist4, edgelist5): H0 = xgi.empty_hypergraph() H1 = xgi.empty_hypergraph() H1.add_nodes_from(range(5)) @@ -103,7 +111,7 @@ def test_max_edge_order(edgelist1, edgelist4,edgelist5): assert H0.max_edge_order() == None assert H1.max_edge_order() == 0 - assert H2.max_edge_order() == 2 + assert H2.max_edge_order() == 2 assert H3.max_edge_order() == 3 assert H4.max_edge_order() == 3 @@ -112,7 +120,7 @@ def test_is_possible_order(edgelist1): H1 = xgi.Hypergraph(edgelist1) assert H1.is_possible_order(-1) == False - assert H1.is_possible_order(0) == False + assert H1.is_possible_order(0) == False assert H1.is_possible_order(1) == True assert H1.is_possible_order(2) == True assert H1.is_possible_order(3) == False @@ -122,7 +130,7 @@ def test_singleton_edges(edgelist1, edgelist2): H1 = xgi.Hypergraph(edgelist1) H2 = xgi.Hypergraph(edgelist2) - assert H1.singleton_edges() == {1 : [4]} + assert H1.singleton_edges() == {1: [4]} assert H2.singleton_edges() == {} @@ -143,8 +151,8 @@ def test_is_uniform(edgelist1, edgelist6, edgelist7): H2 = xgi.Hypergraph(edgelist7) H3 = xgi.empty_hypergraph() - assert H0.is_uniform() == False - assert H1.is_uniform() == 2 + assert H0.is_uniform() == False + assert H1.is_uniform() == 2 assert H2.is_uniform() == 2 assert H3.is_uniform() == False @@ -155,3 +163,33 @@ def test_isolates(edgelist1): assert H.isolates() == {4} H.remove_isolates() assert 4 not in H + + +def test_add_node_attr(edgelist1): + H = xgi.Hypergraph(edgelist1) + assert "new_node" not in H + H.add_node("new_node", color="red") + assert "new_node" in H + assert "color" in H.nodes["new_node"] + assert H.nodes["new_node"]["color"] == "red" + + +def test_hypergraph_attr(edgelist1): + H = xgi.Hypergraph(edgelist1) + with pytest.raises(XGIError): + H["color"] + H["color"] = "red" + assert H["color"] == "red" + + +def test_members(edgelist1): + H = xgi.Hypergraph(edgelist1) + assert H.nodes.memberships(1) == [0] + assert H.nodes.memberships(2) == [0] + assert H.nodes.memberships(3) == [0] + assert H.nodes.memberships(4) == [1] + assert H.nodes.memberships(6) == [2, 3] + with pytest.raises(XGIError): + H.nodes.memberships(0) + with pytest.raises(XGIError): + H.nodes.memberships(slice(1, 4)) diff --git a/tests/conftest.py b/tests/conftest.py index c8391a87d..4fe638159 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,14 +30,16 @@ def edgelist4(): @pytest.fixture def edgelist5(): return [[0, 1, 2, 3], [4], [5, 6], [6, 7, 8]] - + + @pytest.fixture def edgelist6(): return [[0, 1, 2], [1, 2, 3], [2, 3, 4]] - + + @pytest.fixture def edgelist7(): - return [[0, 1, 2], [1, 2, 3], [2, 3, 4], [4]] + return [[0, 1, 2], [1, 2, 3], [2, 3, 4], [4]] @pytest.fixture diff --git a/tests/generators/test_nonuniform.py b/tests/generators/test_nonuniform.py index 1391ab146..4e583da15 100644 --- a/tests/generators/test_nonuniform.py +++ b/tests/generators/test_nonuniform.py @@ -13,7 +13,7 @@ def test_erdos_renyi_hypergraph(): H = xgi.erdos_renyi_hypergraph(10, 20, 0.0) assert H.number_of_nodes() == 10 assert H.number_of_edges() == 0 - + H = xgi.erdos_renyi_hypergraph(10, 20, 1.0) assert H.number_of_nodes() == 10 assert H.number_of_edges() == 20 @@ -32,4 +32,3 @@ def test_chung_lu_hypergraph(): k1 = {1: 1, 2: 2} k2 = {1: 2, 1: 2} H = xgi.chung_lu_hypergraph(k1, k2) - diff --git a/xgi/classes/function.py b/xgi/classes/function.py index 543dc7073..68ea80fa5 100644 --- a/xgi/classes/function.py +++ b/xgi/classes/function.py @@ -64,7 +64,7 @@ def unique_edge_sizes(H, return_counts=False): list() The unique edge sizes """ - return list({len(H.edges[edge]) for edge in H.edges}) + return list({len(H.edges.members(edge)) for edge in H.edges}) def frozen(*args, **kwargs): @@ -198,7 +198,7 @@ def create_empty_copy(H, with_data=True): H_copy = H.__class__() H_copy.add_nodes_from(H.nodes) if with_data: - xgi.set_node_attributes(H_copy, dict(H.nodes.data())) + xgi.set_node_attributes(H_copy, dict(H._node_attr)) H_copy._hypergraph.update(H._hypergraph) return H_copy @@ -251,7 +251,7 @@ def set_node_attributes(H, values, name=None): try: # `values` is a dict for n, v in values.items(): try: - H._node_attr[n][name] = values[n] + H._node_attr[n][name] = v except KeyError: pass except AttributeError: # `values` is a constant @@ -362,7 +362,7 @@ def get_edge_attributes(H, name): get_node_attributes set_edge_attributes """ - edge_data = H.edges.data + edge_data = H._edge_attr return {id: edge_data[edge][name] for edge in edge_data if name in edge_data[edge]} diff --git a/xgi/classes/hypergraph.py b/xgi/classes/hypergraph.py index 869fc7214..d6e2f0b59 100644 --- a/xgi/classes/hypergraph.py +++ b/xgi/classes/hypergraph.py @@ -7,11 +7,9 @@ """ from copy import deepcopy -import numpy as np import xgi import xgi.convert as convert -from xgi.classes.reportviews import (DegreeView, EdgeSizeView, EdgeView, - NodeView) +from xgi.classes.reportviews import DegreeView, EdgeSizeView, EdgeView, NodeView from xgi.exception import XGIError from xgi.utils import XGICounter @@ -51,14 +49,7 @@ def __init__(self, incoming_hypergraph_data=None, **attr): """ self._edge_uid = XGICounter() - self.hypergraph_attr_dict_factory = self.hypergraph_attr_dict_factory - self.node_dict_factory = self.node_dict_factory - self.node_attr_dict_factory = self.node_attr_dict_factory - self.hyperedge_attr_dict_factory = self.hyperedge_attr_dict_factory - - self._hypergraph = ( - self.hypergraph_attr_dict_factory() - ) # dictionary for graph attributes + self._hypergraph = self.hypergraph_attr_dict_factory() self._node = self.node_dict_factory() # empty node attribute dict self._node_attr = self.node_attr_dict_factory() self._edge = self.hyperedge_dict_factory() @@ -69,32 +60,6 @@ def __init__(self, incoming_hypergraph_data=None, **attr): # load hypergraph attributes (must be after convert) self._hypergraph.update(attr) - @property - def name(self): - """Get the string identifier of the hypergraph. - - This hypergraph attribute appears in the attribute dict H._hypergraph - keyed by the string `"name"`. as well as an attribute (technically - a property) `H.name`. This is entirely user controlled. - - Returns - ------- - string - The name of the hypergraph - """ - return self._hypergraph.get("name", "") - - @name.setter - def name(self, s): - """Set the name of the hypergraph - - Parameters - ---------- - s : string - The desired name of the hypergraph. - """ - self._hypergraph["name"] = s - def __str__(self): """Returns a short summary of the hypergraph. @@ -111,13 +76,10 @@ def __str__(self): "Hypergraph named 'foo' with 0 nodes and 0 edges" """ - return "".join( - [ - type(self).__name__, - f" named {self.name!r}", - f" with {self.number_of_nodes()} nodes and {self.number_of_edges()} hyperedges", - ] - ) + try: + return f"{type(self).__name__} named {self['name']} with {self.number_of_nodes()} nodes and {self.number_of_edges()} hyperedges" + except: + return f"Unnamed {type(self).__name__} with {self.number_of_nodes()} nodes and {self.number_of_edges()} hyperedges" def __iter__(self): """Iterate over the nodes. Use: 'for n in H'. @@ -162,19 +124,16 @@ def __len__(self): """ return len(self._node) - def __getitem__(self, n): - """Returns a list of neighboring edge IDs of node n. Use: 'H[n]'. - - Returns - ------- - list - A list of the edges of which the specified node is a part. + def __getitem__(self, attr): + """Read hypergraph attribute.""" + try: + return self._hypergraph[attr] + except KeyError: + raise XGIError("This attribute has not been set.") - Notes - ----- - H[n] is the same as H.nodes[n] - """ - return self._node[n] + def __setitem__(self, attr, val): + """Write hypergraph attribute.""" + self._hypergraph[attr] = val @property def shape(self): @@ -242,8 +201,7 @@ def add_node(self, node_for_adding, **attr): raise ValueError("None cannot be a node") self._node[node_for_adding] = list() self._node_attr[node_for_adding] = self.node_attr_dict_factory() - else: # update attr even if node already exists - self._node_attr[node_for_adding].update(attr) + self._node_attr[node_for_adding].update(attr) def add_nodes_from(self, nodes_for_adding, **attr): """Add multiple nodes. @@ -301,7 +259,7 @@ def remove_node(self, n): remove_nodes_from """ try: - edge_neighbors = self.nodes[n] # list handles self-loops (allows mutation) + edge_neighbors = self._node[n] # list handles self-loops (allows mutation) del self._node[n] del self._node_attr[n] except KeyError as e: # XGIError if n not in self @@ -328,9 +286,8 @@ def remove_nodes_from(self, nodes): """ for n in nodes: try: - edge_neighbors = self.nodes[ - n - ] # list handles self-loops (allows mutation) + edge_neighbors = self._node[n] + # list handles self-loops (allows mutation) del self._node[n] del self._node_attr[n] for edge in edge_neighbors: @@ -347,21 +304,14 @@ def nodes(self): """A NodeView of the Hypergraph as H.nodes or H.nodes(). Can be used as `H.nodes` for data lookup and for set-like operations. - Can also be used as `H.nodes(data='color', default=None)` to return a - NodeDataView which reports specific node data but no set operations. - It presents a dict-like interface as well with `H.nodes.items()` - iterating over `(node, edge_ids)` 2-tuples. In addition, - a view `H.nodes.data('foo')` provides a dict-like interface to the - `foo` attribute of each node. `H.nodes.data('foo', default=1)` - provides a default for nodes that do not have attribute `foo`. + Can also be used as `H.nodes[id]` to return a + dictionary of the node attributes. Returns ------- NodeView Allows set-like operations over the nodes as well as node - attribute dict lookup and calling to get a NodeDataView. - A NodeDataView iterates over `(n, data)` and has no set operations. - A NodeView iterates over `n` and includes set operations. + attribute dict lookup. When called, if data is False, an iterator over nodes. Otherwise an iterator of 2-tuples (node, attribute value) @@ -649,7 +599,7 @@ def remove_edge(self, id): remove_edges_from : remove a collection of edges """ try: - for node in self.edges[id]: + for node in self.edges.members(id): self._node[node].remove(id) del self._edge[id] del self._edge_attr[id] @@ -675,7 +625,7 @@ def remove_edges_from(self, ebunch): """ for id in ebunch: try: - for node in self.edges[id]: + for node in self.edges.members(id): self._node[node].remove(id) del self._edge[id] del self._edge_attr[id] @@ -764,14 +714,12 @@ def has_edge_id(self, id): @property def edges(self): - """An EdgeView of the Hyperraph as H.edges or H.edges(). + """An EdgeView of the Hypergraph as H.edges or H.edges(). edges(self, nbunch=None, data=False, default=None) The EdgeView provides set-like operations on the edge IDs - as well as edge attribute lookup. When called, it also provides - an EdgeDataView object which allows control of access to edge - attributes (but does not provide set-like operations). + as well as edge attribute lookup. Parameters ---------- @@ -787,7 +735,12 @@ def edges(self): ----- Nodes in nbunch that are not in the hypergraph will be (quietly) ignored. """ - return EdgeView(self) + edges = EdgeView(self) + # Lazy View creation: overload the (class) property on the instance + # Then future H.edges use the existing View + # setattr doesn't work because attribute already exists + self.__dict__["edges"] = edges + return edges def get_edge_data(self, id, default=None): """Returns the attribute dictionary associated with edge id. @@ -808,7 +761,9 @@ def get_edge_data(self, id, default=None): The edge attribute dictionary. """ try: - return self.edges.data(id, default=default) + # this may fail because the ID may not exist + # or the property doesn't exist. + return self.edges[id] except KeyError: return default @@ -1216,7 +1171,7 @@ def isolates(self, ignore_singletons=True): """ nodes_in_edges = set() for idx in self.edges: - edge = self.edges(idx) + edge = self.edges.members(idx) if ignore_singletons and len(edge) == 1: continue nodes_in_edges = nodes_in_edges.union(edge) @@ -1243,8 +1198,8 @@ def is_uniform(self): all edges in the hypergraph (excluding singletons, i.e. nodes) have the same degree d. Returns d=None if not uniform. - This function can be used as a boolean check: - >>> if H.is_uniform() + This function can be used as a boolean check: + >>> if H.is_uniform() works as expected. Returns: @@ -1260,7 +1215,7 @@ def is_uniform(self): edge_sizes.remove(1) # discard singleton edges if len(edge_sizes) == 0: # no edges - d = False # not uniform + d = False # not uniform else: uniform = len(edge_sizes) == 1 if uniform: @@ -1268,4 +1223,4 @@ def is_uniform(self): else: d = False - return d + return d diff --git a/xgi/classes/reportviews.py b/xgi/classes/reportviews.py index a592760ba..2c4d44311 100644 --- a/xgi/classes/reportviews.py +++ b/xgi/classes/reportviews.py @@ -1,5 +1,5 @@ """ -View Classes provide node, edge and degree "views" of a hypergraph. +View Classes provide node, edge, degree, and edge size "views" of a hypergraph. A view means a read-only object that is quick to create, automatically updated when the hypergraph changes, and provides basic access like `n in V`, @@ -10,7 +10,7 @@ while iterating through the view. Views can be iterated multiple times. Edge and Node views also allow data attribute lookup. -The resulting attribute dict is writable as `H.edges[3, 4]['color']='red'` +The resulting attribute dict is writable as `H.edges[3]['color']='red'` Degree views allow lookup of degree values for single nodes. Weighted degree is supported with the `weight` argument. @@ -21,17 +21,6 @@ operations e.H. "H.nodes & H.nodes", and `dd = H.nodes[n]`, where `dd` is the node data dict. Iteration is over the nodes by default. -NodeDataView -============ - - To iterate over (node, data) pairs, use arguments to `H.nodes()` - to create a DataView e.H. `DV = H.nodes(data='color', default='red')`. - The DataView iterates as `for n, color in DV` and allows - `(n, 'red') in DV`. Using `DV = H.nodes(data=True)`, the DataViews - use the full datadict in writeable form also allowing contain testing as - `(n, {'color': 'red'}) in VD`. DataViews allow set operations when - data attributes are hashable. - EdgeView ======== @@ -43,15 +32,6 @@ This may be in future functionality. As it stands, however, the same edge can be added more than once using different IDs -EdgeDataView -============ - - Edge data can be reported using an EdgeDataView typically created - by calling an EdgeView: `DV = H.edges(data='weight', default=1)`. - The EdgeDataView allows iteration over edge ids. - - The argument `nbunch` restricts edges to those incident to nodes in nbunch. - DegreeView ========== @@ -82,1111 +62,369 @@ __all__ = [ "NodeView", - "NodeDataView", "EdgeView", - "EdgeDataView", "DegreeView", "EdgeSizeView", ] +# ID View Base Class +class IDView(Mapping, Set): + """A Base View class to act as H.nodes and H.edges for a Hypergraph.""" -# NodeViews -class NodeView(Mapping, Set): - """A NodeView class to act as H.nodes for a Hypergraph.""" - - __slots__ = ("_nodes", "_node_attrs") + __slots__ = ("_ids", "_id_attrs") def __getstate__(self): - """Function that allows pickling of the nodes (write) + """Function that allows pickling of the IDs (write) Returns ------- dict of dict - The keys access the nodes and their attributes respectively + The keys access the IDs and their attributes respectively and the values are dictionarys from the Hypergraph class. """ - return {"_nodes": self._nodes, "_node_attrs": self._node_attrs} + return {"_ids": self._ids, "_id_attrs": self._id_attrs} def __setstate__(self, state): - """Function that allows pickling of the nodes (read) + """Function that allows pickling of the IDs (read) Parameters ---------- state : dict of dict - The keys access the nodes and their attributes respectively + The keys access the IDs and their attributes respectively and the values are dictionarys from the Hypergraph class. """ - self._nodes = state["_nodes"] - self._node_attrs = state["_node_attrs"] - - def __init__(self, hypergraph): - """Initialize the NodeView with a hypergraph + self._ids = state["_ids"] + self._id_attrs = state["_id_attrs"] + def __init__(self, ids, id_attrs): + """Initialize the IDView with IDs and associated attributes Parameters ---------- - hypergraph : Hypergraph object - The hypergraph of interest + ids : dict + Specifies IDs and the associated adjacency list + id_attrs : dict + Specifies IDs and their associated attributes. """ - self._nodes = hypergraph._node - self._node_attrs = hypergraph._node_attr + self._ids = ids + self._id_attrs = id_attrs + # Mapping methods # Mapping methods def __len__(self): - """Return the number of nodes - + """Return the number of IDs Returns ------- int - Number of nodes + Number of IDs """ - return len(self._nodes) + return len(self._ids) def __iter__(self): - """Returns an iterator over the node IDs. - + """Returns an iterator over the IDs. Returns ------- iterator of hashables - Each entry is a node in the hypergraph. - """ - return iter(self._nodes) - - def __getitem__(self, n): - """Get the edges of which the node is a member. - - Identical to __call__, members - - Parameters - ---------- - n : hashable - node ID - - Returns - ------- - list - A list of the edge IDs of which the node is a part. - - Raises - ------ - xgi.XGIError - Returns an error if the user tries passing in a slice or if - the node does not exist in the hypergraph. - - See Also - -------- - __call__ - members - """ - if isinstance(n, slice): - raise XGIError( - f"{type(self).__name__} does not support slicing, " - f"try list(H.nodes)[{n.start}:{n.stop}:{n.step}]" - ) - elif n not in self._nodes: - raise XGIError(f"The node {n} is not in the hypergraph") - - return self._nodes[n] - - # Set methods - def __contains__(self, n): - """Checks whether the node is in the hypergraph - - Parameters - ---------- - n : hashable - The ID of a node - - Returns - ------- - bool - True if the node is in the hypergraph, False otherwise. - """ - return n in self._nodes - - def __call__(self, n): - """Handles calling the nodes, i.e. H.nodes(n). - - Identical to __getitem__, members - - Parameters - ---------- - n : hashable - node ID - - Returns - ------- - list - A list of the edge IDs of which the node is a part. - - Raises - ------ - xgi.XGIError - Returns an error if the user tries passing in a slice or if - the node does not exist in the hypergraph. - - See Also - -------- - __getitem__ - members - """ - if isinstance(n, slice): - raise XGIError( - f"{type(self).__name__} does not support slicing, " - f"try list(H.nodes)[{n.start}:{n.stop}:{n.step}]" - ) - elif n not in self._nodes: - raise XGIError(f"The node {n} is not in the hypergraph") - return self._nodes[n] - - def data(self, data=True, default=None): - """ - Return a read-only view of node data. - - Parameters - ---------- - data : bool or node data key, default=True - If ``data=True`` (the default), return a `NodeDataView` object that - maps each node to *all* of its attributes. `data` may also be an - arbitrary key, in which case the `NodeDataView` maps each node to - the value for the keyed attribute. In this case, if a node does - not have the `data` attribute, the `default` value is used. - default : object, default=None - The value used when a node does not have a specific attribute. - - Returns - ------- - NodeDataView - The layout of the returned NodeDataView depends on the value of the - `data` parameter. - - Notes - ----- - If ``data=False``, returns a `NodeView` object without data. - - See Also - -------- - NodeDataView - - Examples - -------- - >>> H = xgi.Hypergraph() - >>> H.add_nodes_from([ - ... (0, {"color": "red", "weight": 10}), - ... (1, {"color": "blue"}), - ... (2, {"color": "yellow", "weight": 2}) - ... ]) - - Accessing node data with ``data=True`` (the default) returns a - NodeDataView mapping each node to all of its attributes: - - >>> H.nodes.data() - NodeDataView({0: {'color': 'red', 'weight': 10}, 1: {'color': 'blue'}, 2: {'color': 'yellow', 'weight': 2}}) - - If `data` represents a key in the node attribute dict, a NodeDataView mapping - the nodes to the value for that specific key is returned: - - >>> H.nodes.data("color") - NodeDataView({0: 'red', 1: 'blue', 2: 'yellow'}, data='color') - - If a specific key is not found in an attribute dict, the value specified - by `default` is returned: - - >>> H.nodes.data("weight", default=-999) - NodeDataView({0: 10, 1: -999, 2: 2}, data='weight') - - Note that there is no check that the `data` key is in any of the - node attribute dictionaries: - - >>> H.nodes.data("height") - NodeDataView({0: None, 1: None, 2: None}, data='height') - """ - if data is False: - return self - return NodeDataView(self._node_attrs, data, default) - - def __str__(self): - """Returns a string of the list of node IDs. - - Returns - ------- - string - A string of the list of node IDs. - """ - return str(list(self)) - - def __repr__(self): - """Returns a summary of the class - - Returns - ------- - string - The class name with the node IDs. - """ - return f"{self.__class__.__name__}({tuple(self)})" - - def members(self, n): - """Handles calling the nodes, i.e. H.nodes(n). - - Identical to __getitem__, __call__ - - Parameters - ---------- - n : hashable - node ID - - Returns - ------- - list - A list of the edge IDs of which the node is a part. - - Raises - ------ - xgi.XGIError - Returns an error if the user tries passing in a slice or if - the node does not exist in the hypergraph. - - See Also - -------- - __getitem__ + Each entry is an ID in the hypergraph. """ - if isinstance(n, slice): - raise XGIError( - f"{type(self).__name__} does not support slicing, " - f"try list(H.nodes)[{n.start}:{n.stop}:{n.step}]" - ) - elif n not in self._nodes: - raise XGIError(f"The node {n} is not in the hypergraph") - return self._nodes[n] - - -class NodeDataView(Set): - """A DataView class for nodes of a Hypergraph - - The main use for this class is to iterate through node-data pairs. - The data can be the entire data-dictionary for each node, or it - can be a specific attribute (with default) for each node. - Set operations are enabled with NodeDataView, but don't work in - cases where the data is not hashable. Use with caution. - Typically, set operations on nodes use NodeView, not NodeDataView. - That is, they use `H.nodes` instead of `H.nodes(data='foo')`. - """ - - __slots__ = ("_node_attrs", "_data", "_default") + return iter(self._ids) - def __getstate__(self): - """Function that allows pickling of the node data (write) - - Returns - ------- - dict of dict - The keys access the node attributes, the data key, and the default value respectively - and the values are a dictionary, a hashable, and a hashable respectively. - """ - return { - "_node_attrs": self._node_attrs, - "_data": self._data, - "_default": self._default, - } - - def __setstate__(self, state): - """Function that allows pickling of the node data (read) + def __getitem__(self, id): + """Get the attributes of the ID. Parameters ---------- - state : dict of dict - The keys access the node attributes, the data key, and the default value respectively - and the values are a dictionary, a hashable, and a hashable respectively. - """ - self._node_attrs = state["_node_attrs"] - self._data = state["_data"] - self._default = state["_default"] - - def __init__(self, node_attrs, data=True, default=None): - """Initialize the NodeDataView object - - Parameters - ---------- - node_attrs : dict - The dictionary of attributes with node IDs as keys - and dictionaries as values - data : bool or hashable, optional - Whether or not to return all the data - or the key for the attribute, by default True - default : anything, optional - The value that should be returned if - the key value doesn't exist, by default None - """ - self._node_attrs = node_attrs - self._data = data - self._default = default - - def __len__(self): - """Returns the number of nodes - - Returns - ------- - int - Number of nodes - """ - return len(self._node_attrs) - - def __iter__(self): - """Returns an iterator over node IDs - or node ID, value pairs if the data - keyword arg is not False. - - Returns - ------- - iterator - Iterator over node IDs (data=False) or node ID, value - pairs (else). - """ - data = self._data - if data is False: - return iter(self._node_attrs) - if data is True: - return iter(self._node_attrs.items()) - return ( - (n, dd[data] if data in dd else self._default) - for n, dd in self._node_attrs.items() - ) - - def __contains__(self, n): - """Checks whether a node ID are in a - hypergraph. - - Parameters - ---------- - n : hashable + id : hashable node ID - Returns - ------- - bool - Whether the node is the hypergraph. - """ - return n in self._node_attrs - - def __getitem__(self, n): - """Get the data from a node. - - Parameters - ---------- - n : hashable - Node ID - Returns ------- dict - empty dictionary if False, data dictionary if True, - and a dictionary with one key if the key is specified. + Node attributes. Raises ------ xgi.XGIError Returns an error if the user tries passing in a slice or if the node does not exist in the hypergraph. - """ - if isinstance(n, slice): - raise XGIError( - f"{type(self).__name__} does not support slicing, " - f"try list(H.nodes.data())[{n.start}:{n.stop}:{n.step}]" - ) - elif n not in self._data: - raise XGIError(f"The node {n} is not in the hypergraph") - - ddict = self._node_attrs[n] - data = self._data - if data is False: - return dict() - elif data is True: - return ddict - return {data: ddict[data]} if data in ddict else {data: self._default} - - def __str__(self): - """Returns a string listing the node IDs. - - Returns - ------- - string - A string listing the node IDs. - """ - return str(list(self)) - - def __repr__(self): - """Controls what is displayed when calling repr(H.nodes.data) - Returns - ------- - string - A string with the class name, nodes, and data. - """ - name = self.__class__.__name__ - if self._data is False: - return f"{name}({tuple(self)})" - if self._data is True: - return f"{name}({dict(self)})" - return f"{name}({dict(self)}, data={self._data!r})" - - -# EdgeViews have set operations and no data reported -class EdgeView(Set, Mapping): - """A EdgeView class for the edges of a Hypergraph""" - - __slots__ = "_edges" - - def __getstate__(self): - """Function that allows pickling of the edges (write) - - Returns - ------- - dict of dict - The keys access the edges and the value is - a dictionary of the hypergraph "_edge" dictionary. - """ - return {"_edges": self._edges} - - def __setstate__(self, state): - """Function that allows pickling of the edges (read) - - Parameters - ---------- - state : dict of dict - The keys access the edges and the value is - a dictionary of the hypergraph "_edge" dictionary. - """ - self._edges = state["_edges"] - - def __init__(self, H): - """Initialize the EdgeView object - - Parameters - ---------- - H : Hypergraph object - The hypergraph of interest. - """ - self._edges = H._edge - - # Set methods - def __len__(self): - """Get the number of edges - - Returns - ------- - int - The number of edges - """ - return len(self._edges) - - def __iter__(self): - """Returns an iterator over edge IDs. - - Returns - ------- - iterator - Iterator over the edge IDs. - """ - return iter(self._edges) - - def __contains__(self, e): - """Return edge members if the edge ID is - in the hypergraph and False if not. - - Parameters - ---------- - e : hashable - the edge ID - - Returns - ------- - bool or list - Returns False if the edge ID does not exist in - the hypergraph and the edge as a list if it does. """ try: - return self._edges[e] + return self._id_attrs[id] except KeyError: - return False - - # get edge members - def __getitem__(self, e): - """Get the members of an edge as a list - given an edge ID. - - Parameters - ---------- - e : hashable - edge ID - - Returns - ------- - list - edge members - - Raises - ------ - xgi.XGIError - Returns an error if the user tries passing in a slice or if - the edge ID does not exist in the hypergraph. - - See Also - -------- - __call__ - members - """ - if isinstance(e, slice): - raise XGIError( - f"{type(self).__name__} does not support slicing, " - f"try list(H.edges)[{e.start}:{e.stop}:{e.step}]" - ) - elif e not in self._edges: - raise XGIError(f"The edge {e} is not in the hypergraph") - return self._edges[e] - - # get edge members - def __call__(self, e): - """Get the members of an edge as a list - given an edge ID. - - Parameters - ---------- - e : hashable - edge ID - - Returns - ------- - list - edge members - - Raises - ------ - xgi.XGIError - Returns an error if the user tries passing in a slice or if - the edge ID does not exist in the hypergraph. - - See Also - -------- - __getitem__ - members - """ - if isinstance(e, slice): - raise XGIError( - f"{type(self).__name__} does not support slicing, " - f"try list(H.edges)[{e.start}:{e.stop}:{e.step}]" - ) - elif e not in self._edges: - raise XGIError(f"The edge {e} is not in the hypergraph") - return self._edges[e] - - def members(self, e): - """Get the members of an edge as a list - given an edge ID. - - Parameters - ---------- - e : hashable - edge ID - - Returns - ------- - list - edge members - - Raises - ------ - xgi.XGIError - Returns an error if the user tries passing in a slice or if - the edge ID does not exist in the hypergraph. - - See Also - -------- - __getitem__ - __call__ - """ - if isinstance(e, slice): - raise XGIError( - f"{type(self).__name__} does not support slicing, " - f"try list(H.edges)[{e.start}:{e.stop}:{e.step}]" - ) - elif e not in self._edges: - raise XGIError(f"The edge {e} is not in the hypergraph") - return self._edges[e] - - def data(self, data=True, default=None, nbunch=None): - """Return the data associated with edges - - Parameters - ---------- - data : bool, optional - True for all associated data, False for no data, - and a hashable specifying the attribute desired, - by default True - default : anything, optional - The value to return if the attribute doesn't exist, - by default None - nbunch : iterable, optional - A list of edge IDs, by default None - - Returns - ------- - EdgeDataView object - The edges of the hypergraph. - """ - if nbunch is None and data is False: - return self - return EdgeDataView(self, nbunch, data, default) - - # String Methods - def __str__(self): - """Return a string of edge IDs - - Returns - ------- - string - A string of edge IDs - """ - return str(list(self)) - - def __repr__(self): - """Returns a string representing the EdgeView. - - Returns - ------- - string - Returns a string stating the class name - and the edge IDs in the class - """ - return f"{self.__class__.__name__}({list(self)})" - - -# EdgeDataViews -class EdgeDataView: - """EdgeDataView for edges of Hypergraph""" - - __slots__ = ("_edge_attrs", "_data", "_default") - - def __getstate__(self): - """Function that allows pickling of the edge data (write) - - Returns - ------- - dict of dict - The keys access the edge attributes, the data key, and the default value respectively - and the values are a dictionary, a hashable, and a hashable respectively. - """ - self._n - return { - "_edge_attrs": self._edge_attrs, - "_data": self._data, - "_default": self._default, - } - - def __setstate__(self, state): - """Function that allows pickling of the edge data (read) - - Parameters - ---------- - state : dict of dict - The keys access the edge attributes, the data key, and the default value respectively - and the values are a dictionary, a hashable, and a hashable respectively. - """ - self._edge_attrs = state["_edge_attrs"] - self._data = state["_data"] - self._default = state["_default"] - - def __init__(self, edgedict, data=True, default=None): - """Initialize the edge data object with the hypergraph data. - - Parameters - ---------- - edgedict : dict - The edge attribute dictionary - data : bool or hashable, optional - True for all associated data, False for no data, - and a hashable specifying the attribute desired, - by default True - default : anything, optional - The value to return if the attribute doesn't exist, - by default None - """ - self._edge_attrs = edgedict - self._data = data - self._default = default - - def __len__(self): - """Get the number of edges - - Returns - ------- - int - The number of edges - """ - return len(self._edge_attrs) - - def __iter__(self): - """Returns an iterator over edge IDs - or edge ID, value pairs if the data - keyword arg is not False. - - Returns - ------- - iterator - Iterator over edge IDs (data=False) or edge ID, value - pairs (else). - """ - data = self._data - if data is False: - return iter(self._edge_attrs) - if data is True: - return iter(self._edge_attrs.items()) - return ( - (e, dd[data] if data in dd else self._default) - for e, dd in self._edge_attrs.items() - ) - - def __contains__(self, e): - """Returns whether an edge ID is in the hypergraph + raise XGIError(f"The ID {id} is not in the hypergraph") + except: + if isinstance(id, slice): + raise XGIError( + f"{type(self).__name__} does not support slicing, " + f"try list(H.nodes)[{id.start}:{id.stop}:{id.step}]" + ) + # Set methods + def __contains__(self, id): + """Checks whether the ID is in the hypergraph Parameters ---------- - n : hashable - edge ID - + id : hashable + A unique ID Returns ------- bool - True if the edge ID is in the hypergraph, False if note. + True if the ID is in the hypergraph, False otherwise. """ - return e in self._edge_attrs - - def __getitem__(self, e): - """Get the data from an edge. - - Parameters - ---------- - e : hashable - edge ID - - Returns - ------- - dict - empty dictionary if False, data dictionary if True, - and a dictionary with one key if the key is specified. - - Raises - ------ - xgi.XGIError - Returns an error if the user tries passing in a slice or if - the edge ID does not exist in the hypergraph. - """ - if isinstance(e, slice): - raise XGIError( - f"{type(self).__name__} does not support slicing, " - f"try list(H.nodes.data())[{e.start}:{e.stop}:{e.step}]" - ) - elif e not in self._edge_attrs: - raise XGIError(f"The edge ID {e} is not in the hypergraph") - - ddict = self._edge_attrs[e] - data = self._data - if data is False: - return dict() - elif data is True: - return ddict - return {data: ddict[data]} if data in ddict else {data: self._default} + return id in self._ids def __str__(self): - """Return a string of the edge IDs + """Returns a string of the list of IDs. Returns ------- string - A string of the edge IDs + the list of IDs. """ return str(list(self)) def __repr__(self): - """Returns a string representation of the edge data. + """Returns a summary of the class Returns ------- string - A string representation of the EdgeDataView - with the class name, edge IDs, and associated data. + The class name with the IDs. """ - name = self.__class__.__name__ - if self._data is False: - return f"{name}({tuple(self)})" - if self._data is True: - return f"{name}({dict(self)})" - return f"{name}({dict(self)}, data={self._data!r})" - - -# DegreeViews -class DegreeView: - """A View class for the degree of nodes in a Hypergraph + return f"{self.__class__.__name__}({tuple(self)})" - The functionality is like dict.items() with (node, degree) pairs. - Additional functionality includes read-only lookup of node degree, - and calling with optional features nbunch (for only a subset of nodes) - and weight (use edge weights to compute degree). +# ID Degree View Base Class +class IDDegreeView: + """A View class for the degree of IDs in a Hypergraph + The functionality is like dict.items() with (ID, degree) pairs. + Additional functionality includes read-only lookup of degree, + and calling with optional features nbunch (for only a subset of IDs) + and weight (use weights to compute degree). Notes ----- - DegreeView can still lookup any node even if nbunch is specified. + IDDegreeView can still lookup any ID even if nbunch is specified. """ - __slots__ = ("_hypergraph", "_nodes", "_edges", "_edge_attrs", "_weight") + __slots__ = ("_ids", "_id_attrs", "_weight") - def __init__(self, H, nbunch=None, weight=None): + def __init__(self, ids, id_attrs, id_bunch=None, weight=None): """Initialize the DegreeView object - Parameters ---------- - H : Hypergraph object - The hypergraph of interest - nbunch : node, container of nodes, or None meaning all nodes (default=None) - The nodes for which to find the degree - weight : hashable or bool, optional - The name of the attribute to weight the degree, by default None - """ - self._hypergraph = H - self._nodes = ( - H.nodes - if nbunch is None - else {id: val for id, val in H.nodes.items() if id in nbunch} + ids : dict + A dictionary with IDs as keys and a list of bipartite relations + as values + id_attrs : dict + A dictionary with IDs as keys and a dictionary of properties as values. + Used to weight the degree. + nbunch : ID, container of IDs, or None meaning all IDs (default=None) + The IDs for which to find the degree + weight : hashable, optional + The name of the attribute to weight the degree, by default None. + """ + self._ids = ( + ids + if id_bunch is None + else {id: val for id, val in ids.items() if id in id_bunch} ) - self._edges = H.edges - self._edge_attrs = H._edge_attr + self._id_attrs = id_attrs self._weight = weight - def __call__(self, nbunch=None, weight=None): - """Get the degree of specified nodes - + def __call__(self, id_bunch=None, weight=None): + """Get the degree of specified IDs Parameters ---------- - nbunch : node, container of nodes, or None, optional - The nodes for which to find the degree, by default None - weight : [type], optional - [description], by default None - + nbunch : ID, container of IDs, or None, optional + The IDs for which to find the degree, by default None + weight : hashable, optional + The name of the attribute to weight the degree, by default None Returns ------- DegreeView The degrees of the hypergraph """ - if nbunch is None: + if id_bunch is None: if weight == self._weight: return self - return self.__class__(self._hypergraph, None, weight) + return self.__class__(self._ids, self._id_attrs, None, weight) try: - if nbunch in self._nodes: + if id_bunch in self._ids: if weight == self._weight: - return self[nbunch] - return self.__class__(self._hypergraph, None, weight)[nbunch] + return self[id_bunch] + return self.__class__(self._ids, self._id_attrs, None, weight)[id_bunch] except TypeError: pass - return self.__class__(self._hypergraph, nbunch, weight) - - def __getitem__(self, n): - """Get the degree of a node + return self.__class__(self._ids, self._id_attrs, id_bunch, weight) + def __getitem__(self, id): + """Get the degree for an ID Parameters ---------- - n : hashable - node ID - + id : hashable + Unique ID Returns ------- float - The degree of a node, weighted or unweighted + The degree of an ID, weighted or unweighted """ weight = self._weight if weight is None: - return len(self._nodes(n)) - return sum(self._edge_attrs[dd].get(weight, 1) for dd in self._nodes(n)) + return len(self._ids(id)) + return sum(self._id_attrs[dd].get(weight, 1) for dd in self._ids(id)) def __iter__(self): - """Returns an iterator of node ID, node degree pairs. - + """Returns an iterator of ID, degree pairs. Yields ------- iterator of tuples - Each entry is a node ID, degree (Weighted or unweighted) pair. + Each entry is an ID, degree (Weighted or unweighted) pair. """ weight = self._weight if weight is None: - for n in self._nodes: - yield (n, len(self._nodes(n))) + for n in self._ids: + yield (n, len(self._ids[n])) else: - for n in self._nodes: - elements = self._nodes(n) - deg = sum(self._edge_attrs[dd].get(weight, 1) for dd in elements) + for n in self._ids: + elements = self._ids(n) + deg = sum(self._id_attrs[dd].get(weight, 1) for dd in elements) yield (n, deg) def __len__(self): - """Returns the number of nodes/degrees - + """Returns the number of IDs/degrees Returns ------- int - Number of nodes/degrees + Number of IDs/degrees """ - return len(self._nodes) + return len(self._ids) def __str__(self): - """Returns a string of node IDs. - + """Returns a string of IDs. Returns ------- string - A string of the list of node IDs. + A string of the list of IDs. """ - return str(list(self._nodes)) + return str(list(self._ids)) def __repr__(self): """A string representation of the degrees - Returns ------- string - A string representation of the DegreeView + A string representation of the IDDegreeView with the class name and a dictionary of - the node ID, degree pairs + the ID, degree pairs """ return f"{self.__class__.__name__}({dict(self)})" -class EdgeSizeView: - """A View class for the size of edges in a Hypergraph +class NodeView(IDView): + """Class for representing the nodes. - The functionality is like dict.items() with (edge, size) pairs. - Additional functionality includes read-only lookup of edge size, - and calling with optional features nbunch (for only a subset of edges) - and weight (use node weights to compute a weighted size). - - Notes - ----- - EdgeSizeView can still lookup any node even if nbunch is specified. + Much of the functionality in this class inherits from IDView """ - __slots__ = ("_hypergraph", "_edges", "_nodes", "_node_attrs", "_weight") - - def __init__(self, H, nbunch=None, weight=None): - """Initialize + def __init__(self, hypergraph): + super(NodeView, self).__init__(hypergraph._node, hypergraph._node_attr) - Parameters - ---------- - H : Hypergraph object - The hypergraph of interest - nbunch : node, container of nodes, or None meaning all nodes (default=None) - The edges for which to find the size, by default None - weight : bool or string, optional - Weight attribute, by default None - """ - self._hypergraph = H - self._edges = ( - H.edges - if nbunch is None - else {id: val for id, val in H.edges.items() if id in nbunch} - ) - self._nodes = H.nodes - self._node_attrs = H._node_attr - self._weight = weight + def memberships(self, n): + """Get the edges of which a node is a member. - def __call__(self, nbunch=None, weight=None): - """Get the degree of specified nodes + Given a node ID, this method returns the edge IDs + of which this node is a member. Parameters ---------- - nbunch : edge, container of edges, or None, optional - The edges for which to find the size, by default None - weight : hanshable or bool, optional - The weight attribute of the nodes, by default None + n : hashable + node ID Returns ------- - EdgeSizeView - The edge sizes of the hypergraph + list + edge members + + Raises + ------ + xgi.XGIError + Returns an error if the user tries passing in a slice or if + the node ID does not exist in the hypergraph. """ - if nbunch is None: - if weight == self._weight: - return self - return self.__class__(self._hypergraph, None, weight) try: - if nbunch in self._edges: - if weight == self._weight: - return self[nbunch] - return self.__class__(self._hypergraph, None, weight)[nbunch] + return self._ids[n] + except KeyError: + raise XGIError(f"The node ID {n} is not in the hypergraph") except TypeError: - pass - return self.__class__(self._hypergraph, nbunch, weight) + if isinstance(n, slice): + raise XGIError( + f"{type(self).__name__} does not support slicing, " + f"try list(H.nodes)[{n.start}:{n.stop}:{n.step}]" + ) + - def __getitem__(self, e): - """Get the degree of specified nodes +class EdgeView(IDView): + """Class for representing the edges. + + Much of the functionality in this class inherits from IDView + """ + + def __init__(self, hypergraph): + super(EdgeView, self).__init__(hypergraph._edge, hypergraph._edge_attr) + + def members(self, e): + """Get the nodes that are members of an edge. + + Given an edge ID, this method returns the node IDs + that are members of this edge. Parameters ---------- e : hashable - The edge id for which to find the size + edge ID Returns ------- - float - The edge size (weighted or uunweighted) + list + edge members + + Raises + ------ + xgi.XGIError + Returns an error if the user tries passing in a slice or if + the edge ID does not exist in the hypergraph. """ - weight = self._weight - if weight is None: - return len(self._edges(e)) - return sum(self._node_attrs[dd].get(weight, 1) for dd in self._edges(e)) + try: + return self._ids[e] + except KeyError: + raise XGIError(f"The edge ID {e} is not in the hypergraph") + except TypeError: + if isinstance(e, slice): + raise XGIError( + f"{type(self).__name__} does not support slicing, " + f"try list(H.edges)[{e.start}:{e.stop}:{e.step}]" + ) - def __iter__(self): - """Returns an iterator over edge ID, edge size pairs. - Yields - ------- - iterator of tuples - Each entry is an edge ID, edge size - (weighted or unweighted) pairs. - """ - weight = self._weight - if weight is None: - for e in self._edges: - yield (e, len(self._edges(e))) - else: - for e in self._edges: - elements = self._edges(e) - deg = sum(self._node_attrs[dd].get(weight, 1) for dd in elements) - yield (e, deg) +class DegreeView(IDDegreeView): + """Class for representing the degrees. - def __len__(self): - """Returns the number of edges/edge sizes + This class inherits all its functionality from IDDegreeView + """ - Returns - ------- - int - The number of edges/edge sizes - """ - return len(self._edges) + def __init__(self, hypergraph, nbunch=None, weight=None): + super().__init__( + hypergraph._node, hypergraph._edge_attr, id_bunch=nbunch, weight=weight + ) - def __str__(self): - """Returns a string of the edge IDs. - Returns - ------- - string - A string of the list of edge IDs. - """ - return str(list(self._edges)) +class EdgeSizeView(IDDegreeView): + """Class for representing the edge sizes. - def __repr__(self): - """A string representation of the EdgeSizeView class. + This class inherits all its functionality from IDDegreeView + """ - Returns - ------- - string - A string representing the EdgeSizeSize class with the - class name and a dictionary with edge IDs as keys and - edge sizes as values. - """ - return f"{self.__class__.__name__}({dict(self)})" + def __init__(self, hypergraph, ebunch=None, weight=None): + super().__init__( + hypergraph._edge, hypergraph._node_attr, id_bunch=ebunch, weight=weight + ) diff --git a/xgi/generators/nonuniform.py b/xgi/generators/nonuniform.py index 3a7c2f97d..a98ecb78d 100644 --- a/xgi/generators/nonuniform.py +++ b/xgi/generators/nonuniform.py @@ -51,12 +51,12 @@ def erdos_renyi_hypergraph(n, m, p): if p < 0.0 or p > 1.0: raise ValueError("Invalid p value.") - + if p == 0.0: H = xgi.empty_hypergraph() H.add_nodes_from(range(n)) return H - + # this corresponds to a completely filled incidence matrix, # not a complete hypergraph. if p == 1.0: diff --git a/xgi/linalg/matrix.py b/xgi/linalg/matrix.py index ee2b33321..63114a431 100644 --- a/xgi/linalg/matrix.py +++ b/xgi/linalg/matrix.py @@ -63,7 +63,7 @@ def incidence_matrix(H, sparse=True, index=False, weight=lambda node, edge, H: 1 cols = list() data = list() for edge in H.edges: - members = H.edges[edge] + members = H.edges.members(edge) for node in members: data.append(weight(node, edge, H)) rows.append(node_dict[node]) @@ -73,7 +73,7 @@ def incidence_matrix(H, sparse=True, index=False, weight=lambda node, edge, H: 1 # Create an np.matrix I = np.zeros((num_nodes, num_edges), dtype=int) for edge in H.edges: - members = H.edges[edge] + members = H.edges.members(edge) for node in members: I[node_dict[node], edge_dict[edge]] = weight(node, edge, H) if index: diff --git a/xgi/readwrite/bipartite.py b/xgi/readwrite/bipartite.py index e63998825..5b286c5d2 100644 --- a/xgi/readwrite/bipartite.py +++ b/xgi/readwrite/bipartite.py @@ -24,7 +24,7 @@ def generate_bipartite_edgelist(H, delimiter=" "): Each entry is a line to be written to the output file. """ for id in H.edges: - for node in H.edges[id]: + for node in H.edges.members(id): yield delimiter.join(map(str, [node, id])) diff --git a/xgi/readwrite/edgelist.py b/xgi/readwrite/edgelist.py index 419173df2..ee65e0663 100644 --- a/xgi/readwrite/edgelist.py +++ b/xgi/readwrite/edgelist.py @@ -40,15 +40,15 @@ def generate_edgelist(H, delimiter=" ", data=True): """ if data is True: for id in H.edges: - e = *H.edges[id], dict(H._edge_attr[id]) + e = *H.edges.members(id), dict(H._edge_attr[id]) yield delimiter.join(map(str, e)) elif data is False: for id in H.edges: - e = H.edges[id] + e = H.edges.members(id) yield delimiter.join(map(str, e)) else: for id in H.edges: - e = H.edges[id] + e = H.edges.members(id) try: e.extend([H._edge_attr[id][k] for k in data]) except KeyError: diff --git a/xgi/readwrite/json.py b/xgi/readwrite/json.py index 348d3175a..2ed1dc058 100644 --- a/xgi/readwrite/json.py +++ b/xgi/readwrite/json.py @@ -42,7 +42,7 @@ def write_hypergraph_json(H, path): data["hyperedge-data"] = {id: H._edge_attr[id] for id in H.edges} # hyperedge list - data["hyperedges"] = {id: tuple(H.edges[id]) for id in H.edges} + data["hyperedges"] = {id: tuple(H.edges.members(id)) for id in H.edges} datastring = json.dumps(data)