Garfield++ 5.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 Normalise(double& x, double& y, double& z) {
18 const double d = Mag(x, y, z);
19 if (d > 0.) {
20 const double scale = 1. / d;
21 x *= scale;
22 y *= scale;
23 z *= scale;
24 }
25}
26
27void ToLocal(const std::array<std::array<double, 3>, 3>& rot, const double xg,
28 const double yg, const double zg, double& xl, double& yl,
29 double& zl) {
30 xl = rot[0][0] * xg + rot[0][1] * yg + rot[0][2] * zg;
31 yl = rot[1][0] * xg + rot[1][1] * yg + rot[1][2] * zg;
32 zl = rot[2][0] * xg + rot[2][1] * yg + rot[2][2] * zg;
33}
34
35void ToGlobal(const std::array<std::array<double, 3>, 3>& rot, const double xl,
36 const double yl, const double zl, double& xg, double& yg,
37 double& zg) {
38 xg = rot[0][0] * xl + rot[1][0] * yl + rot[2][0] * zl;
39 yg = rot[0][1] * xl + rot[1][1] * yl + rot[2][1] * zl;
40 zg = rot[0][2] * xl + rot[1][2] * yl + rot[2][2] * zl;
41}
42
43void RotationMatrix(double bx, double by, double bz, const double bmag,
44 const double ex, const double ey, const double ez,
45 std::array<std::array<double, 3>, 3>& rot) {
46 // Adopting the Magboltz convention, the stepping is performed
47 // in a coordinate system with the B field along the x axis
48 // and the electric field at an angle btheta in the x-z plane.
49
50 // Calculate the first rotation matrix (to align B with x axis).
51 std::array<std::array<double, 3>, 3> rB = {{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}};
52 if (bmag > Garfield::Small) {
53 bx /= bmag;
54 by /= bmag;
55 bz /= bmag;
56 const double bt = by * by + bz * bz;
57 if (bt > Garfield::Small) {
58 const double btInv = 1. / bt;
59 rB[0][0] = bx;
60 rB[0][1] = by;
61 rB[0][2] = bz;
62 rB[1][0] = -by;
63 rB[2][0] = -bz;
64 rB[1][1] = (bx * by * by + bz * bz) * btInv;
65 rB[2][2] = (bx * bz * bz + by * by) * btInv;
66 rB[1][2] = rB[2][1] = (bx - 1.) * by * bz * btInv;
67 } else if (bx < 0.) {
68 // B field is anti-parallel to x.
69 rB[0][0] = -1.;
70 rB[1][1] = -1.;
71 }
72 }
73 // Calculate the second rotation matrix (rotation around x axis).
74 const double fy = rB[1][0] * ex + rB[1][1] * ey + rB[1][2] * ez;
75 const double fz = rB[2][0] * ex + rB[2][1] * ey + rB[2][2] * ez;
76 const double ft = sqrt(fy * fy + fz * fz);
77 std::array<std::array<double, 3>, 3> rX = {{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}};
78 if (ft > Garfield::Small) {
79 // E and B field are not parallel.
80 rX[1][1] = rX[2][2] = fz / ft;
81 rX[1][2] = -fy / ft;
82 rX[2][1] = -rX[1][2];
83 }
84 for (unsigned int i = 0; i < 3; ++i) {
85 for (unsigned int j = 0; j < 3; ++j) {
86 rot[i][j] = 0.;
87 for (unsigned int k = 0; k < 3; ++k) {
88 rot[i][j] += rX[i][k] * rB[k][j];
89 }
90 }
91 }
92}
93
95 const double x, const double y, const double z, const double t,
96 const double energy, const double dx, const double dy, const double dz,
97 const int band) {
98
100 p.x = x;
101 p.y = y;
102 p.z = z;
103 p.t = t;
104 p.energy = energy;
105 p.kx = dx;
106 p.ky = dy;
107 p.kz = dz;
108 p.band = band;
109 return p;
110}
111
113 const double x, const double y, const double z, const double t,
114 const double energy) {
115 // Randomise the direction.
116 double dx = 0., dy = 0., dz = 1.;
117 Garfield::RndmDirection(dx, dy, dz);
118 return MakePoint(x, y, z, t, energy, dx, dy, dz, 0);
119}
120
121} // namespace
122
123namespace Garfield {
124
126 m_sensor(sensor) {
127 m_electrons.reserve(10000);
128 m_holes.reserve(10000);
129 m_photons.reserve(1000);
130}
131
133 if (!s) {
134 std::cerr << m_className << "::SetSensor: Null pointer.\n";
135 return;
136 }
137 m_sensor = s;
138}
139
141 const size_t nColl) {
142 if (!view) {
143 std::cerr << m_className << "::EnablePlotting: Null pointer.\n";
144 return;
145 }
146 m_viewer = view;
147 m_nCollPlot = std::max(nColl, 1ul);
148}
149
151 if (!histo) {
152 std::cerr << m_className << "::EnableElectronEnergyHistogramming:\n"
153 << " Null pointer.\n";
154 return;
155 }
156
157 m_histElectronEnergy = histo;
158}
159
161 if (!histo) {
162 std::cerr << m_className << "::EnableHoleEnergyHistogramming:\n"
163 << " Null pointer.\n";
164 return;
165 }
166
167 m_histHoleEnergy = histo;
168}
169
170void AvalancheMicroscopic::SetDistanceHistogram(TH1* histo, const char opt) {
171 if (!histo) {
172 std::cerr << m_className << "::SetDistanceHistogram: Null pointer.\n";
173 return;
174 }
175
176 m_histDistance = histo;
177
178 if (opt == 'x' || opt == 'y' || opt == 'z' || opt == 'r') {
179 m_distanceOption = opt;
180 } else {
181 std::cerr << m_className << "::SetDistanceHistogram:";
182 std::cerr << " Unknown option " << opt << ".\n";
183 std::cerr << " Valid options are x, y, z, r.\n";
184 std::cerr << " Using default value (r).\n";
185 m_distanceOption = 'r';
186 }
187
188 if (m_distanceHistogramType.empty()) {
189 std::cout << m_className << "::SetDistanceHistogram:\n";
190 std::cout << " Don't forget to call EnableDistanceHistogramming.\n";
191 }
192}
193
195 // Check if this type of collision is already registered
196 // for histogramming.
197 const unsigned int nDistanceHistogramTypes = m_distanceHistogramType.size();
198 if (nDistanceHistogramTypes > 0) {
199 for (unsigned int i = 0; i < nDistanceHistogramTypes; ++i) {
200 if (m_distanceHistogramType[i] != type) continue;
201 std::cout << m_className << "::EnableDistanceHistogramming:\n";
202 std::cout << " Collision type " << type
203 << " is already being histogrammed.\n";
204 return;
205 }
206 }
207
208 m_distanceHistogramType.push_back(type);
209 std::cout << m_className << "::EnableDistanceHistogramming:\n";
210 std::cout << " Histogramming of collision type " << type << " enabled.\n";
211 if (!m_histDistance) {
212 std::cout << " Don't forget to set the histogram.\n";
213 }
214}
215
217 if (std::find(m_distanceHistogramType.begin(), m_distanceHistogramType.end(),
218 type) == m_distanceHistogramType.end()) {
219 std::cerr << m_className << "::DisableDistanceHistogramming:\n"
220 << " Collision type " << type << " is not histogrammed.\n";
221 return;
222 }
223
224 m_distanceHistogramType.erase(
225 std::remove(m_distanceHistogramType.begin(),
226 m_distanceHistogramType.end(), type),
227 m_distanceHistogramType.end());
228}
229
231 m_histDistance = nullptr;
232 m_distanceHistogramType.clear();
233}
234
236 if (!histo) {
237 std::cerr << m_className << "::EnableSecondaryEnergyHistogramming:\n"
238 << " Null pointer.\n";
239 return;
240 }
241
242 m_histSecondary = histo;
243}
244
245void AvalancheMicroscopic::SetTimeWindow(const double t0, const double t1) {
246 if (fabs(t1 - t0) < Small) {
247 std::cerr << m_className << "::SetTimeWindow:\n";
248 std::cerr << " Time interval must be greater than zero.\n";
249 return;
250 }
251
252 m_tMin = std::min(t0, t1);
253 m_tMax = std::max(t0, t1);
254 m_hasTimeWindow = true;
255}
256
258 double& x0, double& y0, double& z0, double& t0, double& e0,
259 double& x1, double& y1, double& z1, double& t1, double& e1,
260 int& status) const {
261 if (i >= m_electrons.size()) {
262 std::cerr << m_className << "::GetElectronEndpoint: Index out of range.\n";
263 status = -3;
264 return;
265 }
266 if (m_electrons[i].path.empty()) {
267 std::cerr << m_className << "::GetElectronEndpoint: Empty drift line.\n";
268 status = -3;
269 return;
270 }
271 x0 = m_electrons[i].path[0].x;
272 y0 = m_electrons[i].path[0].y;
273 z0 = m_electrons[i].path[0].z;
274 t0 = m_electrons[i].path[0].t;
275 e0 = m_electrons[i].path[0].energy;
276 x1 = m_electrons[i].path.back().x;
277 y1 = m_electrons[i].path.back().y;
278 z1 = m_electrons[i].path.back().z;
279 t1 = m_electrons[i].path.back().t;
280 e1 = m_electrons[i].path.back().energy;
281 status = m_electrons[i].status;
282}
283
285 double& x0, double& y0, double& z0, double& t0, double& e0,
286 double& x1, double& y1, double& z1, double& t1, double& e1,
287 int& status) const {
288 if (i >= m_holes.size()) {
289 std::cerr << m_className << "::GetHoleEndpoint: Index out of range.\n";
290 status = -3;
291 return;
292 }
293 if (m_electrons[i].path.empty()) {
294 std::cerr << m_className << "::GetHoleEndpoint: Empty drift line.\n";
295 status = -3;
296 return;
297 }
298 x0 = m_holes[i].path[0].x;
299 y0 = m_holes[i].path[0].y;
300 z0 = m_holes[i].path[0].z;
301 t0 = m_holes[i].path[0].t;
302 e0 = m_holes[i].path[0].energy;
303 x1 = m_holes[i].path.back().x;
304 y1 = m_holes[i].path.back().y;
305 z1 = m_holes[i].path.back().z;
306 t1 = m_holes[i].path.back().t;
307 e1 = m_holes[i].path.back().energy;
308 status = m_holes[i].status;
309}
310
312 const size_t i) const {
313 if (i >= m_electrons.size()) {
314 std::cerr << m_className << "::GetNumberOfElectronDriftLinePoints: "
315 << "Index out of range.\n";
316 return 0;
317 }
318 return m_electrons[i].path.size();
319}
320
322 const size_t i) const {
323 if (i >= m_holes.size()) {
324 std::cerr << m_className << "::GetNumberOfHoleDriftLinePoints: "
325 << "Index out of range.\n";
326 return 0;
327 }
328 return m_holes[i].path.size();
329}
330
332 double& x, double& y, double& z, double& t, const size_t ip,
333 const size_t ie) const {
334 if (ie >= m_electrons.size()) {
335 std::cerr << m_className << "::GetElectronDriftLinePoint:\n"
336 << " Endpoint index (" << ie << ") out of range.\n";
337 return;
338 }
339 if (ip >= m_electrons[ie].path.size()) {
340 std::cerr << m_className << "::GetElectronDriftLinePoint:\n"
341 << " Drift line point index (" << ip << ") out of range.\n";
342 return;
343 }
344 x = m_electrons[ie].path[ip].x;
345 y = m_electrons[ie].path[ip].y;
346 z = m_electrons[ie].path[ip].z;
347 t = m_electrons[ie].path[ip].t;
348}
349
351 double& z, double& t,
352 const size_t ip,
353 const size_t ih) const {
354 if (ih >= m_holes.size()) {
355 std::cerr << m_className << "::GetHoleDriftLinePoint:\n"
356 << " Endpoint index (" << ih << ") out of range.\n";
357 return;
358 }
359 if (ip >= m_holes[ih].path.size()) {
360 std::cerr << m_className << "::GetHoleDriftLinePoint:\n"
361 << " Drift line point index (" << ip << ") out of range.\n";
362 return;
363 }
364 x = m_holes[ih].path[ip].x;
365 y = m_holes[ih].path[ip].y;
366 z = m_holes[ih].path[ip].z;
367 t = m_holes[ih].path[ip].t;
368}
369
370void AvalancheMicroscopic::GetPhoton(const size_t i, double& e,
371 double& x0, double& y0, double& z0, double& t0,
372 double& x1, double& y1, double& z1, double& t1, int& status) const {
373 if (i >= m_photons.size()) {
374 std::cerr << m_className << "::GetPhoton: Index out of range.\n";
375 return;
376 }
377
378 x0 = m_photons[i].x0;
379 x1 = m_photons[i].x1;
380 y0 = m_photons[i].y0;
381 y1 = m_photons[i].y1;
382 z0 = m_photons[i].z0;
383 z1 = m_photons[i].z1;
384 t0 = m_photons[i].t0;
385 t1 = m_photons[i].t1;
386 status = m_photons[i].status;
387 e = m_photons[i].energy;
388}
389
391 void (*f)(double x, double y, double z, double t, double e, double dx,
392 double dy, double dz, bool hole)) {
393 if (!f) {
394 std::cerr << m_className << "::SetUserHandleStep: Null pointer.\n";
395 return;
396 }
397 m_userHandleStep = f;
398}
399
401 void (*f)(double x, double y, double z, double t, int type, int level,
402 Medium* m, double e0, double e1, double dx0, double dy0,
403 double dz0, double dx1, double dy1, double dz1)) {
404 m_userHandleCollision = f;
405}
406
408 double x, double y, double z, double t, int type, int level, Medium* m)) {
409 m_userHandleAttachment = f;
410}
411
413 double x, double y, double z, double t, int type, int level, Medium* m)) {
414 m_userHandleInelastic = f;
415}
416
418 double x, double y, double z, double t, int type, int level, Medium* m)) {
419 m_userHandleIonisation = f;
420}
421
423 const double x, const double y, const double z, const double t,
424 const double e, const double dx, const double dy, const double dz) {
425 std::vector<std::pair<Point, bool> > particles;
426 Point p = MakePoint(x, y, z, t, e, dx, dy, dz, 0);
427 particles.emplace_back(std::make_pair(std::move(p), false));
428 return TransportElectrons(particles, false);
429}
430
432 const double x, const double y, const double z, const double t,
433 const double e, const double dx, const double dy, const double dz) {
434
435 std::vector<std::pair<Point, bool> > particles;
436 Point p = MakePoint(x, y, z, t, e, dx, dy, dz, 0);
437 particles.emplace_back(std::make_pair(std::move(p), false));
438 return TransportElectrons(particles, true);
439}
440
442 const double x, const double y, const double z, const double t,
443 const double e, const double dx, const double dy, const double dz) {
444
445 Electron electron;
446 electron.status = StatusAlive;
447 electron.path.emplace_back(MakePoint(x, y, z, t, e, dx, dy, dz, 0));
448 m_electrons.push_back(std::move(electron));
449}
450
452 std::vector<std::pair<Point, bool> > particles;
453 for (const auto& p : m_electrons) {
454 if (p.status == StatusAlive || p.status == StatusOutsideTimeWindow) {
455 particles.emplace_back(std::make_pair(p.path.back(), false));
456 }
457 }
458 for (const auto& p : m_holes) {
459 if (p.status == StatusAlive || p.status == StatusOutsideTimeWindow) {
460 particles.emplace_back(std::make_pair(p.path.back(), true));
461 }
462 }
463 return TransportElectrons(particles, true);
464}
465
466bool AvalancheMicroscopic::TransportElectrons(
467 std::vector<std::pair<Point, bool> >& particles, const bool aval) {
468
469 // Clear the list of electrons, holes and photons.
470 m_electrons.clear();
471 m_holes.clear();
472 m_photons.clear();
473
474 // Reset the particle counters.
475 m_nElectrons = m_nHoles = m_nIons = 0;
476
477 // Make sure that the sensor is defined.
478 if (!m_sensor) {
479 std::cerr << m_className
480 << "::TransportElectrons: Sensor is not defined.\n";
481 return false;
482 }
483
484 // Do we need to consider the magnetic field?
485 const bool useBfield = m_useBfieldAuto ? m_sensor->HasMagneticField() :
486 m_useBfield;
487
488 // Do we need to compute the induced signal?
489 const bool signal = m_doSignal && (m_sensor->GetNumberOfElectrodes() > 0);
490 bool sc = false;
491 // Loop over the initial set of electrons/holes.
492 for (auto& p : particles) {
493 // Make sure that the starting point is inside the active area.
494 const double x0 = p.first.x;
495 const double y0 = p.first.y;
496 const double z0 = p.first.z;
497 if (!m_sensor->IsInArea(x0, y0, z0)) {
498 std::cerr << m_className << "::TransportElectrons: "
499 << "Starting point is outside the active area.\n";
500 return false;
501 }
502 // Make sure that the starting point is inside a "driftable"
503 // microscopic medium.
504 Medium* medium = m_sensor->GetMedium(x0, y0, z0);
505 if (!medium || !medium->IsDriftable() || !medium->IsMicroscopic()) {
506 std::cerr << m_className << "::TransportElectrons: "
507 << "Starting point is not in a valid medium.\n";
508 return false;
509 }
510 // Make sure the initial energy is positive.
511 const double e0 = std::max(p.first.energy, Small);
512
513 if (medium->IsSemiconductor() && m_useBandStructure) {
514 sc = true;
515 if (p.first.band < 0) {
516 // Sample the initial momentum and band.
517 medium->GetElectronMomentum(e0, p.first.kx, p.first.ky, p.first.kz,
518 p.first.band);
519 }
520 } else {
521 p.first.band = 0;
522 const double kmag = Mag(p.first.kx, p.first.ky, p.first.kz);
523 if (fabs(kmag) < Small) {
524 // Direction has zero norm, draw a random direction.
525 RndmDirection(p.first.kx, p.first.ky, p.first.kz);
526 } else {
527 // Normalise the direction to 1.
528 const double scale = 1. / kmag;
529 p.first.kx *= scale;
530 p.first.ky *= scale;
531 p.first.kz *= scale;
532 }
533 }
534 if (p.second) {
535 ++m_nHoles;
536 } else {
537 ++m_nElectrons;
538 }
539 }
540 std::vector<std::pair<Point, bool> > newParticles;
541 while (!particles.empty()) {
542 newParticles.clear();
543 // Loop over the electrons/holes in the avalanche.
544 for (const auto& particle : particles) {
545 const bool isHole = particle.second;
546 if (aval && m_sizeCut > 0) {
547 const size_t nH = m_holes.size();
548 const size_t nE = m_electrons.size();
549 if (nH >= m_sizeCut && nE >= m_sizeCut) {
550 break;
551 } else if (isHole) {
552 if (nH >= m_sizeCut) continue;
553 } else {
554 if (nE >= m_sizeCut) continue;
555 }
556 }
557 std::vector<Point> path;
558 double pathLength = 0.;
559 int status = 0;
560 if (sc) {
561 status = TransportElectronSc(particle.first, isHole, aval,
562 signal, path,
563 newParticles, pathLength);
564 } else if (useBfield) {
565 status = TransportElectronBfield(particle.first, isHole, aval,
566 signal, path,
567 newParticles, pathLength);
568 } else {
569 status = TransportElectron(particle.first, isHole, aval, signal,
570 path, newParticles, pathLength);
571 }
572 if (isHole) {
573 Electron hole;
574 hole.status = status;
575 hole.path = std::move(path);
576 hole.pathLength = pathLength;
577 m_holes.push_back(std::move(hole));
578 } else {
579 Electron electron;
580 electron.status = status;
581 electron.path = std::move(path);
582 electron.pathLength = pathLength;
583 m_electrons.push_back(std::move(electron));
584 }
585 }
586 particles.swap(newParticles);
587 }
588
589 // Calculate the induced charge.
590 if (m_doInducedCharge) {
591 for (const auto& p : m_electrons) {
592 m_sensor->AddInducedCharge(-1, p.path[0].x, p.path[0].y, p.path[0].z,
593 p.path.back().x, p.path.back().y, p.path.back().z);
594 }
595 for (const auto& p : m_holes) {
596 m_sensor->AddInducedCharge(+1, p.path[0].x, p.path[0].y, p.path[0].z,
597 p.path.back().x, p.path.back().y, p.path.back().z);
598 }
599 }
600 return true;
601}
602
603int AvalancheMicroscopic::TransportElectron(const Point& p0,
604 const bool hole, const bool aval, const bool signal,
605 std::vector<Point>& path,
606 std::vector<std::pair<Point, bool> >& newParticles,
607 double& pathLength) {
608
609 pathLength = 0.;
610 double x = p0.x;
611 double y = p0.y;
612 double z = p0.z;
613 double t = p0.t;
614 double en = p0.energy;
615 int band = p0.band;
616 double kx = p0.kx;
617 double ky = p0.ky;
618 double kz = p0.kz;
619 path.push_back(p0);
620 size_t did = 0;
621 if (m_viewer) {
622 if (hole) {
623 did = m_viewer->NewDriftLine(Particle::Hole, 1, x, y, z);
624 } else {
625 did = m_viewer->NewDriftLine(Particle::Electron, 1, x, y, z);
626 }
627 }
628
629 // Numerical prefactors in equation of motion
630 const double c1 = SpeedOfLight * sqrt(2. / ElectronMass);
631 const double c2 = 0.25 * c1 * c1;
632
633 // Get the local electric field and medium.
634 double ex = 0., ey = 0., ez = 0.;
635 Medium* medium = nullptr;
636 int status = 0;
637 m_sensor->ElectricField(x, y, z, ex, ey, ez, medium, status);
638 // Sign change for electrons.
639 if (!hole) {
640 ex = -ex;
641 ey = -ey;
642 ez = -ez;
643 }
644 if (m_debug) {
645 std::cout << " Drift line starts at ("
646 << x << ", " << y << ", " << z << ").\n"
647 << " Status: " << status << "\n";
648 if (medium) std::cout << " Medium: " << medium->GetName() << "\n";
649 }
650
651 if (status != 0 || !medium || !medium->IsDriftable() ||
652 !medium->IsMicroscopic()) {
653 if (m_debug) std::cout << " Not in a valid medium.\n";
654 return StatusLeftDriftMedium;
655 }
656 // Get the id number of the drift medium.
657 auto mid = medium->GetId();
658 // Get the null-collision rate.
659 double fLim = medium->GetElectronNullCollisionRate(band);
660 if (fLim <= 0.) {
661 std::cerr << m_className
662 << "::TransportElectron: Got null-collision rate <= 0.\n";
663 return StatusCalculationAbandoned;
664 }
665 double tLim = 1. / fLim;
666
667 std::vector<std::pair<Particle, double> > secondaries;
668 // Keep track of the previous coordinates for distance histogramming.
669 double xLast = x;
670 double yLast = y;
671 double zLast = z;
672 auto hEnergy = hole ? m_histHoleEnergy : m_histElectronEnergy;
673 // Trace the electron/hole.
674 size_t nColl = 0;
675 size_t nCollPlot = 0;
676 while (1) {
677 // Make sure the kinetic energy exceeds the transport cut.
678 if (en < m_deltaCut) {
679 if (m_debug) std::cout << " Kinetic energy below transport cut.\n";
680 status = StatusBelowTransportCut;
681 break;
682 }
683
684 // Fill the energy distribution histogram.
685 if (hEnergy) hEnergy->Fill(en);
686
687 // Make sure the particle is within the specified time window.
688 if (m_hasTimeWindow && (t < m_tMin || t > m_tMax)) {
689 if (m_debug) std::cout << " Outside the time window.\n";
690 status = StatusOutsideTimeWindow;
691 break;
692 }
693
694 if (medium->GetId() != mid) {
695 // Medium has changed.
696 if (!medium->IsMicroscopic()) {
697 // Electron/hole has left the microscopic drift medium.
698 if (m_debug) std::cout << " Not in a microscopic medium.\n";
699 status = StatusLeftDriftMedium;
700 break;
701 }
702 mid = medium->GetId();
703 // TODO
704 // sc = (medium->IsSemiconductor() && m_useBandStructure);
705 // Update the null-collision rate.
706 fLim = medium->GetElectronNullCollisionRate(band);
707 if (fLim <= 0.) {
708 std::cerr << m_className
709 << "::TransportElectron: Got null-collision rate <= 0.\n";
710 status = StatusCalculationAbandoned;
711 break;
712 }
713 tLim = 1. / fLim;
714 }
715
716 // Calculate the initial velocity vector.
717 const double vmag = c1 * sqrt(en);
718 double vx = vmag * kx;
719 double vy = vmag * ky;
720 double vz = vmag * kz;
721 const double a1 = vx * ex + vy * ey + vz * ez;
722 const double a2 = c2 * (ex * ex + ey * ey + ez * ez);
723
724 if (m_userHandleStep) {
725 m_userHandleStep(x, y, z, t, en, kx, ky, kz, hole);
726 }
727
728 // Energy after the step.
729 double en1 = en;
730 // Determine the timestep.
731 double dt = 0.;
732 bool isNullCollision = true;
733 while (isNullCollision) {
734 // Sample the flight time.
735 const double r = RndmUniformPos();
736 dt += -log(r) * tLim;
737 // Calculate the energy after the proposed step.
738 en1 = std::max(en + (a1 + a2 * dt) * dt, Small);
739 // Get the real collision rate at the updated energy.
740 const double fReal = medium->GetElectronCollisionRate(en1, band);
741 if (fReal <= 0.) {
742 std::cerr << m_className << "::TransportElectron:\n"
743 << " Got collision rate <= 0 at " << en1
744 << " eV (band " << band << ").\n";
745 path.emplace_back(MakePoint(x, y, z, t, en1, kx, ky, kz, band));
746 return StatusCalculationAbandoned;
747 }
748 if (fReal > fLim) {
749 // Real collision rate is higher than null-collision rate.
750 dt += log(r) * tLim;
751 // Increase the null collision rate and try again.
752 std::cerr << m_className << "::TransportElectron: "
753 << "Increasing null-collision rate by 5%.\n";
754 fLim *= 1.05;
755 tLim = 1. / fLim;
756 continue;
757 }
758 if (m_useNullCollisionSteps) break;
759 // Check for real or null collision.
760 if (RndmUniform() <= fReal * tLim) isNullCollision = false;
761 }
762
763 // Increase the collision counters.
764 ++nColl;
765 ++nCollPlot;
766
767 // Calculate the direction at the instant before the collision.
768 const double b1 = sqrt(en / en1);
769 const double b2 = 0.5 * c1 * dt / sqrt(en1);
770 double kx1 = kx * b1 + ex * b2;
771 double ky1 = ky * b1 + ey * b2;
772 double kz1 = kz * b1 + ez * b2;
773
774 // Calculate the step in coordinate space.
775 const double b3 = dt * dt * c2;
776 double x1 = x + vx * dt + ex * b3;
777 double y1 = y + vy * dt + ey * b3;
778 double z1 = z + vz * dt + ez * b3;
779 double t1 = t + dt;
780
781 // Get the electric field and medium at the proposed new position.
782 m_sensor->ElectricField(x1, y1, z1, ex, ey, ez, medium, status);
783 if (!hole) {
784 ex = -ex;
785 ey = -ey;
786 ez = -ez;
787 }
788
789 double xc = x, yc = y, zc = z, rc = 0.;
790 // Is the particle still inside a drift medium/the drift area?
791 if (status != 0) {
792 // Try to terminate the drift line close to the boundary (endpoint
793 // outside the drift medium/drift area) using iterative bisection.
794 Terminate(x, y, z, t, x1, y1, z1, t1);
795 if (m_debug) std::cout << " Left the drift medium.\n";
796 status = StatusLeftDriftMedium;
797 } else if (!m_sensor->IsInArea(x1, y1, z1)) {
798 Terminate(x, y, z, t, x1, y1, z1, t1);
799 if (m_debug) std::cout << " Left the drift area.\n";
800 status = StatusLeftDriftArea;
801 } else if (m_sensor->CrossedWire(x, y, z, x1, y1, z1,
802 xc, yc, zc, false, rc)) {
803 t1 = t + dt * Mag(xc - x, yc - y, zc - z) /
804 Mag(x1 - x, y1 - y, z1 - z);
805 x1 = xc;
806 y1 = yc;
807 z1 = zc;
808 if (m_debug) std::cout << " Hit a wire.\n";
809 status = StatusLeftDriftMedium;
810 } else if (m_sensor->CrossedPlane(x, y, z, x1, y1, z1, xc, yc, zc)) {
811 t1 = t + dt * Mag(xc - x, yc - y, zc - z) /
812 Mag(x1 - x, y1 - y, z1 - z);
813 x1 = xc;
814 y1 = yc;
815 z1 = zc;
816 if (m_debug) std::cout << " Hit a plane.\n";
817 status = StatusHitPlane;
818 }
819
820 // If switched on, calculate the induced signal.
821 if (signal) AddSignal(x, y, z, t, x1, y1, z1, t1, hole);
822
823 // Update the coordinates.
824 if (m_computePathLength) pathLength += Mag(x1 - x, y1 - y, z1 - z);
825 x = x1;
826 y = y1;
827 z = z1;
828 t = t1;
829
830 if (status != 0) {
831 en = en1;
832 kx = kx1;
833 ky = ky1;
834 kz = kz1;
835 break;
836 }
837
838 if (isNullCollision) {
839 en = en1;
840 kx = kx1;
841 ky = ky1;
842 kz = kz1;
843 continue;
844 }
845
846 // Get the collision type and parameters.
847 int cstype = 0;
848 int level = 0;
849 int ndxc = 0;
850 secondaries.clear();
851 medium->ElectronCollision(en1, cstype, level, en, kx1, ky1, kz1,
852 secondaries, ndxc, band);
853
854 if (m_debug) std::cout << " Collision type " << cstype << ".\n";
855 // If activated, histogram the distance with respect to the
856 // last collision.
857 if (m_histDistance) {
858 FillDistanceHistogram(cstype, x, y, z, xLast, yLast, zLast);
859 }
860
861 if (m_userHandleCollision) {
862 m_userHandleCollision(x, y, z, t, cstype, level, medium, en1, en, kx,
863 ky, kz, kx1, ky1, kz1);
864 }
865 switch (cstype) {
866 // Elastic collision
867 case ElectronCollisionTypeElastic:
868 break;
869 // Ionising collision
870 case ElectronCollisionTypeIonisation:
871 if (m_userHandleIonisation) {
872 m_userHandleIonisation(x, y, z, t, cstype, level, medium);
873 }
874 for (const auto& secondary : secondaries) {
875 if (secondary.first == Particle::Electron) {
876 const double esec = std::max(secondary.second, Small);
877 if (m_histSecondary) m_histSecondary->Fill(esec);
878 // Increment the electron counter.
879 ++m_nElectrons;
880 if (!aval) continue;
881 // Add the secondary electron to the stack.
882 newParticles.emplace_back(std::make_pair(
883 MakePoint(x, y, z, t, esec), false));
884 } else if (secondary.first == Particle::Hole) {
885 const double esec = std::max(secondary.second, Small);
886 // Increment the hole counter.
887 ++m_nHoles;
888 if (!aval) continue;
889 // Add the secondary hole to the stack.
890 newParticles.emplace_back(std::make_pair(
891 MakePoint(x, y, z, t, esec), true));
892 } else if (secondary.first == Particle::Ion) {
893 ++m_nIons;
894 }
895 }
896 break;
897 // Attachment
898 case ElectronCollisionTypeAttachment:
899 if (m_userHandleAttachment) {
900 m_userHandleAttachment(x, y, z, t, cstype, level, medium);
901 }
902 if (hole) {
903 --m_nHoles;
904 } else {
905 --m_nElectrons;
906 }
907 path.emplace_back(MakePoint(x, y, z, t, en, kx1, ky1, kz1, band));
908 return StatusAttached;
909 break;
910 // Inelastic collision
911 case ElectronCollisionTypeInelastic:
912 if (m_userHandleInelastic) {
913 m_userHandleInelastic(x, y, z, t, cstype, level, medium);
914 }
915 break;
916 // Excitation
917 case ElectronCollisionTypeExcitation:
918 if (m_userHandleInelastic) {
919 m_userHandleInelastic(x, y, z, t, cstype, level, medium);
920 }
921 if (ndxc <= 0) break;
922 // Get the electrons/photons produced in the deexcitation cascade.
923 for (int j = ndxc; j--;) {
924 double tdx = 0., sdx = 0., edx = 0.;
925 int typedx = 0;
926 if (!medium->GetDeexcitationProduct(j, tdx, sdx, typedx, edx)) {
927 std::cerr << m_className << "::TransportElectron: "
928 << "Cannot retrieve deexcitation product " << j
929 << "/" << ndxc << ".\n";
930 break;
931 }
932
933 if (typedx == DxcProdTypeElectron) {
934 // Penning ionisation
935 double xp = x, yp = y, zp = z;
936 if (sdx > Small) {
937 // Randomise the point of creation.
938 double dxp = 0., dyp = 0., dzp = 0.;
939 RndmDirection(dxp, dyp, dzp);
940 xp += sdx * dxp;
941 yp += sdx * dyp;
942 zp += sdx * dzp;
943 }
944 // Get the electric field and medium at this location.
945 Medium* med = nullptr;
946 double fx = 0., fy = 0., fz = 0.;
947 m_sensor->ElectricField(xp, yp, zp, fx, fy, fz, med, status);
948 // Check if this location is inside a drift medium/area.
949 if (status != 0 || !m_sensor->IsInArea(xp, yp, zp)) continue;
950 // Make sure we haven't jumped across a wire.
951 if (m_sensor->CrossedWire(x, y, z, xp, yp, zp, xc, yc, zc,
952 false, rc)) {
953 continue;
954 }
955 // Increment the electron and ion counters.
956 ++m_nElectrons;
957 ++m_nIons;
958 if (m_userHandleIonisation) {
959 m_userHandleIonisation(xp, yp, zp, t, cstype, level, medium);
960 }
961 if (!aval) continue;
962 // Add the Penning electron to the list.
963 newParticles.emplace_back(std::make_pair(
964 MakePoint(xp, yp, zp, t + tdx, std::max(edx, Small)), false));
965 } else if (typedx == DxcProdTypePhoton && m_usePhotons &&
966 edx > m_gammaCut) {
967 // Radiative de-excitation
968 if (aval) TransportPhoton(x, y, z, t + tdx, edx, newParticles);
969 }
970 }
971 break;
972 // Super-elastic collision
973 case ElectronCollisionTypeSuperelastic:
974 break;
975 // Virtual/null collision
976 case ElectronCollisionTypeVirtual:
977 break;
978 // Acoustic phonon scattering (intravalley)
979 case ElectronCollisionTypeAcousticPhonon:
980 break;
981 // Optical phonon scattering (intravalley)
982 case ElectronCollisionTypeOpticalPhonon:
983 break;
984 // Intervalley scattering (phonon assisted)
985 case ElectronCollisionTypeIntervalleyG:
986 case ElectronCollisionTypeIntervalleyF:
987 case ElectronCollisionTypeInterbandXL:
988 case ElectronCollisionTypeInterbandXG:
989 case ElectronCollisionTypeInterbandLG:
990 break;
991 // Coulomb scattering
992 case ElectronCollisionTypeImpurity:
993 break;
994 default:
995 std::cerr << m_className
996 << "::TransportElectron: Unknown collision type.\n";
997 break;
998 }
999 if (m_viewer) PlotCollision(cstype, did, x, y, z, nCollPlot);
1000
1001 // Update the direction vector.
1002 kx = kx1;
1003 ky = ky1;
1004 kz = kz1;
1005
1006 // Normalise the direction vector.
1007 if (nColl % 100 == 0) Normalise(kx, ky, kz);
1008
1009 // Add a new point to the drift line (if enabled).
1010 if (m_storeDriftLines && nColl >= m_nCollSkip) {
1011 path.emplace_back(MakePoint(x, y, z, t, en, kx, ky, kz, band));
1012 nColl = 0;
1013 }
1014 if (m_viewer && nCollPlot >= m_nCollPlot) {
1015 m_viewer->AddDriftLinePoint(did, x, y, z);
1016 nCollPlot = 0;
1017 }
1018 }
1019
1020 if (nColl > 0) {
1021 path.emplace_back(MakePoint(x, y, z, t, en, kx, ky, kz, band));
1022 }
1023 if (m_viewer && nCollPlot > 0) {
1024 m_viewer->AddDriftLinePoint(did, x, y, z);
1025 }
1026 if (m_debug) {
1027 std::cout << " Drift line stops at ("
1028 << x << ", " << y << ", " << z << ").\n";
1029 }
1030 return status;
1031}
1032
1033int AvalancheMicroscopic::TransportElectronBfield(const Point& p0,
1034 const bool hole, const bool aval, const bool signal,
1035 std::vector<Point>& path,
1036 std::vector<std::pair<Point, bool> >& newParticles,
1037 double& pathLength) {
1038
1039 pathLength = 0.;
1040 double x = p0.x;
1041 double y = p0.y;
1042 double z = p0.z;
1043 double t = p0.t;
1044 double en = p0.energy;
1045 int band = p0.band;
1046 double kx = p0.kx;
1047 double ky = p0.ky;
1048 double kz = p0.kz;
1049 path.push_back(p0);
1050 size_t did = 0;
1051 if (m_viewer) {
1052 if (hole) {
1053 did = m_viewer->NewDriftLine(Particle::Hole, 1, x, y, z);
1054 } else {
1055 did = m_viewer->NewDriftLine(Particle::Electron, 1, x, y, z);
1056 }
1057 }
1058
1059 // Numerical prefactors in equation of motion
1060 const double c1 = SpeedOfLight * sqrt(2. / ElectronMass);
1061 const double c2 = 0.25 * c1 * c1;
1062
1063 // Get the local electric field and medium.
1064 double ex = 0., ey = 0., ez = 0.;
1065 Medium* medium = nullptr;
1066 int status = 0;
1067 m_sensor->ElectricField(x, y, z, ex, ey, ez, medium, status);
1068 // Sign change for electrons.
1069 if (!hole) {
1070 ex = -ex;
1071 ey = -ey;
1072 ez = -ez;
1073 }
1074 if (m_debug) {
1075 std::cout << " Drift line starts at ("
1076 << x << ", " << y << ", " << z << ").\n"
1077 << " Status: " << status << "\n";
1078 if (medium) std::cout << " Medium: " << medium->GetName() << "\n";
1079 }
1080
1081 if (status != 0 || !medium || !medium->IsDriftable() ||
1082 !medium->IsMicroscopic()) {
1083 if (m_debug) std::cout << " Not in a valid medium.\n";
1084 return StatusLeftDriftMedium;
1085 }
1086 // Get the id number of the drift medium.
1087 auto mid = medium->GetId();
1088 // Get the null-collision rate.
1089 double fLim = medium->GetElectronNullCollisionRate(band);
1090 if (fLim <= 0.) {
1091 std::cerr << m_className
1092 << "::TransportElectron: Got null-collision rate <= 0.\n";
1093 return StatusCalculationAbandoned;
1094 }
1095 double tLim = 1. / fLim;
1096
1097 std::array<std::array<double, 3>, 3> rot;
1098 // Get the local magnetic field.
1099 double bx = 0., by = 0., bz = 0.;
1100 int st = 0;
1101 m_sensor->MagneticField(x, y, z, bx, by, bz, st);
1102 const double scale = hole ? Tesla2Internal : -Tesla2Internal;
1103 bx *= scale;
1104 by *= scale;
1105 bz *= scale;
1106 double bmag = Mag(bx, by, bz);
1107 // Calculate the rotation matrix to a local coordinate system
1108 // with B along x and E in the x-z plane.
1109 RotationMatrix(bx, by, bz, bmag, ex, ey, ez, rot);
1110 // Calculate the cyclotron frequency.
1111 double omega = OmegaCyclotronOverB * bmag;
1112 // Calculate the electric field in the local frame.
1113 ToLocal(rot, ex, ey, ez, ex, ey, ez);
1114 // Ratio of transverse electric field component and magnetic field.
1115 double ezovb = bmag > Small ? ez / bmag : 0.;
1116
1117 std::vector<std::pair<Particle, double> > secondaries;
1118 // Keep track of the previous coordinates for distance histogramming.
1119 double xLast = x;
1120 double yLast = y;
1121 double zLast = z;
1122 auto hEnergy = hole ? m_histHoleEnergy : m_histElectronEnergy;
1123 // Trace the electron/hole.
1124 size_t nColl = 0;
1125 size_t nCollPlot = 0;
1126 while (1) {
1127 // Make sure the kinetic energy exceeds the transport cut.
1128 if (en < m_deltaCut) {
1129 if (m_debug) std::cout << " Kinetic energy below transport cut.\n";
1130 status = StatusBelowTransportCut;
1131 break;
1132 }
1133
1134 // Fill the energy distribution histogram.
1135 if (hEnergy) hEnergy->Fill(en);
1136
1137 // Make sure the particle is within the specified time window.
1138 if (m_hasTimeWindow && (t < m_tMin || t > m_tMax)) {
1139 if (m_debug) std::cout << " Outside the time window.\n";
1140 status = StatusOutsideTimeWindow;
1141 break;
1142 }
1143
1144 if (medium->GetId() != mid) {
1145 // Medium has changed.
1146 if (!medium->IsMicroscopic()) {
1147 // Electron/hole has left the microscopic drift medium.
1148 if (m_debug) std::cout << " Not in a microscopic medium.\n";
1149 status = StatusLeftDriftMedium;
1150 break;
1151 }
1152 mid = medium->GetId();
1153 // Update the null-collision rate.
1154 fLim = medium->GetElectronNullCollisionRate(band);
1155 if (fLim <= 0.) {
1156 std::cerr << m_className
1157 << "::TransportElectron: Got null-collision rate <= 0.\n";
1158 status = StatusCalculationAbandoned;
1159 break;
1160 }
1161 tLim = 1. / fLim;
1162 }
1163
1164 // Calculate the initial velocity vector in the local frame.
1165 const double vmag = c1 * sqrt(en);
1166 double vx = 0., vy = 0., vz = 0.;
1167 ToLocal(rot, vmag * kx, vmag * ky, vmag * kz, vx, vy, vz);
1168 double a1 = vx * ex;
1169 double a2 = c2 * ex * ex;
1170 if (omega > Small) {
1171 vy -= ezovb;
1172 } else {
1173 a1 += vz * ez;
1174 a2 += c2 * ez * ez;
1175 }
1176
1177 if (m_userHandleStep) {
1178 m_userHandleStep(x, y, z, t, en, kx, ky, kz, hole);
1179 }
1180
1181 // Energy after the step.
1182 double en1 = en;
1183 // Determine the timestep.
1184 double dt = 0.;
1185 // Parameters for B-field stepping.
1186 double cphi = 1., sphi = 0.;
1187 double a3 = 0., a4 = 0.;
1188 bool isNullCollision = true;
1189 while (isNullCollision) {
1190 // Sample the flight time.
1191 const double r = RndmUniformPos();
1192 dt += -log(r) * tLim;
1193 // Calculate the energy after the proposed step.
1194 en1 = en + (a1 + a2 * dt) * dt;
1195 if (omega > Small) {
1196 const double phi = omega * dt;
1197 cphi = cos(phi);
1198 sphi = sin(phi);
1199 a3 = sphi / omega;
1200 a4 = (1. - cphi) / omega;
1201 en1 += ez * (vz * a3 - vy * a4);
1202 }
1203 en1 = std::max(en1, Small);
1204 // Get the real collision rate at the updated energy.
1205 const double fReal = medium->GetElectronCollisionRate(en1, band);
1206 if (fReal <= 0.) {
1207 std::cerr << m_className << "::TransportElectron:\n"
1208 << " Got collision rate <= 0 at " << en1
1209 << " eV (band " << band << ").\n";
1210 path.emplace_back(MakePoint(x, y, z, t, en1, kx, ky, kz, band));
1211 return StatusCalculationAbandoned;
1212 }
1213 if (fReal > fLim) {
1214 // Real collision rate is higher than null-collision rate.
1215 dt += log(r) * tLim;
1216 // Increase the null collision rate and try again.
1217 std::cerr << m_className << "::TransportElectron: "
1218 << "Increasing null-collision rate by 5%.\n";
1219 fLim *= 1.05;
1220 tLim = 1. / fLim;
1221 continue;
1222 }
1223 if (m_useNullCollisionSteps) break;
1224 // Check for real or null collision.
1225 if (RndmUniform() <= fReal * tLim) isNullCollision = false;
1226 }
1227
1228 // Increase the collision counters.
1229 ++nColl;
1230 ++nCollPlot;
1231
1232 // Calculate the direction at the instant before the collision
1233 // and the proposed new position.
1234 double kx1 = 0., ky1 = 0., kz1 = 0.;
1235 double dx = 0., dy = 0., dz = 0.;
1236 // Calculate the new velocity.
1237 double vx1 = vx + 2. * c2 * ex * dt;
1238 double vy1 = vy * cphi + vz * sphi + ezovb;
1239 double vz1 = vz * cphi - vy * sphi;
1240 if (omega < Small) vz1 += 2. * c2 * ez * dt;
1241 // Rotate back to the global frame and normalise.
1242 ToGlobal(rot, vx1, vy1, vz1, kx1, ky1, kz1);
1243 Normalise(kx1, ky1, kz1);
1244 // Calculate the step in coordinate space.
1245 dx = vx * dt + c2 * ex * dt * dt;
1246 if (omega > Small) {
1247 dy = vy * a3 + vz * a4 + ezovb * dt;
1248 dz = vz * a3 - vy * a4;
1249 } else {
1250 dy = vy * dt;
1251 dz = vz * dt + c2 * ez * dt * dt;
1252 }
1253 // Rotate back to the global frame.
1254 ToGlobal(rot, dx, dy, dz, dx, dy, dz);
1255
1256 double x1 = x + dx;
1257 double y1 = y + dy;
1258 double z1 = z + dz;
1259 double t1 = t + dt;
1260 // Get the electric field and medium at the proposed new position.
1261 m_sensor->ElectricField(x1, y1, z1, ex, ey, ez, medium, status);
1262 if (!hole) {
1263 ex = -ex;
1264 ey = -ey;
1265 ez = -ez;
1266 }
1267
1268 double xc = x, yc = y, zc = z, rc = 0.;
1269 // Is the particle still inside a drift medium/the drift area?
1270 if (status != 0) {
1271 // Try to terminate the drift line close to the boundary (endpoint
1272 // outside the drift medium/drift area) using iterative bisection.
1273 Terminate(x, y, z, t, x1, y1, z1, t1);
1274 if (m_debug) std::cout << " Left the drift medium.\n";
1275 status = StatusLeftDriftMedium;
1276 } else if (!m_sensor->IsInArea(x1, y1, z1)) {
1277 Terminate(x, y, z, t, x1, y1, z1, t1);
1278 if (m_debug) std::cout << " Left the drift area.\n";
1279 status = StatusLeftDriftArea;
1280 } else if (m_sensor->CrossedWire(x, y, z, x1, y1, z1,
1281 xc, yc, zc, false, rc)) {
1282 t1 = t + dt * Mag(xc - x, yc - y, zc - z) / Mag(dx, dy, dz);
1283 x1 = xc;
1284 y1 = yc;
1285 z1 = zc;
1286 if (m_debug) std::cout << " Hit a wire.\n";
1287 status = StatusLeftDriftMedium;
1288 } else if (m_sensor->CrossedPlane(x, y, z, x1, y1, z1, xc, yc, zc)) {
1289 t1 = t + dt * Mag(xc - x, yc - y, zc - z) / Mag(dx, dy, dz);
1290 x1 = xc;
1291 y1 = yc;
1292 z1 = zc;
1293 if (m_debug) std::cout << " Hit a plane.\n";
1294 status = StatusHitPlane;
1295 }
1296
1297 // If switched on, calculate the induced signal.
1298 if (signal) AddSignal(x, y, z, t, x1, y1, z1, t1, hole);
1299
1300 // Update the coordinates.
1301 if (m_computePathLength) pathLength += Mag(x1 - x, y1 - y, z1 - z);
1302 x = x1;
1303 y = y1;
1304 z = z1;
1305 t = t1;
1306
1307 if (status != 0) {
1308 en = en1;
1309 kx = kx1;
1310 ky = ky1;
1311 kz = kz1;
1312 break;
1313 }
1314
1315 // Get the magnetic field at the new location.
1316 m_sensor->MagneticField(x, y, z, bx, by, bz, st);
1317 bx *= scale;
1318 by *= scale;
1319 bz *= scale;
1320 bmag = Mag(bx, by, bz);
1321 // Update the rotation matrix.
1322 RotationMatrix(bx, by, bz, bmag, ex, ey, ez, rot);
1323 omega = OmegaCyclotronOverB * bmag;
1324 // Calculate the electric field in the local frame.
1325 ToLocal(rot, ex, ey, ez, ex, ey, ez);
1326 ezovb = bmag > Small ? ez / bmag : 0.;
1327
1328 if (isNullCollision) {
1329 en = en1;
1330 kx = kx1;
1331 ky = ky1;
1332 kz = kz1;
1333 continue;
1334 }
1335
1336 // Get the collision type and parameters.
1337 int cstype = 0;
1338 int level = 0;
1339 int ndxc = 0;
1340 secondaries.clear();
1341 medium->ElectronCollision(en1, cstype, level, en, kx1, ky1, kz1,
1342 secondaries, ndxc, band);
1343
1344 if (m_debug) std::cout << " Collision type " << cstype << ".\n";
1345 // If activated, histogram the distance with respect to the
1346 // last collision.
1347 if (m_histDistance) {
1348 FillDistanceHistogram(cstype, x, y, z, xLast, yLast, zLast);
1349 }
1350
1351 if (m_userHandleCollision) {
1352 m_userHandleCollision(x, y, z, t, cstype, level, medium, en1, en, kx,
1353 ky, kz, kx1, ky1, kz1);
1354 }
1355 switch (cstype) {
1356 // Elastic collision
1357 case ElectronCollisionTypeElastic:
1358 break;
1359 // Ionising collision
1360 case ElectronCollisionTypeIonisation:
1361 if (m_userHandleIonisation) {
1362 m_userHandleIonisation(x, y, z, t, cstype, level, medium);
1363 }
1364 for (const auto& secondary : secondaries) {
1365 if (secondary.first == Particle::Electron) {
1366 const double esec = std::max(secondary.second, Small);
1367 if (m_histSecondary) m_histSecondary->Fill(esec);
1368 // Increment the electron counter.
1369 ++m_nElectrons;
1370 if (!aval) continue;
1371 // Add the secondary electron to the stack.
1372 newParticles.emplace_back(std::make_pair(
1373 MakePoint(x, y, z, t, esec), false));
1374 } else if (secondary.first == Particle::Hole) {
1375 const double esec = std::max(secondary.second, Small);
1376 // Increment the hole counter.
1377 ++m_nHoles;
1378 if (!aval) continue;
1379 // Add the secondary hole to the stack.
1380 newParticles.emplace_back(std::make_pair(
1381 MakePoint(x, y, z, t, esec), true));
1382 } else if (secondary.first == Particle::Ion) {
1383 ++m_nIons;
1384 }
1385 }
1386 break;
1387 // Attachment
1388 case ElectronCollisionTypeAttachment:
1389 if (m_userHandleAttachment) {
1390 m_userHandleAttachment(x, y, z, t, cstype, level, medium);
1391 }
1392 if (hole) {
1393 --m_nHoles;
1394 } else {
1395 --m_nElectrons;
1396 }
1397 path.emplace_back(MakePoint(x, y, z, t, en, kx1, ky1, kz1, band));
1398 return StatusAttached;
1399 break;
1400 // Inelastic collision
1401 case ElectronCollisionTypeInelastic:
1402 if (m_userHandleInelastic) {
1403 m_userHandleInelastic(x, y, z, t, cstype, level, medium);
1404 }
1405 break;
1406 // Excitation
1407 case ElectronCollisionTypeExcitation:
1408 if (m_userHandleInelastic) {
1409 m_userHandleInelastic(x, y, z, t, cstype, level, medium);
1410 }
1411 if (ndxc <= 0) break;
1412 // Get the electrons/photons produced in the deexcitation cascade.
1413 for (int j = ndxc; j--;) {
1414 double tdx = 0., sdx = 0., edx = 0.;
1415 int typedx = 0;
1416 if (!medium->GetDeexcitationProduct(j, tdx, sdx, typedx, edx)) {
1417 std::cerr << m_className << "::TransportElectron: "
1418 << "Cannot retrieve deexcitation product " << j
1419 << "/" << ndxc << ".\n";
1420 break;
1421 }
1422
1423 if (typedx == DxcProdTypeElectron) {
1424 // Penning ionisation
1425 double xp = x, yp = y, zp = z;
1426 if (sdx > Small) {
1427 // Randomise the point of creation.
1428 double dxp = 0., dyp = 0., dzp = 0.;
1429 RndmDirection(dxp, dyp, dzp);
1430 xp += sdx * dxp;
1431 yp += sdx * dyp;
1432 zp += sdx * dzp;
1433 }
1434 // Get the electric field and medium at this location.
1435 Medium* med = nullptr;
1436 double fx = 0., fy = 0., fz = 0.;
1437 m_sensor->ElectricField(xp, yp, zp, fx, fy, fz, med, status);
1438 // Check if this location is inside a drift medium/area.
1439 if (status != 0 || !m_sensor->IsInArea(xp, yp, zp)) continue;
1440 // Make sure we haven't jumped across a wire.
1441 if (m_sensor->CrossedWire(x, y, z, xp, yp, zp, xc, yc, zc,
1442 false, rc)) {
1443 continue;
1444 }
1445 // Increment the electron and ion counters.
1446 ++m_nElectrons;
1447 ++m_nIons;
1448 if (m_userHandleIonisation) {
1449 m_userHandleIonisation(xp, yp, zp, t, cstype, level, medium);
1450 }
1451 if (!aval) continue;
1452 // Add the Penning electron to the list.
1453 newParticles.emplace_back(std::make_pair(
1454 MakePoint(xp, yp, zp, t + tdx, std::max(edx, Small)), false));
1455 } else if (typedx == DxcProdTypePhoton && m_usePhotons &&
1456 edx > m_gammaCut) {
1457 // Radiative de-excitation
1458 if (aval) TransportPhoton(x, y, z, t + tdx, edx, newParticles);
1459 }
1460 }
1461 break;
1462 // Super-elastic collision
1463 case ElectronCollisionTypeSuperelastic:
1464 break;
1465 // Virtual/null collision
1466 case ElectronCollisionTypeVirtual:
1467 break;
1468 // Acoustic phonon scattering (intravalley)
1469 case ElectronCollisionTypeAcousticPhonon:
1470 break;
1471 // Optical phonon scattering (intravalley)
1472 case ElectronCollisionTypeOpticalPhonon:
1473 break;
1474 // Intervalley scattering (phonon assisted)
1475 case ElectronCollisionTypeIntervalleyG:
1476 case ElectronCollisionTypeIntervalleyF:
1477 case ElectronCollisionTypeInterbandXL:
1478 case ElectronCollisionTypeInterbandXG:
1479 case ElectronCollisionTypeInterbandLG:
1480 break;
1481 // Coulomb scattering
1482 case ElectronCollisionTypeImpurity:
1483 break;
1484 default:
1485 std::cerr << m_className
1486 << "::TransportElectron: Unknown collision type.\n";
1487 break;
1488 }
1489 if (m_viewer) PlotCollision(cstype, did, x, y, z, nCollPlot);
1490
1491 // Update the direction vector.
1492 kx = kx1;
1493 ky = ky1;
1494 kz = kz1;
1495
1496 // Normalise the direction vector.
1497 if (nColl % 100 == 0) Normalise(kx, ky, kz);
1498
1499 // Add a new point to the drift line (if enabled).
1500 if (m_storeDriftLines && nColl >= m_nCollSkip) {
1501 path.emplace_back(MakePoint(x, y, z, t, en, kx, ky, kz, band));
1502 nColl = 0;
1503 }
1504 if (m_viewer && nCollPlot >= m_nCollPlot) {
1505 m_viewer->AddDriftLinePoint(did, x, y, z);
1506 nCollPlot = 0;
1507 }
1508 }
1509
1510 if (nColl > 0) {
1511 path.emplace_back(MakePoint(x, y, z, t, en, kx, ky, kz, band));
1512 }
1513 if (m_viewer && nCollPlot > 0) {
1514 m_viewer->AddDriftLinePoint(did, x, y, z);
1515 }
1516 if (m_debug) {
1517 std::cout << " Drift line stops at ("
1518 << x << ", " << y << ", " << z << ").\n";
1519 }
1520 return status;
1521}
1522
1523int AvalancheMicroscopic::TransportElectronSc(const Point& p0,
1524 const bool hole, const bool aval, const bool signal,
1525 std::vector<Point>& path,
1526 std::vector<std::pair<Point, bool> >& newParticles,
1527 double& pathLength) {
1528
1529 pathLength = 0.;
1530 double x = p0.x;
1531 double y = p0.y;
1532 double z = p0.z;
1533 double t = p0.t;
1534 double en = p0.energy;
1535 int band = p0.band;
1536 double kx = p0.kx;
1537 double ky = p0.ky;
1538 double kz = p0.kz;
1539 path.push_back(p0);
1540 size_t did = 0;
1541 if (m_viewer) {
1542 if (hole) {
1543 did = m_viewer->NewDriftLine(Particle::Hole, 1, x, y, z);
1544 } else {
1545 did = m_viewer->NewDriftLine(Particle::Electron, 1, x, y, z);
1546 }
1547 }
1548
1549 // Get the local electric field and medium.
1550 double ex = 0., ey = 0., ez = 0.;
1551 Medium* medium = nullptr;
1552 int status = 0;
1553 m_sensor->ElectricField(x, y, z, ex, ey, ez, medium, status);
1554 // Sign change for electrons.
1555 if (!hole) {
1556 ex = -ex;
1557 ey = -ey;
1558 ez = -ez;
1559 }
1560 if (m_debug) {
1561 std::cout << " Drift line starts at ("
1562 << x << ", " << y << ", " << z << ").\n"
1563 << " Status: " << status << "\n";
1564 if (medium) std::cout << " Medium: " << medium->GetName() << "\n";
1565 }
1566
1567 if (status != 0 || !medium || !medium->IsDriftable() ||
1568 !medium->IsMicroscopic()) {
1569 if (m_debug) std::cout << " Not in a valid medium.\n";
1570 return StatusLeftDriftMedium;
1571 }
1572 // Get the id number of the drift medium.
1573 auto mid = medium->GetId();
1574 // TODO: do we need to re-check this?
1575 // bool sc = (medium->IsSemiconductor() && m_useBandStructure);
1576 // Get the null-collision rate.
1577 double fLim = medium->GetElectronNullCollisionRate(band);
1578 if (fLim <= 0.) {
1579 std::cerr << m_className
1580 << "::TransportElectron: Got null-collision rate <= 0.\n";
1581 return StatusCalculationAbandoned;
1582 }
1583 double tLim = 1. / fLim;
1584
1585 std::vector<std::pair<Particle, double> > secondaries;
1586 // Keep track of the previous coordinates for distance histogramming.
1587 double xLast = x;
1588 double yLast = y;
1589 double zLast = z;
1590 auto hEnergy = hole ? m_histHoleEnergy : m_histElectronEnergy;
1591 // Trace the electron/hole.
1592 size_t nColl = 0;
1593 size_t nCollPlot = 0;
1594 while (1) {
1595 // Make sure the kinetic energy exceeds the transport cut.
1596 if (en < m_deltaCut) {
1597 if (m_debug) std::cout << " Kinetic energy below transport cut.\n";
1598 status = StatusBelowTransportCut;
1599 break;
1600 }
1601
1602 // Fill the energy distribution histogram.
1603 if (hEnergy) hEnergy->Fill(en);
1604
1605 // Make sure the particle is within the specified time window.
1606 if (m_hasTimeWindow && (t < m_tMin || t > m_tMax)) {
1607 if (m_debug) std::cout << " Outside the time window.\n";
1608 status = StatusOutsideTimeWindow;
1609 break;
1610 }
1611
1612 if (medium->GetId() != mid) {
1613 // Medium has changed.
1614 if (!medium->IsMicroscopic()) {
1615 // Electron/hole has left the microscopic drift medium.
1616 if (m_debug) std::cout << " Not in a microscopic medium.\n";
1617 status = StatusLeftDriftMedium;
1618 break;
1619 }
1620 mid = medium->GetId();
1621 // TODO!
1622 // sc = (medium->IsSemiconductor() && m_useBandStructure);
1623 // Update the null-collision rate.
1624 fLim = medium->GetElectronNullCollisionRate(band);
1625 if (fLim <= 0.) {
1626 std::cerr << m_className
1627 << "::TransportElectron: Got null-collision rate <= 0.\n";
1628 status = StatusCalculationAbandoned;
1629 break;
1630 }
1631 tLim = 1. / fLim;
1632 }
1633
1634 // Initial velocity.
1635 double vx = 0., vy = 0., vz = 0.;
1636 en = medium->GetElectronEnergy(kx, ky, kz, vx, vy, vz, band);
1637
1638 if (m_userHandleStep) {
1639 m_userHandleStep(x, y, z, t, en, kx, ky, kz, hole);
1640 }
1641
1642 // Energy after the step.
1643 double en1 = en;
1644 // Determine the timestep.
1645 double dt = 0.;
1646 bool isNullCollision = true;
1647 while (isNullCollision) {
1648 // Sample the flight time.
1649 const double r = RndmUniformPos();
1650 dt += -log(r) * tLim;
1651 // Calculate the energy after the proposed step.
1652 const double cdt = dt * SpeedOfLight;
1653 const double kx1 = kx + ex * cdt;
1654 const double ky1 = ky + ey * cdt;
1655 const double kz1 = kz + ez * cdt;
1656 double vx1 = 0., vy1 = 0., vz1 = 0.;
1657 en1 = medium->GetElectronEnergy(kx1, ky1, kz1, vx1, vy1, vz1, band);
1658 en1 = std::max(en1, Small);
1659 // Get the real collision rate at the updated energy.
1660 const double fReal = medium->GetElectronCollisionRate(en1, band);
1661 if (fReal <= 0.) {
1662 std::cerr << m_className << "::TransportElectron:\n"
1663 << " Got collision rate <= 0 at " << en1
1664 << " eV (band " << band << ").\n";
1665 path.emplace_back(MakePoint(x, y, z, t, en1, kx, ky, kz, band));
1666 return StatusCalculationAbandoned;
1667 }
1668 if (fReal > fLim) {
1669 // Real collision rate is higher than null-collision rate.
1670 dt += log(r) * tLim;
1671 // Increase the null collision rate and try again.
1672 std::cerr << m_className << "::TransportElectron: "
1673 << "Increasing null-collision rate by 5% (band "
1674 << band << ").\n";
1675 fLim *= 1.05;
1676 tLim = 1. / fLim;
1677 continue;
1678 }
1679 if (m_useNullCollisionSteps) break;
1680 // Check for real or null collision.
1681 if (RndmUniform() <= fReal * tLim) isNullCollision = false;
1682 }
1683
1684 // Increase the collision counters.
1685 ++nColl;
1686 ++nCollPlot;
1687
1688 // Calculate the direction at the instant before the collision
1689 // and the proposed new position.
1690 double kx1 = 0., ky1 = 0., kz1 = 0.;
1691 double dx = 0., dy = 0., dz = 0.;
1692 // Update the wave-vector.
1693 const double cdt = dt * SpeedOfLight;
1694 kx1 = kx + ex * cdt;
1695 ky1 = ky + ey * cdt;
1696 kz1 = kz + ez * cdt;
1697 double vx1 = 0., vy1 = 0, vz1 = 0.;
1698 en1 = medium->GetElectronEnergy(kx1, ky1, kz1, vx1, vy1, vz1, band);
1699 dx = 0.5 * (vx + vx1) * dt;
1700 dy = 0.5 * (vy + vy1) * dt;
1701 dz = 0.5 * (vz + vz1) * dt;
1702
1703 double x1 = x + dx;
1704 double y1 = y + dy;
1705 double z1 = z + dz;
1706 double t1 = t + dt;
1707 // Get the electric field and medium at the proposed new position.
1708 m_sensor->ElectricField(x1, y1, z1, ex, ey, ez, medium, status);
1709 if (!hole) {
1710 ex = -ex;
1711 ey = -ey;
1712 ez = -ez;
1713 }
1714
1715 double xc = x, yc = y, zc = z, rc = 0.;
1716 // Is the particle still inside a drift medium/the drift area?
1717 if (status != 0) {
1718 // Try to terminate the drift line close to the boundary (endpoint
1719 // outside the drift medium/drift area) using iterative bisection.
1720 Terminate(x, y, z, t, x1, y1, z1, t1);
1721 if (m_debug) std::cout << " Left the drift medium.\n";
1722 status = StatusLeftDriftMedium;
1723 } else if (!m_sensor->IsInArea(x1, y1, z1)) {
1724 Terminate(x, y, z, t, x1, y1, z1, t1);
1725 if (m_debug) std::cout << " Left the drift area.\n";
1726 status = StatusLeftDriftArea;
1727 } else if (m_sensor->CrossedWire(x, y, z, x1, y1, z1,
1728 xc, yc, zc, false, rc)) {
1729 t1 = t + dt * Mag(xc - x, yc - y, zc - z) / Mag(dx, dy, dz);
1730 x1 = xc;
1731 y1 = yc;
1732 z1 = zc;
1733 if (m_debug) std::cout << " Hit a wire.\n";
1734 status = StatusLeftDriftMedium;
1735 } else if (m_sensor->CrossedPlane(x, y, z, x1, y1, z1, xc, yc, zc)) {
1736 t1 = t + dt * Mag(xc - x, yc - y, zc - z) / Mag(dx, dy, dz);
1737 x1 = xc;
1738 y1 = yc;
1739 z1 = zc;
1740 if (m_debug) std::cout << " Hit a plane.\n";
1741 status = StatusHitPlane;
1742 }
1743
1744 // If switched on, calculate the induced signal.
1745 if (signal) AddSignal(x, y, z, t, x1, y1, z1, t1, hole);
1746
1747 // Update the coordinates.
1748 if (m_computePathLength) pathLength += Mag(x1 - x, y1 - y, z1 - z);
1749 x = x1;
1750 y = y1;
1751 z = z1;
1752 t = t1;
1753
1754 if (status != 0) {
1755 en = en1;
1756 kx = kx1;
1757 ky = ky1;
1758 kz = kz1;
1759 break;
1760 }
1761
1762 if (isNullCollision) {
1763 en = en1;
1764 kx = kx1;
1765 ky = ky1;
1766 kz = kz1;
1767 continue;
1768 }
1769
1770 // Get the collision type and parameters.
1771 int cstype = 0;
1772 int level = 0;
1773 int ndxc = 0;
1774 secondaries.clear();
1775 medium->ElectronCollision(en1, cstype, level, en, kx1, ky1, kz1,
1776 secondaries, ndxc, band);
1777
1778 if (m_debug) std::cout << " Collision type " << cstype << ".\n";
1779 // If activated, histogram the distance with respect to the
1780 // last collision.
1781 if (m_histDistance) {
1782 FillDistanceHistogram(cstype, x, y, z, xLast, yLast, zLast);
1783 }
1784
1785 if (m_userHandleCollision) {
1786 m_userHandleCollision(x, y, z, t, cstype, level, medium, en1, en, kx,
1787 ky, kz, kx1, ky1, kz1);
1788 }
1789 switch (cstype) {
1790 // Elastic collision
1791 case ElectronCollisionTypeElastic:
1792 break;
1793 // Ionising collision
1794 case ElectronCollisionTypeIonisation:
1795 if (m_userHandleIonisation) {
1796 m_userHandleIonisation(x, y, z, t, cstype, level, medium);
1797 }
1798 for (const auto& secondary : secondaries) {
1799 if (secondary.first == Particle::Electron) {
1800 const double esec = std::max(secondary.second, Small);
1801 if (m_histSecondary) m_histSecondary->Fill(esec);
1802 // Increment the electron counter.
1803 ++m_nElectrons;
1804 if (!aval) continue;
1805 // Add the secondary electron to the stack.
1806 double kxs = 0., kys = 0., kzs = 0.;
1807 int bs = -1;
1808 medium->GetElectronMomentum(esec, kxs, kys, kzs, bs);
1809 newParticles.emplace_back(std::make_pair(
1810 MakePoint(x, y, z, t, esec, kxs, kys, kzs, bs), false));
1811 } else if (secondary.first == Particle::Hole) {
1812 const double esec = std::max(secondary.second, Small);
1813 // Increment the hole counter.
1814 ++m_nHoles;
1815 if (!aval) continue;
1816 // Add the secondary hole to the stack.
1817 double kxs = 0., kys = 0., kzs = 0.;
1818 int bs = -1;
1819 medium->GetElectronMomentum(esec, kxs, kys, kzs, bs);
1820 newParticles.emplace_back(std::make_pair(
1821 MakePoint(x, y, z, t, esec, kxs, kys, kzs, bs), true));
1822 } else if (secondary.first == Particle::Ion) {
1823 ++m_nIons;
1824 }
1825 }
1826 break;
1827 // Attachment
1828 case ElectronCollisionTypeAttachment:
1829 if (m_userHandleAttachment) {
1830 m_userHandleAttachment(x, y, z, t, cstype, level, medium);
1831 }
1832 if (hole) {
1833 --m_nHoles;
1834 } else {
1835 --m_nElectrons;
1836 }
1837 path.emplace_back(MakePoint(x, y, z, t, en, kx1, ky1, kz1, band));
1838 return StatusAttached;
1839 break;
1840 // Inelastic collision
1841 case ElectronCollisionTypeInelastic:
1842 if (m_userHandleInelastic) {
1843 m_userHandleInelastic(x, y, z, t, cstype, level, medium);
1844 }
1845 break;
1846 // Excitation
1847 case ElectronCollisionTypeExcitation:
1848 if (m_userHandleInelastic) {
1849 m_userHandleInelastic(x, y, z, t, cstype, level, medium);
1850 }
1851 if (ndxc <= 0) break;
1852 // Get the electrons/photons produced in the deexcitation cascade.
1853 for (int j = ndxc; j--;) {
1854 double tdx = 0., sdx = 0., edx = 0.;
1855 int typedx = 0;
1856 if (!medium->GetDeexcitationProduct(j, tdx, sdx, typedx, edx)) {
1857 std::cerr << m_className << "::TransportElectron: "
1858 << "Cannot retrieve deexcitation product " << j
1859 << "/" << ndxc << ".\n";
1860 break;
1861 }
1862
1863 if (typedx == DxcProdTypeElectron) {
1864 // Penning ionisation
1865 double xp = x, yp = y, zp = z;
1866 if (sdx > Small) {
1867 // Randomise the point of creation.
1868 double dxp = 0., dyp = 0., dzp = 0.;
1869 RndmDirection(dxp, dyp, dzp);
1870 xp += sdx * dxp;
1871 yp += sdx * dyp;
1872 zp += sdx * dzp;
1873 }
1874 // Get the electric field and medium at this location.
1875 Medium* med = nullptr;
1876 double fx = 0., fy = 0., fz = 0.;
1877 m_sensor->ElectricField(xp, yp, zp, fx, fy, fz, med, status);
1878 // Check if this location is inside a drift medium/area.
1879 if (status != 0 || !m_sensor->IsInArea(xp, yp, zp)) continue;
1880 // Make sure we haven't jumped across a wire.
1881 if (m_sensor->CrossedWire(x, y, z, xp, yp, zp, xc, yc, zc,
1882 false, rc)) {
1883 continue;
1884 }
1885 // Increment the electron and ion counters.
1886 ++m_nElectrons;
1887 ++m_nIons;
1888 if (m_userHandleIonisation) {
1889 m_userHandleIonisation(xp, yp, zp, t, cstype, level, medium);
1890 }
1891 if (!aval) continue;
1892 // Add the Penning electron to the list.
1893 newParticles.emplace_back(std::make_pair(
1894 MakePoint(xp, yp, zp, t + tdx, std::max(edx, Small)), false));
1895 } else if (typedx == DxcProdTypePhoton && m_usePhotons &&
1896 edx > m_gammaCut) {
1897 // Radiative de-excitation
1898 if (aval) TransportPhoton(x, y, z, t + tdx, edx, newParticles);
1899 }
1900 }
1901 break;
1902 // Super-elastic collision
1903 case ElectronCollisionTypeSuperelastic:
1904 break;
1905 // Virtual/null collision
1906 case ElectronCollisionTypeVirtual:
1907 break;
1908 // Acoustic phonon scattering (intravalley)
1909 case ElectronCollisionTypeAcousticPhonon:
1910 break;
1911 // Optical phonon scattering (intravalley)
1912 case ElectronCollisionTypeOpticalPhonon:
1913 break;
1914 // Intervalley scattering (phonon assisted)
1915 case ElectronCollisionTypeIntervalleyG:
1916 case ElectronCollisionTypeIntervalleyF:
1917 case ElectronCollisionTypeInterbandXL:
1918 case ElectronCollisionTypeInterbandXG:
1919 case ElectronCollisionTypeInterbandLG:
1920 break;
1921 // Coulomb scattering
1922 case ElectronCollisionTypeImpurity:
1923 break;
1924 default:
1925 std::cerr << m_className
1926 << "::TransportElectron: Unknown collision type.\n";
1927 break;
1928 }
1929 if (m_viewer) PlotCollision(cstype, did, x, y, z, nCollPlot);
1930
1931 // Update the direction vector.
1932 kx = kx1;
1933 ky = ky1;
1934 kz = kz1;
1935
1936 // Add a new point to the drift line (if enabled).
1937 if (m_storeDriftLines && nColl >= m_nCollSkip) {
1938 path.emplace_back(MakePoint(x, y, z, t, en, kx, ky, kz, band));
1939 nColl = 0;
1940 }
1941 if (m_viewer && nCollPlot >= m_nCollPlot) {
1942 m_viewer->AddDriftLinePoint(did, x, y, z);
1943 nCollPlot = 0;
1944 }
1945 }
1946
1947 if (nColl > 0) {
1948 path.emplace_back(MakePoint(x, y, z, t, en, kx, ky, kz, band));
1949 }
1950 if (m_viewer && nCollPlot > 0) {
1951 m_viewer->AddDriftLinePoint(did, x, y, z);
1952 }
1953 if (m_debug) {
1954 std::cout << " Drift line stops at ("
1955 << x << ", " << y << ", " << z << ").\n";
1956 }
1957 return status;
1958}
1959
1960void AvalancheMicroscopic::PlotCollision(const int cstype, const size_t did,
1961 const double x, const double y, const double z,
1962 size_t& nCollPlot) const {
1963 if (!m_viewer) return;
1964 if (cstype == ElectronCollisionTypeIonisation) {
1965 if (m_plotIonisations) {
1966 m_viewer->AddIonisation(x, y, z);
1967 m_viewer->AddDriftLinePoint(did, x, y, z);
1968 nCollPlot = 0;
1969 }
1970 } else if (cstype == ElectronCollisionTypeExcitation) {
1971 if (m_plotExcitations) {
1972 m_viewer->AddExcitation(x, y, z);
1973 m_viewer->AddDriftLinePoint(did, x, y, z);
1974 nCollPlot = 0;
1975 }
1976 } else if (cstype == ElectronCollisionTypeAttachment) {
1977 if (m_plotAttachments) {
1978 m_viewer->AddAttachment(x, y, z);
1979 m_viewer->AddDriftLinePoint(did, x, y, z);
1980 }
1981 }
1982}
1983
1984void AvalancheMicroscopic::FillDistanceHistogram(const int cstype,
1985 const double x, const double y, const double z,
1986 double& xLast, double& yLast, double& zLast) {
1987
1988 for (const auto& htype : m_distanceHistogramType) {
1989 if (htype != cstype) continue;
1990 if (m_debug) std::cout << " Filling distance histogram.\n";
1991 switch (m_distanceOption) {
1992 case 'x':
1993 m_histDistance->Fill(xLast - x);
1994 break;
1995 case 'y':
1996 m_histDistance->Fill(yLast - y);
1997 break;
1998 case 'z':
1999 m_histDistance->Fill(zLast - z);
2000 break;
2001 case 'r':
2002 m_histDistance->Fill(Mag(xLast - x, yLast - y, zLast - z));
2003 break;
2004 }
2005 xLast = x;
2006 yLast = y;
2007 zLast = z;
2008 return;
2009 }
2010}
2011
2012void AvalancheMicroscopic::AddSignal(
2013 const double x0, const double y0, const double z0, const double t0,
2014 const double x1, const double y1, const double z1, const double t1,
2015 const bool hole) const {
2016
2017 const int q = hole ? 1 : -1;
2018 m_sensor->AddSignal(q, t0, t1, x0, y0, z0, x1, y1, z1,
2019 m_integrateWeightingField,
2020 m_useWeightingPotential);
2021}
2022
2023void AvalancheMicroscopic::TransportPhoton(
2024 const double x0, const double y0, const double z0, const double t0,
2025 const double e0, std::vector<std::pair<Point, bool> > & stack) {
2026 // Make sure that the sensor is defined.
2027 if (!m_sensor) {
2028 std::cerr << m_className << "::TransportPhoton: Sensor is not defined.\n";
2029 return;
2030 }
2031
2032 // Make sure that the starting point is inside the active area.
2033 if (!m_sensor->IsInArea(x0, y0, z0)) {
2034 std::cerr << m_className << "::TransportPhoton:\n"
2035 << " No valid field at initial position.\n";
2036 return;
2037 }
2038
2039 // Make sure that the starting point is inside a medium.
2040 Medium* medium = m_sensor->GetMedium(x0, y0, z0);
2041 if (!medium) {
2042 std::cerr << m_className << "::TransportPhoton:\n"
2043 << " No medium at initial position.\n";
2044 return;
2045 }
2046
2047 // Make sure that the medium is "driftable" and microscopic.
2048 if (!medium->IsDriftable() || !medium->IsMicroscopic()) {
2049 std::cerr << m_className << "::TransportPhoton:\n"
2050 << " Medium at initial position does not provide "
2051 << " microscopic tracking data.\n";
2052 return;
2053 }
2054
2055 // Get the id number of the drift medium.
2056 int id = medium->GetId();
2057
2058 // Position
2059 double x = x0, y = y0, z = z0;
2060 double t = t0;
2061 // Initial direction (randomised).
2062 double dx = 0., dy = 0., dz = 0.;
2063 RndmDirection(dx, dy, dz);
2064 // Energy
2065 double e = e0;
2066
2067 // Photon collision rate
2068 double f = medium->GetPhotonCollisionRate(e);
2069 if (f <= 0.) return;
2070 // Timestep
2071 double dt = -log(RndmUniformPos()) / f;
2072 t += dt;
2073 dt *= SpeedOfLight;
2074 x += dt * dx;
2075 y += dt * dy;
2076 z += dt * dz;
2077
2078 // Check if the photon is still inside a medium.
2079 medium = m_sensor->GetMedium(x, y, z);
2080 if (!medium || medium->GetId() != id) {
2081 // Try to terminate the photon track close to the boundary
2082 // by means of iterative bisection.
2083 dx *= dt;
2084 dy *= dt;
2085 dz *= dt;
2086 x -= dx;
2087 y -= dy;
2088 z -= dz;
2089 double delta = Mag(dx, dy, dz);
2090 if (delta > 0) {
2091 dx /= delta;
2092 dy /= delta;
2093 dz /= delta;
2094 }
2095 // Mid-point
2096 double xM = x, yM = y, zM = z;
2097 while (delta > BoundaryDistance) {
2098 delta *= 0.5;
2099 dt *= 0.5;
2100 xM = x + delta * dx;
2101 yM = y + delta * dy;
2102 zM = z + delta * dz;
2103 // Check if the mid-point is inside the drift medium.
2104 medium = m_sensor->GetMedium(xM, yM, zM);
2105 if (medium && medium->GetId() == id) {
2106 x = xM;
2107 y = yM;
2108 z = zM;
2109 t += dt;
2110 }
2111 }
2112 photon newPhoton;
2113 newPhoton.x0 = x0;
2114 newPhoton.y0 = y0;
2115 newPhoton.z0 = z0;
2116 newPhoton.x1 = x;
2117 newPhoton.y1 = y;
2118 newPhoton.z1 = z;
2119 newPhoton.energy = e0;
2120 newPhoton.status = StatusLeftDriftMedium;
2121 m_photons.push_back(std::move(newPhoton));
2122 if (m_viewer) m_viewer->AddPhoton(x0, y0, z0, x, y, z);
2123 return;
2124 }
2125
2126 int type, level;
2127 double e1;
2128 double ctheta = 0.;
2129 int nsec = 0;
2130 double esec = 0.;
2131 if (!medium->GetPhotonCollision(e, type, level, e1, ctheta, nsec, esec))
2132 return;
2133
2134 if (type == PhotonCollisionTypeIonisation) {
2135 // Add the secondary electron (random direction) to the stack.
2136 if (m_sizeCut == 0 || stack.size() < m_sizeCut) {
2137 stack.emplace_back(std::make_pair(
2138 MakePoint(x, y, z, t, std::max(esec, Small)), false));
2139 }
2140 // Increment the electron and ion counters.
2141 ++m_nElectrons;
2142 ++m_nIons;
2143 } else if (type == PhotonCollisionTypeExcitation) {
2144 double tdx = 0.;
2145 double sdx = 0.;
2146 int typedx = 0;
2147 std::vector<double> tPhotons;
2148 std::vector<double> ePhotons;
2149 for (int j = nsec; j--;) {
2150 if (!medium->GetDeexcitationProduct(j, tdx, sdx, typedx, esec)) continue;
2151 if (typedx == DxcProdTypeElectron) {
2152 // Ionisation.
2153 stack.emplace_back(std::make_pair(
2154 MakePoint(x, y, z, t + tdx, std::max(esec, Small)), false));
2155 // Increment the electron and ion counters.
2156 ++m_nElectrons;
2157 ++m_nIons;
2158 } else if (typedx == DxcProdTypePhoton && m_usePhotons &&
2159 esec > m_gammaCut) {
2160 // Radiative de-excitation
2161 tPhotons.push_back(t + tdx);
2162 ePhotons.push_back(esec);
2163 }
2164 }
2165 // Transport the photons (if any).
2166 const int nSizePhotons = tPhotons.size();
2167 for (int k = nSizePhotons; k--;) {
2168 TransportPhoton(x, y, z, tPhotons[k], ePhotons[k], stack);
2169 }
2170 }
2171
2172 photon newPhoton;
2173 newPhoton.x0 = x0;
2174 newPhoton.y0 = y0;
2175 newPhoton.z0 = z0;
2176 newPhoton.x1 = x;
2177 newPhoton.y1 = y;
2178 newPhoton.z1 = z;
2179 newPhoton.energy = e0;
2180 newPhoton.status = -2;
2181 if (m_viewer) m_viewer->AddPhoton(x0, y0, z0, x, y, z);
2182 m_photons.push_back(std::move(newPhoton));
2183}
2184
2185void AvalancheMicroscopic::Terminate(double x0, double y0, double z0, double t0,
2186 double& x1, double& y1, double& z1,
2187 double& t1) {
2188 const double dx = x1 - x0;
2189 const double dy = y1 - y0;
2190 const double dz = z1 - z0;
2191 double d = Mag(dx, dy, dz);
2192 while (d > BoundaryDistance) {
2193 d *= 0.5;
2194 const double xm = 0.5 * (x0 + x1);
2195 const double ym = 0.5 * (y0 + y1);
2196 const double zm = 0.5 * (z0 + z1);
2197 const double tm = 0.5 * (t0 + t1);
2198 // Check if the mid-point is inside the drift medium.
2199 if (m_sensor->IsInside(xm, ym, zm) && m_sensor->IsInArea(xm, ym, zm)) {
2200 x0 = xm;
2201 y0 = ym;
2202 z0 = zm;
2203 t0 = tm;
2204 } else {
2205 x1 = xm;
2206 y1 = ym;
2207 z1 = zm;
2208 t1 = tm;
2209 }
2210 }
2211 // TODO
2212 bool outsideMedium = true;
2213 while (outsideMedium) {
2214 d *= 0.5;
2215 const double xm = 0.5 * (x0 + x1);
2216 const double ym = 0.5 * (y0 + y1);
2217 const double zm = 0.5 * (z0 + z1);
2218 const double tm = 0.5 * (t0 + t1);
2219 // Check if the mid-point is inside the drift medium.
2220 if (m_sensor->IsInside(xm, ym, zm) && m_sensor->IsInArea(xm, ym, zm)) {
2221 outsideMedium = false;
2222 }
2223 x1 = xm;
2224 y1 = ym;
2225 z1 = zm;
2226 t1 = tm;
2227 }
2228}
2229
2230} // namespace Garfield
void EnableDistanceHistogramming(const int type)
Fill distance distribution histograms for a given collision type.
void GetHoleDriftLinePoint(double &x, double &y, double &z, double &t, const size_t ip, const size_t ih=0) const
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 callback function to be called at every step.
void EnableHoleEnergyHistogramming(TH1 *histo)
Fill a histogram with the hole energy distribution.
void AddElectron(const double x, const double y, const double z, const double t, const double e, const double dx=0., const double dy=0., const double dz=0.)
Add an electron to the list of particles to be transported.
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 callback function to be called at every (real) collision.
void GetHoleEndpoint(const size_t 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 SetUserHandleIonisation(void(*f)(double x, double y, double z, double t, int type, int level, Medium *m))
size_t GetNumberOfElectronDriftLinePoints(const size_t i=0) const
void GetPhoton(const size_t i, double &e, double &x0, double &y0, double &z0, double &t0, double &x1, double &y1, double &z1, double &t1, int &status) const
bool DriftElectron(const double x, const double y, const double z, const double t, const double e, const double dx=0., const double dy=0., const double dz=0.)
AvalancheMicroscopic()
Default constructor.
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.
bool AvalancheElectron(const double x, const double y, const double z, const double t, const double e, const double dx=0., const double dy=0., const double dz=0.)
Calculate an avalanche initiated by a given electron.
void EnableElectronEnergyHistogramming(TH1 *histo)
Fill a histogram with the electron energy distribution.
bool ResumeAvalanche()
Continue the avalanche simulation from the current set of electrons.
void DisableDistanceHistogramming()
Stop filling distance distribution histograms.
void GetElectronEndpoint(const size_t 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 EnableSecondaryEnergyHistogramming(TH1 *histo)
Fill histograms of the energy of electrons emitted in ionising collisions.
void EnablePlotting(ViewDrift *view, const size_t nColl=100)
Switch on drift line plotting.
size_t GetNumberOfHoleDriftLinePoints(const size_t 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 GetElectronDriftLinePoint(double &x, double &y, double &z, double &t, const size_t ip, const size_t ie=0) const
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:16
bool HasMagneticField() const
Does the sensor have a non-zero magnetic field?
Definition Sensor.cc:388
bool IsInArea(const double x, const double y, const double z)
Check if a point is inside the user area.
Definition Sensor.cc:260
size_t GetNumberOfElectrodes() const
Get the number of electrodes attached to the sensor.
Definition Sensor.hh:43
Visualize drift lines and tracks.
Definition ViewDrift.hh:19
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:128
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