| 1 | |
|---|
| 2 | |
|---|
| 3 | |
|---|
| 4 | |
|---|
| 5 | |
|---|
| 6 | |
|---|
| 7 | |
|---|
| 8 | |
|---|
| 9 | |
|---|
| 10 | |
|---|
| 11 | |
|---|
| 12 | |
|---|
| 13 | |
|---|
| 14 | |
|---|
| 15 | |
|---|
| 16 | |
|---|
| 17 | |
|---|
| 18 | |
|---|
| 19 | |
|---|
| 20 | |
|---|
| 21 | """Dijkstra/A* path-finding functions.""" |
|---|
| 22 | import sys |
|---|
| 23 | import heapq |
|---|
| 24 | |
|---|
| 25 | |
|---|
| 26 | infinity = sys.maxint ** 2 |
|---|
| 27 | |
|---|
| 28 | |
|---|
| 29 | class DijkstarError(Exception): |
|---|
| 30 | """Base class for Dijkstar errors.""" |
|---|
| 31 | |
|---|
| 32 | class NoPathError(DijkstarError): |
|---|
| 33 | """Error raised when a path can't be found to a given node, ``d``.""" |
|---|
| 34 | |
|---|
| 35 | |
|---|
| 36 | def single_source_shortest_paths(G, H, s, d=None, weight_func=None, |
|---|
| 37 | heuristic_func=None): |
|---|
| 38 | """Find path from node ``s`` to all other nodes, or just to ``d``. |
|---|
| 39 | |
|---|
| 40 | ``G`` |
|---|
| 41 | Graph of sorts. "v" or "u" is a vertex; "e" is an edge. |
|---|
| 42 | |
|---|
| 43 | { |
|---|
| 44 | 'nodes': { # Adjacency matrix |
|---|
| 45 | v: {u: e, ...}, # Vertex v goes to vertex u via edge e |
|---|
| 46 | . |
|---|
| 47 | . |
|---|
| 48 | . |
|---|
| 49 | }, |
|---|
| 50 | |
|---|
| 51 | 'edges': { # Edge attributes |
|---|
| 52 | e: (weight, attr_a, attr_b, ...), # Edge e's attributes |
|---|
| 53 | . |
|---|
| 54 | . |
|---|
| 55 | . |
|---|
| 56 | } |
|---|
| 57 | } |
|---|
| 58 | |
|---|
| 59 | Edge attribute lists _must_ contain the weight entry first; they may |
|---|
| 60 | also contain other attributes of the edge. These other attributes can |
|---|
| 61 | be used to determine a different weight for the edge. |
|---|
| 62 | |
|---|
| 63 | ``H`` |
|---|
| 64 | "Annex" to ``G``; this is a graph just like ``G`` that can be used to |
|---|
| 65 | augment ``G`` without altering it |
|---|
| 66 | |
|---|
| 67 | ``s`` |
|---|
| 68 | Start node ID |
|---|
| 69 | |
|---|
| 70 | ``d`` |
|---|
| 71 | Destination node ID. If ``d`` is None (default) the algorithm is run |
|---|
| 72 | normally. If ``d`` has a value, the algorithm is stopped when a path |
|---|
| 73 | to ``d`` has been found. |
|---|
| 74 | |
|---|
| 75 | ``weight_func`` |
|---|
| 76 | Function to apply to each edge to modify its base weight. |
|---|
| 77 | |
|---|
| 78 | ``heuristic_func`` |
|---|
| 79 | A function to apply at each iteration to help the poor dumb machine |
|---|
| 80 | try to move toward the destination instead of just any and every which |
|---|
| 81 | way. |
|---|
| 82 | |
|---|
| 83 | return |
|---|
| 84 | * Predecessor mapping {v => (u, e), ...} |
|---|
| 85 | * Weights of paths from node ``s`` to all reached nodes {v => w, ...} |
|---|
| 86 | |
|---|
| 87 | """ |
|---|
| 88 | |
|---|
| 89 | W = {s: 0, d: infinity} |
|---|
| 90 | |
|---|
| 91 | open = [(0, s)] |
|---|
| 92 | |
|---|
| 93 | P = {} |
|---|
| 94 | |
|---|
| 95 | nodes, edges = G['nodes'], G['edges'] |
|---|
| 96 | h_nodes, h_edges = H['nodes'], H['edges'] |
|---|
| 97 | |
|---|
| 98 | while open: |
|---|
| 99 | |
|---|
| 100 | |
|---|
| 101 | w_of_s_to_u, u = heapq.heappop(open) |
|---|
| 102 | |
|---|
| 103 | |
|---|
| 104 | try: |
|---|
| 105 | prev_e_attrs = P[u][2] |
|---|
| 106 | except KeyError: |
|---|
| 107 | prev_e_attrs = None |
|---|
| 108 | |
|---|
| 109 | |
|---|
| 110 | if u in h_nodes: |
|---|
| 111 | A = h_nodes[u] |
|---|
| 112 | else: |
|---|
| 113 | try: |
|---|
| 114 | A = nodes[u] |
|---|
| 115 | except KeyError: |
|---|
| 116 | |
|---|
| 117 | continue |
|---|
| 118 | |
|---|
| 119 | |
|---|
| 120 | |
|---|
| 121 | |
|---|
| 122 | for v in A: |
|---|
| 123 | e = A[v] |
|---|
| 124 | |
|---|
| 125 | if e in h_edges: |
|---|
| 126 | e_attrs = h_edges[e] |
|---|
| 127 | else: |
|---|
| 128 | e_attrs = edges[e] |
|---|
| 129 | |
|---|
| 130 | |
|---|
| 131 | try: |
|---|
| 132 | w_of_e = weight_func(v, e_attrs, prev_e_attrs) |
|---|
| 133 | except TypeError: |
|---|
| 134 | w_of_e = e_attrs[0] |
|---|
| 135 | |
|---|
| 136 | |
|---|
| 137 | |
|---|
| 138 | |
|---|
| 139 | w_of_s_to_u_plus_w_of_e = w_of_s_to_u + w_of_e |
|---|
| 140 | |
|---|
| 141 | |
|---|
| 142 | |
|---|
| 143 | |
|---|
| 144 | |
|---|
| 145 | |
|---|
| 146 | |
|---|
| 147 | |
|---|
| 148 | |
|---|
| 149 | |
|---|
| 150 | |
|---|
| 151 | |
|---|
| 152 | try: |
|---|
| 153 | w_of_s_to_v = W[v] |
|---|
| 154 | except KeyError: |
|---|
| 155 | |
|---|
| 156 | |
|---|
| 157 | |
|---|
| 158 | |
|---|
| 159 | |
|---|
| 160 | w_of_s_to_v = infinity |
|---|
| 161 | heapq.heappush(open, (w_of_s_to_u_plus_w_of_e, v)) |
|---|
| 162 | |
|---|
| 163 | |
|---|
| 164 | |
|---|
| 165 | |
|---|
| 166 | |
|---|
| 167 | if w_of_s_to_v > w_of_s_to_u_plus_w_of_e: |
|---|
| 168 | W[v] = w_of_s_to_u_plus_w_of_e |
|---|
| 169 | |
|---|
| 170 | |
|---|
| 171 | |
|---|
| 172 | P[v] = (u, e, e_attrs) |
|---|
| 173 | |
|---|
| 174 | |
|---|
| 175 | if v == d: |
|---|
| 176 | open = None |
|---|
| 177 | break |
|---|
| 178 | |
|---|
| 179 | |
|---|
| 180 | if d is not None and W[d] == infinity: |
|---|
| 181 | raise NoPathError('Could not find a path from node %s to node %s' % |
|---|
| 182 | (s, d)) |
|---|
| 183 | |
|---|
| 184 | return P, W |
|---|
| 185 | |
|---|
| 186 | |
|---|
| 187 | def extract_shortest_path_from_predecessor_list(P, d): |
|---|
| 188 | """Extract ordered lists of nodes, edges, weights from predecessor list. |
|---|
| 189 | |
|---|
| 190 | ``P`` |
|---|
| 191 | Predecessor list {u: (v, e), ...} u's predecessor is v via e |
|---|
| 192 | |
|---|
| 193 | ``d`` |
|---|
| 194 | Destination node ID |
|---|
| 195 | |
|---|
| 196 | return |
|---|
| 197 | * The node IDs on the shortest path from origin to ``d`` |
|---|
| 198 | * The edge IDs on the shortest path from origin to ``d`` |
|---|
| 199 | * The weights of the edges on the shortest path from origin to ``d`` |
|---|
| 200 | * The total weight of the path |
|---|
| 201 | |
|---|
| 202 | """ |
|---|
| 203 | V = [] |
|---|
| 204 | E = [] |
|---|
| 205 | W = [] |
|---|
| 206 | u = d |
|---|
| 207 | while u in P: |
|---|
| 208 | predecessor_data = P[u] |
|---|
| 209 | e = predecessor_data[1] |
|---|
| 210 | attrs = predecessor_data[2] |
|---|
| 211 | V.append(u) |
|---|
| 212 | E.append(e) |
|---|
| 213 | W.append(attrs[0]) |
|---|
| 214 | u = predecessor_data[0] |
|---|
| 215 | V.append(u) |
|---|
| 216 | V.reverse(); E.reverse(); W.reverse() |
|---|
| 217 | w = sum(W) |
|---|
| 218 | return V, E, W, w |
|---|
| 219 | |
|---|
| 220 | |
|---|
| 221 | def find_path(G, H, s, d, weight_func=None, heuristic_func=None): |
|---|
| 222 | """Find the shortest path from ``s`` to ``d`` in ``G``. |
|---|
| 223 | |
|---|
| 224 | This function just combines finding the predecessor list with extracting |
|---|
| 225 | the node IDs from that list in the proper (path) order, 'cause what you |
|---|
| 226 | want is probably that ordered list. |
|---|
| 227 | |
|---|
| 228 | return |
|---|
| 229 | * The node IDs on the shortest path from origin to ``d`` |
|---|
| 230 | * The edge IDs on the shortest path from origin to ``d`` |
|---|
| 231 | * The weights of the edges on the shortest path from origin to ``d`` |
|---|
| 232 | * The total weight of the path |
|---|
| 233 | |
|---|
| 234 | """ |
|---|
| 235 | P, W = single_source_shortest_paths(G, H, s, d, weight_func,heuristic_func) |
|---|
| 236 | return extract_shortest_path_from_predecessor_list(P, d) |
|---|