Garfield++ 5.0
A toolkit for the detailed simulation of particle detectors based on ionisation measurement in gases and semiconductors
Loading...
Searching...
No Matches
AvalancheMC.cc
Go to the documentation of this file.
1#include <cmath>
2#include <fstream>
3#include <iostream>
4#include <numeric>
5#include <string>
6
10#include "Garfield/Numerics.hh"
11#include "Garfield/Random.hh"
12
13namespace {
14
15Garfield::AvalancheMC::Point MakePoint(const std::array<double, 3>& x,
16 const double t) {
18 p.x = x[0];
19 p.y = x[1];
20 p.z = x[2];
21 p.t = t;
22 return p;
23}
24
25Garfield::AvalancheMC::Point MakePoint(const double x, const double y,
26 const double z, const double t) {
28 p.x = x;
29 p.y = y;
30 p.z = z;
31 p.t = t;
32 return p;
33}
34
35std::string PrintVec(const std::array<double, 3>& x) {
36 return "(" + std::to_string(x[0]) + ", " + std::to_string(x[1]) + ", " +
37 std::to_string(x[2]) + ")";
38}
39
40double Mag(const std::array<double, 3>& x) {
41 return sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
42}
43
44double Dist(const std::array<double, 3>& x0,
45 const std::array<double, 3>& x1) {
46 std::array<double, 3> d = x1;
47 for (size_t i = 0; i < 3; ++i) d[i] -= x0[i];
48 return Mag(d);
49}
50
51double Slope(const double x0, const double x1) {
52 return (x0 > 0. && x1 > 0.) ? fabs(x1 - x0) / (x0 + x1) : 0.;
53}
54
55std::array<double, 3> MidPoint(const std::array<double, 3>& x0,
56 const std::array<double, 3>& x1) {
57 std::array<double, 3> xm;
58 for (size_t k = 0; k < 3; ++k) xm[k] = 0.5 * (x0[k] + x1[k]);
59 return xm;
60}
61
62} // namespace
63
64namespace Garfield {
65
66AvalancheMC::AvalancheMC(Sensor* sensor) : m_sensor(sensor) { }
67
69 if (!sensor) {
70 std::cerr << m_className << "::SetSensor: Null pointer.\n";
71 return;
72 }
73
74 m_sensor = sensor;
75}
76
78 if (!view) {
79 std::cerr << m_className << "::EnablePlotting: Null pointer.\n";
80 return;
81 }
82
83 m_viewer = view;
84}
85
86void AvalancheMC::SetTimeSteps(const double d) {
87 m_stepModel = StepModel::FixedTime;
88 if (d < Small) {
89 std::cerr << m_className << "::SetTimeSteps:\n "
90 << "Step size is too small. Using default (20 ps) instead.\n";
91 m_tMc = 0.02;
92 return;
93 }
94 if (m_debug) {
95 std::cout << m_className << "::SetTimeSteps:\n"
96 << " Step size set to " << d << " ns.\n";
97 }
98 m_tMc = d;
99}
100
101void AvalancheMC::SetDistanceSteps(const double d) {
102 m_stepModel = StepModel::FixedDistance;
103 if (d < Small) {
104 std::cerr << m_className << "::SetDistanceSteps:\n "
105 << "Step size is too small. Using default (10 um) instead.\n";
106 m_dMc = 0.001;
107 return;
108 }
109 if (m_debug) {
110 std::cout << m_className << "::SetDistanceSteps:\n"
111 << " Step size set to " << d << " cm.\n";
112 }
113 m_dMc = d;
114}
115
116void AvalancheMC::SetCollisionSteps(const unsigned int n) {
117 m_stepModel = StepModel::CollisionTime;
118 if (n < 1) {
119 std::cerr << m_className << "::SetCollisionSteps:\n "
120 << "Number of collisions set to default value (100).\n";
121 m_nMc = 100;
122 return;
123 }
124 if (m_debug) {
125 std::cout << m_className << "::SetCollisionSteps:\n "
126 << "Number of collisions to be skipped set to " << n << ".\n";
127 }
128 m_nMc = n;
129}
130
131void AvalancheMC::SetStepDistanceFunction(double (*f)(double x, double y,
132 double z)) {
133 if (!f) {
134 std::cerr << m_className << "::SetStepDistanceFunction: Null pointer.\n";
135 return;
136 }
137 m_fStep = f;
138 m_stepModel = StepModel::UserDistance;
139}
140
141void AvalancheMC::SetTimeWindow(const double t0, const double t1) {
142 if (fabs(t1 - t0) < Small) {
143 std::cerr << m_className << "::SetTimeWindow:\n"
144 << " Time interval must be greater than zero.\n";
145 return;
146 }
147
148 m_tMin = std::min(t0, t1);
149 m_tMax = std::max(t0, t1);
150 m_hasTimeWindow = true;
151}
152
153void AvalancheMC::GetHoleEndpoint(const size_t i, double& x0, double& y0,
154 double& z0, double& t0, double& x1,
155 double& y1, double& z1, double& t1,
156 int& status) const {
157 if (i >= m_holes.size()) {
158 std::cerr << m_className << "::GetHoleEndpoint: Index out of range.\n";
159 return;
160 }
161
162 x0 = m_holes[i].path.front().x;
163 y0 = m_holes[i].path.front().y;
164 z0 = m_holes[i].path.front().z;
165 t0 = m_holes[i].path.front().t;
166 x1 = m_holes[i].path.back().x;
167 y1 = m_holes[i].path.back().y;
168 z1 = m_holes[i].path.back().z;
169 t1 = m_holes[i].path.back().t;
170 status = m_holes[i].status;
171}
172
173void AvalancheMC::GetIonEndpoint(const size_t i, double& x0, double& y0,
174 double& z0, double& t0, double& x1, double& y1,
175 double& z1, double& t1, int& status) const {
176 if (i >= m_ions.size()) {
177 std::cerr << m_className << "::GetIonEndpoint: Index out of range.\n";
178 return;
179 }
180
181 x0 = m_ions[i].path.front().x;
182 y0 = m_ions[i].path.front().y;
183 z0 = m_ions[i].path.front().z;
184 t0 = m_ions[i].path.front().t;
185 x1 = m_ions[i].path.back().x;
186 y1 = m_ions[i].path.back().y;
187 z1 = m_ions[i].path.back().z;
188 t1 = m_ions[i].path.back().t;
189 status = m_ions[i].status;
190}
191
192void AvalancheMC::GetElectronEndpoint(const size_t i, double& x0,
193 double& y0, double& z0, double& t0,
194 double& x1, double& y1, double& z1,
195 double& t1, int& status) const {
196 if (i >= m_electrons.size()) {
197 std::cerr << m_className << "::GetElectronEndpoint: Index out of range.\n";
198 return;
199 }
200
201 x0 = m_electrons[i].path.front().x;
202 y0 = m_electrons[i].path.front().y;
203 z0 = m_electrons[i].path.front().z;
204 t0 = m_electrons[i].path.front().t;
205 x1 = m_electrons[i].path.back().x;
206 y1 = m_electrons[i].path.back().y;
207 z1 = m_electrons[i].path.back().z;
208 t1 = m_electrons[i].path.back().t;
209 status = m_electrons[i].status;
210}
211
212bool AvalancheMC::DriftElectron(const double x0, const double y0,
213 const double z0, const double t0) {
214
215 std::vector<std::pair<Point, Particle> > particles;
216 particles.emplace_back(std::make_pair(MakePoint(x0, y0, z0, t0),
218 return TransportParticles(particles, true, false, false);
219}
220
221bool AvalancheMC::DriftHole(const double x0, const double y0, const double z0,
222 const double t0) {
223 std::vector<std::pair<Point, Particle> > particles;
224 particles.emplace_back(std::make_pair(MakePoint(x0, y0, z0, t0),
226 return TransportParticles(particles, false, true, false);
227}
228
229bool AvalancheMC::DriftIon(const double x0, const double y0, const double z0,
230 const double t0) {
231 std::vector<std::pair<Point, Particle> > particles;
232 particles.emplace_back(std::make_pair(MakePoint(x0, y0, z0, t0),
234 return TransportParticles(particles, false, true, false);
235}
236
237bool AvalancheMC::DriftNegativeIon(const double x0, const double y0,
238 const double z0, const double t0) {
239 std::vector<std::pair<Point, Particle> > particles;
240 particles.emplace_back(std::make_pair(MakePoint(x0, y0, z0, t0),
242 return TransportParticles(particles, false, true, false);
243}
244
245int AvalancheMC::DriftLine(const Point& p0, const Particle particle,
246 std::vector<Point>& path,
247 std::vector<std::pair<Point, Particle> > & secondaries,
248 const bool aval, const bool signal) {
249
250 std::array<double, 3> x0 = {p0.x, p0.y, p0.z};
251 double t0 = p0.t;
252 // Make sure the starting point is inside an active region.
253 std::array<double, 3> e0 = {0., 0., 0.};
254 std::array<double, 3> b0 = {0., 0., 0.};
255 Medium* medium = nullptr;
256 int status = GetField(x0, e0, b0, medium);
257 if (status != 0) {
258 std::cerr << m_className + "::DriftLine: "
259 << PrintVec(x0) + " is not in a valid drift region.\n";
260 return status;
261 }
262 // Add the first point to the line.
263 path.emplace_back(MakePoint(x0, t0));
264 if (m_debug) {
265 std::cout << m_className + "::DriftLine: Starting at "
266 << PrintVec(x0) + ".\n";
267 }
268 // Make sure the starting time is within the requested window.
269 if (m_hasTimeWindow && (t0 < m_tMin || t0 > m_tMax)) {
270 if (m_debug) std::cout << " Outside the time window.\n";
271 return StatusOutsideTimeWindow;
272 }
273 // Determine if the medium is a gas or semiconductor.
274 const bool semiconductor = medium->IsSemiconductor();
275
276 while (0 == status) {
277 constexpr double tol = 1.e-10;
278 // Make sure the electric field has a non-vanishing component.
279 const double emag = Mag(e0);
280 if (emag < tol && !m_useDiffusion) {
281 std::cerr << m_className + "::DriftLine: Too small electric field at "
282 << PrintVec(x0) + ".\n";
283 status = StatusCalculationAbandoned;
284 break;
285 }
286 // Compute the drift velocity at this point.
287 std::array<double, 3> v0;
288 if (!GetVelocity(particle, medium, x0, e0, b0, v0)) {
289 status = StatusCalculationAbandoned;
290 break;
291 }
292
293 // Make sure the drift velocity vector has a non-vanishing component.
294 double vmag = Mag(v0);
295 if (vmag < tol && !m_useDiffusion) {
296 std::cerr << m_className + "::DriftLine: Too small drift velocity at "
297 << PrintVec(x0) + ".\n";
298 status = StatusCalculationAbandoned;
299 break;
300 }
301
302 // Coordinates after the step.
303 std::array<double, 3> x1 = x0;
304 // Time after the step.
305 double t1 = t0;
306 if (vmag < tol || emag < tol) {
307 // Diffusion only. Get the mobility.
308 const double mu = GetMobility(particle, medium);
309 if (mu < 0.) {
310 std::cerr << m_className + "::DriftLine: Invalid mobility.\n";
311 status = StatusCalculationAbandoned;
312 break;
313 }
314 // Calculate the diffusion coefficient.
315 const double dif = mu * BoltzmannConstant * medium->GetTemperature();
316 double sigma = 0.;
317 switch (m_stepModel) {
318 case StepModel::FixedTime:
319 sigma = sqrt(2. * dif * m_tMc);
320 t1 += m_tMc;
321 break;
322 case StepModel::FixedDistance:
323 sigma = m_dMc;
324 break;
325 case StepModel::CollisionTime: {
326 // Thermal velocity.
327 const double vth =
328 SpeedOfLight * sqrt(2 * BoltzmannConstant *
329 medium->GetTemperature() / ElectronMass);
330 sigma = m_nMc * dif / vth;
331 } break;
332 case StepModel::UserDistance:
333 sigma = m_fStep(x0[0], x0[1], x0[2]);
334 break;
335 default:
336 std::cerr << m_className + "::DriftLine: Unknown stepping model.\n";
337 status = StatusCalculationAbandoned;
338 break;
339 }
340 if (status != 0) break;
341 if (m_stepModel != StepModel::FixedTime) {
342 t1 += sigma * sigma / (2 * dif);
343 }
344 for (size_t i = 0; i < 3; ++i) x1[i] += RndmGaussian(0., sigma);
345 } else {
346 // Drift and diffusion. Determine the time step.
347 double dt = 0.;
348 switch (m_stepModel) {
349 case StepModel::FixedTime:
350 dt = m_tMc;
351 break;
352 case StepModel::FixedDistance:
353 dt = m_dMc / vmag;
354 break;
355 case StepModel::CollisionTime:
356 if (particle == Particle::Ion || particle == Particle::NegativeIon) {
357 constexpr double c1 = AtomicMassUnitElectronVolt /
358 (SpeedOfLight * SpeedOfLight);
359 dt = -m_nMc * (c1 * vmag / emag) * log(RndmUniformPos());
360 } else {
361 constexpr double c1 = ElectronMass / (SpeedOfLight * SpeedOfLight);
362 dt = -m_nMc * (c1 * vmag / emag) * log(RndmUniformPos());
363 }
364 break;
365 case StepModel::UserDistance:
366 dt = m_fStep(x0[0], x0[1], x0[2]) / vmag;
367 break;
368 default:
369 std::cerr << m_className + "::DriftLine: Unknown stepping model.\n";
370 status = StatusCalculationAbandoned;
371 break;
372 }
373 if (status != 0) break;
374
375 double difl = 0., dift = 0.;
376 if (m_useDiffusion) {
377 // Get the diffusion coefficients.
378 if (!GetDiffusion(particle, medium, e0, b0, difl, dift)) {
379 PrintError("DriftLine", "diffusion", particle, x0);
380 status = StatusCalculationAbandoned;
381 break;
382 }
383 if (m_stepModel != StepModel::FixedTime) {
384 const double ds = vmag * dt;
385 const double dif = std::max(difl, dift);
386 if (dif * dif > ds) {
387 dt = ds * ds / (dif * dif * vmag);
388 }
389 }
390 }
391 // Compute the proposed end point of this step.
392 for (size_t k = 0; k < 3; ++k) x1[k] += dt * v0[k];
393 std::array<double, 3> v1 = v0;
394 constexpr unsigned int nMaxIter = 3;
395 for (unsigned int i = 0; i < nMaxIter; ++i) {
396 status = GetField(x1, e0, b0, medium);
397 if (status != 0) {
398 // Point is outside the active region. Reduce the step size.
399 x1 = MidPoint(x0, x1);
400 dt *= 0.5;
401 continue;
402 }
403 // Compute the velocity at the proposed end point.
404 if (!GetVelocity(particle, medium, x1, e0, b0, v1)) {
405 status = StatusCalculationAbandoned;
406 break;
407 }
408 if (Slope(vmag, Mag(v1)) < 0.05) break;
409 // Halve the step.
410 x1 = MidPoint(x0, x1);
411 dt *= 0.5;
412 }
413 if (status == StatusCalculationAbandoned) break;
414 if (m_doRKF) {
415 StepRKF(particle, x0, v0, dt, x1, v1, status);
416 vmag = Mag(v1);
417 }
418 if (m_useDiffusion) AddDiffusion(sqrt(vmag * dt), difl, dift, x1, v1);
419 t1 += dt;
420 }
421 if (m_debug) std::cout << " Next point: " << PrintVec(x1) + ".\n";
422
423 // Get the electric and magnetic field at the new position.
424 status = GetField(x1, e0, b0, medium);
425 if (status == StatusLeftDriftMedium || status == StatusLeftDriftArea) {
426 // Point is not inside a "driftable" medium or outside the drift area.
427 // Try terminating the drift line close to the boundary.
428 Terminate(x0, t0, x1, t1);
429 if (m_debug) std::cout << " Left the drift region.\n";
430 // Add the point to the drift line.
431 path.emplace_back(MakePoint(x1, t1));
432 break;
433 }
434 // Check if the particle has crossed a wire.
435 std::array<double, 3> xc = x0;
436 double rc = 0.;
437 if (m_sensor->CrossedWire(x0[0], x0[1], x0[2], x1[0], x1[1], x1[2],
438 xc[0], xc[1], xc[2], false, rc)) {
439 if (m_debug) std::cout << " Hit a wire.\n";
440 status = StatusLeftDriftMedium;
441 // Adjust the time step.
442 double tc = t0 + (t1 - t0) * Dist(x0, xc) / Dist(x0, x1);
443 Terminate(x0, t0, xc, tc);
444 // Add the point to the drift line.
445 path.emplace_back(MakePoint(xc, tc));
446 break;
447 }
448 if (m_sensor->CrossedPlane(x0[0], x0[1], x0[2], x1[0], x1[1], x1[2],
449 xc[0], xc[1], xc[2])) {
450 if (m_debug) std::cout << " Hit a plane.\n";
451 status = StatusHitPlane;
452 // Adjust the time step.
453 double tc = t0 + (t1 - t0) * Dist(x0, xc) / Dist(x0, x1);
454 Terminate(x0, t0, xc, tc);
455 // Add the point to the drift line.
456 path.emplace_back(MakePoint(xc, tc));
457 break;
458 }
459
460 // Make sure the time is still within the specified interval.
461 if (m_hasTimeWindow && (t1 < m_tMin || t1 > m_tMax)) {
462 status = StatusOutsideTimeWindow;
463 }
464 // Add the point to the drift line.
465 path.emplace_back(MakePoint(x1, t1));
466 // Update the current position and time.
467 x0 = x1;
468 t0 = t1;
469 }
470
471 if (status == StatusCalculationAbandoned) {
472 std::cerr << m_className + "::DriftLine: Abandoned the calculation.\n";
473 }
474
475 // Compute Townsend and attachment coefficients for each drift step.
476 unsigned int nElectronsOld = m_nElectrons;
477 unsigned int nHolesOld = m_nHoles;
478 unsigned int nIonsOld = m_nIons;
479
480 if ((particle == Particle::Electron || particle == Particle::Hole) &&
481 (aval || m_useAttachment) &&
482 (m_sizeCut == 0 || m_nElectrons < m_sizeCut)) {
483 ComputeGainLoss(particle, path, status, secondaries, semiconductor);
484 if (status == StatusAttached && m_debug) std::cout << " Attached.\n";
485 }
486
487 if (m_debug) {
488 std::cout << " Stopped at "
489 << PrintVec({path.back().x, path.back().y, path.back().z}) + ".\n";
490 const int nNewElectrons = m_nElectrons - nElectronsOld;
491 const int nNewHoles = m_nHoles - nHolesOld;
492 const int nNewIons = m_nIons - nIonsOld;
493 std::cout << " Produced\n"
494 << " " << nNewElectrons << " electrons,\n"
495 << " " << nNewHoles << " holes, and\n"
496 << " " << nNewIons << " ions.\n";
497 }
498
499 // Compute the induced signal and induced charge if requested.
500 double scale = 1.;
501 if (particle == Particle::Electron) {
502 scale = -m_scaleE;
503 } else if (particle == Particle::Ion) {
504 scale = m_scaleI;
505 } else if (particle == Particle::Hole) {
506 scale = m_scaleH;
507 } else if (particle == Particle::NegativeIon) {
508 scale = -m_scaleI;
509 }
510 if (signal) ComputeSignal(particle, scale, path);
511 if (m_doInducedCharge) ComputeInducedCharge(scale, path);
512
513 // Plot the drift line if requested.
514 if (m_viewer && !path.empty()) {
515 const size_t nP = path.size();
516 // Register the new drift line and get its ID.
517 const size_t id = m_viewer->NewDriftLine(particle, nP, p0.x, p0.y, p0.z);
518 // Set the points along the trajectory.
519 for (size_t i = 0; i < nP; ++i) {
520 m_viewer->SetDriftLinePoint(id, i, path[i].x, path[i].y, path[i].z);
521 }
522 }
523 return status;
524}
525
526bool AvalancheMC::AvalancheElectron(const double x0, const double y0,
527 const double z0, const double t0,
528 const bool holes) {
529 std::vector<std::pair<Point, Particle> > particles;
530 particles.emplace_back(std::make_pair(MakePoint(x0, y0, z0, t0),
532 return TransportParticles(particles, true, holes, true);
533}
534
535bool AvalancheMC::AvalancheHole(const double x0, const double y0,
536 const double z0, const double t0,
537 const bool electrons) {
538 std::vector<std::pair<Point, Particle> > particles;
539 particles.emplace_back(std::make_pair(MakePoint(x0, y0, z0, t0),
541 return TransportParticles(particles, electrons, true, true);
542}
543
544bool AvalancheMC::AvalancheElectronHole(const double x0, const double y0,
545 const double z0, const double t0) {
546 std::vector<std::pair<Point, Particle> > particles;
547 particles.emplace_back(std::make_pair(MakePoint(x0, y0, z0, t0),
549 particles.emplace_back(std::make_pair(MakePoint(x0, y0, z0, t0),
551 return TransportParticles(particles, true, true, true);
552}
553
554void AvalancheMC::AddElectron(const double x, const double y, const double z,
555 const double t) {
556
557 EndPoint p;
558 p.status = StatusAlive;
559 p.path = {MakePoint(x, y, z, t)};
560 m_electrons.push_back(std::move(p));
561 // TODO
562 ++m_nElectrons;
563}
564
565void AvalancheMC::AddHole(const double x, const double y, const double z,
566 const double t) {
567 EndPoint p;
568 p.status = StatusAlive;
569 p.path = {MakePoint(x, y, z, t)};
570 m_holes.push_back(std::move(p));
571 // TODO
572 ++m_nHoles;
573}
574void AvalancheMC::AddIon(const double x, const double y, const double z,
575 const double t) {
576 EndPoint p;
577 p.status = StatusAlive;
578 p.path = {MakePoint(x, y, z, t)};
579 m_ions.push_back(std::move(p));
580 // TODO
581 ++m_nIons;
582}
583
584bool AvalancheMC::ResumeAvalanche(const bool electrons, const bool holes) {
585
586 std::vector<std::pair<Point, Particle> > particles;
587 for (const auto& p: m_electrons) {
588 if (p.status == StatusAlive || p.status == StatusOutsideTimeWindow) {
589 particles.push_back(std::make_pair(p.path.back(), Particle::Electron));
590 }
591 }
592 for (const auto& p: m_holes) {
593 if (p.status == StatusAlive || p.status == StatusOutsideTimeWindow) {
594 particles.push_back(std::make_pair(p.path.back(), Particle::Hole));
595 }
596 }
597 for (const auto& p: m_ions) {
598 if (p.status == StatusAlive || p.status == StatusOutsideTimeWindow) {
599 particles.push_back(std::make_pair(p.path.back(), Particle::Ion));
600 }
601 }
602 for (const auto& p: m_negativeIons) {
603 if (p.status == StatusAlive || p.status == StatusOutsideTimeWindow) {
604 particles.push_back(std::make_pair(p.path.back(), Particle::NegativeIon));
605 }
606 }
607 return TransportParticles(particles, electrons, holes, true);
608}
609
610bool AvalancheMC::TransportParticles(
611 std::vector<std::pair<Point, Particle> >& particles,
612 const bool withE, const bool withH, const bool aval) {
613 // -----------------------------------------------------------------------
614 // DLCMCA - Subroutine that computes a drift line using a Monte-Carlo
615 // technique to take account of diffusion and of avalanche
616 // formation.
617 // -----------------------------------------------------------------------
618
619 m_electrons.clear();
620 m_holes.clear();
621 m_ions.clear();
622 m_negativeIons.clear();
623
624 // Make sure the sensor is defined.
625 if (!m_sensor) {
626 std::cerr << m_className
627 << "::TransportParticles: Sensor is not defined.\n";
628 return false;
629 }
630
631 m_nElectrons = 0;
632 m_nHoles = 0;
633 m_nIons = 0;
634 for (const auto& particle : particles) {
635 if (particle.second == Particle::Electron) {
636 ++m_nElectrons;
637 } else if (particle.second == Particle::Hole) {
638 ++m_nHoles;
639 } else if (particle.second == Particle::Ion) {
640 ++m_nIons;
641 }
642 }
643
644 if (!withH && !withE) {
645 std::cerr << m_className + "::TransportParticles: "
646 << "Neither electron nor hole/ion component requested.\n";
647 }
648
649 const bool signal = m_doSignal && (m_sensor->GetNumberOfElectrodes() > 0);
650 std::vector<std::pair<Point, Particle> > secondaries;
651 while (!particles.empty()) {
652 for (const auto& particle : particles) {
653 if (!withE && particle.second == Particle::Electron) continue;
654 if (!withH && particle.second != Particle::Electron) continue;
655 std::vector<Point> path;
656 const int status = DriftLine(particle.first, particle.second,
657 path, secondaries, aval, signal);
658 if (path.empty()) continue;
659 EndPoint p;
660 p.status = status;
661 if (m_storeDriftLines) {
662 p.path = std::move(path);
663 } else {
664 p.path = {path.front(), path.back()};
665 }
666 if (particle.second == Particle::Electron) {
667 m_electrons.push_back(std::move(p));
668 } else if (particle.second == Particle::Hole) {
669 m_holes.push_back(std::move(p));
670 } else if (particle.second == Particle::Ion) {
671 m_ions.push_back(std::move(p));
672 } else if (particle.second == Particle::NegativeIon) {
673 m_negativeIons.push_back(std::move(p));
674 } else {
675 std::cerr << m_className
676 << "::TransportParticles: Unexpected particle type.\n";
677 }
678 }
679 particles.swap(secondaries);
680 secondaries.clear();
681 }
682 return true;
683}
684
685int AvalancheMC::GetField(const std::array<double, 3>& x,
686 std::array<double, 3>& e, std::array<double, 3>& b,
687 Medium*& medium) const {
688 e.fill(0.);
689 b.fill(0.);
690 int status = 0;
691 // Get the magnetic field.
692 m_sensor->MagneticField(x[0], x[1], x[2], b[0], b[1], b[2], status);
693 // Get the electric field.
694 m_sensor->ElectricField(x[0], x[1], x[2], e[0], e[1], e[2], medium, status);
695 // Make sure the point is inside a drift medium.
696 if (status != 0 || !medium) return StatusLeftDriftMedium;
697 // Make sure the point is inside the drift area.
698 if (!m_sensor->IsInArea(x[0], x[1], x[2])) return StatusLeftDriftArea;
699
700 return 0;
701}
702
703double AvalancheMC::GetMobility(const Particle particle, Medium* medium) const {
704 if (particle == Particle::Electron) {
705 return medium->ElectronMobility();
706 } else if (particle == Particle::Hole) {
707 return medium->HoleMobility();
708 } else if (particle == Particle::Ion) {
709 return medium->IonMobility();
710 } else if (particle == Particle::NegativeIon) {
711 return medium->NegativeIonMobility();
712 }
713 return -1.;
714}
715
716bool AvalancheMC::GetVelocity(const Particle particle, Medium* medium,
717 const std::array<double, 3>& x,
718 const std::array<double, 3>& e,
719 const std::array<double, 3>& b,
720 std::array<double, 3>& v) const {
721 v.fill(0.);
722 bool ok = false;
723 if (m_useVelocityMap &&
724 particle != Particle::Ion && particle != Particle::NegativeIon) {
725 // We assume there is only one component with a velocity map.
726 const auto nComponents = m_sensor->GetNumberOfComponents();
727 for (size_t i = 0; i < nComponents; ++i) {
728 auto cmp = m_sensor->GetComponent(i);
729 if (!cmp->HasVelocityMap()) continue;
730 if (particle == Particle::Electron) {
731 ok = cmp->ElectronVelocity(x[0], x[1], x[2], v[0], v[1], v[2]);
732 } else if (particle == Particle::Hole) {
733 ok = cmp->HoleVelocity(x[0], x[1], x[2], v[0], v[1], v[2]);
734 }
735 if (!ok) continue;
736 // Seems to have worked.
737 if (m_debug) {
738 std::cout << m_className << "::GetVelocity: Velocity at "
739 << PrintVec(x) << " = " << PrintVec(v) << "\n";
740 }
741 return true;
742 }
743 }
744 if (particle == Particle::Electron) {
745 ok = medium->ElectronVelocity(e[0], e[1], e[2], b[0], b[1], b[2],
746 v[0], v[1], v[2]);
747 } else if (particle == Particle::Hole) {
748 ok = medium->HoleVelocity(e[0], e[1], e[2], b[0], b[1], b[2],
749 v[0], v[1], v[2]);
750 } else if (particle == Particle::Ion) {
751 ok = medium->IonVelocity(e[0], e[1], e[2], b[0], b[1], b[2],
752 v[0], v[1], v[2]);
753 } else if (particle == Particle::NegativeIon) {
754 ok = medium->NegativeIonVelocity(e[0], e[1], e[2], b[0], b[1], b[2],
755 v[0], v[1], v[2]);
756 }
757 if (!ok) {
758 PrintError("GetVelocity", "velocity", particle, x);
759 return false;
760 }
761 if (m_debug) {
762 std::cout << m_className << "::GetVelocity: Velocity at " << PrintVec(x)
763// << " = " << PrintVec(v) << "\n";
764 << " = " << v[0] << ", " << v[1] << ", " << v[2] << "\n";
765 }
766 return true;
767}
768
769bool AvalancheMC::GetDiffusion(const Particle particle, Medium* medium,
770 const std::array<double, 3>& e,
771 const std::array<double, 3>& b, double& dl,
772 double& dt) const {
773 dl = 0.;
774 dt = 0.;
775 bool ok = false;
776 if (particle == Particle::Electron) {
777 ok = medium->ElectronDiffusion(e[0], e[1], e[2], b[0], b[1], b[2], dl, dt);
778 } else if (particle == Particle::Hole) {
779 ok = medium->HoleDiffusion(e[0], e[1], e[2], b[0], b[1], b[2], dl, dt);
780 } else if (particle == Particle::Ion || particle == Particle::NegativeIon) {
781 ok = medium->IonDiffusion(e[0], e[1], e[2], b[0], b[1], b[2], dl, dt);
782 }
783 return ok;
784}
785
786double AvalancheMC::GetAttachment(const Particle particle, Medium* medium,
787 const std::array<double, 3>& x,
788 const std::array<double, 3>& e,
789 const std::array<double, 3>& b) const {
790 double eta = 0.;
791 if (m_useAttachmentMap) {
792 const auto nComponents = m_sensor->GetNumberOfComponents();
793 for (size_t i = 0; i < nComponents; ++i) {
794 auto cmp = m_sensor->GetComponent(i);
795 if (!cmp->HasAttachmentMap()) continue;
796 if (particle == Particle::Electron) {
797 if (!cmp->ElectronAttachment(x[0], x[1], x[2], eta)) continue;
798 } else {
799 if (!cmp->HoleAttachment(x[0], x[1], x[2], eta)) continue;
800 }
801 return eta;
802 }
803 }
804 if (particle == Particle::Electron) {
805 medium->ElectronAttachment(e[0], e[1], e[2], b[0], b[1], b[2], eta);
806 } else {
807 medium->HoleAttachment(e[0], e[1], e[2], b[0], b[1], b[2], eta);
808 }
809 return eta;
810}
811
812double AvalancheMC::GetTownsend(const Particle particle, Medium* medium,
813 const std::array<double, 3>& x,
814 const std::array<double, 3>& e,
815 const std::array<double, 3>& b) const {
816 double alpha = 0.;
817 if (m_useTownsendMap) {
818 const auto nComponents = m_sensor->GetNumberOfComponents();
819 for (size_t i = 0; i < nComponents; ++i) {
820 auto cmp = m_sensor->GetComponent(i);
821 if (!cmp->HasTownsendMap()) continue;
822 if (particle == Particle::Electron) {
823 if (!cmp->ElectronTownsend(x[0], x[1], x[2], alpha)) continue;
824 } else {
825 if (!cmp->HoleTownsend(x[0], x[1], x[2], alpha)) continue;
826 }
827 return alpha;
828 }
829 }
830 if (particle == Particle::Electron) {
831 medium->ElectronTownsend(e[0], e[1], e[2], b[0], b[1], b[2], alpha);
832 } else {
833 medium->HoleTownsend(e[0], e[1], e[2], b[0], b[1], b[2], alpha);
834 }
835 return alpha;
836}
837
838void AvalancheMC::StepRKF(const Particle particle,
839 const std::array<double, 3>& x0,
840 const std::array<double, 3>& v0, const double dt,
841 std::array<double, 3>& xf, std::array<double, 3>& vf,
842 int& status) const {
843 // Constants appearing in the RKF formulas.
844 constexpr double ci0 = 214. / 891.;
845 constexpr double ci1 = 1. / 33.;
846 constexpr double ci2 = 650. / 891.;
847 constexpr double beta10 = 1. / 4.;
848 constexpr double beta20 = -189. / 800.;
849 constexpr double beta21 = 729. / 800.;
850
851 vf = v0;
852 // First probe point.
853 for (size_t k = 0; k < 3; ++k) {
854 xf[k] = x0[k] + dt * beta10 * v0[k];
855 }
856 std::array<double, 3> e;
857 std::array<double, 3> b;
858 Medium* medium = nullptr;
859 status = GetField(xf, e, b, medium);
860 if (status != 0) return;
861
862 // Get the velocity at the first point.
863 std::array<double, 3> v1;
864 if (!GetVelocity(particle, medium, xf, e, b, v1)) {
865 status = StatusCalculationAbandoned;
866 return;
867 }
868
869 // Second point.
870 for (size_t k = 0; k < 3; ++k) {
871 xf[k] = x0[k] + dt * (beta20 * v0[k] + beta21 * v1[k]);
872 }
873 status = GetField(xf, e, b, medium);
874 if (status != 0) return;
875
876 // Get the velocity at the second point.
877 std::array<double, 3> v2;
878 if (!GetVelocity(particle, medium, xf, e, b, v2)) {
879 status = StatusCalculationAbandoned;
880 return;
881 }
882
883 // Compute the mean velocity and endpoint of the step.
884 for (size_t k = 0; k < 3; ++k) {
885 vf[k] = ci0 * v0[k] + ci1 * v1[k] + ci2 * v2[k];
886 xf[k] = x0[k] + dt * vf[k];
887 }
888}
889
890void AvalancheMC::AddDiffusion(const double step, const double dl,
891 const double dt, std::array<double, 3>& x,
892 const std::array<double, 3>& v) const {
893 // Draw a random diffusion direction in the particle frame.
894 const std::array<double, 3> d = {step * RndmGaussian(0., dl),
895 step * RndmGaussian(0., dt),
896 step * RndmGaussian(0., dt)};
897 if (m_debug) {
898 std::cout << m_className << "::AddDiffusion: Adding diffusion step "
899 << PrintVec(d) << "\n";
900 }
901 // Compute the rotation angles to align diffusion and drift velocity vectors.
902 const double vt = sqrt(v[0] * v[0] + v[1] * v[1]);
903 const double phi = vt > Small ? atan2(v[1], v[0]) : 0.;
904 const double theta =
905 vt > Small ? atan2(v[2], vt) : v[2] < 0. ? -HalfPi : HalfPi;
906 const double cphi = cos(phi);
907 const double sphi = sin(phi);
908 const double ctheta = cos(theta);
909 const double stheta = sin(theta);
910
911 x[0] += cphi * ctheta * d[0] - sphi * d[1] - cphi * stheta * d[2];
912 x[1] += sphi * ctheta * d[0] + cphi * d[1] - sphi * stheta * d[2];
913 x[2] += stheta * d[0] + ctheta * d[2];
914}
915
916void AvalancheMC::Terminate(const std::array<double, 3>& x0, const double t0,
917 std::array<double, 3>& x1, double& t1) const {
918 double dt = t1 - t0;
919 // Calculate the normalised direction vector.
920 std::array<double, 3> dx = {x1[0] - x0[0], x1[1] - x0[1], x1[2] - x0[2]};
921 double ds = Mag(dx);
922 if (ds > 0.) {
923 const double scale = 1. / ds;
924 for (unsigned int k = 0; k < 3; ++k) dx[k] *= scale;
925 }
926 x1 = x0;
927 t1 = t0;
928 while (ds > BoundaryDistance) {
929 dt *= 0.5;
930 ds *= 0.5;
931 std::array<double, 3> xm = x1;
932 for (unsigned int k = 0; k < 3; ++k) xm[k] += dx[k] * ds;
933 // Check if the mid-point is inside the drift medium and the drift area.
934 double ex = 0., ey = 0., ez = 0.;
935 int status = 0;
936 Medium* medium = nullptr;
937 m_sensor->ElectricField(xm[0], xm[1], xm[2], ex, ey, ez, medium, status);
938 if (status == 0 && m_sensor->IsInArea(xm[0], xm[1], xm[2])) {
939 x1 = xm;
940 t1 += dt;
941 }
942 }
943}
944
945bool AvalancheMC::ComputeGainLoss(const Particle particle,
946 std::vector<Point>& path, int& status,
947 std::vector<std::pair<Point, Particle> >& secondaries,
948 const bool semiconductor) {
949
950 std::vector<double> alps;
951 std::vector<double> etas;
952 // Compute the integrated Townsend and attachment coefficients.
953 if (!ComputeAlphaEta(particle, path, alps, etas)) return false;
954
955 // Opposite-charge particle produced in the avalanche.
957 if (particle == Particle::Electron) {
958 other = semiconductor ? Particle::Hole : Particle::Ion;
959 }
960 const size_t nPoints = path.size();
961 // Loop over the drift line.
962 for (size_t i = 0; i < nPoints - 1; ++i) {
963 // Start with the initial electron (or hole).
964 int ne = 1;
965 int ni = 0;
966 if (etas[i] < Small) {
967 ne = RndmYuleFurry(std::exp(alps[i]));
968 ni = ne - 1;
969 } else {
970 // Subdivision of a step.
971 constexpr double probth = 0.01;
972 // Compute the number of subdivisions.
973 const int nDiv = std::max(int((alps[i] + etas[i]) / probth), 1);
974 // Compute the probabilities for gain and loss.
975 const double p = std::max(alps[i] / nDiv, 0.);
976 const double q = std::max(etas[i] / nDiv, 0.);
977 // Loop over the subdivisions.
978 for (int j = 0; j < nDiv; ++j) {
979 if (ne > 100) {
980 // Gaussian approximation.
981 const int gain = int(ne * p + RndmGaussian() * sqrt(ne * p * (1. - p)));
982 const int loss = int(ne * q + RndmGaussian() * sqrt(ne * q * (1. - q)));
983 ne += gain - loss;
984 ni += gain;
985 } else {
986 // Binomial approximation
987 for (int k = ne; k--;) {
988 if (RndmUniform() < p) {
989 ++ne;
990 ++ni;
991 }
992 if (RndmUniform() < q) --ne;
993 }
994 }
995 // Check if the electron (or hole) has survived.
996 if (ne <= 0) {
997 status = StatusAttached;
998 if (particle == Particle::Electron) {
999 --m_nElectrons;
1000 } else if (particle == Particle::Hole) {
1001 --m_nHoles;
1002 } else {
1003 --m_nIons;
1004 }
1005 const double f0 = (j + 0.5) / nDiv;
1006 const double f1 = 1. - f0;
1007 path.resize(i + 2);
1008 path[i + 1].x = f0 * path[i].x + f1 * path[i + 1].x;
1009 path[i + 1].y = f0 * path[i].y + f1 * path[i + 1].y;
1010 path[i + 1].z = f0 * path[i].z + f1 * path[i + 1].z;
1011 path[i + 1].t = f0 * path[i].t + f1 * path[i + 1].t;
1012 break;
1013 }
1014 }
1015 }
1016 // Add the new electrons to the table.
1017 if (ne > 1) {
1018 for (int j = 0; j < ne - 1; ++j) {
1019 secondaries.push_back(std::make_pair(path[i + 1], particle));
1020 }
1021 if (particle == Particle::Electron) {
1022 m_nElectrons += ne - 1;
1023 } else if (particle == Particle::Hole) {
1024 m_nHoles += ne - 1;
1025 }
1026 }
1027 // Add the new holes/ions to the table.
1028 if (ni > 0) {
1029 if (other == Particle::Hole) {
1030 m_nHoles += ni;
1031 } else if (other == Particle::Ion) {
1032 m_nIons += ni;
1033 } else {
1034 m_nElectrons += ni;
1035 }
1036 const double n1 = std::exp(alps[i]) - 1;
1037 const double a1 = n1 > 0. ? 1. / std::log1p(n1) : 0.;
1038 for (int j = 0; j < ni; ++j) {
1039 const double f1 = n1 > 0. ? a1 * std::log1p(RndmUniform() * n1) : 0.5;
1040 const double f0 = 1. - f1;
1041 Point point;
1042 point.x = f0 * path[i].x + f1 * path[i + 1].x;
1043 point.y = f0 * path[i].y + f1 * path[i + 1].y;
1044 point.z = f0 * path[i].z + f1 * path[i + 1].z;
1045 point.t = f0 * path[i].t + f1 * path[i + 1].t;
1046 secondaries.push_back(std::make_pair(std::move(point), other));
1047 }
1048 }
1049 // If trapped, exit the loop over the drift line.
1050 if (status == StatusAttached) return true;
1051 }
1052 return true;
1053}
1054
1055bool AvalancheMC::ComputeAlphaEta(const Particle particle,
1056 std::vector<Point>& path,
1057 std::vector<double>& alps,
1058 std::vector<double>& etas) const {
1059 // -----------------------------------------------------------------------
1060 // DLCEQU - Computes equilibrated alphas and etas.
1061 // -----------------------------------------------------------------------
1062
1063 // Loop a first time over the drift line and get alpha at each point.
1064 size_t nPoints = path.size();
1065 alps.assign(nPoints, 0.);
1066 etas.assign(nPoints, 0.);
1067 for (size_t i = 0; i < nPoints; ++i) {
1068 const std::array<double, 3> x0 = {path[i].x, path[i].y, path[i].z};
1069 std::array<double, 3> e0, b0;
1070 Medium* medium = nullptr;
1071 if (GetField(x0, e0, b0, medium) != 0) continue;
1072 alps[i] = GetTownsend(particle, medium, x0, e0, b0);
1073 }
1074
1075 std::vector<Point> pathExt;
1076 for (size_t i = 0; i < nPoints - 1; ++i) {
1077 pathExt.push_back(path[i]);
1078 if (Slope(alps[i], alps[i + 1]) < 0.5) continue;
1079 const std::array<double, 3> x0 = {path[i].x, path[i].y, path[i].z};
1080 const std::array<double, 3> x1 = {path[i + 1].x, path[i + 1].y, path[i + 1].z};
1081 auto xm = MidPoint(x0, x1);
1082 std::array<double, 3> em, bm;
1083 Medium* medium = nullptr;
1084 if (GetField(xm, em, bm, medium) != 0) continue;
1085 Point pm;
1086 pm.x = xm[0];
1087 pm.y = xm[1];
1088 pm.z = xm[2];
1089 pm.t = 0.5 * (path[i].t + path[i + 1].t);
1090 pathExt.push_back(std::move(pm));
1091 }
1092 pathExt.push_back(path.back());
1093 path.swap(pathExt);
1094
1095 nPoints = path.size();
1096 alps.assign(nPoints, 0.);
1097 etas.assign(nPoints, 0.);
1098 if (nPoints < 2) return true;
1099
1100 // Locations and weights for Gaussian integration.
1101 constexpr size_t nG = 6;
1104
1105 bool equilibrate = m_doEquilibration;
1106 // Loop over the drift line.
1107 for (size_t i = 0; i < nPoints - 1; ++i) {
1108 const std::array<double, 3> x0 = {path[i].x, path[i].y, path[i].z};
1109 const std::array<double, 3> x1 = {path[i + 1].x, path[i + 1].y, path[i + 1].z};
1110 // Compute the step length.
1111 const std::array<double, 3> del = {x1[0] - x0[0], x1[1] - x0[1],
1112 x1[2] - x0[2]};
1113 const double dmag = Mag(del);
1114 if (dmag < Small) continue;
1115 const double veff = dmag / (path[i + 1].t - path[i].t);
1116 // Integrate drift velocity and Townsend and attachment coefficients.
1117 std::array<double, 3> vd = {0., 0., 0.};
1118 for (size_t j = 0; j < nG; ++j) {
1119 const double f = 0.5 * (1. + tg[j]);
1120 std::array<double, 3> x = x0;
1121 for (size_t k = 0; k < 3; ++k) x[k] += f * del[k];
1122 // Get the field.
1123 std::array<double, 3> e;
1124 std::array<double, 3> b;
1125 Medium* medium = nullptr;
1126 const int status = GetField(x, e, b, medium);
1127 // Make sure we are in a drift medium.
1128 if (status != 0) {
1129 // Check if this point is the last but one.
1130 if (i < nPoints - 2) {
1131 std::cerr << m_className << "::ComputeAlphaEta: Got status " << status
1132 << " at segment " << j + 1 << "/" << nG
1133 << ", drift point " << i + 1 << "/" << nPoints << ".\n";
1134 return false;
1135 }
1136 continue;
1137 }
1138 // Get the drift velocity.
1139 std::array<double, 3> v;
1140 if (!GetVelocity(particle, medium, x, e, b, v)) continue;
1141 // Get Townsend and attachment coefficients.
1142 double alpha = GetTownsend(particle, medium, x, e, b);
1143 double eta = GetAttachment(particle, medium, x, e, b);
1144 if (eta < 0.) {
1145 eta = std::abs(eta) * Mag(v) / veff;
1146 equilibrate = false;
1147 }
1148 for (size_t k = 0; k < 3; ++k) vd[k] += wg[j] * v[k];
1149 alps[i] += wg[j] * alpha;
1150 etas[i] += wg[j] * eta;
1151 }
1152
1153 // Compute the scaling factor for the projected length.
1154 double scale = 1.;
1155 if (equilibrate) {
1156 const double vdmag = Mag(vd);
1157 if (vdmag * dmag <= 0.) {
1158 scale = 0.;
1159 } else {
1160 const double dinv = del[0] * vd[0] + del[1] * vd[1] + del[2] * vd[2];
1161 scale = dinv < 0. ? 0. : dinv / (vdmag * dmag);
1162 }
1163 }
1164 alps[i] *= 0.5 * dmag * scale;
1165 etas[i] *= 0.5 * dmag * scale;
1166 }
1167
1168 // Skip equilibration if projection has not been requested.
1169 if (!equilibrate) return true;
1170 if (!Equilibrate(alps)) {
1171 if (m_debug) {
1172 std::cerr << m_className << "::ComputeAlphaEta:\n"
1173 << " Unable to even out alpha steps.\n"
1174 << " Calculation is probably inaccurate.\n";
1175 }
1176 return false;
1177 }
1178 if (!Equilibrate(etas)) {
1179 if (m_debug) {
1180 std::cerr << m_className << "::ComputeAlphaEta:\n"
1181 << " Unable to even out eta steps.\n"
1182 << " Calculation is probably inaccurate.\n";
1183 }
1184 return false;
1185 }
1186 // Seems to have worked.
1187 return true;
1188}
1189
1190bool AvalancheMC::Equilibrate(std::vector<double>& alphas) const {
1191 const size_t nPoints = alphas.size();
1192 // Try to equilibrate the returning parts.
1193 for (size_t i = 0; i < nPoints - 1; ++i) {
1194 // Skip non-negative points.
1195 if (alphas[i] >= 0.) continue;
1196 // Targets for subtracting
1197 double sub1 = -0.5 * alphas[i];
1198 double sub2 = sub1;
1199 bool try1 = false;
1200 bool try2 = false;
1201 // Try to subtract half in earlier points.
1202 for (size_t j = 0; j < i - 1; ++j) {
1203 if (alphas[i - j] > sub1) {
1204 alphas[i - j] -= sub1;
1205 alphas[i] += sub1;
1206 sub1 = 0.;
1207 try1 = true;
1208 break;
1209 } else if (alphas[i - j] > 0.) {
1210 alphas[i] += alphas[i - j];
1211 sub1 -= alphas[i - j];
1212 alphas[i - j] = 0.;
1213 }
1214 }
1215 // Try to subtract the other half in later points.
1216 for (size_t j = 0; j < nPoints - i - 1; ++j) {
1217 if (alphas[i + j] > sub2) {
1218 alphas[i + j] -= sub2;
1219 alphas[i] += sub2;
1220 sub2 = 0.;
1221 try2 = true;
1222 break;
1223 } else if (alphas[i + j] > 0.) {
1224 alphas[i] += alphas[i + j];
1225 sub2 -= alphas[i + j];
1226 alphas[i + j] = 0.;
1227 }
1228 }
1229
1230 // Done if both sides have margin left.
1231 bool done = false;
1232 if (try1 && try2) {
1233 done = true;
1234 } else if (try1) {
1235 // Try earlier points again.
1236 sub1 = -alphas[i];
1237 for (size_t j = 0; j < i - 1; ++j) {
1238 if (alphas[i - j] > sub1) {
1239 alphas[i - j] -= sub1;
1240 alphas[i] += sub1;
1241 sub1 = 0.;
1242 done = true;
1243 break;
1244 } else if (alphas[i - j] > 0.) {
1245 alphas[i] += alphas[i - j];
1246 sub1 -= alphas[i - j];
1247 alphas[i - j] = 0.;
1248 }
1249 }
1250 } else if (try2) {
1251 // Try later points again.
1252 sub2 = -alphas[i];
1253 for (size_t j = 0; j < nPoints - i - 1; ++j) {
1254 if (alphas[i + j] > sub2) {
1255 alphas[i + j] -= sub2;
1256 alphas[i] += sub2;
1257 sub2 = 0.;
1258 done = true;
1259 break;
1260 } else if (alphas[i + j] > 0.) {
1261 alphas[i] += alphas[i + j];
1262 sub2 -= alphas[i + j];
1263 alphas[i + j] = 0.;
1264 }
1265 }
1266 }
1267 // See whether we succeeded.
1268 if (!done) return false;
1269 }
1270 return true;
1271}
1272
1273void AvalancheMC::ComputeSignal(
1274 const Particle particle, const double q,
1275 const std::vector<Point>& path) const {
1276 const size_t nPoints = path.size();
1277 if (nPoints < 2) return;
1278
1279 if (m_useWeightingPotential) {
1280 for (size_t i = 0; i < nPoints - 1; ++i) {
1281 const auto& p0 = path[i];
1282 const auto& p1 = path[i + 1];
1283 m_sensor->AddSignal(q, p0.t, p1.t, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z,
1284 false, true);
1285 }
1286 return;
1287 }
1288 // Get the drift velocity at each point.
1289 std::vector<double> ts;
1290 std::vector<std::array<double, 3> > xs;
1291 std::vector<std::array<double, 3> > vs;
1292 for (const auto& p : path) {
1293 std::array<double, 3> e;
1294 std::array<double, 3> b;
1295 Medium* medium = nullptr;
1296 int status = GetField({p.x, p.y, p.z}, e, b, medium);
1297 if (status != 0) continue;
1298 std::array<double, 3> v;
1299 if (!GetVelocity(particle, medium, {p.x, p.y, p.z}, e, b, v)) continue;
1300 ts.push_back(p.t);
1301 xs.push_back({p.x, p.y, p.z});
1302 vs.push_back(std::move(v));
1303 }
1304 m_sensor->AddSignal(q, ts, xs, vs, {}, m_navg);
1305}
1306
1307void AvalancheMC::ComputeInducedCharge(
1308 const double q, const std::vector<Point>& path) const {
1309 if (path.size() < 2) return;
1310 const auto& p0 = path.front();
1311 const auto& p1 = path.back();
1312 m_sensor->AddInducedCharge(q, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z);
1313}
1314
1315void AvalancheMC::PrintError(const std::string& fcn, const std::string& par,
1316 const Particle particle,
1317 const std::array<double, 3>& x) const {
1318 const std::string ehi = particle == Particle::Electron
1319 ? "electron"
1320 : particle == Particle::Hole ? "hole" : "ion";
1321 std::cerr << m_className + "::" + fcn + ": Error calculating " + ehi + " "
1322 << par + " at " + PrintVec(x) << ".\n";
1323}
1324} // namespace Garfield
void EnablePlotting(ViewDrift *view)
Switch on drift line plotting.
bool AvalancheHole(const double x, const double y, const double z, const double t, const bool electron=false)
Simulate an avalanche initiated by a hole at a given starting point.
void SetTimeSteps(const double d=0.02)
Use fixed-time steps (default 20 ps).
void GetHoleEndpoint(const size_t i, double &x0, double &y0, double &z0, double &t0, double &x1, double &y1, double &z1, double &t1, int &status) const
void SetTimeWindow(const double t0, const double t1)
Define a time interval (only carriers inside the interval are drifted).
void AddHole(const double x, const double y, const double z, const double t)
Add a hole to the list of particles to be transported.
bool DriftNegativeIon(const double x, const double y, const double z, const double t)
Simulate the drift line of a negative ion from a given starting point.
bool ResumeAvalanche(const bool electron=true, const bool hole=true)
Resume the simulation from the current set of charge carriers.
void GetIonEndpoint(const size_t i, double &x0, double &y0, double &z0, double &t0, double &x1, double &y1, double &z1, double &t1, int &status) const
bool DriftIon(const double x, const double y, const double z, const double t)
Simulate the drift line of an ion from a given starting point.
void AddElectron(const double x, const double y, const double z, const double t)
Add an electron to the list of particles to be transported.
AvalancheMC()
Default constructor.
bool AvalancheElectron(const double x, const double y, const double z, const double t, const bool hole=false)
void SetSensor(Sensor *s)
Set the sensor.
bool DriftHole(const double x, const double y, const double z, const double t)
Simulate the drift line of a hole from a given starting point.
void SetStepDistanceFunction(double(*f)(double x, double y, double z))
Retrieve the step distance from a user-supplied function.
void GetElectronEndpoint(const size_t i, double &x0, double &y0, double &z0, double &t0, double &x1, double &y1, double &z1, double &t1, int &status) const
void SetCollisionSteps(const unsigned int n=100)
bool AvalancheElectronHole(const double x, const double y, const double z, const double t)
Simulate an avalanche initiated by an electron-hole pair.
bool DriftElectron(const double x, const double y, const double z, const double t)
Simulate the drift line of an electron from a given starting point.
void AddIon(const double x, const double y, const double z, const double t)
Add an ion to the list of particles to be transported.
void SetDistanceSteps(const double d=0.001)
Use fixed distance steps (default 10 um).
Abstract base class for media.
Definition Medium.hh:16
Visualize drift lines and tracks.
Definition ViewDrift.hh:19
int dinv(const int n, std::vector< std::vector< double > > &a)
Replace square matrix A by its inverse.
Definition Numerics.cc:788
constexpr std::array< double, 6 > GaussLegendreWeights6()
Definition Numerics.hh:49
constexpr std::array< double, 6 > GaussLegendreNodes6()
Definition Numerics.hh:44
unsigned int RndmYuleFurry(const double mean)
Draw a random number from a geometric distribution.
Definition Random.hh:70
double RndmUniform()
Draw a random number uniformly distributed in the range [0, 1).
Definition Random.hh:14
double RndmUniformPos()
Draw a random number uniformly distributed in the range (0, 1).
Definition Random.hh:17
double RndmGaussian()
Draw a Gaussian random variate with mean zero and standard deviation one.
Definition Random.hh:24
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
std::vector< Point > path
Drift line.