The change clarifies the conditions for forcing routing steps and simplifies the codebase to support it. - Makes explicity the search runtime condition for forcing a routing step. Namely, the node is a source of the forward and reverse searches, and it's one of the pre-identified nodes that requires a step to be forced. - Consolidate the two lists of force nodes into one. Not only is there no algorithmic value in separating the nodes by geometric direction, the improvements to via-routes with u-turns mean atleast one of these lists will be empty for any search. - Rename 'force loop' to 'force step'. This moves the code away from the original CH-specific language for checking for self-loops in the case where this condition is met. MLD does not have loops. Additional cucumber tests are added to cover the logic related to negative search weights and forcing routing steps on via-route paths.
850 lines
36 KiB
C++
850 lines
36 KiB
C++
#include "engine/routing_algorithms/alternative_path.hpp"
|
|
#include "engine/routing_algorithms/routing_base_ch.hpp"
|
|
|
|
#include "util/integer_range.hpp"
|
|
|
|
#include <boost/assert.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
namespace osrm::engine::routing_algorithms
|
|
{
|
|
|
|
// Unqualified calls below are from the ch namespace.
|
|
// This alternative implementation works only for ch.
|
|
using namespace ch;
|
|
|
|
namespace
|
|
{
|
|
const double constexpr VIAPATH_ALPHA = 0.25; // alternative is local optimum on 25% sub-paths
|
|
const double constexpr VIAPATH_EPSILON = 0.15; // alternative at most 15% longer
|
|
const double constexpr VIAPATH_GAMMA = 0.75; // alternative shares at most 75% with the shortest.
|
|
|
|
using QueryHeap = SearchEngineData<Algorithm>::QueryHeap;
|
|
using SearchSpaceEdge = std::pair<NodeID, NodeID>;
|
|
|
|
struct RankedCandidateNode
|
|
{
|
|
RankedCandidateNode(const NodeID node, const EdgeWeight weight, const EdgeWeight sharing)
|
|
: node(node), weight(weight), sharing(sharing)
|
|
{
|
|
}
|
|
|
|
NodeID node;
|
|
EdgeWeight weight;
|
|
EdgeWeight sharing;
|
|
|
|
bool operator<(const RankedCandidateNode &other) const
|
|
{
|
|
return (EdgeWeight{2} * weight + sharing) < (EdgeWeight{2} * other.weight + other.sharing);
|
|
}
|
|
};
|
|
|
|
// todo: reorder parameters
|
|
template <bool DIRECTION>
|
|
void alternativeRoutingStep(const DataFacade<Algorithm> &facade,
|
|
QueryHeap &heap1,
|
|
QueryHeap &heap2,
|
|
NodeID *middle_node,
|
|
EdgeWeight *upper_bound_to_shortest_path_weight,
|
|
std::vector<NodeID> &search_space_intersection,
|
|
std::vector<SearchSpaceEdge> &search_space,
|
|
const EdgeWeight min_edge_offset)
|
|
{
|
|
QueryHeap &forward_heap = DIRECTION == FORWARD_DIRECTION ? heap1 : heap2;
|
|
QueryHeap &reverse_heap = DIRECTION == FORWARD_DIRECTION ? heap2 : heap1;
|
|
|
|
// Take a copy (no ref &) of the extracted node because otherwise could be modified later if
|
|
// toHeapNode is the same
|
|
const auto heapNode = forward_heap.DeleteMinGetHeapNode();
|
|
|
|
const auto scaled_weight = to_alias<EdgeWeight>(
|
|
from_alias<double>(heapNode.weight + min_edge_offset) / (1. + VIAPATH_EPSILON));
|
|
if ((INVALID_EDGE_WEIGHT != *upper_bound_to_shortest_path_weight) &&
|
|
(scaled_weight > *upper_bound_to_shortest_path_weight))
|
|
{
|
|
forward_heap.DeleteAll();
|
|
return;
|
|
}
|
|
|
|
search_space.emplace_back(heapNode.data.parent, heapNode.node);
|
|
|
|
const auto reverseHeapNode = reverse_heap.GetHeapNodeIfWasInserted(heapNode.node);
|
|
if (reverseHeapNode)
|
|
{
|
|
search_space_intersection.emplace_back(heapNode.node);
|
|
const EdgeWeight new_weight = reverseHeapNode->weight + heapNode.weight;
|
|
if (new_weight < *upper_bound_to_shortest_path_weight)
|
|
{
|
|
if (new_weight >= EdgeWeight{0})
|
|
{
|
|
*middle_node = heapNode.node;
|
|
*upper_bound_to_shortest_path_weight = new_weight;
|
|
}
|
|
else
|
|
{
|
|
// check whether there is a loop present at the node
|
|
const auto loop_weight =
|
|
std::get<0>(getLoopMetric<EdgeWeight>(facade, heapNode.node));
|
|
const EdgeWeight new_weight_with_loop = new_weight + loop_weight;
|
|
if (loop_weight != INVALID_EDGE_WEIGHT &&
|
|
new_weight_with_loop <= *upper_bound_to_shortest_path_weight)
|
|
{
|
|
*middle_node = heapNode.node;
|
|
*upper_bound_to_shortest_path_weight = loop_weight;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto edge : facade.GetAdjacentEdgeRange(heapNode.node))
|
|
{
|
|
const auto &data = facade.GetEdgeData(edge);
|
|
if (DIRECTION == FORWARD_DIRECTION ? data.forward : data.backward)
|
|
{
|
|
const NodeID to = facade.GetTarget(edge);
|
|
const EdgeWeight edge_weight = data.weight;
|
|
|
|
BOOST_ASSERT(edge_weight > EdgeWeight{0});
|
|
const EdgeWeight to_weight = heapNode.weight + edge_weight;
|
|
|
|
const auto toHeapNode = forward_heap.GetHeapNodeIfWasInserted(to);
|
|
// New Node discovered -> Add to Heap + Node Info Storage
|
|
if (!toHeapNode)
|
|
{
|
|
forward_heap.Insert(to, to_weight, heapNode.node);
|
|
}
|
|
// Found a shorter Path -> Update weight
|
|
else if (to_weight < toHeapNode->weight)
|
|
{
|
|
// new parent
|
|
toHeapNode->data.parent = heapNode.node;
|
|
// decreased weight
|
|
toHeapNode->weight = to_weight;
|
|
forward_heap.DecreaseKey(*toHeapNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void retrievePackedAlternatePath(const QueryHeap &forward_heap1,
|
|
const QueryHeap &reverse_heap1,
|
|
const QueryHeap &forward_heap2,
|
|
const QueryHeap &reverse_heap2,
|
|
const NodeID s_v_middle,
|
|
const NodeID v_t_middle,
|
|
std::vector<NodeID> &packed_path)
|
|
{
|
|
// fetch packed path [s,v)
|
|
std::vector<NodeID> packed_v_t_path;
|
|
retrievePackedPathFromHeap(forward_heap1, reverse_heap2, s_v_middle, packed_path);
|
|
packed_path.pop_back(); // remove middle node. It's in both half-paths
|
|
|
|
// fetch patched path [v,t]
|
|
retrievePackedPathFromHeap(forward_heap2, reverse_heap1, v_t_middle, packed_v_t_path);
|
|
|
|
packed_path.insert(packed_path.end(), packed_v_t_path.begin(), packed_v_t_path.end());
|
|
}
|
|
|
|
// TODO: reorder parameters
|
|
// compute and unpack <s,..,v> and <v,..,t> by exploring search spaces
|
|
// from v and intersecting against queues. only half-searches have to be
|
|
// done at this stage
|
|
void computeWeightAndSharingOfViaPath(SearchEngineData<Algorithm> &engine_working_data,
|
|
const DataFacade<Algorithm> &facade,
|
|
const NodeID via_node,
|
|
EdgeWeight *real_weight_of_via_path,
|
|
EdgeWeight *sharing_of_via_path,
|
|
const std::vector<NodeID> &packed_shortest_path,
|
|
const EdgeWeight min_edge_offset)
|
|
{
|
|
engine_working_data.InitializeOrClearSecondThreadLocalStorage(facade.GetNumberOfNodes());
|
|
|
|
auto &existing_forward_heap = *engine_working_data.forward_heap_1;
|
|
auto &existing_reverse_heap = *engine_working_data.reverse_heap_1;
|
|
auto &new_forward_heap = *engine_working_data.forward_heap_2;
|
|
auto &new_reverse_heap = *engine_working_data.reverse_heap_2;
|
|
|
|
std::vector<NodeID> packed_s_v_path;
|
|
std::vector<NodeID> packed_v_t_path;
|
|
|
|
std::vector<NodeID> partially_unpacked_shortest_path;
|
|
std::vector<NodeID> partially_unpacked_via_path;
|
|
|
|
NodeID s_v_middle = SPECIAL_NODEID;
|
|
EdgeWeight upper_bound_s_v_path_weight = INVALID_EDGE_WEIGHT;
|
|
new_reverse_heap.Insert(via_node, {0}, via_node);
|
|
// compute path <s,..,v> by reusing forward search from s
|
|
while (!new_reverse_heap.Empty())
|
|
{
|
|
routingStep<REVERSE_DIRECTION>(facade,
|
|
new_reverse_heap,
|
|
existing_forward_heap,
|
|
s_v_middle,
|
|
upper_bound_s_v_path_weight,
|
|
min_edge_offset,
|
|
{});
|
|
}
|
|
// compute path <v,..,t> by reusing backward search from node t
|
|
NodeID v_t_middle = SPECIAL_NODEID;
|
|
EdgeWeight upper_bound_of_v_t_path_weight = INVALID_EDGE_WEIGHT;
|
|
new_forward_heap.Insert(via_node, {0}, via_node);
|
|
while (!new_forward_heap.Empty())
|
|
{
|
|
routingStep<FORWARD_DIRECTION>(facade,
|
|
new_forward_heap,
|
|
existing_reverse_heap,
|
|
v_t_middle,
|
|
upper_bound_of_v_t_path_weight,
|
|
min_edge_offset,
|
|
{});
|
|
}
|
|
*real_weight_of_via_path = upper_bound_s_v_path_weight + upper_bound_of_v_t_path_weight;
|
|
|
|
if (SPECIAL_NODEID == s_v_middle || SPECIAL_NODEID == v_t_middle)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// retrieve packed paths
|
|
retrievePackedPathFromHeap(
|
|
existing_forward_heap, new_reverse_heap, s_v_middle, packed_s_v_path);
|
|
retrievePackedPathFromHeap(
|
|
new_forward_heap, existing_reverse_heap, v_t_middle, packed_v_t_path);
|
|
|
|
// partial unpacking, compute sharing
|
|
// First partially unpack s-->v until paths deviate, note length of common path.
|
|
const auto s_v_min_path_size =
|
|
std::min(packed_s_v_path.size(), packed_shortest_path.size()) - 1;
|
|
for (const auto current_node : util::irange<std::size_t>(0UL, s_v_min_path_size))
|
|
{
|
|
if (packed_s_v_path[current_node] == packed_shortest_path[current_node] &&
|
|
packed_s_v_path[current_node + 1] == packed_shortest_path[current_node + 1])
|
|
{
|
|
EdgeID edgeID = facade.FindEdgeInEitherDirection(packed_s_v_path[current_node],
|
|
packed_s_v_path[current_node + 1]);
|
|
*sharing_of_via_path += facade.GetEdgeData(edgeID).weight;
|
|
}
|
|
else
|
|
{
|
|
if (packed_s_v_path[current_node] == packed_shortest_path[current_node])
|
|
{
|
|
unpackEdge(facade,
|
|
packed_s_v_path[current_node],
|
|
packed_s_v_path[current_node + 1],
|
|
partially_unpacked_via_path);
|
|
unpackEdge(facade,
|
|
packed_shortest_path[current_node],
|
|
packed_shortest_path[current_node + 1],
|
|
partially_unpacked_shortest_path);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// traverse partially unpacked edge and note common prefix
|
|
const int64_t packed_path_length =
|
|
static_cast<int64_t>(
|
|
std::min(partially_unpacked_via_path.size(), partially_unpacked_shortest_path.size())) -
|
|
1;
|
|
for (int64_t current_node = 0; (current_node < packed_path_length) &&
|
|
(partially_unpacked_via_path[current_node] ==
|
|
partially_unpacked_shortest_path[current_node] &&
|
|
partially_unpacked_via_path[current_node + 1] ==
|
|
partially_unpacked_shortest_path[current_node + 1]);
|
|
++current_node)
|
|
{
|
|
EdgeID selected_edge =
|
|
facade.FindEdgeInEitherDirection(partially_unpacked_via_path[current_node],
|
|
partially_unpacked_via_path[current_node + 1]);
|
|
*sharing_of_via_path += facade.GetEdgeData(selected_edge).weight;
|
|
}
|
|
|
|
// Second, partially unpack v-->t in reverse order until paths deviate and note lengths
|
|
int64_t via_path_index = static_cast<int64_t>(packed_v_t_path.size()) - 1;
|
|
int64_t shortest_path_index = static_cast<int64_t>(packed_shortest_path.size()) - 1;
|
|
for (; via_path_index > 0 && shortest_path_index > 0; --via_path_index, --shortest_path_index)
|
|
{
|
|
if (packed_v_t_path[via_path_index - 1] == packed_shortest_path[shortest_path_index - 1] &&
|
|
packed_v_t_path[via_path_index] == packed_shortest_path[shortest_path_index])
|
|
{
|
|
EdgeID edgeID = facade.FindEdgeInEitherDirection(packed_v_t_path[via_path_index - 1],
|
|
packed_v_t_path[via_path_index]);
|
|
*sharing_of_via_path += facade.GetEdgeData(edgeID).weight;
|
|
}
|
|
else
|
|
{
|
|
if (packed_v_t_path[via_path_index] == packed_shortest_path[shortest_path_index])
|
|
{
|
|
unpackEdge(facade,
|
|
packed_v_t_path[via_path_index - 1],
|
|
packed_v_t_path[via_path_index],
|
|
partially_unpacked_via_path);
|
|
unpackEdge(facade,
|
|
packed_shortest_path[shortest_path_index - 1],
|
|
packed_shortest_path[shortest_path_index],
|
|
partially_unpacked_shortest_path);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
via_path_index = static_cast<int64_t>(partially_unpacked_via_path.size()) - 1;
|
|
shortest_path_index = static_cast<int64_t>(partially_unpacked_shortest_path.size()) - 1;
|
|
for (; via_path_index > 0 && shortest_path_index > 0; --via_path_index, --shortest_path_index)
|
|
{
|
|
if (partially_unpacked_via_path[via_path_index - 1] ==
|
|
partially_unpacked_shortest_path[shortest_path_index - 1] &&
|
|
partially_unpacked_via_path[via_path_index] ==
|
|
partially_unpacked_shortest_path[shortest_path_index])
|
|
{
|
|
EdgeID edgeID =
|
|
facade.FindEdgeInEitherDirection(partially_unpacked_via_path[via_path_index - 1],
|
|
partially_unpacked_via_path[via_path_index]);
|
|
*sharing_of_via_path += facade.GetEdgeData(edgeID).weight;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// finished partial unpacking spree! Amount of sharing is stored to appropriate pointer
|
|
// variable
|
|
}
|
|
|
|
// conduct T-Test
|
|
bool viaNodeCandidatePassesTTest(SearchEngineData<Algorithm> &engine_working_data,
|
|
const DataFacade<Algorithm> &facade,
|
|
QueryHeap &existing_forward_heap,
|
|
QueryHeap &existing_reverse_heap,
|
|
QueryHeap &new_forward_heap,
|
|
QueryHeap &new_reverse_heap,
|
|
const RankedCandidateNode &candidate,
|
|
const EdgeWeight weight_of_shortest_path,
|
|
EdgeWeight *weight_of_via_path,
|
|
NodeID *s_v_middle,
|
|
NodeID *v_t_middle,
|
|
const EdgeWeight min_edge_offset)
|
|
{
|
|
new_forward_heap.Clear();
|
|
new_reverse_heap.Clear();
|
|
std::vector<NodeID> packed_s_v_path;
|
|
std::vector<NodeID> packed_v_t_path;
|
|
|
|
*s_v_middle = SPECIAL_NODEID;
|
|
EdgeWeight upper_bound_s_v_path_weight = INVALID_EDGE_WEIGHT;
|
|
// compute path <s,..,v> by reusing forward search from s
|
|
new_reverse_heap.Insert(candidate.node, {0}, candidate.node);
|
|
while (new_reverse_heap.Size() > 0)
|
|
{
|
|
routingStep<REVERSE_DIRECTION>(facade,
|
|
new_reverse_heap,
|
|
existing_forward_heap,
|
|
*s_v_middle,
|
|
upper_bound_s_v_path_weight,
|
|
min_edge_offset,
|
|
{});
|
|
}
|
|
|
|
if (INVALID_EDGE_WEIGHT == upper_bound_s_v_path_weight)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// compute path <v,..,t> by reusing backward search from t
|
|
*v_t_middle = SPECIAL_NODEID;
|
|
EdgeWeight upper_bound_of_v_t_path_weight = INVALID_EDGE_WEIGHT;
|
|
new_forward_heap.Insert(candidate.node, {0}, candidate.node);
|
|
while (new_forward_heap.Size() > 0)
|
|
{
|
|
routingStep<FORWARD_DIRECTION>(facade,
|
|
new_forward_heap,
|
|
existing_reverse_heap,
|
|
*v_t_middle,
|
|
upper_bound_of_v_t_path_weight,
|
|
min_edge_offset,
|
|
{});
|
|
}
|
|
|
|
if (INVALID_EDGE_WEIGHT == upper_bound_of_v_t_path_weight)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
*weight_of_via_path = upper_bound_s_v_path_weight + upper_bound_of_v_t_path_weight;
|
|
|
|
// retrieve packed paths
|
|
retrievePackedPathFromHeap(
|
|
existing_forward_heap, new_reverse_heap, *s_v_middle, packed_s_v_path);
|
|
|
|
retrievePackedPathFromHeap(
|
|
new_forward_heap, existing_reverse_heap, *v_t_middle, packed_v_t_path);
|
|
|
|
NodeID s_P = *s_v_middle, t_P = *v_t_middle;
|
|
if (SPECIAL_NODEID == s_P)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (SPECIAL_NODEID == t_P)
|
|
{
|
|
return false;
|
|
}
|
|
const EdgeWeight T_threshold =
|
|
to_alias<EdgeWeight>(VIAPATH_ALPHA * from_alias<double>(weight_of_shortest_path));
|
|
EdgeWeight unpacked_until_weight = {0};
|
|
|
|
std::stack<SearchSpaceEdge> unpack_stack;
|
|
// Traverse path s-->v
|
|
for (std::size_t i = packed_s_v_path.size() - 1; (i > 0) && unpack_stack.empty(); --i)
|
|
{
|
|
const EdgeID current_edge_id =
|
|
facade.FindEdgeInEitherDirection(packed_s_v_path[i - 1], packed_s_v_path[i]);
|
|
const EdgeWeight weight_of_current_edge = facade.GetEdgeData(current_edge_id).weight;
|
|
if ((weight_of_current_edge + unpacked_until_weight) >= T_threshold)
|
|
{
|
|
unpack_stack.emplace(packed_s_v_path[i - 1], packed_s_v_path[i]);
|
|
}
|
|
else
|
|
{
|
|
unpacked_until_weight += weight_of_current_edge;
|
|
s_P = packed_s_v_path[i - 1];
|
|
}
|
|
}
|
|
|
|
while (!unpack_stack.empty())
|
|
{
|
|
const SearchSpaceEdge via_path_edge = unpack_stack.top();
|
|
unpack_stack.pop();
|
|
EdgeID edge_in_via_path_id =
|
|
facade.FindEdgeInEitherDirection(via_path_edge.first, via_path_edge.second);
|
|
|
|
if (SPECIAL_EDGEID == edge_in_via_path_id)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const auto ¤t_edge_data = facade.GetEdgeData(edge_in_via_path_id);
|
|
const bool current_edge_is_shortcut = current_edge_data.shortcut;
|
|
if (current_edge_is_shortcut)
|
|
{
|
|
const NodeID via_path_middle_node_id = current_edge_data.turn_id;
|
|
const EdgeID second_segment_edge_id =
|
|
facade.FindEdgeInEitherDirection(via_path_middle_node_id, via_path_edge.second);
|
|
const auto second_segment_weight = facade.GetEdgeData(second_segment_edge_id).weight;
|
|
// attention: !unpacking in reverse!
|
|
// Check if second segment is the one to go over treshold? if yes add second segment
|
|
// to stack, else push first segment to stack and add weight of second one.
|
|
if (unpacked_until_weight + second_segment_weight >= T_threshold)
|
|
{
|
|
unpack_stack.emplace(via_path_middle_node_id, via_path_edge.second);
|
|
}
|
|
else
|
|
{
|
|
unpacked_until_weight += second_segment_weight;
|
|
unpack_stack.emplace(via_path_edge.first, via_path_middle_node_id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// edge is not a shortcut, set the start node for T-Test to end of edge.
|
|
unpacked_until_weight += current_edge_data.weight;
|
|
s_P = via_path_edge.first;
|
|
}
|
|
}
|
|
|
|
EdgeWeight t_test_path_weight = unpacked_until_weight;
|
|
unpacked_until_weight = {0};
|
|
// Traverse path s-->v
|
|
BOOST_ASSERT(!packed_v_t_path.empty());
|
|
for (unsigned i = 0, packed_path_length = static_cast<unsigned>(packed_v_t_path.size() - 1);
|
|
(i < packed_path_length) && unpack_stack.empty();
|
|
++i)
|
|
{
|
|
const EdgeID edgeID =
|
|
facade.FindEdgeInEitherDirection(packed_v_t_path[i], packed_v_t_path[i + 1]);
|
|
auto weight_of_current_edge = facade.GetEdgeData(edgeID).weight;
|
|
if (weight_of_current_edge + unpacked_until_weight >= T_threshold)
|
|
{
|
|
unpack_stack.emplace(packed_v_t_path[i], packed_v_t_path[i + 1]);
|
|
}
|
|
else
|
|
{
|
|
unpacked_until_weight += weight_of_current_edge;
|
|
t_P = packed_v_t_path[i + 1];
|
|
}
|
|
}
|
|
|
|
while (!unpack_stack.empty())
|
|
{
|
|
const SearchSpaceEdge via_path_edge = unpack_stack.top();
|
|
unpack_stack.pop();
|
|
EdgeID edge_in_via_path_id =
|
|
facade.FindEdgeInEitherDirection(via_path_edge.first, via_path_edge.second);
|
|
if (SPECIAL_EDGEID == edge_in_via_path_id)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const auto ¤t_edge_data = facade.GetEdgeData(edge_in_via_path_id);
|
|
const bool IsViaEdgeShortCut = current_edge_data.shortcut;
|
|
if (IsViaEdgeShortCut)
|
|
{
|
|
const NodeID middleOfViaPath = current_edge_data.turn_id;
|
|
EdgeID edgeIDOfFirstSegment =
|
|
facade.FindEdgeInEitherDirection(via_path_edge.first, middleOfViaPath);
|
|
auto weightOfFirstSegment = facade.GetEdgeData(edgeIDOfFirstSegment).weight;
|
|
// Check if first segment is the one to go over treshold? if yes first segment to
|
|
// stack, else push second segment to stack and add weight of first one.
|
|
if (unpacked_until_weight + weightOfFirstSegment >= T_threshold)
|
|
{
|
|
unpack_stack.emplace(via_path_edge.first, middleOfViaPath);
|
|
}
|
|
else
|
|
{
|
|
unpacked_until_weight += weightOfFirstSegment;
|
|
unpack_stack.emplace(middleOfViaPath, via_path_edge.second);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// edge is not a shortcut, set the start node for T-Test to end of edge.
|
|
unpacked_until_weight += current_edge_data.weight;
|
|
t_P = via_path_edge.second;
|
|
}
|
|
}
|
|
|
|
t_test_path_weight += unpacked_until_weight;
|
|
// Run actual T-Test query and compare if weight equal.
|
|
engine_working_data.InitializeOrClearThirdThreadLocalStorage(facade.GetNumberOfNodes());
|
|
|
|
QueryHeap &forward_heap3 = *engine_working_data.forward_heap_3;
|
|
QueryHeap &reverse_heap3 = *engine_working_data.reverse_heap_3;
|
|
EdgeWeight upper_bound = INVALID_EDGE_WEIGHT;
|
|
NodeID middle = SPECIAL_NODEID;
|
|
|
|
forward_heap3.Insert(s_P, {0}, s_P);
|
|
reverse_heap3.Insert(t_P, {0}, t_P);
|
|
// exploration from s and t until deletemin/(1+epsilon) > _lengt_oO_sShortest_path
|
|
while ((forward_heap3.Size() + reverse_heap3.Size()) > 0)
|
|
{
|
|
if (!forward_heap3.Empty())
|
|
{
|
|
routingStep<FORWARD_DIRECTION>(
|
|
facade, forward_heap3, reverse_heap3, middle, upper_bound, min_edge_offset, {});
|
|
}
|
|
if (!reverse_heap3.Empty())
|
|
{
|
|
routingStep<REVERSE_DIRECTION>(
|
|
facade, reverse_heap3, forward_heap3, middle, upper_bound, min_edge_offset, {});
|
|
}
|
|
}
|
|
return (upper_bound <= t_test_path_weight);
|
|
}
|
|
} // namespace
|
|
|
|
InternalManyRoutesResult alternativePathSearch(SearchEngineData<Algorithm> &engine_working_data,
|
|
const DataFacade<Algorithm> &facade,
|
|
const PhantomEndpointCandidates &endpoint_candidates,
|
|
unsigned /*number_of_alternatives*/)
|
|
{
|
|
InternalRouteResult primary_route;
|
|
InternalRouteResult secondary_route;
|
|
|
|
std::vector<NodeID> alternative_path;
|
|
std::vector<NodeID> via_node_candidate_list;
|
|
std::vector<SearchSpaceEdge> forward_search_space;
|
|
std::vector<SearchSpaceEdge> reverse_search_space;
|
|
|
|
// Init queues, semi-expensive because access to TSS invokes a sys-call
|
|
engine_working_data.InitializeOrClearFirstThreadLocalStorage(facade.GetNumberOfNodes());
|
|
engine_working_data.InitializeOrClearSecondThreadLocalStorage(facade.GetNumberOfNodes());
|
|
engine_working_data.InitializeOrClearThirdThreadLocalStorage(facade.GetNumberOfNodes());
|
|
|
|
auto &forward_heap1 = *engine_working_data.forward_heap_1;
|
|
auto &reverse_heap1 = *engine_working_data.reverse_heap_1;
|
|
auto &forward_heap2 = *engine_working_data.forward_heap_2;
|
|
auto &reverse_heap2 = *engine_working_data.reverse_heap_2;
|
|
|
|
EdgeWeight upper_bound_to_shortest_path_weight = INVALID_EDGE_WEIGHT;
|
|
NodeID middle_node = SPECIAL_NODEID;
|
|
|
|
insertNodesInHeaps(forward_heap1, reverse_heap1, endpoint_candidates);
|
|
// get offset to account for offsets on phantom nodes on compressed edges
|
|
EdgeWeight min_edge_offset =
|
|
forward_heap1.Empty() ? EdgeWeight{0} : std::min<EdgeWeight>({0}, forward_heap1.MinKey());
|
|
BOOST_ASSERT(min_edge_offset <= EdgeWeight{0});
|
|
// we only every insert negative offsets for nodes in the forward heap
|
|
BOOST_ASSERT(reverse_heap1.Empty() || reverse_heap1.MinKey() >= EdgeWeight{0});
|
|
|
|
// search from s and t till new_min/(1+epsilon) > weight_of_shortest_path
|
|
while (0 < (forward_heap1.Size() + reverse_heap1.Size()))
|
|
{
|
|
if (0 < forward_heap1.Size())
|
|
{
|
|
alternativeRoutingStep<FORWARD_DIRECTION>(facade,
|
|
forward_heap1,
|
|
reverse_heap1,
|
|
&middle_node,
|
|
&upper_bound_to_shortest_path_weight,
|
|
via_node_candidate_list,
|
|
forward_search_space,
|
|
min_edge_offset);
|
|
}
|
|
if (0 < reverse_heap1.Size())
|
|
{
|
|
alternativeRoutingStep<REVERSE_DIRECTION>(facade,
|
|
forward_heap1,
|
|
reverse_heap1,
|
|
&middle_node,
|
|
&upper_bound_to_shortest_path_weight,
|
|
via_node_candidate_list,
|
|
reverse_search_space,
|
|
min_edge_offset);
|
|
}
|
|
}
|
|
|
|
if (INVALID_EDGE_WEIGHT == upper_bound_to_shortest_path_weight)
|
|
{
|
|
return InternalManyRoutesResult{std::move(primary_route)};
|
|
}
|
|
|
|
std::sort(begin(via_node_candidate_list), end(via_node_candidate_list));
|
|
auto unique_end = std::unique(begin(via_node_candidate_list), end(via_node_candidate_list));
|
|
via_node_candidate_list.resize(unique_end - begin(via_node_candidate_list));
|
|
|
|
std::vector<NodeID> packed_forward_path;
|
|
std::vector<NodeID> packed_reverse_path;
|
|
|
|
const bool path_is_a_loop =
|
|
upper_bound_to_shortest_path_weight !=
|
|
forward_heap1.GetKey(middle_node) + reverse_heap1.GetKey(middle_node);
|
|
if (path_is_a_loop)
|
|
{
|
|
// Self Loop
|
|
packed_forward_path.push_back(middle_node);
|
|
packed_forward_path.push_back(middle_node);
|
|
}
|
|
else
|
|
{
|
|
|
|
retrievePackedPathFromSingleHeap(forward_heap1, middle_node, packed_forward_path);
|
|
retrievePackedPathFromSingleHeap(reverse_heap1, middle_node, packed_reverse_path);
|
|
}
|
|
|
|
// this set is is used as an indicator if a node is on the shortest path
|
|
std::unordered_set<NodeID> nodes_in_path(packed_forward_path.size() +
|
|
packed_reverse_path.size());
|
|
nodes_in_path.insert(packed_forward_path.begin(), packed_forward_path.end());
|
|
nodes_in_path.insert(middle_node);
|
|
nodes_in_path.insert(packed_reverse_path.begin(), packed_reverse_path.end());
|
|
|
|
std::unordered_map<NodeID, EdgeWeight> approximated_forward_sharing;
|
|
std::unordered_map<NodeID, EdgeWeight> approximated_reverse_sharing;
|
|
|
|
// sweep over search space, compute forward sharing for each current edge (u,v)
|
|
for (const SearchSpaceEdge ¤t_edge : forward_search_space)
|
|
{
|
|
const NodeID u = current_edge.first;
|
|
const NodeID v = current_edge.second;
|
|
|
|
if (nodes_in_path.find(v) != nodes_in_path.end())
|
|
{
|
|
// current_edge is on shortest path => sharing(v):=queue.GetKey(v);
|
|
approximated_forward_sharing.emplace(v, forward_heap1.GetKey(v));
|
|
}
|
|
else
|
|
{
|
|
// current edge is not on shortest path. Check if we know a value for the other
|
|
// endpoint
|
|
const auto sharing_of_u_iterator = approximated_forward_sharing.find(u);
|
|
if (sharing_of_u_iterator != approximated_forward_sharing.end())
|
|
{
|
|
approximated_forward_sharing.emplace(v, sharing_of_u_iterator->second);
|
|
}
|
|
}
|
|
}
|
|
|
|
// sweep over search space, compute backward sharing
|
|
for (const SearchSpaceEdge ¤t_edge : reverse_search_space)
|
|
{
|
|
const NodeID u = current_edge.first;
|
|
const NodeID v = current_edge.second;
|
|
if (nodes_in_path.find(v) != nodes_in_path.end())
|
|
{
|
|
// current_edge is on shortest path => sharing(u):=queue.GetKey(u);
|
|
approximated_reverse_sharing.emplace(v, reverse_heap1.GetKey(v));
|
|
}
|
|
else
|
|
{
|
|
// current edge is not on shortest path. Check if we know a value for the other
|
|
// endpoint
|
|
const auto sharing_of_u_iterator = approximated_reverse_sharing.find(u);
|
|
if (sharing_of_u_iterator != approximated_reverse_sharing.end())
|
|
{
|
|
approximated_reverse_sharing.emplace(v, sharing_of_u_iterator->second);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<NodeID> preselected_node_list;
|
|
for (const NodeID node : via_node_candidate_list)
|
|
{
|
|
if (node == middle_node)
|
|
continue;
|
|
const auto fwd_iterator = approximated_forward_sharing.find(node);
|
|
const EdgeWeight fwd_sharing = (fwd_iterator != approximated_forward_sharing.end())
|
|
? fwd_iterator->second
|
|
: EdgeWeight{0};
|
|
const auto rev_iterator = approximated_reverse_sharing.find(node);
|
|
const EdgeWeight rev_sharing = (rev_iterator != approximated_reverse_sharing.end())
|
|
? rev_iterator->second
|
|
: EdgeWeight{0};
|
|
|
|
const EdgeWeight approximated_sharing = fwd_sharing + rev_sharing;
|
|
const EdgeWeight approximated_weight =
|
|
forward_heap1.GetKey(node) + reverse_heap1.GetKey(node);
|
|
const bool weight_passes =
|
|
(from_alias<double>(approximated_weight) <
|
|
from_alias<double>(upper_bound_to_shortest_path_weight) * (1 + VIAPATH_EPSILON));
|
|
const bool sharing_passes =
|
|
(from_alias<double>(approximated_sharing) <=
|
|
from_alias<double>(upper_bound_to_shortest_path_weight) * VIAPATH_GAMMA);
|
|
const bool stretch_passes =
|
|
from_alias<double>(approximated_weight - approximated_sharing) <
|
|
((1. + VIAPATH_EPSILON) *
|
|
from_alias<double>(upper_bound_to_shortest_path_weight - approximated_sharing));
|
|
|
|
if (weight_passes && sharing_passes && stretch_passes)
|
|
{
|
|
preselected_node_list.emplace_back(node);
|
|
}
|
|
}
|
|
|
|
std::vector<NodeID> &packed_shortest_path = packed_forward_path;
|
|
if (!path_is_a_loop)
|
|
{
|
|
std::reverse(packed_shortest_path.begin(), packed_shortest_path.end());
|
|
packed_shortest_path.emplace_back(middle_node);
|
|
packed_shortest_path.insert(
|
|
packed_shortest_path.end(), packed_reverse_path.begin(), packed_reverse_path.end());
|
|
}
|
|
std::vector<RankedCandidateNode> ranked_candidates_list;
|
|
|
|
// prioritizing via nodes for deep inspection
|
|
for (const NodeID node : preselected_node_list)
|
|
{
|
|
EdgeWeight weight_of_via_path = {0}, sharing_of_via_path = {0};
|
|
computeWeightAndSharingOfViaPath(engine_working_data,
|
|
facade,
|
|
node,
|
|
&weight_of_via_path,
|
|
&sharing_of_via_path,
|
|
packed_shortest_path,
|
|
min_edge_offset);
|
|
const EdgeWeight maximum_allowed_sharing = to_alias<EdgeWeight>(
|
|
from_alias<double>(upper_bound_to_shortest_path_weight) * VIAPATH_GAMMA);
|
|
if (sharing_of_via_path <= maximum_allowed_sharing &&
|
|
from_alias<double>(weight_of_via_path) <=
|
|
from_alias<double>(upper_bound_to_shortest_path_weight) * (1 + VIAPATH_EPSILON))
|
|
{
|
|
ranked_candidates_list.emplace_back(node, weight_of_via_path, sharing_of_via_path);
|
|
}
|
|
}
|
|
std::sort(ranked_candidates_list.begin(), ranked_candidates_list.end());
|
|
|
|
NodeID selected_via_node = SPECIAL_NODEID;
|
|
EdgeWeight weight_of_via_path = INVALID_EDGE_WEIGHT;
|
|
NodeID s_v_middle = SPECIAL_NODEID, v_t_middle = SPECIAL_NODEID;
|
|
for (const RankedCandidateNode &candidate : ranked_candidates_list)
|
|
{
|
|
if (viaNodeCandidatePassesTTest(engine_working_data,
|
|
facade,
|
|
forward_heap1,
|
|
reverse_heap1,
|
|
forward_heap2,
|
|
reverse_heap2,
|
|
candidate,
|
|
upper_bound_to_shortest_path_weight,
|
|
&weight_of_via_path,
|
|
&s_v_middle,
|
|
&v_t_middle,
|
|
min_edge_offset))
|
|
{
|
|
// select first admissible
|
|
selected_via_node = candidate.node;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Unpack shortest path and alternative, if they exist
|
|
if (INVALID_EDGE_WEIGHT != upper_bound_to_shortest_path_weight)
|
|
{
|
|
auto phantom_endpoints = endpointsFromCandidates(endpoint_candidates, packed_shortest_path);
|
|
primary_route.leg_endpoints = {phantom_endpoints};
|
|
|
|
BOOST_ASSERT(!packed_shortest_path.empty());
|
|
primary_route.unpacked_path_segments.resize(1);
|
|
primary_route.source_traversed_in_reverse.push_back(
|
|
(packed_shortest_path.front() !=
|
|
phantom_endpoints.source_phantom.forward_segment_id.id));
|
|
primary_route.target_traversed_in_reverse.push_back((
|
|
packed_shortest_path.back() != phantom_endpoints.target_phantom.forward_segment_id.id));
|
|
|
|
unpackPath(facade,
|
|
// -- packed input
|
|
packed_shortest_path.begin(),
|
|
packed_shortest_path.end(),
|
|
// -- start of route
|
|
phantom_endpoints,
|
|
// -- unpacked output
|
|
primary_route.unpacked_path_segments.front());
|
|
primary_route.shortest_path_weight = upper_bound_to_shortest_path_weight;
|
|
}
|
|
|
|
if (SPECIAL_NODEID != selected_via_node)
|
|
{
|
|
std::vector<NodeID> packed_alternate_path;
|
|
// retrieve alternate path
|
|
retrievePackedAlternatePath(forward_heap1,
|
|
reverse_heap1,
|
|
forward_heap2,
|
|
reverse_heap2,
|
|
s_v_middle,
|
|
v_t_middle,
|
|
packed_alternate_path);
|
|
|
|
auto phantom_endpoints =
|
|
endpointsFromCandidates(endpoint_candidates, packed_alternate_path);
|
|
secondary_route.leg_endpoints = {phantom_endpoints};
|
|
|
|
secondary_route.unpacked_path_segments.resize(1);
|
|
secondary_route.source_traversed_in_reverse.push_back(
|
|
(packed_alternate_path.front() !=
|
|
phantom_endpoints.source_phantom.forward_segment_id.id));
|
|
secondary_route.target_traversed_in_reverse.push_back(
|
|
(packed_alternate_path.back() !=
|
|
phantom_endpoints.target_phantom.forward_segment_id.id));
|
|
|
|
// unpack the alternate path
|
|
unpackPath(facade,
|
|
packed_alternate_path.begin(),
|
|
packed_alternate_path.end(),
|
|
phantom_endpoints,
|
|
secondary_route.unpacked_path_segments.front());
|
|
|
|
secondary_route.shortest_path_weight = weight_of_via_path;
|
|
}
|
|
else
|
|
{
|
|
BOOST_ASSERT(secondary_route.shortest_path_weight == INVALID_EDGE_WEIGHT);
|
|
}
|
|
|
|
return InternalManyRoutesResult{{std::move(primary_route), std::move(secondary_route)}};
|
|
}
|
|
|
|
} // namespace osrm::engine::routing_algorithms
|