diff --git a/features/bicycle/distance.feature b/features/bicycle/distance.feature index 3061bfbb87..d80345b3db 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 75dedc9571..9db2243ba1 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 2ef3b9a8c3..80598da1e6 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/route.js b/features/support/route.js index cc8aa2dc34..87cc109ad9 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 71293a0175..4a3f5177a1 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); diff --git a/features/testbot/self_loop.feature b/features/testbot/self_loop.feature new file mode 100644 index 0000000000..5743dd7022 --- /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_graph.hpp b/include/contractor/contractor_graph.hpp index e88a5185c7..130638be80 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 d3212fe6e5..3bbe605391 100644 --- a/include/contractor/contractor_heap.hpp +++ b/include/contractor/contractor_heap.hpp @@ -7,20 +7,9 @@ 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>; +// Data: if true signals that node is a target +using ContractorHeap = + util::QueryHeap>; } // namespace osrm::contractor diff --git a/include/contractor/contractor_search.hpp b/include/contractor/contractor_search.hpp index 6911a44bb2..b474da38cd 100644 --- a/include/contractor/contractor_search.hpp +++ b/include/contractor/contractor_search.hpp @@ -13,7 +13,8 @@ namespace osrm::contractor 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, diff --git a/include/contractor/graph_contractor.hpp b/include/contractor/graph_contractor.hpp index 9f3a261101..4479f4fdf7 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, + std::vector node_is_contractible, 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, + std::vector node_is_contractible, 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_contractible), core_factor); } } // namespace osrm::contractor diff --git a/include/contractor/graph_contractor_adaptors.hpp b/include/contractor/graph_contractor_adaptors.hpp index 6817da026f..d4dafafea8 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_steps.hpp b/include/engine/guidance/assemble_steps.hpp index 21dad8eeab..53030e9b30 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,11 @@ 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()) { step_name_id = @@ -272,6 +278,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 f1063b0899..2228aab187 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 @@ -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 ad7dbf8245..7740f072a6 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 3b86bf722e..643b2f27e1 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 f7bf71049d..3cea07e18f 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 @@ -333,6 +332,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 9a8aeec10d..9e5c0a6720 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 62494a4461..1a4c8dca50 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 0000000000..1ea93a4946 --- /dev/null +++ b/scripts/debug/dump_hsgr.py @@ -0,0 +1,283 @@ +""" +Contractor 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/scripts/routed_benchmark.py b/scripts/routed_benchmark.py index c9fff8dcf3..c6b0970c85 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 5df261baf0..4251915296 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; @@ -82,11 +63,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 620cd2afda..4ac8774d3b 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 b27e2983fe..baf905a669 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,100 @@ #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 + +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 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. + +See: Algorithm 2 in Chapter 4.3 of [Vetter2009] + +A note about self-loops + +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 + +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 +115,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 +129,368 @@ 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; }; 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); // true: node is a target + max_weight = std::max(max_weight, s2n_data.weight + n2t_data.weight); ++number_of_targets; } } - if (RUNSIMULATION) + // 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 SIMULATION_SEARCH_SPACE_SIZE = 1000; - search(heap, - graph, - contractable, - number_of_targets, - SIMULATION_SEARCH_SPACE_SIZE, - max_weight, - node); - } - else - { - 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 + false); // 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; - } - if (inserted_edges[other].target != inserted_edges[i].target) - { - continue; +#endif } - 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()); - - for (const auto i : util::irange(0, neighbours.size())) - { - graph.DeleteEdgesTo(neighbours[i], node); + 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); } } -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 + * + * 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 + */ + +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) - { - 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]; - - std::vector &neighbours = data->neighbours; - neighbours.clear(); + const float priority = priorities[v]; + BOOST_ASSERT(priority >= 0); - 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 +498,196 @@ 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 a sufficient percentage of all nodes is + // 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 +696,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 +721,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 +755,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 c4bf2f84c9..b3e790c818 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) { @@ -141,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); } @@ -152,6 +156,17 @@ 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"; + for (auto node : packed_leg) + { + log << " " << node; + } + } +#endif } // Requires the heaps for be empty diff --git a/unit_tests/contractor/graph_contractor.cpp b/unit_tests/contractor/graph_contractor.cpp index 2999df11fc..af74a6fbf7 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) @@ -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) @@ -149,20 +154,20 @@ 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) 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) diff --git a/unit_tests/util/query_heap.cpp b/unit_tests/util/query_heap.cpp index 5df633e1c3..4045abd50b 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) {