Garfield++ 5.0
A toolkit for the detailed simulation of particle detectors based on ionisation measurement in gases and semiconductors
Loading...
Searching...
No Matches
TrackBichsel.cc
Go to the documentation of this file.
1#include <algorithm>
2#include <cstdlib>
3#include <fstream>
4#include <iostream>
5#include <sstream>
6
9#include "Garfield/Random.hh"
11#include "Garfield/Sensor.hh"
13
14namespace {
15bool IsComment(const std::string& line) {
16 if (line.empty()) return false;
17 if (line[0] == '#') return true;
18 if (line.size() > 1 && (line[0] == '/' && line[1] == '/')) return true;
19 return false;
20}
21
22}
23
24namespace Garfield {
25
27 m_sensor = sensor;
28 Initialise();
29}
30
32
33 std::cout << m_className << "::Initialize:\n";
34 // Reset the tables.
35 m_E.fill(0.);
36 m_dfdE.fill(0.);
37 m_eps1.fill(0.);
38 m_eps2.fill(0.);
39 m_int.fill(0.);
40 m_k1.fill(0.);
41
43 m_density = si.GetNumberDensity();
44 // Conversion from loss function to oscillator strength density.
45 m_conv = ElectronMass / (2 * Pi2 * FineStructureConstant * pow(HbarC, 3) * m_density);
46 // Number of bins for each factor of 2 in energy
47 constexpr unsigned int n2 = 64;
48 const double u = log(2.) / n2;
49 const double um = exp(u);
50 constexpr double kEdge = 1839.;
51 const double ken = log(kEdge / 1.5) / u;
52 m_E[0] = kEdge / pow(2, ken / n2);
53 if (m_debug) std::cout << " Bin Energy [eV]\n";
54 for (size_t j = 0; j < NEnergyBins; ++j) {
55 m_E[j + 1] = m_E[j] * um;
56 if (!m_debug) continue;
57 if ((j + 1) % 50 == 0) std::printf(" %4zu %16.8f\n", j + 1, m_E[j]);
58 }
59
60 // Read in the complex dielectric function (epsilon). This is used
61 // for the cross section of small momentum transfer excitations.
62 std::string path = "";
63 auto installdir = std::getenv("GARFIELD_INSTALL");
64 if (!installdir) {
65 std::cerr << " Environment variable GARFIELD_INSTALL not set.\n";
66 } else {
67 path = std::string(installdir) + "/share/Garfield/Data/";
68 }
69 std::ifstream infile;
70 std::cout << " Reading dielectric function.\n";
71 infile.open(path + "heps.tab", std::ios::in);
72 if (!infile) {
73 std::cerr << " Could not open heps.tab.\n";
74 return false;
75 }
76 bool ok = true;
77 for (std::string line; std::getline(infile, line);) {
78 ltrim(line);
79 // Skip comments and empty lines.
80 if (IsComment(line)) continue;
81 std::istringstream data(line);
82 size_t j;
83 double energy, eps1, eps2, lossFunction;
84 data >> j >> energy >> eps1 >> eps2 >> lossFunction;
85 if (j < 1 || j > NEnergyBins) {
86 std::cerr << " Index out of range.\n";
87 ok = false;
88 break;
89 }
90 --j;
91 m_eps1[j] = eps1;
92 m_eps2[j] = eps2;
93 m_dfdE[j] = lossFunction * m_conv * m_E[j];
94 }
95 infile.close();
96 if (!ok) {
97 std::cerr << " Error reading dielectric function.\n";
98 return false;
99 }
100
101 // Read in a table of the generalized oscillator strength density
102 // integrated over the momentum transfer K, corresponding to the
103 // function A(E) in Equation (2.11) in (Bichsel, 1988).
104 // These values are used for calculating the cross section for
105 // longitudinal excitations of K and L shell electrons
106 // with large momentum transfer.
107 std::cout << " Reading K and L shell calculations.\n";
108 infile.open(path + "macom.tab", std::ios::in);
109 if (!infile) {
110 std::cerr << " Could not open macom.tab.\n";
111 return false;
112 }
113 for (std::string line; std::getline(infile, line);) {
114 ltrim(line);
115 if (IsComment(line)) continue;
116 std::istringstream data(line);
117 size_t j;
118 double energy, val;
119 data >> j >> energy >> val;
120 if (j < 1 || j > NEnergyBins) {
121 std::cerr << " Index out of range.\n";
122 ok = false;
123 break;
124 }
125 --j;
126 m_int[j] = val;
127 }
128 infile.close();
129 if (!ok) {
130 std::cerr << " Error reading K/L shell data.\n";
131 return false;
132 }
133
134 // Read in a table of the generalized oscillator strength density
135 // for M shell electrons integrated over the momentum transfer K.
136 // Based on equations in the appendix in Emerson et al.,
137 // Phys. Rev. B 7 (1973), 1798 (DOI 10.1103/PhysRevB.7.1798)
138 std::cout << " Reading M shell calculations.\n";
139 infile.open(path + "emerc.tab", std::ios::in);
140 if (!infile) {
141 std::cerr << " Could not open emerc.tab.\n";
142 return false;
143 }
144 for (std::string line; std::getline(infile, line);) {
145 ltrim(line);
146 if (IsComment(line)) continue;
147 std::istringstream data(line);
148 size_t j;
149 double energy, a, k1;
150 data >> j >> energy >> a >> k1;
151 if (j < 1 || j > NEnergyBins) {
152 std::cerr << " Index out of range.\n";
153 ok = false;
154 break;
155 }
156 --j;
157 m_int[j] = a;
158 m_k1[j] = k1;
159 if (!m_debug) continue;
160 if (j < 19 || j > 30) continue;
161 std::printf(" %4zu %11.2f %11.2f %12.6f %12.6f\n",
162 j + 1, m_E[j], energy, m_int[j], m_k1[j]);
163 }
164 infile.close();
165 if (!ok) {
166 std::cerr << " Error reading M shell data.\n";
167 return false;
168 }
169
170 double s0 = 0.;
171 double s1 = 0.;
172 double avI = 0.;
173 for (size_t j = 0; j < NEnergyBins; ++j) {
174 const auto logE = log(m_E[j]);
175 const double dE = m_E[j + 1] - m_E[j];
176 s0 += m_dfdE[j] * dE;
177 s1 += m_dfdE[j] * dE * m_E[j];
178 avI += m_dfdE[j] * dE * logE;
179 }
180 if (m_debug) {
181 std::printf(" S0 = %15.5f\n", s0);
182 std::printf(" S1 = %15.5f\n", s1);
183 std::printf(" lnI = %15.5f\n", avI);
184 }
185 m_initialised = true;
186 return true;
187}
188
190
191 if (!m_initialised) {
192 std::cerr << m_className << "::ComputeCrossSection: Not initialised.\n";
193 return false;
194 }
195
196 const double bg = GetBetaGamma();
197 if (m_debug) {
198 std::cerr << m_className << "::ComputeCrossSection:\n"
199 << " Calculating differential cross-section for bg = "
200 << bg << ".\n";
201 }
202 const double gamma = sqrt(bg * bg + 1.);
203 // Prefactors in Bhabha formula.
204 const double g1 = (gamma - 1.) * (gamma - 1.) / (gamma * gamma);
205 const double g2 = (2. * gamma - 1.) / (gamma * gamma);
206
207 const double ek = GetKineticEnergy();
208 const double rm = ElectronMass / m_mass;
209 // Maximum energy transfer.
210 double emax = 2 * ElectronMass * bg * bg;
211 if (m_isElectron) {
212 emax = 0.5 * ek;
213 } else {
214 emax = 2 * ElectronMass * bg * bg / (1. + 2 * gamma * rm + rm * rm);
215 }
216 if (m_debug) std::printf(" Max. energy transfer: %12.4f eV\n", emax);
217 const double betaSq = bg * bg / (1. + bg * bg);
218 m_speed = SpeedOfLight * sqrt(betaSq);
219 constexpr size_t nTerms = 3;
220 std::array<double, nTerms + 1> m0;
221 std::array<double, nTerms + 1> m1;
222 std::array<double, nTerms + 1> m2;
223 m0.fill(0.);
224 m1.fill(0.);
225 m2.fill(0.);
226
227 std::array<std::array<double, NEnergyBins>, nTerms + 1> cs;
228 std::vector<double> cdf(NEnergyBins, 0.);
229
230 const auto betaSqOverEmax = betaSq / emax;
231 const auto twoMeBetaSq = 2 * ElectronMass * betaSq;
232
233 size_t jmax = 0;
234 for (size_t j = 0; j < NEnergyBins; ++j) {
235 ++jmax;
236 if (m_E[j] > emax) break;
237 const auto e2 = m_E[j] * m_E[j];
238 double q1 = RydbergEnergy;
239 // CCS-33, 39 & 47
240 if (m_E[j] < 11.9) {
241 q1 = m_k1[j] * m_k1[j] * RydbergEnergy;
242 } else if (m_E[j] < 100.) {
243 constexpr double k1 = 0.025;
244 q1 = k1 * k1 * RydbergEnergy;
245 }
246 const auto qmin = e2 / twoMeBetaSq;
247 if (m_E[j] < 11.9 && q1 <= qmin) {
248 cs[0][j] = 0.;
249 } else {
250 cs[0][j] = m_E[j] * m_dfdE[j] * log(q1 / qmin);
251 }
252 // Fano Eq. (47).
253 auto b1 = 1. - betaSq * m_eps1[j];
254 if (b1 == 0.) b1 = 1.e-20;
255 const auto b2 = betaSq * m_eps2[j];
256 const auto g = m_E[j] * m_dfdE[j] * (-0.5) * log(b1 * b1 + b2 * b2);
257 auto theta = atan(b2 / b1);
258 if (theta < 0.) theta += Pi;
259 const auto epsSq = m_eps1[j] * m_eps1[j] + m_eps2[j] * m_eps2[j];
260 const auto h = m_conv * e2 * (betaSq - m_eps1[j] / epsSq) * theta;
261 cs[1][j] = g + h;
262 // The integral was over d(lnK) rather than d(lnQ)
263 cs[2][j] = 2 * m_int[j];
264 if (m_isElectron) {
265 // Uehling Eq. (9).
266 const double u = m_E[j] / (ek - m_E[j]);
267 const double v = m_E[j] / ek;
268 cs[2][j] *= (1. + u * u + g1 * v * v - g2 * u);
269 } else {
270 // Uehling Eq. (2).
271 cs[2][j] *= (1. - m_E[j] * betaSqOverEmax);
272 }
273 cs[3][j] = 0.;
274 cs[nTerms][j] = 0.;
275 const double dE = m_E[j + 1] - m_E[j];
276 for (size_t k = 0; k < nTerms; ++k) {
277 m0[k] += cs[k][j] * dE / e2;
278 m1[k] += cs[k][j] * dE / m_E[j];
279 m2[k] += cs[k][j] * dE;
280 cs[nTerms][j] += cs[k][j];
281 }
282 m0[nTerms] += cs[nTerms][j] * dE / e2;
283 m1[nTerms] += cs[nTerms][j] * dE / m_E[j];
284 m2[nTerms] += cs[nTerms][j] * dE;
285 cs[nTerms][j] /= e2;
286 if (!m_debug) continue;
287 if ((j + 1) % 10 != 0) continue;
288 std::printf(" %4zu %9.1f %9.4f %9.4f %9.4f %9.4f %9.4f %9.4f\n",
289 j + 1, m_E[j], m_dfdE[j], g, h, cs[0][j], cs[1][j], cs[2][j]);
290 }
291 if (jmax < NEnergyBins) cdf.resize(jmax);
292 if (m_debug) {
293 printf(" M0: %15.4f %15.4f %15.4f %15.4f\n", m0[0], m0[1], m0[2], m0[3]);
294 printf(" M1: %15.4f %15.4f %15.4f %15.4f\n", m1[0], m1[1], m1[2], m1[3]);
295 printf(" M2: %15.4f %15.4f %15.4f %15.4f\n", m2[0], m2[1], m2[2], m2[3]);
296 }
297 // Calculate the cumulative differential cross-section.
298 cdf[0] = 0.5 * m_E[0] * cs[nTerms][0];
299 for (size_t j = 1; j < jmax; ++j) {
300 const double dE = m_E[j] - m_E[j - 1];
301 cdf[j] = cdf[j - 1] + 0.5 * (cs[nTerms][j - 1] + cs[nTerms][j]) * dE;
302 }
303
304 constexpr double ary = BohrRadius * RydbergEnergy;
305 constexpr double prefactor = 8 * Pi * ary * ary / ElectronMass;
306 const double dec = m_q * m_q * m_density * prefactor / betaSq;
307 m_imfp = m0.back() * dec;
308 m_dEdx = m1.back() * dec;
309 if (m_debug) {
310 std::printf(" M0 = %12.4f cm-1 ... inverse mean free path\n",
311 m_imfp);
312 std::printf(" M1 = %12.4f keV/cm ... dE/dx\n", m_dEdx * 1.e-3);
313 std::printf(" M2 = %12.4f keV2/cm\n", m2.back() * dec * 1.e-6);
314 }
315 // Calculate the residual cross-section.
316 double integral = cdf.back();
317 if (emax > m_E.back()) {
318 const double e1 = m_E.back();
319 const double rm0 = (1. - 720. * betaSqOverEmax) * (
320 (1. / e1 - 1. / emax) +
321 2 * (1. / (e1 * e1) - 1. / (emax * emax))) -
322 betaSqOverEmax * log(emax / e1);
323 if (m_debug) std::printf(" Residual M0 = %15.5f\n", rm0 * 14. * dec);
324 m_imfp += rm0 * 14. * dec;
325 m0.back() += rm0;
326 integral += 14. * rm0;
327 }
328 const double scale = 1. / integral;
329 for (size_t j = 0; j < jmax; ++j) {
330 cdf[j] *= scale;
331 if (!m_debug) continue;
332 if ((j + 1) % 20 != 0) continue;
333 std::printf(" %4zu %9.1f %15.6f\n", j + 1, m_E[j], cdf[j]);
334 }
335 m_tab.fill(0.);
336 for (size_t i = 0; i < NCdfBins; ++i) {
337 constexpr double step = 1. / NCdfBins;
338 const double x = (i + 1) * step;
339 // Interpolate.
340 m_tab[i] = 0.;
341 const auto it1 = std::upper_bound(cdf.cbegin(), cdf.cend(), x);
342 if (it1 == cdf.cbegin()) {
343 m_tab[i] = m_E.front();
344 continue;
345 }
346 const auto it0 = std::prev(it1);
347 const double x0 = *it0;
348 const double x1 = *it1;
349 const double y0 = m_E[it0 - cdf.cbegin()];
350 const double y1 = m_E[it1 - cdf.cbegin()];
351 const double f0 = (x - x0) / (x1 - x0);
352 const double f1 = 1. - f0;
353 m_tab[i] = f0 * y0 + f1 * y1;
354 }
355 return true;
356}
357
358bool TrackBichsel::NewTrack(const double x0, const double y0, const double z0,
359 const double t0, const double dx0, const double dy0,
360 const double dz0) {
361
362 // Reset the list of clusters.
363 m_clusters.clear();
364 m_cluster = 0;
365
366 // Make sure a sensor has been defined.
367 if (!m_sensor) {
368 std::cerr << m_className << "::NewTrack: Sensor is not defined.\n";
369 return false;
370 }
371
372 // If not yet done, compute the cross-section table.
373 if (m_isChanged) {
374 if (!ComputeCrossSection()) {
375 std::cerr << m_className << "::NewTrack:\n"
376 << " Could not calculate cross-section table.\n";
377 return false;
378 }
379 m_isChanged = false;
380 }
381
382 // Make sure we are inside a medium.
383 Medium* medium = m_sensor->GetMedium(x0, y0, z0);
384 if (!medium || !medium->IsIonisable()) {
385 std::cerr << m_className << "::NewTrack:\n"
386 << " No ionisable medium at initial position.\n";
387 return false;
388 }
389
390 // Check if the medium is silicon.
391 if (medium->GetName() != "Si") {
392 std::cerr << m_className << "::NewTrack: Medium is not silicon.\n";
393 return false;
394 }
395
396 // Normalise the direction vector.
397 double dx = dx0, dy = dy0, dz = dz0;
398 const double d = sqrt(dx0 * dx0 + dy0 * dy0 + dz0 * dz0);
399 if (d < Small) {
400 // In case of a null vector, choose a random direction.
401 RndmDirection(dx, dy, dz);
402 } else {
403 dx = dx0 / d;
404 dy = dy0 / d;
405 dz = dz0 / d;
406 }
407 const double dt = m_speed > 0. ? 1. / m_speed : 0.;
408
409 double x = x0;
410 double y = y0;
411 double z = z0;
412 double t = t0;
413 const double mfp = 1. / m_imfp;
414 double ekin = GetKineticEnergy();
415 while (ekin > 0.) {
416 const double step = -log(RndmUniformPos()) * mfp;
417 x += dx * step;
418 y += dy * step;
419 z += dz * step;
420 t += dt * step;
421
422 medium = m_sensor->GetMedium(x, y, z);
423 if (!medium || !medium->IsIonisable() || medium->GetName() != "Si") {
424 if (m_debug) {
425 std::cout << m_className << "::NewTrack: Particle left the medium.\n";
426 }
427 break;
428 }
429
430 Cluster cluster;
431 cluster.x = x;
432 cluster.y = y;
433 cluster.z = z;
434 cluster.t = t;
435 const double u = NCdfBins * RndmUniform();
436 const size_t j = static_cast<size_t>(std::floor(u));
437 if (j == 0) {
438 cluster.energy = u * m_tab.front();
439 } else if (j >= NCdfBins) {
440 cluster.energy = m_tab.back();
441 } else {
442 cluster.energy = m_tab[j - 1] + (u - j) * (m_tab[j] - m_tab[j - 1]);
443 }
444 ekin -= cluster.energy;
445 m_clusters.push_back(std::move(cluster));
446 }
447 m_cluster = m_clusters.size() + 2;
448 return true;
449}
450
451bool TrackBichsel::GetCluster(double& xc, double& yc, double& zc,
452 double& tc, int& ne, double& ec, double& extra) {
453 xc = yc = zc = tc = ec = extra = 0.;
454 ne = 0;
455 if (m_clusters.empty()) return false;
456 // Increment the cluster index.
457 if (m_cluster < m_clusters.size()) {
458 ++m_cluster;
459 } else if (m_cluster > m_clusters.size()) {
460 m_cluster = 0;
461 }
462 if (m_cluster >= m_clusters.size()) return false;
463
464 xc = m_clusters[m_cluster].x;
465 yc = m_clusters[m_cluster].y;
466 zc = m_clusters[m_cluster].z;
467 tc = m_clusters[m_cluster].t;
468 ec = m_clusters[m_cluster].energy;
469 return true;
470}
471
473
474 if (m_isChanged) {
475 if (!ComputeCrossSection()) return 0.;
476 m_isChanged = false;
477 }
478 return m_imfp;
479}
480
482
483 if (m_isChanged) {
484 if (!ComputeCrossSection()) return 0.;
485 m_isChanged = false;
486 }
487 return m_dEdx;
488}
489
490}
Solid crystalline silicon
Abstract base class for media.
Definition Medium.hh:16
virtual double GetNumberDensity() const
Get the number density [cm-3].
Definition Medium.hh:63
const std::string & GetName() const
Get the medium name/identifier.
Definition Medium.hh:26
bool IsIonisable() const
Is charge deposition by charged particles/photon enabled in this medium?
Definition Medium.hh:81
double GetClusterDensity() override
double GetStoppingPower() override
Get the stopping power (mean energy loss [eV] per cm).
bool NewTrack(const double x0, const double y0, const double z0, const double t0, const double dx0, const double dy0, const double dz0) override
bool GetCluster(double &xc, double &yc, double &zc, double &tc, int &nc, double &ec, double &extra) override
TrackBichsel()
Default constructor.
double GetKineticEnergy() const
Return the kinetic energy of the projectile.
Definition Track.hh:62
double GetBetaGamma() const
Return the of the projectile.
Definition Track.hh:54
Sensor * m_sensor
Definition Track.hh:114
bool m_isElectron
Definition Track.hh:111
bool m_isChanged
Definition Track.hh:116
std::string m_className
Definition Track.hh:104
double m_mass
Definition Track.hh:108
Track()=delete
Default constructor.
double RndmUniform()
Draw a random number uniformly distributed in the range [0, 1).
Definition Random.hh:14
void ltrim(std::string &line)
Definition Utilities.hh:12
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