Skip to content

Commit c447002

Browse files
committed
add dfuds implementation and tests
1 parent a553366 commit c447002

7 files changed

Lines changed: 322 additions & 7 deletions

File tree

.github/workflows/build-test.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ jobs:
3232
working-directory: ./build
3333
run: ./louds_tree_tests
3434

35+
- name: Run DFUDS Tree Tests
36+
working-directory: ./build
37+
run: ./dfuds_tree_tests
38+
3539
- name: Run Benchmark Tests
3640
working-directory: ./build
3741
run: ./benchmark_tests
@@ -92,3 +96,15 @@ jobs:
9296
fi
9397
exit $rc
9498
99+
- name: Run DFUDS Tree Tests
100+
working-directory: ./build
101+
run: |
102+
timeout 1800 sde-external-9.58.0-2025-06-16-lin/sde64 -icl -emu-xinuse 0 -- \
103+
./dfuds_tree_tests --gtest_output=xml:dfuds_results.xml
104+
rc=$?
105+
if [ $rc -eq 124 ] && grep -q 'failures="0"' dfuds_results.xml 2>/dev/null; then
106+
echo "SDE timed out during process teardown (known SDE/ASan issue) - all tests passed, treating as success"
107+
exit 0
108+
fi
109+
exit $rc
110+

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,15 @@ if(PIXIE_TESTS)
157157
gtest
158158
gtest_main
159159
${PIXIE_DIAGNOSTICS_LIBS})
160+
161+
add_executable(dfuds_tree_tests
162+
src/tests/dfuds_tree_tests.cpp)
163+
target_include_directories(dfuds_tree_tests
164+
PUBLIC include)
165+
target_link_libraries(dfuds_tree_tests
166+
gtest
167+
gtest_main
168+
${PIXIE_DIAGNOSTICS_LIBS})
160169
endif()
161170

162171
# ---------------------------------------------------------------------------

include/pixie/dfuds_tree.h

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#pragma once
2+
3+
#include <pixie/rmm_tree.h>
4+
5+
#include <cstdint>
6+
7+
namespace pixie {
8+
9+
/**
10+
* @brief A tree class based on the depth-first unary degree sequence (DFUDS)
11+
* representation
12+
*/
13+
class DFUDSTree {
14+
private:
15+
const size_t num_bits_;
16+
RmMTree rmm_;
17+
18+
public:
19+
20+
struct Node {
21+
size_t number;
22+
23+
size_t pos;
24+
25+
/**
26+
* @brief A node class of DFUDS tree
27+
*/
28+
Node(size_t node_number, size_t dfuds_pos)
29+
: number(node_number), pos(dfuds_pos) {}
30+
};
31+
/**
32+
* @brief Constructor from an external array of uint64_t
33+
*/
34+
explicit DFUDSTree(const std::vector<std::uint64_t>& words, size_t tree_size)
35+
: num_bits_(2 * tree_size - 1), rmm_(words, 2 * tree_size - 1) {}
36+
37+
/**
38+
* @brief Returns the root node
39+
*/
40+
static Node root() { return Node(0, 0); }
41+
42+
/**
43+
* @brief Returns the size of the tree
44+
*/
45+
size_t size() const { return num_bits_ / 2; }
46+
47+
/**
48+
* @brief Indicates if @p node is a leaf
49+
*/
50+
bool is_leaf(const Node& node) const {
51+
return (node.pos + 1 == num_bits_) or rmm_.bit(node.pos) == 0;
52+
}
53+
54+
/**
55+
* @brief Indicates if @p node is a root
56+
*/
57+
bool is_root(const Node& node) const { return node.number == 0; }
58+
59+
/**
60+
* @brief Returns the number of children of a @p node
61+
*/
62+
size_t degree(const Node& node) const {
63+
return rmm_.select0(node.number + 1) - node.pos;
64+
}
65+
66+
/**
67+
* @brief Returns first child of a @p node
68+
*/
69+
Node first_child(const Node& node) {
70+
size_t pos = rmm_.select0(node.number + 1);
71+
size_t num = node.number + 1;
72+
return Node(num, pos + 1);
73+
}
74+
75+
/**
76+
* @brief Returns the i-th child of @p node
77+
* Indexing starts at 0
78+
*/
79+
Node child(const Node& node, size_t i) const {
80+
size_t pos = rmm_.close(rmm_.select0(node.number + 1) - i) + 1;
81+
size_t num = rmm_.rank0(pos);
82+
return Node(num, pos);
83+
}
84+
85+
/**
86+
* @brief Returns next sibling of a @p node
87+
*/
88+
Node next_sibling(const Node& node) const {
89+
size_t end = rmm_.fwdsearch(node.pos, -1);
90+
size_t pos = end + 1;
91+
size_t num = rmm_.rank0(pos);
92+
return Node(num, pos);
93+
}
94+
95+
/**
96+
* @brief Returns the parent of a @p node if @p node is not root,
97+
* else returns root
98+
*/
99+
Node parent(const Node& node) const {
100+
if (node.number == 0) {
101+
return root();
102+
}
103+
size_t open = rmm_.open(node.pos);
104+
size_t rank = rmm_.rank0(open);
105+
size_t pos = rmm_.select0(rank) + 1; // some overflow-related magic here
106+
size_t num = rmm_.rank0(pos);
107+
return Node(num, pos);
108+
}
109+
110+
/**
111+
* @brief Indicates if @p node is last child
112+
*/
113+
bool is_last_child(const Node& node) const {
114+
size_t end = rmm_.fwdsearch(node.pos, -1);
115+
size_t pos = end + 1;
116+
size_t op = rmm_.open(node.pos);
117+
size_t op2 = rmm_.open(pos);
118+
return pos == num_bits_ || op != op2 + 1;
119+
}
120+
};
121+
} // namespace pixie

