arnaucube
arnaucube/poulpyPublic Self-hosted repository
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}