From b494116bebcf4cfa2c1832b52040316bb412c519 Mon Sep 17 00:00:00 2001 From: Rob Rossmiller Date: Tue, 21 May 2024 09:31:59 -0400 Subject: [PATCH 01/38] tests in. run --- .../workflows/tests.yml | 0 .../degree_centrality/CompleteUnweighted.json | 38 +++++++++++ .../degree_centrality/CompleteWeighted.json | 38 +++++++++++ tests/data/complete.png | Bin 0 -> 90799 bytes tests/data/create_baseline.py | 59 ++++++++++++++++++ .../complete_edges.csv | 0 .../complete_edges_directed.csv | 0 .../dag_edges.csv | 0 .../empty_graph_edges.csv | 0 .../hubspoke_connected_spoke_edges.csv | 0 .../hubspoke_edges.csv | 0 .../line_edges.csv | 0 .../mulithub_shared_spoke_edges.csv | 0 .../ring_edges.csv | 0 .../tree_edges.csv | 0 .../complete_edges.csv | 0 .../complete_edges_directed.csv | 0 .../dag_edges.csv | 0 .../data/weighted_edges/empty_graph_edges.csv | 0 .../hubspoke_connected_spoke_edges.csv | 0 .../hubspoke_edges.csv | 0 .../line_edges.csv | 0 .../mulithub_shared_spoke_edges.csv | 0 .../negative_cycles_edges.csv | 0 .../ring_edges.csv | 0 .../tree_edges.csv | 0 tests/test/test_centrality.py | 2 + 27 files changed, 137 insertions(+) rename tests/data/unweighted edges/empty_graph_edges.csv => .github/workflows/tests.yml (100%) create mode 100644 tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/CompleteUnweighted.json create mode 100644 tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/CompleteWeighted.json create mode 100644 tests/data/complete.png create mode 100644 tests/data/create_baseline.py rename tests/data/{unweighted edges => unweighted_edges}/complete_edges.csv (100%) rename tests/data/{unweighted edges => unweighted_edges}/complete_edges_directed.csv (100%) rename tests/data/{unweighted edges => unweighted_edges}/dag_edges.csv (100%) rename tests/data/{weighted edges => unweighted_edges}/empty_graph_edges.csv (100%) rename tests/data/{unweighted edges => unweighted_edges}/hubspoke_connected_spoke_edges.csv (100%) rename tests/data/{unweighted edges => unweighted_edges}/hubspoke_edges.csv (100%) rename tests/data/{unweighted edges => unweighted_edges}/line_edges.csv (100%) rename tests/data/{unweighted edges => unweighted_edges}/mulithub_shared_spoke_edges.csv (100%) rename tests/data/{unweighted edges => unweighted_edges}/ring_edges.csv (100%) rename tests/data/{unweighted edges => unweighted_edges}/tree_edges.csv (100%) rename tests/data/{weighted edges => weighted_edges}/complete_edges.csv (100%) rename tests/data/{weighted edges => weighted_edges}/complete_edges_directed.csv (100%) rename tests/data/{weighted edges => weighted_edges}/dag_edges.csv (100%) create mode 100644 tests/data/weighted_edges/empty_graph_edges.csv rename tests/data/{weighted edges => weighted_edges}/hubspoke_connected_spoke_edges.csv (100%) rename tests/data/{weighted edges => weighted_edges}/hubspoke_edges.csv (100%) rename tests/data/{weighted edges => weighted_edges}/line_edges.csv (100%) rename tests/data/{weighted edges => weighted_edges}/mulithub_shared_spoke_edges.csv (100%) rename tests/data/{weighted edges => weighted_edges}/negative_cycles_edges.csv (100%) rename tests/data/{weighted edges => weighted_edges}/ring_edges.csv (100%) rename tests/data/{weighted edges => weighted_edges}/tree_edges.csv (100%) diff --git a/tests/data/unweighted edges/empty_graph_edges.csv b/.github/workflows/tests.yml similarity index 100% rename from tests/data/unweighted edges/empty_graph_edges.csv rename to .github/workflows/tests.yml diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/CompleteUnweighted.json b/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/CompleteUnweighted.json new file mode 100644 index 00000000..2228929b --- /dev/null +++ b/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/CompleteUnweighted.json @@ -0,0 +1,38 @@ +[ + { + "@@top_scores_heap": [ + { + "Vertex_ID": "A", + "score": 1.2857142857142856 + }, + { + "Vertex_ID": "B", + "score": 1.2857142857142856 + }, + { + "Vertex_ID": "C", + "score": 1.2857142857142856 + }, + { + "Vertex_ID": "D", + "score": 1.2857142857142856 + }, + { + "Vertex_ID": "E", + "score": 1.2857142857142856 + }, + { + "Vertex_ID": "F", + "score": 1.2857142857142856 + }, + { + "Vertex_ID": "G", + "score": 1.2857142857142856 + }, + { + "Vertex_ID": "H", + "score": 1.2857142857142856 + } + ] + } +] \ No newline at end of file diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/CompleteWeighted.json b/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/CompleteWeighted.json new file mode 100644 index 00000000..2228929b --- /dev/null +++ b/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/CompleteWeighted.json @@ -0,0 +1,38 @@ +[ + { + "@@top_scores_heap": [ + { + "Vertex_ID": "A", + "score": 1.2857142857142856 + }, + { + "Vertex_ID": "B", + "score": 1.2857142857142856 + }, + { + "Vertex_ID": "C", + "score": 1.2857142857142856 + }, + { + "Vertex_ID": "D", + "score": 1.2857142857142856 + }, + { + "Vertex_ID": "E", + "score": 1.2857142857142856 + }, + { + "Vertex_ID": "F", + "score": 1.2857142857142856 + }, + { + "Vertex_ID": "G", + "score": 1.2857142857142856 + }, + { + "Vertex_ID": "H", + "score": 1.2857142857142856 + } + ] + } +] \ No newline at end of file diff --git a/tests/data/complete.png b/tests/data/complete.png new file mode 100644 index 0000000000000000000000000000000000000000..b7f922d035c8fc8a0e19e9bbaf82aa9524a18237 GIT binary patch literal 90799 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

