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]);
33 std::cerr << m_className <<
"::SetSensor: Null pointer.\n";
42 std::cerr << m_className <<
"::EnablePlotting: Null pointer.\n";
50 m_stepModel = StepModel::FixedTime;
52 std::cerr << m_className <<
"::SetTimeSteps:\n "
53 <<
"Step size is too small. Using default (20 ps) instead.\n";
58 std::cout << m_className <<
"::SetTimeSteps:\n"
59 <<
" Step size set to " << d <<
" ns.\n";
65 m_stepModel = StepModel::FixedDistance;
67 std::cerr << m_className <<
"::SetDistanceSteps:\n "
68 <<
"Step size is too small. Using default (10 um) instead.\n";
73 std::cout << m_className <<
"::SetDistanceSteps:\n"
74 <<
" Step size set to " << d <<
" cm.\n";
80 m_stepModel = StepModel::CollisionTime;
82 std::cerr << m_className <<
"::SetCollisionSteps:\n "
83 <<
"Number of collisions set to default value (100).\n";
88 std::cout << m_className <<
"::SetCollisionSteps:\n "
89 <<
"Number of collisions to be skipped set to " << n <<
".\n";
95 double (*f)(
double x,
double y,
double z)) {
98 std::cerr << m_className <<
"::SetStepDistanceFunction: Null pointer.\n";
102 m_stepModel = StepModel::UserDistance;
106 if (fabs(t1 - t0) < Small) {
107 std::cerr << m_className <<
"::SetTimeWindow:\n"
108 <<
" Time interval must be greater than zero.\n";
112 m_tMin = std::min(t0, t1);
113 m_tMax = std::max(t0, t1);
114 m_hasTimeWindow =
true;
118 double& z,
double& t)
const {
119 if (i >= m_drift.size()) {
120 std::cerr << m_className <<
"::GetDriftLinePoint: Index out of range.\n";
131 double& z0,
double& t0,
double& x1,
132 double& y1,
double& z1,
double& t1,
134 if (i >= m_endpointsHoles.size()) {
135 std::cerr << m_className <<
"::GetHoleEndpoint: Index out of range.\n";
139 x0 = m_endpointsHoles[i].x0[0];
140 y0 = m_endpointsHoles[i].x0[1];
141 z0 = m_endpointsHoles[i].x0[2];
142 t0 = m_endpointsHoles[i].t0;
143 x1 = m_endpointsHoles[i].x1[0];
144 y1 = m_endpointsHoles[i].x1[1];
145 z1 = m_endpointsHoles[i].x1[2];
146 t1 = m_endpointsHoles[i].t1;
147 status = m_endpointsHoles[i].status;
151 double& z0,
double& t0,
double& x1,
double& y1,
152 double& z1,
double& t1,
int& status)
const {
153 if (i >= m_endpointsIons.size()) {
154 std::cerr << m_className <<
"::GetIonEndpoint: Index out of range.\n";
158 x0 = m_endpointsIons[i].x0[0];
159 y0 = m_endpointsIons[i].x0[1];
160 z0 = m_endpointsIons[i].x0[2];
161 t0 = m_endpointsIons[i].t0;
162 x1 = m_endpointsIons[i].x1[0];
163 y1 = m_endpointsIons[i].x1[1];
164 z1 = m_endpointsIons[i].x1[2];
165 t1 = m_endpointsIons[i].t1;
166 status = m_endpointsIons[i].status;
170 double& y0,
double& z0,
double& t0,
171 double& x1,
double& y1,
double& z1,
172 double& t1,
int& status)
const {
173 if (i >= m_endpointsElectrons.size()) {
174 std::cerr << m_className <<
"::GetElectronEndpoint: Index out of range.\n";
178 x0 = m_endpointsElectrons[i].x0[0];
179 y0 = m_endpointsElectrons[i].x0[1];
180 z0 = m_endpointsElectrons[i].x0[2];
181 t0 = m_endpointsElectrons[i].t0;
182 x1 = m_endpointsElectrons[i].x1[0];
183 y1 = m_endpointsElectrons[i].x1[1];
184 z1 = m_endpointsElectrons[i].x1[2];
185 t1 = m_endpointsElectrons[i].t1;
186 status = m_endpointsElectrons[i].status;
190 const double z0,
const double t0) {
192 std::cerr << m_className <<
"::DriftElectron: Sensor is not defined.\n";
196 m_endpointsElectrons.clear();
197 m_endpointsHoles.clear();
198 m_endpointsIons.clear();
204 return DriftLine(x0, y0, z0, t0, Particle::Electron);
210 std::cerr << m_className <<
"::DriftHole: Sensor is not defined.\n";
214 m_endpointsElectrons.clear();
215 m_endpointsHoles.clear();
216 m_endpointsIons.clear();
222 return DriftLine(x0, y0, z0, t0, Particle::Hole);
228 std::cerr << m_className <<
"::DriftIon: Sensor is not defined.\n";
232 m_endpointsElectrons.clear();
233 m_endpointsHoles.clear();
234 m_endpointsIons.clear();
240 return DriftLine(x0, y0, z0, t0, Particle::Ion);
243bool AvalancheMC::DriftLine(
const double xi,
const double yi,
const double zi,
244 const double ti,
const Particle particle,
251 std::array<double, 3> x0 = {xi, yi, zi};
252 std::array<double, 3> e0 = {0., 0., 0.};
253 std::array<double, 3> b0 = {0., 0., 0.};
254 Medium* medium =
nullptr;
255 int status = GetField(x0, e0, b0, medium);
257 std::cerr << m_className +
"::DriftLine: "
258 << PrintVec(x0) +
" is not in a valid drift region.\n";
262 if (m_hasTimeWindow && (t0 < m_tMin || t0 > m_tMax)) {
263 status = StatusOutsideTimeWindow;
264 std::cerr << m_className +
"::DriftLine: " << t0
265 <<
" is outside the time window.\n";
268 if (status != 0)
return false;
270 AddPoint(x0, t0, 0, 0, 0, m_drift);
272 std::cout << m_className +
"::DriftLine: Starting at "
273 << PrintVec(x0) +
".\n";
276 const bool semiconductor = medium->IsSemiconductor();
278 while (0 == status) {
279 constexpr double tol = 1.e-10;
281 const double emag = Mag(e0);
282 if (emag < tol && !m_useDiffusion) {
283 std::cerr << m_className +
"::DriftLine: Too small electric field at "
284 << PrintVec(x0) +
".\n";
285 status = StatusCalculationAbandoned;
289 std::array<double, 3> v0;
290 if (!GetVelocity(particle, medium, x0, e0, b0, v0)) {
291 status = StatusCalculationAbandoned;
292 std::cerr << m_className +
"::DriftLine: Abandoning the calculation.\n";
297 double vmag = Mag(v0);
298 if (vmag < tol && !m_useDiffusion) {
299 std::cerr << m_className +
"::DriftLine: Too small drift velocity at "
300 << PrintVec(x0) +
".\n";
301 status = StatusCalculationAbandoned;
306 std::array<double, 3> x1 = x0;
309 if (vmag < tol || emag < tol) {
311 const double mu = GetMobility(particle, medium);
313 std::cerr << m_className +
"::DriftLine: Invalid mobility.\n";
314 status = StatusCalculationAbandoned;
318 const double dif = mu * BoltzmannConstant * medium->GetTemperature();
320 switch (m_stepModel) {
321 case StepModel::FixedTime:
322 sigma =
sqrt(2. * dif * m_tMc);
325 case StepModel::FixedDistance:
328 case StepModel::CollisionTime: {
330 const double vth = SpeedOfLight *
sqrt(
331 2 * BoltzmannConstant * medium->GetTemperature() / ElectronMass);
332 sigma = m_nMc * dif / vth;
335 case StepModel::UserDistance:
336 sigma = m_fStep(x0[0], x0[1], x0[2]);
339 std::cerr << m_className +
"::DriftLine: Unknown stepping model.\n";
340 status = StatusCalculationAbandoned;
343 if (status != 0)
break;
344 if (m_stepModel != StepModel::FixedTime) {
345 t1 += sigma * sigma / (2 * dif);
347 for (
unsigned int i = 0; i < 3; ++i) x1[i] +=
RndmGaussian(0., sigma);
352 constexpr double c1 = ElectronMass / (SpeedOfLight * SpeedOfLight);
353 switch (m_stepModel) {
354 case StepModel::FixedTime:
357 case StepModel::FixedDistance:
360 case StepModel::CollisionTime:
363 case StepModel::UserDistance:
364 dt = m_fStep(x0[0], x0[1], x0[2]) / vmag;
367 std::cerr << m_className +
"::DriftLine: Unknown stepping model.\n";
368 status = StatusCalculationAbandoned;
371 if (status != 0)
break;
373 double difl = 0., dift = 0.;
374 if (m_useDiffusion) {
376 if (!GetDiffusion(particle, medium, e0, b0, difl, dift)) {
377 PrintError(
"DriftLine",
"diffusion", particle, x0);
378 status = StatusCalculationAbandoned;
381 if (m_stepModel != StepModel::FixedTime) {
382 const double ds = vmag * dt;
383 const double dif = std::max(difl, dift);
384 if (dif * dif > ds) {
385 dt = ds * ds / (dif * dif * vmag);
390 std::array<double, 3> v1 = v0;
392 StepRKF(particle, x0, v0, dt, x1, v1, status);
395 for (
unsigned int k = 0; k < 3; ++k) x1[k] += dt * v0[k];
398 if (m_useDiffusion) AddDiffusion(
sqrt(vmag * dt), difl, dift, x1, v1);
402 std::cout << m_className +
"::DriftLine: Next point: "
403 << PrintVec(x1) +
".\n";
407 status = GetField(x1, e0, b0, medium);
408 if (status == StatusLeftDriftMedium || status == StatusLeftDriftArea) {
411 Terminate(x0, t0, x1, t1);
413 std::cout << m_className +
"::DriftLine: Left drift region at "
414 << PrintVec(x1) +
".\n";
417 AddPoint(x1, t1, 0, 0, 0, m_drift);
421 std::array<double, 3> xc = x0;
423 if (m_sensor->
IsWireCrossed(x0[0], x0[1], x0[2], x1[0], x1[1], x1[2],
424 xc[0], xc[1], xc[2],
false, rc)) {
426 std::cout << m_className +
"::DriftLine: Hit a wire at "
427 << PrintVec(xc) +
".\n";
429 status = StatusLeftDriftMedium;
431 std::array<double, 3> dc = {xc[0] - x0[0], xc[1] - x0[1], xc[2] - x0[2]};
432 std::array<double, 3> d1 = {x1[0] - x0[0], x1[1] - x0[1], x1[2] - x0[2]};
433 const double tc = t0 + (t1 - t0) * Mag(dc) / Mag(d1);
435 AddPoint(xc, tc, 0, 0, 0, m_drift);
440 if (m_hasTimeWindow && (t1 < m_tMin || t1 > m_tMax)) {
441 status = StatusOutsideTimeWindow;
444 AddPoint(x1, t1, 0, 0, 0, m_drift);
451 unsigned int nElectronsOld = m_nElectrons;
452 unsigned int nHolesOld = m_nHoles;
453 unsigned int nIonsOld = m_nIons;
455 if ((particle == Particle::Electron || particle == Particle::Hole) &&
456 (aval || m_useAttachment) &&
457 (m_sizeCut == 0 || m_nElectrons < m_sizeCut)) {
458 ComputeGainLoss(particle, m_drift, status, semiconductor);
459 if (status == StatusAttached && m_debug) {
460 std::cout << m_className +
"::DriftLine: Attached at "
461 << PrintVec(m_drift.back().x) +
".\n";
466 std::cout << m_className <<
"::DriftLine: Stopped at "
467 << PrintVec(m_drift.back().x) +
".\n";
471 endPoint.x0 = {xi, yi, zi};
473 endPoint.x1 = m_drift.back().x;
474 endPoint.t1 = m_drift.back().t;
475 endPoint.status = status;
476 if (particle == Particle::Electron) {
477 m_endpointsElectrons.push_back(std::move(endPoint));
478 }
else if (particle == Particle::Hole) {
479 m_endpointsHoles.push_back(std::move(endPoint));
480 }
else if (particle == Particle::Ion) {
481 m_endpointsIons.push_back(std::move(endPoint));
485 const int nNewElectrons = m_nElectrons - nElectronsOld;
486 const int nNewHoles = m_nHoles - nHolesOld;
487 const int nNewIons = m_nIons - nIonsOld;
488 std::cout << m_className <<
"::DriftLine: Produced\n"
489 <<
" " << nNewElectrons <<
" electrons,\n"
490 <<
" " << nNewHoles <<
" holes, and\n"
491 <<
" " << nNewIons <<
" ions.\n";
495 const double scale = particle == Particle::Electron ? -m_scaleE :
496 particle == Particle::Hole ? m_scaleH : m_scaleI;
497 if (m_doSignal) ComputeSignal(particle, scale, m_drift);
498 if (m_doInducedCharge) ComputeInducedCharge(scale, m_drift);
501 if (m_viewer && !m_drift.empty()) {
502 const unsigned int nPoints = m_drift.size();
505 if (particle == Particle::Electron) {
507 }
else if (particle == Particle::Hole) {
513 for (
unsigned int i = 0; i < nPoints; ++i) {
514 const auto&
x = m_drift[i].x;
519 if (status == StatusCalculationAbandoned)
return false;
524 const double z0,
const double t0,
526 return Avalanche(x0, y0, z0, t0, 1, 0, 0,
true, holes);
530 const double z0,
const double t0,
531 const bool electrons) {
532 return Avalanche(x0, y0, z0, t0, 0, 1, 0, electrons,
true);
536 const double z0,
const double t0) {
537 return Avalanche(x0, y0, z0, t0, 1, 1, 0,
true,
true);
540bool AvalancheMC::Avalanche(
const double x0,
const double y0,
const double z0,
541 const double t0,
const unsigned int ne0,
542 const unsigned int nh0,
const unsigned int ni0,
543 const bool withElectrons,
const bool withHoles) {
551 m_endpointsElectrons.clear();
552 m_endpointsHoles.clear();
553 m_endpointsIons.clear();
557 std::cerr << m_className <<
"::Avalanche: Sensor is not defined.\n";
562 std::vector<DriftPoint> aval;
563 std::array<double, 3> xi = {x0, y0, z0};
564 AddPoint(xi, t0, ne0, nh0, ni0, aval);
570 if (!withHoles && !withElectrons) {
571 std::cerr << m_className +
"::Avalanche: "
572 <<
"Neither electron nor hole/ion component requested.\n";
575 std::vector<DriftPoint> newAval;
576 while (!aval.empty()) {
577 for (
const auto& point : aval) {
580 const unsigned int ne = point.ne;
581 for (
unsigned int i = 0; i < ne; ++i) {
583 if (!DriftLine(point.x[0], point.x[1], point.x[2], point.t,
584 Particle::Electron,
true)) {
588 const unsigned int nPoints = m_drift.size();
589 for (
unsigned int j = 0; j < nPoints - 2; ++j) {
590 const auto& p = m_drift[j];
591 if (p.ne == 0 && p.nh == 0 && p.ni == 0)
continue;
593 AddPoint(m_drift[j + 1].x, m_drift[j + 1].t, p.ne, p.nh, p.ni,
601 const unsigned int ni = point.ni;
602 for (
unsigned int i = 0; i < ni; ++i) {
604 DriftLine(point.x[0], point.x[1], point.x[2], point.t,
605 Particle::Ion,
false);
609 const unsigned int nh = point.nh;
610 for (
unsigned int i = 0; i < nh; ++i) {
612 if (!DriftLine(point.x[0], point.x[1], point.x[2], point.t,
613 Particle::Hole,
true)) {
617 const unsigned int nPoints = m_drift.size();
618 for (
unsigned int j = 0; j < nPoints - 1; ++j) {
619 const auto& p = m_drift[j];
620 if (p.ne == 0 && p.nh == 0 && p.ni == 0)
continue;
622 AddPoint(m_drift[j + 1].x, m_drift[j + 1].t, p.ne, p.nh, p.ni,
634int AvalancheMC::GetField(
const std::array<double, 3>& x,
635 std::array<double, 3>& e, std::array<double, 3>& b,
636 Medium*& medium)
const {
641 m_sensor->
ElectricField(x[0], x[1], x[2], e[0], e[1], e[2], medium, status);
643 if (status != 0 || !medium)
return StatusLeftDriftMedium;
645 if (!m_sensor->
IsInArea(x[0], x[1], x[2]))
return StatusLeftDriftArea;
649 m_sensor->
MagneticField(x[0], x[1], x[2], b[0], b[1], b[2], status);
650 for (
unsigned int k = 0; k < 3; ++k) b[k] *= Tesla2Internal;
655double AvalancheMC::GetMobility(
const Particle particle, Medium* medium)
const {
657 if (particle == Particle::Electron) {
658 return medium->ElectronMobility();
659 }
else if (particle == Particle::Hole) {
660 return medium->HoleMobility();
661 }
else if (particle == Particle::Ion) {
662 return medium->IonMobility();
667bool AvalancheMC::GetVelocity(
const Particle particle, Medium* medium,
668 const std::array<double, 3>& x,
669 const std::array<double, 3>& e,
670 const std::array<double, 3>& b,
671 std::array<double, 3>& v)
const {
673 if (m_useTcadVelocity && particle != Particle::Ion) {
676 for (
unsigned int i = 0; i < nComponents; ++i) {
678 if (!cmp->IsVelocityActive())
continue;
681 if (particle == Particle::Electron) {
683 }
else if (particle == Particle::Hole) {
684 cmp->HoleVelocity(x[0], x[1], x[2], v[0], v[1], v[2], m, status);
687 PrintError(
"GetVelocity",
"velocity", particle, x);
692 std::cout << m_className <<
"::GetVelocity: TCAD velocity at "
693 << PrintVec(x) <<
" = " << PrintVec(v) <<
"\n";
699 if (particle == Particle::Electron) {
700 ok = medium->ElectronVelocity(e[0], e[1], e[2], b[0], b[1], b[2],
702 }
else if (particle == Particle::Hole) {
703 ok = medium->HoleVelocity(e[0], e[1], e[2], b[0], b[1], b[2],
705 }
else if (particle == Particle::Ion) {
706 ok = medium->IonVelocity(e[0], e[1], e[2], b[0], b[1], b[2],
710 PrintError(
"GetVelocity",
"velocity", particle, x);
714 std::cout << m_className <<
"::GetVelocity: Velocity at "
715 << PrintVec(x) <<
" = " << PrintVec(v) <<
"\n";
720bool AvalancheMC::GetDiffusion(
const Particle particle, Medium* medium,
721 const std::array<double, 3>& e,
722 const std::array<double, 3>& b,
723 double& dl,
double& dt)
const {
727 if (particle == Particle::Electron) {
728 ok = medium->ElectronDiffusion(e[0], e[1], e[2], b[0], b[1], b[2], dl, dt);
729 }
else if (particle == Particle::Hole) {
730 ok = medium->HoleDiffusion(e[0], e[1], e[2], b[0], b[1], b[2], dl, dt);
731 }
else if (particle == Particle::Ion) {
732 ok = medium->IonDiffusion(e[0], e[1], e[2], b[0], b[1], b[2], dl, dt);
737double AvalancheMC::GetAttachment(
const Particle particle, Medium* medium,
738 const std::array<double, 3>& x,
739 const std::array<double, 3>& e,
740 const std::array<double, 3>& b)
const {
743 if (m_useTcadTrapping) {
745 for (
unsigned int i = 0; i < nComponents; ++i) {
747 if (!cmp->IsTrapActive())
continue;
748 if (particle == Particle::Electron) {
751 cmp->HoleAttachment(x[0], x[1], x[2], eta);
756 if (particle == Particle::Electron) {
757 medium->ElectronAttachment(e[0], e[1], e[2], b[0], b[1], b[2], eta);
759 medium->HoleAttachment(e[0], e[1], e[2], b[0], b[1], b[2], eta);
764void AvalancheMC::StepRKF(
const Particle particle,
765 const std::array<double, 3>& x0,
766 const std::array<double, 3>& v0,
const double dt,
767 std::array<double, 3>& xf, std::array<double, 3>& vf,
771 constexpr double ci0 = 214. / 891.;
772 constexpr double ci1 = 1. / 33.;
773 constexpr double ci2 = 650. / 891.;
774 constexpr double beta10 = 1. / 4.;
775 constexpr double beta20 = -189. / 800.;
776 constexpr double beta21 = 729. / 800.;
780 for (
unsigned int k = 0; k < 3; ++k) {
781 xf[k] = x0[k] + dt * beta10 * v0[k];
783 std::array<double, 3> e;
784 std::array<double, 3> b;
785 Medium* medium =
nullptr;
786 status = GetField(xf, e, b, medium);
787 if (status != 0)
return;
790 std::array<double, 3> v1;
791 if (!GetVelocity(particle, medium, xf, e, b, v1)) {
792 status = StatusCalculationAbandoned;
797 for (
unsigned int k = 0; k < 3; ++k) {
798 xf[k] = x0[k] + dt * (beta20 * v0[k] + beta21 * v1[k]);
800 status = GetField(xf, e, b, medium);
801 if (status != 0)
return;
804 std::array<double, 3> v2;
805 if (!GetVelocity(particle, medium, xf, e, b, v2)) {
806 status = StatusCalculationAbandoned;
811 for (
unsigned int k = 0; k < 3; ++k) {
812 vf[k] = ci0 * v0[k] + ci1 * v1[k] + ci2 * v2[k];
816void AvalancheMC::AddDiffusion(
const double step,
817 const double dl,
const double dt,
818 std::array<double, 3>& x,
819 const std::array<double, 3>& v)
const {
821 const std::array<double, 3> d = {step *
RndmGaussian(0., dl),
825 std::cout << m_className <<
"::AddDiffusion: Adding diffusion step "
826 << PrintVec(d) <<
"\n";
829 const double vt =
sqrt(v[0] * v[0] + v[1] * v[1]);
830 const double phi = vt > Small ? atan2(v[1], v[0]) : 0.;
831 const double theta = vt > Small ? atan2(v[2], vt) : v[2] < 0. ? -HalfPi : HalfPi;
832 const double cphi =
cos(phi);
833 const double sphi =
sin(phi);
834 const double ctheta =
cos(theta);
835 const double stheta =
sin(theta);
837 x[0] += cphi * ctheta * d[0] - sphi * d[1] - cphi * stheta * d[2];
838 x[1] += sphi * ctheta * d[0] + cphi * d[1] - sphi * stheta * d[2];
839 x[2] += stheta * d[0] + ctheta * d[2];
842void AvalancheMC::Terminate(
const std::array<double, 3>& x0,
const double t0,
843 std::array<double, 3>& x1,
double& t1)
const {
846 std::array<double, 3> dx = {x1[0] - x0[0], x1[1] - x0[1], x1[2] - x0[2]};
849 const double scale = 1. / ds;
850 for (
unsigned int k = 0; k < 3; ++k) dx[k] *= scale;
854 while (ds > BoundaryDistance) {
857 std::array<double, 3> xm = x1;
858 for (
unsigned int k = 0; k < 3; ++k) xm[k] += dx[k] * ds;
860 double ex = 0., ey = 0., ez = 0.;
862 Medium* medium =
nullptr;
863 m_sensor->
ElectricField(xm[0], xm[1], xm[2], ex, ey, ez, medium, status);
864 if (status == 0 && m_sensor->
IsInArea(xm[0], xm[1], xm[2])) {
871bool AvalancheMC::ComputeGainLoss(
const Particle particle,
872 std::vector<DriftPoint>& driftLine,
int& status,
873 const bool semiconductor) {
875 const unsigned int nPoints = driftLine.size();
876 std::vector<double> alps(nPoints, 0.);
877 std::vector<double> etas(nPoints, 0.);
879 if (!ComputeAlphaEta(particle, driftLine, alps, etas))
return false;
882 constexpr double probth = 0.01;
885 for (
unsigned int i = 0; i < nPoints - 1; ++i) {
890 const int nDiv = std::max(
int((alps[i] + etas[i]) / probth), 1);
892 const double p = std::max(alps[i] / nDiv, 0.);
893 const double q = std::max(etas[i] / nDiv, 0.);
898 for (
int j = 0; j < nDiv; ++j) {
907 for (
int k = ne; k--;) {
917 status = StatusAttached;
918 if (particle == Particle::Electron) {
920 }
else if (particle == Particle::Hole) {
925 driftLine.resize(i + 2);
926 driftLine[i + 1].x[0] = 0.5 * (driftLine[i].x[0] + driftLine[i + 1].x[0]);
927 driftLine[i + 1].x[1] = 0.5 * (driftLine[i].x[1] + driftLine[i + 1].x[1]);
928 driftLine[i + 1].x[2] = 0.5 * (driftLine[i].x[2] + driftLine[i + 1].x[2]);
935 if (particle == Particle::Electron) {
936 driftLine[i].ne = ne - 1;
937 m_nElectrons += ne - 1;
938 }
else if (particle == Particle::Hole) {
939 driftLine[i].nh = ne - 1;
942 driftLine[i].ni = ne - 1;
946 if (particle == Particle::Electron) {
948 driftLine[i].ni = ni;
951 driftLine[i].nh = ni;
955 driftLine[i].ne = ni;
960 if (status == StatusAttached)
return true;
965bool AvalancheMC::ComputeAlphaEta(
const Particle particle,
966 const std::vector<DriftPoint>& driftLine,
967 std::vector<double>& alps,
968 std::vector<double>& etas)
const {
976 constexpr double tg[6] = {-0.932469514203152028, -0.661209386466264514,
977 -0.238619186083196909, 0.238619186083196909,
978 0.661209386466264514, 0.932469514203152028};
979 constexpr double wg[6] = {0.171324492379170345, 0.360761573048138608,
980 0.467913934572691047, 0.467913934572691047,
981 0.360761573048138608, 0.171324492379170345};
983 const unsigned int nPoints = driftLine.size();
984 alps.assign(nPoints, 0.);
985 etas.assign(nPoints, 0.);
986 if (nPoints < 2)
return true;
988 for (
unsigned int i = 0; i < nPoints - 1; ++i) {
989 const auto& x0 = driftLine[i].x;
990 const auto& x1 = driftLine[i + 1].x;
992 const std::array<double, 3> del = {x1[0] - x0[0], x1[1] - x0[1],
994 const double delmag = Mag(del);
995 if (delmag < Small)
continue;
997 std::array<double, 3> vd = {0., 0., 0.};
998 for (
unsigned int j = 0; j < 6; ++j) {
999 const double f = 0.5 * (1. + tg[j]);
1000 std::array<double, 3>
x = x0;
1001 for (
unsigned int k = 0; k < 3; ++k) x[k] += f * del[k];
1003 std::array<double, 3> e;
1004 std::array<double, 3> b;
1005 Medium* medium =
nullptr;
1006 const int status = GetField(x, e, b, medium);
1010 if (i < nPoints - 2) {
1011 std::cerr << m_className <<
"::ComputeAlphaEta: Got status " << status
1012 <<
" at segment " << j + 1 <<
"/6, drift point " << i + 1
1013 <<
"/" << nPoints <<
".\n";
1019 std::array<double, 3> v;
1020 if (!GetVelocity(particle, medium, x, e, b, v))
continue;
1023 if (particle == Particle::Electron) {
1024 medium->ElectronTownsend(e[0], e[1], e[2], b[0], b[1], b[2], alpha);
1026 medium->HoleTownsend(e[0], e[1], e[2], b[0], b[1], b[2], alpha);
1028 const double eta = GetAttachment(particle, medium, x, e, b);
1029 for (
unsigned int k = 0; k < 3; ++k) vd[k] += wg[j] * v[k];
1030 alps[i] += wg[j] *
alpha;
1031 etas[i] += wg[j] * eta;
1036 if (m_doEquilibration) {
1037 const double vdmag = Mag(vd);
1038 if (vdmag * delmag <= 0.) {
1041 const double dinv = del[0] * vd[0] + del[1] * vd[1] + del[2] * vd[2];
1042 scale =
dinv < 0. ? 0. :
dinv / (vdmag * delmag);
1045 alps[i] *= 0.5 * delmag * scale;
1046 etas[i] *= 0.5 * delmag * scale;
1050 if (!m_doEquilibration)
return true;
1051 if (!Equilibrate(alps)) {
1053 std::cerr << m_className <<
"::ComputeAlphaEta:\n Unable to even out "
1054 <<
"alpha steps. Calculation is probably inaccurate.\n";
1058 if (!Equilibrate(etas)) {
1060 std::cerr << m_className <<
"::ComputeAlphaEta:\n Unable to even out "
1061 <<
"eta steps. Calculation is probably inaccurate.\n";
1069bool AvalancheMC::Equilibrate(std::vector<double>& alphas)
const {
1070 const unsigned int nPoints = alphas.size();
1072 for (
unsigned int i = 0; i < nPoints - 1; ++i) {
1074 if (alphas[i] >= 0.)
continue;
1076 double sub1 = -0.5 * alphas[i];
1081 for (
unsigned int j = 0; j < i - 1; ++j) {
1082 if (alphas[i - j] > sub1) {
1083 alphas[i - j] -= sub1;
1088 }
else if (alphas[i - j] > 0.) {
1089 alphas[i] += alphas[i - j];
1090 sub1 -= alphas[i - j];
1095 for (
unsigned int j = 0; j < nPoints - i - 1; ++j) {
1096 if (alphas[i + j] > sub2) {
1097 alphas[i + j] -= sub2;
1102 }
else if (alphas[i + j] > 0.) {
1103 alphas[i] += alphas[i + j];
1104 sub2 -= alphas[i + j];
1116 for (
unsigned int j = 0; j < i - 1; ++j) {
1117 if (alphas[i - j] > sub1) {
1118 alphas[i - j] -= sub1;
1123 }
else if (alphas[i - j] > 0.) {
1124 alphas[i] += alphas[i - j];
1125 sub1 -= alphas[i - j];
1132 for (
unsigned int j = 0; j < nPoints - i - 1; ++j) {
1133 if (alphas[i + j] > sub2) {
1134 alphas[i + j] -= sub2;
1139 }
else if (alphas[i + j] > 0.) {
1140 alphas[i] += alphas[i + j];
1141 sub2 -= alphas[i + j];
1147 if (!done)
return false;
1152void AvalancheMC::ComputeSignal(
const Particle particle,
1153 const double q,
const std::vector<DriftPoint>& driftLine)
const {
1155 const unsigned int nPoints = driftLine.size();
1156 if (nPoints < 2)
return;
1158 if (m_useWeightingPotential) {
1159 for (
unsigned int i = 0; i < nPoints - 1; ++i) {
1160 const auto& x0 = driftLine[i].x;
1161 const auto& x1 = driftLine[i + 1].x;
1162 const double t0 = driftLine[i].t;
1163 const double t1 = driftLine[i + 1].t;
1164 m_sensor->
AddSignal(q, t0, t1, x0[0], x0[1], x0[2],
1165 x1[0], x1[1], x1[2],
false,
true);
1170 std::vector<double> ts;
1171 std::vector<std::array<double, 3> > xs;
1172 std::vector<std::array<double, 3> > vs;
1173 for (
const auto& p : driftLine) {
1174 std::array<double, 3> e;
1175 std::array<double, 3> b;
1176 Medium* medium =
nullptr;
1177 int status = GetField(p.x, e, b, medium);
1178 if (status != 0)
continue;
1179 std::array<double, 3> v;
1180 if (!GetVelocity(particle, medium, p.x, e, b, v))
continue;
1183 vs.push_back(std::move(v));
1185 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 {
1201 const std::string ehi = particle == Particle::Electron ?
"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)
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.
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)
Simulate an avalanche initiated by an electron at a given starting point.
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 void ElectronVelocity(const double, const double, const double, double &vx, double &vy, double &vz, Medium *&, int &status)
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).
bool IsInArea(const double x, const double y, const double z)
Check if a point is inside the user area.
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.
unsigned int GetNumberOfComponents() const
Get the number of components attached to the sensor.
ComponentBase * GetComponent(const unsigned int i)
Retrieve the pointer to a given component.
Visualize drift lines and tracks.
void NewElectronDriftLine(const unsigned int np, int &id, const double x0, const double y0, const double z0)
void SetDriftLinePoint(const unsigned int iL, const unsigned int iP, const double x, const double y, const double z)
void NewHoleDriftLine(const unsigned int np, int &id, const double x0, const double y0, const double z0)
void NewIonDriftLine(const unsigned int np, int &id, const double x0, const double y0, const double 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)