Pixie
Loading...
Searching...
No Matches
index.h
1#pragma once
2
4#include <pixie/detail/serialization.h>
5#include <pixie/packed_bit_builder.h>
6#include <pixie/rank_select/support.h>
10
11#include <algorithm>
12#include <array>
13#include <cstddef>
14#include <cstdint>
15#include <functional>
16#include <limits>
17#include <numeric>
18#include <queue>
19#include <span>
20#include <stdexcept>
21#include <type_traits>
22#include <vector>
23
24namespace pixie {
25
32template <WaveletTreeSymbol Symbol,
34class WaveletTreeIndex
35 : public WaveletTreeBase<WaveletTreeIndex<Symbol, Storage>, Symbol>,
36 public SerializationBase<WaveletTreeIndex<Symbol, Storage>> {
37 private:
38 using node_index_t = size_t;
39 static constexpr node_index_t npos = std::numeric_limits<node_index_t>::max();
40 static constexpr std::array<std::uint8_t, 8> kSerializationMagic = {
41 'P', 'X', 'W', 'A', 'V', 'E', 'T', '\0'};
42 static constexpr std::uint32_t kSerializationVersion = 5;
43 static constexpr std::size_t kSerializationHeaderBytes = 24;
44#if defined(__APPLE__) && defined(__aarch64__)
45 static constexpr std::size_t kByteConstructionBlockSize = 16 * 1024;
46#else
47 static constexpr std::size_t kByteConstructionBlockSize = 32 * 1024;
48#endif
49
50 struct PreWaveletNode {
51 node_index_t parent = npos;
52 node_index_t left_child = npos;
53 node_index_t right_child = npos;
54 std::size_t middle;
55 std::size_t left_size = 0;
56 PackedBitBuilder stream;
57 explicit PreWaveletNode(std::size_t middle) : middle(middle) {}
58 };
59
68 struct WaveletNode {
69 node_index_t parent, left_child, right_child;
70 std::size_t middle;
71 Storage bit_vector_data;
73
74 WaveletNode() = default;
75
76 WaveletNode(const WaveletNode& node)
77 : parent(node.parent),
78 left_child(node.left_child),
79 right_child(node.right_child),
80 middle(node.middle),
81 bit_vector_data(node.bit_vector_data),
82 data([&] {
83 if constexpr (std::same_as<Storage, AlignedStorage>) {
84 return RankSelectSupport<Storage>(bit_vector_data.as_words64(),
85 node.data.size());
86 } else {
87 return node.data;
88 }
89 }()) {}
90
91 WaveletNode& operator=(const WaveletNode& node) {
92 if (this != &node) {
93 WaveletNode copy(node);
94 *this = std::move(copy);
95 }
96 return *this;
97 }
98
99 WaveletNode(WaveletNode&&) noexcept = default;
100 WaveletNode& operator=(WaveletNode&&) noexcept = default;
101
102 WaveletNode(PreWaveletNode&& node)
103 requires(std::same_as<Storage, AlignedStorage>)
104 : parent(node.parent),
105 left_child(node.left_child),
106 right_child(node.right_child),
107 middle(node.middle) {
108 const std::size_t bit_count = node.stream.size_bits();
109 bit_vector_data = node.stream.take_storage();
110 data =
111 RankSelectSupport<Storage>(bit_vector_data.as_words64(), bit_count);
112 }
113
115 void serialize(BinaryWriter& writer) const {
116 writer.write_size(parent);
117 writer.write_size(left_child);
118 writer.write_size(right_child);
119 writer.write_u64(middle);
120 bit_vector_data.serialize(writer);
121 data.serialize(writer);
122 }
123
125 static WaveletNode deserialize(BinaryReader& reader,
126 DeserializationValidation validation) {
127 WaveletNode result;
128 result.parent = reader.read_size();
129 result.left_child = reader.read_size();
130 result.right_child = reader.read_size();
131 result.middle = reader.read_u64();
132 result.bit_vector_data = Storage::deserialize(reader);
134 reader, result.bit_vector_data.as_words64(), validation);
135 return result;
136 }
137 };
138
139 size_t alphabet_size_ = 0;
140 size_t data_size_ = 0;
141 node_index_t root_ = npos;
142 std::vector<WaveletNode> nodes_;
143 std::vector<node_index_t> leaves_;
144 std::vector<size_t> permutation_, inverse_permutation_;
145
146 void validate_deserialized_topology(
147 DeserializationValidation validation) const {
148 if (root_ == npos) {
149 if (validation == DeserializationValidation::kFull &&
150 std::ranges::any_of(leaves_,
151 [](node_index_t leaf) { return leaf != npos; })) {
152 throw std::invalid_argument(
153 "Invalid serialized empty wavelet-tree leaves");
154 }
155 return;
156 }
157 if (nodes_[root_].parent != npos) {
158 throw std::invalid_argument("Serialized wavelet-tree root has a parent");
159 }
160
161 std::vector<std::uint8_t> incoming_edges(nodes_.size());
162 for (node_index_t parent = 0; parent < nodes_.size(); ++parent) {
163 const WaveletNode& node = nodes_[parent];
164 for (const node_index_t child : {node.left_child, node.right_child}) {
165 if (child == npos) {
166 continue;
167 }
168 if (nodes_[child].parent != parent) {
169 throw std::invalid_argument(
170 "Serialized wavelet-tree parent/child links disagree");
171 }
172 if (incoming_edges[child] != 0) {
173 throw std::invalid_argument(
174 "Serialized wavelet-tree node has multiple parents");
175 }
176 ++incoming_edges[child];
177 }
178 }
179
180 for (node_index_t node = 0; node < nodes_.size(); ++node) {
181 const std::size_t expected_edges = node == root_ ? 0 : 1;
182 if (incoming_edges[node] != expected_edges) {
183 throw std::invalid_argument(
184 "Serialized wavelet-tree node is detached from its parent");
185 }
186 }
187
188 struct PendingNode {
189 node_index_t node;
190 std::size_t symbol_begin;
191 std::size_t symbol_end;
192 };
193 std::vector<bool> reached(nodes_.size());
194 std::vector<PendingNode> pending = {{root_, 0, alphabet_size_}};
195 while (!pending.empty()) {
196 const PendingNode current = pending.back();
197 pending.pop_back();
198 const node_index_t node = current.node;
199 reached[node] = true;
200 const WaveletNode& metadata = nodes_[node];
201 if (metadata.middle <= current.symbol_begin ||
202 metadata.middle >= current.symbol_end) {
203 throw std::invalid_argument(
204 "Serialized wavelet-tree split is outside its symbol range");
205 }
206
207 const std::size_t one_count =
209 ? metadata.data.rank(metadata.data.size())
210 : 0;
211 const std::size_t zero_count =
213 ? metadata.data.size() - one_count
214 : 0;
215 const auto validate_branch = [&](node_index_t child,
216 std::size_t symbol_begin,
217 std::size_t symbol_end,
218 std::size_t expected_size) {
219 if (child != npos) {
220 if (validation == DeserializationValidation::kFull &&
221 nodes_[child].data.size() != expected_size) {
222 throw std::invalid_argument(
223 "Serialized wavelet-tree child has the wrong length");
224 }
225 pending.push_back({child, symbol_begin, symbol_end});
226 return;
227 }
228 if (validation == DeserializationValidation::kFull) {
229 bool has_mapped_symbol = false;
230 for (std::size_t symbol = symbol_begin; symbol < symbol_end;
231 ++symbol) {
232 if (leaves_[symbol] == node) {
233 has_mapped_symbol = true;
234 } else if (leaves_[symbol] != npos) {
235 throw std::invalid_argument(
236 "Serialized wavelet-tree leaf map disagrees with topology");
237 }
238 }
239 if (expected_size != 0 && !has_mapped_symbol) {
240 throw std::invalid_argument(
241 "Serialized wavelet-tree leaf is missing from its map");
242 }
243 }
244 };
245 validate_branch(metadata.left_child, current.symbol_begin,
246 metadata.middle, zero_count);
247 validate_branch(metadata.right_child, metadata.middle, current.symbol_end,
248 one_count);
249 }
250 if (std::ranges::find(reached, false) != reached.end()) {
251 throw std::invalid_argument(
252 "Serialized wavelet-tree contains unreachable nodes");
253 }
254 }
255
271 template <typename F>
272 node_index_t build_node(size_t begin,
273 size_t end,
274 node_index_t parent,
275 const F& get_middle,
276 std::span<const size_t> prefix_sum,
277 std::vector<PreWaveletNode>& nodes)
278 requires(std::same_as<Storage, AlignedStorage>)
279 {
280 if (end - begin == 1) {
281 leaves_[begin] = parent;
282 return npos;
283 }
284 if (prefix_sum[end] == prefix_sum[begin]) {
285 for (size_t symbol = begin; symbol < end; symbol++) {
286 leaves_[symbol] = parent;
287 }
288 return npos;
289 }
290
291 node_index_t result = nodes.size();
292 size_t middle = get_middle(result);
293 middle = begin + (middle == npos ? (end - begin) / 2 : middle);
294
295 nodes.emplace_back(middle);
296 nodes[result].left_size = prefix_sum[middle] - prefix_sum[begin];
297 nodes[result].stream.reserve_bits(prefix_sum[end] - prefix_sum[begin]);
298 nodes[result].parent = parent;
299 nodes[result].left_child =
300 build_node(begin, middle, result, get_middle, prefix_sum, nodes);
301 nodes[result].right_child =
302 build_node(middle, end, result, get_middle, prefix_sum, nodes);
303
304 return result;
305 }
306
307 static std::size_t count_huffman_subtree(
308 const detail::HuffmanBuildTable& table,
309 std::int16_t source_node,
310 std::span<const std::size_t> symbol_counts,
311 std::array<std::size_t, detail::kMaximumHuffmanNodes>& subtree_counts) {
312 const detail::HuffmanBuildNode& node = table.tree[source_node];
313 if (node.symbol >= 0) {
314 return subtree_counts[source_node] = symbol_counts[node.symbol];
315 }
316 const std::size_t left =
317 count_huffman_subtree(table, node.left, symbol_counts, subtree_counts);
318 const std::size_t right =
319 count_huffman_subtree(table, node.right, symbol_counts, subtree_counts);
320 return subtree_counts[source_node] = left + right;
321 }
322
323 node_index_t materialize_huffman_node(
324 const detail::HuffmanBuildTable& table,
325 std::int16_t source_node,
326 node_index_t parent,
327 const std::array<std::size_t, detail::kMaximumHuffmanNodes>&
328 subtree_counts,
329 std::vector<PreWaveletNode>& nodes)
330 requires(std::same_as<Symbol, std::uint8_t> &&
331 std::same_as<Storage, AlignedStorage>)
332 {
333 const detail::HuffmanBuildNode& source = table.tree[source_node];
334 if (source.symbol >= 0) {
335 leaves_[table.symbol_to_rank[source.symbol]] = parent;
336 return npos;
337 }
338
339 const node_index_t result = nodes.size();
340 const std::size_t middle =
341 static_cast<std::size_t>(table.split_rank[source_node]) + 1;
342 nodes.emplace_back(middle);
343 nodes[result].parent = parent;
344 nodes[result].left_size = subtree_counts[source.left];
345 nodes[result].stream.reserve_bits(subtree_counts[source_node]);
346 nodes[result].left_child = materialize_huffman_node(
347 table, source.left, result, subtree_counts, nodes);
348 nodes[result].right_child = materialize_huffman_node(
349 table, source.right, result, subtree_counts, nodes);
350 return result;
351 }
352
353 void build_huffman_byte_topology(std::span<const std::size_t> symbol_counts,
354 std::vector<PreWaveletNode>& nodes)
355 requires(std::same_as<Symbol, std::uint8_t> &&
356 std::same_as<Storage, AlignedStorage>)
357 {
358 const detail::HuffmanBuildTable table =
359 detail::build_huffman_table(symbol_counts);
360 permutation_.resize(alphabet_size_);
361 inverse_permutation_.resize(alphabet_size_);
362
363 std::size_t unused_rank = table.symbol_count;
364 for (std::size_t symbol = 0; symbol < alphabet_size_; ++symbol) {
365 const std::size_t rank = symbol_counts[symbol] == 0
366 ? unused_rank++
367 : table.symbol_to_rank[symbol];
368 permutation_[symbol] = rank;
369 inverse_permutation_[rank] = symbol;
370 }
371 if (table.symbol_count < 2) {
372 root_ = npos;
373 return;
374 }
375
376 std::array<std::size_t, detail::kMaximumHuffmanNodes> subtree_counts{};
377 count_huffman_subtree(table, table.root, symbol_counts, subtree_counts);
378 nodes.reserve(table.symbol_count - 1);
379 root_ = materialize_huffman_node(table, table.root, npos, subtree_counts,
380 nodes);
381 }
382
383 void build_node_streams(node_index_t node,
384 std::span<Symbol> input,
385 std::span<Symbol> output,
386 std::vector<PreWaveletNode>& nodes)
387 requires(std::same_as<Storage, AlignedStorage>)
388 {
389 PreWaveletNode& current = nodes[node];
391 std::span<const Symbol>(input), static_cast<Symbol>(current.middle),
392 output, current.left_size, current.left_child != npos,
393 current.right_child != npos, current.stream);
394
395 if (current.left_child != npos) {
396 build_node_streams(current.left_child, output.first(current.left_size),
397 input.first(current.left_size), nodes);
398 }
399 if (current.right_child != npos) {
400 build_node_streams(current.right_child, output.subspan(current.left_size),
401 input.subspan(current.left_size), nodes);
402 }
403 }
404
405 // PivCo-style block walk: keep the left subsequence in place, put the right
406 // subsequence in scratch, and omit scatter for leaf children. Separate node
407 // builders make every block append bit-contiguously without wire padding.
408 void build_byte_block_streams(node_index_t node,
409 std::span<std::uint8_t> ranks,
410 std::span<std::uint8_t> scratch,
411 std::vector<PreWaveletNode>& nodes)
412 requires(std::same_as<Symbol, std::uint8_t> &&
413 std::same_as<Storage, AlignedStorage>)
414 {
415 PreWaveletNode& current = nodes[node];
416 const bool write_left = current.left_child != npos;
417 const bool write_right = current.right_child != npos;
418 const std::size_t left_size = detail::partition_wavelet_byte_block(
419 ranks, static_cast<std::uint8_t>(current.middle), scratch, write_left,
420 write_right, current.stream);
421 const std::size_t right_size = ranks.size() - left_size;
422
423 if (write_left && left_size != 0) {
424 build_byte_block_streams(current.left_child, ranks.first(left_size),
425 scratch.subspan(right_size, left_size), nodes);
426 }
427 if (write_right && right_size != 0) {
428 build_byte_block_streams(current.right_child, scratch.first(right_size),
429 ranks.first(right_size), nodes);
430 }
431 }
432
433 template <bool ValidateSymbolCounts, class ForEachSymbol>
434 void build_byte_bit_streams(std::span<const std::size_t> symbol_counts,
435 ForEachSymbol& for_each_symbol,
436 std::vector<PreWaveletNode>& nodes)
437 requires(std::same_as<Symbol, std::uint8_t> &&
438 std::same_as<Storage, AlignedStorage>)
439 {
440 std::array<std::uint8_t, detail::kByteAlphabetSize> symbol_to_rank{};
441 std::array<std::uint16_t, detail::kByteAlphabetSize> symbol_to_high_rank{};
442 for (std::size_t symbol = 0; symbol < alphabet_size_; ++symbol) {
443 const auto rank = static_cast<std::uint8_t>(permutation_[symbol]);
444 symbol_to_rank[symbol] = rank;
445 symbol_to_high_rank[symbol] = static_cast<std::uint16_t>(rank) << 8;
446 }
447
448 const std::size_t block_capacity = std::min(
449 kByteConstructionBlockSize, std::max<std::size_t>(data_size_, 1));
450 std::vector<std::uint8_t> block;
451 std::vector<std::uint8_t> scratch(block_capacity);
452 block.reserve(block_capacity);
453 struct NoHistogram {};
454 using ValidationHistogram =
455 std::conditional_t<ValidateSymbolCounts, detail::ByteHistogram,
456 NoHistogram>;
457 ValidationHistogram actual_histogram;
458 const auto encode_block = [&] {
459 if (block.empty()) {
460 return;
461 }
462 if (root_ != npos) {
463 build_byte_block_streams(root_, block,
464 std::span(scratch).first(block.size()), nodes);
465 }
466 block.clear();
467 };
468
469 std::size_t emitted_size = 0;
470 const auto append_symbols = [&](std::span<const Symbol> symbols) {
471 if constexpr (ValidateSymbolCounts) {
472 if (emitted_size > data_size_ ||
473 symbols.size() > data_size_ - emitted_size) {
474 throw std::invalid_argument(
475 "Wavelet-tree emitted symbols do not match their counts");
476 }
477 emitted_size += symbols.size();
478 actual_histogram.add(std::as_bytes(symbols));
479 }
480 while (!symbols.empty()) {
481 const std::size_t copied =
482 std::min(block_capacity - block.size(), symbols.size());
483 const std::size_t destination = block.size();
484 block.resize(destination + copied);
486 symbols.first(copied),
487 std::span(block).subspan(destination, copied), symbol_to_rank,
488 symbol_to_high_rank);
489 symbols = symbols.subspan(copied);
490 if (block.size() == block_capacity) {
491 encode_block();
492 }
493 }
494 };
495 for_each_symbol([&](auto&& emitted) {
496 using Emitted = std::remove_cvref_t<decltype(emitted)>;
497 if constexpr (std::same_as<Emitted, Symbol>) {
498 append_symbols(std::span<const Symbol>(&emitted, 1));
499 } else {
500 append_symbols(std::span<const Symbol>(emitted));
501 }
502 });
503 encode_block();
504
505 if constexpr (ValidateSymbolCounts) {
506 const auto actual_counts = actual_histogram.counts();
507 if (emitted_size != data_size_ ||
508 !std::ranges::equal(actual_counts.begin(),
509 actual_counts.begin() + alphabet_size_,
510 symbol_counts.begin(), symbol_counts.end()) ||
511 std::ranges::any_of(actual_counts.begin() + alphabet_size_,
512 actual_counts.end(),
513 [](std::size_t count) { return count != 0; })) {
514 throw std::invalid_argument(
515 "Wavelet-tree emitted symbols do not match their counts");
516 }
517 }
518 }
519
520 template <bool ValidateSymbolCounts, class ForEachSymbol>
521 void build_generic_bit_streams(std::span<const std::size_t> symbol_counts,
522 ForEachSymbol& for_each_symbol,
523 std::vector<PreWaveletNode>& nodes)
524 requires(std::same_as<Storage, AlignedStorage>)
525 {
526 std::vector<std::size_t> actual_counts;
527 if constexpr (ValidateSymbolCounts) {
528 actual_counts.resize(alphabet_size_);
529 }
530 std::vector<Symbol> ranks;
531 if (root_ != npos) {
532 ranks.reserve(data_size_);
533 }
534 for_each_symbol([&](Symbol symbol) {
535 const std::size_t original = static_cast<std::size_t>(symbol);
536 if constexpr (ValidateSymbolCounts) {
537 if (original >= alphabet_size_) {
538 throw std::invalid_argument(
539 "Wavelet-tree symbol is outside the alphabet");
540 }
541 if (actual_counts[original] ==
542 std::numeric_limits<std::size_t>::max()) {
543 throw std::length_error("Wavelet-tree symbol count is too large");
544 }
545 ++actual_counts[original];
546 }
547 if (root_ != npos) {
548 ranks.push_back(static_cast<Symbol>(permutation_[original]));
549 }
550 });
551 if constexpr (ValidateSymbolCounts) {
552 if (!std::ranges::equal(actual_counts, symbol_counts)) {
553 throw std::invalid_argument(
554 "Wavelet-tree emitted symbols do not match their counts");
555 }
556 }
557 if (root_ == npos) {
558 return;
559 }
560
561 const PreWaveletNode& root = nodes[root_];
562 const bool root_needs_output =
563 root.left_child != npos || root.right_child != npos;
564 std::vector<Symbol> scratch(root_needs_output ? data_size_ : 0);
565 build_node_streams(root_, ranks, scratch, nodes);
566 }
567
568 template <bool ValidateSymbolCounts, class ForEachSymbol>
569 void build_bit_streams(std::span<const std::size_t> symbol_counts,
570 ForEachSymbol& for_each_symbol,
571 std::vector<PreWaveletNode>& nodes)
572 requires(std::same_as<Storage, AlignedStorage>)
573 {
574 if constexpr (std::same_as<Symbol, std::uint8_t>) {
575 build_byte_bit_streams<ValidateSymbolCounts>(symbol_counts,
576 for_each_symbol, nodes);
577 } else {
578 build_generic_bit_streams<ValidateSymbolCounts>(symbol_counts,
579 for_each_symbol, nodes);
580 }
581 }
582
598 void copy_segment_content(node_index_t node,
599 size_t begin,
600 size_t end,
601 std::span<Symbol> dst,
602 std::span<Symbol> tmp) const {
603 if (begin == end) {
604 return;
605 }
606 const size_t rank = nodes_[node].data.rank(begin), rank0 = begin - rank;
607 const size_t right = nodes_[node].data.rank(end) - rank,
608 left = (end - begin) - right;
609
610 if (nodes_[node].left_child == npos) {
611 std::fill_n(
612 tmp.begin(), static_cast<long long>(left),
613 static_cast<Symbol>(inverse_permutation_[nodes_[node].middle - 1]));
614 } else {
615 copy_segment_content(nodes_[node].left_child, rank0, rank0 + left,
616 tmp.subspan(0, left), dst.subspan(0, left));
617 }
618 if (nodes_[node].right_child == npos) {
619 std::fill(tmp.begin() + static_cast<long long>(left), tmp.end(),
620 static_cast<Symbol>(inverse_permutation_[nodes_[node].middle]));
621 } else {
622 copy_segment_content(nodes_[node].right_child, rank, rank + right,
623 tmp.subspan(left, right), dst.subspan(left, right));
624 }
625
626 size_t j = 0, k = left;
627 const auto& bit_vector = nodes_[node].bit_vector_data.as_words64();
628 for (size_t i = begin; i < end; i++) {
629 if ((bit_vector[i / 64] >> (i % 64)) & 1) {
630 dst[i - begin] = tmp[k++];
631 } else {
632 dst[i - begin] = tmp[j++];
633 }
634 }
635 }
636
637 static void validate_alphabet_size(std::size_t alphabet_size) {
638 if (alphabet_size != 0 &&
639 alphabet_size - 1 >
640 static_cast<std::size_t>(std::numeric_limits<Symbol>::max())) {
641 throw std::invalid_argument(
642 "Wavelet-tree alphabet does not fit its symbol type");
643 }
644 }
645
646 static std::size_t checked_symbol_index(Symbol symbol,
647 std::size_t alphabet_size) {
648 const std::size_t index = static_cast<std::size_t>(symbol);
649 if (index >= alphabet_size) {
650 throw std::invalid_argument(
651 "Wavelet-tree symbol is outside the alphabet");
652 }
653 return index;
654 }
655
656 template <bool ValidateSymbolCounts, class ForEachSymbol>
657 void build_from_counts(std::size_t alphabet_size,
658 std::span<const std::size_t> symbol_counts,
659 ForEachSymbol&& for_each_symbol,
660 WaveletTreeBuildType build_type)
661 requires(std::same_as<Storage, AlignedStorage>)
662 {
663 validate_alphabet_size(alphabet_size);
664 if (symbol_counts.size() != alphabet_size) {
665 throw std::invalid_argument(
666 "Wavelet-tree symbol counts must match the alphabet size");
667 }
668 alphabet_size_ = alphabet_size;
669 for (const std::size_t count : symbol_counts) {
670 if (count > std::numeric_limits<std::size_t>::max() - data_size_) {
671 throw std::length_error("Wavelet-tree input is too large");
672 }
673 data_size_ += count;
674 }
675 leaves_.assign(alphabet_size_, npos);
676
677 std::vector<PreWaveletNode> nodes;
678 if constexpr (std::same_as<Symbol, std::uint8_t>) {
679 if (build_type == WaveletTreeBuildType::Huffman) {
680 if (alphabet_size_ != 0) {
681 build_huffman_byte_topology(symbol_counts, nodes);
682 }
683 build_bit_streams<ValidateSymbolCounts>(symbol_counts, for_each_symbol,
684 nodes);
685 nodes_.reserve(nodes.size());
686 for (auto& node : nodes) {
687 nodes_.emplace_back(std::move(node));
688 }
689 return;
690 }
691 }
692
693 std::vector<std::size_t> nodes_structure;
694 if (alphabet_size_ != 0) {
695 nodes.reserve(alphabet_size_);
696 nodes_structure.reserve(alphabet_size_);
697
698 if (build_type == WaveletTreeBuildType::Standard) {
699 permutation_.resize(alphabet_size_);
700 inverse_permutation_.resize(alphabet_size_);
701 std::iota(permutation_.begin(), permutation_.end(), 0);
702 std::iota(inverse_permutation_.begin(), inverse_permutation_.end(), 0);
703 nodes_structure.resize(alphabet_size_, npos);
704 } else {
705 struct HuffmanNode {
706 std::size_t size;
707 std::size_t left;
708 std::size_t right;
709 };
710 std::vector<HuffmanNode> huffman_nodes(alphabet_size_, {0, 0, 0});
711 for (std::size_t symbol = 0; symbol < alphabet_size_; ++symbol) {
712 huffman_nodes[symbol].size = symbol_counts[symbol];
713 }
714
715 using QueueElement = std::pair<std::size_t, std::size_t>;
716 std::priority_queue<QueueElement, std::vector<QueueElement>,
717 std::greater<>>
718 queue;
719 for (std::size_t symbol = 0; symbol < alphabet_size_; ++symbol) {
720 queue.emplace(huffman_nodes[symbol].size, symbol);
721 }
722 while (queue.size() >= 2) {
723 const std::size_t right = queue.top().second;
724 queue.pop();
725 const std::size_t left = queue.top().second;
726 queue.pop();
727 huffman_nodes.push_back(
728 {huffman_nodes[left].size + huffman_nodes[right].size, left + 1,
729 right + 1});
730 queue.emplace(huffman_nodes.back().size, huffman_nodes.size() - 1);
731 }
732
733 std::function<std::size_t(std::size_t)> enumerate =
734 [&](std::size_t index) -> std::size_t {
735 const auto& [size, left, right] = huffman_nodes[index];
736 if (left == 0 || right == 0) {
737 permutation_[index] = inverse_permutation_.size();
738 inverse_permutation_.push_back(index);
739 return 1;
740 }
741 const std::size_t node = nodes_structure.size();
742 std::size_t subtree = 0;
743 if (size > 0) {
744 nodes_structure.push_back(0);
745 }
746 subtree += enumerate(left - 1);
747 if (size > 0) {
748 nodes_structure[node] = subtree;
749 }
750 subtree += enumerate(right - 1);
751 return subtree;
752 };
753
754 permutation_.resize(alphabet_size_);
755 inverse_permutation_.reserve(alphabet_size_);
756 enumerate(huffman_nodes.size() - 1);
757 }
758
759 std::vector<std::size_t> prefix_sum(alphabet_size_ + 1);
760 for (std::size_t symbol = 0; symbol < alphabet_size_; ++symbol) {
761 prefix_sum[permutation_[symbol] + 1] = symbol_counts[symbol];
762 }
763 std::partial_sum(prefix_sum.begin(), prefix_sum.end(),
764 prefix_sum.begin());
765 root_ = build_node(
766 0, alphabet_size_, npos,
767 [&](node_index_t node) { return nodes_structure[node]; }, prefix_sum,
768 nodes);
769 }
770
771 build_bit_streams<ValidateSymbolCounts>(symbol_counts, for_each_symbol,
772 nodes);
773
774 nodes_.reserve(nodes.size());
775 for (auto& node : nodes) {
776 nodes_.emplace_back(std::move(node));
777 }
778 }
779
780 WaveletTreeIndex() = default;
781
782 public:
783 using symbol_type = Symbol;
784
796 std::size_t alphabet_size,
797 std::span<const Symbol> data,
798 const WaveletTreeBuildType build_type = WaveletTreeBuildType::Standard)
799 requires(std::same_as<Storage, AlignedStorage>)
800 {
801 validate_alphabet_size(alphabet_size);
802 std::vector<std::size_t> counts;
803 if constexpr (std::same_as<Symbol, std::uint8_t>) {
804 detail::ByteHistogram histogram;
805 histogram.add(std::as_bytes(data));
806 const auto byte_counts = histogram.counts();
807 if (std::ranges::any_of(byte_counts.begin() + alphabet_size,
808 byte_counts.end(),
809 [](std::size_t count) { return count != 0; })) {
810 throw std::invalid_argument(
811 "Wavelet-tree symbol is outside the alphabet");
812 }
813 counts.assign(byte_counts.begin(), byte_counts.begin() + alphabet_size);
814 } else {
815 counts.assign(alphabet_size, 0);
816 for (const Symbol symbol : data) {
817 ++counts[checked_symbol_index(symbol, alphabet_size)];
818 }
819 }
820 build_from_counts<false>(
821 alphabet_size, counts,
822 [&](auto&& emit) {
823 if constexpr (requires { emit(data); }) {
824 emit(data);
825 } else {
826 for (const Symbol symbol : data) {
827 emit(symbol);
828 }
829 }
830 },
831 build_type);
832 }
833
851 template <class ForEachSymbol>
853 std::size_t alphabet_size,
854 std::span<const std::size_t> symbol_counts,
855 ForEachSymbol&& for_each_symbol,
856 const WaveletTreeBuildType build_type = WaveletTreeBuildType::Standard)
857 requires(std::same_as<Storage, AlignedStorage>)
858 {
859 build_from_counts<true>(alphabet_size, symbol_counts,
860 std::forward<ForEachSymbol>(for_each_symbol),
861 build_type);
862 }
863
872 size_t rank_impl(Symbol symbol, size_t pos) const {
873 std::size_t symbol_index = static_cast<std::size_t>(symbol);
874 if (symbol_index >= alphabet_size_) [[unlikely]] {
875 return 0;
876 }
877 symbol_index = permutation_[symbol_index];
878 if (root_ == npos) [[unlikely]] {
879 return data_size_ != 0 && symbol_index == 0 ? pos : 0;
880 }
881 if (leaves_[symbol_index] == npos) [[unlikely]] {
882 return 0;
883 }
884 for (node_index_t current = root_; current != npos;) {
885 const WaveletNode& node = nodes_[current];
886 if (symbol_index < node.middle) {
887 pos = node.data.rank0(pos);
888 current = node.left_child;
889 } else {
890 pos = node.data.rank(pos);
891 current = node.right_child;
892 }
893 }
894 return pos;
895 }
896
905 size_t select_impl(Symbol symbol, size_t rank) const {
906 std::size_t symbol_index = static_cast<std::size_t>(symbol);
907 if (symbol_index >= alphabet_size_ || data_size_ == 0 || rank == 0)
908 [[unlikely]] {
909 return data_size_;
910 }
911 symbol_index = permutation_[symbol_index];
912 if (root_ == npos) [[unlikely]] {
913 return symbol_index == 0 && rank <= data_size_ ? rank - 1 : data_size_;
914 }
915 if (leaves_[symbol_index] == npos) [[unlikely]] {
916 return data_size_;
917 }
918 node_index_t current = leaves_[symbol_index];
919 for (; current != npos; current = nodes_[current].parent) {
920 const WaveletNode& node = nodes_[current];
921 if (symbol_index < node.middle) {
922 rank = node.data.select0(rank) + 1;
923 } else {
924 rank = node.data.select(rank) + 1;
925 }
926 }
927 return rank - 1;
928 }
929
944 std::vector<Symbol> get_segment_impl(size_t begin, size_t end) const {
945 if (alphabet_size_ == 0 || data_size_ == 0 || begin >= end) [[unlikely]] {
946 return {};
947 }
948 const std::size_t length = end - begin;
949 if (root_ == npos) [[unlikely]] {
950 return std::vector<Symbol>(
951 length, static_cast<Symbol>(inverse_permutation_.front()));
952 }
953 if (length > std::vector<Symbol>().max_size() / 2) {
954 throw std::length_error("Wavelet-tree segment is too large");
955 }
956 std::vector<Symbol> result(2 * length);
957 copy_segment_content(root_, begin, end, std::span(result).first(length),
958 std::span(result).subspan(length));
959 result.resize(length);
960 return result;
961 }
962
967 size_t size_impl() const { return data_size_; }
968
975 void serialize_impl(BinaryWriter& writer) const {
976 if (writer.size_bytes() % alignof(std::uint64_t) != 0) {
977 throw std::invalid_argument(
978 "Wavelet-tree serialization requires an aligned writer offset");
979 }
980 const std::size_t artifact_begin = writer.size_bytes();
981 detail::write_magic(writer, kSerializationMagic);
982 writer.write_u32(kSerializationVersion);
983 writer.write_u32(std::numeric_limits<Symbol>::digits);
984 const std::size_t artifact_size_position = writer.write_u64_placeholder();
985
986 writer.write_size(alphabet_size_);
987 writer.write_size(data_size_);
988 writer.write_size(root_);
989 writer.write_size(nodes_.size());
990 for (const WaveletNode& node : nodes_) {
991 node.serialize(writer);
992 }
993 for (const node_index_t leaf : leaves_) {
994 writer.write_size(leaf);
995 }
996 for (const size_t idx : permutation_) {
997 writer.write_size(idx);
998 }
999
1000 const std::size_t unpadded_size = writer.size_bytes() - artifact_begin;
1001 writer.write_zeros(
1002 (sizeof(std::uint64_t) - unpadded_size % sizeof(std::uint64_t)) %
1003 sizeof(std::uint64_t));
1004 writer.patch_u64(
1005 artifact_size_position,
1006 static_cast<std::uint64_t>(writer.size_bytes() - artifact_begin));
1007 }
1008
1026 static WaveletTreeIndex deserialize_impl(
1027 BinaryReader& reader,
1029 requires(std::same_as<Storage, AlignedStorage> ||
1030 std::same_as<Storage, ReadOnlyStorageView>)
1031 {
1032 BinaryReader candidate = reader;
1033 if constexpr (std::same_as<Storage, ReadOnlyStorageView>) {
1034 if (reinterpret_cast<std::uintptr_t>(candidate.remaining_bytes().data()) %
1035 alignof(std::uint64_t) !=
1036 0) {
1037 throw std::invalid_argument(
1038 "Serialized wavelet-tree artifact is not word aligned");
1039 }
1040 }
1041 const std::size_t available_size = candidate.remaining();
1042 detail::require_magic(candidate, kSerializationMagic);
1043 if (candidate.read_u32() != kSerializationVersion ||
1044 candidate.read_u32() != std::numeric_limits<Symbol>::digits) {
1045 throw std::invalid_argument(
1046 "Incompatible serialized wavelet-tree artifact");
1047 }
1048 const std::size_t artifact_size = detail::checked_artifact_size(
1049 candidate.read_u64(), kSerializationHeaderBytes, available_size);
1050 BinaryReader payload =
1051 candidate.read_subreader(artifact_size - kSerializationHeaderBytes);
1052
1053 WaveletTreeIndex result;
1054 result.alphabet_size_ = payload.read_size();
1055 result.validate_alphabet_size(result.alphabet_size_);
1056 result.data_size_ = payload.read_size();
1057 result.root_ = payload.read_size();
1058 const std::size_t node_count = payload.read_size();
1059 const std::vector<WaveletNode> empty_nodes;
1060 if (node_count > empty_nodes.max_size()) {
1061 throw std::length_error(
1062 "Serialized wavelet-tree node count is too large");
1063 }
1064 constexpr std::size_t kMinimumNodeBytes =
1065 4 * sizeof(std::uint64_t) + sizeof(std::uint64_t);
1066 if (node_count > payload.remaining() / kMinimumNodeBytes) {
1067 throw SerializationError("Truncated serialized wavelet-tree nodes",
1068 payload.byte_offset());
1069 }
1070 result.nodes_.resize(node_count);
1071 for (auto& node : result.nodes_) {
1072 node = WaveletNode::deserialize(payload, validation);
1073 }
1074 const std::vector<node_index_t> empty_indices;
1075 if (result.alphabet_size_ > empty_indices.max_size() ||
1076 result.alphabet_size_ >
1077 payload.remaining() / (2 * sizeof(std::uint64_t))) {
1078 throw std::length_error("Serialized wavelet-tree alphabet is too large");
1079 }
1080 result.leaves_.resize(result.alphabet_size_);
1081 for (node_index_t& leaf : result.leaves_) {
1082 leaf = payload.read_size();
1083 }
1084 result.permutation_.resize(result.alphabet_size_);
1085 for (size_t& index : result.permutation_) {
1086 index = payload.read_size();
1087 }
1088 result.inverse_permutation_.resize(result.alphabet_size_);
1089 std::vector<bool> seen(result.alphabet_size_);
1090 for (size_t i = 0; i < result.alphabet_size_; i++) {
1091 if (result.permutation_[i] >= result.alphabet_size_ ||
1092 seen[result.permutation_[i]]) {
1093 throw std::invalid_argument(
1094 "Invalid serialized wavelet-tree permutation");
1095 }
1096 seen[result.permutation_[i]] = true;
1097 result.inverse_permutation_[result.permutation_[i]] = i;
1098 }
1099 const auto valid_node_index = [&result](node_index_t index) {
1100 return index == npos || index < result.nodes_.size();
1101 };
1102 if (!valid_node_index(result.root_) ||
1103 (result.nodes_.empty() != (result.root_ == npos))) {
1104 throw std::invalid_argument("Invalid serialized wavelet-tree root");
1105 }
1106 for (const node_index_t leaf : result.leaves_) {
1107 if (!valid_node_index(leaf)) {
1108 throw std::invalid_argument("Invalid serialized wavelet-tree leaf");
1109 }
1110 }
1111 for (const WaveletNode& node : result.nodes_) {
1112 if (!valid_node_index(node.parent) ||
1113 !valid_node_index(node.left_child) ||
1114 !valid_node_index(node.right_child) ||
1115 node.data.size() > node.bit_vector_data.size_bits() ||
1116 node.middle == 0 || node.middle >= result.alphabet_size_) {
1117 throw std::invalid_argument("Invalid serialized wavelet-tree node");
1118 }
1119 }
1120 result.validate_deserialized_topology(validation);
1121 if (result.root_ != npos &&
1122 result.nodes_[result.root_].data.size() != result.data_size_) {
1123 throw std::invalid_argument(
1124 "Serialized wavelet-tree root has the wrong length");
1125 }
1126 payload.require_zero_padding(sizeof(std::uint64_t) - 1);
1127 reader = candidate;
1128 return result;
1129 }
1130};
1131
1132template <WaveletTreeSymbol Symbol>
1133using WaveletTree = WaveletTreeIndex<Symbol, AlignedStorage>;
1134
1135template <WaveletTreeSymbol Symbol>
1136using WaveletTreeView = WaveletTreeIndex<Symbol, ReadOnlyStorageView>;
1137
1138} // namespace pixie
Dependency-reduced histogram construction for byte streams.
Owning storage with a logical byte size and 64-byte-aligned backing.
Definition aligned.h:41
Bounds-checked reader for canonical little-endian binary data.
Definition serialization.h:526
std::size_t remaining() const noexcept
Return the number of unconsumed bytes.
Definition serialization.h:544
std::span< const std::byte > remaining_bytes() const noexcept
Return all currently unconsumed bytes.
Definition serialization.h:547
BinaryReader read_subreader(std::size_t count)
Read a bounded region as an independent child reader.
Definition serialization.h:615
std::uint64_t read_u64()
Read an unsigned little-endian 64-bit integer.
Definition serialization.h:561
void require_zero_padding(std::size_t maximum)
Consume at most maximum trailing zero-padding bytes.
Definition serialization.h:633
std::uint32_t read_u32()
Read an unsigned little-endian 32-bit integer.
Definition serialization.h:558
std::size_t byte_offset() const noexcept
Return the current byte offset in the outermost input.
Definition serialization.h:541
std::size_t read_size()
Read an unsigned 64-bit size and convert it to size_t.
Definition serialization.h:587
Bounded-buffer writer for canonical little-endian binary data.
Definition serialization.h:198
void write_u32(std::uint32_t value)
Write an unsigned 32-bit integer in little-endian order.
Definition serialization.h:258
std::size_t size_bytes() const noexcept
Return the logical number of bytes written.
Definition serialization.h:244
void write_zeros(std::size_t count)
Append count zero bytes.
Definition serialization.h:328
void patch_u64(std::size_t position, std::uint64_t value)
Replace an existing 64-bit field with value.
Definition serialization.h:373
std::size_t write_u64_placeholder()
Write a zero 64-bit field and return its byte position.
Definition serialization.h:363
void write_u64(std::uint64_t value)
Write an unsigned 64-bit integer in little-endian order.
Definition serialization.h:261
void write_size(std::size_t value)
Write a platform size as an unsigned 64-bit integer.
Definition serialization.h:287
Builder for a packed LSB-first bit sequence.
Definition packed_bit_builder.h:23
std::uint64_t select(std::size_t rank) const
Return the position of the rank-th one bit.
Definition rank_select.h:95
std::uint64_t select0(std::size_t rank) const
Return the position of the rank-th zero bit.
Definition rank_select.h:107
std::uint64_t rank0(std::size_t end_position) const
Count zero bits in the prefix [0, end_position).
Definition rank_select.h:78
std::size_t size() const
Return the number of valid bits.
Definition rank_select.h:39
std::uint64_t rank(std::size_t end_position) const
Count one bits in the prefix [0, end_position).
Definition rank_select.h:66
Rank/select support over an external packed bit sequence.
Definition support.h:58
CRTP facade for optional binary serialization and deserialization.
Definition serialization.h:693
static WaveletTreeIndex< Symbol, AlignedStorage > deserialize(BinaryReader &reader, Context &&... context)
Definition serialization.h:710
void serialize(BinaryWriter &writer) const
Definition serialization.h:696
Error raised while decoding malformed serialized data.
Definition serialization.h:45
CRTP facade for wavelet-tree queries.
Definition wavelet_tree.h:36
std::size_t rank(Symbol symbol, std::size_t end_position) const
Definition wavelet_tree.h:56
void serialize_impl(BinaryWriter &writer) const
Write a versioned canonical little-endian wavelet-tree artifact.
Definition index.h:975
static WaveletTreeIndex deserialize_impl(BinaryReader &reader, DeserializationValidation validation=DeserializationValidation::kQuick)
Restore one checked wavelet-tree artifact.
Definition index.h:1026
size_t size_impl() const
Definition index.h:967
WaveletTreeIndex(std::size_t alphabet_size, std::span< const Symbol > data, const WaveletTreeBuildType build_type=WaveletTreeBuildType::Standard)
Construct from a contiguous sequence of typed symbols.
Definition index.h:795
std::vector< Symbol > get_segment_impl(size_t begin, size_t end) const
Accumulates the original data segment.
Definition index.h:944
WaveletTreeIndex(std::size_t alphabet_size, std::span< const std::size_t > symbol_counts, ForEachSymbol &&for_each_symbol, const WaveletTreeBuildType build_type=WaveletTreeBuildType::Standard)
Construct from counts and one streamed pass over the symbols.
Definition index.h:852
size_t select_impl(Symbol symbol, size_t rank) const
Select the position of the rank-th specified symbol (1-indexed)
Definition index.h:905
size_t rank_impl(Symbol symbol, size_t pos) const
Rank of specified symbol up to position pos (exclusive)
Definition index.h:872
Incremental 256-bin byte histogram with four update lanes.
Definition byte_histogram.h:16
std::array< std::size_t, 256 > counts() const
Return the combined symbol counts.
Definition byte_histogram.h:34
void add(std::span< const std::byte > bytes)
Add one contiguous chunk to the histogram.
Definition byte_histogram.h:19
A concrete CRTP implementation of StorageBase.
Definition storage.h:290
Unsigned code-unit type indexed by a wavelet tree.
Definition wavelet_tree.h:23
Length-limited byte Huffman tree construction for wavelet indexes.
HuffmanBuildTable build_huffman_table(std::span< const std::size_t > frequencies)
Build PivCo's sibling-paired, non-flat byte Huffman tree.
Definition huffman_build_table.h:358
DeserializationValidation
Validation strength used while restoring serialized indexes.
Definition serialization.h:28
@ kQuick
Check framing, dimensions, references, and other conditions needed for memory-safe terminating querie...
Definition serialization.h:33
@ kFull
Additionally authenticate all source-derived metadata against the supplied source contents.
Definition serialization.h:39
Definition huffman_build_table.h:27
Definition huffman_build_table.h:33
Bulk direction-mask and stable-partition primitives.
void map_wavelet_byte_ranks(std::span< const std::uint8_t > symbols, std::span< std::uint8_t > ranks, const std::array< std::uint8_t, 256 > &symbol_to_rank, const std::array< std::uint16_t, 256 > &symbol_to_high_rank)
Definition wavelet_partition.h:262
void partition_wavelet_ranks(std::span< const Symbol > input, Symbol middle, std::span< Symbol > output, std::size_t expected_left, bool write_left, bool write_right, PackedBitBuilder &directions)
Build packed node directions and stably partition ranks.
Definition wavelet_partition.h:224
std::size_t partition_wavelet_byte_block(std::span< std::uint8_t > ranks, std::uint8_t first_right, std::span< std::uint8_t > right_output, bool write_left, bool write_right, PackedBitBuilder &directions)
Emit a cache-sized byte block's directions and live child ranks.
Definition wavelet_partition.h:595
Common interface for wavelet-tree indexes.
WaveletTreeBuildType
Construction strategy for a wavelet-tree implementation.
Definition wavelet_tree.h:19