1 /** 2 * Definitions for running an A* search over a graph 3 */ 4 module anansi.algorithms.astar; 5 6 import std.traits; 7 import std.exception; 8 9 import anansi.algorithms.bfs; 10 import anansi.algorithms.relax : relax; 11 import anansi.algorithms.vertex_queue : VertexQueue; 12 import anansi.types; 13 import anansi.traits; 14 15 version(unittest) { 16 import std.stdio; 17 import anansi.adjacencylist; 18 } 19 20 template isAStarHeuristic (CallableT, VertexT) { 21 enum bool isAStarHeuristic = is(typeof( 22 (ref CallableT fn, VertexT v) { 23 real x = fn(v); 24 })); 25 } 26 27 public template NullAstarVisitor(GraphT) { 28 alias Vertex = GraphT.VertexDescriptor; 29 alias Edge = GraphT.EdgeDescriptor; 30 31 void initVertex(ref const(GraphT) g, Vertex v) {} 32 void discoverVertex(ref const(GraphT) g, Vertex v) {} 33 void examineVertex(ref const(GraphT) g, Vertex v) {} 34 void examineEdge(ref const(GraphT) g, Edge e) {} 35 void edgeRelaxed(ref const(GraphT) g, Edge e) {} 36 void edgeNotRelaxed(ref const(GraphT) g, Edge e) {} 37 void blackTarget(ref const(GraphT), Edge e) {} 38 void finishVertex(ref const(GraphT) g, Vertex e) {} 39 } 40 41 package struct AStarBfsVisitor(GraphT, 42 QueueT, 43 AStarVisitorT, 44 DistanceMapT, 45 PredecessorMapT, 46 WeightMapT, 47 CostMapT, 48 ColourMapT, 49 HeuristicT) { 50 alias Vertex = GraphT.VertexDescriptor; 51 alias Edge = GraphT.EdgeDescriptor; 52 53 static assert(isReadablePropertyMap!(WeightMapT, Edge, real)); 54 static assert(isPropertyMap!(DistanceMapT, Vertex, real)); 55 static assert(isPropertyMap!(PredecessorMapT, Vertex, Vertex)); 56 57 static assert(isCallable!HeuristicT); 58 59 this(ref AStarVisitorT visitor, 60 ref DistanceMapT distanceMap, 61 ref const(WeightMapT) weightMap, 62 ref PredecessorMapT predecessorMap, 63 ref ColourMapT colourMap, 64 ref QueueT queue, 65 ref CostMapT costMap, 66 ref HeuristicT heuristic) { 67 _visitor = &visitor; 68 _distanceMap = &distanceMap; 69 _weightMap = &weightMap; 70 _predecessorMap = &predecessorMap; 71 _queue = &queue; 72 _costMap = &costMap; 73 _colourMap = &colourMap; 74 _heuristic = heuristic; 75 } 76 77 void initVertex(ref const(GraphT) g, Vertex v) { 78 _visitor.initVertex(g, v); 79 } 80 81 void discoverVertex(ref const(GraphT) g, Vertex v) { 82 _visitor.discoverVertex(g, v); 83 } 84 85 void examineVertex(ref const(GraphT) g, Vertex v) { 86 _visitor.examineVertex(g, v); 87 } 88 89 void examineEdge(ref const(GraphT) g, Edge e) { 90 auto weight = (*_weightMap)[e]; 91 enforce (weight >= 0.0); 92 _visitor.examineEdge(g, e); 93 } 94 95 void treeEdge(ref const(GraphT) g, Edge e) { 96 bool decreased = relax(g, *_weightMap, 97 *_distanceMap, 98 *_predecessorMap, e); 99 if (decreased) { 100 auto t = g.target(e); 101 (*_costMap)[t] = (*_distanceMap)[t] + _heuristic(t); 102 _visitor.edgeRelaxed(g, e); 103 } else { 104 _visitor.edgeNotRelaxed(g, e); 105 } 106 } 107 108 void nonTreeEdge(ref const(GraphT) g, Edge e) { 109 110 } 111 112 void greyTarget(ref const(GraphT) g, Edge e) { 113 bool decreased = relax(g, *_weightMap, 114 *_distanceMap, 115 *_predecessorMap, e); 116 if (decreased) { 117 auto t = g.target(e); 118 (*_costMap)[t] = (*_distanceMap)[t] + _heuristic(t); 119 (*_queue).updateVertex(t); 120 _visitor.edgeRelaxed(g, e); 121 } else { 122 _visitor.edgeNotRelaxed(g, e); 123 } 124 } 125 126 void blackTarget(ref const(GraphT) g, Edge e) { 127 bool decreased = relax(g, *_weightMap, 128 *_distanceMap, 129 *_predecessorMap, e); 130 if (decreased) { 131 auto t = g.target(e); 132 _visitor.edgeRelaxed(g, e); 133 (*_costMap)[t] = (*_distanceMap)[t] + _heuristic(t); 134 (*_colourMap)[t] = Colour.Grey; 135 _visitor.blackTarget(g, e); 136 } else { 137 _visitor.edgeNotRelaxed(g, e); 138 } 139 } 140 141 void finishVertex(ref const(GraphT) g, Vertex e) { 142 _visitor.finishVertex(g, e); 143 } 144 145 private AStarVisitorT* _visitor; 146 private DistanceMapT* _distanceMap; 147 private const(WeightMapT*) _weightMap; 148 private PredecessorMapT* _predecessorMap; 149 private QueueT* _queue; 150 private ColourMapT* _colourMap; 151 private CostMapT* _costMap; 152 private HeuristicT _heuristic; 153 } 154 155 template aStarSearch(GraphT, 156 VertexDescriptorT, 157 HeuristicT, 158 VisitorT = NullAstarVisitor!GraphT, 159 WeightMapT = real[VertexDescriptorT], 160 PredecessorMapT = VertexDescriptorT[VertexDescriptorT], 161 DistanceMapT = real[VertexDescriptorT]) { 162 void aStarSearch(ref const(GraphT) g, 163 VertexDescriptorT src, 164 HeuristicT heuristic, 165 ref const (WeightMapT) weights, 166 ref PredecessorMapT predecessorMap, 167 VisitorT visitor = VisitorT.init) { 168 static if (is(VertexDescriptorT == size_t)) { 169 auto colourMap = new Colour[g.vertexCount]; 170 auto distanceMap = new real[g.vertexCount]; 171 auto costMap = new real[g.vertexCount]; 172 } 173 else { 174 real[VertexDescriptorT] distanceMap; 175 real[VertexDescriptorT] costMap; 176 Colour[VertexDescriptorT] colourMap; 177 } 178 179 aStarSearch(g, 180 src, 181 heuristic, 182 weights, 183 predecessorMap, 184 visitor, 185 colourMap, 186 distanceMap, 187 costMap); 188 } 189 } 190 191 template aStarSearch(GraphT, 192 VertexDescriptorT, 193 HeuristicT, 194 VisitorT = NullAstarVisitor!GraphT, 195 ColourMapT = Colour[VertexDescriptorT], 196 PredecessorMapT = VertexDescriptorT[VertexDescriptorT], 197 WeightMapT = real[VertexDescriptorT], 198 DistanceMapT = real[VertexDescriptorT], 199 CostMapT = real[VertexDescriptorT]) { 200 void aStarSearch(ref const(GraphT) g, 201 VertexDescriptorT src, 202 HeuristicT heuristic, 203 ref const(WeightMapT) weights, 204 ref PredecessorMapT predecessorMap, 205 VisitorT visitor, 206 ref ColourMapT colourMap, 207 ref DistanceMapT distanceMap, 208 ref CostMapT costMap) { 209 foreach(v; g.vertices) { 210 visitor.initVertex(g, v); 211 distanceMap[v] = real.infinity; 212 costMap[v] = real.infinity; 213 predecessorMap[v] = v; 214 colourMap[v] = Colour.White; 215 } 216 distanceMap[src] = 0.0; 217 218 aStarSearchNoInit(g, 219 src, 220 heuristic, 221 weights, 222 predecessorMap, 223 visitor, 224 colourMap, 225 distanceMap, 226 costMap); 227 } 228 } 229 230 template aStarSearchNoInit(GraphT, 231 VertexDescriptorT, 232 HeuristicT, 233 VisitorT = NullAstarVisitor!GraphT, 234 ColourMapT = Colour[VertexDescriptorT], 235 PredecessorMapT = VertexDescriptorT[VertexDescriptorT], 236 WeightMapT = real[VertexDescriptorT], 237 DistanceMapT = real[VertexDescriptorT], 238 CostMapT = real[VertexDescriptorT]) { 239 static assert(isGraph!GraphT); 240 241 alias EdgeDescriptorT = GraphT.EdgeDescriptor; 242 243 static assert(isPropertyMap!(ColourMapT, VertexDescriptorT, Colour)); 244 static assert(isPropertyMap!(PredecessorMapT, VertexDescriptorT, VertexDescriptorT)); 245 static assert(isReadablePropertyMap!(WeightMapT, EdgeDescriptorT, real)); 246 static assert(isPropertyMap!(CostMapT, VertexDescriptorT, real)); 247 248 static assert(isCallable!HeuristicT); 249 250 251 void aStarSearchNoInit(ref const(GraphT) g, 252 VertexDescriptorT src, 253 HeuristicT heuristic, 254 ref const(WeightMapT) weights, 255 ref PredecessorMapT predecessorMap, 256 VisitorT visitor, 257 ref ColourMapT colourMap, 258 ref DistanceMapT distanceMap, 259 ref CostMapT costMap) { 260 alias QueueT = VertexQueue!(GraphT, CostMapT); 261 auto queue = QueueT(costMap); 262 263 alias AStar = AStarBfsVisitor!(GraphT, 264 QueueT, 265 VisitorT, 266 DistanceMapT, 267 PredecessorMapT, 268 WeightMapT, 269 CostMapT, 270 ColourMapT, 271 HeuristicT); 272 auto astar = AStar(visitor, 273 distanceMap, 274 weights, 275 predecessorMap, 276 colourMap, 277 queue, 278 costMap, 279 heuristic); 280 breadthFirstVisit(g, src, colourMap, queue, astar); 281 } 282 } 283 284 template extractPath(VertexDescriptorT, 285 PredecessorMapT = VertexDescriptorT[VertexDescriptorT], 286 287 ) { 288 import std.algorithm : reverse; 289 import std.container : Array; 290 291 static assert(isPropertyMap!(PredecessorMapT, VertexDescriptorT, VertexDescriptorT)); 292 293 Array!VertexDescriptorT extractPath(ref PredecessorMapT predecessorMap, 294 VertexDescriptorT dst) { 295 auto path = Array!VertexDescriptorT(); 296 auto v = dst; 297 while (true) { 298 path.insertBack(v); 299 auto prev = predecessorMap[v]; 300 if (prev == v) { 301 break; 302 } 303 v = prev; 304 } 305 reverse(path[]); 306 return path; 307 } 308 } 309 310 version(unittest) { 311 import std.algorithm : reverse; 312 import std.container : Array; 313 314 private struct Node { 315 char id; 316 int weight; 317 }; 318 319 private struct TestGraph(GraphT) { 320 GraphT graph; 321 GraphT.VertexDescriptor[char] vertices; 322 323 alias graph this; 324 325 GraphT.VertexDescriptor opIndex(char id) { 326 return vertices[id]; 327 } 328 }; 329 330 private TestGraph!GraphT MakeTestGraph(GraphT)() { 331 // Test data from: 332 // https://www.slideshare.net/hemak15/lecture-14-heuristic-searcha-star-algorithm 333 GraphT graph; 334 auto s = graph.addVertex(Node('s', 17)); 335 auto a = graph.addVertex(Node('a', 10)); 336 auto b = graph.addVertex(Node('b', 13)); 337 auto c = graph.addVertex(Node('c', 4)); 338 auto d = graph.addVertex(Node('d', 2)); 339 auto e = graph.addVertex(Node('e', 4)); 340 auto f = graph.addVertex(Node('f', 1)); 341 auto g = graph.addVertex(Node('g', 0)); 342 auto h = graph.addVertex(Node('h', 99)); 343 344 // /-> a 345 // / \ 346 // / e h 347 // / / \ 348 // s ----> b f -> g 349 // \ \ / 350 // \ d 351 // \ / 352 // \-> c 353 354 graph.addEdge(s, a, 6); 355 graph.addEdge(s, b, 5); 356 graph.addEdge(s, c, 10); 357 graph.addEdge(a, e, 6); 358 graph.addEdge(b, e, 6); 359 graph.addEdge(c, d, 6); 360 graph.addEdge(b, d, 7); 361 graph.addEdge(d, f, 6); 362 graph.addEdge(e, f, 4); 363 graph.addEdge(f, g, 3); 364 365 GraphT.VertexDescriptor[char] vertices; 366 foreach (v; graph.vertices()) { 367 vertices[graph[v].id] = v; 368 } 369 return TestGraph!GraphT(graph, vertices); 370 } 371 372 private alias G = AdjacencyList!(VecS, VecS, DirectedS, Node, int); 373 private alias Vertex = G.VertexDescriptor; 374 private alias Edge = G.EdgeDescriptor; 375 376 class FoundTarget : Throwable { 377 this() { super("found it"); } 378 } 379 380 struct Visitor { 381 mixin NullAstarVisitor!G; 382 383 Vertex _target; 384 385 this(Vertex target) { 386 _target = target; 387 } 388 389 void examineVertex(ref const(G) g, Vertex v) { 390 if (v == _target) { 391 throw new FoundTarget; 392 } 393 } 394 } 395 } 396 397 unittest { 398 writeln("A*: Finds a path"); 399 400 auto testGraph = MakeTestGraph!G(); 401 auto predecessors = new Vertex[testGraph.vertexCount]; 402 403 auto start = testGraph['s']; 404 auto destination = testGraph['g']; 405 406 real heuristic(Vertex v) { return testGraph.graph[v].weight; } 407 408 try { 409 aStarSearch(testGraph, 410 start, 411 &heuristic, 412 testGraph.graph, 413 predecessors, 414 Visitor(destination)); 415 assert (false, "Path not found"); 416 } 417 catch(FoundTarget) { 418 } 419 auto path = extractPath(predecessors, destination); 420 auto v = testGraph.vertices; 421 const auto expectedPath = Array!Vertex([v['s'], v['b'], v['e'], v['f'], v['g']]); 422 assert (path == expectedPath); 423 } 424 425 unittest { 426 writeln("A*: Terminates on unreachable node"); 427 428 auto testGraph = MakeTestGraph!G(); 429 auto g = &testGraph.graph; 430 auto predecessor = new Vertex[g.vertexCount]; 431 432 auto start = testGraph.vertices['s']; 433 auto destination = testGraph.vertices['h']; 434 435 real heuristic(Vertex v) { return (*g)[v].weight; } 436 try { 437 aStarSearch(*g, 438 start, 439 &heuristic, 440 *g, 441 predecessor, 442 Visitor(destination)); 443 } 444 catch(FoundTarget) { 445 assert (false, "Should never find an answer"); 446 } 447 }