From 93d1501f34a32a222ca0d582c9e29d1ba9a2205b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffan=20S=C3=B8lvsten?= Date: Mon, 5 Jan 2026 12:41:35 +0100 Subject: [PATCH 01/12] Remove 'LevelReverse' feature from Levelized Priority Queue This was only used with the 'narc_ifstream' which is currently not used for anything (and neither planned for anything). Keeping this feature is (1) maintenance and (2) complicated with the virtualization of the 'level_merger' --- .../levelized_priority_queue.h | 30 +-- .../levelized_priority_queue.test.cpp | 236 ------------------ 2 files changed, 1 insertion(+), 265 deletions(-) diff --git a/src/adiar/internal/data_structures/levelized_priority_queue.h b/src/adiar/internal/data_structures/levelized_priority_queue.h index 465b090d9..94a1c8d02 100644 --- a/src/adiar/internal/data_structures/levelized_priority_queue.h +++ b/src/adiar/internal/data_structures/levelized_priority_queue.h @@ -92,8 +92,6 @@ namespace adiar::internal /// \tparam LevelFileComp Comparator to be used for merging multiple levels from the files /// together (`std::less` is top-down while `std::greater` is bottom-up). /// - /// \tparam LevelReverse Whether to reverse the levelised meta data stream. - /// /// \tparam LevelSkip The index for the first level one can push to. In other words, the /// number of levels to 'skip'. ////////////////////////////////////////////////////////////////////////////////////////////////// @@ -104,7 +102,6 @@ namespace adiar::internal typename LevelFile = shared_file_ptr>, size_t LevelFileCount = 1u, typename LevelFileComp = std::less<>, - bool LevelReverse = false, size_t LevelSkip = 1u> class levelized_priority_queue { @@ -141,7 +138,7 @@ namespace adiar::internal //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Type of the level merger. //////////////////////////////////////////////////////////////////////////////////////////////// - using level_merger_t = level_merger; + using level_merger_t = level_merger; public: //////////////////////////////////////////////////////////////////////////////////////////////// @@ -1017,7 +1014,6 @@ namespace adiar::internal typename LevelFile, size_t LevelFileCount, typename LevelFileComp, - bool LevelReverse, size_t LevelSkip> class levelized_priority_queue { public: @@ -1446,7 +1441,6 @@ namespace adiar::internal shared_levelized_file, LevelFileCount, std::less, - false, LevelSkip>; ////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1465,29 +1459,8 @@ namespace adiar::internal shared_levelized_file, LevelFileCount, std::greater, - false, LevelSkip>; - ////////////////////////////////////////////////////////////////////////////////////////////////// - /// \brief Levelized Priority Queue to be used with `levelized_file` and a `narc_ifstream` - /// or `narc_raccess`. - ////////////////////////////////////////////////////////////////////////////////////////////////// - template , - size_t LookAhead = ADIAR_LPQ_LOOKAHEAD, - memory_mode mem_mode = memory_mode::External, - size_t LevelFileCount = 1u, - size_t LevelSkip = 1u> - using levelized_narc_priority_queue = levelized_priority_queue, - LevelFileCount, - std::less, - true, - LevelSkip>; - ////////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Levelized Priority Queue to be used with `shared_file`. ////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1505,7 +1478,6 @@ namespace adiar::internal shared_file, LevelFileCount, std::less, - false, LevelSkip>; } diff --git a/tests/adiar/internal/data_structures/levelized_priority_queue.test.cpp b/tests/adiar/internal/data_structures/levelized_priority_queue.test.cpp index 3afd116e8..5f08bb1e2 100644 --- a/tests/adiar/internal/data_structures/levelized_priority_queue.test.cpp +++ b/tests/adiar/internal/data_structures/levelized_priority_queue.test.cpp @@ -80,7 +80,6 @@ using test_priority_queue = levelized_priority_queue, - false, 1u>; go_bandit([]() { @@ -4117,7 +4116,6 @@ go_bandit([]() { shared_file, 1u, std::greater<>, - false, 1u> pq({ f }, memory_available(), 32, stats_lpq_tests); @@ -4158,7 +4156,6 @@ go_bandit([]() { shared_file, 1u, std::greater<>, - false, 1u> pq({ f }, memory_available(), 32, stats_lpq_tests); @@ -4186,7 +4183,6 @@ go_bandit([]() { shared_file, 1u, std::greater<>, - false, 1u> pq({ f }, memory_available(), 32, stats_lpq_tests); @@ -4247,7 +4243,6 @@ go_bandit([]() { shared_file, 1u, std::greater<>, - false, 1u> pq({ f }, memory_available(), 32, stats_lpq_tests); @@ -4288,7 +4283,6 @@ go_bandit([]() { shared_file, 1u, std::greater<>, - false, 1u> pq({ f }, memory_available(), 32, stats_lpq_tests); @@ -4316,7 +4310,6 @@ go_bandit([]() { shared_file, 1u, std::greater<>, - false, 1u> pq({ f }, memory_available(), 32, stats_lpq_tests); @@ -4368,7 +4361,6 @@ go_bandit([]() { shared_file, 1u, std::less<>, - false, 0u> pq({ f }, memory_available(), 32, stats_lpq_tests); @@ -4394,7 +4386,6 @@ go_bandit([]() { shared_file, 1u, std::less<>, - false, 0u> pq({ f }, memory_available(), 32, stats_lpq_tests); @@ -4420,7 +4411,6 @@ go_bandit([]() { shared_file, 1u, std::less<>, - false, 0u> pq({ f }, memory_available(), 32, stats_lpq_tests); @@ -4448,7 +4438,6 @@ go_bandit([]() { lpq_test_file, 1u, std::less<>, - false, 0u> pq({ f }, memory_available(), 32, stats_lpq_tests); @@ -4492,7 +4481,6 @@ go_bandit([]() { lpq_test_file, 1u, std::less<>, - false, 0u> pq({ f }, memory_available(), 32, stats_lpq_tests); @@ -4518,225 +4506,6 @@ go_bandit([]() { }); }); - describe("levelized_priority_queue<..., level_reverse=true, ...>", []() { - // TODO: these tests break with a look_ahead of 0. Is this indicating some bug? - - it("can setup buckets in reverse order", []() { - lpq_test_file f; - - { // Garbage collect the writer early - lpq_test_ofstream fw(f); - - fw.push(level_info(4, 2u)); // bucket - fw.push(level_info(3, 3u)); // bucket - fw.push(level_info(2, 2u)); // overflow - fw.push(level_info(1, 1u)); // . - } - - levelized_priority_queue, - true, - 0u> - pq({ f }, memory_available(), 32, stats_lpq_tests); - - AssertThat(pq.has_next_level(), Is().True()); - AssertThat(pq.next_level(), Is().EqualTo(4u)); - - pq.setup_next_level(4u); - AssertThat(pq.current_level(), Is().EqualTo(4u)); - - AssertThat(pq.empty_level(), Is().True()); - - AssertThat(pq.has_next_level(), Is().True()); - AssertThat(pq.next_level(), Is().EqualTo(3u)); - - pq.setup_next_level(3u); - - AssertThat(pq.empty_level(), Is().True()); - - AssertThat(pq.has_next_level(), Is().True()); - AssertThat(pq.next_level(), Is().EqualTo(2u)); - - pq.setup_next_level(2u); - - AssertThat(pq.empty_level(), Is().True()); - - AssertThat(pq.has_next_level(), Is().True()); - AssertThat(pq.next_level(), Is().EqualTo(1u)); - - pq.setup_next_level(1u); - - AssertThat(pq.empty_level(), Is().True()); - - AssertThat(pq.has_next_level(), Is().False()); - }); - - it("can push and pull from reversed buckets [1]", []() { - lpq_test_file f; - - { // Garbage collect the writer early - lpq_test_ofstream fw(f); - - fw.push(level_info(4, 2u)); // bucket - fw.push(level_info(3, 3u)); // bucket - fw.push(level_info(2, 2u)); // overflow - fw.push(level_info(1, 1u)); // . - } - - levelized_priority_queue, - true, - 0u> - pq({ f }, memory_available(), 32, stats_lpq_tests); - - AssertThat(pq.size(), Is().EqualTo(0u)); - - pq.push(lpq_test_data{ 4, 1 }); - AssertThat(pq.size(), Is().EqualTo(1u)); - - pq.push(lpq_test_data{ 4, 2 }); - AssertThat(pq.size(), Is().EqualTo(2u)); - - AssertThat(pq.has_next_level(), Is().True()); - AssertThat(pq.next_level(), Is().EqualTo(4u)); - - pq.setup_next_level(); // 4 - AssertThat(pq.current_level(), Is().EqualTo(4u)); - - AssertThat(pq.empty_level(), Is().False()); - - pq.push(lpq_test_data{ 3, 3 }); - AssertThat(pq.size(), Is().EqualTo(3u)); - - AssertThat(pq.can_pull(), Is().True()); - AssertThat(pq.pull(), Is().EqualTo(lpq_test_data{ 4, 2 })); - AssertThat(pq.size(), Is().EqualTo(2u)); - - AssertThat(pq.can_pull(), Is().True()); - AssertThat(pq.pull(), Is().EqualTo(lpq_test_data{ 4, 1 })); - AssertThat(pq.size(), Is().EqualTo(1u)); - - AssertThat(pq.empty_level(), Is().True()); - - AssertThat(pq.has_next_level(), Is().True()); - AssertThat(pq.next_level(), Is().EqualTo(3u)); - - pq.setup_next_level(); // 3 - AssertThat(pq.current_level(), Is().EqualTo(3u)); - - AssertThat(pq.empty_level(), Is().False()); - - AssertThat(pq.can_pull(), Is().True()); - AssertThat(pq.pull(), Is().EqualTo(lpq_test_data{ 3, 3 })); - AssertThat(pq.size(), Is().EqualTo(0u)); - - AssertThat(pq.empty_level(), Is().True()); - }); - - it("can push and pull from reversed buckets [1]", []() { - lpq_test_file f1; - - { // Garbage collect the writer early - lpq_test_ofstream fw(f1); - - fw.push(level_info(4, 2u)); // bucket - fw.push(level_info(2, 2u)); // overflow - fw.push(level_info(1, 1u)); // . - } - - lpq_test_file f2; - { // Garbage collect the writer early - lpq_test_ofstream fw(f2); - - fw.push(level_info(3, 3u)); // bucket - fw.push(level_info(2, 2u)); // overflow - } - - levelized_priority_queue, - true, - 0u> - pq({ f1, f2 }, memory_available(), 32, stats_lpq_tests); - - AssertThat(pq.size(), Is().EqualTo(0u)); - - pq.push(lpq_test_data{ 4, 1 }); // bucket - AssertThat(pq.size(), Is().EqualTo(1u)); - - pq.push(lpq_test_data{ 4, 2 }); // bucket - AssertThat(pq.size(), Is().EqualTo(2u)); - - AssertThat(pq.has_next_level(), Is().True()); - AssertThat(pq.next_level(), Is().EqualTo(4u)); - - pq.setup_next_level(); // 4 - AssertThat(pq.current_level(), Is().EqualTo(4u)); - - AssertThat(pq.empty_level(), Is().False()); - - pq.push(lpq_test_data{ 3, 3 }); // bucket - AssertThat(pq.size(), Is().EqualTo(3u)); - - AssertThat(pq.can_pull(), Is().True()); - AssertThat(pq.pull(), Is().EqualTo(lpq_test_data{ 4, 2 })); - AssertThat(pq.size(), Is().EqualTo(2u)); - - pq.push(lpq_test_data{ 2, 4 }); // overflow - AssertThat(pq.size(), Is().EqualTo(3u)); - - AssertThat(pq.can_pull(), Is().True()); - AssertThat(pq.pull(), Is().EqualTo(lpq_test_data{ 4, 1 })); - AssertThat(pq.size(), Is().EqualTo(2u)); - - AssertThat(pq.empty_level(), Is().True()); - - AssertThat(pq.has_next_level(), Is().True()); - AssertThat(pq.next_level(), Is().EqualTo(3u)); - - pq.setup_next_level(); // 3 - AssertThat(pq.current_level(), Is().EqualTo(3u)); - - pq.push(lpq_test_data{ 2, 5 }); // bucket - AssertThat(pq.size(), Is().EqualTo(3u)); - - AssertThat(pq.empty_level(), Is().False()); - - AssertThat(pq.can_pull(), Is().True()); - AssertThat(pq.pull(), Is().EqualTo(lpq_test_data{ 3, 3 })); - AssertThat(pq.size(), Is().EqualTo(2u)); - - AssertThat(pq.empty_level(), Is().True()); - - pq.setup_next_level(); // 2 - AssertThat(pq.current_level(), Is().EqualTo(2u)); - - AssertThat(pq.can_pull(), Is().True()); - AssertThat(pq.pull(), Is().EqualTo(lpq_test_data{ 2, 5 })); - AssertThat(pq.size(), Is().EqualTo(1u)); - - AssertThat(pq.can_pull(), Is().True()); - AssertThat(pq.pull(), Is().EqualTo(lpq_test_data{ 2, 4 })); - AssertThat(pq.size(), Is().EqualTo(0u)); - - AssertThat(pq.empty_level(), Is().True()); - }); - }); - describe("levelized_priority_queue<..., look_ahead=0, ..., init_level=0>", []() { it("initialises correctly", []() { shared_file f; @@ -4748,7 +4517,6 @@ go_bandit([]() { shared_file, 1u, std::less<>, - false, 0u> pq({ f }, memory_available(), 32, stats_lpq_tests); @@ -4774,7 +4542,6 @@ go_bandit([]() { lpq_test_file, 1u, std::less<>, - false, 0u> pq({ f }, memory_available(), 32, stats_lpq_tests); @@ -4818,7 +4585,6 @@ go_bandit([]() { lpq_test_file, 1u, std::less, - false, 0u> pq({ f }, memory_available(), 32, stats_lpq_tests); @@ -4867,7 +4633,6 @@ go_bandit([]() { lpq_test_file, 2u, std::less<>, - false, 0u> pq({ f1, f2 }, memory_available(), 32, stats_lpq_tests); @@ -4942,7 +4707,6 @@ go_bandit([]() { lpq_test_file, 2u, std::less<>, - false, 0u> pq({ f1, f2 }, memory_available(), 32, stats_lpq_tests); From b8fd81f3739309b0085baf24a6ad8d08532d9d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffan=20S=C3=B8lvsten?= Date: Mon, 5 Jan 2026 15:08:11 +0100 Subject: [PATCH 02/12] Move 'level_ifstream_t' to 'level_merger' for later removal It wasn't really used at all in any of the other places; it only made things more complicated for no discernable benefit. --- src/adiar/bdd/evaluate.cpp | 9 ++--- .../internal/data_structures/level_merger.h | 22 ++++++++++++ src/adiar/internal/util.h | 34 ++----------------- 3 files changed, 29 insertions(+), 36 deletions(-) diff --git a/src/adiar/bdd/evaluate.cpp b/src/adiar/bdd/evaluate.cpp index 50d8a66a1..06b0d4129 100644 --- a/src/adiar/bdd/evaluate.cpp +++ b/src/adiar/bdd/evaluate.cpp @@ -16,6 +16,7 @@ namespace adiar { + ////////////////////////////////////////////////////////////////////////////////////////////////// class bdd_eval_func_visitor { const predicate& af; @@ -54,7 +55,7 @@ namespace adiar return v.get_result(); } - ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////// class bdd_eval_generator_visitor { const generator>& _generator; @@ -118,7 +119,7 @@ namespace adiar return v.get_result(); } - ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////// // TODO: Merge code duplication with Curiously Recurring Template Pattern template @@ -266,7 +267,7 @@ namespace adiar { if (!bdd_iscube(d)) { throw domain_error("BDD 'd' is not a cube"); } - typename internal::level_ifstream_t::template stream_t<> d_levels(d); + internal::level_info_ifstream d_levels(d); const generator d_gen = make_generator__levels(d_levels); const size_t total_levels = std::min(f->levels() + d->levels(), bdd::max_label + 1); @@ -298,7 +299,7 @@ namespace adiar { if (!bdd_iscube(d)) { throw domain_error("BDD 'd' is not a cube"); } - typename internal::level_ifstream_t::template stream_t<> d_levels(d); + internal::level_info_ifstream d_levels(d); const generator d_gen = make_generator__levels(d_levels); const size_t total_levels = std::min(f->levels() + d->levels(), bdd::max_label + 1); diff --git a/src/adiar/internal/data_structures/level_merger.h b/src/adiar/internal/data_structures/level_merger.h index b3ca1c66d..5ab6c3881 100644 --- a/src/adiar/internal/data_structures/level_merger.h +++ b/src/adiar/internal/data_structures/level_merger.h @@ -16,6 +16,28 @@ namespace adiar::internal // Add to 'File' an enum with 'Ascending'/'Descending' to then derive the comparator in // conjunction with 'Reverse'. + // TODO: Remove + template + struct level_ifstream_t + { + template + using stream_t = level_info_ifstream; + }; + + template <> + struct level_ifstream_t> + { + template + using stream_t = ifstream; + }; + + template <> + struct level_ifstream_t> + { + template + using stream_t = ifstream; + }; + ////////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Merges the levels from one or more files. /// diff --git a/src/adiar/internal/util.h b/src/adiar/internal/util.h index 48e44e5d2..feb506c99 100644 --- a/src/adiar/internal/util.h +++ b/src/adiar/internal/util.h @@ -33,33 +33,6 @@ namespace adiar::internal } } - ////////////////////////////////////////////////////////////////////////////////////////////////// - /// \brief Defines at compile time the type of the file stream to use for reading the levels from - /// some file(s). - // - // TODO: Rename 'stream_t' into 'type' - ////////////////////////////////////////////////////////////////////////////////////////////////// - template - struct level_ifstream_t - { - template - using stream_t = level_info_ifstream; - }; - - template <> - struct level_ifstream_t> - { - template - using stream_t = ifstream; - }; - - template <> - struct level_ifstream_t> - { - template - using stream_t = ifstream; - }; - ////////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Turn a `level_ifstream<...>` into a level-only generator function. /// @@ -86,11 +59,8 @@ namespace adiar::internal bool disjoint_levels(const A& a, const B& b) { - using stream1_t = typename level_ifstream_t::template stream_t; - stream1_t sa(a); - - using stream2_t = typename level_ifstream_t::template stream_t; - stream2_t sb(b); + level_info_ifstream sa(a); + level_info_ifstream sb(b); while (sa.can_pull() && sb.can_pull()) { if (level_of(sa.peek()) == level_of(sb.peek())) { From 79a1df15a0b91b766a326be193860979fea22137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffan=20S=C3=B8lvsten?= Date: Fri, 16 Jan 2026 12:30:10 +0100 Subject: [PATCH 03/12] Mark 'disjoint_levels' as only for 'dd' type Which is the only type of argument it is given. --- src/adiar/internal/util.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/adiar/internal/util.h b/src/adiar/internal/util.h index feb506c99..2037237e5 100644 --- a/src/adiar/internal/util.h +++ b/src/adiar/internal/util.h @@ -55,9 +55,9 @@ namespace adiar::internal /// \brief Obtain whether the levels in two files are disjoint. ////////////////////////////////////////////////////////////////////////////////////////////////// // TODO: Move to dd_func? - template + template bool - disjoint_levels(const A& a, const B& b) + disjoint_levels(const DD& a, const DD& b) { level_info_ifstream sa(a); level_info_ifstream sb(b); From 10a671b51e0f466960a2d7e7de8818bbee7197d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffan=20S=C3=B8lvsten?= Date: Fri, 16 Jan 2026 12:30:45 +0100 Subject: [PATCH 04/12] Simplify copy-construction deletion There is no reason to repeat it at the higher levels (except possibly for documentation). Having it only at the basic streams still propagates the information without additional maintenance requirements. --- src/adiar/internal/io/arc_ifstream.h | 4 ---- src/adiar/internal/io/levelized_ifstream.h | 8 -------- src/adiar/internal/io/narc_ifstream.h | 4 ---- src/adiar/internal/io/node_ifstream.h | 4 ---- src/adiar/internal/io/ofstream.h | 4 ++++ 5 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/adiar/internal/io/arc_ifstream.h b/src/adiar/internal/io/arc_ifstream.h index b472e3244..32acd5ad4 100644 --- a/src/adiar/internal/io/arc_ifstream.h +++ b/src/adiar/internal/io/arc_ifstream.h @@ -45,10 +45,6 @@ namespace adiar::internal //////////////////////////////////////////////////////////////////////////// arc_ifstream() = default; - //////////////////////////////////////////////////////////////////////////// - arc_ifstream(const arc_ifstream&) = delete; - arc_ifstream(arc_ifstream&&) = delete; - //////////////////////////////////////////////////////////////////////////// /// \brief Construct a stream attached to an arc file. //////////////////////////////////////////////////////////////////////////// diff --git a/src/adiar/internal/io/levelized_ifstream.h b/src/adiar/internal/io/levelized_ifstream.h index 22154ac12..a7da7e62c 100644 --- a/src/adiar/internal/io/levelized_ifstream.h +++ b/src/adiar/internal/io/levelized_ifstream.h @@ -52,10 +52,6 @@ namespace adiar::internal levelized_ifstream() {} - //////////////////////////////////////////////////////////////////////////////////////////////// - levelized_ifstream(const levelized_ifstream&) = delete; - levelized_ifstream(levelized_ifstream&&) = delete; - //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Create attached to a levelized file. //////////////////////////////////////////////////////////////////////////////////////////////// @@ -199,10 +195,6 @@ namespace adiar::internal //////////////////////////////////////////////////////////////////////////////////////////////// level_info_ifstream() = default; - //////////////////////////////////////////////////////////////////////////////////////////////// - level_info_ifstream(const level_info_ifstream&) = delete; - level_info_ifstream(level_info_ifstream&&) = delete; - //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Construct attached to a file. /// diff --git a/src/adiar/internal/io/narc_ifstream.h b/src/adiar/internal/io/narc_ifstream.h index 2fbb26ca3..c9ac7a418 100644 --- a/src/adiar/internal/io/narc_ifstream.h +++ b/src/adiar/internal/io/narc_ifstream.h @@ -62,10 +62,6 @@ namespace adiar::internal //////////////////////////////////////////////////////////////////////////////////////////////// narc_ifstream() = default; - //////////////////////////////////////////////////////////////////////////////////////////////// - narc_ifstream(const narc_ifstream&) = delete; - narc_ifstream(narc_ifstream&&) = delete; - //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Create attached to an arc file. //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/adiar/internal/io/node_ifstream.h b/src/adiar/internal/io/node_ifstream.h index 6b7028fcb..ab597feab 100644 --- a/src/adiar/internal/io/node_ifstream.h +++ b/src/adiar/internal/io/node_ifstream.h @@ -39,10 +39,6 @@ namespace adiar::internal //////////////////////////////////////////////////////////////////////////////////////////////// node_ifstream() = default; - //////////////////////////////////////////////////////////////////////////////////////////////// - node_ifstream(const node_ifstream&) = delete; - node_ifstream(node_ifstream&&) = delete; - //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Create attached to a node file. //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/adiar/internal/io/ofstream.h b/src/adiar/internal/io/ofstream.h index c0c8baa2a..9072d30a4 100644 --- a/src/adiar/internal/io/ofstream.h +++ b/src/adiar/internal/io/ofstream.h @@ -53,6 +53,10 @@ namespace adiar::internal ofstream() {} + //////////////////////////////////////////////////////////////////////////////////////////////// + ofstream(const ofstream&) = delete; + ofstream(ofstream&&) = delete; + //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Construct attached to a given `file`. /// From b0d0a6f18d55027eeec6466111ff1d37dc48eb19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffan=20S=C3=B8lvsten?= Date: Fri, 16 Jan 2026 12:31:29 +0100 Subject: [PATCH 05/12] Allow moving streams (but still not copying) --- src/adiar/internal/io/ifstream.h | 1 - src/adiar/internal/io/iofstream.h | 1 - src/adiar/internal/io/ofstream.h | 1 - 3 files changed, 3 deletions(-) diff --git a/src/adiar/internal/io/ifstream.h b/src/adiar/internal/io/ifstream.h index 45e875c33..95f272b87 100644 --- a/src/adiar/internal/io/ifstream.h +++ b/src/adiar/internal/io/ifstream.h @@ -67,7 +67,6 @@ namespace adiar::internal //////////////////////////////////////////////////////////////////////////////////////////////// ifstream(const ifstream&) = delete; - ifstream(ifstream&&) = delete; //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Construct attached to a given shared `file`. diff --git a/src/adiar/internal/io/iofstream.h b/src/adiar/internal/io/iofstream.h index f91f928c5..caa9e6be0 100644 --- a/src/adiar/internal/io/iofstream.h +++ b/src/adiar/internal/io/iofstream.h @@ -49,7 +49,6 @@ namespace adiar::internal //////////////////////////////////////////////////////////////////////////////////////////////// iofstream(const iofstream&) = delete; - iofstream(iofstream&&) = delete; //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Construct attached to a given shared `file`. diff --git a/src/adiar/internal/io/ofstream.h b/src/adiar/internal/io/ofstream.h index 9072d30a4..35872b195 100644 --- a/src/adiar/internal/io/ofstream.h +++ b/src/adiar/internal/io/ofstream.h @@ -55,7 +55,6 @@ namespace adiar::internal //////////////////////////////////////////////////////////////////////////////////////////////// ofstream(const ofstream&) = delete; - ofstream(ofstream&&) = delete; //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Construct attached to a given `file`. From e88685238cd29822e891e5f883728ebb22741a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffan=20S=C3=B8lvsten?= Date: Fri, 16 Jan 2026 12:31:58 +0100 Subject: [PATCH 06/12] Virtualize level merger This new level merger provides us with the ability to use inputs of different types, e.g. a `levelized_file` and a `generator`. This will allow us to skip redundant (and expensive) temporary files in the Intercut operation that were only created to make the types match. --- src/adiar/internal/algorithms/intercut.h | 2 +- src/adiar/internal/algorithms/reduce.h | 8 +- .../internal/data_structures/level_merger.h | 328 +++++++--- .../levelized_priority_queue.h | 278 +++------ src/adiar/type_traits.h | 3 + .../data_structures/level_merger.test.cpp | 569 ++++++++---------- .../levelized_priority_queue.test.cpp | 17 - 7 files changed, 589 insertions(+), 616 deletions(-) diff --git a/src/adiar/internal/algorithms/intercut.h b/src/adiar/internal/algorithms/intercut.h index a25c3a35c..383197018 100644 --- a/src/adiar/internal/algorithms/intercut.h +++ b/src/adiar/internal/algorithms/intercut.h @@ -83,7 +83,7 @@ namespace adiar::internal template using intercut_priority_queue_t = - levelized_label_priority_queue; + levelized_node_priority_queue; ////////////////////////////////////////////////////////////////////////////// diff --git a/src/adiar/internal/algorithms/reduce.h b/src/adiar/internal/algorithms/reduce.h index c5fe04f64..4d9a4e795 100644 --- a/src/adiar/internal/algorithms/reduce.h +++ b/src/adiar/internal/algorithms/reduce.h @@ -91,17 +91,17 @@ namespace adiar::internal size_t _terminals[2] = { 0u, 0u }; public: - reduce_priority_queue(const shared_levelized_file (&files)[1u], + reduce_priority_queue(std::array&& files, size_t memory_given, size_t max_size, statistics::levelized_priority_queue_t& stats) - : inner_lpq(files, memory_given, max_size, stats) + : inner_lpq(std::move(files), memory_given, max_size, stats) {} - reduce_priority_queue(const shared_levelized_file (&files)[1u], + reduce_priority_queue(std::array&& files, size_t memory_given, size_t max_size) - : reduce_priority_queue(files, memory_given, max_size, stats_reduce.lpq) + : reduce_priority_queue(std::move(files), memory_given, max_size, stats_reduce.lpq) {} //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/adiar/internal/data_structures/level_merger.h b/src/adiar/internal/data_structures/level_merger.h index 5ab6c3881..3a6ab1b3b 100644 --- a/src/adiar/internal/data_structures/level_merger.h +++ b/src/adiar/internal/data_structures/level_merger.h @@ -1,116 +1,284 @@ #ifndef ADIAR_INTERNAL_DATA_STRUCTURES_LEVEL_MERGER_H #define ADIAR_INTERNAL_DATA_STRUCTURES_LEVEL_MERGER_H +#include +#include + +#include +#include + #include #include -#include -#include #include -#include #include #include namespace adiar::internal { - // TODO (code clarity): - // Add to 'File' an enum with 'Ascending'/'Descending' to then derive the comparator in - // conjunction with 'Reverse'. - - // TODO: Remove - template - struct level_ifstream_t - { - template - using stream_t = level_info_ifstream; - }; - - template <> - struct level_ifstream_t> - { - template - using stream_t = ifstream; - }; - - template <> - struct level_ifstream_t> - { - template - using stream_t = ifstream; - }; - ////////////////////////////////////////////////////////////////////////////////////////////////// - /// \brief Merges the levels from one or more files. + /// \brief Merges the levels from several inputs. /// - /// \tparam File Type of the files to read from. + /// \tparam Comp The comparator with which to merge the levels. /// - /// \tparam Comp Comparator with which to merge the levels. + /// \tparam InputCount Number of inputs. /// - /// \tparam FileCount Number of files to read from. + /// \remark Currently, variadic inputs are supported by use of `virtual` functions, i.e. by use of + /// runtime-resolved inheritance. Yet, this is always used in a context where the type of + /// the individual arguments are known. Hence, one should be able to replace `InputCount` + /// with a compile-time known list ``. To this end, a deduction guide + /// might be useful. ////////////////////////////////////////////////////////////////////////////////////////////////// - template + template class level_merger { - static_assert(0 < FileCount, "At least one file should be merged"); - - using stream_type = typename level_ifstream_t::template stream_t; - public: - static size_t - memory_usage() + using value_type = dd::label_type; + using arg_type = std::variant>; + + private: + //////////////////////////////////////////////////////////////////////////////////////////////// + // TODO: Move to ? + class istream { - return FileCount * stream_type::memory_usage(); - } + public: + using value_type = level_merger::value_type; - using level_type = ptr_uint64::label_type; + virtual ~istream() = default; - private: - Comp _comparator = Comp(); + public: + virtual bool + can_pull(); - unique_ptr _level_ifstreams[FileCount]; + virtual value_type + peek(); - public: + virtual value_type + pull(); + }; + + private: //////////////////////////////////////////////////////////////////////////////////////////////// - /// \brief Attach onto the given list of files. + /// \brief Wrapper for `generator`s to provide the same interface as an `ifstream`, including + /// the ability to `peek()`. //////////////////////////////////////////////////////////////////////////////////////////////// - void - hook(const File (&fs)[FileCount]) + // TODO: Move to or ? + class generator_istream : public istream { - for (size_t idx = 0u; idx < FileCount; idx++) { - _level_ifstreams[idx] = adiar::make_unique(fs[idx]); + private: + const generator _gen; + optional _next; + + public: + generator_istream(const generator& gen) + : _gen(gen) + { + this->_next = this->_gen(); + } + + ~generator_istream() = default; + + public: + bool + can_pull() override + { + return this->_next.has_value(); + } + + value_type + peek() override + { + return this->_next.value(); } - } + value_type + pull() override + { + const value_type ret = this->_next.value(); + this->_next = this->_gen(); + return ret; + } + }; + + private: //////////////////////////////////////////////////////////////////////////////////////////////// - /// \brief Attach onto the given list of decision diagrams. + class level_info_istream : public istream + { + private: + level_info_ifstream<> _ifstream; + + public: + template + level_info_istream(const T& t) + : _ifstream(t) + {} + + ~level_info_istream() = default; + + public: + bool + can_pull() override + { + return this->_ifstream.can_pull(); + } + + value_type + peek() override + { + return this->_ifstream.peek().level(); + } + + value_type + pull() override + { + return this->_ifstream.pull().level(); + } + }; + + private: + //////////////////////////////////////////////////////////////////////////////////////////////// + /// \deprecated This is merely created to help migrate from the old level_merger. //////////////////////////////////////////////////////////////////////////////////////////////// - void - hook(const dd (&dds)[FileCount]) + class file_istream : public istream { - for (size_t idx = 0u; idx < FileCount; idx++) { - _level_ifstreams[idx] = - adiar::make_unique(dds[idx].file_ptr(), dds[idx].shift()); + private: + ifstream _ifstream; + + public: + [[deprecated("Use a 'generator' instead?")]] + file_istream(const shared_file& f) + : _ifstream(f) + {} + + [[deprecated("Use a 'generator' instead?")]] + file_istream(const file& f) + : _ifstream(f) + {} + + ~file_istream() = default; + + public: + bool + can_pull() override + { + return this->_ifstream.can_pull(); } - } + value_type + peek() override + { + return this->_ifstream.peek(); + } + + value_type + pull() override + { + return this->_ifstream.pull(); + } + }; + + public: //////////////////////////////////////////////////////////////////////////////////////////////// - /// \brief Attach onto the given list of (unreduced) decision diagrams. + /// \brief Manages initialization and access to an input of levels. + /// + /// \details Since the `internal::ifstream` is not copy-constructable, then we have to create + /// the object on the heap. //////////////////////////////////////////////////////////////////////////////////////////////// - void - hook(const __dd (&dds)[FileCount]) + class istream_ptr { - for (size_t idx = 0u; idx < FileCount; idx++) { - _level_ifstreams[idx] = adiar::make_unique(dds[idx] /*, dds[idx]._shift*/); + public: + using value_type = typename istream::value_type; + + private: + unique_ptr _ptr; + + public: + /// \brief Conversion for reduced diagrams (top-down). + istream_ptr(const dd& diagram) + : _ptr(adiar::make_unique(diagram)) + {} + + /// \brief Conversion for unreduced diagrams (bottom-up). + istream_ptr(const __dd& diagram) + : _ptr(adiar::make_unique(diagram)) + {} + + /// \brief Conversion for files with levels + template + istream_ptr(const levelized_file& f) + : _ptr(adiar::make_unique(f)) + {} + + /// \brief Conversion for files with levels + template + istream_ptr(const shared_ptr>& f) + : _ptr(adiar::make_unique(f)) + {} + + /// \brief Conversion for files. + istream_ptr(const shared_file& f) + : _ptr(adiar::make_unique(f)) + {} + + /// \brief Conversion for files. + istream_ptr(const file& f) + : _ptr(adiar::make_unique(f)) + {} + + /// \brief Conversion for generators. This requires the addition of an intermediate lambda + /// which takes care of converting to `value_type`. + template , Generator>>> + istream_ptr(const Generator& gen) + : _ptr(adiar::make_unique([=]() -> optional { + const auto x = gen(); + if (!x.has_value()) { return {}; } + return x.value(); + })) + {} + + public: + istream* + operator*() + { + return this->_ptr.get(); + } + + istream* + operator->() + { + return *(*this); } + }; + + public: + static size_t + memory_usage() + { + // NOTE: We assume that all `ifstream`s will use the same amount of memory. + return InputCount * level_info_ifstream<>::memory_usage(); } + private: + Comp _comparator = Comp(); + std::array _istream_ptrs; + + public: + level_merger(const level_merger&) = delete; + level_merger(level_merger&&) = delete; + + level_merger(std::array&& args) + : _istream_ptrs(std::move(args)) + {} + + public: //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Whether there are more levels to fetch. //////////////////////////////////////////////////////////////////////////////////////////////// bool can_pull() { - for (size_t idx = 0u; idx < FileCount; idx++) { - if (_level_ifstreams[idx]->can_pull()) { return true; } + for (istream_ptr& p : this->_istream_ptrs) { + if (p->can_pull()) { return true; } } return false; } @@ -120,19 +288,19 @@ namespace adiar::internal /// /// \pre `can_pull() == true` //////////////////////////////////////////////////////////////////////////////////////////////// - level_type + value_type peek() { adiar_assert(can_pull(), "Cannot peek past end of all streams"); bool has_min_level = false; - level_type min_level = 0u; - for (size_t idx = 0u; idx < FileCount; idx++) { - if (_level_ifstreams[idx]->can_pull() - && (!has_min_level - || _comparator(level_of(_level_ifstreams[idx]->peek()), min_level))) { + value_type min_level = 0u; + for (istream_ptr& p : this->_istream_ptrs) { + if (!p->can_pull()) { continue; } + + if (!has_min_level || _comparator(level_of(p->peek()), min_level)) { has_min_level = true; - min_level = level_of(_level_ifstreams[idx]->peek()); + min_level = level_of(p->peek()); } } @@ -144,22 +312,20 @@ namespace adiar::internal /// /// \pre `can_pull() == true` //////////////////////////////////////////////////////////////////////////////////////////////// - level_type + value_type pull() { adiar_assert(can_pull(), "Cannot pull past end of all streams"); - level_type min_level = peek(); + value_type min_level = peek(); // pull from all with min_level - for (const unique_ptr& level_info_ifstream : _level_ifstreams) { - if (level_info_ifstream->can_pull() && level_of(level_info_ifstream->peek()) == min_level) { - level_info_ifstream->pull(); - } + for (istream_ptr& p : this->_istream_ptrs) { + if (p->can_pull() && level_of(p->peek()) == min_level) { p->pull(); } } return min_level; } }; } -#endif // ADIAR_INTERNAL_DATA_STRUCTURES_LEVEL_MERGER_H +#endif // ADIAR_INTERNAL_DATA_STRUCTURES_LEVEL_MERGER_H diff --git a/src/adiar/internal/data_structures/levelized_priority_queue.h b/src/adiar/internal/data_structures/levelized_priority_queue.h index 94a1c8d02..dbc340430 100644 --- a/src/adiar/internal/data_structures/levelized_priority_queue.h +++ b/src/adiar/internal/data_structures/levelized_priority_queue.h @@ -28,11 +28,11 @@ namespace adiar::internal //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Strictly less ' < ' between two levels. //////////////////////////////////////////////////////////////////////////////////////////////// - template + template inline bool level_cmp_lt(const ptr_uint64::label_type l1, const ptr_uint64::label_type l2, - const LevelFileComp& level_comp) + const LevelComp& level_comp) { return level_comp(l1, l2); } @@ -40,11 +40,11 @@ namespace adiar::internal //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Less or equal ' <= ' between two levels. //////////////////////////////////////////////////////////////////////////////////////////////// - template + template inline bool level_cmp_le(const ptr_uint64::label_type l1, const ptr_uint64::label_type l2, - const LevelFileComp& level_comp) + const LevelComp& level_comp) { return level_comp(l1, l2) || l1 == l2; } @@ -85,11 +85,9 @@ namespace adiar::internal /// /// \tparam MemoryMode Whether to use 'Internal' or 'External' memory data structures. /// - /// \tparam LevelFile Type of the files to obtain the relevant levels from. + /// \tparam LevelInputs Number of files to obtain the levels from. /// - /// \tparam LevelFileCount Number of files to obtain the levels from. - /// - /// \tparam LevelFileComp Comparator to be used for merging multiple levels from the files + /// \tparam LevelComp Comparator to be used for merging multiple levels from the files /// together (`std::less` is top-down while `std::greater` is bottom-up). /// /// \tparam LevelSkip The index for the first level one can push to. In other words, the @@ -99,9 +97,8 @@ namespace adiar::internal typename Comp = std::less<>, size_t LookAhead = ADIAR_LPQ_LOOKAHEAD, memory_mode MemoryMode = memory_mode::External, - typename LevelFile = shared_file_ptr>, - size_t LevelFileCount = 1u, - typename LevelFileComp = std::less<>, + size_t LevelInputs = 1u, + typename LevelComp = std::less<>, size_t LevelSkip = 1u> class levelized_priority_queue { @@ -138,7 +135,7 @@ namespace adiar::internal //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Type of the level merger. //////////////////////////////////////////////////////////////////////////////////////////////// - using level_merger_t = level_merger; + using level_merger_t = level_merger; public: //////////////////////////////////////////////////////////////////////////////////////////////// @@ -247,7 +244,7 @@ namespace adiar::internal //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Instantiation of the comparator between levels. //////////////////////////////////////////////////////////////////////////////////////////////// - LevelFileComp _level_comparator = LevelFileComp(); + LevelComp _level_comparator = LevelComp(); //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Instantiation of the comparator between elements. @@ -369,87 +366,45 @@ namespace adiar::internal } } - levelized_priority_queue(size_t memory_given, - size_t max_size, - [[maybe_unused]] statistics::levelized_priority_queue_t& stats) - : _max_size(max_size) - , _memory_given(memory_given) - , _memory_for_buckets(memory_given - _memory_occupied_by_merger - - mem_overflow_queue(memory_given)) - , _memory_occupied_by_overflow(mem_overflow_queue(memory_given)) - , _overflow_queue(mem_overflow_queue(memory_given), max_size) -#ifdef ADIAR_STATS - , _stats(stats) -#endif - { - adiar_assert(_memory_occupied_by_merger + _memory_for_buckets + _memory_occupied_by_overflow - <= _memory_given, - "the amount of memory used should be within the given bounds"); - } - public: //////////////////////////////////////////////////////////////////////////////////////////////// - /// \brief Instantiate with the given amount of memory. - /// - /// \param files Files to follow the levels of + /// \brief Type of level inputs. /// - /// \param memory_given Total amount of memory to use + /// \remark You only need to use this directly, if you are designing a wrapper/decorator for the + /// televised priority queue, e.g., `reduce` and `nested_sweep`. //////////////////////////////////////////////////////////////////////////////////////////////// - levelized_priority_queue(const LevelFile (&files)[LevelFileCount], - size_t memory_given, - size_t max_size, - statistics::levelized_priority_queue_t& stats) - : levelized_priority_queue(memory_given, max_size, stats) - { - _level_merger.hook(files); - init_buckets(); - } + using level_input_type = typename level_merger_t::istream_ptr; + public: //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Instantiate with the given amount of memory. /// - /// \param dds Decision Diagrams to follow the levels of + /// \param level_inputs Inputs that provide the levels. These are implicitly converted to a + /// subclass of `level_merger::istream` as part of construction. /// /// \param memory_given Total amount of memory to use - //////////////////////////////////////////////////////////////////////////////////////////////// - levelized_priority_queue(const dd (&dds)[LevelFileCount], - size_t memory_given, - size_t max_size, - statistics::levelized_priority_queue_t& stats) - : levelized_priority_queue(memory_given, max_size, stats) - { - _level_merger.hook(dds); - init_buckets(); - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - /// \brief Instantiate with the given amount of memory. - /// - /// \param dds Unreduced Decision Diagrams to follow the levels of /// - /// \param memory_given Total amount of memory to use + /// \param max_size Upper bound on the number of elements placed in the priority queue. //////////////////////////////////////////////////////////////////////////////////////////////// - levelized_priority_queue(const __dd (&dds)[LevelFileCount], + levelized_priority_queue(std::array&& level_inputs, size_t memory_given, size_t max_size, - statistics::levelized_priority_queue_t& stats) - : levelized_priority_queue(memory_given, max_size, stats) + [[maybe_unused]] statistics::levelized_priority_queue_t& stats) + : _max_size(max_size) + , _memory_given(memory_given) + , _memory_for_buckets(memory_given - _memory_occupied_by_merger + - mem_overflow_queue(memory_given)) + , _memory_occupied_by_overflow(mem_overflow_queue(memory_given)) + , _level_merger(std::move(level_inputs)) + , _overflow_queue(mem_overflow_queue(memory_given), max_size) +#ifdef ADIAR_STATS + , _stats(stats) +#endif { - _level_merger.hook(dds); - init_buckets(); - } + adiar_assert(_memory_occupied_by_merger + _memory_for_buckets + _memory_occupied_by_overflow + <= _memory_given, + "the amount of memory used should be within the given bounds"); - private: - //////////////////////////////////////////////////////////////////////////////////////////////// - /// \brief Computes final memory usage of internal data structures to then distribute the - /// remaining memory when initialising the buckets. - /// - /// \remark Call this function at the end of the constructor after the level_merger has hooked - /// into the input. - //////////////////////////////////////////////////////////////////////////////////////////////// - void - init_buckets() - { // Initially skip the number of levels for (size_t idx = 0; _level_merger.can_pull() && idx < LevelSkip; idx++) { _level_merger.pull(); @@ -551,7 +506,7 @@ namespace adiar::internal const ptr_uint64::label_type level = e.level(); - adiar_assert(level_cmp_le(next_bucket_level(), level, _level_comparator), + adiar_assert(level_cmp_le(next_bucket_level(), level, _level_comparator), "Can only push element to next bucket or later."); const size_t pushable_buckets = active_buckets() - has_front_bucket(); @@ -608,7 +563,7 @@ namespace adiar::internal !_overflow_queue.empty() ? _overflow_queue.top().level() : stop_level; stop_level = stop_level == no_label - || level_cmp_lt(overflow_level, stop_level, _level_comparator) + || level_cmp_lt(overflow_level, stop_level, _level_comparator) ? overflow_level : stop_level; @@ -618,20 +573,20 @@ namespace adiar::internal adiar_assert( !has_stop_level || !has_front_bucket() - || level_cmp_lt(front_bucket_level(), stop_level, _level_comparator), + || level_cmp_lt(front_bucket_level(), stop_level, _level_comparator), "'stop_level' should be past the current front bucket (if it exists)"); - adiar_assert(!has_front_bucket() - || level_cmp_lt( - front_bucket_level(), back_bucket_level(), _level_comparator), - "Back bucket should be (strictly) ahead of the back bucket"); + adiar_assert( + !has_front_bucket() + || level_cmp_lt(front_bucket_level(), back_bucket_level(), _level_comparator), + "Back bucket should be (strictly) ahead of the back bucket"); // TODO: Add statistics on what case is hit. // Edge Case: ------------------------------------------------------------------------------ : // The given stop_level is prior to the next bucket if (has_stop_level - && level_cmp_lt(stop_level, next_bucket_level(), _level_comparator)) { + && level_cmp_lt(stop_level, next_bucket_level(), _level_comparator)) { return; } @@ -882,12 +837,12 @@ namespace adiar::internal // Is the next bucket past the 'stop_level'? if (has_stop_level - && level_cmp_lt(stop_level, next_bucket_level(), _level_comparator)) { + && level_cmp_lt(stop_level, next_bucket_level(), _level_comparator)) { break; } adiar_assert(!has_front_bucket() - || level_cmp_lt( + || level_cmp_lt( front_bucket_level(), back_bucket_level(), _level_comparator), "Inconsistency in has_next_bucket predicate"); @@ -904,7 +859,7 @@ namespace adiar::internal _front_bucket_idx = (_front_bucket_idx + 1) % buckets; adiar_assert(!has_next_bucket() || !has_front_bucket() - || level_cmp_lt( + || level_cmp_lt( front_bucket_level(), back_bucket_level(), _level_comparator), "Inconsistency in has_next_bucket predicate"); @@ -927,15 +882,14 @@ namespace adiar::internal adiar_assert( (has_stop_level - && (level_cmp_le(stop_level, front_bucket_level(), _level_comparator) + && (level_cmp_le(stop_level, front_bucket_level(), _level_comparator) || (!has_next_bucket() - || level_cmp_lt( - stop_level, next_bucket_level(), _level_comparator)))) + || level_cmp_lt(stop_level, next_bucket_level(), _level_comparator)))) || _has_next_from_bucket, "Either we stopped early or we found a non-bucket"); adiar_assert( - level_cmp_le(front_bucket_level(), back_bucket_level(), _level_comparator), + level_cmp_le(front_bucket_level(), back_bucket_level(), _level_comparator), "Consistent bucket levels"); } @@ -964,9 +918,9 @@ namespace adiar::internal adiar_assert(has_front_bucket(), "After increment the front bucket will 'exist'"); - if (level_cmp_le(front_bucket_level(), stop_level, _level_comparator)) { + if (level_cmp_le(front_bucket_level(), stop_level, _level_comparator)) { _current_level = front_bucket_level(); - } else { // level_cmp_lt(stop_level, front_bucket_level(), _level_comparator) + } else { // level_cmp_lt(stop_level, front_bucket_level(), _level_comparator) new_levels[++_back_bucket_idx] = front_bucket_level(); } } while (_front_bucket_idx != old_back_bucket_idx); @@ -975,7 +929,7 @@ namespace adiar::internal // Add as many levels from the level_merger as we can fit in while (_level_merger.can_pull() - && level_cmp_le(_level_merger.peek(), stop_level, _level_comparator)) { + && level_cmp_le(_level_merger.peek(), stop_level, _level_comparator)) { _current_level = _level_merger.pull(); } @@ -1011,17 +965,15 @@ namespace adiar::internal template class levelized_priority_queue { public: @@ -1036,10 +988,17 @@ namespace adiar::internal using value_comp_type = Comp; //////////////////////////////////////////////////////////////////////////////////////////////// - /// \brief Number of buckets. + /// \brief Memory mode. //////////////////////////////////////////////////////////////////////////////////////////////// static constexpr memory_mode mem_mode = MemoryMode; + //////////////////////////////////////////////////////////////////////////////////////////////// + /// \brief Type of level inputs. + //////////////////////////////////////////////////////////////////////////////////////////////// + using level_input_type = + typename levelized_priority_queue:: + level_input_type; + public: //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Type of the (overflow) priority queue. @@ -1086,7 +1045,7 @@ namespace adiar::internal //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Instantiation of the comparator between levels. //////////////////////////////////////////////////////////////////////////////////////////////// - LevelFileComp _level_comparator = LevelFileComp(); + LevelComp _level_comparator = LevelComp(); //////////////////////////////////////////////////////////////////////////////////////////////// /// \brief Instantiation of the comparator between elements. @@ -1121,10 +1080,17 @@ namespace adiar::internal statistics::levelized_priority_queue_t& _stats; #endif - private: - levelized_priority_queue(size_t memory_given, - size_t max_size, - [[maybe_unused]] statistics::levelized_priority_queue_t& stats) + public: + //////////////////////////////////////////////////////////////////////////////////////////////// + /// \brief Instantiate with the given amount of memory. + /// + /// \param memory_given Total amount of memory to use. + //////////////////////////////////////////////////////////////////////////////////////////////// + levelized_priority_queue( + std::array::istream_ptr, LevelInputs>&&, + size_t memory_given, + size_t max_size, + [[maybe_unused]] statistics::levelized_priority_queue_t& stats) : _max_size(max_size) , _memory_given(memory_given) , _priority_queue(memory_given, max_size) @@ -1133,49 +1099,6 @@ namespace adiar::internal #endif {} - public: - //////////////////////////////////////////////////////////////////////////////////////////////// - /// \brief Instantiate with the given amount of memory. - /// - /// \param files Files to follow the levels of - /// - /// \param memory_given Total amount of memory to use - //////////////////////////////////////////////////////////////////////////////////////////////// - levelized_priority_queue(const LevelFile (& /*files*/)[LevelFileCount], - size_t memory_given, - size_t max_size, - statistics::levelized_priority_queue_t& stats) - : levelized_priority_queue(memory_given, max_size, stats) - {} - - //////////////////////////////////////////////////////////////////////////////////////////////// - /// \brief Instantiate with the given amount of memory. - /// - /// \param dds Decision Diagrams to follow the levels of - /// - /// \param memory_given Total amount of memory to use - //////////////////////////////////////////////////////////////////////////////////////////////// - levelized_priority_queue(const dd (& /*dds*/)[LevelFileCount], - size_t memory_given, - size_t max_size, - statistics::levelized_priority_queue_t& stats) - : levelized_priority_queue(memory_given, max_size, stats) - {} - - //////////////////////////////////////////////////////////////////////////////////////////////// - /// \brief Instantiate with the given amount of memory. - /// - /// \param dds Decision Diagrams to follow the levels of - /// - /// \param memory_given Total amount of memory to use - //////////////////////////////////////////////////////////////////////////////////////////////// - levelized_priority_queue(const __dd (& /*dds*/)[LevelFileCount], - size_t memory_given, - size_t max_size, - statistics::levelized_priority_queue_t& stats) - : levelized_priority_queue(memory_given, max_size, stats) - {} - public: ~levelized_priority_queue() { @@ -1226,8 +1149,7 @@ namespace adiar::internal if (_priority_queue.empty()) { return false; } ptr_uint64::label_type next_label_from_queue = _priority_queue.top().level(); return (has_current_level() - && level_cmp_lt( - _current_level, next_label_from_queue, _level_comparator)) + && level_cmp_lt(_current_level, next_label_from_queue, _level_comparator)) || (!has_current_level() && !_priority_queue.empty()); } @@ -1289,8 +1211,7 @@ namespace adiar::internal // Edge Case: ------------------------------------------------------------------------------ : // The given stop_level is prior to the next level or there is nothing in the queue - if ((has_stop_level - && level_cmp_lt(stop_level, next_level(), _level_comparator)) + if ((has_stop_level && level_cmp_lt(stop_level, next_level(), _level_comparator)) || _priority_queue.empty()) { _current_level = stop_level; return; @@ -1301,7 +1222,7 @@ namespace adiar::internal adiar_assert(has_next_level(), "There should be a next level to go to"); ptr_uint64::label_type next_level_from_queue = next_level(); if (has_stop_level - && level_cmp_le(stop_level, next_level_from_queue, _level_comparator)) { + && level_cmp_le(stop_level, next_level_from_queue, _level_comparator)) { _current_level = stop_level; return; } @@ -1429,17 +1350,16 @@ namespace adiar::internal /// or `node_raccess`. ////////////////////////////////////////////////////////////////////////////////////////////////// template , - size_t LookAhead = ADIAR_LPQ_LOOKAHEAD, - memory_mode mem_mode = memory_mode::External, - size_t LevelFileCount = 1u, - size_t LevelSkip = 1u> + typename Comp = std::less, + size_t LookAhead = ADIAR_LPQ_LOOKAHEAD, + memory_mode mem_mode = memory_mode::External, + size_t LevelInputs = 1u, + size_t LevelSkip = 1u> using levelized_node_priority_queue = levelized_priority_queue, - LevelFileCount, + LevelInputs, std::less, LevelSkip>; @@ -1447,38 +1367,18 @@ namespace adiar::internal /// \brief Levelized Priority Queue to be used with `levelized_file` and an `arc_ifstream`. ////////////////////////////////////////////////////////////////////////////////////////////////// template , - size_t LookAhead = ADIAR_LPQ_LOOKAHEAD, - memory_mode mem_mode = memory_mode::External, - size_t LevelFileCount = 1u, - size_t LevelSkip = 1u> + typename Comp = std::less, + size_t LookAhead = ADIAR_LPQ_LOOKAHEAD, + memory_mode mem_mode = memory_mode::External, + size_t LevelInputs = 1u, + size_t LevelSkip = 1u> using levelized_arc_priority_queue = levelized_priority_queue, - LevelFileCount, + LevelInputs, std::greater, LevelSkip>; - - ////////////////////////////////////////////////////////////////////////////////////////////////// - /// \brief Levelized Priority Queue to be used with `shared_file`. - ////////////////////////////////////////////////////////////////////////////////////////////////// - template , - size_t LookAhead = ADIAR_LPQ_LOOKAHEAD, - memory_mode mem_mode = memory_mode::External, - size_t LevelFileCount = 1u, - size_t LevelSkip = 1u> - using levelized_label_priority_queue = - levelized_priority_queue, - LevelFileCount, - std::less, - LevelSkip>; } #endif // ADIAR_INTERNAL_DATA_STRUCTURES_LEVELIZED_PRIORITY_QUEUE_H diff --git a/src/adiar/type_traits.h b/src/adiar/type_traits.h index ba898051c..2fcd8b82c 100644 --- a/src/adiar/type_traits.h +++ b/src/adiar/type_traits.h @@ -17,6 +17,9 @@ namespace adiar template inline constexpr bool needs_conversion = !is_same && is_convertible; + template + inline constexpr bool is_constructible = std::is_constructible::value; + template inline constexpr bool is_const = std::is_const>::value; diff --git a/tests/adiar/internal/data_structures/level_merger.test.cpp b/tests/adiar/internal/data_structures/level_merger.test.cpp index 24e965d1b..062ca0adc 100644 --- a/tests/adiar/internal/data_structures/level_merger.test.cpp +++ b/tests/adiar/internal/data_structures/level_merger.test.cpp @@ -4,128 +4,158 @@ go_bandit([]() { describe("adiar/internal/level_merger.h", []() { - describe("level_merger, ...>", []() { - it("can pull from one level_info stream", []() { - shared_levelized_file f; - { // Garbage collect the writer - levelized_ofstream fw(f); + const ptr_uint64 terminal_F = ptr_uint64(false); + const ptr_uint64 terminal_T = ptr_uint64(true); + + shared_levelized_file nf_x0; + /* + // 1 ---- x0 + // / \ + // F T + */ + { + node_ofstream nw(nf_x0); + nw << node(0, node::max_id, terminal_F, terminal_T); + } + + shared_levelized_file nf_x1; + /* + // 1 ---- x1 + // / \ + // F T + */ + { + node_ofstream nw(nf_x1); + nw << node(1, node::max_id, terminal_F, terminal_T); + } + + shared_levelized_file nf_x0_or_x2; + /* + // 1 ---- x0 + // / \ + // | T + // | + // 2 ---- x2 + // / \ + // F T + */ + { + node_ofstream nw(nf_x0_or_x2); + nw << node(2, node::max_id, terminal_F, terminal_T) + << node(0, node::max_id, node::pointer_type(2, node::max_id), terminal_T); + } + + describe("level_merger(dd ...)", [&]() { + it("can pull from a single diagram [x0]", [&]() { + level_merger, 1> merger({ dd(nf_x0) }); - fw.push(level_info(4, 1u)); - fw.push(level_info(3, 2u)); - fw.push(level_info(2, 2u)); - fw.push(level_info(1, 1u)); - } + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(0u)); - level_merger, std::less<>, 1u> merger; + AssertThat(merger.can_pull(), Is().False()); + }); - merger.hook({ f }); + it("can pull from a single diagram [x1]", [&]() { + level_merger, 1> merger({ dd(nf_x1) }); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(1u)); - AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(2u)); + AssertThat(merger.can_pull(), Is().False()); + }); + + it("can pull from a single diagram [x0 | x2]", [&]() { + level_merger, 1> merger({ dd(nf_x0_or_x2) }); AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(3u)); + AssertThat(merger.pull(), Is().EqualTo(0u)); AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(4u)); + AssertThat(merger.pull(), Is().EqualTo(2u)); AssertThat(merger.can_pull(), Is().False()); }); - it("can peek from one level_info streams", []() { - shared_levelized_file f; - - { // Garbage collect the writer - levelized_ofstream fw(f); - - fw.push(level_info(4, 1u)); - fw.push(level_info(3, 2u)); - fw.push(level_info(2, 1u)); - fw.push(level_info(1, 1u)); - } - - level_merger, std::less<>, 1> merger; + it("can merge levels of two diagrams [x0, x1] (std::less)", [&]() { + level_merger, 2> merger({ dd(nf_x0), dd(nf_x1) }); - merger.hook({ f }); + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(0u)); + AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(1u)); - AssertThat(merger.peek(), Is().EqualTo(2u)); - AssertThat(merger.pull(), Is().EqualTo(2u)); - - AssertThat(merger.peek(), Is().EqualTo(3u)); - AssertThat(merger.pull(), Is().EqualTo(3u)); - - AssertThat(merger.pull(), Is().EqualTo(4u)); + AssertThat(merger.can_pull(), Is().False()); }); - it("can pull from merge of two level_info streams, where one is empty [1]", []() { - shared_levelized_file f1; - shared_levelized_file f2; + it("can merge levels of two diagrams [x1, x0] (std::less)", [&]() { + level_merger, 2> merger({ dd(nf_x1), dd(nf_x0) }); - { // Garbage collect the writer - levelized_ofstream fw1(f1); + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(0u)); - fw1.push(level_info(1, 1u)); - } + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(1u)); - level_merger, std::less<>, 2> merger; + AssertThat(merger.can_pull(), Is().False()); + }); - merger.hook({ f1, f2 }); + it("can merge levels of two diagrams [x1, x0] (std::greater)", [&]() { + level_merger, 2> merger({ dd(nf_x1), dd(nf_x0) }); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(1u)); + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(0u)); + AssertThat(merger.can_pull(), Is().False()); }); - it("can pull from merge of two level_info streams, where one is empty [2]", []() { - shared_levelized_file f1; - shared_levelized_file f2; + it("can merge levels of two diagrams [x0, x0 | x2] (std::less)", [&]() { + level_merger, 2> merger({ dd(nf_x0), dd(nf_x0_or_x2) }); - { // Garbage collect the writer - levelized_ofstream fw1(f1); + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(0u)); - fw1.push(level_info(1, 1u)); - fw1.push(level_info(2, 1u)); - } + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(2u)); - level_merger, std::greater<>, 2> merger; + AssertThat(merger.can_pull(), Is().False()); + }); - merger.hook({ f1, f2 }); + it("can merge levels of two diagrams [x0 | x2, x0] (std::less)", [&]() { + level_merger, 2> merger({ dd(nf_x0_or_x2), dd(nf_x0) }); AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(2u)); + AssertThat(merger.pull(), Is().EqualTo(0u)); AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(1u)); + AssertThat(merger.pull(), Is().EqualTo(2u)); AssertThat(merger.can_pull(), Is().False()); }); - it("can pull from merge of two level_info streams [1]", []() { - shared_levelized_file f1; - shared_levelized_file f2; + it("can merge levels of two diagrams [x0 | x2, x1] (std::less)", [&]() { + level_merger, 2> merger({ dd(nf_x0_or_x2), dd(nf_x1) }); - { // Garbage collect the writers - levelized_ofstream fw1(f1); + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(0u)); - fw1.push(level_info(4, 1u)); - fw1.push(level_info(2, 2u)); - fw1.push(level_info(1, 1u)); + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(1u)); - levelized_ofstream fw2(f2); + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(2u)); - fw2.push(level_info(4, 1u)); - fw2.push(level_info(3, 1u)); - } + AssertThat(merger.can_pull(), Is().False()); + }); - level_merger, std::less<>, 2> merger; + it("can merge levels of two diagrams [x1, x0 | x2] (std::less)", [&]() { + level_merger, 2> merger({ dd(nf_x1), dd(nf_x0_or_x2) }); - merger.hook({ f1, f2 }); + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(0u)); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(1u)); @@ -133,32 +163,41 @@ go_bandit([]() { AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(2u)); - AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(3u)); + AssertThat(merger.can_pull(), Is().False()); + }); + + it("can shift levels of a single diagram [x0 +1]", [&]() { + level_merger, 1> merger({ dd(nf_x0, false, +1) }); AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(4u)); + AssertThat(merger.pull(), Is().EqualTo(1u)); AssertThat(merger.can_pull(), Is().False()); }); - it("can pull from merge of two level_info streams [2] (std::less)", []() { - shared_levelized_file f1; - shared_levelized_file f2; + it("can shift levels of a single diagram [x1 +2]", [&]() { + level_merger, 1> merger({ dd(nf_x1, false, +2) }); - { // Garbage collect the writers - levelized_ofstream fw1(f1); + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(3u)); - fw1.push(level_info(2, 1u)); + AssertThat(merger.can_pull(), Is().False()); + }); - levelized_ofstream fw2(f2); + it("can shift levels of a single diagram [x1 -1]", [&]() { + level_merger, 1> merger({ dd(nf_x1, false, -1) }); - fw2.push(level_info(1, 1u)); - } + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(0u)); - level_merger, std::less<>, 2> merger; + AssertThat(merger.can_pull(), Is().False()); + }); - merger.hook({ f1, f2 }); + it("can merge shifted levels of two diagrams [x0 +1, x0 | x2] (std::less)", [&]() { + level_merger, 2> merger({ dd(nf_x0, false, +1), dd(nf_x0_or_x2) }); + + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(0u)); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(1u)); @@ -169,138 +208,96 @@ go_bandit([]() { AssertThat(merger.can_pull(), Is().False()); }); - it("can pull from merge of two level_info streams [2] (std::greater)", []() { - shared_levelized_file f1; - shared_levelized_file f2; - - { // Garbage collect the writers - levelized_ofstream fw1(f1); - - fw1.push(level_info(2, 1u)); - - levelized_ofstream fw2(f2); - - fw2.push(level_info(1, 1u)); - } - - level_merger, std::greater<>, 2> merger; - - merger.hook({ f1, f2 }); + it("can merge shifted levels of two diagrams [x1 +1, x0 | x2] (std::less)", [&]() { + level_merger, 2> merger({ dd(nf_x1, false, +1), dd(nf_x0_or_x2) }); AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(2u)); + AssertThat(merger.pull(), Is().EqualTo(0u)); AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(1u)); + AssertThat(merger.pull(), Is().EqualTo(2u)); AssertThat(merger.can_pull(), Is().False()); }); - it("can peek merge of two level_info stream", []() { - shared_levelized_file f1; - shared_levelized_file f2; - - { // Garbage collect the writers - levelized_ofstream fw1(f1); - - fw1.push(level_info(4, 2u)); - fw1.push(level_info(2, 1u)); + it("can merge shifted levels of two diagrams [x1 -1, x0 | x2] (std::less)", [&]() { + level_merger, 2> merger({ dd(nf_x1, false, -1), dd(nf_x0_or_x2) }); - levelized_ofstream fw2(f2); + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(0u)); - fw2.push(level_info(4, 3u)); - fw2.push(level_info(3, 2u)); - fw2.push(level_info(1, 1u)); - } + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(2u)); - level_merger, std::less<>, 2> merger; + AssertThat(merger.can_pull(), Is().False()); + }); - merger.hook({ f1, f2 }); + it("can merge shifted levels of two diagrams [x1 +2, x0 | x2 +1] (std::less)", [&]() { + level_merger, 2> merger({ dd(nf_x1, false, +2), dd(nf_x0_or_x2, false, +1) }); - AssertThat(merger.peek(), Is().EqualTo(1u)); + AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(1u)); - AssertThat(merger.peek(), Is().EqualTo(2u)); - AssertThat(merger.pull(), Is().EqualTo(2u)); - AssertThat(merger.peek(), Is().EqualTo(3u)); + + AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(3u)); - AssertThat(merger.peek(), Is().EqualTo(4u)); - AssertThat(merger.pull(), Is().EqualTo(4u)); - }); - it("can merge levels in reverse", []() { - shared_levelized_file f1; - shared_levelized_file f2; + AssertThat(merger.can_pull(), Is().False()); + }); + }); - { // Garbage collect the writers - levelized_ofstream fw1(f1); + describe("level_merger(gen ...)", []() { + it("can pull from a single generator []", []() { + const std::vector xs = { }; - fw1.push(level_info(4, 2u)); - fw1.push(level_info(2, 1u)); + level_merger, 1> merger({ make_generator(xs.begin(), xs.end()) }); - levelized_ofstream fw2(f2); + AssertThat(merger.can_pull(), Is().False()); + }); - fw2.push(level_info(4, 3u)); - fw2.push(level_info(3, 2u)); - fw2.push(level_info(1, 1u)); - } + it("can pull from a single generator [0]", []() { + const std::vector xs = { 0 }; - level_merger, std::greater<>, 2, true> merger; + level_merger, 1> merger({ make_generator(xs.begin(), xs.end()) }); - merger.hook({ f1, f2 }); + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(0u)); - AssertThat(merger.peek(), Is().EqualTo(4u)); - AssertThat(merger.pull(), Is().EqualTo(4u)); - AssertThat(merger.peek(), Is().EqualTo(3u)); - AssertThat(merger.pull(), Is().EqualTo(3u)); - AssertThat(merger.peek(), Is().EqualTo(2u)); - AssertThat(merger.pull(), Is().EqualTo(2u)); - AssertThat(merger.peek(), Is().EqualTo(1u)); - AssertThat(merger.pull(), Is().EqualTo(1u)); + AssertThat(merger.can_pull(), Is().False()); }); - it("can pull, even after the original files have been deleted", []() { - shared_levelized_file f1 = shared_levelized_file(); - shared_levelized_file f2 = shared_levelized_file(); + it("can pull from a single generator [1]", []() { + const std::vector xs = { 1 }; - { // Garbage collect the writers - levelized_ofstream fw1(f1); + level_merger, 1> merger({ make_generator(xs.begin(), xs.end()) }); - fw1.push(level_info(4, 2u)); - fw1.push(level_info(2, 1u)); - - levelized_ofstream fw2(f2); + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(1u)); - fw2.push(level_info(4, 1u)); - fw2.push(level_info(3, 2u)); - fw2.push(level_info(1, 1u)); - } + AssertThat(merger.can_pull(), Is().False()); + }); - level_merger, std::less<>, 2> merger; + it("can pull from a single generator [0,2,3]", []() { + const std::vector xs = { 0, 2, 3 }; - merger.hook({ f1, f2 }); + level_merger, 1> merger({ make_generator(xs.begin(), xs.end()) }); - f1.reset(); - f2.reset(); + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(0u)); - AssertThat(merger.pull(), Is().EqualTo(1u)); + AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(2u)); - AssertThat(merger.pull(), Is().EqualTo(3u)); - AssertThat(merger.pull(), Is().EqualTo(4u)); - }); - }); - describe("level_merger, ...>", []() { - it("can use a single label_file", []() { - shared_file f; + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(3u)); - { // Garbage collect the writers - ofstream w(f); - w << 0 << 2 << 3; - } + AssertThat(merger.can_pull(), Is().False()); + }); - level_merger, std::less<>, 1> merger; + it("can merge from a two generators [0,2,3]+[2] (std::less)", []() { + const std::vector xs = { 0, 2, 3 }; + const std::vector ys = { 2 }; - merger.hook({ f }); + level_merger, 2> merger({ make_generator(xs.begin(), xs.end()), make_generator(ys.begin(), ys.end()) }); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(0u)); @@ -314,21 +311,11 @@ go_bandit([]() { AssertThat(merger.can_pull(), Is().False()); }); - it("can merge two label_files", []() { - shared_file f1; - shared_file f2; - - { // Garbage collect the writers - ofstream w1(f1); - w1 << 0 << 2 << 3; + it("can merge from a two generators [0,2,3]+[1,2] (std::less)", []() { + const std::vector xs = { 0, 2, 3 }; + const std::vector ys = { 1, 2 }; - ofstream w2(f2); - w2 << 0 << 1 << 3; - } - - level_merger, std::less<>, 2> merger; - - merger.hook({ f1, f2 }); + level_merger, 2> merger({ make_generator(xs.begin(), xs.end()), make_generator(ys.begin(), ys.end()) }); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(0u)); @@ -344,73 +331,27 @@ go_bandit([]() { AssertThat(merger.can_pull(), Is().False()); }); - }); - describe("level_merger", []() { - const ptr_uint64 terminal_F = ptr_uint64(false); - const ptr_uint64 terminal_T = ptr_uint64(true); - - shared_levelized_file nf_x0; - /* - // 1 ---- x0 - // / \ - // F T - */ - { - node_ofstream nw(nf_x0); - nw << node(0, node::max_id, terminal_F, terminal_T); - } - - shared_levelized_file nf_x1; - /* - // 1 ---- x1 - // / \ - // F T - */ - { - node_ofstream nw(nf_x1); - nw << node(1, node::max_id, terminal_F, terminal_T); - } - - shared_levelized_file nf_x0_or_x2; - /* - // 1 ---- x0 - // / \ - // | T - // | - // 2 ---- x2 - // / \ - // F T - */ - { - node_ofstream nw(nf_x0_or_x2); - nw << node(2, node::max_id, terminal_F, terminal_T) - << node(0, node::max_id, node::pointer_type(2, node::max_id), terminal_T); - } + it("can merge from a two generators [0,2]+[] (std::less)", []() { + const std::vector xs = { 0, 2 }; + const std::vector ys = { }; - it("can pull from a single diagram [x0]", [&]() { - level_merger, std::less<>, 1> merger; - merger.hook({ dd(nf_x0) }); + level_merger, 2> merger({ make_generator(xs.begin(), xs.end()), make_generator(ys.begin(), ys.end()) }); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(0u)); - AssertThat(merger.can_pull(), Is().False()); - }); - - it("can pull from a single diagram [x1]", [&]() { - level_merger, std::less<>, 1> merger; - merger.hook({ dd(nf_x1) }); - AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(1u)); + AssertThat(merger.pull(), Is().EqualTo(2u)); AssertThat(merger.can_pull(), Is().False()); }); - it("can pull from a single diagram [x0 | x2]", [&]() { - level_merger, std::less<>, 1> merger; - merger.hook({ dd(nf_x0_or_x2) }); + it("can merge from a two generators [2]+[0,2] (std::less)", []() { + const std::vector xs = { 2 }; + const std::vector ys = { 0, 2 }; + + level_merger, 2> merger({ make_generator(xs.begin(), xs.end()), make_generator(ys.begin(), ys.end()) }); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(0u)); @@ -421,48 +362,48 @@ go_bandit([]() { AssertThat(merger.can_pull(), Is().False()); }); - it("can merge levels of two diagrams [x0, x1] (std::less)", [&]() { - level_merger, std::less<>, 2> merger; - merger.hook({ dd(nf_x0), dd(nf_x1) }); + it("can merge from a two generators []+[0,2] (std::less)", []() { + const std::vector xs = { }; + const std::vector ys = { 0, 2 }; + + level_merger, 2> merger({ make_generator(xs.begin(), xs.end()), make_generator(ys.begin(), ys.end()) }); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(0u)); AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(1u)); + AssertThat(merger.pull(), Is().EqualTo(2u)); AssertThat(merger.can_pull(), Is().False()); }); - it("can merge levels of two diagrams [x1, x0] (std::less)", [&]() { - level_merger, std::less<>, 2> merger; - merger.hook({ dd(nf_x1), dd(nf_x0) }); + it("can merge from a two generators [4,2,0]+[3,2,0] (std::greater)", []() { + const std::vector xs = { 4, 2, 0 }; + const std::vector ys = { 3, 2, 0 }; - AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(0u)); + level_merger, 2> merger({ make_generator(xs.begin(), xs.end()), make_generator(ys.begin(), ys.end()) }); AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(1u)); - - AssertThat(merger.can_pull(), Is().False()); - }); + AssertThat(merger.pull(), Is().EqualTo(4u)); - it("can merge levels of two diagrams [x1, x0] (std::greater)", [&]() { - level_merger, std::greater<>, 2> merger; - merger.hook({ dd(nf_x1), dd(nf_x0) }); + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(3u)); AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(1u)); + AssertThat(merger.pull(), Is().EqualTo(2u)); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(0u)); AssertThat(merger.can_pull(), Is().False()); }); + }); - it("can merge levels of two diagrams [x0, x0 | x2] (std::less)", [&]() { - level_merger, std::less<>, 2> merger; - merger.hook({ dd(nf_x0), dd(nf_x0_or_x2) }); + describe("level_merger(...)", [&]() { + it("can merge a diagram [x0] and a generator [0,2] (std::less)", [&]() { + const std::vector xs = { 0, 2 }; + + level_merger, 2> merger({ dd(nf_x0), make_generator(xs.begin(), xs.end()) }); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(0u)); @@ -473,9 +414,10 @@ go_bandit([]() { AssertThat(merger.can_pull(), Is().False()); }); - it("can merge levels of two diagrams [x0 | x2, x0] (std::less)", [&]() { - level_merger, std::less<>, 2> merger; - merger.hook({ dd(nf_x0_or_x2), dd(nf_x0) }); + it("can merge a generator [0,2] and a diagram [x0] (std::less)", [&]() { + const std::vector xs = { 0, 2 }; + + level_merger, 2> merger({ make_generator(xs.begin(), xs.end()), dd(nf_x0) }); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(0u)); @@ -486,9 +428,10 @@ go_bandit([]() { AssertThat(merger.can_pull(), Is().False()); }); - it("can merge levels of two diagrams [x0 | x2, x1] (std::less)", [&]() { - level_merger, std::less<>, 2> merger; - merger.hook({ dd(nf_x0_or_x2), dd(nf_x1) }); + it("can merge a diagram [x1] and a generator [0,2] (std::less)", [&]() { + const std::vector xs = { 0, 2 }; + + level_merger, 2> merger({ dd(nf_x1), make_generator(xs.begin(), xs.end()) }); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(0u)); @@ -502,9 +445,10 @@ go_bandit([]() { AssertThat(merger.can_pull(), Is().False()); }); - it("can merge levels of two diagrams [x1, x0 | x2] (std::less)", [&]() { - level_merger, std::less<>, 2> merger; - merger.hook({ dd(nf_x1), dd(nf_x0_or_x2) }); + it("can merge a generator [0,2] and a diagram [x1] (std::less)", [&]() { + const std::vector xs = { 0, 2 }; + + level_merger, 2> merger({ make_generator(xs.begin(), xs.end()), dd(nf_x1) }); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(0u)); @@ -518,45 +462,27 @@ go_bandit([]() { AssertThat(merger.can_pull(), Is().False()); }); - it("can shift levels of a single diagram [x0 +1]", [&]() { - level_merger, std::less<>, 1> merger; - merger.hook({ dd(nf_x0, false, +1) }); - - AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(1u)); - - AssertThat(merger.can_pull(), Is().False()); - }); + it("can merge a diagram [x0|x1] and generator [0,2] (std::less)", [&]() { + const std::vector xs = { 0, 2 }; - it("can shift levels of a single diagram [x1 +2]", [&]() { - level_merger, std::less<>, 1> merger; - merger.hook({ dd(nf_x1, false, +2) }); + level_merger, 2> merger({ dd(nf_x0_or_x2), make_generator(xs.begin(), xs.end()) }); AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(3u)); - - AssertThat(merger.can_pull(), Is().False()); - }); - - it("can shift levels of a single diagram [x1 -1]", [&]() { - level_merger, std::less<>, 1> merger; - merger.hook({ dd(nf_x1, false, -1) }); + AssertThat(merger.pull(), Is().EqualTo(0u)); AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(0u)); + AssertThat(merger.pull(), Is().EqualTo(2u)); AssertThat(merger.can_pull(), Is().False()); }); - it("can merge shifted levels of two diagrams [x0 +1, x0 | x2] (std::less)", [&]() { - level_merger, std::less<>, 2> merger; - merger.hook({ dd(nf_x0, false, +1), dd(nf_x0_or_x2) }); + it("can merge a generator [0,2] and a diagram [x0|x1] (std::less)", [&]() { + const std::vector xs = { 0, 2 }; - AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(0u)); + level_merger, 2> merger({ make_generator(xs.begin(), xs.end()), dd(nf_x0_or_x2) }); AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(1u)); + AssertThat(merger.pull(), Is().EqualTo(0u)); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(2u)); @@ -564,41 +490,36 @@ go_bandit([]() { AssertThat(merger.can_pull(), Is().False()); }); - it("can merge shifted levels of two diagrams [x1 +1, x0 | x2] (std::less)", [&]() { - level_merger, std::less<>, 2> merger; - merger.hook({ dd(nf_x1, false, +1), dd(nf_x0_or_x2) }); + it("can merge a diagram [x0|x1] and generator [1,2] (std::less)", [&]() { + const std::vector xs = { 1, 2 }; + + level_merger, 2> merger({ dd(nf_x0_or_x2), make_generator(xs.begin(), xs.end()) }); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(0u)); + AssertThat(merger.can_pull(), Is().True()); + AssertThat(merger.pull(), Is().EqualTo(1u)); + AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(2u)); AssertThat(merger.can_pull(), Is().False()); }); - it("can merge shifted levels of two diagrams [x1 -1, x0 | x2] (std::less)", [&]() { - level_merger, std::less<>, 2> merger; - merger.hook({ dd(nf_x1, false, -1), dd(nf_x0_or_x2) }); + it("can merge a generator [1,2] and a diagram [x0|x1] (std::less)", [&]() { + const std::vector xs = { 1, 2 }; - AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(0u)); + level_merger, 2> merger({ make_generator(xs.begin(), xs.end()), dd(nf_x0_or_x2) }); AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(2u)); - - AssertThat(merger.can_pull(), Is().False()); - }); - - it("can merge shifted levels of two diagrams [x1 +2, x0 | x2 +1] (std::less)", [&]() { - level_merger, std::less<>, 2> merger; - merger.hook({ dd(nf_x1, false, +2), dd(nf_x0_or_x2, false, +1) }); + AssertThat(merger.pull(), Is().EqualTo(0u)); AssertThat(merger.can_pull(), Is().True()); AssertThat(merger.pull(), Is().EqualTo(1u)); AssertThat(merger.can_pull(), Is().True()); - AssertThat(merger.pull(), Is().EqualTo(3u)); + AssertThat(merger.pull(), Is().EqualTo(2u)); AssertThat(merger.can_pull(), Is().False()); }); diff --git a/tests/adiar/internal/data_structures/levelized_priority_queue.test.cpp b/tests/adiar/internal/data_structures/levelized_priority_queue.test.cpp index 5f08bb1e2..a6cd19b75 100644 --- a/tests/adiar/internal/data_structures/levelized_priority_queue.test.cpp +++ b/tests/adiar/internal/data_structures/levelized_priority_queue.test.cpp @@ -77,7 +77,6 @@ using test_priority_queue = levelized_priority_queue, 1u>; @@ -4113,7 +4112,6 @@ go_bandit([]() { lpq_test_gt, 1u, memory_mode::Internal, - shared_file, 1u, std::greater<>, 1u> @@ -4153,7 +4151,6 @@ go_bandit([]() { lpq_test_gt, 1u, memory_mode::Internal, - shared_file, 1u, std::greater<>, 1u> @@ -4180,7 +4177,6 @@ go_bandit([]() { lpq_test_gt, 1u, memory_mode::Internal, - shared_file, 1u, std::greater<>, 1u> @@ -4240,7 +4236,6 @@ go_bandit([]() { lpq_test_gt, 0u, memory_mode::Internal, - shared_file, 1u, std::greater<>, 1u> @@ -4280,7 +4275,6 @@ go_bandit([]() { lpq_test_gt, 0u, memory_mode::Internal, - shared_file, 1u, std::greater<>, 1u> @@ -4307,7 +4301,6 @@ go_bandit([]() { lpq_test_gt, 0u, memory_mode::Internal, - shared_file, 1u, std::greater<>, 1u> @@ -4358,7 +4351,6 @@ go_bandit([]() { lpq_test_lt, 1u, memory_mode::Internal, - shared_file, 1u, std::less<>, 0u> @@ -4383,7 +4375,6 @@ go_bandit([]() { lpq_test_lt, 1u, memory_mode::Internal, - shared_file, 1u, std::less<>, 0u> @@ -4408,7 +4399,6 @@ go_bandit([]() { lpq_test_lt, 1u, memory_mode::Internal, - shared_file, 1u, std::less<>, 0u> @@ -4435,7 +4425,6 @@ go_bandit([]() { lpq_test_lt, 1u, memory_mode::Internal, - lpq_test_file, 1u, std::less<>, 0u> @@ -4478,7 +4467,6 @@ go_bandit([]() { lpq_test_lt, 1u, memory_mode::Internal, - lpq_test_file, 1u, std::less<>, 0u> @@ -4514,7 +4502,6 @@ go_bandit([]() { lpq_test_lt, 0u, memory_mode::Internal, - shared_file, 1u, std::less<>, 0u> @@ -4539,7 +4526,6 @@ go_bandit([]() { lpq_test_lt, 0u, memory_mode::Internal, - lpq_test_file, 1u, std::less<>, 0u> @@ -4582,7 +4568,6 @@ go_bandit([]() { lpq_test_lt, 0u, memory_mode::Internal, - lpq_test_file, 1u, std::less, 0u> @@ -4630,7 +4615,6 @@ go_bandit([]() { lpq_test_lt, 1u, memory_mode::Internal, - lpq_test_file, 2u, std::less<>, 0u> @@ -4704,7 +4688,6 @@ go_bandit([]() { lpq_test_lt, 0u, memory_mode::Internal, - lpq_test_file, 2u, std::less<>, 0u> From 1e9e0667b5e4d07cc6b3c20331b5a2c4a47bfaa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffan=20S=C3=B8lvsten?= Date: Thu, 29 Jan 2026 10:13:15 +0100 Subject: [PATCH 07/12] Fix virtual functions without an implementation are not marked as 'pure' --- src/adiar/internal/data_structures/level_merger.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/adiar/internal/data_structures/level_merger.h b/src/adiar/internal/data_structures/level_merger.h index 3a6ab1b3b..9f8f43d4e 100644 --- a/src/adiar/internal/data_structures/level_merger.h +++ b/src/adiar/internal/data_structures/level_merger.h @@ -47,13 +47,13 @@ namespace adiar::internal public: virtual bool - can_pull(); + can_pull() = 0; virtual value_type - peek(); + peek() = 0; virtual value_type - pull(); + pull() = 0; }; private: From f14de0b55e7b4d60d66f91e2d7c59e36e088d461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffan=20S=C3=B8lvsten?= Date: Fri, 16 Jan 2026 12:32:40 +0100 Subject: [PATCH 08/12] Skip intermediate temporary file for diagram levels in Intercut --- src/adiar/internal/algorithms/intercut.h | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/adiar/internal/algorithms/intercut.h b/src/adiar/internal/algorithms/intercut.h index 383197018..8842e3d1e 100644 --- a/src/adiar/internal/algorithms/intercut.h +++ b/src/adiar/internal/algorithms/intercut.h @@ -194,7 +194,7 @@ namespace adiar::internal // In the general case, we have to use it both in the priority queue // `intercut_pq` below, and for a lookahead of the algorithm. The main issue // is how to design the priority queue such that it can retrieve, merge with - // `dd_levels`, and expose `xs`. + // the levels of `dd` and expose `xs`. shared_file hit_levels; { ofstream lw(hit_levels); @@ -213,14 +213,8 @@ namespace adiar::internal out_arcs->max_1level_cut = 0; - shared_file dd_levels; - { - ofstream lw(dd_levels); - dd_support(dd, [&lw](const typename intercut_policy::label_type x) { lw << x; }); - } - // Add request for root in the queue - pq_t intercut_pq({ dd_levels, hit_levels }, pq_memory, max_pq_size, stats_intercut.lpq); + pq_t intercut_pq({ dd, hit_levels }, pq_memory, max_pq_size, stats_intercut.lpq); intercut_pq.push(intercut_req(ptr_uint64::nil(), n.uid(), std::min(l, n.label()))); // Process nodes of the decision diagram in topological order From f9aeb86f2b3f90659ff0add097eb0c0af3b09598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffan=20S=C3=B8lvsten?= Date: Fri, 16 Jan 2026 12:33:13 +0100 Subject: [PATCH 09/12] Add 'vector' wrapper This allows us to deal with sequential/random-access data that is stored in internal memory. For example, this could be a small list of levels (at most 2MiB) from a generator which needs to be read more than once, e.g. Intercut. --- makefile | 3 + src/adiar/CMakeLists.txt | 1 + src/adiar/internal/data_structures/vector.h | 185 ++++++++++++++++++ .../internal/data_structures/CMakeLists.txt | 1 + .../internal/data_structures/vector.test.cpp | 83 ++++++++ 5 files changed, 273 insertions(+) create mode 100644 src/adiar/internal/data_structures/vector.h create mode 100644 tests/adiar/internal/data_structures/vector.test.cpp diff --git a/makefile b/makefile index 782cf9c32..3204a6e06 100644 --- a/makefile +++ b/makefile @@ -154,6 +154,9 @@ tests/adiar/internal/data_structures/sorter: tests/adiar/internal/data_structures/stack: $(MAKE) $(MAKE_FLAGS) tests TEST_SUBFOLDER=adiar/internal/data_structures/ TEST_NAME=stack +tests/adiar/internal/data_structures/vector: + $(MAKE) $(MAKE_FLAGS) tests TEST_SUBFOLDER=adiar/internal/data_structures/ TEST_NAME=vector + tests/adiar/internal/data_types/arc: $(MAKE) $(MAKE_FLAGS) tests TEST_SUBFOLDER=adiar/internal/data_types/ TEST_NAME=arc diff --git a/src/adiar/CMakeLists.txt b/src/adiar/CMakeLists.txt index d3f8d4852..a761b3163 100644 --- a/src/adiar/CMakeLists.txt +++ b/src/adiar/CMakeLists.txt @@ -66,6 +66,7 @@ set(HEADERS internal/data_structures/priority_queue.h internal/data_structures/sorter.h internal/data_structures/stack.h + internal/data_structures/vector.h # adiar/internal/data_types internal/data_types/arc.h diff --git a/src/adiar/internal/data_structures/vector.h b/src/adiar/internal/data_structures/vector.h new file mode 100644 index 000000000..7ef1fcbcd --- /dev/null +++ b/src/adiar/internal/data_structures/vector.h @@ -0,0 +1,185 @@ +#ifndef ADIAR_INTERNAL_DATA_STRUCTURES_VECTOR_H +#define ADIAR_INTERNAL_DATA_STRUCTURES_VECTOR_H + +#include + +#include + +namespace adiar::internal +{ + template + class vector; + + ////////////////////////////////////////////////////////////////////////////////////////////////// + /// \brief Wrapper for TPIE's internal memory vector. + ////////////////////////////////////////////////////////////////////////////////////////////////// + template + class vector + { + private: + using vector_type = tpie::internal_vector; + + size_t _capacity; + vector_type _vector; + + public: + using value_type = typename vector_type::value_type; + using iterator = typename vector_type::iterator; + using const_iterator = typename vector_type::const_iterator; + + public: + //////////////////////////////////////////////////////////////////////////////////////////////// + static size_t + memory_usage(size_t capacity = 0) + { + return vector_type::memory_usage(capacity); + } + + static size_t + memory_fits(size_t memory_bytes) + { + const size_t c = vector_type::memory_fits(memory_bytes); + + adiar_assert(memory_usage(c) <= memory_bytes, "memory_fits and memory_usage should agree."); + return c; + } + + public: + //////////////////////////////////////////////////////////////////////////////////////////////// + vector(size_t capacity = 0) + : _capacity(capacity) + , _vector(capacity) + {} + + //////////////////////////////////////////////////////////////////////////////////////////////// + value_type& + at(size_t i) + { + adiar_assert(i < this->size(), "Use of invalid index!"); + return this->_vector[i]; + } + + const value_type& + at(size_t i) const + { + adiar_assert(i < this->size(), "Use of invalid index!"); + return this->_vector[i]; + } + + value_type& + operator[](size_t i) + { + return this->at(i); + } + + const value_type& + operator[](size_t i) const + { + return this->at(i); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + value_type& + front() + { + return this->_vector.front(); + } + + const value_type& + front() const + { + return this->_vector.front(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + value_type& + back() + { + return this->_vector.back(); + } + + const value_type& + back() const + { + return this->_vector.back(); + } + + value_type& + push_back(const value_type& x) + { + adiar_assert(this->size() < this->_capacity, "Cannot push at full capacity"); + return this->_vector.push_back(x); + } + + void + pop_back() + { + return this->_vector.pop_back(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + iterator + begin() + { + return this->_vector.begin(); + } + + const_iterator + begin() const + { + return this->_vector.begin(); + } + + iterator + end() + { + return this->_vector.end(); + } + + const_iterator + end() const + { + return this->_vector.end(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + bool + empty() const + { + return this->_vector.empty(); + } + + size_t + size() const + { + return this->_vector.size(); + } + + size_t + capacity() const + { + return this->_capacity; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void + reset() + { + this->_vector.clear(); + } + + void + reset(size_t capacity) + { + this->_vector.resize(capacity); + this->_capacity = capacity; + } + }; + + ////////////////////////////////////////////////////////////////////////////////////////////////// + /// \brief An external memory vector in TPIE. + ////////////////////////////////////////////////////////////////////////////////////////////////// + // TODO Implement an external memory (temporary) vector (wrapper on `file`, I suppose). +} + +#endif // ADIAR_INTERNAL_DATA_STRUCTURES_VECTOR_H diff --git a/tests/adiar/internal/data_structures/CMakeLists.txt b/tests/adiar/internal/data_structures/CMakeLists.txt index 63fccba65..82e7af4d2 100644 --- a/tests/adiar/internal/data_structures/CMakeLists.txt +++ b/tests/adiar/internal/data_structures/CMakeLists.txt @@ -3,3 +3,4 @@ add_test(adiar-internal-data_structures-levelized_priority_queue levelized_prior add_test(adiar-internal-data_structures-priority_queue priority_queue.test.cpp) add_test(adiar-internal-data_structures-sorter sorter.test.cpp) add_test(adiar-internal-data_structures-stack stack.test.cpp) +add_test(adiar-internal-data_structures-vector vector.test.cpp) diff --git a/tests/adiar/internal/data_structures/vector.test.cpp b/tests/adiar/internal/data_structures/vector.test.cpp new file mode 100644 index 000000000..d387ef7ce --- /dev/null +++ b/tests/adiar/internal/data_structures/vector.test.cpp @@ -0,0 +1,83 @@ +#include "../../../test.h" + +#include + +go_bandit([]() { + describe("adiar/internal/vector.h", []() { + describe("vector", []() { + vector v(16); + + it("is initially empty", [&v]() { + AssertThat(v.empty(), Is().True()); + AssertThat(v.size(), Is().EqualTo(0u)); + }); + + it("can push elements", [&v]() { + v.push_back(3); + v.push_back(1); + v.push_back(2); + + AssertThat(v.empty(), Is().False()); + AssertThat(v.size(), Is().EqualTo(3u)); + }); + + it("can retrieve the front element", [&v]() { + AssertThat(v.front(), Is().EqualTo(3)); + }); + + it("can retrieve the back element", [&v]() { + AssertThat(v.back(), Is().EqualTo(2)); + }); + + it("can retrieve elements via '.at(i)'", [&v]() { + AssertThat(v.at(0), Is().EqualTo(3)); + AssertThat(v.at(1), Is().EqualTo(1)); + AssertThat(v.at(2), Is().EqualTo(2)); + }); + + it("can pop element", [&v]() { + v.pop_back(); + + AssertThat(v.empty(), Is().False()); + AssertThat(v.size(), Is().EqualTo(2u)); + }); + + it("can retrieve different element from the back", [&v]() { + AssertThat(v.back(), Is().EqualTo(1)); + }); + + it("can new elements", [&v]() { + v.push_back(4); + v.push_back(4); + + AssertThat(v.empty(), Is().False()); + AssertThat(v.size(), Is().EqualTo(4u)); + }); + + it("can retrieve different element from the back", [&v]() { + AssertThat(v.back(), Is().EqualTo(4)); + }); + + it("can retrieve elements via '[i]'", [&v]() { + AssertThat(v[0], Is().EqualTo(3)); + AssertThat(v[1], Is().EqualTo(1)); + AssertThat(v[2], Is().EqualTo(4)); + AssertThat(v[3], Is().EqualTo(4)); + }); + + it("can iterate forwards through the vector", [&v]() { + int i = 0; + const int expected[4] = { 3, 1, 4, 4 }; + + for (auto iter = v.begin(); iter != v.end(); ++iter) { + AssertThat(*iter, Is().EqualTo(expected[i])); + i += 1; + } + }); + }); + + describe("vector", []() { + // TODO: Add tests when implemented. + }); + }); +}); From a163d8dc1e2d338d2356240b9f4a2f000f9d09fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffan=20S=C3=B8lvsten?= Date: Fri, 16 Jan 2026 12:34:19 +0100 Subject: [PATCH 10/12] Fix incorrect naming in 'stack' unit tests --- tests/adiar/internal/data_structures/stack.test.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/adiar/internal/data_structures/stack.test.cpp b/tests/adiar/internal/data_structures/stack.test.cpp index 117c64abb..3a6480daa 100644 --- a/tests/adiar/internal/data_structures/stack.test.cpp +++ b/tests/adiar/internal/data_structures/stack.test.cpp @@ -3,8 +3,8 @@ #include go_bandit([]() { - describe("adiar/internal/sorter.h", []() { - describe("sorter", []() { + describe("adiar/internal/stack.h", []() { + describe("stack", []() { stack s(16); it("is initially empty", [&s]() { @@ -52,7 +52,7 @@ go_bandit([]() { }); }); - describe("sorter", []() { + describe("stack", []() { // TODO: Add tests when implemented. }); }); From 7bda6adc3250acd69eb4bbb298bc1088f9443ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffan=20S=C3=B8lvsten?= Date: Fri, 16 Jan 2026 10:47:33 +0100 Subject: [PATCH 11/12] Use 'tpie::array' instead of 'tpie::internal_vector' for 'vector' The 'tpie::internal_vector' has a few pecularities and missing features. Most importantly, it does not support the ability to iterate through it in reverse. So, we'll just use the 'tpie::array' directly. --- src/adiar/internal/data_structures/vector.h | 113 ++++++++++++------ .../internal/data_structures/vector.test.cpp | 10 ++ 2 files changed, 84 insertions(+), 39 deletions(-) diff --git a/src/adiar/internal/data_structures/vector.h b/src/adiar/internal/data_structures/vector.h index 7ef1fcbcd..862da3a83 100644 --- a/src/adiar/internal/data_structures/vector.h +++ b/src/adiar/internal/data_structures/vector.h @@ -11,34 +11,49 @@ namespace adiar::internal class vector; ////////////////////////////////////////////////////////////////////////////////////////////////// - /// \brief Wrapper for TPIE's internal memory vector. + /// \brief Wrapper for TPIE's internal memory array to match the interface of `std::vector`. + /// + /// \details TPIE also provides the `internal_vector`. Yet, that does not (yet) include a reverse + /// iterator. Hence, for now it's easier for us to just wrap the `array` directly + /// instead of using the array. This also gives us the freedom to make it more conform to + /// the `std::vector`. + // + // TODO: explicit or implicit `resize` when full? ////////////////////////////////////////////////////////////////////////////////////////////////// template class vector { private: - using vector_type = tpie::internal_vector; + using array_type = tpie::array; + /// \brief The underlying (internal memory) array to store the information. + array_type _array; + + /// \brief The size of `_array` that was allocated. size_t _capacity; - vector_type _vector; + + /// \brief The number of elements placed in the vector. + size_t _size = 0; public: - using value_type = typename vector_type::value_type; - using iterator = typename vector_type::iterator; - using const_iterator = typename vector_type::const_iterator; + using value_type = typename array_type::value_type; + using iterator = typename array_type::iterator; + using reverse_iterator = typename array_type::reverse_iterator; + using const_iterator = typename array_type::const_iterator; + using const_reverse_iterator = typename array_type::const_reverse_iterator; public: //////////////////////////////////////////////////////////////////////////////////////////////// static size_t memory_usage(size_t capacity = 0) { - return vector_type::memory_usage(capacity); + return array_type::memory_usage(capacity); } static size_t memory_fits(size_t memory_bytes) { - const size_t c = vector_type::memory_fits(memory_bytes); + const size_t c = array_type::memory_fits(memory_bytes); adiar_assert(memory_usage(c) <= memory_bytes, "memory_fits and memory_usage should agree."); return c; @@ -47,8 +62,8 @@ namespace adiar::internal public: //////////////////////////////////////////////////////////////////////////////////////////////// vector(size_t capacity = 0) - : _capacity(capacity) - , _vector(capacity) + : _array(capacity) + , _capacity(capacity) {} //////////////////////////////////////////////////////////////////////////////////////////////// @@ -56,14 +71,14 @@ namespace adiar::internal at(size_t i) { adiar_assert(i < this->size(), "Use of invalid index!"); - return this->_vector[i]; + return this->_array[i]; } const value_type& at(size_t i) const { adiar_assert(i < this->size(), "Use of invalid index!"); - return this->_vector[i]; + return this->_array[i]; } value_type& @@ -82,100 +97,120 @@ namespace adiar::internal value_type& front() { - return this->_vector.front(); + return this->_array[0]; } const value_type& front() const { - return this->_vector.front(); + return this->_array[0]; } //////////////////////////////////////////////////////////////////////////////////////////////// + public: value_type& back() { - return this->_vector.back(); + return this->_array[this->_size - 1]; } const value_type& back() const { - return this->_vector.back(); + return this->_array[this->_size - 1]; } value_type& push_back(const value_type& x) { - adiar_assert(this->size() < this->_capacity, "Cannot push at full capacity"); - return this->_vector.push_back(x); + adiar_assert(this->_size < this->_capacity, "Cannot push at full capacity"); + this->_array[this->_size] = x; + this->_size += 1; + return this->back(); } void pop_back() { - return this->_vector.pop_back(); + this->_size -= 1; } //////////////////////////////////////////////////////////////////////////////////////////////// iterator begin() { - return this->_vector.begin(); + return this->_array.begin(); } const_iterator begin() const { - return this->_vector.begin(); + return this->_array.begin(); } iterator end() { - return this->_vector.end(); + return this->_array.begin() + this->_size; } const_iterator end() const { - return this->_vector.end(); + return this->_array.begin() + this->_size; } //////////////////////////////////////////////////////////////////////////////////////////////// - bool - empty() const + reverse_iterator + rbegin() { - return this->_vector.empty(); + return this->_array.rend() - this->_size; } - size_t - size() const + const_reverse_iterator + rbegin() const { - return this->_vector.size(); + return this->_array.rend() - this->_size; } - size_t - capacity() const + reverse_iterator + rend() { - return this->_capacity; + return this->_array.rend(); + } + + const_reverse_iterator + rend() const + { + return this->_array.rend(); } //////////////////////////////////////////////////////////////////////////////////////////////// - void - reset() + size_t + size() const { - this->_vector.clear(); + return this->_size; } - void - reset(size_t capacity) + bool + empty() const + { + return this->_size == 0; + } + + size_t + capacity() const { - this->_vector.resize(capacity); - this->_capacity = capacity; + return this->_capacity; } }; + ////////////////////////////////////////////////////////////////////////////////////////////////// + /// \brief Alias for a `vector`. + ////////////////////////////////////////////////////////////////////////////////////////////////// + template + using internal_vector = vector; + ////////////////////////////////////////////////////////////////////////////////////////////////// /// \brief An external memory vector in TPIE. ////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/tests/adiar/internal/data_structures/vector.test.cpp b/tests/adiar/internal/data_structures/vector.test.cpp index d387ef7ce..70c24217d 100644 --- a/tests/adiar/internal/data_structures/vector.test.cpp +++ b/tests/adiar/internal/data_structures/vector.test.cpp @@ -74,6 +74,16 @@ go_bandit([]() { i += 1; } }); + + it("can iterate backwards through the vector", [&v]() { + int i = 3; + const int expected[4] = { 3, 1, 4, 4 }; + + for (auto iter = v.rbegin(); iter != v.rend(); ++iter) { + AssertThat(*iter, Is().EqualTo(expected[i])); + i -= 1; + } + }); }); describe("vector", []() { From b99bbde592be35e0e2615bfb1b3277550d36f9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffan=20S=C3=B8lvsten?= Date: Fri, 16 Jan 2026 12:35:14 +0100 Subject: [PATCH 12/12] Store cut variables in an internal memory vector instead of a temporary file --- src/adiar/internal/algorithms/convert.h | 15 +++++------- src/adiar/internal/algorithms/intercut.h | 29 ++++++++++++------------ src/adiar/zdd/change.cpp | 7 +++--- src/adiar/zdd/complement.cpp | 7 +++--- src/adiar/zdd/expand.cpp | 7 +++--- 5 files changed, 31 insertions(+), 34 deletions(-) diff --git a/src/adiar/internal/algorithms/convert.h b/src/adiar/internal/algorithms/convert.h index 6c7e1afb2..cd4e3884e 100644 --- a/src/adiar/internal/algorithms/convert.h +++ b/src/adiar/internal/algorithms/convert.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -54,9 +55,9 @@ namespace adiar::internal static typename to_policy::dd_type on_terminal_input(const bool terminal_value, const typename from_policy::dd_type& /*dd*/, - const shared_file& dom) + const internal_vector& dom) { - adiar_assert(dom->size() > 0, "Emptiness check is before terminal check"); + adiar_assert(dom.size() > 0, "Emptiness check is before terminal check"); node::uid_type prior_node = node::uid_type(terminal_value); @@ -65,13 +66,9 @@ namespace adiar::internal bool has_output = true; node_ofstream nw(nf); - ifstream ls(dom); - - while (ls.can_pull()) { - const typename to_policy::label_type next_label = ls.pull(); - + for (auto iter = dom.rbegin(); iter != dom.rend(); ++iter) { const tuple children = from_policy::reduction_rule_inv(prior_node); - const node next_node = node(next_label, to_policy::max_id, children[0], children[1]); + const node next_node = node(*iter, to_policy::max_id, children[0], children[1]); const typename to_policy::pointer_type reduction_result = to_policy::reduction_rule(next_node); @@ -79,7 +76,7 @@ namespace adiar::internal prior_node = next_node.uid(); nw.unsafe_push(next_node); - nw.unsafe_push(level_info(next_label, 1u)); + nw.unsafe_push(level_info(*iter, 1u)); } else { // If we kill the resulting node once, then we will also do it for all the other labels we // still are missing. diff --git a/src/adiar/internal/algorithms/intercut.h b/src/adiar/internal/algorithms/intercut.h index 8842e3d1e..8a7c8c2de 100644 --- a/src/adiar/internal/algorithms/intercut.h +++ b/src/adiar/internal/algorithms/intercut.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -195,18 +196,14 @@ namespace adiar::internal // `intercut_pq` below, and for a lookahead of the algorithm. The main issue // is how to design the priority queue such that it can retrieve, merge with // the levels of `dd` and expose `xs`. - shared_file hit_levels; - { - ofstream lw(hit_levels); - for (auto x = xs(); x; x = xs()) { lw << x.value(); } - - if (lw.size() == 0) { return intercut_policy::on_empty_labels(dd); } - } + internal_vector hit_levels(dd::max_label); + for (auto x = xs(); x; x = xs()) { hit_levels.push_back(x.value()); } + if (hit_levels.empty()) { return intercut_policy::on_empty_labels(dd); } if (n.is_terminal()) { return intercut_policy::on_terminal_input(n.value(), dd, hit_levels); } - ifstream ls(hit_levels); - typename intercut_policy::label_type l = ls.pull(); + typename internal_vector::iterator ls = + hit_levels.begin(); shared_levelized_file out_arcs; arc_ofstream aw(out_arcs); @@ -214,8 +211,11 @@ namespace adiar::internal out_arcs->max_1level_cut = 0; // Add request for root in the queue - pq_t intercut_pq({ dd, hit_levels }, pq_memory, max_pq_size, stats_intercut.lpq); - intercut_pq.push(intercut_req(ptr_uint64::nil(), n.uid(), std::min(l, n.label()))); + pq_t intercut_pq({ dd, make_generator(hit_levels.begin(), hit_levels.end()) }, + pq_memory, + max_pq_size, + stats_intercut.lpq); + intercut_pq.push(intercut_req(ptr_uint64::nil(), n.uid(), std::min(*ls, n.label()))); // Process nodes of the decision diagram in topological order while (!intercut_pq.empty()) { @@ -225,12 +225,13 @@ namespace adiar::internal const typename intercut_policy::label_type out_label = intercut_pq.current_level(); typename intercut_policy::id_type out_id = 0; - const bool hit_level = out_label == l; + const bool hit_level = out_label == *ls; // Forward to next label to cut on after this level - while (ls.can_pull() && l <= out_label) { l = ls.pull(); } + while (ls != hit_levels.end() && *ls <= out_label) { ++ls; } - if (!ls.can_pull() && l <= out_label) { l = intercut_policy::max_label + 1; } + typename intercut_policy::label_type l = + ls == hit_levels.end() ? intercut_policy::max_label + 1 : *ls; // Update max 1-level cut out_arcs->max_1level_cut = std::max(out_arcs->max_1level_cut, intercut_pq.size()); diff --git a/src/adiar/zdd/change.cpp b/src/adiar/zdd/change.cpp index b6906dcb2..0b33d8fea 100644 --- a/src/adiar/zdd/change.cpp +++ b/src/adiar/zdd/change.cpp @@ -1,8 +1,10 @@ #include +#include #include #include #include +#include #include #include #include @@ -29,12 +31,11 @@ namespace adiar static zdd on_terminal_input(const bool terminal_value, const zdd& dd, - const internal::shared_file& vars) + const internal::internal_vector& vars) { // TODO: simplify with generator function as input if (terminal_value) { - internal::ifstream ls(vars); - return zdd_vars(make_generator(ls)); + return zdd_vars(make_generator(vars.rbegin(), vars.rend())); } else { return dd; } diff --git a/src/adiar/zdd/complement.cpp b/src/adiar/zdd/complement.cpp index c0bb11696..266d83a0e 100644 --- a/src/adiar/zdd/complement.cpp +++ b/src/adiar/zdd/complement.cpp @@ -57,12 +57,11 @@ namespace adiar static zdd on_terminal_input(const bool terminal_value, const zdd& /*dd*/, - const internal::shared_file& universe) + const internal::internal_vector& universe) { // TODO: remove - internal::ifstream ls(universe); - - const generator universe_generator = make_generator(ls); + const generator universe_generator = + make_generator(universe.rbegin(), universe.rend()); using complement_chain_converter = internal::chain_converter>; diff --git a/src/adiar/zdd/expand.cpp b/src/adiar/zdd/expand.cpp index 5b0bbb713..55c55e6d5 100644 --- a/src/adiar/zdd/expand.cpp +++ b/src/adiar/zdd/expand.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -30,12 +31,10 @@ namespace adiar static zdd on_terminal_input(const bool terminal_value, const zdd& dd, - const internal::shared_file& labels) + const internal::internal_vector& vars) { - // TODO: simplify when labels are a generator if (terminal_value) { - internal::ifstream ls(labels); - return zdd_powerset(make_generator(ls)); + return zdd_powerset(make_generator(vars.rbegin(), vars.rend())); } else { return dd; }