atomecs 0.7.1

An data-oriented simulation package for cold atom experiments.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
//! Calculation of scattering events of photons with atoms

extern crate rayon;

use rand;
use rand_distr::{Distribution, Poisson};

use crate::{integrator::Timestep};
use crate::laser::sampler::CoolingLaserSamplerMasks;
use crate::laser_cooling::rate::RateCoefficients;
use crate::laser_cooling::twolevel::TwoLevelPopulation;
use serde::{Deserialize, Serialize};
use specs::prelude::*;
use std::fmt;
use std::marker::PhantomData;

use super::transition::{TransitionComponent};

/// Holds the total number of photons that the atom is expected to scatter
/// in the current simulation step from all beams.
///
/// This is an early estimation used to determine the more precise `ExpectedPhotonsScattered`
/// afterwards.
#[derive(Clone, Copy, Serialize)]
pub struct TotalPhotonsScattered<T> where T : TransitionComponent {
    /// Number of photons scattered from all beams
    pub total: f64,
    phantom: PhantomData<T>
}

impl<T> Default for TotalPhotonsScattered<T> where T : TransitionComponent {
    fn default() -> Self {
        TotalPhotonsScattered {
            /// Number of photons scattered from all beams
            total: f64::NAN,
            phantom: PhantomData
        }
    }
}

impl<T> Component for TotalPhotonsScattered<T> where T : TransitionComponent + 'static {
    type Storage = VecStorage<Self>;
}

/// Calcutates the total number of photons scattered in one iteration step
///
/// This can be calculated by: Timestep * TwolevelPopulation * Linewidth
#[derive(Default)]
pub struct CalculateMeanTotalPhotonsScatteredSystem<T>(PhantomData<T>) where T : TransitionComponent;
impl<'a, T> System<'a> for CalculateMeanTotalPhotonsScatteredSystem<T> 
where T: TransitionComponent {
    type SystemData = (
        ReadExpect<'a, Timestep>,
        ReadStorage<'a, T>,
        ReadStorage<'a, TwoLevelPopulation<T>>,
        WriteStorage<'a, TotalPhotonsScattered<T>>,
    );

    fn run(
        &mut self,
        (timestep, transition, twolevel_population, mut total_photons_scattered): Self::SystemData,
    ) {
        use rayon::prelude::*;

        (
            &transition,
            &twolevel_population,
            &mut total_photons_scattered,
        )
            .par_join()
            .for_each(|(_atominfo, twolevel, total)| {
                total.total = timestep.delta * T::gamma() * twolevel.excited;
            });
    }
}

/// The number of photons scattered by the atom from a single, specific beam
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct ExpectedPhotonsScattered<T> where T : TransitionComponent {
    ///photons scattered by the atom from a specific beam
    scattered: f64,
    phantom: PhantomData<T>
}

impl<T> Default for ExpectedPhotonsScattered<T> where T : TransitionComponent {
    fn default() -> Self {
        ExpectedPhotonsScattered {
            ///photons scattered by the atom from a specific beam
            scattered: f64::NAN,
            phantom: PhantomData
        }
    }
}

/// The List that holds an `ExpectedPhotonsScattered` for each laser
#[derive(Deserialize, Serialize, Clone)]
pub struct ExpectedPhotonsScatteredVector<T, const N: usize> where T : TransitionComponent {
    #[serde(with = "serde_arrays")]
    pub contents: [ExpectedPhotonsScattered<T>; N],
}

impl<T, const N: usize> Component for ExpectedPhotonsScatteredVector<T, N> where T : TransitionComponent {
    type Storage = VecStorage<Self>;
}

impl<T, const N: usize> fmt::Display for ExpectedPhotonsScatteredVector<T, N> where T : TransitionComponent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut result = f.write_str("");
        for aps in &self.contents {
            result = f.write_fmt(format_args!("{},", aps.scattered));
        }
        result
    }
}

/// This system initialises all ´ExpectedPhotonsScatteredVector´ to a NAN value.
///
/// It also ensures that the size of the ´ExpectedPhotonsScatteredVector´ components match the number of CoolingLight entities in the world.
#[derive(Default)]
pub struct InitialiseExpectedPhotonsScatteredVectorSystem<T, const N: usize>(PhantomData<T>) where T : TransitionComponent;
impl<'a, T, const N: usize> System<'a> for InitialiseExpectedPhotonsScatteredVectorSystem<T, N> where T : TransitionComponent {
    type SystemData = (WriteStorage<'a, ExpectedPhotonsScatteredVector<T, N>>,);
    fn run(&mut self, (mut expected_photons,): Self::SystemData) {
        use rayon::prelude::*;

        (&mut expected_photons).par_join().for_each(|mut expected| {
            expected.contents = [ExpectedPhotonsScattered::default(); N];
        });
    }
}

/// Calculates the expected mean number of Photons scattered by each laser in one iteration step
///
/// It is required that the `TotalPhotonsScattered` is already updated since this System divides
/// them between the CoolingLight entities.
#[derive(Default)]
pub struct CalculateExpectedPhotonsScatteredSystem<T, const N: usize>(PhantomData<T>) where T : TransitionComponent;
impl<'a, T, const N: usize> System<'a> for CalculateExpectedPhotonsScatteredSystem<T, N> where T : TransitionComponent {
    type SystemData = (
        ReadStorage<'a, RateCoefficients<T, N>>,
        ReadStorage<'a, TotalPhotonsScattered<T>>,
        ReadStorage<'a, CoolingLaserSamplerMasks<N>>,
        WriteStorage<'a, ExpectedPhotonsScatteredVector<T, N>>,
    );