FAt4H*O4S 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 Date: Tue, 21 May 2024 12:11:55 -0400 Subject: [PATCH 02/38] must divide by possible edge value --- algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql b/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql index e3efb225..59903c87 100644 --- a/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql +++ b/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql @@ -49,7 +49,7 @@ CREATE QUERY tg_degree_cent(SET v_type_set, SET e_type_set, SET< TYPEDEF TUPLE Vertex_Score; HeapAccum(top_k, score DESC) @@top_scores_heap; - SumAccum @sum_degree_score; + SumAccum @sum_degree_score; FILE f (file_path); all = {v_type_set}; @@ -64,7 +64,8 @@ CREATE QUERY tg_degree_cent(SET v_type_set, SET e_type_set, SET< FOREACH edge_type in e_type_set DO s.@sum_degree_score+=s.outdegree(edge_type) END - END; + END + POST-ACCUM s.@sum_degree_score = s.@sum_degree_score / ( all.size()-1 ); #Output IF file_path != "" THEN f.println("Vertex_ID", "Degree"); From e95e9f7794b6bac1212eca766a3a416f1e4d075f Mon Sep 17 00:00:00 2001 From: Rob Rossmiller Date: Tue, 21 May 2024 12:12:16 -0400 Subject: [PATCH 03/38] update degree dataset --- ...{CompleteUnweighted.json => Complete.json} | 18 +++--- .../degree_centrality/CompleteWeighted.json | 38 ------------ tests/data/create_baseline.py | 29 ++++----- tests/run.sh | 6 +- tests/test/test_centrality.py | 60 ++++++++++++++----- 5 files changed, 73 insertions(+), 78 deletions(-) rename tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/{CompleteUnweighted.json => Complete.json} (53%) delete mode 100644 tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/CompleteWeighted.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/CompleteUnweighted.json b/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/Complete.json similarity index 53% rename from tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/CompleteUnweighted.json rename to tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/Complete.json index 2228929b..ab4692cd 100644 --- a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/CompleteUnweighted.json +++ b/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/Complete.json @@ -1,37 +1,37 @@ [ { - "@@top_scores_heap": [ + "top_scores": [ { "Vertex_ID": "A", - "score": 1.2857142857142856 + "score": 1.1428571428571428 }, { "Vertex_ID": "B", - "score": 1.2857142857142856 + "score": 1.1428571428571428 }, { "Vertex_ID": "C", - "score": 1.2857142857142856 + "score": 1.1428571428571428 }, { "Vertex_ID": "D", - "score": 1.2857142857142856 + "score": 1.1428571428571428 }, { "Vertex_ID": "E", - "score": 1.2857142857142856 + "score": 1.1428571428571428 }, { "Vertex_ID": "F", - "score": 1.2857142857142856 + "score": 1.1428571428571428 }, { "Vertex_ID": "G", - "score": 1.2857142857142856 + "score": 1.1428571428571428 }, { "Vertex_ID": "H", - "score": 1.2857142857142856 + "score": 1.1428571428571428 } ] } diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/CompleteWeighted.json b/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/CompleteWeighted.json deleted file mode 100644 index 2228929b..00000000 --- a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/CompleteWeighted.json +++ /dev/null @@ -1,38 +0,0 @@ -[ - { - "@@top_scores_heap": [ - { - "Vertex_ID": "A", - "score": 1.2857142857142856 - }, - { - "Vertex_ID": "B", - "score": 1.2857142857142856 - }, - { - "Vertex_ID": "C", - "score": 1.2857142857142856 - }, - { - "Vertex_ID": "D", - "score": 1.2857142857142856 - }, - { - "Vertex_ID": "E", - "score": 1.2857142857142856 - }, - { - "Vertex_ID": "F", - "score": 1.2857142857142856 - }, - { - "Vertex_ID": "G", - "score": 1.2857142857142856 - }, - { - "Vertex_ID": "H", - "score": 1.2857142857142856 - } - ] - } -] \ No newline at end of file diff --git a/tests/data/create_baseline.py b/tests/data/create_baseline.py index 6cd7bf1e..43c55cc6 100644 --- a/tests/data/create_baseline.py +++ b/tests/data/create_baseline.py @@ -3,26 +3,27 @@ import networkx as nx import numpy as np +from matplotlib import pyplot as plt baseline_path_root = "baseline/graph_algorithms_baselines" def run_degree_baseline(g: nx.Graph): - res = nx.centrality.degree_centrality(g) - nx.centrality.degree_centrality + # res = nx.centrality.degree_centrality(g) + s = 1.0 / (len(g) - 1.0) + res = {n: (d-1) * s for n, d in g.degree()} # d-1 because nx will double count the self-edge out = [] for k, v in res.items(): out.append({"Vertex_ID": k, "score": v}) - out = [{"@@top_scores_heap": out}] + out = [{"top_scores": out}] return out -def create_graph(path, edges, weights): +def create_graph(edges, weights): g = nx.Graph() - # include edge weights if they exist - if weights is not None: + if weights: g.add_weighted_edges_from(edges) else: g.add_edges_from(edges) @@ -30,25 +31,25 @@ def create_graph(path, edges, weights): def create_degree_baseline(): - # input, output + # input, output, weighed paths = [ ( "unweighted_edges/complete_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/CompleteUnweighted.json", + f"{baseline_path_root}/centrality/degree_centrality/Complete.json", False, ), - ( - "weighted_edges/complete_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/CompleteWeighted.json", - True, - ), + # ( + # "weighted_edges/complete_edges.csv", + # f"{baseline_path_root}/centrality/weighted_degree_centrality/CompleteWeighted.json", + # True, + # ), ] for p, o_path, w in paths: with open(p) as f: edges = np.array(list(csv.reader(f))) - g = create_graph(p, edges, w) + g = create_graph(edges, w) res = run_degree_baseline(g) with open(o_path, "w") as f: diff --git a/tests/run.sh b/tests/run.sh index df88fcc5..298110a6 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -1,4 +1,6 @@ clear -python test/setup.py && - pytest +# python test/setup.py && +pytest test/test_centrality.py::TestCentrality::test_degree_centrality4 +# pytest test/test_centrality.py + # pytest # pytest --junitxml "output.xml" #-n 4 diff --git a/tests/test/test_centrality.py b/tests/test/test_centrality.py index d06036e4..a14bfcf9 100644 --- a/tests/test/test_centrality.py +++ b/tests/test/test_centrality.py @@ -7,7 +7,13 @@ class TestCentrality: feat = util.get_featurizer() # undirected graphs - graph_types1 = ["Empty", "Line", "Ring", "Hub_Spoke", "Tree"] + graph_types1 = [ + "Empty", + "Line", + "Ring", + "Hub_Spoke", + "Tree", + ] # directed graphs graph_types2 = [ "Line_Directed", @@ -21,7 +27,6 @@ class TestCentrality: "Ring_Weighted", "Hub_Spoke_Weighted", "Tree_Weighted", - "CompleteWeighted", ] # weighted directed graphs graph_types4 = [ @@ -29,8 +34,9 @@ class TestCentrality: "Ring_Directed_Weighted", "Hub_Spoke_Directed_Weighted", "Tree_Directed_Weighted", - "CompleteUnweighted", ] + # Complete Graphs + graph_types5 = ["Complete"] @pytest.mark.parametrize("test_name", graph_types1) def test_degree_centrality1(self, test_name): @@ -57,7 +63,7 @@ def test_degree_centrality1(self, test_name): for b in baseline: found = False for r in result: - if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == r["score"]: + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == b["score"]: found = True if not found: pytest.fail() @@ -87,7 +93,7 @@ def test_degree_centrality2(self, test_name): for b in baseline: found = False for r in result: - if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == r["score"]: + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == b["score"]: found = True if not found: pytest.fail() @@ -117,11 +123,35 @@ def test_degree_centrality3(self, test_name): for b in baseline: found = False for r in result: - if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == r["score"]: + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == b["score"]: found = True if not found: pytest.fail() + @pytest.mark.parametrize("test_name", graph_types5) + def test_degree_centrality4(self, test_name): + params = { + "v_type_set": ["V8"], + "e_type_set": [test_name], + "reverse_e_type_set": ["reverse_" + test_name], + "in_degree": False, + "out_degree": True, + "print_results": True, + } + with open( + f"data/baseline/graph_algorithms_baselines/centrality/degree_centrality/{test_name}.json" + ) as f: + baseline = json.load(f) + + result = self.feat.runAlgorithm("tg_degree_cent", params=params) + result = sorted(result[0]["top_scores"], key=lambda x: x["Vertex_ID"]) + baseline = sorted(baseline[0]["top_scores"], key=lambda x: x["Vertex_ID"]) + + for b in baseline: + for r in result: + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] != pytest.approx(b["score"]): + pytest.fail(f'{r["score"]} != {b["score"]}') + @pytest.mark.parametrize("test_name", graph_types3) def test_weighted_degree_centrality1(self, test_name): params = { @@ -147,7 +177,7 @@ def test_weighted_degree_centrality1(self, test_name): for b in baseline: found = False for r in result: - if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == r["score"]: + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == b["score"]: found = True if not found: pytest.fail() @@ -177,7 +207,7 @@ def test_weighted_degree_centrality2(self, test_name): for b in baseline: found = False for r in result: - if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == r["score"]: + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == b["score"]: found = True if not found: pytest.fail() @@ -207,7 +237,7 @@ def test_weighted_degree_centrality3(self, test_name): for b in baseline: found = False for r in result: - if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == r["score"]: + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == b["score"]: found = True if not found: pytest.fail() @@ -237,7 +267,7 @@ def test_closeness_centrality(self, test_name): for b in baseline: found = False for r in result: - if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == r["score"]: + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == b["score"]: found = True if not found: pytest.fail() @@ -267,7 +297,7 @@ def test_closeness_centrality2(self, test_name): for b in baseline: found = False for r in result: - if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == r["score"]: + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == b["score"]: found = True if not found: pytest.fail() @@ -297,7 +327,7 @@ def test_harmonic_centrality(self, test_name): for b in baseline: found = False for r in result: - if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == r["score"]: + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == b["score"]: found = True if not found: pytest.fail() @@ -327,7 +357,7 @@ def test_harmonic_centrality2(self, test_name): for b in baseline: found = False for r in result: - if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == r["score"]: + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == b["score"]: found = True if not found: pytest.fail() @@ -358,7 +388,7 @@ def test_article_rank(self, test_name): for b in baseline: found = False for r in result: - if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == r["score"]: + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == b["score"]: found = True if not found: pytest.fail() @@ -390,7 +420,7 @@ def test_pagerank(self, test_name): for b in baseline: found = False for r in result: - if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == r["score"]: + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == b["score"]: found = True if not found: pytest.fail() From be4798fec072b3c5c8d800a485d486445eb2a63f Mon Sep 17 00:00:00 2001 From: Rob Rossmiller Date: Tue, 21 May 2024 12:20:22 -0400 Subject: [PATCH 04/38] mv pth --- .github/workflows/tests.yml | 0 .../centrality/article_rank/Empty.json | 0 .../centrality/article_rank/Hub_Spoke.json | 0 .../centrality/article_rank/Hub_Spoke_Directed.json | 0 .../centrality/article_rank/Line.json | 0 .../centrality/article_rank/Line_Directed.json | 0 .../centrality/article_rank/Ring.json | 0 .../centrality/article_rank/Ring_Directed.json | 0 .../centrality/article_rank/Tree.json | 0 .../centrality/article_rank/Tree_Directed.json | 0 .../centrality/closeness_centrality/Empty.json | 0 .../centrality/closeness_centrality/Hub_Spoke.json | 0 .../centrality/closeness_centrality/Hub_Spoke_Directed.json | 0 .../centrality/closeness_centrality/Line.json | 0 .../centrality/closeness_centrality/Line_Directed.json | 0 .../centrality/closeness_centrality/Ring.json | 0 .../centrality/closeness_centrality/Ring_Directed.json | 0 .../centrality/closeness_centrality/Tree.json | 0 .../centrality/closeness_centrality/Tree_Directed.json | 0 .../centrality/degree_centrality/Complete.json | 0 .../centrality/degree_centrality/Empty.json | 0 .../centrality/degree_centrality/Hub_Spoke.json | 0 .../centrality/degree_centrality/Line.json | 0 .../centrality/degree_centrality/Ring.json | 0 .../centrality/degree_centrality/Tree.json | 0 .../degree_centrality/in_degree/Hub_Spoke_Directed.json | 0 .../degree_centrality/in_degree/Line_Directed.json | 0 .../degree_centrality/in_degree/Ring_Directed.json | 0 .../degree_centrality/in_degree/Tree_Directed.json | 0 .../degree_centrality/out_degree/Hub_Spoke_Directed.json | 0 .../degree_centrality/out_degree/Line_Directed.json | 0 .../degree_centrality/out_degree/Ring_Directed.json | 0 .../degree_centrality/out_degree/Tree_Directed.json | 0 .../centrality/harmonic_centrality/Empty.json | 0 .../centrality/harmonic_centrality/Hub_Spoke.json | 0 .../centrality/harmonic_centrality/Hub_Spoke_Directed.json | 0 .../centrality/harmonic_centrality/Line.json | 0 .../centrality/harmonic_centrality/Line_Directed.json | 0 .../centrality/harmonic_centrality/Ring.json | 0 .../centrality/harmonic_centrality/Ring_Directed.json | 0 .../centrality/harmonic_centrality/Tree.json | 0 .../centrality/harmonic_centrality/Tree_Directed.json | 0 .../centrality/pagerank/Empty.json | 0 .../centrality/pagerank/Hub_Spoke.json | 0 .../centrality/pagerank/Hub_Spoke_Directed.json | 0 .../centrality/pagerank/Line.json | 0 .../centrality/pagerank/Line_Directed.json | 0 .../centrality/pagerank/Ring.json | 0 .../centrality/pagerank/Ring_Directed.json | 0 .../centrality/pagerank/Tree.json | 0 .../centrality/pagerank/Tree_Directed.json | 0 .../weighted_degree_centrality/Hub_Spoke_Weighted.json | 0 .../centrality/weighted_degree_centrality/Line_Weighted.json | 0 .../centrality/weighted_degree_centrality/Ring_Weighted.json | 0 .../centrality/weighted_degree_centrality/Tree_Weighted.json | 0 .../in_degree/Hub_Spoke_Directed_Weighted.json | 0 .../in_degree/Line_Directed_Weighted.json | 0 .../in_degree/Ring_Directed_Weighted.json | 0 .../in_degree/Tree_Directed_Weighted.json | 0 .../out_degree/Hub_Spoke_Directed_Weighted.json | 0 .../out_degree/Line_Directed_Weighted.json | 0 .../out_degree/Ring_Directed_Weighted.json | 0 .../out_degree/Tree_Directed_Weighted.json | 0 .../community/lcc/Complete.json | 0 .../community/lcc/Complete_Directed.json | 0 .../community/lcc/DAG_Directed.json | 0 .../community/lcc/Empty.json | 0 .../community/lcc/Empty_Directed.json | 0 .../community/lcc/Hub_Connected_Hub_Spoke.json | 0 .../community/lcc/Hub_Spoke.json | 0 .../community/lcc/Hub_Spoke_Directed.json | 0 .../{graph_algorithms_baselines => }/community/lcc/Line.json | 0 .../community/lcc/Line_Directed.json | 0 .../community/lcc/Line_Weighted.json | 0 .../{graph_algorithms_baselines => }/community/lcc/Ring.json | 0 .../community/lcc/Ring_Directed.json | 0 .../{graph_algorithms_baselines => }/community/lcc/Tree.json | 0 .../community/lcc/Tree_Directed.json | 0 .../path_finding/bfs/Complete.json | 0 .../path_finding/bfs/Complete_Directed.json | 0 .../path_finding/bfs/DAG_Directed.json | 0 .../path_finding/bfs/Empty.json | 0 .../path_finding/bfs/Empty_Directed.json | 0 .../path_finding/bfs/Hub_Connected_Hub_Spoke.json | 0 .../path_finding/bfs/Hub_Spoke.json | 0 .../path_finding/bfs/Hub_Spoke_Directed.json | 0 .../path_finding/bfs/Line.json | 0 .../path_finding/bfs/Line_Directed.json | 0 .../path_finding/bfs/Line_Weighted.json | 0 .../path_finding/bfs/Ring.json | 0 .../path_finding/bfs/Ring_Directed.json | 0 .../path_finding/bfs/Tree.json | 0 .../path_finding/bfs/Tree_Directed.json | 0 .../path_finding/shortest_ss_no_wt/Complete.json | 0 .../path_finding/shortest_ss_no_wt/Complete_Directed.json | 0 .../path_finding/shortest_ss_no_wt/DAG_Directed.json | 0 .../path_finding/shortest_ss_no_wt/Empty.json | 0 .../path_finding/shortest_ss_no_wt/Empty_Directed.json | 0 .../shortest_ss_no_wt/Hub_Connected_Hub_Spoke.json | 0 .../path_finding/shortest_ss_no_wt/Hub_Spoke.json | 0 .../path_finding/shortest_ss_no_wt/Hub_Spoke_Directed.json | 0 .../path_finding/shortest_ss_no_wt/Line.json | 0 .../path_finding/shortest_ss_no_wt/Line_Directed.json | 0 .../path_finding/shortest_ss_no_wt/Line_Weighted.json | 0 .../path_finding/shortest_ss_no_wt/Ring.json | 0 .../path_finding/shortest_ss_no_wt/Ring_Directed.json | 0 .../path_finding/shortest_ss_no_wt/Tree.json | 0 .../path_finding/shortest_ss_no_wt/Tree_Directed.json | 0 .../topological_link_prediction/adamic_adar/topo_link1.json | 0 .../topological_link_prediction/adamic_adar/topo_link2.json | 0 .../topological_link_prediction/adamic_adar/topo_link3.json | 0 .../topological_link_prediction/adamic_adar/topo_link4.json | 0 .../topological_link_prediction/adamic_adar/topo_link5.json | 0 .../topological_link_prediction/adamic_adar/topo_link6.json | 0 .../adamic_adar/topo_link_directed.json | 0 .../common_neighbors/topo_link1.json | 0 .../common_neighbors/topo_link2.json | 0 .../common_neighbors/topo_link3.json | 0 .../common_neighbors/topo_link4.json | 0 .../common_neighbors/topo_link5.json | 0 .../common_neighbors/topo_link6.json | 0 .../common_neighbors/topo_link_directed.json | 0 .../preferential_attachment/topo_link1.json | 0 .../preferential_attachment/topo_link2.json | 0 .../preferential_attachment/topo_link3.json | 0 .../preferential_attachment/topo_link4.json | 0 .../preferential_attachment/topo_link5.json | 0 .../preferential_attachment/topo_link6.json | 0 .../preferential_attachment/topo_link_directed.json | 0 .../resource_allocation/topo_link1.json | 0 .../resource_allocation/topo_link2.json | 0 .../resource_allocation/topo_link3.json | 0 .../resource_allocation/topo_link4.json | 0 .../resource_allocation/topo_link5.json | 0 .../resource_allocation/topo_link6.json | 0 .../resource_allocation/topo_link_directed.json | 0 .../topological_link_prediction/same_community/test1.json | 0 .../topological_link_prediction/same_community/test2.json | 0 .../topological_link_prediction/same_community/test3.json | 0 .../topological_link_prediction/same_community/test4.json | 0 .../total_neighbors/topo_link1.json | 0 .../total_neighbors/topo_link2.json | 0 .../total_neighbors/topo_link3.json | 0 .../total_neighbors/topo_link4.json | 0 .../total_neighbors/topo_link5.json | 0 .../total_neighbors/topo_link6.json | 0 .../total_neighbors/topo_link_directed.json | 0 tests/data/create_baseline.py | 5 +++++ 148 files changed, 5 insertions(+) delete mode 100644 .github/workflows/tests.yml rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/article_rank/Empty.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/article_rank/Hub_Spoke.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/article_rank/Hub_Spoke_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/article_rank/Line.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/article_rank/Line_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/article_rank/Ring.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/article_rank/Ring_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/article_rank/Tree.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/article_rank/Tree_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/closeness_centrality/Empty.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/closeness_centrality/Hub_Spoke.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/closeness_centrality/Hub_Spoke_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/closeness_centrality/Line.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/closeness_centrality/Line_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/closeness_centrality/Ring.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/closeness_centrality/Ring_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/closeness_centrality/Tree.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/closeness_centrality/Tree_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/degree_centrality/Complete.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/degree_centrality/Empty.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/degree_centrality/Hub_Spoke.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/degree_centrality/Line.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/degree_centrality/Ring.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/degree_centrality/Tree.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/degree_centrality/in_degree/Hub_Spoke_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/degree_centrality/in_degree/Line_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/degree_centrality/in_degree/Ring_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/degree_centrality/in_degree/Tree_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/degree_centrality/out_degree/Hub_Spoke_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/degree_centrality/out_degree/Line_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/degree_centrality/out_degree/Ring_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/degree_centrality/out_degree/Tree_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/harmonic_centrality/Empty.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/harmonic_centrality/Hub_Spoke.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/harmonic_centrality/Hub_Spoke_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/harmonic_centrality/Line.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/harmonic_centrality/Line_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/harmonic_centrality/Ring.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/harmonic_centrality/Ring_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/harmonic_centrality/Tree.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/harmonic_centrality/Tree_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/pagerank/Empty.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/pagerank/Hub_Spoke.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/pagerank/Hub_Spoke_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/pagerank/Line.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/pagerank/Line_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/pagerank/Ring.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/pagerank/Ring_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/pagerank/Tree.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/pagerank/Tree_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/weighted_degree_centrality/Line_Weighted.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/weighted_degree_centrality/Ring_Weighted.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/weighted_degree_centrality/Tree_Weighted.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/community/lcc/Complete.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/community/lcc/Complete_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/community/lcc/DAG_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/community/lcc/Empty.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/community/lcc/Empty_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/community/lcc/Hub_Connected_Hub_Spoke.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/community/lcc/Hub_Spoke.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/community/lcc/Hub_Spoke_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/community/lcc/Line.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/community/lcc/Line_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/community/lcc/Line_Weighted.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/community/lcc/Ring.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/community/lcc/Ring_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/community/lcc/Tree.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/community/lcc/Tree_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/bfs/Complete.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/bfs/Complete_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/bfs/DAG_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/bfs/Empty.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/bfs/Empty_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/bfs/Hub_Connected_Hub_Spoke.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/bfs/Hub_Spoke.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/bfs/Hub_Spoke_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/bfs/Line.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/bfs/Line_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/bfs/Line_Weighted.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/bfs/Ring.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/bfs/Ring_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/bfs/Tree.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/bfs/Tree_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/shortest_ss_no_wt/Complete.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/shortest_ss_no_wt/Complete_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/shortest_ss_no_wt/DAG_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/shortest_ss_no_wt/Empty.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/shortest_ss_no_wt/Empty_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/shortest_ss_no_wt/Hub_Connected_Hub_Spoke.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/shortest_ss_no_wt/Hub_Spoke.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/shortest_ss_no_wt/Hub_Spoke_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/shortest_ss_no_wt/Line.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/shortest_ss_no_wt/Line_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/shortest_ss_no_wt/Line_Weighted.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/shortest_ss_no_wt/Ring.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/shortest_ss_no_wt/Ring_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/shortest_ss_no_wt/Tree.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/path_finding/shortest_ss_no_wt/Tree_Directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/adamic_adar/topo_link1.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/adamic_adar/topo_link2.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/adamic_adar/topo_link3.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/adamic_adar/topo_link4.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/adamic_adar/topo_link5.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/adamic_adar/topo_link6.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/adamic_adar/topo_link_directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/common_neighbors/topo_link1.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/common_neighbors/topo_link2.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/common_neighbors/topo_link3.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/common_neighbors/topo_link4.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/common_neighbors/topo_link5.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/common_neighbors/topo_link6.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/common_neighbors/topo_link_directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/preferential_attachment/topo_link1.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/preferential_attachment/topo_link2.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/preferential_attachment/topo_link3.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/preferential_attachment/topo_link4.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/preferential_attachment/topo_link5.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/preferential_attachment/topo_link6.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/preferential_attachment/topo_link_directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/resource_allocation/topo_link1.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/resource_allocation/topo_link2.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/resource_allocation/topo_link3.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/resource_allocation/topo_link4.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/resource_allocation/topo_link5.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/resource_allocation/topo_link6.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/resource_allocation/topo_link_directed.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/same_community/test1.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/same_community/test2.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/same_community/test3.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/same_community/test4.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/total_neighbors/topo_link1.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/total_neighbors/topo_link2.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/total_neighbors/topo_link3.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/total_neighbors/topo_link4.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/total_neighbors/topo_link5.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/total_neighbors/topo_link6.json (100%) rename tests/data/baseline/{graph_algorithms_baselines => }/topological_link_prediction/total_neighbors/topo_link_directed.json (100%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Empty.json b/tests/data/baseline/centrality/article_rank/Empty.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Empty.json rename to tests/data/baseline/centrality/article_rank/Empty.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Hub_Spoke.json b/tests/data/baseline/centrality/article_rank/Hub_Spoke.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Hub_Spoke.json rename to tests/data/baseline/centrality/article_rank/Hub_Spoke.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Hub_Spoke_Directed.json b/tests/data/baseline/centrality/article_rank/Hub_Spoke_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Hub_Spoke_Directed.json rename to tests/data/baseline/centrality/article_rank/Hub_Spoke_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Line.json b/tests/data/baseline/centrality/article_rank/Line.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Line.json rename to tests/data/baseline/centrality/article_rank/Line.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Line_Directed.json b/tests/data/baseline/centrality/article_rank/Line_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Line_Directed.json rename to tests/data/baseline/centrality/article_rank/Line_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Ring.json b/tests/data/baseline/centrality/article_rank/Ring.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Ring.json rename to tests/data/baseline/centrality/article_rank/Ring.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Ring_Directed.json b/tests/data/baseline/centrality/article_rank/Ring_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Ring_Directed.json rename to tests/data/baseline/centrality/article_rank/Ring_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Tree.json b/tests/data/baseline/centrality/article_rank/Tree.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Tree.json rename to tests/data/baseline/centrality/article_rank/Tree.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Tree_Directed.json b/tests/data/baseline/centrality/article_rank/Tree_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/article_rank/Tree_Directed.json rename to tests/data/baseline/centrality/article_rank/Tree_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Empty.json b/tests/data/baseline/centrality/closeness_centrality/Empty.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Empty.json rename to tests/data/baseline/centrality/closeness_centrality/Empty.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Hub_Spoke.json b/tests/data/baseline/centrality/closeness_centrality/Hub_Spoke.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Hub_Spoke.json rename to tests/data/baseline/centrality/closeness_centrality/Hub_Spoke.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Hub_Spoke_Directed.json b/tests/data/baseline/centrality/closeness_centrality/Hub_Spoke_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Hub_Spoke_Directed.json rename to tests/data/baseline/centrality/closeness_centrality/Hub_Spoke_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Line.json b/tests/data/baseline/centrality/closeness_centrality/Line.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Line.json rename to tests/data/baseline/centrality/closeness_centrality/Line.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Line_Directed.json b/tests/data/baseline/centrality/closeness_centrality/Line_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Line_Directed.json rename to tests/data/baseline/centrality/closeness_centrality/Line_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Ring.json b/tests/data/baseline/centrality/closeness_centrality/Ring.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Ring.json rename to tests/data/baseline/centrality/closeness_centrality/Ring.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Ring_Directed.json b/tests/data/baseline/centrality/closeness_centrality/Ring_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Ring_Directed.json rename to tests/data/baseline/centrality/closeness_centrality/Ring_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Tree.json b/tests/data/baseline/centrality/closeness_centrality/Tree.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Tree.json rename to tests/data/baseline/centrality/closeness_centrality/Tree.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Tree_Directed.json b/tests/data/baseline/centrality/closeness_centrality/Tree_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/Tree_Directed.json rename to tests/data/baseline/centrality/closeness_centrality/Tree_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/Complete.json b/tests/data/baseline/centrality/degree_centrality/Complete.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/Complete.json rename to tests/data/baseline/centrality/degree_centrality/Complete.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/Empty.json b/tests/data/baseline/centrality/degree_centrality/Empty.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/Empty.json rename to tests/data/baseline/centrality/degree_centrality/Empty.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/Hub_Spoke.json b/tests/data/baseline/centrality/degree_centrality/Hub_Spoke.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/Hub_Spoke.json rename to tests/data/baseline/centrality/degree_centrality/Hub_Spoke.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/Line.json b/tests/data/baseline/centrality/degree_centrality/Line.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/Line.json rename to tests/data/baseline/centrality/degree_centrality/Line.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/Ring.json b/tests/data/baseline/centrality/degree_centrality/Ring.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/Ring.json rename to tests/data/baseline/centrality/degree_centrality/Ring.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/Tree.json b/tests/data/baseline/centrality/degree_centrality/Tree.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/Tree.json rename to tests/data/baseline/centrality/degree_centrality/Tree.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/in_degree/Hub_Spoke_Directed.json b/tests/data/baseline/centrality/degree_centrality/in_degree/Hub_Spoke_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/in_degree/Hub_Spoke_Directed.json rename to tests/data/baseline/centrality/degree_centrality/in_degree/Hub_Spoke_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/in_degree/Line_Directed.json b/tests/data/baseline/centrality/degree_centrality/in_degree/Line_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/in_degree/Line_Directed.json rename to tests/data/baseline/centrality/degree_centrality/in_degree/Line_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/in_degree/Ring_Directed.json b/tests/data/baseline/centrality/degree_centrality/in_degree/Ring_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/in_degree/Ring_Directed.json rename to tests/data/baseline/centrality/degree_centrality/in_degree/Ring_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/in_degree/Tree_Directed.json b/tests/data/baseline/centrality/degree_centrality/in_degree/Tree_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/in_degree/Tree_Directed.json rename to tests/data/baseline/centrality/degree_centrality/in_degree/Tree_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/out_degree/Hub_Spoke_Directed.json b/tests/data/baseline/centrality/degree_centrality/out_degree/Hub_Spoke_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/out_degree/Hub_Spoke_Directed.json rename to tests/data/baseline/centrality/degree_centrality/out_degree/Hub_Spoke_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/out_degree/Line_Directed.json b/tests/data/baseline/centrality/degree_centrality/out_degree/Line_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/out_degree/Line_Directed.json rename to tests/data/baseline/centrality/degree_centrality/out_degree/Line_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/out_degree/Ring_Directed.json b/tests/data/baseline/centrality/degree_centrality/out_degree/Ring_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/out_degree/Ring_Directed.json rename to tests/data/baseline/centrality/degree_centrality/out_degree/Ring_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/out_degree/Tree_Directed.json b/tests/data/baseline/centrality/degree_centrality/out_degree/Tree_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/degree_centrality/out_degree/Tree_Directed.json rename to tests/data/baseline/centrality/degree_centrality/out_degree/Tree_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Empty.json b/tests/data/baseline/centrality/harmonic_centrality/Empty.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Empty.json rename to tests/data/baseline/centrality/harmonic_centrality/Empty.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Hub_Spoke.json b/tests/data/baseline/centrality/harmonic_centrality/Hub_Spoke.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Hub_Spoke.json rename to tests/data/baseline/centrality/harmonic_centrality/Hub_Spoke.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Hub_Spoke_Directed.json b/tests/data/baseline/centrality/harmonic_centrality/Hub_Spoke_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Hub_Spoke_Directed.json rename to tests/data/baseline/centrality/harmonic_centrality/Hub_Spoke_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Line.json b/tests/data/baseline/centrality/harmonic_centrality/Line.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Line.json rename to tests/data/baseline/centrality/harmonic_centrality/Line.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Line_Directed.json b/tests/data/baseline/centrality/harmonic_centrality/Line_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Line_Directed.json rename to tests/data/baseline/centrality/harmonic_centrality/Line_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Ring.json b/tests/data/baseline/centrality/harmonic_centrality/Ring.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Ring.json rename to tests/data/baseline/centrality/harmonic_centrality/Ring.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Ring_Directed.json b/tests/data/baseline/centrality/harmonic_centrality/Ring_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Ring_Directed.json rename to tests/data/baseline/centrality/harmonic_centrality/Ring_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Tree.json b/tests/data/baseline/centrality/harmonic_centrality/Tree.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Tree.json rename to tests/data/baseline/centrality/harmonic_centrality/Tree.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Tree_Directed.json b/tests/data/baseline/centrality/harmonic_centrality/Tree_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/Tree_Directed.json rename to tests/data/baseline/centrality/harmonic_centrality/Tree_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Empty.json b/tests/data/baseline/centrality/pagerank/Empty.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Empty.json rename to tests/data/baseline/centrality/pagerank/Empty.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Hub_Spoke.json b/tests/data/baseline/centrality/pagerank/Hub_Spoke.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Hub_Spoke.json rename to tests/data/baseline/centrality/pagerank/Hub_Spoke.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Hub_Spoke_Directed.json b/tests/data/baseline/centrality/pagerank/Hub_Spoke_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Hub_Spoke_Directed.json rename to tests/data/baseline/centrality/pagerank/Hub_Spoke_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Line.json b/tests/data/baseline/centrality/pagerank/Line.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Line.json rename to tests/data/baseline/centrality/pagerank/Line.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Line_Directed.json b/tests/data/baseline/centrality/pagerank/Line_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Line_Directed.json rename to tests/data/baseline/centrality/pagerank/Line_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Ring.json b/tests/data/baseline/centrality/pagerank/Ring.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Ring.json rename to tests/data/baseline/centrality/pagerank/Ring.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Ring_Directed.json b/tests/data/baseline/centrality/pagerank/Ring_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Ring_Directed.json rename to tests/data/baseline/centrality/pagerank/Ring_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Tree.json b/tests/data/baseline/centrality/pagerank/Tree.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Tree.json rename to tests/data/baseline/centrality/pagerank/Tree.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Tree_Directed.json b/tests/data/baseline/centrality/pagerank/Tree_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/pagerank/Tree_Directed.json rename to tests/data/baseline/centrality/pagerank/Tree_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json rename to tests/data/baseline/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/Line_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/Line_Weighted.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/Line_Weighted.json rename to tests/data/baseline/centrality/weighted_degree_centrality/Line_Weighted.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/Ring_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/Ring_Weighted.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/Ring_Weighted.json rename to tests/data/baseline/centrality/weighted_degree_centrality/Ring_Weighted.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/Tree_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/Tree_Weighted.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/Tree_Weighted.json rename to tests/data/baseline/centrality/weighted_degree_centrality/Tree_Weighted.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json rename to tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json rename to tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json rename to tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json rename to tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json rename to tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json rename to tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json rename to tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json diff --git a/tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json rename to tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json diff --git a/tests/data/baseline/graph_algorithms_baselines/community/lcc/Complete.json b/tests/data/baseline/community/lcc/Complete.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/community/lcc/Complete.json rename to tests/data/baseline/community/lcc/Complete.json diff --git a/tests/data/baseline/graph_algorithms_baselines/community/lcc/Complete_Directed.json b/tests/data/baseline/community/lcc/Complete_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/community/lcc/Complete_Directed.json rename to tests/data/baseline/community/lcc/Complete_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/community/lcc/DAG_Directed.json b/tests/data/baseline/community/lcc/DAG_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/community/lcc/DAG_Directed.json rename to tests/data/baseline/community/lcc/DAG_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/community/lcc/Empty.json b/tests/data/baseline/community/lcc/Empty.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/community/lcc/Empty.json rename to tests/data/baseline/community/lcc/Empty.json diff --git a/tests/data/baseline/graph_algorithms_baselines/community/lcc/Empty_Directed.json b/tests/data/baseline/community/lcc/Empty_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/community/lcc/Empty_Directed.json rename to tests/data/baseline/community/lcc/Empty_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/community/lcc/Hub_Connected_Hub_Spoke.json b/tests/data/baseline/community/lcc/Hub_Connected_Hub_Spoke.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/community/lcc/Hub_Connected_Hub_Spoke.json rename to tests/data/baseline/community/lcc/Hub_Connected_Hub_Spoke.json diff --git a/tests/data/baseline/graph_algorithms_baselines/community/lcc/Hub_Spoke.json b/tests/data/baseline/community/lcc/Hub_Spoke.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/community/lcc/Hub_Spoke.json rename to tests/data/baseline/community/lcc/Hub_Spoke.json diff --git a/tests/data/baseline/graph_algorithms_baselines/community/lcc/Hub_Spoke_Directed.json b/tests/data/baseline/community/lcc/Hub_Spoke_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/community/lcc/Hub_Spoke_Directed.json rename to tests/data/baseline/community/lcc/Hub_Spoke_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/community/lcc/Line.json b/tests/data/baseline/community/lcc/Line.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/community/lcc/Line.json rename to tests/data/baseline/community/lcc/Line.json diff --git a/tests/data/baseline/graph_algorithms_baselines/community/lcc/Line_Directed.json b/tests/data/baseline/community/lcc/Line_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/community/lcc/Line_Directed.json rename to tests/data/baseline/community/lcc/Line_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/community/lcc/Line_Weighted.json b/tests/data/baseline/community/lcc/Line_Weighted.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/community/lcc/Line_Weighted.json rename to tests/data/baseline/community/lcc/Line_Weighted.json diff --git a/tests/data/baseline/graph_algorithms_baselines/community/lcc/Ring.json b/tests/data/baseline/community/lcc/Ring.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/community/lcc/Ring.json rename to tests/data/baseline/community/lcc/Ring.json diff --git a/tests/data/baseline/graph_algorithms_baselines/community/lcc/Ring_Directed.json b/tests/data/baseline/community/lcc/Ring_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/community/lcc/Ring_Directed.json rename to tests/data/baseline/community/lcc/Ring_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/community/lcc/Tree.json b/tests/data/baseline/community/lcc/Tree.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/community/lcc/Tree.json rename to tests/data/baseline/community/lcc/Tree.json diff --git a/tests/data/baseline/graph_algorithms_baselines/community/lcc/Tree_Directed.json b/tests/data/baseline/community/lcc/Tree_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/community/lcc/Tree_Directed.json rename to tests/data/baseline/community/lcc/Tree_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Complete.json b/tests/data/baseline/path_finding/bfs/Complete.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Complete.json rename to tests/data/baseline/path_finding/bfs/Complete.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Complete_Directed.json b/tests/data/baseline/path_finding/bfs/Complete_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Complete_Directed.json rename to tests/data/baseline/path_finding/bfs/Complete_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/DAG_Directed.json b/tests/data/baseline/path_finding/bfs/DAG_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/DAG_Directed.json rename to tests/data/baseline/path_finding/bfs/DAG_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Empty.json b/tests/data/baseline/path_finding/bfs/Empty.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Empty.json rename to tests/data/baseline/path_finding/bfs/Empty.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Empty_Directed.json b/tests/data/baseline/path_finding/bfs/Empty_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Empty_Directed.json rename to tests/data/baseline/path_finding/bfs/Empty_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Hub_Connected_Hub_Spoke.json b/tests/data/baseline/path_finding/bfs/Hub_Connected_Hub_Spoke.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Hub_Connected_Hub_Spoke.json rename to tests/data/baseline/path_finding/bfs/Hub_Connected_Hub_Spoke.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Hub_Spoke.json b/tests/data/baseline/path_finding/bfs/Hub_Spoke.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Hub_Spoke.json rename to tests/data/baseline/path_finding/bfs/Hub_Spoke.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Hub_Spoke_Directed.json b/tests/data/baseline/path_finding/bfs/Hub_Spoke_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Hub_Spoke_Directed.json rename to tests/data/baseline/path_finding/bfs/Hub_Spoke_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Line.json b/tests/data/baseline/path_finding/bfs/Line.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Line.json rename to tests/data/baseline/path_finding/bfs/Line.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Line_Directed.json b/tests/data/baseline/path_finding/bfs/Line_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Line_Directed.json rename to tests/data/baseline/path_finding/bfs/Line_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Line_Weighted.json b/tests/data/baseline/path_finding/bfs/Line_Weighted.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Line_Weighted.json rename to tests/data/baseline/path_finding/bfs/Line_Weighted.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Ring.json b/tests/data/baseline/path_finding/bfs/Ring.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Ring.json rename to tests/data/baseline/path_finding/bfs/Ring.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Ring_Directed.json b/tests/data/baseline/path_finding/bfs/Ring_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Ring_Directed.json rename to tests/data/baseline/path_finding/bfs/Ring_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Tree.json b/tests/data/baseline/path_finding/bfs/Tree.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Tree.json rename to tests/data/baseline/path_finding/bfs/Tree.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Tree_Directed.json b/tests/data/baseline/path_finding/bfs/Tree_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/bfs/Tree_Directed.json rename to tests/data/baseline/path_finding/bfs/Tree_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Complete.json b/tests/data/baseline/path_finding/shortest_ss_no_wt/Complete.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Complete.json rename to tests/data/baseline/path_finding/shortest_ss_no_wt/Complete.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Complete_Directed.json b/tests/data/baseline/path_finding/shortest_ss_no_wt/Complete_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Complete_Directed.json rename to tests/data/baseline/path_finding/shortest_ss_no_wt/Complete_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/DAG_Directed.json b/tests/data/baseline/path_finding/shortest_ss_no_wt/DAG_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/DAG_Directed.json rename to tests/data/baseline/path_finding/shortest_ss_no_wt/DAG_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Empty.json b/tests/data/baseline/path_finding/shortest_ss_no_wt/Empty.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Empty.json rename to tests/data/baseline/path_finding/shortest_ss_no_wt/Empty.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Empty_Directed.json b/tests/data/baseline/path_finding/shortest_ss_no_wt/Empty_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Empty_Directed.json rename to tests/data/baseline/path_finding/shortest_ss_no_wt/Empty_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Hub_Connected_Hub_Spoke.json b/tests/data/baseline/path_finding/shortest_ss_no_wt/Hub_Connected_Hub_Spoke.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Hub_Connected_Hub_Spoke.json rename to tests/data/baseline/path_finding/shortest_ss_no_wt/Hub_Connected_Hub_Spoke.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Hub_Spoke.json b/tests/data/baseline/path_finding/shortest_ss_no_wt/Hub_Spoke.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Hub_Spoke.json rename to tests/data/baseline/path_finding/shortest_ss_no_wt/Hub_Spoke.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Hub_Spoke_Directed.json b/tests/data/baseline/path_finding/shortest_ss_no_wt/Hub_Spoke_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Hub_Spoke_Directed.json rename to tests/data/baseline/path_finding/shortest_ss_no_wt/Hub_Spoke_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Line.json b/tests/data/baseline/path_finding/shortest_ss_no_wt/Line.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Line.json rename to tests/data/baseline/path_finding/shortest_ss_no_wt/Line.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Line_Directed.json b/tests/data/baseline/path_finding/shortest_ss_no_wt/Line_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Line_Directed.json rename to tests/data/baseline/path_finding/shortest_ss_no_wt/Line_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Line_Weighted.json b/tests/data/baseline/path_finding/shortest_ss_no_wt/Line_Weighted.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Line_Weighted.json rename to tests/data/baseline/path_finding/shortest_ss_no_wt/Line_Weighted.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Ring.json b/tests/data/baseline/path_finding/shortest_ss_no_wt/Ring.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Ring.json rename to tests/data/baseline/path_finding/shortest_ss_no_wt/Ring.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Ring_Directed.json b/tests/data/baseline/path_finding/shortest_ss_no_wt/Ring_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Ring_Directed.json rename to tests/data/baseline/path_finding/shortest_ss_no_wt/Ring_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Tree.json b/tests/data/baseline/path_finding/shortest_ss_no_wt/Tree.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Tree.json rename to tests/data/baseline/path_finding/shortest_ss_no_wt/Tree.json diff --git a/tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Tree_Directed.json b/tests/data/baseline/path_finding/shortest_ss_no_wt/Tree_Directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/path_finding/shortest_ss_no_wt/Tree_Directed.json rename to tests/data/baseline/path_finding/shortest_ss_no_wt/Tree_Directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/adamic_adar/topo_link1.json b/tests/data/baseline/topological_link_prediction/adamic_adar/topo_link1.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/adamic_adar/topo_link1.json rename to tests/data/baseline/topological_link_prediction/adamic_adar/topo_link1.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/adamic_adar/topo_link2.json b/tests/data/baseline/topological_link_prediction/adamic_adar/topo_link2.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/adamic_adar/topo_link2.json rename to tests/data/baseline/topological_link_prediction/adamic_adar/topo_link2.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/adamic_adar/topo_link3.json b/tests/data/baseline/topological_link_prediction/adamic_adar/topo_link3.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/adamic_adar/topo_link3.json rename to tests/data/baseline/topological_link_prediction/adamic_adar/topo_link3.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/adamic_adar/topo_link4.json b/tests/data/baseline/topological_link_prediction/adamic_adar/topo_link4.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/adamic_adar/topo_link4.json rename to tests/data/baseline/topological_link_prediction/adamic_adar/topo_link4.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/adamic_adar/topo_link5.json b/tests/data/baseline/topological_link_prediction/adamic_adar/topo_link5.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/adamic_adar/topo_link5.json rename to tests/data/baseline/topological_link_prediction/adamic_adar/topo_link5.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/adamic_adar/topo_link6.json b/tests/data/baseline/topological_link_prediction/adamic_adar/topo_link6.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/adamic_adar/topo_link6.json rename to tests/data/baseline/topological_link_prediction/adamic_adar/topo_link6.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/adamic_adar/topo_link_directed.json b/tests/data/baseline/topological_link_prediction/adamic_adar/topo_link_directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/adamic_adar/topo_link_directed.json rename to tests/data/baseline/topological_link_prediction/adamic_adar/topo_link_directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/common_neighbors/topo_link1.json b/tests/data/baseline/topological_link_prediction/common_neighbors/topo_link1.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/common_neighbors/topo_link1.json rename to tests/data/baseline/topological_link_prediction/common_neighbors/topo_link1.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/common_neighbors/topo_link2.json b/tests/data/baseline/topological_link_prediction/common_neighbors/topo_link2.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/common_neighbors/topo_link2.json rename to tests/data/baseline/topological_link_prediction/common_neighbors/topo_link2.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/common_neighbors/topo_link3.json b/tests/data/baseline/topological_link_prediction/common_neighbors/topo_link3.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/common_neighbors/topo_link3.json rename to tests/data/baseline/topological_link_prediction/common_neighbors/topo_link3.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/common_neighbors/topo_link4.json b/tests/data/baseline/topological_link_prediction/common_neighbors/topo_link4.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/common_neighbors/topo_link4.json rename to tests/data/baseline/topological_link_prediction/common_neighbors/topo_link4.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/common_neighbors/topo_link5.json b/tests/data/baseline/topological_link_prediction/common_neighbors/topo_link5.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/common_neighbors/topo_link5.json rename to tests/data/baseline/topological_link_prediction/common_neighbors/topo_link5.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/common_neighbors/topo_link6.json b/tests/data/baseline/topological_link_prediction/common_neighbors/topo_link6.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/common_neighbors/topo_link6.json rename to tests/data/baseline/topological_link_prediction/common_neighbors/topo_link6.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/common_neighbors/topo_link_directed.json b/tests/data/baseline/topological_link_prediction/common_neighbors/topo_link_directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/common_neighbors/topo_link_directed.json rename to tests/data/baseline/topological_link_prediction/common_neighbors/topo_link_directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/preferential_attachment/topo_link1.json b/tests/data/baseline/topological_link_prediction/preferential_attachment/topo_link1.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/preferential_attachment/topo_link1.json rename to tests/data/baseline/topological_link_prediction/preferential_attachment/topo_link1.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/preferential_attachment/topo_link2.json b/tests/data/baseline/topological_link_prediction/preferential_attachment/topo_link2.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/preferential_attachment/topo_link2.json rename to tests/data/baseline/topological_link_prediction/preferential_attachment/topo_link2.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/preferential_attachment/topo_link3.json b/tests/data/baseline/topological_link_prediction/preferential_attachment/topo_link3.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/preferential_attachment/topo_link3.json rename to tests/data/baseline/topological_link_prediction/preferential_attachment/topo_link3.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/preferential_attachment/topo_link4.json b/tests/data/baseline/topological_link_prediction/preferential_attachment/topo_link4.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/preferential_attachment/topo_link4.json rename to tests/data/baseline/topological_link_prediction/preferential_attachment/topo_link4.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/preferential_attachment/topo_link5.json b/tests/data/baseline/topological_link_prediction/preferential_attachment/topo_link5.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/preferential_attachment/topo_link5.json rename to tests/data/baseline/topological_link_prediction/preferential_attachment/topo_link5.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/preferential_attachment/topo_link6.json b/tests/data/baseline/topological_link_prediction/preferential_attachment/topo_link6.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/preferential_attachment/topo_link6.json rename to tests/data/baseline/topological_link_prediction/preferential_attachment/topo_link6.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/preferential_attachment/topo_link_directed.json b/tests/data/baseline/topological_link_prediction/preferential_attachment/topo_link_directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/preferential_attachment/topo_link_directed.json rename to tests/data/baseline/topological_link_prediction/preferential_attachment/topo_link_directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/resource_allocation/topo_link1.json b/tests/data/baseline/topological_link_prediction/resource_allocation/topo_link1.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/resource_allocation/topo_link1.json rename to tests/data/baseline/topological_link_prediction/resource_allocation/topo_link1.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/resource_allocation/topo_link2.json b/tests/data/baseline/topological_link_prediction/resource_allocation/topo_link2.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/resource_allocation/topo_link2.json rename to tests/data/baseline/topological_link_prediction/resource_allocation/topo_link2.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/resource_allocation/topo_link3.json b/tests/data/baseline/topological_link_prediction/resource_allocation/topo_link3.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/resource_allocation/topo_link3.json rename to tests/data/baseline/topological_link_prediction/resource_allocation/topo_link3.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/resource_allocation/topo_link4.json b/tests/data/baseline/topological_link_prediction/resource_allocation/topo_link4.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/resource_allocation/topo_link4.json rename to tests/data/baseline/topological_link_prediction/resource_allocation/topo_link4.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/resource_allocation/topo_link5.json b/tests/data/baseline/topological_link_prediction/resource_allocation/topo_link5.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/resource_allocation/topo_link5.json rename to tests/data/baseline/topological_link_prediction/resource_allocation/topo_link5.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/resource_allocation/topo_link6.json b/tests/data/baseline/topological_link_prediction/resource_allocation/topo_link6.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/resource_allocation/topo_link6.json rename to tests/data/baseline/topological_link_prediction/resource_allocation/topo_link6.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/resource_allocation/topo_link_directed.json b/tests/data/baseline/topological_link_prediction/resource_allocation/topo_link_directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/resource_allocation/topo_link_directed.json rename to tests/data/baseline/topological_link_prediction/resource_allocation/topo_link_directed.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/same_community/test1.json b/tests/data/baseline/topological_link_prediction/same_community/test1.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/same_community/test1.json rename to tests/data/baseline/topological_link_prediction/same_community/test1.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/same_community/test2.json b/tests/data/baseline/topological_link_prediction/same_community/test2.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/same_community/test2.json rename to tests/data/baseline/topological_link_prediction/same_community/test2.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/same_community/test3.json b/tests/data/baseline/topological_link_prediction/same_community/test3.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/same_community/test3.json rename to tests/data/baseline/topological_link_prediction/same_community/test3.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/same_community/test4.json b/tests/data/baseline/topological_link_prediction/same_community/test4.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/same_community/test4.json rename to tests/data/baseline/topological_link_prediction/same_community/test4.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/total_neighbors/topo_link1.json b/tests/data/baseline/topological_link_prediction/total_neighbors/topo_link1.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/total_neighbors/topo_link1.json rename to tests/data/baseline/topological_link_prediction/total_neighbors/topo_link1.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/total_neighbors/topo_link2.json b/tests/data/baseline/topological_link_prediction/total_neighbors/topo_link2.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/total_neighbors/topo_link2.json rename to tests/data/baseline/topological_link_prediction/total_neighbors/topo_link2.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/total_neighbors/topo_link3.json b/tests/data/baseline/topological_link_prediction/total_neighbors/topo_link3.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/total_neighbors/topo_link3.json rename to tests/data/baseline/topological_link_prediction/total_neighbors/topo_link3.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/total_neighbors/topo_link4.json b/tests/data/baseline/topological_link_prediction/total_neighbors/topo_link4.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/total_neighbors/topo_link4.json rename to tests/data/baseline/topological_link_prediction/total_neighbors/topo_link4.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/total_neighbors/topo_link5.json b/tests/data/baseline/topological_link_prediction/total_neighbors/topo_link5.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/total_neighbors/topo_link5.json rename to tests/data/baseline/topological_link_prediction/total_neighbors/topo_link5.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/total_neighbors/topo_link6.json b/tests/data/baseline/topological_link_prediction/total_neighbors/topo_link6.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/total_neighbors/topo_link6.json rename to tests/data/baseline/topological_link_prediction/total_neighbors/topo_link6.json diff --git a/tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/total_neighbors/topo_link_directed.json b/tests/data/baseline/topological_link_prediction/total_neighbors/topo_link_directed.json similarity index 100% rename from tests/data/baseline/graph_algorithms_baselines/topological_link_prediction/total_neighbors/topo_link_directed.json rename to tests/data/baseline/topological_link_prediction/total_neighbors/topo_link_directed.json diff --git a/tests/data/create_baseline.py b/tests/data/create_baseline.py index 43c55cc6..3beb4bb6 100644 --- a/tests/data/create_baseline.py +++ b/tests/data/create_baseline.py @@ -38,6 +38,11 @@ def create_degree_baseline(): f"{baseline_path_root}/centrality/degree_centrality/Complete.json", False, ), + ( + "unweighted_edges/line_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/Complete.json", + False, + ), # ( # "weighted_edges/complete_edges.csv", # f"{baseline_path_root}/centrality/weighted_degree_centrality/CompleteWeighted.json", From 98e96e4b3472c8736e5b83017b437aa0c7e37d15 Mon Sep 17 00:00:00 2001 From: Rob Rossmiller Date: Tue, 21 May 2024 21:42:41 -0400 Subject: [PATCH 05/38] redo graph type1 --- .../degree/unweighted/tg_degree_cent.gsql | 92 +++++++++---------- .../degree_centrality/Complete.json | 39 +------- .../degree_centrality/Hub_Spoke.json | 2 +- .../centrality/degree_centrality/Line.json | 2 +- .../centrality/degree_centrality/Ring.json | 2 +- .../centrality/degree_centrality/Tree.json | 2 +- tests/data/baseline/get_data.py | 2 + tests/data/create_baseline.py | 66 +++++++++---- tests/run.sh | 6 +- tests/test/test_centrality.py | 48 +++++----- 10 files changed, 127 insertions(+), 134 deletions(-) diff --git a/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql b/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql index 59903c87..1777c97b 100644 --- a/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql +++ b/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql @@ -1,50 +1,47 @@ -CREATE QUERY tg_degree_cent(SET v_type_set, SET e_type_set, SET reverse_e_type_set, BOOL in_degree = TRUE, BOOL out_degree = TRUE, - INT top_k=100, BOOL print_results = TRUE, STRING result_attribute = "",STRING file_path = "") SYNTAX V1 { - +CREATE QUERY tg_degree_cent(SET v_type_set, SET e_type_set, SET reverse_e_type_set, BOOL in_degree = TRUE, BOOL out_degree = TRUE, INT top_k=100, BOOL print_results = TRUE, STRING result_attribute = "",STRING file_path = "") SYNTAX V1 { /* - First Author: - First Commit Date: - - Recent Author: - Recent Commit Date: + First Author: + First Commit Date: + Recent Author: Rob Rossmiller + Recent Commit Date: 05/2024 - Repository: - https://github.com/tigergraph/gsql-graph-algorithms/tree/master/algorithms/Centrality - Maturity: - Production + Repository: + https://github.com/tigergraph/gsql-graph-algorithms/tree/master/algorithms/Centrality - Description: - Compute degree Centrality for each VERTEX. - for undirected graph, you only need to set e_type_set and indegree + Maturity: + Production - Publications: - NA + Description: + Compute degree Centrality for each VERTEX. + for undirected graph, you only need to set e_type_set and indegree - TigerGraph Documentation: - https://docs.tigergraph.com/graph-ml/current/centrality-algorithms/degree-centrality + Publications: + NA - Parameters: - v_type_set: - vertex types to traverse - e_type_set: - edge types to traverse - reverse_e_type_set: - for indegree use - in_degree: - If True, count incoming relationships - out_degree: - If True, count outcoming relationships - top_k: - report only this many top scores - print_results: - If True, print the result - result_attribute: - attribute to write result to - file_path: - file to write CSV output to + TigerGraph Documentation: + https://docs.tigergraph.com/graph-ml/current/centrality-algorithms/degree-centrality + Parameters: + v_type_set: + vertex types to traverse + e_type_set: + edge types to traverse + reverse_e_type_set: + for indegree use + in_degree: + If True, count incoming relationships + out_degree: + If True, count outcoming relationships + top_k: + report only this many top scores + print_results: + If True, print the result + result_attribute: + attribute to write result to + file_path: + file to write CSV output to */ TYPEDEF TUPLE Vertex_Score; @@ -55,16 +52,17 @@ CREATE QUERY tg_degree_cent(SET v_type_set, SET e_type_set, SET< all = {v_type_set}; sll = SELECT s FROM all:s - ACCUM IF in_degree THEN - FOREACH edge_type in reverse_e_type_set DO - s.@sum_degree_score+=s.outdegree(edge_type) - END - END, - IF out_degree THEN - FOREACH edge_type in e_type_set DO - s.@sum_degree_score+=s.outdegree(edge_type) - END + ACCUM + IF in_degree THEN + FOREACH edge_type in reverse_e_type_set DO + s.@sum_degree_score += s.outdegree(edge_type) + END + END, + IF out_degree THEN + FOREACH edge_type in e_type_set DO + s.@sum_degree_score += s.outdegree(edge_type) END + END POST-ACCUM s.@sum_degree_score = s.@sum_degree_score / ( all.size()-1 ); #Output IF file_path != "" THEN diff --git a/tests/data/baseline/centrality/degree_centrality/Complete.json b/tests/data/baseline/centrality/degree_centrality/Complete.json index ab4692cd..98255ee1 100644 --- a/tests/data/baseline/centrality/degree_centrality/Complete.json +++ b/tests/data/baseline/centrality/degree_centrality/Complete.json @@ -1,38 +1 @@ -[ - { - "top_scores": [ - { - "Vertex_ID": "A", - "score": 1.1428571428571428 - }, - { - "Vertex_ID": "B", - "score": 1.1428571428571428 - }, - { - "Vertex_ID": "C", - "score": 1.1428571428571428 - }, - { - "Vertex_ID": "D", - "score": 1.1428571428571428 - }, - { - "Vertex_ID": "E", - "score": 1.1428571428571428 - }, - { - "Vertex_ID": "F", - "score": 1.1428571428571428 - }, - { - "Vertex_ID": "G", - "score": 1.1428571428571428 - }, - { - "Vertex_ID": "H", - "score": 1.1428571428571428 - } - ] - } -] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 1.1428571428571428}, {"Vertex_ID": "B", "score": 1.1428571428571428}, {"Vertex_ID": "C", "score": 1.1428571428571428}, {"Vertex_ID": "D", "score": 1.1428571428571428}, {"Vertex_ID": "E", "score": 1.1428571428571428}, {"Vertex_ID": "F", "score": 1.1428571428571428}, {"Vertex_ID": "G", "score": 1.1428571428571428}, {"Vertex_ID": "H", "score": 1.1428571428571428}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/degree_centrality/Hub_Spoke.json b/tests/data/baseline/centrality/degree_centrality/Hub_Spoke.json index 6e10710d..b97010da 100644 --- a/tests/data/baseline/centrality/degree_centrality/Hub_Spoke.json +++ b/tests/data/baseline/centrality/degree_centrality/Hub_Spoke.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 19}, {"Vertex_ID": "S", "score": 1}, {"Vertex_ID": "B", "score": 1}, {"Vertex_ID": "L", "score": 1}, {"Vertex_ID": "T", "score": 1}, {"Vertex_ID": "H", "score": 1}, {"Vertex_ID": "G", "score": 1}, {"Vertex_ID": "M", "score": 1}, {"Vertex_ID": "K", "score": 1}, {"Vertex_ID": "N", "score": 1}, {"Vertex_ID": "F", "score": 1}, {"Vertex_ID": "D", "score": 1}, {"Vertex_ID": "E", "score": 1}, {"Vertex_ID": "O", "score": 1}, {"Vertex_ID": "C", "score": 1}, {"Vertex_ID": "I", "score": 1}, {"Vertex_ID": "R", "score": 1}, {"Vertex_ID": "J", "score": 1}, {"Vertex_ID": "P", "score": 1}, {"Vertex_ID": "Q", "score": 1}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 1.0}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/degree_centrality/Line.json b/tests/data/baseline/centrality/degree_centrality/Line.json index f52803e2..70ee11a4 100644 --- a/tests/data/baseline/centrality/degree_centrality/Line.json +++ b/tests/data/baseline/centrality/degree_centrality/Line.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "P", "score": 2}, {"Vertex_ID": "N", "score": 2}, {"Vertex_ID": "C", "score": 2}, {"Vertex_ID": "H", "score": 2}, {"Vertex_ID": "B", "score": 2}, {"Vertex_ID": "M", "score": 2}, {"Vertex_ID": "O", "score": 2}, {"Vertex_ID": "R", "score": 2}, {"Vertex_ID": "K", "score": 2}, {"Vertex_ID": "L", "score": 2}, {"Vertex_ID": "J", "score": 2}, {"Vertex_ID": "Q", "score": 2}, {"Vertex_ID": "D", "score": 2}, {"Vertex_ID": "E", "score": 2}, {"Vertex_ID": "G", "score": 2}, {"Vertex_ID": "F", "score": 2}, {"Vertex_ID": "I", "score": 2}, {"Vertex_ID": "S", "score": 2}, {"Vertex_ID": "A", "score": 1}, {"Vertex_ID": "T", "score": 1}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.05263157894736842}, {"Vertex_ID": "B", "score": 0.10526315789473684}, {"Vertex_ID": "C", "score": 0.10526315789473684}, {"Vertex_ID": "D", "score": 0.10526315789473684}, {"Vertex_ID": "E", "score": 0.10526315789473684}, {"Vertex_ID": "F", "score": 0.10526315789473684}, {"Vertex_ID": "G", "score": 0.10526315789473684}, {"Vertex_ID": "H", "score": 0.10526315789473684}, {"Vertex_ID": "I", "score": 0.10526315789473684}, {"Vertex_ID": "J", "score": 0.10526315789473684}, {"Vertex_ID": "K", "score": 0.10526315789473684}, {"Vertex_ID": "L", "score": 0.10526315789473684}, {"Vertex_ID": "M", "score": 0.10526315789473684}, {"Vertex_ID": "N", "score": 0.10526315789473684}, {"Vertex_ID": "O", "score": 0.10526315789473684}, {"Vertex_ID": "P", "score": 0.10526315789473684}, {"Vertex_ID": "Q", "score": 0.10526315789473684}, {"Vertex_ID": "R", "score": 0.10526315789473684}, {"Vertex_ID": "S", "score": 0.10526315789473684}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/degree_centrality/Ring.json b/tests/data/baseline/centrality/degree_centrality/Ring.json index 6334382b..1f77b5bc 100644 --- a/tests/data/baseline/centrality/degree_centrality/Ring.json +++ b/tests/data/baseline/centrality/degree_centrality/Ring.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "M", "score": 2}, {"Vertex_ID": "Q", "score": 2}, {"Vertex_ID": "N", "score": 2}, {"Vertex_ID": "O", "score": 2}, {"Vertex_ID": "K", "score": 2}, {"Vertex_ID": "C", "score": 2}, {"Vertex_ID": "B", "score": 2}, {"Vertex_ID": "D", "score": 2}, {"Vertex_ID": "E", "score": 2}, {"Vertex_ID": "T", "score": 2}, {"Vertex_ID": "R", "score": 2}, {"Vertex_ID": "F", "score": 2}, {"Vertex_ID": "A", "score": 2}, {"Vertex_ID": "H", "score": 2}, {"Vertex_ID": "G", "score": 2}, {"Vertex_ID": "I", "score": 2}, {"Vertex_ID": "J", "score": 2}, {"Vertex_ID": "S", "score": 2}, {"Vertex_ID": "L", "score": 2}, {"Vertex_ID": "P", "score": 2}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.10526315789473684}, {"Vertex_ID": "B", "score": 0.10526315789473684}, {"Vertex_ID": "C", "score": 0.10526315789473684}, {"Vertex_ID": "D", "score": 0.10526315789473684}, {"Vertex_ID": "E", "score": 0.10526315789473684}, {"Vertex_ID": "F", "score": 0.10526315789473684}, {"Vertex_ID": "G", "score": 0.10526315789473684}, {"Vertex_ID": "H", "score": 0.10526315789473684}, {"Vertex_ID": "I", "score": 0.10526315789473684}, {"Vertex_ID": "J", "score": 0.10526315789473684}, {"Vertex_ID": "K", "score": 0.10526315789473684}, {"Vertex_ID": "L", "score": 0.10526315789473684}, {"Vertex_ID": "M", "score": 0.10526315789473684}, {"Vertex_ID": "N", "score": 0.10526315789473684}, {"Vertex_ID": "O", "score": 0.10526315789473684}, {"Vertex_ID": "P", "score": 0.10526315789473684}, {"Vertex_ID": "Q", "score": 0.10526315789473684}, {"Vertex_ID": "R", "score": 0.10526315789473684}, {"Vertex_ID": "S", "score": 0.10526315789473684}, {"Vertex_ID": "T", "score": 0.10526315789473684}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/degree_centrality/Tree.json b/tests/data/baseline/centrality/degree_centrality/Tree.json index 85d616d3..51cb839d 100644 --- a/tests/data/baseline/centrality/degree_centrality/Tree.json +++ b/tests/data/baseline/centrality/degree_centrality/Tree.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "E", "score": 3}, {"Vertex_ID": "H", "score": 3}, {"Vertex_ID": "B", "score": 3}, {"Vertex_ID": "G", "score": 3}, {"Vertex_ID": "D", "score": 3}, {"Vertex_ID": "F", "score": 3}, {"Vertex_ID": "I", "score": 3}, {"Vertex_ID": "C", "score": 3}, {"Vertex_ID": "J", "score": 2}, {"Vertex_ID": "A", "score": 2}, {"Vertex_ID": "R", "score": 1}, {"Vertex_ID": "T", "score": 1}, {"Vertex_ID": "Q", "score": 1}, {"Vertex_ID": "L", "score": 1}, {"Vertex_ID": "K", "score": 1}, {"Vertex_ID": "N", "score": 1}, {"Vertex_ID": "S", "score": 1}, {"Vertex_ID": "O", "score": 1}, {"Vertex_ID": "M", "score": 1}, {"Vertex_ID": "P", "score": 1}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.10526315789473684}, {"Vertex_ID": "B", "score": 0.15789473684210525}, {"Vertex_ID": "C", "score": 0.15789473684210525}, {"Vertex_ID": "D", "score": 0.15789473684210525}, {"Vertex_ID": "E", "score": 0.15789473684210525}, {"Vertex_ID": "F", "score": 0.15789473684210525}, {"Vertex_ID": "G", "score": 0.15789473684210525}, {"Vertex_ID": "H", "score": 0.15789473684210525}, {"Vertex_ID": "I", "score": 0.15789473684210525}, {"Vertex_ID": "J", "score": 0.10526315789473684}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file diff --git a/tests/data/baseline/get_data.py b/tests/data/baseline/get_data.py index 88e91682..aa0fe666 100644 --- a/tests/data/baseline/get_data.py +++ b/tests/data/baseline/get_data.py @@ -66,3 +66,5 @@ def download_dir(prefix, local, bucket, client=s3_client): local=".", bucket="tigergraph-public-data", ) + + # os.system("mv graph_algorithms_baselines/* .") diff --git a/tests/data/create_baseline.py b/tests/data/create_baseline.py index 3beb4bb6..3789e046 100644 --- a/tests/data/create_baseline.py +++ b/tests/data/create_baseline.py @@ -3,15 +3,16 @@ import networkx as nx import numpy as np -from matplotlib import pyplot as plt +from tqdm import tqdm -baseline_path_root = "baseline/graph_algorithms_baselines" +baseline_path_root = "baseline" -def run_degree_baseline(g: nx.Graph): - # res = nx.centrality.degree_centrality(g) +def run_degree_baseline_complete(g: nx.Graph): s = 1.0 / (len(g) - 1.0) - res = {n: (d-1) * s for n, d in g.degree()} # d-1 because nx will double count the self-edge + + # d-1 because nx will double count the self-edge + res = {n: (d - 1) * s for n, d in g.degree()} out = [] for k, v in res.items(): @@ -21,7 +22,18 @@ def run_degree_baseline(g: nx.Graph): return out -def create_graph(edges, weights): +def run_degree_baseline(g: nx.Graph): + res = nx.centrality.degree_centrality(g) + + out = [] + for k, v in res.items(): + out.append({"Vertex_ID": k, "score": v}) + + out = [{"top_scores": out}] + return out + + +def create_graph(edges, weights=False): g = nx.Graph() if weights: g.add_weighted_edges_from(edges) @@ -36,29 +48,49 @@ def create_degree_baseline(): ( "unweighted_edges/complete_edges.csv", f"{baseline_path_root}/centrality/degree_centrality/Complete.json", - False, + run_degree_baseline_complete, ), ( "unweighted_edges/line_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/Complete.json", - False, + f"{baseline_path_root}/centrality/degree_centrality/Line.json", + run_degree_baseline, + ), + ( + "unweighted_edges/ring_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/Ring.json", + run_degree_baseline, + ), + ( + "unweighted_edges/hubspoke_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/Hub_Spoke.json", + run_degree_baseline, + ), + ( + "unweighted_edges/tree_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/Tree.json", + run_degree_baseline, ), + # do the following directed edges + # "Line_Directed", + # "Ring_Directed", + # "Hub_Spoke_Directed", + # "Tree_Directed", # ( - # "weighted_edges/complete_edges.csv", - # f"{baseline_path_root}/centrality/weighted_degree_centrality/CompleteWeighted.json", - # True, + # "unweighted_edges/tree_edges.csv", + # f"{baseline_path_root}/centrality/degree_centrality/Tree.json", + # run_degree_baseline, # ), ] - for p, o_path, w in paths: + for p, out_path, fn in tqdm(paths): with open(p) as f: edges = np.array(list(csv.reader(f))) - g = create_graph(edges, w) + g = create_graph(edges) - res = run_degree_baseline(g) - with open(o_path, "w") as f: - json.dump(res, f, indent=2) + res = fn(g) + with open(out_path, "w") as f: + json.dump(res, f) # , indent=2) if __name__ == "__main__": diff --git a/tests/run.sh b/tests/run.sh index 298110a6..f81f5606 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -1,6 +1,10 @@ clear +cd data +python3 create_baseline.py +cd .. # python test/setup.py && -pytest test/test_centrality.py::TestCentrality::test_degree_centrality4 +# pytest test/test_centrality.py::TestCentrality::test_degree_centrality4 +pytest test/test_centrality.py::TestCentrality::test_degree_centrality2 # pytest test/test_centrality.py # pytest # pytest --junitxml "output.xml" #-n 4 diff --git a/tests/test/test_centrality.py b/tests/test/test_centrality.py index a14bfcf9..35fa389a 100644 --- a/tests/test/test_centrality.py +++ b/tests/test/test_centrality.py @@ -51,22 +51,20 @@ def test_degree_centrality1(self, test_name): "result_attribute": "", "file_path": "", } - with open( - f"data/baseline/graph_algorithms_baselines/centrality/degree_centrality/{test_name}.json" - ) as f: + with open(f"data/baseline/centrality/degree_centrality/{test_name}.json") as f: baseline = json.load(f) result = self.feat.runAlgorithm("tg_degree_cent", params=params) result = sorted(result[0]["top_scores"], key=lambda x: x["Vertex_ID"]) baseline = sorted(baseline[0]["top_scores"], key=lambda x: x["Vertex_ID"]) + # pytest.fail(str(result)) for b in baseline: - found = False for r in result: - if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] == b["score"]: - found = True - if not found: - pytest.fail() + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] != pytest.approx( + b["score"] + ): + pytest.fail(f'{r["score"]} != {b["score"]}') @pytest.mark.parametrize("test_name", graph_types2) def test_degree_centrality2(self, test_name): @@ -82,7 +80,7 @@ def test_degree_centrality2(self, test_name): "file_path": "", } with open( - f"data/baseline/graph_algorithms_baselines/centrality/degree_centrality/in_degree/{test_name}.json" + f"data/baseline/centrality/degree_centrality/in_degree/{test_name}.json" ) as f: baseline = json.load(f) @@ -112,7 +110,7 @@ def test_degree_centrality3(self, test_name): "file_path": "", } with open( - f"data/baseline/graph_algorithms_baselines/centrality/degree_centrality/out_degree/{test_name}.json" + f"data/baseline/centrality/degree_centrality/out_degree/{test_name}.json" ) as f: baseline = json.load(f) @@ -138,9 +136,7 @@ def test_degree_centrality4(self, test_name): "out_degree": True, "print_results": True, } - with open( - f"data/baseline/graph_algorithms_baselines/centrality/degree_centrality/{test_name}.json" - ) as f: + with open(f"data/baseline/centrality/degree_centrality/{test_name}.json") as f: baseline = json.load(f) result = self.feat.runAlgorithm("tg_degree_cent", params=params) @@ -149,7 +145,9 @@ def test_degree_centrality4(self, test_name): for b in baseline: for r in result: - if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] != pytest.approx(b["score"]): + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] != pytest.approx( + b["score"] + ): pytest.fail(f'{r["score"]} != {b["score"]}') @pytest.mark.parametrize("test_name", graph_types3) @@ -167,7 +165,7 @@ def test_weighted_degree_centrality1(self, test_name): "file_path": "", } with open( - f"data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/{test_name}.json" + f"data/baseline/centrality/weighted_degree_centrality/{test_name}.json" ) as f: baseline = json.load(f) result = self.feat.runAlgorithm("tg_weighted_degree_cent", params=params) @@ -197,7 +195,7 @@ def test_weighted_degree_centrality2(self, test_name): "file_path": "", } with open( - f"data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/in_degree/{test_name}.json" + f"data/baseline/centrality/weighted_degree_centrality/in_degree/{test_name}.json" ) as f: baseline = json.load(f) result = self.feat.runAlgorithm("tg_weighted_degree_cent", params=params) @@ -227,7 +225,7 @@ def test_weighted_degree_centrality3(self, test_name): "file_path": "", } with open( - f"data/baseline/graph_algorithms_baselines/centrality/weighted_degree_centrality/out_degree/{test_name}.json" + f"data/baseline/centrality/weighted_degree_centrality/out_degree/{test_name}.json" ) as f: baseline = json.load(f) result = self.feat.runAlgorithm("tg_weighted_degree_cent", params=params) @@ -257,7 +255,7 @@ def test_closeness_centrality(self, test_name): "display_edges": False, } with open( - f"data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/{test_name}.json" + f"data/baseline/centrality/closeness_centrality/{test_name}.json" ) as f: baseline = json.load(f) result = self.feat.runAlgorithm("tg_closeness_cent", params=params) @@ -287,7 +285,7 @@ def test_closeness_centrality2(self, test_name): "display_edges": False, } with open( - f"data/baseline/graph_algorithms_baselines/centrality/closeness_centrality/{test_name}.json" + f"data/baseline/centrality/closeness_centrality/{test_name}.json" ) as f: baseline = json.load(f) result = self.feat.runAlgorithm("tg_closeness_cent", params=params) @@ -317,7 +315,7 @@ def test_harmonic_centrality(self, test_name): "display_edges": False, } with open( - f"data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/{test_name}.json" + f"data/baseline/centrality/harmonic_centrality/{test_name}.json" ) as f: baseline = json.load(f) result = self.feat.runAlgorithm("tg_harmonic_cent", params=params) @@ -347,7 +345,7 @@ def test_harmonic_centrality2(self, test_name): "display_edges": False, } with open( - f"data/baseline/graph_algorithms_baselines/centrality/harmonic_centrality/{test_name}.json" + f"data/baseline/centrality/harmonic_centrality/{test_name}.json" ) as f: baseline = json.load(f) result = self.feat.runAlgorithm("tg_harmonic_cent", params=params) @@ -375,9 +373,7 @@ def test_article_rank(self, test_name): "result_attribute": "", "file_path": "", } - with open( - f"data/baseline/graph_algorithms_baselines/centrality/article_rank/{test_name}.json" - ) as f: + with open(f"data/baseline/centrality/article_rank/{test_name}.json") as f: baseline = json.load(f) result = self.feat.runAlgorithm("tg_article_rank", params=params) result = sorted(result[0]["@@top_scores_heap"], key=lambda x: x["Vertex_ID"]) @@ -407,9 +403,7 @@ def test_pagerank(self, test_name): "file_path": "", "display_edges": False, } - with open( - f"data/baseline/graph_algorithms_baselines/centrality/pagerank/{test_name}.json" - ) as f: + with open(f"data/baseline/centrality/pagerank/{test_name}.json") as f: baseline = json.load(f) result = self.feat.runAlgorithm("tg_pagerank", params=params) result = sorted(result[0]["@@top_scores_heap"], key=lambda x: x["Vertex_ID"]) From 3a75a551ab254dad2fafd822ebf1ddd0e6f3a5e2 Mon Sep 17 00:00:00 2001 From: Rob Rossmiller Date: Wed, 22 May 2024 13:30:30 -0400 Subject: [PATCH 06/38] testing readme --- tests/README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/README.md diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..a1f8abc2 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,6 @@ +# Algorithm Testing + +[networkX](https://networkx.org/documentation/stable/reference/index.html) is used as +the baseline for our tests where we can. + +A peculiarity to note about nx is that it counts self edges in undirected graphs as two separate edges. So, you'll see that some baselines use a modified version of the code that is found within nx. Take `run_degree_baseline_complete`, which generates the baseline for degree centrality on complete graphs. It uses the same code that can be found in `nx.centrality.degree_centrality` with the exception of subtracting a node's degree by one. From 4b33c44c1ac03e96cd07570cb39e5a674347e9f9 Mon Sep 17 00:00:00 2001 From: Rob Rossmiller Date: Tue, 28 May 2024 11:23:09 -0400 Subject: [PATCH 07/38] update data, test checks, run.sh --- GDBMS_ALGO/centrality/degree_cent.gsql | 101 +++++++++--------- .../degree/unweighted/tg_degree_cent.gsql | 9 +- .../in_degree/Hub_Spoke_Directed.json | 2 +- .../in_degree/Line_Directed.json | 2 +- .../in_degree/Ring_Directed.json | 2 +- .../in_degree/Tree_Directed.json | 2 +- .../out_degree/Hub_Spoke_Directed.json | 2 +- .../out_degree/Line_Directed.json | 2 +- .../out_degree/Ring_Directed.json | 2 +- .../out_degree/Tree_Directed.json | 2 +- tests/data/complete.png | Bin 90799 -> 0 bytes tests/data/create_baseline.py | 94 ++++++++++++---- tests/run.sh | 8 +- tests/test/test_centrality.py | 99 ++++++++--------- 14 files changed, 190 insertions(+), 137 deletions(-) delete mode 100644 tests/data/complete.png diff --git a/GDBMS_ALGO/centrality/degree_cent.gsql b/GDBMS_ALGO/centrality/degree_cent.gsql index a12b84a3..02d928cf 100644 --- a/GDBMS_ALGO/centrality/degree_cent.gsql +++ b/GDBMS_ALGO/centrality/degree_cent.gsql @@ -1,70 +1,75 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.centrality.degree_cent(SET v_type_set, SET e_type_set, SET reverse_e_type_set, BOOL in_degree = TRUE, BOOL out_degree = TRUE, - INT top_k=100, BOOL print_results = TRUE, STRING result_attribute = "",STRING file_path = "") SYNTAX V1 { - + INT top_k=100, BOOL print_results = TRUE, STRING result_attribute = "",STRING file_path = "", normalize=TRUE) SYNTAX V1 { /* - First Author: - First Commit Date: - - Recent Author: - Recent Commit Date: + First Author: + First Commit Date: + Recent Author: Rob Rossmiller + Recent Commit Date: 05/2024 - Repository: - https://github.com/tigergraph/gsql-graph-algorithms/tree/master/algorithms/Centrality - Maturity: - Production + Repository: + https://github.com/tigergraph/gsql-graph-algorithms/tree/master/algorithms/Centrality - Description: - Compute degree Centrality for each VERTEX. - for undirected graph, you only need to set e_type_set and indegree + Maturity: + Production - Publications: - NA + Description: + Compute degree Centrality for each VERTEX. + for undirected graph, you only need to set e_type_set and indegree - TigerGraph Documentation: - https://docs.tigergraph.com/graph-ml/current/centrality-algorithms/degree-centrality + Publications: + NA - Parameters: - v_type_set: - vertex types to traverse - e_type_set: - edge types to traverse - reverse_e_type_set: - for indegree use - in_degree: - If True, count incoming relationships - out_degree: - If True, count outcoming relationships - top_k: - report only this many top scores - print_results: - If True, print the result - result_attribute: - attribute to write result to - file_path: - file to write CSV output to + TigerGraph Documentation: + https://docs.tigergraph.com/graph-ml/current/centrality-algorithms/degree-centrality + Parameters: + v_type_set: + vertex types to traverse + e_type_set: + edge types to traverse + reverse_e_type_set: + for indegree use + in_degree: + If True, count incoming relationships + out_degree: + If True, count outcoming relationships + top_k: + report only this many top scores + print_results: + If True, print the result + result_attribute: + attribute to write result to + file_path: + file to write CSV output to + normailize: + If True, return the normalized centrality. Default: True */ TYPEDEF TUPLE Vertex_Score; HeapAccum(top_k, score DESC) @@top_scores_heap; - SumAccum @sum_degree_score; + SumAccum @sum_degree_score; FILE f (file_path); all = {v_type_set}; sll = SELECT s FROM all:s - ACCUM IF in_degree THEN - FOREACH edge_type in reverse_e_type_set DO - s.@sum_degree_score+=s.outdegree(edge_type) - END - END, - IF out_degree THEN - FOREACH edge_type in e_type_set DO - s.@sum_degree_score+=s.outdegree(edge_type) - END - END; + ACCUM + IF in_degree THEN + FOREACH edge_type in reverse_e_type_set DO + s.@sum_degree_score += s.outdegree(edge_type) + END + END, + IF out_degree THEN + FOREACH edge_type in e_type_set DO + s.@sum_degree_score += s.outdegree(edge_type) + END + END + POST-ACCUM + IF normalize THEN + s.@sum_degree_score = s.@sum_degree_score / (all.size() - 1) + END; #Output IF file_path != "" THEN f.println("Vertex_ID", "Degree"); diff --git a/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql b/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql index 1777c97b..14f568f0 100644 --- a/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql +++ b/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql @@ -1,4 +1,4 @@ -CREATE QUERY tg_degree_cent(SET v_type_set, SET e_type_set, SET reverse_e_type_set, BOOL in_degree = TRUE, BOOL out_degree = TRUE, INT top_k=100, BOOL print_results = TRUE, STRING result_attribute = "",STRING file_path = "") SYNTAX V1 { +CREATE QUERY tg_degree_cent(SET v_type_set, SET e_type_set, SET reverse_e_type_set, BOOL in_degree = TRUE, BOOL out_degree = TRUE, INT top_k=100, BOOL print_results = TRUE, STRING result_attribute = "",STRING file_path = "", normalize=TRUE) SYNTAX V1 { /* First Author: First Commit Date: @@ -42,6 +42,8 @@ CREATE QUERY tg_degree_cent(SET v_type_set, SET e_type_set, SET< attribute to write result to file_path: file to write CSV output to + normailize: + If True, return the normalized centrality. Default: True */ TYPEDEF TUPLE Vertex_Score; @@ -63,7 +65,10 @@ CREATE QUERY tg_degree_cent(SET v_type_set, SET e_type_set, SET< s.@sum_degree_score += s.outdegree(edge_type) END END - POST-ACCUM s.@sum_degree_score = s.@sum_degree_score / ( all.size()-1 ); + POST-ACCUM + IF normalize THEN + s.@sum_degree_score = s.@sum_degree_score / (all.size() - 1) + END; #Output IF file_path != "" THEN f.println("Vertex_ID", "Degree"); diff --git a/tests/data/baseline/centrality/degree_centrality/in_degree/Hub_Spoke_Directed.json b/tests/data/baseline/centrality/degree_centrality/in_degree/Hub_Spoke_Directed.json index 0527ce44..365279a3 100644 --- a/tests/data/baseline/centrality/degree_centrality/in_degree/Hub_Spoke_Directed.json +++ b/tests/data/baseline/centrality/degree_centrality/in_degree/Hub_Spoke_Directed.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "N", "score": 1}, {"Vertex_ID": "B", "score": 1}, {"Vertex_ID": "K", "score": 1}, {"Vertex_ID": "L", "score": 1}, {"Vertex_ID": "I", "score": 1}, {"Vertex_ID": "S", "score": 1}, {"Vertex_ID": "D", "score": 1}, {"Vertex_ID": "E", "score": 1}, {"Vertex_ID": "F", "score": 1}, {"Vertex_ID": "J", "score": 1}, {"Vertex_ID": "G", "score": 1}, {"Vertex_ID": "Q", "score": 1}, {"Vertex_ID": "R", "score": 1}, {"Vertex_ID": "H", "score": 1}, {"Vertex_ID": "O", "score": 1}, {"Vertex_ID": "C", "score": 1}, {"Vertex_ID": "P", "score": 1}, {"Vertex_ID": "M", "score": 1}, {"Vertex_ID": "T", "score": 1}, {"Vertex_ID": "A", "score": 0}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.0}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/degree_centrality/in_degree/Line_Directed.json b/tests/data/baseline/centrality/degree_centrality/in_degree/Line_Directed.json index cb2bda6d..365279a3 100644 --- a/tests/data/baseline/centrality/degree_centrality/in_degree/Line_Directed.json +++ b/tests/data/baseline/centrality/degree_centrality/in_degree/Line_Directed.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "H", "score": 1}, {"Vertex_ID": "L", "score": 1}, {"Vertex_ID": "G", "score": 1}, {"Vertex_ID": "N", "score": 1}, {"Vertex_ID": "S", "score": 1}, {"Vertex_ID": "K", "score": 1}, {"Vertex_ID": "O", "score": 1}, {"Vertex_ID": "C", "score": 1}, {"Vertex_ID": "T", "score": 1}, {"Vertex_ID": "R", "score": 1}, {"Vertex_ID": "B", "score": 1}, {"Vertex_ID": "P", "score": 1}, {"Vertex_ID": "D", "score": 1}, {"Vertex_ID": "E", "score": 1}, {"Vertex_ID": "F", "score": 1}, {"Vertex_ID": "J", "score": 1}, {"Vertex_ID": "I", "score": 1}, {"Vertex_ID": "M", "score": 1}, {"Vertex_ID": "Q", "score": 1}, {"Vertex_ID": "A", "score": 0}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.0}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/degree_centrality/in_degree/Ring_Directed.json b/tests/data/baseline/centrality/degree_centrality/in_degree/Ring_Directed.json index 18f77d14..62ccb50c 100644 --- a/tests/data/baseline/centrality/degree_centrality/in_degree/Ring_Directed.json +++ b/tests/data/baseline/centrality/degree_centrality/in_degree/Ring_Directed.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "Q", "score": 1}, {"Vertex_ID": "J", "score": 1}, {"Vertex_ID": "L", "score": 1}, {"Vertex_ID": "E", "score": 1}, {"Vertex_ID": "T", "score": 1}, {"Vertex_ID": "M", "score": 1}, {"Vertex_ID": "N", "score": 1}, {"Vertex_ID": "K", "score": 1}, {"Vertex_ID": "I", "score": 1}, {"Vertex_ID": "P", "score": 1}, {"Vertex_ID": "B", "score": 1}, {"Vertex_ID": "O", "score": 1}, {"Vertex_ID": "H", "score": 1}, {"Vertex_ID": "G", "score": 1}, {"Vertex_ID": "R", "score": 1}, {"Vertex_ID": "A", "score": 1}, {"Vertex_ID": "C", "score": 1}, {"Vertex_ID": "D", "score": 1}, {"Vertex_ID": "S", "score": 1}, {"Vertex_ID": "F", "score": 1}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.05263157894736842}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/degree_centrality/in_degree/Tree_Directed.json b/tests/data/baseline/centrality/degree_centrality/in_degree/Tree_Directed.json index a00f147c..365279a3 100644 --- a/tests/data/baseline/centrality/degree_centrality/in_degree/Tree_Directed.json +++ b/tests/data/baseline/centrality/degree_centrality/in_degree/Tree_Directed.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "G", "score": 1}, {"Vertex_ID": "T", "score": 1}, {"Vertex_ID": "P", "score": 1}, {"Vertex_ID": "J", "score": 1}, {"Vertex_ID": "F", "score": 1}, {"Vertex_ID": "O", "score": 1}, {"Vertex_ID": "C", "score": 1}, {"Vertex_ID": "Q", "score": 1}, {"Vertex_ID": "L", "score": 1}, {"Vertex_ID": "I", "score": 1}, {"Vertex_ID": "E", "score": 1}, {"Vertex_ID": "B", "score": 1}, {"Vertex_ID": "M", "score": 1}, {"Vertex_ID": "N", "score": 1}, {"Vertex_ID": "K", "score": 1}, {"Vertex_ID": "S", "score": 1}, {"Vertex_ID": "D", "score": 1}, {"Vertex_ID": "R", "score": 1}, {"Vertex_ID": "H", "score": 1}, {"Vertex_ID": "A", "score": 0}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.0}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/degree_centrality/out_degree/Hub_Spoke_Directed.json b/tests/data/baseline/centrality/degree_centrality/out_degree/Hub_Spoke_Directed.json index 5bb19d3a..870b88d1 100644 --- a/tests/data/baseline/centrality/degree_centrality/out_degree/Hub_Spoke_Directed.json +++ b/tests/data/baseline/centrality/degree_centrality/out_degree/Hub_Spoke_Directed.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 19}, {"Vertex_ID": "D", "score": 0}, {"Vertex_ID": "K", "score": 0}, {"Vertex_ID": "M", "score": 0}, {"Vertex_ID": "I", "score": 0}, {"Vertex_ID": "F", "score": 0}, {"Vertex_ID": "J", "score": 0}, {"Vertex_ID": "B", "score": 0}, {"Vertex_ID": "R", "score": 0}, {"Vertex_ID": "E", "score": 0}, {"Vertex_ID": "P", "score": 0}, {"Vertex_ID": "Q", "score": 0}, {"Vertex_ID": "H", "score": 0}, {"Vertex_ID": "G", "score": 0}, {"Vertex_ID": "O", "score": 0}, {"Vertex_ID": "C", "score": 0}, {"Vertex_ID": "L", "score": 0}, {"Vertex_ID": "T", "score": 0}, {"Vertex_ID": "N", "score": 0}, {"Vertex_ID": "S", "score": 0}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 1.0}, {"Vertex_ID": "B", "score": 0.0}, {"Vertex_ID": "C", "score": 0.0}, {"Vertex_ID": "D", "score": 0.0}, {"Vertex_ID": "E", "score": 0.0}, {"Vertex_ID": "F", "score": 0.0}, {"Vertex_ID": "G", "score": 0.0}, {"Vertex_ID": "H", "score": 0.0}, {"Vertex_ID": "I", "score": 0.0}, {"Vertex_ID": "J", "score": 0.0}, {"Vertex_ID": "K", "score": 0.0}, {"Vertex_ID": "L", "score": 0.0}, {"Vertex_ID": "M", "score": 0.0}, {"Vertex_ID": "N", "score": 0.0}, {"Vertex_ID": "O", "score": 0.0}, {"Vertex_ID": "P", "score": 0.0}, {"Vertex_ID": "Q", "score": 0.0}, {"Vertex_ID": "R", "score": 0.0}, {"Vertex_ID": "S", "score": 0.0}, {"Vertex_ID": "T", "score": 0.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/degree_centrality/out_degree/Line_Directed.json b/tests/data/baseline/centrality/degree_centrality/out_degree/Line_Directed.json index f939bbc8..d7e6932f 100644 --- a/tests/data/baseline/centrality/degree_centrality/out_degree/Line_Directed.json +++ b/tests/data/baseline/centrality/degree_centrality/out_degree/Line_Directed.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 1}, {"Vertex_ID": "F", "score": 1}, {"Vertex_ID": "J", "score": 1}, {"Vertex_ID": "B", "score": 1}, {"Vertex_ID": "L", "score": 1}, {"Vertex_ID": "H", "score": 1}, {"Vertex_ID": "G", "score": 1}, {"Vertex_ID": "Q", "score": 1}, {"Vertex_ID": "M", "score": 1}, {"Vertex_ID": "K", "score": 1}, {"Vertex_ID": "O", "score": 1}, {"Vertex_ID": "N", "score": 1}, {"Vertex_ID": "C", "score": 1}, {"Vertex_ID": "D", "score": 1}, {"Vertex_ID": "E", "score": 1}, {"Vertex_ID": "S", "score": 1}, {"Vertex_ID": "I", "score": 1}, {"Vertex_ID": "P", "score": 1}, {"Vertex_ID": "R", "score": 1}, {"Vertex_ID": "T", "score": 0}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.05263157894736842}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/degree_centrality/out_degree/Ring_Directed.json b/tests/data/baseline/centrality/degree_centrality/out_degree/Ring_Directed.json index b3ae288a..62ccb50c 100644 --- a/tests/data/baseline/centrality/degree_centrality/out_degree/Ring_Directed.json +++ b/tests/data/baseline/centrality/degree_centrality/out_degree/Ring_Directed.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "C", "score": 1}, {"Vertex_ID": "I", "score": 1}, {"Vertex_ID": "Q", "score": 1}, {"Vertex_ID": "P", "score": 1}, {"Vertex_ID": "L", "score": 1}, {"Vertex_ID": "R", "score": 1}, {"Vertex_ID": "A", "score": 1}, {"Vertex_ID": "H", "score": 1}, {"Vertex_ID": "G", "score": 1}, {"Vertex_ID": "D", "score": 1}, {"Vertex_ID": "J", "score": 1}, {"Vertex_ID": "E", "score": 1}, {"Vertex_ID": "M", "score": 1}, {"Vertex_ID": "N", "score": 1}, {"Vertex_ID": "K", "score": 1}, {"Vertex_ID": "B", "score": 1}, {"Vertex_ID": "F", "score": 1}, {"Vertex_ID": "S", "score": 1}, {"Vertex_ID": "O", "score": 1}, {"Vertex_ID": "T", "score": 1}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.05263157894736842}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/degree_centrality/out_degree/Tree_Directed.json b/tests/data/baseline/centrality/degree_centrality/out_degree/Tree_Directed.json index d3515537..9471b1db 100644 --- a/tests/data/baseline/centrality/degree_centrality/out_degree/Tree_Directed.json +++ b/tests/data/baseline/centrality/degree_centrality/out_degree/Tree_Directed.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "I", "score": 2}, {"Vertex_ID": "F", "score": 2}, {"Vertex_ID": "A", "score": 2}, {"Vertex_ID": "D", "score": 2}, {"Vertex_ID": "G", "score": 2}, {"Vertex_ID": "B", "score": 2}, {"Vertex_ID": "C", "score": 2}, {"Vertex_ID": "H", "score": 2}, {"Vertex_ID": "E", "score": 2}, {"Vertex_ID": "J", "score": 1}, {"Vertex_ID": "P", "score": 0}, {"Vertex_ID": "Q", "score": 0}, {"Vertex_ID": "O", "score": 0}, {"Vertex_ID": "S", "score": 0}, {"Vertex_ID": "L", "score": 0}, {"Vertex_ID": "N", "score": 0}, {"Vertex_ID": "T", "score": 0}, {"Vertex_ID": "R", "score": 0}, {"Vertex_ID": "K", "score": 0}, {"Vertex_ID": "M", "score": 0}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.10526315789473684}, {"Vertex_ID": "B", "score": 0.10526315789473684}, {"Vertex_ID": "C", "score": 0.10526315789473684}, {"Vertex_ID": "D", "score": 0.10526315789473684}, {"Vertex_ID": "E", "score": 0.10526315789473684}, {"Vertex_ID": "F", "score": 0.10526315789473684}, {"Vertex_ID": "G", "score": 0.10526315789473684}, {"Vertex_ID": "H", "score": 0.10526315789473684}, {"Vertex_ID": "I", "score": 0.10526315789473684}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.0}, {"Vertex_ID": "L", "score": 0.0}, {"Vertex_ID": "M", "score": 0.0}, {"Vertex_ID": "N", "score": 0.0}, {"Vertex_ID": "O", "score": 0.0}, {"Vertex_ID": "P", "score": 0.0}, {"Vertex_ID": "Q", "score": 0.0}, {"Vertex_ID": "R", "score": 0.0}, {"Vertex_ID": "S", "score": 0.0}, {"Vertex_ID": "T", "score": 0.0}]}] \ No newline at end of file diff --git a/tests/data/complete.png b/tests/data/complete.png deleted file mode 100644 index b7f922d035c8fc8a0e19e9bbaf82aa9524a18237..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90799 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

FAt4H*O4S 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 Date: Tue, 28 May 2024 11:31:01 -0400 Subject: [PATCH 08/38] update param type --- GDBMS_ALGO/centrality/degree_cent.gsql | 2 +- algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql | 2 +- tests/run.sh | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/GDBMS_ALGO/centrality/degree_cent.gsql b/GDBMS_ALGO/centrality/degree_cent.gsql index 02d928cf..f5e8b8a5 100644 --- a/GDBMS_ALGO/centrality/degree_cent.gsql +++ b/GDBMS_ALGO/centrality/degree_cent.gsql @@ -1,5 +1,5 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.centrality.degree_cent(SET v_type_set, SET e_type_set, SET reverse_e_type_set, BOOL in_degree = TRUE, BOOL out_degree = TRUE, - INT top_k=100, BOOL print_results = TRUE, STRING result_attribute = "",STRING file_path = "", normalize=TRUE) SYNTAX V1 { + INT top_k=100, BOOL print_results = TRUE, STRING result_attribute = "",STRING file_path = "", BOOL normalize = TRUE) SYNTAX V1 { /* First Author: First Commit Date: diff --git a/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql b/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql index 14f568f0..a32fb0df 100644 --- a/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql +++ b/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql @@ -1,4 +1,4 @@ -CREATE QUERY tg_degree_cent(SET v_type_set, SET e_type_set, SET reverse_e_type_set, BOOL in_degree = TRUE, BOOL out_degree = TRUE, INT top_k=100, BOOL print_results = TRUE, STRING result_attribute = "",STRING file_path = "", normalize=TRUE) SYNTAX V1 { +CREATE QUERY tg_degree_cent(SET v_type_set, SET e_type_set, SET reverse_e_type_set, BOOL in_degree = TRUE, BOOL out_degree = TRUE, INT top_k=100, BOOL print_results = TRUE, STRING result_attribute = "",STRING file_path = "", BOOL normalize = TRUE) SYNTAX V1 { /* First Author: First Commit Date: diff --git a/tests/run.sh b/tests/run.sh index 4f5a8c1d..b4f5945e 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -3,4 +3,5 @@ cd data python3 create_baseline.py cd .. python test/setup.py && - pytest + pytest test/test_centrality.py + # pytest From a0d5987e2e32c65afd46e7512b1a2b76a3d6f6cb Mon Sep 17 00:00:00 2001 From: Rob Rossmiller Date: Tue, 28 May 2024 11:39:39 -0400 Subject: [PATCH 09/38] trigger auth request action From a9caf03222da7b5c134451e49aed2782e6d7d31e Mon Sep 17 00:00:00 2001 From: Rob Rossmiller Date: Tue, 28 May 2024 12:56:07 -0400 Subject: [PATCH 10/38] remove for loops --- GDBMS_ALGO/centrality/degree_cent.gsql | 9 +++------ .../Centrality/degree/unweighted/tg_degree_cent.gsql | 9 +++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/GDBMS_ALGO/centrality/degree_cent.gsql b/GDBMS_ALGO/centrality/degree_cent.gsql index f5e8b8a5..c048d177 100644 --- a/GDBMS_ALGO/centrality/degree_cent.gsql +++ b/GDBMS_ALGO/centrality/degree_cent.gsql @@ -57,19 +57,16 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.centrality.degree_cent(SET v_type_set, FROM all:s ACCUM IF in_degree THEN - FOREACH edge_type in reverse_e_type_set DO - s.@sum_degree_score += s.outdegree(edge_type) - END + s.@sum_degree_score += s.outdegree(reverse_e_type_set) END, IF out_degree THEN - FOREACH edge_type in e_type_set DO - s.@sum_degree_score += s.outdegree(edge_type) - END + s.@sum_degree_score += s.outdegree(e_type_set) END POST-ACCUM IF normalize THEN s.@sum_degree_score = s.@sum_degree_score / (all.size() - 1) END; + #Output IF file_path != "" THEN f.println("Vertex_ID", "Degree"); diff --git a/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql b/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql index a32fb0df..fffd3f06 100644 --- a/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql +++ b/algorithms/Centrality/degree/unweighted/tg_degree_cent.gsql @@ -56,19 +56,16 @@ CREATE QUERY tg_degree_cent(SET v_type_set, SET e_type_set, SET< FROM all:s ACCUM IF in_degree THEN - FOREACH edge_type in reverse_e_type_set DO - s.@sum_degree_score += s.outdegree(edge_type) - END + s.@sum_degree_score += s.outdegree(reverse_e_type_set) END, IF out_degree THEN - FOREACH edge_type in e_type_set DO - s.@sum_degree_score += s.outdegree(edge_type) - END + s.@sum_degree_score += s.outdegree(e_type_set) END POST-ACCUM IF normalize THEN s.@sum_degree_score = s.@sum_degree_score / (all.size() - 1) END; + #Output IF file_path != "" THEN f.println("Vertex_ID", "Degree"); From 090362c6c3af97f3e705f2fc3593ea6db6ddbb90 Mon Sep 17 00:00:00 2001 From: Rob Rossmiller Date: Tue, 28 May 2024 12:56:07 -0400 Subject: [PATCH 11/38] add reqs --- tests/requirements.txt | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/requirements.txt diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 00000000..af43a402 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,53 @@ +aiohttp==3.9.5 +aiosignal==1.3.1 +attrs==23.2.0 +boto3==1.28.83 +botocore==1.31.85 +certifi==2024.2.2 +charset-normalizer==3.3.2 +contourpy==1.2.1 +cycler==0.12.1 +execnet==2.1.1 +filelock==3.13.4 +fonttools==4.51.0 +frozenlist==1.4.1 +fsspec==2024.3.1 +idna==3.7 +iniconfig==2.0.0 +Jinja2==3.1.3 +jmespath==1.0.1 +joblib==1.4.0 +kiwisolver==1.4.5 +MarkupSafe==2.1.5 +matplotlib==3.9.0 +mpmath==1.3.0 +multidict==6.0.5 +networkx==3.3 +numpy==1.26.4 +packaging==24.0 +pandas==2.1.1 +pillow==10.3.0 +pluggy==1.5.0 +psutil==5.9.8 +py==1.11.0 +pyparsing==3.1.2 +pytest==8.2.1 +pytest-xdist==3.5.0 +python-dateutil==2.9.0.post0 +python-dotenv==1.0.1 +pyTigerGraph==1.5.2 +pytz==2024.1 +requests==2.31.0 +s3transfer==0.7.0 +scikit-learn==1.4.2 +scipy==1.13.0 +six==1.16.0 +sympy==1.12 +threadpoolctl==3.4.0 +tomli==2.0.1 +tqdm==4.66.2 +typing_extensions==4.11.0 +tzdata==2024.1 +urllib3==2.0.7 +validators==0.28.1 +yarl==1.9.4 From 1918d1beda5a306f012f1f47b90e5a096a17ce15 Mon Sep 17 00:00:00 2001 From: Rob Rossmiller Date: Mon, 3 Jun 2024 12:11:00 -0400 Subject: [PATCH 12/38] contribs --- CODE_OF_CONDUCT.md | 133 +++++++++++++++++++++++++++ CONTRIBUTING.md | 53 +++++++++++ tests/CONTRIBUTING.md | 164 ++++++++++++++++++++++++++++++++++ tests/test/test_centrality.py | 41 ++++----- 4 files changed, 368 insertions(+), 23 deletions(-) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 tests/CONTRIBUTING.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..5f1f64d8 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[INSERT CONTACT METHOD]. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..64dd35f6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,53 @@ +# Contribution Guidelines + +## Issues +If you discover an issue with an algorithm, or test, open an issue to point out areas for improvement. +If you are comfortable with it, implement the fix and open a PR. + + +## Adding tests +See the [testing contributors guide](tests/CONTRIBUTING.md) + +## Coding Standards + +### Languages + +*GSQL* +- Follow the [GSQL Style Guide](https://docs.tigergraph.com/gsql-ref/current/appendix/gsql-style-guide) + +*Python* +- Use the [ruff formatter](https://docs.astral.sh/ruff/formatter/#the-ruff-formatter) to format your code +- tests: pytest and networkx wherever applicable + +*C/CPP* + + +## Pull Requests +- Make sure git knows your name and email address: + ``` + $ git config user.name "J. Random User" + $ git config user.email "j.random.user@example.com" + ``` +- The name and email address must be valid as we cannot accept anonymous contributions. +- Write good commit messages. + - Concise commit messages that describe your changes help us better understand your contributions. + +## General Guidelines + +Ensure your pull request (PR) adheres to the following guidelines: + +- Try to make the name concise and descriptive. +- Give a good description of the change being made. Since this is very subjective, see the [Updating Your Pull Request (PR)](#updating-your-pull-request-pr) section below for further details. +- Every pull request should be associated with one or more issues. If no issue exists yet, please create your own. +- Make sure that all applicable issues are mentioned somewhere in the PR description. This can be done by typing # to bring up a list of issues. + +### Updating Your Pull Request (PR) + +A lot of times, making a PR adhere to the standards above can be difficult. If the maintainers notice anything that we'd like changed, we'll ask you to edit your PR before we merge it. +This applies to both the content documented in the PR and the changed contained within the branch being merged. There's no need to open a new PR. Just edit the existing one. + +--- + +Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. + +[email]: mailto:opensource@optum.com diff --git a/tests/CONTRIBUTING.md b/tests/CONTRIBUTING.md new file mode 100644 index 00000000..1dcfb596 --- /dev/null +++ b/tests/CONTRIBUTING.md @@ -0,0 +1,164 @@ +# Contribution Guidelines for Adding Tests + +### Contents + +- [Running the tests](#running-the-tests) +- [Directory Layout](#directory-layout) +- [Adding tests](#adding-tests) +- [Available Graphs](#available-graphs) + +## Running the tests + +Execute the following to download the dependencies and run the tests. Make sure you're in a venv. + +```sh +echo ' +HOST_NAME="https://tg-hostname" +USER_NAME=tigergraph +PASS=tigergraph +' >> test/.env +pip install -r requirements.txt +./run.sh +``` + +`test/.env` + +- HOST_NAME: A that you have querywriter access to so setup.py can load data and queries to a subgraph named `graph_algorithms_testing` +- USER_NAME=admin/writer +- PASS=users_pass + +`run.sh` does a few things: + +- runs `data/create_baseline.py` + - this creates the baselines from the graphs listed in that file +- runs the setup script to make sure the graph is created and data is loaded +- runs the tests with pytest + +## Directory layout + +Data: stores the satic data for creating graphs, and algorithm baseline results. + +- CSV files under `data/[heterogeneous_edges, unweighted_edges, weighted_edges]` store the adjacency information for creating graphs. + - for example `data/weighted_edges/line_edges.csv` stores the edges and weights to create a weighted, line graph. +- JSON files under `data/baseline` store the results for a given algorithm on a given graph type. +- for example `data/baseline/centrality/pagerank/Line_Directed.json` stores the results for pagerank on a directed line graph + +test: + +- setup.py: creates the graph, loads the data and installs the queries from pyTG's featurizer. Any new/custom queries need to be manually installed +- test.py: houses the testing code for each family of algorithms + +``` +├── data +│   ├── baseline +│   │   ├── +│   │   │   └── +│   │ │ └── .json +│   ├── +│   │ └── .csv +│   └── create_baseline.py +├── requirements.txt +├── run.sh +├── test +│   ├── pyrightconfig.json +│   ├── setup.py +│   ├── test_centrality.py +│   ├── test_community.py +│   ├── test_path_finding.py +│   ├── test_topological_link_prediction.py +│   └── util.py +``` + +## Adding tests + +Start with the baseline. Add a section or module to `create_baseline.py` that creates a baseline for all the necessary graph types. The outline of the baseline should be written to the correct +baseline path (see above [layout](#directory-layout)). + +Add a method to the correct test file (i.e., community algorigthms go in community.py). The first test method in `test/test_centrality.py` is a good template to follow: + +```py + # this function will run once for each of the graph names in the undirected_graphs list + @pytest.mark.parametrize("test_name", undirected_graphs) + def test_degree_centrality1(self, test_name): + # query params + params = { + "v_type_set": ["V20"], + "e_type_set": [test_name], + "reverse_e_type_set": [test_name], + "in_degree": True, + "out_degree": False, + "top_k": 100, + "print_results": True, + "result_attribute": "", + "file_path": "", + } + with open(f"data/baseline/centrality/degree_centrality/{test_name}.json") as f: + baseline = json.load(f) + baseline = sorted(baseline[0]["top_scores"], key=lambda x: x["Vertex_ID"]) + + # call the the algorithm through the featurizer + result = self.feat.runAlgorithm("tg_degree_cent", params=params) + result = sorted(result[0]["top_scores"], key=lambda x: x["Vertex_ID"]) + + + # check that the results agree with the baseline + for b in baseline: + for r in result: + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] != pytest.approx( + b["score"] + ): + pytest.fail(f'{r["score"]} != {b["score"]}') +``` + +## Available Graphs + +Example usage: + +- If you want to run a query on a directed, weighted, line graph, use the V20 verts and Line_Directed_Weighted edges. + +| Graph | Type | Vertices | Edges | +| --------------------------- | ------------------------------------------------------------ | -------- | -------------------------------- | +| Null | | V0 | | +| Single node | | V1 | | +| Empty graph | Undirected | V20 | Empty | +| | Directed | | Empty_Directed | +| Line | Undirected, unweighted | V20 | Line | +| | Directed, unweighted | | Line_Directed | +| | Undirected, weighted | | Line_Weighted | +| | Directed, weighted | | Line_Directed_Weighted | +| | Heterogeneous vertex types, directed, weighted | V20, V8 | Line_Heterogeneous | +| Ring | Undirected, unweighted | V20 | Ring | +| | Directed, unweighted | | Ring_Directed | +| | Undirected, weighted | | Ring_Weighted | +| | Directed, weighted | | Ring_Directed_Weighted | +| | Heterogeneous vertex types, directed, weighted | V20, V8 | Ring_Heterogeneous | +| Hub & spoke | Undirected, unweighted | V20 | Hub_Spoke | +| | Directed (towards the spokes), unweighted Hub_Spoke_Directed | | | +| | Undirected, weighted Hub_Spoke_Weighted | | | +| | Directed, weighted Hub_Spoke_Directed_Weighted | | | +| | Heterogeneous vertex types, directed, weighted | V20, V8 | Hub_Spoke_Heterogeneous | +| Hub-connected hub & spoke | Undirected, unweighted | V20 | Hub_Connected_Hub_Spoke | +| | Undirected, weighted | | Hub_Connected_Hub_Spoke_Weighted | +| Tree | Undirected, unweighted | V20 | Tree | +| | Directed, unweighted | | Tree_Directed | +| | Undirected, weighted | | Tree_Weighted | +| | Directed, weighted | | Tree_Directed_Weighted | +| | Heterogeneous vertex types, directed, weighted | V20, V8 | Tree_Heterogeneous | +| Complete | Undirected, unweighted | V8 | Complete | +| | Directed, unweighted | | Complete_Directed | +| | Undirected, weighted | | Complete_Weighted | +| | Directed, weighted | | Complete_Directed_Weighted | +| | Heterogeneous vertex types, directed, weighted | V4, V8 | Complete_Heterogeneous | +| DAG | Directed, unweighted | V20 | DAG_Directed | +| | Directed, weighted | | DAG_Directed_Weighted | +| | Heterogeneous vertex types, directed, weighted | V20, V8 | DAG_Heterogeneous | +| Graph with negative cycles | Directed, weighted | V20 | Negative_cycles | +| | Heterogeneous vertex types, directed, weighted | V20, V8 | Negative_Cycle_Heterogeneous | +| Topological link prediction | Unweighted, undirected | V8 | topo_link1 | +| | topo_link2 | | | +| | topo_link3 | | | +| | topo_link4 | | | +| | topo_link5 | | | +| | topo_link6 | | | +| | Unweighted, directed | | topo_link_directed | +| Same Community | no edges | V4 | | diff --git a/tests/test/test_centrality.py b/tests/test/test_centrality.py index 68b1bcbb..efabdc1f 100644 --- a/tests/test/test_centrality.py +++ b/tests/test/test_centrality.py @@ -6,39 +6,34 @@ class TestCentrality: feat = util.get_featurizer() - # undirected graphs - graph_types1 = [ + undirected_graphs = [ "Empty", "Line", "Ring", "Hub_Spoke", "Tree", ] - # directed graphs - graph_types2 = [ + directed_graphs = [ "Line_Directed", "Ring_Directed", "Hub_Spoke_Directed", "Tree_Directed", ] - # weighted undirected graphs - graph_types3 = [ + weighted_undirected_graphs = [ "Line_Weighted", "Ring_Weighted", "Hub_Spoke_Weighted", "Tree_Weighted", ] - # weighted directed graphs - graph_types4 = [ + weighted_directed_graphs = [ "Line_Directed_Weighted", "Ring_Directed_Weighted", "Hub_Spoke_Directed_Weighted", "Tree_Directed_Weighted", ] - # Complete Graphs - graph_types5 = ["Complete"] + complete_graphs = ["Complete"] - @pytest.mark.parametrize("test_name", graph_types1) + @pytest.mark.parametrize("test_name", undirected_graphs) def test_degree_centrality1(self, test_name): params = { "v_type_set": ["V20"], @@ -66,7 +61,7 @@ def test_degree_centrality1(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types2) + @pytest.mark.parametrize("test_name", directed_graphs) def test_degree_centrality2(self, test_name): params = { "v_type_set": ["V20"], @@ -95,7 +90,7 @@ def test_degree_centrality2(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types2) + @pytest.mark.parametrize("test_name", directed_graphs) def test_degree_centrality3(self, test_name): params = { "v_type_set": ["V20"], @@ -124,7 +119,7 @@ def test_degree_centrality3(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types5) + @pytest.mark.parametrize("test_name", complete_graphs) def test_degree_centrality4(self, test_name): params = { "v_type_set": ["V8"], @@ -148,7 +143,7 @@ def test_degree_centrality4(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types3) + @pytest.mark.parametrize("test_name", weighted_undirected_graphs) def test_weighted_degree_centrality1(self, test_name): params = { "v_type": "V20", @@ -177,7 +172,7 @@ def test_weighted_degree_centrality1(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types4) + @pytest.mark.parametrize("test_name", weighted_directed_graphs) def test_weighted_degree_centrality2(self, test_name): params = { "v_type": "V20", @@ -206,7 +201,7 @@ def test_weighted_degree_centrality2(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types4) + @pytest.mark.parametrize("test_name", weighted_directed_graphs) def test_weighted_degree_centrality3(self, test_name): params = { "v_type": "V20", @@ -235,7 +230,7 @@ def test_weighted_degree_centrality3(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types1) + @pytest.mark.parametrize("test_name", undirected_graphs) def test_closeness_centrality(self, test_name): params = { "v_type_set": ["V20"], @@ -264,7 +259,7 @@ def test_closeness_centrality(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types2) + @pytest.mark.parametrize("test_name", directed_graphs) def test_closeness_centrality2(self, test_name): params = { "v_type_set": ["V20"], @@ -293,7 +288,7 @@ def test_closeness_centrality2(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types1) + @pytest.mark.parametrize("test_name", undirected_graphs) def test_harmonic_centrality(self, test_name): params = { "v_type_set": ["V20"], @@ -322,7 +317,7 @@ def test_harmonic_centrality(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types2) + @pytest.mark.parametrize("test_name", directed_graphs) def test_harmonic_centrality2(self, test_name): params = { "v_type_set": ["V20"], @@ -351,7 +346,7 @@ def test_harmonic_centrality2(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types1 + graph_types2) + @pytest.mark.parametrize("test_name", undirected_graphs + directed_graphs) def test_article_rank(self, test_name): params = { "v_type": "V20", @@ -379,7 +374,7 @@ def test_article_rank(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types1 + graph_types2) + @pytest.mark.parametrize("test_name", undirected_graphs + directed_graphs) def test_pagerank(self, test_name): params = { "v_type": "V20", From e47d576e237d474b4f43d3eaa85e64c168ec18d8 Mon Sep 17 00:00:00 2001 From: Rob Rossmiller Date: Mon, 3 Jun 2024 14:03:43 -0400 Subject: [PATCH 13/38] contribs --- tests/CONTRIBUTING.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/CONTRIBUTING.md b/tests/CONTRIBUTING.md index 1dcfb596..5a021193 100644 --- a/tests/CONTRIBUTING.md +++ b/tests/CONTRIBUTING.md @@ -23,9 +23,9 @@ pip install -r requirements.txt `test/.env` -- HOST_NAME: A that you have querywriter access to so setup.py can load data and queries to a subgraph named `graph_algorithms_testing` -- USER_NAME=admin/writer -- PASS=users_pass +- HOST_NAME: A TG environment that you have querywriter access to so setup.py can load data and queries to a subgraph named `graph_algorithms_testing` +- USER_NAME: user +- PASS: user's password `run.sh` does a few things: @@ -38,10 +38,10 @@ pip install -r requirements.txt Data: stores the satic data for creating graphs, and algorithm baseline results. -- CSV files under `data/[heterogeneous_edges, unweighted_edges, weighted_edges]` store the adjacency information for creating graphs. - - for example `data/weighted_edges/line_edges.csv` stores the edges and weights to create a weighted, line graph. -- JSON files under `data/baseline` store the results for a given algorithm on a given graph type. -- for example `data/baseline/centrality/pagerank/Line_Directed.json` stores the results for pagerank on a directed line graph +- CSV files under `data/[heterogeneous_edges, unweighted_edges, weighted_edges]` store the adjacency information for creating graphs. The baselines for algorithms are made from these graphs + - For example `data/weighted_edges/line_edges.csv` stores the edges and weights to create a weighted, line graph. +- JSON files under `data/baseline` store the baseline results for a given algorithm on a given graph type. + - For example `data/baseline/centrality/pagerank/Line_Directed.json` stores the baseline results for pagerank on a directed line graph test: @@ -71,10 +71,11 @@ test: ## Adding tests -Start with the baseline. Add a section or module to `create_baseline.py` that creates a baseline for all the necessary graph types. The outline of the baseline should be written to the correct -baseline path (see above [layout](#directory-layout)). +Start with creating the baseline. Add a section to `create_baseline.py` that creates a baseline for all the necessary graph types for your algorithm. The output of the baseline should be written to +the correct baseline path (see above [layout](#directory-layout)). -Add a method to the correct test file (i.e., community algorigthms go in community.py). The first test method in `test/test_centrality.py` is a good template to follow: +If you're adding a new algorithm, add a test method for it to the algorithm family that it belongs to (i.e., community algorigthms go in community.py). The first test method in `test/test_centrality.py` +is a good template to follow: ```py # this function will run once for each of the graph names in the undirected_graphs list From 8cf86069eec20f13db96e0554fa7699f9eb68869 Mon Sep 17 00:00:00 2001 From: Rob Rossmiller Date: Mon, 3 Jun 2024 20:04:25 -0400 Subject: [PATCH 14/38] rm optum contact email --- CONTRIBUTING.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 64dd35f6..ebbb9d0e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,4 +50,3 @@ This applies to both the content documented in the PR and the changed contained Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. -[email]: mailto:opensource@optum.com From 43695f109621b8fce267c5f5eb6f044c2eaa182c Mon Sep 17 00:00:00 2001 From: Rob Rossmiller Date: Mon, 3 Jun 2024 20:11:07 -0400 Subject: [PATCH 15/38] update contact methodgp --- CODE_OF_CONDUCT.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 5f1f64d8..fa0f963c 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -59,9 +59,8 @@ representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -[INSERT CONTACT METHOD]. -All complaints will be reviewed and investigated promptly and fairly. +reported to the community leaders responsible for enforcement by reaching out to one of the contributors +of this repository. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. From 1239a9fbe30ec7d6fb59e5768966abe1a4596dfc Mon Sep 17 00:00:00 2001 From: Bill Shi Date: Tue, 4 Jun 2024 13:36:22 -0700 Subject: [PATCH 16/38] feat(UDF): add milvus udfs --- UDF/tg_ExprFunctions.hpp | 15 +++++ UDF/tg_ExprUtil.hpp | 124 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/UDF/tg_ExprFunctions.hpp b/UDF/tg_ExprFunctions.hpp index 79c330e9..13f7637e 100644 --- a/UDF/tg_ExprFunctions.hpp +++ b/UDF/tg_ExprFunctions.hpp @@ -440,6 +440,21 @@ namespace UDIMPL { /* =========== END APPROXIMATE NEAREST NEIGHBORS ============= */ + /* ============== START Milvus ===== */ + inline ListAccum tg_searchInMilvus( + const std::string milvus_host, int milvus_port, const std::string& collection_name, + const std::string& vector_field_name, const std::string& vertex_id_field_name, const std::string& query_vector_str, + const std::string& metric_type, int top_k) { + + tg::tg_MilvusUtil milvus_util(milvus_host, milvus_port); + + // Convert query vector string to std::vector + std::vector query_vector = milvus_util.stringToFloatVector(query_vector_str); + + std::cout << "Beginning the search on: " << collection_name << std::endl; + return milvus_util.search(collection_name, vector_field_name, vertex_id_field_name, query_vector, metric_type, top_k); + } + /* ============== END Milvus ===== */ } /****************************************/ diff --git a/UDF/tg_ExprUtil.hpp b/UDF/tg_ExprUtil.hpp index 21a432bf..8346b673 100644 --- a/UDF/tg_ExprUtil.hpp +++ b/UDF/tg_ExprUtil.hpp @@ -638,6 +638,130 @@ namespace tg { /* ============ END NODE2VEC =============== */ + /* ============== START Milvus =========== */ + class tg_MilvusUtil { + public: + tg_MilvusUtil(const std::string& host, int port) { + this->host = host; + this->port = port; + curl_global_init(CURL_GLOBAL_ALL); + } + + ~tg_MilvusUtil() { + curl_global_cleanup(); + } + + std::vector stringToFloatVector(const std::string& str, char delimiter = ',') { + std::vector result; + std::stringstream ss(str); + std::string item; + + while (std::getline(ss, item, delimiter)) { + try { + result.push_back(std::stof(item)); + } catch (const std::invalid_argument& ia) { + std::cerr << "Invalid argument: " << ia.what() << '\n'; + } catch (const std::out_of_range& oor) { + std::cerr << "Out of Range error: " << oor.what() << '\n'; + } + } + + return result; + } + + ListAccum search(const std::string& collection_name, const std::string& vector_field_name, + const std::string& vertex_id_field_name, const std::vector& query_vector, const std::string& metric_type, int top_k) const { + ListAccum vertexIdList; + + Json::Value search_body; + search_body["collectionName"] = collection_name; + + // Convert query_vector to Json::Value format + for (const auto& val : query_vector) { + search_body["vector"].append(val); + } + + search_body["outputFields"] = Json::arrayValue; + search_body["outputFields"].append("pk"); + search_body["outputFields"].append(vertex_id_field_name); + search_body["limit"] = top_k; + + // You may need to adjust 'search_body' to match the exact format expected by your Milvus server version + + CURL* curl = curl_easy_init(); + if (curl) { + CURLcode res; + std::string readBuffer; + std::string url; + + if (host.substr(0, 4) == "http" && host.find(":") != std::string::npos && host.find(std::to_string(port)) != std::string::npos) { + url = host + "/v1/vector/search"; + } else if (host.substr(0, 4) == "http") { + url = host + ":" + std::to_string(port) + "/v1/vector/search"; + } else { + url = "http://" + host + ":" + std::to_string(port) + "/v1/vector/search"; + } + + Json::StreamWriterBuilder writerBuilder; + std::string requestBody = Json::writeString(writerBuilder, search_body); + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, requestBody.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Content-Type: application/json"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl; + } else { + Json::CharReaderBuilder readerBuilder; + Json::Value json_response; + std::unique_ptr const reader(readerBuilder.newCharReader()); + std::string parseErrors; + + bool parsingSuccessful = reader->parse(readBuffer.c_str(), readBuffer.c_str() + readBuffer.size(), &json_response, &parseErrors); + + if (parsingSuccessful) { + std::cout << "JSON successfully parsed" << std::endl; + } else { + // If parsing was unsuccessful, print the errors encountered + std::cerr << "Failed to parse JSON: " << parseErrors << std::endl; + } + + if (parsingSuccessful) { + for (const auto& item : json_response["data"]) { + std::string pk = item["pk"].asString(); + std::string vertex_id_str = item[vertex_id_field_name].asString(); + std::cout << "Vector ID: " << pk << "\tVertex ID: " << vertex_id_str << std::endl; + vertexIdList += vertex_id_str; + } + } + } + + curl_easy_cleanup(curl); + curl_slist_free_all(headers); + } + + return vertexIdList; + } + + private: + std::string host; + int port; + + static size_t WriteCallback(void *contents, size_t size, size_t nmemb, std::string *userp) { + userp->append((char*)contents, size * nmemb); + return size * nmemb; + } + }; + + /* ============== END Milvus =========== */ + /* ============== START A STAR =========== */ inline float rad(float d) { From 73cc81a1b0c72ef2678fa3007473fec24247483f Mon Sep 17 00:00:00 2001 From: RobRossmiller-TG <165701656+RobRossmiller-TG@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:33:27 -0400 Subject: [PATCH 17/38] init --- .../Complete_Weighted.json | 1 + .../Hub_Spoke_Weighted.json | 2 +- .../Line_Weighted.json | 2 +- .../Ring_Weighted.json | 2 +- .../Tree_Weighted.json | 2 +- .../Hub_Spoke_Directed_Weighted.json | 2 +- .../in_degree/Line_Directed_Weighted.json | 2 +- .../in_degree/Ring_Directed_Weighted.json | 2 +- .../in_degree/Tree_Directed_Weighted.json | 2 +- .../Hub_Spoke_Directed_Weighted.json | 2 +- .../out_degree/Line_Directed_Weighted.json | 2 +- .../out_degree/Ring_Directed_Weighted.json | 2 +- .../out_degree/Tree_Directed_Weighted.json | 2 +- tests/data/create_baseline.py | 155 ------------ tests/run.sh | 3 +- tests/test/create_baseline.py | 236 ++++++++++++++++++ 16 files changed, 251 insertions(+), 168 deletions(-) create mode 100644 tests/data/baseline/centrality/weighted_degree_centrality/Complete_Weighted.json delete mode 100644 tests/data/create_baseline.py create mode 100644 tests/test/create_baseline.py diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/Complete_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/Complete_Weighted.json new file mode 100644 index 00000000..98255ee1 --- /dev/null +++ b/tests/data/baseline/centrality/weighted_degree_centrality/Complete_Weighted.json @@ -0,0 +1 @@ +[{"top_scores": [{"Vertex_ID": "A", "score": 1.1428571428571428}, {"Vertex_ID": "B", "score": 1.1428571428571428}, {"Vertex_ID": "C", "score": 1.1428571428571428}, {"Vertex_ID": "D", "score": 1.1428571428571428}, {"Vertex_ID": "E", "score": 1.1428571428571428}, {"Vertex_ID": "F", "score": 1.1428571428571428}, {"Vertex_ID": "G", "score": 1.1428571428571428}, {"Vertex_ID": "H", "score": 1.1428571428571428}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json index 12a7f190..b97010da 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 190}, {"Vertex_ID": "M", "score": 44}, {"Vertex_ID": "T", "score": 35}, {"Vertex_ID": "I", "score": 31}, {"Vertex_ID": "H", "score": 25}, {"Vertex_ID": "P", "score": 24}, {"Vertex_ID": "N", "score": 17}, {"Vertex_ID": "S", "score": 16}, {"Vertex_ID": "B", "score": 10}, {"Vertex_ID": "G", "score": 8}, {"Vertex_ID": "L", "score": 6}, {"Vertex_ID": "D", "score": 5}, {"Vertex_ID": "J", "score": 4}, {"Vertex_ID": "C", "score": 2}, {"Vertex_ID": "O", "score": 0}, {"Vertex_ID": "E", "score": -3}, {"Vertex_ID": "Q", "score": -7}, {"Vertex_ID": "K", "score": -8}, {"Vertex_ID": "R", "score": -9}, {"Vertex_ID": "F", "score": -10}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 1.0}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/Line_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/Line_Weighted.json index 7a185704..70ee11a4 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/Line_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/Line_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "J", "score": 74}, {"Vertex_ID": "E", "score": 71}, {"Vertex_ID": "K", "score": 52}, {"Vertex_ID": "N", "score": 48}, {"Vertex_ID": "F", "score": 47}, {"Vertex_ID": "D", "score": 35}, {"Vertex_ID": "O", "score": 33}, {"Vertex_ID": "I", "score": 30}, {"Vertex_ID": "Q", "score": 26}, {"Vertex_ID": "R", "score": 17}, {"Vertex_ID": "T", "score": 15}, {"Vertex_ID": "S", "score": 7}, {"Vertex_ID": "C", "score": 6}, {"Vertex_ID": "A", "score": 5}, {"Vertex_ID": "M", "score": 5}, {"Vertex_ID": "B", "score": 4}, {"Vertex_ID": "P", "score": 3}, {"Vertex_ID": "H", "score": -2}, {"Vertex_ID": "G", "score": -6}, {"Vertex_ID": "L", "score": -12}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.05263157894736842}, {"Vertex_ID": "B", "score": 0.10526315789473684}, {"Vertex_ID": "C", "score": 0.10526315789473684}, {"Vertex_ID": "D", "score": 0.10526315789473684}, {"Vertex_ID": "E", "score": 0.10526315789473684}, {"Vertex_ID": "F", "score": 0.10526315789473684}, {"Vertex_ID": "G", "score": 0.10526315789473684}, {"Vertex_ID": "H", "score": 0.10526315789473684}, {"Vertex_ID": "I", "score": 0.10526315789473684}, {"Vertex_ID": "J", "score": 0.10526315789473684}, {"Vertex_ID": "K", "score": 0.10526315789473684}, {"Vertex_ID": "L", "score": 0.10526315789473684}, {"Vertex_ID": "M", "score": 0.10526315789473684}, {"Vertex_ID": "N", "score": 0.10526315789473684}, {"Vertex_ID": "O", "score": 0.10526315789473684}, {"Vertex_ID": "P", "score": 0.10526315789473684}, {"Vertex_ID": "Q", "score": 0.10526315789473684}, {"Vertex_ID": "R", "score": 0.10526315789473684}, {"Vertex_ID": "S", "score": 0.10526315789473684}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/Ring_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/Ring_Weighted.json index de4a4934..1f77b5bc 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/Ring_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/Ring_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "I", "score": 21}, {"Vertex_ID": "A", "score": 21}, {"Vertex_ID": "P", "score": 20}, {"Vertex_ID": "E", "score": 18}, {"Vertex_ID": "H", "score": 18}, {"Vertex_ID": "M", "score": 18}, {"Vertex_ID": "T", "score": 18}, {"Vertex_ID": "N", "score": 17}, {"Vertex_ID": "J", "score": 15}, {"Vertex_ID": "O", "score": 14}, {"Vertex_ID": "R", "score": 14}, {"Vertex_ID": "S", "score": 13}, {"Vertex_ID": "Q", "score": 11}, {"Vertex_ID": "G", "score": 11}, {"Vertex_ID": "F", "score": 6}, {"Vertex_ID": "D", "score": 6}, {"Vertex_ID": "B", "score": 3}, {"Vertex_ID": "L", "score": 2}, {"Vertex_ID": "C", "score": -1}, {"Vertex_ID": "K", "score": -7}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.10526315789473684}, {"Vertex_ID": "B", "score": 0.10526315789473684}, {"Vertex_ID": "C", "score": 0.10526315789473684}, {"Vertex_ID": "D", "score": 0.10526315789473684}, {"Vertex_ID": "E", "score": 0.10526315789473684}, {"Vertex_ID": "F", "score": 0.10526315789473684}, {"Vertex_ID": "G", "score": 0.10526315789473684}, {"Vertex_ID": "H", "score": 0.10526315789473684}, {"Vertex_ID": "I", "score": 0.10526315789473684}, {"Vertex_ID": "J", "score": 0.10526315789473684}, {"Vertex_ID": "K", "score": 0.10526315789473684}, {"Vertex_ID": "L", "score": 0.10526315789473684}, {"Vertex_ID": "M", "score": 0.10526315789473684}, {"Vertex_ID": "N", "score": 0.10526315789473684}, {"Vertex_ID": "O", "score": 0.10526315789473684}, {"Vertex_ID": "P", "score": 0.10526315789473684}, {"Vertex_ID": "Q", "score": 0.10526315789473684}, {"Vertex_ID": "R", "score": 0.10526315789473684}, {"Vertex_ID": "S", "score": 0.10526315789473684}, {"Vertex_ID": "T", "score": 0.10526315789473684}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/Tree_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/Tree_Weighted.json index 048f85f0..51cb839d 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/Tree_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/Tree_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "G", "score": 83}, {"Vertex_ID": "C", "score": 73}, {"Vertex_ID": "I", "score": 59}, {"Vertex_ID": "S", "score": 33}, {"Vertex_ID": "E", "score": 29}, {"Vertex_ID": "D", "score": 29}, {"Vertex_ID": "J", "score": 24}, {"Vertex_ID": "O", "score": 21}, {"Vertex_ID": "F", "score": 17}, {"Vertex_ID": "N", "score": 12}, {"Vertex_ID": "P", "score": 11}, {"Vertex_ID": "B", "score": 8}, {"Vertex_ID": "T", "score": 7}, {"Vertex_ID": "M", "score": 5}, {"Vertex_ID": "H", "score": 5}, {"Vertex_ID": "K", "score": 4}, {"Vertex_ID": "A", "score": 3}, {"Vertex_ID": "R", "score": -5}, {"Vertex_ID": "Q", "score": -6}, {"Vertex_ID": "L", "score": -10}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.10526315789473684}, {"Vertex_ID": "B", "score": 0.15789473684210525}, {"Vertex_ID": "C", "score": 0.15789473684210525}, {"Vertex_ID": "D", "score": 0.15789473684210525}, {"Vertex_ID": "E", "score": 0.15789473684210525}, {"Vertex_ID": "F", "score": 0.15789473684210525}, {"Vertex_ID": "G", "score": 0.15789473684210525}, {"Vertex_ID": "H", "score": 0.15789473684210525}, {"Vertex_ID": "I", "score": 0.15789473684210525}, {"Vertex_ID": "J", "score": 0.10526315789473684}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json index 07d55f27..365279a3 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "M", "score": 44}, {"Vertex_ID": "T", "score": 35}, {"Vertex_ID": "I", "score": 31}, {"Vertex_ID": "H", "score": 25}, {"Vertex_ID": "P", "score": 24}, {"Vertex_ID": "N", "score": 17}, {"Vertex_ID": "S", "score": 16}, {"Vertex_ID": "B", "score": 10}, {"Vertex_ID": "G", "score": 8}, {"Vertex_ID": "L", "score": 6}, {"Vertex_ID": "D", "score": 5}, {"Vertex_ID": "J", "score": 4}, {"Vertex_ID": "C", "score": 2}, {"Vertex_ID": "O", "score": 0}, {"Vertex_ID": "A", "score": 0}, {"Vertex_ID": "E", "score": -3}, {"Vertex_ID": "Q", "score": -7}, {"Vertex_ID": "K", "score": -8}, {"Vertex_ID": "R", "score": -9}, {"Vertex_ID": "F", "score": -10}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.0}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json index 76ae95f2..365279a3 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "K", "score": 52}, {"Vertex_ID": "F", "score": 43}, {"Vertex_ID": "O", "score": 31}, {"Vertex_ID": "E", "score": 28}, {"Vertex_ID": "R", "score": 25}, {"Vertex_ID": "J", "score": 22}, {"Vertex_ID": "N", "score": 17}, {"Vertex_ID": "T", "score": 15}, {"Vertex_ID": "I", "score": 8}, {"Vertex_ID": "D", "score": 7}, {"Vertex_ID": "B", "score": 5}, {"Vertex_ID": "G", "score": 4}, {"Vertex_ID": "P", "score": 2}, {"Vertex_ID": "Q", "score": 1}, {"Vertex_ID": "A", "score": 0}, {"Vertex_ID": "L", "score": 0}, {"Vertex_ID": "C", "score": -1}, {"Vertex_ID": "S", "score": -8}, {"Vertex_ID": "H", "score": -10}, {"Vertex_ID": "M", "score": -12}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.0}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json index 7c769c9f..62ccb50c 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 19}, {"Vertex_ID": "J", "score": 18}, {"Vertex_ID": "H", "score": 15}, {"Vertex_ID": "S", "score": 14}, {"Vertex_ID": "N", "score": 12}, {"Vertex_ID": "Q", "score": 11}, {"Vertex_ID": "F", "score": 10}, {"Vertex_ID": "P", "score": 9}, {"Vertex_ID": "E", "score": 8}, {"Vertex_ID": "M", "score": 6}, {"Vertex_ID": "O", "score": 5}, {"Vertex_ID": "I", "score": 3}, {"Vertex_ID": "B", "score": 2}, {"Vertex_ID": "C", "score": 1}, {"Vertex_ID": "R", "score": 0}, {"Vertex_ID": "T", "score": -1}, {"Vertex_ID": "D", "score": -2}, {"Vertex_ID": "K", "score": -3}, {"Vertex_ID": "G", "score": -4}, {"Vertex_ID": "L", "score": -4}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.05263157894736842}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json index 090c8105..365279a3 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "G", "score": 50}, {"Vertex_ID": "S", "score": 33}, {"Vertex_ID": "I", "score": 31}, {"Vertex_ID": "F", "score": 22}, {"Vertex_ID": "O", "score": 21}, {"Vertex_ID": "J", "score": 17}, {"Vertex_ID": "N", "score": 12}, {"Vertex_ID": "P", "score": 11}, {"Vertex_ID": "E", "score": 8}, {"Vertex_ID": "T", "score": 7}, {"Vertex_ID": "M", "score": 5}, {"Vertex_ID": "K", "score": 4}, {"Vertex_ID": "B", "score": 2}, {"Vertex_ID": "C", "score": 1}, {"Vertex_ID": "A", "score": 0}, {"Vertex_ID": "H", "score": 0}, {"Vertex_ID": "D", "score": -2}, {"Vertex_ID": "R", "score": -5}, {"Vertex_ID": "Q", "score": -6}, {"Vertex_ID": "L", "score": -10}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.0}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json index 66c5eccd..870b88d1 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 190}, {"Vertex_ID": "B", "score": 0}, {"Vertex_ID": "I", "score": 0}, {"Vertex_ID": "N", "score": 0}, {"Vertex_ID": "Q", "score": 0}, {"Vertex_ID": "L", "score": 0}, {"Vertex_ID": "T", "score": 0}, {"Vertex_ID": "F", "score": 0}, {"Vertex_ID": "O", "score": 0}, {"Vertex_ID": "J", "score": 0}, {"Vertex_ID": "P", "score": 0}, {"Vertex_ID": "C", "score": 0}, {"Vertex_ID": "D", "score": 0}, {"Vertex_ID": "E", "score": 0}, {"Vertex_ID": "H", "score": 0}, {"Vertex_ID": "G", "score": 0}, {"Vertex_ID": "R", "score": 0}, {"Vertex_ID": "S", "score": 0}, {"Vertex_ID": "K", "score": 0}, {"Vertex_ID": "M", "score": 0}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 1.0}, {"Vertex_ID": "B", "score": 0.0}, {"Vertex_ID": "C", "score": 0.0}, {"Vertex_ID": "D", "score": 0.0}, {"Vertex_ID": "E", "score": 0.0}, {"Vertex_ID": "F", "score": 0.0}, {"Vertex_ID": "G", "score": 0.0}, {"Vertex_ID": "H", "score": 0.0}, {"Vertex_ID": "I", "score": 0.0}, {"Vertex_ID": "J", "score": 0.0}, {"Vertex_ID": "K", "score": 0.0}, {"Vertex_ID": "L", "score": 0.0}, {"Vertex_ID": "M", "score": 0.0}, {"Vertex_ID": "N", "score": 0.0}, {"Vertex_ID": "O", "score": 0.0}, {"Vertex_ID": "P", "score": 0.0}, {"Vertex_ID": "Q", "score": 0.0}, {"Vertex_ID": "R", "score": 0.0}, {"Vertex_ID": "S", "score": 0.0}, {"Vertex_ID": "T", "score": 0.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json index 1843cdef..d7e6932f 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "J", "score": 52}, {"Vertex_ID": "E", "score": 43}, {"Vertex_ID": "N", "score": 31}, {"Vertex_ID": "D", "score": 28}, {"Vertex_ID": "Q", "score": 25}, {"Vertex_ID": "I", "score": 22}, {"Vertex_ID": "M", "score": 17}, {"Vertex_ID": "S", "score": 15}, {"Vertex_ID": "H", "score": 8}, {"Vertex_ID": "C", "score": 7}, {"Vertex_ID": "A", "score": 5}, {"Vertex_ID": "F", "score": 4}, {"Vertex_ID": "O", "score": 2}, {"Vertex_ID": "P", "score": 1}, {"Vertex_ID": "K", "score": 0}, {"Vertex_ID": "T", "score": 0}, {"Vertex_ID": "B", "score": -1}, {"Vertex_ID": "R", "score": -8}, {"Vertex_ID": "G", "score": -10}, {"Vertex_ID": "L", "score": -12}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.05263157894736842}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json index cfead7a4..62ccb50c 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "T", "score": 19}, {"Vertex_ID": "I", "score": 18}, {"Vertex_ID": "G", "score": 15}, {"Vertex_ID": "R", "score": 14}, {"Vertex_ID": "M", "score": 12}, {"Vertex_ID": "P", "score": 11}, {"Vertex_ID": "E", "score": 10}, {"Vertex_ID": "O", "score": 9}, {"Vertex_ID": "D", "score": 8}, {"Vertex_ID": "L", "score": 6}, {"Vertex_ID": "N", "score": 5}, {"Vertex_ID": "H", "score": 3}, {"Vertex_ID": "A", "score": 2}, {"Vertex_ID": "B", "score": 1}, {"Vertex_ID": "Q", "score": 0}, {"Vertex_ID": "S", "score": -1}, {"Vertex_ID": "C", "score": -2}, {"Vertex_ID": "J", "score": -3}, {"Vertex_ID": "K", "score": -4}, {"Vertex_ID": "F", "score": -4}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.05263157894736842}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json index 7d13d741..9471b1db 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "C", "score": 72}, {"Vertex_ID": "G", "score": 33}, {"Vertex_ID": "D", "score": 31}, {"Vertex_ID": "I", "score": 28}, {"Vertex_ID": "E", "score": 21}, {"Vertex_ID": "J", "score": 7}, {"Vertex_ID": "B", "score": 6}, {"Vertex_ID": "H", "score": 5}, {"Vertex_ID": "A", "score": 3}, {"Vertex_ID": "Q", "score": 0}, {"Vertex_ID": "T", "score": 0}, {"Vertex_ID": "N", "score": 0}, {"Vertex_ID": "M", "score": 0}, {"Vertex_ID": "L", "score": 0}, {"Vertex_ID": "O", "score": 0}, {"Vertex_ID": "P", "score": 0}, {"Vertex_ID": "R", "score": 0}, {"Vertex_ID": "S", "score": 0}, {"Vertex_ID": "K", "score": 0}, {"Vertex_ID": "F", "score": -5}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 0.10526315789473684}, {"Vertex_ID": "B", "score": 0.10526315789473684}, {"Vertex_ID": "C", "score": 0.10526315789473684}, {"Vertex_ID": "D", "score": 0.10526315789473684}, {"Vertex_ID": "E", "score": 0.10526315789473684}, {"Vertex_ID": "F", "score": 0.10526315789473684}, {"Vertex_ID": "G", "score": 0.10526315789473684}, {"Vertex_ID": "H", "score": 0.10526315789473684}, {"Vertex_ID": "I", "score": 0.10526315789473684}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.0}, {"Vertex_ID": "L", "score": 0.0}, {"Vertex_ID": "M", "score": 0.0}, {"Vertex_ID": "N", "score": 0.0}, {"Vertex_ID": "O", "score": 0.0}, {"Vertex_ID": "P", "score": 0.0}, {"Vertex_ID": "Q", "score": 0.0}, {"Vertex_ID": "R", "score": 0.0}, {"Vertex_ID": "S", "score": 0.0}, {"Vertex_ID": "T", "score": 0.0}]}] \ No newline at end of file diff --git a/tests/data/create_baseline.py b/tests/data/create_baseline.py deleted file mode 100644 index 53e55b5f..00000000 --- a/tests/data/create_baseline.py +++ /dev/null @@ -1,155 +0,0 @@ -import csv -import json - -import networkx as nx -import numpy as np -from tqdm import tqdm - -baseline_path_root = "baseline" - - -def run_degree_baseline_complete(g: nx.Graph, _): - s = 1.0 / (len(g) - 1.0) - - # d-1 because nx will double count the self-edge - res = {n: (d - 1) * s for n, d in g.degree()} - - out = [] - for k, v in res.items(): - out.append({"Vertex_ID": k, "score": v}) - - out = [{"top_scores": out}] - return out - - -def run_degree_baseline(g: nx.Graph, metric): - res = metric(g) - - out = [] - for k, v in res.items(): - out.append({"Vertex_ID": k, "score": v}) - - out = [{"top_scores": out}] - return out - - -def create_graph(edges, weights=False, directed=False): - if directed: - g = nx.DiGraph() - else: - g = nx.Graph() - if weights: - g.add_weighted_edges_from(edges) - else: - g.add_edges_from(edges) - return g - - -def create_degree_baseline(): - # input, output, weighed - paths = [ - ( - "unweighted_edges/complete_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/Complete.json", - run_degree_baseline_complete, - None, - ), - # - ( - "unweighted_edges/line_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/Line.json", - run_degree_baseline, - nx.centrality.degree_centrality, - ), - ( - "unweighted_edges/ring_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/Ring.json", - run_degree_baseline, - nx.centrality.degree_centrality, - ), - ( - "unweighted_edges/hubspoke_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/Hub_Spoke.json", - run_degree_baseline, - nx.centrality.degree_centrality, - ), - ( - "unweighted_edges/tree_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/Tree.json", - run_degree_baseline, - nx.centrality.degree_centrality, - ), - # in_degree - ( - "unweighted_edges/line_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/in_degree/Line_Directed.json", - run_degree_baseline, - nx.centrality.in_degree_centrality, - ), - ( - "unweighted_edges/ring_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/in_degree/Ring_Directed.json", - run_degree_baseline, - nx.centrality.in_degree_centrality, - ), - ( - "unweighted_edges/hubspoke_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/in_degree/Hub_Spoke_Directed.json", - run_degree_baseline, - nx.centrality.in_degree_centrality, - ), - ( - "unweighted_edges/tree_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/in_degree/Tree_Directed.json", - run_degree_baseline, - nx.centrality.in_degree_centrality, - ), - # out_degree - ( - "unweighted_edges/line_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/out_degree/Line_Directed.json", - run_degree_baseline, - nx.centrality.out_degree_centrality, - ), - ( - "unweighted_edges/ring_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/out_degree/Ring_Directed.json", - run_degree_baseline, - nx.centrality.out_degree_centrality, - ), - ( - "unweighted_edges/hubspoke_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/out_degree/Hub_Spoke_Directed.json", - run_degree_baseline, - nx.centrality.out_degree_centrality, - ), - ( - "unweighted_edges/tree_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/out_degree/Tree_Directed.json", - run_degree_baseline, - nx.centrality.out_degree_centrality, - ), - ] - - for p, out_path, fn, m in tqdm(paths, desc="Creating baselines"): - with open(p) as f: - edges = np.array(list(csv.reader(f))) - - if "Directed" in out_path: - g = create_graph(edges, directed=True) - - # from matplotlib import pyplot as plt - # pos = nx.drawing.layout.kamada_kawai_layout(g) - # nx.draw(g, pos) - # nx.draw_networkx_labels(g, pos, {n: n for n in g.nodes}) - # plt.savefig(f"{out_path.split('/')[-1]}.png") - else: - g = create_graph(edges) - - res = fn(g, m) - with open(out_path, "w") as f: - json.dump(res, f) # , indent=2) - - -if __name__ == "__main__": - create_degree_baseline() diff --git a/tests/run.sh b/tests/run.sh index b4f5945e..e2f54a4c 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -1,6 +1,7 @@ clear -cd data +cd test python3 create_baseline.py +# exit 0 cd .. python test/setup.py && pytest test/test_centrality.py diff --git a/tests/test/create_baseline.py b/tests/test/create_baseline.py new file mode 100644 index 00000000..9ca84d63 --- /dev/null +++ b/tests/test/create_baseline.py @@ -0,0 +1,236 @@ +import csv +import json + +import networkx as nx +import numpy as np +from tqdm import tqdm + +data_path_root = "../data/" +baseline_path_root = f"{data_path_root}/baseline/" + + +def run_degree_baseline_complete(g: nx.Graph, _): + s = 1.0 / (len(g) - 1.0) + + # d-1 because nx will double count the self-edge + res = {n: (d - 1) * s for n, d in g.degree()} + + out = [] + for k, v in res.items(): + out.append({"Vertex_ID": k, "score": v}) + + out = [{"top_scores": out}] + return out + + +def run_degree_baseline(g: nx.Graph, metric): + res = metric(g) + + out = [] + for k, v in res.items(): + out.append({"Vertex_ID": k, "score": v}) + + out = [{"top_scores": out}] + return out + + +def create_graph(edges, weights=False, directed=False): + if directed: + g = nx.DiGraph() + else: + g = nx.Graph() + if weights: + g.add_weighted_edges_from(edges) + else: + g.add_edges_from(edges) + return g + + +def create_degree_baseline(paths): + for p, out_path, fn, m in tqdm(paths, desc="Creating baselines"): + with open(p) as f: + edges = np.array(list(csv.reader(f))) + + directed = True if "Directed" in out_path else False + # weights = edges[:, -1].astype(float) if "Weighted" in out_path else None + weights = True if "Weighted" in out_path else False + g = create_graph(edges, weights, directed) + # from matplotlib import pyplot as plt + # pos = nx.drawing.layout.kamada_kawai_layout(g) + # nx.draw(g, pos) + # nx.draw_networkx_labels(g, pos, {n: n for n in g.nodes}) + # plt.savefig(f"{out_path.split('/')[-1]}.png") + + res = fn(g, m) + with open(out_path, "w") as f: + json.dump(res, f) # , indent=2) + + +if __name__ == "__main__": + # (data, output_path, fun, metric) + paths = [ + # unweighted + ( + f"{data_path_root}/unweighted_edges/complete_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/Complete.json", + run_degree_baseline_complete, + None, + ), + ( + f"{data_path_root}/unweighted_edges/line_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/Line.json", + run_degree_baseline, + nx.centrality.degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/ring_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/Ring.json", + run_degree_baseline, + nx.centrality.degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/hubspoke_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/Hub_Spoke.json", + run_degree_baseline, + nx.centrality.degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/tree_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/Tree.json", + run_degree_baseline, + nx.centrality.degree_centrality, + ), + # in_degree + ( + f"{data_path_root}/unweighted_edges/line_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/in_degree/Line_Directed.json", + run_degree_baseline, + nx.centrality.in_degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/ring_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/in_degree/Ring_Directed.json", + run_degree_baseline, + nx.centrality.in_degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/hubspoke_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/in_degree/Hub_Spoke_Directed.json", + run_degree_baseline, + nx.centrality.in_degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/tree_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/in_degree/Tree_Directed.json", + run_degree_baseline, + nx.centrality.in_degree_centrality, + ), + # out_degree + ( + f"{data_path_root}/unweighted_edges/line_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/out_degree/Line_Directed.json", + run_degree_baseline, + nx.centrality.out_degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/ring_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/out_degree/Ring_Directed.json", + run_degree_baseline, + nx.centrality.out_degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/hubspoke_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/out_degree/Hub_Spoke_Directed.json", + run_degree_baseline, + nx.centrality.out_degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/tree_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/out_degree/Tree_Directed.json", + run_degree_baseline, + nx.centrality.out_degree_centrality, + ), + # weighted + ( + f"{data_path_root}/weighted_edges/complete_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/Complete_Weighted.json", + run_degree_baseline_complete, + None, + ), + ( + f"{data_path_root}/weighted_edges/line_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/Line_Weighted.json", + run_degree_baseline, + nx.centrality.degree_centrality, + ), + ( + f"{data_path_root}/weighted_edges/ring_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/Ring_Weighted.json", + run_degree_baseline, + nx.centrality.degree_centrality, + ), + ( + f"{data_path_root}/weighted_edges/hubspoke_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json", + run_degree_baseline, + nx.centrality.degree_centrality, + ), + ( + f"{data_path_root}/weighted_edges/tree_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/Tree_Weighted.json", + run_degree_baseline, + nx.centrality.degree_centrality, + ), + # in_degree + ( + f"{data_path_root}/weighted_edges/line_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json", + run_degree_baseline, + nx.centrality.in_degree_centrality, + ), + ( + f"{data_path_root}/weighted_edges/ring_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json", + run_degree_baseline, + nx.centrality.in_degree_centrality, + ), + ( + f"{data_path_root}/weighted_edges/hubspoke_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json", + run_degree_baseline, + nx.centrality.in_degree_centrality, + ), + ( + f"{data_path_root}/weighted_edges/tree_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json", + run_degree_baseline, + nx.centrality.in_degree_centrality, + ), + # out_degree + ( + f"{data_path_root}/weighted_edges/line_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json", + run_degree_baseline, + nx.centrality.out_degree_centrality, + ), + ( + f"{data_path_root}/weighted_edges/ring_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json", + run_degree_baseline, + nx.centrality.out_degree_centrality, + ), + ( + f"{data_path_root}/weighted_edges/hubspoke_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json", + run_degree_baseline, + nx.centrality.out_degree_centrality, + ), + ( + f"{data_path_root}/weighted_edges/tree_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json", + run_degree_baseline, + nx.centrality.out_degree_centrality, + ), + # weighted + ] + create_degree_baseline(paths) From 60818ce11eb74410a46b103710f221ce852db04f Mon Sep 17 00:00:00 2001 From: RobRossmiller-TG <165701656+RobRossmiller-TG@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:17:03 -0400 Subject: [PATCH 18/38] complete graphs --- .DS_Store | Bin 6148 -> 6148 bytes .../weighted/tg_weighted_degree_cent.gsql | 54 ++++++------- .../Complete_Weighted.json | 2 +- .../Hub_Spoke_Weighted.json | 2 +- .../Line_Weighted.json | 2 +- .../Ring_Weighted.json | 2 +- .../Tree_Weighted.json | 2 +- .../in_degree/Complete_Directed_Weighted.json | 1 + .../Hub_Spoke_Directed_Weighted.json | 2 +- .../in_degree/Line_Directed_Weighted.json | 2 +- .../in_degree/Ring_Directed_Weighted.json | 2 +- .../in_degree/Tree_Directed_Weighted.json | 2 +- .../Complete_Directed_Weighted.json | 1 + .../Hub_Spoke_Directed_Weighted.json | 2 +- .../out_degree/Line_Directed_Weighted.json | 2 +- .../out_degree/Ring_Directed_Weighted.json | 2 +- .../out_degree/Tree_Directed_Weighted.json | 2 +- tests/run.sh | 11 +-- tests/test/create_baseline.py | 71 +++++++++++++----- tests/test/test_centrality.py | 13 +++- 20 files changed, 110 insertions(+), 67 deletions(-) create mode 100644 tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Complete_Directed_Weighted.json create mode 100644 tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Complete_Directed_Weighted.json diff --git a/.DS_Store b/.DS_Store index 5a06dfc6a4cee2dc5d1470d216bab1132f94df5f..644280363c6a9fea02a03f62bdd0ba03ae58b52e 100644 GIT binary patch delta 192 zcmZoMXfc@J&&azmU^g=(?`9sBcqR*0h7yKUhGHODoK#+1kd%|3#K6F?BdH)Kv$({- z;2I+nGYcylI|n-lH%Dx6Mt*s4Nn%N9vD0K(R_`=U4$gQ1iRx-|V-p<(Q$v$l9ffL3 zBLf`;6JxX5T22m8Wqs?Q`0SkAy!>DOFyxGjo@s}R}pa%-C diff --git a/algorithms/Centrality/degree/weighted/tg_weighted_degree_cent.gsql b/algorithms/Centrality/degree/weighted/tg_weighted_degree_cent.gsql index a284380b..6876f371 100644 --- a/algorithms/Centrality/degree/weighted/tg_weighted_degree_cent.gsql +++ b/algorithms/Centrality/degree/weighted/tg_weighted_degree_cent.gsql @@ -1,5 +1,4 @@ -CREATE QUERY tg_weighted_degree_cent(STRING v_type, STRING e_type, STRING reverse_e_type, string weight_attribute, BOOL in_degree = TRUE, BOOL out_degree = TRUE, INT top_k=100, BOOL print_results = TRUE, STRING result_attribute = "",STRING file_path = "") SYNTAX V1 { - +CREATE QUERY tg_weighted_degree_cent(STRING v_type, STRING e_type, STRING reverse_e_type, STRING weight_attribute, BOOL in_degree = TRUE, BOOL out_degree = TRUE, INT top_k=100, BOOL print_results = TRUE, STRING result_attribute = "",STRING file_path = "") SYNTAX V1 { /* First Author: First Commit Date: @@ -22,18 +21,20 @@ CREATE QUERY tg_weighted_degree_cent(STRING v_type, STRING e_type, STRING revers for undirected graph, you only need to set e_type and in_degree Publications: - + NA TigerGraph Documentation: - + https://docs.tigergraph.com/graph-ml/current/centrality-algorithms/weighted-degree-centrality Parameters: v_type: - vertex types to traverse + Vertex types to traverse e_type: - edge types to traverse + Edge types to traverse reverse_e_type: - for indegree use + For indegree use + weight_attribute: + The edge weight attribute name in_degree: If True, count incoming relationships out_degree: @@ -43,27 +44,28 @@ CREATE QUERY tg_weighted_degree_cent(STRING v_type, STRING e_type, STRING revers print_results: If True, print the results result_attribute: - attribute to write result to + Attribute to write result to file_path: - file to write CSV output to - */ + File to write CSV output to + */ TYPEDEF TUPLE Vertex_Score; HeapAccum(top_k, score DESC) @@top_scores_heap; - SumAccum @sum_degree_score; + SumAccum @sum_degree_score; FILE f (file_path); all = {v_type}; IF in_degree THEN sll = SELECT s - FROM all:s-(reverse_e_type:e)-:t - ACCUM s.@sum_degree_score+=e.getAttr(weight_attribute,"INT"); + FROM all:s -(reverse_e_type:e)- :t + ACCUM s.@sum_degree_score += e.getAttr(weight_attribute, "FLOAT"); END; IF out_degree THEN sll = SELECT s - FROM all:s-(e_type:e)-:t - ACCUM s.@sum_degree_score+=e.getAttr(weight_attribute,"INT"); + FROM all:s -(e_type:e)- :t + ACCUM s.@sum_degree_score += e.getAttr(weight_attribute, "FLOAT"); END; + #Output IF file_path != "" THEN f.println("Vertex_ID", "Degree"); @@ -72,17 +74,17 @@ CREATE QUERY tg_weighted_degree_cent(STRING v_type, STRING e_type, STRING revers Start = SELECT s FROM all:s POST-ACCUM - IF result_attribute != "" THEN - s.setAttr(result_attribute, s.@sum_degree_score) - END, - - IF print_results THEN - @@top_scores_heap += Vertex_Score(s, s.@sum_degree_score) - END, - - IF file_path != "" THEN - f.println(s, s.@sum_degree_score) - END; + IF result_attribute != "" THEN + s.setAttr(result_attribute, s.@sum_degree_score) + END, + + IF print_results THEN + @@top_scores_heap += Vertex_Score(s, s.@sum_degree_score) + END, + + IF file_path != "" THEN + f.println(s, s.@sum_degree_score) + END; IF print_results THEN PRINT @@top_scores_heap AS top_scores; diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/Complete_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/Complete_Weighted.json index 98255ee1..997882ed 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/Complete_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/Complete_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 1.1428571428571428}, {"Vertex_ID": "B", "score": 1.1428571428571428}, {"Vertex_ID": "C", "score": 1.1428571428571428}, {"Vertex_ID": "D", "score": 1.1428571428571428}, {"Vertex_ID": "E", "score": 1.1428571428571428}, {"Vertex_ID": "F", "score": 1.1428571428571428}, {"Vertex_ID": "G", "score": 1.1428571428571428}, {"Vertex_ID": "H", "score": 1.1428571428571428}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 70.0}, {"Vertex_ID": "B", "score": 36.0}, {"Vertex_ID": "C", "score": 99.0}, {"Vertex_ID": "D", "score": 105.0}, {"Vertex_ID": "E", "score": 33.0}, {"Vertex_ID": "F", "score": 20.0}, {"Vertex_ID": "G", "score": 84.0}, {"Vertex_ID": "H", "score": 109.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json index b97010da..f6e52713 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 1.0}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 190.0}, {"Vertex_ID": "B", "score": 10.0}, {"Vertex_ID": "C", "score": 2.0}, {"Vertex_ID": "D", "score": 5.0}, {"Vertex_ID": "E", "score": -3.0}, {"Vertex_ID": "F", "score": -10.0}, {"Vertex_ID": "G", "score": 8.0}, {"Vertex_ID": "H", "score": 25.0}, {"Vertex_ID": "I", "score": 31.0}, {"Vertex_ID": "J", "score": 4.0}, {"Vertex_ID": "K", "score": -8.0}, {"Vertex_ID": "L", "score": 6.0}, {"Vertex_ID": "M", "score": 44.0}, {"Vertex_ID": "N", "score": 17.0}, {"Vertex_ID": "O", "score": 0.0}, {"Vertex_ID": "P", "score": 24.0}, {"Vertex_ID": "Q", "score": -7.0}, {"Vertex_ID": "R", "score": -9.0}, {"Vertex_ID": "S", "score": 16.0}, {"Vertex_ID": "T", "score": 35.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/Line_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/Line_Weighted.json index 70ee11a4..627ee40d 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/Line_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/Line_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 0.05263157894736842}, {"Vertex_ID": "B", "score": 0.10526315789473684}, {"Vertex_ID": "C", "score": 0.10526315789473684}, {"Vertex_ID": "D", "score": 0.10526315789473684}, {"Vertex_ID": "E", "score": 0.10526315789473684}, {"Vertex_ID": "F", "score": 0.10526315789473684}, {"Vertex_ID": "G", "score": 0.10526315789473684}, {"Vertex_ID": "H", "score": 0.10526315789473684}, {"Vertex_ID": "I", "score": 0.10526315789473684}, {"Vertex_ID": "J", "score": 0.10526315789473684}, {"Vertex_ID": "K", "score": 0.10526315789473684}, {"Vertex_ID": "L", "score": 0.10526315789473684}, {"Vertex_ID": "M", "score": 0.10526315789473684}, {"Vertex_ID": "N", "score": 0.10526315789473684}, {"Vertex_ID": "O", "score": 0.10526315789473684}, {"Vertex_ID": "P", "score": 0.10526315789473684}, {"Vertex_ID": "Q", "score": 0.10526315789473684}, {"Vertex_ID": "R", "score": 0.10526315789473684}, {"Vertex_ID": "S", "score": 0.10526315789473684}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 5.0}, {"Vertex_ID": "B", "score": 4.0}, {"Vertex_ID": "C", "score": 6.0}, {"Vertex_ID": "D", "score": 35.0}, {"Vertex_ID": "E", "score": 71.0}, {"Vertex_ID": "F", "score": 47.0}, {"Vertex_ID": "G", "score": -6.0}, {"Vertex_ID": "H", "score": -2.0}, {"Vertex_ID": "I", "score": 30.0}, {"Vertex_ID": "J", "score": 74.0}, {"Vertex_ID": "K", "score": 52.0}, {"Vertex_ID": "L", "score": -12.0}, {"Vertex_ID": "M", "score": 5.0}, {"Vertex_ID": "N", "score": 48.0}, {"Vertex_ID": "O", "score": 33.0}, {"Vertex_ID": "P", "score": 3.0}, {"Vertex_ID": "Q", "score": 26.0}, {"Vertex_ID": "R", "score": 17.0}, {"Vertex_ID": "S", "score": 7.0}, {"Vertex_ID": "T", "score": 15.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/Ring_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/Ring_Weighted.json index 1f77b5bc..32c2e50c 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/Ring_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/Ring_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 0.10526315789473684}, {"Vertex_ID": "B", "score": 0.10526315789473684}, {"Vertex_ID": "C", "score": 0.10526315789473684}, {"Vertex_ID": "D", "score": 0.10526315789473684}, {"Vertex_ID": "E", "score": 0.10526315789473684}, {"Vertex_ID": "F", "score": 0.10526315789473684}, {"Vertex_ID": "G", "score": 0.10526315789473684}, {"Vertex_ID": "H", "score": 0.10526315789473684}, {"Vertex_ID": "I", "score": 0.10526315789473684}, {"Vertex_ID": "J", "score": 0.10526315789473684}, {"Vertex_ID": "K", "score": 0.10526315789473684}, {"Vertex_ID": "L", "score": 0.10526315789473684}, {"Vertex_ID": "M", "score": 0.10526315789473684}, {"Vertex_ID": "N", "score": 0.10526315789473684}, {"Vertex_ID": "O", "score": 0.10526315789473684}, {"Vertex_ID": "P", "score": 0.10526315789473684}, {"Vertex_ID": "Q", "score": 0.10526315789473684}, {"Vertex_ID": "R", "score": 0.10526315789473684}, {"Vertex_ID": "S", "score": 0.10526315789473684}, {"Vertex_ID": "T", "score": 0.10526315789473684}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 21.0}, {"Vertex_ID": "B", "score": 3.0}, {"Vertex_ID": "T", "score": 18.0}, {"Vertex_ID": "C", "score": -1.0}, {"Vertex_ID": "D", "score": 6.0}, {"Vertex_ID": "E", "score": 18.0}, {"Vertex_ID": "F", "score": 6.0}, {"Vertex_ID": "G", "score": 11.0}, {"Vertex_ID": "H", "score": 18.0}, {"Vertex_ID": "I", "score": 21.0}, {"Vertex_ID": "J", "score": 15.0}, {"Vertex_ID": "K", "score": -7.0}, {"Vertex_ID": "L", "score": 2.0}, {"Vertex_ID": "M", "score": 18.0}, {"Vertex_ID": "N", "score": 17.0}, {"Vertex_ID": "O", "score": 14.0}, {"Vertex_ID": "P", "score": 20.0}, {"Vertex_ID": "Q", "score": 11.0}, {"Vertex_ID": "R", "score": 14.0}, {"Vertex_ID": "S", "score": 13.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/Tree_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/Tree_Weighted.json index 51cb839d..39475474 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/Tree_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/Tree_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 0.10526315789473684}, {"Vertex_ID": "B", "score": 0.15789473684210525}, {"Vertex_ID": "C", "score": 0.15789473684210525}, {"Vertex_ID": "D", "score": 0.15789473684210525}, {"Vertex_ID": "E", "score": 0.15789473684210525}, {"Vertex_ID": "F", "score": 0.15789473684210525}, {"Vertex_ID": "G", "score": 0.15789473684210525}, {"Vertex_ID": "H", "score": 0.15789473684210525}, {"Vertex_ID": "I", "score": 0.15789473684210525}, {"Vertex_ID": "J", "score": 0.10526315789473684}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 3.0}, {"Vertex_ID": "B", "score": 8.0}, {"Vertex_ID": "C", "score": 73.0}, {"Vertex_ID": "D", "score": 29.0}, {"Vertex_ID": "E", "score": 29.0}, {"Vertex_ID": "F", "score": 17.0}, {"Vertex_ID": "G", "score": 83.0}, {"Vertex_ID": "H", "score": 5.0}, {"Vertex_ID": "I", "score": 59.0}, {"Vertex_ID": "J", "score": 24.0}, {"Vertex_ID": "K", "score": 4.0}, {"Vertex_ID": "L", "score": -10.0}, {"Vertex_ID": "M", "score": 5.0}, {"Vertex_ID": "N", "score": 12.0}, {"Vertex_ID": "O", "score": 21.0}, {"Vertex_ID": "P", "score": 11.0}, {"Vertex_ID": "Q", "score": -6.0}, {"Vertex_ID": "R", "score": -5.0}, {"Vertex_ID": "S", "score": 33.0}, {"Vertex_ID": "T", "score": 7.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Complete_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Complete_Directed_Weighted.json new file mode 100644 index 00000000..9a6907cc --- /dev/null +++ b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Complete_Directed_Weighted.json @@ -0,0 +1 @@ +[{"top_scores": [{"Vertex_ID": "A", "score": 79.0}, {"Vertex_ID": "B", "score": 75.0}, {"Vertex_ID": "C", "score": 116.0}, {"Vertex_ID": "D", "score": 80.0}, {"Vertex_ID": "E", "score": 81.0}, {"Vertex_ID": "F", "score": 17.0}, {"Vertex_ID": "G", "score": 96.0}, {"Vertex_ID": "H", "score": 90.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json index 365279a3..f70fa0bb 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 0.0}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "B", "score": 10.0}, {"Vertex_ID": "C", "score": 2.0}, {"Vertex_ID": "D", "score": 5.0}, {"Vertex_ID": "E", "score": -3.0}, {"Vertex_ID": "F", "score": -10.0}, {"Vertex_ID": "G", "score": 8.0}, {"Vertex_ID": "H", "score": 25.0}, {"Vertex_ID": "I", "score": 31.0}, {"Vertex_ID": "J", "score": 4.0}, {"Vertex_ID": "K", "score": -8.0}, {"Vertex_ID": "L", "score": 6.0}, {"Vertex_ID": "M", "score": 44.0}, {"Vertex_ID": "N", "score": 17.0}, {"Vertex_ID": "O", "score": 0.0}, {"Vertex_ID": "P", "score": 24.0}, {"Vertex_ID": "Q", "score": -7.0}, {"Vertex_ID": "R", "score": -9.0}, {"Vertex_ID": "S", "score": 16.0}, {"Vertex_ID": "T", "score": 35.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json index 365279a3..923197e9 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 0.0}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "B", "score": 5.0}, {"Vertex_ID": "C", "score": -1.0}, {"Vertex_ID": "D", "score": 7.0}, {"Vertex_ID": "E", "score": 28.0}, {"Vertex_ID": "F", "score": 43.0}, {"Vertex_ID": "G", "score": 4.0}, {"Vertex_ID": "H", "score": -10.0}, {"Vertex_ID": "I", "score": 8.0}, {"Vertex_ID": "J", "score": 22.0}, {"Vertex_ID": "K", "score": 52.0}, {"Vertex_ID": "L", "score": 0.0}, {"Vertex_ID": "M", "score": -12.0}, {"Vertex_ID": "N", "score": 17.0}, {"Vertex_ID": "O", "score": 31.0}, {"Vertex_ID": "P", "score": 2.0}, {"Vertex_ID": "Q", "score": 1.0}, {"Vertex_ID": "R", "score": 25.0}, {"Vertex_ID": "S", "score": -8.0}, {"Vertex_ID": "T", "score": 15.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json index 62ccb50c..83098f49 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 0.05263157894736842}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "B", "score": 2.0}, {"Vertex_ID": "C", "score": 1.0}, {"Vertex_ID": "D", "score": -2.0}, {"Vertex_ID": "E", "score": 8.0}, {"Vertex_ID": "F", "score": 10.0}, {"Vertex_ID": "G", "score": -4.0}, {"Vertex_ID": "H", "score": 15.0}, {"Vertex_ID": "I", "score": 3.0}, {"Vertex_ID": "J", "score": 18.0}, {"Vertex_ID": "K", "score": -3.0}, {"Vertex_ID": "L", "score": -4.0}, {"Vertex_ID": "M", "score": 6.0}, {"Vertex_ID": "N", "score": 12.0}, {"Vertex_ID": "O", "score": 5.0}, {"Vertex_ID": "P", "score": 9.0}, {"Vertex_ID": "Q", "score": 11.0}, {"Vertex_ID": "R", "score": 0.0}, {"Vertex_ID": "S", "score": 14.0}, {"Vertex_ID": "T", "score": -1.0}, {"Vertex_ID": "A", "score": 19.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json index 365279a3..abb2e1f9 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 0.0}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "B", "score": 2.0}, {"Vertex_ID": "C", "score": 1.0}, {"Vertex_ID": "D", "score": -2.0}, {"Vertex_ID": "E", "score": 8.0}, {"Vertex_ID": "F", "score": 22.0}, {"Vertex_ID": "G", "score": 50.0}, {"Vertex_ID": "H", "score": 0.0}, {"Vertex_ID": "I", "score": 31.0}, {"Vertex_ID": "J", "score": 17.0}, {"Vertex_ID": "K", "score": 4.0}, {"Vertex_ID": "L", "score": -10.0}, {"Vertex_ID": "M", "score": 5.0}, {"Vertex_ID": "N", "score": 12.0}, {"Vertex_ID": "O", "score": 21.0}, {"Vertex_ID": "P", "score": 11.0}, {"Vertex_ID": "Q", "score": -6.0}, {"Vertex_ID": "R", "score": -5.0}, {"Vertex_ID": "S", "score": 33.0}, {"Vertex_ID": "T", "score": 7.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Complete_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Complete_Directed_Weighted.json new file mode 100644 index 00000000..e5c2b1de --- /dev/null +++ b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Complete_Directed_Weighted.json @@ -0,0 +1 @@ +[{"top_scores": [{"Vertex_ID": "A", "score": 68.0}, {"Vertex_ID": "B", "score": 51.0}, {"Vertex_ID": "C", "score": 61.0}, {"Vertex_ID": "D", "score": 168.0}, {"Vertex_ID": "E", "score": 67.0}, {"Vertex_ID": "F", "score": 80.0}, {"Vertex_ID": "G", "score": 114.0}, {"Vertex_ID": "H", "score": 25.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json index 870b88d1..4b28c2fc 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 1.0}, {"Vertex_ID": "B", "score": 0.0}, {"Vertex_ID": "C", "score": 0.0}, {"Vertex_ID": "D", "score": 0.0}, {"Vertex_ID": "E", "score": 0.0}, {"Vertex_ID": "F", "score": 0.0}, {"Vertex_ID": "G", "score": 0.0}, {"Vertex_ID": "H", "score": 0.0}, {"Vertex_ID": "I", "score": 0.0}, {"Vertex_ID": "J", "score": 0.0}, {"Vertex_ID": "K", "score": 0.0}, {"Vertex_ID": "L", "score": 0.0}, {"Vertex_ID": "M", "score": 0.0}, {"Vertex_ID": "N", "score": 0.0}, {"Vertex_ID": "O", "score": 0.0}, {"Vertex_ID": "P", "score": 0.0}, {"Vertex_ID": "Q", "score": 0.0}, {"Vertex_ID": "R", "score": 0.0}, {"Vertex_ID": "S", "score": 0.0}, {"Vertex_ID": "T", "score": 0.0}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 190.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json index d7e6932f..102dd49c 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 0.05263157894736842}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.0}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 5.0}, {"Vertex_ID": "B", "score": -1.0}, {"Vertex_ID": "C", "score": 7.0}, {"Vertex_ID": "D", "score": 28.0}, {"Vertex_ID": "E", "score": 43.0}, {"Vertex_ID": "F", "score": 4.0}, {"Vertex_ID": "G", "score": -10.0}, {"Vertex_ID": "H", "score": 8.0}, {"Vertex_ID": "I", "score": 22.0}, {"Vertex_ID": "J", "score": 52.0}, {"Vertex_ID": "K", "score": 0.0}, {"Vertex_ID": "L", "score": -12.0}, {"Vertex_ID": "M", "score": 17.0}, {"Vertex_ID": "N", "score": 31.0}, {"Vertex_ID": "O", "score": 2.0}, {"Vertex_ID": "P", "score": 1.0}, {"Vertex_ID": "Q", "score": 25.0}, {"Vertex_ID": "R", "score": -8.0}, {"Vertex_ID": "S", "score": 15.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json index 62ccb50c..4609b3bf 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 0.05263157894736842}, {"Vertex_ID": "B", "score": 0.05263157894736842}, {"Vertex_ID": "C", "score": 0.05263157894736842}, {"Vertex_ID": "D", "score": 0.05263157894736842}, {"Vertex_ID": "E", "score": 0.05263157894736842}, {"Vertex_ID": "F", "score": 0.05263157894736842}, {"Vertex_ID": "G", "score": 0.05263157894736842}, {"Vertex_ID": "H", "score": 0.05263157894736842}, {"Vertex_ID": "I", "score": 0.05263157894736842}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.05263157894736842}, {"Vertex_ID": "L", "score": 0.05263157894736842}, {"Vertex_ID": "M", "score": 0.05263157894736842}, {"Vertex_ID": "N", "score": 0.05263157894736842}, {"Vertex_ID": "O", "score": 0.05263157894736842}, {"Vertex_ID": "P", "score": 0.05263157894736842}, {"Vertex_ID": "Q", "score": 0.05263157894736842}, {"Vertex_ID": "R", "score": 0.05263157894736842}, {"Vertex_ID": "S", "score": 0.05263157894736842}, {"Vertex_ID": "T", "score": 0.05263157894736842}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 2.0}, {"Vertex_ID": "B", "score": 1.0}, {"Vertex_ID": "C", "score": -2.0}, {"Vertex_ID": "D", "score": 8.0}, {"Vertex_ID": "E", "score": 10.0}, {"Vertex_ID": "F", "score": -4.0}, {"Vertex_ID": "G", "score": 15.0}, {"Vertex_ID": "H", "score": 3.0}, {"Vertex_ID": "I", "score": 18.0}, {"Vertex_ID": "J", "score": -3.0}, {"Vertex_ID": "K", "score": -4.0}, {"Vertex_ID": "L", "score": 6.0}, {"Vertex_ID": "M", "score": 12.0}, {"Vertex_ID": "N", "score": 5.0}, {"Vertex_ID": "O", "score": 9.0}, {"Vertex_ID": "P", "score": 11.0}, {"Vertex_ID": "Q", "score": 0.0}, {"Vertex_ID": "R", "score": 14.0}, {"Vertex_ID": "S", "score": -1.0}, {"Vertex_ID": "T", "score": 19.0}]}] \ No newline at end of file diff --git a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json index 9471b1db..051b899a 100644 --- a/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json +++ b/tests/data/baseline/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json @@ -1 +1 @@ -[{"top_scores": [{"Vertex_ID": "A", "score": 0.10526315789473684}, {"Vertex_ID": "B", "score": 0.10526315789473684}, {"Vertex_ID": "C", "score": 0.10526315789473684}, {"Vertex_ID": "D", "score": 0.10526315789473684}, {"Vertex_ID": "E", "score": 0.10526315789473684}, {"Vertex_ID": "F", "score": 0.10526315789473684}, {"Vertex_ID": "G", "score": 0.10526315789473684}, {"Vertex_ID": "H", "score": 0.10526315789473684}, {"Vertex_ID": "I", "score": 0.10526315789473684}, {"Vertex_ID": "J", "score": 0.05263157894736842}, {"Vertex_ID": "K", "score": 0.0}, {"Vertex_ID": "L", "score": 0.0}, {"Vertex_ID": "M", "score": 0.0}, {"Vertex_ID": "N", "score": 0.0}, {"Vertex_ID": "O", "score": 0.0}, {"Vertex_ID": "P", "score": 0.0}, {"Vertex_ID": "Q", "score": 0.0}, {"Vertex_ID": "R", "score": 0.0}, {"Vertex_ID": "S", "score": 0.0}, {"Vertex_ID": "T", "score": 0.0}]}] \ No newline at end of file +[{"top_scores": [{"Vertex_ID": "A", "score": 3.0}, {"Vertex_ID": "B", "score": 6.0}, {"Vertex_ID": "C", "score": 72.0}, {"Vertex_ID": "D", "score": 31.0}, {"Vertex_ID": "E", "score": 21.0}, {"Vertex_ID": "F", "score": -5.0}, {"Vertex_ID": "G", "score": 33.0}, {"Vertex_ID": "H", "score": 5.0}, {"Vertex_ID": "I", "score": 28.0}, {"Vertex_ID": "J", "score": 7.0}]}] \ No newline at end of file diff --git a/tests/run.sh b/tests/run.sh index e2f54a4c..7e6c6b29 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -1,8 +1,5 @@ clear -cd test -python3 create_baseline.py -# exit 0 -cd .. -python test/setup.py && - pytest test/test_centrality.py - # pytest +python3 test/create_baseline.py && + python3 test/setup.py && + pytest test/test_centrality.py::TestCentrality +# pytest diff --git a/tests/test/create_baseline.py b/tests/test/create_baseline.py index 9ca84d63..ce6e0ff8 100644 --- a/tests/test/create_baseline.py +++ b/tests/test/create_baseline.py @@ -1,14 +1,34 @@ import csv import json +from collections import Counter +from functools import partial import networkx as nx import numpy as np from tqdm import tqdm -data_path_root = "../data/" +data_path_root = "data/" baseline_path_root = f"{data_path_root}/baseline/" +def weighted_deg_cent( + g: nx.Graph, + dir: str = "", +): + res = Counter() + for e in g.edges: + a = g.get_edge_data(e[0], e[1])["weight"] + match dir: + case "in": + res[e[1]] += a + case "out": + res[e[0]] += a + case _: + res[e[0]] += a + res[e[1]] += a + return res + + def run_degree_baseline_complete(g: nx.Graph, _): s = 1.0 / (len(g) - 1.0) @@ -40,6 +60,8 @@ def create_graph(edges, weights=False, directed=False): else: g = nx.Graph() if weights: + # make weights floats + edges = [[a, b, float(c)] for a, b, c in edges] g.add_weighted_edges_from(edges) else: g.add_edges_from(edges) @@ -47,14 +69,16 @@ def create_graph(edges, weights=False, directed=False): def create_degree_baseline(paths): - for p, out_path, fn, m in tqdm(paths, desc="Creating baselines"): + t = tqdm(paths, desc="Creating baselines") + for p, out_path, fn, m in t: + t.set_postfix_str(out_path.split("/")[-1].split(".")[0]) with open(p) as f: edges = np.array(list(csv.reader(f))) directed = True if "Directed" in out_path else False - # weights = edges[:, -1].astype(float) if "Weighted" in out_path else None weights = True if "Weighted" in out_path else False g = create_graph(edges, weights, directed) + # from matplotlib import pyplot as plt # pos = nx.drawing.layout.kamada_kawai_layout(g) # nx.draw(g, pos) @@ -154,83 +178,94 @@ def create_degree_baseline(paths): ( f"{data_path_root}/weighted_edges/complete_edges.csv", f"{baseline_path_root}/centrality/weighted_degree_centrality/Complete_Weighted.json", - run_degree_baseline_complete, - None, + run_degree_baseline, + partial(weighted_deg_cent), ), ( f"{data_path_root}/weighted_edges/line_edges.csv", f"{baseline_path_root}/centrality/weighted_degree_centrality/Line_Weighted.json", run_degree_baseline, - nx.centrality.degree_centrality, + weighted_deg_cent, ), ( f"{data_path_root}/weighted_edges/ring_edges.csv", f"{baseline_path_root}/centrality/weighted_degree_centrality/Ring_Weighted.json", run_degree_baseline, - nx.centrality.degree_centrality, + weighted_deg_cent, ), ( f"{data_path_root}/weighted_edges/hubspoke_edges.csv", f"{baseline_path_root}/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json", run_degree_baseline, - nx.centrality.degree_centrality, + weighted_deg_cent, ), ( f"{data_path_root}/weighted_edges/tree_edges.csv", f"{baseline_path_root}/centrality/weighted_degree_centrality/Tree_Weighted.json", run_degree_baseline, - nx.centrality.degree_centrality, + weighted_deg_cent, ), # in_degree + ( + f"{data_path_root}/weighted_edges/complete_edges_directed.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Complete_Directed_Weighted.json", + run_degree_baseline, + partial(weighted_deg_cent, dir="in"), + ), ( f"{data_path_root}/weighted_edges/line_edges.csv", f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json", run_degree_baseline, - nx.centrality.in_degree_centrality, + partial(weighted_deg_cent, dir="in"), ), ( f"{data_path_root}/weighted_edges/ring_edges.csv", f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json", run_degree_baseline, - nx.centrality.in_degree_centrality, + partial(weighted_deg_cent, dir="in"), ), ( f"{data_path_root}/weighted_edges/hubspoke_edges.csv", f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json", run_degree_baseline, - nx.centrality.in_degree_centrality, + partial(weighted_deg_cent, dir="in"), ), ( f"{data_path_root}/weighted_edges/tree_edges.csv", f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json", run_degree_baseline, - nx.centrality.in_degree_centrality, + partial(weighted_deg_cent, dir="in"), ), # out_degree + ( + f"{data_path_root}/weighted_edges/complete_edges_directed.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Complete_Directed_Weighted.json", + run_degree_baseline, + partial(weighted_deg_cent, dir="out"), + ), ( f"{data_path_root}/weighted_edges/line_edges.csv", f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json", run_degree_baseline, - nx.centrality.out_degree_centrality, + partial(weighted_deg_cent, dir="out"), ), ( f"{data_path_root}/weighted_edges/ring_edges.csv", f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json", run_degree_baseline, - nx.centrality.out_degree_centrality, + partial(weighted_deg_cent, dir="out"), ), ( f"{data_path_root}/weighted_edges/hubspoke_edges.csv", f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json", run_degree_baseline, - nx.centrality.out_degree_centrality, + partial(weighted_deg_cent, dir="out"), ), ( f"{data_path_root}/weighted_edges/tree_edges.csv", f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json", run_degree_baseline, - nx.centrality.out_degree_centrality, + partial(weighted_deg_cent, dir="out"), ), - # weighted ] create_degree_baseline(paths) diff --git a/tests/test/test_centrality.py b/tests/test/test_centrality.py index efabdc1f..6f36295b 100644 --- a/tests/test/test_centrality.py +++ b/tests/test/test_centrality.py @@ -1,6 +1,7 @@ import json import pytest + import util @@ -30,8 +31,11 @@ class TestCentrality: "Ring_Directed_Weighted", "Hub_Spoke_Directed_Weighted", "Tree_Directed_Weighted", + "Complete_Directed_Weighted", + ] + complete_graphs = [ + "Complete", ] - complete_graphs = ["Complete"] @pytest.mark.parametrize("test_name", undirected_graphs) def test_degree_centrality1(self, test_name): @@ -165,6 +169,7 @@ def test_weighted_degree_centrality1(self, test_name): result = sorted(result[0]["top_scores"], key=lambda x: x["Vertex_ID"]) baseline = sorted(baseline[0]["top_scores"], key=lambda x: x["Vertex_ID"]) + print(result) for b in baseline: for r in result: if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] != pytest.approx( @@ -174,8 +179,9 @@ def test_weighted_degree_centrality1(self, test_name): @pytest.mark.parametrize("test_name", weighted_directed_graphs) def test_weighted_degree_centrality2(self, test_name): + vt = "V20" if "Complete" not in test_name else "V8" params = { - "v_type": "V20", + "v_type": vt, "e_type": test_name, "reverse_e_type": "reverse_" + test_name, "weight_attribute": "weight", @@ -203,8 +209,9 @@ def test_weighted_degree_centrality2(self, test_name): @pytest.mark.parametrize("test_name", weighted_directed_graphs) def test_weighted_degree_centrality3(self, test_name): + vt = "V20" if "Complete" not in test_name else "V8" params = { - "v_type": "V20", + "v_type": vt, "e_type": test_name, "reverse_e_type": "reverse_" + test_name, "weight_attribute": "weight", From 6c8fb8f8f4e40d3689f5eda2a765804828ad474f Mon Sep 17 00:00:00 2001 From: RobRossmiller-TG <165701656+RobRossmiller-TG@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:55:16 -0400 Subject: [PATCH 19/38] update template query --- .../centrality/weighted_degree_cent.gsql | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/GDBMS_ALGO/centrality/weighted_degree_cent.gsql b/GDBMS_ALGO/centrality/weighted_degree_cent.gsql index 3c8dcac4..79fcaef9 100644 --- a/GDBMS_ALGO/centrality/weighted_degree_cent.gsql +++ b/GDBMS_ALGO/centrality/weighted_degree_cent.gsql @@ -1,5 +1,4 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.centrality.weighted_degree_cent(STRING v_type, STRING e_type, STRING reverse_e_type, string weight_attribute, BOOL in_degree = TRUE, BOOL out_degree = TRUE, INT top_k=100, BOOL print_results = TRUE, STRING result_attribute = "",STRING file_path = "") SYNTAX V1 { - /* First Author: First Commit Date: @@ -22,18 +21,20 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.centrality.weighted_degree_cent(STRING v_type, for undirected graph, you only need to set e_type and in_degree Publications: - + NA TigerGraph Documentation: - + https://docs.tigergraph.com/graph-ml/current/centrality-algorithms/weighted-degree-centrality Parameters: v_type: - vertex types to traverse + Vertex types to traverse e_type: - edge types to traverse + Edge types to traverse reverse_e_type: - for indegree use + For indegree use + weight_attribute: + The edge weight attribute name in_degree: If True, count incoming relationships out_degree: @@ -43,27 +44,28 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.centrality.weighted_degree_cent(STRING v_type, print_results: If True, print the results result_attribute: - attribute to write result to + Attribute to write result to file_path: - file to write CSV output to - */ + File to write CSV output to + */ TYPEDEF TUPLE Vertex_Score; HeapAccum(top_k, score DESC) @@top_scores_heap; - SumAccum @sum_degree_score; + SumAccum @sum_degree_score; FILE f (file_path); all = {v_type}; IF in_degree THEN sll = SELECT s - FROM all:s-(reverse_e_type:e)-:t - ACCUM s.@sum_degree_score+=e.getAttr(weight_attribute,"INT"); + FROM all:s -(reverse_e_type:e)- :t + ACCUM s.@sum_degree_score += e.getAttr(weight_attribute, "FLOAT"); END; IF out_degree THEN sll = SELECT s - FROM all:s-(e_type:e)-:t - ACCUM s.@sum_degree_score+=e.getAttr(weight_attribute,"INT"); + FROM all:s -(e_type:e)- :t + ACCUM s.@sum_degree_score += e.getAttr(weight_attribute, "FLOAT"); END; + #Output IF file_path != "" THEN f.println("Vertex_ID", "Degree"); @@ -72,17 +74,17 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.centrality.weighted_degree_cent(STRING v_type, Start = SELECT s FROM all:s POST-ACCUM - IF result_attribute != "" THEN - s.setAttr(result_attribute, s.@sum_degree_score) - END, - - IF print_results THEN - @@top_scores_heap += Vertex_Score(s, s.@sum_degree_score) - END, - - IF file_path != "" THEN - f.println(s, s.@sum_degree_score) - END; + IF result_attribute != "" THEN + s.setAttr(result_attribute, s.@sum_degree_score) + END, + + IF print_results THEN + @@top_scores_heap += Vertex_Score(s, s.@sum_degree_score) + END, + + IF file_path != "" THEN + f.println(s, s.@sum_degree_score) + END; IF print_results THEN PRINT @@top_scores_heap AS top_scores; From 31254c6890bcea665901ce90b482e535125b29a7 Mon Sep 17 00:00:00 2001 From: minzhangtg Date: Mon, 8 Jul 2024 11:44:30 +0800 Subject: [PATCH 20/38] [GLE-7418] fix(install): move gsql temp folder into data folder --- tools/scripts/bash_functions | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/scripts/bash_functions b/tools/scripts/bash_functions index b2d060bc..a55b0c07 100644 --- a/tools/scripts/bash_functions +++ b/tools/scripts/bash_functions @@ -151,7 +151,8 @@ function decrypt () { else QNAME=$1 fi - codegen_path="$(getAppRoot)/dev/gdk/gsql/.tmp/codeGen" + version=$(basename "$(getAppRoot)") + codegen_path="$(gadmin config get System.DataRoot)/gsql/${version}/.tmp/codeGen" if [[ x$QNAME != x ]] then FILES=$(ls ${codegen_path}/*$QNAME.cpp 2>/dev/null) @@ -181,7 +182,8 @@ function _complete_decrypt { fi local cur=${COMP_WORDS[COMP_CWORD]} - local QUERIES=$(ls $(getAppRoot)/dev/gdk/gsql/.tmp/codeGen/${cur}*.cpp 2>/dev/null) + local version=$(basename "$(getAppRoot)") + local QUERIES=$(ls "$(gadmin config get System.DataRoot)/gsql/${version}/.tmp/codeGen/${cur}*.cpp" 2>/dev/null) for QUERY in $QUERIES; do local filename=$(basename $QUERY) From fa6f0fe65a2c26a63a6ff866e10b0e467ca3dd02 Mon Sep 17 00:00:00 2001 From: RobRossmiller-TG <165701656+RobRossmiller-TG@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:43:22 -0400 Subject: [PATCH 21/38] reorganize --- .../GraphML/Embeddings/FastRP/tg_fastRP.gsql | 2 +- tests/run.sh | 7 +- tests/test/create_baseline.py | 271 ------------------ tests/test/setup.py | 25 +- tests/test/util.py | 4 +- 5 files changed, 29 insertions(+), 280 deletions(-) delete mode 100644 tests/test/create_baseline.py diff --git a/algorithms/GraphML/Embeddings/FastRP/tg_fastRP.gsql b/algorithms/GraphML/Embeddings/FastRP/tg_fastRP.gsql index fa78bef3..5dac70b5 100644 --- a/algorithms/GraphML/Embeddings/FastRP/tg_fastRP.gsql +++ b/algorithms/GraphML/Embeddings/FastRP/tg_fastRP.gsql @@ -265,7 +265,7 @@ CREATE QUERY tg_fastRP( IF print_results THEN res = SELECT a FROM verts:a; - PRINT res[res.@final_embedding_arr]; + PRINT res[res.@final_embedding_list]; END; // (un)comment depending on whether you want to write to graph diff --git a/tests/run.sh b/tests/run.sh index 7e6c6b29..801936b8 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -1,5 +1,6 @@ clear -python3 test/create_baseline.py && - python3 test/setup.py && - pytest test/test_centrality.py::TestCentrality +python3 test/setup.py && + python3 test/baseline/create_baselines.py && + pytest test/test_centrality.py && + pytest test/test_ml.py # pytest diff --git a/tests/test/create_baseline.py b/tests/test/create_baseline.py deleted file mode 100644 index ce6e0ff8..00000000 --- a/tests/test/create_baseline.py +++ /dev/null @@ -1,271 +0,0 @@ -import csv -import json -from collections import Counter -from functools import partial - -import networkx as nx -import numpy as np -from tqdm import tqdm - -data_path_root = "data/" -baseline_path_root = f"{data_path_root}/baseline/" - - -def weighted_deg_cent( - g: nx.Graph, - dir: str = "", -): - res = Counter() - for e in g.edges: - a = g.get_edge_data(e[0], e[1])["weight"] - match dir: - case "in": - res[e[1]] += a - case "out": - res[e[0]] += a - case _: - res[e[0]] += a - res[e[1]] += a - return res - - -def run_degree_baseline_complete(g: nx.Graph, _): - s = 1.0 / (len(g) - 1.0) - - # d-1 because nx will double count the self-edge - res = {n: (d - 1) * s for n, d in g.degree()} - - out = [] - for k, v in res.items(): - out.append({"Vertex_ID": k, "score": v}) - - out = [{"top_scores": out}] - return out - - -def run_degree_baseline(g: nx.Graph, metric): - res = metric(g) - - out = [] - for k, v in res.items(): - out.append({"Vertex_ID": k, "score": v}) - - out = [{"top_scores": out}] - return out - - -def create_graph(edges, weights=False, directed=False): - if directed: - g = nx.DiGraph() - else: - g = nx.Graph() - if weights: - # make weights floats - edges = [[a, b, float(c)] for a, b, c in edges] - g.add_weighted_edges_from(edges) - else: - g.add_edges_from(edges) - return g - - -def create_degree_baseline(paths): - t = tqdm(paths, desc="Creating baselines") - for p, out_path, fn, m in t: - t.set_postfix_str(out_path.split("/")[-1].split(".")[0]) - with open(p) as f: - edges = np.array(list(csv.reader(f))) - - directed = True if "Directed" in out_path else False - weights = True if "Weighted" in out_path else False - g = create_graph(edges, weights, directed) - - # from matplotlib import pyplot as plt - # pos = nx.drawing.layout.kamada_kawai_layout(g) - # nx.draw(g, pos) - # nx.draw_networkx_labels(g, pos, {n: n for n in g.nodes}) - # plt.savefig(f"{out_path.split('/')[-1]}.png") - - res = fn(g, m) - with open(out_path, "w") as f: - json.dump(res, f) # , indent=2) - - -if __name__ == "__main__": - # (data, output_path, fun, metric) - paths = [ - # unweighted - ( - f"{data_path_root}/unweighted_edges/complete_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/Complete.json", - run_degree_baseline_complete, - None, - ), - ( - f"{data_path_root}/unweighted_edges/line_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/Line.json", - run_degree_baseline, - nx.centrality.degree_centrality, - ), - ( - f"{data_path_root}/unweighted_edges/ring_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/Ring.json", - run_degree_baseline, - nx.centrality.degree_centrality, - ), - ( - f"{data_path_root}/unweighted_edges/hubspoke_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/Hub_Spoke.json", - run_degree_baseline, - nx.centrality.degree_centrality, - ), - ( - f"{data_path_root}/unweighted_edges/tree_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/Tree.json", - run_degree_baseline, - nx.centrality.degree_centrality, - ), - # in_degree - ( - f"{data_path_root}/unweighted_edges/line_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/in_degree/Line_Directed.json", - run_degree_baseline, - nx.centrality.in_degree_centrality, - ), - ( - f"{data_path_root}/unweighted_edges/ring_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/in_degree/Ring_Directed.json", - run_degree_baseline, - nx.centrality.in_degree_centrality, - ), - ( - f"{data_path_root}/unweighted_edges/hubspoke_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/in_degree/Hub_Spoke_Directed.json", - run_degree_baseline, - nx.centrality.in_degree_centrality, - ), - ( - f"{data_path_root}/unweighted_edges/tree_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/in_degree/Tree_Directed.json", - run_degree_baseline, - nx.centrality.in_degree_centrality, - ), - # out_degree - ( - f"{data_path_root}/unweighted_edges/line_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/out_degree/Line_Directed.json", - run_degree_baseline, - nx.centrality.out_degree_centrality, - ), - ( - f"{data_path_root}/unweighted_edges/ring_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/out_degree/Ring_Directed.json", - run_degree_baseline, - nx.centrality.out_degree_centrality, - ), - ( - f"{data_path_root}/unweighted_edges/hubspoke_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/out_degree/Hub_Spoke_Directed.json", - run_degree_baseline, - nx.centrality.out_degree_centrality, - ), - ( - f"{data_path_root}/unweighted_edges/tree_edges.csv", - f"{baseline_path_root}/centrality/degree_centrality/out_degree/Tree_Directed.json", - run_degree_baseline, - nx.centrality.out_degree_centrality, - ), - # weighted - ( - f"{data_path_root}/weighted_edges/complete_edges.csv", - f"{baseline_path_root}/centrality/weighted_degree_centrality/Complete_Weighted.json", - run_degree_baseline, - partial(weighted_deg_cent), - ), - ( - f"{data_path_root}/weighted_edges/line_edges.csv", - f"{baseline_path_root}/centrality/weighted_degree_centrality/Line_Weighted.json", - run_degree_baseline, - weighted_deg_cent, - ), - ( - f"{data_path_root}/weighted_edges/ring_edges.csv", - f"{baseline_path_root}/centrality/weighted_degree_centrality/Ring_Weighted.json", - run_degree_baseline, - weighted_deg_cent, - ), - ( - f"{data_path_root}/weighted_edges/hubspoke_edges.csv", - f"{baseline_path_root}/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json", - run_degree_baseline, - weighted_deg_cent, - ), - ( - f"{data_path_root}/weighted_edges/tree_edges.csv", - f"{baseline_path_root}/centrality/weighted_degree_centrality/Tree_Weighted.json", - run_degree_baseline, - weighted_deg_cent, - ), - # in_degree - ( - f"{data_path_root}/weighted_edges/complete_edges_directed.csv", - f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Complete_Directed_Weighted.json", - run_degree_baseline, - partial(weighted_deg_cent, dir="in"), - ), - ( - f"{data_path_root}/weighted_edges/line_edges.csv", - f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json", - run_degree_baseline, - partial(weighted_deg_cent, dir="in"), - ), - ( - f"{data_path_root}/weighted_edges/ring_edges.csv", - f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json", - run_degree_baseline, - partial(weighted_deg_cent, dir="in"), - ), - ( - f"{data_path_root}/weighted_edges/hubspoke_edges.csv", - f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json", - run_degree_baseline, - partial(weighted_deg_cent, dir="in"), - ), - ( - f"{data_path_root}/weighted_edges/tree_edges.csv", - f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json", - run_degree_baseline, - partial(weighted_deg_cent, dir="in"), - ), - # out_degree - ( - f"{data_path_root}/weighted_edges/complete_edges_directed.csv", - f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Complete_Directed_Weighted.json", - run_degree_baseline, - partial(weighted_deg_cent, dir="out"), - ), - ( - f"{data_path_root}/weighted_edges/line_edges.csv", - f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json", - run_degree_baseline, - partial(weighted_deg_cent, dir="out"), - ), - ( - f"{data_path_root}/weighted_edges/ring_edges.csv", - f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json", - run_degree_baseline, - partial(weighted_deg_cent, dir="out"), - ), - ( - f"{data_path_root}/weighted_edges/hubspoke_edges.csv", - f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json", - run_degree_baseline, - partial(weighted_deg_cent, dir="out"), - ), - ( - f"{data_path_root}/weighted_edges/tree_edges.csv", - f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json", - run_degree_baseline, - partial(weighted_deg_cent, dir="out"), - ), - ] - create_degree_baseline(paths) diff --git a/tests/test/setup.py b/tests/test/setup.py index 406077c3..fad28088 100644 --- a/tests/test/setup.py +++ b/tests/test/setup.py @@ -1,17 +1,31 @@ import json import os import re +import time import pyTigerGraph as tg -import util from dotenv import load_dotenv from pyTigerGraph.datasets import Datasets -from tqdm import tqdm +from tqdm import tqdm, trange + +import util load_dotenv() graph_name = "graph_algorithms_testing" pattern = re.compile(r'"name":\s*"tg_.*"') + +def add_reverse_edge(ds: Datasets): + with open(f"{dataset.tmp_dir}/{ds.name}/create_schema.gsql") as f: + schema: str = f.read() + with open(f"{dataset.tmp_dir}/{ds.name}/create_schema.gsql", "w") as f: + schema = schema.replace( + "ADD DIRECTED EDGE Cite (from Paper, to Paper, time Int, is_train Bool, is_val Bool);", + 'ADD DIRECTED EDGE Cite (from Paper, to Paper, time Int, is_train Bool, is_val Bool) WITH REVERSE_EDGE="reverse_Cite";', + ) + f.write(schema) + + if __name__ == "__main__": host_name = os.getenv("HOST_NAME") user_name = os.getenv("USER_NAME") @@ -28,9 +42,14 @@ if res["error"]: exit(1) # load the data + dataset = Datasets("Cora") + add_reverse_edge(dataset) + conn.ingestDataset(dataset, getToken=True) + dataset = Datasets("graph_algorithms_testing") conn.ingestDataset(dataset, getToken=True) + conn.graphname = graph_name # install the queries feat = conn.gds.featurizer() installed_queries = util.get_installed_queries(conn) @@ -43,3 +62,5 @@ print(q) feat.installAlgorithm(q) + for _ in trange(30, desc="Sleeping while data loads"): + time.sleep(1) diff --git a/tests/test/util.py b/tests/test/util.py index 3418a913..ca26c785 100644 --- a/tests/test/util.py +++ b/tests/test/util.py @@ -6,9 +6,7 @@ load_dotenv() -def get_featurizer(): - graph_name = "graph_algorithms_testing" - +def get_featurizer(graph_name="graph_algorithms_testing"): host_name = os.getenv("HOST_NAME") user_name = os.getenv("USER_NAME") password = os.getenv("PASS") From 08717cb9274fa470183887f33249f03df0e59e8c Mon Sep 17 00:00:00 2001 From: RobRossmiller-TG <165701656+RobRossmiller-TG@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:43:35 -0400 Subject: [PATCH 22/38] start fastrp testinggp --- tests/data/baseline/ml/fastRP.json.gz | Bin 0 -> 209385 bytes tests/test/baseline/__init__.py | 0 tests/test/baseline/algos/__init__.py | 2 + tests/test/baseline/algos/degree_cent.py | 46 ++++ tests/test/baseline/algos/fastrp.py | 107 +++++++++ tests/test/baseline/create_baselines.py | 7 + tests/test/baseline/degree_cent_baseline.py | 228 ++++++++++++++++++++ tests/test/baseline/fast_rp_baseline.py | 44 ++++ tests/test/test_ml.py | 64 ++++++ 9 files changed, 498 insertions(+) create mode 100644 tests/data/baseline/ml/fastRP.json.gz create mode 100644 tests/test/baseline/__init__.py create mode 100644 tests/test/baseline/algos/__init__.py create mode 100644 tests/test/baseline/algos/degree_cent.py create mode 100644 tests/test/baseline/algos/fastrp.py create mode 100644 tests/test/baseline/create_baselines.py create mode 100644 tests/test/baseline/degree_cent_baseline.py create mode 100644 tests/test/baseline/fast_rp_baseline.py create mode 100644 tests/test/test_ml.py diff --git a/tests/data/baseline/ml/fastRP.json.gz b/tests/data/baseline/ml/fastRP.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..31ab1974eb5e9b1689ef01d639ea3b6f183fee4a GIT binary patch literal 209385 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ä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

g| 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 literal 0 HcmV?d00001 diff --git a/tests/test/baseline/__init__.py b/tests/test/baseline/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test/baseline/algos/__init__.py b/tests/test/baseline/algos/__init__.py new file mode 100644 index 00000000..328e264d --- /dev/null +++ b/tests/test/baseline/algos/__init__.py @@ -0,0 +1,2 @@ +from .degree_cent import * +from .fastrp import fastrp_wrapper as fastrp diff --git a/tests/test/baseline/algos/degree_cent.py b/tests/test/baseline/algos/degree_cent.py new file mode 100644 index 00000000..4ef80d57 --- /dev/null +++ b/tests/test/baseline/algos/degree_cent.py @@ -0,0 +1,46 @@ +from collections import Counter + +import networkx as nx + + +def run_degree_baseline_complete(g: nx.Graph, _): + s = 1.0 / (len(g) - 1.0) + + # d-1 because nx will double count the self-edge + res = {n: (d - 1) * s for n, d in g.degree()} + + out = [] + for k, v in res.items(): + out.append({"Vertex_ID": k, "score": v}) + + out = [{"top_scores": out}] + return out + + +def run_degree_baseline(g: nx.Graph, metric): + res = metric(g) + + out = [] + for k, v in res.items(): + out.append({"Vertex_ID": k, "score": v}) + + out = [{"top_scores": out}] + return out + + +def weighted_deg_cent( + g: nx.Graph, + dir: str = "", +): + res = Counter() + for e in g.edges: + a = g.get_edge_data(e[0], e[1])["weight"] + match dir: + case "in": + res[e[1]] += a + case "out": + res[e[0]] += a + case _: + res[e[0]] += a + res[e[1]] += a + return res diff --git a/tests/test/baseline/algos/fastrp.py b/tests/test/baseline/algos/fastrp.py new file mode 100644 index 00000000..3200f984 --- /dev/null +++ b/tests/test/baseline/algos/fastrp.py @@ -0,0 +1,107 @@ +# source: https://github.com/GTmac/FastRP/blob/master/fastrp.py + +import numpy as np +from scipy.sparse import csc_matrix, csr_matrix, spdiags +from sklearn import random_projection +from sklearn.preprocessing import normalize, scale + + +# projection method: choose from Gaussian and Sparse +# input matrix: choose from adjacency and transition matrix +# alpha adjusts the weighting of nodes according to their degree +def fastrp_projection( + A, q=3, dim=128, projection_method="gaussian", input_matrix="adj", alpha=None +): + assert input_matrix == "adj" or input_matrix == "trans" + assert projection_method == "gaussian" or projection_method == "sparse" + + if input_matrix == "adj": + M = A + else: + N = A.shape[0] + normalizer = spdiags(np.squeeze(1.0 / csc_matrix.sum(A, axis=1)), 0, N, N) + M = normalizer @ A + # Gaussian projection matrix + if projection_method == "gaussian": + transformer = random_projection.GaussianRandomProjection( + n_components=dim, random_state=42 + ) + # Sparse projection matrix + else: + transformer = random_projection.SparseRandomProjection( + n_components=dim, random_state=42 + ) + Y = transformer.fit(M) + # Random projection for A + if alpha is not None: + Y.components_ = Y.components_ @ spdiags( + np.squeeze(np.power(csc_matrix.sum(A, axis=1), alpha)), 0, N, N + ) + cur_U = transformer.transform(M) + U_list = [cur_U] + + for _ in range(2, q + 1): + cur_U = M @ cur_U + U_list.append(cur_U) + return U_list + + +# When weights is None, concatenate instead of linearly combines the embeddings from different powers of A +def fastrp_merge(U_list, weights, normalization=False): + dense_U_list = ( + [_U.todense() for _U in U_list] if type(U_list[0]) == csc_matrix else U_list + ) + _U_list = ( + [normalize(_U, norm="l2", axis=1) for _U in dense_U_list] + if normalization + else dense_U_list + ) + + if weights is None: + return np.concatenate(_U_list, axis=1) + U = np.zeros_like(_U_list[0]) + for cur_U, weight in zip(_U_list, weights): + U += cur_U * weight + # U = scale(U.todense()) + # U = normalize(U.todense(), norm='l2', axis=1) + return scale(U.toarray()) if type(U) == csr_matrix else scale(U) + + +# A is always the adjacency matrix +# the choice between adj matrix and trans matrix is decided in the conf +def fastrp_wrapper(A, conf): + U_list = fastrp_projection( + A, + q=len(conf["weights"]), + dim=conf["dim"], + projection_method=conf["projection_method"], + input_matrix=conf["input_matrix"], + alpha=conf["alpha"], + ) + U = fastrp_merge(U_list, conf["weights"], conf["normalization"]) + return U + + +def get_emb_filename(prefix, conf): + return ( + prefix + + "-dim=" + + str(conf["dim"]) + + ",projection_method=" + + conf["projection_method"] + + ",input_matrix=" + + conf["input_matrix"] + + ",normalization=" + + str(conf["normalization"]) + + ",weights=" + + ( + ",".join(map(str, conf["weights"])) + if conf["weights"] is not None + else "None" + ) + + ",alpha=" + + (str(conf["alpha"]) if "alpha" in conf else "") + + ",C=" + + (str(conf["C"]) if "alpha" in conf else "1.0") + + ".mat" + ) diff --git a/tests/test/baseline/create_baselines.py b/tests/test/baseline/create_baselines.py new file mode 100644 index 00000000..76fa0b0f --- /dev/null +++ b/tests/test/baseline/create_baselines.py @@ -0,0 +1,7 @@ +import degree_cent_baseline +import fast_rp_baseline + +if __name__ == "__main__": + # degree_cent_baseline.run() + + fast_rp_baseline.run() diff --git a/tests/test/baseline/degree_cent_baseline.py b/tests/test/baseline/degree_cent_baseline.py new file mode 100644 index 00000000..9791226d --- /dev/null +++ b/tests/test/baseline/degree_cent_baseline.py @@ -0,0 +1,228 @@ +import csv +import json +from functools import partial + +import networkx as nx +import numpy as np +from algos import run_degree_baseline, run_degree_baseline_complete, weighted_deg_cent +from tqdm import tqdm + +data_path_root = "data/" +baseline_path_root = f"{data_path_root}/baseline/" + + +def create_graph(edges, weights=False, directed=False): + if directed: + g = nx.DiGraph() + else: + g = nx.Graph() + if weights: + # make weights floats + edges = [[a, b, float(c)] for a, b, c in edges] + g.add_weighted_edges_from(edges) + else: + g.add_edges_from(edges) + return g + + +def create_degree_baseline(paths): + t = tqdm(paths, desc="Creating baselines") + for p, out_path, fn, m in t: + t.set_postfix_str(out_path.split("/")[-1].split(".")[0]) + with open(p) as f: + edges = np.array(list(csv.reader(f))) + + directed = True if "Directed" in out_path else False + weights = True if "Weighted" in out_path else False + g = create_graph(edges, weights, directed) + + # from matplotlib import pyplot as plt + # pos = nx.drawing.layout.kamada_kawai_layout(g) + # nx.draw(g, pos) + # nx.draw_networkx_labels(g, pos, {n: n for n in g.nodes}) + # plt.savefig(f"{out_path.split('/')[-1]}.png") + + res = fn(g, m) + with open(out_path, "w") as f: + json.dump(res, f) # , indent=2) + + +def run(): + # (data, output_path, fun, metric) + paths = [ + # unweighted + ( + f"{data_path_root}/unweighted_edges/complete_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/Complete.json", + run_degree_baseline_complete, + None, + ), + ( + f"{data_path_root}/unweighted_edges/line_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/Line.json", + run_degree_baseline, + nx.centrality.degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/ring_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/Ring.json", + run_degree_baseline, + nx.centrality.degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/hubspoke_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/Hub_Spoke.json", + run_degree_baseline, + nx.centrality.degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/tree_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/Tree.json", + run_degree_baseline, + nx.centrality.degree_centrality, + ), + # in_degree + ( + f"{data_path_root}/unweighted_edges/line_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/in_degree/Line_Directed.json", + run_degree_baseline, + nx.centrality.in_degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/ring_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/in_degree/Ring_Directed.json", + run_degree_baseline, + nx.centrality.in_degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/hubspoke_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/in_degree/Hub_Spoke_Directed.json", + run_degree_baseline, + nx.centrality.in_degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/tree_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/in_degree/Tree_Directed.json", + run_degree_baseline, + nx.centrality.in_degree_centrality, + ), + # out_degree + ( + f"{data_path_root}/unweighted_edges/line_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/out_degree/Line_Directed.json", + run_degree_baseline, + nx.centrality.out_degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/ring_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/out_degree/Ring_Directed.json", + run_degree_baseline, + nx.centrality.out_degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/hubspoke_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/out_degree/Hub_Spoke_Directed.json", + run_degree_baseline, + nx.centrality.out_degree_centrality, + ), + ( + f"{data_path_root}/unweighted_edges/tree_edges.csv", + f"{baseline_path_root}/centrality/degree_centrality/out_degree/Tree_Directed.json", + run_degree_baseline, + nx.centrality.out_degree_centrality, + ), + # weighted + ( + f"{data_path_root}/weighted_edges/complete_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/Complete_Weighted.json", + run_degree_baseline, + partial(weighted_deg_cent), + ), + ( + f"{data_path_root}/weighted_edges/line_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/Line_Weighted.json", + run_degree_baseline, + weighted_deg_cent, + ), + ( + f"{data_path_root}/weighted_edges/ring_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/Ring_Weighted.json", + run_degree_baseline, + weighted_deg_cent, + ), + ( + f"{data_path_root}/weighted_edges/hubspoke_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/Hub_Spoke_Weighted.json", + run_degree_baseline, + weighted_deg_cent, + ), + ( + f"{data_path_root}/weighted_edges/tree_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/Tree_Weighted.json", + run_degree_baseline, + weighted_deg_cent, + ), + # in_degree + ( + f"{data_path_root}/weighted_edges/complete_edges_directed.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Complete_Directed_Weighted.json", + run_degree_baseline, + partial(weighted_deg_cent, dir="in"), + ), + ( + f"{data_path_root}/weighted_edges/line_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Line_Directed_Weighted.json", + run_degree_baseline, + partial(weighted_deg_cent, dir="in"), + ), + ( + f"{data_path_root}/weighted_edges/ring_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Ring_Directed_Weighted.json", + run_degree_baseline, + partial(weighted_deg_cent, dir="in"), + ), + ( + f"{data_path_root}/weighted_edges/hubspoke_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Hub_Spoke_Directed_Weighted.json", + run_degree_baseline, + partial(weighted_deg_cent, dir="in"), + ), + ( + f"{data_path_root}/weighted_edges/tree_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/in_degree/Tree_Directed_Weighted.json", + run_degree_baseline, + partial(weighted_deg_cent, dir="in"), + ), + # out_degree + ( + f"{data_path_root}/weighted_edges/complete_edges_directed.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Complete_Directed_Weighted.json", + run_degree_baseline, + partial(weighted_deg_cent, dir="out"), + ), + ( + f"{data_path_root}/weighted_edges/line_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Line_Directed_Weighted.json", + run_degree_baseline, + partial(weighted_deg_cent, dir="out"), + ), + ( + f"{data_path_root}/weighted_edges/ring_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Ring_Directed_Weighted.json", + run_degree_baseline, + partial(weighted_deg_cent, dir="out"), + ), + ( + f"{data_path_root}/weighted_edges/hubspoke_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Hub_Spoke_Directed_Weighted.json", + run_degree_baseline, + partial(weighted_deg_cent, dir="out"), + ), + ( + f"{data_path_root}/weighted_edges/tree_edges.csv", + f"{baseline_path_root}/centrality/weighted_degree_centrality/out_degree/Tree_Directed_Weighted.json", + run_degree_baseline, + partial(weighted_deg_cent, dir="out"), + ), + ] + create_degree_baseline(paths) diff --git a/tests/test/baseline/fast_rp_baseline.py b/tests/test/baseline/fast_rp_baseline.py new file mode 100644 index 00000000..1b2ff5a4 --- /dev/null +++ b/tests/test/baseline/fast_rp_baseline.py @@ -0,0 +1,44 @@ +import gzip +import json + +import networkx as nx +import numpy as np +import pandas as pd +from algos import fastrp +from dotenv import load_dotenv +from pyTigerGraph.datasets import Datasets + +load_dotenv() +data_path_root = "data" +baseline_path_root = f"{data_path_root}/baseline" + + +def run(ds_name="Cora"): + dataset = Datasets(ds_name) + edges = pd.read_csv(dataset.tmp_dir + f"/{ds_name}/edges.csv", header=None) + edges.columns = ["src", "tgt"] + + g = nx.Graph() + g.add_edges_from(edges.to_numpy()) + node_ids = sorted(list(g.nodes)) + A = nx.adjacency_matrix(g, nodelist=node_ids) + conf = { + "weights": [1, 2, 4], + "dim": 8, + # "projection_method": "sparse", + "projection_method": "gaussian", + "input_matrix": "trans", + "alpha": -0.628, + "normalization": False, + } + + vecs = fastrp(A, conf) + + assert len(vecs) == len(node_ids) + + res = {str(k): list(v) for k, v in zip(node_ids, vecs)} + with gzip.open(f"{baseline_path_root}/ml/fastRP.json.gz", "wb") as f: + f.write(json.dumps(res).encode()) + + with gzip.open(f"{baseline_path_root}/ml/fastRP.json.gz", "rb") as f: + d = json.load(f) diff --git a/tests/test/test_ml.py b/tests/test/test_ml.py new file mode 100644 index 00000000..27648c27 --- /dev/null +++ b/tests/test/test_ml.py @@ -0,0 +1,64 @@ +import gzip +import json +import os + +import numpy as np +import pytest +from dotenv import load_dotenv +from pyTigerGraph import TigerGraphConnection + +load_dotenv() +data_path_root = "data" +baseline_path_root = f"{data_path_root}/baseline" + + +conn = TigerGraphConnection( + host=os.environ["HOST_NAME"], + graphname="Cora", + username=os.environ["USER_NAME"], + password=os.environ["PASS"], +) +conn.getToken() + + +def cos_sim(x, y): + x_mag = np.linalg.norm(x) + y_mag = np.linalg.norm(y) + + return np.dot(x, y) / (x_mag * y_mag) + + +class TestML: + def test_fastRP(self): + params = { + "v_type_set": ["Paper"], + "e_type_set": ["Cite", "reverse_Cite"], + "output_v_type_set": ["Paper"], + "iteration_weights": "1,2,4", + "beta": -0.1, + "embedding_dimension": 8, + "embedding_dim_map": [], + "default_length": 8, + "sampling_constant": 3, + "random_seed": 42, + "print_results": True, + } + + with gzip.open(f"{baseline_path_root}/ml/fastRP.json.gz", "rb") as f: + baseline = json.load(f) + + result = conn.runInstalledQuery( + "tg_fastRP", + params=params, + usePost=True, + ) + result = { + v["v_id"]: v["attributes"]["res.@final_embedding_list"] + for v in result[1]["res"] + } + threshold = 0.5 + for bk, bv in baseline.items(): + v = result[bk] + sim = abs(cos_sim(v, bv)) + if sim < threshold: + pytest.fail(f"cos-sim of {bk} is {sim} (< threshold of {threshold}") From 00506fc4c5b6e24ad05d211ab294e16003f8077c Mon Sep 17 00:00:00 2001 From: RobRossmiller-TG <165701656+RobRossmiller-TG@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:53:42 -0400 Subject: [PATCH 23/38] use featurizer --- tests/data/baseline/ml/fastRP.json.gz | Bin 209385 -> 209385 bytes tests/run.sh | 5 ++--- tests/test/baseline/create_baselines.py | 3 +-- tests/test/test_ml.py | 20 ++++++-------------- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/tests/data/baseline/ml/fastRP.json.gz b/tests/data/baseline/ml/fastRP.json.gz index 31ab1974eb5e9b1689ef01d639ea3b6f183fee4a..2fa29708fc02b32f7ac3819f0068df35f61344a0 100644 GIT binary patch delta 26 icmaF)nCInV9ya-I4i4F=jcl!KjIC@;TiKW&8v+1^xCoU1 delta 26 icmaF)nCInV9ya-I4i2wLjcl!KjIC@;TiKW&8v+1{F$li^ diff --git a/tests/run.sh b/tests/run.sh index 801936b8..be492a1e 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -1,6 +1,5 @@ clear -python3 test/setup.py && +# python3 test/setup.py && python3 test/baseline/create_baselines.py && - pytest test/test_centrality.py && + # pytest test/test_centrality.py test/test_ml.py pytest test/test_ml.py -# pytest diff --git a/tests/test/baseline/create_baselines.py b/tests/test/baseline/create_baselines.py index 76fa0b0f..b2e0a946 100644 --- a/tests/test/baseline/create_baselines.py +++ b/tests/test/baseline/create_baselines.py @@ -2,6 +2,5 @@ import fast_rp_baseline if __name__ == "__main__": - # degree_cent_baseline.run() - + degree_cent_baseline.run() fast_rp_baseline.run() diff --git a/tests/test/test_ml.py b/tests/test/test_ml.py index 27648c27..00cfc858 100644 --- a/tests/test/test_ml.py +++ b/tests/test/test_ml.py @@ -1,26 +1,17 @@ import gzip import json -import os import numpy as np import pytest from dotenv import load_dotenv -from pyTigerGraph import TigerGraphConnection + +from util import get_featurizer load_dotenv() data_path_root = "data" baseline_path_root = f"{data_path_root}/baseline" -conn = TigerGraphConnection( - host=os.environ["HOST_NAME"], - graphname="Cora", - username=os.environ["USER_NAME"], - password=os.environ["PASS"], -) -conn.getToken() - - def cos_sim(x, y): x_mag = np.linalg.norm(x) y_mag = np.linalg.norm(y) @@ -29,6 +20,8 @@ def cos_sim(x, y): class TestML: + feat = get_featurizer("Cora") + def test_fastRP(self): params = { "v_type_set": ["Paper"], @@ -47,10 +40,9 @@ def test_fastRP(self): with gzip.open(f"{baseline_path_root}/ml/fastRP.json.gz", "rb") as f: baseline = json.load(f) - result = conn.runInstalledQuery( + result = self.feat.runAlgorithm( "tg_fastRP", params=params, - usePost=True, ) result = { v["v_id"]: v["attributes"]["res.@final_embedding_list"] @@ -61,4 +53,4 @@ def test_fastRP(self): v = result[bk] sim = abs(cos_sim(v, bv)) if sim < threshold: - pytest.fail(f"cos-sim of {bk} is {sim} (< threshold of {threshold}") + pytest.fail(f"cos-sim of ID: {bk} is {sim} (< threshold of {threshold}") From 5646e0013abe7401889c4288578e81f88a879615 Mon Sep 17 00:00:00 2001 From: RobRossmiller-TG <165701656+RobRossmiller-TG@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:54:35 -0400 Subject: [PATCH 24/38] run.sh --- tests/run.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/run.sh b/tests/run.sh index be492a1e..e05cb3cd 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -1,5 +1,4 @@ clear -# python3 test/setup.py && +python3 test/setup.py && python3 test/baseline/create_baselines.py && - # pytest test/test_centrality.py test/test_ml.py - pytest test/test_ml.py + pytest test/test_centrality.py test/test_ml.py From e52b383be682373bbdd0ffd2c49e1e97eb71b567 Mon Sep 17 00:00:00 2001 From: "xunalei.lin" Date: Mon, 15 Jul 2024 14:17:32 +0000 Subject: [PATCH 25/38] [ALGOS-266] feat(algos): Rewrite the Label Propagation algorithm --- GDBMS_ALGO/community/label_prop.gsql | 232 ++++++++++++++++++--------- 1 file changed, 159 insertions(+), 73 deletions(-) diff --git a/GDBMS_ALGO/community/label_prop.gsql b/GDBMS_ALGO/community/label_prop.gsql index aa4c727d..fd405523 100644 --- a/GDBMS_ALGO/community/label_prop.gsql +++ b/GDBMS_ALGO/community/label_prop.gsql @@ -1,14 +1,21 @@ -CREATE TEMPLATE QUERY GDBMS_ALGO.community.label_prop (SET v_type_set, SET e_type_set, INT maximum_iteration, INT print_limit, - BOOL print_results = TRUE, STRING file_path = "", STRING result_attribute = "") SYNTAX V1 { - +CREATE TEMPLATE QUERY GDBMS_ALGO.community.label_prop( + SET v_type_set, + SET e_type_set, + UINT maximum_iteration = 10, + UINT sample_edge_num = 1000, + UINT batch_num = 12, + INT print_limit, + BOOL print_results = TRUE, + STRING file_path="", + STRING result_attribute = "" +) FOR GRAPH MyGraph SYNTAX V1 { /* - First Author: - First Commit Date: - - Recent Author: - Recent Commit Date: + First Author: xuanlei.lin@tigergraph.com + First Commit Date: 2024-07-15 + Recent Author: xuanlei.lin@tigergraph.com + Recent Commit Date: 2024-07-15 Repository: https://github.com/tigergraph/gsql-graph-algorithms/tree/master/algorithms/Community @@ -17,89 +24,168 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.community.label_prop (SET v_type_set, S Production Description: - Partition the vertices into communities, according to the Label Propagation method. - Indicate community membership by assigning each vertex a community ID. - - Publications: - NA + This query partitions vertices into communities using the Label Propagation method. + It assigns a community ID to each vertex based on its neighbors' community IDs. TigerGraph Documentation: https://docs.tigergraph.com/graph-ml/current/community-algorithms/label-propagation Parameters: v_type_set: - Names of vertex types to use + The set of vertex types to traverse. e_type_set: - Names of edge types to use + The set of edge types to traverse. maximum_iteration: - Number of maximum iteration of the algorithm + The maximum number of iterations for the algorithm. + sample_edge_num: + The number of edges to sample for super nodes. + batch_num: + The number of batches. Using batches reduces memory consumption. print_limit: - If >=0, max number of vertices to output to JSON. + If >= 0, the maximum number of vertices to output to JSON. print_results: - If True, output JSON to standard output + If True, output JSON to standard output. WARNING: Avoid printing results for large datasets. result_attribute: - If not empty, store community id values (INT) to this attribute + If not empty, store community ID values (INT) in this attribute. file_path: - If not empty, write output to this file. + File to write CSV output to. */ -OrAccum @@or_changed = true; -MapAccum @map; # -MapAccum @@comm_sizes_map; # -SumAccum @sum_label, @sum_num; -FILE f (file_path); -Start = {v_type_set}; + TYPEDEF TUPLE MoveScore; + MinAccum @community_id; // Community ID of the node + SumAccum @vid; // Vertex's internal ID + SumAccum @batch_id; // Batch ID for the node + SumAccum @degree; // Outdegree of the node + SumAccum @@vertex_num; // Total number of vertices + MapAccum> @community_k_in_map; // Number of neighbors belonging to each community + MaxAccum @best_move; // Best move for the node with the highest score + MaxAccum @@min_double; // Used to reset the @best_move + OrAccum @to_change_community; // Flag to check if the node needs to change community + MapAccum @@comm_sizes_map; // Map: community ID -> size of the community + FILE f(file_path); // File to write results to -# Assign unique labels to each vertex -Start = SELECT s - FROM Start:s - ACCUM s.@sum_label = getvid(s); + // Initialization + All_Nodes = {v_type_set}; + Tmp = + SELECT s + FROM All_Nodes:s -(e_type_set:e)- :t + POST-ACCUM + s.@community_id = s, + s.@vid = getvid(s), + s.@batch_id = s.@vid % batch_num, + s.@degree = s.outdegree(e_type_set); + @@vertex_num = All_Nodes.size(); + @@vertex_num = @@vertex_num / batch_num; -# Propagate labels to neighbors until labels converge or the max iterations is reached -WHILE @@or_changed == true LIMIT maximum_iteration DO - @@or_changed = false; - Start = SELECT s - FROM Start:s -(e_type_set:e)- :t - ACCUM t.@map += (s.@sum_label -> 1) # count the occurrences of neighbor's labels + // Label propagation + INT hop = 0; + Candidates = All_Nodes; + WHILE Candidates.size() > 0 AND hop < maximum_iteration DO + hop = hop + 1; + // Find the best move + IF hop == 1 THEN // First iteration + ChangedNodes = + SELECT s + FROM Candidates:s -(e_type_set:e)- :t + WHERE s.@degree < t.@degree + ACCUM s.@best_move += MoveScore(t.@degree, t.@community_id) + POST-ACCUM + IF s.@best_move.community != s.@community_id THEN + s.@to_change_community = TRUE + END + HAVING s.@to_change_community == TRUE; + ELSE // Remaining iterations + IF Candidates.size() < @@vertex_num OR batch_num == 1 THEN // No batch processing + ChangedNodes = + SELECT s + FROM Candidates:s -(e_type_set:e)- :t + SAMPLE sample_edge_num EDGE WHEN s.outdegree(e_type_set) > sample_edge_num + ACCUM s.@community_k_in_map += (t.@community_id -> 1) + POST-ACCUM + s.@best_move = MoveScore(@@min_double, s), // Reset best move + FOREACH (community_id, k_in) IN s.@community_k_in_map DO + s.@best_move += MoveScore(k_in, community_id) + END, + IF s.@best_move.community != s.@community_id THEN + s.@to_change_community = TRUE + END, + s.@community_k_in_map.clear() + HAVING s.@to_change_community == TRUE; + ELSE // Use batch processing + ChangedNodes = {}; + FOREACH batch_id IN RANGE[0, batch_num-1] DO + Nodes = + SELECT s + FROM Candidates:s + WHERE s.@batch_id == batch_id; + Nodes = + SELECT s + FROM Nodes:s -(e_type_set:e)- :t + SAMPLE sample_edge_num EDGE WHEN s.outdegree(e_type_set) > sample_edge_num + ACCUM s.@community_k_in_map += (t.@community_id -> 1) POST-ACCUM - INT max_v = 0, - INT label = 0, - # Iterate over the map to get the neighbor label that occurs most often - FOREACH (k,v) IN t.@map DO - CASE WHEN v > max_v THEN - max_v = v, - label = k - END - END, - # When the neighbor search finds a label AND it is a new label - # AND the label's count has increased, update the label. - CASE WHEN label != 0 AND t.@sum_label != label AND max_v > t.@sum_num THEN - @@or_changed += true, - t.@sum_label = label, - t.@sum_num = max_v - END, - t.@map.clear(); -END; + s.@best_move = MoveScore(@@min_double, s), // Reset best move + FOREACH (community_id, k_in) IN s.@community_k_in_map DO + s.@best_move += MoveScore(k_in, community_id) + END, + IF s.@best_move.community != s.@community_id THEN + s.@to_change_community = TRUE + END, + s.@community_k_in_map.clear() + HAVING s.@to_change_community == TRUE; + ChangedNodes = ChangedNodes UNION Nodes; + END; + END; + END; + + // Handle nodes that swap communities + SwapNodes = + SELECT s + FROM ChangedNodes:s -(e_type_set:e)- :t + WHERE s.@best_move.community == t.@community_id + AND t.@to_change_community == TRUE + AND t.@best_move.community == s.@community_id + AND (s.@best_move.score < t.@best_move.score + OR (abs(s.@best_move.score - t.@best_move.score) < 0.00000000001 + AND s.@vid > t.@vid)) + POST-ACCUM + s.@to_change_community = FALSE; + ChangedNodes = ChangedNodes MINUS SwapNodes; + + // Update community IDs + ChangedNodes = + SELECT s + FROM ChangedNodes:s + POST-ACCUM + s.@community_id = s.@best_move.community, + s.@to_change_community = FALSE; + + // Find candidates for the next iteration + Candidates = + SELECT t + FROM ChangedNodes:s -(e_type_set:e)- :t + WHERE t.@community_id != s.@community_id; + END; -Start = {v_type_set}; -Start = SELECT s - FROM Start:s - POST-ACCUM - IF result_attribute != "" THEN - s.setAttr(result_attribute, s.@sum_label) - END, - - IF file_path != "" THEN - f.println(s, s.@sum_label) - END, - - IF print_results THEN - @@comm_sizes_map += (s.@sum_label -> 1) - END - LIMIT print_limit; + // Output results + Nodes = + SELECT s + FROM All_Nodes:s + POST-ACCUM + IF result_attribute != "" THEN + s.setAttr(result_attribute, getvid(s.@community_id)) + END, + IF print_results THEN + @@comm_sizes_map += (s.@community_id -> 1) + END, + IF file_path != "" THEN + f.println(s.id, s.@community_id) + END + LIMIT print_limit; -IF print_results THEN + // Print results if print_results is True + IF print_results THEN PRINT @@comm_sizes_map; - PRINT Start[Start.@sum_label]; -END; + PRINT Nodes[Nodes.@community_id]; + END; } From bbcf2dc9fa0056b5d922a97bd0c7e6edb2e35761 Mon Sep 17 00:00:00 2001 From: "xunalei.lin" Date: Mon, 15 Jul 2024 14:23:56 +0000 Subject: [PATCH 26/38] [ALGOS-266] feat(algos): Adjust the code indentation to comply with the formatting standards. --- GDBMS_ALGO/community/label_prop.gsql | 165 +++++++++++++-------------- 1 file changed, 78 insertions(+), 87 deletions(-) diff --git a/GDBMS_ALGO/community/label_prop.gsql b/GDBMS_ALGO/community/label_prop.gsql index fd405523..73898934 100644 --- a/GDBMS_ALGO/community/label_prop.gsql +++ b/GDBMS_ALGO/community/label_prop.gsql @@ -66,14 +66,13 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.community.label_prop( // Initialization All_Nodes = {v_type_set}; - Tmp = - SELECT s - FROM All_Nodes:s -(e_type_set:e)- :t - POST-ACCUM - s.@community_id = s, - s.@vid = getvid(s), - s.@batch_id = s.@vid % batch_num, - s.@degree = s.outdegree(e_type_set); + Tmp = SELECT s + FROM All_Nodes:s -(e_type_set:e)- :t + POST-ACCUM + s.@community_id = s, + s.@vid = getvid(s), + s.@batch_id = s.@vid % batch_num, + s.@degree = s.outdegree(e_type_set); @@vertex_num = All_Nodes.size(); @@vertex_num = @@vertex_num / batch_num; @@ -84,104 +83,96 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.community.label_prop( hop = hop + 1; // Find the best move IF hop == 1 THEN // First iteration - ChangedNodes = - SELECT s - FROM Candidates:s -(e_type_set:e)- :t - WHERE s.@degree < t.@degree - ACCUM s.@best_move += MoveScore(t.@degree, t.@community_id) - POST-ACCUM - IF s.@best_move.community != s.@community_id THEN - s.@to_change_community = TRUE - END - HAVING s.@to_change_community == TRUE; + ChangedNodes = SELECT s + FROM Candidates:s -(e_type_set:e)- :t + WHERE s.@degree < t.@degree + ACCUM s.@best_move += MoveScore(t.@degree, t.@community_id) + POST-ACCUM + IF s.@best_move.community != s.@community_id THEN + s.@to_change_community = TRUE + END + HAVING s.@to_change_community == TRUE; ELSE // Remaining iterations IF Candidates.size() < @@vertex_num OR batch_num == 1 THEN // No batch processing - ChangedNodes = - SELECT s - FROM Candidates:s -(e_type_set:e)- :t - SAMPLE sample_edge_num EDGE WHEN s.outdegree(e_type_set) > sample_edge_num - ACCUM s.@community_k_in_map += (t.@community_id -> 1) - POST-ACCUM - s.@best_move = MoveScore(@@min_double, s), // Reset best move - FOREACH (community_id, k_in) IN s.@community_k_in_map DO - s.@best_move += MoveScore(k_in, community_id) - END, - IF s.@best_move.community != s.@community_id THEN - s.@to_change_community = TRUE - END, - s.@community_k_in_map.clear() - HAVING s.@to_change_community == TRUE; + ChangedNodes = SELECT s + FROM Candidates:s -(e_type_set:e)- :t + SAMPLE sample_edge_num EDGE WHEN s.outdegree(e_type_set) > sample_edge_num + ACCUM s.@community_k_in_map += (t.@community_id -> 1) + POST-ACCUM + s.@best_move = MoveScore(@@min_double, s), // Reset best move + FOREACH (community_id, k_in) IN s.@community_k_in_map DO + s.@best_move += MoveScore(k_in, community_id) + END, + IF s.@best_move.community != s.@community_id THEN + s.@to_change_community = TRUE + END, + s.@community_k_in_map.clear() + HAVING s.@to_change_community == TRUE; ELSE // Use batch processing ChangedNodes = {}; FOREACH batch_id IN RANGE[0, batch_num-1] DO - Nodes = - SELECT s - FROM Candidates:s - WHERE s.@batch_id == batch_id; - Nodes = - SELECT s - FROM Nodes:s -(e_type_set:e)- :t - SAMPLE sample_edge_num EDGE WHEN s.outdegree(e_type_set) > sample_edge_num - ACCUM s.@community_k_in_map += (t.@community_id -> 1) - POST-ACCUM - s.@best_move = MoveScore(@@min_double, s), // Reset best move - FOREACH (community_id, k_in) IN s.@community_k_in_map DO - s.@best_move += MoveScore(k_in, community_id) - END, - IF s.@best_move.community != s.@community_id THEN - s.@to_change_community = TRUE - END, - s.@community_k_in_map.clear() - HAVING s.@to_change_community == TRUE; + Nodes = SELECT s + FROM Candidates:s + WHERE s.@batch_id == batch_id; + Nodes = SELECT s + FROM Nodes:s -(e_type_set:e)- :t + SAMPLE sample_edge_num EDGE WHEN s.outdegree(e_type_set) > sample_edge_num + ACCUM s.@community_k_in_map += (t.@community_id -> 1) + POST-ACCUM + s.@best_move = MoveScore(@@min_double, s), // Reset best move + FOREACH (community_id, k_in) IN s.@community_k_in_map DO + s.@best_move += MoveScore(k_in, community_id) + END, + IF s.@best_move.community != s.@community_id THEN + s.@to_change_community = TRUE + END, + s.@community_k_in_map.clear() + HAVING s.@to_change_community == TRUE; ChangedNodes = ChangedNodes UNION Nodes; END; END; END; // Handle nodes that swap communities - SwapNodes = - SELECT s - FROM ChangedNodes:s -(e_type_set:e)- :t - WHERE s.@best_move.community == t.@community_id - AND t.@to_change_community == TRUE - AND t.@best_move.community == s.@community_id - AND (s.@best_move.score < t.@best_move.score - OR (abs(s.@best_move.score - t.@best_move.score) < 0.00000000001 - AND s.@vid > t.@vid)) - POST-ACCUM - s.@to_change_community = FALSE; + SwapNodes = SELECT s + FROM ChangedNodes:s -(e_type_set:e)- :t + WHERE s.@best_move.community == t.@community_id + AND t.@to_change_community == TRUE + AND t.@best_move.community == s.@community_id + AND (s.@best_move.score < t.@best_move.score + OR (abs(s.@best_move.score - t.@best_move.score) < 0.00000000001 + AND s.@vid > t.@vid)) + POST-ACCUM + s.@to_change_community = FALSE; ChangedNodes = ChangedNodes MINUS SwapNodes; // Update community IDs - ChangedNodes = - SELECT s - FROM ChangedNodes:s - POST-ACCUM - s.@community_id = s.@best_move.community, - s.@to_change_community = FALSE; + ChangedNodes = SELECT s + FROM ChangedNodes:s + POST-ACCUM + s.@community_id = s.@best_move.community, + s.@to_change_community = FALSE; // Find candidates for the next iteration - Candidates = - SELECT t - FROM ChangedNodes:s -(e_type_set:e)- :t - WHERE t.@community_id != s.@community_id; + Candidates = SELECT t + FROM ChangedNodes:s -(e_type_set:e)- :t + WHERE t.@community_id != s.@community_id; END; // Output results - Nodes = - SELECT s - FROM All_Nodes:s - POST-ACCUM - IF result_attribute != "" THEN - s.setAttr(result_attribute, getvid(s.@community_id)) - END, - IF print_results THEN - @@comm_sizes_map += (s.@community_id -> 1) - END, - IF file_path != "" THEN - f.println(s.id, s.@community_id) - END - LIMIT print_limit; + Nodes = SELECT s + FROM All_Nodes:s + POST-ACCUM + IF result_attribute != "" THEN + s.setAttr(result_attribute, getvid(s.@community_id)) + END, + IF print_results THEN + @@comm_sizes_map += (s.@community_id -> 1) + END, + IF file_path != "" THEN + f.println(s.id, s.@community_id) + END + LIMIT print_limit; // Print results if print_results is True IF print_results THEN From d5c1d9df1d6ebadafbc75e2b099c9104f07fbee7 Mon Sep 17 00:00:00 2001 From: "xunalei.lin" Date: Tue, 16 Jul 2024 08:32:33 +0000 Subject: [PATCH 27/38] [ALGOS-266] feat(algos): print to file with vertex type if there're multiple vertex types. --- GDBMS_ALGO/community/label_prop.gsql | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/GDBMS_ALGO/community/label_prop.gsql b/GDBMS_ALGO/community/label_prop.gsql index 73898934..95d6ef71 100644 --- a/GDBMS_ALGO/community/label_prop.gsql +++ b/GDBMS_ALGO/community/label_prop.gsql @@ -15,7 +15,7 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.community.label_prop( First Commit Date: 2024-07-15 Recent Author: xuanlei.lin@tigergraph.com - Recent Commit Date: 2024-07-15 + Recent Commit Date: 2024-07-16 Repository: https://github.com/tigergraph/gsql-graph-algorithms/tree/master/algorithms/Community @@ -170,7 +170,12 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.community.label_prop( @@comm_sizes_map += (s.@community_id -> 1) END, IF file_path != "" THEN - f.println(s.id, s.@community_id) + IF v_type_set.size() == 1 THEN + f.println(s.id, s.@community_id) + ELSE + VERTEX node = s.@community_id, + f.println(s.type, s, node.type, node) + END END LIMIT print_limit; From 76fcb43c538fc183a960db4047175a2ebec25e22 Mon Sep 17 00:00:00 2001 From: "xunalei.lin" Date: Tue, 16 Jul 2024 08:55:42 +0000 Subject: [PATCH 28/38] [ALGOS-266] feat(algos): switch the order of parameter result_attribute and file_path. --- GDBMS_ALGO/community/label_prop.gsql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GDBMS_ALGO/community/label_prop.gsql b/GDBMS_ALGO/community/label_prop.gsql index 95d6ef71..7e243325 100644 --- a/GDBMS_ALGO/community/label_prop.gsql +++ b/GDBMS_ALGO/community/label_prop.gsql @@ -6,8 +6,8 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.community.label_prop( UINT batch_num = 12, INT print_limit, BOOL print_results = TRUE, - STRING file_path="", - STRING result_attribute = "" + STRING result_attribute = "", + STRING file_path="" ) FOR GRAPH MyGraph SYNTAX V1 { /* From 6e3ac7e25950b1bd9acd587218b21048d9510887 Mon Sep 17 00:00:00 2001 From: "xunalei.lin" Date: Tue, 16 Jul 2024 10:32:22 +0000 Subject: [PATCH 29/38] [ALGOS-267] feat(algos): Improve the Louvain algorithm --- GDBMS_ALGO/community/louvain.gsql | 546 +++++++++++++++++++----------- 1 file changed, 343 insertions(+), 203 deletions(-) diff --git a/GDBMS_ALGO/community/louvain.gsql b/GDBMS_ALGO/community/louvain.gsql index f7c13e32..41050d90 100644 --- a/GDBMS_ALGO/community/louvain.gsql +++ b/GDBMS_ALGO/community/louvain.gsql @@ -1,13 +1,20 @@ -CREATE TEMPLATE QUERY GDBMS_ALGO.community.louvain(SET v_type_set, SET e_type_set, STRING weight_attribute = "weight", INT maximum_iteration = 10, - STRING result_attribute = "cid", STRING file_path = "", BOOL print_stats = FALSE) SYNTAX V1 { +CREATE TEMPLATE QUERY GDBMS_ALGO.community.louvain( + SET v_type_set, + SET e_type_set, + STRING weight_attribute = "weight", + UINT total_passes_count = 3, + UINT maximum_iteration = 10, + UINT total_batch_count = 12, + STRING result_attribute = "", + STRING file_path = "" +) SYNTAX V2 { /* - First Author: - First Commit Date: - - Recent Author: - Recent Commit Date: + First Author: xuanlei.lin@tigergraph.com + First Commit Date: 2024-07-16 + Recent Author: xuanlei.lin@tigergraph.com + Recent Commit Date: 2024-07-16 Repository: https://github.com/tigergraph/gsql-graph-algorithms/tree/master/algorithms/Community @@ -16,231 +23,364 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.community.louvain(SET v_type_set, SET move; - SumAccum @sum_ac; #sum of the degrees of all the vertices in community C of the vertex - ListAccum @cc_list; #the community center - SumAccum @sum_weight; # total weight incident to this vertex - SumAccum @sum_cc_weight; # total weight incident to the cc vertex - MapAccum> @A_map; #A[c]: sum of the edge weights for the edges in community c - MaxAccum @max_best_move; # highest dQ, highest -Outdegree, highest cc - ListAccum @cm_list; #community member list - SumAccum @@sum_m; # total edge weight - SumAccum @sum_outdegree; # helper variable for outdegree calculation - SumAccum @@sum_cc_change; - MapAccum> @@community_map; - MapAccum> @@community_size_count; + TYPEDEF TUPLE MyTuple; + SumAccum @@m; // The sum of the weights of all the links in the network. + MinAccum @community_id; // The community ID of the node. + SumAccum @k; // The sum of the weights of the links incident to the node. + SumAccum @k_in; // The sum of the weights of the links inside the previous community of the node. + SumAccum @k_self_loop; // The weight of the self-loop link. + MapAccum> @community_k_in_map; // Community of the neighbors of the nodes -> Sum of the weights of the links inside the community. + MapAccum> @@community_sum_total_map; // Community ID C -> Sum of the weights of the links incident to nodes in C. + SumAccum @community_sum_total; // Sum of the weights of the links incident to nodes in the community of the node. + MapAccum> @@community_sum_in_map; // Community ID -> Sum of the weights of the links inside the community. + MapAccum>> @@source_target_k_in_map; // Source community ID -> (Target community ID -> Sum of the weights of the links from the source community to the target community). + SumAccum @delta_Q_remove; // Delta Q to remove the node from the previous community. + MaxAccum @best_move; // Best move of the node with the highest delta Q to move the isolated node into the new community. + MaxAccum @@min_double; // Used to reset the @best_move. + OrAccum @to_change_community; + SumAccum @batch_id; + SumAccum @vid; FILE f(file_path); - // initialize - Start = {v_type_set}; - Start = SELECT s - FROM Start:s -(e_type_set:e)- :t - ACCUM - @@sum_m += e.getAttr(weight_attribute, "FLOAT")*0.5, - s.@sum_weight += e.getAttr(weight_attribute, "FLOAT")*1.0, - s.@sum_cc_weight += e.getAttr(weight_attribute, "FLOAT")*1.0, - s.@sum_outdegree += 1 - // mark @cc only for vertices with more than 1 neighbors - // and only the marked vertices will participate in the actual louvain algorithm - // the unmorked vertices will be resolved by the vertex following heuristic - POST-ACCUM - IF s.@sum_outdegree > 1 THEN - s.@cc_list += s - END; - IF print_stats THEN - PRINT Start.size() AS AllVertexCount; - END; + // Virtual edges + CREATE DIRECTED VIRTUAL EDGE belongs_to (FROM *, TO *, layer_set SET); + CREATE UNDIRECTED VIRTUAL EDGE links_to (FROM *, TO *, layer_weight_map MAP); - // special @cc update in the first iteration - Start = SELECT t - FROM Start:s -(e_type_set:e)- :t - WHERE s.@sum_outdegree > 1 AND t.@sum_outdegree > 1 - ACCUM - t.@max_best_move += move(e.getAttr(weight_attribute, "FLOAT")*1.0 + @@sum_m*t.@sum_weight * - (t.@sum_weight - s.@sum_weight), -s.@sum_cc_weight, s.@cc_list.get(0)) - POST-ACCUM - IF t.@max_best_move.deltaQ > 0 THEN - IF -t.@max_best_move.weight < t.@sum_cc_weight THEN - t.@cc_list.clear(), - t.@cc_list += t.@max_best_move.cc, - t.@sum_cc_weight = -t.@max_best_move.weight, - @@sum_cc_change += 1 - ELSE - IF -t.@max_best_move.weight == t.@sum_cc_weight AND getvid(t) < getvid(t.@max_best_move.cc) THEN - t.@cc_list.clear(), - t.@cc_list += t.@max_best_move.cc, - t.@sum_cc_weight = -t.@max_best_move.weight, - @@sum_cc_change += 1 + // -------------------- 1. First pass -------------------- + // Initialization + All_Nodes = {v_type_set}; + Pass_Nodes = SELECT s + FROM All_Nodes:s -(e_type_set:e)- :t + ACCUM @@m += e.getAttr(weight_attribute, "DOUBLE") / 2, + s.@k += e.getAttr(weight_attribute, "DOUBLE"), + IF s == t THEN // Self-loop link + s.@k_self_loop += e.getAttr(weight_attribute, "DOUBLE") END - END - END; - IF print_stats THEN - PRINT @@sum_cc_change AS InitChangeCount; + POST-ACCUM + s.@community_id = s, + s.@vid = getvid(s), + s.@batch_id = s.@vid % total_batch_count; + IF @@m < 0.00000000001 THEN + RETURN; END; - // main loop - WHILE @@sum_cc_change > 0 LIMIT maximum_iteration DO - // initialize for iteration - @@sum_cc_change = 0; - Start = SELECT s - FROM Start:s - WHERE s.@sum_outdegree > 1 + // Local moving + INT hop = 0; + Candidates (ANY) = Pass_Nodes; + WHILE Candidates.size() > 0 AND hop < maximum_iteration DO + hop = hop + 1; + IF hop == 1 THEN // First iteration + ChangedNodes = SELECT s + FROM Candidates:s -(e_type_set:e)- :t + WHERE s.@community_id != t.@community_id + ACCUM s.@best_move += MyTuple(1 - s.@k * t.@k / (2 * @@m), t.@community_id) + POST-ACCUM + IF s.@best_move.delta_Q_add > 0 THEN // The gain (delta Q) is positive + s.@to_change_community = TRUE + END + HAVING s.@to_change_community == TRUE; + ELSE // Remaining iterations + // Calculate sum_total + Tmp = SELECT s + FROM Pass_Nodes:s + POST-ACCUM + @@community_sum_total_map += (s.@community_id -> s.@k); + Tmp = SELECT s + FROM Pass_Nodes:s + POST-ACCUM + s.@community_sum_total = @@community_sum_total_map.get(s.@community_id); + @@community_sum_total_map.clear(); + // Find the best move + ChangedNodes = {}; + FOREACH batch_id IN RANGE[0, total_batch_count-1] DO + // Calculate the delta Q to remove the node from the previous community + Nodes = SELECT s + FROM Candidates:s -(e_type_set:e)- :t + WHERE s.@batch_id == batch_id + ACCUM IF s.@community_id == t.@community_id THEN + s.@k_in += e.getAttr(weight_attribute, "DOUBLE") + ELSE + s.@community_k_in_map += (t.@community_id -> e.getAttr(weight_attribute, "DOUBLE")) + END + POST-ACCUM + s.@delta_Q_remove = 2 * s.@k_self_loop - 2 * s.@k_in + s.@k * (s.@community_sum_total - s.@k) / @@m, + s.@k_in = 0, + s.@best_move = MyTuple(@@min_double, s); // Reset the delta_Q_add + // Find the best move + Nodes = SELECT s + FROM Nodes:s -(e_type_set:e)- :t + WHERE s.@community_id != t.@community_id + ACCUM DOUBLE delta_Q_add = 2 * s.@community_k_in_map.get(t.@community_id) - s.@k * t.@community_sum_total / @@m, + s.@best_move += MyTuple(delta_Q_add, t.@community_id) + POST-ACCUM + IF s.@delta_Q_remove + s.@best_move.delta_Q_add > 0 THEN // The gain (delta Q) is positive + s.@to_change_community = TRUE + END, + s.@community_k_in_map.clear() + HAVING s.@to_change_community == TRUE; + ChangedNodes = ChangedNodes UNION Nodes; + END; + END; + // If two nodes swap, only change the community of one of them + SwapNodes = SELECT s + FROM ChangedNodes:s -(e_type_set:e)- :t + WHERE s.@best_move.community == t.@community_id + AND t.@to_change_community == TRUE + AND t.@best_move.community == s.@community_id + // Only change the one with larger delta Q or the one with smaller @vid if delta Q are the same + AND (s.@delta_Q_remove + s.@best_move.delta_Q_add < t.@delta_Q_remove + t.@best_move.delta_Q_add + OR (abs((s.@delta_Q_remove + s.@best_move.delta_Q_add) - (t.@delta_Q_remove + t.@best_move.delta_Q_add)) < 0.00000000001 + AND s.@vid > t.@vid)) + POST-ACCUM + s.@to_change_community = FALSE; + ChangedNodes = ChangedNodes MINUS SwapNodes; + // Place each node of ChangedNodes in the community in which the gain is maximum + ChangedNodes = SELECT s + FROM ChangedNodes:s + POST-ACCUM + s.@community_id = s.@best_move.community, + s.@to_change_community = FALSE; + // Get all neighbours of the changed node that do not belong to the node’s new community + Candidates = SELECT t + FROM ChangedNodes:s -(e_type_set:e)- :t + WHERE t.@community_id != s.@community_id; + END; + + // Coarsening + UINT new_layer = 0; + @@community_sum_total_map.clear(); + Tmp = SELECT s + FROM Pass_Nodes:s -(e_type_set:e)- :t + ACCUM IF s.@community_id == t.@community_id THEN + @@community_sum_in_map += (s.@community_id -> e.getAttr(weight_attribute, "DOUBLE")) + END + POST-ACCUM + VERTEX cid = s.@community_id, + INSERT INTO belongs_to VALUES(s, cid, new_layer), + IF @@community_sum_in_map.containsKey(s) THEN + INSERT INTO links_to VALUES(s, s, (new_layer -> @@community_sum_in_map.get(s))) + END; + @@community_sum_in_map.clear(); + Tmp = SELECT s + FROM Pass_Nodes:s -(e_type_set:e)- :t + ACCUM IF s.@community_id != t.@community_id THEN + @@source_target_k_in_map += (s.@community_id -> (t.@community_id -> e.getAttr(weight_attribute, "DOUBLE"))) + END + POST-ACCUM + IF @@source_target_k_in_map.containsKey(s) THEN + FOREACH (target_community, k_in) IN @@source_target_k_in_map.get(s) DO + INSERT INTO links_to VALUES(s, target_community, (new_layer -> k_in)) + END + END; + @@source_target_k_in_map.clear(); + + // -------------------- 2. Remaining passes -------------------- + SumAccum @@sum; + INT layer = 0; + WHILE layer < total_passes_count - 1 DO + // Reset + Tmp = SELECT s + FROM Pass_Nodes:s -(links_to:e)- :t + ACCUM @@sum += 1 + POST-ACCUM // Reset + s.@k = 0, + s.@k_in = 0, + s.@k_self_loop = 0, + s.@best_move = MyTuple(@@min_double, s); + // Initialization + Pass_Nodes = SELECT s + FROM Pass_Nodes:s -(links_to:e)- :t + WHERE e.layer_weight_map.containsKey(layer) + ACCUM DOUBLE weight = e.layer_weight_map.get(layer), + s.@k += weight, + IF s == t THEN // Self-loop link + s.@k_self_loop += weight + END + POST-ACCUM + s.@community_id = s; + IF @@m < 0.00000000001 THEN + PRINT "Warning: the sum of the weights in the edges should be greater than zero!"; + RETURN; + END; + + // Local moving + INT hop = 0; + Candidates = Pass_Nodes; + WHILE Candidates.size() > 0 AND hop < maximum_iteration DO + hop = hop + 1; + IF hop == 1 THEN // First iteration + ChangedNodes = SELECT s + FROM Candidates:s -(links_to:e)- :t + WHERE e.layer_weight_map.containsKey(layer) + AND s.@community_id != t.@community_id + ACCUM s.@best_move += MyTuple(1 - s.@k * t.@k / (2 * @@m), t.@community_id) + POST-ACCUM + IF s.@best_move.delta_Q_add > 0 THEN // The gain (delta Q) is positive + s.@to_change_community = TRUE + END + HAVING s.@to_change_community == TRUE; + ELSE // Remaining iterations + // Calculate sum_total + Tmp = SELECT s + FROM Pass_Nodes:s POST-ACCUM - s.@sum_ac = 0, - s.@cm_list.clear(), - s.@A_map.clear(); - - Start = SELECT s - FROM Start:s - ACCUM - FOREACH v IN s.@cc_list DO - CASE WHEN getvid(v) != -1 THEN - v.@cm_list += s - END - END; - - Start = SELECT s - FROM Start:s -(e_type_set:e)- :t - WHERE t.@sum_outdegree > 1 - ACCUM - s.@A_map += (t.@cc_list.get(0) -> e.getAttr(weight_attribute, "FLOAT")*1.0); - - Start = SELECT s - FROM Start:s - ACCUM - FOREACH v IN s.@cc_list DO - CASE WHEN getvid(v) != -1 THEN - v.@sum_ac += s.@sum_weight - END - END; - - Start = SELECT s - FROM Start:s - ACCUM - FOREACH v IN s.@cm_list DO - CASE WHEN getvid(v) != -1 THEN - v.@sum_ac = s.@sum_ac - END - END; - - // compute @max_dQ - Start = SELECT s - FROM Start:s -(e_type_set:e)- :t - WHERE t.@sum_outdegree > 1 - ACCUM - INT A_s = 0, - IF s.@A_map.containsKey(s) THEN - A_s = s.@A_map.get(s) - END, - s.@max_best_move += move(s.@A_map.get(t.@cc_list.get(0)) - A_s + - 1/@@sum_m*s.@sum_weight*(s.@sum_ac-t.@sum_ac), -t.@sum_cc_weight, t.@cc_list.get(0)) + @@community_sum_total_map += (s.@community_id -> s.@k); + Tmp = SELECT s + FROM Pass_Nodes:s POST-ACCUM - IF s.@max_best_move.deltaQ > 0 THEN - IF -s.@max_best_move.weight < s.@sum_cc_weight THEN // smallest best_move weight < current weight - s.@cc_list.clear(), - s.@cc_list += s.@max_best_move.cc, - s.@sum_cc_weight = -s.@max_best_move.weight, - @@sum_cc_change += 1 - ELSE - IF -s.@max_best_move.weight == s.@sum_cc_weight AND getvid(s.@cc_list.get(0)) < getvid(s.@max_best_move.cc) THEN - s.@cc_list.clear(), - s.@cc_list += s.@max_best_move.cc, - s.@sum_cc_weight = -s.@max_best_move.weight, - @@sum_cc_change += 1 - END - END - END; - IF print_stats THEN - PRINT @@sum_cc_change AS IterChangeCount; + s.@community_sum_total = @@community_sum_total_map.get(s.@community_id); + @@community_sum_total_map.clear(); + // Find the best move + ChangedNodes = {}; + // Calculate the delta Q to remove the node from the previous community + Nodes = SELECT s + FROM Candidates:s -(links_to:e)- :t + WHERE e.layer_weight_map.containsKey(layer) + ACCUM DOUBLE weight = e.layer_weight_map.get(layer), + IF s.@community_id == t.@community_id THEN + s.@k_in += weight + ELSE + s.@community_k_in_map += (t.@community_id -> weight) + END + POST-ACCUM + s.@delta_Q_remove = 2 * s.@k_self_loop - 2 * s.@k_in + s.@k * (s.@community_sum_total - s.@k) / @@m, + s.@k_in = 0, + s.@best_move = MyTuple(@@min_double, s); // Reset the delta_Q_add + // Find the best move + Nodes = SELECT s + FROM Nodes:s -(links_to:e)- :t + WHERE e.layer_weight_map.containsKey(layer) + AND s.@community_id != t.@community_id + ACCUM DOUBLE delta_Q_add = 2 * s.@community_k_in_map.get(t.@community_id) - s.@k * t.@community_sum_total / @@m, + s.@best_move += MyTuple(delta_Q_add, t.@community_id) + POST-ACCUM + IF s.@delta_Q_remove + s.@best_move.delta_Q_add > 0 THEN // The gain (delta Q) is positive + s.@to_change_community = TRUE + END, + s.@community_k_in_map.clear() + HAVING s.@to_change_community == TRUE; + ChangedNodes = ChangedNodes UNION Nodes; END; - END; + // If two nodes swap, only change the community of one of them + SwapNodes = SELECT s + FROM ChangedNodes:s -(links_to:e)- :t + WHERE e.layer_weight_map.containsKey(layer) + AND s.@best_move.community == t.@community_id + AND t.@to_change_community == TRUE + AND t.@best_move.community == s.@community_id + // Only change the one with larger delta Q or the one with smaller @vid if delta Q are the same + AND (s.@delta_Q_remove + s.@best_move.delta_Q_add < t.@delta_Q_remove + t.@best_move.delta_Q_add + OR (abs((s.@delta_Q_remove + s.@best_move.delta_Q_add) - (t.@delta_Q_remove + t.@best_move.delta_Q_add)) < 0.00000000001 + AND s.@vid > t.@vid)) + POST-ACCUM + s.@to_change_community = FALSE; + ChangedNodes = ChangedNodes MINUS SwapNodes; + // Place each node of ChangedNodes in the community in which the gain is maximum + ChangedNodes = SELECT s + FROM ChangedNodes:s + POST-ACCUM + s.@community_id = s.@best_move.community, + s.@to_change_community = FALSE; + // Get all neighbours of the changed node that do not belong to the node’s new community + Candidates = SELECT t + FROM ChangedNodes:s -(links_to:e)- :t + WHERE e.layer_weight_map.containsKey(layer) + AND t.@community_id != s.@community_id; + END; - // process node with outdegree=1 - // follow the vertex to its neighbor's community - // if the neighbor also have outdegree=1, mark the two vertices as one community - Start = {v_type_set}; - Start = SELECT s - FROM Start:s -(e_type_set:e)- :t - WHERE s.@sum_outdegree == 1 AND t.@sum_outdegree != 1 - ACCUM - s.@cc_list += t.@cc_list.get(0); - IF print_stats THEN - PRINT Start.size() AS VertexFollowedToCommunity; + // Coarsening + UINT new_layer = layer + 1; + @@community_sum_total_map.clear(); + Tmp = SELECT s + FROM Pass_Nodes:s -(links_to:e)- :t + WHERE e.layer_weight_map.containsKey(layer) + ACCUM IF s.@community_id == t.@community_id THEN + DOUBLE weight = e.layer_weight_map.get(layer), + @@community_sum_in_map += (s.@community_id -> weight) + END + POST-ACCUM + VERTEX cid = s.@community_id, + INSERT INTO belongs_to VALUES(s, cid, new_layer), + IF @@community_sum_in_map.containsKey(s) THEN + INSERT INTO links_to VALUES(s, s, (new_layer -> @@community_sum_in_map.get(s))) + END; + @@community_sum_in_map.clear(); + Tmp = SELECT s + FROM Pass_Nodes:s -(links_to:e)- :t + WHERE e.layer_weight_map.containsKey(layer) + ACCUM DOUBLE weight = e.layer_weight_map.get(layer), + IF s.@community_id != t.@community_id THEN + @@source_target_k_in_map += (s.@community_id -> (t.@community_id -> weight)) + END + POST-ACCUM + IF @@source_target_k_in_map.containsKey(s) THEN + FOREACH (target_community, k_in) IN @@source_target_k_in_map.get(s) DO + INSERT INTO links_to VALUES(s, target_community, (new_layer -> k_in)) + END + END; + @@source_target_k_in_map.clear(); + layer = layer + 1; END; - Start = {v_type_set}; - Start = SELECT s - FROM Start:s -(e_type_set:e)- :t - WHERE s.@sum_outdegree == 1 AND t.@sum_outdegree == 1 - ACCUM - IF getvid(s) <= getvid(t) THEN - s.@cc_list += s - ELSE - s.@cc_list += t - END; - IF print_stats THEN - PRINT Start.size() AS VertexFollowedToVertex; - END; + // -------------------- 3. Final community and output -------------------- + // Top layer + layer = total_passes_count - 1; + Nodes = SELECT s + FROM All_Nodes:s -(belongs_to>:e)- :t + WHERE layer IN e.layer_set + ACCUM s.@community_id = t; - // process node with outdegree=0 - // assign them to communities containing only itself - Start = {v_type_set}; - Start = SELECT s - FROM Start:s - WHERE s.@sum_outdegree == 0 - ACCUM - s.@cc_list += s; - IF print_stats THEN - PRINT Start.size() AS VertexAssignedToItself; + // Other layers + WHILE Nodes.size() > 0 AND layer > 0 DO + layer = layer - 1; + Nodes = SELECT s + FROM All_Nodes:s -(belongs_to>:e)- :t + WHERE layer IN e.layer_set + ACCUM s.@community_id = t.@community_id; END; - // save result - Start = {v_type_set}; - Start = SELECT s - FROM Start:s + // Output results + Nodes = SELECT s + FROM All_Nodes:s POST-ACCUM - IF result_attribute != "" THEN - s.setAttr(result_attribute, getvid(s.@cc_list.get(0))) - END, - IF file_path != "" THEN - f.println(s, getvid(s.@cc_list.get(0))) - END; - - // print result satistic - IF print_stats THEN - Start = SELECT s - FROM Start:s - WHERE s.@cc_list.size() > 0 - POST-ACCUM - @@community_map += (getvid(s.@cc_list.get(0)) -> 1); - PRINT @@community_map.size() AS FinalCommunityCount; - END; + IF result_attribute != "" THEN + s.setAttr(result_attribute, getvid(s.@community_id)) + END, + IF file_path != "" THEN + IF v_type_set.size() == 1 THEN + f.println(s.id, s.@community_id) + ELSE + VERTEX node = s.@community_id, + f.println(s.type, s, node.type, node) + END + END; } From 362024f4758b6294d0878accd7d4c04fbc6e2a49 Mon Sep 17 00:00:00 2001 From: RobRossmiller-TG <165701656+RobRossmiller-TG@users.noreply.github.com> Date: Tue, 16 Jul 2024 11:32:26 -0400 Subject: [PATCH 30/38] Update tg_fastRP.gsql --- algorithms/GraphML/Embeddings/FastRP/tg_fastRP.gsql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/algorithms/GraphML/Embeddings/FastRP/tg_fastRP.gsql b/algorithms/GraphML/Embeddings/FastRP/tg_fastRP.gsql index 5dac70b5..fa78bef3 100644 --- a/algorithms/GraphML/Embeddings/FastRP/tg_fastRP.gsql +++ b/algorithms/GraphML/Embeddings/FastRP/tg_fastRP.gsql @@ -265,7 +265,7 @@ CREATE QUERY tg_fastRP( IF print_results THEN res = SELECT a FROM verts:a; - PRINT res[res.@final_embedding_list]; + PRINT res[res.@final_embedding_arr]; END; // (un)comment depending on whether you want to write to graph From eb6d29fd3d80e1c0f6622e32362d3e637c1561fe Mon Sep 17 00:00:00 2001 From: RobRossmiller-TG <165701656+RobRossmiller-TG@users.noreply.github.com> Date: Tue, 16 Jul 2024 13:07:15 -0400 Subject: [PATCH 31/38] update contrib --- .github/auto_request_review.yml | 24 +++++++++++------------- tests/CONTRIBUTING.md | 24 +++++++++++++++--------- tests/run.sh | 2 +- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/.github/auto_request_review.yml b/.github/auto_request_review.yml index a00ed4a7..197024ed 100644 --- a/.github/auto_request_review.yml +++ b/.github/auto_request_review.yml @@ -4,30 +4,28 @@ reviewers: # The default reviewers if not assigned specifically defaults: + - primary - secondary - tertiary # Reviewer groups each of which has a list of GitHub usernames groups: - group1: - - yimingpantg - - TannerW + primary: + - RobRossmiller-TG - wyattjoynertg - parkererickson-tg - - alexthomasTG - secondary: - - yimingpantg - - wyattjoynertg - - TannerW - tertiary: - - xinyuchtg - victorleeTG - - TannerW + # secondary: + # - RobRossmiller-TG + # - wyattjoynertg + # tertiary: + # - victorleeTG files: # All review request will be sent to the following groups/people. '**': - - secondary - - tertiary + - primary + # - secondary + # - tertiary options: # ignore draft PRs diff --git a/tests/CONTRIBUTING.md b/tests/CONTRIBUTING.md index 5a021193..a4b05fcd 100644 --- a/tests/CONTRIBUTING.md +++ b/tests/CONTRIBUTING.md @@ -29,23 +29,26 @@ pip install -r requirements.txt `run.sh` does a few things: -- runs `data/create_baseline.py` - - this creates the baselines from the graphs listed in that file -- runs the setup script to make sure the graph is created and data is loaded +- runs the setup script to make sure the graphs are created and data is loaded +- runs `test/create_baseline.py` + - this creates the baselines for the algorithm types listed in that file - runs the tests with pytest ## Directory layout -Data: stores the satic data for creating graphs, and algorithm baseline results. +`data`: stores the satic data for creating graphs, and algorithm baseline results. - CSV files under `data/[heterogeneous_edges, unweighted_edges, weighted_edges]` store the adjacency information for creating graphs. The baselines for algorithms are made from these graphs - For example `data/weighted_edges/line_edges.csv` stores the edges and weights to create a weighted, line graph. - JSON files under `data/baseline` store the baseline results for a given algorithm on a given graph type. - For example `data/baseline/centrality/pagerank/Line_Directed.json` stores the baseline results for pagerank on a directed line graph +Some data (like the Cora graph) comes from [pyTigerGraph Datasets](https://docs.tigergraph.com/pytigergraph/current/datasets/datasets_object), so it will not be in the this path. + test: - setup.py: creates the graph, loads the data and installs the queries from pyTG's featurizer. Any new/custom queries need to be manually installed +- create_baseline.py: creates the baselines the algorithms will be compared to. - test.py: houses the testing code for each family of algorithms ``` @@ -56,25 +59,28 @@ test: │   │ │ └── .json │   ├── │   │ └── .csv -│   └── create_baseline.py ├── requirements.txt ├── run.sh ├── test │   ├── pyrightconfig.json │   ├── setup.py +│   ├── baseline/ +│ │   ├── algos/ +│ │ │ └── .py +│ │ └── .py │   ├── test_centrality.py │   ├── test_community.py │   ├── test_path_finding.py -│   ├── test_topological_link_prediction.py +│   ├── ... │   └── util.py ``` ## Adding tests -Start with creating the baseline. Add a section to `create_baseline.py` that creates a baseline for all the necessary graph types for your algorithm. The output of the baseline should be written to -the correct baseline path (see above [layout](#directory-layout)). +Start with creating the baseline. Add a method call to `create_baseline.py` that creates a baseline for all the necessary graph types for your algorithm. The output of the baseline should be written to +the correct path in `data/baseline` (see above [layout](#directory-layout)). -If you're adding a new algorithm, add a test method for it to the algorithm family that it belongs to (i.e., community algorigthms go in community.py). The first test method in `test/test_centrality.py` +If you're adding a new algorithm, add a test method to the algorithm family that it belongs to (i.e., community algorigthms go in community.py). The first test method in `test/test_centrality.py` is a good template to follow: ```py diff --git a/tests/run.sh b/tests/run.sh index e05cb3cd..b3a6b214 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -1,4 +1,4 @@ clear python3 test/setup.py && python3 test/baseline/create_baselines.py && - pytest test/test_centrality.py test/test_ml.py + pytest test/test_centrality.py #test/test_ml.py From d34a3c5781ad438a1d68bb7af68f839971f4c07f Mon Sep 17 00:00:00 2001 From: RobRossmiller-TG <165701656+RobRossmiller-TG@users.noreply.github.com> Date: Wed, 17 Jul 2024 09:54:12 -0400 Subject: [PATCH 32/38] rm ds store --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 644280363c6a9fea02a03f62bdd0ba03ae58b52e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKO>YuG7=DM+vOpS%(O3^An;35d!6Mce6H4JgqQ(@X2Q`!x0vnfIWEV+A3}^iZ zn)nyI`Ww8fzr_#^M# zH!~LwJU3|BZf7XScDnO1qFvt zWdQvm>ai>656AQ#)#-q0^ne;v3v10jM7)!92+w`0(tThK(P>8*zaorHM6p9Ya(^Wz zUl%d?v4}~+NE>S+@;9O&g0M50+}bT>&6|Z{GzJ&1UcYnLeChOE?>(L#wu}J(I#!MA zv9h9w?&q<}E;W%Hyup@uFL}+US7^!6G4yi6dc}CmR^!1(_waL~k&Atgc&B{Oo zGt#G{YnE0(D{zJt;QND##IdY!CQ)o1$ix)@7)P@(l*M0vpvfD6WrZ_|Xn`?F1xhMY zR}7|Z4ouR~UsnDziIPrC%#3l=%uL--n7Vi{Q@9f=OVqhmKr0YdU_(|j{QR%|`Tid! z>6%tRD{!V1VDW0BTEUXk*?MAe{H!&Rj*z% Date: Mon, 22 Jul 2024 07:27:48 +0000 Subject: [PATCH 33/38] [ALGOS-266] feat(algos): Fix bugs for template query; --- GDBMS_ALGO/community/label_prop.gsql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/GDBMS_ALGO/community/label_prop.gsql b/GDBMS_ALGO/community/label_prop.gsql index 7e243325..6be6c654 100644 --- a/GDBMS_ALGO/community/label_prop.gsql +++ b/GDBMS_ALGO/community/label_prop.gsql @@ -8,7 +8,7 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.community.label_prop( BOOL print_results = TRUE, STRING result_attribute = "", STRING file_path="" -) FOR GRAPH MyGraph SYNTAX V1 { +) SYNTAX V1 { /* First Author: xuanlei.lin@tigergraph.com @@ -171,7 +171,8 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.community.label_prop( END, IF file_path != "" THEN IF v_type_set.size() == 1 THEN - f.println(s.id, s.@community_id) + VERTEX node = s.@community_id, + f.println(s, node) ELSE VERTEX node = s.@community_id, f.println(s.type, s, node.type, node) From a8cae74ff8007c241fda887f41f22eb2efb3e259 Mon Sep 17 00:00:00 2001 From: "xunalei.lin" Date: Mon, 22 Jul 2024 07:35:11 +0000 Subject: [PATCH 34/38] [ALGOS-267] feat(algos): Fix bugs for template query; --- GDBMS_ALGO/community/louvain.gsql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GDBMS_ALGO/community/louvain.gsql b/GDBMS_ALGO/community/louvain.gsql index 41050d90..132283fe 100644 --- a/GDBMS_ALGO/community/louvain.gsql +++ b/GDBMS_ALGO/community/louvain.gsql @@ -377,7 +377,8 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.community.louvain( END, IF file_path != "" THEN IF v_type_set.size() == 1 THEN - f.println(s.id, s.@community_id) + VERTEX node = s.@community_id, + f.println(s, node) ELSE VERTEX node = s.@community_id, f.println(s.type, s, node.type, node) From ae0522e951e25e9fad9dee0a127a048a517ef721 Mon Sep 17 00:00:00 2001 From: "xunalei.lin" Date: Mon, 22 Jul 2024 12:07:30 +0000 Subject: [PATCH 35/38] [ALGOS-267] feat(algos): Fix bugs for template query; --- GDBMS_ALGO/community/louvain.gsql | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/GDBMS_ALGO/community/louvain.gsql b/GDBMS_ALGO/community/louvain.gsql index 132283fe..3b0da700 100644 --- a/GDBMS_ALGO/community/louvain.gsql +++ b/GDBMS_ALGO/community/louvain.gsql @@ -376,12 +376,7 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.community.louvain( s.setAttr(result_attribute, getvid(s.@community_id)) END, IF file_path != "" THEN - IF v_type_set.size() == 1 THEN - VERTEX node = s.@community_id, - f.println(s, node) - ELSE - VERTEX node = s.@community_id, - f.println(s.type, s, node.type, node) - END + VERTEX node = s.@community_id, + f.println(s.type, s, node.type, node) END; } From 884833d0ed3ba5df8d5aad6fc90d1ec35905452a Mon Sep 17 00:00:00 2001 From: "xunalei.lin" Date: Mon, 22 Jul 2024 12:08:44 +0000 Subject: [PATCH 36/38] [ALGOS-266] feat(algos): Fix bugs for template query; --- GDBMS_ALGO/community/label_prop.gsql | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/GDBMS_ALGO/community/label_prop.gsql b/GDBMS_ALGO/community/label_prop.gsql index 6be6c654..779adee0 100644 --- a/GDBMS_ALGO/community/label_prop.gsql +++ b/GDBMS_ALGO/community/label_prop.gsql @@ -170,13 +170,8 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.community.label_prop( @@comm_sizes_map += (s.@community_id -> 1) END, IF file_path != "" THEN - IF v_type_set.size() == 1 THEN - VERTEX node = s.@community_id, - f.println(s, node) - ELSE - VERTEX node = s.@community_id, - f.println(s.type, s, node.type, node) - END + VERTEX node = s.@community_id, + f.println(s.type, s, node.type, node) END LIMIT print_limit; From 8c3431e3af043ad6895a878105a96f0e0d77c427 Mon Sep 17 00:00:00 2001 From: "xunalei.lin" Date: Mon, 22 Jul 2024 13:35:26 +0000 Subject: [PATCH 37/38] [ALGOS-267] feat(algos): Fix bugs for template query; --- GDBMS_ALGO/community/louvain.gsql | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/GDBMS_ALGO/community/louvain.gsql b/GDBMS_ALGO/community/louvain.gsql index 3b0da700..bc9d9fdd 100644 --- a/GDBMS_ALGO/community/louvain.gsql +++ b/GDBMS_ALGO/community/louvain.gsql @@ -78,23 +78,24 @@ CREATE TEMPLATE QUERY GDBMS_ALGO.community.louvain( // -------------------- 1. First pass -------------------- // Initialization All_Nodes = {v_type_set}; - Pass_Nodes = SELECT s - FROM All_Nodes:s -(e_type_set:e)- :t - ACCUM @@m += e.getAttr(weight_attribute, "DOUBLE") / 2, - s.@k += e.getAttr(weight_attribute, "DOUBLE"), - IF s == t THEN // Self-loop link - s.@k_self_loop += e.getAttr(weight_attribute, "DOUBLE") - END - POST-ACCUM - s.@community_id = s, - s.@vid = getvid(s), - s.@batch_id = s.@vid % total_batch_count; + All_Nodes = SELECT s + FROM All_Nodes:s -(e_type_set:e)- :t + ACCUM @@m += e.getAttr(weight_attribute, "DOUBLE") / 2, + s.@k += e.getAttr(weight_attribute, "DOUBLE"), + IF s == t THEN // Self-loop link + s.@k_self_loop += e.getAttr(weight_attribute, "DOUBLE") + END + POST-ACCUM + s.@community_id = s, + s.@vid = getvid(s), + s.@batch_id = s.@vid % total_batch_count; IF @@m < 0.00000000001 THEN RETURN; END; // Local moving INT hop = 0; + Pass_Nodes = All_Nodes; Candidates (ANY) = Pass_Nodes; WHILE Candidates.size() > 0 AND hop < maximum_iteration DO hop = hop + 1; From ee1ea0eef3e54a0762d5821e2352d8ef210f74b0 Mon Sep 17 00:00:00 2001 From: Bill Shi Date: Wed, 4 Sep 2024 23:49:05 -0700 Subject: [PATCH 38/38] fix(tg_ExprUtil): add curl header --- UDF/tg_ExprUtil.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/UDF/tg_ExprUtil.hpp b/UDF/tg_ExprUtil.hpp index 8346b673..9d9d906b 100644 --- a/UDF/tg_ExprUtil.hpp +++ b/UDF/tg_ExprUtil.hpp @@ -38,6 +38,7 @@ #include #include #include +#include namespace tg {