use std::f64::consts::PI;
use crate::matrix::FdMatrix;
use super::compute_mean_curve;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct LombScargleResult {
pub frequencies: Vec<f64>,
pub periods: Vec<f64>,
pub power: Vec<f64>,
pub peak_period: f64,
pub peak_frequency: f64,
pub peak_power: f64,
pub false_alarm_probability: f64,
pub significance: f64,
}
pub fn lomb_scargle(
times: &[f64],
values: &[f64],
frequencies: Option<&[f64]>,
oversampling: Option<f64>,
nyquist_factor: Option<f64>,
) -> LombScargleResult {
let n = times.len();
if n != values.len() || n < 3 {
return LombScargleResult {
frequencies: vec![],
periods: vec![],
power: vec![],
peak_period: f64::NAN,
peak_frequency: f64::NAN,
peak_power: f64::NAN,
false_alarm_probability: f64::NAN,
significance: f64::NAN,
};
}
let mean_y: f64 = values.iter().sum::<f64>() / n as f64;
let var_y: f64 = values.iter().map(|&y| (y - mean_y).powi(2)).sum::<f64>() / (n - 1) as f64;
let freq_vec: Vec<f64>;
let freqs = if let Some(f) = frequencies {
f
} else {
freq_vec = generate_ls_frequencies(
times,
oversampling.unwrap_or(4.0),
nyquist_factor.unwrap_or(1.0),
);
&freq_vec
};
let mut power = Vec::with_capacity(freqs.len());
for &freq in freqs {
let omega = 2.0 * PI * freq;
let p = lomb_scargle_single_freq(times, values, mean_y, var_y, omega);
power.push(p);
}
let (peak_idx, &peak_power) = power
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or((0, &0.0));
let peak_frequency = freqs.get(peak_idx).copied().unwrap_or(0.0);
let peak_period = if peak_frequency > 0.0 {
1.0 / peak_frequency
} else {
f64::INFINITY
};
let n_indep = estimate_independent_frequencies(times, freqs.len());
let fap = lomb_scargle_fap(peak_power, n_indep, n);
let periods: Vec<f64> = freqs
.iter()
.map(|&f| if f > 0.0 { 1.0 / f } else { f64::INFINITY })
.collect();
LombScargleResult {
frequencies: freqs.to_vec(),
periods,
power,
peak_period,
peak_frequency,
peak_power,
false_alarm_probability: fap,
significance: 1.0 - fap,
}
}
pub(super) fn lomb_scargle_single_freq(
times: &[f64],
values: &[f64],
mean_y: f64,
var_y: f64,
omega: f64,
) -> f64 {
if var_y <= 0.0 || omega <= 0.0 {
return 0.0;
}
let n = times.len();
let mut sum_sin2 = 0.0;
let mut sum_cos2 = 0.0;
for &t in times {
let arg = 2.0 * omega * t;
sum_sin2 += arg.sin();
sum_cos2 += arg.cos();
}
let tau = (sum_sin2).atan2(sum_cos2) / (2.0 * omega);
let mut ss = 0.0; let mut sc = 0.0; let mut css = 0.0; let mut sss = 0.0;
for i in 0..n {
let y_centered = values[i] - mean_y;
let arg = omega * (times[i] - tau);
let c = arg.cos();
let s = arg.sin();
sc += y_centered * c;
ss += y_centered * s;
css += c * c;
sss += s * s;
}
let css = css.max(1e-15);
let sss = sss.max(1e-15);
0.5 * (sc * sc / css + ss * ss / sss) / var_y
}
pub(super) fn generate_ls_frequencies(
times: &[f64],
oversampling: f64,
nyquist_factor: f64,
) -> Vec<f64> {
let n = times.len();
if n < 2 {
return vec![0.0];
}
let t_min = times.iter().copied().fold(f64::INFINITY, f64::min);
let t_max = times.iter().copied().fold(f64::NEG_INFINITY, f64::max);
let t_span = (t_max - t_min).max(1e-10);
let f_min = 1.0 / t_span;
let f_nyquist = 0.5 * (n - 1) as f64 / t_span;
let f_max = f_nyquist * nyquist_factor;
let df = f_min / oversampling;
let n_freq = ((f_max - f_min) / df).ceil() as usize + 1;
let n_freq = n_freq.min(10000);
(0..n_freq).map(|i| f_min + i as f64 * df).collect()
}
pub(super) fn estimate_independent_frequencies(times: &[f64], n_freq: usize) -> usize {
let n = times.len();
n.min(n_freq)
}
pub(super) fn lomb_scargle_fap(power: f64, n_indep: usize, _n_data: usize) -> f64 {
if power <= 0.0 || n_indep == 0 {
return 1.0;
}
let prob_single = 1.0 - (-power).exp();
if prob_single >= 1.0 {
return 0.0; }
if prob_single <= 0.0 {
return 1.0; }
let log_prob = prob_single.ln();
let log_cdf = n_indep as f64 * log_prob;
if log_cdf < -700.0 {
0.0 } else {
1.0 - log_cdf.exp()
}
}
pub fn lomb_scargle_fdata(
data: &FdMatrix,
argvals: &[f64],
oversampling: Option<f64>,
nyquist_factor: Option<f64>,
) -> LombScargleResult {
let mean_curve = compute_mean_curve(data);
lomb_scargle(argvals, &mean_curve, None, oversampling, nyquist_factor)
}