Skip to main content

pdg_rs/models/
pdgsearch.rs

1use crate::{AngularMomentum, Charge, Isospin, Parity, ParticleClass, ParticleType};
2
3/// Builder for particle searches run by [`crate::Pdg::search_particles`].
4///
5/// Filters are combined with logical `AND`.
6///
7/// # Examples
8///
9/// ```no_run
10/// use pdg_rs::{Charge, ParticleClass, ParticleSearchQuery, Pdg};
11///
12/// # fn main() -> pdg_rs::PdgResult<()> {
13/// let query = ParticleSearchQuery::new()
14///     .class(ParticleClass::Meson)
15///     .charge(Charge::Neutral)
16///     .mass_range_mev(100.0, 1000.0);
17///
18/// let pdg = Pdg::open()?;
19/// let particles = pdg.search_particles(query)?;
20/// assert!(particles.iter().all(|particle| particle.charge == Charge::Neutral));
21/// # Ok(())
22/// # }
23/// ```
24#[derive(Clone, Debug, Default)]
25pub struct ParticleSearchQuery {
26    pub(crate) name_contains: Option<String>,
27    pub(crate) particle_class: Option<ParticleClass>,
28    pub(crate) particle_type: Option<ParticleType>,
29    pub(crate) charge: Option<Charge>,
30    pub(crate) isospin: QuantumFilter<Isospin>,
31    pub(crate) g_parity: QuantumFilter<Parity>,
32    pub(crate) angular_momentum: QuantumFilter<AngularMomentum>,
33    pub(crate) parity: QuantumFilter<Parity>,
34    pub(crate) charge_conjugation: QuantumFilter<Parity>,
35    pub(crate) mass_range_mev: Option<(f64, f64)>,
36    pub(crate) width_range_mev: Option<(f64, f64)>,
37    pub(crate) lifetime_range_seconds: Option<(f64, f64)>,
38    pub(crate) decays_to: DecayFilter,
39    pub(crate) decays_from: Vec<String>,
40    pub(crate) decay_state_expansion: DecayStateExpansion,
41}
42
43#[derive(Clone, Debug, Default)]
44pub struct DecayFilter {
45    pub(crate) states: Vec<String>,
46    pub(crate) mode: DecayMatchMode,
47}
48
49/// Matching mode for decay-product filters.
50#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
51pub enum DecayMatchMode {
52    /// Require a decay mode whose outgoing products exactly match the requested states.
53    #[default]
54    Exact,
55    /// Require a decay mode containing all requested states, allowing additional products.
56    Contains,
57}
58
59/// Controls how named decay states are expanded through the PDG item hierarchy.
60#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
61pub enum DecayStateExpansion {
62    /// Include hierarchy-related names such as aliases and grouped particle names.
63    #[default]
64    Inclusive,
65    /// Use only the literal names supplied in the query.
66    Literal,
67}
68
69/// Filter for optional quantum numbers.
70#[derive(Clone, Debug, Default)]
71pub enum QuantumFilter<T> {
72    /// Accept any value, including missing values.
73    #[default]
74    Any,
75    /// Require the quantum number to be missing.
76    Missing,
77    /// Require an exact value.
78    Value(T),
79}
80
81impl ParticleSearchQuery {
82    /// Creates an empty particle search query.
83    #[must_use]
84    pub fn new() -> Self {
85        Self::default()
86    }
87
88    /// Filters particles whose name contains `value`.
89    #[must_use]
90    pub fn name_contains(mut self, value: impl Into<String>) -> Self {
91        self.name_contains = Some(value.into());
92        self
93    }
94
95    /// Filters particles by [`ParticleClass`].
96    #[must_use]
97    pub const fn class(mut self, particle_class: ParticleClass) -> Self {
98        self.particle_class = Some(particle_class);
99        self
100    }
101
102    /// Filters particles by particle/antiparticle relation.
103    #[must_use]
104    pub const fn particle_type(mut self, particle_type: ParticleType) -> Self {
105        self.particle_type = Some(particle_type);
106        self
107    }
108
109    /// Filters particles by electric charge.
110    #[must_use]
111    pub const fn charge(mut self, charge: Charge) -> Self {
112        self.charge = Some(charge);
113        self
114    }
115
116    /// Filters particles by isospin.
117    ///
118    /// Passing `None` requires the isospin value to be missing.
119    #[must_use]
120    pub fn isospin(mut self, isospin: impl Into<Option<Isospin>>) -> Self {
121        self.isospin = quantum_filter(isospin);
122        self
123    }
124
125    /// Filters particles by G-parity.
126    ///
127    /// Passing `None` requires the G-parity value to be missing.
128    #[must_use]
129    pub fn g_parity(mut self, g_parity: impl Into<Option<Parity>>) -> Self {
130        self.g_parity = quantum_filter(g_parity);
131        self
132    }
133
134    /// Filters particles by angular momentum.
135    ///
136    /// Passing `None` requires the angular-momentum value to be missing.
137    #[must_use]
138    pub fn angular_momentum(
139        mut self,
140        angular_momentum: impl Into<Option<AngularMomentum>>,
141    ) -> Self {
142        self.angular_momentum = quantum_filter(angular_momentum);
143        self
144    }
145
146    /// Filters particles by parity.
147    ///
148    /// Passing `None` requires the parity value to be missing.
149    #[must_use]
150    pub fn parity(mut self, parity: impl Into<Option<Parity>>) -> Self {
151        self.parity = quantum_filter(parity);
152        self
153    }
154
155    /// Filters particles by charge-conjugation parity.
156    ///
157    /// Passing `None` requires the charge-conjugation value to be missing.
158    #[must_use]
159    pub fn charge_conjugation(mut self, charge_conjugation: impl Into<Option<Parity>>) -> Self {
160        self.charge_conjugation = quantum_filter(charge_conjugation);
161        self
162    }
163
164    /// Filters particles whose mass interval overlaps the given range in `MeV`.
165    #[must_use]
166    pub const fn mass_range_mev(mut self, min: f64, max: f64) -> Self {
167        self.mass_range_mev = Some((min, max));
168        self
169    }
170
171    /// Filters particles whose width interval overlaps the given range in `MeV`.
172    #[must_use]
173    pub const fn width_range_mev(mut self, min: f64, max: f64) -> Self {
174        self.width_range_mev = Some((min, max));
175        self
176    }
177
178    /// Filters particles whose lifetime interval overlaps the given range in seconds.
179    #[must_use]
180    pub const fn lifetime_range_seconds(mut self, min: f64, max: f64) -> Self {
181        self.lifetime_range_seconds = Some((min, max));
182        self
183    }
184
185    /// Filters particles by an exact outgoing decay state.
186    ///
187    /// Use [`ParticleSearchQuery::decay_contains`] to allow additional outgoing
188    /// products.
189    #[must_use]
190    pub fn decays_to<I, S>(mut self, states: I) -> Self
191    where
192        I: IntoIterator<Item = S>,
193        S: Into<String>,
194    {
195        self.decays_to = DecayFilter {
196            states: states.into_iter().map(Into::into).collect(),
197            mode: DecayMatchMode::Exact,
198        };
199        self
200    }
201
202    /// Filters particles by outgoing decay products contained in a decay state.
203    #[must_use]
204    pub fn decay_contains<I, S>(mut self, states: I) -> Self
205    where
206        I: IntoIterator<Item = S>,
207        S: Into<String>,
208    {
209        self.decays_to = DecayFilter {
210            states: states.into_iter().map(Into::into).collect(),
211            mode: DecayMatchMode::Contains,
212        };
213        self
214    }
215
216    /// Filters particles by incoming decay products.
217    #[must_use]
218    pub fn decays_from<I, S>(mut self, states: I) -> Self
219    where
220        I: IntoIterator<Item = S>,
221        S: Into<String>,
222    {
223        self.decays_from = states.into_iter().map(Into::into).collect();
224        self
225    }
226
227    /// Sets decay-state expansion behavior.
228    #[must_use]
229    pub const fn decay_state_expansion(mut self, expansion: DecayStateExpansion) -> Self {
230        self.decay_state_expansion = expansion;
231        self
232    }
233}
234
235fn quantum_filter<T>(filter: impl Into<Option<T>>) -> QuantumFilter<T> {
236    filter.into().map_or_else(
237        || QuantumFilter::Missing,
238        |value| QuantumFilter::Value(value),
239    )
240}