From 8f9e9809459781509f0d45d18b1aec44144ac703 Mon Sep 17 00:00:00 2001 From: Patrick Niklaus Date: Sun, 5 Mar 2017 20:31:45 +0000 Subject: [PATCH] Add class to translate from bisection ids to cell ids --- include/partition/bisection_to_partition.hpp | 24 ++ include/partition/multi_level_partition.hpp | 24 +- include/util/msb.hpp | 44 +++ src/partition/bisection_to_partition.cpp | 138 ++++++++ src/partition/partitioner.cpp | 123 +------ .../partition/bisection_to_partition.cpp | 331 ++++++++++++++++++ 6 files changed, 558 insertions(+), 126 deletions(-) create mode 100644 include/partition/bisection_to_partition.hpp create mode 100644 include/util/msb.hpp create mode 100644 src/partition/bisection_to_partition.cpp create mode 100644 unit_tests/partition/bisection_to_partition.cpp diff --git a/include/partition/bisection_to_partition.hpp b/include/partition/bisection_to_partition.hpp new file mode 100644 index 000000000..526110f54 --- /dev/null +++ b/include/partition/bisection_to_partition.hpp @@ -0,0 +1,24 @@ +#ifndef BISECTION_TO_PARTITION_HPP +#define BISECTION_TO_PARTITION_HPP + +#include "partition/multi_level_partition.hpp" +#include "partition/recursive_bisection.hpp" + +#include + +namespace osrm +{ +namespace partition +{ + +using Partition = std::vector; + +// Converts a representation of the bisection to cell ids over multiple level +std::tuple, std::vector> +bisectionToPartition(const std::vector &node_to_bisection_id, + const std::vector &max_cell_sizes); + +} +} + +#endif diff --git a/include/partition/multi_level_partition.hpp b/include/partition/multi_level_partition.hpp index 0475198c9..f0145c68f 100644 --- a/include/partition/multi_level_partition.hpp +++ b/include/partition/multi_level_partition.hpp @@ -5,6 +5,7 @@ #include "util/for_each_pair.hpp" #include "util/shared_memory_vector_wrapper.hpp" #include "util/typedefs.hpp" +#include "util/msb.hpp" #include "storage/io.hpp" @@ -38,27 +39,6 @@ void write(const boost::filesystem::path &file, namespace detail { -// get the msb of an integer -// return 0 for integers without msb -template std::size_t highestMSB(T value) -{ - static_assert(std::is_integral::value, "Integer required."); - std::size_t msb = 0; - while (value > 0) - { - value >>= 1u; - msb++; - } - return msb; -} - -#if (defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)) && __x86_64__ -inline std::size_t highestMSB(std::uint64_t v) -{ - BOOST_ASSERT(v > 0); - return 63UL - __builtin_clzl(v); -} -#endif } using LevelID = std::uint8_t; @@ -132,7 +112,7 @@ template class MultiLevelPartitionImpl final if (partition[first] == partition[second]) return 0; - auto msb = detail::highestMSB(partition[first] ^ partition[second]); + auto msb = util::msb(partition[first] ^ partition[second]); return level_data.bit_to_level[msb]; } diff --git a/include/util/msb.hpp b/include/util/msb.hpp new file mode 100644 index 000000000..a76b79f16 --- /dev/null +++ b/include/util/msb.hpp @@ -0,0 +1,44 @@ +#ifndef OSRM_UTIL_MSB_HPP +#define OSRM_UTIL_MSB_HPP + +#include + +#include +#include + +namespace osrm +{ +namespace util +{ + +// get the msb of an integer +// return 0 for integers without msb +template std::size_t msb(T value) +{ + static_assert(std::is_integral::value, "Integer required."); + std::size_t msb = 0; + while (value > 0) + { + value >>= 1u; + msb++; + } + return msb; +} + +#if (defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)) && __x86_64__ +inline std::size_t msb(std::uint64_t v) +{ + BOOST_ASSERT(v > 0); + return 63UL - __builtin_clzl(v); +} +inline std::size_t msb(std::uint32_t v) +{ + BOOST_ASSERT(v > 0); + return 31UL - __builtin_clz(v); +} +#endif + +} +} + +#endif diff --git a/src/partition/bisection_to_partition.cpp b/src/partition/bisection_to_partition.cpp new file mode 100644 index 000000000..0e0ddd8c2 --- /dev/null +++ b/src/partition/bisection_to_partition.cpp @@ -0,0 +1,138 @@ +#include "partition/bisection_to_partition.hpp" + +namespace osrm +{ +namespace partition +{ + +namespace +{ +struct CellBisection +{ + std::uint32_t begin; + std::uint32_t end; + std::uint8_t depth; + bool tabu; // we will not attempt to split this cell anymore +}; +static constexpr std::size_t NUM_BISECTION_BITS = sizeof(BisectionID) * CHAR_BIT; + +bool getSide(std::uint8_t depth, BisectionID id) +{ + return id & (1UL << (NUM_BISECTION_BITS - 1 - depth)); +} + +std::vector getLargeCells(const std::size_t max_cell_size, + const std::vector &cells) +{ + std::vector large_cells; + + for (auto index = 0u; index < cells.size(); ++index) + { + if (!cells[index].tabu && cells[index].end - cells[index].begin > max_cell_size) + large_cells.push_back(index); + } + + return large_cells; +} + +Partition cellsToPartition(const std::vector &cells, + const std::vector &permutation) +{ + Partition partition(permutation.size(), INVALID_CELL_ID); + CellID cell_id = 0; + for (const auto &cell : cells) + { + std::for_each(permutation.begin() + cell.begin, + permutation.begin() + cell.end, + [&partition, cell_id](const auto node_id) { partition[node_id] = cell_id; }); + cell_id++; + } + + return partition; +} + +void partitionLevel(const std::vector &node_to_bisection_id, + std::size_t max_cell_size, + std::vector &permutation, + std::vector &cells) +{ + for (auto large_cells = getLargeCells(max_cell_size, cells); large_cells.size() > 0; + large_cells = getLargeCells(max_cell_size, cells)) + { + for (const auto cell_index : large_cells) + { + auto &cell = cells[cell_index]; + BOOST_ASSERT(cell.depth < NUM_BISECTION_BITS); + + // Go over all nodes and sum up the bits to determine at which position the first one bit is + BisectionID sum = + std::accumulate(permutation.begin() + cell.begin, + permutation.begin() + cell.end, + BisectionID{0}, + [&node_to_bisection_id](const BisectionID lhs, const NodeID rhs) { + return lhs | node_to_bisection_id[rhs]; + }); + // masks all bit strictly higher then cell.depth + const BisectionID mask = (1UL << (NUM_BISECTION_BITS - cell.depth)) - 1; + const auto masked_sum = sum & mask; + // we can't split the cell anymore, but it also doesn't conform to the max size constraint + // -> we need to remove it from the optimization + if (masked_sum == 0) + { + cell.tabu = true; + continue; + } + const auto msb = util::msb(sum & mask); + // depth counts from MSB to LSB + const auto depth = NUM_BISECTION_BITS - 1 - msb; + + std::uint32_t middle = + std::partition(permutation.begin() + cell.begin, + permutation.begin() + cell.end, + [depth, &node_to_bisection_id](const auto node_id) { + return getSide(depth, node_to_bisection_id[node_id]); + }) - + permutation.begin(); + + cell.depth = depth + 1; + if (middle != cell.begin && middle != cell.end) + { + auto old_end = cell.end; + cell.end = middle; + cells.push_back( + CellBisection{middle, old_end, static_cast(depth + 1), false}); + } + } + } +} +} + +// Implements a greedy algorithm that split cells using the bisection until a target cell size is +// reached +std::tuple, std::vector> +bisectionToPartition(const std::vector &node_to_bisection_id, + const std::vector &max_cell_sizes) +{ + std::vector permutation(node_to_bisection_id.size()); + std::iota(permutation.begin(), permutation.end(), 0); + + std::vector cells; + cells.push_back(CellBisection{0, static_cast(node_to_bisection_id.size()), 0, false}); + + std::vector partitions(max_cell_sizes.size()); + std::vector num_cells(max_cell_sizes.size()); + + auto level_idx = max_cell_sizes.size() - 1; + for (auto max_cell_size : boost::adaptors::reverse(max_cell_sizes)) + { + partitionLevel(node_to_bisection_id, max_cell_size, permutation, cells); + + partitions[level_idx] = cellsToPartition(cells, permutation); + num_cells[level_idx] = cells.size(); + level_idx--; + } + + return std::make_tuple(std::move(partitions), std::move(num_cells)); +} +} +} diff --git a/src/partition/partitioner.cpp b/src/partition/partitioner.cpp index 6306634ae..a1026d61d 100644 --- a/src/partition/partitioner.cpp +++ b/src/partition/partitioner.cpp @@ -1,12 +1,12 @@ #include "partition/partitioner.hpp" -#include "partition/annotated_partition.hpp" #include "partition/bisection_graph.hpp" +#include "partition/bisection_to_partition.hpp" #include "partition/compressed_node_based_graph_reader.hpp" #include "partition/edge_based_graph_reader.hpp" #include "partition/io.hpp" +#include "partition/multi_level_partition.hpp" #include "partition/node_based_graph_to_edge_based_graph_mapping_reader.hpp" #include "partition/recursive_bisection.hpp" -#include "partition/multi_level_partition.hpp" #include "util/coordinate.hpp" #include "util/geojson_debug_logger.hpp" @@ -32,27 +32,6 @@ namespace osrm namespace partition { -void LogStatistics(const std::string &filename, std::vector bisection_ids) -{ - auto compressed_node_based_graph = LoadCompressedNodeBasedGraph(filename); - - util::Log() << "Loaded compressed node based graph: " - << compressed_node_based_graph.edges.size() << " edges, " - << compressed_node_based_graph.coordinates.size() << " nodes"; - - groupEdgesBySource(begin(compressed_node_based_graph.edges), - end(compressed_node_based_graph.edges)); - - auto graph = - makeBisectionGraph(compressed_node_based_graph.coordinates, - adaptToBisectionEdge(std::move(compressed_node_based_graph.edges))); - - TIMER_START(annotation); - AnnotatedPartition partition(graph, bisection_ids); - TIMER_STOP(annotation); - std::cout << "Annotation took " << TIMER_SEC(annotation) << " seconds" << std::endl; -} - void LogGeojson(const std::string &filename, const std::vector &bisection_ids) { // reload graph, since we destroyed the old one @@ -138,9 +117,6 @@ int Partitioner::Run(const PartitionConfig &config) config.num_optimizing_cuts, config.small_component_size); - LogStatistics(config.compressed_node_based_graph_path.string(), - recursive_bisection.BisectionIDs()); - // Up until now we worked on the compressed node based graph. // But what we actually need is a partition for the edge based graph to work on. // The following loads a mapping from node based graph to edge based graph. @@ -184,87 +160,26 @@ int Partitioner::Run(const PartitionConfig &config) } } - // FIXME The CellID computation code need to be replaced by a more sophisticated method - { - BOOST_ASSERT(edge_based_partition_ids.size() == edge_based_graph->GetNumberOfNodes()); + std::vector partitions; + std::vector level_to_num_cells; + std::tie(partitions, level_to_num_cells) = bisectionToPartition(edge_based_partition_ids, + {128, 128 * 32, 128 * 32 * 16, 128 * 32 * 16 * 32}); - using namespace osrm::partition; + TIMER_START(packed_mlp); + MultiLevelPartition mlp{partitions, level_to_num_cells}; + TIMER_STOP(packed_mlp); + util::Log() << "MultiLevelPartition constructed in " << TIMER_SEC(packed_mlp) << " seconds"; - // find bit size of bisection ids - int first_nonzero_position = sizeof(BisectionID) * CHAR_BIT; - for (auto id : edge_based_partition_ids) - { - first_nonzero_position = id == 0 ? first_nonzero_position - : std::min(first_nonzero_position, __builtin_ctz(id)); - } - BOOST_ASSERT(first_nonzero_position != sizeof(BisectionID) * CHAR_BIT); + TIMER_START(cell_storage); + CellStorage storage(mlp, *edge_based_graph); + TIMER_STOP(cell_storage); + util::Log() << "CellStorage constructed in " << TIMER_SEC(cell_storage) << " seconds"; - // split bisection id bits into groups starting from SCC and stop at level 1 - BOOST_ASSERT(recursive_bisection.SCCDepth() != 0); - int mask_from = sizeof(BisectionID) * CHAR_BIT - recursive_bisection.SCCDepth(); - std::vector level_masks; - for (int mask_to = sizeof(BisectionID) * CHAR_BIT; mask_to > first_nonzero_position; - mask_to = mask_from, mask_from -= 3) // TODO: find better grouping - { - auto bit = std::max(first_nonzero_position, mask_from); - level_masks.push_back(((1u << (sizeof(BisectionID) * CHAR_BIT - bit)) - 1) << bit); - } - - util::Log() << "Bisection IDs split for SCC depth " << recursive_bisection.SCCDepth() - << " and first non-zero bit position " << first_nonzero_position - << " number of levels is " << level_masks.size(); - for (auto x : level_masks) - std::cout << std::setw(8) << std::hex << x << std::dec << "\n"; - - // collect cell ids as masked bisection ids - std::vector> partitions( - level_masks.size(), std::vector(edge_based_partition_ids.size())); - std::vector> partition_sets(level_masks.size()); - for (std::size_t index = 0; index < edge_based_partition_ids.size(); ++index) - { - auto bisection_id = edge_based_partition_ids[index]; - for (std::size_t level = 0; level < level_masks.size(); ++level) - { - CellID cell_id = - bisection_id & level_masks[level_masks.size() - 1 - level]; - partitions[level][index] = cell_id; - partition_sets[level].insert(cell_id); - } - } - - std::vector level_to_num_cells; - std::transform(partition_sets.begin(), - partition_sets.end(), - std::back_inserter(level_to_num_cells), - [](const std::unordered_set &partition_set) { - return partition_set.size(); - }); - std::cout << "# of cell on levels\n"; - for (std::size_t level = 0; level < partition_sets.size(); ++level) - { - std::cout << level_to_num_cells[level] << ": "; - for (auto x : partition_sets[level]) - std::cout << " " << x; - std::cout << "\n"; - } - - TIMER_START(packed_mlp); - MultiLevelPartition mlp{partitions, level_to_num_cells}; - TIMER_STOP(packed_mlp); - util::Log() << "MultiLevelPartition constructed in " << TIMER_SEC(packed_mlp) - << " seconds"; - - TIMER_START(cell_storage); - CellStorage storage(mlp, *edge_based_graph); - TIMER_STOP(cell_storage); - util::Log() << "CellStorage constructed in " << TIMER_SEC(cell_storage) << " seconds"; - - TIMER_START(writing_mld_data); - io::write(config.mld_partition_path, mlp); - io::write(config.mld_storage_path, storage); - TIMER_STOP(writing_mld_data); - util::Log() << "MLD data writing took " << TIMER_SEC(writing_mld_data) << " seconds"; - } + TIMER_START(writing_mld_data); + io::write(config.mld_partition_path, mlp); + io::write(config.mld_storage_path, storage); + TIMER_STOP(writing_mld_data); + util::Log() << "MLD data writing took " << TIMER_SEC(writing_mld_data) << " seconds"; return 0; } diff --git a/unit_tests/partition/bisection_to_partition.cpp b/unit_tests/partition/bisection_to_partition.cpp new file mode 100644 index 000000000..6384b7871 --- /dev/null +++ b/unit_tests/partition/bisection_to_partition.cpp @@ -0,0 +1,331 @@ +#include +#include + +#include "partition/bisection_to_partition.hpp" + +#define CHECK_SIZE_RANGE(range, ref) BOOST_CHECK_EQUAL(range.end() - range.begin(), ref) +#define CHECK_EQUAL_RANGE(range, ref) \ + do \ + { \ + const auto &lhs = range; \ + const auto &rhs = ref; \ + BOOST_CHECK_EQUAL_COLLECTIONS(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); \ + } while (0) + +using namespace osrm; +using namespace osrm::partition; + +BOOST_AUTO_TEST_SUITE(bisection_to_partition_tests) + +BOOST_AUTO_TEST_CASE(unsplitable_case) +{ + std::vector partitions; + std::vector num_cells; + + /* + 0 | 1 + / \ + 0 | 1 \ + / \ | + 0 | 1 0 | 1 | + / \ / \ / \ + | | | | / \ + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + */ + const std::vector ids_1 = { + 0b000, 0b000, 0b001, 0b001, 0b010, 0b010, 0b011, 0b011, 0b100, 0b100, 0b100, 0b100, 0b100, 0b100, 0b100, 0b100, + }; + + // If cell sizes are not a factor of two we will see sub-optimal results like below + std::tie(partitions, num_cells) = bisectionToPartition(ids_1, {2, 4, 8, 16}); + BOOST_CHECK_EQUAL(partitions.size(), 4); + + std::vector reference_num_cells = {5, 3, 2, 1}; + CHECK_EQUAL_RANGE(reference_num_cells, num_cells); + + // Four cells of size 2 and one of size 4 (could not be split) + const std::vector reference_l1{partitions[0][0], // 0 + partitions[0][0], // 1 + partitions[0][2], // 2 + partitions[0][2], // 3 + partitions[0][4], // 4 + partitions[0][4], // 5 + partitions[0][6], // 6 + partitions[0][6], // 7 + partitions[0][8], // 8 + partitions[0][8], // 9 + partitions[0][8], // 10 + partitions[0][8], // 11 + partitions[0][8], // 12 + partitions[0][8], // 13 + partitions[0][8], // 14 + partitions[0][8]}; // 15 + // Two cells of size 4 and one of size 8 + const std::vector reference_l2{partitions[1][0], // 0 + partitions[1][0], // 1 + partitions[1][0], // 2 + partitions[1][0], // 3 + partitions[1][4], // 4 + partitions[1][4], // 5 + partitions[1][4], // 6 + partitions[1][4], // 7 + partitions[1][8], // 8 + partitions[1][8], // 9 + partitions[1][8], // 10 + partitions[1][8], // 11 + partitions[1][8], // 12 + partitions[1][8], // 13 + partitions[1][8], // 14 + partitions[1][8]}; // 15 + // Two cells of size 8 + const std::vector reference_l3{partitions[2][0], // 0 + partitions[2][0], // 1 + partitions[2][0], // 2 + partitions[2][0], // 3 + partitions[2][0], // 4 + partitions[2][0], // 5 + partitions[2][0], // 6 + partitions[2][0], // 7 + partitions[2][8], // 8 + partitions[2][8], // 9 + partitions[2][8], // 10 + partitions[2][8], // 11 + partitions[2][8], // 12 + partitions[2][8], // 13 + partitions[2][8], // 14 + partitions[2][8]}; // 15 + // All in one cell + const std::vector reference_l4{partitions[3][0], // 0 + partitions[3][0], // 1 + partitions[3][0], // 2 + partitions[3][0], // 3 + partitions[3][0], // 4 + partitions[3][0], // 5 + partitions[3][0], // 6 + partitions[3][0], // 7 + partitions[3][0], // 8 + partitions[3][0], // 9 + partitions[3][0], // 10 + partitions[3][0], // 11 + partitions[3][0], // 12 + partitions[3][0], // 13 + partitions[3][0], // 14 + partitions[3][0]}; // 15 + + CHECK_EQUAL_RANGE(reference_l1, partitions[0]); + CHECK_EQUAL_RANGE(reference_l2, partitions[1]); + CHECK_EQUAL_RANGE(reference_l3, partitions[2]); + CHECK_EQUAL_RANGE(reference_l4, partitions[3]); +} + +BOOST_AUTO_TEST_CASE(power_of_two_case) +{ + std::vector partitions; + std::vector num_cells; + + /* + 0 | 1 + / \ + 0 | 1 | + / \ | + 0 | 1 0 | 1 0 | 1 + / \ / \ / \ + | | | | | | + 0 1 2 3 4 5 6 7 8 9 10 11 + */ + const std::vector ids_1 = { + 0b000, 0b000, 0b001, 0b001, 0b010, 0b010, 0b011, 0b011, 0b100, 0b100, 0b101, 0b101, + }; + + // If cell sizes are not a factor of two we will see sub-optimal results like below + std::tie(partitions, num_cells) = bisectionToPartition(ids_1, {2, 4, 8, 16}); + BOOST_CHECK_EQUAL(partitions.size(), 4); + + std::vector reference_num_cells = {6, 3, 2, 1}; + CHECK_EQUAL_RANGE(reference_num_cells, num_cells); + + // Six cells of size 2 + const std::vector reference_l1{partitions[0][0], // 0 + partitions[0][0], // 1 + partitions[0][2], // 2 + partitions[0][2], // 3 + partitions[0][4], // 4 + partitions[0][4], // 5 + partitions[0][6], // 6 + partitions[0][6], // 7 + partitions[0][8], // 8 + partitions[0][8], // 9 + partitions[0][10], // 10 + partitions[0][10]}; // 11 + // Three cells of size 4 + const std::vector reference_l2{partitions[1][0], // 0 + partitions[1][0], // 1 + partitions[1][0], // 2 + partitions[1][0], // 3 + partitions[1][4], // 4 + partitions[1][4], // 5 + partitions[1][4], // 6 + partitions[1][4], // 7 + partitions[1][8], // 8 + partitions[1][8], // 9 + partitions[1][8], // 10 + partitions[1][8]}; // 11 + // Two cells of size 8 and 4 + const std::vector reference_l3{partitions[2][0], // 0 + partitions[2][0], // 1 + partitions[2][0], // 2 + partitions[2][0], // 3 + partitions[2][0], // 4 + partitions[2][0], // 5 + partitions[2][0], // 6 + partitions[2][0], // 7 + partitions[2][8], // 8 + partitions[2][8], // 9 + partitions[2][8], // 10 + partitions[2][8]}; // 11 + // All in one cell + const std::vector reference_l4{partitions[3][0], // 0 + partitions[3][0], // 1 + partitions[3][0], // 2 + partitions[3][0], // 3 + partitions[3][0], // 4 + partitions[3][0], // 5 + partitions[3][0], // 6 + partitions[3][0], // 7 + partitions[3][0], // 8 + partitions[3][0], // 9 + partitions[3][0], // 10 + partitions[3][0]}; // 11 + + CHECK_EQUAL_RANGE(reference_l1, partitions[0]); + CHECK_EQUAL_RANGE(reference_l2, partitions[1]); + CHECK_EQUAL_RANGE(reference_l3, partitions[2]); + CHECK_EQUAL_RANGE(reference_l4, partitions[3]); + + // Inserting zeros at bit position 0, and 2 should not change the result + const std::vector ids_2 = { + 0b00000, 0b00000, 0b00010, 0b00010, 0b00100, 0b00100, 0b00110, 0b00110, 0b10000, 0b10000, 0b10010, 0b10010, + }; + std::tie(partitions, num_cells) = bisectionToPartition(ids_2, {2, 4, 8, 16}); + CHECK_EQUAL_RANGE(reference_l1, partitions[0]); + CHECK_EQUAL_RANGE(reference_l2, partitions[1]); + CHECK_EQUAL_RANGE(reference_l3, partitions[2]); + CHECK_EQUAL_RANGE(reference_l4, partitions[3]); + + // Inserting a prefix should not change anything + const std::vector ids_3 = { + 0b101000, 0b101000, 0b101001, 0b101001, 0b101010, 0b101010, 0b101011, 0b101011, 0b101100, 0b101100, 0b101101, 0b101101, + }; + std::tie(partitions, num_cells) = bisectionToPartition(ids_3, {2, 4, 8, 16}); + CHECK_EQUAL_RANGE(reference_l1, partitions[0]); + CHECK_EQUAL_RANGE(reference_l2, partitions[1]); + CHECK_EQUAL_RANGE(reference_l3, partitions[2]); + CHECK_EQUAL_RANGE(reference_l4, partitions[3]); +} + +BOOST_AUTO_TEST_CASE(non_factor_two_case) +{ + std::vector partitions; + std::vector num_cells; + + /* + 0 | 1 + / \ + 0 | 1 | + / \ | + 0 | 1 0 | 1 0 | 1 + / \ / \ / \ + | | | | | | + 0 1 2 3 4 5 6 7 8 9 10 11 + */ + const std::vector ids_1 = { + 0b000, 0b000, 0b001, 0b001, 0b010, 0b010, 0b011, 0b011, 0b100, 0b100, 0b101, 0b101, + }; + + // If cell sizes are not a factor of two we will see sub-optimal results like below + std::tie(partitions, num_cells) = bisectionToPartition(ids_1, {2, 4, 6, 12}); + BOOST_CHECK_EQUAL(partitions.size(), 4); + + std::vector reference_num_cells = {6, 3, 3, 1}; + CHECK_EQUAL_RANGE(reference_num_cells, num_cells); + + // Six cells of size 2 + const std::vector reference_l1{partitions[0][0], // 0 + partitions[0][0], // 1 + partitions[0][2], // 2 + partitions[0][2], // 3 + partitions[0][4], // 4 + partitions[0][4], // 5 + partitions[0][6], // 6 + partitions[0][6], // 7 + partitions[0][8], // 8 + partitions[0][8], // 9 + partitions[0][10], // 10 + partitions[0][10]}; // 11 + // Three cells of size 4 + const std::vector reference_l2{partitions[1][0], // 0 + partitions[1][0], // 1 + partitions[1][0], // 2 + partitions[1][0], // 3 + partitions[1][4], // 4 + partitions[1][4], // 5 + partitions[1][4], // 6 + partitions[1][4], // 7 + partitions[1][8], // 8 + partitions[1][8], // 9 + partitions[1][8], // 10 + partitions[1][8]}; // 11 + // Again three cells of size 4 (bad) + const std::vector reference_l3{partitions[2][0], // 0 + partitions[2][0], // 1 + partitions[2][0], // 2 + partitions[2][0], // 3 + partitions[2][4], // 4 + partitions[2][4], // 5 + partitions[2][4], // 6 + partitions[2][4], // 7 + partitions[2][8], // 8 + partitions[2][8], // 9 + partitions[2][8], // 10 + partitions[2][8]}; // 11 + // All in one cell + const std::vector reference_l4{partitions[3][0], // 0 + partitions[3][0], // 1 + partitions[3][0], // 2 + partitions[3][0], // 3 + partitions[3][0], // 4 + partitions[3][0], // 5 + partitions[3][0], // 6 + partitions[3][0], // 7 + partitions[3][0], // 8 + partitions[3][0], // 9 + partitions[3][0], // 10 + partitions[3][0]}; // 11 + + CHECK_EQUAL_RANGE(reference_l1, partitions[0]); + CHECK_EQUAL_RANGE(reference_l2, partitions[1]); + CHECK_EQUAL_RANGE(reference_l3, partitions[2]); + CHECK_EQUAL_RANGE(reference_l4, partitions[3]); + + // Inserting zeros at bit position 0, and 2 should not change the result + const std::vector ids_2 = { + 0b00000, 0b00000, 0b00010, 0b00010, 0b00100, 0b00100, 0b00110, 0b00110, 0b10000, 0b10000, 0b10010, 0b10010, + }; + std::tie(partitions, num_cells) = bisectionToPartition(ids_2, {2, 4, 6, 12}); + CHECK_EQUAL_RANGE(reference_l1, partitions[0]); + CHECK_EQUAL_RANGE(reference_l2, partitions[1]); + CHECK_EQUAL_RANGE(reference_l3, partitions[2]); + CHECK_EQUAL_RANGE(reference_l4, partitions[3]); + + // Inserting a prefix should not change anything + const std::vector ids_3 = { + 0b101000, 0b101000, 0b101001, 0b101001, 0b101010, 0b101010, 0b101011, 0b101011, 0b101100, 0b101100, 0b101101, 0b101101, + }; + std::tie(partitions, num_cells) = bisectionToPartition(ids_3, {2, 4, 6, 12}); + CHECK_EQUAL_RANGE(reference_l1, partitions[0]); + CHECK_EQUAL_RANGE(reference_l2, partitions[1]); + CHECK_EQUAL_RANGE(reference_l3, partitions[2]); + CHECK_EQUAL_RANGE(reference_l4, partitions[3]); +} + +BOOST_AUTO_TEST_SUITE_END()