Skip to main content

phasesmith_core/
radiation.rs

1//! Wavelength-component models for constant-wavelength radiation.
2
3use std::error::Error;
4use std::fmt::{Display, Formatter};
5
6/// Errors in a wavelength-component array model.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum WavelengthComponentsError {
9    /// Wavelength and relative-intensity arrays differ in length.
10    LengthMismatch,
11    /// At least one component is required.
12    Empty,
13    /// A wavelength is not positive and finite.
14    InvalidWavelength {
15        /// Index of the invalid component.
16        component: usize,
17    },
18    /// A relative integrated intensity is negative or non-finite.
19    InvalidRelativeIntensity {
20        /// Index of the invalid component.
21        component: usize,
22    },
23    /// The reference component must have positive relative intensity.
24    NonPositiveReferenceIntensity,
25}
26
27impl Display for WavelengthComponentsError {
28    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
29        match self {
30            Self::LengthMismatch => write!(
31                formatter,
32                "wavelength and relative-intensity arrays must have equal length"
33            ),
34            Self::Empty => write!(formatter, "at least one wavelength component is required"),
35            Self::InvalidWavelength { component } => write!(
36                formatter,
37                "wavelength component {component} must be positive and finite"
38            ),
39            Self::InvalidRelativeIntensity { component } => write!(
40                formatter,
41                "relative intensity component {component} must be non-negative and finite"
42            ),
43            Self::NonPositiveReferenceIntensity => write!(
44                formatter,
45                "reference component relative intensity must be positive"
46            ),
47        }
48    }
49}
50
51impl Error for WavelengthComponentsError {}
52
53/// Validated borrowed wavelength and relative integrated-intensity arrays.
54///
55/// Component zero is the reference. Secondary wavelength and intensity
56/// parameters are represented as ratios to component zero. Relative
57/// intensities are normalized to unit sum during accumulation.
58#[derive(Clone, Copy, Debug, PartialEq)]
59pub struct WavelengthComponentsView<'a> {
60    wavelengths_angstrom: &'a [f64],
61    relative_intensities: &'a [f64],
62}
63
64impl<'a> WavelengthComponentsView<'a> {
65    /// Validate and borrow a wavelength-component model.
66    ///
67    /// # Errors
68    ///
69    /// Returns [`WavelengthComponentsError`] for unequal/empty arrays,
70    /// invalid wavelengths or intensities, or a zero reference intensity.
71    pub fn new(
72        wavelengths_angstrom: &'a [f64],
73        relative_intensities: &'a [f64],
74    ) -> Result<Self, WavelengthComponentsError> {
75        if wavelengths_angstrom.len() != relative_intensities.len() {
76            return Err(WavelengthComponentsError::LengthMismatch);
77        }
78        if wavelengths_angstrom.is_empty() {
79            return Err(WavelengthComponentsError::Empty);
80        }
81        for (component, wavelength) in wavelengths_angstrom.iter().copied().enumerate() {
82            if !wavelength.is_finite() || wavelength <= 0.0 {
83                return Err(WavelengthComponentsError::InvalidWavelength { component });
84            }
85        }
86        for (component, intensity) in relative_intensities.iter().copied().enumerate() {
87            if !intensity.is_finite() || intensity < 0.0 {
88                return Err(WavelengthComponentsError::InvalidRelativeIntensity { component });
89            }
90        }
91        if relative_intensities[0] <= 0.0 {
92            return Err(WavelengthComponentsError::NonPositiveReferenceIntensity);
93        }
94        Ok(Self {
95            wavelengths_angstrom,
96            relative_intensities,
97        })
98    }
99
100    /// Number of wavelength components.
101    #[must_use]
102    pub const fn len(self) -> usize {
103        self.wavelengths_angstrom.len()
104    }
105
106    /// Whether the model contains no components.
107    #[must_use]
108    pub const fn is_empty(self) -> bool {
109        self.wavelengths_angstrom.is_empty()
110    }
111
112    pub(crate) const fn wavelength(self, component: usize) -> f64 {
113        self.wavelengths_angstrom[component]
114    }
115
116    pub(crate) const fn relative_intensity(self, component: usize) -> f64 {
117        self.relative_intensities[component]
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn validates_component_arrays_and_reference_intensity() {
127        let wavelengths = [1.540_56, 1.544_39];
128        let intensities = [1.0, 0.5];
129        let model = WavelengthComponentsView::new(&wavelengths, &intensities).expect("valid");
130        assert_eq!(model.len(), 2);
131        assert_eq!(model.wavelength(1).to_bits(), 1.544_39_f64.to_bits());
132        assert_eq!(model.relative_intensity(1).to_bits(), 0.5_f64.to_bits());
133        assert_eq!(
134            WavelengthComponentsView::new(&wavelengths, &[0.0, 1.0]),
135            Err(WavelengthComponentsError::NonPositiveReferenceIntensity)
136        );
137    }
138}