Skip to main content

laddu_core/reaction/
particle.rs

1use std::fmt::Display;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{data::EventLike, vectors::Vec4, LadduError, LadduResult};
6
7/// A kinematic particle or composite system used to define a reaction.
8#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
9pub struct Particle {
10    label: String,
11    source: ParticleSource,
12}
13
14/// Source of a particle four-momentum.
15#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
16pub enum ParticleSource {
17    /// The four-momentum is read from a dataset p4 column with the same identifier.
18    Stored {
19        /// Dataset p4 column name.
20        p4_name: String,
21    },
22    /// The four-momentum is fixed for every event.
23    Fixed {
24        /// Fixed four-momentum.
25        p4: Vec4,
26    },
27    /// The four-momentum is solved from reaction-level four-momentum conservation.
28    Missing,
29    /// The four-momentum is the sum of two ordered daughter particles.
30    Composite {
31        /// Daughter particles whose momenta are summed.
32        daughters: Box<[Particle; 2]>,
33    },
34}
35
36impl Particle {
37    /// Construct a stored particle backed by a dataset p4 column with the same identifier.
38    pub fn stored(id: impl Into<String>) -> Self {
39        let id = id.into();
40        Self {
41            label: id.clone(),
42            source: ParticleSource::Stored { p4_name: id },
43        }
44    }
45
46    /// Construct a particle with a fixed four-momentum.
47    pub fn fixed(label: impl Into<String>, p4: Vec4) -> Self {
48        Self {
49            label: label.into(),
50            source: ParticleSource::Fixed { p4 },
51        }
52    }
53
54    /// Construct a missing particle solved by the enclosing reaction topology.
55    pub fn missing(label: impl Into<String>) -> Self {
56        Self {
57            label: label.into(),
58            source: ParticleSource::Missing,
59        }
60    }
61
62    /// Construct a composite particle from exactly two ordered daughter particles.
63    pub fn composite(
64        label: impl Into<String>,
65        daughters: (&Particle, &Particle),
66    ) -> LadduResult<Self> {
67        let daughters = [daughters.0.clone(), daughters.1.clone()];
68        if daughters.iter().any(Self::contains_missing) {
69            return Err(LadduError::Custom(
70                "missing particles cannot be used as composite daughters".to_string(),
71            ));
72        }
73        Ok(Self {
74            label: label.into(),
75            source: ParticleSource::Composite {
76                daughters: Box::new(daughters),
77            },
78        })
79    }
80
81    /// Return the particle label.
82    pub fn label(&self) -> &str {
83        &self.label
84    }
85
86    /// Return the particle four-momentum source.
87    pub const fn source(&self) -> &ParticleSource {
88        &self.source
89    }
90
91    /// Return whether this particle is missing.
92    pub const fn is_missing(&self) -> bool {
93        matches!(self.source, ParticleSource::Missing)
94    }
95
96    pub(super) fn contains_missing(&self) -> bool {
97        self.is_missing() || self.daughters().iter().any(Self::contains_missing)
98    }
99
100    /// Return the daughters if this particle is composite.
101    pub fn daughters(&self) -> &[Particle] {
102        match &self.source {
103            ParticleSource::Composite { daughters } => daughters.as_slice(),
104            _ => &[],
105        }
106    }
107
108    pub(super) fn contains_id(&self, particle: &str) -> bool {
109        if self.label() == particle {
110            return true;
111        }
112        self.daughters()
113            .iter()
114            .any(|daughter| daughter.contains_id(particle))
115    }
116
117    pub(super) fn parent_of_id(&self, child: &str) -> Option<&Particle> {
118        if self
119            .daughters()
120            .iter()
121            .any(|daughter| daughter.label() == child)
122        {
123            return Some(self);
124        }
125        self.daughters()
126            .iter()
127            .find_map(|daughter| daughter.parent_of_id(child))
128    }
129}
130
131impl Display for Particle {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        write!(f, "{}", self.label)
134    }
135}
136
137pub(super) fn resolve_particle_direct(
138    event: &dyn EventLike,
139    particle: &Particle,
140) -> LadduResult<Option<Vec4>> {
141    match particle.source() {
142        ParticleSource::Stored { p4_name } => event
143            .p4(p4_name)
144            .ok_or_else(|| LadduError::Custom(format!("unknown p4 column '{p4_name}'")))
145            .map(Some),
146        ParticleSource::Fixed { p4 } => Ok(Some(*p4)),
147        ParticleSource::Missing => Ok(None),
148        ParticleSource::Composite { daughters } => daughters
149            .iter()
150            .map(|daughter| {
151                resolve_particle_direct(event, daughter)?.ok_or_else(|| {
152                    LadduError::Custom(format!(
153                        "missing daughter '{}' cannot be resolved inside composite '{}'",
154                        daughter.label(),
155                        particle.label()
156                    ))
157                })
158            })
159            .try_fold(Vec4::new(0.0, 0.0, 0.0, 0.0), |acc, value| {
160                value.map(|value| acc + value)
161            })
162            .map(Some),
163    }
164}