26 const double z,
const double t) {
35std::string PrintVec(
const std::array<double, 3>& x) {
36 return "(" + std::to_string(x[0]) +
", " + std::to_string(x[1]) +
", " +
37 std::to_string(x[2]) +
")";
40double Mag(
const std::array<double, 3>& x) {
41 return sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
44double Dist(
const std::array<double, 3>& x0,
45 const std::array<double, 3>& x1) {
46 std::array<double, 3> d = x1;
47 for (
size_t i = 0; i < 3; ++i) d[i] -= x0[i];
51double Slope(
const double x0,
const double x1) {
52 return (x0 > 0. && x1 > 0.) ?
fabs(x1 - x0) / (x0 + x1) : 0.;
55std::array<double, 3> MidPoint(
const std::array<double, 3>& x0,
56 const std::array<double, 3>& x1) {
57 std::array<double, 3> xm;
58 for (
size_t k = 0; k < 3; ++k) xm[k] = 0.5 * (x0[k] + x1[k]);
70 std::cerr << m_className <<
"::SetSensor: Null pointer.\n";
79 std::cerr << m_className <<
"::EnablePlotting: Null pointer.\n";
87 m_stepModel = StepModel::FixedTime;
89 std::cerr << m_className <<
"::SetTimeSteps:\n "
90 <<
"Step size is too small. Using default (20 ps) instead.\n";
95 std::cout << m_className <<
"::SetTimeSteps:\n"
96 <<
" Step size set to " << d <<
" ns.\n";
102 m_stepModel = StepModel::FixedDistance;
104 std::cerr << m_className <<
"::SetDistanceSteps:\n "
105 <<
"Step size is too small. Using default (10 um) instead.\n";
110 std::cout << m_className <<
"::SetDistanceSteps:\n"
111 <<
" Step size set to " << d <<
" cm.\n";
117 m_stepModel = StepModel::CollisionTime;
119 std::cerr << m_className <<
"::SetCollisionSteps:\n "
120 <<
"Number of collisions set to default value (100).\n";
125 std::cout << m_className <<
"::SetCollisionSteps:\n "
126 <<
"Number of collisions to be skipped set to " << n <<
".\n";
134 std::cerr << m_className <<
"::SetStepDistanceFunction: Null pointer.\n";
138 m_stepModel = StepModel::UserDistance;
142 if (fabs(t1 - t0) < Small) {
143 std::cerr << m_className <<
"::SetTimeWindow:\n"
144 <<
" Time interval must be greater than zero.\n";
148 m_tMin = std::min(t0, t1);
149 m_tMax = std::max(t0, t1);
150 m_hasTimeWindow =
true;
154 double& z0,
double& t0,
double& x1,
155 double& y1,
double& z1,
double& t1,
157 if (i >= m_holes.size()) {
158 std::cerr << m_className <<
"::GetHoleEndpoint: Index out of range.\n";
162 x0 = m_holes[i].path.front().x;
163 y0 = m_holes[i].path.front().y;
164 z0 = m_holes[i].path.front().z;
165 t0 = m_holes[i].path.front().t;
166 x1 = m_holes[i].path.back().x;
167 y1 = m_holes[i].path.back().y;
168 z1 = m_holes[i].path.back().z;
169 t1 = m_holes[i].path.back().t;
170 status = m_holes[i].status;
174 double& z0,
double& t0,
double& x1,
double& y1,
175 double& z1,
double& t1,
int& status)
const {
176 if (i >= m_ions.size()) {
177 std::cerr << m_className <<
"::GetIonEndpoint: Index out of range.\n";
181 x0 = m_ions[i].path.front().x;
182 y0 = m_ions[i].path.front().y;
183 z0 = m_ions[i].path.front().z;
184 t0 = m_ions[i].path.front().t;
185 x1 = m_ions[i].path.back().x;
186 y1 = m_ions[i].path.back().y;
187 z1 = m_ions[i].path.back().z;
188 t1 = m_ions[i].path.back().t;
189 status = m_ions[i].status;
193 double& y0,
double& z0,
double& t0,
194 double& x1,
double& y1,
double& z1,
195 double& t1,
int& status)
const {
196 if (i >= m_electrons.size()) {
197 std::cerr << m_className <<
"::GetElectronEndpoint: Index out of range.\n";
201 x0 = m_electrons[i].path.front().x;
202 y0 = m_electrons[i].path.front().y;
203 z0 = m_electrons[i].path.front().z;
204 t0 = m_electrons[i].path.front().t;
205 x1 = m_electrons[i].path.back().x;
206 y1 = m_electrons[i].path.back().y;
207 z1 = m_electrons[i].path.back().z;
208 t1 = m_electrons[i].path.back().t;
209 status = m_electrons[i].status;
213 const double z0,
const double t0) {
215 std::vector<std::pair<Point, Particle> > particles;
216 particles.emplace_back(std::make_pair(MakePoint(x0, y0, z0, t0),
218 return TransportParticles(particles,
true,
false,
false);
223 std::vector<std::pair<Point, Particle> > particles;
224 particles.emplace_back(std::make_pair(MakePoint(x0, y0, z0, t0),
226 return TransportParticles(particles,
false,
true,
false);
231 std::vector<std::pair<Point, Particle> > particles;
232 particles.emplace_back(std::make_pair(MakePoint(x0, y0, z0, t0),
234 return TransportParticles(particles,
false,
true,
false);
238 const double z0,
const double t0) {
239 std::vector<std::pair<Point, Particle> > particles;
240 particles.emplace_back(std::make_pair(MakePoint(x0, y0, z0, t0),
242 return TransportParticles(particles,
false,
true,
false);
245int AvalancheMC::DriftLine(
const Point& p0,
const Particle particle,
246 std::vector<Point>& path,
247 std::vector<std::pair<Point, Particle> > & secondaries,
248 const bool aval,
const bool signal) {
250 std::array<double, 3> x0 = {p0.x, p0.y, p0.z};
253 std::array<double, 3> e0 = {0., 0., 0.};
254 std::array<double, 3> b0 = {0., 0., 0.};
255 Medium* medium =
nullptr;
256 int status = GetField(x0, e0, b0, medium);
258 std::cerr << m_className +
"::DriftLine: "
259 << PrintVec(x0) +
" is not in a valid drift region.\n";
263 path.emplace_back(MakePoint(x0, t0));
265 std::cout << m_className +
"::DriftLine: Starting at "
266 << PrintVec(x0) +
".\n";
269 if (m_hasTimeWindow && (t0 < m_tMin || t0 > m_tMax)) {
270 if (m_debug) std::cout <<
" Outside the time window.\n";
271 return StatusOutsideTimeWindow;
274 const bool semiconductor = medium->IsSemiconductor();
276 while (0 == status) {
277 constexpr double tol = 1.e-10;
279 const double emag = Mag(e0);
280 if (emag < tol && !m_useDiffusion) {
281 std::cerr << m_className +
"::DriftLine: Too small electric field at "
282 << PrintVec(x0) +
".\n";
283 status = StatusCalculationAbandoned;
287 std::array<double, 3> v0;
288 if (!GetVelocity(particle, medium, x0, e0, b0, v0)) {
289 status = StatusCalculationAbandoned;
294 double vmag = Mag(v0);
295 if (vmag < tol && !m_useDiffusion) {
296 std::cerr << m_className +
"::DriftLine: Too small drift velocity at "
297 << PrintVec(x0) +
".\n";
298 status = StatusCalculationAbandoned;
303 std::array<double, 3> x1 = x0;
306 if (vmag < tol || emag < tol) {
308 const double mu = GetMobility(particle, medium);
310 std::cerr << m_className +
"::DriftLine: Invalid mobility.\n";
311 status = StatusCalculationAbandoned;
315 const double dif = mu * BoltzmannConstant * medium->GetTemperature();
317 switch (m_stepModel) {
318 case StepModel::FixedTime:
319 sigma =
sqrt(2. * dif * m_tMc);
322 case StepModel::FixedDistance:
325 case StepModel::CollisionTime: {
328 SpeedOfLight *
sqrt(2 * BoltzmannConstant *
329 medium->GetTemperature() / ElectronMass);
330 sigma = m_nMc * dif / vth;
332 case StepModel::UserDistance:
333 sigma = m_fStep(x0[0], x0[1], x0[2]);
336 std::cerr << m_className +
"::DriftLine: Unknown stepping model.\n";
337 status = StatusCalculationAbandoned;
340 if (status != 0)
break;
341 if (m_stepModel != StepModel::FixedTime) {
342 t1 += sigma * sigma / (2 * dif);
344 for (
size_t i = 0; i < 3; ++i) x1[i] +=
RndmGaussian(0., sigma);
348 switch (m_stepModel) {
349 case StepModel::FixedTime:
352 case StepModel::FixedDistance:
355 case StepModel::CollisionTime:
357 constexpr double c1 = AtomicMassUnitElectronVolt /
358 (SpeedOfLight * SpeedOfLight);
361 constexpr double c1 = ElectronMass / (SpeedOfLight * SpeedOfLight);
365 case StepModel::UserDistance:
366 dt = m_fStep(x0[0], x0[1], x0[2]) / vmag;
369 std::cerr << m_className +
"::DriftLine: Unknown stepping model.\n";
370 status = StatusCalculationAbandoned;
373 if (status != 0)
break;
375 double difl = 0., dift = 0.;
376 if (m_useDiffusion) {
378 if (!GetDiffusion(particle, medium, e0, b0, difl, dift)) {
379 PrintError(
"DriftLine",
"diffusion", particle, x0);
380 status = StatusCalculationAbandoned;
383 if (m_stepModel != StepModel::FixedTime) {
384 const double ds = vmag * dt;
385 const double dif = std::max(difl, dift);
386 if (dif * dif > ds) {
387 dt = ds * ds / (dif * dif * vmag);
392 for (
size_t k = 0; k < 3; ++k) x1[k] += dt * v0[k];
393 std::array<double, 3> v1 = v0;
394 constexpr unsigned int nMaxIter = 3;
395 for (
unsigned int i = 0; i < nMaxIter; ++i) {
396 status = GetField(x1, e0, b0, medium);
399 x1 = MidPoint(x0, x1);
404 if (!GetVelocity(particle, medium, x1, e0, b0, v1)) {
405 status = StatusCalculationAbandoned;
408 if (Slope(vmag, Mag(v1)) < 0.05)
break;
410 x1 = MidPoint(x0, x1);
413 if (status == StatusCalculationAbandoned)
break;
415 StepRKF(particle, x0, v0, dt, x1, v1, status);
418 if (m_useDiffusion) AddDiffusion(
sqrt(vmag * dt), difl, dift, x1, v1);
421 if (m_debug) std::cout <<
" Next point: " << PrintVec(x1) +
".\n";
424 status = GetField(x1, e0, b0, medium);
425 if (status == StatusLeftDriftMedium || status == StatusLeftDriftArea) {
428 Terminate(x0, t0, x1, t1);
429 if (m_debug) std::cout <<
" Left the drift region.\n";
431 path.emplace_back(MakePoint(x1, t1));
435 std::array<double, 3> xc = x0;
437 if (m_sensor->CrossedWire(x0[0], x0[1], x0[2], x1[0], x1[1], x1[2],
438 xc[0], xc[1], xc[2],
false, rc)) {
439 if (m_debug) std::cout <<
" Hit a wire.\n";
440 status = StatusLeftDriftMedium;
442 double tc = t0 + (t1 - t0) * Dist(x0, xc) / Dist(x0, x1);
443 Terminate(x0, t0, xc, tc);
445 path.emplace_back(MakePoint(xc, tc));
448 if (m_sensor->CrossedPlane(x0[0], x0[1], x0[2], x1[0], x1[1], x1[2],
449 xc[0], xc[1], xc[2])) {
450 if (m_debug) std::cout <<
" Hit a plane.\n";
451 status = StatusHitPlane;
453 double tc = t0 + (t1 - t0) * Dist(x0, xc) / Dist(x0, x1);
454 Terminate(x0, t0, xc, tc);
456 path.emplace_back(MakePoint(xc, tc));
461 if (m_hasTimeWindow && (t1 < m_tMin || t1 > m_tMax)) {
462 status = StatusOutsideTimeWindow;
465 path.emplace_back(MakePoint(x1, t1));
471 if (status == StatusCalculationAbandoned) {
472 std::cerr << m_className +
"::DriftLine: Abandoned the calculation.\n";
476 unsigned int nElectronsOld = m_nElectrons;
477 unsigned int nHolesOld = m_nHoles;
478 unsigned int nIonsOld = m_nIons;
481 (aval || m_useAttachment) &&
482 (m_sizeCut == 0 || m_nElectrons < m_sizeCut)) {
483 ComputeGainLoss(particle, path, status, secondaries, semiconductor);
484 if (status == StatusAttached && m_debug) std::cout <<
" Attached.\n";
488 std::cout <<
" Stopped at "
489 << PrintVec({path.back().x, path.back().y, path.back().z}) +
".\n";
490 const int nNewElectrons = m_nElectrons - nElectronsOld;
491 const int nNewHoles = m_nHoles - nHolesOld;
492 const int nNewIons = m_nIons - nIonsOld;
493 std::cout <<
" Produced\n"
494 <<
" " << nNewElectrons <<
" electrons,\n"
495 <<
" " << nNewHoles <<
" holes, and\n"
496 <<
" " << nNewIons <<
" ions.\n";
510 if (signal) ComputeSignal(particle, scale, path);
511 if (m_doInducedCharge) ComputeInducedCharge(scale, path);
514 if (m_viewer && !path.empty()) {
515 const size_t nP = path.size();
517 const size_t id = m_viewer->NewDriftLine(particle, nP, p0.x, p0.y, p0.z);
519 for (
size_t i = 0; i < nP; ++i) {
520 m_viewer->SetDriftLinePoint(
id, i, path[i].x, path[i].y, path[i].z);
527 const double z0,
const double t0,
529 std::vector<std::pair<Point, Particle> > particles;
530 particles.emplace_back(std::make_pair(MakePoint(x0, y0, z0, t0),
532 return TransportParticles(particles,
true, holes,
true);
536 const double z0,
const double t0,
537 const bool electrons) {
538 std::vector<std::pair<Point, Particle> > particles;
539 particles.emplace_back(std::make_pair(MakePoint(x0, y0, z0, t0),
541 return TransportParticles(particles, electrons,
true,
true);
545 const double z0,
const double t0) {
546 std::vector<std::pair<Point, Particle> > particles;
547 particles.emplace_back(std::make_pair(MakePoint(x0, y0, z0, t0),
549 particles.emplace_back(std::make_pair(MakePoint(x0, y0, z0, t0),
551 return TransportParticles(particles,
true,
true,
true);
559 p.
path = {MakePoint(x, y, z, t)};
560 m_electrons.push_back(std::move(p));
569 p.
path = {MakePoint(x, y, z, t)};
570 m_holes.push_back(std::move(p));
578 p.
path = {MakePoint(x, y, z, t)};
579 m_ions.push_back(std::move(p));
586 std::vector<std::pair<Point, Particle> > particles;
587 for (
const auto& p: m_electrons) {
588 if (p.status == StatusAlive || p.status == StatusOutsideTimeWindow) {
592 for (
const auto& p: m_holes) {
593 if (p.status == StatusAlive || p.status == StatusOutsideTimeWindow) {
594 particles.push_back(std::make_pair(p.path.back(),
Particle::Hole));
597 for (
const auto& p: m_ions) {
598 if (p.status == StatusAlive || p.status == StatusOutsideTimeWindow) {
599 particles.push_back(std::make_pair(p.path.back(),
Particle::Ion));
602 for (
const auto& p: m_negativeIons) {
603 if (p.status == StatusAlive || p.status == StatusOutsideTimeWindow) {
607 return TransportParticles(particles, electrons, holes,
true);
610bool AvalancheMC::TransportParticles(
611 std::vector<std::pair<Point, Particle> >& particles,
612 const bool withE,
const bool withH,
const bool aval) {
622 m_negativeIons.clear();
626 std::cerr << m_className
627 <<
"::TransportParticles: Sensor is not defined.\n";
634 for (
const auto& particle : particles) {
644 if (!withH && !withE) {
645 std::cerr << m_className +
"::TransportParticles: "
646 <<
"Neither electron nor hole/ion component requested.\n";
649 const bool signal = m_doSignal && (m_sensor->GetNumberOfElectrodes() > 0);
650 std::vector<std::pair<Point, Particle> > secondaries;
651 while (!particles.empty()) {
652 for (
const auto& particle : particles) {
655 std::vector<Point> path;
656 const int status = DriftLine(particle.first, particle.second,
657 path, secondaries, aval, signal);
658 if (path.empty())
continue;
661 if (m_storeDriftLines) {
662 p.path = std::move(path);
664 p.path = {path.front(), path.back()};
667 m_electrons.push_back(std::move(p));
669 m_holes.push_back(std::move(p));
671 m_ions.push_back(std::move(p));
673 m_negativeIons.push_back(std::move(p));
675 std::cerr << m_className
676 <<
"::TransportParticles: Unexpected particle type.\n";
679 particles.swap(secondaries);
685int AvalancheMC::GetField(
const std::array<double, 3>& x,
686 std::array<double, 3>& e, std::array<double, 3>& b,
692 m_sensor->MagneticField(x[0], x[1], x[2], b[0], b[1], b[2], status);
694 m_sensor->ElectricField(x[0], x[1], x[2], e[0], e[1], e[2], medium, status);
696 if (status != 0 || !medium)
return StatusLeftDriftMedium;
698 if (!m_sensor->IsInArea(x[0], x[1], x[2]))
return StatusLeftDriftArea;
703double AvalancheMC::GetMobility(
const Particle particle,
Medium* medium)
const {
705 return medium->ElectronMobility();
707 return medium->HoleMobility();
709 return medium->IonMobility();
711 return medium->NegativeIonMobility();
716bool AvalancheMC::GetVelocity(
const Particle particle,
Medium* medium,
717 const std::array<double, 3>& x,
718 const std::array<double, 3>& e,
719 const std::array<double, 3>& b,
720 std::array<double, 3>& v)
const {
723 if (m_useVelocityMap &&
726 const auto nComponents = m_sensor->GetNumberOfComponents();
727 for (
size_t i = 0; i < nComponents; ++i) {
728 auto cmp = m_sensor->GetComponent(i);
729 if (!cmp->HasVelocityMap())
continue;
731 ok = cmp->ElectronVelocity(x[0], x[1], x[2], v[0], v[1], v[2]);
733 ok = cmp->HoleVelocity(x[0], x[1], x[2], v[0], v[1], v[2]);
738 std::cout << m_className <<
"::GetVelocity: Velocity at "
739 << PrintVec(x) <<
" = " << PrintVec(v) <<
"\n";
745 ok = medium->ElectronVelocity(e[0], e[1], e[2], b[0], b[1], b[2],
748 ok = medium->HoleVelocity(e[0], e[1], e[2], b[0], b[1], b[2],
751 ok = medium->IonVelocity(e[0], e[1], e[2], b[0], b[1], b[2],
754 ok = medium->NegativeIonVelocity(e[0], e[1], e[2], b[0], b[1], b[2],
758 PrintError(
"GetVelocity",
"velocity", particle, x);
762 std::cout << m_className <<
"::GetVelocity: Velocity at " << PrintVec(x)
764 <<
" = " << v[0] <<
", " << v[1] <<
", " << v[2] <<
"\n";
769bool AvalancheMC::GetDiffusion(
const Particle particle,
Medium* medium,
770 const std::array<double, 3>& e,
771 const std::array<double, 3>& b,
double& dl,
777 ok = medium->ElectronDiffusion(e[0], e[1], e[2], b[0], b[1], b[2], dl, dt);
779 ok = medium->HoleDiffusion(e[0], e[1], e[2], b[0], b[1], b[2], dl, dt);
781 ok = medium->IonDiffusion(e[0], e[1], e[2], b[0], b[1], b[2], dl, dt);
786double AvalancheMC::GetAttachment(
const Particle particle,
Medium* medium,
787 const std::array<double, 3>& x,
788 const std::array<double, 3>& e,
789 const std::array<double, 3>& b)
const {
791 if (m_useAttachmentMap) {
792 const auto nComponents = m_sensor->GetNumberOfComponents();
793 for (
size_t i = 0; i < nComponents; ++i) {
794 auto cmp = m_sensor->GetComponent(i);
795 if (!cmp->HasAttachmentMap())
continue;
797 if (!cmp->ElectronAttachment(x[0], x[1], x[2], eta))
continue;
799 if (!cmp->HoleAttachment(x[0], x[1], x[2], eta))
continue;
805 medium->ElectronAttachment(e[0], e[1], e[2], b[0], b[1], b[2], eta);
807 medium->HoleAttachment(e[0], e[1], e[2], b[0], b[1], b[2], eta);
812double AvalancheMC::GetTownsend(
const Particle particle,
Medium* medium,
813 const std::array<double, 3>& x,
814 const std::array<double, 3>& e,
815 const std::array<double, 3>& b)
const {
817 if (m_useTownsendMap) {
818 const auto nComponents = m_sensor->GetNumberOfComponents();
819 for (
size_t i = 0; i < nComponents; ++i) {
820 auto cmp = m_sensor->GetComponent(i);
821 if (!cmp->HasTownsendMap())
continue;
823 if (!cmp->ElectronTownsend(x[0], x[1], x[2], alpha))
continue;
825 if (!cmp->HoleTownsend(x[0], x[1], x[2], alpha))
continue;
831 medium->ElectronTownsend(e[0], e[1], e[2], b[0], b[1], b[2], alpha);
833 medium->HoleTownsend(e[0], e[1], e[2], b[0], b[1], b[2], alpha);
838void AvalancheMC::StepRKF(
const Particle particle,
839 const std::array<double, 3>& x0,
840 const std::array<double, 3>& v0,
const double dt,
841 std::array<double, 3>& xf, std::array<double, 3>& vf,
844 constexpr double ci0 = 214. / 891.;
845 constexpr double ci1 = 1. / 33.;
846 constexpr double ci2 = 650. / 891.;
847 constexpr double beta10 = 1. / 4.;
848 constexpr double beta20 = -189. / 800.;
849 constexpr double beta21 = 729. / 800.;
853 for (
size_t k = 0; k < 3; ++k) {
854 xf[k] = x0[k] + dt * beta10 * v0[k];
856 std::array<double, 3> e;
857 std::array<double, 3> b;
858 Medium* medium =
nullptr;
859 status = GetField(xf, e, b, medium);
860 if (status != 0)
return;
863 std::array<double, 3> v1;
864 if (!GetVelocity(particle, medium, xf, e, b, v1)) {
865 status = StatusCalculationAbandoned;
870 for (
size_t k = 0; k < 3; ++k) {
871 xf[k] = x0[k] + dt * (beta20 * v0[k] + beta21 * v1[k]);
873 status = GetField(xf, e, b, medium);
874 if (status != 0)
return;
877 std::array<double, 3> v2;
878 if (!GetVelocity(particle, medium, xf, e, b, v2)) {
879 status = StatusCalculationAbandoned;
884 for (
size_t k = 0; k < 3; ++k) {
885 vf[k] = ci0 * v0[k] + ci1 * v1[k] + ci2 * v2[k];
886 xf[k] = x0[k] + dt * vf[k];
890void AvalancheMC::AddDiffusion(
const double step,
const double dl,
891 const double dt, std::array<double, 3>& x,
892 const std::array<double, 3>& v)
const {
894 const std::array<double, 3> d = {step *
RndmGaussian(0., dl),
898 std::cout << m_className <<
"::AddDiffusion: Adding diffusion step "
899 << PrintVec(d) <<
"\n";
902 const double vt =
sqrt(v[0] * v[0] + v[1] * v[1]);
903 const double phi = vt > Small ? atan2(v[1], v[0]) : 0.;
905 vt > Small ? atan2(v[2], vt) : v[2] < 0. ? -HalfPi : HalfPi;
906 const double cphi =
cos(phi);
907 const double sphi =
sin(phi);
908 const double ctheta =
cos(theta);
909 const double stheta =
sin(theta);
911 x[0] += cphi * ctheta * d[0] - sphi * d[1] - cphi * stheta * d[2];
912 x[1] += sphi * ctheta * d[0] + cphi * d[1] - sphi * stheta * d[2];
913 x[2] += stheta * d[0] + ctheta * d[2];
916void AvalancheMC::Terminate(
const std::array<double, 3>& x0,
const double t0,
917 std::array<double, 3>& x1,
double& t1)
const {
920 std::array<double, 3> dx = {x1[0] - x0[0], x1[1] - x0[1], x1[2] - x0[2]};
923 const double scale = 1. / ds;
924 for (
unsigned int k = 0; k < 3; ++k) dx[k] *= scale;
928 while (ds > BoundaryDistance) {
931 std::array<double, 3> xm = x1;
932 for (
unsigned int k = 0; k < 3; ++k) xm[k] += dx[k] * ds;
934 double ex = 0., ey = 0., ez = 0.;
936 Medium* medium =
nullptr;
937 m_sensor->ElectricField(xm[0], xm[1], xm[2], ex, ey, ez, medium, status);
938 if (status == 0 && m_sensor->IsInArea(xm[0], xm[1], xm[2])) {
945bool AvalancheMC::ComputeGainLoss(
const Particle particle,
946 std::vector<Point>& path,
int& status,
947 std::vector<std::pair<Point, Particle> >& secondaries,
948 const bool semiconductor) {
950 std::vector<double> alps;
951 std::vector<double> etas;
953 if (!ComputeAlphaEta(particle, path, alps, etas))
return false;
960 const size_t nPoints = path.size();
962 for (
size_t i = 0; i < nPoints - 1; ++i) {
966 if (etas[i] < Small) {
971 constexpr double probth = 0.01;
973 const int nDiv = std::max(
int((alps[i] + etas[i]) / probth), 1);
975 const double p = std::max(alps[i] / nDiv, 0.);
976 const double q = std::max(etas[i] / nDiv, 0.);
978 for (
int j = 0; j < nDiv; ++j) {
987 for (
int k = ne; k--;) {
997 status = StatusAttached;
1005 const double f0 = (j + 0.5) / nDiv;
1006 const double f1 = 1. - f0;
1008 path[i + 1].x = f0 * path[i].x + f1 * path[i + 1].x;
1009 path[i + 1].y = f0 * path[i].y + f1 * path[i + 1].y;
1010 path[i + 1].z = f0 * path[i].z + f1 * path[i + 1].z;
1011 path[i + 1].t = f0 * path[i].t + f1 * path[i + 1].t;
1018 for (
int j = 0; j < ne - 1; ++j) {
1019 secondaries.push_back(std::make_pair(path[i + 1], particle));
1022 m_nElectrons += ne - 1;
1036 const double n1 = std::exp(alps[i]) - 1;
1037 const double a1 = n1 > 0. ? 1. / std::log1p(n1) : 0.;
1038 for (
int j = 0; j < ni; ++j) {
1039 const double f1 = n1 > 0. ? a1 * std::log1p(
RndmUniform() * n1) : 0.5;
1040 const double f0 = 1. - f1;
1042 point.
x = f0 * path[i].x + f1 * path[i + 1].x;
1043 point.y = f0 * path[i].y + f1 * path[i + 1].y;
1044 point.z = f0 * path[i].z + f1 * path[i + 1].z;
1045 point.t = f0 * path[i].t + f1 * path[i + 1].t;
1046 secondaries.push_back(std::make_pair(std::move(point), other));
1050 if (status == StatusAttached)
return true;
1055bool AvalancheMC::ComputeAlphaEta(
const Particle particle,
1056 std::vector<Point>& path,
1057 std::vector<double>& alps,
1058 std::vector<double>& etas)
const {
1064 size_t nPoints = path.size();
1065 alps.assign(nPoints, 0.);
1066 etas.assign(nPoints, 0.);
1067 for (
size_t i = 0; i < nPoints; ++i) {
1068 const std::array<double, 3> x0 = {path[i].x, path[i].y, path[i].z};
1069 std::array<double, 3> e0, b0;
1070 Medium* medium =
nullptr;
1071 if (GetField(x0, e0, b0, medium) != 0)
continue;
1072 alps[i] = GetTownsend(particle, medium, x0, e0, b0);
1075 std::vector<Point> pathExt;
1076 for (
size_t i = 0; i < nPoints - 1; ++i) {
1077 pathExt.push_back(path[i]);
1078 if (Slope(alps[i], alps[i + 1]) < 0.5)
continue;
1079 const std::array<double, 3> x0 = {path[i].x, path[i].y, path[i].z};
1080 const std::array<double, 3> x1 = {path[i + 1].x, path[i + 1].y, path[i + 1].z};
1081 auto xm = MidPoint(x0, x1);
1082 std::array<double, 3> em, bm;
1083 Medium* medium =
nullptr;
1084 if (GetField(xm, em, bm, medium) != 0)
continue;
1089 pm.t = 0.5 * (path[i].t + path[i + 1].t);
1090 pathExt.push_back(std::move(pm));
1092 pathExt.push_back(path.back());
1095 nPoints = path.size();
1096 alps.assign(nPoints, 0.);
1097 etas.assign(nPoints, 0.);
1098 if (nPoints < 2)
return true;
1101 constexpr size_t nG = 6;
1105 bool equilibrate = m_doEquilibration;
1107 for (
size_t i = 0; i < nPoints - 1; ++i) {
1108 const std::array<double, 3> x0 = {path[i].x, path[i].y, path[i].z};
1109 const std::array<double, 3> x1 = {path[i + 1].x, path[i + 1].y, path[i + 1].z};
1111 const std::array<double, 3> del = {x1[0] - x0[0], x1[1] - x0[1],
1113 const double dmag = Mag(del);
1114 if (dmag < Small)
continue;
1115 const double veff = dmag / (path[i + 1].t - path[i].t);
1117 std::array<double, 3> vd = {0., 0., 0.};
1118 for (
size_t j = 0; j < nG; ++j) {
1119 const double f = 0.5 * (1. + tg[j]);
1120 std::array<double, 3>
x = x0;
1121 for (
size_t k = 0; k < 3; ++k) x[k] += f * del[k];
1123 std::array<double, 3> e;
1124 std::array<double, 3> b;
1125 Medium* medium =
nullptr;
1126 const int status = GetField(x, e, b, medium);
1130 if (i < nPoints - 2) {
1131 std::cerr << m_className <<
"::ComputeAlphaEta: Got status " << status
1132 <<
" at segment " << j + 1 <<
"/" << nG
1133 <<
", drift point " << i + 1 <<
"/" << nPoints <<
".\n";
1139 std::array<double, 3> v;
1140 if (!GetVelocity(particle, medium, x, e, b, v))
continue;
1142 double alpha = GetTownsend(particle, medium, x, e, b);
1143 double eta = GetAttachment(particle, medium, x, e, b);
1145 eta = std::abs(eta) * Mag(v) / veff;
1146 equilibrate =
false;
1148 for (
size_t k = 0; k < 3; ++k) vd[k] += wg[j] * v[k];
1149 alps[i] += wg[j] *
alpha;
1150 etas[i] += wg[j] * eta;
1156 const double vdmag = Mag(vd);
1157 if (vdmag * dmag <= 0.) {
1160 const double dinv = del[0] * vd[0] + del[1] * vd[1] + del[2] * vd[2];
1161 scale =
dinv < 0. ? 0. :
dinv / (vdmag * dmag);
1164 alps[i] *= 0.5 * dmag * scale;
1165 etas[i] *= 0.5 * dmag * scale;
1169 if (!equilibrate)
return true;
1170 if (!Equilibrate(alps)) {
1172 std::cerr << m_className <<
"::ComputeAlphaEta:\n"
1173 <<
" Unable to even out alpha steps.\n"
1174 <<
" Calculation is probably inaccurate.\n";
1178 if (!Equilibrate(etas)) {
1180 std::cerr << m_className <<
"::ComputeAlphaEta:\n"
1181 <<
" Unable to even out eta steps.\n"
1182 <<
" Calculation is probably inaccurate.\n";
1190bool AvalancheMC::Equilibrate(std::vector<double>& alphas)
const {
1191 const size_t nPoints = alphas.size();
1193 for (
size_t i = 0; i < nPoints - 1; ++i) {
1195 if (alphas[i] >= 0.)
continue;
1197 double sub1 = -0.5 * alphas[i];
1202 for (
size_t j = 0; j < i - 1; ++j) {
1203 if (alphas[i - j] > sub1) {
1204 alphas[i - j] -= sub1;
1209 }
else if (alphas[i - j] > 0.) {
1210 alphas[i] += alphas[i - j];
1211 sub1 -= alphas[i - j];
1216 for (
size_t j = 0; j < nPoints - i - 1; ++j) {
1217 if (alphas[i + j] > sub2) {
1218 alphas[i + j] -= sub2;
1223 }
else if (alphas[i + j] > 0.) {
1224 alphas[i] += alphas[i + j];
1225 sub2 -= alphas[i + j];
1237 for (
size_t j = 0; j < i - 1; ++j) {
1238 if (alphas[i - j] > sub1) {
1239 alphas[i - j] -= sub1;
1244 }
else if (alphas[i - j] > 0.) {
1245 alphas[i] += alphas[i - j];
1246 sub1 -= alphas[i - j];
1253 for (
size_t j = 0; j < nPoints - i - 1; ++j) {
1254 if (alphas[i + j] > sub2) {
1255 alphas[i + j] -= sub2;
1260 }
else if (alphas[i + j] > 0.) {
1261 alphas[i] += alphas[i + j];
1262 sub2 -= alphas[i + j];
1268 if (!done)
return false;
1273void AvalancheMC::ComputeSignal(
1274 const Particle particle,
const double q,
1275 const std::vector<Point>& path)
const {
1276 const size_t nPoints = path.size();
1277 if (nPoints < 2)
return;
1279 if (m_useWeightingPotential) {
1280 for (
size_t i = 0; i < nPoints - 1; ++i) {
1281 const auto& p0 = path[i];
1282 const auto& p1 = path[i + 1];
1283 m_sensor->AddSignal(q, p0.t, p1.t, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z,
1289 std::vector<double> ts;
1290 std::vector<std::array<double, 3> > xs;
1291 std::vector<std::array<double, 3> > vs;
1292 for (
const auto& p : path) {
1293 std::array<double, 3> e;
1294 std::array<double, 3> b;
1295 Medium* medium =
nullptr;
1296 int status = GetField({p.x, p.y, p.z}, e, b, medium);
1297 if (status != 0)
continue;
1298 std::array<double, 3> v;
1299 if (!GetVelocity(particle, medium, {p.x, p.y, p.z}, e, b, v))
continue;
1301 xs.push_back({p.x, p.y, p.z});
1302 vs.push_back(std::move(v));
1304 m_sensor->AddSignal(q, ts, xs, vs, {}, m_navg);
1307void AvalancheMC::ComputeInducedCharge(
1308 const double q,
const std::vector<Point>& path)
const {
1309 if (path.size() < 2)
return;
1310 const auto& p0 = path.front();
1311 const auto& p1 = path.back();
1312 m_sensor->AddInducedCharge(q, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z);
1315void AvalancheMC::PrintError(
const std::string& fcn,
const std::string& par,
1317 const std::array<double, 3>& x)
const {
1321 std::cerr << m_className +
"::" + fcn +
": Error calculating " + ehi +
" "
1322 << par +
" at " + PrintVec(x) <<
".\n";
void EnablePlotting(ViewDrift *view)
Switch on drift line plotting.
bool AvalancheHole(const double x, const double y, const double z, const double t, const bool electron=false)
Simulate an avalanche initiated by a hole at a given starting point.
void SetTimeSteps(const double d=0.02)
Use fixed-time steps (default 20 ps).
void GetHoleEndpoint(const size_t i, double &x0, double &y0, double &z0, double &t0, double &x1, double &y1, double &z1, double &t1, int &status) const
void SetTimeWindow(const double t0, const double t1)
Define a time interval (only carriers inside the interval are drifted).
void AddHole(const double x, const double y, const double z, const double t)
Add a hole to the list of particles to be transported.
bool DriftNegativeIon(const double x, const double y, const double z, const double t)
Simulate the drift line of a negative ion from a given starting point.
bool ResumeAvalanche(const bool electron=true, const bool hole=true)
Resume the simulation from the current set of charge carriers.
void GetIonEndpoint(const size_t i, double &x0, double &y0, double &z0, double &t0, double &x1, double &y1, double &z1, double &t1, int &status) const
bool DriftIon(const double x, const double y, const double z, const double t)
Simulate the drift line of an ion from a given starting point.
void AddElectron(const double x, const double y, const double z, const double t)
Add an electron to the list of particles to be transported.
AvalancheMC()
Default constructor.
bool AvalancheElectron(const double x, const double y, const double z, const double t, const bool hole=false)
void SetSensor(Sensor *s)
Set the sensor.
bool DriftHole(const double x, const double y, const double z, const double t)
Simulate the drift line of a hole from a given starting point.
void SetStepDistanceFunction(double(*f)(double x, double y, double z))
Retrieve the step distance from a user-supplied function.
void GetElectronEndpoint(const size_t i, double &x0, double &y0, double &z0, double &t0, double &x1, double &y1, double &z1, double &t1, int &status) const
void SetCollisionSteps(const unsigned int n=100)
bool AvalancheElectronHole(const double x, const double y, const double z, const double t)
Simulate an avalanche initiated by an electron-hole pair.
bool DriftElectron(const double x, const double y, const double z, const double t)
Simulate the drift line of an electron from a given starting point.
void AddIon(const double x, const double y, const double z, const double t)
Add an ion to the list of particles to be transported.
void SetDistanceSteps(const double d=0.001)
Use fixed distance steps (default 10 um).
Abstract base class for media.
Visualize drift lines and tracks.
int dinv(const int n, std::vector< std::vector< double > > &a)
Replace square matrix A by its inverse.
constexpr std::array< double, 6 > GaussLegendreWeights6()
constexpr std::array< double, 6 > GaussLegendreNodes6()
unsigned int RndmYuleFurry(const double mean)
Draw a random number from a geometric distribution.
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 fabs(const DoubleAc &f)
DoubleAc sin(const DoubleAc &f)
DoubleAc sqrt(const DoubleAc &f)
std::vector< Point > path
Drift line.