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