Skip to main content

stwo_gpu/core/
utils.rs

1use core::iter::Peekable;
2
3use std_shims::Vec;
4
5use super::fields::Field;
6
7pub trait IteratorMutExt<'a, T: 'a>: Iterator<Item = &'a mut T> {
8    fn assign(self, other: impl IntoIterator<Item = T>)
9    where
10        Self: Sized,
11    {
12        self.zip(other).for_each(|(a, b)| *a = b);
13    }
14}
15
16impl<'a, T: 'a, I: Iterator<Item = &'a mut T>> IteratorMutExt<'a, T> for I {}
17
18/// An iterator that takes elements from the underlying [Peekable] while the predicate is true.
19/// Used to implement [PeekableExt::peek_take_while].
20pub struct PeekTakeWhile<'a, I: Iterator, P: FnMut(&I::Item) -> bool> {
21    iter: &'a mut Peekable<I>,
22    predicate: P,
23}
24impl<I: Iterator, P: FnMut(&I::Item) -> bool> Iterator for PeekTakeWhile<'_, I, P> {
25    type Item = I::Item;
26
27    fn next(&mut self) -> Option<Self::Item> {
28        self.iter.next_if(&mut self.predicate)
29    }
30}
31pub trait PeekableExt<'a, I: Iterator> {
32    /// Returns an iterator that takes elements from the underlying [Peekable] while the predicate
33    /// is true.
34    /// Unlike [Iterator::take_while], this iterator does not consume the first element that does
35    /// not satisfy the predicate.
36    fn peek_take_while<P: FnMut(&I::Item) -> bool>(
37        &'a mut self,
38        predicate: P,
39    ) -> PeekTakeWhile<'a, I, P>;
40}
41impl<'a, I: Iterator> PeekableExt<'a, I> for Peekable<I> {
42    fn peek_take_while<P: FnMut(&I::Item) -> bool>(
43        &'a mut self,
44        predicate: P,
45    ) -> PeekTakeWhile<'a, I, P> {
46        PeekTakeWhile {
47            iter: self,
48            predicate,
49        }
50    }
51}
52
53pub fn all_unique<T: Eq + core::hash::Hash>(iter: impl IntoIterator<Item = T>) -> bool {
54    let mut used = hashbrown::HashSet::new();
55    iter.into_iter().all(|elt| used.insert(elt))
56}
57
58/// Returns the bit reversed index of `i` which is represented by `log_size` bits.
59pub const fn bit_reverse_index(i: usize, log_size: u32) -> usize {
60    if log_size == 0 {
61        return i;
62    }
63    i.reverse_bits() >> (usize::BITS - log_size)
64}
65
66/// Performs a naive bit-reversal permutation inplace.
67///
68/// # Panics
69///
70/// Panics if the length of the slice is not a power of two.
71pub fn bit_reverse<T>(v: &mut [T]) {
72    let n = v.len();
73    assert!(n.is_power_of_two());
74    let log_n = n.ilog2();
75    for i in 0..n {
76        let j = bit_reverse_index(i, log_n);
77        if j > i {
78            v.swap(i, j);
79        }
80    }
81}
82
83/// Returns the index of the previous element in a bit reversed
84/// [crate::prover::poly::circle::CircleEvaluation] of log size `eval_log_size` relative to a
85/// smaller domain of size `domain_log_size`.
86pub const fn previous_bit_reversed_circle_domain_index(
87    i: usize,
88    domain_log_size: u32,
89    eval_log_size: u32,
90) -> usize {
91    offset_bit_reversed_circle_domain_index(i, domain_log_size, eval_log_size, -1)
92}
93
94/// Returns the index of the offset element in a bit reversed
95/// [crate::prover::poly::circle::CircleEvaluation] of log size `eval_log_size` relative to a
96/// smaller domain of size `domain_log_size`.
97pub const fn offset_bit_reversed_circle_domain_index(
98    i: usize,
99    domain_log_size: u32,
100    eval_log_size: u32,
101    offset: isize,
102) -> usize {
103    let mut prev_index = bit_reverse_index(i, eval_log_size);
104    let half_size = 1 << (eval_log_size - 1);
105    let step_size = offset * (1 << (eval_log_size - domain_log_size - 1)) as isize;
106    if prev_index < half_size {
107        prev_index = (prev_index as isize + step_size).rem_euclid(half_size as isize) as usize;
108    } else {
109        prev_index =
110            ((prev_index as isize - step_size).rem_euclid(half_size as isize) as usize) + half_size;
111    }
112    bit_reverse_index(prev_index, eval_log_size)
113}
114
115// TODO(AlonH): Pair both functions below with bit reverse. Consider removing both and calculating
116// the indices instead.
117#[cfg(feature = "prover")]
118pub(crate) fn circle_domain_order_to_coset_order(
119    values: &[crate::core::fields::m31::BaseField],
120) -> Vec<crate::core::fields::m31::BaseField> {
121    let n = values.len();
122    let mut coset_order = vec![];
123    for i in 0..(n / 2) {
124        coset_order.push(values[i]);
125        coset_order.push(values[n - 1 - i]);
126    }
127    coset_order
128}
129
130pub fn coset_order_to_circle_domain_order<F: Field>(values: &[F]) -> Vec<F> {
131    let mut circle_domain_order = Vec::with_capacity(values.len());
132    let n = values.len();
133    let half_len = n / 2;
134    for i in 0..half_len {
135        circle_domain_order.push(values[i << 1]);
136    }
137    for i in 0..half_len {
138        circle_domain_order.push(values[n - 1 - (i << 1)]);
139    }
140    circle_domain_order
141}
142
143/// Converts an index within a [`CircleDomain`] to the corresponding index in a [`Coset`].
144///
145/// [`CircleDomain`]: crate::core::poly::circle::CircleDomain
146/// [`Coset`]: crate::core::circle::Coset
147pub const fn circle_domain_index_to_coset_index(
148    circle_index: usize,
149    log_domain_size: u32,
150) -> usize {
151    let n = 1 << log_domain_size;
152    if circle_index < n / 2 {
153        circle_index * 2
154    } else {
155        (n - 1 - circle_index) * 2 + 1
156    }
157}
158
159/// Converts an index within a [`Coset`] to the corresponding index in a [`CircleDomain`].
160///
161/// [`CircleDomain`]: crate::core::poly::circle::CircleDomain
162/// [`Coset`]: crate::core::circle::Coset
163pub const fn coset_index_to_circle_domain_index(coset_index: usize, log_domain_size: u32) -> usize {
164    if coset_index.is_multiple_of(2) {
165        coset_index / 2
166    } else {
167        ((2 << log_domain_size) - coset_index) / 2
168    }
169}
170
171/// Performs a coset-natural-order to circle-domain-bit-reversed-order permutation in-place.
172///
173/// # Panics
174///
175/// Panics if the length of the slice is not a power of two.
176pub fn bit_reverse_coset_to_circle_domain_order<T>(v: &mut [T]) {
177    let n = v.len();
178    assert!(n.is_power_of_two());
179    let log_n = n.ilog2();
180    for i in 0..n {
181        let j = bit_reverse_index(coset_index_to_circle_domain_index(i, log_n), log_n);
182        if j > i {
183            v.swap(i, j);
184        }
185    }
186}
187
188/// # Safety
189///
190/// The caller must ensure that the vector is initialized before use.
191#[allow(clippy::uninit_vec)]
192pub unsafe fn uninit_vec<T>(len: usize) -> Vec<T> {
193    let mut vec = Vec::with_capacity(len);
194    vec.set_len(len);
195    vec
196}
197
198#[cfg(all(test, feature = "prover"))]
199mod tests {
200    use itertools::Itertools;
201
202    use super::{
203        offset_bit_reversed_circle_domain_index, previous_bit_reversed_circle_domain_index,
204    };
205    use crate::core::poly::circle::CanonicCoset;
206    use crate::core::utils::{
207        circle_domain_index_to_coset_index, coset_index_to_circle_domain_index,
208    };
209    use crate::m31;
210    use crate::prover::backend::cpu::CpuCircleEvaluation;
211    use crate::prover::poly::NaturalOrder;
212
213    #[test]
214    fn test_offset_bit_reversed_circle_domain_index() {
215        let domain_log_size = 3;
216        let eval_log_size = 6;
217        let initial_index = 5;
218
219        let actual = offset_bit_reversed_circle_domain_index(
220            initial_index,
221            domain_log_size,
222            eval_log_size,
223            -2,
224        );
225        let expected_prev = previous_bit_reversed_circle_domain_index(
226            initial_index,
227            domain_log_size,
228            eval_log_size,
229        );
230        let expected_prev2 = previous_bit_reversed_circle_domain_index(
231            expected_prev,
232            domain_log_size,
233            eval_log_size,
234        );
235        assert_eq!(actual, expected_prev2);
236    }
237
238    #[test]
239    fn test_previous_bit_reversed_circle_domain_index() {
240        let log_size = 4;
241        let n = 1 << log_size;
242        let domain = CanonicCoset::new(log_size).circle_domain();
243        let values = (0..n).map(|i| m31!(i as u32)).collect_vec();
244        let evaluation = CpuCircleEvaluation::<_, NaturalOrder>::new(domain, values);
245        let bit_reversed_evaluation = evaluation.clone().bit_reverse();
246
247        //            2   ·  14
248        //         ·      |       ·
249        //      13        |          1
250        //    ·           |            ·
251        //   3            |             15
252        //  ·             |              ·
253        // 12             |               0
254        // ·--------------|---------------·
255        // 4              |               8
256        //  ·             |              ·
257        //   11           |              7
258        //    ·           |            ·
259        //      5         |          9
260        //         ·      |       ·
261        //            10  ·   6
262        let neighbor_pairs = (0..n)
263            .map(|index| {
264                let prev_index =
265                    previous_bit_reversed_circle_domain_index(index, log_size - 3, log_size);
266                (
267                    bit_reversed_evaluation[index],
268                    bit_reversed_evaluation[prev_index],
269                )
270            })
271            .sorted()
272            .collect_vec();
273        let mut expected_neighbor_pairs = vec![
274            (m31!(0), m31!(4)),
275            (m31!(15), m31!(11)),
276            (m31!(1), m31!(5)),
277            (m31!(14), m31!(10)),
278            (m31!(2), m31!(6)),
279            (m31!(13), m31!(9)),
280            (m31!(3), m31!(7)),
281            (m31!(12), m31!(8)),
282            (m31!(4), m31!(0)),
283            (m31!(11), m31!(15)),
284            (m31!(5), m31!(1)),
285            (m31!(10), m31!(14)),
286            (m31!(6), m31!(2)),
287            (m31!(9), m31!(13)),
288            (m31!(7), m31!(3)),
289            (m31!(8), m31!(12)),
290        ];
291        expected_neighbor_pairs.sort();
292
293        assert_eq!(neighbor_pairs, expected_neighbor_pairs);
294    }
295
296    #[test]
297    fn test_circle_domain_and_coset_index_conversion() {
298        let log_size = 3;
299        let n = 1 << log_size;
300
301        // Test that both functions are inverses of each other
302        for i in 0..n {
303            let coset_idx = circle_domain_index_to_coset_index(i, log_size);
304            let circle_idx = coset_index_to_circle_domain_index(coset_idx, log_size);
305            assert_eq!(i, circle_idx);
306        }
307    }
308}