chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
import os
|
||||
|
||||
from numbers import Number
|
||||
from typing import Iterator
|
||||
|
||||
import easygraph as eg
|
||||
|
||||
|
||||
def fuzzy_equal(o1, o2):
|
||||
if isinstance(o1, dict) and isinstance(o2, dict):
|
||||
if set(o1.keys()) != set(o2.keys()):
|
||||
return False
|
||||
for key in o1.keys():
|
||||
o1_, o2_ = o1[key], o2[key]
|
||||
if not fuzzy_equal(o1_, o2_):
|
||||
return False
|
||||
return True
|
||||
if isinstance(o1, Iterator) and isinstance(o2, Iterator):
|
||||
return fuzzy_equal(list(o1), list(o2))
|
||||
if isinstance(o1, list) and isinstance(
|
||||
o2, list
|
||||
): # every item in list1 should be in list2
|
||||
if len(o1) != len(o2):
|
||||
return False
|
||||
for item1 in o1:
|
||||
belong = False
|
||||
for item2 in o2:
|
||||
if fuzzy_equal(item1, item2):
|
||||
belong = True
|
||||
break
|
||||
if not belong:
|
||||
print(item1)
|
||||
return False
|
||||
return True
|
||||
if isinstance(o1, tuple) and isinstance(
|
||||
o2, tuple
|
||||
): # corresponding items should be equal
|
||||
if len(o1) != len(o2):
|
||||
return False
|
||||
for i in range(len(o1)):
|
||||
if not fuzzy_equal(o1[i], o2[i]):
|
||||
return False
|
||||
return True
|
||||
if isinstance(o1, Number) and isinstance(o2, Number):
|
||||
return abs(o2 - o1) < 1e-6
|
||||
return o1 == o2
|
||||
|
||||
|
||||
class Tester:
|
||||
def __init__(self, class1, class2):
|
||||
self.class1 = class1
|
||||
self.class2 = class2
|
||||
|
||||
def run_method(self, name, *args, **kwargs):
|
||||
r1 = getattr(self.G1, name)(*args, **kwargs)
|
||||
r2 = getattr(self.G2, name)(*args, **kwargs)
|
||||
return r1, r2
|
||||
|
||||
def assert_object(self, o1, o2):
|
||||
if not fuzzy_equal(o1, o2):
|
||||
print(f"o1: {o1}")
|
||||
print(f"o2: {o2}")
|
||||
raise AssertionError(
|
||||
f"FAILED: o1 != o2 in test for {self.class1.__name__} and"
|
||||
f" {self.class2.__name__}"
|
||||
)
|
||||
|
||||
def assert_property(self, name, g1=None, g2=None):
|
||||
if g1 is None:
|
||||
g1 = self.G1
|
||||
if g2 is None:
|
||||
g2 = self.G2
|
||||
r1 = getattr(g1, name)
|
||||
r2 = getattr(g2, name)
|
||||
if name == "edges":
|
||||
if not g1.is_directed():
|
||||
r1 = r1 + [
|
||||
(edge[1], edge[0])
|
||||
if len(edge) == 2
|
||||
else (edge[1], edge[0], edge[2])
|
||||
for edge in r1
|
||||
]
|
||||
r2 = r2 + [
|
||||
(edge[1], edge[0])
|
||||
if len(edge) == 2
|
||||
else (edge[1], edge[0], edge[2])
|
||||
for edge in r2
|
||||
]
|
||||
self.assert_object(r1, r2)
|
||||
|
||||
def assert_method(self, name, *args, **kwargs):
|
||||
r1 = getattr(self.G1, name)(*args, **kwargs)
|
||||
r2 = getattr(self.G2, name)(*args, **kwargs)
|
||||
self.assert_object(r1, r2)
|
||||
|
||||
def assert_graph(self, g1, g2):
|
||||
self.assert_property("graph", g1, g2)
|
||||
self.assert_property("nodes", g1, g2)
|
||||
self.assert_property("edges", g1, g2)
|
||||
self.assert_property("adj", g1, g2)
|
||||
|
||||
def test(self):
|
||||
self.G1 = self.class1(name="graph", time=0)
|
||||
self.G2 = self.class2(name="graph", time=0)
|
||||
self.assert_property("graph")
|
||||
|
||||
self.run_method("add_node", 1, x=2)
|
||||
self.assert_property("nodes")
|
||||
|
||||
self.run_method("add_nodes", [2])
|
||||
self.run_method("add_nodes", [3, 4], [{"x": 2}, {"x": 2}])
|
||||
self.assert_property("nodes")
|
||||
|
||||
self.run_method("add_nodes_from", [5], y=3)
|
||||
self.assert_property("nodes")
|
||||
|
||||
self.assert_property("adj")
|
||||
|
||||
self.run_method("add_edges", [(1, 2)])
|
||||
self.run_method("add_edges", [(2, 3), (1, 3)], [{"weight": 1}, {"weight": 1}])
|
||||
self.assert_property("edges")
|
||||
|
||||
self.run_method("add_edges_from", [(1, 4)], we=2)
|
||||
self.assert_property("edges")
|
||||
|
||||
self.assert_property("adj")
|
||||
|
||||
with open("test.txt", "w") as f:
|
||||
f.writelines(["6,7\n"])
|
||||
self.run_method("add_edges_from_file", "test.txt")
|
||||
with open("test.txt", "w") as f:
|
||||
f.writelines(["8,9,10\n", "9,10,11\n"])
|
||||
self.run_method("add_edges_from_file", "test.txt", True)
|
||||
os.remove("test.txt")
|
||||
self.assert_property("edges")
|
||||
|
||||
self.run_method("remove_node", "8")
|
||||
self.run_method("remove_nodes", ["9", "10"])
|
||||
self.assert_property("nodes")
|
||||
|
||||
self.assert_property("edges")
|
||||
self.assert_property("adj")
|
||||
|
||||
self.run_method("remove_edge", "6", "7")
|
||||
self.run_method("remove_edges", [(2, 3), (1, 3)])
|
||||
self.assert_property("edges")
|
||||
|
||||
self.assert_property("adj")
|
||||
|
||||
self.assert_method("has_node", 1)
|
||||
self.assert_method("has_node", 10)
|
||||
self.assert_method("has_edge", 1, 2)
|
||||
self.assert_method("has_edge", 2, 3)
|
||||
self.assert_method("number_of_edges")
|
||||
self.assert_method("number_of_edges", 2)
|
||||
self.assert_method("number_of_nodes")
|
||||
self.assert_method("is_directed")
|
||||
self.assert_method("is_multigraph")
|
||||
self.assert_method("degree", "we")
|
||||
self.assert_method("size")
|
||||
self.assert_method("size", "we")
|
||||
|
||||
if self.G1.is_directed():
|
||||
self.assert_method("in_degree", "we")
|
||||
self.assert_method("out_degree")
|
||||
|
||||
G_1, G_2 = self.run_method("copy")
|
||||
G_1.add_edge(-1, -1)
|
||||
G_2.add_edge(-1, -1)
|
||||
self.assert_graph(G_1, G_2)
|
||||
self.assert_graph(self.G1, self.G2)
|
||||
self.assert_object(G_1.has_edge(-1, -1), True)
|
||||
self.assert_object(self.G1.has_edge(-1, -1), False)
|
||||
|
||||
G_2 = self.G2.py()
|
||||
self.assert_graph(self.G1, G_2)
|
||||
G_1 = self.G1.cpp()
|
||||
self.assert_graph(G_1, self.G2)
|
||||
|
||||
G_1, G_2 = self.run_method("nodes_subgraph", [1, 2, 3, "6"])
|
||||
self.assert_graph(G_1, G_2)
|
||||
|
||||
G_1, G_2 = self.run_method("ego_subgraph", 1)
|
||||
self.assert_graph(G_1, G_2)
|
||||
|
||||
(G_1, _, node_of_index_1), (
|
||||
G_2,
|
||||
_,
|
||||
node_of_index_2,
|
||||
) = self.run_method("to_index_node_graph")
|
||||
G_1_nodes = {node_of_index_1[i]: j for i, j in G_1.nodes.items()}
|
||||
G_1_adj = {
|
||||
node_of_index_1[i]: {node_of_index_1[a]: b for a, b in j.items()}
|
||||
for i, j in G_1.adj.items()
|
||||
}
|
||||
G_2_nodes = {node_of_index_2[i]: j for i, j in G_2.nodes.items()}
|
||||
G_2_adj = {
|
||||
node_of_index_2[i]: {node_of_index_2[a]: b for a, b in j.items()}
|
||||
for i, j in G_2.adj.items()
|
||||
}
|
||||
self.assert_object(G_1_nodes, G_2_nodes)
|
||||
self.assert_object(G_1_adj, G_2_adj)
|
||||
|
||||
self.assert_method("__len__")
|
||||
|
||||
self.assert_method("__contains__", 1)
|
||||
self.assert_method("__contains__", 10)
|
||||
|
||||
self.assert_method("__getitem__", 1)
|
||||
|
||||
self.assert_method("__iter__")
|
||||
|
||||
print(f"PASSED: Test for {self.class1.__name__} and {self.class2.__name__}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
graph_tester = Tester(eg.Graph, eg.GraphC)
|
||||
digraph_tester = Tester(eg.DiGraph, eg.DiGraphC)
|
||||
|
||||
graph_tester.test()
|
||||
digraph_tester.test()
|
||||
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Yeah, I tried for hours but I can't get it to work with pytest.
|
||||
Just write in pytest next time. :)
|
||||
|
||||
Teddy
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from typing import Iterator
|
||||
|
||||
import easygraph as eg
|
||||
|
||||
from pytest import approx
|
||||
|
||||
|
||||
def fuzzy_equal(o1, o2):
|
||||
if isinstance(o1, dict) and isinstance(o2, dict):
|
||||
if set(o1.keys()) != set(o2.keys()):
|
||||
return False
|
||||
for key in o1.keys():
|
||||
o1_, o2_ = o1[key], o2[key]
|
||||
if not fuzzy_equal(o1_, o2_):
|
||||
return False
|
||||
return True
|
||||
if str(o1).isdigit() and str(o2).isdigit():
|
||||
# return abs(o2 - o1) < 1e-6
|
||||
assert o1 == approx(o2)
|
||||
if isinstance(o1, Iterator) and isinstance(o2, Iterator):
|
||||
return fuzzy_equal(list(o1), list(o2))
|
||||
return o1 == o2
|
||||
|
||||
|
||||
class CPPGraphTestBase:
|
||||
# def __init__(self, class1, class2):
|
||||
@classmethod
|
||||
def setup_method(cls):
|
||||
cls.class1 = object
|
||||
cls.class2 = object
|
||||
cls.G1 = cls.class1(name="graph", time=0) # type: ignore
|
||||
cls.G2 = cls.class2(name="graph", time=0) # type: ignore
|
||||
|
||||
@classmethod
|
||||
def run_method(cls, name, *args, **kwargs):
|
||||
r1 = getattr(cls.G1, name)(*args, **kwargs)
|
||||
r2 = getattr(cls.G2, name)(*args, **kwargs)
|
||||
return r1, r2
|
||||
|
||||
@classmethod
|
||||
def assert_object(cls, o1, o2):
|
||||
if not fuzzy_equal(o1, o2):
|
||||
print(f"o1: {o1}")
|
||||
print(f"o2: {o2}")
|
||||
raise AssertionError(
|
||||
f"FAILED: o1 != o2 in test for {cls.class1.__name__} and"
|
||||
f" {cls.class2.__name__}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def assert_property(cls, name):
|
||||
r1 = getattr(cls.G1, name)
|
||||
r2 = getattr(cls.G2, name)
|
||||
cls.assert_object(r1, r2)
|
||||
|
||||
def assert_method(self, name, *args, **kwargs):
|
||||
r1 = getattr(self.G1, name)(*args, **kwargs)
|
||||
r2 = getattr(self.G2, name)(*args, **kwargs)
|
||||
self.assert_object(r1, r2)
|
||||
|
||||
def assert_graph(self, g1, g2):
|
||||
self.assert_object(g1.graph, g2.graph)
|
||||
self.assert_object(g1.nodes, g2.nodes)
|
||||
self.assert_object(g1.edges, g2.edges)
|
||||
self.assert_object(g1.adj, g2.adj)
|
||||
|
||||
def test_graph(self):
|
||||
self.assert_property("graph")
|
||||
|
||||
self.run_method("add_node", 1, x=2)
|
||||
self.assert_property("nodes")
|
||||
|
||||
self.run_method("add_nodes", [2])
|
||||
self.run_method("add_nodes", [3, 4], [{"x": 2}, {"x": 2}])
|
||||
self.assert_property("nodes")
|
||||
|
||||
self.run_method("add_nodes_from", [5], y=3)
|
||||
self.assert_property("nodes")
|
||||
|
||||
self.assert_property("adj")
|
||||
|
||||
self.run_method("add_edges", [(1, 2)])
|
||||
self.run_method("add_edges", [(2, 3), (1, 3)], [{"weight": 1}, {"weight": 1}])
|
||||
self.assert_property("edges")
|
||||
|
||||
self.run_method("add_edges_from", [(1, 4)], we=2)
|
||||
self.assert_property("edges")
|
||||
|
||||
self.assert_property("adj")
|
||||
|
||||
with open("test.txt", "w") as f:
|
||||
f.writelines(["6,7\n"])
|
||||
self.run_method("add_edges_from_file", "test.txt")
|
||||
with open("test.txt", "w") as f:
|
||||
f.writelines(["8,9,10\n", "9,10,11\n"])
|
||||
self.run_method("add_edges_from_file", "test.txt", True)
|
||||
os.remove("test.txt")
|
||||
self.assert_property("edges")
|
||||
|
||||
self.run_method("remove_node", "8")
|
||||
self.run_method("remove_nodes", ["9", "10"])
|
||||
self.assert_property("nodes")
|
||||
|
||||
self.assert_property("edges")
|
||||
self.assert_property("adj")
|
||||
|
||||
self.run_method("remove_edge", "6", "7")
|
||||
self.run_method("remove_edges", [(2, 3), (1, 3)])
|
||||
self.assert_property("edges")
|
||||
|
||||
self.assert_property("adj")
|
||||
|
||||
self.assert_method("has_node", 1)
|
||||
self.assert_method("has_node", 10)
|
||||
self.assert_method("has_edge", 1, 2)
|
||||
self.assert_method("has_edge", 2, 3)
|
||||
self.assert_method("number_of_edges")
|
||||
self.assert_method("number_of_edges", 2)
|
||||
self.assert_method("number_of_nodes")
|
||||
self.assert_method("is_directed")
|
||||
self.assert_method("is_multigraph")
|
||||
self.assert_method("degree", "we")
|
||||
self.assert_method("size")
|
||||
self.assert_method("size", "we")
|
||||
|
||||
if self.G1.is_directed(): # type: ignore
|
||||
self.assert_method("in_degree", "we")
|
||||
self.assert_method("out_degree")
|
||||
|
||||
G_1, G_2 = self.run_method("copy")
|
||||
G_1.add_edge(-1, -1)
|
||||
G_2.add_edge(-1, -1)
|
||||
self.assert_graph(G_1, G_2)
|
||||
self.assert_graph(self.G1, self.G2)
|
||||
self.assert_object(G_1.has_edge(-1, -1), True)
|
||||
self.assert_object(self.G1.has_edge(-1, -1), False) # type: ignore
|
||||
|
||||
G_1, G_2 = self.run_method("nodes_subgraph", [1, 2, 3, "6"])
|
||||
self.assert_graph(G_1, G_2)
|
||||
|
||||
G_1, G_2 = self.run_method("ego_subgraph", 1)
|
||||
self.assert_graph(G_1, G_2)
|
||||
|
||||
self.assert_method("__len__")
|
||||
|
||||
self.assert_method("__contains__", 1)
|
||||
self.assert_method("__contains__", 10)
|
||||
|
||||
self.assert_method("__getitem__", 1)
|
||||
|
||||
self.assert_method("__iter__")
|
||||
|
||||
print(f"PASSED: Test for {self.class1.__name__} and {self.class2.__name__}")
|
||||
|
||||
|
||||
class TestGraphC(CPPGraphTestBase):
|
||||
def setup_method(self, method):
|
||||
self.class1 = eg.Graph
|
||||
self.class2 = eg.GraphC
|
||||
self.G1 = self.class1(name="graph", time=0) # type: ignore
|
||||
self.G2 = self.class2(name="graph", time=0) # type: ignore
|
||||
|
||||
|
||||
class TestDiGraphC(CPPGraphTestBase):
|
||||
def setup_method(self, method):
|
||||
self.class1 = eg.DiGraph
|
||||
self.class2 = eg.DiGraphC
|
||||
self.G1 = self.class1(name="graph", time=0) # type: ignore
|
||||
self.G2 = self.class2(name="graph", time=0) # type: ignore
|
||||
@@ -0,0 +1,153 @@
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
pd = pytest.importorskip("pandas")
|
||||
sp = pytest.importorskip("scipy")
|
||||
|
||||
import easygraph as eg
|
||||
|
||||
from easygraph.utils.misc import *
|
||||
|
||||
|
||||
class TestConvertNumpyArray:
|
||||
def setup_method(self):
|
||||
self.G1 = eg.complete_graph(5)
|
||||
|
||||
def assert_equal(self, G1, G2):
|
||||
assert nodes_equal(G1.nodes, G2.nodes)
|
||||
assert edges_equal(G1.edges, G2.edges, need_data=False)
|
||||
|
||||
def identity_conversion(self, G, A, create_using):
|
||||
assert A.sum() > 0
|
||||
GG = eg.from_numpy_array(A, create_using=create_using)
|
||||
self.assert_equal(G, GG)
|
||||
GW = eg.to_easygraph_graph(A, create_using=create_using)
|
||||
self.assert_equal(G, GW)
|
||||
|
||||
def test_identity_graph_array(self):
|
||||
"Conversion from graph to array to graph."
|
||||
A = eg.to_numpy_array(self.G1)
|
||||
self.identity_conversion(self.G1, A, eg.Graph())
|
||||
|
||||
|
||||
class TestConvertPandas:
|
||||
def setup_method(self):
|
||||
self.rng = np.random.RandomState(seed=5)
|
||||
ints = self.rng.randint(1, 11, size=(3, 2))
|
||||
a = ["A", "B", "C"]
|
||||
b = ["D", "A", "E"]
|
||||
df = pd.DataFrame(ints, columns=["weight", "cost"])
|
||||
df[0] = a # Column label 0 (int)
|
||||
df["b"] = b # Column label 'b' (str)
|
||||
self.df = df
|
||||
|
||||
mdf = pd.DataFrame([[4, 16, "A", "D"]], columns=["weight", "cost", 0, "b"])
|
||||
# self.mdf = df.append(mdf)
|
||||
self.mdf = pd.concat([df, mdf])
|
||||
|
||||
def assert_equal(self, G1, G2):
|
||||
assert nodes_equal(G1.nodes, G2.nodes)
|
||||
assert edges_equal(G1.edges, G2.edges, need_data=False)
|
||||
|
||||
def test_from_edgelist_multi_attr(self):
|
||||
Gtrue = eg.Graph(
|
||||
[
|
||||
("E", "C", {"cost": 9, "weight": 10}),
|
||||
("B", "A", {"cost": 1, "weight": 7}),
|
||||
("A", "D", {"cost": 7, "weight": 4}),
|
||||
]
|
||||
)
|
||||
G = eg.from_pandas_edgelist(self.df, 0, "b", ["weight", "cost"])
|
||||
self.assert_equal(G, Gtrue)
|
||||
|
||||
def test_from_adjacency(self):
|
||||
Gtrue = eg.DiGraph(
|
||||
[
|
||||
("A", "B"),
|
||||
("B", "C"),
|
||||
]
|
||||
)
|
||||
data = {
|
||||
"A": {"A": 0, "B": 0, "C": 0},
|
||||
"B": {"A": 1, "B": 0, "C": 0},
|
||||
"C": {"A": 0, "B": 1, "C": 0},
|
||||
}
|
||||
dftrue = pd.DataFrame(data, dtype=np.intp)
|
||||
df = dftrue[["A", "C", "B"]]
|
||||
G = eg.from_pandas_adjacency(df, create_using=eg.DiGraph())
|
||||
self.assert_equal(G, Gtrue)
|
||||
|
||||
|
||||
class TestConvertScipy:
|
||||
def setup_method(self):
|
||||
self.G1 = eg.complete_graph(3)
|
||||
|
||||
def assert_equal(self, G1, G2):
|
||||
assert nodes_equal(G1.nodes, G2.nodes)
|
||||
assert edges_equal(G1.edges, G2.edges, need_data=False)
|
||||
|
||||
# skip if on python < 3.8
|
||||
@pytest.mark.skipif(
|
||||
sys.version_info < (3, 8), reason="requires python3.8 or higher"
|
||||
)
|
||||
def test_from_scipy(self):
|
||||
data = sp.sparse.csr_matrix([[0, 1, 1], [1, 0, 1], [1, 1, 0]])
|
||||
G = eg.from_scipy_sparse_matrix(data)
|
||||
self.assert_equal(self.G1, G)
|
||||
|
||||
|
||||
def test_from_edgelist():
|
||||
edgelist = [(0, 1), (1, 2)]
|
||||
G = eg.from_edgelist(edgelist)
|
||||
assert sorted((u, v) for u, v, _ in G.edges) == [(0, 1), (1, 2)]
|
||||
|
||||
|
||||
def test_from_dict_of_lists():
|
||||
d = {0: [1], 1: [2]}
|
||||
G = eg.to_easygraph_graph(d)
|
||||
assert sorted((u, v) for u, v, _ in G.edges) == [(0, 1), (1, 2)]
|
||||
|
||||
|
||||
def test_from_dict_of_dicts():
|
||||
d = {0: {1: {}}, 1: {2: {}}}
|
||||
G = eg.to_easygraph_graph(d)
|
||||
assert sorted((u, v) for u, v, _ in G.edges) == [(0, 1), (1, 2)]
|
||||
|
||||
|
||||
def test_from_numpy_array():
|
||||
G = eg.complete_graph(3)
|
||||
A = eg.to_numpy_array(G)
|
||||
G2 = eg.from_numpy_array(A)
|
||||
assert sorted((u, v) for u, v, _ in G.edges) == sorted(
|
||||
(u, v) for u, v, _ in G2.edges
|
||||
)
|
||||
|
||||
|
||||
def test_from_pandas_edgelist():
|
||||
df = pd.DataFrame({"source": [0, 1], "target": [1, 2], "weight": [0.5, 0.7]})
|
||||
G = eg.from_pandas_edgelist(df, source="source", target="target", edge_attr=True)
|
||||
assert sorted((u, v) for u, v, _ in G.edges) == [(0, 1), (1, 2)]
|
||||
|
||||
|
||||
def test_from_pandas_adjacency():
|
||||
df = pd.DataFrame([[0, 1], [1, 0]], columns=["A", "B"], index=["A", "B"])
|
||||
G = eg.from_pandas_adjacency(df)
|
||||
assert sorted((u, v) for u, v, _ in G.edges) == [("A", "B")]
|
||||
|
||||
|
||||
def test_from_scipy_sparse_matrix():
|
||||
mat = sp.sparse.csr_matrix([[0, 1, 0], [1, 0, 1], [0, 1, 0]])
|
||||
G = eg.from_scipy_sparse_matrix(mat)
|
||||
expected_edges = [(0, 1), (1, 2)]
|
||||
assert sorted((u, v) for u, v, _ in G.edges) == expected_edges
|
||||
|
||||
|
||||
def test_invalid_dict_type():
|
||||
class NotGraph:
|
||||
pass
|
||||
|
||||
with pytest.raises(eg.EasyGraphError):
|
||||
eg.to_easygraph_graph(NotGraph())
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# from pathlib import Path
|
||||
# from subprocess import run
|
||||
#
|
||||
#
|
||||
# script_test_cpp_easygraph_path = Path(__file__).parent / "script_test_cpp_easygraph.py"
|
||||
#
|
||||
#
|
||||
# def test_cpp_easygraph():
|
||||
# p = run(["python3", str(script_test_cpp_easygraph_path)])
|
||||
# assert p.returncode == 0
|
||||
@@ -0,0 +1,117 @@
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import easygraph as eg
|
||||
|
||||
|
||||
MOCK_HIF_DATA = {
|
||||
"metadata": {"name": "test_organism", "description": "Simulation for unit test"},
|
||||
"network-type": "directed",
|
||||
"nodes": [
|
||||
{"node": "n1", "weight": 1.0, "attrs": {"name": "Node A"}},
|
||||
{"node": "n2", "weight": 1.0, "attrs": {"name": "Node B"}},
|
||||
],
|
||||
"edges": [{"edge": "e1", "weight": 1.0, "attrs": {"name": "Edge Alpha"}}],
|
||||
"incidences": [
|
||||
{"edge": "e1", "node": "n1", "weight": 1.0, "direction": "tail"},
|
||||
{"edge": "e1", "node": "n2", "weight": 1.0, "direction": "head"},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
class HIFTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp_dir = tempfile.TemporaryDirectory()
|
||||
self.temp_dir_path = Path(self.temp_dir.name)
|
||||
|
||||
self.input_file = self.temp_dir_path / "input_mock.hif.json"
|
||||
self.output_file = self.temp_dir_path / "output_result.hif.json"
|
||||
|
||||
with open(self.input_file, "w", encoding="utf-8") as f:
|
||||
json.dump(MOCK_HIF_DATA, f)
|
||||
|
||||
def tearDown(self):
|
||||
self.temp_dir.cleanup()
|
||||
|
||||
def test_hif_roundtrip_preservation(self):
|
||||
"""
|
||||
Test that custom attributes are preserved AND the generated
|
||||
EasyGraph object is structurally valid.
|
||||
"""
|
||||
hg = eg.hif_to_hypergraph(filename=self.input_file)
|
||||
|
||||
self.assertEqual(hg.num_v, 2, "Loaded graph should have 2 nodes")
|
||||
self.assertEqual(hg.num_e, 1, "Loaded graph should have 1 edge")
|
||||
|
||||
node_names = [props.get("name") for props in hg.v_property]
|
||||
self.assertEqual(
|
||||
node_names,
|
||||
["Node A", "Node B"],
|
||||
"HIF attrs.name should be preserved in v_property",
|
||||
)
|
||||
|
||||
hif_node_ids = [node["node"] for node in hg.custom_hif_nodes]
|
||||
self.assertEqual(
|
||||
hif_node_ids,
|
||||
["n1", "n2"],
|
||||
"Original HIF node IDs should be preserved separately",
|
||||
)
|
||||
|
||||
edges = hg.e[0]
|
||||
self.assertEqual(len(edges), 1, "Should have 1 edge group")
|
||||
self.assertEqual(len(edges[0]), 2, "Edge e1 should connect 2 nodes")
|
||||
|
||||
self.assertTrue(
|
||||
hasattr(hg, "custom_hif_incidences"), "Failed to attach custom incidences"
|
||||
)
|
||||
self.assertTrue(hasattr(hg, "metadata"), "Failed to attach metadata")
|
||||
|
||||
eg.hypergraph_to_hif(hg, filename=self.output_file)
|
||||
|
||||
with open(self.output_file, "r", encoding="utf-8") as f:
|
||||
res = json.load(f)
|
||||
|
||||
output_node_ids = [node["node"] for node in res["nodes"]]
|
||||
self.assertEqual(
|
||||
output_node_ids,
|
||||
["n1", "n2"],
|
||||
"Original HIF node IDs should survive roundtrip export",
|
||||
)
|
||||
|
||||
first_incidence = res["incidences"][0]
|
||||
self.assertIn(
|
||||
"direction", first_incidence, "'direction' field lost in roundtrip"
|
||||
)
|
||||
self.assertIn(first_incidence["direction"], ["tail", "head"])
|
||||
|
||||
self.assertNotIn(
|
||||
"default_attrs",
|
||||
res["metadata"],
|
||||
"'default_attrs' was forced into metadata",
|
||||
)
|
||||
self.assertEqual(res["metadata"]["name"], "test_organism")
|
||||
|
||||
def test_manual_graph_export(self):
|
||||
"""Test exporting a manually created Hypergraph (not loaded from file)."""
|
||||
hg = eg.Hypergraph(
|
||||
num_v=5, e_list=[(0, 1, 2), (2, 3), (2, 3), (0, 4)], merge_op="sum"
|
||||
)
|
||||
hg.metadata = {"created_by": "manual_test"}
|
||||
|
||||
eg.hypergraph_to_hif(hg, filename=self.output_file)
|
||||
|
||||
with open(self.output_file, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
self.assertEqual(len(data["nodes"]), 5)
|
||||
self.assertEqual(len(data["edges"]), 3)
|
||||
self.assertEqual(data["metadata"]["created_by"], "manual_test")
|
||||
|
||||
weights = [e["weight"] for e in data["edges"]]
|
||||
self.assertIn(2.0, weights, "Merged edge weight should be 2.0")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user