Skip to content

Commit 38e2b66

Browse files
committed
add some tests
1 parent 96dbe9d commit 38e2b66

7 files changed

Lines changed: 672 additions & 35 deletions

tests/network_strategies.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
from hypothesis import strategies as st
2+
import reticula as ret
3+
4+
# Shared integer vertex type
5+
VERT = ret.int64
6+
TIME = ret.double
7+
8+
9+
@st.composite
10+
def undirected_network(draw: st.DrawFn,
11+
min_verts: int = 0, max_verts: int = 10,
12+
self_loops: bool = True):
13+
if min_verts < 0:
14+
raise ValueError("min_verts cannot be negative")
15+
if max_verts < min_verts:
16+
raise ValueError("min_verts should not exceed max_verts")
17+
verts = list(
18+
range(draw(st.integers(min_value=min_verts, max_value=max_verts))))
19+
max_edges = len(verts)*(len(verts)-1)//2
20+
if self_loops:
21+
max_edges += len(verts)
22+
edges = st.lists(
23+
st.tuples(
24+
st.integers(min_value=0, max_value=len(verts)),
25+
st.integers(min_value=0, max_value=len(verts))).filter(
26+
lambda e: e[0] != e[1] or self_loops),
27+
min_size=0, max_size=max_edges)
28+
return ret.undirected_network[VERT](draw(edges), verts)
29+
30+
31+
@st.composite
32+
def directed_network(draw: st.DrawFn,
33+
min_verts: int = 0, max_verts: int = 10,
34+
self_loops: bool = True):
35+
if min_verts < 0:
36+
raise ValueError("min_verts cannot be negative")
37+
if max_verts < min_verts:
38+
raise ValueError("min_verts should not exceed max_verts")
39+
verts = list(
40+
range(draw(st.integers(min_value=min_verts, max_value=max_verts))))
41+
max_edges = len(verts)*(len(verts)-1)
42+
if self_loops:
43+
max_edges += len(verts)
44+
edges = st.lists(
45+
st.tuples(
46+
st.integers(min_value=0, max_value=len(verts)),
47+
st.integers(min_value=0, max_value=len(verts))).filter(
48+
lambda e: e[0] != e[1] or self_loops),
49+
min_size=0, max_size=max_edges)
50+
return ret.directed_network[VERT](draw(edges), verts)
51+
52+
53+
@st.composite
54+
def undirected_hypernetwork(draw: st.DrawFn,
55+
min_verts: int = 0, max_verts: int = 10):
56+
if min_verts < 0:
57+
raise ValueError("min_verts cannot be negative")
58+
if max_verts < min_verts:
59+
raise ValueError("min_verts should not exceed max_verts")
60+
verts = list(
61+
range(draw(st.integers(min_value=min_verts, max_value=max_verts))))
62+
max_edges = len(verts)*2
63+
hyperedge = st.lists(st.integers(min_value=0, max_value=len(
64+
verts)), min_size=0, max_size=len(verts))
65+
edges = st.lists(hyperedge, min_size=0, max_size=max_edges)
66+
return ret.undirected_hypernetwork[VERT](draw(edges), verts)
67+
68+
69+
@st.composite
70+
def directed_hypernetwork(draw: st.DrawFn,
71+
min_verts: int = 0, max_verts: int = 10):
72+
if min_verts < 0:
73+
raise ValueError("min_verts cannot be negative")
74+
if max_verts < min_verts:
75+
raise ValueError("min_verts should not exceed max_verts")
76+
verts = list(
77+
range(draw(st.integers(min_value=min_verts, max_value=max_verts))))
78+
max_edges = len(verts)*2
79+
vertex_list = st.lists(st.integers(
80+
min_value=0, max_value=len(verts)), min_size=0, max_size=len(verts))
81+
edge = st.tuples(vertex_list, vertex_list)
82+
edges = st.lists(edge, min_size=0, max_size=max_edges)
83+
return ret.directed_hypernetwork[VERT](draw(edges), verts)
84+
85+
86+
@st.composite
87+
def undirected_temporal_network(draw: st.DrawFn,
88+
min_verts: int = 0, max_verts: int = 10,
89+
self_loops: bool = True):
90+
verts = list(
91+
range(draw(st.integers(min_value=min_verts, max_value=max_verts))))
92+
max_edges = len(verts)*(len(verts)-1)//2
93+
if self_loops:
94+
max_edges += len(verts)
95+
edge = st.tuples(
96+
st.integers(min_value=0, max_value=len(verts)),
97+
st.integers(min_value=0, max_value=len(verts)),
98+
st.floats(min_value=0, max_value=10)).filter(
99+
lambda e: e[0] != e[1] or self_loops)
100+
edges = st.lists(edge, min_size=0, max_size=max_edges)
101+
return ret.undirected_temporal_network[VERT, TIME](draw(edges), verts)
102+
103+
104+
@st.composite
105+
def directed_temporal_network(draw: st.DrawFn,
106+
min_verts: int = 0, max_verts: int = 10,
107+
self_loops: bool = True):
108+
verts = list(
109+
range(draw(st.integers(min_value=min_verts, max_value=max_verts))))
110+
max_edges = len(verts)*(len(verts)-1)
111+
if self_loops:
112+
max_edges += len(verts)
113+
edge = st.tuples(
114+
st.integers(min_value=0, max_value=len(verts)),
115+
st.integers(min_value=0, max_value=len(verts)),
116+
st.floats(min_value=0, max_value=10)).filter(
117+
lambda e: e[0] != e[1] or self_loops)
118+
edges = st.lists(edge, min_size=0, max_size=max_edges)
119+
return ret.directed_temporal_network[VERT, TIME](draw(edges), verts)
120+
121+
122+
@st.composite
123+
def undirected_temporal_hypernetwork(draw: st.DrawFn,
124+
min_verts: int = 0, max_verts: int = 10):
125+
verts = list(
126+
range(draw(st.integers(min_value=min_verts, max_value=max_verts))))
127+
max_edges = len(verts)*2
128+
hyperedge = st.tuples(
129+
st.lists(st.integers(min_value=0, max_value=len(verts)),
130+
min_size=0, max_size=len(verts)),
131+
st.floats(min_value=0, max_value=10))
132+
edges = st.lists(hyperedge, min_size=0, max_size=max_edges)
133+
return ret.undirected_temporal_hypernetwork[VERT, TIME](draw(edges), verts)
134+
135+
136+
@st.composite
137+
def directed_temporal_hypernetwork(draw: st.DrawFn,
138+
min_verts: int = 0, max_verts: int = 10):
139+
verts = list(
140+
range(draw(st.integers(min_value=min_verts, max_value=max_verts))))
141+
max_edges = len(verts)*2
142+
vertex_list = st.lists(st.integers(
143+
min_value=0, max_value=len(verts)), min_size=0, max_size=len(verts))
144+
hyperedge = st.tuples(vertex_list, vertex_list,
145+
st.floats(min_value=0, max_value=10))
146+
edges = st.lists(hyperedge, min_size=0, max_size=max_edges)
147+
return ret.directed_temporal_hypernetwork[VERT, TIME](draw(edges), verts)

