From e06ffabf215ec051c52b611fdf718ade0d17861e Mon Sep 17 00:00:00 2001 From: Patrick Niklaus Date: Thu, 9 Feb 2017 15:43:53 +0000 Subject: [PATCH] Add storage for cell weights used in the MLD algorithm --- include/util/cell_storage.hpp | 321 ++++++++++++++++++++++ include/util/for_each_range.hpp | 26 ++ include/util/multi_level_partition.hpp | 32 +++ unit_tests/util/cell_storage.cpp | 357 +++++++++++++++++++++++++ 4 files changed, 736 insertions(+) create mode 100644 include/util/cell_storage.hpp create mode 100644 include/util/for_each_range.hpp create mode 100644 include/util/multi_level_partition.hpp create mode 100644 unit_tests/util/cell_storage.cpp diff --git a/include/util/cell_storage.hpp b/include/util/cell_storage.hpp new file mode 100644 index 000000000..bd99c3b79 --- /dev/null +++ b/include/util/cell_storage.hpp @@ -0,0 +1,321 @@ +#ifndef OSRM_UTIL_CELL_STORAGE_HPP +#define OSRM_UTIL_CELL_STORAGE_HPP + +#include "util/assert.hpp" +#include "util/for_each_range.hpp" +#include "util/multi_level_partition.hpp" +#include "util/typedefs.hpp" + +#include +#include + +#include +#include +#include +#include + +namespace osrm +{ +namespace util +{ + +class CellStorage +{ + public: + using WeightOffset = std::uint32_t; + using BoundaryOffset = std::uint32_t; + using BoundarySize = std::uint32_t; + using SourceIndex = std::uint32_t; + using DestinationIndex = std::uint32_t; + + static constexpr auto INVALID_WEIGHT_OFFSET = std::numeric_limits::max(); + static constexpr auto INVALID_BOUNDARY_OFFSET = std::numeric_limits::max(); + + private: + struct CellData + { + WeightOffset weight_offset = INVALID_WEIGHT_OFFSET; + BoundaryOffset source_boundary_offset = INVALID_BOUNDARY_OFFSET; + BoundaryOffset destination_boundary_offset = INVALID_BOUNDARY_OFFSET; + BoundarySize num_source_nodes = 0; + BoundarySize num_destination_nodes = 0; + }; + + // Implementation of the cell view. We need a template parameter here + // because we need to derive a read-only and read-write view from this. + template class CellImpl + { + private: + using WeightPtrT = WeightValueT *; + using WeightRefT = WeightValueT &; + BoundarySize num_source_nodes; + BoundarySize num_destination_nodes; + + WeightPtrT const weights; + const NodeID *const source_boundary; + const NodeID *const destination_boundary; + + using RowIterator = WeightPtrT; + // Possibly replace with + // http://www.boost.org/doc/libs/1_55_0/libs/range/doc/html/range/reference/adaptors/reference/strided.html + class ColumnIterator : public std::iterator + { + public: + explicit ColumnIterator(WeightPtrT begin, std::size_t row_length) + : current(begin), stride(row_length) + { + BOOST_ASSERT(begin != nullptr); + } + + WeightRefT operator*() const { return *current; } + + ColumnIterator &operator++() + { + current += stride; + return *this; + } + + ColumnIterator &operator+=(int amount) + { + current += stride * amount; + return *this; + } + + bool operator==(const ColumnIterator &other) const { return current == other.current; } + + bool operator!=(const ColumnIterator &other) const { return current != other.current; } + + std::int64_t operator-(const ColumnIterator &other) const + { + return (current - other.current) / stride; + } + + private: + WeightPtrT current; + std::size_t stride; + }; + + std::size_t GetRow(NodeID node) const + { + auto iter = std::find(source_boundary, source_boundary + num_source_nodes, node); + BOOST_ASSERT(iter != source_boundary + num_source_nodes); + return iter - source_boundary; + } + + std::size_t GetColumn(NodeID node) const + { + auto iter = + std::find(destination_boundary, destination_boundary + num_destination_nodes, node); + BOOST_ASSERT(iter != destination_boundary + num_destination_nodes); + return iter - destination_boundary; + } + + public: + auto GetOutWeight(NodeID node) const + { + auto row = GetRow(node); + auto begin = weights + num_destination_nodes * row; + auto end = begin + num_destination_nodes; + return boost::make_iterator_range(begin, end); + } + + auto GetInWeight(NodeID node) const + { + auto column = GetColumn(node); + auto begin = ColumnIterator{weights + column, num_destination_nodes}; + auto end = ColumnIterator{weights + column + num_source_nodes * num_destination_nodes, + num_destination_nodes}; + return boost::make_iterator_range(begin, end); + } + + auto GetSourceNodes() const + { + return boost::make_iterator_range(source_boundary, source_boundary + num_source_nodes); + } + + auto GetDestinationNodes() const + { + return boost::make_iterator_range(destination_boundary, + destination_boundary + num_destination_nodes); + } + + CellImpl(const CellData &data, + WeightPtrT const all_weight, + const NodeID *const all_sources, + const NodeID *const all_destinations) + : num_source_nodes{data.num_source_nodes}, + num_destination_nodes{data.num_destination_nodes}, + weights{all_weight + data.weight_offset}, + source_boundary{all_sources + data.source_boundary_offset}, + destination_boundary{all_destinations + data.destination_boundary_offset} + { + BOOST_ASSERT(all_weight != nullptr); + BOOST_ASSERT(all_sources != nullptr); + BOOST_ASSERT(all_destinations != nullptr); + } + }; + + std::size_t LevelIDToIndex(LevelID level) const { return level - 1; } + + public: + using Cell = CellImpl; + using ConstCell = CellImpl; + + template + CellStorage(const MultiLevelPartition &partition, const GraphT &base_graph) + { + // pre-allocate storge for CellData so we can have random access to it by cell id + unsigned number_of_cells = 0; + for (LevelID level = 1u; level < partition.GetNumberOfLevels(); ++level) + { + level_to_cell_offset.push_back(number_of_cells); + number_of_cells += partition.GetNumberOfCells(level); + } + level_to_cell_offset.push_back(number_of_cells); + cells.resize(number_of_cells); + + std::vector> level_source_boundary; + std::vector> level_destination_boundary; + + for (LevelID level = 1u; level < partition.GetNumberOfLevels(); ++level) + { + auto level_offset = level_to_cell_offset[LevelIDToIndex(level)]; + + level_source_boundary.clear(); + level_destination_boundary.clear(); + + for (auto node = 0u; node < base_graph.GetNumberOfNodes(); ++node) + { + const CellID cell_id = partition.GetCell(level, node); + bool is_source_node = false; + bool is_destination_node = false; + bool is_boundary_node = false; + + for (auto edge : base_graph.GetAdjacentEdgeRange(node)) + { + auto other = base_graph.GetTarget(edge); + const auto &data = base_graph.GetEdgeData(edge); + + is_boundary_node |= partition.GetCell(level, other) != cell_id; + is_source_node |= partition.GetCell(level, other) == cell_id && data.forward; + is_destination_node |= + partition.GetCell(level, other) == cell_id && data.backward; + } + + if (is_boundary_node) + { + if (is_source_node) + level_source_boundary.emplace_back(cell_id, node); + if (is_destination_node) + level_destination_boundary.emplace_back(cell_id, node); + // a partition that contains boundary nodes that have no arcs going into + // the cells or coming out of it is invalid. These nodes should be reassigned + // to a different cell. + BOOST_ASSERT_MSG( + is_source_node || is_destination_node, + "Node needs to either have incoming or outgoing edges in cell"); + } + } + + tbb::parallel_sort(level_source_boundary.begin(), level_source_boundary.end()); + tbb::parallel_sort(level_destination_boundary.begin(), + level_destination_boundary.end()); + + const auto insert_cell_boundary = [this, level_offset](auto &boundary, + auto set_num_nodes_fn, + auto set_boundary_offset_fn, + auto begin, + auto end) { + BOOST_ASSERT(std::distance(begin, end) > 0); + + const auto cell_id = begin->first; + BOOST_ASSERT(level_offset + cell_id < cells.size()); + auto &cell = cells[level_offset + cell_id]; + set_num_nodes_fn(cell, std::distance(begin, end)); + set_boundary_offset_fn(cell, boundary.size()); + + std::transform(begin, + end, + std::back_inserter(boundary), + [](const auto &cell_and_node) { return cell_and_node.second; }); + }; + + util::for_each_range( + level_source_boundary.begin(), + level_source_boundary.end(), + [this, insert_cell_boundary](auto begin, auto end) { + insert_cell_boundary( + source_boundary, + [](auto &cell, auto value) { cell.num_source_nodes = value; }, + [](auto &cell, auto value) { cell.source_boundary_offset = value; }, + begin, + end); + }); + util::for_each_range( + level_destination_boundary.begin(), + level_destination_boundary.end(), + [this, insert_cell_boundary](auto begin, auto end) { + insert_cell_boundary( + destination_boundary, + [](auto &cell, auto value) { cell.num_destination_nodes = value; }, + [](auto &cell, auto value) { cell.destination_boundary_offset = value; }, + begin, + end); + }); + } + + // Set weight offsets and calculate total storage size + WeightOffset weight_offset = 0; + for (auto &cell : cells) + { + cell.weight_offset = weight_offset; + weight_offset += cell.num_source_nodes * cell.num_destination_nodes; + } + + weights.resize(weight_offset + 1, INVALID_EDGE_WEIGHT); + } + + CellStorage(std::vector weights_, + std::vector source_boundary_, + std::vector destination_boundary_, + std::vector cells_, + std::vector level_to_cell_offset_) + : weights(std::move(weights_)), source_boundary(std::move(source_boundary_)), + destination_boundary(std::move(destination_boundary_)), cells(std::move(cells_)), + level_to_cell_offset(std::move(level_to_cell_offset_)) + { + } + + ConstCell GetCell(LevelID level, CellID id) const + { + const auto level_index = LevelIDToIndex(level); + BOOST_ASSERT(level_index < level_to_cell_offset.size()); + const auto offset = level_to_cell_offset[level_index]; + const auto cell_index = offset + id; + BOOST_ASSERT(cell_index < cells.size()); + return ConstCell{ + cells[cell_index], weights.data(), source_boundary.data(), destination_boundary.data()}; + } + + Cell GetCell(LevelID level, CellID id) + { + const auto level_index = LevelIDToIndex(level); + BOOST_ASSERT(level_index < level_to_cell_offset.size()); + const auto offset = level_to_cell_offset[level_index]; + const auto cell_index = offset + id; + BOOST_ASSERT(cell_index < cells.size()); + return Cell{ + cells[cell_index], weights.data(), source_boundary.data(), destination_boundary.data()}; + } + + private: + std::vector weights; + std::vector source_boundary; + std::vector destination_boundary; + std::vector cells; + std::vector level_to_cell_offset; +}; +} +} + +#endif diff --git a/include/util/for_each_range.hpp b/include/util/for_each_range.hpp new file mode 100644 index 000000000..01f29e781 --- /dev/null +++ b/include/util/for_each_range.hpp @@ -0,0 +1,26 @@ +#ifndef OSRM_UTIL_FOR_EACH_RANGE_HPP +#define OSRM_UTIL_FOR_EACH_RANGE_HPP + +namespace osrm +{ +namespace util +{ + +template void for_each_range(Iter begin, Iter end, Func f) +{ + auto iter = begin; + while (iter != end) + { + const auto key = iter->first; + auto begin_range = iter; + while (iter != end && iter->first == key) + { + iter++; + } + f(begin_range, iter); + } +} +} +} + +#endif diff --git a/include/util/multi_level_partition.hpp b/include/util/multi_level_partition.hpp new file mode 100644 index 000000000..d3bad229a --- /dev/null +++ b/include/util/multi_level_partition.hpp @@ -0,0 +1,32 @@ +#ifndef OSRM_UTIL_MULTI_LEVEL_PARTITION_HPP +#define OSRM_UTIL_MULTI_LEVEL_PARTITION_HPP + +#include "util/typedefs.hpp" +#include + +namespace osrm +{ +namespace util +{ + +using LevelID = std::uint8_t; +using CellID = std::uint32_t; + +// Mock interface, can be removed when we have an actual implementation +class MultiLevelPartition +{ + public: + // Returns the cell id of `node` at `level` + virtual CellID GetCell(LevelID level, NodeID node) const = 0; + + // Returns the highest level in which `first` and `second` are still in different cells + virtual LevelID GetHighestDifferentLevel(NodeID first, NodeID second) const = 0; + + virtual std::size_t GetNumberOfLevels() const = 0; + + virtual std::size_t GetNumberOfCells(LevelID level) const = 0; +}; +} +} + +#endif diff --git a/unit_tests/util/cell_storage.cpp b/unit_tests/util/cell_storage.cpp new file mode 100644 index 000000000..1fd88abe4 --- /dev/null +++ b/unit_tests/util/cell_storage.cpp @@ -0,0 +1,357 @@ +#include +#include + +#include "util/cell_storage.hpp" +#include "util/static_graph.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::util; + +class MockMLP final : public MultiLevelPartition +{ + public: + CellID GetCell(LevelID level, NodeID node) const { return levels[level - 1][node]; }; + + LevelID GetHighestDifferentLevel(NodeID, NodeID) const { return 3; }; + + std::size_t GetNumberOfLevels() const { return levels.size() + 1; } + + std::size_t GetNumberOfCells(LevelID level) const + { + auto max_id = 0; + for (auto cell : levels[level - 1]) + max_id = std::max(max_id, cell); + return max_id + 1; + } + + MockMLP(std::vector> levels_) : levels(std::move(levels_)) {} + + std::vector> levels; +}; + +struct MockEdge +{ + NodeID start; + NodeID target; +}; + +auto makeGraph(const std::vector &mock_edges) +{ + struct EdgeData + { + bool forward; + bool backward; + }; + using Edge = util::StaticGraph::InputEdge; + std::vector edges; + std::size_t max_id = 0; + for (const auto &m : mock_edges) + { + max_id = std::max(max_id, std::max(m.start, m.target)); + edges.push_back(Edge{m.start, m.target, EdgeData{true, false}}); + edges.push_back(Edge{m.target, m.start, EdgeData{false, true}}); + } + std::sort(edges.begin(), edges.end()); + return util::StaticGraph(max_id + 1, edges); +} + +BOOST_AUTO_TEST_SUITE(cell_storage_tests) + +BOOST_AUTO_TEST_CASE(mutable_cell_storage) +{ + const auto fill_range = [](auto range, const std::vector &values) { + auto iter = range.begin(); + for (auto v : values) + *iter++ = v; + BOOST_CHECK_EQUAL(range.end(), iter); + }; + + // node: 0 1 2 3 4 5 6 7 8 9 10 11 + std::vector l1{{0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5}}; + std::vector l2{{0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3}}; + std::vector l3{{0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}}; + std::vector l4{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; + MockMLP mlp{{l1, l2, l3, l4}}; + + std::vector edges = { + // edges sorted into border/internal by level + // level: (1) (2) (3) (4) + {0, 1}, // i i i i + {2, 3}, // i i i i + {3, 7}, // b b b i + {4, 0}, // b b b i + {4, 5}, // i i i i + {5, 6}, // b i i i + {6, 4}, // b i i i + {6, 7}, // i i i i + {7, 11}, // b b i i + {8, 9}, // i i i i + {9, 8}, // i i i i + {10, 11}, // i i i i + {11, 10} // i i i i + }; + + auto graph = makeGraph(edges); + + // test non-const storage + CellStorage storage(mlp, graph); + + // Level 1 + auto cell_1_0 = storage.GetCell(1, 0); + auto cell_1_1 = storage.GetCell(1, 1); + auto cell_1_2 = storage.GetCell(1, 2); + auto cell_1_3 = storage.GetCell(1, 3); + auto cell_1_4 = storage.GetCell(1, 4); + auto cell_1_5 = storage.GetCell(1, 5); + + (void)cell_1_4; // does not have border nodes + + auto out_range_1_0_0 = cell_1_0.GetOutWeight(0); + auto out_range_1_2_4 = cell_1_2.GetOutWeight(4); + auto out_range_1_3_6 = cell_1_3.GetOutWeight(6); + auto out_range_1_5_11 = cell_1_5.GetOutWeight(11); + + auto in_range_1_1_3 = cell_1_1.GetInWeight(3); + auto in_range_1_2_5 = cell_1_2.GetInWeight(5); + auto in_range_1_3_7 = cell_1_3.GetInWeight(7); + auto in_range_1_5_11 = cell_1_5.GetInWeight(11); + + fill_range(out_range_1_0_0, {}); + fill_range(out_range_1_2_4, {1}); + fill_range(out_range_1_3_6, {2}); + fill_range(out_range_1_5_11, {3}); + + CHECK_EQUAL_RANGE(in_range_1_1_3, std::vector{}); + CHECK_EQUAL_RANGE(in_range_1_2_5, std::vector{1}); + CHECK_EQUAL_RANGE(in_range_1_3_7, std::vector{2}); + CHECK_EQUAL_RANGE(in_range_1_5_11, std::vector{3}); + + // Level 2 + auto cell_2_0 = storage.GetCell(2, 0); + auto cell_2_1 = storage.GetCell(2, 1); + auto cell_2_2 = storage.GetCell(2, 2); + auto cell_2_3 = storage.GetCell(2, 3); + + (void)cell_2_2; // does not have border nodes + + auto out_range_2_0_0 = cell_2_0.GetOutWeight(0); + auto out_range_2_1_4 = cell_2_1.GetOutWeight(4); + auto out_range_2_3_11 = cell_2_3.GetOutWeight(11); + + auto in_range_2_0_3 = cell_2_0.GetInWeight(3); + auto in_range_2_1_4 = cell_2_1.GetInWeight(4); + auto in_range_2_1_7 = cell_2_1.GetInWeight(7); + auto in_range_2_3_11 = cell_2_3.GetInWeight(11); + + fill_range(out_range_2_0_0, {1}); + fill_range(out_range_2_1_4, {2, 3}); + fill_range(out_range_2_3_11, {4}); + + CHECK_EQUAL_RANGE(in_range_2_0_3, std::vector{1}); + CHECK_EQUAL_RANGE(in_range_2_1_4, std::vector{2}); + CHECK_EQUAL_RANGE(in_range_2_1_7, std::vector{3}); + CHECK_EQUAL_RANGE(in_range_2_3_11, std::vector{4}); + + // Level 3 + auto cell_3_0 = storage.GetCell(3, 0); + auto cell_3_1 = storage.GetCell(3, 1); + + auto out_range_3_0_0 = cell_3_0.GetOutWeight(0); + auto out_range_3_1_4 = cell_3_1.GetOutWeight(4); + auto out_range_3_1_7 = cell_3_1.GetOutWeight(7); + + auto in_range_3_0_3 = cell_3_0.GetInWeight(3); + auto in_range_3_1_4 = cell_3_1.GetInWeight(4); + auto in_range_3_1_7 = cell_3_1.GetInWeight(7); + + fill_range(out_range_3_0_0, {1}); + fill_range(out_range_3_1_4, {2, 3}); + fill_range(out_range_3_1_7, {4, 5}); + + CHECK_EQUAL_RANGE(in_range_3_0_3, std::vector({1})); + CHECK_EQUAL_RANGE(in_range_3_1_4, std::vector({2, 4})); + CHECK_EQUAL_RANGE(in_range_3_1_7, std::vector({3, 5})); +} + +BOOST_AUTO_TEST_CASE(immutable_cell_storage) +{ + // node: 0 1 2 3 4 5 6 7 8 9 10 11 + std::vector l1{{0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5}}; + std::vector l2{{0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3}}; + std::vector l3{{0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}}; + std::vector l4{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; + MockMLP mlp{{l1, l2, l3, l4}}; + + std::vector edges = { + // edges sorted into border/internal by level + // level: (1) (2) (3) (4) + {0, 1}, // i i i i + {2, 3}, // i i i i + {3, 7}, // b b b i + {4, 0}, // b b b i + {4, 5}, // i i i i + {5, 6}, // b i i i + {6, 4}, // b i i i + {6, 7}, // i i i i + {7, 11}, // b b i i + {8, 9}, // i i i i + {9, 8}, // i i i i + {10, 11}, // i i i i + {11, 10} // i i i i + }; + + auto graph = makeGraph(edges); + + // nodes sorted into border/internal by level + // (1) (2) (3) (4) + // 0 b b b i + // 1 i i i i + // 2 i i i i + // 3 b b b i + // 4 b b b i + // 5 b i i i + // 6 b i i i + // 7 b b i i + // 8 i i i i + // 9 i i i i + // 10 i i i i + // 11 b b i i + + // 1/0: 0 : 1,1,0 + // 1/2: 4 : 1,1,0 + // 1/3: 6 : 1,1,0 + // 1/5: 11 : 1,1,1 + // 1/1: 3 : 1,0,1 + // 1/2: 5 : 1,0,1 + // 1/3: 7 : 1,0,1 + + // 2/0: 0 : 1,1,0 + // 2/1: 4 : 1,1,1 + // 2/3: 11 : 1,1,1 + // 2/0: 3 : 1,0,1 + // 2/1: 7 : 1,0,1 + + // 3/0: 0 : 1,1,0 + // 3/1: 4 : 1,1,1 + // 3/1: 7 : 1,1,1 + // 3/0: 3 : 1,0,1 + + // test const storage + const CellStorage const_storage(mlp, graph); + + auto const_cell_1_0 = const_storage.GetCell(1, 0); + auto const_cell_1_1 = const_storage.GetCell(1, 1); + auto const_cell_1_2 = const_storage.GetCell(1, 2); + auto const_cell_1_3 = const_storage.GetCell(1, 3); + auto const_cell_1_4 = const_storage.GetCell(1, 4); + auto const_cell_1_5 = const_storage.GetCell(1, 5); + + CHECK_EQUAL_RANGE(const_cell_1_0.GetSourceNodes(), std::vector({0})); + CHECK_EQUAL_RANGE(const_cell_1_1.GetSourceNodes(), std::vector({})); + CHECK_EQUAL_RANGE(const_cell_1_2.GetSourceNodes(), std::vector({4})); + CHECK_EQUAL_RANGE(const_cell_1_3.GetSourceNodes(), std::vector({6})); + CHECK_EQUAL_RANGE(const_cell_1_4.GetSourceNodes(), std::vector({})); + CHECK_EQUAL_RANGE(const_cell_1_5.GetSourceNodes(), std::vector({11})); + + CHECK_EQUAL_RANGE(const_cell_1_0.GetDestinationNodes(), std::vector({})); + CHECK_EQUAL_RANGE(const_cell_1_1.GetDestinationNodes(), std::vector({3})); + CHECK_EQUAL_RANGE(const_cell_1_2.GetDestinationNodes(), std::vector({5})); + CHECK_EQUAL_RANGE(const_cell_1_3.GetDestinationNodes(), std::vector({7})); + CHECK_EQUAL_RANGE(const_cell_1_4.GetDestinationNodes(), std::vector({})); + CHECK_EQUAL_RANGE(const_cell_1_5.GetDestinationNodes(), std::vector({11})); + + auto out_const_range_1_0_0 = const_cell_1_0.GetOutWeight(0); + auto out_const_range_1_2_4 = const_cell_1_2.GetOutWeight(4); + auto out_const_range_1_3_6 = const_cell_1_3.GetOutWeight(6); + auto out_const_range_1_5_11 = const_cell_1_5.GetOutWeight(11); + + auto in_const_range_1_1_3 = const_cell_1_1.GetInWeight(3); + auto in_const_range_1_2_5 = const_cell_1_2.GetInWeight(5); + auto in_const_range_1_3_7 = const_cell_1_3.GetInWeight(7); + auto in_const_range_1_5_11 = const_cell_1_5.GetInWeight(11); + + CHECK_SIZE_RANGE(out_const_range_1_0_0, 0); + CHECK_SIZE_RANGE(out_const_range_1_2_4, 1); + CHECK_SIZE_RANGE(out_const_range_1_3_6, 1); + CHECK_SIZE_RANGE(out_const_range_1_5_11, 1); + + CHECK_SIZE_RANGE(in_const_range_1_1_3, 0); + CHECK_SIZE_RANGE(in_const_range_1_2_5, 1); + CHECK_SIZE_RANGE(in_const_range_1_3_7, 1); + CHECK_SIZE_RANGE(in_const_range_1_5_11, 1); + + // Level 2 + auto const_cell_2_0 = const_storage.GetCell(2, 0); + auto const_cell_2_1 = const_storage.GetCell(2, 1); + auto const_cell_2_2 = const_storage.GetCell(2, 2); + auto const_cell_2_3 = const_storage.GetCell(2, 3); + + CHECK_EQUAL_RANGE(const_cell_2_0.GetSourceNodes(), std::vector({0})); + CHECK_EQUAL_RANGE(const_cell_2_1.GetSourceNodes(), std::vector({4})); + CHECK_EQUAL_RANGE(const_cell_2_2.GetSourceNodes(), std::vector({})); + CHECK_EQUAL_RANGE(const_cell_2_3.GetSourceNodes(), std::vector({11})); + + CHECK_EQUAL_RANGE(const_cell_2_0.GetDestinationNodes(), std::vector({3})); + CHECK_EQUAL_RANGE(const_cell_2_1.GetDestinationNodes(), std::vector({4, 7})); + CHECK_EQUAL_RANGE(const_cell_2_2.GetDestinationNodes(), std::vector({})); + CHECK_EQUAL_RANGE(const_cell_2_3.GetDestinationNodes(), std::vector({11})); + + auto out_const_range_2_0_0 = const_cell_2_0.GetOutWeight(0); + auto out_const_range_2_1_4 = const_cell_2_1.GetOutWeight(4); + auto out_const_range_2_3_11 = const_cell_2_3.GetOutWeight(11); + + auto in_const_range_2_0_3 = const_cell_2_0.GetInWeight(3); + auto in_const_range_2_1_4 = const_cell_2_1.GetInWeight(4); + auto in_const_range_2_1_7 = const_cell_2_1.GetInWeight(7); + auto in_const_range_2_3_7 = const_cell_2_3.GetInWeight(11); + + CHECK_SIZE_RANGE(out_const_range_2_0_0, 1); + CHECK_SIZE_RANGE(out_const_range_2_1_4, 2); + CHECK_SIZE_RANGE(out_const_range_2_3_11, 1); + + CHECK_SIZE_RANGE(in_const_range_2_0_3, 1); + CHECK_SIZE_RANGE(in_const_range_2_1_4, 1); + CHECK_SIZE_RANGE(in_const_range_2_1_7, 1); + CHECK_SIZE_RANGE(in_const_range_2_3_7, 1); + + // Level 3 + auto const_cell_3_0 = const_storage.GetCell(3, 0); + auto const_cell_3_1 = const_storage.GetCell(3, 1); + + CHECK_EQUAL_RANGE(const_cell_3_0.GetSourceNodes(), std::vector({0})); + CHECK_EQUAL_RANGE(const_cell_3_1.GetSourceNodes(), std::vector({4, 7})); + + CHECK_EQUAL_RANGE(const_cell_3_0.GetDestinationNodes(), std::vector({3})); + CHECK_EQUAL_RANGE(const_cell_3_1.GetDestinationNodes(), std::vector({4, 7})); + + auto out_const_range_3_0_0 = const_cell_3_0.GetOutWeight(0); + auto out_const_range_3_1_4 = const_cell_3_1.GetOutWeight(4); + auto out_const_range_3_1_7 = const_cell_3_1.GetOutWeight(7); + + auto in_const_range_3_0_3 = const_cell_3_0.GetInWeight(3); + auto in_const_range_3_1_4 = const_cell_3_1.GetInWeight(4); + auto in_const_range_3_1_7 = const_cell_3_1.GetInWeight(7); + + CHECK_SIZE_RANGE(out_const_range_3_0_0, 1); + CHECK_SIZE_RANGE(out_const_range_3_1_4, 2); + CHECK_SIZE_RANGE(out_const_range_3_1_7, 2); + + CHECK_SIZE_RANGE(in_const_range_3_0_3, 1); + CHECK_SIZE_RANGE(in_const_range_3_1_4, 2); + CHECK_SIZE_RANGE(in_const_range_3_1_7, 2); + + auto const_cell_4_0 = const_storage.GetCell(4, 0); + CHECK_EQUAL_RANGE(const_cell_4_0.GetSourceNodes(), std::vector({})); + CHECK_EQUAL_RANGE(const_cell_4_0.GetDestinationNodes(), std::vector({})); +} + +BOOST_AUTO_TEST_SUITE_END()