mod calculate;
#[cfg(test)]
mod tests;
use crate::atomic::{
AtomicData, AtomicDataIdentity, AtomicEdge, AtomicEmission, CompoundAttenuation,
EmissionSelection,
};
use serde::{Deserialize, Serialize};
use std::ops::RangeInclusive;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum AbsorptionMode {
#[default]
Unknown,
Transmission,
Fluorescence,
}
impl AbsorptionMode {
pub(crate) fn is_unknown(&self) -> bool {
*self == Self::Unknown
}
}
#[derive(Debug, thiserror::Error)]
#[error("Fluorescence correction: {0}")]
pub struct FluorescenceError(pub String);
type Result<T> = std::result::Result<T, FluorescenceError>;
fn invalid(value: impl std::fmt::Display) -> FluorescenceError {
FluorescenceError(value.to_string())
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct FluorescenceCorrection {
pub formula: String,
pub element: String,
pub edge: String,
pub emission: Option<EmissionSelection>,
pub incidence_deg: Option<f64>,
pub exit_deg: Option<f64>,
pub e0: Option<f64>,
pub pre_edge: Option<[f64; 2]>,
pub post_edge: Option<[f64; 2]>,
pub degree: usize,
pub reference: Option<AtomicDataIdentity>,
}
impl FluorescenceCorrection {
pub fn new(
formula: impl Into<String>,
element: impl Into<String>,
edge: impl Into<String>,
) -> Self {
Self {
formula: formula.into(),
element: element.into(),
edge: edge.into(),
emission: None,
incidence_deg: None,
exit_deg: None,
e0: None,
pre_edge: None,
post_edge: None,
degree: 1,
reference: None,
}
}
pub fn line(mut self, line: impl Into<String>) -> Self {
self.emission = Some(EmissionSelection::Line(line.into()));
self
}
pub fn line_family(mut self, family: impl Into<String>) -> Self {
self.emission = Some(EmissionSelection::Family(family.into()));
self
}
pub fn angles(mut self, incidence_deg: f64, exit_deg: f64) -> Self {
self.incidence_deg = Some(incidence_deg);
self.exit_deg = Some(exit_deg);
self
}
pub fn e0(mut self, value: f64) -> Self {
self.e0 = Some(value);
self
}
pub fn pre_edge(mut self, range: RangeInclusive<f64>) -> Self {
self.pre_edge = Some([*range.start(), *range.end()]);
self
}
pub fn post_edge(mut self, range: RangeInclusive<f64>) -> Self {
self.post_edge = Some([*range.start(), *range.end()]);
self
}
pub fn degree(mut self, degree: usize) -> Self {
self.degree = degree;
self
}
pub fn apply(&self, energy: &[f64], mu: &[f64]) -> Result<FluorescenceCorrectionResult> {
calculate::apply(self, energy, mu, &AtomicData::new().map_err(invalid)?)
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct FluorescenceInternalNormalization {
pub e0: f64,
pub pre_edge: [f64; 2],
pub post_edge: [f64; 2],
pub degree: usize,
pub edge_step: f64,
pub pre_curve: Vec<f64>,
pub post_curve: Vec<f64>,
pub norm: Vec<f64>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct FluorescenceCorrectionResult {
pub method: String,
pub requested: FluorescenceCorrection,
pub input_mode: AbsorptionMode,
pub energy: Vec<f64>,
pub original_mu: Vec<f64>,
pub corrected_mu: Vec<f64>,
pub edge: AtomicEdge,
pub emission: AtomicEmission,
pub attenuation: CompoundAttenuation,
pub geometry_ratio: f64,
pub alpha: f64,
pub internal: FluorescenceInternalNormalization,
pub denominator: Vec<f64>,
pub factor: Vec<f64>,
pub minimum_denominator: f64,
pub maximum_amplification: f64,
pub singularity_threshold: f64,
pub warnings: Vec<String>,
}
impl FluorescenceCorrectionResult {
pub fn definition(&self) -> FluorescenceCorrection {
let mut model = self.requested.clone();
model.e0 = Some(self.internal.e0);
model.pre_edge = Some(self.internal.pre_edge);
model.post_edge = Some(self.internal.post_edge);
model.reference = Some(self.edge.reference.data.clone());
model
}
}