This change takes the existing typedefs for weight, duration and distance, and makes them proper types, using the existing Alias functionality. Primarily this is to prevent bugs where the metrics are switched, but it also adds additional documentation. For example, it now makes it clear (despite the naming of variables) that most of the trip algorithm is running on the duration metric. I've not made any changes to the casts performed between metrics and numeric types, they now just more explicit.
183 lines
7.6 KiB
C++
183 lines
7.6 KiB
C++
#ifndef TRIP_FARTHEST_INSERTION_HPP
|
|
#define TRIP_FARTHEST_INSERTION_HPP
|
|
|
|
#include "util/dist_table_wrapper.hpp"
|
|
#include "util/typedefs.hpp"
|
|
|
|
#include "osrm/json_container.hpp"
|
|
#include <boost/assert.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <cstdlib>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace osrm
|
|
{
|
|
namespace engine
|
|
{
|
|
namespace trip
|
|
{
|
|
|
|
// given a route and a new location, find the best place of insertion and
|
|
// check the distance of roundtrip when the new location is additionally visited
|
|
using NodeIDIter = std::vector<NodeID>::iterator;
|
|
inline std::pair<EdgeDuration, NodeIDIter>
|
|
GetShortestRoundTrip(const NodeID new_loc,
|
|
const util::DistTableWrapper<EdgeDuration> &dist_table,
|
|
const std::size_t number_of_locations,
|
|
std::vector<NodeID> &route)
|
|
{
|
|
(void)number_of_locations; // unused
|
|
|
|
auto min_trip_distance = INVALID_EDGE_DURATION;
|
|
NodeIDIter next_insert_point_candidate;
|
|
|
|
// for all nodes in the current trip find the best insertion resulting in the shortest path
|
|
// assert min 2 nodes in route
|
|
const auto start = std::begin(route);
|
|
const auto end = std::end(route);
|
|
for (auto from_node = start; from_node != end; ++from_node)
|
|
{
|
|
auto to_node = std::next(from_node);
|
|
if (to_node == end)
|
|
{
|
|
to_node = start;
|
|
}
|
|
|
|
const auto dist_from = dist_table(*from_node, new_loc);
|
|
const auto dist_to = dist_table(new_loc, *to_node);
|
|
// If the edge_weight is very large (INVALID_EDGE_DURATION) then the algorithm will not
|
|
// choose this edge in final minimal path. So instead of computing all the permutations
|
|
// after this large edge, discard this edge right here and don't consider the path after
|
|
// this edge.
|
|
if (dist_from == INVALID_EDGE_DURATION || dist_to == INVALID_EDGE_DURATION)
|
|
continue;
|
|
|
|
const auto trip_dist = dist_from + dist_to - dist_table(*from_node, *to_node);
|
|
|
|
// This is not neccessarily true:
|
|
// Lets say you have an edge (u, v) with duration 100. If you place a coordinate exactly in
|
|
// the middle of the segment yielding (u, v'), the adjusted duration will be 100 * 0.5 = 50.
|
|
// Now imagine two coordinates. One placed at 0.99 and one at 0.999. This means (u, v') now
|
|
// has a duration of 100 * 0.99 = 99, but (u, v'') also has a duration of 100 * 0.995 = 99.
|
|
// In which case (v', v'') has a duration of 0.
|
|
// BOOST_ASSERT_MSG(trip_dist >= 0, "previous trip was not minimal. something's wrong");
|
|
|
|
// from all possible insertions to the current trip, choose the shortest of all insertions
|
|
if (trip_dist < min_trip_distance)
|
|
{
|
|
min_trip_distance = trip_dist;
|
|
next_insert_point_candidate = to_node;
|
|
}
|
|
}
|
|
BOOST_ASSERT_MSG(min_trip_distance != INVALID_EDGE_DURATION, "trip has invalid edge weight");
|
|
|
|
return std::make_pair(min_trip_distance, next_insert_point_candidate);
|
|
}
|
|
|
|
// given two initial start nodes, find a roundtrip route using the farthest insertion algorithm
|
|
inline std::vector<NodeID> FindRoute(const std::size_t &number_of_locations,
|
|
const util::DistTableWrapper<EdgeDuration> &dist_table,
|
|
const NodeID &start1,
|
|
const NodeID &start2)
|
|
{
|
|
BOOST_ASSERT_MSG(number_of_locations * number_of_locations == dist_table.size(),
|
|
"number_of_locations and dist_table size do not match");
|
|
|
|
std::vector<NodeID> route;
|
|
route.reserve(number_of_locations);
|
|
|
|
// tracks which nodes have been already visited
|
|
std::vector<bool> visited(number_of_locations, false);
|
|
|
|
visited[start1] = true;
|
|
visited[start2] = true;
|
|
route.push_back(start1);
|
|
route.push_back(start2);
|
|
|
|
// two nodes are already in the initial start trip, so we need to add all other nodes
|
|
for (std::size_t added_nodes = 2; added_nodes < number_of_locations; ++added_nodes)
|
|
{
|
|
auto farthest_distance = EdgeDuration{std::numeric_limits<EdgeDuration::value_type>::min()};
|
|
auto next_node = -1;
|
|
NodeIDIter next_insert_point;
|
|
|
|
// find unvisited node that is the farthest away from all other visited locs
|
|
for (std::size_t id = 0; id < number_of_locations; ++id)
|
|
{
|
|
// find the shortest distance from i to all visited nodes
|
|
if (!visited[id])
|
|
{
|
|
const auto insert_candidate =
|
|
GetShortestRoundTrip(id, dist_table, number_of_locations, route);
|
|
|
|
BOOST_ASSERT_MSG(insert_candidate.first != INVALID_EDGE_DURATION,
|
|
"shortest round trip is invalid");
|
|
|
|
// add the location to the current trip such that it results in the shortest total
|
|
// tour
|
|
if (insert_candidate.first > farthest_distance)
|
|
{
|
|
farthest_distance = insert_candidate.first;
|
|
next_node = id;
|
|
next_insert_point = insert_candidate.second;
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOST_ASSERT_MSG(next_node >= 0, "next node to visit is invalid");
|
|
|
|
// mark as visited and insert node
|
|
visited[next_node] = true;
|
|
route.insert(next_insert_point, next_node);
|
|
}
|
|
return route;
|
|
}
|
|
|
|
inline std::vector<NodeID>
|
|
FarthestInsertionTrip(const std::size_t number_of_locations,
|
|
const util::DistTableWrapper<EdgeDuration> &dist_table)
|
|
{
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// START FARTHEST INSERTION HERE
|
|
// 1. start at a random round trip of 2 locations
|
|
// 2. find the location that is the farthest away from the visited locations and whose insertion
|
|
// will make the round trip the longest
|
|
// 3. add the found location to the current round trip such that round trip is the shortest
|
|
// 4. repeat 2-3 until all locations are visited
|
|
// 5. DONE!
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Guard against division-by-zero in the code path below.
|
|
BOOST_ASSERT(number_of_locations > 0);
|
|
|
|
// Guard against dist_table being empty therefore max_element returning the end iterator.
|
|
BOOST_ASSERT(dist_table.size() > 0);
|
|
|
|
BOOST_ASSERT_MSG(number_of_locations * number_of_locations == dist_table.size(),
|
|
"number_of_locations and dist_table size do not match");
|
|
|
|
// find the pair of location with the biggest distance and make the pair the initial start
|
|
// trip. Skipping over the very first element (0,0), we make sure not to end up with the
|
|
// same start/end in the special case where all entries are the same.
|
|
const auto index_of_farthest_distance = std::distance(
|
|
std::begin(dist_table), std::max_element(std::begin(dist_table) + 1, std::end(dist_table)));
|
|
// distance table is a nxn matrix with the distance(u,v) in column u and row v
|
|
// but the distance table is stored in an 1D array of distances
|
|
// to get the actual (u,v), get the row by dividing and the column by computing modulo n
|
|
NodeID max_from = index_of_farthest_distance / number_of_locations;
|
|
NodeID max_to = index_of_farthest_distance % number_of_locations;
|
|
|
|
BOOST_ASSERT_MSG(static_cast<std::size_t>(max_from) < number_of_locations, "start node");
|
|
BOOST_ASSERT_MSG(static_cast<std::size_t>(max_to) < number_of_locations, "start node");
|
|
return FindRoute(number_of_locations, dist_table, max_from, max_to);
|
|
}
|
|
|
|
} // namespace trip
|
|
} // namespace engine
|
|
} // namespace osrm
|
|
|
|
#endif // TRIP_FARTHEST_INSERTION_HPP
|