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<M: Matrix<MatElement = u64>, T> ShoupMatrixFMA<M> for ModularOpsU64<T>
234where
235 M::R: RowMut,
236{
237 fn shoup_matrix_fma(&self, out: &mut <M as Matrix>::R, a: &M, a_shoup: &M, b: &M) {
238 assert!(a.dimension() == a_shoup.dimension());
239 assert!(a.dimension() == b.dimension());
240
241 let q = self.q;
242 let q_twice = self.q << 1;
243
244 // first row (without summation)
245 izip!(
246 out.as_mut().iter_mut(),
247 a.get_row(0),
248 a_shoup.get_row(0),
249 b.get_row(0)
250 )
251 .for_each(|(o, a, a_shoup, b)| {
252 *o = ShoupMul::mul(*b, *a, *a_shoup, q);
253 });
254
255 izip!(
256 a.iter_rows().skip(1),
257 a_shoup.iter_rows().skip(1),
258 b.iter_rows().skip(1)
259 )
260 .for_each(|(a_row, a_shoup_row, b_row)| {
261 izip!(
262 out.as_mut().iter_mut(),
263 a_row.as_ref().iter(),
264 a_shoup_row.as_ref().iter(),
265 b_row.as_ref().iter()
266 )
267 .for_each(|(o, a0, a0_shoup, b0)| {
268 let quotient = ((*a0_shoup as u128 * *b0 as u128) >> 64) as u64;
269 let mut v = (a0.wrapping_mul(b0)).wrapping_add(*o);
270 v = v.wrapping_sub(q.wrapping_mul(quotient));
271
272 if v >= q_twice {
273 v -= q_twice;
274 }
275
276 *o = v;
277 });
278 });
279 }
280}
281
282impl<T> GetModulus for ModularOpsU64<T>
283where
284 T: Modulus,
285{
286 type Element = T::Element;
287 type M = T;
288 fn modulus(&self) -> &Self::M {
289 &self.modulus
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use itertools::Itertools;
297 use rand::{thread_rng, Rng};
298 use rand_distr::Uniform;
299
300 #[test]
301 fn fma() {
302 let mut rng = thread_rng();
303 let prime = 36028797017456641;
304 let ring_size = 1 << 3;
305
306 let dist = Uniform::new(0, prime);
307 let d = 2;
308 let a0_matrix = (0..d)
309 .into_iter()
310 .map(|_| (&mut rng).sample_iter(dist).take(ring_size).collect_vec())
311 .collect_vec();
312 // a0 in shoup representation
313 let a0_shoup_matrix = a0_matrix
314 .iter()
315 .map(|r| {
316 r.iter()
317 .map(|v| {
318 // $(v * 2^{\beta}) / p$
319 ((*v as u128 * (1u128 << 64)) / prime as u128) as u64
320 })
321 .collect_vec()
322 })
323 .collect_vec();
324 let a1_matrix = (0..d)
325 .into_iter()
326 .map(|_| (&mut rng).sample_iter(dist).take(ring_size).collect_vec())
327 .collect_vec();
328
329 let modop = ModularOpsU64::new(prime);
330
331 let mut out_shoup_fma_lazy = vec![0u64; ring_size];
332 modop.shoup_matrix_fma(
333 &mut out_shoup_fma_lazy,
334 &a0_matrix,
335 &a0_shoup_matrix,
336 &a1_matrix,
337 );
338 let out_shoup_fma = out_shoup_fma_lazy
339 .iter()
340 .map(|v| if *v >= prime { v - prime } else { *v })
341 .collect_vec();
342
343 // expected
344 let mut out_expected = vec![0u64; ring_size];
345 izip!(a0_matrix.iter(), a1_matrix.iter()).for_each(|(a_r, b_r)| {
346 izip!(out_expected.iter_mut(), a_r.iter(), b_r.iter()).for_each(|(o, a0, a1)| {
347 *o = (*o + ((*a0 as u128 * *a1 as u128) % prime as u128) as u64) % prime;
348 });
349 });
350
351 assert_eq!(out_expected, out_shoup_fma);
352 }
353}