binius_math/
test_utils.rs

1// Copyright 2025 Irreducible Inc.
2
3use std::iter::repeat_with;
4
5use binius_field::{BinaryField128bGhash, Field, PackedBinaryGhash4x128b, PackedField};
6use rand::RngCore;
7
8use crate::FieldBuffer;
9
10/// Type alias for 128b field element with fast arithmetic.
11pub type B128 = BinaryField128bGhash;
12
13/// Type alias for a packed 128b field element with non-trivial packing width.
14pub type Packed128b = PackedBinaryGhash4x128b;
15
16/// Generates a vector of random field elements.
17///
18/// # Arguments
19///
20/// * `rng` - Random number generator implementing RngCore
21/// * `n` - Number of random field elements to generate
22///
23/// # Returns
24///
25/// Vector containing n random field elements
26pub fn random_scalars<F: Field>(mut rng: impl RngCore, n: usize) -> Vec<F> {
27	repeat_with(|| F::random(&mut rng)).take(n).collect()
28}
29
30/// Generates a [`FieldBuffer`] of random elements.
31///
32/// # Arguments
33///
34/// * `rng` - Random number generator implementing RngCore
35/// * `log_n` - log2 the number of random field elements to generate
36///
37/// # Returns
38///
39/// Vector containing `2^log_n` random field elements
40pub fn random_field_buffer<P: PackedField>(mut rng: impl RngCore, log_n: usize) -> FieldBuffer<P> {
41	FieldBuffer::<P>::new(
42		log_n,
43		repeat_with(|| P::random(&mut rng))
44			.take(1 << log_n.saturating_sub(P::LOG_WIDTH))
45			.collect(),
46	)
47}
48
49/// Converts an index to a hypercube point representation.
50///
51/// Given an index and number of variables, decomposes the index into a vector
52/// of field elements where each element is either F::ZERO or F::ONE based on
53/// the corresponding bit in the index.
54///
55/// # Arguments
56///
57/// * `n_vars` - Number of variables (bits) in the hypercube point
58/// * `index` - The index to convert (must be less than 2^n_vars)
59///
60/// # Returns
61///
62/// Vector of n_vars field elements, where element i is F::ONE if bit i of index is 1,
63/// and F::ZERO otherwise.
64///
65/// # Example
66///
67/// ```
68/// # use binius_field::BinaryField128bGhash as B128;
69/// # use binius_math::test_utils::index_to_hypercube_point;
70/// let point = index_to_hypercube_point::<B128>(3, 5);
71/// // 5 = 0b101, so point = [F::ONE, F::ZERO, F::ONE]
72/// ```
73pub fn index_to_hypercube_point<F: Field>(n_vars: usize, index: usize) -> Vec<F> {
74	debug_assert!(
75		index < (1 << n_vars),
76		"Index {index} out of bounds for {n_vars}-variable hypercube"
77	);
78	(0..n_vars)
79		.map(|i| {
80			if (index >> i) & 1 == 1 {
81				F::ONE
82			} else {
83				F::ZERO
84			}
85		})
86		.collect()
87}
88
89#[cfg(test)]
90mod tests {
91	use binius_field::BinaryField128bGhash as B128;
92	use proptest::prelude::*;
93	use rand::{SeedableRng, rngs::StdRng};
94
95	use super::*;
96
97	proptest! {
98		#[test]
99		fn same_seed_produces_identical_results(
100			seed: u64,
101			n in 0..100usize
102		) {
103			let mut rng1 = StdRng::seed_from_u64(seed);
104			let mut rng2 = StdRng::seed_from_u64(seed);
105
106			let scalars1 = random_scalars::<B128>(&mut rng1, n);
107			let scalars2 = random_scalars::<B128>(&mut rng2, n);
108
109			prop_assert_eq!(scalars1, scalars2);
110		}
111
112		#[test]
113		fn different_seeds_produce_different_results(seed1: u64, seed2: u64) {
114			prop_assume!(seed1 != seed2);
115
116			// Test with 10 elements - collision probability is 1/2^320 ≈ 10^-96
117			let n = 10;
118
119			let mut rng1 = StdRng::seed_from_u64(seed1);
120			let mut rng2 = StdRng::seed_from_u64(seed2);
121
122			let scalars1 = random_scalars::<B128>(&mut rng1, n);
123			let scalars2 = random_scalars::<B128>(&mut rng2, n);
124
125			prop_assert_ne!(scalars1, scalars2);
126		}
127	}
128}