Skip to main content

use_particle/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4//! Small particle classification and metadata helpers.
5
6pub mod prelude;
7
8/// Broad family groupings for the supported particles.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum ParticleFamily {
11    /// Leptons such as electrons, muons, taus, and neutrinos.
12    Lepton,
13    /// Quarks and antiquarks.
14    Quark,
15    /// Gauge bosons such as photons and gluons.
16    GaugeBoson,
17    /// Scalar bosons such as the Higgs boson.
18    ScalarBoson,
19    /// Baryons such as protons and neutrons.
20    Baryon,
21    /// Mesons such as pions.
22    Meson,
23}
24
25/// Spin-statistics classification for the supported particles.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub enum ParticleStatistics {
28    /// A fermion with half-integer spin.
29    Fermion,
30    /// A boson with integer spin.
31    Boson,
32}
33
34/// Identifies a supported particle kind.
35///
36/// The enum is intentionally small and practical rather than exhaustive.
37///
38/// # Examples
39///
40/// ```rust
41/// use use_particle::ParticleKind;
42///
43/// let electron = ParticleKind::Electron;
44///
45/// assert_eq!(electron, ParticleKind::Electron);
46/// ```
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48pub enum ParticleKind {
49    /// The electron.
50    Electron,
51    /// The positron.
52    Positron,
53    /// The muon.
54    Muon,
55    /// The antimuon.
56    Antimuon,
57    /// The tau lepton.
58    Tau,
59    /// The antitau.
60    Antitau,
61
62    /// The electron neutrino.
63    ElectronNeutrino,
64    /// The electron antineutrino.
65    ElectronAntineutrino,
66    /// The muon neutrino.
67    MuonNeutrino,
68    /// The muon antineutrino.
69    MuonAntineutrino,
70    /// The tau neutrino.
71    TauNeutrino,
72    /// The tau antineutrino.
73    TauAntineutrino,
74
75    /// The up quark.
76    UpQuark,
77    /// The anti-up quark.
78    AntiUpQuark,
79    /// The down quark.
80    DownQuark,
81    /// The anti-down quark.
82    AntiDownQuark,
83    /// The charm quark.
84    CharmQuark,
85    /// The anti-charm quark.
86    AntiCharmQuark,
87    /// The strange quark.
88    StrangeQuark,
89    /// The anti-strange quark.
90    AntiStrangeQuark,
91    /// The top quark.
92    TopQuark,
93    /// The anti-top quark.
94    AntiTopQuark,
95    /// The bottom quark.
96    BottomQuark,
97    /// The anti-bottom quark.
98    AntiBottomQuark,
99
100    /// The photon.
101    Photon,
102    /// The gluon.
103    Gluon,
104    /// The positively charged `W` boson.
105    WPlusBoson,
106    /// The negatively charged `W` boson.
107    WMinusBoson,
108    /// The neutral `Z` boson.
109    ZBoson,
110    /// The Higgs boson.
111    HiggsBoson,
112
113    /// The proton.
114    Proton,
115    /// The antiproton.
116    Antiproton,
117    /// The neutron.
118    Neutron,
119    /// The antineutron.
120    Antineutron,
121
122    /// The positively charged pion.
123    PionPlus,
124    /// The negatively charged pion.
125    PionMinus,
126    /// The neutral pion.
127    PionZero,
128}
129
130/// An exact electric charge expressed in thirds of the elementary charge.
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
132pub struct ElementaryCharge {
133    /// Charge measured in thirds of the elementary charge.
134    pub thirds: i8,
135}
136
137impl ElementaryCharge {
138    /// Creates a charge from thirds of the elementary charge.
139    #[must_use]
140    pub const fn new_thirds(thirds: i8) -> Self {
141        Self { thirds }
142    }
143
144    /// Returns a neutral charge.
145    #[must_use]
146    pub const fn neutral() -> Self {
147        Self::new_thirds(0)
148    }
149
150    /// Returns a `+1e` charge.
151    #[must_use]
152    pub const fn positive_one() -> Self {
153        Self::new_thirds(3)
154    }
155
156    /// Returns a `-1e` charge.
157    #[must_use]
158    pub const fn negative_one() -> Self {
159        Self::new_thirds(-3)
160    }
161
162    /// Returns the charge in elementary-charge units.
163    #[must_use]
164    pub fn as_elementary_units(self) -> f64 {
165        f64::from(self.thirds) / 3.0
166    }
167
168    /// Returns `true` when the charge is neutral.
169    #[must_use]
170    pub const fn is_neutral(self) -> bool {
171        self.thirds == 0
172    }
173
174    /// Returns `true` when the charge is positive.
175    #[must_use]
176    pub const fn is_positive(self) -> bool {
177        self.thirds > 0
178    }
179
180    /// Returns `true` when the charge is negative.
181    #[must_use]
182    pub const fn is_negative(self) -> bool {
183        self.thirds < 0
184    }
185}
186
187/// A spin value expressed as doubled units of `hbar`.
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
189pub struct Spin {
190    /// Spin measured in doubled units of `hbar`.
191    pub doubled: i8,
192}
193
194impl Spin {
195    /// Creates a spin from doubled units of `hbar`.
196    #[must_use]
197    pub const fn new_doubled(doubled: i8) -> Self {
198        Self { doubled }
199    }
200
201    /// Returns spin `0`.
202    #[must_use]
203    pub const fn zero() -> Self {
204        Self::new_doubled(0)
205    }
206
207    /// Returns spin `1/2`.
208    #[must_use]
209    pub const fn half() -> Self {
210        Self::new_doubled(1)
211    }
212
213    /// Returns spin `1`.
214    #[must_use]
215    pub const fn one() -> Self {
216        Self::new_doubled(2)
217    }
218
219    /// Returns the spin in units of `hbar`.
220    #[must_use]
221    pub fn as_units_of_hbar(self) -> f64 {
222        f64::from(self.doubled) / 2.0
223    }
224
225    /// Returns `true` when the spin is an integer multiple of `hbar`.
226    #[must_use]
227    pub const fn is_integer(self) -> bool {
228        self.doubled % 2 == 0
229    }
230
231    /// Returns `true` when the spin is a half-integer multiple of `hbar`.
232    #[must_use]
233    pub const fn is_half_integer(self) -> bool {
234        !self.is_integer()
235    }
236}
237
238/// A lightweight particle wrapper that delegates to the free helper functions.
239#[derive(Debug, Clone, Copy, PartialEq, Eq)]
240pub struct Particle {
241    /// The underlying particle kind.
242    pub kind: ParticleKind,
243}
244
245#[allow(clippy::trivially_copy_pass_by_ref)]
246impl Particle {
247    /// Creates a new `Particle` from `kind`.
248    ///
249    /// # Examples
250    ///
251    /// ```rust
252    /// use use_particle::{Particle, ParticleKind};
253    ///
254    /// let electron = Particle::new(ParticleKind::Electron);
255    ///
256    /// assert_eq!(electron.kind, ParticleKind::Electron);
257    /// ```
258    #[must_use]
259    pub const fn new(kind: ParticleKind) -> Self {
260        Self { kind }
261    }
262
263    /// Returns the particle family.
264    #[must_use]
265    pub const fn family(&self) -> ParticleFamily {
266        family(self.kind)
267    }
268
269    /// Returns the exact charge.
270    #[must_use]
271    pub const fn charge(&self) -> ElementaryCharge {
272        charge(self.kind)
273    }
274
275    /// Returns the spin.
276    #[must_use]
277    pub const fn spin(&self) -> Spin {
278        spin(self.kind)
279    }
280
281    /// Returns the particle statistics.
282    #[must_use]
283    pub const fn statistics(&self) -> ParticleStatistics {
284        statistics(self.kind)
285    }
286
287    /// Returns the antiparticle when it is modeled by this crate.
288    #[must_use]
289    pub const fn antiparticle(&self) -> Option<Self> {
290        match antiparticle(self.kind) {
291            Some(kind) => Some(Self::new(kind)),
292            None => None,
293        }
294    }
295
296    /// Returns the approximate rest mass in `MeV/c^2`.
297    #[must_use]
298    pub const fn rest_mass_mev_c2(&self) -> Option<f64> {
299        rest_mass_mev_c2(self.kind)
300    }
301
302    /// Returns `true` when this particle is represented as an antiparticle variant.
303    #[must_use]
304    pub const fn is_antiparticle(&self) -> bool {
305        is_antiparticle(self.kind)
306    }
307
308    /// Returns `true` when the particle is its own antiparticle.
309    #[must_use]
310    pub const fn is_self_antiparticle(&self) -> bool {
311        is_self_antiparticle(self.kind)
312    }
313}
314
315/// Returns the exact charge in thirds of the elementary charge.
316#[must_use]
317pub const fn charge_thirds(kind: ParticleKind) -> i8 {
318    match kind {
319        ParticleKind::Electron
320        | ParticleKind::Muon
321        | ParticleKind::Tau
322        | ParticleKind::WMinusBoson
323        | ParticleKind::Antiproton
324        | ParticleKind::PionMinus => -3,
325        ParticleKind::Positron
326        | ParticleKind::Antimuon
327        | ParticleKind::Antitau
328        | ParticleKind::WPlusBoson
329        | ParticleKind::Proton
330        | ParticleKind::PionPlus => 3,
331        ParticleKind::UpQuark | ParticleKind::CharmQuark | ParticleKind::TopQuark => 2,
332        ParticleKind::AntiUpQuark | ParticleKind::AntiCharmQuark | ParticleKind::AntiTopQuark => -2,
333        ParticleKind::DownQuark | ParticleKind::StrangeQuark | ParticleKind::BottomQuark => -1,
334        ParticleKind::AntiDownQuark
335        | ParticleKind::AntiStrangeQuark
336        | ParticleKind::AntiBottomQuark => 1,
337        ParticleKind::ElectronNeutrino
338        | ParticleKind::ElectronAntineutrino
339        | ParticleKind::MuonNeutrino
340        | ParticleKind::MuonAntineutrino
341        | ParticleKind::TauNeutrino
342        | ParticleKind::TauAntineutrino
343        | ParticleKind::Photon
344        | ParticleKind::Gluon
345        | ParticleKind::ZBoson
346        | ParticleKind::HiggsBoson
347        | ParticleKind::Neutron
348        | ParticleKind::Antineutron
349        | ParticleKind::PionZero => 0,
350    }
351}
352
353/// Returns the exact charge for `kind`.
354///
355/// # Examples
356///
357/// ```rust
358/// use use_particle::{ParticleKind, charge};
359///
360/// assert_eq!(charge(ParticleKind::Electron).thirds, -3);
361/// assert_eq!(charge(ParticleKind::UpQuark).thirds, 2);
362/// ```
363#[must_use]
364pub const fn charge(kind: ParticleKind) -> ElementaryCharge {
365    ElementaryCharge::new_thirds(charge_thirds(kind))
366}
367
368/// Returns the charge in elementary-charge units.
369#[must_use]
370pub fn charge_in_elementary_units(kind: ParticleKind) -> f64 {
371    charge(kind).as_elementary_units()
372}
373
374/// Returns the spin for `kind`.
375///
376/// # Examples
377///
378/// ```rust
379/// use use_particle::{ParticleKind, Spin, spin};
380///
381/// assert_eq!(spin(ParticleKind::Electron), Spin::half());
382/// assert_eq!(spin(ParticleKind::Photon), Spin::one());
383/// ```
384#[must_use]
385pub const fn spin(kind: ParticleKind) -> Spin {
386    match kind {
387        ParticleKind::Electron
388        | ParticleKind::Positron
389        | ParticleKind::Muon
390        | ParticleKind::Antimuon
391        | ParticleKind::Tau
392        | ParticleKind::Antitau
393        | ParticleKind::ElectronNeutrino
394        | ParticleKind::ElectronAntineutrino
395        | ParticleKind::MuonNeutrino
396        | ParticleKind::MuonAntineutrino
397        | ParticleKind::TauNeutrino
398        | ParticleKind::TauAntineutrino
399        | ParticleKind::UpQuark
400        | ParticleKind::AntiUpQuark
401        | ParticleKind::DownQuark
402        | ParticleKind::AntiDownQuark
403        | ParticleKind::CharmQuark
404        | ParticleKind::AntiCharmQuark
405        | ParticleKind::StrangeQuark
406        | ParticleKind::AntiStrangeQuark
407        | ParticleKind::TopQuark
408        | ParticleKind::AntiTopQuark
409        | ParticleKind::BottomQuark
410        | ParticleKind::AntiBottomQuark
411        | ParticleKind::Proton
412        | ParticleKind::Antiproton
413        | ParticleKind::Neutron
414        | ParticleKind::Antineutron => Spin::half(),
415        ParticleKind::Photon
416        | ParticleKind::Gluon
417        | ParticleKind::WPlusBoson
418        | ParticleKind::WMinusBoson
419        | ParticleKind::ZBoson => Spin::one(),
420        ParticleKind::HiggsBoson
421        | ParticleKind::PionPlus
422        | ParticleKind::PionMinus
423        | ParticleKind::PionZero => Spin::zero(),
424    }
425}
426
427/// Returns the particle statistics implied by the modeled spin.
428#[must_use]
429pub const fn statistics(kind: ParticleKind) -> ParticleStatistics {
430    if spin(kind).is_half_integer() {
431        ParticleStatistics::Fermion
432    } else {
433        ParticleStatistics::Boson
434    }
435}
436
437/// Returns the broad particle family for `kind`.
438///
439/// # Examples
440///
441/// ```rust
442/// use use_particle::{ParticleFamily, ParticleKind, family};
443///
444/// assert_eq!(family(ParticleKind::Electron), ParticleFamily::Lepton);
445/// assert_eq!(family(ParticleKind::Proton), ParticleFamily::Baryon);
446/// ```
447#[must_use]
448pub const fn family(kind: ParticleKind) -> ParticleFamily {
449    match kind {
450        ParticleKind::Electron
451        | ParticleKind::Positron
452        | ParticleKind::Muon
453        | ParticleKind::Antimuon
454        | ParticleKind::Tau
455        | ParticleKind::Antitau
456        | ParticleKind::ElectronNeutrino
457        | ParticleKind::ElectronAntineutrino
458        | ParticleKind::MuonNeutrino
459        | ParticleKind::MuonAntineutrino
460        | ParticleKind::TauNeutrino
461        | ParticleKind::TauAntineutrino => ParticleFamily::Lepton,
462        ParticleKind::UpQuark
463        | ParticleKind::AntiUpQuark
464        | ParticleKind::DownQuark
465        | ParticleKind::AntiDownQuark
466        | ParticleKind::CharmQuark
467        | ParticleKind::AntiCharmQuark
468        | ParticleKind::StrangeQuark
469        | ParticleKind::AntiStrangeQuark
470        | ParticleKind::TopQuark
471        | ParticleKind::AntiTopQuark
472        | ParticleKind::BottomQuark
473        | ParticleKind::AntiBottomQuark => ParticleFamily::Quark,
474        ParticleKind::Photon
475        | ParticleKind::Gluon
476        | ParticleKind::WPlusBoson
477        | ParticleKind::WMinusBoson
478        | ParticleKind::ZBoson => ParticleFamily::GaugeBoson,
479        ParticleKind::HiggsBoson => ParticleFamily::ScalarBoson,
480        ParticleKind::Proton
481        | ParticleKind::Antiproton
482        | ParticleKind::Neutron
483        | ParticleKind::Antineutron => ParticleFamily::Baryon,
484        ParticleKind::PionPlus | ParticleKind::PionMinus | ParticleKind::PionZero => {
485            ParticleFamily::Meson
486        },
487    }
488}
489
490/// Returns `true` when `kind` is a lepton.
491#[must_use]
492pub const fn is_lepton(kind: ParticleKind) -> bool {
493    matches!(family(kind), ParticleFamily::Lepton)
494}
495
496/// Returns `true` when `kind` is a quark.
497#[must_use]
498pub const fn is_quark(kind: ParticleKind) -> bool {
499    matches!(family(kind), ParticleFamily::Quark)
500}
501
502/// Returns `true` when `kind` is a boson.
503#[must_use]
504pub const fn is_boson(kind: ParticleKind) -> bool {
505    matches!(statistics(kind), ParticleStatistics::Boson)
506}
507
508/// Returns `true` when `kind` is a baryon.
509#[must_use]
510pub const fn is_baryon(kind: ParticleKind) -> bool {
511    matches!(family(kind), ParticleFamily::Baryon)
512}
513
514/// Returns `true` when `kind` is a meson.
515#[must_use]
516pub const fn is_meson(kind: ParticleKind) -> bool {
517    matches!(family(kind), ParticleFamily::Meson)
518}
519
520/// Returns `true` when `kind` is a fermion.
521#[must_use]
522pub const fn is_fermion(kind: ParticleKind) -> bool {
523    matches!(statistics(kind), ParticleStatistics::Fermion)
524}
525
526/// Returns the modeled antiparticle for `kind`.
527///
528/// Self-conjugate particles such as the photon return themselves.
529///
530/// # Examples
531///
532/// ```rust
533/// use use_particle::{ParticleKind, antiparticle};
534///
535/// assert_eq!(antiparticle(ParticleKind::Electron), Some(ParticleKind::Positron));
536/// assert_eq!(antiparticle(ParticleKind::Photon), Some(ParticleKind::Photon));
537/// ```
538#[must_use]
539#[allow(clippy::unnecessary_wraps)]
540pub const fn antiparticle(kind: ParticleKind) -> Option<ParticleKind> {
541    match kind {
542        ParticleKind::Electron => Some(ParticleKind::Positron),
543        ParticleKind::Positron => Some(ParticleKind::Electron),
544        ParticleKind::Muon => Some(ParticleKind::Antimuon),
545        ParticleKind::Antimuon => Some(ParticleKind::Muon),
546        ParticleKind::Tau => Some(ParticleKind::Antitau),
547        ParticleKind::Antitau => Some(ParticleKind::Tau),
548        ParticleKind::ElectronNeutrino => Some(ParticleKind::ElectronAntineutrino),
549        ParticleKind::ElectronAntineutrino => Some(ParticleKind::ElectronNeutrino),
550        ParticleKind::MuonNeutrino => Some(ParticleKind::MuonAntineutrino),
551        ParticleKind::MuonAntineutrino => Some(ParticleKind::MuonNeutrino),
552        ParticleKind::TauNeutrino => Some(ParticleKind::TauAntineutrino),
553        ParticleKind::TauAntineutrino => Some(ParticleKind::TauNeutrino),
554        ParticleKind::UpQuark => Some(ParticleKind::AntiUpQuark),
555        ParticleKind::AntiUpQuark => Some(ParticleKind::UpQuark),
556        ParticleKind::DownQuark => Some(ParticleKind::AntiDownQuark),
557        ParticleKind::AntiDownQuark => Some(ParticleKind::DownQuark),
558        ParticleKind::CharmQuark => Some(ParticleKind::AntiCharmQuark),
559        ParticleKind::AntiCharmQuark => Some(ParticleKind::CharmQuark),
560        ParticleKind::StrangeQuark => Some(ParticleKind::AntiStrangeQuark),
561        ParticleKind::AntiStrangeQuark => Some(ParticleKind::StrangeQuark),
562        ParticleKind::TopQuark => Some(ParticleKind::AntiTopQuark),
563        ParticleKind::AntiTopQuark => Some(ParticleKind::TopQuark),
564        ParticleKind::BottomQuark => Some(ParticleKind::AntiBottomQuark),
565        ParticleKind::AntiBottomQuark => Some(ParticleKind::BottomQuark),
566        ParticleKind::Photon
567        | ParticleKind::Gluon
568        | ParticleKind::ZBoson
569        | ParticleKind::HiggsBoson => Some(kind),
570        ParticleKind::WPlusBoson => Some(ParticleKind::WMinusBoson),
571        ParticleKind::WMinusBoson => Some(ParticleKind::WPlusBoson),
572        ParticleKind::Proton => Some(ParticleKind::Antiproton),
573        ParticleKind::Antiproton => Some(ParticleKind::Proton),
574        ParticleKind::Neutron => Some(ParticleKind::Antineutron),
575        ParticleKind::Antineutron => Some(ParticleKind::Neutron),
576        ParticleKind::PionPlus => Some(ParticleKind::PionMinus),
577        ParticleKind::PionMinus => Some(ParticleKind::PionPlus),
578        ParticleKind::PionZero => Some(ParticleKind::PionZero),
579    }
580}
581
582/// Returns `true` when `kind` is represented as an antiparticle variant.
583#[must_use]
584pub const fn is_antiparticle(kind: ParticleKind) -> bool {
585    matches!(
586        kind,
587        ParticleKind::Positron
588            | ParticleKind::Antimuon
589            | ParticleKind::Antitau
590            | ParticleKind::ElectronAntineutrino
591            | ParticleKind::MuonAntineutrino
592            | ParticleKind::TauAntineutrino
593            | ParticleKind::AntiUpQuark
594            | ParticleKind::AntiDownQuark
595            | ParticleKind::AntiCharmQuark
596            | ParticleKind::AntiStrangeQuark
597            | ParticleKind::AntiTopQuark
598            | ParticleKind::AntiBottomQuark
599            | ParticleKind::WMinusBoson
600            | ParticleKind::Antiproton
601            | ParticleKind::Antineutron
602            | ParticleKind::PionMinus
603    )
604}
605
606/// Returns `true` when `kind` is its own antiparticle.
607#[must_use]
608pub const fn is_self_antiparticle(kind: ParticleKind) -> bool {
609    matches!(
610        kind,
611        ParticleKind::Photon
612            | ParticleKind::Gluon
613            | ParticleKind::ZBoson
614            | ParticleKind::HiggsBoson
615            | ParticleKind::PionZero
616    )
617}
618
619/// Returns an approximate rest mass in `MeV/c^2` for `kind`.
620///
621/// This metadata is intended for practical examples rather than precision reference work.
622#[must_use]
623pub const fn rest_mass_mev_c2(kind: ParticleKind) -> Option<f64> {
624    match kind {
625        ParticleKind::Electron | ParticleKind::Positron => Some(0.511),
626        ParticleKind::Muon | ParticleKind::Antimuon => Some(105.658),
627        ParticleKind::Tau | ParticleKind::Antitau => Some(1_776.86),
628        ParticleKind::ElectronNeutrino
629        | ParticleKind::ElectronAntineutrino
630        | ParticleKind::MuonNeutrino
631        | ParticleKind::MuonAntineutrino
632        | ParticleKind::TauNeutrino
633        | ParticleKind::TauAntineutrino
634        | ParticleKind::UpQuark
635        | ParticleKind::AntiUpQuark
636        | ParticleKind::DownQuark
637        | ParticleKind::AntiDownQuark
638        | ParticleKind::CharmQuark
639        | ParticleKind::AntiCharmQuark
640        | ParticleKind::StrangeQuark
641        | ParticleKind::AntiStrangeQuark
642        | ParticleKind::TopQuark
643        | ParticleKind::AntiTopQuark
644        | ParticleKind::BottomQuark
645        | ParticleKind::AntiBottomQuark => None,
646        ParticleKind::Photon | ParticleKind::Gluon => Some(0.0),
647        ParticleKind::WPlusBoson | ParticleKind::WMinusBoson => Some(80_379.0),
648        ParticleKind::ZBoson => Some(91_188.0),
649        ParticleKind::HiggsBoson => Some(125_250.0),
650        ParticleKind::Proton | ParticleKind::Antiproton => Some(938.272),
651        ParticleKind::Neutron | ParticleKind::Antineutron => Some(939.565),
652        ParticleKind::PionPlus | ParticleKind::PionMinus => Some(139.570),
653        ParticleKind::PionZero => Some(134.977),
654    }
655}
656
657/// Returns whether `kind` has nonzero rest mass when that metadata is modeled here.
658#[must_use]
659pub const fn has_rest_mass(kind: ParticleKind) -> Option<bool> {
660    match kind {
661        ParticleKind::Photon | ParticleKind::Gluon => Some(false),
662        ParticleKind::Electron
663        | ParticleKind::Positron
664        | ParticleKind::Muon
665        | ParticleKind::Antimuon
666        | ParticleKind::Tau
667        | ParticleKind::Antitau
668        | ParticleKind::WPlusBoson
669        | ParticleKind::WMinusBoson
670        | ParticleKind::ZBoson
671        | ParticleKind::HiggsBoson
672        | ParticleKind::Proton
673        | ParticleKind::Antiproton
674        | ParticleKind::Neutron
675        | ParticleKind::Antineutron
676        | ParticleKind::PionPlus
677        | ParticleKind::PionMinus
678        | ParticleKind::PionZero => Some(true),
679        ParticleKind::ElectronNeutrino
680        | ParticleKind::ElectronAntineutrino
681        | ParticleKind::MuonNeutrino
682        | ParticleKind::MuonAntineutrino
683        | ParticleKind::TauNeutrino
684        | ParticleKind::TauAntineutrino
685        | ParticleKind::UpQuark
686        | ParticleKind::AntiUpQuark
687        | ParticleKind::DownQuark
688        | ParticleKind::AntiDownQuark
689        | ParticleKind::CharmQuark
690        | ParticleKind::AntiCharmQuark
691        | ParticleKind::StrangeQuark
692        | ParticleKind::AntiStrangeQuark
693        | ParticleKind::TopQuark
694        | ParticleKind::AntiTopQuark
695        | ParticleKind::BottomQuark
696        | ParticleKind::AntiBottomQuark => None,
697    }
698}
699
700#[cfg(test)]
701mod tests {
702    use super::{
703        ElementaryCharge, Particle, ParticleFamily, ParticleKind, ParticleStatistics, Spin,
704        antiparticle, charge, charge_thirds, family, has_rest_mass, is_antiparticle, is_baryon,
705        is_boson, is_fermion, is_lepton, is_meson, is_quark, is_self_antiparticle,
706        rest_mass_mev_c2, spin, statistics,
707    };
708
709    fn approx_eq(left: f64, right: f64) -> bool {
710        (left - right).abs() < 1.0e-10
711    }
712
713    #[test]
714    fn charge_helpers_cover_common_particles() {
715        assert_eq!(charge_thirds(ParticleKind::Electron), -3);
716        assert_eq!(charge_thirds(ParticleKind::Positron), 3);
717        assert_eq!(charge_thirds(ParticleKind::UpQuark), 2);
718        assert_eq!(charge_thirds(ParticleKind::DownQuark), -1);
719        assert_eq!(charge_thirds(ParticleKind::Photon), 0);
720
721        assert!(approx_eq(
722            charge(ParticleKind::Electron).as_elementary_units(),
723            -1.0
724        ));
725        assert!(approx_eq(
726            charge(ParticleKind::UpQuark).as_elementary_units(),
727            0.666_666_666_7,
728        ));
729    }
730
731    #[test]
732    fn spin_helpers_follow_statistics_rules() {
733        assert_eq!(spin(ParticleKind::Electron), Spin::half());
734        assert_eq!(spin(ParticleKind::Photon), Spin::one());
735        assert_eq!(spin(ParticleKind::HiggsBoson), Spin::zero());
736
737        assert_eq!(
738            statistics(ParticleKind::Electron),
739            ParticleStatistics::Fermion
740        );
741        assert_eq!(statistics(ParticleKind::Photon), ParticleStatistics::Boson);
742        assert_eq!(
743            statistics(ParticleKind::HiggsBoson),
744            ParticleStatistics::Boson
745        );
746    }
747
748    #[test]
749    fn family_helpers_group_particle_kinds() {
750        assert_eq!(family(ParticleKind::Electron), ParticleFamily::Lepton);
751        assert_eq!(family(ParticleKind::UpQuark), ParticleFamily::Quark);
752        assert_eq!(family(ParticleKind::Photon), ParticleFamily::GaugeBoson);
753        assert_eq!(
754            family(ParticleKind::HiggsBoson),
755            ParticleFamily::ScalarBoson
756        );
757        assert_eq!(family(ParticleKind::Proton), ParticleFamily::Baryon);
758        assert_eq!(family(ParticleKind::PionPlus), ParticleFamily::Meson);
759
760        assert!(is_lepton(ParticleKind::Electron));
761        assert!(is_quark(ParticleKind::UpQuark));
762        assert!(is_boson(ParticleKind::Photon));
763        assert!(is_baryon(ParticleKind::Proton));
764        assert!(is_meson(ParticleKind::PionZero));
765        assert!(is_fermion(ParticleKind::Electron));
766        assert!(!is_fermion(ParticleKind::Photon));
767    }
768
769    #[test]
770    fn antimatter_helpers_cover_pairs_and_self_conjugate_particles() {
771        assert_eq!(
772            antiparticle(ParticleKind::Electron),
773            Some(ParticleKind::Positron)
774        );
775        assert_eq!(
776            antiparticle(ParticleKind::Positron),
777            Some(ParticleKind::Electron)
778        );
779        assert_eq!(
780            antiparticle(ParticleKind::Proton),
781            Some(ParticleKind::Antiproton)
782        );
783        assert_eq!(
784            antiparticle(ParticleKind::Photon),
785            Some(ParticleKind::Photon)
786        );
787
788        assert!(is_antiparticle(ParticleKind::Positron));
789        assert!(!is_antiparticle(ParticleKind::Electron));
790        assert!(is_self_antiparticle(ParticleKind::Photon));
791        assert!(is_self_antiparticle(ParticleKind::PionZero));
792        assert!(!is_self_antiparticle(ParticleKind::Electron));
793    }
794
795    #[test]
796    fn rest_mass_metadata_is_small_and_practical() {
797        assert!(matches!(
798            rest_mass_mev_c2(ParticleKind::Electron),
799            Some(mass) if approx_eq(mass, 0.511)
800        ));
801        assert_eq!(rest_mass_mev_c2(ParticleKind::Photon), Some(0.0));
802        assert_eq!(rest_mass_mev_c2(ParticleKind::ElectronNeutrino), None);
803
804        assert_eq!(has_rest_mass(ParticleKind::Photon), Some(false));
805        assert_eq!(has_rest_mass(ParticleKind::Electron), Some(true));
806    }
807
808    #[test]
809    fn particle_wrapper_delegates_to_free_functions() {
810        let electron = Particle::new(ParticleKind::Electron);
811
812        assert_eq!(electron.charge(), ElementaryCharge::negative_one());
813        assert_eq!(electron.family(), ParticleFamily::Lepton);
814        assert!(matches!(
815            electron.antiparticle(),
816            Some(Particle {
817                kind: ParticleKind::Positron
818            })
819        ));
820    }
821}