From c4fb286908c9550c967bcdf8175a067cc364e5f4 Mon Sep 17 00:00:00 2001 From: Sanya239 Date: Thu, 2 Apr 2026 14:15:27 +0300 Subject: [PATCH 01/13] add bp_tree.h --- include/pixie/bp_tree.h | 131 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 include/pixie/bp_tree.h diff --git a/include/pixie/bp_tree.h b/include/pixie/bp_tree.h new file mode 100644 index 0000000..c267dfe --- /dev/null +++ b/include/pixie/bp_tree.h @@ -0,0 +1,131 @@ +#pragma once + +#include + +#include +#include + +namespace pixie { +/** + * @brief A node class of BP tree + */ +struct BpNode { + size_t number; + size_t pos; + + BpNode(size_t node_number, size_t bp_pos) + : number(node_number), pos(bp_pos) {} +}; + +/** + * @brief A tree class based on the balances parentheses (BP) + * representation + */ +class BpTree { + private: + const size_t num_bits_; + RmMTree rmm; + + public: + /** + * @brief Constructor from an external array of uint64_t + */ + explicit BpTree(const std::vector& words, size_t tree_size) + : num_bits_(2 * tree_size), rmm(words, 2 * tree_size) {} + + /** + * @brief Returns the root node + */ + BpNode root() const { return BpNode(0, 0); } + + /** + * @brief Returns the size of the tree + */ + size_t size() const { return num_bits_; } + + /** + * @brief Indicates if @p node is a leaf + */ + bool is_leaf(const BpNode& node) const { + return (node.pos + 1 == num_bits_) or rmm.bit(node.pos + 1) == 0; + } + + /** + * @brief Returns the number of children of a @p node + * this method has O(d) time complexity! + * + * TODO try make this faster + */ + size_t degree(const BpNode& node) const { + if (is_leaf(node)) { + return 0; + } + BpNode child = first_child(node); + int child_count = 1; + while (true) { + if (is_last_child(child)) { + return child_count; + } + child = next_sibling(child); + child_count++; + } + } + + /** + * @brief Returns first child of a @p node + */ + BpNode first_child(const BpNode& node) const { + size_t pos = node.pos + 1; + size_t num = node.number + 1; + return BpNode(num, pos); + } + + /** + * @brief Returns the i-th child of @p node + * Indexing starts at 0 + * this method has O(i) time complexity! + * + * TODO try make this faster + */ + BpNode child(const BpNode& node, size_t i) const { + BpNode child = first_child(node); + while (i--) { + child = next_sibling(child); + } + return child; + } + + /** + * @brief Returns the parent of a @p node if @p node is not root, + * else returns root + */ + BpNode parent(const BpNode& node) const { + if (node.number == 0) { + return root(); + } + size_t pos = rmm.enclose(node.pos); + size_t num = rmm.rank1(pos); + return BpNode(num, pos); + } + + /** + * @brief Indicates if @p node is last child + */ + bool is_last_child(const BpNode& node) const { + size_t end = rmm.close(node.pos); + if (end + 1 >= num_bits_) { + return true; + } + return rmm.bit(end + 1) == 0; + } + + /** + * @brief Returns next sibling of a @p node + */ + BpNode next_sibling(const BpNode& node) const { + size_t pos = rmm.close(node.pos) + 1; + size_t num = rmm.rank1(pos); + return BpNode(num, pos); + } +}; +} // namespace pixie From b04f6205fd21f55df1d45000bebfc1de2f03424a Mon Sep 17 00:00:00 2001 From: Sanya239 Date: Sun, 19 Apr 2026 19:10:44 +0300 Subject: [PATCH 02/13] fix bp tree --- include/pixie/bp_tree.h | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/include/pixie/bp_tree.h b/include/pixie/bp_tree.h index c267dfe..d73bc6c 100644 --- a/include/pixie/bp_tree.h +++ b/include/pixie/bp_tree.h @@ -3,7 +3,6 @@ #include #include -#include namespace pixie { /** @@ -36,7 +35,7 @@ class BpTree { /** * @brief Returns the root node */ - BpNode root() const { return BpNode(0, 0); } + static BpNode root() { return BpNode(0, 0); } /** * @brief Returns the size of the tree @@ -50,6 +49,11 @@ class BpTree { return (node.pos + 1 == num_bits_) or rmm.bit(node.pos + 1) == 0; } + /** + * @brief Indicates if @p node is a root + */ + static bool is_root(const BpNode& node) { return node.number == 0; } + /** * @brief Returns the number of children of a @p node * this method has O(d) time complexity! @@ -74,7 +78,7 @@ class BpTree { /** * @brief Returns first child of a @p node */ - BpNode first_child(const BpNode& node) const { + static BpNode first_child(const BpNode& node) { size_t pos = node.pos + 1; size_t num = node.number + 1; return BpNode(num, pos); @@ -103,8 +107,8 @@ class BpTree { if (node.number == 0) { return root(); } - size_t pos = rmm.enclose(node.pos); - size_t num = rmm.rank1(pos); + size_t pos = rmm.enclose(node.pos + 1); + size_t num = rmm.rank1(pos) - 1; return BpNode(num, pos); } @@ -112,7 +116,7 @@ class BpTree { * @brief Indicates if @p node is last child */ bool is_last_child(const BpNode& node) const { - size_t end = rmm.close(node.pos); + size_t end = rmm.close(node.pos + 1); if (end + 1 >= num_bits_) { return true; } @@ -123,8 +127,8 @@ class BpTree { * @brief Returns next sibling of a @p node */ BpNode next_sibling(const BpNode& node) const { - size_t pos = rmm.close(node.pos) + 1; - size_t num = rmm.rank1(pos); + size_t pos = rmm.close(node.pos + 1) + 1; + size_t num = rmm.rank1(pos + 1) - 1; return BpNode(num, pos); } }; From cb355e9b1ecda5da70849ec2c8ca832ffa9101c7 Mon Sep 17 00:00:00 2001 From: Sanya239 Date: Sun, 19 Apr 2026 19:11:02 +0300 Subject: [PATCH 03/13] add bp tree tests --- CMakeLists.txt | 9 +++ include/pixie/utils.h | 34 +++++++++++ src/tests/bp_tree_tests.cpp | 110 ++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 src/tests/bp_tree_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 87c59ad..8ad7c17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,6 +175,15 @@ if(PIXIE_TESTS) gtest_main ${PIXIE_DIAGNOSTICS_LIBS}) + add_executable(bp_tree_tests + src/tests/bp_tree_tests.cpp) + target_include_directories(bp_tree_tests + PUBLIC include) + target_link_libraries(bp_tree_tests + gtest + gtest_main + ${PIXIE_DIAGNOSTICS_LIBS}) + add_executable(dfuds_tree_tests src/tests/dfuds_tree_tests.cpp) target_include_directories(dfuds_tree_tests diff --git a/include/pixie/utils.h b/include/pixie/utils.h index 2ce0234..2c5f2f8 100644 --- a/include/pixie/utils.h +++ b/include/pixie/utils.h @@ -6,6 +6,9 @@ #include #include +#include "bp_tree.h" + +using pixie::BpNode; using pixie::LoudsNode; std::vector> generate_random_tree(size_t tree_size, @@ -85,6 +88,29 @@ std::vector adj_to_louds( return louds; } +std::vector adj_to_bp(size_t tree_size, + const std::vector>& adj) { + size_t bp_size = tree_size * 2; + std::vector bp((bp_size + 63) / 64, 0); + std::vector> stack; + stack.push_back(std::make_pair(0, 0)); + int pos = 0; + bp[pos >> 6] = bp[pos >> 6] | (1ULL << (pos & 63)); + while (!stack.empty()) { + auto& [v, p] = stack.back(); + p++; + if (p >= adj[v].size()) { + pos++; + stack.pop_back(); + continue; + } + pos++; + bp[pos >> 6] = bp[pos >> 6] | (1ULL << (pos & 63)); + stack.push_back(std::make_pair(adj[v][p], 0)); + } + return bp; +} + struct AdjListNode { size_t number; }; @@ -97,6 +123,14 @@ bool operator==(const LoudsNode& b, const AdjListNode& a) { return a.number == b.number; } +bool operator==(const AdjListNode& a, const BpNode& b) { + return a.number == b.number; +} + +bool operator==(const BpNode& b, const AdjListNode& a) { + return a.number == b.number; +} + class AdjListTree { private: std::vector> adj; diff --git a/src/tests/bp_tree_tests.cpp b/src/tests/bp_tree_tests.cpp new file mode 100644 index 0000000..f262c5c --- /dev/null +++ b/src/tests/bp_tree_tests.cpp @@ -0,0 +1,110 @@ +#include "pixie/bp_tree.h" + +#include +#include + +#include +#include + +using pixie::BpNode; +using pixie::BpTree; + +TEST(BpTreeTest, Basic) { + std::vector> adj = {{0, 1}, {0, 2}, {1, 3}, {2, 4}, {3}}; + size_t tree_size = 5; + + std::vector bp = adj_to_bp(tree_size, adj); + + BpTree bp_tree(bp, 5); + AdjListTree debug_tree(adj); + + BpNode cur = bp_tree.root(); + AdjListNode debug = debug_tree.root(); + for (size_t i = 0; i < tree_size - 1; i++) { + EXPECT_EQ(cur, debug); + cur = bp_tree.child(cur, 0); + debug = debug_tree.child(debug, 0); + } + EXPECT_EQ(cur, debug); +} + +TEST(BpTreeTest, RandomTreeDFS) { + for (size_t tree_size = 8; tree_size < (1 << 22); tree_size <<= 1) { + std::mt19937_64 rng(42); + std::vector> adj = generate_random_tree(tree_size, rng); + adj = dfs_order(tree_size, adj); + std::vector bp = adj_to_bp(tree_size, adj); + BpTree bp_tree(bp, tree_size); + AdjListTree debug_tree(adj); + + std::stack> st; + + st.push({bp_tree.root(), debug_tree.root()}); + + while (!st.empty()) { + auto cur = st.top().first; + auto debug = st.top().second; + st.pop(); + EXPECT_EQ(cur, debug); + EXPECT_EQ(bp_tree.parent(cur), debug_tree.parent(debug)); + + if (cur.number > 0) { + EXPECT_EQ(bp_tree.is_last_child(cur), debug_tree.is_last_child(debug)); + } + size_t deg = bp_tree.degree(cur); + EXPECT_EQ(deg, debug_tree.degree(debug)); + + if (deg == 0) { + continue; + } + auto child = bp_tree.first_child(cur); + auto debug_child = debug_tree.first_child(debug); + st.push({child, debug_child}); + for (size_t i = 1; i < deg; i++) { + child = bp_tree.next_sibling(child); + st.push({child, debug_tree.child(debug, i)}); + } + } + } +} + + +TEST(BpTreeTest, RandomTreeBFS) { + for (size_t tree_size = 8; tree_size < (1 << 22); tree_size <<= 1) { + std::mt19937_64 rng(42); + std::vector> adj = generate_random_tree(tree_size, rng); + adj = dfs_order(tree_size, adj); + std::vector bp = adj_to_bp(tree_size, adj); + BpTree bp_tree(bp, tree_size); + AdjListTree debug_tree(adj); + + std::queue> st; + + st.push({bp_tree.root(), debug_tree.root()}); + + while (!st.empty()) { + auto cur = st.front().first; + auto debug = st.front().second; + st.pop(); + EXPECT_EQ(bp_tree.parent(cur), debug_tree.parent(debug)); + + if (cur.number > 0) { + EXPECT_EQ(bp_tree.is_last_child(cur), debug_tree.is_last_child(debug)); + } + size_t deg = bp_tree.degree(cur); + EXPECT_EQ(deg, debug_tree.degree(debug)); + + if (deg == 0) { + continue; + } + auto child = bp_tree.first_child(cur); + auto debug_child = debug_tree.first_child(debug); + st.push({child, debug_child}); + for (size_t i = 1; i < deg; i++) { + child = bp_tree.next_sibling(child); + st.push({child, debug_tree.child(debug, i)}); + } + } + } +} + From 0b446605463f5d75ddb1642067f051d31ebd170b Mon Sep 17 00:00:00 2001 From: Sanya239 Date: Sun, 19 Apr 2026 19:28:59 +0300 Subject: [PATCH 04/13] fix kilo-code warnings --- include/pixie/bp_tree.h | 4 ++-- include/pixie/utils.h | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/pixie/bp_tree.h b/include/pixie/bp_tree.h index d73bc6c..854e200 100644 --- a/include/pixie/bp_tree.h +++ b/include/pixie/bp_tree.h @@ -40,7 +40,7 @@ class BpTree { /** * @brief Returns the size of the tree */ - size_t size() const { return num_bits_; } + size_t size() const { return num_bits_ / 2; } /** * @brief Indicates if @p node is a leaf @@ -65,7 +65,7 @@ class BpTree { return 0; } BpNode child = first_child(node); - int child_count = 1; + size_t child_count = 1; while (true) { if (is_last_child(child)) { return child_count; diff --git a/include/pixie/utils.h b/include/pixie/utils.h index 2c5f2f8..3dc5c4b 100644 --- a/include/pixie/utils.h +++ b/include/pixie/utils.h @@ -33,7 +33,7 @@ std::vector> bfs_order( std::queue> q; bfs_adj[0].push_back(0); q.push({0, 0}); - int cnt = 1; + size_t cnt = 1; while (!q.empty()) { size_t old_v = q.front().first; size_t cur_v = q.front().second; @@ -92,9 +92,9 @@ std::vector adj_to_bp(size_t tree_size, const std::vector>& adj) { size_t bp_size = tree_size * 2; std::vector bp((bp_size + 63) / 64, 0); - std::vector> stack; + std::vector> stack; stack.push_back(std::make_pair(0, 0)); - int pos = 0; + size_t pos = 0; bp[pos >> 6] = bp[pos >> 6] | (1ULL << (pos & 63)); while (!stack.empty()) { auto& [v, p] = stack.back(); From 8d3db20a299f273b4db8687b5ac499ca6814b059 Mon Sep 17 00:00:00 2001 From: Sanya239 Date: Fri, 15 May 2026 02:00:31 +0300 Subject: [PATCH 05/13] fix suggestions --- .github/workflows/build-test.yml | 16 ++++++++++ include/pixie/bp_tree.h | 50 +++++++++++++++++--------------- include/pixie/utils.h | 6 ++-- src/tests/bp_tree_tests.cpp | 8 ++--- 4 files changed, 49 insertions(+), 31 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 29903f7..84f4196 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -32,6 +32,10 @@ jobs: working-directory: ./build run: ./louds_tree_tests + - name: Run BP Tree Tests + working-directory: ./build + run: ./bp_tree_tests + - name: Run DFUDS Tree Tests working-directory: ./build run: ./dfuds_tree_tests @@ -96,6 +100,18 @@ jobs: fi exit $rc + - name: Run BP Tree Tests + working-directory: ./build + run: | + timeout 1800 sde-external-9.58.0-2025-06-16-lin/sde64 -icl -emu-xinuse 0 -- \ + ./bp_tree_tests --gtest_output=xml:bp_results.xml + rc=$? + if [ $rc -eq 124 ] && grep -q 'failures="0"' bp_results.xml 2>/dev/null; then + echo "SDE timed out during process teardown (known SDE/ASan issue) - all tests passed, treating as success" + exit 0 + fi + exit $rc + - name: Run DFUDS Tree Tests working-directory: ./build run: | diff --git a/include/pixie/bp_tree.h b/include/pixie/bp_tree.h index 854e200..cb53922 100644 --- a/include/pixie/bp_tree.h +++ b/include/pixie/bp_tree.h @@ -5,16 +5,6 @@ #include namespace pixie { -/** - * @brief A node class of BP tree - */ -struct BpNode { - size_t number; - size_t pos; - - BpNode(size_t node_number, size_t bp_pos) - : number(node_number), pos(bp_pos) {} -}; /** * @brief A tree class based on the balances parentheses (BP) @@ -26,6 +16,18 @@ class BpTree { RmMTree rmm; public: + + /** + * @brief A node class of BP tree + */ + struct Node { + size_t number; + size_t pos; + + Node(size_t node_number, size_t bp_pos) + : number(node_number), pos(bp_pos) {} + }; + /** * @brief Constructor from an external array of uint64_t */ @@ -35,7 +37,7 @@ class BpTree { /** * @brief Returns the root node */ - static BpNode root() { return BpNode(0, 0); } + Node root() const { return Node(0, 0); } /** * @brief Returns the size of the tree @@ -45,14 +47,14 @@ class BpTree { /** * @brief Indicates if @p node is a leaf */ - bool is_leaf(const BpNode& node) const { + bool is_leaf(const Node& node) const { return (node.pos + 1 == num_bits_) or rmm.bit(node.pos + 1) == 0; } /** * @brief Indicates if @p node is a root */ - static bool is_root(const BpNode& node) { return node.number == 0; } + bool is_root(const Node& node) { return node.number == 0; } /** * @brief Returns the number of children of a @p node @@ -60,11 +62,11 @@ class BpTree { * * TODO try make this faster */ - size_t degree(const BpNode& node) const { + size_t degree(const Node& node) const { if (is_leaf(node)) { return 0; } - BpNode child = first_child(node); + Node child = first_child(node); size_t child_count = 1; while (true) { if (is_last_child(child)) { @@ -78,10 +80,10 @@ class BpTree { /** * @brief Returns first child of a @p node */ - static BpNode first_child(const BpNode& node) { + Node first_child(const Node& node) const{ size_t pos = node.pos + 1; size_t num = node.number + 1; - return BpNode(num, pos); + return Node(num, pos); } /** @@ -91,8 +93,8 @@ class BpTree { * * TODO try make this faster */ - BpNode child(const BpNode& node, size_t i) const { - BpNode child = first_child(node); + Node child(const Node& node, size_t i) const { + Node child = first_child(node); while (i--) { child = next_sibling(child); } @@ -103,19 +105,19 @@ class BpTree { * @brief Returns the parent of a @p node if @p node is not root, * else returns root */ - BpNode parent(const BpNode& node) const { + Node parent(const Node& node) const { if (node.number == 0) { return root(); } size_t pos = rmm.enclose(node.pos + 1); size_t num = rmm.rank1(pos) - 1; - return BpNode(num, pos); + return Node(num, pos); } /** * @brief Indicates if @p node is last child */ - bool is_last_child(const BpNode& node) const { + bool is_last_child(const Node& node) const { size_t end = rmm.close(node.pos + 1); if (end + 1 >= num_bits_) { return true; @@ -126,10 +128,10 @@ class BpTree { /** * @brief Returns next sibling of a @p node */ - BpNode next_sibling(const BpNode& node) const { + Node next_sibling(const Node& node) const { size_t pos = rmm.close(node.pos + 1) + 1; size_t num = rmm.rank1(pos + 1) - 1; - return BpNode(num, pos); + return Node(num, pos); } }; } // namespace pixie diff --git a/include/pixie/utils.h b/include/pixie/utils.h index 3dc5c4b..8416dab 100644 --- a/include/pixie/utils.h +++ b/include/pixie/utils.h @@ -8,7 +8,7 @@ #include "bp_tree.h" -using pixie::BpNode; +using Node = pixie::BpTree::Node; using pixie::LoudsNode; std::vector> generate_random_tree(size_t tree_size, @@ -123,11 +123,11 @@ bool operator==(const LoudsNode& b, const AdjListNode& a) { return a.number == b.number; } -bool operator==(const AdjListNode& a, const BpNode& b) { +bool operator==(const AdjListNode& a, const Node& b) { return a.number == b.number; } -bool operator==(const BpNode& b, const AdjListNode& a) { +bool operator==(const Node& b, const AdjListNode& a) { return a.number == b.number; } diff --git a/src/tests/bp_tree_tests.cpp b/src/tests/bp_tree_tests.cpp index f262c5c..dd69baf 100644 --- a/src/tests/bp_tree_tests.cpp +++ b/src/tests/bp_tree_tests.cpp @@ -6,7 +6,7 @@ #include #include -using pixie::BpNode; +using Node = pixie::BpTree::Node; using pixie::BpTree; TEST(BpTreeTest, Basic) { @@ -18,7 +18,7 @@ TEST(BpTreeTest, Basic) { BpTree bp_tree(bp, 5); AdjListTree debug_tree(adj); - BpNode cur = bp_tree.root(); + Node cur = bp_tree.root(); AdjListNode debug = debug_tree.root(); for (size_t i = 0; i < tree_size - 1; i++) { EXPECT_EQ(cur, debug); @@ -37,7 +37,7 @@ TEST(BpTreeTest, RandomTreeDFS) { BpTree bp_tree(bp, tree_size); AdjListTree debug_tree(adj); - std::stack> st; + std::stack> st; st.push({bp_tree.root(), debug_tree.root()}); @@ -78,7 +78,7 @@ TEST(BpTreeTest, RandomTreeBFS) { BpTree bp_tree(bp, tree_size); AdjListTree debug_tree(adj); - std::queue> st; + std::queue> st; st.push({bp_tree.root(), debug_tree.root()}); From 315c191a4e2e906558fbc162ed7368226d0b3b22 Mon Sep 17 00:00:00 2001 From: Sanya239 Date: Sat, 16 May 2026 02:06:18 +0300 Subject: [PATCH 06/13] add bp to coverage_report.sh --- scripts/coverage_report.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/coverage_report.sh b/scripts/coverage_report.sh index 84bec46..8b85ce8 100755 --- a/scripts/coverage_report.sh +++ b/scripts/coverage_report.sh @@ -10,6 +10,7 @@ cmake --build --preset coverage "${BUILD_DIR}/unittests" "${BUILD_DIR}/excess_positions_tests" "${BUILD_DIR}/louds_tree_tests" +"${BUILD_DIR}/bp_tree_tests" "${BUILD_DIR}/dfuds_tree_tests" "${BUILD_DIR}/test_rmm" From 5a7036dc963722b333a1bd8dd332e64e5cd8091a Mon Sep 17 00:00:00 2001 From: Sanya239 Date: Sat, 16 May 2026 02:10:13 +0300 Subject: [PATCH 07/13] fix clang format --- include/pixie/bp_tree.h | 7 +++---- src/tests/bp_tree_tests.cpp | 2 -- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/include/pixie/bp_tree.h b/include/pixie/bp_tree.h index cb53922..4b67de6 100644 --- a/include/pixie/bp_tree.h +++ b/include/pixie/bp_tree.h @@ -16,10 +16,9 @@ class BpTree { RmMTree rmm; public: - /** - * @brief A node class of BP tree - */ + * @brief A node class of BP tree + */ struct Node { size_t number; size_t pos; @@ -80,7 +79,7 @@ class BpTree { /** * @brief Returns first child of a @p node */ - Node first_child(const Node& node) const{ + Node first_child(const Node& node) const { size_t pos = node.pos + 1; size_t num = node.number + 1; return Node(num, pos); diff --git a/src/tests/bp_tree_tests.cpp b/src/tests/bp_tree_tests.cpp index dd69baf..2f80342 100644 --- a/src/tests/bp_tree_tests.cpp +++ b/src/tests/bp_tree_tests.cpp @@ -68,7 +68,6 @@ TEST(BpTreeTest, RandomTreeDFS) { } } - TEST(BpTreeTest, RandomTreeBFS) { for (size_t tree_size = 8; tree_size < (1 << 22); tree_size <<= 1) { std::mt19937_64 rng(42); @@ -107,4 +106,3 @@ TEST(BpTreeTest, RandomTreeBFS) { } } } - From 1ccd405692d4b34fd8cd46f99ebdfa2ea64c37c6 Mon Sep 17 00:00:00 2001 From: Sanya239 Date: Wed, 27 May 2026 17:27:56 +0300 Subject: [PATCH 08/13] add benchmarks --- CMakeLists.txt | 9 ++++ src/benchmarks/bp_tree_benchmarks.cpp | 66 +++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 src/benchmarks/bp_tree_benchmarks.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ad7c17..24971d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -248,6 +248,15 @@ if(PIXIE_BENCHMARKS) benchmark_main ${PIXIE_DIAGNOSTICS_LIBS}) + add_executable(bp_tree_benchmarks + src/benchmarks/bp_tree_benchmarks.cpp) + target_include_directories(bp_tree_benchmarks + PUBLIC include) + target_link_libraries(bp_tree_benchmarks + benchmark + benchmark_main + ${PIXIE_DIAGNOSTICS_LIBS}) + add_executable(dfuds_tree_benchmarks src/benchmarks/dfuds_tree_benchmarks.cpp) target_include_directories(dfuds_tree_benchmarks diff --git a/src/benchmarks/bp_tree_benchmarks.cpp b/src/benchmarks/bp_tree_benchmarks.cpp new file mode 100644 index 0000000..d0d561a --- /dev/null +++ b/src/benchmarks/bp_tree_benchmarks.cpp @@ -0,0 +1,66 @@ +#include +#include +#include + +#include + +using Node = pixie::BpTree::Node; +using pixie::BpTree; + +/** + * DFS with O(1) extra memory + */ +static void BM_BpTreeDFS(benchmark::State& state) { + size_t tree_size = state.range(0); + std::mt19937_64 rng(42); + + for (auto _ : state) { + state.PauseTiming(); + std::vector> adj = generate_random_tree(tree_size, rng); + adj = dfs_order(tree_size, adj); + std::vector bp = adj_to_bp(tree_size, adj); + BpTree tree(bp, tree_size); + + Node cur = tree.root(); + bool above = 1; + + state.ResumeTiming(); + + benchmark::DoNotOptimize(cur); + + while (true) { + if (above) { + if (tree.is_leaf(cur)) { + above = 0; + } else { + cur = tree.first_child(cur); + } + benchmark::DoNotOptimize(cur); + } else { + if (tree.is_last_child(cur)) { + cur = tree.parent(cur); + if (tree.is_root(cur)) { + break; + } + benchmark::DoNotOptimize(cur); + } else { + cur = tree.next_sibling(cur); + above = 1; + benchmark::DoNotOptimize(cur); + } + } + } + } +} + +BENCHMARK(BM_BpTreeDFS) + ->ArgNames({"tree_size"}) + ->RangeMultiplier(2) + ->Range(1ull << 8, 1ull << 18) + ->Iterations(100); + +BENCHMARK(BM_BpTreeDFS) + ->ArgNames({"tree_size"}) + ->RangeMultiplier(2) + ->Range(1ull << 18, 1ull << 26) + ->Iterations(10); \ No newline at end of file From e78d7ce5e94fa221f1fac0da3dfd557f9b63df90 Mon Sep 17 00:00:00 2001 From: Sanya239 Date: Tue, 2 Jun 2026 13:34:40 +0300 Subject: [PATCH 09/13] huge refactoring --- include/pixie/bp_tree.h | 41 +++++++++++++++++++++++++++++++++++-- include/pixie/utils.h | 34 ------------------------------ src/tests/bp_tree_tests.cpp | 13 +++++++----- 3 files changed, 47 insertions(+), 41 deletions(-) diff --git a/include/pixie/bp_tree.h b/include/pixie/bp_tree.h index 4b67de6..82e5f70 100644 --- a/include/pixie/bp_tree.h +++ b/include/pixie/bp_tree.h @@ -4,13 +4,15 @@ #include +#include "utils.h" + namespace pixie { /** * @brief A tree class based on the balances parentheses (BP) * representation */ -class BpTree { +class BPTree { private: const size_t num_bits_; RmMTree rmm; @@ -23,6 +25,9 @@ class BpTree { size_t number; size_t pos; + /** + * @brief A node class of BP tree + */ Node(size_t node_number, size_t bp_pos) : number(node_number), pos(bp_pos) {} }; @@ -30,7 +35,7 @@ class BpTree { /** * @brief Constructor from an external array of uint64_t */ - explicit BpTree(const std::vector& words, size_t tree_size) + explicit BPTree(const std::vector& words, size_t tree_size) : num_bits_(2 * tree_size), rmm(words, 2 * tree_size) {} /** @@ -133,4 +138,36 @@ class BpTree { return Node(num, pos); } }; + +std::vector adj_to_bp(size_t tree_size, + const std::vector>& adj) { + size_t bp_size = tree_size * 2; + std::vector bp((bp_size + 63) / 64, 0); + std::vector> stack; + stack.push_back(std::make_pair(0, 0)); + size_t pos = 0; + bp[pos >> 6] = bp[pos >> 6] | (1ULL << (pos & 63)); + while (!stack.empty()) { + auto& [v, p] = stack.back(); + p++; + if (p >= adj[v].size()) { + pos++; + stack.pop_back(); + continue; + } + pos++; + bp[pos >> 6] = bp[pos >> 6] | (1ULL << (pos & 63)); + stack.push_back(std::make_pair(adj[v][p], 0)); + } + return bp; +} + +bool operator==(const AdjListNode& a, const BPTree::Node& b) { + return a.number == b.number; +} + +bool operator==(const BPTree::Node& b, const AdjListNode& a) { + return a.number == b.number; +} + } // namespace pixie diff --git a/include/pixie/utils.h b/include/pixie/utils.h index 8416dab..fd08631 100644 --- a/include/pixie/utils.h +++ b/include/pixie/utils.h @@ -6,9 +6,6 @@ #include #include -#include "bp_tree.h" - -using Node = pixie::BpTree::Node; using pixie::LoudsNode; std::vector> generate_random_tree(size_t tree_size, @@ -88,29 +85,6 @@ std::vector adj_to_louds( return louds; } -std::vector adj_to_bp(size_t tree_size, - const std::vector>& adj) { - size_t bp_size = tree_size * 2; - std::vector bp((bp_size + 63) / 64, 0); - std::vector> stack; - stack.push_back(std::make_pair(0, 0)); - size_t pos = 0; - bp[pos >> 6] = bp[pos >> 6] | (1ULL << (pos & 63)); - while (!stack.empty()) { - auto& [v, p] = stack.back(); - p++; - if (p >= adj[v].size()) { - pos++; - stack.pop_back(); - continue; - } - pos++; - bp[pos >> 6] = bp[pos >> 6] | (1ULL << (pos & 63)); - stack.push_back(std::make_pair(adj[v][p], 0)); - } - return bp; -} - struct AdjListNode { size_t number; }; @@ -123,14 +97,6 @@ bool operator==(const LoudsNode& b, const AdjListNode& a) { return a.number == b.number; } -bool operator==(const AdjListNode& a, const Node& b) { - return a.number == b.number; -} - -bool operator==(const Node& b, const AdjListNode& a) { - return a.number == b.number; -} - class AdjListTree { private: std::vector> adj; diff --git a/src/tests/bp_tree_tests.cpp b/src/tests/bp_tree_tests.cpp index 2f80342..5c4b15c 100644 --- a/src/tests/bp_tree_tests.cpp +++ b/src/tests/bp_tree_tests.cpp @@ -6,8 +6,9 @@ #include #include -using Node = pixie::BpTree::Node; -using pixie::BpTree; +using Node = pixie::BPTree::Node; +using pixie::adj_to_bp; +using pixie::BPTree; TEST(BpTreeTest, Basic) { std::vector> adj = {{0, 1}, {0, 2}, {1, 3}, {2, 4}, {3}}; @@ -15,7 +16,7 @@ TEST(BpTreeTest, Basic) { std::vector bp = adj_to_bp(tree_size, adj); - BpTree bp_tree(bp, 5); + BPTree bp_tree(bp, 5); AdjListTree debug_tree(adj); Node cur = bp_tree.root(); @@ -34,7 +35,7 @@ TEST(BpTreeTest, RandomTreeDFS) { std::vector> adj = generate_random_tree(tree_size, rng); adj = dfs_order(tree_size, adj); std::vector bp = adj_to_bp(tree_size, adj); - BpTree bp_tree(bp, tree_size); + BPTree bp_tree(bp, tree_size); AdjListTree debug_tree(adj); std::stack> st; @@ -53,6 +54,7 @@ TEST(BpTreeTest, RandomTreeDFS) { } size_t deg = bp_tree.degree(cur); EXPECT_EQ(deg, debug_tree.degree(debug)); + EXPECT_EQ(bp_tree.is_leaf(cur), debug_tree.is_leaf(debug)); if (deg == 0) { continue; @@ -74,7 +76,7 @@ TEST(BpTreeTest, RandomTreeBFS) { std::vector> adj = generate_random_tree(tree_size, rng); adj = dfs_order(tree_size, adj); std::vector bp = adj_to_bp(tree_size, adj); - BpTree bp_tree(bp, tree_size); + BPTree bp_tree(bp, tree_size); AdjListTree debug_tree(adj); std::queue> st; @@ -92,6 +94,7 @@ TEST(BpTreeTest, RandomTreeBFS) { } size_t deg = bp_tree.degree(cur); EXPECT_EQ(deg, debug_tree.degree(debug)); + EXPECT_EQ(bp_tree.is_leaf(cur), debug_tree.is_leaf(debug)); if (deg == 0) { continue; From 5a19e4e67dc30549f9bdd1e9fc63adef881c8de4 Mon Sep 17 00:00:00 2001 From: Sanya239 Date: Fri, 5 Jun 2026 01:44:11 +0300 Subject: [PATCH 10/13] adapt bp_tree to new rmm specifics --- include/pixie/bp_tree.h | 10 +++++----- src/benchmarks/bp_tree_benchmarks.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/pixie/bp_tree.h b/include/pixie/bp_tree.h index 82e5f70..6f81841 100644 --- a/include/pixie/bp_tree.h +++ b/include/pixie/bp_tree.h @@ -113,8 +113,8 @@ class BPTree { if (node.number == 0) { return root(); } - size_t pos = rmm.enclose(node.pos + 1); - size_t num = rmm.rank1(pos) - 1; + size_t pos = rmm.enclose(node.pos); + size_t num = rmm.rank1(pos); return Node(num, pos); } @@ -122,8 +122,8 @@ class BPTree { * @brief Indicates if @p node is last child */ bool is_last_child(const Node& node) const { - size_t end = rmm.close(node.pos + 1); - if (end + 1 >= num_bits_) { + size_t end = rmm.close(node.pos); + if (end + 2 >= num_bits_) { return true; } return rmm.bit(end + 1) == 0; @@ -133,7 +133,7 @@ class BPTree { * @brief Returns next sibling of a @p node */ Node next_sibling(const Node& node) const { - size_t pos = rmm.close(node.pos + 1) + 1; + size_t pos = rmm.close(node.pos) + 1; size_t num = rmm.rank1(pos + 1) - 1; return Node(num, pos); } diff --git a/src/benchmarks/bp_tree_benchmarks.cpp b/src/benchmarks/bp_tree_benchmarks.cpp index d0d561a..164283e 100644 --- a/src/benchmarks/bp_tree_benchmarks.cpp +++ b/src/benchmarks/bp_tree_benchmarks.cpp @@ -63,4 +63,4 @@ BENCHMARK(BM_BpTreeDFS) ->ArgNames({"tree_size"}) ->RangeMultiplier(2) ->Range(1ull << 18, 1ull << 26) - ->Iterations(10); \ No newline at end of file + ->Iterations(10); From 56ca74b38be01c17897f2721f10437a83f5db646 Mon Sep 17 00:00:00 2001 From: Sanya239 Date: Fri, 5 Jun 2026 01:59:19 +0300 Subject: [PATCH 11/13] add bp_tree to CMakePresets.json --- CMakePresets.json | 7 +++++++ include/pixie/bp_tree.h | 8 +++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 5da7763..b8bbc58 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -114,6 +114,7 @@ "benchmark_tests", "test_rmm", "louds_tree_tests", + "bp_tree_tests", "dfuds_tree_tests", "excess_positions_tests" ] @@ -127,6 +128,7 @@ "benchmark_tests", "test_rmm", "louds_tree_tests", + "bp_tree_tests", "dfuds_tree_tests", "excess_positions_tests" ] @@ -139,6 +141,7 @@ "benchmarks", "bench_rmm", "louds_tree_benchmarks", + "bp_tree_benchmarks", "dfuds_tree_benchmarks", "alignment_comparison", "excess_positions_benchmarks" @@ -153,6 +156,7 @@ "bench_rmm", "bench_rmm_sdsl", "louds_tree_benchmarks", + "bp_tree_benchmarks", "dfuds_tree_benchmarks", "alignment_comparison", "excess_positions_benchmarks" @@ -166,6 +170,7 @@ "benchmarks", "bench_rmm", "louds_tree_benchmarks", + "bp_tree_benchmarks", "dfuds_tree_benchmarks", "alignment_comparison", "excess_positions_benchmarks" @@ -188,6 +193,7 @@ "benchmark_tests", "test_rmm", "louds_tree_tests", + "bp_tree_tests", "dfuds_tree_tests", "excess_positions_tests" ] @@ -201,6 +207,7 @@ "benchmark_tests", "test_rmm", "louds_tree_tests", + "bp_tree_tests", "dfuds_tree_tests", "excess_positions_tests" ] diff --git a/include/pixie/bp_tree.h b/include/pixie/bp_tree.h index 6f81841..54f4957 100644 --- a/include/pixie/bp_tree.h +++ b/include/pixie/bp_tree.h @@ -52,7 +52,7 @@ class BPTree { * @brief Indicates if @p node is a leaf */ bool is_leaf(const Node& node) const { - return (node.pos + 1 == num_bits_) or rmm.bit(node.pos + 1) == 0; + return (node.pos + 2 == num_bits_) or rmm.bit(node.pos + 1) == 0; } /** @@ -123,10 +123,8 @@ class BPTree { */ bool is_last_child(const Node& node) const { size_t end = rmm.close(node.pos); - if (end + 2 >= num_bits_) { - return true; - } - return rmm.bit(end + 1) == 0; + + return end + 2 >= num_bits_ or rmm.bit(end + 1) == 0; } /** From 3f9033333ee21f69015f65b17b3efe65b4ebc2ae Mon Sep 17 00:00:00 2001 From: Sanya239 Date: Mon, 8 Jun 2026 15:20:59 +0300 Subject: [PATCH 12/13] Make BPTree template-based --- include/pixie/bp_tree.h | 26 +++++++++++++------------- src/benchmarks/bp_tree_benchmarks.cpp | 8 ++++---- src/tests/bp_tree_tests.cpp | 4 ++-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/include/pixie/bp_tree.h b/include/pixie/bp_tree.h index 54f4957..1636cc8 100644 --- a/include/pixie/bp_tree.h +++ b/include/pixie/bp_tree.h @@ -2,8 +2,6 @@ #include -#include - #include "utils.h" namespace pixie { @@ -12,10 +10,11 @@ namespace pixie { * @brief A tree class based on the balances parentheses (BP) * representation */ +template class BPTree { private: const size_t num_bits_; - RmMTree rmm; + RMMTree rmm_; public: /** @@ -36,7 +35,7 @@ class BPTree { * @brief Constructor from an external array of uint64_t */ explicit BPTree(const std::vector& words, size_t tree_size) - : num_bits_(2 * tree_size), rmm(words, 2 * tree_size) {} + : num_bits_(2 * tree_size), rmm_(words, 2 * tree_size) {} /** * @brief Returns the root node @@ -52,7 +51,7 @@ class BPTree { * @brief Indicates if @p node is a leaf */ bool is_leaf(const Node& node) const { - return (node.pos + 2 == num_bits_) or rmm.bit(node.pos + 1) == 0; + return (node.pos + 2 == num_bits_) or rmm_.bit(node.pos + 1) == 0; } /** @@ -113,8 +112,8 @@ class BPTree { if (node.number == 0) { return root(); } - size_t pos = rmm.enclose(node.pos); - size_t num = rmm.rank1(pos); + size_t pos = rmm_.enclose(node.pos); + size_t num = rmm_.rank1(pos); return Node(num, pos); } @@ -122,17 +121,17 @@ class BPTree { * @brief Indicates if @p node is last child */ bool is_last_child(const Node& node) const { - size_t end = rmm.close(node.pos); + size_t end = rmm_.close(node.pos); - return end + 2 >= num_bits_ or rmm.bit(end + 1) == 0; + return end + 2 >= num_bits_ or rmm_.bit(end + 1) == 0; } /** * @brief Returns next sibling of a @p node */ Node next_sibling(const Node& node) const { - size_t pos = rmm.close(node.pos) + 1; - size_t num = rmm.rank1(pos + 1) - 1; + size_t pos = rmm_.close(node.pos) + 1; + size_t num = rmm_.rank1(pos + 1) - 1; return Node(num, pos); } }; @@ -160,11 +159,12 @@ std::vector adj_to_bp(size_t tree_size, return bp; } -bool operator==(const AdjListNode& a, const BPTree::Node& b) { +bool operator==(const AdjListNode& a, const typename BPTree::Node b) { return a.number == b.number; } -bool operator==(const BPTree::Node& b, const AdjListNode& a) { +template +bool operator==(const typename BPTree::Node b, const AdjListNode& a) { return a.number == b.number; } diff --git a/src/benchmarks/bp_tree_benchmarks.cpp b/src/benchmarks/bp_tree_benchmarks.cpp index 164283e..a2e3e02 100644 --- a/src/benchmarks/bp_tree_benchmarks.cpp +++ b/src/benchmarks/bp_tree_benchmarks.cpp @@ -4,8 +4,8 @@ #include -using Node = pixie::BpTree::Node; -using pixie::BpTree; +using BPTree = pixie::BPTree; +using Node = BPTree::Node; /** * DFS with O(1) extra memory @@ -18,8 +18,8 @@ static void BM_BpTreeDFS(benchmark::State& state) { state.PauseTiming(); std::vector> adj = generate_random_tree(tree_size, rng); adj = dfs_order(tree_size, adj); - std::vector bp = adj_to_bp(tree_size, adj); - BpTree tree(bp, tree_size); + std::vector bp = pixie::adj_to_bp(tree_size, adj); + BPTree tree(bp, tree_size); Node cur = tree.root(); bool above = 1; diff --git a/src/tests/bp_tree_tests.cpp b/src/tests/bp_tree_tests.cpp index 5c4b15c..afa5e16 100644 --- a/src/tests/bp_tree_tests.cpp +++ b/src/tests/bp_tree_tests.cpp @@ -6,9 +6,9 @@ #include #include -using Node = pixie::BPTree::Node; +using BPTree = pixie::BPTree; +using Node = BPTree::Node; using pixie::adj_to_bp; -using pixie::BPTree; TEST(BpTreeTest, Basic) { std::vector> adj = {{0, 1}, {0, 2}, {1, 3}, {2, 4}, {3}}; From aa824edfd96f88e7d71287d656b9c3086ae1e4be Mon Sep 17 00:00:00 2001 From: Sanya239 Date: Mon, 8 Jun 2026 16:30:56 +0300 Subject: [PATCH 13/13] Support SdslRmMTree in BP benchmarks --- CMakeLists.txt | 4 ++++ CMakePresets.json | 3 ++- include/pixie/bp_tree.h | 10 ---------- src/benchmarks/bp_tree_benchmarks.cpp | 7 +++++++ src/tests/bp_tree_tests.cpp | 8 ++++++++ 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 24971d9..f2d10b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -256,6 +256,10 @@ if(PIXIE_BENCHMARKS) benchmark benchmark_main ${PIXIE_DIAGNOSTICS_LIBS}) + if(PIXIE_THIRD_PARTY_BACKENDS) + target_include_directories(bp_tree_benchmarks + PRIVATE ${sdsl_lite_SOURCE_DIR}/include) + endif() add_executable(dfuds_tree_benchmarks src/benchmarks/dfuds_tree_benchmarks.cpp) diff --git a/CMakePresets.json b/CMakePresets.json index b8bbc58..f6ceafc 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -43,7 +43,8 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", "PIXIE_TESTS": "OFF", - "PIXIE_BENCHMARKS": "ON" + "PIXIE_BENCHMARKS": "ON", + "PIXIE_THIRD_PARTY_BACKENDS": "OFF" } }, { diff --git a/include/pixie/bp_tree.h b/include/pixie/bp_tree.h index 1636cc8..f723b2c 100644 --- a/include/pixie/bp_tree.h +++ b/include/pixie/bp_tree.h @@ -158,14 +158,4 @@ std::vector adj_to_bp(size_t tree_size, } return bp; } - -bool operator==(const AdjListNode& a, const typename BPTree::Node b) { - return a.number == b.number; -} - -template -bool operator==(const typename BPTree::Node b, const AdjListNode& a) { - return a.number == b.number; -} - } // namespace pixie diff --git a/src/benchmarks/bp_tree_benchmarks.cpp b/src/benchmarks/bp_tree_benchmarks.cpp index a2e3e02..d390a7e 100644 --- a/src/benchmarks/bp_tree_benchmarks.cpp +++ b/src/benchmarks/bp_tree_benchmarks.cpp @@ -4,7 +4,14 @@ #include +#ifdef SDSL_SUPPORT +#pragma message("SDSL_SUPPORT enabled") +#include "pixie/rmm_tree_sdsl.h" +using BPTree = pixie::BPTree; +#else +#pragma message("SDSL_SUPPORT disabled") using BPTree = pixie::BPTree; +#endif using Node = BPTree::Node; /** diff --git a/src/tests/bp_tree_tests.cpp b/src/tests/bp_tree_tests.cpp index afa5e16..8ad1d1f 100644 --- a/src/tests/bp_tree_tests.cpp +++ b/src/tests/bp_tree_tests.cpp @@ -10,6 +10,14 @@ using BPTree = pixie::BPTree; using Node = BPTree::Node; using pixie::adj_to_bp; +bool operator==(const AdjListNode& a, const Node& b) { + return a.number == b.number; +} + +bool operator==(Node& b, const AdjListNode& a) { + return a.number == b.number; +} + TEST(BpTreeTest, Basic) { std::vector> adj = {{0, 1}, {0, 2}, {1, 3}, {2, 4}, {3}}; size_t tree_size = 5;