Skip to main content

vortex_mask/
intersect_by_rank.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::iter::Chain;
5use std::iter::Once;
6use std::iter::once;
7use std::sync::Arc;
8
9use vortex_buffer::BitBuffer;
10use vortex_buffer::BitChunkIterator;
11use vortex_buffer::BufferMut;
12use vortex_buffer::CpuKernel;
13use vortex_error::VortexExpect;
14
15use crate::Mask;
16use crate::MaskValues;
17
18trait DepositBits {
19    /// Whether the implementation benefits from short-circuiting on `rank_bits == 0`
20    /// and `self_chunk == u64::MAX`. The portable path loops `popcount(mask)` times,
21    /// so an all-ones mask is genuinely expensive; BMI2 PDEP is constant-time and
22    /// the branches just add mispredict cost.
23    const PREFER_BRANCHES: bool;
24
25    fn deposit_bits(source: u64, mask: u64, mask_count: usize) -> u64;
26}
27
28trait SelectBit {
29    /// Position (0..63) of the `rank`-th set bit in `word`. Caller ensures
30    /// `rank < word.count_ones()`.
31    fn select_bit_position(word: u64, rank: usize) -> usize;
32}
33
34struct Portable;
35
36impl DepositBits for Portable {
37    const PREFER_BRANCHES: bool = true;
38
39    #[inline]
40    fn deposit_bits(source: u64, mask: u64, mask_count: usize) -> u64 {
41        if mask_count >= 16 && source.count_ones() as usize * 8 < mask_count {
42            return deposit_sparse_source(source, mask);
43        }
44
45        deposit_by_mask(source, mask)
46    }
47}
48
49impl SelectBit for Portable {
50    #[inline]
51    fn select_bit_position(word: u64, rank: usize) -> usize {
52        select_bit_position_portable(word, rank)
53    }
54}
55
56#[inline]
57fn deposit_by_mask(mut source: u64, mut mask: u64) -> u64 {
58    let mut result = 0u64;
59    while mask != 0 {
60        let bit = mask & mask.wrapping_neg();
61        if source & 1 != 0 {
62            result |= bit;
63        }
64        source >>= 1;
65        mask &= mask - 1;
66    }
67    result
68}
69
70#[inline]
71fn deposit_sparse_source(mut source: u64, mask: u64) -> u64 {
72    let mut result = 0u64;
73    while source != 0 {
74        result |= select_set_bit(mask, source.trailing_zeros() as usize);
75        source &= source - 1;
76    }
77    result
78}
79
80#[inline]
81fn select_set_bit(word: u64, rank: usize) -> u64 {
82    1u64 << select_bit_position_portable(word, rank)
83}
84
85#[inline]
86fn select_bit_position_portable(word: u64, mut rank: usize) -> usize {
87    debug_assert!(rank < word.count_ones() as usize);
88    let mut bit_offset = 0usize;
89    for byte in word.to_le_bytes() {
90        let count = byte.count_ones() as usize;
91        if rank < count {
92            let mut bits = byte;
93            for _ in 0..rank {
94                bits &= bits - 1;
95            }
96
97            return bit_offset + bits.trailing_zeros() as usize;
98        }
99
100        rank -= count;
101        bit_offset += 8;
102    }
103
104    debug_assert!(false, "rank out of bounds");
105    0
106}
107
108#[cfg(target_arch = "x86_64")]
109struct Bmi2;
110
111#[cfg(target_arch = "x86_64")]
112impl DepositBits for Bmi2 {
113    const PREFER_BRANCHES: bool = false;
114
115    #[inline]
116    fn deposit_bits(source: u64, mask: u64, _mask_count: usize) -> u64 {
117        // SAFETY: callers only instantiate this implementation after checking BMI2 support.
118        unsafe { pdep_bmi2(source, mask) }
119    }
120}
121
122#[cfg(target_arch = "x86_64")]
123impl SelectBit for Bmi2 {
124    #[inline]
125    fn select_bit_position(word: u64, rank: usize) -> usize {
126        // SAFETY: callers only instantiate this implementation after checking BMI2 support.
127        unsafe { select_bit_position_bmi2(word, rank) }
128    }
129}
130
131#[cfg(target_arch = "x86_64")]
132#[target_feature(enable = "bmi2")]
133unsafe fn pdep_bmi2(source: u64, mask: u64) -> u64 {
134    use std::arch::x86_64;
135    x86_64::_pdep_u64(source, mask)
136}
137
138#[cfg(target_arch = "x86_64")]
139#[target_feature(enable = "bmi2")]
140unsafe fn select_bit_position_bmi2(word: u64, rank: usize) -> usize {
141    use std::arch::x86_64;
142    debug_assert!(rank < word.count_ones() as usize);
143    // PDEP places the rank-th bit of source into the rank-th set bit of mask, returning a single
144    // bit at the desired position.
145    let bit = x86_64::_pdep_u64(1u64 << rank, word);
146    bit.trailing_zeros() as usize
147}
148
149/// Reader that pulls variable-length (0..=64 bit) groups from a [`BitBuffer`] sequentially.
150///
151/// Maintains a 128-bit window over two consecutive chunks (`current`, `next`) and uses a
152/// funnel shift via `u128` to extract bits at any offset without branching. The shift
153/// pattern compiles to a single funnel-shift / SHRD-style sequence on x86_64.
154struct RankBitReader<'a> {
155    chunk_iter: Chain<BitChunkIterator<'a>, Once<u64>>,
156    current: u64,
157    next: u64,
158    bit_offset: usize,
159}
160
161impl<'a> RankBitReader<'a> {
162    fn new(buffer: &'a BitBuffer) -> Self {
163        let chunks = buffer.chunks();
164        let mut chunk_iter = chunks.iter().chain(once(chunks.remainder_bits()));
165
166        let current = chunk_iter.next().unwrap_or(0);
167        let next = chunk_iter.next().unwrap_or(0);
168
169        Self {
170            chunk_iter,
171            current,
172            next,
173            bit_offset: 0,
174        }
175    }
176
177    #[inline]
178    fn fetch_next(&mut self) -> u64 {
179        self.chunk_iter.next().unwrap_or(0)
180    }
181
182    #[inline]
183    fn read(&mut self, bit_count: usize) -> u64 {
184        debug_assert!(bit_count <= 64);
185
186        // Funnel shift: extract `bit_count` bits at `bit_offset` from the (next:current)
187        // 128-bit window. For bit_offset in 0..=63 this is a single SHRD-style instruction
188        // on x86_64; the u128 cast keeps it well-defined when bit_offset == 0.
189        let combined = ((self.next as u128) << 64) | (self.current as u128);
190        // The truncation is intentional: we want the low 64 bits of the funnel-shifted
191        // window, which is exactly what `as u64` produces.
192        #[expect(clippy::cast_possible_truncation)]
193        let bits = (combined >> self.bit_offset) as u64 & low_bits(bit_count);
194
195        let new_offset = self.bit_offset + bit_count;
196        if new_offset >= 64 {
197            self.current = self.next;
198            self.next = self.fetch_next();
199            self.bit_offset = new_offset - 64;
200        } else {
201            self.bit_offset = new_offset;
202        }
203
204        bits
205    }
206}
207
208#[inline]
209fn low_bits(bit_count: usize) -> u64 {
210    debug_assert!(bit_count <= 64);
211    if bit_count == 64 {
212        u64::MAX
213    } else {
214        (1u64 << bit_count) - 1
215    }
216}
217
218#[inline]
219fn mask_from_buffer(buffer: BitBuffer, true_count: usize) -> Mask {
220    let len = buffer.len();
221    if true_count == 0 {
222        return Mask::new_false(len);
223    }
224    if true_count == len {
225        return Mask::new_true(len);
226    }
227
228    Mask::Values(Arc::new(MaskValues {
229        buffer,
230        indices: Default::default(),
231        slices: Default::default(),
232        true_count,
233        density: true_count as f64 / len as f64,
234    }))
235}
236
237#[inline]
238fn push_result_chunk<D: DepositBits>(
239    result: &mut BufferMut<u64>,
240    self_chunk: u64,
241    self_count: usize,
242    rank_bits: u64,
243) {
244    let chunk = if D::PREFER_BRANCHES {
245        if rank_bits == 0 {
246            0
247        } else if self_chunk == u64::MAX {
248            rank_bits
249        } else {
250            D::deposit_bits(rank_bits, self_chunk, self_count)
251        }
252    } else {
253        D::deposit_bits(rank_bits, self_chunk, self_count)
254    };
255
256    // SAFETY: callers allocate enough capacity for every output chunk.
257    unsafe { result.push_unchecked(chunk) };
258}
259
260fn intersect_bit_buffers<D: DepositBits>(
261    self_buffer: &BitBuffer,
262    mask_buffer: &BitBuffer,
263    true_count: usize,
264) -> Mask {
265    let len = self_buffer.len();
266    let mut result = BufferMut::with_capacity(len.div_ceil(64));
267    let mut reader = RankBitReader::new(mask_buffer);
268    let self_chunks = self_buffer.chunks();
269
270    for self_chunk in self_chunks.iter() {
271        let self_count = self_chunk.count_ones() as usize;
272        let rank_bits = reader.read(self_count);
273        push_result_chunk::<D>(&mut result, self_chunk, self_count, rank_bits);
274    }
275
276    if self_chunks.remainder_len() != 0 {
277        let self_chunk = self_chunks.remainder_bits();
278        let self_count = self_chunk.count_ones() as usize;
279        let rank_bits = reader.read(self_count);
280        push_result_chunk::<D>(&mut result, self_chunk, self_count, rank_bits);
281    }
282
283    mask_from_buffer(
284        BitBuffer::new(result.freeze().into_byte_buffer(), len),
285        true_count,
286    )
287}
288
289fn intersect_bit_buffer_by_rank_indices<D: DepositBits>(
290    self_buffer: &BitBuffer,
291    mask_indices: &[usize],
292) -> Mask {
293    let len = self_buffer.len();
294    let mut result = BufferMut::with_capacity(len.div_ceil(64));
295    let self_chunks = self_buffer.chunks();
296    let mut rank_base = 0usize;
297    let mut rank_idx = 0usize;
298
299    for self_chunk in self_chunks.iter() {
300        let self_count = self_chunk.count_ones() as usize;
301        let next_rank_base = rank_base + self_count;
302        let rank_bits = rank_bits_for_chunk(mask_indices, &mut rank_idx, rank_base, next_rank_base);
303        push_result_chunk::<D>(&mut result, self_chunk, self_count, rank_bits);
304        rank_base = next_rank_base;
305    }
306
307    if self_chunks.remainder_len() != 0 {
308        let self_chunk = self_chunks.remainder_bits();
309        let self_count = self_chunk.count_ones() as usize;
310        let next_rank_base = rank_base + self_count;
311        let rank_bits = rank_bits_for_chunk(mask_indices, &mut rank_idx, rank_base, next_rank_base);
312        push_result_chunk::<D>(&mut result, self_chunk, self_count, rank_bits);
313    }
314
315    debug_assert_eq!(rank_idx, mask_indices.len());
316
317    mask_from_buffer(
318        BitBuffer::new(result.freeze().into_byte_buffer(), len),
319        mask_indices.len(),
320    )
321}
322
323/// Walks `mask_indices` (global ranks into `self_buffer.set_bits`) and emits the corresponding
324/// positions in `self_buffer`. For each rank, advances `self_buffer`'s chunks via popcount
325/// skip-while, then locates the bit inside the current chunk with rank-select.
326///
327/// This dominates the chunk-scan paths when the mask is very sparse: cost is
328/// `O(mask.true_count() + self.len() / 64)` rather than `O(self.len() / 64)` per chunk.
329fn intersect_mask_driven<S, I>(self_buffer: &BitBuffer, mask_indices: I, true_count: usize) -> Mask
330where
331    S: SelectBit,
332    I: Iterator<Item = usize>,
333{
334    let len = self_buffer.len();
335    if true_count == 0 {
336        return Mask::new_false(len);
337    }
338
339    let mut chunk_iter = self_buffer.chunks().iter_padded();
340
341    let mut current_chunk = chunk_iter.next().unwrap_or(0);
342    let mut current_count = current_chunk.count_ones() as usize;
343    let mut current_chunk_idx = 0usize;
344    let mut rank_before = 0usize;
345
346    let mut output = Vec::with_capacity(true_count);
347
348    for global_rank in mask_indices {
349        while rank_before + current_count <= global_rank {
350            rank_before += current_count;
351            current_chunk_idx += 1;
352            current_chunk = chunk_iter.next().vortex_expect("mask index out of bounds");
353            current_count = current_chunk.count_ones() as usize;
354        }
355
356        let local_rank = global_rank - rank_before;
357        let bit_pos = S::select_bit_position(current_chunk, local_rank);
358        output.push(current_chunk_idx * 64 + bit_pos);
359    }
360
361    debug_assert_eq!(output.len(), true_count);
362    Mask::from_indices(len, output)
363}
364
365#[inline]
366fn rank_bits_for_chunk(
367    mask_indices: &[usize],
368    rank_idx: &mut usize,
369    rank_base: usize,
370    next_rank_base: usize,
371) -> u64 {
372    let mut rank_bits = 0u64;
373    while let Some(&rank) = mask_indices.get(*rank_idx) {
374        if rank >= next_rank_base {
375            break;
376        }
377        rank_bits |= 1u64 << (rank - rank_base);
378        *rank_idx += 1;
379    }
380    rank_bits
381}
382
383fn intersect_by_rank_indices(len: usize, self_indices: &[usize], mask_indices: &[usize]) -> Mask {
384    Mask::from_indices(
385        len,
386        mask_indices.iter().map(|idx| {
387            // SAFETY: mask indices are ranks into self_indices, because
388            // mask.len() == self.true_count() == self_indices.len().
389            unsafe { *self_indices.get_unchecked(*idx) }
390        }),
391    )
392}
393
394#[inline]
395fn intersect_bit_buffers_dispatch(
396    self_buffer: &BitBuffer,
397    mask_buffer: &BitBuffer,
398    true_count: usize,
399) -> Mask {
400    type IntersectBuffers = fn(&BitBuffer, &BitBuffer, usize) -> Mask;
401    static KERNEL: CpuKernel<IntersectBuffers> = CpuKernel::new(|| {
402        #[cfg(target_arch = "x86_64")]
403        {
404            if std::arch::is_x86_feature_detected!("bmi2") {
405                return intersect_bit_buffers::<Bmi2>;
406            }
407        }
408        intersect_bit_buffers::<Portable>
409    });
410    KERNEL.get()(self_buffer, mask_buffer, true_count)
411}
412
413#[inline]
414fn intersect_rank_indices_dispatch(self_buffer: &BitBuffer, mask_indices: &[usize]) -> Mask {
415    type IntersectRankIndices = fn(&BitBuffer, &[usize]) -> Mask;
416    static KERNEL: CpuKernel<IntersectRankIndices> = CpuKernel::new(|| {
417        #[cfg(target_arch = "x86_64")]
418        {
419            if std::arch::is_x86_feature_detected!("bmi2") {
420                return intersect_bit_buffer_by_rank_indices::<Bmi2>;
421            }
422        }
423        intersect_bit_buffer_by_rank_indices::<Portable>
424    });
425    KERNEL.get()(self_buffer, mask_indices)
426}
427
428#[inline]
429fn intersect_mask_driven_dispatch<I>(
430    self_buffer: &BitBuffer,
431    mask_indices: I,
432    true_count: usize,
433) -> Mask
434where
435    I: Iterator<Item = usize>,
436{
437    #[cfg(target_arch = "x86_64")]
438    if std::arch::is_x86_feature_detected!("bmi2") {
439        return intersect_mask_driven::<Bmi2, _>(self_buffer, mask_indices, true_count);
440    }
441
442    intersect_mask_driven::<Portable, _>(self_buffer, mask_indices, true_count)
443}
444
445/// Check if a mask is sparse.
446///
447/// BitBuffer traversal uses u64, hence we conclude that one or fewer values per u64 is sparse
448fn mask_is_sparse(values: &Arc<MaskValues>) -> bool {
449    values.true_count().saturating_mul(64) < values.len()
450}
451
452/// Check if a rank mask is sparse
453///
454/// The mask-driven path becomes worthwhile around ~3% mask density: each set
455/// bit costs a select and push, but we save a per-self-chunk popcount + deposit.
456fn rank_mask_is_sparse(values: &Arc<MaskValues>) -> bool {
457    values.true_count().saturating_mul(32) < values.len()
458}
459
460impl Mask {
461    /// Take the intersection of the `mask` with the set of true values in `self`.
462    ///
463    /// The hot path keeps bit-buffer-backed masks as bit buffers. It scans the set bits of `self`
464    /// by rank and deposits selected rank bits into their original positions.
465    ///
466    /// # Examples
467    ///
468    /// Keep the third and fifth set values from mask `m1`:
469    /// ```
470    /// use vortex_mask::Mask;
471    ///
472    /// let m1 = Mask::from_iter([true, false, false, true, true, true, false, true]);
473    /// let m2 = Mask::from_iter([false, false, true, false, true]);
474    /// assert_eq!(
475    ///     m1.intersect_by_rank(&m2),
476    ///     Mask::from_iter([false, false, false, false, true, false, false, true])
477    /// );
478    /// ```
479    pub fn intersect_by_rank(&self, mask: &Mask) -> Mask {
480        assert_eq!(self.true_count(), mask.len());
481
482        match (self, mask) {
483            (Self::AllTrue(_), _) => mask.clone(),
484            (_, Self::AllTrue(_)) => self.clone(),
485            (Self::AllFalse(_), _) | (_, Self::AllFalse(_)) => Self::new_false(self.len()),
486            (Self::Values(self_values), Self::Values(mask_values)) => {
487                // Four dispatch cases keyed by (self density, mask density):
488                //
489                //              | mask sparse | mask dense
490                // -------------+-------------+------------
491                // self sparse  | indices     | indices
492                // self dense   | mask-driven | bit-buffer
493                if let Some(mask_indices) = mask_values.indices.get() {
494                    if let Some(self_indices) = self_values.indices.get()
495                        && mask_indices.len() < self.len().div_ceil(64)
496                    {
497                        return intersect_by_rank_indices(self.len(), self_indices, mask_indices);
498                    }
499
500                    let self_is_very_sparse = mask_is_sparse(self_values);
501                    let mask_is_very_sparse = rank_mask_is_sparse(mask_values);
502
503                    if self_is_very_sparse {
504                        return intersect_by_rank_indices(
505                            self.len(),
506                            self_values.indices(),
507                            mask_indices,
508                        );
509                    }
510
511                    if mask_is_very_sparse {
512                        return intersect_mask_driven_dispatch(
513                            self_values.bit_buffer(),
514                            mask_indices.iter().copied(),
515                            mask_values.true_count(),
516                        );
517                    }
518
519                    if mask_indices.len().saturating_mul(4) > mask.len() {
520                        return intersect_bit_buffers_dispatch(
521                            self_values.bit_buffer(),
522                            mask_values.bit_buffer(),
523                            mask_values.true_count(),
524                        );
525                    }
526
527                    return intersect_rank_indices_dispatch(self_values.bit_buffer(), mask_indices);
528                }
529
530                let self_is_very_sparse = mask_is_sparse(self_values);
531                let mask_is_very_sparse = rank_mask_is_sparse(mask_values);
532
533                if self_is_very_sparse {
534                    return intersect_by_rank_indices(
535                        self.len(),
536                        self_values.indices(),
537                        mask_values.indices(),
538                    );
539                }
540
541                if mask_is_very_sparse {
542                    return intersect_mask_driven_dispatch(
543                        self_values.bit_buffer(),
544                        mask_values.bit_buffer().set_indices(),
545                        mask_values.true_count(),
546                    );
547                }
548
549                intersect_bit_buffers_dispatch(
550                    self_values.bit_buffer(),
551                    mask_values.bit_buffer(),
552                    mask_values.true_count(),
553                )
554            }
555        }
556    }
557}
558
559#[cfg(test)]
560mod test {
561    use rstest::rstest;
562    use vortex_buffer::BitBuffer;
563
564    use crate::Mask;
565
566    #[test]
567    fn mask_bitand_all_as_bit_and() {
568        let this = Mask::from_buffer(BitBuffer::from_iter(vec![true, true, true, true, true]));
569        let mask = Mask::from_buffer(BitBuffer::from_iter(vec![false, true, false, true, true]));
570        assert_eq!(
571            this.intersect_by_rank(&mask),
572            Mask::from_indices(5, vec![1, 3, 4])
573        );
574    }
575
576    #[test]
577    fn mask_bitand_all_true() {
578        let this = Mask::from_buffer(BitBuffer::from_iter(vec![false, false, true, true, true]));
579        let mask = Mask::from_buffer(BitBuffer::from_iter(vec![true, true, true]));
580        assert_eq!(
581            this.intersect_by_rank(&mask),
582            Mask::from_indices(5, vec![2, 3, 4])
583        );
584    }
585
586    #[test]
587    fn mask_bitand_true() {
588        let this = Mask::from_buffer(BitBuffer::from_iter(vec![true, false, false, true, true]));
589        let mask = Mask::from_buffer(BitBuffer::from_iter(vec![true, false, true]));
590        assert_eq!(
591            this.intersect_by_rank(&mask),
592            Mask::from_indices(5, vec![0, 4])
593        );
594    }
595
596    #[test]
597    fn mask_bitand_false() {
598        let this = Mask::from_buffer(BitBuffer::from_iter(vec![true, false, false, true, true]));
599        let mask = Mask::from_buffer(BitBuffer::from_iter(vec![false, false, false]));
600        assert_eq!(this.intersect_by_rank(&mask), Mask::from_indices(5, vec![]));
601    }
602
603    #[test]
604    fn mask_intersect_by_rank_all_false() {
605        let this = Mask::AllFalse(10);
606        let mask = Mask::AllFalse(0);
607        assert_eq!(this.intersect_by_rank(&mask), Mask::AllFalse(10));
608    }
609
610    #[rstest]
611    #[case::all_true_with_all_true(
612        Mask::new_true(5),
613        Mask::new_true(5),
614        vec![0, 1, 2, 3, 4]
615    )]
616    #[case::all_true_with_all_false(
617        Mask::new_true(5),
618        Mask::new_false(5),
619        vec![]
620    )]
621    #[case::all_false_with_any(
622        Mask::new_false(10),
623        Mask::new_true(0),
624        vec![]
625    )]
626    #[case::indices_with_all_true(
627        Mask::from_indices(10, vec![2, 5, 7, 9]),
628        Mask::new_true(4),
629        vec![2, 5, 7, 9]
630    )]
631    #[case::indices_with_all_false(
632        Mask::from_indices(10, vec![2, 5, 7, 9]),
633        Mask::new_false(4),
634        vec![]
635    )]
636    fn test_intersect_by_rank_special_cases(
637        #[case] base_mask: Mask,
638        #[case] rank_mask: Mask,
639        #[case] expected_indices: Vec<usize>,
640    ) {
641        let result = base_mask.intersect_by_rank(&rank_mask);
642
643        match result.indices() {
644            crate::AllOr::All => assert_eq!(expected_indices.len(), result.len()),
645            crate::AllOr::None => assert!(expected_indices.is_empty()),
646            crate::AllOr::Some(indices) => assert_eq!(indices, &expected_indices[..]),
647        }
648    }
649
650    #[test]
651    fn test_intersect_by_rank_example() {
652        // Example from the documentation
653        let m1 = Mask::from_iter([true, false, false, true, true, true, false, true]);
654        let m2 = Mask::from_iter([false, false, true, false, true]);
655        let result = m1.intersect_by_rank(&m2);
656        let expected = Mask::from_iter([false, false, false, false, true, false, false, true]);
657        assert_eq!(result, expected);
658    }
659
660    #[test]
661    #[should_panic]
662    fn test_intersect_by_rank_wrong_length() {
663        let m1 = Mask::from_indices(10, vec![2, 5, 7]); // 3 true values
664        let m2 = Mask::new_true(5); // 5 true values - doesn't match
665        m1.intersect_by_rank(&m2);
666    }
667
668    #[rstest]
669    #[case::single_element(
670        vec![3],
671        vec![true],
672        vec![3]
673    )]
674    #[case::single_element_masked(
675        vec![3],
676        vec![false],
677        vec![]
678    )]
679    #[case::alternating(
680        vec![0, 2, 4, 6, 8],
681        vec![true, false, true, false, true],
682        vec![0, 4, 8]
683    )]
684    #[case::consecutive(
685        vec![5, 6, 7, 8, 9],
686        vec![false, true, true, true, false],
687        vec![6, 7, 8]
688    )]
689    fn test_intersect_by_rank_patterns(
690        #[case] base_indices: Vec<usize>,
691        #[case] rank_pattern: Vec<bool>,
692        #[case] expected_indices: Vec<usize>,
693    ) {
694        let base = Mask::from_indices(10, base_indices);
695        let rank = Mask::from_iter(rank_pattern);
696        let result = base.intersect_by_rank(&rank);
697
698        match result.indices() {
699            crate::AllOr::Some(indices) => assert_eq!(indices, &expected_indices[..]),
700            crate::AllOr::None => assert!(expected_indices.is_empty()),
701            _ => panic!("Unexpected result"),
702        }
703    }
704
705    #[rstest]
706    // Larger sizes to push the bench-shaped buffer paths through the unit tests too.
707    #[case::dense_len_1024(1024, 31, 0.5, 0.5)]
708    // Very-sparse mask exercises the mask-driven dispatch path. Both densities live in
709    // the half-open interval where `mask_is_very_sparse` is true.
710    #[case::sparse_mask_1pct(1024, 17, 0.5, 0.01)]
711    #[case::sparse_mask_2pct(2048, 0, 0.5, 0.02)]
712    #[case::very_sparse_mask_with_offsets(513, 5, 0.5, 0.005)]
713    fn test_intersect_by_rank_density_matrix(
714        #[case] base_len: usize,
715        #[case] base_offset: usize,
716        #[case] base_density: f64,
717        #[case] rank_density: f64,
718    ) {
719        #[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
720        let base_threshold = (base_density * 1024.0) as usize;
721        #[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
722        let rank_threshold = (rank_density * 1024.0) as usize;
723
724        let base_source: Vec<bool> = (0..base_len + base_offset + 16)
725            .map(|i| (i * 7 + 13) % 1024 < base_threshold)
726            .collect();
727        let base_bits = base_source[base_offset..base_offset + base_len].to_vec();
728        let base = Mask::from_buffer(
729            BitBuffer::from(base_source).slice(base_offset..base_offset + base_len),
730        );
731
732        let rank_len = base.true_count();
733        let rank_bits: Vec<bool> = (0..rank_len)
734            .map(|i| (i * 11 + 7) % 1024 < rank_threshold)
735            .collect();
736        let rank_from_buffer = Mask::from_buffer(BitBuffer::from(rank_bits.clone()));
737        let rank_indices_vec = rank_bits
738            .iter()
739            .enumerate()
740            .filter_map(|(idx, &v)| v.then_some(idx))
741            .collect::<Vec<_>>();
742        let rank_from_indices = Mask::from_indices(rank_len, rank_indices_vec);
743
744        let expected = expected_intersect_by_rank(&base_bits, &rank_bits);
745
746        assert_eq!(
747            base.intersect_by_rank(&rank_from_buffer),
748            expected,
749            "uncached rank"
750        );
751        assert_eq!(
752            base.intersect_by_rank(&rank_from_indices),
753            expected,
754            "cached rank"
755        );
756    }
757
758    #[rstest]
759    #[case::short(37, 0, 0)]
760    #[case::base_offset(257, 5, 0)]
761    #[case::rank_offset(257, 0, 3)]
762    #[case::both_offsets(513, 6, 5)]
763    fn test_intersect_by_rank_bitbuffer_paths_with_offsets(
764        #[case] base_len: usize,
765        #[case] base_offset: usize,
766        #[case] rank_offset: usize,
767    ) {
768        let base_source: Vec<bool> = (0..base_len + base_offset + 16)
769            .map(|i| (i % 3 == 0) ^ (i % 11 == 0) ^ (i % 17 == 0))
770            .collect();
771        let base_bits = base_source[base_offset..base_offset + base_len].to_vec();
772        let base = Mask::from_buffer(
773            BitBuffer::from(base_source).slice(base_offset..base_offset + base_len),
774        );
775
776        let rank_len = base.true_count();
777        let rank_bits: Vec<bool> = (0..rank_len)
778            .map(|i| (i % 5 == 0) || (i % 13 == 3))
779            .collect();
780        let mut rank_source = vec![false; rank_offset];
781        rank_source.extend(rank_bits.iter().copied());
782        rank_source.extend([true, false, true, false, true, false, true, false]);
783
784        let rank_from_buffer = Mask::from_buffer(
785            BitBuffer::from(rank_source).slice(rank_offset..rank_offset + rank_len),
786        );
787        let rank_indices = rank_bits
788            .iter()
789            .enumerate()
790            .filter_map(|(idx, &value)| value.then_some(idx))
791            .collect::<Vec<_>>();
792        let rank_from_indices = Mask::from_indices(rank_len, rank_indices);
793
794        let expected = expected_intersect_by_rank(&base_bits, &rank_bits);
795
796        assert_eq!(base.intersect_by_rank(&rank_from_buffer), expected);
797        assert_eq!(base.intersect_by_rank(&rank_from_indices), expected);
798    }
799
800    fn expected_intersect_by_rank(base_bits: &[bool], rank_bits: &[bool]) -> Mask {
801        let mut rank = 0usize;
802        Mask::from_iter(base_bits.iter().map(|&is_set| {
803            if is_set {
804                let keep = rank_bits[rank];
805                rank += 1;
806                keep
807            } else {
808                false
809            }
810        }))
811    }
812}