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