pub mod dielectric_spectrum;
pub mod ir_flux;
pub mod ir_spectrum;
pub mod power_spectrum;
pub mod raman_spectrum;
pub mod raman_tensor;
pub mod resonance_raman_spectrum;
pub mod resonance_raman_tensor;
pub mod roa_cross_tensor;
pub mod roa_spectrum;
pub mod spectra;
pub mod vcd_cross_flux;
pub mod vcd_spectrum;
pub use dielectric_spectrum::{
DielectricSpectrumResult, EinsteinHelfandSpectrum, GreenKuboSpectrum,
};
pub use ir_flux::{IRFlux, IRFluxArgs, IRFluxResult};
pub use ir_spectrum::IRSpectrum;
pub use power_spectrum::PowerSpectrum;
pub use raman_spectrum::RamanSpectrum;
pub use raman_tensor::{RamanTensor, RamanTensorArgs, RamanTensorResult};
pub use resonance_raman_spectrum::ResonanceRamanSpectrum;
pub use resonance_raman_tensor::{ResonanceRamanArgs, ResonanceRamanTensor};
pub use roa_cross_tensor::{RoaCrossArgs, RoaCrossResult, RoaCrossTensor};
pub use roa_spectrum::RoaSpectrum;
pub use spectra::{RamanSpectrumResult, SpectrumResult};
pub use vcd_cross_flux::{VcdCrossArgs, VcdCrossFlux, VcdCrossResult};
pub use vcd_spectrum::VcdSpectrum;
use ndarray::{Array1, Array2, ArrayD};
use rustfft::FftPlanner;
use crate::compute::error::ComputeError;
use crate::compute::fit::forward_fft_onesided;
use molrs::signal as sig;
const C_MS: f64 = 299_792_458.0;
const FS_TO_S: f64 = 1e-15;
const M_TO_CM: f64 = 100.0;
pub(crate) const ANGULAR_FREQ_TO_CM1: f64 =
1.0 / (2.0 * std::f64::consts::PI * C_MS * FS_TO_S * M_TO_CM);
const MAX_EXP_ARG: f64 = 700.0;
pub(crate) fn window_and_fft(
planner: &mut FftPlanner<f64>,
acf: &Array1<f64>,
dt_fs: f64,
) -> Result<(Array1<f64>, Array1<f64>), ComputeError> {
let n = acf.len();
let acf_dyn = ArrayD::from_shape_vec(ndarray::IxDyn(&[n]), acf.to_vec()).map_err(|e| {
ComputeError::BadShape {
expected: "1d".into(),
got: e.to_string(),
}
})?;
let windowed = sig::apply_window(&acf_dyn, sig::WindowType::CosineSq, 0).map_err(|e| {
ComputeError::OutOfRange {
field: "apply_window",
value: e.to_string(),
}
})?;
let windowed_1d: Array1<f64> = windowed.iter().copied().collect();
let n_pad = (4 * n).next_power_of_two();
Ok(acf_to_spectrum(planner, &windowed_1d, dt_fs, n_pad))
}
pub(crate) fn acf_to_spectrum(
planner: &mut FftPlanner<f64>,
acf: &Array1<f64>,
dt_fs: f64,
n_pad: usize,
) -> (Array1<f64>, Array1<f64>) {
let freqs_rad = sig::frequency_grid(n_pad, dt_fs);
let intensities = acf_to_intensities(planner, acf, n_pad);
let n_freq = intensities.len();
let mut frequencies_cm1 = Array1::zeros(n_freq);
for j in 0..n_freq {
frequencies_cm1[j] = freqs_rad[j] * ANGULAR_FREQ_TO_CM1;
}
(frequencies_cm1, intensities)
}
pub(crate) fn acf_to_intensities(
planner: &mut FftPlanner<f64>,
acf: &Array1<f64>,
n_pad: usize,
) -> Array1<f64> {
let acf_vec;
let acf_slice = match acf.as_slice() {
Some(s) => s,
None => {
acf_vec = acf.to_vec();
&acf_vec
}
};
let bins = forward_fft_onesided(planner, acf_slice, n_pad);
let mut intensities = Array1::zeros(bins.len());
for (j, b) in bins.iter().enumerate() {
intensities[j] = b.re / n_pad as f64;
}
intensities
}
pub(crate) fn cosine_sq_window(n: usize) -> Vec<f64> {
if n <= 1 {
return vec![1.0];
}
(0..n)
.map(|i| {
let angle = std::f64::consts::PI * i as f64 / (2.0 * (n - 1) as f64);
angle.cos().powi(2)
})
.collect()
}
pub(crate) fn bose_factor(nu: f64, temperature_k: f64) -> f64 {
if nu <= 0.0 || temperature_k <= 0.0 {
return 1.0;
}
let exponent = -1.438777 * nu / temperature_k;
if exponent > -MAX_EXP_ARG {
1.0 / (1.0 - exponent.exp())
} else {
1.0
}
}
pub(crate) fn cross_correlate(
planner: &mut FftPlanner<f64>,
a: &Array1<f64>,
b: &Array1<f64>,
max_lag: usize,
) -> Array1<f64> {
sig::xcorr_fft_with_planner(planner, a, b, max_lag)
.expect("cross_correlate: equal-length inputs with max_lag < n by construction")
}
pub(crate) fn central_diff_col(series: &Array2<f64>, col: usize, dt: f64) -> Array1<f64> {
let n = series.shape()[0];
let inv_2dt = 0.5 / dt;
(1..n - 1)
.map(|t| (series[[t + 1, col]] - series[[t - 1, col]]) * inv_2dt)
.collect()
}