#![allow(dead_code)]
#[derive(Debug, Clone, Copy)]
pub struct SpdSample {
pub wavelength_nm: f32,
pub power: f32,
}
#[derive(Debug, Default, Clone)]
pub struct Spd {
pub name: String,
pub samples: Vec<SpdSample>,
}
impl Spd {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
samples: Vec::new(),
}
}
pub fn push(&mut self, wavelength_nm: f32, power: f32) {
self.samples.push(SpdSample {
wavelength_nm,
power,
});
}
pub fn sample_count(&self) -> usize {
self.samples.len()
}
pub fn peak_power(&self) -> f32 {
self.samples.iter().map(|s| s.power).fold(0.0f32, f32::max)
}
pub fn normalized(&self) -> Spd {
let peak = self.peak_power();
let mut out = Spd::new(&self.name);
if peak < f32::EPSILON {
return out;
}
for s in &self.samples {
out.push(s.wavelength_nm, s.power / peak);
}
out
}
}
pub fn export_spd_csv(spd: &Spd) -> String {
let mut out = format!("# SPD: {}\nwavelength_nm,power\n", spd.name);
for s in &spd.samples {
out.push_str(&format!("{},{}\n", s.wavelength_nm, s.power));
}
out
}
pub fn flat_spd(name: impl Into<String>, n: u32) -> Spd {
let mut spd = Spd::new(name);
for i in 0..n {
let wl = 380.0 + (400.0 * i as f32) / n.max(1) as f32;
spd.push(wl, 1.0);
}
spd
}
pub fn validate_spd(spd: &Spd) -> bool {
spd.samples.iter().all(|s| s.power >= 0.0)
}
pub fn integrate_spd(spd: &Spd) -> f32 {
if spd.samples.len() < 2 {
return 0.0;
}
spd.samples
.windows(2)
.map(|w| {
let dw = w[1].wavelength_nm - w[0].wavelength_nm;
(w[0].power + w[1].power) * 0.5 * dw
})
.sum()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_spd_empty() {
assert_eq!(Spd::new("test").sample_count(), 0);
}
#[test]
fn test_push_increases_count() {
let mut spd = Spd::new("x");
spd.push(550.0, 1.0);
assert_eq!(spd.sample_count(), 1);
}
#[test]
fn test_peak_power_empty() {
assert_eq!(Spd::new("x").peak_power(), 0.0);
}
#[test]
fn test_peak_power_known() {
let mut spd = Spd::new("x");
spd.push(450.0, 0.3);
spd.push(550.0, 1.0);
assert!((spd.peak_power() - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_normalized_peak_is_one() {
let mut spd = Spd::new("x");
spd.push(450.0, 2.0);
spd.push(550.0, 4.0);
let n = spd.normalized();
assert!((n.peak_power() - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_export_csv_header() {
let spd = flat_spd("white", 4);
assert!(export_spd_csv(&spd).contains("wavelength_nm,power"));
}
#[test]
fn test_flat_spd_count() {
assert_eq!(flat_spd("f", 10).sample_count(), 10);
}
#[test]
fn test_validate_spd_valid() {
assert!(validate_spd(&flat_spd("f", 5)));
}
#[test]
fn test_integrate_flat_spd() {
let spd = flat_spd("flat", 100);
let integral = integrate_spd(&spd);
assert!(integral > 300.0 && integral < 500.0);
}
#[test]
fn test_integrate_single_sample() {
let mut spd = Spd::new("single");
spd.push(550.0, 1.0);
assert_eq!(integrate_spd(&spd), 0.0);
}
}