use std::f64::consts::PI;
use hisab::Complex;
use serde::{Deserialize, Serialize};
use tracing::{instrument, warn};
use crate::error::{MimamsaError, ensure_finite, ensure_finite_complex, require_finite};
use super::FourMomentum;
use super::propagator;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum ParticleType {
Scalar,
Fermion,
Photon,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Vertex {
pub coupling: f64,
pub particles: Vec<ParticleType>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InternalLine {
pub particle_type: ParticleType,
pub momentum: FourMomentum,
pub mass_gev: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExternalLine {
pub particle_type: ParticleType,
pub momentum: FourMomentum,
pub incoming: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TreeDiagram {
pub vertices: Vec<Vertex>,
pub internal_lines: Vec<InternalLine>,
pub external_lines: Vec<ExternalLine>,
}
impl TreeDiagram {
pub fn check_momentum_conservation(&self) -> Result<bool, MimamsaError> {
let mut sum_e = 0.0;
let mut sum_px = 0.0;
let mut sum_py = 0.0;
let mut sum_pz = 0.0;
for line in &self.external_lines {
let sign = if line.incoming { 1.0 } else { -1.0 };
sum_e += sign * line.momentum.e;
sum_px += sign * line.momentum.px;
sum_py += sign * line.momentum.py;
sum_pz += sign * line.momentum.pz;
}
let tol = 1e-8;
Ok(sum_e.abs() < tol && sum_px.abs() < tol && sum_py.abs() < tol && sum_pz.abs() < tol)
}
}
#[instrument(level = "trace", skip(diagram))]
pub fn tree_level_amplitude(diagram: &TreeDiagram) -> Result<Complex, MimamsaError> {
let mut amplitude = Complex::new(1.0, 0.0);
for vertex in &diagram.vertices {
require_finite(vertex.coupling, "tree_level_amplitude")?;
amplitude *= vertex.coupling;
}
for line in &diagram.internal_lines {
let prop = match line.particle_type {
ParticleType::Scalar | ParticleType::Fermion => propagator::scalar_propagator(
&line.momentum,
line.mass_gev,
propagator::DEFAULT_EPSILON,
)?,
ParticleType::Photon => {
propagator::gauge_boson_propagator(&line.momentum, propagator::DEFAULT_EPSILON)?
}
};
amplitude *= prop;
}
ensure_finite_complex(amplitude, "tree_level_amplitude")
}
#[inline]
pub fn mandelstam_s(p1: &FourMomentum, p2: &FourMomentum) -> Result<f64, MimamsaError> {
let total = p1.add(p2)?;
total.invariant_mass_sq()
}
#[inline]
pub fn mandelstam_t(p1: &FourMomentum, p3: &FourMomentum) -> Result<f64, MimamsaError> {
let diff = p1.sub(p3)?;
diff.invariant_mass_sq()
}
#[inline]
pub fn mandelstam_u(p1: &FourMomentum, p4: &FourMomentum) -> Result<f64, MimamsaError> {
let diff = p1.sub(p4)?;
diff.invariant_mass_sq()
}
pub fn verify_mandelstam_identity(
s: f64,
t: f64,
u: f64,
masses_gev: &[f64],
) -> Result<bool, MimamsaError> {
require_finite(s, "verify_mandelstam_identity")?;
require_finite(t, "verify_mandelstam_identity")?;
require_finite(u, "verify_mandelstam_identity")?;
let sum_m2: f64 = masses_gev.iter().map(|m| m * m).sum();
let lhs = s + t + u;
Ok((lhs - sum_m2).abs() < 1e-6 * (sum_m2.abs() + 1.0))
}
#[instrument(level = "trace")]
#[inline]
pub fn differential_cross_section_2to2(
amplitude_sq: f64,
s_mandelstam: f64,
) -> Result<f64, MimamsaError> {
require_finite(amplitude_sq, "differential_cross_section_2to2")?;
require_finite(s_mandelstam, "differential_cross_section_2to2")?;
if amplitude_sq < 0.0 {
warn!(amplitude_sq, "|M|² must be non-negative");
return Err(MimamsaError::Computation(
"differential_cross_section_2to2: |M|² must be non-negative".to_string(),
));
}
if s_mandelstam <= 0.0 {
warn!(
s_mandelstam,
"differential_cross_section_2to2: s must be positive"
);
return Err(MimamsaError::Computation(
"differential_cross_section_2to2: s must be positive".to_string(),
));
}
ensure_finite(
amplitude_sq / (64.0 * PI * PI * s_mandelstam),
"differential_cross_section_2to2",
)
}
#[instrument(level = "trace")]
#[inline]
pub fn total_cross_section_2to2_massless(
amplitude_sq: f64,
s_mandelstam: f64,
) -> Result<f64, MimamsaError> {
require_finite(amplitude_sq, "total_cross_section_2to2_massless")?;
require_finite(s_mandelstam, "total_cross_section_2to2_massless")?;
if amplitude_sq < 0.0 {
warn!(amplitude_sq, "|M|² must be non-negative");
return Err(MimamsaError::Computation(
"total_cross_section_2to2_massless: |M|² must be non-negative".to_string(),
));
}
if s_mandelstam <= 0.0 {
warn!(
s_mandelstam,
"total_cross_section_2to2_massless: s must be positive"
);
return Err(MimamsaError::Computation(
"total_cross_section_2to2_massless: s must be positive".to_string(),
));
}
ensure_finite(
amplitude_sq / (16.0 * PI * s_mandelstam),
"total_cross_section_2to2_massless",
)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_2to2_momenta() -> (FourMomentum, FourMomentum, FourMomentum, FourMomentum) {
let e = 5.0;
let p1 = FourMomentum::new(e, 0.0, 0.0, e).unwrap(); let p2 = FourMomentum::new(e, 0.0, 0.0, -e).unwrap(); let p3 = FourMomentum::new(e, e, 0.0, 0.0).unwrap(); let p4 = FourMomentum::new(e, -e, 0.0, 0.0).unwrap(); (p1, p2, p3, p4)
}
#[test]
fn test_mandelstam_s() {
let (p1, p2, _, _) = make_2to2_momenta();
let s = mandelstam_s(&p1, &p2).unwrap();
assert!((s - 100.0).abs() < 1e-8);
}
#[test]
fn test_mandelstam_identity_massless() {
let (p1, p2, p3, p4) = make_2to2_momenta();
let s = mandelstam_s(&p1, &p2).unwrap();
let t = mandelstam_t(&p1, &p3).unwrap();
let u = mandelstam_u(&p1, &p4).unwrap();
let ok = verify_mandelstam_identity(s, t, u, &[0.0, 0.0, 0.0, 0.0]).unwrap();
assert!(ok, "s+t+u = {}, expected 0", s + t + u);
}
#[test]
fn test_momentum_conservation() {
let (p1, p2, p3, p4) = make_2to2_momenta();
let diagram = TreeDiagram {
vertices: vec![],
internal_lines: vec![],
external_lines: vec![
ExternalLine {
particle_type: ParticleType::Fermion,
momentum: p1,
incoming: true,
},
ExternalLine {
particle_type: ParticleType::Fermion,
momentum: p2,
incoming: true,
},
ExternalLine {
particle_type: ParticleType::Fermion,
momentum: p3,
incoming: false,
},
ExternalLine {
particle_type: ParticleType::Fermion,
momentum: p4,
incoming: false,
},
],
};
assert!(diagram.check_momentum_conservation().unwrap());
}
#[test]
fn test_tree_level_amplitude_single_propagator() {
let k = FourMomentum::new(10.0, 5.0, 3.0, 0.0).unwrap();
let diagram = TreeDiagram {
vertices: vec![
Vertex {
coupling: 0.3,
particles: vec![
ParticleType::Fermion,
ParticleType::Fermion,
ParticleType::Photon,
],
},
Vertex {
coupling: 0.3,
particles: vec![
ParticleType::Fermion,
ParticleType::Fermion,
ParticleType::Photon,
],
},
],
internal_lines: vec![InternalLine {
particle_type: ParticleType::Photon,
momentum: k,
mass_gev: 0.0,
}],
external_lines: vec![],
};
let amp = tree_level_amplitude(&diagram).unwrap();
assert!(amp.abs() > 0.0);
}
#[test]
fn test_cross_section_positive() {
let sigma = total_cross_section_2to2_massless(1.0, 100.0).unwrap();
assert!(sigma > 0.0);
}
#[test]
fn test_cross_section_negative_s_rejected() {
assert!(total_cross_section_2to2_massless(1.0, -1.0).is_err());
assert!(total_cross_section_2to2_massless(1.0, 0.0).is_err());
}
#[test]
fn test_differential_cross_section_negative_s_rejected() {
assert!(differential_cross_section_2to2(1.0, -1.0).is_err());
assert!(differential_cross_section_2to2(1.0, 0.0).is_err());
}
#[test]
fn test_cross_section_negative_amplitude_sq_rejected() {
assert!(total_cross_section_2to2_massless(-1.0, 100.0).is_err());
assert!(differential_cross_section_2to2(-0.5, 100.0).is_err());
}
#[test]
fn test_differential_vs_total() {
let m2 = 1.0;
let s = 100.0;
let diff = differential_cross_section_2to2(m2, s).unwrap();
let total = total_cross_section_2to2_massless(m2, s).unwrap();
let ratio = total / diff;
assert!((ratio - 4.0 * PI).abs() < 1e-8);
}
}