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