1use alloc::vec;
2use alloc::vec::Vec;
3use core::ops::Div;
4
5use p3_field::extension::{
6 BinomialExtensionField, BinomiallyExtendable, PackedBinomialExtensionField,
7};
8use p3_field::integers::QuotientMap;
9use p3_field::{
10 Algebra, BasedVectorSpace, ExtensionField, Field, PackedField, PackedFieldExtension,
11 PackedFieldPow2, PackedValue, PrimeCharacteristicRing, PrimeField32,
12};
13use proptest::prelude::*;
14use rand::distr::{Distribution, StandardUniform};
15use rand::rngs::SmallRng;
16use rand::{RngExt, SeedableRng};
17
18fn packed_from_random<PV>(seed: u64) -> PV
19where
20 PV: PackedValue,
21 StandardUniform: Distribution<PV::Value>,
22{
23 let mut rng = SmallRng::seed_from_u64(seed);
24 PV::from_fn(|_| rng.random())
25}
26
27fn interleave<T: Copy + Default>(arr1: &[T], arr2: &[T], i: usize) -> (Vec<T>, Vec<T>) {
29 let width = arr1.len();
30 assert_eq!(width, arr2.len());
31 assert_eq!(width % i, 0);
32
33 if i == width {
34 return (arr1.to_vec(), arr2.to_vec());
35 }
36
37 let mut outleft = vec![T::default(); width];
38 let mut outright = vec![T::default(); width];
39
40 let mut flag = false;
41
42 for j in 0..width {
43 if j.is_multiple_of(i) {
44 flag = !flag;
45 }
46 if flag {
47 outleft[j] = arr1[j];
48 outleft[j + i] = arr2[j];
49 } else {
50 outright[j - i] = arr1[j];
51 outright[j] = arr2[j];
52 }
53 }
54
55 (outleft, outright)
56}
57
58fn test_interleave<PF>(i: usize)
59where
60 PF: PackedFieldPow2 + Eq,
61 StandardUniform: Distribution<PF::Scalar>,
62{
63 assert!(PF::WIDTH.is_multiple_of(i));
64
65 let vec1 = packed_from_random::<PF>(0x4ff5dec04791e481);
66 let vec2 = packed_from_random::<PF>(0x5806c495e9451f8e);
67
68 let arr1 = vec1.as_slice();
69 let arr2 = vec2.as_slice();
70
71 let (res1, res2) = vec1.interleave(vec2, i);
72 let (out1, out2) = interleave(arr1, arr2, i);
73
74 assert_eq!(
75 res1.as_slice(),
76 &out1,
77 "Error in left output when testing interleave {i}. Data is: \n {arr1:?} \n {arr2:?} \n {res1:?} \n {res2:?} \n {out1:?} \n {out2:?}.",
78 );
79 assert_eq!(
80 res2.as_slice(),
81 &out2,
82 "Error in right output when testing interleave {i}.",
83 );
84}
85
86pub fn test_interleaves<PF>()
87where
88 PF: PackedFieldPow2 + Eq,
89 StandardUniform: Distribution<PF::Scalar>,
90{
91 let mut i = 1;
92 while i <= PF::WIDTH {
93 test_interleave::<PF>(i);
94 i *= 2;
95 }
96}
97
98pub fn test_packed_mixed_dot_product<PF: PackedField + Eq>()
99where
100 StandardUniform: Distribution<PF> + Distribution<PF::Scalar>,
101{
102 let mut rng = SmallRng::seed_from_u64(42);
103 let a: [PF; 64] = rng.random();
104 let f: [PF::Scalar; 64] = rng.random();
105
106 let mut dot = PF::ZERO;
107 assert_eq!(
108 dot,
109 PF::mixed_dot_product::<0>(a[..0].try_into().unwrap(), f[..0].try_into().unwrap())
110 );
111 dot += a[0] * f[0];
112 assert_eq!(
113 dot,
114 PF::mixed_dot_product::<1>(a[..1].try_into().unwrap(), f[..1].try_into().unwrap())
115 );
116 dot += a[1] * f[1];
117 assert_eq!(
118 dot,
119 PF::mixed_dot_product::<2>(a[..2].try_into().unwrap(), f[..2].try_into().unwrap())
120 );
121 dot += a[2] * f[2];
122 assert_eq!(
123 dot,
124 PF::mixed_dot_product::<3>(a[..3].try_into().unwrap(), f[..3].try_into().unwrap())
125 );
126 dot += a[3] * f[3];
127 assert_eq!(
128 dot,
129 PF::mixed_dot_product::<4>(a[..4].try_into().unwrap(), f[..4].try_into().unwrap())
130 );
131 dot += a[4] * f[4];
132 assert_eq!(
133 dot,
134 PF::mixed_dot_product::<5>(a[..5].try_into().unwrap(), f[..5].try_into().unwrap())
135 );
136 dot += a[5] * f[5];
137 assert_eq!(
138 dot,
139 PF::mixed_dot_product::<6>(a[..6].try_into().unwrap(), f[..6].try_into().unwrap())
140 );
141 dot += a[6] * f[6];
142 assert_eq!(
143 dot,
144 PF::mixed_dot_product::<7>(a[..7].try_into().unwrap(), f[..7].try_into().unwrap())
145 );
146 dot += a[7] * f[7];
147 assert_eq!(
148 dot,
149 PF::mixed_dot_product::<8>(a[..8].try_into().unwrap(), f[..8].try_into().unwrap())
150 );
151 dot += a[8] * f[8];
152 assert_eq!(
153 dot,
154 PF::mixed_dot_product::<9>(a[..9].try_into().unwrap(), f[..9].try_into().unwrap())
155 );
156 dot += a[9] * f[9];
157 assert_eq!(
158 dot,
159 PF::mixed_dot_product::<10>(a[..10].try_into().unwrap(), f[..10].try_into().unwrap())
160 );
161 dot += a[10] * f[10];
162 assert_eq!(
163 dot,
164 PF::mixed_dot_product::<11>(a[..11].try_into().unwrap(), f[..11].try_into().unwrap())
165 );
166 dot += a[11] * f[11];
167 assert_eq!(
168 dot,
169 PF::mixed_dot_product::<12>(a[..12].try_into().unwrap(), f[..12].try_into().unwrap())
170 );
171 dot += a[12] * f[12];
172 assert_eq!(
173 dot,
174 PF::mixed_dot_product::<13>(a[..13].try_into().unwrap(), f[..13].try_into().unwrap())
175 );
176 dot += a[13] * f[13];
177 assert_eq!(
178 dot,
179 PF::mixed_dot_product::<14>(a[..14].try_into().unwrap(), f[..14].try_into().unwrap())
180 );
181 dot += a[14] * f[14];
182 assert_eq!(
183 dot,
184 PF::mixed_dot_product::<15>(a[..15].try_into().unwrap(), f[..15].try_into().unwrap())
185 );
186 dot += a[15] * f[15];
187 assert_eq!(
188 dot,
189 PF::mixed_dot_product::<16>(a[..16].try_into().unwrap(), f[..16].try_into().unwrap())
190 );
191
192 let dot_64: PF = a
193 .iter()
194 .zip(f.iter())
195 .fold(PF::ZERO, |acc, (&ai, &fi)| acc + (ai * fi));
196 assert_eq!(dot_64, PF::mixed_dot_product::<64>(&a, &f));
197}
198
199pub fn test_batched_linear_combination<PF: PackedField + Eq>()
200where
201 StandardUniform: Distribution<PF> + Distribution<PF::Scalar>,
202{
203 let mut rng = SmallRng::seed_from_u64(99);
204 let values: [PF; 64] = rng.random();
205 let coeffs: [PF::Scalar; 64] = rng.random();
206
207 for len in [0, 1, 3, 7, 8, 9, 15, 16, 17, 32, 64] {
208 let expected: PF = values[..len]
209 .iter()
210 .zip(&coeffs[..len])
211 .fold(PF::ZERO, |acc, (&v, &c)| acc + v * c);
212 let got = PF::batched_linear_combination(&values[..len], &coeffs[..len]);
213 assert_eq!(expected, got, "failed for len={len}");
214 }
215}
216
217pub fn test_batched_linear_combination_ext<BF, EF, PE>()
218where
219 BF: Field,
220 EF: ExtensionField<BF, ExtensionPacking = PE>,
221 PE: PackedFieldExtension<BF, EF> + Algebra<EF> + Copy + Eq,
222 StandardUniform: Distribution<PE> + Distribution<EF>,
223{
224 let mut rng = SmallRng::seed_from_u64(99);
225 let values: [PE; 64] = rng.random();
226 let coeffs: [EF; 64] = rng.random();
227
228 for len in [0, 1, 3, 7, 8, 9, 15, 16, 17, 32, 64] {
229 let expected: PE = values[..len]
230 .iter()
231 .zip(&coeffs[..len])
232 .fold(PE::ZERO, |acc, (&v, &c)| acc + v * c);
233 let got = PE::batched_linear_combination(&values[..len], &coeffs[..len]);
234 assert_eq!(expected, got, "failed for len={len}");
235 }
236}
237
238pub fn test_add_assign_lane_ext<BF, EF, PE>()
245where
246 BF: Field,
247 EF: ExtensionField<BF, ExtensionPacking = PE>,
248 PE: PackedFieldExtension<BF, EF> + Copy + Eq,
249 StandardUniform: Distribution<PE> + Distribution<EF>,
250{
251 let mut rng = SmallRng::seed_from_u64(0xa55e_3b1c);
252 let width = BF::Packing::WIDTH;
253
254 for _ in 0..32 {
256 let start: PE = rng.random();
257 let value: EF = rng.random();
258 for lane in 0..width {
260 let mut fast = start;
262 fast.add_assign_lane(lane, value);
263
264 let reference = start + PE::from_ext_fn(|l| if l == lane { value } else { EF::ZERO });
266 assert_eq!(fast, reference, "lane {lane} mismatch");
267
268 for other in 0..width {
271 let expected = start.extract(other) + if other == lane { value } else { EF::ZERO };
272 assert_eq!(fast.extract(other), expected, "leaked into lane {other}");
273 }
274 }
275 }
276}
277
278pub fn test_packed_binomial_extension_division<F, const D: usize>()
289where
290 F: BinomiallyExtendable<D>,
291 StandardUniform: Distribution<BinomialExtensionField<F, D>>,
292{
293 let mut rng = SmallRng::seed_from_u64(0x04dd6059d9d02758);
295 let width = F::Packing::WIDTH;
297
298 let numerators: Vec<BinomialExtensionField<F, D>> = (0..width).map(|_| rng.random()).collect();
300 let mut sample_nonzero = || loop {
302 let x: BinomialExtensionField<F, D> = rng.random();
303 if !x.is_zero() {
304 break x;
305 }
306 };
307 let denominators: Vec<BinomialExtensionField<F, D>> =
308 (0..width).map(|_| sample_nonzero()).collect();
309
310 let packed_num = PackedBinomialExtensionField::<F, F::Packing, D>::from_ext_slice(&numerators);
312 let packed_den =
313 PackedBinomialExtensionField::<F, F::Packing, D>::from_ext_slice(&denominators);
314
315 let extract_lane = |x: &PackedBinomialExtensionField<F, F::Packing, D>, lane: usize| {
319 BinomialExtensionField::<F, D>::new(core::array::from_fn(|i| {
320 <PackedBinomialExtensionField<F, F::Packing, D> as BasedVectorSpace<F::Packing>>::as_basis_coefficients_slice(x)[i]
321 .as_slice()[lane]
322 }))
323 };
324
325 let quot = packed_num / packed_den;
328 for lane in 0..width {
329 assert_eq!(
330 extract_lane(", lane),
331 numerators[lane] / denominators[lane],
332 "packed/packed division mismatch at lane {lane}"
333 );
334 }
335
336 let mut quot_assign = packed_num;
339 quot_assign /= packed_den;
340 for lane in 0..width {
341 assert_eq!(
342 extract_lane("_assign, lane),
343 extract_lane(", lane),
344 "packed/packed div_assign mismatch at lane {lane}"
345 );
346 }
347
348 let scalar_den = sample_nonzero();
351 let quot_scalar = packed_num / scalar_den;
352 for (lane, numerator) in numerators.iter().enumerate() {
353 assert_eq!(
354 extract_lane("_scalar, lane),
355 *numerator / scalar_den,
356 "packed/scalar division mismatch at lane {lane}"
357 );
358 }
359
360 let mut quot_scalar_assign = packed_num;
363 quot_scalar_assign /= scalar_den;
364 for lane in 0..width {
365 assert_eq!(
366 extract_lane("_scalar_assign, lane),
367 extract_lane("_scalar, lane),
368 "packed/scalar div_assign mismatch at lane {lane}"
369 );
370 }
371}
372
373pub fn test_packed_extension_div_consistency<F, EF, PEF>()
383where
384 F: Field,
385 EF: ExtensionField<F, ExtensionPacking = PEF>,
386 PEF: PackedFieldExtension<F, EF> + Div<Output = PEF> + Copy,
387 StandardUniform: Distribution<EF>,
388{
389 let width = F::Packing::WIDTH;
394
395 let mut rng = SmallRng::seed_from_u64(0x_d1ef_d1ef_d1ef_d1ef);
397
398 let nums: Vec<EF> = (0..width).map(|_| rng.random()).collect();
403
404 let dens: Vec<EF> = (0..width)
409 .map(|_| {
410 loop {
411 let x: EF = rng.random();
412 if !x.is_zero() {
413 break x;
414 }
415 }
416 })
417 .collect();
418
419 let pef_n: PEF = PEF::from_ext_slice(&nums);
424 let pef_d: PEF = PEF::from_ext_slice(&dens);
425
426 let pef_q = pef_n / pef_d;
428
429 for lane in 0..width {
433 let expected = nums[lane] / dens[lane];
434 let got = pef_q.extract(lane);
435 assert_eq!(
436 got, expected,
437 "lane {lane}: packed Div disagrees with scalar Div (W = {width})"
438 );
439 }
440}
441
442pub fn test_vs_scalar<PF>(special_vals: PF)
443where
444 PF: PackedField + Eq,
445 StandardUniform: Distribution<PF::Scalar>,
446{
447 let vec0: PF = packed_from_random(0x278d9e202925a1d1);
448 let vec1: PF = packed_from_random(0xf04cbac0cbad419f);
449 let vec_special = special_vals;
450
451 let arr0 = vec0.as_slice();
452 let arr1 = vec1.as_slice();
453
454 let vec_sum = vec0 + vec1;
455 let arr_sum = vec_sum.as_slice();
456 let vec_special_sum_left = vec_special + vec0;
457 let arr_special_sum_left = vec_special_sum_left.as_slice();
458 let vec_special_sum_right = vec1 + vec_special;
459 let arr_special_sum_right = vec_special_sum_right.as_slice();
460
461 let vec_sub = vec0 - vec1;
462 let arr_sub = vec_sub.as_slice();
463 let vec_special_sub_left = vec_special - vec0;
464 let arr_special_sub_left = vec_special_sub_left.as_slice();
465 let vec_special_sub_right = vec1 - vec_special;
466 let arr_special_sub_right = vec_special_sub_right.as_slice();
467
468 let vec_mul = vec0 * vec1;
469 let arr_mul = vec_mul.as_slice();
470 let vec_special_mul_left = vec_special * vec0;
471 let arr_special_mul_left = vec_special_mul_left.as_slice();
472 let vec_special_mul_right = vec1 * vec_special;
473 let arr_special_mul_right = vec_special_mul_right.as_slice();
474
475 let vec_div = vec0 / vec1;
476 let arr_div = vec_div.as_slice();
477
478 let vec_neg = -vec0;
479 let arr_neg = vec_neg.as_slice();
480 let vec_special_neg = -vec_special;
481 let arr_special_neg = vec_special_neg.as_slice();
482
483 let vec_exp_3 = vec0.exp_const_u64::<3>();
484 let arr_exp_3 = vec_exp_3.as_slice();
485 let vec_special_exp_3 = vec_special.exp_const_u64::<3>();
486 let arr_special_exp_3 = vec_special_exp_3.as_slice();
487
488 let vec_exp_5 = vec0.exp_const_u64::<5>();
489 let arr_exp_5 = vec_exp_5.as_slice();
490 let vec_special_exp_5 = vec_special.exp_const_u64::<5>();
491 let arr_special_exp_5 = vec_special_exp_5.as_slice();
492
493 let vec_exp_7 = vec0.exp_const_u64::<7>();
494 let arr_exp_7 = vec_exp_7.as_slice();
495 let vec_special_exp_7 = vec_special.exp_const_u64::<7>();
496 let arr_special_exp_7 = vec_special_exp_7.as_slice();
497
498 let special_vals = special_vals.as_slice();
499 for i in 0..PF::WIDTH {
500 assert_eq!(
501 arr_sum[i],
502 arr0[i] + arr1[i],
503 "Error when testing add consistency of packed and scalar at location {i}.",
504 );
505 assert_eq!(
506 arr_special_sum_left[i],
507 special_vals[i] + arr0[i],
508 "Error when testing consistency of left add for special values for packed and scalar at location {i}.",
509 );
510 assert_eq!(
511 arr_special_sum_right[i],
512 arr1[i] + special_vals[i],
513 "Error when testing consistency of right add for special values for packed and scalar at location {i}.",
514 );
515
516 assert_eq!(
517 arr_sub[i],
518 arr0[i] - arr1[i],
519 "Error when testing sub consistency of packed and scalar at location {i}.",
520 );
521 assert_eq!(
522 arr_special_sub_left[i],
523 special_vals[i] - arr0[i],
524 "Error when testing consistency of left sub for special values for packed and scalar at location {i}.",
525 );
526 assert_eq!(
527 arr_special_sub_right[i],
528 arr1[i] - special_vals[i],
529 "Error when testing consistency of right sub for special values for packed and scalar at location {i}.",
530 );
531
532 assert_eq!(
533 arr_mul[i],
534 arr0[i] * arr1[i],
535 "Error when testing mul consistency of packed and scalar at location {i}.",
536 );
537 assert_eq!(
538 arr_special_mul_left[i],
539 special_vals[i] * arr0[i],
540 "Error when testing consistency of left mul for special values for packed and scalar at location {i}.",
541 );
542 assert_eq!(
543 arr_special_mul_right[i],
544 arr1[i] * special_vals[i],
545 "Error when testing consistency of right mul for special values for packed and scalar at location {i}.",
546 );
547
548 assert_eq!(
549 arr_div[i],
550 arr0[i] / arr1[i],
551 "Error when testing div consistency of packed and scalar at location {i}.",
552 );
553
554 assert_eq!(
555 arr_neg[i], -arr0[i],
556 "Error when testing neg consistency of packed and scalar at location {i}.",
557 );
558 assert_eq!(
559 arr_special_neg[i], -special_vals[i],
560 "Error when testing consistency of neg for special values for packed and scalar at location {i}.",
561 );
562 assert_eq!(
563 arr_exp_3[i],
564 arr0[i].exp_const_u64::<3>(),
565 "Error when testing exp_const_u64::<3> consistency of packed and scalar at location {i}.",
566 );
567 assert_eq!(
568 arr_special_exp_3[i],
569 special_vals[i].exp_const_u64::<3>(),
570 "Error when testing consistency of exp_const_u64::<3> for special values for packed and scalar at location {i}.",
571 );
572 assert_eq!(
573 arr_exp_5[i],
574 arr0[i].exp_const_u64::<5>(),
575 "Error when testing exp_const_u64::<5> consistency of packed and scalar at location {i}.",
576 );
577 assert_eq!(
578 arr_special_exp_5[i],
579 special_vals[i].exp_const_u64::<5>(),
580 "Error when testing consistency of exp_const_u64::<5> for special values for packed and scalar at location {i}.",
581 );
582 assert_eq!(
583 arr_exp_7[i],
584 arr0[i].exp_const_u64::<7>(),
585 "Error when testing exp_const_u64::<7> consistency of packed and scalar at location {i}.",
586 );
587 assert_eq!(
588 arr_special_exp_7[i],
589 special_vals[i].exp_const_u64::<7>(),
590 "Error when testing consistency of exp_const_u64::<7> for special values for packed and scalar at location {i}.",
591 );
592 }
593}
594
595pub fn test_multiplicative_inverse<PF>()
596where
597 PF: PackedField + Eq,
598 StandardUniform: Distribution<PF::Scalar>,
599{
600 let sampled: PF = packed_from_random(0xb0c7a5153103c5a8);
601
602 let vec = PF::from_fn(|i| {
605 let lane = sampled.as_slice()[i];
606 if lane.is_zero() {
607 PF::Scalar::ONE
608 } else {
609 lane
610 }
611 });
612 let arr = vec.as_slice();
613 let vec_inv = PF::from_fn(|i| arr[i].inverse());
614 let res = vec * vec_inv;
615 assert_eq!(
616 res,
617 PF::ONE,
618 "Error when testing multiplication by inverse."
619 );
620}
621
622pub fn test_broadcast<PF>()
624where
625 PF: PackedField + Eq,
626 StandardUniform: Distribution<PF::Scalar>,
627{
628 let mut rng = SmallRng::seed_from_u64(0xdeadbeef);
629 let x: PF::Scalar = rng.random();
630 let packed = PF::broadcast(x);
631 for lane in 0..PF::WIDTH {
632 assert_eq!(
633 packed.as_slice()[lane],
634 x,
635 "broadcast mismatch at lane {lane}"
636 );
637 }
638
639 let zero = PF::broadcast(PF::Scalar::default());
640 assert_eq!(zero, PF::ZERO, "broadcast(default) should equal ZERO");
641}
642
643pub fn test_pack_columns<PF>()
644where
645 PF: PackedField + Eq,
646 StandardUniform: Distribution<PF::Scalar>,
647{
648 let mut rng = SmallRng::seed_from_u64(0xc0ffee42);
649
650 let rows: Vec<[PF::Scalar; 4]> = (0..PF::WIDTH)
652 .map(|_| [rng.random(), rng.random(), rng.random(), rng.random()])
653 .collect();
654 let packed = PF::pack_columns::<4>(&rows);
655 let mut unpacked = vec![[PF::Scalar::default(); 4]; PF::WIDTH];
656 PF::unpack_into(&packed, &mut unpacked);
657 assert_eq!(
658 rows, unpacked,
659 "pack_columns -> unpack_into round-trip failed"
660 );
661
662 let original: [PF; 4] = [
664 packed_from_random(0x1111),
665 packed_from_random(0x2222),
666 packed_from_random(0x3333),
667 packed_from_random(0x4444),
668 ];
669 let mut rows2 = vec![[PF::Scalar::default(); 4]; PF::WIDTH];
670 PF::unpack_into(&original, &mut rows2);
671 let repacked = PF::pack_columns::<4>(&rows2);
672 assert_eq!(
673 original, repacked,
674 "unpack_into -> pack_columns round-trip failed"
675 );
676}
677
678pub fn test_pack_columns_fn<PF>()
679where
680 PF: PackedField + Eq,
681 StandardUniform: Distribution<PF::Scalar>,
682{
683 let mut rng = SmallRng::seed_from_u64(0xbaadf00d);
684 let rows: Vec<[PF::Scalar; 4]> = (0..PF::WIDTH)
685 .map(|_| [rng.random(), rng.random(), rng.random(), rng.random()])
686 .collect();
687
688 let from_slice = PF::pack_columns::<4>(&rows);
689 let from_fn = PF::pack_columns_fn(|lane| rows[lane]);
690 assert_eq!(
691 from_slice, from_fn,
692 "pack_columns_fn should match pack_columns"
693 );
694}
695
696pub fn test_unpack_iter<PF>()
697where
698 PF: PackedField + Eq,
699 StandardUniform: Distribution<PF::Scalar>,
700{
701 let packed: [PF; 4] = [
702 packed_from_random(0xaaaa),
703 packed_from_random(0xbbbb),
704 packed_from_random(0xcccc),
705 packed_from_random(0xdddd),
706 ];
707
708 let mut rows_via_into = vec![[PF::Scalar::default(); 4]; PF::WIDTH];
710 PF::unpack_into(&packed, &mut rows_via_into);
711 let rows_via_iter: Vec<[PF::Scalar; 4]> = PF::unpack_iter(packed).collect();
712 assert_eq!(
713 rows_via_into, rows_via_iter,
714 "unpack_iter should match unpack_into"
715 );
716
717 let repacked = PF::pack_columns::<4>(&rows_via_iter);
719 assert_eq!(
720 packed, repacked,
721 "unpack_iter -> pack_columns round-trip failed"
722 );
723}
724
725pub fn test_dot_product_boundary<PF>()
730where
731 PF: PackedField + Eq,
732{
733 let big = PF::from(PF::Scalar::NEG_ONE);
734 let scalar_big = PF::Scalar::NEG_ONE;
735
736 macro_rules! test_dot_n {
738 ($n:literal) => {
739 let packed_result = PF::dot_product::<$n>(&[big; $n], &[big; $n]);
740 let scalar_result = PF::Scalar::dot_product::<$n>(&[scalar_big; $n], &[scalar_big; $n]);
741 for lane in 0..PF::WIDTH {
742 assert_eq!(
743 packed_result.as_slice()[lane],
744 scalar_result,
745 "dot_product::<{}> overflow mismatch at lane {}",
746 $n,
747 lane,
748 );
749 }
750 };
751 }
752 test_dot_n!(1);
753 test_dot_n!(2);
754 test_dot_n!(3);
755 test_dot_n!(4);
756 test_dot_n!(5);
757 test_dot_n!(6);
758 test_dot_n!(7);
759 test_dot_n!(8);
760 test_dot_n!(9);
761 test_dot_n!(10);
762 test_dot_n!(11);
763 test_dot_n!(12);
764 test_dot_n!(13);
765 test_dot_n!(14);
766 test_dot_n!(15);
767 test_dot_n!(16);
768}
769
770const fn boundary_u32_values<F: PrimeField32>() -> [u32; 8] {
777 let p = F::ORDER_U32;
779 [0, 1, 2, p / 4, p / 2, p / 2 + 1, p - 2, p - 1]
780}
781
782fn u32_array_to_field<F, const N: usize>(values: [u32; N]) -> [F; N]
784where
785 F: PrimeField32 + QuotientMap<u32>,
786{
787 values.map(F::from_int)
788}
789
790pub fn assert_packed_broadcast_dot_product_matches_scalar<PF, const N: usize>(
799 lhs_raw: [u32; N],
800 rhs_raw: [u32; N],
801) where
802 PF: PackedField + Eq,
803 PF::Scalar: PrimeField32 + QuotientMap<u32>,
804{
805 let lhs = u32_array_to_field::<PF::Scalar, N>(lhs_raw);
807 let rhs = u32_array_to_field::<PF::Scalar, N>(rhs_raw);
808
809 let scalar_ref = PF::Scalar::dot_product::<N>(&lhs, &rhs);
811
812 let packed_lhs: [PF; N] = lhs.map(PF::broadcast);
814 let packed_rhs: [PF; N] = rhs.map(PF::broadcast);
815 let packed = PF::dot_product::<N>(&packed_lhs, &packed_rhs);
816
817 for (lane, got) in packed.as_slice().iter().enumerate() {
819 assert_eq!(
820 *got, scalar_ref,
821 "lane {lane}: packed dot_product::<{N}> mismatch — lhs={lhs_raw:?} rhs={rhs_raw:?}",
822 );
823 }
824}
825
826pub fn test_packed_dot_product_broadcast_boundary_sweep<PF, const N: usize>()
830where
831 PF: PackedField + Eq,
832 PF::Scalar: PrimeField32 + QuotientMap<u32>,
833{
834 let edges = boundary_u32_values::<PF::Scalar>();
835
836 for &v in &edges {
837 for &w in &edges {
838 assert_packed_broadcast_dot_product_matches_scalar::<PF, N>([v; N], [w; N]);
840 }
841 }
842}
843
844pub fn test_packed_dot_product_lanes_random<PF, const N: usize>()
849where
850 PF: PackedField + Eq,
851 PF::Scalar: PrimeField32 + QuotientMap<u32>,
852{
853 const ITERS: usize = 1024;
855
856 let p = PF::Scalar::ORDER_U32;
857 let edges = boundary_u32_values::<PF::Scalar>();
858
859 let mut rng = SmallRng::seed_from_u64(0xD07B_0571_0DDE_DEAD);
861
862 for _ in 0..ITERS {
863 let lhs_per_lane: Vec<[PF::Scalar; N]> = (0..PF::WIDTH)
865 .map(|_| {
866 u32_array_to_field::<PF::Scalar, N>(core::array::from_fn(|_| {
867 sample_edge_or_uniform(&mut rng, &edges, p)
868 }))
869 })
870 .collect();
871 let rhs_per_lane: Vec<[PF::Scalar; N]> = (0..PF::WIDTH)
872 .map(|_| {
873 u32_array_to_field::<PF::Scalar, N>(core::array::from_fn(|_| {
874 sample_edge_or_uniform(&mut rng, &edges, p)
875 }))
876 })
877 .collect();
878
879 let packed_lhs = PF::pack_columns_fn::<N>(|lane| lhs_per_lane[lane]);
881 let packed_rhs = PF::pack_columns_fn::<N>(|lane| rhs_per_lane[lane]);
882
883 let result = PF::dot_product::<N>(&packed_lhs, &packed_rhs);
884
885 for lane in 0..PF::WIDTH {
887 let expected = PF::Scalar::dot_product::<N>(&lhs_per_lane[lane], &rhs_per_lane[lane]);
888 assert_eq!(
889 result.as_slice()[lane],
890 expected,
891 "lane {lane}: packed dot_product::<{N}> mismatch (random seed)",
892 );
893 }
894 }
895}
896
897fn monty_radix<F: PrimeField32 + QuotientMap<u32>>() -> F {
905 F::from_int(((1u64 << 32) % F::ORDER_U64) as u32)
906}
907
908fn max_limb_value<F: PrimeField32 + QuotientMap<u32>>() -> u32 {
913 (F::NEG_ONE * monty_radix::<F>().inverse()).as_canonical_u32()
915}
916
917fn unit_limb_value<F: PrimeField32 + QuotientMap<u32>>() -> u32 {
922 monty_radix::<F>().inverse().as_canonical_u32()
924}
925
926pub fn test_packed_dot_product_max_limb<PF, const N: usize>()
934where
935 PF: PackedField + Eq,
936 PF::Scalar: PrimeField32 + QuotientMap<u32>,
937{
938 let max = max_limb_value::<PF::Scalar>();
939
940 assert_packed_broadcast_dot_product_matches_scalar::<PF, N>([max; N], [max; N]);
942
943 for &other in &boundary_u32_values::<PF::Scalar>() {
945 assert_packed_broadcast_dot_product_matches_scalar::<PF, N>([max; N], [other; N]);
946 assert_packed_broadcast_dot_product_matches_scalar::<PF, N>([other; N], [max; N]);
947 }
948
949 let unit = unit_limb_value::<PF::Scalar>();
958 let alternating: [u32; N] = core::array::from_fn(|i| if i % 2 == 0 { max } else { unit });
959 assert_packed_broadcast_dot_product_matches_scalar::<PF, N>(alternating, [max; N]);
960 assert_packed_broadcast_dot_product_matches_scalar::<PF, N>([max; N], alternating);
961}
962
963pub fn test_packed_dot_product_proptest<PF, const N: usize>()
969where
970 PF: PackedField + Eq + 'static,
971 PF::Scalar: PrimeField32 + QuotientMap<u32>,
972{
973 let prime = PF::Scalar::ORDER_U32;
974 let config = ProptestConfig::with_cases(256);
975
976 let draw = prop::collection::vec(0..prime, N);
978
979 proptest!(config, |(lhs in draw.clone(), rhs in draw)| {
980 let lhs: [u32; N] = lhs.try_into().unwrap();
981 let rhs: [u32; N] = rhs.try_into().unwrap();
982 assert_packed_broadcast_dot_product_matches_scalar::<PF, N>(lhs, rhs);
983 });
984}
985
986fn sample_edge_or_uniform(rng: &mut SmallRng, edges: &[u32; 8], prime: u32) -> u32 {
988 if rng.random::<bool>() {
989 edges[(rng.random::<u32>() as usize) % edges.len()]
990 } else {
991 rng.random::<u32>() % prime
992 }
993}
994
995pub fn test_packed_vs_scalar_proptest<PF>()
999where
1000 PF: PackedField + Eq + 'static,
1001 StandardUniform: Distribution<PF::Scalar>,
1002{
1003 let config = ProptestConfig::with_cases(256);
1004 proptest!(config, |(seed_a in any::<u64>(), seed_b in any::<u64>())| {
1005 let mut rng_a = SmallRng::seed_from_u64(seed_a);
1006 let mut rng_b = SmallRng::seed_from_u64(seed_b);
1007 let a = PF::from_fn(|_| rng_a.random());
1008 let b = PF::from_fn(|_| rng_b.random());
1009
1010 let sum = a + b;
1011 let diff = a - b;
1012 let prod = a * b;
1013 let denominator = PF::from_fn(|i| {
1016 let value = b.as_slice()[i];
1017 if value.is_zero() { PF::Scalar::ONE } else { value }
1018 });
1019 let quot = a / denominator;
1020 let neg_a = -a;
1021
1022 let sum = sum.as_slice();
1023 let diff = diff.as_slice();
1024 let prod = prod.as_slice();
1025 let quot = quot.as_slice();
1026 let neg_a = neg_a.as_slice();
1027 let a = a.as_slice();
1028 let b = b.as_slice();
1029
1030 for i in 0..PF::WIDTH {
1031 prop_assert_eq!(sum[i], a[i] + b[i],
1032 "add mismatch at lane {}", i);
1033 prop_assert_eq!(diff[i], a[i] - b[i],
1034 "sub mismatch at lane {}", i);
1035 prop_assert_eq!(prod[i], a[i] * b[i],
1036 "mul mismatch at lane {}", i);
1037 prop_assert_eq!(quot[i], a[i] / denominator.as_slice()[i],
1038 "div mismatch at lane {}", i);
1039 prop_assert_eq!(neg_a[i], -a[i],
1040 "neg mismatch at lane {}", i);
1041 }
1042 });
1043}
1044
1045#[macro_export]
1049macro_rules! test_packed_field_ops {
1050 ($packedfield:ty, $specials:expr) => {
1051 mod packed_field_tests {
1052 use p3_field::PrimeCharacteristicRing;
1053
1054 #[test]
1055 fn test_interleaves() {
1056 $crate::test_interleaves::<$packedfield>();
1057 }
1058 #[test]
1059 fn test_packed_mixed_dot_product() {
1060 $crate::test_packed_mixed_dot_product::<$packedfield>();
1061 }
1062 #[test]
1063 fn test_batched_linear_combination() {
1064 $crate::test_batched_linear_combination::<$packedfield>();
1065 }
1066 #[test]
1067 fn test_vs_scalar() {
1068 $crate::test_vs_scalar::<$packedfield>($specials);
1069 }
1070 #[test]
1071 fn test_multiplicative_inverse() {
1072 $crate::test_multiplicative_inverse::<$packedfield>();
1073 }
1074 #[test]
1075 fn test_dot_product_boundary() {
1076 $crate::test_dot_product_boundary::<$packedfield>();
1077 }
1078 #[test]
1079 fn test_broadcast() {
1080 $crate::test_broadcast::<$packedfield>();
1081 }
1082 #[test]
1083 fn test_pack_columns() {
1084 $crate::test_pack_columns::<$packedfield>();
1085 }
1086 #[test]
1087 fn test_pack_columns_fn() {
1088 $crate::test_pack_columns_fn::<$packedfield>();
1089 }
1090 #[test]
1091 fn test_unpack_iter() {
1092 $crate::test_unpack_iter::<$packedfield>();
1093 }
1094 #[test]
1095 fn test_packed_vs_scalar_proptest() {
1096 $crate::test_packed_vs_scalar_proptest::<$packedfield>();
1097 }
1098 }
1099 };
1100}
1101
1102#[macro_export]
1103macro_rules! test_packed_field {
1104 ($packedfield:ty, $zeros:expr, $ones:expr, $specials:expr) => {
1105 $crate::test_ring_with_eq!($packedfield, $zeros, $ones);
1106 $crate::test_packed_field_ops!($packedfield, $specials);
1107 };
1108}
1109
1110#[macro_export]
1114macro_rules! test_packed_binary_field {
1115 ($packedfield:ty, $zeros:expr, $ones:expr, $specials:expr) => {
1116 $crate::test_ring_with_eq_char2!($packedfield, $zeros, $ones);
1117 $crate::test_packed_field_ops!($packedfield, $specials);
1118 };
1119}
1120
1121#[macro_export]
1135macro_rules! test_packed_field_dot_product_boundary {
1136 ($packedfield:ty) => {
1137 mod packed_dot_product_boundary_tests {
1138 #[test]
1141 fn boundary_sweep_n1() {
1142 $crate::test_packed_dot_product_broadcast_boundary_sweep::<$packedfield, 1>();
1143 }
1144 #[test]
1145 fn boundary_sweep_n2() {
1146 $crate::test_packed_dot_product_broadcast_boundary_sweep::<$packedfield, 2>();
1147 }
1148 #[test]
1149 fn boundary_sweep_n3() {
1150 $crate::test_packed_dot_product_broadcast_boundary_sweep::<$packedfield, 3>();
1151 }
1152 #[test]
1153 fn boundary_sweep_n4() {
1154 $crate::test_packed_dot_product_broadcast_boundary_sweep::<$packedfield, 4>();
1155 }
1156 #[test]
1157 fn boundary_sweep_n5() {
1158 $crate::test_packed_dot_product_broadcast_boundary_sweep::<$packedfield, 5>();
1159 }
1160 #[test]
1161 fn boundary_sweep_n6() {
1162 $crate::test_packed_dot_product_broadcast_boundary_sweep::<$packedfield, 6>();
1163 }
1164 #[test]
1165 fn boundary_sweep_n7() {
1166 $crate::test_packed_dot_product_broadcast_boundary_sweep::<$packedfield, 7>();
1167 }
1168 #[test]
1169 fn boundary_sweep_n8() {
1170 $crate::test_packed_dot_product_broadcast_boundary_sweep::<$packedfield, 8>();
1171 }
1172 #[test]
1173 fn boundary_sweep_n9() {
1174 $crate::test_packed_dot_product_broadcast_boundary_sweep::<$packedfield, 9>();
1175 }
1176 #[test]
1177 fn boundary_sweep_n16() {
1178 $crate::test_packed_dot_product_broadcast_boundary_sweep::<$packedfield, 16>();
1179 }
1180
1181 #[test]
1184 fn lanes_random_n2() {
1185 $crate::test_packed_dot_product_lanes_random::<$packedfield, 2>();
1186 }
1187 #[test]
1188 fn lanes_random_n3() {
1189 $crate::test_packed_dot_product_lanes_random::<$packedfield, 3>();
1190 }
1191 #[test]
1192 fn lanes_random_n4() {
1193 $crate::test_packed_dot_product_lanes_random::<$packedfield, 4>();
1194 }
1195 #[test]
1196 fn lanes_random_n5() {
1197 $crate::test_packed_dot_product_lanes_random::<$packedfield, 5>();
1198 }
1199 #[test]
1200 fn lanes_random_n6() {
1201 $crate::test_packed_dot_product_lanes_random::<$packedfield, 6>();
1202 }
1203 #[test]
1204 fn lanes_random_n7() {
1205 $crate::test_packed_dot_product_lanes_random::<$packedfield, 7>();
1206 }
1207 #[test]
1208 fn lanes_random_n8() {
1209 $crate::test_packed_dot_product_lanes_random::<$packedfield, 8>();
1210 }
1211 #[test]
1212 fn lanes_random_n9() {
1213 $crate::test_packed_dot_product_lanes_random::<$packedfield, 9>();
1214 }
1215 #[test]
1216 fn lanes_random_n16() {
1217 $crate::test_packed_dot_product_lanes_random::<$packedfield, 16>();
1218 }
1219
1220 #[test]
1223 fn max_limb_n4() {
1224 $crate::test_packed_dot_product_max_limb::<$packedfield, 4>();
1225 }
1226 #[test]
1227 fn max_limb_n5() {
1228 $crate::test_packed_dot_product_max_limb::<$packedfield, 5>();
1229 }
1230 #[test]
1231 fn max_limb_n6() {
1232 $crate::test_packed_dot_product_max_limb::<$packedfield, 6>();
1233 }
1234 #[test]
1235 fn max_limb_n7() {
1236 $crate::test_packed_dot_product_max_limb::<$packedfield, 7>();
1237 }
1238 #[test]
1239 fn max_limb_n8() {
1240 $crate::test_packed_dot_product_max_limb::<$packedfield, 8>();
1241 }
1242
1243 #[test]
1246 fn proptest_n5() {
1247 $crate::test_packed_dot_product_proptest::<$packedfield, 5>();
1248 }
1249 #[test]
1250 fn proptest_n6() {
1251 $crate::test_packed_dot_product_proptest::<$packedfield, 6>();
1252 }
1253 #[test]
1254 fn proptest_n7() {
1255 $crate::test_packed_dot_product_proptest::<$packedfield, 7>();
1256 }
1257 #[test]
1258 fn proptest_n8() {
1259 $crate::test_packed_dot_product_proptest::<$packedfield, 8>();
1260 }
1261 }
1262 };
1263}
1264
1265#[macro_export]
1266macro_rules! test_packed_extension_field {
1267 ($basefield:ty, $extfield:ty, $packedextfield:ty, $zeros:expr, $ones:expr) => {
1268 mod packed_field_tests {
1269 use p3_field::PrimeCharacteristicRing;
1270
1271 #[test]
1272 fn test_ring_with_eq() {
1273 $crate::test_ring_with_eq::<$packedextfield>($zeros, $ones);
1274 }
1275 #[test]
1276 fn test_mul_2exp_u64() {
1277 $crate::test_mul_2exp_u64::<$packedextfield>();
1278 }
1279 #[test]
1280 fn test_div_2exp_u64() {
1281 $crate::test_div_2exp_u64::<$packedextfield>();
1282 }
1283 #[test]
1284 fn test_ring_axioms_proptest() {
1285 $crate::test_ring_axioms_proptest::<$packedextfield>();
1286 }
1287 #[test]
1288 fn test_batched_linear_combination_ext() {
1289 $crate::test_batched_linear_combination_ext::<
1290 $basefield,
1291 $extfield,
1292 $packedextfield,
1293 >();
1294 }
1295 #[test]
1296 fn test_packed_extension_div_consistency() {
1297 $crate::test_packed_extension_div_consistency::<
1298 $basefield,
1299 $extfield,
1300 $packedextfield,
1301 >();
1302 }
1303 #[test]
1304 fn test_add_assign_lane_ext() {
1305 $crate::test_add_assign_lane_ext::<$basefield, $extfield, $packedextfield>();
1306 }
1307 }
1308 };
1309}
1310
1311#[macro_export]
1312macro_rules! test_packed_binomial_extension_division {
1313 ($basefield:ty, $degree:expr) => {
1314 #[test]
1315 fn test_packed_binomial_extension_division() {
1316 $crate::test_packed_binomial_extension_division::<$basefield, $degree>();
1317 }
1318 };
1319}