#ifndef OSRM_ENGINE_ROUTING_BASE_MLD_HPP #define OSRM_ENGINE_ROUTING_BASE_MLD_HPP #include "engine/algorithm.hpp" #include "engine/datafacade.hpp" #include "engine/routing_algorithms/routing_base.hpp" #include "engine/search_engine_data.hpp" #include "util/typedefs.hpp" #include #include #include #include #include #include #include namespace osrm::engine::routing_algorithms::mld { namespace { // Unrestricted search (Args is const PhantomNodes &): // * use partition.GetQueryLevel to find the node query level based on source and target phantoms // * allow to traverse all cells template inline LevelID getNodeQueryLevel(const MultiLevelPartition &partition, NodeID node, const PhantomNode &source, const PhantomNode &target) { auto level = [&partition, node](const SegmentID &source, const SegmentID &target) { if (source.enabled && target.enabled) return partition.GetQueryLevel(source.id, target.id, node); return INVALID_LEVEL_ID; }; return std::min(std::min(level(source.forward_segment_id, target.forward_segment_id), level(source.forward_segment_id, target.reverse_segment_id)), std::min(level(source.reverse_segment_id, target.forward_segment_id), level(source.reverse_segment_id, target.reverse_segment_id))); } template inline LevelID getNodeQueryLevel(const MultiLevelPartition &partition, NodeID node, const PhantomEndpoints &endpoints) { return getNodeQueryLevel(partition, node, endpoints.source_phantom, endpoints.target_phantom); } template inline LevelID getNodeQueryLevel(const MultiLevelPartition &partition, NodeID node, const PhantomCandidatesToTarget &endpoint_candidates) { auto min_level = std::accumulate( endpoint_candidates.source_phantoms.begin(), endpoint_candidates.source_phantoms.end(), INVALID_LEVEL_ID, [&](LevelID current_level, const PhantomNode &source) { return std::min( current_level, getNodeQueryLevel(partition, node, source, endpoint_candidates.target_phantom)); }); return min_level; } template inline LevelID getNodeQueryLevel(const MultiLevelPartition &partition, NodeID node, const PhantomEndpointCandidates &endpoint_candidates) { auto min_level = std::accumulate( endpoint_candidates.source_phantoms.begin(), endpoint_candidates.source_phantoms.end(), INVALID_LEVEL_ID, [&](LevelID level_1, const PhantomNode &source) { return std::min( level_1, std::accumulate(endpoint_candidates.target_phantoms.begin(), endpoint_candidates.target_phantoms.end(), level_1, [&](LevelID level_2, const PhantomNode &target) { return std::min( level_2, getNodeQueryLevel(partition, node, source, target)); })); }); return min_level; } template inline bool checkParentCellRestriction(CellID, const PhantomCandidateT &) { return true; } // Restricted search (Args is LevelID, CellID): // * use the fixed level for queries // * check if the node cell is the same as the specified parent template inline LevelID getNodeQueryLevel(const MultiLevelPartition &, NodeID, LevelID level, CellID) { return level; } inline bool checkParentCellRestriction(CellID cell, LevelID, CellID parent) { return cell == parent; } // Unrestricted search with a single phantom node (Args is const PhantomNode &): // * use partition.GetQueryLevel to find the node query level // * allow to traverse all cells template inline LevelID getNodeQueryLevel(const MultiLevelPartition &partition, const NodeID node, const PhantomNodeCandidates &candidates) { auto highest_different_level = [&partition, node](const SegmentID &segment) { return segment.enabled ? partition.GetHighestDifferentLevel(segment.id, node) : INVALID_LEVEL_ID; }; auto node_level = std::accumulate(candidates.begin(), candidates.end(), INVALID_LEVEL_ID, [&](LevelID current_level, const PhantomNode &phantom_node) { auto highest_level = std::min(highest_different_level(phantom_node.forward_segment_id), highest_different_level(phantom_node.reverse_segment_id)); return std::min(current_level, highest_level); }); return node_level; } // Unrestricted search with a single phantom node and a vector of phantom nodes: // * use partition.GetQueryLevel to find the node query level // * allow to traverse all cells template inline LevelID getNodeQueryLevel(const MultiLevelPartition &partition, NodeID node, const std::vector &candidates_list, const std::size_t phantom_index, const std::vector &phantom_indices) { // Get minimum level over all phantoms of the highest different level with respect to node // This is equivalent to min_{∀ source, target} partition.GetQueryLevel(source, node, target) auto init = getNodeQueryLevel(partition, node, candidates_list[phantom_index]); auto result = std::accumulate( phantom_indices.begin(), phantom_indices.end(), init, [&](LevelID level, size_t index) { return std::min(level, getNodeQueryLevel(partition, node, candidates_list[index])); }); return result; } } // namespace // Heaps only record for each node its predecessor ("parent") on the shortest path. // For re-constructing the actual path we need to trace back all parent "pointers". // In contrast to the CH code MLD needs to know the edges (with clique arc property). using PackedEdge = std::tuple; using PackedPath = std::vector; template inline void retrievePackedPathFromSingleManyToManyHeap( const SearchEngineData::ManyToManyQueryHeap &heap, const NodeID middle, OutIter out) { NodeID current = middle; NodeID parent = heap.GetData(current).parent; while (current != parent) { const auto &data = heap.GetData(current); if (DIRECTION == FORWARD_DIRECTION) { *out = std::make_tuple(parent, current, data.from_clique_arc); ++out; } else if (DIRECTION == REVERSE_DIRECTION) { *out = std::make_tuple(current, parent, data.from_clique_arc); ++out; } current = parent; parent = heap.GetData(parent).parent; } } template inline PackedPath retrievePackedPathFromSingleManyToManyHeap( const SearchEngineData::ManyToManyQueryHeap &heap, const NodeID middle) { PackedPath packed_path; retrievePackedPathFromSingleManyToManyHeap( heap, middle, std::back_inserter(packed_path)); return packed_path; } template inline void retrievePackedPathFromSingleHeap(const SearchEngineData::QueryHeap &heap, const NodeID middle, OutIter out) { NodeID current = middle; NodeID parent = heap.GetData(current).parent; while (current != parent) { const auto &data = heap.GetData(current); if (DIRECTION == FORWARD_DIRECTION) { *out = std::make_tuple(parent, current, data.from_clique_arc); ++out; } else if (DIRECTION == REVERSE_DIRECTION) { *out = std::make_tuple(current, parent, data.from_clique_arc); ++out; } current = parent; parent = heap.GetData(parent).parent; } } template inline PackedPath retrievePackedPathFromSingleHeap(const SearchEngineData::QueryHeap &heap, const NodeID middle) { PackedPath packed_path; retrievePackedPathFromSingleHeap(heap, middle, std::back_inserter(packed_path)); return packed_path; } // Trace path from middle to start in the forward search space (in reverse) // and from middle to end in the reverse search space. Middle connects paths. inline PackedPath retrievePackedPathFromHeap(const SearchEngineData::QueryHeap &forward_heap, const SearchEngineData::QueryHeap &reverse_heap, const NodeID middle) { // Retrieve start -> middle. Is in reverse order since tracing back starts from middle. auto packed_path = retrievePackedPathFromSingleHeap(forward_heap, middle); std::reverse(begin(packed_path), end(packed_path)); // Retrieve middle -> end. Is already in correct order, tracing starts from middle. auto into = std::back_inserter(packed_path); retrievePackedPathFromSingleHeap(reverse_heap, middle, into); return packed_path; } template void insertOrUpdate(Heap &heap, const NodeID node, const EdgeWeight weight, const typename Heap::DataType &data) { const auto heapNode = heap.GetHeapNodeIfWasInserted(node); if (!heapNode) { heap.Insert(node, weight, data); } else if (weight < heapNode->weight) { heapNode->data = data; heapNode->weight = weight; heap.DecreaseKey(*heapNode); } } template void relaxOutgoingEdges(const DataFacade &facade, Heap &forward_heap, const typename Heap::HeapNode &heapNode, const Args &...args) { const auto &partition = facade.GetMultiLevelPartition(); const auto &cells = facade.GetCellStorage(); const auto &metric = facade.GetCellMetric(); const auto level = getNodeQueryLevel(partition, heapNode.node, args...); static constexpr auto IS_MAP_MATCHING = std::is_same_v::MapMatchingQueryHeap, Heap>; if (level >= 1 && !heapNode.data.from_clique_arc) { if constexpr (DIRECTION == FORWARD_DIRECTION) { // Shortcuts in forward direction const auto &cell = cells.GetCell(metric, level, partition.GetCell(level, heapNode.node)); auto destination = cell.GetDestinationNodes().begin(); auto distance = [&cell, node = heapNode.node ]() -> auto { if constexpr (IS_MAP_MATCHING) { return cell.GetOutDistance(node).begin(); } else { boost::ignore_unused(cell, node); return 0; } } (); for (auto shortcut_weight : cell.GetOutWeight(heapNode.node)) { BOOST_ASSERT(destination != cell.GetDestinationNodes().end()); const NodeID to = *destination; if (shortcut_weight != INVALID_EDGE_WEIGHT && heapNode.node != to) { const EdgeWeight to_weight = heapNode.weight + shortcut_weight; BOOST_ASSERT(to_weight >= heapNode.weight); if constexpr (IS_MAP_MATCHING) { const EdgeDistance to_distance = heapNode.data.distance + *distance; insertOrUpdate( forward_heap, to, to_weight, {heapNode.node, true, to_distance}); } else { insertOrUpdate(forward_heap, to, to_weight, {heapNode.node, true}); } } ++destination; if constexpr (IS_MAP_MATCHING) { ++distance; } } } else { // Shortcuts in backward direction const auto &cell = cells.GetCell(metric, level, partition.GetCell(level, heapNode.node)); auto source = cell.GetSourceNodes().begin(); auto distance = [&cell, node = heapNode.node ]() -> auto { if constexpr (IS_MAP_MATCHING) { return cell.GetInDistance(node).begin(); } else { boost::ignore_unused(cell, node); return 0; } } (); for (auto shortcut_weight : cell.GetInWeight(heapNode.node)) { BOOST_ASSERT(source != cell.GetSourceNodes().end()); const NodeID to = *source; if (shortcut_weight != INVALID_EDGE_WEIGHT && heapNode.node != to) { const EdgeWeight to_weight = heapNode.weight + shortcut_weight; BOOST_ASSERT(to_weight >= heapNode.weight); if constexpr (IS_MAP_MATCHING) { const EdgeDistance to_distance = heapNode.data.distance + *distance; insertOrUpdate( forward_heap, to, to_weight, {heapNode.node, true, to_distance}); } else { insertOrUpdate(forward_heap, to, to_weight, {heapNode.node, true}); } } ++source; if constexpr (IS_MAP_MATCHING) { ++distance; } } } } // Boundary edges for (const auto edge : facade.GetBorderEdgeRange(level, heapNode.node)) { const auto &edge_data = facade.GetEdgeData(edge); if ((DIRECTION == FORWARD_DIRECTION) ? facade.IsForwardEdge(edge) : facade.IsBackwardEdge(edge)) { const NodeID to = facade.GetTarget(edge); if (!facade.ExcludeNode(to) && checkParentCellRestriction(partition.GetCell(level + 1, to), args...)) { const auto node_weight = facade.GetNodeWeight(DIRECTION == FORWARD_DIRECTION ? heapNode.node : to); const auto turn_penalty = facade.GetWeightPenaltyForEdgeID(edge_data.turn_id); // TODO: BOOST_ASSERT(edge_data.weight == node_weight + turn_penalty); const EdgeWeight to_weight = heapNode.weight + node_weight + alias_cast(turn_penalty); if constexpr (IS_MAP_MATCHING) { const auto node_distance = facade.GetNodeDistance(DIRECTION == FORWARD_DIRECTION ? heapNode.node : to); const EdgeDistance to_distance = heapNode.data.distance + node_distance; insertOrUpdate( forward_heap, to, to_weight, {heapNode.node, false, to_distance}); } else { insertOrUpdate(forward_heap, to, to_weight, {heapNode.node, false}); } } } } } template void routingStep(const DataFacade &facade, Heap &forward_heap, Heap &reverse_heap, NodeID &middle_node, EdgeWeight &path_upper_bound, const std::vector &force_step_nodes, const Args &...args) { const auto heapNode = forward_heap.DeleteMinGetHeapNode(); const auto weight = heapNode.weight; BOOST_ASSERT(!facade.ExcludeNode(heapNode.node)); // Upper bound for the path source -> target with // weight(source -> node) = weight weight(to -> target) ≤ reverse_weight // is weight + reverse_weight // More tighter upper bound requires additional condition reverse_heap.WasRemoved(to) // with weight(to -> target) = reverse_weight and all weights ≥ 0 const auto reverseHeapNode = reverse_heap.GetHeapNodeIfWasInserted(heapNode.node); if (reverseHeapNode) { auto reverse_weight = reverseHeapNode->weight; auto path_weight = weight + reverse_weight; if (!shouldForceStep(force_step_nodes, heapNode, *reverseHeapNode) && (path_weight >= EdgeWeight{0}) && (path_weight < path_upper_bound)) { middle_node = heapNode.node; path_upper_bound = path_weight; } } // Relax outgoing edges from node relaxOutgoingEdges(facade, forward_heap, heapNode, args...); } // With (s, middle, t) we trace back the paths middle -> s and middle -> t. // This gives us a packed path (node ids) from the base graph around s and t, // and overlay node ids otherwise. We then have to unpack the overlay clique // edges by recursively descending unpacking the path down to the base graph. using UnpackedNodes = std::vector; using UnpackedEdges = std::vector; struct UnpackedPath { EdgeWeight weight; UnpackedNodes nodes; UnpackedEdges edges; }; template std::optional> runSearch(const DataFacade &facade, Heap &forward_heap, Heap &reverse_heap, const std::vector &force_step_nodes, EdgeWeight weight_upper_bound, const Args &...args) { if (forward_heap.Empty() || reverse_heap.Empty()) { return {}; } BOOST_ASSERT(!forward_heap.Empty() && forward_heap.MinKey() < INVALID_EDGE_WEIGHT); BOOST_ASSERT(!reverse_heap.Empty() && reverse_heap.MinKey() < INVALID_EDGE_WEIGHT); // run two-Target Dijkstra routing step. NodeID middle = SPECIAL_NODEID; EdgeWeight weight = weight_upper_bound; EdgeWeight forward_heap_min = forward_heap.MinKey(); EdgeWeight reverse_heap_min = reverse_heap.MinKey(); while (forward_heap.Size() + reverse_heap.Size() > 0 && forward_heap_min + reverse_heap_min < weight) { if (!forward_heap.Empty()) { routingStep( facade, forward_heap, reverse_heap, middle, weight, force_step_nodes, args...); if (!forward_heap.Empty()) forward_heap_min = forward_heap.MinKey(); } if (!reverse_heap.Empty()) { routingStep( facade, reverse_heap, forward_heap, middle, weight, force_step_nodes, args...); if (!reverse_heap.Empty()) reverse_heap_min = reverse_heap.MinKey(); } }; // No path found for both target nodes? if (weight >= weight_upper_bound || SPECIAL_NODEID == middle) { return {}; } return {{middle, weight}}; } template UnpackedPath search(SearchEngineData &engine_working_data, const DataFacade &facade, typename SearchEngineData::QueryHeap &forward_heap, typename SearchEngineData::QueryHeap &reverse_heap, const std::vector &force_step_nodes, EdgeWeight weight_upper_bound, const Args &...args) { auto searchResult = runSearch( facade, forward_heap, reverse_heap, force_step_nodes, weight_upper_bound, args...); if (!searchResult) { return {INVALID_EDGE_WEIGHT, std::vector(), std::vector()}; } auto [middle, weight] = *searchResult; const auto &partition = facade.GetMultiLevelPartition(); // Get packed path as edges {from node ID, to node ID, from_clique_arc} auto packed_path = retrievePackedPathFromHeap(forward_heap, reverse_heap, middle); // Beware the edge case when start, middle, end are all the same. // In this case we return a single node, no edges. We also don't unpack. const NodeID source_node = !packed_path.empty() ? std::get<0>(packed_path.front()) : middle; // Unpack path std::vector unpacked_nodes; std::vector unpacked_edges; unpacked_nodes.reserve(packed_path.size()); unpacked_edges.reserve(packed_path.size()); unpacked_nodes.push_back(source_node); for (auto const &packed_edge : packed_path) { auto [source, target, overlay_edge] = packed_edge; if (!overlay_edge) { // a base graph edge unpacked_nodes.push_back(target); unpacked_edges.push_back(facade.FindEdge(source, target)); } else { // an overlay graph edge LevelID level = getNodeQueryLevel(partition, source, args...); CellID parent_cell_id = partition.GetCell(level, source); BOOST_ASSERT(parent_cell_id == partition.GetCell(level, target)); LevelID sublevel = level - 1; // Here heaps can be reused, let's go deeper! forward_heap.Clear(); reverse_heap.Clear(); forward_heap.Insert(source, {0}, {source}); reverse_heap.Insert(target, {0}, {target}); auto unpacked_subpath = search(engine_working_data, facade, forward_heap, reverse_heap, force_step_nodes, INVALID_EDGE_WEIGHT, sublevel, parent_cell_id); BOOST_ASSERT(!unpacked_subpath.edges.empty()); BOOST_ASSERT(unpacked_subpath.nodes.size() > 1); BOOST_ASSERT(unpacked_subpath.nodes.front() == source); BOOST_ASSERT(unpacked_subpath.nodes.back() == target); unpacked_nodes.insert(unpacked_nodes.end(), std::next(unpacked_subpath.nodes.begin()), unpacked_subpath.nodes.end()); unpacked_edges.insert( unpacked_edges.end(), unpacked_subpath.edges.begin(), unpacked_subpath.edges.end()); } } return {weight, std::move(unpacked_nodes), std::move(unpacked_edges)}; } template EdgeDistance searchDistance(SearchEngineData &, const DataFacade &facade, typename SearchEngineData::MapMatchingQueryHeap &forward_heap, typename SearchEngineData::MapMatchingQueryHeap &reverse_heap, const std::vector &force_step_nodes, EdgeWeight weight_upper_bound, const Args &...args) { auto searchResult = runSearch( facade, forward_heap, reverse_heap, force_step_nodes, weight_upper_bound, args...); if (!searchResult) { return INVALID_EDGE_DISTANCE; } auto [middle, _] = *searchResult; auto distance = forward_heap.GetData(middle).distance + reverse_heap.GetData(middle).distance; return distance; } // Alias to be compatible with the CH-based search template inline void search(SearchEngineData &engine_working_data, const DataFacade &facade, typename SearchEngineData::QueryHeap &forward_heap, typename SearchEngineData::QueryHeap &reverse_heap, EdgeWeight &weight, std::vector &unpacked_nodes, const std::vector &force_step_nodes, const PhantomEndpointT &endpoints, const EdgeWeight weight_upper_bound = INVALID_EDGE_WEIGHT) { // TODO: change search calling interface to use unpacked_edges result auto unpacked_path = search(engine_working_data, facade, forward_heap, reverse_heap, force_step_nodes, weight_upper_bound, endpoints); weight = unpacked_path.weight; unpacked_nodes = std::move(unpacked_path.nodes); } // TODO: refactor CH-related stub to use unpacked_edges template void unpackPath(const FacadeT &facade, RandomIter packed_path_begin, RandomIter packed_path_end, const PhantomEndpoints &route_endpoints, std::vector &unpacked_path) { const auto nodes_number = std::distance(packed_path_begin, packed_path_end); BOOST_ASSERT(nodes_number > 0); std::vector unpacked_nodes; std::vector unpacked_edges; unpacked_nodes.reserve(nodes_number); unpacked_edges.reserve(nodes_number); unpacked_nodes.push_back(*packed_path_begin); if (nodes_number > 1) { util::for_each_pair( packed_path_begin, packed_path_end, [&facade, &unpacked_nodes, &unpacked_edges](const auto from, const auto to) { unpacked_nodes.push_back(to); unpacked_edges.push_back(facade.FindEdge(from, to)); }); } annotatePath(facade, route_endpoints, unpacked_nodes, unpacked_edges, unpacked_path); } template double getNetworkDistance(SearchEngineData &engine_working_data, const DataFacade &facade, typename SearchEngineData::MapMatchingQueryHeap &forward_heap, typename SearchEngineData::MapMatchingQueryHeap &reverse_heap, const PhantomNode &source_phantom, const PhantomNode &target_phantom, EdgeWeight weight_upper_bound = INVALID_EDGE_WEIGHT) { forward_heap.Clear(); reverse_heap.Clear(); if (source_phantom.IsValidForwardSource()) { forward_heap.Insert(source_phantom.forward_segment_id.id, EdgeWeight{0} - source_phantom.GetForwardWeightPlusOffset(), {source_phantom.forward_segment_id.id, false, EdgeDistance{0} - source_phantom.GetForwardDistance()}); } if (source_phantom.IsValidReverseSource()) { forward_heap.Insert(source_phantom.reverse_segment_id.id, EdgeWeight{0} - source_phantom.GetReverseWeightPlusOffset(), {source_phantom.reverse_segment_id.id, false, EdgeDistance{0} - source_phantom.GetReverseDistance()}); } if (target_phantom.IsValidForwardTarget()) { reverse_heap.Insert( target_phantom.forward_segment_id.id, target_phantom.GetForwardWeightPlusOffset(), {target_phantom.forward_segment_id.id, false, target_phantom.GetForwardDistance()}); } if (target_phantom.IsValidReverseTarget()) { reverse_heap.Insert( target_phantom.reverse_segment_id.id, target_phantom.GetReverseWeightPlusOffset(), {target_phantom.reverse_segment_id.id, false, target_phantom.GetReverseDistance()}); } const PhantomEndpoints endpoints{source_phantom, target_phantom}; auto distance = searchDistance( engine_working_data, facade, forward_heap, reverse_heap, {}, weight_upper_bound, endpoints); if (distance == INVALID_EDGE_DISTANCE) { return std::numeric_limits::max(); } return from_alias(distance); } } // namespace osrm::engine::routing_algorithms::mld #endif // OSRM_ENGINE_ROUTING_BASE_MLD_HPP