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