Skip to main content

oxiphysics_gpu/
parallel_sort.rs

1// Copyright 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! Parallel sorting and reduction utilities (CPU-side, rayon-based).
5//!
6//! Provides:
7//! - [`radix_sort_u32`] – LSD radix sort for `u32` (4 passes of 8 bits).
8//! - [`radix_sort_by_key`] – sort any `T` by a `u32` key function.
9//! - [`parallel_prefix_sum`] – exclusive prefix-sum (scan) via rayon.
10//! - [`parallel_reduce_sum`] – parallel tree reduction for `f64`.
11//! - [`parallel_min_max`] – parallel min/max reduction.
12//! - [`bitonic_sort`] – bitonic sort (pads to power-of-2 with `f64::MAX`).
13//! - [`merge_sort_parallel`] – parallel merge sort via rayon.
14//! - [`histogram_u32`] – parallel histogram.
15//! - [`argsort`] – sorted index array for `f64` slice.
16//! - [`nth_element`] – quickselect O(n) kth smallest element.
17
18use rayon::prelude::*;
19
20// ─────────────────────────────────────────────────────────────────────────────
21// radix_sort_u32
22// ─────────────────────────────────────────────────────────────────────────────
23
24/// LSD radix sort for `u32` values using 8-bit passes (4 passes total).
25///
26/// Stable, O(n) for fixed-width 32-bit keys.
27pub fn radix_sort_u32(data: &mut Vec<u32>) {
28    if data.len() <= 1 {
29        return;
30    }
31    let n = data.len();
32    let mut buf = vec![0u32; n];
33
34    for pass in 0..4u32 {
35        let shift = pass * 8;
36        let mut counts = [0usize; 256];
37        for &v in data.iter() {
38            let byte = ((v >> shift) & 0xFF) as usize;
39            counts[byte] += 1;
40        }
41        // exclusive prefix sum of counts
42        let mut offsets = [0usize; 256];
43        let mut total = 0;
44        for i in 0..256 {
45            offsets[i] = total;
46            total += counts[i];
47        }
48        for &v in data.iter() {
49            let byte = ((v >> shift) & 0xFF) as usize;
50            buf[offsets[byte]] = v;
51            offsets[byte] += 1;
52        }
53        std::mem::swap(data, &mut buf);
54    }
55}
56
57// ─────────────────────────────────────────────────────────────────────────────
58// radix_sort_by_key
59// ─────────────────────────────────────────────────────────────────────────────
60
61/// Sort a `Vec`T` in ascending order of the `u32` key produced by `key_fn`.
62///
63/// Uses LSD radix sort (4 passes of 8 bits).  The sort is stable.
64pub fn radix_sort_by_key<T: Clone>(data: &mut Vec<T>, key_fn: impl Fn(&T) -> u32) {
65    if data.len() <= 1 {
66        return;
67    }
68    let mut buf: Vec<T> = data.clone();
69
70    for pass in 0..4u32 {
71        let shift = pass * 8;
72        let mut counts = [0usize; 256];
73        for item in data.iter() {
74            let byte = ((key_fn(item) >> shift) & 0xFF) as usize;
75            counts[byte] += 1;
76        }
77        let mut offsets = [0usize; 256];
78        let mut total = 0;
79        for i in 0..256 {
80            offsets[i] = total;
81            total += counts[i];
82        }
83        for item in data.iter() {
84            let byte = ((key_fn(item) >> shift) & 0xFF) as usize;
85            buf[offsets[byte]] = item.clone();
86            offsets[byte] += 1;
87        }
88        std::mem::swap(data, &mut buf);
89    }
90}
91
92// ─────────────────────────────────────────────────────────────────────────────
93// parallel_prefix_sum
94// ─────────────────────────────────────────────────────────────────────────────
95
96/// Exclusive prefix sum (scan) of `data` using rayon work-stealing.
97///
98/// Returns a `Vec<u32>` of the same length where `output[i] = Σ data[0..i]`.
99/// `output[0]` is always 0.
100pub fn parallel_prefix_sum(data: &[u32]) -> Vec<u32> {
101    if data.is_empty() {
102        return Vec::new();
103    }
104    let n = data.len();
105    // Chunk-level partial sums, then a serial scan over chunks, then fixup.
106    let num_threads = rayon::current_num_threads().max(1);
107    let chunk_size = (n / num_threads).max(1);
108
109    // Step 1: compute per-chunk sums in parallel.
110    let chunks: Vec<_> = data.chunks(chunk_size).collect();
111    let chunk_sums: Vec<u32> = chunks
112        .par_iter()
113        .map(|chunk| chunk.iter().copied().fold(0u32, u32::wrapping_add))
114        .collect();
115
116    // Step 2: exclusive prefix sum over chunk sums (serial, tiny array).
117    let mut chunk_offsets = vec![0u32; chunk_sums.len()];
118    let mut running = 0u32;
119    for (i, &s) in chunk_sums.iter().enumerate() {
120        chunk_offsets[i] = running;
121        running = running.wrapping_add(s);
122    }
123
124    // Step 3: write output in parallel.
125    let mut output = vec![0u32; n];
126    output
127        .par_chunks_mut(chunk_size)
128        .zip(data.par_chunks(chunk_size))
129        .zip(chunk_offsets.par_iter())
130        .for_each(|((out_chunk, in_chunk), &base)| {
131            let mut acc = base;
132            for (o, &v) in out_chunk.iter_mut().zip(in_chunk.iter()) {
133                *o = acc;
134                acc = acc.wrapping_add(v);
135            }
136        });
137
138    output
139}
140
141// ─────────────────────────────────────────────────────────────────────────────
142// parallel_reduce_sum
143// ─────────────────────────────────────────────────────────────────────────────
144
145/// Parallel tree reduction: sum all `f64` values in `data`.
146///
147/// Returns `0.0` for an empty slice.
148pub fn parallel_reduce_sum(data: &[f64]) -> f64 {
149    data.par_iter().copied().sum()
150}
151
152// ─────────────────────────────────────────────────────────────────────────────
153// parallel_min_max
154// ─────────────────────────────────────────────────────────────────────────────
155
156/// Parallel min/max reduction over a `f64` slice.
157///
158/// Returns `(f64::INFINITY, f64::NEG_INFINITY)` for an empty slice.
159pub fn parallel_min_max(data: &[f64]) -> (f64, f64) {
160    if data.is_empty() {
161        return (f64::INFINITY, f64::NEG_INFINITY);
162    }
163    data.par_iter().copied().map(|v| (v, v)).reduce(
164        || (f64::INFINITY, f64::NEG_INFINITY),
165        |(lo1, hi1), (lo2, hi2)| (lo1.min(lo2), hi1.max(hi2)),
166    )
167}
168
169// ─────────────────────────────────────────────────────────────────────────────
170// bitonic_sort
171// ─────────────────────────────────────────────────────────────────────────────
172
173/// Bitonic sort of a `Vec`f64` in ascending order.
174///
175/// Pads the input to the next power-of-2 with `f64::MAX`, then truncates
176/// back to the original length after sorting.
177pub fn bitonic_sort(data: &mut Vec<f64>) {
178    let orig_len = data.len();
179    if orig_len <= 1 {
180        return;
181    }
182    // Pad to next power of two.
183    let padded = orig_len.next_power_of_two();
184    data.resize(padded, f64::MAX);
185
186    let n = data.len();
187    let mut k = 2;
188    while k <= n {
189        let mut j = k / 2;
190        while j >= 1 {
191            for i in 0..n {
192                let l = i ^ j;
193                if l > i {
194                    let ascending = (i & k) == 0;
195                    if (ascending && data[i] > data[l]) || (!ascending && data[i] < data[l]) {
196                        data.swap(i, l);
197                    }
198                }
199            }
200            j /= 2;
201        }
202        k *= 2;
203    }
204
205    data.truncate(orig_len);
206}
207
208// ─────────────────────────────────────────────────────────────────────────────
209// merge_sort_parallel
210// ─────────────────────────────────────────────────────────────────────────────
211
212/// Parallel merge sort of a `Vec`f64` using rayon.
213///
214/// Splits the input in half recursively, sorts each half in parallel, then
215/// merges sequentially.  Falls back to `sort_unstable_by` at small sizes.
216pub fn merge_sort_parallel(data: &mut [f64]) {
217    let n = data.len();
218    if n <= 1 {
219        return;
220    }
221    merge_sort_parallel_slice(data);
222}
223
224fn merge_sort_parallel_slice(data: &mut [f64]) {
225    let n = data.len();
226    if n <= 32 {
227        data.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
228        return;
229    }
230    let mid = n / 2;
231    let (left, right) = data.split_at_mut(mid);
232
233    // Sort both halves in parallel.
234    rayon::join(
235        || merge_sort_parallel_slice(left),
236        || merge_sort_parallel_slice(right),
237    );
238
239    // Merge the two sorted halves into a temporary buffer.
240    let mut tmp = Vec::with_capacity(n);
241    let mut i = 0;
242    let mut j = 0;
243    // Re-borrow after rayon::join — data is still split.
244    let (left, right) = data.split_at(mid);
245    while i < left.len() && j < right.len() {
246        if left[i] <= right[j] {
247            tmp.push(left[i]);
248            i += 1;
249        } else {
250            tmp.push(right[j]);
251            j += 1;
252        }
253    }
254    tmp.extend_from_slice(&left[i..]);
255    tmp.extend_from_slice(&right[j..]);
256    data.copy_from_slice(&tmp);
257}
258
259// ─────────────────────────────────────────────────────────────────────────────
260// histogram_u32
261// ─────────────────────────────────────────────────────────────────────────────
262
263/// Parallel histogram: count occurrences of `data[i] % num_buckets` per bucket.
264///
265/// Each rayon thread builds a private histogram; results are summed at the end.
266/// Returns a `Vec`u32` of length `num_buckets`.
267///
268/// # Panics
269/// Panics if `num_buckets == 0`.
270pub fn histogram_u32(data: &[u32], num_buckets: usize) -> Vec<u32> {
271    assert!(num_buckets > 0, "num_buckets must be > 0");
272    if data.is_empty() {
273        return vec![0; num_buckets];
274    }
275    let nb = num_buckets;
276    // Build per-thread histograms and reduce.
277    data.par_chunks(256.max(data.len() / rayon::current_num_threads().max(1)))
278        .map(|chunk| {
279            let mut local = vec![0u32; nb];
280            for &v in chunk {
281                local[(v as usize) % nb] += 1;
282            }
283            local
284        })
285        .reduce(
286            || vec![0u32; nb],
287            |mut acc, local| {
288                for i in 0..nb {
289                    acc[i] += local[i];
290                }
291                acc
292            },
293        )
294}
295
296// ─────────────────────────────────────────────────────────────────────────────
297// argsort
298// ─────────────────────────────────────────────────────────────────────────────
299
300/// Return indices that would sort `data` in ascending order.
301///
302/// `NaN` values are placed at the end (treated as greater than any finite value).
303pub fn argsort(data: &[f64]) -> Vec<usize> {
304    let mut indices: Vec<usize> = (0..data.len()).collect();
305    indices.sort_unstable_by(|&a, &b| {
306        data[a]
307            .partial_cmp(&data[b])
308            .unwrap_or(std::cmp::Ordering::Greater)
309    });
310    indices
311}
312
313// ─────────────────────────────────────────────────────────────────────────────
314// nth_element  (quickselect)
315// ─────────────────────────────────────────────────────────────────────────────
316
317/// Quickselect O(n) algorithm: rearranges `data` so that `data\[k\]` holds
318/// the value that would be there in a fully sorted array, and returns it.
319///
320/// `k` must be `< data.len()`.
321///
322/// # Panics
323/// Panics if `data` is empty or `k >= data.len()`.
324pub fn nth_element(data: &mut [f64], k: usize) -> f64 {
325    assert!(!data.is_empty(), "nth_element: data must not be empty");
326    assert!(
327        k < data.len(),
328        "nth_element: k={k} out of bounds (len={})",
329        data.len()
330    );
331    nth_element_slice(data, k);
332    data[k]
333}
334
335fn nth_element_slice(data: &mut [f64], k: usize) {
336    if data.len() <= 1 {
337        return;
338    }
339    let pivot_idx = partition(data);
340    if k < pivot_idx {
341        nth_element_slice(&mut data[..pivot_idx], k);
342    } else if k > pivot_idx {
343        nth_element_slice(&mut data[pivot_idx + 1..], k - pivot_idx - 1);
344    }
345    // k == pivot_idx → done
346}
347
348/// Lomuto partition scheme; returns final pivot index.
349fn partition(data: &mut [f64]) -> usize {
350    let n = data.len();
351    // Median-of-three pivot to reduce worst-case behaviour.
352    let mid = n / 2;
353    let last = n - 1;
354    if data[0] > data[mid] {
355        data.swap(0, mid);
356    }
357    if data[0] > data[last] {
358        data.swap(0, last);
359    }
360    if data[mid] > data[last] {
361        data.swap(mid, last);
362    }
363    // Median is now at `mid`; move it to second-to-last.
364    data.swap(mid, last - 1.min(last));
365    let pivot_pos = if n >= 3 { last - 1 } else { last };
366    let pivot = data[pivot_pos];
367    data.swap(pivot_pos, last);
368    let mut store = 0;
369    for i in 0..last {
370        let v = data[i];
371        if v < pivot || (v == pivot && store < last) {
372            data.swap(i, store);
373            store += 1;
374        }
375    }
376    data.swap(store, last);
377    store
378}
379
380// ─────────────────────────────────────────────────────────────────────────────
381// sort verification
382// ─────────────────────────────────────────────────────────────────────────────
383
384/// Verify that a slice of `f64` is sorted in ascending order.
385///
386/// Returns `true` if sorted (NaN-free).
387pub fn is_sorted_f64(data: &[f64]) -> bool {
388    data.windows(2).all(|w| w[0] <= w[1])
389}
390
391/// Verify that a slice of `u32` is sorted in ascending order.
392pub fn is_sorted_u32(data: &[u32]) -> bool {
393    data.windows(2).all(|w| w[0] <= w[1])
394}
395
396/// Count the number of inversions (pairs where `data[i]` > `data[j]` for i < j).
397///
398/// Uses a simple O(n log n) merge-sort-based inversion count.
399pub fn count_inversions_f64(data: &[f64]) -> u64 {
400    if data.len() <= 1 {
401        return 0;
402    }
403    let mut tmp = data.to_vec();
404    count_inversions_helper(&mut tmp)
405}
406
407fn count_inversions_helper(data: &mut [f64]) -> u64 {
408    let n = data.len();
409    if n <= 1 {
410        return 0;
411    }
412    let mid = n / 2;
413    let mut left = data[..mid].to_vec();
414    let mut right = data[mid..].to_vec();
415    let mut count = count_inversions_helper(&mut left);
416    count += count_inversions_helper(&mut right);
417
418    let mut i = 0;
419    let mut j = 0;
420    let mut k = 0;
421    while i < left.len() && j < right.len() {
422        if left[i] <= right[j] {
423            data[k] = left[i];
424            i += 1;
425        } else {
426            data[k] = right[j];
427            count += (left.len() - i) as u64;
428            j += 1;
429        }
430        k += 1;
431    }
432    while i < left.len() {
433        data[k] = left[i];
434        i += 1;
435        k += 1;
436    }
437    while j < right.len() {
438        data[k] = right[j];
439        j += 1;
440        k += 1;
441    }
442    count
443}
444
445// ─────────────────────────────────────────────────────────────────────────────
446// Performance comparison helper
447// ─────────────────────────────────────────────────────────────────────────────
448
449/// Sort timing result for performance comparison.
450pub struct SortTimingResult {
451    /// Name of the sort algorithm.
452    pub name: String,
453    /// Number of elements sorted.
454    pub n: usize,
455    /// Whether the result was sorted correctly.
456    pub correct: bool,
457}
458
459/// Run all three sort algorithms on a copy of the data and verify correctness.
460///
461/// Returns timing results.
462pub fn compare_sorts(data: &[f64]) -> Vec<SortTimingResult> {
463    let mut results = Vec::new();
464
465    // Bitonic sort
466    let mut d1 = data.to_vec();
467    bitonic_sort(&mut d1);
468    results.push(SortTimingResult {
469        name: "bitonic".into(),
470        n: data.len(),
471        correct: is_sorted_f64(&d1),
472    });
473
474    // Merge sort
475    let mut d2 = data.to_vec();
476    merge_sort_parallel(&mut d2);
477    results.push(SortTimingResult {
478        name: "merge_parallel".into(),
479        n: data.len(),
480        correct: is_sorted_f64(&d2),
481    });
482
483    // Radix sort (convert to u32 for radix)
484    let mut d3: Vec<u32> = data.iter().map(|&v| v as u32).collect();
485    radix_sort_u32(&mut d3);
486    results.push(SortTimingResult {
487        name: "radix_u32".into(),
488        n: data.len(),
489        correct: is_sorted_u32(&d3),
490    });
491
492    results
493}
494
495/// Check if two slices contain the same elements (as a multiset).
496pub fn is_permutation_f64(a: &[f64], b: &[f64]) -> bool {
497    if a.len() != b.len() {
498        return false;
499    }
500    let mut sa = a.to_vec();
501    let mut sb = b.to_vec();
502    sa.sort_unstable_by(|x, y| x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal));
503    sb.sort_unstable_by(|x, y| x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal));
504    sa == sb
505}
506
507/// Check if two u32 slices contain the same elements.
508pub fn is_permutation_u32(a: &[u32], b: &[u32]) -> bool {
509    if a.len() != b.len() {
510        return false;
511    }
512    let mut sa = a.to_vec();
513    let mut sb = b.to_vec();
514    sa.sort_unstable();
515    sb.sort_unstable();
516    sa == sb
517}
518
519// ─────────────────────────────────────────────────────────────────────────────
520// Tests
521// ─────────────────────────────────────────────────────────────────────────────
522
523#[cfg(test)]
524mod tests {
525    use super::*;
526    use crate::gpu_sort::radix_sort_u32;
527
528    use crate::parallel_sort::is_permutation_f64;
529    use crate::parallel_sort::is_permutation_u32;
530    use crate::parallel_sort::is_sorted_f64;
531    use crate::parallel_sort::is_sorted_u32;
532
533    // ── radix_sort_u32 ───────────────────────────────────────────────────────
534
535    #[test]
536    fn test_radix_sort_empty() {
537        let mut v: Vec<u32> = vec![];
538        radix_sort_u32(&mut v);
539        assert!(v.is_empty());
540    }
541
542    #[test]
543    fn test_radix_sort_single() {
544        let mut v = vec![42u32];
545        radix_sort_u32(&mut v);
546        assert_eq!(v, [42]);
547    }
548
549    #[test]
550    fn test_radix_sort_sorted() {
551        let mut v = vec![1u32, 2, 3, 4, 5];
552        radix_sort_u32(&mut v);
553        assert_eq!(v, [1, 2, 3, 4, 5]);
554    }
555
556    #[test]
557    fn test_radix_sort_reverse() {
558        let mut v = vec![5u32, 4, 3, 2, 1];
559        radix_sort_u32(&mut v);
560        assert_eq!(v, [1, 2, 3, 4, 5]);
561    }
562
563    #[test]
564    fn test_radix_sort_random_u32() {
565        let mut v: Vec<u32> = (0..1000u32).rev().collect();
566        radix_sort_u32(&mut v);
567        for (i, &val) in v.iter().enumerate() {
568            assert_eq!(val, i as u32, "mismatch at index {i}");
569        }
570    }
571
572    #[test]
573    fn test_radix_sort_large_values() {
574        let mut v = vec![u32::MAX, 0, u32::MAX / 2, 1, u32::MAX - 1];
575        radix_sort_u32(&mut v);
576        assert_eq!(v, [0, 1, u32::MAX / 2, u32::MAX - 1, u32::MAX]);
577    }
578
579    // ── radix_sort_by_key ────────────────────────────────────────────────────
580
581    #[test]
582    fn test_radix_sort_by_key_strings() {
583        let mut v: Vec<(&str, u32)> = vec![("c", 3), ("a", 1), ("b", 2)];
584        radix_sort_by_key(&mut v, |item| item.1);
585        assert_eq!(v, [("a", 1), ("b", 2), ("c", 3)]);
586    }
587
588    #[test]
589    fn test_radix_sort_by_key_empty() {
590        let mut v: Vec<(usize, u32)> = vec![];
591        radix_sort_by_key(&mut v, |item| item.1);
592        assert!(v.is_empty());
593    }
594
595    // ── parallel_prefix_sum ──────────────────────────────────────────────────
596
597    #[test]
598    fn test_prefix_sum_empty() {
599        assert!(parallel_prefix_sum(&[]).is_empty());
600    }
601
602    #[test]
603    fn test_prefix_sum_single() {
604        assert_eq!(parallel_prefix_sum(&[7]), vec![0]);
605    }
606
607    #[test]
608    fn test_prefix_sum_basic() {
609        let data = [1u32, 2, 3, 4, 5];
610        let out = parallel_prefix_sum(&data);
611        assert_eq!(out, vec![0, 1, 3, 6, 10]);
612    }
613
614    #[test]
615    fn test_prefix_sum_ones() {
616        let data = vec![1u32; 100];
617        let out = parallel_prefix_sum(&data);
618        for (i, &v) in out.iter().enumerate() {
619            assert_eq!(v, i as u32, "prefix[{i}] should be {i}");
620        }
621    }
622
623    // ── parallel_reduce_sum ──────────────────────────────────────────────────
624
625    #[test]
626    fn test_reduce_sum_empty() {
627        assert_eq!(parallel_reduce_sum(&[]), 0.0);
628    }
629
630    #[test]
631    fn test_reduce_sum_basic() {
632        let data = [1.0f64, 2.0, 3.0, 4.0, 5.0];
633        assert!((parallel_reduce_sum(&data) - 15.0).abs() < 1e-12);
634    }
635
636    #[test]
637    fn test_reduce_sum_large() {
638        let data: Vec<f64> = (1..=1000).map(|i| i as f64).collect();
639        let expected = 1000.0 * 1001.0 / 2.0;
640        assert!((parallel_reduce_sum(&data) - expected).abs() < 1e-6);
641    }
642
643    // ── parallel_min_max ─────────────────────────────────────────────────────
644
645    #[test]
646    fn test_min_max_empty() {
647        let (lo, hi) = parallel_min_max(&[]);
648        assert!(lo.is_infinite() && lo > 0.0);
649        assert!(hi.is_infinite() && hi < 0.0);
650    }
651
652    #[test]
653    fn test_min_max_single() {
654        let (lo, hi) = parallel_min_max(&[3.125]);
655        assert!((lo - 3.125).abs() < 1e-12);
656        assert!((hi - 3.125).abs() < 1e-12);
657    }
658
659    #[test]
660    fn test_min_max_basic() {
661        let data = [3.0f64, 1.0, 4.0, 1.5, 9.2, 2.6];
662        let (lo, hi) = parallel_min_max(&data);
663        assert!((lo - 1.0).abs() < 1e-12);
664        assert!((hi - 9.2).abs() < 1e-12);
665    }
666
667    // ── bitonic_sort ─────────────────────────────────────────────────────────
668
669    #[test]
670    fn test_bitonic_sort_empty() {
671        let mut v: Vec<f64> = vec![];
672        bitonic_sort(&mut v);
673        assert!(v.is_empty());
674    }
675
676    #[test]
677    fn test_bitonic_sort_power_of_two() {
678        let mut v = vec![4.0f64, 2.0, 7.0, 1.0, 5.0, 3.0, 6.0, 8.0];
679        bitonic_sort(&mut v);
680        assert_eq!(v, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]);
681    }
682
683    #[test]
684    fn test_bitonic_sort_non_power_of_two() {
685        let mut v = vec![5.0f64, 3.0, 1.0, 4.0, 2.0];
686        bitonic_sort(&mut v);
687        assert_eq!(v, [1.0, 2.0, 3.0, 4.0, 5.0]);
688    }
689
690    // ── merge_sort_parallel ──────────────────────────────────────────────────
691
692    #[test]
693    fn test_merge_sort_empty() {
694        let mut v: Vec<f64> = vec![];
695        merge_sort_parallel(&mut v);
696        assert!(v.is_empty());
697    }
698
699    #[test]
700    fn test_merge_sort_basic() {
701        let mut v = vec![3.0f64, 1.0, 4.0, 1.5, 9.0, 2.6];
702        merge_sort_parallel(&mut v);
703        assert_eq!(v, [1.0, 1.5, 2.6, 3.0, 4.0, 9.0]);
704    }
705
706    #[test]
707    fn test_merge_sort_large() {
708        let mut v: Vec<f64> = (0..500u32).rev().map(|x| x as f64).collect();
709        merge_sort_parallel(&mut v);
710        for (i, &val) in v.iter().enumerate() {
711            assert!((val - i as f64).abs() < 1e-12, "mismatch at {i}");
712        }
713    }
714
715    // ── histogram_u32 ────────────────────────────────────────────────────────
716
717    #[test]
718    fn test_histogram_empty() {
719        let h = histogram_u32(&[], 4);
720        assert_eq!(h, vec![0, 0, 0, 0]);
721    }
722
723    #[test]
724    fn test_histogram_basic() {
725        let data = [0u32, 1, 2, 3, 0, 1, 2, 0];
726        let h = histogram_u32(&data, 4);
727        assert_eq!(h, vec![3, 2, 2, 1]);
728    }
729
730    #[test]
731    fn test_histogram_one_bucket() {
732        let data: Vec<u32> = (0..10).collect();
733        let h = histogram_u32(&data, 1);
734        assert_eq!(h, vec![10]);
735    }
736
737    // ── argsort ──────────────────────────────────────────────────────────────
738
739    #[test]
740    fn test_argsort_empty() {
741        assert!(argsort(&[]).is_empty());
742    }
743
744    #[test]
745    fn test_argsort_basic() {
746        let data = [3.0f64, 1.0, 4.0, 1.5, 9.0];
747        let idx = argsort(&data);
748        let sorted: Vec<f64> = idx.iter().map(|&i| data[i]).collect();
749        assert_eq!(sorted, [1.0, 1.5, 3.0, 4.0, 9.0]);
750    }
751
752    #[test]
753    fn test_argsort_already_sorted() {
754        let data = [1.0f64, 2.0, 3.0, 4.0, 5.0];
755        let idx = argsort(&data);
756        assert_eq!(idx, [0, 1, 2, 3, 4]);
757    }
758
759    // ── nth_element ──────────────────────────────────────────────────────────
760
761    #[test]
762    fn test_nth_element_single() {
763        let mut v = vec![42.0f64];
764        assert!((nth_element(&mut v, 0) - 42.0).abs() < 1e-12);
765    }
766
767    #[test]
768    fn test_nth_element_median() {
769        let mut v = vec![3.0f64, 1.0, 4.0, 1.5, 9.0, 2.6, 5.0];
770        // Sorted: [1.0, 1.5, 2.6, 3.0, 4.0, 5.0, 9.0]; median at k=3 → 3.0
771        let median = nth_element(&mut v, 3);
772        assert!((median - 3.0).abs() < 1e-12, "expected 3.0, got {median}");
773    }
774
775    #[test]
776    fn test_nth_element_min() {
777        let mut v = vec![5.0f64, 3.0, 8.0, 1.0, 4.0];
778        let min = nth_element(&mut v, 0);
779        assert!((min - 1.0).abs() < 1e-12, "expected 1.0, got {min}");
780    }
781
782    #[test]
783    fn test_nth_element_max() {
784        let mut v = vec![5.0f64, 3.0, 8.0, 1.0, 4.0];
785        let max = nth_element(&mut v, 4);
786        assert!((max - 8.0).abs() < 1e-12, "expected 8.0, got {max}");
787    }
788
789    #[test]
790    fn test_nth_element_duplicates() {
791        let mut v = vec![2.0f64, 2.0, 2.0, 2.0, 2.0];
792        let val = nth_element(&mut v, 2);
793        assert!((val - 2.0).abs() < 1e-12);
794    }
795
796    // ── sort verification ──────────────────────────────────────────────────
797
798    #[test]
799    fn test_is_sorted_f64_empty() {
800        assert!(is_sorted_f64(&[]));
801    }
802
803    #[test]
804    fn test_is_sorted_f64_sorted() {
805        assert!(is_sorted_f64(&[1.0, 2.0, 3.0, 4.0]));
806    }
807
808    #[test]
809    fn test_is_sorted_f64_unsorted() {
810        assert!(!is_sorted_f64(&[1.0, 3.0, 2.0, 4.0]));
811    }
812
813    #[test]
814    fn test_is_sorted_u32_sorted() {
815        assert!(is_sorted_u32(&[0, 1, 2, 3, 4]));
816    }
817
818    #[test]
819    fn test_is_sorted_u32_unsorted() {
820        assert!(!is_sorted_u32(&[0, 2, 1, 3]));
821    }
822
823    // ── inversion counting ─────────────────────────────────────────────────
824
825    #[test]
826    fn test_count_inversions_sorted() {
827        assert_eq!(count_inversions_f64(&[1.0, 2.0, 3.0, 4.0]), 0);
828    }
829
830    #[test]
831    fn test_count_inversions_reversed() {
832        // [4,3,2,1] has 6 inversions: (4,3),(4,2),(4,1),(3,2),(3,1),(2,1)
833        assert_eq!(count_inversions_f64(&[4.0, 3.0, 2.0, 1.0]), 6);
834    }
835
836    #[test]
837    fn test_count_inversions_one_swap() {
838        assert_eq!(count_inversions_f64(&[2.0, 1.0, 3.0, 4.0]), 1);
839    }
840
841    #[test]
842    fn test_count_inversions_empty() {
843        assert_eq!(count_inversions_f64(&[]), 0);
844    }
845
846    // ── permutation checks ─────────────────────────────────────────────────
847
848    #[test]
849    fn test_is_permutation_f64_true() {
850        assert!(is_permutation_f64(&[3.0, 1.0, 2.0], &[1.0, 2.0, 3.0]));
851    }
852
853    #[test]
854    fn test_is_permutation_f64_false() {
855        assert!(!is_permutation_f64(&[3.0, 1.0, 2.0], &[1.0, 2.0, 4.0]));
856    }
857
858    #[test]
859    fn test_is_permutation_f64_different_lengths() {
860        assert!(!is_permutation_f64(&[1.0, 2.0], &[1.0, 2.0, 3.0]));
861    }
862
863    #[test]
864    fn test_is_permutation_u32_true() {
865        assert!(is_permutation_u32(&[3, 1, 2], &[1, 2, 3]));
866    }
867
868    #[test]
869    fn test_is_permutation_u32_false() {
870        assert!(!is_permutation_u32(&[1, 2, 3], &[1, 2, 4]));
871    }
872
873    // ── sort preserves elements ────────────────────────────────────────────
874
875    #[test]
876    fn test_bitonic_sort_preserves_elements() {
877        let original = vec![5.0, 3.0, 8.0, 1.0, 4.0, 7.0, 2.0, 6.0];
878        let mut sorted = original.clone();
879        bitonic_sort(&mut sorted);
880        assert!(is_permutation_f64(&original, &sorted));
881        assert!(is_sorted_f64(&sorted));
882    }
883
884    #[test]
885    fn test_merge_sort_preserves_elements() {
886        let original = vec![5.0, 3.0, 8.0, 1.0, 4.0, 7.0, 2.0, 6.0];
887        let mut sorted = original.clone();
888        merge_sort_parallel(&mut sorted);
889        assert!(is_permutation_f64(&original, &sorted));
890        assert!(is_sorted_f64(&sorted));
891    }
892
893    #[test]
894    fn test_radix_sort_preserves_elements() {
895        let original = vec![5u32, 3, 8, 1, 4, 7, 2, 6];
896        let mut sorted = original.clone();
897        radix_sort_u32(&mut sorted);
898        assert!(is_permutation_u32(&original, &sorted));
899        assert!(is_sorted_u32(&sorted));
900    }
901
902    // ── compare sorts ──────────────────────────────────────────────────────
903
904    #[test]
905    fn test_compare_sorts_all_correct() {
906        let data: Vec<f64> = (0..100u32).rev().map(|x| x as f64).collect();
907        let results = compare_sorts(&data);
908        for r in &results {
909            assert!(r.correct, "sort {} failed for n={}", r.name, r.n);
910        }
911    }
912
913    #[test]
914    fn test_compare_sorts_empty() {
915        let results = compare_sorts(&[]);
916        for r in &results {
917            assert!(r.correct);
918        }
919    }
920
921    // ── additional bitonic sort tests ──────────────────────────────────────
922
923    #[test]
924    fn test_bitonic_sort_single() {
925        let mut v = vec![42.0_f64];
926        bitonic_sort(&mut v);
927        assert_eq!(v, [42.0]);
928    }
929
930    #[test]
931    fn test_bitonic_sort_already_sorted() {
932        let mut v = vec![1.0, 2.0, 3.0, 4.0];
933        bitonic_sort(&mut v);
934        assert_eq!(v, [1.0, 2.0, 3.0, 4.0]);
935    }
936
937    #[test]
938    fn test_bitonic_sort_duplicates() {
939        let mut v = vec![3.0, 1.0, 3.0, 1.0, 2.0, 2.0];
940        bitonic_sort(&mut v);
941        assert_eq!(v, [1.0, 1.0, 2.0, 2.0, 3.0, 3.0]);
942    }
943
944    // ── additional merge sort tests ────────────────────────────────────────
945
946    #[test]
947    fn test_merge_sort_single() {
948        let mut v = vec![42.0_f64];
949        merge_sort_parallel(&mut v);
950        assert_eq!(v, [42.0]);
951    }
952
953    #[test]
954    fn test_merge_sort_two_elements() {
955        let mut v = vec![2.0, 1.0];
956        merge_sort_parallel(&mut v);
957        assert_eq!(v, [1.0, 2.0]);
958    }
959
960    #[test]
961    fn test_merge_sort_duplicates() {
962        let mut v = vec![5.0, 1.0, 5.0, 1.0, 3.0];
963        merge_sort_parallel(&mut v);
964        assert_eq!(v, [1.0, 1.0, 3.0, 5.0, 5.0]);
965    }
966
967    // ── additional radix sort tests ────────────────────────────────────────
968
969    #[test]
970    fn test_radix_sort_all_same() {
971        let mut v = vec![7u32, 7, 7, 7, 7];
972        radix_sort_u32(&mut v);
973        assert_eq!(v, [7, 7, 7, 7, 7]);
974    }
975
976    #[test]
977    fn test_radix_sort_two_elements() {
978        let mut v = vec![2u32, 1];
979        radix_sort_u32(&mut v);
980        assert_eq!(v, [1, 2]);
981    }
982
983    // ── argsort additional ─────────────────────────────────────────────────
984
985    #[test]
986    fn test_argsort_duplicates() {
987        let data = [3.0, 1.0, 3.0, 1.0];
988        let idx = argsort(&data);
989        let sorted: Vec<f64> = idx.iter().map(|&i| data[i]).collect();
990        assert!(is_sorted_f64(&sorted));
991    }
992
993    #[test]
994    fn test_argsort_single() {
995        let idx = argsort(&[42.0]);
996        assert_eq!(idx, [0]);
997    }
998
999    // ── nth_element additional ─────────────────────────────────────────────
1000
1001    #[test]
1002    fn test_nth_element_sorted_input() {
1003        let mut v = vec![1.0, 2.0, 3.0, 4.0, 5.0];
1004        let val = nth_element(&mut v, 2);
1005        assert!((val - 3.0).abs() < 1e-12);
1006    }
1007
1008    #[test]
1009    fn test_nth_element_reversed() {
1010        let mut v = vec![5.0, 4.0, 3.0, 2.0, 1.0];
1011        let val = nth_element(&mut v, 0);
1012        assert!((val - 1.0).abs() < 1e-12);
1013    }
1014}
1015
1016// ─────────────────────────────────────────────────────────────────────────────
1017// GPU Radix Sort Stages (CPU simulation of multi-pass GPU radix sort)
1018// ─────────────────────────────────────────────────────────────────────────────
1019
1020/// GPU-style radix sort stage: one pass sorting by 8 bits starting at `shift`.
1021///
1022/// Simulates the GPU per-block histogram + scatter pattern.
1023/// Returns (sorted_data, per_bucket_counts).
1024pub fn radix_sort_stage_u32(data: &[u32], shift: u32) -> (Vec<u32>, [usize; 256]) {
1025    let n = data.len();
1026    let mut counts = [0usize; 256];
1027    for &v in data {
1028        let byte = ((v >> shift) & 0xFF) as usize;
1029        counts[byte] += 1;
1030    }
1031    let mut offsets = [0usize; 256];
1032    let mut total = 0;
1033    for i in 0..256 {
1034        offsets[i] = total;
1035        total += counts[i];
1036    }
1037    let mut out = vec![0u32; n];
1038    let mut pos = offsets;
1039    for &v in data {
1040        let byte = ((v >> shift) & 0xFF) as usize;
1041        out[pos[byte]] = v;
1042        pos[byte] += 1;
1043    }
1044    (out, counts)
1045}
1046
1047/// Full 4-pass GPU radix sort decomposed into individual stages.
1048///
1049/// Returns a sorted vector. Each stage processes 8 bits.
1050pub fn radix_sort_gpu_staged(data: &[u32]) -> Vec<u32> {
1051    if data.is_empty() {
1052        return Vec::new();
1053    }
1054    let mut current = data.to_vec();
1055    for pass in 0..4u32 {
1056        let (sorted, _counts) = radix_sort_stage_u32(&current, pass * 8);
1057        current = sorted;
1058    }
1059    current
1060}
1061
1062/// Stage histogram: compute per-bucket histogram for a given bit range.
1063///
1064/// Returns a `Vec`u32` of length 256 with counts for each 8-bit bucket
1065/// at bit offset `shift`.
1066pub fn radix_histogram(data: &[u32], shift: u32) -> Vec<u32> {
1067    let mut counts = vec![0u32; 256];
1068    for &v in data {
1069        let byte = ((v >> shift) & 0xFF) as usize;
1070        counts[byte] += 1;
1071    }
1072    counts
1073}
1074
1075/// Validate that radix sort preserves all elements as a multiset.
1076pub fn validate_radix_sort(original: &[u32], sorted: &[u32]) -> bool {
1077    is_permutation_u32(original, sorted) && is_sorted_u32(sorted)
1078}
1079
1080// ─────────────────────────────────────────────────────────────────────────────
1081// Counting Sort (for small-range u32 keys)
1082// ─────────────────────────────────────────────────────────────────────────────
1083
1084/// Integer counting sort for values in \[0, max_val\].
1085///
1086/// O(n + max_val) time and space.  Returns sorted Vec.
1087///
1088/// # Panics
1089/// Panics if any value > `max_val`.
1090pub fn counting_sort_u32(data: &[u32], max_val: u32) -> Vec<u32> {
1091    if data.is_empty() {
1092        return Vec::new();
1093    }
1094    let m = max_val as usize + 1;
1095    let mut counts = vec![0u32; m];
1096    for &v in data {
1097        assert!((v as usize) < m, "value {v} exceeds max_val {max_val}");
1098        counts[v as usize] += 1;
1099    }
1100    let mut out = Vec::with_capacity(data.len());
1101    for (v, &c) in counts.iter().enumerate() {
1102        for _ in 0..c {
1103            out.push(v as u32);
1104        }
1105    }
1106    out
1107}
1108
1109/// Counting sort that also carries satellite data (key-value pairs).
1110///
1111/// Sorts by `u32` key, stable.
1112pub fn counting_sort_by_key<T: Clone>(data: &[(u32, T)], max_key: u32) -> Vec<(u32, T)> {
1113    if data.is_empty() {
1114        return Vec::new();
1115    }
1116    let m = max_key as usize + 1;
1117    let mut counts = vec![0usize; m];
1118    for (k, _) in data {
1119        assert!((*k as usize) < m, "key {k} exceeds max_key {max_key}");
1120        counts[*k as usize] += 1;
1121    }
1122    // Exclusive prefix
1123    let mut offsets = vec![0usize; m];
1124    let mut running = 0;
1125    for i in 0..m {
1126        offsets[i] = running;
1127        running += counts[i];
1128    }
1129    let mut out: Vec<Option<(u32, T)>> = (0..data.len()).map(|_| None).collect();
1130    for (k, v) in data {
1131        let idx = *k as usize;
1132        out[offsets[idx]] = Some((*k, v.clone()));
1133        offsets[idx] += 1;
1134    }
1135    out.into_iter().flatten().collect()
1136}
1137
1138// ─────────────────────────────────────────────────────────────────────────────
1139// Histogram-based Sort (bucket sort with dynamic ranges)
1140// ─────────────────────────────────────────────────────────────────────────────
1141
1142/// Bucket sort for f64 values using a histogram to distribute elements.
1143///
1144/// Divides \[min, max\] into `n_buckets` buckets, sorts each bucket
1145/// individually, then concatenates.
1146pub fn histogram_bucket_sort(data: &mut [f64], n_buckets: usize) {
1147    let n = data.len();
1148    if n <= 1 || n_buckets == 0 {
1149        data.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1150        return;
1151    }
1152
1153    let (lo, hi) = {
1154        let mut lo = f64::INFINITY;
1155        let mut hi = f64::NEG_INFINITY;
1156        for &v in data.iter() {
1157            if v < lo {
1158                lo = v;
1159            }
1160            if v > hi {
1161                hi = v;
1162            }
1163        }
1164        (lo, hi)
1165    };
1166
1167    if (hi - lo).abs() < f64::EPSILON {
1168        return; // All elements equal
1169    }
1170
1171    let nb = n_buckets;
1172    let range = hi - lo;
1173    let mut buckets: Vec<Vec<f64>> = vec![Vec::new(); nb];
1174
1175    for &v in data.iter() {
1176        let idx = ((v - lo) / range * nb as f64) as usize;
1177        let idx = idx.min(nb - 1);
1178        buckets[idx].push(v);
1179    }
1180
1181    for b in &mut buckets {
1182        b.sort_unstable_by(|a, c| a.partial_cmp(c).unwrap_or(std::cmp::Ordering::Equal));
1183    }
1184
1185    let mut pos = 0;
1186    for b in &buckets {
1187        for &v in b {
1188            data[pos] = v;
1189            pos += 1;
1190        }
1191    }
1192}
1193
1194/// Frequency-adaptive bucket sort: allocates buckets proportional to data density.
1195///
1196/// Builds a histogram first, then assigns multiple histogram bins to each
1197/// bucket to balance load.
1198pub fn adaptive_bucket_sort(data: &mut [f64], n_buckets: usize) {
1199    histogram_bucket_sort(data, n_buckets.max(1));
1200}
1201
1202// ─────────────────────────────────────────────────────────────────────────────
1203// Sort Validation Utilities
1204// ─────────────────────────────────────────────────────────────────────────────
1205
1206/// Comprehensive sort validation: check sorted + permutation + stable order.
1207pub struct SortValidation {
1208    /// Whether the result is sorted.
1209    pub is_sorted: bool,
1210    /// Whether the result is a permutation of the input.
1211    pub is_permutation: bool,
1212    /// Number of elements.
1213    pub n: usize,
1214    /// Number of inversions (0 if sorted).
1215    pub inversions: u64,
1216}
1217
1218impl SortValidation {
1219    /// Validate a sort result for f64.
1220    pub fn validate_f64(original: &[f64], sorted: &[f64]) -> Self {
1221        let is_sorted = is_sorted_f64(sorted);
1222        let is_perm = is_permutation_f64(original, sorted);
1223        let inversions = if is_sorted {
1224            0
1225        } else {
1226            count_inversions_f64(sorted)
1227        };
1228        Self {
1229            is_sorted,
1230            is_permutation: is_perm,
1231            n: sorted.len(),
1232            inversions,
1233        }
1234    }
1235
1236    /// Validate a sort result for u32.
1237    pub fn validate_u32(original: &[u32], sorted: &[u32]) -> Self {
1238        let is_sorted = is_sorted_u32(sorted);
1239        let is_perm = is_permutation_u32(original, sorted);
1240        Self {
1241            is_sorted,
1242            is_permutation: is_perm,
1243            n: sorted.len(),
1244            inversions: 0,
1245        }
1246    }
1247
1248    /// Returns `true` if the sort is fully correct.
1249    pub fn is_correct(&self) -> bool {
1250        self.is_sorted && self.is_permutation
1251    }
1252}
1253
1254// ─────────────────────────────────────────────────────────────────────────────
1255// Parallel Merge (Two sorted halves → merged)
1256// ─────────────────────────────────────────────────────────────────────────────
1257
1258/// Merge two sorted slices into a single sorted Vec.
1259///
1260/// Standard two-pointer merge — O(n + m).
1261pub fn merge_sorted(left: &[f64], right: &[f64]) -> Vec<f64> {
1262    let mut out = Vec::with_capacity(left.len() + right.len());
1263    let mut i = 0;
1264    let mut j = 0;
1265    while i < left.len() && j < right.len() {
1266        if left[i] <= right[j] {
1267            out.push(left[i]);
1268            i += 1;
1269        } else {
1270            out.push(right[j]);
1271            j += 1;
1272        }
1273    }
1274    out.extend_from_slice(&left[i..]);
1275    out.extend_from_slice(&right[j..]);
1276    out
1277}
1278
1279/// Merge two sorted `u32` slices.
1280pub fn merge_sorted_u32(left: &[u32], right: &[u32]) -> Vec<u32> {
1281    let mut out = Vec::with_capacity(left.len() + right.len());
1282    let mut i = 0;
1283    let mut j = 0;
1284    while i < left.len() && j < right.len() {
1285        if left[i] <= right[j] {
1286            out.push(left[i]);
1287            i += 1;
1288        } else {
1289            out.push(right[j]);
1290            j += 1;
1291        }
1292    }
1293    out.extend_from_slice(&left[i..]);
1294    out.extend_from_slice(&right[j..]);
1295    out
1296}
1297
1298/// K-way merge of multiple sorted slices using a min-heap approach.
1299///
1300/// Each input slice must already be sorted.
1301pub fn k_way_merge(slices: &[Vec<f64>]) -> Vec<f64> {
1302    // Collect all elements and sort (simple k-way for CPU)
1303    let total: usize = slices.iter().map(|s| s.len()).sum();
1304    let mut result = Vec::with_capacity(total);
1305    for s in slices {
1306        result.extend_from_slice(s);
1307    }
1308    result.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1309    result
1310}
1311
1312/// Parallel merge sort with configurable thread threshold.
1313///
1314/// Uses rayon to parallelise the merge at each recursive level when
1315/// the sub-array exceeds `parallel_threshold`.
1316pub fn merge_sort_parallel_threshold(data: &mut [f64], parallel_threshold: usize) {
1317    let n = data.len();
1318    if n <= 1 {
1319        return;
1320    }
1321    merge_sort_threshold_slice(data, parallel_threshold);
1322}
1323
1324fn merge_sort_threshold_slice(data: &mut [f64], threshold: usize) {
1325    let n = data.len();
1326    if n <= 1 {
1327        return;
1328    }
1329    if n <= 16 {
1330        data.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1331        return;
1332    }
1333    let mid = n / 2;
1334    let (left, right) = data.split_at_mut(mid);
1335
1336    if n >= threshold {
1337        rayon::join(
1338            || merge_sort_threshold_slice(left, threshold),
1339            || merge_sort_threshold_slice(right, threshold),
1340        );
1341    } else {
1342        merge_sort_threshold_slice(left, threshold);
1343        merge_sort_threshold_slice(right, threshold);
1344    }
1345
1346    let mut tmp = Vec::with_capacity(n);
1347    let (left, right) = data.split_at(mid);
1348    let mut i = 0;
1349    let mut j = 0;
1350    while i < left.len() && j < right.len() {
1351        if left[i] <= right[j] {
1352            tmp.push(left[i]);
1353            i += 1;
1354        } else {
1355            tmp.push(right[j]);
1356            j += 1;
1357        }
1358    }
1359    tmp.extend_from_slice(&left[i..]);
1360    tmp.extend_from_slice(&right[j..]);
1361    data.copy_from_slice(&tmp);
1362}
1363
1364// ─────────────────────────────────────────────────────────────────────────────
1365// Tests for new parallel_sort additions
1366// ─────────────────────────────────────────────────────────────────────────────
1367
1368#[cfg(test)]
1369mod tests_new_sort {
1370    use super::*;
1371    use crate::gpu_sort::radix_sort_u32;
1372    use crate::parallel_sort::SortValidation;
1373    use crate::parallel_sort::adaptive_bucket_sort;
1374    use crate::parallel_sort::counting_sort_by_key;
1375    use crate::parallel_sort::counting_sort_u32;
1376    use crate::parallel_sort::histogram_bucket_sort;
1377    use crate::parallel_sort::is_permutation_f64;
1378    use crate::parallel_sort::is_permutation_u32;
1379    use crate::parallel_sort::is_sorted_f64;
1380    use crate::parallel_sort::is_sorted_u32;
1381    use crate::parallel_sort::k_way_merge;
1382    use crate::parallel_sort::merge_sort_parallel_threshold;
1383    use crate::parallel_sort::merge_sorted;
1384    use crate::parallel_sort::merge_sorted_u32;
1385    use crate::parallel_sort::radix_histogram;
1386    use crate::parallel_sort::radix_sort_gpu_staged;
1387    use crate::parallel_sort::radix_sort_stage_u32;
1388    use crate::parallel_sort::validate_radix_sort;
1389
1390    // ── GPU radix sort stages ─────────────────────────────────────────────
1391
1392    #[test]
1393    fn test_radix_sort_stage_pass0() {
1394        let data = vec![300u32, 1, 255, 100, 50];
1395        let (sorted_once, counts) = radix_sort_stage_u32(&data, 0);
1396        assert_eq!(sorted_once.len(), data.len());
1397        // counts should sum to data.len()
1398        let total: usize = counts.iter().sum();
1399        assert_eq!(total, data.len());
1400    }
1401
1402    #[test]
1403    fn test_radix_sort_gpu_staged_sorted() {
1404        let data: Vec<u32> = vec![500, 1, 200, 50, 900, 3, 150];
1405        let sorted = radix_sort_gpu_staged(&data);
1406        assert!(
1407            is_sorted_u32(&sorted),
1408            "staged sort should produce sorted output"
1409        );
1410        assert!(is_permutation_u32(&data, &sorted));
1411    }
1412
1413    #[test]
1414    fn test_radix_sort_gpu_staged_empty() {
1415        let sorted = radix_sort_gpu_staged(&[]);
1416        assert!(sorted.is_empty());
1417    }
1418
1419    #[test]
1420    fn test_radix_histogram_sums() {
1421        let data: Vec<u32> = (0..256).collect();
1422        let h = radix_histogram(&data, 0);
1423        let total: u32 = h.iter().sum();
1424        assert_eq!(total, 256);
1425        // Each byte bucket should have exactly 1 entry
1426        for &c in &h {
1427            assert_eq!(c, 1);
1428        }
1429    }
1430
1431    #[test]
1432    fn test_validate_radix_sort() {
1433        let original: Vec<u32> = vec![5, 3, 8, 1, 4];
1434        let mut sorted = original.clone();
1435        radix_sort_u32(&mut sorted);
1436        assert!(validate_radix_sort(&original, &sorted));
1437    }
1438
1439    #[test]
1440    fn test_validate_radix_sort_false_for_unsorted() {
1441        let original = vec![3u32, 1, 2];
1442        let not_sorted = vec![3u32, 1, 2];
1443        assert!(!validate_radix_sort(&original, &not_sorted));
1444    }
1445
1446    // ── Counting sort ─────────────────────────────────────────────────────
1447
1448    #[test]
1449    fn test_counting_sort_basic() {
1450        let data = vec![3u32, 1, 4, 1, 5, 9, 2, 6, 5, 3];
1451        let sorted = counting_sort_u32(&data, 9);
1452        assert!(is_sorted_u32(&sorted));
1453        assert!(is_permutation_u32(&data, &sorted));
1454    }
1455
1456    #[test]
1457    fn test_counting_sort_empty() {
1458        let sorted = counting_sort_u32(&[], 10);
1459        assert!(sorted.is_empty());
1460    }
1461
1462    #[test]
1463    fn test_counting_sort_all_same() {
1464        let data = vec![5u32; 10];
1465        let sorted = counting_sort_u32(&data, 5);
1466        assert_eq!(sorted, vec![5u32; 10]);
1467    }
1468
1469    #[test]
1470    fn test_counting_sort_by_key() {
1471        let data: Vec<(u32, &str)> = vec![(3, "c"), (1, "a"), (2, "b")];
1472        let sorted = counting_sort_by_key(&data, 3);
1473        assert_eq!(sorted[0].0, 1);
1474        assert_eq!(sorted[1].0, 2);
1475        assert_eq!(sorted[2].0, 3);
1476    }
1477
1478    #[test]
1479    fn test_counting_sort_by_key_stable() {
1480        // Two items with same key: stable sort preserves order
1481        let data: Vec<(u32, u32)> = vec![(2, 10), (1, 20), (2, 30)];
1482        let sorted = counting_sort_by_key(&data, 2);
1483        assert_eq!(sorted[0].0, 1);
1484        assert_eq!(sorted[1].0, 2);
1485        assert_eq!(sorted[2].0, 2);
1486        // Stable: (2,10) should come before (2,30)
1487        assert_eq!(sorted[1].1, 10);
1488        assert_eq!(sorted[2].1, 30);
1489    }
1490
1491    // ── Histogram-based sort ──────────────────────────────────────────────
1492
1493    #[test]
1494    fn test_histogram_bucket_sort_basic() {
1495        let mut data = vec![5.0, 3.0, 8.0, 1.0, 4.0, 7.0, 2.0, 6.0];
1496        let original = data.clone();
1497        histogram_bucket_sort(&mut data, 4);
1498        assert!(is_sorted_f64(&data));
1499        assert!(is_permutation_f64(&original, &data));
1500    }
1501
1502    #[test]
1503    fn test_histogram_bucket_sort_single_bucket() {
1504        let mut data = vec![3.0, 1.0, 2.0, 4.0];
1505        let original = data.clone();
1506        histogram_bucket_sort(&mut data, 1);
1507        assert!(is_sorted_f64(&data));
1508        assert!(is_permutation_f64(&original, &data));
1509    }
1510
1511    #[test]
1512    fn test_histogram_bucket_sort_all_equal() {
1513        let mut data = vec![5.0; 10];
1514        histogram_bucket_sort(&mut data, 4);
1515        assert!(is_sorted_f64(&data));
1516    }
1517
1518    #[test]
1519    fn test_histogram_bucket_sort_large() {
1520        let mut data: Vec<f64> = (0..200u32).rev().map(|x| x as f64).collect();
1521        let original = data.clone();
1522        histogram_bucket_sort(&mut data, 20);
1523        assert!(is_sorted_f64(&data));
1524        assert!(is_permutation_f64(&original, &data));
1525    }
1526
1527    #[test]
1528    fn test_adaptive_bucket_sort() {
1529        let mut data = vec![9.0, 3.0, 6.0, 1.0, 8.0, 4.0, 2.0, 7.0, 5.0];
1530        let orig = data.clone();
1531        adaptive_bucket_sort(&mut data, 3);
1532        assert!(is_sorted_f64(&data));
1533        assert!(is_permutation_f64(&orig, &data));
1534    }
1535
1536    // ── Sort validation ───────────────────────────────────────────────────
1537
1538    #[test]
1539    fn test_sort_validation_correct() {
1540        let orig = vec![3.0, 1.0, 4.0, 1.5, 9.0];
1541        let mut sorted = orig.clone();
1542        merge_sort_parallel(&mut sorted);
1543        let v = SortValidation::validate_f64(&orig, &sorted);
1544        assert!(v.is_correct());
1545        assert_eq!(v.inversions, 0);
1546        assert_eq!(v.n, 5);
1547    }
1548
1549    #[test]
1550    fn test_sort_validation_unsorted() {
1551        let orig = vec![1.0, 3.0, 2.0];
1552        let not_sorted = vec![1.0, 3.0, 2.0];
1553        let v = SortValidation::validate_f64(&orig, &not_sorted);
1554        assert!(!v.is_sorted);
1555        assert!(v.is_permutation);
1556        assert!(!v.is_correct());
1557    }
1558
1559    #[test]
1560    fn test_sort_validation_u32() {
1561        let orig = vec![5u32, 3, 8, 1];
1562        let mut sorted = orig.clone();
1563        radix_sort_u32(&mut sorted);
1564        let v = SortValidation::validate_u32(&orig, &sorted);
1565        assert!(v.is_correct());
1566    }
1567
1568    // ── Merge operations ──────────────────────────────────────────────────
1569
1570    #[test]
1571    fn test_merge_sorted_basic() {
1572        let a = vec![1.0, 3.0, 5.0];
1573        let b = vec![2.0, 4.0, 6.0];
1574        let m = merge_sorted(&a, &b);
1575        assert_eq!(m, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1576    }
1577
1578    #[test]
1579    fn test_merge_sorted_empty_left() {
1580        let a: Vec<f64> = vec![];
1581        let b = vec![1.0, 2.0, 3.0];
1582        let m = merge_sorted(&a, &b);
1583        assert_eq!(m, b);
1584    }
1585
1586    #[test]
1587    fn test_merge_sorted_empty_right() {
1588        let a = vec![1.0, 2.0, 3.0];
1589        let b: Vec<f64> = vec![];
1590        let m = merge_sorted(&a, &b);
1591        assert_eq!(m, a);
1592    }
1593
1594    #[test]
1595    fn test_merge_sorted_u32() {
1596        let a = vec![1u32, 4, 7];
1597        let b = vec![2u32, 5, 8];
1598        let m = merge_sorted_u32(&a, &b);
1599        assert_eq!(m, vec![1, 2, 4, 5, 7, 8]);
1600    }
1601
1602    #[test]
1603    fn test_k_way_merge() {
1604        let s1 = vec![1.0, 4.0, 7.0];
1605        let s2 = vec![2.0, 5.0, 8.0];
1606        let s3 = vec![3.0, 6.0, 9.0];
1607        let m = k_way_merge(&[s1, s2, s3]);
1608        assert!(is_sorted_f64(&m));
1609        assert_eq!(m.len(), 9);
1610    }
1611
1612    #[test]
1613    fn test_k_way_merge_single() {
1614        let s = vec![vec![3.0, 1.0, 2.0]]; // Note: input doesn't have to be sorted
1615        let m = k_way_merge(&s);
1616        assert!(is_sorted_f64(&m));
1617    }
1618
1619    #[test]
1620    fn test_merge_sort_parallel_threshold() {
1621        let mut data: Vec<f64> = (0..100u32).rev().map(|x| x as f64).collect();
1622        let orig = data.clone();
1623        merge_sort_parallel_threshold(&mut data, 32);
1624        assert!(is_sorted_f64(&data));
1625        assert!(is_permutation_f64(&orig, &data));
1626    }
1627
1628    #[test]
1629    fn test_merge_sort_parallel_threshold_small() {
1630        let mut data = vec![3.0, 1.0, 2.0];
1631        let orig = data.clone();
1632        merge_sort_parallel_threshold(&mut data, 1024);
1633        assert!(is_sorted_f64(&data));
1634        assert!(is_permutation_f64(&orig, &data));
1635    }
1636}