mod solver;
#[cfg(test)]
mod tests;
use super::errors::NormalizationError;
use super::normalization::Normalization;
use crate::atomic::{
AtomicData, AtomicDataProvider, AtomicEmission, AtomicReference, EmissionSelection,
};
use nalgebra::DVector;
use serde::{Deserialize, Serialize};
use std::ops::RangeInclusive;
#[cfg(not(feature = "ndarray-compat"))]
type Array = DVector<f64>;
#[cfg(feature = "ndarray-compat")]
type Array = ndarray::Array1<f64>;
pub(super) fn invalid(message: impl std::fmt::Display) -> NormalizationError {
NormalizationError::Other {
message: format!("MBACK: {message}"),
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MbackErfc {
pub emission: EmissionSelection,
pub width_ev: [f64; 2],
pub amplitude: [f64; 2],
}
impl MbackErfc {
pub fn new(
line: impl Into<String>,
width_ev: RangeInclusive<f64>,
amplitude: RangeInclusive<f64>,
) -> Self {
Self {
emission: EmissionSelection::Line(line.into()),
width_ev: [*width_ev.start(), *width_ev.end()],
amplitude: [*amplitude.start(), *amplitude.end()],
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct MbackOptions {
pub element: String,
pub edge: String,
pub pre_edge: Option<[f64; 2]>,
pub post_edge: Option<[f64; 2]>,
pub degree: usize,
pub erfc: Option<MbackErfc>,
pub reference: Option<AtomicReference>,
}
impl Default for MbackOptions {
fn default() -> Self {
Self {
element: String::new(),
edge: String::new(),
pre_edge: None,
post_edge: None,
degree: 2,
erfc: None,
reference: None,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MbackResult {
pub method: String,
pub requested: MbackOptions,
pub requested_e0: Option<f64>,
pub reference: AtomicReference,
pub tabulated_edge_ev: f64,
pub e0: f64,
pub pre_edge: [f64; 2],
pub post_edge: [f64; 2],
pub energy: Vec<f64>,
pub fit_indices: Vec<usize>,
pub weights: Vec<f64>,
pub scale: f64,
pub energy_scale: f64,
pub coefficients: Vec<f64>,
pub emission: Option<AtomicEmission>,
pub erfc_width: Option<f64>,
pub erfc_amplitude: f64,
pub background: Vec<f64>,
pub f2: Vec<f64>,
pub fpp: Vec<f64>,
pub residual: Vec<f64>,
pub objective: f64,
pub rank: usize,
pub condition: f64,
pub evaluations: usize,
pub edge_step: f64,
pub pre_curve: Vec<f64>,
pub post_curve: Vec<f64>,
pub auxiliary_method: String,
pub norm: Vec<f64>,
pub flat: Vec<f64>,
pub warnings: Vec<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct MBack {
pub e0: Option<f64>,
pub edge_step: Option<f64>,
pub norm: Option<Array>,
pub flat: Option<Array>,
pub options: MbackOptions,
pub result: Option<Box<MbackResult>>,
}
impl MBack {
pub fn new() -> Self {
Self::default()
}
pub fn for_edge(element: impl Into<String>, edge: impl Into<String>) -> Self {
Self {
options: MbackOptions {
element: element.into(),
edge: edge.into(),
..Default::default()
},
..Default::default()
}
}
pub fn e0(mut self, energy_ev: f64) -> Self {
self.e0 = Some(energy_ev);
self
}
pub fn pre_edge(mut self, range: RangeInclusive<f64>) -> Self {
self.options.pre_edge = Some([*range.start(), *range.end()]);
self
}
pub fn post_edge(mut self, range: RangeInclusive<f64>) -> Self {
self.options.post_edge = Some([*range.start(), *range.end()]);
self
}
pub fn degree(mut self, degree: usize) -> Self {
self.options.degree = degree;
self
}
pub fn erfc(mut self, term: MbackErfc) -> Self {
self.options.erfc = Some(term);
self
}
pub fn reference(mut self, reference: AtomicReference) -> Self {
self.options.reference = Some(reference);
self
}
pub fn fit(&self, energy: &[f64], mu: &[f64]) -> Result<MbackResult, NormalizationError> {
let data = AtomicData::new().map_err(invalid)?;
self.fit_with_provider(energy, mu, &data)
}
pub fn fit_with_provider(
&self,
energy: &[f64],
mu: &[f64],
data: &dyn AtomicDataProvider,
) -> Result<MbackResult, NormalizationError> {
solver::fit(self, energy, mu, data)
}
pub fn fill_parameter(&mut self) {}
}
impl Normalization for MBack {
fn normalize(
&mut self,
energy: &DVector<f64>,
mu: &DVector<f64>,
) -> Result<&mut Self, NormalizationError> {
self.norm = None;
self.flat = None;
self.result = None;
self.edge_step = None;
let result = self.fit(energy.as_slice(), mu.as_slice())?;
self.e0 = Some(result.e0);
self.options.reference = Some(result.reference.clone());
self.edge_step = Some(result.edge_step);
self.norm = Some(Array::from_vec(result.norm.clone()));
self.flat = Some(Array::from_vec(result.flat.clone()));
self.result = Some(Box::new(result));
Ok(self)
}
fn get_e0(&self) -> Option<f64> {
self.e0
}
fn get_edge_step(&self) -> Option<f64> {
self.edge_step
}
fn get_norm(&self) -> Option<&Array> {
self.norm.as_ref()
}
fn get_flat(&self) -> Option<&Array> {
self.flat.as_ref()
}
fn set_e0(&mut self, e0: Option<f64>) -> &mut Self {
self.e0 = e0;
self
}
fn set_edge_step(&mut self, step: Option<f64>) -> &mut Self {
self.edge_step = step;
self
}
}