arnaucube
arnaucube/phantom-zonePublic Self-hosted repository
phantom-zone/src/backend/modulus_u64.rs
src/backend/modulus_u64.rsRaw
  1use itertools::izip;
  2use num_traits::WrappingMul;
  3
  4use super::{
  5    ArithmeticLazyOps, ArithmeticOps, GetModulus, ModInit, Modulus, ShoupMatrixFMA, VectorOps,
  6};
  7use crate::RowMut;
  8
  9pub struct ModularOpsU64<T> {
 10    q: u64,
 11    q_twice: u64,
 12    logq: usize,
 13    barrett_mu: u128,
 14    barrett_alpha: usize,
 15    modulus: T,
 16}
 17
 18impl<T> ModInit for ModularOpsU64<T>
 19where
 20    T: Modulus<Element = u64>,
 21{
 22    type M = T;
 23    fn new(modulus: Self::M) -> ModularOpsU64<T> {
 24        assert!(!modulus.is_native());
 25
 26        // largest unsigned value modulus fits is modulus-1
 27        let q = modulus.largest_unsigned_value() + 1;
 28        let logq = 64 - (q + 1u64).leading_zeros();
 29
 30        // barrett calculation
 31        let mu = (1u128 << (logq * 2 + 3)) / (q as u128);
 32        let alpha = logq + 3;
 33
 34        ModularOpsU64 {
 35            q,
 36            q_twice: q << 1,
 37            logq: logq as usize,
 38            barrett_alpha: alpha as usize,
 39            barrett_mu: mu,
 40            modulus,
 41        }
 42    }
 43}
 44
 45impl<T> ModularOpsU64<T> {
 46    fn add_mod_fast(&self, a: u64, b: u64) -> u64 {
 47        debug_assert!(a < self.q);
 48        debug_assert!(b < self.q);
 49
 50        let mut o = a + b;
 51        if o >= self.q {
 52            o -= self.q;
 53        }
 54        o
 55    }
 56
 57    fn add_mod_fast_lazy(&self, a: u64, b: u64) -> u64 {
 58        debug_assert!(a < self.q_twice);
 59        debug_assert!(b < self.q_twice);
 60
 61        let mut o = a + b;
 62        if o >= self.q_twice {
 63            o -= self.q_twice;
 64        }
 65        o
 66    }
 67
 68    fn sub_mod_fast(&self, a: u64, b: u64) -> u64 {
 69        debug_assert!(a < self.q);
 70        debug_assert!(b < self.q);
 71
 72        if a >= b {
 73            a - b
 74        } else {
 75            (self.q + a) - b
 76        }
 77    }
 78
 79    // returns (a * b)  % q
 80    ///
 81    /// - both a and b must be in range [0, 2q)
 82    /// - output is in range [0 , 2q)
 83    fn mul_mod_fast_lazy(&self, a: u64, b: u64) -> u64 {
 84        debug_assert!(a < 2 * self.q);
 85        debug_assert!(b < 2 * self.q);
 86
 87        let ab = a as u128 * b as u128;
 88
 89        // ab / (2^{n + \beta})
 90        // note: \beta is assumed to -2
 91        let tmp = ab >> (self.logq - 2);
 92
 93        // k = ((ab / (2^{n + \beta})) * \mu) / 2^{\alpha - (-2)}
 94        let k = (tmp * self.barrett_mu) >> (self.barrett_alpha + 2);
 95
 96        // ab - k*p
 97        let tmp = k * (self.q as u128);
 98
 99        (ab - tmp) as u64
100    }
101
102    /// returns (a * b)  % q
103    ///
104    /// - both a and b must be in range [0, 2q)
105    /// - output is in range [0 , q)
106    fn mul_mod_fast(&self, a: u64, b: u64) -> u64 {
107        debug_assert!(a < 2 * self.q);
108        debug_assert!(b < 2 * self.q);
109
110        let ab = a as u128 * b as u128;
111
112        // ab / (2^{n + \beta})
113        // note: \beta is assumed to -2
114        let tmp = ab >> (self.logq - 2);
115
116        // k = ((ab / (2^{n + \beta})) * \mu) / 2^{\alpha - (-2)}
117        let k = (tmp * self.barrett_mu) >> (self.barrett_alpha + 2);
118
119        // ab - k*p
120        let tmp = k * (self.q as u128);
121
122        let mut out = (ab - tmp) as u64;
123
124        if out >= self.q {
125            out -= self.q;
126        }
127
128        return out;
129    }
130}
131
132impl<T> ArithmeticOps for ModularOpsU64<T> {
133    type Element = u64;
134
135    fn add(&self, a: &Self::Element, b: &Self::Element) -> Self::Element {
136        self.add_mod_fast(*a, *b)
137    }
138
139    fn mul(&self, a: &Self::Element, b: &Self::Element) -> Self::Element {
140        self.mul_mod_fast(*a, *b)
141    }
142
143    fn sub(&self, a: &Self::Element, b: &Self::Element) -> Self::Element {
144        self.sub_mod_fast(*a, *b)
145    }
146
147    fn neg(&self, a: &Self::Element) -> Self::Element {
148        self.q - *a
149    }
150
151    // fn modulus(&self) -> Self::Element {
152    //     self.q
153    // }
154}
155
156impl<T> ArithmeticLazyOps for ModularOpsU64<T> {
157    type Element = u64;
158    fn add_lazy(&self, a: &Self::Element, b: &Self::Element) -> Self::Element {
159        self.add_mod_fast_lazy(*a, *b)
160    }
161    fn mul_lazy(&self, a: &Self::Element, b: &Self::Element) -> Self::Element {
162        self.mul_mod_fast_lazy(*a, *b)
163    }
164}
165
166impl<T> VectorOps for ModularOpsU64<T> {
167    type Element = u64;
168
169    fn elwise_add_mut(&self, a: &mut [Self::Element], b: &[Self::Element]) {
170        izip!(a.iter_mut(), b.iter()).for_each(|(ai, bi)| {
171            *ai = self.add_mod_fast(*ai, *bi);
172        });
173    }
174
175    fn elwise_sub_mut(&self, a: &mut [Self::Element], b: &[Self::Element]) {
176        izip!(a.iter_mut(), b.iter()).for_each(|(ai, bi)| {
177            *ai = self.sub_mod_fast(*ai, *bi);
178        });
179    }
180
181    fn elwise_mul_mut(&self, a: &mut [Self::Element], b: &[Self::Element]) {
182        izip!(a.iter_mut(), b.iter()).for_each(|(ai, bi)| {
183            *ai = self.mul_mod_fast(*ai, *bi);
184        });
185    }
186
187    fn elwise_neg_mut(&self, a: &mut [Self::Element]) {
188        a.iter_mut().for_each(|ai| *ai = self.q - *ai);
189    }
190
191    fn elwise_scalar_mul(&self, out: &mut [Self::Element], a: &[Self::Element], b: &Self::Element) {
192        izip!(out.iter_mut(), a.iter()).for_each(|(oi, ai)| {
193            *oi = self.mul_mod_fast(*ai, *b);
194        });
195    }
196
197    fn elwise_mul(&self, out: &mut [Self::Element], a: &[Self::Element], b: &[Self::Element]) {
198        izip!(out.iter_mut(), a.iter(), b.iter()).for_each(|(oi, ai, bi)| {
199            *oi = self.mul_mod_fast(*ai, *bi);
200        });
201    }
202
203    fn elwise_scalar_mul_mut(&self, a: &mut [Self::Element], b: &Self::Element) {
204        a.iter_mut().for_each(|ai| {
205            *ai = self.mul_mod_fast(*ai, *b);
206        });
207    }
208
209    fn elwise_fma_mut(&self, a: &mut [Self::Element], b: &[Self::Element], c: &[Self::Element]) {
210        izip!(a.iter_mut(), b.iter(), c.iter()).for_each(|(ai, bi, ci)| {
211            *ai = self.add_mod_fast(*ai, self.mul_mod_fast(*bi, *ci));
212        });
213    }
214
215    fn elwise_fma_scalar_mut(
216        &self,
217        a: &mut [Self::Element],
218        b: &[Self::Element],
219        c: &Self::Element,
220    ) {
221        izip!(a.iter_mut(), b.iter()).for_each(|(ai, bi)| {
222            *ai = self.add_mod_fast(*ai, self.mul_mod_fast(*bi, *c));
223        });
224    }
225
226    // fn modulus(&self) -> Self::Element {
227    //     self.q
228    // }
229}
230
231impl<R: RowMut<Element = u64>, T> ShoupMatrixFMA<R> for ModularOpsU64<T> {
232    fn shoup_matrix_fma(&self, out: &mut [R::Element], a: &[R], a_shoup: &[R], b: &[R]) {
233        assert!(a.len() == a_shoup.len());
234        assert!(a.len() == b.len());
235
236        let q = self.q;
237        let q_twice = self.q << 1;
238
239        izip!(a.iter(), a_shoup.iter(), b.iter()).for_each(|(a_row, a_shoup_row, b_row)| {
240            izip!(
241                out.as_mut().iter_mut(),
242                a_row.as_ref().iter(),
243                a_shoup_row.as_ref().iter(),
244                b_row.as_ref().iter()
245            )
246            .for_each(|(o, a0, a0_shoup, b0)| {
247                let quotient = ((*a0_shoup as u128 * *b0 as u128) >> 64) as u64;
248                let mut v = (a0.wrapping_mul(b0)).wrapping_add(*o);
249                v = v.wrapping_sub(q.wrapping_mul(quotient));
250
251                if v >= q_twice {
252                    v -= q_twice;
253                }
254
255                *o = v;
256            });
257        });
258    }
259}
260
261impl<T> GetModulus for ModularOpsU64<T>
262where
263    T: Modulus,
264{
265    type Element = T::Element;
266    type M = T;
267    fn modulus(&self) -> &Self::M {
268        &self.modulus
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275    use itertools::Itertools;
276    use rand::{thread_rng, Rng};
277    use rand_distr::Uniform;
278
279    #[test]
280    fn fma() {
281        let mut rng = thread_rng();
282        let prime = 36028797017456641;
283        let ring_size = 1 << 3;
284
285        let dist = Uniform::new(0, prime);
286        let d = 2;
287        let a0_matrix = (0..d)
288            .into_iter()
289            .map(|_| (&mut rng).sample_iter(dist).take(ring_size).collect_vec())
290            .collect_vec();
291        // a0 in shoup representation
292        let a0_shoup_matrix = a0_matrix
293            .iter()
294            .map(|r| {
295                r.iter()
296                    .map(|v| {
297                        // $(v * 2^{\beta}) / p$
298                        ((*v as u128 * (1u128 << 64)) / prime as u128) as u64
299                    })
300                    .collect_vec()
301            })
302            .collect_vec();
303        let a1_matrix = (0..d)
304            .into_iter()
305            .map(|_| (&mut rng).sample_iter(dist).take(ring_size).collect_vec())
306            .collect_vec();
307
308        let modop = ModularOpsU64::new(prime);
309
310        let mut out_shoup_fma_lazy = vec![0u64; ring_size];
311        modop.shoup_matrix_fma(
312            &mut out_shoup_fma_lazy,
313            &a0_matrix,
314            &a0_shoup_matrix,
315            &a1_matrix,
316        );
317        let out_shoup_fma = out_shoup_fma_lazy
318            .iter()
319            .map(|v| if *v >= prime { v - prime } else { *v })
320            .collect_vec();
321
322        // expected
323        let mut out_expected = vec![0u64; ring_size];
324        izip!(a0_matrix.iter(), a1_matrix.iter()).for_each(|(a_r, b_r)| {
325            izip!(out_expected.iter_mut(), a_r.iter(), b_r.iter()).for_each(|(o, a0, a1)| {
326                *o = (*o + ((*a0 as u128 * *a1 as u128) % prime as u128) as u64) % prime;
327            });
328        });
329
330        assert_eq!(out_expected, out_shoup_fma);
331    }
332}