rs_graph/search/biastar.rs
1/*
2 * Copyright (c) 2019, 2021-2022 Frank Fischer <frank-fischer@shadow-soft.de>
3 *
4 * This program is free software: you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation, either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>
16 */
17
18//! Bidirectional A*-search.
19//!
20//! This module implements a bidirectional A*-search for finding a shortest path
21//! between two nodes starting from both endpoints. Each node may be assigned a
22//! potential (or "heuristic value") estimating the distance to the target
23//! nodes. The potential $h\colon V \to \mathbb{R}$ must satisfy
24//! \\[ w(u,v) - h(u) + h(v) \ge 0, (u,v) \in E \\]
25//! where $w\colon E \to \mathbb{R}$ are the weights (or lengths) of the edges.
26//! (The relation must hold for both directions in case the graph is
27//! undirected).
28//!
29//! If $s,t \in V$ are the start and destination nodes of the path,
30//! respectively, and $h_s\colon V \to \mathbb{R}$ and $h_t\colon V \to
31//! \mathbb{R}$ are lower bounds for the distance from each node $u \in V$ to
32//! $s$ and $t$, then the canonical choice of $h$ is \\[ h\colon u \to
33//! \mathbb{R}, u \mapsto \frac12 (h_s(u) - h_t(u)). \\]
34//!
35//! # Example
36//!
37//! ```
38//! use rs_graph::traits::*;
39//! use rs_graph::search::biastar;
40//! use rs_graph::string::{from_ascii, Data};
41//! use rs_graph::LinkedListGraph;
42//!
43//! let Data {
44//! graph: g,
45//! weights,
46//! nodes,
47//! } = from_ascii::<LinkedListGraph>(
48//! r"
49//! *--2--*--2--*--2--*--2--*--2--*--2--*--2--*--2--*--2--*
50//! | | | | | | | | | |
51//! 2 2 2 2 2 2 2 2 2 2
52//! | | | | | | | | | |
53//! *--2--*--2--*--2--*--2--*--3--e--2--f--2--t--2--*--2--*
54//! | | | | | | | | | |
55//! 2 2 2 2 2 2 3 2 2 2
56//! | | | | | | | | | |
57//! *--2--*--2--*--3--*--3--c--2--d--2--*--3--*--2--*--2--*
58//! | | | | | | | | | |
59//! 2 2 2 2 2 3 2 2 2 2
60//! | | | | | | | | | |
61//! *--2--*--2--s--2--a--2--b--2--*--2--*--3--*--2--*--2--*
62//! | | | | | | | | | |
63//! 2 2 2 2 2 2 2 2 2 2
64//! | | | | | | | | | |
65//! *--2--*--2--*--2--*--2--*--2--*--2--*--2--*--2--*--2--*
66//! ",
67//! )
68//! .unwrap();
69//!
70//! let s = g.id2node(nodes[&'s']);
71//! let t = g.id2node(nodes[&'t']);
72//!
73//! // nodes are numbered row-wise -> get node coordinates
74//! let coords = |u| ((g.node_id(u) % 10) as isize, (g.node_id(u) / 10) as isize);
75//!
76//! let (xs, ys) = coords(s);
77//! let (xt, yt) = coords(t);
78//!
79//! // Manhatten distance heuristic
80//! let manh_heur = |u| {
81//! let (x, y) = coords(u);
82//! 0.5 * (((x - xt).abs() + (y - yt).abs()) as f64 - ((x - xs).abs() + (y - ys).abs()) as f64)
83//! };
84//!
85//! let (path, dist) = biastar::find_undirected_path(&g, s, t, |e| weights[e.index()] as f64, manh_heur).unwrap();
86//!
87//! assert!((dist - 14.0).abs() < 1e-6);
88//!
89//! let mut pathnodes = vec![s];
90//! for e in path {
91//! let uv = g.enodes(e);
92//! if uv.0 == *pathnodes.last().unwrap() {
93//! pathnodes.push(uv.1);
94//! } else {
95//! pathnodes.push(uv.0);
96//! }
97//! }
98//!
99//! assert_eq!(pathnodes, "sabcdeft".chars().map(|c| g.id2node(nodes[&c])).collect::<Vec<_>>());
100//!
101//! // verify that we did not go too far in the "wrong" direction
102//! for (v, _, _) in biastar::start_undirected(&g, s, t, |e| weights[e.index()] as f64, manh_heur) {
103//! let (x, y) = coords(v);
104//! assert!(x + 1 >= xs && x <= xt + 1 && y + 1 >= yt && y <= ys + 1);
105//! }
106//! ```
107
108use crate::adjacencies::{Adjacencies, InEdges, Neighbors, OutEdges};
109use crate::collections::{BinHeap, ItemMap, ItemPriQueue};
110pub use crate::search::astar::AStarHeuristic as Heuristic;
111use crate::search::path_from_incomings;
112use crate::traits::{Digraph, Graph};
113
114use either::Either::{self, Left, Right};
115use num_traits::Zero;
116
117use std::cmp::Ordering;
118use std::collections::HashMap;
119use std::hash::Hash;
120use std::ops::{Add, Neg, Sub};
121
122pub use super::astar::DefaultData;
123
124/// Direction of search.
125#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
126pub enum Direction<E> {
127 /// Edge in forward search.
128 Forward(E),
129 /// Edge in backward search.
130 Backward(E),
131}
132
133/// Predecessor edge information.
134#[derive(Clone, Copy, Debug)]
135pub struct BiData<E, D, H> {
136 /// adjacent edge
137 edge: E,
138 /// distance to source or sink (None if never seen).
139 distance: D,
140 /// the lower bound of this node
141 lower: H,
142}
143
144impl<E, D, H> PartialEq for BiData<E, D, H>
145where
146 D: PartialEq,
147{
148 fn eq(&self, data: &Self) -> bool {
149 self.distance.eq(&data.distance)
150 }
151}
152
153impl<E, D, H> PartialOrd for BiData<E, D, H>
154where
155 D: PartialOrd + Clone,
156 H: Add<D, Output = D> + Clone,
157{
158 fn partial_cmp(&self, data: &Self) -> Option<Ordering> {
159 (self.lower.clone() + self.distance.clone()).partial_cmp(&(data.lower.clone() + data.distance.clone()))
160 }
161}
162
163/// Information about the meeting edge.
164struct Meet<N, E, D> {
165 /// The destination node of the connecting edge.
166 node: N,
167 /// The connecting edge.
168 edge: E,
169 /// The forward distance of the node.
170 fwd_distance: D,
171 /// The total distance of the path.
172 total_distance: D,
173}
174
175/// Iterator for visiting edges in A*-order.
176pub struct BiAStar<'a, Aout, Ain, D, W, M, P, H>
177where
178 Aout: Adjacencies<'a>,
179 Ain: Adjacencies<'a, Node = Aout::Node, Edge = Aout::Edge>,
180 M: ItemMap<Direction<Aout::Node>, Either<P::Item, D>>,
181 P: ItemPriQueue<Direction<Aout::Node>, BiData<Aout::Edge, D, H::Result>>,
182 D: Copy,
183 W: Fn(Aout::Edge) -> D,
184 H: Heuristic<Aout::Node>,
185 H::Result: Copy,
186{
187 adjout: Aout,
188 adjin: Ain,
189 nodes: M,
190 pqueue: P,
191 weights: W,
192 heur: H,
193 /// The meet information, i.e. the final connecting edge.
194 meet: Option<Meet<Aout::Node, Aout::Edge, D>>,
195 /// The currently top-most (i.e. smallest) value in forward direction on the heap.
196 top_fwd: D,
197 /// The currently top-most (i.e. smallest) value in backward direction on the heap.
198 top_bwd: D,
199}
200
201/// Default map type to be used in an A* search.
202///
203/// - `A` is the graph type information
204/// - `D` is the type of distance values
205/// - `H` is the type of heuristic values
206pub type DefaultMap<'a, A, D, H> = HashMap<
207 Direction<<A as Adjacencies<'a>>::Node>,
208 Either<
209 <BinHeap<Direction<<A as Adjacencies<'a>>::Node>, BiData<<A as Adjacencies<'a>>::Edge, D, H>> as ItemPriQueue<
210 Direction<<A as Adjacencies<'a>>::Node>,
211 BiData<<A as Adjacencies<'a>>::Edge, D, H>,
212 >>::Item,
213 D,
214 >,
215>;
216
217/// Default priority queue type to be used in an A* search.
218///
219/// - `A` is the graph type information
220/// - `D` is the type of distance values
221/// - `H` is the type of heuristic values
222/// - `ID` is used for identifying items on the heap internally
223pub type DefaultPriQueue<'a, A, D, H, ID = u32> =
224 BinHeap<Direction<<A as Adjacencies<'a>>::Node>, BiData<<A as Adjacencies<'a>>::Edge, D, H>, ID>;
225
226/// BiAStar iterator with default data structures.
227pub type BiAStarDefault<'a, Aout, Ain, D, W, H> = BiAStar<
228 'a,
229 Aout,
230 Ain,
231 D,
232 W,
233 DefaultMap<'a, Aout, D, <H as Heuristic<<Aout as Adjacencies<'a>>::Node>>::Result>,
234 DefaultPriQueue<'a, Aout, D, <H as Heuristic<<Aout as Adjacencies<'a>>::Node>>::Result>,
235 H,
236>;
237
238/// Start and return a bidirectional A*-iterator using default data structures.
239///
240/// This is a convenience wrapper around [`start_with_data`] using the default
241/// data structures [`DefaultData`].
242///
243/// # Parameters
244/// - `adjout`: adjacency information for the forward search (i.e outgoing edges)
245/// - `adjin`: adjacency information for the backward search (i.e incoming edges)
246/// - `src`: the source node at which the path should start.
247/// - `snk`: the snk node at which the path should end.
248/// - `weights`: the weight function for each edge
249/// - `heur`: the heuristic used in the search
250pub fn start<'a, Aout, Ain, D, W, H>(
251 adjout: Aout,
252 adjin: Ain,
253 src: Aout::Node,
254 snk: Aout::Node,
255 weights: W,
256 heur: H,
257) -> BiAStarDefault<'a, Aout, Ain, D, W, H>
258where
259 Aout: Adjacencies<'a>,
260 Aout::Node: Hash + Eq,
261 Ain: Adjacencies<'a, Node = Aout::Node, Edge = Aout::Edge>,
262 D: Copy + Zero + PartialOrd,
263 W: Fn(Aout::Edge) -> D,
264 H: Heuristic<Aout::Node>,
265 H::Result: Add<D, Output = D> + Add<H::Result, Output = H::Result> + Neg<Output = H::Result>,
266{
267 start_with_data(adjout, adjin, src, snk, weights, heur, DefaultData::default())
268}
269
270/// Start and return a bidirectional A*-iterator.
271///
272/// The returned iterator traverses the edges in the order of a bidirectional
273/// A*-search. The iterator returns the next node, its incoming edge with
274/// direction information and the distance to the start node or end node
275/// depending on the direction.
276///
277/// The heuristic is a assigning a potential to each node. The potential of all
278/// nodes must be so that $w(u,v) - h(u) + h(v) \ge 0$ for all edges $(u,v) \in
279/// E$. The value returned by the heuristic must be compatible with the distance
280/// type, i.e., is must be possible to compute the sum of both.
281///
282/// Note that the start and end nodes are *not* returned by the iterator.
283///
284/// The algorithm requires a pair `(M, P)` with `M` implementing
285/// [`ItemMap<Direction<Node>, Item>`][crate::collections::ItemMap], and `P`
286/// implementing [`ItemPriQueue<Direction<Node>,
287/// D>`][crate::collections::ItemStack] as internal data structures. The map is
288/// used to store information about the last edge on a shortest path for each
289/// reachable node. The priority queue is used the handle the nodes in the
290/// correct order. The data structures can be reused for multiple searches.
291///
292/// # Parameters
293/// - `adjout`: adjacency information for the forward search (i.e outgoing edges)
294/// - `adjin`: adjacency information for the backward search (i.e incoming edges)
295/// - `src`: the source node at which the path should start.
296/// - `snk`: the snk node at which the path should end.
297/// - `weights`: the weight function for each edge
298/// - `heur`: the heuristic used in the search
299/// - `data`: the data structures
300pub fn start_with_data<'a, Aout, Ain, D, W, H, M, P>(
301 adjout: Aout,
302 adjin: Ain,
303 src: Aout::Node,
304 snk: Aout::Node,
305 weights: W,
306 heur: H,
307 data: (M, P),
308) -> BiAStar<'a, Aout, Ain, D, W, M, P, H>
309where
310 Aout: Adjacencies<'a>,
311 Ain: Adjacencies<'a, Node = Aout::Node, Edge = Aout::Edge>,
312 D: Copy + PartialOrd + Zero,
313 W: Fn(Aout::Edge) -> D,
314 M: ItemMap<Direction<Aout::Node>, Either<P::Item, D>>,
315 P: ItemPriQueue<Direction<Aout::Node>, BiData<Aout::Edge, D, H::Result>>,
316 H: Heuristic<Aout::Node>,
317 H::Result: Add<D, Output = D> + Add<H::Result, Output = H::Result> + Neg<Output = H::Result>,
318{
319 let (mut nodes, mut pqueue) = data;
320 pqueue.clear();
321 nodes.clear();
322
323 if src == snk {
324 // if src == snk then we do not start the search at all
325 return BiAStar {
326 adjout,
327 adjin,
328 nodes,
329 pqueue,
330 weights,
331 heur,
332 meet: None,
333 top_fwd: D::zero(),
334 top_bwd: D::zero(),
335 };
336 }
337
338 nodes.insert(Direction::Forward(src), Right(D::zero()));
339 nodes.insert(Direction::Backward(snk), Right(D::zero()));
340
341 // insert neighbors of source
342 for (e, v) in adjout.neighs(src) {
343 let dir_v = Direction::Forward(v);
344 let d = (weights)(e);
345 match nodes.get_mut(dir_v) {
346 Some(Left(item_v)) => {
347 // node is known but unhandled
348 let (distance, lower) = {
349 let data = pqueue.value(item_v);
350 (data.distance, data.lower)
351 };
352
353 if d < distance {
354 pqueue.decrease_key(
355 item_v,
356 BiData {
357 edge: e,
358 distance: d,
359 lower,
360 },
361 );
362 }
363 }
364 None => {
365 // node is unknown
366 let item_v = pqueue.push(
367 dir_v,
368 BiData {
369 edge: e,
370 distance: d,
371 lower: heur.call(v),
372 },
373 );
374 nodes.insert(dir_v, Left(item_v));
375 }
376 _ => (), // node has already been handled
377 }
378 }
379
380 let mut meet: Option<Meet<_, _, _>> = None;
381 // insert neighbors of sink
382 for (e, v) in adjin.neighs(snk) {
383 let dir_v = Direction::Backward(v);
384 let d = (weights)(e);
385 if v == src {
386 // found a possible first path, this is only possible for (src, snk)
387 // edges and the length of this path is the edge weight
388 if meet.as_ref().map(|m| d < m.total_distance).unwrap_or(true) {
389 // found a better path -> save
390 meet = Some(Meet {
391 node: snk,
392 edge: e,
393 fwd_distance: d,
394 total_distance: d,
395 });
396 }
397 }
398 match nodes.get_mut(dir_v) {
399 Some(Left(item_v)) => {
400 // node is known but unhandled
401 let (distance, lower) = {
402 let data = pqueue.value(item_v);
403 (data.distance, data.lower)
404 };
405 if d < distance {
406 pqueue.decrease_key(
407 item_v,
408 BiData {
409 edge: e,
410 distance: d,
411 lower,
412 },
413 );
414 }
415 }
416 None => {
417 // node is unknown
418 let item_v = pqueue.push(
419 dir_v,
420 BiData {
421 edge: e,
422 distance: d,
423 lower: -heur.call(v),
424 },
425 );
426 nodes.insert(dir_v, Left(item_v));
427 }
428 _ => (), // node has already been handled
429 }
430 }
431
432 BiAStar {
433 adjout,
434 adjin,
435 nodes,
436 pqueue,
437 weights,
438 heur,
439 meet,
440 top_fwd: D::zero(),
441 top_bwd: D::zero(),
442 }
443}
444
445impl<'a, Aout, Ain, D, W, M, P, H> Iterator for BiAStar<'a, Aout, Ain, D, W, M, P, H>
446where
447 Aout: Adjacencies<'a>,
448 Ain: Adjacencies<'a, Node = Aout::Node, Edge = Aout::Edge>,
449 D: Copy + PartialOrd + Add<D, Output = D> + Sub<D, Output = D> + Zero,
450 W: Fn(Aout::Edge) -> D,
451 M: ItemMap<Direction<Aout::Node>, Either<P::Item, D>>,
452 P: ItemPriQueue<Direction<Aout::Node>, BiData<Aout::Edge, D, H::Result>>,
453 H: Heuristic<Aout::Node>,
454 H::Result: Add<D, Output = D> + Add<H::Result, Output = H::Result> + Neg<Output = H::Result>,
455{
456 type Item = (Aout::Node, Direction<Aout::Edge>, D);
457
458 fn next(&mut self) -> Option<Self::Item> {
459 if let Some((dir_u, data)) = self.pqueue.pop_min() {
460 // node is not in the queue anymore, forget its item
461 self.nodes.insert_or_replace(dir_u, Right(data.distance));
462 let (distance, edge) = (data.distance, data.edge);
463
464 // stopping test, first update forward or backward bound
465 //
466 // Note: `top_fwd` and `top_bwd` are actually lower bounds on the
467 // values on the heap because the value with that value has just
468 // been removed ... anyway, it is good enough for us
469 if let Direction::Forward(_) = dir_u {
470 self.top_fwd = data.lower + data.distance;
471 } else {
472 self.top_bwd = data.lower + data.distance;
473 };
474
475 if self
476 .meet
477 .as_ref()
478 .map(|m| m.total_distance <= self.top_fwd + self.top_bwd)
479 .unwrap_or(false)
480 {
481 // stopping condition met, we cannot find a better path
482 self.pqueue.clear();
483
484 // return the final connecting edge
485 let meet = self.meet.as_ref().unwrap();
486 return Some((meet.node, Direction::Forward(meet.edge), meet.fwd_distance));
487 }
488
489 match dir_u {
490 // forward search
491 Direction::Forward(u) => {
492 // look for neighbors
493 for (e, v) in self.adjout.neighs(u) {
494 let dir_v = Direction::Forward(v);
495 let edge_weight = (self.weights)(e);
496 let d = distance + edge_weight;
497 if let Some(Right(rdistance)) = self.nodes.get(Direction::Backward(v)) {
498 // check whether we found a better path
499 let new_dist = *rdistance + distance + edge_weight;
500 if self.meet.as_ref().map(|m| new_dist < m.total_distance).unwrap_or(true) {
501 // found a better path -> save
502 self.meet = Some(Meet {
503 node: v,
504 edge: e,
505 fwd_distance: d,
506 total_distance: new_dist,
507 });
508 }
509 }
510 match self.nodes.get_mut(dir_v) {
511 Some(Left(item_v)) => {
512 // node is known but unhandled
513 let (distance, lower) = {
514 let data = self.pqueue.value(item_v);
515 (data.distance, data.lower)
516 };
517 if d < distance {
518 self.pqueue.decrease_key(
519 item_v,
520 BiData {
521 edge: e,
522 distance: d,
523 lower,
524 },
525 );
526 }
527 }
528 None => {
529 // node is unknown
530 let item_v = self.pqueue.push(
531 dir_v,
532 BiData {
533 edge: e,
534 distance: d,
535 lower: self.heur.call(v),
536 },
537 );
538 self.nodes.insert(dir_v, Left(item_v));
539 }
540 _ => (), // node has already been handled
541 }
542 }
543 }
544 // backward search
545 Direction::Backward(u) => {
546 // look for neighbors
547 for (e, v) in self.adjin.neighs(u) {
548 assert!((-self.heur.call(v) + self.heur.call(u)) + (self.weights)(e) >= D::zero());
549 let dir_v = Direction::Backward(v);
550 let edge_weight = (self.weights)(e);
551 let d = distance + edge_weight;
552 if let Some(Right(rdistance)) = self.nodes.get(Direction::Forward(v)) {
553 // check whether we found a better path
554 let new_dist = *rdistance + distance + edge_weight;
555 if self.meet.as_ref().map(|m| new_dist < m.total_distance).unwrap_or(true) {
556 // found a better path -> save
557 self.meet = Some(Meet {
558 node: u,
559 edge: e,
560 fwd_distance: *rdistance + edge_weight,
561 total_distance: new_dist,
562 });
563 }
564 }
565 match self.nodes.get_mut(dir_v) {
566 Some(Left(item_v)) => {
567 // node is known but unhandled
568 let (distance, lower) = {
569 let data = self.pqueue.value(item_v);
570 (data.distance, data.lower)
571 };
572 if d < distance {
573 self.pqueue.decrease_key(
574 item_v,
575 BiData {
576 edge: e,
577 distance: d,
578 lower,
579 },
580 );
581 }
582 }
583 None => {
584 // node is unknown
585 let item_v = self.pqueue.push(
586 dir_v,
587 BiData {
588 edge: e,
589 distance: d,
590 lower: -self.heur.call(v),
591 },
592 );
593 self.nodes.insert(dir_v, Left(item_v));
594 }
595 _ => (), // node has already been handled
596 }
597 }
598 }
599 }
600
601 match dir_u {
602 Direction::Forward(u) => Some((u, Direction::Forward(edge), distance)),
603 Direction::Backward(u) => Some((u, Direction::Backward(edge), distance)),
604 }
605 } else {
606 None
607 }
608 }
609}
610
611impl<'a, Aout, Ain, D, W, M, P, H> BiAStar<'a, Aout, Ain, D, W, M, P, H>
612where
613 Aout: Adjacencies<'a>,
614 Ain: Adjacencies<'a, Node = Aout::Node, Edge = Aout::Edge>,
615 D: Copy + PartialOrd + Add<D, Output = D> + Sub<D, Output = D>,
616 W: Fn(Aout::Edge) -> D,
617 M: ItemMap<Direction<Aout::Node>, Either<P::Item, D>>,
618 P: ItemPriQueue<Direction<Aout::Node>, BiData<Aout::Edge, D, H::Result>>,
619 H: Heuristic<Aout::Node>,
620 H::Result: Add<D, Output = D> + Neg<Output = H::Result>,
621{
622 /// Return the meet node.
623 ///
624 /// This is the node where forward and backward search met.
625 fn meet(&self) -> Option<Aout::Node> {
626 self.meet.as_ref().map(|m| m.node)
627 }
628
629 /// Return the value of the shortest path.
630 fn value(&self) -> Option<D> {
631 self.meet.as_ref().map(|m| m.total_distance)
632 }
633}
634
635/// Start a bidirectional A*-search on an undirected graph.
636///
637/// Each edge can be traversed in both directions with the same weight.
638///
639/// This is a convenience wrapper to start the search on an undirected graph
640/// with the default data structures.
641///
642/// # Parameters
643///
644/// - `g`: the undirected graph
645/// - `src`: the source node at which the path should start.
646/// - `snk`: the snk node at which the path should end.
647/// - `weights`: the weight function for each edge
648/// - `heur`: the heuristic used in the search
649pub fn start_undirected<'a, G, D, W, H>(
650 g: &'a G,
651 src: G::Node<'a>,
652 snk: G::Node<'a>,
653 weights: W,
654 heur: H,
655) -> BiAStarDefault<'a, Neighbors<'a, G>, Neighbors<'a, G>, D, W, H>
656where
657 G: Graph,
658 G::Node<'a>: Hash,
659 D: Copy + PartialOrd + Zero,
660 W: Fn(G::Edge<'a>) -> D,
661 H: Heuristic<G::Node<'a>>,
662 H::Result: Add<D, Output = D> + Add<H::Result, Output = H::Result> + Neg<Output = H::Result>,
663{
664 start(Neighbors(g), Neighbors(g), src, snk, weights, heur)
665}
666
667/// Run a bidirectional A*-search on an undirected graph and return the path.
668///
669/// Each edge can be traversed in both directions with the same weight.
670///
671/// This is a convenience wrapper to run the search on an undirected graph
672/// with the default data structures and obtain the shortest path.
673///
674/// # Parameters
675///
676/// - `g`: the undirected graph
677/// - `src`: the source node at which the path should start.
678/// - `snk`: the snk node at which the path should end.
679/// - `weights`: the weight function for each edge
680/// - `heur`: the heuristic used in the search
681///
682/// The function returns the edges on the path and its length.
683pub fn find_undirected_path<'a, G, D, W, H>(
684 g: &'a G,
685 src: G::Node<'a>,
686 snk: G::Node<'a>,
687 weights: W,
688 heur: H,
689) -> Option<(Vec<G::Edge<'a>>, D)>
690where
691 G: Graph,
692 G::Node<'a>: Hash,
693 D: Copy + PartialOrd + Zero + Add<D, Output = D> + Sub<D, Output = D>,
694 W: Fn(G::Edge<'a>) -> D,
695 H: Heuristic<G::Node<'a>>,
696 H::Result: Add<D, Output = D> + Add<H::Result, Output = H::Result> + Neg<Output = H::Result>,
697{
698 if src == snk {
699 return Some((vec![], D::zero()));
700 }
701 // run search until a node has been seen from both sides
702 let mut incoming_edges = HashMap::new();
703 let mut it = start_undirected(g, src, snk, weights, heur);
704 for (u, dir_e, _) in it.by_ref() {
705 match dir_e {
706 Direction::Forward(e) => incoming_edges.insert(Direction::Forward(u), e),
707 Direction::Backward(e) => incoming_edges.insert(Direction::Backward(u), e),
708 };
709 }
710
711 it.meet().map(|meet| {
712 let mut path = path_from_incomings(meet, |u| {
713 incoming_edges
714 .get(&Direction::Forward(u))
715 .map(|&e| (e, g.enodes(e)))
716 .map(|(e, (v, w))| (e, if v == u { w } else { v }))
717 })
718 .collect::<Vec<_>>();
719 path.reverse();
720 path.extend(path_from_incomings(meet, |u| {
721 incoming_edges
722 .get(&Direction::Backward(u))
723 .map(|&e| (e, g.enodes(e)))
724 .map(|(e, (v, w))| (e, if v == u { w } else { v }))
725 }));
726 (path, it.value().unwrap())
727 })
728}
729
730/// Start a bidirectional A*-search on a directed graph.
731///
732/// This is a convenience wrapper to start the search on an directed graph
733/// with the default data structures.
734///
735/// # Parameters
736///
737/// - `g`: the directed graph
738/// - `src`: the source node at which the path should start.
739/// - `snk`: the snk node at which the path should end.
740/// - `weights`: the weight function for each edge
741/// - `heur`: the heuristic used in the search
742pub fn start_directed<'a, G, D, W, H>(
743 g: &'a G,
744 src: G::Node<'a>,
745 snk: G::Node<'a>,
746 weights: W,
747 heur: H,
748) -> BiAStarDefault<'a, OutEdges<'a, G>, InEdges<'a, G>, D, W, H>
749where
750 G: Digraph,
751 G::Node<'a>: Hash,
752 D: Copy + PartialOrd + Zero,
753 W: Fn(G::Edge<'a>) -> D,
754 H: Heuristic<G::Node<'a>>,
755 H::Result: Add<D, Output = D> + Add<H::Result, Output = H::Result> + Neg<Output = H::Result>,
756{
757 start(OutEdges(g), InEdges(g), src, snk, weights, heur)
758}
759
760/// Run a bidirectional A*-search on an directed graph and return the path.
761///
762/// This is a convenience wrapper to run the search on an directed graph
763/// with the default data structures and obtain the shortest path.
764///
765/// # Parameters
766///
767/// - `g`: the directed graph
768/// - `src`: the source node at which the path should start.
769/// - `snk`: the snk node at which the path should end.
770/// - `weights`: the weight function for each edge
771/// - `heur`: the heuristic used in the search
772///
773/// The function returns the edges on the path and its length.
774pub fn find_directed_path<'a, G, D, W, H>(
775 g: &'a G,
776 src: G::Node<'a>,
777 snk: G::Node<'a>,
778 weights: W,
779 heur: H,
780) -> Option<(Vec<G::Edge<'a>>, D)>
781where
782 G: Digraph,
783 G::Node<'a>: Hash,
784 D: Copy + PartialOrd + Zero + Add<D, Output = D> + Sub<D, Output = D>,
785 W: Fn(G::Edge<'a>) -> D,
786 H: Heuristic<G::Node<'a>>,
787 H::Result: Add<D, Output = D> + Add<H::Result, Output = H::Result> + Neg<Output = H::Result>,
788{
789 if src == snk {
790 return Some((vec![], D::zero()));
791 }
792 // run search until a node has been seen from both sides
793 let mut incoming_edges = HashMap::new();
794 let mut it = start_directed(g, src, snk, weights, heur);
795 for (u, dir_e, _) in it.by_ref() {
796 match dir_e {
797 Direction::Forward(e) => incoming_edges.insert(Direction::Forward(u), e),
798 Direction::Backward(e) => incoming_edges.insert(Direction::Backward(u), e),
799 };
800 }
801
802 it.meet().map(|meet| {
803 let mut path = path_from_incomings(meet, |u| {
804 incoming_edges.get(&Direction::Forward(u)).map(|&e| (e, g.src(e)))
805 })
806 .collect::<Vec<_>>();
807 path.reverse();
808 path.extend(path_from_incomings(meet, |u| {
809 incoming_edges.get(&Direction::Backward(u)).map(|&e| (e, g.snk(e)))
810 }));
811
812 (path, it.value().unwrap())
813 })
814}
815
816#[test]
817fn test_biastar() {
818 use crate::search::biastar;
819 use crate::string::{from_ascii, Data};
820 use crate::traits::*;
821 use crate::LinkedListGraph;
822
823 let Data {
824 graph: g,
825 weights,
826 nodes,
827 } = from_ascii::<LinkedListGraph>(
828 r"
829 *--2--*--2--*--2--*--2--*--2--*--2--*--2--*--2--*--2--*
830 | | | | | | | | | |
831 2 2 2 2 2 2 2 2 2 2
832 | | | | | | | | | |
833 *--2--*--2--*--2--*--2--*--3--e--2--f--2--t--2--*--2--*
834 | | | | | | | | | |
835 2 2 2 2 2 2 3 2 2 2
836 | | | | | | | | | |
837 *--2--*--2--*--3--*--3--c--2--d--2--*--3--*--2--*--2--*
838 | | | | | | | | | |
839 2 2 2 2 2 3 2 2 2 2
840 | | | | | | | | | |
841 *--2--*--2--s--2--a--2--b--2--*--2--*--3--*--2--*--2--*
842 | | | | | | | | | |
843 2 2 2 2 2 2 2 2 2 2
844 | | | | | | | | | |
845 *--2--*--2--*--2--*--2--*--2--*--2--*--2--*--2--*--2--*
846 ",
847 )
848 .unwrap();
849
850 let s = g.id2node(nodes[&'s']);
851 let t = g.id2node(nodes[&'t']);
852
853 // nodes are numbered row-wise -> get node coordinates
854 let coords = |u| ((g.node_id(u) % 10) as isize, (g.node_id(u) / 10) as isize);
855
856 let (xs, ys) = coords(s);
857 let (xt, yt) = coords(t);
858
859 // Manhatten distance heuristic
860 let manh_heur = |u| {
861 let (x, y) = coords(u);
862 0.5 * (((x - xt).abs() + (y - yt).abs()) as f64 - ((x - xs).abs() + (y - ys).abs()) as f64)
863 };
864
865 let (path, dist) = biastar::find_undirected_path(&g, s, t, |e| weights[e.index()] as f64, manh_heur).unwrap();
866
867 assert!((dist - 14.0).abs() < 1e-6);
868
869 let mut pathnodes = vec![s];
870 for e in path {
871 let uv = g.enodes(e);
872 if uv.0 == *pathnodes.last().unwrap() {
873 pathnodes.push(uv.1);
874 } else {
875 pathnodes.push(uv.0);
876 }
877 }
878
879 assert_eq!(
880 pathnodes,
881 "sabcdeft".chars().map(|c| g.id2node(nodes[&c])).collect::<Vec<_>>()
882 );
883
884 // verify that we did not go too far in the "wrong" direction
885 for (v, _, _) in biastar::start_undirected(&g, s, t, |e| weights[e.index()] as f64, manh_heur) {
886 let (x, y) = coords(v);
887 assert!(x + 1 >= xs && x <= xt + 1 && y + 1 >= yt && y <= ys + 1);
888 }
889}
890
891#[test]
892fn test_biastar_correct() {
893 use crate::search::biastar;
894 use crate::string::{from_ascii, Data};
895 use crate::traits::*;
896 use crate::LinkedListGraph;
897
898 let Data {
899 graph: g,
900 weights,
901 nodes,
902 } = from_ascii::<LinkedListGraph>(
903 r"
904 b--11--c---1--t
905 | |
906 1 8
907 | |
908 s--1--a--10--*
909 ",
910 )
911 .unwrap();
912
913 let s = g.id2node(nodes[&'s']);
914 let t = g.id2node(nodes[&'t']);
915 let a = g.id2node(nodes[&'a']);
916 let b = g.id2node(nodes[&'b']);
917 let c = g.id2node(nodes[&'c']);
918
919 let (path, dist) = biastar::find_undirected_path(&g, s, t, |e| weights[e.index()] as isize, |_| 0).unwrap();
920
921 let length: usize = path.iter().map(|e| weights[e.index()]).sum();
922 assert_eq!(dist, 14);
923 assert_eq!(length, 14);
924
925 let path = path
926 .into_iter()
927 .map(|e| g.enodes(e))
928 .map(|(u, v)| if g.node_id(u) < g.node_id(v) { (u, v) } else { (v, u) })
929 .collect::<Vec<_>>();
930 assert_eq!(path, vec![(s, a), (b, a), (b, c), (c, t)]);
931}