osrm-backend/include/engine/trip/trip_farthest_insertion.hpp

181 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<EdgeWeight, NodeIDIter>
GetShortestRoundTrip(const NodeID new_loc,
const util::DistTableWrapper<EdgeWeight> &dist_table,
const std::size_t number_of_locations,
std::vector<NodeID> &route)
{
(void)number_of_locations; // unused
auto min_trip_distance = INVALID_EDGE_WEIGHT;
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);
const auto trip_dist = dist_from + dist_to - dist_table(*from_node, *to_node);
// If the edge_weight is very large (INVALID_EDGE_WEIGHT) 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_WEIGHT || dist_to == INVALID_EDGE_WEIGHT)
continue;
// 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_WEIGHT, "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<EdgeWeight> &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 = std::numeric_limits<int>::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_WEIGHT,
"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<EdgeWeight> &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