Newer
Older
use crate::error::OpossumError;
use ndarray_csv::Array2Reader;
use csv::ReaderBuilder;
use std::f64::consts::PI;
use std::ops::Range;
use uom::fmt::DisplayStyle::Abbreviation;
use uom::num_traits::Zero;
use uom::si::{f64::Length, length::nanometer};
type Result<T> = std::result::Result<T, OpossumError>;
/// Structure for handling spectral data.
///
/// This structure handles an array of values over a given wavelength range. Although the interface
/// is still limited. The structure is prepared for handling non-equidistant wavelength slots.
data: Array1<f64>, // data in 1/meters
lambdas: Array1<f64>, // wavelength in meters
/// Create a new (empty) spectrum of a given wavelength range and (equidistant) resolution.
///
/// # Errors
///
/// This function will return an [`OpossumError::Spectrum`] if
/// - the wavelength range is not in ascending order
/// - the wavelength limits are not both positive
/// - the resolution is not positive
pub fn new(range: Range<Length>, resolution: Length) -> Result<Self> {
if resolution <= Length::zero() {
return Err(OpossumError::Spectrum("resolution must be positive".into()));
}
if range.start >= range.end {
return Err(OpossumError::Spectrum(
"wavelength range must be in ascending order".into(),
));
}
if range.start <= Length::zero() || range.end <= Length::zero() {
return Err(OpossumError::Spectrum(
"wavelength range limits must both be positive".into(),
));
}
let l = Array1::range(
range.start.get::<meter>(),
range.end.get::<meter>(),
resolution.get::<meter>(),
);
let length = l.len();
lambdas: l,
data: Array1::zeros(length),
pub fn from_csv() {
// Read an array back from the file
let file = File::open("NE03B.csv").unwrap();
let mut reader = ReaderBuilder::new().has_headers(false).delimiter(b';').from_reader(file);
for record in reader.records() {
if let Ok(record)=record {
println!("{:?} {:?}",record.get(0), record.get(1));
}
}
//let array_read: Array2<u64> = reader.deserialize_array2((2, 3)).unwrap();
/// Returns the wavelength range of this [`Spectrum`].
pub fn range(&self) -> Range<Length> {
Length::new::<meter>(*self.lambdas.first().unwrap())
..Length::new::<meter>(*self.lambdas.last().unwrap())
}
/// Add a single peak to the given [`Spectrum`].
///
/// This functions adds a single (resolution limited) peak to the [`Spectrum`] at the given wavelength and
/// the given energy / intensity. If the given wavelength does not exactly match a spectrum slot the energy is distributed
/// over neighboring slots such that the total energy matches the given energy.
///
/// # Errors
///
/// This function will return an [`OpossumError::Spectrum`] if
/// - the wavelength i s outside the spectrum range
/// - the energy is negative
pub fn add_single_peak(&mut self, wavelength: Length, value: f64) -> Result<()> {
let spectrum_range = self.lambdas.first().unwrap()..self.lambdas.last().unwrap();
if !spectrum_range.contains(&&wavelength.get::<meter>()) {
"wavelength is not in spectrum range".into(),
));
}
return Err(OpossumError::Spectrum("energy must be positive".into()));
}
let wavelength_in_meters = wavelength.get::<meter>();
let idx = self
.lambdas
.clone()
.into_iter()
.position(|w| w >= wavelength_in_meters);
if let Some(idx) = idx {
if idx == 0 {
let delta = self.lambdas.get(1).unwrap() - self.lambdas.get(0).unwrap();
self.data[0] += value / delta;
} else {
let lower_lambda = self.lambdas.get(idx - 1).unwrap();
let upper_lambda = self.lambdas.get(idx).unwrap();
let delta = upper_lambda - lower_lambda;
self.data[idx - 1] +=
value * (1.0 - (wavelength_in_meters - lower_lambda) / delta) / delta;
self.data[idx] += value * (wavelength_in_meters - lower_lambda) / delta / delta;
Err(OpossumError::Spectrum("insertion point not found".into()))
/// Adds an emission line to this [`Spectrum`].
///
/// This function adds a laser line (following a [Lorentzian](https://en.wikipedia.org/wiki/Cauchy_distribution) function) with a given
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/// center wavelength, width and energy to the spectrum. **Note**: Due to rounding errors (discrete wavelength bins, upper/lower spectrum
/// limits) the total energy is not exactly the given value.
/// # Errors
///
/// This function will return an [`OpossumError::Spectrum`] if
/// - the center wavelength in negative
/// - the width is negative
/// - the energy is negative
pub fn add_lorentzian_peak(
&mut self,
center: Length,
width: Length,
energy: f64,
) -> Result<()> {
if center.is_sign_negative() {
return Err(OpossumError::Spectrum(
"center wavelength must be positive".into(),
));
}
if width.is_sign_negative() {
return Err(OpossumError::Spectrum("line width must be positive".into()));
}
if energy < 0.0 {
return Err(OpossumError::Spectrum("energy must be positive".into()));
}
let wavelength_in_meters = center.get::<meter>();
let width_in_meters = width.get::<meter>();
let spectrum_data: Array1<f64> = self
.lambdas
.iter_mut()
.map(|x| lorentz(wavelength_in_meters, width_in_meters, *x))
.collect();
self.data = &self.data + (spectrum_data * energy);
Ok(())
}
/// Returns the total energy of this [`Spectrum`].
///
/// This function sums the values over all wavelength slots weighted with the individual slot widths. This
/// way it also works for non-equidistant spectra.
pub fn total_energy(&self) -> f64 {
let lambda_deltas: Vec<f64> = self
.lambdas
.windows(2)
.into_iter()
.map(|l| l[1] - l[0])
.collect();
let total_energy = lambda_deltas
.into_iter()
.zip(self.data.iter())
.map(|d| d.0 * *d.1)
.sum();
/// Scale the spectrum by a constant factor.
///
/// # Errors
///
/// This function will return an [`OpossumError::Spectrum`] if the scaling factor is < 0.0.
pub fn scale_vertical(&mut self, factor: f64) -> Result<()> {
if factor < 0.0 {
return Err(OpossumError::Spectrum(
"scaling factor mus be >= 0.0".into(),
));
}
self.data = &self.data * factor;
Ok(())
}
fn enclosing_interval(&self, x_lower: f64, x_upper: f64) -> Vec<Option<usize>> {
.enumerate()
.filter(|x| x.1 < x_upper && x.1 > x_lower)
.map(|x| Some(x.0))
.collect::<Vec<Option<usize>>>();
let mut lower_idx = self.clone().lambdas.into_iter().position(|x| x >= x_lower);
let upper_idx = self.clone().lambdas.into_iter().position(|x| x >= x_upper);
if let Some(l_i) = lower_idx {
if l_i > 0 && self.lambdas[l_i] > x_lower {
lower_idx = Some(l_i - 1);
}
}
if lower_idx == upper_idx {
res = vec![None, None];
} else {
res = vec![lower_idx, upper_idx];
let res_cp = res.clone();
let first = res_cp.first().unwrap();
let last = res_cp.last().unwrap();
if let Some(first) = first {
if *first > 0 {
res.insert(0, Some(first - 1));
}
if let Some(last) = last {
if *last < self.lambdas.len() - 1 {
res.push(Some(last + 1))
}
/// Resample a provided [`Spectrum`] to match the given one.
///
/// This function maps values and wavelengths of a provided spectrum to the structure of self. This function conserves the toal
/// energy if the the given interval is fully contained in self. This does not necessarily conserve peak widths or positions.
///
/// # Panics
///
/// Panics if ???.
pub fn resample(&mut self, spectrum: &Spectrum) {
let _data = spectrum.data.clone();
let _x = spectrum.lambdas.clone();
let max_idx = self.data.len() - 1;
for x in self.data.iter_mut().enumerate().filter(|x| x.0 < max_idx) {
let lower_bound = *self.lambdas.get(x.0).unwrap();
let upper_bound = *self.lambdas.get(x.0 + 1).unwrap();
let interval = spectrum.enclosing_interval(lower_bound, upper_bound);
if (*src_idx)[0].is_some() && (*src_idx)[1].is_some() {
let source_left = spectrum.lambdas[src_idx[0].unwrap()];
let source_right = spectrum.lambdas[src_idx[1].unwrap()];
let ratio = calc_ratio(lower_bound, upper_bound, source_left, source_right);
bucket_value +=
spectrum.data[src_idx[0].unwrap()] * ratio * (source_right - source_left)
/ (upper_bound - lower_bound);
}
/// Filter the spectrum with another given spectrum by multiplying the data values. The given spectrum is resampled before the multiplication.
pub fn filter(&mut self, filter_spectrum: &Spectrum) {
let mut resampled_spec = self.clone();
resampled_spec.resample(filter_spectrum);
self.data = self
.data
.iter()
.zip(resampled_spec.data.iter())
.map(|d| d.0 * d.1)
.collect();
/// The given spectrum might be resampled in order to match self.
pub fn add(&mut self, spectrum_to_be_added: &Spectrum) {
let mut resampled_spec = self.clone();
resampled_spec.resample(spectrum_to_be_added);
self.data = self
.data
.iter()
.zip(resampled_spec.data.iter())
.map(|d| d.0 + d.1)
.collect();
}
/// Subtract a given spectrum.
///
/// The given spectrum might be resampled in order to match self. **Note**: Negative values as result from the subtraction will be
/// clamped to 0.0 (negative spectrum values are not allowed).
pub fn sub(&mut self, spectrum_to_be_subtracted: &Spectrum) {
let mut resampled_spec = self.clone();
resampled_spec.resample(spectrum_to_be_subtracted);
self.data = self
.data
.iter()
.zip(resampled_spec.data.iter())
.map(|d| (d.0 - d.1).clamp(0.0, f64::abs(d.0 - d.1)))
.collect();
}
pub fn to_plot(&self, filename: &str) {
let root = SVGBackend::new(filename, (640, 480)).into_drawing_area();
root.fill(&WHITE).unwrap();
let x_left = *self.lambdas.first().unwrap();
let x_right = *self.lambdas.last().unwrap();
let mut chart = ChartBuilder::on(&root)
.margin(5)
.x_label_area_size(30)
.y_label_area_size(30)
.build_cartesian_2d(x_left..x_right, 0.0..y_top)
.unwrap();
chart.configure_mesh().draw().unwrap();
chart
.draw_series(LineSeries::new(
self.lambdas
.iter()
.zip(self.data.iter())
.map(|x| (*x.0, *x.1)),
&RED,
))
.unwrap();
root.present().unwrap();
}
}
impl Display for Spectrum {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let fmt_length = Length::format_args(nanometer, Abbreviation);
for value in self.data.iter().enumerate() {
fmt_length.with(Length::new::<meter>(self.lambdas[value.0])),
write!(f, "\nTotal energy: {}", self.total_energy())
impl Debug for Spectrum {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let fmt_length = Length::format_args(nanometer, Abbreviation);
for value in self.data.iter().enumerate() {
writeln!(
f,
"{:7.2} -> {}",
fmt_length.with(Length::new::<meter>(self.lambdas[value.0])),
*value.1
)
.unwrap();
}
Ok(())
}
}
fn calc_ratio(bucket_left: f64, bucket_right: f64, source_left: f64, source_right: f64) -> f64 {
if bucket_left < source_left && bucket_right > source_left && bucket_right < source_right {
// bucket is left partly outside source
return (bucket_right - source_left) / (source_right - source_left);
}
if bucket_left <= source_left && bucket_right >= source_right {
// bucket contains source
return 1.0;
}
if bucket_left >= source_left && bucket_right <= source_right {
// bucket is part of source
return (bucket_right - bucket_left) / (source_right - source_left);
}
if bucket_left > source_left && bucket_left < source_right && bucket_right > source_right {
// bucket is right partly outside source
return (source_right - bucket_left) / (source_right - source_left);
}
fn lorentz(center: f64, width: f64, x: f64) -> f64 {
0.5 / PI * width / ((0.25 * width * width) + (x - center) * (x - center))
}
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
/// Helper function for generating a visible spectrum.
///
/// This function generates an empty spectrum in the visible range (350 - 750 nm) with a resolution
/// of 0.1 nm.
pub fn create_visible_spectrum() -> Spectrum {
Spectrum::new(
Length::new::<nanometer>(380.0)..Length::new::<nanometer>(750.0),
Length::new::<nanometer>(0.1),
)
.unwrap()
}
/// Helper function for generating a near infrared spectrum.
///
/// This function generates an empty spectrum in the near infrared range (800 - 2500 nm) with a resolution
/// of 0.1 nm.
pub fn create_nir_spectrum() -> Spectrum {
Spectrum::new(
Length::new::<nanometer>(800.0)..Length::new::<nanometer>(2500.0),
Length::new::<nanometer>(0.1),
)
.unwrap()
}
/// Helper function for generating a spectrum of a narrow-band HeNe laser.
///
/// This function generates an spectrum in the visible range (350 - 750 nm) with a resolution
/// of 0.1 nm and a (spectrum resolution limited) laser line at 632.816 nm.
pub fn create_he_ne_spectrum(energy: f64) -> Spectrum {
let mut s=create_visible_spectrum();
s.add_single_peak(Length::new::<nanometer>(632.816), energy).unwrap();
s
}
/// Helper function for generating a spectrum of a narrow-band Nd:glass laser.
///
/// This function generates an spectrum in the near infrared range (800 - 2500 nm) with a resolution
/// of 0.1 nm and a (Lorentzian) laser line at 1054 nm with a width of 0.5 nm.
pub fn create_yb_yag_spectrum(energy: f64) -> Spectrum {
let mut s=create_nir_spectrum();
s.add_lorentzian_peak(Length::new::<nanometer>(1054.0), Length::new::<nanometer>(0.5), energy).unwrap();
s
}
#[cfg(test)]
mod test {
use super::*;
use ndarray::array;
fn prep() -> Spectrum {
Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(4.0),
Length::new::<meter>(0.5),
)
.unwrap()
}
#[test]
fn new() {
let s = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(4.0),
Length::new::<meter>(0.5),
);
assert_eq!(
s.as_ref().unwrap().lambdas,
array![1.0, 1.5, 2.0, 2.5, 3.0, 3.5]
);
assert_eq!(s.unwrap().data, array![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
}
#[test]
fn new_negative_resolution() {
let s = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(4.0),
Length::new::<meter>(-0.5),
);
}
#[test]
fn new_wrong_range() {
let s = Spectrum::new(
Length::new::<meter>(4.0)..Length::new::<meter>(1.0),
Length::new::<meter>(0.5),
);
}
#[test]
fn new_negative_range() {
let s = Spectrum::new(
Length::new::<meter>(-1.0)..Length::new::<meter>(4.0),
Length::new::<meter>(0.5),
);
fn from_csv() {
Spectrum::from_csv();
}
#[test]
fn range() {
let s = prep();
assert_eq!(
s.range(),
Length::new::<meter>(1.0)..Length::new::<meter>(3.5)
)
}
#[test]
s.add_single_peak(Length::new::<meter>(2.0), 1.0).is_ok(),
}
#[test]
fn set_single_peak_interpolated() {
s.add_single_peak(Length::new::<meter>(2.25), 1.0).is_ok(),
assert_eq!(s.data[2], 1.0);
assert_eq!(s.data[3], 1.0);
fn set_single_peak_additive() {
s.add_single_peak(Length::new::<meter>(2.0), 1.0).unwrap();
s.add_single_peak(Length::new::<meter>(2.0), 1.0).unwrap();
assert_eq!(s.data[2], 4.0);
}
#[test]
fn set_single_peak_interp_additive() {
s.add_single_peak(Length::new::<meter>(2.0), 1.0).unwrap();
s.add_single_peak(Length::new::<meter>(2.25), 1.0).unwrap();
assert_eq!(s.data[2], 3.0);
assert_eq!(s.data[3], 1.0);
}
#[test]
s.add_single_peak(Length::new::<meter>(1.0), 1.0).is_ok(),
fn set_single_peak_wrong_params() {
let mut s = prep();
assert!(s.add_single_peak(Length::new::<meter>(0.5), 1.0).is_err());
assert!(s.add_single_peak(Length::new::<meter>(4.0), 1.0).is_err());
assert!(s.add_single_peak(Length::new::<meter>(1.5), -1.0).is_err());
fn add_lorentzian() {
let mut s = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(50.0),
Length::new::<meter>(0.1),
)
.unwrap();
assert!(s
.add_lorentzian_peak(Length::new::<meter>(25.0), Length::new::<meter>(0.5), 2.0)
.is_ok());
assert!(f64::abs(s.total_energy() - 2.0) < 0.1)
}
#[test]
fn add_lorentzian_wrong_params() {
let mut s = prep();
.add_lorentzian_peak(Length::new::<meter>(-5.0), Length::new::<meter>(0.5), 2.0)
.add_lorentzian_peak(Length::new::<meter>(2.0), Length::new::<meter>(-0.5), 2.0)
.add_lorentzian_peak(Length::new::<meter>(2.0), Length::new::<meter>(0.5), -2.0)
}
#[test]
s.add_single_peak(Length::new::<meter>(2.0), 1.0).unwrap();
assert_eq!(s.total_energy(), 1.0);
fn total_energy2() {
let mut s = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(4.0),
Length::new::<meter>(1.0),
)
.unwrap();
s.add_single_peak(Length::new::<meter>(1.5), 1.0).unwrap();
assert_eq!(s.total_energy(), 1.0);
}
#[test]
fn scale_vertical() {
let mut s = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(5.0),
Length::new::<meter>(1.0),
)
.unwrap();
s.add_single_peak(Length::new::<meter>(2.5), 1.0).unwrap();
assert_eq!(s.data, array![0.0, 0.25, 0.25, 0.0]);
}
#[test]
fn scale_vertical_negative() {
let mut s = prep();
assert!(s.scale_vertical(-0.5).is_err());
#[test]
fn enclosing_interval() {
let s = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(5.0),
Length::new::<meter>(1.0),
)
.unwrap();
let v = s.enclosing_interval(2.1, 3.9);
assert_eq!(v, vec![Some(1), Some(2), Some(3)]);
}
#[test]
fn enclosing_interval_exact() {
let s = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(5.0),
Length::new::<meter>(1.0),
)
.unwrap();
let v = s.enclosing_interval(2.0, 4.0);
assert_eq!(v, vec![Some(1), Some(2), Some(3)]);
}
#[test]
fn enclosing_interval_upper_bound() {
let s = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(5.0),
Length::new::<meter>(1.0),
)
.unwrap();
let v = s.enclosing_interval(3.0, 5.0);
assert_eq!(v, vec![Some(2), Some(3)]); // ranges are exclusive upper bound !
}
#[test]
fn enclosing_interval_lower_bound() {
let s = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(5.0),
Length::new::<meter>(1.0),
)
.unwrap();
let v = s.enclosing_interval(1.0, 3.0);
assert_eq!(v, vec![Some(0), Some(1), Some(2)]);
}
#[test]
fn enclosing_interval_small_range() {
let s = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(5.0),
Length::new::<meter>(1.0),
)
.unwrap();
let v = s.enclosing_interval(2.6, 2.9);
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
assert_eq!(v, vec![Some(1), Some(2)]);
}
#[test]
fn enclosing_interval_right_outside() {
let s = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(5.0),
Length::new::<meter>(1.0),
)
.unwrap();
let v = s.enclosing_interval(5.1, 7.0);
assert_eq!(v, vec![None, None]);
}
#[test]
fn enclosing_interval_right_partly_outside() {
let s = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(5.0),
Length::new::<meter>(1.0),
)
.unwrap();
let v = s.enclosing_interval(2.1, 7.0);
assert_eq!(v, vec![Some(1), Some(2), Some(3)]);
}
#[test]
fn enclosing_interval_left_outside() {
let s = Spectrum::new(
Length::new::<meter>(5.0)..Length::new::<meter>(10.0),
Length::new::<meter>(1.0),
)
.unwrap();
let v = s.enclosing_interval(1.0, 4.9);
assert_eq!(v, vec![None, None]);
}
#[test]
fn enclosing_interval_left_partly_outside() {
let s = Spectrum::new(
Length::new::<meter>(5.0)..Length::new::<meter>(10.0),
Length::new::<meter>(1.0),
)
.unwrap();
let v = s.enclosing_interval(2.1, 6.5);
assert_eq!(v, vec![Some(0), Some(1), Some(2)]);
#[test]
fn calc_ratio_test() {
assert_eq!(calc_ratio(1.0, 2.0, 3.0, 4.0), 0.0); // bucket completely outside
assert_eq!(calc_ratio(1.0, 4.0, 2.0, 3.0), 1.0); // bucket contains source
assert_eq!(calc_ratio(2.0, 3.0, 0.0, 4.0), 0.25); // bucket is part of source
assert_eq!(calc_ratio(0.0, 1.0, 0.0, 2.0), 0.5); // bucket is part of source (matching left)
assert_eq!(calc_ratio(1.0, 2.0, 0.0, 2.0), 0.5); // bucket is part of source (matching right)
assert_eq!(calc_ratio(0.0, 2.0, 1.0, 3.0), 0.5); // bucket is left outside source
assert_eq!(calc_ratio(0.0, 2.0, 1.0, 2.0), 1.0); // bucket is left outside source (matching)
assert_eq!(calc_ratio(2.0, 4.0, 1.0, 3.0), 0.5); // bucket is right outside source
assert_eq!(calc_ratio(1.0, 4.0, 1.0, 3.0), 1.0); // bucket is right outside source (matching)
assert_eq!(calc_ratio(1.0, 2.0, 1.0, 2.0), 1.0); // bucket matches source
}
#[test]
fn resample() {
let mut s1 = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(5.0),
Length::new::<meter>(1.0),
)
.unwrap();
s1.add_single_peak(Length::new::<meter>(2.0), 1.0).unwrap();
let s2 = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(5.0),
Length::new::<meter>(1.0),
)
.unwrap();
s1.resample(&s2);
assert_eq!(s1.total_energy(), s2.total_energy());
let mut s1 = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(5.0),
Length::new::<meter>(0.5),
)
.unwrap();
let mut s2 = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(6.0),
Length::new::<meter>(1.0),
)
.unwrap();
s2.add_single_peak(Length::new::<meter>(2.0), 1.0).unwrap();
s1.resample(&s2);
assert_eq!(s1.data, array![0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0]);
assert_eq!(s1.total_energy(), s2.total_energy());
}
#[test]
fn resample_interp2() {
Length::new::<meter>(1.0)..Length::new::<meter>(5.0),
Length::new::<meter>(1.0)..Length::new::<meter>(6.0),
Length::new::<meter>(0.5),
)
.unwrap();
s2.add_single_peak(Length::new::<meter>(2.0), 1.0).unwrap();
s1.resample(&s2);
assert_eq!(s1.data, array![0.0, 1.0, 0.0, 0.0]);
assert_eq!(s1.total_energy(), s2.total_energy());
}
#[test]
fn resample_right_outside() {
let mut s1 = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(4.0),
Length::new::<meter>(1.0),
)
.unwrap();
let mut s2 = Spectrum::new(
Length::new::<meter>(4.0)..Length::new::<meter>(6.0),
Length::new::<meter>(1.0),
)
.unwrap();
s2.add_single_peak(Length::new::<meter>(4.0), 1.0).unwrap();
s1.resample(&s2);
assert_eq!(s1.data, array![0.0, 0.0, 0.0]);
assert_eq!(s1.total_energy(), 0.0);
}
#[test]
fn resample_left_outside() {
let mut s1 = Spectrum::new(
Length::new::<meter>(4.0)..Length::new::<meter>(6.0),
Length::new::<meter>(1.0),
)
.unwrap();
let mut s2 = Spectrum::new(
Length::new::<meter>(1.0)..Length::new::<meter>(4.0),
s2.add_single_peak(Length::new::<meter>(2.0), 1.0).unwrap();
s1.resample(&s2);
assert_eq!(s1.data, array![0.0, 0.0]);
assert_eq!(s1.total_energy(), 0.0);
s.add_single_peak(Length::new::<meter>(1.75), 1.0).unwrap();
s2.add_single_peak(Length::new::<meter>(2.25), 0.5).unwrap();
s.add(&s2);
assert_eq!(s.data, array![0.0, 1.0, 1.5, 0.5, 0.0, 0.0]);
}
#[test]
fn sub() {
s.add_single_peak(Length::new::<meter>(1.75), 1.0).unwrap();
s2.add_single_peak(Length::new::<meter>(2.25), 0.5).unwrap();
s.sub(&s2);
assert_eq!(s.data, array![0.0, 1.0, 0.5, 0.0, 0.0, 0.0]);
}