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