use crate::error::SpotResult;
use crate::ubend::Ubend;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Peaks {
e: f64,
e2: f64,
#[cfg_attr(feature = "serde", serde(with = "crate::ser::nan_safe_f64"))]
min: f64,
#[cfg_attr(feature = "serde", serde(with = "crate::ser::nan_safe_f64"))]
max: f64,
container: Ubend,
}
impl Peaks {
pub fn new(size: usize) -> SpotResult<Self> {
Ok(Self {
e: 0.0,
e2: 0.0,
min: f64::NAN,
max: f64::NAN,
container: Ubend::new(size)?,
})
}
pub fn size(&self) -> usize {
self.container.size()
}
pub fn push(&mut self, x: f64) {
let erased = self.container.push(x);
let size = self.size();
self.e += x;
self.e2 += x * x;
if size == 1 || x < self.min {
self.min = x;
}
if size == 1 || x > self.max {
self.max = x;
}
if !erased.is_nan() {
self.e -= erased;
self.e2 -= erased * erased;
if (erased <= self.min) || (erased >= self.max) {
self.update_stats();
}
}
}
pub fn mean(&self) -> f64 {
let size = self.size();
if size == 0 {
f64::NAN
} else {
self.e / (size as f64)
}
}
pub fn variance(&self) -> f64 {
let size = self.size();
if size == 0 {
f64::NAN
} else {
let size_f = size as f64;
let mean = self.e / size_f;
(self.e2 / size_f) - (mean * mean)
}
}
pub fn min(&self) -> f64 {
self.min
}
pub fn max(&self) -> f64 {
self.max
}
pub fn sum(&self) -> f64 {
self.e
}
pub fn sum_squares(&self) -> f64 {
self.e2
}
pub fn container(&self) -> &Ubend {
&self.container
}
fn update_stats(&mut self) {
self.min = f64::NAN;
self.max = f64::NAN;
self.e = 0.0;
self.e2 = 0.0;
let max_iteration = self.container.size();
for i in 0..max_iteration {
let value = self.container.raw_data()[i];
self.e += value;
self.e2 += value * value;
if self.min.is_nan() || (value < self.min) {
self.min = value;
}
if self.max.is_nan() || (value > self.max) {
self.max = value;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::SpotError;
use approx::assert_relative_eq;
#[test]
fn test_peaks_creation() {
let peaks = Peaks::new(5).unwrap();
assert_eq!(peaks.size(), 0);
assert_relative_eq!(peaks.sum(), 0.0);
assert_relative_eq!(peaks.sum_squares(), 0.0);
assert!(peaks.min().is_nan());
assert!(peaks.max().is_nan());
assert!(peaks.mean().is_nan());
assert!(peaks.variance().is_nan());
}
#[test]
fn test_peaks_zero_size() {
let result = Peaks::new(0);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), SpotError::MemoryAllocationFailed);
}
#[test]
fn test_peaks_single_element() {
let mut peaks = Peaks::new(3).unwrap();
peaks.push(5.0);
assert_eq!(peaks.size(), 1);
assert_relative_eq!(peaks.sum(), 5.0);
assert_relative_eq!(peaks.sum_squares(), 25.0);
assert_relative_eq!(peaks.min(), 5.0);
assert_relative_eq!(peaks.max(), 5.0);
assert_relative_eq!(peaks.mean(), 5.0);
assert_relative_eq!(peaks.variance(), 0.0);
}
#[test]
fn test_peaks_multiple_elements() {
let mut peaks = Peaks::new(5).unwrap();
peaks.push(1.0);
peaks.push(2.0);
peaks.push(3.0);
assert_eq!(peaks.size(), 3);
assert_relative_eq!(peaks.sum(), 6.0);
assert_relative_eq!(peaks.sum_squares(), 14.0);
assert_relative_eq!(peaks.min(), 1.0);
assert_relative_eq!(peaks.max(), 3.0);
assert_relative_eq!(peaks.mean(), 2.0);
assert_relative_eq!(peaks.variance(), 2.0 / 3.0, epsilon = 1e-14);
}
#[test]
fn test_peaks_overflow_and_min_max_update() {
let mut peaks = Peaks::new(3).unwrap();
peaks.push(1.0); peaks.push(2.0); peaks.push(3.0);
assert_relative_eq!(peaks.min(), 1.0);
assert_relative_eq!(peaks.max(), 3.0);
peaks.push(0.5);
assert_eq!(peaks.size(), 3);
assert_relative_eq!(peaks.min(), 0.5);
assert_relative_eq!(peaks.max(), 3.0);
assert_relative_eq!(peaks.sum(), 5.5);
peaks.push(4.0);
assert_relative_eq!(peaks.min(), 0.5);
assert_relative_eq!(peaks.max(), 4.0);
assert_relative_eq!(peaks.sum(), 7.5);
}
#[test]
fn test_peaks_stats_after_min_erasure() {
let mut peaks = Peaks::new(3).unwrap();
peaks.push(2.0);
peaks.push(1.0); peaks.push(3.0);
assert_relative_eq!(peaks.min(), 1.0);
assert_relative_eq!(peaks.max(), 3.0);
peaks.push(2.5);
assert_relative_eq!(peaks.min(), 1.0); assert_relative_eq!(peaks.max(), 3.0);
peaks.push(2.7);
assert_relative_eq!(peaks.min(), 2.5);
assert_relative_eq!(peaks.max(), 3.0);
}
#[test]
fn test_peaks_stats_after_max_erasure() {
let mut peaks = Peaks::new(3).unwrap();
peaks.push(1.0);
peaks.push(3.0); peaks.push(2.0);
assert_relative_eq!(peaks.min(), 1.0);
assert_relative_eq!(peaks.max(), 3.0);
peaks.push(1.5); peaks.push(1.7);
assert_relative_eq!(peaks.min(), 1.5);
assert_relative_eq!(peaks.max(), 2.0);
}
}