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