Skip to main content

oxiphysics_gpu/
gpu_sort.rs

1// Copyright 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! GPU-style parallel sorting algorithms (CPU simulation).
5//!
6//! This module provides GPU-pattern algorithms for sorting and related
7//! operations on `f32`/`u32` data:
8//! - Bitonic sort (power-of-2 padded)
9//! - LSD radix sort for `u32` and `f32`
10//! - Exclusive prefix sum (Blelloch scan style)
11//! - Parallel histogram
12//! - Counting sort
13//! - Morton Z-order sort for 3-D point clouds
14//! - [`GpuSortBuffer`] — key+value buffer with `sort_pairs`
15//! - Parallel merge
16
17// ─────────────────────────────────────────────────────────────────────────────
18// Bitonic sort (f32)
19// ─────────────────────────────────────────────────────────────────────────────
20
21/// Sort a `Vec`f32` in ascending order using the bitonic sort algorithm.
22///
23/// If `data.len()` is not a power of two, the vector is padded to the next
24/// power of two with `f32::MAX` sentinel values, sorted, then truncated.
25pub fn bitonic_sort(data: &mut Vec<f32>) {
26    let orig = data.len();
27    if orig <= 1 {
28        return;
29    }
30    let padded = next_pow2(orig);
31    data.resize(padded, f32::MAX);
32    bitonic_sort_slice_f32(data);
33    data.truncate(orig);
34}
35
36/// Sort a `Vec`T` by the `u32` key returned by `key_fn`.
37///
38/// Padding uses sentinel key `u32::MAX`; after sorting, only the first
39/// `orig_len` elements (those with valid indices) are retained.
40pub fn bitonic_sort_by_key<T: Clone>(data: &mut [T], key_fn: impl Fn(&T) -> u32) {
41    let orig = data.len();
42    if orig <= 1 {
43        return;
44    }
45    let padded = next_pow2(orig);
46    // Build (key, original-index) pairs and pad.
47    let mut pairs: Vec<(u32, usize)> = data
48        .iter()
49        .enumerate()
50        .map(|(i, v)| (key_fn(v), i))
51        .collect();
52    pairs.resize(padded, (u32::MAX, usize::MAX));
53
54    // Bitonic sort the pairs.
55    let n = pairs.len();
56    let mut k = 2;
57    while k <= n {
58        let mut j = k / 2;
59        while j >= 1 {
60            for i in 0..n {
61                let l = i ^ j;
62                if l > i {
63                    let ascending = (i & k) == 0;
64                    let should_swap = if ascending {
65                        pairs[i].0 > pairs[l].0
66                    } else {
67                        pairs[i].0 < pairs[l].0
68                    };
69                    if should_swap {
70                        pairs.swap(i, l);
71                    }
72                }
73            }
74            j /= 2;
75        }
76        k *= 2;
77    }
78
79    // Reconstruct data: take the first `orig` sorted entries that have valid indices.
80    let old: Vec<T> = data.to_vec();
81    let mut out: Vec<T> = pairs
82        .iter()
83        .filter(|&&(_, idx)| idx < orig)
84        .map(|&(_, idx)| old[idx].clone())
85        .collect();
86    out.truncate(orig);
87    // In case some items were duplicated by the sentinel logic, ensure length.
88    for (i, v) in out.into_iter().enumerate().take(orig) {
89        data[i] = v;
90    }
91}
92
93/// Internal: in-place bitonic sort on a slice whose length is a power of two.
94fn bitonic_sort_slice_f32(data: &mut [f32]) {
95    let n = data.len();
96    let mut k = 2;
97    while k <= n {
98        let mut j = k / 2;
99        while j >= 1 {
100            for i in 0..n {
101                let l = i ^ j;
102                if l > i {
103                    let ascending = (i & k) == 0;
104                    let should_swap = if ascending {
105                        data[i] > data[l]
106                    } else {
107                        data[i] < data[l]
108                    };
109                    if should_swap {
110                        data.swap(i, l);
111                    }
112                }
113            }
114            j /= 2;
115        }
116        k *= 2;
117    }
118}
119
120// ─────────────────────────────────────────────────────────────────────────────
121// Radix sort
122// ─────────────────────────────────────────────────────────────────────────────
123
124/// LSD radix sort for `u32` using 4 passes of 8-bit digits.
125///
126/// Stable, O(n) complexity for 32-bit keys.
127pub fn radix_sort_u32(data: &mut Vec<u32>) {
128    if data.len() <= 1 {
129        return;
130    }
131    let n = data.len();
132    let mut buf = vec![0u32; n];
133    for pass in 0..4u32 {
134        let shift = pass * 8;
135        let mut counts = [0usize; 256];
136        for &v in data.iter() {
137            counts[((v >> shift) & 0xFF) as usize] += 1;
138        }
139        let mut offsets = [0usize; 256];
140        let mut total = 0;
141        for i in 0..256 {
142            offsets[i] = total;
143            total += counts[i];
144        }
145        for &v in data.iter() {
146            let b = ((v >> shift) & 0xFF) as usize;
147            buf[offsets[b]] = v;
148            offsets[b] += 1;
149        }
150        std::mem::swap(data, &mut buf);
151    }
152}
153
154/// Sort `f32` values by reinterpreting bits and flipping the sign bit for
155/// negatives so that the full IEEE 754 ordering maps to unsigned integer order.
156///
157/// After sorting, the bits are un-flipped back to valid `f32` values.
158pub fn radix_sort_f32(data: &mut [f32]) {
159    if data.len() <= 1 {
160        return;
161    }
162    // Map f32 to a sortable u32.
163    let mut keys: Vec<u32> = data.iter().map(|&v| f32_to_sort_key(v)).collect();
164    radix_sort_u32(&mut keys);
165    for (dst, k) in data.iter_mut().zip(keys.iter()) {
166        *dst = sort_key_to_f32(*k);
167    }
168}
169
170/// Convert an `f32` to a sortable `u32` (flip sign bit; for negatives also
171/// flip the remaining bits so the order is preserved).
172#[inline]
173fn f32_to_sort_key(v: f32) -> u32 {
174    let bits = v.to_bits();
175    if bits >> 31 == 0 {
176        bits | 0x8000_0000 // positive: set sign bit
177    } else {
178        !bits // negative: flip all bits
179    }
180}
181
182/// Inverse of [`f32_to_sort_key`].
183#[inline]
184fn sort_key_to_f32(key: u32) -> f32 {
185    let bits = if key >> 31 != 0 {
186        key & 0x7FFF_FFFF // positive
187    } else {
188        !key // negative
189    };
190    f32::from_bits(bits)
191}
192
193// ─────────────────────────────────────────────────────────────────────────────
194// Prefix sum (Blelloch exclusive scan)
195// ─────────────────────────────────────────────────────────────────────────────
196
197/// Exclusive prefix sum (Blelloch scan) for `u32` values.
198///
199/// Returns a new `Vec`u32` where `result\[i\] = sum(data\[0..i\])`.
200/// `result\[0\]` is always `0`.
201pub fn prefix_sum(data: &[u32]) -> Vec<u32> {
202    let mut result = Vec::with_capacity(data.len());
203    let mut acc = 0u32;
204    for &v in data {
205        result.push(acc);
206        acc = acc.wrapping_add(v);
207    }
208    result
209}
210
211// ─────────────────────────────────────────────────────────────────────────────
212// Histogram
213// ─────────────────────────────────────────────────────────────────────────────
214
215/// Compute a histogram of `data` into `n_bins` equal-width bins over
216/// `\[min_val, max_val)`.
217///
218/// Values outside the range are clamped into the first or last bin.
219/// Returns a `Vec`u32` of length `n_bins`.
220///
221/// # Panics
222/// Panics if `n_bins == 0`.
223pub fn histogram(data: &[u32], n_bins: usize) -> Vec<u32> {
224    assert!(n_bins > 0, "n_bins must be > 0");
225    if data.is_empty() {
226        return vec![0u32; n_bins];
227    }
228    let max_val = *data.iter().max().unwrap_or(&0) as u64 + 1;
229    let mut bins = vec![0u32; n_bins];
230    for &v in data {
231        let idx = ((v as u64 * n_bins as u64) / max_val) as usize;
232        let idx = idx.min(n_bins - 1);
233        bins[idx] += 1;
234    }
235    bins
236}
237
238// ─────────────────────────────────────────────────────────────────────────────
239// Counting sort
240// ─────────────────────────────────────────────────────────────────────────────
241
242/// Counting sort for `u32` values bounded by `max_val` (inclusive).
243///
244/// Creates a count array of size `max_val + 1` and reconstructs sorted data
245/// from it. O(n + max_val) time and space.
246pub fn counting_sort(data: &mut [u32], max_val: u32) {
247    if data.len() <= 1 {
248        return;
249    }
250    let size = (max_val as usize).saturating_add(1);
251    let mut counts = vec![0u32; size];
252    for &v in data.iter() {
253        let idx = (v as usize).min(size - 1);
254        counts[idx] += 1;
255    }
256    let mut pos = 0usize;
257    for (val, &cnt) in counts.iter().enumerate() {
258        for _ in 0..cnt {
259            data[pos] = val as u32;
260            pos += 1;
261        }
262    }
263}
264
265// ─────────────────────────────────────────────────────────────────────────────
266// Morton sort (Z-order curve for 3-D points)
267// ─────────────────────────────────────────────────────────────────────────────
268
269/// Sort 3-D `f32` points by their Morton Z-order (space-filling curve) code.
270///
271/// Coordinates are quantised to 10-bit integers before interleaving, which
272/// gives 30-bit Morton codes that fit in a `u32`.
273pub fn morton_sort_3d(points: &mut [[f32; 3]]) {
274    if points.len() <= 1 {
275        return;
276    }
277    // Compute bounding box.
278    let mut lo = [f32::INFINITY; 3];
279    let mut hi = [f32::NEG_INFINITY; 3];
280    for p in points.iter() {
281        for d in 0..3 {
282            lo[d] = lo[d].min(p[d]);
283            hi[d] = hi[d].max(p[d]);
284        }
285    }
286    let scale: Vec<f32> = (0..3)
287        .map(|d| {
288            let range = hi[d] - lo[d];
289            if range > 0.0 { 1023.0 / range } else { 0.0 }
290        })
291        .collect();
292
293    let mut pairs: Vec<(u32, usize)> = points
294        .iter()
295        .enumerate()
296        .map(|(i, p)| {
297            let ix = ((p[0] - lo[0]) * scale[0]) as u32;
298            let iy = ((p[1] - lo[1]) * scale[1]) as u32;
299            let iz = ((p[2] - lo[2]) * scale[2]) as u32;
300            (morton3(ix.min(1023), iy.min(1023), iz.min(1023)), i)
301        })
302        .collect();
303
304    pairs.sort_unstable_by_key(|&(code, _)| code);
305
306    let old: Vec<[f32; 3]> = points.to_vec();
307    for (i, &(_, idx)) in pairs.iter().enumerate() {
308        points[i] = old[idx];
309    }
310}
311
312/// Interleave the lower 10 bits of x, y, z into a 30-bit Morton code.
313fn morton3(x: u32, y: u32, z: u32) -> u32 {
314    spread_bits(x) | (spread_bits(y) << 1) | (spread_bits(z) << 2)
315}
316
317/// Spread the lower 10 bits of `v` into every third bit position.
318fn spread_bits(mut v: u32) -> u32 {
319    v &= 0x3FF; // keep lower 10 bits
320    v = (v | (v << 16)) & 0x030000FF;
321    v = (v | (v << 8)) & 0x0300F00F;
322    v = (v | (v << 4)) & 0x030C30C3;
323    v = (v | (v << 2)) & 0x09249249;
324    v
325}
326
327// ─────────────────────────────────────────────────────────────────────────────
328// GpuSortBuffer
329// ─────────────────────────────────────────────────────────────────────────────
330
331/// A buffer abstraction that holds parallel key and value arrays.
332///
333/// Provides `sort_pairs` to co-sort both arrays by key using radix sort.
334#[derive(Debug, Clone)]
335pub struct GpuSortBuffer {
336    /// Sort keys.
337    pub keys: Vec<u32>,
338    /// Associated values (same length as `keys`).
339    pub values: Vec<u32>,
340}
341
342impl GpuSortBuffer {
343    /// Create a new `GpuSortBuffer` with the given key and value arrays.
344    ///
345    /// # Panics
346    /// Panics if `keys` and `values` have different lengths.
347    pub fn new(keys: Vec<u32>, values: Vec<u32>) -> Self {
348        assert_eq!(
349            keys.len(),
350            values.len(),
351            "keys and values must have equal length"
352        );
353        Self { keys, values }
354    }
355
356    /// Create an empty buffer.
357    pub fn empty() -> Self {
358        Self {
359            keys: Vec::new(),
360            values: Vec::new(),
361        }
362    }
363
364    /// Number of key-value pairs.
365    pub fn len(&self) -> usize {
366        self.keys.len()
367    }
368
369    /// Returns `true` if the buffer is empty.
370    pub fn is_empty(&self) -> bool {
371        self.keys.is_empty()
372    }
373
374    /// Sort keys and values together using LSD radix sort, stable by key.
375    pub fn sort_pairs(&mut self) {
376        if self.len() <= 1 {
377            return;
378        }
379        let n = self.len();
380        let mut key_buf = vec![0u32; n];
381        let mut val_buf = vec![0u32; n];
382        for pass in 0..4u32 {
383            let shift = pass * 8;
384            let mut counts = [0usize; 256];
385            for &k in self.keys.iter() {
386                counts[((k >> shift) & 0xFF) as usize] += 1;
387            }
388            let mut offsets = [0usize; 256];
389            let mut total = 0;
390            for i in 0..256 {
391                offsets[i] = total;
392                total += counts[i];
393            }
394            for (i, &k) in self.keys.iter().enumerate() {
395                let b = ((k >> shift) & 0xFF) as usize;
396                let dest = offsets[b];
397                key_buf[dest] = k;
398                val_buf[dest] = self.values[i];
399                offsets[b] += 1;
400            }
401            std::mem::swap(&mut self.keys, &mut key_buf);
402            std::mem::swap(&mut self.values, &mut val_buf);
403        }
404    }
405
406    /// Append a key-value pair to the buffer.
407    pub fn push(&mut self, key: u32, value: u32) {
408        self.keys.push(key);
409        self.values.push(value);
410    }
411}
412
413// ─────────────────────────────────────────────────────────────────────────────
414// Parallel merge
415// ─────────────────────────────────────────────────────────────────────────────
416
417/// Merge two sorted `f32` slices into a new sorted `Vec`f32`.
418///
419/// Both inputs must already be sorted in non-decreasing order.
420/// Uses a standard two-pointer merge (O(n+m)).
421pub fn parallel_merge(left: &[f32], right: &[f32]) -> Vec<f32> {
422    let mut result = Vec::with_capacity(left.len() + right.len());
423    let (mut i, mut j) = (0, 0);
424    while i < left.len() && j < right.len() {
425        if left[i] <= right[j] {
426            result.push(left[i]);
427            i += 1;
428        } else {
429            result.push(right[j]);
430            j += 1;
431        }
432    }
433    result.extend_from_slice(&left[i..]);
434    result.extend_from_slice(&right[j..]);
435    result
436}
437
438// ─────────────────────────────────────────────────────────────────────────────
439// Internal helpers
440// ─────────────────────────────────────────────────────────────────────────────
441
442/// Round `n` up to the next power of two (returns 1 for 0).
443fn next_pow2(n: usize) -> usize {
444    if n == 0 {
445        return 1;
446    }
447    let mut p = 1usize;
448    while p < n {
449        p <<= 1;
450    }
451    p
452}
453
454// ─────────────────────────────────────────────────────────────────────────────
455// Tests
456// ─────────────────────────────────────────────────────────────────────────────
457
458#[cfg(test)]
459mod tests {
460    use super::*;
461
462    // ── next_pow2 ─────────────────────────────────────────────────────────────
463
464    #[test]
465    fn test_next_pow2_zero() {
466        assert_eq!(next_pow2(0), 1);
467    }
468
469    #[test]
470    fn test_next_pow2_one() {
471        assert_eq!(next_pow2(1), 1);
472    }
473
474    #[test]
475    fn test_next_pow2_exact() {
476        assert_eq!(next_pow2(8), 8);
477    }
478
479    #[test]
480    fn test_next_pow2_non_exact() {
481        assert_eq!(next_pow2(9), 16);
482    }
483
484    // ── bitonic_sort ──────────────────────────────────────────────────────────
485
486    #[test]
487    fn test_bitonic_sort_power_of_two() {
488        let mut data = vec![4.0f32, 2.0, 7.0, 1.0, 8.0, 3.0, 6.0, 5.0];
489        bitonic_sort(&mut data);
490        assert_eq!(data, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]);
491    }
492
493    #[test]
494    fn test_bitonic_sort_non_power_of_two() {
495        let mut data = vec![5.0f32, 3.0, 1.0, 4.0, 2.0];
496        bitonic_sort(&mut data);
497        assert_eq!(data, vec![1.0, 2.0, 3.0, 4.0, 5.0]);
498    }
499
500    #[test]
501    fn test_bitonic_sort_empty() {
502        let mut data: Vec<f32> = vec![];
503        bitonic_sort(&mut data);
504        assert!(data.is_empty());
505    }
506
507    #[test]
508    fn test_bitonic_sort_single() {
509        let mut data = vec![42.0f32];
510        bitonic_sort(&mut data);
511        assert_eq!(data, vec![42.0]);
512    }
513
514    #[test]
515    fn test_bitonic_sort_already_sorted() {
516        let mut data = vec![1.0f32, 2.0, 3.0, 4.0];
517        bitonic_sort(&mut data);
518        assert_eq!(data, vec![1.0, 2.0, 3.0, 4.0]);
519    }
520
521    #[test]
522    fn test_bitonic_sort_reverse() {
523        let mut data = vec![8.0f32, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0];
524        bitonic_sort(&mut data);
525        assert_eq!(data, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]);
526    }
527
528    #[test]
529    fn test_bitonic_sort_duplicates() {
530        let mut data = vec![3.0f32, 1.0, 3.0, 2.0, 1.0];
531        bitonic_sort(&mut data);
532        assert_eq!(data, vec![1.0, 1.0, 2.0, 3.0, 3.0]);
533    }
534
535    #[test]
536    fn test_bitonic_sort_large_non_pow2() {
537        let mut data: Vec<f32> = (0..100).map(|i| (100 - i) as f32).collect();
538        bitonic_sort(&mut data);
539        for i in 0..data.len() - 1 {
540            assert!(data[i] <= data[i + 1]);
541        }
542    }
543
544    // ── bitonic_sort_by_key ───────────────────────────────────────────────────
545
546    #[test]
547    fn test_bitonic_sort_by_key_u32() {
548        let mut data = vec![30u32, 10, 20, 40];
549        bitonic_sort_by_key(&mut data, |x| *x);
550        assert_eq!(data, vec![10, 20, 30, 40]);
551    }
552
553    #[test]
554    fn test_bitonic_sort_by_key_empty() {
555        let mut data: Vec<u32> = vec![];
556        bitonic_sort_by_key(&mut data, |x| *x);
557        assert!(data.is_empty());
558    }
559
560    #[test]
561    fn test_bitonic_sort_by_key_single() {
562        let mut data = vec![42u32];
563        bitonic_sort_by_key(&mut data, |x| *x);
564        assert_eq!(data, vec![42]);
565    }
566
567    // ── radix_sort_u32 ────────────────────────────────────────────────────────
568
569    #[test]
570    fn test_radix_sort_u32_basic() {
571        let mut data = vec![5u32, 3, 8, 1, 9, 2, 7, 4, 6];
572        radix_sort_u32(&mut data);
573        assert_eq!(data, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
574    }
575
576    #[test]
577    fn test_radix_sort_u32_empty() {
578        let mut data: Vec<u32> = vec![];
579        radix_sort_u32(&mut data);
580        assert!(data.is_empty());
581    }
582
583    #[test]
584    fn test_radix_sort_u32_single() {
585        let mut data = vec![42u32];
586        radix_sort_u32(&mut data);
587        assert_eq!(data, vec![42]);
588    }
589
590    #[test]
591    fn test_radix_sort_u32_large_values() {
592        let mut data = vec![u32::MAX, 0u32, u32::MAX / 2, 1u32];
593        radix_sort_u32(&mut data);
594        assert_eq!(data[0], 0);
595        assert_eq!(data[3], u32::MAX);
596    }
597
598    #[test]
599    fn test_radix_sort_u32_duplicates() {
600        let mut data = vec![3u32, 1, 3, 2, 1];
601        radix_sort_u32(&mut data);
602        assert_eq!(data, vec![1, 1, 2, 3, 3]);
603    }
604
605    // ── radix_sort_f32 ────────────────────────────────────────────────────────
606
607    #[test]
608    fn test_radix_sort_f32_positive_only() {
609        let mut data = vec![3.0f32, 1.0, 4.0, 1.5, 0.5];
610        radix_sort_f32(&mut data);
611        for i in 0..data.len() - 1 {
612            assert!(data[i] <= data[i + 1]);
613        }
614    }
615
616    #[test]
617    fn test_radix_sort_f32_with_negatives() {
618        let mut data = vec![1.0f32, -2.0, 0.5, -0.5, 3.0, -1.0];
619        radix_sort_f32(&mut data);
620        for i in 0..data.len() - 1 {
621            assert!(
622                data[i] <= data[i + 1],
623                "not sorted at {i}: {} > {}",
624                data[i],
625                data[i + 1]
626            );
627        }
628    }
629
630    #[test]
631    fn test_radix_sort_f32_empty() {
632        let mut data: Vec<f32> = vec![];
633        radix_sort_f32(&mut data);
634        assert!(data.is_empty());
635    }
636
637    #[test]
638    fn test_f32_sort_key_roundtrip_positive() {
639        let v = 3.125f32;
640        assert_eq!(sort_key_to_f32(f32_to_sort_key(v)), v);
641    }
642
643    #[test]
644    fn test_f32_sort_key_roundtrip_negative() {
645        let v = -2.719f32;
646        assert_eq!(sort_key_to_f32(f32_to_sort_key(v)), v);
647    }
648
649    // ── prefix_sum ────────────────────────────────────────────────────────────
650
651    #[test]
652    fn test_prefix_sum_basic() {
653        let data = [1u32, 2, 3, 4];
654        let result = prefix_sum(&data);
655        assert_eq!(result, vec![0, 1, 3, 6]);
656    }
657
658    #[test]
659    fn test_prefix_sum_empty() {
660        let result = prefix_sum(&[]);
661        assert!(result.is_empty());
662    }
663
664    #[test]
665    fn test_prefix_sum_single() {
666        let result = prefix_sum(&[5u32]);
667        assert_eq!(result, vec![0]);
668    }
669
670    #[test]
671    fn test_prefix_sum_all_ones() {
672        let data = vec![1u32; 5];
673        let result = prefix_sum(&data);
674        assert_eq!(result, vec![0, 1, 2, 3, 4]);
675    }
676
677    // ── histogram ────────────────────────────────────────────────────────────
678
679    #[test]
680    fn test_histogram_basic() {
681        let data = [0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9];
682        let h = histogram(&data, 5);
683        assert_eq!(h.len(), 5);
684        let total: u32 = h.iter().sum();
685        assert_eq!(total, 10);
686    }
687
688    #[test]
689    fn test_histogram_empty_data() {
690        let h = histogram(&[], 4);
691        assert_eq!(h, vec![0, 0, 0, 0]);
692    }
693
694    #[test]
695    fn test_histogram_all_same() {
696        let data = vec![5u32; 10];
697        let h = histogram(&data, 3);
698        assert_eq!(h.iter().sum::<u32>(), 10);
699    }
700
701    // ── counting_sort ─────────────────────────────────────────────────────────
702
703    #[test]
704    fn test_counting_sort_basic() {
705        let mut data = vec![3u32, 1, 4, 1, 5, 9, 2, 6];
706        counting_sort(&mut data, 9);
707        for i in 0..data.len() - 1 {
708            assert!(data[i] <= data[i + 1]);
709        }
710    }
711
712    #[test]
713    fn test_counting_sort_empty() {
714        let mut data: Vec<u32> = vec![];
715        counting_sort(&mut data, 10);
716        assert!(data.is_empty());
717    }
718
719    #[test]
720    fn test_counting_sort_single() {
721        let mut data = vec![7u32];
722        counting_sort(&mut data, 10);
723        assert_eq!(data, vec![7]);
724    }
725
726    #[test]
727    fn test_counting_sort_duplicates() {
728        let mut data = vec![2u32, 2, 2, 1, 1];
729        counting_sort(&mut data, 2);
730        assert_eq!(data, vec![1, 1, 2, 2, 2]);
731    }
732
733    #[test]
734    fn test_counting_sort_all_zero() {
735        let mut data = vec![0u32; 5];
736        counting_sort(&mut data, 0);
737        assert_eq!(data, vec![0u32; 5]);
738    }
739
740    // ── morton_sort_3d ────────────────────────────────────────────────────────
741
742    #[test]
743    fn test_morton_sort_3d_basic() {
744        let mut points = vec![
745            [1.0f32, 0.0, 0.0],
746            [0.0, 0.0, 0.0],
747            [0.0, 1.0, 0.0],
748            [1.0, 1.0, 0.0],
749        ];
750        morton_sort_3d(&mut points);
751        // After sorting, [0,0,0] should come first (Morton code 0).
752        assert_eq!(points[0], [0.0, 0.0, 0.0]);
753    }
754
755    #[test]
756    fn test_morton_sort_3d_empty() {
757        let mut points: Vec<[f32; 3]> = vec![];
758        morton_sort_3d(&mut points);
759        assert!(points.is_empty());
760    }
761
762    #[test]
763    fn test_morton_sort_3d_single() {
764        let mut points = vec![[1.0f32, 2.0, 3.0]];
765        morton_sort_3d(&mut points);
766        assert_eq!(points, vec![[1.0, 2.0, 3.0]]);
767    }
768
769    #[test]
770    fn test_morton3_origin() {
771        assert_eq!(morton3(0, 0, 0), 0);
772    }
773
774    #[test]
775    fn test_morton3_unit_x() {
776        // x=1, y=0, z=0 => bit 0 set in x position => interleaved x is bit 0
777        let code = morton3(1, 0, 0);
778        assert_ne!(code, 0);
779    }
780
781    #[test]
782    fn test_spread_bits_zero() {
783        assert_eq!(spread_bits(0), 0);
784    }
785
786    #[test]
787    fn test_spread_bits_one() {
788        // bit 0 of input stays as bit 0 of output
789        assert_eq!(spread_bits(1) & 1, 1);
790    }
791
792    // ── GpuSortBuffer ─────────────────────────────────────────────────────────
793
794    #[test]
795    fn test_gpu_sort_buffer_sort_pairs_basic() {
796        let keys = vec![3u32, 1, 4, 1, 5, 9, 2, 6];
797        let values: Vec<u32> = (0..keys.len() as u32).collect();
798        let mut buf = GpuSortBuffer::new(keys, values);
799        buf.sort_pairs();
800        for i in 0..buf.keys.len() - 1 {
801            assert!(buf.keys[i] <= buf.keys[i + 1]);
802        }
803    }
804
805    #[test]
806    fn test_gpu_sort_buffer_empty() {
807        let mut buf = GpuSortBuffer::empty();
808        buf.sort_pairs();
809        assert!(buf.is_empty());
810    }
811
812    #[test]
813    fn test_gpu_sort_buffer_push() {
814        let mut buf = GpuSortBuffer::empty();
815        buf.push(5, 100);
816        buf.push(2, 200);
817        assert_eq!(buf.len(), 2);
818        buf.sort_pairs();
819        assert_eq!(buf.keys[0], 2);
820        assert_eq!(buf.values[0], 200);
821    }
822
823    #[test]
824    fn test_gpu_sort_buffer_values_follow_keys() {
825        let keys = vec![30u32, 10, 20];
826        let values = vec![3u32, 1, 2];
827        let mut buf = GpuSortBuffer::new(keys, values);
828        buf.sort_pairs();
829        assert_eq!(buf.keys, vec![10, 20, 30]);
830        assert_eq!(buf.values, vec![1, 2, 3]);
831    }
832
833    #[test]
834    fn test_gpu_sort_buffer_len_is_empty() {
835        let buf = GpuSortBuffer::empty();
836        assert_eq!(buf.len(), 0);
837        assert!(buf.is_empty());
838    }
839
840    // ── parallel_merge ────────────────────────────────────────────────────────
841
842    #[test]
843    fn test_parallel_merge_basic() {
844        let left = vec![1.0f32, 3.0, 5.0];
845        let right = vec![2.0f32, 4.0, 6.0];
846        let merged = parallel_merge(&left, &right);
847        assert_eq!(merged, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
848    }
849
850    #[test]
851    fn test_parallel_merge_empty_left() {
852        let merged = parallel_merge(&[], &[1.0f32, 2.0]);
853        assert_eq!(merged, vec![1.0, 2.0]);
854    }
855
856    #[test]
857    fn test_parallel_merge_empty_right() {
858        let merged = parallel_merge(&[1.0f32, 2.0], &[]);
859        assert_eq!(merged, vec![1.0, 2.0]);
860    }
861
862    #[test]
863    fn test_parallel_merge_both_empty() {
864        let merged: Vec<f32> = parallel_merge(&[], &[]);
865        assert!(merged.is_empty());
866    }
867
868    #[test]
869    fn test_parallel_merge_unequal_lengths() {
870        let left = vec![1.0f32, 10.0];
871        let right = vec![2.0f32, 3.0, 4.0, 5.0];
872        let merged = parallel_merge(&left, &right);
873        assert_eq!(merged.len(), 6);
874        for i in 0..merged.len() - 1 {
875            assert!(merged[i] <= merged[i + 1]);
876        }
877    }
878
879    #[test]
880    fn test_parallel_merge_duplicates() {
881        let left = vec![1.0f32, 2.0, 2.0];
882        let right = vec![2.0f32, 3.0];
883        let merged = parallel_merge(&left, &right);
884        assert_eq!(merged.len(), 5);
885        for i in 0..merged.len() - 1 {
886            assert!(merged[i] <= merged[i + 1]);
887        }
888    }
889}