p3_interpolation/
lib.rs

1//! Tools for Lagrange interpolation.
2
3#![no_std]
4
5extern crate alloc;
6
7use alloc::vec::Vec;
8
9use p3_field::{
10    ExtensionField, TwoAdicField, batch_multiplicative_inverse, scale_slice_in_place_single_core,
11};
12use p3_matrix::Matrix;
13use p3_maybe_rayon::prelude::*;
14use p3_util::log2_strict_usize;
15
16/// Given evaluations of a batch of polynomials over the canonical power-of-two subgroup, evaluate
17/// the polynomials at `point`.
18///
19/// This assumes the point is not in the subgroup, otherwise the behavior is undefined.
20pub fn interpolate_subgroup<F, EF, Mat>(subgroup_evals: &Mat, point: EF) -> Vec<EF>
21where
22    F: TwoAdicField,
23    EF: ExtensionField<F>,
24    Mat: Matrix<F>,
25{
26    interpolate_coset(subgroup_evals, F::ONE, point)
27}
28
29/// Given evaluations of a batch of polynomials over the given coset of the canonical power-of-two
30/// subgroup, evaluate the polynomials at `point`.
31///
32/// This assumes the point is not in the coset, otherwise the behavior is undefined.
33///
34/// The `coset_evals` must be given in standard (not bit-reversed) order.
35pub fn interpolate_coset<F, EF, Mat>(coset_evals: &Mat, shift: F, point: EF) -> Vec<EF>
36where
37    F: TwoAdicField,
38    EF: ExtensionField<F>,
39    Mat: Matrix<F>,
40{
41    let height = coset_evals.height();
42    let log_height = log2_strict_usize(height);
43    let coset = F::two_adic_generator(log_height)
44        .shifted_powers(shift)
45        .take(height)
46        .collect::<Vec<_>>();
47
48    // Compute `1/(z - gh^i)` for each elements of the coset.
49    let diffs: Vec<_> = coset.par_iter().map(|&g| point - g).collect();
50    let diff_invs = batch_multiplicative_inverse(&diffs);
51
52    interpolate_coset_with_precomputation(coset_evals, shift, point, &coset, &diff_invs)
53}
54
55/// Given evaluations of a batch of polynomials over the given coset of the
56/// canonical power-of-two subgroup, evaluate the polynomials at `point`.
57///
58/// This assumes the point is not in the coset, otherwise the behavior is undefined.
59///
60/// This function takes the precomputed `subgroup` points and `diff_invs` (the
61/// inverses of the differences between the evaluation point and each shifted
62/// subgroup element), and should be preferred over `interpolate_coset` when
63/// repeatedly called with the same subgroup and/or point.
64///
65/// Unlike `interpolate_coset`, the parameters `subgroup`, `coset_evals`, and
66/// `diff_invs` may use any indexing scheme, as long as they are all consistent.
67pub fn interpolate_coset_with_precomputation<F, EF, Mat>(
68    coset_evals: &Mat,
69    shift: F,
70    point: EF,
71    coset: &[F],
72    diff_invs: &[EF],
73) -> Vec<EF>
74where
75    F: TwoAdicField,
76    EF: ExtensionField<F>,
77    Mat: Matrix<F>,
78{
79    // Slight variation of this approach: https://hackmd.io/@vbuterin/barycentric_evaluation
80    debug_assert_eq!(coset.len(), diff_invs.len());
81    debug_assert_eq!(coset.len(), coset_evals.height());
82
83    // We start with the evaluations of a polynomial `f` over a coset `gH` of size `N` and want to compute `f(z)`.
84    // Observe that `z^N - g^N` is equal to `0` at all points in the coset.
85    // Thus `(z^N - g^N)/(z - gh^i)` is equal to `0` at all points except for `gh^i` where it is equal to:
86    //          `N * (gh^i)^{N - 1} = N * g^N * (gh^i)^{-1}.`
87    //
88    // Hence `L_i(z) = h^i * (z^N - g^N)/(N * g^{N - 1} * (z - gh^i))` will be equal to `1` at `gh^i` and `0`
89    // at all other points in the coset. This means that we can compute `f(z)` as:
90    //          `\sum_i L_i(z) f(gh^i) = (z^N - g^N)/(N * g^N) * \sum_i gh^i/(z - gh^i) f(gh^i).`
91
92    // TODO: It might be possible to speed this up by refactoring the code to instead compute:
93    //          `((z/g)^N - 1)/N * \sum_i 1/(z/(gh^i) - 1) f(gh^i).`
94    // This would remove the need for the multiplications and collections in `col_scale` and
95    // let us remove one of the `exp_power_of_2` calls (which are somewhat expensive as they are over the
96    // extension field). We could also remove the .inverse() in scale_vec.
97
98    let height = coset_evals.height();
99    let log_height = log2_strict_usize(height);
100
101    // Compute `gh^i/(z - gh^i)` for each i.
102    let col_scale: Vec<_> = coset
103        .par_iter()
104        .zip(diff_invs)
105        .map(|(&sg, &diff_inv)| diff_inv * sg)
106        .collect();
107
108    let point_pow_height = point.exp_power_of_2(log_height);
109    let shift_pow_height = shift.exp_power_of_2(log_height);
110
111    // Compute the vanishing polynomial of the coset: `Z_{sH}(z) = z^N - g^N`.
112    let vanishing_polynomial = point_pow_height - shift_pow_height;
113
114    // Compute N * g^N
115    let denominator = shift_pow_height.mul_2exp_u64(log_height as u64);
116
117    // Scaling factor s = Z_{sH}(z)/(N * g^N)
118    let scaling_factor = vanishing_polynomial * denominator.inverse();
119
120    // For each column polynomial `f_j`, compute `\sum_i h^i/(gh^i - z) * f_j(gh^i)`,
121    // then scale by s.
122    let mut evals = coset_evals.columnwise_dot_product(&col_scale);
123    scale_slice_in_place_single_core(&mut evals, scaling_factor);
124    evals
125}
126
127#[cfg(test)]
128mod tests {
129    use alloc::vec;
130    use alloc::vec::Vec;
131
132    use p3_baby_bear::BabyBear;
133    use p3_field::extension::BinomialExtensionField;
134    use p3_field::{Field, PrimeCharacteristicRing, TwoAdicField, batch_multiplicative_inverse};
135    use p3_matrix::dense::RowMajorMatrix;
136    use p3_util::log2_strict_usize;
137
138    use crate::{interpolate_coset, interpolate_coset_with_precomputation, interpolate_subgroup};
139
140    #[test]
141    fn test_interpolate_subgroup() {
142        // x^2 + 2 x + 3
143        type F = BabyBear;
144        let evals = [
145            6, 886605102, 1443543107, 708307799, 2, 556938009, 569722818, 1874680944,
146        ]
147        .map(F::from_u32);
148        let evals_mat = RowMajorMatrix::new(evals.to_vec(), 1);
149        let point = F::from_u16(100);
150        let result = interpolate_subgroup(&evals_mat, point);
151        assert_eq!(result, vec![F::from_u16(10203)]);
152    }
153
154    #[test]
155    fn test_interpolate_coset() {
156        // x^2 + 2 x + 3
157        type F = BabyBear;
158        let shift = F::GENERATOR;
159        let evals = [
160            1026, 129027310, 457985035, 994890337, 902, 1988942953, 1555278970, 913671254,
161        ]
162        .map(F::from_u32);
163        let evals_mat = RowMajorMatrix::new(evals.to_vec(), 1);
164        let point = F::from_u16(100);
165        let result = interpolate_coset(&evals_mat, shift, point);
166        assert_eq!(result, vec![F::from_u16(10203)]);
167
168        let n = evals.len();
169        let k = log2_strict_usize(n);
170
171        let coset: Vec<_> = F::two_adic_generator(k)
172            .shifted_powers(shift)
173            .take(n)
174            .collect();
175
176        let denom: Vec<_> = coset.iter().map(|&w| point - w).collect();
177
178        let denom = batch_multiplicative_inverse(&denom);
179        let result =
180            interpolate_coset_with_precomputation(&evals_mat, shift, point, &coset, &denom);
181        assert_eq!(result, vec![F::from_u16(10203)]);
182    }
183
184    #[test]
185    fn test_interpolate_coset_single_point_identity() {
186        type F = BabyBear;
187
188        // Test a trivial case: constant polynomial f(x) = c
189        // Regardless of x, f(x) = c, so interpolation must always return c
190        let c = F::from_u32(42); // constant polynomial
191        let evals = vec![c; 8];
192        let evals_mat = RowMajorMatrix::new(evals.clone(), 1);
193
194        let shift = F::GENERATOR;
195        let point = F::from_u16(1337);
196
197        let result = interpolate_coset(&evals_mat, shift, point);
198        assert_eq!(result, vec![c]); // must recover the constant
199    }
200
201    #[test]
202    fn test_interpolate_subgroup_degree_3_correctness() {
203        type F = BabyBear;
204        type EF4 = BinomialExtensionField<BabyBear, 4>;
205
206        // This test checks that interpolation works for a degree-3 polynomial
207        // when evaluated over 2^2 = 4 subgroup points, which is valid.
208        let poly = |x: EF4| x * x * x + x * x * F::TWO + x * F::from_u32(3) + F::from_u32(4);
209
210        let subgroup: Vec<_> = EF4::two_adic_generator(2).powers().take(4).collect();
211        let evals: Vec<_> = subgroup.iter().map(|&x| poly(x)).collect();
212
213        let evals_mat = RowMajorMatrix::new(evals, 1);
214        let point = EF4::from_u16(5);
215
216        let result = interpolate_subgroup(&evals_mat, point);
217        let expected = poly(point);
218
219        assert_eq!(result[0], expected);
220    }
221
222    #[test]
223    fn test_interpolate_coset_multiple_polynomials() {
224        type F = BabyBear;
225        type EF4 = BinomialExtensionField<BabyBear, 4>;
226
227        // We test interpolation of two polynomials evaluated over a coset.
228        // f1(x) = x^2 + 2x + 3
229        // f2(x) = 4x^2 + 5x + 6
230        //
231        // Each is evaluated at the coset and interpolated at the same external point.
232        let shift = EF4::GENERATOR;
233        let coset = EF4::two_adic_generator(3)
234            .shifted_powers(shift)
235            .take(8)
236            .collect::<Vec<_>>();
237
238        let f1 = |x: EF4| x * x + x * F::TWO + F::from_u32(3);
239        let f2 = |x: EF4| x * x * F::from_u32(4) + x * F::from_u32(5) + F::from_u32(6);
240
241        let evals: Vec<_> = coset.iter().flat_map(|&x| vec![f1(x), f2(x)]).collect();
242        let evals_mat = RowMajorMatrix::new(evals, 2);
243
244        let point = EF4::from_u32(77);
245        let result = interpolate_coset(&evals_mat, shift, point);
246
247        // Evaluate f1 and f2 at the same point directly
248        let expected_f1 = f1(point);
249        let expected_f2 = f2(point);
250
251        assert_eq!(result[0], expected_f1);
252        assert_eq!(result[1], expected_f2);
253    }
254
255    #[test]
256    fn test_interpolate_subgroup_multiple_columns() {
257        type F = BabyBear;
258        type EF4 = BinomialExtensionField<BabyBear, 4>;
259
260        // Define two polynomials f1(x) = x^2 + 2x + 3 and f2(x) = 4x^2 + 5x + 6
261        let f1 = |x: EF4| x * x + x * F::TWO + F::from_u32(3);
262        let f2 = |x: EF4| x * x * F::from_u32(4) + x * F::from_u32(5) + F::from_u32(6);
263
264        // Evaluation domain: 2^3 = 8-point subgroup
265        let subgroup_iter = EF4::two_adic_generator(3).powers().take(8);
266
267        // Evaluate both polynomials on the subgroup
268        let evals: Vec<_> = subgroup_iter.flat_map(|x| vec![f1(x), f2(x)]).collect();
269
270        // Organize into a 2-column matrix (column-major: 8 rows × 2 columns)
271        let evals_mat = RowMajorMatrix::new(evals, 2);
272
273        // Choose a point outside the subgroup to interpolate at
274        let point = EF4::from_u32(77);
275
276        // Perform interpolation
277        let result = interpolate_subgroup(&evals_mat, point);
278
279        // Expected results: f1(point), f2(point)
280        let expected_f1 = f1(point);
281        let expected_f2 = f2(point);
282
283        assert_eq!(result, vec![expected_f1, expected_f2]);
284    }
285}