atomecs/laser_cooling/
twolevel.rs1extern 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#[derive(Deserialize, Serialize, Clone)]
15pub struct TwoLevelPopulation<T> where T : TransitionComponent {
16 pub ground: f64,
18 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 ground: f64::NAN,
34 excited: f64::NAN,
36 marker: PhantomData
37 }
38 }
39}
40
41impl<T> TwoLevelPopulation<T> where T : TransitionComponent {
42 pub fn calculate_ground_state(&mut self) {
44 self.ground = 1. - self.excited;
45 }
46 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#[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 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 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}