1#![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 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}