Rename namespace partition to partitioner
Rename module partition to partitioner. This cultivates naming used in existing modules like extractor, customizer, etc. - noun vs verb (word partition is both though).
This commit is contained in:
committed by
Patrick Niklaus
parent
03f598b93d
commit
8114104a43
@@ -0,0 +1,136 @@
|
||||
#ifndef OSRM_PARTITIONER_ANNOTATE_HPP_
|
||||
#define OSRM_PARTITIONER_ANNOTATE_HPP_
|
||||
|
||||
#include "partitioner/bisection_graph.hpp"
|
||||
#include "util/typedefs.hpp"
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
|
||||
// takes the result of a recursive bisection and turns it into an annotated partition for MLD. These
|
||||
// annotated partitions provide a mapping from every node in the graph to a consecutively
|
||||
// numbered cell in each level of the multi level partition. Instead of using the bisection directly
|
||||
// (which can result in a unbalanced tree structure)
|
||||
//
|
||||
// _____o______
|
||||
// / \
|
||||
// o ____o____
|
||||
// / \ / \
|
||||
// a b o _o_
|
||||
// / \ / \
|
||||
// c d o o
|
||||
// / \ / \
|
||||
// e f g h
|
||||
//
|
||||
// we build a balanced structure that will result in a multi-cut on any level. We transform this
|
||||
// layout into:
|
||||
//
|
||||
// _____o__________
|
||||
// / | \
|
||||
// o | \
|
||||
// / \ | \
|
||||
// a b o _o_
|
||||
// / \ / \
|
||||
// c d o o
|
||||
// / \ / \
|
||||
// e f g h
|
||||
class AnnotatedPartition
|
||||
{
|
||||
public:
|
||||
// Used to generate an implicit tree representation
|
||||
struct SizedID
|
||||
{
|
||||
BisectionID id;
|
||||
std::size_t count;
|
||||
|
||||
bool operator<(const SizedID &other) const { return id < other.id; };
|
||||
};
|
||||
|
||||
// Metrics that describe a single level
|
||||
struct LevelMetrics
|
||||
{
|
||||
std::size_t border_nodes;
|
||||
std::size_t border_arcs;
|
||||
|
||||
// impresses imbalance, if not all nodes are in that cell anymore
|
||||
std::size_t contained_nodes;
|
||||
std::size_t number_of_cells;
|
||||
|
||||
std::size_t max_border_nodes_per_cell;
|
||||
std::size_t max_border_arcs_per_cell;
|
||||
|
||||
std::size_t total_memory_cells;
|
||||
std::vector<std::size_t> cell_sizes;
|
||||
|
||||
std::ostream &print(std::ostream &os) const
|
||||
{
|
||||
os << "[level]\n"
|
||||
<< "\t#border nodes: " << border_nodes << " #border arcs: " << border_arcs
|
||||
<< " #cells: " << number_of_cells << " #contained nodes: " << contained_nodes << "\n"
|
||||
<< "\tborder nodes: max: " << max_border_nodes_per_cell
|
||||
<< " avg : " << static_cast<double>(border_nodes) / number_of_cells
|
||||
<< " border arcs: max: " << max_border_arcs_per_cell << " "
|
||||
<< " avg: " << static_cast<double>(border_arcs) / number_of_cells << "\n"
|
||||
<< "\tmemory consumption: " << total_memory_cells / (1024.0 * 1024.0) << " MB."
|
||||
<< "\n";
|
||||
os << "\tcell sizes:";
|
||||
for (auto s : cell_sizes)
|
||||
os << " " << s;
|
||||
os << std::endl;
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream &logMachinereadable(std::ostream &os,
|
||||
const std::string &identification,
|
||||
std::size_t depth,
|
||||
const bool print_header = false) const
|
||||
{
|
||||
if (print_header)
|
||||
os << "[" << identification << "] # depth cells total_nodes border_nodes "
|
||||
"max_border_nodes border_arcs max_border_arcs bytes "
|
||||
"cell_sizes*\n";
|
||||
|
||||
os << "[" << identification << "] " << depth << " " << number_of_cells << " "
|
||||
<< contained_nodes << " " << border_nodes << " " << max_border_nodes_per_cell << " "
|
||||
<< border_arcs << " " << max_border_arcs_per_cell << " " << total_memory_cells;
|
||||
|
||||
for (auto s : cell_sizes)
|
||||
os << " " << s;
|
||||
|
||||
os << "\n";
|
||||
return os;
|
||||
}
|
||||
};
|
||||
|
||||
AnnotatedPartition(const BisectionGraph &graph, const std::vector<BisectionID> &bisection_ids);
|
||||
|
||||
private:
|
||||
// print distribution of level graph as it is
|
||||
void PrintBisection(const std::vector<SizedID> &implicit_tree,
|
||||
const BisectionGraph &graph,
|
||||
const std::vector<BisectionID> &bisection_ids) const;
|
||||
|
||||
// find levels that offer good distribution of average cell sizes
|
||||
void SearchLevels(const std::vector<SizedID> &implicit_tree,
|
||||
const BisectionGraph &graph,
|
||||
const std::vector<BisectionID> &bisection_ids) const;
|
||||
|
||||
// set cell_ids[i] == INFTY to exclude element
|
||||
LevelMetrics AnalyseLevel(const BisectionGraph &graph,
|
||||
const std::vector<std::uint32_t> &cell_ids) const;
|
||||
|
||||
std::vector<std::uint32_t>
|
||||
ComputeCellIDs(std::vector<std::pair<BisectionID, std::int32_t>> &prefixes,
|
||||
const BisectionGraph &graph,
|
||||
const std::vector<BisectionID> &bisection_ids) const;
|
||||
};
|
||||
|
||||
} // namespace partitioner
|
||||
} // namespace osrm
|
||||
|
||||
#endif // OSRM_PARTITIONER_ANNOTATE_HPP_
|
||||
@@ -0,0 +1,115 @@
|
||||
#ifndef OSRM_PARTITIONER_BISECTION_GRAPH_HPP_
|
||||
#define OSRM_PARTITIONER_BISECTION_GRAPH_HPP_
|
||||
|
||||
#include "util/coordinate.hpp"
|
||||
#include "util/typedefs.hpp"
|
||||
|
||||
#include "partitioner/partition_graph.hpp"
|
||||
|
||||
#include "extractor/edge_based_edge.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
|
||||
// Node in the bisection graph. We require the original node id (since we remap the nodes all the
|
||||
// time and can track the correct ID this way). In addtition, the node provides the coordinate its
|
||||
// located at for use in the inertial flow sorting by slope.
|
||||
struct BisectionNode
|
||||
{
|
||||
BisectionNode(util::Coordinate coordinate_ = {util::FloatLongitude{0}, util::FloatLatitude{0}},
|
||||
const NodeID original_id_ = SPECIAL_NODEID)
|
||||
: coordinate(std::move(coordinate_)), original_id(original_id_)
|
||||
{
|
||||
}
|
||||
|
||||
// the coordinate the node is located at
|
||||
util::Coordinate coordinate;
|
||||
|
||||
// the node id to access the bisection result
|
||||
NodeID original_id;
|
||||
};
|
||||
|
||||
// For max-flow/min-cut computations, we operate on a undirected graph. This has some benefits:
|
||||
// - we don't disconnect the graph more than we have to
|
||||
// - small components will actually be disconnected (no border nodes)
|
||||
// - parts of the graph that are clonnected in one way (not reachable/not exitable) will remain
|
||||
// close to their connected nodes
|
||||
// As a result, we only require a target as our only data member in the edge.
|
||||
struct BisectionEdge
|
||||
{
|
||||
BisectionEdge(const NodeID target_ = SPECIAL_NODEID) : target(target_) {}
|
||||
// StaticGraph Edge requirement (see static graph traits): .target, .data
|
||||
NodeID target;
|
||||
};
|
||||
|
||||
// Aliases for the graph used during the bisection, based on the Remappable graph
|
||||
using BisectionGraphNode = NodeEntryWrapper<BisectionNode>;
|
||||
using BisectionInputEdge = GraphConstructionWrapper<BisectionEdge>;
|
||||
using BisectionGraph = RemappableGraph<BisectionGraphNode, BisectionEdge>;
|
||||
|
||||
// Factory method to construct the bisection graph form a set of coordinates and Input Edges (need
|
||||
// to contain source and target). Edges needs to be labeled from zero
|
||||
inline BisectionGraph makeBisectionGraph(const std::vector<util::Coordinate> &coordinates,
|
||||
const std::vector<BisectionInputEdge> &edges)
|
||||
{
|
||||
std::vector<BisectionGraph::NodeT> result_nodes;
|
||||
result_nodes.reserve(coordinates.size());
|
||||
std::vector<BisectionGraph::EdgeT> result_edges;
|
||||
result_edges.reserve(edges.size());
|
||||
|
||||
// find the end of edges that belong to node_id
|
||||
const auto advance_edge_itr = [&edges, &result_edges](const std::size_t node_id,
|
||||
auto edge_itr) {
|
||||
while (edge_itr != edges.end() && edge_itr->source == node_id)
|
||||
{
|
||||
result_edges.push_back(edge_itr->Reduce());
|
||||
++edge_itr;
|
||||
}
|
||||
return edge_itr;
|
||||
};
|
||||
|
||||
// create a bisection node, requires the ID of the node as well as the lower bound to its edges
|
||||
const auto make_bisection_node = [&edges, &coordinates](
|
||||
const std::size_t node_id, const auto begin_itr, const auto end_itr) {
|
||||
std::size_t range_begin = std::distance(edges.begin(), begin_itr);
|
||||
std::size_t range_end = std::distance(edges.begin(), end_itr);
|
||||
return BisectionGraph::NodeT(range_begin, range_end, coordinates[node_id], node_id);
|
||||
};
|
||||
|
||||
auto edge_itr = edges.begin();
|
||||
for (std::size_t node_id = 0; node_id < coordinates.size(); ++node_id)
|
||||
{
|
||||
const auto begin_itr = edge_itr;
|
||||
edge_itr = advance_edge_itr(node_id, edge_itr);
|
||||
result_nodes.emplace_back(make_bisection_node(node_id, begin_itr, edge_itr));
|
||||
}
|
||||
|
||||
return BisectionGraph(std::move(result_nodes), std::move(result_edges));
|
||||
}
|
||||
|
||||
// Reduce any edge to a fitting input edge for the bisection graph
|
||||
template <typename InputEdge>
|
||||
std::vector<BisectionInputEdge> adaptToBisectionEdge(std::vector<InputEdge> edges)
|
||||
{
|
||||
std::vector<BisectionInputEdge> result;
|
||||
result.reserve(edges.size());
|
||||
|
||||
std::transform(begin(edges), end(edges), std::back_inserter(result), [](const auto &edge) {
|
||||
return BisectionInputEdge{edge.source, edge.target};
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace partitioner
|
||||
} // namespace osrm
|
||||
|
||||
#endif // OSRM_PARTITIONER_BISECTION_GRAPH_HPP_
|
||||
@@ -0,0 +1,70 @@
|
||||
#ifndef OSRM_PARTITIONER_BISECTION_GRAPHVIEW_HPP_
|
||||
#define OSRM_PARTITIONER_BISECTION_GRAPHVIEW_HPP_
|
||||
|
||||
#include "partitioner/bisection_graph.hpp"
|
||||
|
||||
#include <boost/iterator/filter_iterator.hpp>
|
||||
#include <boost/iterator/iterator_facade.hpp>
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
|
||||
// Non-owning immutable sub-graph view into a base graph.
|
||||
// The part of the graph to select is determined by the recursive bisection state.
|
||||
class BisectionGraphView
|
||||
{
|
||||
public:
|
||||
using ConstNodeIterator = BisectionGraph::ConstNodeIterator;
|
||||
using NodeIterator = BisectionGraph::NodeIterator;
|
||||
using NodeT = BisectionGraph::NodeT;
|
||||
using EdgeT = BisectionGraph::EdgeT;
|
||||
|
||||
// Construction either for a subrange, or for a full range
|
||||
BisectionGraphView(const BisectionGraph &graph);
|
||||
BisectionGraphView(const BisectionGraph &graph,
|
||||
const ConstNodeIterator begin,
|
||||
const ConstNodeIterator end);
|
||||
|
||||
// construction from a different view, no need to keep the graph around
|
||||
BisectionGraphView(const BisectionGraphView &view,
|
||||
const ConstNodeIterator begin,
|
||||
const ConstNodeIterator end);
|
||||
|
||||
// Number of nodes _in this sub-graph.
|
||||
std::size_t NumberOfNodes() const;
|
||||
|
||||
// Iteration over all nodes (direct access into the node)
|
||||
ConstNodeIterator Begin() const;
|
||||
ConstNodeIterator End() const;
|
||||
auto Nodes() const { return boost::make_iterator_range(begin, end); }
|
||||
|
||||
// Re-Construct the ID of a node from a reference
|
||||
NodeID GetID(const NodeT &node) const;
|
||||
|
||||
// Access into single nodes/Edges
|
||||
const NodeT &Node(const NodeID nid) const;
|
||||
const EdgeT &Edge(const EdgeID eid) const;
|
||||
|
||||
// Access into all Edges
|
||||
auto Edges(const NodeT &node) const { return bisection_graph.Edges(node); }
|
||||
auto Edges(const NodeID nid) const { return bisection_graph.Edges(*(begin + nid)); }
|
||||
auto BeginEdges(const NodeID nid) const { return bisection_graph.BeginEdges(*(begin + nid)); }
|
||||
auto EndEdges(const NodeID nid) const { return bisection_graph.EndEdges(*(begin + nid)); }
|
||||
|
||||
private:
|
||||
const BisectionGraph &bisection_graph;
|
||||
|
||||
const BisectionGraph::ConstNodeIterator begin;
|
||||
const BisectionGraph::ConstNodeIterator end;
|
||||
};
|
||||
|
||||
} // namespace partitioner
|
||||
} // namespace osrm
|
||||
|
||||
#endif // OSRM_PARTITIONER_GRAPHVIEW_HPP_
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef OSRM_PARTITIONER_BISECTION_TO_PARTITION_HPP
|
||||
#define OSRM_PARTITIONER_BISECTION_TO_PARTITION_HPP
|
||||
|
||||
#include "partitioner/multi_level_partition.hpp"
|
||||
#include "partitioner/recursive_bisection.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
|
||||
using Partition = std::vector<CellID>;
|
||||
|
||||
// Converts a representation of the bisection to cell ids over multiple level
|
||||
std::tuple<std::vector<Partition>, std::vector<std::uint32_t>>
|
||||
bisectionToPartition(const std::vector<BisectionID> &node_to_bisection_id,
|
||||
const std::vector<std::size_t> &max_cell_sizes);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,408 @@
|
||||
#ifndef OSRM_PARTITIONER_CUSTOMIZE_CELL_STORAGE_HPP
|
||||
#define OSRM_PARTITIONER_CUSTOMIZE_CELL_STORAGE_HPP
|
||||
|
||||
#include "partitioner/multi_level_partition.hpp"
|
||||
|
||||
#include "util/assert.hpp"
|
||||
#include "util/for_each_range.hpp"
|
||||
#include "util/log.hpp"
|
||||
#include "util/typedefs.hpp"
|
||||
#include "util/vector_view.hpp"
|
||||
|
||||
#include "storage/io_fwd.hpp"
|
||||
#include "storage/shared_memory_ownership.hpp"
|
||||
|
||||
#include "customizer/cell_metric.hpp"
|
||||
|
||||
#include <boost/iterator/iterator_facade.hpp>
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
|
||||
#include <tbb/parallel_sort.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template <storage::Ownership Ownership> class CellStorageImpl;
|
||||
}
|
||||
using CellStorage = detail::CellStorageImpl<storage::Ownership::Container>;
|
||||
using CellStorageView = detail::CellStorageImpl<storage::Ownership::View>;
|
||||
|
||||
namespace serialization
|
||||
{
|
||||
template <storage::Ownership Ownership>
|
||||
inline void read(storage::io::FileReader &reader, detail::CellStorageImpl<Ownership> &storage);
|
||||
template <storage::Ownership Ownership>
|
||||
inline void write(storage::io::FileWriter &writer,
|
||||
const detail::CellStorageImpl<Ownership> &storage);
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template <storage::Ownership Ownership> class CellStorageImpl
|
||||
{
|
||||
public:
|
||||
using ValueOffset = std::uint32_t;
|
||||
using BoundaryOffset = std::uint32_t;
|
||||
using BoundarySize = std::uint32_t;
|
||||
using SourceIndex = std::uint32_t;
|
||||
using DestinationIndex = std::uint32_t;
|
||||
|
||||
static constexpr auto INVALID_VALUE_OFFSET = std::numeric_limits<ValueOffset>::max();
|
||||
static constexpr auto INVALID_BOUNDARY_OFFSET = std::numeric_limits<BoundaryOffset>::max();
|
||||
|
||||
struct CellData
|
||||
{
|
||||
ValueOffset value_offset = INVALID_VALUE_OFFSET;
|
||||
BoundaryOffset source_boundary_offset = INVALID_BOUNDARY_OFFSET;
|
||||
BoundaryOffset destination_boundary_offset = INVALID_BOUNDARY_OFFSET;
|
||||
BoundarySize num_source_nodes = 0;
|
||||
BoundarySize num_destination_nodes = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
template <typename T> using Vector = util::ViewOrVector<T, Ownership>;
|
||||
|
||||
// Implementation of the cell view. We need a template parameter here
|
||||
// because we need to derive a read-only and read-write view from this.
|
||||
template <typename WeightValueT, typename DurationValueT> class CellImpl
|
||||
{
|
||||
private:
|
||||
using WeightPtrT = WeightValueT *;
|
||||
using DurationPtrT = DurationValueT *;
|
||||
BoundarySize num_source_nodes;
|
||||
BoundarySize num_destination_nodes;
|
||||
|
||||
WeightPtrT const weights;
|
||||
DurationPtrT const durations;
|
||||
const NodeID *const source_boundary;
|
||||
const NodeID *const destination_boundary;
|
||||
|
||||
using RowIterator = WeightPtrT;
|
||||
// Possibly replace with
|
||||
// http://www.boost.org/doc/libs/1_55_0/libs/range/doc/html/range/reference/adaptors/reference/strided.html
|
||||
class ColumnIterator : public boost::iterator_facade<ColumnIterator,
|
||||
WeightValueT,
|
||||
boost::random_access_traversal_tag>
|
||||
{
|
||||
typedef boost::iterator_facade<ColumnIterator,
|
||||
WeightValueT,
|
||||
boost::random_access_traversal_tag>
|
||||
base_t;
|
||||
|
||||
public:
|
||||
typedef typename base_t::value_type value_type;
|
||||
typedef typename base_t::difference_type difference_type;
|
||||
typedef typename base_t::reference reference;
|
||||
typedef std::random_access_iterator_tag iterator_category;
|
||||
|
||||
explicit ColumnIterator() : current(nullptr), stride(1) {}
|
||||
|
||||
explicit ColumnIterator(WeightPtrT begin, std::size_t row_length)
|
||||
: current(begin), stride(row_length)
|
||||
{
|
||||
BOOST_ASSERT(begin != nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
void increment() { current += stride; }
|
||||
void decrement() { current -= stride; }
|
||||
void advance(difference_type offset) { current += stride * offset; }
|
||||
bool equal(const ColumnIterator &other) const { return current == other.current; }
|
||||
reference dereference() const { return *current; }
|
||||
difference_type distance_to(const ColumnIterator &other) const
|
||||
{
|
||||
return (other.current - current) / static_cast<std::intptr_t>(stride);
|
||||
}
|
||||
|
||||
friend class ::boost::iterator_core_access;
|
||||
WeightPtrT current;
|
||||
const std::size_t stride;
|
||||
};
|
||||
|
||||
template <typename ValuePtr> auto GetOutRange(const ValuePtr ptr, const NodeID node) const
|
||||
{
|
||||
auto iter = std::find(source_boundary, source_boundary + num_source_nodes, node);
|
||||
if (iter == source_boundary + num_source_nodes)
|
||||
return boost::make_iterator_range(ptr, ptr);
|
||||
|
||||
auto row = std::distance(source_boundary, iter);
|
||||
auto begin = ptr + num_destination_nodes * row;
|
||||
auto end = begin + num_destination_nodes;
|
||||
return boost::make_iterator_range(begin, end);
|
||||
}
|
||||
|
||||
template <typename ValuePtr> auto GetInRange(const ValuePtr ptr, const NodeID node) const
|
||||
{
|
||||
auto iter =
|
||||
std::find(destination_boundary, destination_boundary + num_destination_nodes, node);
|
||||
if (iter == destination_boundary + num_destination_nodes)
|
||||
return boost::make_iterator_range(ColumnIterator{}, ColumnIterator{});
|
||||
|
||||
auto column = std::distance(destination_boundary, iter);
|
||||
auto begin = ColumnIterator{ptr + column, num_destination_nodes};
|
||||
auto end = ColumnIterator{ptr + column + num_source_nodes * num_destination_nodes,
|
||||
num_destination_nodes};
|
||||
return boost::make_iterator_range(begin, end);
|
||||
}
|
||||
|
||||
public:
|
||||
auto GetOutWeight(NodeID node) const { return GetOutRange(weights, node); }
|
||||
|
||||
auto GetInWeight(NodeID node) const { return GetInRange(weights, node); }
|
||||
|
||||
auto GetOutDuration(NodeID node) const { return GetOutRange(durations, node); }
|
||||
|
||||
auto GetInDuration(NodeID node) const { return GetInRange(durations, node); }
|
||||
|
||||
auto GetSourceNodes() const
|
||||
{
|
||||
return boost::make_iterator_range(source_boundary, source_boundary + num_source_nodes);
|
||||
}
|
||||
|
||||
auto GetDestinationNodes() const
|
||||
{
|
||||
return boost::make_iterator_range(destination_boundary,
|
||||
destination_boundary + num_destination_nodes);
|
||||
}
|
||||
|
||||
CellImpl(const CellData &data,
|
||||
WeightPtrT const all_weights,
|
||||
DurationPtrT const all_durations,
|
||||
const NodeID *const all_sources,
|
||||
const NodeID *const all_destinations)
|
||||
: num_source_nodes{data.num_source_nodes},
|
||||
num_destination_nodes{data.num_destination_nodes},
|
||||
weights{all_weights + data.value_offset},
|
||||
durations{all_durations + data.value_offset},
|
||||
source_boundary{all_sources + data.source_boundary_offset},
|
||||
destination_boundary{all_destinations + data.destination_boundary_offset}
|
||||
{
|
||||
BOOST_ASSERT(all_weights != nullptr);
|
||||
BOOST_ASSERT(all_durations != nullptr);
|
||||
BOOST_ASSERT(num_source_nodes == 0 || all_sources != nullptr);
|
||||
BOOST_ASSERT(num_destination_nodes == 0 || all_destinations != nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
std::size_t LevelIDToIndex(LevelID level) const { return level - 1; }
|
||||
|
||||
public:
|
||||
using Cell = CellImpl<EdgeWeight, EdgeDuration>;
|
||||
using ConstCell = CellImpl<const EdgeWeight, const EdgeDuration>;
|
||||
|
||||
CellStorageImpl() {}
|
||||
|
||||
template <typename GraphT,
|
||||
typename = std::enable_if<Ownership == storage::Ownership::Container>>
|
||||
CellStorageImpl(const partitioner::MultiLevelPartition &partition, const GraphT &base_graph)
|
||||
{
|
||||
// pre-allocate storge for CellData so we can have random access to it by cell id
|
||||
unsigned number_of_cells = 0;
|
||||
for (LevelID level = 1u; level < partition.GetNumberOfLevels(); ++level)
|
||||
{
|
||||
level_to_cell_offset.push_back(number_of_cells);
|
||||
number_of_cells += partition.GetNumberOfCells(level);
|
||||
}
|
||||
level_to_cell_offset.push_back(number_of_cells);
|
||||
cells.resize(number_of_cells);
|
||||
|
||||
std::vector<std::pair<CellID, NodeID>> level_source_boundary;
|
||||
std::vector<std::pair<CellID, NodeID>> level_destination_boundary;
|
||||
|
||||
std::size_t number_of_unconneced = 0;
|
||||
|
||||
for (LevelID level = 1u; level < partition.GetNumberOfLevels(); ++level)
|
||||
{
|
||||
auto level_offset = level_to_cell_offset[LevelIDToIndex(level)];
|
||||
|
||||
level_source_boundary.clear();
|
||||
level_destination_boundary.clear();
|
||||
|
||||
for (auto node = 0u; node < base_graph.GetNumberOfNodes(); ++node)
|
||||
{
|
||||
const CellID cell_id = partition.GetCell(level, node);
|
||||
bool is_source_node = false;
|
||||
bool is_destination_node = false;
|
||||
bool is_boundary_node = false;
|
||||
|
||||
for (auto edge : base_graph.GetAdjacentEdgeRange(node))
|
||||
{
|
||||
auto other = base_graph.GetTarget(edge);
|
||||
const auto &data = base_graph.GetEdgeData(edge);
|
||||
|
||||
is_boundary_node |= partition.GetCell(level, other) != cell_id;
|
||||
is_source_node |= partition.GetCell(level, other) == cell_id && data.forward;
|
||||
is_destination_node |=
|
||||
partition.GetCell(level, other) == cell_id && data.backward;
|
||||
}
|
||||
|
||||
if (is_boundary_node)
|
||||
{
|
||||
if (is_source_node)
|
||||
level_source_boundary.emplace_back(cell_id, node);
|
||||
if (is_destination_node)
|
||||
level_destination_boundary.emplace_back(cell_id, node);
|
||||
|
||||
// if a node is unconnected we still need to keep it for correctness
|
||||
// this adds it to the destination array to form an "empty" column
|
||||
if (!is_source_node && !is_destination_node)
|
||||
{
|
||||
number_of_unconneced++;
|
||||
util::Log(logWARNING) << "Found unconnected boundary node " << node << "("
|
||||
<< cell_id << ") on level " << (int)level;
|
||||
level_destination_boundary.emplace_back(cell_id, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbb::parallel_sort(level_source_boundary.begin(), level_source_boundary.end());
|
||||
tbb::parallel_sort(level_destination_boundary.begin(),
|
||||
level_destination_boundary.end());
|
||||
|
||||
const auto insert_cell_boundary = [this, level_offset](auto &boundary,
|
||||
auto set_num_nodes_fn,
|
||||
auto set_boundary_offset_fn,
|
||||
auto begin,
|
||||
auto end) {
|
||||
BOOST_ASSERT(std::distance(begin, end) > 0);
|
||||
|
||||
const auto cell_id = begin->first;
|
||||
BOOST_ASSERT(level_offset + cell_id < cells.size());
|
||||
auto &cell = cells[level_offset + cell_id];
|
||||
set_num_nodes_fn(cell, std::distance(begin, end));
|
||||
set_boundary_offset_fn(cell, boundary.size());
|
||||
|
||||
std::transform(begin,
|
||||
end,
|
||||
std::back_inserter(boundary),
|
||||
[](const auto &cell_and_node) { return cell_and_node.second; });
|
||||
};
|
||||
|
||||
util::for_each_range(
|
||||
level_source_boundary.begin(),
|
||||
level_source_boundary.end(),
|
||||
[this, insert_cell_boundary](auto begin, auto end) {
|
||||
insert_cell_boundary(
|
||||
source_boundary,
|
||||
[](auto &cell, auto value) { cell.num_source_nodes = value; },
|
||||
[](auto &cell, auto value) { cell.source_boundary_offset = value; },
|
||||
begin,
|
||||
end);
|
||||
});
|
||||
util::for_each_range(
|
||||
level_destination_boundary.begin(),
|
||||
level_destination_boundary.end(),
|
||||
[this, insert_cell_boundary](auto begin, auto end) {
|
||||
insert_cell_boundary(
|
||||
destination_boundary,
|
||||
[](auto &cell, auto value) { cell.num_destination_nodes = value; },
|
||||
[](auto &cell, auto value) { cell.destination_boundary_offset = value; },
|
||||
begin,
|
||||
end);
|
||||
});
|
||||
}
|
||||
|
||||
// a partition that contains boundary nodes that have no arcs going into
|
||||
// the cells or coming out of it is bad. These nodes should be reassigned
|
||||
// to a different cell.
|
||||
if (number_of_unconneced > 0)
|
||||
{
|
||||
util::Log(logWARNING) << "Node needs to either have incoming or outgoing edges in cell."
|
||||
<< " Number of unconnected nodes is " << number_of_unconneced;
|
||||
}
|
||||
|
||||
// Set cell values offsets and calculate total storage size
|
||||
ValueOffset value_offset = 0;
|
||||
for (auto &cell : cells)
|
||||
{
|
||||
cell.value_offset = value_offset;
|
||||
value_offset += cell.num_source_nodes * cell.num_destination_nodes;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a new metric that can be used with this container
|
||||
customizer::CellMetric MakeMetric() const
|
||||
{
|
||||
customizer::CellMetric metric;
|
||||
|
||||
if (cells.empty())
|
||||
{
|
||||
return metric;
|
||||
}
|
||||
|
||||
const auto &last_cell = cells.back();
|
||||
ValueOffset total_size =
|
||||
last_cell.value_offset + last_cell.num_source_nodes * last_cell.num_destination_nodes;
|
||||
|
||||
metric.weights.resize(total_size + 1, INVALID_EDGE_WEIGHT);
|
||||
metric.durations.resize(total_size + 1, MAXIMAL_EDGE_DURATION);
|
||||
|
||||
return metric;
|
||||
}
|
||||
|
||||
template <typename = std::enable_if<Ownership == storage::Ownership::View>>
|
||||
CellStorageImpl(Vector<NodeID> source_boundary_,
|
||||
Vector<NodeID> destination_boundary_,
|
||||
Vector<CellData> cells_,
|
||||
Vector<std::uint64_t> level_to_cell_offset_)
|
||||
: source_boundary(std::move(source_boundary_)),
|
||||
destination_boundary(std::move(destination_boundary_)), cells(std::move(cells_)),
|
||||
level_to_cell_offset(std::move(level_to_cell_offset_))
|
||||
{
|
||||
}
|
||||
|
||||
ConstCell GetCell(const customizer::detail::CellMetricImpl<Ownership> &metric,
|
||||
LevelID level,
|
||||
CellID id) const
|
||||
{
|
||||
const auto level_index = LevelIDToIndex(level);
|
||||
BOOST_ASSERT(level_index < level_to_cell_offset.size());
|
||||
const auto offset = level_to_cell_offset[level_index];
|
||||
const auto cell_index = offset + id;
|
||||
BOOST_ASSERT(cell_index < cells.size());
|
||||
return ConstCell{cells[cell_index],
|
||||
metric.weights.data(),
|
||||
metric.durations.data(),
|
||||
source_boundary.empty() ? nullptr : source_boundary.data(),
|
||||
destination_boundary.empty() ? nullptr : destination_boundary.data()};
|
||||
}
|
||||
|
||||
template <typename = std::enable_if<Ownership == storage::Ownership::Container>>
|
||||
Cell GetCell(customizer::CellMetric &metric, LevelID level, CellID id) const
|
||||
{
|
||||
const auto level_index = LevelIDToIndex(level);
|
||||
BOOST_ASSERT(level_index < level_to_cell_offset.size());
|
||||
const auto offset = level_to_cell_offset[level_index];
|
||||
const auto cell_index = offset + id;
|
||||
BOOST_ASSERT(cell_index < cells.size());
|
||||
return Cell{cells[cell_index],
|
||||
metric.weights.data(),
|
||||
metric.durations.data(),
|
||||
source_boundary.data(),
|
||||
destination_boundary.data()};
|
||||
}
|
||||
|
||||
friend void serialization::read<Ownership>(storage::io::FileReader &reader,
|
||||
detail::CellStorageImpl<Ownership> &storage);
|
||||
friend void serialization::write<Ownership>(storage::io::FileWriter &writer,
|
||||
const detail::CellStorageImpl<Ownership> &storage);
|
||||
|
||||
private:
|
||||
Vector<NodeID> source_boundary;
|
||||
Vector<NodeID> destination_boundary;
|
||||
Vector<CellData> cells;
|
||||
Vector<std::uint64_t> level_to_cell_offset;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // OSRM_PARTITIONER_CUSTOMIZE_CELL_STORAGE_HPP
|
||||
@@ -0,0 +1,60 @@
|
||||
#ifndef OSRM_PARTITIONER_COMPRESSED_NODE_BASED_GRAPH_READER_HPP
|
||||
#define OSRM_PARTITIONER_COMPRESSED_NODE_BASED_GRAPH_READER_HPP
|
||||
|
||||
#include "storage/io.hpp"
|
||||
#include "util/coordinate.hpp"
|
||||
#include "util/typedefs.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
|
||||
struct CompressedNodeBasedGraphEdge
|
||||
{
|
||||
NodeID source;
|
||||
NodeID target;
|
||||
};
|
||||
|
||||
struct CompressedNodeBasedGraph
|
||||
{
|
||||
CompressedNodeBasedGraph(storage::io::FileReader &reader)
|
||||
{
|
||||
// Reads: | Fingerprint | #e | #n | edges | coordinates |
|
||||
// - uint64: number of edges (from, to) pairs
|
||||
// - uint64: number of nodes and therefore also coordinates
|
||||
// - (uint32_t, uint32_t): num_edges * edges
|
||||
// - (int32_t, int32_t: num_nodes * coordinates (lon, lat)
|
||||
//
|
||||
// Gets written in Extractor::WriteCompressedNodeBasedGraph
|
||||
|
||||
const auto num_edges = reader.ReadElementCount64();
|
||||
const auto num_nodes = reader.ReadElementCount64();
|
||||
|
||||
edges.resize(num_edges);
|
||||
coordinates.resize(num_nodes);
|
||||
|
||||
reader.ReadInto(edges);
|
||||
reader.ReadInto(coordinates);
|
||||
}
|
||||
|
||||
std::vector<CompressedNodeBasedGraphEdge> edges;
|
||||
std::vector<util::Coordinate> coordinates;
|
||||
};
|
||||
|
||||
inline CompressedNodeBasedGraph LoadCompressedNodeBasedGraph(const std::string &path)
|
||||
{
|
||||
const auto fingerprint = storage::io::FileReader::VerifyFingerprint;
|
||||
storage::io::FileReader reader(path, fingerprint);
|
||||
|
||||
CompressedNodeBasedGraph graph{reader};
|
||||
return graph;
|
||||
}
|
||||
|
||||
} // ns partition
|
||||
} // ns osrm
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,97 @@
|
||||
#ifndef OSRM_PARTITIONER_DINIC_MAX_FLOW_HPP_
|
||||
#define OSRM_PARTITIONER_DINIC_MAX_FLOW_HPP_
|
||||
|
||||
#include "partitioner/bisection_graph_view.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
|
||||
class DinicMaxFlow
|
||||
{
|
||||
public:
|
||||
// maximal number of hops in the graph from source to sink
|
||||
using Level = std::uint32_t;
|
||||
|
||||
using MinCut = struct
|
||||
{
|
||||
std::size_t num_nodes_source;
|
||||
std::size_t num_edges;
|
||||
std::vector<bool> flags;
|
||||
};
|
||||
|
||||
// input parameter storing the set o
|
||||
using SourceSinkNodes = std::unordered_set<NodeID>;
|
||||
|
||||
MinCut operator()(const BisectionGraphView &view,
|
||||
const SourceSinkNodes &source_nodes,
|
||||
const SourceSinkNodes &sink_nodes) const;
|
||||
|
||||
// validates the inpiut parameters to the flow algorithm (e.g. not intersecting)
|
||||
bool Validate(const BisectionGraphView &view,
|
||||
const SourceSinkNodes &source_nodes,
|
||||
const SourceSinkNodes &sink_nodes) const;
|
||||
|
||||
private:
|
||||
// the level of each node in the graph (==hops in BFS from source)
|
||||
using LevelGraph = std::vector<Level>;
|
||||
|
||||
// this is actually faster than using an unordered_set<Edge>, stores all edges that have
|
||||
// capacity grouped by node
|
||||
using FlowEdges = std::vector<std::set<NodeID>>;
|
||||
|
||||
// The level graph (see [1]) is based on a BFS computation. We assign a level to all nodes
|
||||
// (starting with 0 for all source nodes) and assign the hop distance in the residual graph as
|
||||
// the level of the node.
|
||||
// a
|
||||
// / \
|
||||
// s t
|
||||
// \ /
|
||||
// b
|
||||
// would assign s = 0, a,b = 1, t=2
|
||||
LevelGraph ComputeLevelGraph(const BisectionGraphView &view,
|
||||
const std::vector<NodeID> &border_source_nodes,
|
||||
const SourceSinkNodes &source_nodes,
|
||||
const SourceSinkNodes &sink_nodes,
|
||||
const FlowEdges &flow) const;
|
||||
|
||||
// Using the above levels (see ComputeLevelGraph), we can use multiple DFS (that can now be
|
||||
// directed at the sink) to find a flow that completely blocks the level graph (i.e. no path
|
||||
// with increasing level exists from `s` to `t`).
|
||||
std::size_t BlockingFlow(FlowEdges &flow,
|
||||
LevelGraph &levels,
|
||||
const BisectionGraphView &view,
|
||||
const SourceSinkNodes &source_nodes,
|
||||
const std::vector<NodeID> &border_sink_nodes) const;
|
||||
|
||||
// Finds a single augmenting path from a node to the sink side following levels in the level
|
||||
// graph. We don't actually remove the edges, so we have to check for increasing level values.
|
||||
// Since we know which sinks have been reached, we actually search for these paths starting at
|
||||
// sink nodes, instead of the source, so we can save a few dfs runs
|
||||
std::vector<NodeID> GetAugmentingPath(LevelGraph &levels,
|
||||
const NodeID from,
|
||||
const BisectionGraphView &view,
|
||||
const FlowEdges &flow,
|
||||
const SourceSinkNodes &source_nodes) const;
|
||||
|
||||
// Builds an actual cut result from a level graph
|
||||
MinCut MakeCut(const BisectionGraphView &view,
|
||||
const LevelGraph &levels,
|
||||
const std::size_t flow_value) const;
|
||||
};
|
||||
|
||||
} // namespace partitioner
|
||||
} // namespace osrm
|
||||
|
||||
// Implementation of Dinics [1] algorithm for max-flow/min-cut.
|
||||
// [1] https://www.cs.bgu.ac.il/~dinitz/D70.pdf
|
||||
|
||||
#endif // OSRM_PARTITIONER_DINIC_MAX_FLOW_HPP_
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef OSRM_PARTITIONER_EDGE_BASED_GRAPH_HPP
|
||||
#define OSRM_PARTITIONER_EDGE_BASED_GRAPH_HPP
|
||||
|
||||
#include "extractor/edge_based_edge.hpp"
|
||||
#include "storage/io.hpp"
|
||||
#include "util/coordinate.hpp"
|
||||
#include "util/dynamic_graph.hpp"
|
||||
#include "util/typedefs.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
|
||||
struct EdgeBasedGraphEdgeData : extractor::EdgeBasedEdge::EdgeData
|
||||
{
|
||||
using Base = extractor::EdgeBasedEdge::EdgeData;
|
||||
using Base::Base;
|
||||
|
||||
EdgeBasedGraphEdgeData(const EdgeBasedGraphEdgeData &) = default;
|
||||
EdgeBasedGraphEdgeData(EdgeBasedGraphEdgeData &&) = default;
|
||||
EdgeBasedGraphEdgeData &operator=(const EdgeBasedGraphEdgeData &) = default;
|
||||
EdgeBasedGraphEdgeData &operator=(EdgeBasedGraphEdgeData &&) = default;
|
||||
EdgeBasedGraphEdgeData(const Base &base) : Base(base) {}
|
||||
EdgeBasedGraphEdgeData() : Base() {}
|
||||
};
|
||||
|
||||
struct DynamicEdgeBasedGraph : util::DynamicGraph<EdgeBasedGraphEdgeData>
|
||||
{
|
||||
using Base = util::DynamicGraph<EdgeBasedGraphEdgeData>;
|
||||
using Base::Base;
|
||||
};
|
||||
|
||||
struct DynamicEdgeBasedGraphEdge : DynamicEdgeBasedGraph::InputEdge
|
||||
{
|
||||
using Base = DynamicEdgeBasedGraph::InputEdge;
|
||||
using Base::Base;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,201 @@
|
||||
#ifndef OSRM_PARTITIONER_EDGE_BASED_GRAPH_READER_HPP
|
||||
#define OSRM_PARTITIONER_EDGE_BASED_GRAPH_READER_HPP
|
||||
|
||||
#include "partitioner/edge_based_graph.hpp"
|
||||
|
||||
#include "extractor/edge_based_edge.hpp"
|
||||
#include "extractor/files.hpp"
|
||||
#include "storage/io.hpp"
|
||||
#include "util/coordinate.hpp"
|
||||
#include "util/dynamic_graph.hpp"
|
||||
#include "util/typedefs.hpp"
|
||||
|
||||
#include <tbb/blocked_range.h>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_reduce.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
|
||||
// Bidirectional (s,t) to (s,t) and (t,s)
|
||||
inline std::vector<extractor::EdgeBasedEdge>
|
||||
splitBidirectionalEdges(const std::vector<extractor::EdgeBasedEdge> &edges)
|
||||
{
|
||||
std::vector<extractor::EdgeBasedEdge> directed;
|
||||
directed.reserve(edges.size() * 2);
|
||||
|
||||
for (const auto &edge : edges)
|
||||
{
|
||||
if (edge.data.weight == INVALID_EDGE_WEIGHT)
|
||||
continue;
|
||||
|
||||
directed.emplace_back(edge.source,
|
||||
edge.target,
|
||||
edge.data.turn_id,
|
||||
std::max(edge.data.weight, 1),
|
||||
edge.data.duration,
|
||||
edge.data.forward,
|
||||
edge.data.backward);
|
||||
|
||||
directed.emplace_back(edge.target,
|
||||
edge.source,
|
||||
edge.data.turn_id,
|
||||
std::max(edge.data.weight, 1),
|
||||
edge.data.duration,
|
||||
edge.data.backward,
|
||||
edge.data.forward);
|
||||
}
|
||||
|
||||
return directed;
|
||||
}
|
||||
|
||||
template <typename OutputEdgeT>
|
||||
std::vector<OutputEdgeT> prepareEdgesForUsageInGraph(std::vector<extractor::EdgeBasedEdge> edges)
|
||||
{
|
||||
// sort into blocks of edges with same source + target
|
||||
// the we partition by the forward flag to sort all edges with a forward direction first.
|
||||
// the we sort by weight to ensure the first forward edge is the smallest forward edge
|
||||
std::sort(begin(edges), end(edges), [](const auto &lhs, const auto &rhs) {
|
||||
return std::tie(lhs.source, lhs.target, rhs.data.forward, lhs.data.weight) <
|
||||
std::tie(rhs.source, rhs.target, lhs.data.forward, rhs.data.weight);
|
||||
});
|
||||
|
||||
std::vector<OutputEdgeT> output_edges;
|
||||
output_edges.reserve(edges.size());
|
||||
|
||||
for (auto begin_interval = edges.begin(); begin_interval != edges.end();)
|
||||
{
|
||||
const NodeID source = begin_interval->source;
|
||||
const NodeID target = begin_interval->target;
|
||||
|
||||
auto end_interval =
|
||||
std::find_if_not(begin_interval, edges.end(), [source, target](const auto &edge) {
|
||||
return std::tie(edge.source, edge.target) == std::tie(source, target);
|
||||
});
|
||||
BOOST_ASSERT(begin_interval != end_interval);
|
||||
|
||||
// remove eigenloops
|
||||
if (source == target)
|
||||
{
|
||||
begin_interval = end_interval;
|
||||
continue;
|
||||
}
|
||||
|
||||
BOOST_ASSERT_MSG(begin_interval->data.forward != begin_interval->data.backward,
|
||||
"The forward and backward flag need to be mutally exclusive");
|
||||
|
||||
// find smallest backward edge and check if we can merge
|
||||
auto first_backward = std::find_if(
|
||||
begin_interval, end_interval, [](const auto &edge) { return edge.data.backward; });
|
||||
|
||||
// thanks to the sorting we know this is the smallest backward edge
|
||||
// and there is no forward edge
|
||||
if (begin_interval == first_backward)
|
||||
{
|
||||
output_edges.push_back(OutputEdgeT{source, target, first_backward->data});
|
||||
}
|
||||
// only a forward edge, thanks to the sorting this is the smallest
|
||||
else if (first_backward == end_interval)
|
||||
{
|
||||
output_edges.push_back(OutputEdgeT{source, target, begin_interval->data});
|
||||
}
|
||||
// we have both a forward and a backward edge, we need to evaluate
|
||||
// if we can merge them
|
||||
else
|
||||
{
|
||||
BOOST_ASSERT(begin_interval->data.forward);
|
||||
BOOST_ASSERT(first_backward->data.backward);
|
||||
BOOST_ASSERT(first_backward != end_interval);
|
||||
|
||||
// same weight, so we can just merge them
|
||||
if (begin_interval->data.weight == first_backward->data.weight)
|
||||
{
|
||||
OutputEdgeT merged{source, target, begin_interval->data};
|
||||
merged.data.backward = true;
|
||||
output_edges.push_back(std::move(merged));
|
||||
}
|
||||
// we need to insert separate forward and reverse edges
|
||||
else
|
||||
{
|
||||
output_edges.push_back(OutputEdgeT{source, target, begin_interval->data});
|
||||
output_edges.push_back(OutputEdgeT{source, target, first_backward->data});
|
||||
}
|
||||
}
|
||||
|
||||
begin_interval = end_interval;
|
||||
}
|
||||
|
||||
return output_edges;
|
||||
}
|
||||
|
||||
inline std::vector<extractor::EdgeBasedEdge>
|
||||
graphToEdges(const DynamicEdgeBasedGraph &edge_based_graph)
|
||||
{
|
||||
auto range = tbb::blocked_range<NodeID>(0, edge_based_graph.GetNumberOfNodes());
|
||||
auto max_turn_id =
|
||||
tbb::parallel_reduce(range,
|
||||
NodeID{0},
|
||||
[&edge_based_graph](const auto range, NodeID initial) {
|
||||
NodeID max_turn_id = initial;
|
||||
for (auto node = range.begin(); node < range.end(); ++node)
|
||||
{
|
||||
for (auto edge : edge_based_graph.GetAdjacentEdgeRange(node))
|
||||
{
|
||||
const auto &data = edge_based_graph.GetEdgeData(edge);
|
||||
max_turn_id = std::max(max_turn_id, data.turn_id);
|
||||
}
|
||||
}
|
||||
return max_turn_id;
|
||||
},
|
||||
[](const NodeID lhs, const NodeID rhs) { return std::max(lhs, rhs); });
|
||||
|
||||
std::vector<extractor::EdgeBasedEdge> edges(max_turn_id + 1);
|
||||
tbb::parallel_for(range, [&](const auto range) {
|
||||
for (auto node = range.begin(); node < range.end(); ++node)
|
||||
{
|
||||
for (auto edge : edge_based_graph.GetAdjacentEdgeRange(node))
|
||||
{
|
||||
const auto &data = edge_based_graph.GetEdgeData(edge);
|
||||
// we only need to save the forward edges, since the read method will
|
||||
// convert from forward to bi-directional edges again
|
||||
if (data.forward)
|
||||
{
|
||||
auto target = edge_based_graph.GetTarget(edge);
|
||||
BOOST_ASSERT(data.turn_id <= max_turn_id);
|
||||
edges[data.turn_id] = extractor::EdgeBasedEdge{node, target, data};
|
||||
// only save the forward edge
|
||||
edges[data.turn_id].data.forward = true;
|
||||
edges[data.turn_id].data.backward = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return edges;
|
||||
}
|
||||
|
||||
inline DynamicEdgeBasedGraph LoadEdgeBasedGraph(const boost::filesystem::path &path)
|
||||
{
|
||||
EdgeID number_of_edge_based_nodes;
|
||||
std::vector<extractor::EdgeBasedEdge> edges;
|
||||
extractor::files::readEdgeBasedGraph(path, number_of_edge_based_nodes, edges);
|
||||
|
||||
auto directed = splitBidirectionalEdges(edges);
|
||||
auto tidied = prepareEdgesForUsageInGraph<DynamicEdgeBasedGraphEdge>(std::move(directed));
|
||||
|
||||
return DynamicEdgeBasedGraph(number_of_edge_based_nodes, std::move(tidied));
|
||||
}
|
||||
|
||||
} // ns partition
|
||||
} // ns osrm
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,104 @@
|
||||
#ifndef OSRM_PARTITIONER_SERILIZATION_HPP
|
||||
#define OSRM_PARTITIONER_SERILIZATION_HPP
|
||||
|
||||
#include "customizer/edge_based_graph.hpp"
|
||||
|
||||
#include "partitioner/serialization.hpp"
|
||||
|
||||
#include "storage/io.hpp"
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
namespace files
|
||||
{
|
||||
|
||||
// reads .osrm.mldgr file
|
||||
template <typename MultiLevelGraphT>
|
||||
inline void readGraph(const boost::filesystem::path &path, MultiLevelGraphT &graph)
|
||||
{
|
||||
static_assert(std::is_same<customizer::MultiLevelEdgeBasedGraphView, MultiLevelGraphT>::value ||
|
||||
std::is_same<customizer::MultiLevelEdgeBasedGraph, MultiLevelGraphT>::value,
|
||||
"");
|
||||
|
||||
const auto fingerprint = storage::io::FileReader::VerifyFingerprint;
|
||||
storage::io::FileReader reader{path, fingerprint};
|
||||
|
||||
serialization::read(reader, graph);
|
||||
}
|
||||
|
||||
// writes .osrm.mldgr file
|
||||
template <typename MultiLevelGraphT>
|
||||
inline void writeGraph(const boost::filesystem::path &path, const MultiLevelGraphT &graph)
|
||||
{
|
||||
static_assert(std::is_same<customizer::MultiLevelEdgeBasedGraphView, MultiLevelGraphT>::value ||
|
||||
std::is_same<customizer::MultiLevelEdgeBasedGraph, MultiLevelGraphT>::value,
|
||||
"");
|
||||
|
||||
const auto fingerprint = storage::io::FileWriter::GenerateFingerprint;
|
||||
storage::io::FileWriter writer{path, fingerprint};
|
||||
|
||||
serialization::write(writer, graph);
|
||||
}
|
||||
|
||||
// read .osrm.partition file
|
||||
template <typename MultiLevelPartitionT>
|
||||
inline void readPartition(const boost::filesystem::path &path, MultiLevelPartitionT &mlp)
|
||||
{
|
||||
static_assert(std::is_same<MultiLevelPartitionView, MultiLevelPartitionT>::value ||
|
||||
std::is_same<MultiLevelPartition, MultiLevelPartitionT>::value,
|
||||
"");
|
||||
|
||||
const auto fingerprint = storage::io::FileReader::VerifyFingerprint;
|
||||
storage::io::FileReader reader{path, fingerprint};
|
||||
|
||||
serialization::read(reader, mlp);
|
||||
}
|
||||
|
||||
// writes .osrm.partition file
|
||||
template <typename MultiLevelPartitionT>
|
||||
inline void writePartition(const boost::filesystem::path &path, const MultiLevelPartitionT &mlp)
|
||||
{
|
||||
static_assert(std::is_same<MultiLevelPartitionView, MultiLevelPartitionT>::value ||
|
||||
std::is_same<MultiLevelPartition, MultiLevelPartitionT>::value,
|
||||
"");
|
||||
|
||||
const auto fingerprint = storage::io::FileWriter::GenerateFingerprint;
|
||||
storage::io::FileWriter writer{path, fingerprint};
|
||||
|
||||
serialization::write(writer, mlp);
|
||||
}
|
||||
|
||||
// reads .osrm.cells file
|
||||
template <typename CellStorageT>
|
||||
inline void readCells(const boost::filesystem::path &path, CellStorageT &storage)
|
||||
{
|
||||
static_assert(std::is_same<CellStorageView, CellStorageT>::value ||
|
||||
std::is_same<CellStorage, CellStorageT>::value,
|
||||
"");
|
||||
|
||||
const auto fingerprint = storage::io::FileReader::VerifyFingerprint;
|
||||
storage::io::FileReader reader{path, fingerprint};
|
||||
|
||||
serialization::read(reader, storage);
|
||||
}
|
||||
|
||||
// writes .osrm.cells file
|
||||
template <typename CellStorageT>
|
||||
inline void writeCells(const boost::filesystem::path &path, CellStorageT &storage)
|
||||
{
|
||||
static_assert(std::is_same<CellStorageView, CellStorageT>::value ||
|
||||
std::is_same<CellStorage, CellStorageT>::value,
|
||||
"");
|
||||
|
||||
const auto fingerprint = storage::io::FileWriter::GenerateFingerprint;
|
||||
storage::io::FileWriter writer{path, fingerprint};
|
||||
|
||||
serialization::write(writer, storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef OSRM_PARTITIONER_INERTIAL_FLOW_HPP_
|
||||
#define OSRM_PARTITIONER_INERTIAL_FLOW_HPP_
|
||||
|
||||
#include "partitioner/bisection_graph_view.hpp"
|
||||
#include "partitioner/dinic_max_flow.hpp"
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
|
||||
DinicMaxFlow::MinCut computeInertialFlowCut(const BisectionGraphView &view,
|
||||
const std::size_t num_slopes,
|
||||
const double balance,
|
||||
const double source_sink_rate);
|
||||
|
||||
} // namespace partitioner
|
||||
} // namespace osrm
|
||||
|
||||
#endif // OSRM_PARTITIONER_INERTIAL_FLOW_HPP_
|
||||
@@ -0,0 +1,212 @@
|
||||
#ifndef OSRM_PARTITIONER_MULTI_LEVEL_GRAPH_HPP
|
||||
#define OSRM_PARTITIONER_MULTI_LEVEL_GRAPH_HPP
|
||||
|
||||
#include "partitioner/multi_level_partition.hpp"
|
||||
|
||||
#include "storage/io_fwd.hpp"
|
||||
#include "storage/shared_memory_ownership.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
|
||||
{
|
||||
|
||||
namespace partitioner
|
||||
{
|
||||
template <typename EdgeDataT, storage::Ownership Ownership> class MultiLevelGraph;
|
||||
|
||||
namespace serialization
|
||||
{
|
||||
template <typename EdgeDataT, storage::Ownership Ownership>
|
||||
void read(storage::io::FileReader &reader, MultiLevelGraph<EdgeDataT, Ownership> &graph);
|
||||
|
||||
template <typename EdgeDataT, storage::Ownership Ownership>
|
||||
void write(storage::io::FileWriter &writer, const MultiLevelGraph<EdgeDataT, Ownership> &graph);
|
||||
}
|
||||
|
||||
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:
|
||||
// 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(); }
|
||||
|
||||
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::io::FileReader &reader,
|
||||
MultiLevelGraph<EdgeDataT, Ownership> &graph);
|
||||
friend void
|
||||
serialization::write<EdgeDataT, Ownership>(storage::io::FileWriter &writer,
|
||||
const MultiLevelGraph<EdgeDataT, Ownership> &graph);
|
||||
|
||||
Vector<EdgeOffset> node_to_edge_offset;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,339 @@
|
||||
#ifndef OSRM_PARTITIONER_MULTI_LEVEL_PARTITION_HPP
|
||||
#define OSRM_PARTITIONER_MULTI_LEVEL_PARTITION_HPP
|
||||
|
||||
#include "util/exception.hpp"
|
||||
#include "util/for_each_pair.hpp"
|
||||
#include "util/msb.hpp"
|
||||
#include "util/typedefs.hpp"
|
||||
#include "util/vector_view.hpp"
|
||||
|
||||
#include "storage/io_fwd.hpp"
|
||||
#include "storage/shared_memory_ownership.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template <storage::Ownership Ownership> class MultiLevelPartitionImpl;
|
||||
}
|
||||
using MultiLevelPartition = detail::MultiLevelPartitionImpl<storage::Ownership::Container>;
|
||||
using MultiLevelPartitionView = detail::MultiLevelPartitionImpl<storage::Ownership::View>;
|
||||
|
||||
namespace serialization
|
||||
{
|
||||
template <storage::Ownership Ownership>
|
||||
void read(storage::io::FileReader &reader, detail::MultiLevelPartitionImpl<Ownership> &mlp);
|
||||
template <storage::Ownership Ownership>
|
||||
void write(storage::io::FileWriter &writer, const detail::MultiLevelPartitionImpl<Ownership> &mlp);
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template <storage::Ownership Ownership> class MultiLevelPartitionImpl final
|
||||
{
|
||||
// we will support at most 16 levels
|
||||
static const constexpr std::uint8_t MAX_NUM_LEVEL = 16;
|
||||
static const constexpr std::uint8_t NUM_PARTITION_BITS = sizeof(PartitionID) * CHAR_BIT;
|
||||
|
||||
template <typename T> using Vector = util::ViewOrVector<T, Ownership>;
|
||||
|
||||
public:
|
||||
// Contains all data necessary to describe the level hierarchy
|
||||
struct LevelData
|
||||
{
|
||||
|
||||
std::uint32_t num_level;
|
||||
std::array<std::uint8_t, MAX_NUM_LEVEL - 1> lidx_to_offset;
|
||||
std::array<PartitionID, MAX_NUM_LEVEL - 1> lidx_to_mask;
|
||||
std::array<LevelID, NUM_PARTITION_BITS> bit_to_level;
|
||||
std::array<std::uint32_t, MAX_NUM_LEVEL - 1> lidx_to_children_offsets;
|
||||
};
|
||||
using LevelDataPtr = typename std::conditional<Ownership == storage::Ownership::View,
|
||||
LevelData *,
|
||||
std::unique_ptr<LevelData>>::type;
|
||||
|
||||
MultiLevelPartitionImpl();
|
||||
|
||||
// cell_sizes is index by level (starting at 0, the base graph).
|
||||
// However level 0 always needs to have cell size 1, since it is the
|
||||
// basegraph.
|
||||
template <typename = typename std::enable_if<Ownership == storage::Ownership::Container>>
|
||||
MultiLevelPartitionImpl(const std::vector<std::vector<CellID>> &partitions,
|
||||
const std::vector<std::uint32_t> &lidx_to_num_cells)
|
||||
: level_data(MakeLevelData(lidx_to_num_cells))
|
||||
{
|
||||
InitializePartitionIDs(partitions);
|
||||
}
|
||||
|
||||
template <typename = typename std::enable_if<Ownership == storage::Ownership::View>>
|
||||
MultiLevelPartitionImpl(LevelDataPtr level_data,
|
||||
Vector<PartitionID> partition_,
|
||||
Vector<CellID> cell_to_children_)
|
||||
: level_data(std::move(level_data)), partition(std::move(partition_)),
|
||||
cell_to_children(std::move(cell_to_children_))
|
||||
{
|
||||
}
|
||||
|
||||
// returns the index of the cell the vertex is contained at level l
|
||||
CellID GetCell(LevelID l, NodeID node) const
|
||||
{
|
||||
auto p = partition[node];
|
||||
auto lidx = LevelIDToIndex(l);
|
||||
auto masked = p & level_data->lidx_to_mask[lidx];
|
||||
return masked >> level_data->lidx_to_offset[lidx];
|
||||
}
|
||||
|
||||
LevelID GetQueryLevel(NodeID start, NodeID target, NodeID node) const
|
||||
{
|
||||
return std::min(GetHighestDifferentLevel(start, node),
|
||||
GetHighestDifferentLevel(target, node));
|
||||
}
|
||||
|
||||
LevelID GetHighestDifferentLevel(NodeID first, NodeID second) const
|
||||
{
|
||||
if (partition[first] == partition[second])
|
||||
return 0;
|
||||
|
||||
auto msb = util::msb(partition[first] ^ partition[second]);
|
||||
return level_data->bit_to_level[msb];
|
||||
}
|
||||
|
||||
std::uint8_t GetNumberOfLevels() const { return level_data->num_level; }
|
||||
|
||||
std::uint32_t GetNumberOfCells(LevelID level) const
|
||||
{
|
||||
return GetCell(level, GetSentinelNode());
|
||||
}
|
||||
|
||||
// Returns the lowest cell id (at `level - 1`) of all children `cell` at `level`
|
||||
CellID BeginChildren(LevelID level, CellID cell) const
|
||||
{
|
||||
BOOST_ASSERT(level > 1);
|
||||
auto lidx = LevelIDToIndex(level);
|
||||
auto offset = level_data->lidx_to_children_offsets[lidx];
|
||||
return cell_to_children[offset + cell];
|
||||
}
|
||||
|
||||
// Returns the highest cell id (at `level - 1`) of all children `cell` at `level`
|
||||
CellID EndChildren(LevelID level, CellID cell) const
|
||||
{
|
||||
BOOST_ASSERT(level > 1);
|
||||
auto lidx = LevelIDToIndex(level);
|
||||
auto offset = level_data->lidx_to_children_offsets[lidx];
|
||||
return cell_to_children[offset + cell + 1];
|
||||
}
|
||||
|
||||
friend void serialization::read<Ownership>(storage::io::FileReader &reader,
|
||||
MultiLevelPartitionImpl &mlp);
|
||||
friend void serialization::write<Ownership>(storage::io::FileWriter &writer,
|
||||
const MultiLevelPartitionImpl &mlp);
|
||||
|
||||
private:
|
||||
auto MakeLevelData(const std::vector<std::uint32_t> &lidx_to_num_cells)
|
||||
{
|
||||
std::uint32_t num_level = lidx_to_num_cells.size() + 1;
|
||||
auto offsets = MakeLevelOffsets(lidx_to_num_cells);
|
||||
auto masks = MakeLevelMasks(offsets, num_level);
|
||||
auto bits = MakeBitToLevel(offsets, num_level);
|
||||
return std::make_unique<LevelData>(LevelData{num_level, offsets, masks, bits, {{0}}});
|
||||
}
|
||||
|
||||
inline std::size_t LevelIDToIndex(LevelID l) const { return l - 1; }
|
||||
|
||||
// We save the sentinel as last node in the partition information.
|
||||
// It has the highest cell id in each level so we can derived the range
|
||||
// of cell ids efficiently.
|
||||
inline NodeID GetSentinelNode() const { return partition.size() - 1; }
|
||||
|
||||
void SetCellID(LevelID l, NodeID node, std::size_t cell_id)
|
||||
{
|
||||
auto lidx = LevelIDToIndex(l);
|
||||
|
||||
auto shifted_id = cell_id << level_data->lidx_to_offset[lidx];
|
||||
auto cleared_cell = partition[node] & ~level_data->lidx_to_mask[lidx];
|
||||
partition[node] = cleared_cell | shifted_id;
|
||||
}
|
||||
|
||||
// If we have N cells per level we need log_2 bits for every cell ID
|
||||
auto MakeLevelOffsets(const std::vector<std::uint32_t> &lidx_to_num_cells) const
|
||||
{
|
||||
std::array<std::uint8_t, MAX_NUM_LEVEL - 1> offsets;
|
||||
|
||||
auto lidx = 0UL;
|
||||
auto sum_bits = 0;
|
||||
for (auto num_cells : lidx_to_num_cells)
|
||||
{
|
||||
// bits needed to number all contained vertexes
|
||||
auto bits = static_cast<std::uint64_t>(std::ceil(std::log2(num_cells + 1)));
|
||||
offsets[lidx++] = sum_bits;
|
||||
sum_bits += bits;
|
||||
if (sum_bits > 64)
|
||||
{
|
||||
throw util::exception(
|
||||
"Can't pack the partition information at level " + std::to_string(lidx) +
|
||||
" into a 64bit integer. Would require " + std::to_string(sum_bits) + " bits.");
|
||||
}
|
||||
}
|
||||
// sentinel
|
||||
offsets[lidx++] = sum_bits;
|
||||
BOOST_ASSERT(lidx < MAX_NUM_LEVEL);
|
||||
|
||||
return offsets;
|
||||
}
|
||||
|
||||
auto MakeLevelMasks(const std::array<std::uint8_t, MAX_NUM_LEVEL - 1> &level_offsets,
|
||||
std::uint32_t num_level) const
|
||||
{
|
||||
std::array<PartitionID, MAX_NUM_LEVEL - 1> masks;
|
||||
|
||||
auto lidx = 0UL;
|
||||
util::for_each_pair(level_offsets.begin(),
|
||||
level_offsets.begin() + num_level,
|
||||
[&](const auto offset, const auto next_offset) {
|
||||
// create mask that has `bits` ones at its LSBs.
|
||||
// 000011
|
||||
BOOST_ASSERT(offset < NUM_PARTITION_BITS);
|
||||
PartitionID mask = (1UL << offset) - 1UL;
|
||||
// 001111
|
||||
BOOST_ASSERT(next_offset < NUM_PARTITION_BITS);
|
||||
PartitionID next_mask = (1UL << next_offset) - 1UL;
|
||||
// 001100
|
||||
masks[lidx++] = next_mask ^ mask;
|
||||
});
|
||||
|
||||
return masks;
|
||||
}
|
||||
|
||||
auto MakeBitToLevel(const std::array<std::uint8_t, MAX_NUM_LEVEL - 1> &level_offsets,
|
||||
std::uint32_t num_level) const
|
||||
{
|
||||
std::array<LevelID, NUM_PARTITION_BITS> bit_to_level;
|
||||
|
||||
for (auto l = 1u; l < num_level; ++l)
|
||||
{
|
||||
auto bits = level_offsets[l - 1];
|
||||
// set all bits to point to the correct level.
|
||||
for (auto idx = bits; idx < NUM_PARTITION_BITS; ++idx)
|
||||
{
|
||||
bit_to_level[idx] = l;
|
||||
}
|
||||
}
|
||||
|
||||
return bit_to_level;
|
||||
}
|
||||
|
||||
void InitializePartitionIDs(const std::vector<std::vector<CellID>> &partitions)
|
||||
{
|
||||
auto num_nodes = partitions.front().size();
|
||||
std::vector<NodeID> permutation(num_nodes);
|
||||
std::iota(permutation.begin(), permutation.end(), 0);
|
||||
// We include a sentinel element at the end of the partition
|
||||
partition.resize(num_nodes + 1, 0);
|
||||
NodeID sentinel = num_nodes;
|
||||
|
||||
// Sort nodes bottum-up by cell id.
|
||||
// This ensures that we get a nice grouping from parent to child cells:
|
||||
//
|
||||
// intitial:
|
||||
// level 0: 0 1 2 3 4 5
|
||||
// level 1: 2 1 3 4 3 4
|
||||
// level 2: 2 2 0 1 0 1
|
||||
//
|
||||
// first round:
|
||||
// level 0: 1 0 2 4 3 5
|
||||
// level 1: 1 2 3 3 4 4 (< sorted)
|
||||
// level 2: 2 2 0 0 1 1
|
||||
//
|
||||
// second round:
|
||||
// level 0: 2 4 3 5 1 0
|
||||
// level 1: 3 3 4 4 1 2
|
||||
// level 2: 0 0 1 1 2 2 (< sorted)
|
||||
for (const auto &partition : partitions)
|
||||
{
|
||||
std::stable_sort(permutation.begin(),
|
||||
permutation.end(),
|
||||
[&partition](const auto lhs, const auto rhs) {
|
||||
return partition[lhs] < partition[rhs];
|
||||
});
|
||||
}
|
||||
|
||||
// top down assign new cell ids
|
||||
LevelID level = partitions.size();
|
||||
for (const auto &partition : boost::adaptors::reverse(partitions))
|
||||
{
|
||||
BOOST_ASSERT(permutation.size() > 0);
|
||||
CellID last_cell_id = partition[permutation.front()];
|
||||
CellID cell_id = 0;
|
||||
for (const auto node : permutation)
|
||||
{
|
||||
if (last_cell_id != partition[node])
|
||||
{
|
||||
cell_id++;
|
||||
last_cell_id = partition[node];
|
||||
}
|
||||
SetCellID(level, node, cell_id);
|
||||
}
|
||||
// Store the number of cells of the level in the sentinel
|
||||
SetCellID(level, sentinel, cell_id + 1);
|
||||
level--;
|
||||
}
|
||||
|
||||
level_data->lidx_to_children_offsets[0] = 0;
|
||||
|
||||
for (auto level_idx = 0UL; level_idx < partitions.size() - 1; ++level_idx)
|
||||
{
|
||||
const auto &parent_partition = partitions[level_idx + 1];
|
||||
|
||||
level_data->lidx_to_children_offsets[level_idx + 1] = cell_to_children.size();
|
||||
|
||||
CellID last_parent_id = parent_partition[permutation.front()];
|
||||
cell_to_children.push_back(GetCell(level_idx + 1, permutation.front()));
|
||||
for (const auto node : permutation)
|
||||
{
|
||||
if (last_parent_id != parent_partition[node])
|
||||
{
|
||||
// Note: we use the new cell id here, not the ones contained
|
||||
// in the input partition
|
||||
cell_to_children.push_back(GetCell(level_idx + 1, node));
|
||||
last_parent_id = parent_partition[node];
|
||||
}
|
||||
}
|
||||
// insert sentinel for the last cell
|
||||
cell_to_children.push_back(GetCell(level_idx + 1, permutation.back()) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
LevelDataPtr level_data = {};
|
||||
Vector<PartitionID> partition;
|
||||
Vector<CellID> cell_to_children;
|
||||
};
|
||||
|
||||
template <>
|
||||
inline MultiLevelPartitionImpl<storage::Ownership::Container>::MultiLevelPartitionImpl()
|
||||
: level_data(std::make_unique<LevelData>())
|
||||
{
|
||||
}
|
||||
|
||||
template <>
|
||||
inline MultiLevelPartitionImpl<storage::Ownership::View>::MultiLevelPartitionImpl()
|
||||
: level_data(nullptr)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,162 @@
|
||||
#ifndef OSRM_PARTITIONER_GRAPH_HPP_
|
||||
#define OSRM_PARTITIONER_GRAPH_HPP_
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
#include "util/typedefs.hpp"
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
|
||||
// forward declaration to allow finding friends
|
||||
template <typename NodeEntryT, typename EdgeEntryT> class RemappableGraph;
|
||||
|
||||
// wrapper for nodes to augment with a tag storing first edge id
|
||||
template <typename Base> class NodeEntryWrapper : public Base
|
||||
{
|
||||
public:
|
||||
template <typename... Args>
|
||||
NodeEntryWrapper(std::size_t edges_begin_, std::size_t edges_end_, Args &&... args)
|
||||
: Base(std::forward<Args>(args)...), edges_begin(edges_begin_), edges_end(edges_end_)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
// only to be modified by the graph itself
|
||||
std::size_t edges_begin;
|
||||
std::size_t edges_end;
|
||||
|
||||
// give the graph access to the node data wrapper
|
||||
template <typename NodeEntryT, typename EdgeEntryT> friend class RemappableGraph;
|
||||
};
|
||||
|
||||
using RemappableGraphNode = NodeEntryWrapper<struct zero_base_class>;
|
||||
|
||||
template <typename Base> class GraphConstructionWrapper : public Base
|
||||
{
|
||||
public:
|
||||
template <typename... Args>
|
||||
GraphConstructionWrapper(const NodeID source_, Args &&... args)
|
||||
: Base(std::forward<Args>(args)...), source(source_)
|
||||
{
|
||||
}
|
||||
|
||||
NodeID source;
|
||||
|
||||
Base Reduce() const { return *this; }
|
||||
};
|
||||
|
||||
template <typename RandomIt> void groupEdgesBySource(RandomIt first, RandomIt last)
|
||||
{
|
||||
std::sort(
|
||||
first, last, [](const auto &lhs, const auto &rhs) { return lhs.source < rhs.source; });
|
||||
}
|
||||
|
||||
template <typename NodeEntryT, typename EdgeEntryT> class RemappableGraph
|
||||
{
|
||||
public:
|
||||
using NodeT = NodeEntryT;
|
||||
using EdgeT = EdgeEntryT;
|
||||
|
||||
using NodeIterator = typename std::vector<NodeT>::iterator;
|
||||
using ConstNodeIterator = typename std::vector<NodeT>::const_iterator;
|
||||
using EdgeIterator = typename std::vector<EdgeT>::iterator;
|
||||
using ConstEdgeIterator = typename std::vector<EdgeT>::const_iterator;
|
||||
|
||||
// Constructs an empty graph with a given number of nodes.
|
||||
explicit RemappableGraph(std::vector<NodeT> nodes_, std::vector<EdgeT> edges_)
|
||||
: nodes(std::move(nodes_)), edges(std::move(edges_))
|
||||
{
|
||||
}
|
||||
|
||||
unsigned NumberOfNodes() const { return nodes.size(); }
|
||||
|
||||
auto &Node(const NodeID nid) { return nodes[nid]; }
|
||||
auto &Node(const NodeID nid) const { return nodes[nid]; }
|
||||
|
||||
auto &Edge(const EdgeID eid) { return edges[eid]; }
|
||||
auto &Edge(const EdgeID eid) const { return edges[eid]; }
|
||||
|
||||
auto Edges(const NodeID nid)
|
||||
{
|
||||
return boost::make_iterator_range(edges.begin() + nodes[nid].edges_begin,
|
||||
edges.begin() + nodes[nid].edges_end);
|
||||
}
|
||||
|
||||
auto Edges(const NodeID nid) const
|
||||
{
|
||||
return boost::make_iterator_range(edges.begin() + nodes[nid].edges_begin,
|
||||
edges.begin() + nodes[nid].edges_end);
|
||||
}
|
||||
|
||||
auto Edges(const NodeT &node)
|
||||
{
|
||||
return boost::make_iterator_range(edges.begin() + node.edges_begin,
|
||||
edges.begin() + node.edges_end);
|
||||
}
|
||||
|
||||
auto Edges(const NodeT &node) const
|
||||
{
|
||||
return boost::make_iterator_range(edges.begin() + node.edges_begin,
|
||||
edges.begin() + node.edges_end);
|
||||
}
|
||||
|
||||
auto BeginEdges(const NodeID nid) const { return edges.begin() + nodes[nid].edges_begin; }
|
||||
auto EndEdges(const NodeID nid) const { return edges.begin() + nodes[nid].edges_end; }
|
||||
|
||||
auto BeginEdges(const NodeT &node) const { return edges.begin() + node.edges_begin; }
|
||||
auto EndEdges(const NodeT &node) const { return edges.begin() + node.edges_end; }
|
||||
auto BeginEdges(const NodeT &node) { return edges.begin() + node.edges_begin; }
|
||||
auto EndEdges(const NodeT &node) { return edges.begin() + node.edges_end; }
|
||||
|
||||
EdgeID BeginEdgeID(const NodeID nid) const { return nodes[nid].edges_begin; }
|
||||
EdgeID EndEdgeID(const NodeID nid) const { return nodes[nid].edges_end; }
|
||||
|
||||
// iterate over all nodes
|
||||
auto Nodes() { return boost::make_iterator_range(nodes.begin(), nodes.end()); }
|
||||
auto Nodes() const { return boost::make_iterator_range(nodes.begin(), nodes.end()); }
|
||||
|
||||
NodeID GetID(const NodeT &node) const
|
||||
{
|
||||
BOOST_ASSERT(&node >= &nodes[0] && &node <= &nodes.back());
|
||||
return (&node - &nodes[0]);
|
||||
}
|
||||
EdgeID GetID(const EdgeT &edge) const
|
||||
{
|
||||
BOOST_ASSERT(&edge >= &edges[0] && &edge <= &edges.back());
|
||||
return (&edge - &edges[0]);
|
||||
}
|
||||
|
||||
NodeIterator Begin() { return nodes.begin(); }
|
||||
NodeIterator End() { return nodes.end(); }
|
||||
ConstNodeIterator CBegin() const { return nodes.cbegin(); }
|
||||
ConstNodeIterator CEnd() const { return nodes.cend(); }
|
||||
|
||||
// removes the edges from the graph that return true for the filter, returns new end
|
||||
template <typename FilterT> auto RemoveEdges(NodeT &node, FilterT filter)
|
||||
{
|
||||
BOOST_ASSERT(&node >= &nodes[0] && &node <= &nodes.back());
|
||||
// required since we are not on std++17 yet, otherwise we are missing an argument_type
|
||||
const auto negate_filter = [&](const EdgeT &edge) { return !filter(edge); };
|
||||
const auto center = std::stable_partition(BeginEdges(node), EndEdges(node), negate_filter);
|
||||
const auto remaining_edges = std::distance(BeginEdges(node), center);
|
||||
node.edges_end = node.edges_begin + remaining_edges;
|
||||
return center;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<NodeT> nodes;
|
||||
std::vector<EdgeT> edges;
|
||||
};
|
||||
|
||||
} // namespace partitioner
|
||||
} // namespace osrm
|
||||
|
||||
#endif // OSRM_PARTITIONER_GRAPH_HPP_
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef OSRM_PARTITIONER_PARTITIONER_HPP_
|
||||
#define OSRM_PARTITIONER_PARTITIONER_HPP_
|
||||
|
||||
#include "partitioner/partitioner_config.hpp"
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
|
||||
// tool access to the recursive partitioner
|
||||
class Partitioner
|
||||
{
|
||||
public:
|
||||
int Run(const PartitionerConfig &config);
|
||||
};
|
||||
|
||||
} // namespace partitioner
|
||||
} // namespace osrm
|
||||
|
||||
#endif // OSRM_PARTITIONER_PARTITIONER_HPP_
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef OSRM_PARTITIONER_CONFIG_HPP
|
||||
#define OSRM_PARTITIONER_CONFIG_HPP
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "storage/io_config.hpp"
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
|
||||
struct PartitionerConfig final : storage::IOConfig
|
||||
{
|
||||
PartitionerConfig()
|
||||
: IOConfig(
|
||||
{".osrm", ".osrm.fileIndex", ".osrm.ebg_nodes"},
|
||||
{".osrm.hsgr", ".osrm.cnbg"},
|
||||
{".osrm.ebg", ".osrm.cnbg", ".osrm.cnbg_to_ebg", ".osrm.partition", ".osrm.cells"}),
|
||||
requested_num_threads(0), balance(1.2), boundary_factor(0.25), num_optimizing_cuts(10),
|
||||
small_component_size(1000),
|
||||
max_cell_sizes({128, 128 * 32, 128 * 32 * 16, 128 * 32 * 16 * 32})
|
||||
{
|
||||
}
|
||||
|
||||
void UseDefaultOutputNames(const boost::filesystem::path &base)
|
||||
{
|
||||
IOConfig::UseDefaultOutputNames(base);
|
||||
}
|
||||
|
||||
unsigned requested_num_threads;
|
||||
|
||||
double balance;
|
||||
double boundary_factor;
|
||||
std::size_t num_optimizing_cuts;
|
||||
std::size_t small_component_size;
|
||||
std::vector<std::size_t> max_cell_sizes;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif // OSRM_PARTITIONER_CONFIG_HPP
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef OSRM_PARTITIONER_RECURSIVE_BISECTION_HPP_
|
||||
#define OSRM_PARTITIONER_RECURSIVE_BISECTION_HPP_
|
||||
|
||||
#include "partitioner/bisection_graph.hpp"
|
||||
#include "partitioner/recursive_bisection_state.hpp"
|
||||
#include "util/typedefs.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
|
||||
class RecursiveBisection
|
||||
{
|
||||
public:
|
||||
RecursiveBisection(BisectionGraph &bisection_graph,
|
||||
const std::size_t maximum_cell_size,
|
||||
const double balance,
|
||||
const double boundary_factor,
|
||||
const std::size_t num_optimizing_cuts,
|
||||
const std::size_t small_component_size);
|
||||
|
||||
const std::vector<BisectionID> &BisectionIDs() const;
|
||||
|
||||
std::uint32_t SCCDepth() const;
|
||||
|
||||
private:
|
||||
BisectionGraph &bisection_graph;
|
||||
RecursiveBisectionState internal_state;
|
||||
};
|
||||
|
||||
} // namespace partitioner
|
||||
} // namespace osrm
|
||||
|
||||
#endif // OSRM_PARTITIONER_RECURSIVE_BISECTION_HPP_
|
||||
@@ -0,0 +1,56 @@
|
||||
#ifndef OSRM_PARTITIONER_RECURSIVE_BISECTION_STATE_HPP_
|
||||
#define OSRM_PARTITIONER_RECURSIVE_BISECTION_STATE_HPP_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "partitioner/bisection_graph.hpp"
|
||||
#include "partitioner/bisection_graph_view.hpp"
|
||||
#include "util/typedefs.hpp"
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
|
||||
// Keeps track of the bisection ids, modifies the graph accordingly, splitting it into a left/right
|
||||
// section with consecutively labelled nodes. Requires a GraphView to look at.
|
||||
class RecursiveBisectionState
|
||||
{
|
||||
public:
|
||||
// The ID in the partition array
|
||||
using NodeIterator = BisectionGraph::ConstNodeIterator;
|
||||
|
||||
RecursiveBisectionState(BisectionGraph &bisection_graph);
|
||||
~RecursiveBisectionState();
|
||||
|
||||
BisectionID GetBisectionID(const NodeID node) const;
|
||||
|
||||
// Bisects the node id array's sub-range based on the partition mask.
|
||||
// Returns: partition point of the bisection: iterator to the second group's first element.
|
||||
NodeIterator ApplyBisection(NodeIterator begin,
|
||||
const NodeIterator end,
|
||||
const std::size_t depth,
|
||||
const std::vector<bool> &partition);
|
||||
|
||||
// perform an initial pre-partitioning into small components
|
||||
// on larger graphs, SCCs give perfect cuts (think Amerika vs Europe)
|
||||
// This function performs an initial pre-partitioning using these sccs.
|
||||
std::vector<BisectionGraphView> PrePartitionWithSCC(const std::size_t small_component_size);
|
||||
|
||||
const std::vector<BisectionID> &BisectionIDs() const;
|
||||
|
||||
// return the depth encoded in the SCCs
|
||||
std::uint32_t SCCDepth() const;
|
||||
|
||||
private:
|
||||
std::uint32_t scc_levels;
|
||||
BisectionGraph &bisection_graph;
|
||||
std::vector<BisectionID> bisection_ids;
|
||||
};
|
||||
|
||||
} // namespace partitioner
|
||||
} // namespace osrm
|
||||
|
||||
#endif // OSRM_PARTITIONER_RECURSIVE_BISECTION_STATE_HPP_
|
||||
@@ -0,0 +1,115 @@
|
||||
#ifndef OSRM_PARTITIONER_REMOVE_UNCONNECTED_HPP
|
||||
#define OSRM_PARTITIONER_REMOVE_UNCONNECTED_HPP
|
||||
|
||||
#include "util/log.hpp"
|
||||
#include "util/typedefs.hpp"
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
using Partition = std::vector<CellID>;
|
||||
|
||||
template <typename GraphT>
|
||||
std::size_t removeUnconnectedBoundaryNodes(const GraphT &edge_based_graph,
|
||||
std::vector<Partition> &partitions)
|
||||
{
|
||||
auto num_unconnected = 0;
|
||||
auto could_not_fix = 0;
|
||||
for (int level_index = partitions.size() - 1; level_index >= 0; level_index--)
|
||||
{
|
||||
struct Witness
|
||||
{
|
||||
NodeID id;
|
||||
std::size_t induced_border_edges;
|
||||
};
|
||||
std::vector<Witness> witnesses;
|
||||
for (NodeID node = 0; node < edge_based_graph.GetNumberOfNodes(); ++node)
|
||||
{
|
||||
witnesses.clear();
|
||||
|
||||
bool is_source = false;
|
||||
bool is_target = false;
|
||||
|
||||
const auto cell_id = partitions[level_index][node];
|
||||
for (auto edge : edge_based_graph.GetAdjacentEdgeRange(node))
|
||||
{
|
||||
const auto data = edge_based_graph.GetEdgeData(edge);
|
||||
const auto target = edge_based_graph.GetTarget(edge);
|
||||
const auto target_cell_id = partitions[level_index][target];
|
||||
if (target_cell_id == cell_id)
|
||||
{
|
||||
is_source |= data.forward;
|
||||
is_target |= data.backward;
|
||||
}
|
||||
else
|
||||
{
|
||||
witnesses.push_back({target, 0});
|
||||
}
|
||||
}
|
||||
|
||||
const auto unconnected = witnesses.size() > 0 && !is_source && !is_target;
|
||||
|
||||
if (unconnected)
|
||||
{
|
||||
num_unconnected++;
|
||||
|
||||
if (level_index < static_cast<int>(partitions.size() - 1))
|
||||
{
|
||||
auto new_end = std::remove_if(
|
||||
witnesses.begin(), witnesses.end(), [&](const auto &witness) {
|
||||
return partitions[level_index + 1][node] !=
|
||||
partitions[level_index + 1][witness.id];
|
||||
});
|
||||
witnesses.resize(new_end - witnesses.begin());
|
||||
}
|
||||
if (witnesses.size() == 0)
|
||||
{
|
||||
could_not_fix++;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto &witness : witnesses)
|
||||
{
|
||||
for (auto edge : edge_based_graph.GetAdjacentEdgeRange(node))
|
||||
{
|
||||
auto target = edge_based_graph.GetTarget(edge);
|
||||
for (auto sublevel_index = level_index; sublevel_index >= 0;
|
||||
--sublevel_index)
|
||||
{
|
||||
if (partitions[sublevel_index][target] !=
|
||||
partitions[sublevel_index][witness.id])
|
||||
witness.induced_border_edges++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto best_witness = std::min_element(
|
||||
witnesses.begin(), witnesses.end(), [](const auto &lhs, const auto &rhs) {
|
||||
return lhs.induced_border_edges < rhs.induced_border_edges;
|
||||
});
|
||||
BOOST_ASSERT(best_witness != witnesses.end());
|
||||
|
||||
// assign `node` to same subcells as `best_witness`
|
||||
for (auto sublevel_index = level_index; sublevel_index >= 0; --sublevel_index)
|
||||
{
|
||||
partitions[sublevel_index][node] = partitions[sublevel_index][best_witness->id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (could_not_fix > 0)
|
||||
util::Log(logWARNING) << "Could not fix " << could_not_fix << " unconnected boundary nodes";
|
||||
|
||||
return num_unconnected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,79 @@
|
||||
#ifndef OSRM_PARTITIONER_RENUMBER_HPP
|
||||
#define OSRM_PARTITIONER_RENUMBER_HPP
|
||||
|
||||
#include "extractor/edge_based_node_segment.hpp"
|
||||
#include "extractor/nbg_to_ebg.hpp"
|
||||
#include "extractor/node_data_container.hpp"
|
||||
|
||||
#include "partitioner/bisection_to_partition.hpp"
|
||||
#include "partitioner/edge_based_graph.hpp"
|
||||
|
||||
#include "util/dynamic_graph.hpp"
|
||||
#include "util/static_graph.hpp"
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
std::vector<std::uint32_t> makePermutation(const DynamicEdgeBasedGraph &graph,
|
||||
const std::vector<Partition> &partitions);
|
||||
|
||||
template <typename EdgeDataT>
|
||||
inline void renumber(util::DynamicGraph<EdgeDataT> &graph,
|
||||
const std::vector<std::uint32_t> &permutation)
|
||||
{
|
||||
// dynamic graph has own specilization
|
||||
graph.Renumber(permutation);
|
||||
}
|
||||
|
||||
template <typename EdgeDataT>
|
||||
inline void renumber(util::StaticGraph<EdgeDataT> &graph,
|
||||
const std::vector<std::uint32_t> &permutation)
|
||||
{
|
||||
// static graph has own specilization
|
||||
graph.Renumber(permutation);
|
||||
}
|
||||
|
||||
inline void renumber(extractor::EdgeBasedNodeDataContainer &node_data_container,
|
||||
const std::vector<std::uint32_t> &permutation)
|
||||
{
|
||||
node_data_container.Renumber(permutation);
|
||||
}
|
||||
|
||||
inline void renumber(std::vector<Partition> &partitions,
|
||||
const std::vector<std::uint32_t> &permutation)
|
||||
{
|
||||
for (auto &partition : partitions)
|
||||
{
|
||||
util::inplacePermutation(partition.begin(), partition.end(), permutation);
|
||||
}
|
||||
}
|
||||
|
||||
inline void renumber(util::vector_view<extractor::EdgeBasedNodeSegment> &segments,
|
||||
const std::vector<std::uint32_t> &permutation)
|
||||
{
|
||||
for (auto &segment : segments)
|
||||
{
|
||||
BOOST_ASSERT(segment.forward_segment_id.enabled);
|
||||
segment.forward_segment_id.id = permutation[segment.forward_segment_id.id];
|
||||
if (segment.reverse_segment_id.enabled)
|
||||
segment.reverse_segment_id.id = permutation[segment.reverse_segment_id.id];
|
||||
}
|
||||
}
|
||||
|
||||
inline void renumber(std::vector<extractor::NBGToEBG> &mapping,
|
||||
const std::vector<std::uint32_t> &permutation)
|
||||
{
|
||||
for (extractor::NBGToEBG &m : mapping)
|
||||
{
|
||||
if (m.backward_ebg_node != SPECIAL_NODEID)
|
||||
m.backward_ebg_node = permutation[m.backward_ebg_node];
|
||||
if (m.forward_ebg_node != SPECIAL_NODEID)
|
||||
m.forward_ebg_node = permutation[m.forward_ebg_node];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace partitioner
|
||||
} // namespace osrm
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,55 @@
|
||||
#ifndef OSRM_PARTITIONER_REORDER_FIRST_LAST_HPP
|
||||
#define OSRM_PARTITIONER_REORDER_FIRST_LAST_HPP
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
|
||||
// Reorders the first n elements in the range to satisfy the comparator,
|
||||
// and the last n elements to satisfy the comparator with arguments flipped.
|
||||
// Note: no guarantees to the element's ordering inside the reordered ranges.
|
||||
template <typename RandomIt, typename Comparator>
|
||||
void reorderFirstLast(RandomIt first, RandomIt last, std::size_t n, Comparator comp)
|
||||
{
|
||||
BOOST_ASSERT_MSG(n <= (last - first) / std::size_t{2}, "overlapping subranges not allowed");
|
||||
|
||||
if (n == 0 || (last - first < 2))
|
||||
return;
|
||||
|
||||
// Reorder first n: guarantees that the predicate holds for the first elements.
|
||||
std::nth_element(first, first + (n - 1), last, comp);
|
||||
|
||||
// Reorder last n: guarantees that the flipped predicate holds for the last k elements.
|
||||
// We reorder from the end backwards up to the end of the already reordered range.
|
||||
// We can not use std::not2, since then e.g. std::less<> would lose its irreflexive
|
||||
// requirements.
|
||||
std::reverse_iterator<RandomIt> rfirst{last}, rlast{first + n};
|
||||
|
||||
const auto flipped = [](auto fn) {
|
||||
return [fn](auto &&lhs, auto &&rhs) {
|
||||
return fn(std::forward<decltype(lhs)>(rhs), std::forward<decltype(rhs)>(lhs));
|
||||
};
|
||||
};
|
||||
|
||||
std::nth_element(rfirst, rfirst + (n - 1), rlast, flipped(comp));
|
||||
}
|
||||
|
||||
template <typename RandomAccessRange, typename Compare>
|
||||
void reorderFirstLast(RandomAccessRange &rng, std::size_t n, Compare comp)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
return reorderFirstLast(begin(rng), end(rng), n, comp);
|
||||
}
|
||||
|
||||
} // ns partition
|
||||
} // ns osrm
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,76 @@
|
||||
#ifndef OSRM_PARTITIONER_SERIALIZATION_HPP
|
||||
#define OSRM_PARTITIONER_SERIALIZATION_HPP
|
||||
|
||||
#include "partitioner/cell_storage.hpp"
|
||||
#include "partitioner/edge_based_graph.hpp"
|
||||
#include "partitioner/multi_level_graph.hpp"
|
||||
#include "partitioner/multi_level_partition.hpp"
|
||||
|
||||
#include "storage/io.hpp"
|
||||
#include "storage/serialization.hpp"
|
||||
#include "storage/shared_memory_ownership.hpp"
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
namespace serialization
|
||||
{
|
||||
|
||||
template <typename EdgeDataT, storage::Ownership Ownership>
|
||||
inline void read(storage::io::FileReader &reader, MultiLevelGraph<EdgeDataT, Ownership> &graph)
|
||||
{
|
||||
storage::serialization::read(reader, graph.node_array);
|
||||
storage::serialization::read(reader, graph.edge_array);
|
||||
storage::serialization::read(reader, graph.node_to_edge_offset);
|
||||
}
|
||||
|
||||
template <typename EdgeDataT, storage::Ownership Ownership>
|
||||
inline void write(storage::io::FileWriter &writer,
|
||||
const MultiLevelGraph<EdgeDataT, Ownership> &graph)
|
||||
{
|
||||
storage::serialization::write(writer, graph.node_array);
|
||||
storage::serialization::write(writer, graph.edge_array);
|
||||
storage::serialization::write(writer, graph.node_to_edge_offset);
|
||||
}
|
||||
|
||||
template <storage::Ownership Ownership>
|
||||
inline void read(storage::io::FileReader &reader, detail::MultiLevelPartitionImpl<Ownership> &mlp)
|
||||
{
|
||||
reader.ReadInto(*mlp.level_data);
|
||||
storage::serialization::read(reader, mlp.partition);
|
||||
storage::serialization::read(reader, mlp.cell_to_children);
|
||||
}
|
||||
|
||||
template <storage::Ownership Ownership>
|
||||
inline void write(storage::io::FileWriter &writer,
|
||||
const detail::MultiLevelPartitionImpl<Ownership> &mlp)
|
||||
{
|
||||
writer.WriteOne(*mlp.level_data);
|
||||
storage::serialization::write(writer, mlp.partition);
|
||||
storage::serialization::write(writer, mlp.cell_to_children);
|
||||
}
|
||||
|
||||
template <storage::Ownership Ownership>
|
||||
inline void read(storage::io::FileReader &reader, detail::CellStorageImpl<Ownership> &storage)
|
||||
{
|
||||
storage::serialization::read(reader, storage.source_boundary);
|
||||
storage::serialization::read(reader, storage.destination_boundary);
|
||||
storage::serialization::read(reader, storage.cells);
|
||||
storage::serialization::read(reader, storage.level_to_cell_offset);
|
||||
}
|
||||
|
||||
template <storage::Ownership Ownership>
|
||||
inline void write(storage::io::FileWriter &writer,
|
||||
const detail::CellStorageImpl<Ownership> &storage)
|
||||
{
|
||||
storage::serialization::write(writer, storage.source_boundary);
|
||||
storage::serialization::write(writer, storage.destination_boundary);
|
||||
storage::serialization::write(writer, storage.cells);
|
||||
storage::serialization::write(writer, storage.level_to_cell_offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef OSRM_PARTITIONER_TARJAN_GRAPH_WRAPPER_HPP_
|
||||
#define OSRM_PARTITIONER_TARJAN_GRAPH_WRAPPER_HPP_
|
||||
|
||||
#include "partitioner/bisection_graph.hpp"
|
||||
#include "util/integer_range.hpp"
|
||||
#include "util/typedefs.hpp"
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partitioner
|
||||
{
|
||||
|
||||
class TarjanGraphWrapper
|
||||
{
|
||||
public:
|
||||
TarjanGraphWrapper(const BisectionGraph &bisection_graph);
|
||||
|
||||
std::size_t GetNumberOfNodes() const;
|
||||
util::range<EdgeID> GetAdjacentEdgeRange(const NodeID nid) const;
|
||||
NodeID GetTarget(const EdgeID eid) const;
|
||||
|
||||
protected:
|
||||
const BisectionGraph &bisection_graph;
|
||||
};
|
||||
|
||||
} // namespace partitioner
|
||||
} // namespace osrm
|
||||
|
||||
#endif // OSRM_PARTITIONER_TARJAN_GRAPH_WRAPPER_HPP_
|
||||
Reference in New Issue
Block a user