Garfield++ 5.0
A toolkit for the detailed simulation of particle detectors based on ionisation measurement in gases and semiconductors
Loading...
Searching...
No Matches
TrackDegrade.cc
Go to the documentation of this file.
1#include <iostream>
2#include <cstdio>
3
8#include "Garfield/Random.hh"
9#include "Garfield/Sensor.hh"
12
13namespace {
14
16 const double energy, const double x, const double y, const double z,
17 const double t, const double dx, const double dy, const double dz) {
18
20 electron.energy = energy;
21 electron.x = x;
22 electron.y = y;
23 electron.z = z;
24 electron.t = t;
25 electron.dx = dx;
26 electron.dy = dy;
27 electron.dz = dz;
28 return electron;
29}
30
32 const double energy, const double x, const double y, const double z,
33 const double t) {
34
36 exc.energy = energy;
37 exc.x = x;
38 exc.y = y;
39 exc.z = z;
40 exc.t = t;
41 return exc;
42}
43
44}
45
46namespace Garfield {
47
49 m_sensor = sensor;
50 m_q = -1;
51 m_spin = 1;
52 m_mass = ElectronMass;
53 m_isElectron = true;
54 SetBetaGamma(3.);
55 m_particleName = "electron";
56}
57
58void TrackDegrade::SetThresholdEnergy(const double ethr) {
59
60 if (ethr < Small) {
61 std::cerr << m_className << "::SetThresholdEnergy: Energy must be > 0.\n";
62 } else {
63 m_ethr = ethr;
64 }
65}
66
67bool TrackDegrade::NewTrack(const double x0, const double y0, const double z0,
68 const double t0, const double dx0, const double dy0,
69 const double dz0) {
70
71 m_clusters.clear();
72 m_cluster = 0;
73 // Make sure the sensor is defined.
74 if (!m_sensor) {
75 std::cerr << m_className << "::NewTrack: Sensor is not defined.\n";
76 return false;
77 }
78
79 // Make sure we are inside a medium.
80 Medium* medium = m_sensor->GetMedium(x0, y0, z0);
81 if (!medium) {
82 std::cerr << m_className << "::NewTrack: No medium at initial position.\n";
83 return false;
84 }
85 // Check if the medium has changed since the last call.
86 if (medium->GetName() != m_mediumName ||
87 fabs(medium->GetPressure() - m_pressure) > 1.e-4 ||
88 fabs(medium->GetTemperature() - m_temperature) > 1.e-4) {
89 m_isChanged = true;
90 }
91 if (m_isChanged) {
92 if (!Initialise(medium, m_debug)) return false;
93 m_isChanged = false;
94 }
96 const double eMinIon = Degrade::ionpot();
97 if (m_debug) {
98 std::cout << " Ionisation potential: " << eMinIon << " eV.\n";
99 }
100 double xp = x0;
101 double yp = y0;
102 double zp = z0;
103 double tp = t0;
104
105 // Normalise the direction.
106 double dxp = dx0;
107 double dyp = dy0;
108 double dzp = dz0;
109 const double dp = sqrt(dxp * dxp + dyp * dyp + dzp * dzp);
110 if (dp < Small) {
111 // Choose a random direction.
112 RndmDirection(dxp, dyp, dzp);
113 } else {
114 const double scale = 1. / dp;
115 dxp *= scale;
116 dyp *= scale;
117 dzp *= scale;
118 }
119 // Get the total collision rate.
120 int64_t ie = 20000;
121 double tcf = Degrade::gettcf(&ie);
122 // Convert to ns-1.
123 tcf *= 1.e3;
124 double beta = GetBeta();
125 double gamma = GetGamma();
126 double ep = GetKineticEnergy();
127 if (m_debug) {
128 std::cout << m_className << "::NewTrack:\n"
129 << " Particle energy: " << ep << " eV\n"
130 << " Collision rate: " << tcf << " / ns\n"
131 << " Collisions / cm: "
132 << tcf / (SpeedOfLight * beta) << "\n";
133 }
134 bool ok = true;
135 while (ok) {
136 // Draw a time step.
137 double dt = -log(RndmUniformPos()) / tcf;
138 const double step = SpeedOfLight * beta * dt;
139 xp += dxp * step;
140 yp += dyp * step;
141 zp += dzp * step;
142 tp += dt;
143 if (!IsInside(xp, yp, zp)) break;
144 // Reset the scattering angle.
145 double cthetap = -99.;
146 double sthetap = -99.;
147 // Determine the type of interaction.
148 double r1 = RndmUniform();
149 int64_t izbr = 0;
150 double rgas = 0.;
151 double ein = 0.;
152 int64_t ia = 0;
153 double wpl = 0.;
154 int64_t index = 0;
155 double an = 0.;
156 double ps = 0.;
157 double wklm = 0.;
158 int64_t nc0 = 0;
159 double ec0 = 0.;
160 int64_t ng2 = 0;
161 double eg2 = 0.;
162 int64_t ng1 = 0;
163 double eg1 = 0.;
164 double dstfl = 0.;
165 int64_t ipn = 0;
166 int64_t kg1 = 0;
167 int64_t lg1 = 0;
168 int64_t igshel = 0;
169 int64_t ionmodel = 0;
170 int64_t ilvl = 0;
171 Degrade::getlevel(&ie, &r1, &izbr, &rgas, &ein, &ia, &wpl, &index,
172 &an, &ps, &wklm, &nc0, &ec0, &ng1, &eg1, &ng2, &eg2,
173 &dstfl, &ipn, &kg1, &lg1, &igshel, &ionmodel, &ilvl);
174 // Bremsstrahlung?
175 if (izbr != 0 && m_bremsStrahlung) {
176 double eout = 0., egamma = 0.;
177 double dxe = 0., dye = 0., dze = 0.;
178 double dxg = 0., dyg = 0., dzg = 0.;
179 Degrade::brems(&izbr, &ep, &dxp, &dyp, &dzp, &eout, &dxe, &dye, &dze,
180 &egamma, &dxg, &dyg, &dzg);
181 // TODO
182 // ep = eout;
183 dxp = dxe;
184 dyp = dye;
185 dzp = dze;
186 continue;
187 }
188 if (ia == 2 || ia == 7 || ia == 12 || ia == 17 ||
189 ia == 22 || ia == 27) {
190 Cluster cluster;
191 cluster.x = xp;
192 cluster.y = yp;
193 cluster.z = zp;
194 cluster.t = tp;
195 // Ionisation
196 double esec = wpl * tan(RndmUniform() * atan((ep - ein) / (2. * wpl)));
197 esec = wpl * pow(esec / wpl, 0.9524);
198 // TODO: while esec > ECUT?
199 // Calculate primary scattering angle.
200 if (index == 1) {
201 cthetap = 1. - RndmUniform() * an;
202 if (RndmUniform() > ps) cthetap = -cthetap;
203 } else if (index == 2) {
204 const double ctheta0 = 1. - 2 * RndmUniform();
205 cthetap = (ctheta0 + ps) / (1. + ps * ctheta0);
206 } else {
207 cthetap = 1. - 2. * RndmUniform();
208 }
209 sthetap = sin(acos(cthetap));
210 const double gammas = (ElectronMass + esec) / ElectronMass;
211 // Calculate secondary recoil angle from free kinematics.
212 const double sthetas = std::min(sthetap * sqrt(ep / esec) *
213 gamma / gammas, 1.);
214 double thetas = asin(sthetas);
215 double phis = TwoPi * RndmUniform();
216 // Calculate new direction cosines from initial values and
217 // scattering angles.
218 double dxs = 0., dys = 0., dzs = 0.;
219 Degrade::drcos(&dxp, &dyp, &dzp, &thetas, &phis, &dxs, &dys, &dzs);
220 // Add the secondary to the list.
221 cluster.deltaElectrons.emplace_back(
222 MakeElectron(esec, xp, yp, zp, tp, dxs, dys, dzs));
223 // Calculate possible shell emissions (Auger or fluorescence).
224 if (wklm > 0.0 && RndmUniform() < wklm) {
225 // Auger emission and fluorescence.
226 if (ng2 > 0) {
227 const double eavg = eg2 / ng2;
228 for (int64_t j = 0; j < ng2; ++j) {
229 // Random emission angle.
230 const double ctheta = 1. - 2. * RndmUniform();
231 const double stheta = sin(acos(ctheta));
232 const double phi = TwoPi * RndmUniform();
233 const double dx = cos(phi) * stheta;
234 const double dy = sin(phi) * stheta;
235 const double dz = ctheta;
236 cluster.deltaElectrons.emplace_back(
237 MakeElectron(eavg, xp, yp, zp, tp, dx, dy, dz));
238 }
239 }
240 if (ng1 > 0) {
241 const double eavg = eg1 / ng1;
242 // Fluorescence absorption distance.
243 double dfl = -log(RndmUniformPos()) * dstfl;
244 for (int64_t j = 0; j < ng1; ++j) {
245 // Random emission angle.
246 const double ctheta = 1. - 2. * RndmUniform();
247 const double stheta = sin(acos(ctheta));
248 const double phi = TwoPi * RndmUniform();
249 const double dx = cos(phi) * stheta;
250 const double dy = sin(phi) * stheta;
251 const double dz = ctheta;
252 const double cthetafl = 1. - 2. * RndmUniform();
253 const double sthetafl = sin(acos(cthetafl));
254 const double phifl = TwoPi * RndmUniform();
255 const double xs = dfl * sthetafl * cos(phifl);
256 const double ys = dfl * sthetafl * sin(phifl);
257 const double zs = dfl * cthetafl;
258 const double ts = dfl / SpeedOfLight;
259 cluster.deltaElectrons.emplace_back(
260 MakeElectron(eavg, xs, ys, zs, ts, dx, dy, dz));
261 }
262 }
263 } else {
264 // Auger emission without fluorescence.
265 const double eavg = ec0 / nc0;
266 for (int64_t j = 0; j < nc0; ++j) {
267 // Random emission angle.
268 const double ctheta = 1. - 2. * RndmUniform();
269 const double stheta = sin(acos(ctheta));
270 const double phi = TwoPi * RndmUniform();
271 const double dx = cos(phi) * stheta;
272 const double dy = sin(phi) * stheta;
273 const double dz = ctheta;
274 cluster.deltaElectrons.emplace_back(
275 MakeElectron(eavg, xp, yp, zp, tp, dx, dy, dz));
276 }
277 }
278 m_clusters.push_back(std::move(cluster));
279 } else if (ia == 4 || ia == 9 || ia == 14 || ia == 19 ||
280 ia == 24 || ia == 29) {
281 // Excitation.
282 const double eExc = rgas * ein;
283 // Find the gas in which the excitation occured.
284 int64_t igas = Degrade::getgas(&ilvl);
285 if (igas <= 0 || igas >= 6) {
286 std::cerr << m_className << "::NewTrack: "
287 << "Could not retrieve gas index.\n";
288 igas = 0;
289 } else {
290 igas -= 1;
291 }
292 const double penfra1 = m_rPenning[igas];
293 const double penfra2 = m_dPenning[igas];
294 constexpr double penfra3 = 0.;
295 const bool penning = m_penning && eExc > eMinIon &&
296 penfra1 > 0. && m_nGas > 1;
297 if (penning && RndmUniform() < penfra1) {
298 // Penning transfer
299 Cluster cluster;
300 cluster.x = xp;
301 cluster.y = yp;
302 cluster.z = zp;
303 cluster.t = tp;
304 // Random emission angle.
305 const double ctheta = 1. - 2. * RndmUniform();
306 const double stheta = sin(acos(ctheta));
307 const double phi = TwoPi * RndmUniform();
308 const double dx = cos(phi) * stheta;
309 const double dy = sin(phi) * stheta;
310 const double dz = ctheta;
311 // Penning transfer distance.
312 double asign = 1.;
313 if (RndmUniform() < 0.5) asign = -asign;
314 const double xs = xp - log(RndmUniformPos()) * penfra2 * asign;
315 if (RndmUniform() < 0.5) asign = -asign;
316 const double ys = yp - log(RndmUniformPos()) * penfra2 * asign;
317 if (RndmUniform() < 0.5) asign = -asign;
318 const double zs = zp - log(RndmUniformPos()) * penfra2 * asign;
319 const double ts = tp - log(RndmUniformPos()) * penfra3;
320 // Fix Penning electron energy to 4 eV.
321 cluster.deltaElectrons.emplace_back(
322 MakeElectron(4., xs, ys, zs, ts, dx, dy, dz));
323 m_clusters.push_back(std::move(cluster));
324 } else {
325 if (m_storeExcitations && eExc > m_ethrExc) {
326 Cluster cluster;
327 cluster.x = xp;
328 cluster.y = yp;
329 cluster.z = zp;
330 cluster.t = tp;
331 cluster.excitations.emplace_back(
332 MakeExcitation(eExc, xp, yp, zp, tp));
333 m_clusters.push_back(std::move(cluster));
334 }
335 }
336 }
337 double s1 = 1. + gamma * (rgas - 1.);
338 double s2 = (s1 * s1) / (s1 - 1.);
339 if (cthetap < -1.) {
340 if (index == 1) {
341 // Anisotropic scattering
342 cthetap = 1. - RndmUniform() * an;
343 if (RndmUniform() > ps) cthetap = -cthetap;
344 } else if (index == 2) {
345 // Anisotropic scattering
346 const double ctheta0 = 1. - 2 * RndmUniform();
347 cthetap = (ctheta0 + ps) / (1. + ps * ctheta0);
348 } else {
349 // Isotropic scattering
350 cthetap = 1. - 2. * RndmUniform();
351 }
352 sthetap = sin(acos(cthetap));
353 }
354 double phi0 = TwoPi * RndmUniform();
355 double sphi0 = sin(phi0);
356 double cphi0 = cos(phi0);
357 if (ep < ein) ein = 0.;
358 double arg1 = std::max(1. - s1 * ein / ep, 1.e-20);
359 const double d = 1. - cthetap * sqrt(arg1);
360 double e1 = std::max(ep * (1. - ein / (s1 * ep) - 2. * d / s2), 1.e-20);
361 const double q = std::min(sqrt((ep / e1) * arg1) / s1, 1.);
362 const double theta = asin(q * sthetap);
363 double ctheta = cos(theta);
364 if (cthetap < 0.) {
365 const double u = (s1 - 1.) * (s1 - 1.) / arg1;
366 if (cthetap * cthetap > u) ctheta = -ctheta;
367 }
368 const double stheta = sin(theta);
369 double dx1 = dxp;
370 double dy1 = dyp;
371 double dz1 = std::min(dzp, 1.);
372 double argz = sqrt(dx1 * dx1 + dy1 * dy1);
373 if (argz == 0.) {
374 dzp = ctheta;
375 dxp = cphi0 * stheta;
376 dyp = sphi0 * stheta;
377 } else {
378 dzp = dz1 * ctheta + argz * stheta * sphi0;
379 dyp = dy1 * ctheta + (stheta / argz) * (dx1 * cphi0 - dy1 * dz1 * sphi0);
380 dxp = dx1 * ctheta - (stheta / argz) * (dy1 * cphi0 + dx1 * dz1 * sphi0);
381 }
382 // TODO
383 // ep = e1;
384 }
385 if (m_debug) std::cout << " " << m_clusters.size() << " clusters.\n";
386 for (auto& cluster : m_clusters) {
387 for (const auto& delta : cluster.deltaElectrons) {
388 auto secondaries = TransportDeltaElectron(delta.x, delta.y, delta.z,
389 delta.t, delta.energy,
390 delta.dx, delta.dy, delta.dz);
391 cluster.electrons.insert(cluster.electrons.end(),
392 secondaries.first.begin(),
393 secondaries.first.end());
394 cluster.excitations.insert(cluster.excitations.end(),
395 secondaries.second.begin(),
396 secondaries.second.end());
397 }
398 }
399 return true;
400}
401
402bool TrackDegrade::GetCluster(double& xc, double& yc, double& zc, double& tc,
403 int& ne, double& ec, double& extra) {
404 xc = yc = zc = tc = ec = extra = 0.;
405 ne = 0;
406 if (m_clusters.empty() || m_cluster >= m_clusters.size()) return false;
407 const auto& cluster = m_clusters[m_cluster];
408 xc = cluster.x;
409 yc = cluster.y;
410 zc = cluster.z;
411 tc = cluster.t;
412 ne = cluster.electrons.size();
413 ++m_cluster;
414 return true;
415}
416
417bool TrackDegrade::Initialise(Medium* medium, const bool verbose) {
418
419 if (!medium) {
420 std::cerr << m_className << "::Initialise: Null pointer.\n";
421 return false;
422 }
423 if (!medium->IsGas()) {
424 std::cerr << m_className << "::Initialise: Medium "
425 << medium->GetName() << " is not a gas.\n";
426 return false;
427 }
428
429 // Get temperature and pressure.
430 double pressure = medium->GetPressure();
431 double temperature = medium->GetTemperature() - ZeroCelsius;
432 // Get the gas composition.
433 std::array<int64_t, 6> ngas;
434 std::array<double, 6> frac;
435 const unsigned int nComponents = medium->GetNumberOfComponents();
436 if (nComponents < 1) {
437 std::cerr << m_className << "::Initialise: Invalid gas mixture.\n";
438 return false;
439 }
440 std::vector<unsigned int> notdone = {
441 13, 17, 20, 22, 24, 26, 27, 28, 32, 33, 37, 38, 39, 40, 41, 42, 43,
442 50, 51, 53, 54, 55, 56, 57};
443 for (unsigned int i = 0; i < nComponents; ++i) {
444 std::string name;
445 double f;
446 medium->GetComponent(i, name, f);
448 if (std::find(notdone.begin(), notdone.end(), ngas[i]) != notdone.end()) {
449 std::cerr << m_className << "::Initialise:\n"
450 << " Cross-sections for " << name
451 << " are not yet implemented.\n";
452 return false;
453 }
454 frac[i] = 100. * f;
455 }
456
457 int64_t ng = nComponents;
458 int64_t ne = 1;
459 int64_t mip = 1;
460 int64_t idvec = 1;
461 int32_t iseed = 0;
462 double e0 = GetKineticEnergy();
463 double et = m_ethr;
464 double ec = 10000.;
465 double etot = 100.;
466 double btot = 0.;
467 double bang = 90.;
468 int64_t jcmp = 1;
469 int64_t jray = 1;
470 int64_t jpap = 1;
471 int64_t jbrm = m_bremsStrahlung ? 1 : 0;
472 int64_t jecasc = m_fullCascade ? 1 : 0;
473 int64_t iverb = verbose ? 1 : 0;
474 Degrade::deginit(&ng, &ne, &mip, &idvec, &iseed, &e0, &et, &ec,
475 &ngas[0], &ngas[1], &ngas[2], &ngas[3], &ngas[4], &ngas[5],
476 &frac[0], &frac[1], &frac[2], &frac[3], &frac[4], &frac[5],
477 &temperature, &pressure, &etot, &btot, &bang,
478 &jcmp, &jray, &jpap, &jbrm, &jecasc, &iverb);
479
480 m_mediumName = medium->GetName();
481 m_pressure = medium->GetPressure();
482 m_temperature = medium->GetTemperature();
483 m_nGas = nComponents;
484 m_isChanged = false;
485 m_dedx = -1.;
486 m_clusterDensity = -1.;
487 return true;
488}
489
491 if (m_isChanged) return 0.;
492 if (m_clusterDensity < 0.) {
493 // Compute dE/dx and cluster density.
495 }
496 return m_clusterDensity;
497}
498
500 if (m_isChanged) return 0.;
501 if (m_dedx < 0.) {
502 // Compute dE/dx and cluster density.
504 }
505 return m_dedx;
506}
507
508void TrackDegrade::SetParticle(const std::string& particle) {
509 if (particle != "electron" && particle != "e" && particle != "e-") {
510 std::cerr << m_className << "::SetParticle: Only electrons are allowed.\n";
511 }
512}
513
514std::pair<std::vector<TrackDegrade::Electron>,
515 std::vector<TrackDegrade::Excitation> >
517 const double x0, const double y0, const double z0, const double t0,
518 const double e0, const double dx0, const double dy0, const double dz0) {
519
520 // Based on MONTEFE subroutine.
521 std::vector<Electron> thermalisedElectrons;
522 std::vector<Excitation> excitations;
523
524 if (!m_sensor) {
525 std::cerr << m_className << "::TransportDeltaElectron: "
526 << "Sensor is not defined.\n";
527 return std::make_pair(thermalisedElectrons, excitations);
528 }
529
530 const double eMinIon = Degrade::ionpot();
531 if (m_debug) {
532 std::cout << " Ionisation potential: " << eMinIon << " eV.\n";
533 }
534
535 // Calculate maximum collision frequency.
536 double flim = 0.;
537 for (int64_t j = 1; j <= 20000; ++j) {
538 double tcf = Degrade::gettcf(&j);
539 double tcfn = Degrade::gettcfn(&j);
540 flim = std::max(flim, tcf + tcfn);
541 }
542 // Convert to ns-1.
543 flim *= 1.e3;
544 std::vector<Electron> deltas;
545 deltas.emplace_back(MakeElectron(e0, x0, y0, z0, t0, dx0, dy0, dz0));
546 std::vector<Electron> newDeltas;
547
548 while (!deltas.empty()) {
549 for (const auto& delta : deltas) {
550 double x1 = delta.x;
551 double y1 = delta.y;
552 double z1 = delta.z;
553 double t1 = delta.t;
554 double e1 = delta.energy;
555 double dx1 = delta.dx;
556 double dy1 = delta.dy;
557 double dz1 = delta.dz;
558
559 bool attached = false;
560 double tdash = 0.;
561 while (1) {
562 if (e1 <= m_ethr) break;
563 bool ionised = false;
564 size_t jsec = 0;
565 double esec = 0.;
566 const double dt = tdash -log(RndmUniformPos()) / flim;
567 tdash = dt;
568 const double gamma1 = (ElectronMass + e1) / ElectronMass;
569 const double beta1 = sqrt(1. - 1. / (gamma1 * gamma1));
570 // Energy after the free-flight step.
571 // TODO: electric field.
572 double e2 = e1;
573 if (e2 < 0.) e2 = 0.001;
574 int64_t ie = Degrade::getie(&e2);
575 // Test for real or null collision.
576 double tcf = Degrade::gettcf(&ie);
577 // Convert to ns-1.
578 tcf *= 1.e3;
579 const double r5 = RndmUniform();
580 if (r5 > tcf / flim) {
581 // Null collision
582 // TODO: molecular light emission from null excitations.
583 continue;
584 }
585 tdash = 0.;
586 // Compute direction and position at the instant before the collision.
587 // TODO: electric and magnetic field.
588 const double gamma2 = (ElectronMass + e2) / ElectronMass;
589 // const double gamma12 = 0.5 * (gamma1 + gamma2);
590 const double beta2 = sqrt(1. - 1. / (gamma2 * gamma2));
591 const double c6 = beta1 / beta2;
592 double dx2 = dx1 * c6;
593 double dy2 = dy1 * c6;
594 double dz2 = dz1 * c6;
595 const double a = dt * SpeedOfLight * beta1;
596 double x2 = x1 + dx1 * a;
597 double y2 = y1 + dy1 * a;
598 double z2 = z1 + dz1 * a;
599 double t2 = t1 + dt;
600
601 if (!IsInside(x2, y2, z2)) {
602 // Endpoint of the free-flight step is outside the active area.
603 break;
604 }
605 x1 = x2;
606 y1 = y2;
607 z1 = z2;
608 t1 = t2;
609
610 // Determine the real collision type.
611 double r2 = RndmUniform();
612 int64_t izbr = 0;
613 double rgas = 0.;
614 double ein = 0.;
615 int64_t ia = 0;
616 double wpl = 0.;
617 int64_t index = 0;
618 double an = 0.;
619 double ps = 0.;
620 double wklm = 0.;
621 int64_t nc0 = 0;
622 double ec0 = 0.;
623 int64_t ng2 = 0;
624 double eg2 = 0.;
625 int64_t ng1 = 0;
626 double eg1 = 0.;
627 double dstfl = 0.;
628 int64_t ipn = 0;
629 int64_t kg1 = 0;
630 int64_t lg1 = 0;
631 int64_t igshel = 0;
632 int64_t ionmodel = 0;
633 int64_t ilvl = 0;
634 Degrade::getlevel(&ie, &r2, &izbr, &rgas, &ein, &ia, &wpl, &index,
635 &an, &ps, &wklm, &nc0, &ec0, &ng1, &eg1, &ng2, &eg2,
636 &dstfl, &ipn, &kg1, &lg1, &igshel, &ionmodel,
637 &ilvl);
638 // Bremsstrahlung?
639 if (izbr != 0 && m_bremsStrahlung) {
640 int64_t kg = 0;
641 for (kg = 1; kg <= 6; ++kg) {
642 if (ia == (kg * 5) - 1) break;
643 }
644 kg -= 1;
645 double dxe = 0., dye = 0., dze = 0.;
646 double dxg = 0., dyg = 0., dzg = 0.;
647 double eout = 0., egamma = 0.;
648 Degrade::brems(&izbr, &e2, &dx2, &dy2, &dz2, &eout,
649 &dxe, &dye, &dze, &egamma, &dxg, &dyg, &dzg);
650 // TODO: counters.
651 // NBREM[kg] += 1;
652 // EBRTOT[kg] += egamma;
653 // Update direction and energy of the electron.
654 e1 = eout;
655 dx1 = dxe;
656 dy1 = dye;
657 dz1 = dze;
658 // Run bremsstrahlung gamma through cascade.
659 int64_t j11 = 1;
660 int64_t ilow = 0;
661 Degrade::bremscasc(&j11, &egamma, &x1, &y1, &z1, &t1,
662 &dxg, &dyg, &dzg, &ilow);
663 // If bremsstrahlung energy is not too low to ionise,
664 // retrieve the secondary electrons.
665 if (ilow != 0) {
666 for (int64_t k = 0; k <= 400; ++k) {
667 int64_t iok = 0;
668 double ee = 0., xe = 0., ye = 0., ze = 0., te = 0.;
669 Degrade::getebrem(&k, &ee, &xe, &ye, &ze, &te,
670 &dxe, &dye, &dze, &iok);
671 if (iok != 1) break;
672 newDeltas.emplace_back(
673 MakeElectron(ee, xe, ye, ze, te, dxe, dye, dze));
674 }
675 }
676 continue;
677 }
678 if (e2 < ein) ein = e2 - 0.0001;
679 if (ipn == -1) {
680 // Attachment.
681 attached = true;
682 break;
683 } else if (ipn == 0) {
684 // Excitation.
685 const double eExc = rgas * ein;
686 // Find the gas in which the excitation occured.
687 int64_t igas = Degrade::getgas(&ilvl);
688 if (igas <= 0 || igas >= 6) {
689 std::cerr << m_className << "::TransportDeltaElectron: "
690 << "Could not retrieve gas index.\n";
691 igas = 0;
692 } else {
693 igas -= 1;
694 }
695 const double penfra1 = m_rPenning[igas];
696 const double penfra2 = m_dPenning[igas];
697 constexpr double penfra3 = 0.;
698 const bool penning = m_penning && eExc > eMinIon &&
699 penfra1 > 0. && m_nGas > 1;
700 if (penning && RndmUniform() < penfra1) {
701 // Penning transfer.
702 double xs = x2;
703 double ys = y2;
704 double zs = z2;
705 if (penfra2 > 0.) {
706 double asign = 1.;
707 if (RndmUniform() < 0.5) asign = -asign;
708 xs -= log(RndmUniformPos()) * penfra2 * asign;
709 if (RndmUniform() < 0.5) asign = -asign;
710 ys -= log(RndmUniformPos()) * penfra2 * asign;
711 if (RndmUniform() < 0.5) asign = -asign;
712 zs -= log(RndmUniformPos()) * penfra2 * asign;
713 }
714 double ts = t2 - log(RndmUniformPos()) * penfra3;
715 // Assign excess energy of 1 eV to Penning electron.
716 newDeltas.emplace_back(
717 MakeElectron(1., xs, ys, zs, ts, dx1, dy1, dz1));
718 } else {
719 if (eExc > m_ethrExc) {
720 // Store excitation.
721 if (m_storeExcitations) {
722 excitations.emplace_back(
723 MakeExcitation(eExc, x2, y2, z2, t2));
724 }
725 }
726 }
727 } else if (ipn == 1) {
728 const double eistr = ein;
729 if (ionmodel > 0) {
730 // Calculate secondary energy in ionising collision using
731 // five different models.
732 Degrade::ionsplit(&ilvl, &e2, &ein, &esec);
733 } else {
734 // Use Opal, Peterson and Beaty splitting factor.
735 esec = wpl * tan(RndmUniform() * atan((e2 - ein) / (2. * wpl)));
736 esec = wpl * pow(esec / wpl, 0.9524);
737 }
738 ein += esec;
739 // Store secondary ionisation electron.
740 newDeltas.emplace_back(
741 MakeElectron(esec, x2, y2, z2, t2, dx2, dy2, dz2));
742 ionised = true;
743 jsec = newDeltas.size() - 1;
744 if (m_fullCascade && lg1 != 0) {
745 // Use complete cascade for electron ionisation.
746 int64_t j11 = 1;
747 Degrade::cascadee(&j11, &kg1, &lg1, &x2, &y2, &z2, &t2, &esec,
748 &igshel);
749 // Retrieve electrons.
750 for (int64_t k = 1; k <= 400; ++k) {
751 int64_t iok = 0;
752 double ee = 0., xe = 0., ye = 0., ze = 0., te = 0.;
753 double dxe = 0., dye = 0., dze = 0.;
754 Degrade::getecasc(&k, &ee, &xe, &ye, &ze, &te,
755 &dxe, &dye, &dze, &iok);
756 if (iok != 1) break;
757 newDeltas.emplace_back(
758 MakeElectron(ee, xe, ye, ze, te, dxe, dye, dze));
759 }
760 } else {
761 // Store possible shell emissions.
762 if (eistr > 30.0) {
763 // Test if fluorescence emission.
764 if (wklm > 0.0 && RndmUniform() < wklm) {
765 // Auger emission and fluorescence.
766 if (ng2 > 0) {
767 const double eav = eg2 / ng2;
768 for (int64_t j = 0; j < ng2; ++j) {
769 const double ctheta = 1. - 2. * RndmUniform();
770 const double stheta = sin(acos(ctheta));
771 const double phi = TwoPi * RndmUniform();
772 const double dx = cos(phi) * stheta;
773 const double dy = sin(phi) * stheta;
774 const double dz = ctheta;
775 newDeltas.emplace_back(
776 MakeElectron(eav, x2, y2, z2, t2, dx, dy, dz));
777 }
778 }
779 if (ng1 > 0) {
780 const double eav = eg1 / ng1;
781 const double dfl = -log(RndmUniformPos()) * dstfl;
782 for (int64_t j = 0; j < ng1; ++j) {
783 const double cthetafl = 1. - 2. * RndmUniform();
784 const double sthetafl = sin(acos(cthetafl));
785 const double phifl = TwoPi * RndmUniform();
786 const double xs = x2 + dfl * sthetafl * cos(phifl);
787 const double ys = y2 + dfl * sthetafl * sin(phifl);
788 const double zs = z2 + dfl * cthetafl;
789 const double ts = t2;
790 const double ctheta = 1. - 2. * RndmUniform();
791 const double stheta = sin(acos(ctheta));
792 const double phi = TwoPi * RndmUniform();
793 const double dx = cos(phi) * stheta;
794 const double dy = sin(phi) * stheta;
795 const double dz = ctheta;
796 newDeltas.emplace_back(
797 MakeElectron(eav, xs, ys, zs, ts, dx, dy, dz));
798 }
799 }
800 } else {
801 // Auger emission without fluorescence.
802 const double eav = ec0 / nc0;
803 for (int64_t j = 0; j < nc0; ++j) {
804 const double ctheta = 1. - 2. * RndmUniform();
805 const double stheta = sin(acos(ctheta));
806 const double phi = TwoPi * RndmUniform();
807 const double dx = cos(phi) * stheta;
808 const double dy = sin(phi) * stheta;
809 const double dz = ctheta;
810 newDeltas.emplace_back(
811 MakeElectron(eav, x2, y2, z2, t2, dx, dy, dz));
812 }
813 }
814 }
815 }
816 }
817 // Generate scattering angles and update direction after collision.
818 const double s1 = 1. + gamma2 * (rgas - 1.);
819 const double s2 = (s1 * s1) / (s1 - 1.);
820 double ctheta0 = 0.;
821 if (index == 1) {
822 // Anisotropic scattering
823 ctheta0 = 1. - RndmUniform() * an;
824 if (RndmUniform() > ps) ctheta0 = -ctheta0;
825 } else if (index == 2) {
826 // Anisotropic scattering
827 ctheta0 = 1. - 2 * RndmUniform();
828 ctheta0 = (ctheta0 + ps) / (1. + ps * ctheta0);
829 } else {
830 // Isotropic scattering
831 ctheta0 = 1. - 2. * RndmUniform();
832 }
833 const double theta0 = acos(ctheta0);
834 const double phi0 = TwoPi * RndmUniform();
835 const double sphi0 = sin(phi0);
836 const double cphi0 = cos(phi0);
837 if (e2 < ein) ein = 0.;
838 const double arg1 = std::max(1. - s1 * ein / e2, 1.e-20);
839 const double d = 1. - ctheta0 * sqrt(arg1);
840 // Update electron energy.
841 e1 = std::max(e2 * (1. - ein / (s1 * e2) - 2. * d / s2), 1.e-20);
842 const double q = std::min(sqrt((e2/ e1) * arg1) / s1, 1.);
843 const double theta = asin(q * sin(theta0));
844 double ctheta = cos(theta);
845 if (ctheta0 < 0.) {
846 const double u = (s1 - 1.) * (s1 - 1.) / arg1;
847 if (ctheta0 * ctheta0 > u) ctheta = -ctheta;
848 }
849 double stheta = sin(theta);
850 dz2 = std::min(dz2, 1.);
851 const double argz = sqrt(dx2 * dx2 + dy2 * dy2);
852 if (argz > 0.) {
853 dz1 = dz2 * ctheta + argz * stheta * sphi0;
854 dy1 = dy2 * ctheta + (stheta / argz) * (dx2 * cphi0 - dy2 * dz2 * sphi0);
855 dx1 = dx2 * ctheta - (stheta / argz) * (dy2 * cphi0 + dx2 * dz2 * sphi0);
856 if (ionised) {
857 // Use free kinematics for ionisation secondary angles.
858 double sthetas = std::min(stheta * sqrt(e1 / esec), 1.);
859 double cthetas = cos(asin(sthetas));
860 if (ctheta < 0.0) cthetas = -cthetas;
861 double phis = phi0 + Pi;
862 if (phis > TwoPi) phis = phi0 - TwoPi;
863 const double sphis = sin(phis);
864 const double cphis = cos(phis);
865 newDeltas[jsec].dz = dz2 * cthetas + argz * sthetas * sphis;
866 newDeltas[jsec].dy = dy2 * cthetas +
867 (sthetas / argz) * (dx2 * cphis - dy2 * dz2 * sphis);
868 newDeltas[jsec].dx = dx2 * cthetas -
869 (sthetas / argz) * (dy2 * cphis + dx2 * dz2 * sphis);
870 }
871 } else {
872 dz1 = ctheta;
873 dx1 = cphi0 * stheta;
874 dy1 = sphi0 * stheta;
875 if (ionised) {
876 // Use free kinematics for ionisation secondary angles.
877 double sthetas = std::min(stheta * sqrt(e1 / esec), 1.);
878 double cthetas = cos(asin(sthetas));
879 if (ctheta < 0.) cthetas = -cthetas;
880 double phis = phi0 + Pi;
881 if (phis > TwoPi) phis = phi0 - TwoPi;
882 newDeltas[jsec].dz = cthetas;
883 newDeltas[jsec].dx = cos(phis) * sthetas;
884 newDeltas[jsec].dy = sin(phis) * sthetas;
885 }
886 }
887 }
888 if (attached) continue;
889 thermalisedElectrons.emplace_back(
890 MakeElectron(e1, x1, y1, z1, t1, dx1, dy1, dz1));
891 }
892 deltas.swap(newDeltas);
893 newDeltas.clear();
894 }
895 return std::make_pair(thermalisedElectrons, excitations);
896}
897
899 std::array<double, 6>& rP,
900 std::array<double, 6>& dP) {
901 rP.fill(0.);
902 dP.fill(0.);
903 auto gas = dynamic_cast<MediumGas*>(medium);
904 if (!gas) return;
905 const unsigned int nComponents = medium->GetNumberOfComponents();
906 if (m_debug) std::cout << m_className << "::SetupPenning:\n";
907 for (unsigned int i = 0; i < nComponents; ++i) {
908 std::string name;
909 double f;
910 medium->GetComponent(i, name, f);
911 gas->GetPenningTransfer(name, rP[i], dP[i]);
912 if (m_debug) std::printf(" %-15s %5.3f\n", name.c_str(), rP[i]);
913 }
914}
915
916bool TrackDegrade::IsInside(const double x, const double y, const double z) {
917 // Check if the point is inside the drift area.
918 if (!m_sensor->IsInArea(x, y, z)) return false;
919 // Check if the point is inside a medium.
920 Medium* medium = m_sensor->GetMedium(x, y, z);
921 if (!medium) return false;
922 // Make sure the medium has not changed.
923 if (medium->GetName() != m_mediumName ||
924 fabs(medium->GetPressure() - m_pressure) > 1.e-4 ||
925 fabs(medium->GetTemperature() - m_temperature) > 1.e-4 ||
926 !medium->IsIonisable() || !medium->IsGas()) {
927 return false;
928 }
929 return true;
930}
931
932}
Base class for gas media.
Definition MediumGas.hh:15
static int GetGasNumberMagboltz(const std::string &input)
Abstract base class for media.
Definition Medium.hh:16
virtual void GetComponent(const unsigned int i, std::string &label, double &f)
Get the name and fraction of a given component.
Definition Medium.cc:106
double GetPressure() const
Definition Medium.hh:41
unsigned int GetNumberOfComponents() const
Get number of components of the medium.
Definition Medium.hh:48
const std::string & GetName() const
Get the medium name/identifier.
Definition Medium.hh:26
virtual bool IsGas() const
Is this medium a gas?
Definition Medium.hh:28
bool IsIonisable() const
Is charge deposition by charged particles/photon enabled in this medium?
Definition Medium.hh:81
double GetTemperature() const
Get the temperature [K].
Definition Medium.hh:37
std::array< double, 6 > m_dPenning
void SetThresholdEnergy(const double eth)
Set the energy down to which electrons are tracked (default: 2 eV).
double GetClusterDensity() override
std::pair< std::vector< Electron >, std::vector< Excitation > > TransportDeltaElectron(const double x0, const double y0, const double z0, const double t0, const double e0, const double dx, const double dy, const double dz)
std::vector< Cluster > m_clusters
double GetStoppingPower() override
Get the stopping power (mean energy loss [eV] per cm).
TrackDegrade()
Default constructor.
void SetupPenning(Medium *medium, std::array< double, 6 > &rP, std::array< double, 6 > &dP)
std::array< double, 6 > m_rPenning
bool GetCluster(double &xc, double &yc, double &zc, double &tc, int &ne, double &ec, double &extra) override
bool NewTrack(const double x0, const double y0, const double z0, const double t0, const double dx0, const double dy0, const double dz0) override
void SetParticle(const std::string &particle) override
bool IsInside(const double x, const double y, const double z)
bool Initialise(Medium *medium, const bool verbose=false)
double GetKineticEnergy() const
Return the kinetic energy of the projectile.
Definition Track.hh:62
Sensor * m_sensor
Definition Track.hh:114
void SetBetaGamma(const double bg)
Set the relative momentum of the particle.
Definition Track.cc:106
double GetBeta() const
Return the speed ( ) of the projectile.
Definition Track.hh:56
double GetGamma() const
Return the Lorentz factor of the projectile.
Definition Track.hh:58
bool m_isElectron
Definition Track.hh:111
bool m_isChanged
Definition Track.hh:116
std::string m_particleName
Definition Track.hh:112
std::string m_className
Definition Track.hh:104
double m_mass
Definition Track.hh:108
Track()=delete
Default constructor.
int64_t getie(double *e)
void getlevel(int64_t *ie, double *r1, int64_t *izbr, double *rgas, double *ein, int64_t *ia, double *wpl, int64_t *index, double *an, double *ps, double *wklm, int64_t *nc0, double *ec0, int64_t *ng1, double *eg1, int64_t *ng2, double *eg2, double *dstfl, int64_t *jpn, int64_t *kg1, int64_t *lg1, int64_t *igshel, int64_t *ionmdl, int64_t *ilvl)
void ionsplit(int64_t *i, double *e, double *ei, double *esec)
void getecasc(int64_t *k, double *ee, double *xe, double *ye, double *ze, double *te, double *dxe, double *dye, double *dze, int64_t *iok)
void brems(int64_t *iz, double *ein, double *dx, double *dy, double *dz, double *eout, double *dxe, double *dye, double *dze, double *egamma, double *dxg, double *dyg, double *dzg)
void getebrem(int64_t *k, double *ee, double *xe, double *ye, double *ze, double *te, double *dxe, double *dye, double *dze, int64_t *iok)
void drcos(double *drx, double *dry, double *drz, double *theta, double *phi, double *drxx, double *dryy, double *drzz)
void bremscasc(int64_t *j11, double *egamma, double *x0, double *y0, double *z0, double *t0, double *gdcx, double *gdcy, double *gdcz, int64_t *ilow)
int64_t getgas(int64_t *ilvl)
double gettcfn(int64_t *ie)
void deginit(int64_t *ng, int64_t *nevt, int64_t *mip, int64_t *idvec, int32_t *iseed, double *e0, double *et, double *ec, int64_t *ngas1, int64_t *ngas2, int64_t *ngas3, int64_t *ngas4, int64_t *ngas5, int64_t *ngas6, double *frac1, double *frac2, double *frac3, double *frac4, double *frac5, double *frac6, double *t0, double *p0, double *etot, double *btot, double *bang, int64_t *jcmp, int64_t *jray, int64_t *jpap, int64_t *jbrm, int64_t *jecasc, int64_t *iverb)
void cascadee(int64_t *j11, int64_t *kgas, int64_t *lgas, double *x0, double *y0, double *z0, double *t0, double *einit, int64_t *ishell)
double gettcf(int64_t *ie)
void getdedx(double *dedxi, double *cldensi)
double RndmUniform()
Draw a random number uniformly distributed in the range [0, 1).
Definition Random.hh:14
void RndmDirection(double &dx, double &dy, double &dz, const double length=1.)
Draw a random (isotropic) direction vector.
Definition Random.hh:128
double RndmUniformPos()
Draw a random number uniformly distributed in the range (0, 1).
Definition Random.hh:17
std::vector< Electron > deltaElectrons
std::vector< Excitation > excitations