tests/test_algorithms.py

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import math
2+
3+
import pytest
4+
import reticula as ret
5+
from hypothesis import given
6+
from network_strategies import undirected_network, directed_network
7+
8+
9+
def test_density():
10+
g = ret.complete_graph[ret.int64](size=4)
11+
assert ret.density(g) == 1.0
12+
13+
g = ret.undirected_network[ret.int64](edges=[], verts=range(4))
14+
assert ret.density(g) == 0.0
15+
16+
g = ret.path_graph[ret.int64](size=4)
17+
expected_density = 3 / 6
18+
assert ret.density(g) == pytest.approx(expected_density)
19+
20+
21+
def test_is_connected():
22+
g = ret.path_graph[ret.int64](size=4)
23+
assert ret.is_connected(g)
24+
25+
g = ret.undirected_network[ret.int64]([(0, 1), (2, 3)])
26+
assert not ret.is_connected(g)
27+
28+
g = ret.undirected_network[ret.int64]([], [0])
29+
assert ret.is_connected(g)
30+
31+
g = ret.undirected_network[ret.int64]()
32+
assert ret.is_connected(g)
33+
34+
35+
def test_is_weakly_connected():
36+
g = ret.directed_network[ret.int64]([(0, 1), (1, 2)])
37+
assert ret.is_weakly_connected(g)
38+
39+
g = ret.directed_network[ret.int64]([(0, 1), (2, 3)])
40+
assert not ret.is_weakly_connected(g)
41+
42+
43+
def test_is_acyclic():
44+
g = ret.directed_network[ret.int64]([(0, 1), (0, 2), (1, 3), (2, 3)])
45+
assert ret.is_acyclic(g)
46+
47+
g = ret.directed_network[ret.int64]([(0, 1), (1, 2), (2, 0)])
48+
assert not ret.is_acyclic(g)
49+
50+
51+
def test_topological_order():
52+
g = ret.directed_network[ret.int64]([(0, 1), (0, 2), (1, 3), (2, 3)])
53+
order = ret.topological_order(g)
54+
assert len(order) == 4
55+
assert order.index(0) < order.index(1)
56+
assert order.index(0) < order.index(2)
57+
assert order.index(1) < order.index(3)
58+
assert order.index(2) < order.index(3)
59+
60+
g = ret.directed_network[ret.int64]([(0, 1), (1, 2), (2, 0)])
61+
pytest.raises(ValueError, ret.topological_order, g)
62+
63+
64+
def test_shortest_path_lengths():
65+
g = ret.path_graph[ret.int64](size=4)
66+
67+
distances_from = ret.shortest_path_lengths_from(g, 0)
68+
assert distances_from[0] == 0
69+
assert distances_from[1] == 1
70+
assert distances_from[2] == 2
71+
assert distances_from[3] == 3
72+
assert ret.shortest_path_lengths_from(g, 12) == {12: 0}
73+
74+
distances_to = ret.shortest_path_lengths_to(g, 3)
75+
assert distances_to[0] == 3
76+
assert distances_to[1] == 2
77+
assert distances_to[2] == 1
78+
assert distances_to[3] == 0
79+
assert ret.shortest_path_lengths_to(g, 12) == {12: 0}
80+
81+
82+
def test_is_reachable():
83+
g = ret.path_graph[ret.int64](size=4)
84+
assert ret.is_reachable(g, 0, 3)
85+
assert ret.is_reachable(g, 3, 0)
86+
87+
g = ret.undirected_network[ret.int64]([(0, 1), (2, 3)])
88+
assert not ret.is_reachable(g, 0, 2)
89+
90+
91+
def test_component_functions():
92+
g = ret.undirected_network[ret.int64]([(0, 1), (2, 3), (4, 5), (5, 6)])
93+
94+
comp = ret.connected_component(g, 0)
95+
assert set(comp) == {0, 1}
96+
assert len(comp) == 2
97+
98+
comp = ret.largest_connected_component(g)
99+
assert set(comp) == {4, 5, 6}
100+
101+
all_comps = ret.connected_components(g)
102+
assert len(all_comps) == 3
103+
104+
105+
def test_weakly_connected_components():
106+
g = ret.directed_network[ret.int64]([(0, 1), (2, 3)])
107+
wccs = ret.weakly_connected_components(g)
108+
assert len(wccs) == 2
109+
110+
wcc_sets = [set(comp) for comp in wccs]
111+
assert {0, 1} in wcc_sets
112+
assert {2, 3} in wcc_sets
113+
114+
lwcc = ret.largest_weakly_connected_component(g)
115+
assert len(lwcc) == 2
116+
117+
118+
def test_in_out_components():
119+
g = ret.directed_network[ret.int64]([(0, 1), (1, 2), (3, 1)])
120+
121+
in_comp = ret.in_component(g, 1)
122+
assert 0 in in_comp
123+
assert 3 in in_comp
124+
125+
out_comp = ret.out_component(g, 1)
126+
assert 2 in out_comp
127+
128+
in_sizes = ret.in_component_sizes(g)
129+
assert len(in_sizes) == len(g.vertices())
130+
131+
out_sizes = ret.out_component_sizes(g)
132+
assert len(out_sizes) == len(g.vertices())
133+
134+
135+
def test_degree_sequence():
136+
g = ret.undirected_network[ret.int64]([(0, 1), (1, 2), (2, 3)])
137+
138+
deg_seq = ret.degree_sequence(g)
139+
assert set(deg_seq) == {1, 2, 2, 1}
140+
141+
g = ret.directed_network[ret.int64]([(0, 1), (0, 2), (1, 2)])
142+
in_deg_seq = ret.in_degree_sequence(g)
143+
assert set(in_deg_seq) == {0, 1, 2}
144+
145+
out_deg_seq = ret.out_degree_sequence(g)
146+
assert set(out_deg_seq) == {2, 1, 0}
147+
148+
in_out_deg_seq = ret.in_out_degree_pair_sequence(g)
149+
assert set(in_out_deg_seq) == {(0, 2), (1, 1), (2, 0)}
150+
151+
inc_deg_seq = ret.incident_degree_sequence(g)
152+
assert set(inc_deg_seq) == {2, 2, 2}
153+
154+
155+
@given(undirected_network())
156+
def test_degree_properties(net):
157+
for v in net.vertices():
158+
deg = ret.degree(net, v)
159+
assert deg >= 0
160+
assert deg == len([e for e in net.edges() if v in e.incident_verts()])
161+
162+
163+
@given(directed_network())
164+
def test_directed_degree_properties(net):
165+
for v in net.vertices():
166+
in_deg = ret.in_degree(net, v)
167+
out_deg = ret.out_degree(net, v)
168+
assert in_deg >= 0
169+
assert out_deg >= 0
170+
171+
172+
def test_attribute_assortativity():
173+
g = ret.undirected_network[ret.int64]([(0, 1), (1, 2), (2, 3)])
174+
attrs = {0: 1.0, 1: 2.0, 2: 2.0, 3: 1.0}
175+
176+
r = ret.attribute_assortativity(g, attrs, 0.0)
177+
assert isinstance(r, float)
178+
assert r == pytest.approx(-0.5)
179+
180+
r = ret.attribute_assortativity(g, {}, 1.0)
181+
assert math.isnan(r)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import pytest
2+
3+
import reticula as ret
4+
5+
6+
def test_temporal_clusters():
7+
edges = [(0, 1, 1.0), (1, 2, 1.5), (2, 3, 2.0), (3, 4, 3.0)]
8+
net = ret.directed_temporal_network[ret.int64, ret.double](edges)
9+
adj = ret.temporal_adjacency.simple[net.edge_type()]()
10+
11+
e = net.edge_type()(1, 2, 1.5)
12+
13+
in_clusters = ret.in_clusters(net, adj)
14+
assert dict(in_clusters)[e].volume() == 2
15+
16+
out_clusters = ret.out_clusters(net, adj)
17+
assert dict(out_clusters)[e].volume() == 3
18+
19+
20+
def test_cluster_functions():
21+
edges = [(0, 1, 1.0), (1, 2, 1.5), (2, 3, 2.0), (3, 4, 3.0)]
22+
net = ret.directed_temporal_network[ret.int64, ret.double](edges)
23+
adj = ret.temporal_adjacency.simple[net.edge_type()]()
24+
25+
in_cluster = ret.in_cluster(net, adj, 2, 1.5)
26+
assert in_cluster.volume() == 1
27+
28+
out_cluster = ret.out_cluster(net, adj, 1, 1.5)
29+
assert out_cluster.volume() == 1
30+
31+
32+
def test_cluster_sizes():
33+
edges = [(0, 1, 1.0), (1, 2, 1.5), (2, 3, 2.0), (3, 4, 3.0)]
34+
net = ret.directed_temporal_network[ret.int64, ret.double](edges)
35+
adj = ret.temporal_adjacency.simple[net.edge_type()]()
36+
37+
e = net.edge_type()(1, 2, 1.5)
38+
39+
in_sizes = ret.in_cluster_sizes(net, adj)
40+
assert dict(in_sizes)[e].volume() == 2
41+
42+
out_sizes = ret.out_cluster_sizes(net, adj)
43+
assert dict(out_sizes)[e].volume() == 3
44+
45+
46+
def test_cluster_size_estimates():
47+
edges = [(0, 1, 1.0), (1, 2, 1.5), (2, 3, 2.0), (3, 4, 3.0)]
48+
net = ret.directed_temporal_network[ret.int64, ret.double](edges)
49+
adj = ret.temporal_adjacency.simple[net.edge_type()]()
50+
51+
e = net.edge_type()(1, 2, 1.5)
52+
53+
in_sizes = ret.in_cluster_sizes(net, adj)
54+
assert dict(in_sizes)[e].volume() == pytest.approx(2, rel=0.1)
55+
56+
out_sizes = ret.out_cluster_sizes(net, adj)
57+
assert dict(out_sizes)[e].volume() == pytest.approx(3, rel=0.1)

0 commit comments

Comments
 (0)