use rusty_fitpack::{splev, splrep};
pub const KTOE: f64 = 3.809_982_116_154_859_7;
pub const ETOK: f64 = 1.0 / KTOE;
pub fn etok(energy: f64) -> f64 {
if energy < 0.0 {
0.0
} else {
(energy * ETOK).sqrt()
}
}
pub fn ktoe(k: f64) -> f64 {
k * k * KTOE
}
pub fn index_of(array: &[f64], value: f64) -> usize {
if array.is_empty() {
return 0;
}
let amin = array.iter().cloned().fold(f64::INFINITY, f64::min);
if value < amin {
return 0;
}
let mut idx = 0;
for (i, &a) in array.iter().enumerate() {
if a <= value {
idx = i;
}
}
idx
}
pub fn index_nearest(array: &[f64], value: f64) -> usize {
let mut best = 0;
let mut bestd = f64::INFINITY;
for (i, &a) in array.iter().enumerate() {
let d = (a - value).abs();
if d < bestd {
bestd = d;
best = i;
}
}
best
}
pub fn gradient(f: &[f64]) -> Vec<f64> {
let n = f.len();
let mut g = vec![0.0; n];
if n < 2 {
return g;
}
g[0] = f[1] - f[0];
g[n - 1] = f[n - 1] - f[n - 2];
for i in 1..n - 1 {
g[i] = (f[i + 1] - f[i - 1]) / 2.0;
}
g
}
pub fn dmude(mu: &[f64], energy: &[f64]) -> Vec<f64> {
let gm = gradient(mu);
let ge = gradient(energy);
gm.iter().zip(&ge).map(|(a, b)| a / b).collect()
}
pub fn remove_dups(arr: &[f64], tiny: f64) -> Vec<f64> {
let n = arr.len();
if n <= 1 {
return arr.to_vec();
}
let min_step = (1..n)
.map(|i| arr[i] - arr[i - 1])
.fold(f64::INFINITY, f64::min);
if min_step > 10.0 * tiny {
return arr.to_vec();
}
let mut add = vec![0.0; n];
let mut previous_val = f64::NAN;
let mut previous_add = 0.0;
for i in 1..n {
if !arr[i - 1].is_nan() {
previous_val = arr[i - 1];
previous_add = add[i - 1];
}
let val = arr[i];
if val.is_nan() || previous_val.is_nan() {
continue;
}
if (val - previous_val).abs() < tiny {
add[i] = previous_add + tiny;
}
}
arr.iter().zip(&add).map(|(a, d)| a + d).collect()
}
pub fn remove_nans2(a: &[f64], b: &[f64]) -> (Vec<f64>, Vec<f64>) {
let mut ao = Vec::with_capacity(a.len());
let mut bo = Vec::with_capacity(b.len());
for (&x, &y) in a.iter().zip(b.iter()) {
if x.is_finite() && y.is_finite() {
ao.push(x);
bo.push(y);
}
}
(ao, bo)
}
fn argsort(a: &[f64]) -> Vec<usize> {
let mut idx: Vec<usize> = (0..a.len()).collect();
idx.sort_by(|&i, &j| a[i].partial_cmp(&a[j]).unwrap_or(std::cmp::Ordering::Equal));
idx
}
pub fn find_energy_step(energy: &[f64], frac_ignore: f64, nave: usize) -> f64 {
let nskip = (frac_ignore * energy.len() as f64) as usize;
let order = argsort(energy);
let e_ordered: Vec<usize> = (0..order.len().saturating_sub(1))
.filter(|&i| order[i + 1] as i64 - order[i] as i64 == 1)
.collect();
let evals: Vec<f64> = e_ordered.iter().map(|&i| energy[i]).collect();
let lo = nskip;
let hi = evals.len().saturating_sub(nskip);
let trimmed = &evals[lo..hi];
let mut ediff: Vec<f64> = (1..trimmed.len())
.map(|i| trimmed[i] - trimmed[i - 1])
.collect();
ediff.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let s = nskip.min(ediff.len());
let e = (nskip + nave).min(ediff.len());
let win = &ediff[s..e];
win.iter().sum::<f64>() / win.len() as f64
}
pub(crate) fn solve_linear(mut a: Vec<Vec<f64>>, mut b: Vec<f64>) -> Vec<f64> {
let n = b.len();
for col in 0..n {
let mut piv = col;
let mut best = a[col][col].abs();
for (r, row) in a.iter().enumerate().skip(col + 1) {
if row[col].abs() > best {
best = row[col].abs();
piv = r;
}
}
a.swap(col, piv);
b.swap(col, piv);
let pivot_row = a[col].clone();
let pivot_b = b[col];
let d = pivot_row[col];
for (row, bv) in a.iter_mut().zip(b.iter_mut()).skip(col + 1) {
let factor = row[col] / d;
if factor != 0.0 {
for (c, pv) in pivot_row.iter().enumerate().skip(col) {
row[c] -= factor * pv;
}
*bv -= factor * pivot_b;
}
}
}
let mut x = vec![0.0; n];
for col in (0..n).rev() {
let dot: f64 = a[col][col + 1..]
.iter()
.zip(&x[col + 1..])
.map(|(aij, xj)| aij * xj)
.sum();
x[col] = (b[col] - dot) / a[col][col];
}
x
}
pub fn polyfit(x: &[f64], y: &[f64], deg: usize) -> Vec<f64> {
let xmin = x.iter().cloned().fold(f64::INFINITY, f64::min);
let xmax = x.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let (scl_a, scl_b) = if xmax > xmin {
(2.0 / (xmax - xmin), -(xmax + xmin) / (xmax - xmin))
} else {
(1.0, 0.0)
};
let nc = deg + 1;
let us: Vec<f64> = x.iter().map(|&xi| scl_a * xi + scl_b).collect();
let mut vtv = vec![vec![0.0; nc]; nc];
let mut vty = vec![0.0; nc];
for (k, &u) in us.iter().enumerate() {
let mut pows = vec![1.0; nc];
for j in 1..nc {
pows[j] = pows[j - 1] * u;
}
for i in 0..nc {
vty[i] += pows[i] * y[k];
for j in 0..nc {
vtv[i][j] += pows[i] * pows[j];
}
}
}
let cu = solve_linear(vtv, vty); let mut cx = vec![0.0; nc];
for (j, &cj) in cu.iter().enumerate() {
let mut comb = 1.0; for (m, cxm) in cx.iter_mut().take(j + 1).enumerate() {
let term = comb * scl_a.powi(m as i32) * scl_b.powi((j - m) as i32);
*cxm += cj * term;
comb = comb * (j - m) as f64 / (m + 1) as f64;
}
}
cx
}
pub fn interp_linear(xnew: &[f64], x: &[f64], y: &[f64]) -> Vec<f64> {
let n = x.len();
xnew.iter()
.map(|&xv| {
if xv <= x[0] {
return y[0];
}
if xv >= x[n - 1] {
return y[n - 1];
}
let mut lo = 0usize;
let mut hi = n - 1;
while hi - lo > 1 {
let mid = (lo + hi) / 2;
if x[mid] <= xv {
lo = mid;
} else {
hi = mid;
}
}
let t = (xv - x[lo]) / (x[lo + 1] - x[lo]);
y[lo] + t * (y[lo + 1] - y[lo])
})
.collect()
}
pub fn interp_cubic(x: &[f64], y: &[f64], xnew: &[f64]) -> Vec<f64> {
let order = 3;
let (t, c, k) = splrep(
x.to_vec(),
y.to_vec(),
None,
None,
None,
Some(order),
None,
None,
None,
None,
None,
None,
);
let mut out = splev(t, c, k, xnew.to_vec(), 0);
let (x0, xl) = (x[0], x[x.len() - 1]);
let below: Vec<usize> = (0..xnew.len()).filter(|&i| xnew[i] < x0).collect();
let above: Vec<usize> = (0..xnew.len()).filter(|&i| xnew[i] > xl).collect();
if below.is_empty() && above.is_empty() {
return out;
}
let ascending = |a: &[f64]| a.windows(2).all(|w| w[1] >= w[0]);
if !ascending(x) || !ascending(xnew) {
for &i in below.iter().chain(&above) {
out[i] = f64::NAN;
}
return out;
}
let ncoef = 5.min(x.len());
for (span, sel_lo) in [(below, 0usize), (above, x.len() - ncoef)] {
if span.is_empty() {
continue;
}
let sx = x[sel_lo..sel_lo + ncoef].to_vec();
let sy = y[sel_lo..sel_lo + ncoef].to_vec();
let (t2, c2, k2) = splrep(
sx,
sy,
None,
None,
None,
Some(order),
None,
None,
None,
None,
None,
None,
);
let xs: Vec<f64> = span.iter().map(|&i| xnew[i]).collect();
let vals = splev(t2, c2, k2, xs, 0);
for (&i, v) in span.iter().zip(vals) {
out[i] = v;
}
}
out
}
pub fn convolve_full(a: &[f64], v: &[f64]) -> Vec<f64> {
let n = a.len() + v.len() - 1;
let mut out = vec![0.0; n];
for (i, &ai) in a.iter().enumerate() {
for (j, &vj) in v.iter().enumerate() {
out[i + j] += ai * vj;
}
}
out
}
pub fn convolve_valid(a: &[f64], v: &[f64]) -> Vec<f64> {
let full = convolve_full(a, v);
let (m, n) = (a.len(), v.len());
let minlen = m.min(n);
let maxlen = m.max(n);
full[(minlen - 1)..maxlen].to_vec()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SmoothForm {
Lorentzian,
Gaussian,
}
pub fn smooth(
x: &[f64],
y: &[f64],
sigma: f64,
xstep: f64,
npad: usize,
form: SmoothForm,
) -> Vec<f64> {
let xmin0 = x.iter().cloned().fold(f64::INFINITY, f64::min);
let xmax0 = x.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let xmin = xstep * ((xmin0 - npad as f64 * xstep) / xstep).trunc();
let xmax = xstep * ((xmax0 + npad as f64 * xstep) / xstep).trunc();
let npts1 = 1 + ((xmax - xmin + xstep * 0.1).abs() / xstep) as usize;
let npts = npts1.min(50 * x.len());
let x0: Vec<f64> = (0..npts)
.map(|i| {
if npts == 1 {
xmin
} else {
xmin + (xmax - xmin) * i as f64 / (npts - 1) as f64
}
})
.collect();
let y0 = interp_linear(&x0, x, y);
let sig = sigma / xstep;
let nwin = 2 * npts;
let win: Vec<f64> = (0..nwin)
.map(|i| {
let d = i as f64 - npts as f64;
match form {
SmoothForm::Lorentzian => sig / (d * d + sig * sig),
SmoothForm::Gaussian => (-(d * d) / (2.0 * sig * sig)).exp(),
}
})
.collect();
let wsum: f64 = win.iter().sum();
let winn: Vec<f64> = win.iter().map(|w| w / wsum).collect();
let mut y1 = Vec::with_capacity(3 * npts);
for i in (1..npts).rev() {
y1.push(y0[i]); }
y1.extend_from_slice(&y0); for i in (0..npts).rev() {
y1.push(y0[i]); }
let mut y2 = convolve_valid(&winn, &y1);
if y2.len() > x0.len() {
let nex = (y2.len() - x0.len()) / 2;
y2 = y2[nex..nex + x0.len()].to_vec();
}
interp_linear(x, &x0, &y2)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gradient_unit_spacing() {
let f = [1.0, 4.0, 9.0, 16.0]; let g = gradient(&f);
assert_eq!(g[0], 3.0); assert_eq!(g[1], 4.0); assert_eq!(g[2], 6.0); assert_eq!(g[3], 7.0); }
#[test]
fn polyfit_recovers_line() {
let x = [0.0, 1.0, 2.0, 3.0, 4.0];
let y: Vec<f64> = x.iter().map(|&xi| 2.0 + 3.0 * xi).collect();
let c = polyfit(&x, &y, 1);
assert!((c[0] - 2.0).abs() < 1e-10, "c0={}", c[0]);
assert!((c[1] - 3.0).abs() < 1e-10, "c1={}", c[1]);
}
#[test]
fn polyfit_recovers_quadratic_shifted() {
let x: Vec<f64> = (0..20).map(|i| 8000.0 + 5.0 * i as f64).collect();
let y: Vec<f64> = x
.iter()
.map(|&xi| 1.5 - 2e-3 * xi + 4e-7 * xi * xi)
.collect();
let c = polyfit(&x, &y, 2);
assert!((c[0] - 1.5).abs() < 1e-6, "c0={}", c[0]);
assert!((c[1] + 2e-3).abs() < 1e-9, "c1={}", c[1]);
assert!((c[2] - 4e-7).abs() < 1e-13, "c2={}", c[2]);
}
#[test]
fn interp_linear_clamps_edges() {
let x = [0.0, 1.0, 2.0];
let y = [0.0, 10.0, 20.0];
assert_eq!(interp_linear(&[-1.0], &x, &y)[0], 0.0);
assert_eq!(interp_linear(&[3.0], &x, &y)[0], 20.0);
assert_eq!(interp_linear(&[0.5], &x, &y)[0], 5.0);
}
#[test]
fn remove_dups_nudges_repeats() {
let x = [1.0, 2.0, 3.0, 3.0, 3.0, 4.0];
let out = remove_dups(&x, 1e-6);
assert_eq!(out[2], 3.0);
assert!((out[3] - 3.000001).abs() < 1e-12);
assert!((out[4] - 3.000002).abs() < 1e-12);
assert_eq!(out[5], 4.0);
}
}