14std::string PrintVec(
const std::array<double, 3>& x) {
15 return "(" + std::to_string(x[0]) +
", " + std::to_string(x[1]) +
", " +
16 std::to_string(x[2]) +
")";
19double Mag(
const std::array<double, 3>& x) {
20 return sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
31 std::cerr << m_className <<
"::SetSensor: Null pointer.\n";
40 std::cerr << m_className <<
"::EnablePlotting: Null pointer.\n";
48 m_stepModel = StepModel::FixedTime;
50 std::cerr << m_className <<
"::SetTimeSteps:\n "
51 <<
"Step size is too small. Using default (20 ps) instead.\n";
56 std::cout << m_className <<
"::SetTimeSteps:\n"
57 <<
" Step size set to " << d <<
" ns.\n";
63 m_stepModel = StepModel::FixedDistance;
65 std::cerr << m_className <<
"::SetDistanceSteps:\n "
66 <<
"Step size is too small. Using default (10 um) instead.\n";
71 std::cout << m_className <<
"::SetDistanceSteps:\n"
72 <<
" Step size set to " << d <<
" cm.\n";
78 m_stepModel = StepModel::CollisionTime;
80 std::cerr << m_className <<
"::SetCollisionSteps:\n "
81 <<
"Number of collisions set to default value (100).\n";
86 std::cout << m_className <<
"::SetCollisionSteps:\n "
87 <<
"Number of collisions to be skipped set to " << n <<
".\n";
95 std::cerr << m_className <<
"::SetStepDistanceFunction: Null pointer.\n";
99 m_stepModel = StepModel::UserDistance;
103 if (fabs(t1 - t0) < Small) {
104 std::cerr << m_className <<
"::SetTimeWindow:\n"
105 <<
" Time interval must be greater than zero.\n";
109 m_tMin = std::min(t0, t1);
110 m_tMax = std::max(t0, t1);
111 m_hasTimeWindow =
true;
115 double& z,
double& t)
const {
116 if (i >= m_drift.size()) {
117 std::cerr << m_className <<
"::GetDriftLinePoint: Index out of range.\n";
128 double& z0,
double& t0,
double& x1,
129 double& y1,
double& z1,
double& t1,
131 if (i >= m_endpointsHoles.size()) {
132 std::cerr << m_className <<
"::GetHoleEndpoint: Index out of range.\n";
136 x0 = m_endpointsHoles[i].x0[0];
137 y0 = m_endpointsHoles[i].x0[1];
138 z0 = m_endpointsHoles[i].x0[2];
139 t0 = m_endpointsHoles[i].t0;
140 x1 = m_endpointsHoles[i].x1[0];
141 y1 = m_endpointsHoles[i].x1[1];
142 z1 = m_endpointsHoles[i].x1[2];
143 t1 = m_endpointsHoles[i].t1;
144 status = m_endpointsHoles[i].status;
148 double& z0,
double& t0,
double& x1,
double& y1,
149 double& z1,
double& t1,
int& status)
const {
150 if (i >= m_endpointsIons.size()) {
151 std::cerr << m_className <<
"::GetIonEndpoint: Index out of range.\n";
155 x0 = m_endpointsIons[i].x0[0];
156 y0 = m_endpointsIons[i].x0[1];
157 z0 = m_endpointsIons[i].x0[2];
158 t0 = m_endpointsIons[i].t0;
159 x1 = m_endpointsIons[i].x1[0];
160 y1 = m_endpointsIons[i].x1[1];
161 z1 = m_endpointsIons[i].x1[2];
162 t1 = m_endpointsIons[i].t1;
163 status = m_endpointsIons[i].status;
167 double& y0,
double& z0,
double& t0,
168 double& x1,
double& y1,
double& z1,
169 double& t1,
int& status)
const {
170 if (i >= m_endpointsElectrons.size()) {
171 std::cerr << m_className <<
"::GetElectronEndpoint: Index out of range.\n";
175 x0 = m_endpointsElectrons[i].x0[0];
176 y0 = m_endpointsElectrons[i].x0[1];
177 z0 = m_endpointsElectrons[i].x0[2];
178 t0 = m_endpointsElectrons[i].t0;
179 x1 = m_endpointsElectrons[i].x1[0];
180 y1 = m_endpointsElectrons[i].x1[1];
181 z1 = m_endpointsElectrons[i].x1[2];
182 t1 = m_endpointsElectrons[i].t1;
183 status = m_endpointsElectrons[i].status;
187 const double z0,
const double t0) {
189 std::cerr << m_className <<
"::DriftElectron: Sensor is not defined.\n";
193 m_endpointsElectrons.clear();
194 m_endpointsHoles.clear();
195 m_endpointsIons.clear();
201 std::vector<DriftPoint> secondaries;
202 return DriftLine({x0, y0, z0}, t0, Particle::Electron, secondaries);
208 std::cerr << m_className <<
"::DriftHole: Sensor is not defined.\n";
212 m_endpointsElectrons.clear();
213 m_endpointsHoles.clear();
214 m_endpointsIons.clear();
220 std::vector<DriftPoint> secondaries;
221 return DriftLine({x0, y0, z0}, t0, Particle::Hole, secondaries);
227 std::cerr << m_className <<
"::DriftIon: Sensor is not defined.\n";
231 m_endpointsElectrons.clear();
232 m_endpointsHoles.clear();
233 m_endpointsIons.clear();
239 std::vector<DriftPoint> secondaries;
240 return DriftLine({x0, y0, z0}, t0, Particle::Ion, secondaries);
243bool AvalancheMC::DriftLine(
const std::array<double, 3>& xi,
const double ti,
244 const Particle particle,
245 std::vector<DriftPoint>& secondaries,
250 std::array<double, 3> x0 = xi;
251 std::array<double, 3> e0 = {0., 0., 0.};
252 std::array<double, 3> b0 = {0., 0., 0.};
253 Medium* medium =
nullptr;
254 int status = GetField(x0, e0, b0, medium);
256 std::cerr << m_className +
"::DriftLine: "
257 << PrintVec(x0) +
" is not in a valid drift region.\n";
261 if (m_hasTimeWindow && (t0 < m_tMin || t0 > m_tMax)) {
262 status = StatusOutsideTimeWindow;
263 std::cerr << m_className +
"::DriftLine: " << t0
264 <<
" is outside the time window.\n";
267 if (status != 0)
return false;
269 AddPoint(x0, t0, particle, 1, m_drift);
271 std::cout << m_className +
"::DriftLine: Starting at "
272 << PrintVec(x0) +
".\n";
275 const bool semiconductor = medium->IsSemiconductor();
277 while (0 == status) {
278 constexpr double tol = 1.e-10;
280 const double emag = Mag(e0);
281 if (emag < tol && !m_useDiffusion) {
282 std::cerr << m_className +
"::DriftLine: Too small electric field at "
283 << PrintVec(x0) +
".\n";
284 status = StatusCalculationAbandoned;
288 std::array<double, 3> v0;
289 if (!GetVelocity(particle, medium, x0, e0, b0, v0)) {
290 status = StatusCalculationAbandoned;
291 std::cerr << m_className +
"::DriftLine: Abandoning the calculation.\n";
296 double vmag = Mag(v0);
297 if (vmag < tol && !m_useDiffusion) {
298 std::cerr << m_className +
"::DriftLine: Too small drift velocity at "
299 << PrintVec(x0) +
".\n";
300 status = StatusCalculationAbandoned;
305 std::array<double, 3> x1 = x0;
308 if (vmag < tol || emag < tol) {
310 const double mu = GetMobility(particle, medium);
312 std::cerr << m_className +
"::DriftLine: Invalid mobility.\n";
313 status = StatusCalculationAbandoned;
317 const double dif = mu * BoltzmannConstant * medium->GetTemperature();
319 switch (m_stepModel) {
320 case StepModel::FixedTime:
321 sigma =
sqrt(2. * dif * m_tMc);
324 case StepModel::FixedDistance:
327 case StepModel::CollisionTime: {
330 SpeedOfLight *
sqrt(2 * BoltzmannConstant *
331 medium->GetTemperature() / ElectronMass);
332 sigma = m_nMc * dif / vth;
334 case StepModel::UserDistance:
335 sigma = m_fStep(x0[0], x0[1], x0[2]);
338 std::cerr << m_className +
"::DriftLine: Unknown stepping model.\n";
339 status = StatusCalculationAbandoned;
342 if (status != 0)
break;
343 if (m_stepModel != StepModel::FixedTime) {
344 t1 += sigma * sigma / (2 * dif);
346 for (
unsigned int i = 0; i < 3; ++i) x1[i] +=
RndmGaussian(0., sigma);
350 switch (m_stepModel) {
351 case StepModel::FixedTime:
354 case StepModel::FixedDistance:
357 case StepModel::CollisionTime:
358 if (particle == Particle::Ion) {
359 constexpr double c1 = AtomicMassUnitElectronVolt /
360 (SpeedOfLight * SpeedOfLight);
363 constexpr double c1 = ElectronMass / (SpeedOfLight * SpeedOfLight);
367 case StepModel::UserDistance:
368 dt = m_fStep(x0[0], x0[1], x0[2]) / vmag;
371 std::cerr << m_className +
"::DriftLine: Unknown stepping model.\n";
372 status = StatusCalculationAbandoned;
375 if (status != 0)
break;
377 double difl = 0., dift = 0.;
378 if (m_useDiffusion) {
380 if (!GetDiffusion(particle, medium, e0, b0, difl, dift)) {
381 PrintError(
"DriftLine",
"diffusion", particle, x0);
382 status = StatusCalculationAbandoned;
385 if (m_stepModel != StepModel::FixedTime) {
386 const double ds = vmag * dt;
387 const double dif = std::max(difl, dift);
388 if (dif * dif > ds) {
389 dt = ds * ds / (dif * dif * vmag);
394 std::array<double, 3> v1 = v0;
396 StepRKF(particle, x0, v0, dt, x1, v1, status);
399 for (
unsigned int k = 0; k < 3; ++k) x1[k] += dt * v0[k];
402 if (m_useDiffusion) AddDiffusion(
sqrt(vmag * dt), difl, dift, x1, v1);
406 std::cout << m_className +
"::DriftLine: Next point: "
407 << PrintVec(x1) +
".\n";
411 status = GetField(x1, e0, b0, medium);
412 if (status == StatusLeftDriftMedium || status == StatusLeftDriftArea) {
415 Terminate(x0, t0, x1, t1);
417 std::cout << m_className +
"::DriftLine: Left drift region at "
418 << PrintVec(x1) +
".\n";
421 AddPoint(x1, t1, particle, 1, m_drift);
425 std::array<double, 3> xc = x0;
427 if (m_sensor->
IsWireCrossed(x0[0], x0[1], x0[2], x1[0], x1[1], x1[2], xc[0],
428 xc[1], xc[2],
false, rc)) {
430 std::cout << m_className +
"::DriftLine: Hit a wire at "
431 << PrintVec(xc) +
".\n";
433 status = StatusLeftDriftMedium;
435 std::array<double, 3> dc = {xc[0] - x0[0], xc[1] - x0[1], xc[2] - x0[2]};
436 std::array<double, 3> d1 = {x1[0] - x0[0], x1[1] - x0[1], x1[2] - x0[2]};
437 const double tc = t0 + (t1 - t0) * Mag(dc) / Mag(d1);
439 AddPoint(xc, tc, particle, 1, m_drift);
444 if (m_hasTimeWindow && (t1 < m_tMin || t1 > m_tMax)) {
445 status = StatusOutsideTimeWindow;
448 AddPoint(x1, t1, particle, 1, m_drift);
455 unsigned int nElectronsOld = m_nElectrons;
456 unsigned int nHolesOld = m_nHoles;
457 unsigned int nIonsOld = m_nIons;
459 if ((particle == Particle::Electron || particle == Particle::Hole) &&
460 (aval || m_useAttachment) &&
461 (m_sizeCut == 0 || m_nElectrons < m_sizeCut)) {
462 ComputeGainLoss(particle, m_drift, status, secondaries, semiconductor);
463 if (status == StatusAttached && m_debug) {
464 std::cout << m_className +
"::DriftLine: Attached at "
465 << PrintVec(m_drift.back().x) +
".\n";
470 std::cout << m_className <<
"::DriftLine: Stopped at "
471 << PrintVec(m_drift.back().x) +
".\n";
474 AddEndPoint(xi, ti, m_drift.back().x, m_drift.back().t,
478 const int nNewElectrons = m_nElectrons - nElectronsOld;
479 const int nNewHoles = m_nHoles - nHolesOld;
480 const int nNewIons = m_nIons - nIonsOld;
481 std::cout << m_className <<
"::DriftLine: Produced\n"
482 <<
" " << nNewElectrons <<
" electrons,\n"
483 <<
" " << nNewHoles <<
" holes, and\n"
484 <<
" " << nNewIons <<
" ions.\n";
488 const double scale = particle == Particle::Electron
490 : particle == Particle::Hole ? m_scaleH : m_scaleI;
491 if (m_doSignal) ComputeSignal(particle, scale, m_drift);
492 if (m_doInducedCharge) ComputeInducedCharge(scale, m_drift);
495 if (m_viewer && !m_drift.empty()) {
496 const size_t nPoints = m_drift.size();
499 if (particle == Particle::Electron) {
501 }
else if (particle == Particle::Hole) {
507 for (
size_t i = 0; i < nPoints; ++i) {
508 const auto&
x = m_drift[i].x;
513 if (status == StatusCalculationAbandoned)
return false;
518 const double z0,
const double t0,
520 std::vector<DriftPoint> aval;
521 AddPoint({x0, y0, z0}, t0, Particle::Electron, 1, aval);
522 return Avalanche(aval,
true, holes);
526 const double z0,
const double t0,
527 const bool electrons) {
528 std::vector<DriftPoint> aval;
529 AddPoint({x0, y0, z0}, t0, Particle::Hole, 1, aval);
530 return Avalanche(aval, electrons,
true);
534 const double z0,
const double t0) {
535 std::vector<DriftPoint> aval;
536 AddPoint({x0, y0, z0}, t0, Particle::Electron, 1, aval);
537 AddPoint({x0, y0, z0}, t0, Particle::Hole, 1, aval);
538 return Avalanche(aval,
true,
true);
543 std::vector<DriftPoint> aval;
544 for (
const auto& p: m_endpointsElectrons) {
545 if (p.status == StatusAlive || p.status == StatusOutsideTimeWindow) {
546 AddPoint(p.x1, p.t1, Particle::Electron, 1, aval);
549 for (
const auto& p: m_endpointsHoles) {
550 if (p.status == StatusAlive || p.status == StatusOutsideTimeWindow) {
551 AddPoint(p.x1, p.t1, Particle::Hole, 1, aval);
554 for (
const auto& p: m_endpointsIons) {
555 if (p.status == StatusAlive || p.status == StatusOutsideTimeWindow) {
556 AddPoint(p.x1, p.t1, Particle::Ion, 1, aval);
559 return Avalanche(aval, electrons, holes);
562bool AvalancheMC::Avalanche(std::vector<DriftPoint>& aval,
563 const bool withE,
const bool withH) {
570 m_endpointsElectrons.clear();
571 m_endpointsHoles.clear();
572 m_endpointsIons.clear();
576 std::cerr << m_className <<
"::Avalanche: Sensor is not defined.\n";
583 for (
const auto& point : aval) {
584 if (point.particle == Particle::Electron) {
586 }
else if (point.particle == Particle::Hole) {
593 if (!withH && !withE) {
594 std::cerr << m_className +
"::Avalanche: "
595 <<
"Neither electron nor hole/ion component requested.\n";
598 std::vector<DriftPoint> secondaries;
599 while (!aval.empty()) {
600 for (
const auto& point : aval) {
601 if (!withE && point.particle == Particle::Electron)
continue;
602 if (!withH && point.particle != Particle::Electron)
continue;
604 if (m_hasTimeWindow && (point.t < m_tMin || point.t > m_tMax)) {
605 for (
unsigned int i = 0; i < point.n; ++i) {
606 AddEndPoint(point.x, point.t, point.x, point.t,
607 StatusOutsideTimeWindow, point.particle);
611 for (
unsigned int i = 0; i < point.n; ++i) {
613 DriftLine(point.x, point.t, point.particle, secondaries,
true);
616 aval.swap(secondaries);
622int AvalancheMC::GetField(
const std::array<double, 3>& x,
623 std::array<double, 3>& e, std::array<double, 3>& b,
624 Medium*& medium)
const {
629 m_sensor->
ElectricField(x[0], x[1], x[2], e[0], e[1], e[2], medium, status);
631 if (status != 0 || !medium)
return StatusLeftDriftMedium;
633 if (!m_sensor->
IsInArea(x[0], x[1], x[2]))
return StatusLeftDriftArea;
637 m_sensor->
MagneticField(x[0], x[1], x[2], b[0], b[1], b[2], status);
638 for (
size_t k = 0; k < 3; ++k) b[k] *= Tesla2Internal;
643double AvalancheMC::GetMobility(
const Particle particle, Medium* medium)
const {
644 if (particle == Particle::Electron) {
645 return medium->ElectronMobility();
646 }
else if (particle == Particle::Hole) {
647 return medium->HoleMobility();
648 }
else if (particle == Particle::Ion) {
649 return medium->IonMobility();
654bool AvalancheMC::GetVelocity(
const Particle particle, Medium* medium,
655 const std::array<double, 3>& x,
656 const std::array<double, 3>& e,
657 const std::array<double, 3>& b,
658 std::array<double, 3>& v)
const {
661 if (m_useVelocityMap && particle != Particle::Ion) {
664 for (
size_t i = 0; i < nComponents; ++i) {
666 if (!cmp->HasVelocityMap())
continue;
667 if (particle == Particle::Electron) {
669 }
else if (particle == Particle::Hole) {
670 ok = cmp->HoleVelocity(x[0], x[1], x[2], v[0], v[1], v[2]);
675 std::cout << m_className <<
"::GetVelocity: Velocity at "
676 << PrintVec(x) <<
" = " << PrintVec(v) <<
"\n";
681 if (particle == Particle::Electron) {
682 ok = medium->ElectronVelocity(e[0], e[1], e[2], b[0], b[1], b[2], v[0],
684 }
else if (particle == Particle::Hole) {
685 ok = medium->HoleVelocity(e[0], e[1], e[2], b[0], b[1], b[2], v[0], v[1],
687 }
else if (particle == Particle::Ion) {
688 ok = medium->IonVelocity(e[0], e[1], e[2], b[0], b[1], b[2], v[0], v[1],
692 PrintError(
"GetVelocity",
"velocity", particle, x);
696 std::cout << m_className <<
"::GetVelocity: Velocity at " << PrintVec(x)
697 <<
" = " << PrintVec(v) <<
"\n";
702bool AvalancheMC::GetDiffusion(
const Particle particle, Medium* medium,
703 const std::array<double, 3>& e,
704 const std::array<double, 3>& b,
double& dl,
709 if (particle == Particle::Electron) {
710 ok = medium->ElectronDiffusion(e[0], e[1], e[2], b[0], b[1], b[2], dl, dt);
711 }
else if (particle == Particle::Hole) {
712 ok = medium->HoleDiffusion(e[0], e[1], e[2], b[0], b[1], b[2], dl, dt);
713 }
else if (particle == Particle::Ion) {
714 ok = medium->IonDiffusion(e[0], e[1], e[2], b[0], b[1], b[2], dl, dt);
719double AvalancheMC::GetAttachment(
const Particle particle, Medium* medium,
720 const std::array<double, 3>& x,
721 const std::array<double, 3>& e,
722 const std::array<double, 3>& b)
const {
724 if (m_useAttachmentMap) {
726 for (
size_t i = 0; i < nComponents; ++i) {
728 if (!cmp->HasAttachmentMap())
continue;
729 if (particle == Particle::Electron) {
730 if (!cmp->ElectronAttachment(x[0], x[1], x[2], eta))
continue;
732 if (!cmp->HoleAttachment(x[0], x[1], x[2], eta))
continue;
737 if (particle == Particle::Electron) {
740 medium->HoleAttachment(e[0], e[1], e[2], b[0], b[1], b[2], eta);
745void AvalancheMC::StepRKF(
const Particle particle,
746 const std::array<double, 3>& x0,
747 const std::array<double, 3>& v0,
const double dt,
748 std::array<double, 3>& xf, std::array<double, 3>& vf,
751 constexpr double ci0 = 214. / 891.;
752 constexpr double ci1 = 1. / 33.;
753 constexpr double ci2 = 650. / 891.;
754 constexpr double beta10 = 1. / 4.;
755 constexpr double beta20 = -189. / 800.;
756 constexpr double beta21 = 729. / 800.;
760 for (
size_t k = 0; k < 3; ++k) {
761 xf[k] = x0[k] + dt * beta10 * v0[k];
763 std::array<double, 3> e;
764 std::array<double, 3> b;
765 Medium* medium =
nullptr;
766 status = GetField(xf, e, b, medium);
767 if (status != 0)
return;
770 std::array<double, 3> v1;
771 if (!GetVelocity(particle, medium, xf, e, b, v1)) {
772 status = StatusCalculationAbandoned;
777 for (
size_t k = 0; k < 3; ++k) {
778 xf[k] = x0[k] + dt * (beta20 * v0[k] + beta21 * v1[k]);
780 status = GetField(xf, e, b, medium);
781 if (status != 0)
return;
784 std::array<double, 3> v2;
785 if (!GetVelocity(particle, medium, xf, e, b, v2)) {
786 status = StatusCalculationAbandoned;
791 for (
size_t k = 0; k < 3; ++k) {
792 vf[k] = ci0 * v0[k] + ci1 * v1[k] + ci2 * v2[k];
793 xf[k] = x0[k] + dt * vf[k];
797void AvalancheMC::AddDiffusion(
const double step,
const double dl,
798 const double dt, std::array<double, 3>& x,
799 const std::array<double, 3>& v)
const {
801 const std::array<double, 3> d = {step *
RndmGaussian(0., dl),
805 std::cout << m_className <<
"::AddDiffusion: Adding diffusion step "
806 << PrintVec(d) <<
"\n";
809 const double vt =
sqrt(v[0] * v[0] + v[1] * v[1]);
810 const double phi = vt > Small ? atan2(v[1], v[0]) : 0.;
812 vt > Small ? atan2(v[2], vt) : v[2] < 0. ? -HalfPi : HalfPi;
813 const double cphi =
cos(phi);
814 const double sphi =
sin(phi);
815 const double ctheta =
cos(theta);
816 const double stheta =
sin(theta);
818 x[0] += cphi * ctheta * d[0] - sphi * d[1] - cphi * stheta * d[2];
819 x[1] += sphi * ctheta * d[0] + cphi * d[1] - sphi * stheta * d[2];
820 x[2] += stheta * d[0] + ctheta * d[2];
823void AvalancheMC::Terminate(
const std::array<double, 3>& x0,
const double t0,
824 std::array<double, 3>& x1,
double& t1)
const {
827 std::array<double, 3> dx = {x1[0] - x0[0], x1[1] - x0[1], x1[2] - x0[2]};
830 const double scale = 1. / ds;
831 for (
unsigned int k = 0; k < 3; ++k) dx[k] *= scale;
835 while (ds > BoundaryDistance) {
838 std::array<double, 3> xm = x1;
839 for (
unsigned int k = 0; k < 3; ++k) xm[k] += dx[k] * ds;
841 double ex = 0., ey = 0., ez = 0.;
843 Medium* medium =
nullptr;
844 m_sensor->
ElectricField(xm[0], xm[1], xm[2], ex, ey, ez, medium, status);
845 if (status == 0 && m_sensor->
IsInArea(xm[0], xm[1], xm[2])) {
852bool AvalancheMC::ComputeGainLoss(
const Particle particle,
853 std::vector<DriftPoint>& driftLine,
854 int& status, std::vector<DriftPoint>& secondaries,
855 const bool semiconductor) {
856 const size_t nPoints = driftLine.size();
857 std::vector<double> alps(nPoints, 0.);
858 std::vector<double> etas(nPoints, 0.);
860 if (!ComputeAlphaEta(particle, driftLine, alps, etas))
return false;
863 Particle other = Particle::Electron;
864 if (particle == Particle::Electron) {
865 other = semiconductor ? Particle::Hole : Particle::Ion;
868 constexpr double probth = 0.01;
870 for (
size_t i = 0; i < nPoints - 1; ++i) {
872 const int nDiv = std::max(
int((alps[i] + etas[i]) / probth), 1);
874 const double p = std::max(alps[i] / nDiv, 0.);
875 const double q = std::max(etas[i] / nDiv, 0.);
879 for (
int j = 0; j < nDiv; ++j) {
891 for (
int k = ne; k--;) {
900 if (other == Particle::Hole) {
902 }
else if (other == Particle::Ion) {
907 for (
int k = 0; k < ni; ++k) {
909 const double f1 = 1. - f0;
911 point.x[0] = f0 * driftLine[i].x[0] + f1 * driftLine[i + 1].x[0];
912 point.x[1] = f0 * driftLine[i].x[1] + f1 * driftLine[i + 1].x[1];
913 point.x[2] = f0 * driftLine[i].x[2] + f1 * driftLine[i + 1].x[2];
914 point.t = f0 * driftLine[i].t + f1 * driftLine[i + 1].t;
915 point.particle = other;
917 secondaries.push_back(std::move(point));
922 status = StatusAttached;
923 if (particle == Particle::Electron) {
925 }
else if (particle == Particle::Hole) {
930 const double f0 = (j + 0.5) / nDiv;
931 const double f1 = 1. - f0;
932 const auto x0 = driftLine[i].x;
933 const auto x1 = driftLine[i + 1].x;
934 driftLine.resize(i + 2);
935 for (
size_t k = 0; k < 3; ++k) {
936 driftLine[i + 1].x[k] = f0 * x0[k] + f1 * x1[k];
938 driftLine[i + 1].t = f0 * driftLine[i].t + f1 * driftLine[i + 1].t;
944 DriftPoint point = driftLine[i + 1];
946 secondaries.push_back(std::move(point));
947 if (particle == Particle::Electron) {
948 m_nElectrons += ne - 1;
949 }
else if (particle == Particle::Hole) {
954 if (status == StatusAttached)
return true;
959bool AvalancheMC::ComputeAlphaEta(
const Particle particle,
960 const std::vector<DriftPoint>& driftLine,
961 std::vector<double>& alps,
962 std::vector<double>& etas)
const {
969 constexpr double tg[6] = {-0.932469514203152028, -0.661209386466264514,
970 -0.238619186083196909, 0.238619186083196909,
971 0.661209386466264514, 0.932469514203152028};
972 constexpr double wg[6] = {0.171324492379170345, 0.360761573048138608,
973 0.467913934572691047, 0.467913934572691047,
974 0.360761573048138608, 0.171324492379170345};
976 const size_t nPoints = driftLine.size();
977 alps.assign(nPoints, 0.);
978 etas.assign(nPoints, 0.);
979 if (nPoints < 2)
return true;
980 bool equilibrate = m_doEquilibration;
982 for (
size_t i = 0; i < nPoints - 1; ++i) {
983 const auto& x0 = driftLine[i].x;
984 const auto& x1 = driftLine[i + 1].x;
986 const std::array<double, 3> del = {x1[0] - x0[0], x1[1] - x0[1],
988 const double dmag = Mag(del);
989 if (dmag < Small)
continue;
990 const double veff = dmag / (driftLine[i + 1].t - driftLine[i].t);
992 std::array<double, 3> vd = {0., 0., 0.};
993 for (
size_t j = 0; j < 6; ++j) {
994 const double f = 0.5 * (1. + tg[j]);
995 std::array<double, 3>
x = x0;
996 for (
size_t k = 0; k < 3; ++k) x[k] += f * del[k];
998 std::array<double, 3> e;
999 std::array<double, 3> b;
1000 Medium* medium =
nullptr;
1001 const int status = GetField(x, e, b, medium);
1005 if (i < nPoints - 2) {
1006 std::cerr << m_className <<
"::ComputeAlphaEta: Got status " << status
1007 <<
" at segment " << j + 1 <<
"/6, drift point " << i + 1
1008 <<
"/" << nPoints <<
".\n";
1014 std::array<double, 3> v;
1015 if (!GetVelocity(particle, medium, x, e, b, v))
continue;
1019 if (particle == Particle::Electron) {
1020 medium->ElectronTownsend(e[0], e[1], e[2], b[0], b[1], b[2], alpha);
1022 medium->HoleTownsend(e[0], e[1], e[2], b[0], b[1], b[2], alpha);
1025 double eta = GetAttachment(particle, medium, x, e, b);
1027 eta = std::abs(eta) * Mag(v) / veff;
1028 equilibrate =
false;
1030 for (
size_t k = 0; k < 3; ++k) vd[k] += wg[j] * v[k];
1031 alps[i] += wg[j] *
alpha;
1032 etas[i] += wg[j] * eta;
1038 const double vdmag = Mag(vd);
1039 if (vdmag * dmag <= 0.) {
1042 const double dinv = del[0] * vd[0] + del[1] * vd[1] + del[2] * vd[2];
1043 scale =
dinv < 0. ? 0. :
dinv / (vdmag * dmag);
1046 alps[i] *= 0.5 * dmag * scale;
1047 etas[i] *= 0.5 * dmag * scale;
1051 if (!equilibrate)
return true;
1052 if (!Equilibrate(alps)) {
1054 std::cerr << m_className <<
"::ComputeAlphaEta:\n Unable to even out "
1055 <<
"alpha steps. Calculation is probably inaccurate.\n";
1059 if (!Equilibrate(etas)) {
1061 std::cerr << m_className <<
"::ComputeAlphaEta:\n Unable to even out "
1062 <<
"eta steps. Calculation is probably inaccurate.\n";
1070bool AvalancheMC::Equilibrate(std::vector<double>& alphas)
const {
1071 const size_t nPoints = alphas.size();
1073 for (
size_t i = 0; i < nPoints - 1; ++i) {
1075 if (alphas[i] >= 0.)
continue;
1077 double sub1 = -0.5 * alphas[i];
1082 for (
size_t j = 0; j < i - 1; ++j) {
1083 if (alphas[i - j] > sub1) {
1084 alphas[i - j] -= sub1;
1089 }
else if (alphas[i - j] > 0.) {
1090 alphas[i] += alphas[i - j];
1091 sub1 -= alphas[i - j];
1096 for (
size_t j = 0; j < nPoints - i - 1; ++j) {
1097 if (alphas[i + j] > sub2) {
1098 alphas[i + j] -= sub2;
1103 }
else if (alphas[i + j] > 0.) {
1104 alphas[i] += alphas[i + j];
1105 sub2 -= alphas[i + j];
1117 for (
size_t j = 0; j < i - 1; ++j) {
1118 if (alphas[i - j] > sub1) {
1119 alphas[i - j] -= sub1;
1124 }
else if (alphas[i - j] > 0.) {
1125 alphas[i] += alphas[i - j];
1126 sub1 -= alphas[i - j];
1133 for (
size_t j = 0; j < nPoints - i - 1; ++j) {
1134 if (alphas[i + j] > sub2) {
1135 alphas[i + j] -= sub2;
1140 }
else if (alphas[i + j] > 0.) {
1141 alphas[i] += alphas[i + j];
1142 sub2 -= alphas[i + j];
1148 if (!done)
return false;
1153void AvalancheMC::ComputeSignal(
1154 const Particle particle,
const double q,
1155 const std::vector<DriftPoint>& driftLine)
const {
1156 const size_t nPoints = driftLine.size();
1157 if (nPoints < 2)
return;
1159 if (m_useWeightingPotential) {
1160 for (
size_t i = 0; i < nPoints - 1; ++i) {
1161 const auto& x0 = driftLine[i].x;
1162 const auto& x1 = driftLine[i + 1].x;
1163 const double t0 = driftLine[i].t;
1164 const double t1 = driftLine[i + 1].t;
1165 m_sensor->
AddSignal(q, t0, t1, x0[0], x0[1], x0[2], x1[0], x1[1], x1[2],
1171 std::vector<double> ts;
1172 std::vector<std::array<double, 3> > xs;
1173 std::vector<std::array<double, 3> > vs;
1174 for (
const auto& p : driftLine) {
1175 std::array<double, 3> e;
1176 std::array<double, 3> b;
1177 Medium* medium =
nullptr;
1178 int status = GetField(p.x, e, b, medium);
1179 if (status != 0)
continue;
1180 std::array<double, 3> v;
1181 if (!GetVelocity(particle, medium, p.x, e, b, v))
continue;
1184 vs.push_back(std::move(v));
1186 m_sensor->
AddSignal(q, ts, xs, vs, {}, m_navg);
1189void AvalancheMC::ComputeInducedCharge(
1190 const double q,
const std::vector<DriftPoint>& driftLine)
const {
1191 if (driftLine.size() < 2)
return;
1192 const auto& x0 = driftLine.front().x;
1193 const auto& x1 = driftLine.back().x;
1197void AvalancheMC::PrintError(
const std::string& fcn,
const std::string& par,
1198 const Particle particle,
1199 const std::array<double, 3>& x)
const {
1200 const std::string ehi = particle == Particle::Electron
1202 : particle == Particle::Hole ?
"hole" :
"ion";
1203 std::cerr << m_className +
"::" + fcn +
": Error calculating " + ehi +
" "
1204 << par +
" at " + PrintVec(x) <<
".\n";
void GetDriftLinePoint(const unsigned int i, double &x, double &y, double &z, double &t) const
Return the coordinates and time of a point along the last drift line.
void EnablePlotting(ViewDrift *view)
Switch on drift line plotting.
void SetTimeSteps(const double d=0.02)
Use fixed-time steps (default 20 ps).
bool AvalancheElectronHole(const double x0, const double y0, const double z0, const double t0)
Simulate an avalanche initiated by an electron-hole pair.
bool DriftIon(const double x0, const double y0, const double z0, const double t0)
Simulate the drift line of an ion from a given starting point.
void SetTimeWindow(const double t0, const double t1)
Define a time interval (only carriers inside the interval are drifted).
void GetIonEndpoint(const unsigned int i, double &x0, double &y0, double &z0, double &t0, double &x1, double &y1, double &z1, double &t1, int &status) const
bool DriftElectron(const double x0, const double y0, const double z0, const double t0)
Simulate the drift line of an electron from a given starting point.
bool ResumeAvalanche(const bool electron=true, const bool hole=true)
void GetElectronEndpoint(const unsigned int i, double &x0, double &y0, double &z0, double &t0, double &x1, double &y1, double &z1, double &t1, int &status) const
void GetHoleEndpoint(const unsigned int i, double &x0, double &y0, double &z0, double &t0, double &x1, double &y1, double &z1, double &t1, int &status) const
AvalancheMC()
Constructor.
void SetSensor(Sensor *s)
Set the sensor.
void SetStepDistanceFunction(double(*f)(double x, double y, double z))
Retrieve the step distance from a user-supplied function.
void SetCollisionSteps(const unsigned int n=100)
bool AvalancheElectron(const double x0, const double y0, const double z0, const double t0, const bool hole=false)
void SetDistanceSteps(const double d=0.001)
Use fixed distance steps (default 10 um).
bool AvalancheHole(const double x0, const double y0, const double z0, const double t0, const bool electron=false)
Simulate an avalanche initiated by a hole at a given starting point.
bool DriftHole(const double x0, const double y0, const double z0, const double t0)
Simulate the drift line of a hole from a given starting point.
virtual bool ElectronVelocity(const double, const double, const double, double &vx, double &vy, double &vz)
Get the electron drift velocity.
virtual bool ElectronAttachment(const double, const double, const double, double &eta)
Get the electron attachment coefficient.
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).
size_t GetNumberOfComponents() const
Get the number of components attached to the sensor.
bool IsInArea(const double x, const double y, const double z)
Check if a point is inside the user area.
Component * GetComponent(const unsigned int i)
Retrieve the pointer to a given component.
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.
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)
void AddInducedCharge(const double q, const double x0, const double y0, const double z0, const double x1, const double y1, const double z1)
Add the induced charge from a charge carrier drift between two points.
Visualize drift lines and tracks.
void NewHoleDriftLine(const size_t np, size_t &id, const float x0, const float y0, const float z0)
void NewIonDriftLine(const size_t np, size_t &id, const float x0, const float y0, const float z0)
void SetDriftLinePoint(const size_t iL, const size_t iP, const float x, const float y, const float z)
void NewElectronDriftLine(const size_t np, size_t &id, const float x0, const float y0, const float z0)
int dinv(const int n, std::vector< std::vector< double > > &a)
Replace square matrix A by its inverse.
double RndmUniform()
Draw a random number uniformly distributed in the range [0, 1).
double RndmUniformPos()
Draw a random number uniformly distributed in the range (0, 1).
double RndmGaussian()
Draw a Gaussian random variate with mean zero and standard deviation one.
DoubleAc cos(const DoubleAc &f)
DoubleAc sin(const DoubleAc &f)
DoubleAc sqrt(const DoubleAc &f)