def test_self_loops_2(self): graph = Graph() node_1 = Node(graph) branch_a = Branch(node_1, node_1, "a") cycles = [[branch.id for branch in cycle] for cycle in simple_cycles(graph)] self.assertCountEqual([[branch_a.id]], cycles) graph = Graph() node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) node_4 = Node(graph) branch_a = Branch(node_1, node_2, "a") branch_b = Branch(node_2, node_3, "b") branch_c = Branch(node_3, node_1, "c") Branch(node_3, node_4, "d") branch_e = Branch(node_1, node_1, "e") branch_f = Branch(node_2, node_2, "f") branch_g = Branch(node_3, node_3, "g") branch_h = Branch(node_4, node_4, "h") cycles = [[branch.id for branch in cycle] for cycle in simple_cycles(graph)] self.assertTrue(self.__check_loop_order( [[branch_e.id], [branch_a.id, branch_b.id, branch_c.id], [branch_f.id], [branch_g.id], [branch_h.id]], cycles))
def test_graph_copy(self): # Create graph graph = Graph() node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) node_4 = Node(graph) Branch(node_1, node_2) Branch(node_2, node_2) Branch(node_2, node_3) Branch(node_3, node_1) Branch(node_1, node_4) # Make copy graph_copy = graph.copy() # Assert nodes_original = list(graph.nodes) nodes_original.sort(key=lambda n: n.id) nodes_copy = list(graph_copy.nodes) nodes_copy.sort(key=lambda n: n.id) branches_original = list(graph.branches) branches_original.sort(key=lambda n: n.id) branches_copy = list(graph_copy.branches) branches_copy.sort(key=lambda n: n.id) for node_original, node_copy in zip(nodes_original, nodes_copy): self.assertTrue(self.__node_equals(node_original, node_copy)) for branch_original, branch_copy in zip(branches_original, branches_copy): self.assertTrue(self.__branch_equals(branch_original, branch_copy))
def test_add_remove_branches(self): graph = Graph() branch1 = MagicMock(Branch) branch2 = MagicMock(Branch) branch3 = MagicMock(Branch) branch1.start = MagicMock(Node) branch1.end = MagicMock(Node) branch1.graph = graph branch2.start = MagicMock(Node) branch2.end = MagicMock(Node) branch2.graph = graph branch3.start = MagicMock(Node) branch3.end = MagicMock(Node) branch3.graph = graph graph.add_branch(branch1) self.assertSetEqual({branch1}, graph.branches) graph.add_branch(branch2) graph.add_branch(branch3) self.assertSetEqual({branch1, branch2, branch3}, graph.branches) def add_branch_again(): graph.add_branch(branch1) self.assertRaises(ValueError, add_branch_again) branch4 = MagicMock(Branch) branch4.start = MagicMock(Node) branch4.end = MagicMock(Node) branch4.graph = None def add_branch4(): graph.add_branch(branch4) branch4.end = None self.assertRaises(ValueError, add_branch4) self.assertSetEqual({branch1, branch2, branch3}, graph.branches) branch1.start = None branch1.end = None branch2.start = None branch2.end = None branch3.start = None branch3.end = None graph.remove_branch(branch1) self.assertSetEqual({branch2, branch3}, graph.branches) graph.remove_branch(branch2) graph.remove_branch(branch3) self.assertSetEqual(set(), graph.branches)
def test_not_touching_loops(self): graph = Graph() node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) branch_a = Branch(node_1, node_1, "a") branch_b = Branch(node_2, node_2, "b") branch_c = Branch(node_3, node_3, "c") res = find_loop_groups([ [branch_a], [branch_b], [branch_c] ]) loops = [(loopGroup.loops, loopGroup.loop_count) for loopGroup in res] expected = [ ([[branch_a]], 1), ([[branch_b]], 1), ([[branch_c]], 1), ([[branch_b], [branch_a]], 2), ([[branch_c], [branch_b]], 2), ([[branch_c], [branch_a]], 2), ([[branch_c], [branch_b], [branch_a]], 3), ] self.assertCountEqual(expected, loops)
def test_johnson_4(self): graph = Graph() node_a = Node(graph) node_b = Node(graph) node_c = Node(graph) node_d = Node(graph) branch_ac = Branch(node_a, node_c, "ac") branch_ca = Branch(node_c, node_a, "ca") branch_ab = Branch(node_a, node_b, "ab") branch_ba = Branch(node_b, node_a, "ba") branch_bc = Branch(node_b, node_c, "bc") branch_cd = Branch(node_c, node_d, "cd") branch_db = Branch(node_d, node_b, "db") actual = strongly_connected_components(graph) expected = [[node_a, node_b, node_c, node_d]] self.assertTrue(self.__check_strongly_connected_components( expected, actual)) cycles = [[branch.id for branch in cycle] for cycle in simple_cycles(graph)] self.assertTrue( self.__check_loop_order([[branch_ac.id, branch_ca.id], [branch_ab.id, branch_ba.id], [branch_ab.id, branch_bc.id, branch_ca.id], [branch_ac.id, branch_cd.id, branch_db.id, branch_ba.id], [branch_bc.id, branch_cd.id, branch_db.id]], cycles))
def test_johnson_3(self): graph = Graph() node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) node_4 = Node(graph) node_5 = Node(graph) node_6 = Node(graph) node_7 = Node(graph) Branch(node_1, node_2, "a") branch_b = Branch(node_2, node_3, "b") branch_c = Branch(node_3, node_4, "c") Branch(node_4, node_5, "d") branch_e = Branch(node_5, node_6, "e") Branch(node_6, node_7, "f") branch_g = Branch(node_6, node_5, "g") branch_h = Branch(node_4, node_3, "h") branch_i = Branch(node_4, node_2, "i") Branch(node_2, node_6, "j") cycles = cycles = [[branch.id for branch in cycle] for cycle in simple_cycles(graph)] self.assertTrue(self.__check_loop_order( [[branch_b.id, branch_c.id, branch_i.id], [branch_h.id, branch_c.id], [branch_e.id, branch_g.id]], cycles))
def test_johnson_2(self): graph = Graph() node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) node_4 = Node(graph) branch_a = Branch(node_1, node_2, "a") branch_b = Branch(node_2, node_1, "b") branch_c = Branch(node_2, node_4, "c") branch_d = Branch(node_4, node_3, "d") branch_e = Branch(node_3, node_1, "e") branch_f = Branch(node_1, node_3, "f") components = strongly_connected_components(graph) self.assertEqual(1, len(components)) self.assertCountEqual([node_3, node_4, node_2, node_1], components[0]) cycles = [[branch.id for branch in cycle] for cycle in simple_cycles(graph)] self.assertTrue(self.__check_loop_order( [[branch_f.id, branch_e.id], [branch_a.id, branch_c.id, branch_d.id, branch_e.id], [branch_a.id, branch_b.id], ], cycles))
def test_add_remove_nodes(self): graph = Graph() node1 = MagicMock(Node) node2 = MagicMock(Node) node3 = MagicMock(Node) node1.graph = graph node2.graph = graph node3.graph = graph graph.add_node(node1) self.assertSetEqual({node1}, graph.nodes) graph.add_node(node2) graph.add_node(node3) def add_node_again(): graph.add_node(node1) self.assertRaises(ValueError, add_node_again) self.assertSetEqual({node1, node2, node3}, graph.nodes) node4 = MagicMock(Node) node4.graph = MagicMock(Graph) def add_node4(): graph.add_node(node4) self.assertRaises(ValueError, add_node4) self.assertSetEqual({node1, node2, node3}, graph.nodes) node4.graph = graph node4.ingoing = [1, 2, 3] self.assertRaises(ValueError, add_node4) self.assertSetEqual({node1, node2, node3}, graph.nodes) graph.remove_node(node1) self.assertSetEqual({node2, node3}, graph.nodes) graph.remove_node(node2) graph.remove_node(node3) self.assertSetEqual(set(), graph.nodes)
def test_find_paths_line(self): graph = Graph() node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) branch_a = Branch(node_1, node_2, "a") branch_b = Branch(node_2, node_3, "b") self.assertCountEqual([[branch_a, branch_b]], find_paths(node_1, node_3))
def test_loop_to_expression_1(self): graph = Graph() node_1 = Node(graph) branch_a = Branch(node_1, node_1, "a") branches = [branch_a] expectedExpression = Symbol(branch_a.weight) expression = loop_to_expression(branches) self.assertEqual(srepr(expression), srepr(expectedExpression))
def test_johnson_1(self): graph = Graph() node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) branch_a = Branch(node_1, node_2, "a") branch_b = Branch(node_2, node_3, "b") branch_c = Branch(node_3, node_1, "c") cycles = [[branch.id for branch in cycle] for cycle in simple_cycles(graph)] self.assertTrue(self.__check_loop_order( [[branch_a.id, branch_b.id, branch_c.id]], cycles))
def test_find_paths_self_loop4(self): graph = Graph() node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) node_4 = Node(graph) branch_a = Branch(node_1, node_2, "a") Branch(node_1, node_3, "b") Branch(node_3, node_4, "c") Branch(node_4, node_1, "d") paths = find_paths(node_1, node_2) self.assertCountEqual([[branch_a]], paths)
def test_strongly_connected_components(self): graph = Graph() node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) Branch(node_1, node_2, "a") Branch(node_2, node_3, "b") Branch(node_2, node_3, "c") Branch(node_3, node_1, "d") components = strongly_connected_components(graph) self.assertEqual(1, len(components)) component = components[0] self.assertCountEqual([node_3, node_2, node_1], component)
def test_loop_to_expression_3(self): graph = Graph() node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) node_4 = Node(graph) node_5 = Node(graph) node_6 = Node(graph) node_7 = Node(graph) node_8 = Node(graph) node_9 = Node(graph) node_10 = Node(graph) node_11 = Node(graph) branch_a = Branch(node_1, node_2, "a") branch_b = Branch(node_2, node_3, "b") branch_c = Branch(node_3, node_4, "c") branch_d = Branch(node_4, node_5, "d") branch_e = Branch(node_5, node_6, "e") branch_f = Branch(node_6, node_7, "f") branch_g = Branch(node_7, node_8, "g") branch_h = Branch(node_8, node_9, "h") branch_j = Branch(node_9, node_10, "j") branch_k = Branch(node_10, node_11, "k") branch_l = Branch(node_11, node_1, "l") branches = [ branch_a, branch_b, branch_c, branch_d, branch_e, branch_f, branch_g, branch_h, branch_j, branch_k, branch_l ] expectedExpression = Mul(Symbol(branch_a.weight), Symbol(branch_b.weight), Symbol(branch_c.weight), Symbol(branch_d.weight), Symbol(branch_e.weight), Symbol(branch_f.weight), Symbol(branch_g.weight), Symbol(branch_h.weight), Symbol(branch_j.weight), Symbol(branch_k.weight), Symbol(branch_l.weight)) expression = loop_to_expression(branches) self.assertEqual(srepr(expression), srepr(expectedExpression))
def test_loop_to_expression_2(self): graph = Graph() node_1 = Node(graph) node_2 = Node(graph) branch_a = Branch(node_1, node_2, "a") branch_b = Branch(node_2, node_1, "b") branches = [branch_a, branch_b] expectedExpression = Mul(Symbol(branch_a.weight), Symbol(branch_b.weight)) expression = loop_to_expression(branches) self.assertEqual(srepr(expression), srepr(expectedExpression))
def test_find_paths_self_loop3(self): graph = Graph() node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) node_4 = Node(graph) node_5 = Node(graph) branch_a = Branch(node_1, node_2, "a") branch_b = Branch(node_2, node_3, "b") branch_c = Branch(node_3, node_4, "c") branch_d = Branch(node_4, node_5, "d") Branch(node_4, node_2, "e") paths = find_paths(node_1, node_5) self.assertCountEqual([[branch_a, branch_b, branch_c, branch_d]], paths)
def test_mason_1(self): # Create graph graph = Graph() # Add nodes node_x = Node(graph) # input node node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) node_4 = Node(graph) node_5 = Node(graph) node_z = Node(graph) # output node # Add branches Branch(node_x, node_1, "a") Branch(node_1, node_2, "b") Branch(node_2, node_3, "c") Branch(node_3, node_4, "d") Branch(node_4, node_5, "e") Branch(node_5, node_z, "f") Branch(node_5, node_4, "g") Branch(node_3, node_2, "h") Branch(node_3, node_1, "j") Branch(node_1, node_5, "k") # Execute result = mason(graph, node_x, node_z) # Assert expected = [ "(a*b*c*d*e*f + a*f*k*(-c*h + 1))", "/(b*c*e*g*j - b*c*j + c*e*g*h - c*h - e*g + 1)" ] T = result.transfer_function[0][0] actual = T.subs(result.transfer_function) \ .subs(result.numerator) \ .subs(result.denominator) \ .subs(result.determinant) \ .subs(result.paths) \ .subs(result.loops) self.assertEqual(expected[0] + expected[1], str(actual))
def test_johson_multiple_scc(self): graph = Graph() node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) node_4 = Node(graph) node_5 = Node(graph) node_6 = Node(graph) node_7 = Node(graph) node_8 = Node(graph) node_9 = Node(graph) Branch(node_1, node_2, "a") branch_b = Branch(node_2, node_3, "b") branch_c = Branch(node_3, node_4, "c") branch_d = Branch(node_4, node_2, "d") branch_e = Branch(node_1, node_5, "e") branch_f = Branch(node_5, node_6, "f") branch_g = Branch(node_6, node_1, "g") branch_h = Branch(node_9, node_7, "h") branch_i = Branch(node_7, node_8, "i") branch_j = Branch(node_8, node_9, "j") Branch(node_6, node_9, "k") actual = strongly_connected_components(graph) expected = [[node_9, node_8, node_7], [node_3, node_2, node_4], [node_6, node_5, node_1]] self.assertTrue(self.__check_strongly_connected_components( expected, actual)) cycles = [[branch.id for branch in cycle] for cycle in simple_cycles(graph)] self.assertTrue( self.__check_loop_order([[branch_b.id, branch_c.id, branch_d.id], [branch_e.id, branch_f.id, branch_g.id], [branch_h.id, branch_i.id, branch_j.id]], cycles))
def test_find_paths_self_loops(self): graph = Graph() node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) branch_a = Branch(node_1, node_2, "a") branch_b = Branch(node_2, node_3, "b") branch_c = Branch(node_2, node_3, "c") Branch(node_2, node_2, "d") branch_e = Branch(node_2, node_1, "e") branch_f = Branch(node_3, node_1, "f") self.assertCountEqual([[branch_a, branch_b], [branch_a, branch_c]], find_paths(node_1, node_3)) paths = find_paths(node_2, node_1) self.assertCountEqual( [[branch_c, branch_f], [branch_e], [branch_b, branch_f]], paths)
def test_mason_3(self): # Create graph graph = Graph() # Add nodes node_x = Node(graph) # input node node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) node_4 = Node(graph) node_5 = Node(graph) node_z = Node(graph) # output node # Add branches Branch(node_x, node_z, "g7") Branch(node_z, node_x, "h7") Branch(node_z, node_1, "g8") Branch(node_1, node_2, "C") Branch(node_2, node_3, "g3") Branch(node_3, node_4, "g4") Branch(node_4, node_5, "g5") Branch(node_5, node_x, "g6") Branch(node_2, node_1, "h2") Branch(node_4, node_3, "N") # Execute result = mason(graph, node_x, node_z) # Assert expected_num = "g7*(C*N*g4*h2 - C*h2 - N*g4 + 1)" actual_num = result.numerator[0][1] \ .subs(result.numerator) \ .subs(result.denominator) \ .subs(result.determinant) \ .subs(result.paths) \ .subs(result.loops) self.assertEqual(expected_num, str(actual_num))
def test_find_paths_loop_over_loop(self): # <--j ------------| # | <--h----| <--g---- # | | | | | # X --a--> 1 --b--> 2 --c--> 3 --d--> 4 --e--> 5 --f--> Z # | | # -------------------------------k--> # Create graph graph = Graph() # Create nodes node_x = Node(graph) # input node node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) node_4 = Node(graph) node_5 = Node(graph) node_z = Node(graph) # output node # Create branches and connect nodes branch_a = Branch(node_x, node_1, "a") branch_b = Branch(node_1, node_2, "b") branch_c = Branch(node_2, node_3, "c") branch_d = Branch(node_3, node_4, "d") branch_e = Branch(node_4, node_5, "e") branch_f = Branch(node_5, node_z, "f") Branch(node_5, node_4, "g") Branch(node_3, node_2, "h") Branch(node_3, node_1, "j") branch_k = Branch(node_1, node_5, "k") # Assert paths = find_paths(node_x, node_z) self.assertCountEqual( [[branch_a, branch_b, branch_c, branch_d, branch_e, branch_f], [branch_a, branch_k, branch_f]], paths)
def test_touching_loops(self): graph = Graph() node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) node_4 = Node(graph) node_5 = Node(graph) node_6 = Node(graph) node_7 = Node(graph) Branch(node_1, node_2, "a") branch_b = Branch(node_2, node_3, "b") branch_c = Branch(node_3, node_4, "c") Branch(node_4, node_5, "d") branch_e = Branch(node_5, node_6, "e") Branch(node_6, node_7, "f") branch_g = Branch(node_6, node_5, "g") branch_h = Branch(node_4, node_3, "h") branch_i = Branch(node_4, node_2, "i") Branch(node_2, node_6, "j") res = find_loop_groups([[branch_h, branch_c], [branch_g, branch_e], [branch_i, branch_b, branch_c]]) loops = [(loopGroup.loops, loopGroup.loop_count) for loopGroup in res] expected = [ ([[branch_h, branch_c]], 1), ([[branch_g, branch_e]], 1), ([[branch_i, branch_b, branch_c]], 1), ([[branch_g, branch_e], [branch_h, branch_c]], 2), ([[branch_i, branch_b, branch_c], [branch_g, branch_e]], 2) ] self.assertCountEqual(expected, loops)
def test_subgraph(self): graph = Graph() node = Node(graph) subgraph = graph.subgraph({node}) self.assertEqual(1, len(subgraph.nodes)) self.assertEqual(0, len(graph.nodes)) self.assertEqual(node, list(subgraph.nodes)[0]) self.assertEqual(node.graph, subgraph) graph = Graph() node_1 = Node(graph) node_2 = Node(graph) node_3 = Node(graph) branch_1 = Branch(node_1, node_2, "1") branch_2 = Branch(node_2, node_1, "2") Branch(node_2, node_3, "3") Branch(node_3, node_1, "4") branch_5 = Branch(node_3, node_3, "5") branch_6 = Branch(node_2, node_2, "6") subgraph = graph.subgraph([node_1, node_2]) self.assertCountEqual([node_1, node_2], subgraph.nodes) self.assertCountEqual([node_3], graph.nodes) self.assertCountEqual([branch_1, branch_2, branch_6], subgraph.branches) self.assertCountEqual([branch_5], graph.branches) self.assertEqual(node_1.graph, subgraph) self.assertEqual(node_2.graph, subgraph) self.assertEqual(node_3.graph, graph) self.assertEqual(branch_1.graph, subgraph) self.assertEqual(branch_2.graph, subgraph) self.assertEqual(branch_5.graph, graph) self.assertEqual(branch_6.graph, subgraph)
def simple_cycles(g: Graph) -> List[List[Branch]]: """Find all simple cycles in a graph""" # Make copy because the graph gets altered during the algorithm graph_copy = g.copy() branch_map = {} copy_result = list() # Create map to allow returning original branches for branch in g.branches: branch_map[branch.id] = branch # Yield every elementary cycle in python graph G exactly once # Expects a dictionary mapping from vertices to iterables of vertices def _unblock(thisnode, blocked, B): stack = set([thisnode]) while stack: node = stack.pop() if node in blocked: blocked.remove(node) stack.update(B[node]) B[node].clear() sccs = [(graph_copy, scc) for scc in strongly_connected_components(graph_copy)] while sccs: current_graph, scc = sccs.pop() startnode = scc.pop() path = [startnode.id] pathBranches = [] blocked = set() closed = set() blocked.add(startnode.id) B = defaultdict(set) stack = [(startnode, list(startnode.outgoing))] while stack: thisnode, nbrs = stack[-1] if nbrs: branch = nbrs.pop() nextnode = branch.end if nextnode.id == startnode.id: result = pathBranches[:] result.append(branch) copy_result.append(result) closed.update(path) elif nextnode.id not in blocked: path.append(nextnode.id) pathBranches.append(branch) stack.append((nextnode, list(nextnode.outgoing))) closed.discard(nextnode.id) blocked.add(nextnode.id) continue if not nbrs: if thisnode.id in closed: _unblock(thisnode.id, blocked, B) else: for nbr in map(lambda x: x.end, thisnode.outgoing): if thisnode.id not in B[nbr.id]: B[nbr.id].add(thisnode.id) stack.pop() path.pop() if (pathBranches): pathBranches.pop() startnode.remove() subgraph = current_graph.subgraph(set(scc)) new_scc = strongly_connected_components(subgraph) sccs.extend([(subgraph, scc) for scc in new_scc]) for loop in copy_result: yield list(map(lambda b: branch_map[b.id], loop))