Newer
Older
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
use rand::Rng;
use thiserror::Error;
use crate::{
atom::{
Atom,
AtomIter,
RadiationPattern,
State,
},
newton::{
NewtonError,
PhaseSpace,
rka,
},
trap::Trap,
};
#[derive(Error, Debug)]
pub enum ScatterError {
#[error("newton error {0}")]
NewtonError(NewtonError),
}
pub type ScatterResult<T> = Result<T, ScatterError>;
#[derive(Clone, Debug)]
pub struct ScatterHistory<S>
where S: State,
{
pub state: Vec<S>,
pub time: Vec<f64>,
pub trajectory: Vec<PhaseSpace>,
}
/// Calculate a single trajectory up to `t_final` *or* until the atom goes dark;
/// i.e. the final time is not guaranteed to be equal to `t_final`.
pub fn scatter_sim<S, T, R, G>(
atom: &Atom<S, T, R>,
t_final: f64,
epsilon: f64,
rng: &mut G,
) -> ScatterResult<(ScatterHistory<S>, Atom<S, T, R>)>
where
S: State,
T: Trap,
R: RadiationPattern,
G: Rng + ?Sized,
{
let m: f64 = atom.mass;
let mut state_history: Vec<S> = vec![atom.state];
let mut t_cur: f64 = 0.0;
let mut t: Vec<f64> = Vec::new();
let (mut atom_iter, mut q_cur): (AtomIter<S, T, R>, PhaseSpace)
= atom.to_state_iter();
let mut trajectory: Vec<PhaseSpace> = Vec::new();
// simulate until t_final or the atom goes dark
while let Some((state, photon)) = atom_iter.next() {
state_history.push(state);
match photon.momentum_kick_lifetime(rng) {
// absorb a photon
(kick, None) => {
q_cur.mom += kick;
},
// integrate position until photon emission
(kick, Some((lifetime, tau))) => {
let dt: f64
= atom_iter.get_trap().period(m)
.min(tau)
.min(lifetime)
.min(t_final - t_cur)
/ 10.0;
let t_int_end: f64 = (t_cur + lifetime).min(t_final);
let rhs
= |_t: f64, q: PhaseSpace| -> PhaseSpace {
PhaseSpace {
pos: q.mom / m,
mom: -atom_iter.get_trap().gradient(q.pos),
}
};
let (mut t_int, mut q_int): (Vec<f64>, Vec<PhaseSpace>)
= rka((t_cur, t_int_end), q_cur, dt, rhs, epsilon)
.map_err(ScatterError::NewtonError)?;
t_cur = t_int.pop().unwrap();
q_cur = q_int.pop().unwrap();
t.append(&mut t_int);
trajectory.append(&mut q_int);
q_cur.mom += kick;
},
}
if t_cur >= t_final { break; }
}
return Ok((
ScatterHistory {
state: state_history,
time: t,
trajectory,
},
atom_iter.dump(),
));
}
/// Calculate a single trajectory, recording times and positions/momenta only
/// when photons are emitted. Necessarily limited to when the atom goes dark.
pub fn scatter_sim_emission<S, T, R, G>(
atom: &Atom<S, T, R>,
t_final: f64,
epsilon: f64,
rng: &mut G,
) -> ScatterResult<(ScatterHistory<S>, Atom<S, T, R>)>
where
S: State,
T: Trap,
R: RadiationPattern,
G: Rng + ?Sized,
{
let m: f64 = atom.mass;
let mut state_history: Vec<S> = vec![atom.state];
let mut t_cur: f64 = 0.0;
let mut t: Vec<f64> = vec![0.0];
let (mut atom_iter, mut q_cur): (AtomIter<S, T, R>, PhaseSpace)
= atom.to_state_iter();
let mut trajectory: Vec<PhaseSpace> = vec![q_cur];
while let Some((state, photon)) = atom_iter.next() {
state_history.push(state);
match photon.momentum_kick_lifetime(rng) {
(kick, None) => {
q_cur.mom += kick;
},
(kick, Some((lifetime, tau))) => {
let dt: f64
= atom_iter.get_trap().period(m)
.min(tau)
.min(lifetime)
.min(t_final - t_cur)
/ 10.0;
let t_int_end: f64 = (t_cur + lifetime).min(t_final);
let rhs
= |_t: f64, q: PhaseSpace| -> PhaseSpace {
PhaseSpace {
pos: q.mom / m,
mom: -atom_iter.get_trap().gradient(q.pos),
}
};
let (mut t_int, mut q_int): (Vec<f64>, Vec<PhaseSpace>)
= rka((t_cur, t_int_end), q_cur, dt, rhs, epsilon)
.map_err(ScatterError::NewtonError)?;
t_cur = t_int.pop().unwrap();
q_cur = q_int.pop().unwrap();
t.push(t_cur);
trajectory.push(q_cur);
q_cur.mom += kick;
},
}
if t_cur >= t_final { break; }
}
return Ok((
ScatterHistory {
state: state_history,
time: t,
trajectory,
},
atom_iter.dump(),
));
}
/// Calculate a single trajectory up to `t_final`, continuing past the point at
/// which the atom goes dark.
pub fn scatter_sim_dark<S, T, R, G>(
atom: &Atom<S, T, R>,
t_final: f64,
epsilon: f64,
rng: &mut G,
) -> ScatterResult<(ScatterHistory<S>, Atom<S, T, R>)>
where
S: State,
T: Trap,
R: RadiationPattern,
G: Rng + ?Sized,
{
let (mut scatter_history, atom_sim): (ScatterHistory<S>, Atom<S, T, R>)
= scatter_sim(atom, t_final, epsilon, rng)?;
if scatter_history.time.last().unwrap_or(&0.0) < &t_final {
let t_init: f64 = scatter_history.time.pop().unwrap_or(0.0);
let q_init: PhaseSpace
= scatter_history.trajectory.pop()
.unwrap_or_else(|| atom_sim.sample_phasespace(rng));
let dt: f64
= atom_sim.get_trap().period(atom_sim.mass)
.min(t_final - t_init)
/ 10.0;
let rhs
= |_t: f64, q: PhaseSpace| -> PhaseSpace {
PhaseSpace {
pos: q.mom / atom_sim.mass,
mom: -atom_sim.get_trap().gradient(q.pos),
}
};
let (mut t_int, mut q_int): (Vec<f64>, Vec<PhaseSpace>)
= rka((t_init, t_final), q_init, dt, rhs, epsilon)
.map_err(ScatterError::NewtonError)?;
scatter_history.time.append(&mut t_int);
scatter_history.trajectory.append(&mut q_int);
}
return Ok((scatter_history, atom_sim));
}