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