Pixie
Loading...
Searching...
No Matches
cartesian_hybrid_btree.h
1#pragma once
2
3#include <pixie/bits.h>
4#include <pixie/memory_usage.h>
5#include <pixie/rank_select/support.h>
6#include <pixie/rmq.h>
7#include <pixie/rmq/utils/succinct_monotone_stack.h>
8#include <pixie/storage/aligned.h>
9
10#include <algorithm>
11#include <array>
12#include <bit>
13#include <cstddef>
14#include <cstdint>
15#include <functional>
16#include <limits>
17#include <optional>
18#include <span>
19#include <stdexcept>
20#include <type_traits>
21#include <utility>
22#include <vector>
23
24namespace pixie::rmq {
25
26namespace detail {
27
56template <class Index = std::size_t,
57 std::size_t LeafSize = 512,
58 bool UseHighSparseLayout = true,
59 std::size_t HighSparseLayoutLevels = 2>
61 public:
62 static_assert(std::is_unsigned_v<Index>,
63 "HybridBTreePlusMinusOne index type must be unsigned");
64 static_assert(LeafSize != 0 && LeafSize % 512 == 0,
65 "HybridBTreePlusMinusOne leaf size must be a positive "
66 "multiple of 512");
67 static_assert(!UseHighSparseLayout || HighSparseLayoutLevels > 0,
68 "HybridBTreePlusMinusOne high sparse layout must cover "
69 "at least one level when enabled");
70
71 static constexpr std::size_t npos = std::numeric_limits<std::size_t>::max();
72 static constexpr Index invalid_index = std::numeric_limits<Index>::max();
73 static constexpr std::size_t kLeafSize = LeafSize;
74 static constexpr std::size_t kHighLevelFanout = 256;
75 static constexpr std::size_t kMiddleFanout = 192;
76 static constexpr bool kUseHighSparseLayout = UseHighSparseLayout;
77 static constexpr std::size_t kHighSparseLayoutLevels = HighSparseLayoutLevels;
78
83
92 HybridBTreePlusMinusOne(std::span<const std::uint64_t> bits,
93 std::size_t depth_count) {
94 build(bits, depth_count);
95 }
96
104 HybridBTreePlusMinusOne(std::span<const std::uint64_t> bits,
105 std::size_t depth_count,
106 const RankSelectSupport<>& rank_index) {
107 build(bits, depth_count, rank_index);
108 }
109
113 void build(std::span<const std::uint64_t> bits, std::size_t depth_count) {
114 input_bits_ = bits;
115 depth_count_ = depth_count;
116 external_rank_index_ = nullptr;
117 build();
118 }
119
123 void build(std::span<const std::uint64_t> bits,
124 std::size_t depth_count,
125 const RankSelectSupport<>& rank_index) {
126 input_bits_ = bits;
127 depth_count_ = depth_count;
128 external_rank_index_ = &rank_index;
129 build();
130 }
131
135 std::size_t size() const { return depth_count_; }
136
140 bool empty() const { return depth_count_ == 0; }
141
148 std::size_t memory_usage_bytes() const {
149 std::size_t bytes = sizeof(*this);
150 if (external_rank_index_ == nullptr) {
151 bytes += pixie::optional_nested_owned_memory_bytes(owned_rank_index_);
152 }
153 bytes += pixie::vector_capacity_bytes(internal_selectors_);
154 bytes += pixie::vector_capacity_bytes(internal_min_positions_);
155 bytes += pixie::vector_capacity_bytes(internal_min_depths_);
156 bytes += pixie::vector_capacity_bytes(high_child_metadata_);
157 bytes += pixie::vector_capacity_bytes(high_sparse_min_slots_);
158 bytes += pixie::vector_capacity_bytes(internal_level_offsets_);
159 bytes += pixie::vector_capacity_bytes(min_summary_level_offsets_);
160 bytes += pixie::vector_capacity_bytes(high_level_offsets_);
161 bytes += pixie::vector_capacity_bytes(level_sizes_);
162 bytes += pixie::vector_capacity_bytes(level_position_spans_);
163 bytes += pixie::vector_capacity_bytes(level_fanouts_);
164 return bytes;
165 }
166
173 std::size_t arg_min(std::size_t left, std::size_t right) const {
174 if (left >= right || right > depth_count_ || level_sizes_.empty()) {
175 return npos;
176 }
177 if (left + 1 == right) {
178 return left;
179 }
180
181 const std::size_t root_level = level_count() - 1;
182 if (left == 0 && right == depth_count_) {
183 return subtree_min_candidate(root_level, 0).position;
184 }
185
186 const std::size_t left_leaf = leaf_for_position(left);
187 const std::size_t right_leaf = leaf_for_position(right - 1);
188 if (left_leaf == right_leaf) {
189 return leaf_range_arg_min_relative(left_leaf, left, right);
190 }
191
192 const auto [level, node] = covering_node(left_leaf, right_leaf);
193 return query_node(level, node, left, right).position;
194 }
195
204 std::size_t select0(std::size_t rank) const {
205 if (rank == 0 || depth_count_ == 0 || rank_index_or_null() == nullptr) {
206 return npos;
207 }
208 const std::size_t delta_count = depth_count_ - 1;
209 const std::size_t position = rank_index().select0(rank);
210 return position < delta_count ? position : npos;
211 }
212
213 private:
214 static constexpr std::size_t kLeafWords = LeafSize / 64;
215 static constexpr std::size_t kLeafChunks = LeafSize / 128;
216 static constexpr std::size_t kSelectorEntries = 256;
217 static constexpr std::size_t kSelectorBits = 2 * kSelectorEntries;
218 static constexpr std::size_t kSelectorWords = kSelectorBits / 64;
219 static constexpr std::size_t kEmbeddedSummaryWords = 2;
220 static constexpr std::size_t kEmbeddedSummaryBits =
221 64 * kEmbeddedSummaryWords;
222 static constexpr std::size_t kEmbeddedSummaryMaxEntries =
223 (kSelectorBits - kEmbeddedSummaryBits) / 2;
224 static constexpr std::size_t kEmbeddedSummaryPositionWord =
225 kSelectorWords - 2;
226 static constexpr std::size_t kEmbeddedSummaryDepthWord = kSelectorWords - 1;
227 static constexpr std::size_t kHighSparseTableLevels =
228 static_cast<std::size_t>(std::bit_width(kHighLevelFanout));
229 static constexpr std::size_t kHighSparseSlotsPerNode =
230 kHighSparseTableLevels * kHighLevelFanout;
231 static constexpr bool kInvalidIndexEqualsNpos =
232 static_cast<std::size_t>(invalid_index) == npos;
233 static_assert(kEmbeddedSummaryMaxEntries == 192);
234 static_assert(kMiddleFanout == kEmbeddedSummaryMaxEntries);
235 static_assert(sizeof(std::size_t) <= sizeof(std::uint64_t));
236
237 struct DepthCandidate {
238 std::size_t position = npos;
239 std::int64_t depth = std::numeric_limits<std::int64_t>::max();
240 };
241
242 struct HighChildMetadata {
243 std::size_t position_begin = 0;
244 std::size_t position_end = 0;
245 Index min_position = invalid_index;
246 std::int64_t min_depth = std::numeric_limits<std::int64_t>::max();
247 };
248
249 class alignas(64) Bp512Selector {
250 public:
254 Bp512Selector() = default;
255
263 template <class EntryLess>
264 void build(std::size_t entry_count, EntryLess entry_less) {
265 if (entry_count > kSelectorEntries) {
266 throw std::length_error(
267 "HybridBTreePlusMinusOne local selector too large");
268 }
269
270 bp_bits_.fill(0);
271 if (entry_count == 0) {
272 return;
273 }
274
275 std::array<std::uint16_t, kSelectorEntries> stack{};
276 std::size_t stack_size = 0;
277 std::size_t write_position = 2 * entry_count;
278
279 for (std::size_t i = entry_count; i-- > 0;) {
280 while (stack_size != 0 && !entry_less(stack[stack_size - 1], i)) {
281 --stack_size;
282 prepend_bp_bit(write_position, true);
283 }
284 stack[stack_size++] = static_cast<std::uint16_t>(i);
285 prepend_bp_bit(write_position, false);
286 }
287
288 while (write_position != 0) {
289 prepend_bp_bit(write_position, true);
290 }
291 }
292
296 std::size_t arg_min(std::size_t slot_left,
297 std::size_t slot_right,
298 std::size_t entry_count) const {
299 if (slot_left >= slot_right || slot_right > entry_count ||
300 entry_count > kSelectorEntries) {
301 return npos;
302 }
303 if (slot_left + 1 == slot_right) {
304 return slot_left;
305 }
306
307 const std::size_t bit_count = 2 * entry_count;
308 const std::size_t first_close = close_position(slot_left);
309 const std::size_t last_close = close_position(slot_right - 1);
310 if (first_close > last_close || last_close >= bit_count) {
311 return npos;
312 }
313
314 const std::size_t shifted_min =
315 depth_arg_min(first_close + 1, last_close + 2, bit_count);
316 if (shifted_min == npos || shifted_min == 0) {
317 return npos;
318 }
319
320 const std::size_t zero_rank = rank0_at(shifted_min, bit_count);
321 if (zero_rank == 0) {
322 return npos;
323 }
324 const std::size_t entry = zero_rank - 1;
325 return entry < entry_count ? entry : npos;
326 }
327
336 void set_embedded_min_summary(std::size_t position, std::int64_t depth) {
337 bp_bits_[kEmbeddedSummaryPositionWord] =
338 static_cast<std::uint64_t>(position);
339 bp_bits_[kEmbeddedSummaryDepthWord] = std::bit_cast<std::uint64_t>(depth);
340 }
341
345 std::size_t embedded_min_position() const {
346 return static_cast<std::size_t>(bp_bits_[kEmbeddedSummaryPositionWord]);
347 }
348
352 std::int64_t embedded_min_depth() const {
353 return std::bit_cast<std::int64_t>(bp_bits_[kEmbeddedSummaryDepthWord]);
354 }
355
356 private:
360 void prepend_bp_bit(std::size_t& write_position, bool bit) {
361 --write_position;
362 if (bit) {
363 bp_bits_[write_position >> 6] |= std::uint64_t{1}
364 << (write_position & 63);
365 }
366 }
367
371 std::size_t close_position(std::size_t slot) const {
372 return select0_512(bp_bits_.data(), slot);
373 }
374
378 std::size_t rank0_at(std::size_t position, std::size_t bit_count) const {
379 position = std::min(position, bit_count);
380 return position - rank_512(bp_bits_.data(), position);
381 }
382
386 int prefix_excess(std::size_t position) const {
387 position = std::min(position, kSelectorBits);
388 const std::size_t ones = rank_512(bp_bits_.data(), position);
389 return static_cast<int>(2 * ones) - static_cast<int>(position);
390 }
391
395 std::size_t depth_arg_min(std::size_t left,
396 std::size_t right,
397 std::size_t bit_count) const {
398 const std::size_t depth_count = bit_count + 1;
399 if (left >= right || right > depth_count) {
400 return npos;
401 }
402
403 std::size_t position = left;
404 int best_depth = prefix_excess(position);
405 std::size_t best_position = position;
406
407 while (position < right) {
408 const std::size_t chunk_begin = (position / 128) * 128;
409 const std::size_t local_left = position - chunk_begin;
410 const std::size_t local_right =
411 std::min<std::size_t>(right - 1, chunk_begin + 128) - chunk_begin;
412
413 int candidate_depth;
414 std::size_t candidate_position;
415 if (chunk_begin >= bit_count) {
416 candidate_depth = prefix_excess(bit_count);
417 candidate_position = bit_count;
418 } else {
419 const std::size_t word = chunk_begin >> 6;
420 const ExcessResult result =
421 excess_min_128(bp_bits_.data() + word, local_left, local_right);
422 candidate_depth = prefix_excess(chunk_begin) + result.min_excess;
423 candidate_position = chunk_begin + result.offset;
424 }
425
426 if (candidate_depth < best_depth) {
427 best_depth = candidate_depth;
428 best_position = candidate_position;
429 }
430
431 position = chunk_begin + local_right + 1;
432 }
433
434 return best_position;
435 }
436
437 std::array<std::uint64_t, kSelectorWords> bp_bits_{};
438 };
439
440 static_assert(sizeof(Bp512Selector) == 64);
441
445 bool missing_position(std::size_t position) const {
446 if constexpr (kInvalidIndexEqualsNpos) {
447 return position == npos;
448 } else {
449 return position == npos ||
450 position == static_cast<std::size_t>(invalid_index);
451 }
452 }
453
457 DepthCandidate better_candidate(DepthCandidate left,
458 DepthCandidate right) const {
459 if (missing_position(left.position)) {
460 return right;
461 }
462 if (missing_position(right.position)) {
463 return left;
464 }
465 if (right.depth < left.depth) {
466 return right;
467 }
468 if (left.depth < right.depth) {
469 return left;
470 }
471 return right.position < left.position ? right : left;
472 }
473
477 bool strictly_better_candidate(DepthCandidate left,
478 DepthCandidate right) const {
479 if (missing_position(left.position)) {
480 return false;
481 }
482 if (missing_position(right.position)) {
483 return true;
484 }
485 if (left.depth != right.depth) {
486 return left.depth < right.depth;
487 }
488 return left.position < right.position;
489 }
490
494 void build() {
495 owned_rank_index_.reset();
496 internal_selectors_.clear();
497 internal_min_positions_.clear();
498 internal_min_depths_.clear();
499 high_child_metadata_.clear();
500 high_sparse_min_slots_.clear();
501 internal_level_offsets_.clear();
502 min_summary_level_offsets_.clear();
503 high_level_offsets_.clear();
504 level_sizes_.clear();
505 level_position_spans_.clear();
506 level_fanouts_.clear();
507 high_level_begin_ = std::numeric_limits<std::size_t>::max();
508
509 if (depth_count_ == 0) {
510 return;
511 }
512 if (depth_count_ > static_cast<std::size_t>(invalid_index)) {
513 throw std::length_error(
514 "HybridBTreePlusMinusOne index type is too small");
515 }
516 if (depth_count_ >
517 static_cast<std::size_t>(std::numeric_limits<std::int64_t>::max())) {
518 throw std::length_error(
519 "HybridBTreePlusMinusOne depth range is too large");
520 }
521 if (input_bits_.size() < (depth_count_ - 1 + 63) / 64) {
522 throw std::invalid_argument(
523 "HybridBTreePlusMinusOne bit span is too small");
524 }
525
526 const std::size_t delta_count = depth_count_ - 1;
527 if (external_rank_index_ == nullptr) {
528 owned_rank_index_.emplace(input_bits_, delta_count,
529 RankSelectSupport<>::SelectSupport::kSelect0);
530 } else if (external_rank_index_->size() < delta_count) {
531 throw std::invalid_argument(
532 "HybridBTreePlusMinusOne external rank index is too small");
533 }
534
535 initialize_layout((depth_count_ + LeafSize - 1) / LeafSize);
536 for (std::size_t level = 1; level < level_count(); ++level) {
537 for (std::size_t node = 0; node < level_sizes_[level]; ++node) {
538 build_internal_node(level, node);
539 }
540 }
541 }
542
546 void initialize_layout(std::size_t leaf_count) {
547 level_sizes_.push_back(leaf_count);
548 level_position_spans_.push_back(LeafSize);
549 level_fanouts_.push_back(0);
550
551 std::size_t current_count = leaf_count;
552 std::size_t current_span = LeafSize;
553 while (current_count > kHighLevelFanout * kHighLevelFanout) {
554 level_fanouts_.push_back(kMiddleFanout);
555 current_count = ceil_div(current_count, kMiddleFanout);
556 current_span = saturating_product(current_span, kMiddleFanout);
557 level_sizes_.push_back(current_count);
558 level_position_spans_.push_back(current_span);
559 }
560 while (current_count > 1) {
561 level_fanouts_.push_back(kHighLevelFanout);
562 current_count = ceil_div(current_count, kHighLevelFanout);
563 current_span = saturating_product(current_span, kHighLevelFanout);
564 level_sizes_.push_back(current_count);
565 level_position_spans_.push_back(current_span);
566 }
567 internal_level_offsets_.assign(level_count(), 0);
568 min_summary_level_offsets_.assign(level_count(), npos);
569 high_level_offsets_.assign(level_count(), 0);
570 if (level_count() <= 1) {
571 return;
572 }
573
574 std::size_t internal_count = 0;
575 for (std::size_t level = 1; level < level_count(); ++level) {
576 internal_level_offsets_[level] = internal_count;
577 internal_count += level_sizes_[level];
578 }
579 internal_selectors_.resize(internal_count);
580
581 if constexpr (UseHighSparseLayout) {
582 const std::size_t root_level = level_count() - 1;
583 std::size_t high_layout_levels = 0;
584 for (std::size_t level = root_level;
585 level > 0 && high_layout_levels < HighSparseLayoutLevels &&
586 fanout_at_level(level) == kHighLevelFanout;
587 --level) {
588 ++high_layout_levels;
589 }
590 high_level_begin_ = high_layout_levels == 0
591 ? level_count()
592 : root_level + 1 - high_layout_levels;
593
594 std::size_t high_node_count = 0;
595 for (std::size_t level = high_level_begin_; level < level_count();
596 ++level) {
597 high_level_offsets_[level] = high_node_count;
598 high_node_count += level_sizes_[level];
599 }
600 high_child_metadata_.resize(high_node_count * kHighLevelFanout);
601 high_sparse_min_slots_.resize(high_node_count * kHighSparseSlotsPerNode);
602 } else {
603 high_level_begin_ = level_count();
604 }
605
606 std::size_t side_summary_count = 0;
607 for (std::size_t level = 1; level < level_count(); ++level) {
608 if (!level_embeds_min_summary(level)) {
609 min_summary_level_offsets_[level] = side_summary_count;
610 side_summary_count += level_sizes_[level];
611 }
612 }
613 internal_min_positions_.resize(side_summary_count, invalid_index);
614 internal_min_depths_.resize(side_summary_count,
615 std::numeric_limits<std::int64_t>::max());
616 }
617
621 void build_internal_node(std::size_t level, std::size_t node) {
622 const std::size_t count = entry_count(level, node);
623 const std::size_t first_child = node * fanout_at_level(level);
624 const bool high_level = is_high_level(level);
625 const std::size_t high_flat = high_level ? high_flat_index(level, node) : 0;
626
627 std::array<DepthCandidate, kSelectorEntries> child_minima{};
628 for (std::size_t slot = 0; slot < count; ++slot) {
629 child_minima[slot] = subtree_min_candidate(level - 1, first_child + slot);
630 }
631
632 if (high_level) {
633 for (std::size_t slot = 0; slot < count; ++slot) {
634 const std::size_t child = first_child + slot;
635 const DepthCandidate child_min = child_minima[slot];
636 HighChildMetadata& metadata =
637 mutable_high_child_metadata_at(high_flat, slot);
638 metadata.position_begin = node_position_begin(level - 1, child);
639 metadata.position_end = node_position_end(level - 1, child);
640 metadata.min_position = static_cast<Index>(child_min.position);
641 metadata.min_depth = child_min.depth;
642 }
643 build_high_sparse_min_slots(level, node, count);
644 }
645
646 Bp512Selector& selector = mutable_selector_at(level, node);
647 selector.build(count, [&](std::size_t left, std::size_t right) {
648 return strictly_better_candidate(child_minima[left], child_minima[right]);
649 });
650
651 const std::size_t slot = selector.arg_min(0, count, count);
652 const DepthCandidate minimum = child_minima[slot];
653 if (level_embeds_min_summary(level)) {
654 selector.set_embedded_min_summary(minimum.position, minimum.depth);
655 } else {
656 const std::size_t flat = min_summary_flat_index(level, node);
657 internal_min_positions_[flat] = static_cast<Index>(minimum.position);
658 internal_min_depths_[flat] = minimum.depth;
659 }
660 }
661
665 static std::size_t ceil_div(std::size_t value, std::size_t divisor) {
666 return (value + divisor - 1) / divisor;
667 }
668
672 static std::size_t saturating_product(std::size_t left, std::size_t right) {
673 if (left != 0 && right > std::numeric_limits<std::size_t>::max() / left) {
674 return std::numeric_limits<std::size_t>::max();
675 }
676 return left * right;
677 }
678
682 std::uint64_t word_or_zero(std::size_t word) const {
683 return word < input_bits_.size() ? input_bits_[word] : 0;
684 }
685
693 const std::uint64_t* chunk_words_or_copy(
694 std::size_t first_word,
695 std::array<std::uint64_t, 2>& storage) const {
696 if (first_word + 1 < input_bits_.size()) {
697 return input_bits_.data() + first_word;
698 }
699 storage[0] = word_or_zero(first_word);
700 storage[1] = word_or_zero(first_word + 1);
701 return storage.data();
702 }
703
707 const RankSelectSupport<>* rank_index_or_null() const {
708 return external_rank_index_ != nullptr
709 ? external_rank_index_
710 : (owned_rank_index_ ? &*owned_rank_index_ : nullptr);
711 }
712
716 const RankSelectSupport<>& rank_index() const {
717 return *rank_index_or_null();
718 }
719
723 std::int64_t depth_at_position(std::size_t position) const {
724 const std::size_t delta_count = depth_count_ == 0 ? 0 : depth_count_ - 1;
725 position = std::min(position, delta_count);
726 const std::uint64_t ones = rank_index().rank(position);
727 return static_cast<std::int64_t>(ones) -
728 static_cast<std::int64_t>(position - ones);
729 }
730
734 DepthCandidate scan_leaf_range_with_base(std::size_t leaf,
735 std::size_t left_offset,
736 std::size_t right_offset,
737 std::int64_t base_depth) const {
738 const std::size_t begin = node_position_begin(0, leaf);
739 const std::size_t count = entry_count(0, leaf);
740 if (count == 0 || left_offset > right_offset || left_offset >= count) {
741 return {};
742 }
743 right_offset = std::min(right_offset, count - 1);
744
745 DepthCandidate answer;
746 std::int64_t chunk_base_excess = 0;
747 const std::size_t first_word = leaf * kLeafWords;
748 for (std::size_t chunk = 0; chunk < kLeafChunks; ++chunk) {
749 const std::size_t chunk_begin = chunk * 128;
750 if (chunk_begin >= count || chunk_begin > right_offset) {
751 break;
752 }
753
754 std::array<std::uint64_t, 2> chunk_storage{};
755 const std::uint64_t* chunk_words =
756 chunk_words_or_copy(first_word + 2 * chunk, chunk_storage);
757
758 const std::size_t chunk_end =
759 std::min<std::size_t>(count - 1, chunk_begin + 127);
760 if (left_offset > chunk_end) {
761 chunk_base_excess += prefix_excess_128(chunk_words, 128);
762 continue;
763 }
764
765 const std::size_t local_left =
766 std::max(left_offset, chunk_begin) - chunk_begin;
767 const std::size_t local_right =
768 std::min(right_offset, chunk_end) - chunk_begin;
769 const ExcessResult result =
770 excess_min_128(chunk_words, local_left, local_right);
771 const std::size_t offset = chunk_begin + result.offset;
772 if (result.offset != npos && offset < count) {
773 answer = better_candidate(
774 answer, {begin + offset,
775 base_depth + chunk_base_excess + result.min_excess});
776 }
777
778 chunk_base_excess += prefix_excess_128(chunk_words, 128);
779 }
780 return answer;
781 }
782
790 std::size_t leaf_range_arg_min_relative(std::size_t leaf,
791 std::size_t left,
792 std::size_t right) const {
793 if (left >= right) {
794 return npos;
795 }
796
797 const std::size_t begin = node_position_begin(0, leaf);
798 const std::size_t count = entry_count(0, leaf);
799 std::size_t left_offset = left - begin;
800 std::size_t right_offset = right - begin - 1;
801 if (count == 0 || left_offset >= count || left_offset > right_offset) {
802 return npos;
803 }
804 right_offset = std::min(right_offset, count - 1);
805
806 std::size_t best_position = npos;
807 std::int64_t best_depth = std::numeric_limits<std::int64_t>::max();
808 std::int64_t chunk_base_excess = 0;
809 bool first_chunk = true;
810
811 const std::size_t first_word = leaf * kLeafWords;
812 std::size_t chunk = left_offset / 128;
813 for (; chunk < kLeafChunks; ++chunk) {
814 const std::size_t chunk_begin = chunk * 128;
815 if (chunk_begin >= count || chunk_begin > right_offset) {
816 break;
817 }
818
819 std::array<std::uint64_t, 2> chunk_storage{};
820 const std::uint64_t* chunk_words =
821 chunk_words_or_copy(first_word + 2 * chunk, chunk_storage);
822
823 const std::size_t chunk_end =
824 std::min<std::size_t>(count - 1, chunk_begin + 127);
825 const std::size_t local_left =
826 std::max(left_offset, chunk_begin) - chunk_begin;
827 const std::size_t local_right =
828 std::min(right_offset, chunk_end) - chunk_begin;
829 const int left_prefix =
830 first_chunk ? prefix_excess_128(chunk_words, local_left) : 0;
831 const std::int64_t local_base =
832 first_chunk ? -static_cast<std::int64_t>(left_prefix)
833 : chunk_base_excess;
834 const ExcessResult result =
835 excess_min_128(chunk_words, local_left, local_right);
836 const std::size_t offset = chunk_begin + result.offset;
837 if (result.offset != npos && offset < count) {
838 const std::int64_t candidate_depth = local_base + result.min_excess;
839 if (best_position == npos || candidate_depth < best_depth) {
840 best_position = begin + offset;
841 best_depth = candidate_depth;
842 }
843 }
844
845 const int chunk_excess = prefix_excess_128(chunk_words, 128);
846 if (first_chunk) {
847 chunk_base_excess =
848 static_cast<std::int64_t>(chunk_excess) - left_prefix;
849 first_chunk = false;
850 } else {
851 chunk_base_excess += chunk_excess;
852 }
853 }
854
855 return best_position;
856 }
857
861 DepthCandidate leaf_range_min(std::size_t leaf,
862 std::size_t left,
863 std::size_t right) const {
864 if (left >= right) {
865 return {};
866 }
867
868 const std::size_t begin = node_position_begin(0, leaf);
869 const std::size_t slot_left = left - begin;
870 const std::size_t slot_right = right - begin;
871 return scan_leaf_range_with_base(leaf, slot_left, slot_right - 1,
872 depth_at_position(begin));
873 }
874
878 std::pair<std::size_t, std::size_t> covering_node(
879 std::size_t left_leaf,
880 std::size_t right_leaf) const {
881 std::size_t level = 0;
882 std::size_t left_node = left_leaf;
883 std::size_t right_node = right_leaf;
884 while (left_node != right_node) {
885 ++level;
886 const std::size_t fanout = fanout_at_level(level);
887 left_node /= fanout;
888 right_node /= fanout;
889 }
890 return {level, left_node};
891 }
892
896 std::size_t leaf_for_position(std::size_t position) const {
897 return position / LeafSize;
898 }
899
903 std::size_t child_for_position(std::size_t child_level,
904 std::size_t position) const {
905 return position / level_position_spans_[child_level];
906 }
907
911 DepthCandidate query_child_slots(std::size_t level,
912 std::size_t node,
913 std::size_t slot_left,
914 std::size_t slot_right,
915 std::size_t left,
916 std::size_t right) const {
917 if (slot_left >= slot_right) {
918 return {};
919 }
920
921 const std::size_t count = entry_count(level, node);
922 const std::size_t slot =
923 slot_left + 1 == slot_right
924 ? slot_left
925 : selector_arg_min(level, node, slot_left, slot_right, count);
926 if (slot == npos) {
927 return {};
928 }
929
930 const std::size_t child_level = level - 1;
931 const std::size_t first_child = node * fanout_at_level(level);
932 const std::size_t child = first_child + slot;
933 const HighChildMetadata* high_children =
934 is_high_level(level) ? high_child_metadata_begin(level, node) : nullptr;
935 const DepthCandidate child_min =
936 high_children != nullptr ? high_child_min_candidate(level, node, slot)
937 : subtree_min_candidate(child_level, child);
938 const std::size_t child_begin =
939 high_children != nullptr ? high_children[slot].position_begin
940 : node_position_begin(child_level, child);
941 const std::size_t child_end = high_children != nullptr
942 ? high_children[slot].position_end
943 : node_position_end(child_level, child);
944 if ((left <= child_begin && child_end <= right) ||
945 contains_position(left, right, child_min.position)) {
946 return child_min;
947 }
948
949 const std::size_t last_slot = slot_right - 1;
950 const std::size_t left_child_begin =
951 high_children != nullptr
952 ? high_children[slot_left].position_begin
953 : node_position_begin(child_level, first_child + slot_left);
954 const std::size_t left_child_end =
955 high_children != nullptr
956 ? high_children[slot_left].position_end
957 : node_position_end(child_level, first_child + slot_left);
958 DepthCandidate answer = query_node(child_level, first_child + slot_left,
959 std::max(left, left_child_begin),
960 std::min(right, left_child_end));
961
962 if (slot_left != last_slot) {
963 const std::size_t right_child_begin =
964 high_children != nullptr
965 ? high_children[last_slot].position_begin
966 : node_position_begin(child_level, first_child + last_slot);
967 const std::size_t right_child_end =
968 high_children != nullptr
969 ? high_children[last_slot].position_end
970 : node_position_end(child_level, first_child + last_slot);
971 answer = better_candidate(answer,
972 query_node(child_level, first_child + last_slot,
973 std::max(left, right_child_begin),
974 std::min(right, right_child_end)));
975 }
976
977 if (slot_left + 1 < last_slot) {
978 answer = better_candidate(
979 answer,
980 full_child_slot_range_min(level, node, slot_left + 1, last_slot));
981 }
982
983 return answer;
984 }
985
989 DepthCandidate full_child_slot_range_min(std::size_t level,
990 std::size_t node,
991 std::size_t slot_left,
992 std::size_t slot_right) const {
993 if (slot_left >= slot_right) {
994 return {};
995 }
996
997 const std::size_t slot =
998 slot_left + 1 == slot_right
999 ? slot_left
1000 : selector_arg_min(level, node, slot_left, slot_right,
1001 entry_count(level, node));
1002 if (slot == npos) {
1003 return {};
1004 }
1005 return child_min_candidate(level, node, slot);
1006 }
1007
1011 DepthCandidate query_node(std::size_t level,
1012 std::size_t node,
1013 std::size_t left,
1014 std::size_t right) const {
1015 if (left >= right) {
1016 return {};
1017 }
1018
1019 const std::size_t begin = node_position_begin(level, node);
1020 const std::size_t end = node_position_end(level, node);
1021 if (left <= begin && end <= right) {
1022 return subtree_min_candidate(level, node);
1023 }
1024 if (level == 0) {
1025 return leaf_range_min(node, left, right);
1026 }
1027
1028 const std::size_t child_level = level - 1;
1029 const std::size_t left_child = child_for_position(child_level, left);
1030 const std::size_t right_child = child_for_position(child_level, right - 1);
1031 const std::size_t first_child = node * fanout_at_level(level);
1032 const std::size_t left_slot = left_child - first_child;
1033 const std::size_t right_slot = right_child - first_child + 1;
1034 return query_child_slots(level, node, left_slot, right_slot, left, right);
1035 }
1036
1040 bool contains_position(std::size_t left,
1041 std::size_t right,
1042 std::size_t position) const {
1043 return !missing_position(position) && left <= position && position < right;
1044 }
1045
1049 std::size_t level_count() const { return level_sizes_.size(); }
1050
1054 std::size_t entry_count(std::size_t level, std::size_t node) const {
1055 if (level == 0) {
1056 const std::size_t begin = node_position_begin(0, node);
1057 return std::min<std::size_t>(LeafSize, depth_count_ - begin);
1058 }
1059 const std::size_t first_child = node * fanout_at_level(level);
1060 return std::min<std::size_t>(fanout_at_level(level),
1061 level_sizes_[level - 1] - first_child);
1062 }
1063
1067 std::size_t node_position_begin(std::size_t level, std::size_t node) const {
1068 return node * level_position_spans_[level];
1069 }
1070
1074 std::size_t node_position_end(std::size_t level, std::size_t node) const {
1075 return std::min(depth_count_, node_position_begin(level, node) +
1076 level_position_spans_[level]);
1077 }
1078
1082 DepthCandidate subtree_min_candidate(std::size_t level,
1083 std::size_t node) const {
1084 if (level == 0) {
1085 return leaf_range_min(node, node_position_begin(0, node),
1086 node_position_end(0, node));
1087 }
1088 if (level_embeds_min_summary(level)) {
1089 const Bp512Selector& selector = selector_at(level, node);
1090 return {selector.embedded_min_position(), selector.embedded_min_depth()};
1091 }
1092 const std::size_t flat = min_summary_flat_index(level, node);
1093 return {static_cast<std::size_t>(internal_min_positions_[flat]),
1094 internal_min_depths_[flat]};
1095 }
1096
1100 DepthCandidate child_min_candidate(std::size_t level,
1101 std::size_t node,
1102 std::size_t slot) const {
1103 if (is_high_level(level)) {
1104 return high_child_min_candidate(level, node, slot);
1105 }
1106 return subtree_min_candidate(level - 1,
1107 node * fanout_at_level(level) + slot);
1108 }
1109
1113 DepthCandidate high_child_min_candidate(std::size_t level,
1114 std::size_t node,
1115 std::size_t slot) const {
1116 const HighChildMetadata& metadata =
1117 high_child_metadata_at(high_flat_index(level, node), slot);
1118 return {static_cast<std::size_t>(metadata.min_position),
1119 metadata.min_depth};
1120 }
1121
1125 const Bp512Selector& selector_at(std::size_t level, std::size_t node) const {
1126 return internal_selectors_[internal_flat_index(level, node)];
1127 }
1128
1132 Bp512Selector& mutable_selector_at(std::size_t level, std::size_t node) {
1133 return internal_selectors_[internal_flat_index(level, node)];
1134 }
1135
1139 std::size_t selector_arg_min(std::size_t level,
1140 std::size_t node,
1141 std::size_t slot_left,
1142 std::size_t slot_right,
1143 std::size_t count) const {
1144 if constexpr (UseHighSparseLayout) {
1145 if (is_high_level(level)) {
1146 return high_sparse_arg_min(level, node, slot_left, slot_right, count);
1147 }
1148 }
1149 return selector_at(level, node).arg_min(slot_left, slot_right, count);
1150 }
1151
1155 bool is_high_level(std::size_t level) const {
1156 if constexpr (!UseHighSparseLayout) {
1157 (void)level;
1158 return false;
1159 }
1160 return level > 0 && level >= high_level_begin_ && level < level_count();
1161 }
1162
1166 std::size_t internal_flat_index(std::size_t level, std::size_t node) const {
1167 return internal_level_offsets_[level] + node;
1168 }
1169
1173 bool level_embeds_min_summary(std::size_t level) const {
1174 return level > 0 && !is_high_level(level) &&
1175 fanout_at_level(level) <= kEmbeddedSummaryMaxEntries;
1176 }
1177
1181 std::size_t min_summary_flat_index(std::size_t level,
1182 std::size_t node) const {
1183 return min_summary_level_offsets_[level] + node;
1184 }
1185
1189 std::size_t fanout_at_level(std::size_t level) const {
1190 return level_fanouts_[level];
1191 }
1192
1196 std::size_t high_flat_index(std::size_t level, std::size_t node) const {
1197 return high_level_offsets_[level] + node;
1198 }
1199
1203 const HighChildMetadata* high_child_metadata_begin(std::size_t level,
1204 std::size_t node) const {
1205 return high_child_metadata_.data() +
1206 high_flat_index(level, node) * kHighLevelFanout;
1207 }
1208
1212 const HighChildMetadata& high_child_metadata_at(std::size_t high_flat,
1213 std::size_t slot) const {
1214 return high_child_metadata_[high_flat * kHighLevelFanout + slot];
1215 }
1216
1220 HighChildMetadata& mutable_high_child_metadata_at(std::size_t high_flat,
1221 std::size_t slot) {
1222 return high_child_metadata_[high_flat * kHighLevelFanout + slot];
1223 }
1224
1228 std::uint8_t* mutable_high_sparse_min_slots_begin(std::size_t high_flat) {
1229 return high_sparse_min_slots_.data() + high_flat * kHighSparseSlotsPerNode;
1230 }
1231
1235 const std::uint8_t* high_sparse_min_slots_begin(std::size_t high_flat) const {
1236 return high_sparse_min_slots_.data() + high_flat * kHighSparseSlotsPerNode;
1237 }
1238
1242 std::size_t better_high_child_slot(std::size_t level,
1243 std::size_t node,
1244 std::size_t left_slot,
1245 std::size_t right_slot) const {
1246 const DepthCandidate left =
1247 high_child_min_candidate(level, node, left_slot);
1248 const DepthCandidate right =
1249 high_child_min_candidate(level, node, right_slot);
1250 return better_candidate(left, right).position == right.position ? right_slot
1251 : left_slot;
1252 }
1253
1257 void build_high_sparse_min_slots(std::size_t level,
1258 std::size_t node,
1259 std::size_t count) {
1260 const std::size_t high_flat = high_flat_index(level, node);
1261 std::uint8_t* table = mutable_high_sparse_min_slots_begin(high_flat);
1262 for (std::size_t slot = 0; slot < count; ++slot) {
1263 table[slot] = static_cast<std::uint8_t>(slot);
1264 }
1265
1266 for (std::size_t table_level = 1; table_level < kHighSparseTableLevels;
1267 ++table_level) {
1268 const std::size_t span = std::size_t{1} << table_level;
1269 if (span > count) {
1270 break;
1271 }
1272 const std::size_t half_span = span >> 1;
1273 const std::uint8_t* previous =
1274 table + (table_level - 1) * kHighLevelFanout;
1275 std::uint8_t* current = table + table_level * kHighLevelFanout;
1276 for (std::size_t slot = 0; slot + span <= count; ++slot) {
1277 current[slot] = static_cast<std::uint8_t>(better_high_child_slot(
1278 level, node, previous[slot], previous[slot + half_span]));
1279 }
1280 }
1281 }
1282
1286 std::size_t high_sparse_arg_min(std::size_t level,
1287 std::size_t node,
1288 std::size_t slot_left,
1289 std::size_t slot_right,
1290 std::size_t count) const {
1291 if (slot_left >= slot_right || slot_right > count) {
1292 return npos;
1293 }
1294 const std::size_t length = slot_right - slot_left;
1295 if (length == 1) {
1296 return slot_left;
1297 }
1298
1299 const std::size_t high_flat = high_flat_index(level, node);
1300 const std::size_t table_level = std::bit_width(length) - 1;
1301 const std::size_t span = std::size_t{1} << table_level;
1302 const std::uint8_t* table =
1303 high_sparse_min_slots_begin(high_flat) + table_level * kHighLevelFanout;
1304 return better_high_child_slot(level, node, table[slot_left],
1305 table[slot_right - span]);
1306 }
1307
1308 std::span<const std::uint64_t> input_bits_;
1309 std::size_t depth_count_ = 0;
1310 std::optional<RankSelectSupport<>> owned_rank_index_;
1311 const RankSelectSupport<>* external_rank_index_ = nullptr;
1312 std::vector<Bp512Selector> internal_selectors_;
1313 std::vector<Index> internal_min_positions_;
1314 std::vector<std::int64_t> internal_min_depths_;
1315 std::vector<HighChildMetadata> high_child_metadata_;
1316 std::vector<std::uint8_t> high_sparse_min_slots_;
1317 std::vector<std::size_t> internal_level_offsets_;
1318 std::vector<std::size_t> min_summary_level_offsets_;
1319 std::vector<std::size_t> high_level_offsets_;
1320 std::vector<std::size_t> level_sizes_;
1321 std::vector<std::size_t> level_position_spans_;
1322 std::vector<std::size_t> level_fanouts_;
1323 std::size_t high_level_begin_ = std::numeric_limits<std::size_t>::max();
1324};
1325
1326} // namespace detail
1327
1348template <class T,
1349 class Compare = std::less<T>,
1350 class Index = std::size_t,
1351 std::size_t LeafSize = 512,
1352 bool UseTopSparseOverlay = true>
1354 : public RmqBase<CartesianHybridBTree<T,
1355 Compare,
1356 Index,
1357 LeafSize,
1358 UseTopSparseOverlay>,
1359 T> {
1360 public:
1361 static_assert(std::is_unsigned_v<Index>,
1362 "CartesianHybridBTree index type must be unsigned");
1363 static_assert(LeafSize != 0 && LeafSize % 512 == 0,
1364 "CartesianHybridBTree leaf size must be a positive "
1365 "multiple of 512");
1366
1367 using Self =
1369
1370 static constexpr std::size_t npos = RmqBase<Self, T>::npos;
1371 static constexpr Index invalid_index = std::numeric_limits<Index>::max();
1372 static constexpr std::size_t kMinTopSparseBlockSize = 4096;
1373 static constexpr std::size_t kMaxTopSparseBlocks = std::size_t{1} << 14;
1374 static constexpr bool kUseTopSparseOverlay = UseTopSparseOverlay;
1375
1380
1387 explicit CartesianHybridBTree(std::span<const T> values,
1388 Compare compare = Compare())
1389 : values_(values), compare_(compare) {
1390 build();
1391 }
1392
1397 : values_(other.values_),
1398 compare_(other.compare_),
1399 bp_bits_(other.bp_bits_),
1400 bp_bit_count_(other.bp_bit_count_),
1401 top_sparse_candidates_(other.top_sparse_candidates_),
1402 top_block_size_(other.top_block_size_),
1403 top_block_count_(other.top_block_count_),
1404 top_sparse_levels_(other.top_sparse_levels_) {
1405 reset_bp_indexes();
1406 }
1407
1412 if (this == &other) {
1413 return *this;
1414 }
1415 values_ = other.values_;
1416 compare_ = other.compare_;
1417 bp_bits_ = other.bp_bits_;
1418 bp_bit_count_ = other.bp_bit_count_;
1419 top_sparse_candidates_ = other.top_sparse_candidates_;
1420 top_block_size_ = other.top_block_size_;
1421 top_block_count_ = other.top_block_count_;
1422 top_sparse_levels_ = other.top_sparse_levels_;
1423 reset_bp_indexes();
1424 return *this;
1425 }
1426
1431 : values_(other.values_),
1432 compare_(std::move(other.compare_)),
1433 bp_bits_(std::move(other.bp_bits_)),
1434 bp_bit_count_(other.bp_bit_count_),
1435 top_sparse_candidates_(std::move(other.top_sparse_candidates_)),
1436 top_block_size_(other.top_block_size_),
1437 top_block_count_(other.top_block_count_),
1438 top_sparse_levels_(other.top_sparse_levels_) {
1439 other.values_ = std::span<const T>();
1440 other.bp_bit_count_ = 0;
1441 other.top_block_size_ = kMinTopSparseBlockSize;
1442 other.top_block_count_ = 0;
1443 other.top_sparse_levels_ = 0;
1444 reset_bp_indexes();
1445 }
1446
1451 if (this == &other) {
1452 return *this;
1453 }
1454 values_ = other.values_;
1455 compare_ = std::move(other.compare_);
1456 bp_bits_ = std::move(other.bp_bits_);
1457 bp_bit_count_ = other.bp_bit_count_;
1458 top_sparse_candidates_ = std::move(other.top_sparse_candidates_);
1459 top_block_size_ = other.top_block_size_;
1460 top_block_count_ = other.top_block_count_;
1461 top_sparse_levels_ = other.top_sparse_levels_;
1462 other.values_ = std::span<const T>();
1463 other.bp_bit_count_ = 0;
1464 other.top_block_size_ = kMinTopSparseBlockSize;
1465 other.top_block_count_ = 0;
1466 other.top_sparse_levels_ = 0;
1467 reset_bp_indexes();
1468 return *this;
1469 }
1470
1474 std::size_t size_impl() const { return values_.size(); }
1475
1479 T value_at_impl(std::size_t position) const { return values_[position]; }
1480
1484 std::size_t arg_min_impl(std::size_t left, std::size_t right) const {
1485 if (left >= right || right > values_.size()) {
1486 return npos;
1487 }
1488 if constexpr (!UseTopSparseOverlay) {
1489 return cartesian_arg_min(left, right);
1490 }
1491 if (right - left <= top_block_size_) {
1492 return cartesian_arg_min(left, right);
1493 }
1494 const std::size_t top_answer = top_sparse_arg_min(left, right);
1495 if (top_answer != npos) {
1496 return top_answer;
1497 }
1498 return cartesian_arg_min(left, right);
1499 }
1500
1504 std::size_t bp_bit_count() const { return bp_bit_count_; }
1505
1509 std::span<const std::uint64_t> bp_words() const {
1510 return bp_storage_words().first(bp_word_count());
1511 }
1512
1516 static std::size_t top_sparse_block_size_for(std::size_t value_count) {
1517 if (value_count == 0) {
1518 return kMinTopSparseBlockSize;
1519 }
1520 return std::max(kMinTopSparseBlockSize,
1521 ceil_div(value_count, kMaxTopSparseBlocks));
1522 }
1523
1527 static std::size_t top_sparse_block_count_for(std::size_t value_count) {
1528 if (value_count == 0) {
1529 return 0;
1530 }
1531 return ceil_div(value_count, top_sparse_block_size_for(value_count));
1532 }
1533
1537 std::size_t top_sparse_block_size() const { return top_block_size_; }
1538
1542 std::size_t top_sparse_block_count() const { return top_block_count_; }
1543
1551 std::size_t memory_usage_bytes_impl() const {
1552 return sizeof(*this) + bp_bits_.allocated_bytes() +
1553 pixie::vector_capacity_bytes(top_sparse_candidates_) +
1554 pixie::optional_nested_owned_memory_bytes(bp_index_) +
1555 pixie::nested_owned_memory_bytes(bp_depth_rmq_);
1556 }
1557
1558 private:
1560
1561 struct TopCandidate {
1562 Index position = invalid_index;
1563 };
1564 static_assert(sizeof(TopCandidate) == sizeof(Index));
1565
1570 std::size_t cartesian_arg_min(std::size_t left, std::size_t right) const {
1571 const std::size_t first_close = select_close_position(left + 1);
1572 const std::size_t last_close = select_close_position(right);
1573 if (first_close == npos || last_close == npos || first_close > last_close) {
1574 return npos;
1575 }
1576
1577 const std::size_t shifted_min =
1578 bp_depth_rmq_.arg_min(first_close + 1, last_close + 2);
1579 if (shifted_min == npos || shifted_min == 0) {
1580 return npos;
1581 }
1582 const RankSelectSupport<>& bp_index = *bp_index_;
1583 const std::size_t answer = bp_index.rank0(shifted_min) - 1;
1584 return answer < values_.size() ? answer : npos;
1585 }
1586
1590 void build() {
1591 bp_bits_.resize(0);
1592 bp_bit_count_ = 0;
1593 top_sparse_candidates_.clear();
1594 top_block_size_ = kMinTopSparseBlockSize;
1595 top_block_count_ = 0;
1596 top_sparse_levels_ = 0;
1597 reset_bp_indexes();
1598
1599 if (values_.empty()) {
1600 return;
1601 }
1602 if (values_.size() > (static_cast<std::size_t>(invalid_index) - 1) / 2) {
1603 throw std::length_error(
1604 "CartesianHybridBTree RMQ index type is too small");
1605 }
1606
1607 bp_bit_count_ = 2 * values_.size();
1608 bp_bits_.resize(padded_bp_bit_capacity());
1609 std::ranges::fill(bp_storage_words(), std::uint64_t{0});
1610 build_bp_bits();
1611 if constexpr (UseTopSparseOverlay) {
1612 build_top_sparse_table();
1613 }
1614 reset_bp_indexes();
1615 }
1616
1620 void build_bp_bits() {
1621 utils::SuccinctIncreasingStack stack(values_.size());
1622 std::size_t write_position = bp_bit_count_;
1623 std::span<std::uint64_t> words = bp_storage_words();
1624 const auto prepend_open = [&]() {
1625 --write_position;
1626 words[write_position >> 6] |= std::uint64_t{1} << (write_position & 63);
1627 };
1628
1629 for (std::size_t i = values_.size(); i-- > 0;) {
1630 while (!stack.empty() &&
1631 !compare_(values_[stack_index(stack.top())], values_[i])) {
1632 stack.pop();
1633 prepend_open();
1634 }
1635 stack.push(stack_key(i));
1636 --write_position;
1637 }
1638
1639 while (write_position != 0) {
1640 prepend_open();
1641 }
1642 }
1643
1648 std::size_t stack_key(std::size_t value_index) const {
1649 return values_.size() - 1 - value_index;
1650 }
1651
1656 std::size_t stack_index(std::size_t key) const {
1657 return values_.size() - 1 - key;
1658 }
1659
1663 void reset_bp_indexes() {
1664 bp_index_.reset();
1665 bp_depth_rmq_ = BpDepthRmq();
1666 if (bp_bit_count_ == 0) {
1667 return;
1668 }
1669 const std::span<const std::uint64_t> words = bp_words();
1670 const std::span<const std::uint64_t> padded_words = bp_storage_words();
1671 // TODO: try incorporating rank/select information into the tree.
1672 bp_index_.emplace(words, bp_bit_count_,
1673 RankSelectSupport<>::SelectSupport::kSelect0,
1674 values_.size());
1675 bp_depth_rmq_ = BpDepthRmq(padded_words, bp_bit_count_ + 1, *bp_index_);
1676 }
1677
1681 std::size_t bp_word_count() const { return ceil_div(bp_bit_count_, 64); }
1682
1686 std::size_t padded_bp_bit_capacity() const {
1687 if (bp_bit_count_ == 0) {
1688 return 0;
1689 }
1690 const std::size_t depth_count = bp_bit_count_ + 1;
1691 return ceil_div(depth_count, LeafSize) * LeafSize;
1692 }
1693
1697 std::span<std::uint64_t> bp_storage_words() {
1698 return bp_bits_.writable_words64();
1699 }
1700
1704 std::span<const std::uint64_t> bp_storage_words() const {
1705 return bp_bits_.as_words64();
1706 }
1707
1711 void build_top_sparse_table() {
1712 top_sparse_candidates_.clear();
1713 top_block_size_ = top_sparse_block_size_for(values_.size());
1714 top_block_count_ = ceil_div(values_.size(), top_block_size_);
1715 top_sparse_levels_ =
1716 top_block_count_ == 0 ? 0 : std::bit_width(top_block_count_);
1717 if (top_block_count_ == 0) {
1718 return;
1719 }
1720
1721 top_sparse_candidates_.assign(top_sparse_levels_ * top_block_count_,
1722 TopCandidate{});
1723 for (std::size_t block = 0; block < top_block_count_; ++block) {
1724 const std::size_t begin = block * top_block_size_;
1725 const std::size_t end = std::min(values_.size(), begin + top_block_size_);
1726 std::size_t minimum = begin;
1727 for (std::size_t position = begin + 1; position < end; ++position) {
1728 if (strictly_better_value_position(position, minimum)) {
1729 minimum = position;
1730 }
1731 }
1732 top_sparse_candidates_[block] = make_top_candidate(minimum);
1733 }
1734
1735 for (std::size_t level = 1; level < top_sparse_levels_; ++level) {
1736 const std::size_t span = std::size_t{1} << level;
1737 const std::size_t half_span = span >> 1;
1738 TopCandidate* current =
1739 top_sparse_candidates_.data() + level * top_block_count_;
1740 const TopCandidate* previous =
1741 top_sparse_candidates_.data() + (level - 1) * top_block_count_;
1742 for (std::size_t block = 0; block + span <= top_block_count_; ++block) {
1743 current[block] =
1744 better_top_candidate(previous[block], previous[block + half_span]);
1745 }
1746 }
1747 }
1748
1752 static std::size_t ceil_div(std::size_t value, std::size_t divisor) {
1753 return value == 0 ? 0 : 1 + (value - 1) / divisor;
1754 }
1755
1759 TopCandidate make_top_candidate(std::size_t position) const {
1760 if (position >= values_.size()) {
1761 return {};
1762 }
1763 return {static_cast<Index>(position)};
1764 }
1765
1769 bool valid_value_position(std::size_t position) const {
1770 return position != npos &&
1771 position != static_cast<std::size_t>(invalid_index) &&
1772 position < values_.size();
1773 }
1774
1779 bool strictly_better_value_position(std::size_t left,
1780 std::size_t right) const {
1781 if (!valid_value_position(left)) {
1782 return false;
1783 }
1784 if (!valid_value_position(right)) {
1785 return true;
1786 }
1787 if (compare_(values_[left], values_[right])) {
1788 return true;
1789 }
1790 if (compare_(values_[right], values_[left])) {
1791 return false;
1792 }
1793 return left < right;
1794 }
1795
1799 TopCandidate better_top_candidate(TopCandidate left,
1800 TopCandidate right) const {
1801 const std::size_t left_position = static_cast<std::size_t>(left.position);
1802 const std::size_t right_position = static_cast<std::size_t>(right.position);
1803 return strictly_better_value_position(right_position, left_position) ? right
1804 : left;
1805 }
1806
1810 TopCandidate top_sparse_block_arg_min(std::size_t block_left,
1811 std::size_t block_right) const {
1812 if (block_left >= block_right || block_right > top_block_count_ ||
1813 top_sparse_levels_ == 0) {
1814 return {};
1815 }
1816 const std::size_t length = block_right - block_left;
1817 const std::size_t level = std::bit_width(length) - 1;
1818 const std::size_t span = std::size_t{1} << level;
1819 const TopCandidate* table =
1820 top_sparse_candidates_.data() + level * top_block_count_;
1821 return better_top_candidate(table[block_left], table[block_right - span]);
1822 }
1823
1827 bool top_candidate_inside(TopCandidate candidate,
1828 std::size_t left,
1829 std::size_t right) const {
1830 const std::size_t position = static_cast<std::size_t>(candidate.position);
1831 return valid_value_position(position) && left <= position &&
1832 position < right;
1833 }
1834
1839 std::size_t top_sparse_arg_min(std::size_t left, std::size_t right) const {
1840 if (top_block_count_ <= 1) {
1841 return npos;
1842 }
1843
1844 const std::size_t padded_block_left = left / top_block_size_;
1845 const std::size_t padded_block_right = (right - 1) / top_block_size_ + 1;
1846 if (padded_block_left + 1 >= padded_block_right) {
1847 return npos;
1848 }
1849
1850 const TopCandidate padded =
1851 top_sparse_block_arg_min(padded_block_left, padded_block_right);
1852 if (top_candidate_inside(padded, left, right)) {
1853 return static_cast<std::size_t>(padded.position);
1854 }
1855
1856 const std::size_t first_full_block =
1857 (left + top_block_size_ - 1) / top_block_size_;
1858 const std::size_t full_block_right = right / top_block_size_;
1859 if (first_full_block >= full_block_right) {
1860 return npos;
1861 }
1862
1863 TopCandidate answer =
1864 top_sparse_block_arg_min(first_full_block, full_block_right);
1865
1866 const std::size_t left_border_end = first_full_block * top_block_size_;
1867 if (left < left_border_end) {
1868 answer = better_top_candidate(
1869 answer, make_top_candidate(cartesian_arg_min(left, left_border_end)));
1870 }
1871
1872 const std::size_t right_border_begin = full_block_right * top_block_size_;
1873 if (right_border_begin < right) {
1874 answer = better_top_candidate(
1875 answer,
1876 make_top_candidate(cartesian_arg_min(right_border_begin, right)));
1877 }
1878
1879 return valid_value_position(static_cast<std::size_t>(answer.position))
1880 ? static_cast<std::size_t>(answer.position)
1881 : npos;
1882 }
1883
1887 std::size_t select_close_position(std::size_t rank) const {
1888 if (rank == 0 || rank > values_.size() || !bp_index_) {
1889 return npos;
1890 }
1891 const std::size_t position = bp_index_->select0(rank);
1892 return position < bp_bit_count_ ? position : npos;
1893 }
1894
1895 std::span<const T> values_;
1896 Compare compare_;
1897 pixie::AlignedStorage bp_bits_;
1898 std::size_t bp_bit_count_ = 0;
1899 std::vector<TopCandidate> top_sparse_candidates_;
1900 std::size_t top_block_size_ = kMinTopSparseBlockSize;
1901 std::size_t top_block_count_ = 0;
1902 std::size_t top_sparse_levels_ = 0;
1903 std::optional<RankSelectSupport<>> bp_index_;
1904 BpDepthRmq bp_depth_rmq_;
1905};
1906
1915template <class T,
1916 class Compare = std::less<T>,
1917 class Index = std::size_t,
1918 std::size_t LeafSize = 512>
1920
1921} // namespace pixie::rmq
Rank/select support over an external packed bit sequence.
Definition support.h:55
Cartesian-tree value RMQ using HybridBTree-style LCA.
Definition cartesian_hybrid_btree.h:1359
std::size_t memory_usage_bytes_impl() const
Return owned auxiliary memory usage in bytes.
Definition cartesian_hybrid_btree.h:1551
T value_at_impl(std::size_t position) const
Return the value at an indexed position.
Definition cartesian_hybrid_btree.h:1479
std::span< const std::uint64_t > bp_words() const
Return the packed BP words used by the RMQ encoding.
Definition cartesian_hybrid_btree.h:1509
CartesianHybridBTree(std::span< const T > values, Compare compare=Compare())
Build a Cartesian-tree RMQ index over values.
Definition cartesian_hybrid_btree.h:1387
CartesianHybridBTree & operator=(CartesianHybridBTree &&other) noexcept
Move-assign an RMQ index and rebuild internal non-owning views.
Definition cartesian_hybrid_btree.h:1450
CartesianHybridBTree()=default
Construct an empty Cartesian-tree RMQ index.
CartesianHybridBTree & operator=(const CartesianHybridBTree &other)
Copy-assign an RMQ index and rebuild internal non-owning views.
Definition cartesian_hybrid_btree.h:1411
static std::size_t top_sparse_block_count_for(std::size_t value_count)
Return the number of top sparse-table blocks for a value count.
Definition cartesian_hybrid_btree.h:1527
std::size_t arg_min_impl(std::size_t left, std::size_t right) const
Return the first minimum position in [left, right).
Definition cartesian_hybrid_btree.h:1484
CartesianHybridBTree(const CartesianHybridBTree &other)
Copy an RMQ index and rebuild internal non-owning views.
Definition cartesian_hybrid_btree.h:1396
std::size_t size_impl() const
Return the number of indexed values.
Definition cartesian_hybrid_btree.h:1474
static std::size_t top_sparse_block_size_for(std::size_t value_count)
Return the top sparse-table block width chosen for a value count.
Definition cartesian_hybrid_btree.h:1516
std::size_t bp_bit_count() const
Return the number of BP bits in the Cartesian-tree RMQ encoding.
Definition cartesian_hybrid_btree.h:1504
CartesianHybridBTree(CartesianHybridBTree &&other) noexcept
Move an RMQ index and rebuild internal non-owning views.
Definition cartesian_hybrid_btree.h:1430
std::size_t top_sparse_block_size() const
Return the current top sparse-table block width.
Definition cartesian_hybrid_btree.h:1537
std::size_t top_sparse_block_count() const
Return the current number of top sparse-table blocks.
Definition cartesian_hybrid_btree.h:1542
CRTP facade for static range-minimum-query indexes.
Definition rmq.h:28
static constexpr std::size_t npos
Sentinel returned when no valid query answer exists.
Definition rmq.h:33
HybridBTree-style RMQ backend for ±1 depth sequences.
Definition cartesian_hybrid_btree.h:60
std::size_t memory_usage_bytes() const
Return owned auxiliary memory usage in bytes.
Definition cartesian_hybrid_btree.h:148
HybridBTreePlusMinusOne(std::span< const std::uint64_t > bits, std::size_t depth_count)
Build a ±1 RMQ index over external packed delta bits.
Definition cartesian_hybrid_btree.h:92
void build(std::span< const std::uint64_t > bits, std::size_t depth_count, const RankSelectSupport<> &rank_index)
Rebuild this index using non-owning rank support for the same bits.
Definition cartesian_hybrid_btree.h:123
std::size_t arg_min(std::size_t left, std::size_t right) const
Return the first minimum depth position in [left, right).
Definition cartesian_hybrid_btree.h:173
HybridBTreePlusMinusOne(std::span< const std::uint64_t > bits, std::size_t depth_count, const RankSelectSupport<> &rank_index)
Build a ±1 RMQ index over external packed bits and rank support.
Definition cartesian_hybrid_btree.h:104
HybridBTreePlusMinusOne()=default
Construct an empty ±1 RMQ index.
std::size_t select0(std::size_t rank) const
Return the one-based rank-th zero delta bit position.
Definition cartesian_hybrid_btree.h:204
std::size_t size() const
Return the number of indexed depth positions.
Definition cartesian_hybrid_btree.h:135
void build(std::span< const std::uint64_t > bits, std::size_t depth_count)
Definition cartesian_hybrid_btree.h:113
bool empty() const
Whether the indexed depth sequence is empty.
Definition cartesian_hybrid_btree.h:140
Definition rmq.h:15
CartesianHybridBTree< T, Compare, Index, LeafSize, false > CartesianBTree
Cartesian-tree RMQ variant without the value-level top sparse overlay.
Definition cartesian_hybrid_btree.h:1919
Common interface for static range-minimum-query indexes.