osrm-backend/include/partitioner/multi_level_graph.hpp
2024-05-06 09:14:46 +02:00

242 lines
9.7 KiB
C++

#ifndef OSRM_PARTITIONER_MULTI_LEVEL_GRAPH_HPP
#define OSRM_PARTITIONER_MULTI_LEVEL_GRAPH_HPP
#include "partitioner/edge_based_graph.hpp"
#include "partitioner/multi_level_partition.hpp"
#include "storage/shared_memory_ownership.hpp"
#include "storage/tar_fwd.hpp"
#include "util/static_graph.hpp"
#include "util/vector_view.hpp"
#include <tbb/parallel_sort.h>
#include <boost/iterator/permutation_iterator.hpp>
#include <boost/range/combine.hpp>
namespace osrm::partitioner
{
template <typename EdgeDataT, storage::Ownership Ownership> class MultiLevelGraph;
namespace serialization
{
template <typename EdgeDataT, storage::Ownership Ownership>
void read(storage::tar::FileReader &reader,
const std::string &name,
MultiLevelGraph<EdgeDataT, Ownership> &graph);
template <typename EdgeDataT, storage::Ownership Ownership>
void write(storage::tar::FileWriter &writer,
const std::string &name,
const MultiLevelGraph<EdgeDataT, Ownership> &graph);
} // namespace serialization
template <typename EdgeDataT, storage::Ownership Ownership>
class MultiLevelGraph : public util::StaticGraph<EdgeDataT, Ownership>
{
private:
using SuperT = util::StaticGraph<EdgeDataT, Ownership>;
template <typename T> using Vector = util::ViewOrVector<T, Ownership>;
public:
using SuperT::SuperT;
// We limit each node to have 255 edges
// this is very generous, we could probably pack this
using EdgeOffset = std::uint8_t;
MultiLevelGraph() = default;
MultiLevelGraph(MultiLevelGraph &&) = default;
MultiLevelGraph(const MultiLevelGraph &) = default;
MultiLevelGraph &operator=(MultiLevelGraph &&) = default;
MultiLevelGraph &operator=(const MultiLevelGraph &) = default;
MultiLevelGraph(Vector<typename SuperT::NodeArrayEntry> node_array_,
Vector<typename SuperT::EdgeArrayEntry> edge_array_,
Vector<EdgeOffset> node_to_edge_offset_)
: SuperT(std::move(node_array_), std::move(edge_array_)),
node_to_edge_offset(std::move(node_to_edge_offset_))
{
}
template <typename ContainerT>
MultiLevelGraph(const MultiLevelPartition &mlp,
const std::uint32_t num_nodes,
const ContainerT &edges)
{
auto highest_border_level = GetHighestBorderLevel(mlp, edges);
auto permutation = SortEdgesByHighestLevel(highest_border_level, edges);
auto sorted_edges_begin =
boost::make_permutation_iterator(edges.begin(), permutation.begin());
auto sorted_edges_end = boost::make_permutation_iterator(edges.begin(), permutation.end());
SuperT::InitializeFromSortedEdgeRange(num_nodes, sorted_edges_begin, sorted_edges_end);
// if the node ordering is sorting the border nodes first,
// the id of the maximum border node will be rather low
// enabling us to save some memory here
auto max_border_node_id = 0u;
for (auto edge_index : util::irange<std::size_t>(0, edges.size()))
{
if (highest_border_level[edge_index] > 0)
{
max_border_node_id =
std::max(max_border_node_id,
std::max(edges[edge_index].source, edges[edge_index].target));
}
}
BOOST_ASSERT(max_border_node_id < num_nodes);
auto edge_and_level_range = boost::combine(edges, highest_border_level);
auto sorted_edge_and_level_begin =
boost::make_permutation_iterator(edge_and_level_range.begin(), permutation.begin());
auto sorted_edge_and_level_end =
boost::make_permutation_iterator(edge_and_level_range.begin(), permutation.end());
InitializeOffsetsFromSortedEdges(
mlp, max_border_node_id, sorted_edge_and_level_begin, sorted_edge_and_level_end);
}
// Fast scan over all relevant border edges
// For level 0 this yield the same result as GetAdjacentEdgeRange
auto GetBorderEdgeRange(const LevelID level, const NodeID node) const
{
auto begin = BeginBorderEdges(level, node);
auto end = SuperT::EndEdges(node);
return util::irange<EdgeID>(begin, end);
}
// Fast scan over all relevant internal edges, that is edges that will not
// leave the cell of that node at the given level
// For level 0 this returns an empty edge range
auto GetInternalEdgeRange(const LevelID level, const NodeID node) const
{
auto begin = SuperT::BeginEdges(node);
auto end = BeginBorderEdges(level, node);
return util::irange<EdgeID>(begin, end);
}
EdgeID BeginBorderEdges(const LevelID level, const NodeID node) const
{
auto index = node * GetNumberOfLevels();
// `node_to_edge_offset` has size `max_border_node_id + 1`
// which can be smaller then the total number of nodes.
// this will save memory in case we sort the border nodes first
if (index >= node_to_edge_offset.size() - 1)
{
// On level 0 all edges are border edges
if (level == 0)
return SuperT::BeginEdges(node);
else
return SuperT::EndEdges(node);
}
else
{
return SuperT::BeginEdges(node) + node_to_edge_offset[index + level];
}
}
// We save the level as sentinel at the end
LevelID GetNumberOfLevels() const { return node_to_edge_offset.back(); }
NodeID GetMaxBorderNodeID() const
{
auto num_levels = GetNumberOfLevels();
BOOST_ASSERT((node_to_edge_offset.size() - 1) % num_levels == 0);
auto max_border_node_id = (node_to_edge_offset.size() - 1) / num_levels - 1;
return max_border_node_id;
}
auto data() && // rvalue ref-qualifier is a safety-belt
{
return std::make_tuple(std::move(SuperT::node_array),
std::move(SuperT::edge_array),
std::move(node_to_edge_offset),
connectivity_checksum);
}
private:
template <typename ContainerT>
auto GetHighestBorderLevel(const MultiLevelPartition &mlp, const ContainerT &edges) const
{
std::vector<LevelID> highest_border_level(edges.size());
std::transform(edges.begin(),
edges.end(),
highest_border_level.begin(),
[&mlp](const auto &edge)
{ return mlp.GetHighestDifferentLevel(edge.source, edge.target); });
return highest_border_level;
}
template <typename ContainerT>
auto SortEdgesByHighestLevel(const std::vector<LevelID> &highest_border_level,
const ContainerT &edges) const
{
std::vector<std::uint32_t> permutation(edges.size());
std::iota(permutation.begin(), permutation.end(), 0);
tbb::parallel_sort(
permutation.begin(),
permutation.end(),
[&edges, &highest_border_level](const auto &lhs, const auto &rhs)
{
// sort by source node and then by level in ascending order
return std::tie(edges[lhs].source, highest_border_level[lhs], edges[lhs].target) <
std::tie(edges[rhs].source, highest_border_level[rhs], edges[rhs].target);
});
return permutation;
}
template <typename ZipIterT>
auto InitializeOffsetsFromSortedEdges(const MultiLevelPartition &mlp,
const NodeID max_border_node_id,
ZipIterT edge_and_level_begin,
ZipIterT edge_and_level_end)
{
auto num_levels = mlp.GetNumberOfLevels();
// we save one sentinel element at the end
node_to_edge_offset.reserve(num_levels * (max_border_node_id + 1) + 1);
auto iter = edge_and_level_begin;
for (auto node : util::irange<NodeID>(0, max_border_node_id + 1))
{
node_to_edge_offset.push_back(0);
auto level_begin = iter;
for (auto level : util::irange<LevelID>(0, mlp.GetNumberOfLevels()))
{
iter = std::find_if(iter,
edge_and_level_end,
[node, level](const auto &edge_and_level) {
return boost::get<0>(edge_and_level).source != node ||
boost::get<1>(edge_and_level) != level;
});
EdgeOffset offset = std::distance(level_begin, iter);
node_to_edge_offset.push_back(offset);
}
node_to_edge_offset.pop_back();
}
BOOST_ASSERT(node_to_edge_offset.size() ==
mlp.GetNumberOfLevels() * (max_border_node_id + 1));
// save number of levels as last element so we can reconstruct the stride
node_to_edge_offset.push_back(mlp.GetNumberOfLevels());
}
friend void
serialization::read<EdgeDataT, Ownership>(storage::tar::FileReader &reader,
const std::string &name,
MultiLevelGraph<EdgeDataT, Ownership> &graph);
friend void
serialization::write<EdgeDataT, Ownership>(storage::tar::FileWriter &writer,
const std::string &name,
const MultiLevelGraph<EdgeDataT, Ownership> &graph);
protected:
Vector<EdgeOffset> node_to_edge_offset;
std::uint32_t connectivity_checksum = 0;
};
using MultiLevelEdgeBasedGraph =
MultiLevelGraph<EdgeBasedGraphEdgeData, storage::Ownership::Container>;
} // namespace osrm::partitioner
#endif