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