def test_returns_hyperpath_containing_source_if_source_equals_destination( self): s = '1' T = {s: None} H = DirectedHypergraph() H.add_node(s) path = directed_paths.get_hyperpath_from_predecessors(H, T, s, s) self.assertTrue(path.has_node(s))
def test_returns_hyperpath_with_single_node_if_source_equals_destination( self): s = '1' T = {s: None} H = DirectedHypergraph() H.add_node(s) path = directed_paths.get_hyperpath_from_predecessors(H, T, s, s) self.assertEqual(len(path.get_node_set()), 1) self.assertEqual(len(path.get_hyperedge_id_set()), 0)
def test_returns_hyperpath_for_simple_tree(self): s1, s2, s3, s4 = 1, 2, 3, 4 H = DirectedHypergraph() H.add_nodes([s1, s2, s3, s4]) e1 = H.add_hyperedge([s1], [s2]) e2 = H.add_hyperedge([s1], [s3]) e3 = H.add_hyperedge([s3], [s4]) T = {s4: e3, s3: e2, s2: e1, s1: None} path = directed_paths.get_hyperpath_from_predecessors(H, T, s1, s4) # validate nodes self.assertEqual(path.get_node_set(), {s1, s3, s4}) # validate hyperedges self.assertEqual(len(path.get_hyperedge_id_set()), 2) self.assertTrue(path.get_hyperedge_id([1], [3])) self.assertTrue(path.get_hyperedge_id([3], [4]))
def test_returns_hyperpath_when_node_is_in_tail_of_two_edges(self): s1, s2, s3 = 1, 2, 3 s4 = 4 H = DirectedHypergraph() e1 = H.add_hyperedge([s1], [s2]) e2 = H.add_hyperedge([s2], [s3]) e3 = H.add_hyperedge([s2, s3], [s4]) T = {s4: e3, s3: e2, s2: e1, s1: None} path = directed_paths.get_hyperpath_from_predecessors(H, T, s1, s4) # validate nodes self.assertEqual(path.get_node_set(), {s1, s2, s3, s4}) # validate hyperedges self.assertEqual(len(path.get_hyperedge_id_set()), 3) self.assertTrue(path.get_hyperedge_id([2, 3], [4])) self.assertTrue(path.get_hyperedge_id([2], [3])) self.assertTrue(path.get_hyperedge_id([1], [2]))
def test_returns_hyperpath_for_tree_with_multiple_nodes_in_tail(self): s1, s2, s3 = 1, 2, 3 s4, s5, s6 = 4, 5, 6 H = DirectedHypergraph() H.add_nodes([s1, s2, s3, s4, s5, s6]) e1 = H.add_hyperedge([s1], [s2]) e2 = H.add_hyperedge([s1], [s3]) e3 = H.add_hyperedge([s1], [s4]) e4 = H.add_hyperedge([s2, s3], [s5]) e5 = H.add_hyperedge([s5], [s6]) T = {s6: e5, s5: e4, s4: e3, s3: e2, s2: e1, s1: None} path = directed_paths.get_hyperpath_from_predecessors(H, T, s1, s6) # validate nodes self.assertEqual(path.get_node_set(), {s1, s2, s3, s5, s6}) # validate hyperedges self.assertEqual(len(path.get_hyperedge_id_set()), 4) self.assertTrue(path.get_hyperedge_id([5], [6])) self.assertTrue(path.get_hyperedge_id([2, 3], [5])) self.assertTrue(path.get_hyperedge_id([1], [3])) self.assertTrue(path.get_hyperedge_id([1], [2]))
def k_shortest_hyperpaths(H, source_node, destination_node, k, F=sum_function): """Computes the k shortest hyperpaths from a source node to every other node in the hypergraph. This algorithm is only applicable to directed B-hypergraphs. The algorithm is described in the paper: Lars Relund Nielsen, Kim Allan Andersen, Daniele Pretolani, Finding the K shortest hyperpaths, Computers & Operations Research, Volume 32, Issue 6, June 2005, Pages 1477-1497, ISSN 0305-0548, http://dx.doi.org/10.1016/j.cor.2003.11.014. (http://www.sciencedirect.com/science/article/pii/S0305054803003459) :param H: the hypergraph for which the function will compute the shortest hyperpaths. :param source_node: the source node in H for the path computation. :param destination_node: the destination node in H for the path computation. :param k: a positive integer indicating how many paths to compute. :param F: [optional] function used for the shortest path computation. See algorithms.directed_paths module for expected format of function. :returns: a list containing at most k hyperpaths (DirectedHypergraph) from source to destination in ascending order of path length. :raises: TypeError -- Input hypergraph must be a B-hypergraph :raises: TypeError -- Algorithm only applicable to directed hypergraphs :raises: ValueError -- source_node must be a node in H :raises: ValueError -- destination_node must be a node in H :raises: TypeError -- k must be an integer :raises: ValueError -- k must be a positive integer """ try: if not H.is_B_hypergraph(): raise TypeError("Input graph must be a B-hypergraph") except AttributeError: raise TypeError("Algorithm only applicable to directed hypergraphs") if not H.has_node(source_node): raise ValueError("source_node must be a node in H. \ %s received" % source_node) if not H.has_node(destination_node): raise ValueError("destination_node must be a node in H. \ %s received" % destination_node) if type(k) != int: raise TypeError("k must be an integer. %s received" % k) if k <= 0: raise ValueError("k must be a positive integer. %s received" % k) # Container for the k-shortest hyperpaths paths = [] # Container for the candidate paths. Every item is a 4-tuple: # 1) subgraph H' # 2) lower bound on shortest hyperpath weight # 3) predecessor function of shortest hypertree rootes at s on H' # 4) valid ordering of the nodes in H' candidates = [] shortest_hypertree, W, ordering = \ shortest_b_tree(H, source_node, F=F, valid_ordering=True) # Check if there is source-destination hyperpath # if there isn't the for loop below # will break immediately and the function returns an empty list if W[destination_node] != float('inf'): candidates.append((H, W, shortest_hypertree, ordering)) i = 1 while i <= k and candidates: ind = candidates.index( min(candidates, key=lambda x: x[1][destination_node])) kShortest = candidates[ind] if kShortest[2]: candidates.pop(ind) path = \ get_hyperpath_from_predecessors(kShortest[0], kShortest[2], source_node, destination_node) pathPredecessor = \ {node: edge for node, edge in kShortest[2].items() if node in path.get_node_set()} pathOrdering = \ [node for node in kShortest[3] if node in pathPredecessor] paths.append(path) # check if we are done if len(paths) == k: break branches = _branching_step(kShortest[0], pathPredecessor, pathOrdering) for j, branch in enumerate(branches): lb = _compute_lower_bound(branch, j, kShortest[2], pathOrdering, kShortest[1], destination_node, F) if lb < float('inf'): candidates.append((branch, {destination_node: lb}, None, None)) i += 1 else: # Compute shortest hypertree for kShortest[0] and exact bound # reinsert into candidates H_sub = kShortest[0] tree_sub, W_sub, ordering_sub = \ shortest_b_tree(H_sub, source_node, valid_ordering=True) candidates[ind] = (H_sub, W_sub, tree_sub, ordering_sub) return paths