Garfield++ 5.0
A toolkit for the detailed simulation of particle detectors based on ionisation measurement in gases and semiconductors
Loading...
Searching...
No Matches
TrackSrim.cc
Go to the documentation of this file.
1#include <fstream>
2#include <iostream>
3#include <algorithm>
4
5#include <TCanvas.h>
6#include <TGraph.h>
7#include <TH1.h>
8#include <TLegend.h>
9#include <TLatex.h>
10
13#include "Garfield/Numerics.hh"
14#include "Garfield/Random.hh"
15#include "Garfield/Sensor.hh"
16#include "Garfield/ViewBase.hh"
17#include "Garfield/TrackSrim.hh"
18
19namespace {
20
21double StepVavilov(const double rkappa) {
22 double xlmin = -3.7;
23 if (rkappa < 0.1) {
24 xlmin = -2.7;
25 } else if (rkappa < 1) {
26 xlmin = -2.9;
27 } else if (rkappa < 2) {
28 xlmin = -3.0;
29 } else if (rkappa < 3) {
30 xlmin = -3.1;
31 } else if (rkappa < 4) {
32 xlmin = -3.2;
33 } else if (rkappa < 5) {
34 xlmin = -3.3;
35 } else if (rkappa < 6) {
36 xlmin = -3.4;
37 } else if (rkappa < 7) {
38 xlmin = -3.5;
39 } else if (rkappa < 8) {
40 xlmin = -3.6;
41 }
42 return xlmin;
43}
44
45double Interpolate(const double x, const std::vector<double>& xtab,
46 const std::vector<double>& ytab) {
47 if (x < xtab[0]) {
48 return ytab[0];
49 } else if (x > xtab.back()) {
50 return ytab.back();
51 }
52 return Garfield::Numerics::Divdif(ytab, xtab, xtab.size(), x, 2);
53}
54
55void PrintSettings(const std::string& hdr, const double de, const double step,
56 const double ekin, const double beta2, const double gamma,
57 const double edensity, const double qp, const double mp,
58 const double emax, const double xi, const double kappa) {
59 std::cout << hdr << "Settings:\n"
60 << " dE = " << de << " MeV,\n"
61 << " step = " << step << " cm.\n"
62 << " Ekin = " << ekin << " MeV,\n"
63 << " beta2 = " << beta2 << ",\n"
64 << " gamma = " << gamma << ".\n"
65 << " electron density = " << edensity << " / cm3.\n"
66 << " Qpart = " << qp << ", mpart = " << 1.e-6 * mp << " MeV.\n"
67 << " Emax = " << emax << " MeV,\n"
68 << " xi = " << xi << " MeV,\n"
69 << " kappa = " << kappa << ".\n";
70}
71}
72
73namespace Garfield {
74
75TrackSrim::TrackSrim(Sensor* sensor) : Track("Srim") {
76 m_sensor = sensor;
77}
78
79bool TrackSrim::ReadFile(const std::string& file) {
80 // SRMREA
81
82 const std::string hdr = m_className + "::ReadFile:\n ";
83 // Open the material list.
84 std::ifstream fsrim(file);
85 if (!fsrim) {
86 std::cerr << hdr << "Could not open SRIM file " << file
87 << " for reading.\n The file perhaps does not exist.\n";
88 return false;
89 }
90 unsigned int nread = 0;
91
92 // Read the header
93 if (m_debug) {
94 std::cout << hdr << "SRIM header records from file " << file << "\n";
95 }
96 constexpr size_t size = 100;
97 char line[size];
98 while (fsrim.getline(line, size, '\n')) {
99 nread++;
100 if (strstr(line, "SRIM version") != NULL) {
101 if (m_debug) std::cout << "\t" << line << "\n";
102 } else if (strstr(line, "Calc. date") != NULL) {
103 if (m_debug) std::cout << "\t" << line << "\n";
104 } else if (strstr(line, "Ion =") != NULL) {
105 break;
106 }
107 }
108
109 // Identify the ion
110 char* token = nullptr;
111 token = strtok(line, " []=");
112 token = strtok(nullptr, " []=");
113 token = strtok(nullptr, " []=");
114 // Set the ion charge.
115 if (token) {
116 m_qion = std::atof(token);
117 m_chargeset = true;
118 }
119 token = strtok(nullptr, " []=");
120 token = strtok(nullptr, " []=");
121 token = strtok(nullptr, " []=");
122 // Set the ion mass (convert amu to eV).
123 m_mion = std::atof(token) * AtomicMassUnitElectronVolt;
124
125 // Find the target density
126 if (!fsrim.getline(line, size, '\n')) {
127 std::cerr << hdr << "Premature EOF looking for target density (line "
128 << nread << ").\n";
129 return false;
130 }
131 nread++;
132 if (!fsrim.getline(line, size, '\n')) {
133 std::cerr << hdr << "Premature EOF looking for target density (line "
134 << nread << ").\n";
135 return false;
136 }
137 nread++;
138 const bool pre2013 = (strstr(line, "Target Density") != NULL);
139 token = strtok(line, " ");
140 token = strtok(NULL, " ");
141 token = strtok(NULL, " ");
142 if (pre2013) token = strtok(NULL, " ");
143 SetDensity(std::atof(token));
144
145 // Check the stopping units
146 while (fsrim.getline(line, size, '\n')) {
147 nread++;
148 if (strstr(line, "Stopping Units") == NULL) continue;
149 if (strstr(line, "Stopping Units = MeV / (mg/cm2)") != NULL ||
150 strstr(line, "Stopping Units = MeV/(mg/cm2)") != NULL) {
151 if (m_debug) {
152 std::cout << hdr << "Stopping units: MeV / (mg/cm2) as expected.\n";
153 }
154 break;
155 }
156 std::cerr << hdr << "Unknown stopping units. Aborting (line " << nread
157 << ").\n";
158 return false;
159 }
160
161 // Skip to the table
162 while (fsrim.getline(line, size, '\n')) {
163 nread++;
164 if (strstr(line, "-----------") != NULL) break;
165 }
166
167 // Read the table line by line
168 m_ekin.clear();
169 m_emloss.clear();
170 m_hdloss.clear();
171 m_range.clear();
172 m_transstraggle.clear();
173 m_longstraggle.clear();
174 unsigned int ntable = 0;
175 while (fsrim.getline(line, size, '\n')) {
176 nread++;
177 if (strstr(line, "-----------") != NULL) break;
178 // Energy
179 token = strtok(line, " ");
180 m_ekin.push_back(atof(token));
181 token = strtok(NULL, " ");
182 if (strcmp(token, "eV") == 0) {
183 m_ekin[ntable] *= 1.0e-6;
184 } else if (strcmp(token, "keV") == 0) {
185 m_ekin[ntable] *= 1.0e-3;
186 } else if (strcmp(token, "GeV") == 0) {
187 m_ekin[ntable] *= 1.0e3;
188 } else if (strcmp(token, "MeV") != 0) {
189 std::cerr << hdr << "Unknown energy unit " << token << "; aborting\n";
190 return false;
191 }
192 // EM loss
193 token = strtok(NULL, " ");
194 m_emloss.push_back(atof(token));
195 // HD loss
196 token = strtok(NULL, " ");
197 m_hdloss.push_back(atof(token));
198 // Projected range
199 token = strtok(NULL, " ");
200 m_range.push_back(atof(token));
201 token = strtok(NULL, " ");
202 if (strcmp(token, "A") == 0) {
203 m_range[ntable] *= 1.0e-8;
204 } else if (strcmp(token, "um") == 0) {
205 m_range[ntable] *= 1.0e-4;
206 } else if (strcmp(token, "mm") == 0) {
207 m_range[ntable] *= 1.0e-1;
208 } else if (strcmp(token, "m") == 0) {
209 m_range[ntable] *= 1.0e2;
210 } else if (strcmp(token, "km") == 0) {
211 m_range[ntable] *= 1.0e5;
212 } else if (strcmp(token, "cm") != 0) {
213 std::cerr << hdr << "Unknown distance unit " << token << "; aborting\n";
214 return false;
215 }
216 // Longitudinal straggling
217 token = strtok(NULL, " ");
218 m_longstraggle.push_back(atof(token));
219 token = strtok(NULL, " ");
220 if (strcmp(token, "A") == 0) {
221 m_longstraggle[ntable] *= 1.0e-8;
222 } else if (strcmp(token, "um") == 0) {
223 m_longstraggle[ntable] *= 1.0e-4;
224 } else if (strcmp(token, "mm") == 0) {
225 m_longstraggle[ntable] *= 1.0e-1;
226 } else if (strcmp(token, "m") == 0) {
227 m_longstraggle[ntable] *= 1.0e2;
228 } else if (strcmp(token, "km") == 0) {
229 m_longstraggle[ntable] *= 1.0e5;
230 } else if (strcmp(token, "cm") != 0) {
231 std::cerr << hdr << "Unknown distance unit " << token << "; aborting\n";
232 return false;
233 }
234 // Transverse straggling
235 token = strtok(NULL, " ");
236 m_transstraggle.push_back(atof(token));
237 token = strtok(NULL, " ");
238 if (strcmp(token, "A") == 0) {
239 m_transstraggle[ntable] *= 1.0e-8;
240 } else if (strcmp(token, "um") == 0) {
241 m_transstraggle[ntable] *= 1.0e-4;
242 } else if (strcmp(token, "mm") == 0) {
243 m_transstraggle[ntable] *= 1.0e-1;
244 } else if (strcmp(token, "m") == 0) {
245 m_transstraggle[ntable] *= 1.0e2;
246 } else if (strcmp(token, "km") == 0) {
247 m_transstraggle[ntable] *= 1.0e5;
248 } else if (strcmp(token, "cm") != 0) {
249 std::cerr << hdr << "Unknown distance unit " << token << "; aborting\n";
250 return false;
251 }
252
253 // Increment table line counter
254 ++ntable;
255 }
256
257 // Find the scaling factor and convert to MeV/cm
258 double scale = -1.;
259 while (fsrim.getline(line, size, '\n')) {
260 nread++;
261 if (strstr(line, "=============") != NULL) {
262 break;
263 } else if (strstr(line, "MeV / (mg/cm2)") != NULL ||
264 strstr(line, "MeV/(mg/cm2)") != NULL) {
265 token = strtok(line, " ");
266 scale = std::atof(token);
267 }
268 }
269 if (scale < 0) {
270 std::cerr << hdr << "Did not find stopping unit scaling; aborting.\n";
271 return false;
272 }
273 scale *= 1.e3;
274 for (unsigned int i = 0; i < ntable; ++i) {
275 m_emloss[i] *= scale;
276 m_hdloss[i] *= scale;
277 }
278
279 // Seems to have worked
280 if (m_debug) {
281 std::cout << hdr << "Successfully read " << file << "(" << nread
282 << " lines).\n";
283 }
284 return true;
285}
286
288 std::cout << "TrackSrim::Print:\n SRIM energy loss table\n\n"
289 << " Energy EM Loss HD loss Range "
290 << "l straggle t straggle\n"
291 << " [MeV] [MeV/cm] [MeV/cm] [cm] "
292 << " [cm] [cm]\n\n";
293 const unsigned int nPoints = m_emloss.size();
294 for (unsigned int i = 0; i < nPoints; ++i) {
295 printf("%10g %10g %10g %10g %10g %10g\n", m_ekin[i],
296 m_emloss[i] * m_rho, m_hdloss[i] * m_rho, m_range[i],
298 }
299 std::cout << "\n";
300 if (m_work > 0.) {
301 std::printf(" Work function: %g eV\n", m_work);
302 } else {
303 std::cout << " Work function: automatic\n";
304 }
305 if (m_fset) {
306 std::printf(" Fano factor: %g\n", m_fano);
307 } else {
308 std::cout << " Fano factor: automatic\n";
309 }
310 printf(" Ion charge: %g\n", m_qion);
311 printf(" Mass: %g MeV\n", 1.e-6 * m_mion);
312 printf(" Density: %g g/cm3\n", m_rho);
313 if (m_a > 0. && m_z > 0.) {
314 std::printf(" A, Z: %g, %g\n", m_a, m_z);
315 } else {
316 std::cout << " A, Z: automatic\n";
317 }
318}
319
321
322 const unsigned int nPoints = m_ekin.size();
323 std::vector<double> yE;
324 std::vector<double> yH;
325 std::vector<double> yT;
326 for (unsigned int i = 0; i < nPoints; ++i) {
327 const double em = m_emloss[i] * m_rho;
328 const double hd = m_hdloss[i] * m_rho;
329 yE.push_back(em);
330 yH.push_back(hd);
331 yT.push_back(em + hd);
332 }
333 const double xmin = *std::min_element(std::begin(m_ekin), std::end(m_ekin));
334 const double xmax = *std::max_element(std::begin(m_ekin), std::end(m_ekin));
335 const double ymax = *std::max_element(std::begin(yT), std::end(yT));
336 // Prepare a plot frame.
337 const std::string name = ViewBase::FindUnusedCanvasName("cSRIM");
338 TCanvas* celoss = new TCanvas(name.c_str(), "Energy loss");
339 celoss->SetLogx();
340 celoss->SetGridx();
341 celoss->SetGridy();
342 celoss->DrawFrame(xmin, 0., xmax, 1.05 * ymax, ";Ion energy [MeV];Energy loss [MeV/cm]");
343
344 // Make a graph for the 3 curves to plot.
345 TGraph gr;
346 gr.SetLineStyle(kSolid);
347 gr.SetLineWidth(2);
348 gr.SetMarkerStyle(21);
349 gr.SetLineColor(kBlue + 1);
350 gr.SetMarkerColor(kBlue + 1);
351 gr.DrawGraph(nPoints, m_ekin.data(), yE.data(), "plsame");
352
353 gr.SetLineColor(kGreen + 2);
354 gr.SetMarkerColor(kGreen + 2);
355 gr.DrawGraph(nPoints, m_ekin.data(), yH.data(), "plsame");
356
357 gr.SetLineColor(kOrange - 3);
358 gr.SetMarkerColor(kOrange - 3);
359 gr.DrawGraph(nPoints, m_ekin.data(), yT.data(), "plsame");
360
361 TLatex label;
362 double xLabel = 0.4 * xmax;
363 double yLabel = 0.9 * ymax;
364 label.SetTextColor(kBlue + 1);
365 label.SetText(xLabel, yLabel, "EM energy loss");
366 label.DrawLatex(xLabel, yLabel, "EM energy loss");
367 yLabel -= 1.5 * label.GetYsize();
368 label.SetTextColor(kGreen + 2);
369 label.DrawLatex(xLabel, yLabel, "HD energy loss");
370 yLabel -= 1.5 * label.GetYsize();
371 label.SetTextColor(kOrange - 3);
372 label.DrawLatex(xLabel, yLabel, "Total energy loss");
373 celoss->Update();
374}
375
377
378 const double xmin = *std::min_element(std::begin(m_ekin), std::end(m_ekin));
379 const double xmax = *std::max_element(std::begin(m_ekin), std::end(m_ekin));
380 const double ymax = *std::max_element(std::begin(m_range), std::end(m_range));
381
382 // Prepare a plot frame.
383 const std::string name = ViewBase::FindUnusedCanvasName("cSRIM");
384 TCanvas* crange = new TCanvas(name.c_str(), "Range");
385 crange->SetLogx();
386 crange->SetGridx();
387 crange->SetGridy();
388 crange->DrawFrame(xmin, 0., xmax, 1.05 * ymax, ";Ion energy [MeV];Projected range [cm]");
389 // Make a graph.
390 TGraph gr;
391 gr.SetLineColor(kOrange - 3);
392 gr.SetMarkerColor(kOrange - 3);
393 gr.SetLineStyle(kSolid);
394 gr.SetLineWidth(2);
395 gr.SetMarkerStyle(21);
396 gr.DrawGraph(m_ekin.size(), m_ekin.data(), m_range.data(), "plsame");
397 crange->Update();
398}
399
401
402 const double xmin = *std::min_element(std::begin(m_ekin), std::end(m_ekin));
403 const double xmax = *std::max_element(std::begin(m_ekin), std::end(m_ekin));
404 const double ymax = std::max(*std::max_element(std::begin(m_longstraggle),
405 std::end(m_longstraggle)),
406 *std::max_element(std::begin(m_transstraggle),
407 std::end(m_transstraggle)));
408 // Prepare a plot frame.
409 const std::string name = ViewBase::FindUnusedCanvasName("cSRIM");
410 TCanvas* cstraggle = new TCanvas(name.c_str(), "Straggling");
411 cstraggle->SetLogx();
412 cstraggle->SetGridx();
413 cstraggle->SetGridy();
414 cstraggle->DrawFrame(xmin, 0., xmax, 1.05 * ymax, ";Ion energy [MeV];Straggling [cm]");
415
416 // Make a graph for the 2 curves to plot.
417 const unsigned int nPoints = m_ekin.size();
418 TGraph gr;
419 gr.SetLineStyle(kSolid);
420 gr.SetLineWidth(2);
421 gr.SetMarkerStyle(21);
422
423 gr.SetLineColor(kOrange - 3);
424 gr.SetMarkerColor(kOrange - 3);
425 gr.DrawGraph(nPoints, m_ekin.data(), m_longstraggle.data(), "plsame");
426
427 gr.SetLineColor(kGreen + 2);
428 gr.SetMarkerColor(kGreen + 2);
429 gr.DrawGraph(nPoints, m_ekin.data(), m_transstraggle.data(), "plsame");
430
431 TLatex label;
432 double xLabel = 1.2 * xmin;
433 double yLabel = 0.9 * ymax;
434 label.SetTextColor(kOrange - 3);
435 label.SetText(xLabel, yLabel, "Longitudinal");
436 label.DrawLatex(xLabel, yLabel, "Longitudinal");
437 yLabel -= 1.5 * label.GetYsize();
438 label.SetTextColor(kGreen + 2);
439 label.DrawLatex(xLabel, yLabel, "Transverse");
440 cstraggle->Update();
441}
442
443double TrackSrim::DedxEM(const double e) const {
444 return Interpolate(e, m_ekin, m_emloss);
445}
446
447double TrackSrim::DedxHD(const double e) const {
448 return Interpolate(e, m_ekin, m_hdloss);
449}
450
451double TrackSrim::Xi(const double x, const double beta2,
452 const double edens) const {
453
454 constexpr double fconst = 1.e-6 * TwoPi * (
455 FineStructureConstant * FineStructureConstant * HbarC * HbarC) /
456 ElectronMass;
457 return fconst * m_qion * m_qion * edens * x / beta2;
458}
459
460bool TrackSrim::PreciseLoss(const double step, const double estart,
461 double& deem, double& dehd) const {
462 // SRMRKS
463
464 // Debugging
465 if (m_debug) printf(" Integrating energy losses over %g cm.\n", step);
466 // Precision aimed for.
467 const double eps = 1.0e-2;
468 // Number of intervals.
469 unsigned int ndiv = 1;
470 // Loop until precision achieved
471 const unsigned int nMaxIter = 10;
472 bool converged = false;
473 for (unsigned int iter = 0; iter < nMaxIter; ++iter) {
474 double e4 = estart;
475 double e2 = estart;
476 deem = 0.;
477 dehd = 0.;
478 // Compute rk2 and rk4 over the number of sub-divisions
479 const double s = m_rho * step / ndiv;
480 for (unsigned int i = 0; i < ndiv; i++) {
481 // rk2: initial point
482 const double de21 = s * (DedxEM(e2) + DedxHD(e2));
483 // Mid-way point
484 const double em22 = s * DedxEM(e2 - 0.5 * de21);
485 const double hd22 = s * DedxHD(e2 - 0.5 * de21);
486 // Trace the rk2 energy
487 e2 -= em22 + hd22;
488 // rk4: initial point
489 const double em41 = s * DedxEM(e4);
490 const double hd41 = s * DedxHD(e4);
491 const double de41 = em41 + hd41;
492 // Mid-way point
493 const double em42 = s * DedxEM(e4 - 0.5 * de41);
494 const double hd42 = s * DedxHD(e4 - 0.5 * de41);
495 const double de42 = em42 + hd42;
496 // Second mid-point estimate
497 const double em43 = s * DedxEM(e4 - 0.5 * de42);
498 const double hd43 = s * DedxHD(e4 - 0.5 * de42);
499 const double de43 = em43 + hd43;
500 // End point estimate
501 const double em44 = s * DedxEM(e4 - de43);
502 const double hd44 = s * DedxHD(e4 - de43);
503 const double de44 = em44 + hd44;
504 // Store the energy loss terms (according to rk4)
505 deem += (em41 + em44) / 6. + (em42 + em43) / 3.;
506 dehd += (hd41 + hd44) / 6. + (hd42 + hd43) / 3.;
507 // Store the new energy computed with rk4
508 e4 -= (de41 + de44) / 6. + (de42 + de43) / 3.;
509 }
510 if (m_debug) {
511 printf(" Iteration %u has %u division(s). Losses:\n", iter, ndiv);
512 printf(" de4 = %12g, de2 = %12g MeV\n", estart - e2, estart - e4);
513 printf(" em4 = %12g, hd4 = %12g MeV\n", deem, dehd);
514 }
515 // Compare the two estimates
516 if (fabs(e2 - e4) > eps * (fabs(e2) + fabs(e4) + fabs(estart))) {
517 // Repeat with twice the number of steps.
518 ndiv *= 2;
519 } else {
520 converged = true;
521 break;
522 }
523 }
524
525 if (!converged) {
526 std::cerr << m_className << "::PreciseLoss: "
527 << "No convergence achieved integrating energy loss.\n";
528 } else if (m_debug) {
529 std::cout << " Convergence at eps = " << eps << ".\n";
530 }
531 return converged;
532}
533
534bool TrackSrim::EstimateRange(const double ekin, const double step,
535 double& stpmax) const {
536 // Find distance over which the ion just does not lose all its energy
537 // ekin : Kinetic energy [MeV]
538 // step : Step length as guessed [cm]
539 // stpmax : Maximum step
540 // SRMDEZ
541
542 const std::string hdr = m_className + "::EstimateRange: ";
543 // Initial estimate
544 stpmax = step;
545
546 // Find the energy loss expected for the present step length.
547 double st1 = step;
548 double deem = 0., dehd = 0.;
549 PreciseLoss(st1, ekin, deem, dehd);
550 double de1 = deem + dehd;
551 // Do nothing if this is ok
552 if (de1 < ekin) {
553 if (m_debug) std::cout << " Initial step OK.\n";
554 return true;
555 }
556 // Find a smaller step for which the energy loss is less than EKIN.
557 double st2 = 0.5 * step;
558 double de2 = de1;
559 const unsigned int nMaxIter = 20;
560 for (unsigned int iter = 0; iter < nMaxIter; ++iter) {
561 // See where we stand
562 PreciseLoss(st2, ekin, deem, dehd);
563 de2 = deem + dehd;
564 // Below the kinetic energy: done
565 if (de2 < ekin) break;
566 // Not yet below the kinetic energy: new iteration.
567 st1 = st2;
568 de1 = de2;
569 st2 *= 0.5;
570 }
571 if (de2 >= ekin) {
572 std::cerr << hdr << "\n Did not find a smaller step in " << nMaxIter
573 << " iterations. Abandoned.\n";
574 stpmax = 0.5 * (st1 + st2);
575 return false;
576 }
577 if (m_debug) {
578 printf(" Step 1 = %g cm, dE 1 = %g MeV\n", st1, de1 - ekin);
579 printf(" Step 2 = %g cm, dE 2 = %g MeV\n", st2, de2 - ekin);
580 }
581 // Now perform a bisection
582 for (unsigned int iter = 0; iter < nMaxIter; ++iter) {
583 // Avoid division by zero.
584 if (de2 == de1) {
585 if (m_debug) {
586 std::cerr << " Bisection failed due to equal energy loss for "
587 << "two step sizes. Abandoned.\n";
588 }
589 stpmax = 0.5 * (st1 + st2);
590 return false;
591 }
592 // Estimate step to give total energy loss.
593 double st3;
594 if ((fabs(de1 - ekin) < 0.01 * fabs(de2 - de1)) ||
595 (fabs(de1 - ekin) > 0.99 * fabs(de2 - de1))) {
596 st3 = 0.5 * (st1 + st2);
597 } else {
598 st3 = st1 - (st2 - st1) * (de1 - ekin) / (de2 - de1);
599 }
600 // See how well we are doing.
601 PreciseLoss(st3, ekin, deem, dehd);
602 const double de3 = deem + dehd;
603 if (m_debug) {
604 std::printf(" Step 1 = %g cm, dE 1 = %g MeV\n", st1, de1 - ekin);
605 std::printf(" Step 2 = %g cm, dE 2 = %g MeV\n", st2, de2 - ekin);
606 std::printf(" Step 3 = %g cm, dE 3 = %g MeV\n", st3, de3 - ekin);
607 }
608 // Update the estimates above and below.
609 if (de3 > ekin) {
610 st1 = st3;
611 de1 = de3;
612 } else {
613 st2 = st3;
614 de2 = de3;
615 }
616 // See whether we've converged.
617 if (fabs(de3 - ekin) < 1e-3 * (fabs(de3) + fabs(ekin)) ||
618 fabs(st1 - st2) < 1e-3 * (fabs(st1) + fabs(st2))) {
619 stpmax = st1 - (st2 - st1) * (de1 - ekin) / (de2 - de1);
620 return true;
621 }
622 }
623 if (m_debug) {
624 std::cout << " Bisection did not converge in " << nMaxIter
625 << " steps. Abandoned.\n";
626 }
627 stpmax = st1 - (st2 - st1) * (de1 - ekin) / (de2 - de1);
628 return false;
629}
630
631Medium* TrackSrim::GetMedium(const std::array<double, 3>& x) const {
632 Medium* medium = m_sensor->GetMedium(x[0], x[1], x[2]);
633 if (medium && medium->IsIonisable() && m_sensor->IsInArea(x[0], x[1], x[2])) {
634 return medium;
635 }
636 return nullptr;
637}
638
639bool TrackSrim::NewTrack(const double x0, const double y0, const double z0,
640 const double t0, const double dx0, const double dy0,
641 const double dz0) {
642 // Generates electrons for a SRIM track
643 // SRMGEN
644 const std::string hdr = m_className + "::NewTrack: ";
645
646 // Verify that a sensor has been set.
647 if (!m_sensor) {
648 std::cerr << hdr << "Sensor is not defined.\n";
649 return false;
650 }
651
652 // Make sure the initial position is inside an ionisable medium.
653 Medium* medium = GetMedium({x0, y0, z0});
654 if (!medium) {
655 std::cerr << hdr << "No valid medium at initial position.\n";
656 return false;
657 }
658 // Get the W value of the medium (unless it has been set explicitly
659 // by the user).
660 double w = m_work < Small ? medium->GetW() : m_work;
661 if (w < Small) {
662 std::cerr << hdr << "Work function not defined.\n";
663 return false;
664 }
665 // Get the Fano factor
666 double fano = m_fset ? m_fano : medium->GetFanoFactor();
667 fano = std::max(fano, 0.);
668
669 // Compute the electron (number) density of the target.
670 double edens = 0.;
671 if (m_a > 0. && m_z > 0. && m_rho > 0.) {
672 edens = m_z * m_rho / (AtomicMassUnit * m_a);
673 } else {
674 edens = medium->GetNumberDensity() * medium->GetAtomicNumber();
675 if (m_rho > 0.) {
676 edens *= m_rho / medium->GetMassDensity();
677 }
678 }
679 if (edens < Small) {
680 std::cerr << hdr << "Invalid target density.\n";
681 return false;
682 }
683
684 // Normalise and store the direction.
685 const double normdir = sqrt(dx0 * dx0 + dy0 * dy0 + dz0 * dz0);
686 std::array<double, 3> v = {dx0, dy0, dz0};
687 if (normdir < Small) {
688 if (m_debug) {
689 std::cout << hdr << "Direction vector has zero norm.\n"
690 << " Initial direction is randomized.\n";
691 }
692 // Null vector. Sample the direction isotropically.
693 RndmDirection(v[0], v[1], v[2]);
694 } else {
695 // Normalise the direction vector.
696 v[0] /= normdir;
697 v[1] /= normdir;
698 v[2] /= normdir;
699 }
700
701 // Make sure all necessary parameters have been set.
702 if (m_mion < Small) {
703 std::cerr << hdr << "Particle mass not set.\n";
704 return false;
705 } else if (!m_chargeset) {
706 std::cerr << hdr << "Particle charge not set.\n";
707 return false;
708 } else if (m_energy < Small) {
709 std::cerr << hdr << "Initial particle energy not set.\n";
710 return false;
711 }
712
713 // Check the initial energy (in MeV).
714 const double ekin0 = 1.e-6 * GetKineticEnergy();
715 if (ekin0 < 1.e-14 * m_mion || ekin0 < 1.e-6 * w) {
716 std::cerr << hdr << "Initial kinetic energy E = " << ekin0
717 << " MeV such that beta2 = 0 or E << W; particle stopped.\n";
718 return true;
719 }
720
721 // Get an upper limit for the track length.
722 const double tracklength = 10 * Interpolate(ekin0, m_ekin, m_range);
723
724 // Header of debugging output.
725 if (m_debug) {
726 std::cout << hdr << "Track generation with the following parameters:\n";
727 const unsigned int nTable = m_ekin.size();
728 printf(" Table size %u\n", nTable);
729 printf(" Particle kin. energy %g MeV\n", ekin0);
730 printf(" Particle mass %g MeV\n", 1.e-6 * m_mion);
731 printf(" Particle charge %g\n", m_qion);
732 printf(" Work function %g eV\n", w);
733 printf(" Fano factor %g\n", fano);
734 printf(" Long. straggling: %d\n", m_useLongStraggle);
735 printf(" Trans. straggling: %d\n", m_useTransStraggle);
736 printf(" Cluster size %d\n", m_nsize);
737 }
738
739 // Plot.
740 if (m_viewer) PlotNewTrack(x0, y0, z0);
741
742 // Reset the cluster count.
743 m_currcluster = 0;
744 m_clusters.clear();
745
746 // Initial situation: starting position
747 std::array<double, 3> x = {x0, y0, z0};
748 double t = t0;
749 // Store the energy [MeV].
750 double ekin = ekin0;
751 // Total distance covered
752 double dsum = 0.0;
753 // Pool of unused energy
754 double epool = 0.0;
755
756 // Loop generating clusters
757 int iter = 0;
758 while (iter < m_maxclusters || m_maxclusters < 0) {
759 if (m_debug) printf(" Iteration %d. Ekin = %g MeV.\n", iter, ekin);
760 // Work out the energy loss per cm at the start of the step.
761 const double dedxem = DedxEM(ekin) * m_rho;
762 const double dedxhd = DedxHD(ekin) * m_rho;
763 // Find the step size for which we get approximately the
764 // requested number of clusters or cluster size.
765 double step = 0.;
766 double eloss = 0.;
767 if (m_nsize > 0) {
768 step = m_nsize * 1.e-6 * w / dedxem;
769 } else {
770 const double ncls = m_maxclusters > 0 ? 0.5 * m_maxclusters : 100;
771 step = ekin0 / (ncls * (dedxem + dedxhd));
772 }
773 if (m_debug) printf(" Estimated step size: %g cm\n", step);
774 bool finish = false;
775 // Truncate if this step exceeds the length.
776 if (dsum + step > tracklength) {
777 step = tracklength - dsum;
778 if (m_debug) printf(" Truncating step to %g cm.\n", step);
779 finish = true;
780 }
781 // Make an accurate integration of the energy loss over the step.
782 double deem = 0., dehd = 0.;
783 PreciseLoss(step, ekin, deem, dehd);
784 double stpmax = tracklength - dsum;
785 // If the energy loss exceeds the particle energy, truncate the step.
786 if (deem + dehd > ekin) {
787 EstimateRange(ekin, step, stpmax);
788 step = stpmax;
789 PreciseLoss(step, ekin, deem, dehd);
790 deem = ekin * deem / (dehd + deem);
791 dehd = ekin - deem;
792 finish = true;
793 if (m_debug) std::cout << " Track length reached.\n";
794 }
795 if (m_debug) {
796 std::cout << " Maximum step size set to " << stpmax << " cm.\n";
797 }
798 // Ensure that this is larger than the minimum modelable step size.
799 double stpmin;
800 if (!SmallestStep(ekin, edens, deem, step, stpmin)) {
801 std::cerr << hdr << "Failure computing the minimum step size.\n"
802 << " Clustering abandoned.\n";
803 return false;
804 }
805 if (stpmin > stpmax) {
806 // No way to find a suitable step size: use fixed energy loss.
807 if (m_debug) {
808 std::cout << " Min. step > max. step; depositing all energy.\n";
809 }
810 if (deem + dehd > ekin) {
811 eloss = ekin - dehd;
812 } else {
813 eloss = deem;
814 }
815 finish = true;
816 } else if (step < stpmin) {
817 // If needed enlarge the step size.
818 if (m_debug) std::cout << " Enlarging step size.\n";
819 step = stpmin;
820 PreciseLoss(step, ekin, deem, dehd);
821 if (deem + dehd > ekin) {
822 if (m_debug) std::cout << " Excess loss. Recomputing max. step.\n";
823 EstimateRange(ekin, step, stpmax);
824 step = stpmax;
825 PreciseLoss(step, ekin, deem, dehd);
826 deem = ekin * deem / (dehd + deem);
827 dehd = ekin - deem;
828 eloss = deem;
829 } else {
830 eloss = RndmEnergyLoss(ekin, deem, step, edens);
831 }
832 } else {
833 // Draw an actual energy loss for such a step.
834 if (m_debug) std::cout << " Step size ok.\n";
835 eloss = RndmEnergyLoss(ekin, deem, step, edens);
836 }
837 // Ensure we are neither below 0 nor above the total energy.
838 if (eloss < 0) {
839 if (m_debug) std::cout << " Truncating negative energy loss.\n";
840 eloss = 0;
841 } else if (eloss > ekin - dehd) {
842 if (m_debug) std::cout << " Excess energy loss, using mean.\n";
843 if (deem + dehd > ekin) {
844 eloss = ekin - dehd;
845 finish = true;
846 } else {
847 eloss = deem;
848 }
849 }
850 if (m_debug) {
851 std::cout << " Step length = " << step << " cm.\n"
852 << " Mean loss = " << deem << " MeV.\n"
853 << " Actual loss = " << eloss << " MeV.\n";
854 }
855 // Get the particle velocity.
856 const double rk = 1.e6 * ekin / m_mion;
857 const double gamma = 1. + rk;
858 const double beta2 = rk > 1.e-5 ? 1. - 1. / (gamma * gamma) : 2. * rk;
859 const double vmag = sqrt(beta2) * SpeedOfLight;
860 // Compute the timestep.
861 const double dt = vmag > 0. ? step / vmag : 0.;
862 // Compute the endpoint of this step.
863 std::array<double, 3> x1 = x;
864 std::array<double, 3> v1 = v;
865 if (m_sensor->HasMagneticField()) {
866 double bx = 0., by = 0., bz = 0.;
867 int status = 0;
868 m_sensor->MagneticField(x[0], x[1], x[2], bx, by, bz, status);
869 const double qoverm = m_qion / m_mion;
870 std::array<double, 3> d = StepBfield(dt, qoverm, vmag, bx, by, bz, v1);
871 x1[0] = x[0] + d[0];
872 x1[1] = x[1] + d[1];
873 x1[2] = x[2] + d[2];
874 } else {
875 x1[0] = x[0] + step * v[0];
876 x1[1] = x[1] + step * v[1];
877 x1[2] = x[2] + step * v[2];
878 }
879 // Is the endpoint in an ionisable medium and inside the active area?
880 if (!GetMedium(x1)) {
881 if (!m_sensor->HasMagneticField()) {
882 step = Terminate(x, v, step);
883 } else {
884 step = TerminateBfield(x, v, dt, vmag);
885 }
886 PreciseLoss(step, ekin, deem, dehd);
887 if (deem + dehd > ekin) {
888 if (m_debug) std::cout << " Excess loss.\n";
889 deem = ekin * deem / (dehd + deem);
890 dehd = ekin - deem;
891 }
892 eloss = RndmEnergyLoss(ekin, deem, step, edens);
893 if (eloss > ekin - dehd) eloss = deem;
894 finish = true;
895 }
896 // Add a cluster.
897 Cluster cluster;
898 cluster.x = x[0];
899 cluster.y = x[1];
900 cluster.z = x[2];
901 cluster.t = t;
902 if (fano < Small) {
903 // No fluctuations.
904 cluster.n = int((eloss + epool) / (1.e-6 * w));
905 cluster.energy = w * cluster.n;
906 } else {
907 double ecl = 1.e6 * (eloss + epool);
908 cluster.n = 0;
909 cluster.energy = 0.0;
910 while (true) {
911 // if (cluster.energy < 100) printf("ec = %g\n", cluster.energy);
912 const double ernd1 = RndmHeedWF(w, fano);
913 if (ernd1 > ecl) break;
914 cluster.n++;
915 cluster.energy += ernd1;
916 ecl -= ernd1;
917 }
918 if (m_debug) {
919 std::cout << " EM + pool: " << 1.e6 * (eloss + epool)
920 << " eV, W: " << w
921 << " eV, E/w: " << (eloss + epool) / (1.e-6 * w)
922 << ", n: " << cluster.n << ".\n";
923 }
924 }
925 cluster.kinetic = ekin;
926 epool += eloss - 1.e-6 * cluster.energy;
927 if (m_debug) {
928 std::cout << " Adding cluster " << m_clusters.size() << " at ("
929 << cluster.x << ", " << cluster.y << ", " << cluster.z
930 << "), e = " << cluster.energy << ", n = " << cluster.n
931 << ".\n"
932 << " Pool = " << epool << " MeV.\n";
933 }
934 m_clusters.push_back(std::move(cluster));
935 if (m_viewer) PlotCluster(x[0], x[1], x[2]);
936
937 // Keep track of the length and energy
938 dsum += step;
939 ekin -= eloss + dehd;
940 if (finish) {
941 // Stop if the flag is raised.
942 if (m_debug) std::cout << " Finishing flag raised.\n";
943 break;
944 } else if (ekin < ekin0 * 1.e-9) {
945 // No energy left.
946 if (m_debug) std::cout << " Energy exhausted.\n";
947 break;
948 }
949 // Update time, position and direction.
950 t += dt;
951 x = x1;
952 v = v1;
953 // Get the projected range and straggling.
954 const double prange = Interpolate(ekin, m_ekin, m_range);
955 const double strlat = m_useTransStraggle ?
956 Interpolate(ekin, m_ekin, m_transstraggle) : 0.;
957 const double strlon = m_useLongStraggle ?
958 Interpolate(ekin, m_ekin, m_longstraggle) : 0.;
959 // Draw scattering distances
960 const double scale = sqrt(step / prange);
961 const double sigt1 = RndmGaussian(0., scale * strlat);
962 const double sigt2 = RndmGaussian(0., scale * strlat);
963 const double sigl = RndmGaussian(0., scale * strlon);
964 if (m_debug) {
965 printf(" Sigma longitudinal: %g cm\n", sigl);
966 printf(" Sigma transverse: %g, %g cm\n", sigt1, sigt2);
967 }
968 // Rotation angles to bring z-axis in line
969 double theta, phi;
970 if (v[0] * v[0] + v[2] * v[2] <= 0) {
971 if (v[1] < 0) {
972 theta = -HalfPi;
973 } else if (v[1] > 0) {
974 theta = +HalfPi;
975 } else {
976 std::cerr << hdr << "Zero step length; clustering abandoned.\n";
977 return false;
978 }
979 phi = 0;
980 } else {
981 phi = atan2(v[0], v[2]);
982 theta = atan2(v[1], sqrt(v[0] * v[0] + v[2] * v[2]));
983 }
984 // Update the position.
985 const double cp = cos(phi);
986 const double ct = cos(theta);
987 const double sp = sin(phi);
988 const double st = sin(theta);
989 x[0] += +cp * sigt1 - sp * st * sigt2 + sp * ct * sigl;
990 x[1] += +ct * sigt2 + st * sigl;
991 x[2] += -sp * sigt1 - cp * st * sigt2 + cp * ct * sigl;
992 medium = GetMedium(x);
993 if (!medium) break;
994 // Update the W value (unless it is set explicitly).
995 if (m_work < Small) w = medium->GetW();
996 if (w < Small) {
997 std::cerr << hdr << "W value in medium " << medium->GetName()
998 << " is not defined.\n";
999 break;
1000 }
1001 // Next cluster
1002 iter++;
1003 }
1004 if (iter == m_maxclusters) {
1005 std::cerr << hdr << "Exceeded maximum number of clusters.\n";
1006 }
1007 return true;
1008 // finished generating
1009}
1010
1011double TrackSrim::Terminate(const std::array<double, 3>& x0,
1012 const std::array<double, 3>& v0,
1013 const double step0) const {
1014 double step = 0.;
1015 std::array<double, 3> x1 = x0;
1016 double s = step0;
1017 const double tol = std::max(1.e-6 * step0, 1.e-6);
1018 while (s > tol) {
1019 s *= 0.5;
1020 const std::array<double, 3> x2 = {x1[0] + s * v0[0], x1[1] + s * v0[1],
1021 x1[2] + s * v0[2]};
1022 if (GetMedium(x2)) {
1023 x1 = x2;
1024 step += s;
1025 }
1026 }
1027 return step;
1028}
1029
1030double TrackSrim::TerminateBfield(const std::array<double, 3>& x0,
1031 const std::array<double, 3>& v0,
1032 const double dt0, const double vmag) const {
1033
1034 const double qoverm = m_qion / m_mion;
1035 double dt = 0.;
1036 std::array<double, 3> x1 = x0;
1037 std::array<double, 3> v1 = v0;
1038 double s = dt0;
1039 const double tol = std::max(1.e-6 * dt0, 1.e-6);
1040 while (s > tol) {
1041 s *= 0.5;
1042 double bx = 0., by = 0., bz = 0.;
1043 int status = 0;
1044 m_sensor->MagneticField(x1[0], x1[1], x1[2], bx, by, bz, status);
1045 std::array<double, 3> v2 = v1;
1046 std::array<double, 3> d = StepBfield(s, qoverm, vmag, bx, by, bz, v2);
1047 std::array<double, 3> x2 = {x1[0] + d[0], x1[1] + d[1], x1[2] + d[2]};
1048 if (GetMedium(x2)) {
1049 x1 = x2;
1050 v1 = v2;
1051 dt += s;
1052 }
1053 }
1054 return dt * vmag;
1055}
1056
1057bool TrackSrim::SmallestStep(const double ekin, const double edens,
1058 double de, double step, double& stpmin) {
1059 // Determines the smallest step size for which there is little
1060 // or no risk of finding negative energy fluctuations.
1061 // SRMMST
1062
1063 const std::string hdr = m_className + "::SmallestStep: ";
1064 constexpr double expmax = 30;
1065
1066 // By default, assume the step is right.
1067 stpmin = step;
1068 // Check correctness.
1069 if (ekin <= 0 || de <= 0 || step <= 0) {
1070 std::cerr << hdr << "Input parameters not valid.\n Ekin = " << ekin
1071 << " MeV, dE = " << de << " MeV, step length = " << step
1072 << " cm.\n";
1073 return false;
1074 } else if (m_mion <= 0 || fabs(m_qion) <= 0) {
1075 std::cerr << hdr
1076 << "Track parameters not valid.\n Mass = " << 1.e-6 * m_mion
1077 << " MeV, charge = " << m_qion << ".\n";
1078 return false;
1079 } else if (edens <= 0) {
1080 std::cerr << hdr << "Target parameters not valid.\n"
1081 << " electron density = " << edens << " / cm3.\n";
1082 return false;
1083 }
1084
1085 // Basic kinematic parameters
1086 const double rkin = 1.e6 * ekin / m_mion;
1087 const double gamma = 1. + rkin;
1088 const double beta2 = rkin > 1.e-5 ? 1. - 1. / (gamma * gamma) : 2. * rkin;
1089
1090 // Compute the maximum energy transfer [MeV]
1091 const double rm = ElectronMass / m_mion;
1092 const double emax = 2 * ElectronMass * 1.e-6 * beta2 * gamma * gamma /
1093 (1. + 2 * gamma * rm + rm * rm);
1094 // Compute the Rutherford term.
1095 double xi = Xi(step, beta2, edens);
1096 // Compute the scaling parameter
1097 double rkappa = xi / emax;
1098 // Step size and energy loss
1099 double denow = de;
1100 double stpnow = step;
1101 constexpr unsigned int nMaxIter = 10;
1102 for (unsigned int iter = 0; iter < nMaxIter; ++iter) {
1103 bool retry = false;
1104 // Debugging output.
1105 if (m_debug) {
1106 PrintSettings(hdr, denow, stpnow, ekin, beta2, gamma, edens,
1107 m_qion, m_mion, emax, xi, rkappa);
1108 }
1109 double xinew = xi;
1110 double rknew = rkappa;
1112 // No fluctuations: any step is permitted
1113 stpmin = stpnow;
1114 } else if (m_model == 1) {
1115 // Landau distribution
1116 constexpr double xlmin = -3.;
1117 const double exponent = -xlmin - 1. + Gamma - beta2 - denow / xi;
1118 const double rklim = exponent < -expmax ? 0. : exp(exponent);
1119 stpmin = stpnow * (rklim / rkappa);
1120 if (m_debug) {
1121 std::cout << " Landau distribution is imposed (kappa_min = "
1122 << rklim << ", d_min = " << stpmin << " cm).\n";
1123 }
1124 } else if (m_model == 2) {
1125 // Vavilov distribution, ensure we're in range.
1126 const double xlmin = StepVavilov(rkappa);
1127 const double exponent = -xlmin - 1. + Gamma - beta2 - denow / xi;
1128 const double rklim = exponent < -expmax ? 0. : exp(exponent);
1129 stpmin = stpnow * (rklim / rkappa);
1130 xinew = Xi(stpmin, beta2, edens);
1131 rknew = xinew / emax;
1132 if (m_debug) {
1133 std::cout << " Vavilov distribution is imposed (kappa_min = "
1134 << rklim << ", d_min = " << stpmin << " cm, kappa_new = "
1135 << rknew << ", xi_new = " << xinew << " MeV).\n";
1136 }
1137 if (stpmin > stpnow * 1.1) {
1138 if (m_debug) std::cout << " Step size increase. New pass.\n";
1139 retry = true;
1140 }
1141 } else if (m_model == 3) {
1142 // Gaussian model
1143 const double sigma2 = xi * emax * (1 - 0.5 * beta2);
1144 stpmin = stpnow * 16 * sigma2 / (denow * denow);
1145 if (m_debug) {
1146 const double sigmaMin2 = Xi(stpmin, beta2, edens) * emax * (1 - 0.5 * beta2);
1147 std::cout << " Gaussian distribution is imposed, d_min = "
1148 << stpmin << " cm, sigma/mu (old) = "
1149 << sqrt(sigma2) / de << ", sigma/mu (min) = "
1150 << sqrt(sigmaMin2) / (stpmin * denow / stpnow) << ".\n";
1151 }
1152 } else if (rkappa < 0.05) {
1153 // Combined model: for low kappa, use the Landau distribution.
1154 constexpr double xlmin = -3.;
1155 const double exponent = -xlmin - 1. + Gamma - beta2 - denow / xi;
1156 const double rklim = exponent < -expmax ? 0. : exp(exponent);
1157 stpmin = stpnow * (rklim / rkappa);
1158 xinew = Xi(stpmin, beta2, edens);
1159 rknew = xinew / emax;
1160 if (m_debug) {
1161 std::cout << " Landau distribution automatic (kappa_min = "
1162 << rklim << ", d_min = " << stpmin << " cm).\n";
1163 }
1164 if (rknew > 0.05 || stpmin > stpnow * 1.1) {
1165 retry = true;
1166 if (m_debug) {
1167 std::cout << " Model change or step increase. New pass.\n";
1168 }
1169 }
1170 } else if (rkappa < 5) {
1171 // For medium kappa, use the Vavilov distribution
1172 const double xlmin = StepVavilov(rkappa);
1173 const double exponent = -xlmin - 1. + Gamma - beta2 - denow / xi;
1174 const double rklim = exponent < -expmax ? 0. : exp(exponent);
1175 stpmin = stpnow * (rklim / rkappa);
1176 xinew = Xi(stpmin, beta2, edens);
1177 rknew = xinew / emax;
1178 if (m_debug) {
1179 std::cout << " Vavilov distribution automatic (kappa_min = "
1180 << rklim << ", d_min = " << stpmin << " cm, kappa_new = "
1181 << rknew << ", xi_new = " << xinew << " MeV).\n";
1182 }
1183 if (rknew > 5 || stpmin > stpnow * 1.1) {
1184 retry = true;
1185 if (m_debug) {
1186 std::cout << " Model change or step increase. New pass.\n";
1187 }
1188 }
1189 } else {
1190 // And for large kappa, use the Gaussian values.
1191 const double sigma2 = xi * emax * (1 - 0.5 * beta2);
1192 stpmin = stpnow * 16 * sigma2 / (denow * denow);
1193 if (m_debug) {
1194 const double sigmaMin2 = Xi(stpmin, beta2, edens) * emax * (1 - 0.5 * beta2);
1195 std::cout << " Gaussian distribution automatic, d_min = "
1196 << stpmin << " cm, sigma/mu (old) = "
1197 << sqrt(sigma2) / de << ", sigma/mu (min) = "
1198 << sqrt(sigmaMin2) / (stpmin * denow / stpnow) << ".\n";
1199 }
1200 }
1201 // See whether we should do another pass.
1202 if (stpnow > stpmin) {
1203 if (m_debug) {
1204 std::cout << " Step size ok, minimum: " << stpmin << " cm\n";
1205 }
1206 break;
1207 }
1208 if (!retry) {
1209 if (m_debug) {
1210 std::cerr << " Step size must be increased to " << stpmin
1211 << "cm.\n";
1212 }
1213 break;
1214 }
1215 // New iteration
1216 rkappa = rknew;
1217 xi = xinew;
1218 denow *= stpmin / stpnow;
1219 stpnow = stpmin;
1220 if (m_debug) std::cout << " Iteration " << iter << "\n";
1221 if (iter == nMaxIter - 1) {
1222 // Need interation, but ran out of tries
1223 std::cerr << hdr << "No convergence reached on step size.\n";
1224 }
1225 }
1226 return true;
1227}
1228
1229double TrackSrim::RndmEnergyLoss(const double ekin, const double de,
1230 const double step, const double edens) const {
1231 // RNDDE - Generates a random energy loss.
1232 // VARIABLES : EKIN : Kinetic energy [MeV]
1233 // DE : Mean energy loss over the step [MeV]
1234 // STEP : Step length [cm]
1235 // BETA2 : Velocity-squared
1236 // GAMMA : Projectile gamma
1237 // EMAX : Maximum energy transfer per collision [MeV]
1238 // XI : Rutherford term [MeV]
1239 // FCONST : Proportionality constant
1240 // EMASS : Electron mass [MeV]
1241
1242 const std::string hdr = "TrackSrim::RndmEnergyLoss: ";
1243 // Check correctness.
1244 if (ekin <= 0 || de <= 0 || step <= 0) {
1245 std::cerr << hdr << "Input parameters not valid.\n Ekin = " << ekin
1246 << " MeV, dE = " << de << " MeV, step length = " << step
1247 << " cm.\n";
1248 return 0.;
1249 } else if (m_mion <= 0 || fabs(m_qion) <= 0) {
1250 std::cerr << hdr << "Track parameters not valid.\n Mass = "
1251 << m_mion << " MeV, charge = " << m_qion << ".\n";
1252 return 0.;
1253 } else if (edens <= 0.) {
1254 std::cerr << hdr << "Target parameters not valid.\n"
1255 << " electron density = " << edens << " / cm3.\n";
1256 return 0.;
1257 }
1258 // Basic kinematic parameters
1259 const double rkin = 1.e6 * ekin / m_mion;
1260 const double gamma = 1. + rkin;
1261 const double beta2 = rkin > 1.e-5 ? 1. - 1. / (gamma * gamma) : 2. * rkin;
1262
1263 // Compute maximum energy transfer
1264 const double rm = ElectronMass / m_mion;
1265 const double emax = 2 * ElectronMass * 1.e-6 * beta2 * gamma * gamma /
1266 (1. + 2 * gamma * rm + rm * rm);
1267 // Compute the Rutherford term.
1268 const double xi = Xi(step, beta2, edens);
1269 // Compute the scaling parameter
1270 const double rkappa = xi / emax;
1271 // Debugging output.
1272 if (m_debug) {
1273 PrintSettings(hdr, de, step, ekin, beta2, gamma, edens,
1274 m_qion, m_mion, emax, xi, rkappa);
1275 }
1276 double rndde = de;
1278 // No fluctuations.
1279 if (m_debug) std::cout << " Fixed energy loss.\n";
1280 } else if (m_model == 1) {
1281 // Landau distribution
1282 if (m_debug) std::cout << " Landau imposed.\n";
1283 const double xlmean = -(log(rkappa) + beta2 + 1. - Gamma);
1284 rndde += xi * (RndmLandau() - xlmean);
1285 } else if (m_model == 2) {
1286 // Vavilov distribution, ensure we are in range.
1287 if (m_debug) std::cout << " Vavilov imposed.\n";
1288 if (rkappa > 0.01 && rkappa < 12) {
1289 const double xvav = RndmVavilov(rkappa, beta2);
1290 rndde += xi * (xvav + log(rkappa) + beta2 + (1 - Gamma));
1291 }
1292 } else if (m_model == 3) {
1293 // Gaussian model
1294 if (m_debug) std::cout << " Gaussian imposed.\n";
1295 rndde += RndmGaussian(0., sqrt(xi * emax * (1 - 0.5 * beta2)));
1296 } else if (rkappa < 0.05) {
1297 // Combined model: for low kappa, use the landau distribution.
1298 if (m_debug) std::cout << " Landau automatic.\n";
1299 const double xlmean = -(log(rkappa) + beta2 + (1 - Gamma));
1300 const double par[] = {0.50884, 1.26116, 0.0346688, 1.46314,
1301 0.15088e-2, 1.00324, -0.13049e-3};
1302 const double xlmax = par[0] + par[1] * xlmean + par[2] * xlmean * xlmean +
1303 par[6] * xlmean * xlmean * xlmean +
1304 (par[3] + xlmean * par[4]) * exp(par[5] * xlmean);
1305 double xlan = RndmLandau();
1306 for (unsigned int iter = 0; iter < 100; ++iter) {
1307 if (xlan < xlmax) break;
1308 xlan = RndmLandau();
1309 }
1310 rndde += xi * (xlan - xlmean);
1311 } else if (rkappa < 5) {
1312 // For medium kappa, use the Vavilov distribution.
1313 if (m_debug) std::cout << " Vavilov fast automatic.\n";
1314 const double xvav = RndmVavilov(rkappa, beta2);
1315 rndde += xi * (xvav + log(rkappa) + beta2 + (1 - Gamma));
1316 } else {
1317 // And for large kappa, use the Gaussian values.
1318 if (m_debug) std::cout << " Gaussian automatic.\n";
1319 rndde = RndmGaussian(de, sqrt(xi * emax * (1 - 0.5 * beta2)));
1320 }
1321 // Debugging output
1322 if (m_debug) {
1323 std::cout << " Energy loss generated = " << rndde << " MeV.\n";
1324 }
1325 return rndde;
1326}
1327
1328bool TrackSrim::GetCluster(double& xcls, double& ycls, double& zcls,
1329 double& tcls, int& n, double& e, double& extra) {
1330 if (m_debug) {
1331 std::cout << m_className << "::GetCluster: Cluster " << m_currcluster
1332 << " of " << m_clusters.size() << "\n";
1333 }
1334 // Stop if we have exhausted the list of clusters.
1335 if (m_currcluster >= m_clusters.size()) return false;
1336
1337 const auto& cluster = m_clusters[m_currcluster];
1338 xcls = cluster.x;
1339 ycls = cluster.y;
1340 zcls = cluster.z;
1341 tcls = cluster.t;
1342
1343 n = cluster.n;
1344 e = cluster.energy;
1345 extra = cluster.kinetic;
1346 // Move to next cluster
1347 ++m_currcluster;
1348 return true;
1349}
1350}
Abstract base class for media.
Definition Medium.hh:16
double GetW() const
Get the W value.
Definition Medium.hh:86
virtual double GetMassDensity() const
Get the mass density [g/cm3].
Definition Medium.cc:102
virtual double GetNumberDensity() const
Get the number density [cm-3].
Definition Medium.hh:63
virtual double GetAtomicNumber() const
Get the effective atomic number.
Definition Medium.hh:55
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 GetFanoFactor() const
Get the Fano factor.
Definition Medium.hh:90
std::vector< double > m_longstraggle
Longitudinal straggling [cm].
Definition TrackSrim.hh:139
void Print()
Print the energy loss table.
Definition TrackSrim.cc:287
double Xi(const double x, const double beta2, const double edens) const
Definition TrackSrim.cc:451
double m_qion
Charge of the projectile.
Definition TrackSrim.hh:110
double m_z
Effective Z of the target.
Definition TrackSrim.hh:124
std::vector< double > m_hdloss
Hadronic energy loss [MeV cm2/g].
Definition TrackSrim.hh:133
void SetDensity(const double density)
Set the density [g/cm3] of the target medium.
Definition TrackSrim.hh:64
std::vector< double > m_range
Projected range [cm].
Definition TrackSrim.hh:135
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 TrackSrim.cc:639
double DedxHD(const double e) const
Definition TrackSrim.cc:447
bool EstimateRange(const double ekin, const double step, double &stpmax) const
Definition TrackSrim.cc:534
std::vector< Cluster > m_clusters
Definition TrackSrim.hh:148
size_t m_currcluster
Index of the next cluster to be returned.
Definition TrackSrim.hh:142
std::vector< double > m_emloss
EM energy loss [MeV cm2/g].
Definition TrackSrim.hh:131
bool m_chargeset
Has the charge been defined?
Definition TrackSrim.hh:108
double m_fano
Fano factor [-] of the target.
Definition TrackSrim.hh:120
TrackSrim()
Default constructor.
Definition TrackSrim.hh:14
Medium * GetMedium(const std::array< double, 3 > &x) const
Definition TrackSrim.cc:631
double RndmEnergyLoss(const double ekin, const double de, const double step, const double edens) const
double m_mion
Mass [MeV] of the projectile.
Definition TrackSrim.hh:112
bool PreciseLoss(const double step, const double estart, double &deem, double &dehd) const
Definition TrackSrim.cc:460
double m_work
Work function [eV] of the target.
Definition TrackSrim.hh:116
unsigned int m_model
Definition TrackSrim.hh:145
bool m_useTransStraggle
Include transverse straggling.
Definition TrackSrim.hh:103
int m_nsize
Targeted cluster size.
Definition TrackSrim.hh:147
std::vector< double > m_ekin
Energy in energy loss table [MeV].
Definition TrackSrim.hh:129
std::vector< double > m_transstraggle
Transverse straggling [cm].
Definition TrackSrim.hh:137
double Terminate(const std::array< double, 3 > &x0, const std::array< double, 3 > &v0, const double step0) const
double m_rho
Mass density [g/cm3] of the target.
Definition TrackSrim.hh:114
bool m_useLongStraggle
Include longitudinal straggling.
Definition TrackSrim.hh:105
double m_a
Effective A of the target.
Definition TrackSrim.hh:122
bool GetCluster(double &xc, double &yc, double &zc, double &tc, int &nc, double &ec, double &extra) override
void PlotRange()
Plot the projected range as function of the projectile energy.
Definition TrackSrim.cc:376
bool m_fset
Has the Fano factor been set?
Definition TrackSrim.hh:118
int m_maxclusters
Maximum number of clusters allowed (infinite if 0)
Definition TrackSrim.hh:127
double DedxEM(const double e) const
Definition TrackSrim.cc:443
bool SmallestStep(const double ekin, const double edens, double de, double step, double &stpmin)
double TerminateBfield(const std::array< double, 3 > &x0, const std::array< double, 3 > &v0, const double dt0, const double vmag) const
bool ReadFile(const std::string &file)
Load data from a SRIM file.
Definition TrackSrim.cc:79
double GetKineticEnergy() const
Return the kinetic energy of the projectile.
Definition Track.hh:62
Sensor * m_sensor
Definition Track.hh:114
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
void PlotCluster(const double x0, const double y0, const double z0)
Definition Track.cc:195
std::string m_className
Definition Track.hh:104
Track()=delete
Default constructor.
double m_energy
Definition Track.hh:109
void PlotNewTrack(const double x0, const double y0, const double z0)
Definition Track.cc:190
static std::string FindUnusedCanvasName(const std::string &s)
Find an unused canvas name.
Definition ViewBase.cc:337
double Divdif(const std::vector< double > &f, const std::vector< double > &a, int nn, double x, int mm)
Definition Numerics.cc:1255
double RndmHeedWF(const double w, const double f)
Definition Random.cc:699
double RndmVavilov(const double rkappa, const double beta2)
Draw a random number from a Vavilov distribution.
Definition Random.cc:300
void RndmDirection(double &dx, double &dy, double &dz, const double length=1.)
Draw a random (isotropic) direction vector.
Definition Random.hh:128
double RndmGaussian()
Draw a Gaussian random variate with mean zero and standard deviation one.
Definition Random.hh:24
double RndmLandau()
Draw a random number from a Landau distribution.
Definition Random.cc:104
int n
Number of electrons in this cluster.
Definition TrackSrim.hh:91
double kinetic
Ion energy when cluster was created.
Definition TrackSrim.hh:90
double t
Cluster location and time.
Definition TrackSrim.hh:88
double energy
Energy spent to make the cluster.
Definition TrackSrim.hh:89