use std::cmp::Ordering; pub fn clamp(data: &mut [f64], lower: f64, upper: f64) { if data.is_empty() { return; } for x in data.iter_mut() { *x = f64::min(upper, f64::max(lower, *x)); } } pub fn normalize(data: &mut [f64]) { if data.is_empty() { return; } let max = *data .iter() .max_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal)) .unwrap(); let min = *data .iter() .min_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal)) .unwrap(); for x in data.iter_mut() { *x = (*x - min) / (max - min); } } pub fn smooth(data: &mut [f64], sigma: f64) { const WINDOW_SIZE: usize = 5; if data.is_empty() { return; } // Build Gaussian filter let sigsig = 2.0 * sigma.powi(2); let mut g: Vec = (0..WINDOW_SIZE) .map(|i| { let x2 = (i as f64).powi(2); std::f64::consts::E.powf(-x2 / sigsig) }) .collect(); // Normalize Gaussian filter let filter_sum: f64 = g.iter().sum(); g.iter_mut().for_each(|x| *x /= filter_sum); // Smooth data let first = data.first().unwrap(); let mut buffer = [f64::NAN; WINDOW_SIZE]; let mut bi = 0; for (_loc, x) in data.iter_mut().enumerate() { buffer[bi] = *x; *x = 0.0; for (i, b) in g.iter().enumerate() { *x += b * buffer[(bi + i) % WINDOW_SIZE]; } bi = (bi + 1) % WINDOW_SIZE; } } pub fn derive(data: &mut [f64]) { const WINDOW_SIZE: usize = 5; if data.is_empty() { return; } // Calculate the derivative. let first = data.first().unwrap(); let mut buffer = [f64::NAN; WINDOW_SIZE]; let mut bi = 0; for (_loc, x) in data.iter_mut().enumerate() { buffer[bi] = *x; *x = (buffer[bi] - buffer[(bi + WINDOW_SIZE - 1) % (WINDOW_SIZE)]) / (WINDOW_SIZE as f64); bi = (bi + 1) % WINDOW_SIZE; } } enum State { Start, NoClue(usize, f64), SeekingMinimum(usize, f64), SeekingMaximum(usize, f64), } #[derive(Debug)] pub enum Extremum { Maximum(usize), Minimum(usize), } pub fn find_extrema(data: &[f64]) -> Vec { let mut result = Vec::new(); let mut state = State::Start; for (i, v) in data.iter().enumerate() { let datum = *v; state = match state { State::Start => State::NoClue(i, datum), State::NoClue(pi, prior) => { if datum > prior { result.push(Extremum::Minimum(pi)); State::SeekingMaximum(i, datum) } else { result.push(Extremum::Maximum(pi)); State::SeekingMinimum(i, datum) } } State::SeekingMinimum(pi, prior) => { if datum <= prior { State::SeekingMinimum(i, datum) } else { result.push(Extremum::Minimum(pi)); State::SeekingMaximum(i, datum) } } State::SeekingMaximum(pi, prior) => { if datum >= prior { State::SeekingMaximum(i, datum) } else { result.push(Extremum::Maximum(pi)); State::SeekingMinimum(i, datum) } } }; } match state { State::Start => (), State::NoClue(..) => (), State::SeekingMinimum(i, _) => result.push(Extremum::Minimum(i)), State::SeekingMaximum(i, _) => result.push(Extremum::Maximum(i)), } result } #[cfg(test)] mod tests { #[test] fn normalize_normalizes() { let mut x = vec![1.0, 2.0, 3.0, 4.0]; super::normalize(&mut x); assert!((0.0 - x[0]).abs() < 0.001); assert!((0.333 - x[1]).abs() < 0.001); assert!((0.667 - x[2]).abs() < 0.001); assert!((1.0 - x[3]).abs() < 0.001); } #[test] fn first_run() { // let mut values = [37200000.0, 13323636.0, 10937.16, 12403.11, 12771.62, 12771.62, 13511.11, 13140.96]; let mut values = [13323636.0, 10937.16, 12403.11, 12771.62, 12771.62, 13511.11, 13140.96]; for (i, x) in values.iter().enumerate() { println!("original: {} => {:?}", i, x); } super::normalize(&mut values); for (i, x) in values.iter().enumerate() { println!("normalize: {} => {:?}", i, x); } // super::smooth(&mut values, 1.0); // for (i, x) in values.iter().enumerate() { // println!("smooth: {} => {:?}", i, x); // } super::derive(&mut values); for (i, x) in values.iter().enumerate() { println!("derive: {} => {:?}", i, x); } let extrema = super::find_extrema(&values); for x in extrema { println!("{:?}", x); } assert!(false); } }