From a98d46caa8c6c84ca102f8e09f79d0c267ca5c27 Mon Sep 17 00:00:00 2001 From: Marcello Perathoner Date: Thu, 2 Apr 2026 20:07:46 +0200 Subject: [PATCH 1/3] Rebase origin/master --- features/bicycle/distance.feature | 4 + features/foot/distance.feature | 4 + features/lib/osrm_loader.js | 10 +- features/support/run.js | 6 +- include/contractor/contractor_graph.hpp | 5 +- include/contractor/contractor_heap.hpp | 16 +- include/contractor/contractor_search.hpp | 1 + include/contractor/graph_contractor.hpp | 17 +- include/engine/guidance/assemble_leg.hpp | 1 + include/engine/guidance/assemble_steps.hpp | 11 + .../routing_algorithms/routing_base_ch.hpp | 61 +- include/util/d_ary_heap.hpp | 9 + include/util/dynamic_graph.hpp | 8 +- include/util/query_heap.hpp | 9 + package-lock.json | 14 +- scripts/contractor_benchmark.py | 19 + scripts/debug/dump_hsgr.py | 283 +++++ src/contractor/contractor.cpp | 29 +- src/contractor/contractor_search.cpp | 19 +- src/contractor/graph_contractor.cpp | 1105 ++++++++--------- .../routing_algorithms/routing_base_ch.cpp | 12 + src/tools/routed.cpp | 2 - unit_tests/contractor/graph_contractor.cpp | 12 +- unit_tests/util/query_heap.cpp | 12 +- 24 files changed, 966 insertions(+), 703 deletions(-) create mode 100644 scripts/debug/dump_hsgr.py diff --git a/features/bicycle/distance.feature b/features/bicycle/distance.feature index 3061bfbb871..d80345b3db9 100644 --- a/features/bicycle/distance.feature +++ b/features/bicycle/distance.feature @@ -32,4 +32,8 @@ Feature: Bike - Use distance weight When I route I should get | from | to | route | weight | time | distance | | a | b | abc,abc | 200 | 48s | 200m +-1 | + | b | c | abc,abc | 200 | 48s | 200m +-1 | | a | c | abc,abc | 400 | 96s | 400m +-1 | + | b | a | abc,abc | 200 | 48s | 200m +-1 | + | c | b | abc,abc | 200 | 48s | 200m +-1 | + | c | a | abc,abc | 400 | 96s | 400m +-1 | diff --git a/features/foot/distance.feature b/features/foot/distance.feature index 75dedc9571c..9db2243ba16 100644 --- a/features/foot/distance.feature +++ b/features/foot/distance.feature @@ -32,4 +32,8 @@ Feature: Foot - Use distance weight When I route I should get | from | to | route | weight | time | distance | | a | b | abc,abc | 200 | 144s | 200m +-1 | + | b | c | abc,abc | 200 | 144s | 200m +-1 | | a | c | abc,abc | 400 | 288s | 400m +-1 | + | b | a | abc,abc | 200 | 144s | 200m +-1 | + | c | b | abc,abc | 200 | 144s | 200m +-1 | + | c | a | abc,abc | 400 | 288s | 400m +-1 | diff --git a/features/lib/osrm_loader.js b/features/lib/osrm_loader.js index 2ef3b9a8c36..80598da1e6c 100644 --- a/features/lib/osrm_loader.js +++ b/features/lib/osrm_loader.js @@ -38,12 +38,12 @@ class OSRMBaseLoader { this.child.stdout.on('data', (data) => { if (data.includes('running and waiting for requests')) { - log('Routed running and waiting for requests'); - resolve(); + resolve(); } + log(`${data}`.trim()); }); - this.child.on('exit', (code) => { + this.child.on('close', (code) => { log(`osrm-routed completed with exit code ${code}`); this.child = null; }); @@ -209,7 +209,9 @@ export class OSRMDatastoreLoader extends OSRMBaseLoader { // we MUST consume stdout and stderr or the osrm-routed process will block eventually this.child.stderr.on('data', (data) => this.logSync(`osrm-routed stderr:\n${data}`)); - this.child.on('exit', (code, signal) => { + this.child.on('close', (code, signal) => { + this.child.stdout.read(); + this.child.stderr.read(); this.child = null; if (signal != null) { const msg = `osrm-routed aborted with signal ${signal}`; diff --git a/features/support/run.js b/features/support/run.js index 71293a01758..b0a0fba666e 100644 --- a/features/support/run.js +++ b/features/support/run.js @@ -39,7 +39,7 @@ export function runBin(bin, args, options, log) { log(`${bin} aborted with error ${err}`); throw(err); }); - child.on('exit', (code, signal) => { + child.on('close', (code, signal) => { if (signal != null) { const msg = `${bin} aborted with signal ${child.signal}`; log(msg); @@ -76,9 +76,9 @@ export function runBinSync(bin, args, options, log) { if (child.stderr) log(`${bin} stderr:\n${child.stderr}`); if (child.status != null) - log(`${bin} completed with exit code ${child.status}`); + log(`${bin} sync completed with exit code ${child.status}`); if (child.signal != null) { - const msg = `${bin} aborted with signal ${child.signal}`; + const msg = `${bin} sync aborted with signal ${child.signal}`; log(msg); if (child.signal != 'SIGABRT') // some tests deliberately fail throw new Error(msg); diff --git a/include/contractor/contractor_graph.hpp b/include/contractor/contractor_graph.hpp index e88a5185c73..130638be802 100644 --- a/include/contractor/contractor_graph.hpp +++ b/include/contractor/contractor_graph.hpp @@ -18,7 +18,7 @@ struct ContractorEdgeData EdgeDuration duration, EdgeDistance distance, unsigned original_edges, - unsigned id, + NodeID id, bool shortcut, bool forward, bool backward) @@ -30,7 +30,8 @@ struct ContractorEdgeData EdgeWeight weight; EdgeDuration duration; EdgeDistance distance; - unsigned id; + NodeID id; + /** Recursive count of how many edges are replaced by this shortcut. */ unsigned originalEdges : 29; bool shortcut : 1; bool forward : 1; diff --git a/include/contractor/contractor_heap.hpp b/include/contractor/contractor_heap.hpp index d3212fe6e58..c5a20ceb0d3 100644 --- a/include/contractor/contractor_heap.hpp +++ b/include/contractor/contractor_heap.hpp @@ -7,20 +7,8 @@ namespace osrm::contractor { -struct ContractorHeapData -{ - ContractorHeapData() {} - ContractorHeapData(short hop_, bool target_) : hop(hop_), target(target_) {} - - short hop = 0; - bool target = false; -}; - -using ContractorHeap = util::QueryHeap>; +using ContractorHeap = + util::QueryHeap>; } // namespace osrm::contractor diff --git a/include/contractor/contractor_search.hpp b/include/contractor/contractor_search.hpp index 6911a44bb2c..66e27a7bf70 100644 --- a/include/contractor/contractor_search.hpp +++ b/include/contractor/contractor_search.hpp @@ -13,6 +13,7 @@ namespace osrm::contractor void search(ContractorHeap &heap, const ContractorGraph &graph, + const NodeID start, const std::vector &contractable, const unsigned number_of_targets, const int node_limit, diff --git a/include/contractor/graph_contractor.hpp b/include/contractor/graph_contractor.hpp index 9f3a261101b..aab0cb62152 100644 --- a/include/contractor/graph_contractor.hpp +++ b/include/contractor/graph_contractor.hpp @@ -4,8 +4,6 @@ #include "contractor/contractor_graph.hpp" #include "contractor/query_graph.hpp" -#include "util/filtered_graph.hpp" - #include namespace osrm::contractor @@ -13,35 +11,28 @@ namespace osrm::contractor using GraphAndFilter = std::tuple>>; -GraphAndFilter contractFullGraph(ContractorGraph contractor_graph, - std::vector node_weights); +GraphAndFilter contractFullGraph(ContractorGraph contractor_graph); GraphAndFilter contractExcludableGraph(ContractorGraph contractor_graph_, - std::vector node_weights, const std::vector> &filters); std::vector contractGraph(ContractorGraph &graph, std::vector node_is_uncontracted, std::vector node_is_contractable, - std::vector node_weights, double core_factor = 1.0); // Overload for contracting all nodes -inline auto contractGraph(ContractorGraph &graph, - std::vector node_weights, - double core_factor = 1.0) +inline auto contractGraph(ContractorGraph &graph, double core_factor = 1.0) { - return contractGraph(graph, {}, {}, std::move(node_weights), core_factor); + return contractGraph(graph, {}, {}, core_factor); } // Overload no contracted nodes inline auto contractGraph(ContractorGraph &graph, std::vector node_is_contractable, - std::vector node_weights, double core_factor = 1.0) { - return contractGraph( - graph, {}, std::move(node_is_contractable), std::move(node_weights), core_factor); + return contractGraph(graph, {}, std::move(node_is_contractable), core_factor); } } // namespace osrm::contractor diff --git a/include/engine/guidance/assemble_leg.hpp b/include/engine/guidance/assemble_leg.hpp index a80b18f9e75..654671fe455 100644 --- a/include/engine/guidance/assemble_leg.hpp +++ b/include/engine/guidance/assemble_leg.hpp @@ -7,6 +7,7 @@ #include "engine/guidance/route_step.hpp" #include "engine/internal_route_result.hpp" #include "util/coordinate_calculation.hpp" +#include "util/log.hpp" #include "util/typedefs.hpp" #include diff --git a/include/engine/guidance/assemble_steps.hpp b/include/engine/guidance/assemble_steps.hpp index 21dad8eeab2..627d26d642f 100644 --- a/include/engine/guidance/assemble_steps.hpp +++ b/include/engine/guidance/assemble_steps.hpp @@ -15,6 +15,7 @@ #include "util/coordinate_calculation.hpp" #include "util/guidance/entry_class.hpp" #include "util/guidance/turn_lanes.hpp" +#include "util/log.hpp" #include "util/typedefs.hpp" #include @@ -153,6 +154,9 @@ inline std::vector assembleSteps(const datafacade::BaseDataFacade &fa {intersection}, is_left_hand_driving}); + util::Log(logDEBUG) << "pushing RouteStep of duration " + << from_alias(segment_duration) / 10.; + if (leg_data_index + 1 < leg_data.size()) { step_name_id = @@ -272,6 +276,13 @@ inline std::vector assembleSteps(const datafacade::BaseDataFacade &fa // u-------------v // | |---------| source_weight // | |---| target_weight + +#ifndef NDEBUG + if (source_weight > target_weight) + util::Log(logDEBUG) << "error: source_weight = " << source_weight + << " target_weight = " << target_weight; +#endif + BOOST_ASSERT(target_weight >= source_weight); const EdgeWeight weight = target_weight - source_weight; diff --git a/include/engine/routing_algorithms/routing_base_ch.hpp b/include/engine/routing_algorithms/routing_base_ch.hpp index f1063b08998..0afe57449db 100644 --- a/include/engine/routing_algorithms/routing_base_ch.hpp +++ b/include/engine/routing_algorithms/routing_base_ch.hpp @@ -6,6 +6,7 @@ #include "engine/routing_algorithms/routing_base.hpp" #include "engine/search_engine_data.hpp" +#include "util/log.hpp" #include "util/typedefs.hpp" #include @@ -112,7 +113,7 @@ void routingStep(const DataFacade &facade, NodeID &middle_node_id, EdgeWeight &upper_bound, EdgeWeight min_edge_offset, - const std::vector &force_step_nodes) + [[maybe_unused]] const std::vector &force_step_nodes) { auto heapNode = forward_heap.DeleteMinGetHeapNode(); const auto reverseHeapNode = reverse_heap.GetHeapNodeIfWasInserted(heapNode.node); @@ -120,41 +121,36 @@ void routingStep(const DataFacade &facade, if (reverseHeapNode) { const EdgeWeight new_weight = reverseHeapNode->weight + heapNode.weight; - if (new_weight < upper_bound) + + if (new_weight >= EdgeWeight{0} && new_weight <= upper_bound && + !shouldForceStep(force_step_nodes, heapNode, *reverseHeapNode)) + { + middle_node_id = heapNode.node; + upper_bound = new_weight; + } + else { - if (shouldForceStep(force_step_nodes, heapNode, *reverseHeapNode) || - // in this case we are looking at a bi-directional way where the source - // and target phantom are on the same edge based node - new_weight < EdgeWeight{0}) + // The two identical nodes did not match after all. + // Before forcing step, check whether there is a loop present at the node. + // We may find a valid weight path by following the loop. + for (const auto edge : facade.GetAdjacentEdgeRange(heapNode.node)) { - // Before forcing step, check whether there is a loop present at the node. - // We may find a valid weight path by following the loop. - for (const auto edge : facade.GetAdjacentEdgeRange(heapNode.node)) + const auto &data = facade.GetEdgeData(edge); + if (DIRECTION == FORWARD_DIRECTION ? data.forward : data.backward) { - const auto &data = facade.GetEdgeData(edge); - if (DIRECTION == FORWARD_DIRECTION ? data.forward : data.backward) + const NodeID to = facade.GetTarget(edge); + if (to == heapNode.node) { - const NodeID to = facade.GetTarget(edge); - if (to == heapNode.node) + const EdgeWeight edge_weight = data.weight; + const EdgeWeight loop_weight = new_weight + edge_weight; + if (loop_weight >= EdgeWeight{0} && loop_weight < upper_bound) { - const EdgeWeight edge_weight = data.weight; - const EdgeWeight loop_weight = new_weight + edge_weight; - if (loop_weight >= EdgeWeight{0} && loop_weight < upper_bound) - { - middle_node_id = heapNode.node; - upper_bound = loop_weight; - } + middle_node_id = heapNode.node; + upper_bound = loop_weight; } } } } - else - { - BOOST_ASSERT(new_weight >= EdgeWeight{0}); - - middle_node_id = heapNode.node; - upper_bound = new_weight; - } } } @@ -389,6 +385,17 @@ void unpackPath(const FacadeT &facade, } annotatePath(facade, route_endpoints, unpacked_nodes, unpacked_edges, unpacked_path); + +#ifndef NDEBUG + { + auto log = util::Log(logDEBUG); + log << "unpacked path ="; + for (const auto &p : unpacked_path) + { + log << " " << p.from_edge_based_node; + } + } +#endif } /** diff --git a/include/util/d_ary_heap.hpp b/include/util/d_ary_heap.hpp index ad7dbf82452..7740f072a64 100644 --- a/include/util/d_ary_heap.hpp +++ b/include/util/d_ary_heap.hpp @@ -40,6 +40,15 @@ template (reorderHandler)); } + template + void increase(HeapHandle handle, HeapData &&data, ReorderHandler &&reorderHandler) + { + BOOST_ASSERT(handle < heap.size()); + + heap[handle] = std::forward(data); + heapifyDown(handle, std::forward(reorderHandler)); + } + void clear() { heap.clear(); } template void pop(ReorderHandler &&reorderHandler) diff --git a/include/util/dynamic_graph.hpp b/include/util/dynamic_graph.hpp index 3b86bf722ee..643b2f27e1a 100644 --- a/include/util/dynamic_graph.hpp +++ b/include/util/dynamic_graph.hpp @@ -415,8 +415,11 @@ template class DynamicGraph void Renumber(const std::vector &old_to_new_node) { + bool renumber = old_to_new_node.size() != 0; + // permutate everything but the sentinel - util::inplacePermutation(node_array.begin(), node_array.end(), old_to_new_node); + if (renumber) + util::inplacePermutation(node_array.begin(), node_array.end(), old_to_new_node); // Build up edge permutation if (edge_list.size() >= std::numeric_limits::max()) @@ -432,7 +435,8 @@ template class DynamicGraph // move all filled edges for (auto edge : GetAdjacentEdgeRange(node)) { - edge_list[edge].target = old_to_new_node[edge_list[edge].target]; + if (renumber) + edge_list[edge].target = old_to_new_node[edge_list[edge].target]; BOOST_ASSERT(edge_list[edge].target != SPECIAL_NODEID); old_to_new_edge[edge] = new_edge_index++; } diff --git a/include/util/query_heap.hpp b/include/util/query_heap.hpp index f7bf71049db..66607d7f985 100644 --- a/include/util/query_heap.hpp +++ b/include/util/query_heap.hpp @@ -333,6 +333,15 @@ class QueryHeap { inserted_nodes[heapData.index].handle = new_handle; }); } + void IncreaseKey(const HeapNode &heapNode) + { + BOOST_ASSERT(!WasRemoved(heapNode.node)); + heap.increase(heapNode.handle, + HeapData{heapNode.weight, heap[heapNode.handle].index}, + [this](const auto &heapData, auto new_handle) + { inserted_nodes[heapData.index].handle = new_handle; }); + } + private: std::vector inserted_nodes; HeapContainer heap; diff --git a/package-lock.json b/package-lock.json index 9a8aeec10d0..9e5c0a67201 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6700,16 +6700,16 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", - "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -14161,9 +14161,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true, "license": "ISC", "bin": { diff --git a/scripts/contractor_benchmark.py b/scripts/contractor_benchmark.py index 62494a44616..1a4c8dca502 100644 --- a/scripts/contractor_benchmark.py +++ b/scripts/contractor_benchmark.py @@ -1,3 +1,22 @@ +""" +A benchmark to compare osrm-contract binaries. + +This program lets you compare two or more `osrm-contract` binaries wrt. time, memory, +and number of generated edges. + +The run subcommand will select the first binary and contract the dataset. It will then +select the next binary dataset and do the same. The whole process will be repeated +`--runs` times. + +Ideally the argument `logfiles` will have the same number of items as the argument +`binaries`. If not, it is extended by repeating the last item. + +The `report` subcommand can be used to analyze logfiles generated by earlier runs. + +The `copy` subcommand can be used to copy an existing dataset into a new temp directory. + +""" + import argparse import datetime import glob diff --git a/scripts/debug/dump_hsgr.py b/scripts/debug/dump_hsgr.py new file mode 100644 index 00000000000..63890cd0cb3 --- /dev/null +++ b/scripts/debug/dump_hsgr.py @@ -0,0 +1,283 @@ +""" +Contractior debugging script. + +This script displays the content of `osrm.ebg` and `osrm.hsgr` files both in tabular +form and as `.dot` graph. + +""" + +import argparse +import fnmatch +import os +import struct +import tabulate +import tarfile + +# ".osrm.ebg" +# /common/edge_based_edge_list +# struct EdgeBasedEdge +# { +# struct EdgeData +# { +# NodeID turn_id; // ID of the edge based node (node based edge) +# EdgeWeight weight; +# EdgeDistance distance; +# EdgeDuration::value_type duration : 30; +# std::uint32_t forward : 1; +# std::uint32_t backward : 1; +# +# auto is_unidirectional() const { return !forward || !backward; } +# }; +# NodeID source; +# NodeID target; +# EdgeData data; +# }; + + +class EbgEdge: + + fields = "source target turn_id weight distance duration".split() + headers = "source target turn_id weight distance duration forward backward".split() + + def __init__(self, iter): + self.__dict__.update(zip(self.fields, iter)) + self.forward = self.duration & 0x40000000 > 0 + self.backward = self.duration & 0x80000000 > 0 + self.duration = self.duration & 0x3FFFFFFF + + @classmethod + def make(cls, buffer): + return sorted([cls(e) for e in struct.iter_unpack(" struct EdgeArrayEntry +# { +# NodeID target; +# EdgeDataT data; +# }; +# struct EdgeData +# { +# // this ID is either the middle node of the shortcut, or the ID of the edge based node (node +# // based edge) storing the appropriate data. If `shortcut` is set to true, we get the middle +# // node. Otherwise we see the edge based node to access node data. +# NodeID turn_id : 31; +# bool shortcut : 1; +# EdgeWeight weight; +# EdgeDuration::value_type duration : 30; +# std::uint32_t forward : 1; +# std::uint32_t backward : 1; +# EdgeDistance distance; +# }; + + +class HsgrEdge: + + fields = "target turn_id weight duration distance".split() + headers = "source target turn_id weight duration distance shortcut forward backward".split() + + def __init__(self, iter): + self.__dict__.update(zip(self.fields, iter)) + self.forward = self.duration & 0x40000000 > 0 + self.backward = self.duration & 0x80000000 > 0 + self.duration = self.duration & 0x3FFFFFFF + self.shortcut = self.turn_id & 0x80000000 > 0 + self.turn_id = self.turn_id & 0x7FFFFFFF + + @classmethod + def make(cls, buffer): + return [cls(e) for e in struct.iter_unpack("{sub}{e.target}[{join(params)}];\n") + labels[f"{sub}{e.source}"] = e.source + labels[f"{sub}{e.target}"] = e.target + + dot.write("subgraph cluster_ebg {\n label=ebg;\n") + for e in ebg_edges: + params = edge_params(e) + draw_edge(e, params, "ebg") + dot.write("}\n") + + dot.write("subgraph cluster_hsgr {\n label=hsgr;\n") + for e in sorted(hsgr_edges): + params = edge_params(e) + if e.shortcut: + params["color"] = "red" + params["label"] = f'"{e.turn_id}|{e.weight}"' + draw_edge(e, params, "hsgr") + dot.write("}\n") + + for id_, label in labels.items(): + params = {"label": label} + dot.write(f"{id_}[{join(params)}];\n") + dot.write( + f""" + labelloc="t"; + label="{os.path.basename(args.input)}"; + }}\n""" + ) + + +def print_table(rows, headers): + print(tabulate.tabulate(rows, headers, tablefmt="github", floatfmt=".2f")) + return + + +def build_parser(): + parser = argparse.ArgumentParser(description=__doc__) + + parser.add_argument( + "input", + help="The input file", + metavar="FILE", + ) + + parser.add_argument("--metric", help="The graph metric (default: *)", default="*") + + parser.add_argument( + "--dot", help="Output a .dot file", type=argparse.FileType("wt") + ) + + return parser + + +if __name__ == "__main__": + args = build_parser().parse_args() + if args.input.endswith(".hsgr"): + args.input = args.input[:-5] + ebg_edges = read_ebg_file(args) + hsgr_nodes, hsgr_edges = read_hsgr_file(args) + + if args.dot: + write_dot_file(ebg_edges, hsgr_edges) + else: + print("ebg") + print_table(ebg_edges, EbgEdge.headers) + + print("\nhsgr nodes") + print_table(hsgr_nodes, "node_offset") + + print("\nhsgr") + print_table(hsgr_edges, HsgrEdge.headers) diff --git a/src/contractor/contractor.cpp b/src/contractor/contractor.cpp index 5df261baf00..cc81560204e 100644 --- a/src/contractor/contractor.cpp +++ b/src/contractor/contractor.cpp @@ -1,32 +1,18 @@ #include "contractor/contractor.hpp" -#include "contractor/contracted_edge_container.hpp" #include "contractor/files.hpp" #include "contractor/graph_contractor.hpp" #include "contractor/graph_contractor_adaptors.hpp" -#include "extractor/compressed_edge_container.hpp" -#include "extractor/edge_based_graph_factory.hpp" #include "extractor/files.hpp" -#include "extractor/node_based_edge.hpp" - -#include "storage/io.hpp" #include "updater/updater.hpp" -#include "util/exception.hpp" -#include "util/exception_utils.hpp" #include "util/exclude_flag.hpp" -#include "util/filtered_graph.hpp" -#include "util/integer_range.hpp" #include "util/log.hpp" -#include "util/static_graph.hpp" -#include "util/string_util.hpp" #include "util/timing_util.hpp" #include "util/typedefs.hpp" -#include #include -#include #include #include @@ -55,18 +41,13 @@ int Contractor::Run() EdgeID number_of_edge_based_nodes = updater.LoadAndUpdateEdgeExpandedGraph( edge_based_edge_list, node_weights, connectivity_checksum); - // Convert node weights for oneway streets to INVALID_EDGE_WEIGHT - for (auto &weight : node_weights) - { - weight = (from_alias(weight) & 0x80000000) ? INVALID_EDGE_WEIGHT - : weight; - } - // Contracting the edge-expanded graph TIMER_START(contraction); std::string metric_name; + // filters on way classes like: 'toll', 'motorway', 'ferry', 'restricted', 'tunnel', ... + // max. 7 classes can be defined std::vector> node_filters; { extractor::EdgeBasedNodeDataContainer node_data; @@ -83,10 +64,8 @@ int Contractor::Run() QueryGraph query_graph; std::vector> edge_filters; std::vector> cores; - std::tie(query_graph, edge_filters) = - contractExcludableGraph(toContractorGraph(number_of_edge_based_nodes, edge_based_edge_list), - std::move(node_weights), - node_filters); + std::tie(query_graph, edge_filters) = contractExcludableGraph( + toContractorGraph(number_of_edge_based_nodes, edge_based_edge_list), node_filters); TIMER_STOP(contraction); util::Log() << "Contracted graph has " << query_graph.GetNumberOfEdges() << " edges."; util::Log() << "Contraction took " << TIMER_SEC(contraction) << " sec"; diff --git a/src/contractor/contractor_search.cpp b/src/contractor/contractor_search.cpp index 620cd2afda9..4ac8774d3b3 100644 --- a/src/contractor/contractor_search.cpp +++ b/src/contractor/contractor_search.cpp @@ -2,6 +2,7 @@ #include "contractor/contractor_graph.hpp" #include "contractor/contractor_heap.hpp" +#include "util/typedefs.hpp" namespace osrm::contractor { @@ -10,12 +11,11 @@ namespace { void relaxNode(ContractorHeap &heap, const ContractorGraph &graph, - const std::vector &contractable, + const std::vector &contractible, const NodeID node, const EdgeWeight node_weight, const NodeID forbidden_node) { - const short current_hop = heap.GetData(node).hop + 1; for (auto edge : graph.GetAdjacentEdgeRange(node)) { const auto &data = graph.GetEdgeData(edge); @@ -35,18 +35,17 @@ void relaxNode(ContractorHeap &heap, // New Node discovered -> Add to Heap + Node Info Storage if (!toHeapNode) { - if (!contractable[to]) + if (!contractible[to]) { continue; } - heap.Insert(to, to_weight, ContractorHeapData{current_hop, false}); + heap.Insert(to, to_weight, false); } // Found a shorter Path -> Update weight else if (to_weight < toHeapNode->weight) { toHeapNode->weight = to_weight; heap.DecreaseKey(*toHeapNode); - toHeapNode->data.hop = current_hop; } } } @@ -54,7 +53,8 @@ void relaxNode(ContractorHeap &heap, void search(ContractorHeap &heap, const ContractorGraph &graph, - const std::vector &contractable, + const NodeID start, + const std::vector &contractible, const unsigned number_of_targets, const int node_limit, const EdgeWeight weight_limit, @@ -62,6 +62,7 @@ void search(ContractorHeap &heap, { int nodes = 0; unsigned number_of_targets_found = 0; + relaxNode(heap, graph, contractible, start, EdgeWeight{0}, forbidden_node); while (!heap.Empty()) { const NodeID node = heap.DeleteMin(); @@ -76,8 +77,8 @@ void search(ContractorHeap &heap, return; } - // Destination settled? - if (heap.GetData(node).target) + // Target settled? + if (heap.GetData(node)) { ++number_of_targets_found; if (number_of_targets_found >= number_of_targets) @@ -86,7 +87,7 @@ void search(ContractorHeap &heap, } } - relaxNode(heap, graph, contractable, node, node_weight, forbidden_node); + relaxNode(heap, graph, contractible, node, node_weight, forbidden_node); } } } // namespace osrm::contractor diff --git a/src/contractor/graph_contractor.cpp b/src/contractor/graph_contractor.cpp index b27e2983fea..073177cb0e9 100644 --- a/src/contractor/graph_contractor.cpp +++ b/src/contractor/graph_contractor.cpp @@ -1,10 +1,10 @@ #include "contractor/graph_contractor.hpp" #include "contractor/contracted_edge_container.hpp" #include "contractor/contractor_graph.hpp" +#include "contractor/contractor_heap.hpp" #include "contractor/contractor_search.hpp" #include "contractor/graph_contractor_adaptors.hpp" #include "contractor/query_edge.hpp" -#include "contractor/query_graph.hpp" #include "util/integer_range.hpp" #include "util/log.hpp" #include "util/percent.hpp" @@ -12,28 +12,97 @@ #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include -#include #include +#define SELF_LOOPS + +/** +The algorithm here implemented is described in the papers: + +[Geisberger2008] +Contraction Hierarchies: Faster and Simpler Hierarchical Routing in Road Networks +Robert Geisberger, Peter Sanders, Dominik Schultes, and Daniel Delling +https://turing.iem.thm.de/routeplanning/hwy/contract.pdf + +[Vetter2009] +Parallel Time-Dependent Contraction Hierarchies +Christian Vetter - July 13, 2009 +https://ae.iti.kit.edu/download/vetter_sa.pdf + +tldr: + +All nodes in the graph are ordered by a unique "priority". Searches only follow edges +from lower priority nodes (think tertiary road) to higher priority nodes (think +motorways). Searches start from both source and target until they meet somewhere in the +middle. + +The shortest distance between any two nodes must be invariant under contraction. Node +contraction removes all edges going "down" into the contracted node. This does not +change any distance from the contracted node to any other since all searches starting at +the contracted node only go "up". But all paths that went through the contracted node +before are now interrupted. To fix this, contraction also inserts "shortcuts" around the +contracted node: For each pair of immediate neighbours of the contracted node a shortcut +is inserted iff the way through the node was the shortest path between both of them. + +The nodes get contracted in order of their priority. Since there will be fewer and fewer +nodes left to be contracted the shortcuts will cover greater and greater distances. +After a node is contracted the priorities of all neighbouring nodes are updated. + +Contraction should be strictly sequential from the lowest priority node to the highest. +To parallelize this process we introduce the concept of independent node. A node is +independent if it is far enough removed from other independent nodes. Independent nodes +can be contracted in parallel. + +We first find all independent nodes, then contract all of them in parallel. This step is +repeated until a sufficient percentage of all nodes is contracted. + +See: Algorithm 2 in Chapter 4.3 of [Vetter2009] + +A note about self-loops + +Contraction must keep the graph invariant wrt. shortest distance between any two nodes. +We have the added requirement to keep invariant the shortest loop distance from any node +to itself. This requirement arises from the need to "go around" if source and target are +on the same node with source downstream from target. For this reason we must insert +self-loops for all nodes. + +Notes + +The overlay graph G' for node v consists of v and all nodes with higher priority than v. +The core is the set of nodes not (yet) contracted. +*/ + namespace osrm::contractor { +using CLOCK = std::chrono::high_resolution_clock; + +#define TIMER_DECLARE(_X) \ + auto _X##_start = CLOCK::now(); \ + auto _X##_duration = CLOCK::now() - CLOCK::now(); +#define TIMER_START(_X) _X##_start = CLOCK::now() +#define TIMER_STOP(_X) _X##_duration = (CLOCK::now() - _X##_start) +#define TIMER_MSEC(_X) \ + std::fixed << std::setprecision(2) \ + << (0.000001 * \ + std::chrono::duration_cast(_X##_duration).count()) \ + << "ms" + namespace { -struct ContractorThreadData -{ - ContractorHeap heap; - std::vector inserted_edges; - std::vector neighbours; - explicit ContractorThreadData(NodeID nodes) : heap(nodes) {} -}; struct ContractorNodeData { @@ -43,14 +112,13 @@ struct ContractorNodeData ContractorNodeData(std::size_t number_of_nodes, std::vector uncontracted_nodes_, - std::vector contractable_, - std::vector weights_) - : is_core(std::move(uncontracted_nodes_)), contractable(std::move(contractable_)), - priorities(number_of_nodes), weights(std::move(weights_)), depths(number_of_nodes, 0) + std::vector contractible_) + : is_core(std::move(uncontracted_nodes_)), is_contractible(std::move(contractible_)), + priorities(number_of_nodes), depths(number_of_nodes, 0) { - if (contractable.empty()) + if (is_contractible.empty()) { - contractable.resize(number_of_nodes, true); + is_contractible.resize(number_of_nodes, true); } if (is_core.empty()) { @@ -58,486 +126,390 @@ struct ContractorNodeData } } - void Renumber(const std::vector &old_to_new) - { - tbb::parallel_invoke( - [&] { util::inplacePermutation(priorities.begin(), priorities.end(), old_to_new); }, - [&] { util::inplacePermutation(weights.begin(), weights.end(), old_to_new); }, - [&] { util::inplacePermutation(is_core.begin(), is_core.end(), old_to_new); }, - [&] { util::inplacePermutation(contractable.begin(), contractable.end(), old_to_new); }, - [&] { util::inplacePermutation(depths.begin(), depths.end(), old_to_new); }); - } - + /** All these are keyed by NodeID */ std::vector is_core; - std::vector contractable; + std::vector is_contractible; std::vector priorities; - std::vector weights; std::vector depths; + /** List of "lost" edges to re-insert into the graph */ + tbb::concurrent_vector lost_edges; }; struct ContractionStats { - int edges_deleted_count; - int edges_added_count; - int original_edges_deleted_count; - int original_edges_added_count; - ContractionStats() - : edges_deleted_count(0), edges_added_count(0), original_edges_deleted_count(0), - original_edges_added_count(0) - { - } + int edges_deleted_count{}; + int edges_added_count{}; + int original_edges_deleted_count{}; + int original_edges_added_count{}; }; -struct RemainingNodeData -{ - RemainingNodeData() = default; - RemainingNodeData(NodeID id, bool is_independent) : id(id), is_independent(is_independent) {} - NodeID id : 31; - bool is_independent : 1; -}; +using ThreadData = tbb::enumerable_thread_specific; -struct ThreadDataContainer +/** + * @brief Get all immediate neighbours of a node. Duplicates removed. + * + * @param graph The graph + * @param v The node + * @return std::vector The neighbours + */ +inline std::vector GetNeighbours(const ContractorGraph &graph, const NodeID v) { - explicit ThreadDataContainer(int number_of_nodes) : number_of_nodes(number_of_nodes) {} + auto rg = graph.GetAdjacentEdgeRange(v); + std::vector neighbours; + neighbours.reserve(rg.size()); - inline ContractorThreadData *GetThreadData() + for (auto e : rg) { - bool exists = false; - auto &ref = data.local(exists); - if (!exists) + const NodeID u = graph.GetTarget(e); + if (u != v) { - ref = std::make_shared(number_of_nodes); + neighbours.push_back(u); } - - return ref.get(); } + std::sort(neighbours.begin(), neighbours.end()); + neighbours.erase(std::unique(neighbours.begin(), neighbours.end()), neighbours.end()); + return neighbours; +} - int number_of_nodes; - using EnumerableThreadData = - tbb::enumerable_thread_specific>; - EnumerableThreadData data; -}; - -template -void ContractNode(ContractorThreadData *data, - const ContractorGraph &graph, +/** + * @brief Contract one node or simulate its contraction + * + * For each pair of neighbours (u, w) if the path u->v->w was the shortest path in the + * graph from u to w, a shortcut edge u->w is inserted. + * + * @tparam RUNSIMULATION + * @param graph + * @param node the node to contract + * @param thread_data + * @param node_data + * @param inserted_edges output of actual contraction + * @param stats output of simulation + */ +template +void ContractNode(const ContractorGraph &graph, const NodeID node, - std::vector &node_weights, - const std::vector &contractable, - ContractionStats *stats = nullptr) + ThreadData &thread_data, + ContractorNodeData &node_data, + tbb::concurrent_vector *inserted_edges, + ContractionStats *stats) { - auto &heap = data->heap; - std::size_t inserted_edges_size = data->inserted_edges.size(); - std::vector &inserted_edges = data->inserted_edges; - constexpr bool SHORTCUT_ARC = true; - constexpr bool FORWARD_DIRECTION_ENABLED = true; - constexpr bool FORWARD_DIRECTION_DISABLED = false; - constexpr bool REVERSE_DIRECTION_ENABLED = true; - constexpr bool REVERSE_DIRECTION_DISABLED = false; - - for (auto in_edge : graph.GetAdjacentEdgeRange(node)) + if (RUNSIMULATION) + { + BOOST_ASSERT(stats); + } + else + { + BOOST_ASSERT(inserted_edges); + } + const int SEARCH_SPACE_SIZE = RUNSIMULATION ? 1000 : 2000; + + ContractorHeap &heap = thread_data.local(); + + // loop over all incoming edges + auto node_edge_range = graph.GetAdjacentEdgeRange(node); + for (auto in_edge : node_edge_range) { - const ContractorEdgeData &in_data = graph.GetEdgeData(in_edge); const NodeID source = graph.GetTarget(in_edge); if (source == node) continue; + const ContractorEdgeData &s2n_data = graph.GetEdgeData(in_edge); if (RUNSIMULATION) { - BOOST_ASSERT(stats != nullptr); ++stats->edges_deleted_count; - stats->original_edges_deleted_count += in_data.originalEdges; + stats->original_edges_deleted_count += s2n_data.originalEdges; } - if (!in_data.backward) - { + + if (!s2n_data.backward) continue; - } heap.Clear(); - heap.Insert(source, {0}, ContractorHeapData{}); - EdgeWeight max_weight = {0}; unsigned number_of_targets = 0; + EdgeWeight max_weight = {0}; - for (auto out_edge : graph.GetAdjacentEdgeRange(node)) + // insert all targets outgoing from node into query heap + for (auto out_edge : node_edge_range) { - const ContractorEdgeData &out_data = graph.GetEdgeData(out_edge); - if (!out_data.forward) - { + const ContractorEdgeData &n2t_data = graph.GetEdgeData(out_edge); + if (!n2t_data.forward) continue; - } const NodeID target = graph.GetTarget(out_edge); - if (node == target) - { + if (target == node) continue; - } - - const EdgeWeight path_weight = in_data.weight + out_data.weight; +#ifndef SELF_LOOPS if (target == source) - { - if (path_weight < node_weights[node]) - { - if (RUNSIMULATION) - { - // make sure to prune better, but keep inserting this loop if it should - // still be the best - // CAREFUL: This only works due to the independent node-setting. This - // guarantees that source is not connected to another node that is - // contracted - node_weights[source] = path_weight + EdgeWeight{1}; - BOOST_ASSERT(stats != nullptr); - stats->edges_added_count += 2; - stats->original_edges_added_count += - 2 * (out_data.originalEdges + in_data.originalEdges); - } - else - { - // CAREFUL: This only works due to the independent node-setting. This - // guarantees that source is not connected to another node that is - // contracted - node_weights[source] = path_weight; // make sure to prune better - inserted_edges.emplace_back(source, - target, - path_weight, - in_data.duration + out_data.duration, - in_data.distance + out_data.distance, - out_data.originalEdges + in_data.originalEdges, - node, - SHORTCUT_ARC, - FORWARD_DIRECTION_ENABLED, - REVERSE_DIRECTION_DISABLED); - - inserted_edges.emplace_back(target, - source, - path_weight, - in_data.duration + out_data.duration, - in_data.distance + out_data.distance, - out_data.originalEdges + in_data.originalEdges, - node, - SHORTCUT_ARC, - FORWARD_DIRECTION_DISABLED, - REVERSE_DIRECTION_ENABLED); - } - } continue; - } - max_weight = std::max(max_weight, path_weight); +#endif + if (!heap.WasInserted(target)) { - heap.Insert(target, INVALID_EDGE_WEIGHT, ContractorHeapData{0, true}); + heap.Insert(target, INVALID_EDGE_WEIGHT, true); + max_weight = std::max(max_weight, s2n_data.weight + n2t_data.weight); ++number_of_targets; } } - if (RUNSIMULATION) - { - const int constexpr SIMULATION_SEARCH_SPACE_SIZE = 1000; - search(heap, - graph, - contractable, - number_of_targets, - SIMULATION_SEARCH_SPACE_SIZE, - max_weight, - node); - } - else + // Look for paths around the node that are shorter than the way through the + // node. If we find any we don't have to insert a shortcut. This is a + // one-to-many search. + search(heap, + graph, + source, + node_data.is_contractible, + number_of_targets, + SEARCH_SPACE_SIZE, + max_weight, + node); + + for (auto n2t_edge : node_edge_range) { - const int constexpr FULL_SEARCH_SPACE_SIZE = 2000; - search(heap, - graph, - contractable, - number_of_targets, - FULL_SEARCH_SPACE_SIZE, - max_weight, - node); - } - for (auto out_edge : graph.GetAdjacentEdgeRange(node)) - { - const ContractorEdgeData &out_data = graph.GetEdgeData(out_edge); - if (!out_data.forward) - { + const ContractorEdgeData &n2t_data = graph.GetEdgeData(n2t_edge); + if (!n2t_data.forward) continue; - } - const NodeID target = graph.GetTarget(out_edge); + const NodeID target = graph.GetTarget(n2t_edge); if (target == node) continue; +#ifndef SELF_LOOPS + if (target == source) + continue; +#endif + const EdgeWeight thru_weight = s2n_data.weight + n2t_data.weight; + const EdgeWeight around_weight = heap.GetKey(target); - const EdgeWeight path_weight = in_data.weight + out_data.weight; - const EdgeWeight weight = heap.GetKey(target); - if (path_weight < weight) + if (thru_weight < around_weight) { + // the way through the node was the shortest: since the node after + // contraction is not reachable from source any more we must insert a + // shortcut to preserve shortest distances if (RUNSIMULATION) { - BOOST_ASSERT(stats != nullptr); - stats->edges_added_count += 2; +#ifdef SELF_LOOPS + const int mult = (target == source) ? 1 : 2; +#else + const int mult = 1; +#endif + stats->edges_added_count += mult; stats->original_edges_added_count += - 2 * (out_data.originalEdges + in_data.originalEdges); + mult * (n2t_data.originalEdges + s2n_data.originalEdges); } else { - inserted_edges.emplace_back(source, - target, - path_weight, - in_data.duration + out_data.duration, - in_data.distance + out_data.distance, - out_data.originalEdges + in_data.originalEdges, - node, - SHORTCUT_ARC, - FORWARD_DIRECTION_ENABLED, - REVERSE_DIRECTION_DISABLED); - - inserted_edges.emplace_back(target, - source, - path_weight, - in_data.duration + out_data.duration, - in_data.distance + out_data.distance, - out_data.originalEdges + in_data.originalEdges, - node, - SHORTCUT_ARC, - FORWARD_DIRECTION_DISABLED, - REVERSE_DIRECTION_ENABLED); - } - } - } - } + // For any logical edge u->v the contractor inserts a forward edge + // on u and a backward edge on v. The backward edge on v is logically + // redundant but essential when we look for neighbours of v. + if (target != source) + { + inserted_edges->emplace_back(source, + target, + thru_weight, + s2n_data.duration + n2t_data.duration, + s2n_data.distance + n2t_data.distance, + n2t_data.originalEdges + + s2n_data.originalEdges, + node, + true, // shortcut + true, // forward + false); // backward + + inserted_edges->emplace_back(target, + source, + thru_weight, + s2n_data.duration + n2t_data.duration, + s2n_data.distance + n2t_data.distance, + n2t_data.originalEdges + + s2n_data.originalEdges, + node, + true, // shortcut + false, // forward + true); // backward + } +#ifdef SELF_LOOPS + if (target == source) + { + inserted_edges->emplace_back(source, + target, + thru_weight, + s2n_data.duration + n2t_data.duration, + s2n_data.distance + n2t_data.distance, + n2t_data.originalEdges + + s2n_data.originalEdges, + node, + true, // shortcut + true, // forward + true); // backward + } - // Check For One-Way Streets to decide on the creation of self-loops - if (!RUNSIMULATION) - { - std::size_t iend = inserted_edges.size(); - for (std::size_t i = inserted_edges_size; i < iend; ++i) - { - bool found = false; - for (std::size_t other = i + 1; other < iend; ++other) - { - if (inserted_edges[other].source != inserted_edges[i].source) - { - continue; +#endif } - if (inserted_edges[other].target != inserted_edges[i].target) - { - continue; - } - if (inserted_edges[other].data.weight != inserted_edges[i].data.weight) - { - continue; - } - if (inserted_edges[other].data.shortcut != inserted_edges[i].data.shortcut) - { - continue; - } - inserted_edges[other].data.forward |= inserted_edges[i].data.forward; - inserted_edges[other].data.backward |= inserted_edges[i].data.backward; - found = true; - break; - } - if (!found) - { - inserted_edges[inserted_edges_size++] = inserted_edges[i]; - } - } - inserted_edges.resize(inserted_edges_size); - } -} - -void ContractNode(ContractorThreadData *data, - const ContractorGraph &graph, - const NodeID node, - std::vector &node_weights, - const std::vector &contractable) -{ - ContractNode(data, graph, node, node_weights, contractable, nullptr); -} - -ContractionStats SimulateNodeContraction(ContractorThreadData *data, - const ContractorGraph &graph, - const NodeID node, - std::vector &node_weights, - const std::vector &contractable) -{ - ContractionStats stats; - ContractNode(data, graph, node, node_weights, contractable, &stats); - return stats; -} - -void RenumberGraph(ContractorGraph &graph, const std::vector &old_to_new) -{ - graph.Renumber(old_to_new); - // Renumber all shortcut node IDs - for (const auto node : util::irange(0, graph.GetNumberOfNodes())) - { - for (const auto edge : graph.GetAdjacentEdgeRange(node)) - { - auto &data = graph.GetEdgeData(edge); - if (data.shortcut) - { - data.id = old_to_new[data.id]; } } } } -/* Reorder nodes for better locality during contraction */ -void RenumberData(std::vector &remaining_nodes, - std::vector &new_to_old_node_id, - ContractorNodeData &node_data, - ContractorGraph &graph) -{ - std::vector current_to_new_node_id(graph.GetNumberOfNodes(), SPECIAL_NODEID); - - // we need to make a copy here because we are going to modify it - auto to_orig = new_to_old_node_id; - - auto new_node_id = 0u; - - // All remaining nodes get the low IDs - for (auto &remaining : remaining_nodes) - { - auto id = new_node_id++; - current_to_new_node_id[remaining.id] = id; - new_to_old_node_id[id] = to_orig[remaining.id]; - remaining.id = id; - } - - // Already contracted nodes get the high IDs - for (const auto current_id : util::irange(0, graph.GetNumberOfNodes())) - { - if (current_to_new_node_id[current_id] == SPECIAL_NODEID) - { - auto id = new_node_id++; - current_to_new_node_id[current_id] = id; - new_to_old_node_id[id] = to_orig[current_id]; - } - } - BOOST_ASSERT(new_node_id == graph.GetNumberOfNodes()); - - node_data.Renumber(current_to_new_node_id); - RenumberGraph(graph, current_to_new_node_id); -} - +/** + * @brief Calculate a node's priority. Lower priority nodes get contracted first. + * + * Note: This function is metric-agnostic to better accomodate the following + * customization phase in which metrics will be added. + * + * @param stats The statistics obtained from a simulated contraction. + * @param node_depth The node's depth. + * @return float The priority + */ float EvaluateNodePriority(const ContractionStats &stats, const ContractorNodeData::NodeDepth node_depth) { - // Result will contain the priority - float result; - if (0 == (stats.edges_deleted_count * stats.original_edges_deleted_count)) + float priority; + if (stats.edges_deleted_count == 0 || stats.original_edges_deleted_count == 0) { - result = 1.f * node_depth; + priority = 1.f * node_depth; } else { - result = + priority = 2.f * (((float)stats.edges_added_count) / stats.edges_deleted_count) + 4.f * (((float)stats.original_edges_added_count) / stats.original_edges_deleted_count) + 1.f * node_depth; } - BOOST_ASSERT(result >= 0); - return result; + BOOST_ASSERT(priority >= 0); + return priority; } -void DeleteIncomingEdges(ContractorThreadData *data, ContractorGraph &graph, const NodeID node) +/** + * @brief Post-process an independent node after contraction + * + * - Algo 2: Move I to their Level + * + * @param graph + * @param v + * @param node_data + */ +void PostProcess(ContractorGraph &graph, const NodeID v, ContractorNodeData &node_data) { - std::vector &neighbours = data->neighbours; - neighbours.clear(); - - // find all neighbours - for (auto e : graph.GetAdjacentEdgeRange(node)) + ContractorNodeData::NodeDepth depth = node_data.depths[v] + 1; + for (const NodeID u : GetNeighbours(graph, v)) { - const NodeID u = graph.GetTarget(e); - if (u != node) - { - neighbours.push_back(u); - } - } - // eliminate duplicate entries ( forward + backward edges ) - std::sort(neighbours.begin(), neighbours.end()); - neighbours.resize(std::unique(neighbours.begin(), neighbours.end()) - neighbours.begin()); + node_data.depths[u] = std::max(depth, node_data.depths[u]); + // "Irrespective of the direction flags, each edge (u, v) is stored only once, + // namely at the smaller node, which complies with the requirements of both + // forward and backward search (including the stall-on-demand technique)." + // [Geisberger2008] - for (const auto i : util::irange(0, neighbours.size())) - { - graph.DeleteEdgesTo(neighbours[i], node); + // See also: self-loops + + graph.DeleteEdgesTo(u, v); } } -bool UpdateNodeNeighbours(ContractorNodeData &node_data, - ContractorThreadData *data, - const ContractorGraph &graph, - const NodeID node) +/** + * @brief Inserts the edges produced by node contraction into the graph. + * + * - Algo 2: Insert E into Remaining graph + * + * Alas edge insertion is not thread-safe even for "independent" nodes (but edge erasure + * curiously is!). If the graph ever gets fixed to be thread-safe, this function can use + * parallel execution too. + * + * @param graph + * @param inserted_edges + */ + +void InsertEdges(ContractorGraph &graph, + const tbb::concurrent_vector &inserted_edges) { - std::vector &neighbours = data->neighbours; - neighbours.clear(); - - // find all neighbours - for (auto e : graph.GetAdjacentEdgeRange(node)) + for (const ContractorEdge &edge : inserted_edges) { - const NodeID u = graph.GetTarget(e); - if (u == node) + const EdgeID current_edge_ID = graph.FindEdge(edge.source, edge.target); + if (current_edge_ID != SPECIAL_EDGEID) { - continue; + // found edge in graph ... + auto ¤t_data = graph.GetEdgeData(current_edge_ID); + if (edge.data.forward == current_data.forward && + edge.data.backward == current_data.backward) + { + // ... but found edge has smaller weight, update it. + if (edge.data.weight < current_data.weight || + edge.data.duration < current_data.duration || + edge.data.distance < current_data.distance) + { + current_data = edge.data; + } + // ... so don't insert a duplicate. + continue; + } } - neighbours.push_back(u); - node_data.depths[u] = std::max(node_data.depths[node] + 1, node_data.depths[u]); + graph.InsertEdge(edge.source, edge.target, edge.data); } - // eliminate duplicate entries ( forward + backward edges ) - std::sort(neighbours.begin(), neighbours.end()); - neighbours.resize(std::unique(neighbours.begin(), neighbours.end()) - neighbours.begin()); +} - // re-evaluate priorities of neighboring nodes - for (const NodeID u : neighbours) +/** + * @brief Recalculate the priorities of all neighbouring nodes. + * + * @param graph + * @param v The node id + * @param data + * @param node_data + */ +void UpdateNeighbourPriorities(const ContractorGraph &graph, + const NodeID v, + ContractorNodeData &node_data, + ThreadData &thread_data) +{ + for (const NodeID u : GetNeighbours(graph, v)) { - if (node_data.contractable[u]) + if (node_data.is_core[u] && node_data.is_contractible[u]) { - node_data.priorities[u] = EvaluateNodePriority( - SimulateNodeContraction(data, graph, u, node_data.weights, node_data.contractable), - node_data.depths[u]); + ContractionStats stats; + ContractNode(graph, u, thread_data, node_data, nullptr, &stats); + node_data.priorities[u] = EvaluateNodePriority(stats, node_data.depths[u]); } } - return true; } -bool IsNodeIndependent(const std::vector &priorities, - const ContractorGraph &graph, - ContractorThreadData *const data, - const NodeID node) +/** + * @brief Test if a node is independent. + * + * Two independent nodes can be contracted in parallel without influencing each other. + * + * A node is independent if there is no node with a lower priority less than 3 hops away + * from it. (In case of equal priorities the node id is used as tie breaker.) The + * next-nearest independent node must be at least 3 hops away: they can be processed at + * the same time because all their neighbours are distinct. + * + * @param graph + * @param v the node to test + * @param priorities + * @return bool true if the node is independent. + */ +bool IsNodeIndependent(const ContractorGraph &graph, + const NodeID v, + const std::vector &priorities) { - const float priority = priorities[node]; + const float priority = priorities[v]; + BOOST_ASSERT(priority >= 0); - std::vector &neighbours = data->neighbours; - neighbours.clear(); - - for (auto e : graph.GetAdjacentEdgeRange(node)) + for (const NodeID hop1 : GetNeighbours(graph, v)) { - const NodeID target = graph.GetTarget(e); - if (node == target) - { - continue; - } - const float target_priority = priorities[target]; - BOOST_ASSERT(target_priority >= 0); - // found a neighbour with lower priority? - if (target_priority < priority || (target_priority == priority && target < node)) + // 1 hop away + const float hop1_priority = priorities[hop1]; + BOOST_ASSERT(hop1_priority >= 0); + + if (hop1_priority < priority || (hop1_priority == priority && hop1 < v)) { return false; } - neighbours.push_back(target); - } - - std::sort(neighbours.begin(), neighbours.end()); - neighbours.resize(std::unique(neighbours.begin(), neighbours.end()) - neighbours.begin()); - // examine all neighbours that are at most 2 hops away - for (const NodeID u : neighbours) - { - for (auto e : graph.GetAdjacentEdgeRange(u)) + for (auto e : graph.GetAdjacentEdgeRange(hop1)) { - const NodeID target = graph.GetTarget(e); - if (node == target) - { + // 2 hops away + const NodeID hop2 = graph.GetTarget(e); + // it is cheaper to evaluate a node twice than to do an expensive test here + if (hop2 == v) continue; - } - const float target_priority = priorities[target]; - BOOST_ASSERT(target_priority >= 0); - // found a neighbour with lower priority? - if (target_priority < priority || (target_priority == priority && target < node)) + const float hop2_priority = priorities[hop2]; + BOOST_ASSERT(hop2_priority >= 0); + + if (hop2_priority < priority || (hop2_priority == priority && hop2 < v)) { return false; } @@ -545,228 +517,195 @@ bool IsNodeIndependent(const std::vector &priorities, } return true; } + } // namespace +/** + * @brief Contract the graph + * + * See: Algorithm 2 in Chapter 4.3 of [Vetter2009] + * + * @param graph + * @param node_is_uncontracted_ + * @param node_is_contractible_ + * @param edge_weights_ + * @param core_factor + * @return std::vector + */ std::vector contractGraph(ContractorGraph &graph, std::vector node_is_uncontracted_, - std::vector node_is_contractable_, - std::vector node_weights_, + std::vector node_is_contractible_, double core_factor) { - BOOST_ASSERT(node_weights_.size() == graph.GetNumberOfNodes()); - - // for the preperation we can use a big grain size, which is much faster (probably cache) - const constexpr size_t PQGrainSize = 100000; - // auto_partitioner will automatically increase the blocksize if we have - // a lot of data. It is *important* for the last loop iterations - // (which have a very small dataset) that it is devisible. - const constexpr size_t IndependentGrainSize = 1; - const constexpr size_t ContractGrainSize = 1; - const constexpr size_t NeighboursGrainSize = 1; - const constexpr size_t DeleteGrainSize = 1; - - const NodeID number_of_nodes = graph.GetNumberOfNodes(); - - ThreadDataContainer thread_data_list(number_of_nodes); - - NodeID number_of_contracted_nodes = 0; - std::vector new_to_old_node_id(number_of_nodes); - // Fill the map with an identiy mapping - std::iota(new_to_old_node_id.begin(), new_to_old_node_id.end(), 0); - - ContractorNodeData node_data{graph.GetNumberOfNodes(), - std::move(node_is_uncontracted_), - std::move(node_is_contractable_), - std::move(node_weights_)}; - - std::vector remaining_nodes; - remaining_nodes.reserve(number_of_nodes); - for (auto node : util::irange(0, number_of_nodes)) - { - if (node_data.is_core[node]) - { - if (node_data.contractable[node]) - { - remaining_nodes.emplace_back(node, false); - } - else - { - node_data.priorities[node] = - std::numeric_limits::max(); - } - } - else - { - node_data.priorities[node] = 0; - } - } - - { - util::UnbufferedLog log; - log << "initializing node priorities..."; - tbb::parallel_for( - tbb::blocked_range(0, remaining_nodes.size(), PQGrainSize), - [&](const auto &range) - { - ContractorThreadData *data = thread_data_list.GetThreadData(); - for (auto x = range.begin(), end = range.end(); x != end; ++x) - { - auto node = remaining_nodes[x].id; - BOOST_ASSERT(node_data.contractable[node]); - node_data.priorities[node] = EvaluateNodePriority( - SimulateNodeContraction( - data, graph, node, node_data.weights, node_data.contractable), - node_data.depths[node]); - } - }); - log << " ok."; - } + /** A heap kept in thread-local storage to avoid multiple recreations of it. */ + ContractorHeap heap_exemplar(8000); + ThreadData thread_data(heap_exemplar); + /** Nodes still waiting for contraction. Not all of them will be contracted though. */ + tbb::concurrent_vector remaining_nodes; + + std::size_t number_of_contracted_nodes = 0; + + const unsigned int number_of_nodes = graph.GetNumberOfNodes(); + ContractorNodeData node_data{ + number_of_nodes, std::move(node_is_uncontracted_), std::move(node_is_contractible_)}; + + TIMER_DECLARE(init_priorities); + TIMER_DECLARE(contract); + TIMER_DECLARE(post_process); + TIMER_DECLARE(insert_edges); + TIMER_DECLARE(update_priorities); + TIMER_DECLARE(update_core); + TIMER_DECLARE(adjust_remaining); + TIMER_DECLARE(renumber); + + // Update Priorities of all Nodes with Simulated Contractions + util::Log() << "initializing node priorities..."; + TIMER_START(init_priorities); + tbb::parallel_for(NodeID{0}, + NodeID{number_of_nodes}, + [&](const NodeID v) + { + if (node_data.is_core[v] && node_data.is_contractible[v]) + { + remaining_nodes.emplace_back(v); + ContractionStats stats; + ContractNode(graph, v, thread_data, node_data, nullptr, &stats); + node_data.priorities[v] = + EvaluateNodePriority(stats, node_data.depths[v]); + } + else + { + node_data.priorities[v] = + std::numeric_limits::max(); + } + }); + TIMER_STOP(init_priorities); auto number_of_core_nodes = std::max(0, (1 - core_factor) * number_of_nodes); auto number_of_nodes_to_contract = remaining_nodes.size() - number_of_core_nodes; - util::Log() << "preprocessing " << number_of_nodes_to_contract << " (" + util::Log() << "will contract " << number_of_nodes_to_contract << " (" << (number_of_nodes_to_contract / (float)number_of_nodes * 100.) << "%) nodes..."; + util::Log() << "will leave " << number_of_core_nodes << " core nodes (" + << (number_of_core_nodes / (float)number_of_nodes * 100.) << "%) nodes..."; util::UnbufferedLog log; util::Percent p(log, remaining_nodes.size()); - std::size_t next_renumbering = number_of_nodes * 0.35; + // Algo 2: while Remaining Graph not Empty + // + // contract a chunk of nodes until enough nodes are contracted while (remaining_nodes.size() > number_of_core_nodes) { - if (remaining_nodes.size() < next_renumbering) - { - RenumberData(remaining_nodes, new_to_old_node_id, node_data, graph); - log << "[renumbered]"; - // only one renumbering for now - next_renumbering = 0; - } - - tbb::parallel_for( - tbb::blocked_range(0, remaining_nodes.size(), IndependentGrainSize), - [&](const auto &range) + /** List of discovered independent nodes */ + tbb::concurrent_vector independent_nodes; + /** List of new edges to insert into the graph */ + tbb::concurrent_vector inserted_edges; + + TIMER_START(contract); + tbb::parallel_for_each( + remaining_nodes, + [&](NodeID &v) { - ContractorThreadData *data = thread_data_list.GetThreadData(); - // determine independent node set - for (auto i = range.begin(), end = range.end(); i != end; ++i) + // Algo 2: I ← Independent Node Set + // + // push the discovered independent nodes into + // `independent_nodes` and mark them for deletion from + // `remaining_nodes` + if (IsNodeIndependent(graph, v, node_data.priorities)) { - const NodeID node = remaining_nodes[i].id; - remaining_nodes[i].is_independent = - IsNodeIndependent(node_data.priorities, graph, data, node); + independent_nodes.emplace_back(v); + + // Algo 2: E ← Necessary Shortcuts + // + // contract all independent nodes + // since all nodes are independent the order does not matter + ContractNode(graph, v, thread_data, node_data, &inserted_edges, nullptr); + v = SPECIAL_NODEID; // mark for removal } }); + TIMER_STOP(contract); - // sort all remaining nodes to the beginning of the sequence - const auto begin_independent_nodes = std::stable_partition( - remaining_nodes.begin(), - remaining_nodes.end(), - [](RemainingNodeData node_data) { return !node_data.is_independent; }); - auto begin_independent_nodes_idx = - std::distance(remaining_nodes.begin(), begin_independent_nodes); - auto end_independent_nodes_idx = remaining_nodes.size(); - - // contract independent nodes - tbb::parallel_for( - tbb::blocked_range( - begin_independent_nodes_idx, end_independent_nodes_idx, ContractGrainSize), - [&](const auto &range) - { - ContractorThreadData *data = thread_data_list.GetThreadData(); - for (auto position = range.begin(), end = range.end(); position != end; ++position) - { - const NodeID node = remaining_nodes[position].id; - ContractNode(data, graph, node, node_data.weights, node_data.contractable); - } - }); + if (independent_nodes.size() == 0) + // safety exit + break; + // adjust remaining_nodes + TIMER_START(adjust_remaining); + const auto new_end = + std::remove(remaining_nodes.begin(), remaining_nodes.end(), SPECIAL_NODEID); + remaining_nodes.resize(std::distance(remaining_nodes.begin(), new_end)); + TIMER_STOP(adjust_remaining); + + // core graph: the high(est) priority nodes // core flags need to be set in serial since vector is not thread safe - for (auto position : - util::irange(begin_independent_nodes_idx, end_independent_nodes_idx)) + TIMER_START(update_core); + for (const NodeID v : independent_nodes) { - node_data.is_core[remaining_nodes[position].id] = false; + node_data.is_core[v] = false; } + TIMER_STOP(update_core); - tbb::parallel_for( - tbb::blocked_range( - begin_independent_nodes_idx, end_independent_nodes_idx, DeleteGrainSize), - [&](const auto &range) - { - ContractorThreadData *data = thread_data_list.GetThreadData(); - for (auto position = range.begin(), end = range.end(); position != end; ++position) - { - const NodeID node = remaining_nodes[position].id; - DeleteIncomingEdges(data, graph, node); - } - }); - - // make sure we really sort each block - tbb::parallel_for(thread_data_list.data.range(), - [&](const auto &range) - { - for (auto &data : range) - tbb::parallel_sort(data->inserted_edges.begin(), - data->inserted_edges.end()); - }); - - // insert new edges - for (auto &data : thread_data_list.data) + // We cannot incorporate this into the loop above because the graph search done + // during `ContractNode()` above may well intrude upon other nodes' zones of + // "independence". *The graph cannot change while searches are done.* + TIMER_START(post_process); + if (remaining_nodes.size() > number_of_core_nodes) { - for (const ContractorEdge &edge : data->inserted_edges) - { - const EdgeID current_edge_ID = graph.FindEdge(edge.source, edge.target); - if (current_edge_ID != SPECIAL_EDGEID) - { - auto ¤t_data = graph.GetEdgeData(current_edge_ID); - if (current_data.shortcut && edge.data.forward == current_data.forward && - edge.data.backward == current_data.backward) - { - // found a duplicate edge with smaller weight, update it. - if (edge.data.weight < current_data.weight) - { - current_data = edge.data; - } - // don't insert duplicates - continue; - } - } - graph.InsertEdge(edge.source, edge.target, edge.data); - } - data->inserted_edges.clear(); + tbb::parallel_for_each(independent_nodes, + [&](const NodeID v) + { + // Algo 2: Move I to their Level + // and delete "down" edges + PostProcess(graph, v, node_data); + }); } + TIMER_STOP(post_process); + + // Algo 2: Insert E into Remaining graph + TIMER_START(insert_edges); + tbb::parallel_sort(inserted_edges); + InsertEdges(graph, inserted_edges); + TIMER_STOP(insert_edges); + + // Algo 2: Update Priority of Neighbors of I with Simulated Contractions + // This again searches the graph, so graph updates cannot happen at the same time. + TIMER_START(update_priorities); + if (remaining_nodes.size() > number_of_core_nodes) + { + tbb::parallel_for_each(independent_nodes, + [&](const NodeID v) { + UpdateNeighbourPriorities(graph, v, node_data, thread_data); + }); + } + TIMER_STOP(update_priorities); - tbb::parallel_for( - tbb::blocked_range( - begin_independent_nodes_idx, end_independent_nodes_idx, NeighboursGrainSize), - [&](const auto &range) - { - ContractorThreadData *data = thread_data_list.GetThreadData(); - for (auto position = range.begin(), end = range.end(); position != end; ++position) - { - NodeID node = remaining_nodes[position].id; - UpdateNodeNeighbours(node_data, data, graph, node); - } - }); - - // remove contracted nodes from the pool - BOOST_ASSERT(end_independent_nodes_idx - begin_independent_nodes_idx > 0); - number_of_contracted_nodes += end_independent_nodes_idx - begin_independent_nodes_idx; - remaining_nodes.resize(begin_independent_nodes_idx); - + number_of_contracted_nodes += independent_nodes.size(); p.PrintStatus(number_of_contracted_nodes); } - node_data.Renumber(new_to_old_node_id); - RenumberGraph(graph, new_to_old_node_id); + // no permutation happens here but the edge list is compressed + TIMER_START(renumber); + graph.Renumber(std::vector()); + TIMER_STOP(renumber); + + util::Log() << "node priorities initialized in " << TIMER_MSEC(init_priorities); + util::Log() << "nodes contracted in " << TIMER_MSEC(contract); + util::Log() << "nodes post-processed in " << TIMER_MSEC(post_process); + util::Log() << "edges inserted in " << TIMER_MSEC(insert_edges); + util::Log() << "node priorities updated in " << TIMER_MSEC(update_priorities); + util::Log() << "core flags updated in " << TIMER_MSEC(update_core); + util::Log() << "adjusted remaining nodes left in " << TIMER_MSEC(adjust_remaining); + util::Log() << "graph renumbered in " << TIMER_MSEC(renumber); return std::move(node_data.is_core); } using GraphAndFilter = std::tuple>>; -GraphAndFilter contractFullGraph(ContractorGraph contractor_graph, - std::vector node_weights) +GraphAndFilter contractFullGraph(ContractorGraph contractor_graph) { auto num_nodes = contractor_graph.GetNumberOfNodes(); - contractGraph(contractor_graph, std::move(node_weights)); + contractGraph(contractor_graph); auto edges = toEdges(std::move(contractor_graph)); std::vector edge_filter(edges.size(), true); @@ -775,14 +714,13 @@ GraphAndFilter contractFullGraph(ContractorGraph contractor_graph, } GraphAndFilter contractExcludableGraph(ContractorGraph contractor_graph_, - std::vector node_weights, const std::vector> &filters) { if (filters.size() == 1) { if (std::all_of(filters.front().begin(), filters.front().end(), [](auto v) { return v; })) { - return contractFullGraph(std::move(contractor_graph_), std::move(node_weights)); + return contractFullGraph(std::move(contractor_graph_)); } } @@ -801,12 +739,11 @@ GraphAndFilter contractExcludableGraph(ContractorGraph contractor_graph_, } } - // By not contracting all contractable nodes we avoid creating + // By not contracting all contractible nodes we avoid creating // a very dense core. This increases the overall graph sizes a little bit // but increases the final CH quality and contraction speed. constexpr float BASE_CORE = 0.9f; - is_shared_core = - contractGraph(contractor_graph, std::move(always_allowed), node_weights, BASE_CORE); + is_shared_core = contractGraph(contractor_graph, std::move(always_allowed), BASE_CORE); // Add all non-core edges to container { @@ -836,7 +773,7 @@ GraphAndFilter contractExcludableGraph(ContractorGraph contractor_graph_, auto filtered_core_graph = shared_core_graph.Filter([&filter](const NodeID node) { return filter[node]; }); - contractGraph(filtered_core_graph, is_shared_core, is_shared_core, node_weights); + contractGraph(filtered_core_graph, is_shared_core, is_shared_core); edge_container.Merge(toEdges(std::move(filtered_core_graph))); } diff --git a/src/engine/routing_algorithms/routing_base_ch.cpp b/src/engine/routing_algorithms/routing_base_ch.cpp index c4bf2f84c92..051d93f5aec 100644 --- a/src/engine/routing_algorithms/routing_base_ch.cpp +++ b/src/engine/routing_algorithms/routing_base_ch.cpp @@ -1,4 +1,5 @@ #include "engine/routing_algorithms/routing_base_ch.hpp" +#include "util/log.hpp" namespace osrm::engine::routing_algorithms::ch { @@ -131,6 +132,9 @@ void search(SearchEngineData & /*engine_working_data*/, } } + util::Log(logDEBUG) << "Search completed with middle node " << middle << " at weight " + << weight; + // No path found for both target nodes? if (weight_upper_bound <= weight || SPECIAL_NODEID == middle) { @@ -152,6 +156,14 @@ void search(SearchEngineData & /*engine_working_data*/, { retrievePackedPathFromHeap(forward_heap, reverse_heap, middle, packed_leg); } + { + auto log = util::Log(logDEBUG); + log << " and packed_leg of size " << packed_leg.size() << " with nodes"; + for (auto node : packed_leg) + { + log << " " << node; + } + } } // Requires the heaps for be empty diff --git a/src/tools/routed.cpp b/src/tools/routed.cpp index a3cf4dba054..ffcf19f517c 100644 --- a/src/tools/routed.cpp +++ b/src/tools/routed.cpp @@ -427,10 +427,8 @@ catch (const std::bad_alloc &e) util::Log(logWARNING) << "Please provide more memory or consider using a larger swapfile"; return EXIT_FAILURE; } -#ifdef _WIN32 catch (const std::exception &e) { util::Log(logERROR) << "[exception] " << e.what(); return EXIT_FAILURE; } -#endif diff --git a/unit_tests/contractor/graph_contractor.cpp b/unit_tests/contractor/graph_contractor.cpp index 2999df11fcc..382b408ea5f 100644 --- a/unit_tests/contractor/graph_contractor.cpp +++ b/unit_tests/contractor/graph_contractor.cpp @@ -30,7 +30,7 @@ BOOST_AUTO_TEST_CASE(contract_graph) const ContractorGraph g = makeGraph({{0, 1, 1}}); // start, target, weight auto query_graph = g; - contractGraph(query_graph, {{1}, {1}}); + contractGraph(query_graph); HAS(0, 1) NOT(1, 0) @@ -49,7 +49,7 @@ BOOST_AUTO_TEST_CASE(contract_graph) {1, 2, 1}}); auto query_graph = g; - contractGraph(query_graph, {{1}, {1}, {1}}); + contractGraph(query_graph); HAS(0, 1) HAS(2, 1) @@ -78,7 +78,7 @@ BOOST_AUTO_TEST_CASE(contract_graph) {0, 2, 1}}); auto query_graph = g; - contractGraph(query_graph, {{1}, {1}, {1}}); + contractGraph(query_graph, {}); HAS(0, 1) HAS(0, 2) @@ -108,7 +108,7 @@ BOOST_AUTO_TEST_CASE(contract_graph) {3, 0, 1}}); auto query_graph = g; - contractGraph(query_graph, {{1}, {1}, {1}, {1}}); + contractGraph(query_graph, {}); HAS(0, 1) HAS(0, 3) @@ -149,8 +149,8 @@ BOOST_AUTO_TEST_CASE(contract_excludable_graph) {2, 3, 1}, {3, 0, 1}}); - auto [query_graph, ignore] = contractExcludableGraph( - g, {{1}, {1}, {1}, {1}}, {{true, true, true, true}, {false, true, true, true}}); + auto [query_graph, ignore] = + contractExcludableGraph(g, {{true, true, true, true}, {false, true, true, true}}); HAS(1, 0) HAS(1, 2) diff --git a/unit_tests/util/query_heap.cpp b/unit_tests/util/query_heap.cpp index 5df633e1c3f..4045abd50b5 100644 --- a/unit_tests/util/query_heap.cpp +++ b/unit_tests/util/query_heap.cpp @@ -1,4 +1,5 @@ #include "util/query_heap.hpp" +#include "util/linear_hash_storage.hpp" #include "util/typedefs.hpp" #include @@ -22,8 +23,9 @@ struct TestData using TestNodeID = NodeID; using TestKey = int; using TestWeight = int; -using storage_types = - boost::mpl::list, UnorderedMapStorage>; +using storage_types = boost::mpl::list, + UnorderedMapStorage, + LinearHashStorage>; template struct RandomDataFixture { @@ -49,7 +51,7 @@ template struct RandomDataFixture std::vector order; }; -constexpr unsigned NUM_NODES = 100; +constexpr unsigned NUM_NODES = 0x100; BOOST_FIXTURE_TEST_CASE_TEMPLATE(insert_test, T, storage_types, RandomDataFixture) { @@ -142,9 +144,9 @@ BOOST_FIXTURE_TEST_CASE_TEMPLATE(smoke_test, T, storage_types, RandomDataFixture } } -BOOST_FIXTURE_TEST_CASE_TEMPLATE(decrease_key_test, T, storage_types, RandomDataFixture<10>) +BOOST_FIXTURE_TEST_CASE_TEMPLATE(decrease_key_test, T, storage_types, RandomDataFixture<16>) { - QueryHeap heap(10); + QueryHeap heap(16); for (unsigned idx : order) { From b55d41f7f93d4f937ff28013f75763f4331fa7e8 Mon Sep 17 00:00:00 2001 From: Marcello Perathoner Date: Thu, 2 Apr 2026 22:57:35 +0200 Subject: [PATCH 2/3] WIP --- include/contractor/contractor_heap.hpp | 1 + include/util/query_heap.hpp | 1 - src/contractor/graph_contractor.cpp | 53 +++++++++++----------- unit_tests/contractor/graph_contractor.cpp | 11 +++-- 4 files changed, 36 insertions(+), 30 deletions(-) diff --git a/include/contractor/contractor_heap.hpp b/include/contractor/contractor_heap.hpp index c5a20ceb0d3..3bbe6053916 100644 --- a/include/contractor/contractor_heap.hpp +++ b/include/contractor/contractor_heap.hpp @@ -7,6 +7,7 @@ namespace osrm::contractor { +// Data: if true signals that node is a target using ContractorHeap = util::QueryHeap>; diff --git a/include/util/query_heap.hpp b/include/util/query_heap.hpp index 66607d7f985..3cea07e18fe 100644 --- a/include/util/query_heap.hpp +++ b/include/util/query_heap.hpp @@ -7,7 +7,6 @@ #include "d_ary_heap.hpp" #include #include -#include #include #include diff --git a/src/contractor/graph_contractor.cpp b/src/contractor/graph_contractor.cpp index 073177cb0e9..93150c62c13 100644 --- a/src/contractor/graph_contractor.cpp +++ b/src/contractor/graph_contractor.cpp @@ -43,29 +43,31 @@ Parallel Time-Dependent Contraction Hierarchies Christian Vetter - July 13, 2009 https://ae.iti.kit.edu/download/vetter_sa.pdf -tldr: - -All nodes in the graph are ordered by a unique "priority". Searches only follow edges -from lower priority nodes (think tertiary road) to higher priority nodes (think -motorways). Searches start from both source and target until they meet somewhere in the -middle. - -The shortest distance between any two nodes must be invariant under contraction. Node -contraction removes all edges going "down" into the contracted node. This does not -change any distance from the contracted node to any other since all searches starting at -the contracted node only go "up". But all paths that went through the contracted node -before are now interrupted. To fix this, contraction also inserts "shortcuts" around the -contracted node: For each pair of immediate neighbours of the contracted node a shortcut -is inserted iff the way through the node was the shortest path between both of them. +tl;dr: + +All nodes in the graph are ordered by their "priority". Searches only follow edges from +lower priority nodes (think: residential road) to higher priority nodes (think: +motorway). Searches start from both source and target at the same time until they meet +somewhere in the middle. + +"A node v is contracted by removing it from the network in such a way that shortest +paths in the remaining overlay graph are preserved." [Geisberger2008] Node contraction +removes all edges going "down" into the contracted node. This does not change any +distance from the contracted node itself to any other node since all searches starting +at the contracted node only ever go "up". But all paths that went through the +contracted node before are now interrupted. To fix this, contraction also inserts +"shortcuts" around the contracted node: For each pair of immediate neighbours of the +contracted node a shortcut is inserted iff the way through the node was the shortest +path between both nodes in the pair. The nodes get contracted in order of their priority. Since there will be fewer and fewer nodes left to be contracted the shortcuts will cover greater and greater distances. After a node is contracted the priorities of all neighbouring nodes are updated. -Contraction should be strictly sequential from the lowest priority node to the highest. -To parallelize this process we introduce the concept of independent node. A node is -independent if it is far enough removed from other independent nodes. Independent nodes -can be contracted in parallel. +Contraction in [Geisberger2008] is strictly sequential from the lowest priority node to +the highest. To parallelize this process we introduce the concept of independent node +[Vetter2009]. A node is independent if it is far enough removed from any other +independent node. Independent nodes can thus be contracted in parallel. We first find all independent nodes, then contract all of them in parallel. This step is repeated until a sufficient percentage of all nodes is contracted. @@ -74,11 +76,12 @@ See: Algorithm 2 in Chapter 4.3 of [Vetter2009] A note about self-loops -Contraction must keep the graph invariant wrt. shortest distance between any two nodes. -We have the added requirement to keep invariant the shortest loop distance from any node -to itself. This requirement arises from the need to "go around" if source and target are -on the same node with source downstream from target. For this reason we must insert -self-loops for all nodes. +The shortest distance between any two nodes must be invariant under contraction. We +must also keep invariant the shortest loop distance from any node back to itself. This +requirement arises for us from the need to "go around" if source and target are on the +same node, with source downstream from target. For this reason we must insert +self-loops whenever this is the shortest path from self to self. + Notes @@ -131,8 +134,6 @@ struct ContractorNodeData std::vector is_contractible; std::vector priorities; std::vector depths; - /** List of "lost" edges to re-insert into the graph */ - tbb::concurrent_vector lost_edges; }; struct ContractionStats @@ -243,7 +244,7 @@ void ContractNode(const ContractorGraph &graph, if (!heap.WasInserted(target)) { - heap.Insert(target, INVALID_EDGE_WEIGHT, true); + heap.Insert(target, INVALID_EDGE_WEIGHT, true); // true: node is a target max_weight = std::max(max_weight, s2n_data.weight + n2t_data.weight); ++number_of_targets; } diff --git a/unit_tests/contractor/graph_contractor.cpp b/unit_tests/contractor/graph_contractor.cpp index 382b408ea5f..af74a6fbf75 100644 --- a/unit_tests/contractor/graph_contractor.cpp +++ b/unit_tests/contractor/graph_contractor.cpp @@ -78,7 +78,7 @@ BOOST_AUTO_TEST_CASE(contract_graph) {0, 2, 1}}); auto query_graph = g; - contractGraph(query_graph, {}); + contractGraph(query_graph); HAS(0, 1) HAS(0, 2) @@ -108,7 +108,7 @@ BOOST_AUTO_TEST_CASE(contract_graph) {3, 0, 1}}); auto query_graph = g; - contractGraph(query_graph, {}); + contractGraph(query_graph); HAS(0, 1) HAS(0, 3) @@ -116,6 +116,11 @@ BOOST_AUTO_TEST_CASE(contract_graph) HAS(2, 3) HAS(1, 3) + HAS(3, 3) // self-loops + HAS(1, 1) + NOT(0, 0) + NOT(2, 2) + NOT(1, 0) NOT(3, 0) NOT(1, 2) @@ -157,12 +162,12 @@ BOOST_AUTO_TEST_CASE(contract_excludable_graph) HAS(3, 0) HAS(3, 2) HAS(2, 0) + HAS(0, 2) NOT(0, 1) NOT(2, 1) NOT(0, 3) NOT(2, 3) - NOT(0, 2) NOT(1, 3) NOT(3, 1) From c768db25513a870e4e57eee908c066bb75a6cc15 Mon Sep 17 00:00:00 2001 From: Marcello Perathoner Date: Sat, 4 Apr 2026 13:01:56 +0200 Subject: [PATCH 3/3] WIP --- features/support/route.js | 1 + features/support/run.js | 4 +- features/testbot/self_loop.feature | 45 +++++++++++++++++++ include/contractor/contractor_search.hpp | 2 +- include/contractor/graph_contractor.hpp | 6 +-- .../contractor/graph_contractor_adaptors.hpp | 10 ++--- include/engine/guidance/assemble_leg.hpp | 1 - include/engine/guidance/assemble_steps.hpp | 2 + .../routing_algorithms/routing_base_ch.hpp | 2 +- scripts/debug/dump_hsgr.py | 2 +- scripts/routed_benchmark.py | 44 +++++++++++++----- src/contractor/contractor.cpp | 1 - src/contractor/graph_contractor.cpp | 37 ++++----------- .../routing_algorithms/routing_base_ch.cpp | 7 ++- src/tools/routed.cpp | 2 + 15 files changed, 111 insertions(+), 55 deletions(-) create mode 100644 features/testbot/self_loop.feature diff --git a/features/support/route.js b/features/support/route.js index cc8aa2dc34d..87cc109ad98 100644 --- a/features/support/route.js +++ b/features/support/route.js @@ -75,6 +75,7 @@ export default class Route { if (bearings.length) { params.bearings = bearings .map((b) => { + if (b === '*') return ''; const bs = b.split(','); if (bs.length === 2) return b; else return (b += ',10'); diff --git a/features/support/run.js b/features/support/run.js index b0a0fba666e..4a3f5177a19 100644 --- a/features/support/run.js +++ b/features/support/run.js @@ -76,9 +76,9 @@ export function runBinSync(bin, args, options, log) { if (child.stderr) log(`${bin} stderr:\n${child.stderr}`); if (child.status != null) - log(`${bin} sync completed with exit code ${child.status}`); + log(`${bin} completed with exit code ${child.status}`); if (child.signal != null) { - const msg = `${bin} sync aborted with signal ${child.signal}`; + const msg = `${bin} aborted with signal ${child.signal}`; log(msg); if (child.signal != 'SIGABRT') // some tests deliberately fail throw new Error(msg); diff --git a/features/testbot/self_loop.feature b/features/testbot/self_loop.feature new file mode 100644 index 00000000000..5743dd7022b --- /dev/null +++ b/features/testbot/self_loop.feature @@ -0,0 +1,45 @@ +@routing @testbot @self-loop +Feature: Self-loops + Background: + Given the profile "testbot" + + Scenario: Waypoints on same edge with approaches + Given the node map + """ + 4 3 + a-------b + 1 2 + """ + + And the ways + | nodes | highway | + | ab | residential | + + When I route I should get + | waypoints | approaches | route | + | 1,2 | curb curb | ab,ab | + | 2,3 | curb curb | ab,ab,ab | + | 3,4 | curb curb | ab,ab | + | 4,1 | curb curb | ab,ab,ab | + | 4,3 | curb curb | ab,ab,ab,ab | + | 3,2 | curb curb | ab,ab,ab | + | 2,1 | curb curb | ab,ab,ab,ab | + | 1,4 | curb curb | ab,ab,ab | + + + Scenario: Waypoints on same edge with bearings + Given the node map + """ + 4 3 + a-------b + 1 2 + """ + + And the ways + | nodes | highway | + | ab | residential | + + When I route I should get + | waypoints | bearings | route | + | 2,1 | 90 90 | ab,ab,ab,ab | + | 4,3 | 270 270 | ab,ab,ab,ab | diff --git a/include/contractor/contractor_search.hpp b/include/contractor/contractor_search.hpp index 66e27a7bf70..b474da38cd6 100644 --- a/include/contractor/contractor_search.hpp +++ b/include/contractor/contractor_search.hpp @@ -14,7 +14,7 @@ namespace osrm::contractor void search(ContractorHeap &heap, const ContractorGraph &graph, const NodeID start, - const std::vector &contractable, + const std::vector &contractible, const unsigned number_of_targets, const int node_limit, const EdgeWeight weight_limit, diff --git a/include/contractor/graph_contractor.hpp b/include/contractor/graph_contractor.hpp index aab0cb62152..4479f4fdf77 100644 --- a/include/contractor/graph_contractor.hpp +++ b/include/contractor/graph_contractor.hpp @@ -18,7 +18,7 @@ GraphAndFilter contractExcludableGraph(ContractorGraph contractor_graph_, std::vector contractGraph(ContractorGraph &graph, std::vector node_is_uncontracted, - std::vector node_is_contractable, + std::vector node_is_contractible, double core_factor = 1.0); // Overload for contracting all nodes @@ -29,10 +29,10 @@ inline auto contractGraph(ContractorGraph &graph, double core_factor = 1.0) // Overload no contracted nodes inline auto contractGraph(ContractorGraph &graph, - std::vector node_is_contractable, + std::vector node_is_contractible, double core_factor = 1.0) { - return contractGraph(graph, {}, std::move(node_is_contractable), core_factor); + return contractGraph(graph, {}, std::move(node_is_contractible), core_factor); } } // namespace osrm::contractor diff --git a/include/contractor/graph_contractor_adaptors.hpp b/include/contractor/graph_contractor_adaptors.hpp index 6817da026fa..d4dafafea81 100644 --- a/include/contractor/graph_contractor_adaptors.hpp +++ b/include/contractor/graph_contractor_adaptors.hpp @@ -44,9 +44,9 @@ ContractorGraph toContractorGraph(NodeID number_of_nodes, const InputEdgeContain input_edge.data.distance, 1, input_edge.data.turn_id, - false, - input_edge.data.forward ? true : false, - input_edge.data.backward ? true : false); + false, // shortcut + input_edge.data.forward, + input_edge.data.backward); edges.emplace_back(input_edge.target, input_edge.source, @@ -56,8 +56,8 @@ ContractorGraph toContractorGraph(NodeID number_of_nodes, const InputEdgeContain 1, input_edge.data.turn_id, false, - input_edge.data.backward ? true : false, - input_edge.data.forward ? true : false); + input_edge.data.backward, + input_edge.data.forward); }; tbb::parallel_sort(edges.begin(), edges.end()); diff --git a/include/engine/guidance/assemble_leg.hpp b/include/engine/guidance/assemble_leg.hpp index 654671fe455..a80b18f9e75 100644 --- a/include/engine/guidance/assemble_leg.hpp +++ b/include/engine/guidance/assemble_leg.hpp @@ -7,7 +7,6 @@ #include "engine/guidance/route_step.hpp" #include "engine/internal_route_result.hpp" #include "util/coordinate_calculation.hpp" -#include "util/log.hpp" #include "util/typedefs.hpp" #include diff --git a/include/engine/guidance/assemble_steps.hpp b/include/engine/guidance/assemble_steps.hpp index 627d26d642f..53030e9b305 100644 --- a/include/engine/guidance/assemble_steps.hpp +++ b/include/engine/guidance/assemble_steps.hpp @@ -154,8 +154,10 @@ inline std::vector assembleSteps(const datafacade::BaseDataFacade &fa {intersection}, is_left_hand_driving}); +#ifndef NDEBUG util::Log(logDEBUG) << "pushing RouteStep of duration " << from_alias(segment_duration) / 10.; +#endif if (leg_data_index + 1 < leg_data.size()) { diff --git a/include/engine/routing_algorithms/routing_base_ch.hpp b/include/engine/routing_algorithms/routing_base_ch.hpp index 0afe57449db..2228aab1871 100644 --- a/include/engine/routing_algorithms/routing_base_ch.hpp +++ b/include/engine/routing_algorithms/routing_base_ch.hpp @@ -113,7 +113,7 @@ void routingStep(const DataFacade &facade, NodeID &middle_node_id, EdgeWeight &upper_bound, EdgeWeight min_edge_offset, - [[maybe_unused]] const std::vector &force_step_nodes) + const std::vector &force_step_nodes) { auto heapNode = forward_heap.DeleteMinGetHeapNode(); const auto reverseHeapNode = reverse_heap.GetHeapNodeIfWasInserted(heapNode.node); diff --git a/scripts/debug/dump_hsgr.py b/scripts/debug/dump_hsgr.py index 63890cd0cb3..1ea93a49469 100644 --- a/scripts/debug/dump_hsgr.py +++ b/scripts/debug/dump_hsgr.py @@ -1,5 +1,5 @@ """ -Contractior debugging script. +Contractor debugging script. This script displays the content of `osrm.ebg` and `osrm.hsgr` files both in tabular form and as `.dot` graph. diff --git a/scripts/routed_benchmark.py b/scripts/routed_benchmark.py index c9fff8dcf36..c6b0970c85f 100644 --- a/scripts/routed_benchmark.py +++ b/scripts/routed_benchmark.py @@ -159,22 +159,43 @@ def make_csv(args): def report(args): rows = list() index = list() + + current_run = None + current_log = None + for logfile in args.logfiles: with open(logfile) as log: for line in log: - if m := re.search(r"^### (\d+) \"(.*?)\" \"(.*?)\"$", line): - index.append((int(m.group(1)), m.group(3))) - rows.append({"samples": 0, "time": 0.0, "distance": 0.0}) - if m := re.search(r"([.\d]+)ms", line): - rows[-1]["time"] += float(m.group(1)) - rows[-1]["samples"] += 1 - if m := re.search(r"Distance: ([.\d]+)", line): - rows[-1]["distance"] += float(m.group(1)) + if m := re.search( + r"^### (\d+) \"(.*?)\" \"(.*?)\"$", line + ): # run build log + current_run = int(m.group(1)) + current_log = m.group(3) + continue + + if m := re.search(r"(/route/v1/.*$)", line): + current_url = m.group(1) + index.append([current_run, current_log, current_url]) + if m := re.search(r"([.\d]+)ms", line): + current_time = float(m.group(1)) + line = log.readline() + if m := re.search(r"Distance: ([.\d]+)", line): + current_distance = float(m.group(1)) + rows.append( + { + "time": current_time, + "distance": current_distance, + } + ) df = pd.DataFrame( - rows, index=pd.MultiIndex.from_tuples(index, names=("run", "log")) + rows, index=pd.MultiIndex.from_tuples(index, names=("run", "log", "url")) ) + # pd.set_option('display.max_rows', 500) + # pd.set_option('display.max_columns', 500) + # pd.set_option("display.width", 1000) + print(f"## RAW data - {datetime.datetime.now().isoformat()}\n```") print(df) print("```") @@ -182,6 +203,9 @@ def report(args): def norm(series): return series / series.iloc[0] + check = df.distance.groupby(["url"], sort=False).agg(["std"]) + print(check[check["std"] > 0]) + groupby = ["log"] agg = df.time.groupby(groupby, sort=False).agg(["median"]) @@ -197,7 +221,7 @@ def norm(series): agg = agg.reset_index(names=groupby) headers = ("log", "time (ms)", "norm", "distance", "norm") - floatfmt = ("", ".2f", ".3f", ".0f", ".3f") + floatfmt = ("", ".2f", ".3f", ".3f", ".3f") print( tabulate.tabulate( agg, headers, tablefmt="github", floatfmt=floatfmt, showindex=False diff --git a/src/contractor/contractor.cpp b/src/contractor/contractor.cpp index cc81560204e..4251915296f 100644 --- a/src/contractor/contractor.cpp +++ b/src/contractor/contractor.cpp @@ -63,7 +63,6 @@ int Contractor::Run() QueryGraph query_graph; std::vector> edge_filters; - std::vector> cores; std::tie(query_graph, edge_filters) = contractExcludableGraph( toContractorGraph(number_of_edge_based_nodes, edge_based_edge_list), node_filters); TIMER_STOP(contraction); diff --git a/src/contractor/graph_contractor.cpp b/src/contractor/graph_contractor.cpp index 93150c62c13..baf905a669a 100644 --- a/src/contractor/graph_contractor.cpp +++ b/src/contractor/graph_contractor.cpp @@ -335,9 +335,9 @@ void ContractNode(const ContractorGraph &graph, n2t_data.originalEdges + s2n_data.originalEdges, node, - true, // shortcut - true, // forward - true); // backward + true, // shortcut + true, // forward + false); // backward } #endif @@ -391,13 +391,12 @@ void PostProcess(ContractorGraph &graph, const NodeID v, ContractorNodeData &nod for (const NodeID u : GetNeighbours(graph, v)) { node_data.depths[u] = std::max(depth, node_data.depths[u]); + // "Irrespective of the direction flags, each edge (u, v) is stored only once, // namely at the smaller node, which complies with the requirements of both // forward and backward search (including the stall-on-demand technique)." // [Geisberger2008] - // See also: self-loops - graph.DeleteEdgesTo(u, v); } } @@ -407,9 +406,9 @@ void PostProcess(ContractorGraph &graph, const NodeID v, ContractorNodeData &nod * * - Algo 2: Insert E into Remaining graph * - * Alas edge insertion is not thread-safe even for "independent" nodes (but edge erasure - * curiously is!). If the graph ever gets fixed to be thread-safe, this function can use - * parallel execution too. + * This function is not thread-safe because edge insertion is not thread-safe even for + * "independent" nodes (but edge erasure curiously is!). If the graph ever gets fixed to + * be thread-safe, this function can use parallel execution too. * * @param graph * @param inserted_edges @@ -420,25 +419,6 @@ void InsertEdges(ContractorGraph &graph, { for (const ContractorEdge &edge : inserted_edges) { - const EdgeID current_edge_ID = graph.FindEdge(edge.source, edge.target); - if (current_edge_ID != SPECIAL_EDGEID) - { - // found edge in graph ... - auto ¤t_data = graph.GetEdgeData(current_edge_ID); - if (edge.data.forward == current_data.forward && - edge.data.backward == current_data.backward) - { - // ... but found edge has smaller weight, update it. - if (edge.data.weight < current_data.weight || - edge.data.duration < current_data.duration || - edge.data.distance < current_data.distance) - { - current_data = edge.data; - } - // ... so don't insert a duplicate. - continue; - } - } graph.InsertEdge(edge.source, edge.target, edge.data); } } @@ -594,7 +574,8 @@ std::vector contractGraph(ContractorGraph &graph, // Algo 2: while Remaining Graph not Empty // - // contract a chunk of nodes until enough nodes are contracted + // contract a chunk of nodes until a sufficient percentage of all nodes is + // contracted while (remaining_nodes.size() > number_of_core_nodes) { /** List of discovered independent nodes */ diff --git a/src/engine/routing_algorithms/routing_base_ch.cpp b/src/engine/routing_algorithms/routing_base_ch.cpp index 051d93f5aec..b3e790c8188 100644 --- a/src/engine/routing_algorithms/routing_base_ch.cpp +++ b/src/engine/routing_algorithms/routing_base_ch.cpp @@ -145,10 +145,10 @@ void search(SearchEngineData & /*engine_working_data*/, // Was a paths over one of the forward/reverse nodes not found? BOOST_ASSERT_MSG((SPECIAL_NODEID != middle && INVALID_EDGE_WEIGHT != weight), "no path found"); - // make sure to correctly unpack loops + // We have to detect and report self-loops this way because we cannot insert the + // same node twice into the heap with differing weights. if (weight != forward_heap.GetKey(middle) + reverse_heap.GetKey(middle)) { - // self loop makes up the full path packed_leg.push_back(middle); packed_leg.push_back(middle); } @@ -156,6 +156,8 @@ void search(SearchEngineData & /*engine_working_data*/, { retrievePackedPathFromHeap(forward_heap, reverse_heap, middle, packed_leg); } + +#ifndef NDEBUG { auto log = util::Log(logDEBUG); log << " and packed_leg of size " << packed_leg.size() << " with nodes"; @@ -164,6 +166,7 @@ void search(SearchEngineData & /*engine_working_data*/, log << " " << node; } } +#endif } // Requires the heaps for be empty diff --git a/src/tools/routed.cpp b/src/tools/routed.cpp index ffcf19f517c..a3cf4dba054 100644 --- a/src/tools/routed.cpp +++ b/src/tools/routed.cpp @@ -427,8 +427,10 @@ catch (const std::bad_alloc &e) util::Log(logWARNING) << "Please provide more memory or consider using a larger swapfile"; return EXIT_FAILURE; } +#ifdef _WIN32 catch (const std::exception &e) { util::Log(logERROR) << "[exception] " << e.what(); return EXIT_FAILURE; } +#endif