use rayon::prelude::*;
use crate::xasdata::group::XasGroup;
use crate::xasdata::reader::ColumnFile;
use crate::xasdata::reduce::{FtParams, autobk_group, normalize, xftf_group};
use crate::xasdata::xmu::{MuSpec, XmuError, build_mu};
use crate::xasproc::{AutobkParams, PreEdgeParams};
pub fn make_xmu_batch(files: &[ColumnFile], spec: &MuSpec) -> Vec<Result<XasGroup, XmuError>> {
files
.par_iter()
.map(|cf| {
let (energy, mu) = build_mu(cf, spec)?;
let label = cf
.path
.as_ref()
.and_then(|p| p.file_stem())
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_else(|| "group".to_owned());
let mut g = XasGroup::from_mu(label, energy, mu);
g.filename = cf.path.clone();
Ok(g)
})
.collect()
}
pub fn reduce_all(
groups: &mut [XasGroup],
pre: &PreEdgeParams,
bk: &AutobkParams,
ft: &FtParams,
err_sigma: f64,
) -> usize {
groups
.par_iter_mut()
.filter(|g| !g.mu.is_empty())
.map(|g| {
normalize(g, pre);
autobk_group(g, bk, err_sigma);
xftf_group(g, ft);
})
.count()
}
pub fn average_curves(curves: &[(&[f64], &[f64])]) -> Option<(Vec<f64>, Vec<f64>)> {
if curves.is_empty() {
return None;
}
for (x, y) in curves {
if x.len() != y.len() || x.len() < 2 {
return None;
}
}
let lo = curves
.iter()
.map(|(x, _)| x[0])
.fold(f64::NEG_INFINITY, f64::max);
let hi = curves
.iter()
.map(|(x, _)| *x.last().unwrap())
.fold(f64::INFINITY, f64::min);
if hi.partial_cmp(&lo) != Some(std::cmp::Ordering::Greater) {
return None;
}
let grid: Vec<f64> = curves[0]
.0
.iter()
.copied()
.filter(|&v| v >= lo && v <= hi)
.collect();
if grid.len() < 2 {
return None;
}
let mut sum = vec![0.0; grid.len()];
for (x, y) in curves {
let yi = crate::xasproc::mathutils::interp_linear(&grid, x, y);
for (s, v) in sum.iter_mut().zip(yi) {
*s += v;
}
}
let n = curves.len() as f64;
for s in &mut sum {
*s /= n;
}
Some((grid, sum))
}
pub fn resample_matrix(
curves: &[(&[f64], &[f64])],
xmin: f64,
xmax: f64,
npts: usize,
) -> Option<(Vec<f64>, Vec<Vec<f64>>)> {
if curves.is_empty() || npts < 2 {
return None;
}
for (x, y) in curves {
if x.len() != y.len() || x.len() < 2 {
return None;
}
}
let lo = curves.iter().map(|(x, _)| x[0]).fold(xmin, f64::max);
let hi = curves
.iter()
.map(|(x, _)| *x.last().unwrap())
.fold(xmax, f64::min);
if hi.partial_cmp(&lo) != Some(std::cmp::Ordering::Greater) {
return None;
}
let step = (hi - lo) / (npts - 1) as f64;
let grid: Vec<f64> = (0..npts).map(|i| lo + step * i as f64).collect();
let rows = curves
.iter()
.map(|(x, y)| crate::xasproc::mathutils::interp_linear(&grid, x, y))
.collect();
Some((grid, rows))
}
pub fn peak_in_range(x: &[f64], y: &[f64], lo: f64, hi: f64) -> Option<(f64, f64)> {
let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
let mut best: Option<(f64, f64)> = None;
for (&xi, &yi) in x.iter().zip(y) {
if xi < lo || xi > hi {
continue;
}
match best {
Some((_, by)) if yi <= by => {}
_ => best = Some((xi, yi)),
}
}
best
}
#[cfg(test)]
mod tests {
use super::*;
use crate::xasdata::reader::ColumnFile;
use crate::xasdata::xmu::MuSpec;
#[test]
fn make_xmu_batch_applies_one_spec() {
let a = ColumnFile::from_text("# e mu\n1 0.5\n2 1.5\n").unwrap();
let b = ColumnFile::from_text("# e mu\n1 0.7\n2 1.1\n").unwrap();
let one_col = ColumnFile::from_text("# e\n1\n2\n").unwrap();
let spec = MuSpec::Raw { energy: 0, mu: 1 };
let out = make_xmu_batch(&[a, b, one_col], &spec);
assert_eq!(out.len(), 3);
assert_eq!(out[0].as_ref().unwrap().mu, vec![0.5, 1.5]);
assert_eq!(out[1].as_ref().unwrap().mu, vec![0.7, 1.1]);
assert_eq!(out[2].as_ref().unwrap_err(), &XmuError::BadColumn(1));
}
#[test]
fn reduce_all_populates_k_chi_r() {
let cf = ColumnFile::from_text(include_str!("../../tests/data/cu.xmu")).unwrap();
let (energy, mu) = build_mu(&cf, &MuSpec::Raw { energy: 0, mu: 1 }).unwrap();
let mut groups = vec![
XasGroup::from_mu("a", energy.clone(), mu.clone()),
XasGroup::from_mu("b", energy, mu),
XasGroup::default(), ];
let n = reduce_all(
&mut groups,
&PreEdgeParams::default(),
&AutobkParams::default(),
&FtParams::default(),
0.0,
);
assert_eq!(n, 2);
for g in &groups[..2] {
assert!(g.norm.as_ref().is_some_and(|v| !v.is_empty()));
assert!(g.k.as_ref().is_some_and(|v| !v.is_empty()));
assert!(g.chi.as_ref().is_some_and(|v| !v.is_empty()));
assert!(g.r.as_ref().is_some_and(|v| !v.is_empty()));
}
assert!(groups[2].k.is_none());
}
#[test]
fn average_of_identical_curves_is_the_curve() {
let x = [0.0, 1.0, 2.0, 3.0];
let y = [1.0, 2.0, 3.0, 4.0];
let (gx, gy) = average_curves(&[(&x, &y), (&x, &y)]).unwrap();
assert_eq!(gx, x.to_vec());
for (a, b) in gy.iter().zip(y.iter()) {
assert!((a - b).abs() < 1e-12);
}
}
#[test]
fn average_interpolates_onto_overlap() {
let xa = [0.0, 1.0, 2.0, 3.0];
let ya = [0.0, 10.0, 20.0, 30.0];
let xb = [1.0, 3.0];
let yb = [12.0, 32.0]; let (gx, gy) = average_curves(&[(&xa, &ya), (&xb, &yb)]).unwrap();
assert_eq!(gx, vec![1.0, 2.0, 3.0]);
let want = [11.0, 21.0, 31.0];
for (a, b) in gy.iter().zip(want.iter()) {
assert!((a - b).abs() < 1e-12, "got {gy:?}");
}
}
#[test]
fn average_rejects_disjoint_and_malformed() {
assert!(average_curves(&[]).is_none());
let x = [0.0, 1.0];
let short = [0.0];
assert!(average_curves(&[(&x[..], &short[..])]).is_none()); let xa = [0.0, 1.0];
let xb = [5.0, 6.0];
let y = [1.0, 2.0];
assert!(average_curves(&[(&xa, &y), (&xb, &y)]).is_none()); }
#[test]
fn resample_matrix_clamps_to_overlap_and_interpolates() {
let xa = [0.0, 1.0, 2.0, 3.0, 4.0];
let ya = [0.0, 10.0, 20.0, 30.0, 40.0]; let xb = [1.0, 2.0, 3.0];
let yb = [100.0, 200.0, 300.0]; let (grid, rows) = resample_matrix(&[(&xa, &ya), (&xb, &yb)], 0.0, 4.0, 3).unwrap();
assert_eq!(grid, vec![1.0, 2.0, 3.0]);
assert_eq!(rows.len(), 2);
assert!((rows[0][1] - 20.0).abs() < 1e-12);
assert!((rows[1][1] - 200.0).abs() < 1e-12);
}
#[test]
fn resample_matrix_rejects_bad() {
let x = [0.0, 1.0];
let y = [0.0, 1.0];
assert!(resample_matrix(&[(&x, &y)], 0.0, 1.0, 1).is_none()); let xb = [5.0, 6.0];
assert!(resample_matrix(&[(&x, &y), (&xb, &y)], 0.0, 6.0, 4).is_none()); }
#[test]
fn peak_in_range_finds_max_first_on_ties() {
let x = [0.0, 1.0, 2.0, 3.0, 4.0];
let y = [0.0, 5.0, 9.0, 9.0, 1.0];
assert_eq!(peak_in_range(&x, &y, 0.0, 4.0), Some((2.0, 9.0)));
assert_eq!(peak_in_range(&x, &y, 3.5, 4.5), Some((4.0, 1.0)));
assert_eq!(peak_in_range(&x, &y, 4.0, 0.0), Some((2.0, 9.0)));
assert_eq!(peak_in_range(&x, &y, 10.0, 11.0), None);
}
}