Skip to main content

zinc_poly/univariate/
nat_evaluation.rs

1use crypto_primitives::{
2    FieldConfig, ProjectPrimitiveIntegersWithConfig, Semiring, SetConfig, SetElement,
3};
4
5use crate::EvaluationError;
6
7/// Polynomial evaluated on 0, 1, 2, ....
8#[derive(Clone, Debug, PartialEq)]
9pub struct NatEvaluatedPoly<E: SetElement> {
10    /// Evaluations on P(0), P(1), P(2), ...
11    pub evaluations: Vec<E>,
12}
13
14impl<E: SetElement> NatEvaluatedPoly<E> {
15    #[inline(always)]
16    pub const fn new(evaluations: Vec<E>) -> Self {
17        Self { evaluations }
18    }
19
20    /// Interpolate the *unique* univariate polynomial of degree *at most*
21    /// `evaluations.len()-1` passing through the y-values in `evaluations` at x
22    /// = 0,..., evaluations.len()-1
23    /// and evaluate this  polynomial at `point`. In other words, efficiently
24    /// compute  $\sum_{i=0}^{len\ evaluations - 1} evaluations\[i\] *
25    /// (\prod_{j!=i} (\text{point} - j)/(i-j))$.
26    // All the arithmetic ops in the function
27    // are made sure to not overflow.
28    #[allow(clippy::arithmetic_side_effects, clippy::cast_possible_wrap)]
29    pub fn evaluate_at_point<C>(&self, cfg: &C, point: &E) -> Result<E, EvaluationError>
30    where
31        C: FieldConfig + ProjectPrimitiveIntegersWithConfig + SetConfig<Element = E>,
32    {
33        let evaluations = &self.evaluations;
34        // TODO(Alex): Once we have benches, it's worth checking
35        //             if we're even winning anything
36        //             with specialized branches above.
37
38        let zero = cfg.zero();
39        let one = cfg.one();
40
41        let len = evaluations.len();
42
43        let mut evals = vec![];
44
45        let mut prod = point.clone();
46        evals.push(point.clone());
47
48        //`prod = \prod_{j} (x - j)`
49        // we return early if 0 <= x < len, i.e. if the desired value has been passed
50        let mut j = zero.clone();
51        for i in 1..len {
52            if *point == j {
53                return Ok(evaluations[i - 1].clone());
54            }
55            cfg.add_assign(&mut j, &one);
56
57            let tmp = cfg.sub(point, &j);
58            evals.push(tmp.clone());
59            cfg.mul_assign(&mut prod, &tmp);
60        }
61
62        if *point == j {
63            return Ok(evaluations[len - 1].clone());
64        }
65
66        let div = |num: &E, denom: &E| cfg.div(num, denom);
67
68        let mut res = zero;
69        // we want to compute \prod (j!=i) (i-j) for a given i
70        //
71        // we start from the last step, which is
72        //  denom[len-1] = (len-1) * (len-2) *... * 2 * 1
73        // the step before that is
74        //  denom[len-2] = (len-2) * (len-3) * ... * 2 * 1 * -1
75        // and the step before that is
76        //  denom[len-3] = (len-3) * (len-4) * ... * 2 * 1 * -1 * -2
77        //
78        // i.e., for any i, the one before this will be derived from
79        //  denom[i-1] = - denom[i] * (len-i) / i
80        //
81        // that is, we only need to store
82        // - the last denom for i = len-1, and
83        // - the ratio between the current step and the last step, which is the product
84        //   of -(len-i) / i from all previous steps and we store this product as a
85        //   fraction number to reduce field divisions.
86
87        // We know
88        //  - 2^61 < factorial(20) < 2^62
89        //  - 2^122 < factorial(33) < 2^123
90        // so we will be able to compute the ratio
91        //  - for len <= 20 with i64
92        //  - for len <= 33 with i128
93        //  - for len >  33 with field elements
94        if evaluations.len() <= 20 {
95            let last_denom: E = cfg.project(&factorial(len - 1, u64::from));
96
97            let mut ratio_numerator = 1i64;
98            let mut ratio_denominator = 1u64;
99
100            for i in (0..len).rev() {
101                let ratio_numerator_f = cfg.project(&ratio_numerator);
102                let ratio_denominator_f = cfg.project(&ratio_denominator);
103
104                let num = cfg.mul(&prod, &ratio_denominator_f);
105                let denom = cfg.mul(&cfg.mul(&last_denom, &ratio_numerator_f), &evals[i]);
106                let x = div(&num, &denom);
107
108                let term = cfg.mul(&evaluations[i], &x);
109                cfg.add_assign(&mut res, &term);
110
111                // compute ratio for the next step which is current_ratio * -(len-i)/i
112                if i != 0 {
113                    // Using intentionally, overflow isn't possible
114                    ratio_numerator *= -(len as i64 - i as i64);
115                    ratio_denominator *= i as u64;
116                }
117            }
118        } else if evaluations.len() <= 33 {
119            let last_denom: E = cfg.project(&factorial(len - 1, u128::from));
120            let mut ratio_numerator = 1i128;
121            let mut ratio_denominator = 1u128;
122
123            for i in (0..len).rev() {
124                let ratio_numerator_f = cfg.project(&ratio_numerator);
125                let ratio_denominator_f = cfg.project(&ratio_denominator);
126
127                let num = cfg.mul(&prod, &ratio_denominator_f);
128                let denom = cfg.mul(&cfg.mul(&last_denom, &ratio_numerator_f), &evals[i]);
129                let x = div(&num, &denom);
130
131                let term = cfg.mul(&evaluations[i], &x);
132                cfg.add_assign(&mut res, &term);
133
134                // compute ratio for the next step which is current_ratio * -(len-i)/i
135                if i != 0 {
136                    ratio_numerator *= -(len as i128 - i as i128);
137                    ratio_denominator *= i as u128;
138                }
139            }
140        } else {
141            // since we are using field operations, we can merge
142            // `last_denom` and `ratio_numerator` into a single field element.
143            let mut denom_up = cfg.product((1..=(len as u64 - 1)).map(|u: u64| cfg.project(&u)));
144            let mut denom_down = one;
145
146            for i in (0..len).rev() {
147                let num = cfg.mul(&prod, &denom_down);
148                let denom = cfg.mul(&denom_up, &evals[i]);
149                let x = div(&num, &denom);
150
151                let term = cfg.mul(&evaluations[i], &x);
152                cfg.add_assign(&mut res, &term);
153
154                // compute denom for the next step is -current_denom * (len-i)/i
155                if i != 0 {
156                    let denom_up_factor = cfg.project(&((len - i) as u64));
157                    denom_up = cfg.neg(&cfg.mul(&denom_up, &denom_up_factor));
158
159                    let denom_down_factor = cfg.project(&(i as u64));
160                    cfg.mul_assign(&mut denom_down, &denom_down_factor);
161                }
162            }
163        }
164
165        Ok(res)
166    }
167}
168
169/// Compute the factorial(a) = 1 * 2 * ... * a.
170#[allow(clippy::arithmetic_side_effects)]
171fn factorial<R, F>(a: usize, from_u64: F) -> R
172where
173    R: Semiring,
174    F: Fn(u64) -> R + Send + Sync,
175{
176    (1..=(a as u64))
177        .map(&from_u64)
178        .reduce(|mut acc, next| {
179            acc *= next;
180            acc
181        })
182        .unwrap_or(from_u64(1))
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188    use crypto_primitives::{
189        BaseFieldConfig, ProjectElementWithConfig, crypto_bigint_monty::MontyField,
190        crypto_bigint_uint::Uint,
191    };
192    use itertools::Itertools;
193
194    const LIMBS: usize = 4;
195
196    fn test_config() -> MontyField<LIMBS> {
197        let modulus =
198            Uint::from_be_hex("0000000000000000000000000000000000860995AE68FC80E1B1BD1E39D54B33");
199        MontyField::new(&modulus).expect("modulus should be a valid odd prime")
200    }
201
202    #[test]
203    fn evaluate_nat_evaluation() {
204        let cfg = test_config();
205        let field_elem = cfg.project(&100u64);
206
207        let poly = NatEvaluatedPoly::new((0..1024u64).map(|x| cfg.project(&x)).collect_vec());
208
209        let res = poly.evaluate_at_point(&cfg, &field_elem).unwrap();
210
211        assert_eq!(res, cfg.project(&100u64));
212    }
213}