Garfield++ 5.0
A toolkit for the detailed simulation of particle detectors based on ionisation measurement in gases and semiconductors
Loading...
Searching...
No Matches
TrackTrim.cc
Go to the documentation of this file.
1#include <fstream>
2#include <iostream>
3#include <sstream>
4#include <algorithm>
5#include <numeric>
6
9#include "Garfield/Random.hh"
10#include "Garfield/Sensor.hh"
11#include "Garfield/Utilities.hh"
12#include "Garfield/TrackTrim.hh"
13
14namespace {
15
16void Rotate(const std::array<double, 3>& f,
17 const std::array<double, 3>& t,
18 std::array<double, 3>& a) {
19
20 // T. Moller and J. F. Hughes,
21 // Efficiently Building a Matrix to Rotate One Vector to Another, 1999
22
23 // We assume that f and t are unit vectors.
24 // Calculate the cross-product.
25 std::array<double, 3> v = {f[1] * t[2] - f[2] * t[1],
26 f[2] * t[0] - f[0] * t[2],
27 f[0] * t[1] - f[1] * t[0]};
28 const auto v2 = std::inner_product(v.cbegin(), v.cend(), v.cbegin(), 0.);
29 if (v2 < Garfield::Small) return;
30 const double c = std::inner_product(f.cbegin(), f.cend(), t.cbegin(), 0.);
31 const double h = (1. - c) / v2;
32 std::array<std::array<double, 3>, 3> r;
33 if (fabs(c) > 0.99) {
34 // f and t are not parallel.
35 r[0][0] = h * v[0] * v[0] + c;
36 r[0][1] = h * v[0] * v[1] - v[2];
37 r[0][2] = h * v[0] * v[2] + v[1];
38 r[1][0] = h * v[0] * v[1] + v[2];
39 r[1][1] = h * v[1] * v[1] + c;
40 r[1][2] = h * v[1] * v[2] - v[0];
41 r[2][0] = h * v[0] * v[2] - v[1];
42 r[2][1] = h * v[1] * v[2] + v[0];
43 r[2][2] = h * v[2] * v[2] + c;
44 } else {
45 // f and t are nearly parallel.
46 std::array<double, 3> x = {0, 0, 0};
47 if (fabs(f[0]) < fabs(f[1]) && fabs(f[0]) < fabs(f[2])) {
48 x[0] = 1;
49 } else if (fabs(f[1]) < fabs(f[0]) && fabs(f[1]) < fabs(f[2])) {
50 x[1] = 1;
51 } else {
52 x[2] = 1;
53 }
54 const std::array<double, 3> u = {x[0] - f[0], x[1] - f[1], x[2] - f[2]};
55 const std::array<double, 3> g = {x[0] - t[0], x[1] - t[1], x[2] - t[2]};
56 const double u2 = std::inner_product(u.cbegin(), u.cend(), u.cbegin(), 0.);
57 const double g2 = std::inner_product(g.cbegin(), g.cend(), g.cbegin(), 0.);
58 const double ug = std::inner_product(u.cbegin(), u.cend(), g.cbegin(), 0.);
59 const double c0 = -2. / u2;
60 const double c1 = -2. / g2;
61 const double c2 = 4. * ug / (u2 * g2);
62 for (size_t i = 0; i < 3; ++i) {
63 for (size_t j = 0; j < 3; ++j) {
64 r[i][j] = c0 * u[i] * u[j] + c1 * g[i] * g[j] + c2 * g[i] * u[j];
65 if (i == j) r[i][j] += 1.;
66 }
67 }
68 }
69 // Compute the rotated vector.
70 std::array<double, 3> b = {0., 0., 0.};
71 for (size_t i = 0; i < 3; ++i) {
72 for (size_t j = 0; j < 3; ++j) {
73 b[i] += r[i][j] * a[j];
74 }
75 }
76 std::swap(a, b);
77}
78
79double Speed(const double ekin, const double mass) {
80 const double rk = ekin / mass;
81 const double gamma = 1. + rk;
82 const double beta2 = rk > 1.e-5 ? 1. - 1. / (gamma * gamma) : 2. * rk;
83 return sqrt(beta2) * Garfield::SpeedOfLight;
84}
85
86}
87
88namespace Garfield {
89
90TrackTrim::TrackTrim(Sensor* sensor) : Track("Trim") {
91 m_sensor = sensor;
92 m_q = 1.;
93}
94
95void TrackTrim::SetParticle(const std::string& /*particle*/) {
96 std::cerr << m_className << "::SetParticle: Not applicable.\n";
97}
98
99bool TrackTrim::ReadFile(const std::string& filename,
100 const unsigned int nIons, const unsigned int nSkip) {
101
102 // TRMREE - Reads the TRIM EXYZ file.
103
104 // Reset.
105 m_ekin = 0.;
106 m_ions.clear();
107 m_ion = 0;
108 m_clusters.clear();
109 m_cluster = 0;
110
111 std::ifstream infile(filename);
112 if (!infile) {
113 std::cerr << m_className << "::ReadFile:\n"
114 << " Unable to open the EXYZ file (" << filename << ").\n";
115 return false;
116 }
117
118 constexpr double Angstrom = 1.e-8;
119 unsigned int nRead = 0;
120 unsigned int ionNumber = 0;
121 bool header = true;
122 double mass = 0.;
123 std::vector<float> x;
124 std::vector<float> y;
125 std::vector<float> z;
126 std::vector<float> dedx;
127 std::vector<float> ekin;
128 for (std::string line; std::getline(infile, line);) {
129 if (line.find("------- ") != std::string::npos) {
130 // Reached the end of the header.
131 header = false;
132 continue;
133 } else if (header) {
134 if (line.find("Ion Data: ") != std::string::npos) {
135 // Read the next line.
136 std::getline(infile, line);
137 auto words = tokenize(line);
138 if (words.size() >= 3) {
139 m_particleName = words[0];
140 mass = std::stod(words[1]);
141 auto pos = words[2].find("keV");
142 if (pos != std::string::npos) {
143 m_ekin = 1.e3 * std::stod(words[2].substr(0, pos));
144 }
145 }
146 }
147 // Otherwise, skip the header.
148 continue;
149 }
150 auto words = tokenize(line);
151 if (words.size() < 6) {
152 std::cerr << m_className << "::ReadFile: Unexpected line:\n"
153 << line << "\n";
154 continue;
155 }
156 if (ionNumber != std::stoul(words[0])) {
157 // New ion.
158 if (ionNumber > 0) {
159 if (nRead >= nSkip) AddIon(x, y, z, dedx, ekin);
160 x.clear();
161 y.clear();
162 z.clear();
163 dedx.clear();
164 ekin.clear();
165 ++nRead;
166 // Stop if we are done reading the requested number of ions.
167 if (nIons > 0 && m_ions.size() >= nIons) break;
168 }
169 ionNumber = std::stoi(words[0]);
170 }
171 if (nRead < nSkip) continue;
172 // Convert coordinates to cm.
173 x.push_back(std::stof(words[2]) * Angstrom);
174 y.push_back(std::stof(words[3]) * Angstrom);
175 z.push_back(std::stof(words[4]) * Angstrom);
176 // Convert stopping power from eV/A to eV/cm.
177 dedx.push_back(std::stof(words[5]) / Angstrom);
178 // Convert ion energy from keV to eV.
179 ekin.push_back(std::stof(words[1]) * 1.e3);
180 }
181 infile.close();
182 AddIon(x, y, z, dedx, ekin);
183 std::cout << m_className << "::ReadFile: Read energy vs position for "
184 << m_ions.size() << " ions.\n";
185 if (m_ekin > 0. && mass > 0.) {
186 std::cout << " Initial kinetic energy set to "
187 << m_ekin * 1.e-3 << " keV. Mass number: " << mass << ".\n";
188 m_mass = AtomicMassUnitElectronVolt * mass;
190 }
191 return true;
192}
193
194void TrackTrim::AddIon(const std::vector<float>& x,
195 const std::vector<float>& y,
196 const std::vector<float>& z,
197 const std::vector<float>& dedx,
198 const std::vector<float>& ekin) {
199
200 const size_t nPoints = x.size();
201 if (nPoints < 2) return;
202 std::vector<std::array<float, 6> > path;
203 for (size_t i = 0; i < nPoints - 1; ++i) {
204 const float dx = x[i + 1] - x[i];
205 const float dy = y[i + 1] - y[i];
206 const float dz = z[i + 1] - z[i];
207 const float dmag = sqrt(dx * dx + dy * dy + dz * dz);
208 const float scale = dmag > 0. ? 1. / dmag : 1.;
209 float eloss = 0.;
210 if (i == 0 && dedx[i] > 10. * dedx[i + 1]) {
211 eloss = dmag * dedx[i + 1];
212 } else {
213 eloss = dmag * dedx[i];
214 }
215 const float dekin = ekin[i] - ekin[i + 1];
216 if (dekin > 0.) eloss = std::min(eloss, dekin);
217 path.push_back({dx * scale, dy * scale, dz * scale, dmag, eloss, ekin[i]});
218 }
219 m_ions.push_back(std::move(path));
220}
221
223 std::cout << m_className << "::Print:\n";
224 if (m_ions.empty()) {
225 std::cerr << " No TRIM data present.\n";
226 return;
227 }
228 std::cout << " Projectile: " << m_particleName << ", "
229 << m_ekin * 1.e-3 << " keV\n"
230 << " Number of tracks: " << m_ions.size() << "\n";
231 if (m_work > 0.) {
232 std::cout << " Work function: " << m_work << " eV\n";
233 } else {
234 std::cout << " Work function: Automatic\n";
235 }
236 if (m_fset) {
237 std::cout << " Fano factor: " << m_fano << "\n";
238 } else {
239 std::cout << " Fano factor: Automatic\n";
240 }
241}
242
243bool TrackTrim::NewTrack(const double x0, const double y0, const double z0,
244 const double t0, const double dx0, const double dy0, const double dz0) {
245
246 // TRMGEN - Generates TRIM clusters
247
248 if (m_ions.empty()) {
249 std::cerr << m_className << "::NewTrack: No TRIM data present.\n";
250 return false;
251 }
252
253 if (m_ion >= m_ions.size()) {
254 // Rewind.
255 std::cout << m_className << "::NewTrack: Rewinding.\n";
256 m_ion = 0;
257 }
258
259 // Verify that a sensor has been set.
260 if (!m_sensor) {
261 std::cerr << m_className << "::NewTrack: Sensor is not defined.\n";
262 return false;
263 }
264
265 // Set the initial direction.
266 std::array<double, 3> v = {dx0, dy0, dz0};
267 const double d0 = sqrt(dx0 * dx0 + dy0 * dy0 + dz0 * dz0);
268 if (d0 < Small) {
269 if (m_debug) {
270 std::cout << m_className << "::NewTrack: Sampling initial direction.\n";
271 }
272 // Null vector. Sample the direction isotropically.
273 RndmDirection(v[0], v[1], v[2]);
274 } else {
275 const double scale = 1. / d0;
276 for (size_t i = 0; i < 3; ++i) v[i] *= scale;
277 }
278
279 Medium* medium = m_sensor->GetMedium(x0, y0, z0);
280 if (!medium) {
281 std::cerr << m_className << "::NewTrack: No medium at initial point.\n";
282 return false;
283 }
284 double w = m_work > 0. ? m_work : medium->GetW();
285 // Warn if the W value is not defined.
286 if (w < Small) {
287 std::cerr << m_className << "::NewTrack: "
288 << "Work function at initial point is not defined.\n";
289 }
290 double fano = m_fset ? m_fano : medium->GetFanoFactor();
291 fano = std::max(fano, 0.);
292
293 // Charge-over-mass ratio.
294 const double qoverm = m_mass > 0. ? m_q / m_mass : 0.;
295
296 // Plot.
297 if (m_viewer) PlotNewTrack(x0, y0, z0);
298
299 // Reset the cluster count.
300 m_cluster = 0;
301 m_clusters.clear();
302
303 // Pool of unused energy
304 double epool = 0.0;
305
306 const double ekin0 = GetKineticEnergy();
307
308 const auto& path = m_ions[m_ion];
309 const size_t nPoints = path.size();
310 std::array<double, 3> x = {x0, y0, z0};
311 double t = t0;
312 for (size_t i = 0; i < nPoints; ++i) {
313 // Skip points with kinetic energy below the initial one set by the user.
314 double ekin = path[i][5];
315 if (ekin > ekin0) continue;
316 // Rotate the direction vector.
317 if (i == 0) {
318 Rotate({1, 0, 0}, {path[i][0], path[i][1], path[i][2]}, v);
319 } else {
320 Rotate({path[i - 1][0], path[i - 1][1], path[i - 1][2]},
321 {path[i][0], path[i][1], path[i][2]}, v);
322 }
323 // Get the step length and energy loss.
324 double dmag = path[i][3];
325 double eloss = path[i][4];
326 std::array<double, 3> d = {dmag * v[0], dmag * v[1], dmag * v[2]};
327 // Subdivide the step if necessary.
328 unsigned int nSteps = 1;
329 if (m_maxStepSize > 0. && dmag > m_maxStepSize) {
330 nSteps = std::ceil(dmag / m_maxStepSize);
331 }
332 if (m_maxLossPerStep > 0. && eloss > nSteps * m_maxLossPerStep) {
333 nSteps = std::ceil(eloss / m_maxLossPerStep);
334 }
335 if (nSteps > 1) {
336 const double scale = 1. / nSteps;
337 for (size_t j = 0; j < 3; ++j) d[j] *= scale;
338 dmag *= scale;
339 eloss *= scale;
340 }
341 // Compute the particle velocity.
342 const double vmag = Speed(ekin, m_mass);
343 // Compute the timestep.
344 const double dt = vmag > 0. ? dmag / vmag : 0.;
345 for (unsigned int j = 0; j < nSteps; ++j) {
346 Cluster cluster;
347 if (m_sensor->HasMagneticField()) {
348 double bx = 0., by = 0., bz = 0.;
349 int status = 0;
350 m_sensor->MagneticField(x[0], x[1], x[2], bx, by, bz, status);
351 d = StepBfield(dt, qoverm, vmag, bx, by, bz, v);
352 }
353 cluster.x = x[0] + d[0];
354 cluster.y = x[1] + d[1];
355 cluster.z = x[2] + d[2];
356 cluster.t = t + dt;
357 x = {cluster.x, cluster.y, cluster.z};
358 t = cluster.t;
359 // Is this point inside an ionisable medium?
360 medium = m_sensor->GetMedium(cluster.x, cluster.y, cluster.z);
361 if (!medium || !medium->IsIonisable()) continue;
362 if (m_work < Small) w = medium->GetW();
363 if (w < Small) continue;
364 if (fano < Small) {
365 // No fluctuations.
366 cluster.n = int((eloss + epool) / w);
367 cluster.energy = w * cluster.n;
368 } else {
369 double ec = eloss + epool;
370 cluster.n = 0;
371 cluster.energy = 0.0;
372 while (true) {
373 const double er = RndmHeedWF(w, fano);
374 if (er > ec) break;
375 cluster.n++;
376 cluster.energy += er;
377 ec -= er;
378 }
379 }
380 // TODO
381 cluster.ekin = i < nPoints - 1 ? path[i + 1][5] : ekin;
382 epool += eloss - cluster.energy;
383 if (cluster.n == 0) continue;
384 m_clusters.push_back(std::move(cluster));
385 if (m_viewer) PlotCluster(cluster.x, cluster.y, cluster.z);
386 }
387 }
388 // Move to the next ion in the list.
389 ++m_ion;
390 return true;
391}
392
393bool TrackTrim::GetCluster(double& xcls, double& ycls, double& zcls,
394 double& tcls, int& n, double& e, double& extra) {
395 if (m_debug) {
396 std::cout << m_className << "::GetCluster: Cluster " << m_cluster
397 << " of " << m_clusters.size() << "\n";
398 }
399 // Stop if we have exhausted the list of clusters.
400 if (m_cluster >= m_clusters.size()) return false;
401
402 const auto& cluster = m_clusters[m_cluster];
403 xcls = cluster.x;
404 ycls = cluster.y;
405 zcls = cluster.z;
406 tcls = cluster.t;
407
408 n = cluster.n;
409 e = cluster.energy;
410 extra = cluster.ekin;
411 // Move to the next cluster.
412 ++m_cluster;
413 return true;
414}
415}
Abstract base class for media.
Definition Medium.hh:16
double GetW() const
Get the W value.
Definition Medium.hh:86
bool IsIonisable() const
Is charge deposition by charged particles/photon enabled in this medium?
Definition Medium.hh:81
double GetFanoFactor() const
Get the Fano factor.
Definition Medium.hh:90
double m_ekin
Projectile energy [eV].
Definition TrackTrim.hh:83
size_t m_cluster
Index of the next cluster to be returned.
Definition TrackTrim.hh:92
void SetParticle(const std::string &part) override
Definition TrackTrim.cc:95
void AddIon(const std::vector< float > &x, const std::vector< float > &y, const std::vector< float > &z, const std::vector< float > &dedx, const std::vector< float > &ekin)
Definition TrackTrim.cc:194
TrackTrim()
Default constructor.
Definition TrackTrim.hh:17
std::vector< Cluster > m_clusters
Clusters on the current track.
Definition TrackTrim.hh:90
bool m_fset
Has the Fano factor been set?
Definition TrackTrim.hh:78
double m_fano
Fano factor [-] of the target.
Definition TrackTrim.hh:80
size_t m_ion
Index of the current track.
Definition TrackTrim.hh:87
double m_work
Work function [eV] of the target.
Definition TrackTrim.hh:76
bool GetCluster(double &xc, double &yc, double &zc, double &tc, int &nc, double &ec, double &extra) override
Definition TrackTrim.cc:393
void Print()
Print a summary of the available TRIM data.
Definition TrackTrim.cc:222
double m_maxLossPerStep
Energy loss limit per step.
Definition TrackTrim.hh:97
bool ReadFile(const std::string &file, const unsigned int nIons=0, const unsigned int nSkip=0)
Load data from an EXYZ.txt file.
Definition TrackTrim.cc:99
double m_maxStepSize
Step size limit.
Definition TrackTrim.hh:95
bool NewTrack(const double x0, const double y0, const double z0, const double t0, const double dx0, const double dy0, const double dz0) override
Definition TrackTrim.cc:243
std::vector< std::vector< std::array< float, 6 > > > m_ions
List of tracks.
Definition TrackTrim.hh:85
double GetKineticEnergy() const
Return the kinetic energy of the projectile.
Definition Track.hh:62
Sensor * m_sensor
Definition Track.hh:114
void SetKineticEnergy(const double ekin)
Set the kinetic energy of the particle.
Definition Track.cc:156
static std::array< double, 3 > StepBfield(const double dt, const double qoverm, const double vmag, double bx, double by, double bz, std::array< double, 3 > &dir)
Definition Track.cc:199
ViewDrift * m_viewer
Definition Track.hh:118
std::string m_particleName
Definition Track.hh:112
void PlotCluster(const double x0, const double y0, const double z0)
Definition Track.cc:195
std::string m_className
Definition Track.hh:104
double m_mass
Definition Track.hh:108
Track()=delete
Default constructor.
void PlotNewTrack(const double x0, const double y0, const double z0)
Definition Track.cc:190
double RndmHeedWF(const double w, const double f)
Definition Random.cc:699
std::vector< std::string > tokenize(const std::string &line)
Definition Utilities.hh:25
void RndmDirection(double &dx, double &dy, double &dz, const double length=1.)
Draw a random (isotropic) direction vector.
Definition Random.hh:128
DoubleAc fabs(const DoubleAc &f)
Definition DoubleAc.h:615
DoubleAc sqrt(const DoubleAc &f)
Definition DoubleAc.cpp:314
double energy
Energy spent to make the cluster.
Definition TrackTrim.hh:60
int n
Number of electrons in this cluster.
Definition TrackTrim.hh:62
double ekin
Ion energy when cluster was created.
Definition TrackTrim.hh:61