root/spinoffs/Dijkstar/dijkstar/__init__.py

Revision 815, 8.3 KB (checked in by bycycle, 5 years ago)

Dijkstar -- made infinity sys.maxint2, as that *should*
be sufficiently large. Users can set this before calling Dijkstar's
functions if need be, so it doesn't matter too much anyway.

  • Property svn:keywords set to Id
Line 
1###############################################################################
2# $Id$
3# Created 2004-12-28.
4#
5# Dijkstra/A* path-finding functions.
6#
7# Copyright (C) 2004-2007, Wyatt Baldwin. All rights reserved.
8#
9# Licensed under the MIT license.
10#
11#    http://www.opensource.org/licenses/mit-license.php
12#
13# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19# THE SOFTWARE.
20###############################################################################
21"""Dijkstra/A* path-finding functions."""
22import sys
23import heapq
24
25
26infinity = sys.maxint ** 2
27
28
29class DijkstarError(Exception):
30    """Base class for Dijkstar errors."""
31
32class NoPathError(DijkstarError):
33    """Error raised when a path can't be found to a given node, ``d``."""
34
35
36def 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    # weights of shortest paths from s to all v (ID of v => w)
89    W = {s: 0, d: infinity}
90    # partially sorted list of nodes w/ known weights from s
91    open = [(0, s)]
92    # predecessor of each node that has shortest path from s
93    P = {}
94
95    nodes, edges = G['nodes'], G['edges']
96    h_nodes, h_edges = H['nodes'], H['edges']
97
98    while open:
99        # In the nodes remaining in G that have a known weight from s,
100        # find the node, u, that currently has the shortest path from s
101        w_of_s_to_u, u = heapq.heappop(open)
102
103        # Get the attributes of the segment crossed to get to u
104        try:
105            prev_e_attrs = P[u][2]
106        except KeyError:
107            prev_e_attrs = None
108
109        # Get nodes adjacent to u...
110        if u in h_nodes:
111            A = h_nodes[u]
112        else:
113            try:
114                A = nodes[u]
115            except KeyError:
116                # We'll get here upon reaching a node with no outgoing edges
117                continue
118
119        # ...and explore the edges that connect u to those nodes, updating
120        # the weight of the shortest paths to any or all of those nodes as
121        # necessary. v is the node across the current edge from u.
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            # Get the weight of the edge running from u to v
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            # Weight of s to u plus the weight of u to v across e--this is *a*
137            # weight from s to v that may or may not be less than the current
138            # known weight to v
139            w_of_s_to_u_plus_w_of_e = w_of_s_to_u + w_of_e
140
141            # When there is a heuristic function, we use a "guess-timated"
142            # weight, which is the normal weight plus some other heuristic
143            # weight from v to d that is calculated so as to keep us moving
144            # in the right direction (generally more toward the goal instead
145            # of away from it).
146            #try:
147            #    w_of_s_to_u_plus_w_of_e += heuristic_func(e)
148            #except TypeError:
149            #    pass
150
151            # Get the weight of the path from s to v, if known
152            try:
153                w_of_s_to_v = W[v]
154            except KeyError:
155                # If no path to v had been found previously, v's path-weight
156                # from s will have been previously unknown (infinity);
157                # since we have just found a path from s to v, we need to add
158                # v's path-weight from s to the list of nodes with known
159                # weights from s
160                w_of_s_to_v = infinity  # note: this gets used below
161                heapq.heappush(open, (w_of_s_to_u_plus_w_of_e, v))
162
163            # If the current known weight from s to v is greater than the new
164            # weight we just found (weight of s to u plus weight of u to v
165            # across e), update v's weight in the weight list and update v's
166            # predecessor in the predecessor list (it's now u)
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                # u is v's predecessor node. e is the ID of the edge running
170                # from u to v on the shortest known path from s to v. We
171                # include the edge's other attributes too.
172                P[v] = (u, e, e_attrs)
173
174            # If a destination node was specified and we reached it, we're done
175            if v == d:
176                open = None
177                break
178
179    # There is no path from start to d when the weight to d is infinite
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
187def 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 = []  # Node IDs on the shortest path from s to d
204    E = []  # Edge IDs on the shortest path from s to d
205    W = []  # Weights of the edges on the shortest path from s to d
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)  # Start node
216    V.reverse(); E.reverse(); W.reverse()
217    w = sum(W)
218    return V, E, W, w
219
220
221def 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)
Note: See TracBrowser for help on using the browser.