Garfield++ 5.0
A toolkit for the detailed simulation of particle detectors based on ionisation measurement in gases and semiconductors
Loading...
Searching...
No Matches
MediumMagboltz.cc
Go to the documentation of this file.
1#include <algorithm>
2#include <cassert>
3#include <cmath>
4#include <cstdio>
5#include <cstdlib>
6#include <fstream>
7#include <iomanip>
8#include <iostream>
9#include <map>
10#include <numeric>
11
12#include <TCanvas.h>
13#include <TGraph.h>
14#include <TH1F.h>
15#include <TMath.h>
16
22#include "Garfield/Random.hh"
23#include "Garfield/Utilities.hh"
24#include "Garfield/ViewBase.hh"
25
26namespace {
27
28bool IsComment(const std::string& line) {
29 if (line.empty()) return false;
30 if (line[0] == '#') return true;
31 return false;
32}
33
34std::string GetDescription(const unsigned int index,
35 char scrpt[][Garfield::Magboltz::nCharDescr]) {
36 return std::string(scrpt[index],
37 scrpt[index] + Garfield::Magboltz::nCharDescr);
38}
39
40std::string GetDescription(const unsigned int i1, const unsigned int i2,
41 char scrpt[][6][Garfield::Magboltz::nCharDescr]) {
42 return std::string(scrpt[i1][i2],
43 scrpt[i1][i2] + Garfield::Magboltz::nCharDescr);
44}
45
46void SetScatteringParameters(const int model, const double parIn, double& cut,
47 double& parOut) {
48 cut = 1.;
49 parOut = 0.5;
50 if (model <= 0) return;
51
52 if (model >= 2) {
53 parOut = parIn;
54 return;
55 }
56
57 // Set cuts on angular distribution and
58 // renormalise forward scattering probability.
59 if (parIn <= 1.) {
60 parOut = parIn;
61 return;
62 }
63
64 const double cns = parIn - 0.5;
65 const double thetac = asin(2. * sqrt(cns - cns * cns));
66 const double fac = (1. - cos(thetac)) / pow(sin(thetac), 2.);
67 parOut = cns * fac + 0.5;
68 cut = thetac * 2. / Garfield::Pi;
69}
70
71}
72
73namespace Garfield {
74
75const int MediumMagboltz::DxcTypeRad = 0;
76const int MediumMagboltz::DxcTypeCollIon = 1;
77const int MediumMagboltz::DxcTypeCollNonIon = -1;
78
80 : MediumGas(),
81 m_eMax(40.),
82 m_eStep(m_eMax / Magboltz::nEnergySteps),
83 m_eStepInv(1. / m_eStep),
84 m_eHigh(400.),
85 m_eHighLog(log(m_eHigh)),
86 m_lnStep(1.),
87 m_eFinalGamma(20.),
88 m_eStepGamma(m_eFinalGamma / nEnergyStepsGamma) {
89 m_className = "MediumMagboltz";
90
91 // Set physical constants in Magboltz common blocks.
92 Magboltz::cnsts_.echarg = ElementaryCharge * 1.e-15;
93 Magboltz::cnsts_.emass = ElectronMassGramme;
94 Magboltz::cnsts_.amu = AtomicMassUnit;
95 Magboltz::cnsts_.pir2 = BohrRadius * BohrRadius * Pi;
96 Magboltz::inpt_.ary = RydbergEnergy;
97
98 // Set parameters in Magboltz common blocks.
101 // Select the scattering model.
102 Magboltz::inpt_.nAniso = 2;
103 // Max. energy [eV]
104 Magboltz::inpt_.efinal = m_eMax;
105 // Energy step size [eV]
106 Magboltz::inpt_.estep = m_eStep;
107 // Temperature and pressure
108 Magboltz::inpt_.akt = BoltzmannConstant * m_temperature;
109 Magboltz::inpt_.tempc = m_temperature - ZeroCelsius;
111 // Disable Penning transfer.
112 Magboltz::inpt_.ipen = 0;
113
114 m_description.assign(Magboltz::nMaxLevels,
115 std::string(Magboltz::nCharDescr, ' '));
116
117 m_cfTot.assign(Magboltz::nEnergySteps, 0.);
118 m_cfTotLog.assign(nEnergyStepsLog, 0.);
119 m_cf.assign(Magboltz::nEnergySteps,
120 std::vector<double>(Magboltz::nMaxLevels, 0.));
121 m_cfLog.assign(nEnergyStepsLog,
122 std::vector<double>(Magboltz::nMaxLevels, 0.));
123
124 m_isChanged = true;
125
126 m_driftable = true;
127 m_ionisable = true;
128 m_microscopic = true;
129
130 m_scaleExc.fill(1.);
131}
132MediumMagboltz::MediumMagboltz(const std::string& gas1, const double f1,
133 const std::string& gas2, const double f2,
134 const std::string& gas3, const double f3,
135 const std::string& gas4, const double f4,
136 const std::string& gas5, const double f5,
137 const std::string& gas6, const double f6)
138 : MediumMagboltz() {
139 SetComposition(gas1, f1, gas2, f2, gas3, f3,
140 gas4, f4, gas5, f5, gas6, f6);
141}
142
144 if (e <= Small) {
145 std::cerr << m_className << "::SetMaxElectronEnergy: Invalid energy.\n";
146 return false;
147 }
148 m_eMax = e;
149
150 std::lock_guard<std::mutex> guard(m_mutex);
151 // Determine the energy interval size.
152 m_eStep = std::min(m_eMax, m_eHigh) / Magboltz::nEnergySteps;
153 m_eStepInv = 1. / m_eStep;
154
155 // Force recalculation of the scattering rates table.
156 m_isChanged = true;
157
158 return true;
159}
160
162 if (e <= Small) {
163 std::cerr << m_className << "::SetMaxPhotonEnergy: Invalid energy.\n";
164 return false;
165 }
166 m_eFinalGamma = e;
167
168 // Determine the energy interval size.
169 m_eStepGamma = m_eFinalGamma / nEnergyStepsGamma;
170
171 // Force recalculation of the scattering rates table.
172 m_isChanged = true;
173
174 return true;
175}
176
178 m_useOpalBeaty = true;
179 m_useGreenSawada = false;
180}
181
183 m_useOpalBeaty = false;
184 m_useGreenSawada = true;
185 if (m_isChanged) return;
186
187 bool allset = true;
188 for (unsigned int i = 0; i < m_nComponents; ++i) {
189 if (!m_hasGreenSawada[i]) {
190 if (allset) {
191 std::cout << m_className << "::SetSplittingFunctionGreenSawada:\n";
192 allset = false;
193 }
194 std::cout << " Fit parameters for " << m_gas[i] << " not available.\n"
195 << " Using Opal-Beaty formula instead.\n";
196 }
197 }
198}
199
201 m_useOpalBeaty = false;
202 m_useGreenSawada = false;
203}
204
206 if (m_usePenning) {
207 std::cout << m_className << "::EnableDeexcitation:\n"
208 << " Penning transfer will be switched off.\n";
209 }
210 // if (m_useRadTrap) {
211 // std::cout << " Radiation trapping is switched on.\n";
212 // } else {
213 // std::cout << " Radiation trapping is switched off.\n";
214 // }
215 m_usePenning = false;
216 m_useDeexcitation = true;
217 m_isChanged = true;
218 m_dxcProducts.clear();
219}
220
222 m_useRadTrap = true;
223 if (!m_useDeexcitation) {
224 std::cout << m_className << "::EnableRadiationTrapping:\n "
225 << "Radiation trapping is enabled but de-excitation is not.\n";
226 } else {
227 m_isChanged = true;
228 }
229}
230
232 m_rPenning.fill(0.);
233 m_lambdaPenning.fill(0.);
234 if (!MediumGas::EnablePenningTransfer()) return false;
235
236 m_usePenning = true;
237 return true;
238}
239
241 const double lambda) {
242
243 if (!MediumGas::EnablePenningTransfer(r, lambda)) return false;
244
245 m_rPenning.fill(0.);
246 m_lambdaPenning.fill(0.);
247
248 // Make sure that the collision rate table is up to date.
249 if (!Update()) return false;
250 unsigned int nLevelsFound = 0;
251 for (unsigned int i = 0; i < m_nTerms; ++i) {
252 if (m_csType[i] % nCsTypes == ElectronCollisionTypeExcitation) {
253 ++nLevelsFound;
254 }
255 m_rPenning[i] = m_rPenningGlobal;
256 m_lambdaPenning[i] = m_lambdaPenningGlobal;
257 }
258
259 if (nLevelsFound > 0) {
260 std::cout << m_className << "::EnablePenningTransfer:\n "
261 << "Updated Penning transfer parameters for " << nLevelsFound
262 << " excitation cross-sections.\n";
263 if (nLevelsFound != m_excLevels.size() && !m_excLevels.empty()) {
264 std::cerr << m_className << "::EnablePenningTransfer:\n Warning: "
265 << "mismatch between number of excitation cross-sections ("
266 << nLevelsFound << ")\n and number of excitation rates in "
267 << "the gas table (" << m_excLevels.size() << ").\n "
268 << "The gas table was probably calculated using a different "
269 << "version of Magboltz.\n";
270 }
271 } else {
272 std::cerr << m_className << "::EnablePenningTransfer:\n "
273 << "No excitation cross-sections in the present energy range.\n";
274 }
275
276 if (m_useDeexcitation) {
277 std::cout << m_className << "::EnablePenningTransfer:\n "
278 << "Deexcitation handling will be switched off.\n";
279 }
280 m_usePenning = true;
281 return true;
282}
283
284bool MediumMagboltz::EnablePenningTransfer(const double r, const double lambda,
285 std::string gasname) {
286
287 if (!MediumGas::EnablePenningTransfer(r, lambda, gasname)) return false;
288
289 // Get (again) the "standard" name of this gas.
290 gasname = GetGasName(gasname);
291 if (gasname.empty()) return false;
292
293 // Look (again) for this gas in the present mixture.
294 int iGas = -1;
295 for (unsigned int i = 0; i < m_nComponents; ++i) {
296 if (m_gas[i] == gasname) {
297 iGas = i;
298 break;
299 }
300 }
301
302 // Make sure that the collision rate table is up to date.
303 if (!Update()) return false;
304 unsigned int nLevelsFound = 0;
305 for (unsigned int i = 0; i < m_nTerms; ++i) {
306 if (int(m_csType[i] / nCsTypes) != iGas) continue;
307 if (m_csType[i] % nCsTypes == ElectronCollisionTypeExcitation) {
308 ++nLevelsFound;
309 }
310 m_rPenning[i] = m_rPenningGas[iGas];
311 m_lambdaPenning[i] = m_lambdaPenningGas[iGas];
312 }
313
314 if (nLevelsFound > 0) {
315 std::cout << m_className << "::EnablePenningTransfer:\n";
316 if (m_lambdaPenningGas[iGas] > 0.) {
317 std::cout << " Penning transfer parameters for " << nLevelsFound
318 << " " << gasname << " excitation levels set to:\n"
319 << " r = " << m_rPenningGas[iGas] << ", lambda = "
320 << m_lambdaPenningGas[iGas] << " cm\n";
321 } else {
322 std::cout << " Penning transfer probability for " << nLevelsFound
323 << " " << gasname << " excitation levels set to r = "
324 << m_rPenningGas[iGas] << "\n";
325 }
326 } else {
327 std::cerr << m_className << "::EnablePenningTransfer:\n " << gasname
328 << " has no excitation levels in the present energy range.\n";
329 }
330
331 m_usePenning = true;
332 return true;
333}
334
336
338 m_rPenning.fill(0.);
339 m_lambdaPenning.fill(0.);
340
341 m_usePenning = false;
342}
343
344bool MediumMagboltz::DisablePenningTransfer(std::string gasname) {
345
346 if (!MediumGas::DisablePenningTransfer(gasname)) return false;
347 // Get the "standard" name of this gas.
348 gasname = GetGasName(gasname);
349 if (gasname.empty()) return false;
350
351 // Look (again) for this gas in the present gas mixture.
352 int iGas = -1;
353 for (unsigned int i = 0; i < m_nComponents; ++i) {
354 if (m_gas[i] == gasname) {
355 iGas = i;
356 break;
357 }
358 }
359
360 if (iGas < 0) return false;
361
362 unsigned int nLevelsFound = 0;
363 for (unsigned int i = 0; i < m_nTerms; ++i) {
364 if (int(m_csType[i] / nCsTypes) == iGas) {
365 m_rPenning[i] = 0.;
366 m_lambdaPenning[i] = 0.;
367 } else {
368 if (m_csType[i] % nCsTypes == ElectronCollisionTypeExcitation &&
369 m_rPenning[i] > Small) {
370 ++nLevelsFound;
371 }
372 }
373 }
374
375 if (nLevelsFound == 0) {
376 // There are no more excitation levels with r > 0.
377 std::cout << m_className << "::DisablePenningTransfer:\n"
378 << " Penning transfer switched off for all excitations.\n";
379 m_usePenning = false;
380 }
381 return true;
382}
383
384void MediumMagboltz::SetExcitationScaling(const double r, std::string gasname) {
385 if (r <= 0.) {
386 std::cerr << m_className << "::SetExcitationScaling: Incorrect value.\n";
387 return;
388 }
389
390 // Get the "standard" name of this gas.
391 gasname = GetGasName(gasname);
392 if (gasname.empty()) {
393 std::cerr << m_className << "::SetExcitationScaling: Unknown gas name.\n";
394 return;
395 }
396
397 // Look for this gas in the present gas mixture.
398 bool found = false;
399 for (unsigned int i = 0; i < m_nComponents; ++i) {
400 if (m_gas[i] == gasname) {
401 m_scaleExc[i] = r;
402 found = true;
403 break;
404 }
405 }
406
407 if (!found) {
408 std::cerr << m_className << "::SetExcitationScaling:\n"
409 << " Specified gas (" << gasname
410 << ") is not part of the present gas mixture.\n";
411 return;
412 }
413
414 // Force re-calculation of the collision rate table.
415 m_isChanged = true;
416}
417
418bool MediumMagboltz::Initialise(const bool verbose) {
419 if (!m_isChanged) {
420 if (m_debug) {
421 std::cerr << m_className << "::Initialise: Nothing changed.\n";
422 }
423 return true;
424 }
425 return Update(verbose);
426}
427
430
431 if (m_isChanged) {
432 if (!Initialise()) return;
433 }
434
435 std::cout << " Electron cross-sections:\n";
436 int igas = -1;
437 for (unsigned int i = 0; i < m_nTerms; ++i) {
438 // Collision type
439 int type = m_csType[i] % nCsTypes;
440 if (igas != int(m_csType[i] / nCsTypes)) {
441 igas = int(m_csType[i] / nCsTypes);
442 std::cout << " " << m_gas[igas] << "\n";
443 }
444 // Description (from Magboltz)
445 // Threshold energy
446 double e = m_rgas[igas] * m_energyLoss[i];
447 std::cout << " Level " << i << ": " << m_description[i] << "\n";
448 std::cout << " Type " << type;
449 if (type == ElectronCollisionTypeElastic) {
450 std::cout << " (elastic)\n";
451 } else if (type == ElectronCollisionTypeIonisation) {
452 std::cout << " (ionisation). Ionisation threshold: " << e << " eV.\n";
453 } else if (type == ElectronCollisionTypeAttachment) {
454 std::cout << " (attachment)\n";
455 } else if (type == ElectronCollisionTypeInelastic) {
456 std::cout << " (inelastic). Energy loss: " << e << " eV.\n";
457 } else if (type == ElectronCollisionTypeExcitation) {
458 std::cout << " (excitation). Excitation energy: " << e << " eV.\n";
459 } else if (type == ElectronCollisionTypeSuperelastic) {
460 std::cout << " (super-elastic). Energy gain: " << -e << " eV.\n";
461 } else if (type == ElectronCollisionTypeVirtual) {
462 std::cout << " (virtual)\n";
463 } else {
464 std::cout << " (unknown)\n";
465 }
466 if (type == ElectronCollisionTypeExcitation && m_usePenning &&
467 e > m_minIonPot) {
468 std::cout << " Penning transfer coefficient: "
469 << m_rPenning[i] << "\n";
470 } else if (type == ElectronCollisionTypeExcitation && m_useDeexcitation) {
471 const int idxc = m_iDeexcitation[i];
472 if (idxc < 0 || idxc >= (int)m_deexcitations.size()) {
473 std::cout << " Deexcitation cascade not implemented.\n";
474 continue;
475 }
476 const auto& dxc = m_deexcitations[idxc];
477 if (dxc.osc > 0.) {
478 std::cout << " Oscillator strength: " << dxc.osc << "\n";
479 }
480 std::cout << " Decay channels:\n";
481 const int nChannels = dxc.type.size();
482 for (int j = 0; j < nChannels; ++j) {
483 if (dxc.type[j] == DxcTypeRad) {
484 std::cout << " Radiative decay to ";
485 if (dxc.final[j] < 0) {
486 std::cout << "ground state: ";
487 } else {
488 std::cout << m_deexcitations[dxc.final[j]].label << ": ";
489 }
490 } else if (dxc.type[j] == DxcTypeCollIon) {
491 if (dxc.final[j] < 0) {
492 std::cout << " Penning ionisation: ";
493 } else {
494 std::cout << " Associative ionisation: ";
495 }
496 } else if (dxc.type[j] == DxcTypeCollNonIon) {
497 if (dxc.final[j] >= 0) {
498 std::cout << " Collision-induced transition to "
499 << m_deexcitations[dxc.final[j]].label << ": ";
500 } else {
501 std::cout << " Loss: ";
502 }
503 }
504 const double br = j == 0 ? dxc.p[j] : dxc.p[j] - dxc.p[j - 1];
505 std::cout << std::setprecision(5) << br * 100. << "%\n";
506 }
507 }
508 }
509}
510
512 // If necessary, update the collision rates table.
513 if (!Update()) return 0.;
514 return m_cfNull;
515}
516
518 const int /*band*/) {
519 // Check if the electron energy is within the currently set range.
520 if (e <= 0.) {
521 std::cerr << m_className << "::GetElectronCollisionRate: Invalid energy.\n";
522 return m_cfTot[0];
523 }
524 if (e > m_eMax) {
525 std::cerr << m_className << "::GetElectronCollisionRate:\n Rate at " << e
526 << " eV is not included in the current table.\n "
527 << "Increasing energy range to " << 1.05 * e << " eV.\n";
528 SetMaxElectronEnergy(1.05 * e);
529 }
530
531 // If necessary, update the collision rates table.
532 if (!Update()) return 0.;
533
534 // Get the energy interval.
535 if (e < m_eHigh) {
536 // Linear binning
537 return m_cfTot[int(e * m_eStepInv)];
538 }
539
540 // Logarithmic binning
541 const double eLog = log(e);
542 int iE = int((eLog - m_eHighLog) / m_lnStep);
543 // Calculate the collision rate by log-log interpolation.
544 const double fmax = m_cfTotLog[iE];
545 const double fmin = iE == 0 ? log(m_cfTot.back()) : m_cfTotLog[iE - 1];
546 const double emin = m_eHighLog + iE * m_lnStep;
547 const double f = fmin + (eLog - emin) * (fmax - fmin) / m_lnStep;
548 return exp(f);
549}
550
552 const unsigned int level,
553 const int band) {
554 // Check if the electron energy is within the currently set range.
555 if (e <= 0.) {
556 std::cerr << m_className << "::GetElectronCollisionRate: Invalid energy.\n";
557 return 0.;
558 }
559
560 // Check if the level exists.
561 if (level >= m_nTerms) {
562 std::cerr << m_className << "::GetElectronCollisionRate: Invalid level.\n";
563 return 0.;
564 }
565
566 // Get the total scattering rate.
567 double rate = GetElectronCollisionRate(e, band);
568 // Get the energy interval.
569 if (e < m_eHigh) {
570 // Linear binning
571 const int iE = int(e * m_eStepInv);
572 if (level == 0) {
573 rate *= m_cf[iE][0];
574 } else {
575 rate *= m_cf[iE][level] - m_cf[iE][level - 1];
576 }
577 } else {
578 // Logarithmic binning
579 const int iE = int((log(e) - m_eHighLog) / m_lnStep);
580 if (level == 0) {
581 rate *= m_cfLog[iE][0];
582 } else {
583 rate *= m_cfLog[iE][level] - m_cfLog[iE][level - 1];
584 }
585 }
586 return rate;
587}
588
589bool MediumMagboltz::ElectronCollision(const double e, int& type,
590 int& level, double& e1, double& dx, double& dy, double& dz,
591 std::vector<std::pair<Particle, double> >& secondaries, int& ndxc,
592 int& band) {
593 band = 0;
594 ndxc = 0;
595 if (e <= 0.) {
596 std::cerr << m_className << "::ElectronCollision: Invalid energy.\n";
597 return false;
598 }
599 // Check if the electron energy is within the currently set range.
600 if (e > m_eMax) {
601 std::cerr << m_className << "::ElectronCollision:\n Requested energy ("
602 << e << " eV) exceeds current energy range.\n"
603 << " Increasing energy range to " << 1.05 * e << " eV.\n";
604 SetMaxElectronEnergy(1.05 * e);
605 }
606
607 // If necessary, update the collision rates table.
608 if (!Update()) return false;
609
610 double angCut = 1.;
611 double angPar = 0.5;
612
613 if (e < m_eHigh) {
614 // Linear binning
615 // Get the energy interval.
616 const int iE = int(e * m_eStepInv);
617
618 // Sample the scattering process.
619 const double r = RndmUniform();
620 level = 0;
621 if (r > m_cf[iE][0]) {
622 int iLow = 0;
623 int iUp = m_nTerms - 1;
624 while (iUp - iLow > 1) {
625 int iMid = (iLow + iUp) >> 1;
626 if (r < m_cf[iE][iMid]) {
627 iUp = iMid;
628 } else {
629 iLow = iMid;
630 }
631 }
632 level = iUp;
633 }
634 // Get the angular distribution parameters.
635 angCut = m_scatCut[iE][level];
636 angPar = m_scatPar[iE][level];
637 } else {
638 // Logarithmic binning
639 // Get the energy interval.
640 const int iE = std::min(std::max(int(log(e / m_eHigh) / m_lnStep), 0),
641 nEnergyStepsLog - 1);
642 // Sample the scattering process.
643 const double r = RndmUniform();
644 level = 0;
645 if (r > m_cfLog[iE][0]) {
646 int iLow = 0;
647 int iUp = m_nTerms - 1;
648 while (iUp - iLow > 1) {
649 int iMid = (iLow + iUp) >> 1;
650 if (r < m_cfLog[iE][iMid]) {
651 iUp = iMid;
652 } else {
653 iLow = iMid;
654 }
655 }
656 level = iUp;
657 }
658 // Get the angular distribution parameters.
659 angCut = m_scatCutLog[iE][level];
660 angPar = m_scatParLog[iE][level];
661 }
662
663 // Extract the collision type.
664 type = m_csType[level] % nCsTypes;
665 const int igas = int(m_csType[level] / nCsTypes);
666 // Increase the collision counters.
667 ++m_nCollisions[type];
668 ++m_nCollisionsDetailed[level];
669
670 // Get the energy loss for this process.
671 double loss = m_energyLoss[level];
672
673 if (type == ElectronCollisionTypeVirtual) return true;
674
675 if (type == ElectronCollisionTypeIonisation) {
676 // Sample the secondary electron energy according to
677 // the Opal-Beaty-Peterson parameterisation.
678 double esec = 0.;
679 if (e < loss) loss = e - 0.0001;
680 if (m_useOpalBeaty) {
681 // Get the splitting parameter.
682 const double w = m_wOpalBeaty[level];
683 esec = w * tan(RndmUniform() * atan(0.5 * (e - loss) / w));
684 // Rescaling (SST)
685 // esec = w * pow(esec / w, 0.9524);
686 } else if (m_useGreenSawada) {
687 const double gs = m_parGreenSawada[igas][0];
688 const double gb = m_parGreenSawada[igas][1];
689 const double w = gs * e / (e + gb);
690 const double ts = m_parGreenSawada[igas][2];
691 const double ta = m_parGreenSawada[igas][3];
692 const double tb = m_parGreenSawada[igas][4];
693 const double esec0 = ts - ta / (e + tb);
694 const double r = RndmUniform();
695 esec = esec0 +
696 w * tan((r - 1.) * atan(esec0 / w) +
697 r * atan((0.5 * (e - loss) - esec0) / w));
698 } else {
699 esec = RndmUniform() * (e - loss);
700 }
701 if (esec <= 0) esec = Small;
702 loss += esec;
703 // Add the secondary electron.
704 secondaries.emplace_back(std::make_pair(Particle::Electron, esec));
705 // Add the ion.
706 secondaries.emplace_back(std::make_pair(Particle::Ion, 0.));
707 bool fluorescence = false;
708 if (m_yFluorescence[level] > Small) {
709 if (RndmUniform() < m_yFluorescence[level]) fluorescence = true;
710 }
711 // Add Auger and photo electrons (if any).
712 if (fluorescence) {
713 if (m_nAuger2[level] > 0) {
714 const double eav = m_eAuger2[level] / m_nAuger2[level];
715 for (unsigned int i = 0; i < m_nAuger2[level]; ++i) {
716 secondaries.emplace_back(std::make_pair(Particle::Electron, eav));
717 }
718 }
719 if (m_nFluorescence[level] > 0) {
720 const double eav = m_eFluorescence[level] / m_nFluorescence[level];
721 for (unsigned int i = 0; i < m_nFluorescence[level]; ++i) {
722 secondaries.emplace_back(std::make_pair(Particle::Electron, eav));
723 }
724 }
725 } else if (m_nAuger1[level] > 0) {
726 const double eav = m_eAuger1[level] / m_nAuger1[level];
727 for (unsigned int i = 0; i < m_nAuger1[level]; ++i) {
728 secondaries.emplace_back(std::make_pair(Particle::Electron, eav));
729 }
730 }
731 } else if (type == ElectronCollisionTypeExcitation) {
732 // Follow the de-excitation cascade (if switched on).
733 if (m_useDeexcitation && m_iDeexcitation[level] >= 0) {
734 int fLevel = 0;
735 ComputeDeexcitationInternal(m_iDeexcitation[level], fLevel);
736 ndxc = m_dxcProducts.size();
737 } else if (m_usePenning) {
738 m_dxcProducts.clear();
739 // Simplified treatment of Penning ionisation.
740 // If the energy threshold of this level exceeds the
741 // ionisation potential of one of the gases,
742 // create a new electron (with probability rPenning).
743 if (m_debug) {
744 std::cout << m_className << "::ElectronCollision:\n"
745 << " Level: " << level << "\n"
746 << " Ionization potential: " << m_minIonPot << "\n"
747 << " Excitation energy: " << loss * m_rgas[igas] << "\n"
748 << " Penning probability: " << m_rPenning[level] << "\n";
749 }
750 if (loss * m_rgas[igas] > m_minIonPot &&
751 RndmUniform() < m_rPenning[level]) {
752 // The energy of the secondary electron is assumed to be given by
753 // the difference of excitation and ionisation threshold.
754 double esec = loss * m_rgas[igas] - m_minIonPot;
755 if (esec <= 0) esec = Small;
756 // Add the secondary electron to the list.
757 dxcProd newDxcProd;
758 newDxcProd.t = 0.;
759 newDxcProd.s = 0.;
760 if (m_lambdaPenning[level] > Small) {
761 // Uniform distribution within a sphere of radius lambda
762 newDxcProd.s = m_lambdaPenning[level] * std::cbrt(RndmUniformPos());
763 }
764 newDxcProd.energy = esec;
765 newDxcProd.type = DxcProdTypeElectron;
766 m_dxcProducts.push_back(std::move(newDxcProd));
767 ndxc = 1;
768 ++m_nPenning;
769 }
770 }
771 }
772
773 if (e < loss) loss = e - 0.0001;
774
775 // Determine the scattering angle.
776 double ctheta0 = 1. - 2. * RndmUniform();
777 if (m_useAnisotropic) {
778 switch (m_scatModel[level]) {
779 case 0:
780 break;
781 case 1:
782 ctheta0 = 1. - RndmUniform() * angCut;
783 if (RndmUniform() > angPar) ctheta0 = -ctheta0;
784 break;
785 case 2:
786 ctheta0 = (ctheta0 + angPar) / (1. + angPar * ctheta0);
787 break;
788 default:
789 std::cerr << m_className << "::ElectronCollision:\n"
790 << " Unknown scattering model.\n"
791 << " Using isotropic distribution.\n";
792 break;
793 }
794 }
795
796 const double s1 = m_rgas[igas];
797 const double theta0 = acos(ctheta0);
798 const double arg = std::max(1. - s1 * loss / e, Small);
799 const double d = 1. - ctheta0 * sqrt(arg);
800
801 // Update the energy.
802 e1 = std::max(e * (1. - loss / (s1 * e) - 2. * d * m_s2[igas]), Small);
803 double q = std::min(sqrt((e / e1) * arg) / s1, 1.);
804 const double theta = asin(q * sin(theta0));
805 double ctheta = cos(theta);
806 if (ctheta0 < 0.) {
807 const double u = (s1 - 1.) * (s1 - 1.) / arg;
808 if (ctheta0 * ctheta0 > u) ctheta = -ctheta;
809 }
810 const double stheta = sin(theta);
811 // Calculate the direction after the collision.
812 dz = std::min(dz, 1.);
813 const double argZ = sqrt(dx * dx + dy * dy);
814
815 // Azimuth is chosen at random.
816 const double phi = TwoPi * RndmUniform();
817 const double cphi = cos(phi);
818 const double sphi = sin(phi);
819 if (argZ > 0.) {
820 const double a = stheta / argZ;
821 const double dz1 = dz * ctheta + argZ * stheta * sphi;
822 const double dy1 = dy * ctheta + a * (dx * cphi - dy * dz * sphi);
823 const double dx1 = dx * ctheta - a * (dy * cphi + dx * dz * sphi);
824 dz = dz1;
825 dy = dy1;
826 dx = dx1;
827 } else {
828 dz = ctheta;
829 dx = cphi * stheta;
830 dy = sphi * stheta;
831 }
832 return true;
833}
834
835bool MediumMagboltz::GetDeexcitationProduct(const unsigned int i, double& t,
836 double& s, int& type,
837 double& energy) const {
838 if (i >= m_dxcProducts.size() || !(m_useDeexcitation || m_usePenning)) {
839 return false;
840 }
841 t = m_dxcProducts[i].t;
842 s = m_dxcProducts[i].s;
843 type = m_dxcProducts[i].type;
844 energy = m_dxcProducts[i].energy;
845 return true;
846}
847
849 if (e <= 0.) {
850 std::cerr << m_className << "::GetPhotonCollisionRate: Invalid energy.\n";
851 return m_cfTotGamma[0];
852 }
853 if (e > m_eFinalGamma) {
854 std::cerr << m_className << "::GetPhotonCollisionRate:\n Rate at " << e
855 << " eV is not included in the current table.\n"
856 << " Increasing energy range to " << 1.05 * e << " eV.\n";
857 SetMaxPhotonEnergy(1.05 * e);
858 }
859
860 if (!Update()) return 0.;
861
862 const int iE =
863 std::min(std::max(int(e / m_eStepGamma), 0), nEnergyStepsGamma - 1);
864
865 double cfSum = m_cfTotGamma[iE];
866 if (m_useDeexcitation && m_useRadTrap && !m_deexcitations.empty()) {
867 // Loop over the excitations.
868 for (const auto& dxc : m_deexcitations) {
869 if (dxc.cf > 0. && fabs(e - dxc.energy) <= dxc.width) {
870 cfSum += dxc.cf *
871 TMath::Voigt(e - dxc.energy, dxc.sDoppler, 2 * dxc.gPressure);
872 }
873 }
874 }
875
876 return cfSum;
877}
878
879bool MediumMagboltz::GetPhotonCollision(const double e, int& type, int& level,
880 double& e1, double& ctheta, int& nsec,
881 double& esec) {
882 if (e <= 0.) {
883 std::cerr << m_className << "::GetPhotonCollision: Invalid energy.\n";
884 return false;
885 }
886 if (e > m_eFinalGamma) {
887 std::cerr << m_className << "::GetPhotonCollision:\n Provided energy ("
888 << e << " eV) exceeds current energy range.\n"
889 << " Increasing energy range to " << 1.05 * e << " eV.\n";
890 SetMaxPhotonEnergy(1.05 * e);
891 }
892
893 if (!Update()) return false;
894
895 // Energy interval
896 const int iE =
897 std::min(std::max(int(e / m_eStepGamma), 0), nEnergyStepsGamma - 1);
898
899 double r = m_cfTotGamma[iE];
900 if (m_useDeexcitation && m_useRadTrap && !m_deexcitations.empty()) {
901 int nLines = 0;
902 std::vector<double> pLine(0);
903 std::vector<int> iLine(0);
904 // Loop over the excitations.
905 const unsigned int nDeexcitations = m_deexcitations.size();
906 for (unsigned int i = 0; i < nDeexcitations; ++i) {
907 const auto& dxc = m_deexcitations[i];
908 if (dxc.cf > 0. && fabs(e - dxc.energy) <= dxc.width) {
909 r += dxc.cf *
910 TMath::Voigt(e - dxc.energy, dxc.sDoppler, 2 * dxc.gPressure);
911 pLine.push_back(r);
912 iLine.push_back(i);
913 ++nLines;
914 }
915 }
916 r *= RndmUniform();
917 if (nLines > 0 && r >= m_cfTotGamma[iE]) {
918 // Photon is absorbed by a discrete line.
919 for (int i = 0; i < nLines; ++i) {
920 if (r <= pLine[i]) {
921 ++m_nPhotonCollisions[PhotonCollisionTypeExcitation];
922 int fLevel = 0;
923 ComputeDeexcitationInternal(iLine[i], fLevel);
924 type = PhotonCollisionTypeExcitation;
925 nsec = m_dxcProducts.size();
926 return true;
927 }
928 }
929 std::cerr << m_className << "::GetPhotonCollision:\n";
930 std::cerr << " Random sampling of deexcitation line failed.\n";
931 std::cerr << " Program bug!\n";
932 return false;
933 }
934 } else {
935 r *= RndmUniform();
936 }
937
938 if (r <= m_cfGamma[iE][0]) {
939 level = 0;
940 } else if (r >= m_cfGamma[iE][m_nPhotonTerms - 1]) {
941 level = m_nPhotonTerms - 1;
942 } else {
943 const auto begin = m_cfGamma[iE].cbegin();
944 level = std::lower_bound(begin, begin + m_nPhotonTerms, r) - begin;
945 }
946
947 nsec = 0;
948 esec = e1 = 0.;
949 type = csTypeGamma[level];
950 // Collision type
951 type = type % nCsTypesGamma;
952 int ngas = int(csTypeGamma[level] / nCsTypesGamma);
953 ++m_nPhotonCollisions[type];
954 // Ionising collision
955 if (type == 1) {
956 esec = std::max(e - m_ionPot[ngas], Small);
957 nsec = 1;
958 }
959
960 // Determine the scattering angle
961 ctheta = 2 * RndmUniform() - 1.;
962
963 return true;
964}
965
967 m_nCollisions.fill(0);
968 m_nCollisionsDetailed.assign(m_nTerms, 0);
969 m_nPenning = 0;
970 m_nPhotonCollisions.fill(0);
971}
972
974 return std::accumulate(std::begin(m_nCollisions), std::end(m_nCollisions), 0);
975}
976
978 unsigned int& nElastic, unsigned int& nIonisation,
979 unsigned int& nAttachment, unsigned int& nInelastic,
980 unsigned int& nExcitation, unsigned int& nSuperelastic) const {
981 nElastic = m_nCollisions[ElectronCollisionTypeElastic];
982 nIonisation = m_nCollisions[ElectronCollisionTypeIonisation];
983 nAttachment = m_nCollisions[ElectronCollisionTypeAttachment];
984 nInelastic = m_nCollisions[ElectronCollisionTypeInelastic];
985 nExcitation = m_nCollisions[ElectronCollisionTypeExcitation];
986 nSuperelastic = m_nCollisions[ElectronCollisionTypeSuperelastic];
987 return nElastic + nIonisation + nAttachment + nInelastic + nExcitation +
988 nSuperelastic;
989}
990
992 if (!Update()) return 0;
993 return m_nTerms;
994}
995
996bool MediumMagboltz::GetLevel(const unsigned int i, int& ngas, int& type,
997 std::string& descr, double& e) {
998 if (!Update()) return false;
999
1000 if (i >= m_nTerms) {
1001 std::cerr << m_className << "::GetLevel: Index out of range.\n";
1002 return false;
1003 }
1004
1005 // Collision type
1006 type = m_csType[i] % nCsTypes;
1007 ngas = int(m_csType[i] / nCsTypes);
1008 // Description (from Magboltz)
1009 descr = m_description[i];
1010 // Threshold energy
1011 e = m_rgas[ngas] * m_energyLoss[i];
1012 if (m_debug) {
1013 std::cout << m_className << "::GetLevel:\n"
1014 << " Level " << i << ": " << descr << "\n"
1015 << " Type " << type << "\n"
1016 << " Threshold energy: " << e << " eV\n";
1017 if (type == ElectronCollisionTypeExcitation && m_usePenning &&
1018 e > m_minIonPot) {
1019 std::cout << " Penning transfer coefficient: " << m_rPenning[i]
1020 << "\n";
1021 } else if (type == ElectronCollisionTypeExcitation && m_useDeexcitation) {
1022 const int idxc = m_iDeexcitation[i];
1023 if (idxc < 0 || idxc >= (int)m_deexcitations.size()) {
1024 std::cout << " Deexcitation cascade not implemented.\n";
1025 return true;
1026 }
1027 const auto& dxc = m_deexcitations[idxc];
1028 if (dxc.osc > 0.) {
1029 std::cout << " Oscillator strength: " << dxc.osc << "\n";
1030 }
1031 std::cout << " Decay channels:\n";
1032 const int nChannels = dxc.type.size();
1033 for (int j = 0; j < nChannels; ++j) {
1034 if (dxc.type[j] == DxcTypeRad) {
1035 std::cout << " Radiative decay to ";
1036 if (dxc.final[j] < 0) {
1037 std::cout << "ground state: ";
1038 } else {
1039 std::cout << m_deexcitations[dxc.final[j]].label << ": ";
1040 }
1041 } else if (dxc.type[j] == DxcTypeCollIon) {
1042 if (dxc.final[j] < 0) {
1043 std::cout << " Penning ionisation: ";
1044 } else {
1045 std::cout << " Associative ionisation: ";
1046 }
1047 } else if (dxc.type[j] == DxcTypeCollNonIon) {
1048 if (dxc.final[j] >= 0) {
1049 std::cout << " Collision-induced transition to "
1050 << m_deexcitations[dxc.final[j]].label << ": ";
1051 } else {
1052 std::cout << " Loss: ";
1053 }
1054 }
1055 const double br = j == 0 ? dxc.p[j] : dxc.p[j] - dxc.p[j - 1];
1056 std::cout << std::setprecision(5) << br * 100. << "%\n";
1057 }
1058 }
1059 }
1060
1061 return true;
1062}
1063
1064bool MediumMagboltz::GetPenningTransfer(const unsigned int i,
1065 double& r, double& lambda) {
1066 r = 0.;
1067 lambda = 0.;
1068 if (!Update()) return false;
1069 if (i >= m_nTerms) return false;
1070 r = m_rPenning[i];
1071 lambda = m_lambdaPenning[i];
1072 return true;
1073}
1074
1076 const unsigned int level) const {
1077 if (level >= m_nTerms) {
1078 std::cerr << m_className << "::GetNumberOfElectronCollisions: "
1079 << "Level " << level << " does not exist.\n";
1080 return 0;
1081 }
1082 return m_nCollisionsDetailed[level];
1083}
1084
1086 return std::accumulate(std::begin(m_nPhotonCollisions),
1087 std::end(m_nPhotonCollisions), 0);
1088}
1089
1091 unsigned int& nElastic, unsigned int& nIonising,
1092 unsigned int& nInelastic) const {
1093 nElastic = m_nPhotonCollisions[0];
1094 nIonising = m_nPhotonCollisions[1];
1095 nInelastic = m_nPhotonCollisions[2];
1096 return nElastic + nIonising + nInelastic;
1097}
1098
1099int MediumMagboltz::GetGasNumberMagboltz(const std::string& input) {
1100
1101 if (input.empty()) return 0;
1102
1103 if (input == "CF4") {
1104 return 1;
1105 } else if (input == "Ar") {
1106 return 2;
1107 } else if (input == "He" || input == "He-4") {
1108 // Helium 4
1109 return 3;
1110 } else if (input == "He-3") {
1111 // Helium 3
1112 return 4;
1113 } else if (input == "Ne") {
1114 return 5;
1115 } else if (input == "Kr") {
1116 return 6;
1117 } else if (input == "Xe") {
1118 return 7;
1119 } else if (input == "CH4") {
1120 // Methane
1121 return 8;
1122 } else if (input == "C2H6") {
1123 // Ethane
1124 return 9;
1125 } else if (input == "C3H8") {
1126 // Propane
1127 return 10;
1128 } else if (input == "iC4H10") {
1129 // Isobutane
1130 return 11;
1131 } else if (input == "CO2") {
1132 return 12;
1133 } else if (input == "neoC5H12") {
1134 // Neopentane
1135 return 13;
1136 } else if (input == "H2O") {
1137 return 14;
1138 } else if (input == "O2") {
1139 return 15;
1140 } else if (input == "N2") {
1141 return 16;
1142 } else if (input == "NO") {
1143 // Nitric oxide (NO)
1144 return 17;
1145 } else if (input == "N2O") {
1146 // Nitrous oxide (N2O)
1147 return 18;
1148 } else if (input == "C2H4") {
1149 // Ethene (C2H4)
1150 return 19;
1151 } else if (input == "C2H2") {
1152 // Acetylene (C2H2)
1153 return 20;
1154 } else if (input == "H2") {
1155 // Hydrogen
1156 return 21;
1157 } else if (input == "D2") {
1158 // Deuterium
1159 return 22;
1160 } else if (input == "CO") {
1161 // Carbon monoxide (CO)
1162 return 23;
1163 } else if (input == "Methylal") {
1164 // Methylal (dimethoxymethane, CH3-O-CH2-O-CH3, "hot" version)
1165 return 24;
1166 } else if (input == "DME") {
1167 return 25;
1168 } else if (input == "Reid-Step") {
1169 return 26;
1170 } else if (input == "Maxwell-Model") {
1171 return 27;
1172 } else if (input == "Reid-Ramp") {
1173 return 28;
1174 } else if (input == "C2F6") {
1175 return 29;
1176 } else if (input == "SF6") {
1177 return 30;
1178 } else if (input == "NH3") {
1179 return 31;
1180 } else if (input == "C3H6") {
1181 // Propene
1182 return 32;
1183 } else if (input == "cC3H6") {
1184 // Cyclopropane
1185 return 33;
1186 } else if (input == "CH3OH") {
1187 // Methanol
1188 return 34;
1189 } else if (input == "C2H5OH") {
1190 // Ethanol
1191 return 35;
1192 } else if (input == "C3H7OH") {
1193 // Propanol
1194 return 36;
1195 } else if (input == "Cs") {
1196 return 37;
1197 } else if (input == "F2") {
1198 // Fluorine
1199 return 38;
1200 } else if (input == "CS2") {
1201 return 39;
1202 } else if (input == "COS") {
1203 return 40;
1204 } else if (input == "CD4") {
1205 // Deuterated methane
1206 return 41;
1207 } else if (input == "BF3") {
1208 return 42;
1209 } else if (input == "C2HF5" || input == "C2H2F4") {
1210 return 43;
1211 } else if (input == "TMA") {
1212 return 44;
1213 } else if (input == "nC3H7OH") {
1214 // n-propanol
1215 return 46;
1216 } else if (input == "paraH2") {
1217 // Para hydrogen
1218 return 47;
1219 } else if (input == "orthoD2") {
1220 // Ortho deuterium
1221 return 48;
1222 } else if (input == "CHF3") {
1223 return 50;
1224 } else if (input == "CF3Br") {
1225 return 51;
1226 } else if (input == "C3F8") {
1227 return 52;
1228 } else if (input == "O3") {
1229 // Ozone
1230 return 53;
1231 } else if (input == "Hg") {
1232 // Mercury
1233 return 54;
1234 } else if (input == "H2S") {
1235 return 55;
1236 } else if (input == "nC4H10") {
1237 // n-Butane
1238 return 56;
1239 } else if (input == "nC5H12") {
1240 // n-Pentane
1241 return 57;
1242 } else if (input == "N2 (Phelps)") {
1243 return 58;
1244 } else if (input == "GeH4") {
1245 // Germane, GeH4
1246 return 59;
1247 } else if (input == "SiH4") {
1248 // Silane, SiH4
1249 return 60;
1250 } else if (input == "CCl4") {
1251 return 61;
1252 }
1253
1254 std::cerr << "MediumMagboltz::GetGasNumberMagboltz:\n"
1255 << " Gas " << input << " is not defined.\n";
1256 return 0;
1257}
1258
1259bool MediumMagboltz::Update(const bool verbose) {
1260
1261 if (!m_isChanged) return true;
1262 std::lock_guard<std::mutex> guard(m_mutex);
1263 if (!Mixer(verbose)) {
1264 std::cerr << m_className
1265 << "::Update: Error calculating the collision rates table.\n";
1266 return false;
1267 }
1268 m_isChanged = false;
1269 return true;
1270}
1271
1272
1273bool MediumMagboltz::Mixer(const bool verbose) {
1274
1275 // Set constants and parameters in Magboltz common blocks.
1276 Magboltz::cnsts_.echarg = ElementaryCharge * 1.e-15;
1277 Magboltz::cnsts_.emass = ElectronMassGramme;
1278 Magboltz::cnsts_.amu = AtomicMassUnit;
1279 Magboltz::cnsts_.pir2 = BohrRadius * BohrRadius * Pi;
1280 Magboltz::inpt_.ary = RydbergEnergy;
1281
1282 Magboltz::inpt_.akt = BoltzmannConstant * m_temperature;
1283 Magboltz::inpt_.tempc = m_temperature - ZeroCelsius;
1285
1288 Magboltz::inpt_.nAniso = m_useAnisotropic ? 2 : 0;
1289
1290 for (unsigned int i = 0; i < Magboltz::nEnergySteps; ++i) {
1291 const double en = (i + 0.5) * m_eStep;
1292 Magboltz::mix2_.eg[i] = en;
1293 Magboltz::mix2_.eroot[i] = sqrt(en);
1294 Magboltz::dens_.den[i] = 0.;
1295 }
1296 constexpr int iemax = Magboltz::nEnergySteps - 1;
1297
1298 // Calculate the atomic density (ideal gas law).
1299 const double dens = GetNumberDensity();
1300 // Prefactor for calculation of scattering rate from cross-section.
1301 const double prefactor = dens * SpeedOfLight * sqrt(2. / ElectronMass);
1302
1303 m_rgas.fill(1.);
1304 m_s2.fill(0.);
1305
1306 m_ionPot.fill(-1.);
1307 m_minIonPot = -1.;
1308
1309 m_parGreenSawada.fill({1., 0., 0., 0., 0.});
1310 m_hasGreenSawada.fill(false);
1311
1312 m_wOpalBeaty.fill(1.);
1313 m_energyLoss.fill(0.);
1314 m_csType.fill(0);
1315
1316 m_yFluorescence.fill(0.);
1317 m_nAuger1.fill(0);
1318 m_eAuger1.fill(0.);
1319 m_nAuger2.fill(0);
1320 m_eAuger2.fill(0.);
1321 m_nFluorescence.fill(0);
1322 m_eFluorescence.fill(0.);
1323
1324 m_scatModel.fill(0);
1325
1326 m_rPenning.fill(0.);
1327 m_lambdaPenning.fill(0.);
1328
1329 m_deexcitations.clear();
1330 m_iDeexcitation.fill(-1);
1331
1332 // Reset the collision rates.
1333 m_cfTot.assign(Magboltz::nEnergySteps, 0.);
1334 m_cfTotLog.assign(nEnergyStepsLog, 0.);
1335
1336 m_cf.assign(Magboltz::nEnergySteps,
1337 std::vector<double>(Magboltz::nMaxLevels, 0.));
1338 m_cfLog.assign(nEnergyStepsLog,
1339 std::vector<double>(Magboltz::nMaxLevels, 0.));
1340
1341 m_scatPar.assign(Magboltz::nEnergySteps,
1342 std::vector<double>(Magboltz::nMaxLevels, 0.5));
1343 m_scatCut.assign(Magboltz::nEnergySteps,
1344 std::vector<double>(Magboltz::nMaxLevels, 1.));
1345
1346 m_scatParLog.assign(nEnergyStepsLog,
1347 std::vector<double>(Magboltz::nMaxLevels, 0.5));
1348 m_scatCutLog.assign(nEnergyStepsLog,
1349 std::vector<double>(Magboltz::nMaxLevels, 1.));
1350
1351 // Cross-sections
1352 // 0: total, 1: elastic,
1353 // 2: ionisation, 3: attachment,
1354 // 4, 5: unused
1355 static double q[Magboltz::nEnergySteps][6];
1356 // Inelastic cross-sections
1358 // Ionisation cross-sections
1360 // Attachment cross-sections
1362 // "Null-collision" cross-sections
1363 static double qNull[Magboltz::nEnergySteps][Magboltz::nMaxNullTerms];
1364 // Parameters for scattering angular distribution
1365 static double pEqEl[Magboltz::nEnergySteps][6];
1366 // Parameters for angular distribution in inelastic collisions
1368 // Parameters for angular distribution in ionising collisions
1370 // Penning transfer parameters
1371 static double penFra[Magboltz::nMaxInelasticTerms][3];
1372 // Description of cross-section terms
1374 // Description of "null-collision" cross-section terms
1375 static char scrptn[Magboltz::nMaxNullTerms][Magboltz::nCharDescr];
1376
1377 // Check the gas composition and establish the gas numbers.
1378 int gasNumber[m_nMaxGases];
1379 for (unsigned int i = 0; i < m_nComponents; ++i) {
1380 const int ng = GetGasNumberMagboltz(m_gas[i]);
1381 if (ng <= 0) {
1382 std::cerr << m_className << "::Mixer:\n Gas " << m_gas[i]
1383 << " does not have a gas number in Magboltz.\n";
1384 return false;
1385 }
1386 gasNumber[i] = ng;
1387 }
1388
1389 if (m_debug || verbose) {
1390 std::cout << m_className << "::Mixer:\n " << Magboltz::nEnergySteps
1391 << " linear energy steps between 0 and "
1392 << std::min(m_eMax, m_eHigh) << " eV.\n";
1393 if (m_eMax > m_eHigh) {
1394 std::cout << " " << nEnergyStepsLog << " logarithmic steps between "
1395 << m_eHigh << " and " << m_eMax << " eV\n";
1396 }
1397 }
1398 m_nTerms = 0;
1399
1400 std::ofstream outfile;
1401 if (m_useCsOutput) {
1402 outfile.open("cs.txt", std::ios::out);
1403 outfile << "# energy [eV] vs. cross-section [cm2]\n";
1404 }
1405
1406 // Loop over the gases in the mixture.
1407 for (unsigned int iGas = 0; iGas < m_nComponents; ++iGas) {
1408 Magboltz::inpt_.efinal = std::min(m_eMax, m_eHigh);
1409 Magboltz::inpt_.estep = m_eStep;
1410 Magboltz::mix2_.eg[iemax] = (iemax + 0.5) * m_eStep;
1411 Magboltz::mix2_.eroot[iemax] = sqrt((iemax + 0.5) * m_eStep);
1412 char name[Magboltz::nCharName];
1413 // Number of inelastic cross-sections
1414 static std::int64_t nIn = 0;
1415 // Number of ionisation cross-sections
1416 static std::int64_t nIon = 0;
1417 // Number of attachment cross-sections
1418 static std::int64_t nAtt = 0;
1419 // Number of "null-collision" cross-sections
1420 static std::int64_t nNull = 0;
1421 // Virial coefficient (not used).
1422 static double virial = 0.;
1423 // Thresholds/characteristic energies.
1424 static double e[6];
1425 // Energy losses for inelastic cross-sections.
1426 static double eIn[Magboltz::nMaxInelasticTerms];
1427 // Ionisation thresholds.
1428 static double eIon[Magboltz::nMaxIonisationTerms];
1429 // Scattering algorithms
1430 static std::int64_t kIn[Magboltz::nMaxInelasticTerms];
1431 static std::int64_t kEl[6];
1432 // Opal-Beaty parameter
1433 static double eoby[Magboltz::nMaxIonisationTerms];
1434 // Scaling factor for "null-collision" terms
1435 static double scln[Magboltz::nMaxNullTerms];
1436 // Parameters for simulation of Auger and fluorescence processes.
1437 static std::int64_t nc0[Magboltz::nMaxIonisationTerms];
1438 static std::int64_t ng1[Magboltz::nMaxIonisationTerms];
1439 static std::int64_t ng2[Magboltz::nMaxIonisationTerms];
1440 static double ec0[Magboltz::nMaxIonisationTerms];
1441 static double wklm[Magboltz::nMaxIonisationTerms];
1442 static double efl[Magboltz::nMaxIonisationTerms];
1443 static double eg1[Magboltz::nMaxIonisationTerms];
1444 static double eg2[Magboltz::nMaxIonisationTerms];
1445 // Retrieve the cross-section data for this gas from Magboltz.
1446 std::int64_t ngs = gasNumber[iGas];
1448 &ngs, q[0], qIn[0], &nIn, &e[0], eIn, name, &virial, eoby, pEqEl[0],
1449 pEqIn[0], penFra[0], kEl, kIn, qIon[0], pEqIon[0], eIon, &nIon,
1450 qAtt[0], &nAtt, qNull[0], &nNull, scln, nc0, ec0, wklm, efl,
1451 ng1, eg1, ng2, eg2, scrpt, scrptn,
1453 if (m_debug || verbose) {
1454 const double m = (2. / e[1]) * ElectronMass / AtomicMassUnitElectronVolt;
1455 std::cout << " " << name << "\n"
1456 << " mass: " << m << " amu\n";
1457 if (nIon > 1) {
1458 std::cout << " ionisation threshold: " << eIon[0] << " eV\n";
1459 } else {
1460 std::cout << " ionisation threshold: " << e[2] << " eV\n";
1461 }
1462 if (e[3] > 0. && e[4] > 0.) {
1463 std::cout << " cross-sections at minimum ionising energy:\n"
1464 << " excitation: " << e[3] * 1.e18 << " Mbarn\n"
1465 << " ionisation: " << e[4] * 1.e18 << " Mbarn\n";
1466 }
1467 }
1468 int np0 = m_nTerms;
1469 // Make sure there is still sufficient space.
1470 if (np0 + nIn + nIon + nAtt + nNull >= Magboltz::nMaxLevels) {
1471 std::cerr << m_className << "::Mixer:\n"
1472 << " Max. number of levels (" << Magboltz::nMaxLevels
1473 << ") exceeded.\n";
1474 return false;
1475 }
1476 const double van = m_fraction[iGas] * prefactor;
1477 int np = np0;
1478 if (m_useCsOutput) {
1479 outfile << "# cross-sections for " << name << "\n";
1480 outfile << "# cross-section types:\n";
1481 outfile << "# elastic\n";
1482 }
1483 // Elastic scattering
1484 ++m_nTerms;
1485 m_scatModel[np] = kEl[1];
1486 const double r = 1. + 0.5 * e[1];
1487 m_rgas[iGas] = r;
1488 m_s2[iGas] = (r - 1.) / (r * r);
1489 m_energyLoss[np] = 0.;
1490 m_description[np] = GetDescription(1, scrpt);
1491 m_csType[np] = nCsTypes * iGas + ElectronCollisionTypeElastic;
1492 bool withIon = false;
1493 // Ionisation
1494 if (nIon > 1) {
1495 for (int j = 0; j < nIon; ++j) {
1496 if (m_eMax < eIon[j]) continue;
1497 withIon = true;
1498 ++m_nTerms;
1499 ++np;
1500 m_scatModel[np] = kEl[2];
1501 m_energyLoss[np] = eIon[j] / r;
1502 m_wOpalBeaty[np] = eoby[j];
1503 m_yFluorescence[np] = wklm[j];
1504 m_nAuger1[np] = nc0[j];
1505 m_eAuger1[np] = ec0[j];
1506 m_nFluorescence[np] = ng1[j];
1507 m_eFluorescence[np] = eg1[j];
1508 m_nAuger2[np] = ng2[j];
1509 m_eAuger2[np] = eg2[j];
1510 m_description[np] = GetDescription(2 + j, scrpt);
1511 m_csType[np] = nCsTypes * iGas + ElectronCollisionTypeIonisation;
1512 if (m_useCsOutput) outfile << "# " << m_description[np] << "\n";
1513 }
1514 m_parGreenSawada[iGas][0] = eoby[0];
1515 m_parGreenSawada[iGas][4] = 2 * eIon[0];
1516 m_ionPot[iGas] = eIon[0];
1517 } else {
1518 if (m_eMax >= e[2]) {
1519 withIon = true;
1520 ++m_nTerms;
1521 ++np;
1522 m_scatModel[np] = kEl[2];
1523 m_energyLoss[np] = e[2] / r;
1524 m_wOpalBeaty[np] = eoby[0];
1525 m_parGreenSawada[iGas][0] = eoby[0];
1526 m_parGreenSawada[iGas][4] = 2 * e[2];
1527 m_ionPot[iGas] = e[2];
1528 m_description[np] = GetDescription(2, scrpt);
1529 m_csType[np] = nCsTypes * iGas + ElectronCollisionTypeIonisation;
1530 if (m_useCsOutput) outfile << "# ionisation (gross)\n";
1531 }
1532 }
1533 // Attachment
1534 for (int j = 0; j < nAtt; ++j) {
1535 ++m_nTerms;
1536 ++np;
1537 m_scatModel[np] = 0;
1538 m_energyLoss[np] = 0.;
1539 m_description[np] = GetDescription(2 + nIon + j, scrpt);
1540 m_csType[np] = nCsTypes * iGas + ElectronCollisionTypeAttachment;
1541 if (m_useCsOutput) outfile << "# " << m_description[np] << "\n";
1542 }
1543 // Inelastic terms
1544 int nExc = 0, nSuperEl = 0;
1545 for (int j = 0; j < nIn; ++j) {
1546 ++np;
1547 m_scatModel[np] = kIn[j];
1548 m_energyLoss[np] = eIn[j] / r;
1549 m_description[np] = GetDescription(4 + nIon + nAtt + j, scrpt);
1550 if ((m_description[np][1] == 'E' && m_description[np][2] == 'X') ||
1551 (m_description[np][0] == 'E' && m_description[np][1] == 'X') ||
1552 (m_gas[iGas] == "N2" && eIn[j] > 6.)) {
1553 // Excitation
1554 m_csType[np] = nCsTypes * iGas + ElectronCollisionTypeExcitation;
1555 ++nExc;
1556 } else if (eIn[j] < 0.) {
1557 // Super-elastic collision
1558 m_csType[np] = nCsTypes * iGas + ElectronCollisionTypeSuperelastic;
1559 ++nSuperEl;
1560 } else {
1561 // Inelastic collision
1562 m_csType[np] = nCsTypes * iGas + ElectronCollisionTypeInelastic;
1563 }
1564 if (m_useCsOutput) outfile << "# " << m_description[np] << "\n";
1565 }
1566 m_nTerms += nIn;
1567 if (nNull > 0) {
1568 for (int j = 0; j < nNull; ++j) {
1569 ++m_nTerms;
1570 ++np;
1571 m_scatModel[np] = 0;
1572 m_energyLoss[np] = 0.;
1573 m_description[np] = GetDescription(j, scrptn);
1574 m_csType[np] = nCsTypes * iGas + ElectronCollisionTypeVirtual;
1575 if (m_useCsOutput) outfile << "# " << m_description[np] << "\n";
1576 }
1577 }
1578 // Loop over the energy table.
1579 for (unsigned int iE = 0; iE < Magboltz::nEnergySteps; ++iE) {
1580 np = np0;
1581 if (m_useCsOutput) {
1582 outfile << (iE + 0.5) * m_eStep << " " << q[iE][1] << " ";
1583 }
1584 // Elastic scattering
1585 m_cf[iE][np] = q[iE][1] * van;
1586 SetScatteringParameters(m_scatModel[np], pEqEl[iE][1], m_scatCut[iE][np],
1587 m_scatPar[iE][np]);
1588 // Ionisation
1589 if (withIon) {
1590 if (nIon > 1) {
1591 for (int j = 0; j < nIon; ++j) {
1592 if (m_eMax < eIon[j]) continue;
1593 ++np;
1594 m_cf[iE][np] = qIon[iE][j] * van;
1595 SetScatteringParameters(m_scatModel[np], pEqIon[iE][j],
1596 m_scatCut[iE][np], m_scatPar[iE][np]);
1597 if (m_useCsOutput) outfile << qIon[iE][j] << " ";
1598 }
1599 } else {
1600 ++np;
1601 m_cf[iE][np] = q[iE][2] * van;
1602 SetScatteringParameters(m_scatModel[np], pEqEl[iE][2],
1603 m_scatCut[iE][np], m_scatPar[iE][np]);
1604 if (m_useCsOutput) outfile << q[iE][2] << " ";
1605 }
1606 }
1607 // Attachment
1608 for (int j = 0; j < nAtt; ++j) {
1609 ++np;
1610 m_cf[iE][np] = qAtt[iE][j] * van;
1611 // m_cf[iE][np] = q[iE][3] * van;
1612 m_scatPar[iE][np] = 0.5;
1613 if (m_useCsOutput) outfile << qAtt[iE][j] << " ";
1614 }
1615 // Inelastic terms
1616 for (int j = 0; j < nIn; ++j) {
1617 ++np;
1618 if (m_useCsOutput) outfile << qIn[iE][j] << " ";
1619 m_cf[iE][np] = qIn[iE][j] * van;
1620 // Scale the excitation cross-sections (for error estimates).
1621 m_cf[iE][np] *= m_scaleExc[iGas];
1622 if (m_cf[iE][np] < 0.) {
1623 std::cerr << m_className << "::Mixer:\n"
1624 << " Negative inelastic cross-section at "
1625 << (iE + 0.5) * m_eStep << " eV. Set to zero.\n";
1626 m_cf[iE][np] = 0.;
1627 }
1628 SetScatteringParameters(m_scatModel[np], pEqIn[iE][j],
1629 m_scatCut[iE][np], m_scatPar[iE][np]);
1630 }
1631 if ((m_debug || verbose) && nIn > 0 && iE == iemax) {
1632 std::cout << " " << nIn << " inelastic terms (" << nExc
1633 << " excitations, " << nSuperEl << " superelastic, "
1634 << nIn - nExc - nSuperEl << " other)\n";
1635 }
1636 if (nNull > 0) {
1637 for (int j = 0; j < nNull; ++j) {
1638 ++np;
1639 m_cf[iE][np] = qNull[iE][j] * van * scln[j];
1640 if (m_useCsOutput) outfile << qNull[iE][j] << " ";
1641 }
1642 }
1643 if (m_useCsOutput) outfile << "\n";
1644 }
1645 if (m_eMax <= m_eHigh) continue;
1646 // Fill the high-energy part (logarithmic binning).
1647 // Calculate the growth factor.
1648 const double rLog = pow(m_eMax / m_eHigh, 1. / nEnergyStepsLog);
1649 m_lnStep = log(rLog);
1650 // Set the upper limit of the first bin.
1651 double emax = m_eHigh * rLog;
1652
1653 for (int iE = 0; iE < nEnergyStepsLog; ++iE) {
1654 Magboltz::inpt_.estep = emax / (Magboltz::nEnergySteps - 0.5);
1655 Magboltz::inpt_.efinal = emax + 0.5 * Magboltz::inpt_.estep;
1656 Magboltz::mix2_.eg[iemax] = emax;
1657 Magboltz::mix2_.eroot[iemax] = sqrt(emax);
1659 &ngs, q[0], qIn[0], &nIn, e, eIn, name, &virial, eoby, pEqEl[0],
1660 pEqIn[0], penFra[0], kEl, kIn, qIon[0], pEqIon[0], eIon, &nIon,
1661 qAtt[0], &nAtt, qNull[0], &nNull, scln, nc0, ec0, wklm, efl,
1662 ng1, eg1, ng2, eg2, scrpt, scrptn,
1664 np = np0;
1665 if (m_useCsOutput) outfile << emax << " " << q[iemax][1] << " ";
1666 // Elastic scattering
1667 m_cfLog[iE][np] = q[iemax][1] * van;
1668 SetScatteringParameters(m_scatModel[np], pEqEl[iemax][1],
1669 m_scatCutLog[iE][np], m_scatParLog[iE][np]);
1670 // Ionisation
1671 if (withIon) {
1672 if (nIon > 1) {
1673 for (int j = 0; j < nIon; ++j) {
1674 if (m_eMax < eIon[j]) continue;
1675 ++np;
1676 m_cfLog[iE][np] = qIon[iemax][j] * van;
1677 SetScatteringParameters(m_scatModel[np], pEqIon[iemax][j],
1678 m_scatCutLog[iE][np], m_scatParLog[iE][np]);
1679 if (m_useCsOutput) outfile << qIon[iemax][j] << " ";
1680 }
1681 } else {
1682 ++np;
1683 // Gross cross-section
1684 m_cfLog[iE][np] = q[iemax][2] * van;
1685 // Counting cross-section
1686 // m_cfLog[iE][np] = q[iemax][4] * van;
1687 SetScatteringParameters(m_scatModel[np], pEqEl[iemax][2],
1688 m_scatCutLog[iE][np], m_scatParLog[iE][np]);
1689 if (m_useCsOutput) outfile << q[iemax][2] << " ";
1690 }
1691 }
1692 // Attachment
1693 for (int j = 0; j < nAtt; ++j) {
1694 ++np;
1695 m_cfLog[iE][np] = qAtt[iemax][j] * van;
1696 // m_cfLog[iE][np] = q[iemax][3] * van;
1697 if (m_useCsOutput) outfile << qAtt[iemax][j] << " ";
1698 }
1699 // Inelastic terms
1700 for (int j = 0; j < nIn; ++j) {
1701 ++np;
1702 if (m_useCsOutput) outfile << qIn[iemax][j] << " ";
1703 m_cfLog[iE][np] = qIn[iemax][j] * van;
1704 // Scale the excitation cross-sections (for error estimates).
1705 m_cfLog[iE][np] *= m_scaleExc[iGas];
1706 if (m_cfLog[iE][np] < 0.) {
1707 std::cerr << m_className << "::Mixer:\n"
1708 << " Negative inelastic cross-section at " << emax
1709 << " eV. Set to zero.\n";
1710 m_cfLog[iE][np] = 0.;
1711 }
1712 SetScatteringParameters(m_scatModel[np], pEqIn[iemax][j],
1713 m_scatCutLog[iE][np], m_scatParLog[iE][np]);
1714 }
1715 if (nNull > 0) {
1716 for (int j = 0; j < nNull; ++j) {
1717 ++np;
1718 m_cfLog[iE][np] = qNull[iemax][j] * van * scln[j];
1719 if (m_useCsOutput) outfile << qNull[iemax][j] << " ";
1720 }
1721 }
1722 if (m_useCsOutput) outfile << "\n";
1723 // Increase the energy.
1724 emax *= rLog;
1725 }
1726 }
1727 if (m_useCsOutput) outfile.close();
1728
1729 // Find the smallest ionisation threshold.
1730 auto it = std::min_element(std::begin(m_ionPot),
1731 std::begin(m_ionPot) + m_nComponents);
1732 m_minIonPot = *it;
1733 std::string minIonPotGas = m_gas[std::distance(std::begin(m_ionPot), it)];
1734
1735 if (m_debug || verbose) {
1736 std::cout << m_className << "::Mixer:\n"
1737 << " Lowest ionisation threshold in the mixture: "
1738 << m_minIonPot << " eV (" << minIonPotGas << ")\n";
1739 }
1740
1741 for (unsigned int iE = 0; iE < Magboltz::nEnergySteps; ++iE) {
1742 // Calculate the total collision frequency.
1743 for (unsigned int k = 0; k < m_nTerms; ++k) {
1744 if (m_cf[iE][k] < 0.) {
1745 std::cerr << m_className << "::Mixer:\n"
1746 << " Negative collision rate at " << (iE + 0.5) * m_eStep
1747 << " eV, cross-section " << k << ". Set to zero.\n";
1748 std::cout << m_description[k] << "\n";
1749 m_cf[iE][k] = 0.;
1750 }
1751 m_cfTot[iE] += m_cf[iE][k];
1752 }
1753 // Normalise the collision probabilities.
1754 if (m_cfTot[iE] > 0.) {
1755 for (unsigned int k = 0; k < m_nTerms; ++k) m_cf[iE][k] /= m_cfTot[iE];
1756 }
1757 for (unsigned int k = 1; k < m_nTerms; ++k) {
1758 m_cf[iE][k] += m_cf[iE][k - 1];
1759 }
1760 const double ekin = m_eStep * (iE + 0.5);
1761 m_cfTot[iE] *= sqrt(ekin);
1762 // Use relativistic expression at high energies.
1763 if (ekin > 1.e3) {
1764 const double re = ekin / ElectronMass;
1765 m_cfTot[iE] *= sqrt(1. + 0.5 * re) / (1. + re);
1766 }
1767 }
1768
1769 if (m_eMax > m_eHigh) {
1770 const double rLog = pow(m_eMax / m_eHigh, 1. / nEnergyStepsLog);
1771 for (int iE = 0; iE < nEnergyStepsLog; ++iE) {
1772 // Calculate the total collision frequency.
1773 for (unsigned int k = 0; k < m_nTerms; ++k) {
1774 if (m_cfLog[iE][k] < 0.) m_cfLog[iE][k] = 0.;
1775 m_cfTotLog[iE] += m_cfLog[iE][k];
1776 }
1777 // Normalise the collision probabilities.
1778 if (m_cfTotLog[iE] > 0.) {
1779 for (unsigned int k = 0; k < m_nTerms; ++k) {
1780 m_cfLog[iE][k] /= m_cfTotLog[iE];
1781 }
1782 }
1783 for (unsigned int k = 1; k < m_nTerms; ++k) {
1784 m_cfLog[iE][k] += m_cfLog[iE][k - 1];
1785 }
1786 const double ekin = m_eHigh * pow(rLog, iE + 1);
1787 const double re = ekin / ElectronMass;
1788 m_cfTotLog[iE] *= sqrt(ekin) * sqrt(1. + re) / (1. + re);
1789 // Store the logarithm (for log-log interpolation)
1790 m_cfTotLog[iE] = log(m_cfTotLog[iE]);
1791 }
1792 }
1793
1794 // Determine the null collision frequency.
1795 m_cfNull = 0.;
1796 for (unsigned int j = 0; j < Magboltz::nEnergySteps; ++j) {
1797 if (m_cfTot[j] > m_cfNull) m_cfNull = m_cfTot[j];
1798 }
1799 if (m_eMax > m_eHigh) {
1800 for (int j = 0; j < nEnergyStepsLog; ++j) {
1801 const double r = exp(m_cfTotLog[j]);
1802 if (r > m_cfNull) m_cfNull = r;
1803 }
1804 }
1805
1806 // Reset the collision counters.
1807 m_nCollisionsDetailed.assign(m_nTerms, 0);
1808 m_nCollisions.fill(0);
1809
1810 if (m_debug || verbose) {
1811 std::cout << m_className << "::Mixer:\n"
1812 << " Energy [eV] Collision Rate [ns-1]\n";
1813 const double emax = std::min(m_eHigh, m_eMax);
1814 for (int i = 0; i < 8; ++i) {
1815 const double en = (2 * i + 1) * emax / 16;
1816 const double cf = m_cfTot[(i + 1) * Magboltz::nEnergySteps / 16];
1817 std::printf(" %10.2f %18.2f\n", en, cf);
1818 }
1819 }
1820
1821 // Set up the de-excitation channels.
1822 if (m_useDeexcitation) {
1823 ComputeDeexcitationTable(verbose);
1824 for (const auto& dxc : m_deexcitations) {
1825 if (dxc.p.size() == dxc.final.size() && dxc.p.size() == dxc.type.size())
1826 continue;
1827 std::cerr << m_className << "::Mixer:\n"
1828 << " Mismatch in deexcitation channel count. Program bug!\n"
1829 << " Deexcitation handling is switched off.\n";
1830 m_useDeexcitation = false;
1831 break;
1832 }
1833 }
1834
1835 // Fill the photon collision rates table.
1836 if (!ComputePhotonCollisionTable(verbose)) {
1837 // std::cerr << m_className << "::Mixer:\n"
1838 // << " Photon collision rates could not be calculated.\n";
1839 if (m_useDeexcitation) {
1840 std::cerr << " Deexcitation handling is switched off.\n";
1841 m_useDeexcitation = false;
1842 }
1843 }
1844
1845 // Reset the Penning transfer parameters.
1846 if (m_debug) {
1847 std::cout << m_className << "::Mixer: Resetting transfer probabilities.\n"
1848 << " Global: " << m_rPenningGlobal << "\n";
1849 for (unsigned int i = 0; i < m_nMaxGases; ++i) {
1850 std::cout << " Component " << i << ": " << m_rPenningGas[i] << "\n";
1851 }
1852 }
1853 if (m_rPenningGlobal > Small) {
1854 m_rPenning.fill(m_rPenningGlobal);
1855 m_lambdaPenning.fill(m_lambdaPenningGlobal);
1856 }
1857 for (unsigned int i = 0; i < m_nTerms; ++i) {
1858 int iGas = int(m_csType[i] / nCsTypes);
1859 if (m_rPenningGas[iGas] > Small) {
1860 m_rPenning[i] = m_rPenningGas[iGas];
1861 m_lambdaPenning[i] = m_lambdaPenningGas[iGas];
1862 }
1863 }
1864
1865 // Set the Green-Sawada splitting function parameters.
1866 SetupGreenSawada();
1867
1868 return true;
1869}
1870
1872
1873 if (!Update()) return;
1874
1875 std::array<float, Magboltz::nEnergySteps> en;
1876 for (unsigned int k = 0; k < Magboltz::nEnergySteps; ++k) {
1877 en[k] = (k + 0.5) * m_eStep;
1878 }
1879 std::array<std::array<float, Magboltz::nEnergySteps>, 5> cs;
1880 for (unsigned int i = 0; i < m_nComponents; ++i) {
1881 for (size_t j = 0; j < 5; ++j) cs[j].fill(0.);
1882 const double density = GetNumberDensity() * m_fraction[i];
1883 const double scale = sqrt(0.5 * ElectronMass) / (density * SpeedOfLight);
1884 for (unsigned int j = 0; j < m_nTerms; ++j) {
1885 if (int(m_csType[j] / nCsTypes) != int(i)) continue;
1886 int cstype = m_csType[j] % nCsTypes;
1887 if (cstype >= ElectronCollisionTypeVirtual) continue;
1888 // Group inelastic and superelastic collisions.
1889 if (cstype == 5) cstype = 3;
1890 for (unsigned int k = 0; k < Magboltz::nEnergySteps; ++k) {
1891 double cf = m_cf[k][j];
1892 if (j > 0) cf -= m_cf[k][j - 1];
1893 cs[cstype][k] += cf * 1.e18 * scale;
1894 }
1895 }
1896 const std::string name = ViewBase::FindUnusedCanvasName("cCs");
1897 TCanvas* canvas = new TCanvas(name.c_str(), m_gas[i].c_str(), 800, 600);
1898 canvas->cd();
1899 canvas->SetLogx();
1900 canvas->SetLogy();
1901 canvas->SetGridx();
1902 canvas->SetGridy();
1903 canvas->DrawFrame(en[0], 0.01, en.back(), 100.,
1904 ";energy [eV];#sigma [Mbarn]");
1905 TGraph gr(Magboltz::nEnergySteps);
1906 gr.SetLineWidth(3);
1907 const std::array<short, 5> cols = {kBlack, kCyan - 2, kRed + 2,
1908 kGreen + 3, kMagenta + 3};
1909 for (size_t j = 0; j < 5; ++j) {
1910 if (*std::max_element(cs[j].begin(), cs[j].end()) < 1.e-10) continue;
1911 gr.SetLineColor(cols[j]);
1912 gr.DrawGraph(Magboltz::nEnergySteps, en.data(), cs[j].data(), "lsame");
1913 }
1914 canvas->Update();
1915 }
1916
1917}
1918
1919void MediumMagboltz::SetupGreenSawada() {
1920 for (unsigned int i = 0; i < m_nComponents; ++i) {
1921 const double ta = 1000.;
1922 const double tb = m_parGreenSawada[i][4];
1923 m_hasGreenSawada[i] = true;
1924 if (m_gas[i] == "He" || m_gas[i] == "He-3") {
1925 m_parGreenSawada[i] = {15.5, 24.5, -2.25, ta, tb};
1926 } else if (m_gas[i] == "Ne") {
1927 m_parGreenSawada[i] = {24.3, 21.6, -6.49, ta, tb};
1928 } else if (m_gas[i] == "Ar") {
1929 m_parGreenSawada[i] = {6.92, 7.85, 6.87, ta, tb};
1930 } else if (m_gas[i] == "Kr") {
1931 m_parGreenSawada[i] = {7.95, 13.5, 3.90, ta, tb};
1932 } else if (m_gas[i] == "Xe") {
1933 m_parGreenSawada[i] = {7.93, 11.5, 3.81, ta, tb};
1934 } else if (m_gas[i] == "H2" || m_gas[i] == "D2") {
1935 m_parGreenSawada[i] = {7.07, 7.7, 1.87, ta, tb};
1936 } else if (m_gas[i] == "N2") {
1937 m_parGreenSawada[i] = {13.8, 15.6, 4.71, ta, tb};
1938 } else if (m_gas[i] == "O2") {
1939 m_parGreenSawada[i] = {18.5, 12.1, 1.86, ta, tb};
1940 } else if (m_gas[i] == "CH4") {
1941 m_parGreenSawada[i] = {7.06, 12.5, 3.45, ta, tb};
1942 } else if (m_gas[i] == "H2O") {
1943 m_parGreenSawada[i] = {12.8, 12.6, 1.28, ta, tb};
1944 } else if (m_gas[i] == "CO") {
1945 m_parGreenSawada[i] = {13.3, 14.0, 2.03, ta, tb};
1946 } else if (m_gas[i] == "C2H2") {
1947 m_parGreenSawada[i] = {9.28, 5.8, 1.37, ta, tb};
1948 } else if (m_gas[i] == "NO") {
1949 m_parGreenSawada[i] = {10.4, 9.5, -4.30, ta, tb};
1950 } else if (m_gas[i] == "CO2") {
1951 m_parGreenSawada[i] = {12.3, 13.8, -2.46, ta, tb};
1952 } else {
1953 m_parGreenSawada[i][3] = 0.;
1954 m_hasGreenSawada[i] = false;
1955 if (m_useGreenSawada) {
1956 std::cout << m_className << "::SetupGreenSawada:\n"
1957 << " Fit parameters for " << m_gas[i]
1958 << " not available.\n"
1959 << " Opal-Beaty formula is used instead.\n";
1960 }
1961 }
1962 }
1963}
1964
1965void MediumMagboltz::ComputeDeexcitationTable(const bool verbose) {
1966 m_iDeexcitation.fill(-1);
1967 m_deexcitations.clear();
1968
1969 // Indices of "de-excitable" gases (only Ar for the time being).
1970 int iAr = -1;
1971
1972 std::map<std::string, int> lvl;
1973 for (unsigned int i = 0; i < m_nTerms; ++i) {
1974 // Skip non-excitation levels.
1975 if (m_csType[i] % nCsTypes != ElectronCollisionTypeExcitation) continue;
1976 // Extract the index of the gas.
1977 const int ngas = int(m_csType[i] / nCsTypes);
1978 std::string level;
1979 if (m_gas[ngas] == "Ar") {
1980 // Argon
1981 if (iAr < 0) iAr = ngas;
1982 // Get the level description (as specified in Magboltz).
1983 level = " ";
1984 for (int j = 0; j < 7; ++j) level[j] = m_description[i][5 + j];
1985 rtrim(level);
1986 level = "Ar_" + level;
1987 } else {
1988 continue;
1989 }
1990
1991 lvl[level] = m_deexcitations.size();
1992 m_iDeexcitation[i] = lvl[level];
1993
1994 Deexcitation dxc;
1995 dxc.gas = ngas;
1996 dxc.level = i;
1997 dxc.label = level;
1998 // Excitation energy
1999 dxc.energy = m_energyLoss[i] * m_rgas[ngas];
2000 // Oscillator strength
2001 dxc.osc = dxc.cf = 0.;
2002 dxc.sDoppler = dxc.gPressure = dxc.width = 0.;
2003
2004 if (level == "Ar_HIGH") {
2005 // This (artificial) level represents the sum of higher J = 1 states.
2006 // The deeexcitation cascade is simulated by allocating it
2007 // with equal probability to one of the five nearest levels below.
2008 dxc.type.assign(5, DxcTypeCollNonIon);
2009 dxc.p = {100., 100., 100., 100., 100.};
2010 dxc.final = {lvl["Ar_6D5"], lvl["Ar_5S1!"], lvl["Ar_4S2"], lvl["Ar_5S4"],
2011 lvl["Ar_6D2"]};
2012 }
2013 m_deexcitations.push_back(std::move(dxc));
2014 }
2015
2016 if (m_deexcitations.empty()) return;
2017
2018 std::string path = "";
2019 auto installdir = std::getenv("GARFIELD_INSTALL");
2020 if (!installdir) {
2021 std::cerr << m_className << "::ComputeDeexcitationTable:\n"
2022 << " Environment variable GARFIELD_INSTALL not set.\n";
2023 } else {
2024 path = std::string(installdir) + "/share/Garfield/Data/Deexcitation/";
2025 }
2026
2027 std::string filename = path + "OscillatorStrengths_Ar.txt";
2028 std::ifstream infile(filename);
2029 if (!infile) {
2030 std::cerr << m_className << "::ComputeDeexcitationTable:\n"
2031 << " Could not open " << filename << ".\n";
2032 return;
2033 }
2034 for (std::string line; std::getline(infile, line);) {
2035 ltrim(line);
2036 if (line.empty() || IsComment(line)) continue;
2037 auto words = tokenize(line);
2038 if (words.size() < 2) continue;
2039 std::string level = "Ar_" + words[0];
2040 if (lvl.count(level) == 0) {
2041 std::cout << " Unexpected level " << level << "\n";
2042 continue;
2043 }
2044 m_deexcitations[lvl[level]].osc = std::stod(words[1]);
2045 }
2046 infile.close();
2047
2048 filename = path + "TransitionRates_Ar.txt";
2049 infile.open(filename);
2050 if (!infile.is_open()) {
2051 std::cerr << m_className << "::ComputeDeexcitationTable:\n"
2052 << " Could not open " << filename << ".\n";
2053 return;
2054 }
2055 for (std::string line; std::getline(infile, line);) {
2056 ltrim(line);
2057 if (line.empty() || IsComment(line)) continue;
2058 auto words = tokenize(line);
2059 if (words.size() < 3) continue;
2060 std::string level0 = "Ar_" + words[0];
2061 if (lvl.count(level0) == 0) {
2062 std::cout << " Unexpected level " << level0 << "\n";
2063 continue;
2064 }
2065 auto& dxc = m_deexcitations[lvl[level0]];
2066 if (words[1] == "Ground") {
2067 dxc.final.push_back(-1);
2068 } else {
2069 std::string level1 = "Ar_" + words[1];
2070 if (lvl.count(level1) == 0) {
2071 std::cout << " Unexpected level " << level1 << "\n";
2072 continue;
2073 }
2074 dxc.final.push_back(lvl[level1]);
2075 }
2076 dxc.p.push_back(std::stod(words[2]));
2077 dxc.type.push_back(DxcTypeRad);
2078 }
2079 infile.close();
2080
2081 if (m_debug || verbose) {
2082 std::cout << m_className << "::ComputeDeexcitationTable:\n";
2083 std::cout << " Found " << m_deexcitations.size() << " levels "
2084 << "with available radiative de-excitation data.\n";
2085 }
2086
2087 // Collisional de-excitation channels
2088 if (iAr >= 0) {
2089 // Add the Ar dimer ground state.
2090 Deexcitation dimer;
2091 dimer.label = "Ar_Dimer";
2092 dimer.level = -1;
2093 dimer.gas = iAr;
2094 dimer.energy = 14.71;
2095 dimer.osc = dimer.cf = 0.;
2096 dimer.sDoppler = dimer.gPressure = dimer.width = 0.;
2097 lvl["Ar_Dimer"] = m_deexcitations.size();
2098 m_deexcitations.push_back(std::move(dimer));
2099 // Add an Ar excimer level.
2100 Deexcitation excimer;
2101 excimer.label = "Ar_Excimer";
2102 excimer.level = -1;
2103 excimer.gas = iAr;
2104 excimer.energy = 14.71;
2105 excimer.osc = excimer.cf = 0.;
2106 excimer.sDoppler = excimer.gPressure = excimer.width = 0.;
2107 lvl["Ar_Excimer"] = m_deexcitations.size();
2108 m_deexcitations.push_back(std::move(excimer));
2109 const double nAr = GetNumberDensity() * m_fraction[iAr];
2110
2111 filename = path + "RateConstants_Ar_Ar.txt";
2112 infile.open(filename);
2113 if (!infile.is_open()) {
2114 std::cerr << m_className << "::ComputeDeexcitationTable:\n"
2115 << " Could not open " << filename << ".\n";
2116 return;
2117 }
2118 for (std::string line; std::getline(infile, line);) {
2119 ltrim(line);
2120 if (line.empty() || IsComment(line)) continue;
2121 auto words = tokenize(line);
2122 if (words.size() < 3) continue;
2123 std::string level0 = "Ar_" + words[0];
2124 if (lvl.count(level0) == 0) {
2125 std::cout << " Unexpected level " << level0 << "\n";
2126 continue;
2127 }
2128 auto& dxc = m_deexcitations[lvl[level0]];
2129 std::string level1 = "Ar_" + words[1];
2130 if (lvl.count(level1) == 0) {
2131 std::cout << " Unexpected level " << level1 << "\n";
2132 continue;
2133 }
2134 const double k = std::stod(words[2]);
2135 // Three-body collisions lead to excimer formation.
2136 // Two-body collisions give rise to collisional mixing.
2137 if (level1 == "Ar_Excimer") {
2138 dxc.p.push_back(k * nAr * nAr);
2139 } else {
2140 dxc.p.push_back(k * nAr);
2141 }
2142 dxc.final.push_back(lvl[level1]);
2143 dxc.type.push_back(DxcTypeCollNonIon);
2144 }
2145 infile.close();
2146
2147 // Transfer from 3d and 5s levels to 4p levels.
2148 std::vector<std::string> levels3d5s = {
2149 "3D6", "3D5", "3D3", "3D4!", "3D4", "3D1!!", "3D1!", "3D2",
2150 "3S1!!!!", "3S1!!", "3S1!!!", "3S1!", "2S5", "2S4", "2S3", "2S2"
2151 };
2152 std::vector<int> levels4p;
2153 for (unsigned int j = 1; j <= 10; ++j) {
2154 std::string level = "Ar_2P" + std::to_string(j);
2155 if (lvl.count(level) == 0) {
2156 std::cout << " Unexpected level " << level << ".\n";
2157 } else {
2158 levels4p.push_back(lvl[level]);
2159 }
2160 }
2161 for (const std::string& level0 : levels3d5s) {
2162 if (lvl.count("Ar_" + level0) == 0) {
2163 std::cout << " Unexpected level " << level0 << ".\n";
2164 continue;
2165 }
2166 auto& dxc = m_deexcitations[lvl["Ar_" + level0]];
2167 // Parameter to be tuned (order of magnitude guess).
2168 constexpr double k4p = 1.e-20;
2169 const double p4p = 0.1 * k4p * nAr;
2170 for (const auto level1 : levels4p) {
2171 dxc.p.push_back(p4p);
2172 dxc.final.push_back(level1);
2173 dxc.type.push_back(DxcTypeCollNonIon);
2174 }
2175 }
2176 std::vector<std::string> levels = {
2177 "4D5", "3S4", "4D2", "4S1!", "3S2", "5D5", "4S4", "5D2",
2178 "6D5", "5S1!", "4S2", "5S4", "6D2"};
2179 for (const std::string& level0 : levels) {
2180 if (lvl.count("Ar_" + level0) == 0) {
2181 std::cout << " Unexpected level " << level0 << ".\n";
2182 continue;
2183 }
2184 auto& dxc = m_deexcitations[lvl["Ar_" + level0]];
2185 // Transfer to 4p levels.
2186 constexpr double k4p = 1.e-20;
2187 const double p4p = 0.1 * k4p * nAr;
2188 for (const auto level1 : levels4p) {
2189 dxc.p.push_back(p4p);
2190 dxc.final.push_back(level1);
2191 dxc.type.push_back(DxcTypeCollNonIon);
2192 }
2193 // Hornbeck-Molnar ionisation
2194 // P. Becker and F. Lampe, J. Chem. Phys. 42 (1965), 3857-3863
2195 // A. Bogaerts and R. Gijbels, Phys. Rev. A 52 (1995), 3743-3751
2196 // This value seems high, to be checked!
2197 constexpr double kHM = 2.e-18;
2198 dxc.p.push_back(kHM * nAr);
2199 dxc.final.push_back(lvl["Ar_Dimer"]);
2200 dxc.type.push_back(DxcTypeCollIon);
2201 }
2202 }
2203
2204 // Collisional deexcitation by quenching gases.
2205 for (unsigned int i = 0; i < m_nComponents; ++i) {
2206 std::string gas = m_gas[i];
2207 // Collision radius
2208 double rQ = 0.;
2209 if (m_gas[i] == "CO2") {
2210 rQ = 165.e-10;
2211 } else if (m_gas[i] == "CH4") {
2212 rQ = 190.e-10;
2213 } else if (m_gas[i] == "C2H6") {
2214 rQ = 195.e-10;
2215 } else if (m_gas[i] == "iC4H10") {
2216 rQ = 250.e-10;
2217 gas = "nC4H10";
2218 } else if (m_gas[i] == "C2H2") {
2219 rQ = 165.e-10;
2220 } else if (m_gas[i] == "CF4") {
2221 rQ = 235.e-10;
2222 } else {
2223 continue;
2224 }
2225
2226 // Partial density.
2227 const double nQ = GetNumberDensity() * m_fraction[i];
2228
2229 filename = path + "RateConstants_Ar_" + m_gas[i] + ".txt";
2230 infile.open(filename);
2231 if (!infile.is_open()) {
2232 std::cerr << m_className << "::ComputeDeexcitationTable:\n"
2233 << " Could not open " << filename << ".\n";
2234 return;
2235 }
2236 for (std::string line; std::getline(infile, line);) {
2237 ltrim(line);
2238 if (line.empty() || IsComment(line)) continue;
2239 auto words = tokenize(line);
2240 if (words.size() < 2) continue;
2241 std::string level0 = "Ar_" + words[0];
2242 if (lvl.count(level0) == 0) {
2243 std::cout << " Unexpected level " << level0 << "\n";
2244 continue;
2245 }
2246 auto& dxc = m_deexcitations[lvl[level0]];
2247 if (dxc.energy < m_ionPot[i]) {
2248 AddPenningDeexcitation(dxc, std::stod(words[1]) * nQ, 0.);
2249 } else {
2250 const double eta = OpticalData::PhotoionisationYield(gas, dxc.energy);
2251 double pIon = pow(eta, 0.4);
2252 if (words.size() > 2 && !IsComment(words[2])) {
2253 pIon = std::stod(words[2]);
2254 }
2255 AddPenningDeexcitation(dxc, std::stod(words[1]) * nQ, pIon);
2256 }
2257 }
2258
2259 for (auto& dxc : m_deexcitations) {
2260 std::string level = dxc.label;
2261 if (level.find("Ar_1S") == 0 || level.find("Ar_2P") == 0) {
2262 continue;
2263 }
2264 const double eta = OpticalData::PhotoionisationYield(gas, dxc.energy);
2265 const double pIon = pow(eta, 0.4);
2266 if (dxc.osc > 0.) {
2267 // Higher resonance levels
2268 // Calculate rate constant from Watanabe-Katsuura formula.
2269 double pacs = OpticalData::PhotoabsorptionCrossSection(gas, dxc.energy);
2270 const double kQ = RateConstantWK(dxc.energy, dxc.osc, pacs, iAr, i);
2271 if (dxc.energy < m_ionPot[i]) {
2272 AddPenningDeexcitation(dxc, kQ * nQ, 0.);
2273 } else {
2274 AddPenningDeexcitation(dxc, kQ * nQ, pIon);
2275 }
2276 } else if (level == "Ar_3D6" || level == "Ar_3D3" || level == "Ar_3D4!" ||
2277 level == "Ar_3D4" || level == "Ar_3D1!!" ||
2278 level == "Ar_3D1!" || level == "Ar_3S1!!!!" ||
2279 level == "Ar_3S1!!" || level == "Ar_3S1!!!") {
2280 // Non-resonant 3d levels
2281 constexpr double rAr3d = 436.e-10;
2282 const double kQ = RateConstantHardSphere(rAr3d, rQ, iAr, i);
2283 if (dxc.energy < m_ionPot[i]) {
2284 AddPenningDeexcitation(dxc, kQ * nQ, 0.);
2285 } else {
2286 AddPenningDeexcitation(dxc, kQ * nQ, pIon);
2287 }
2288 } else if (level == "Ar_2S5" || level == "Ar_2S3") {
2289 // Non-resonant 5s levels
2290 constexpr double rAr5s = 635.e-10;
2291 const double kQ = RateConstantHardSphere(rAr5s, rQ, iAr, i);
2292 if (dxc.energy < m_ionPot[i]) {
2293 AddPenningDeexcitation(dxc, kQ * nQ, 0.);
2294 } else {
2295 AddPenningDeexcitation(dxc, kQ * nQ, pIon);
2296 }
2297 }
2298 }
2299 }
2300
2301 if (m_debug || verbose) {
2302 std::cout << m_className << "::ComputeDeexcitationTable:\n"
2303 << " Level Energy [eV] Lifetimes [ns]\n"
2304 << " Total Radiative "
2305 << " Collisional\n"
2306 << " "
2307 << " Ionisation Transfer Loss\n";
2308 }
2309
2310 for (auto& dxc : m_deexcitations) {
2311 // Calculate the total decay rate of each level.
2312 dxc.rate = 0.;
2313 double fRad = 0.;
2314 double fCollIon = 0., fCollTransfer = 0., fCollLoss = 0.;
2315 const unsigned int nChannels = dxc.type.size();
2316 for (unsigned int j = 0; j < nChannels; ++j) {
2317 dxc.rate += dxc.p[j];
2318 if (dxc.type[j] == DxcTypeRad) {
2319 fRad += dxc.p[j];
2320 } else if (dxc.type[j] == DxcTypeCollIon) {
2321 fCollIon += dxc.p[j];
2322 } else if (dxc.type[j] == DxcTypeCollNonIon) {
2323 if (dxc.final[j] < 0) {
2324 fCollLoss += dxc.p[j];
2325 } else {
2326 fCollTransfer += dxc.p[j];
2327 }
2328 } else {
2329 std::cerr << m_className << "::ComputeDeexcitationTable:\n "
2330 << "Unknown type of deexcitation channel (level " << dxc.label
2331 << "). Program bug!\n";
2332 }
2333 }
2334 if (dxc.rate <= 0.) continue;
2335 // Print the radiative and collisional decay rates.
2336 if (m_debug || verbose) {
2337 std::cout << std::setw(12) << dxc.label << " " << std::fixed
2338 << std::setprecision(3) << std::setw(7) << dxc.energy << " "
2339 << std::setw(10) << 1. / dxc.rate << " ";
2340 if (fRad > 0.) {
2341 std::cout << std::fixed << std::setprecision(3) << std::setw(10)
2342 << 1. / fRad << " ";
2343 } else {
2344 std::cout << "---------- ";
2345 }
2346 if (fCollIon > 0.) {
2347 std::cout << std::fixed << std::setprecision(3) << std::setw(10)
2348 << 1. / fCollIon << " ";
2349 } else {
2350 std::cout << "---------- ";
2351 }
2352 if (fCollTransfer > 0.) {
2353 std::cout << std::fixed << std::setprecision(3) << std::setw(10)
2354 << 1. / fCollTransfer << " ";
2355 } else {
2356 std::cout << "---------- ";
2357 }
2358 if (fCollLoss > 0.) {
2359 std::cout << std::fixed << std::setprecision(3) << std::setw(10)
2360 << 1. / fCollLoss << "\n";
2361 } else {
2362 std::cout << "---------- \n";
2363 }
2364 }
2365 // Normalise the branching ratios.
2366 for (unsigned int j = 0; j < nChannels; ++j) {
2367 dxc.p[j] /= dxc.rate;
2368 if (j > 0) dxc.p[j] += dxc.p[j - 1];
2369 }
2370 }
2371}
2372
2373double MediumMagboltz::RateConstantWK(const double energy, const double osc,
2374 const double pacs, const int igas1,
2375 const int igas2) const {
2376 // Calculate rate constant from Watanabe-Katsuura formula.
2377 const double m1 = ElectronMassGramme / (m_rgas[igas1] - 1.);
2378 const double m2 = ElectronMassGramme / (m_rgas[igas2] - 1.);
2379 // Compute the reduced mass.
2380 double mR = (m1 * m2 / (m1 + m2)) / AtomicMassUnit;
2381 const double uA = (RydbergEnergy / energy) * osc;
2382 const double uQ = (2 * RydbergEnergy / energy) * pacs /
2383 (4 * Pi2 * FineStructureConstant * BohrRadius * BohrRadius);
2384 return 2.591e-19 * pow(uA * uQ, 0.4) * pow(m_temperature / mR, 0.3);
2385}
2386
2387double MediumMagboltz::RateConstantHardSphere(const double r1, const double r2,
2388 const int igas1,
2389 const int igas2) const {
2390 // Hard sphere cross-section
2391 const double r = r1 + r2;
2392 const double sigma = r * r * Pi;
2393 // Reduced mass
2394 const double m1 = ElectronMass / (m_rgas[igas1] - 1.);
2395 const double m2 = ElectronMass / (m_rgas[igas2] - 1.);
2396 const double mR = m1 * m2 / (m1 + m2);
2397 // Relative velocity
2398 const double vel =
2399 SpeedOfLight * sqrt(8. * BoltzmannConstant * m_temperature / (Pi * mR));
2400 return sigma * vel;
2401}
2402
2403void MediumMagboltz::ComputeDeexcitation(int iLevel, int& fLevel) {
2404 if (!m_useDeexcitation) {
2405 std::cerr << m_className << "::ComputeDeexcitation: Not enabled.\n";
2406 return;
2407 }
2408
2409 // Make sure that the tables are updated.
2410 if (!Update()) return;
2411
2412 if (iLevel < 0 || iLevel >= (int)m_nTerms) {
2413 std::cerr << m_className << "::ComputeDeexcitation: Index out of range.\n";
2414 return;
2415 }
2416
2417 iLevel = m_iDeexcitation[iLevel];
2418 if (iLevel < 0 || iLevel >= (int)m_deexcitations.size()) {
2419 std::cerr << m_className << "::ComputeDeexcitation:\n"
2420 << " Level is not deexcitable.\n";
2421 return;
2422 }
2423
2424 ComputeDeexcitationInternal(iLevel, fLevel);
2425 if (fLevel >= 0 && fLevel < (int)m_deexcitations.size()) {
2426 fLevel = m_deexcitations[fLevel].level;
2427 }
2428}
2429
2430void MediumMagboltz::ComputeDeexcitationInternal(int iLevel, int& fLevel) {
2431 m_dxcProducts.clear();
2432
2433 double t = 0.;
2434 fLevel = iLevel;
2435 while (iLevel >= 0 && iLevel < (int)m_deexcitations.size()) {
2436 const auto& dxc = m_deexcitations[iLevel];
2437 const int nChannels = dxc.p.size();
2438 if (dxc.rate <= 0. || nChannels <= 0) {
2439 // This level is a dead end.
2440 fLevel = iLevel;
2441 return;
2442 }
2443 // Determine the de-excitation time.
2444 t += -log(RndmUniformPos()) / dxc.rate;
2445 // Select the transition.
2446 fLevel = -1;
2447 int type = DxcTypeRad;
2448 const double r = RndmUniform();
2449 for (int j = 0; j < nChannels; ++j) {
2450 if (r <= dxc.p[j]) {
2451 fLevel = dxc.final[j];
2452 type = dxc.type[j];
2453 break;
2454 }
2455 }
2456 if (type == DxcTypeRad) {
2457 // Radiative decay
2458 dxcProd photon;
2459 photon.s = 0.;
2460 photon.t = t;
2461 photon.type = DxcProdTypePhoton;
2462 photon.energy = dxc.energy;
2463 if (fLevel >= 0) {
2464 // Decay to a lower lying excited state.
2465 photon.energy -= m_deexcitations[fLevel].energy;
2466 if (photon.energy < Small) photon.energy = Small;
2467 m_dxcProducts.push_back(std::move(photon));
2468 // Proceed with the next level in the cascade.
2469 iLevel = fLevel;
2470 } else {
2471 // Decay to ground state.
2472 double delta = RndmVoigt(0., dxc.sDoppler, dxc.gPressure);
2473 while (photon.energy + delta < Small || fabs(delta) >= dxc.width) {
2474 delta = RndmVoigt(0., dxc.sDoppler, dxc.gPressure);
2475 }
2476 photon.energy += delta;
2477 m_dxcProducts.push_back(std::move(photon));
2478 // Deexcitation cascade is over.
2479 fLevel = iLevel;
2480 return;
2481 }
2482 } else if (type == DxcTypeCollIon) {
2483 // Ionisation electron
2484 dxcProd electron;
2485 electron.s = 0.;
2486 electron.t = t;
2487 electron.type = DxcProdTypeElectron;
2488 electron.energy = dxc.energy;
2489 if (fLevel >= 0) {
2490 // Associative ionisation
2491 electron.energy -= m_deexcitations[fLevel].energy;
2492 if (electron.energy < Small) electron.energy = Small;
2493 ++m_nPenning;
2494 m_dxcProducts.push_back(std::move(electron));
2495 // Proceed with the next level in the cascade.
2496 iLevel = fLevel;
2497 } else {
2498 // Penning ionisation
2499 electron.energy -= m_minIonPot;
2500 if (electron.energy < Small) electron.energy = Small;
2501 ++m_nPenning;
2502 m_dxcProducts.push_back(std::move(electron));
2503 // Deexcitation cascade is over.
2504 fLevel = iLevel;
2505 return;
2506 }
2507 } else if (type == DxcTypeCollNonIon) {
2508 // Proceed with the next level in the cascade.
2509 iLevel = fLevel;
2510 } else {
2511 std::cerr << m_className << "::ComputeDeexcitationInternal:\n"
2512 << " Unknown deexcitation type (" << type << "). Bug!\n";
2513 // Abort the calculation.
2514 fLevel = iLevel;
2515 return;
2516 }
2517 }
2518}
2519
2520bool MediumMagboltz::ComputePhotonCollisionTable(const bool verbose) {
2521
2522 // Atomic density
2523 const double dens = GetNumberDensity();
2524
2525 // Reset the collision rate arrays.
2526 m_cfTotGamma.assign(nEnergyStepsGamma, 0.);
2527 m_cfGamma.assign(nEnergyStepsGamma, std::vector<double>());
2528 csTypeGamma.clear();
2529
2530 m_nPhotonTerms = 0;
2531 for (unsigned int i = 0; i < m_nComponents; ++i) {
2532 const double prefactor = dens * SpeedOfLight * m_fraction[i];
2533 // Check if optical data for this gas is available.
2534 std::string gasname = m_gas[i];
2535 if (gasname == "iC4H10") {
2536 gasname = "nC4H10";
2537 if (m_debug || verbose) {
2538 std::cout << m_className << "::ComputePhotonCollisionTable:\n"
2539 << " Photoabsorption cross-section for "
2540 << "iC4H10 not available.\n"
2541 << " Using n-butane cross-section instead.\n";
2542 }
2543 }
2544 if (!OpticalData::IsAvailable(gasname)) return false;
2545 csTypeGamma.push_back(i * nCsTypesGamma + PhotonCollisionTypeIonisation);
2546 csTypeGamma.push_back(i * nCsTypesGamma + PhotonCollisionTypeInelastic);
2547 m_nPhotonTerms += 2;
2548 for (int j = 0; j < nEnergyStepsGamma; ++j) {
2549 const double en = (j + 0.5) * m_eStepGamma;
2550 // Retrieve total photoabsorption cross-section and ionisation yield.
2551 const double cs = OpticalData::PhotoabsorptionCrossSection(gasname, en);
2552 const double eta = OpticalData::PhotoionisationYield(gasname, en);
2553 m_cfTotGamma[j] += cs * prefactor;
2554 // Ionisation
2555 m_cfGamma[j].push_back(cs * prefactor * eta);
2556 // Inelastic absorption
2557 m_cfGamma[j].push_back(cs * prefactor * (1. - eta));
2558 }
2559 }
2560
2561 // If requested, write the cross-sections to file.
2562 if (m_useCsOutput) {
2563 std::ofstream csfile;
2564 csfile.open("csgamma.txt", std::ios::out);
2565 for (int j = 0; j < nEnergyStepsGamma; ++j) {
2566 csfile << (j + 0.5) * m_eStepGamma << " ";
2567 for (unsigned int i = 0; i < m_nPhotonTerms; ++i)
2568 csfile << m_cfGamma[j][i] << " ";
2569 csfile << "\n";
2570 }
2571 csfile.close();
2572 }
2573
2574 // Calculate the cumulative rates.
2575 for (int j = 0; j < nEnergyStepsGamma; ++j) {
2576 for (unsigned int i = 0; i < m_nPhotonTerms; ++i) {
2577 if (i > 0) m_cfGamma[j][i] += m_cfGamma[j][i - 1];
2578 }
2579 }
2580
2581 if (m_debug || verbose) {
2582 std::cout << m_className << "::ComputePhotonCollisionTable:\n";
2583 std::cout << " Energy [eV] Mean free path [um]\n";
2584 for (int i = 0; i < 10; ++i) {
2585 const int j = (2 * i + 1) * nEnergyStepsGamma / 20;
2586 const double en = (2 * i + 1) * m_eFinalGamma / 20;
2587 const double imfp = m_cfTotGamma[j] / SpeedOfLight;
2588 if (imfp > 0.) {
2589 printf(" %10.2f %18.4f\n", en, 1.e4 / imfp);
2590 } else {
2591 printf(" %10.2f ------------\n", en);
2592 }
2593 }
2594 }
2595
2596 if (!m_useDeexcitation) return true;
2597
2598 // Conversion factor from oscillator strength to cross-section
2599 constexpr double f2cs =
2600 FineStructureConstant * 2 * Pi2 * HbarC * HbarC / ElectronMass;
2601 // Discrete absorption lines
2602 int nResonanceLines = 0;
2603 for (auto& dxc : m_deexcitations) {
2604 if (dxc.osc < Small) continue;
2605 const double prefactor = dens * SpeedOfLight * m_fraction[dxc.gas];
2606 dxc.cf = prefactor * f2cs * dxc.osc;
2607 // Compute the line width due to Doppler broadening.
2608 const double mgas = ElectronMass / (m_rgas[dxc.gas] - 1.);
2609 const double wDoppler = sqrt(BoltzmannConstant * m_temperature / mgas);
2610 dxc.sDoppler = wDoppler * dxc.energy;
2611 // Compute the half width at half maximum due to resonance broadening.
2612 // A. W. Ali and H. R. Griem, Phys. Rev. 140, 1044
2613 // A. W. Ali and H. R. Griem, Phys. Rev. 144, 366
2614 const double kResBroad = 1.92 * Pi * sqrt(1. / 3.);
2615 dxc.gPressure = kResBroad * FineStructureConstant * pow(HbarC, 3) *
2616 dxc.osc * dens * m_fraction[dxc.gas] /
2617 (ElectronMass * dxc.energy);
2618 // Make an estimate for the width within which a photon can be
2619 // absorbed by the line
2620 constexpr double nWidths = 1000.;
2621 // Calculate the FWHM of the Voigt distribution according to the
2622 // approximation formula given in
2623 // Olivero and Longbothum, J. Quant. Spectr. Rad. Trans. 17, 233-236
2624 const double fwhmGauss = dxc.sDoppler * sqrt(2. * log(2.));
2625 const double fwhmLorentz = dxc.gPressure;
2626 const double fwhmVoigt =
2627 0.5 * (1.0692 * fwhmLorentz + sqrt(0.86639 * fwhmLorentz * fwhmLorentz +
2628 4 * fwhmGauss * fwhmGauss));
2629 dxc.width = nWidths * fwhmVoigt;
2630 ++nResonanceLines;
2631 }
2632
2633 if (nResonanceLines <= 0) {
2634 std::cerr << m_className << "::ComputePhotonCollisionTable:\n"
2635 << " No resonance lines found.\n";
2636 return true;
2637 }
2638
2639 if (!(m_debug || verbose)) return true;
2640 std::cout << m_className << "::ComputePhotonCollisionTable:\n "
2641 << "Discrete absorption lines:\n Energy [eV] "
2642 << "Line width (FWHM) [eV] Mean free path [um]\n "
2643 << " Doppler Pressure (peak)\n";
2644 for (const auto& dxc : m_deexcitations) {
2645 if (dxc.osc < Small) continue;
2646 const double wp = 2 * dxc.gPressure;
2647 const double wd = 2 * sqrt(2 * log(2.)) * dxc.sDoppler;
2648 const double imfpP =
2649 (dxc.cf / SpeedOfLight) * TMath::Voigt(0., dxc.sDoppler, wp);
2650 if (imfpP > 0.) {
2651 printf(" %6.3f +/- %6.1e %6.2e %6.3e %14.4f\n", dxc.energy,
2652 dxc.width, wd, wp, 1.e4 / imfpP);
2653 } else {
2654 printf(" %6.3f +/- %6.1e %6.2e %6.3e -------------\n", dxc.energy,
2655 dxc.width, wd, wp);
2656 }
2657 }
2658 return true;
2659}
2660
2662 const double emag, const double bmag, const double btheta, const int ncoll,
2663 bool verbose, double& vx, double& vy, double& vz, double& dl, double& dt,
2664 double& alpha, double& eta, double& lor, double& vxerr, double& vyerr,
2665 double& vzerr, double& dlerr, double& dterr, double& alphaerr,
2666 double& etaerr, double& lorerr, double& alphatof,
2667 std::array<double, 6>& difftens) {
2668 // Initialize the values.
2669 vx = vy = vz = 0.;
2670 dl = dt = 0.;
2671 alpha = eta = alphatof = 0.;
2672 lor = 0.;
2673 vxerr = vyerr = vzerr = 0.;
2674 dlerr = dterr = 0.;
2675 alphaerr = etaerr = 0.;
2676 lorerr = 0.;
2677
2678 // Set the input parameters in the Magboltz common blocks.
2680 Magboltz::inpt_.nAniso = 2;
2681 if (m_autoEnergyLimit) {
2682 Magboltz::inpt_.efinal = 0.;
2683 } else {
2684 Magboltz::inpt_.efinal = std::min(m_eMax, m_eHigh);
2685 }
2686 Magboltz::inpt_.tempc = m_temperature - ZeroCelsius;
2688 Magboltz::inpt_.ipen = 0;
2689 Magboltz::setp_.nmax = ncoll;
2690
2691 Magboltz::thrm_.ithrm = m_useGasMotion ? 1 : 0;
2692
2693 Magboltz::setp_.efield = emag;
2694 // Convert from Tesla to kGauss.
2695 Magboltz::bfld_.bmag = bmag * 10.;
2696 // Convert from radians to degree.
2697 Magboltz::bfld_.btheta = btheta * RadToDegree;
2698
2699 // Set the gas composition in Magboltz.
2700 for (unsigned int i = 0; i < m_nComponents; ++i) {
2701 const int ng = GetGasNumberMagboltz(m_gas[i]);
2702 if (ng <= 0) {
2703 std::cerr << m_className << "::RunMagboltz:\n Gas " << m_gas[i]
2704 << " does not have a gas number in Magboltz.\n";
2705 return;
2706 }
2707 Magboltz::gasn_.ngasn[i] = ng;
2708 Magboltz::ratio_.frac[i] = 100 * m_fraction[i];
2709 }
2710
2711 // Run Magboltz.
2713
2714 // Velocities. Convert to cm / ns.
2715 vx = Magboltz::vel_.wx * 1.e-9;
2716 vxerr = Magboltz::velerr_.dwx;
2717 vy = Magboltz::vel_.wy * 1.e-9;
2718 vyerr = Magboltz::velerr_.dwy;
2719 vz = Magboltz::vel_.wz * 1.e-9;
2720 vzerr = Magboltz::velerr_.dwz;
2721
2722 // Calculate the Lorentz angle.
2723 const double vt = sqrt(vx * vx + vy * vy);
2724 const double v2 = (vx * vx + vy * vy + vz * vz);
2725 lor = atan2(vt, vz);
2726 if (vt > 0. && v2 > 0. && fabs(lor) > 0.) {
2727 const double dvx = vx * vxerr;
2728 const double dvy = vy * vyerr;
2729 const double dvz = vz * vzerr;
2730 const double a = vz / vt;
2731 lorerr = sqrt(a * a * (vx * vx * dvx * dvx + vy * vy * dvy * dvy) +
2732 vt * vt * dvz * dvz) /
2733 v2;
2734 lorerr /= lor;
2735 }
2736
2737 // Diffusion coefficients.
2738 dt = sqrt(0.2 * 0.5 * (Magboltz::diflab_.difxx + Magboltz::diflab_.difyy) /
2739 vz) *
2740 1.e-4;
2741 dterr = 0.5 * sqrt(Magboltz::diferl_.dfter * Magboltz::diferl_.dfter +
2742 vzerr * vzerr);
2743 dl = sqrt(0.2 * Magboltz::diflab_.difzz / vz) * 1.e-4;
2744 dlerr = 0.5 * sqrt(Magboltz::diferl_.dfler * Magboltz::diferl_.dfler +
2745 vzerr * vzerr);
2746 // Diffusion tensor.
2747 difftens[0] = 0.2e-4 * Magboltz::diflab_.difzz / vz;
2748 difftens[1] = 0.2e-4 * Magboltz::diflab_.difxx / vz;
2749 difftens[2] = 0.2e-4 * Magboltz::diflab_.difyy / vz;
2750 difftens[3] = 0.2e-4 * Magboltz::diflab_.difxz / vz;
2751 difftens[4] = 0.2e-4 * Magboltz::diflab_.difyz / vz;
2752 difftens[5] = 0.2e-4 * Magboltz::diflab_.difxy / vz;
2753 // Townsend and attachment coefficients.
2754 alpha = Magboltz::ctowns_.alpha;
2755 alphaerr = Magboltz::ctwner_.alper;
2756 eta = Magboltz::ctowns_.att;
2757 etaerr = Magboltz::ctwner_.atter;
2758
2759 // Calculate effective Townsend SST coefficient from TOF results.
2760 if (fabs(Magboltz::tofout_.tofdl) > 0.) {
2761 const double wrzn = 1.e5 * Magboltz::tofout_.tofwr;
2762 const double fc1 = 0.5 * wrzn / Magboltz::tofout_.tofdl;
2763 const double fc2 = (Magboltz::tofout_.ralpha -
2764 Magboltz::tofout_.rattof) * 1.e12 /
2765 Magboltz::tofout_.tofdl;
2766 alphatof = fc1 - sqrt(fc1 * fc1 - fc2);
2767 }
2768 // Print the results.
2769 if (!(m_debug || verbose)) return;
2770 std::cout << m_className << "::RunMagboltz: Results:\n";
2771 printf(" Drift velocity along E: %12.8f cm/ns +/- %5.2f%%\n", vz, vzerr);
2772 printf(" Drift velocity along Bt: %12.8f cm/ns +/- %5.2f%%\n", vx, vxerr);
2773 printf(" Drift velocity along ExB: %12.8f cm/ns +/- %5.2f%%\n", vy, vyerr);
2774 printf(" Lorentz angle: %12.3f degree\n", lor * RadToDegree);
2775 printf(" Longitudinal diffusion: %12.8f cm1/2 +/- %5.2f%%\n", dl, dlerr);
2776 printf(" Transverse diffusion: %12.8f cm1/2 +/- %5.2f%%\n", dt, dterr);
2777 printf(" Townsend coefficient: %12.4f cm-1 +/- %5.2f%%\n", alpha,
2778 alphaerr);
2779 printf(" Attachment coefficient: %12.4f cm-1 +/- %5.2f%%\n", eta,
2780 etaerr);
2781 if (alphatof > 0.) {
2782 printf(" TOF effective Townsend: %12.4f cm-1 (alpha - eta)\n",
2783 alphatof);
2784 }
2785}
2786
2787void MediumMagboltz::GenerateGasTable(const int numColl, const bool verbose) {
2788 // Set the reference pressure and temperature.
2791
2792 // Initialize the parameter arrays.
2793 const unsigned int nEfields = m_eFields.size();
2794 const unsigned int nBfields = m_bFields.size();
2795 const unsigned int nAngles = m_bAngles.size();
2796 Init(nEfields, nBfields, nAngles, m_eVelE, 0.);
2797 Init(nEfields, nBfields, nAngles, m_eVelB, 0.);
2798 Init(nEfields, nBfields, nAngles, m_eVelX, 0.);
2799 Init(nEfields, nBfields, nAngles, m_eDifL, 0.);
2800 Init(nEfields, nBfields, nAngles, m_eDifT, 0.);
2801 Init(nEfields, nBfields, nAngles, m_eLor, 0.);
2802 Init(nEfields, nBfields, nAngles, m_eAlp, -30.);
2803 Init(nEfields, nBfields, nAngles, m_eAlp0, -30.);
2804 Init(nEfields, nBfields, nAngles, m_eAtt, -30.);
2805 Init(nEfields, nBfields, nAngles, 6, m_eDifM, 0.);
2806
2807 m_excRates.clear();
2808 m_ionRates.clear();
2809 // Retrieve the excitation and ionisation cross-sections in the gas mixture.
2810 GetExcitationIonisationLevels();
2811 std::cout << m_className << "::GenerateGasTable: Found "
2812 << m_excLevels.size() << " excitations and "
2813 << m_ionLevels.size() << " ionisations.\n";
2814 for (const auto& exc : m_excLevels) {
2815 std::cout << " " << exc.label << ", energy = " << exc.energy << " eV.\n";
2816 }
2817 for (const auto& ion : m_ionLevels) {
2818 std::cout << " " << ion.label << ", energy = " << ion.energy << " eV.\n";
2819 }
2820 if (!m_excLevels.empty()) {
2821 Init(nEfields, nBfields, nAngles, m_excLevels.size(), m_excRates, 0.);
2822 }
2823 if (!m_ionLevels.empty()) {
2824 Init(nEfields, nBfields, nAngles, m_ionLevels.size(), m_ionRates, 0.);
2825 }
2826 double vx = 0., vy = 0., vz = 0.;
2827 double difl = 0., dift = 0.;
2828 double alpha = 0., eta = 0.;
2829 double lor = 0.;
2830 double vxerr = 0., vyerr = 0., vzerr = 0.;
2831 double diflerr = 0., difterr = 0.;
2832 double alphaerr = 0., etaerr = 0.;
2833 double alphatof = 0.;
2834 double lorerr = 0.;
2835 std::array<double, 6> difftens;
2836
2837 // Run through the grid of E- and B-fields and angles.
2838 for (unsigned int i = 0; i < nEfields; ++i) {
2839 const double e = m_eFields[i];
2840 for (unsigned int j = 0; j < nAngles; ++j) {
2841 const double a = m_bAngles[j];
2842 for (unsigned int k = 0; k < nBfields; ++k) {
2843 const double b = m_bFields[k];
2844 std::cout << m_className << "::GenerateGasTable: E = " << e
2845 << " V/cm, B = " << b << " T, angle: " << a << " rad\n";
2846 RunMagboltz(e, b, a, numColl, verbose, vx, vy, vz, difl, dift, alpha,
2847 eta, lor, vxerr, vyerr, vzerr, diflerr, difterr, alphaerr,
2848 etaerr, lorerr, alphatof, difftens);
2849 m_eVelE[j][k][i] = vz;
2850 m_eVelX[j][k][i] = vy;
2851 m_eVelB[j][k][i] = vx;
2852 m_eDifL[j][k][i] = difl;
2853 m_eDifT[j][k][i] = dift;
2854 m_eLor[j][k][i] = lor;
2855 m_eAlp[j][k][i] = alpha > 0. ? log(alpha) : -30.;
2856 m_eAlp0[j][k][i] = m_eAlp[j][k][i];
2857 m_eAtt[j][k][i] = eta > 0. ? log(eta) : -30.;
2858 for (unsigned int l = 0; l < 6; ++l) {
2859 m_eDifM[l][j][k][i] = difftens[l];
2860 }
2861 // Retrieve the excitation and ionisation rates.
2862 unsigned int nNonZero = 0;
2863 if (m_useGasMotion) {
2864 // Retrieve the total collision frequency and number of collisions.
2865 double ftot = 0., fel = 0., fion = 0., fatt = 0., fin = 0.;
2866 std::int64_t ntotal = 0;
2867 Magboltz::colft_(&ftot, &fel, &fion, &fatt, &fin, &ntotal);
2868 if (ntotal == 0) continue;
2869 // Convert from ps-1 to ns-1.
2870 const double scale = 1.e3 * ftot / ntotal;
2871 for (unsigned int ig = 0; ig < m_nComponents; ++ig) {
2872 const auto nL = Magboltz::larget_.last[ig];
2873 for (std::int64_t il = 0; il < nL; ++il) {
2874 if (Magboltz::larget_.iarry[il][ig] <= 0) break;
2875 // Skip levels that are not ionisations or inelastic collisions.
2876 const int cstype = (Magboltz::larget_.iarry[il][ig] - 1) % 5;
2877 if (cstype != 1 && cstype != 3) continue;
2878 // const int igas = int((Magboltz::larget_.iarry[il][ig] - 1) / 5);
2879 auto descr = GetDescription(il, ig, Magboltz::script_.dscrpt);
2880 descr = m_gas[ig] + descr;
2881 if (cstype == 3) {
2882 const unsigned int nExc = m_excLevels.size();
2883 for (unsigned int ie = 0; ie < nExc; ++ie) {
2884 if (descr != m_excLevels[ie].label) continue;
2885 const auto ncoll = Magboltz::outptt_.icoln[il][ig];
2886 m_excRates[ie][j][k][i] = scale * ncoll;
2887 if (ncoll > 0) ++nNonZero;
2888 break;
2889 }
2890 } else if (cstype == 1) {
2891 const unsigned int nIon = m_ionLevels.size();
2892 for (unsigned int ii = 0; ii < nIon; ++ii) {
2893 if (descr != m_ionLevels[ii].label) continue;
2894 const auto ncoll = Magboltz::outptt_.icoln[il][ig];
2895 m_ionRates[ii][j][k][i] = scale * ncoll;
2896 if (ncoll > 0) ++nNonZero;
2897 break;
2898 }
2899 }
2900 }
2901 }
2902 } else {
2903 // Retrieve the total collision frequency and number of collisions.
2904 double ftot = 0., fel = 0., fion = 0., fatt = 0., fin = 0.;
2905 std::int64_t ntotal = 0;
2906 Magboltz::colf_(&ftot, &fel, &fion, &fatt, &fin, &ntotal);
2907 if (ntotal == 0) continue;
2908 // Convert from ps-1 to ns-1.
2909 const double scale = 1.e3 * ftot / ntotal;
2910 for (std::int64_t il = 0; il < Magboltz::nMaxLevels; ++il) {
2911 if (Magboltz::large_.iarry[il] <= 0) break;
2912 // Skip levels that are not ionisations or inelastic collisions.
2913 const int cstype = (Magboltz::large_.iarry[il] - 1) % 5;
2914 if (cstype != 1 && cstype != 3) continue;
2915 const int igas = int((Magboltz::large_.iarry[il] - 1) / 5);
2916 std::string descr = GetDescription(il, Magboltz::scrip_.dscrpt);
2917 descr = m_gas[igas] + descr;
2918 if (cstype == 3) {
2919 const unsigned int nExc = m_excLevels.size();
2920 for (unsigned int ie = 0; ie < nExc; ++ie) {
2921 if (descr != m_excLevels[ie].label) continue;
2922 m_excRates[ie][j][k][i] = scale * Magboltz::outpt_.icoln[il];
2923 if (Magboltz::outpt_.icoln[il] > 0) ++nNonZero;
2924 break;
2925 }
2926 } else if (cstype == 1) {
2927 const unsigned int nIon = m_ionLevels.size();
2928 for (unsigned int ii = 0; ii < nIon; ++ii) {
2929 if (descr != m_ionLevels[ii].label) continue;
2930 m_ionRates[ii][j][k][i] = scale * Magboltz::outpt_.icoln[il];
2931 if (Magboltz::outpt_.icoln[il] > 0) ++nNonZero;
2932 break;
2933 }
2934 }
2935 }
2936 }
2937 if (nNonZero > 0) {
2938 std::cout << " Excitation and ionisation rates:\n";
2939 std::cout << " Level "
2940 << " Rate [ns-1]\n";
2941 const unsigned int nExc = m_excLevels.size();
2942 for (unsigned int ie = 0; ie < nExc; ++ie) {
2943 if (m_excRates[ie][j][k][i] <= 0) continue;
2944 std::cout << std::setw(60) << m_excLevels[ie].label;
2945 std::printf(" %15.8f\n", m_excRates[ie][j][k][i]);
2946 }
2947 const unsigned int nIon = m_ionLevels.size();
2948 for (unsigned int ii = 0; ii < nIon; ++ii) {
2949 if (m_ionRates[ii][j][k][i] <= 0) continue;
2950 std::cout << std::setw(60) << m_ionLevels[ii].label;
2951 std::printf(" %15.8f\n", m_ionRates[ii][j][k][i]);
2952 }
2953 }
2954 }
2955 }
2956 }
2957 // Set the threshold indices.
2960}
2961
2962void MediumMagboltz::GetExcitationIonisationLevels() {
2963
2964 // Reset.
2965 m_excLevels.clear();
2966 m_ionLevels.clear();
2967 // Cross-sections.
2968 static double q[Magboltz::nEnergySteps][6];
2972 static double qNull[Magboltz::nEnergySteps][Magboltz::nMaxNullTerms];
2973 // Parameters for angular distributions.
2974 static double pEqEl[Magboltz::nEnergySteps][6];
2977 // Penning transfer parameters
2978 static double penFra[Magboltz::nMaxInelasticTerms][3];
2979 // Description of cross-section terms
2981 static char scrptn[Magboltz::nMaxNullTerms][Magboltz::nCharDescr];
2982
2983 // Loop over the gases in the mixture.
2984 for (unsigned int i = 0; i < m_nComponents; ++i) {
2985 // Choose the energy range large enough to cover all relevant levels.
2986 const double emax = 400.;
2987 Magboltz::inpt_.efinal = emax;
2989 char name[Magboltz::nCharName];
2990 // Number of inelastic, ionisation, attachment and null-collision levels.
2991 std::int64_t nIn = 0, nIon = 0, nAtt = 1, nNull = 0;
2992 // Virial coefficient (not used)
2993 double virial = 0.;
2994 // Thresholds/characteristic energies.
2995 static double e[6];
2996 // Energy losses and ionisation thresholds.
2997 static double eIn[Magboltz::nMaxInelasticTerms];
2998 static double eIon[Magboltz::nMaxIonisationTerms];
2999 // Scattering parameters.
3000 static std::int64_t kIn[Magboltz::nMaxInelasticTerms];
3001 static std::int64_t kEl[6];
3002 // Opal-Beaty parameters.
3003 static double eoby[Magboltz::nMaxIonisationTerms];
3004 // Scaling factor for "null-collision" terms
3005 static double scln[Magboltz::nMaxNullTerms];
3006 // Parameters for simulation of Auger and fluorescence processes.
3007 static std::int64_t nc0[Magboltz::nMaxIonisationTerms];
3008 static std::int64_t ng1[Magboltz::nMaxIonisationTerms];
3009 static std::int64_t ng2[Magboltz::nMaxIonisationTerms];
3010 static double ec0[Magboltz::nMaxIonisationTerms];
3011 static double wklm[Magboltz::nMaxIonisationTerms];
3012 static double efl[Magboltz::nMaxIonisationTerms];
3013 static double eg1[Magboltz::nMaxIonisationTerms];
3014 static double eg2[Magboltz::nMaxIonisationTerms];
3015
3016 // Retrieve the cross-section data for this gas from Magboltz.
3017 std::int64_t ng = GetGasNumberMagboltz(m_gas[i]);
3018 if (ng <= 0) {
3019 std::cerr << m_className << "::GetExcitationIonisationLevels:\n\n"
3020 << " Gas " << m_gas[i] << " not available in Magboltz.\n";
3021 continue;
3022 }
3024 &ng, q[0], qIn[0], &nIn, e, eIn, name, &virial, eoby, pEqEl[0],
3025 pEqIn[0], penFra[0], kEl, kIn, qIon[0], pEqIon[0], eIon, &nIon,
3026 qAtt[0], &nAtt, qNull[0], &nNull, scln, nc0, ec0, wklm, efl,
3027 ng1, eg1, ng2, eg2, scrpt, scrptn,
3029 const double r = 1. + 0.5 * e[1];
3030 // Ionisation cross section(s).
3031 for (int j = 0; j < nIon; ++j) {
3032 const std::string descr = GetDescription(2 + j, scrpt);
3033 IonLevel ion;
3034 ion.label = m_gas[i] + descr;
3035 ion.energy = eIon[j] / r;
3036 m_ionLevels.push_back(std::move(ion));
3037 }
3038 // Excitation cross-sections.
3039 for (int j = 0; j < nIn; ++j) {
3040 const std::string descr = GetDescription(4 + nIon + nAtt + j, scrpt);
3041 if ((descr[1] == 'E' && descr[2] == 'X') ||
3042 (descr[0] == 'E' && descr[1] == 'X')) {
3043 // Excitation
3044 ExcLevel exc;
3045 exc.label = m_gas[i] + descr;
3046 exc.energy = eIn[j] / r;
3047 exc.prob = 0.;
3048 exc.rms = 0.;
3049 exc.dt = 0.;
3050 m_excLevels.push_back(std::move(exc));
3051 }
3052 }
3053 }
3054}
3055
3056}
double GetNumberDensity() const override
Get the number density [cm-3].
Definition MediumGas.cc:304
virtual bool EnablePenningTransfer()
static constexpr unsigned int m_nMaxGases
Definition MediumGas.hh:161
std::vector< std::vector< std::vector< std::vector< double > > > > m_excRates
Definition MediumGas.hh:190
double m_lambdaPenningGlobal
Definition MediumGas.hh:175
std::vector< IonLevel > m_ionLevels
Definition MediumGas.hh:207
std::array< double, m_nMaxGases > m_rPenningGas
Definition MediumGas.hh:177
std::vector< std::vector< std::vector< std::vector< double > > > > m_ionRates
Definition MediumGas.hh:191
std::vector< ExcLevel > m_excLevels
Definition MediumGas.hh:201
MediumGas()
Constructor.
Definition MediumGas.cc:115
virtual void PrintGas()
Print information about the present gas mixture and available data.
static std::string GetGasName(const int gasnumber, const int version)
std::array< double, m_nMaxGases > m_lambdaPenningGas
Definition MediumGas.hh:179
virtual void DisablePenningTransfer()
Switch the simulation of Penning transfers off globally.
bool SetComposition(const std::string &gas1, const double f1=1., const std::string &gas2="", const double f2=0., const std::string &gas3="", const double f3=0., const std::string &gas4="", const double f4=0., const std::string &gas5="", const double f5=0., const std::string &gas6="", const double f6=0.)
Set the gas mixture.
Definition MediumGas.cc:138
std::vector< std::vector< std::vector< double > > > m_eAlp0
Definition MediumGas.hh:187
std::array< std::string, m_nMaxGases > m_gas
Definition MediumGas.hh:164
std::array< double, m_nMaxGases > m_fraction
Definition MediumGas.hh:165
void SetSplittingFunctionGreenSawada()
Sample the secondary electron energy according to the Green-Sawada model.
bool EnablePenningTransfer() override
unsigned int GetNumberOfLevels()
Get the number of cross-section terms.
void SetExcitationScaling(const double r, std::string gasname)
Multiply all excitation cross-sections by a uniform scaling factor.
bool GetLevel(const unsigned int i, int &ngas, int &type, std::string &descr, double &e)
Get detailed information about a given cross-section term i.
double GetElectronNullCollisionRate(const int band) override
Get the overall null-collision rate [ns-1].
void ResetCollisionCounters()
Reset the collision counters.
void EnableRadiationTrapping()
Switch on discrete photoabsorption levels.
unsigned int GetNumberOfElectronCollisions() const
Get the total number of electron collisions.
double GetPhotonCollisionRate(const double e) override
bool ElectronCollision(const double e, int &type, int &level, double &e1, double &dx, double &dy, double &dz, std::vector< std::pair< Particle, double > > &secondaries, int &ndxc, int &band) override
Sample the collision type.
void RunMagboltz(const double e, const double b, const double btheta, const int ncoll, bool verbose, double &vx, double &vy, double &vz, double &dl, double &dt, double &alpha, double &eta, double &lor, double &vxerr, double &vyerr, double &vzerr, double &dlerr, double &dterr, double &alphaerr, double &etaerr, double &lorerr, double &alphatof, std::array< double, 6 > &difftens)
void ComputeDeexcitation(int iLevel, int &fLevel)
void SetSplittingFunctionOpalBeaty()
Sample the secondary electron energy according to the Opal-Beaty model.
static int GetGasNumberMagboltz(const std::string &input)
void GenerateGasTable(const int numCollisions=10, const bool verbose=true)
bool GetDeexcitationProduct(const unsigned int i, double &t, double &s, int &type, double &energy) const override
void SetSplittingFunctionFlat()
Sample the secondary electron energy from a flat distribution.
MediumMagboltz()
Default constructor.
unsigned int GetNumberOfPhotonCollisions() const
Get the total number of photon collisions.
bool SetMaxPhotonEnergy(const double e)
void DisablePenningTransfer() override
Switch the simulation of Penning transfers off globally.
bool GetPhotonCollision(const double e, int &type, int &level, double &e1, double &ctheta, int &nsec, double &esec) override
bool GetPenningTransfer(const unsigned int i, double &r, double &lambda)
Get the Penning transfer probability and distance of a specific level.
bool Initialise(const bool verbose=false)
double GetElectronCollisionRate(const double e, const int band) override
Get the (real) collision rate [ns-1] at a given electron energy e [eV].
bool SetMaxElectronEnergy(const double e)
void PrintGas() override
Print information about the present gas mixture and available data.
void EnableDeexcitation()
Switch on (microscopic) de-excitation handling.
std::vector< double > m_bFields
Definition Medium.hh:573
double m_pressure
Definition Medium.hh:542
std::vector< std::vector< std::vector< double > > > m_eAlp
Definition Medium.hh:582
size_t SetThreshold(const std::vector< std::vector< std::vector< double > > > &tab) const
Definition Medium.cc:1252
std::vector< std::vector< std::vector< double > > > m_eVelE
Definition Medium.hh:577
std::vector< std::vector< std::vector< double > > > m_eVelX
Definition Medium.hh:578
std::vector< std::vector< std::vector< double > > > m_eDifL
Definition Medium.hh:580
void Init(const size_t nE, const size_t nB, const size_t nA, std::vector< std::vector< std::vector< double > > > &tab, const double val)
Definition Medium.cc:1408
std::vector< double > m_eFields
Definition Medium.hh:572
std::vector< std::vector< std::vector< double > > > m_eAtt
Definition Medium.hh:583
std::vector< double > m_bAngles
Definition Medium.hh:574
std::vector< std::vector< std::vector< double > > > m_eLor
Definition Medium.hh:584
std::vector< std::vector< std::vector< double > > > m_eDifT
Definition Medium.hh:581
std::vector< std::vector< std::vector< double > > > m_eVelB
Definition Medium.hh:579
unsigned int m_nComponents
Definition Medium.hh:536
std::string m_className
Definition Medium.hh:529
std::vector< std::vector< std::vector< std::vector< double > > > > m_eDifM
Definition Medium.hh:586
double m_temperature
Definition Medium.hh:540
static bool IsAvailable(const std::string &material)
Check whether optical data have been implemented for a given gas.
static double PhotoionisationYield(const std::string &material, const double energy)
Photo-ionisation yield at a given energy.
static bool PhotoabsorptionCrossSection(const std::string &material, const double energy, double &cs, double &eta)
Photo-absorption cross-section and ionisation yield at a given energy.
static std::string FindUnusedCanvasName(const std::string &s)
Find an unused canvas name.
Definition ViewBase.cc:337
struct Garfield::Magboltz::@060222016335007357276230325267072324307245075335 velerr_
struct Garfield::Magboltz::@071266015331337350342074140245340115315173206173 script_
constexpr unsigned int nMaxInelasticTerms
constexpr unsigned int nEnergySteps
struct Garfield::Magboltz::@272077330053140267135131315064054111215365003061 inpt_
constexpr unsigned int nMaxAttachmentTerms
struct Garfield::Magboltz::@236122006074061251067327101212023331117065272002 larget_
constexpr unsigned int nCharDescr
struct Garfield::Magboltz::@016351066267021123171150005363312076230027317210 outpt_
double cf[nMaxLevels][nEnergySteps]
struct Garfield::Magboltz::@367143224022215036160202274360277173043324053167 diferl_
constexpr unsigned int nMaxIonisationTerms
constexpr unsigned int nCharName
struct Garfield::Magboltz::@157012065177270115006254216365004043341121046005 vel_
struct Garfield::Magboltz::@245266170301200003106052333313347204035061117342 ratio_
struct Garfield::Magboltz::@010063252323105360345105253327133025274175326133 bfld_
struct Garfield::Magboltz::@367172176335371151066116323064254124322366107157 diflab_
struct Garfield::Magboltz::@057270266311332313123374024066154010246130331023 cnsts_
struct Garfield::Magboltz::@222150133151105054154302130362347315126001152334 dens_
struct Garfield::Magboltz::@304340026334031267143114007126147372164074306062 outptt_
constexpr unsigned int nMaxLevels
void colf_(double *freq, double *freel, double *freion, double *freatt, double *frein, std::int64_t *ntotal)
constexpr unsigned int nMaxNullTerms
struct Garfield::Magboltz::@043341123353356340117075350116005072335377127162 gasn_
struct Garfield::Magboltz::@152023163236327126074317062211255304316001105103 tofout_
struct Garfield::Magboltz::@261142307054266253060114162042322335114302253152 ctowns_
struct Garfield::Magboltz::@343242004052315221243247367236006336135121373020 setp_
struct Garfield::Magboltz::@363161251140110014365135052375245247262302340235 large_
struct Garfield::Magboltz::@243337270271362237030277156013024035163331176157 thrm_
void gasmix_(std::int64_t *ngs, double *q, double *qin, std::int64_t *nin, double *e, double *ei, char *name, double *virl, double *eb, double *peqel, double *peqin, double *penfra, std::int64_t *kel, std::int64_t *kin, double *qion, double *peqion, double *eion, std::int64_t *nion, double *qatt, std::int64_t *natt, double *qnull, std::int64_t *nnull, double *scln, std::int64_t *nc0, double *ec0, double *wk, double *efl, std::int64_t *ng1, double *eg1, std::int64_t *ng2, double *eg2, char scrpt[nMaxLevelsPerComponent][nCharDescr], char scrptn[nMaxNullTerms][nCharDescr], short namelen, short scrpt_len, short scrptn_len)
void colft_(double *freq, double *freel, double *freion, double *freatt, double *frein, std::int64_t *ntotal)
struct Garfield::Magboltz::@301116172155326317070225220046057374317172177377 ctwner_
constexpr unsigned int nMaxLevelsPerComponent
struct Garfield::Magboltz::@276025017301256117263211055021110262335047200203 mix2_
struct Garfield::Magboltz::@024075325050032167066064232170013161226346016145 scrip_
double RndmUniform()
Draw a random number uniformly distributed in the range [0, 1).
Definition Random.hh:14
std::vector< std::string > tokenize(const std::string &line)
Definition Utilities.hh:25
void ltrim(std::string &line)
Definition Utilities.hh:12
double RndmVoigt(const double mu, const double sigma, const double gamma)
Definition Random.hh:61
double RndmUniformPos()
Draw a random number uniformly distributed in the range (0, 1).
Definition Random.hh:17
void rtrim(std::string &line)
Definition Utilities.hh:18
DoubleAc cos(const DoubleAc &f)
Definition DoubleAc.cpp:432
DoubleAc pow(const DoubleAc &f, double p)
Definition DoubleAc.cpp:337
DoubleAc exp(const DoubleAc &f)
Definition DoubleAc.cpp:377
DoubleAc fabs(const DoubleAc &f)
Definition DoubleAc.h:615
DoubleAc sin(const DoubleAc &f)
Definition DoubleAc.cpp:384
DoubleAc asin(const DoubleAc &f)
Definition DoubleAc.cpp:470
DoubleAc sqrt(const DoubleAc &f)
Definition DoubleAc.cpp:314