From d7564c46af4c2f9de70856f94eeb9d21029c44fd Mon Sep 17 00:00:00 2001 From: nwlandry Date: Mon, 1 Aug 2022 09:03:51 -0400 Subject: [PATCH 01/16] updated README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 69e43944b..a56022031 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,8 @@ The XGI package has been supported by NSF Grant 2121905, ["HNDS-I: Using Hypergr ## Other resources This library may not meet your needs and if this is this case, consider checking out these other resources: * [HyperNetX](https://pnnl.github.io/HyperNetX): A package in Python for representing, analyzing, and visualizing hypergraphs. +* [Reticula](https://docs.reticula.network/): A package with a Python wrapper of C++ functions for representing, analyzing, and visualizing temporal and static graphs and hypergraphs. * [SimpleHypergraphs.jl](https://pszufe.github.io/SimpleHypergraphs.jl/v0.1/): A package in Julia for representing, analyzing, and generating hypergraphs. +* [HyperGraphs.jl](https://github.com/lpmdiaz/HyperGraphs.jl): A package in Julia for representing, analyzing, and generating hypergraphs which may be oriented and weighted. * [hyperG](https://cran.r-project.org/package=HyperG): A package in R for storing and analyzing hypergraphs * [NetworkX](https://networkx.org/): A package in Python for representing, analyzing, and visualizing networks. From 724d2c01f04e30ca5c2f06b4126c208dbfbc3de4 Mon Sep 17 00:00:00 2001 From: nwlandry Date: Mon, 1 Aug 2022 09:48:37 -0400 Subject: [PATCH 02/16] added json tests --- tests/readwrite/test_hypergraph_json.py | 114 ----------- tests/readwrite/test_json.py | 253 ++++++++++++++++++++++++ xgi/readwrite/json.py | 8 +- 3 files changed, 257 insertions(+), 118 deletions(-) delete mode 100644 tests/readwrite/test_hypergraph_json.py create mode 100644 tests/readwrite/test_json.py diff --git a/tests/readwrite/test_hypergraph_json.py b/tests/readwrite/test_hypergraph_json.py deleted file mode 100644 index 3ed11ac40..000000000 --- a/tests/readwrite/test_hypergraph_json.py +++ /dev/null @@ -1,114 +0,0 @@ -import tempfile - -import xgi - -json_string = """ -{ - "hypergraph-data": { - "name": "test", - "author": "Nicholas Landry" - }, - "node-data": { - "1": { - "color": "blue" - }, - "2": { - "color": "yellow" - }, - "3": { - "color": "cyan" - }, - "4": { - "color": "green" - } - }, - "edge-data": { - "edge1": { - "weight": 2 - }, - "edge2": { - "weight": 4 - }, - "edge3": { - "weight": -1 - } - }, - "edge-dict": { - "edge1": [ - "1", - "2" - ], - "edge2": [ - "2", - "3", - "4" - ], - "edge3": [ - "1", - "4" - ] - } - } -""" - - -def test_read_hypergraph_json(): - _, filename = tempfile.mkstemp() - with open(filename, "w") as file: - file.write(json_string) - - H1 = xgi.read_hypergraph_json(filename, nodetype=int) - H2 = xgi.read_hypergraph_json(filename) - - assert list(H1.nodes) == [1, 2, 3, 4] - assert list(H1.edges) == ["edge1", "edge2", "edge3"] - - assert list(H2.nodes) == ["1", "2", "3", "4"] - assert H1["name"] == "test" - assert H1["author"] == "Nicholas Landry" - assert [H1.edges.members(id) for id in H1.edges] == [[1, 2], [2, 3, 4], [1, 4]] - assert [H2.edges.members(id) for id in H2.edges] == [ - ["1", "2"], - ["2", "3", "4"], - ["1", "4"], - ] - - assert H1.nodes[1]["color"] == "blue" - assert H1.edges["edge2"]["weight"] == 4 - - -def test_write_hypergraph_json(edgelist1): - _, filename = tempfile.mkstemp() - H1 = xgi.Hypergraph(edgelist1) - - H1["name"] = "test" - H1["author"] = "Nicholas Landry" - - node_attr_dict = { - 1: {"name": "Leonie"}, - 2: {"name": "Ilya"}, - 3: {"name": "Alice"}, - 4: {"name": "Giovanni"}, - } - xgi.set_node_attributes(H1, node_attr_dict) - - edge_attr_dict = { - 0: {"weight": 1}, - 1: {"weight": 2}, - 2: {"weight": 3}, - 3: {"weight": -1}, - } - xgi.set_edge_attributes(H1, edge_attr_dict) - - xgi.write_hypergraph_json(H1, filename) - - H2 = xgi.read_hypergraph_json(filename, nodetype=int, edgetype=int) - - assert H1.nodes == H2.nodes - assert H1.edges == H2.edges - assert [H1.edges.members(id) for id in H1.edges] == [ - H2.edges.members(id) for id in H2.edges - ] - assert H2.nodes[2] == {"name": "Ilya"} - assert H2.edges[1] == {"weight": 2} - assert H2["name"] == "test" diff --git a/tests/readwrite/test_json.py b/tests/readwrite/test_json.py new file mode 100644 index 000000000..09b7ed97a --- /dev/null +++ b/tests/readwrite/test_json.py @@ -0,0 +1,253 @@ +import tempfile + +import pytest + +import xgi +from xgi.exception import XGIError + +json_string1 = """ +{ + "hypergraph-data": { + "name": "test", + "author": "Nicholas Landry" + }, + "node-data": { + "1": { + "color": "blue" + }, + "2": { + "color": "yellow" + }, + "3": { + "color": "cyan" + }, + "4": { + "color": "green" + } + }, + "edge-data": { + "edge1": { + "weight": 2 + }, + "edge2": { + "weight": 4 + }, + "edge3": { + "weight": -1 + } + }, + "edge-dict": { + "edge1": [ + "1", + "2" + ], + "edge2": [ + "2", + "3", + "4" + ], + "edge3": [ + "1", + "4" + ] + } +} +""" + +json_string2 = """ +{ + "node-data": { + "1": { + "color": "blue" + }, + "2": { + "color": "yellow" + } + }, + "edge-data": { + "edge1": { + "weight": 2 + } + }, + "edge-dict": { + "edge1": [ + "1", + "2" + ] + } +} +""" + +json_string3 = """ +{ + "hypergraph-data": { + "name": "test", + "author": "Nicholas Landry" + } +} +""" + +json_string4 = """ +{ + "hypergraph-data": { + "name": "test", + "author": "Nicholas Landry" + }, + "node-data": { + "test": { + "color": "blue" + } + } +} +""" + +json_string5 = """ +{ + "hypergraph-data": { + "name": "test", + "author": "Nicholas Landry" + }, + "node-data": { + "1": { + "color": "blue" + }, + "2": { + "color": "yellow" + } + } +} +""" + +json_string6 = """ +{ + "hypergraph-data": { + "name": "test", + "author": "Nicholas Landry" + }, + "node-data": { + "1": { + "color": "blue" + }, + "2": { + "color": "yellow" + } + }, + "edge-dict": { + "edge1": [ + "1", + "2" + ] + } +} +""" + + +def test_read_json(): + # Test a correctly formatted file + _, filename = tempfile.mkstemp() + with open(filename, "w") as file: + file.write(json_string1) + + H1 = xgi.read_hypergraph_json(filename, nodetype=int) + H2 = xgi.read_hypergraph_json(filename) + + assert list(H1.nodes) == [1, 2, 3, 4] + assert list(H1.edges) == ["edge1", "edge2", "edge3"] + + assert list(H2.nodes) == ["1", "2", "3", "4"] + assert H1["name"] == "test" + assert H1["author"] == "Nicholas Landry" + assert [H1.edges.members(id) for id in H1.edges] == [[1, 2], [2, 3, 4], [1, 4]] + assert [H2.edges.members(id) for id in H2.edges] == [ + ["1", "2"], + ["2", "3", "4"], + ["1", "4"], + ] + + assert H1.nodes[1]["color"] == "blue" + assert H1.edges["edge2"]["weight"] == 4 + + # Test missing header + with pytest.raises(XGIError): + _, filename = tempfile.mkstemp() + with open(filename, "w") as file: + file.write(json_string2) + + xgi.read_hypergraph_json(filename) + + # Test missing node-data + with pytest.raises(XGIError): + _, filename = tempfile.mkstemp() + with open(filename, "w") as file: + file.write(json_string2) + + xgi.read_hypergraph_json(filename) + + # Test failed node type conversion + with pytest.raises(TypeError): + _, filename = tempfile.mkstemp() + with open(filename, "w") as file: + file.write(json_string4) + + xgi.read_hypergraph_json(filename, nodetype=int) + + # Test missing edge dict + with pytest.raises(XGIError): + _, filename = tempfile.mkstemp() + with open(filename, "w") as file: + file.write(json_string5) + + xgi.read_hypergraph_json(filename) + + # Test missing edge-data + with pytest.raises(XGIError): + _, filename = tempfile.mkstemp() + with open(filename, "w") as file: + file.write(json_string6) + + xgi.read_hypergraph_json(filename) + + # Test failed edge type conversion + with pytest.raises(TypeError): + _, filename = tempfile.mkstemp() + with open(filename, "w") as file: + file.write(json_string1) + + xgi.read_hypergraph_json(filename, edgetype=int) + + +def test_write_json(edgelist1): + _, filename = tempfile.mkstemp() + H1 = xgi.Hypergraph(edgelist1) + + H1["name"] = "test" + H1["author"] = "Nicholas Landry" + + node_attr_dict = { + 1: {"name": "Leonie"}, + 2: {"name": "Ilya"}, + 3: {"name": "Alice"}, + 4: {"name": "Giovanni"}, + } + xgi.set_node_attributes(H1, node_attr_dict) + + edge_attr_dict = { + 0: {"weight": 1}, + 1: {"weight": 2}, + 2: {"weight": 3}, + 3: {"weight": -1}, + } + xgi.set_edge_attributes(H1, edge_attr_dict) + + xgi.write_hypergraph_json(H1, filename) + + H2 = xgi.read_hypergraph_json(filename, nodetype=int, edgetype=int) + + assert H1.nodes == H2.nodes + assert H1.edges == H2.edges + assert [H1.edges.members(id) for id in H1.edges] == [ + H2.edges.members(id) for id in H2.edges + ] + assert H2.nodes[2] == {"name": "Ilya"} + assert H2.edges[1] == {"weight": 2} + assert H2["name"] == "test" diff --git a/xgi/readwrite/json.py b/xgi/readwrite/json.py index 699d1c918..599fb7115 100644 --- a/xgi/readwrite/json.py +++ b/xgi/readwrite/json.py @@ -93,11 +93,11 @@ def read_hypergraph_json(path, nodetype=None, edgetype=None): if edgetype is not None: try: id = edgetype(id) - except Exception as e: + except Exception: raise TypeError( f"Failed to convert the edge with ID {id} to type {edgetype}." - ) from e - + ) + # convert the members of the edge to the nodetype if specified. if nodetype is not None: try: edge = [nodetype(n) for n in edge] @@ -106,7 +106,7 @@ def read_hypergraph_json(path, nodetype=None, edgetype=None): f"Failed to convert nodes to type {nodetype}." ) from e H.add_edge(edge, id) - except: + except KeyError: raise XGIError("Failed to import edge dictionary.") try: From 5a17bdd65b947d20b16201c958347a2a7106ab55 Mon Sep 17 00:00:00 2001 From: nwlandry Date: Mon, 1 Aug 2022 16:34:22 -0400 Subject: [PATCH 03/16] breaking change!: changed `read_hypergraph_json` to `read_json` --- .../api/readwrite/xgi.readwrite.json.rst | 2 +- tests/readwrite/test_json.py | 18 +++++++++--------- tutorials/Tutorial 2 - Read and Write.ipynb | 17 ++++++++++------- xgi/convert.py | 2 +- xgi/readwrite/json.py | 4 ++-- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/docs/source/api/readwrite/xgi.readwrite.json.rst b/docs/source/api/readwrite/xgi.readwrite.json.rst index 563326246..ac1d8c82a 100644 --- a/docs/source/api/readwrite/xgi.readwrite.json.rst +++ b/docs/source/api/readwrite/xgi.readwrite.json.rst @@ -7,7 +7,7 @@ xgi.readwrite.json .. rubric:: Functions - .. autofunction:: read_hypergraph_json + .. autofunction:: read_json .. autofunction:: write_hypergraph_json diff --git a/tests/readwrite/test_json.py b/tests/readwrite/test_json.py index 09b7ed97a..817b5ce80 100644 --- a/tests/readwrite/test_json.py +++ b/tests/readwrite/test_json.py @@ -148,8 +148,8 @@ def test_read_json(): with open(filename, "w") as file: file.write(json_string1) - H1 = xgi.read_hypergraph_json(filename, nodetype=int) - H2 = xgi.read_hypergraph_json(filename) + H1 = xgi.read_json(filename, nodetype=int) + H2 = xgi.read_json(filename) assert list(H1.nodes) == [1, 2, 3, 4] assert list(H1.edges) == ["edge1", "edge2", "edge3"] @@ -173,7 +173,7 @@ def test_read_json(): with open(filename, "w") as file: file.write(json_string2) - xgi.read_hypergraph_json(filename) + xgi.read_json(filename) # Test missing node-data with pytest.raises(XGIError): @@ -181,7 +181,7 @@ def test_read_json(): with open(filename, "w") as file: file.write(json_string2) - xgi.read_hypergraph_json(filename) + xgi.read_json(filename) # Test failed node type conversion with pytest.raises(TypeError): @@ -189,7 +189,7 @@ def test_read_json(): with open(filename, "w") as file: file.write(json_string4) - xgi.read_hypergraph_json(filename, nodetype=int) + xgi.read_json(filename, nodetype=int) # Test missing edge dict with pytest.raises(XGIError): @@ -197,7 +197,7 @@ def test_read_json(): with open(filename, "w") as file: file.write(json_string5) - xgi.read_hypergraph_json(filename) + xgi.read_json(filename) # Test missing edge-data with pytest.raises(XGIError): @@ -205,7 +205,7 @@ def test_read_json(): with open(filename, "w") as file: file.write(json_string6) - xgi.read_hypergraph_json(filename) + xgi.read_json(filename) # Test failed edge type conversion with pytest.raises(TypeError): @@ -213,7 +213,7 @@ def test_read_json(): with open(filename, "w") as file: file.write(json_string1) - xgi.read_hypergraph_json(filename, edgetype=int) + xgi.read_json(filename, edgetype=int) def test_write_json(edgelist1): @@ -241,7 +241,7 @@ def test_write_json(edgelist1): xgi.write_hypergraph_json(H1, filename) - H2 = xgi.read_hypergraph_json(filename, nodetype=int, edgetype=int) + H2 = xgi.read_json(filename, nodetype=int, edgetype=int) assert H1.nodes == H2.nodes assert H1.edges == H2.edges diff --git a/tutorials/Tutorial 2 - Read and Write.ipynb b/tutorials/Tutorial 2 - Read and Write.ipynb index 6f87690ad..8249a2977 100644 --- a/tutorials/Tutorial 2 - Read and Write.ipynb +++ b/tutorials/Tutorial 2 - Read and Write.ipynb @@ -66,7 +66,7 @@ "# Write the example hypergraph to a JSON file\n", "xgi.write_hypergraph_json(H,\"hypergraph_json.json\")\n", "# Load the file just written and store it in a new hypergraph\n", - "H_json = xgi.read_hypergraph_json(\"hypergraph_json.json\")" + "H_json = xgi.read_json(\"hypergraph_json.json\")" ] }, { @@ -113,11 +113,9 @@ } ], "metadata": { - "interpreter": { - "hash": "e8c90be8a507c947d600755d98ec41f3a8064ae1d46b0505dd1a3ad2a7800759" - }, "kernelspec": { - "display_name": "Python 3.9.7 64-bit (conda)", + "display_name": "Python 3.10.4 ('hypergraph')", + "language": "python", "name": "python3" }, "language_info": { @@ -130,9 +128,14 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.7" + "version": "3.10.4" }, - "orig_nbformat": 4 + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "fdeb83b6e5b2333358b6ba79181fac315f1a722b4574d7079c134c9ae27f7c53" + } + } }, "nbformat": 4, "nbformat_minor": 2 diff --git a/xgi/convert.py b/xgi/convert.py index 60e475ee0..f84a2d1f9 100644 --- a/xgi/convert.py +++ b/xgi/convert.py @@ -572,7 +572,7 @@ def dict_to_hypergraph(hypergraph_dict, nodetype=None, edgetype=None): See Also -------- - read_hypergraph_json + read_json """ H = empty_hypergraph() diff --git a/xgi/readwrite/json.py b/xgi/readwrite/json.py index 599fb7115..24627461e 100644 --- a/xgi/readwrite/json.py +++ b/xgi/readwrite/json.py @@ -5,7 +5,7 @@ from ..exception import XGIError from ..generators import empty_hypergraph -__all__ = ["write_hypergraph_json", "read_hypergraph_json"] +__all__ = ["write_hypergraph_json", "read_json"] def write_hypergraph_json(H, path): @@ -42,7 +42,7 @@ def write_hypergraph_json(H, path): output_file.write(datastring) -def read_hypergraph_json(path, nodetype=None, edgetype=None): +def read_json(path, nodetype=None, edgetype=None): """ A function to read a file in a standardized JSON format. From ef03320173daa507d3bcb05bee664d5338be9fba Mon Sep 17 00:00:00 2001 From: nwlandry Date: Mon, 1 Aug 2022 16:35:53 -0400 Subject: [PATCH 04/16] breaking change!: renamed `write_hypergraph_json` to `write_json` --- docs/source/api/readwrite/xgi.readwrite.json.rst | 2 +- tests/readwrite/test_json.py | 2 +- tutorials/Tutorial 2 - Read and Write.ipynb | 2 +- xgi/readwrite/json.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/source/api/readwrite/xgi.readwrite.json.rst b/docs/source/api/readwrite/xgi.readwrite.json.rst index ac1d8c82a..744648526 100644 --- a/docs/source/api/readwrite/xgi.readwrite.json.rst +++ b/docs/source/api/readwrite/xgi.readwrite.json.rst @@ -8,7 +8,7 @@ xgi.readwrite.json .. rubric:: Functions .. autofunction:: read_json - .. autofunction:: write_hypergraph_json + .. autofunction:: write_json diff --git a/tests/readwrite/test_json.py b/tests/readwrite/test_json.py index 817b5ce80..3ec9d5c51 100644 --- a/tests/readwrite/test_json.py +++ b/tests/readwrite/test_json.py @@ -239,7 +239,7 @@ def test_write_json(edgelist1): } xgi.set_edge_attributes(H1, edge_attr_dict) - xgi.write_hypergraph_json(H1, filename) + xgi.write_json(H1, filename) H2 = xgi.read_json(filename, nodetype=int, edgetype=int) diff --git a/tutorials/Tutorial 2 - Read and Write.ipynb b/tutorials/Tutorial 2 - Read and Write.ipynb index 8249a2977..eeb4ad39b 100644 --- a/tutorials/Tutorial 2 - Read and Write.ipynb +++ b/tutorials/Tutorial 2 - Read and Write.ipynb @@ -64,7 +64,7 @@ "outputs": [], "source": [ "# Write the example hypergraph to a JSON file\n", - "xgi.write_hypergraph_json(H,\"hypergraph_json.json\")\n", + "xgi.write_json(H,\"hypergraph_json.json\")\n", "# Load the file just written and store it in a new hypergraph\n", "H_json = xgi.read_json(\"hypergraph_json.json\")" ] diff --git a/xgi/readwrite/json.py b/xgi/readwrite/json.py index 24627461e..e8dbeff1f 100644 --- a/xgi/readwrite/json.py +++ b/xgi/readwrite/json.py @@ -5,10 +5,10 @@ from ..exception import XGIError from ..generators import empty_hypergraph -__all__ = ["write_hypergraph_json", "read_json"] +__all__ = ["write_json", "read_json"] -def write_hypergraph_json(H, path): +def write_json(H, path): """ A function to write a file in a standardized JSON format. From 7265a89ad54258584179be34da7a22265428d312 Mon Sep 17 00:00:00 2001 From: nwlandry Date: Mon, 1 Aug 2022 17:03:16 -0400 Subject: [PATCH 05/16] style: format with black --- tests/readwrite/test_json.py | 54 ++++++++++++++++++------------------ xgi/drawing/xgi_pylab.py | 28 +++++-------------- xgi/readwrite/json.py | 14 ++++------ 3 files changed, 39 insertions(+), 57 deletions(-) diff --git a/tests/readwrite/test_json.py b/tests/readwrite/test_json.py index 3ec9d5c51..8b64afea5 100644 --- a/tests/readwrite/test_json.py +++ b/tests/readwrite/test_json.py @@ -169,51 +169,51 @@ def test_read_json(): # Test missing header with pytest.raises(XGIError): - _, filename = tempfile.mkstemp() - with open(filename, "w") as file: - file.write(json_string2) + _, filename = tempfile.mkstemp() + with open(filename, "w") as file: + file.write(json_string2) - xgi.read_json(filename) + xgi.read_json(filename) # Test missing node-data with pytest.raises(XGIError): - _, filename = tempfile.mkstemp() - with open(filename, "w") as file: - file.write(json_string2) + _, filename = tempfile.mkstemp() + with open(filename, "w") as file: + file.write(json_string3) + + xgi.read_json(filename) - xgi.read_json(filename) - # Test failed node type conversion with pytest.raises(TypeError): - _, filename = tempfile.mkstemp() - with open(filename, "w") as file: - file.write(json_string4) + _, filename = tempfile.mkstemp() + with open(filename, "w") as file: + file.write(json_string4) - xgi.read_json(filename, nodetype=int) + xgi.read_json(filename, nodetype=int) # Test missing edge dict with pytest.raises(XGIError): - _, filename = tempfile.mkstemp() - with open(filename, "w") as file: - file.write(json_string5) + _, filename = tempfile.mkstemp() + with open(filename, "w") as file: + file.write(json_string5) + + xgi.read_json(filename) - xgi.read_json(filename) - # Test missing edge-data with pytest.raises(XGIError): - _, filename = tempfile.mkstemp() - with open(filename, "w") as file: - file.write(json_string6) + _, filename = tempfile.mkstemp() + with open(filename, "w") as file: + file.write(json_string6) + + xgi.read_json(filename) - xgi.read_json(filename) - # Test failed edge type conversion with pytest.raises(TypeError): - _, filename = tempfile.mkstemp() - with open(filename, "w") as file: - file.write(json_string1) + _, filename = tempfile.mkstemp() + with open(filename, "w") as file: + file.write(json_string1) - xgi.read_json(filename, edgetype=int) + xgi.read_json(filename, edgetype=int) def test_write_json(edgelist1): diff --git a/xgi/drawing/xgi_pylab.py b/xgi/drawing/xgi_pylab.py index 7ba757b50..6cade17c0 100644 --- a/xgi/drawing/xgi_pylab.py +++ b/xgi/drawing/xgi_pylab.py @@ -133,9 +133,7 @@ def draw( if isinstance(H, SimplicialComplex): draw_xgi_simplices(H, pos, ax, dyad_color, dyad_lw, edge_fc, settings) elif isinstance(H, Hypergraph): - draw_xgi_hyperedges( - H, pos, ax, dyad_color, dyad_lw, edge_fc, d_max, settings - ) + draw_xgi_hyperedges(H, pos, ax, dyad_color, dyad_lw, edge_fc, d_max, settings) else: raise XGIError("The input must be a SimplicialComplex or Hypergraph") @@ -193,12 +191,8 @@ def draw_xgi_nodes( * max_node_size """ # Note Iterable covers lists, tuples, ranges, generators, np.ndarrays, etc - node_fc = _color_arg_to_dict( - node_fc, H.nodes, settings["node_fc_cmap"] - ) - node_ec = _color_arg_to_dict( - node_ec, H.nodes, settings["node_lc_cmap"] - ) + node_fc = _color_arg_to_dict(node_fc, H.nodes, settings["node_fc_cmap"]) + node_ec = _color_arg_to_dict(node_ec, H.nodes, settings["node_lc_cmap"]) node_lw = _scalar_arg_to_dict( node_lw, H.nodes, @@ -222,9 +216,7 @@ def draw_xgi_nodes( ) -def draw_xgi_hyperedges( - H, pos, ax, dyad_color, dyad_lw, edge_fc, d_max, settings -): +def draw_xgi_hyperedges(H, pos, ax, dyad_color, dyad_lw, edge_fc, d_max, settings): """Draw hyperedges. Parameters @@ -258,9 +250,7 @@ def draw_xgi_hyperedges( dyad_lw, H.edges, settings["min_dyad_lw"], settings["max_dyad_lw"] ) - edge_fc = _color_arg_to_dict( - edge_fc, H.edges, settings["edge_fc_cmap"] - ) + edge_fc = _color_arg_to_dict(edge_fc, H.edges, settings["edge_fc_cmap"]) # Looping over the hyperedges of different order (reversed) -- nodes will be plotted separately for id, he in H.edges.members(dtype=dict).items(): @@ -334,9 +324,7 @@ def draw_xgi_simplices(SC, pos, ax, dyad_color, dyad_lw, edge_fc, settings): settings["max_dyad_lw"], ) - edge_fc = _color_arg_to_dict( - edge_fc, H_.edges, settings["edge_fc_cmap"] - ) + edge_fc = _color_arg_to_dict(edge_fc, H_.edges, settings["edge_fc_cmap"]) # Looping over the hyperedges of different order (reversed) -- nodes will be plotted separately for id, he in H_.edges.members(dtype=dict).items(): d = len(he) - 1 @@ -346,9 +334,7 @@ def draw_xgi_simplices(SC, pos, ax, dyad_color, dyad_lw, edge_fc, settings): x_coords = [pos[he[0]][0], pos[he[1]][0]] y_coords = [pos[he[0]][1], pos[he[1]][1]] - line = plt.Line2D( - x_coords, y_coords, color=dyad_color[id], lw=dyad_lw[id] - ) + line = plt.Line2D(x_coords, y_coords, color=dyad_color[id], lw=dyad_lw[id]) ax.add_line(line) else: # Hyperedges of order d (d=1: links, etc.) diff --git a/xgi/readwrite/json.py b/xgi/readwrite/json.py index e8dbeff1f..c980c4596 100644 --- a/xgi/readwrite/json.py +++ b/xgi/readwrite/json.py @@ -80,10 +80,8 @@ def read_json(path, nodetype=None, edgetype=None): if nodetype is not None: try: id = nodetype(id) - except Exception as e: - raise TypeError( - f"Failed to convert edge IDs to type {nodetype}." - ) from e + except ValueError: + raise TypeError(f"Failed to convert edge IDs to type {nodetype}.") H.add_node(id, **dd) except KeyError: raise XGIError("Failed to import node attributes.") @@ -93,7 +91,7 @@ def read_json(path, nodetype=None, edgetype=None): if edgetype is not None: try: id = edgetype(id) - except Exception: + except ValueError: raise TypeError( f"Failed to convert the edge with ID {id} to type {edgetype}." ) @@ -101,10 +99,8 @@ def read_json(path, nodetype=None, edgetype=None): if nodetype is not None: try: edge = [nodetype(n) for n in edge] - except Exception as e: - raise TypeError( - f"Failed to convert nodes to type {nodetype}." - ) from e + except ValueError: + raise TypeError(f"Failed to convert nodes to type {nodetype}.") H.add_edge(edge, id) except KeyError: raise XGIError("Failed to import edge dictionary.") From 65155d91667d939e2b9751527fbbcbf61cd88d23 Mon Sep 17 00:00:00 2001 From: nwlandry Date: Mon, 1 Aug 2022 17:07:16 -0400 Subject: [PATCH 06/16] add xgi_data test --- tests/readwrite/{test_load_xgi_data.py => test_xgi_data.py} | 4 ++++ 1 file changed, 4 insertions(+) rename tests/readwrite/{test_load_xgi_data.py => test_xgi_data.py} (78%) diff --git a/tests/readwrite/test_load_xgi_data.py b/tests/readwrite/test_xgi_data.py similarity index 78% rename from tests/readwrite/test_load_xgi_data.py rename to tests/readwrite/test_xgi_data.py index f3c52bf4d..1805b6a88 100644 --- a/tests/readwrite/test_load_xgi_data.py +++ b/tests/readwrite/test_xgi_data.py @@ -1,6 +1,7 @@ import pytest from xgi import load_xgi_data +from xgi.exception import XGIError @pytest.mark.webtest @@ -12,3 +13,6 @@ def test_load_xgi_data(): assert H["name"] == "email-Enron" assert H.nodes["4"]["name"] == "robert.badeer@enron.com" assert H.edges["0"]["timestamp"] == "2000-01-11T10:29:00" + + with pytest.raises(XGIError): + load_xgi_data("test") From e99627073c0dc3273d4133e07af79d4c5a23eaca Mon Sep 17 00:00:00 2001 From: nwlandry Date: Mon, 1 Aug 2022 17:15:59 -0400 Subject: [PATCH 07/16] added edgelist unit test --- tests/readwrite/test_edgelist.py | 2 ++ xgi/readwrite/edgelist.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/readwrite/test_edgelist.py b/tests/readwrite/test_edgelist.py index 5322e4aae..be6feb211 100644 --- a/tests/readwrite/test_edgelist.py +++ b/tests/readwrite/test_edgelist.py @@ -50,6 +50,8 @@ def test_parse_edgelist(): [1, 4, 7, 8], [2, 3], ] + with pytest.raises(TypeError): + xgi.parse_edgelist(["test 2", "2 3 4", "test 4 7 8", "2 3"], nodetype=int) def test_write_edgelist(edgelist1): diff --git a/xgi/readwrite/edgelist.py b/xgi/readwrite/edgelist.py index 3848d7ff0..baa43fe22 100644 --- a/xgi/readwrite/edgelist.py +++ b/xgi/readwrite/edgelist.py @@ -150,8 +150,8 @@ def parse_edgelist( if nodetype is not None: try: edge = [nodetype(node) for node in edge] - except Exception as e: - raise TypeError(f"Failed to convert nodes to type {nodetype}.") from e + except ValueError: + raise TypeError(f"Failed to convert nodes to type {nodetype}.") H.add_edge(edge) return H From 7a42e2dbaaa2dcd9fe6bea0dca87cca2000b8e59 Mon Sep 17 00:00:00 2001 From: nwlandry Date: Mon, 1 Aug 2022 17:23:19 -0400 Subject: [PATCH 08/16] add bipartite unit tests --- tests/readwrite/test_bipartite_edgelist.py | 17 +++++++++++++++++ xgi/readwrite/bipartite.py | 8 ++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/tests/readwrite/test_bipartite_edgelist.py b/tests/readwrite/test_bipartite_edgelist.py index c4cae0e15..b45ec71f7 100644 --- a/tests/readwrite/test_bipartite_edgelist.py +++ b/tests/readwrite/test_bipartite_edgelist.py @@ -1,8 +1,10 @@ import tempfile +from typing import Type import pytest import xgi +from xgi.exception import XGIError bipartite_edgelist_spaces_string = """0 0 # Comment @@ -56,6 +58,10 @@ def test_read_bipartite_edgelist(file_string, extra_kwargs): def test_parse_bipartite_edgelist(): lines = ["0 0", "1 0", "2 0", "3 0", "4 1", "5 2", "6 2", "6 3", "7 3", "8 3"] + bad_lines1 = ["0", "1 0", "2 0", "3 0", "4 1", "5 2", "6 2", "6 3", "7 3", "8 3"] + bad_lines2 = ["test 0", "1 0", "2 0", "3 0", "4 1", "5 test", "6 test", "6 3", "7 3", "8 3"] + bad_lines3 = ["0 0", "1 0", "2 0", "3 0", "4 1", "5 test", "6 test", "6 3", "7 3", "8 3"] + H = xgi.parse_bipartite_edgelist(lines, nodetype=int) assert list(H.nodes) == [0, 1, 2, 3, 4, 5, 6, 7, 8] assert list(H.edges) == ["0", "1", "2", "3"] @@ -88,7 +94,18 @@ def test_parse_bipartite_edgelist(): [3], [3], ] + + # test less than two entries per line + with pytest.raises(XGIError): + xgi.parse_bipartite_edgelist(bad_lines1) + # test failed nodetype conversion + with pytest.raises(TypeError): + xgi.parse_bipartite_edgelist(bad_lines2, nodetype=int) + + # test failed edgetype conversion + with pytest.raises(TypeError): + xgi.parse_bipartite_edgelist(bad_lines3, edgetype=int) def test_write_bipartite_edgelist(edgelist1): _, filename = tempfile.mkstemp() diff --git a/xgi/readwrite/bipartite.py b/xgi/readwrite/bipartite.py index bee13b247..e9df213c8 100644 --- a/xgi/readwrite/bipartite.py +++ b/xgi/readwrite/bipartite.py @@ -194,10 +194,10 @@ def parse_bipartite_edgelist( if nodetype is not None: try: node = nodetype(s[node_index]) - except Exception as e: + except ValueError: raise TypeError( f"Failed to convert the node with ID {s[node_index]} to type {nodetype}." - ) from e + ) else: node = s[node_index] @@ -205,10 +205,10 @@ def parse_bipartite_edgelist( if edgetype is not None: try: edge = edgetype(s[edge_index]) - except Exception as e: + except ValueError: raise TypeError( f"Failed to convert the edge with ID {s[edge_index]} to type {edgetype}." - ) from e + ) else: edge = s[edge_index] From 8bff1c8d00cb3cf24f4199d1adad8505b8634939 Mon Sep 17 00:00:00 2001 From: nwlandry Date: Mon, 1 Aug 2022 17:39:21 -0400 Subject: [PATCH 09/16] add assortativity tests --- tests/algorithms/test_assortativity.py | 15 +++++++++++++-- xgi/algorithms/assortativity.py | 10 ++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/tests/algorithms/test_assortativity.py b/tests/algorithms/test_assortativity.py index fd567efcc..98ccab2a4 100644 --- a/tests/algorithms/test_assortativity.py +++ b/tests/algorithms/test_assortativity.py @@ -26,25 +26,36 @@ def test_dynamical_assortativity(edgelist1, edgelist6): assert abs(xgi.dynamical_assortativity(H1) - -0.0526) < 1e-3 -def test_degree_assortativity(edgelist1, edgelist6): +def test_degree_assortativity(edgelist1, edgelist5): H1 = xgi.Hypergraph(edgelist1) assert -1 <= xgi.degree_assortativity(H1, kind="uniform") <= 1 assert -1 <= xgi.degree_assortativity(H1, kind="top-2") <= 1 assert -1 <= xgi.degree_assortativity(H1, kind="top-bottom") <= 1 - H2 = xgi.Hypergraph(edgelist6) + H2 = xgi.Hypergraph(edgelist5) assert -1 <= xgi.degree_assortativity(H2, kind="uniform") <= 1 assert -1 <= xgi.degree_assortativity(H2, kind="top-2") <= 1 assert -1 <= xgi.degree_assortativity(H2, kind="top-bottom") <= 1 + # test "exact" keyword + assert -1 <= xgi.degree_assortativity(H1, kind="uniform", exact=True) <= 1 + assert -1 <= xgi.degree_assortativity(H1, kind="top-2", exact=True) <= 1 + assert -1 <= xgi.degree_assortativity(H1, kind="top-bottom", exact=True) <= 1 + def test_choose_degrees(edgelist1, edgelist6): H1 = xgi.Hypergraph(edgelist1) k = H1.degree() + # test singleton edges with pytest.raises(XGIError): e = H1.edges.members(1) choose_degrees(e, k) + + # invalid choice function + with pytest.raises(XGIError): + e = H1.edges.members(0) + choose_degrees(e, k, "test") e = H1.edges.members(0) assert np.all(np.array(choose_degrees(e, k)) == 1) diff --git a/xgi/algorithms/assortativity.py b/xgi/algorithms/assortativity.py index 8fabbf987..25c2aee46 100644 --- a/xgi/algorithms/assortativity.py +++ b/xgi/algorithms/assortativity.py @@ -37,11 +37,11 @@ def dynamical_assortativity(H): DOI: 10.1063/5.0086905 """ - if not xgi.is_uniform(H): - raise XGIError("Hypergraph must be uniform!") - if H.num_nodes == 0 or H.num_edges == 0: raise XGIError("Hypergraph must contain nodes and edges!") + + if not xgi.is_uniform(H): + raise XGIError("Hypergraph must be uniform!") degs = H.degree() k1 = np.mean(list(degs.values())) @@ -89,9 +89,7 @@ def degree_assortativity(H, kind="uniform", exact=False, num_samples=1000): if exact: k1k2 = [ choose_degrees(H.edges.members(e), degs, kind) - for e in H.edges - if len(H.edges.members(e)) > 1 - ] + for e in H.edges if len(H.edges.members(e)) > 1] else: edges = [e for e in H.edges if len(H.edges.members(e)) > 1] k1k2 = [ From c6423f4252c0a45645790e1d157c99c1a2229a1e Mon Sep 17 00:00:00 2001 From: nwlandry Date: Mon, 1 Aug 2022 18:43:21 -0400 Subject: [PATCH 10/16] fixed assortativity function --- tests/algorithms/test_assortativity.py | 2 +- tests/readwrite/test_bipartite_edgelist.py | 33 ++++++++++++++++++---- xgi/algorithms/assortativity.py | 12 ++++++-- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/tests/algorithms/test_assortativity.py b/tests/algorithms/test_assortativity.py index 98ccab2a4..2ca892a76 100644 --- a/tests/algorithms/test_assortativity.py +++ b/tests/algorithms/test_assortativity.py @@ -51,7 +51,7 @@ def test_choose_degrees(edgelist1, edgelist6): with pytest.raises(XGIError): e = H1.edges.members(1) choose_degrees(e, k) - + # invalid choice function with pytest.raises(XGIError): e = H1.edges.members(0) diff --git a/tests/readwrite/test_bipartite_edgelist.py b/tests/readwrite/test_bipartite_edgelist.py index b45ec71f7..6aa396469 100644 --- a/tests/readwrite/test_bipartite_edgelist.py +++ b/tests/readwrite/test_bipartite_edgelist.py @@ -59,9 +59,31 @@ def test_read_bipartite_edgelist(file_string, extra_kwargs): def test_parse_bipartite_edgelist(): lines = ["0 0", "1 0", "2 0", "3 0", "4 1", "5 2", "6 2", "6 3", "7 3", "8 3"] bad_lines1 = ["0", "1 0", "2 0", "3 0", "4 1", "5 2", "6 2", "6 3", "7 3", "8 3"] - bad_lines2 = ["test 0", "1 0", "2 0", "3 0", "4 1", "5 test", "6 test", "6 3", "7 3", "8 3"] - bad_lines3 = ["0 0", "1 0", "2 0", "3 0", "4 1", "5 test", "6 test", "6 3", "7 3", "8 3"] - + bad_lines2 = [ + "test 0", + "1 0", + "2 0", + "3 0", + "4 1", + "5 test", + "6 test", + "6 3", + "7 3", + "8 3", + ] + bad_lines3 = [ + "0 0", + "1 0", + "2 0", + "3 0", + "4 1", + "5 test", + "6 test", + "6 3", + "7 3", + "8 3", + ] + H = xgi.parse_bipartite_edgelist(lines, nodetype=int) assert list(H.nodes) == [0, 1, 2, 3, 4, 5, 6, 7, 8] assert list(H.edges) == ["0", "1", "2", "3"] @@ -94,7 +116,7 @@ def test_parse_bipartite_edgelist(): [3], [3], ] - + # test less than two entries per line with pytest.raises(XGIError): xgi.parse_bipartite_edgelist(bad_lines1) @@ -102,11 +124,12 @@ def test_parse_bipartite_edgelist(): # test failed nodetype conversion with pytest.raises(TypeError): xgi.parse_bipartite_edgelist(bad_lines2, nodetype=int) - + # test failed edgetype conversion with pytest.raises(TypeError): xgi.parse_bipartite_edgelist(bad_lines3, edgetype=int) + def test_write_bipartite_edgelist(edgelist1): _, filename = tempfile.mkstemp() H1 = xgi.Hypergraph(edgelist1) diff --git a/xgi/algorithms/assortativity.py b/xgi/algorithms/assortativity.py index 25c2aee46..31f531d19 100644 --- a/xgi/algorithms/assortativity.py +++ b/xgi/algorithms/assortativity.py @@ -39,7 +39,7 @@ def dynamical_assortativity(H): """ if H.num_nodes == 0 or H.num_edges == 0: raise XGIError("Hypergraph must contain nodes and edges!") - + if not xgi.is_uniform(H): raise XGIError("Hypergraph must be uniform!") @@ -89,14 +89,20 @@ def degree_assortativity(H, kind="uniform", exact=False, num_samples=1000): if exact: k1k2 = [ choose_degrees(H.edges.members(e), degs, kind) - for e in H.edges if len(H.edges.members(e)) > 1] + for e in H.edges + if len(H.edges.members(e)) > 1 + ] else: edges = [e for e in H.edges if len(H.edges.members(e)) > 1] k1k2 = [ choose_degrees(H.edges.members(random.choice(edges)), degs, kind) for _ in range(num_samples) ] - return np.corrcoef(np.array(k1k2).T)[0, 1] + + rho = np.corrcoef(np.array(k1k2).T)[0, 1] + if np.isnan(rho): + return 0 + return rho def choose_degrees(e, k, kind="uniform"): From bfafb37323b4b4120935aeae84542a5eb41496fe Mon Sep 17 00:00:00 2001 From: nwlandry Date: Mon, 1 Aug 2022 19:06:41 -0400 Subject: [PATCH 11/16] add IDDict tests --- tests/classes/test_iddict.py | 13 ++++++++++++- xgi/classes/hypergraph.py | 8 ++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/classes/test_iddict.py b/tests/classes/test_iddict.py index 478c463fe..54c28eb7b 100644 --- a/tests/classes/test_iddict.py +++ b/tests/classes/test_iddict.py @@ -1,7 +1,7 @@ import pytest import xgi -from xgi.exception import IDNotFound +from xgi.exception import IDNotFound, XGIError def test_iddict(edgelist1): @@ -17,6 +17,17 @@ def test_iddict(edgelist1): with pytest.raises(IDNotFound): H.edges[4] + assert H.edges[0] == dict() + + with pytest.raises(XGIError): + H._edge[None] = [1, 2, 3] + + with pytest.raises(IDNotFound): + del H._node["test"] + + with pytest.raises(TypeError): + H._node[[0, 1, 2]] = [0, 1] + def test_neighbors(): H = xgi.Hypergraph() diff --git a/xgi/classes/hypergraph.py b/xgi/classes/hypergraph.py index 867696c6d..a27eb7938 100644 --- a/xgi/classes/hypergraph.py +++ b/xgi/classes/hypergraph.py @@ -24,16 +24,16 @@ class IDDict(dict): def __getitem__(self, item): try: return dict.__getitem__(self, item) - except KeyError as e: - raise IDNotFound(f"ID {item} not found") from e + except KeyError: + raise IDNotFound(f"ID {item} not found") def __setitem__(self, item, value): if item is None: raise XGIError("None cannot be a node or edge") try: return dict.__setitem__(self, item, value) - except KeyError as e: - raise IDNotFound(f"ID {item} not found") from e + except TypeError: + raise TypeError(f"ID {item} not a valid type") def __delitem__(self, item): try: From 8a0c14545a0f1a591a6e36d456f9bf19426bb1b7 Mon Sep 17 00:00:00 2001 From: nwlandry Date: Mon, 1 Aug 2022 20:47:40 -0400 Subject: [PATCH 12/16] add unit test --- tests/classes/test_hypergraph.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/classes/test_hypergraph.py b/tests/classes/test_hypergraph.py index 3187e52e0..d547a78d5 100644 --- a/tests/classes/test_hypergraph.py +++ b/tests/classes/test_hypergraph.py @@ -50,7 +50,8 @@ def test_contains(edgelist1): for node in unique_nodes: assert node in H - assert 0 not in H + # test TypeError handling + assert [1, 2, 3] not in H def test_string(): From 070e77a00e06e1f6ff6de1656b1bc916584a6801 Mon Sep 17 00:00:00 2001 From: nwlandry Date: Mon, 1 Aug 2022 21:56:47 -0400 Subject: [PATCH 13/16] added matrix tests --- tests/linalg/test_matrix.py | 34 ++++++++++++++++ xgi/linalg/matrix.py | 77 +++++++++++++++++-------------------- 2 files changed, 69 insertions(+), 42 deletions(-) diff --git a/tests/linalg/test_matrix.py b/tests/linalg/test_matrix.py index 1dec5631d..2e18c7c48 100644 --- a/tests/linalg/test_matrix.py +++ b/tests/linalg/test_matrix.py @@ -317,6 +317,12 @@ def test_multiorder_laplacian(edgelist2, edgelist6): L1, node_dict1 = xgi.multiorder_laplacian( H1, orders=[1, 2], weights=[1, 1], index=True ) + + # different order and weight lengths + with pytest.raises(ValueError): + L1, node_dict1 = xgi.multiorder_laplacian( + H2, orders=[1, 2], weights=[1, 1, 1], index=True + ) node_dict1 = {k: v for v, k in node_dict1.items()} assert L1.shape == (5, 5) assert np.all((L1.T == L1)) @@ -343,6 +349,9 @@ def test_multiorder_laplacian(edgelist2, edgelist6): assert L2[node_dict2[3], node_dict2[5]] == 0 assert L2[node_dict2[3], node_dict2[6]] == 0 + L2 = xgi.multiorder_laplacian(H2, orders=[1, 2], weights=[1, 1]) + assert np.shape(L2) == (6, 6) + def test_intersection_profile(edgelist2): el1 = edgelist2 @@ -370,6 +379,10 @@ def test_intersection_profile(edgelist2): assert P2[edge_dict2[2], edge_dict2[2]] == 3 + P2 = xgi.intersection_profile(H1, order=2) + assert np.shape(P2) == (1, 1) + assert P2[0, 0] == 3 + def test_clique_motif_matrix(edgelist4): H1 = xgi.Hypergraph(edgelist4) @@ -407,3 +420,24 @@ def test_empty(): assert xgi.adjacency_matrix(H).shape == (0, 0) assert xgi.laplacian(H).shape == (0, 0) assert xgi.clique_motif_matrix(H).shape == (0,) + + # with indices + data = xgi.incidence_matrix(H, index=True) + assert len(data) == 3 + assert data[0].shape == (0,) + assert type(data[1]) == dict and type(data[2]) == dict + + data = xgi.adjacency_matrix(H, index=True) + assert len(data) == 2 + assert data[0].shape == (0, 0) + assert type(data[1]) == dict + + data = xgi.laplacian(H, index=True) + assert len(data) == 2 + assert data[0].shape == (0, 0) + assert type(data[1]) == dict + + data = xgi.clique_motif_matrix(H, index=True) + assert len(data) == 2 + assert data[0].shape == (0,) + assert type(data[1]) == dict \ No newline at end of file diff --git a/xgi/linalg/matrix.py b/xgi/linalg/matrix.py index c37f9c5c3..1b3cc3b78 100644 --- a/xgi/linalg/matrix.py +++ b/xgi/linalg/matrix.py @@ -48,7 +48,7 @@ def incidence_matrix( """ edge_ids = H.edges if order is not None: - edge_ids = [id_ for id_, edge in H._edge.items() if len(edge) == order + 1] + edge_ids = H.edges.filterby("order", order) if not edge_ids: return (np.array([]), {}, {}) if index else np.array([]) @@ -59,48 +59,41 @@ def incidence_matrix( node_dict = dict(zip(node_ids, range(num_nodes))) edge_dict = dict(zip(edge_ids, range(num_edges))) - if node_dict and edge_dict: - - if index: - rowdict = {v: k for k, v in node_dict.items()} - coldict = {v: k for k, v in edge_dict.items()} - - if sparse: - # Create csr sparse matrix - rows = [] - cols = [] - data = [] - for node in node_ids: - memberships = H.nodes.memberships(node) - # keep only those with right order - memberships = [i for i in memberships if i in edge_ids] - if len(memberships) > 0: - for edge in memberships: - data.append(weight(node, edge, H)) - rows.append(node_dict[node]) - cols.append(edge_dict[edge]) - else: # include disconnected nodes - for edge in edge_ids: - data.append(0) - rows.append(node_dict[node]) - cols.append(edge_dict[edge]) - I = csr_matrix((data, (rows, cols))) - else: - # Create an np.matrix - I = np.zeros((num_nodes, num_edges), dtype=int) - for edge in edge_ids: - members = H.edges.members(edge) - for node in members: - I[node_dict[node], edge_dict[edge]] = weight(node, edge, H) - if index: - return I, rowdict, coldict - else: - return I + if index: + rowdict = {v: k for k, v in node_dict.items()} + coldict = {v: k for k, v in edge_dict.items()} + + if sparse: + # Create csr sparse matrix + rows = [] + cols = [] + data = [] + for node in node_ids: + memberships = H.nodes.memberships(node) + # keep only those with right order + memberships = [i for i in memberships if i in edge_ids] + if len(memberships) > 0: + for edge in memberships: + data.append(weight(node, edge, H)) + rows.append(node_dict[node]) + cols.append(edge_dict[edge]) + else: # include disconnected nodes + for edge in edge_ids: + data.append(0) + rows.append(node_dict[node]) + cols.append(edge_dict[edge]) + I = csr_matrix((data, (rows, cols))) else: - if index: - return np.array([]), {}, {} - else: - return np.array([]) + # Create an np.matrix + I = np.zeros((num_nodes, num_edges), dtype=int) + for edge in edge_ids: + members = H.edges.members(edge) + for node in members: + I[node_dict[node], edge_dict[edge]] = weight(node, edge, H) + if index: + return I, rowdict, coldict + else: + return I def adjacency_matrix(H, order=None, s=1, weighted=False, index=False): From 692f92ff1f4c8f68b327db71d5e8041d668554ea Mon Sep 17 00:00:00 2001 From: nwlandry Date: Mon, 1 Aug 2022 21:58:14 -0400 Subject: [PATCH 14/16] style: format with black --- tests/linalg/test_matrix.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/linalg/test_matrix.py b/tests/linalg/test_matrix.py index 2e18c7c48..4851af241 100644 --- a/tests/linalg/test_matrix.py +++ b/tests/linalg/test_matrix.py @@ -317,7 +317,7 @@ def test_multiorder_laplacian(edgelist2, edgelist6): L1, node_dict1 = xgi.multiorder_laplacian( H1, orders=[1, 2], weights=[1, 1], index=True ) - + # different order and weight lengths with pytest.raises(ValueError): L1, node_dict1 = xgi.multiorder_laplacian( @@ -436,8 +436,8 @@ def test_empty(): assert len(data) == 2 assert data[0].shape == (0, 0) assert type(data[1]) == dict - + data = xgi.clique_motif_matrix(H, index=True) assert len(data) == 2 assert data[0].shape == (0,) - assert type(data[1]) == dict \ No newline at end of file + assert type(data[1]) == dict From 25738cd99526288c012d1806780416b1f19243bb Mon Sep 17 00:00:00 2001 From: nwlandry Date: Tue, 2 Aug 2022 07:51:39 -0400 Subject: [PATCH 15/16] changes from PR review --- tests/readwrite/test_bipartite_edgelist.py | 1 - tests/readwrite/test_edgelist.py | 2 ++ xgi/linalg/matrix.py | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/readwrite/test_bipartite_edgelist.py b/tests/readwrite/test_bipartite_edgelist.py index 6aa396469..06f762b30 100644 --- a/tests/readwrite/test_bipartite_edgelist.py +++ b/tests/readwrite/test_bipartite_edgelist.py @@ -1,5 +1,4 @@ import tempfile -from typing import Type import pytest diff --git a/tests/readwrite/test_edgelist.py b/tests/readwrite/test_edgelist.py index be6feb211..ac1b0972d 100644 --- a/tests/readwrite/test_edgelist.py +++ b/tests/readwrite/test_edgelist.py @@ -50,6 +50,8 @@ def test_parse_edgelist(): [1, 4, 7, 8], [2, 3], ] + + # This will fail because the "test" node ID can't be converted to int with pytest.raises(TypeError): xgi.parse_edgelist(["test 2", "2 3 4", "test 4 7 8", "2 3"], nodetype=int) diff --git a/xgi/linalg/matrix.py b/xgi/linalg/matrix.py index 1b3cc3b78..92f0b2ef8 100644 --- a/xgi/linalg/matrix.py +++ b/xgi/linalg/matrix.py @@ -46,13 +46,14 @@ def incidence_matrix( The dictionary mapping indices to edge IDs, if index is True """ + node_ids = H.nodes edge_ids = H.edges + if order is not None: edge_ids = H.edges.filterby("order", order) - if not edge_ids: + if not edge_ids or not node_ids: return (np.array([]), {}, {}) if index else np.array([]) - node_ids = H.nodes num_edges = len(edge_ids) num_nodes = len(node_ids) From 2e13ce42a6824fcad7be6cf384db924336678dd4 Mon Sep 17 00:00:00 2001 From: nwlandry Date: Tue, 2 Aug 2022 10:01:06 -0400 Subject: [PATCH 16/16] fixes from PR review --- xgi/classes/hypergraph.py | 8 ++++---- xgi/linalg/matrix.py | 2 +- xgi/readwrite/bipartite.py | 8 ++++---- xgi/readwrite/edgelist.py | 4 ++-- xgi/readwrite/json.py | 32 ++++++++++++++++++-------------- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/xgi/classes/hypergraph.py b/xgi/classes/hypergraph.py index a27eb7938..007a747a2 100644 --- a/xgi/classes/hypergraph.py +++ b/xgi/classes/hypergraph.py @@ -24,16 +24,16 @@ class IDDict(dict): def __getitem__(self, item): try: return dict.__getitem__(self, item) - except KeyError: - raise IDNotFound(f"ID {item} not found") + except KeyError as e: + raise IDNotFound(f"ID {item} not found") from e def __setitem__(self, item, value): if item is None: raise XGIError("None cannot be a node or edge") try: return dict.__setitem__(self, item, value) - except TypeError: - raise TypeError(f"ID {item} not a valid type") + except TypeError as e: + raise TypeError(f"ID {item} not a valid type") from e def __delitem__(self, item): try: diff --git a/xgi/linalg/matrix.py b/xgi/linalg/matrix.py index 92f0b2ef8..d30f33aca 100644 --- a/xgi/linalg/matrix.py +++ b/xgi/linalg/matrix.py @@ -48,7 +48,7 @@ def incidence_matrix( """ node_ids = H.nodes edge_ids = H.edges - + if order is not None: edge_ids = H.edges.filterby("order", order) if not edge_ids or not node_ids: diff --git a/xgi/readwrite/bipartite.py b/xgi/readwrite/bipartite.py index e9df213c8..fdbb9e485 100644 --- a/xgi/readwrite/bipartite.py +++ b/xgi/readwrite/bipartite.py @@ -194,10 +194,10 @@ def parse_bipartite_edgelist( if nodetype is not None: try: node = nodetype(s[node_index]) - except ValueError: + except ValueError as e: raise TypeError( f"Failed to convert the node with ID {s[node_index]} to type {nodetype}." - ) + ) from e else: node = s[node_index] @@ -205,10 +205,10 @@ def parse_bipartite_edgelist( if edgetype is not None: try: edge = edgetype(s[edge_index]) - except ValueError: + except ValueError as e: raise TypeError( f"Failed to convert the edge with ID {s[edge_index]} to type {edgetype}." - ) + ) from e else: edge = s[edge_index] diff --git a/xgi/readwrite/edgelist.py b/xgi/readwrite/edgelist.py index baa43fe22..231fb3303 100644 --- a/xgi/readwrite/edgelist.py +++ b/xgi/readwrite/edgelist.py @@ -150,8 +150,8 @@ def parse_edgelist( if nodetype is not None: try: edge = [nodetype(node) for node in edge] - except ValueError: - raise TypeError(f"Failed to convert nodes to type {nodetype}.") + except ValueError as e: + raise TypeError(f"Failed to convert nodes to type {nodetype}.") from e H.add_edge(edge) return H diff --git a/xgi/readwrite/json.py b/xgi/readwrite/json.py index c980c4596..f67a534a6 100644 --- a/xgi/readwrite/json.py +++ b/xgi/readwrite/json.py @@ -72,38 +72,42 @@ def read_json(path, nodetype=None, edgetype=None): H = empty_hypergraph() try: H._hypergraph.update(data["hypergraph-data"]) - except KeyError: - raise XGIError("Failed to get hypergraph data attributes.") + except KeyError as e: + raise XGIError("Failed to get hypergraph data attributes.") from e try: for id, dd in data["node-data"].items(): if nodetype is not None: try: id = nodetype(id) - except ValueError: - raise TypeError(f"Failed to convert edge IDs to type {nodetype}.") + except ValueError as e: + raise TypeError( + f"Failed to convert edge IDs to type {nodetype}." + ) from e H.add_node(id, **dd) - except KeyError: - raise XGIError("Failed to import node attributes.") + except KeyError as e: + raise XGIError("Failed to import node attributes.") from e try: for id, edge in data["edge-dict"].items(): if edgetype is not None: try: id = edgetype(id) - except ValueError: + except ValueError as e: raise TypeError( f"Failed to convert the edge with ID {id} to type {edgetype}." - ) + ) from e # convert the members of the edge to the nodetype if specified. if nodetype is not None: try: edge = [nodetype(n) for n in edge] - except ValueError: - raise TypeError(f"Failed to convert nodes to type {nodetype}.") + except ValueError as e: + raise TypeError( + f"Failed to convert nodes to type {nodetype}." + ) from e H.add_edge(edge, id) - except KeyError: - raise XGIError("Failed to import edge dictionary.") + except KeyError as e: + raise XGIError("Failed to import edge dictionary.") from e try: set_edge_attributes( @@ -112,7 +116,7 @@ def read_json(path, nodetype=None, edgetype=None): if edgetype is None else {edgetype(e): dd for e, dd in data["edge-data"].items()}, ) - except KeyError: - raise XGIError("Failed to import edge attributes.") + except KeyError as e: + raise XGIError("Failed to import edge attributes.") from e return H