def test_filter_squash_diamond(): r"""Test that diamond edges are collapsed when squashing. Ensure we can handle the most basic DAG. a / \ remove bc a b c ----------> | \ / d d """ d = Node(Frame(name="d")) check_filter_squash( GraphFrame.from_lists(("a", ("b", d), ("c", d))), lambda row: row["node"].frame["name"] not in ("b", "c"), Graph.from_lists(("a", "d")), [2, 1], # a, d ) check_filter_no_squash( GraphFrame.from_lists(("a", ("b", d), ("c", d))), lambda row: row["node"].frame["name"] not in ("b", "c"), 2, # a, d )
def test_filter_squash_bunny(): r"""Test squash on a complicated "bunny" shaped graph. This has multiple roots as well as multiple parents that themselves have parents. e g / \ / \ f a h remove abc e g / \ -----------> / \ / \ b c f d h \ / d """ d = Node(Frame(name="d")) diamond = Node.from_lists(("a", ("b", d), ("c", d))) new_d = Node(Frame(name="d")) check_filter_squash( GraphFrame.from_lists(("e", "f", diamond), ("g", diamond, "h")), lambda row: row["node"].frame["name"] not in ("a", "b", "c"), Graph.from_lists(("e", new_d, "f"), ("g", new_d, "h")), [3, 1, 1, 3, 1], # e, d, f, g, h ) check_filter_no_squash( GraphFrame.from_lists(("e", "f", diamond), ("g", diamond, "h")), lambda row: row["node"].frame["name"] not in ("a", "b", "c"), 5, # e, d, f, g, h )
def test_filter_squash_with_rootless_merge(): r"""Test squash on a simple tree with several rootless node merges. a ___/ | \___ remove abcd b c d ------------> e f g /|\ /|\ /|\ e f g e f g e f g Note that here, because b and d have been removed, a will have only one child called c, which will contain merged (summed) data from the original c rows. """ check_filter_squash( GraphFrame.from_lists( ("a", ("b", "e", "f", "g"), ("c", "e", "f", "g"), ("d", "e", "f", "g")) ), lambda row: row["node"].frame["name"] not in ("a", "b", "c", "d"), Graph.from_lists(["e"], ["f"], ["g"]), [3, 3, 3], # e, f, g ) check_filter_no_squash( GraphFrame.from_lists( ("a", ("b", "e", "f", "g"), ("c", "e", "f", "g"), ("d", "e", "f", "g")) ), lambda row: row["node"].frame["name"] not in ("a", "b", "c", "d"), 9, # e, f, g, e, f, g, e, f, g )
def test_filter_squash_bunny_to_goat_with_merge(): r"""Test squash on a "bunny" shaped graph: This one is more complex because there are more transitive edges to maintain between the roots (e, g) and b and c. e g / \ / \ f a h remove ac e g / \ ----------> / \ / \ b c f b h \ / b """ b = Node(Frame(name="b")) diamond = Node.from_lists(("a", ("b", b), ("c", b))) new_b = Node(Frame(name="b")) check_filter_squash( GraphFrame.from_lists(("e", "f", diamond), ("g", diamond, "h")), lambda row: row["node"].frame["name"] not in ("a", "c"), Graph.from_lists(("e", new_b, "f"), ("g", new_b, "h")), [4, 2, 1, 4, 1], # e, b, f, g, h ) check_filter_no_squash( GraphFrame.from_lists(("e", "f", diamond), ("g", diamond, "h")), lambda row: row["node"].frame["name"] not in ("a", "c"), 5, # e, b, f, g, h )
def test_filter_squash_bunny_to_goat(): r"""Test squash on a "bunny" shaped graph: This one is more complex because there are more transitive edges to maintain between the roots (e, g) and b and c. e g e g / \ / \ /|\ /|\ f a h remove ac f | b | h / \ ----------> | | | b c \|/ \ / d d """ d = Node(Frame(name="d")) diamond = Node.from_lists(("a", ("b", d), ("c", d))) new_d = Node(Frame(name="d")) new_b = Node.from_lists(("b", new_d)) check_filter_squash( GraphFrame.from_lists(("e", "f", diamond), ("g", diamond, "h")), lambda row: row["node"].frame["name"] not in ("a", "c"), Graph.from_lists(("e", new_b, new_d, "f"), ("g", new_b, new_d, "h")), [4, 2, 1, 1, 4, 1], # e, b, d, f, g, h ) check_filter_no_squash( GraphFrame.from_lists(("e", "f", diamond), ("g", diamond, "h")), lambda row: row["node"].frame["name"] not in ("a", "c"), 6, # e, b, d, f, g, h )
def test_filter_squash_with_merge(): r"""Test squash with a simple node merge. a / \ remove bd a b d ----------> | / \ c c c Note that here, because b and d have been removed, a will have only one child called c, which will contain merged (summed) data from the original c rows. """ check_filter_squash( GraphFrame.from_lists(("a", ("b", "c"), ("d", "c"))), lambda row: row["node"].frame["name"] in ("a", "c"), Graph.from_lists(("a", "c")), [3, 2], # a, c ) check_filter_no_squash( GraphFrame.from_lists(("a", ("b", "c"), ("d", "c"))), lambda row: row["node"].frame["name"] in ("a", "c"), 3, # a, c, c )
def test_unify_diff_graphs(): gf1 = GraphFrame.from_lists(("a", ("b", "c"), ("d", "e"))) gf2 = GraphFrame.from_lists(("a", ("b", "c", "d"), ("e", "f"), "g")) assert gf1.graph is not gf2.graph gf1.unify(gf2) assert gf1.graph is gf2.graph assert len(gf1.graph) == gf1.dataframe.shape[0]
def test_from_lists(): gf = GraphFrame.from_lists(("a", ("b", "c"), ("d", "e"))) assert list(gf.graph.traverse(attrs="name")) == ["a", "b", "c", "d", "e"] assert all(gf.dataframe["time"] == ([1.0] * 5)) nodes = set(gf.graph.traverse()) assert all(node in gf.dataframe["node"] for node in nodes)
def test_subtree_sum_inplace(): gf = GraphFrame.from_lists(("a", ("b", "c"), ("d", "e"))) (a, b, c, d, e) = gf.graph.traverse() gf.subtree_sum(["time"]) assert gf.dataframe.loc[a, "time"] == 5 assert gf.dataframe.loc[b, "time"] == 2 assert gf.dataframe.loc[c, "time"] == 1 assert gf.dataframe.loc[d, "time"] == 2 assert gf.dataframe.loc[e, "time"] == 1
def test_from_lists(): gf = GraphFrame.from_lists(("a", ("b", "c"), ("d", "e"))) gf.dataframe.reset_index(inplace=True, drop=False) assert list(gf.graph.traverse(attrs="name")) == ["a", "b", "c", "d", "e"] assert all(gf.dataframe["time"] == ([1.0] * 5)) nodes = set(gf.graph.traverse()) assert all(node in list(gf.dataframe["node"]) for node in nodes)
def test_filter_squash(): r"""Test squash on a simple tree with one root. a / \ remove bd a b d ----------> / \ / \ c e c e """ check_filter_squash( GraphFrame.from_lists(("a", ("b", "c"), ("d", "e"))), lambda row: row["node"].frame["name"] in ("a", "c", "e"), Graph.from_lists(("a", "c", "e")), [3, 1, 1], # a, c, e ) check_filter_no_squash( GraphFrame.from_lists(("a", ("b", "c"), ("d", "e"))), lambda row: row["node"].frame["name"] in ("a", "c", "e"), 3, # a, c, e )
def test_update_inclusive_metrics(): gf = GraphFrame.from_lists(("a", ("b", "c"), ("d", "e"))) (a, b, c, d, e) = gf.graph.traverse() # this is computed automatically by from_lists -- drop it for this test. del gf.dataframe["time (inc)"] gf.update_inclusive_columns() assert gf.dataframe.loc[a, "time (inc)"] == 5 assert gf.dataframe.loc[b, "time (inc)"] == 2 assert gf.dataframe.loc[c, "time (inc)"] == 1 assert gf.dataframe.loc[d, "time (inc)"] == 2 assert gf.dataframe.loc[e, "time (inc)"] == 1
def test_filter_squash_different_roots(): r"""Test squash on a simple tree with one root but make multiple roots. a / \ remove a b d b d ---------> / \ / \ c e c e """ check_filter_squash( GraphFrame.from_lists(("a", ("b", "c"), ("d", "e"))), lambda row: row["node"].frame["name"] != "a", Graph.from_lists(("b", "c"), ("d", "e")), [2, 1, 2, 1], # b, c, d, e ) check_filter_no_squash( GraphFrame.from_lists(("a", ("b", "c"), ("d", "e"))), lambda row: row["node"].frame["name"] != "a", 4, # b, c, d, e )
def test_subtree_product(): gf = GraphFrame.from_lists(("a", ("b", "c"), ("d", "e"))) (a, b, c, d, e) = gf.graph.traverse() gf.dataframe["time2"] = gf.dataframe["time"] * 2 gf.subtree_sum(["time", "time2"], ["out", "out2"], function=np.prod) assert gf.dataframe.loc[a, "out"] == 1 assert gf.dataframe.loc[b, "out"] == 1 assert gf.dataframe.loc[c, "out"] == 1 assert gf.dataframe.loc[d, "out"] == 1 assert gf.dataframe.loc[e, "out"] == 1 assert gf.dataframe.loc[a, "out2"] == 32 assert gf.dataframe.loc[b, "out2"] == 4 assert gf.dataframe.loc[c, "out2"] == 2 assert gf.dataframe.loc[d, "out2"] == 4 assert gf.dataframe.loc[e, "out2"] == 2
def test_to_literal_node_ids(): r"""Test to_literal and from_literal with ids on a graph with cycles, multiple parents and children. a -- / \ / b c \ / d / \ e f """ a = Node(Frame(name="a")) d = Node(Frame(name="d")) gf = GraphFrame.from_lists([a, ["b", [d]], ["c", [d, ["e"], ["f"]], [a]]]) lit_list = gf.to_literal() gf2 = gf.from_literal(lit_list) lit_list2 = gf2.to_literal() assert lit_list == lit_list2
def test_output_with_cycle_graphs(): r"""Test three output modes on a graph with cycles, multiple parents and children. a -- / \ / b c \ / d / \ e f """ dot_edges = [ # d has two parents and two children '"1" -> "2";', '"5" -> "2";', '"2" -> "3";', '"2" -> "4";', # a -> c -> a cycle '"0" -> "5";', '"5" -> "0";', ] a = Node(Frame(name="a")) d = Node(Frame(name="d")) gf = GraphFrame.from_lists([a, ["b", [d]], ["c", [d, ["e"], ["f"]], [a]]]) lit_list = gf.to_literal() treeout = gf.tree() dotout = gf.to_dot() # scan through litout produced dictionary for edges a_children = [n["frame"]["name"] for n in lit_list[0]["children"]] a_c_children = [n["frame"]["name"] for n in lit_list[0]["children"][1]["children"]] a_b_children = [n["frame"]["name"] for n in lit_list[0]["children"][0]["children"]] assert len(lit_list) == 1 assert len(a_children) == 2 # a -> (b,c) assert "b" in a_children assert "c" in a_children # a -> c -> a cycle assert "a" in a_c_children # d has two parents assert "d" in a_c_children assert "d" in a_b_children # check certain edges are in dot for edge in dot_edges: assert edge in dotout # check that a certain number of occurences # of same node are in tree indicating multiple # edges assert treeout.count("a") == 2 assert treeout.count("d") == 2 assert treeout.count("e") == 1 assert treeout.count("f") == 1
def test_subtree_sum_value_error(): gf = GraphFrame.from_lists(("a", ("b", "c"), ("d", "e"))) # in and out columns with different lengths with pytest.raises(ValueError): gf.subtree_sum(["time"], [])