include/pixie/rmm_tree.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,13 @@ class RmMTree {
966966
return (result == npos ? npos : result + 1);
967967
}
968968

969+
/**
970+
* @brief Read bit at position @p position (LSB-first across words).
971+
*/
972+
inline int bit(const size_t& position) const noexcept {
973+
return (bits[position >> 6] >> (position & 63)) & 1u;
974+
}
975+
969976
private:
970977
/**
971978
* @brief Count "10" occurrences inside a 64-bit slice of given logical
@@ -2215,13 +2222,6 @@ class RmMTree {
22152222
build(leaf_block_bits, max_overhead);
22162223
}
22172224

2218-
/**
2219-
* @brief Read bit at position @p position (LSB-first across words).
2220-
*/
2221-
inline int bit(const size_t& position) const noexcept {
2222-
return (bits[position >> 6] >> (position & 63)) & 1u;
2223-
}
2224-
22252225
/**
22262226
* @brief Set bit at position @p position to 1.
22272227
*/

include/pixie/utils.h

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <pixie/dfuds_tree.h>
34
#include <pixie/louds_tree.h>
45

56
#include <queue>
@@ -8,6 +9,8 @@
89

910
using pixie::LoudsNode;
1011

12+
using Node = pixie::DFUDSTree::Node;
13+
1114
std::vector<std::vector<size_t>> generate_random_tree(size_t tree_size,
1215
std::mt19937_64& rng) {
1316
if (tree_size == 0) {
@@ -46,6 +49,32 @@ std::vector<std::vector<size_t>> bfs_order(
4649
return bfs_adj;
4750
}
4851

52+
std::vector<std::vector<size_t>> dfs_order(
53+
size_t tree_size,
54+
const std::vector<std::vector<size_t>>& adj) {
55+
std::vector<std::vector<size_t>> dfs_adj(tree_size);
56+
std::vector<std::pair<size_t, size_t>> stack;
57+
dfs_adj[0].push_back(0);
58+
stack.push_back({0, 0});
59+
std::vector<size_t> renumbering(tree_size, 0);
60+
size_t next_number = 1;
61+
while (!stack.empty()) {
62+
auto& [v, i] = stack.back();
63+
i++;
64+
if (i == adj[v].size()) {
65+
stack.pop_back();
66+
continue;
67+
}
68+
size_t u = adj[v][i];
69+
renumbering[u] = next_number++;
70+
dfs_adj[renumbering[v]].push_back(renumbering[u]);
71+
dfs_adj[renumbering[u]].push_back(renumbering[v]);
72+
73+
stack.push_back(std::pair{u, 0});
74+
}
75+
return dfs_adj;
76+
}
77+
4978
std::vector<uint64_t> adj_to_louds(
5079
size_t tree_size,
5180
const std::vector<std::vector<size_t>>& adj) {
@@ -59,6 +88,28 @@ std::vector<uint64_t> adj_to_louds(
5988
return louds;
6089
}
6190

91+
std::vector<uint64_t> adj_to_dfuds(
92+
size_t tree_size,
93+
const std::vector<std::vector<size_t>>& adj) {
94+
size_t dfuds_size = tree_size * 2 - 1;
95+
std::vector<uint64_t> dfuds((dfuds_size + 63) / 64, 0);
96+
std::vector<size_t> stack;
97+
stack.push_back(0);
98+
size_t pos = 0;
99+
while (!stack.empty()) {
100+
auto v = stack.back();
101+
stack.pop_back();
102+
size_t edge_count = adj[v].size();
103+
for (size_t i = 0; i < edge_count - 1; ++i) { // edge 0 goes to parent
104+
dfuds[pos >> 6] = dfuds[pos >> 6] | (1ULL << (pos & 63));
105+
pos++;
106+
stack.push_back(adj[v][edge_count - 1 - i]);
107+
}
108+
pos++;
109+
}
110+
return dfuds;
111+
}
112+
62113
struct AdjListNode {
63114
size_t number;
64115
};
@@ -71,6 +122,14 @@ bool operator==(const LoudsNode& b, const AdjListNode& a) {
71122
return a.number == b.number;
72123
}
73124

125+
bool operator==(const AdjListNode& a, const Node& b) {
126+
return a.number == b.number;
127+
}
128+
129+
bool operator==(const Node& b, const AdjListNode& a) {
130+
return a.number == b.number;
131+
}
132+
74133
class AdjListTree {
75134
private:
76135
std::vector<std::vector<size_t>> adj;

scripts/coverage_report.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ cmake --build --preset coverage
1010
"${BUILD_DIR}/unittests"
1111
"${BUILD_DIR}/excess_positions_tests"
1212
"${BUILD_DIR}/louds_tree_tests"
13+
"${BUILD_DIR}/dfuds_tree_tests"
1314
"${BUILD_DIR}/test_rmm"
1415

1516
cd "${BUILD_DIR}"

0 commit comments

Comments
 (0)