    fn run(
        &mut self,
        (rate_coefficients, total_photons_scattered, masks, mut expected_photons_vector): Self::SystemData,
    ) {
        use rayon::prelude::*;

        (
            &rate_coefficients,
            &total_photons_scattered,
            &masks,
            &mut expected_photons_vector,
        )
            .par_join()
            .for_each(|(rates, total, mask, expected)| {
                let mut sum_rates: f64 = 0.;

                for index in 0..rates.contents.len() {
                    if mask.contents[index].filled {
                        sum_rates += rates.contents[index].rate;
                    }
                }

                for index in 0..expected.contents.len() {
                    if mask.contents[index].filled {
                        expected.contents[index].scattered =
                            rates.contents[index].rate / sum_rates * total.total;
                    }
                }
            });
    }
}

/// The number of photons actually scattered by the atom from a single, specific beam
///
/// If `EnableScatteringFluctuations` is not activated, this will be the same as
/// `ExpectedPhotonsScattered`.
///
/// If `EnableScatteringFluctuations` is activated, this number represents the outcome
/// of a sampling process from a poisson distribution where the lambda parameter is
/// `ExpectedPhotonsScattered`. This adds an additional degree of randomness to
/// the simulation that helps to recreate the recoil limit.  
#[derive(Deserialize, Serialize, Clone, Copy)]
pub struct ActualPhotonsScattered<T> where T : TransitionComponent {
    ///  number of photons actually scattered by an atomic transition from a specific beam.
    pub scattered: f64,
    phantom: PhantomData<T>
}

impl<T> Default for ActualPhotonsScattered<T> where T : TransitionComponent {
    fn default() -> Self {
        ActualPhotonsScattered {
            ///  number of photons actually scattered by the atom from a specific beam
            scattered: 0.0,
            phantom: PhantomData
        }
    }
}

/// The ist that holds an `ActualPhotonsScattered` for each CoolingLight entity
#[derive(Deserialize, Serialize, Clone)]
pub struct ActualPhotonsScatteredVector<T, const N: usize> where T : TransitionComponent {
    #[serde(with = "serde_arrays")]
    pub contents: [ActualPhotonsScattered<T>; N],
}

impl<T, const N: usize> ActualPhotonsScatteredVector<T, N> where T : TransitionComponent{
    /// Calculate the sum of all entries
    pub fn calculate_total_scattered(&self) -> u64 {
        let mut sum: f64 = 0.0;
        // for i in 0..self.contents.len() {
        //     sum += self.contents[i].scattered;
        // }
        for item in &self.contents {
            sum += item.scattered;
        }
        sum as u64
    }
}
impl<T, const N: usize> fmt::Display for ActualPhotonsScatteredVector<T, N> where T : TransitionComponent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut result = f.write_str("");
        for aps in &self.contents {
            result = f.write_fmt(format_args!("{},", aps.scattered));
        }
        result
    }
}
impl<T, const N: usize> Component for ActualPhotonsScatteredVector<T, N> where T : TransitionComponent + 'static {
    type Storage = VecStorage<Self>;
}

/// If this is added as a resource, the number of actual photons will be drawn from a poisson distribution.
///
/// Otherwise, the entries of `ActualPhotonsScatteredVector` will be identical with those of
/// `ExpectedPhotonsScatteredVector`.
#[derive(Clone, Copy)]
pub enum ScatteringFluctuationsOption {
    Off,
    On,
}
impl Default for ScatteringFluctuationsOption {
    fn default() -> Self {
        ScatteringFluctuationsOption::On
    }
}

/// Calcutates the actual number of photons scattered by each CoolingLight entity in one iteration step
/// by drawing from a Poisson Distribution that has `ExpectedPhotonsScattered` as the lambda parameter.
#[derive(Default)]
pub struct CalculateActualPhotonsScatteredSystem<T, const N: usize>(PhantomData<T>) where T : TransitionComponent;

impl<'a, T, const N: usize> System<'a> for CalculateActualPhotonsScatteredSystem<T, N> where T : TransitionComponent {
    type SystemData = (
        Option<Read<'a, ScatteringFluctuationsOption>>,
        ReadStorage<'a, ExpectedPhotonsScatteredVector<T, N>>,
        WriteStorage<'a, ActualPhotonsScatteredVector<T, N>>,
    );

