Skip to main content

stwo_gpu/core/poly/
line.rs

1use core::cmp::Ordering;
2use core::fmt::Debug;
3use core::iter::Map;
4use core::ops::{Deref, DerefMut};
5
6use num_traits::Zero;
7use serde::{Deserialize, Serialize};
8use std_shims::Vec;
9
10use super::circle::CircleDomain;
11use crate::core::circle::{CirclePoint, Coset, CosetIterator};
12use crate::core::fields::m31::BaseField;
13use crate::core::fields::qm31::SecureField;
14use crate::core::poly::utils::fold;
15use crate::core::utils::bit_reverse;
16
17/// Domain comprising of the x-coordinates of points in a [Coset].
18///
19/// For use with univariate polynomials.
20#[derive(Copy, Clone, Debug)]
21pub struct LineDomain {
22    coset: Coset,
23}
24
25impl LineDomain {
26    /// Returns a domain comprising of the x-coordinates of points in a coset.
27    ///
28    /// # Panics
29    ///
30    /// Panics if the coset items don't have unique x-coordinates.
31    pub fn new(coset: Coset) -> Self {
32        match coset.size().cmp(&2) {
33            Ordering::Less => {}
34            Ordering::Equal => {
35                // If the coset with two points contains (0, y) then the coset is {(0, y), (0, -y)}.
36                assert!(!coset.initial.x.is_zero(), "coset x-coordinates not unique");
37            }
38            Ordering::Greater => {
39                // Let our coset be `E = c + <G>` with `|E| > 2` then:
40                // 1. if `ord(c) <= ord(G)` the coset contains two points at x=0
41                // 2. if `ord(c) = 2 * ord(G)` then `c` and `-c` are in our coset
42                assert!(
43                    coset.initial.log_order() >= coset.step.log_order() + 2,
44                    "coset x-coordinates not unique"
45                );
46            }
47        }
48        Self { coset }
49    }
50
51    /// Returns the `i`th domain element.
52    pub fn at(&self, i: usize) -> BaseField {
53        self.coset.at(i).x
54    }
55
56    /// Returns the size of the domain.
57    pub const fn size(&self) -> usize {
58        self.coset.size()
59    }
60
61    /// Returns the log size of the domain.
62    pub const fn log_size(&self) -> u32 {
63        self.coset.log_size()
64    }
65
66    /// Returns an iterator over elements in the domain.
67    pub fn iter(&self) -> LineDomainIterator {
68        self.coset.iter().map(|p| p.x)
69    }
70
71    /// Returns a new domain comprising of all points in current domain doubled.
72    pub fn double(&self) -> Self {
73        Self {
74            coset: self.coset.double(),
75        }
76    }
77
78    /// Returns the domain's underlying coset.
79    pub const fn coset(&self) -> Coset {
80        self.coset
81    }
82}
83
84impl IntoIterator for LineDomain {
85    type Item = BaseField;
86    type IntoIter = LineDomainIterator;
87
88    /// Returns an iterator over elements in the domain.
89    fn into_iter(self) -> LineDomainIterator {
90        self.iter()
91    }
92}
93
94impl From<CircleDomain> for LineDomain {
95    fn from(domain: CircleDomain) -> Self {
96        Self {
97            coset: domain.half_coset,
98        }
99    }
100}
101
102/// An iterator over the x-coordinates of points in a coset.
103type LineDomainIterator =
104    Map<CosetIterator<CirclePoint<BaseField>>, fn(CirclePoint<BaseField>) -> BaseField>;
105
106/// A univariate polynomial defined on a [LineDomain].
107#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
108pub struct LinePoly {
109    /// Coefficients of the polynomial in [`line_ifft`] algorithm's basis.
110    ///
111    /// The coefficients are stored in bit-reversed order.
112    #[allow(rustdoc::private_intra_doc_links)]
113    coeffs: Vec<SecureField>,
114    /// The number of coefficients stored as `log2(len(coeffs))`.
115    log_size: u32,
116}
117
118impl LinePoly {
119    /// Creates a new line polynomial from bit reversed coefficients.
120    ///
121    /// # Panics
122    ///
123    /// Panics if the number of coefficients is not a power of two.
124    pub fn new(coeffs: Vec<SecureField>) -> Self {
125        assert!(coeffs.len().is_power_of_two());
126        let log_size = coeffs.len().ilog2();
127        Self { coeffs, log_size }
128    }
129
130    /// Evaluates the polynomial at a single point.
131    pub fn eval_at_point(&self, mut x: SecureField) -> SecureField {
132        let mut doublings = Vec::new();
133        for _ in 0..self.log_size {
134            doublings.push(x);
135            x = CirclePoint::double_x(x);
136        }
137        fold(&self.coeffs, &doublings)
138    }
139
140    /// Returns the number of coefficients.
141    #[allow(clippy::len_without_is_empty)]
142    pub fn len(&self) -> usize {
143        // `.len().ilog2()` is a common operation. By returning the length like so the compiler
144        // optimizes `.len().ilog2()` to a load of `log_size` instead of a branch and a bit count.
145        debug_assert_eq!(self.coeffs.len(), 1 << self.log_size);
146        1 << self.log_size
147    }
148
149    /// Returns the polynomial's coefficients in their natural order.
150    pub fn into_ordered_coefficients(mut self) -> Vec<SecureField> {
151        bit_reverse(&mut self.coeffs);
152        self.coeffs
153    }
154
155    /// Creates a new line polynomial from coefficients in their natural order.
156    ///
157    /// # Panics
158    ///
159    /// Panics if the number of coefficients is not a power of two.
160    pub fn from_ordered_coefficients(mut coeffs: Vec<SecureField>) -> Self {
161        bit_reverse(&mut coeffs);
162        Self::new(coeffs)
163    }
164}
165
166impl Deref for LinePoly {
167    type Target = [SecureField];
168
169    fn deref(&self) -> &[SecureField] {
170        &self.coeffs
171    }
172}
173
174impl DerefMut for LinePoly {
175    fn deref_mut(&mut self) -> &mut [SecureField] {
176        &mut self.coeffs
177    }
178}
179#[cfg(test)]
180mod tests {
181
182    use super::LineDomain;
183    use crate::core::circle::{CirclePoint, Coset};
184    use crate::core::fields::m31::BaseField;
185
186    #[test]
187    #[should_panic]
188    fn bad_line_domain() {
189        // This coset doesn't have points with unique x-coordinates.
190        let coset = Coset::odds(2);
191
192        LineDomain::new(coset);
193    }
194
195    #[test]
196    fn line_domain_of_size_two_works() {
197        const LOG_SIZE: u32 = 1;
198        let coset = Coset::subgroup(LOG_SIZE);
199
200        LineDomain::new(coset);
201    }
202
203    #[test]
204    fn line_domain_of_size_one_works() {
205        const LOG_SIZE: u32 = 0;
206        let coset = Coset::subgroup(LOG_SIZE);
207
208        LineDomain::new(coset);
209    }
210
211    #[test]
212    fn line_domain_size_is_correct() {
213        const LOG_SIZE: u32 = 8;
214        let coset = Coset::half_odds(LOG_SIZE);
215        let domain = LineDomain::new(coset);
216
217        let size = domain.size();
218
219        assert_eq!(size, 1 << LOG_SIZE);
220    }
221
222    #[test]
223    fn line_domain_coset_returns_the_coset() {
224        let coset = Coset::half_odds(5);
225        let domain = LineDomain::new(coset);
226
227        assert_eq!(domain.coset(), coset);
228    }
229
230    #[test]
231    fn line_domain_double_works() {
232        const LOG_SIZE: u32 = 8;
233        let coset = Coset::half_odds(LOG_SIZE);
234        let domain = LineDomain::new(coset);
235
236        let doubled_domain = domain.double();
237
238        assert_eq!(doubled_domain.size(), 1 << (LOG_SIZE - 1));
239        assert_eq!(doubled_domain.at(0), CirclePoint::double_x(domain.at(0)));
240        assert_eq!(doubled_domain.at(1), CirclePoint::double_x(domain.at(1)));
241    }
242
243    #[test]
244    fn line_domain_iter_works() {
245        const LOG_SIZE: u32 = 8;
246        let coset = Coset::half_odds(LOG_SIZE);
247        let domain = LineDomain::new(coset);
248
249        let elements = domain.iter().collect::<std_shims::Vec<BaseField>>();
250
251        assert_eq!(elements.len(), domain.size());
252        for (i, element) in elements.into_iter().enumerate() {
253            assert_eq!(element, domain.at(i), "mismatch at {i}");
254        }
255    }
256}