def test_get_hypertree_from_predecessors(): H = DirectedHypergraph() H.read("tests/data/basic_directed_hypergraph.txt") # Test with a weighting Pv, W, valid_ordering = \ directed_paths.shortest_b_tree( H, 's', directed_paths.sum_function, True) sub_H = directed_paths.get_hypertree_from_predecessors(H, Pv, 's', W) sub_H._check_consistency() assert sub_H.get_node_set() == set(['s', 'x', 'y', 'z', 't', 'u']) assert sub_H.get_node_attribute('s', 'weight') == 0 assert sub_H.get_node_attribute('x', 'weight') == 1 assert sub_H.get_node_attribute('y', 'weight') == 2 assert sub_H.get_node_attribute('z', 'weight') == 2 assert sub_H.get_node_attribute('u', 'weight') == 8 assert sub_H.get_node_attribute('t', 'weight') == 8 assert Pv['s'] == None assert Pv['x'] == "e1" assert Pv['y'] == "e2" assert Pv['z'] == "e3" assert (Pv['u'], Pv['t']) == ("e4", "e4") assert (Pv['a'], Pv['b']) == (None, None) assert len(sub_H.get_hyperedge_id_set()) == 4 assert sub_H.has_hyperedge(['s'], ['x']) assert sub_H.has_hyperedge(['s'], ['x', 'y']) assert sub_H.has_hyperedge(['s'], ['z']) assert sub_H.has_hyperedge(['x', 'y', 'z'], ['u', 't']) # Test without a weighting Pv, W = directed_paths.shortest_f_tree(H, 't', directed_paths.sum_function) sub_H = directed_paths.get_hypertree_from_predecessors(H, Pv, 't') sub_H._check_consistency() assert sub_H.get_node_set() == set(['t', 's', 'x']) assert len(sub_H.get_hyperedge_id_set()) == 2 assert sub_H.has_hyperedge(['x'], ['s']) assert sub_H.has_hyperedge(['s'], ['t']) # Try an invalid hypergraph try: directed_paths.shortest_b_tree('s', 't') assert False except TypeError: pass except BaseException as e: assert False, e
def test_shortest_sum_b_tree(): H = DirectedHypergraph() H.read("tests/data/basic_directed_hypergraph.txt") Pv, W, valid_ordering = \ directed_paths.shortest_b_tree( H, 's', directed_paths.sum_function, True) assert valid_ordering.count('s') == 1 assert valid_ordering.index('s') < valid_ordering.index('x') assert valid_ordering.index('s') < valid_ordering.index('y') assert valid_ordering.index('s') < valid_ordering.index('z') assert valid_ordering.index('s') < valid_ordering.index('t') assert valid_ordering.index('s') < valid_ordering.index('u') assert valid_ordering.count('x') == 1 assert valid_ordering.index('x') < valid_ordering.index('t') assert valid_ordering.index('x') < valid_ordering.index('u') assert valid_ordering.count('y') == 1 assert valid_ordering.index('y') < valid_ordering.index('t') assert valid_ordering.index('y') < valid_ordering.index('u') assert valid_ordering.count('z') == 1 assert valid_ordering.index('z') < valid_ordering.index('t') assert valid_ordering.index('z') < valid_ordering.index('u') assert valid_ordering.count('t') == 1 assert valid_ordering.count('u') == 1 assert valid_ordering.count('a') == 0 assert valid_ordering.count('b') == 0 assert Pv['s'] is None assert Pv['x'] == 'e1' assert Pv['y'] == 'e2' assert Pv['z'] == 'e3' assert Pv['t'] == 'e4' assert Pv['u'] == 'e4' assert (Pv['a'], Pv['b']) == (None, None) assert W['s'] == 0 assert W['x'] == 1 assert W['y'] == 2 assert W['z'] == 2 assert W['u'] == 8 assert W['t'] == 8 assert W['a'] == float('inf') assert W['b'] == float('inf') # Try an invalid hypergraph try: directed_paths.shortest_b_tree('s', 't') assert False except TypeError: pass except BaseException as e: assert False, e
def test_shortest_gap_b_tree(): H = DirectedHypergraph() H.read("tests/data/basic_directed_hypergraph.txt") Pv, W = \ directed_paths.shortest_b_tree(H, 's', directed_paths.gap_function) assert Pv['s'] is None assert Pv['x'] == 'e1' assert Pv['y'] == 'e2' assert Pv['z'] == 'e3' assert Pv['t'] == 'e4' assert Pv['u'] == 'e4' assert (Pv['a'], Pv['b']) == (None, None) assert W['s'] == 0 assert W['x'] == 1 assert W['y'] == 2 assert W['z'] == 2 assert W['u'] == 4 assert W['t'] == 4 assert W['a'] == float('inf') assert W['b'] == float('inf')
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