Pixie
Loading...
Searching...
No Matches
cartesian_hybrid_btree.h
1#pragma once
2
3#include <pixie/bits.h>
4#include <pixie/detail/serialization.h>
5#include <pixie/memory_usage.h>
6#include <pixie/rank_select/support.h>
7#include <pixie/rmq.h>
8#include <pixie/rmq/utils/succinct_monotone_stack.h>
9#include <pixie/storage/aligned.h>
10
11#include <algorithm>
12#include <array>
13#include <bit>
14#include <cstddef>
15#include <cstdint>
16#include <functional>
17#include <limits>
18#include <optional>
19#include <span>
20#include <stdexcept>
21#include <type_traits>
22#include <utility>
23#include <vector>
24
25namespace pixie::rmq {
26
27template <class T,
28 class Compare,
29 class Index,
30 std::size_t LeafSize,
31 bool UseTopSparseOverlay>
33
34namespace detail {
35
64template <class Index = std::size_t,
65 std::size_t LeafSize = 512,
66 bool UseHighSparseLayout = true,
67 std::size_t HighSparseLayoutLevels = 2>
69 template <class, class, class, std::size_t, bool>
70 friend class ::pixie::rmq::CartesianHybridBTree;
71
72 public:
73 static_assert(std::is_unsigned_v<Index>,
74 "HybridBTreePlusMinusOne index type must be unsigned");
75 static_assert(LeafSize != 0 && LeafSize % 512 == 0,
76 "HybridBTreePlusMinusOne leaf size must be a positive "
77 "multiple of 512");
78 static_assert(!UseHighSparseLayout || HighSparseLayoutLevels > 0,
79 "HybridBTreePlusMinusOne high sparse layout must cover "
80 "at least one level when enabled");
81
82 static constexpr std::size_t npos = std::numeric_limits<std::size_t>::max();
83 static constexpr Index invalid_index = std::numeric_limits<Index>::max();
84 static constexpr std::size_t kLeafSize = LeafSize;
85 static constexpr std::size_t kHighLevelFanout = 256;
86 static constexpr std::size_t kMiddleFanout = 192;
87 static constexpr bool kUseHighSparseLayout = UseHighSparseLayout;
88 static constexpr std::size_t kHighSparseLayoutLevels = HighSparseLayoutLevels;
89
94
103 HybridBTreePlusMinusOne(std::span<const std::uint64_t> bits,
104 std::size_t depth_count) {
105 build(bits, depth_count);
106 }
107
115 HybridBTreePlusMinusOne(std::span<const std::uint64_t> bits,
116 std::size_t depth_count,
117 const RankSelectSupport<>& rank_index) {
118 build(bits, depth_count, rank_index);
119 }
120
124 void build(std::span<const std::uint64_t> bits, std::size_t depth_count) {
125 input_bits_ = bits;
126 depth_count_ = depth_count;
127 external_rank_index_ = nullptr;
128 build();
129 }
130
134 void build(std::span<const std::uint64_t> bits,
135 std::size_t depth_count,
136 const RankSelectSupport<>& rank_index) {
137 input_bits_ = bits;
138 depth_count_ = depth_count;
139 external_rank_index_ = &rank_index;
140 build();
141 }
142
146 std::size_t size() const { return depth_count_; }
147
151 bool empty() const { return depth_count_ == 0; }
152
159 std::size_t memory_usage_bytes() const {
160 std::size_t bytes = sizeof(*this);
161 if (external_rank_index_ == nullptr) {
162 bytes += pixie::optional_nested_owned_memory_bytes(owned_rank_index_);
163 }
164 bytes += pixie::vector_capacity_bytes(internal_selectors_);
165 bytes += pixie::vector_capacity_bytes(internal_min_positions_);
166 bytes += pixie::vector_capacity_bytes(internal_min_depths_);
167 bytes += pixie::vector_capacity_bytes(high_child_metadata_);
168 bytes += pixie::vector_capacity_bytes(high_sparse_min_slots_);
169 bytes += pixie::vector_capacity_bytes(internal_level_offsets_);
170 bytes += pixie::vector_capacity_bytes(min_summary_level_offsets_);
171 bytes += pixie::vector_capacity_bytes(high_level_offsets_);
172 bytes += pixie::vector_capacity_bytes(level_sizes_);
173 bytes += pixie::vector_capacity_bytes(level_position_spans_);
174 bytes += pixie::vector_capacity_bytes(level_fanouts_);
175 return bytes;
176 }
177
184 std::size_t arg_min(std::size_t left, std::size_t right) const {
185 if (left >= right || right > depth_count_ || level_sizes_.empty()) {
186 return npos;
187 }
188 if (left + 1 == right) {
189 return left;
190 }
191
192 const std::size_t root_level = level_count() - 1;
193 if (left == 0 && right == depth_count_) {
194 return subtree_min_candidate(root_level, 0).position;
195 }
196
197 const std::size_t left_leaf = leaf_for_position(left);
198 const std::size_t right_leaf = leaf_for_position(right - 1);
199 if (left_leaf == right_leaf) {
200 return leaf_range_arg_min_relative(left_leaf, left, right);
201 }
202
203 const auto [level, node] = covering_node(left_leaf, right_leaf);
204 return query_node(level, node, left, right).position;
205 }
206
215 std::size_t select0(std::size_t rank) const {
216 if (rank == 0 || depth_count_ == 0 || rank_index_or_null() == nullptr) {
217 return npos;
218 }
219 const std::size_t delta_count = depth_count_ - 1;
220 const std::size_t position = rank_index().select0(rank);
221 return position < delta_count ? position : npos;
222 }
223
224 private:
225 static constexpr std::size_t kLeafWords = LeafSize / 64;
226 static constexpr std::size_t kLeafChunks = LeafSize / 128;
227 static constexpr std::size_t kSelectorEntries = 256;
228 static constexpr std::size_t kSelectorBits = 2 * kSelectorEntries;
229 static constexpr std::size_t kSelectorWords = kSelectorBits / 64;
230 static constexpr std::size_t kEmbeddedSummaryWords = 2;
231 static constexpr std::size_t kEmbeddedSummaryBits =
232 64 * kEmbeddedSummaryWords;
233 static constexpr std::size_t kEmbeddedSummaryMaxEntries =
234 (kSelectorBits - kEmbeddedSummaryBits) / 2;
235 static constexpr std::size_t kEmbeddedSummaryPositionWord =
236 kSelectorWords - 2;
237 static constexpr std::size_t kEmbeddedSummaryDepthWord = kSelectorWords - 1;
238 static constexpr std::size_t kHighSparseTableLevels =
239 static_cast<std::size_t>(std::bit_width(kHighLevelFanout));
240 static constexpr std::size_t kHighSparseSlotsPerNode =
241 kHighSparseTableLevels * kHighLevelFanout;
242 static constexpr bool kInvalidIndexEqualsNpos =
243 static_cast<std::size_t>(invalid_index) == npos;
244 static_assert(kEmbeddedSummaryMaxEntries == 192);
245 static_assert(kMiddleFanout == kEmbeddedSummaryMaxEntries);
246 static_assert(sizeof(std::size_t) <= sizeof(std::uint64_t));
247
248 struct DepthCandidate {
249 std::size_t position = npos;
250 std::int64_t depth = std::numeric_limits<std::int64_t>::max();
251 };
252
253 struct HighChildMetadata {
254 std::size_t position_begin = 0;
255 std::size_t position_end = 0;
256 Index min_position = invalid_index;
257 std::int64_t min_depth = std::numeric_limits<std::int64_t>::max();
258 };
259
260 class alignas(64) Bp512Selector {
261 public:
265 Bp512Selector() = default;
266
274 template <class EntryLess>
275 void build(std::size_t entry_count, EntryLess entry_less) {
276 if (entry_count > kSelectorEntries) {
277 throw std::length_error(
278 "HybridBTreePlusMinusOne local selector too large");
279 }
280
281 bp_bits_.fill(0);
282 if (entry_count == 0) {
283 return;
284 }
285
286 std::array<std::uint16_t, kSelectorEntries> stack{};
287 std::size_t stack_size = 0;
288 std::size_t write_position = 2 * entry_count;
289
290 for (std::size_t i = entry_count; i-- > 0;) {
291 while (stack_size != 0 && !entry_less(stack[stack_size - 1], i)) {
292 --stack_size;
293 prepend_bp_bit(write_position, true);
294 }
295 stack[stack_size++] = static_cast<std::uint16_t>(i);
296 prepend_bp_bit(write_position, false);
297 }
298
299 while (write_position != 0) {
300 prepend_bp_bit(write_position, true);
301 }
302 }
303
307 std::size_t arg_min(std::size_t slot_left,
308 std::size_t slot_right,
309 std::size_t entry_count) const {
310 if (slot_left >= slot_right || slot_right > entry_count ||
311 entry_count > kSelectorEntries) {
312 return npos;
313 }
314 if (slot_left + 1 == slot_right) {
315 return slot_left;
316 }
317
318 const std::size_t bit_count = 2 * entry_count;
319 const std::size_t first_close = close_position(slot_left);
320 const std::size_t last_close = close_position(slot_right - 1);
321 if (first_close > last_close || last_close >= bit_count) {
322 return npos;
323 }
324
325 const std::size_t shifted_min =
326 depth_arg_min(first_close + 1, last_close + 2, bit_count);
327 if (shifted_min == npos || shifted_min == 0) {
328 return npos;
329 }
330
331 const std::size_t zero_rank = rank0_at(shifted_min, bit_count);
332 if (zero_rank == 0) {
333 return npos;
334 }
335 const std::size_t entry = zero_rank - 1;
336 return entry < entry_count ? entry : npos;
337 }
338
347 void set_embedded_min_summary(std::size_t position, std::int64_t depth) {
348 bp_bits_[kEmbeddedSummaryPositionWord] =
349 static_cast<std::uint64_t>(position);
350 bp_bits_[kEmbeddedSummaryDepthWord] = std::bit_cast<std::uint64_t>(depth);
351 }
352
356 std::size_t embedded_min_position() const {
357 return static_cast<std::size_t>(bp_bits_[kEmbeddedSummaryPositionWord]);
358 }
359
363 std::int64_t embedded_min_depth() const {
364 return std::bit_cast<std::int64_t>(bp_bits_[kEmbeddedSummaryDepthWord]);
365 }
366
367 private:
368 friend class HybridBTreePlusMinusOne;
369
373 void prepend_bp_bit(std::size_t& write_position, bool bit) {
374 --write_position;
375 if (bit) {
376 bp_bits_[write_position >> 6] |= std::uint64_t{1}
377 << (write_position & 63);
378 }
379 }
380
384 std::size_t close_position(std::size_t slot) const {
385 return select0_512(bp_bits_.data(), slot);
386 }
387
391 std::size_t rank0_at(std::size_t position, std::size_t bit_count) const {
392 position = std::min(position, bit_count);
393 return position - rank_512(bp_bits_.data(), position);
394 }
395
399 int prefix_excess(std::size_t position) const {
400 position = std::min(position, kSelectorBits);
401 const std::size_t ones = rank_512(bp_bits_.data(), position);
402 return static_cast<int>(2 * ones) - static_cast<int>(position);
403 }
404
408 std::size_t depth_arg_min(std::size_t left,
409 std::size_t right,
410 std::size_t bit_count) const {
411 const std::size_t depth_count = bit_count + 1;
412 if (left >= right || right > depth_count) {
413 return npos;
414 }
415
416 std::size_t position = left;
417 int best_depth = prefix_excess(position);
418 std::size_t best_position = position;
419
420 while (position < right) {
421 const std::size_t chunk_begin = (position / 128) * 128;
422 const std::size_t local_left = position - chunk_begin;
423 const std::size_t local_right =
424 std::min<std::size_t>(right - 1, chunk_begin + 128) - chunk_begin;
425
426 int candidate_depth;
427 std::size_t candidate_position;
428 if (chunk_begin >= bit_count) {
429 candidate_depth = prefix_excess(bit_count);
430 candidate_position = bit_count;
431 } else {
432 const std::size_t word = chunk_begin >> 6;
433 const ExcessResult result =
434 excess_min_128(bp_bits_.data() + word, local_left, local_right);
435 candidate_depth = prefix_excess(chunk_begin) + result.min_excess;
436 candidate_position = chunk_begin + result.offset;
437 }
438
439 if (candidate_depth < best_depth) {
440 best_depth = candidate_depth;
441 best_position = candidate_position;
442 }
443
444 position = chunk_begin + local_right + 1;
445 }
446
447 return best_position;
448 }
449
450 std::array<std::uint64_t, kSelectorWords> bp_bits_{};
451 };
452
453 static_assert(sizeof(Bp512Selector) == 64);
454
455 void serialize_metadata(BinaryWriter& writer) const {
456 if (depth_count_ != 0 &&
457 (owned_rank_index_.has_value() || external_rank_index_ == nullptr)) {
458 throw std::invalid_argument(
459 "Depth RMQ serialization requires external rank support");
460 }
461 if (depth_count_ != 0) {
462 validate_serialized_state(*external_rank_index_,
463 DeserializationValidation::kQuick);
464 } else {
465 validate_serialized_state(RankSelectSupport<>(),
466 DeserializationValidation::kQuick);
467 }
468
469 writer.write_size(depth_count_);
470 writer.write_size(internal_selectors_.size());
471 for (const Bp512Selector& selector : internal_selectors_) {
472 for (const std::uint64_t word : selector.bp_bits_) {
473 writer.write_u64(word);
474 }
475 }
476 pixie::detail::write_vector(
477 writer, std::span<const Index>(internal_min_positions_));
478 pixie::detail::write_vector(
479 writer, std::span<const std::int64_t>(internal_min_depths_));
480
481 writer.write_size(high_child_metadata_.size());
482 for (const HighChildMetadata& metadata : high_child_metadata_) {
483 writer.write_size(metadata.position_begin);
484 writer.write_size(metadata.position_end);
485 pixie::detail::write_integral(writer, metadata.min_position);
486 writer.write_i64(metadata.min_depth);
487 }
488 pixie::detail::write_vector(
489 writer, std::span<const std::uint8_t>(high_sparse_min_slots_));
490 pixie::detail::write_vector(
491 writer, std::span<const std::size_t>(internal_level_offsets_));
492 pixie::detail::write_vector(
493 writer, std::span<const std::size_t>(min_summary_level_offsets_));
494 pixie::detail::write_vector(
495 writer, std::span<const std::size_t>(high_level_offsets_));
496 pixie::detail::write_vector(writer,
497 std::span<const std::size_t>(level_sizes_));
498 pixie::detail::write_vector(
499 writer, std::span<const std::size_t>(level_position_spans_));
500 pixie::detail::write_vector(writer,
501 std::span<const std::size_t>(level_fanouts_));
502 writer.write_size(high_level_begin_);
503 }
504
505 static HybridBTreePlusMinusOne deserialize_metadata(
506 std::span<const std::uint64_t> bits,
507 const RankSelectSupport<>& rank_index,
508 BinaryReader& reader) {
510 result.input_bits_ = bits;
511 result.depth_count_ = reader.read_size();
512 result.external_rank_index_ = &rank_index;
513
514 const std::size_t selector_count = reader.read_size();
515 const std::vector<Bp512Selector> empty_selectors;
516 if (selector_count > empty_selectors.max_size()) {
517 throw std::length_error(
518 "Serialized depth RMQ selector count is too large");
519 }
520 if (selector_count >
521 reader.remaining() / (kSelectorWords * sizeof(std::uint64_t))) {
522 throw std::invalid_argument("Truncated serialized depth RMQ selectors");
523 }
524 result.internal_selectors_.resize(selector_count);
525 for (Bp512Selector& selector : result.internal_selectors_) {
526 for (std::uint64_t& word : selector.bp_bits_) {
527 word = reader.read_u64();
528 }
529 }
530 result.internal_min_positions_ = pixie::detail::read_vector<Index>(reader);
531 result.internal_min_depths_ =
532 pixie::detail::read_vector<std::int64_t>(reader);
533
534 const std::size_t metadata_count = reader.read_size();
535 const std::vector<HighChildMetadata> empty_metadata;
536 if (metadata_count > empty_metadata.max_size()) {
537 throw std::length_error(
538 "Serialized depth RMQ child metadata is too large");
539 }
540 constexpr std::size_t kSerializedMetadataBytes =
541 2 * sizeof(std::uint64_t) + sizeof(Index) + sizeof(std::int64_t);
542 if (metadata_count > reader.remaining() / kSerializedMetadataBytes) {
543 throw std::invalid_argument(
544 "Truncated serialized depth RMQ child metadata");
545 }
546 result.high_child_metadata_.resize(metadata_count);
547 for (HighChildMetadata& metadata : result.high_child_metadata_) {
548 metadata.position_begin = reader.read_size();
549 metadata.position_end = reader.read_size();
550 metadata.min_position = pixie::detail::read_integral<Index>(reader);
551 metadata.min_depth = reader.read_i64();
552 }
553 result.high_sparse_min_slots_ =
554 pixie::detail::read_vector<std::uint8_t>(reader);
555 result.internal_level_offsets_ =
556 pixie::detail::read_vector<std::size_t>(reader);
557 result.min_summary_level_offsets_ =
558 pixie::detail::read_vector<std::size_t>(reader);
559 result.high_level_offsets_ =
560 pixie::detail::read_vector<std::size_t>(reader);
561 result.level_sizes_ = pixie::detail::read_vector<std::size_t>(reader);
562 result.level_position_spans_ =
563 pixie::detail::read_vector<std::size_t>(reader);
564 result.level_fanouts_ = pixie::detail::read_vector<std::size_t>(reader);
565 result.high_level_begin_ = reader.read_size();
566 return result;
567 }
568
569 void restore_external_sources(
570 std::span<const std::uint64_t> bits,
571 const RankSelectSupport<>& rank_index) noexcept {
572 input_bits_ = bits;
573 owned_rank_index_.reset();
574 external_rank_index_ = &rank_index;
575 }
576
577 void restore_empty_sources() noexcept {
578 input_bits_ = {};
579 owned_rank_index_.reset();
580 external_rank_index_ = nullptr;
581 }
582
583 void validate_serialized_state(const RankSelectSupport<>& rank_index,
584 DeserializationValidation validation) const {
585 if (depth_count_ == 0) {
586 if (!input_bits_.empty() || owned_rank_index_.has_value() ||
587 !internal_selectors_.empty() || !internal_min_positions_.empty() ||
588 !internal_min_depths_.empty() || !high_child_metadata_.empty() ||
589 !high_sparse_min_slots_.empty() || !internal_level_offsets_.empty() ||
590 !min_summary_level_offsets_.empty() || !high_level_offsets_.empty() ||
591 !level_sizes_.empty() || !level_position_spans_.empty() ||
592 !level_fanouts_.empty() ||
593 high_level_begin_ != std::numeric_limits<std::size_t>::max()) {
594 throw std::invalid_argument(
595 "Invalid serialized empty depth RMQ metadata");
596 }
597 return;
598 }
599
600 const std::size_t delta_count = depth_count_ - 1;
601 const std::size_t required_words =
602 delta_count == 0 ? 0 : 1 + (delta_count - 1) / 64;
603 if (required_words > input_bits_.size() ||
604 rank_index.size() < delta_count || owned_rank_index_.has_value()) {
605 throw std::invalid_argument(
606 "Invalid serialized depth RMQ source metadata");
607 }
608
609 const std::size_t level_count = level_sizes_.size();
610 if (level_count == 0 || internal_level_offsets_.size() != level_count ||
611 min_summary_level_offsets_.size() != level_count ||
612 high_level_offsets_.size() != level_count ||
613 level_position_spans_.size() != level_count ||
614 level_fanouts_.size() != level_count) {
615 throw std::invalid_argument(
616 "Invalid serialized depth RMQ topology sizes");
617 }
618
619 const std::size_t expected_leaf_count = 1 + (depth_count_ - 1) / LeafSize;
620 if (level_sizes_[0] != expected_leaf_count ||
621 level_position_spans_[0] != LeafSize || level_fanouts_[0] != 0 ||
622 internal_level_offsets_[0] != 0 ||
623 min_summary_level_offsets_[0] != npos || high_level_offsets_[0] != 0) {
624 throw std::invalid_argument("Invalid serialized depth RMQ leaf topology");
625 }
626
627 std::size_t current_count = expected_leaf_count;
628 std::size_t current_span = LeafSize;
629 std::size_t expected_internal_count = 0;
630 for (std::size_t level = 1; level < level_count; ++level) {
631 const std::size_t expected_fanout =
632 current_count > kHighLevelFanout * kHighLevelFanout
633 ? kMiddleFanout
634 : kHighLevelFanout;
635 current_count = ceil_div(current_count, expected_fanout);
636 current_span = saturating_product(current_span, expected_fanout);
637 if (level_fanouts_[level] != expected_fanout ||
638 level_sizes_[level] != current_count ||
639 level_position_spans_[level] != current_span ||
640 internal_level_offsets_[level] != expected_internal_count) {
641 throw std::invalid_argument(
642 "Invalid serialized depth RMQ level topology");
643 }
644 if (current_count >
645 std::numeric_limits<std::size_t>::max() - expected_internal_count) {
646 throw std::length_error(
647 "Serialized depth RMQ internal count is too large");
648 }
649 expected_internal_count += current_count;
650 }
651 if (current_count != 1 ||
652 internal_selectors_.size() != expected_internal_count) {
653 throw std::invalid_argument(
654 "Invalid serialized depth RMQ selector topology");
655 }
656
657 const std::size_t root_level = level_count - 1;
658 const std::size_t expected_high_begin =
659 level_count == 1
660 ? std::numeric_limits<std::size_t>::max()
661 : (level_fanouts_[root_level] == kHighLevelFanout ? root_level
662 : level_count);
663 if (high_level_begin_ != expected_high_begin) {
664 throw std::invalid_argument(
665 "Invalid serialized depth RMQ high-level topology");
666 }
667
668 std::size_t high_node_count = 0;
669 std::size_t side_summary_count = 0;
670 for (std::size_t level = 1; level < level_count; ++level) {
671 const bool high_level = level >= expected_high_begin;
672 if (high_level_offsets_[level] != high_node_count) {
673 throw std::invalid_argument(
674 "Invalid serialized depth RMQ high-level offsets");
675 }
676 if (high_level) {
677 high_node_count += level_sizes_[level];
678 }
679 const bool embeds =
680 !high_level && level_fanouts_[level] <= kEmbeddedSummaryMaxEntries;
681 const std::size_t expected_summary_offset =
682 embeds ? npos : side_summary_count;
683 if (min_summary_level_offsets_[level] != expected_summary_offset) {
684 throw std::invalid_argument(
685 "Invalid serialized depth RMQ summary offsets");
686 }
687 if (!embeds) {
688 side_summary_count += level_sizes_[level];
689 }
690 }
691 if (internal_min_positions_.size() != side_summary_count ||
692 internal_min_depths_.size() != side_summary_count ||
693 high_node_count >
694 std::numeric_limits<std::size_t>::max() / kHighLevelFanout ||
695 high_child_metadata_.size() != high_node_count * kHighLevelFanout ||
696 high_node_count >
697 std::numeric_limits<std::size_t>::max() / kHighSparseSlotsPerNode ||
698 high_sparse_min_slots_.size() !=
699 high_node_count * kHighSparseSlotsPerNode) {
700 throw std::invalid_argument(
701 "Invalid serialized depth RMQ metadata counts");
702 }
703
704 for (const Index position : internal_min_positions_) {
705 if (position == invalid_index ||
706 static_cast<std::size_t>(position) >= depth_count_) {
707 throw std::invalid_argument(
708 "Invalid serialized depth RMQ minimum position");
709 }
710 }
711 for (std::size_t level = expected_high_begin; level < level_count;
712 ++level) {
713 for (std::size_t node = 0; node < level_sizes_[level]; ++node) {
714 const std::size_t count = entry_count(level, node);
715 const std::size_t first_child = node * level_fanouts_[level];
716 const std::size_t flat = high_level_offsets_[level] + node;
717 for (std::size_t slot = 0; slot < count; ++slot) {
718 const HighChildMetadata& metadata =
719 high_child_metadata_[flat * kHighLevelFanout + slot];
720 const std::size_t child = first_child + slot;
721 const std::size_t expected_begin =
722 child * level_position_spans_[level - 1];
723 const std::size_t expected_end = std::min(
724 depth_count_, expected_begin + level_position_spans_[level - 1]);
725 if (metadata.position_begin != expected_begin ||
726 metadata.position_end != expected_end ||
727 metadata.min_position == invalid_index ||
728 metadata.min_position < expected_begin ||
729 metadata.min_position >= expected_end) {
730 throw std::invalid_argument(
731 "Invalid serialized depth RMQ child metadata");
732 }
733 }
734 }
735 }
736 if (validation == DeserializationValidation::kFull) {
737 validate_exact_metadata();
738 }
739 }
740
741 void validate_exact_metadata() const {
742 for (std::size_t level = 1; level < level_count(); ++level) {
743 for (std::size_t node = 0; node < level_sizes_[level]; ++node) {
744 const std::size_t count = entry_count(level, node);
745 const std::size_t first_child = node * level_fanouts_[level];
746 std::array<DepthCandidate, kSelectorEntries> child_minima{};
747 for (std::size_t slot = 0; slot < count; ++slot) {
748 child_minima[slot] =
749 subtree_min_candidate(level - 1, first_child + slot);
750 }
751
752 Bp512Selector expected_selector;
753 expected_selector.build(count,
754 [&](std::size_t left, std::size_t right) {
755 return strictly_better_candidate(
756 child_minima[left], child_minima[right]);
757 });
758 std::size_t best_slot = 0;
759 for (std::size_t slot = 1; slot < count; ++slot) {
760 if (strictly_better_candidate(child_minima[slot],
761 child_minima[best_slot])) {
762 best_slot = slot;
763 }
764 }
765 const DepthCandidate expected_minimum = child_minima[best_slot];
766 if (level_embeds_min_summary(level)) {
767 expected_selector.set_embedded_min_summary(expected_minimum.position,
768 expected_minimum.depth);
769 } else {
770 const std::size_t flat = min_summary_flat_index(level, node);
771 if (internal_min_positions_[flat] != expected_minimum.position ||
772 internal_min_depths_[flat] != expected_minimum.depth) {
773 throw std::invalid_argument(
774 "Serialized depth RMQ minimum disagrees with source");
775 }
776 }
777 if (selector_at(level, node).bp_bits_ != expected_selector.bp_bits_) {
778 throw std::invalid_argument(
779 "Serialized depth RMQ selector disagrees with source");
780 }
781
782 if (!is_high_level(level)) {
783 continue;
784 }
785 const std::size_t high_flat = high_flat_index(level, node);
786 for (std::size_t slot = 0; slot < kHighLevelFanout; ++slot) {
787 HighChildMetadata expected;
788 if (slot < count) {
789 const std::size_t child = first_child + slot;
790 expected.position_begin = node_position_begin(level - 1, child);
791 expected.position_end = node_position_end(level - 1, child);
792 expected.min_position =
793 static_cast<Index>(child_minima[slot].position);
794 expected.min_depth = child_minima[slot].depth;
795 }
796 const HighChildMetadata& actual =
797 high_child_metadata_at(high_flat, slot);
798 if (actual.position_begin != expected.position_begin ||
799 actual.position_end != expected.position_end ||
800 actual.min_position != expected.min_position ||
801 actual.min_depth != expected.min_depth) {
802 throw std::invalid_argument(
803 "Serialized depth RMQ child metadata disagrees with source");
804 }
805 }
806
807 std::array<std::uint8_t, kHighSparseSlotsPerNode> expected_sparse{};
808 for (std::size_t slot = 0; slot < count; ++slot) {
809 expected_sparse[slot] = static_cast<std::uint8_t>(slot);
810 }
811 for (std::size_t sparse_level = 1;
812 sparse_level < kHighSparseTableLevels; ++sparse_level) {
813 const std::size_t span = std::size_t{1} << sparse_level;
814 if (span > count) {
815 break;
816 }
817 const std::size_t half_span = span >> 1;
818 const std::size_t previous = (sparse_level - 1) * kHighLevelFanout;
819 const std::size_t current = sparse_level * kHighLevelFanout;
820 for (std::size_t slot = 0; slot + span <= count; ++slot) {
821 const std::size_t left = expected_sparse[previous + slot];
822 const std::size_t right =
823 expected_sparse[previous + slot + half_span];
824 expected_sparse[current + slot] = static_cast<std::uint8_t>(
825 strictly_better_candidate(child_minima[right],
826 child_minima[left])
827 ? right
828 : left);
829 }
830 }
831 const std::uint8_t* actual_sparse =
832 high_sparse_min_slots_begin(high_flat);
833 if (!std::ranges::equal(
834 expected_sparse,
835 std::span(actual_sparse, kHighSparseSlotsPerNode))) {
836 throw std::invalid_argument(
837 "Serialized depth RMQ sparse metadata disagrees with source");
838 }
839 }
840 }
841 }
842
846 bool missing_position(std::size_t position) const {
847 if constexpr (kInvalidIndexEqualsNpos) {
848 return position == npos;
849 } else {
850 return position == npos ||
851 position == static_cast<std::size_t>(invalid_index);
852 }
853 }
854
858 DepthCandidate better_candidate(DepthCandidate left,
859 DepthCandidate right) const {
860 if (missing_position(left.position)) {
861 return right;
862 }
863 if (missing_position(right.position)) {
864 return left;
865 }
866 if (right.depth < left.depth) {
867 return right;
868 }
869 if (left.depth < right.depth) {
870 return left;
871 }
872 return right.position < left.position ? right : left;
873 }
874
878 bool strictly_better_candidate(DepthCandidate left,
879 DepthCandidate right) const {
880 if (missing_position(left.position)) {
881 return false;
882 }
883 if (missing_position(right.position)) {
884 return true;
885 }
886 if (left.depth != right.depth) {
887 return left.depth < right.depth;
888 }
889 return left.position < right.position;
890 }
891
895 void build() {
896 owned_rank_index_.reset();
897 internal_selectors_.clear();
898 internal_min_positions_.clear();
899 internal_min_depths_.clear();
900 high_child_metadata_.clear();
901 high_sparse_min_slots_.clear();
902 internal_level_offsets_.clear();
903 min_summary_level_offsets_.clear();
904 high_level_offsets_.clear();
905 level_sizes_.clear();
906 level_position_spans_.clear();
907 level_fanouts_.clear();
908 high_level_begin_ = std::numeric_limits<std::size_t>::max();
909
910 if (depth_count_ == 0) {
911 return;
912 }
913 if (depth_count_ > static_cast<std::size_t>(invalid_index)) {
914 throw std::length_error(
915 "HybridBTreePlusMinusOne index type is too small");
916 }
917 if (depth_count_ >
918 static_cast<std::size_t>(std::numeric_limits<std::int64_t>::max())) {
919 throw std::length_error(
920 "HybridBTreePlusMinusOne depth range is too large");
921 }
922 if (input_bits_.size() < (depth_count_ - 1 + 63) / 64) {
923 throw std::invalid_argument(
924 "HybridBTreePlusMinusOne bit span is too small");
925 }
926
927 const std::size_t delta_count = depth_count_ - 1;
928 if (external_rank_index_ == nullptr) {
929 owned_rank_index_.emplace(input_bits_, delta_count,
930 RankSelectSupport<>::SelectSupport::kSelect0);
931 } else if (external_rank_index_->size() < delta_count) {
932 throw std::invalid_argument(
933 "HybridBTreePlusMinusOne external rank index is too small");
934 }
935
936 initialize_layout((depth_count_ + LeafSize - 1) / LeafSize);
937 for (std::size_t level = 1; level < level_count(); ++level) {
938 for (std::size_t node = 0; node < level_sizes_[level]; ++node) {
939 build_internal_node(level, node);
940 }
941 }
942 }
943
947 void initialize_layout(std::size_t leaf_count) {
948 level_sizes_.push_back(leaf_count);
949 level_position_spans_.push_back(LeafSize);
950 level_fanouts_.push_back(0);
951
952 std::size_t current_count = leaf_count;
953 std::size_t current_span = LeafSize;
954 while (current_count > kHighLevelFanout * kHighLevelFanout) {
955 level_fanouts_.push_back(kMiddleFanout);
956 current_count = ceil_div(current_count, kMiddleFanout);
957 current_span = saturating_product(current_span, kMiddleFanout);
958 level_sizes_.push_back(current_count);
959 level_position_spans_.push_back(current_span);
960 }
961 while (current_count > 1) {
962 level_fanouts_.push_back(kHighLevelFanout);
963 current_count = ceil_div(current_count, kHighLevelFanout);
964 current_span = saturating_product(current_span, kHighLevelFanout);
965 level_sizes_.push_back(current_count);
966 level_position_spans_.push_back(current_span);
967 }
968 internal_level_offsets_.assign(level_count(), 0);
969 min_summary_level_offsets_.assign(level_count(), npos);
970 high_level_offsets_.assign(level_count(), 0);
971 if (level_count() <= 1) {
972 return;
973 }
974
975 std::size_t internal_count = 0;
976 for (std::size_t level = 1; level < level_count(); ++level) {
977 internal_level_offsets_[level] = internal_count;
978 internal_count += level_sizes_[level];
979 }
980 internal_selectors_.resize(internal_count);
981
982 if constexpr (UseHighSparseLayout) {
983 const std::size_t root_level = level_count() - 1;
984 std::size_t high_layout_levels = 0;
985 for (std::size_t level = root_level;
986 level > 0 && high_layout_levels < HighSparseLayoutLevels &&
987 fanout_at_level(level) == kHighLevelFanout;
988 --level) {
989 ++high_layout_levels;
990 }
991 high_level_begin_ = high_layout_levels == 0
992 ? level_count()
993 : root_level + 1 - high_layout_levels;
994
995 std::size_t high_node_count = 0;
996 for (std::size_t level = high_level_begin_; level < level_count();
997 ++level) {
998 high_level_offsets_[level] = high_node_count;
999 high_node_count += level_sizes_[level];
1000 }
1001 high_child_metadata_.resize(high_node_count * kHighLevelFanout);
1002 high_sparse_min_slots_.resize(high_node_count * kHighSparseSlotsPerNode);
1003 } else {
1004 high_level_begin_ = level_count();
1005 }
1006
1007 std::size_t side_summary_count = 0;
1008 for (std::size_t level = 1; level < level_count(); ++level) {
1009 if (!level_embeds_min_summary(level)) {
1010 min_summary_level_offsets_[level] = side_summary_count;
1011 side_summary_count += level_sizes_[level];
1012 }
1013 }
1014 internal_min_positions_.resize(side_summary_count, invalid_index);
1015 internal_min_depths_.resize(side_summary_count,
1016 std::numeric_limits<std::int64_t>::max());
1017 }
1018
1022 void build_internal_node(std::size_t level, std::size_t node) {
1023 const std::size_t count = entry_count(level, node);
1024 const std::size_t first_child = node * fanout_at_level(level);
1025 const bool high_level = is_high_level(level);
1026 const std::size_t high_flat = high_level ? high_flat_index(level, node) : 0;
1027
1028 std::array<DepthCandidate, kSelectorEntries> child_minima{};
1029 for (std::size_t slot = 0; slot < count; ++slot) {
1030 child_minima[slot] = subtree_min_candidate(level - 1, first_child + slot);
1031 }
1032
1033 if (high_level) {
1034 for (std::size_t slot = 0; slot < count; ++slot) {
1035 const std::size_t child = first_child + slot;
1036 const DepthCandidate child_min = child_minima[slot];
1037 HighChildMetadata& metadata =
1038 mutable_high_child_metadata_at(high_flat, slot);
1039 metadata.position_begin = node_position_begin(level - 1, child);
1040 metadata.position_end = node_position_end(level - 1, child);
1041 metadata.min_position = static_cast<Index>(child_min.position);
1042 metadata.min_depth = child_min.depth;
1043 }
1044 build_high_sparse_min_slots(level, node, count);
1045 }
1046
1047 Bp512Selector& selector = mutable_selector_at(level, node);
1048 selector.build(count, [&](std::size_t left, std::size_t right) {
1049 return strictly_better_candidate(child_minima[left], child_minima[right]);
1050 });
1051
1052 const std::size_t slot = selector.arg_min(0, count, count);
1053 const DepthCandidate minimum = child_minima[slot];
1054 if (level_embeds_min_summary(level)) {
1055 selector.set_embedded_min_summary(minimum.position, minimum.depth);
1056 } else {
1057 const std::size_t flat = min_summary_flat_index(level, node);
1058 internal_min_positions_[flat] = static_cast<Index>(minimum.position);
1059 internal_min_depths_[flat] = minimum.depth;
1060 }
1061 }
1062
1066 static std::size_t ceil_div(std::size_t value, std::size_t divisor) {
1067 return (value + divisor - 1) / divisor;
1068 }
1069
1073 static std::size_t saturating_product(std::size_t left, std::size_t right) {
1074 if (left != 0 && right > std::numeric_limits<std::size_t>::max() / left) {
1075 return std::numeric_limits<std::size_t>::max();
1076 }
1077 return left * right;
1078 }
1079
1083 std::uint64_t word_or_zero(std::size_t word) const {
1084 return word < input_bits_.size() ? input_bits_[word] : 0;
1085 }
1086
1094 const std::uint64_t* chunk_words_or_copy(
1095 std::size_t first_word,
1096 std::array<std::uint64_t, 2>& storage) const {
1097 if (first_word + 1 < input_bits_.size()) {
1098 return input_bits_.data() + first_word;
1099 }
1100 storage[0] = word_or_zero(first_word);
1101 storage[1] = word_or_zero(first_word + 1);
1102 return storage.data();
1103 }
1104
1108 const RankSelectSupport<>* rank_index_or_null() const {
1109 return external_rank_index_ != nullptr
1110 ? external_rank_index_
1111 : (owned_rank_index_ ? &*owned_rank_index_ : nullptr);
1112 }
1113
1117 const RankSelectSupport<>& rank_index() const {
1118 return *rank_index_or_null();
1119 }
1120
1124 std::int64_t depth_at_position(std::size_t position) const {
1125 const std::size_t delta_count = depth_count_ == 0 ? 0 : depth_count_ - 1;
1126 position = std::min(position, delta_count);
1127 const std::uint64_t ones = rank_index().rank(position);
1128 return static_cast<std::int64_t>(ones) -
1129 static_cast<std::int64_t>(position - ones);
1130 }
1131
1135 DepthCandidate scan_leaf_range_with_base(std::size_t leaf,
1136 std::size_t left_offset,
1137 std::size_t right_offset,
1138 std::int64_t base_depth) const {
1139 const std::size_t begin = node_position_begin(0, leaf);
1140 const std::size_t count = entry_count(0, leaf);
1141 if (count == 0 || left_offset > right_offset || left_offset >= count) {
1142 return {};
1143 }
1144 right_offset = std::min(right_offset, count - 1);
1145
1146 DepthCandidate answer;
1147 std::int64_t chunk_base_excess = 0;
1148 const std::size_t first_word = leaf * kLeafWords;
1149 for (std::size_t chunk = 0; chunk < kLeafChunks; ++chunk) {
1150 const std::size_t chunk_begin = chunk * 128;
1151 if (chunk_begin >= count || chunk_begin > right_offset) {
1152 break;
1153 }
1154
1155 std::array<std::uint64_t, 2> chunk_storage{};
1156 const std::uint64_t* chunk_words =
1157 chunk_words_or_copy(first_word + 2 * chunk, chunk_storage);
1158
1159 const std::size_t chunk_end =
1160 std::min<std::size_t>(count - 1, chunk_begin + 127);
1161 if (left_offset > chunk_end) {
1162 chunk_base_excess += prefix_excess_128(chunk_words, 128);
1163 continue;
1164 }
1165
1166 const std::size_t local_left =
1167 std::max(left_offset, chunk_begin) - chunk_begin;
1168 const std::size_t local_right =
1169 std::min(right_offset, chunk_end) - chunk_begin;
1170 const ExcessResult result =
1171 excess_min_128(chunk_words, local_left, local_right);
1172 const std::size_t offset = chunk_begin + result.offset;
1173 if (result.offset != npos && offset < count) {
1174 answer = better_candidate(
1175 answer, {begin + offset,
1176 base_depth + chunk_base_excess + result.min_excess});
1177 }
1178
1179 chunk_base_excess += prefix_excess_128(chunk_words, 128);
1180 }
1181 return answer;
1182 }
1183
1191 std::size_t leaf_range_arg_min_relative(std::size_t leaf,
1192 std::size_t left,
1193 std::size_t right) const {
1194 if (left >= right) {
1195 return npos;
1196 }
1197
1198 const std::size_t begin = node_position_begin(0, leaf);
1199 const std::size_t count = entry_count(0, leaf);
1200 std::size_t left_offset = left - begin;
1201 std::size_t right_offset = right - begin - 1;
1202 if (count == 0 || left_offset >= count || left_offset > right_offset) {
1203 return npos;
1204 }
1205 right_offset = std::min(right_offset, count - 1);
1206
1207 std::size_t best_position = npos;
1208 std::int64_t best_depth = std::numeric_limits<std::int64_t>::max();
1209 std::int64_t chunk_base_excess = 0;
1210 bool first_chunk = true;
1211
1212 const std::size_t first_word = leaf * kLeafWords;
1213 std::size_t chunk = left_offset / 128;
1214 for (; chunk < kLeafChunks; ++chunk) {
1215 const std::size_t chunk_begin = chunk * 128;
1216 if (chunk_begin >= count || chunk_begin > right_offset) {
1217 break;
1218 }
1219
1220 std::array<std::uint64_t, 2> chunk_storage{};
1221 const std::uint64_t* chunk_words =
1222 chunk_words_or_copy(first_word + 2 * chunk, chunk_storage);
1223
1224 const std::size_t chunk_end =
1225 std::min<std::size_t>(count - 1, chunk_begin + 127);
1226 const std::size_t local_left =
1227 std::max(left_offset, chunk_begin) - chunk_begin;
1228 const std::size_t local_right =
1229 std::min(right_offset, chunk_end) - chunk_begin;
1230 const int left_prefix =
1231 first_chunk ? prefix_excess_128(chunk_words, local_left) : 0;
1232 const std::int64_t local_base =
1233 first_chunk ? -static_cast<std::int64_t>(left_prefix)
1234 : chunk_base_excess;
1235 const ExcessResult result =
1236 excess_min_128(chunk_words, local_left, local_right);
1237 const std::size_t offset = chunk_begin + result.offset;
1238 if (result.offset != npos && offset < count) {
1239 const std::int64_t candidate_depth = local_base + result.min_excess;
1240 if (best_position == npos || candidate_depth < best_depth) {
1241 best_position = begin + offset;
1242 best_depth = candidate_depth;
1243 }
1244 }
1245
1246 const int chunk_excess = prefix_excess_128(chunk_words, 128);
1247 if (first_chunk) {
1248 chunk_base_excess =
1249 static_cast<std::int64_t>(chunk_excess) - left_prefix;
1250 first_chunk = false;
1251 } else {
1252 chunk_base_excess += chunk_excess;
1253 }
1254 }
1255
1256 return best_position;
1257 }
1258
1262 DepthCandidate leaf_range_min(std::size_t leaf,
1263 std::size_t left,
1264 std::size_t right) const {
1265 if (left >= right) {
1266 return {};
1267 }
1268
1269 const std::size_t begin = node_position_begin(0, leaf);
1270 const std::size_t slot_left = left - begin;
1271 const std::size_t slot_right = right - begin;
1272 return scan_leaf_range_with_base(leaf, slot_left, slot_right - 1,
1273 depth_at_position(begin));
1274 }
1275
1279 std::pair<std::size_t, std::size_t> covering_node(
1280 std::size_t left_leaf,
1281 std::size_t right_leaf) const {
1282 std::size_t level = 0;
1283 std::size_t left_node = left_leaf;
1284 std::size_t right_node = right_leaf;
1285 while (left_node != right_node) {
1286 ++level;
1287 const std::size_t fanout = fanout_at_level(level);
1288 left_node /= fanout;
1289 right_node /= fanout;
1290 }
1291 return {level, left_node};
1292 }
1293
1297 std::size_t leaf_for_position(std::size_t position) const {
1298 return position / LeafSize;
1299 }
1300
1304 std::size_t child_for_position(std::size_t child_level,
1305 std::size_t position) const {
1306 return position / level_position_spans_[child_level];
1307 }
1308
1312 DepthCandidate query_child_slots(std::size_t level,
1313 std::size_t node,
1314 std::size_t slot_left,
1315 std::size_t slot_right,
1316 std::size_t left,
1317 std::size_t right) const {
1318 if (slot_left >= slot_right) {
1319 return {};
1320 }
1321
1322 const std::size_t count = entry_count(level, node);
1323 const std::size_t slot =
1324 slot_left + 1 == slot_right
1325 ? slot_left
1326 : selector_arg_min(level, node, slot_left, slot_right, count);
1327 if (slot == npos) {
1328 return {};
1329 }
1330
1331 const std::size_t child_level = level - 1;
1332 const std::size_t first_child = node * fanout_at_level(level);
1333 const std::size_t child = first_child + slot;
1334 const HighChildMetadata* high_children =
1335 is_high_level(level) ? high_child_metadata_begin(level, node) : nullptr;
1336 const DepthCandidate child_min =
1337 high_children != nullptr ? high_child_min_candidate(level, node, slot)
1338 : subtree_min_candidate(child_level, child);
1339 const std::size_t child_begin =
1340 high_children != nullptr ? high_children[slot].position_begin
1341 : node_position_begin(child_level, child);
1342 const std::size_t child_end = high_children != nullptr
1343 ? high_children[slot].position_end
1344 : node_position_end(child_level, child);
1345 if ((left <= child_begin && child_end <= right) ||
1346 contains_position(left, right, child_min.position)) {
1347 return child_min;
1348 }
1349
1350 const std::size_t last_slot = slot_right - 1;
1351 const std::size_t left_child_begin =
1352 high_children != nullptr
1353 ? high_children[slot_left].position_begin
1354 : node_position_begin(child_level, first_child + slot_left);
1355 const std::size_t left_child_end =
1356 high_children != nullptr
1357 ? high_children[slot_left].position_end
1358 : node_position_end(child_level, first_child + slot_left);
1359 DepthCandidate answer = query_node(child_level, first_child + slot_left,
1360 std::max(left, left_child_begin),
1361 std::min(right, left_child_end));
1362
1363 if (slot_left != last_slot) {
1364 const std::size_t right_child_begin =
1365 high_children != nullptr
1366 ? high_children[last_slot].position_begin
1367 : node_position_begin(child_level, first_child + last_slot);
1368 const std::size_t right_child_end =
1369 high_children != nullptr
1370 ? high_children[last_slot].position_end
1371 : node_position_end(child_level, first_child + last_slot);
1372 answer = better_candidate(answer,
1373 query_node(child_level, first_child + last_slot,
1374 std::max(left, right_child_begin),
1375 std::min(right, right_child_end)));
1376 }
1377
1378 if (slot_left + 1 < last_slot) {
1379 answer = better_candidate(
1380 answer,
1381 full_child_slot_range_min(level, node, slot_left + 1, last_slot));
1382 }
1383
1384 return answer;
1385 }
1386
1390 DepthCandidate full_child_slot_range_min(std::size_t level,
1391 std::size_t node,
1392 std::size_t slot_left,
1393 std::size_t slot_right) const {
1394 if (slot_left >= slot_right) {
1395 return {};
1396 }
1397
1398 const std::size_t slot =
1399 slot_left + 1 == slot_right
1400 ? slot_left
1401 : selector_arg_min(level, node, slot_left, slot_right,
1402 entry_count(level, node));
1403 if (slot == npos) {
1404 return {};
1405 }
1406 return child_min_candidate(level, node, slot);
1407 }
1408
1412 DepthCandidate query_node(std::size_t level,
1413 std::size_t node,
1414 std::size_t left,
1415 std::size_t right) const {
1416 if (left >= right) {
1417 return {};
1418 }
1419
1420 const std::size_t begin = node_position_begin(level, node);
1421 const std::size_t end = node_position_end(level, node);
1422 if (left <= begin && end <= right) {
1423 return subtree_min_candidate(level, node);
1424 }
1425 if (level == 0) {
1426 return leaf_range_min(node, left, right);
1427 }
1428
1429 const std::size_t child_level = level - 1;
1430 const std::size_t left_child = child_for_position(child_level, left);
1431 const std::size_t right_child = child_for_position(child_level, right - 1);
1432 const std::size_t first_child = node * fanout_at_level(level);
1433 const std::size_t left_slot = left_child - first_child;
1434 const std::size_t right_slot = right_child - first_child + 1;
1435 return query_child_slots(level, node, left_slot, right_slot, left, right);
1436 }
1437
1441 bool contains_position(std::size_t left,
1442 std::size_t right,
1443 std::size_t position) const {
1444 return !missing_position(position) && left <= position && position < right;
1445 }
1446
1450 std::size_t level_count() const { return level_sizes_.size(); }
1451
1455 std::size_t entry_count(std::size_t level, std::size_t node) const {
1456 if (level == 0) {
1457 const std::size_t begin = node_position_begin(0, node);
1458 return std::min<std::size_t>(LeafSize, depth_count_ - begin);
1459 }
1460 const std::size_t first_child = node * fanout_at_level(level);
1461 return std::min<std::size_t>(fanout_at_level(level),
1462 level_sizes_[level - 1] - first_child);
1463 }
1464
1468 std::size_t node_position_begin(std::size_t level, std::size_t node) const {
1469 return node * level_position_spans_[level];
1470 }
1471
1475 std::size_t node_position_end(std::size_t level, std::size_t node) const {
1476 return std::min(depth_count_, node_position_begin(level, node) +
1477 level_position_spans_[level]);
1478 }
1479
1483 DepthCandidate subtree_min_candidate(std::size_t level,
1484 std::size_t node) const {
1485 if (level == 0) {
1486 return leaf_range_min(node, node_position_begin(0, node),
1487 node_position_end(0, node));
1488 }
1489 if (level_embeds_min_summary(level)) {
1490 const Bp512Selector& selector = selector_at(level, node);
1491 return {selector.embedded_min_position(), selector.embedded_min_depth()};
1492 }
1493 const std::size_t flat = min_summary_flat_index(level, node);
1494 return {static_cast<std::size_t>(internal_min_positions_[flat]),
1495 internal_min_depths_[flat]};
1496 }
1497
1501 DepthCandidate child_min_candidate(std::size_t level,
1502 std::size_t node,
1503 std::size_t slot) const {
1504 if (is_high_level(level)) {
1505 return high_child_min_candidate(level, node, slot);
1506 }
1507 return subtree_min_candidate(level - 1,
1508 node * fanout_at_level(level) + slot);
1509 }
1510
1514 DepthCandidate high_child_min_candidate(std::size_t level,
1515 std::size_t node,
1516 std::size_t slot) const {
1517 const HighChildMetadata& metadata =
1518 high_child_metadata_at(high_flat_index(level, node), slot);
1519 return {static_cast<std::size_t>(metadata.min_position),
1520 metadata.min_depth};
1521 }
1522
1526 const Bp512Selector& selector_at(std::size_t level, std::size_t node) const {
1527 return internal_selectors_[internal_flat_index(level, node)];
1528 }
1529
1533 Bp512Selector& mutable_selector_at(std::size_t level, std::size_t node) {
1534 return internal_selectors_[internal_flat_index(level, node)];
1535 }
1536
1540 std::size_t selector_arg_min(std::size_t level,
1541 std::size_t node,
1542 std::size_t slot_left,
1543 std::size_t slot_right,
1544 std::size_t count) const {
1545 if constexpr (UseHighSparseLayout) {
1546 if (is_high_level(level)) {
1547 return high_sparse_arg_min(level, node, slot_left, slot_right, count);
1548 }
1549 }
1550 return selector_at(level, node).arg_min(slot_left, slot_right, count);
1551 }
1552
1556 bool is_high_level(std::size_t level) const {
1557 if constexpr (!UseHighSparseLayout) {
1558 (void)level;
1559 return false;
1560 }
1561 return level > 0 && level >= high_level_begin_ && level < level_count();
1562 }
1563
1567 std::size_t internal_flat_index(std::size_t level, std::size_t node) const {
1568 return internal_level_offsets_[level] + node;
1569 }
1570
1574 bool level_embeds_min_summary(std::size_t level) const {
1575 return level > 0 && !is_high_level(level) &&
1576 fanout_at_level(level) <= kEmbeddedSummaryMaxEntries;
1577 }
1578
1582 std::size_t min_summary_flat_index(std::size_t level,
1583 std::size_t node) const {
1584 return min_summary_level_offsets_[level] + node;
1585 }
1586
1590 std::size_t fanout_at_level(std::size_t level) const {
1591 return level_fanouts_[level];
1592 }
1593
1597 std::size_t high_flat_index(std::size_t level, std::size_t node) const {
1598 return high_level_offsets_[level] + node;
1599 }
1600
1604 const HighChildMetadata* high_child_metadata_begin(std::size_t level,
1605 std::size_t node) const {
1606 return high_child_metadata_.data() +
1607 high_flat_index(level, node) * kHighLevelFanout;
1608 }
1609
1613 const HighChildMetadata& high_child_metadata_at(std::size_t high_flat,
1614 std::size_t slot) const {
1615 return high_child_metadata_[high_flat * kHighLevelFanout + slot];
1616 }
1617
1621 HighChildMetadata& mutable_high_child_metadata_at(std::size_t high_flat,
1622 std::size_t slot) {
1623 return high_child_metadata_[high_flat * kHighLevelFanout + slot];
1624 }
1625
1629 std::uint8_t* mutable_high_sparse_min_slots_begin(std::size_t high_flat) {
1630 return high_sparse_min_slots_.data() + high_flat * kHighSparseSlotsPerNode;
1631 }
1632
1636 const std::uint8_t* high_sparse_min_slots_begin(std::size_t high_flat) const {
1637 return high_sparse_min_slots_.data() + high_flat * kHighSparseSlotsPerNode;
1638 }
1639
1643 std::size_t better_high_child_slot(std::size_t level,
1644 std::size_t node,
1645 std::size_t left_slot,
1646 std::size_t right_slot) const {
1647 const DepthCandidate left =
1648 high_child_min_candidate(level, node, left_slot);
1649 const DepthCandidate right =
1650 high_child_min_candidate(level, node, right_slot);
1651 return better_candidate(left, right).position == right.position ? right_slot
1652 : left_slot;
1653 }
1654
1658 void build_high_sparse_min_slots(std::size_t level,
1659 std::size_t node,
1660 std::size_t count) {
1661 const std::size_t high_flat = high_flat_index(level, node);
1662 std::uint8_t* table = mutable_high_sparse_min_slots_begin(high_flat);
1663 for (std::size_t slot = 0; slot < count; ++slot) {
1664 table[slot] = static_cast<std::uint8_t>(slot);
1665 }
1666
1667 for (std::size_t table_level = 1; table_level < kHighSparseTableLevels;
1668 ++table_level) {
1669 const std::size_t span = std::size_t{1} << table_level;
1670 if (span > count) {
1671 break;
1672 }
1673 const std::size_t half_span = span >> 1;
1674 const std::uint8_t* previous =
1675 table + (table_level - 1) * kHighLevelFanout;
1676 std::uint8_t* current = table + table_level * kHighLevelFanout;
1677 for (std::size_t slot = 0; slot + span <= count; ++slot) {
1678 current[slot] = static_cast<std::uint8_t>(better_high_child_slot(
1679 level, node, previous[slot], previous[slot + half_span]));
1680 }
1681 }
1682 }
1683
1687 std::size_t high_sparse_arg_min(std::size_t level,
1688 std::size_t node,
1689 std::size_t slot_left,
1690 std::size_t slot_right,
1691 std::size_t count) const {
1692 if (slot_left >= slot_right || slot_right > count) {
1693 return npos;
1694 }
1695 const std::size_t length = slot_right - slot_left;
1696 if (length == 1) {
1697 return slot_left;
1698 }
1699
1700 const std::size_t high_flat = high_flat_index(level, node);
1701 const std::size_t table_level = std::bit_width(length) - 1;
1702 const std::size_t span = std::size_t{1} << table_level;
1703 const std::uint8_t* table =
1704 high_sparse_min_slots_begin(high_flat) + table_level * kHighLevelFanout;
1705 return better_high_child_slot(level, node, table[slot_left],
1706 table[slot_right - span]);
1707 }
1708
1709 std::span<const std::uint64_t> input_bits_;
1710 std::size_t depth_count_ = 0;
1711 std::optional<RankSelectSupport<>> owned_rank_index_;
1712 const RankSelectSupport<>* external_rank_index_ = nullptr;
1713 std::vector<Bp512Selector> internal_selectors_;
1714 std::vector<Index> internal_min_positions_;
1715 std::vector<std::int64_t> internal_min_depths_;
1716 std::vector<HighChildMetadata> high_child_metadata_;
1717 std::vector<std::uint8_t> high_sparse_min_slots_;
1718 std::vector<std::size_t> internal_level_offsets_;
1719 std::vector<std::size_t> min_summary_level_offsets_;
1720 std::vector<std::size_t> high_level_offsets_;
1721 std::vector<std::size_t> level_sizes_;
1722 std::vector<std::size_t> level_position_spans_;
1723 std::vector<std::size_t> level_fanouts_;
1724 std::size_t high_level_begin_ = std::numeric_limits<std::size_t>::max();
1725};
1726
1727} // namespace detail
1728
1749template <class T,
1750 class Compare = std::less<T>,
1751 class Index = std::size_t,
1752 std::size_t LeafSize = 512,
1753 bool UseTopSparseOverlay = true>
1755 : public RmqBase<CartesianHybridBTree<T,
1756 Compare,
1757 Index,
1758 LeafSize,
1759 UseTopSparseOverlay>,
1760 T>,
1761 public SerializationBase<CartesianHybridBTree<T,
1762 Compare,
1763 Index,
1764 LeafSize,
1765 UseTopSparseOverlay>> {
1766 private:
1768
1769 struct TopCandidate {
1770 Index position = std::numeric_limits<Index>::max();
1771 };
1772 static_assert(sizeof(TopCandidate) == sizeof(Index));
1773
1774 struct LoadedState {
1775 pixie::AlignedStorage bp_bits_;
1776 std::size_t bp_bit_count_ = 0;
1777 std::vector<TopCandidate> top_sparse_candidates_;
1778 std::size_t top_block_size_ = 4096;
1779 std::size_t top_block_count_ = 0;
1780 std::size_t top_sparse_levels_ = 0;
1781 std::optional<RankSelectSupport<>> bp_index_;
1782 BpDepthRmq bp_depth_rmq_;
1783 };
1784
1785 struct LoadTag {};
1786
1787 static constexpr std::array<std::uint8_t, 8> kSerializationMagic = {
1788 'P', 'I', 'X', 'I', 'E', 'R', 'M', 'Q'};
1789 static constexpr std::uint32_t kSerializationVersion = 4;
1790 static constexpr std::size_t kSerializationHeaderBytes = 48;
1791
1792 public:
1793 static_assert(std::is_unsigned_v<Index>,
1794 "CartesianHybridBTree index type must be unsigned");
1795 static_assert(LeafSize != 0 && LeafSize % 512 == 0,
1796 "CartesianHybridBTree leaf size must be a positive "
1797 "multiple of 512");
1798
1799 using Self =
1801
1802 static constexpr std::size_t npos = RmqBase<Self, T>::npos;
1803 static constexpr Index invalid_index = std::numeric_limits<Index>::max();
1804 static constexpr std::size_t kMinTopSparseBlockSize = 4096;
1805 static constexpr std::size_t kMaxTopSparseBlocks = std::size_t{1} << 14;
1806 static constexpr bool kUseTopSparseOverlay = UseTopSparseOverlay;
1807 static constexpr bool kSerializationSupported =
1808 std::same_as<T, std::int64_t> &&
1809 std::same_as<Compare, std::less<std::int64_t>> &&
1810 std::same_as<Index, std::size_t> && LeafSize == 512 &&
1811 UseTopSparseOverlay;
1812
1817
1824 explicit CartesianHybridBTree(std::span<const T> values,
1825 Compare compare = Compare())
1826 : values_(values), compare_(compare) {
1827 build();
1828 }
1829
1838 void serialize_impl(BinaryWriter& writer) const
1839 requires(kSerializationSupported)
1840 {
1841 validate_serialized_state(DeserializationValidation::kQuick);
1842
1843 const std::size_t artifact_begin = writer.size_bytes();
1844 pixie::detail::write_magic(writer, kSerializationMagic);
1845 writer.write_u32(kSerializationVersion);
1846 writer.write_u8(pixie::detail::kLittleEndianMarker);
1847 writer.write_u8(sizeof(std::uint64_t));
1848 writer.write_u8(sizeof(T));
1849 writer.write_u8(sizeof(Index));
1850 const std::size_t artifact_size_position = writer.write_u64_placeholder();
1851 writer.write_size(values_.size());
1852 writer.write_size(LeafSize);
1853 writer.write_u32(static_cast<std::uint32_t>(UseTopSparseOverlay));
1854 writer.write_u32(0);
1855
1856 writer.write_size(bp_bit_count_);
1857 writer.write_size(top_block_size_);
1858 writer.write_size(top_block_count_);
1859 writer.write_size(top_sparse_levels_);
1860 bp_bits_.serialize(writer);
1861 writer.write_size(top_sparse_candidates_.size());
1862 for (const TopCandidate candidate : top_sparse_candidates_) {
1863 pixie::detail::write_integral(writer, candidate.position);
1864 }
1865 writer.write_u8(static_cast<std::uint8_t>(bp_index_.has_value()));
1866 if (bp_index_) {
1867 bp_index_->serialize(writer);
1868 }
1869 bp_depth_rmq_.serialize_metadata(writer);
1870
1871 const std::size_t unpadded_size = writer.size_bytes() - artifact_begin;
1872 writer.write_zeros(
1873 (sizeof(std::uint64_t) - unpadded_size % sizeof(std::uint64_t)) %
1874 sizeof(std::uint64_t));
1875 const std::size_t artifact_size = writer.size_bytes() - artifact_begin;
1876 writer.patch_u64(artifact_size_position,
1877 static_cast<std::uint64_t>(artifact_size));
1878 }
1879
1898 static Self deserialize_impl(
1899 BinaryReader& reader,
1900 std::span<const std::int64_t> values,
1902 requires(kSerializationSupported)
1903 {
1904 BinaryReader candidate = reader;
1905 const std::size_t available_size = candidate.remaining();
1906 pixie::detail::require_magic(candidate, kSerializationMagic);
1907 if (candidate.read_u32() != kSerializationVersion ||
1908 candidate.read_u8() != pixie::detail::kLittleEndianMarker ||
1909 candidate.read_u8() != sizeof(std::uint64_t) ||
1910 candidate.read_u8() != sizeof(T) ||
1911 candidate.read_u8() != sizeof(Index)) {
1912 throw std::invalid_argument("Incompatible serialized RMQ artifact");
1913 }
1914 const std::size_t artifact_size = pixie::detail::checked_artifact_size(
1915 candidate.read_u64(), kSerializationHeaderBytes, available_size);
1916 const std::size_t source_value_count = candidate.read_size();
1917 if (candidate.read_size() != LeafSize ||
1918 candidate.read_u32() !=
1919 static_cast<std::uint32_t>(UseTopSparseOverlay) ||
1920 candidate.read_u32() != 0) {
1921 throw std::invalid_argument("Incompatible serialized RMQ configuration");
1922 }
1923
1924 BinaryReader payload =
1925 candidate.read_subreader(artifact_size - kSerializationHeaderBytes);
1926 LoadedState state;
1927 state.bp_bit_count_ = payload.read_size();
1928 state.top_block_size_ = payload.read_size();
1929 state.top_block_count_ = payload.read_size();
1930 state.top_sparse_levels_ = payload.read_size();
1931 state.bp_bits_ = deserialize_aligned_storage(payload);
1932
1933 const std::size_t candidate_count = payload.read_size();
1934 const std::vector<TopCandidate> empty_candidates;
1935 if (candidate_count > empty_candidates.max_size()) {
1936 throw std::length_error(
1937 "Serialized RMQ top candidate count is too large");
1938 }
1939 if (candidate_count > payload.remaining() / sizeof(Index)) {
1940 throw std::invalid_argument("Truncated serialized RMQ top candidates");
1941 }
1942 state.top_sparse_candidates_.resize(candidate_count);
1943 for (TopCandidate& candidate : state.top_sparse_candidates_) {
1944 candidate.position = pixie::detail::read_integral<Index>(payload);
1945 }
1946
1947 const std::uint8_t has_rank_index = payload.read_u8();
1948 if (has_rank_index > 1) {
1949 throw std::invalid_argument("Invalid serialized RMQ rank-index marker");
1950 }
1951 const std::size_t bp_word_count =
1952 state.bp_bit_count_ == 0 ? 0 : 1 + (state.bp_bit_count_ - 1) / 64;
1953 if (bp_word_count > state.bp_bits_.as_words64().size()) {
1954 throw std::invalid_argument("Serialized RMQ BP storage is too small");
1955 }
1956 if (has_rank_index != 0) {
1957 state.bp_index_ = RankSelectSupport<>::deserialize(
1958 payload, state.bp_bits_.as_words64().first(bp_word_count),
1959 validation);
1960 }
1961
1962 const RankSelectSupport<> empty_rank_index;
1963 const RankSelectSupport<>& rank_index =
1964 state.bp_index_ ? *state.bp_index_ : empty_rank_index;
1965 state.bp_depth_rmq_ = BpDepthRmq::deserialize_metadata(
1966 state.bp_bits_.as_words64(), rank_index, payload);
1967 payload.require_zero_padding(sizeof(std::uint64_t) - 1);
1968
1969 if (source_value_count != values.size()) {
1970 throw std::invalid_argument(
1971 "Serialized RMQ source value count is inconsistent");
1972 }
1973 validate_loaded_state(values, state, validation);
1974 reader = candidate;
1975 return Self(LoadTag{}, values, std::move(state));
1976 }
1977
1982 : values_(other.values_),
1983 compare_(other.compare_),
1984 bp_bits_(other.bp_bits_),
1985 bp_bit_count_(other.bp_bit_count_),
1986 top_sparse_candidates_(other.top_sparse_candidates_),
1987 top_block_size_(other.top_block_size_),
1988 top_block_count_(other.top_block_count_),
1989 top_sparse_levels_(other.top_sparse_levels_) {
1990 reset_bp_indexes();
1991 }
1992
1997 if (this == &other) {
1998 return *this;
1999 }
2000 values_ = other.values_;
2001 compare_ = other.compare_;
2002 bp_bits_ = other.bp_bits_;
2003 bp_bit_count_ = other.bp_bit_count_;
2004 top_sparse_candidates_ = other.top_sparse_candidates_;
2005 top_block_size_ = other.top_block_size_;
2006 top_block_count_ = other.top_block_count_;
2007 top_sparse_levels_ = other.top_sparse_levels_;
2008 reset_bp_indexes();
2009 return *this;
2010 }
2011
2016 : values_(other.values_),
2017 compare_(std::move(other.compare_)),
2018 bp_bits_(std::move(other.bp_bits_)),
2019 bp_bit_count_(other.bp_bit_count_),
2020 top_sparse_candidates_(std::move(other.top_sparse_candidates_)),
2021 top_block_size_(other.top_block_size_),
2022 top_block_count_(other.top_block_count_),
2023 top_sparse_levels_(other.top_sparse_levels_) {
2024 other.values_ = std::span<const T>();
2025 other.bp_bit_count_ = 0;
2026 other.top_block_size_ = kMinTopSparseBlockSize;
2027 other.top_block_count_ = 0;
2028 other.top_sparse_levels_ = 0;
2029 reset_bp_indexes();
2030 }
2031
2036 if (this == &other) {
2037 return *this;
2038 }
2039 values_ = other.values_;
2040 compare_ = std::move(other.compare_);
2041 bp_bits_ = std::move(other.bp_bits_);
2042 bp_bit_count_ = other.bp_bit_count_;
2043 top_sparse_candidates_ = std::move(other.top_sparse_candidates_);
2044 top_block_size_ = other.top_block_size_;
2045 top_block_count_ = other.top_block_count_;
2046 top_sparse_levels_ = other.top_sparse_levels_;
2047 other.values_ = std::span<const T>();
2048 other.bp_bit_count_ = 0;
2049 other.top_block_size_ = kMinTopSparseBlockSize;
2050 other.top_block_count_ = 0;
2051 other.top_sparse_levels_ = 0;
2052 reset_bp_indexes();
2053 return *this;
2054 }
2055
2059 std::size_t size_impl() const { return values_.size(); }
2060
2064 T value_at_impl(std::size_t position) const { return values_[position]; }
2065
2069 std::size_t arg_min_impl(std::size_t left, std::size_t right) const {
2070 if (left >= right || right > values_.size()) {
2071 return npos;
2072 }
2073 if constexpr (!UseTopSparseOverlay) {
2074 return cartesian_arg_min(left, right);
2075 }
2076 if (right - left <= top_block_size_) {
2077 return cartesian_arg_min(left, right);
2078 }
2079 const std::size_t top_answer = top_sparse_arg_min(left, right);
2080 if (top_answer != npos) {
2081 return top_answer;
2082 }
2083 return cartesian_arg_min(left, right);
2084 }
2085
2089 std::size_t bp_bit_count() const { return bp_bit_count_; }
2090
2094 std::span<const std::uint64_t> bp_words() const {
2095 return bp_storage_words().first(bp_word_count());
2096 }
2097
2101 static std::size_t top_sparse_block_size_for(std::size_t value_count) {
2102 if (value_count == 0) {
2103 return kMinTopSparseBlockSize;
2104 }
2105 return std::max(kMinTopSparseBlockSize,
2106 ceil_div(value_count, kMaxTopSparseBlocks));
2107 }
2108
2112 static std::size_t top_sparse_block_count_for(std::size_t value_count) {
2113 if (value_count == 0) {
2114 return 0;
2115 }
2116 return ceil_div(value_count, top_sparse_block_size_for(value_count));
2117 }
2118
2122 std::size_t top_sparse_block_size() const { return top_block_size_; }
2123
2127 std::size_t top_sparse_block_count() const { return top_block_count_; }
2128
2136 std::size_t memory_usage_bytes_impl() const {
2137 return sizeof(*this) + bp_bits_.allocated_bytes() +
2138 pixie::vector_capacity_bytes(top_sparse_candidates_) +
2139 pixie::optional_nested_owned_memory_bytes(bp_index_) +
2140 pixie::nested_owned_memory_bytes(bp_depth_rmq_);
2141 }
2142
2143 private:
2144 static pixie::AlignedStorage deserialize_aligned_storage(
2145 BinaryReader& reader) {
2146 const std::size_t size = reader.read_size();
2147 const std::span<const std::byte> bytes = reader.read_bytes(size);
2148 if (size > std::numeric_limits<std::size_t>::max() / 8) {
2149 throw std::length_error("Serialized RMQ storage is too large");
2150 }
2151 pixie::AlignedStorage result(size * 8);
2152 std::ranges::copy(bytes, result.writable_bytes().begin());
2153 return result;
2154 }
2155
2156 template <class State>
2157 static void validate_loaded_state(std::span<const T> values,
2158 const State& state,
2159 DeserializationValidation validation) {
2160 if (values.size() > (static_cast<std::size_t>(invalid_index) - 1) / 2) {
2161 throw std::length_error("Serialized RMQ value count is too large");
2162 }
2163 const std::size_t expected_bp_bit_count = 2 * values.size();
2164 if (state.bp_bit_count_ != expected_bp_bit_count) {
2165 throw std::invalid_argument("Invalid serialized RMQ BP bit count");
2166 }
2167
2168 std::size_t expected_padded_bits = 0;
2169 if (expected_bp_bit_count != 0) {
2170 const std::size_t depth_count = expected_bp_bit_count + 1;
2171 if (depth_count > std::numeric_limits<std::int64_t>::max()) {
2172 throw std::length_error("Serialized RMQ BP depth count is too large");
2173 }
2174 const std::size_t leaf_count = 1 + (depth_count - 1) / LeafSize;
2175 if (leaf_count > std::numeric_limits<std::size_t>::max() / LeafSize) {
2176 throw std::length_error("Serialized RMQ padded BP size is too large");
2177 }
2178 expected_padded_bits = leaf_count * LeafSize;
2179 }
2180 if (state.bp_bits_.size_bits() != expected_padded_bits) {
2181 throw std::invalid_argument("Invalid serialized RMQ BP storage size");
2182 }
2183
2184 if (values.empty()) {
2185 if (state.top_block_size_ != kMinTopSparseBlockSize ||
2186 state.top_block_count_ != 0 || state.top_sparse_levels_ != 0 ||
2187 !state.top_sparse_candidates_.empty() ||
2188 state.bp_index_.has_value() || !state.bp_depth_rmq_.empty()) {
2189 throw std::invalid_argument("Invalid serialized empty RMQ metadata");
2190 }
2191 const RankSelectSupport<> empty_rank;
2192 state.bp_depth_rmq_.validate_serialized_state(empty_rank, validation);
2193 return;
2194 }
2195
2196 const std::size_t expected_block_size =
2197 top_sparse_block_size_for(values.size());
2198 const std::size_t expected_block_count =
2199 top_sparse_block_count_for(values.size());
2200 const std::size_t expected_levels = std::bit_width(expected_block_count);
2201 if (state.top_block_size_ != expected_block_size ||
2202 state.top_block_count_ != expected_block_count ||
2203 state.top_sparse_levels_ != expected_levels ||
2204 expected_block_count >
2205 std::numeric_limits<std::size_t>::max() / expected_levels ||
2206 state.top_sparse_candidates_.size() !=
2207 expected_block_count * expected_levels ||
2208 !state.bp_index_.has_value() ||
2209 state.bp_index_->size() != expected_bp_bit_count ||
2210 state.bp_index_->supports_select1() ||
2211 !state.bp_index_->supports_select0() ||
2212 state.bp_depth_rmq_.size() != expected_bp_bit_count + 1) {
2213 throw std::invalid_argument("Invalid serialized RMQ index metadata");
2214 }
2215
2216 for (std::size_t level = 0;
2217 validation == DeserializationValidation::kQuick &&
2218 level < expected_levels;
2219 ++level) {
2220 const std::size_t span = std::size_t{1} << level;
2221 for (std::size_t block = 0; block < expected_block_count; ++block) {
2222 const std::size_t position = static_cast<std::size_t>(
2223 state.top_sparse_candidates_[level * expected_block_count + block]
2224 .position);
2225 const bool populated = block + span <= expected_block_count;
2226 if (!populated) {
2227 if (position != static_cast<std::size_t>(invalid_index)) {
2228 throw std::invalid_argument(
2229 "Invalid serialized RMQ sparse-table padding");
2230 }
2231 continue;
2232 }
2233 const std::size_t begin = block * expected_block_size;
2234 const std::size_t end =
2235 std::min(values.size(), (block + span) * expected_block_size);
2236 if (position < begin || position >= end) {
2237 throw std::invalid_argument(
2238 "Invalid serialized RMQ sparse-table candidate");
2239 }
2240 }
2241 }
2242 state.bp_depth_rmq_.validate_serialized_state(*state.bp_index_, validation);
2243 if (validation == DeserializationValidation::kFull) {
2244 validate_exact_source(values, state, expected_padded_bits);
2245 }
2246 }
2247
2248 template <class State>
2249 static void validate_exact_source(std::span<const T> values,
2250 const State& state,
2251 std::size_t expected_padded_bits) {
2252 const std::span<const std::uint64_t> bp_words = state.bp_bits_.as_words64();
2253 const auto bp_bit = [bp_words](std::size_t position) {
2254 return ((bp_words[position >> 6] >> (position & 63)) & 1u) != 0;
2255 };
2256 const auto require_bp_bit = [&](std::size_t position, bool expected) {
2257 if (bp_bit(position) != expected) {
2258 throw std::invalid_argument(
2259 "Serialized RMQ Cartesian BP disagrees with source values");
2260 }
2261 };
2262 const auto better_position = [&](std::size_t left, std::size_t right) {
2263 if (right == static_cast<std::size_t>(invalid_index)) {
2264 return left;
2265 }
2266 if (left == static_cast<std::size_t>(invalid_index)) {
2267 return right;
2268 }
2269 if (std::less<T>{}(values[right], values[left])) {
2270 return right;
2271 }
2272 if (std::less<T>{}(values[left], values[right])) {
2273 return left;
2274 }
2275 return std::min(left, right);
2276 };
2277
2278 utils::SuccinctIncreasingStack stack(values.size());
2279 std::size_t write_position = 2 * values.size();
2280 std::size_t block_minimum = static_cast<std::size_t>(invalid_index);
2281 for (std::size_t position = values.size(); position-- > 0;) {
2282 while (!stack.empty()) {
2283 const std::size_t top_position = values.size() - 1 - stack.top();
2284 if (std::less<T>{}(values[top_position], values[position])) {
2285 break;
2286 }
2287 stack.pop();
2288 require_bp_bit(--write_position, true);
2289 }
2290 stack.push(values.size() - 1 - position);
2291 require_bp_bit(--write_position, false);
2292
2293 block_minimum = better_position(block_minimum, position);
2294 if (position % state.top_block_size_ == 0) {
2295 const std::size_t block = position / state.top_block_size_;
2296 if (static_cast<std::size_t>(
2297 state.top_sparse_candidates_[block].position) !=
2298 block_minimum) {
2299 throw std::invalid_argument(
2300 "Serialized RMQ block minimum disagrees with source values");
2301 }
2302 block_minimum = static_cast<std::size_t>(invalid_index);
2303 }
2304 }
2305 while (write_position != 0) {
2306 require_bp_bit(--write_position, true);
2307 }
2308
2309 for (std::size_t position = 2 * values.size();
2310 position < expected_padded_bits; ++position) {
2311 if (bp_bit(position)) {
2312 throw std::invalid_argument("Serialized RMQ BP padding is non-zero");
2313 }
2314 }
2315
2316 for (std::size_t level = 1; level < state.top_sparse_levels_; ++level) {
2317 const std::size_t span = std::size_t{1} << level;
2318 const std::size_t half_span = span >> 1;
2319 const std::size_t current_offset = level * state.top_block_count_;
2320 const std::size_t previous_offset = (level - 1) * state.top_block_count_;
2321 for (std::size_t block = 0; block < state.top_block_count_; ++block) {
2322 const std::size_t actual = static_cast<std::size_t>(
2323 state.top_sparse_candidates_[current_offset + block].position);
2324 if (block + span > state.top_block_count_) {
2325 if (actual != static_cast<std::size_t>(invalid_index)) {
2326 throw std::invalid_argument(
2327 "Invalid serialized RMQ sparse-table padding");
2328 }
2329 continue;
2330 }
2331 const std::size_t left = static_cast<std::size_t>(
2332 state.top_sparse_candidates_[previous_offset + block].position);
2333 const std::size_t right = static_cast<std::size_t>(
2334 state.top_sparse_candidates_[previous_offset + block + half_span]
2335 .position);
2336 if (actual != better_position(left, right)) {
2337 throw std::invalid_argument(
2338 "Serialized RMQ sparse-table candidate disagrees with source");
2339 }
2340 }
2341 }
2342 }
2343
2344 void validate_serialized_state(DeserializationValidation validation) const {
2345 validate_loaded_state(values_, *this, validation);
2346 }
2347
2348 CartesianHybridBTree(LoadTag, std::span<const T> values, LoadedState&& state)
2349 : values_(values),
2350 compare_(),
2351 bp_bits_(std::move(state.bp_bits_)),
2352 bp_bit_count_(state.bp_bit_count_),
2353 top_sparse_candidates_(std::move(state.top_sparse_candidates_)),
2354 top_block_size_(state.top_block_size_),
2355 top_block_count_(state.top_block_count_),
2356 top_sparse_levels_(state.top_sparse_levels_),
2357 bp_index_(std::move(state.bp_index_)),
2358 bp_depth_rmq_(std::move(state.bp_depth_rmq_)) {
2359 if (bp_index_) {
2360 bp_depth_rmq_.restore_external_sources(bp_bits_.as_words64(), *bp_index_);
2361 } else {
2362 bp_depth_rmq_.restore_empty_sources();
2363 }
2364 }
2365
2370 std::size_t cartesian_arg_min(std::size_t left, std::size_t right) const {
2371 const std::size_t first_close = select_close_position(left + 1);
2372 const std::size_t last_close = select_close_position(right);
2373 if (first_close == npos || last_close == npos || first_close > last_close) {
2374 return npos;
2375 }
2376
2377 const std::size_t shifted_min =
2378 bp_depth_rmq_.arg_min(first_close + 1, last_close + 2);
2379 if (shifted_min == npos || shifted_min == 0) {
2380 return npos;
2381 }
2382 const RankSelectSupport<>& bp_index = *bp_index_;
2383 const std::size_t answer = bp_index.rank0(shifted_min) - 1;
2384 return answer < values_.size() ? answer : npos;
2385 }
2386
2390 void build() {
2391 bp_bits_.resize(0);
2392 bp_bit_count_ = 0;
2393 top_sparse_candidates_.clear();
2394 top_block_size_ = kMinTopSparseBlockSize;
2395 top_block_count_ = 0;
2396 top_sparse_levels_ = 0;
2397 reset_bp_indexes();
2398
2399 if (values_.empty()) {
2400 return;
2401 }
2402 if (values_.size() > (static_cast<std::size_t>(invalid_index) - 1) / 2) {
2403 throw std::length_error(
2404 "CartesianHybridBTree RMQ index type is too small");
2405 }
2406
2407 bp_bit_count_ = 2 * values_.size();
2408 bp_bits_.resize(padded_bp_bit_capacity());
2409 std::ranges::fill(bp_storage_words(), std::uint64_t{0});
2410 build_bp_bits();
2411 if constexpr (UseTopSparseOverlay) {
2412 build_top_sparse_table();
2413 }
2414 reset_bp_indexes();
2415 }
2416
2420 void build_bp_bits() {
2421 utils::SuccinctIncreasingStack stack(values_.size());
2422 std::size_t write_position = bp_bit_count_;
2423 std::span<std::uint64_t> words = bp_storage_words();
2424 const auto prepend_open = [&]() {
2425 --write_position;
2426 words[write_position >> 6] |= std::uint64_t{1} << (write_position & 63);
2427 };
2428
2429 for (std::size_t i = values_.size(); i-- > 0;) {
2430 while (!stack.empty() &&
2431 !compare_(values_[stack_index(stack.top())], values_[i])) {
2432 stack.pop();
2433 prepend_open();
2434 }
2435 stack.push(stack_key(i));
2436 --write_position;
2437 }
2438
2439 while (write_position != 0) {
2440 prepend_open();
2441 }
2442 }
2443
2448 std::size_t stack_key(std::size_t value_index) const {
2449 return values_.size() - 1 - value_index;
2450 }
2451
2456 std::size_t stack_index(std::size_t key) const {
2457 return values_.size() - 1 - key;
2458 }
2459
2463 void reset_bp_indexes() {
2464 bp_index_.reset();
2465 bp_depth_rmq_ = BpDepthRmq();
2466 if (bp_bit_count_ == 0) {
2467 return;
2468 }
2469 const std::span<const std::uint64_t> words = bp_words();
2470 const std::span<const std::uint64_t> padded_words = bp_storage_words();
2471 // TODO: try incorporating rank/select information into the tree.
2472 bp_index_.emplace(words, bp_bit_count_,
2473 RankSelectSupport<>::SelectSupport::kSelect0,
2474 values_.size());
2475 bp_depth_rmq_ = BpDepthRmq(padded_words, bp_bit_count_ + 1, *bp_index_);
2476 }
2477
2481 std::size_t bp_word_count() const { return ceil_div(bp_bit_count_, 64); }
2482
2486 std::size_t padded_bp_bit_capacity() const {
2487 if (bp_bit_count_ == 0) {
2488 return 0;
2489 }
2490 const std::size_t depth_count = bp_bit_count_ + 1;
2491 return ceil_div(depth_count, LeafSize) * LeafSize;
2492 }
2493
2497 std::span<std::uint64_t> bp_storage_words() {
2498 return bp_bits_.writable_words64();
2499 }
2500
2504 std::span<const std::uint64_t> bp_storage_words() const {
2505 return bp_bits_.as_words64();
2506 }
2507
2511 void build_top_sparse_table() {
2512 top_sparse_candidates_.clear();
2513 top_block_size_ = top_sparse_block_size_for(values_.size());
2514 top_block_count_ = ceil_div(values_.size(), top_block_size_);
2515 top_sparse_levels_ =
2516 top_block_count_ == 0 ? 0 : std::bit_width(top_block_count_);
2517 if (top_block_count_ == 0) {
2518 return;
2519 }
2520
2521 top_sparse_candidates_.assign(top_sparse_levels_ * top_block_count_,
2522 TopCandidate{});
2523 for (std::size_t block = 0; block < top_block_count_; ++block) {
2524 const std::size_t begin = block * top_block_size_;
2525 const std::size_t end = std::min(values_.size(), begin + top_block_size_);
2526 std::size_t minimum = begin;
2527 for (std::size_t position = begin + 1; position < end; ++position) {
2528 if (strictly_better_value_position(position, minimum)) {
2529 minimum = position;
2530 }
2531 }
2532 top_sparse_candidates_[block] = make_top_candidate(minimum);
2533 }
2534
2535 for (std::size_t level = 1; level < top_sparse_levels_; ++level) {
2536 const std::size_t span = std::size_t{1} << level;
2537 const std::size_t half_span = span >> 1;
2538 TopCandidate* current =
2539 top_sparse_candidates_.data() + level * top_block_count_;
2540 const TopCandidate* previous =
2541 top_sparse_candidates_.data() + (level - 1) * top_block_count_;
2542 for (std::size_t block = 0; block + span <= top_block_count_; ++block) {
2543 current[block] =
2544 better_top_candidate(previous[block], previous[block + half_span]);
2545 }
2546 }
2547 }
2548
2552 static std::size_t ceil_div(std::size_t value, std::size_t divisor) {
2553 return value == 0 ? 0 : 1 + (value - 1) / divisor;
2554 }
2555
2559 TopCandidate make_top_candidate(std::size_t position) const {
2560 if (position >= values_.size()) {
2561 return {};
2562 }
2563 return {static_cast<Index>(position)};
2564 }
2565
2569 bool valid_value_position(std::size_t position) const {
2570 return position != npos &&
2571 position != static_cast<std::size_t>(invalid_index) &&
2572 position < values_.size();
2573 }
2574
2579 bool strictly_better_value_position(std::size_t left,
2580 std::size_t right) const {
2581 if (!valid_value_position(left)) {
2582 return false;
2583 }
2584 if (!valid_value_position(right)) {
2585 return true;
2586 }
2587 if (compare_(values_[left], values_[right])) {
2588 return true;
2589 }
2590 if (compare_(values_[right], values_[left])) {
2591 return false;
2592 }
2593 return left < right;
2594 }
2595
2599 TopCandidate better_top_candidate(TopCandidate left,
2600 TopCandidate right) const {
2601 const std::size_t left_position = static_cast<std::size_t>(left.position);
2602 const std::size_t right_position = static_cast<std::size_t>(right.position);
2603 return strictly_better_value_position(right_position, left_position) ? right
2604 : left;
2605 }
2606
2610 TopCandidate top_sparse_block_arg_min(std::size_t block_left,
2611 std::size_t block_right) const {
2612 if (block_left >= block_right || block_right > top_block_count_ ||
2613 top_sparse_levels_ == 0) {
2614 return {};
2615 }
2616 const std::size_t length = block_right - block_left;
2617 const std::size_t level = std::bit_width(length) - 1;
2618 const std::size_t span = std::size_t{1} << level;
2619 const TopCandidate* table =
2620 top_sparse_candidates_.data() + level * top_block_count_;
2621 return better_top_candidate(table[block_left], table[block_right - span]);
2622 }
2623
2627 bool top_candidate_inside(TopCandidate candidate,
2628 std::size_t left,
2629 std::size_t right) const {
2630 const std::size_t position = static_cast<std::size_t>(candidate.position);
2631 return valid_value_position(position) && left <= position &&
2632 position < right;
2633 }
2634
2639 std::size_t top_sparse_arg_min(std::size_t left, std::size_t right) const {
2640 if (top_block_count_ <= 1) {
2641 return npos;
2642 }
2643
2644 const std::size_t padded_block_left = left / top_block_size_;
2645 const std::size_t padded_block_right = (right - 1) / top_block_size_ + 1;
2646 if (padded_block_left + 1 >= padded_block_right) {
2647 return npos;
2648 }
2649
2650 const TopCandidate padded =
2651 top_sparse_block_arg_min(padded_block_left, padded_block_right);
2652 if (top_candidate_inside(padded, left, right)) {
2653 return static_cast<std::size_t>(padded.position);
2654 }
2655
2656 const std::size_t first_full_block =
2657 (left + top_block_size_ - 1) / top_block_size_;
2658 const std::size_t full_block_right = right / top_block_size_;
2659 if (first_full_block >= full_block_right) {
2660 return npos;
2661 }
2662
2663 TopCandidate answer =
2664 top_sparse_block_arg_min(first_full_block, full_block_right);
2665
2666 const std::size_t left_border_end = first_full_block * top_block_size_;
2667 if (left < left_border_end) {
2668 answer = better_top_candidate(
2669 answer, make_top_candidate(cartesian_arg_min(left, left_border_end)));
2670 }
2671
2672 const std::size_t right_border_begin = full_block_right * top_block_size_;
2673 if (right_border_begin < right) {
2674 answer = better_top_candidate(
2675 answer,
2676 make_top_candidate(cartesian_arg_min(right_border_begin, right)));
2677 }
2678
2679 return valid_value_position(static_cast<std::size_t>(answer.position))
2680 ? static_cast<std::size_t>(answer.position)
2681 : npos;
2682 }
2683
2687 std::size_t select_close_position(std::size_t rank) const {
2688 if (rank == 0 || rank > values_.size() || !bp_index_) {
2689 return npos;
2690 }
2691 const std::size_t position = bp_index_->select0(rank);
2692 return position < bp_bit_count_ ? position : npos;
2693 }
2694
2695 std::span<const T> values_;
2696 Compare compare_;
2697 pixie::AlignedStorage bp_bits_;
2698 std::size_t bp_bit_count_ = 0;
2699 std::vector<TopCandidate> top_sparse_candidates_;
2700 std::size_t top_block_size_ = kMinTopSparseBlockSize;
2701 std::size_t top_block_count_ = 0;
2702 std::size_t top_sparse_levels_ = 0;
2703 std::optional<RankSelectSupport<>> bp_index_;
2704 BpDepthRmq bp_depth_rmq_;
2705};
2706
2715template <class T,
2716 class Compare = std::less<T>,
2717 class Index = std::size_t,
2718 std::size_t LeafSize = 512>
2720
2721} // namespace pixie::rmq
Owning storage with a logical byte size and 64-byte-aligned backing.
Definition aligned.h:41
Bounds-checked reader for canonical little-endian binary data.
Definition serialization.h:526
std::size_t remaining() const noexcept
Return the number of unconsumed bytes.
Definition serialization.h:544
std::uint8_t read_u8()
Read an unsigned eight-bit integer.
Definition serialization.h:552
BinaryReader read_subreader(std::size_t count)
Read a bounded region as an independent child reader.
Definition serialization.h:615
std::span< const std::byte > read_bytes(std::size_t count)
Read exactly count uninterpreted bytes.
Definition serialization.h:604
std::uint64_t read_u64()
Read an unsigned little-endian 64-bit integer.
Definition serialization.h:561
void require_zero_padding(std::size_t maximum)
Consume at most maximum trailing zero-padding bytes.
Definition serialization.h:633
std::uint32_t read_u32()
Read an unsigned little-endian 32-bit integer.
Definition serialization.h:558
std::size_t position() const noexcept
Return the number of bytes consumed by this reader.
Definition serialization.h:538
std::size_t read_size()
Read an unsigned 64-bit size and convert it to size_t.
Definition serialization.h:587
Bounded-buffer writer for canonical little-endian binary data.
Definition serialization.h:198
Rank/select support over an external packed bit sequence.
Definition support.h:58
CRTP facade for optional binary serialization and deserialization.
Definition serialization.h:693
static RankSelectSupport< AlignedStorage > deserialize(BinaryReader &reader, Context &&... context)
Definition serialization.h:710
std::span< const std::uint64_t > as_words64() const
Return a read-only view as 64-bit words.
Definition storage.h:181
Cartesian-tree value RMQ using HybridBTree-style LCA.
Definition cartesian_hybrid_btree.h:1765
void serialize_impl(BinaryWriter &writer) const
Serialize the complete owning RMQ metadata.
Definition cartesian_hybrid_btree.h:1838
static Self deserialize_impl(BinaryReader &reader, std::span< const std::int64_t > values, DeserializationValidation validation=DeserializationValidation::kQuick)
Restore owning RMQ metadata over caller-owned values.
Definition cartesian_hybrid_btree.h:1898
std::size_t memory_usage_bytes_impl() const
Return owned auxiliary memory usage in bytes.
Definition cartesian_hybrid_btree.h:2136
T value_at_impl(std::size_t position) const
Return the value at an indexed position.
Definition cartesian_hybrid_btree.h:2064
std::span< const std::uint64_t > bp_words() const
Return the packed BP words used by the RMQ encoding.
Definition cartesian_hybrid_btree.h:2094
CartesianHybridBTree(std::span< const T > values, Compare compare=Compare())
Build a Cartesian-tree RMQ index over values.
Definition cartesian_hybrid_btree.h:1824
CartesianHybridBTree & operator=(CartesianHybridBTree &&other) noexcept
Move-assign an RMQ index and rebuild internal non-owning views.
Definition cartesian_hybrid_btree.h:2035
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:1996
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:2112
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:2069
CartesianHybridBTree(const CartesianHybridBTree &other)
Copy an RMQ index and rebuild internal non-owning views.
Definition cartesian_hybrid_btree.h:1981
std::size_t size_impl() const
Return the number of indexed values.
Definition cartesian_hybrid_btree.h:2059
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:2101
std::size_t bp_bit_count() const
Return the number of BP bits in the Cartesian-tree RMQ encoding.
Definition cartesian_hybrid_btree.h:2089
CartesianHybridBTree(CartesianHybridBTree &&other) noexcept
Move an RMQ index and rebuild internal non-owning views.
Definition cartesian_hybrid_btree.h:2015
std::size_t top_sparse_block_size() const
Return the current top sparse-table block width.
Definition cartesian_hybrid_btree.h:2122
std::size_t top_sparse_block_count() const
Return the current number of top sparse-table blocks.
Definition cartesian_hybrid_btree.h:2127
CRTP facade for static range-minimum-query indexes.
Definition rmq.h:28
std::size_t size() const
Number of indexed values.
Definition rmq.h:40
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:68
std::size_t memory_usage_bytes() const
Return owned auxiliary memory usage in bytes.
Definition cartesian_hybrid_btree.h:159
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:103
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:134
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:184
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:115
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:215
std::size_t size() const
Return the number of indexed depth positions.
Definition cartesian_hybrid_btree.h:146
void build(std::span< const std::uint64_t > bits, std::size_t depth_count)
Definition cartesian_hybrid_btree.h:124
bool empty() const
Whether the indexed depth sequence is empty.
Definition cartesian_hybrid_btree.h:151
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:2719
Common interface for static range-minimum-query indexes.
DeserializationValidation
Validation strength used while restoring serialized indexes.
Definition serialization.h:28
@ kQuick
Check framing, dimensions, references, and other conditions needed for memory-safe terminating querie...
Definition serialization.h:33