Skip to main content

xlog_cuda_tests/harness/
generators.rs

1//! Property-based data generators for CUDA certification tests.
2
3use rand::prelude::*;
4use rand_chacha::ChaCha8Rng;
5
6/// Size generator for edge case testing.
7pub struct SizeGen;
8
9impl SizeGen {
10    /// Standard edge case sizes covering block boundaries and common edge cases.
11    pub fn edge_cases() -> Vec<usize> {
12        vec![
13            0, 1, 2, 3, 7, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 255, 256, 257, 511,
14            512, 513, 1023, 1024, 1025, 2047, 2048, 2049, 4095, 4096, 4097, 8191, 8192, 8193,
15            16383, 16384, 16385, 32767, 32768, 32769, 65535, 65536, 65537,
16        ]
17    }
18
19    /// Sizes near 32-bit overflow boundary.
20    pub fn near_i32_max() -> Vec<usize> {
21        vec![
22            (i32::MAX as usize) - 2,
23            (i32::MAX as usize) - 1,
24            i32::MAX as usize,
25        ]
26    }
27
28    /// Sizes that are prime numbers (worst case for some algorithms).
29    pub fn primes() -> Vec<usize> {
30        vec![
31            2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83,
32            89, 97, 101, 103, 107, 127, 131, 251, 257, 509, 521, 1021, 1031, 2039, 2053, 4093,
33            4099, 8191, 8209, 16381, 16411, 32749, 32771, 65521, 65537,
34        ]
35    }
36
37    /// Random sizes within a range using deterministic RNG.
38    pub fn random(count: usize, min: usize, max: usize, seed: u64) -> Vec<usize> {
39        let mut rng = ChaCha8Rng::seed_from_u64(seed);
40        (0..count).map(|_| rng.gen_range(min..=max)).collect()
41    }
42
43    /// Warp-related sizes (multiples of 32, and off-by-one).
44    pub fn warp_related() -> Vec<usize> {
45        vec![
46            31, 32, 33, 63, 64, 65, 95, 96, 97, 127, 128, 129, 159, 160, 161, 191, 192, 193, 223,
47            224, 225, 255, 256, 257, 287, 288, 289,
48        ]
49    }
50
51    /// Block-related sizes (multiples of 256, and off-by-one).
52    pub fn block_related() -> Vec<usize> {
53        vec![
54            255, 256, 257, 511, 512, 513, 767, 768, 769, 1023, 1024, 1025, 1279, 1280, 1281, 1535,
55            1536, 1537,
56        ]
57    }
58}
59
60/// Value distribution patterns for test data generation.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum Distribution {
63    AllEqual,
64    AllUnique,
65    Sorted,
66    ReverseSorted,
67    Alternating,
68    AdversarialHash,
69    Random,
70    HalfAndHalf,
71    Sparse,
72    Dense,
73}
74
75impl Distribution {
76    /// Get all distribution variants.
77    pub fn all() -> Vec<Distribution> {
78        vec![
79            Distribution::AllEqual,
80            Distribution::AllUnique,
81            Distribution::Sorted,
82            Distribution::ReverseSorted,
83            Distribution::Alternating,
84            Distribution::AdversarialHash,
85            Distribution::Random,
86        ]
87    }
88
89    /// Generate u32 values with this distribution.
90    pub fn generate_u32(&self, count: usize, seed: u64) -> Vec<u32> {
91        let mut rng = ChaCha8Rng::seed_from_u64(seed);
92        match self {
93            Distribution::AllEqual => vec![42u32; count],
94            Distribution::AllUnique => (0..count as u32).collect(),
95            Distribution::Sorted => (0..count as u32).collect(),
96            Distribution::ReverseSorted => (0..count as u32).rev().collect(),
97            Distribution::Alternating => (0..count)
98                .map(|i| if i % 2 == 0 { 0 } else { u32::MAX })
99                .collect(),
100            Distribution::AdversarialHash => (0..count).map(|i| (i as u32) * 256).collect(),
101            Distribution::Random => (0..count).map(|_| rng.gen()).collect(),
102            Distribution::HalfAndHalf => (0..count)
103                .map(|i| if i < count / 2 { 0 } else { 1 })
104                .collect(),
105            Distribution::Sparse => (0..count)
106                .map(|i| if i % 10 == 0 { 1 } else { 0 })
107                .collect(),
108            Distribution::Dense => (0..count)
109                .map(|i| if i % 10 == 0 { 0 } else { 1 })
110                .collect(),
111        }
112    }
113
114    /// Generate i64 values with this distribution.
115    pub fn generate_i64(&self, count: usize, seed: u64) -> Vec<i64> {
116        let mut rng = ChaCha8Rng::seed_from_u64(seed);
117        match self {
118            Distribution::AllEqual => vec![42i64; count],
119            Distribution::AllUnique => (0..count as i64).collect(),
120            Distribution::Sorted => (0..count as i64).collect(),
121            Distribution::ReverseSorted => (0..count as i64).rev().collect(),
122            Distribution::Alternating => (0..count)
123                .map(|i| if i % 2 == 0 { i64::MIN } else { i64::MAX })
124                .collect(),
125            Distribution::AdversarialHash => (0..count).map(|i| (i as i64) * 256).collect(),
126            Distribution::Random => (0..count).map(|_| rng.gen()).collect(),
127            Distribution::HalfAndHalf => (0..count)
128                .map(|i| if i < count / 2 { -1 } else { 1 })
129                .collect(),
130            Distribution::Sparse => (0..count)
131                .map(|i| if i % 10 == 0 { 1 } else { 0 })
132                .collect(),
133            Distribution::Dense => (0..count)
134                .map(|i| if i % 10 == 0 { 0 } else { 1 })
135                .collect(),
136        }
137    }
138
139    /// Generate f64 values with this distribution.
140    pub fn generate_f64(&self, count: usize, seed: u64) -> Vec<f64> {
141        let mut rng = ChaCha8Rng::seed_from_u64(seed);
142        match self {
143            Distribution::AllEqual => vec![42.0f64; count],
144            Distribution::AllUnique => (0..count).map(|i| i as f64).collect(),
145            Distribution::Sorted => (0..count).map(|i| i as f64).collect(),
146            Distribution::ReverseSorted => (0..count).map(|i| (count - 1 - i) as f64).collect(),
147            Distribution::Alternating => (0..count)
148                .map(|i| if i % 2 == 0 { f64::MIN } else { f64::MAX })
149                .collect(),
150            Distribution::AdversarialHash => (0..count).map(|i| (i as f64) * 256.0).collect(),
151            Distribution::Random => (0..count).map(|_| rng.gen_range(-1e10..1e10)).collect(),
152            Distribution::HalfAndHalf => (0..count)
153                .map(|i| if i < count / 2 { -1.0 } else { 1.0 })
154                .collect(),
155            Distribution::Sparse => (0..count)
156                .map(|i| if i % 10 == 0 { 1.0 } else { 0.0 })
157                .collect(),
158            Distribution::Dense => (0..count)
159                .map(|i| if i % 10 == 0 { 0.0 } else { 1.0 })
160                .collect(),
161        }
162    }
163
164    /// Generate u8 mask values with this distribution.
165    pub fn generate_mask(&self, count: usize, seed: u64) -> Vec<u8> {
166        let mut rng = ChaCha8Rng::seed_from_u64(seed);
167        match self {
168            Distribution::AllEqual => vec![1u8; count],
169            Distribution::AllUnique => (0..count).map(|i| (i % 2) as u8).collect(),
170            Distribution::Sorted => (0..count)
171                .map(|i| if i < count / 2 { 0 } else { 1 })
172                .collect(),
173            Distribution::ReverseSorted => (0..count)
174                .map(|i| if i < count / 2 { 1 } else { 0 })
175                .collect(),
176            Distribution::Alternating => (0..count).map(|i| (i % 2) as u8).collect(),
177            Distribution::AdversarialHash => (0..count).map(|i| (i % 2) as u8).collect(),
178            Distribution::Random => (0..count)
179                .map(|_| if rng.gen_bool(0.5) { 1 } else { 0 })
180                .collect(),
181            Distribution::HalfAndHalf => (0..count)
182                .map(|i| if i < count / 2 { 0 } else { 1 })
183                .collect(),
184            Distribution::Sparse => (0..count)
185                .map(|i| if i % 10 == 0 { 1 } else { 0 })
186                .collect(),
187            Distribution::Dense => (0..count)
188                .map(|i| if i % 10 == 0 { 0 } else { 1 })
189                .collect(),
190        }
191    }
192}
193
194/// Numeric edge value generators for specific types.
195pub struct NumericEdges;
196
197impl NumericEdges {
198    pub fn u32_edges() -> Vec<u32> {
199        vec![0, 1, 2, u32::MAX - 1, u32::MAX]
200    }
201
202    pub fn i32_edges() -> Vec<i32> {
203        vec![i32::MIN, i32::MIN + 1, -1, 0, 1, i32::MAX - 1, i32::MAX]
204    }
205
206    pub fn u64_edges() -> Vec<u64> {
207        vec![0, 1, 2, u64::MAX - 1, u64::MAX]
208    }
209
210    pub fn i64_edges() -> Vec<i64> {
211        vec![i64::MIN, i64::MIN + 1, -1, 0, 1, i64::MAX - 1, i64::MAX]
212    }
213
214    pub fn f32_edges() -> Vec<f32> {
215        vec![
216            f32::NEG_INFINITY,
217            f32::MIN,
218            -1.0,
219            -f32::MIN_POSITIVE,
220            -0.0,
221            0.0,
222            f32::MIN_POSITIVE,
223            1.0,
224            f32::MAX,
225            f32::INFINITY,
226            f32::NAN,
227        ]
228    }
229
230    pub fn f64_edges() -> Vec<f64> {
231        vec![
232            f64::NEG_INFINITY,
233            f64::MIN,
234            -1.0,
235            -f64::MIN_POSITIVE,
236            -0.0,
237            0.0,
238            f64::MIN_POSITIVE,
239            1.0,
240            f64::MAX,
241            f64::INFINITY,
242            f64::NAN,
243        ]
244    }
245
246    pub fn f64_subnormals() -> Vec<f64> {
247        vec![
248            5e-324,
249            1e-323,
250            1e-320,
251            1e-310,
252            f64::MIN_POSITIVE / 2.0,
253            -5e-324,
254            -1e-323,
255            -f64::MIN_POSITIVE / 2.0,
256        ]
257    }
258
259    pub fn f64_fma_stress() -> Vec<(f64, f64, f64)> {
260        vec![
261            (1.0 + 1e-15, 1.0 - 1e-15, 1e-30),
262            (1e100, 1e100, 1e-100),
263            (1e-100, 1e-100, 1e-200),
264            (f64::MAX / 2.0, 2.0, -f64::MAX),
265        ]
266    }
267}
268
269/// Alignment generator for memory access testing.
270pub struct AlignmentGen;
271
272impl AlignmentGen {
273    pub fn offsets() -> Vec<(usize, &'static str)> {
274        vec![
275            (0, "aligned"),
276            (1, "off-by-1"),
277            (2, "off-by-2"),
278            (3, "off-by-3"),
279            (4, "off-by-4"),
280            (5, "off-by-5"),
281            (6, "off-by-6"),
282            (7, "off-by-7"),
283        ]
284    }
285
286    pub fn violating_offsets(required_alignment: usize) -> Vec<usize> {
287        (1..required_alignment).collect()
288    }
289}
290
291/// Key distribution generator for hash table and join testing.
292pub struct KeyDistribution;
293
294impl KeyDistribution {
295    /// Generate key pairs for join testing. Returns (left_keys, right_keys, expected_match_count).
296    pub fn join_test_data(
297        size: usize,
298        overlap_ratio: f64,
299        seed: u64,
300    ) -> (Vec<u32>, Vec<u32>, usize) {
301        let mut rng = ChaCha8Rng::seed_from_u64(seed);
302        let overlap_size = (size as f64 * overlap_ratio) as usize;
303        let common: Vec<u32> = (0..overlap_size as u32).collect();
304        let left_only: Vec<u32> = (overlap_size as u32..(size as u32)).collect();
305        let right_only: Vec<u32> = ((size + overlap_size) as u32..((2 * size) as u32)).collect();
306
307        let mut left = common.clone();
308        left.extend(left_only);
309        left.shuffle(&mut rng);
310
311        let mut right = common.clone();
312        right.extend(right_only);
313        right.shuffle(&mut rng);
314
315        (left, right, overlap_size)
316    }
317
318    /// Generate keys with many hash collisions.
319    pub fn collision_keys(count: usize, collision_factor: usize) -> Vec<u32> {
320        (0..count).map(|i| (i * collision_factor) as u32).collect()
321    }
322
323    /// Generate keys for groupby testing with specified group count.
324    pub fn groupby_keys(total_rows: usize, num_groups: usize, seed: u64) -> Vec<u32> {
325        let mut rng = ChaCha8Rng::seed_from_u64(seed);
326        (0..total_rows)
327            .map(|_| rng.gen_range(0..num_groups as u32))
328            .collect()
329    }
330}