Skip to main content

p3_util/
lib.rs

1//! Various simple utilities.
2
3#![no_std]
4
5extern crate alloc;
6
7use alloc::slice;
8use alloc::string::String;
9use alloc::vec::Vec;
10use core::any::type_name;
11use core::hint::assert_unchecked;
12use core::mem::{ManuallyDrop, MaybeUninit};
13use core::{iter, mem};
14
15use crate::transpose::transpose_in_place_square;
16
17pub mod array_serialization;
18pub mod linear_map;
19pub mod transpose;
20pub mod zip_eq;
21
22/// Computes `ceil(log_2(n))`.
23#[must_use]
24pub const fn log2_ceil_usize(n: usize) -> usize {
25    (usize::BITS - n.saturating_sub(1).leading_zeros()) as usize
26}
27
28/// Computes `floor(log_2(n))`.
29///
30/// Returns `0` for `n == 0` (matching `log2_ceil_usize(0) == 0`); `floor(log2(0))`
31/// is undefined mathematically and the saturating behaviour is the convention used
32/// elsewhere in the workspace.
33#[must_use]
34pub const fn log2_floor_usize(n: usize) -> usize {
35    if n == 0 {
36        return 0;
37    }
38    (usize::BITS - 1 - n.leading_zeros()) as usize
39}
40
41#[must_use]
42pub const fn log2_ceil_u64(n: u64) -> u64 {
43    (u64::BITS - n.saturating_sub(1).leading_zeros()) as u64
44}
45
46/// Returns `2^log_degree` if it can be represented by `usize`.
47#[must_use]
48pub const fn checked_pow2(log_degree: usize) -> Option<usize> {
49    if log_degree < usize::BITS as usize {
50        Some(1usize << log_degree)
51    } else {
52        None
53    }
54}
55
56/// Adds two log-sizes and computes the resulting power of two.
57///
58/// Returns:
59/// - `(a + b, 2^(a + b))` when the sum fits in a `usize` shift,
60/// - `None` if the addition overflows or the resulting power exceeds the representable range.
61#[must_use]
62pub const fn checked_log_size_sum(a: usize, b: usize) -> Option<(usize, usize)> {
63    match a.checked_add(b) {
64        Some(sum) => match checked_pow2(sum) {
65            Some(size) => Some((sum, size)),
66            None => None,
67        },
68        None => None,
69    }
70}
71
72/// Computes `log_2(n)`
73///
74/// # Panics
75/// Panics if `n` is not a power of two.
76#[must_use]
77#[inline]
78pub const fn log2_strict_usize(n: usize) -> usize {
79    let res = n.trailing_zeros();
80    assert!(n.wrapping_shr(res) == 1, "Not a power of two");
81    // Tell the optimizer about the semantics of `log2_strict`. i.e. it can replace `n` with
82    // `1 << res` and vice versa.
83    unsafe {
84        assert_unchecked(n == 1 << res);
85    }
86    res as usize
87}
88
89/// Precomputed table of all powers of 3 that fit in a `u64`.
90///
91/// The maximum power is `3^40 = 12_157_665_459_056_928_801`.
92///
93/// We use `u64` instead of `usize` so the table compiles safely on 32-bit targets,
94/// where `3^40` would overflow a 32-bit `usize`.
95const POWERS_OF_3: [u64; 41] = {
96    // Start with 3^0 = 1.
97    let mut table = [0u64; 41];
98    table[0] = 1;
99
100    // Fill iteratively: each entry is 3 times the previous one.
101    let mut i = 1;
102    while i < 41 {
103        table[i] = table[i - 1] * 3;
104        i += 1;
105    }
106    table
107};
108
109/// Maps a bit-position (i.e. `floor(log2(n))`) to the corresponding base-3 exponent.
110///
111/// Because `3^k` grows faster than `2^k`, every power of 3 has a unique highest set
112/// bit position. This lets us use `leading_zeros()` to jump straight to the answer
113/// in O(1) without any loop or binary search.
114///
115/// Entries that don't correspond to any power of 3 are unused (left as 0).
116const LOG2_TO_EXP: [u8; 64] = {
117    // Initialize every slot to 0.
118    let mut table = [0u8; 64];
119
120    // For each power of 3, record which log2 bucket it falls into.
121    let mut i = 0;
122    while i < 41 {
123        // Compute floor(log2(3^i)) via the highest set bit.
124        let log2 = (u64::BITS - 1 - POWERS_OF_3[i].leading_zeros()) as usize;
125
126        // Store the exponent i at the corresponding bit-position.
127        table[log2] = i as u8;
128        i += 1;
129    }
130    table
131};
132
133/// Computes the strict base-3 logarithm of `n`.
134///
135/// Returns `k` such that `3^k == n`. Panics if `n` is not a power of 3.
136///
137/// This is the base-3 analogue of [`log2_strict_usize`].
138///
139/// # Arguments
140///
141/// * `n` - A positive integer that must be a power of 3 (i.e., 1, 3, 9, 27, 81, ...).
142///
143/// # Returns
144///
145/// The exponent `k` where `3^k == n`.
146///
147/// # Panics
148///
149/// Panics if:
150/// - `n` is zero
151/// - `n` is not a power of 3
152#[must_use]
153#[inline]
154pub const fn log3_strict_usize(n: usize) -> usize {
155    // Zero has no logarithm - check explicitly for a clear error message.
156    assert!(n != 0, "log3_strict_usize: input must be non-zero");
157
158    // Instantly find the candidate exponent via the highest set bit.
159    //
160    // Because every power of 3 occupies a unique log2 bucket, this single
161    // lookup gives us the answer in O(1) with zero branches.
162    let log2 = (usize::BITS - 1 - n.leading_zeros()) as usize;
163    let res = LOG2_TO_EXP[log2] as usize;
164
165    // Verify the result: catches non-powers of 3 in a single O(1) check.
166    assert!(
167        POWERS_OF_3[res] as usize == n,
168        "log3_strict_usize: input is not a power of 3"
169    );
170
171    res
172}
173
174/// Returns `[0, ..., N - 1]`.
175#[must_use]
176pub const fn indices_arr<const N: usize>() -> [usize; N] {
177    let mut indices_arr = [0; N];
178    let mut i = 0;
179    while i < N {
180        indices_arr[i] = i;
181        i += 1;
182    }
183    indices_arr
184}
185
186/// Statically asserts that `T` implements [`Clone`].
187pub const fn assert_clone<T: Clone>() {}
188
189/// Statically asserts that `T` implements [`Send`].
190pub const fn assert_send<T: Send>() {}
191
192/// Statically asserts that `T` implements [`Sync`].
193pub const fn assert_sync<T: Sync>() {}
194
195#[inline]
196pub const fn reverse_bits(x: usize, n: usize) -> usize {
197    // Assert that n is a power of 2
198    debug_assert!(n.is_power_of_two());
199    reverse_bits_len(x, n.trailing_zeros() as usize)
200}
201
202#[inline]
203pub const fn reverse_bits_len(x: usize, bit_len: usize) -> usize {
204    // A `bit_len` wider than the word would underflow the shift below.
205    // That yields a wrong, non-panicking permutation in release, so reject it up front.
206    debug_assert!(bit_len <= usize::BITS as usize);
207    // NB: The only reason we need overflowing_shr() here as opposed
208    // to plain '>>' is to accommodate the case n == num_bits == 0,
209    // which would become `0 >> 64`. Rust thinks that any shift of 64
210    // bits causes overflow, even when the argument is zero.
211    x.reverse_bits()
212        .overflowing_shr(usize::BITS - bit_len as u32)
213        .0
214}
215
216// Lookup table of 6-bit reverses.
217// NB: 2^6=64 bytes is a cache line. A smaller table wastes cache space.
218#[cfg(not(all(target_arch = "aarch64", target_feature = "neon")))]
219#[rustfmt::skip]
220const BIT_REVERSE_6BIT: &[u8] = &[
221    0o00, 0o40, 0o20, 0o60, 0o10, 0o50, 0o30, 0o70,
222    0o04, 0o44, 0o24, 0o64, 0o14, 0o54, 0o34, 0o74,
223    0o02, 0o42, 0o22, 0o62, 0o12, 0o52, 0o32, 0o72,
224    0o06, 0o46, 0o26, 0o66, 0o16, 0o56, 0o36, 0o76,
225    0o01, 0o41, 0o21, 0o61, 0o11, 0o51, 0o31, 0o71,
226    0o05, 0o45, 0o25, 0o65, 0o15, 0o55, 0o35, 0o75,
227    0o03, 0o43, 0o23, 0o63, 0o13, 0o53, 0o33, 0o73,
228    0o07, 0o47, 0o27, 0o67, 0o17, 0o57, 0o37, 0o77,
229];
230
231const BIG_T_SIZE: usize = 1 << 14;
232const SMALL_ARR_SIZE: usize = 1 << 16;
233const _: () = assert!(SMALL_ARR_SIZE >= 4 * BIG_T_SIZE);
234
235/// Permutes `arr` such that each index is mapped to its reverse in binary.
236///
237/// If the whole array fits in fast cache, then the trivial algorithm is cache friendly. Also, if
238/// `T` is really big, then the trivial algorithm is cache-friendly, no matter the size of the array.
239pub fn reverse_slice_index_bits<F>(vals: &mut [F])
240where
241    F: Copy + Send + Sync,
242{
243    let n = vals.len();
244    if n == 0 {
245        return;
246    }
247    let log_n = log2_strict_usize(n);
248
249    // If the whole array fits in fast cache, then the trivial algorithm is cache friendly. Also, if
250    // `T` is really big, then the trivial algorithm is cache-friendly, no matter the size of the array.
251    if core::mem::size_of::<F>() << log_n <= SMALL_ARR_SIZE
252        || core::mem::size_of::<F>() >= BIG_T_SIZE
253    {
254        reverse_slice_index_bits_small(vals, log_n);
255    } else {
256        debug_assert!(n >= 4); // By our choice of `BIG_T_SIZE` and `SMALL_ARR_SIZE`.
257
258        // Algorithm:
259        //
260        // Treat `arr` as a `sqrt(n)` by `sqrt(n)` row-major matrix. (Assume for now that `lb_n` is
261        // even, i.e., `n` is a square number.) To perform bit-order reversal we:
262        //  1. Bit-reverse the order of the rows. (They are contiguous in memory, so this is
263        //     basically a series of large `memcpy`s.)
264        //  2. Transpose the matrix.
265        //  3. Bit-reverse the order of the rows.
266        //
267        // This is equivalent to, for every index `0 <= i < n`:
268        //  1. bit-reversing `i[lb_n / 2..lb_n]`,
269        //  2. swapping `i[0..lb_n / 2]` and `i[lb_n / 2..lb_n]`,
270        //  3. bit-reversing `i[lb_n / 2..lb_n]`.
271        //
272        // If `lb_n` is odd, i.e., `n` is not a square number, then the above procedure requires
273        // slight modification. At steps 1 and 3 we bit-reverse bits `ceil(lb_n / 2)..lb_n`, of the
274        // index (shuffling `floor(lb_n / 2)` chunks of length `ceil(lb_n / 2)`). At step 2, we
275        // perform _two_ transposes. We treat `arr` as two matrices, one where the middle bit of the
276        // index is `0` and another, where the middle bit is `1`; we transpose each individually.
277
278        let lb_num_chunks = log_n >> 1;
279        let lb_chunk_size = log_n - lb_num_chunks;
280        unsafe {
281            reverse_slice_index_bits_chunks(vals, lb_num_chunks, lb_chunk_size);
282            transpose_in_place_square(vals, lb_chunk_size, lb_num_chunks, 0);
283            if lb_num_chunks != lb_chunk_size {
284                // `arr` cannot be interpreted as a square matrix. We instead interpret it as a
285                // `1 << lb_num_chunks` by `2` by `1 << lb_num_chunks` tensor, in row-major order.
286                // The above transpose acted on `tensor[..., 0, ...]` (all indices with middle bit
287                // `0`). We still need to transpose `tensor[..., 1, ...]`. To do so, we advance
288                // arr by `1 << lb_num_chunks` effectively, adding that to every index.
289                let vals_with_offset = &mut vals[1 << lb_num_chunks..];
290                transpose_in_place_square(vals_with_offset, lb_chunk_size, lb_num_chunks, 0);
291            }
292            reverse_slice_index_bits_chunks(vals, lb_num_chunks, lb_chunk_size);
293        }
294    }
295}
296
297// Both functions below are semantically equivalent to:
298//     for i in 0..n {
299//         result.push(arr[reverse_bits(i, n_power)]);
300//     }
301// where reverse_bits(i, n_power) computes the n_power-bit reverse. The complications are there
302// to guide the compiler to generate optimal assembly.
303
304#[cfg(not(all(target_arch = "aarch64", target_feature = "neon")))]
305fn reverse_slice_index_bits_small<F>(vals: &mut [F], lb_n: usize) {
306    if lb_n <= 6 {
307        // BIT_REVERSE_6BIT holds 6-bit reverses. This shift makes them lb_n-bit reverses.
308        let dst_shr_amt = 6 - lb_n as u32;
309        for (src, &br) in BIT_REVERSE_6BIT.iter().enumerate().take(vals.len()) {
310            let dst = (br as usize).wrapping_shr(dst_shr_amt);
311            if src < dst {
312                vals.swap(src, dst);
313            }
314        }
315    } else {
316        // LLVM does not know that it does not need to reverse src at each iteration (which is
317        // expensive on x86). We take advantage of the fact that the low bits of dst change rarely and the high
318        // bits of dst are dependent only on the low bits of src.
319        let dst_lo_shr_amt = usize::BITS - (lb_n - 6) as u32;
320        let dst_hi_shl_amt = lb_n - 6;
321        for src_chunk in 0..(vals.len() >> 6) {
322            let src_hi = src_chunk << 6;
323            let dst_lo = src_chunk.reverse_bits().wrapping_shr(dst_lo_shr_amt);
324            for (src_lo, &br) in BIT_REVERSE_6BIT.iter().enumerate() {
325                let dst_hi = (br as usize) << dst_hi_shl_amt;
326                let src = src_hi + src_lo;
327                let dst = dst_hi + dst_lo;
328                if src < dst {
329                    vals.swap(src, dst);
330                }
331            }
332        }
333    }
334}
335
336#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
337const fn reverse_slice_index_bits_small<F>(vals: &mut [F], lb_n: usize) {
338    // Aarch64 can reverse bits in one instruction, so the trivial version works best.
339    let mut src = 0;
340    while src < vals.len() {
341        let dst = src.reverse_bits().wrapping_shr(usize::BITS - lb_n as u32);
342        if src < dst {
343            vals.swap(src, dst);
344        }
345
346        src += 1;
347    }
348}
349
350/// Split `arr` chunks and bit-reverse the order of the chunks. There are `1 << lb_num_chunks`
351/// chunks, each of length `1 << lb_chunk_size`.
352/// SAFETY: ensure that `arr.len() == 1 << lb_num_chunks + lb_chunk_size`.
353unsafe fn reverse_slice_index_bits_chunks<F>(
354    vals: &mut [F],
355    lb_num_chunks: usize,
356    lb_chunk_size: usize,
357) {
358    for i in 0..1usize << lb_num_chunks {
359        // `wrapping_shr` handles the silly case when `lb_num_chunks == 0`.
360        let j = i
361            .reverse_bits()
362            .wrapping_shr(usize::BITS - lb_num_chunks as u32);
363        if i < j {
364            unsafe {
365                core::ptr::swap_nonoverlapping(
366                    vals.get_unchecked_mut(i << lb_chunk_size),
367                    vals.get_unchecked_mut(j << lb_chunk_size),
368                    1 << lb_chunk_size,
369                );
370            }
371        }
372    }
373}
374
375/// Try to force Rust to emit a branch. Example:
376///
377/// ```no_run
378/// let x = 100;
379/// if x > 20 {
380///     println!("x is big!");
381///     p3_util::branch_hint();
382/// } else {
383///     println!("x is small!");
384/// }
385/// ```
386///
387/// This function has no semantics. It is a hint only.
388#[inline(always)]
389pub fn branch_hint() {
390    // NOTE: These are the currently supported assembly architectures. See the
391    // [nightly reference](https://doc.rust-lang.org/nightly/reference/inline-assembly.html) for
392    // the most up-to-date list.
393    #[cfg(any(
394        target_arch = "aarch64",
395        target_arch = "arm",
396        target_arch = "riscv32",
397        target_arch = "riscv64",
398        target_arch = "x86",
399        target_arch = "x86_64",
400    ))]
401    unsafe {
402        core::arch::asm!("", options(nomem, nostack, preserves_flags));
403    }
404}
405
406/// Return a String containing the name of T but with all the crate
407/// and module prefixes removed.
408pub fn pretty_name<T>() -> String {
409    let name = type_name::<T>();
410    let mut result = String::new();
411    for qual in name.split_inclusive(&['<', '>', ',']) {
412        result.push_str(qual.split("::").last().unwrap());
413    }
414    result
415}
416
417/// A C-style buffered input reader, similar to
418/// `core::iter::Iterator::next_chunk()` from nightly.
419///
420/// Returns an array of `MaybeUninit<T>` and the number of items in the
421/// array which have been correctly initialized.
422#[inline]
423fn iter_next_chunk_erased<const BUFLEN: usize, I: Iterator>(
424    iter: &mut I,
425) -> ([MaybeUninit<I::Item>; BUFLEN], usize)
426where
427    I::Item: Copy,
428{
429    let mut buf = [const { MaybeUninit::<I::Item>::uninit() }; BUFLEN];
430    let mut i = 0;
431
432    while i < BUFLEN {
433        if let Some(c) = iter.next() {
434            // Copy the next Item into `buf`.
435            unsafe {
436                buf.get_unchecked_mut(i).write(c);
437                i = i.unchecked_add(1);
438            }
439        } else {
440            // No more items in the iterator.
441            break;
442        }
443    }
444    (buf, i)
445}
446
447/// Split an iterator into small arrays and apply `func` to each.
448///
449/// Repeatedly read `BUFLEN` elements from `input` into an array and
450/// pass the array to `func` as a slice. If less than `BUFLEN`
451/// elements are remaining, that smaller slice is passed to `func` (if
452/// it is non-empty) and the function returns.
453#[inline]
454pub fn apply_to_chunks<const BUFLEN: usize, I, H>(input: I, mut func: H)
455where
456    I: IntoIterator<Item = u8>,
457    H: FnMut(&[u8]),
458{
459    let mut iter = input.into_iter();
460    loop {
461        let (buf, n) = iter_next_chunk_erased::<BUFLEN, _>(&mut iter);
462        if n == 0 {
463            break;
464        }
465        func(unsafe { buf.get_unchecked(..n).assume_init_ref() });
466    }
467}
468
469/// Pulls `N` items from `iter` and returns them as an array. If the iterator
470/// yields fewer than `N` items (but more than `0`), pads by the given default value.
471///
472/// Since the iterator is passed as a mutable reference and this function calls
473/// `next` at most `N` times, the iterator can still be used afterwards to
474/// retrieve the remaining items.
475///
476/// If `iter.next()` panics, all items already yielded by the iterator are
477/// dropped.
478#[inline]
479fn iter_next_chunk_padded<T: Copy, const N: usize>(
480    iter: &mut impl Iterator<Item = T>,
481    default: T, // Needed due to [T; M] not always implementing Default. Can probably be dropped if const generics stabilize.
482) -> Option<[T; N]> {
483    let (mut arr, n) = iter_next_chunk_erased::<N, _>(iter);
484    (n != 0).then(|| {
485        // Fill the rest of the array with default values.
486        arr[n..].fill(MaybeUninit::new(default));
487        unsafe { mem::transmute_copy::<_, [T; N]>(&arr) }
488    })
489}
490
491/// Returns an iterator over `N` elements of the iterator at a time.
492///
493/// The chunks do not overlap. If `N` does not divide the length of the
494/// iterator, then the last chunk is padded with up to `N-1` copies of the given default value.
495///
496/// This is essentially a copy pasted version of the nightly `array_chunks` function.
497/// <https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.array_chunks>
498/// Once that is stabilized this and the functions above it should be removed.
499#[inline]
500pub fn iter_array_chunks_padded<T: Copy, const N: usize>(
501    iter: impl IntoIterator<Item = T>,
502    default: T, // Needed due to [T; M] not always implementing Default. Can probably be dropped if const generics stabilize.
503) -> impl Iterator<Item = [T; N]> {
504    let mut iter = iter.into_iter();
505    iter::from_fn(move || iter_next_chunk_padded(&mut iter, default))
506}
507
508/// Reinterpret a slice of `BaseArray` elements as a slice of `Base` elements
509///
510/// This is useful to convert `&[F; N]` to `&[F]` or `&[A]` to `&[F]` where
511/// `A` has the same size, alignment and memory layout as `[F; N]` for some `N`.
512///
513/// # Safety
514///
515/// This is assumes that `BaseArray` has the same alignment and memory layout as `[Base; N]`.
516/// As Rust guarantees that arrays elements are contiguous in memory and the alignment of
517/// the array is the same as the alignment of its elements, this means that `BaseArray`
518/// must have the same alignment as `Base`.
519///
520/// # Panics
521///
522/// This panics if the size of `BaseArray` is not a multiple of the size of `Base`.
523#[inline]
524pub const unsafe fn as_base_slice<Base, BaseArray>(buf: &[BaseArray]) -> &[Base] {
525    const {
526        assert!(align_of::<Base>() == align_of::<BaseArray>());
527        assert!(size_of::<BaseArray>().is_multiple_of(size_of::<Base>()));
528    }
529
530    let d = size_of::<BaseArray>() / size_of::<Base>();
531
532    let buf_ptr = buf.as_ptr().cast::<Base>();
533    let n = buf.len() * d;
534    unsafe { slice::from_raw_parts(buf_ptr, n) }
535}
536
537/// Reinterpret a mutable slice of `BaseArray` elements as a slice of `Base` elements
538///
539/// This is useful to convert `&[F; N]` to `&[F]` or `&[A]` to `&[F]` where
540/// `A` has the same size, alignment and memory layout as `[F; N]` for some `N`.
541///
542/// # Safety
543///
544/// This is assumes that `BaseArray` has the same alignment and memory layout as `[Base; N]`.
545/// As Rust guarantees that arrays elements are contiguous in memory and the alignment of
546/// the array is the same as the alignment of its elements, this means that `BaseArray`
547/// must have the same alignment as `Base`.
548///
549/// # Panics
550///
551/// This panics if the size of `BaseArray` is not a multiple of the size of `Base`.
552#[inline]
553pub const unsafe fn as_base_slice_mut<Base, BaseArray>(buf: &mut [BaseArray]) -> &mut [Base] {
554    const {
555        assert!(align_of::<Base>() == align_of::<BaseArray>());
556        assert!(size_of::<BaseArray>().is_multiple_of(size_of::<Base>()));
557    }
558
559    let d = size_of::<BaseArray>() / size_of::<Base>();
560
561    let buf_ptr = buf.as_mut_ptr().cast::<Base>();
562    let n = buf.len() * d;
563    unsafe { slice::from_raw_parts_mut(buf_ptr, n) }
564}
565
566/// Convert a vector of `BaseArray` elements to a vector of `Base` elements without any
567/// reallocations.
568///
569/// This is useful to convert `Vec<[F; N]>` to `Vec<F>` or `Vec<A>` to `Vec<F>` where
570/// `A` has the same size, alignment and memory layout as `[F; N]` for some `N`. It can also,
571/// be used to safely convert `Vec<u32>` to `Vec<F>` if `F` is a `32` bit field
572/// or `Vec<u64>` to `Vec<F>` if `F` is a `64` bit field.
573///
574/// # Safety
575///
576/// This is assumes that `BaseArray` has the same alignment and memory layout as `[Base; N]`.
577/// As Rust guarantees that arrays elements are contiguous in memory and the alignment of
578/// the array is the same as the alignment of its elements, this means that `BaseArray`
579/// must have the same alignment as `Base`.
580///
581/// # Panics
582///
583/// This panics if the size of `BaseArray` is not a multiple of the size of `Base`.
584#[inline]
585pub unsafe fn flatten_to_base<Base, BaseArray>(vec: Vec<BaseArray>) -> Vec<Base> {
586    const {
587        assert!(align_of::<Base>() == align_of::<BaseArray>());
588        assert!(size_of::<BaseArray>().is_multiple_of(size_of::<Base>()));
589    }
590
591    let d = size_of::<BaseArray>() / size_of::<Base>();
592    // Prevent running `vec`'s destructor so we are in complete control
593    // of the allocation.
594    let mut values = ManuallyDrop::new(vec);
595
596    // Each `Self` is an array of `d` elements, so the length and capacity of
597    // the new vector will be multiplied by `d`.
598    let new_len = values.len() * d;
599    let new_cap = values.capacity() * d;
600
601    // Safe as BaseArray and Base have the same alignment.
602    let ptr = values.as_mut_ptr() as *mut Base;
603
604    unsafe {
605        // Safety:
606        // - BaseArray and Base have the same alignment.
607        // - As size_of::<BaseArray>() == size_of::<Base>() * d:
608        //      -- The capacity of the new vector is equal to the capacity of the old vector.
609        //      -- The first new_len elements of the new vector correspond to the first
610        //         len elements of the old vector and so are properly initialized.
611        Vec::from_raw_parts(ptr, new_len, new_cap)
612    }
613}
614
615/// Convert a vector of `Base` elements to a vector of `BaseArray` elements ideally without any
616/// reallocations.
617///
618/// This is an inverse of `flatten_to_base`. Unfortunately, unlike `flatten_to_base`, it may not be
619/// possible to avoid allocations. This issue is that there is not way to guarantee that the capacity
620/// of the vector is a multiple of `d`.
621///
622/// # Safety
623///
624/// This is assumes that `BaseArray` has the same alignment and memory layout as `[Base; N]`.
625/// As Rust guarantees that arrays elements are contiguous in memory and the alignment of
626/// the array is the same as the alignment of its elements, this means that `BaseArray`
627/// must have the same alignment as `Base`.
628///
629/// # Panics
630///
631/// This panics if the size of `BaseArray` is not a multiple of the size of `Base`.
632/// This panics if the length of the vector is not a multiple of the ratio of the sizes.
633#[inline]
634pub unsafe fn reconstitute_from_base<Base, BaseArray: Clone>(mut vec: Vec<Base>) -> Vec<BaseArray> {
635    const {
636        assert!(align_of::<Base>() == align_of::<BaseArray>());
637        assert!(size_of::<BaseArray>().is_multiple_of(size_of::<Base>()));
638    }
639
640    let d = size_of::<BaseArray>() / size_of::<Base>();
641
642    assert!(
643        vec.len().is_multiple_of(d),
644        "Vector length (got {}) must be a multiple of the extension field dimension ({}).",
645        vec.len(),
646        d
647    );
648
649    let new_len = vec.len() / d;
650
651    // We could call vec.shrink_to_fit() here to try and increase the probability that
652    // the capacity is a multiple of d. That might cause a reallocation though which
653    // would defeat the whole purpose.
654    let cap = vec.capacity();
655
656    // The assumption is that basically all callers of `reconstitute_from_base_vec` will be calling it
657    // with a vector constructed from `flatten_to_base` and so the capacity should be a multiple of `d`.
658    // But capacities can do strange things so we need to support both possibilities.
659    // Note that the `else` branch would also work if the capacity is a multiple of `d` but it is slower.
660    if cap.is_multiple_of(d) {
661        // Prevent running `vec`'s destructor so we are in complete control
662        // of the allocation.
663        let mut values = ManuallyDrop::new(vec);
664
665        // If we are on this branch then the capacity is a multiple of `d`.
666        let new_cap = cap / d;
667
668        // Safe as BaseArray and Base have the same alignment.
669        let ptr = values.as_mut_ptr() as *mut BaseArray;
670
671        unsafe {
672            // Safety:
673            // - BaseArray and Base have the same alignment.
674            // - As size_of::<Base>() == size_of::<BaseArray>() / d:
675            //      -- If we have reached this point, the length and capacity are both divisible by `d`.
676            //      -- The capacity of the new vector is equal to the capacity of the old vector.
677            //      -- The first new_len elements of the new vector correspond to the first
678            //         len elements of the old vector and so are properly initialized.
679            Vec::from_raw_parts(ptr, new_len, new_cap)
680        }
681    } else {
682        // If the capacity is not a multiple of `D`, we go via slices.
683
684        let buf_ptr = vec.as_mut_ptr().cast::<BaseArray>();
685        let slice = unsafe {
686            // Safety:
687            // - BaseArray and Base have the same alignment.
688            // - As size_of::<Base>() == size_of::<BaseArray>() / D:
689            //      -- If we have reached this point, the length is divisible by `D`.
690            //      -- The first new_len elements of the slice correspond to the first
691            //         len elements of the old slice and so are properly initialized.
692            slice::from_raw_parts(buf_ptr, new_len)
693        };
694
695        // Ideally the compiler could optimize this away to avoid the copy but it appears not to.
696        slice.to_vec()
697    }
698}
699
700#[inline(always)]
701pub const fn relatively_prime_u64(mut u: u64, mut v: u64) -> bool {
702    // Check that neither input is 0.
703    if u == 0 || v == 0 {
704        return false;
705    }
706
707    // Check divisibility by 2.
708    if (u | v) & 1 == 0 {
709        return false;
710    }
711
712    // Remove factors of 2 from `u` and `v`
713    u >>= u.trailing_zeros();
714    if u == 1 {
715        return true;
716    }
717
718    while v != 0 {
719        v >>= v.trailing_zeros();
720        if v == 1 {
721            return true;
722        }
723
724        // Ensure u <= v
725        if u > v {
726            core::mem::swap(&mut u, &mut v);
727        }
728
729        // This looks inefficient for v >> u but thanks to the fact that we remove
730        // trailing_zeros of v in every iteration, it ends up much more performative
731        // than first glance implies.
732        v -= u;
733    }
734    // If we made it through the loop, at no point is u or v equal to 1 and so the gcd
735    // must be greater than 1.
736    false
737}
738
739/// Inner loop of the deferred GCD algorithm.
740///
741/// See: <https://eprint.iacr.org/2020/972.pdf> for more information.
742///
743/// This is basically a mini GCD algorithm which builds up a transformation to apply to the larger
744/// numbers in the main loop. The key point is that this small loop only uses u64s, subtractions and
745/// bit shifts, which are very fast operations.
746///
747/// The bottom `NUM_ROUNDS` bits of `a` and `b` should match the bottom `NUM_ROUNDS` bits of
748/// the corresponding big-ints and the top `NUM_ROUNDS + 2` should match the top bits including
749/// zeroes if the original numbers have different sizes.
750#[inline]
751pub const fn gcd_inner<const NUM_ROUNDS: usize>(a: &mut u64, b: &mut u64) -> (i64, i64, i64, i64) {
752    // Initialise update factors.
753    // At the start of round 0: -1 < f0, g0, f1, g1 <= 1
754    let (mut f0, mut g0, mut f1, mut g1) = (1, 0, 0, 1);
755
756    // If at the start of a round: -2^i < f0, g0, f1, g1 <= 2^i
757    // Then, at the end of the round: -2^{i + 1} < f0, g0, f1, g1 <= 2^{i + 1}
758    // use manual `while` loop to enable `const`
759    let mut round = 0;
760    while round < NUM_ROUNDS {
761        if *a & 1 == 0 {
762            *a >>= 1;
763        } else {
764            if *a < *b {
765                core::mem::swap(a, b);
766                (f0, f1) = (f1, f0);
767                (g0, g1) = (g1, g0);
768            }
769            *a -= *b;
770            *a >>= 1;
771            f0 -= f1;
772            g0 -= g1;
773        }
774        f1 <<= 1;
775        g1 <<= 1;
776
777        round += 1;
778    }
779
780    // -2^NUM_ROUNDS < f0, g0, f1, g1 <= 2^NUM_ROUNDS
781    // Hence provided NUM_ROUNDS <= 62, we will not get any overflow.
782    // Additionally, if NUM_ROUNDS <= 63, then the only source of overflow will be
783    // if a variable is meant to equal 2^{63} in which case it will overflow to -2^{63}.
784    (f0, g0, f1, g1)
785}
786
787/// Inverts elements inside the prime field `F_P` with `P < 2^FIELD_BITS`.
788///
789/// Arguments:
790///  - a: The value we want to invert. It must be < P.
791///  - b: The value of the prime `P > 2`.
792///
793/// Output:
794/// - A `64-bit` signed integer `v` equal to `2^{2 * FIELD_BITS - 2} a^{-1} mod P` with
795///   size `|v| < 2^{2 * FIELD_BITS - 2}`.
796///
797/// It is up to the user to ensure that `b` is an odd prime with at most `FIELD_BITS` bits and
798/// `a < b`. If either of these assumptions break, the output is undefined.
799#[inline]
800pub const fn gcd_inversion_prime_field_32<const FIELD_BITS: u32>(mut a: u32, mut b: u32) -> i64 {
801    const {
802        assert!(FIELD_BITS <= 32);
803    }
804    debug_assert!(((1_u64 << FIELD_BITS) - 1) >= b as u64);
805
806    // Initialise u, v. Note that |u|, |v| <= 2^0
807    let (mut u, mut v) = (1_i64, 0_i64);
808
809    // Let a0 and P denote the initial values of a and b. Observe:
810    // `a = u * a0 mod P`
811    // `b = v * a0 mod P`
812    // `len(a) + len(b) <= 2 * len(P) <= 2 * FIELD_BITS`
813
814    // use manual `while` loop to enable `const`
815    let mut i = 0;
816    while i < 2 * FIELD_BITS - 2 {
817        // Assume at the start of the loop i:
818        // (1) `|u|, |v| <= 2^{i}`
819        // (2) `2^i * a = u * a0 mod P`
820        // (3) `2^i * b = v * a0 mod P`
821        // (4) `gcd(a, b) = 1`
822        // (5) `b` is odd.
823        // (6) `len(a) + len(b) <= max(n - i, 1)`
824
825        if a & 1 != 0 {
826            if a < b {
827                (a, b) = (b, a);
828                (u, v) = (v, u);
829            }
830            // As b < a, this subtraction cannot increase `len(a) + len(b)`
831            a -= b;
832            // Observe |u'| = |u - v| <= |u| + |v| <= 2^{i + 1}
833            u -= v;
834
835            // As (1) and (2) hold, we have
836            // `2^i a' = 2^i * (a - b) = (u - v) * a0 mod P = u' * a0 mod P`
837        }
838        // As b is odd, a must now be even.
839        // This reduces `len(a) + len(b)` by 1 (unless `a = 0` in which case `b = 1` and the sum of the lengths is always 1)
840        a >>= 1;
841
842        // Observe |v'| = 2|v| <= 2^{i + 1}
843        v <<= 1;
844
845        // Thus as the end of loop i:
846        // (1) `|u|, |v| <= 2^{i + 1}`
847        // (2) `2^{i + 1} * a = u * a0 mod P`  (As we have halved a)
848        // (3) `2^{i + 1} * b = v * a0 mod P`  (As we have doubled v)
849        // (4) `gcd(a, b) = 1`
850        // (5) `b` is odd.
851        // (6) `len(a) + len(b) <= max(n - i - 1, 1)`
852
853        i += 1;
854    }
855
856    // After the loops, we see that:
857    // |u|, |v| <= 2^{2 * FIELD_BITS - 2}: Hence for FIELD_BITS <= 32 we will not overflow an i64.
858    // `2^{2 * FIELD_BITS - 2} * b = v * a0 mod P`
859    // `len(a) + len(b) <= 2` with `gcd(a, b) = 1` and `b` odd.
860    // This implies that `b` must be `1` and so `v = 2^{2 * FIELD_BITS - 2} a0^{-1} mod P` as desired.
861    v
862}
863
864/// A raw mutable pointer wrapper that implements [`Send`] and [`Sync`].
865///
866/// Used to enable parallel writes to disjoint slices of a pre-allocated buffer
867/// from within closures that require `Send + Sync` (e.g. `rayon::ParallelIterator::for_each_init`).
868///
869/// # Safety
870///
871/// The caller must ensure that concurrent accesses through this pointer always
872/// target **non-overlapping** memory regions.
873#[derive(Clone, Copy)]
874pub struct DisjointMutPtr<T>(*mut T);
875
876// SAFETY: The contract of DisjointMutPtr guarantees that each thread writes to
877// a disjoint region, so sharing the pointer across threads is safe.
878unsafe impl<T> Send for DisjointMutPtr<T> {}
879unsafe impl<T> Sync for DisjointMutPtr<T> {}
880
881impl<T> DisjointMutPtr<T> {
882    /// Create a new `DisjointMutPtr` from a mutable slice.
883    #[inline]
884    pub const fn new(slice: &mut [T]) -> Self {
885        Self(slice.as_mut_ptr())
886    }
887
888    /// Get a mutable slice starting at `offset` with `len` elements.
889    ///
890    /// # Safety
891    ///
892    /// The caller must ensure the range `[offset, offset+len)` is within bounds
893    /// and does not overlap with any other concurrent access. The returned
894    /// slice must not outlive the buffer passed to [`Self::new`].
895    #[inline]
896    pub const unsafe fn slice_mut<'a>(self, offset: usize, len: usize) -> &'a mut [T] {
897        unsafe { core::slice::from_raw_parts_mut(self.0.add(offset), len) }
898    }
899}
900
901#[cfg(test)]
902mod tests {
903    use alloc::vec;
904    use alloc::vec::Vec;
905
906    use proptest::prelude::*;
907    use rand::rngs::SmallRng;
908    use rand::{RngExt, SeedableRng};
909
910    use super::*;
911
912    #[test]
913    fn test_reverse_bits_len() {
914        assert_eq!(reverse_bits_len(0b0000000000, 10), 0b0000000000);
915        assert_eq!(reverse_bits_len(0b0000000001, 10), 0b1000000000);
916        assert_eq!(reverse_bits_len(0b1000000000, 10), 0b0000000001);
917        assert_eq!(reverse_bits_len(0b00000, 5), 0b00000);
918        assert_eq!(reverse_bits_len(0b01011, 5), 0b11010);
919    }
920
921    #[test]
922    fn test_reverse_bits_len_full_width() {
923        // A full-width reversal is the largest valid bit length and must reverse every bit.
924        let bits = usize::BITS as usize;
925        assert_eq!(reverse_bits_len(1, bits), 1 << (bits - 1));
926        assert_eq!(reverse_bits_len(1 << (bits - 1), bits), 1);
927    }
928
929    #[test]
930    #[cfg(debug_assertions)]
931    #[should_panic(expected = "bit_len <= usize::BITS")]
932    fn test_reverse_bits_len_rejects_oversized_bit_len() {
933        // One bit past the word width: the shift would underflow into a wrong permutation.
934        // The expected message pins the guard, not the incidental subtraction-overflow panic.
935        let _ = reverse_bits_len(0, usize::BITS as usize + 1);
936    }
937
938    #[test]
939    fn test_reverse_index_bits() {
940        let mut arg = vec![10, 20, 30, 40];
941        reverse_slice_index_bits(&mut arg);
942        assert_eq!(arg, vec![10, 30, 20, 40]);
943
944        let mut input256: Vec<u64> = (0..256).collect();
945        #[rustfmt::skip]
946        let output256: Vec<u64> = vec![
947            0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
948            0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
949            0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
950            0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
951            0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
952            0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
953            0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
954            0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
955            0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
956            0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
957            0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
958            0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
959            0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
960            0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
961            0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
962            0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
963        ];
964        reverse_slice_index_bits(&mut input256[..]);
965        assert_eq!(input256, output256);
966    }
967
968    #[test]
969    fn test_apply_to_chunks_exact_fit() {
970        const CHUNK_SIZE: usize = 4;
971        let input: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8];
972        let mut results: Vec<Vec<u8>> = Vec::new();
973
974        apply_to_chunks::<CHUNK_SIZE, _, _>(input, |chunk| {
975            results.push(chunk.to_vec());
976        });
977
978        assert_eq!(results, vec![vec![1, 2, 3, 4], vec![5, 6, 7, 8]]);
979    }
980
981    #[test]
982    fn test_apply_to_chunks_with_remainder() {
983        const CHUNK_SIZE: usize = 3;
984        let input: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7];
985        let mut results: Vec<Vec<u8>> = Vec::new();
986
987        apply_to_chunks::<CHUNK_SIZE, _, _>(input, |chunk| {
988            results.push(chunk.to_vec());
989        });
990
991        assert_eq!(results, vec![vec![1, 2, 3], vec![4, 5, 6], vec![7]]);
992    }
993
994    #[test]
995    fn test_apply_to_chunks_empty_input() {
996        const CHUNK_SIZE: usize = 4;
997        let input: Vec<u8> = vec![];
998        let mut results: Vec<Vec<u8>> = Vec::new();
999
1000        apply_to_chunks::<CHUNK_SIZE, _, _>(input, |chunk| {
1001            results.push(chunk.to_vec());
1002        });
1003
1004        assert!(results.is_empty());
1005    }
1006
1007    #[test]
1008    fn test_apply_to_chunks_single_chunk() {
1009        const CHUNK_SIZE: usize = 10;
1010        let input: Vec<u8> = vec![1, 2, 3, 4, 5];
1011        let mut results: Vec<Vec<u8>> = Vec::new();
1012
1013        apply_to_chunks::<CHUNK_SIZE, _, _>(input, |chunk| {
1014            results.push(chunk.to_vec());
1015        });
1016
1017        assert_eq!(results, vec![vec![1, 2, 3, 4, 5]]);
1018    }
1019
1020    #[test]
1021    fn test_apply_to_chunks_large_chunk_size() {
1022        const CHUNK_SIZE: usize = 100;
1023        let input: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8];
1024        let mut results: Vec<Vec<u8>> = Vec::new();
1025
1026        apply_to_chunks::<CHUNK_SIZE, _, _>(input, |chunk| {
1027            results.push(chunk.to_vec());
1028        });
1029
1030        assert_eq!(results, vec![vec![1, 2, 3, 4, 5, 6, 7, 8]]);
1031    }
1032
1033    #[test]
1034    fn test_apply_to_chunks_large_input() {
1035        const CHUNK_SIZE: usize = 5;
1036        let input: Vec<u8> = (1..=20).collect();
1037        let mut results: Vec<Vec<u8>> = Vec::new();
1038
1039        apply_to_chunks::<CHUNK_SIZE, _, _>(input, |chunk| {
1040            results.push(chunk.to_vec());
1041        });
1042
1043        assert_eq!(
1044            results,
1045            vec![
1046                vec![1, 2, 3, 4, 5],
1047                vec![6, 7, 8, 9, 10],
1048                vec![11, 12, 13, 14, 15],
1049                vec![16, 17, 18, 19, 20]
1050            ]
1051        );
1052    }
1053
1054    #[test]
1055    fn test_reverse_slice_index_bits_random() {
1056        let lengths = [32, 128, 1 << 16];
1057        let mut rng = SmallRng::seed_from_u64(1);
1058        for _ in 0..32 {
1059            for &length in &lengths {
1060                let mut rand_list: Vec<u32> = Vec::with_capacity(length);
1061                rand_list.resize_with(length, || rng.random());
1062                let expect = reverse_index_bits_naive(&rand_list);
1063
1064                let mut actual = rand_list.clone();
1065                reverse_slice_index_bits(&mut actual);
1066
1067                assert_eq!(actual, expect);
1068            }
1069        }
1070    }
1071
1072    #[test]
1073    fn test_log2_strict_usize_edge_cases() {
1074        assert_eq!(log2_strict_usize(1), 0);
1075        assert_eq!(log2_strict_usize(2), 1);
1076        assert_eq!(log2_strict_usize(1 << 18), 18);
1077        assert_eq!(log2_strict_usize(1 << 31), 31);
1078        assert_eq!(
1079            log2_strict_usize(1 << (usize::BITS - 1)),
1080            usize::BITS as usize - 1
1081        );
1082    }
1083
1084    #[test]
1085    fn test_checked_pow2() {
1086        // 2^0 = 1, the smallest valid exponent.
1087        assert_eq!(checked_pow2(0), Some(1));
1088
1089        // 2^1 = 2.
1090        assert_eq!(checked_pow2(1), Some(2));
1091
1092        // 2^5 = 32, a typical small power.
1093        assert_eq!(checked_pow2(5), Some(32));
1094
1095        // 2^10 = 1024, commonly used as a domain size in FRI.
1096        assert_eq!(checked_pow2(10), Some(1024));
1097
1098        // 2^20 = 1_048_576, a realistic large trace length.
1099        assert_eq!(checked_pow2(20), Some(1_048_576));
1100
1101        // Largest representable power: 2^(BITS - 1).
1102        // On a 64-bit platform this is 2^63 = 0x8000_0000_0000_0000.
1103        let max_exp = usize::BITS as usize - 1;
1104        assert_eq!(checked_pow2(max_exp), Some(1usize << max_exp));
1105
1106        // Exponent equal to the bit width would shift 1 out of range.
1107        //
1108        //     1_usize << 64  (on 64-bit)  →  overflow
1109        //
1110        // Must return `None`.
1111        assert_eq!(checked_pow2(usize::BITS as usize), None);
1112
1113        // One past the maximum: also out of range.
1114        assert_eq!(checked_pow2(usize::BITS as usize + 1), None);
1115
1116        // Extreme exponent: usize::MAX is astronomically beyond
1117        // representable range — must return `None`.
1118        assert_eq!(checked_pow2(usize::MAX), None);
1119    }
1120
1121    #[test]
1122    fn test_checked_log_size_sum() {
1123        // Both zero: 0 + 0 = 0, 2^0 = 1.
1124        assert_eq!(checked_log_size_sum(0, 0), Some((0, 1)));
1125
1126        // Identity cases: adding zero to either side is a no-op.
1127        assert_eq!(checked_log_size_sum(5, 0), Some((5, 32)));
1128        assert_eq!(checked_log_size_sum(0, 10), Some((10, 1024)));
1129
1130        // Typical FRI scenario: degree_bits=10, log_quotient_chunks=2.
1131        //
1132        //     10 + 2 = 12,  2^12 = 4096
1133        assert_eq!(checked_log_size_sum(10, 2), Some((12, 4096)));
1134
1135        // Commutativity: order of operands must not matter.
1136        assert_eq!(checked_log_size_sum(2, 10), Some((12, 4096)));
1137
1138        // Large realistic case: degree_bits=20, log_chunks=3.
1139        //
1140        //     20 + 3 = 23,  2^23 = 8_388_608
1141        assert_eq!(checked_log_size_sum(20, 3), Some((23, 8_388_608)));
1142
1143        // Largest representable sum: (BITS - 2) + 1 = BITS - 1.
1144        let almost_max = usize::BITS as usize - 2;
1145        let max_exp = usize::BITS as usize - 1;
1146        assert_eq!(
1147            checked_log_size_sum(almost_max, 1),
1148            Some((max_exp, 1usize << max_exp))
1149        );
1150
1151        // Sum exactly at the bit width: overflows the shift.
1152        //
1153        //     (BITS - 1) + 1 = BITS  →  2^BITS is unrepresentable  →  None
1154        assert_eq!(checked_log_size_sum(max_exp, 1), None);
1155
1156        // Both operands large but sum still within range.
1157        //
1158        //     32 + 31 = 63  (on 64-bit)  →  2^63 is representable
1159        let half = usize::BITS as usize / 2;
1160        let other_half = max_exp - half;
1161        assert_eq!(
1162            checked_log_size_sum(half, other_half),
1163            Some((max_exp, 1usize << max_exp))
1164        );
1165
1166        // Addition itself overflows usize, not just the shift.
1167        //
1168        //     usize::MAX + 1  →  checked_add returns None  →  None
1169        assert_eq!(checked_log_size_sum(usize::MAX, 1), None);
1170
1171        // Both operands at usize::MAX: addition doubly overflows.
1172        assert_eq!(checked_log_size_sum(usize::MAX, usize::MAX), None);
1173    }
1174
1175    #[test]
1176    #[should_panic]
1177    fn test_log2_strict_usize_zero() {
1178        let _ = log2_strict_usize(0);
1179    }
1180
1181    #[test]
1182    #[should_panic]
1183    fn test_log2_strict_usize_nonpower_2() {
1184        let _ = log2_strict_usize(0x78c341c65ae6d262);
1185    }
1186
1187    #[test]
1188    #[should_panic]
1189    fn test_log2_strict_usize_max() {
1190        let _ = log2_strict_usize(usize::MAX);
1191    }
1192
1193    #[test]
1194    fn test_log3_strict_powers_of_3() {
1195        // Test all powers of 3 up to 3^12 = 531441.
1196        assert_eq!(log3_strict_usize(1), 0);
1197        assert_eq!(log3_strict_usize(3), 1);
1198        assert_eq!(log3_strict_usize(9), 2);
1199        assert_eq!(log3_strict_usize(27), 3);
1200        assert_eq!(log3_strict_usize(81), 4);
1201        assert_eq!(log3_strict_usize(243), 5);
1202        assert_eq!(log3_strict_usize(729), 6);
1203        assert_eq!(log3_strict_usize(2187), 7);
1204        assert_eq!(log3_strict_usize(6561), 8);
1205        assert_eq!(log3_strict_usize(19683), 9);
1206        assert_eq!(log3_strict_usize(59049), 10);
1207        assert_eq!(log3_strict_usize(177_147), 11);
1208        assert_eq!(log3_strict_usize(531_441), 12);
1209    }
1210
1211    #[test]
1212    #[should_panic(expected = "input must be non-zero")]
1213    fn test_log3_strict_panics_on_zero() {
1214        let _ = log3_strict_usize(0);
1215    }
1216
1217    #[test]
1218    #[should_panic(expected = "is not a power of 3")]
1219    fn test_log3_strict_panics_on_non_power_of_3() {
1220        // 2 is not a power of 3.
1221        let _ = log3_strict_usize(2);
1222    }
1223
1224    #[test]
1225    #[should_panic(expected = "is not a power of 3")]
1226    fn test_log3_strict_panics_on_power_of_2() {
1227        // 8 = 2^3 is not a power of 3.
1228        let _ = log3_strict_usize(8);
1229    }
1230
1231    #[test]
1232    #[should_panic(expected = "is not a power of 3")]
1233    fn test_log3_strict_panics_on_product_with_other_primes() {
1234        // 6 = 2 * 3 is not a power of 3.
1235        let _ = log3_strict_usize(6);
1236    }
1237
1238    proptest! {
1239        #[test]
1240        fn test_log3_strict_roundtrip(k in 0u32..25u32) {
1241            // Roundtrip: 3^k -> log3_strict_usize -> k
1242            let n = 3usize.pow(k);
1243            assert_eq!(log3_strict_usize(n), k as usize);
1244        }
1245    }
1246
1247    #[test]
1248    fn test_log2_ceil_usize_comprehensive() {
1249        // Powers of 2
1250        assert_eq!(log2_ceil_usize(0), 0);
1251        assert_eq!(log2_ceil_usize(1), 0);
1252        assert_eq!(log2_ceil_usize(2), 1);
1253        assert_eq!(log2_ceil_usize(1 << 18), 18);
1254        assert_eq!(log2_ceil_usize(1 << 31), 31);
1255        assert_eq!(
1256            log2_ceil_usize(1 << (usize::BITS - 1)),
1257            usize::BITS as usize - 1
1258        );
1259
1260        // Nonpowers; want to round up
1261        assert_eq!(log2_ceil_usize(3), 2);
1262        assert_eq!(log2_ceil_usize(0x14fe901b), 29);
1263        assert_eq!(
1264            log2_ceil_usize((1 << (usize::BITS - 1)) + 1),
1265            usize::BITS as usize
1266        );
1267        assert_eq!(log2_ceil_usize(usize::MAX - 1), usize::BITS as usize);
1268        assert_eq!(log2_ceil_usize(usize::MAX), usize::BITS as usize);
1269    }
1270
1271    fn reverse_index_bits_naive<T: Copy>(arr: &[T]) -> Vec<T> {
1272        let n = arr.len();
1273        let n_power = log2_strict_usize(n);
1274
1275        let mut out = vec![None; n];
1276        for (i, v) in arr.iter().enumerate() {
1277            let dst = i.reverse_bits() >> (usize::BITS - n_power as u32);
1278            out[dst] = Some(*v);
1279        }
1280
1281        out.into_iter().map(|x| x.unwrap()).collect()
1282    }
1283
1284    #[test]
1285    fn test_relatively_prime_u64() {
1286        // Zero cases (should always return false)
1287        assert!(!relatively_prime_u64(0, 0));
1288        assert!(!relatively_prime_u64(10, 0));
1289        assert!(!relatively_prime_u64(0, 10));
1290        assert!(!relatively_prime_u64(0, 123456789));
1291
1292        // Number with itself (if greater than 1, not relatively prime)
1293        assert!(relatively_prime_u64(1, 1));
1294        assert!(!relatively_prime_u64(10, 10));
1295        assert!(!relatively_prime_u64(99999, 99999));
1296
1297        // Powers of 2 (always false since they share factor 2)
1298        assert!(!relatively_prime_u64(2, 4));
1299        assert!(!relatively_prime_u64(16, 32));
1300        assert!(!relatively_prime_u64(64, 128));
1301        assert!(!relatively_prime_u64(1024, 4096));
1302        assert!(!relatively_prime_u64(u64::MAX, u64::MAX));
1303
1304        // One number is a multiple of the other (always false)
1305        assert!(!relatively_prime_u64(5, 10));
1306        assert!(!relatively_prime_u64(12, 36));
1307        assert!(!relatively_prime_u64(15, 45));
1308        assert!(!relatively_prime_u64(100, 500));
1309
1310        // Co-prime numbers (should be true)
1311        assert!(relatively_prime_u64(17, 31));
1312        assert!(relatively_prime_u64(97, 43));
1313        assert!(relatively_prime_u64(7919, 65537));
1314        assert!(relatively_prime_u64(15485863, 32452843));
1315
1316        // Small prime numbers (should be true)
1317        assert!(relatively_prime_u64(13, 17));
1318        assert!(relatively_prime_u64(101, 103));
1319        assert!(relatively_prime_u64(1009, 1013));
1320
1321        // Large numbers (some cases where they are relatively prime or not)
1322        assert!(!relatively_prime_u64(
1323            190266297176832000,
1324            10430732356495263744
1325        ));
1326        assert!(!relatively_prime_u64(
1327            2040134905096275968,
1328            5701159354248194048
1329        ));
1330        assert!(!relatively_prime_u64(
1331            16611311494648745984,
1332            7514969329383038976
1333        ));
1334        assert!(!relatively_prime_u64(
1335            14863931409971066880,
1336            7911906750992527360
1337        ));
1338
1339        // Max values
1340        assert!(relatively_prime_u64(u64::MAX, 1));
1341        assert!(relatively_prime_u64(u64::MAX, u64::MAX - 1));
1342        assert!(!relatively_prime_u64(u64::MAX, u64::MAX));
1343    }
1344}