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	.expect("correct number of packed elements are generated")
48}
49
50/// Converts an index to a hypercube point representation.
51///
52/// Given an index and number of variables, decomposes the index into a vector
53/// of field elements where each element is either F::ZERO or F::ONE based on
54/// the corresponding bit in the index.
55///
56/// # Arguments
57///
58/// * `n_vars` - Number of variables (bits) in the hypercube point
59/// * `index` - The index to convert (must be less than 2^n_vars)
60///
61/// # Returns
62///
63/// Vector of n_vars field elements, where element i is F::ONE if bit i of index is 1,
64/// and F::ZERO otherwise.
65///
66/// # Example
67///
68/// ```
69/// # use binius_field::BinaryField128bGhash as B128;
70/// # use binius_math::test_utils::index_to_hypercube_point;
71/// let point = index_to_hypercube_point::<B128>(3, 5);
72/// // 5 = 0b101, so point = [F::ONE, F::ZERO, F::ONE]
73/// ```
74pub fn index_to_hypercube_point<F: Field>(n_vars: usize, index: usize) -> Vec<F> {
75	debug_assert!(
76		index < (1 << n_vars),
77		"Index {index} out of bounds for {n_vars}-variable hypercube"
78	);
79	(0..n_vars)
80		.map(|i| {
81			if (index >> i) & 1 == 1 {
82				F::ONE
83			} else {
84				F::ZERO
85			}
86		})
87		.collect()
88}
89
90#[cfg(test)]
91mod tests {
92	use binius_field::BinaryField128bGhash as B128;
93	use proptest::prelude::*;
94	use rand::{SeedableRng, rngs::StdRng};
95
96	use super::*;
97
98	proptest! {
99		#[test]
100		fn same_seed_produces_identical_results(
101			seed: u64,
102			n in 0..100usize
103		) {
104			let mut rng1 = StdRng::seed_from_u64(seed);
105			let mut rng2 = StdRng::seed_from_u64(seed);
106
107			let scalars1 = random_scalars::<B128>(&mut rng1, n);
108			let scalars2 = random_scalars::<B128>(&mut rng2, n);
109
110			prop_assert_eq!(scalars1, scalars2);
111		}
112
113		#[test]
114		fn different_seeds_produce_different_results(seed1: u64, seed2: u64) {
115			prop_assume!(seed1 != seed2);
116
117			// Test with 10 elements - collision probability is 1/2^320 ≈ 10^-96
118			let n = 10;
119
120			let mut rng1 = StdRng::seed_from_u64(seed1);
121			let mut rng2 = StdRng::seed_from_u64(seed2);
122
123			let scalars1 = random_scalars::<B128>(&mut rng1, n);
124			let scalars2 = random_scalars::<B128>(&mut rng2, n);
125
126			prop_assert_ne!(scalars1, scalars2);
127		}
128	}
129}