Garfield++ 4.0
A toolkit for the detailed simulation of particle detectors based on ionisation measurement in gases and semiconductors
Loading...
Searching...
No Matches
TrackElectron.cc
Go to the documentation of this file.
1#include <cmath>
2#include <iostream>
3
6#include "Garfield/Random.hh"
7#include "Garfield/Sensor.hh"
9
10namespace Garfield {
11
13 m_className = "TrackElectron";
14
15 // Setup the particle properties.
16 m_q = -1;
17 m_spin = 1;
18 m_mass = ElectronMass;
19 m_isElectron = true;
20 SetBetaGamma(3.);
21 m_particleName = "electron";
22}
23
24void TrackElectron::SetParticle(const std::string& particle) {
25 if (particle != "electron" && particle != "e" && particle != "e-") {
26 std::cerr << m_className << "::SetParticle: Only electrons are allowed.\n";
27 }
28}
29
30bool TrackElectron::NewTrack(const double x0, const double y0, const double z0,
31 const double t0, const double dx0,
32 const double dy0, const double dz0) {
33 m_ready = false;
34
35 // Make sure the sensor has been set.
36 if (!m_sensor) {
37 std::cerr << m_className << "::NewTrack: Sensor is not defined.\n";
38 return false;
39 }
40
41 // Get the medium at this location and check if it is "ionisable".
42 Medium* medium = nullptr;
43 if (!m_sensor->GetMedium(x0, y0, z0, medium)) {
44 std::cerr << m_className << "::NewTrack:\n";
45 std::cerr << " No medium at initial position.\n";
46 return false;
47 }
48 if (!medium->IsIonisable()) {
49 std::cerr << m_className << "::NewTrack:\n";
50 std::cerr << " Medium at initial position is not ionisable.\n";
51 return false;
52 }
53
54 // Check if the medium is a gas.
55 if (!medium->IsGas()) {
56 std::cerr << m_className << "::NewTrack:\n";
57 std::cerr << " Medium at initial position is not a gas.\n";
58 return false;
59 }
60
61 if (!SetupGas(medium)) {
62 std::cerr << m_className << "::NewTrack:\n";
63 std::cerr << " Properties of medium " << medium->GetName()
64 << " are not available.\n";
65 return false;
66 }
67
68 if (!UpdateCrossSection()) {
69 std::cerr << m_className << "::NewTrack:\n";
70 std::cerr << " Cross-sections could not be calculated.\n";
71 return false;
72 }
73
74 m_mediumName = medium->GetName();
75
76 m_x = x0;
77 m_y = y0;
78 m_z = z0;
79 m_t = t0;
80 const double dd = sqrt(dx0 * dx0 + dy0 * dy0 + dz0 * dz0);
81 if (dd < Small) {
82 if (m_debug) {
83 std::cout << m_className << "::NewTrack:\n";
84 std::cout << " Direction vector has zero norm.\n";
85 std::cout << " Initial direction is randomized.\n";
86 }
87 RndmDirection(m_dx, m_dy, m_dz);
88 } else {
89 // Normalize the direction vector.
90 m_dx = dx0 / dd;
91 m_dy = dy0 / dd;
92 m_dz = dz0 / dd;
93 }
94
95 m_ready = true;
96 return true;
97}
98
99bool TrackElectron::GetCluster(double& xcls, double& ycls, double& zcls,
100 double& tcls, int& ncls, double& edep,
101 double& extra) {
102 edep = extra = 0.;
103 ncls = 0;
104
105 m_electrons.clear();
106
107 if (!m_ready) {
108 std::cerr << m_className << "::GetCluster:\n"
109 << " Track not initialized. Call NewTrack first.\n";
110 return false;
111 }
112
113 // Draw a step length and propagate the electron.
114 const double d = -m_mfp * log(RndmUniformPos());
115 m_x += d * m_dx;
116 m_y += d * m_dy;
117 m_z += d * m_dz;
118 m_t += d / (sqrt(m_beta2) * SpeedOfLight);
119
120 if (!m_sensor->IsInArea(m_x, m_y, m_z)) {
121 m_ready = false;
122 return false;
123 }
124
125 Medium* medium = nullptr;
126 if (!m_sensor->GetMedium(m_x, m_y, m_z, medium)) {
127 m_ready = false;
128 return false;
129 }
130
131 if (medium->GetName() != m_mediumName ||
132 medium->GetNumberDensity() != m_mediumDensity || !medium->IsIonisable()) {
133 m_ready = false;
134 return false;
135 }
136
137 xcls = m_x;
138 ycls = m_y;
139 zcls = m_z;
140 tcls = m_t;
141 const double r = RndmUniform();
142 int iComponent = 0;
143 const int nComponents = m_components.size();
144 for (int i = 0; i < nComponents; ++i) {
145 if (r <= RndmUniform()) {
146 iComponent = i;
147 break;
148 }
149 }
150
151 // Sample secondary electron energy according to
152 // Opal-Beaty-Peterson splitting function.
153 const double e0 = ElectronMass * (sqrt(1. / (1. - m_beta2)) - 1.);
154 double esec =
155 m_components[iComponent].wSplit *
156 tan(RndmUniform() * atan((e0 - m_components[iComponent].ethr) /
157 (2. * m_components[iComponent].wSplit)));
158 esec = m_components[iComponent].wSplit *
159 pow(esec / m_components[iComponent].wSplit, 0.9524);
160 m_electrons.resize(1);
161 m_electrons[0].energy = esec;
162 m_electrons[0].x = xcls;
163 m_electrons[0].y = ycls;
164 m_electrons[0].z = zcls;
165
166 ncls = 1;
167 edep = esec;
168
169 return true;
170}
171
173 if (!m_ready) {
174 std::cerr << m_className << "::GetClusterDensity:\n";
175 std::cerr << " Track has not been initialized.\n";
176 return 0.;
177 }
178
179 if (m_mfp <= 0.) {
180 std::cerr << m_className << "::GetClusterDensity:\n";
181 std::cerr << " Mean free path is not available.\n";
182 return 0.;
183 }
184
185 return 1. / m_mfp;
186}
187
189 if (!m_ready) {
190 std::cerr << m_className << "::GetStoppingPower:\n";
191 std::cerr << " Track has not been initialised.\n";
192 return 0.;
193 }
194
195 constexpr double prefactor =
196 4 * Pi * HbarC * HbarC / (ElectronMass * ElectronMass);
197 const double lnBg2 = log(m_beta2 / (1. - m_beta2));
198
199 double dedx = 0.;
200 // Primary energy
201 const double e0 = ElectronMass * (sqrt(1. / (1. - m_beta2)) - 1.);
202 const int nComponents = m_components.size();
203 for (int i = nComponents; i--;) {
204 // Calculate the mean number of clusters per cm.
205 const double cmean =
206 m_mediumDensity * m_components[i].fraction * (prefactor / m_beta2) *
207 (m_components[i].m2Ion * (lnBg2 - m_beta2) + m_components[i].cIon);
208 const double ew =
209 (e0 - m_components[i].ethr) / (2 * m_components[i].wSplit);
210 // Calculate the mean secondary electron energy.
211 const double emean =
212 (m_components[i].wSplit / (2 * atan(ew))) * log(1. + ew * ew);
213 dedx += cmean * emean;
214 }
215
216 return dedx;
217}
218
219bool TrackElectron::SetupGas(Medium* gas) {
220 m_components.clear();
221
222 if (!gas) {
223 std::cerr << m_className << "::SetupGas:\n";
224 std::cerr << " Medium is not defined.\n";
225 return false;
226 }
227
228 m_mediumDensity = gas->GetNumberDensity();
229 const int nComponents = gas->GetNumberOfComponents();
230 if (nComponents <= 0) {
231 std::cerr << m_className << "::SetupGas:\n";
232 std::cerr << " Medium composition is not defined.\n";
233 return false;
234 }
235 m_components.resize(nComponents);
236
237 // Density correction parameters from
238 // R. M. Sternheimer, M. J. Berger, S. M. Seltzer,
239 // Atomic Data and Nuclear Data Tables 30 (1984), 261-271
240 bool ok = true;
241 for (int i = nComponents; i--;) {
242 std::string gasname = "";
243 double frac = 0.;
244 gas->GetComponent(i, gasname, frac);
245 m_components[i].fraction = frac;
246 m_components[i].p = 0.;
247 if (gasname == "CF4") {
248 m_components[i].m2Ion = 7.2;
249 m_components[i].cIon = 93.;
250 m_components[i].x0Dens = 1.;
251 m_components[i].x1Dens = 0.;
252 m_components[i].cDens = 0.;
253 m_components[i].aDens = 0.;
254 m_components[i].mDens = 0.;
255 m_components[i].ethr = 15.9;
256 m_components[i].wSplit = 19.5;
257 } else if (gasname == "Ar") {
258 m_components[i].m2Ion = 3.593;
259 m_components[i].cIon = 39.7;
260 m_components[i].x0Dens = 1.7635;
261 m_components[i].x1Dens = 4.4855;
262 m_components[i].cDens = 11.9480;
263 m_components[i].aDens = 0.19714;
264 m_components[i].mDens = 2.9618;
265 m_components[i].ethr = 15.75961;
266 m_components[i].wSplit = 15.;
267 } else if (gasname == "He") {
268 m_components[i].m2Ion = 0.489;
269 m_components[i].cIon = 5.5;
270 m_components[i].x0Dens = 2.2017;
271 m_components[i].x1Dens = 3.6122;
272 m_components[i].cDens = 11.1393;
273 m_components[i].aDens = 0.13443;
274 m_components[i].mDens = 5.8347;
275 m_components[i].ethr = 24.58739;
276 m_components[i].wSplit = 10.5;
277 } else if (gasname == "He-3") {
278 m_components[i].m2Ion = 0.489;
279 m_components[i].cIon = 5.5;
280 m_components[i].x0Dens = 2.2017;
281 m_components[i].x1Dens = 3.6122;
282 m_components[i].cDens = 11.1393;
283 m_components[i].aDens = 0.13443;
284 m_components[i].mDens = 5.8347;
285 m_components[i].ethr = 24.58739;
286 m_components[i].wSplit = 10.5;
287 } else if (gasname == "Ne") {
288 m_components[i].m2Ion = 1.69;
289 m_components[i].cIon = 17.8;
290 m_components[i].x0Dens = 2.0735;
291 m_components[i].x1Dens = 4.6421;
292 m_components[i].cDens = 11.9041;
293 m_components[i].aDens = 0.08064;
294 m_components[i].mDens = 3.5771;
295 m_components[i].ethr = 21.56454;
296 m_components[i].wSplit = 19.5;
297 } else if (gasname == "Kr") {
298 m_components[i].m2Ion = 5.5;
299 m_components[i].cIon = 56.9;
300 m_components[i].x0Dens = 1.7158;
301 m_components[i].x1Dens = 5.0748;
302 m_components[i].cDens = 12.5115;
303 m_components[i].aDens = 0.07446;
304 m_components[i].mDens = 3.4051;
305 m_components[i].ethr = 13.996;
306 m_components[i].wSplit = 21.;
307 } else if (gasname == "Xe") {
308 m_components[i].m2Ion = 8.04;
309 m_components[i].cIon = 75.25;
310 m_components[i].x0Dens = 1.5630;
311 m_components[i].x1Dens = 4.7371;
312 m_components[i].cDens = 12.7281;
313 m_components[i].aDens = 0.23314;
314 m_components[i].mDens = 2.7414;
315 m_components[i].ethr = 12.129843;
316 m_components[i].wSplit = 23.7;
317 } else if (gasname == "CH4") {
318 m_components[i].m2Ion = 3.75;
319 m_components[i].cIon = 42.5;
320 m_components[i].x0Dens = 1.6263;
321 m_components[i].x1Dens = 3.9716;
322 m_components[i].cDens = 9.5243;
323 m_components[i].aDens = 0.09253;
324 m_components[i].mDens = 3.6257;
325 m_components[i].ethr = 12.65;
326 m_components[i].wSplit = 8.;
327 } else if (gasname == "iC4H10") {
328 m_components[i].m2Ion = 15.5;
329 m_components[i].cIon = 160.;
330 m_components[i].x0Dens = 1.3788;
331 m_components[i].x1Dens = 3.7524;
332 m_components[i].cDens = 8.5633;
333 m_components[i].aDens = 0.10852;
334 m_components[i].mDens = 3.4884;
335 m_components[i].ethr = 10.67;
336 m_components[i].wSplit = 7.;
337 } else if (gasname == "CO2") {
338 m_components[i].m2Ion = 5.6;
339 m_components[i].cIon = 57.91;
340 m_components[i].x0Dens = 1.6294;
341 m_components[i].x1Dens = 4.1825;
342 m_components[i].aDens = 0.11768;
343 m_components[i].mDens = 3.3227;
344 m_components[i].ethr = 13.777;
345 m_components[i].wSplit = 13.;
346 } else if (gasname == "N2") {
347 m_components[i].m2Ion = 3.35;
348 m_components[i].cIon = 38.1;
349 m_components[i].x0Dens = 1.7378;
350 m_components[i].x1Dens = 4.1323;
351 m_components[i].cDens = 10.5400;
352 m_components[i].aDens = 0.15349;
353 m_components[i].mDens = 3.2125;
354 m_components[i].ethr = 15.581;
355 m_components[i].wSplit = 13.8;
356 } else {
357 std::cerr << m_className << "::SetupGas:\n";
358 std::cerr << " Cross-section for " << gasname
359 << " is not available.\n";
360 ok = false;
361 }
362 }
363
364 if (!ok) {
365 m_components.clear();
366 }
367
368 return true;
369}
370
371bool TrackElectron::UpdateCrossSection() {
372 constexpr double prefactor =
373 4 * Pi * HbarC * HbarC / (ElectronMass * ElectronMass);
374 const double lnBg2 = log(m_beta2 / (1. - m_beta2));
375 // Parameter X in the Sternheimer fit formula
376 const double eta = m_mediumDensity / LoschmidtNumber;
377 const double x = 0.5 * (lnBg2 + log(eta)) / log(10.);
378 double csSum = 0.;
379 const int nComponents = m_components.size();
380 for (int i = nComponents; i--;) {
381 double delta = 0.;
382 if (m_components[i].x0Dens < m_components[i].x1Dens &&
383 x >= m_components[i].x0Dens) {
384 delta = 2 * log(10.) * x - m_components[i].cDens;
385 if (x < m_components[i].x1Dens) {
386 delta += m_components[i].aDens *
387 pow(m_components[i].x1Dens - x, m_components[i].mDens);
388 }
389 }
390 const double cs = (m_components[i].fraction * prefactor / m_beta2) *
391 (m_components[i].m2Ion * (lnBg2 - m_beta2 - delta) +
392 m_components[i].cIon);
393 m_components[i].p = cs;
394 csSum += cs;
395 }
396
397 if (csSum <= 0.) {
398 std::cerr << m_className << "::UpdateCrossSection:\n";
399 std::cerr << " Total cross-section <= 0.\n";
400 return false;
401 }
402
403 m_mfp = 1. / (csSum * m_mediumDensity);
404
405 for (int i = 0; i < nComponents; ++i) {
406 m_components[i].p /= csSum;
407 if (i > 0) m_components[i].p += m_components[i - 1].p;
408 }
409
410 return true;
411}
412}
Abstract base class for media.
Definition: Medium.hh:13
virtual void GetComponent(const unsigned int i, std::string &label, double &f)
Get the name and fraction of a given component.
Definition: Medium.cc:105
virtual double GetNumberDensity() const
Get the number density [cm-3].
Definition: Medium.hh:60
unsigned int GetNumberOfComponents() const
Get number of components of the medium.
Definition: Medium.hh:45
const std::string & GetName() const
Get the medium name/identifier.
Definition: Medium.hh:23
virtual bool IsGas() const
Is this medium a gas?
Definition: Medium.hh:25
bool IsIonisable() const
Is charge deposition by charged particles/photon enabled in this medium?
Definition: Medium.hh:78
bool IsInArea(const double x, const double y, const double z)
Check if a point is inside the user area.
Definition: Sensor.cc:254
bool GetMedium(const double x, const double y, const double z, Medium *&medium)
Get the medium at (x, y, z).
Definition: Sensor.cc:159
virtual void SetParticle(const std::string &particle)
virtual double GetClusterDensity()
virtual bool NewTrack(const double x0, const double y0, const double z0, const double t0, const double dx0, const double dy0, const double dz0)
virtual bool GetCluster(double &xcls, double &ycls, double &zcls, double &tcls, int &ncls, double &ecls, double &extra)
virtual double GetStoppingPower()
Get the stopping power (mean energy loss [eV] per cm).
Abstract base class for track generation.
Definition: Track.hh:14
Sensor * m_sensor
Definition: Track.hh:112
void SetBetaGamma(const double bg)
Set the relative momentum of the particle.
Definition: Track.cc:103
bool m_debug
Definition: Track.hh:118
bool m_isElectron
Definition: Track.hh:109
double m_q
Definition: Track.hh:104
std::string m_particleName
Definition: Track.hh:110
double m_beta2
Definition: Track.hh:108
std::string m_className
Definition: Track.hh:102
double m_mass
Definition: Track.hh:106
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:107
double RndmUniformPos()
Draw a random number uniformly distributed in the range (0, 1).
Definition: Random.hh:17
DoubleAc pow(const DoubleAc &f, double p)
Definition: DoubleAc.cpp:337