use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{LadduPhysicsError, LadduPhysicsResult};
use super::{J, L, Parity, ParticleProperties, RuleReport, RuleSet, S};
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct PartialWave {
pub j: J,
pub l: L,
pub s: S,
}
impl PartialWave {
pub fn new(j: J, l: L, s: S) -> LadduPhysicsResult<Self> {
PartialWave::validate_coupling(j, l, s)?;
Ok(Self { j, l, s })
}
pub fn label(&self) -> String {
let multiplicity = self.s.doubled() + 1;
format!("{}{}{}", multiplicity, self.l, self.j)
}
pub fn validate_coupling(j: J, l: L, s: S) -> LadduPhysicsResult<()> {
let l_twice = 2 * l.value();
let s_twice = s.doubled();
let j_twice = j.doubled();
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(LadduPhysicsError::invalid_relation(
"j, l, and s must be compatible",
))
}
}
}
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: infer_parity(daughters, wave.l),
c_parity: infer_c_parity(daughters, wave.l, wave.s),
wave,
}
}
}
pub(super) fn infer_parity(
daughters: (&ParticleProperties, &ParticleProperties),
l: L,
) -> Option<Parity> {
Some(daughters.0.parity? * daughters.1.parity? * l.orbital_parity())
}
pub(super) fn infer_c_parity(
daughters: (&ParticleProperties, &ParticleProperties),
l: L,
s: S,
) -> Option<Parity> {
daughters.0.is_antiparticle_of(daughters.1).then_some(())?;
let s_doubled = s.doubled();
if !s_doubled.is_multiple_of(2) {
return None;
}
Some(L::int(l.value() + (s_doubled / 2)).orbital_parity())
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PartialWaveCandidate {
pub wave: PartialWave,
pub inferred: AllowedPartialWave,
pub report: RuleReport,
}
impl PartialWaveCandidate {
pub fn is_allowed(&self) -> bool {
self.report.is_allowed()
}
}
#[derive(Clone, Debug, Eq, PartialEq, Default)]
pub struct PartialWaveScan {
pub candidates: Vec<PartialWaveCandidate>,
pub missing_inputs: Vec<String>,
}
impl PartialWaveScan {
pub fn allowed(&self) -> impl Iterator<Item = &AllowedPartialWave> {
self.candidates
.iter()
.filter(|candidate| candidate.is_allowed())
.map(|candidate| &candidate.inferred)
}
pub fn rejected(&self) -> impl Iterator<Item = &PartialWaveCandidate> {
self.candidates
.iter()
.filter(|candidate| !candidate.is_allowed())
}
pub fn into_allowed(self) -> Vec<AllowedPartialWave> {
self.candidates
.into_iter()
.filter_map(|candidate| {
if candidate.is_allowed() {
Some(candidate.inferred)
} else {
None
}
})
.collect()
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct SelectionRules {
pub rules: RuleSet,
pub max_l: L,
}
impl Default for SelectionRules {
fn default() -> Self {
Self::strong(L::int(6))
}
}
impl SelectionRules {
pub fn new(rules: RuleSet, max_l: L) -> Self {
Self { rules, max_l }
}
pub fn angular(max_l: L) -> Self {
Self::new(RuleSet::angular(), max_l)
}
pub fn electromagnetic(max_l: L) -> Self {
Self::new(RuleSet::electromagnetic(), max_l)
}
pub fn weak(max_l: L) -> Self {
Self::new(RuleSet::weak(), max_l)
}
pub fn strong(max_l: L) -> Self {
Self::new(RuleSet::strong(), max_l)
}
pub fn coupled_spins(a: J, b: J) -> Vec<S> {
a.coupled_with(b)
}
pub fn scan_partial_waves(
&self,
parent: &ParticleProperties,
daughters: (&ParticleProperties, &ParticleProperties),
) -> PartialWaveScan {
let mut missing_inputs = Vec::new();
let Some(parent_j) = parent.spin else {
missing_inputs.push("parent.spin".to_string());
return PartialWaveScan {
candidates: Vec::new(),
missing_inputs,
};
};
let Some(ja) = daughters.0.spin else {
missing_inputs.push("daughter_a.spin".to_string());
return PartialWaveScan {
candidates: Vec::new(),
missing_inputs,
};
};
let Some(jb) = daughters.1.spin else {
missing_inputs.push("daughter_b.spin".to_string());
return PartialWaveScan {
candidates: Vec::new(),
missing_inputs,
};
};
let mut candidates = Vec::new();
for s in Self::coupled_spins(ja, jb) {
for l_raw in 0..=self.max_l.value() {
let l = L::int(l_raw);
let Ok(wave) = PartialWave::new(parent_j, l, s) else {
continue;
};
let report = self.rules.evaluate(parent, daughters, l, s);
let inferred = AllowedPartialWave::new(wave, daughters);
candidates.push(PartialWaveCandidate {
wave,
inferred,
report,
});
}
}
PartialWaveScan {
candidates,
missing_inputs,
}
}
pub fn allowed_partial_waves(
&self,
parent: &ParticleProperties,
daughters: (&ParticleProperties, &ParticleProperties),
) -> Vec<AllowedPartialWave> {
self.scan_partial_waves(parent, daughters).into_allowed()
}
}