1use crate::{
2 ConstCoeffBitWidth, EvaluatablePolynomial, EvaluationError, Polynomial,
3 univariate::{dense::DensePolynomial, prepare_projection},
4};
5use core::mem::MaybeUninit;
6use crypto_primitives::{FieldConfig, Wrapper, boolean::Boolean};
7use derive_more::{AsRef, Display};
8use num_traits::{CheckedAdd, CheckedMul, One, Zero};
9use rand::{distr::StandardUniform, prelude::*};
10use std::{
11 array,
12 hash::Hash,
13 marker::PhantomData,
14 ops::{Add, Mul},
15};
16use zinc_transcript::{delegate_const_transcribable, traits::ConstTranscribable};
17use zinc_utils::{
18 from_ref::FromRef,
19 inner_product::{InnerProduct, InnerProductError},
20 mul_by_scalar::MulByScalar,
21 named::Named,
22 projectable_to_field::ProjectableToField,
23};
24
25#[derive(AsRef, Clone, Debug, Default, Display, Hash, PartialEq, Eq)]
26#[repr(transparent)]
27pub struct BinaryU64Poly<const DEGREE_PLUS_ONE: usize>(u64); impl<const DEGREE_PLUS_ONE: usize> BinaryU64Poly<DEGREE_PLUS_ONE> {
30 const _DEGREE_CHECK: () = {
31 assert!(
34 DEGREE_PLUS_ONE <= 64,
35 "For BinaryU64Poly, degree cannot exceed 63"
36 );
37 };
38
39 #[inline(always)]
40 #[allow(clippy::let_unit_value)]
41 pub const fn inner(&self) -> &u64 {
42 let _ = Self::_DEGREE_CHECK; &self.0
44 }
45}
46
47impl<const DEGREE_PLUS_ONE: usize> From<BinaryU64Poly<DEGREE_PLUS_ONE>> for u64 {
48 #[inline(always)]
49 fn from(binary_poly: BinaryU64Poly<DEGREE_PLUS_ONE>) -> Self {
50 binary_poly.0
51 }
52}
53
54impl<const DEGREE_PLUS_ONE: usize> From<BinaryU64Poly<DEGREE_PLUS_ONE>>
55 for DensePolynomial<Boolean, DEGREE_PLUS_ONE>
56{
57 #[inline(always)]
58 fn from(binary_poly: BinaryU64Poly<DEGREE_PLUS_ONE>) -> Self {
59 DensePolynomial {
60 coeffs: array::from_fn(|i| Boolean::new(!(binary_poly.0 & (1 << i)).is_zero())),
61 }
62 }
63}
64
65impl<const DEGREE_PLUS_ONE: usize> From<u32> for BinaryU64Poly<DEGREE_PLUS_ONE> {
66 #[inline(always)]
67 fn from(value: u32) -> Self {
68 Self::from(u64::from(value))
70 }
71}
72
73impl<const DEGREE_PLUS_ONE: usize> From<u64> for BinaryU64Poly<DEGREE_PLUS_ONE> {
74 #[inline(always)]
75 #[allow(clippy::arithmetic_side_effects, clippy::let_unit_value)]
76 fn from(value: u64) -> Self {
77 let _ = Self::_DEGREE_CHECK; if DEGREE_PLUS_ONE == 64 {
81 Self(value)
82 } else {
83 Self(value & ((1_u64 << DEGREE_PLUS_ONE) - 1))
87 }
88 }
89}
90
91impl<const DEGREE_PLUS_ONE: usize> BinaryU64Poly<DEGREE_PLUS_ONE> {
92 #[inline(always)]
93 pub fn new(coeffs: [Boolean; DEGREE_PLUS_ONE]) -> Self {
94 BinaryU64Poly::new_padded(coeffs)
95 }
96
97 #[inline(always)]
102 pub fn new_padded(coeffs: impl AsRef<[Boolean]>) -> Self {
103 let coeffs = coeffs.as_ref();
104 assert!(coeffs.len() <= DEGREE_PLUS_ONE);
105 let mut value: u64 = 0;
106 for (i, coeff) in coeffs.iter().enumerate() {
107 if *coeff.inner() {
108 value |= 1 << i;
109 }
110 }
111 Self(value)
112 }
113}
114
115impl<const DEGREE_PLUS_ONE: usize> Zero for BinaryU64Poly<DEGREE_PLUS_ONE> {
116 #[inline(always)]
117 fn zero() -> Self {
118 Self(0)
119 }
120
121 #[inline(always)]
122 fn is_zero(&self) -> bool {
123 self.0.is_zero()
124 }
125}
126
127impl<const DEGREE_PLUS_ONE: usize> One for BinaryU64Poly<DEGREE_PLUS_ONE> {
128 #[inline(always)]
129 fn one() -> Self {
130 Self(1)
131 }
132}
133
134impl<'a, const DEGREE_PLUS_ONE: usize> Add<&'a Self> for BinaryU64Poly<DEGREE_PLUS_ONE> {
135 type Output = Self;
136
137 #[allow(clippy::arithmetic_side_effects, clippy::suspicious_arithmetic_impl)]
138 #[inline(always)]
139 fn add(self, rhs: &'a Self) -> Self::Output {
140 Self(self.0 ^ rhs.0)
142 }
143}
144
145impl<const DEGREE_PLUS_ONE: usize> Add for BinaryU64Poly<DEGREE_PLUS_ONE> {
146 type Output = Self;
147
148 #[inline(always)]
149 fn add(self, _rhs: Self) -> Self::Output {
150 panic!("Binary polynomials addition is not defined")
151 }
152}
153
154impl<const DEGREE_PLUS_ONE: usize> Mul for BinaryU64Poly<DEGREE_PLUS_ONE> {
155 type Output = Self;
156
157 #[inline(always)]
158 fn mul(self, _rhs: Self) -> Self::Output {
159 panic!("Binary polynomials multiplication is not defined")
160 }
161}
162
163impl<const DEGREE_PLUS_ONE: usize> Distribution<BinaryU64Poly<DEGREE_PLUS_ONE>>
164 for StandardUniform
165{
166 #[inline(always)]
167 #[allow(clippy::arithmetic_side_effects)]
168 fn sample<Gen: Rng + ?Sized>(&self, rng: &mut Gen) -> BinaryU64Poly<DEGREE_PLUS_ONE> {
169 let mut value: u64 = rng.next_u64();
170 if DEGREE_PLUS_ONE < 64 {
171 value &= (1u64 << DEGREE_PLUS_ONE) - 1;
172 }
173 BinaryU64Poly::<DEGREE_PLUS_ONE>(value)
174 }
175}
176
177impl<const DEGREE_PLUS_ONE: usize> Polynomial<Boolean> for BinaryU64Poly<DEGREE_PLUS_ONE> {
181 const DEGREE_BOUND: usize = DensePolynomial::<Boolean, DEGREE_PLUS_ONE>::DEGREE_BOUND;
182}
183
184#[allow(clippy::arithmetic_side_effects)]
185impl<R: Clone + Zero + One + CheckedAdd + CheckedMul, const DEGREE_PLUS_ONE: usize>
186 EvaluatablePolynomial<Boolean, R> for BinaryU64Poly<DEGREE_PLUS_ONE>
187{
188 type EvaluationPoint = R;
189
190 fn evaluate_at_point(&self, point: &R) -> Result<R, EvaluationError> {
191 if DEGREE_PLUS_ONE.is_one() {
192 return Ok(R::zero());
193 }
194
195 let mut result = R::zero();
196 let mut pow = R::one();
197 for i in 0..DEGREE_PLUS_ONE {
198 if (self.0 & (1 << i)) != 0 {
199 result = result.checked_add(&pow).ok_or(EvaluationError::Overflow)?;
200 }
201 if i + 1 < DEGREE_PLUS_ONE {
202 pow = pow.checked_mul(point).ok_or(EvaluationError::Overflow)?;
203 }
204 }
205
206 Ok(result)
207 }
208}
209
210impl<const DEGREE_PLUS_ONE: usize> ConstCoeffBitWidth for BinaryU64Poly<DEGREE_PLUS_ONE> {
211 const COEFF_BIT_WIDTH: usize = Boolean::NUM_BITS;
212}
213
214impl<const DEGREE_PLUS_ONE: usize> Named for BinaryU64Poly<DEGREE_PLUS_ONE> {
215 fn type_name() -> String {
216 format!("BPoly<{}>", Self::DEGREE_BOUND)
217 }
218}
219
220delegate_const_transcribable!(BinaryU64Poly<const DEGREE_PLUS_ONE: usize>(u64));
221
222impl<const DEGREE_PLUS_ONE: usize> FromRef<BinaryU64Poly<DEGREE_PLUS_ONE>>
223 for BinaryU64Poly<DEGREE_PLUS_ONE>
224{
225 #[inline(always)]
226 fn from_ref(poly: &BinaryU64Poly<DEGREE_PLUS_ONE>) -> Self {
227 poly.clone()
228 }
229}
230
231impl<const DEGREE_PLUS_ONE: usize> From<&BinaryU64Poly<DEGREE_PLUS_ONE>>
232 for BinaryU64Poly<DEGREE_PLUS_ONE>
233{
234 #[inline(always)]
235 fn from(value: &BinaryU64Poly<DEGREE_PLUS_ONE>) -> Self {
236 Self::from_ref(value)
237 }
238}
239
240pub struct BinaryU64PolyIter<'a, const DEGREE_PLUS_ONE: usize> {
241 i: usize,
242 poly: &'a BinaryU64Poly<DEGREE_PLUS_ONE>,
243}
244
245impl<'a, const DEGREE_PLUS_ONE: usize> BinaryU64PolyIter<'a, DEGREE_PLUS_ONE> {
246 pub fn new(poly: &'a BinaryU64Poly<DEGREE_PLUS_ONE>) -> Self {
247 Self { i: 0, poly }
248 }
249}
250
251impl<'a, const DEGREE_PLUS_ONE: usize> Iterator for BinaryU64PolyIter<'a, DEGREE_PLUS_ONE> {
252 type Item = Boolean;
253
254 #[allow(clippy::arithmetic_side_effects)]
255 fn next(&mut self) -> Option<Self::Item> {
256 if self.i < DEGREE_PLUS_ONE {
257 let i = self.i;
258
259 self.i += 1;
260
261 Some((!(self.poly.0 & (1 << i)).is_zero()).into())
262 } else {
263 None
264 }
265 }
266}
267
268impl<const DEGREE_PLUS_ONE: usize> BinaryU64Poly<DEGREE_PLUS_ONE> {
269 pub fn iter(&self) -> BinaryU64PolyIter<'_, DEGREE_PLUS_ONE> {
270 BinaryU64PolyIter::new(self)
271 }
272}
273
274#[derive(Clone, Debug)]
275pub struct BinaryU64PolyInnerProduct<R, const DEGREE_PLUS_ONE: usize>(PhantomData<R>);
276
277impl<C, Rhs, Out, const DEGREE_PLUS_ONE: usize>
278 InnerProduct<C, BinaryU64Poly<DEGREE_PLUS_ONE>, Rhs, Out>
279 for BinaryU64PolyInnerProduct<Rhs, DEGREE_PLUS_ONE>
280where
281 Rhs: Clone,
282 Out: FromRef<Rhs> + CheckedAdd,
283{
284 #[inline(always)]
285 #[allow(clippy::arithmetic_side_effects)] fn inner_product<const CHECK: bool>(
287 _cfg: &C,
288 lhs: &BinaryU64Poly<DEGREE_PLUS_ONE>,
289 rhs: &[Rhs],
290 zero: Out,
291 ) -> Result<Out, InnerProductError> {
292 if rhs.len() != DEGREE_PLUS_ONE {
293 return Err(InnerProductError::LengthMismatch {
294 lhs: DEGREE_PLUS_ONE,
295 rhs: rhs.len(),
296 });
297 }
298
299 let mut acc = zero;
300 let mut bits = lhs.0;
301 while bits != 0 {
302 let i = bits.trailing_zeros() as usize;
303 let rhs = Out::from_ref(&rhs[i]);
304 if CHECK {
305 acc = acc.checked_add(&rhs).ok_or(InnerProductError::Overflow)?;
306 } else {
307 acc = acc + rhs;
308 }
309 bits &= bits - 1;
311 }
312
313 Ok(acc)
314 }
315}
316
317impl<C, const DEGREE_PLUS_ONE: usize> ProjectableToField<C> for BinaryU64Poly<DEGREE_PLUS_ONE>
318where
319 C: FieldConfig + 'static,
320{
321 fn prepare_projection(cfg: &C, sampled_value: &C::Element) -> impl Fn(&Self) -> C::Element {
322 prepare_projection::<C, Self, _, DEGREE_PLUS_ONE>(cfg, sampled_value, |poly, i| {
323 (poly.0 & (1 << i)) != 0
324 })
325 }
326}
327
328impl<const DEGREE_PLUS_ONE: usize> MulByScalar<i64, DensePolynomial<i64, DEGREE_PLUS_ONE>>
329 for BinaryU64Poly<DEGREE_PLUS_ONE>
330{
331 fn mul_by_scalar<const CHECK: bool>(
332 self,
333 rhs: &i64,
334 ) -> Option<DensePolynomial<i64, DEGREE_PLUS_ONE>> {
335 Some(widen_simd::<DEGREE_PLUS_ONE>(&self, *rhs))
336 }
337}
338
339#[allow(unreachable_code, unused_variables)] #[inline(always)]
341pub fn widen_simd<const DEGREE_PLUS_ONE: usize>(
342 poly: &BinaryU64Poly<DEGREE_PLUS_ONE>,
343 scalar: i64,
344) -> DensePolynomial<i64, DEGREE_PLUS_ONE> {
345 let mut coeffs_uninit = MaybeUninit::<[i64; DEGREE_PLUS_ONE]>::uninit();
346 let out_ptr = coeffs_uninit.as_mut_ptr() as *mut i64;
347
348 #[cfg(target_arch = "aarch64")]
349 unsafe {
350 widen_fill_neon::<DEGREE_PLUS_ONE>(&poly.0, out_ptr, scalar);
351 }
352 #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
353 unsafe {
354 widen_fill_avx512::<DEGREE_PLUS_ONE>(&poly.0, out_ptr, scalar);
355 }
356 #[cfg(not(any(
357 target_arch = "aarch64",
358 all(target_arch = "x86_64", target_feature = "avx512f")
359 )))]
360 {
361 panic!("SIMD widening not supported on this architecture");
362 }
363
364 let coeffs = unsafe { coeffs_uninit.assume_init() };
365 DensePolynomial { coeffs }
366}
367
368#[allow(
369 clippy::arithmetic_side_effects,
370 clippy::cast_possible_truncation,
371 clippy::cast_lossless
372)]
373#[allow(unsafe_op_in_unsafe_fn)]
374#[cfg(target_arch = "aarch64")]
375#[inline(always)]
376unsafe fn widen_fill_neon<const N: usize>(mask_ref: &u64, out_ptr: *mut i64, scalar: i64) {
379 use core::arch::aarch64::*;
380
381 let mask64: u64 = *mask_ref;
382
383 let scalar_v: int64x2_t = vdupq_n_s64(scalar);
385
386 let mut i = 0usize;
387
388 if N == 64 {
390 #[repr(align(64))]
394 struct LookupTable([[i64; 8]; 256]);
395
396 static LUT: LookupTable = {
397 let mut table = [[0i64; 8]; 256];
398 let mut i = 0;
399 while i < 256 {
400 let mut bit = 0;
401 while bit < 8 {
402 table[i][bit] = if (i & (1 << bit)) != 0 { -1 } else { 0 };
403 bit += 1;
404 }
405 i += 1;
406 }
407 LookupTable(table)
408 };
409
410 while i + 8 <= N {
412 let shift = i as u32;
413 let byte: u8 = ((mask64 >> shift) & 0xFF) as u8;
414
415 let masks_ptr = LUT.0[byte as usize].as_ptr();
417 let m0: int64x2_t = vld1q_s64(masks_ptr);
418 let m1: int64x2_t = vld1q_s64(masks_ptr.add(2));
419 let m2: int64x2_t = vld1q_s64(masks_ptr.add(4));
420 let m3: int64x2_t = vld1q_s64(masks_ptr.add(6));
421
422 vst1q_s64(out_ptr.add(i), vandq_s64(scalar_v, m0));
424 vst1q_s64(out_ptr.add(i + 2), vandq_s64(scalar_v, m1));
425 vst1q_s64(out_ptr.add(i + 4), vandq_s64(scalar_v, m2));
426 vst1q_s64(out_ptr.add(i + 6), vandq_s64(scalar_v, m3));
427
428 i += 8;
429 }
430 } else {
431 const BIT_MASKS: [u8; 8] = [1, 2, 4, 8, 16, 32, 64, 128];
433 let bit_masks: uint8x8_t = vld1_u8(BIT_MASKS.as_ptr());
434 let zero_u8: uint8x8_t = vdup_n_u8(0);
435
436 while i + 8 <= N {
438 let shift = i as u32;
439 let byte: u8 = ((mask64 >> shift) & 0xFF) as u8;
440
441 let vbyte: uint8x8_t = vdup_n_u8(byte);
443 let selected: uint8x8_t = vand_u8(vbyte, bit_masks);
444 let nz: uint8x8_t = vcgt_u8(selected, zero_u8); let nz_signed: int8x8_t = vreinterpret_s8_u8(nz); let s16: int16x8_t = vmovl_s8(nz_signed); let lo32: int32x4_t = vmovl_s16(vget_low_s16(s16));
450 let hi32: int32x4_t = vmovl_s16(vget_high_s16(s16));
451
452 let m0: int64x2_t = vmovl_s32(vget_low_s32(lo32)); let m1: int64x2_t = vmovl_s32(vget_high_s32(lo32));
454 let m2: int64x2_t = vmovl_s32(vget_low_s32(hi32));
455 let m3: int64x2_t = vmovl_s32(vget_high_s32(hi32));
456
457 vst1q_s64(out_ptr.add(i), vandq_s64(scalar_v, m0));
459 vst1q_s64(out_ptr.add(i + 2), vandq_s64(scalar_v, m1));
460 vst1q_s64(out_ptr.add(i + 4), vandq_s64(scalar_v, m2));
461 vst1q_s64(out_ptr.add(i + 6), vandq_s64(scalar_v, m3));
462
463 i += 8;
464 }
465 }
466
467 while i < N {
469 let bit = ((mask64 >> i) & 1) != 0;
470 let mask = -(bit as i64); *out_ptr.add(i) = scalar & mask;
472 i += 1;
473 }
474}
475
476#[allow(
477 clippy::arithmetic_side_effects,
478 clippy::cast_possible_truncation,
479 clippy::cast_lossless
480)]
481#[allow(unsafe_op_in_unsafe_fn)]
482#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
483#[inline(always)]
484unsafe fn widen_fill_avx512<const N: usize>(mask_ref: &u64, out_ptr: *mut i64, scalar: i64) {
489 #[cfg(target_arch = "x86_64")]
490 use core::arch::x86_64::*;
491
492 let mask64: u64 = *mask_ref;
493 let mut i = 0usize;
494
495 while i + 32 <= N {
498 let shift = i as u32;
499
500 let byte0: u8 = ((mask64 >> shift) & 0xFF) as u8;
502 let byte1: u8 = ((mask64 >> (shift + 8)) & 0xFF) as u8;
503 let byte2: u8 = ((mask64 >> (shift + 16)) & 0xFF) as u8;
504 let byte3: u8 = ((mask64 >> (shift + 24)) & 0xFF) as u8;
505
506 let kmask0: __mmask8 = byte0;
508 let kmask1: __mmask8 = byte1;
509 let kmask2: __mmask8 = byte2;
510 let kmask3: __mmask8 = byte3;
511
512 let result0: __m512i = _mm512_maskz_set1_epi64(kmask0, scalar);
514 let result1: __m512i = _mm512_maskz_set1_epi64(kmask1, scalar);
515 let result2: __m512i = _mm512_maskz_set1_epi64(kmask2, scalar);
516 let result3: __m512i = _mm512_maskz_set1_epi64(kmask3, scalar);
517
518 _mm512_storeu_si512(out_ptr.add(i) as *mut __m512i, result0);
520 _mm512_storeu_si512(out_ptr.add(i + 8) as *mut __m512i, result1);
521 _mm512_storeu_si512(out_ptr.add(i + 16) as *mut __m512i, result2);
522 _mm512_storeu_si512(out_ptr.add(i + 24) as *mut __m512i, result3);
523
524 i += 32;
525 }
526
527 while i + 8 <= N {
529 let shift = i as u32;
530 let byte: u8 = ((mask64 >> shift) & 0xFF) as u8;
531 let kmask: __mmask8 = byte;
532 let result: __m512i = _mm512_maskz_set1_epi64(kmask, scalar);
533 _mm512_storeu_si512(out_ptr.add(i) as *mut __m512i, result);
534 i += 8;
535 }
536
537 while i < N {
539 let bit = ((mask64 >> i) & 1) != 0;
540 let mask = -(bit as i64); *out_ptr.add(i) = scalar & mask;
542 i += 1;
543 }
544}
545
546#[cfg(test)]
547mod tests {
548 use crate::univariate::binary_ref::BinaryRefPoly;
549 use zinc_transcript::traits::{ConstTranscribable, GenTranscribable};
550 use zinc_utils::CHECKED;
551
552 use super::*;
553
554 #[test]
555 fn evaluate_is_correct() {
556 for i in 0..16 {
557 let poly = BinaryU64Poly::<4>::new([
558 (i & 0b0001 != 0).into(),
559 (i & 0b0010 != 0).into(),
560 (i & 0b0100 != 0).into(),
561 (i & 0b1000 != 0).into(),
562 ]);
563
564 let result = poly.evaluate_at_point(&2).unwrap();
565
566 assert_eq!(result, i);
567 }
568 }
569
570 #[test]
571 fn transcribable() {
572 let original =
573 BinaryU64Poly::<4>::new([true.into(), false.into(), true.into(), false.into()]);
574 let mut bytes = [0u8; BinaryU64Poly::<4>::NUM_BYTES];
575 assert_eq!(bytes.len(), u64::NUM_BYTES);
576
577 original.write_transcription_bytes_exact(&mut bytes);
578 let deserialized = BinaryU64Poly::<4>::read_transcription_bytes_exact(&bytes);
579 assert_eq!(original, deserialized);
580 }
581
582 fn widen_ref<const DEGREE_PLUS_ONE: usize>(
583 poly: &BinaryU64Poly<DEGREE_PLUS_ONE>,
584 scalar: i64,
585 ) -> DensePolynomial<i64, DEGREE_PLUS_ONE> {
586 let mut coeffs: [i64; DEGREE_PLUS_ONE] = [0; DEGREE_PLUS_ONE];
587 for (i, coeff) in coeffs.iter_mut().enumerate().take(DEGREE_PLUS_ONE) {
588 if (poly.0 & (1 << i)) != 0 {
589 *coeff = scalar;
590 }
591 }
592 DensePolynomial { coeffs }
593 }
594
595 #[ignore = "CI system does not support SIMD features"]
596 #[test]
597 fn widen_ref_and_widen_ref_simd_match() {
598 for i in 0..16 {
600 let poly = BinaryU64Poly::<4>::new([
601 (i & 0b0001 != 0).into(),
602 (i & 0b0010 != 0).into(),
603 (i & 0b0100 != 0).into(),
604 (i & 0b1000 != 0).into(),
605 ]);
606
607 let poly_ref = BinaryRefPoly::<4>::new([
608 (i & 0b0001 != 0).into(),
609 (i & 0b0010 != 0).into(),
610 (i & 0b0100 != 0).into(),
611 (i & 0b1000 != 0).into(),
612 ]);
613
614 for scalar in [1, 42, -7, 100, -100, i64::MAX, i64::MIN] {
615 let result_simd_ref = widen_ref(&poly, scalar);
616 let result_simd = widen_simd(&poly, scalar);
617 let result_ref = poly_ref.clone().mul_by_scalar::<CHECKED>(&scalar).unwrap();
618
619 assert_eq!(
620 result_simd_ref.coeffs, result_simd.coeffs,
621 "Mismatch for pattern {} with scalar {}",
622 i, scalar
623 );
624 assert_eq!(
625 result_simd_ref.coeffs, result_ref.coeffs,
626 "Mismatch for pattern {} with scalar {} between ref and MulByScalar",
627 i, scalar
628 );
629 }
630 }
631
632 let coeffs: Vec<_> = [
633 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1,
634 0, 1, 0,
635 ]
636 .into_iter()
637 .map(|x| (x != 0).into())
638 .collect();
639
640 let poly32 = BinaryU64Poly::<32>::new_padded(coeffs.clone());
641 let poly32_ref = BinaryRefPoly::<32>::new_padded(coeffs);
642
643 for scalar in [1, 42, -7, 100, -100] {
644 let result_simd_ref = widen_ref(&poly32, scalar);
645 let result_simd = widen_simd(&poly32, scalar);
646 let result_ref = poly32_ref
647 .clone()
648 .mul_by_scalar::<CHECKED>(&scalar)
649 .unwrap();
650 assert_eq!(
651 result_simd_ref.coeffs, result_simd.coeffs,
652 "Mismatch for degree 32 with scalar {}",
653 scalar
654 );
655 assert_eq!(
656 result_simd_ref.coeffs, result_ref.coeffs,
657 "Mismatch for degree 32 with scalar {} between ref and MulByScalar",
658 scalar
659 );
660 }
661
662 let poly_zeros = BinaryU64Poly::<16>::new([false.into(); 16]);
664 let result_ref_zeros = widen_ref(&poly_zeros, 42);
665 let result_simd_zeros = widen_simd(&poly_zeros, 42);
666 assert_eq!(result_ref_zeros.coeffs, result_simd_zeros.coeffs);
667
668 let poly_ones = BinaryU64Poly::<16>::new([true.into(); 16]);
670 let result_ref_ones = widen_ref(&poly_ones, 42);
671 let result_simd_ones = widen_simd(&poly_ones, 42);
672 assert_eq!(result_ref_ones.coeffs, result_simd_ones.coeffs);
673 }
674}