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