use hisab::Complex;
use tracing::{error, instrument, warn};
use crate::error::{MimamsaError, ensure_finite_complex, require_finite};
use super::FourMomentum;
pub const DEFAULT_EPSILON: f64 = 1e-10;
#[instrument(level = "trace", skip(p))]
#[inline]
pub fn scalar_propagator(
p: &FourMomentum,
mass_gev: f64,
epsilon: f64,
) -> Result<Complex, MimamsaError> {
require_finite(mass_gev, "scalar_propagator")?;
require_finite(epsilon, "scalar_propagator")?;
if epsilon <= 0.0 {
warn!(epsilon, "scalar_propagator: epsilon must be positive");
return Err(MimamsaError::Computation(
"scalar_propagator: epsilon must be positive".to_string(),
));
}
let p2 = p.invariant_mass_sq()?;
let denom = Complex::new(p2 - mass_gev * mass_gev, epsilon);
let i = Complex::new(0.0, 1.0);
ensure_finite_complex(i / denom, "scalar_propagator")
}
#[instrument(level = "trace", skip(p))]
#[inline]
pub fn fermion_propagator_scalar(
p: &FourMomentum,
mass_gev: f64,
epsilon: f64,
) -> Result<Complex, MimamsaError> {
scalar_propagator(p, mass_gev, epsilon)
}
#[instrument(level = "trace", skip(k))]
#[inline]
pub fn gauge_boson_propagator(k: &FourMomentum, epsilon: f64) -> Result<Complex, MimamsaError> {
require_finite(epsilon, "gauge_boson_propagator")?;
if epsilon <= 0.0 {
warn!(epsilon, "gauge_boson_propagator: epsilon must be positive");
return Err(MimamsaError::Computation(
"gauge_boson_propagator: epsilon must be positive".to_string(),
));
}
let k2 = k.invariant_mass_sq()?;
let denom = Complex::new(k2, epsilon);
let neg_i = Complex::new(0.0, -1.0);
ensure_finite_complex(neg_i / denom, "gauge_boson_propagator")
}
#[instrument(level = "debug")]
pub fn scalar_propagator_position_space(
mass_gev: f64,
time_separation: f64,
n_points: usize,
e_max: f64,
) -> Result<Complex, MimamsaError> {
require_finite(mass_gev, "scalar_propagator_position_space")?;
require_finite(time_separation, "scalar_propagator_position_space")?;
require_finite(e_max, "scalar_propagator_position_space")?;
if n_points == 0 || !n_points.is_power_of_two() {
warn!(
n_points,
"scalar_propagator_position_space: n_points must be a positive power of 2"
);
return Err(MimamsaError::Computation(
"scalar_propagator_position_space: n_points must be a positive power of 2".to_string(),
));
}
if e_max <= 0.0 {
warn!(
e_max,
"scalar_propagator_position_space: e_max must be positive"
);
return Err(MimamsaError::Computation(
"scalar_propagator_position_space: e_max must be positive".to_string(),
));
}
let de = 2.0 * e_max / n_points as f64;
let n = n_points;
let mut data: Vec<Complex> = Vec::with_capacity(n);
for k in 0..n {
let e = -e_max + (k as f64 + 0.5) * de;
let denom = Complex::new(e * e - mass_gev * mass_gev, DEFAULT_EPSILON);
let prop = Complex::new(0.0, 1.0) / denom;
let phase = Complex::from_polar(1.0, -e * time_separation);
data.push(prop * phase * de);
}
hisab::num::fft(&mut data).map_err(|e| {
error!(%e, "FFT failed in position-space propagator");
MimamsaError::Computation(format!("scalar_propagator_position_space: FFT failed: {e}"))
})?;
let result = data[0] * (1.0 / (2.0 * std::f64::consts::PI));
ensure_finite_complex(result, "scalar_propagator_position_space")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::M_ELECTRON_GEV;
#[test]
fn test_scalar_propagator_on_shell_large() {
let m = M_ELECTRON_GEV;
let p = FourMomentum::new(m, 0.0, 0.0, 0.0).unwrap();
let prop = scalar_propagator(&p, m, DEFAULT_EPSILON).unwrap();
assert!(prop.abs() > 1e8);
}
#[test]
fn test_scalar_propagator_off_shell_small() {
let m = M_ELECTRON_GEV;
let p = FourMomentum::new(100.0, 0.0, 0.0, 0.0).unwrap();
let prop = scalar_propagator(&p, m, DEFAULT_EPSILON).unwrap();
assert!(prop.abs() < 0.01);
}
#[test]
fn test_gauge_boson_propagator_massless() {
let k = FourMomentum::new(10.0, 5.0, 0.0, 0.0).unwrap();
let prop = gauge_boson_propagator(&k, DEFAULT_EPSILON).unwrap();
assert!((prop.abs() - 1.0 / 75.0).abs() < 1e-4);
}
#[test]
fn test_propagator_symmetry() {
let m = 1.0;
let p = FourMomentum::new(5.0, 3.0, 1.0, 2.0).unwrap();
let neg_p = FourMomentum::new(-5.0, -3.0, -1.0, -2.0).unwrap();
let d1 = scalar_propagator(&p, m, DEFAULT_EPSILON).unwrap();
let d2 = scalar_propagator(&neg_p, m, DEFAULT_EPSILON).unwrap();
assert!((d1.re - d2.re).abs() < 1e-12);
assert!((d1.im - d2.im).abs() < 1e-12);
}
#[test]
fn test_propagator_nan_rejected() {
let p = FourMomentum::new(1.0, 0.0, 0.0, 0.0).unwrap();
assert!(scalar_propagator(&p, f64::NAN, DEFAULT_EPSILON).is_err());
}
#[test]
fn test_epsilon_zero_rejected() {
let p = FourMomentum::new(1.0, 0.0, 0.0, 0.0).unwrap();
assert!(scalar_propagator(&p, 0.5, 0.0).is_err());
assert!(scalar_propagator(&p, 0.5, -1e-10).is_err());
}
#[test]
fn test_gauge_epsilon_zero_rejected() {
let k = FourMomentum::new(10.0, 5.0, 0.0, 0.0).unwrap();
assert!(gauge_boson_propagator(&k, 0.0).is_err());
assert!(gauge_boson_propagator(&k, -1.0).is_err());
}
#[test]
fn test_position_space_propagator_runs() {
let result = scalar_propagator_position_space(1.0, 0.1, 256, 50.0);
assert!(result.is_ok());
let val = result.unwrap();
assert!(val.re.is_finite());
assert!(val.im.is_finite());
}
#[test]
fn test_position_space_bad_n_rejected() {
assert!(scalar_propagator_position_space(1.0, 0.1, 100, 50.0).is_err()); assert!(scalar_propagator_position_space(1.0, 0.1, 0, 50.0).is_err());
}
#[test]
fn test_position_space_bad_emax_rejected() {
assert!(scalar_propagator_position_space(1.0, 0.1, 256, 0.0).is_err());
assert!(scalar_propagator_position_space(1.0, 0.1, 256, -1.0).is_err());
}
#[test]
fn test_fermion_propagator_matches_scalar() {
let m = M_ELECTRON_GEV;
let p = FourMomentum::new(m, 0.0, 0.0, 0.0).unwrap();
let scalar = scalar_propagator(&p, m, DEFAULT_EPSILON).unwrap();
let fermion = fermion_propagator_scalar(&p, m, DEFAULT_EPSILON).unwrap();
assert!((scalar.re - fermion.re).abs() < 1e-12);
assert!((scalar.im - fermion.im).abs() < 1e-12);
}
}