14std::string PrintVec(
const std::array<double, 3>& x) {
16 return "(" + std::to_string(x[0]) +
", " + std::to_string(x[1]) +
", " +
17 std::to_string(x[2]) +
")";
20double Mag(
const std::array<double, 3>& x) {
22 return sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
25double Mag(
const double x,
const double y,
const double z) {
27 return sqrt(x * x + y * y + z * z);
30double Mag(
const double x,
const double y) {
32 return sqrt(x * x + y * y);
35double Mag2(
const double x,
const double y) {
40double Dist(
const std::array<double, 3>& x0,
41 const std::array<double, 3>& x1) {
43 return Mag(x1[0] - x0[0], x1[1] - x0[1], x1[2] - x0[2]);
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]);
56using Vec = std::array<double, 3>;
65 std::cerr << m_className <<
"::SetSensor: Null pointer.\n";
75 std::cerr << m_className <<
"::SetIntegrationAccuracy:\n"
76 <<
" Accuracy must be greater than zero.\n";
82 m_useStepSizeLimit =
true;
88 m_useStepSizeLimit =
true;
90 std::cerr << m_className <<
"::SetMaximumStepSize:\n"
91 <<
" Step size must be greater than zero.\n";
97 std::cerr << m_className <<
"::EnablePlotting: Null pointer.\n";
108 std::cout << m_className <<
"::SetGainFluctuationsFixed: "
109 <<
"Avalanche size set to " << gain <<
".\n";
111 std::cout << m_className <<
"::SetGainFluctuationsFixed:\n "
112 <<
"Avalanche size will be given by "
113 <<
"the integrated Townsend coefficient.\n";
116 m_gainFluctuations = GainFluctuations::None;
124 std::cerr << m_className <<
"::SetGainFluctuationsPolya: "
125 <<
"Shape parameter must be >= 0.\n";
130 std::cout << m_className <<
"::SetGainFluctuationsPolya: "
131 <<
"Mean avalanche size set to " << mean <<
".\n";
133 std::cout << m_className <<
"::SetGainFluctuationsPolya:\n "
134 <<
"Mean avalanche size will be given by "
135 <<
"the integrated Townsend coefficient.\n";
140 m_gainFluctuations = GainFluctuations::Polya;
144 const double z0,
const double t0) {
145 std::vector<std::array<double, 3> > x;
146 std::vector<double> t;
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.);
161 if (m_doIonTail) AddIonTail(t, x, ni, scale);
162 if (m_doNegativeIonTail) AddNegativeIonTail(t, x, nn, scale);
164 m_nE = scale * ne.back();
165 m_nI = scale * std::accumulate(ni.begin(), ni.end(), 0.);
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 {
182 const size_t nPoints = te.size();
183 if (nPoints < 2 || ni.size() != nPoints)
return false;
185 for (
size_t i = 1; i < nPoints; ++i) {
187 if (scale * ni[i] < 1.)
continue;
191 std::vector<double> ti;
195 std::cerr << m_className <<
"::AddIonTail:\n"
196 <<
" Unable to obtain an ion tail; tail not added.\n";
200 std::cout << m_className <<
"::AddIonTail: Origin = " << PrintVec(xe[i])
201 <<
", n = " << ti.size() <<
", status = " << stat <<
"\n";
204 ComputeSignal(
Particle::Ion, scale * m_scaleI * ni[i], ti, xi, {});
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;
215 for (
size_t i = 1; i < nPoints; ++i) {
217 if (scale * nn[i] < Small)
continue;
219 std::vector<double> tn;
223 std::cerr << m_className <<
"::AddNegativeIonTail:\n"
224 <<
" Unable to obtain a negative ion tail.\n";
234 const double z0,
const double t0) {
235 std::vector<std::array<double, 3> > x;
236 std::vector<double> t;
240 if (ok && m_doSignal) {
252 std::vector<std::array<double, 3> > x;
253 std::vector<double> t;
255 const bool ok = DriftLine({x0, y0, z0}, t0,
Particle::Hole, t, x, status);
256 if (ok && m_doSignal) {
268 std::vector<std::array<double, 3> > x;
269 std::vector<double> t;
271 const bool ok = DriftLine({x0, y0, z0}, t0,
Particle::Ion, t, x, status);
272 if (ok && m_doSignal) {
283 const double z0,
const double t0) {
284 std::vector<std::array<double, 3> > x;
285 std::vector<double> t;
289 if (ok && m_doSignal) {
299bool DriftLineRKF::DriftLine(
const Vec& xi,
const double ti,
301 std::vector<double>& ts,
302 std::vector<Vec>& xs,
int& flag)
const {
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.;
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.;
342 std::cerr << m_className <<
"::DriftLine: Sensor is not defined.\n";
343 flag = StatusCalculationAbandoned;
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);
354 double maxStep = -1.;
355 if (m_useStepSizeLimit) {
356 if (m_maxStepSize > 0.) {
357 maxStep = m_maxStepSize;
359 maxStep = 0.5 * m_sensor->StepSizeHint();
364 const double charge = Charge(particle);
368 Vec v0 = GetVelocity(x0, particle, flag);
370 std::cerr << m_className <<
"::DriftLine:\n"
371 <<
" Cannot retrieve drift velocity at initial position "
372 << PrintVec(x0) <<
".\n";
376 const double speed0 = Mag(v0);
377 if (speed0 < Small) {
378 std::cerr << m_className <<
"::DriftLine: "
379 <<
"Zero velocity at initial position.\n";
384 double h = m_accuracy / speed0;
393 std::cout << m_className <<
"::DriftLine:\n"
394 <<
" Initial step size: " << h <<
" ns.\n";
401 for (
unsigned int i = 0; i < 3; ++i) {
402 x1[i] += h * b10 * v0[i];
405 const Vec v1 = GetVelocity(x1, particle, stat);
406 if (stat == StatusCalculationAbandoned) {
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;
420 for (
unsigned int i = 0; i < 3; ++i) {
421 x2[i] += h * (b20 * v0[i] + b21 * v1[i]);
423 const Vec v2 = GetVelocity(x2, particle, stat);
424 if (stat == StatusCalculationAbandoned) {
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;
438 for (
unsigned int i = 0; i < 3; ++i) {
439 x3[i] += h * (b30 * v0[i] + b31 * v1[i] + b32 * v2[i]);
441 const Vec v3 = GetVelocity(x3, particle, stat);
442 if (stat == StatusCalculationAbandoned) {
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;
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)) {
465 }
else if (h > Small) {
469 std::cerr << m_className <<
"::DriftLine: Step size too small. Stop.\n";
470 flag = StatusCalculationAbandoned;
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;
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;
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;
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])) {
504 ts.push_back(t0 + Dist(x0, xp) / Mag(v0));
506 flag = StatusHitPlane;
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];
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;
522 }
else if (maxStep > 0. && h * phi1mag > maxStep) {
524 std::cout <<
" Step is considered too long. H is reduced.\n";
526 h = 0.5 * maxStep / phi1mag;
530 if (h *
fabs(phi1[0]) > 0.1 *
fabs(xmax - xmin) ||
531 h *
fabs(phi1[1]) > 0.1 *
fabs(ymax - ymin)) {
534 std::cout <<
" Step is considered too long. H is halved.\n";
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;
549 if (m_debug) std::cout <<
" Step size ok.\n";
551 for (
size_t i = 0; i < 3; ++i) x0[i] += h * phi1[i];
553 if (!m_sensor->IsInside(x0[0], x0[1], x0[2])) {
556 if (m_debug) std::cout <<
" New point is outside. Terminate.\n";
557 if (!Terminate(xs.back(), x0, particle, ts, xs)) {
558 flag = StatusCalculationAbandoned;
567 const double dphi =
fabs(phi1[0] - phi2[0]) +
fabs(phi1[1] - phi2[1]) +
568 fabs(phi1[2] - phi2[2]);
570 h =
sqrt(h * m_accuracy / dphi);
571 if (m_debug) std::cout <<
" Adapting H to " << h <<
".\n";
574 if (m_debug) std::cout <<
" H increased by factor two.\n";
578 std::cerr << m_className <<
"::DriftLine: Step size is zero. Stop.\n";
579 flag = StatusCalculationAbandoned;
583 if (initCycle > 0 && h < 0.2 * hprev) {
584 if (m_debug) std::cout <<
" Reinitialise step size.\n";
594 if (h > 10 * hprev) {
597 std::cout <<
" H restricted to 10 times the previous value.\n";
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;
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]);
618 if (flag == StatusCalculationAbandoned)
return false;
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 {
629 const size_t nPoints = xs.size();
630 if (nPoints < 2)
return true;
636 ne.assign(nPoints, 1.);
637 ni.assign(nPoints, 0.);
638 nn.assign(nPoints, 0.);
640 bool overflow =
false;
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]};
649 for (
size_t j = 0; j < nG; ++j) {
650 const double f = 0.5 * (1. + tg[j]);
652 for (
size_t k = 0; k < 3; ++k) xj[k] += f * dx[k];
653 const double alp = GetAlpha(xj, particle);
655 std::cerr << m_className <<
"::Avalanche:\n Cannot retrieve alpha at "
656 <<
"drift line point " << i <<
", segment " << j <<
".\n";
659 const double eta = GetEta(xj, particle);
661 std::cerr << m_className <<
"::Avalanche:\n Cannot retrieve eta at "
662 <<
"drift line point " << i <<
", segment " << j <<
".\n";
665 alpsum += wg[j] * alp;
666 etasum += wg[j] * eta;
670 if (alpsum > 1.e-6 && !start) {
672 std::cout << m_className <<
"::Avalanche: Avalanche starts at step "
677 const double d = Mag(dx);
679 constexpr double expmax = 30.;
680 const double logp = log(std::max(1., ne[i - 1]));
681 if (logp + d * (alpsum - etasum) > expmax) {
685 ne[i] = ne[i - 1] *
exp(d * (alpsum - etasum));
688 if (logp + d * alpsum > expmax) {
692 ni[i] = ne[i - 1] * (
exp(d * alpsum) - 1);
694 nn[i] = std::max(ne[i - 1] + ni[i] - ne[i], 0.);
697 std::cerr << m_className <<
"::Avalanche:\n "
698 <<
"Warning: Integrating the Townsend coefficients "
699 <<
"would lead to exponential overflow.\n "
700 <<
"Avalanche truncated.\n";
702 const double qe = ne.back();
703 const double qi = std::accumulate(ni.begin(), ni.end(), 0.);
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);
710 if (m_gainFluctuations == GainFluctuations::Polya) {
711 for (
unsigned int i = 0; i < 100; ++i) {
715 q1 = std::max(q1, 1.);
717 q1 *= ComputeLoss(xs, particle, eps);
718 scale = (q1 + 1.) / (qi + 1.);
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]);
736 return ComputeSigma(m_x, m_particle, eps);
739double DriftLineRKF::ComputeSigma(
const std::vector<Vec>& x,
741 const double eps)
const {
749 const size_t nPoints = x.size();
751 if (nPoints < 2)
return 0.;
756 for (
size_t i = 0; i < nPoints; ++i) {
758 const double var = GetVar(x[i], particle);
760 std::cerr << m_className <<
"::ComputeSigma:\n"
761 <<
" Cannot retrieve variance at point " << i <<
".\n";
764 if (i > 0) crude += 0.5 * Dist(x[i - 1], x[i]) * (var + varPrev);
769 const double tol = eps * crude;
771 for (
size_t i = 0; i < nPoints - 1; ++i) {
772 sum += IntegrateDiffusion(x[i], x[i + 1], particle, tol);
778 if (m_status == StatusCalculationAbandoned)
return 1.;
779 return ComputeGain(m_x, m_particle, eps);
782double DriftLineRKF::ComputeGain(
const std::vector<Vec>& x,
784 const double eps)
const {
792 const size_t nPoints = m_x.size();
794 if (nPoints < 2)
return 1.;
801 double alphaPrev = 0.;
802 for (
size_t i = 0; i < nPoints; ++i) {
804 const double alpha = GetAlpha(x[i], particle);
806 std::cerr << m_className <<
"::ComputeGain:\n"
807 <<
" Cannot retrieve alpha at point " << i <<
".\n";
810 if (i > 0) crude += 0.5 * Dist(x[i - 1], x[i]) * (
alpha + alphaPrev);
814 if (crude < Small)
return 1.;
817 const double tol = eps * crude;
819 for (
size_t i = 0; i < nPoints - 1; ++i) {
820 sum += IntegrateAlpha(x[i], x[i + 1], particle, tol);
826 if (m_status == StatusCalculationAbandoned)
return 1.;
827 return ComputeLoss(m_x, m_particle, eps);
830double DriftLineRKF::ComputeLoss(
const std::vector<Vec>& x,
832 const double eps)
const {
840 const size_t nPoints = x.size();
842 if (nPoints < 2)
return 1.;
850 for (
size_t i = 0; i < nPoints; ++i) {
852 const double eta = GetEta(x[i], particle);
854 std::cerr << m_className <<
"::ComputeLoss:\n"
855 <<
" Cannot retrieve eta at point " << i <<
".\n";
858 if (i > 0) crude += 0.5 * Dist(x[i - 1], x[i]) * (eta + etaPrev);
863 const double tol = eps * crude;
865 for (
size_t i = 0; i < nPoints - 1; ++i) {
866 sum += IntegrateEta(x[i], x[i + 1], particle, tol);
873 const size_t nPoints = m_x.size();
874 if (nPoints < 2)
return 0.;
876 for (
size_t i = 1; i < nPoints; ++i) {
877 path += Dist(m_x[i - 1], m_x[i]);
882int DriftLineRKF::GetField(
const std::array<double, 3>& x,
883 double& ex,
double& ey,
double& ez,
884 double& bx,
double& by,
double& bz,
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);
892Vec DriftLineRKF::GetVelocity(
const std::array<double, 3>& x,
895 Vec v = {0., 0., 0.};
898 if (!m_sensor->IsInArea(x[0], x[1], x[2])) {
899 status = StatusLeftDriftArea;
902 if (m_useVelocityMap &&
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;
911 ok = cmp->ElectronVelocity(x[0], x[1], x[2], v[0], v[1], v[2]);
913 ok = cmp->HoleVelocity(x[0], x[1], x[2], v[0], v[1], v[2]);
918 for (
unsigned int k = 0; k < 3; ++k) v[k] *= -1;
923 double ex = 0., ey = 0., ez = 0.;
924 double bx = 0., by = 0., bz = 0.;
925 Medium* medium =
nullptr;
927 status = GetField(x, ex, ey, ez, bx, by, bz, medium);
928 if (status != 0)
return v;
931 ok = medium->ElectronVelocity(ex, ey, ez, bx, by, bz, v[0], v[1], v[2]);
933 ok = medium->IonVelocity(ex, ey, ez, bx, by, bz, v[0], v[1], v[2]);
935 ok = medium->HoleVelocity(ex, ey, ez, bx, by, bz, v[0], v[1], v[2]);
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;
940 ok = medium->NegativeIonVelocity(ex, ey, ez, bx, by, bz, v[0], v[1], v[2]);
943 std::cerr << m_className <<
"::GetVelocity:\n"
944 <<
" Cannot retrieve drift velocity at "
945 << PrintVec(x) <<
".\n";
946 status = StatusCalculationAbandoned;
951bool DriftLineRKF::GetDiffusion(
const std::array<double, 3>& x,
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;
960 return medium->ElectronDiffusion(ex, ey, ez, bx, by, bz, dl, dt);
962 return medium->IonDiffusion(ex, ey, ez, bx, by, bz, dl, dt);
964 return medium->HoleDiffusion(ex, ey, ez, bx, by, bz, dl, dt);
969double DriftLineRKF::GetVar(
const std::array<double, 3>& x,
973 const Vec v = GetVelocity(x, particle, stat);
974 if (stat != 0)
return -1.;
976 const double speed = Mag(v);
978 std::cerr << m_className <<
"::GetVariance: Zero velocity.\n";
982 double dl = 0., dt = 0.;
983 if (!GetDiffusion(x, particle, dl, dt))
return -1.;
985 const double sigma = dl / speed;
986 return sigma * sigma;
989double DriftLineRKF::GetAlpha(
const std::array<double, 3>& x,
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;
999 if (!cmp->ElectronTownsend(x[0], x[1], x[2], alpha))
continue;
1001 if (!cmp->HoleTownsend(x[0], x[1], x[2], alpha))
continue;
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.;
1012 medium->ElectronTownsend(ex, ey, ez, bx, by, bz, alpha);
1014 medium->HoleTownsend(ex, ey, ez, bx, by, bz, alpha);
1019double DriftLineRKF::GetEta(
const std::array<double, 3>& x,
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.;
1028 medium->ElectronAttachment(ex, ey, ez, bx, by, bz, eta);
1030 medium->HoleAttachment(ex, ey, ez, bx, by, bz, eta);
1035bool DriftLineRKF::Terminate(
const std::array<double, 3>& xx0,
1036 const std::array<double, 3>& xx1,
1038 std::vector<double>& ts,
1039 std::vector<Vec>& xs)
const {
1050 const Vec vv0 = GetVelocity(xx0, particle, status);
1052 std::cerr << m_className <<
"::Terminate:\n"
1053 <<
" Cannot retrieve drift velocity at initial point.\n";
1056 double speed = Mag(vv0);
1057 if (speed < Small) {
1058 std::cerr << m_className <<
"::Terminate:\n"
1059 <<
" Zero velocity at initial position.\n";
1068 constexpr unsigned int nBisections = 20;
1069 for (
unsigned int i = 0; i < nBisections; ++i) {
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]))) {
1080 std::cout << m_className <<
"::Terminate:\n"
1081 <<
" Bisection ended at cycle " << i <<
".\n";
1086 const Vec xm = MidPoint(x0, x1);
1088 if (m_sensor->IsInside(xm[0], xm[1], xm[2]) &&
1089 m_sensor->IsInArea(xm[0], xm[1], xm[2])) {
1097 Vec v0 = GetVelocity(x0, particle, status);
1099 std::cerr << m_className <<
"::Terminate:\n"
1100 <<
" Warning: Unable to compute mean velocity at last step.\n";
1102 speed = 0.5 * (speed + Mag(v0));
1105 const double dt = Dist(xx0, x0) / speed;
1107 ts.push_back(ts.back() + dt);
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 {
1126 double t0 = ts.back() - ts.front();
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";
1135 Vec v0 = GetVelocity(x0, particle, status);
1137 std::cerr << m_className <<
"::DriftToWire:\n"
1138 <<
" Cannot retrieve initial drift velocity.\n";
1144 double dt = (Mag(xw - x0[0], yw - x0[1]) - rw) / Mag(v0[0], v0[1]);
1146 std::cout <<
" Estimated time needed to reach the wire: "
1150 constexpr unsigned int nMaxSplit = 10;
1151 unsigned int nSplit = 0;
1153 bool onwire =
false;
1154 const double r2 = rw * rw;
1155 while (!onwire && dt > 1.e-6 * t0) {
1158 for (
unsigned int j = 0; j < 3; ++j) x1[j] += dt * v0[j];
1160 const double xinp0 = (x1[0] - x0[0]) * (xw - x0[0]) +
1161 (x1[1] - x0[1]) * (yw - x0[1]);
1164 std::cerr <<
" Particle moves away from the wire. Quit.\n";
1169 const double xinp1 = (x0[0] - x1[0]) * (xw - x1[0]) +
1170 (x0[1] - x1[1]) * (yw - x1[1]);
1172 if (Mag2(xw - x1[0], yw - x1[1]) <= r2) {
1174 if (m_debug) std::cout <<
" Inside.\n";
1177 if (m_debug) std::cout <<
" Wire crossed.\n";
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];
1188 Vec v1 = GetVelocity(x1, particle, status);
1190 std::cerr << m_className <<
"::DriftToWire:\n"
1191 <<
" Cannot retrieve drift velocity at end point. Quit.\n";
1195 const Vec xm = MidPoint(x0, x1);
1197 Vec vm = GetVelocity(xm, particle, status);
1199 std::cerr << m_className <<
"::DriftToWire:\n"
1200 <<
" Cannot retrieve drift velocity at mid point. Quit.\n";
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";
1211 const double p0 = 1. / speed0;
1212 const double p1 = 1. / speed1;
1213 const double pm = 1. / speedm;
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) {
1219 if (m_debug) std::cout <<
" Reducing step size.\n";
1225 const double t1 = t0 + d * (p0 + 4 * pm + p1) / 6.;
1227 ts.push_back(ts[0] + t1);
1235 double ex = 0., ey = 0., ez = 0.;
1236 Medium* medium =
nullptr;
1237 m_sensor->ElectricField(xw, yw, 0., ex, ey, ez, medium, stat);
1243 std::cout << m_className <<
"::PrintDriftLine:\n";
1245 std::cout <<
" No drift line present.\n";
1249 std::cout <<
" Particle: electron\n";
1251 std::cout <<
" Particle: ion\n";
1253 std::cout <<
" Particle: hole\n";
1255 std::cout <<
" Particle: positive electron\n";
1257 std::cout <<
" Particle: negative ion\n";
1259 std::cout <<
" Particle: unknown\n";
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]);
1279 const auto& p = m_x.back();
1288 double& z,
double& t)
const {
1289 if (i >= m_x.size()) {
1290 std::cerr << m_className <<
"::GetDriftLinePoint: Index out of range.\n";
1294 const auto& p = m_x[i];
1301double DriftLineRKF::IntegrateDiffusion(
const std::array<double, 3>& xi,
1302 const std::array<double, 3>& xe,
1304 const double tol)
const {
1307 double var0 = GetVar(x0, particle);
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";
1316 double integral = 0.;
1317 while (Dist(xe, x0) > 1.e-6) {
1318 const double d = Dist(x1, x0);
1322 std::cout << m_className <<
"::IntegrateDiffusion: Small step.\n";
1324 integral += var0 * d;
1331 var1 = GetVar(x1, particle);
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";
1342 if (
fabs(var0 - 2 * varm + var1) *
sqrt(d * 2 / (var0 + var1)) / 6. < tol) {
1344 integral += d * (var0 + 4 * varm + var1) / 6.;
1358double DriftLineRKF::IntegrateAlpha(
const std::array<double, 3>& xi,
1359 const std::array<double, 3>& xe,
1361 const double tol)
const {
1365 double alpha0 = GetAlpha(x0, particle);
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";
1374 double integral = 0.;
1375 while (Dist(xe, x0) > 1.e-6) {
1376 const double d = Dist(x1, x0);
1380 std::cout << m_className <<
"::IntegrateAlpha: Small step.\n";
1382 integral += alpha0 * d;
1389 alpha1 = GetAlpha(x1, particle);
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";
1399 if (d *
fabs(alpha0 - 2 * alpham + alpha1) / 3. < tol) {
1401 integral += d * (alpha0 + 4 * alpham + alpha1) / 6.;
1415double DriftLineRKF::IntegrateEta(
const std::array<double, 3>& xi,
1416 const std::array<double, 3>& xe,
1418 const double tol)
const {
1421 double eta0 = GetEta(x0, particle);
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";
1430 double integral = 0.;
1431 while (Dist(xe, x0) > 1.e-6) {
1432 const double d = Dist(x1, x0);
1436 std::cout << m_className <<
"::IntegrateEta: Small step.\n";
1438 integral += eta0 * d;
1445 eta1 = GetEta(x1, particle);
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";
1455 if (d *
fabs(eta0 - 2 * etam + eta1) / 3. < tol) {
1457 integral += d * (eta0 + 4 * etam + eta1) / 6.;
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 {
1476 const auto nPoints = ts.size();
1477 if (nPoints < 2)
return;
1478 const double q0 = Charge(particle) * scale;
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],
1493 std::vector<std::array<double, 3> > vs;
1494 for (
const auto& x : xs) {
1496 Vec v = GetVelocity(x, particle, stat);
1498 std::cerr << m_className <<
"::ComputeSignal:\n"
1499 <<
" Cannot retrieve velocity at " << PrintVec(x) <<
"\n";
1501 vs.push_back(std::move(v));
1503 m_sensor->AddSignal(q0, ts, xs, vs, ne, m_navg);
1507 std::vector<std::array<float, 3> >& xl,
1508 const bool electron)
const {
1513 std::cerr << m_className <<
"::FieldLine: Sensor is not defined.\n";
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);
1524 double maxStep = -1.;
1525 if (m_useStepSizeLimit) {
1526 if (m_maxStepSize > 0.) {
1527 maxStep = m_maxStepSize;
1529 maxStep = 0.5 * m_sensor->StepSizeHint();
1534 double ex = 0., ey = 0., ez = 0.;
1535 Medium* medium =
nullptr;
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";
1543 Vec x0 = {xi, yi, zi};
1544 Vec f0 = {ex, ey, ez};
1545 if (electron)
for (
auto& f : f0) f *= -1;
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.;
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.;
1562 const double fmag0 = Mag(f0);
1563 if (fmag0 < Small) {
1564 std::cerr << m_className <<
"::FieldLine:\n"
1565 <<
" Zero field at initial position.\n";
1570 double h = m_accuracy / fmag0;
1573 std::cout << m_className <<
"::FieldLine:\n"
1574 <<
" Initial step size: " << h <<
".\n";
1577 xl.push_back({float(x0[0]), float(x0[1]), float(x0[2])});
1584 for (
unsigned int i = 0; i < 3; ++i) {
1585 x1[i] += h * b10 * f0[i];
1587 m_sensor->ElectricField(x1[0], x1[1], x1[2], ex, ey, ez, medium, stat);
1589 if (m_debug) std::cout <<
" Point 1 outside.\n";
1590 Terminate(x0, x1, xl);
1593 Vec f1 = {ex, ey, ez};
1594 if (electron)
for (
auto& f : f1) f *= -1;
1597 for (
unsigned int i = 0; i < 3; ++i) {
1598 x2[i] += h * (b20 * f0[i] + b21 * f1[i]);
1600 m_sensor->ElectricField(x2[0], x2[1], x2[2], ex, ey, ez, medium, stat);
1602 if (m_debug) std::cout <<
" Point 2 outside.\n";
1603 Terminate(x0, x2, xl);
1606 Vec f2 = {ex, ey, ez};
1607 if (electron)
for (
auto& f : f2) f *= -1;
1610 for (
unsigned int i = 0; i < 3; ++i) {
1611 x3[i] += h * (b30 * f0[i] + b31 * f1[i] + b32 * f2[i]);
1613 m_sensor->ElectricField(x3[0], x3[1], x3[2], ex, ey, ez, medium, stat);
1615 if (m_debug) std::cout <<
" Point 3 outside.\n";
1616 Terminate(x0, x3, xl);
1619 Vec f3 = {ex, ey, ez};
1620 if (electron)
for (
auto& f : f3) f *= -1;
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)) {
1630 xl.push_back({float(xw), float(yw), float(zw)});
1637 std::cerr << m_className <<
"::FieldLine: Step size too small. Stop.\n";
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];
1649 const double phi1mag = Mag(phi1);
1650 if (phi1mag < Small) {
1651 std::cerr << m_className <<
"::FieldLine: Step has zero length. Stop.\n";
1653 }
else if (maxStep > 0. && h * phi1mag > maxStep) {
1655 std::cout <<
" Step is considered too long. H is reduced.\n";
1657 h = 0.5 * maxStep / phi1mag;
1661 if (h * fabs(phi1[0]) > 0.1 * fabs(xmax - xmin) ||
1662 h * fabs(phi1[1]) > 0.1 * fabs(ymax - ymin)) {
1665 std::cout <<
" Step is considered too long. H is halved.\n";
1670 if (m_debug) std::cout <<
" Step size ok.\n";
1672 for (
size_t i = 0; i < 3; ++i) x0[i] += h * phi1[i];
1674 m_sensor->ElectricField(x0[0], x0[1], x0[2], ex, ey, ez, medium, stat);
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);
1684 xl.push_back({float(x0[0]), float(x0[1]), float(x0[2])});
1687 const double dphi = fabs(phi1[0] - phi2[0]) + fabs(phi1[1] - phi2[1]) +
1688 fabs(phi1[2] - phi2[2]);
1690 h = sqrt(h * m_accuracy / dphi);
1691 if (m_debug) std::cout <<
" Adapting H to " << h <<
".\n";
1694 if (m_debug) std::cout <<
" H increased by factor two.\n";
1698 std::cerr << m_className <<
"::FieldLine: Step size is zero. Stop.\n";
1702 if (initCycle > 0 && h < 0.2 * hprev) {
1703 if (m_debug) std::cout <<
" Reinitialise step size.\n";
1707 xl.push_back({float(xi), float(yi), float(zi)});
1712 if (h > 10 * hprev) {
1715 std::cout <<
" H restricted to 10 times the previous value.\n";
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";
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 {
1739 constexpr unsigned int nBisections = 20;
1740 for (
unsigned int i = 0; i < nBisections; ++i) {
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]))) {
1751 std::cout << m_className <<
"::Terminate: Bisection ends at cycle "
1757 const Vec xm = MidPoint(x0, x1);
1759 if (m_sensor->IsInside(xm[0], xm[1], xm[2]) &&
1760 m_sensor->IsInArea(xm[0], xm[1], xm[2])) {
1767 xs.push_back({float(x0[0]), float(x0[1]), float(x0[2])});
std::array< double, 3 > Vec
bool FieldLine(const double xi, const double yi, const double zi, std::vector< std::array< float, 3 > > &xl, const bool electron=true) const
void SetMaximumStepSize()
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.
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).
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).
bool GetArea(double &xmin, double &ymin, double &zmin, double &xmax, double &ymax, double &zmax)
Return the current user area.
Visualize drift lines and tracks.
constexpr std::array< double, 6 > GaussLegendreWeights6()
constexpr std::array< double, 6 > GaussLegendreNodes6()
std::array< double, 3 > Vec
double RndmPolya(const double theta)
Draw a Polya distributed random number.
DoubleAc exp(const DoubleAc &f)
DoubleAc fabs(const DoubleAc &f)
DoubleAc sqrt(const DoubleAc &f)