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 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))