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