Garfield++ 5.0
A toolkit for the detailed simulation of particle detectors based on ionisation measurement in gases and semiconductors
Loading...
Searching...
No Matches
AvalancheGrid.cc
Go to the documentation of this file.
2
3#include <TF1.h>
4
5#include <algorithm>
6#include <cmath>
7#include <iostream>
8#include <numeric>
9
10#include "Garfield/Medium.hh"
11#include "Garfield/Random.hh"
12
13namespace Garfield {
14void AvalancheGrid::SetZGrid(Grid &av, const double ztop, const double zbottom,
15 const int zsteps) {
16 // Creating the z-coordinate grid.
17 av.zsteps = zsteps;
18 av.zStepSize = (ztop - zbottom) / zsteps;
19 // Loop bassed grid creation.
20 for (int i = 0; i < zsteps; i++) {
21 av.zgrid.push_back(zbottom + i * av.zStepSize);
22 }
23}
24
25void AvalancheGrid::SetYGrid(Grid &av, const double ytop, const double ybottom,
26 const int ysteps) {
27 // Idem to SetZGrid for the x-coordinate grid.
28 av.ysteps = ysteps;
29 av.yStepSize = (ytop - ybottom) / ysteps;
30
31 if (av.yStepSize == 0) av.yStepSize = 1;
32
33 for (int i = 0; i < ysteps; i++) {
34 av.ygrid.push_back(ybottom + i * av.yStepSize);
35 }
36}
37
38void AvalancheGrid::SetXGrid(Grid &av, const double xtop, const double xbottom,
39 const int xsteps) {
40 // Idem to SetZGrid for the x-coordinate grid.
41 av.xsteps = xsteps;
42 av.xStepSize = (xtop - xbottom) / xsteps;
43
44 if (av.xStepSize == 0) av.xStepSize = 1;
45
46 for (int i = 0; i < xsteps; i++) {
47 av.xgrid.push_back(xbottom + i * av.xStepSize);
48 }
49}
50
51void AvalancheGrid::SetGrid(const double xmin, const double xmax,
52 const int xsteps, const double ymin,
53 const double ymax, const int ysteps,
54 const double zmin, const double zmax,
55 const int zsteps) {
56 m_avgrid.gridset = true;
57
58 if (zmin >= zmax || zsteps <= 0 || xmin > xmax || xsteps <= 0 ||
59 ymin > ymax || ysteps <= 0) {
60 std::cerr << m_className
61 << "::SetGrid:Error: Grid is not properly defined.\n";
62 return;
63 }
64
65 // Setting grid
66
67 SetZGrid(m_avgrid, zmax, zmin, zsteps);
68 SetYGrid(m_avgrid, ymax, ymin, ysteps);
69 SetXGrid(m_avgrid, xmax, xmin, xsteps);
70
71 if (m_debug) {
72 std::cerr << m_className << "::SetGrid: Grid created:\n";
73 std::cerr << " x range = (" << xmin << "," << xmax << ").\n";
74 std::cerr << " y range = (" << ymin << "," << ymax << ").\n";
75 std::cerr << " z range = (" << zmin << "," << zmax << ").\n";
76 }
77}
78
79int AvalancheGrid::GetAvalancheSize(double dx, const int nsize,
80 const double alpha, const double eta) {
81 // Algorithm to get the size of the avalanche after it has propagated over a
82 // distance dx.
83
84 int newnsize = 0; // Holder for final size.
85
86 const double k = eta / alpha;
87 const double ndx = exp((alpha - eta) *
88 dx); // Scaling Townsend and Attachment coef. to 1/mm.
89 // If the size is higher than 1e3 the central limit theorem will be used to
90 // describe the growth of the Townsend avalanche.
91 if (nsize < 1e3) {
92 // Running over all electrons in the avalanche.
93 for (int i = 0; i < nsize; i++) {
94 // Draw a random number from the uniform distribution (0,1).
95 double s = RndmUniformPos();
96 // Condition to which the random number will be compared. If the number is
97 // smaller than the condition, nothing happens. Otherwise, the single
98 // electron will be attached or retrieve additional electrons from the
99 // gas.
100 double condition = k * (ndx - 1) / (ndx - k);
101
102 if (s >= condition)
103 newnsize += (int)(1 + log((ndx - k) * (1 - s) / (ndx * (1 - k))) /
104 log(1 - (1 - k) / (ndx - k)));
105 }
106
107 } else {
108 // Central limit theorem.
109 const double sigma = sqrt((1 + k) * ndx * (ndx - 1) / (1 - k));
110 newnsize = RndmGaussian(nsize * ndx, sqrt(nsize) * sigma);
111 }
112
113 return newnsize;
114}
115
116bool AvalancheGrid::SnapToGrid(Grid &av, const double x, const double y,
117 const double z, const double /*v*/,
118 const int n) {
119 // Snap electron from AvalancheMicroscopic to the predefined grid.
120 if (!av.gridset) {
121 std::cerr << m_className << "::SnapToGrid:Error: grid is not defined.\n";
122 return false;
123 }
124 // Finding the z position on the grid.
125
126 int indexX, indexY, indexZ = 0;
127
128 // TODO: Snap must be dependent on the direction of drift.
129 indexX = round((x - av.xgrid.front()) / av.xStepSize);
130 indexY = floor((y - av.ygrid.front()) / av.yStepSize);
131 indexZ = round((z - av.zgrid.front()) / av.zStepSize);
132
133 if (m_debug)
134 std::cerr << m_className << "::SnapToGrid: ix = " << indexX
135 << ", iy = " << indexY << ", iz = " << indexZ << ".\n\n";
136 if (indexX < 0 || indexX >= av.xsteps || indexY < 0 || indexY >= av.ysteps ||
137 indexZ < 0 || indexZ >= av.zsteps) {
138 if (m_debug)
139 std::cerr << m_className << "::SnapToGrid: Point is outside the grid.\n";
140 return false;
141 }
142
143 AvalancheNode newNode;
144 newNode.ix = indexX;
145 newNode.iy = indexY;
146 newNode.iz = indexZ;
147 if (!GetParameters(newNode)) {
148 // TODO:Memory leak?
149 if (m_debug)
150 std::cerr << m_className
151 << "::SnapToGrid: Could not retrieve parameters from sensor.\n";
152 return false;
153 }
154
155 // When snapping the electron to the grid the distance traveled can yield
156 // additional electrons or attachment.
157
158 double step = z - av.zgrid[indexZ];
159
160 if (newNode.velNormal[0] != 0) {
161 step = x - av.xgrid[indexX];
162 } else if (newNode.velNormal[1] != 0) {
163 step = y - av.ygrid[indexY];
164 }
165
166 const int nholder =
167 GetAvalancheSize(step, n, newNode.townsend, newNode.attachment);
168 if (nholder == 0) {
169 if (m_debug)
170 std::cerr << m_className << "::SnapToGrid: n from 1 to 0 -> cancel.\n";
171 return false;
172 }
173
174 newNode.n = nholder < m_MaxSize ? nholder : m_MaxSize;
175 av.N += newNode.n;
176
177 bool alreadyExists = false;
178
179 for (AvalancheNode &existingNode : m_activeNodes) {
180 if (existingNode.ix == newNode.ix && existingNode.iy == newNode.iy &&
181 existingNode.iz == newNode.iz) {
182 alreadyExists = true;
183 existingNode.n += newNode.n;
184 }
185 }
186
187 // TODO: What if time changes as you are importing avalanches?
188 newNode.time = av.time;
189 if (!alreadyExists) m_activeNodes.push_back(newNode);
190
191 if (m_debug) {
192 std::cerr << m_className << "::SnapToGrid: n from 1 to " << nholder << ".\n"
193 << " Snapped to (x,y,z) = (" << x << " -> " << av.xgrid[indexX]
194 << ", " << y << " -> " << av.ygrid[indexY] << ", " << z << " -> "
195 << av.zgrid[indexZ] << ").\n";
196 }
197 return true;
198}
199
200void AvalancheGrid::NextAvalancheGridPoint(Grid &av) {
201 // This main function propagates the electrons and applies the avalanche
202 // statistics.
203 int Nholder = 0; // Holds the avalanche size before propagating it to the
204 // next point in the grid.
205 av.run = false;
206 for (AvalancheNode &node : m_activeNodes) { // For every avalanche node
207
208 if (!node.active) {
209 continue;
210 } else {
211 av.run = true;
212 }
213
214 if (m_debug)
215 std::cerr << m_className << "::NextAvalancheGridPoint:(ix,iy,iz) = ("
216 << node.ix << "," << node.iy << "," << node.iz << ").\n";
217
218 // Get avalanche size.
219 Nholder = node.n;
220
221 if (Nholder == 0) continue; // If empty go to next point.
222 // If the total avalanche size is smaller than the set saturation
223 // limit the GetAvalancheSize function is utilized to obtain the size
224 // after its propagation to the next z-coordinate grid point. Else,
225 // the size will be kept constant under the propagation.
226
227 if (!m_layerIndix && av.N < m_MaxSize) {
228 int holdnsize = GetAvalancheSize(node.stepSize, node.n, node.townsend,
229 node.attachment);
230
231 if (m_MaxSize - av.N < holdnsize - node.n)
232 holdnsize = m_MaxSize - av.N + node.n;
233
234 node.n = holdnsize;
235
236 } else if (m_layerIndix && m_NLayer[node.layer - 1] < m_MaxSize) {
237 int holdnsize = GetAvalancheSize(node.stepSize, node.n, node.townsend,
238 node.attachment);
239
240 if (m_MaxSize - m_NLayer[node.layer - 1] < holdnsize - node.n)
241 holdnsize = m_MaxSize - m_NLayer[node.layer - 1] + node.n;
242
243 node.n = holdnsize;
244
245 } else {
246 m_Saturated = true;
247
248 if (m_SaturationTime == -1) m_SaturationTime = node.time + node.dt;
249 }
250 // Produce induced signal on readout electrodes.
251
252 m_sensor->AddSignal(-(Nholder + node.n) / 2, node.time, node.time + node.dt,
253 av.xgrid[node.ix], av.ygrid[node.iy], av.zgrid[node.iz],
254 av.xgrid[node.ix + node.velNormal[0]],
255 av.ygrid[node.iy + node.velNormal[1]],
256 av.zgrid[node.iz + node.velNormal[2]], false, true);
257
258 // Update total number of electrons.
259
260 if (m_layerIndix) m_NLayer[node.layer - 1] += node.n - Nholder;
261
262 av.N += node.n - Nholder;
263
264 if (m_diffusion) {
265 // TODO: to impliment
266 }
267
268 if (m_debug) std::cerr << "n = " << Nholder << " -> " << node.n << ".\n";
269
270 // Update position index.
271
272 node.ix += node.velNormal[0];
273 node.iy += node.velNormal[1];
274 node.iz += node.velNormal[2];
275
276 // After all active grid points have propagated, update the time.
277 if (m_debug) std::cerr << "t = " << node.time << " -> ";
278 node.time += node.dt;
279 if (m_debug) std::cerr << node.time << ".\n";
280
281 DeactivateNode(node);
282 }
283 if (m_debug) std::cerr << "N = " << av.N << ".\n\n";
284}
285
286void AvalancheGrid::DeactivateNode(AvalancheNode &node) {
287
288 if (node.n == 0) node.active = false;
289
290 if (node.velNormal[2] != 0) {
291 if ((node.velNormal[2] < 0 && node.iz == 0) ||
292 (node.velNormal[2] > 0 && node.iz == m_avgrid.zsteps - 1))
293 node.active = false;
294 } else if (node.velNormal[1] != 0) {
295 if ((node.velNormal[1] < 0 && node.iy == 0) ||
296 (node.velNormal[1] > 0 && node.iy == m_avgrid.ysteps - 1))
297 node.active = false;
298 } else {
299 if ((node.velNormal[0] < 0 && node.ix == 0) ||
300 (node.velNormal[0] > 0 && node.ix == m_avgrid.xsteps - 1))
301 node.active = false;
302 }
303
304 double e[3], v;
305 int status;
306 Medium *m = nullptr;
307 m_sensor->ElectricField(m_avgrid.xgrid[node.ix], m_avgrid.ygrid[node.iy],
308 m_avgrid.zgrid[node.iz], e[0], e[1], e[2], v, m,
309 status);
310
311 if (status == -5 || status == -6) {
312 node.active = false; // If not inside a gas gap return false to terminate
313 }
314
315 if (m_debug && !node.active)
316 std::cerr << m_className << "::DeactivateNode: Node deactivated.\n";
317}
318
320 // Start the AvalancheGrid algorithm.
321 if ((!m_importAvalanche && !m_driftAvalanche) || !m_sensor) return;
322
323 std::cerr << m_className
324 << "::StartGridAvalanche::Starting grid based simulation with "
325 << m_avgrid.N << " initial electrons.\n";
326 if (m_avgrid.N <= 0) {
327 std::cerr << m_className << "::StartGridAvalanche::Cancelled.\n";
328 return;
329 }
330
331 m_nestart = m_avgrid.N;
332
333 // Main loop.
334 while (m_avgrid.run == true) {
335 if (m_debug)
336 std::cerr
337 << "============ \n" << m_className
338 << "::StartGridAvalanche: Looping over nodes.\n ============ \n";
339 NextAvalancheGridPoint(m_avgrid);
340 }
341
342 std::vector<double> tlist = {};
343 for (AvalancheNode &node : m_activeNodes) {
344 tlist.push_back(node.time);
345 }
346 double maxTime = *max_element(std::begin(tlist), std::end(tlist));
347
348 if (m_Saturated)
349 std::cerr << m_className
350 << "::StartGridAvalanche::Avalanche maximum size of " << m_MaxSize
351 << " electrons reached at " << m_SaturationTime << " ns.\n";
352
353 std::cerr << m_className
354 << "::StartGridAvalanche::Final avalanche size = " << m_avgrid.N
355 << " ended at t = " << maxTime << " ns.\n";
356
357 return;
358}
359
360void AvalancheGrid::AvalancheElectron(const double x, const double y,
361 const double z, const double t,
362 const int n) {
363 m_driftAvalanche = true;
364
365 if (m_avgrid.time == 0 && m_avgrid.time != t && m_debug)
366 std::cerr << m_className
367 << "::CreateAvalanche::Overwriting start time of avalanche for t "
368 "= 0 to " << t << ".\n";
369 m_avgrid.time = t;
370
371 if (SnapToGrid(m_avgrid, x, y, z, 0, n) && m_debug)
372 std::cerr << m_className
373 << "::CreateAvalanche::Electron added at (t,x,y,z) = (" << t
374 << "," << x << "," << y << "," << z << ").\n";
375}
376
378 AvalancheMicroscopic *avmc) {
379 // Get the information of the electrons from the AvalancheMicroscopic class.
380 if (!avmc) return;
381
382 if (!m_importAvalanche) m_importAvalanche = true;
383
384 // Get initial positions of electrons
385 for (const auto& electron : avmc->GetElectrons()) {
386 // If the electron is not stopped due to
387 // the upper bound of the time range: then skip this electron.
388 if (electron.status != -17) continue;
389 const auto& p1 = electron.path.at(0);
390 const auto& p2 = electron.path.back();
391 const double vel = (p2.z - p1.z) / (p2.t - p1.t);
392 m_avgrid.time = p2.t;
393
394 if (SnapToGrid(m_avgrid, p2.x, p2.y, p2.z, vel) && m_debug) {
395 std::cerr << m_className
396 << "::ImportElectronsFromAvalancheMicroscopic: Electron added at "
397 "(x,y,z) = (" << p2.x << "," << p2.y << "," << p2.z << ").\n";
398 }
399 }
400}
401
402bool AvalancheGrid::GetParameters(AvalancheNode &node) {
403 if (!m_sensor) return false;
404
405 double x = m_avgrid.xgrid[node.ix];
406 double y = m_avgrid.ygrid[node.iy];
407 double z = m_avgrid.zgrid[node.iz];
408
409 if (m_debug)
410 std::cerr << m_className
411 << "::GetParametersFromSensor::Getting parameters from "
412 "(x,y,z) = (" << x << "," << y << "," << z << ").\n";
413
414 double e[3], v;
415 int status;
416 Medium *m = nullptr;
417 m_sensor->ElectricField(x, y, z, e[0], e[1], e[2], v, m, status);
418
419 if (m_debug)
420 std::cerr << m_className << "::GetParametersFromSensor::status = " << status
421 << ".\n";
422
423 if (status == -5 || status == -6)
424 return false; // If not inside a gas gap return false to terminate
425
426 if (m_Townsend >=
427 0) { // If Townsend coef. is not set by user, take it from the sensor.
428 node.townsend = m_Townsend;
429 } else {
430 m->ElectronTownsend(e[0], e[1], e[2], 0., 0., 0., node.townsend);
431 }
432
433 if (m_Attachment >=
434 0) { // If attachment coef. is not set by user, take it from the sensor.
435 node.attachment = m_Attachment;
436 } else {
437 m->ElectronAttachment(e[0], e[1], e[2], 0., 0., 0., node.attachment);
438 }
439
440 if (m_Velocity >
441 0) { // If velocity is not set by user, take it from the sensor.
442 node.velocity = m_Velocity;
443 node.velNormal = m_velNormal;
444 } else {
445 double vx, vy, vz;
446 m->ElectronVelocity(e[0], e[1], e[2], 0., 0., 0., vx, vy, vz);
447
448 double vel = sqrt(vx * vx + vy * vy + vz * vz);
449 if (vel == 0.) return false;
450 if (vel != std::abs(vx) && vel != std::abs(vy) && vel != std::abs(vz))
451 return false;
452 int nx = (int)round(vx / vel);
453 int ny = (int)round(vy / vel);
454 int nz = (int)round(vz / vel);
455
456 node.velNormal = {nx, ny, nz};
457 node.velocity = -std::abs(vel);
458 }
459
460 if (node.velNormal[0] != 0) {
461 node.stepSize = m_avgrid.xStepSize;
462 } else if (node.velNormal[1] != 0) {
463 node.stepSize = m_avgrid.yStepSize;
464 } else {
465 node.stepSize = m_avgrid.zStepSize;
466 }
467
468 if (m_debug) {
469 std::cerr << m_className << "::GetParametersFromSensor:\n"
470 << " stepSize = " << node.stepSize << " [cm].\n"
471 << " velNormal = (" << node.velNormal[0] << ", "
472 << node.velNormal[1] << ", " << node.velNormal[2] << ") [1].\n";
473 }
474 node.dt = std::abs(node.stepSize / node.velocity);
475
476 // print
477 if (m_debug || !m_printPar) {
478 std::cerr << m_className << "::GetParametersFromSensor:\n"
479 << " Electric field = (" << 1.e-3 * e[0] << ", "
480 << 1.e-3 * e[1] << ", " << 1.e-3 * e[2] << ") [kV/cm].\n"
481 << " Townsend = " << node.townsend
482 << " [1/cm], Attachment = " << node.attachment
483 << " [1/cm], Velocity = " << node.velocity << " [cm/ns].\n";
484 }
485 if (m_debug)
486 std::cerr << m_className << "::StartGridAvalanche::Time steps per loop "
487 << node.dt << " ns.\n";
488 m_printPar = true;
489 return true;
490}
491
493
494 std::cerr << m_className << "::Reset::Resetting AvalancheGrid.\n";
495 m_avgrid.time = 0;
496 m_avgrid.N = 0;
497 m_avgrid.run = true;
498
499 m_Saturated = false;
500 m_SaturationTime = -1;
501
502 m_driftAvalanche = false;
503
504 m_activeNodes.clear();
505 m_layerIndix = false;
506 m_NLayer.clear();
507}
508
510 int im = 0;
511 double epsM = 0;
512 std::vector<double> nLayer(RPC->NumberOfLayers(), 0);
513 for (AvalancheNode &node : m_activeNodes) {
514 double y = m_avgrid.ygrid[node.iy];
515 RPC->getLayer(y, im, epsM);
516 node.layer = im;
517 nLayer[im - 1] += node.n;
518 // std::cerr << m_className << "::AsignLayerIndex::im = "<<im<<".\n";
519 }
520
521 m_NLayer = nLayer;
522 m_layerIndix = true;
523}
524
525} // namespace Garfield
void AvalancheElectron(const double x, const double y, const double z, const double t=0, const int n=1)
void ImportElectronsFromAvalancheMicroscopic(AvalancheMicroscopic *avmc)
Import electron data from AvalancheMicroscopic class.
void AsignLayerIndex(ComponentParallelPlate *RPC)
Asigning layer index to all Avalanche nodes.
void SetGrid(const double xmin, const double xmax, const int xsteps, const double ymin, const double ymax, const int ysteps, const double zmin, const double zmax, const int zsteps)
Import electron data from AvalancheMicroscopic class.
int GetAvalancheSize()
Returns the final number of electrons in the avalanche.
Calculate electron drift lines and avalanches using microscopic tracking.
const std::vector< Electron > & GetElectrons() const
Component for parallel-plate geometries.
bool getLayer(const double y, int &m, double &epsM)
Abstract base class for media.
Definition Medium.hh:16
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)
Drift velocity [cm / ns].
Definition Medium.cc:444
virtual bool ElectronTownsend(const double ex, const double ey, const double ez, const double bx, const double by, const double bz, double &alpha)
Ionisation coefficient [cm-1].
Definition Medium.cc:468
virtual bool ElectronAttachment(const double ex, const double ey, const double ez, const double bx, const double by, const double bz, double &eta)
Attachment coefficient [cm-1].
Definition Medium.cc:481
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:70
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 sqrt(const DoubleAc &f)
Definition DoubleAc.cpp:314