Skip to main content

petaplot_core/compute/
simd.rs

1use bytemuck::{Pod, Zeroable};
2use rayon::prelude::*;
3
4/// Par Min-Max que representa el rango de valores en un bin horizontal.
5/// Compatible con diseño de memoria GPU (`bytemuck::Pod`).
6#[repr(C)]
7#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
8pub struct MinMaxPair {
9    pub min: f32,
10    pub max: f32,
11}
12
13impl MinMaxPair {
14    #[inline]
15    pub fn new(min: f32, max: f32) -> Self {
16        Self { min, max }
17    }
18
19    #[inline]
20    pub fn identity() -> Self {
21        Self {
22            min: f32::MAX,
23            max: f32::MIN,
24        }
25    }
26
27    #[inline]
28    pub fn combine(&self, other: &Self) -> Self {
29        Self {
30            min: self.min.min(other.min),
31            max: self.max.max(other.max),
32        }
33    }
34}
35
36/// Decimación en paralelo usando chunking SIMD y `rayon`.
37///
38/// Convierte un slice raw `&[f32]` de $N$ muestras en una lista de `MinMaxPair` agrupados en bins de tamaño `bin_size`.
39pub fn reduce_min_max_chunk(data: &[f32], bin_size: usize) -> Vec<MinMaxPair> {
40    if data.is_empty() || bin_size == 0 {
41        return Vec::new();
42    }
43
44    data.par_chunks(bin_size)
45        .map(|chunk| {
46            let mut min_val = f32::MAX;
47            let mut max_val = f32::MIN;
48
49            // El compilador autovectoriza este bucle sencillo a instrucciones AVX2 / AVX-512 / NEON
50            for &val in chunk {
51                if val < min_val {
52                    min_val = val;
53                }
54                if val > max_val {
55                    max_val = val;
56                }
57            }
58
59            MinMaxPair {
60                min: min_val,
61                max: max_val,
62            }
63        })
64        .collect()
65}
66
67/// Decimación jerárquica de pares `MinMaxPair` existentes (para reducir niveles superiores de la pirámide LOD).
68pub fn reduce_min_max_pairs(pairs: &[MinMaxPair], bin_size: usize) -> Vec<MinMaxPair> {
69    if pairs.is_empty() || bin_size == 0 {
70        return Vec::new();
71    }
72
73    pairs
74        .par_chunks(bin_size)
75        .map(|chunk| {
76            let mut combined = MinMaxPair::identity();
77            for pair in chunk {
78                combined = combined.combine(pair);
79            }
80            combined
81        })
82        .collect()
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn test_reduce_min_max_chunk_simple() {
91        let data = vec![1.0, 5.0, 2.0, 8.0, 3.0, 0.5, -4.0, 10.0, 7.0];
92        let bin_size = 3;
93
94        let result = reduce_min_max_chunk(&data, bin_size);
95        assert_eq!(result.len(), 3);
96        assert_eq!(result[0], MinMaxPair::new(1.0, 5.0));
97        assert_eq!(result[1], MinMaxPair::new(0.5, 8.0));
98        assert_eq!(result[2], MinMaxPair::new(-4.0, 10.0));
99    }
100
101    #[test]
102    fn test_reduce_min_max_pairs_hierarchical() {
103        let pairs = vec![
104            MinMaxPair::new(1.0, 5.0),
105            MinMaxPair::new(-2.0, 3.0),
106            MinMaxPair::new(0.0, 10.0),
107            MinMaxPair::new(4.0, 6.0),
108        ];
109
110        let result = reduce_min_max_pairs(&pairs, 2);
111        assert_eq!(result.len(), 2);
112        assert_eq!(result[0], MinMaxPair::new(-2.0, 5.0));
113        assert_eq!(result[1], MinMaxPair::new(0.0, 10.0));
114    }
115}