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