Garfield++ 5.0
A toolkit for the detailed simulation of particle detectors based on ionisation measurement in gases and semiconductors
Loading...
Searching...
No Matches
DriftLineRKF.cc
Go to the documentation of this file.
1#include <iostream>
2#include <cstdio>
3#include <cmath>
4#include <numeric>
5
10#include "Garfield/Random.hh"
11
12namespace {
13
14std::string PrintVec(const std::array<double, 3>& x) {
15
16 return "(" + std::to_string(x[0]) + ", " + std::to_string(x[1]) + ", " +
17 std::to_string(x[2]) + ")";
18}
19
20double Mag(const std::array<double, 3>& x) {
21
22 return sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
23}
24
25double Mag(const double x, const double y, const double z) {
26
27 return sqrt(x * x + y * y + z * z);
28}
29
30double Mag(const double x, const double y) {
31
32 return sqrt(x * x + y * y);
33}
34
35double Mag2(const double x, const double y) {
36
37 return x * x + y * y;
38}
39
40double Dist(const std::array<double, 3>& x0,
41 const std::array<double, 3>& x1) {
42
43 return Mag(x1[0] - x0[0], x1[1] - x0[1], x1[2] - x0[2]);
44}
45
46std::array<double, 3> MidPoint(const std::array<double, 3>& x0,
47 const std::array<double, 3>& x1) {
48 std::array<double, 3> xm;
49 for (size_t k = 0; k < 3; ++k) xm[k] = 0.5 * (x0[k] + x1[k]);
50 return xm;
51}
52}
53
54namespace Garfield {
55
56using Vec = std::array<double, 3>;
57
58DriftLineRKF::DriftLineRKF(Sensor* sensor) : m_sensor(sensor) {
59 m_t.reserve(1000);
60 m_x.reserve(1000);
61}
62
64 if (!s) {
65 std::cerr << m_className << "::SetSensor: Null pointer.\n";
66 return;
67 }
68 m_sensor = s;
69}
70
72 if (eps > 0.) {
73 m_accuracy = eps;
74 } else {
75 std::cerr << m_className << "::SetIntegrationAccuracy:\n"
76 << " Accuracy must be greater than zero.\n";
77 }
78}
79
81 m_maxStepSize = -1.;
82 m_useStepSizeLimit = true;
83}
84
85void DriftLineRKF::SetMaximumStepSize(const double ms) {
86 if (ms > 0.) {
87 m_maxStepSize = ms;
88 m_useStepSizeLimit = true;
89 } else {
90 std::cerr << m_className << "::SetMaximumStepSize:\n"
91 << " Step size must be greater than zero.\n";
92 }
93}
94
96 if (!view) {
97 std::cerr << m_className << "::EnablePlotting: Null pointer.\n";
98 return;
99 }
100 m_view = view;
101}
102
103void DriftLineRKF::DisablePlotting() { m_view = nullptr; }
104
106
107 if (gain > 1.) {
108 std::cout << m_className << "::SetGainFluctuationsFixed: "
109 << "Avalanche size set to " << gain << ".\n";
110 } else {
111 std::cout << m_className << "::SetGainFluctuationsFixed:\n "
112 << "Avalanche size will be given by "
113 << "the integrated Townsend coefficient.\n";
114 }
115 m_gain = gain;
116 m_gainFluctuations = GainFluctuations::None;
117}
118
120 const double mean,
121 const bool quiet) {
122
123 if (theta < 0.) {
124 std::cerr << m_className << "::SetGainFluctuationsPolya: "
125 << "Shape parameter must be >= 0.\n";
126 return;
127 }
128 if (!quiet) {
129 if (mean > 1.) {
130 std::cout << m_className << "::SetGainFluctuationsPolya: "
131 << "Mean avalanche size set to " << mean << ".\n";
132 } else {
133 std::cout << m_className << "::SetGainFluctuationsPolya:\n "
134 << "Mean avalanche size will be given by "
135 << "the integrated Townsend coefficient.\n";
136 }
137 }
138 m_gain = mean;
139 m_theta = theta;
140 m_gainFluctuations = GainFluctuations::Polya;
141}
142
143bool DriftLineRKF::DriftElectron(const double x0, const double y0,
144 const double z0, const double t0) {
145 std::vector<std::array<double, 3> > x;
146 std::vector<double> t;
147 int status = 0;
148 const bool ok = DriftLine({x0, y0, z0}, t0, Particle::Electron,
149 t, x, status);
150 if (ok) {
151 const size_t nPoints = t.size();
152 std::vector<double> ne(nPoints, 1.);
153 std::vector<double> ni(nPoints, 0.);
154 std::vector<double> nn(nPoints, 0.);
155 double scale = 1.;
156 if (m_doAvalanche) Avalanche(Particle::Electron, x, ne, ni, nn, scale);
157 if (m_doSignal) {
158 ComputeSignal(Particle::Electron, scale * m_scaleE, t, x, ne);
159 }
160 if (m_doAvalanche) {
161 if (m_doIonTail) AddIonTail(t, x, ni, scale);
162 if (m_doNegativeIonTail) AddNegativeIonTail(t, x, nn, scale);
163 }
164 m_nE = scale * ne.back();
165 m_nI = scale * std::accumulate(ni.begin(), ni.end(), 0.);
166 } else {
167 m_nE = 0.;
168 m_nI = 0.;
169 }
170 std::swap(m_x, x);
171 std::swap(m_t, t);
172 m_particle = Particle::Electron;
173 m_status = status;
174 return ok;
175}
176
177bool DriftLineRKF::AddIonTail(const std::vector<double>& te,
178 const std::vector<Vec>& xe,
179 const std::vector<double>& ni,
180 const double scale) const {
181 // SIGETR, SIGIOR
182 const size_t nPoints = te.size();
183 if (nPoints < 2 || ni.size() != nPoints) return false;
184 // Loop over the electron track.
185 for (size_t i = 1; i < nPoints; ++i) {
186 // Skip points at which there are no ions yet.
187 if (scale * ni[i] < 1.) continue;
188 // Skip also points with a negligible contribution.
189 // if (scale * ni[i] < threshold * m_nI) continue;
190 // Compute the ion drift line.
191 std::vector<double> ti;
192 std::vector<Vec> xi;
193 int stat = 0;
194 if (!DriftLine(xe[i], te[i], Particle::Ion, ti, xi, stat)) {
195 std::cerr << m_className << "::AddIonTail:\n"
196 << " Unable to obtain an ion tail; tail not added.\n";
197 return false;
198 }
199 if (m_debug) {
200 std::cout << m_className << "::AddIonTail: Origin = " << PrintVec(xe[i])
201 << ", n = " << ti.size() << ", status = " << stat << "\n";
202 }
203 // Compute the contribution of the drift line to the signal.
204 ComputeSignal(Particle::Ion, scale * m_scaleI * ni[i], ti, xi, {});
205 }
206 return true;
207}
208
209bool DriftLineRKF::AddNegativeIonTail(
210 const std::vector<double>& te, const std::vector<Vec>& xe,
211 const std::vector<double>& nn, const double scale) const {
212 const size_t nPoints = te.size();
213 if (nPoints < 2 || nn.size() != nPoints) return false;
214 // Loop over the electron track.
215 for (size_t i = 1; i < nPoints; ++i) {
216 // Skip points at which there are no negative ions yet.
217 if (scale * nn[i] < Small) continue;
218 // Compute the negative ion drift line.
219 std::vector<double> tn;
220 std::vector<Vec> xn;
221 int stat = 0;
222 if (!DriftLine(xe[i], te[i], Particle::NegativeIon, tn, xn, stat)) {
223 std::cerr << m_className << "::AddNegativeIonTail:\n"
224 << " Unable to obtain a negative ion tail.\n";
225 return false;
226 }
227 // Compute the contribution of the drift line to the signal.
228 ComputeSignal(Particle::NegativeIon, scale * m_scaleI * nn[i], tn, xn, {});
229 }
230 return true;
231}
232
233bool DriftLineRKF::DriftPositron(const double x0, const double y0,
234 const double z0, const double t0) {
235 std::vector<std::array<double, 3> > x;
236 std::vector<double> t;
237 int status = 0;
238 const bool ok = DriftLine({x0, y0, z0}, t0, Particle::Positron,
239 t, x, status);
240 if (ok && m_doSignal) {
241 ComputeSignal(Particle::Positron, m_scaleE, t, x, {});
242 }
243 std::swap(m_x, x);
244 std::swap(m_t, t);
245 m_particle = Particle::Positron;
246 m_status = status;
247 return ok;
248}
249
250bool DriftLineRKF::DriftHole(const double x0, const double y0, const double z0,
251 const double t0) {
252 std::vector<std::array<double, 3> > x;
253 std::vector<double> t;
254 int status = 0;
255 const bool ok = DriftLine({x0, y0, z0}, t0, Particle::Hole, t, x, status);
256 if (ok && m_doSignal) {
257 ComputeSignal(Particle::Hole, m_scaleH, t, x, {});
258 }
259 std::swap(m_x, x);
260 std::swap(m_t, t);
261 m_particle = Particle::Hole;
262 m_status = status;
263 return ok;
264}
265
266bool DriftLineRKF::DriftIon(const double x0, const double y0, const double z0,
267 const double t0) {
268 std::vector<std::array<double, 3> > x;
269 std::vector<double> t;
270 int status = 0;
271 const bool ok = DriftLine({x0, y0, z0}, t0, Particle::Ion, t, x, status);
272 if (ok && m_doSignal) {
273 ComputeSignal(Particle::Ion, m_scaleI, t, x, {});
274 }
275 std::swap(m_x, x);
276 std::swap(m_t, t);
277 m_particle = Particle::Ion;
278 m_status = status;
279 return ok;
280}
281
282bool DriftLineRKF::DriftNegativeIon(const double x0, const double y0,
283 const double z0, const double t0) {
284 std::vector<std::array<double, 3> > x;
285 std::vector<double> t;
286 int status = 0;
287 const bool ok = DriftLine({x0, y0, z0}, t0, Particle::NegativeIon,
288 t, x, status);
289 if (ok && m_doSignal) {
290 ComputeSignal(Particle::NegativeIon, m_scaleI, t, x, {});
291 }
292 std::swap(m_x, x);
293 std::swap(m_t, t);
294 m_particle = Particle::NegativeIon;
295 m_status = status;
296 return ok;
297}
298
299bool DriftLineRKF::DriftLine(const Vec& xi, const double ti,
300 const Particle particle,
301 std::vector<double>& ts,
302 std::vector<Vec>& xs, int& flag) const {
303
304 // -----------------------------------------------------------------------
305 // DLCALC - Subroutine doing the actual drift line calculations.
306 // The calculations are based on a Runge-Kutta-Fehlberg method
307 // which has the advantage of controlling the stepsize and the
308 // error while needing only relatively few calls to EFIELD.
309 // Full details are given in the reference quoted below.
310 // VARIABLES : H : Current stepsize (it is in fact a delta t).
311 // HPREV : Stores the previous value of H (comparison)
312 // INITCH : Used for checking initial stepsize (1 = ok)
313 // Other variables such as F0, F1, F2, F3, PHII, PHIII,
314 // CI. ,CII. , BETA.. etc are explained in the reference.
315 // REFERENCE : Stoer + Bulirsch, Einfuhrung in die Numerische
316 // Mathematic II, chapter 7, page 122, 1978, HTB, Springer.
317 // -----------------------------------------------------------------------
318
319 // Set the numerical constants for the RKF integration.
320 constexpr double c10 = 214. / 891.;
321 constexpr double c11 = 1. / 33.;
322 constexpr double c12 = 650. / 891.;
323 constexpr double c20 = 533. / 2106.;
324 constexpr double c22 = 800. / 1053.;
325 constexpr double c23 = -1. / 78.;
326
327 constexpr double b10 = 1. / 4.;
328 constexpr double b20 = -189. / 800.;
329 constexpr double b21 = 729. / 800.;
330 constexpr double b30 = 214. / 891.;
331 constexpr double b31 = 1. / 33.;
332 constexpr double b32 = 650. / 891.;
333
334 // Reset the drift line.
335 ts.clear();
336 xs.clear();
337 // Reset the status flag.
338 flag = StatusAlive;
339
340 // Check if the sensor is defined.
341 if (!m_sensor) {
342 std::cerr << m_className << "::DriftLine: Sensor is not defined.\n";
343 flag = StatusCalculationAbandoned;
344 return false;
345 }
346
347 // Get the sensor's bounding box.
348 double xmin = 0., xmax = 0.;
349 double ymin = 0., ymax = 0.;
350 double zmin = 0., zmax = 0.;
351 bool bbox = m_sensor->GetArea(xmin, ymin, zmin, xmax, ymax, zmax);
352
353 // Set the step size limit if requested.
354 double maxStep = -1.;
355 if (m_useStepSizeLimit) {
356 if (m_maxStepSize > 0.) {
357 maxStep = m_maxStepSize;
358 } else {
359 maxStep = 0.5 * m_sensor->StepSizeHint();
360 }
361 }
362
363 // Set the charge of the drifting particle.
364 const double charge = Charge(particle);
365
366 // Initialise the current position and velocity.
367 Vec x0 = xi;
368 Vec v0 = GetVelocity(x0, particle, flag);
369 if (flag != 0) {
370 std::cerr << m_className << "::DriftLine:\n"
371 << " Cannot retrieve drift velocity at initial position "
372 << PrintVec(x0) << ".\n";
373 return false;
374 }
375
376 const double speed0 = Mag(v0);
377 if (speed0 < Small) {
378 std::cerr << m_className << "::DriftLine: "
379 << "Zero velocity at initial position.\n";
380 return false;
381 }
382
383 // Initialise time step and previous time step.
384 double h = m_accuracy / speed0;
385 double hprev = h;
386 double t0 = ti;
387
388 // Set the initial point.
389 ts.push_back(t0);
390 xs.push_back(x0);
391
392 if (m_debug) {
393 std::cout << m_className << "::DriftLine:\n"
394 << " Initial step size: " << h << " ns.\n";
395 }
396 int initCycle = 3;
397 bool ok = true;
398 while (ok) {
399 // Get the velocity at the first probe point.
400 Vec x1 = x0;
401 for (unsigned int i = 0; i < 3; ++i) {
402 x1[i] += h * b10 * v0[i];
403 }
404 int stat = 0;
405 const Vec v1 = GetVelocity(x1, particle, stat);
406 if (stat == StatusCalculationAbandoned) {
407 flag = stat;
408 break;
409 } else if (stat != 0) {
410 if (m_debug) std::cout << " Point 1 outside.\n";
411 if (!Terminate(x0, x1, particle, ts, xs)) {
412 flag = StatusCalculationAbandoned;
413 } else {
414 flag = stat;
415 }
416 break;
417 }
418 // Get the velocity at the second probe point.
419 Vec x2 = x0;
420 for (unsigned int i = 0; i < 3; ++i) {
421 x2[i] += h * (b20 * v0[i] + b21 * v1[i]);
422 }
423 const Vec v2 = GetVelocity(x2, particle, stat);
424 if (stat == StatusCalculationAbandoned) {
425 flag = stat;
426 break;
427 } else if (stat != 0) {
428 if (m_debug) std::cout << " Point 2 outside.\n";
429 if (!Terminate(x0, x2, particle, ts, xs)) {
430 flag = StatusCalculationAbandoned;
431 } else {
432 flag = stat;
433 }
434 break;
435 }
436 // Get the velocity at the third probe point.
437 Vec x3 = x0;
438 for (unsigned int i = 0; i < 3; ++i) {
439 x3[i] += h * (b30 * v0[i] + b31 * v1[i] + b32 * v2[i]);
440 }
441 const Vec v3 = GetVelocity(x3, particle, stat);
442 if (stat == StatusCalculationAbandoned) {
443 flag = stat;
444 break;
445 } else if (stat != 0) {
446 if (m_debug) std::cout << " Point 3 outside.\n";
447 if (!Terminate(x0, x3, particle, ts, xs)) {
448 flag = StatusCalculationAbandoned;
449 } else {
450 flag = stat;
451 }
452 break;
453 }
454 // Check if we crossed a wire.
455 double xw = 0., yw = 0., zw = 0., rw = 0.;
456 if (m_sensor->CrossedWire(x0[0], x0[1], x0[2],
457 x1[0], x1[1], x1[2], xw, yw, zw, true, rw) ||
458 m_sensor->CrossedWire(x0[0], x0[1], x0[2],
459 x2[0], x2[1], x2[2], xw, yw, zw, true, rw) ||
460 m_sensor->CrossedWire(x0[0], x0[1], x0[2],
461 x3[0], x3[1], x3[2], xw, yw, zw, true, rw)) {
462 if (m_debug) std::cout << " Crossed wire.\n";
463 if (DriftToWire(xw, yw, rw, particle, ts, xs, stat)) {
464 flag = stat;
465 } else if (h > Small) {
466 h *= 0.5;
467 continue;
468 } else {
469 std::cerr << m_className << "::DriftLine: Step size too small. Stop.\n";
470 flag = StatusCalculationAbandoned;
471 }
472 break;
473 }
474 // Check if we are inside the trap radius of a wire.
475 if (particle != Particle::Ion) {
476 if (m_sensor->InTrapRadius(charge, x1[0], x1[1], x1[2], xw, yw, rw)) {
477 if (!DriftToWire(xw, yw, rw, particle, ts, xs, flag)) {
478 flag = StatusCalculationAbandoned;
479 }
480 break;
481 }
482 if (m_sensor->InTrapRadius(charge, x2[0], x2[1], x2[2], xw, yw, rw)) {
483 if (!DriftToWire(xw, yw, rw, particle, ts, xs, flag)) {
484 flag = StatusCalculationAbandoned;
485 }
486 break;
487 }
488 if (m_sensor->InTrapRadius(charge, x3[0], x3[1], x3[2], xw, yw, rw)) {
489 if (!DriftToWire(xw, yw, rw, particle, ts, xs, flag)) {
490 flag = StatusCalculationAbandoned;
491 }
492 break;
493 }
494 }
495 // Check if we crossed a plane.
496 Vec xp = {0., 0., 0.};
497 if (m_sensor->CrossedPlane(x0[0], x0[1], x0[2],
498 x1[0], x1[1], x1[2], xp[0], xp[1], xp[2]) ||
499 m_sensor->CrossedPlane(x0[0], x0[1], x0[2],
500 x2[0], x2[1], x2[2], xp[0], xp[1], xp[2]) ||
501 m_sensor->CrossedPlane(x0[0], x0[1], x0[2],
502 x3[0], x3[1], x3[2], xp[0], xp[1], xp[2])) {
503 // DLCPLA
504 ts.push_back(t0 + Dist(x0, xp) / Mag(v0));
505 xs.push_back(xp);
506 flag = StatusHitPlane;
507 break;
508 }
509 // Calculate the correction terms.
510 Vec phi1 = {0., 0., 0.};
511 Vec phi2 = {0., 0., 0.};
512 for (size_t i = 0; i < 3; ++i) {
513 phi1[i] = c10 * v0[i] + c11 * v1[i] + c12 * v2[i];
514 phi2[i] = c20 * v0[i] + c22 * v2[i] + c23 * v3[i];
515 }
516 // Check if the step length is valid.
517 const double phi1mag = Mag(phi1);
518 if (phi1mag < Small) {
519 std::cerr << m_className << "::DriftLine: Step has zero length. Stop.\n";
520 flag = StatusCalculationAbandoned;
521 break;
522 } else if (maxStep > 0. && h * phi1mag > maxStep) {
523 if (m_debug) {
524 std::cout << " Step is considered too long. H is reduced.\n";
525 }
526 h = 0.5 * maxStep / phi1mag;
527 continue;
528 } else if (bbox) {
529 // Don't allow h to become too large in view of the time resolution.
530 if (h * fabs(phi1[0]) > 0.1 * fabs(xmax - xmin) ||
531 h * fabs(phi1[1]) > 0.1 * fabs(ymax - ymin)) {
532 h *= 0.5;
533 if (m_debug) {
534 std::cout << " Step is considered too long. H is halved.\n";
535 }
536 continue;
537 }
538 } else if (m_rejectKinks && xs.size() > 1) {
539 const unsigned int np = xs.size();
540 const auto& x = xs[np - 1];
541 const auto& xprev = xs[np - 2];
542 if (phi1[0] * (x[0] - xprev[0]) + phi1[1] * (x[1] - xprev[1]) +
543 phi1[2] * (x[2] - xprev[2]) < 0.) {
544 std::cerr << m_className << "::DriftLine: Bending angle > 90 degree.\n";
545 flag = StatusSharpKink;
546 break;
547 }
548 }
549 if (m_debug) std::cout << " Step size ok.\n";
550 // Update the position and time.
551 for (size_t i = 0; i < 3; ++i) x0[i] += h * phi1[i];
552 t0 += h;
553 if (!m_sensor->IsInside(x0[0], x0[1], x0[2])) {
554 // The new position is not inside a valid drift medium.
555 // Terminate the drift line.
556 if (m_debug) std::cout << " New point is outside. Terminate.\n";
557 if (!Terminate(xs.back(), x0, particle, ts, xs)) {
558 flag = StatusCalculationAbandoned;
559 }
560 break;
561 }
562 // Add the new point to the drift line.
563 ts.push_back(t0);
564 xs.push_back(x0);
565 // Adjust the step size according to the accuracy of the two estimates.
566 hprev = h;
567 const double dphi = fabs(phi1[0] - phi2[0]) + fabs(phi1[1] - phi2[1]) +
568 fabs(phi1[2] - phi2[2]);
569 if (dphi > 0) {
570 h = sqrt(h * m_accuracy / dphi);
571 if (m_debug) std::cout << " Adapting H to " << h << ".\n";
572 } else {
573 h *= 2;
574 if (m_debug) std::cout << " H increased by factor two.\n";
575 }
576 // Make sure that H is different from zero; this should always be ok.
577 if (h < Small) {
578 std::cerr << m_className << "::DriftLine: Step size is zero. Stop.\n";
579 flag = StatusCalculationAbandoned;
580 break;
581 }
582 // Check the initial step size.
583 if (initCycle > 0 && h < 0.2 * hprev) {
584 if (m_debug) std::cout << " Reinitialise step size.\n";
585 --initCycle;
586 t0 = ti;
587 x0 = xi;
588 ts = {t0};
589 xs = {x0};
590 continue;
591 }
592 initCycle = 0;
593 // Don't allow H to grow too quickly
594 if (h > 10 * hprev) {
595 h = 10 * hprev;
596 if (m_debug) {
597 std::cout << " H restricted to 10 times the previous value.\n";
598 }
599 }
600 // Stop in case H tends to become too small.
601 if (h * (fabs(phi1[0]) + fabs(phi1[1]) + fabs(phi1[2])) < m_accuracy) {
602 std::cerr << m_className << "::DriftLine: Step size has become smaller "
603 << "than int. accuracy. Stop.\n";
604 flag = StatusCalculationAbandoned;
605 break;
606 }
607 // Update the velocity.
608 v0 = v3;
609 }
610 if (m_view) {
611 // If requested, add the drift line to a plot.
612 const size_t nP = xs.size();
613 const size_t id = m_view->NewDriftLine(particle, nP, xi[0], xi[1], xi[2]);
614 for (size_t i = 0; i < nP; ++i) {
615 m_view->SetDriftLinePoint(id, i, xs[i][0], xs[i][1], xs[i][2]);
616 }
617 }
618 if (flag == StatusCalculationAbandoned) return false;
619 return true;
620}
621
622bool DriftLineRKF::Avalanche(const Particle particle,
623 const std::vector<Vec>& xs,
624 std::vector<double>& ne,
625 std::vector<double>& ni,
626 std::vector<double>& nn, double& scale) const {
627
628 // SIGETR
629 const size_t nPoints = xs.size();
630 if (nPoints < 2) return true;
631 // Locations and weights for 6-point Gaussian integration.
632 const size_t nG = 6;
635
636 ne.assign(nPoints, 1.);
637 ni.assign(nPoints, 0.);
638 nn.assign(nPoints, 0.);
639 bool start = false;
640 bool overflow = false;
641 // Loop over the drift line.
642 for (size_t i = 1; i < nPoints; ++i) {
643 const auto& xp = xs[i - 1];
644 const auto& x = xs[i];
645 const Vec dx = {x[0] - xp[0], x[1] - xp[1], x[2] - xp[2]};
646 // Calculate the integrated Townsend and attachment coefficients.
647 double alpsum = 0.;
648 double etasum = 0.;
649 for (size_t j = 0; j < nG; ++j) {
650 const double f = 0.5 * (1. + tg[j]);
651 Vec xj = xp;
652 for (size_t k = 0; k < 3; ++k) xj[k] += f * dx[k];
653 const double alp = GetAlpha(xj, particle);
654 if (alp < 0.) {
655 std::cerr << m_className << "::Avalanche:\n Cannot retrieve alpha at "
656 << "drift line point " << i << ", segment " << j << ".\n";
657 continue;
658 }
659 const double eta = GetEta(xj, particle);
660 if (eta < 0.) {
661 std::cerr << m_className << "::Avalanche:\n Cannot retrieve eta at "
662 << "drift line point " << i << ", segment " << j << ".\n";
663 continue;
664 }
665 alpsum += wg[j] * alp;
666 etasum += wg[j] * eta;
667 }
668 alpsum *= 0.5;
669 etasum *= 0.5;
670 if (alpsum > 1.e-6 && !start) {
671 if (m_debug) {
672 std::cout << m_className << "::Avalanche: Avalanche starts at step "
673 << i << ".\n";
674 }
675 start = true;
676 }
677 const double d = Mag(dx);
678 // Update the number of electrons.
679 constexpr double expmax = 30.;
680 const double logp = log(std::max(1., ne[i - 1]));
681 if (logp + d * (alpsum - etasum) > expmax) {
682 overflow = true;
683 ne[i] = exp(expmax);
684 } else {
685 ne[i] = ne[i - 1] * exp(d * (alpsum - etasum));
686 }
687 // Update the number of ions.
688 if (logp + d * alpsum > expmax) {
689 overflow = true;
690 ni[i] = exp(expmax);
691 } else {
692 ni[i] = ne[i - 1] * (exp(d * alpsum) - 1);
693 }
694 nn[i] = std::max(ne[i - 1] + ni[i] - ne[i], 0.);
695 }
696 if (overflow) {
697 std::cerr << m_className << "::Avalanche:\n "
698 << "Warning: Integrating the Townsend coefficients "
699 << "would lead to exponential overflow.\n "
700 << "Avalanche truncated.\n";
701 }
702 const double qe = ne.back();
703 const double qi = std::accumulate(ni.begin(), ni.end(), 0.);
704 scale = 1.;
705 if (qi > 1. &&
706 !(m_gainFluctuations == GainFluctuations::None && m_gain < 1.)) {
707 constexpr double eps = 1.e-4;
708 const double gain = m_gain > 1. ? m_gain : ComputeGain(xs, particle, eps);
709 double q1 = gain;
710 if (m_gainFluctuations == GainFluctuations::Polya) {
711 for (unsigned int i = 0; i < 100; ++i) {
712 q1 = gain * RndmPolya(m_theta);
713 if (q1 >= 1.) break;
714 }
715 q1 = std::max(q1, 1.);
716 }
717 q1 *= ComputeLoss(xs, particle, eps);
718 scale = (q1 + 1.) / (qi + 1.);
719 }
720 if (m_debug) {
721 const double qn = std::accumulate(nn.begin(), nn.end(), 0.);
722 std::cout << m_className << "::Avalanche:\n "
723 << "Final number of electrons: " << qe << "\n "
724 << "Number of positive ions: " << qi << "\n "
725 << "Number of negative ions: " << qn << "\n "
726 << "Charge scaling factor: " << scale << "\n "
727 << "Avalanche development:\n Step Electrons Ions\n";
728 for (unsigned int i = 0; i < nPoints; ++i) {
729 std::printf("%6u %15.7f %15.7f\n", i, scale * ne[i], scale * ni[i]);
730 }
731 }
732 return true;
733}
734
735double DriftLineRKF::GetArrivalTimeSpread(const double eps) const {
736 return ComputeSigma(m_x, m_particle, eps);
737}
738
739double DriftLineRKF::ComputeSigma(const std::vector<Vec>& x,
740 const Particle particle,
741 const double eps) const {
742
743 // -----------------------------------------------------------------------
744 // DLCDF1 - Routine returning the integrated diffusion coefficient of
745 // the current drift line. The routine uses an adaptive
746 // Simpson integration.
747 // -----------------------------------------------------------------------
748
749 const size_t nPoints = x.size();
750 // Return straight away if there is only one point.
751 if (nPoints < 2) return 0.;
752
753 // First get a rough estimate of the result.
754 double crude = 0.;
755 double varPrev = 0.;
756 for (size_t i = 0; i < nPoints; ++i) {
757 // Get the variance at this point.
758 const double var = GetVar(x[i], particle);
759 if (var < 0.) {
760 std::cerr << m_className << "::ComputeSigma:\n"
761 << " Cannot retrieve variance at point " << i << ".\n";
762 continue;
763 }
764 if (i > 0) crude += 0.5 * Dist(x[i - 1], x[i]) * (var + varPrev);
765 varPrev = var;
766 }
767 crude = sqrt(crude);
768
769 const double tol = eps * crude;
770 double sum = 0.;
771 for (size_t i = 0; i < nPoints - 1; ++i) {
772 sum += IntegrateDiffusion(x[i], x[i + 1], particle, tol);
773 }
774 return sqrt(sum);
775}
776
777double DriftLineRKF::GetGain(const double eps) const {
778 if (m_status == StatusCalculationAbandoned) return 1.;
779 return ComputeGain(m_x, m_particle, eps);
780}
781
782double DriftLineRKF::ComputeGain(const std::vector<Vec>& x,
783 const Particle particle,
784 const double eps) const {
785
786 // -----------------------------------------------------------------------
787 // DLCTW1 - Routine returning the multiplication factor for the current
788 // drift line. The routine uses an adaptive Simpson style
789 // integration.
790 // -----------------------------------------------------------------------
791
792 const size_t nPoints = m_x.size();
793 // Return straight away if there is only one point.
794 if (nPoints < 2) return 1.;
795 if (particle == Particle::Ion || particle == Particle::NegativeIon) {
796 return 1.;
797 }
798
799 // First get a rough estimate of the result.
800 double crude = 0.;
801 double alphaPrev = 0.;
802 for (size_t i = 0; i < nPoints; ++i) {
803 // Get the Townsend coefficient at this point.
804 const double alpha = GetAlpha(x[i], particle);
805 if (alpha < 0.) {
806 std::cerr << m_className << "::ComputeGain:\n"
807 << " Cannot retrieve alpha at point " << i << ".\n";
808 continue;
809 }
810 if (i > 0) crude += 0.5 * Dist(x[i - 1], x[i]) * (alpha + alphaPrev);
811 alphaPrev = alpha;
812 }
813 // Stop if the rough estimate is negligibly small.
814 if (crude < Small) return 1.;
815
816 // Calculate the integration tolerance based on the rough estimate.
817 const double tol = eps * crude;
818 double sum = 0.;
819 for (size_t i = 0; i < nPoints - 1; ++i) {
820 sum += IntegrateAlpha(x[i], x[i + 1], particle, tol);
821 }
822 return exp(sum);
823}
824
825double DriftLineRKF::GetLoss(const double eps) const {
826 if (m_status == StatusCalculationAbandoned) return 1.;
827 return ComputeLoss(m_x, m_particle, eps);
828}
829
830double DriftLineRKF::ComputeLoss(const std::vector<Vec>& x,
831 const Particle particle,
832 const double eps) const {
833
834 // -----------------------------------------------------------------------
835 // DLCAT1 - Routine returning the attachment losses for the current
836 // drift line. The routine uses an adaptive Simpson style
837 // integration.
838 // -----------------------------------------------------------------------
839
840 const size_t nPoints = x.size();
841 // Return straight away if there is only one point.
842 if (nPoints < 2) return 1.;
843 if (particle == Particle::Ion || particle == Particle::NegativeIon) {
844 return 1.;
845 }
846
847 // First get a rough estimate of the result.
848 double crude = 0.;
849 double etaPrev = 0.;
850 for (size_t i = 0; i < nPoints; ++i) {
851 // Get the attachment coefficient at this point.
852 const double eta = GetEta(x[i], particle);
853 if (eta < 0.) {
854 std::cerr << m_className << "::ComputeLoss:\n"
855 << " Cannot retrieve eta at point " << i << ".\n";
856 continue;
857 }
858 if (i > 0) crude += 0.5 * Dist(x[i - 1], x[i]) * (eta + etaPrev);
859 etaPrev = eta;
860 }
861
862 // Calculate the integration tolerance based on the rough estimate.
863 const double tol = eps * crude;
864 double sum = 0.;
865 for (size_t i = 0; i < nPoints - 1; ++i) {
866 sum += IntegrateEta(x[i], x[i + 1], particle, tol);
867 }
868 return exp(-sum);
869}
870
872
873 const size_t nPoints = m_x.size();
874 if (nPoints < 2) return 0.;
875 double path = 0.;
876 for (size_t i = 1; i < nPoints; ++i) {
877 path += Dist(m_x[i - 1], m_x[i]);
878 }
879 return path;
880}
881
882int DriftLineRKF::GetField(const std::array<double, 3>& x,
883 double& ex, double& ey, double& ez,
884 double& bx, double& by, double& bz,
885 Medium*& medium) const {
886 int status = 0;
887 m_sensor->MagneticField(x[0], x[1], x[2], bx, by, bz, status);
888 m_sensor->ElectricField(x[0], x[1], x[2], ex, ey, ez, medium, status);
889 return status;
890}
891
892Vec DriftLineRKF::GetVelocity(const std::array<double, 3>& x,
893 const Particle particle,
894 int& status) const {
895 Vec v = {0., 0., 0.};
896 status = 0;
897 // Stop if we are outside the drift area.
898 if (!m_sensor->IsInArea(x[0], x[1], x[2])) {
899 status = StatusLeftDriftArea;
900 return v;
901 }
902 if (m_useVelocityMap &&
903 particle != Particle::Ion && particle != Particle::NegativeIon) {
904 // We assume there is only one component with a velocity map.
905 const auto nComponents = m_sensor->GetNumberOfComponents();
906 for (size_t i = 0; i < nComponents; ++i) {
907 auto cmp = m_sensor->GetComponent(i);
908 if (!cmp->HasVelocityMap()) continue;
909 bool ok = false;
910 if (particle == Particle::Electron || particle == Particle::Positron) {
911 ok = cmp->ElectronVelocity(x[0], x[1], x[2], v[0], v[1], v[2]);
912 } else if (particle == Particle::Hole) {
913 ok = cmp->HoleVelocity(x[0], x[1], x[2], v[0], v[1], v[2]);
914 }
915 if (!ok) continue;
916 // Seems to have worked.
917 if (particle == Particle::Positron) {
918 for (unsigned int k = 0; k < 3; ++k) v[k] *= -1;
919 }
920 return v;
921 }
922 }
923 double ex = 0., ey = 0., ez = 0.;
924 double bx = 0., by = 0., bz = 0.;
925 Medium* medium = nullptr;
926 // Stop if we are outside a valid drift medium.
927 status = GetField(x, ex, ey, ez, bx, by, bz, medium);
928 if (status != 0) return v;
929 bool ok = false;
930 if (particle == Particle::Electron) {
931 ok = medium->ElectronVelocity(ex, ey, ez, bx, by, bz, v[0], v[1], v[2]);
932 } else if (particle == Particle::Ion) {
933 ok = medium->IonVelocity(ex, ey, ez, bx, by, bz, v[0], v[1], v[2]);
934 } else if (particle == Particle::Hole) {
935 ok = medium->HoleVelocity(ex, ey, ez, bx, by, bz, v[0], v[1], v[2]);
936 } else if (particle == Particle::Positron) {
937 ok = medium->ElectronVelocity(ex, ey, ez, bx, by, bz, v[0], v[1], v[2]);
938 for (unsigned int i = 0; i < 3; ++i) v[i] *= -1;
939 } else if (particle == Particle::NegativeIon) {
940 ok = medium->NegativeIonVelocity(ex, ey, ez, bx, by, bz, v[0], v[1], v[2]);
941 }
942 if (!ok) {
943 std::cerr << m_className << "::GetVelocity:\n"
944 << " Cannot retrieve drift velocity at "
945 << PrintVec(x) << ".\n";
946 status = StatusCalculationAbandoned;
947 }
948 return v;
949}
950
951bool DriftLineRKF::GetDiffusion(const std::array<double, 3>& x,
952 const Particle particle,
953 double& dl, double& dt) const {
954 double ex = 0., ey = 0., ez = 0.;
955 double bx = 0., by = 0., bz = 0.;
956 Medium* medium = nullptr;
957 if (GetField(x, ex, ey, ez, bx, by, bz, medium) != 0) return false;
958
959 if (particle == Particle::Electron || particle == Particle::Positron) {
960 return medium->ElectronDiffusion(ex, ey, ez, bx, by, bz, dl, dt);
961 } else if (particle == Particle::Ion || particle == Particle::NegativeIon) {
962 return medium->IonDiffusion(ex, ey, ez, bx, by, bz, dl, dt);
963 } else if (particle == Particle::Hole) {
964 return medium->HoleDiffusion(ex, ey, ez, bx, by, bz, dl, dt);
965 }
966 return false;
967}
968
969double DriftLineRKF::GetVar(const std::array<double, 3>& x,
970 const Particle particle) const {
971 // Get the drift velocity.
972 int stat = 0;
973 const Vec v = GetVelocity(x, particle, stat);
974 if (stat != 0) return -1.;
975
976 const double speed = Mag(v);
977 if (speed < Small) {
978 std::cerr << m_className << "::GetVariance: Zero velocity.\n";
979 return -1.;
980 }
981 // Get the diffusion coefficients.
982 double dl = 0., dt = 0.;
983 if (!GetDiffusion(x, particle, dl, dt)) return -1.;
984
985 const double sigma = dl / speed;
986 return sigma * sigma;
987}
988
989double DriftLineRKF::GetAlpha(const std::array<double, 3>& x,
990 const Particle particle) const {
991 double alpha = 0.;
992 if (m_useTownsendMap && (particle == Particle::Electron ||
993 particle == Particle::Hole || particle == Particle::Positron)) {
994 const auto nComponents = m_sensor->GetNumberOfComponents();
995 for (size_t i = 0; i < nComponents; ++i) {
996 auto cmp = m_sensor->GetComponent(i);
997 if (!cmp->HasTownsendMap()) continue;
998 if (particle == Particle::Electron || particle == Particle::Positron) {
999 if (!cmp->ElectronTownsend(x[0], x[1], x[2], alpha)) continue;
1000 } else {
1001 if (!cmp->HoleTownsend(x[0], x[1], x[2], alpha)) continue;
1002 }
1003 return alpha;
1004 }
1005 }
1006 double ex = 0., ey = 0., ez = 0.;
1007 double bx = 0., by = 0., bz = 0.;
1008 Medium* medium = nullptr;
1009 if (GetField(x, ex, ey, ez, bx, by, bz, medium) != 0) return -1.;
1010
1011 if (particle == Particle::Electron || particle == Particle::Positron) {
1012 medium->ElectronTownsend(ex, ey, ez, bx, by, bz, alpha);
1013 } else if (particle == Particle::Hole) {
1014 medium->HoleTownsend(ex, ey, ez, bx, by, bz, alpha);
1015 }
1016 return alpha;
1017}
1018
1019double DriftLineRKF::GetEta(const std::array<double, 3>& x,
1020 const Particle particle) const {
1021
1022 double ex = 0., ey = 0., ez = 0.;
1023 double bx = 0., by = 0., bz = 0.;
1024 Medium* medium = nullptr;
1025 if (GetField(x, ex, ey, ez, bx, by, bz, medium) != 0) return -1.;
1026 double eta = 0.;
1027 if (particle == Particle::Electron) {
1028 medium->ElectronAttachment(ex, ey, ez, bx, by, bz, eta);
1029 } else if (particle == Particle::Hole) {
1030 medium->HoleAttachment(ex, ey, ez, bx, by, bz, eta);
1031 }
1032 return eta;
1033}
1034
1035bool DriftLineRKF::Terminate(const std::array<double, 3>& xx0,
1036 const std::array<double, 3>& xx1,
1037 const Particle particle,
1038 std::vector<double>& ts,
1039 std::vector<Vec>& xs) const {
1040
1041 // -----------------------------------------------------------------------
1042 // DLCFMP - Terminates drift line calculation by making a last step
1043 // to the boundary of the mesh or the drift medium.
1044 // VARIABLES : XX0: Last point in drift medium.
1045 // XX1: Estimated step, outside drift medium.
1046 // -----------------------------------------------------------------------
1047
1048 // Check the validity of the initial point.
1049 int status = 0;
1050 const Vec vv0 = GetVelocity(xx0, particle, status);
1051 if (status != 0) {
1052 std::cerr << m_className << "::Terminate:\n"
1053 << " Cannot retrieve drift velocity at initial point.\n";
1054 return false;
1055 }
1056 double speed = Mag(vv0);
1057 if (speed < Small) {
1058 std::cerr << m_className << "::Terminate:\n"
1059 << " Zero velocity at initial position.\n";
1060 return false;
1061 }
1062
1063 // Final point just inside the medium.
1064 Vec x0 = xx0;
1065 // Final point just outside the medium.
1066 Vec x1 = xx1;
1067 // Perform some bisections.
1068 constexpr unsigned int nBisections = 20;
1069 for (unsigned int i = 0; i < nBisections; ++i) {
1070 // Quit bisection when interval becomes too small.
1071 bool small = true;
1072 for (unsigned int j = 0; j < 3; ++j) {
1073 if (fabs(x1[j] - x0[j]) > 1.e-6 * (fabs(x0[j]) + fabs(x1[j]))) {
1074 small = false;
1075 break;
1076 }
1077 }
1078 if (small) {
1079 if (m_debug) {
1080 std::cout << m_className << "::Terminate:\n"
1081 << " Bisection ended at cycle " << i << ".\n";
1082 }
1083 break;
1084 }
1085 // Calculate the mid point.
1086 const Vec xm = MidPoint(x0, x1);
1087 // Halve the step.
1088 if (m_sensor->IsInside(xm[0], xm[1], xm[2]) &&
1089 m_sensor->IsInArea(xm[0], xm[1], xm[2])) {
1090 x0 = xm;
1091 } else {
1092 x1 = xm;
1093 }
1094 }
1095
1096 // Compute drift velocity at the end of the step.
1097 Vec v0 = GetVelocity(x0, particle, status);
1098 if (status != 0) {
1099 std::cerr << m_className << "::Terminate:\n"
1100 << " Warning: Unable to compute mean velocity at last step.\n";
1101 } else {
1102 speed = 0.5 * (speed + Mag(v0));
1103 }
1104 // Calculate the time step.
1105 const double dt = Dist(xx0, x0) / speed;
1106 // Add the last point, just inside the drift area.
1107 ts.push_back(ts.back() + dt);
1108 xs.push_back(x0);
1109 return true;
1110}
1111
1112bool DriftLineRKF::DriftToWire(const double xw, const double yw,
1113 const double rw, const Particle particle,
1114 std::vector<double>& ts,
1115 std::vector<Vec>& xs, int& stat) const {
1116
1117 // -----------------------------------------------------------------------
1118 // DLCWIR - Terminates drift line calculation by making some last steps
1119 // towards the surface of the wire on which it is supposed to
1120 // end. The precision is controlled in order to obtain a
1121 // good estimate of the total remaining drift-time.
1122 // -----------------------------------------------------------------------
1123
1124 // Get the starting point.
1125 Vec x0 = xs.back();
1126 double t0 = ts.back() - ts.front();
1127 if (m_debug) {
1128 std::cout << m_className << "::DriftToWire:\n Drifting from ("
1129 << x0[0] << ", " << x0[1] << ") to wire at ("
1130 << xw << ", " << yw << ") with radius " << rw << " cm.\n";
1131 }
1132
1133 // Get the initial drift velocity.
1134 int status = 0;
1135 Vec v0 = GetVelocity(x0, particle, status);
1136 if (status != 0) {
1137 std::cerr << m_className << "::DriftToWire:\n"
1138 << " Cannot retrieve initial drift velocity.\n";
1139 return false;
1140 }
1141
1142 // Estimate the time needed to reach the wire
1143 // assuming a straight-line trajectory and constant velocity.
1144 double dt = (Mag(xw - x0[0], yw - x0[1]) - rw) / Mag(v0[0], v0[1]);
1145 if (m_debug) {
1146 std::cout << " Estimated time needed to reach the wire: "
1147 << dt << " ns.\n";
1148 }
1149
1150 constexpr unsigned int nMaxSplit = 10;
1151 unsigned int nSplit = 0;
1152 // Move towards the wire.
1153 bool onwire = false;
1154 const double r2 = rw * rw;
1155 while (!onwire && dt > 1.e-6 * t0) {
1156 // Calculate the estimated end point.
1157 Vec x1 = x0;
1158 for (unsigned int j = 0; j < 3; ++j) x1[j] += dt * v0[j];
1159 // Make sure we are not moving away from the wire.
1160 const double xinp0 = (x1[0] - x0[0]) * (xw - x0[0]) +
1161 (x1[1] - x0[1]) * (yw - x0[1]);
1162 if (xinp0 < 0.) {
1163 if (m_debug) {
1164 std::cerr << " Particle moves away from the wire. Quit.\n";
1165 }
1166 return false;
1167 }
1168 // Check if the end point is inside the wire or the wire was crossed.
1169 const double xinp1 = (x0[0] - x1[0]) * (xw - x1[0]) +
1170 (x0[1] - x1[1]) * (yw - x1[1]);
1171 if (xinp1 < 0.) {
1172 if (Mag2(xw - x1[0], yw - x1[1]) <= r2) {
1173 onwire = true;
1174 if (m_debug) std::cout << " Inside.\n";
1175 }
1176 } else {
1177 if (m_debug) std::cout << " Wire crossed.\n";
1178 onwire = true;
1179 }
1180 if (onwire) {
1181 const double dw0 = Mag(xw - x0[0], yw - x0[1]);
1182 x1[0] = xw - (rw + BoundaryDistance) * (xw - x0[0]) / dw0;
1183 x1[1] = yw - (rw + BoundaryDistance) * (yw - x0[1]) / dw0;
1184 dt = Mag(x1[0] - x0[0], x1[1] - x0[1]) / Mag(v0[0], v0[1]);
1185 x1[2] = x0[2] + dt * v0[2];
1186 }
1187 // Calculate the drift velocity at the end point.
1188 Vec v1 = GetVelocity(x1, particle, status);
1189 if (status != 0) {
1190 std::cerr << m_className << "::DriftToWire:\n"
1191 << " Cannot retrieve drift velocity at end point. Quit.\n";
1192 return false;
1193 }
1194 // Get a point halfway between for an accuracy check.
1195 const Vec xm = MidPoint(x0, x1);
1196 // Calculate the drift velocity at the mid point.
1197 Vec vm = GetVelocity(xm, particle, status);
1198 if (status != 0) {
1199 std::cerr << m_className << "::DriftToWire:\n"
1200 << " Cannot retrieve drift velocity at mid point. Quit.\n";
1201 return false;
1202 }
1203 // Make sure the velocities are non-zero.
1204 const double speed0 = Mag(v0[0], v0[1]);
1205 const double speed1 = Mag(v1[0], v1[1]);
1206 const double speedm = Mag(vm[0], vm[1]);
1207 if (speed0 < Small || speed1 < Small || speedm < Small) {
1208 std::cerr << m_className << "::DriftToWire: Zero velocity. Stop.\n";
1209 return false;
1210 }
1211 const double p0 = 1. / speed0;
1212 const double p1 = 1. / speed1;
1213 const double pm = 1. / speedm;
1214 // Compare first and second order estimates.
1215 const double d = Mag(x0[0] - x1[0], x0[1] - x1[1]);
1216 const double tol = 1.e-4 * (1. + fabs(t0));
1217 if (d * fabs(p0 - 2 * pm + p1) / 3. > tol && nSplit < nMaxSplit) {
1218 // Accuracy was not good enough so halve the step time.
1219 if (m_debug) std::cout << " Reducing step size.\n";
1220 dt *= 0.5;
1221 onwire = false;
1222 ++nSplit;
1223 continue;
1224 }
1225 const double t1 = t0 + d * (p0 + 4 * pm + p1) / 6.;
1226 // Add a new point to the drift line.
1227 ts.push_back(ts[0] + t1);
1228 xs.push_back(x1);
1229 // Proceed to the next step.
1230 x0 = x1;
1231 t0 = t1;
1232 v0 = v1;
1233 }
1234 // Get the wire index (status code inside the wire).
1235 double ex = 0., ey = 0., ez = 0.;
1236 Medium* medium = nullptr;
1237 m_sensor->ElectricField(xw, yw, 0., ex, ey, ez, medium, stat);
1238 return true;
1239}
1240
1242
1243 std::cout << m_className << "::PrintDriftLine:\n";
1244 if (m_x.empty()) {
1245 std::cout << " No drift line present.\n";
1246 return;
1247 }
1248 if (m_particle == Particle::Electron) {
1249 std::cout << " Particle: electron\n";
1250 } else if (m_particle == Particle::Ion) {
1251 std::cout << " Particle: ion\n";
1252 } else if (m_particle == Particle::Hole) {
1253 std::cout << " Particle: hole\n";
1254 } else if (m_particle == Particle::Positron) {
1255 std::cout << " Particle: positive electron\n";
1256 } else if (m_particle == Particle::NegativeIon) {
1257 std::cout << " Particle: negative ion\n";
1258 } else {
1259 std::cout << " Particle: unknown\n";
1260 }
1261 std::cout << " Status: " << m_status << "\n"
1262 << " Step time [ns] "
1263 << "x [cm] y [cm] z [cm]\n";
1264 const unsigned int nPoints = m_x.size();
1265 for (unsigned int i = 0; i < nPoints; ++i) {
1266 std::printf("%6u %15.7f %15.7f %15.7f %15.7f\n",
1267 i, m_t[i], m_x[i][0], m_x[i][1], m_x[i][2]);
1268 }
1269
1270}
1271
1272void DriftLineRKF::GetEndPoint(double& x, double& y, double& z, double& t,
1273 int& stat) const {
1274 if (m_x.empty()) {
1275 x = y = z = t = 0.;
1276 stat = m_status;
1277 return;
1278 }
1279 const auto& p = m_x.back();
1280 x = p[0];
1281 y = p[1];
1282 z = p[2];
1283 t = m_t.back();
1284 stat = m_status;
1285}
1286
1287void DriftLineRKF::GetDriftLinePoint(const size_t i, double& x, double& y,
1288 double& z, double& t) const {
1289 if (i >= m_x.size()) {
1290 std::cerr << m_className << "::GetDriftLinePoint: Index out of range.\n";
1291 return;
1292 }
1293
1294 const auto& p = m_x[i];
1295 x = p[0];
1296 y = p[1];
1297 z = p[2];
1298 t = m_t[i];
1299}
1300
1301double DriftLineRKF::IntegrateDiffusion(const std::array<double, 3>& xi,
1302 const std::array<double, 3>& xe,
1303 const Particle particle,
1304 const double tol) const {
1305 // Make sure the starting and end points are valid.
1306 Vec x0 = xi;
1307 double var0 = GetVar(x0, particle);
1308 Vec x1 = xe;
1309 double var1 = GetVar(x1, particle);
1310 if (var0 < 0. || var1 < 0.) {
1311 std::cerr << m_className << "::IntegrateDiffusion:\n"
1312 << " Cannot retrieve variance at initial point.\n";
1313 return 0.;
1314 }
1315
1316 double integral = 0.;
1317 while (Dist(xe, x0) > 1.e-6) {
1318 const double d = Dist(x1, x0);
1319 if (d < 1.e-6) {
1320 // Step length has become very small.
1321 if (m_debug) {
1322 std::cout << m_className << "::IntegrateDiffusion: Small step.\n";
1323 }
1324 integral += var0 * d;
1325 // Proceed with the next step.
1326 x0 = x1;
1327 x1 = xe;
1328 continue;
1329 }
1330 // Determine the variance at the end point of the step.
1331 var1 = GetVar(x1, particle);
1332 // Determine the variance at the mid point of the step.
1333 const Vec xm = MidPoint(x0, x1);
1334 const double varm = GetVar(xm, particle);
1335 if (var1 < 0. || varm < 0.) {
1336 std::cerr << m_className << "::IntegrateDiffusion:\n"
1337 << " Cannot retrieve variance at mid or end point.\n";
1338 break;
1339 }
1340 // Compare first and second order estimates
1341 // (integrals calculated using trapezoidal and Simpson's rule).
1342 if (fabs(var0 - 2 * varm + var1) * sqrt(d * 2 / (var0 + var1)) / 6. < tol) {
1343 // Accuracy is good enough.
1344 integral += d * (var0 + 4 * varm + var1) / 6.;
1345 // Proceed to the next step.
1346 x0 = x1;
1347 x1 = xe;
1348 var0 = var1;
1349 } else {
1350 // Accuracy is not good enough, so halve the step.
1351 x1 = xm;
1352 var1 = varm;
1353 }
1354 }
1355 return integral;
1356}
1357
1358double DriftLineRKF::IntegrateAlpha(const std::array<double, 3>& xi,
1359 const std::array<double, 3>& xe,
1360 const Particle particle,
1361 const double tol) const {
1362
1363 // Determine the Townsend coefficient at the initial point.
1364 Vec x0 = xi;
1365 double alpha0 = GetAlpha(x0, particle);
1366 // Determine the Townsend coefficient at the end point.
1367 Vec x1 = xe;
1368 double alpha1 = GetAlpha(x1, particle);
1369 if (alpha0 < 0. || alpha1 < 0.) {
1370 std::cerr << m_className << "::IntegrateAlpha:\n"
1371 << " Cannot retrieve alpha at start point or end point.\n";
1372 return 0.;
1373 }
1374 double integral = 0.;
1375 while (Dist(xe, x0) > 1.e-6) {
1376 const double d = Dist(x1, x0);
1377 if (d < 1.e-6) {
1378 // Step length has become very small.
1379 if (m_debug) {
1380 std::cout << m_className << "::IntegrateAlpha: Small step.\n";
1381 }
1382 integral += alpha0 * d;
1383 // Proceed with the next step.
1384 x0 = x1;
1385 x1 = xe;
1386 continue;
1387 }
1388 // Calculate the Townsend coefficient at the end point of the step.
1389 alpha1 = GetAlpha(x1, particle);
1390 // Calculate the Townsend coefficient at the mid point of the step.
1391 const Vec xm = MidPoint(x0, x1);
1392 const double alpham = GetAlpha(xm, particle);
1393 if (alpha1 < 0. || alpham < 0.) {
1394 std::cerr << m_className << "::IntegrateAlpha:\n"
1395 << " Cannot retrieve alpha at mid point or end point.\n";
1396 break;
1397 }
1398 // Compare first and second order estimates.
1399 if (d * fabs(alpha0 - 2 * alpham + alpha1) / 3. < tol) {
1400 // Accuracy is good enough.
1401 integral += d * (alpha0 + 4 * alpham + alpha1) / 6.;
1402 // Proceed to the next step.
1403 x0 = x1;
1404 x1 = xe;
1405 alpha0 = alpha1;
1406 } else {
1407 // Accuracy is not good enough, so halve the step.
1408 x1 = xm;
1409 alpha1 = alpham;
1410 }
1411 }
1412 return integral;
1413}
1414
1415double DriftLineRKF::IntegrateEta(const std::array<double, 3>& xi,
1416 const std::array<double, 3>& xe,
1417 const Particle particle,
1418 const double tol) const {
1419 // Determine the attachment coefficient at the initial point.
1420 Vec x0 = xi;
1421 double eta0 = GetEta(x0, particle);
1422 // Determine the attachment coefficient at the end point.
1423 Vec x1 = xe;
1424 double eta1 = GetEta(x1, particle);
1425 if (eta0 < 0. || eta1 < 0.) {
1426 std::cerr << m_className << "::IntegrateEta:\n"
1427 << " Cannot retrieve eta at start point or end point.\n";
1428 return 0.;
1429 }
1430 double integral = 0.;
1431 while (Dist(xe, x0) > 1.e-6) {
1432 const double d = Dist(x1, x0);
1433 if (d < 1.e-6) {
1434 // Step length has become very small.
1435 if (m_debug) {
1436 std::cout << m_className << "::IntegrateEta: Small step.\n";
1437 }
1438 integral += eta0 * d;
1439 // Proceed with the next step.
1440 x0 = x1;
1441 x1 = xe;
1442 continue;
1443 }
1444 // Calculate the attachment coefficient at the end point of the step.
1445 eta1 = GetEta(x1, particle);
1446 // Calculate the attachment coefficient at the mid point of the step.
1447 const Vec xm = MidPoint(x0, x1);
1448 const double etam = GetEta(xm, particle);
1449 if (eta1 < 0. || etam < 0.) {
1450 std::cerr << m_className << "::IntegrateEta:\n"
1451 << " Cannot retrieve eta at mid point or end point.\n";
1452 break;
1453 }
1454 // Compare first and second order estimates.
1455 if (d * fabs(eta0 - 2 * etam + eta1) / 3. < tol) {
1456 // Accuracy is good enough.
1457 integral += d * (eta0 + 4 * etam + eta1) / 6.;
1458 // Proceed to the next step.
1459 x0 = x1;
1460 x1 = xe;
1461 eta0 = eta1;
1462 } else {
1463 // Accuracy is not good enough, so halve the step.
1464 x1 = xm;
1465 eta1 = etam;
1466 }
1467 }
1468 return integral;
1469}
1470
1471void DriftLineRKF::ComputeSignal(const Particle particle, const double scale,
1472 const std::vector<double>& ts,
1473 const std::vector<Vec>& xs,
1474 const std::vector<double>& ne) const {
1475
1476 const auto nPoints = ts.size();
1477 if (nPoints < 2) return;
1478 const double q0 = Charge(particle) * scale;
1479
1480 if (m_useWeightingPotential) {
1481 const bool aval = ne.size() == nPoints;
1482 for (size_t i = 0; i < nPoints - 1; ++i) {
1483 const auto& x0 = xs[i];
1484 const auto& x1 = xs[i + 1];
1485 const double q = aval ? q0 * 0.5 * (ne[i] + ne[i + 1]) : q0;
1486 m_sensor->AddSignal(q, ts[i], ts[i + 1],
1487 x0[0], x0[1], x0[2], x1[0], x1[1], x1[2],
1488 false, true);
1489 }
1490 return;
1491 }
1492 // Get the drift velocity at each point.
1493 std::vector<std::array<double, 3> > vs;
1494 for (const auto& x : xs) {
1495 int stat = 0;
1496 Vec v = GetVelocity(x, particle, stat);
1497 if (stat != 0) {
1498 std::cerr << m_className << "::ComputeSignal:\n"
1499 << " Cannot retrieve velocity at " << PrintVec(x) << "\n";
1500 }
1501 vs.push_back(std::move(v));
1502 }
1503 m_sensor->AddSignal(q0, ts, xs, vs, ne, m_navg);
1504}
1505
1506bool DriftLineRKF::FieldLine(const double xi, const double yi, const double zi,
1507 std::vector<std::array<float, 3> >& xl,
1508 const bool electron) const {
1509
1510 xl.clear();
1511 // Is the sensor set?
1512 if (!m_sensor) {
1513 std::cerr << m_className << "::FieldLine: Sensor is not defined.\n";
1514 return false;
1515 }
1516
1517 // Get the sensor's bounding box.
1518 double xmin = 0., xmax = 0.;
1519 double ymin = 0., ymax = 0.;
1520 double zmin = 0., zmax = 0.;
1521 bool bbox = m_sensor->GetArea(xmin, ymin, zmin, xmax, ymax, zmax);
1522
1523 // Set the step size limit if requested.
1524 double maxStep = -1.;
1525 if (m_useStepSizeLimit) {
1526 if (m_maxStepSize > 0.) {
1527 maxStep = m_maxStepSize;
1528 } else {
1529 maxStep = 0.5 * m_sensor->StepSizeHint();
1530 }
1531 }
1532
1533 // Make sure the initial position is at a valid location.
1534 double ex = 0., ey = 0., ez = 0.;
1535 Medium* medium = nullptr;
1536 int stat = 0;
1537 m_sensor->ElectricField(xi, yi, zi, ex, ey, ez, medium, stat);
1538 if (!medium || stat != 0) {
1539 std::cerr << m_className << "::FieldLine: "
1540 << "No valid field at initial position.\n";
1541 return false;
1542 }
1543 Vec x0 = {xi, yi, zi};
1544 Vec f0 = {ex, ey, ez};
1545 if (electron) for (auto& f : f0) f *= -1;
1546
1547 // Set the numerical constants for the RKF integration.
1548 constexpr double c10 = 214. / 891.;
1549 constexpr double c11 = 1. / 33.;
1550 constexpr double c12 = 650. / 891.;
1551 constexpr double c20 = 533. / 2106.;
1552 constexpr double c22 = 800. / 1053.;
1553 constexpr double c23 = -1. / 78.;
1554
1555 constexpr double b10 = 1. / 4.;
1556 constexpr double b20 = -189. / 800.;
1557 constexpr double b21 = 729. / 800.;
1558 constexpr double b30 = 214. / 891.;
1559 constexpr double b31 = 1. / 33.;
1560 constexpr double b32 = 650. / 891.;
1561
1562 const double fmag0 = Mag(f0);
1563 if (fmag0 < Small) {
1564 std::cerr << m_className << "::FieldLine:\n"
1565 << " Zero field at initial position.\n";
1566 return false;
1567 }
1568
1569 // Initialise time step and previous time step.
1570 double h = m_accuracy / fmag0;
1571 double hprev = h;
1572 if (m_debug) {
1573 std::cout << m_className << "::FieldLine:\n"
1574 << " Initial step size: " << h << ".\n";
1575 }
1576 // Set the initial point.
1577 xl.push_back({float(x0[0]), float(x0[1]), float(x0[2])});
1578
1579 int initCycle = 3;
1580 bool ok = true;
1581 while (ok) {
1582 // Get the field at the first probe point.
1583 Vec x1 = x0;
1584 for (unsigned int i = 0; i < 3; ++i) {
1585 x1[i] += h * b10 * f0[i];
1586 }
1587 m_sensor->ElectricField(x1[0], x1[1], x1[2], ex, ey, ez, medium, stat);
1588 if (stat != 0) {
1589 if (m_debug) std::cout << " Point 1 outside.\n";
1590 Terminate(x0, x1, xl);
1591 return true;
1592 }
1593 Vec f1 = {ex, ey, ez};
1594 if (electron) for (auto& f : f1) f *= -1;
1595 // Get the field at the second probe point.
1596 Vec x2 = x0;
1597 for (unsigned int i = 0; i < 3; ++i) {
1598 x2[i] += h * (b20 * f0[i] + b21 * f1[i]);
1599 }
1600 m_sensor->ElectricField(x2[0], x2[1], x2[2], ex, ey, ez, medium, stat);
1601 if (stat != 0) {
1602 if (m_debug) std::cout << " Point 2 outside.\n";
1603 Terminate(x0, x2, xl);
1604 return true;
1605 }
1606 Vec f2 = {ex, ey, ez};
1607 if (electron) for (auto& f : f2) f *= -1;
1608 // Get the field at the third probe point.
1609 Vec x3 = x0;
1610 for (unsigned int i = 0; i < 3; ++i) {
1611 x3[i] += h * (b30 * f0[i] + b31 * f1[i] + b32 * f2[i]);
1612 }
1613 m_sensor->ElectricField(x3[0], x3[1], x3[2], ex, ey, ez, medium, stat);
1614 if (stat != 0) {
1615 if (m_debug) std::cout << " Point 3 outside.\n";
1616 Terminate(x0, x3, xl);
1617 return true;
1618 }
1619 Vec f3 = {ex, ey, ez};
1620 if (electron) for (auto& f : f3) f *= -1;
1621 // Check if we crossed a wire.
1622 double xw = 0., yw = 0., zw = 0., rw = 0.;
1623 if (m_sensor->CrossedWire(x0[0], x0[1], x0[2],
1624 x1[0], x1[1], x1[2], xw, yw, zw, false, rw) ||
1625 m_sensor->CrossedWire(x0[0], x0[1], x0[2],
1626 x2[0], x2[1], x2[2], xw, yw, zw, false, rw) ||
1627 m_sensor->CrossedWire(x0[0], x0[1], x0[2],
1628 x3[0], x3[1], x3[2], xw, yw, zw, false, rw)) {
1629 // TODO!
1630 xl.push_back({float(xw), float(yw), float(zw)});
1631 return true;
1632 // Drift to wire.
1633 if (h > Small) {
1634 h *= 0.5;
1635 continue;
1636 } else {
1637 std::cerr << m_className << "::FieldLine: Step size too small. Stop.\n";
1638 return false;
1639 }
1640 }
1641 // Calculate the correction terms.
1642 Vec phi1 = {0., 0., 0.};
1643 Vec phi2 = {0., 0., 0.};
1644 for (unsigned int i = 0; i < 3; ++i) {
1645 phi1[i] = c10 * f0[i] + c11 * f1[i] + c12 * f2[i];
1646 phi2[i] = c20 * f0[i] + c22 * f2[i] + c23 * f3[i];
1647 }
1648 // Check if the step length is valid.
1649 const double phi1mag = Mag(phi1);
1650 if (phi1mag < Small) {
1651 std::cerr << m_className << "::FieldLine: Step has zero length. Stop.\n";
1652 break;
1653 } else if (maxStep > 0. && h * phi1mag > maxStep) {
1654 if (m_debug) {
1655 std::cout << " Step is considered too long. H is reduced.\n";
1656 }
1657 h = 0.5 * maxStep / phi1mag;
1658 continue;
1659 } else if (bbox) {
1660 // Don't allow h to become too large.
1661 if (h * fabs(phi1[0]) > 0.1 * fabs(xmax - xmin) ||
1662 h * fabs(phi1[1]) > 0.1 * fabs(ymax - ymin)) {
1663 h *= 0.5;
1664 if (m_debug) {
1665 std::cout << " Step is considered too long. H is halved.\n";
1666 }
1667 continue;
1668 }
1669 }
1670 if (m_debug) std::cout << " Step size ok.\n";
1671 // Update the position.
1672 for (size_t i = 0; i < 3; ++i) x0[i] += h * phi1[i];
1673 // Check the new position.
1674 m_sensor->ElectricField(x0[0], x0[1], x0[2], ex, ey, ez, medium, stat);
1675 if (stat != 0) {
1676 // The new position is not inside a valid drift medium.
1677 // Terminate the drift line.
1678 if (m_debug) std::cout << " Point outside. Terminate.\n";
1679 std::array<double, 3> xp = {xl.back()[0], xl.back()[1], xl.back()[2]};
1680 Terminate(xp, x0, xl);
1681 return true;
1682 }
1683 // Add the new point to the drift line.
1684 xl.push_back({float(x0[0]), float(x0[1]), float(x0[2])});
1685 // Adjust the step size according to the accuracy of the two estimates.
1686 hprev = h;
1687 const double dphi = fabs(phi1[0] - phi2[0]) + fabs(phi1[1] - phi2[1]) +
1688 fabs(phi1[2] - phi2[2]);
1689 if (dphi > 0) {
1690 h = sqrt(h * m_accuracy / dphi);
1691 if (m_debug) std::cout << " Adapting H to " << h << ".\n";
1692 } else {
1693 h *= 2;
1694 if (m_debug) std::cout << " H increased by factor two.\n";
1695 }
1696 // Make sure that H is different from zero; this should always be ok.
1697 if (h < Small) {
1698 std::cerr << m_className << "::FieldLine: Step size is zero. Stop.\n";
1699 return false;
1700 }
1701 // Check the initial step size.
1702 if (initCycle > 0 && h < 0.2 * hprev) {
1703 if (m_debug) std::cout << " Reinitialise step size.\n";
1704 --initCycle;
1705 x0 = {xi, yi, zi};
1706 xl.clear();
1707 xl.push_back({float(xi), float(yi), float(zi)});
1708 continue;
1709 }
1710 initCycle = 0;
1711 // Don't allow H to grow too quickly
1712 if (h > 10 * hprev) {
1713 h = 10 * hprev;
1714 if (m_debug) {
1715 std::cout << " H restricted to 10 times the previous value.\n";
1716 }
1717 }
1718 // Stop in case H tends to become too small.
1719 if (h * (fabs(phi1[0]) + fabs(phi1[1]) + fabs(phi1[2])) < m_accuracy) {
1720 std::cerr << m_className << "::FieldLine: Step size has become smaller "
1721 << "than int. accuracy. Stop.\n";
1722 return false;
1723 }
1724 // Update the field.
1725 f0 = f3;
1726 }
1727 return true;
1728}
1729
1730void DriftLineRKF::Terminate(const std::array<double, 3>& xx0,
1731 const std::array<double, 3>& xx1,
1732 std::vector<std::array<float, 3> >& xs) const {
1733
1734 // Final point just inside the medium.
1735 Vec x0 = xx0;
1736 // Final point just outside the medium.
1737 Vec x1 = xx1;
1738 // Perform some bisections.
1739 constexpr unsigned int nBisections = 20;
1740 for (unsigned int i = 0; i < nBisections; ++i) {
1741 // Quit bisection when interval becomes too small.
1742 bool small = true;
1743 for (unsigned int j = 0; j < 3; ++j) {
1744 if (fabs(x1[j] - x0[j]) > 1.e-6 * (fabs(x0[j]) + fabs(x1[j]))) {
1745 small = false;
1746 break;
1747 }
1748 }
1749 if (small) {
1750 if (m_debug) {
1751 std::cout << m_className << "::Terminate: Bisection ends at cycle "
1752 << i << ".\n";
1753 }
1754 break;
1755 }
1756 // Calculate the mid point.
1757 const Vec xm = MidPoint(x0, x1);
1758 // Halve the step.
1759 if (m_sensor->IsInside(xm[0], xm[1], xm[2]) &&
1760 m_sensor->IsInArea(xm[0], xm[1], xm[2])) {
1761 x0 = xm;
1762 } else {
1763 x1 = xm;
1764 }
1765 }
1766
1767 xs.push_back({float(x0[0]), float(x0[1]), float(x0[2])});
1768}
1769}
std::array< double, 3 > Vec
Definition TGeoTet.cc:14
bool FieldLine(const double xi, const double yi, const double zi, std::vector< std::array< float, 3 > > &xl, const bool electron=true) const
void SetGainFluctuationsPolya(const double theta, const double mean=-1., const bool quiet=false)
void DisablePlotting()
Switch off drift line plotting.
bool DriftNegativeIon(const double x0, const double y0, const double z0, const double t0)
double GetPathLength() const
Get the cumulative path length.
bool DriftHole(const double x0, const double y0, const double z0, const double t0)
Simulate the drift line of a hole with a given starting point.
double GetArrivalTimeSpread(const double eps=1.e-4) const
bool DriftIon(const double x0, const double y0, const double z0, const double t0)
Simulate the drift line of an ion with a given starting point.
void SetSensor(Sensor *s)
Set the sensor.
void GetDriftLinePoint(const size_t i, double &x, double &y, double &z, double &t) const
Get the coordinates and time of a point along the most recent drift line.
void SetIntegrationAccuracy(const double eps)
Set the accuracy of the Runge Kutta Fehlberg drift line integration.
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.
void SetGainFluctuationsFixed(const double gain=-1.)
Do not randomize the avalanche size.
bool DriftPositron(const double x0, const double y0, const double z0, const double t0)
void PrintDriftLine() const
Print the trajectory of the most recent drift line.
void EnablePlotting(ViewDrift *view)
Switch on drift line plotting.
double GetGain(const double eps=1.e-4) const
Compute the multiplication factor for the current drift line.
DriftLineRKF()
Default constructor.
double GetLoss(const double eps=1.e-4) const
Compute the attachment loss factor for the current drift line.
void GetEndPoint(double &x, double &y, double &z, double &t, int &st) const
Get the end point and status flag of the most recent drift line.
Abstract base class for media.
Definition Medium.hh:16
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:121
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
bool GetArea(double &xmin, double &ymin, double &zmin, double &xmax, double &ymax, double &zmax)
Return the current user area.
Definition Sensor.cc:234
Visualize drift lines and tracks.
Definition ViewDrift.hh:19
constexpr std::array< double, 6 > GaussLegendreWeights6()
Definition Numerics.hh:49
constexpr std::array< double, 6 > GaussLegendreNodes6()
Definition Numerics.hh:44
std::array< double, 3 > Vec
double RndmPolya(const double theta)
Draw a Polya distributed random number.
Definition Random.hh:91
DoubleAc exp(const DoubleAc &f)
Definition DoubleAc.cpp:377
DoubleAc fabs(const DoubleAc &f)
Definition DoubleAc.h:615
DoubleAc sqrt(const DoubleAc &f)
Definition DoubleAc.cpp:314