Skip to main content

oxiphysics_gpu/parallel/
functions_2.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5#[cfg(test)]
6mod tests {
7    use super::super::functions::*;
8    use crate::parallel::LoadBalanceStrategy;
9    use crate::parallel::WorkChunker;
10    use crate::parallel::WorkGroupConfig;
11    use crate::parallel::WorkStealQueue;
12    use std::sync::atomic::{AtomicUsize, Ordering};
13    #[test]
14    fn parallel_for_processes_all_items() {
15        let counter = AtomicUsize::new(0);
16        let n = 100;
17        parallel_for(n, 16, |_i| {
18            counter.fetch_add(1, Ordering::Relaxed);
19        });
20        assert_eq!(counter.load(Ordering::Relaxed), n);
21    }
22    #[test]
23    fn test_parallel_for_produces_correct_results() {
24        let n = 64;
25        let mut results = vec![0.0f64; n];
26        let ptr = results.as_mut_ptr();
27        parallel_for(n, 8, |i| unsafe {
28            *ptr.add(i) = (i as f64) * (i as f64);
29        });
30        for (i, &val) in results.iter().enumerate() {
31            let expected = (i as f64) * (i as f64);
32            assert!(
33                (val - expected).abs() < 1e-15,
34                "index {i}: expected {expected}, got {val}"
35            );
36        }
37    }
38    /// Gaussian-like kernel used in density tests: W(r, h) = exp(-r^2/h^2) / h^3
39    fn gauss_kernel(r: f64, h: f64) -> f64 {
40        (-(r / h) * (r / h)).exp() / (h * h * h)
41    }
42    #[test]
43    fn test_parallel_density_uniform() {
44        let spacing = 0.5_f64;
45        let h = 0.6_f64;
46        let mut positions: Vec<[f64; 3]> = Vec::new();
47        for ix in 0..3_i32 {
48            for iy in 0..3_i32 {
49                for iz in 0..3_i32 {
50                    positions.push([
51                        ix as f64 * spacing,
52                        iy as f64 * spacing,
53                        iz as f64 * spacing,
54                    ]);
55                }
56            }
57        }
58        let n = positions.len();
59        let mass = 1.0_f64;
60        let masses = vec![mass; n];
61        let densities = parallel_sph_density(&positions, &masses, h, gauss_kernel);
62        assert_eq!(densities.len(), n);
63        for &rho in &densities {
64            assert!(rho > 0.0, "density should be positive, got {rho}");
65        }
66        let self_contrib = 1.0 / (h * h * h);
67        assert!(
68            densities[13] >= self_contrib * 0.9,
69            "interior density too low: {}",
70            densities[13]
71        );
72    }
73    #[test]
74    fn test_parallel_lj_repulsion() {
75        let sigma = 1.0_f64;
76        let epsilon = 1.0_f64;
77        let cutoff = 3.0_f64;
78        let positions = vec![[0.0, 0.0, 0.0], [0.9 * sigma, 0.0, 0.0]];
79        let forces = parallel_lj_forces(&positions, epsilon, sigma, cutoff);
80        assert_eq!(forces.len(), 2);
81        assert!(
82            forces[0][0] < 0.0,
83            "expected repulsive force on particle 0 in -x, got {}",
84            forces[0][0]
85        );
86        assert!(
87            (forces[0][0] + forces[1][0]).abs() < 1e-12,
88            "forces not equal and opposite: {} vs {}",
89            forces[0][0],
90            forces[1][0]
91        );
92    }
93    #[test]
94    fn test_parallel_lj_attraction() {
95        let sigma = 1.0_f64;
96        let epsilon = 1.0_f64;
97        let cutoff = 5.0_f64;
98        let r_eq = 2.0_f64.powf(1.0 / 6.0) * sigma;
99        let positions = vec![[0.0, 0.0, 0.0], [r_eq, 0.0, 0.0]];
100        let forces = parallel_lj_forces(&positions, epsilon, sigma, cutoff);
101        assert_eq!(forces.len(), 2);
102        for (k, &fk) in forces[0].iter().enumerate() {
103            assert!(
104                fk.abs() < 1e-10,
105                "force[0][{k}] should be ~0 at equilibrium, got {}",
106                fk
107            );
108        }
109    }
110    #[test]
111    fn test_parallel_verlet() {
112        let mut positions = vec![[0.0_f64, 0.0, 0.0]];
113        let mut velocities = vec![[0.0_f64, 0.0, 0.0]];
114        let forces = vec![[0.0_f64, -9.81, 0.0]];
115        let masses = vec![1.0_f64];
116        let dt = 0.1_f64;
117        parallel_verlet_step(&mut positions, &mut velocities, &forces, &masses, dt);
118        let expected_y = 0.5 * (-9.81) * dt * dt;
119        let expected_vy = 0.5 * (-9.81) * dt;
120        assert!(
121            (positions[0][1] - expected_y).abs() < 1e-12,
122            "y position: expected {expected_y}, got {}",
123            positions[0][1]
124        );
125        assert!(
126            (velocities[0][1] - expected_vy).abs() < 1e-12,
127            "vy: expected {expected_vy}, got {}",
128            velocities[0][1]
129        );
130        assert!((positions[0][0]).abs() < 1e-15);
131        assert!((positions[0][2]).abs() < 1e-15);
132    }
133    #[test]
134    fn test_parallel_aabb_pairs() {
135        let aabbs = vec![
136            ([0.0, 0.0, 0.0], [2.0, 2.0, 2.0]),
137            ([0.5, 0.5, 0.5], [1.5, 1.5, 1.5]),
138            ([0.3, 0.3, 0.3], [1.8, 1.8, 1.8]),
139            ([0.6, 0.6, 0.6], [2.5, 2.5, 2.5]),
140        ];
141        let mut pairs = parallel_aabb_pairs(&aabbs);
142        pairs.sort_unstable();
143        assert_eq!(
144            pairs.len(),
145            6,
146            "expected 6 overlapping pairs, got {}: {:?}",
147            pairs.len(),
148            pairs
149        );
150        for &(a, b) in &pairs {
151            assert!(a < b, "pair ({a}, {b}) not in canonical order");
152        }
153    }
154    #[test]
155    fn test_work_chunker() {
156        let n = 100;
157        let chunker = WorkChunker::new(n);
158        let chunks = chunker.chunks();
159        assert!(!chunks.is_empty(), "should produce at least one chunk");
160        let mut covered = vec![false; n];
161        for range in &chunks {
162            for idx in range.clone() {
163                assert!(idx < n, "index {idx} out of bounds");
164                assert!(!covered[idx], "index {idx} covered twice");
165                covered[idx] = true;
166            }
167        }
168        assert!(
169            covered.iter().all(|&c| c),
170            "not all indices covered by chunks"
171        );
172    }
173    #[test]
174    fn test_work_group_config_new() {
175        let cfg = WorkGroupConfig::new(128);
176        assert_eq!(cfg.preferred_size, 128);
177        assert_eq!(cfg.max_size, 1024);
178        assert_eq!(cfg.min_size, 32);
179    }
180    #[test]
181    fn test_work_group_config_zero() {
182        let cfg = WorkGroupConfig::new(0);
183        assert_eq!(cfg.preferred_size, 1);
184    }
185    #[test]
186    fn test_work_group_optimal_small() {
187        let cfg = WorkGroupConfig::new(64);
188        let size = cfg.optimal_size(10);
189        assert!(size >= cfg.min_size);
190        assert!(size <= cfg.max_size);
191    }
192    #[test]
193    fn test_work_group_optimal_large() {
194        let cfg = WorkGroupConfig::new(64);
195        let size = cfg.optimal_size(1000);
196        assert!(size >= cfg.min_size);
197        assert!(size <= cfg.max_size);
198    }
199    #[test]
200    fn test_work_group_ranges_cover_all() {
201        let cfg = WorkGroupConfig::new(64);
202        let total = 200;
203        let ranges = cfg.group_ranges(total);
204        let mut covered = vec![false; total];
205        for range in &ranges {
206            for idx in range.clone() {
207                assert!(!covered[idx], "index {idx} covered twice");
208                covered[idx] = true;
209            }
210        }
211        assert!(covered.iter().all(|&c| c));
212    }
213    #[test]
214    fn test_work_group_num_groups() {
215        let cfg = WorkGroupConfig::new(64);
216        let ng = cfg.num_groups(256);
217        assert!(ng >= 1);
218        assert!(ng * cfg.optimal_size(256) >= 256);
219    }
220    #[test]
221    fn test_work_group_cpu_default() {
222        let cfg = WorkGroupConfig::cpu_default();
223        assert_eq!(cfg.preferred_size, 64);
224        assert!(cfg.min_size >= 1);
225    }
226    #[test]
227    fn test_parallel_reduce_sum() {
228        let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
229        assert!((parallel_reduce_sum(&data) - 15.0).abs() < 1e-10);
230    }
231    #[test]
232    fn test_parallel_reduce_sum_empty() {
233        assert!((parallel_reduce_sum(&[]) - 0.0).abs() < 1e-10);
234    }
235    #[test]
236    fn test_parallel_reduce_max() {
237        let data = vec![3.0, 1.0, 4.0, 1.0, 5.0, 9.0, 2.0, 6.0];
238        assert!((parallel_reduce_max(&data) - 9.0).abs() < 1e-10);
239    }
240    #[test]
241    fn test_parallel_reduce_min() {
242        let data = vec![3.0, 1.0, 4.0, 1.0, 5.0, 9.0, 2.0, 6.0];
243        assert!((parallel_reduce_min(&data) - 1.0).abs() < 1e-10);
244    }
245    #[test]
246    fn test_parallel_dot_product() {
247        let a = vec![1.0, 2.0, 3.0];
248        let b = vec![4.0, 5.0, 6.0];
249        assert!((parallel_dot_product(&a, &b) - 32.0).abs() < 1e-10);
250    }
251    #[test]
252    fn test_parallel_norm2() {
253        let data = vec![3.0, 4.0];
254        assert!((parallel_norm2(&data) - 5.0).abs() < 1e-10);
255    }
256    #[test]
257    fn test_parallel_mean() {
258        let data = vec![2.0, 4.0, 6.0, 8.0];
259        assert!((parallel_mean(&data) - 5.0).abs() < 1e-10);
260    }
261    #[test]
262    fn test_parallel_mean_empty() {
263        assert!((parallel_mean(&[]) - 0.0).abs() < 1e-10);
264    }
265    #[test]
266    fn test_parallel_variance() {
267        let data = vec![2.0, 4.0, 6.0, 8.0];
268        assert!((parallel_variance(&data) - 5.0).abs() < 1e-10);
269    }
270    #[test]
271    fn test_parallel_sum_count() {
272        let data = vec![10.0, 20.0, 30.0];
273        let (sum, count) = parallel_sum_count(&data);
274        assert!((sum - 60.0).abs() < 1e-10);
275        assert_eq!(count, 3);
276    }
277    #[test]
278    fn test_parallel_reduce_custom_product() {
279        let data = vec![2.0, 3.0, 4.0];
280        let product = parallel_reduce_custom(&data, 1.0, |a, b| a * b);
281        assert!((product - 24.0).abs() < 1e-10);
282    }
283    #[test]
284    fn test_parallel_exclusive_scan() {
285        let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
286        let result = parallel_exclusive_scan(&data);
287        assert_eq!(result.len(), 5);
288        let expected = [0.0, 1.0, 3.0, 6.0, 10.0];
289        for i in 0..5 {
290            assert!(
291                (result[i] - expected[i]).abs() < 1e-10,
292                "exclusive_scan[{i}]: expected {}, got {}",
293                expected[i],
294                result[i]
295            );
296        }
297    }
298    #[test]
299    fn test_parallel_exclusive_scan_empty() {
300        let result = parallel_exclusive_scan(&[]);
301        assert!(result.is_empty());
302    }
303    #[test]
304    fn test_parallel_inclusive_scan() {
305        let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
306        let result = parallel_inclusive_scan(&data);
307        let expected = [1.0, 3.0, 6.0, 10.0, 15.0];
308        for i in 0..5 {
309            assert!(
310                (result[i] - expected[i]).abs() < 1e-10,
311                "inclusive_scan[{i}]: expected {}, got {}",
312                expected[i],
313                result[i]
314            );
315        }
316    }
317    #[test]
318    fn test_parallel_inclusive_scan_large() {
319        let n = 1000;
320        let data: Vec<f64> = (0..n).map(|i| i as f64).collect();
321        let result = parallel_inclusive_scan(&data);
322        assert_eq!(result.len(), n);
323        let expected_last = (n - 1) as f64 * n as f64 / 2.0;
324        assert!(
325            (result[n - 1] - expected_last).abs() < 1e-6,
326            "last element: expected {expected_last}, got {}",
327            result[n - 1]
328        );
329    }
330    #[test]
331    fn test_parallel_exclusive_scan_large() {
332        let n = 1000;
333        let data: Vec<f64> = (0..n).map(|i| i as f64).collect();
334        let result = parallel_exclusive_scan(&data);
335        assert_eq!(result.len(), n);
336        assert!((result[0]).abs() < 1e-10);
337        let expected = (n - 1) as f64 * (n - 2) as f64 / 2.0;
338        assert!(
339            (result[n - 1] - expected).abs() < 1e-6,
340            "result[{n}]: expected {expected}, got {}",
341            result[n - 1]
342        );
343    }
344    #[test]
345    fn test_segmented_exclusive_scan() {
346        let data = vec![1.0, 2.0, 3.0, 10.0, 20.0];
347        let segs = vec![0, 0, 0, 1, 1];
348        let result = segmented_exclusive_scan(&data, &segs);
349        let expected = [0.0, 1.0, 3.0, 0.0, 10.0];
350        for i in 0..5 {
351            assert!(
352                (result[i] - expected[i]).abs() < 1e-10,
353                "seg_scan[{i}]: expected {}, got {}",
354                expected[i],
355                result[i]
356            );
357        }
358    }
359    #[test]
360    fn test_segmented_exclusive_scan_empty() {
361        let result = segmented_exclusive_scan(&[], &[]);
362        assert!(result.is_empty());
363    }
364    #[test]
365    fn test_parallel_sort_f64() {
366        let mut data = vec![5.0, 2.0, 8.0, 1.0, 9.0, 3.0];
367        parallel_sort_f64(&mut data);
368        assert_eq!(data, vec![1.0, 2.0, 3.0, 5.0, 8.0, 9.0]);
369    }
370    #[test]
371    fn test_parallel_sort_f64_empty() {
372        let mut data: Vec<f64> = vec![];
373        parallel_sort_f64(&mut data);
374        assert!(data.is_empty());
375    }
376    #[test]
377    fn test_parallel_sort_f64_single() {
378        let mut data = vec![42.0];
379        parallel_sort_f64(&mut data);
380        assert_eq!(data, vec![42.0]);
381    }
382    #[test]
383    fn test_parallel_argsort() {
384        let data = vec![3.0, 1.0, 4.0, 1.0, 5.0];
385        let indices = parallel_argsort(&data);
386        assert_eq!(indices.len(), 5);
387        for i in 1..indices.len() {
388            assert!(data[indices[i]] >= data[indices[i - 1]]);
389        }
390    }
391    #[test]
392    fn test_parallel_sort_by_key() {
393        let mut items = vec![(3, "c"), (1, "a"), (2, "b")];
394        parallel_sort_by_key(&mut items, |item| item.0 as f64);
395        assert_eq!(items, vec![(1, "a"), (2, "b"), (3, "c")]);
396    }
397    #[test]
398    fn test_parallel_partition() {
399        let data = vec![1, 2, 3, 4, 5, 6, 7, 8];
400        let (evens, odds) = parallel_partition(&data, |&x| x % 2 == 0);
401        assert_eq!(evens.len(), 4);
402        assert_eq!(odds.len(), 4);
403        for v in &evens {
404            assert_eq!(v % 2, 0);
405        }
406        for v in &odds {
407            assert_eq!(v % 2, 1);
408        }
409    }
410    #[test]
411    fn test_parallel_rank() {
412        let data = vec![30.0, 10.0, 20.0];
413        let ranks = parallel_rank(&data);
414        assert_eq!(ranks[0], 2);
415        assert_eq!(ranks[1], 0);
416        assert_eq!(ranks[2], 1);
417    }
418    #[test]
419    fn test_load_balance_static() {
420        let plan = compute_load_balance(100, 4, LoadBalanceStrategy::Static, None);
421        assert_eq!(plan.num_workers(), 4);
422        let mut covered = [false; 100];
423        for range in &plan.ranges {
424            for idx in range.clone() {
425                covered[idx] = true;
426            }
427        }
428        assert!(covered.iter().all(|&c| c));
429    }
430    #[test]
431    fn test_load_balance_static_imbalance() {
432        let plan = compute_load_balance(100, 4, LoadBalanceStrategy::Static, None);
433        let ratio = plan.imbalance_ratio();
434        assert!(ratio < 1.5, "imbalance too high: {ratio}");
435    }
436    #[test]
437    fn test_load_balance_weighted() {
438        let mut weights = vec![1.0; 100];
439        for w in weights.iter_mut().take(50) {
440            *w = 10.0;
441        }
442        let plan = compute_load_balance(100, 4, LoadBalanceStrategy::Weighted, Some(&weights));
443        assert!(plan.num_workers() >= 1);
444        let mut covered = [false; 100];
445        for range in &plan.ranges {
446            for idx in range.clone() {
447                covered[idx] = true;
448            }
449        }
450        assert!(covered.iter().all(|&c| c));
451    }
452    #[test]
453    fn test_load_balance_weighted_better_than_static() {
454        let mut weights = vec![1.0; 100];
455        for w in weights.iter_mut().take(10) {
456            *w = 50.0;
457        }
458        let static_plan = compute_load_balance(100, 4, LoadBalanceStrategy::Static, Some(&weights));
459        let weighted_plan =
460            compute_load_balance(100, 4, LoadBalanceStrategy::Weighted, Some(&weights));
461        assert!(
462            weighted_plan.imbalance_ratio() <= static_plan.imbalance_ratio() + 0.1,
463            "weighted imbalance {} should be <= static imbalance {}",
464            weighted_plan.imbalance_ratio(),
465            static_plan.imbalance_ratio()
466        );
467    }
468    #[test]
469    fn test_load_balance_guided() {
470        let plan = compute_load_balance(100, 4, LoadBalanceStrategy::Guided, None);
471        assert!(plan.num_workers() >= 1);
472        let mut covered = [false; 100];
473        for range in &plan.ranges {
474            for idx in range.clone() {
475                covered[idx] = true;
476            }
477        }
478        assert!(covered.iter().all(|&c| c));
479    }
480    #[test]
481    fn test_load_balance_guided_chunk_sizes_decrease() {
482        let plan = compute_load_balance(1000, 4, LoadBalanceStrategy::Guided, None);
483        if plan.ranges.len() >= 2 {
484            let first_len = plan.ranges[0].len();
485            let last_len = plan.ranges.last().unwrap().len();
486            assert!(
487                first_len >= last_len,
488                "guided chunks should decrease: first={first_len}, last={last_len}"
489            );
490        }
491    }
492    #[test]
493    fn test_load_balance_single_worker() {
494        let plan = compute_load_balance(50, 1, LoadBalanceStrategy::Static, None);
495        assert_eq!(plan.num_workers(), 1);
496        assert_eq!(plan.ranges[0], 0..50);
497    }
498    #[test]
499    fn test_load_balance_zero_items() {
500        let plan = compute_load_balance(0, 4, LoadBalanceStrategy::Static, None);
501        assert!(plan.ranges.is_empty() || plan.ranges.iter().all(|r| r.is_empty()));
502    }
503    #[test]
504    fn test_execute_balanced() {
505        let plan = compute_load_balance(100, 4, LoadBalanceStrategy::Static, None);
506        let counter = AtomicUsize::new(0);
507        execute_balanced(&plan, |_worker, range| {
508            counter.fetch_add(range.len(), Ordering::Relaxed);
509        });
510        assert_eq!(counter.load(Ordering::Relaxed), 100);
511    }
512    #[test]
513    fn test_parallel_map_reduce() {
514        let data = vec![1.0, 2.0, 3.0, 4.0];
515        let result = parallel_map_reduce(&data, |&x| x * x, 0.0, |a, b| a + b);
516        assert!((result - 30.0).abs() < 1e-10);
517    }
518    #[test]
519    fn test_parallel_map_reduce_max() {
520        let data = vec![1.0, 5.0, 3.0, 2.0];
521        let result = parallel_map_reduce(&data, |&x| x, f64::NEG_INFINITY, f64::max);
522        assert!((result - 5.0).abs() < 1e-10);
523    }
524    #[test]
525    fn test_parallel_histogram() {
526        let data = vec![0.5, 1.5, 2.5, 3.5, 0.1, 1.9, 2.1, 3.9];
527        let hist = parallel_histogram(&data, 0.0, 4.0, 4);
528        assert_eq!(hist[0], 2);
529        assert_eq!(hist[1], 2);
530        assert_eq!(hist[2], 2);
531        assert_eq!(hist[3], 2);
532    }
533    #[test]
534    fn test_parallel_histogram_empty() {
535        let hist = parallel_histogram(&[], 0.0, 10.0, 5);
536        assert_eq!(hist, vec![0; 5]);
537    }
538    #[test]
539    fn test_parallel_histogram_boundary() {
540        let data = vec![0.0, 10.0];
541        let hist = parallel_histogram(&data, 0.0, 10.0, 2);
542        assert_eq!(hist[0], 1);
543        assert_eq!(hist[1], 1);
544    }
545    #[test]
546    fn test_dist3() {
547        let a = [0.0, 0.0, 0.0];
548        let b = [3.0, 4.0, 0.0];
549        assert!((dist3(a, b) - 5.0).abs() < 1e-10);
550    }
551    #[test]
552    fn test_dist3_same_point() {
553        let a = [1.0, 2.0, 3.0];
554        assert!(dist3(a, a) < 1e-15);
555    }
556    #[test]
557    fn test_stream_compaction_filter_positive() {
558        let data = vec![-1.0f64, 2.0, -3.0, 4.0, 0.0, 5.0];
559        let (compacted, scatter_map) = stream_compaction(&data, |&v| v > 0.0);
560        assert_eq!(compacted, vec![2.0, 4.0, 5.0]);
561        assert_eq!(scatter_map, vec![1, 3, 5]);
562    }
563    #[test]
564    fn test_stream_compaction_empty_input() {
565        let data: Vec<f64> = Vec::new();
566        let (compacted, scatter_map) = stream_compaction(&data, |&v| v > 0.0);
567        assert!(compacted.is_empty());
568        assert!(scatter_map.is_empty());
569    }
570    #[test]
571    fn test_stream_compaction_all_pass() {
572        let data = vec![1.0f64, 2.0, 3.0];
573        let (compacted, scatter_map) = stream_compaction(&data, |_| true);
574        assert_eq!(compacted, data);
575        assert_eq!(scatter_map, vec![0, 1, 2]);
576    }
577    #[test]
578    fn test_stream_compaction_none_pass() {
579        let data = vec![1.0f64, 2.0, 3.0];
580        let (compacted, scatter_map) = stream_compaction(&data, |_| false);
581        assert!(compacted.is_empty());
582        assert!(scatter_map.is_empty());
583    }
584    #[test]
585    fn test_parallel_stream_compaction_matches_serial() {
586        let data = vec![3.0f64, -1.0, 5.0, -2.0, 7.0];
587        let (c_serial, s_serial) = stream_compaction(&data, |&v| v > 0.0);
588        let (c_par, s_par) = parallel_stream_compaction(&data, |&v| v > 0.0);
589        assert_eq!(c_serial, c_par);
590        assert_eq!(s_serial, s_par);
591    }
592    #[test]
593    fn test_segmented_reduce_sum_basic() {
594        let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
595        let segs = vec![0usize, 0, 1, 1, 1, 2];
596        let result = segmented_reduce_sum(&data, &segs);
597        assert_eq!(result.len(), 3);
598        assert!((result[0] - 3.0).abs() < 1e-12, "seg 0: {}", result[0]);
599        assert!((result[1] - 12.0).abs() < 1e-12, "seg 1: {}", result[1]);
600        assert!((result[2] - 6.0).abs() < 1e-12, "seg 2: {}", result[2]);
601    }
602    #[test]
603    fn test_segmented_reduce_sum_empty() {
604        let result = segmented_reduce_sum(&[], &[]);
605        assert!(result.is_empty());
606    }
607    #[test]
608    fn test_segmented_reduce_max_basic() {
609        let data = vec![1.0, 5.0, 2.0, 4.0, 3.0];
610        let segs = vec![0usize, 0, 1, 1, 1];
611        let result = segmented_reduce_max(&data, &segs);
612        assert!((result[0] - 5.0).abs() < 1e-12);
613        assert!((result[1] - 4.0).abs() < 1e-12);
614    }
615    #[test]
616    fn test_segmented_reduce_min_basic() {
617        let data = vec![1.0, 5.0, 2.0, 4.0, 3.0];
618        let segs = vec![0usize, 0, 1, 1, 1];
619        let result = segmented_reduce_min(&data, &segs);
620        assert!((result[0] - 1.0).abs() < 1e-12);
621        assert!((result[1] - 2.0).abs() < 1e-12);
622    }
623    #[test]
624    fn test_merge_sort_f64_basic() {
625        let data = vec![5.0, 3.0, 8.0, 1.0, 9.0, 2.0];
626        let sorted = merge_sort_f64(&data);
627        let mut expected = data.clone();
628        expected.sort_by(|a, b| a.partial_cmp(b).unwrap());
629        assert_eq!(sorted, expected);
630    }
631    #[test]
632    fn test_merge_sort_f64_empty() {
633        assert!(merge_sort_f64(&[]).is_empty());
634    }
635    #[test]
636    fn test_merge_sort_f64_single() {
637        assert_eq!(merge_sort_f64(&[42.0]), vec![42.0]);
638    }
639    #[test]
640    fn test_merge_sort_f64_already_sorted() {
641        let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
642        let sorted = merge_sort_f64(&data);
643        assert_eq!(sorted, data);
644    }
645    #[test]
646    fn test_merge_sort_argsort_is_permutation() {
647        let data = vec![5.0, 3.0, 8.0, 1.0, 9.0];
648        let indices = merge_sort_argsort(&data);
649        assert_eq!(indices.len(), data.len());
650        let mut check = indices.clone();
651        check.sort_unstable();
652        assert_eq!(check, vec![0, 1, 2, 3, 4]);
653        for w in indices.windows(2) {
654            assert!(data[w[0]] <= data[w[1]]);
655        }
656    }
657    #[test]
658    fn test_merge_sort_does_not_modify_input() {
659        let data = vec![3.0, 1.0, 4.0, 1.5, 9.0];
660        let original = data.clone();
661        let _ = merge_sort_f64(&data);
662        assert_eq!(data, original, "input should not be modified");
663    }
664    #[test]
665    fn test_bitonic_sort_power_of_two() {
666        let data = vec![8.0, 3.0, 6.0, 1.0, 7.0, 2.0, 5.0, 4.0];
667        let sorted = bitonic_sort(&data);
668        let mut expected = data.clone();
669        expected.sort_by(|a, b| a.partial_cmp(b).unwrap());
670        assert_eq!(sorted, expected);
671    }
672    #[test]
673    fn test_bitonic_sort_non_power_of_two() {
674        let data = vec![5.0, 3.0, 8.0, 1.0, 9.0];
675        let sorted = bitonic_sort(&data);
676        assert_eq!(sorted.len(), data.len());
677        for w in sorted.windows(2) {
678            assert!(w[0] <= w[1], "not sorted: {} > {}", w[0], w[1]);
679        }
680    }
681    #[test]
682    fn test_bitonic_sort_empty() {
683        assert!(bitonic_sort(&[]).is_empty());
684    }
685    #[test]
686    fn test_bitonic_sort_single() {
687        assert_eq!(bitonic_sort(&[42.0]), vec![42.0]);
688    }
689    #[test]
690    fn test_bitonic_sort_already_sorted() {
691        let data = vec![1.0, 2.0, 3.0, 4.0];
692        let sorted = bitonic_sort(&data);
693        assert_eq!(sorted, data);
694    }
695    #[test]
696    fn test_bitonic_argsort_is_permutation() {
697        let data = vec![5.0, 3.0, 8.0, 1.0, 9.0, 2.0, 4.0, 7.0];
698        let indices = bitonic_argsort(&data);
699        assert_eq!(indices.len(), data.len());
700        let mut check = indices.clone();
701        check.sort_unstable();
702        assert_eq!(check, vec![0, 1, 2, 3, 4, 5, 6, 7]);
703        for w in indices.windows(2) {
704            assert!(data[w[0]] <= data[w[1]], "not sorted via indices");
705        }
706    }
707    #[test]
708    fn test_bitonic_sort_matches_merge_sort() {
709        let data = vec![9.0, 7.0, 5.0, 3.0, 1.0, 2.0, 4.0, 6.0];
710        let bitonic_result = bitonic_sort(&data);
711        let merge_result = merge_sort_f64(&data);
712        assert_eq!(bitonic_result, merge_result);
713    }
714    #[test]
715    fn test_work_steal_queue_push_pop() {
716        let mut q: WorkStealQueue<usize> = WorkStealQueue::new();
717        q.push(1);
718        q.push(2);
719        q.push(3);
720        assert_eq!(q.pop(), Some(3));
721        assert_eq!(q.pop(), Some(2));
722        assert_eq!(q.pop(), Some(1));
723        assert_eq!(q.pop(), None);
724    }
725    #[test]
726    fn test_work_steal_queue_steal_from_front() {
727        let mut q: WorkStealQueue<usize> = WorkStealQueue::new();
728        q.push(10);
729        q.push(20);
730        assert_eq!(q.steal(), Some(10));
731        assert_eq!(q.steal(), Some(20));
732        assert_eq!(q.steal(), None);
733    }
734    #[test]
735    fn test_work_steal_queue_is_empty() {
736        let mut q: WorkStealQueue<i32> = WorkStealQueue::new();
737        assert!(q.is_empty());
738        q.push(5);
739        assert!(!q.is_empty());
740        let _ = q.pop();
741        assert!(q.is_empty());
742    }
743    #[test]
744    fn test_load_balance_metric_perfect() {
745        let loads = vec![10usize, 10, 10, 10];
746        let metric = compute_load_balance_metric(&loads);
747        assert!(
748            (metric - 1.0).abs() < 1e-12,
749            "perfect balance should give 1.0"
750        );
751    }
752    #[test]
753    fn test_load_balance_metric_imbalanced() {
754        let loads = vec![1usize, 1, 1, 100];
755        let metric = compute_load_balance_metric(&loads);
756        assert!(
757            metric < 0.5,
758            "heavily imbalanced should give < 0.5, got {metric}"
759        );
760    }
761    #[test]
762    fn test_load_balance_metric_empty() {
763        let metric = compute_load_balance_metric(&[]);
764        assert!((metric - 1.0).abs() < 1e-12);
765    }
766    #[test]
767    fn test_merge_sort_parallel_basic() {
768        let data = vec![5.0, 1.0, 4.0, 2.0, 8.0, 3.0];
769        let sorted = merge_sort_parallel(&data);
770        let mut expected = data.clone();
771        expected.sort_by(|a, b| a.partial_cmp(b).unwrap());
772        assert_eq!(sorted, expected);
773    }
774    #[test]
775    fn test_merge_sort_parallel_empty() {
776        assert!(merge_sort_parallel(&[]).is_empty());
777    }
778    #[test]
779    fn test_merge_sort_parallel_large() {
780        let data: Vec<f64> = (0..512).map(|i| (512 - i) as f64).collect();
781        let sorted = merge_sort_parallel(&data);
782        for w in sorted.windows(2) {
783            assert!(w[0] <= w[1], "not sorted: {} > {}", w[0], w[1]);
784        }
785        assert_eq!(sorted.len(), 512);
786    }
787    #[test]
788    fn test_merge_two_sorted_basic() {
789        let a = [1.0, 3.0, 5.0];
790        let b = [2.0, 4.0, 6.0];
791        let merged = merge_two_sorted(&a, &b);
792        assert_eq!(merged, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
793    }
794}