arnaucube
arnaucube/poulpyPublic Self-hosted repository
poulpy/poulpy-hal/src/reference/vec_znx/shift.rs
poulpy-hal/src/reference/vec_znx/shift.rsRaw
  1use std::hint::black_box;
  2
  3use criterion::{BenchmarkId, Criterion};
  4
  5use crate::{
  6    api::{ModuleNew, ScratchOwnedAlloc, ScratchOwnedBorrow, VecZnxLsh, VecZnxLshInplace, VecZnxRsh, VecZnxRshInplace},
  7    layouts::{Backend, FillUniform, Module, ScratchOwned, VecZnx, VecZnxToMut, VecZnxToRef, ZnxInfos, ZnxView, ZnxViewMut},
  8    reference::znx::{
  9        ZnxCopy, ZnxNormalizeFinalStep, ZnxNormalizeFinalStepInplace, ZnxNormalizeFirstStep, ZnxNormalizeFirstStepCarryOnly,
 10        ZnxNormalizeFirstStepInplace, ZnxNormalizeMiddleStep, ZnxNormalizeMiddleStepCarryOnly, ZnxNormalizeMiddleStepInplace,
 11        ZnxZero,
 12    },
 13    source::Source,
 14};
 15
 16pub fn vec_znx_lsh_tmp_bytes(n: usize) -> usize {
 17    n * size_of::<i64>()
 18}
 19
 20pub fn vec_znx_lsh_inplace<R, ZNXARI>(base2k: usize, k: usize, res: &mut R, res_col: usize, carry: &mut [i64])
 21where
 22    R: VecZnxToMut,
 23    ZNXARI: ZnxZero
 24        + ZnxCopy
 25        + ZnxNormalizeFirstStepInplace
 26        + ZnxNormalizeMiddleStepInplace
 27        + ZnxNormalizeFirstStepInplace
 28        + ZnxNormalizeFinalStepInplace,
 29{
 30    let mut res: VecZnx<&mut [u8]> = res.to_mut();
 31
 32    let n: usize = res.n();
 33    let cols: usize = res.cols();
 34    let size: usize = res.size();
 35    let steps: usize = k / base2k;
 36    let k_rem: usize = k % base2k;
 37
 38    if steps >= size {
 39        for j in 0..size {
 40            ZNXARI::znx_zero(res.at_mut(res_col, j));
 41        }
 42        return;
 43    }
 44
 45    // Inplace shift of limbs by a k/base2k
 46    if steps > 0 {
 47        let start: usize = n * res_col;
 48        let end: usize = start + n;
 49        let slice_size: usize = n * cols;
 50        let res_raw: &mut [i64] = res.raw_mut();
 51
 52        (0..size - steps).for_each(|j| {
 53            let (lhs, rhs) = res_raw.split_at_mut(slice_size * (j + steps));
 54            ZNXARI::znx_copy(&mut lhs[start + j * slice_size..end + j * slice_size], &rhs[start..end]);
 55        });
 56
 57        for j in size - steps..size {
 58            ZNXARI::znx_zero(res.at_mut(res_col, j));
 59        }
 60    }
 61
 62    for j in (0..size - steps).rev() {
 63        if j == size - steps - 1 {
 64            ZNXARI::znx_normalize_first_step_inplace(base2k, k_rem, res.at_mut(res_col, j), carry);
 65        } else if j == 0 {
 66            ZNXARI::znx_normalize_final_step_inplace(base2k, k_rem, res.at_mut(res_col, j), carry);
 67        } else {
 68            ZNXARI::znx_normalize_middle_step_inplace(base2k, k_rem, res.at_mut(res_col, j), carry);
 69        }
 70    }
 71}
 72
 73pub fn vec_znx_lsh<R, A, ZNXARI>(base2k: usize, k: usize, res: &mut R, res_col: usize, a: &A, a_col: usize, carry: &mut [i64])
 74where
 75    R: VecZnxToMut,
 76    A: VecZnxToRef,
 77    ZNXARI: ZnxZero + ZnxNormalizeFirstStep + ZnxNormalizeMiddleStep + ZnxNormalizeFirstStep + ZnxCopy + ZnxNormalizeFinalStep,
 78{
 79    let mut res: VecZnx<&mut [u8]> = res.to_mut();
 80    let a: VecZnx<&[u8]> = a.to_ref();
 81
 82    let res_size: usize = res.size();
 83    let a_size = a.size();
 84    let steps: usize = k / base2k;
 85    let k_rem: usize = k % base2k;
 86
 87    if steps >= res_size.min(a_size) {
 88        for j in 0..res_size {
 89            ZNXARI::znx_zero(res.at_mut(res_col, j));
 90        }
 91        return;
 92    }
 93
 94    let min_size: usize = a_size.min(res_size) - steps;
 95
 96    // Simply a left shifted normalization of limbs
 97    // by k/base2k and intra-limb by base2k - k%base2k
 98    for j in (0..min_size).rev() {
 99        if j == min_size - 1 {
100            ZNXARI::znx_normalize_first_step(base2k, k_rem, res.at_mut(res_col, j), a.at(a_col, j + steps), carry);
101        } else if j == 0 {
102            ZNXARI::znx_normalize_final_step(base2k, k_rem, res.at_mut(res_col, j), a.at(a_col, j + steps), carry);
103        } else {
104            ZNXARI::znx_normalize_middle_step(base2k, k_rem, res.at_mut(res_col, j), a.at(a_col, j + steps), carry);
105        }
106    }
107
108    // Zeroes bottom
109    for j in min_size..res_size {
110        ZNXARI::znx_zero(res.at_mut(res_col, j));
111    }
112}
113
114pub fn vec_znx_rsh_tmp_bytes(n: usize) -> usize {
115    2 * n * size_of::<i64>()
116}
117
118pub fn vec_znx_rsh_inplace<R, ZNXARI>(base2k: usize, k: usize, res: &mut R, res_col: usize, tmp: &mut [i64])
119where
120    R: VecZnxToMut,
121    ZNXARI: ZnxZero
122        + ZnxCopy
123        + ZnxNormalizeFirstStepCarryOnly
124        + ZnxNormalizeMiddleStepCarryOnly
125        + ZnxNormalizeMiddleStep
126        + ZnxNormalizeMiddleStepInplace
127        + ZnxNormalizeFirstStepInplace
128        + ZnxNormalizeFinalStepInplace,
129{
130    let mut res: VecZnx<&mut [u8]> = res.to_mut();
131    let n: usize = res.n();
132
133    let size: usize = res.size();
134
135    let mut steps: usize = k / base2k;
136    let k_rem: usize = k % base2k;
137
138    if !k.is_multiple_of(base2k) {
139        // We rsh by an additional base2k and then lsh by base2k-k
140        // Allows to re-use efficient normalization code, avoids
141        // avoids overflows & produce output that is normalized
142        steps += 1;
143    }
144
145    let (carry, tmp) = tmp[..2 * n].split_at_mut(n);
146
147    let lsh: usize = (base2k - k_rem) % base2k;
148
149    // All limbs of a that would fall outside of the limbs of res are discarded,
150    // but the carry still need to be computed.
151    for j in 0..steps {
152        if j == 0 {
153            ZNXARI::znx_normalize_first_step_carry_only(base2k, lsh, res.at(res_col, size - j - 1), carry);
154        } else {
155            ZNXARI::znx_normalize_middle_step_carry_only(base2k, lsh, res.at(res_col, size - j - 1), carry);
156        }
157    }
158
159    // Continues with shifted normalization
160    for j in 0..size - steps {
161        ZNXARI::znx_copy(tmp, res.at(res_col, size - steps - j - 1));
162        ZNXARI::znx_normalize_middle_step_inplace(base2k, lsh, tmp, carry);
163        ZNXARI::znx_copy(res.at_mut(res_col, size - j - 1), tmp);
164    }
165
166    // Propagates carry on the rest of the limbs of res
167    for j in 0..steps {
168        ZNXARI::znx_zero(res.at_mut(res_col, j));
169        if j == 0 {
170            ZNXARI::znx_normalize_final_step_inplace(base2k, lsh, res.at_mut(res_col, steps - j - 1), carry);
171        } else {
172            ZNXARI::znx_normalize_middle_step_inplace(base2k, lsh, res.at_mut(res_col, steps - j - 1), carry);
173        }
174    }
175}
176
177pub fn vec_znx_rsh<R, A, ZNXARI>(base2k: usize, k: usize, res: &mut R, res_col: usize, a: &A, a_col: usize, carry: &mut [i64])
178where
179    R: VecZnxToMut,
180    A: VecZnxToRef,
181    ZNXARI: ZnxZero
182        + ZnxCopy
183        + ZnxNormalizeFirstStepCarryOnly
184        + ZnxNormalizeMiddleStepCarryOnly
185        + ZnxNormalizeFirstStep
186        + ZnxNormalizeMiddleStep
187        + ZnxNormalizeMiddleStepInplace
188        + ZnxNormalizeFirstStepInplace
189        + ZnxNormalizeFinalStepInplace,
190{
191    let mut res: VecZnx<&mut [u8]> = res.to_mut();
192    let a: VecZnx<&[u8]> = a.to_ref();
193
194    let res_size: usize = res.size();
195    let a_size: usize = a.size();
196
197    let mut steps: usize = k / base2k;
198    let k_rem: usize = k % base2k;
199
200    if !k.is_multiple_of(base2k) {
201        // We rsh by an additional base2k and then lsh by base2k-k
202        // Allows to re-use efficient normalization code, avoids
203        // avoids overflows & produce output that is normalized
204        steps += 1;
205    }
206
207    let lsh: usize = (base2k - k_rem) % base2k; // 0 if k | base2k
208    let res_end: usize = res_size.min(steps);
209    let res_start: usize = res_size.min(a_size + steps);
210    let a_start: usize = a_size.min(res_size.saturating_sub(steps));
211
212    // All limbs of a that are moved outside of the limbs of res are discarded,
213    // but the carry still need to be computed.
214    let a_out_range: usize = a_size.saturating_sub(a_start);
215
216    for j in 0..a_out_range {
217        if j == 0 {
218            ZNXARI::znx_normalize_first_step_carry_only(base2k, lsh, a.at(a_col, a_size - j - 1), carry);
219        } else {
220            ZNXARI::znx_normalize_middle_step_carry_only(base2k, lsh, a.at(a_col, a_size - j - 1), carry);
221        }
222    }
223
224    if a_out_range == 0 {
225        ZNXARI::znx_zero(carry);
226    }
227
228    // Zeroes lower limbs of res if a_size + steps < res_size
229    for j in 0..res_size {
230        ZNXARI::znx_zero(res.at_mut(res_col, j));
231    }
232
233    // Continues with shifted normalization
234    let mid_range: usize = res_start.saturating_sub(res_end);
235
236    for j in 0..mid_range {
237        ZNXARI::znx_normalize_middle_step(
238            base2k,
239            lsh,
240            res.at_mut(res_col, res_start - j - 1),
241            a.at(a_col, a_start - j - 1),
242            carry,
243        );
244    }
245
246    // Propagates carry on the rest of the limbs of res
247    for j in 0..res_end {
248        if j == res_end - 1 {
249            ZNXARI::znx_normalize_final_step_inplace(base2k, lsh, res.at_mut(res_col, res_end - j - 1), carry);
250        } else {
251            ZNXARI::znx_normalize_middle_step_inplace(base2k, lsh, res.at_mut(res_col, res_end - j - 1), carry);
252        }
253    }
254}
255
256pub fn bench_vec_znx_lsh_inplace<B: Backend>(c: &mut Criterion, label: &str)
257where
258    Module<B>: ModuleNew<B> + VecZnxLshInplace<B>,
259    ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
260{
261    let group_name: String = format!("vec_znx_lsh_inplace::{label}");
262
263    let mut group = c.benchmark_group(group_name);
264
265    fn runner<B: Backend>(params: [usize; 3]) -> impl FnMut()
266    where
267        Module<B>: VecZnxLshInplace<B> + ModuleNew<B>,
268        ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
269    {
270        let n: usize = 1 << params[0];
271        let cols: usize = params[1];
272        let size: usize = params[2];
273
274        let module: Module<B> = Module::<B>::new(n as u64);
275
276        let base2k: usize = 50;
277
278        let mut source: Source = Source::new([0u8; 32]);
279
280        let mut a: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
281        let mut b: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
282
283        let mut scratch: ScratchOwned<B> = ScratchOwned::alloc(vec_znx_lsh_tmp_bytes(n));
284
285        // Fill a with random i64
286        a.fill_uniform(50, &mut source);
287        b.fill_uniform(50, &mut source);
288
289        move || {
290            for i in 0..cols {
291                module.vec_znx_lsh_inplace(base2k, base2k - 1, &mut b, i, scratch.borrow());
292            }
293            black_box(());
294        }
295    }
296
297    for params in [[10, 2, 2], [11, 2, 4], [12, 2, 8], [13, 2, 16], [14, 2, 32]] {
298        let id: BenchmarkId = BenchmarkId::from_parameter(format!("{}x({}x{})", 1 << params[0], params[1], params[2]));
299        let mut runner = runner::<B>(params);
300        group.bench_with_input(id, &(), |b, _| b.iter(&mut runner));
301    }
302
303    group.finish();
304}
305
306pub fn bench_vec_znx_lsh<B: Backend>(c: &mut Criterion, label: &str)
307where
308    Module<B>: VecZnxLsh<B> + ModuleNew<B>,
309    ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
310{
311    let group_name: String = format!("vec_znx_lsh::{label}");
312
313    let mut group = c.benchmark_group(group_name);
314
315    fn runner<B: Backend>(params: [usize; 3]) -> impl FnMut()
316    where
317        Module<B>: VecZnxLsh<B> + ModuleNew<B>,
318        ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
319    {
320        let n: usize = 1 << params[0];
321        let cols: usize = params[1];
322        let size: usize = params[2];
323
324        let module: Module<B> = Module::<B>::new(n as u64);
325
326        let base2k: usize = 50;
327
328        let mut source: Source = Source::new([0u8; 32]);
329
330        let mut a: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
331        let mut res: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
332
333        let mut scratch: ScratchOwned<B> = ScratchOwned::alloc(vec_znx_lsh_tmp_bytes(n));
334
335        // Fill a with random i64
336        a.fill_uniform(50, &mut source);
337        res.fill_uniform(50, &mut source);
338
339        move || {
340            for i in 0..cols {
341                module.vec_znx_lsh(base2k, base2k - 1, &mut res, i, &a, i, scratch.borrow());
342            }
343            black_box(());
344        }
345    }
346
347    for params in [[10, 2, 2], [11, 2, 4], [12, 2, 8], [13, 2, 16], [14, 2, 32]] {
348        let id: BenchmarkId = BenchmarkId::from_parameter(format!("{}x({}x{})", 1 << params[0], params[1], params[2]));
349        let mut runner = runner::<B>(params);
350        group.bench_with_input(id, &(), |b, _| b.iter(&mut runner));
351    }
352
353    group.finish();
354}
355
356pub fn bench_vec_znx_rsh_inplace<B: Backend>(c: &mut Criterion, label: &str)
357where
358    Module<B>: VecZnxRshInplace<B> + ModuleNew<B>,
359    ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
360{
361    let group_name: String = format!("vec_znx_rsh_inplace::{label}");
362
363    let mut group = c.benchmark_group(group_name);
364
365    fn runner<B: Backend>(params: [usize; 3]) -> impl FnMut()
366    where
367        Module<B>: VecZnxRshInplace<B> + ModuleNew<B>,
368        ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
369    {
370        let n: usize = 1 << params[0];
371        let cols: usize = params[1];
372        let size: usize = params[2];
373
374        let module: Module<B> = Module::<B>::new(n as u64);
375
376        let base2k: usize = 50;
377
378        let mut source: Source = Source::new([0u8; 32]);
379
380        let mut a: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
381        let mut b: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
382
383        let mut scratch: ScratchOwned<B> = ScratchOwned::alloc(vec_znx_rsh_tmp_bytes(n));
384
385        // Fill a with random i64
386        a.fill_uniform(50, &mut source);
387        b.fill_uniform(50, &mut source);
388
389        move || {
390            for i in 0..cols {
391                module.vec_znx_rsh_inplace(base2k, base2k - 1, &mut b, i, scratch.borrow());
392            }
393            black_box(());
394        }
395    }
396
397    for params in [[10, 2, 2], [11, 2, 4], [12, 2, 8], [13, 2, 16], [14, 2, 32]] {
398        let id: BenchmarkId = BenchmarkId::from_parameter(format!("{}x({}x{})", 1 << params[0], params[1], params[2]));
399        let mut runner = runner::<B>(params);
400        group.bench_with_input(id, &(), |b, _| b.iter(&mut runner));
401    }
402
403    group.finish();
404}
405
406pub fn bench_vec_znx_rsh<B: Backend>(c: &mut Criterion, label: &str)
407where
408    Module<B>: VecZnxRsh<B> + ModuleNew<B>,
409    ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
410{
411    let group_name: String = format!("vec_znx_rsh::{label}");
412
413    let mut group = c.benchmark_group(group_name);
414
415    fn runner<B: Backend>(params: [usize; 3]) -> impl FnMut()
416    where
417        Module<B>: VecZnxRsh<B> + ModuleNew<B>,
418        ScratchOwned<B>: ScratchOwnedAlloc<B> + ScratchOwnedBorrow<B>,
419    {
420        let n: usize = 1 << params[0];
421        let cols: usize = params[1];
422        let size: usize = params[2];
423
424        let module: Module<B> = Module::<B>::new(n as u64);
425
426        let base2k: usize = 50;
427
428        let mut source: Source = Source::new([0u8; 32]);
429
430        let mut a: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
431        let mut res: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
432
433        let mut scratch: ScratchOwned<B> = ScratchOwned::alloc(vec_znx_rsh_tmp_bytes(n));
434
435        // Fill a with random i64
436        a.fill_uniform(50, &mut source);
437        res.fill_uniform(50, &mut source);
438
439        move || {
440            for i in 0..cols {
441                module.vec_znx_rsh(base2k, base2k - 1, &mut res, i, &a, i, scratch.borrow());
442            }
443            black_box(());
444        }
445    }
446
447    for params in [[10, 2, 2], [11, 2, 4], [12, 2, 8], [13, 2, 16], [14, 2, 32]] {
448        let id: BenchmarkId = BenchmarkId::from_parameter(format!("{}x({}x{})", 1 << params[0], params[1], params[2]));
449        let mut runner = runner::<B>(params);
450        group.bench_with_input(id, &(), |b, _| b.iter(&mut runner));
451    }
452
453    group.finish();
454}
455
456#[cfg(test)]
457mod tests {
458    use crate::{
459        layouts::{FillUniform, VecZnx, ZnxView},
460        reference::{
461            vec_znx::{
462                vec_znx_copy, vec_znx_lsh, vec_znx_lsh_inplace, vec_znx_lsh_tmp_bytes, vec_znx_normalize_inplace, vec_znx_rsh,
463                vec_znx_rsh_inplace, vec_znx_rsh_tmp_bytes, vec_znx_sub_inplace,
464            },
465            znx::ZnxRef,
466        },
467        source::Source,
468    };
469
470    #[test]
471    fn test_vec_znx_lsh() {
472        let n: usize = 8;
473        let cols: usize = 2;
474        let size: usize = 7;
475
476        let mut a: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
477        let mut res_ref: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
478        let mut res_test: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, size);
479
480        let mut source: Source = Source::new([0u8; 32]);
481
482        let mut carry: Vec<i64> = vec![0i64; vec_znx_lsh_tmp_bytes(n) / size_of::<i64>()];
483
484        let base2k: usize = 50;
485
486        for k in 0..256 {
487            a.fill_uniform(50, &mut source);
488
489            for i in 0..cols {
490                vec_znx_normalize_inplace::<_, ZnxRef>(base2k, &mut a, i, &mut carry);
491                vec_znx_copy::<_, _, ZnxRef>(&mut res_ref, i, &a, i);
492            }
493
494            for i in 0..cols {
495                vec_znx_lsh_inplace::<_, ZnxRef>(base2k, k, &mut res_ref, i, &mut carry);
496                vec_znx_lsh::<_, _, ZnxRef>(base2k, k, &mut res_test, i, &a, i, &mut carry);
497                vec_znx_normalize_inplace::<_, ZnxRef>(base2k, &mut res_test, i, &mut carry);
498            }
499
500            assert_eq!(res_ref, res_test);
501        }
502    }
503
504    #[test]
505    fn test_vec_znx_rsh() {
506        let n: usize = 8;
507        let cols: usize = 2;
508
509        let res_size: usize = 7;
510
511        let mut res_ref: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, res_size);
512        let mut res_test: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, res_size);
513
514        let mut carry: Vec<i64> = vec![0i64; vec_znx_rsh_tmp_bytes(n) / size_of::<i64>()];
515
516        let base2k: usize = 50;
517
518        let mut source: Source = Source::new([0u8; 32]);
519
520        let zero: Vec<i64> = vec![0i64; n];
521
522        for a_size in [res_size - 1, res_size, res_size + 1] {
523            let mut a: VecZnx<Vec<u8>> = VecZnx::alloc(n, cols, a_size);
524
525            for k in 0..res_size * base2k {
526                a.fill_uniform(50, &mut source);
527
528                for i in 0..cols {
529                    vec_znx_normalize_inplace::<_, ZnxRef>(base2k, &mut a, i, &mut carry);
530                    vec_znx_copy::<_, _, ZnxRef>(&mut res_ref, i, &a, i);
531                }
532
533                res_test.fill_uniform(50, &mut source);
534
535                for j in 0..cols {
536                    vec_znx_rsh_inplace::<_, ZnxRef>(base2k, k, &mut res_ref, j, &mut carry);
537                    vec_znx_rsh::<_, _, ZnxRef>(base2k, k, &mut res_test, j, &a, j, &mut carry);
538                }
539
540                for j in 0..cols {
541                    vec_znx_lsh_inplace::<_, ZnxRef>(base2k, k, &mut res_ref, j, &mut carry);
542                    vec_znx_lsh_inplace::<_, ZnxRef>(base2k, k, &mut res_test, j, &mut carry);
543                }
544
545                // Case where res has enough to fully store a right shifted without any loss
546                // In this case we can check exact equality.
547                if a_size + k.div_ceil(base2k) <= res_size {
548                    assert_eq!(res_ref, res_test);
549
550                    for i in 0..cols {
551                        for j in 0..a_size {
552                            assert_eq!(res_ref.at(i, j), a.at(i, j), "r0 {} {}", i, j);
553                            assert_eq!(res_test.at(i, j), a.at(i, j), "r1 {} {}", i, j);
554                        }
555
556                        for j in a_size..res_size {
557                            assert_eq!(res_ref.at(i, j), zero, "r0 {} {}", i, j);
558                            assert_eq!(res_test.at(i, j), zero, "r1 {} {}", i, j);
559                        }
560                    }
561                // Some loss occures, either because a initially has more precision than res
562                // or because the storage of the right shift of a requires more precision than
563                // res.
564                } else {
565                    for j in 0..cols {
566                        vec_znx_sub_inplace::<_, _, ZnxRef>(&mut res_ref, j, &a, j);
567                        vec_znx_sub_inplace::<_, _, ZnxRef>(&mut res_test, j, &a, j);
568
569                        vec_znx_normalize_inplace::<_, ZnxRef>(base2k, &mut res_ref, j, &mut carry);
570                        vec_znx_normalize_inplace::<_, ZnxRef>(base2k, &mut res_test, j, &mut carry);
571
572                        assert!(res_ref.stats(base2k, j).std().log2() - (k as f64) <= (k * base2k) as f64);
573                        assert!(res_test.stats(base2k, j).std().log2() - (k as f64) <= (k * base2k) as f64);
574                    }
575                }
576            }
577        }
578    }
579}