Skip to main content

p3_field_testing/
packedfield_testing.rs

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
27/// Interleave arr1 and arr2 using chunks of size i.
28fn 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
238/// Pin the fast `add_assign_lane` override against its default.
239///
240/// - The default rebuilds a full packed element and adds it.
241/// - The override touches only the `D` base lanes at the target lane.
242/// - Both must agree on every lane.
243/// - Only the target lane may change.
244pub 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    // Random starting register and the scalar to inject, over several trials.
255    for _ in 0..32 {
256        let start: PE = rng.random();
257        let value: EF = rng.random();
258        // Inject into each lane in turn.
259        for lane in 0..width {
260            // Fast path: the per-type override.
261            let mut fast = start;
262            fast.add_assign_lane(lane, value);
263
264            // Reference: add `value` only to the target lane via the public constructor.
265            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            // The target lane gains exactly `value`.
269            // Every other lane is untouched.
270            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
278/// Verify packed binomial extension division against scalar reference results.
279///
280/// Tests four operations:
281/// - Packed / packed (lane-wise division via Montgomery's trick)
282/// - Packed /= packed (in-place variant)
283/// - Packed / scalar (broadcast scalar inverse, then multiply)
284/// - Packed /= scalar (in-place variant)
285///
286/// Each is checked by extracting individual SIMD lanes and comparing
287/// against the equivalent scalar division.
288pub fn test_packed_binomial_extension_division<F, const D: usize>()
289where
290    F: BinomiallyExtendable<D>,
291    StandardUniform: Distribution<BinomialExtensionField<F, D>>,
292{
293    // Deterministic RNG for reproducible test failures.
294    let mut rng = SmallRng::seed_from_u64(0x04dd6059d9d02758);
295    // Number of SIMD lanes in the packed representation.
296    let width = F::Packing::WIDTH;
297
298    // Generate one random extension field element per lane for the numerator.
299    let numerators: Vec<BinomialExtensionField<F, D>> = (0..width).map(|_| rng.random()).collect();
300    // Rejection-sample non-zero elements for the denominator (zero is not invertible).
301    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    // Pack the scalar vectors into the SoA packed representation.
311    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    // Helper closure: extract a single extension field element from a given SIMD lane.
316    // Reads the lane-th scalar from each of the D packed coefficient arrays,
317    // then reassembles them into a scalar extension field element.
318    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    // Test 1: packed / packed division.
326    // Each lane of the result must equal the scalar quotient of the corresponding lane inputs.
327    let quot = packed_num / packed_den;
328    for lane in 0..width {
329        assert_eq!(
330            extract_lane(&quot, lane),
331            numerators[lane] / denominators[lane],
332            "packed/packed division mismatch at lane {lane}"
333        );
334    }
335
336    // Test 2: packed /= packed (in-place assignment).
337    // Must produce the same result as the non-assignment variant above.
338    let mut quot_assign = packed_num;
339    quot_assign /= packed_den;
340    for lane in 0..width {
341        assert_eq!(
342            extract_lane(&quot_assign, lane),
343            extract_lane(&quot, lane),
344            "packed/packed div_assign mismatch at lane {lane}"
345        );
346    }
347
348    // Test 3: packed / scalar (broadcast division).
349    // Divides every lane by the same scalar extension field element.
350    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(&quot_scalar, lane),
355            *numerator / scalar_den,
356            "packed/scalar division mismatch at lane {lane}"
357        );
358    }
359
360    // Test 4: packed /= scalar (in-place broadcast division).
361    // Must produce the same result as the non-assignment variant above.
362    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(&quot_scalar_assign, lane),
367            extract_lane(&quot_scalar, lane),
368            "packed/scalar div_assign mismatch at lane {lane}"
369        );
370    }
371}
372
373/// Verify that packed divide agrees with scalar divide on every SIMD lane.
374///
375/// # Invariant
376///
377/// For every lane `i`:
378///
379/// ```text
380/// (a / b).lane(i) == a.lane(i) / b.lane(i)
381/// ```
382pub 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    // SIMD lane count.
390    // - Goldilocks NEON → 2.
391    // - KoalaBear NEON → 4.
392    // - Scalar → 1.
393    let width = F::Packing::WIDTH;
394
395    // Fixed seed → reproducible bytes on any failure.
396    let mut rng = SmallRng::seed_from_u64(0x_d1ef_d1ef_d1ef_d1ef);
397
398    // Numerator lane fixture: any value, zeros are fine.
399    //
400    //     lane:   0    1    …    W-1
401    //     nums:  [n0,  n1,  …,   n_{W-1}]
402    let nums: Vec<EF> = (0..width).map(|_| rng.random()).collect();
403
404    // Denominator lane fixture: reject-sample so every lane is invertible.
405    //
406    //     lane:   0       1       …    W-1
407    //     dens:  [d0 ≠ 0, d1 ≠ 0, …,   d_{W-1} ≠ 0]
408    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    // Pack the per-lane scalars into one SIMD value each.
420    //
421    //     lanes [n0, n1, …, n_{W-1}]  →  one packed extension value
422    //     lanes [d0, d1, …, d_{W-1}]  →  one packed extension value
423    let pef_n: PEF = PEF::from_ext_slice(&nums);
424    let pef_d: PEF = PEF::from_ext_slice(&dens);
425
426    // Run the packed divide. Each lane must independently compute n_i / d_i.
427    let pef_q = pef_n / pef_d;
428
429    // Per-lane invariant:
430    //
431    //     packed quotient at lane i  ==  nums[i] / dens[i]
432    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    // Zero is the one element with no inverse, and over a small field a random lane hits it
603    // often enough to matter, so those lanes are pinned to the identity instead.
604    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
622/// Test that [`PackedValue::broadcast`] sets all lanes to the same scalar.
623pub 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    // Test round-trip: pack_columns then unpack_into
651    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    // Test round-trip: unpack_into then pack_columns
663    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    // Compare with unpack_into
709    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    // Round-trip with pack_columns
718    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
725/// Test dot products with maximum field values (P-1) to catch overflow bugs.
726///
727/// This verifies that SIMD dot product implementations handle the edge case where
728/// `N*(P-1)^2` can overflow `u64` (which happens for N >= 5 with 31-bit primes).
729pub 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    // Test dot_product for N = 1..=16 with all-maximum inputs.
737    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
770/// Eight canonical edge values that probe carry propagation in dot products.
771///
772/// - `0`: additive identity.
773/// - `1`, `2`: small coefficients.
774/// - `p / 4`, `p / 2`, `p / 2 + 1`: mid-range, straddle `2^32` after squaring.
775/// - `p - 2`, `p - 1`: maximum canonical band, products near `(p - 1)^2`.
776const fn boundary_u32_values<F: PrimeField32>() -> [u32; 8] {
777    // `ORDER_U32 > 4`, so every entry is canonical.
778    let p = F::ORDER_U32;
779    [0, 1, 2, p / 4, p / 2, p / 2 + 1, p - 2, p - 1]
780}
781
782/// Map a length-`N` array of canonical `u32`s through the field's quotient map.
783fn 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
790/// Compare a broadcast packed dot product against the scalar reference.
791///
792/// Each input is replicated across every SIMD lane, so all lanes must agree
793/// with the scalar result.
794///
795/// # Panics
796///
797/// On lane mismatch. The message echoes the raw inputs for replay.
798pub 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    // Lift raw u32 inputs into the field.
806    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    // Scalar reference.
810    let scalar_ref = PF::Scalar::dot_product::<N>(&lhs, &rhs);
811
812    // Packed path with each scalar broadcast over all lanes.
813    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    // Every lane must equal the scalar reference.
818    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
826/// Run `dot_product::<N>` against every (left, right) pair from the edge table.
827///
828/// Eight edges → 64 invocations per `N`. Deterministic.
829pub 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            // Broadcast (v, w) into both length-N input arrays.
839            assert_packed_broadcast_dot_product_matches_scalar::<PF, N>([v; N], [w; N]);
840        }
841    }
842}
843
844/// Per-lane edge-biased random stress for `dot_product::<N>`.
845///
846/// Each SIMD lane gets independently sampled inputs to expose lane-cross bugs
847/// that broadcast tests miss.
848pub fn test_packed_dot_product_lanes_random<PF, const N: usize>()
849where
850    PF: PackedField + Eq,
851    PF::Scalar: PrimeField32 + QuotientMap<u32>,
852{
853    // 1024 iterations × WIDTH lanes × N elements stays under a second per N.
854    const ITERS: usize = 1024;
855
856    let p = PF::Scalar::ORDER_U32;
857    let edges = boundary_u32_values::<PF::Scalar>();
858
859    // Fixed seed so failures replay verbatim.
860    let mut rng = SmallRng::seed_from_u64(0xD07B_0571_0DDE_DEAD);
861
862    for _ in 0..ITERS {
863        // One length-N array per lane, sampled independently per side.
864        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        // Transpose lane-major rows to N packed columns.
880        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        // Per-lane scalar reference.
886        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
897/// The Montgomery radix `R mod p` as a field element, with `R = 2^{32}` for a 31-bit prime.
898///
899/// A Montgomery field stores `x` as the limb `x R mod p`.
900/// Scaling a desired limb by the inverse of this element gives the value that stores it.
901///
902/// On a field that is not Montgomery encoded this is simply some other element.
903/// The caller still gets a valid, if less pointed, test.
904fn monty_radix<F: PrimeField32 + QuotientMap<u32>>() -> F {
905    F::from_int(((1u64 << 32) % F::ORDER_U64) as u32)
906}
907
908/// The canonical value whose 32-bit Montgomery limb is the largest possible, `p - 1`.
909///
910/// Delayed-reduction dot products bound their accumulators by the limbs, not the canonical values.
911/// This is the input that drives each 64-bit accumulator to its documented maximum.
912fn max_limb_value<F: PrimeField32 + QuotientMap<u32>>() -> u32 {
913    // `NEG_ONE` is the element `p - 1`, so this element has limb `(p - 1) R^{-1} R = p - 1`.
914    (F::NEG_ONE * monty_radix::<F>().inverse()).as_canonical_u32()
915}
916
917/// The canonical value whose 32-bit Montgomery limb is exactly `1`.
918///
919/// This is the smallest nonzero limb, so a product against it barely moves an accumulator.
920/// Mixing it with maximal limbs is how a test lands an accumulator just under a fold threshold.
921fn unit_limb_value<F: PrimeField32 + QuotientMap<u32>>() -> u32 {
922    // The inverse of the radix has limb `R^{-1} R = 1`.
923    monty_radix::<F>().inverse().as_canonical_u32()
924}
925
926/// Drive every accumulator of `dot_product::<N>` to its maximum and compare against the scalar.
927///
928/// With every limb equal to `p - 1`, each product is exactly `(p - 1)^2`.
929/// A group of four then reaches `4 (p - 1)^2`, within a factor of two of `2^{64}`.
930///
931/// A routine merging two such groups without first folding `2^{32} p` out of each would wrap.
932/// It would silently return a wrong field element.
933pub 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    // Both sides maximal: every accumulator sits at its upper bound.
941    assert_packed_broadcast_dot_product_matches_scalar::<PF, N>([max; N], [max; N]);
942
943    // One maximal side against the edge table: a maximal limb mixed with assorted others.
944    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    // Alternating maximal and unit limbs, against an all-maximal other side.
950    //
951    // A group of four then holds two maximal products and two negligible ones:
952    //
953    //     hi  =  2 (p - 1)^2 / 2^{32}  <  p       because 2p < 2^{32}
954    //
955    // So this is the case where every conditional subtraction must be a no-op.
956    // The all-maximal case above is the one where each of them must fire.
957    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
963/// Compare `dot_product::<N>` against the scalar reference on proptest-generated inputs.
964///
965/// Inputs are drawn uniformly from the canonical range `[0, p)` and broadcast across the lanes.
966///
967/// It complements the seeded, edge-biased per-lane variant.
968pub 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    // Naming the strategy once keeps the invocation inside the line limit.
977    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
986/// 50/50 draw between the eight-element edge table and uniform `[0, p)`.
987fn 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
995/// Test packed field operations vs scalar with 256 random packed vectors via proptest.
996///
997/// Verifies add, sub, mul, neg match lane-by-lane scalar results.
998pub 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        // Division requires a nonzero denominator in every lane.
1014        // Keep the original operands for the other operations, including zero products.
1015        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/// Everything a packing must satisfy beyond being a ring.
1046///
1047/// Held apart from the ring suite so a characteristic-2 packing can pair it with its own.
1048#[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/// The full packing suite, for a packing of a field of characteristic 2.
1111///
1112/// Only the ring half differs: halving and dividing by a power of two panic there.
1113#[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/// Run the dot-product carry / boundary stress suite on a packed `PrimeField32`.
1122///
1123/// Four kinds of test, each over its own set of lengths:
1124/// - edge-pair sweep over an eight-value table, lengths `1` to `9` and `16`,
1125/// - per-lane edge-biased random, lengths `2` to `9` and `16`,
1126/// - maximal Montgomery limbs, lengths `4` to `8`,
1127/// - uniform canonical proptest, lengths `5` to `8`.
1128///
1129/// Between them those lengths hit every dispatch arm a 31-bit Monty packing offers:
1130/// - a bare product (`1`),
1131/// - one fused reduction (`2` and `4` to `8` on x86, `2` to `5` and `8` on NEON),
1132/// - two routines summed (`3` on x86, `6` and `7` on NEON),
1133/// - the chunk-of-four loop (`9`, `16`).
1134#[macro_export]
1135macro_rules! test_packed_field_dot_product_boundary {
1136    ($packedfield:ty) => {
1137        mod packed_dot_product_boundary_tests {
1138            // Exhaustive sweep of (v, w) edge pairs.
1139
1140            #[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            // Independent per-lane inputs catch shuffle / lane-cross bugs.
1182
1183            #[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            // Maximal Montgomery limbs pin the accumulator bounds the routines rely on.
1221
1222            #[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            // Uniform canonical inputs, shrunk on failure.
1244
1245            #[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}