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