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