atomecs/atom_sources/
mass.rs

1//! Masses and isotopes of atoms
2
3use crate::atom::Mass;
4use rand;
5use rand::Rng;
6extern crate specs;
7
8use serde::{Deserialize, Serialize};
9use specs::{Component, HashMapStorage};
10
11/// A [MassRatio](struct.MassRatio.html) describes the abundance of a given isotope.
12#[derive(Deserialize, Serialize, Clone)]
13pub struct MassRatio {
14    /// The mass an atom will be created with. See [Mass](struct.Mass.html).
15    pub mass: f64,
16    /// The relative abundance of this mass.
17    pub ratio: f64,
18}
19
20/// Describes the abundance of each mass.
21///
22/// When atoms are created, a random mass is drawn from the [MassDistribution](struct.MassDistribution.html) and assigned to the atom.
23#[derive(Deserialize, Serialize, Clone)]
24pub struct MassDistribution {
25    pub distribution: Vec<MassRatio>,
26    pub normalised: bool,
27}
28impl Component for MassDistribution {
29    type Storage = HashMapStorage<Self>;
30}
31impl MassDistribution {
32    /// Creates a new [MassDistribution](struct.MassDistribution.html), with the specified [MassRatio](struct.MassRatio.html)s.
33    ///
34    /// The created distribution will be normalised.
35    pub fn new(distribution: Vec<MassRatio>) -> Self {
36        let mut mass_dist = MassDistribution {
37            distribution,
38            normalised: false,
39        };
40        mass_dist.normalise();
41        mass_dist
42    }
43
44    /// Normalises the distribution of masses so that the ratios add to one.
45    pub fn normalise(&mut self) {
46        let mut total = 0.;
47        for mr in self.distribution.iter() {
48            total += mr.ratio;
49        }
50
51        for mut mr in &mut self.distribution {
52            mr.ratio /= total;
53        }
54        self.normalised = true
55    }
56
57    /// Randomly draw a mass from the distribution.
58    pub fn draw_random_mass(&self) -> Mass {
59        assert!(self.normalised);
60        let mut level = 0.;
61        let mut rng = rand::thread_rng();
62        let luck = rng.gen_range(0.0..1.0);
63        let mut finalmass = 0.;
64        for masspercent in self.distribution.iter() {
65            level += masspercent.ratio;
66            if level > luck {
67                return Mass {
68                    value: masspercent.mass,
69                };
70            }
71            finalmass = masspercent.mass;
72        }
73        Mass { value: finalmass }
74    }
75}
76
77pub mod tests {
78    #[allow(unused_imports)]
79    use super::*;
80    #[allow(unused_imports)]
81    use assert_approx_eq::assert_approx_eq;
82
83    #[test]
84    fn test_mass_distribution_normalised() {
85        let mass_distribution = MassDistribution::new(vec![
86            MassRatio {
87                mass: 1.0,
88                ratio: 10.0,
89            },
90            MassRatio {
91                mass: 2.0,
92                ratio: 1.0,
93            },
94        ]);
95
96        let mut total_ratio = 0.0;
97        for mr in &mass_distribution.distribution {
98            total_ratio += mr.ratio;
99        }
100
101        assert_approx_eq!(total_ratio, 1., 0.0001);
102    }
103}