use crate::ext_qtty::{Quantity, Unit};
use super::algo;
use super::interp::{Interpolation, OutOfRange};
use super::provenance::Provenance;
use super::SpectrumError;
#[derive(Debug, Clone)]
pub struct SampledSpectrum<X: Unit, Y: Unit, S: crate::ext_qtty::Scalar = f64> {
xs: Vec<Quantity<X, S>>,
ys: Vec<Quantity<Y, S>>,
interp: Interpolation,
oor: OutOfRange,
provenance: Option<Provenance>,
}
impl<X: Unit, Y: Unit, S: crate::ext_qtty::Scalar> SampledSpectrum<X, Y, S> {
#[inline]
pub fn xs(&self) -> &[Quantity<X, S>] {
&self.xs
}
#[inline]
pub fn ys(&self) -> &[Quantity<Y, S>] {
&self.ys
}
#[inline]
pub fn len(&self) -> usize {
self.xs.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
false
}
#[inline]
pub fn interpolation(&self) -> Interpolation {
self.interp
}
#[inline]
pub fn out_of_range(&self) -> OutOfRange {
self.oor
}
#[inline]
pub fn provenance(&self) -> Option<&Provenance> {
self.provenance.as_ref()
}
pub fn set_provenance(&mut self, p: Option<Provenance>) {
self.provenance = p;
}
pub fn with_provenance(mut self, p: Provenance) -> Self {
self.provenance = Some(p);
self
}
}
impl<X: Unit, Y: Unit> SampledSpectrum<X, Y, f64> {
pub fn from_raw(
xs: Vec<f64>,
ys: Vec<f64>,
interpolation: Interpolation,
out_of_range: OutOfRange,
provenance: Option<Provenance>,
) -> Result<Self, SpectrumError> {
algo::validate(&xs, &ys)?;
Ok(Self {
xs: xs.into_iter().map(Quantity::<X, f64>::new).collect(),
ys: ys.into_iter().map(Quantity::<Y, f64>::new).collect(),
interp: interpolation,
oor: out_of_range,
provenance,
})
}
pub fn from_typed_xs_raw_ys(
xs: Vec<Quantity<X, f64>>,
ys: Vec<f64>,
interpolation: Interpolation,
out_of_range: OutOfRange,
provenance: Option<Provenance>,
) -> Result<Self, SpectrumError> {
let raw_xs: Vec<f64> = xs.iter().map(|q| q.value()).collect();
algo::validate(&raw_xs, &ys)?;
Ok(Self {
xs,
ys: ys.into_iter().map(Quantity::<Y, f64>::new).collect(),
interp: interpolation,
oor: out_of_range,
provenance,
})
}
pub fn from_typed(
xs: Vec<Quantity<X, f64>>,
ys: Vec<Quantity<Y, f64>>,
interpolation: Interpolation,
out_of_range: OutOfRange,
provenance: Option<Provenance>,
) -> Result<Self, SpectrumError> {
let raw_xs: Vec<f64> = xs.iter().map(|q| q.value()).collect();
let raw_ys: Vec<f64> = ys.iter().map(|q| q.value()).collect();
algo::validate(&raw_xs, &raw_ys)?;
Ok(Self {
xs,
ys,
interp: interpolation,
oor: out_of_range,
provenance,
})
}
pub fn xs_raw(&self) -> Vec<f64> {
self.xs.iter().map(|q| q.value()).collect()
}
pub fn ys_raw(&self) -> Vec<f64> {
self.ys.iter().map(|q| q.value()).collect()
}
pub fn interp_at(&self, x: Quantity<X, f64>) -> Result<Quantity<Y, f64>, SpectrumError> {
let xs = self.xs_raw();
let ys = self.ys_raw();
let v = algo::interp(&xs, &ys, x.value(), self.interp, self.oor)?;
Ok(Quantity::<Y, f64>::new(v))
}
pub fn integrate(&self) -> Quantity<crate::ext_qtty::Prod<Y, X>, f64>
where
Y::Dim: crate::ext_qtty::DimMul<X::Dim>,
<Y::Dim as crate::ext_qtty::DimMul<X::Dim>>::Output: crate::ext_qtty::Dimension,
{
let xs = self.xs_raw();
let ys = self.ys_raw();
Quantity::<crate::ext_qtty::Prod<Y, X>, f64>::new(algo::trapz(&xs, &ys))
}
pub fn integrate_range(
&self,
lo: Quantity<X, f64>,
hi: Quantity<X, f64>,
) -> Quantity<crate::ext_qtty::Prod<Y, X>, f64>
where
Y::Dim: crate::ext_qtty::DimMul<X::Dim>,
<Y::Dim as crate::ext_qtty::DimMul<X::Dim>>::Output: crate::ext_qtty::Dimension,
{
let xs = self.xs_raw();
let ys = self.ys_raw();
Quantity::<crate::ext_qtty::Prod<Y, X>, f64>::new(algo::trapz_range(
&xs,
&ys,
lo.value(),
hi.value(),
))
}
pub fn integrate_weighted<Yw: Unit>(
&self,
weight: &SampledSpectrum<X, Yw, f64>,
) -> Quantity<crate::ext_qtty::Prod<crate::ext_qtty::Prod<Y, Yw>, X>, f64>
where
Y::Dim: crate::ext_qtty::DimMul<Yw::Dim>,
<Y::Dim as crate::ext_qtty::DimMul<Yw::Dim>>::Output: crate::ext_qtty::Dimension,
<Y::Dim as crate::ext_qtty::DimMul<Yw::Dim>>::Output:
crate::ext_qtty::DimMul<X::Dim>,
<<Y::Dim as crate::ext_qtty::DimMul<Yw::Dim>>::Output as crate::ext_qtty::DimMul<
X::Dim,
>>::Output: crate::ext_qtty::Dimension,
{
let sx = self.xs_raw();
let sy = self.ys_raw();
let wx = weight.xs_raw();
let wy = weight.ys_raw();
Quantity::<crate::ext_qtty::Prod<crate::ext_qtty::Prod<Y, Yw>, X>, f64>::new(
algo::trapz_weighted(&sx, &sy, &wx, &wy),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ext_qtty::length::{Meter, Nanometer};
use approx::assert_abs_diff_eq;
#[test]
fn from_raw_validates() {
let bad = SampledSpectrum::<Nanometer, Meter, f64>::from_raw(
vec![3.0, 1.0],
vec![0.0, 0.0],
Interpolation::Linear,
OutOfRange::ClampToEndpoints,
None,
);
assert!(bad.is_err());
}
#[test]
fn typed_interp_round_trip() {
let xs = vec![0.0_f64, 1.0, 2.0, 3.0];
let ys = vec![1.0_f64, 3.0, 5.0, 7.0];
let s = SampledSpectrum::<Nanometer, Meter, f64>::from_raw(
xs,
ys,
Interpolation::Linear,
OutOfRange::ClampToEndpoints,
None,
)
.unwrap();
let q = s
.interp_at(crate::ext_qtty::Quantity::<Nanometer, f64>::new(1.5))
.unwrap();
assert_abs_diff_eq!(q.value(), 4.0, epsilon = 1e-12);
}
#[test]
fn integrate_returns_typed_product() {
let s = SampledSpectrum::<Nanometer, Meter, f64>::from_raw(
vec![0.0, 1.0, 2.0, 3.0, 4.0],
(0..5).map(|i| 2.0 * i as f64 + 1.0).collect(),
Interpolation::Linear,
OutOfRange::ClampToEndpoints,
None,
)
.unwrap();
let area = s.integrate();
assert_abs_diff_eq!(area.value(), 20.0, epsilon = 1e-12);
}
#[test]
fn integrate_range_clips_correctly() {
let s = SampledSpectrum::<Nanometer, Meter, f64>::from_raw(
vec![0.0, 1.0, 2.0, 3.0, 4.0],
(0..5).map(|i| 2.0 * i as f64 + 1.0).collect(),
Interpolation::Linear,
OutOfRange::ClampToEndpoints,
None,
)
.unwrap();
let lo = crate::ext_qtty::Quantity::<Nanometer, f64>::new(0.5);
let hi = crate::ext_qtty::Quantity::<Nanometer, f64>::new(3.5);
let area = s.integrate_range(lo, hi);
assert_abs_diff_eq!(area.value(), 15.0, epsilon = 1e-12);
}
#[test]
fn integrate_weighted_against_rectangle() {
let source = SampledSpectrum::<Nanometer, Meter, f64>::from_raw(
(0..=4).map(|i| i as f64).collect(),
(0..=4).map(|i| i as f64).collect(),
Interpolation::Linear,
OutOfRange::ClampToEndpoints,
None,
)
.unwrap();
let weight = SampledSpectrum::<Nanometer, Meter, f64>::from_raw(
vec![1.0, 3.0],
vec![1.0, 1.0],
Interpolation::Linear,
OutOfRange::ClampToEndpoints,
None,
)
.unwrap();
let v = source.integrate_weighted(&weight);
assert_abs_diff_eq!(v.value(), 4.0, epsilon = 1e-12);
}
}