poulpy/base2k/src/vec_znx.rs
base2k/src/vec_znx.rsRaw
1use crate::cast_mut;
2use crate::ffi::vec_znx;
3use crate::ffi::znx;
4use crate::ffi::znx::znx_zero_i64_ref;
5use crate::{alias_mut_slice_to_vec, alloc_aligned};
6use crate::{Infos, Module};
7use itertools::izip;
8use std::cmp::min;
9
10pub trait VecZnxApi {
11 /// Returns the minimum size of the [u8] array required to assign a
12 /// new backend array to a [VecZnx] through [VecZnx::from_bytes].
13 fn bytes_of(n: usize, limbs: usize) -> usize;
14 fn as_ptr(&self) -> *const i64;
15 fn as_mut_ptr(&mut self) -> *mut i64;
16 fn at(&self, i: usize) -> &[i64];
17 fn at_mut(&mut self, i: usize) -> &mut [i64];
18 fn at_ptr(&self, i: usize) -> *const i64;
19 fn at_mut_ptr(&mut self, i: usize) -> *mut i64;
20 fn zero(&mut self);
21 fn normalize(&mut self, log_base2k: usize, carry: &mut [u8]);
22}
23
24pub fn bytes_of_vec_znx(n: usize, limbs: usize) -> usize {
25 n * limbs * 8
26}
27
28pub struct VecZnxBorrow {
29 pub n: usize,
30 pub limbs: usize,
31 pub data: *mut i64,
32}
33
34impl VecZnxBorrow {
35 /// Returns a new struct implementing [VecZnxApi] with the provided data as backing array.
36 ///
37 /// The struct will take ownership of buf[..[VecZnx::bytes_of]]
38 ///
39 /// User must ensure that data is properly alligned and that
40 /// the size of data is at least equal to [Module::bytes_of_vec_znx].
41 pub fn from_bytes(n: usize, limbs: usize, bytes: &mut [u8]) -> impl VecZnxApi {
42 let size = Self::bytes_of(n, limbs);
43 assert!(
44 bytes.len() >= size,
45 "invalid buffer: buf.len()={} < self.buffer_size(n={}, limbs={})={}",
46 bytes.len(),
47 n,
48 limbs,
49 size
50 );
51 VecZnxBorrow {
52 n: n,
53 limbs: limbs,
54 data: cast_mut(&mut bytes[..size]).as_mut_ptr(),
55 }
56 }
57}
58
59impl VecZnxApi for VecZnxBorrow {
60 fn bytes_of(n: usize, limbs: usize) -> usize {
61 bytes_of_vec_znx(n, limbs)
62 }
63
64 fn as_ptr(&self) -> *const i64 {
65 self.data
66 }
67
68 fn as_mut_ptr(&mut self) -> *mut i64 {
69 self.data
70 }
71
72 fn at(&self, i: usize) -> &[i64] {
73 unsafe { std::slice::from_raw_parts(self.data.wrapping_add(self.n * i), self.n) }
74 }
75
76 fn at_mut(&mut self, i: usize) -> &mut [i64] {
77 unsafe { std::slice::from_raw_parts_mut(self.at_mut_ptr(i), self.n) }
78 }
79
80 fn at_ptr(&self, i: usize) -> *const i64 {
81 self.data.wrapping_add(self.n * i)
82 }
83
84 fn at_mut_ptr(&mut self, i: usize) -> *mut i64 {
85 self.data.wrapping_add(self.n * i)
86 }
87
88 fn zero(&mut self) {
89 unsafe {
90 znx_zero_i64_ref((self.n * self.limbs) as u64, self.data);
91 }
92 }
93
94 fn normalize(&mut self, log_base2k: usize, carry: &mut [u8]) {
95 assert!(
96 carry.len() >= self.n() * 8,
97 "invalid carry: carry.len()={} < self.n()={}",
98 carry.len(),
99 self.n()
100 );
101
102 let carry_i64: &mut [i64] = cast_mut(carry);
103
104 unsafe {
105 znx::znx_zero_i64_ref(self.n() as u64, carry_i64.as_mut_ptr());
106 (0..self.limbs()).rev().for_each(|i| {
107 znx::znx_normalize(
108 self.n as u64,
109 log_base2k as u64,
110 self.at_mut_ptr(i),
111 carry_i64.as_mut_ptr(),
112 self.at_mut_ptr(i),
113 carry_i64.as_mut_ptr(),
114 )
115 });
116 }
117 }
118}
119
120impl VecZnx {
121 /// Returns a new struct implementing [VecZnxApi] with the provided data as backing array.
122 ///
123 /// The struct will take ownership of buf[..[VecZnx::bytes_of]]
124 ///
125 /// User must ensure that data is properly alligned and that
126 /// the size of data is at least equal to [Module::bytes_of_vec_znx].
127 pub fn from_bytes(n: usize, limbs: usize, buf: &mut [u8]) -> impl VecZnxApi {
128 let size = Self::bytes_of(n, limbs);
129 assert!(
130 buf.len() >= size,
131 "invalid buffer: buf.len()={} < self.buffer_size(n={}, limbs={})={}",
132 buf.len(),
133 n,
134 limbs,
135 size
136 );
137
138 VecZnx {
139 n: n,
140 data: alias_mut_slice_to_vec(cast_mut(&mut buf[..size])),
141 }
142 }
143}
144
145impl VecZnxApi for VecZnx {
146 fn bytes_of(n: usize, limbs: usize) -> usize {
147 bytes_of_vec_znx(n, limbs)
148 }
149
150 /// Returns a non-mutable pointer to the backing array of the [VecZnx].
151 fn as_ptr(&self) -> *const i64 {
152 self.data.as_ptr()
153 }
154
155 /// Returns a mutable pointer to the backing array of the [VecZnx].
156 fn as_mut_ptr(&mut self) -> *mut i64 {
157 self.data.as_mut_ptr()
158 }
159
160 /// Returns a non-mutable reference to the i-th limb of the [VecZnx].
161 fn at(&self, i: usize) -> &[i64] {
162 &self.data[i * self.n..(i + 1) * self.n]
163 }
164
165 /// Returns a mutable reference to the i-th limb of the [VecZnx].
166 fn at_mut(&mut self, i: usize) -> &mut [i64] {
167 &mut self.data[i * self.n..(i + 1) * self.n]
168 }
169
170 /// Returns a non-mutable pointer to the i-th limb of the [VecZnx].
171 fn at_ptr(&self, i: usize) -> *const i64 {
172 &self.data[i * self.n] as *const i64
173 }
174
175 /// Returns a mutable pointer to the i-th limb of the [VecZnx].
176 fn at_mut_ptr(&mut self, i: usize) -> *mut i64 {
177 &mut self.data[i * self.n] as *mut i64
178 }
179
180 /// Zeroes the backing array of the [VecZnx].
181 fn zero(&mut self) {
182 unsafe { znx::znx_zero_i64_ref(self.data.len() as u64, self.data.as_mut_ptr()) }
183 }
184
185 fn normalize(&mut self, log_base2k: usize, carry: &mut [u8]) {
186 assert!(
187 carry.len() >= self.n() * 8,
188 "invalid carry: carry.len()={} < self.n()={}",
189 carry.len(),
190 self.n()
191 );
192
193 let carry_i64: &mut [i64] = cast_mut(carry);
194
195 unsafe {
196 znx::znx_zero_i64_ref(self.n() as u64, carry_i64.as_mut_ptr());
197 (0..self.limbs()).rev().for_each(|i| {
198 znx::znx_normalize(
199 self.n as u64,
200 log_base2k as u64,
201 self.at_mut_ptr(i),
202 carry_i64.as_mut_ptr(),
203 self.at_mut_ptr(i),
204 carry_i64.as_mut_ptr(),
205 )
206 });
207 }
208 }
209}
210
211/// [VecZnx] represents a vector of small norm polynomials of Zn\[X\] with [i64] coefficients.
212/// A [VecZnx] is composed of multiple Zn\[X\] polynomials stored in a single contiguous array
213/// in the memory.
214#[derive(Clone)]
215pub struct VecZnx {
216 /// Polynomial degree.
217 pub n: usize,
218 /// Polynomial coefficients, as a contiguous array. Each limb is equally spaced by n.
219 pub data: Vec<i64>,
220}
221
222impl VecZnx {
223 /// Allocates a new [VecZnx] composed of #limbs polynomials of Z\[X\].
224 pub fn new(n: usize, limbs: usize) -> Self {
225 Self {
226 n: n,
227 data: alloc_aligned::<i64>(n * limbs, 64),
228 }
229 }
230
231 /// Copies the coefficients of `a` on the receiver.
232 /// Copy is done with the minimum size matching both backing arrays.
233 pub fn copy_from(&mut self, a: &VecZnx) {
234 let size = min(self.data.len(), a.data.len());
235 self.data[..size].copy_from_slice(&a.data[..size])
236 }
237
238 /// Truncates the precision of the [VecZnx] by k bits.
239 ///
240 /// # Arguments
241 ///
242 /// * `log_base2k`: the base two logarithm of the coefficients decomposition.
243 /// * `k`: the number of bits of precision to drop.
244 pub fn trunc_pow2(&mut self, log_base2k: usize, k: usize) {
245 if k == 0 {
246 return;
247 }
248
249 self.data
250 .truncate((self.limbs() - k / log_base2k) * self.n());
251
252 let k_rem: usize = k % log_base2k;
253
254 if k_rem != 0 {
255 let mask: i64 = ((1 << (log_base2k - k_rem - 1)) - 1) << k_rem;
256 self.at_mut(self.limbs() - 1)
257 .iter_mut()
258 .for_each(|x: &mut i64| *x &= mask)
259 }
260 }
261
262 /// Right shifts the coefficients by k bits.
263 ///
264 /// # Arguments
265 ///
266 /// * `log_base2k`: the base two logarithm of the coefficients decomposition.
267 /// * `k`: the shift amount.
268 /// * `carry`: scratch space of size at least equal to self.n() * self.limbs() << 3.
269 ///
270 /// # Panics
271 ///
272 /// The method will panic if carry.len() < self.n() * self.limbs() << 3.
273 pub fn rsh(&mut self, log_base2k: usize, k: usize, carry: &mut [u8]) {
274 let n: usize = self.n();
275
276 assert!(
277 carry.len() >> 3 >= n,
278 "invalid carry: carry.len()/8={} < self.n()={}",
279 carry.len() >> 3,
280 n
281 );
282
283 let limbs: usize = self.limbs();
284 let limbs_steps: usize = k / log_base2k;
285
286 self.data.rotate_right(self.n * limbs_steps);
287 unsafe {
288 znx::znx_zero_i64_ref((self.n * limbs_steps) as u64, self.data.as_mut_ptr());
289 }
290
291 let k_rem = k % log_base2k;
292
293 if k_rem != 0 {
294 let carry_i64: &mut [i64] = cast_mut(carry);
295
296 unsafe {
297 znx::znx_zero_i64_ref(n as u64, carry_i64.as_mut_ptr());
298 }
299
300 let mask: i64 = (1 << k_rem) - 1;
301 let log_base2k: usize = log_base2k;
302
303 (limbs_steps..limbs).for_each(|i| {
304 izip!(carry_i64.iter_mut(), self.at_mut(i).iter_mut()).for_each(|(ci, xi)| {
305 *xi += *ci << log_base2k;
306 *ci = *xi & mask;
307 *xi /= 1 << k_rem;
308 });
309 })
310 }
311 }
312
313 /// If self.n() > a.n(): Extracts X^{i*self.n()/a.n()} -> X^{i}.
314 /// If self.n() < a.n(): Extracts X^{i} -> X^{i*a.n()/self.n()}.
315 ///
316 /// # Arguments
317 ///
318 /// * `a`: the receiver polynomial in which the extracted coefficients are stored.
319 pub fn switch_degree(&self, a: &mut VecZnx) {
320 let (n_in, n_out) = (self.n(), a.n());
321 let (gap_in, gap_out): (usize, usize);
322
323 if n_in > n_out {
324 (gap_in, gap_out) = (n_in / n_out, 1)
325 } else {
326 (gap_in, gap_out) = (1, n_out / n_in);
327 a.zero();
328 }
329
330 let limbs = min(self.limbs(), a.limbs());
331
332 (0..limbs).for_each(|i| {
333 izip!(
334 self.at(i).iter().step_by(gap_in),
335 a.at_mut(i).iter_mut().step_by(gap_out)
336 )
337 .for_each(|(x_in, x_out)| *x_out = *x_in);
338 });
339 }
340
341 pub fn print_limbs(&self, limbs: usize, n: usize) {
342 (0..limbs).for_each(|i| println!("{}: {:?}", i, &self.at(i)[..n]))
343 }
344}
345
346pub trait VecZnxOps {
347 /// Allocates a new [VecZnx].
348 ///
349 /// # Arguments
350 ///
351 /// * `limbs`: the number of limbs.
352 fn new_vec_znx(&self, limbs: usize) -> VecZnx;
353
354 /// Returns the minimum number of bytes necessary to allocate
355 /// a new [VecZnx] through [VecZnx::from_bytes].
356 fn bytes_of_vec_znx(&self, limbs: usize) -> usize;
357
358 /// c <- a + b.
359 fn vec_znx_add<T: VecZnxApi + Infos>(&self, c: &mut T, a: &T, b: &T);
360
361 /// b <- b + a.
362 fn vec_znx_add_inplace<T: VecZnxApi + Infos>(&self, b: &mut T, a: &T);
363
364 /// c <- a - b.
365 fn vec_znx_sub<T: VecZnxApi + Infos>(&self, c: &mut T, a: &T, b: &T);
366
367 /// b <- b - a.
368 fn vec_znx_sub_inplace<T: VecZnxApi + Infos>(&self, b: &mut T, a: &T);
369
370 /// b <- -a.
371 fn vec_znx_negate<T: VecZnxApi + Infos>(&self, b: &mut T, a: &T);
372
373 /// b <- -b.
374 fn vec_znx_negate_inplace<T: VecZnxApi + Infos>(&self, a: &mut T);
375
376 /// b <- a * X^k (mod X^{n} + 1)
377 fn vec_znx_rotate<T: VecZnxApi + Infos>(&self, k: i64, b: &mut T, a: &T);
378
379 /// a <- a * X^k (mod X^{n} + 1)
380 fn vec_znx_rotate_inplace<T: VecZnxApi + Infos>(&self, k: i64, a: &mut T);
381
382 /// b <- phi_k(a) where phi_k: X^i -> X^{i*k} (mod (X^{n} + 1))
383 fn vec_znx_automorphism<T: VecZnxApi + Infos>(&self, k: i64, b: &mut T, a: &T, a_limbs: usize);
384
385 /// a <- phi_k(a) where phi_k: X^i -> X^{i*k} (mod (X^{n} + 1))
386 fn vec_znx_automorphism_inplace<T: VecZnxApi + Infos>(&self, k: i64, a: &mut T, a_limbs: usize);
387
388 /// Splits b into subrings and copies them them into a.
389 ///
390 /// # Panics
391 ///
392 /// This method requires that all [VecZnx] of b have the same ring degree
393 /// and that b.n() * b.len() <= a.n()
394 fn vec_znx_split(&self, b: &mut Vec<VecZnx>, a: &VecZnx, buf: &mut VecZnx);
395
396 /// Merges the subrings a into b.
397 ///
398 /// # Panics
399 ///
400 /// This method requires that all [VecZnx] of a have the same ring degree
401 /// and that a.n() * a.len() <= b.n()
402 fn vec_znx_merge(&self, b: &mut VecZnx, a: &Vec<VecZnx>);
403}
404
405impl VecZnxOps for Module {
406 fn new_vec_znx(&self, limbs: usize) -> VecZnx {
407 VecZnx::new(self.n(), limbs)
408 }
409
410 fn bytes_of_vec_znx(&self, limbs: usize) -> usize {
411 self.n() * limbs * 8
412 }
413
414 // c <- a + b
415 fn vec_znx_add<T: VecZnxApi + Infos>(&self, c: &mut T, a: &T, b: &T) {
416 unsafe {
417 vec_znx::vec_znx_add(
418 self.0,
419 c.as_mut_ptr(),
420 c.limbs() as u64,
421 c.n() as u64,
422 a.as_ptr(),
423 a.limbs() as u64,
424 a.n() as u64,
425 b.as_ptr(),
426 b.limbs() as u64,
427 b.n() as u64,
428 )
429 }
430 }
431
432 // b <- a + b
433 fn vec_znx_add_inplace<T: VecZnxApi + Infos>(&self, b: &mut T, a: &T) {
434 unsafe {
435 vec_znx::vec_znx_add(
436 self.0,
437 b.as_mut_ptr(),
438 b.limbs() as u64,
439 b.n() as u64,
440 a.as_ptr(),
441 a.limbs() as u64,
442 a.n() as u64,
443 b.as_ptr(),
444 b.limbs() as u64,
445 b.n() as u64,
446 )
447 }
448 }
449
450 // c <- a + b
451 fn vec_znx_sub<T: VecZnxApi + Infos>(&self, c: &mut T, a: &T, b: &T) {
452 unsafe {
453 vec_znx::vec_znx_sub(
454 self.0,
455 c.as_mut_ptr(),
456 c.limbs() as u64,
457 c.n() as u64,
458 a.as_ptr(),
459 a.limbs() as u64,
460 a.n() as u64,
461 b.as_ptr(),
462 b.limbs() as u64,
463 b.n() as u64,
464 )
465 }
466 }
467
468 // b <- a + b
469 fn vec_znx_sub_inplace<T: VecZnxApi + Infos>(&self, b: &mut T, a: &T) {
470 unsafe {
471 vec_znx::vec_znx_sub(
472 self.0,
473 b.as_mut_ptr(),
474 b.limbs() as u64,
475 b.n() as u64,
476 a.as_ptr(),
477 a.limbs() as u64,
478 a.n() as u64,
479 b.as_ptr(),
480 b.limbs() as u64,
481 b.n() as u64,
482 )
483 }
484 }
485
486 fn vec_znx_negate<T: VecZnxApi + Infos>(&self, b: &mut T, a: &T) {
487 unsafe {
488 vec_znx::vec_znx_negate(
489 self.0,
490 b.as_mut_ptr(),
491 b.limbs() as u64,
492 b.n() as u64,
493 a.as_ptr(),
494 a.limbs() as u64,
495 a.n() as u64,
496 )
497 }
498 }
499
500 fn vec_znx_negate_inplace<T: VecZnxApi + Infos>(&self, a: &mut T) {
501 unsafe {
502 vec_znx::vec_znx_negate(
503 self.0,
504 a.as_mut_ptr(),
505 a.limbs() as u64,
506 a.n() as u64,
507 a.as_ptr(),
508 a.limbs() as u64,
509 a.n() as u64,
510 )
511 }
512 }
513
514 fn vec_znx_rotate<T: VecZnxApi + Infos>(&self, k: i64, a: &mut T, b: &T) {
515 unsafe {
516 vec_znx::vec_znx_rotate(
517 self.0,
518 k,
519 a.as_mut_ptr(),
520 a.limbs() as u64,
521 a.n() as u64,
522 b.as_ptr(),
523 b.limbs() as u64,
524 b.n() as u64,
525 )
526 }
527 }
528
529 fn vec_znx_rotate_inplace<T: VecZnxApi + Infos>(&self, k: i64, a: &mut T) {
530 unsafe {
531 vec_znx::vec_znx_rotate(
532 self.0,
533 k,
534 a.as_mut_ptr(),
535 a.limbs() as u64,
536 a.n() as u64,
537 a.as_ptr(),
538 a.limbs() as u64,
539 a.n() as u64,
540 )
541 }
542 }
543
544 /// Maps X^i to X^{ik} mod X^{n}+1. The mapping is applied independently on each limbs.
545 ///
546 /// # Arguments
547 ///
548 /// * `a`: input.
549 /// * `b`: output.
550 /// * `k`: the power to which to map each coefficients.
551 /// * `limbs_a`: the number of limbs_a on which to apply the mapping.
552 ///
553 /// # Panics
554 ///
555 /// The method will panic if the argument `limbs_a` is greater than `a.limbs()`.
556 ///
557 /// # Example
558 /// ```
559 /// use base2k::{Module, FFT64, VecZnx, Encoding, Infos, VecZnxApi, VecZnxOps};
560 /// use itertools::izip;
561 ///
562 /// let n: usize = 8; // polynomial degree
563 /// let module = Module::new::<FFT64>(n);
564 /// let mut a: VecZnx = module.new_vec_znx(2);
565 /// let mut b: VecZnx = module.new_vec_znx(2);
566 /// let mut c: VecZnx = module.new_vec_znx(2);
567 ///
568 /// (0..a.limbs()).for_each(|i|{
569 /// a.at_mut(i).iter_mut().enumerate().for_each(|(i, x)|{
570 /// *x = i as i64
571 /// })
572 /// });
573 ///
574 /// module.vec_znx_automorphism(-1, &mut b, &a, 1); // X^i -> X^(-i)
575 /// let limb = c.at_mut(0);
576 /// (1..limb.len()).for_each(|i|{
577 /// limb[n-i] = -(i as i64)
578 /// });
579 /// izip!(b.data.iter(), c.data.iter()).for_each(|(a, b)| assert_eq!(a, b, "{} != {}", a, b));
580 /// ```
581 fn vec_znx_automorphism<T: VecZnxApi + Infos>(&self, k: i64, b: &mut T, a: &T, limbs_a: usize) {
582 assert_eq!(a.n(), self.n());
583 assert_eq!(b.n(), self.n());
584 assert!(a.limbs() >= limbs_a);
585 unsafe {
586 vec_znx::vec_znx_automorphism(
587 self.0,
588 k,
589 b.as_mut_ptr(),
590 b.limbs() as u64,
591 b.n() as u64,
592 a.as_ptr(),
593 limbs_a as u64,
594 a.n() as u64,
595 );
596 }
597 }
598
599 /// Maps X^i to X^{ik} mod X^{n}+1. The mapping is applied independently on each limbs.
600 ///
601 /// # Arguments
602 ///
603 /// * `a`: input and output.
604 /// * `k`: the power to which to map each coefficients.
605 /// * `limbs_a`: the number of limbs on which to apply the mapping.
606 ///
607 /// # Panics
608 ///
609 /// The method will panic if the argument `limbs` is greater than `self.limbs()`.
610 ///
611 /// # Example
612 /// ```
613 /// use base2k::{Module, FFT64, VecZnx, Encoding, Infos, VecZnxApi, VecZnxOps};
614 /// use itertools::izip;
615 ///
616 /// let n: usize = 8; // polynomial degree
617 /// let module = Module::new::<FFT64>(n);
618 /// let mut a: VecZnx = VecZnx::new(n, 2);
619 /// let mut b: VecZnx = VecZnx::new(n, 2);
620 ///
621 /// (0..a.limbs()).for_each(|i|{
622 /// a.at_mut(i).iter_mut().enumerate().for_each(|(i, x)|{
623 /// *x = i as i64
624 /// })
625 /// });
626 ///
627 /// module.vec_znx_automorphism_inplace(-1, &mut a, 1); // X^i -> X^(-i)
628 /// let limb = b.at_mut(0);
629 /// (1..limb.len()).for_each(|i|{
630 /// limb[n-i] = -(i as i64)
631 /// });
632 /// izip!(a.data.iter(), b.data.iter()).for_each(|(a, b)| assert_eq!(a, b, "{} != {}", a, b));
633 /// ```
634 fn vec_znx_automorphism_inplace<T: VecZnxApi + Infos>(
635 &self,
636 k: i64,
637 a: &mut T,
638 limbs_a: usize,
639 ) {
640 assert_eq!(a.n(), self.n());
641 assert!(a.limbs() >= limbs_a);
642 unsafe {
643 vec_znx::vec_znx_automorphism(
644 self.0,
645 k,
646 a.as_mut_ptr(),
647 a.limbs() as u64,
648 a.n() as u64,
649 a.as_ptr(),
650 limbs_a as u64,
651 a.n() as u64,
652 );
653 }
654 }
655
656 fn vec_znx_split(&self, b: &mut Vec<VecZnx>, a: &VecZnx, buf: &mut VecZnx) {
657 let (n_in, n_out) = (a.n(), b[0].n());
658
659 assert!(
660 n_out < n_in,
661 "invalid a: output ring degree should be smaller"
662 );
663 b[1..].iter().for_each(|bi| {
664 assert_eq!(
665 bi.n(),
666 n_out,
667 "invalid input a: all VecZnx must have the same degree"
668 )
669 });
670
671 b.iter_mut().enumerate().for_each(|(i, bi)| {
672 if i == 0 {
673 a.switch_degree(bi);
674 self.vec_znx_rotate(-1, buf, a);
675 } else {
676 buf.switch_degree(bi);
677 self.vec_znx_rotate_inplace(-1, buf);
678 }
679 })
680 }
681
682 fn vec_znx_merge(&self, b: &mut VecZnx, a: &Vec<VecZnx>) {
683 let (n_in, n_out) = (b.n(), a[0].n());
684
685 assert!(
686 n_out < n_in,
687 "invalid a: output ring degree should be smaller"
688 );
689 a[1..].iter().for_each(|ai| {
690 assert_eq!(
691 ai.n(),
692 n_out,
693 "invalid input a: all VecZnx must have the same degree"
694 )
695 });
696
697 a.iter().enumerate().for_each(|(_, ai)| {
698 ai.switch_degree(b);
699 self.vec_znx_rotate_inplace(-1, b);
700 });
701
702 self.vec_znx_rotate_inplace(a.len() as i64, b);
703 }
704}