use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{
quantum::types::Statistics, AngularMomentum, Charge, LadduError, LadduResult,
OrbitalAngularMomentum, Parity, Projection,
};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct SpinState {
spin: AngularMomentum,
projection: Projection,
}
impl SpinState {
pub fn new(spin: AngularMomentum, projection: Projection) -> LadduResult<Self> {
validate_projection(spin, projection)?;
Ok(Self { spin, projection })
}
pub const fn spin(self) -> AngularMomentum {
self.spin
}
pub const fn projection(self) -> Projection {
self.projection
}
pub fn allowed_projections(spin: AngularMomentum) -> Vec<Self> {
let spin_value = spin.value() as i32;
(-spin_value..=spin_value)
.step_by(2)
.map(|projection| Self {
spin,
projection: Projection::half_integer(projection),
})
.collect()
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct Isospin {
isospin: AngularMomentum,
projection: Option<Projection>,
}
impl Isospin {
pub fn new(isospin: AngularMomentum, projection: Option<Projection>) -> LadduResult<Self> {
if let Some(projection) = projection {
validate_projection(isospin, projection)?;
}
Ok(Self {
isospin,
projection,
})
}
pub fn isospin(self) -> AngularMomentum {
self.isospin
}
pub fn projection(self) -> Option<Projection> {
self.projection
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ParticleProperties {
pub name: Option<String>,
pub species: Option<String>,
pub antiparticle_species: Option<String>,
pub self_conjugate: Option<bool>,
pub spin: Option<AngularMomentum>,
pub parity: Option<Parity>,
pub c_parity: Option<Parity>,
pub g_parity: Option<Parity>,
pub charge: Option<Charge>,
pub isospin: Option<Isospin>,
pub strangeness: Option<i32>,
pub charm: Option<i32>,
pub bottomness: Option<i32>,
pub topness: Option<i32>,
pub baryon_number: Option<i32>,
pub electron_lepton_number: Option<i32>,
pub muon_lepton_number: Option<i32>,
pub tau_lepton_number: Option<i32>,
pub statistics: Option<Statistics>,
}
impl ParticleProperties {
pub fn unknown() -> Self {
Self::default()
}
pub fn jp(j: AngularMomentum, p: Parity) -> Self {
Self {
spin: Some(j),
parity: Some(p),
statistics: Some(Statistics::from_spin(j)),
..Self::default()
}
}
pub fn jpc(j: AngularMomentum, p: Parity, c: Parity) -> Self {
Self {
spin: Some(j),
parity: Some(p),
c_parity: Some(c),
statistics: Some(Statistics::from_spin(j)),
..Self::default()
}
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn with_species(mut self, species: impl Into<String>) -> Self {
self.species = Some(species.into());
self
}
pub fn with_antiparticle_species(mut self, antiparticle_species: impl Into<String>) -> Self {
self.antiparticle_species = Some(antiparticle_species.into());
self
}
pub fn with_self_conjugate(mut self, value: bool) -> Self {
self.self_conjugate = Some(value);
self
}
pub fn with_spin(mut self, j: AngularMomentum) -> Self {
self.spin = Some(j);
self.statistics = Some(Statistics::from_spin(j));
self
}
pub fn with_parity(mut self, p: Parity) -> Self {
self.parity = Some(p);
self
}
pub fn with_c_parity(mut self, c: Parity) -> Self {
self.c_parity = Some(c);
self
}
pub fn with_g_parity(mut self, g: Parity) -> Self {
self.g_parity = Some(g);
self
}
pub fn with_charge(mut self, q: Charge) -> Self {
self.charge = Some(q);
self
}
pub fn with_isospin(mut self, isospin: Isospin) -> Self {
self.isospin = Some(isospin);
self
}
pub fn with_strangeness(mut self, s: i32) -> Self {
self.strangeness = Some(s);
self
}
pub fn with_charm(mut self, c: i32) -> Self {
self.charm = Some(c);
self
}
pub fn with_bottomness(mut self, b: i32) -> Self {
self.bottomness = Some(b);
self
}
pub fn with_topness(mut self, t: i32) -> Self {
self.topness = Some(t);
self
}
pub fn with_baryon_number(mut self, b: i32) -> Self {
self.baryon_number = Some(b);
self
}
pub fn with_electron_lepton_number(mut self, e: i32) -> Self {
self.electron_lepton_number = Some(e);
self
}
pub fn with_muon_lepton_number(mut self, m: i32) -> Self {
self.muon_lepton_number = Some(m);
self
}
pub fn with_tau_lepton_number(mut self, t: i32) -> Self {
self.tau_lepton_number = Some(t);
self
}
pub fn with_statistics(mut self, s: Statistics) -> LadduResult<Self> {
if let Some(spin) = self.spin {
if Statistics::from_spin(spin) != s {
return Err(LadduError::Custom(
"spin and statistics must be consistent".to_string(),
));
}
}
self.statistics = Some(s);
Ok(self)
}
pub fn is_antiparticle_of(&self, other: &ParticleProperties) -> bool {
let a_species = self.species.as_ref();
let b_species = other.species.as_ref();
let a_anti = self.antiparticle_species.as_ref();
let b_anti = other.antiparticle_species.as_ref();
match (a_species, b_species, a_anti, b_anti) {
(Some(a), Some(b), Some(a_bar), Some(b_bar)) => a_bar == b && b_bar == a,
(Some(_), Some(b), Some(a_bar), None) => a_bar == b,
(Some(a), Some(_), None, Some(b_bar)) => b_bar == a,
_ => false,
}
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct PartialWave {
pub j: AngularMomentum,
pub l: OrbitalAngularMomentum,
pub s: AngularMomentum,
pub label: String,
}
impl PartialWave {
pub fn new(
j: AngularMomentum,
l: OrbitalAngularMomentum,
s: AngularMomentum,
) -> LadduResult<Self> {
PartialWave::validate_coupling(j, l, s)?;
let multiplicity = s.value() + 1;
Ok(Self {
j,
l,
s,
label: format!("{}{}{}", multiplicity, l, j),
})
}
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = label.into();
self
}
pub fn validate_coupling(
j: AngularMomentum,
l: OrbitalAngularMomentum,
s: AngularMomentum,
) -> LadduResult<()> {
let l_twice = 2 * l.value();
let s_twice = s.value();
let j_twice = j.value();
let min = l_twice.abs_diff(s_twice);
let max = l_twice + s_twice;
if j_twice >= min && j_twice <= max && (j_twice - min).is_multiple_of(2) {
Ok(())
} else {
Err(LadduError::Custom(
"j, l, and s must be compatible".to_string(),
))
}
}
}
impl Display for PartialWave {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.label)
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct AllowedPartialWave {
pub wave: PartialWave,
pub parity: Option<Parity>,
pub c_parity: Option<Parity>,
}
impl AllowedPartialWave {
pub fn new(wave: PartialWave, daughters: (&ParticleProperties, &ParticleProperties)) -> Self {
Self {
parity: Self::infer_parity(daughters, wave.l),
c_parity: Self::infer_c_parity(daughters, wave.l, wave.s),
wave,
}
}
pub fn infer_parity(
daughters: (&ParticleProperties, &ParticleProperties),
l: OrbitalAngularMomentum,
) -> Option<Parity> {
let p_a = daughters.0.parity?;
let p_b = daughters.1.parity?;
let value = p_a.value() * p_b.value() * if l.value() & 1 == 0 { 1 } else { -1 };
Some(if value == 1 {
Parity::Positive
} else {
Parity::Negative
})
}
pub fn infer_c_parity(
daughters: (&ParticleProperties, &ParticleProperties),
l: OrbitalAngularMomentum,
s: AngularMomentum,
) -> Option<Parity> {
if !daughters.0.is_antiparticle_of(daughters.1) {
return None;
}
let exp_twice = 2 * l.value() + s.value();
if !exp_twice.is_multiple_of(2) {
return None;
}
Some(if (exp_twice / 2).is_multiple_of(2) {
Parity::Positive
} else {
Parity::Negative
})
}
}
fn validate_projection(spin: AngularMomentum, projection: Projection) -> LadduResult<()> {
if projection.value().unsigned_abs() > spin.value() {
return Err(LadduError::Custom(
"spin projection must satisfy -J <= m <= J".to_string(),
));
}
if !spin.has_same_parity_as(projection) {
return Err(LadduError::Custom(
"spin projection must have the same integer or half-integer parity as spin".to_string(),
));
}
Ok(())
}