Garfield++ v2r0
A toolkit for the detailed simulation of particle detectors based on ionisation measurement in gases and semiconductors
Loading...
Searching...
No Matches
AvalancheMicroscopic.cc
Go to the documentation of this file.
1#include <iostream>
2#include <cmath>
3#include <string>
4#include <functional>
5#include <algorithm>
6
9#include "Random.hh"
10
11namespace {
12
13void PrintStatus(const std::string& hdr, const std::string& status,
14 const double x, const double y, const double z,
15 const bool hole) {
16
17 const std::string eh = hole ? "Hole " : "Electron ";
18 std::cout << hdr << eh << status << " at " << x << ", " << y << ", " << z << "\n";
19}
20
21}
22
23namespace Garfield {
24
26 : m_sensor(NULL),
27 m_nElectrons(0),
28 m_nHoles(0),
29 m_nIons(0),
30 m_usePlotting(false),
31 m_viewer(NULL),
32 m_plotExcitations(true),
33 m_plotIonisations(true),
34 m_plotAttachments(true),
35 m_histElectronEnergy(NULL),
36 m_histHoleEnergy(NULL),
37 m_histDistance(NULL),
38 m_distanceOption('r'),
39 m_histSecondary(NULL),
40 m_useSignal(false),
41 m_useInducedCharge(false),
42 m_useDriftLines(false),
43 m_usePhotons(false),
44 m_useBandStructureDefault(true),
45 m_useNullCollisionSteps(false),
46 m_useBfield(false),
47 m_rb11(1.),
48 m_rb12(0.),
49 m_rb13(0.),
50 m_rb21(0.),
51 m_rb22(1.),
52 m_rb23(0.),
53 m_rb31(0.),
54 m_rb32(0.),
55 m_rb33(1.),
56 m_rx22(1.),
57 m_rx23(0.),
58 m_rx32(0.),
59 m_rx33(1.),
60 m_deltaCut(0.),
61 m_gammaCut(0.),
62 m_sizeCut(0),
63 m_nCollSkip(100),
64 m_hasTimeWindow(false),
65 m_tMin(0.),
66 m_tMax(0.),
67 m_hasUserHandleStep(false),
68 m_hasUserHandleAttachment(false),
69 m_hasUserHandleInelastic(false),
70 m_hasUserHandleIonisation(false),
71 m_userHandleStep(0),
72 m_userHandleAttachment(0),
73 m_userHandleInelastic(0),
74 m_userHandleIonisation(0),
75 m_debug(false) {
76
77 m_className = "AvalancheMicroscopic";
78
79 m_endpointsElectrons.reserve(10000);
80 m_endpointsHoles.reserve(10000);
81 m_photons.reserve(1000);
82
83}
84
86
87 if (!s) {
88 std::cerr << m_className << "::SetSensor:\n Null pointer.\n";
89 return;
90 }
91 m_sensor = s;
92}
93
95
96 if (!view) {
97 std::cerr << m_className << "::EnablePlotting:\n Null pointer.\n";
98 return;
99 }
100
101 m_viewer = view;
102 m_usePlotting = true;
103 if (!m_useDriftLines) {
104 std::cout << m_className << "::EnablePlotting:\n"
105 << " Enabling storage of drift line.\n";
107 }
108}
109
111
112 m_viewer = NULL;
113 m_usePlotting = false;
114}
115
117
118 if (!histo) {
119 std::cerr << m_className << "::EnableElectronEnergyHistogramming:\n"
120 << " Null pointer.\n";
121 return;
122 }
123
124 m_histElectronEnergy = histo;
125}
126
128
129 if (!histo) {
130 std::cerr << m_className << "::EnableHoleEnergyHistogramming:\n"
131 << " Null pointer.\n";
132 return;
133 }
134
135 m_histHoleEnergy = histo;
136}
137
138void AvalancheMicroscopic::SetDistanceHistogram(TH1* histo, const char opt) {
139
140 if (!histo) {
141 std::cerr << m_className << "::SetDistanceHistogram:\n Null pointer.\n";
142 return;
143 }
144
145 m_histDistance = histo;
146
147 if (opt == 'x' || opt == 'y' || opt == 'z' || opt == 'r') {
148 m_distanceOption = opt;
149 } else {
150 std::cerr << m_className << "::SetDistanceHistogram:";
151 std::cerr << " Unknown option " << opt << ".\n";
152 std::cerr << " Valid options are x, y, z, r.\n";
153 std::cerr << " Using default value (r).\n";
154 m_distanceOption = 'r';
155 }
156
157 if (m_distanceHistogramType.empty()) {
158 std::cout << m_className << "::SetDistanceHistogram:\n";
159 std::cout << " Don't forget to call EnableDistanceHistogramming.\n";
160 }
161}
162
164
165 // Check if this type of collision is already registered
166 // for histogramming.
167 const unsigned int nDistanceHistogramTypes = m_distanceHistogramType.size();
168 if (nDistanceHistogramTypes > 0) {
169 for (unsigned int i = 0; i < nDistanceHistogramTypes; ++i) {
170 if (m_distanceHistogramType[i] != type) continue;
171 std::cout << m_className << "::EnableDistanceHistogramming:\n";
172 std::cout << " Collision type " << type
173 << " is already being histogrammed.\n";
174 return;
175 }
176 }
177
178 m_distanceHistogramType.push_back(type);
179 std::cout << m_className << "::EnableDistanceHistogramming:\n";
180 std::cout << " Histogramming of collision type " << type << " enabled.\n";
181 if (!m_histDistance) {
182 std::cout << " Don't forget to set the histogram.\n";
183 }
184}
185
187
188 if (m_distanceHistogramType.empty()) {
189 std::cerr << m_className << "::DisableDistanceHistogramming:\n";
190 std::cerr << " Collision type " << type << " is not histogrammed.\n";
191 return;
192 }
193 const unsigned int nDistanceHistogramTypes = m_distanceHistogramType.size();
194 for (unsigned int i = 0; i < nDistanceHistogramTypes; ++i) {
195 if (m_distanceHistogramType[i] != type) continue;
196 m_distanceHistogramType.erase(m_distanceHistogramType.begin() + i);
197 std::cout << " Histogramming of collision type " << type
198 << " disabled.\n";
199 return;
200 }
201
202 std::cerr << m_className << "::DisableDistanceHistogramming:\n";
203 std::cerr << " Collision type " << type << " is not histogrammed.\n";
204}
205
207
208 m_histDistance = NULL;
209 m_distanceHistogramType.clear();
210}
211
213
214 if (!histo) {
215 std::cerr << m_className << "::EnableSecondaryEnergyHistogramming:\n"
216 << " Null pointer.\n";
217 return;
218 }
219
220 m_histSecondary = histo;
221}
222
223void AvalancheMicroscopic::SetTimeWindow(const double t0, const double t1) {
224
225 if (fabs(t1 - t0) < Small) {
226 std::cerr << m_className << "::SetTimeWindow:\n";
227 std::cerr << " Time interval must be greater than zero.\n";
228 return;
229 }
230
231 m_tMin = std::min(t0, t1);
232 m_tMax = std::max(t0, t1);
233 m_hasTimeWindow = true;
234}
235
236void AvalancheMicroscopic::UnsetTimeWindow() { m_hasTimeWindow = false; }
237
238void AvalancheMicroscopic::GetElectronEndpoint(const unsigned int i, double& x0,
239 double& y0, double& z0,
240 double& t0, double& e0,
241 double& x1, double& y1,
242 double& z1, double& t1,
243 double& e1, int& status) const {
244
245 if (i >= m_endpointsElectrons.size()) {
246 std::cerr << m_className << "::GetElectronEndpoint:\n";
247 std::cerr << " Endpoint index " << i << " out of range.\n";
248 x0 = y0 = z0 = t0 = e0 = 0.;
249 x1 = y1 = z1 = t1 = e1 = 0.;
250 status = 0;
251 return;
252 }
253
254 x0 = m_endpointsElectrons[i].x0;
255 y0 = m_endpointsElectrons[i].y0;
256 z0 = m_endpointsElectrons[i].z0;
257 t0 = m_endpointsElectrons[i].t0;
258 e0 = m_endpointsElectrons[i].e0;
259 x1 = m_endpointsElectrons[i].x;
260 y1 = m_endpointsElectrons[i].y;
261 z1 = m_endpointsElectrons[i].z;
262 t1 = m_endpointsElectrons[i].t;
263 e1 = m_endpointsElectrons[i].energy;
264 status = m_endpointsElectrons[i].status;
265}
266
268 const unsigned int i, double& x0, double& y0, double& z0, double& t0, double& e0,
269 double& x1, double& y1, double& z1, double& t1, double& e1, double& dx1,
270 double& dy1, double& dz1, int& status) const {
271
272 if (i >= m_endpointsElectrons.size()) {
273 std::cerr << m_className << "::GetElectronEndpoint:\n";
274 std::cerr << " Endpoint index " << i << " out of range.\n";
275 x0 = y0 = z0 = t0 = e0 = 0.;
276 x1 = y1 = z1 = t1 = e1 = 0.;
277 dx1 = dy1 = dz1 = 0.;
278 status = 0;
279 return;
280 }
281
282 x0 = m_endpointsElectrons[i].x0;
283 y0 = m_endpointsElectrons[i].y0;
284 z0 = m_endpointsElectrons[i].z0;
285 t0 = m_endpointsElectrons[i].t0;
286 e0 = m_endpointsElectrons[i].e0;
287 x1 = m_endpointsElectrons[i].x;
288 y1 = m_endpointsElectrons[i].y;
289 z1 = m_endpointsElectrons[i].z;
290 t1 = m_endpointsElectrons[i].t;
291 e1 = m_endpointsElectrons[i].energy;
292 dx1 = m_endpointsElectrons[i].kx;
293 dy1 = m_endpointsElectrons[i].ky;
294 dz1 = m_endpointsElectrons[i].kz;
295 status = m_endpointsElectrons[i].status;
296}
297
298void AvalancheMicroscopic::GetHoleEndpoint(const unsigned int i, double& x0, double& y0,
299 double& z0, double& t0, double& e0,
300 double& x1, double& y1, double& z1,
301 double& t1, double& e1,
302 int& status) const {
303
304 if (i >= m_endpointsHoles.size()) {
305 std::cerr << m_className << "::GetHoleEndpoint:\n";
306 std::cerr << " Endpoint index " << i << " out of range.\n";
307 x0 = y0 = z0 = t0 = e0 = 0.;
308 x1 = y1 = z1 = t1 = e1 = 0.;
309 status = 0;
310 return;
311 }
312
313 x0 = m_endpointsHoles[i].x0;
314 y0 = m_endpointsHoles[i].y0;
315 z0 = m_endpointsHoles[i].z0;
316 t0 = m_endpointsHoles[i].t0;
317 e0 = m_endpointsHoles[i].e0;
318 x1 = m_endpointsHoles[i].x;
319 y1 = m_endpointsHoles[i].y;
320 z1 = m_endpointsHoles[i].z;
321 t1 = m_endpointsHoles[i].t;
322 e1 = m_endpointsHoles[i].energy;
323 status = m_endpointsHoles[i].status;
324}
325
327 const {
328
329 if (i >= m_endpointsElectrons.size()) {
330 std::cerr << m_className << "::GetNumberOfElectronDriftLinePoints:\n";
331 std::cerr << " Endpoint index (" << i << ") out of range.\n";
332 return 0;
333 }
334
335 if (!m_useDriftLines) return 2;
336
337 return m_endpointsElectrons[i].driftLine.size() + 2;
338}
339
340unsigned int AvalancheMicroscopic::GetNumberOfHoleDriftLinePoints(const unsigned int i) const {
341
342 if (i >= m_endpointsHoles.size()) {
343 std::cerr << m_className << "::GetNumberOfHoleDriftLinePoints:\n";
344 std::cerr << " Endpoint index (" << i << ") out of range.\n";
345 return 0;
346 }
347
348 if (!m_useDriftLines) return 2;
349
350 return m_endpointsHoles[i].driftLine.size() + 2;
351}
352
354 double& z, double& t,
355 const int ip,
356 const unsigned int iel) const {
357
358 if (iel >= m_endpointsElectrons.size()) {
359 std::cerr << m_className << "::GetElectronDriftLinePoint:\n";
360 std::cerr << " Endpoint index (" << iel << ") out of range.\n";
361 return;
362 }
363
364 if (ip <= 0) {
365 x = m_endpointsElectrons[iel].x0;
366 y = m_endpointsElectrons[iel].y0;
367 z = m_endpointsElectrons[iel].z0;
368 t = m_endpointsElectrons[iel].t0;
369 return;
370 }
371
372 const int np = m_endpointsElectrons[iel].driftLine.size();
373 if (ip > np) {
374 x = m_endpointsElectrons[iel].x;
375 y = m_endpointsElectrons[iel].y;
376 z = m_endpointsElectrons[iel].z;
377 t = m_endpointsElectrons[iel].t;
378 return;
379 }
380
381 x = m_endpointsElectrons[iel].driftLine[ip - 1].x;
382 y = m_endpointsElectrons[iel].driftLine[ip - 1].y;
383 z = m_endpointsElectrons[iel].driftLine[ip - 1].z;
384 t = m_endpointsElectrons[iel].driftLine[ip - 1].t;
385}
386
388 double& z, double& t,
389 const int ip,
390 const unsigned int ih) const {
391
392 if (ih >= m_endpointsHoles.size()) {
393 std::cerr << m_className << "::GetHoleDriftLinePoint:\n";
394 std::cerr << " Endpoint index (" << ih << ") out of range.\n";
395 return;
396 }
397
398 if (ip <= 0) {
399 x = m_endpointsHoles[ih].x0;
400 y = m_endpointsHoles[ih].y0;
401 z = m_endpointsHoles[ih].z0;
402 t = m_endpointsHoles[ih].t0;
403 return;
404 }
405
406 const int np = m_endpointsHoles[ih].driftLine.size();
407 if (ip > np) {
408 x = m_endpointsHoles[ih].x;
409 y = m_endpointsHoles[ih].y;
410 z = m_endpointsHoles[ih].z;
411 t = m_endpointsHoles[ih].t;
412 return;
413 }
414
415 x = m_endpointsHoles[ih].driftLine[ip - 1].x;
416 y = m_endpointsHoles[ih].driftLine[ip - 1].y;
417 z = m_endpointsHoles[ih].driftLine[ip - 1].z;
418 t = m_endpointsHoles[ih].driftLine[ip - 1].t;
419}
420
421void AvalancheMicroscopic::GetPhoton(const unsigned int i, double& e, double& x0,
422 double& y0, double& z0, double& t0,
423 double& x1, double& y1, double& z1,
424 double& t1, int& status) const {
425
426 if (i >= m_photons.size()) {
427 std::cerr << m_className << "::GetPhoton:\n Index out of range.\n";
428 return;
429 }
430
431 x0 = m_photons[i].x0;
432 x1 = m_photons[i].x1;
433 y0 = m_photons[i].y0;
434 y1 = m_photons[i].y1;
435 z0 = m_photons[i].z0;
436 z1 = m_photons[i].z1;
437 t0 = m_photons[i].t0;
438 t1 = m_photons[i].t1;
439 status = m_photons[i].status;
440 e = m_photons[i].energy;
441}
442
444 void (*f)(double x, double y, double z, double t, double e, double dx,
445 double dy, double dz, bool hole)) {
446
447 if (!f) {
448 std::cerr << m_className << "::SetUserHandleStep:\n";
449 std::cerr << " Function pointer is a null pointer.\n";
450 return;
451 }
452 m_userHandleStep = f;
453 m_hasUserHandleStep = true;
454}
455
457
458 m_userHandleStep = 0;
459 m_hasUserHandleStep = false;
460}
461
463 double x, double y, double z, double t, int type, int level, Medium* m)) {
464
465 m_userHandleAttachment = f;
466 m_hasUserHandleAttachment = true;
467}
468
470
471 m_userHandleAttachment = 0;
472 m_hasUserHandleAttachment = false;
473}
474
476 double x, double y, double z, double t, int type, int level, Medium* m)) {
477
478 m_userHandleInelastic = f;
479 m_hasUserHandleInelastic = true;
480}
481
483
484 m_userHandleInelastic = 0;
485 m_hasUserHandleInelastic = false;
486}
487
489 double x, double y, double z, double t, int type, int level, Medium* m)) {
490
491 m_userHandleIonisation = f;
492 m_hasUserHandleIonisation = true;
493}
494
496
497 m_userHandleIonisation = 0;
498 m_hasUserHandleIonisation = false;
499}
500
501bool AvalancheMicroscopic::DriftElectron(const double x0, const double y0,
502 const double z0, const double t0,
503 const double e0, const double dx0,
504 const double dy0, const double dz0) {
505
506 // Clear the list of electrons and photons.
507 m_endpointsElectrons.clear();
508 m_endpointsHoles.clear();
509 m_photons.clear();
510
511 // Reset the particle counters.
512 m_nElectrons = m_nHoles = m_nIons = 0;
513
514 return TransportElectron(x0, y0, z0, t0, e0, dx0, dy0, dz0, false, false);
515}
516
517bool AvalancheMicroscopic::AvalancheElectron(const double x0, const double y0,
518 const double z0, const double t0,
519 const double e0, const double dx0,
520 const double dy0,
521 const double dz0) {
522
523 // Clear the list of electrons, holes and photons.
524 m_endpointsElectrons.clear();
525 m_endpointsHoles.clear();
526 m_photons.clear();
527
528 // Reset the particle counters.
529 m_nElectrons = m_nHoles = m_nIons = 0;
530
531 return TransportElectron(x0, y0, z0, t0, e0, dx0, dy0, dz0, true, false);
532}
533
534bool AvalancheMicroscopic::TransportElectron(const double x0, const double y0,
535 const double z0, const double t0,
536 double e0, const double dx0,
537 const double dy0, const double dz0,
538 const bool aval, bool hole0) {
539
540 const std::string hdr = m_className + "::TransportElectron: ";
541 // Make sure that the sensor is defined.
542 if (!m_sensor) {
543 std::cerr << hdr << "Sensor is not defined.\n";
544 return false;
545 }
546
547 // Make sure that the starting point is inside a medium.
548 Medium* medium = NULL;
549 if (!m_sensor->GetMedium(x0, y0, z0, medium) || !medium) {
550 std::cerr << hdr << "No medium at initial position.\n";
551 return false;
552 }
553
554 // Make sure that the medium is "driftable" and microscopic.
555 if (!medium->IsDriftable() || !medium->IsMicroscopic()) {
556 std::cerr << hdr << "Medium does not have cross-section data.\n";
557 return false;
558 }
559
560 // If the medium is a semiconductor, we may use "band structure" stepping.
561 bool useBandStructure = medium->IsSemiconductor() && m_useBandStructureDefault;
562 if (m_debug) {
563 std::cout << hdr << "Start drifting in medium "
564 << medium->GetName() << ".\n";
565 }
566
567 // Get the id number of the drift medium.
568 int id = medium->GetId();
569
570 // Numerical prefactors in equation of motion
571 const double c1 = SpeedOfLight * sqrt(2. / ElectronMass);
572 const double c2 = c1 * c1 / 4.;
573
574 // Electric and magnetic field
575 double ex = 0., ey = 0., ez = 0.;
576 double bx = 0., by = 0., bz = 0., bmag = 0.;
577 int status = 0;
578 // Cyclotron frequency
579 double cwt = 1., swt = 0.;
580 double wb = 0.;
581 // Flag indicating if magnetic field is usable
582 bool bOk = true;
583
584 // Direction, velocity and energy after a step
585 double newKx = 0., newKy = 0., newKz = 0.;
586 double newVx = 0., newVy = 0., newVz = 0.;
587 double newEnergy = 0.;
588
589 // Numerical factors
590 double a1 = 0., a2 = 0., a3 = 0., a4 = 0.;
591
592 // Make sure the initial energy is positive.
593 e0 = std::max(e0, Small);
594
595 std::vector<Electron> stackOld;
596 std::vector<Electron> stackNew;
597 stackOld.reserve(10000);
598 stackNew.reserve(1000);
599 std::vector<std::pair<double, double> > stackPhotons;
600
601 // Put the initial electron on the stack.
602 if (useBandStructure) {
603 // With band structure, (kx, ky, kz) represents the momentum.
604 // No normalization in this case.
605 int band = -1;
606 double kx = 0., ky = 0., kz = 0.;
607 medium->GetElectronMomentum(e0, kx, ky, kz, band);
608 AddToStack(x0, y0, z0, t0, e0, kx, ky, kz, band, hole0, stackOld);
609 } else {
610 double kx = dx0;
611 double ky = dy0;
612 double kz = dz0;
613 // Check the given initial direction.
614 const double k = sqrt(kx * kx + ky * ky + kz * kz);
615 if (fabs(k) < Small) {
616 // Direction has zero norm, draw a random direction.
617 RndmDirection(kx, ky, kz);
618 } else {
619 // Normalise the direction to 1.
620 kx /= k;
621 ky /= k;
622 kz /= k;
623 }
624 AddToStack(x0, y0, z0, t0, e0, kx, ky, kz, 0, hole0, stackOld);
625 }
626 if (hole0) {
627 ++m_nHoles;
628 } else {
629 ++m_nElectrons;
630 }
631
632 // Get the null-collision rate.
633 double fLim = medium->GetElectronNullCollisionRate(stackOld.front().band);
634 if (fLim <= 0.) {
635 std::cerr << hdr << "Got null-collision rate <= 0.\n";
636 return false;
637 }
638 double fInv = 1. / fLim;
639
640 while (true) {
641 // Remove all inactive items from the stack.
642 stackOld.erase(std::remove_if(stackOld.begin(), stackOld.end(), IsInactive),
643 stackOld.end());
644 // Add the electrons produced in the last iteration.
645 if (aval && m_sizeCut > 0) {
646 // If needed, reduce the number of electrons to add.
647 if (stackOld.size() > m_sizeCut) {
648 stackNew.clear();
649 } else if (stackOld.size() + stackNew.size() > m_sizeCut) {
650 stackNew.resize(m_sizeCut - stackOld.size());
651 }
652 }
653 stackOld.insert(stackOld.end(), stackNew.begin(), stackNew.end());
654 stackNew.clear();
655 // If the list of electrons/holes is exhausted, we're done.
656 if (stackOld.empty()) break;
657 // Loop over all electrons/holes in the avalanche.
658 const std::vector<Electron>::const_iterator end = stackOld.end();
659 std::vector<Electron>::iterator it;
660 for (it = stackOld.begin(); it != end; ++it) {
661 // Get an electron/hole from the stack.
662 double x = (*it).x;
663 double y = (*it).y;
664 double z = (*it).z;
665 double t = (*it).t;
666 double energy = (*it).energy;
667 int band = (*it).band;
668 double kx = (*it).kx;
669 double ky = (*it).ky;
670 double kz = (*it).kz;
671 bool hole = (*it).hole;
672
673 bool ok = true;
674
675 // Count number of collisions between updates.
676 unsigned int nCollTemp = 0;
677
678 // Get the local electric field and medium.
679 m_sensor->ElectricField(x, y, z, ex, ey, ez, medium, status);
680 // Sign change for electrons.
681 if (!hole) {
682 ex = -ex;
683 ey = -ey;
684 ez = -ez;
685 }
686
687 if (m_debug) {
688 const std::string eh = hole ? "hole " : "electron ";
689 std::cout << hdr << "\n Drifting " << eh << it - stackOld.begin()
690 << ".\n Field [V/cm] at (" << x << ", " << y << ", " << z
691 << "): " << ex << ", " << ey << ", " << ez << "\n Status: "
692 << status << "\n";
693 if (medium) std::cout << " Medium: " << medium->GetName() << "\n";
694 }
695
696 if (status != 0) {
697 // Electron is not inside a drift medium.
698 Update(it, x, y, z, t, energy, kx, ky, kz, band);
699 (*it).status = StatusLeftDriftMedium;
700 AddToEndPoints(*it, hole);
701 if (m_debug) PrintStatus(hdr, "left the drift medium", x, y, z, hole);
702 continue;
703 }
704
705 // If switched on, get the local magnetic field.
706 if (m_useBfield) {
707 m_sensor->MagneticField(x, y, z, bx, by, bz, status);
708 const double scale = hole ? Tesla2Internal : -Tesla2Internal;
709 bx *= scale;
710 by *= scale;
711 bz *= scale;
712 // Make sure that neither E nor B are zero.
713 bmag = sqrt(bx * bx + by * by + bz * bz);
714 const double emag2 = ex * ex + ey * ey + ez * ez;
715 bOk = (bmag > Small && emag2 > Small);
716 }
717
718 // Trace the electron/hole.
719 while (1) {
720
721 bool isNullCollision = false;
722
723 // Make sure the electron energy exceeds the transport cut.
724 if (energy < m_deltaCut) {
725 Update(it, x, y, z, t, energy, kx, ky, kz, band);
726 (*it).status = StatusBelowTransportCut;
727 AddToEndPoints(*it, hole);
728 if (m_debug) {
729 std::cout << hdr << "Kinetic energy (" << energy
730 << ") below transport cut.\n";
731 }
732 ok = false;
733 break;
734 }
735
736 // Fill the energy distribution histogram.
737 if (hole && m_histHoleEnergy) {
738 m_histHoleEnergy->Fill(energy);
739 } else if (!hole && m_histElectronEnergy) {
740 m_histElectronEnergy->Fill(energy);
741 }
742
743 // Check if the electrons is within the specified time window.
744 if (m_hasTimeWindow && (t < m_tMin || t > m_tMax)) {
745 Update(it, x, y, z, t, energy, kx, ky, kz, band);
746 (*it).status = StatusOutsideTimeWindow;
747 AddToEndPoints(*it, hole);
748 if (m_debug) PrintStatus(hdr, "left the time window", x, y, z, hole);
749 ok = false;
750 break;
751 }
752
753 if (medium->GetId() != id) {
754 // Medium has changed.
755 if (!medium->IsMicroscopic()) {
756 // Electron/hole has left the microscopic drift medium.
757 Update(it, x, y, z, t, energy, kx, ky, kz, band);
758 (*it).status = StatusLeftDriftMedium;
759 AddToEndPoints(*it, hole);
760 ok = false;
761 if (m_debug) {
762 std::cout << hdr << "\n Medium at "
763 << x << ", " << y << ", " << z
764 << " does not have cross-section data.\n";
765 }
766 break;
767 }
768 id = medium->GetId();
769 useBandStructure = (medium->IsSemiconductor() && m_useBandStructureDefault);
770 // Update the null-collision rate.
771 fLim = medium->GetElectronNullCollisionRate(band);
772 if (fLim <= 0.) {
773 std::cerr << hdr << "Got null-collision rate <= 0.\n";
774 return false;
775 }
776 fInv = 1. / fLim;
777 }
778
779 double vx = 0., vy = 0., vz = 0.;
780 if (m_useBfield && bOk) {
781 // Calculate the cyclotron frequency.
782 wb = OmegaCyclotronOverB * bmag;
783 // Rotate the direction vector into the local coordinate system.
784 ComputeRotationMatrix(bx, by, bz, bmag, ex, ey, ez);
785 RotateGlobal2Local(kx, ky, kz);
786 // Calculate the electric field in the rotated system.
787 RotateGlobal2Local(ex, ey, ez);
788 // Calculate the velocity vector in the local frame.
789 const double v = c1 * sqrt(energy);
790 vx = v * kx;
791 vy = v * ky;
792 vz = v * kz;
793 a1 = vx * ex;
794 a2 = c2 * ex * ex;
795 a3 = ez / bmag - vy;
796 a4 = (ez / wb);
797 } else if (useBandStructure) {
798 energy = medium->GetElectronEnergy(kx, ky, kz, vx, vy, vz, band);
799 } else {
800 // No band structure, no magnetic field.
801 // Calculate the velocity vector.
802 const double v = c1 * sqrt(energy);
803 vx = v * kx;
804 vy = v * ky;
805 vz = v * kz;
806
807 a1 = vx * ex + vy * ey + vz * ez;
808 a2 = c2 * (ex * ex + ey * ey + ez * ez);
809 }
810
811 if (m_hasUserHandleStep) {
812 m_userHandleStep(x, y, z, t, energy, kx, ky, kz, hole);
813 }
814
815 // Determine the timestep.
816 double dt = 0.;
817 while (1) {
818 // Sample the flight time.
819 const double r = RndmUniformPos();
820 dt += -log(r) * fInv;
821 // Calculate the energy after the proposed step.
822 if (m_useBfield && bOk) {
823 cwt = cos(wb * dt);
824 swt = sin(wb * dt);
825 newEnergy = std::max(energy + (a1 + a2 * dt) * dt +
826 a4 * (a3 * (1. - cwt) + vz * swt),
827 Small);
828 } else if (useBandStructure) {
829 const double cdt = dt * SpeedOfLight;
830 newEnergy = std::max(
831 medium->GetElectronEnergy(kx + ex * cdt, ky + ey * cdt,
832 kz + ez * cdt, newVx, newVy, newVz, band),
833 Small);
834 } else {
835 newEnergy = std::max(energy + (a1 + a2 * dt) * dt, Small);
836 }
837 // Get the real collision rate at the updated energy.
838 double fReal = medium->GetElectronCollisionRate(newEnergy, band);
839 if (fReal <= 0.) {
840 std::cerr << hdr << "Got collision rate <= 0 at " << newEnergy
841 << " eV (band " << band << ").\n";
842 return false;
843 }
844 if (fReal > fLim) {
845 // Real collision rate is higher than null-collision rate.
846 dt += log(r) * fInv;
847 // Increase the null collision rate and try again.
848 std::cerr << hdr << "Increasing null-collision rate by 5%.\n";
849 if (useBandStructure) std::cerr << " Band " << band << "\n";
850 fLim *= 1.05;
851 fInv = 1. / fLim;
852 continue;
853 }
854 // Check for real or null collision.
855 if (RndmUniform() <= fReal * fInv) break;
856 if (m_useNullCollisionSteps) {
857 isNullCollision = true;
858 break;
859 }
860 }
861 if (!ok) break;
862
863 // Increase the collision counter.
864 ++nCollTemp;
865
866 // Update the directions (at instant before collision)
867 // and calculate the proposed new position.
868 if (m_useBfield && bOk) {
869 // Calculate the new velocity.
870 newVx = vx + 2. * c2 * ex * dt;
871 newVy = vz * swt - a3 * cwt + ez / bmag;
872 newVz = vz * cwt + a3 * swt;
873 // Normalise and rotate back to the lab frame.
874 const double v = sqrt(newVx * newVx + newVy * newVy + newVz * newVz);
875 newKx = newVx / v;
876 newKy = newVy / v;
877 newKz = newVz / v;
878 RotateLocal2Global(newKx, newKy, newKz);
879 // Calculate the step in coordinate space.
880 vx += c2 * ex * dt;
881 ky = (vz * (1. - cwt) - a3 * swt) / (wb * dt) + ez / bmag;
882 kz = (vz * swt + a3 * (1. - cwt)) / (wb * dt);
883 vy = ky;
884 vz = kz;
885 // Rotate back to the lab frame.
886 RotateLocal2Global(vx, vy, vz);
887 } else if (useBandStructure) {
888 // Update the wave-vector.
889 newKx = kx + ex * dt * SpeedOfLight;
890 newKy = ky + ey * dt * SpeedOfLight;
891 newKz = kz + ez * dt * SpeedOfLight;
892 // Average velocity over the step.
893 vx = 0.5 * (vx + newVx);
894 vy = 0.5 * (vy + newVy);
895 vz = 0.5 * (vz + newVz);
896 } else {
897 // Update the direction.
898 a1 = sqrt(energy / newEnergy);
899 a2 = 0.5 * c1 * dt / sqrt(newEnergy);
900 newKx = kx * a1 + ex * a2;
901 newKy = ky * a1 + ey * a2;
902 newKz = kz * a1 + ez * a2;
903
904 // Calculate the step in coordinate space.
905 a1 = c1 * sqrt(energy);
906 a2 = dt * c2;
907 vx = kx * a1 + ex * a2;
908 vy = ky * a1 + ey * a2;
909 vz = kz * a1 + ez * a2;
910 }
911
912 double x1 = x + vx * dt;
913 double y1 = y + vy * dt;
914 double z1 = z + vz * dt;
915 double t1 = t + dt;
916 // Get the electric field and medium at the proposed new position.
917 m_sensor->ElectricField(x1, y1, z1, ex, ey, ez, medium, status);
918 if (!hole) {
919 ex = -ex;
920 ey = -ey;
921 ez = -ez;
922 }
923
924 // Check if the electron is still inside a drift medium/the drift area.
925 if (status != 0 || !m_sensor->IsInArea(x1, y1, z1)) {
926 // Try to terminate the drift line close to the boundary (endpoint
927 // outside the drift medium/drift area) using iterative bisection.
928 Terminate(x, y, z, t, x1, y1, z1, t1);
929 if (m_useSignal) {
930 const int q = hole ? 1 : -1;
931 m_sensor->AddSignal(q, t, t1 - t,
932 0.5 * (x + x1), 0.5 * (y + y1),
933 0.5 * (z + z1), vx, vy, vz);
934 }
935 Update(it, x1, y1, z1, t1, energy, newKx, newKy, newKz, band);
936 if (status != 0) {
937 (*it).status = StatusLeftDriftMedium;
938 if (m_debug) PrintStatus(hdr, "left the drift medium", x1, y1, z1, hole);
939 } else {
940 (*it).status = StatusLeftDriftArea;
941 if (m_debug) PrintStatus(hdr, "left the drift area", x1, y1, z1, hole);
942 }
943 AddToEndPoints(*it, hole);
944 ok = false;
945 break;
946 }
947
948 // Check if the electron/hole has crossed a wire.
949 double xc = x, yc = y, zc = z;
950 if (m_sensor->IsWireCrossed(x, y, z, x1, y1, z1, xc, yc, zc)) {
951 // If switched on, calculated the induced signal over this step.
952 if (m_useSignal) {
953 const double dx = xc - x;
954 const double dy = yc - y;
955 const double dz = zc - z;
956 dt = sqrt(dx * dx + dy * dy + dz * dz) /
957 sqrt(vx * vx + vy * vy + vz * vz);
958 const int q = hole ? 1 : -1;
959 m_sensor->AddSignal(q, t, dt, 0.5 * (x + xc), 0.5 * (y + yc),
960 0.5 * (z + zc), vx, vy, vz);
961 }
962 Update(it, xc, yc, zc, t + dt, energy, newKx, newKy, newKz, band);
963 (*it).status = StatusLeftDriftMedium;
964 AddToEndPoints(*it, hole);
965 ok = false;
966 if (m_debug) PrintStatus(hdr, "hit a wire", x, y, z, hole);
967 break;
968 }
969
970 // If switched on, calculate the induced signal.
971 if (m_useSignal) {
972 const int q = hole ? 1 : -1;
973 m_sensor->AddSignal(q, t, dt, 0.5 * (x + x1), 0.5 * (y + y1),
974 0.5 * (z + z1), vx, vy, vz);
975 }
976
977 // Update the coordinates.
978 x = x1;
979 y = y1;
980 z = z1;
981 t = t1;
982
983 // If switched on, get the magnetic field at the new location.
984 if (m_useBfield) {
985 m_sensor->MagneticField(x, y, z, bx, by, bz, status);
986 const double scale = hole ? Tesla2Internal : -Tesla2Internal;
987 bx *= scale;
988 by *= scale;
989 bz *= scale;
990 // Make sure that neither E nor B are zero.
991 bmag = sqrt(bx * bx + by * by + bz * bz);
992 const double emag2 = ex * ex + ey * ey + ez * ez;
993 bOk = (bmag > Small && emag2 > Small);
994 }
995
996 if (isNullCollision) {
997 energy = newEnergy;
998 kx = newKx;
999 ky = newKy;
1000 kz = newKz;
1001 continue;
1002 }
1003
1004 // Get the collision type and parameters.
1005 int cstype = 0;
1006 int level = 0;
1007 int nion = 0;
1008 int ndxc = 0;
1009 medium->GetElectronCollision(newEnergy, cstype, level, energy, newKx,
1010 newKy, newKz, nion, ndxc, band);
1011
1012 // If activated, histogram the distance with respect to the
1013 // last collision.
1014 if (m_histDistance && !m_distanceHistogramType.empty()) {
1015 const int nDistanceHistogramTypes = m_distanceHistogramType.size();
1016 for (int iType = nDistanceHistogramTypes; iType--;) {
1017 if (m_distanceHistogramType[iType] != cstype) continue;
1018 if (m_debug) {
1019 std::cout << m_className << "::TransportElectron:\n";
1020 std::cout << " Collision type: " << cstype << "\n";
1021 std::cout << " Fill distance histogram.\n";
1022 getchar();
1023 }
1024 switch (m_distanceOption) {
1025 case 'x':
1026 m_histDistance->Fill((*it).xLast - x);
1027 break;
1028 case 'y':
1029 m_histDistance->Fill((*it).yLast - y);
1030 break;
1031 case 'z':
1032 m_histDistance->Fill((*it).zLast - z);
1033 break;
1034 case 'r':
1035 const double r2 = pow((*it).xLast - x, 2) +
1036 pow((*it).yLast - y, 2) +
1037 pow((*it).zLast - z, 2);
1038 m_histDistance->Fill(sqrt(r2));
1039 break;
1040 }
1041 (*it).xLast = x;
1042 (*it).yLast = y;
1043 (*it).zLast = z;
1044 break;
1045 }
1046 }
1047
1048 switch (cstype) {
1049 // Elastic collision
1050 case ElectronCollisionTypeElastic:
1051 break;
1052 // Ionising collision
1053 case ElectronCollisionTypeIonisation:
1054 if (m_usePlotting && m_plotIonisations) {
1055 m_viewer->AddIonisationMarker(x, y, z);
1056 }
1057 if (m_hasUserHandleIonisation) {
1058 m_userHandleIonisation(x, y, z, t, cstype, level, medium);
1059 }
1060 for (int j = nion; j--;) {
1061 int itype;
1062 double esec;
1063 medium->GetIonisationProduct(j, itype, esec);
1064 if (itype == IonProdTypeElectron) {
1065 esec = std::max(esec, Small);
1066 if (m_histSecondary) m_histSecondary->Fill(esec);
1067 // Increment the electron counter.
1068 ++m_nElectrons;
1069 if (!aval) continue;
1070 // Add the secondary electron to the stack.
1071 if (useBandStructure) {
1072 double kxs = 0., kys = 0., kzs = 0.;
1073 int bs = -1;
1074 medium->GetElectronMomentum(esec, kxs, kys, kzs, bs);
1075 AddToStack(x, y, z, t, esec, kxs, kys, kzs, bs, false, stackNew);
1076 } else {
1077 AddToStack(x, y, z, t, esec, false, stackNew);
1078 }
1079 } else if (itype == IonProdTypeHole) {
1080 esec = std::max(esec, Small);
1081 // Increment the hole counter.
1082 ++m_nHoles;
1083 if (!aval) continue;
1084 // Add the secondary hole to the stack.
1085 if (useBandStructure) {
1086 double kxs = 0., kys = 0., kzs = 0.;
1087 int bs = -1;
1088 medium->GetElectronMomentum(esec, kxs, kys, kzs, bs);
1089 AddToStack(x, y, z, t, esec, kxs, kys, kzs, bs, true, stackNew);
1090 } else {
1091 AddToStack(x, y, z, t, esec, true, stackNew);
1092 }
1093 } else if (itype == IonProdTypeIon) {
1094 ++m_nIons;
1095 }
1096 }
1097 if (m_debug) PrintStatus(hdr, "ionised", x, y, z, hole);
1098 break;
1099 // Attachment
1100 case ElectronCollisionTypeAttachment:
1101 if (m_usePlotting && m_plotAttachments) {
1102 m_viewer->AddAttachmentMarker(x, y, z);
1103 }
1104 if (m_hasUserHandleAttachment) {
1105 m_userHandleAttachment(x, y, z, t, cstype, level, medium);
1106 }
1107 // TODO: check kx or newKx!
1108 Update(it, x, y, z, t, energy, newKx, newKy, newKz, band);
1109 (*it).status = StatusAttached;
1110 if (hole) {
1111 m_endpointsHoles.push_back(*it);
1112 --m_nHoles;
1113 } else {
1114 m_endpointsElectrons.push_back(*it);
1115 --m_nElectrons;
1116 }
1117 ok = false;
1118 break;
1119 // Inelastic collision
1120 case ElectronCollisionTypeInelastic:
1121 if (m_hasUserHandleInelastic) {
1122 m_userHandleInelastic(x, y, z, t, cstype, level, medium);
1123 }
1124 break;
1125 // Excitation
1126 case ElectronCollisionTypeExcitation:
1127 if (m_usePlotting && m_plotExcitations) {
1128 m_viewer->AddExcitationMarker(x, y, z);
1129 }
1130 if (m_hasUserHandleInelastic) {
1131 m_userHandleInelastic(x, y, z, t, cstype, level, medium);
1132 }
1133 if (ndxc <= 0) break;
1134 // Get the electrons/photons produced in the deexcitation cascade.
1135 stackPhotons.clear();
1136 for (int j = ndxc; j--;) {
1137 double tdx = 0., sdx = 0., edx = 0.;
1138 int typedx = 0;
1139 if (!medium->GetDeexcitationProduct(j, tdx, sdx, typedx, edx)) {
1140 std::cerr << hdr << "Cannot retrieve deexcitation product "
1141 << j << "/" << ndxc << ".\n";
1142 break;
1143 }
1144
1145 if (typedx == DxcProdTypeElectron) {
1146 // Penning ionisation
1147 double xp = x, yp = y, zp = z;
1148 if (sdx > Small) {
1149 // Randomise the point of creation.
1150 double dxp = 0., dyp = 0., dzp = 0.;
1151 RndmDirection(dxp, dyp, dzp);
1152 xp += sdx * dxp;
1153 yp += sdx * dyp;
1154 zp += sdx * dzp;
1155 }
1156 // Get the electric field and medium at this location.
1157 Medium* med = NULL;
1158 double fx = 0., fy = 0., fz = 0.;
1159 m_sensor->ElectricField(xp, yp, zp, fx, fy, fz, med, status);
1160 // Check if this location is inside a drift medium/area.
1161 if (status != 0 || !m_sensor->IsInArea(xp, yp, zp)) continue;
1162 // Increment the electron and ion counters.
1163 ++m_nElectrons;
1164 ++m_nIons;
1165 // Make sure we haven't jumped across a wire.
1166 if (m_sensor->IsWireCrossed(x, y, z, xp, yp, zp, xc, yc, zc)) {
1167 continue;
1168 }
1169 if (!aval) continue;
1170 // Add the Penning electron to the list.
1171 AddToStack(xp, yp, zp, t + tdx, std::max(edx, Small), false, stackNew);
1172 } else if (typedx == DxcProdTypePhoton && m_usePhotons &&
1173 edx > m_gammaCut) {
1174 // Radiative de-excitation
1175 stackPhotons.push_back(std::make_pair(t + tdx, edx));
1176 }
1177 }
1178
1179 // Transport the photons (if any)
1180 if (aval) {
1181 const int nSizePhotons = stackPhotons.size();
1182 for (int j = nSizePhotons; j--;) {
1183 TransportPhoton(x, y, z, stackPhotons[j].first, stackPhotons[j].second, stackNew);
1184 }
1185 }
1186 break;
1187 // Super-elastic collision
1188 case ElectronCollisionTypeSuperelastic:
1189 break;
1190 // Acoustic phonon scattering (intravalley)
1191 case ElectronCollisionTypeAcousticPhonon:
1192 break;
1193 // Optical phonon scattering (intravalley)
1194 case ElectronCollisionTypeOpticalPhonon:
1195 break;
1196 // Intervalley scattering (phonon assisted)
1197 case ElectronCollisionTypeIntervalleyG:
1198 case ElectronCollisionTypeIntervalleyF:
1199 case ElectronCollisionTypeInterbandXL:
1200 case ElectronCollisionTypeInterbandXG:
1201 case ElectronCollisionTypeInterbandLG:
1202 break;
1203 // Coulomb scattering
1204 case ElectronCollisionTypeImpurity:
1205 break;
1206 default:
1207 std::cerr << hdr << "Unknown collision type.\n";
1208 ok = false;
1209 break;
1210 }
1211
1212 // Continue with the next electron/hole?
1213 if (!ok || nCollTemp > m_nCollSkip ||
1214 cstype == ElectronCollisionTypeIonisation ||
1215 (m_plotExcitations && cstype == ElectronCollisionTypeExcitation) ||
1216 (m_plotAttachments && cstype == ElectronCollisionTypeAttachment)) {
1217 break;
1218 }
1219 kx = newKx;
1220 ky = newKy;
1221 kz = newKz;
1222 }
1223
1224 if (!ok) continue;
1225
1226 if (!useBandStructure) {
1227 // Normalise the direction vector.
1228 const double k = sqrt(kx * kx + ky * ky + kz * kz);
1229 kx /= k;
1230 ky /= k;
1231 kz /= k;
1232 }
1233 // Update the stack.
1234 Update(it, x, y, z, t, energy, kx, ky, kz, band);
1235 // Add a new point to the drift line (if enabled).
1236 if (m_useDriftLines) {
1237 point newPoint;
1238 newPoint.x = x;
1239 newPoint.y = y;
1240 newPoint.z = z;
1241 newPoint.t = t;
1242 (*it).driftLine.push_back(newPoint);
1243 }
1244 }
1245 }
1246
1247 // Calculate the induced charge.
1248 if (m_useInducedCharge) {
1249 const unsigned int nElectronEndpoints = m_endpointsElectrons.size();
1250 for (unsigned int i = 0; i < nElectronEndpoints; ++i) {
1251 const Electron& p = m_endpointsElectrons[i];
1252 m_sensor->AddInducedCharge(-1, p.x0, p.y0, p.z0, p.x, p.y, p.z);
1253 }
1254 const unsigned int nHoleEndpoints = m_endpointsHoles.size();
1255 for (unsigned int i = 0; i < nHoleEndpoints; ++i) {
1256 const Electron& p = m_endpointsHoles[i];
1257 m_sensor->AddInducedCharge(+1, p.x0, p.y0, p.z0, p.x, p.y, p.z);
1258 }
1259 }
1260
1261 // Plot the drift paths and photon tracks.
1262 if (m_usePlotting) {
1263 // Electrons
1264 const unsigned int nElectronEndpoints = m_endpointsElectrons.size();
1265 for (unsigned int i = 0; i < nElectronEndpoints; ++i) {
1266 const int np = GetNumberOfElectronDriftLinePoints(i);
1267 int jL;
1268 if (np <= 0) continue;
1269 const Electron& p = m_endpointsElectrons[i];
1270 m_viewer->NewElectronDriftLine(np, jL, p.x0, p.y0, p.z0);
1271 for (int jP = np; jP--;) {
1272 double x = 0., y = 0., z = 0., t = 0.;
1273 GetElectronDriftLinePoint(x, y, z, t, jP, i);
1274 m_viewer->SetDriftLinePoint(jL, jP, x, y, z);
1275 }
1276 }
1277 // Holes
1278 const unsigned int nHoleEndpoints = m_endpointsHoles.size();
1279 for (unsigned int i = 0; i < nHoleEndpoints; ++i) {
1280 const int np = GetNumberOfHoleDriftLinePoints(i);
1281 int jL;
1282 if (np <= 0) continue;
1283 const Electron& p = m_endpointsHoles[i];
1284 m_viewer->NewHoleDriftLine(np, jL, p.x0, p.y0, p.z0);
1285 for (int jP = np; jP--;) {
1286 double x = 0., y = 0., z = 0., t = 0.;
1287 GetHoleDriftLinePoint(x, y, z, t, jP, i);
1288 m_viewer->SetDriftLinePoint(jL, jP, x, y, z);
1289 }
1290 }
1291 // Photons
1292 const unsigned int nPhotons = m_photons.size();
1293 for (unsigned int i = 0; i < nPhotons; ++i) {
1294 const photon& p = m_photons[i];
1295 m_viewer->NewPhotonTrack(p.x0, p.y0, p.z0, p.x1, p.y1, p.z1);
1296 }
1297 }
1298 return true;
1299}
1300
1301void AvalancheMicroscopic::TransportPhoton(const double x0, const double y0,
1302 const double z0, const double t0,
1303 const double e0,
1304 std::vector<Electron>& stack) {
1305
1306 // Make sure that the sensor is defined.
1307 if (!m_sensor) {
1308 std::cerr << m_className << "::TransportPhoton: Sensor is not defined.\n";
1309 return;
1310 }
1311
1312 // Make sure that the starting point is inside a medium.
1313 Medium* medium;
1314 if (!m_sensor->GetMedium(x0, y0, z0, medium)) {
1315 std::cerr << m_className << "::TransportPhoton:\n"
1316 << " No medium at initial position.\n";
1317 return;
1318 }
1319
1320 // Make sure that the medium is "driftable" and microscopic.
1321 if (!medium->IsDriftable() || !medium->IsMicroscopic()) {
1322 std::cerr << m_className << "::TransportPhoton:\n"
1323 << " Medium at initial position does not provide "
1324 << " microscopic tracking data.\n";
1325 return;
1326 }
1327
1328 // Get the id number of the drift medium.
1329 int id = medium->GetId();
1330
1331 // Position
1332 double x = x0, y = y0, z = z0;
1333 double t = t0;
1334 // Initial direction (randomised).
1335 double dx = 0., dy = 0., dz = 0.;
1336 RndmDirection(dx, dy, dz);
1337 // Energy
1338 double e = e0;
1339
1340
1341 // Photon collision rate
1342 double f = medium->GetPhotonCollisionRate(e);
1343 if (f <= 0.) return;
1344 // Timestep
1345 double dt = -log(RndmUniformPos()) / f;
1346 t += dt;
1347 dt *= SpeedOfLight;
1348 x += dt * dx;
1349 y += dt * dy;
1350 z += dt * dz;
1351
1352 // Check if the photon is still inside a medium.
1353 if (!m_sensor->GetMedium(x, y, z, medium) || medium->GetId() != id) {
1354 // Try to terminate the photon track close to the boundary
1355 // by means of iterative bisection.
1356 dx *= dt;
1357 dy *= dt;
1358 dz *= dt;
1359 x -= dx;
1360 y -= dy;
1361 z -= dz;
1362 double delta = sqrt(dx * dx + dy * dy + dz * dz);
1363 if (delta > 0) {
1364 dx /= delta;
1365 dy /= delta;
1366 dz /= delta;
1367 }
1368 // Mid-point
1369 double xM = x, yM = y, zM = z;
1370 while (delta > BoundaryDistance) {
1371 delta *= 0.5;
1372 dt *= 0.5;
1373 xM = x + delta * dx;
1374 yM = y + delta * dy;
1375 zM = z + delta * dz;
1376 // Check if the mid-point is inside the drift medium.
1377 if (m_sensor->GetMedium(xM, yM, zM, medium) && medium->GetId() == id) {
1378 x = xM;
1379 y = yM;
1380 z = zM;
1381 t += dt;
1382 }
1383 }
1384 photon newPhoton;
1385 newPhoton.x0 = x0;
1386 newPhoton.y0 = y0;
1387 newPhoton.z0 = z0;
1388 newPhoton.x1 = x;
1389 newPhoton.y1 = y;
1390 newPhoton.z1 = z;
1391 newPhoton.energy = e0;
1392 newPhoton.status = StatusLeftDriftMedium;
1393 m_photons.push_back(newPhoton);
1394 return;
1395 }
1396
1397 int type, level;
1398 double e1;
1399 double ctheta = 0.;
1400 int nsec = 0;
1401 double esec = 0.;
1402 if (!medium->GetPhotonCollision(e, type, level, e1, ctheta, nsec, esec))
1403 return;
1404
1405 if (type == PhotonCollisionTypeIonisation) {
1406 // Add the secondary electron (random direction) to the stack.
1407 if (m_sizeCut == 0 || stack.size() < m_sizeCut) {
1408 AddToStack(x, y, z, t, std::max(esec, Small), false, stack);
1409 }
1410 // Increment the electron and ion counters.
1411 ++m_nElectrons;
1412 ++m_nIons;
1413 } else if (type == PhotonCollisionTypeExcitation) {
1414 double tdx = 0.;
1415 double sdx = 0.;
1416 int typedx = 0;
1417 std::vector<double> tPhotons;
1418 std::vector<double> ePhotons;
1419 for (int j = nsec; j--;) {
1420 if (!medium->GetDeexcitationProduct(j, tdx, sdx, typedx, esec))
1421 continue;
1422 if (typedx == DxcProdTypeElectron) {
1423 // Ionisation.
1424 AddToStack(x, y, z, t + tdx, std::max(esec, Small), false, stack);
1425 // Increment the electron and ion counters.
1426 ++m_nElectrons;
1427 ++m_nIons;
1428 } else if (typedx == DxcProdTypePhoton && m_usePhotons &&
1429 esec > m_gammaCut) {
1430 // Radiative de-excitation
1431 tPhotons.push_back(t + tdx);
1432 ePhotons.push_back(esec);
1433 }
1434 }
1435 // Transport the photons (if any).
1436 const int nSizePhotons = tPhotons.size();
1437 for (int k = nSizePhotons; k--;) {
1438 TransportPhoton(x, y, z, tPhotons[k], ePhotons[k], stack);
1439 }
1440 }
1441
1442 photon newPhoton;
1443 newPhoton.x0 = x0;
1444 newPhoton.y0 = y0;
1445 newPhoton.z0 = z0;
1446 newPhoton.x1 = x;
1447 newPhoton.y1 = y;
1448 newPhoton.z1 = z;
1449 newPhoton.energy = e0;
1450 newPhoton.status = -2;
1451 m_photons.push_back(newPhoton);
1452}
1453
1454void AvalancheMicroscopic::ComputeRotationMatrix(
1455 const double bx, const double by, const double bz, const double bmag,
1456 const double ex, const double ey, const double ez) {
1457
1458 // Adopting the Magboltz convention, the stepping is performed
1459 // in a coordinate system with the B field along the x axis
1460 // and the electric field at an angle btheta in the x-z plane.
1461
1462 // Calculate the first rotation matrix (to align B with x axis).
1463 const double bt = by * by + bz * bz;
1464 if (bt < Small) {
1465 // B field is already along axis.
1466 m_rb11 = m_rb22 = m_rb33 = 1.;
1467 m_rb12 = m_rb13 = m_rb21 = m_rb23 = m_rb31 = m_rb32 = 0.;
1468 } else {
1469 const double btInv = 1. / bt;
1470 m_rb11 = bx / bmag;
1471 m_rb12 = by / bmag;
1472 m_rb21 = -m_rb12;
1473 m_rb13 = bz / bmag;
1474 m_rb31 = -m_rb13;
1475 m_rb22 = (m_rb11 * by * by + bz * bz) * btInv;
1476 m_rb33 = (m_rb11 * bz * bz + by * by) * btInv;
1477 m_rb23 = m_rb32 = (m_rb11 - 1.) * by * bz * btInv;
1478 }
1479 // Calculate the second rotation matrix (rotation around x axis).
1480 const double fy = m_rb21 * ex + m_rb22 * ey + m_rb23 * ez;
1481 const double fz = m_rb31 * ex + m_rb32 * ey + m_rb33 * ez;
1482 const double ft = sqrt(fy * fy + fz * fz);
1483 if (ft < Small) {
1484 // E and B field are parallel.
1485 m_rx22 = m_rx33 = 1.;
1486 m_rx23 = m_rx32 = 0.;
1487 } else {
1488 m_rx22 = m_rx33 = fz / ft;
1489 m_rx23 = -fy / ft;
1490 m_rx32 = -m_rx23;
1491 }
1492}
1493
1494void AvalancheMicroscopic::RotateGlobal2Local(double& dx, double& dy,
1495 double& dz) const {
1496
1497 const double dx1 = m_rb11 * dx + m_rb12 * dy + m_rb13 * dz;
1498 const double dy1 = m_rb21 * dx + m_rb22 * dy + m_rb23 * dz;
1499 const double dz1 = m_rb31 * dx + m_rb32 * dy + m_rb33 * dz;
1500
1501 dx = dx1;
1502 dy = m_rx22 * dy1 + m_rx23 * dz1;
1503 dz = m_rx32 * dy1 + m_rx33 * dz1;
1504}
1505
1506void AvalancheMicroscopic::RotateLocal2Global(double& dx, double& dy,
1507 double& dz) const {
1508
1509 const double dx1 = dx;
1510 const double dy1 = m_rx22 * dy + m_rx32 * dz;
1511 const double dz1 = m_rx23 * dy + m_rx33 * dz;
1512
1513 dx = m_rb11 * dx1 + m_rb21 * dy1 + m_rb31 * dz1;
1514 dy = m_rb12 * dx1 + m_rb22 * dy1 + m_rb32 * dz1;
1515 dz = m_rb13 * dx1 + m_rb23 * dy1 + m_rb33 * dz1;
1516}
1517
1518void AvalancheMicroscopic::Update(std::vector<Electron>::iterator it,
1519 const double x, const double y,
1520 const double z, const double t,
1521 const double energy,
1522 const double kx, const double ky,
1523 const double kz, const int band) {
1524
1525 (*it).x = x;
1526 (*it).y = y;
1527 (*it).z = z;
1528 (*it).t = t;
1529 (*it).energy = energy;
1530 (*it).kx = kx;
1531 (*it).ky = ky;
1532 (*it).kz = kz;
1533 (*it).band = band;
1534}
1535
1536void AvalancheMicroscopic::AddToStack(const double x, const double y,
1537 const double z, const double t,
1538 const double energy,
1539 const bool hole,
1540 std::vector<Electron>& container) const {
1541
1542 // Randomise the direction.
1543 double dx = 0., dy = 0., dz = 1.;
1544 RndmDirection(dx, dy, dz);
1545 AddToStack(x, y, z, t, energy, dx, dy, dz, 0, hole, container);
1546}
1547
1548void AvalancheMicroscopic::AddToStack(const double x, const double y,
1549 const double z, const double t,
1550 const double energy,
1551 const double dx, const double dy,
1552 const double dz, const int band,
1553 const bool hole,
1554 std::vector<Electron>& container) const {
1555
1556 Electron electron;
1557 electron.status = 0;
1558 electron.hole = hole;
1559 electron.x0 = x;
1560 electron.y0 = y;
1561 electron.z0 = z;
1562 electron.t0 = t;
1563 electron.e0 = energy;
1564 electron.x = x;
1565 electron.y = y;
1566 electron.z = z;
1567 electron.t = t;
1568 electron.energy = energy;
1569 electron.kx = dx;
1570 electron.ky = dy;
1571 electron.kz = dz;
1572 electron.band = band;
1573 // Previous coordinates for distance histogramming.
1574 electron.xLast = x;
1575 electron.yLast = y;
1576 electron.zLast = z;
1577 electron.driftLine.reserve(1000);
1578 container.push_back(electron);
1579}
1580
1581void AvalancheMicroscopic::Terminate(double x0, double y0, double z0, double t0,
1582 double& x1, double& y1,
1583 double& z1, double& t1) {
1584
1585 const double dx = x1 - x0;
1586 const double dy = y1 - y0;
1587 const double dz = z1 - z0;
1588 double d = sqrt(dx * dx + dy * dy + dz * dz);
1589 while (d > BoundaryDistance) {
1590 d *= 0.5;
1591 const double xm = 0.5 * (x0 + x1);
1592 const double ym = 0.5 * (y0 + y1);
1593 const double zm = 0.5 * (z0 + z1);
1594 const double tm = 0.5 * (t0 + t1);
1595 // Check if the mid-point is inside the drift medium.
1596 double ex = 0., ey = 0., ez = 0.;
1597 Medium* medium = NULL;
1598 int status = 0;
1599 m_sensor->ElectricField(xm, ym, zm, ex, ey, ez, medium, status);
1600 if (status == 0 && m_sensor->IsInArea(xm, ym, zm)) {
1601 x0 = xm;
1602 y0 = ym;
1603 z0 = zm;
1604 t0 = tm;
1605 } else {
1606 x1 = xm;
1607 y1 = ym;
1608 z1 = zm;
1609 t1 = tm;
1610 }
1611 }
1612}
1613
1614}
void EnableDistanceHistogramming(const int type)
void SetDistanceHistogram(TH1 *histo, const char opt='r')
void SetUserHandleStep(void(*f)(double x, double y, double z, double t, double e, double dx, double dy, double dz, bool hole))
Set a user handling procedure. This function is called at every step.
void EnablePlotting(ViewDrift *view)
Switch on drift line plotting.
void SetUserHandleIonisation(void(*f)(double x, double y, double z, double t, int type, int level, Medium *m))
Set a user handling procedure, to be called at every ionising collision.
bool DriftElectron(const double x0, const double y0, const double z0, const double t0, const double e0, const double dx0=0., const double dy0=0., const double dz0=0.)
unsigned int GetNumberOfElectronDriftLinePoints(const unsigned int i=0) const
void EnableDriftLines()
Switch on storage of drift lines.
void SetUserHandleAttachment(void(*f)(double x, double y, double z, double t, int type, int level, Medium *m))
Set a user handling procedure, to be called at every attachment.
void SetSensor(Sensor *sensor)
Set the sensor.
void GetHoleDriftLinePoint(double &x, double &y, double &z, double &t, const int ip, const unsigned int iel=0) const
void GetElectronEndpoint(const unsigned int i, double &x0, double &y0, double &z0, double &t0, double &e0, double &x1, double &y1, double &z1, double &t1, double &e1, int &status) const
void GetHoleEndpoint(const unsigned int i, double &x0, double &y0, double &z0, double &t0, double &e0, double &x1, double &y1, double &z1, double &t1, double &e1, int &status) const
void GetElectronDriftLinePoint(double &x, double &y, double &z, double &t, const int ip, const unsigned int iel=0) const
void EnableElectronEnergyHistogramming(TH1 *histo)
Switch on filling histograms for electron energy distribution.
void GetPhoton(const unsigned int i, double &e, double &x0, double &y0, double &z0, double &t0, double &x1, double &y1, double &z1, double &t1, int &status) const
void EnableSecondaryEnergyHistogramming(TH1 *histo)
Fill histograms of the energy of electrons emitted in ionising collisions.
bool AvalancheElectron(const double x0, const double y0, const double z0, const double t0, const double e0, const double dx0=0., const double dy0=0., const double dz0=0.)
Calculate an avalanche initiated by a given electron.
unsigned int GetNumberOfHoleDriftLinePoints(const unsigned int i=0) const
void SetUserHandleInelastic(void(*f)(double x, double y, double z, double t, int type, int level, Medium *m))
Set a user handling procedure, to be called at every inelastic collision.
void SetTimeWindow(const double t0, const double t1)
Define a time interval (only carriers inside the interval are simulated).
Abstract base class for media.
Definition: Medium.hh:11
void MagneticField(const double x, const double y, const double z, double &bx, double &by, double &bz, int &status)
Get the magnetic field at (x, y, z).
Definition: Sensor.cc:101
void ElectricField(const double x, const double y, const double z, double &ex, double &ey, double &ez, double &v, Medium *&medium, int &status)
Get the drift field and potential at (x, y, z).
Definition: Sensor.cc:48
bool IsInArea(const double x, const double y, const double z)
Check if a point is inside the user area.
Definition: Sensor.cc:264
void AddSignal(const double q, const double t, const double dt, const double x, const double y, const double z, const double vx, const double vy, const double vz)
Definition: Sensor.cc:417
bool GetMedium(const double x, const double y, const double z, Medium *&medium)
Get the medium at (x, y, z).
Definition: Sensor.cc:150
bool IsWireCrossed(const double x0, const double y0, const double z0, const double x1, const double y1, const double z1, double &xc, double &yc, double &zc)
Definition: Sensor.cc:288
void AddInducedCharge(const double q, const double x0, const double y0, const double z0, const double x1, const double y1, const double z1)
Definition: Sensor.cc:501
Visualize drift lines and tracks.
Definition: ViewDrift.hh:15
void NewElectronDriftLine(const unsigned int np, int &id, const double x0, const double y0, const double z0)
Definition: ViewDrift.cc:127
void AddExcitationMarker(const double x, const double y, const double z)
Definition: ViewDrift.cc:283
void AddIonisationMarker(const double x, const double y, const double z)
Definition: ViewDrift.cc:293
void SetDriftLinePoint(const unsigned int iL, const unsigned int iP, const double x, const double y, const double z)
Definition: ViewDrift.cc:230
void AddAttachmentMarker(const double x, const double y, const double z)
Definition: ViewDrift.cc:303
void NewPhotonTrack(const double x0, const double y0, const double z0, const double x1, const double y1, const double z1)
Definition: ViewDrift.cc:203
void NewHoleDriftLine(const unsigned int np, int &id, const double x0, const double y0, const double z0)
Definition: ViewDrift.cc:156
double RndmUniform()
Draw a random number uniformly distributed in the range [0, 1).
Definition: Random.hh:14
void RndmDirection(double &dx, double &dy, double &dz, const double length=1.)
Draw a random (isotropic) direction vector.
Definition: Random.hh:106
double RndmUniformPos()
Draw a random number uniformly distributed in the range (0, 1).
Definition: Random.hh:17
DoubleAc cos(const DoubleAc &f)
Definition: DoubleAc.cpp:432
DoubleAc pow(const DoubleAc &f, double p)
Definition: DoubleAc.cpp:337
DoubleAc fabs(const DoubleAc &f)
Definition: DoubleAc.h:615
DoubleAc sin(const DoubleAc &f)
Definition: DoubleAc.cpp:384
DoubleAc sqrt(const DoubleAc &f)
Definition: DoubleAc.cpp:314