1use crate::ion::bca::{BcaTally, ElectronicChannel, EnergyBudget, Face, LatticeDeposit, Particle};
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub struct TrajectoryConfig {
49 pub max_histories: u64,
51 pub max_points: usize,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq)]
57pub struct TrajectoryPoint {
58 pub pos: [f64; 3],
60 pub energy_ev: f64,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum TrackEnd {
67 Stopped,
69 Escaped(Face),
71}
72
73#[derive(Debug, Clone, PartialEq)]
75pub struct Track {
76 pub species: usize,
78 pub generation: u32,
80 pub parent: Option<usize>,
83 pub end: Option<TrackEnd>,
85 pub points: Vec<TrajectoryPoint>,
87}
88
89#[derive(Debug, Clone, PartialEq)]
92pub struct History {
93 pub index: u64,
95 pub tracks: Vec<Track>,
97 pub truncated: bool,
99}
100
101#[derive(Debug, Clone, Copy)]
103struct Queued {
104 track: Option<usize>,
105 generation: u32,
106 pos: [f64; 3],
107 energy_ev: f64,
108}
109
110#[derive(Debug, Clone, Copy)]
114struct Current {
115 track: Option<usize>,
116 generation: u32,
117}
118
119#[derive(Debug, Clone)]
120struct Active {
121 history: History,
122 points: usize,
123 queue: Vec<Queued>,
124 cur: Current,
125}
126
127#[derive(Debug, Clone)]
130pub struct TrajectoryRecorder {
131 cfg: TrajectoryConfig,
132 pub histories: Vec<History>,
134 pub histories_skipped: u64,
136 active: Option<Active>,
137}
138
139impl TrajectoryRecorder {
140 pub fn new(cfg: TrajectoryConfig) -> Self {
142 Self {
143 cfg,
144 histories: Vec::new(),
145 histories_skipped: 0,
146 active: None,
147 }
148 }
149
150 pub fn config(&self) -> TrajectoryConfig {
152 self.cfg
153 }
154}
155
156impl Active {
157 fn push_point(&mut self, max_points: usize, pos: [f64; 3], energy_ev: f64) {
158 let Some(t) = self.cur.track else { return };
159 let track = &mut self.history.tracks[t];
160 let pt = TrajectoryPoint { pos, energy_ev };
161 if track.points.last() == Some(&pt) {
163 return;
164 }
165 if self.points >= max_points {
166 self.history.truncated = true;
167 return;
168 }
169 self.points += 1;
170 track.points.push(pt);
171 }
172
173 fn finish(&mut self, max_points: usize, p: &Particle, end: TrackEnd) {
175 if let Some(t) = self.cur.track {
176 let before = self.history.tracks[t].points.len();
177 self.push_point(max_points, p.pos, p.energy_ev);
178 let track = &mut self.history.tracks[t];
179 if track.points.len() > before || track.points.last().map(|l| l.pos) == Some(p.pos) {
181 track.end = Some(end);
182 }
183 }
184 self.cur = match self.queue.pop() {
185 Some(q) => Current {
186 track: q.track,
187 generation: q.generation,
188 },
189 None => Current {
190 track: None,
191 generation: 0,
192 },
193 };
194 }
195
196 fn take_unqueued(&mut self, p: &Particle) -> bool {
198 match self.queue.last() {
199 Some(q)
200 if q.generation == p.generation
201 && q.generation == self.cur.generation + 1
202 && q.pos == p.pos
203 && q.energy_ev == p.energy_ev =>
204 {
205 let q = self.queue.pop().expect("checked non-empty");
206 if let Some(t) = q.track {
207 self.history.tracks[t].end = Some(TrackEnd::Stopped);
208 }
209 true
210 }
211 _ => false,
212 }
213 }
214}
215
216impl BcaTally for TrajectoryRecorder {
217 fn begin_history(&mut self, index: u64) {
218 if index >= self.cfg.max_histories {
219 self.histories_skipped += 1;
220 self.active = None;
221 return;
222 }
223 self.active = Some(Active {
224 history: History {
225 index,
226 tracks: vec![Track {
227 species: 0,
228 generation: 0,
229 parent: None,
230 end: None,
231 points: Vec::new(),
232 }],
233 truncated: false,
234 },
235 points: 0,
236 queue: Vec::new(),
237 cur: Current {
238 track: Some(0),
239 generation: 0,
240 },
241 });
242 }
243
244 fn partner(&mut self, p: &Particle, _impact_parameter_m: f64, _partners: usize) {
245 let max = self.cfg.max_points;
246 if let Some(a) = &mut self.active {
247 a.push_point(max, p.pos, p.energy_ev);
248 }
249 }
250
251 fn recoil(&mut self, r: &Particle) {
252 let max = self.cfg.max_points;
253 let Some(a) = &mut self.active else { return };
254 let mut track = None;
255 if let Some(parent) = a.cur.track {
256 if a.points < max {
257 a.history.tracks.push(Track {
258 species: r.species,
259 generation: r.generation,
260 parent: Some(parent),
261 end: None,
262 points: Vec::new(),
263 });
264 track = Some(a.history.tracks.len() - 1);
265 let saved = a.cur;
267 a.cur.track = track;
268 a.push_point(max, r.pos, r.energy_ev);
269 a.cur = saved;
270 } else {
271 a.history.truncated = true;
272 }
273 }
274 a.queue.push(Queued {
275 track,
276 generation: r.generation,
277 pos: r.pos,
278 energy_ev: r.energy_ev,
279 });
280 }
281
282 fn stopped(&mut self, p: &Particle) {
283 let max = self.cfg.max_points;
284 let Some(a) = &mut self.active else { return };
285 if !a.take_unqueued(p) {
286 a.finish(max, p, TrackEnd::Stopped);
287 }
288 }
289
290 fn escaped(&mut self, p: &Particle, face: Face) {
291 let max = self.cfg.max_points;
292 if let Some(a) = &mut self.active {
293 a.finish(max, p, TrackEnd::Escaped(face));
294 }
295 }
296
297 fn end_history(&mut self, _index: u64, _budget: &EnergyBudget) {
298 if let Some(a) = self.active.take() {
299 self.histories.push(a.history);
300 }
301 }
302
303 fn merge(&mut self, other: Self) {
304 self.histories.extend(other.histories);
305 self.histories_skipped += other.histories_skipped;
306 }
307}
308
309#[derive(Debug, Clone)]
312pub struct Recorded<T> {
313 pub inner: T,
315 pub recorder: TrajectoryRecorder,
317}
318
319impl<T: BcaTally> BcaTally for Recorded<T> {
320 fn begin_history(&mut self, index: u64) {
321 self.inner.begin_history(index);
322 self.recorder.begin_history(index);
323 }
324 fn electronic(&mut self, p: &Particle, from: [f64; 3], c: ElectronicChannel, e: f64) {
325 self.inner.electronic(p, from, c, e);
326 }
327 fn partner(&mut self, p: &Particle, b: f64, n: usize) {
328 self.inner.partner(p, b, n);
329 self.recorder.partner(p, b, n);
330 }
331 fn lattice(&mut self, at: [f64; 3], layer: usize, kind: LatticeDeposit, e: f64) {
332 self.inner.lattice(at, layer, kind, e);
333 }
334 fn recoil(&mut self, r: &Particle) {
335 self.inner.recoil(r);
336 self.recorder.recoil(r);
337 }
338 fn stopped(&mut self, p: &Particle) {
339 self.inner.stopped(p);
340 self.recorder.stopped(p);
341 }
342 fn escaped(&mut self, p: &Particle, face: Face) {
343 self.inner.escaped(p, face);
344 self.recorder.escaped(p, face);
345 }
346 fn end_history(&mut self, index: u64, budget: &EnergyBudget) {
347 self.inner.end_history(index, budget);
348 self.recorder.end_history(index, budget);
349 }
350 fn merge(&mut self, other: Self) {
351 self.inner.merge(other.inner);
352 self.recorder.merge(other.recorder);
353 }
354}