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 "
67 static_assert(!UseHighSparseLayout || HighSparseLayoutLevels > 0,
68 "HybridBTreePlusMinusOne high sparse layout must cover "
69 "at least one level when enabled");
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;
93 std::size_t depth_count) {
94 build(bits, depth_count);
105 std::size_t depth_count,
107 build(bits, depth_count, rank_index);
113 void build(std::span<const std::uint64_t> bits, std::size_t depth_count) {
115 depth_count_ = depth_count;
116 external_rank_index_ =
nullptr;
123 void build(std::span<const std::uint64_t> bits,
124 std::size_t depth_count,
127 depth_count_ = depth_count;
128 external_rank_index_ = &rank_index;
135 std::size_t
size()
const {
return depth_count_; }
140 bool empty()
const {
return depth_count_ == 0; }
149 std::size_t bytes =
sizeof(*this);
150 if (external_rank_index_ ==
nullptr) {
151 bytes += pixie::optional_nested_owned_memory_bytes(owned_rank_index_);
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_);
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()) {
177 if (left + 1 == right) {
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;
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);
192 const auto [level, node] = covering_node(left_leaf, right_leaf);
193 return query_node(level, node, left, right).position;
205 if (rank == 0 || depth_count_ == 0 || rank_index_or_null() ==
nullptr) {
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;
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 =
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));
237 struct DepthCandidate {
238 std::size_t position = npos;
239 std::int64_t depth = std::numeric_limits<std::int64_t>::max();
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();
249 class alignas(64) Bp512Selector {
254 Bp512Selector() =
default;
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");
271 if (entry_count == 0) {
275 std::array<std::uint16_t, kSelectorEntries> stack{};
276 std::size_t stack_size = 0;
277 std::size_t write_position = 2 * entry_count;
279 for (std::size_t i = entry_count; i-- > 0;) {
280 while (stack_size != 0 && !entry_less(stack[stack_size - 1], i)) {
282 prepend_bp_bit(write_position,
true);
284 stack[stack_size++] =
static_cast<std::uint16_t
>(i);
285 prepend_bp_bit(write_position,
false);
288 while (write_position != 0) {
289 prepend_bp_bit(write_position,
true);
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) {
303 if (slot_left + 1 == slot_right) {
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) {
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) {
320 const std::size_t zero_rank = rank0_at(shifted_min, bit_count);
321 if (zero_rank == 0) {
324 const std::size_t entry = zero_rank - 1;
325 return entry < entry_count ? entry : npos;
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);
345 std::size_t embedded_min_position()
const {
346 return static_cast<std::size_t
>(bp_bits_[kEmbeddedSummaryPositionWord]);
352 std::int64_t embedded_min_depth()
const {
353 return std::bit_cast<std::int64_t>(bp_bits_[kEmbeddedSummaryDepthWord]);
360 void prepend_bp_bit(std::size_t& write_position,
bool bit) {
363 bp_bits_[write_position >> 6] |= std::uint64_t{1}
364 << (write_position & 63);
371 std::size_t close_position(std::size_t slot)
const {
372 return select0_512(bp_bits_.data(), slot);
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);
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);
395 std::size_t depth_arg_min(std::size_t left,
397 std::size_t bit_count)
const {
398 const std::size_t depth_count = bit_count + 1;
399 if (left >= right || right > depth_count) {
403 std::size_t position = left;
404 int best_depth = prefix_excess(position);
405 std::size_t best_position = position;
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;
414 std::size_t candidate_position;
415 if (chunk_begin >= bit_count) {
416 candidate_depth = prefix_excess(bit_count);
417 candidate_position = bit_count;
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;
426 if (candidate_depth < best_depth) {
427 best_depth = candidate_depth;
428 best_position = candidate_position;
431 position = chunk_begin + local_right + 1;
434 return best_position;
437 std::array<std::uint64_t, kSelectorWords> bp_bits_{};
440 static_assert(
sizeof(Bp512Selector) == 64);
445 bool missing_position(std::size_t position)
const {
446 if constexpr (kInvalidIndexEqualsNpos) {
447 return position == npos;
449 return position == npos ||
450 position ==
static_cast<std::size_t
>(invalid_index);
457 DepthCandidate better_candidate(DepthCandidate left,
458 DepthCandidate right)
const {
459 if (missing_position(left.position)) {
462 if (missing_position(right.position)) {
465 if (right.depth < left.depth) {
468 if (left.depth < right.depth) {
471 return right.position < left.position ? right : left;
477 bool strictly_better_candidate(DepthCandidate left,
478 DepthCandidate right)
const {
479 if (missing_position(left.position)) {
482 if (missing_position(right.position)) {
485 if (left.depth != right.depth) {
486 return left.depth < right.depth;
488 return left.position < right.position;
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();
509 if (depth_count_ == 0) {
512 if (depth_count_ >
static_cast<std::size_t
>(invalid_index)) {
513 throw std::length_error(
514 "HybridBTreePlusMinusOne index type is too small");
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");
521 if (input_bits_.size() < (depth_count_ - 1 + 63) / 64) {
522 throw std::invalid_argument(
523 "HybridBTreePlusMinusOne bit span is too small");
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");
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);
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);
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);
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);
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) {
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];
579 internal_selectors_.resize(internal_count);
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;
588 ++high_layout_levels;
590 high_level_begin_ = high_layout_levels == 0
592 : root_level + 1 - high_layout_levels;
594 std::size_t high_node_count = 0;
595 for (std::size_t level = high_level_begin_; level < level_count();
597 high_level_offsets_[level] = high_node_count;
598 high_node_count += level_sizes_[level];
600 high_child_metadata_.resize(high_node_count * kHighLevelFanout);
601 high_sparse_min_slots_.resize(high_node_count * kHighSparseSlotsPerNode);
603 high_level_begin_ = level_count();
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];
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());
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;
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);
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;
643 build_high_sparse_min_slots(level, node, count);
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]);
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);
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;
665 static std::size_t ceil_div(std::size_t value, std::size_t divisor) {
666 return (value + divisor - 1) / divisor;
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();
682 std::uint64_t word_or_zero(std::size_t word)
const {
683 return word < input_bits_.size() ? input_bits_[word] : 0;
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;
699 storage[0] = word_or_zero(first_word);
700 storage[1] = word_or_zero(first_word + 1);
701 return storage.data();
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);
716 const RankSelectSupport<>& rank_index()
const {
717 return *rank_index_or_null();
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);
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) {
743 right_offset = std::min(right_offset, count - 1);
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) {
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);
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);
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});
778 chunk_base_excess += prefix_excess_128(chunk_words, 128);
790 std::size_t leaf_range_arg_min_relative(std::size_t leaf,
792 std::size_t right)
const {
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) {
804 right_offset = std::min(right_offset, count - 1);
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;
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) {
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);
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)
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;
845 const int chunk_excess = prefix_excess_128(chunk_words, 128);
848 static_cast<std::int64_t
>(chunk_excess) - left_prefix;
851 chunk_base_excess += chunk_excess;
855 return best_position;
861 DepthCandidate leaf_range_min(std::size_t leaf,
863 std::size_t right)
const {
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));
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) {
886 const std::size_t fanout = fanout_at_level(level);
888 right_node /= fanout;
890 return {level, left_node};
896 std::size_t leaf_for_position(std::size_t position)
const {
897 return position / LeafSize;
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];
911 DepthCandidate query_child_slots(std::size_t level,
913 std::size_t slot_left,
914 std::size_t slot_right,
916 std::size_t right)
const {
917 if (slot_left >= slot_right) {
921 const std::size_t count = entry_count(level, node);
922 const std::size_t slot =
923 slot_left + 1 == slot_right
925 : selector_arg_min(level, node, slot_left, slot_right, count);
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)) {
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));
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)));
977 if (slot_left + 1 < last_slot) {
978 answer = better_candidate(
980 full_child_slot_range_min(level, node, slot_left + 1, last_slot));
989 DepthCandidate full_child_slot_range_min(std::size_t level,
991 std::size_t slot_left,
992 std::size_t slot_right)
const {
993 if (slot_left >= slot_right) {
997 const std::size_t slot =
998 slot_left + 1 == slot_right
1000 : selector_arg_min(level, node, slot_left, slot_right,
1001 entry_count(level, node));
1005 return child_min_candidate(level, node, slot);
1011 DepthCandidate query_node(std::size_t level,
1014 std::size_t right)
const {
1015 if (left >= right) {
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);
1025 return leaf_range_min(node, left, right);
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);
1040 bool contains_position(std::size_t left,
1042 std::size_t position)
const {
1043 return !missing_position(position) && left <= position && position < right;
1049 std::size_t level_count()
const {
return level_sizes_.size(); }
1054 std::size_t entry_count(std::size_t level, std::size_t node)
const {
1056 const std::size_t begin = node_position_begin(0, node);
1057 return std::min<std::size_t>(LeafSize, depth_count_ - begin);
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);
1067 std::size_t node_position_begin(std::size_t level, std::size_t node)
const {
1068 return node * level_position_spans_[level];
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]);
1082 DepthCandidate subtree_min_candidate(std::size_t level,
1083 std::size_t node)
const {
1085 return leaf_range_min(node, node_position_begin(0, node),
1086 node_position_end(0, node));
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()};
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]};
1100 DepthCandidate child_min_candidate(std::size_t level,
1102 std::size_t slot)
const {
1103 if (is_high_level(level)) {
1104 return high_child_min_candidate(level, node, slot);
1106 return subtree_min_candidate(level - 1,
1107 node * fanout_at_level(level) + slot);
1113 DepthCandidate high_child_min_candidate(std::size_t level,
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};
1125 const Bp512Selector& selector_at(std::size_t level, std::size_t node)
const {
1126 return internal_selectors_[internal_flat_index(level, node)];
1132 Bp512Selector& mutable_selector_at(std::size_t level, std::size_t node) {
1133 return internal_selectors_[internal_flat_index(level, node)];
1139 std::size_t selector_arg_min(std::size_t level,
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);
1149 return selector_at(level, node).arg_min(slot_left, slot_right, count);
1155 bool is_high_level(std::size_t level)
const {
1156 if constexpr (!UseHighSparseLayout) {
1160 return level > 0 && level >= high_level_begin_ && level < level_count();
1166 std::size_t internal_flat_index(std::size_t level, std::size_t node)
const {
1167 return internal_level_offsets_[level] + node;
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;
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;
1189 std::size_t fanout_at_level(std::size_t level)
const {
1190 return level_fanouts_[level];
1196 std::size_t high_flat_index(std::size_t level, std::size_t node)
const {
1197 return high_level_offsets_[level] + node;
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;
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];
1220 HighChildMetadata& mutable_high_child_metadata_at(std::size_t high_flat,
1222 return high_child_metadata_[high_flat * kHighLevelFanout + slot];
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;
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;
1242 std::size_t better_high_child_slot(std::size_t level,
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
1257 void build_high_sparse_min_slots(std::size_t level,
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);
1266 for (std::size_t table_level = 1; table_level < kHighSparseTableLevels;
1268 const std::size_t span = std::size_t{1} << table_level;
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]));
1286 std::size_t high_sparse_arg_min(std::size_t level,
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) {
1294 const std::size_t length = slot_right - slot_left;
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]);
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();
1354 :
public RmqBase<CartesianHybridBTree<T,
1358 UseTopSparseOverlay>,
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 "
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;
1388 Compare compare = Compare())
1389 : values_(values), compare_(compare) {
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_) {
1412 if (
this == &other) {
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_;
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;
1451 if (
this == &other) {
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;
1485 if (left >= right || right > values_.size()) {
1488 if constexpr (!UseTopSparseOverlay) {
1489 return cartesian_arg_min(left, right);
1491 if (right - left <= top_block_size_) {
1492 return cartesian_arg_min(left, right);
1494 const std::size_t top_answer = top_sparse_arg_min(left, right);
1495 if (top_answer != npos) {
1498 return cartesian_arg_min(left, right);
1510 return bp_storage_words().first(bp_word_count());
1517 if (value_count == 0) {
1518 return kMinTopSparseBlockSize;
1520 return std::max(kMinTopSparseBlockSize,
1521 ceil_div(value_count, kMaxTopSparseBlocks));
1528 if (value_count == 0) {
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_);
1561 struct TopCandidate {
1562 Index position = invalid_index;
1564 static_assert(
sizeof(TopCandidate) ==
sizeof(Index));
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) {
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) {
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;
1593 top_sparse_candidates_.clear();
1594 top_block_size_ = kMinTopSparseBlockSize;
1595 top_block_count_ = 0;
1596 top_sparse_levels_ = 0;
1599 if (values_.empty()) {
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");
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});
1611 if constexpr (UseTopSparseOverlay) {
1612 build_top_sparse_table();
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 = [&]() {
1626 words[write_position >> 6] |= std::uint64_t{1} << (write_position & 63);
1629 for (std::size_t i = values_.size(); i-- > 0;) {
1630 while (!stack.empty() &&
1631 !compare_(values_[stack_index(stack.top())], values_[i])) {
1635 stack.push(stack_key(i));
1639 while (write_position != 0) {
1648 std::size_t stack_key(std::size_t value_index)
const {
1649 return values_.size() - 1 - value_index;
1656 std::size_t stack_index(std::size_t key)
const {
1657 return values_.size() - 1 - key;
1663 void reset_bp_indexes() {
1665 bp_depth_rmq_ = BpDepthRmq();
1666 if (bp_bit_count_ == 0) {
1669 const std::span<const std::uint64_t> words =
bp_words();
1670 const std::span<const std::uint64_t> padded_words = bp_storage_words();
1672 bp_index_.emplace(words, bp_bit_count_,
1673 RankSelectSupport<>::SelectSupport::kSelect0,
1675 bp_depth_rmq_ = BpDepthRmq(padded_words, bp_bit_count_ + 1, *bp_index_);
1681 std::size_t bp_word_count()
const {
return ceil_div(bp_bit_count_, 64); }
1686 std::size_t padded_bp_bit_capacity()
const {
1687 if (bp_bit_count_ == 0) {
1690 const std::size_t depth_count = bp_bit_count_ + 1;
1691 return ceil_div(depth_count, LeafSize) * LeafSize;
1697 std::span<std::uint64_t> bp_storage_words() {
1698 return bp_bits_.writable_words64();
1704 std::span<const std::uint64_t> bp_storage_words()
const {
1705 return bp_bits_.as_words64();
1711 void build_top_sparse_table() {
1712 top_sparse_candidates_.clear();
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) {
1721 top_sparse_candidates_.assign(top_sparse_levels_ * top_block_count_,
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)) {
1732 top_sparse_candidates_[block] = make_top_candidate(minimum);
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) {
1744 better_top_candidate(previous[block], previous[block + half_span]);
1752 static std::size_t ceil_div(std::size_t value, std::size_t divisor) {
1753 return value == 0 ? 0 : 1 + (value - 1) / divisor;
1759 TopCandidate make_top_candidate(std::size_t position)
const {
1760 if (position >= values_.size()) {
1763 return {
static_cast<Index
>(position)};
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();
1779 bool strictly_better_value_position(std::size_t left,
1780 std::size_t right)
const {
1781 if (!valid_value_position(left)) {
1784 if (!valid_value_position(right)) {
1787 if (compare_(values_[left], values_[right])) {
1790 if (compare_(values_[right], values_[left])) {
1793 return left < right;
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
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) {
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]);
1827 bool top_candidate_inside(TopCandidate candidate,
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 &&
1839 std::size_t top_sparse_arg_min(std::size_t left, std::size_t right)
const {
1840 if (top_block_count_ <= 1) {
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) {
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);
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) {
1863 TopCandidate answer =
1864 top_sparse_block_arg_min(first_full_block, full_block_right);
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)));
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(
1876 make_top_candidate(cartesian_arg_min(right_border_begin, right)));
1879 return valid_value_position(
static_cast<std::size_t
>(answer.position))
1880 ?
static_cast<std::size_t
>(answer.position)
1887 std::size_t select_close_position(std::size_t rank)
const {
1888 if (rank == 0 || rank > values_.size() || !bp_index_) {
1891 const std::size_t position = bp_index_->select0(rank);
1892 return position < bp_bit_count_ ? position : npos;
1895 std::span<const T> values_;
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_;