add unit tests for the different components of the parttion tool
This commit is contained in:
parent
e316dad1cb
commit
b789da45bd
@ -197,6 +197,7 @@ script:
|
||||
- ./unit_tests/engine-tests
|
||||
- ./unit_tests/util-tests
|
||||
- ./unit_tests/server-tests
|
||||
- ./unit_tests/partition-tests
|
||||
- popd
|
||||
- npm test
|
||||
|
||||
|
@ -56,7 +56,7 @@ 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)
|
||||
// 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)
|
||||
{
|
||||
|
@ -32,8 +32,13 @@ class DinicMaxFlow
|
||||
using SourceSinkNodes = std::unordered_set<NodeID>;
|
||||
|
||||
MinCut operator()(const GraphView &view,
|
||||
const SourceSinkNodes &sink_nodes,
|
||||
const SourceSinkNodes &source_nodes) const;
|
||||
const SourceSinkNodes &source_nodes,
|
||||
const SourceSinkNodes &sink_nodes) const;
|
||||
|
||||
// validates the inpiut parameters to the flow algorithm (e.g. not intersecting)
|
||||
bool Validate(const GraphView &view,
|
||||
const SourceSinkNodes &source_nodes,
|
||||
const SourceSinkNodes &sink_nodes) const;
|
||||
|
||||
private:
|
||||
// the level of each node in the graph (==hops in BFS from source)
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define OSRM_PARTITION_GRAPHVIEW_HPP_
|
||||
|
||||
#include "partition/bisection_graph.hpp"
|
||||
#include "partition/recursive_bisection_state.hpp"
|
||||
|
||||
#include <boost/iterator/filter_iterator.hpp>
|
||||
#include <boost/iterator/iterator_facade.hpp>
|
||||
@ -41,6 +40,7 @@ class GraphView
|
||||
// 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;
|
||||
@ -50,6 +50,7 @@ class GraphView
|
||||
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)); }
|
||||
|
@ -47,6 +47,7 @@ struct PartitionConfig
|
||||
double balance;
|
||||
double boundary_factor;
|
||||
std::size_t num_optimizing_cuts;
|
||||
std::size_t small_component_size;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -123,11 +123,16 @@ template <typename NodeEntryT, typename EdgeEntryT> class RemappableGraph
|
||||
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)
|
||||
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(); }
|
||||
|
@ -16,21 +16,18 @@ namespace partition
|
||||
class RecursiveBisection
|
||||
{
|
||||
public:
|
||||
RecursiveBisection(std::size_t maximum_cell_size,
|
||||
double balance,
|
||||
double boundary_factor,
|
||||
std::size_t num_optimizing_cuts,
|
||||
BisectionGraph &bisection_graph);
|
||||
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<RecursiveBisectionState::BisectionID> &BisectionIDs() const;
|
||||
|
||||
private:
|
||||
BisectionGraph &bisection_graph;
|
||||
RecursiveBisectionState internal_state;
|
||||
|
||||
// on larger graphs, SCCs give perfect cuts (think Amerika vs Europe)
|
||||
// This function performs an initial pre-partitioning using these sccs.
|
||||
std::vector<GraphView> FakeFirstPartitionWithSCC(const std::size_t small_component_size);
|
||||
};
|
||||
|
||||
} // namespace partition
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "partition/bisection_graph.hpp"
|
||||
#include "partition/graph_view.hpp"
|
||||
#include "util/typedefs.hpp"
|
||||
|
||||
namespace osrm
|
||||
@ -13,51 +14,8 @@ namespace osrm
|
||||
namespace partition
|
||||
{
|
||||
|
||||
// The recursive state describes the relation between our global graph and the recursive state in a
|
||||
// recursive bisection.
|
||||
//
|
||||
// We describe the state with the use of two arrays:
|
||||
//
|
||||
// The id-arrays keeps nodes in order, based on their partitioning. Initially, it contains all nodes
|
||||
// in sorted order:
|
||||
//
|
||||
// ids: [0,1,2,3,4,5,6,7,8,9]
|
||||
//
|
||||
// When partitioned (for this example we use even : odd), the arrays is re-ordered to group all
|
||||
// elements within a single cell of the partition:
|
||||
//
|
||||
// ids: [0,2,4,6,8,1,3,5,7,9]
|
||||
//
|
||||
// This can be performed recursively by interpreting the arrays [0,2,4,6,8] and [1,3,5,7,9] as new
|
||||
// input.
|
||||
//
|
||||
// The partitioning array describes all results of the partitioning in form of `left` or `right`.
|
||||
// It is a sequence of bits for every id that describes if it is moved to the `front` or `back`
|
||||
// during the split of the ID array. In the example, we would get the result
|
||||
//
|
||||
// bisection-ids: [0,1,0,1,0,1,0,1,0,1]
|
||||
//
|
||||
// Further partitioning [0,2,4,6,8] into [0,4,8], [2,6] and [1,3,5,7,9] into [1,3,9] and [5,7]
|
||||
// the result would be:
|
||||
//
|
||||
// bisection-ids: [00,10,01,10,00,11,01,11,00,10]
|
||||
|
||||
/* Written out in a recursive tree form:
|
||||
|
||||
ids: [0,1,2,3,4,5,6,7,8,9]
|
||||
mask: [0,1,0,1,0,1,0,1,0,1]
|
||||
/ \
|
||||
ids: [0,2,4,6,8] [1,3,5,7,9]
|
||||
mask: [0,1,0,1,0] [0,0,1,1,0]
|
||||
/ \ / \
|
||||
ids: [0,4,8] [2,6] [1,3,9] [5,7]
|
||||
|
||||
The bisection ids then trace the path (left: 0, right: 1) through the tree:
|
||||
|
||||
ids: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
|
||||
path: [00, 10, 01, 10, 00, 11, 01, 11, 00, 10]
|
||||
|
||||
*/
|
||||
// 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:
|
||||
@ -77,6 +35,11 @@ class RecursiveBisectionState
|
||||
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<GraphView> PrePartitionWithSCC(const std::size_t small_component_size);
|
||||
|
||||
const std::vector<BisectionID> &BisectionIDs() const;
|
||||
|
||||
private:
|
||||
|
19
include/partition/recursive_bisection_stats.hpp
Normal file
19
include/partition/recursive_bisection_stats.hpp
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef OSRM_PARTITION_RECURSIVE_BISECTION_STATS_HPP_
|
||||
#define OSRM_PARTITION_RECURSIVE_BISECTION_STATS_HPP_
|
||||
|
||||
#include "partition/bisection_graph.hpp"
|
||||
#include "partition/recursive_bisection_state.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partition
|
||||
{
|
||||
// generates some statistics on a recursive bisection to describe its quality/parameters
|
||||
void printBisectionStats(std::vector<RecursiveBisectionState::BisectionID> const &bisection_ids,
|
||||
const BisectionGraph &graph);
|
||||
} // namespace partition
|
||||
} // namespace osrm
|
||||
|
||||
#endif // OSRM_PARTITION_RECURSIVE_BISECTION_STATS_HPP_
|
@ -85,7 +85,7 @@ template <typename NodeT, typename EdgeT, bool UseSharedMemory = false> class Fl
|
||||
{
|
||||
static_assert(traits::HasFirstEdgeMember<NodeT>::value,
|
||||
"Model for compatible Node type requires .first_edge member attribute");
|
||||
static_assert(traits::HasTargetMember<EdgeT>(),
|
||||
static_assert(traits::HasTargetMember<EdgeT>::value,
|
||||
"Model for compatible Node type requires .target member attribute");
|
||||
|
||||
public:
|
||||
|
@ -35,6 +35,7 @@ DinicMaxFlow::MinCut DinicMaxFlow::operator()(const GraphView &view,
|
||||
const SourceSinkNodes &source_nodes,
|
||||
const SourceSinkNodes &sink_nodes) const
|
||||
{
|
||||
BOOST_ASSERT(Validate(view, source_nodes, sink_nodes));
|
||||
// for the inertial flow algorithm, we use quite a large set of nodes as source/sink nodes. Only
|
||||
// a few of them can be part of the process, since they are grouped together. A standard
|
||||
// parameterisation would be 25% sink/source nodes. This already includes 50% of the graph. By
|
||||
@ -219,6 +220,7 @@ std::size_t DinicMaxFlow::BlockingFlow(FlowEdges &flow,
|
||||
};
|
||||
|
||||
std::for_each(border_sink_nodes.begin(), border_sink_nodes.end(), augment_all_paths);
|
||||
BOOST_ASSERT(flow_increase > 0);
|
||||
return flow_increase;
|
||||
}
|
||||
|
||||
@ -288,5 +290,24 @@ std::vector<NodeID> DinicMaxFlow::GetAugmentingPath(LevelGraph &levels,
|
||||
return path;
|
||||
}
|
||||
|
||||
bool DinicMaxFlow::Validate(const GraphView &view,
|
||||
const SourceSinkNodes &source_nodes,
|
||||
const SourceSinkNodes &sink_nodes) const
|
||||
{
|
||||
// sink and source cannot share a common node
|
||||
const auto separated =
|
||||
std::find_if(source_nodes.begin(), source_nodes.end(), [&sink_nodes](const auto node) {
|
||||
return sink_nodes.count(node);
|
||||
}) == source_nodes.end();
|
||||
|
||||
const auto invalid_id = [&view](const NodeID nid) { return nid >= view.NumberOfNodes(); };
|
||||
const auto in_range_source =
|
||||
std::find_if(source_nodes.begin(), source_nodes.end(), invalid_id) == source_nodes.end();
|
||||
const auto in_range_sink =
|
||||
std::find_if(sink_nodes.begin(), sink_nodes.end(), invalid_id) == sink_nodes.end();
|
||||
|
||||
return separated && in_range_source && in_range_sink;
|
||||
}
|
||||
|
||||
} // namespace partition
|
||||
} // namespace osrm
|
||||
|
@ -42,10 +42,7 @@ BisectionGraph::ConstNodeIterator GraphView::Begin() const { return begin; }
|
||||
|
||||
BisectionGraph::ConstNodeIterator GraphView::End() const { return end; }
|
||||
|
||||
const GraphView::NodeT &GraphView::Node(const NodeID nid) const
|
||||
{
|
||||
return bisection_graph.Node(nid);
|
||||
}
|
||||
const GraphView::NodeT &GraphView::Node(const NodeID nid) const { return *(begin + nid); }
|
||||
|
||||
const GraphView::EdgeT &GraphView::Edge(const EdgeID eid) const
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "partition/partitioner.hpp"
|
||||
#include "partition/bisection_graph.hpp"
|
||||
#include "partition/recursive_bisection.hpp"
|
||||
#include "partition/recursive_bisection_stats.hpp"
|
||||
|
||||
#include "storage/io.hpp"
|
||||
#include "util/coordinate.hpp"
|
||||
@ -64,7 +65,7 @@ CompressedNodeBasedGraph LoadCompressedNodeBasedGraph(const std::string &path)
|
||||
return graph;
|
||||
}
|
||||
|
||||
void LogGeojson(const std::string &filename, const std::vector<std::uint32_t> &bisection_ids)
|
||||
void LogGeojson(const std::string &filename, std::vector<std::uint32_t> bisection_ids)
|
||||
{
|
||||
// reload graph, since we destroyed the old one
|
||||
auto compressed_node_based_graph = LoadCompressedNodeBasedGraph(filename);
|
||||
@ -95,14 +96,19 @@ void LogGeojson(const std::string &filename, const std::vector<std::uint32_t> &b
|
||||
return x;
|
||||
};
|
||||
|
||||
|
||||
std::transform(bisection_ids.begin(),bisection_ids.end(),bisection_ids.begin(),reverse_bits);
|
||||
|
||||
printBisectionStats(bisection_ids, graph);
|
||||
std::vector<std::vector<util::Coordinate>> border_vertices(33);
|
||||
|
||||
|
||||
for (NodeID nid = 0; nid < graph.NumberOfNodes(); ++nid)
|
||||
{
|
||||
const auto source_id = reverse_bits(bisection_ids[nid]);
|
||||
const auto source_id = bisection_ids[nid];
|
||||
for (const auto &edge : graph.Edges(nid))
|
||||
{
|
||||
const auto target_id = reverse_bits(bisection_ids[edge.target]);
|
||||
const auto target_id = bisection_ids[edge.target];
|
||||
if (source_id != target_id)
|
||||
{
|
||||
auto level = get_level(source_id, target_id);
|
||||
@ -147,11 +153,12 @@ int Partitioner::Run(const PartitionConfig &config)
|
||||
makeBisectionGraph(compressed_node_based_graph.coordinates,
|
||||
adaptToBisectionEdge(std::move(compressed_node_based_graph.edges)));
|
||||
|
||||
RecursiveBisection recursive_bisection(config.maximum_cell_size,
|
||||
RecursiveBisection recursive_bisection(graph,
|
||||
config.maximum_cell_size,
|
||||
config.balance,
|
||||
config.boundary_factor,
|
||||
config.num_optimizing_cuts,
|
||||
graph);
|
||||
config.small_component_size);
|
||||
|
||||
LogGeojson(config.compressed_node_based_graph_path.string(),
|
||||
recursive_bisection.BisectionIDs());
|
||||
|
@ -9,32 +9,28 @@
|
||||
|
||||
#include <tbb/parallel_do.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits> // for CHAR_BIT
|
||||
#include <cstddef>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "extractor/tarjan_scc.hpp"
|
||||
#include "partition/tarjan_graph_wrapper.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partition
|
||||
{
|
||||
|
||||
RecursiveBisection::RecursiveBisection(std::size_t maximum_cell_size,
|
||||
double balance,
|
||||
double boundary_factor,
|
||||
std::size_t num_optimizing_cuts,
|
||||
BisectionGraph &bisection_graph_)
|
||||
RecursiveBisection::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)
|
||||
: bisection_graph(bisection_graph_), internal_state(bisection_graph_)
|
||||
{
|
||||
auto components = FakeFirstPartitionWithSCC(1000 /*limit for small*/); // TODO
|
||||
auto components = internal_state.PrePartitionWithSCC(small_component_size);
|
||||
BOOST_ASSERT(!components.empty());
|
||||
|
||||
// Parallelize recursive bisection trees. Root cut happens serially (well, this is a lie:
|
||||
@ -104,74 +100,6 @@ RecursiveBisection::RecursiveBisection(std::size_t maximum_cell_size,
|
||||
util::Log() << "Full bisection done in " << TIMER_SEC(bisection) << "s";
|
||||
}
|
||||
|
||||
std::vector<GraphView>
|
||||
RecursiveBisection::FakeFirstPartitionWithSCC(const std::size_t small_component_size)
|
||||
{
|
||||
// since our graphs are unidirectional, we don't realy need the scc. But tarjan is so nice and
|
||||
// assigns IDs and counts sizes
|
||||
TarjanGraphWrapper wrapped_graph(bisection_graph);
|
||||
extractor::TarjanSCC<TarjanGraphWrapper> scc_algo(wrapped_graph);
|
||||
scc_algo.Run();
|
||||
|
||||
// Map Edges to Sccs
|
||||
const auto in_small = [&scc_algo, small_component_size](const NodeID node_id) {
|
||||
return scc_algo.GetComponentSize(scc_algo.GetComponentID(node_id)) <= small_component_size;
|
||||
};
|
||||
|
||||
const constexpr std::size_t small_component_id = -1;
|
||||
std::unordered_map<std::size_t, std::size_t> component_map;
|
||||
const auto transform_id = [&](const NodeID node_id) -> std::size_t {
|
||||
if (in_small(node_id))
|
||||
return small_component_id;
|
||||
else
|
||||
return scc_algo.GetComponentID(node_id);
|
||||
};
|
||||
|
||||
std::vector<NodeID> mapping(bisection_graph.NumberOfNodes(), SPECIAL_NODEID);
|
||||
for (const auto &node : bisection_graph.Nodes())
|
||||
mapping[node.original_id] = component_map[transform_id(node.original_id)]++;
|
||||
|
||||
// needs to remove edges, if we should ever switch to directed graphs here
|
||||
std::stable_sort(
|
||||
bisection_graph.Begin(), bisection_graph.End(), [&](const auto &lhs, const auto &rhs) {
|
||||
return transform_id(lhs.original_id) < transform_id(rhs.original_id);
|
||||
});
|
||||
|
||||
// remap all remaining edges
|
||||
std::for_each(bisection_graph.Begin(), bisection_graph.End(), [&](const auto &node) {
|
||||
for (auto &edge : bisection_graph.Edges(node))
|
||||
edge.target = mapping[edge.target];
|
||||
});
|
||||
|
||||
std::vector<GraphView> views;
|
||||
auto last = bisection_graph.CBegin();
|
||||
auto last_id = transform_id(bisection_graph.Begin()->original_id);
|
||||
|
||||
for (auto itr = bisection_graph.CBegin(); itr != bisection_graph.CEnd(); ++itr)
|
||||
{
|
||||
auto itr_id = transform_id(itr->original_id);
|
||||
if (last_id != itr_id)
|
||||
{
|
||||
views.push_back(GraphView(bisection_graph, last, itr));
|
||||
last_id = itr_id;
|
||||
last = itr;
|
||||
}
|
||||
}
|
||||
views.push_back(GraphView(bisection_graph, last, bisection_graph.CEnd()));
|
||||
|
||||
bool has_small_component = [&]() {
|
||||
for (std::size_t i = 0; i < scc_algo.GetNumberOfComponents(); ++i)
|
||||
if (scc_algo.GetComponentSize(i) <= small_component_size)
|
||||
return true;
|
||||
return false;
|
||||
}();
|
||||
|
||||
if (!has_small_component)
|
||||
views.push_back(GraphView(bisection_graph, bisection_graph.CEnd(), bisection_graph.CEnd()));
|
||||
|
||||
return views;
|
||||
}
|
||||
|
||||
const std::vector<RecursiveBisectionState::BisectionID> &RecursiveBisection::BisectionIDs() const
|
||||
{
|
||||
return internal_state.BisectionIDs();
|
||||
|
@ -1,4 +1,6 @@
|
||||
#include "partition/recursive_bisection_state.hpp"
|
||||
#include "extractor/tarjan_scc.hpp"
|
||||
#include "partition/tarjan_graph_wrapper.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
@ -6,6 +8,7 @@
|
||||
// TODO remove
|
||||
#include <bitset>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
@ -32,6 +35,10 @@ RecursiveBisectionState::ApplyBisection(const NodeIterator const_begin,
|
||||
const std::size_t depth,
|
||||
const std::vector<bool> &partition)
|
||||
{
|
||||
// ensure that the iterators belong to the graph
|
||||
BOOST_ASSERT(bisection_graph.GetID(*const_begin) < bisection_graph.NumberOfNodes() &&
|
||||
bisection_graph.GetID(*const_begin) + std::distance(const_begin, const_end) <=
|
||||
bisection_graph.NumberOfNodes());
|
||||
// augment the partition ids
|
||||
const auto flag = BisectionID{1} << depth;
|
||||
for (auto itr = const_begin; itr != const_end; ++itr)
|
||||
@ -80,6 +87,73 @@ RecursiveBisectionState::ApplyBisection(const NodeIterator const_begin,
|
||||
return const_begin + std::distance(begin, center);
|
||||
}
|
||||
|
||||
std::vector<GraphView>
|
||||
RecursiveBisectionState::PrePartitionWithSCC(const std::size_t small_component_size)
|
||||
{
|
||||
// since our graphs are unidirectional, we don't realy need the scc. But tarjan is so nice and
|
||||
// assigns IDs and counts sizes
|
||||
TarjanGraphWrapper wrapped_graph(bisection_graph);
|
||||
extractor::TarjanSCC<TarjanGraphWrapper> scc_algo(wrapped_graph);
|
||||
scc_algo.Run();
|
||||
|
||||
// Map Edges to Sccs
|
||||
const auto in_small = [&scc_algo, small_component_size](const NodeID node_id) {
|
||||
return scc_algo.GetComponentSize(scc_algo.GetComponentID(node_id)) <= small_component_size;
|
||||
};
|
||||
|
||||
const constexpr std::size_t small_component_id = -1;
|
||||
std::unordered_map<std::size_t, std::size_t> component_map;
|
||||
const auto transform_id = [&](const NodeID node_id) -> std::size_t {
|
||||
if (in_small(node_id))
|
||||
return small_component_id;
|
||||
else
|
||||
return scc_algo.GetComponentID(node_id);
|
||||
};
|
||||
|
||||
std::vector<NodeID> mapping(bisection_graph.NumberOfNodes(), SPECIAL_NODEID);
|
||||
for (const auto &node : bisection_graph.Nodes())
|
||||
mapping[node.original_id] = component_map[transform_id(node.original_id)]++;
|
||||
|
||||
// needs to remove edges, if we should ever switch to directed graphs here
|
||||
std::stable_sort(
|
||||
bisection_graph.Begin(), bisection_graph.End(), [&](const auto &lhs, const auto &rhs) {
|
||||
return transform_id(lhs.original_id) < transform_id(rhs.original_id);
|
||||
});
|
||||
|
||||
// remap all remaining edges
|
||||
std::for_each(bisection_graph.Begin(), bisection_graph.End(), [&](const auto &node) {
|
||||
for (auto &edge : bisection_graph.Edges(node))
|
||||
edge.target = mapping[edge.target];
|
||||
});
|
||||
|
||||
std::vector<GraphView> views;
|
||||
auto last = bisection_graph.CBegin();
|
||||
auto last_id = transform_id(bisection_graph.Begin()->original_id);
|
||||
for (auto itr = bisection_graph.CBegin(); itr != bisection_graph.CEnd(); ++itr)
|
||||
{
|
||||
auto itr_id = transform_id(itr->original_id);
|
||||
if (last_id != itr_id)
|
||||
{
|
||||
views.push_back(GraphView(bisection_graph, last, itr));
|
||||
last_id = itr_id;
|
||||
last = itr;
|
||||
}
|
||||
}
|
||||
views.push_back(GraphView(bisection_graph, last, bisection_graph.CEnd()));
|
||||
|
||||
bool has_small_component = [&]() {
|
||||
for (std::size_t i = 0; i < scc_algo.GetNumberOfComponents(); ++i)
|
||||
if (scc_algo.GetComponentSize(i) <= small_component_size)
|
||||
return true;
|
||||
return false;
|
||||
}();
|
||||
|
||||
if (!has_small_component)
|
||||
views.push_back(GraphView(bisection_graph, bisection_graph.CEnd(), bisection_graph.CEnd()));
|
||||
|
||||
return views;
|
||||
}
|
||||
|
||||
const std::vector<RecursiveBisectionState::BisectionID> &
|
||||
RecursiveBisectionState::BisectionIDs() const
|
||||
{
|
||||
|
97
src/partition/recursive_bisection_stats.cpp
Normal file
97
src/partition/recursive_bisection_stats.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
#include "partition/recursive_bisection_stats.hpp"
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
#include <bitset>
|
||||
#include <cstddef>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace partition
|
||||
{
|
||||
void printBisectionStats(std::vector<RecursiveBisectionState::BisectionID> const &bisection_ids,
|
||||
const BisectionGraph &graph)
|
||||
{
|
||||
BOOST_ASSERT(graph.NumberOfNodes() == bisection_ids.size());
|
||||
std::size_t total_border_nodes = 0;
|
||||
std::unordered_map<RecursiveBisectionState::BisectionID, std::size_t> cell_sizes[32];
|
||||
std::unordered_map<RecursiveBisectionState::BisectionID, std::size_t> border_nodes[32];
|
||||
|
||||
std::unordered_set<RecursiveBisectionState::BisectionID> all_ids[32];
|
||||
|
||||
|
||||
std::uint32_t flag = 0;
|
||||
for (std::uint32_t level = 0; level < 32; ++level)
|
||||
{
|
||||
flag |= (1 << level);
|
||||
for (const auto &node : graph.Nodes())
|
||||
{
|
||||
const auto bisection_id_node = bisection_ids[node.original_id];
|
||||
all_ids[level].insert(bisection_id_node&flag);
|
||||
auto is_border_node = false;
|
||||
for (const auto &edge : graph.Edges(node))
|
||||
{
|
||||
if (bisection_ids[edge.target] != bisection_id_node)
|
||||
is_border_node = true;
|
||||
}
|
||||
|
||||
if (is_border_node)
|
||||
++total_border_nodes;
|
||||
|
||||
cell_sizes[level][bisection_id_node & flag]++;
|
||||
if (is_border_node)
|
||||
{
|
||||
for (const auto &edge : graph.Edges(node))
|
||||
{
|
||||
if ((bisection_id_node & flag) != (bisection_ids[edge.target] & flag))
|
||||
{
|
||||
border_nodes[level][bisection_id_node & flag]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Partition statistics\n";
|
||||
std::cout << "Total border vertices: " << total_border_nodes << std::endl;
|
||||
unsigned level = 0;
|
||||
do
|
||||
{
|
||||
std::size_t min_size = -1, max_size = 0, total_size = 0;
|
||||
std::size_t min_border = -1, max_border = 1, total_border = 0;
|
||||
|
||||
const auto summarize =
|
||||
[](const std::unordered_map<RecursiveBisectionState::BisectionID, std::size_t> &map,
|
||||
std::size_t &min,
|
||||
std::size_t &max,
|
||||
std::size_t &total) {
|
||||
for (const auto itr : map)
|
||||
{
|
||||
min = std::min(min, itr.second);
|
||||
max = std::max(max, itr.second);
|
||||
total += itr.second;
|
||||
}
|
||||
};
|
||||
|
||||
summarize(cell_sizes[level], min_size, max_size, total_size);
|
||||
summarize(border_nodes[level], min_border, max_border, total_border);
|
||||
|
||||
std::cout << "Level: " << level << " Cells: " << cell_sizes[level].size();
|
||||
if (cell_sizes[level].size() > 1)
|
||||
std::cout << " Border: " << min_border << " " << max_border << " "
|
||||
<< total_border / (double)cell_sizes[level].size();
|
||||
|
||||
std::cout << " Cell Sizes: " << min_size << " " << max_size << " "
|
||||
<< total_size / (double)cell_sizes[level].size();
|
||||
|
||||
std::cout << std::endl;
|
||||
|
||||
} while (level < 31 && cell_sizes[level++].size() > 1);
|
||||
}
|
||||
|
||||
} // namespace partition
|
||||
} // namespace osrm
|
@ -52,6 +52,11 @@ return_code parseArguments(int argc, char *argv[], partition::PartitionConfig &p
|
||||
("optimizing-cuts",
|
||||
boost::program_options::value<std::size_t>(&partition_config.num_optimizing_cuts)
|
||||
->default_value(10),
|
||||
"Number of cuts to use for optimizing a single bisection")
|
||||
//
|
||||
("small-component-size",
|
||||
boost::program_options::value<std::size_t>(&partition_config.small_component_size)
|
||||
->default_value(1000),
|
||||
"Number of cuts to use for optimizing a single bisection");
|
||||
|
||||
// hidden options, will be allowed on command line, but will not be
|
||||
|
@ -62,6 +62,7 @@ target_compile_definitions(extractor-tests PRIVATE COMPILE_DEFINITIONS OSRM_FIXT
|
||||
target_include_directories(engine-tests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_include_directories(library-tests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_include_directories(util-tests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_include_directories(partition-tests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
|
||||
target_link_libraries(engine-tests ${ENGINE_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY})
|
||||
|
154
unit_tests/partition/bisection_graph.cpp
Normal file
154
unit_tests/partition/bisection_graph.cpp
Normal file
@ -0,0 +1,154 @@
|
||||
#include "partition/bisection_graph.hpp"
|
||||
#include "partition/graph_generator.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/test/test_case_template.hpp>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
using namespace osrm::partition;
|
||||
using namespace osrm::util;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(bisection_graph)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(access_nodes)
|
||||
{
|
||||
// 40 entries of left/right edges
|
||||
double step_size = 0.01;
|
||||
int rows = 10;
|
||||
int cols = 4;
|
||||
const auto coordinates = makeGridCoordinates(rows, cols, step_size, 0, 0);
|
||||
|
||||
std::vector<EdgeWithSomeAdditionalData> grid_edges;
|
||||
auto graph = makeBisectionGraph(coordinates, adaptToBisectionEdge(std::move(grid_edges)));
|
||||
|
||||
const auto to_row = [cols](const NodeID nid) { return nid / cols; };
|
||||
const auto to_col = [cols](const NodeID nid) { return nid % cols; };
|
||||
|
||||
const auto get_expected = [&](const NodeID id) {
|
||||
const auto expected_lon = FloatLongitude{to_col(id) * step_size};
|
||||
const auto expected_lat = FloatLatitude{to_row(id) * step_size};
|
||||
Coordinate compare(expected_lon, expected_lat);
|
||||
return compare;
|
||||
};
|
||||
|
||||
// check const access
|
||||
BOOST_CHECK_EQUAL(graph.NumberOfNodes(), 40);
|
||||
{
|
||||
NodeID increasing = 0;
|
||||
for (const auto &node : graph.Nodes())
|
||||
{
|
||||
const auto id = graph.GetID(node);
|
||||
BOOST_CHECK_EQUAL(id, increasing++);
|
||||
BOOST_CHECK_EQUAL(node.coordinate, get_expected(id));
|
||||
}
|
||||
}
|
||||
// non-const access
|
||||
{
|
||||
NodeID increasing = 0;
|
||||
for (auto &node : graph.Nodes())
|
||||
{
|
||||
const auto id = graph.GetID(node);
|
||||
BOOST_CHECK_EQUAL(id, increasing++);
|
||||
BOOST_CHECK_EQUAL(node.coordinate, get_expected(id));
|
||||
}
|
||||
}
|
||||
// iterator access
|
||||
{
|
||||
const auto begin = graph.Begin();
|
||||
const auto end = graph.End();
|
||||
NodeID increasing = 0;
|
||||
for (auto itr = begin; itr != end; ++itr)
|
||||
{
|
||||
const auto id = static_cast<NodeID>(std::distance(begin, itr));
|
||||
BOOST_CHECK_EQUAL(id, increasing++);
|
||||
BOOST_CHECK_EQUAL(itr->coordinate, get_expected(id));
|
||||
}
|
||||
}
|
||||
// const iterator access
|
||||
{
|
||||
const auto begin = graph.CBegin();
|
||||
const auto end = graph.CEnd();
|
||||
NodeID increasing = 0;
|
||||
for (auto itr = begin; itr != end; ++itr)
|
||||
{
|
||||
const auto id = static_cast<NodeID>(std::distance(begin, itr));
|
||||
BOOST_CHECK_EQUAL(id, increasing++);
|
||||
BOOST_CHECK_EQUAL(itr->coordinate, get_expected(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(access_edges)
|
||||
{
|
||||
// 40 entries of left/right edges
|
||||
double step_size = 0.01;
|
||||
int rows = 10;
|
||||
int cols = 4;
|
||||
const auto coordinates = makeGridCoordinates(rows, cols, step_size, 0, 0);
|
||||
|
||||
auto grid_edges = makeGridEdges(rows, cols, 0);
|
||||
|
||||
std::random_shuffle(grid_edges.begin(), grid_edges.end());
|
||||
groupEdgesBySource(grid_edges.begin(), grid_edges.end());
|
||||
|
||||
const auto graph = makeBisectionGraph(coordinates, adaptToBisectionEdge(std::move(grid_edges)));
|
||||
|
||||
const auto to_row = [cols](const NodeID nid) { return nid / cols; };
|
||||
const auto to_col = [cols](const NodeID nid) { return nid % cols; };
|
||||
|
||||
BOOST_CHECK_EQUAL(graph.NumberOfNodes(), 40);
|
||||
for (const auto &node : graph.Nodes())
|
||||
{
|
||||
for (const auto &edge : graph.Edges(node))
|
||||
{
|
||||
BOOST_CHECK(edge.target < graph.NumberOfNodes());
|
||||
BOOST_CHECK(std::abs(static_cast<int>(to_row(graph.GetID(node))) -
|
||||
static_cast<int>(to_row(edge.target))) <= 1);
|
||||
BOOST_CHECK(std::abs(static_cast<int>(to_col(graph.GetID(node))) -
|
||||
static_cast<int>(to_col(edge.target))) <= 1);
|
||||
}
|
||||
// itr of node
|
||||
for (auto itr = graph.BeginEdges(node); itr != graph.EndEdges(node); ++itr)
|
||||
{
|
||||
BOOST_CHECK(itr->target < graph.NumberOfNodes());
|
||||
BOOST_CHECK(std::abs(static_cast<int>(to_row(graph.GetID(node))) -
|
||||
static_cast<int>(to_row(itr->target))) <= 1);
|
||||
BOOST_CHECK(std::abs(static_cast<int>(to_col(graph.GetID(node))) -
|
||||
static_cast<int>(to_col(itr->target))) <= 1);
|
||||
}
|
||||
|
||||
// access via ID of ndoe
|
||||
const auto id = graph.GetID(node);
|
||||
for (const auto &edge : graph.Edges(id))
|
||||
{
|
||||
BOOST_CHECK(edge.target < graph.NumberOfNodes());
|
||||
BOOST_CHECK(std::abs(static_cast<int>(to_row(graph.GetID(node))) -
|
||||
static_cast<int>(to_row(edge.target))) <= 1);
|
||||
BOOST_CHECK(std::abs(static_cast<int>(to_col(graph.GetID(node))) -
|
||||
static_cast<int>(to_col(edge.target))) <= 1);
|
||||
}
|
||||
|
||||
for (auto itr = graph.BeginEdges(id); itr != graph.EndEdges(id); ++itr)
|
||||
{
|
||||
BOOST_CHECK(itr->target < graph.NumberOfNodes());
|
||||
BOOST_CHECK(std::abs(static_cast<int>(to_row(graph.GetID(node))) -
|
||||
static_cast<int>(to_row(itr->target))) <= 1);
|
||||
BOOST_CHECK(std::abs(static_cast<int>(to_col(graph.GetID(node))) -
|
||||
static_cast<int>(to_col(itr->target))) <= 1);
|
||||
}
|
||||
for (auto eid = graph.BeginEdgeID(id); eid != graph.EndEdgeID(id); ++eid)
|
||||
{
|
||||
const auto &itr = graph.Edge(eid);
|
||||
BOOST_CHECK(itr.target < graph.NumberOfNodes());
|
||||
BOOST_CHECK(std::abs(static_cast<int>(to_row(graph.GetID(node))) -
|
||||
static_cast<int>(to_row(itr.target))) <= 1);
|
||||
BOOST_CHECK(std::abs(static_cast<int>(to_col(graph.GetID(node))) -
|
||||
static_cast<int>(to_col(itr.target))) <= 1);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
75
unit_tests/partition/dinic.cpp
Normal file
75
unit_tests/partition/dinic.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
#include "partition/dinic_max_flow.hpp"
|
||||
#include "partition/graph_generator.hpp"
|
||||
#include "partition/graph_view.hpp"
|
||||
#include "partition/recursive_bisection_state.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/test/test_case_template.hpp>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
using namespace osrm::partition;
|
||||
using namespace osrm::util;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(dinic_algorithm)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(horizontal_cut_between_two_grids)
|
||||
{
|
||||
// 40 entries of left/right edges
|
||||
const double step_size = 0.01;
|
||||
const int rows = 10;
|
||||
const int cols = 10;
|
||||
|
||||
// build a small grid (10*10) and a (100 * 10) below (to make the different steps unique)
|
||||
auto graph = [&]() {
|
||||
std::vector<Coordinate> grid_coordinates;
|
||||
std::vector<EdgeWithSomeAdditionalData> grid_edges;
|
||||
|
||||
const auto connect = [&grid_edges](const NodeID from, const NodeID to) {
|
||||
grid_edges.push_back({from, to, 1});
|
||||
grid_edges.push_back({to, from, 1});
|
||||
};
|
||||
|
||||
// 10 rows of large components, interrupted by small disconnected components
|
||||
const auto small_coordinates = makeGridCoordinates(rows, cols, step_size, 0, 0);
|
||||
grid_coordinates.insert(
|
||||
grid_coordinates.end(), small_coordinates.begin(), small_coordinates.end());
|
||||
|
||||
// connect the grid edges, starting with i * (rows * cols + 1) as first id (0,11,22...)
|
||||
const auto small_edges = makeGridEdges(rows, cols, 0);
|
||||
grid_edges.insert(grid_edges.end(), small_edges.begin(), small_edges.end());
|
||||
|
||||
const auto large_coordinates =
|
||||
makeGridCoordinates(10 * rows, cols, step_size, 0, rows * step_size);
|
||||
grid_coordinates.insert(
|
||||
grid_coordinates.end(), large_coordinates.begin(), large_coordinates.end());
|
||||
const auto large_edges = makeGridEdges(10 * rows, cols, (rows * cols));
|
||||
grid_edges.insert(grid_edges.end(), large_edges.begin(), large_edges.end());
|
||||
|
||||
connect(45, 1001);
|
||||
connect(55, 800);
|
||||
connect(65, 600);
|
||||
connect(75, 200);
|
||||
|
||||
groupEdgesBySource(grid_edges.begin(), grid_edges.end());
|
||||
return makeBisectionGraph(grid_coordinates, adaptToBisectionEdge(std::move(grid_edges)));
|
||||
}();
|
||||
|
||||
RecursiveBisectionState bisection_state(graph);
|
||||
GraphView view(graph);
|
||||
|
||||
DinicMaxFlow::SourceSinkNodes sources, sinks;
|
||||
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
sources.insert(static_cast<NodeID>(i));
|
||||
sinks.insert(static_cast<NodeID>(1000 + i));
|
||||
}
|
||||
|
||||
DinicMaxFlow flow;
|
||||
const auto cut = flow(view, sources, sinks);
|
||||
BOOST_CHECK(cut.num_edges == 4);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
80
unit_tests/partition/graph_generator.hpp
Normal file
80
unit_tests/partition/graph_generator.hpp
Normal file
@ -0,0 +1,80 @@
|
||||
#ifndef OSRM_UNIT_TEST_PARTITION_GRAPH_GENERATOR_HPP
|
||||
#define OSRM_UNIT_TEST_PARTITION_GRAPH_GENERATOR_HPP
|
||||
|
||||
#include "util/coordinate.hpp"
|
||||
#include "util/typedefs.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
using namespace osrm::util;
|
||||
|
||||
struct EdgeWithSomeAdditionalData
|
||||
{
|
||||
NodeID source;
|
||||
NodeID target;
|
||||
unsigned important_data;
|
||||
};
|
||||
|
||||
inline Coordinate
|
||||
makeCoordinate(int x, int y, double step_size, double offset_x = 0, double offset_y = 0)
|
||||
{
|
||||
return {FloatLongitude{offset_x + x * step_size}, FloatLatitude{offset_y + y * step_size}};
|
||||
}
|
||||
|
||||
std::vector<Coordinate> inline makeGridCoordinates(
|
||||
int rows, int columns, double step_size, double lon_base, double lat_base)
|
||||
{
|
||||
std::vector<Coordinate> result;
|
||||
|
||||
for (int r = 0; r < rows; ++r)
|
||||
for (int c = 0; c < columns; ++c)
|
||||
result.push_back(makeCoordinate(c, r, step_size, lon_base, lat_base));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::vector<EdgeWithSomeAdditionalData> makeGridEdges(int rows, int columns, int id_base)
|
||||
{
|
||||
const int min_id = id_base;
|
||||
const int max_id = id_base + rows * columns;
|
||||
const auto get_id = [id_base, columns](int r, int c) { return id_base + r * columns + c; };
|
||||
const auto valid = [min_id, max_id](int id) { return id >= min_id && id < max_id; };
|
||||
|
||||
std::vector<EdgeWithSomeAdditionalData> edges;
|
||||
|
||||
for (int r = 0; r < rows; ++r)
|
||||
{
|
||||
for (int c = 0; c < columns; ++c)
|
||||
{
|
||||
auto id = static_cast<NodeID>(get_id(r, c));
|
||||
if (c > 0)
|
||||
{
|
||||
auto left = get_id(r, c - 1);
|
||||
edges.push_back({id, static_cast<NodeID>(left), 1});
|
||||
}
|
||||
if (c + 1 < columns)
|
||||
{
|
||||
auto right = get_id(r, c + 1);
|
||||
if (valid(right))
|
||||
edges.push_back({id, static_cast<NodeID>(right), 1});
|
||||
}
|
||||
if (r > 0)
|
||||
{
|
||||
auto top = get_id(r - 1, c);
|
||||
if (valid(top))
|
||||
edges.push_back({id, static_cast<NodeID>(top), 1});
|
||||
}
|
||||
if (r + 1 < rows)
|
||||
{
|
||||
auto bottom = get_id(r + 1, c);
|
||||
if (valid(bottom))
|
||||
edges.push_back({id, static_cast<NodeID>(bottom), 1});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return edges;
|
||||
}
|
||||
|
||||
#endif // OSRM_UNIT_TEST_PARTITION_GRAPH_GENERATOR_HPP
|
175
unit_tests/partition/graph_view.cpp
Normal file
175
unit_tests/partition/graph_view.cpp
Normal file
@ -0,0 +1,175 @@
|
||||
#include "partition/graph_view.hpp"
|
||||
#include "partition/graph_generator.hpp"
|
||||
#include "partition/recursive_bisection_state.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/test/test_case_template.hpp>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
using namespace osrm::partition;
|
||||
using namespace osrm::util;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(graph_view)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(separate_top_bottom)
|
||||
{
|
||||
// 40 entries of left/right edges
|
||||
double step_size = 0.01;
|
||||
int rows = 2;
|
||||
int cols = 4;
|
||||
const auto coordinates = makeGridCoordinates(rows, cols, step_size, 0, 0);
|
||||
|
||||
auto grid_edges = makeGridEdges(rows, cols, 0);
|
||||
|
||||
std::random_shuffle(grid_edges.begin(), grid_edges.end());
|
||||
groupEdgesBySource(grid_edges.begin(), grid_edges.end());
|
||||
|
||||
auto graph = makeBisectionGraph(coordinates, adaptToBisectionEdge(std::move(grid_edges)));
|
||||
RecursiveBisectionState bisection_state(graph);
|
||||
std::vector<bool> partition(8, false);
|
||||
partition[4] = partition[5] = partition[6] = partition[7] = true;
|
||||
|
||||
const auto center = bisection_state.ApplyBisection(graph.Begin(), graph.End(), 0, partition);
|
||||
GraphView left(graph, graph.Begin(), center);
|
||||
GraphView right(graph, center, graph.End());
|
||||
|
||||
BOOST_CHECK_EQUAL(left.NumberOfNodes(), 4);
|
||||
for (const auto &node : left.Nodes())
|
||||
{
|
||||
auto id = left.GetID(node);
|
||||
const auto compare = makeCoordinate(id, 0, step_size);
|
||||
BOOST_CHECK_EQUAL(compare, node.coordinate);
|
||||
BOOST_CHECK(id < left.NumberOfNodes());
|
||||
BOOST_CHECK_EQUAL(bisection_state.GetBisectionID(node.original_id), 0);
|
||||
|
||||
for (const auto &edge : left.Edges(node))
|
||||
BOOST_CHECK(edge.target < left.NumberOfNodes());
|
||||
}
|
||||
|
||||
BOOST_CHECK_EQUAL(right.NumberOfNodes(), 4);
|
||||
for (const auto &node : right.Nodes())
|
||||
{
|
||||
BOOST_CHECK_EQUAL(bisection_state.GetBisectionID(node.original_id), 1);
|
||||
auto id = right.GetID(node);
|
||||
const auto compare = makeCoordinate(id, 1, step_size);
|
||||
BOOST_CHECK_EQUAL(compare, node.coordinate);
|
||||
BOOST_CHECK(id < right.NumberOfNodes());
|
||||
for (const auto &edge : right.Edges(node))
|
||||
BOOST_CHECK(edge.target < right.NumberOfNodes());
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(separate_top_bottom_copy)
|
||||
{
|
||||
// 40 entries of left/right edges
|
||||
double step_size = 0.01;
|
||||
int rows = 2;
|
||||
int cols = 4;
|
||||
const auto coordinates = makeGridCoordinates(rows, cols, step_size, 0, 0);
|
||||
|
||||
auto grid_edges = makeGridEdges(rows, cols, 0);
|
||||
|
||||
std::random_shuffle(grid_edges.begin(), grid_edges.end());
|
||||
groupEdgesBySource(grid_edges.begin(), grid_edges.end());
|
||||
|
||||
auto graph = makeBisectionGraph(coordinates, adaptToBisectionEdge(std::move(grid_edges)));
|
||||
RecursiveBisectionState bisection_state(graph);
|
||||
std::vector<bool> partition(8, false);
|
||||
partition[4] = partition[5] = partition[6] = partition[7] = true;
|
||||
|
||||
const auto center = bisection_state.ApplyBisection(graph.Begin(), graph.End(), 0, partition);
|
||||
GraphView total(graph, graph.Begin(), graph.End());
|
||||
|
||||
GraphView left(total, total.Begin(), center);
|
||||
GraphView right(total, center, total.End());
|
||||
|
||||
BOOST_CHECK_EQUAL(left.NumberOfNodes(), 4);
|
||||
for (const auto &node : left.Nodes())
|
||||
{
|
||||
auto id = left.GetID(node);
|
||||
const auto compare = makeCoordinate(id, 0, step_size);
|
||||
BOOST_CHECK_EQUAL(compare, node.coordinate);
|
||||
BOOST_CHECK(id < left.NumberOfNodes());
|
||||
BOOST_CHECK_EQUAL(bisection_state.GetBisectionID(node.original_id), 0);
|
||||
|
||||
for( const auto & edge : left.Edges(id) )
|
||||
BOOST_CHECK(edge.target < left.NumberOfNodes());
|
||||
}
|
||||
|
||||
BOOST_CHECK_EQUAL(right.NumberOfNodes(), 4);
|
||||
for( NodeID id = 0; id < right.NumberOfNodes(); ++id )
|
||||
{
|
||||
const auto &node = right.Node(id);
|
||||
BOOST_CHECK_EQUAL(bisection_state.GetBisectionID(node.original_id), 1);
|
||||
const auto compare = makeCoordinate(id, 1, step_size);
|
||||
BOOST_CHECK_EQUAL(compare, node.coordinate);
|
||||
BOOST_CHECK(id < right.NumberOfNodes());
|
||||
for (const auto &edge : right.Edges(id))
|
||||
BOOST_CHECK(edge.target < right.NumberOfNodes());
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(separate_left_right)
|
||||
{
|
||||
// 40 entries of left/right edges
|
||||
double step_size = 0.01;
|
||||
int rows = 2;
|
||||
int cols = 4;
|
||||
const auto coordinates = makeGridCoordinates(rows, cols, step_size, 0, 0);
|
||||
|
||||
auto grid_edges = makeGridEdges(rows, cols, 0);
|
||||
|
||||
std::random_shuffle(grid_edges.begin(), grid_edges.end());
|
||||
groupEdgesBySource(grid_edges.begin(), grid_edges.end());
|
||||
|
||||
auto graph = makeBisectionGraph(coordinates, adaptToBisectionEdge(std::move(grid_edges)));
|
||||
// separate Left 2 nodes from rest
|
||||
RecursiveBisectionState bisection_state(graph);
|
||||
std::vector<bool> partition(8, true);
|
||||
partition[0] = partition[4] = false;
|
||||
|
||||
const auto center = bisection_state.ApplyBisection(graph.Begin(), graph.End(), 0, partition);
|
||||
GraphView left(graph, graph.Begin(), center);
|
||||
GraphView right(graph, center, graph.End());
|
||||
|
||||
BOOST_CHECK_EQUAL(left.NumberOfNodes(), 2);
|
||||
std::vector<Coordinate> left_coordinates;
|
||||
left_coordinates.push_back(makeCoordinate(0, 0, step_size));
|
||||
left_coordinates.push_back(makeCoordinate(0, 1, step_size));
|
||||
auto left_compare = left_coordinates.begin();
|
||||
for (const auto &node : left.Nodes())
|
||||
{
|
||||
auto id = left.GetID(node);
|
||||
const auto compare = *left_compare++;
|
||||
BOOST_CHECK_EQUAL(compare, node.coordinate);
|
||||
BOOST_CHECK(id < left.NumberOfNodes());
|
||||
BOOST_CHECK_EQUAL(bisection_state.GetBisectionID(node.original_id), 0);
|
||||
|
||||
for (const auto &edge : left.Edges(node))
|
||||
BOOST_CHECK(edge.target < left.NumberOfNodes());
|
||||
}
|
||||
|
||||
BOOST_CHECK_EQUAL(right.NumberOfNodes(), 6);
|
||||
std::vector<Coordinate> right_coordinates;
|
||||
right_coordinates.push_back(makeCoordinate(1, 0, step_size));
|
||||
right_coordinates.push_back(makeCoordinate(2, 0, step_size));
|
||||
right_coordinates.push_back(makeCoordinate(3, 0, step_size));
|
||||
right_coordinates.push_back(makeCoordinate(1, 1, step_size));
|
||||
right_coordinates.push_back(makeCoordinate(2, 1, step_size));
|
||||
right_coordinates.push_back(makeCoordinate(3, 1, step_size));
|
||||
auto right_compare = right_coordinates.begin();
|
||||
for (const auto &node : right.Nodes())
|
||||
{
|
||||
BOOST_CHECK_EQUAL(bisection_state.GetBisectionID(node.original_id), 1);
|
||||
auto id = right.GetID(node);
|
||||
const auto compare = *right_compare++;
|
||||
BOOST_CHECK_EQUAL(compare, node.coordinate);
|
||||
BOOST_CHECK(id < right.NumberOfNodes());
|
||||
for (const auto &edge : right.Edges(node))
|
||||
BOOST_CHECK(edge.target < right.NumberOfNodes());
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
79
unit_tests/partition/recursive_bisection.cpp
Normal file
79
unit_tests/partition/recursive_bisection.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
#include "partition/recursive_bisection.hpp"
|
||||
#include "partition/graph_generator.hpp"
|
||||
#include "partition/recursive_bisection_state.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/test/test_case_template.hpp>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
using namespace osrm::partition;
|
||||
using namespace osrm::util;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(recursive_bisection)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dividing_four_grid_cells)
|
||||
{
|
||||
// 40 entries of left/right edges
|
||||
const double step_size = 0.01;
|
||||
const int rows = 10;
|
||||
const int cols = 10;
|
||||
const int cut_edges = 4;
|
||||
|
||||
auto graph = [&]() {
|
||||
std::vector<Coordinate> grid_coordinates;
|
||||
std::vector<EdgeWithSomeAdditionalData> grid_edges;
|
||||
|
||||
const auto connect =
|
||||
[&grid_edges](int min_left, int max_left, int min_right, int max_right) {
|
||||
const NodeID source = (rand() % (max_left - min_left)) + min_left;
|
||||
const NodeID target = (rand() % (max_right - min_right)) + min_right;
|
||||
|
||||
grid_edges.push_back({source, target, 1});
|
||||
grid_edges.push_back({target, source, 1});
|
||||
};
|
||||
|
||||
// generate 10 big components
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
// 10 rows of large components, interrupted by small disconnected components
|
||||
const auto coordinates = makeGridCoordinates(
|
||||
rows, cols, step_size, cols * (i % 2), (i * rows / 2) * step_size);
|
||||
grid_coordinates.insert(grid_coordinates.end(), coordinates.begin(), coordinates.end());
|
||||
|
||||
// connect the grid edges, starting with i * (rows * cols + 1) as first id (0,11,22...)
|
||||
const auto edges = makeGridEdges(rows, cols, i * (rows * cols));
|
||||
grid_edges.insert(grid_edges.end(), edges.begin(), edges.end());
|
||||
}
|
||||
|
||||
// add cut edges between neighboring cells
|
||||
int n = rows * cols;
|
||||
for (int i = 0; i < cut_edges; ++i)
|
||||
{
|
||||
// left/right
|
||||
connect(0, n, n, 2 * n);
|
||||
connect(2 * n, 3 * n, 3 * n, 4 * n);
|
||||
// top/bottom
|
||||
connect(0, n, 2 * n, 3 * n);
|
||||
connect(n, 2 * n, 3 * n, 4 * n);
|
||||
}
|
||||
groupEdgesBySource(grid_edges.begin(), grid_edges.end());
|
||||
return makeBisectionGraph(grid_coordinates, adaptToBisectionEdge(std::move(grid_edges)));
|
||||
}();
|
||||
|
||||
RecursiveBisection bisection(graph, 120, 1.1, 0.25, 10, 1);
|
||||
|
||||
const auto result = bisection.BisectionIDs();
|
||||
// all same IDs withing a group
|
||||
for (int i = 0; i < 4; ++i)
|
||||
for (int j = 0; j < rows * cols; ++j)
|
||||
BOOST_CHECK(result[i * (rows * cols)] == result[i * (rows * cols) + j]);
|
||||
|
||||
// different IDs for all four groups
|
||||
for (int i = 0; i < 4; ++i)
|
||||
for (int j = 0; j < 4; ++j)
|
||||
BOOST_CHECK(i == j || result[i * (rows * cols)] != result[j * (rows * cols)]);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
@ -35,10 +35,10 @@ BOOST_AUTO_TEST_CASE(reordering_n_shuffles_n_smallest_to_front_n_largest_to_back
|
||||
reorderFirstLast(begin(range), end(range), 2, std::less<>{});
|
||||
|
||||
// Smallest at front, but: no ordering guarantee in that subrange!
|
||||
BOOST_CHECK((range[0] == 2 and range[1] == 3) or (range[0] == 3 and range[1] == 2));
|
||||
BOOST_CHECK((range[0] == 2 && range[1] == 3) || (range[0] == 3 && range[1] == 2));
|
||||
|
||||
// Largest at back, but: no ordering guarantee in that subrange!
|
||||
BOOST_CHECK((range[2] == 8 and range[3] == 9) or (range[2] == 9 and range[3] == 8));
|
||||
BOOST_CHECK((range[2] == 8 && range[3] == 9) || (range[2] == 9 && range[3] == 8));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(reordering_n_with_iterators)
|
||||
|
80
unit_tests/partition/scc_integration.cpp
Normal file
80
unit_tests/partition/scc_integration.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
#include "partition/graph_generator.hpp"
|
||||
#include "partition/graph_view.hpp"
|
||||
#include "partition/recursive_bisection_state.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/test/test_case_template.hpp>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
using namespace osrm::partition;
|
||||
using namespace osrm::util;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(scc_integration)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(graph_views_on_components)
|
||||
{
|
||||
// 40 entries of left/right edges
|
||||
const double step_size = 0.01;
|
||||
const int rows = 1;
|
||||
const int cols = 10;
|
||||
|
||||
const int num_components = 10;
|
||||
|
||||
auto graph = [&]() {
|
||||
std::vector<Coordinate> grid_coordinates;
|
||||
std::vector<EdgeWithSomeAdditionalData> grid_edges;
|
||||
|
||||
// generate 10 big components
|
||||
for (int i = 0; i < num_components; ++i)
|
||||
{
|
||||
// 10 rows of large components, interrupted by small disconnected components
|
||||
const auto coordinates = makeGridCoordinates(rows, cols, step_size, 0, i * step_size);
|
||||
grid_coordinates.insert(grid_coordinates.end(), coordinates.begin(), coordinates.end());
|
||||
|
||||
// add a single disconnected node to have ids between large components
|
||||
if (i + 1 < num_components)
|
||||
grid_coordinates.push_back(
|
||||
makeCoordinate(1, 1, 0.5 * step_size, 0, (i + 1) * step_size));
|
||||
|
||||
// connect the grid edges, starting with i * (rows * cols + 1) as first id (0,11,22...)
|
||||
const auto edges = makeGridEdges(rows, cols, i * (rows * cols + 1));
|
||||
grid_edges.insert(grid_edges.end(), edges.begin(), edges.end());
|
||||
}
|
||||
|
||||
groupEdgesBySource(grid_edges.begin(), grid_edges.end());
|
||||
return makeBisectionGraph(grid_coordinates, adaptToBisectionEdge(std::move(grid_edges)));
|
||||
}();
|
||||
|
||||
RecursiveBisectionState bisection_state(graph);
|
||||
|
||||
auto views = bisection_state.PrePartitionWithSCC(2);
|
||||
BOOST_CHECK_EQUAL(views.size(), num_components + 1); // big components + 1 small one
|
||||
|
||||
for (std::size_t comp = 0; comp < num_components; ++comp)
|
||||
{
|
||||
const auto &view = views[comp];
|
||||
BOOST_CHECK(views[comp].NumberOfNodes() == 10);
|
||||
|
||||
const auto to_component_id = [&](const auto &node) {
|
||||
return node.original_id / (rows * cols + 1);
|
||||
};
|
||||
|
||||
std::size_t expected_component_id = to_component_id(view.Node(0));
|
||||
BOOST_CHECK(std::all_of(view.Begin(), view.End(), [&](const auto &node) {
|
||||
return to_component_id(node) == expected_component_id;
|
||||
}));
|
||||
|
||||
for (const auto &node : view.Nodes())
|
||||
{
|
||||
for (const auto &edge : view.Edges(node))
|
||||
{
|
||||
BOOST_CHECK(edge.target < view.NumberOfNodes());
|
||||
}
|
||||
}
|
||||
}
|
||||
BOOST_CHECK(views.back().NumberOfNodes() == 9);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
Loading…
Reference in New Issue
Block a user