def test_permute(): """ Test permuting a graph to be equal to another, or permuting back and forth to stay equal to itself. """ g1 = tg.TinyGraph(5, np.int32, vp_types={'color': np.int32}, ep_types={'color2': np.int32}) g2 = tg.TinyGraph(5, np.int32, vp_types={'color': np.int32}, ep_types={'color2': np.int32}) g1[0, 1] = 5 g2[3, 4] = 5 g1[2, 3] = 1 g2[1, 2] = 1 g1.v['color'][0] = 10 g2.v['color'][4] = 10 g1.e['color2'][2, 3] = 4 g2.e['color2'][2, 1] = 4 pG11 = permute(g1, [3, 4, 1, 2, 0]) pG12 = permute(g1, [4, 3, 1, 2, 0]) pG13 = permute(pG11, [4, 2, 3, 0, 1]) assert not graph_equality(g2, pG11) assert graph_equality(g2, pG12) assert graph_equality(g1, pG13)
def test_merge_empty(): """Merge two empty graphs""" g1 = tg.TinyGraph(0, np.bool) g2 = tg.TinyGraph(0, np.bool) gg = merge(g1, g2) gg2 = merge(g2, g1) assert 0 == gg.vert_N assert 0 == gg2.vert_N
def test_merge_prop_types(): """ Check for desired behavior in case of mismatched property types (both for global types and vertex/edge proprty types) """ g1 = tg.TinyGraph(2, np.int32, vp_types={'name': np.dtype('<U10')}, ep_types={'length': np.double}) g1.v['name'][:] = ['aaa', 'baa'] g1.props['name'] = 'base' g1[0, 1] = 1 g1.e['length'][0, 1] = 2.2 g2 = tg.TinyGraph(2, np.double, vp_types={}, ep_types={'length': np.double}) with pytest.raises(TypeError): merge(g1, g2) g3 = tg.TinyGraph(2, np.int32, vp_types={}, ep_types={'length': np.int}) g3.props['name'] = 'secondary' g3[0, 1] = 2 g3.e['length'][0, 1] = 4 with pytest.raises(TypeError): merge(g1, g3) g4 = tg.TinyGraph(2, np.int32, vp_types={}, ep_types={'length': np.double}) g4.props['name'] = 'secondary' g4[0, 1] = 2 g4.e['length'][0, 1] = 4.0 with pytest.warns(UserWarning): gg = merge(g1, g4) assert np.array_equal(gg.v['name'], ['aaa', 'baa', '', '']) g5 = tg.TinyGraph(2, np.int32, vp_types={'name': np.dtype('<U10')}, ep_types={'length': np.double}) g5.props['name'] = 'secondary' g5.v['name'][:] = ['hello', 'world'] g5[0, 1] = 2 g5.e['length'][0, 1] = 4.0 gh = merge(g1, g5) assert gh.props['name'] == 'base' assert np.array_equal(gh.v['name'], ['aaa', 'baa', 'hello', 'world']) assert gh.e['length'][0, 1] == g1.e['length'][0, 1] assert gh.e['length'][2, 3] == g5.e['length'][0, 1]
def test_misbehavior(): """ Tests for illegal behavior handling """ g = tg.TinyGraph(3,vp_types = {"color": np.int32},\ ep_types = {"width": np.int32}) with pytest.raises(KeyError,match='Expecting exactly two endpoints.'): g[0] = 3 with pytest.raises(KeyError, match='Expecting exactly two endpoints.'): g[0,1,2] = 3 with pytest.raises(IndexError, match='Self-loops are not allowed.'): g[1,1] = 3 with pytest.raises(KeyError,match='Expecting exactly two endpoints.'): e = g[0] with pytest.raises(KeyError, match='Expecting exactly two endpoints.'): e = g[0,1,2] with pytest.raises(KeyError,match='Expecting exactly two endpoints.'): g.e['width'][0] = 3 with pytest.raises(KeyError, match='Expecting exactly two endpoints.'): g.e['width'][0,1,2] = 3 with pytest.raises(KeyError,match='Expecting exactly two endpoints.'): e = g.e["width"][0] with pytest.raises(KeyError, match='Expecting exactly two endpoints.'): e = g.e['width'][0,1,2] with pytest.raises(IndexError): g.v['color'][0,2] = 1 with pytest.raises(IndexError): v = g.v['color'][0,2]
def test_edges(): """ Simple test of getting edge properties. """ g = tg.TinyGraph(5, np.int32, ep_types = {'color': np.int32, 'elem': np.bool}) g[0,4] = 10 g[1,0] = 20 g[2,1] = 30 g[3,2] = 40 g[4,3] = 50 g.e['color'][0,1] = 3 g.e['color'][1,2] = 4 g.e['color'][2,3] = 5 g.e['color'][3,4] = 6 g.e['color'][4,0] = 1 g.e['elem'][4,0] = True g.e['elem'][0,1] = False g.e['elem'][1,2] = True g.e['elem'][2,3] = False g.e['elem'][3,4] = True weights = {(0,4):10,(0,1):20,(1,2):30,(2,3):40,(3,4):50} elem = {(0,4):True,(0,1):False,(1,2):True,(2,3):False,(3,4):True} assert len(g.edges(edge_props={})[0]) == 3 for i, j, w in g.edges(weight = True): assert w == weights[(i,j)] for i, j, d in g.edges(edge_props = ['elem']): assert len(d) == 1 assert d['elem'] == elem[(i,j)]
def test_cycles_empty(): """ An empty graph has no vertices to be in cycles. """ g = tg.TinyGraph(0) assert algs.get_min_cycles(g) == []
def test_remove_edge(): """ Simple test of removing edges. """ g = tg.TinyGraph(5, np.int32, ep_types = {'color': np.int32, 'elem': np.bool}) g[0,4] = 10 g[1,0] = 20 g[2,1] = 30 g[3,2] = 40 g[4,3] = 50 g.e['color'][0,1] = 3 g.e['color'][1,2] = 4 g.e['color'][2,3] = 5 g.e['color'][3,4] = 6 g.e['color'][4,0] = 1 g[0,4] = 0 g[1,0] = 0 with pytest.raises(IndexError, match='No such edge'): g.e['color'][1,4] = 6 with pytest.raises(IndexError, match='No such edge.'): g.e['color'][0,1] assert g.e['color'][1,2] == 4 assert g.e['color'][2,3] == 5 assert g.e['color'][3,4] == 6 with pytest.raises(IndexError, match='No such edge.'): g.e['color'][4,0]
def test_permute_error_handling(): """Demonstrate behavior in case not handed a proper permutation""" g1 = tg.TinyGraph(5, np.int32, vp_types={'color': np.int32}, ep_types={'color2': np.int32}) g1[0, 1] = 5 g1[1, 3] = 3 g1[2, 3] = 1 g1[0, 4] = 2 g1.v['color'][0] = 10 g1.e['color2'][2, 3] = 4 bad_perm_1 = [0] bad_perm_2 = [1, 3, 3, 4, 4] bad_perm_3 = [1, 3, 3, 4, 4, 2, 2, 1] bad_perm_4 = [-5, 0, 3, 3, 4, 6] # Is this behavior we want? with pytest.raises(IndexError): bg1 = permute(g1, bad_perm_1) with pytest.raises(IndexError): bg2 = permute(g1, bad_perm_2) with pytest.raises(IndexError): bg3 = permute(g1, bad_perm_3) with pytest.raises(IndexError): bg4 = permute(g1, bad_perm_4)
def test_merge_simple(): """Merge two graphs, each with two vertices and no properties""" g1 = tg.TinyGraph(2) g1[0, 1] = 2 g2 = tg.TinyGraph(2) g2[0, 1] = 3 gg = merge(g1, g2) result = np.zeros((4, 4), dtype=np.int32) result[0, 1] = 2 result[1, 0] = 2 result[2, 3] = 3 result[3, 2] = 3 assert gg.vert_N == 4 assert np.array_equal(gg.adjacency, result)
def test_graph_props(): """ Simple tests of per-graph properties """ g1 = tg.TinyGraph(10) g1.props['foo'] = 'bar' g2 = tg.TinyGraph(10) g2.props['foo'] = 'bar' assert tg.util.graph_equality(g1, g2) g2.props['baz'] = 7 assert not tg.util.graph_equality(g1, g2)
def test_cc_empty(): """ Empty graph has no connected components. """ g = tg.TinyGraph(0) assert algs.get_connected_components(g) == [] assert algs.is_connected(g) == False
def test_bad_edge_subset(): g = tg.TinyGraph(2, vp_types={'color': np.int}) with pytest.raises(KeyError): ng = tg.io.to_nx(g, weight_prop='weight', name_prop='name', vp_subset=['color'], ep_subset=['friends']) # no friends :/
def test_bad_vertex_subset(): """Ensure a KeyError is raised on an invalid subset request""" t = tg.TinyGraph(3, ep_types={'weight': np.int}) t[0, 1] = 2 t[1, 2] = 4 with pytest.raises(KeyError): ng = tg.io.to_nx(t, vp_subset=['color'])
def test_vanishing_edge(): """Current behavior is for 0-weighted edges to vanish""" t = tg.TinyGraph(2) t[0, 1] = 0 ng = tg.io.to_nx(t) t2 = tg.io.from_nx(ng) assert np.all(t2.adjacency == 0)
def test_paths_fully_connected_with_path(): """ Test shortest paths on a fully connected graph. """ g = tg.TinyGraph(5) g[0, 1] = 1 g[1, 2] = 1 g[2, 3] = 1 g[3, 4] = 1 g[4, 0] = 1 dists, next = algs.get_shortest_paths(g, True, True) paths = algs.construct_all_shortest_paths(next) for i in paths: for j in i: print(list(j)) np.testing.assert_equal(dists,\ np.array([[0,1,2,2,1],\ [1,0,1,2,2],\ [2,1,0,1,2],\ [2,2,1,0,1],\ [1,2,2,1,0]],dtype=np.float64)) np.testing.assert_equal(next,\ np.array([[0,1,1,4,4],\ [0,1,2,2,0],\ [1,1,2,3,3],\ [4,2,2,3,4],\ [0,0,3,3,4]],dtype=np.float64)) np.testing.assert_equal(paths,\ np.array([[[0,np.inf,np.inf,np.inf,np.inf],\ [0,1,np.inf,np.inf,np.inf],\ [0,1,2,np.inf,np.inf],\ [0,4,3,np.inf,np.inf],\ [0,4,np.inf,np.inf,np.inf]],\ [[1,0,np.inf,np.inf,np.inf],\ [1,np.inf,np.inf,np.inf,np.inf],\ [1,2,np.inf,np.inf,np.inf],\ [1,2,3,np.inf,np.inf],\ [1,0,4,np.inf,np.inf]],\ [[2,1,0,np.inf,np.inf],\ [2,1,np.inf,np.inf,np.inf],\ [2,np.inf,np.inf,np.inf,np.inf],\ [2,3,np.inf,np.inf,np.inf],\ [2,3,4,np.inf,np.inf]],\ [[3,4,0,np.inf,np.inf],\ [3,2,1,np.inf,np.inf],\ [3,2,np.inf,np.inf,np.inf],\ [3,np.inf,np.inf,np.inf,np.inf],\ [3,4,np.inf,np.inf,np.inf]],\ [[4,0,np.inf,np.inf,np.inf],\ [4,0,1,np.inf,np.inf],\ [4,3,2,np.inf,np.inf],\ [4,3,np.inf,np.inf,np.inf],\ [4,np.inf,np.inf,np.inf,np.inf]]],dtype=np.float64))
def test_merge_identity(): """Merge with an empty graph""" g1 = graph_test_suite.gen_random(5, np.bool, [True], 0.5) g2 = tg.TinyGraph(0, np.bool) gg = merge(g1, g2) gh = merge(g2, g1) assert graph_equality(g1, gg) assert graph_equality(g1, gh)
def test_items(): t = tg.TinyGraph(3, vp_types={'name': np.str}) t.v['name'][0] = 'a' t.v['name'][1] = 'b' t.v['name'][2] = 'c' assert(t.v['name'][0] == 'a') assert(t.v['name'][1] == 'b') assert(t.v['name'][2] == 'c')
def test_subgraph_error(): """Check error-handling for out-of-bounds stuff""" vertices = set([0, 1, 12]) g = tg.TinyGraph(5) with pytest.raises(IndexError): sg = subgraph(g, vertices) vertices2 = set([-1, 0, 3, 4]) with pytest.raises(IndexError): sg2 = subgraph(g, vertices2)
def test_create_graphs_types(): """ Simple tests to try creating graphs of various dtypes """ g1_bool = tg.TinyGraph(5, np.bool) g1_bool[3, 2] = True assert g1_bool[2, 3] == True assert g1_bool[3, 2] == True g1_int32 = tg.TinyGraph(5, np.int32) g1_int32[3, 2] = 7 assert g1_int32[3, 2] == 7 assert g1_int32[2, 3] == 7 g1_float64 = tg.TinyGraph(5, np.float64) g1_float64[3, 2] = 3.14 assert g1_float64[3, 2] == 3.14 assert g1_float64[2, 3] == 3.14
def test_paths_empty(): """ Test shortest paths on an empty graph. """ g = tg.TinyGraph(0, adj_type=np.bool) with pytest.raises(TypeError, match='Graph weights are not numbers.'): algs.get_shortest_paths(g, True) np.testing.assert_equal(algs.get_shortest_paths(g, False), np.zeros((0, 0), dtype=np.float64))
def _subgraph_relabel(g, vert_iter): """ Helper function to perform the work of permute and subgraph. Not intended for use by end-users. See instead functions permute and subgraph below. Returns the subgraph of g induced by vert_iter (as the set of vertices), maintaining the ordering of vert_iter in constructing the new subgraph. Inputs: g (TinyGraph): Original Tinygraph vert_iter (iterable): iterable containing indices of vertices to take as the vertices of the subgraph. Contrary to permute(), here we expect vert_iter[new_vertex] = old_vertex to support dropping (and possibly duplicating) old vertices. Outputs: sg (TinyGraph): subgraph with vertices in the same order as vert_iter. """ N = len(vert_iter) new_g = tg.TinyGraph(N, g.adjacency.dtype, {p: val.dtype for p, val in g.v.items()}, {p: val.dtype for p, val in g.e.items()}) # Copy graph props # new_g.props = {k:v for k, v in g.props.items()} new_g.props = deepcopy(g.props) if N == 0: # Weird things happen if we keep going ;_; return new_g # Magic index converter eliminates python-for-loops # at the expense of ignoring sparsity (moves everything) new_list = np.arange(N) # list of the new vertices #vert_iter is a list of old vertices ordered by destination indices old_indices = (np.repeat(new_list, N), np.tile(new_list, N)) new_indices = (np.repeat(vert_iter, N), np.tile(vert_iter, N)) # Copy edge values new_g.adjacency[old_indices] = g.adjacency[new_indices] # Copy vertex properties for prop in g.v.keys(): new_g.v[prop][:] = g.v[prop][vert_iter] # Copy edge properties for prop in g.e.keys(): new_g.e_p[prop][old_indices] = g.e_p[prop][new_indices] return new_g
def test_permute_dict(): """Pass the permutation as a dictionary and ensure the permutation operates in the desired direction""" perm = [2, 3, 1, 0] # perm_dict = dict(zip(range(4), perm)) # purposely written out of order perm_dict = {3: 0, 0: 2, 2: 1, 1: 3} g = tg.TinyGraph(4, np.bool, vp_types={'name': np.dtype('<U20')}) g.v['name'][:] = ['a', 'b', 'c', 'd'] h = permute(g, perm_dict) assert list(h.v['name']) == ['d', 'c', 'a', 'b']
def test_negative_cycles(): """ Test shortest path on a graph with a negative cycle (expect raise error). """ g = tg.TinyGraph(3) g[0, 1] = 1 g[1, 2] = -5 g[2, 0] = 1 with pytest.raises(Exception, match='Graph has a negative cycle.'): algs.get_shortest_paths(g, True) assert np.array_equal( algs.get_shortest_paths(g, False), np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]], dtype=np.float64))
def test_cc_one_comp(): """ Test graph with one connected component. """ g = tg.TinyGraph(5) g[0, 1] = 1 g[0, 2] = 1 g[2, 3] = 1 g[3, 4] = 1 assert algs.get_connected_components(g) == [ set(range(5)), ] assert algs.is_connected(g) == True
def test_cycles_medium(): """ Test some medium sized graphs with various sized cycles. """ g1 = tg.TinyGraph(6) g1[0, 1] = 1 g1[0, 2] = 1 g1[1, 2] = 1 g1[0, 3] = 1 g1[0, 5] = 1 g1[3, 4] = 1 g1[4, 5] = 1 assert algs.get_min_cycles(g1) == [{0, 1, 2}, {0, 1, 2}, {0, 1, 2},\ {0, 3, 4, 5},{0, 3, 4, 5},{0, 3, 4, 5}] g2 = tg.TinyGraph(9) g2[0, 8] = 1 g2[7, 8] = 1 g2[6, 8] = 1 g2[3, 7] = 1 g2[3, 6] = 1 g2[2, 3] = 1 g2[5, 6] = 1 g2[1, 2] = 1 g2[1, 4] = 1 g2[4, 5] = 1 assert algs.get_min_cycles(g2) == [set(), \ {1, 2, 3, 4, 5, 6}, \ {1, 2, 3, 4, 5, 6}, \ {3, 7, 8, 6}, \ {1, 2, 3, 4, 5, 6}, \ {1, 2, 3, 4, 5, 6}, \ {3, 7, 8, 6}, \ {3, 7, 8, 6}, \ {3, 7, 8, 6} ]
def test_cc_multi_comp(): """ Test graph with mulitple connected components. """ g = tg.TinyGraph(6) g[0, 1] = 1 g[0, 2] = 1 g[1, 2] = 1 g[3, 4] = 1 g[4, 5] = 1 assert algs.get_connected_components(g) == [ set(range(3)), set(range(3, 6)) ] assert algs.is_connected(g) == False
def test_triangle(): """Makes a cycle graph and checks for perservation of the adjacency matrix""" t = tg.TinyGraph(3, vp_types={'name': np.str}) t[0, 1] = 1 t[1, 2] = 1.1 t[2, 0] = 3 t.v['name'][0] = 'a' t.v['name'][1] = 'b' t.v['name'][2] = 'c' ng = tg.io.to_nx(t, weight_prop='weight') t2 = tg.io.from_nx(ng, weight_prop='weight', vp_types={'name': np.str}) assert tg.util.graph_equality(t, t2)
def test_permute_path(): """Another case to ensure permutations work in the expected direction""" g = tg.TinyGraph(4, np.bool, vp_types={'name': np.dtype('<U20')}) perm = [2, 3, 1, 0] inv_perm = np.argsort(perm) # [3, 2, 0, 1] assert np.array_equal(inv_perm, [3, 2, 0, 1]) g.v['name'][:] = ['a', 'b', 'c', 'd'] g[0, 1] = 1 g[1, 2] = 1 g[2, 3] = 1 h = permute(g, perm) assert list(h.v['name']) == ['d', 'c', 'a', 'b'] gp = permute(h, inv_perm) assert graph_equality(g, gp)
def gen_random(N, dtype, edge_weights, prob_edge, rng=None): """ Generate a random graph of the given dtype with a fixed edge probability. 0 = empty graph, 1.0 = full graph, possible edge weight values from edge_weights """ if rng is None: rng = np.random.RandomState() g = tg.TinyGraph(N, dtype) for i in range(N): for j in range(i + 1, N): if rng.rand() < prob_edge: g[i, j] = rng.choice(edge_weights) return g
def test_permute_identity(): """Ensure that the identity permutation preserves graph equality""" g1 = tg.TinyGraph(5, np.int32, vp_types={'color': np.int32}, ep_types={'color2': np.int32}) g1[0, 1] = 5 g1[1, 3] = 3 g1[2, 3] = 1 g1[0, 4] = 2 g1.v['color'][0] = 10 g1.e['color2'][2, 3] = 4 g2 = permute(g1, [0, 1, 2, 3, 4]) assert graph_equality(g1, g2)