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