#![allow(dead_code)]
use crate::{faddeeva_complex, SpecialError, SpecialResult};
use scirs2_core::ndarray::{Array1, ArrayView1};
use scirs2_core::numeric::Complex64;
use scirs2_core::numeric::{Float, FromPrimitive};
use scirs2_core::validation::{check_finite, check_positive};
use std::fmt::{Debug, Display};
#[allow(dead_code)]
pub fn voigt_profile<T>(x: T, sigma: T, gamma: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Copy + Debug,
{
check_finite(x, "x value")?;
check_positive(sigma, "sigma")?;
check_positive(gamma, "gamma")?;
let _sqrt2 = T::from_f64(std::f64::consts::SQRT_2).expect("Operation failed");
let _sqrt2pi = T::from_f64((2.0 * std::f64::consts::PI).sqrt()).expect("Operation failed");
let x_f64 = x
.to_f64()
.ok_or_else(|| SpecialError::DomainError("Cannot convert x to f64".to_string()))?;
let sigma_f64 = sigma
.to_f64()
.ok_or_else(|| SpecialError::DomainError("Cannot convert sigma to f64".to_string()))?;
let gamma_f64 = gamma
.to_f64()
.ok_or_else(|| SpecialError::DomainError("Cannot convert gamma to f64".to_string()))?;
if sigma_f64 < 1e-8 {
let result = gamma_f64 / (std::f64::consts::PI * (x_f64 * x_f64 + gamma_f64 * gamma_f64));
return T::from_f64(result).ok_or_else(|| {
SpecialError::DomainError("Cannot convert result back to T".to_string())
});
}
let denominator = sigma_f64 * std::f64::consts::SQRT_2;
let z = Complex64::new(x_f64 / denominator, gamma_f64 / denominator);
let w_z = faddeeva_complex(z);
if !w_z.re.is_finite() {
return Err(SpecialError::ConvergenceError(
"Faddeeva function returned non-finite value".to_string(),
));
}
let normalization = sigma_f64 * (2.0 * std::f64::consts::PI).sqrt();
let result = w_z.re / normalization;
T::from_f64(result)
.ok_or_else(|| SpecialError::DomainError("Cannot convert result back to T".to_string()))
}
#[allow(dead_code)]
pub fn voigt_profile_normalized<T>(x: T, sigma: T, gamma: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Copy + Debug,
{
voigt_profile(x, sigma, gamma)
}
#[allow(dead_code)]
pub fn voigt_profile_fwhm<T>(x: T, fwhm_gaussian: T, fwhmlorentzian: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Copy + Debug,
{
check_positive(fwhm_gaussian, "fwhm_gaussian")?;
check_positive(fwhmlorentzian, "fwhmlorentzian")?;
let two = T::from_f64(2.0).expect("Operation failed");
let ln2 = T::from_f64(std::f64::consts::LN_2).expect("Operation failed");
let sqrt2ln2 = (two * ln2).sqrt();
let sigma = fwhm_gaussian / (two * sqrt2ln2);
let gamma = fwhmlorentzian / two;
voigt_profile(x, sigma, gamma)
}
#[allow(dead_code)]
pub fn voigt_profile_array<T>(x: &ArrayView1<T>, sigma: T, gamma: T) -> SpecialResult<Array1<T>>
where
T: Float + FromPrimitive + Display + Copy + Debug,
{
let mut result = Array1::zeros(x.len());
for (i, &val) in x.iter().enumerate() {
result[i] = voigt_profile(val, sigma, gamma)?;
}
Ok(result)
}
#[allow(dead_code)]
pub fn voigt_profile_fwhm_array<T>(
x: &ArrayView1<T>,
fwhm_gaussian: T,
fwhm_lorentzian: T,
) -> SpecialResult<Array1<T>>
where
T: Float + FromPrimitive + Display + Copy + Debug,
{
let mut result = Array1::zeros(x.len());
for (i, &val) in x.iter().enumerate() {
result[i] = voigt_profile_fwhm(val, fwhm_gaussian, fwhm_lorentzian)?;
}
Ok(result)
}
#[allow(dead_code)]
pub fn pseudo_voigt<T>(x: T, sigma: T, gamma: T, eta: T) -> SpecialResult<T>
where
T: Float + FromPrimitive + Display + Copy + Debug,
{
check_finite(x, "x value")?;
check_positive(sigma, "sigma")?;
check_positive(gamma, "gamma")?;
check_finite(eta, "eta value")?;
let zero = T::zero();
let one = T::one();
if eta < zero || eta > one {
return Err(SpecialError::DomainError(
"eta must be between 0 and 1".to_string(),
));
}
let pi = T::from_f64(std::f64::consts::PI).expect("Operation failed");
let two = T::from_f64(2.0).expect("Operation failed");
let gaussian_norm = one / (sigma * (two * pi).sqrt());
let gaussian_exp = (-(x * x) / (two * sigma * sigma)).exp();
let gaussian = gaussian_norm * gaussian_exp;
let lorentzian = (gamma / pi) / (x * x + gamma * gamma);
Ok(eta * lorentzian + (one - eta) * gaussian)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use scirs2_core::ndarray::array;
use std::f64::consts::PI;
#[test]
fn test_voigt_profile_basic() {
let sigma = 1.0;
let gamma = 0.5;
let center = voigt_profile(0.0, sigma, gamma).expect("Operation failed");
assert!(center > 0.0);
let x = 1.5;
let v_pos = voigt_profile(x, sigma, gamma).expect("Operation failed");
let v_neg = voigt_profile(-x, sigma, gamma).expect("Operation failed");
assert_relative_eq!(v_pos, v_neg, epsilon = 1e-12);
}
#[test]
fn test_voigt_limiting_cases() {
let x = 0.0;
let sigma = 1.0;
let small_gamma = 1e-10;
let _large_gamma = 1e10;
let gaussian_limit = voigt_profile(x, sigma, small_gamma).expect("Operation failed");
let expected_gaussian = 1.0 / (sigma * (2.0 * PI).sqrt());
assert_relative_eq!(gaussian_limit, expected_gaussian, epsilon = 1e-6);
let small_sigma = 1e-10;
let gamma = 1.0;
let lorentzian_limit = voigt_profile(x, small_sigma, gamma).expect("Operation failed");
let expected_lorentzian = gamma / (PI * (x * x + gamma * gamma));
assert_relative_eq!(lorentzian_limit, expected_lorentzian, epsilon = 1e-6);
}
#[test]
fn test_voigt_profile_fwhm() {
let fwhm_g = 2.0;
let fwhm_l = 1.0;
let result = voigt_profile_fwhm(0.0, fwhm_g, fwhm_l).expect("Operation failed");
assert!(result > 0.0);
let x = 1.0;
let v_pos = voigt_profile_fwhm(x, fwhm_g, fwhm_l).expect("Operation failed");
let v_neg = voigt_profile_fwhm(-x, fwhm_g, fwhm_l).expect("Operation failed");
assert_relative_eq!(v_pos, v_neg, epsilon = 1e-12);
}
#[test]
fn test_pseudo_voigt() {
let sigma = 1.0;
let gamma = 0.5;
let x = 0.0;
let pure_gaussian = pseudo_voigt(x, sigma, gamma, 0.0).expect("Operation failed");
let expected_gaussian = 1.0 / (sigma * (2.0 * PI).sqrt());
assert_relative_eq!(pure_gaussian, expected_gaussian, epsilon = 1e-12);
let pure_lorentzian = pseudo_voigt(x, sigma, gamma, 1.0).expect("Operation failed");
let expected_lorentzian = gamma / (PI * (x * x + gamma * gamma));
assert_relative_eq!(pure_lorentzian, expected_lorentzian, epsilon = 1e-12);
let mixed = pseudo_voigt(x, sigma, gamma, 0.5).expect("Operation failed");
assert!(mixed > 0.0);
assert!(mixed < pure_gaussian.max(pure_lorentzian));
}
#[test]
fn test_array_operations() {
let x = array![-2.0, -1.0, 0.0, 1.0, 2.0];
let sigma = 1.0;
let gamma = 0.3;
let result = voigt_profile_array(&x.view(), sigma, gamma).expect("Operation failed");
assert_eq!(result.len(), 5);
assert_relative_eq!(result[0], result[4], epsilon = 1e-12);
assert_relative_eq!(result[1], result[3], epsilon = 1e-12);
assert!(result[2] >= result[0]);
assert!(result[2] >= result[1]);
assert!(result[2] >= result[3]);
assert!(result[2] >= result[4]);
}
#[test]
fn test_error_conditions() {
assert!(voigt_profile(0.0, -1.0, 1.0).is_err());
assert!(voigt_profile(0.0, 1.0, -1.0).is_err());
assert!(pseudo_voigt(0.0, 1.0, 1.0, -0.1).is_err());
assert!(pseudo_voigt(0.0, 1.0, 1.0, 1.1).is_err());
}
#[test]
fn test_normalization_approximation() {
let sigma = 1.0;
let gamma = 0.5;
let x_vals: Vec<f64> = (-100..=100).map(|i| i as f64 * 0.1).collect();
let dx = 0.1;
let mut integral = 0.0;
for &x in &x_vals {
integral += voigt_profile(x, sigma, gamma).expect("Operation failed") * dx;
}
println!("Voigt integral: {}", integral);
assert!(
(integral - 1.0).abs() < 0.1,
"Integral {} should be close to 1.0",
integral
);
}
}