add ClPotentials derivation from LSV cathodic peaks

This commit is contained in:
jess 2026-04-03 02:07:47 -07:00
parent 01edb88e0b
commit c6bbaa5bc4
1 changed files with 63 additions and 0 deletions

View File

@ -91,6 +91,69 @@ pub fn detect_qhq_peak(points: &[LsvPoint]) -> Option<f32> {
.map(|&(idx, _)| v_vals[idx])
}
#[derive(Debug, Clone, Copy)]
pub struct ClPotentials {
pub v_free: f32,
pub v_free_detected: bool,
pub v_total: f32,
pub v_total_detected: bool,
}
pub fn derive_cl_potentials(points: &[LsvPoint]) -> ClPotentials {
let default = ClPotentials {
v_free: 100.0,
v_free_detected: false,
v_total: -200.0,
v_total_detected: false,
};
if points.len() < 5 {
return default;
}
let i_vals: Vec<f32> = points.iter().map(|p| p.i_ua).collect();
let v_vals: Vec<f32> = points.iter().map(|p| p.v_mv).collect();
let window = 5.max(points.len() / 50);
let smoothed = smooth(&i_vals, window);
let i_min = smoothed.iter().copied().fold(f32::INFINITY, f32::min);
let i_max = smoothed.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let prominence = (i_max - i_min) * 0.05;
let extrema = find_extrema(&v_vals, &smoothed, prominence);
// v_free: most prominent cathodic peak (is_max==false) in +300 to -300 mV
let free_peak = extrema.iter()
.filter(|&&(idx, is_max)| !is_max && v_vals[idx] >= -300.0 && v_vals[idx] <= 300.0)
.min_by(|a, b| smoothed[a.0].partial_cmp(&smoothed[b.0]).unwrap_or(std::cmp::Ordering::Equal));
let (v_free, v_free_detected, free_idx) = match free_peak {
Some(&(idx, _)) => (v_vals[idx], true, Some(idx)),
None => (100.0, false, None),
};
// v_total: secondary cathodic peak between (v_free - 100) and -500 mV, excluding free peak
let total_lo = -500.0_f32;
let total_hi = v_free - 100.0;
let total_peak = extrema.iter()
.filter(|&&(idx, is_max)| {
!is_max
&& v_vals[idx] >= total_lo
&& v_vals[idx] <= total_hi
&& Some(idx) != free_idx
})
.min_by(|a, b| smoothed[a.0].partial_cmp(&smoothed[b.0]).unwrap_or(std::cmp::Ordering::Equal));
let (v_total, v_total_detected) = match total_peak {
Some(&(idx, _)) => (v_vals[idx], true),
None => (v_free - 300.0, false),
};
let v_total = v_total.max(-400.0);
ClPotentials { v_free, v_free_detected, v_total, v_total_detected }
}
pub fn detect_peaks(points: &[LsvPoint]) -> Vec<LsvPeak> {
if points.len() < 5 {
return Vec::new();