From 186c5cc559f306c1b17cd0c310bd3dce2171138a Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Sun, 27 Oct 2024 17:51:30 -0400 Subject: [PATCH 01/18] move hif functionality to convert --- xgi/convert/__init__.py | 2 + xgi/convert/hif_dict.py | 142 +++++++++++++++++++++++++++++++++++++++ xgi/readwrite/hif.py | 143 ++-------------------------------------- 3 files changed, 149 insertions(+), 138 deletions(-) create mode 100644 xgi/convert/hif_dict.py diff --git a/xgi/convert/__init__.py b/xgi/convert/__init__.py index 84340a080..8a5f38bf9 100644 --- a/xgi/convert/__init__.py +++ b/xgi/convert/__init__.py @@ -3,6 +3,7 @@ bipartite_graph, encapsulation_dag, graph, + hif_dict, higher_order_network, hyperedges, hypergraph_dict, @@ -15,6 +16,7 @@ from .bipartite_graph import * from .encapsulation_dag import * from .graph import * +from .hif_dict import * from .higher_order_network import * from .hyperedges import * from .hypergraph_dict import * diff --git a/xgi/convert/hif_dict.py b/xgi/convert/hif_dict.py new file mode 100644 index 000000000..d5eac6cc9 --- /dev/null +++ b/xgi/convert/hif_dict.py @@ -0,0 +1,142 @@ +from collections import defaultdict + +from ..core import DiHypergraph, Hypergraph, SimplicialComplex +from ..utils import IDDict +from .bipartite_edges import to_bipartite_edgelist + +__all__ = ["to_hif_dict", "from_hif_dict"] + + +def to_hif_dict(H): + data = defaultdict(list) + + data["metadata"] = {} + data["metadata"].update(H._net_attr) + + if isinstance(H, SimplicialComplex): + data["network-type"] = "asc" + elif isinstance(H, Hypergraph): + data["network-type"] = "undirected" + elif isinstance(H, DiHypergraph): + data["network-type"] = "directed" + + # get node data + isolates = set(H.nodes.isolates()) + nodes_with_attrs = set(n for n in H.nodes if H.nodes[n]) + for n in isolates.union(nodes_with_attrs): + attr = {"attrs": H.nodes[n]} if H.nodes[n] else {} + data["nodes"].append(IDDict({"node": n}) + attr) + + empty = set(H.edges.empty()) + edges_with_attrs = set(e for e in H.edges if H.edges[e]) + for e in empty.union(edges_with_attrs): + attr = {"attrs": H.edges[e]} if H.edges[e] else {} + data["edges"].append(IDDict({"edge": e}) + attr) + + # hyperedge dict + if data["network-type"] == "directed": + _convert_d = lambda d: "tail" if d == "in" else "head" + data["incidences"] = [ + IDDict({"edge": e, "node": n, "direction": _convert_d(d)}) + for n, e, d in to_bipartite_edgelist(H) + ] + elif data["network-type"] in {"undirected", "asc"}: + data["incidences"] = [ + IDDict({"edge": e, "node": n}) for n, e in to_bipartite_edgelist(H) + ] + return data + + +def from_hif_dict(data, nodetype=None, edgetype=None): + """ + A helper function to read a file created according to the HIF format. + + For more information, see the HIF `project `_. + + Parameters + ---------- + data: dict + A dictionary in the hypergraph JSON format + nodetype: type, optional + Type that the node IDs will be cast to + edgetype: type, optional + Type that the edge IDs will be cast to + + Returns + ------- + A Hypergraph, SimplicialComplex, or DiHypergraph object + The loaded network + """ + + def _empty_edge(network_type): + if network_type in {"asc", "undirected"}: + return set() + else: + return (set(), set()) + + def _convert_id(i, idtype): + if idtype: + try: + return idtype(i) + except ValueError as e: + raise TypeError(f"Failed to convert ID {i} to type {idtype}.") from e + else: + return i + + _convert_d = lambda d: "in" if d == "tail" else "out" + + if "network-type" in data: + network_type = data["network-type"] + else: + network_type = "undirected" + + if network_type in {"asc", "undirected"}: + G = Hypergraph() + elif network_type == "directed": + G = DiHypergraph() + + # Import network metadata + if "metadata" in data: + G._net_attr.update(data["metadata"]) + + for record in data["incidences"]: + n = _convert_id(record["node"], nodetype) + e = _convert_id(record["edge"], edgetype) + + if network_type == "directed": + d = record["direction"] + d = _convert_d(d) # convert from head/tail to in/out + G.add_node_to_edge(e, n, d) + else: + G.add_node_to_edge(e, n) + + # import node attributes if they exist + if "nodes" in data: + for record in data["nodes"]: + n = _convert_id(record["node"], nodetype) + if "attrs" in record: + attr = record["attrs"] + else: + attr = {} + + if n not in G._node: + G.add_node(n, **attr) + else: + G.set_node_attributes({n: attr}) + + # import edge attributes if they exist + if "edges" in data: + for record in data["edges"]: + e = _convert_id(record["edge"], edgetype) + if "attrs" in record: + attr = record["attrs"] + else: + attr = {} + if e not in G._edge: + G.add_edge(_empty_edge(network_type), e, **attr) + else: + G.set_edge_attributes({e: attr}) + + if network_type == "asc": + G = SimplicialComplex(G) + return G diff --git a/xgi/readwrite/hif.py b/xgi/readwrite/hif.py index 9d00ee1f9..91740ac68 100644 --- a/xgi/readwrite/hif.py +++ b/xgi/readwrite/hif.py @@ -5,16 +5,13 @@ """ import json -from collections import defaultdict -from ..convert import to_bipartite_edgelist -from ..core import DiHypergraph, Hypergraph, SimplicialComplex -from ..utils import IDDict +from ..convert import from_hif_dict, to_hif_dict __all__ = ["write_hif", "read_hif"] -def write_hif(G, path): +def write_hif(H, path): """ A function to write a higher-order network according to the HIF standard. @@ -22,48 +19,13 @@ def write_hif(G, path): Parameters ---------- - G: Hypergraph, DiHypergraph, or SimplicialComplex object + H: Hypergraph, DiHypergraph, or SimplicialComplex object The specified higher-order network path: string The path of the file to read from """ # initialize empty data - data = defaultdict(list) - - data["metadata"] = {} - data["metadata"].update(G._net_attr) - - if isinstance(G, SimplicialComplex): - data["network-type"] = "asc" - elif isinstance(G, Hypergraph): - data["network-type"] = "undirected" - elif isinstance(G, DiHypergraph): - data["network-type"] = "directed" - - # get node data - isolates = set(G.nodes.isolates()) - nodes_with_attrs = set(n for n in G.nodes if G.nodes[n]) - for n in isolates.union(nodes_with_attrs): - attr = {"attrs": G.nodes[n]} if G.nodes[n] else {} - data["nodes"].append(IDDict({"node": n}) + attr) - - empty = set(G.edges.empty()) - edges_with_attrs = set(e for e in G.edges if G.edges[e]) - for e in empty.union(edges_with_attrs): - attr = {"attrs": G.edges[e]} if G.edges[e] else {} - data["edges"].append(IDDict({"edge": e}) + attr) - - # hyperedge dict - if data["network-type"] == "directed": - _convert_d = lambda d: "tail" if d == "in" else "head" - data["incidences"] = [ - IDDict({"edge": e, "node": n, "direction": _convert_d(d)}) - for n, e, d in to_bipartite_edgelist(G) - ] - elif data["network-type"] in {"undirected", "asc"}: - data["incidences"] = [ - IDDict({"edge": e, "node": n}) for n, e in to_bipartite_edgelist(G) - ] + data = to_hif_dict(H) datastring = json.dumps(data, indent=2) @@ -94,99 +56,4 @@ def read_hif(path, nodetype=None, edgetype=None): with open(path) as file: data = json.loads(file.read()) - return _from_dict(data, nodetype=nodetype, edgetype=edgetype) - - -def _from_dict(data, nodetype=None, edgetype=None): - """ - A helper function to read a file created according to the HIF format. - - For more information, see the HIF `project `_. - - Parameters - ---------- - data: dict - A dictionary in the hypergraph JSON format - nodetype: type, optional - Type that the node IDs will be cast to - edgetype: type, optional - Type that the edge IDs will be cast to - - Returns - ------- - A Hypergraph, SimplicialComplex, or DiHypergraph object - The loaded network - """ - - def _empty_edge(network_type): - if network_type in {"asc", "undirected"}: - return set() - else: - return (set(), set()) - - def _convert_id(i, idtype): - if idtype: - try: - return idtype(i) - except ValueError as e: - raise TypeError(f"Failed to convert ID {i} to type {idtype}.") from e - else: - return i - - _convert_d = lambda d: "in" if d == "tail" else "out" - - if "network-type" in data: - network_type = data["network-type"] - else: - network_type = "undirected" - - if network_type in {"asc", "undirected"}: - G = Hypergraph() - elif network_type == "directed": - G = DiHypergraph() - - # Import network metadata - if "metadata" in data: - G._net_attr.update(data["metadata"]) - - for record in data["incidences"]: - n = _convert_id(record["node"], nodetype) - e = _convert_id(record["edge"], edgetype) - - if network_type == "directed": - d = record["direction"] - d = _convert_d(d) # convert from head/tail to in/out - G.add_node_to_edge(e, n, d) - else: - G.add_node_to_edge(e, n) - - # import node attributes if they exist - if "nodes" in data: - for record in data["nodes"]: - n = _convert_id(record["node"], nodetype) - if "attrs" in record: - attr = record["attrs"] - else: - attr = {} - - if n not in G._node: - G.add_node(n, **attr) - else: - G.set_node_attributes({n: attr}) - - # import edge attributes if they exist - if "edges" in data: - for record in data["edges"]: - e = _convert_id(record["edge"], edgetype) - if "attrs" in record: - attr = record["attrs"] - else: - attr = {} - if e not in G._edge: - G.add_edge(_empty_edge(network_type), e, **attr) - else: - G.set_edge_attributes({e: attr}) - - if network_type == "asc": - G = SimplicialComplex(G) - return G + return from_hif_dict(data, nodetype=nodetype, edgetype=edgetype) From 6cd6eebc874b12fa7b87f2044c405c6d10c20d0e Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Sun, 27 Oct 2024 17:51:39 -0400 Subject: [PATCH 02/18] format with isort and black --- docs/source/conf.py | 2 +- tests/algorithms/test_clustering.py | 6 +++--- xgi/algorithms/clustering.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index cd0f340d2..70d1701c5 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,9 +14,9 @@ import os import sys from datetime import date, datetime -from sphinx_gallery.sorting import ExplicitOrder, FileNameSortKey import requests +from sphinx_gallery.sorting import ExplicitOrder, FileNameSortKey import xgi diff --git a/tests/algorithms/test_clustering.py b/tests/algorithms/test_clustering.py index f9ca9737d..5bd98a5d0 100644 --- a/tests/algorithms/test_clustering.py +++ b/tests/algorithms/test_clustering.py @@ -1,7 +1,7 @@ +import networkx as nx import pytest import xgi -import networkx as nx from xgi.exception import XGIError @@ -41,11 +41,11 @@ def test_local_clustering_coefficient(edgelist8): 3: 0.625, 4: 0.5833333333333334, 5: 1.0, - 6: 1.0 + 6: 1.0, } for n in cc: assert round(cc[n], 3) == round(true_cc[n], 3) - + G = nx.erdos_renyi_graph(50, 0.1, seed=0) H = xgi.Hypergraph() H.add_nodes_from(G.nodes) diff --git a/xgi/algorithms/clustering.py b/xgi/algorithms/clustering.py index 11fb637d7..c7c9a1984 100644 --- a/xgi/algorithms/clustering.py +++ b/xgi/algorithms/clustering.py @@ -1,8 +1,9 @@ """Algorithms for computing nodal clustering coefficients.""" -import numpy as np from itertools import combinations +import numpy as np + from ..exception import XGIError from ..linalg import adjacency_matrix @@ -151,8 +152,7 @@ def local_clustering_coefficient(H): neighD2 = {i for d in D2 for i in H.nodes.neighbors(d)} # compute extra overlap [len() is used for cardinality of edges] eo = ( - len(neighD1.intersection(D2)) - + len(neighD2.intersection(D1)) + len(neighD1.intersection(D2)) + len(neighD2.intersection(D1)) ) / len( D1.union(D2) ) # add it up From 212eff66a81be98cf65307e53e597b81f204f804 Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Sun, 27 Oct 2024 20:57:10 -0400 Subject: [PATCH 03/18] add docs --- docs/source/api/convert.rst | 1 + docs/source/api/convert/xgi.convert.hif_dict.rst | 11 +++++++++++ docs/source/api/readwrite/xgi.readwrite.hif.rst | 2 ++ 3 files changed, 14 insertions(+) create mode 100644 docs/source/api/convert/xgi.convert.hif_dict.rst diff --git a/docs/source/api/convert.rst b/docs/source/api/convert.rst index c78e1d519..d1565213a 100644 --- a/docs/source/api/convert.rst +++ b/docs/source/api/convert.rst @@ -11,6 +11,7 @@ convert package ~xgi.convert.bipartite_graph ~xgi.convert.encapsulation_dag ~xgi.convert.graph + ~xgi.convert.hif_dict ~xgi.convert.higher_order_network ~xgi.convert.hyperedges ~xgi.convert.hypergraph_dict diff --git a/docs/source/api/convert/xgi.convert.hif_dict.rst b/docs/source/api/convert/xgi.convert.hif_dict.rst new file mode 100644 index 000000000..0c835941b --- /dev/null +++ b/docs/source/api/convert/xgi.convert.hif_dict.rst @@ -0,0 +1,11 @@ +xgi.convert.hif_dict +==================== + +.. currentmodule:: xgi.convert.hif_dict + +.. automodule:: xgi.convert.hif_dict + + .. rubric:: Functions + + .. autofunction:: to_hif_dict + .. autofunction:: from_hif_dict \ No newline at end of file diff --git a/docs/source/api/readwrite/xgi.readwrite.hif.rst b/docs/source/api/readwrite/xgi.readwrite.hif.rst index 0f99b9c82..2aa2e7ca9 100644 --- a/docs/source/api/readwrite/xgi.readwrite.hif.rst +++ b/docs/source/api/readwrite/xgi.readwrite.hif.rst @@ -8,4 +8,6 @@ xgi.readwrite.hif .. rubric:: Functions .. autofunction:: read_hif + .. autofunction:: read_hif_collection .. autofunction:: write_hif + .. autofunction:: write_hif_collection From 17e2754c33370701b96d87a28ff952a40bce9177 Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Sun, 27 Oct 2024 20:59:31 -0400 Subject: [PATCH 04/18] add collection handling --- xgi/readwrite/hif.py | 92 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/xgi/readwrite/hif.py b/xgi/readwrite/hif.py index 91740ac68..a94dec244 100644 --- a/xgi/readwrite/hif.py +++ b/xgi/readwrite/hif.py @@ -5,8 +5,11 @@ """ import json +from collections import defaultdict +from os.path import dirname, join from ..convert import from_hif_dict, to_hif_dict +from ..exception import XGIError __all__ = ["write_hif", "read_hif"] @@ -24,7 +27,6 @@ def write_hif(H, path): path: string The path of the file to read from """ - # initialize empty data data = to_hif_dict(H) datastring = json.dumps(data, indent=2) @@ -33,6 +35,50 @@ def write_hif(H, path): output_file.write(datastring) +def write_hif_collection(H, path, collection_name=""): + """ + A function to write a collection of higher-order network according to the HIF standard. + + For more information, see the HIF `project `_. + + Parameters + ---------- + H: list or dict of Hypergraph, DiHypergraph, or SimplicialComplex objects + The specified higher-order network + path: string + The path of the file to read from + """ + if isinstance(H, list): + collection_data = defaultdict(dict) + for i, H in enumerate(H): + fname = f"{path}/{collection_name}_{i}.json" + collection_data["datasets"][i] = { + "relative-path": f"{collection_name}_{i}.json" + } + write_hif(H, fname) + collection_data["type"] = "collection" + datastring = json.dumps(collection_data, indent=2) + with open( + f"{path}/{collection_name}_collection_information.json", "w" + ) as output_file: + output_file.write(datastring) + + elif isinstance(H, dict): + collection_data = defaultdict(dict) + for name, H in H.items(): + fname = f"{path}/{collection_name}_{name}.json" + collection_data["datasets"][name] = { + "relative-path": f"{collection_name}_{name}.json" + } + write_hif(H, fname) + collection_data["type"] = "collection" + datastring = json.dumps(collection_data, indent=2) + with open( + f"{path}/{collection_name}_collection_information.json", "w" + ) as output_file: + output_file.write(datastring) + + def read_hif(path, nodetype=None, edgetype=None): """ A function to read a file created according to the HIF format. @@ -41,8 +87,8 @@ def read_hif(path, nodetype=None, edgetype=None): Parameters ---------- - data: dict - A dictionary in the hypergraph JSON format + path: str + The path to the json file nodetype: type, optional type that the node IDs will be cast to edgetype: type, optional @@ -57,3 +103,43 @@ def read_hif(path, nodetype=None, edgetype=None): data = json.loads(file.read()) return from_hif_dict(data, nodetype=nodetype, edgetype=edgetype) + + +def read_hif_collection(path, nodetype=None, edgetype=None): + """ + A function to read a collection of files created according to the HIF format. + + There must be a collection information JSON file which has a top-level field "datasets" + with subfields "relative-path", indicating each dataset's location relative to the + collection file + + For more information, see the HIF `project `_. + + Parameters + ---------- + path: str + A path to the collection json file. + nodetype: type, optional + type that the node IDs will be cast to + edgetype: type, optional + type that the edge IDs will be cast to + + Returns + ------- + A dictionary of Hypergraph, SimplicialComplex, or DiHypergraph objects + The collection of networks + """ + with open(path) as file: + jsondata = json.loads(file.read()) + + try: + collection = {} + for name, data in jsondata["datasets"].items(): + relpath = data["relative-path"] + H = read_hif( + join(dirname(path), relpath), nodetype=nodetype, edgetype=edgetype + ) + collection[name] = H + return collection + except KeyError: + raise XGIError("Data collection is in the wrong format!") From f1cd8069b17c8f3e68d2c49b7e13caff9b8b3186 Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Sun, 27 Oct 2024 20:59:44 -0400 Subject: [PATCH 05/18] add warning --- xgi/readwrite/json.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xgi/readwrite/json.py b/xgi/readwrite/json.py index 8de683c6b..3493aac95 100644 --- a/xgi/readwrite/json.py +++ b/xgi/readwrite/json.py @@ -3,6 +3,7 @@ import json from collections import defaultdict from os.path import dirname, join +from warnings import warn from ..convert import from_hypergraph_dict, to_hypergraph_dict from ..core import Hypergraph, SimplicialComplex @@ -36,6 +37,7 @@ def write_json(H, path, collection_name=""): to strings, e.g., node IDs "2" and 2. """ + warn("This function is deprecated in favor of the 'write_hif()' function") if collection_name: collection_name += "_" @@ -101,6 +103,7 @@ def read_json(path, nodetype=None, edgetype=None): If the JSON is not in a format that can be loaded. """ + warn("This function is deprecated in favor of the 'read_hif()' function") with open(path) as file: jsondata = json.loads(file.read()) From c7ee4acf6958091f9664a3b80a4d05017f76e80d Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Sun, 27 Oct 2024 21:00:00 -0400 Subject: [PATCH 06/18] add docstrings --- xgi/convert/hif_dict.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/xgi/convert/hif_dict.py b/xgi/convert/hif_dict.py index d5eac6cc9..0d288fb34 100644 --- a/xgi/convert/hif_dict.py +++ b/xgi/convert/hif_dict.py @@ -1,3 +1,5 @@ +"""Methods for converting to/from HIF standard.""" + from collections import defaultdict from ..core import DiHypergraph, Hypergraph, SimplicialComplex @@ -8,6 +10,21 @@ def to_hif_dict(H): + """ + A function to create a dictionary according to the HIF standard from a higher-order network. + + For more information, see the HIF `project `_. + + Parameters + ---------- + H: Hypergraph, DiHypergraph, or SimplicialComplex object + The specified higher-order network + + Returns + ------- + defaultdict + A dict according to the HIF standard. + """ data = defaultdict(list) data["metadata"] = {} @@ -49,7 +66,7 @@ def to_hif_dict(H): def from_hif_dict(data, nodetype=None, edgetype=None): """ - A helper function to read a file created according to the HIF format. + A function to read a dictionary that follows the HIF standard. For more information, see the HIF `project `_. From 84a059bc77b832d1277efbae5053cad2d569c60b Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Sun, 27 Oct 2024 21:01:24 -0400 Subject: [PATCH 07/18] added HIF to `load_xgi_data` --- xgi/readwrite/xgi_data.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/xgi/readwrite/xgi_data.py b/xgi/readwrite/xgi_data.py index 4fe2f9d29..920d0b45e 100644 --- a/xgi/readwrite/xgi_data.py +++ b/xgi/readwrite/xgi_data.py @@ -3,9 +3,10 @@ from os.path import dirname, exists, join from warnings import warn -from ..convert import from_hypergraph_dict +from ..convert import cut_to_order, from_hif_dict, from_hypergraph_dict from ..exception import XGIError from ..utils import request_json_from_url, request_json_from_url_cached +from .hif import write_hif, write_hif_collection __all__ = ["load_xgi_data", "download_xgi_data"] @@ -58,9 +59,9 @@ def load_xgi_data( if read: cfp = join(path, dataset + ".json") if exists(cfp): - from ..readwrite import read_json + from ..readwrite import read_hif - return read_json(cfp, nodetype=nodetype, edgetype=edgetype) + return read_hif(cfp, nodetype=nodetype, edgetype=edgetype) else: warn( f"No local copy was found at {cfp}. The data is requested " @@ -125,10 +126,10 @@ def download_xgi_data(dataset, path="", collection_name=""): url, nodetype=None, edgetype=None, max_order=None, cache=True ) if isinstance(H, dict): - write_json(H, path, collection_name=collection_name) + write_hif_collection(H, path, collection_name=collection_name) else: filename = join(path, key + ".json") - write_json(H, filename) + write_hif(H, filename) def _request_from_xgi_data( @@ -164,7 +165,10 @@ def _request_from_xgi_data( else: jsondata = request_json_from_url(url) - if "type" in jsondata and jsondata["type"] == "collection": + if "incidences" in jsondata: + H = from_hif_dict(H, nodetype=nodetype, edgetype=edgetype) + return cut_to_order(H, order=max_order) + if "type" in jsondata and jsondata["type"] != "collection": collection = {} for name, data in jsondata["datasets"].items(): relpath = data["relative-path"] From b38db1b779b5bbe27f007c1417e818e708e6fe97 Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Sun, 27 Oct 2024 21:01:37 -0400 Subject: [PATCH 08/18] added more close to tests --- tests/drawing/test_draw.py | 48 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/drawing/test_draw.py b/tests/drawing/test_draw.py index 24c301a40..a2f695beb 100644 --- a/tests/drawing/test_draw.py +++ b/tests/drawing/test_draw.py @@ -33,7 +33,7 @@ def test_draw(edgelist8): assert patch.get_zorder() == z assert node_collection.get_zorder() == 4 # nodes - plt.close() + plt.close("all") # simplicial complex S = xgi.SimplicialComplex(edgelist8) @@ -58,7 +58,7 @@ def test_draw(edgelist8): for patch, z in zip(ax.patches, [0, 2, 2]): # hyperedges assert patch.get_zorder() == z - plt.close() + plt.close("all") def test_draw_nodes(edgelist8): @@ -116,10 +116,10 @@ def test_draw_nodes(edgelist8): # negative node_lw or node_size with pytest.raises(ValueError): ax3, node_collection3 = xgi.draw_nodes(H, node_size=-1) - plt.close() + plt.close("all") with pytest.raises(ValueError): ax3, node_collection3 = xgi.draw_nodes(H, node_lw=-1) - plt.close() + plt.close("all") plt.close("all") @@ -132,14 +132,14 @@ def test_draw_nodes_fc_cmap(edgelist8): fig, ax = plt.subplots() ax, node_collection = xgi.draw_nodes(H, ax=ax, node_fc="r") assert node_collection.get_cmap() == plt.cm.viridis - plt.close() + plt.close("all") # default cmap fig, ax = plt.subplots() colors = [11, 12, 14, 16, 17, 19, 21] ax, node_collection = xgi.draw_nodes(H, ax=ax, node_fc=colors) assert node_collection.get_cmap() == plt.cm.Reds - plt.close() + plt.close("all") # set cmap fig, ax = plt.subplots() @@ -148,13 +148,13 @@ def test_draw_nodes_fc_cmap(edgelist8): ) assert node_collection.get_cmap() == plt.cm.Greens assert (min(colors), max(colors)) == node_collection.get_clim() - plt.close() + plt.close("all") # vmin/vmax fig, ax = plt.subplots() ax, node_collection = xgi.draw_nodes(H, ax=ax, node_fc=colors, vmin=14, vmax=19) assert (14, 19) == node_collection.get_clim() - plt.close() + plt.close("all") def test_draw_nodes_interp(edgelist8): @@ -168,7 +168,7 @@ def test_draw_nodes_interp(edgelist8): ax, node_collection = xgi.draw_nodes(H, ax=ax, node_size=1, node_lw=10) assert np.all(node_collection.get_sizes() == np.array([1])) assert np.all(node_collection.get_linewidth() == np.array([10])) - plt.close() + plt.close("all") # rescaling does not affect scalars fig, ax = plt.subplots() @@ -177,7 +177,7 @@ def test_draw_nodes_interp(edgelist8): ) assert np.all(node_collection.get_sizes() == np.array([1])) assert np.all(node_collection.get_linewidth() == np.array([10])) - plt.close() + plt.close("all") # not rescaling IDStat fig, ax = plt.subplots() @@ -186,7 +186,7 @@ def test_draw_nodes_interp(edgelist8): ) assert np.all(node_collection.get_sizes() == deg_arr**2) assert np.all(node_collection.get_linewidth() == deg_arr) - plt.close() + plt.close("all") # rescaling IDStat fig, ax = plt.subplots() @@ -197,7 +197,7 @@ def test_draw_nodes_interp(edgelist8): assert max(node_collection.get_sizes()) == 30**2 assert min(node_collection.get_linewidth()) == 0 assert max(node_collection.get_linewidth()) == 5 - plt.close() + plt.close("all") # rescaling IDStat with manual values fig, ax = plt.subplots() @@ -218,7 +218,7 @@ def test_draw_nodes_interp(edgelist8): assert max(node_collection.get_sizes()) == 20**2 assert min(node_collection.get_linewidth()) == 1 assert max(node_collection.get_linewidth()) == 10 - plt.close() + plt.close("all") # rescaling ndarray fig, ax = plt.subplots() @@ -229,7 +229,7 @@ def test_draw_nodes_interp(edgelist8): assert max(node_collection.get_sizes()) == 30**2 assert min(node_collection.get_linewidth()) == 0 assert max(node_collection.get_linewidth()) == 5 - plt.close() + plt.close("all") def test_draw_hyperedges(edgelist8): @@ -281,7 +281,7 @@ def test_draw_hyperedges(edgelist8): with pytest.raises(ValueError): ax, collections = xgi.draw_hyperedges(H, ax=ax, dyad_lw=-1) (dyad_collection, edge_collection) = collections - plt.close() + plt.close("all") plt.close("all") @@ -296,7 +296,7 @@ def test_draw_hyperedges_fc_cmap(edgelist8): (dyad_collection, edge_collection) = collections assert dyad_collection.get_cmap() == plt.cm.Greys assert edge_collection.get_cmap() == sb.color_palette("crest_r", as_cmap=True) - plt.close() + plt.close("all") # set cmap fig, ax = plt.subplots() @@ -313,7 +313,7 @@ def test_draw_hyperedges_fc_cmap(edgelist8): assert (min(dyad_colors), max(dyad_colors)) == dyad_collection.get_clim() assert (3, 5) == edge_collection.get_clim() - plt.close() + plt.close("all") # vmin/vmax fig, ax = plt.subplots() @@ -332,7 +332,7 @@ def test_draw_hyperedges_fc_cmap(edgelist8): assert (14, 19) == edge_collection.get_clim() assert (5, 6) == dyad_collection.get_clim() - plt.close() + plt.close("all") def test_draw_hyperedges_ec(edgelist8): @@ -364,7 +364,7 @@ def test_draw_simplices(edgelist8): with pytest.raises(XGIError): H = xgi.Hypergraph(edgelist8) ax = xgi.draw_simplices(H) - plt.close() + plt.close("all") S = xgi.SimplicialComplex(edgelist8) @@ -386,7 +386,7 @@ def test_draw_simplices(edgelist8): for patch, z in zip(ax.patches, [0, 2, 2]): # hyperedges assert patch.get_zorder() == z - plt.close() + plt.close("all") def test_draw_hypergraph_hull(edgelist8): @@ -414,7 +414,7 @@ def test_draw_hypergraph_hull(edgelist8): assert patch.get_zorder() == z assert node_collection.get_zorder() == 4 # nodes - plt.close() + plt.close("all") def test_draw_multilayer(edgelist8): @@ -461,7 +461,7 @@ def test_draw_multilayer(edgelist8): # node_size assert np.all(node_coll.get_sizes() == np.array([5**2])) - plt.close() + plt.close("all") # max_order parameter max_order = 2 @@ -484,7 +484,7 @@ def test_draw_multilayer(edgelist8): offsets = node_coll2.get_offsets() assert offsets.shape[0] == H.num_nodes # nodes - plt.close() + plt.close("all") # conn_lines parameter ax3, (node_coll3, edge_coll3) = xgi.draw_multilayer(H, conn_lines=False) @@ -503,7 +503,7 @@ def test_draw_multilayer(edgelist8): + num_dyad_collections == len(ax3.collections) ) - plt.close() + plt.close("all") # custom parameters pos = xgi.circular_layout(H) From 9868d9546668d194422ce17a5241d0d248ad0b7b Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Sun, 27 Oct 2024 21:02:36 -0400 Subject: [PATCH 09/18] Update xgi_data.py --- xgi/readwrite/xgi_data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xgi/readwrite/xgi_data.py b/xgi/readwrite/xgi_data.py index 920d0b45e..7c65960af 100644 --- a/xgi/readwrite/xgi_data.py +++ b/xgi/readwrite/xgi_data.py @@ -168,6 +168,7 @@ def _request_from_xgi_data( if "incidences" in jsondata: H = from_hif_dict(H, nodetype=nodetype, edgetype=edgetype) return cut_to_order(H, order=max_order) + if "type" in jsondata and jsondata["type"] != "collection": collection = {} for name, data in jsondata["datasets"].items(): From 8cad73b31edaab3411efa4b47f7d287eff39ee5c Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Sun, 27 Oct 2024 21:48:53 -0400 Subject: [PATCH 10/18] Update xgi_data.py --- xgi/readwrite/xgi_data.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/xgi/readwrite/xgi_data.py b/xgi/readwrite/xgi_data.py index 7c65960af..15ab8bceb 100644 --- a/xgi/readwrite/xgi_data.py +++ b/xgi/readwrite/xgi_data.py @@ -109,8 +109,6 @@ def download_xgi_data(dataset, path="", collection_name=""): The name of the collection of data (if any). If `dataset` is not a collection, this argument is unused. """ - from ..readwrite import write_json - index_url = "https://raw.githubusercontent.com/xgi-org/xgi-data/main/index.json" index_data = request_json_from_url(index_url) From 6938d26a25f3df2b280ee79ca16c7c0689572beb Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Mon, 28 Oct 2024 14:05:09 -0400 Subject: [PATCH 11/18] remove other changes --- xgi/readwrite/xgi_data.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/xgi/readwrite/xgi_data.py b/xgi/readwrite/xgi_data.py index 15ab8bceb..1c6fcedae 100644 --- a/xgi/readwrite/xgi_data.py +++ b/xgi/readwrite/xgi_data.py @@ -6,7 +6,6 @@ from ..convert import cut_to_order, from_hif_dict, from_hypergraph_dict from ..exception import XGIError from ..utils import request_json_from_url, request_json_from_url_cached -from .hif import write_hif, write_hif_collection __all__ = ["load_xgi_data", "download_xgi_data"] @@ -59,9 +58,9 @@ def load_xgi_data( if read: cfp = join(path, dataset + ".json") if exists(cfp): - from ..readwrite import read_hif + from ..readwrite import read_json - return read_hif(cfp, nodetype=nodetype, edgetype=edgetype) + return read_json(cfp, nodetype=nodetype, edgetype=edgetype) else: warn( f"No local copy was found at {cfp}. The data is requested " @@ -109,6 +108,8 @@ def download_xgi_data(dataset, path="", collection_name=""): The name of the collection of data (if any). If `dataset` is not a collection, this argument is unused. """ + from ..readwrite import write_json + index_url = "https://raw.githubusercontent.com/xgi-org/xgi-data/main/index.json" index_data = request_json_from_url(index_url) @@ -124,10 +125,10 @@ def download_xgi_data(dataset, path="", collection_name=""): url, nodetype=None, edgetype=None, max_order=None, cache=True ) if isinstance(H, dict): - write_hif_collection(H, path, collection_name=collection_name) + write_json(H, path, collection_name=collection_name) else: filename = join(path, key + ".json") - write_hif(H, filename) + write_json(H, filename) def _request_from_xgi_data( @@ -166,8 +167,8 @@ def _request_from_xgi_data( if "incidences" in jsondata: H = from_hif_dict(H, nodetype=nodetype, edgetype=edgetype) return cut_to_order(H, order=max_order) - - if "type" in jsondata and jsondata["type"] != "collection": + + if "type" in jsondata and jsondata["type"] == "collection": collection = {} for name, data in jsondata["datasets"].items(): relpath = data["relative-path"] From b3b135c645c5520d5ceec3756bdc180286c2836e Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Tue, 29 Oct 2024 14:57:24 -0400 Subject: [PATCH 12/18] added unit tests --- HOW_TO_CONTRIBUTE.md | 2 +- tests/convert/test_hif_dict.py | 226 +++++++++++++++++++++++++++++++++ tests/readwrite/test_hif.py | 63 +++++++-- xgi/readwrite/hif.py | 2 +- 4 files changed, 282 insertions(+), 11 deletions(-) create mode 100644 tests/convert/test_hif_dict.py diff --git a/HOW_TO_CONTRIBUTE.md b/HOW_TO_CONTRIBUTE.md index 796c871c6..b82501c64 100644 --- a/HOW_TO_CONTRIBUTE.md +++ b/HOW_TO_CONTRIBUTE.md @@ -9,7 +9,7 @@ Please note we have a [code of conduct](/CODE_OF_CONDUCT.md), please follow it i 1. Download the dependencies in the developer [requirements file](/requirements/developer.txt). 2. Add unit tests for features being added or bugs being fixed. 3. Include any new method/function in the corresponding docs file. -4. Run `pytest` to verify all unit tests pass. +4. Run `pytest` to verify all unit tests pass. (If you want to see the overall coverage, run `pytest --cov=xgi tests` and to see the missing lines, run `pytest --cov-report term-missing --cov=xgi tests`) 5. Identify the unnecessary imports in the 1. source code by running `pylint xgi/ --disable=all --enable W0611` 2. notebooks by running `nbqa pylint . --disable=all --enable W0611` diff --git a/tests/convert/test_hif_dict.py b/tests/convert/test_hif_dict.py new file mode 100644 index 000000000..b2b263daf --- /dev/null +++ b/tests/convert/test_hif_dict.py @@ -0,0 +1,226 @@ +import pytest + +import xgi + + +def test_to_hif_dict( + edgelist1, + hyperwithdupsandattrs, + simplicialcomplex1, + diedgedict1, + dihyperwithattrs, +): + H = xgi.Hypergraph(edgelist1) + d = xgi.to_hif_dict(H) + + assert "nodes" not in d + assert "edges" not in d + assert "incidences" in d + assert "network-type" in d + assert d["network-type"] == "undirected" + + incidences = [ + {"edge": 0, "node": 1}, + {"edge": 0, "node": 2}, + {"edge": 0, "node": 3}, + {"edge": 1, "node": 4}, + {"edge": 2, "node": 5}, + {"edge": 2, "node": 6}, + {"edge": 3, "node": 6}, + {"edge": 3, "node": 7}, + {"edge": 3, "node": 8}, + ] + assert sorted(d["incidences"], key=lambda x: (x["edge"], x["node"])) == incidences + + # hypergraph with attributes + hyperwithdupsandattrs["name"] = "test" + d = xgi.to_hif_dict(hyperwithdupsandattrs) + + assert "nodes" in d + assert "edges" in d + assert "incidences" in d + assert "network-type" in d + assert d["network-type"] == "undirected" + assert "metadata" in d + assert d["metadata"] == {"name": "test"} + + nodes = [ + {"node": 1, "attrs": {"color": "red", "name": "horse"}}, + {"node": 2, "attrs": {"color": "blue", "name": "pony"}}, + {"node": 3, "attrs": {"color": "yellow", "name": "zebra"}}, + {"node": 4, "attrs": {"color": "red", "name": "orangutan", "age": 20}}, + {"node": 5, "attrs": {"color": "blue", "name": "fish", "age": 2}}, + ] + + edges = [ + {"edge": 0, "attrs": {"color": "blue"}}, + {"edge": 1, "attrs": {"color": "red", "weight": 2}}, + {"edge": 2, "attrs": {"color": "yellow"}}, + {"edge": 3, "attrs": {"color": "purple"}}, + {"edge": 4, "attrs": {"color": "purple", "name": "test"}}, + ] + + incidences = [ + {"edge": 0, "node": 1}, + {"edge": 0, "node": 2}, + {"edge": 1, "node": 1}, + {"edge": 1, "node": 2}, + {"edge": 2, "node": 1}, + {"edge": 2, "node": 2}, + {"edge": 3, "node": 3}, + {"edge": 3, "node": 4}, + {"edge": 3, "node": 5}, + {"edge": 4, "node": 3}, + {"edge": 4, "node": 4}, + {"edge": 4, "node": 5}, + ] + + assert sorted(d["nodes"], key=lambda x: x["node"]) == nodes + assert d["edges"] == edges + assert sorted(d["incidences"], key=lambda x: (x["edge"], x["node"])) == incidences + + # Simplicial complexes + d = xgi.to_hif_dict(simplicialcomplex1) + + assert "nodes" not in d + assert "edges" not in d + assert "incidences" in d + assert "network-type" in d + assert d["network-type"] == "asc" + + incidences = [ + {"edge": "e1", "node": 0}, + {"edge": "e1", "node": "b"}, + {"edge": "e2", "node": 0}, + {"edge": "e2", "node": "c"}, + {"edge": "e3", "node": 0}, + {"edge": "e3", "node": "b"}, + {"edge": "e3", "node": "c"}, + {"edge": "e4", "node": "b"}, + {"edge": "e4", "node": "c"}, + ] + + def _mixed(ele): + return (0, int(ele)) if isinstance(ele, int) else (1, ele) + + sorted_incidences = sorted( + d["incidences"], key=lambda x: (_mixed(x["edge"]), _mixed(x["node"])) + ) + assert sorted_incidences == incidences + + # dihypergraphs without attributes + H = xgi.DiHypergraph(diedgedict1) + + d = xgi.to_hif_dict(H) + + assert "nodes" not in d + assert "edges" not in d + assert "incidences" in d + + # dihypergraphs with attributes + d = xgi.to_hif_dict(dihyperwithattrs) + + assert "nodes" in d + assert "edges" in d + assert "incidences" in d + + +def test_from_hif_dict( + hyperwithdupsandattrs, + simplicialcomplex1, + dihyperwithattrs, +): + d = xgi.to_hif_dict(hyperwithdupsandattrs) + + # test basic import + H = xgi.from_hif_dict(d) + + assert isinstance(H, xgi.Hypergraph) + assert (H.num_nodes, H.num_edges) == (5, 5) + assert set(H.nodes) == {1, 2, 3, 4, 5} + assert H.nodes[1] == {"color": "red", "name": "horse"} + assert H.nodes[2] == {"color": "blue", "name": "pony"} + assert H.nodes[3] == {"color": "yellow", "name": "zebra"} + assert H.nodes[4] == {"color": "red", "name": "orangutan", "age": 20} + assert H.nodes[5] == {"color": "blue", "name": "fish", "age": 2} + + assert set(H.edges) == {0, 1, 2, 3, 4} + assert H.edges[0] == {"color": "blue"} + assert H.edges[1] == {"color": "red", "weight": 2} + assert H.edges[2] == {"color": "yellow"} + assert H.edges[3] == {"color": "purple"} + assert H.edges[4] == {"color": "purple", "name": "test"} + + edgedict = {0: {1, 2}, 1: {1, 2}, 2: {1, 2}, 3: {3, 4, 5}, 4: {3, 4, 5}} + assert H.edges.members(dtype=dict) == edgedict + + # cast nodes and edges + H = xgi.from_hif_dict(d, nodetype=str, edgetype=float) + assert set(H.nodes) == {"1", "2", "3", "4", "5"} + assert set(H.edges) == {0.0, 1.0, 2.0, 3.0, 4.0} + + assert H.nodes["1"] == {"color": "red", "name": "horse"} + assert H.edges[0.0] == {"color": "blue"} + + edgedict = { + 0: {"1", "2"}, + 1: {"1", "2"}, + 2: {"1", "2"}, + 3: {"3", "4", "5"}, + 4: {"3", "4", "5"}, + } + assert H.edges.members(dtype=dict) == edgedict + + ds = xgi.to_hif_dict(simplicialcomplex1) + + S = xgi.from_hif_dict(ds) + assert isinstance(S, xgi.SimplicialComplex) + assert (S.num_nodes, S.num_edges) == (3, 4) + + assert set(S.nodes) == {0, "b", "c"} + assert set(S.edges) == {"e1", "e2", "e3", "e4"} + + # dihypergraphs + d = xgi.to_hif_dict(dihyperwithattrs) + + DH = xgi.from_hif_dict(d) + assert (DH.num_nodes, DH.num_edges) == (6, 3) + assert isinstance(DH, xgi.DiHypergraph) + assert set(DH.nodes) == {0, 1, 2, 3, 4, 5} + assert set(DH.edges) == {0, 1, 2} + + edgedict = {0: ({0, 1}, {2}), 1: ({1, 2}, {4}), 2: ({2, 3, 4}, {4, 5})} + assert DH.edges.dimembers(dtype=dict) == edgedict + + # test error checking + with pytest.raises(TypeError): + S = xgi.from_hif_dict(ds, edgetype=int) + + # metadata + hyperwithdupsandattrs["name"] = "test" + d = xgi.to_hif_dict(hyperwithdupsandattrs) + H = xgi.from_hif_dict(d) + + assert H["name"] == "test" + + # test isolates and empty edges + H = xgi.Hypergraph() + H.add_nodes_from(range(5)) + H.add_edges_from([[1, 2, 3], []]) + + d = xgi.to_hif_dict(H) + + H = xgi.from_hif_dict(d) + assert H.edges.size.aslist() == [3, 0] + assert set(H.nodes.isolates()) == {0, 4} + + H = xgi.DiHypergraph() + H.add_nodes_from(range(5)) + H.add_edges_from([([1, 2, 3], [2, 4]), [[], []]]) + + d = xgi.to_hif_dict(H) + + H = xgi.from_hif_dict(d) + + assert H.edges.size.aslist() == [4, 0] + assert set(H.nodes.isolates()) == {0} diff --git a/tests/readwrite/test_hif.py b/tests/readwrite/test_hif.py index 62ae6173b..b12b280b5 100644 --- a/tests/readwrite/test_hif.py +++ b/tests/readwrite/test_hif.py @@ -1,12 +1,13 @@ import json import tempfile +from os.path import join import pytest import xgi -def test_to_hif( +def test_write_hif( edgelist1, hyperwithdupsandattrs, simplicialcomplex1, @@ -153,7 +154,7 @@ def _mixed(ele): assert "incidences" in jsondata -def test_from_hif( +def test_read_hif( hyperwithdupsandattrs, simplicialcomplex1, dihyperwithattrs, @@ -247,13 +248,57 @@ def test_from_hif( assert H.edges.size.aslist() == [3, 0] assert set(H.nodes.isolates()) == {0, 4} - H = xgi.DiHypergraph() - H.add_nodes_from(range(5)) - H.add_edges_from([([1, 2, 3], [2, 4]), [[], []]]) + DH = xgi.DiHypergraph() + DH.add_nodes_from(range(5)) + DH.add_edges_from([([1, 2, 3], [2, 4]), [[], []]]) _, filename6 = tempfile.mkstemp() - xgi.write_hif(H, filename6) + xgi.write_hif(DH, filename6) + + DH = xgi.read_hif(filename6) + + assert DH.edges.size.aslist() == [4, 0] + assert set(DH.nodes.isolates()) == {0} + + +def test_read_hif_collection(): + # test isolates and empty edges + H = xgi.Hypergraph() + H.add_nodes_from(range(5)) + H.add_edges_from([[1, 2, 3], []]) + + _, filename5 = tempfile.mkstemp() + xgi.write_hif(H, filename5) + + H = xgi.read_hif(filename5) + assert H.edges.size.aslist() == [3, 0] + assert set(H.nodes.isolates()) == {0, 4} + + DH = xgi.DiHypergraph() + DH.add_nodes_from(range(5)) + DH.add_edges_from([([1, 2, 3], [2, 4]), [[], []]]) + # test collections - H = xgi.read_hif(filename6) - # assert H.edges.size.aslist() == [5, 0] - # assert set(H.nodes.isolates()) == {0} + # test list collection + collection = [H, DH] + tempdir = tempfile.mkdtemp() + + xgi.write_hif_collection(collection, tempdir, collection_name="test") + collection = xgi.read_hif_collection( + join(tempdir, "test_collection_information.json") + ) + assert len(collection) == 2 + assert isinstance(collection, dict) + assert sorted(collection) == ["0", "1"] + + # test dict collection + collection = {"dataset1": H, "dataset2": DH} + tempdir = tempfile.mkdtemp() + + xgi.write_hif_collection(collection, tempdir, collection_name="test") + collection = xgi.read_hif_collection( + join(tempdir, "test_collection_information.json") + ) + assert len(collection) == 2 + assert isinstance(collection, dict) + assert sorted(collection) == ["dataset1", "dataset2"] diff --git a/xgi/readwrite/hif.py b/xgi/readwrite/hif.py index a94dec244..1b8dec9c4 100644 --- a/xgi/readwrite/hif.py +++ b/xgi/readwrite/hif.py @@ -11,7 +11,7 @@ from ..convert import from_hif_dict, to_hif_dict from ..exception import XGIError -__all__ = ["write_hif", "read_hif"] +__all__ = ["write_hif", "write_hif_collection", "read_hif", "read_hif_collection"] def write_hif(H, path): From aa40c3a563897ee5da9a86f8faef1948f3d830c1 Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Tue, 29 Oct 2024 15:16:43 -0400 Subject: [PATCH 13/18] Update xgi_data.py --- xgi/readwrite/xgi_data.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xgi/readwrite/xgi_data.py b/xgi/readwrite/xgi_data.py index 1c6fcedae..e29225253 100644 --- a/xgi/readwrite/xgi_data.py +++ b/xgi/readwrite/xgi_data.py @@ -166,7 +166,9 @@ def _request_from_xgi_data( if "incidences" in jsondata: H = from_hif_dict(H, nodetype=nodetype, edgetype=edgetype) - return cut_to_order(H, order=max_order) + if max_order: + H = cut_to_order(H, order=max_order) + return H if "type" in jsondata and jsondata["type"] == "collection": collection = {} From b281209a5239b0dc2bfb5e386d8af3cc0e68bfe3 Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Sun, 24 Nov 2024 11:32:45 -0500 Subject: [PATCH 14/18] Update HOW_TO_CONTRIBUTE.md --- HOW_TO_CONTRIBUTE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HOW_TO_CONTRIBUTE.md b/HOW_TO_CONTRIBUTE.md index e722fcd4e..77eca0c75 100644 --- a/HOW_TO_CONTRIBUTE.md +++ b/HOW_TO_CONTRIBUTE.md @@ -9,7 +9,7 @@ Please note we have a [code of conduct](/CODE_OF_CONDUCT.md), please follow it i 1. Download the dependencies in the developer [requirements file](/requirements/developer.txt). 2. Add unit tests for features being added or bugs being fixed. 3. Include any new method/function in the corresponding docs file. -4. Run `pytest` to verify all unit tests pass. (If you want to see the overall test coverage, run `pytest --cov=xgi tests` and to see the lines of code not covered by tests, run `pytest --cov-report term-missing --cov=xgi tests`) +4. Run `pytest` to verify all unit tests pass. (To see what lines are covered, read the [`pytest-cov`](https://pytest-cov.readthedocs.io/en/latest/reporting.html) documentation.) 5. [OPTIONAL] Format codebase according to the steps below. 5. Submit Pull Request with a list of changes, links to issues that it addresses (if applicable) 6. You may merge the Pull Request in once you have the sign-off of at least one other developer, or if you do not have permission to do that, you may request the reviewer to merge it for you. From 763299b2048a32bbee749af79716dd71d016cd4c Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Tue, 26 Nov 2024 13:26:06 -0500 Subject: [PATCH 15/18] response to review --- docs/source/xgi-data.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/xgi-data.rst b/docs/source/xgi-data.rst index 5b964bdc1..a4c9dc6d5 100644 --- a/docs/source/xgi-data.rst +++ b/docs/source/xgi-data.rst @@ -30,7 +30,7 @@ See the `load_bigg_data() documentation `_ documentation on format (1). Format (2) is structured as follows: * :code:`hypergraph-data`: This tag accesses the attributes of the entire hypergraph dataset such as the authors or dataset name. From e30221074abeca2efa6c610302d4d5a24366790d Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Wed, 27 Nov 2024 13:15:58 -0500 Subject: [PATCH 16/18] Response to review --- docs/source/xgi-data.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/xgi-data.rst b/docs/source/xgi-data.rst index a4c9dc6d5..1d36ab925 100644 --- a/docs/source/xgi-data.rst +++ b/docs/source/xgi-data.rst @@ -30,7 +30,9 @@ See the `load_bigg_data() documentation `_ documentation on format (1). Format (2) is structured as follows: +XGI-DATA provides higher-order datasets in two formats: (1) Hypergraph Interchange Format (HIF)-compliant JSON (2) XGI-specific JSON format. See the `HIF-standard `_ documentation on format (1). +All future datasets and updates to current datasets will be stored as format (1). +Format (2) is structured as follows: * :code:`hypergraph-data`: This tag accesses the attributes of the entire hypergraph dataset such as the authors or dataset name. From e5b515abc8932f490c4daeb6a3941e54d343508b Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Mon, 2 Dec 2024 16:19:40 -0500 Subject: [PATCH 17/18] Update docs.txt --- requirements/docs.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/docs.txt b/requirements/docs.txt index ebca68425..e19920f62 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -11,3 +11,4 @@ ipykernel nbsphinx nbsphinx-link pydata-sphinx-theme +sphinx-gallery From 84588a9db4df2fd2407b6ac460b5a45a2621382d Mon Sep 17 00:00:00 2001 From: Nicholas Landry Date: Mon, 2 Dec 2024 16:20:58 -0500 Subject: [PATCH 18/18] Update release.txt --- requirements/release.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements/release.txt b/requirements/release.txt index acb0418ff..4e41020a0 100644 --- a/requirements/release.txt +++ b/requirements/release.txt @@ -3,4 +3,3 @@ twine>=3.4 build>=1.2.1 wheel>=0.36 -github-changelog