diff --git a/src/alt.rs b/src/alt.rs index 1050da4..3b3c0c6 100644 --- a/src/alt.rs +++ b/src/alt.rs @@ -12,6 +12,11 @@ pub struct Landmark { #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct LandmarkSet { pub landmarks: Vec, +} + +#[derive(Debug, Clone)] +pub struct LandmarkBestSet<'a> { + pub landmark_set: &'a LandmarkSet, pub best_size: usize, best_landmarks: Vec, } @@ -24,7 +29,7 @@ impl Landmark { }; landmark.node = node; - #[derive(Eq)] + #[derive(Eq, PartialEq)] struct DijkstraElement { index: u32, cost: EdgeCost, @@ -44,40 +49,23 @@ impl Landmark { } } - impl PartialEq for DijkstraElement { - fn eq(&self, other: &Self) -> bool { - self.cost == other.cost - } - } - let mut heap = BinaryHeap::new(); heap.push(DijkstraElement { cost: 0, index: landmark.node.index, }); - let mut counter = 0; - while let Some(DijkstraElement { index, cost }) = heap.pop() { // the heap does not support "update" operations, so we // insert elements again and if they come out of the heap but have // been processed earlier we simply skip them. - if landmark.distances[index as usize] <= cost { + if cost > landmark.distances[index as usize] { continue; }; - counter += 1; - - if counter % 1000 == 0 { - println!("Finished {} nodes", counter); - } - landmark.distances[index as usize] = cost; - let edge_start = graph.edge_offsets[index as usize] as usize; - let edge_end = graph.edge_offsets[(index + 1) as usize] as usize; - - for edge in graph.edges[edge_start..edge_end].iter() { + for edge in graph.get_edges(index as NodeId) { let new_cost = cost + edge.cost; if new_cost < landmark.distances[edge.neighbor as usize] { @@ -86,11 +74,11 @@ impl Landmark { index: edge.neighbor, cost: new_cost, }); + landmark.distances[edge.neighbor as usize] = new_cost; } } } - - // now the shortest paths to all reachable nodes is calculated. + // now the costs to all reachable nodes is calculated. landmark } @@ -103,18 +91,28 @@ impl Landmark { let l_to = self.distances[to]; let l_from = self.distances[from]; - if l_to == EdgeCost::MAX { - 0 + if l_to == EdgeCost::MAX || l_from == EdgeCost::MAX { + EdgeCost::MAX } else { - l_to.saturating_sub(l_from) + // since we are working on an undirected graph we can + // use the distances once from and once to the landmark. + // This leads to l_to - l_from and l_from - l_to (as signed subtractions) + // which except for the sign are the same value. + // We can simply take the bigger one, which is handled + // nicely the abs() function + let distance = (l_to as i64 - l_from as i64).abs() as EdgeCost; + //println!( + // "distance from {} to {} via landmark {} is at least {}", + // from, to, self.node.index, distance + //); + distance } } } impl LandmarkSet { - pub fn random_set(size: usize, best_size: usize, graph: &GridGraph) -> Self { + pub fn random_set(size: usize, graph: &GridGraph) -> Self { let mut set = LandmarkSet::default(); - set.best_size = best_size; let nodes = graph.get_random_nodes(size); @@ -125,11 +123,13 @@ impl LandmarkSet { set } +} +impl LandmarkBestSet<'_> { pub fn select_best(&mut self, from: NodeId, to: NodeId) { let mut results = vec![]; - for (index, landmark) in self.landmarks.iter().enumerate() { + for (index, landmark) in self.landmark_set.landmarks.iter().enumerate() { results.push((index, landmark.estimate(from, to))); } @@ -143,18 +143,28 @@ impl LandmarkSet { } pub fn estimate(&self, from: NodeId, to: NodeId) -> EdgeCost { - let mut distance = 0; for index in &self.best_landmarks { - distance = distance.max(self.landmarks[*index].estimate(from, to)); - }; + let candidate = self.landmark_set.landmarks[*index].estimate(from, to); - if distance == 0 { - distance = EdgeCost::MAX; + if candidate == EdgeCost::MAX { + continue; + } + + distance = distance.max(candidate) } - distance + //println!("calculated estimate {:?} for {} to {}", distance, from, to); + distance + } + + pub fn new<'a>(best_size: usize, landmark_set: &'a LandmarkSet) -> LandmarkBestSet<'a> { + LandmarkBestSet { + best_size, + landmark_set, + best_landmarks: Vec::new(), + } } } diff --git a/src/astar.rs b/src/astar.rs index d5e78a1..1d6afe4 100644 --- a/src/astar.rs +++ b/src/astar.rs @@ -4,17 +4,16 @@ use crate::utils::EARTH_RADIUS; use std::cmp::Ordering; use std::collections::BinaryHeap; -pub struct AStar { - pub graph: GridGraph, +pub struct AStar<'a> { + pub graph: &'a GridGraph, } -#[derive(Eq)] +#[derive(Eq, PartialEq)] struct HeapElement { index: u32, cost: EdgeCost, // the cost so far plus the estimated cost until we reach the - // destination + // destination path_cost: EdgeCost, // the cost to reach this node from the start node - ancestor: Option, } impl Ord for HeapElement { @@ -31,12 +30,6 @@ impl PartialOrd for HeapElement { } } -impl PartialEq for HeapElement { - fn eq(&self, other: &Self) -> bool { - self.cost == other.cost - } -} - pub fn estimate_haversine(node: &GraphNode, destination: &GraphNode) -> EdgeCost { // simple haversine distance (node.position.distance_to(&destination.position) * EARTH_RADIUS) as EdgeCost @@ -47,64 +40,72 @@ pub fn estimate_haversine(node: &GraphNode, destination: &GraphNode) -> EdgeCost } pub fn estimate_latitude(node: &GraphNode, destination: &GraphNode) -> EdgeCost { - let lat_dist_a = (node.position.lat - destination.position.lat).abs(); - let lat_dist_b = (destination.position.lat - node.position.lat).abs(); + let lat_dist = (node.position.lat - destination.position.lat).abs(); - (lat_dist_a.min(lat_dist_b) * EARTH_RADIUS) as EdgeCost + (lat_dist * EARTH_RADIUS) as EdgeCost } -impl AStar { - +impl AStar<'_> { pub fn shortest_path(&self, start: &GraphNode, end: &GraphNode, estimate: F) -> Option - where F: Fn(&GraphNode, &GraphNode) -> EdgeCost { + where + F: Fn(&GraphNode, &GraphNode) -> EdgeCost, + { let mut heap = BinaryHeap::new(); heap.push(HeapElement { cost: estimate(start, end), path_cost: 0, index: start.index, - ancestor: None, }); let mut distance = vec![EdgeCost::MAX; self.graph.nodes.len()]; let mut ancestor: Vec> = vec![None; self.graph.nodes.len()]; + distance[start.index as usize] = 0; + let mut popcount = 0; while let Some(HeapElement { index, cost, // the cost value, no longer needed, because it is only important for the - // distance estimate + // distance estimate path_cost, - ancestor: prev, }) = heap.pop() { + //println!("working on node {} with cost {} and path cost {}", index, cost, path_cost); + popcount += 1; // the heap does not support "update" operations, so we // insert elements again and if they come out of the heap but have // been processed earlier (which implies a shorter distance) // we simply skip them. - if distance[index as usize] <= path_cost { + if path_cost > distance[index as usize] { continue; }; - popcount += 1; - - distance[index as usize] = path_cost; - ancestor[index as usize] = prev; if index == end.index { break; } for edge in self.graph.get_edges(index as usize).iter() { + //println!("working on edge {:?}", edge); let new_cost = path_cost + edge.cost; if new_cost < distance[edge.neighbor as usize] { //println!("adding new element to heap"); - heap.push(HeapElement{ + let remaining_cost = estimate(&self.graph.nodes[edge.neighbor as usize], end); + + // check for unreachable nodes + if remaining_cost == EdgeCost::MAX { + continue; + } + + distance[edge.neighbor as usize] = new_cost; + ancestor[edge.neighbor as usize] = Some(index); + + heap.push(HeapElement { index: edge.neighbor, - cost: new_cost + estimate(&self.graph.nodes[edge.neighbor as usize], end), + cost: new_cost + remaining_cost, path_cost: new_cost, - ancestor: Some(index), }); } } @@ -122,10 +123,14 @@ impl AStar { let mut current = end.index; while current != start.index { - route.nodes.push(self.graph.nodes[current as usize].position); + route + .nodes + .push(self.graph.nodes[current as usize].position); current = ancestor[current as usize].unwrap(); } - route.nodes.push(self.graph.nodes[current as usize].position); + route + .nodes + .push(self.graph.nodes[current as usize].position); route.nodes.reverse(); diff --git a/src/bin/performance.rs b/src/bin/performance.rs index 39bf722..f8a3a5e 100644 --- a/src/bin/performance.rs +++ b/src/bin/performance.rs @@ -1,5 +1,5 @@ use clap::Parser; -use fapra_osm_2::alt::LandmarkSet; +use fapra_osm_2::alt::{LandmarkSet, LandmarkBestSet}; use fapra_osm_2::astar::{estimate_haversine, estimate_latitude, AStar}; use fapra_osm_2::gridgraph::{GridGraph, NodeId}; use fapra_osm_2::utils::RoutingQuery; @@ -77,10 +77,9 @@ fn main() { } }; - let mut landmarks: LandmarkSet = bincode::deserialize_from(BufReader::new(landmarks)).unwrap(); - landmarks.best_size = 4; + let landmarks: LandmarkSet = bincode::deserialize_from(BufReader::new(landmarks)).unwrap(); - let astar = AStar { graph: *graph }; + let astar = AStar { graph: &(*graph) }; println!("{:?}", args); @@ -93,7 +92,7 @@ fn main() { let source = astar.graph.nodes[query.source]; let destination = astar.graph.nodes[query.destination]; - let _result = astar.shortest_path(&source, &destination, estimate_latitude); + let _result = astar.shortest_path(&source, &destination, estimate_haversine); } let elapsed = start.elapsed(); @@ -113,7 +112,7 @@ fn main() { let source = astar.graph.nodes[query.source]; let destination = astar.graph.nodes[query.destination]; - let result = astar.graph.shortest_path(&source, &destination); + let _result = astar.graph.shortest_path(&source, &destination); //println!("{}", result.unwrap().to_geojson()); } @@ -126,17 +125,20 @@ fn main() { if args.alt { println!("running ALT"); + + let mut best_landmarks = LandmarkBestSet::new(4, &landmarks); // Landmarks let start = Instant::now(); for query in targets.iter() { + println!("working on {:?}", query); let source = astar.graph.nodes[query.source]; let destination = astar.graph.nodes[query.destination]; - landmarks.select_best(source.index as NodeId, destination.index as NodeId); + best_landmarks.select_best(source.index as NodeId, destination.index as NodeId); let _result = astar.shortest_path(&source, &destination, |src, dest| { - landmarks.estimate(src.index as NodeId, dest.index as NodeId) + best_landmarks.estimate(src.index as NodeId, dest.index as NodeId) }); } diff --git a/src/bin/task6-rocket.rs b/src/bin/task6-rocket.rs index b2aa5b4..29fd886 100644 --- a/src/bin/task6-rocket.rs +++ b/src/bin/task6-rocket.rs @@ -3,10 +3,14 @@ use clap::Parser; use std::process::exit; use std::fs::File; -use fapra_osm_2::gridgraph::{GridGraph, Route}; +use fapra_osm_2::gridgraph::{GridGraph, Route, NodeId}; use fapra_osm_2::coordinates::{RadianCoordinate, DegreeCoordinate}; +use fapra_osm_2::astar::{AStar, estimate_haversine}; +use fapra_osm_2::alt::{LandmarkSet, LandmarkBestSet}; use rand::seq::SliceRandom; use serde::Serialize; +use std::io::BufReader; +use fapra_osm_2::utils::EARTH_RADIUS; use rocket::State; use rocket::response::status::BadRequest; @@ -21,6 +25,10 @@ struct Args { /// name of the FMI file to read #[clap(short, long)] filename: String, + + /// the landmarks to load + #[clap(short, long)] + landmarks: String, } #[get("/")] @@ -47,6 +55,7 @@ fn random_route(graphwrapper: &State) -> String { struct RouteQuery<'r> { r#from: &'r str, r#to: &'r str, + r#algorithm: &'r str, } #[derive(Debug)] @@ -54,6 +63,7 @@ struct RouteQuery<'r> { pub struct RouteResponse { success: bool, route: Option, + algorithm: String, } #[post("/route", data = "")] @@ -71,16 +81,50 @@ fn route(routequery: Form>, graphwrapper: &State) - let from = graphwrapper.graph.get_nearest_node(from).unwrap(); let to = graphwrapper.graph.get_nearest_node(to).unwrap(); - let route = graphwrapper.graph.shortest_path(from, to); + println!("working on route from {:?} to {:?}", from, to); + let direct_distance = from.position.distance_to(&to.position) * EARTH_RADIUS; + println!("haversine distance: {}", direct_distance); + let graph_distance = graphwrapper.graph.shortest_path(from, to).unwrap().cost; + println!("graph distance is: {}", graph_distance); + + let mut algorithm = routequery.algorithm; + + let route = if algorithm == "astar-haversine" { + println!("running A* with haversine distance"); + let astar = AStar{graph: &graphwrapper.graph}; + + astar.shortest_path(from, to, estimate_haversine) + } else if algorithm == "alt" { + println!("running ALT"); + + let mut best_landmarks = LandmarkBestSet::new(4, &graphwrapper.landmarks); + + let astar = AStar{graph: &graphwrapper.graph}; + best_landmarks.select_best(from.index as usize, to.index as usize); + + println!("initial estimate: {:?}", best_landmarks.estimate(from.index as NodeId, to.index as NodeId)); + + astar.shortest_path(from, to, |src, dest| { + best_landmarks.estimate(src.index as NodeId, dest.index as NodeId) + }) + + + } else { + println!("running dijkstra"); + algorithm = "dijkstra"; + graphwrapper.graph.shortest_path(from, to) + }; + println!("from: {:?}, to: {:?}", from, to); - let response = RouteResponse{success: route.is_some(), route}; + let response = RouteResponse{success: route.is_some(), route, algorithm: algorithm.to_string()}; Ok(Json(response)) } struct GraphWrapper { - graph: Box + graph: GridGraph, + landmarks: LandmarkSet, } #[launch] @@ -106,10 +150,21 @@ fn rocket() -> _ { println!("Loaded graph file"); - // let graph = GridGraph::generate_regular_grid(10,10); + let landmarks = match File::open(args.landmarks.clone()) { + Ok(f) => f, + Err(e) => { + println!( + "Error while opening landmark file {}: {:?}", + args.landmarks, e + ); + exit(1); + } + }; + + let landmarks: LandmarkSet = bincode::deserialize_from(BufReader::new(landmarks)).unwrap(); rocket::build() - .manage(GraphWrapper{graph: graph}) + .manage(GraphWrapper{graph: *graph, landmarks}) .mount("/", routes![index]) .mount("/", routes![random_route]) .mount("/", routes![route]) diff --git a/src/bin/test_alt_gen.rs b/src/bin/test_alt_gen.rs index fb199ec..30951fe 100644 --- a/src/bin/test_alt_gen.rs +++ b/src/bin/test_alt_gen.rs @@ -44,7 +44,7 @@ fn main() { } }; - let set = LandmarkSet::random_set(args.amount, 4, &grid); + let set = LandmarkSet::random_set(args.amount, &grid); let encoded = bincode::serialize(&set).unwrap(); diff --git a/src/gridgraph.rs b/src/gridgraph.rs index c7a00f9..d469da7 100644 --- a/src/gridgraph.rs +++ b/src/gridgraph.rs @@ -291,7 +291,6 @@ impl GridGraph { let mut gridgraph = Box::new(GridGraph::default()); let reader = BufReader::new(file); - let lines = reader.lines(); let mut total_node_count: u32 = 0; diff --git a/static/js/script.js b/static/js/script.js index 627db96..47c6992 100644 --- a/static/js/script.js +++ b/static/js/script.js @@ -67,6 +67,11 @@ function set_route_cost(cost) { field.value = cost; } +function set_algorithm_type(alg) { + let field = document.getElementById("algorithm_used"); + field.value = alg; +} + async function get_route() { let form = document.getElementById("queryform"); data = new FormData(form); @@ -74,6 +79,7 @@ async function get_route() { let response = await(fetch("/route", { method: "POST", body: data })); let result = await response.json(); + set_algorithm_type(result.algorithm); if (result.success === true) { set_route_cost(result.route.cost); diff --git a/templates/index.html.hbs b/templates/index.html.hbs index d84b886..d62085a 100644 --- a/templates/index.html.hbs +++ b/templates/index.html.hbs @@ -31,10 +31,20 @@ +
+ + +
+

Route Cost:

+

Algorithm: