add unit tests for the different components of the parttion tool

This commit is contained in:
Moritz Kobitzsch 2017-02-02 15:53:42 +01:00 committed by Patrick Niklaus
parent e316dad1cb
commit b789da45bd
25 changed files with 917 additions and 152 deletions

View File

@ -197,6 +197,7 @@ script:
- ./unit_tests/engine-tests
- ./unit_tests/util-tests
- ./unit_tests/server-tests
- ./unit_tests/partition-tests
- popd
- npm test

View File

@ -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)
{

View File

@ -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)

View File

@ -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)); }

View File

@ -47,6 +47,7 @@ struct PartitionConfig
double balance;
double boundary_factor;
std::size_t num_optimizing_cuts;
std::size_t small_component_size;
};
}
}

View File

@ -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(); }

View File

@ -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

View File

@ -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:

View 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_

View File

@ -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:

View File

@ -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

View File

@ -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
{

View File

@ -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());

View File

@ -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();

View File

@ -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
{

View 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

View File

@ -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

View File

@ -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})

View 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()

View 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()

View 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

View 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()

View 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()

View File

@ -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)

View 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()