    fn run(
        &mut self,
        (fluctuations_option, expected_photons_vector, mut actual_photons_vector): Self::SystemData,
    ) {
        use rayon::prelude::*;

        match fluctuations_option {
            None => {
                (&expected_photons_vector, &mut actual_photons_vector)
                    .par_join()
                    .for_each(|(expected, actual)| {
                        for index in 0..expected.contents.len() {
                            actual.contents[index].scattered = expected.contents[index].scattered;
                        }
                    });
            }
            Some(rand_option) => match *rand_option {
                ScatteringFluctuationsOption::Off => {
                    (&expected_photons_vector, &mut actual_photons_vector)
                        .par_join()
                        .for_each(|(expected, actual)| {
                            for index in 0..expected.contents.len() {
                                actual.contents[index].scattered =
                                    expected.contents[index].scattered;
                            }
                        });
                }
                ScatteringFluctuationsOption::On => {
                    (&expected_photons_vector, &mut actual_photons_vector)
                        .par_join()
                        .for_each(|(expected, actual)| {
                            for index in 0..expected.contents.len() {
                                let lambda = expected.contents[index].scattered;
                                actual.contents[index].scattered =
                                    if lambda <= 1.0e-5 || lambda.is_nan() {
                                        0.0
                                    } else {
                                        let poisson = Poisson::new(lambda).unwrap();
                                        let drawn_number = poisson.sample(&mut rand::thread_rng());
                                        drawn_number as f64
                                    }
                            }
                        });
                }
            },
        }
    }
}

#[cfg(test)]
pub mod tests {

    use crate::{laser::{DEFAULT_BEAM_LIMIT, sampler::LaserSamplerMask}, species::Strontium88_461, laser_cooling::{rate::RateCoefficient, transition::AtomicTransition}};

    use super::*;

    extern crate specs;
    use assert_approx_eq::assert_approx_eq;
    use specs::{Builder, RunNow, World};
    extern crate nalgebra;

    /// Tests the correct implementation of the `CalculateMeanTotalPhotonsScatteredSystem`
    #[test]
    fn test_calculate_mean_total_photons_scattered_system() {
        let mut test_world = World::new();

        let time_delta = 1.0e-6;

        test_world.register::<TwoLevelPopulation<Strontium88_461>>();
        test_world.register::<Strontium88_461>();
        test_world.register::<TotalPhotonsScattered<Strontium88_461>>();
        test_world.insert(Timestep { delta: time_delta });

        let mut tlp = TwoLevelPopulation::<Strontium88_461>::default();
        tlp.ground = 0.7;
        tlp.excited = 0.3;

        let atom1 = test_world
            .create_entity()
            .with(TotalPhotonsScattered::<Strontium88_461>::default())
            .with(Strontium88_461)
            .with(tlp)
            .build();

        let mut system = CalculateMeanTotalPhotonsScatteredSystem::<Strontium88_461>::default();
        system.run_now(&test_world);
        test_world.maintain();
        let sampler_storage = test_world.read_storage::<TotalPhotonsScattered<Strontium88_461>>();

        let scattered = Strontium88_461::gamma() * 0.3 * time_delta;

        assert_approx_eq!(
            sampler_storage.get(atom1).expect("entity not found").total,
            scattered,
            1e-5_f64
        );
    }

    /// Tests the correct implementation of the `CalculateExpectedPhotonsScatteredSystem`
    #[test]
    fn test_calculate_expected_photons_scattered_system() {
        let mut test_world = World::new();

        test_world.register::<RateCoefficients<Strontium88_461, { DEFAULT_BEAM_LIMIT }>>();
        test_world.register::<CoolingLaserSamplerMasks<{ DEFAULT_BEAM_LIMIT }>>();
        test_world.register::<TotalPhotonsScattered<Strontium88_461>>();
        test_world.register::<ExpectedPhotonsScatteredVector<Strontium88_461, { DEFAULT_BEAM_LIMIT }>>();

        //We assume 16 beams with equal `RateCoefficient`s for this test
        let mut rc = RateCoefficient::<Strontium88_461>::default();
        rc.rate = 1_000_000.0;
        let mut tps = TotalPhotonsScattered::<Strontium88_461>::default();
        tps.total = 8.0;

        let atom1 = test_world
            .create_entity()
            .with(tps)
            .with(CoolingLaserSamplerMasks {
                contents: [LaserSamplerMask { filled: true };
                    DEFAULT_BEAM_LIMIT],
            })
            .with(RateCoefficients {
                contents: [rc; DEFAULT_BEAM_LIMIT],
            })
            .with(ExpectedPhotonsScatteredVector {
                contents: [ExpectedPhotonsScattered::<Strontium88_461>::default(); crate::laser::DEFAULT_BEAM_LIMIT],
            })
            .build();
        let mut system = CalculateExpectedPhotonsScatteredSystem::<Strontium88_461, { DEFAULT_BEAM_LIMIT }>::default();
        system.run_now(&test_world);
        test_world.maintain();
        let sampler_storage =
            test_world.read_storage::<ExpectedPhotonsScatteredVector<Strontium88_461, { DEFAULT_BEAM_LIMIT }>>();

        let scattered = 8.0 / crate::laser::DEFAULT_BEAM_LIMIT as f64;

        assert_approx_eq!(
            sampler_storage
                .get(atom1)
                .expect("entity not found")
                .contents[12] //any entry between 0 and 15 should be the same
                .scattered,
            scattered,
            1e-5_f64
        );
    }
}