atomecs/laser_cooling/
twolevel.rs

1//! Calculation of the steady-state twolevel populations
2
3extern crate rayon;
4
5use crate::laser::sampler::CoolingLaserSamplerMasks;
6use crate::laser_cooling::rate::RateCoefficients;
7use serde::{Deserialize, Serialize};
8use specs::prelude::*;
9use std::{fmt, marker::PhantomData};
10
11use super::transition::{TransitionComponent};
12
13/// Represents the steady-state population density of the excited state and ground state for a given atomic transition.
14#[derive(Deserialize, Serialize, Clone)]
15pub struct TwoLevelPopulation<T> where T : TransitionComponent {
16    /// steady-state population density of the ground state, a number in [0,1]
17    pub ground: f64,
18    /// steady-state population density of the excited state, a number in [0,1]
19    pub excited: f64,
20    marker: PhantomData<T>,
21}
22
23impl<T> fmt::Display for TwoLevelPopulation<T> where T : TransitionComponent {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        write!(f, "g:{},e:{}", self.ground, self.excited)
26    }
27}
28
29impl<T> Default for TwoLevelPopulation<T> where T : TransitionComponent {
30    fn default() -> Self {
31        TwoLevelPopulation {
32            /// steady-state population density of the ground state, a number in [0,1]
33            ground: f64::NAN,
34            /// steady-state population density of the excited state, a number in [0,1]
35            excited: f64::NAN,
36            marker: PhantomData
37        }
38    }
39}
40
41impl<T> TwoLevelPopulation<T> where T : TransitionComponent {
42    /// Calculate the ground state population from excited state population
43    pub fn calculate_ground_state(&mut self) {
44        self.ground = 1. - self.excited;
45    }
46    /// Calculate the excited state population from ground state population
47    pub fn calculate_excited_state(&mut self) {
48        self.excited = 1. - self.ground;
49    }
50}
51
52impl<T> Component for TwoLevelPopulation<T> where T : TransitionComponent + 'static {
53    type Storage = VecStorage<Self>;
54}
55
56/// Calculates the TwoLevelPopulation from the natural linewidth and the `RateCoefficients`
57#[derive(Default)]
58pub struct CalculateTwoLevelPopulationSystem<T, const N: usize>(PhantomData<T>) where T: TransitionComponent;
59
60impl<'a, T, const N: usize> System<'a> for CalculateTwoLevelPopulationSystem<T, N> where T: TransitionComponent {
61    type SystemData = (
62        ReadStorage<'a, T>,
63        ReadStorage<'a, RateCoefficients<T, N>>,
64        ReadStorage<'a, CoolingLaserSamplerMasks<N>>,
65        WriteStorage<'a, TwoLevelPopulation<T>>,
66    );
67
68    fn run(
69        &mut self,
70        (transition, rate_coefficients, masks, mut twolevel_population): Self::SystemData,
71    ) {
72        use rayon::prelude::*;
73
74        (
75            &transition,
76            &rate_coefficients,
77            &masks,
78            &mut twolevel_population,
79        )
80            .par_join()
81            .for_each(|(_transition, rates, mask, twolevel)| {
82                let mut sum_rates: f64 = 0.;
83
84                for count in 0..rates.contents.len() {
85                    if mask.contents[count].filled {
86                        sum_rates += rates.contents[count].rate;
87                    }
88                }
89                twolevel.excited = sum_rates / (T::gamma() + 2. * sum_rates);
90                twolevel.calculate_ground_state();
91            });
92    }
93}
94
95#[cfg(test)]
96pub mod tests {
97
98    use super::*;
99    use crate::{laser::{DEFAULT_BEAM_LIMIT, sampler::LaserSamplerMask}, species::{Strontium88_461, Rubidium87_780D2}, laser_cooling::{rate::RateCoefficient, transition::AtomicTransition}};
100    use assert_approx_eq::assert_approx_eq;
101    extern crate nalgebra;
102
103    #[test]
104    fn test_calculate_twolevel_population_system() {
105        let mut test_world = World::new();
106        test_world.register::<RateCoefficients<Strontium88_461, { DEFAULT_BEAM_LIMIT }>>();
107        test_world.register::<CoolingLaserSamplerMasks<{ DEFAULT_BEAM_LIMIT }>>();
108        test_world.register::<TwoLevelPopulation<Strontium88_461>>();
109        test_world.register::<Strontium88_461>();
110
111        // this test runs with two lasers only and we have to tell this the mask
112        let mut active_lasers =
113            [LaserSamplerMask { filled: false }; DEFAULT_BEAM_LIMIT];
114        active_lasers[0] = LaserSamplerMask { filled: true };
115        active_lasers[1] = LaserSamplerMask { filled: true };
116
117        let mut rc = RateCoefficient::<Strontium88_461>::default();
118        rc.rate = 1_000_000.0;
119
120        let atom1 = test_world
121            .create_entity()
122            .with(RateCoefficients  {
123                contents: [rc; DEFAULT_BEAM_LIMIT],
124            })
125            .with(Strontium88_461)
126            .with(CoolingLaserSamplerMasks {
127                contents: active_lasers,
128            })
129            .with(TwoLevelPopulation::<Strontium88_461>::default())
130            .build();
131
132        let mut system = CalculateTwoLevelPopulationSystem::<Strontium88_461, { DEFAULT_BEAM_LIMIT }>::default();
133        system.run_now(&test_world);
134        test_world.maintain();
135        let sampler_storage = test_world.read_storage::<TwoLevelPopulation<Strontium88_461>>();
136
137        let mut sum_rates = 0.0;
138
139        for active_laser in active_lasers.iter().take(crate::laser::DEFAULT_BEAM_LIMIT) {
140            if active_laser.filled {
141                sum_rates += 1_000_000.0;
142            }
143        }
144
145        assert_approx_eq!(
146            sampler_storage
147                .get(atom1)
148                .expect("entity not found")
149                .excited,
150            sum_rates / (Strontium88_461::gamma() + 2.0 * sum_rates),
151            1e-5_f64
152        );
153    }
154
155    #[test]
156    fn test_popn_high_intensity_limit() {
157        let mut test_world = World::new();
158        test_world.register::<RateCoefficients<Rubidium87_780D2, { DEFAULT_BEAM_LIMIT }>>();
159        test_world.register::<Rubidium87_780D2>();
160        test_world.register::<CoolingLaserSamplerMasks<{ DEFAULT_BEAM_LIMIT }>>();
161        test_world.register::<TwoLevelPopulation<Rubidium87_780D2>>();
162
163        // this test runs with two lasers only and we have to tell this the mask
164        let mut active_lasers = [LaserSamplerMask { filled: false };
165            crate::laser::DEFAULT_BEAM_LIMIT];
166        active_lasers[0] = LaserSamplerMask { filled: true };
167
168        let mut rc = RateCoefficient::<Rubidium87_780D2>::default();
169        rc.rate = 1.0e9;
170
171        let atom1 = test_world
172            .create_entity()
173            .with(RateCoefficients {
174                contents: [rc; DEFAULT_BEAM_LIMIT],
175            })
176            .with(Rubidium87_780D2)
177            .with(CoolingLaserSamplerMasks {
178                contents: active_lasers,
179            })
180            .with(TwoLevelPopulation::<Rubidium87_780D2>::default())
181            .build();
182
183        let mut system = CalculateTwoLevelPopulationSystem::<Rubidium87_780D2, { DEFAULT_BEAM_LIMIT }>::default();
184        system.run_now(&test_world);
185        test_world.maintain();
186        let sampler_storage = test_world.read_storage::<TwoLevelPopulation<Rubidium87_780D2>>();
187
188        assert_approx_eq!(
189            sampler_storage
190                .get(atom1)
191                .expect("entity not found")
192                .excited,
193            0.5,
194            0.01
195        );
196    }
197}