Pixie
Loading...
Searching...
No Matches
tree.h
1#pragma once
2#include <immintrin.h>
3#include <pixie/bits.h>
4#include <pixie/detail/serialization.h>
5#include <pixie/rmm.h>
6
7#include <algorithm>
8#include <array>
9#include <bit>
10#include <climits>
11#include <cstddef>
12#include <cstdint>
13#include <limits>
14#include <span>
15#include <stdexcept>
16#include <vector>
17
18namespace pixie {
39class RmMTree : public RmMBase<RmMTree>, public SerializationBase<RmMTree> {
40 static constexpr std::array<std::uint8_t, 8> kSerializationMagic = {
41 'P', 'I', 'X', 'I', 'E', 'R', 'M', 'M'};
42 static constexpr std::uint32_t kSerializationVersion = 1;
43 static constexpr std::size_t kSerializationHeaderBytes = 32;
44
45 // ------------ bitvector ------------
46 std::span<const std::uint64_t> bits; // LSB-first, externally owned
47 size_t num_bits = 0; // number of bits
48
49 // ------------ blocking ------------
50 size_t block_bits = 64; // block size (bits), leaf covers <= block_bits bits
51 size_t leaf_count = 0; // #leaves = ceil(num_bits/block_bits)
52
53 // ------------ tree arrays (heap order: 1 is root) ------------
54 // size of segment (in bits) covered by node
55 // needed for: rank1/rank0, select1/select0, select10,
56 // excess, fwdsearch/bwdsearch/close/open/enclose,
57 // range_min_query/range_max_query, minselect.
58 std::vector<uint32_t> segment_size_bits;
59
60 // node_total_excess = total excess (+1 for '1', -1 for '0') on the node
61 // needed for: rank1/rank0, select1/select0, excess,
62 // fwdsearch/bwdsearch/close/open/enclose,
63 // range_min_query/range_max_query, mincount/minselect.
64 std::vector<int32_t> node_total_excess;
65
66 // node_min_prefix_excess = minimum pref-excess on the node (from 0)
67 // needed for: fwdsearch/bwdsearch/close/open/enclose, range_min_query,
68 // mincount/minselect.
69 std::vector<int32_t> node_min_prefix_excess;
70
71 // node_max_prefix_excess = maximum pref-excess on the node (from 0)
72 // needed for: fwdsearch/bwdsearch/close/open/enclose, range_max_query.
73 std::vector<int32_t> node_max_prefix_excess;
74
75 // node_min_count = number of positions where the minimum is attained
76 // needed for: mincount/minselect.
77 std::vector<uint32_t> node_min_count;
78
79 // node_pattern10_count = # of "10" pattern occurrences inside the node
80 // needed for: rank10, select10.
81 std::vector<uint32_t> node_pattern10_count;
82
83 // node_first_bit = first bit (0/1), node_last_bit = last bit (0/1) of the
84 // segment (to handle "10" crossing)
85 // both needed for: rank10, select10.
86 std::vector<uint8_t> node_first_bit, node_last_bit;
87
88 public:
92 static constexpr size_t npos = std::numeric_limits<size_t>::max();
93
94#ifdef DEBUG
95 float built_overhead = 0.0;
96#endif
97
98 // --------- construction ----------
99
103 RmMTree() = default;
104
118 explicit RmMTree(std::span<const std::uint64_t> words,
119 size_t bit_count,
120 const size_t& leaf_block_bits /*0=auto*/ = 0,
121 const float& max_overhead /*<0=off*/ = -1.0) {
122 build_from_words(words, bit_count, leaf_block_bits, max_overhead);
123 }
124
132 void serialize_impl(BinaryWriter& writer) const {
133 validate_serialized_state(DeserializationValidation::kQuick);
134
135 const std::size_t artifact_begin = writer.size_bytes();
136 detail::write_magic(writer, kSerializationMagic);
137 writer.write_u32(kSerializationVersion);
138 writer.write_u8(detail::kLittleEndianMarker);
139 writer.write_u8(sizeof(std::uint64_t));
140 writer.write_u16(0);
141 const std::size_t artifact_size_position = writer.write_u64_placeholder();
142 writer.write_size(num_bits);
143
144 writer.write_size(num_bits);
145 writer.write_size(block_bits);
146 writer.write_size(leaf_count);
147 writer.write_size(first_leaf_index);
148 detail::write_vector(writer,
149 std::span<const std::uint32_t>(segment_size_bits));
150 detail::write_vector(writer,
151 std::span<const std::int32_t>(node_total_excess));
152 detail::write_vector(writer,
153 std::span<const std::int32_t>(node_min_prefix_excess));
154 detail::write_vector(writer,
155 std::span<const std::int32_t>(node_max_prefix_excess));
156 detail::write_vector(writer,
157 std::span<const std::uint32_t>(node_min_count));
158 detail::write_vector(writer,
159 std::span<const std::uint32_t>(node_pattern10_count));
160 detail::write_vector(writer, std::span<const std::uint8_t>(node_first_bit));
161 detail::write_vector(writer, std::span<const std::uint8_t>(node_last_bit));
162
163 const std::size_t unpadded_size = writer.size_bytes() - artifact_begin;
164 writer.write_zeros(
165 (sizeof(std::uint64_t) - unpadded_size % sizeof(std::uint64_t)) %
166 sizeof(std::uint64_t));
167 const std::size_t artifact_size = writer.size_bytes() - artifact_begin;
168 writer.patch_u64(artifact_size_position,
169 static_cast<std::uint64_t>(artifact_size));
170 }
171
190 std::span<const std::uint64_t> words,
191 DeserializationValidation validation =
193 BinaryReader candidate = reader;
194 const std::size_t available_size = candidate.remaining();
195 detail::require_magic(candidate, kSerializationMagic);
196 if (candidate.read_u32() != kSerializationVersion ||
197 candidate.read_u8() != detail::kLittleEndianMarker ||
198 candidate.read_u8() != sizeof(std::uint64_t) ||
199 candidate.read_u16() != 0) {
200 throw std::invalid_argument("Incompatible serialized RmM artifact");
201 }
202 const std::size_t artifact_size = detail::checked_artifact_size(
203 candidate.read_u64(), kSerializationHeaderBytes, available_size);
204 const std::size_t source_bit_count = candidate.read_size();
205
206 BinaryReader payload =
207 candidate.read_subreader(artifact_size - kSerializationHeaderBytes);
208 RmMTree result;
209 result.bits = words;
210 result.num_bits = payload.read_size();
211 result.block_bits = payload.read_size();
212 result.leaf_count = payload.read_size();
213 result.first_leaf_index = payload.read_size();
214 result.segment_size_bits = detail::read_vector<std::uint32_t>(payload);
215 result.node_total_excess = detail::read_vector<std::int32_t>(payload);
216 result.node_min_prefix_excess = detail::read_vector<std::int32_t>(payload);
217 result.node_max_prefix_excess = detail::read_vector<std::int32_t>(payload);
218 result.node_min_count = detail::read_vector<std::uint32_t>(payload);
219 result.node_pattern10_count = detail::read_vector<std::uint32_t>(payload);
220 result.node_first_bit = detail::read_vector<std::uint8_t>(payload);
221 result.node_last_bit = detail::read_vector<std::uint8_t>(payload);
222 payload.require_zero_padding(sizeof(std::uint64_t) - 1);
223
224 if (source_bit_count != result.num_bits) {
225 throw std::invalid_argument(
226 "Serialized RmM source bit count is inconsistent");
227 }
228 result.validate_serialized_state(validation);
229 reader = candidate;
230 return result;
231 }
232
233 size_t size_impl() const { return num_bits; }
234
235 // --------- queries: rank/select/excess ----------
236
241 size_t rank1_impl(const size_t& end_position) const {
242 if (end_position == 0) {
243 return 0;
244 }
245 const size_t block_index = block_of(end_position - 1);
246 size_t ones_count = 0;
247 if (block_index > 0) {
248 size_t nodes_buffer[64];
249 const size_t node_count =
250 cover_blocks_collect(0, block_index - 1, nodes_buffer);
251 for (size_t j = 0; j < node_count; ++j) {
252 ones_count += ones_in_node(nodes_buffer[j]);
253 }
254 }
255 const size_t block_begin = block_index * block_bits;
256 const size_t block_end = std::min(num_bits, block_begin + block_bits);
257 ones_count +=
258 rank1_in_block(block_begin, std::min(end_position, block_end));
259 return ones_count;
260 }
261
266 size_t rank0_impl(const size_t& end_position) const {
267 return end_position - rank1_impl(end_position);
268 }
269
274 size_t select1_impl(size_t target_one_rank) const {
275 if (target_one_rank == 0 || num_bits == 0) {
276 return npos;
277 }
278 size_t node_index = 1;
279 if (ones_in_node(node_index) < target_one_rank) {
280 return npos;
281 }
282 size_t segment_base = 0;
283 while (node_index < first_leaf_index) {
284 const size_t left_child = node_index << 1;
285 const size_t right_child = left_child | 1;
286 const uint32_t ones_in_left_child = ones_in_node(left_child);
287 if (ones_in_left_child >= target_one_rank) {
288 node_index = left_child;
289 } else {
290 target_one_rank -= ones_in_left_child;
291 segment_base += segment_size_bits[left_child];
292 node_index = right_child;
293 }
294 }
295 return select1_in_block(
296 segment_base,
297 std::min(segment_base + segment_size_bits[node_index], num_bits),
298 target_one_rank);
299 }
300
305 size_t select0_impl(size_t target_zero_rank) const {
306 if (target_zero_rank == 0 || num_bits == 0) {
307 return npos;
308 }
309 size_t node_index = 1;
310 const auto zeros_in_node = [&](const size_t& node) noexcept {
311 return segment_size_bits[node] - ones_in_node(node);
312 };
313 if (zeros_in_node(node_index) < target_zero_rank) {
314 return npos;
315 }
316 size_t segment_base = 0;
317 while (node_index < first_leaf_index) {
318 const size_t left_child = node_index << 1;
319 const size_t right_child = left_child | 1;
320 const size_t zeros_in_left_child = zeros_in_node(left_child);
321 if (zeros_in_left_child >= target_zero_rank) {
322 node_index = left_child;
323 } else {
324 target_zero_rank -= zeros_in_left_child;
325 segment_base += segment_size_bits[left_child];
326 node_index = right_child;
327 }
328 }
329 return select0_in_block(
330 segment_base,
331 std::min(segment_base + segment_size_bits[node_index], num_bits),
332 target_zero_rank);
333 }
334
340 size_t rank10_impl(const size_t& end_position) const {
341 if (end_position <= 1) {
342 return 0;
343 }
344 const size_t block_index = block_of(end_position - 1);
345 size_t pattern_count = 0;
346 int previous_last_bit = -1;
347
348 if (block_index > 0) {
349 const auto covered_nodes = cover_blocks(0, block_index - 1);
350 for (const size_t& node_index : covered_nodes) {
351 pattern_count += node_pattern10_count[node_index];
352 if (previous_last_bit != -1 && previous_last_bit == 1 &&
353 node_first_bit[node_index] == 0) {
354 ++pattern_count;
355 }
356 previous_last_bit = node_last_bit[node_index];
357 }
358 }
359 const size_t block_begin = block_index * block_bits;
360 pattern_count += rr_in_block(block_begin, end_position);
361 // boundary between the last full node and the leaf tail
362 if (block_index > 0 && end_position > block_begin &&
363 previous_last_bit == 1 && bit(block_begin) == 0) {
364 ++pattern_count;
365 }
366 return pattern_count;
367 }
368
373 size_t select10_impl(size_t target_pattern_rank) const {
374 if (target_pattern_rank == 0 || num_bits == 0) {
375 return npos;
376 }
377 size_t node_index = 1;
378 if (node_pattern10_count[node_index] < target_pattern_rank) {
379 return npos;
380 }
381 const size_t tree_size = segment_size_bits.size() - 1;
382 size_t segment_base = 0;
383 while (node_index < first_leaf_index) {
384 const size_t left_child = node_index << 1;
385 const size_t left_segment_size =
386 (left_child <= tree_size) ? segment_size_bits[left_child] : 0;
387 if (left_segment_size == 0) {
388 return npos;
389 }
390
391 const size_t left_count = node_pattern10_count[left_child];
392 if (left_count >= target_pattern_rank) {
393 node_index = left_child;
394 continue;
395 }
396
397 size_t remaining_rank = target_pattern_rank - left_count;
398 const size_t right_child = left_child | 1;
399 const bool has_right =
400 (right_child <= tree_size) && (segment_size_bits[right_child] != 0);
401 if (!has_right) {
402 return npos;
403 }
404
405 const size_t crossing_pattern =
406 (node_last_bit[left_child] == 1 && node_first_bit[right_child] == 0)
407 ? 1u
408 : 0u;
409 if (crossing_pattern) {
410 if (remaining_rank == 1) {
411 return segment_base + left_segment_size - 1;
412 }
413 --remaining_rank;
414 }
415 segment_base += left_segment_size;
416 node_index = right_child;
417 target_pattern_rank = remaining_rank;
418 }
419 return select10_in_block(
420 segment_base,
421 std::min(segment_base + segment_size_bits[node_index], num_bits),
422 target_pattern_rank);
423 }
424
428 inline int excess_impl(const size_t& end_position) const {
429 return int64_t(rank1_impl(end_position)) * 2 - int64_t(end_position);
430 }
431
438 size_t fwdsearch_impl(const size_t& start_position, const int& delta) const {
439 if (start_position >= num_bits) {
440 return npos;
441 }
442
443 // 1) scan the remainder of the current leaf
444 const size_t leaf_block_index = block_of(start_position);
445 const size_t block_begin = leaf_block_index * block_bits;
446 const size_t block_end = std::min(num_bits, block_begin + block_bits);
447 int leaf_delta = 0;
448 const size_t leaf_result = leaf_fwd_bp_simd(
449 leaf_block_index, block_begin, start_position, delta, leaf_delta);
450 if (leaf_result != npos) {
451 return leaf_result;
452 }
453
454 int remaining_delta = delta - leaf_delta;
455 size_t segment_base = block_end;
456 if (remaining_delta == 0) {
457 return segment_base;
458 }
459
460 // Tree-walk to the right:
461 // go up; whenever we come from a left child, try the right sibling subtree.
462 // If target is inside sibling -> descend; else skip it and continue up.
463 size_t node_index = leaf_index_of(block_begin);
464 const size_t tree_size = segment_size_bits.size() - 1;
465
466 // If we are already at/after the last leaf boundary, there's nothing to
467 // scan.
468 if (segment_base >= num_bits || leaf_block_index + 1 >= leaf_count) {
469 return npos;
470 }
471
472 while (node_index > 1) {
473 const bool is_left_child = ((node_index & 1u) == 0u);
474 if (is_left_child) {
475 const size_t sibling = node_index | 1u; // right sibling
476 if (sibling <= tree_size && segment_size_bits[sibling]) {
477 // Boundary at sibling start already handled above via
478 // remaining_delta==0.
479 if (node_min_prefix_excess[sibling] <= remaining_delta &&
480 remaining_delta <= node_max_prefix_excess[sibling]) {
481 return descend_fwd(sibling, remaining_delta, segment_base);
482 }
483 // Skip whole sibling subtree.
484 remaining_delta -= node_total_excess[sibling];
485 segment_base += segment_size_bits[sibling];
486 if (remaining_delta == 0) {
487 return segment_base;
488 }
489 }
490 }
491 node_index >>= 1;
492 }
493 return npos;
494 }
495
502 size_t bwdsearch_impl(const size_t& start_position, const int& delta) const {
503 if (start_position > num_bits || start_position == 0) {
504 return npos;
505 }
506
507 // 1) scan inside the block
508 const size_t leaf_block_index = block_of(start_position - 1);
509 const size_t block_begin = leaf_block_index * block_bits;
510 int leaf_delta =
511 0; // excess_impl(start_position) - excess_impl(block_begin)
512 const size_t leaf_result = leaf_bwd_bp_simd(
513 leaf_block_index, block_begin, start_position, delta, leaf_delta);
514 if (leaf_result != npos) {
515 return leaf_result;
516 }
517
518 // need = target - excess_impl(block_begin) = excess_impl(start_position) +
519 // delta - excess_impl(block_begin) = leaf_delta + delta
520 int remaining_delta = leaf_delta + delta;
521 size_t node_index = leaf_index_of(block_begin);
522 size_t segment_base = block_begin;
523 while (node_index > 1) {
524 if (node_index & 1) { // node_index is the right child
525 const size_t sibling_index = node_index ^ 1; // left sibling
526 const size_t sibling_border =
527 segment_base; // right border of the sibling (== start(node_index))
528 const int needed_inside_sibling =
529 remaining_delta +
530 node_total_excess[sibling_index]; // target in coordinates relative
531 // to the start of sibling
532 const bool allow_right_border =
533 (sibling_border != start_position); // j must be < start_position
534
535 // try inside the sibling, but return only if a position is found
536 if (needed_inside_sibling == 0 ||
537 (node_min_prefix_excess[sibling_index] <= needed_inside_sibling &&
538 needed_inside_sibling <= node_max_prefix_excess[sibling_index])) {
539 const size_t result = descend_bwd(
540 sibling_index, sibling_border - segment_size_bits[sibling_index],
541 needed_inside_sibling, sibling_border, allow_right_border);
542 if (result != npos) {
543 return result;
544 }
545 }
546 // junction between children is a separate branch (allowed only if < i)
547 if (needed_inside_sibling == node_total_excess[sibling_index] &&
548 sibling_border < start_position) {
549 return sibling_border;
550 }
551
552 // stepped over the sibling, shifted the zero point of the coordinates
553 remaining_delta += node_total_excess[sibling_index];
554 segment_base -= segment_size_bits[sibling_index];
555 }
556 node_index >>= 1;
557 }
558 return npos;
559 }
560
567 size_t range_min_query_pos_impl(const size_t& range_begin,
568 const size_t& range_end) const {
569 if (range_begin > range_end || range_end >= num_bits) {
570 return npos;
571 }
572
573 const size_t begin_block_index = block_of(range_begin);
574 const size_t begin_block_start = begin_block_index * block_bits;
575 const size_t begin_block_end =
576 std::min(num_bits, begin_block_start + block_bits);
577 const size_t end_block_index = block_of(range_end);
578 const size_t end_block_start = end_block_index * block_bits;
579
580 int best_value = INT_MAX;
581 size_t best_position = npos;
582 size_t chosen_node_index = 0;
583 int prefix_excess = 0, prefix_at_choice = 0;
584
585 // prefix
586 int min_prefix_first_chunk = INT_MAX;
587 size_t first_chunk_position = npos;
588 const size_t end_of_first_chunk = std::min(
589 range_end, (size_t)(begin_block_end ? begin_block_end - 1 : 0));
590 if (range_begin <= end_of_first_chunk) {
591 first_min_value_pos8(range_begin, end_of_first_chunk,
592 min_prefix_first_chunk, first_chunk_position);
593 prefix_excess =
594 (int64_t)rank1_in_block(range_begin, end_of_first_chunk + 1) * 2 -
595 int64_t(end_of_first_chunk + 1 - range_begin);
596 best_value = min_prefix_first_chunk;
597 best_position = first_chunk_position;
598 chosen_node_index = 0;
599 }
600
601 // middle
602 if (begin_block_index + 1 <= end_block_index - 1) {
603 size_t left_index = first_leaf_index + (begin_block_index + 1);
604 size_t right_index = first_leaf_index + (end_block_index - 1);
605 size_t right_nodes[64];
606 int right_nodes_count = 0;
607
608 while (left_index <= right_index) {
609 if (left_index & 1) {
610 const size_t node_index = left_index++;
611 const int candidate =
612 prefix_excess + node_min_prefix_excess[node_index];
613 if (candidate < best_value) {
614 best_value = candidate;
615 best_position = npos;
616 chosen_node_index = node_index;
617 prefix_at_choice = prefix_excess;
618 }
619 prefix_excess += node_total_excess[node_index];
620 }
621 if ((right_index & 1) == 0) {
622 right_nodes[right_nodes_count++] = right_index--;
623 }
624 left_index >>= 1;
625 right_index >>= 1;
626 }
627 while (right_nodes_count--) {
628 const size_t node_index = right_nodes[right_nodes_count];
629 const int candidate =
630 prefix_excess + node_min_prefix_excess[node_index];
631 if (candidate < best_value) {
632 best_value = candidate;
633 best_position = npos;
634 chosen_node_index = node_index;
635 prefix_at_choice = prefix_excess;
636 }
637 prefix_excess += node_total_excess[node_index];
638 }
639 }
640
641 // tail
642 if (end_block_index != begin_block_index) {
643 int min_prefix_last_chunk;
644 size_t last_chunk_position;
645 first_min_value_pos8(end_block_start, range_end, min_prefix_last_chunk,
646 last_chunk_position);
647 const int candidate = prefix_excess + min_prefix_last_chunk;
648 if (candidate < best_value) {
649 best_value = candidate;
650 best_position = last_chunk_position;
651 chosen_node_index = 0;
652 }
653 }
654
655 if (best_position != npos) {
656 return best_position;
657 }
658
659 return descend_first_min(chosen_node_index, best_value - prefix_at_choice,
660 node_base(chosen_node_index));
661 }
662
669 int range_min_query_val_impl(const size_t& range_begin,
670 const size_t& range_end) const {
671 if (range_begin > range_end || range_end >= num_bits) {
672 return 0;
673 }
674 size_t min_position = range_min_query_pos_impl(range_begin, range_end);
675 if (min_position == npos) {
676 return 0;
677 }
678 return excess_impl(min_position + 1) - excess_impl(range_begin);
679 }
680
687 size_t range_max_query_pos_impl(const size_t& range_begin,
688 const size_t& range_end) const {
689 if (range_begin > range_end || range_end >= num_bits) {
690 return npos;
691 }
692
693 const size_t begin_block_index = block_of(range_begin);
694 const size_t begin_block_start = begin_block_index * block_bits;
695 const size_t begin_block_end =
696 std::min(num_bits, begin_block_start + block_bits);
697 const size_t end_block_index = block_of(range_end);
698 const size_t end_block_start = end_block_index * block_bits;
699
700 int best_value = INT_MIN;
701 size_t best_position = npos;
702 size_t chosen_node_index = 0;
703 int prefix_excess = 0, prefix_at_choice = 0;
704
705 // prefix
706 int max_prefix_first_chunk = INT_MIN;
707 size_t first_chunk_position = npos;
708 const size_t end_of_first_chunk = std::min(
709 range_end, (size_t)(begin_block_end ? begin_block_end - 1 : 0));
710 if (range_begin <= end_of_first_chunk) {
711 first_max_value_pos8(range_begin, end_of_first_chunk,
712 max_prefix_first_chunk, first_chunk_position);
713 prefix_excess =
714 (int64_t)rank1_in_block(range_begin, end_of_first_chunk + 1) * 2 -
715 int64_t(end_of_first_chunk + 1 - range_begin);
716 best_value = max_prefix_first_chunk;
717 best_position = first_chunk_position;
718 chosen_node_index = 0;
719 }
720
721 // middle
722 if (begin_block_index + 1 <= end_block_index - 1) {
723 size_t left_index = first_leaf_index + (begin_block_index + 1);
724 size_t right_index = first_leaf_index + (end_block_index - 1);
725 size_t right_nodes[64];
726 int right_nodes_count = 0;
727
728 while (left_index <= right_index) {
729 if (left_index & 1) {
730 const size_t node_index = left_index++;
731 const int candidate =
732 prefix_excess + node_max_prefix_excess[node_index];
733 if (candidate > best_value) {
734 best_value = candidate;
735 best_position = npos;
736 chosen_node_index = node_index;
737 prefix_at_choice = prefix_excess;
738 }
739 prefix_excess += node_total_excess[node_index];
740 }
741 if ((right_index & 1) == 0) {
742 right_nodes[right_nodes_count++] = right_index--;
743 }
744 left_index >>= 1;
745 right_index >>= 1;
746 }
747 while (right_nodes_count--) {
748 const size_t node_index = right_nodes[right_nodes_count];
749 const int candidate =
750 prefix_excess + node_max_prefix_excess[node_index];
751 if (candidate > best_value) {
752 best_value = candidate;
753 best_position = npos;
754 chosen_node_index = node_index;
755 prefix_at_choice = prefix_excess;
756 }
757 prefix_excess += node_total_excess[node_index];
758 }
759 }
760
761 // tail
762 if (end_block_index != begin_block_index) {
763 int max_prefix_last_chunk;
764 size_t last_chunk_position;
765 first_max_value_pos8(end_block_start, range_end, max_prefix_last_chunk,
766 last_chunk_position);
767 const int candidate = prefix_excess + max_prefix_last_chunk;
768 if (candidate > best_value) {
769 best_value = candidate;
770 best_position = last_chunk_position;
771 chosen_node_index = 0;
772 }
773 }
774
775 if (best_position != npos) {
776 return best_position;
777 }
778
779 return descend_first_max(chosen_node_index, best_value - prefix_at_choice,
780 node_base(chosen_node_index));
781 }
782
787 int range_max_query_val_impl(const size_t& range_begin,
788 const size_t& range_end) const {
789 if (range_begin > range_end || range_end >= num_bits) {
790 return 0;
791 }
792 size_t max_position = range_max_query_pos_impl(range_begin, range_end);
793 if (max_position == npos) {
794 return 0;
795 }
796 return excess_impl(max_position + 1) - excess_impl(range_begin);
797 }
798
803 size_t mincount_impl(const size_t& range_begin,
804 const size_t& range_end) const {
805 if (range_begin > range_end || range_end >= num_bits) {
806 return 0;
807 }
808
809 const size_t begin_block_index = block_of(range_begin);
810 const size_t begin_block_start = begin_block_index * block_bits;
811 const size_t begin_block_end =
812 std::min(num_bits, begin_block_start + block_bits);
813 const size_t end_block_index = block_of(range_end);
814 const size_t end_block_start = end_block_index * block_bits;
815
816 int best_value = INT_MAX;
817 size_t min_count = 0;
818 int prefix_excess = 0;
819
820 // first chunk
821 {
822 int current_excess = 0, min_value = INT_MAX, local_count = 0;
823 const size_t end_of_first_chunk =
824 std::min(range_end, begin_block_end - 1);
825 for (size_t position = range_begin; position <= end_of_first_chunk;
826 ++position) {
827 current_excess += bit(position) ? +1 : -1;
828 if (current_excess < min_value) {
829 min_value = current_excess;
830 local_count = 1;
831 } else if (current_excess == min_value) {
832 ++local_count;
833 }
834 }
835 best_value = min_value;
836 min_count = local_count;
837 prefix_excess = current_excess; // offset toward the middle
838 }
839
840 // middle
841 if (begin_block_index + 1 <= end_block_index - 1) {
842 const auto middle_nodes =
843 cover_blocks(begin_block_index + 1, end_block_index - 1);
844 for (const size_t& node_index : middle_nodes) {
845 const int candidate =
846 prefix_excess + node_min_prefix_excess[node_index];
847 if (candidate < best_value) {
848 best_value = candidate;
849 min_count = node_min_count[node_index];
850 } else if (candidate == best_value) {
851 min_count += node_min_count[node_index];
852 }
853 prefix_excess += node_total_excess[node_index];
854 }
855 }
856
857 // last chunk
858 if (end_block_index != begin_block_index) {
859 int current_excess = 0, min_value = INT_MAX, local_count = 0;
860 for (size_t position = end_block_start; position <= range_end;
861 ++position) {
862 current_excess += bit(position) ? +1 : -1;
863 if (current_excess < min_value) {
864 min_value = current_excess;
865 local_count = 1;
866 } else if (current_excess == min_value) {
867 ++local_count;
868 }
869 }
870 const int candidate = prefix_excess + min_value;
871 if (candidate < best_value) {
872 best_value = candidate;
873 min_count = local_count;
874 } else if (candidate == best_value) {
875 min_count += local_count;
876 }
877 }
878 return min_count;
879 }
880
887 size_t minselect_impl(const size_t& range_begin,
888 const size_t& range_end,
889 size_t target_min_rank) const {
890 if (range_begin > range_end || range_end >= num_bits ||
891 target_min_rank == 0) {
892 return npos;
893 }
894
895 const size_t begin_block_index = block_of(range_begin);
896 const size_t begin_block_start = begin_block_index * block_bits;
897 const size_t begin_block_end =
898 std::min(num_bits, begin_block_start + block_bits);
899 const size_t end_block_index = block_of(range_end);
900 const size_t end_block_start = end_block_index * block_bits;
901
902 // prefix
903 const size_t end_of_first_chunk = std::min(range_end, begin_block_end - 1);
904 int current_first_chunk_excess = 0, min_first_chunk = 0;
905 uint32_t count_first_chunk = 0;
906
907 if (range_begin <= end_of_first_chunk) {
908 scan_range_min_count8(range_begin, end_of_first_chunk,
909 current_first_chunk_excess, min_first_chunk,
910 count_first_chunk);
911 } else {
912 current_first_chunk_excess = 0;
913 min_first_chunk = INT_MAX;
914 count_first_chunk = 0;
915 }
916
917 int best_value = (min_first_chunk == INT_MAX ? INT_MAX : min_first_chunk);
918 size_t total_count =
919 (min_first_chunk == INT_MAX ? 0u : (size_t)count_first_chunk);
920 int prefix_excess = current_first_chunk_excess; // offset for middle
921
922 size_t left_index = first_leaf_index + begin_block_index + 1;
923 size_t right_index = first_leaf_index + end_block_index - 1;
924 size_t right_nodes[64];
925 int right_nodes_count = 0;
926
927 // middle
928 if (begin_block_index + 1 <= end_block_index - 1) {
929 while (left_index <= right_index) {
930 if (left_index & 1) {
931 const int candidate =
932 prefix_excess + node_min_prefix_excess[left_index];
933 if (candidate < best_value) {
934 best_value = candidate;
935 total_count = node_min_count[left_index];
936 } else if (candidate == best_value) {
937 total_count += node_min_count[left_index];
938 }
939 prefix_excess += node_total_excess[left_index++];
940 }
941 if ((right_index & 1) == 0) {
942 right_nodes[right_nodes_count++] = right_index--;
943 }
944 left_index >>= 1;
945 right_index >>= 1;
946 }
947 while (right_nodes_count--) {
948 const size_t node_index = right_nodes[right_nodes_count];
949 const int candidate =
950 prefix_excess + node_min_prefix_excess[node_index];
951 if (candidate < best_value) {
952 best_value = candidate;
953 total_count = node_min_count[node_index];
954 } else if (candidate == best_value) {
955 total_count += node_min_count[node_index];
956 }
957 prefix_excess += node_total_excess[node_index];
958 }
959 }
960
961 // tail
962 int current_last_chunk_excess = 0, min_last_chunk = INT_MAX;
963 uint32_t count_last_chunk = 0;
964 if (end_block_index != begin_block_index) {
965 scan_range_min_count8(end_block_start, range_end,
966 current_last_chunk_excess, min_last_chunk,
967 count_last_chunk);
968 const int candidate = prefix_excess + min_last_chunk;
969 if (candidate < best_value) {
970 best_value = candidate;
971 total_count = count_last_chunk;
972 } else if (candidate == best_value) {
973 total_count += count_last_chunk;
974 }
975 }
976
977 if (target_min_rank > total_count) {
978 return npos;
979 }
980
981 // prefix
982 if (min_first_chunk == best_value && count_first_chunk) {
983 if (target_min_rank <= count_first_chunk) {
984 return qth_min_in_block(range_begin, end_of_first_chunk,
985 target_min_rank);
986 }
987 target_min_rank -= count_first_chunk;
988 }
989
990 // middle
991 prefix_excess = current_first_chunk_excess;
992 if (begin_block_index + 1 <= end_block_index - 1) {
993 left_index = first_leaf_index + (begin_block_index + 1);
994 right_index = first_leaf_index + (end_block_index - 1);
995 right_nodes_count = 0;
996 while (left_index <= right_index) {
997 if (left_index & 1) {
998 const size_t node_index = left_index++;
999 const int candidate =
1000 prefix_excess + node_min_prefix_excess[node_index];
1001 if (candidate == best_value) {
1002 if (target_min_rank <= node_min_count[node_index]) {
1003 return descend_qth_min(node_index, best_value - prefix_excess,
1004 target_min_rank, node_base(node_index));
1005 }
1006 target_min_rank -= node_min_count[node_index];
1007 }
1008 prefix_excess += node_total_excess[node_index];
1009 }
1010 if (!(right_index & 1)) {
1011 right_nodes[right_nodes_count++] = right_index--;
1012 }
1013 left_index >>= 1;
1014 right_index >>= 1;
1015 }
1016 while (right_nodes_count--) {
1017 const size_t node_index = right_nodes[right_nodes_count];
1018 const int candidate =
1019 prefix_excess + node_min_prefix_excess[node_index];
1020 if (candidate == best_value) {
1021 if (target_min_rank <= node_min_count[node_index]) {
1022 return descend_qth_min(node_index, best_value - prefix_excess,
1023 target_min_rank, node_base(node_index));
1024 }
1025 target_min_rank -= node_min_count[node_index];
1026 }
1027 prefix_excess += node_total_excess[node_index];
1028 }
1029 }
1030
1031 // tail
1032 if (end_block_index != begin_block_index &&
1033 (prefix_excess + min_last_chunk) == best_value) {
1034 return qth_min_in_block(end_block_start, range_end, target_min_rank);
1035 }
1036
1037 return npos;
1038 }
1039
1040 // ----- parentheses navigation (BP) -----
1041
1047 inline size_t close_impl(const size_t& open_position) const {
1048 if (open_position >= num_bits) {
1049 return npos;
1050 }
1051 if (!bit(open_position)) {
1052 return open_position;
1053 }
1054 return fwdsearch_impl(open_position, 0);
1055 }
1056
1062 inline size_t open_impl(const size_t& close_position) const {
1063 if (close_position >= num_bits) {
1064 return npos;
1065 }
1066 if (bit(close_position)) {
1067 return close_position;
1068 }
1069 return bwdsearch_impl(close_position + 1, 0);
1070 }
1071
1077 inline size_t enclose_impl(const size_t& position) const {
1078 if (position >= num_bits) {
1079 return npos;
1080 }
1081 if (!bit(position)) {
1082 return open_impl(position);
1083 }
1084 return bwdsearch_impl(position + 1, -2);
1085 }
1086
1090 inline int bit(const size_t& position) const noexcept {
1091 return (bits[position >> 6] >> (position & 63)) & 1u;
1092 }
1093
1094 private:
1095 void validate_serialized_state(DeserializationValidation validation) const {
1096 const std::size_t required_words =
1097 num_bits == 0 ? 0 : 1 + (num_bits - 1) / 64;
1098 if (required_words > bits.size()) {
1099 throw std::invalid_argument("RmM source word span is too small");
1100 }
1101 if (block_bits == 0 || !std::has_single_bit(block_bits) ||
1102 block_bits > std::numeric_limits<std::uint32_t>::max()) {
1103 throw std::invalid_argument("Invalid serialized RmM block size");
1104 }
1105
1106 const std::size_t expected_leaf_count =
1107 num_bits == 0 ? 0 : 1 + (num_bits - 1) / block_bits;
1108 if (leaf_count != expected_leaf_count ||
1109 leaf_count > std::bit_floor(std::numeric_limits<std::size_t>::max())) {
1110 throw std::invalid_argument("Invalid serialized RmM leaf count");
1111 }
1112 const std::size_t expected_first_leaf =
1113 std::bit_ceil(std::max<std::size_t>(1, leaf_count));
1114 if (first_leaf_index != expected_first_leaf ||
1115 leaf_count >
1116 std::numeric_limits<std::size_t>::max() - first_leaf_index) {
1117 throw std::invalid_argument("Invalid serialized RmM tree shape");
1118 }
1119
1120 const std::size_t expected_node_count = num_bits == 0
1121 ? segment_size_bits.size()
1122 : first_leaf_index + leaf_count;
1123 const bool supported_empty_shape =
1124 num_bits != 0 || expected_node_count == 0 || expected_node_count == 1;
1125 if (!supported_empty_shape ||
1126 (num_bits != 0 && segment_size_bits.size() != expected_node_count) ||
1127 node_total_excess.size() != segment_size_bits.size() ||
1128 node_min_prefix_excess.size() != segment_size_bits.size() ||
1129 node_max_prefix_excess.size() != segment_size_bits.size() ||
1130 node_min_count.size() != segment_size_bits.size() ||
1131 node_pattern10_count.size() != segment_size_bits.size() ||
1132 node_first_bit.size() != segment_size_bits.size() ||
1133 node_last_bit.size() != segment_size_bits.size()) {
1134 throw std::invalid_argument(
1135 "Invalid serialized RmM metadata vector sizes");
1136 }
1137
1138 const std::size_t node_count = segment_size_bits.size();
1139 const auto node_is_zero = [&](std::size_t node) {
1140 return segment_size_bits[node] == 0 && node_total_excess[node] == 0 &&
1141 node_min_prefix_excess[node] == 0 &&
1142 node_max_prefix_excess[node] == 0 && node_min_count[node] == 0 &&
1143 node_pattern10_count[node] == 0 && node_first_bit[node] == 0 &&
1144 node_last_bit[node] == 0;
1145 };
1146 if (node_count != 0 && !node_is_zero(0)) {
1147 throw std::invalid_argument("Invalid serialized RmM sentinel metadata");
1148 }
1149 if (num_bits == 0) {
1150 if (node_count == 1 && !node_is_zero(0)) {
1151 throw std::invalid_argument("Invalid serialized empty RmM metadata");
1152 }
1153 return;
1154 }
1155
1156 const auto signed_magnitude = [](std::int64_t value) {
1157 return value < 0
1158 ? static_cast<std::uint64_t>(-(value + 1)) + std::uint64_t{1}
1159 : static_cast<std::uint64_t>(value);
1160 };
1161
1162 for (std::size_t leaf = 0; leaf < leaf_count; ++leaf) {
1163 const std::size_t node = first_leaf_index + leaf;
1164 const std::size_t begin = leaf * block_bits;
1165 const std::size_t expected_size = std::min(block_bits, num_bits - begin);
1166 if (validation == DeserializationValidation::kFull) {
1167 std::int64_t total = 0;
1168 std::int64_t minimum = std::numeric_limits<std::int64_t>::max();
1169 std::int64_t maximum = std::numeric_limits<std::int64_t>::min();
1170 std::uint64_t minimum_count = 0;
1171 std::uint64_t pattern10_count = 0;
1172 std::uint8_t previous_bit = 0;
1173 for (std::size_t position = begin; position < begin + expected_size;
1174 ++position) {
1175 const std::uint8_t current_bit =
1176 static_cast<std::uint8_t>(bit(position));
1177 if (position != begin && previous_bit == 1 && current_bit == 0) {
1178 ++pattern10_count;
1179 }
1180 total += current_bit != 0 ? 1 : -1;
1181 if (total < minimum) {
1182 minimum = total;
1183 minimum_count = 1;
1184 } else if (total == minimum) {
1185 ++minimum_count;
1186 }
1187 maximum = std::max(maximum, total);
1188 previous_bit = current_bit;
1189 }
1190 if (segment_size_bits[node] != expected_size ||
1191 node_total_excess[node] != total ||
1192 node_min_prefix_excess[node] != minimum ||
1193 node_max_prefix_excess[node] != maximum ||
1194 node_min_count[node] != minimum_count ||
1195 node_pattern10_count[node] != pattern10_count ||
1196 node_first_bit[node] != bit(begin) ||
1197 node_last_bit[node] != previous_bit) {
1198 throw std::invalid_argument(
1199 "Serialized RmM leaf metadata disagrees with source");
1200 }
1201 continue;
1202 }
1203 const std::int64_t total = node_total_excess[node];
1204 const std::int64_t minimum = node_min_prefix_excess[node];
1205 const std::int64_t maximum = node_max_prefix_excess[node];
1206 const std::int64_t expected_signed_size =
1207 static_cast<std::int64_t>(expected_size);
1208 if (segment_size_bits[node] != expected_size ||
1209 signed_magnitude(total) > expected_size ||
1210 ((expected_signed_size + total) & 1) != 0 || minimum > total ||
1211 maximum < total || minimum < -expected_signed_size ||
1212 maximum > expected_signed_size || node_min_count[node] == 0 ||
1213 node_min_count[node] > expected_size ||
1214 node_pattern10_count[node] >= expected_size ||
1215 node_first_bit[node] > 1 || node_last_bit[node] > 1) {
1216 throw std::invalid_argument("Invalid serialized RmM leaf metadata");
1217 }
1218 }
1219
1220 for (std::size_t node = first_leaf_index; node-- > 1;) {
1221 if (validation == DeserializationValidation::kQuick) {
1222 const std::size_t size = segment_size_bits[node];
1223 const std::int64_t minimum = node_min_prefix_excess[node];
1224 const std::int64_t maximum = node_max_prefix_excess[node];
1225 if (size > num_bits ||
1226 signed_magnitude(node_total_excess[node]) > size ||
1227 (minimum < 0 && signed_magnitude(minimum) > size) ||
1228 (maximum > 0 && static_cast<std::uint64_t>(maximum) > size) ||
1229 node_min_prefix_excess[node] > node_max_prefix_excess[node] ||
1230 node_min_count[node] > segment_size_bits[node] ||
1231 node_pattern10_count[node] > segment_size_bits[node] ||
1232 node_first_bit[node] > 1 || node_last_bit[node] > 1) {
1233 throw std::invalid_argument(
1234 "Invalid serialized RmM internal metadata");
1235 }
1236 continue;
1237 }
1238 const std::size_t left = node << 1;
1239 const std::size_t right = left | 1;
1240 const bool has_left = left < node_count && segment_size_bits[left] != 0;
1241 const bool has_right =
1242 right < node_count && segment_size_bits[right] != 0;
1243 if (!has_left && !has_right) {
1244 if (!node_is_zero(node)) {
1245 throw std::invalid_argument(
1246 "Invalid serialized empty RmM internal node");
1247 }
1248 continue;
1249 }
1250
1251 const std::size_t first = has_left ? left : right;
1252 if (has_left != has_right) {
1253 if (segment_size_bits[node] != segment_size_bits[first] ||
1254 node_total_excess[node] != node_total_excess[first] ||
1255 node_min_prefix_excess[node] != node_min_prefix_excess[first] ||
1256 node_max_prefix_excess[node] != node_max_prefix_excess[first] ||
1257 node_min_count[node] != node_min_count[first] ||
1258 node_pattern10_count[node] != node_pattern10_count[first] ||
1259 node_first_bit[node] != node_first_bit[first] ||
1260 node_last_bit[node] != node_last_bit[first]) {
1261 throw std::invalid_argument(
1262 "Invalid serialized unary RmM internal node");
1263 }
1264 continue;
1265 }
1266
1267 const std::uint64_t expected_size =
1268 static_cast<std::uint64_t>(segment_size_bits[left]) +
1269 segment_size_bits[right];
1270 const std::int64_t expected_total =
1271 static_cast<std::int64_t>(node_total_excess[left]) +
1272 node_total_excess[right];
1273 const std::int64_t right_min =
1274 static_cast<std::int64_t>(node_total_excess[left]) +
1275 node_min_prefix_excess[right];
1276 const std::int64_t right_max =
1277 static_cast<std::int64_t>(node_total_excess[left]) +
1278 node_max_prefix_excess[right];
1279 const std::int64_t expected_min =
1280 std::min<std::int64_t>(node_min_prefix_excess[left], right_min);
1281 const std::int64_t expected_max =
1282 std::max<std::int64_t>(node_max_prefix_excess[left], right_max);
1283 const std::uint64_t expected_min_count =
1284 (node_min_prefix_excess[left] == expected_min ? node_min_count[left]
1285 : 0) +
1286 (right_min == expected_min ? node_min_count[right] : 0);
1287 const std::uint64_t expected_pattern_count =
1288 static_cast<std::uint64_t>(node_pattern10_count[left]) +
1289 node_pattern10_count[right] +
1290 (node_last_bit[left] == 1 && node_first_bit[right] == 0 ? 1 : 0);
1291 if (segment_size_bits[node] != expected_size ||
1292 node_total_excess[node] != expected_total ||
1293 node_min_prefix_excess[node] != expected_min ||
1294 node_max_prefix_excess[node] != expected_max ||
1295 node_min_count[node] != expected_min_count ||
1296 node_pattern10_count[node] != expected_pattern_count ||
1297 node_first_bit[node] != node_first_bit[left] ||
1298 node_last_bit[node] != node_last_bit[right]) {
1299 throw std::invalid_argument(
1300 "Invalid serialized binary RmM internal node");
1301 }
1302 }
1303 if (segment_size_bits[1] != num_bits) {
1304 throw std::invalid_argument(
1305 "Serialized RmM root does not cover the source");
1306 }
1307 }
1308
1314 static inline size_t pop10_in_slice64(const std::uint64_t& slice,
1315 const int& length) noexcept {
1316 if (length <= 1) {
1317 return 0;
1318 }
1319 std::uint64_t pattern_mask = slice & ~(slice >> 1); // candidates for "10"
1320 if (length < 64) {
1321 pattern_mask &= ((std::uint64_t(1) << (length - 1)) - 1);
1322 } else {
1323 pattern_mask &= 0x7FFFFFFFFFFFFFFFull;
1324 }
1325 return (size_t)std::popcount(pattern_mask);
1326 }
1327
1332 size_t rank1_in_block(const size_t& block_begin,
1333 const size_t& block_end) const noexcept {
1334 if (block_end <= block_begin) {
1335 return 0;
1336 }
1337 size_t left_word_index = block_begin >> 6;
1338 const size_t right_word_index = block_end >> 6;
1339 size_t left_offset = block_begin & 63;
1340 const size_t right_offset = block_end & 63;
1341 size_t count = 0;
1342 if (left_word_index == right_word_index) {
1343 const std::uint64_t mask =
1344 ((right_offset == 0) ? 0 : ((std::uint64_t(1) << right_offset) - 1)) &
1345 (~std::uint64_t(0) << left_offset);
1346 return (size_t)std::popcount(bits[left_word_index] & mask);
1347 }
1348 if (left_offset) {
1349 count += (size_t)std::popcount(bits[left_word_index] &
1350 (~std::uint64_t(0) << left_offset));
1351 ++left_word_index;
1352 }
1353 while (left_word_index < right_word_index) {
1354 count += (size_t)std::popcount(bits[left_word_index]);
1355 ++left_word_index;
1356 }
1357 if (right_offset) {
1358 count += (size_t)std::popcount(bits[right_word_index] &
1359 ((std::uint64_t(1) << right_offset) - 1));
1360 }
1361 return count;
1362 }
1363
1368 size_t rr_in_block(const size_t& block_begin,
1369 const size_t& block_end) const noexcept {
1370 if (block_end <= block_begin + 1) {
1371 return 0;
1372 }
1373 size_t left_word_index = block_begin >> 6;
1374 const size_t right_word_index = (block_end - 1) >> 6;
1375 const int left_offset = block_begin & 63;
1376 const int right_offset = (block_end - 1) & 63;
1377 size_t count = 0;
1378
1379 if (left_word_index == right_word_index) {
1380 const int length = right_offset - left_offset + 1;
1381 const std::uint64_t slice = bits[left_word_index] >> left_offset;
1382 return pop10_in_slice64(slice, length);
1383 }
1384
1385 // prefix word
1386 {
1387 const int length = 64 - left_offset;
1388 const std::uint64_t slice = bits[left_word_index] >> left_offset;
1389 count += pop10_in_slice64(slice, length);
1390 }
1391 // full interior words
1392 for (size_t word_index = left_word_index + 1; word_index < right_word_index;
1393 ++word_index) {
1394 const std::uint64_t word = bits[word_index];
1395 count += pop10_in_slice64(word, 64);
1396 }
1397 // suffix word
1398 {
1399 const int length = right_offset + 1;
1400 const std::uint64_t mask = (length == 64)
1401 ? ~std::uint64_t(0)
1402 : ((std::uint64_t(1) << length) - 1);
1403 const std::uint64_t slice = bits[right_word_index] & mask;
1404 count += pop10_in_slice64(slice, length);
1405 }
1406 // cross-word boundaries (bit 63 of w and bit 0 of w+1)
1407 for (size_t word_index = left_word_index; word_index < right_word_index;
1408 ++word_index) {
1409 if (((bits[word_index] >> 63) & 1u) &&
1410 ((bits[word_index + 1] & 1u) == 0)) {
1411 ++count;
1412 }
1413 }
1414 return count;
1415 }
1416
1422 size_t select10_in_block(const size_t& block_begin,
1423 const size_t& block_end,
1424 size_t target_pattern_rank) const noexcept {
1425 if (block_end <= block_begin + 1) {
1426 return npos;
1427 }
1428 size_t left_word_index = block_begin >> 6;
1429 const size_t right_word_index = (block_end - 1) >> 6;
1430 const int left_offset = block_begin & 63;
1431 const int right_offset = (block_end - 1) & 63;
1432
1433 const auto select_in_masked_slice =
1434 [&](const std::uint64_t& slice, const int& length,
1435 const size_t& target_index) noexcept -> int {
1436 if (length <= 1) {
1437 return -1;
1438 }
1439 std::uint64_t pattern_mask = slice & ~(slice >> 1);
1440 if (length < 64) {
1441 pattern_mask &= ((std::uint64_t(1) << (length - 1)) - 1);
1442 } else {
1443 pattern_mask &= 0x7FFFFFFFFFFFFFFFull;
1444 }
1445 return select_in_word(pattern_mask, target_index);
1446 };
1447
1448 if (left_word_index == right_word_index) {
1449 const int length = right_offset - left_offset + 1;
1450 const std::uint64_t slice = bits[left_word_index] >> left_offset;
1451 const int offset =
1452 select_in_masked_slice(slice, length, target_pattern_rank);
1453 return offset >= 0 ? (block_begin + (size_t)offset) : npos;
1454 }
1455
1456 // prefix word
1457 {
1458 const int length = 64 - left_offset;
1459 const std::uint64_t slice = bits[left_word_index] >> left_offset;
1460 std::uint64_t pattern_mask = slice & ~(slice >> 1);
1461 pattern_mask &= ((std::uint64_t(1) << (length - 1)) - 1);
1462 const int count = std::popcount(pattern_mask);
1463 if (target_pattern_rank <= (size_t)count) {
1464 const int offset =
1465 select_in_masked_slice(slice, length, target_pattern_rank);
1466 return block_begin + (size_t)offset;
1467 }
1468 target_pattern_rank -= count;
1469 }
1470
1471 // walk interior boundaries and words
1472 for (size_t word_index = left_word_index; word_index + 1 < right_word_index;
1473 ++word_index) {
1474 // boundary between w and w+1
1475 if (((bits[word_index] >> 63) & 1u) &&
1476 ((bits[word_index + 1] & 1u) == 0)) {
1477 if (--target_pattern_rank == 0) {
1478 return (word_index << 6) + 63;
1479 }
1480 }
1481 // full word w+1 (positions 0..62)
1482 const std::uint64_t next_word = bits[word_index + 1];
1483 const std::uint64_t pattern_mask =
1484 (next_word & ~(next_word >> 1)) & 0x7FFFFFFFFFFFFFFFull;
1485 const int count = std::popcount(pattern_mask);
1486 if (target_pattern_rank <= (size_t)count) {
1487 const int offset = select_in_word(pattern_mask, target_pattern_rank);
1488 if (offset == -1) {
1489 return npos;
1490 }
1491 return ((word_index + 1) << 6) + (size_t)offset;
1492 }
1493 target_pattern_rank -= count;
1494 }
1495
1496 // boundary (w_r-1, w_r)
1497 if (((bits[right_word_index - 1] >> 63) & 1u) &&
1498 ((bits[right_word_index] & 1u) == 0)) {
1499 if (--target_pattern_rank == 0) {
1500 return ((right_word_index - 1) << 6) + 63;
1501 }
1502 }
1503
1504 // suffix word w_r: [0..off_r]
1505 {
1506 const int length = right_offset + 1;
1507 const std::uint64_t mask = (length == 64)
1508 ? ~std::uint64_t(0)
1509 : ((std::uint64_t(1) << length) - 1);
1510 const std::uint64_t slice = bits[right_word_index] & mask;
1511 const int offset =
1512 select_in_masked_slice(slice, length, target_pattern_rank);
1513 if (offset >= 0) {
1514 return (right_word_index << 6) + (size_t)offset;
1515 }
1516 }
1517 return npos;
1518 }
1519
1520 struct ByteAgg {
1521 int8_t excess_total; // total excess for the byte
1522 int8_t min_prefix; // minimum prefix within the byte (from 0)
1523 int8_t max_prefix; // maximum prefix within the byte (from 0)
1524 uint8_t min_count; // number of positions attaining the minimum in the byte
1525 uint8_t pattern10_count; // number of "10" patterns inside the byte
1526 uint8_t first_bit; // first bit (LSB)
1527 uint8_t last_bit; // last bit (MSB)
1528 uint8_t pos_first_min; // pos of first minimum in this byte
1529 uint8_t pos_first_max; // pos of first maximum in this byte
1530 };
1531
1532 struct LUT8Tables {
1533 std::array<ByteAgg, 256> agg;
1538 std::array<std::array<int8_t, 17>, 256> fwd_pos;
1543 std::array<std::array<int8_t, 17>, 256> bwd_pos;
1544 };
1545
1549 static inline const LUT8Tables& LUT8_ALL() noexcept {
1550 static const LUT8Tables tables = [] {
1551 LUT8Tables lookup_tables{};
1552 for (int byte_value = 0; byte_value < 256; ++byte_value) {
1553 int current_excess = 0, min_prefix = INT_MAX, max_prefix = INT_MIN,
1554 min_count = 0, pattern10_count = 0;
1555 int first_min_position = 0, first_max_position = 0;
1556 int prefixes[8];
1557 const auto bit_at = [&](const int& bit_index) {
1558 return (byte_value >> bit_index) & 1;
1559 }; // LSB-first
1560 for (int bit_index = 0; bit_index < 8; ++bit_index) {
1561 int bit_value = bit_at(bit_index);
1562 if (bit_index + 1 < 8 && bit_value && bit_at(bit_index + 1) == 0) {
1563 ++pattern10_count;
1564 }
1565 current_excess += bit_value ? +1 : -1;
1566 prefixes[bit_index] = current_excess;
1567 if (current_excess < min_prefix) {
1568 min_prefix = current_excess;
1569 min_count = 1;
1570 first_min_position = bit_index;
1571 } else if (current_excess == min_prefix) {
1572 ++min_count;
1573 }
1574 if (current_excess > max_prefix) {
1575 max_prefix = current_excess;
1576 first_max_position = bit_index;
1577 }
1578 }
1579 ByteAgg aggregates{};
1580 aggregates.excess_total = current_excess;
1581 aggregates.min_prefix = (min_prefix == INT_MAX ? 0 : min_prefix);
1582 aggregates.max_prefix = (max_prefix == INT_MIN ? 0 : max_prefix);
1583 aggregates.min_count = min_count;
1584 aggregates.pattern10_count = pattern10_count;
1585 aggregates.first_bit = bit_at(0);
1586 aggregates.last_bit = bit_at(7);
1587 aggregates.pos_first_min = first_min_position;
1588 aggregates.pos_first_max = first_max_position;
1589 lookup_tables.agg[byte_value] = aggregates;
1590 auto& forward_positions = lookup_tables.fwd_pos[byte_value];
1591 auto& backward_positions = lookup_tables.bwd_pos[byte_value];
1592 forward_positions.fill(-1);
1593 backward_positions.fill(-1);
1594 for (int delta = -8; delta <= 8; ++delta) {
1595 for (int bit_index = 0; bit_index < 8; ++bit_index) {
1596 if (prefixes[bit_index] == delta) {
1597 forward_positions[delta + 8] = bit_index;
1598 break;
1599 }
1600 }
1601 for (int bit_index = 7; bit_index >= 0; --bit_index) {
1602 if (prefixes[bit_index] == delta) {
1603 backward_positions[delta + 8] = bit_index;
1604 break;
1605 }
1606 }
1607 }
1608 }
1609 return lookup_tables;
1610 }();
1611 return tables;
1612 }
1613
1617 static inline const std::array<ByteAgg, 256>& LUT8() noexcept {
1618 return LUT8_ALL().agg;
1619 }
1620
1624 static inline const std::array<std::array<int8_t, 17>, 256>&
1625 LUT8_FWD_POS() noexcept {
1626 return LUT8_ALL().fwd_pos;
1627 }
1628
1632 static inline const std::array<std::array<int8_t, 17>, 256>&
1633 LUT8_BWD_POS() noexcept {
1634 return LUT8_ALL().bwd_pos;
1635 }
1636
1640 inline uint16_t get_u16(const size_t& position) const noexcept {
1641 const size_t word_index = position >> 6;
1642 const unsigned offset = unsigned(position & 63);
1643 const std::uint64_t w0 =
1644 (word_index < bits.size()) ? bits[word_index] : 0ULL;
1645 if (offset == 0) {
1646 return uint16_t(w0 & 0xFFFFu);
1647 }
1648 const std::uint64_t w1 =
1649 (word_index + 1 < bits.size()) ? bits[word_index + 1] : 0ULL;
1650 const std::uint64_t v = (w0 >> offset) | (w1 << (64u - offset));
1651 return uint16_t(v & 0xFFFFu);
1652 }
1653
1654#if defined(PIXIE_AVX2_SUPPORT)
1655 static inline __m256i bit_masks_16x() noexcept {
1656 // 16 lanes: (1<<0), (1<<1), ... (1<<15)
1657 return _mm256_setr_epi16(0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020,
1658 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800,
1659 0x1000, 0x2000, 0x4000, (int16_t)0x8000);
1660 }
1661
1662 static inline __m256i prefix_sum_16x_i16(__m256i v) noexcept {
1663 // Inclusive prefix sum within 128-bit lanes, then fix carry into the high
1664 // lane.
1665 __m256i x = v;
1666 __m256i t = _mm256_slli_si256(x, 2);
1667 x = _mm256_add_epi16(x, t);
1668 t = _mm256_slli_si256(x, 4);
1669 x = _mm256_add_epi16(x, t);
1670 t = _mm256_slli_si256(x, 8);
1671 x = _mm256_add_epi16(x, t);
1672
1673 __m128i lo = _mm256_extracti128_si256(x, 0);
1674 __m128i hi = _mm256_extracti128_si256(x, 1);
1675 const int16_t carry =
1676 (int16_t)_mm_extract_epi16(lo, 7); // sum of first 8 elems
1677 hi = _mm_add_epi16(hi, _mm_set1_epi16(carry));
1678
1679 __m256i out = _mm256_castsi128_si256(lo);
1680 out = _mm256_inserti128_si256(out, hi, 1);
1681 return out;
1682 }
1683
1684 static inline int16_t last_prefix_16x_i16(__m256i pref) noexcept {
1685 __m128i hi = _mm256_extracti128_si256(pref, 1);
1686 return (int16_t)_mm_extract_epi16(hi, 7); // lane 15
1687 }
1688
1695 inline size_t scan_leaf_fwd_simd(const size_t& start,
1696 const size_t& end,
1697 const int& required_delta,
1698 int* out_total) const noexcept {
1699 if (start >= end) {
1700 if (out_total) {
1701 *out_total = 0;
1702 }
1703 return npos;
1704 }
1705 if (required_delta < -32768 || required_delta > 32767) {
1706 if (out_total) {
1707 const int len = int(end - start);
1708 const int ones = int(rank1_in_block(start, end));
1709 *out_total = ones * 2 - len;
1710 }
1711 return npos;
1712 }
1713
1714 static const __m256i masks = bit_masks_16x();
1715 static const __m256i vzero = _mm256_setzero_si256();
1716 static const __m256i vallones = _mm256_cmpeq_epi16(vzero, vzero);
1717 static const __m256i vminus1 = _mm256_set1_epi16(-1);
1718 static const __m256i vtwo = _mm256_set1_epi16(2);
1719 const __m256i vtarget = _mm256_set1_epi16((int16_t)required_delta);
1720
1721 int cur = 0;
1722 size_t pos = start;
1723 while (pos + 16 <= end) {
1724 const uint16_t bits16 = get_u16(pos);
1725 const __m256i vb = _mm256_set1_epi16((int16_t)bits16);
1726 const __m256i m = _mm256_and_si256(vb, masks);
1727 const __m256i is_zero = _mm256_cmpeq_epi16(m, vzero);
1728 const __m256i is_set = _mm256_andnot_si256(is_zero, vallones);
1729 const __m256i steps =
1730 _mm256_add_epi16(vminus1, _mm256_and_si256(is_set, vtwo));
1731
1732 const __m256i pref_rel = prefix_sum_16x_i16(steps);
1733 const __m256i base = _mm256_set1_epi16((int16_t)cur);
1734 const __m256i pref = _mm256_add_epi16(pref_rel, base);
1735 const __m256i cmp = _mm256_cmpeq_epi16(pref, vtarget);
1736 const uint32_t mask = (uint32_t)_mm256_movemask_epi8(cmp);
1737 if (mask) {
1738 const int lane = int(std::countr_zero(mask)) >> 1;
1739 return pos + (size_t)lane;
1740 }
1741 cur += (int)last_prefix_16x_i16(pref_rel);
1742 pos += 16;
1743 }
1744 while (pos < end) {
1745 cur += bit(pos) ? +1 : -1;
1746 if (cur == required_delta) {
1747 return pos;
1748 }
1749 ++pos;
1750 }
1751 if (out_total) {
1752 *out_total = cur;
1753 }
1754 return npos;
1755 }
1756#endif // PIXIE_AVX2_SUPPORT
1757
1764 inline size_t scan_leaf_fwd_lut8_fast(const size_t& start,
1765 const size_t& end,
1766 const int& required_delta,
1767 int* out_total) const noexcept {
1768 if (start >= end) {
1769 if (out_total) {
1770 *out_total = 0;
1771 }
1772 return npos;
1773 }
1774 int cur = 0;
1775
1776 size_t pos = start;
1777 while (pos < end && (pos & 7)) {
1778 cur += bit(pos) ? +1 : -1;
1779 if (cur == required_delta) {
1780 if (out_total) {
1781 *out_total = cur;
1782 }
1783 return pos;
1784 }
1785 ++pos;
1786 }
1787
1788 // Byte-aligned fast path: read bytes directly.
1789 // bits are LSB-first; on little-endian x86 the in-memory byte order matches
1790 // bit groups [pos..pos+7].
1791 const uint8_t* bytep =
1792 reinterpret_cast<const uint8_t*>(bits.data()) + (pos >> 3);
1793 const auto& agg = LUT8();
1794 const auto& fwd = LUT8_FWD_POS();
1795
1796 while (pos + 8 <= end) {
1797 const uint8_t bv = *bytep++;
1798 const auto& a = agg[bv];
1799 const int need = required_delta - cur;
1800 // need must be in [-8..8] for a match inside one byte.
1801 if ((unsigned)(need + 8) <= 16u) {
1802 // min/max pruning first, then position lookup.
1803 if (need >= a.min_prefix && need <= a.max_prefix) {
1804 const int8_t off = fwd[bv][need + 8];
1805 if (off >= 0) {
1806 if (out_total) {
1807 *out_total = cur + a.excess_total; // not exact end, but caller
1808 // only uses when not found
1809 }
1810 return pos + (size_t)off;
1811 }
1812 }
1813 }
1814 cur += a.excess_total;
1815 pos += 8;
1816 }
1817
1818 while (pos < end) {
1819 cur += bit(pos) ? +1 : -1;
1820 if (cur == required_delta) {
1821 if (out_total) {
1822 *out_total = cur;
1823 }
1824 return pos;
1825 }
1826 ++pos;
1827 }
1828 if (out_total) {
1829 *out_total = cur;
1830 }
1831 return npos;
1832 }
1833
1841 inline size_t scan_leaf_fwd(const size_t& search_start,
1842 const size_t& search_end,
1843 const int& required_delta) const noexcept {
1844 if (search_start >= search_end) {
1845 return npos;
1846 }
1847 const auto& aggregates_table = LUT8();
1848 const auto& forward_lookup = LUT8_FWD_POS();
1849 int current_excess = 0;
1850 size_t position = search_start;
1851 while (position + 8 <= search_end) {
1852 const uint8_t byte_value = get_byte(position);
1853 const auto& byte_aggregate = aggregates_table[byte_value];
1854 const int local_need = required_delta - current_excess;
1855 if (local_need >= byte_aggregate.min_prefix &&
1856 local_need <= byte_aggregate.max_prefix && local_need >= -8 &&
1857 local_need <= 8) {
1858 const int8_t offset = forward_lookup[byte_value][local_need + 8];
1859 if (offset >= 0) {
1860 return position + size_t(offset);
1861 }
1862 }
1863 current_excess += byte_aggregate.excess_total;
1864 position += 8;
1865 }
1866
1867 while (position < search_end) {
1868 current_excess += bit(position) ? 1 : -1;
1869 if (current_excess == required_delta) {
1870 return position;
1871 }
1872 ++position;
1873 }
1874
1875 return npos;
1876 }
1877
1886 inline size_t scan_leaf_bwd(
1887 const size_t& block_begin,
1888 const size_t& block_end,
1889 const int& required_delta,
1890 const bool& allow_right_boundary,
1891 const size_t& global_right_border,
1892 const int& prefix_at_boundary_max /*=kNoPrefixOverride*/) const noexcept {
1893 // We scan bits in [block_begin, block_end) and look for the LAST boundary
1894 // where prefix == required_delta, with optional exclusion of the right
1895 // boundary.
1896 if (block_begin > block_end) {
1897 return npos;
1898 }
1899
1900 // Maximum allowed boundary (inclusive) inside this scan.
1901 // If right boundary is forbidden, we forbid boundary == block_end.
1902 size_t boundary_max = block_end;
1903 if (!allow_right_boundary && boundary_max > block_begin) {
1904 --boundary_max;
1905 }
1906
1907 // No bits to scan -> only possible answer is the left boundary.
1908 if (block_begin >= boundary_max) {
1909 if ((block_begin < global_right_border || allow_right_boundary) &&
1910 required_delta == 0) {
1911 return block_begin;
1912 }
1913 return npos;
1914 }
1915
1916 if (required_delta < -32768 || required_delta > 32767) {
1917 // Just in case. Should be impossible.
1918 if ((block_begin < global_right_border || allow_right_boundary) &&
1919 required_delta == 0) {
1920 return block_begin;
1921 }
1922 return npos;
1923 }
1924
1925#if defined(PIXIE_AVX2_SUPPORT)
1926 // Fast reverse scan with early exit:
1927 // find the RIGHTMOST boundary j in (block_begin..boundary_max] such that
1928 // prefix(j) == required_delta.
1929
1930 // prefix_end = prefix(boundary_max) relative to block_begin
1931 int prefix_end = prefix_at_boundary_max;
1932 if (prefix_end == kNoPrefixOverride) {
1933 const int len = int(boundary_max - block_begin);
1934 const int ones = int(rank1_in_block(block_begin, boundary_max));
1935 prefix_end = ones * 2 - len;
1936 }
1937
1938 const __m256i masks = bit_masks_16x();
1939 const __m256i vzero = _mm256_setzero_si256();
1940 const __m256i vallones = _mm256_cmpeq_epi16(vzero, vzero);
1941 const __m256i vminus1 = _mm256_set1_epi16(-1);
1942 const __m256i vtwo = _mm256_set1_epi16(2);
1943 const __m256i vtarget = _mm256_set1_epi16((int16_t)required_delta);
1944
1945 size_t pos_end = boundary_max; // boundary (not bit index)
1946 int cur_end = prefix_end; // prefix(pos_end)
1947
1948 // Vector chunks: process 16 bits ending at pos_end.
1949 while (pos_end >= block_begin + 16) {
1950 const size_t pos = pos_end - 16; // bit index of the chunk start
1951 const uint16_t bits16 = get_u16(pos);
1952 const __m256i vb = _mm256_set1_epi16((int16_t)bits16);
1953 const __m256i m = _mm256_and_si256(vb, masks);
1954 const __m256i is_zero = _mm256_cmpeq_epi16(m, vzero);
1955 const __m256i is_set = _mm256_andnot_si256(is_zero, vallones);
1956 const __m256i steps =
1957 _mm256_add_epi16(vminus1, _mm256_and_si256(is_set, vtwo));
1958
1959 const __m256i pref_rel =
1960 prefix_sum_16x_i16(steps); // prefix after each bit (relative)
1961 const int16_t sum16 =
1962 last_prefix_16x_i16(pref_rel); // total sum on this 16-bit chunk
1963 const int cur_start =
1964 cur_end - (int)sum16; // prefix at boundary pos (chunk start)
1965
1966 const __m256i base = _mm256_set1_epi16((int16_t)cur_start);
1967 const __m256i pref = _mm256_add_epi16(
1968 pref_rel, base); // prefix at boundaries (pos+1..pos+16)
1969 const __m256i cmp = _mm256_cmpeq_epi16(pref, vtarget);
1970 const uint32_t mask = (uint32_t)_mm256_movemask_epi8(cmp);
1971 if (mask) {
1972 const int bit_i = 31 - int(std::countl_zero(mask));
1973 const int lane = bit_i >> 1;
1974 const size_t boundary = pos + (size_t)lane + 1;
1975 if (boundary < global_right_border || allow_right_boundary) {
1976 return boundary;
1977 }
1978 return npos;
1979 }
1980
1981 cur_end = cur_start;
1982 pos_end = pos;
1983 }
1984
1985 while (pos_end > block_begin) {
1986 // boundary pos_end corresponds to prefix cur_end
1987 if (cur_end == required_delta) {
1988 if (pos_end < global_right_border || allow_right_boundary) {
1989 return pos_end;
1990 }
1991 return npos;
1992 }
1993 const size_t bit_pos = pos_end - 1;
1994 cur_end -= bit(bit_pos) ? +1 : -1; // move one bit to the left
1995 pos_end = bit_pos;
1996 }
1997#else
1998 size_t last_boundary = npos;
1999 int cur = 0;
2000 for (size_t pos = block_begin; pos < boundary_max; ++pos) {
2001 cur += bit(pos) ? +1 : -1;
2002 if (cur == required_delta) {
2003 last_boundary = pos + 1;
2004 }
2005 }
2006 if (last_boundary != npos) {
2007 if (last_boundary < global_right_border || allow_right_boundary) {
2008 return last_boundary;
2009 }
2010 return npos;
2011 }
2012#endif
2013
2014 // Left boundary (prefix == 0) is always the final candidate.
2015 if ((block_begin < global_right_border || allow_right_boundary) &&
2016 required_delta == 0) {
2017 return block_begin;
2018 }
2019 return npos;
2020 }
2021
2025 inline uint8_t get_byte(const size_t& position) const noexcept {
2026 const size_t word_index = position >> 6;
2027 const size_t offset = position & 63;
2028 const std::uint64_t lower_word = bits[word_index] >> offset;
2029 if (offset <= 56) {
2030 return uint8_t(lower_word & 0xFFu);
2031 }
2032 const std::uint64_t higher_word =
2033 (word_index + 1 < bits.size()) ? bits[word_index + 1] : 0;
2034 const std::uint64_t byte_value =
2035 (lower_word | (higher_word << (64 - offset))) & 0xFFu;
2036 return uint8_t(byte_value);
2037 }
2038
2047 size_t descend_first_max(size_t node_index,
2048 int target_prefix,
2049 size_t segment_base) const noexcept {
2050 while (node_index < first_leaf_index) {
2051 const size_t left_child = node_index << 1, right_child = left_child | 1;
2052 const int left_max = node_max_prefix_excess[left_child];
2053 const int right_max =
2054 node_total_excess[left_child] + node_max_prefix_excess[right_child];
2055 if (left_max >= right_max && left_max == target_prefix) {
2056 node_index = left_child;
2057 } else if (right_max == target_prefix) {
2058 segment_base += segment_size_bits[left_child];
2059 target_prefix -= node_total_excess[left_child];
2060 node_index = right_child;
2061 } else {
2062 return npos;
2063 }
2064 }
2065
2066 const size_t segment_begin = segment_base;
2067 const size_t segment_end =
2068 std::min(segment_base + segment_size_bits[node_index], num_bits);
2069 int max_value;
2070 size_t position;
2071
2072 first_max_value_pos8(segment_begin,
2073 segment_end ? (segment_end - 1) : segment_begin,
2074 max_value, position);
2075 return (max_value == target_prefix ? position : npos);
2076 }
2077
2081 size_t first_leaf_index = 1;
2082
2087 static constexpr int kNoPrefixOverride = std::numeric_limits<int>::min();
2088
2092 size_t block_of(const size_t& position) const noexcept {
2093 return position / block_bits;
2094 }
2095
2099 size_t leaf_index_of(const size_t& block_start) const noexcept {
2100 return first_leaf_index + block_of(block_start);
2101 }
2102
2107 size_t node_base(size_t node_index) const noexcept {
2108 if (node_index >= first_leaf_index) {
2109 return (node_index - first_leaf_index) * block_bits;
2110 }
2111
2112 size_t base = 0;
2113 for (; node_index > 1; node_index >>= 1) {
2114 if (node_index & 1) {
2115 base += segment_size_bits[node_index - 1];
2116 }
2117 }
2118 return base;
2119 }
2120
2126 std::vector<size_t> cover_blocks(const size_t& block_begin_index,
2127 const size_t& block_end_index) const {
2128 size_t left_index = first_leaf_index + block_begin_index;
2129 size_t right_index = first_leaf_index + block_end_index;
2130 std::vector<size_t> left_nodes, right_nodes;
2131 while (left_index <= right_index) {
2132 if ((left_index & 1) == 1) {
2133 left_nodes.push_back(left_index++);
2134 }
2135 if ((right_index & 1) == 0) {
2136 right_nodes.push_back(right_index--);
2137 }
2138 left_index >>= 1;
2139 right_index >>= 1;
2140 }
2141 std::reverse(right_nodes.begin(), right_nodes.end());
2142 left_nodes.insert(left_nodes.end(), right_nodes.begin(), right_nodes.end());
2143 return left_nodes;
2144 }
2145
2150 size_t descend_fwd(size_t node_index,
2151 int required_delta,
2152 size_t segment_base) const noexcept {
2153 while (node_index < first_leaf_index) {
2154 const size_t left_child = node_index << 1;
2155 const size_t right_child = left_child | 1;
2156 if (node_min_prefix_excess[left_child] <= required_delta &&
2157 required_delta <= node_max_prefix_excess[left_child]) {
2158 node_index = left_child;
2159 } else {
2160 required_delta -= node_total_excess[left_child];
2161 segment_base += segment_size_bits[left_child];
2162 node_index = right_child;
2163 }
2164 }
2165 const size_t seg_end =
2166 std::min(segment_base + segment_size_bits[node_index], num_bits);
2167#if defined(PIXIE_AVX2_SUPPORT)
2168 return scan_leaf_fwd_simd(segment_base, seg_end, required_delta, nullptr);
2169#else
2170 return scan_leaf_fwd(segment_base, seg_end, required_delta);
2171#endif
2172 }
2173
2183 size_t descend_bwd(size_t node_index,
2184 const size_t& segment_base,
2185 const int& required_delta,
2186 const size_t& global_right_border,
2187 const bool& allow_right_boundary) const noexcept {
2188 while (node_index < first_leaf_index) {
2189 const size_t left_child = node_index << 1;
2190 const size_t right_child = left_child | 1;
2191 const int required_in_right =
2192 required_delta - node_total_excess[left_child];
2193
2194 // 1) try the right child first (to capture the rightmost j)
2195 if (node_min_prefix_excess[right_child] <= required_in_right &&
2196 required_in_right <= node_max_prefix_excess[right_child]) {
2197 const size_t result = descend_bwd(
2198 right_child, segment_base + segment_size_bits[left_child],
2199 required_in_right, global_right_border, allow_right_boundary);
2200 if (result != npos) {
2201 return result;
2202 }
2203 }
2204
2205 // 2) junction between children (end of the left child)
2206 const size_t junction = segment_base + segment_size_bits[left_child];
2207 if (required_delta == node_total_excess[left_child] &&
2208 (junction < global_right_border || allow_right_boundary)) {
2209 return junction;
2210 }
2211
2212 // 3) can we move left within the range?
2213 if (node_min_prefix_excess[left_child] <= required_delta &&
2214 required_delta <= node_max_prefix_excess[left_child]) {
2215 node_index = left_child;
2216 continue;
2217 }
2218
2219 // None of (1)-(3) worked. The only possible point is the left border of
2220 // the node.
2221 if (required_delta == 0 &&
2222 (segment_base < global_right_border || allow_right_boundary)) {
2223 return segment_base;
2224 }
2225
2226 return npos;
2227 }
2228
2229 const size_t seg_end =
2230 std::min(segment_base + segment_size_bits[node_index], num_bits);
2231 const size_t block_end = std::min(global_right_border, seg_end);
2232
2233 int prefix_override = kNoPrefixOverride;
2234 // If we scan the full leaf up to its end boundary, we know
2235 // prefix(block_end) from node_total_excess[leaf]. If the right boundary is
2236 // forbidden, we can still derive prefix(block_end-1) cheaply.
2237 if (block_end == seg_end) {
2238 const int total = node_total_excess[node_index];
2239 if (!allow_right_boundary && block_end > segment_base) {
2240 prefix_override = total - (bit(block_end - 1) ? +1 : -1);
2241 } else {
2242 prefix_override = total;
2243 }
2244 }
2245
2246 return scan_leaf_bwd(segment_base, block_end, required_delta,
2247 allow_right_boundary, global_right_border,
2248 prefix_override);
2249 }
2250
2255 size_t descend_first_min(size_t node_index,
2256 int target_prefix,
2257 size_t segment_base) const noexcept {
2258 while (node_index < first_leaf_index) {
2259 const size_t left_child = node_index << 1, right_child = left_child | 1;
2260 const int left_min = node_min_prefix_excess[left_child];
2261 const int right_min =
2262 node_total_excess[left_child] + node_min_prefix_excess[right_child];
2263 if (left_min <= right_min && left_min == target_prefix) {
2264 node_index = left_child;
2265 } else if (right_min == target_prefix) {
2266 segment_base += segment_size_bits[left_child];
2267 target_prefix -= node_total_excess[left_child];
2268 node_index = right_child;
2269 } else {
2270 return npos;
2271 }
2272 }
2273
2274 const size_t segment_begin = segment_base;
2275 const size_t segment_end =
2276 std::min(segment_base + segment_size_bits[node_index], num_bits);
2277 int min_value;
2278 size_t position;
2279
2280 first_min_value_pos8(segment_begin,
2281 segment_end ? (segment_end - 1) : segment_begin,
2282 min_value, position);
2283 return (min_value == target_prefix ? position : npos);
2284 }
2285
2290 size_t descend_qth_min(size_t node_index,
2291 int target_prefix,
2292 size_t target_min_rank,
2293 size_t segment_base) const noexcept {
2294 while (node_index < first_leaf_index) {
2295 const size_t left_child = node_index << 1;
2296 const size_t right_child = left_child | 1;
2297 const int left_min = node_min_prefix_excess[left_child];
2298 const int right_min =
2299 node_total_excess[left_child] + node_min_prefix_excess[right_child];
2300 if (left_min == target_prefix) {
2301 if (node_min_count[left_child] >= target_min_rank) {
2302 node_index = left_child;
2303 continue;
2304 }
2305 target_min_rank -= node_min_count[left_child];
2306 }
2307 if (right_min == target_prefix) {
2308 segment_base += segment_size_bits[left_child];
2309 target_prefix -= node_total_excess[left_child];
2310 node_index = right_child;
2311 continue;
2312 }
2313 return npos;
2314 }
2315 return qth_min_in_block(
2316 segment_base,
2317 std::min(segment_base + segment_size_bits[node_index], num_bits) - 1,
2318 target_min_rank);
2319 }
2320
2325 size_t select1_in_block(const size_t& block_begin,
2326 const size_t& block_end,
2327 size_t target_one_rank) const noexcept {
2328 size_t left_word_index = block_begin >> 6;
2329 const size_t right_word_index = (block_end >> 6);
2330 const size_t left_offset = block_begin & 63;
2331 const std::uint64_t left_mask =
2332 (left_offset ? (~std::uint64_t(0) << left_offset) : ~std::uint64_t(0));
2333 if (left_word_index == right_word_index) {
2334 const std::uint64_t word =
2335 bits[left_word_index] & left_mask &
2336 ((block_end & 63) ? ((std::uint64_t(1) << (block_end & 63)) - 1)
2337 : ~std::uint64_t(0));
2338 return block_begin + select_in_word(word, target_one_rank);
2339 }
2340 // prefix
2341 if (left_offset) {
2342 const std::uint64_t word = bits[left_word_index] & left_mask;
2343 const int count = std::popcount(word);
2344 if (target_one_rank <= (size_t)count) {
2345 return block_begin + select_in_word(word, target_one_rank);
2346 }
2347 target_one_rank -= count;
2348 left_word_index++;
2349 }
2350 // full words
2351 while (left_word_index < right_word_index) {
2352 const std::uint64_t word = bits[left_word_index];
2353 const int count = std::popcount(word);
2354 if (target_one_rank <= (size_t)count) {
2355 return (left_word_index << 6) + select_in_word(word, target_one_rank);
2356 }
2357 target_one_rank -= count;
2358 ++left_word_index;
2359 }
2360 // tail
2361 const size_t right_offset = block_end & 63;
2362 if (right_offset) {
2363 const std::uint64_t word =
2364 bits[left_word_index] & ((std::uint64_t(1) << right_offset) - 1);
2365 const int count = std::popcount(word);
2366 if (target_one_rank <= (size_t)count) {
2367 return (left_word_index << 6) + select_in_word(word, target_one_rank);
2368 }
2369 }
2370 return npos;
2371 }
2372
2377 size_t select0_in_block(const size_t& block_begin,
2378 const size_t& block_end,
2379 size_t target_zero_rank) const noexcept {
2380 if (block_end <= block_begin) {
2381 return npos;
2382 }
2383
2384 size_t left_word_index = block_begin >> 6;
2385 const size_t right_word_index = block_end >> 6;
2386 const size_t left_offset = block_begin & 63;
2387
2388 if (left_word_index == right_word_index) {
2389 const std::uint64_t left_mask =
2390 (left_offset ? (~std::uint64_t(0) << left_offset)
2391 : ~std::uint64_t(0));
2392 const std::uint64_t right_mask =
2393 ((block_end & 63) ? ((std::uint64_t(1) << (block_end & 63)) - 1)
2394 : ~std::uint64_t(0));
2395 const std::uint64_t word =
2396 (~bits[left_word_index]) & left_mask & right_mask;
2397 const int offset = select_in_word(word, target_zero_rank);
2398 return (offset >= 0) ? (block_begin + (size_t)offset) : npos;
2399 }
2400
2401 // prefix
2402 if (left_offset) {
2403 const std::uint64_t word =
2404 (~bits[left_word_index]) & (~std::uint64_t(0) << left_offset);
2405 const int count = std::popcount(word);
2406 if (target_zero_rank <= (size_t)count) {
2407 const int offset = select_in_word(word, target_zero_rank);
2408 return (offset >= 0) ? (block_begin + (size_t)offset) : npos;
2409 }
2410 target_zero_rank -= count;
2411 ++left_word_index;
2412 }
2413
2414 // full words
2415 while (left_word_index < right_word_index) {
2416 const std::uint64_t word = ~bits[left_word_index];
2417 const int count = std::popcount(word);
2418 if (target_zero_rank <= (size_t)count) {
2419 const int offset = select_in_word(word, target_zero_rank);
2420 return (offset >= 0) ? ((left_word_index << 6) + (size_t)offset) : npos;
2421 }
2422 target_zero_rank -= count;
2423 ++left_word_index;
2424 }
2425
2426 // tail
2427 const size_t right_offset = block_end & 63;
2428 if (right_offset) {
2429 const std::uint64_t word =
2430 (~bits[left_word_index]) & ((std::uint64_t(1) << right_offset) - 1);
2431 const int count = std::popcount(word);
2432 if (target_zero_rank <= (size_t)count) {
2433 const int offset = select_in_word(word, target_zero_rank);
2434 return (offset >= 0) ? ((left_word_index << 6) + (size_t)offset) : npos;
2435 }
2436 }
2437 return npos;
2438 }
2439
2444 static inline int select_in_word(std::uint64_t word,
2445 size_t target_rank) noexcept {
2446 while (word) {
2447 if (--target_rank == 0) {
2448 return std::countr_zero(word);
2449 }
2450 word &= (word - 1);
2451 }
2452 return -1;
2453 }
2454
2458 static inline size_t ceil_div(const size_t& numerator,
2459 const size_t& denominator) noexcept {
2460 return (numerator + denominator - 1) / denominator;
2461 }
2462
2468 static inline size_t nodeslots_for(const size_t& bit_count,
2469 const size_t& block_size_pow2) noexcept {
2470 if (bit_count == 0) {
2471 return 0;
2472 }
2473 size_t leaf_node_count = ceil_div(bit_count, block_size_pow2);
2474 return std::bit_ceil(std::max<size_t>(1, leaf_node_count)) +
2475 leaf_node_count;
2476 }
2477
2481 static inline float overhead_for(const size_t& bit_count,
2482 const size_t& block_size_pow2) noexcept {
2483 static constexpr size_t AUX_SLOT_BYTES =
2484 sizeof(uint32_t) + sizeof(int32_t) + sizeof(int32_t) + sizeof(int32_t) +
2485 sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint8_t);
2486
2487 size_t bitvector_bytes = ceil_div(bit_count, 64) * 8;
2488 if (bitvector_bytes == 0) {
2489 return 0;
2490 }
2491 size_t slot_count = nodeslots_for(bit_count, block_size_pow2);
2492 size_t aux_bytes = slot_count * AUX_SLOT_BYTES;
2493 return ((float)aux_bytes) / ((float)bitvector_bytes);
2494 }
2495
2502 static inline size_t choose_block_bits_for_overhead(
2503 const size_t& bit_count,
2504 const float& overhead_cap) noexcept {
2505 if (overhead_cap < 0.f) {
2506 return 64;
2507 }
2508
2509 const size_t max_block_bits = std::min<size_t>(bit_count, 16384);
2510 size_t candidate_block_bits = 64;
2511 while (candidate_block_bits < max_block_bits) {
2512 if (overhead_for(bit_count, candidate_block_bits) <= overhead_cap) {
2513 break;
2514 }
2515 candidate_block_bits <<= 1;
2516 }
2517 return candidate_block_bits;
2518 }
2519
2527 void build_from_words(std::span<const std::uint64_t> words,
2528 const size_t& bit_count,
2529 const size_t& leaf_block_bits = 0,
2530 const float& max_overhead = -1.0) {
2531 bits = words;
2532 num_bits = bit_count;
2533 if (bits.size() * 64 < num_bits) {
2534 throw std::invalid_argument(
2535 "RmMTree bit_count exceeds the provided word span");
2536 }
2537 build(leaf_block_bits, max_overhead);
2538 }
2539
2544 inline uint32_t ones_in_node(const size_t& node_index) const noexcept {
2545 return ((int64_t)segment_size_bits[node_index] +
2546 (int64_t)node_total_excess[node_index]) >>
2547 1;
2548 }
2549
2557 inline void scan_range_min_count8(size_t range_begin,
2558 const size_t& range_end,
2559 int& current_excess,
2560 int& min_value,
2561 uint32_t& count) const noexcept {
2562 current_excess = 0;
2563 min_value = INT_MAX;
2564 count = 0;
2565 if (range_end < range_begin) {
2566 min_value = 0;
2567 return;
2568 }
2569 // to byte alignment
2570 while (range_begin <= range_end && (range_begin & 7)) {
2571 current_excess += bit(range_begin) ? +1 : -1;
2572 if (current_excess < min_value) {
2573 min_value = current_excess;
2574 count = 1;
2575 } else if (current_excess == min_value) {
2576 ++count;
2577 }
2578 ++range_begin;
2579 }
2580 // full bytes
2581 const auto& aggregates_table = LUT8();
2582 while (range_begin + 7 <= range_end) {
2583 const auto& byte_aggregate = aggregates_table[get_byte(range_begin)];
2584 const int candidate = current_excess + byte_aggregate.min_prefix;
2585 if (candidate < min_value) {
2586 min_value = candidate;
2587 count = byte_aggregate.min_count;
2588 } else if (candidate == min_value) {
2589 count += byte_aggregate.min_count;
2590 }
2591 current_excess += byte_aggregate.excess_total;
2592 range_begin += 8;
2593 }
2594 // tail
2595 while (range_begin <= range_end) {
2596 current_excess += bit(range_begin) ? +1 : -1;
2597 if (current_excess < min_value) {
2598 min_value = current_excess;
2599 count = 1;
2600 } else if (current_excess == min_value) {
2601 ++count;
2602 }
2603 ++range_begin;
2604 }
2605 if (min_value == INT_MAX) {
2606 min_value = count = 0;
2607 }
2608 }
2609
2616 inline size_t cover_blocks_collect(const size_t& block_begin_index,
2617 const size_t& block_end_index,
2618 size_t (&out_nodes)[64]) const noexcept {
2619 if (leaf_count == 0 || block_begin_index > block_end_index) {
2620 return 0;
2621 }
2622 size_t left_index = first_leaf_index + block_begin_index;
2623 size_t right_index = first_leaf_index + block_end_index;
2624 size_t left_nodes[32];
2625 size_t right_nodes[32];
2626 size_t left_count = 0, right_count = 0;
2627 while (left_index <= right_index) {
2628 if (left_index & 1) {
2629 left_nodes[left_count++] = left_index++;
2630 }
2631 if ((right_index & 1) == 0) {
2632 right_nodes[right_count++] = right_index--;
2633 }
2634 left_index >>= 1;
2635 right_index >>= 1;
2636 }
2637 size_t out_count = 0;
2638 for (size_t i = 0; i < left_count; ++i) {
2639 out_nodes[out_count++] = left_nodes[i];
2640 }
2641 while (right_count > 0) {
2642 out_nodes[out_count++] = right_nodes[--right_count];
2643 }
2644 return out_count;
2645 }
2646
2653 inline size_t qth_min_in_block(const size_t& range_begin,
2654 const size_t& range_end,
2655 size_t target_min_rank) const noexcept {
2656 if (range_end < range_begin || target_min_rank == 0) {
2657 return npos;
2658 }
2659
2660 const auto& aggregates_table = LUT8();
2661
2662 int current_excess = 0, min_value = INT_MAX;
2663 size_t position = range_begin;
2664
2665 while (position <= range_end && (position & 7)) {
2666 current_excess += bit(position) ? +1 : -1;
2667 if (current_excess < min_value) {
2668 min_value = current_excess;
2669 }
2670 ++position;
2671 }
2672 while (position + 7 <= range_end) {
2673 const auto& byte_aggregate = aggregates_table[get_byte(position)];
2674 min_value =
2675 std::min(min_value, current_excess + byte_aggregate.min_prefix);
2676 current_excess += byte_aggregate.excess_total;
2677 position += 8;
2678 }
2679 while (position <= range_end) {
2680 current_excess += bit(position) ? +1 : -1;
2681 if (current_excess < min_value) {
2682 min_value = current_excess;
2683 }
2684 ++position;
2685 }
2686
2687 current_excess = 0;
2688 position = range_begin;
2689
2690 // to byte alignment
2691 while (position <= range_end && (position & 7)) {
2692 current_excess += bit(position) ? +1 : -1;
2693 if (current_excess == min_value) {
2694 if (--target_min_rank == 0) {
2695 return position;
2696 }
2697 }
2698 ++position;
2699 }
2700
2701 // full bytes
2702 while (position + 7 <= range_end) {
2703 const uint8_t byte_value = get_byte(position);
2704 const auto& byte_aggregate = aggregates_table[byte_value];
2705 const int candidate = current_excess + byte_aggregate.min_prefix;
2706 if (candidate == min_value) {
2707 int prefix_sum = 0;
2708 for (int k = 0; k < 8; ++k) {
2709 prefix_sum += ((byte_value >> k) & 1u) ? +1 : -1;
2710 if (prefix_sum == byte_aggregate.min_prefix) {
2711 if (--target_min_rank == 0) {
2712 return position + k;
2713 }
2714 }
2715 }
2716 }
2717 current_excess += byte_aggregate.excess_total;
2718 position += 8;
2719 }
2720
2721 // tail
2722 while (position <= range_end) {
2723 current_excess += bit(position) ? +1 : -1;
2724 if (current_excess == min_value) {
2725 if (--target_min_rank == 0) {
2726 return position;
2727 }
2728 }
2729 ++position;
2730 }
2731
2732 return npos;
2733 }
2734
2745 inline size_t leaf_fwd_bp_simd(const size_t& leaf_index,
2746 const size_t& leaf_block_begin,
2747 const size_t& start_position,
2748 const int& delta,
2749 int& leaf_delta) const noexcept {
2750 const size_t leaf_length = segment_size_bits[first_leaf_index + leaf_index];
2751 const size_t leaf_end = std::min(num_bits, leaf_block_begin + leaf_length);
2752 if (start_position >= leaf_end) {
2753 leaf_delta = 0;
2754 return npos;
2755 }
2756#if defined(PIXIE_AVX2_SUPPORT)
2757 int total = 0;
2758 // Heuristic: for tiny tails and tiny deltas, LUT8 wins because AVX2 setup
2759 // is heavy...
2760 // At least I think so...
2761 const size_t tail_len = leaf_end - start_position;
2762 size_t res = npos;
2763 if (delta >= -8 && delta <= 8 && tail_len <= 256) {
2764 res = scan_leaf_fwd_lut8_fast(start_position, leaf_end, delta, &total);
2765 } else {
2766 res = scan_leaf_fwd_simd(start_position, leaf_end, delta, &total);
2767 }
2768 if (res != npos) {
2769 return res;
2770 }
2771 leaf_delta = total;
2772 return npos;
2773#else
2774 const size_t res = scan_leaf_fwd(start_position, leaf_end, delta);
2775 if (res != npos) {
2776 return res;
2777 }
2778 const int len = int(leaf_end - start_position);
2779 const int ones = int(rank1_in_block(start_position, leaf_end));
2780 leaf_delta = ones * 2 - len;
2781 return npos;
2782#endif
2783 }
2784
2793 inline size_t leaf_bwd_bp_simd(const size_t& leaf_index,
2794 const size_t& leaf_block_begin,
2795 const size_t& start_position,
2796 const int& delta,
2797 int& leaf_delta) const noexcept {
2798 const size_t leaf_length = segment_size_bits[first_leaf_index + leaf_index];
2799 const size_t leaf_end = std::min(num_bits, leaf_block_begin + leaf_length);
2800 if (start_position < leaf_block_begin || start_position > leaf_end) {
2801 leaf_delta = 0;
2802 return npos;
2803 }
2804
2805 // leaf_delta = excess_impl(start_position) - excess_impl(leaf_block_begin)
2806 // = 2*rank1_impl([leaf_begin, start)) - (start - leaf_begin)
2807 const int len = int(start_position - leaf_block_begin);
2808 const int ones = int(rank1_in_block(leaf_block_begin, start_position));
2809 leaf_delta = ones * 2 - len;
2810
2811 // Must find a boundary strictly < start_position (so
2812 // boundary==start_position forbidden).
2813 if (start_position == leaf_block_begin) {
2814 return npos;
2815 }
2816 const int target_prefix = leaf_delta + delta;
2817 return scan_leaf_bwd(
2818 leaf_block_begin,
2819 start_position, // do not look to the right of start_position
2820 target_prefix,
2821 false, // right boundary (=start_position) forbidden
2822 start_position,
2823 // prefix at boundary_max = start_position-1:
2824 // leaf_delta is prefix at start_position, subtract last step
2825 (start_position > leaf_block_begin
2826 ? (leaf_delta - (bit(start_position - 1) ? +1 : -1))
2827 : 0));
2828 }
2829
2836 inline void first_min_value_pos8(size_t range_begin,
2837 const size_t& range_end,
2838 int& min_value_out,
2839 size_t& first_position) const noexcept {
2840 const auto& aggregates_table = LUT8();
2841 int current_excess = 0;
2842 int min_value = INT_MAX;
2843 first_position = npos;
2844
2845 // to byte allignment
2846 while (range_begin <= range_end && (range_begin & 7)) {
2847 current_excess += bit(range_begin) ? +1 : -1;
2848 if (current_excess < min_value) {
2849 min_value = current_excess;
2850 first_position = range_begin;
2851 }
2852 ++range_begin;
2853 }
2854
2855 // full bytes
2856 while (range_begin + 7 <= range_end) {
2857 const auto& byte_aggregate = aggregates_table[get_byte(range_begin)];
2858 const int candidate = current_excess + byte_aggregate.min_prefix;
2859 if (candidate < min_value) {
2860 min_value = candidate;
2861 first_position = range_begin + byte_aggregate.pos_first_min;
2862 }
2863 current_excess += byte_aggregate.excess_total;
2864 range_begin += 8;
2865 }
2866
2867 // tail
2868 while (range_begin <= range_end) {
2869 current_excess += bit(range_begin) ? +1 : -1;
2870 if (current_excess < min_value) {
2871 min_value = current_excess;
2872 first_position = range_begin;
2873 }
2874 ++range_begin;
2875 }
2876
2877 min_value_out = (min_value == INT_MAX ? 0 : min_value);
2878 }
2879
2886 inline void first_max_value_pos8(size_t range_begin,
2887 const size_t& range_end,
2888 int& max_value_out,
2889 size_t& first_position) const noexcept {
2890 const auto& aggregates_table = LUT8();
2891 int current_excess = 0;
2892 int max_value = INT_MIN;
2893 first_position = npos;
2894
2895 while (range_begin <= range_end && (range_begin & 7)) {
2896 current_excess += bit(range_begin) ? +1 : -1;
2897 if (current_excess > max_value) {
2898 max_value = current_excess;
2899 first_position = range_begin;
2900 }
2901 ++range_begin;
2902 }
2903
2904 while (range_begin + 7 <= range_end) {
2905 const auto& byte_aggregate = aggregates_table[get_byte(range_begin)];
2906 const int candidate = current_excess + byte_aggregate.max_prefix;
2907 if (candidate > max_value) {
2908 max_value = candidate;
2909 first_position = range_begin + byte_aggregate.pos_first_max;
2910 }
2911 current_excess += byte_aggregate.excess_total;
2912 range_begin += 8;
2913 }
2914
2915 while (range_begin <= range_end) {
2916 current_excess += bit(range_begin) ? +1 : -1;
2917 if (current_excess > max_value) {
2918 max_value = current_excess;
2919 first_position = range_begin;
2920 }
2921 ++range_begin;
2922 }
2923
2924 max_value_out = (max_value == INT_MIN ? 0 : max_value);
2925 }
2926
2933 void build(const size_t& leaf_block_bits, const float& max_overhead) {
2934 // the lower clamp depends on the desired overhead fraction; otherwise use
2935 // 64
2936 const size_t clamp_by_overhead =
2937 (max_overhead >= 0.0
2938 ? choose_block_bits_for_overhead(num_bits, max_overhead)
2939 : size_t(64));
2940
2941 // chosen block_bits: honor an explicit request, but not below
2942 // clamp_by_overhead
2943 if (leaf_block_bits == 0) {
2944 block_bits =
2945 std::max(clamp_by_overhead,
2946 std::bit_ceil<size_t>(
2947 (num_bits <= 1) ? 1 : std::bit_width(num_bits - 1)));
2948 } else {
2949 block_bits =
2950 std::max(clamp_by_overhead,
2951 std::bit_ceil(std::max<size_t>(1, leaf_block_bits)));
2952 }
2953
2954#ifdef DEBUG
2955 // finalizes the achieved overhead percentage
2956 built_overhead = overhead_for(num_bits, block_bits);
2957#endif
2958
2959 leaf_count = ceil_div(num_bits, block_bits);
2960 first_leaf_index = std::bit_ceil(std::max<size_t>(1, leaf_count));
2961 const size_t tree_size = first_leaf_index + leaf_count - 1;
2962 segment_size_bits.assign(tree_size + 1, 0);
2963 node_total_excess.assign(tree_size + 1, 0);
2964 node_min_prefix_excess.assign(tree_size + 1, 0);
2965 node_max_prefix_excess.assign(tree_size + 1, 0);
2966 node_min_count.assign(tree_size + 1, 0);
2967 node_pattern10_count.assign(tree_size + 1, 0);
2968 node_first_bit.assign(tree_size + 1, 0);
2969 node_last_bit.assign(tree_size + 1, 0);
2970
2971 // leaves
2972 for (size_t leaf_block_index = 0; leaf_block_index < leaf_count;
2973 ++leaf_block_index) {
2974 const size_t leaf_node_index = first_leaf_index + leaf_block_index;
2975 const size_t segment_begin = leaf_block_index * block_bits;
2976 const size_t segment_end = std::min(num_bits, segment_begin + block_bits);
2977 segment_size_bits[leaf_node_index] = segment_end - segment_begin;
2978
2979 if (segment_begin < segment_end) {
2980 node_first_bit[leaf_node_index] = bit(segment_begin);
2981 }
2982
2983 const auto& aggregates_table = LUT8();
2984
2985 int current_excess = 0, min_value = INT_MAX, max_value = INT_MIN;
2986 uint32_t min_count = 0;
2987 uint32_t pattern10_count = 0;
2988
2989 uint8_t previous_bit = 0;
2990
2991 size_t position = segment_begin;
2992
2993 // Full bytes
2994 while (position + 8 <= segment_end) {
2995 const uint8_t byte_value = get_byte(position);
2996 const auto& byte_aggregate = aggregates_table[byte_value];
2997
2998 // internal "10" inside the byte
2999 pattern10_count += byte_aggregate.pattern10_count;
3000 // stitching across the boundary between the previous and current byte
3001 // (within the segment)
3002 if (previous_bit == 1 && byte_aggregate.first_bit == 0) {
3003 pattern10_count++;
3004 }
3005
3006 // prefix min/max accounting for the current offset
3007 const int candidate_min = current_excess + byte_aggregate.min_prefix;
3008 if (candidate_min < min_value) {
3009 min_value = candidate_min;
3010 min_count = byte_aggregate.min_count;
3011 } else if (candidate_min == min_value) {
3012 min_count += byte_aggregate.min_count;
3013 }
3014
3015 max_value =
3016 std::max(max_value, current_excess + byte_aggregate.max_prefix);
3017 current_excess += byte_aggregate.excess_total;
3018 previous_bit = byte_aggregate.last_bit;
3019 position += 8;
3020 }
3021
3022 // Tail < 8 bits
3023 while (position < segment_end) {
3024 const uint8_t bit_value = bit(position);
3025 if (previous_bit == 1 && bit_value == 0) {
3026 pattern10_count++;
3027 }
3028 const int step = bit_value ? +1 : -1;
3029 current_excess += step;
3030 if (current_excess < min_value) {
3031 min_value = current_excess;
3032 min_count = 1;
3033 } else if (current_excess == min_value) {
3034 ++min_count;
3035 }
3036 if (current_excess > max_value) {
3037 max_value = current_excess;
3038 }
3039
3040 previous_bit = bit_value;
3041 ++position;
3042 }
3043
3044 if (segment_begin < segment_end) {
3045 node_last_bit[leaf_node_index] = previous_bit;
3046 }
3047
3048 node_total_excess[leaf_node_index] = current_excess;
3049 node_min_prefix_excess[leaf_node_index] =
3050 (segment_size_bits[leaf_node_index] == 0 ? 0 : min_value);
3051 node_max_prefix_excess[leaf_node_index] =
3052 (segment_size_bits[leaf_node_index] == 0 ? 0 : max_value);
3053 node_min_count[leaf_node_index] = min_count;
3054 node_pattern10_count[leaf_node_index] = (uint32_t)pattern10_count;
3055 }
3056 // internal nodes
3057 for (size_t node_index = first_leaf_index - 1; node_index >= 1;
3058 --node_index) {
3059 const size_t left_child = node_index << 1;
3060 const size_t right_child = left_child | 1;
3061 const bool has_left =
3062 (left_child <= tree_size) && segment_size_bits[left_child];
3063 const bool has_right =
3064 (right_child <= tree_size) && segment_size_bits[right_child];
3065 if (!has_left && !has_right) {
3066 segment_size_bits[node_index] = 0;
3067 continue;
3068 }
3069 if (has_left && !has_right) {
3070 segment_size_bits[node_index] = segment_size_bits[left_child];
3071 node_total_excess[node_index] = node_total_excess[left_child];
3072 node_min_prefix_excess[node_index] = node_min_prefix_excess[left_child];
3073 node_max_prefix_excess[node_index] = node_max_prefix_excess[left_child];
3074 node_min_count[node_index] = node_min_count[left_child];
3075 node_pattern10_count[node_index] = node_pattern10_count[left_child];
3076 node_first_bit[node_index] = node_first_bit[left_child];
3077 node_last_bit[node_index] = node_last_bit[left_child];
3078 } else if (!has_left && has_right) {
3079 segment_size_bits[node_index] = segment_size_bits[right_child];
3080 node_total_excess[node_index] = node_total_excess[right_child];
3081 node_min_prefix_excess[node_index] =
3082 node_min_prefix_excess[right_child];
3083 node_max_prefix_excess[node_index] =
3084 node_max_prefix_excess[right_child];
3085 node_min_count[node_index] = node_min_count[right_child];
3086 node_pattern10_count[node_index] = node_pattern10_count[right_child];
3087 node_first_bit[node_index] = node_first_bit[right_child];
3088 node_last_bit[node_index] = node_last_bit[right_child];
3089 } else {
3090 segment_size_bits[node_index] =
3091 segment_size_bits[left_child] + segment_size_bits[right_child];
3092 node_total_excess[node_index] =
3093 node_total_excess[left_child] + node_total_excess[right_child];
3094 const int right_min_candidate =
3095 node_total_excess[left_child] + node_min_prefix_excess[right_child];
3096 const int right_max_candidate =
3097 node_total_excess[left_child] + node_max_prefix_excess[right_child];
3098 node_min_prefix_excess[node_index] =
3099 std::min(node_min_prefix_excess[left_child], right_min_candidate);
3100 node_max_prefix_excess[node_index] =
3101 std::max(node_max_prefix_excess[left_child], right_max_candidate);
3102 node_min_count[node_index] =
3103 (node_min_prefix_excess[left_child] ==
3104 node_min_prefix_excess[node_index]
3105 ? node_min_count[left_child]
3106 : 0) +
3107 (right_min_candidate == node_min_prefix_excess[node_index]
3108 ? node_min_count[right_child]
3109 : 0);
3110 node_pattern10_count[node_index] = node_pattern10_count[left_child] +
3111 node_pattern10_count[right_child] +
3112 ((node_last_bit[left_child] == 1 &&
3113 node_first_bit[right_child] == 0)
3114 ? 1u
3115 : 0u);
3116 node_first_bit[node_index] = node_first_bit[left_child];
3117 node_last_bit[node_index] = node_last_bit[right_child];
3118 }
3119 if (node_index == 1) {
3120 break;
3121 }
3122 }
3123 }
3124};
3125
3126} // namespace pixie
Bounds-checked reader for canonical little-endian binary data.
Definition serialization.h:526
std::uint16_t read_u16()
Read an unsigned little-endian 16-bit integer.
Definition serialization.h:555
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::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 read_size()
Read an unsigned 64-bit size and convert it to size_t.
Definition serialization.h:587
Bounded-buffer writer for canonical little-endian binary data.
Definition serialization.h:198
void write_u32(std::uint32_t value)
Write an unsigned 32-bit integer in little-endian order.
Definition serialization.h:258
void write_u8(std::uint8_t value)
Write an unsigned eight-bit integer.
Definition serialization.h:252
std::size_t size_bytes() const noexcept
Return the logical number of bytes written.
Definition serialization.h:244
void write_zeros(std::size_t count)
Append count zero bytes.
Definition serialization.h:328
void write_u16(std::uint16_t value)
Write an unsigned 16-bit integer in little-endian order.
Definition serialization.h:255
void patch_u64(std::size_t position, std::uint64_t value)
Replace an existing 64-bit field with value.
Definition serialization.h:373
std::size_t write_u64_placeholder()
Write a zero 64-bit field and return its byte position.
Definition serialization.h:363
void write_size(std::size_t value)
Write a platform size as an unsigned 64-bit integer.
Definition serialization.h:287
CRTP facade for rank/select and range min-max tree operations.
Definition rmm.h:29
std::size_t size() const
Definition rmm.h:39
int range_max_query_val_impl(const size_t &range_begin, const size_t &range_end) const
Value of the maximum prefix excess on [range_begin, range_end] relative to range_begin.
Definition tree.h:787
size_t fwdsearch_impl(const size_t &start_position, const int &delta) const
Forward search: first position p ≥ start_position where excess_impl(p) = excess_impl(start_position) ...
Definition tree.h:438
static RmMTree deserialize_impl(BinaryReader &reader, std::span< const std::uint64_t > words, DeserializationValidation validation=DeserializationValidation::kQuick)
Restore owning tree metadata over caller-owned source words.
Definition tree.h:189
size_t minselect_impl(const size_t &range_begin, const size_t &range_end, size_t target_min_rank) const
Position of the target_min_rank-th (1-based) occurrence of the minimum on [range_begin,...
Definition tree.h:887
size_t rank0_impl(const size_t &end_position) const
Number of zeros in prefix [0, end_position).
Definition tree.h:266
int bit(const size_t &position) const noexcept
Read bit at position position (LSB-first across words).
Definition tree.h:1090
size_t close_impl(const size_t &open_position) const
close_impl(open_position): matching ')' for '(' at open_position.
Definition tree.h:1047
int excess_impl(const size_t &end_position) const
Prefix excess on [0, end_position): +1 for '1', −1 for '0'.
Definition tree.h:428
size_t bwdsearch_impl(const size_t &start_position, const int &delta) const
Backward search: last position p ≤ start_position where excess_impl(p) = excess_impl(start_position) ...
Definition tree.h:502
RmMTree()=default
Construct empty structure.
size_t rank1_impl(const size_t &end_position) const
Number of ones in prefix [0, end_position).
Definition tree.h:241
RmMTree(std::span< const std::uint64_t > words, size_t bit_count, const size_t &leaf_block_bits=0, const float &max_overhead=-1.0)
Build from a non-owning view of 64-bit words (LSB-first).
Definition tree.h:118
size_t enclose_impl(const size_t &position) const
enclose_impl(position): opening '(' that strictly encloses position.
Definition tree.h:1077
int range_min_query_val_impl(const size_t &range_begin, const size_t &range_end) const
Value of the minimum prefix excess on [range_begin, range_end] relative to range_begin.
Definition tree.h:669
size_t open_impl(const size_t &close_position) const
open_impl(close_position): matching '(' for ')' at close_position.
Definition tree.h:1062
size_t rank10_impl(const size_t &end_position) const
Rank of the pattern "10" (starts) within [0, end_position).
Definition tree.h:340
size_t mincount_impl(const size_t &range_begin, const size_t &range_end) const
How many times the minimum prefix excess occurs on [range_begin, range_end].
Definition tree.h:803
size_t range_min_query_pos_impl(const size_t &range_begin, const size_t &range_end) const
Position of the first minimum of excess on [range_begin, range_end] (inclusive).
Definition tree.h:567
size_t select0_impl(size_t target_zero_rank) const
1-based select of the target_zero_rank-th zero.
Definition tree.h:305
size_t select10_impl(size_t target_pattern_rank) const
1-based select of the target_pattern_rank-th "10" start.
Definition tree.h:373
static constexpr size_t npos
Sentinel for "not found".
Definition tree.h:92
void serialize_impl(BinaryWriter &writer) const
Serialize the complete owning tree metadata.
Definition tree.h:132
size_t select1_impl(size_t target_one_rank) const
1-based select of the target_one_rank-th one.
Definition tree.h:274
size_t range_max_query_pos_impl(const size_t &range_begin, const size_t &range_end) const
Position of the first maximum of excess on [range_begin, range_end] (inclusive).
Definition tree.h:687
CRTP facade for optional binary serialization and deserialization.
Definition serialization.h:693
Common interface for rank/select and range min-max 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