def remove_redundant(self): """ Find and remove redundant edges. An edge is redundant if there there are multiple possibilities to reach an end node from a start node. Since the longer path triggers more needed database updates the shorter path gets discarded. Might raise a ``CycleNodeException``. Returns the removed edges. """ paths = self.get_nodepaths() possible_replaces = [] for p_path in paths: for q_path in paths: if self._can_replace_nodepath(q_path, p_path): possible_replaces.append((q_path, p_path)) removed = set() for candidate, _ in possible_replaces: edges = [Edge(*nodes) for nodes in pairwise(candidate)] for edge in edges: if edge in removed: continue self.remove_edge(edge) removed.add(edge) # make sure all startpoints will still update all endpoints if not self._compare_startend_nodepaths( self.get_nodepaths(), paths): self.add_edge(edge) removed.remove(edge) self._removed.update(removed) return removed
def test_graph_cycle_detection(self): nodes = [ Node('A'), Node('B'), Node('C'), Node('D'), Node('E'), Node('F') ] simple_edges = [Edge(a, b) for a, b in pairwise(nodes)] graph = Graph() for edge in simple_edges: graph.add_edge(edge) self.assertTrue(graph.is_cyclefree) self.assertFalse(graph.edge_cycles) self.assertFalse(graph.node_cycles) # add one cycle graph.add_edge(Edge(nodes[1], nodes[0])) self.assertFalse(graph.is_cyclefree) self.assertEqual(len(graph.node_cycles), 1) # add second cycle graph.add_edge(Edge(nodes[2], nodes[0])) self.assertEqual(len(graph.node_cycles), 2) # add third cycle graph.add_edge(Edge(nodes[5], nodes[4])) self.assertEqual(len(graph.node_cycles), 3) # add tricky edge (adds multiple cycles at once) graph.add_edge(Edge(nodes[4], nodes[2])) self.assertGreater(len(graph.node_cycles), 4) self.assertGreater(len(graph.edge_cycles), 4)
def test_raise_cycle_exceptions(self): nodes = [ Node('A'), Node('B'), Node('C'), Node('D'), Node('E'), Node('F') ] simple_edges = [Edge(a, b) for a, b in pairwise(nodes)] graph = Graph() for edge in simple_edges: graph.add_edge(edge) # should not raise CycleExceptions graph.get_nodepaths() graph.get_edgepaths() # add one cycle graph.add_edge(Edge(nodes[1], nodes[0])) # should raise suitable exceptions self.assertRaises(CycleNodeException, lambda: graph.get_nodepaths()) self.assertRaises(CycleEdgeException, lambda: graph.get_edgepaths()) # CycleNodeException message should contain # cycling nodes (order is undetermined) try: graph.get_nodepaths() except CycleNodeException as e: self.assertIn(e.args[0], [[nodes[0], nodes[1], nodes[0]], [nodes[1], nodes[0], nodes[1]]])
def test_paths(self): nodes = [Node('A'), Node('B'), Node('C'), Node('D'), Node('E'), Node('F')] simple_edges = [Edge(a, b) for a, b in pairwise(nodes)] graph = Graph() for edge in simple_edges: graph.add_edge(edge) all_edge_paths = graph.get_edgepaths() all_node_paths = graph.get_nodepaths() self.assertEqual(len(all_node_paths), 15) # should match 6 choose 2 (n!/((n-k)!*k!)) self.assertEqual(all_edge_paths, [graph.nodepath_to_edgepath(path) for path in all_node_paths]) self.assertEqual(all_node_paths, [graph.edgepath_to_nodepath(path) for path in all_edge_paths])
def nodepath_to_edgepath(self, path): """ Converts a list of nodes to a list of edges. """ return [Edge(*pair) for pair in pairwise(path)]
def nodepath_to_edgepath(path: Sequence[Node]) -> List[Edge]: """ Converts a list of nodes to a list of edges. """ return [Edge(*pair) for pair in pairwise(path)]