Garfield++ 3.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 <cmath>
3#include <cstdio>
4#include <fstream>
5#include <iomanip>
6#include <iostream>
7#include <map>
8#include <numeric>
9
10#include <TMath.h>
11
17#include "Garfield/Random.hh"
18
19namespace {
20
21void PrintErrorMixer(const std::string& fcn) {
22 std::cerr << fcn << ": Error calculating the collision rates table.\n";
23}
24
25std::string GetDescription(const unsigned int index,
26 char scrpt[][Garfield::Magboltz::nCharDescr]) {
27 return std::string(scrpt[index],
28 scrpt[index] + Garfield::Magboltz::nCharDescr);
29}
30}
31
32namespace Garfield {
33
34const int MediumMagboltz::DxcTypeRad = 0;
35const int MediumMagboltz::DxcTypeCollIon = 1;
36const int MediumMagboltz::DxcTypeCollNonIon = -1;
37
39 : MediumGas(),
40 m_eMax(40.),
41 m_eStep(m_eMax / Magboltz::nEnergySteps),
42 m_eHigh(400.),
43 m_eHighLog(log(m_eHigh)),
44 m_lnStep(1.),
45 m_eFinalGamma(20.),
46 m_eStepGamma(m_eFinalGamma / nEnergyStepsGamma) {
47 m_className = "MediumMagboltz";
48
49 // Set physical constants in Magboltz common blocks.
50 Magboltz::cnsts_.echarg = ElementaryCharge * 1.e-15;
51 Magboltz::cnsts_.emass = ElectronMassGramme;
52 Magboltz::cnsts_.amu = AtomicMassUnit;
53 Magboltz::cnsts_.pir2 = BohrRadius * BohrRadius * Pi;
54 Magboltz::inpt_.ary = RydbergEnergy;
55
56 // Set parameters in Magboltz common blocks.
59 // Select the scattering model.
60 Magboltz::inpt_.nAniso = 2;
61 // Max. energy [eV]
62 Magboltz::inpt_.efinal = m_eMax;
63 // Energy step size [eV]
64 Magboltz::inpt_.estep = m_eStep;
65 // Temperature and pressure
66 Magboltz::inpt_.akt = BoltzmannConstant * m_temperature;
67 Magboltz::inpt_.tempc = m_temperature - ZeroCelsius;
69 // Disable Penning transfer.
70 Magboltz::inpt_.ipen = 0;
71
72 m_description.assign(Magboltz::nMaxLevels,
73 std::string(Magboltz::nCharDescr, ' '));
74
75 m_cfTot.assign(Magboltz::nEnergySteps, 0.);
76 m_cfTotLog.assign(nEnergyStepsLog, 0.);
77 m_cf.assign(Magboltz::nEnergySteps,
78 std::vector<double>(Magboltz::nMaxLevels, 0.));
79 m_cfLog.assign(nEnergyStepsLog,
80 std::vector<double>(Magboltz::nMaxLevels, 0.));
81
82 m_isChanged = true;
83
86 m_microscopic = true;
87
88 m_scaleExc.fill(1.);
89}
90
92 if (e <= Small) {
93 std::cerr << m_className << "::SetMaxElectronEnergy: Invalid energy.\n";
94 return false;
95 }
96 m_eMax = e;
97
98 // Determine the energy interval size.
99 m_eStep = std::min(m_eMax, m_eHigh) / Magboltz::nEnergySteps;
100
101 // Force recalculation of the scattering rates table.
102 m_isChanged = true;
103
104 return true;
105}
106
108 if (e <= Small) {
109 std::cerr << m_className << "::SetMaxPhotonEnergy: Invalid energy.\n";
110 return false;
111 }
112 m_eFinalGamma = e;
113
114 // Determine the energy interval size.
115 m_eStepGamma = m_eFinalGamma / nEnergyStepsGamma;
116
117 // Force recalculation of the scattering rates table.
118 m_isChanged = true;
119
120 return true;
121}
122
124 m_useOpalBeaty = true;
125 m_useGreenSawada = false;
126}
127
129 m_useOpalBeaty = false;
130 m_useGreenSawada = true;
131 if (m_isChanged) return;
132
133 bool allset = true;
134 for (unsigned int i = 0; i < m_nComponents; ++i) {
135 if (!m_hasGreenSawada[i]) {
136 if (allset) {
137 std::cout << m_className << "::SetSplittingFunctionGreenSawada:\n";
138 allset = false;
139 }
140 std::cout << " Fit parameters for " << m_gas[i] << " not available.\n"
141 << " Using Opal-Beaty formula instead.\n";
142 }
143 }
144}
145
147 m_useOpalBeaty = false;
148 m_useGreenSawada = false;
149}
150
152 if (m_usePenning) {
153 std::cout << m_className << "::EnableDeexcitation:\n"
154 << " Penning transfer will be switched off.\n";
155 }
156 // if (m_useRadTrap) {
157 // std::cout << " Radiation trapping is switched on.\n";
158 // } else {
159 // std::cout << " Radiation trapping is switched off.\n";
160 // }
161 m_usePenning = false;
162 m_useDeexcitation = true;
163 m_isChanged = true;
164 m_dxcProducts.clear();
165}
166
168 m_useRadTrap = true;
169 if (!m_useDeexcitation) {
170 std::cout << m_className << "::EnableRadiationTrapping:\n "
171 << "Radiation trapping is enabled but de-excitation is not.\n";
172 } else {
173 m_isChanged = true;
174 }
175}
176
178 const double lambda) {
179
180 if (!MediumGas::EnablePenningTransfer(r, lambda)) return false;
181
182 m_rPenning.fill(0.);
183 m_lambdaPenning.fill(0.);
184
185 // Make sure that the collision rate table is updated.
186 if (m_isChanged) {
187 if (!Mixer()) {
188 PrintErrorMixer(m_className + "::EnablePenningTransfer");
189 return false;
190 }
191 m_isChanged = false;
192 }
193 unsigned int nLevelsFound = 0;
194 for (unsigned int i = 0; i < m_nTerms; ++i) {
195 if (m_csType[i] % nCsTypes == ElectronCollisionTypeExcitation) {
196 ++nLevelsFound;
197 }
198 m_rPenning[i] = m_rPenningGlobal;
199 m_lambdaPenning[i] = m_lambdaPenningGlobal;
200 }
201
202 if (nLevelsFound > 0) {
203 std::cout << m_className << "::EnablePenningTransfer:\n "
204 << "Updated Penning transfer parameters for " << nLevelsFound
205 << " excitation cross-sections.\n";
206 if (nLevelsFound != m_excLevels.size() && !m_excLevels.empty()) {
207 std::cerr << m_className << "::EnablePenningTransfer:\n Warning: "
208 << "mismatch between number of excitation cross-sections ("
209 << nLevelsFound << ")\n and number of excitation rates in "
210 << "the gas table (" << m_excLevels.size() << ").\n "
211 << "The gas table was probably calculated using a different "
212 << "version of Magboltz.\n";
213 }
214 } else {
215 std::cerr << m_className << "::EnablePenningTransfer:\n "
216 << "No excitation cross-sections in the present energy range.\n";
217 }
218
219 if (m_useDeexcitation) {
220 std::cout << m_className << "::EnablePenningTransfer:\n "
221 << "Deexcitation handling will be switched off.\n";
222 }
223 m_usePenning = true;
224 return true;
225}
226
227bool MediumMagboltz::EnablePenningTransfer(const double r, const double lambda,
228 std::string gasname) {
229
230 if (!MediumGas::EnablePenningTransfer(r, lambda, gasname)) return false;
231
232 // Get (again) the "standard" name of this gas.
233 gasname = GetGasName(gasname);
234 if (gasname.empty()) return false;
235
236 // Look (again) for this gas in the present mixture.
237 int iGas = -1;
238 for (unsigned int i = 0; i < m_nComponents; ++i) {
239 if (m_gas[i] == gasname) {
240 iGas = i;
241 break;
242 }
243 }
244
245 // Make sure that the collision rate table is updated.
246 if (m_isChanged) {
247 if (!Mixer()) {
248 PrintErrorMixer(m_className + "::EnablePenningTransfer");
249 return false;
250 }
251 m_isChanged = false;
252 }
253 unsigned int nLevelsFound = 0;
254 for (unsigned int i = 0; i < m_nTerms; ++i) {
255 if (int(m_csType[i] / nCsTypes) != iGas) continue;
256 if (m_csType[i] % nCsTypes == ElectronCollisionTypeExcitation) {
257 ++nLevelsFound;
258 }
259 m_rPenning[i] = m_rPenningGas[iGas];
260 m_lambdaPenning[i] = m_lambdaPenningGas[iGas];
261 }
262
263 if (nLevelsFound > 0) {
264 std::cout << m_className << "::EnablePenningTransfer:\n"
265 << " Penning transfer parameters for " << nLevelsFound
266 << " excitation levels set to:\n"
267 << " r = " << m_rPenningGas[iGas] << "\n"
268 << " lambda = " << m_lambdaPenningGas[iGas] << " cm\n";
269 } else {
270 std::cerr << m_className << "::EnablePenningTransfer:\n"
271 << " Specified gas (" << gasname
272 << ") has no excitation levels in the present energy range.\n";
273 }
274
275 m_usePenning = true;
276 return true;
277}
278
280
282 m_rPenning.fill(0.);
283 m_lambdaPenning.fill(0.);
284
285 m_usePenning = false;
286}
287
288bool MediumMagboltz::DisablePenningTransfer(std::string gasname) {
289
290 if (!MediumGas::DisablePenningTransfer(gasname)) return false;
291 // Get the "standard" name of this gas.
292 gasname = GetGasName(gasname);
293 if (gasname.empty()) return false;
294
295 // Look (again) for this gas in the present gas mixture.
296 int iGas = -1;
297 for (unsigned int i = 0; i < m_nComponents; ++i) {
298 if (m_gas[i] == gasname) {
299 iGas = i;
300 break;
301 }
302 }
303
304 if (iGas < 0) return false;
305
306 unsigned int nLevelsFound = 0;
307 for (unsigned int i = 0; i < m_nTerms; ++i) {
308 if (int(m_csType[i] / nCsTypes) == iGas) {
309 m_rPenning[i] = 0.;
310 m_lambdaPenning[i] = 0.;
311 } else {
312 if (m_csType[i] % nCsTypes == ElectronCollisionTypeExcitation &&
313 m_rPenning[i] > Small) {
314 ++nLevelsFound;
315 }
316 }
317 }
318
319 if (nLevelsFound == 0) {
320 // There are no more excitation levels with r > 0.
321 std::cout << m_className << "::DisablePenningTransfer:\n"
322 << " Penning transfer switched off for all excitations.\n";
323 m_usePenning = false;
324 }
325 return true;
326}
327
328void MediumMagboltz::SetExcitationScaling(const double r, std::string gasname) {
329 if (r <= 0.) {
330 std::cerr << m_className << "::SetExcitationScaling: Incorrect value.\n";
331 return;
332 }
333
334 // Get the "standard" name of this gas.
335 gasname = GetGasName(gasname);
336 if (gasname.empty()) {
337 std::cerr << m_className << "::SetExcitationScaling: Unknown gas name.\n";
338 return;
339 }
340
341 // Look for this gas in the present gas mixture.
342 bool found = false;
343 for (unsigned int i = 0; i < m_nComponents; ++i) {
344 if (m_gas[i] == gasname) {
345 m_scaleExc[i] = r;
346 found = true;
347 break;
348 }
349 }
350
351 if (!found) {
352 std::cerr << m_className << "::SetExcitationScaling:\n"
353 << " Specified gas (" << gasname
354 << ") is not part of the present gas mixture.\n";
355 return;
356 }
357
358 // Make sure that the collision rate table is updated.
359 m_isChanged = true;
360}
361
362bool MediumMagboltz::Initialise(const bool verbose) {
363 if (!m_isChanged) {
364 if (m_debug) {
365 std::cerr << m_className << "::Initialise: Nothing changed.\n";
366 }
367 return true;
368 }
369 if (!Mixer(verbose)) {
370 PrintErrorMixer(m_className + "::Initialise");
371 return false;
372 }
373 m_isChanged = false;
374 return true;
375}
376
379
380 if (m_isChanged) {
381 if (!Initialise()) return;
382 }
383
384 std::cout << " Electron cross-sections:\n";
385 int igas = -1;
386 for (unsigned int i = 0; i < m_nTerms; ++i) {
387 // Collision type
388 int type = m_csType[i] % nCsTypes;
389 if (igas != int(m_csType[i] / nCsTypes)) {
390 igas = int(m_csType[i] / nCsTypes);
391 std::cout << " " << m_gas[igas] << "\n";
392 }
393 // Description (from Magboltz)
394 // Threshold energy
395 double e = m_rgas[igas] * m_energyLoss[i];
396 std::cout << " Level " << i << ": " << m_description[i] << "\n";
397 std::cout << " Type " << type;
398 if (type == ElectronCollisionTypeElastic) {
399 std::cout << " (elastic)\n";
400 } else if (type == ElectronCollisionTypeIonisation) {
401 std::cout << " (ionisation). Ionisation threshold: " << e << " eV.\n";
402 } else if (type == ElectronCollisionTypeAttachment) {
403 std::cout << " (attachment)\n";
404 } else if (type == ElectronCollisionTypeInelastic) {
405 std::cout << " (inelastic). Energy loss: " << e << " eV.\n";
406 } else if (type == ElectronCollisionTypeExcitation) {
407 std::cout << " (excitation). Excitation energy: " << e << " eV.\n";
408 } else if (type == ElectronCollisionTypeSuperelastic) {
409 std::cout << " (super-elastic). Energy gain: " << -e << " eV.\n";
410 } else if (type == ElectronCollisionTypeVirtual) {
411 std::cout << " (virtual)\n";
412 } else {
413 std::cout << " (unknown)\n";
414 }
415 if (type == ElectronCollisionTypeExcitation && m_usePenning &&
416 e > m_minIonPot) {
417 std::cout << " Penning transfer coefficient: "
418 << m_rPenning[i] << "\n";
419 } else if (type == ElectronCollisionTypeExcitation && m_useDeexcitation) {
420 const int idxc = m_iDeexcitation[i];
421 if (idxc < 0 || idxc >= (int)m_deexcitations.size()) {
422 std::cout << " Deexcitation cascade not implemented.\n";
423 continue;
424 }
425 const auto& dxc = m_deexcitations[idxc];
426 if (dxc.osc > 0.) {
427 std::cout << " Oscillator strength: " << dxc.osc << "\n";
428 }
429 std::cout << " Decay channels:\n";
430 const int nChannels = dxc.type.size();
431 for (int j = 0; j < nChannels; ++j) {
432 if (dxc.type[j] == DxcTypeRad) {
433 std::cout << " Radiative decay to ";
434 if (dxc.final[j] < 0) {
435 std::cout << "ground state: ";
436 } else {
437 std::cout << m_deexcitations[dxc.final[j]].label << ": ";
438 }
439 } else if (dxc.type[j] == DxcTypeCollIon) {
440 if (dxc.final[j] < 0) {
441 std::cout << " Penning ionisation: ";
442 } else {
443 std::cout << " Associative ionisation: ";
444 }
445 } else if (dxc.type[j] == DxcTypeCollNonIon) {
446 if (dxc.final[j] >= 0) {
447 std::cout << " Collision-induced transition to "
448 << m_deexcitations[dxc.final[j]].label << ": ";
449 } else {
450 std::cout << " Loss: ";
451 }
452 }
453 const double br = j == 0 ? dxc.p[j] : dxc.p[j] - dxc.p[j - 1];
454 std::cout << std::setprecision(5) << br * 100. << "%\n";
455 }
456 }
457 }
458}
459
461 // If necessary, update the collision rates table.
462 if (m_isChanged) {
463 if (!Mixer()) {
464 PrintErrorMixer(m_className + "::GetElectronNullCollisionRate");
465 return 0.;
466 }
467 m_isChanged = false;
468 }
469
470 if (m_debug && band > 0) {
471 std::cerr << m_className << "::GetElectronNullCollisionRate: Band > 0.\n";
472 }
473
474 return m_cfNull;
475}
476
478 const int band) {
479 // Check if the electron energy is within the currently set range.
480 if (e <= 0.) {
481 std::cerr << m_className << "::GetElectronCollisionRate: Invalid energy.\n";
482 return m_cfTot[0];
483 }
484 if (e > m_eMax && m_useAutoAdjust) {
485 std::cerr << m_className << "::GetElectronCollisionRate:\n Rate at " << e
486 << " eV is not included in the current table.\n "
487 << "Increasing energy range to " << 1.05 * e << " eV.\n";
488 SetMaxElectronEnergy(1.05 * e);
489 }
490
491 // If necessary, update the collision rates table.
492 if (m_isChanged) {
493 if (!Mixer()) {
494 PrintErrorMixer(m_className + "::GetElectronCollisionRate");
495 return 0.;
496 }
497 m_isChanged = false;
498 }
499
500 if (m_debug && band > 0) {
501 std::cerr << m_className << "::GetElectronCollisionRate: Band > 0.\n";
502 }
503
504 // Get the energy interval.
505 if (e <= m_eHigh) {
506 // Linear binning
507 constexpr int iemax = Magboltz::nEnergySteps - 1;
508 const int iE = std::min(std::max(int(e / m_eStep), 0), iemax);
509 return m_cfTot[iE];
510 }
511
512 // Logarithmic binning
513 const double eLog = log(e);
514 int iE = int((eLog - m_eHighLog) / m_lnStep);
515 // Calculate the collision rate by log-log interpolation.
516 const double fmax = m_cfTotLog[iE];
517 const double fmin = iE == 0 ? log(m_cfTot.back()) : m_cfTotLog[iE - 1];
518 const double emin = m_eHighLog + iE * m_lnStep;
519 const double f = fmin + (eLog - emin) * (fmax - fmin) / m_lnStep;
520 return exp(f);
521}
522
524 const unsigned int level,
525 const int band) {
526 // Check if the electron energy is within the currently set range.
527 if (e <= 0.) {
528 std::cerr << m_className << "::GetElectronCollisionRate: Invalid energy.\n";
529 return 0.;
530 }
531
532 // Check if the level exists.
533 if (level >= m_nTerms) {
534 std::cerr << m_className << "::GetElectronCollisionRate: Invalid level.\n";
535 return 0.;
536 }
537
538 // Get the total scattering rate.
539 double rate = GetElectronCollisionRate(e, band);
540 // Get the energy interval.
541 if (e <= m_eHigh) {
542 // Linear binning
543 constexpr int iemax = Magboltz::nEnergySteps - 1;
544 const int iE = std::min(std::max(int(e / m_eStep), 0), iemax);
545 if (level == 0) {
546 rate *= m_cf[iE][0];
547 } else {
548 rate *= m_cf[iE][level] - m_cf[iE][level - 1];
549 }
550 } else {
551 // Logarithmic binning
552 const int iE = int((log(e) - m_eHighLog) / m_lnStep);
553 if (level == 0) {
554 rate *= m_cfLog[iE][0];
555 } else {
556 rate *= m_cfLog[iE][level] - m_cfLog[iE][level - 1];
557 }
558 }
559 return rate;
560}
561
563 const double e, int& type, int& level, double& e1, double& dx, double& dy,
564 double& dz, std::vector<std::pair<int, double> >& secondaries, int& ndxc,
565 int& band) {
566 ndxc = 0;
567 if (e <= 0.) {
568 std::cerr << m_className << "::GetElectronCollision: Invalid energy.\n";
569 return false;
570 }
571 // Check if the electron energy is within the currently set range.
572 if (e > m_eMax && m_useAutoAdjust) {
573 std::cerr << m_className << "::GetElectronCollision:\n Provided energy ("
574 << e << " eV) exceeds current energy range.\n"
575 << " Increasing energy range to " << 1.05 * e << " eV.\n";
576 SetMaxElectronEnergy(1.05 * e);
577 }
578
579 // If necessary, update the collision rates table.
580 if (m_isChanged) {
581 if (!Mixer()) {
582 PrintErrorMixer(m_className + "::GetElectronCollision");
583 return false;
584 }
585 m_isChanged = false;
586 }
587
588 if (m_debug && band > 0) {
589 std::cerr << m_className << "::GetElectronCollision: Band > 0.\n";
590 }
591
592 double angCut = 1.;
593 double angPar = 0.5;
594
595 if (e <= m_eHigh) {
596 // Linear binning
597 // Get the energy interval.
598 constexpr int iemax = Magboltz::nEnergySteps - 1;
599 const int iE = std::min(std::max(int(e / m_eStep), 0), iemax);
600
601 // Sample the scattering process.
602 const double r = RndmUniform();
603 if (r <= m_cf[iE][0]) {
604 level = 0;
605 } else if (r >= m_cf[iE][m_nTerms - 1]) {
606 level = m_nTerms - 1;
607 } else {
608 const auto begin = m_cf[iE].cbegin();
609 level = std::lower_bound(begin, begin + m_nTerms, r) - begin;
610 }
611 // Get the angular distribution parameters.
612 angCut = m_scatCut[iE][level];
613 angPar = m_scatPar[iE][level];
614 } else {
615 // Logarithmic binning
616 // Get the energy interval.
617 const int iE = std::min(std::max(int(log(e / m_eHigh) / m_lnStep), 0),
618 nEnergyStepsLog - 1);
619 // Sample the scattering process.
620 const double r = RndmUniform();
621 if (r <= m_cfLog[iE][0]) {
622 level = 0;
623 } else if (r >= m_cfLog[iE][m_nTerms - 1]) {
624 level = m_nTerms - 1;
625 } else {
626 const auto begin = m_cfLog[iE].cbegin();
627 level = std::lower_bound(begin, begin + m_nTerms, r) - begin;
628 }
629 // Get the angular distribution parameters.
630 angCut = m_scatCutLog[iE][level];
631 angPar = m_scatParLog[iE][level];
632 }
633
634 // Extract the collision type.
635 type = m_csType[level] % nCsTypes;
636 const int igas = int(m_csType[level] / nCsTypes);
637 // Increase the collision counters.
638 ++m_nCollisions[type];
639 ++m_nCollisionsDetailed[level];
640
641 // Get the energy loss for this process.
642 double loss = m_energyLoss[level];
643
644 if (type == ElectronCollisionTypeVirtual) return true;
645
646 if (type == ElectronCollisionTypeIonisation) {
647 // Sample the secondary electron energy according to
648 // the Opal-Beaty-Peterson parameterisation.
649 double esec = 0.;
650 if (e < loss) loss = e - 0.0001;
651 if (m_useOpalBeaty) {
652 // Get the splitting parameter.
653 const double w = m_wOpalBeaty[level];
654 esec = w * tan(RndmUniform() * atan(0.5 * (e - loss) / w));
655 // Rescaling (SST)
656 // esec = w * pow(esec / w, 0.9524);
657 } else if (m_useGreenSawada) {
658 const double gs = m_parGreenSawada[igas][0];
659 const double gb = m_parGreenSawada[igas][1];
660 const double w = gs * e / (e + gb);
661 const double ts = m_parGreenSawada[igas][2];
662 const double ta = m_parGreenSawada[igas][3];
663 const double tb = m_parGreenSawada[igas][4];
664 const double esec0 = ts - ta / (e + tb);
665 const double r = RndmUniform();
666 esec = esec0 +
667 w * tan((r - 1.) * atan(esec0 / w) +
668 r * atan((0.5 * (e - loss) - esec0) / w));
669 } else {
670 esec = RndmUniform() * (e - loss);
671 }
672 if (esec <= 0) esec = Small;
673 loss += esec;
674 // Add the secondary electron.
675 secondaries.emplace_back(std::make_pair(IonProdTypeElectron, esec));
676 // Add the ion.
677 secondaries.emplace_back(std::make_pair(IonProdTypeIon, 0.));
678 bool fluorescence = false;
679 if (m_yFluorescence[level] > Small) {
680 if (RndmUniform() < m_yFluorescence[level]) fluorescence = true;
681 }
682 // Add Auger and photo electrons (if any).
683 if (fluorescence) {
684 if (m_nAuger2[level] > 0) {
685 const double eav = m_eAuger2[level] / m_nAuger2[level];
686 for (unsigned int i = 0; i < m_nAuger2[level]; ++i) {
687 secondaries.emplace_back(std::make_pair(IonProdTypeElectron, eav));
688 }
689 }
690 if (m_nFluorescence[level] > 0) {
691 const double eav = m_eFluorescence[level] / m_nFluorescence[level];
692 for (unsigned int i = 0; i < m_nFluorescence[level]; ++i) {
693 secondaries.emplace_back(std::make_pair(IonProdTypeElectron, eav));
694 }
695 }
696 } else if (m_nAuger1[level] > 0) {
697 const double eav = m_eAuger1[level] / m_nAuger1[level];
698 for (unsigned int i = 0; i < m_nAuger1[level]; ++i) {
699 secondaries.emplace_back(std::make_pair(IonProdTypeElectron, eav));
700 }
701 }
702 } else if (type == ElectronCollisionTypeExcitation) {
703 // Follow the de-excitation cascade (if switched on).
704 if (m_useDeexcitation && m_iDeexcitation[level] >= 0) {
705 int fLevel = 0;
706 ComputeDeexcitationInternal(m_iDeexcitation[level], fLevel);
707 ndxc = m_dxcProducts.size();
708 } else if (m_usePenning) {
709 m_dxcProducts.clear();
710 // Simplified treatment of Penning ionisation.
711 // If the energy threshold of this level exceeds the
712 // ionisation potential of one of the gases,
713 // create a new electron (with probability rPenning).
714 if (m_debug) {
715 std::cout << m_className << "::GetElectronCollision:\n"
716 << " Level: " << level << "\n"
717 << " Ionization potential: " << m_minIonPot << "\n"
718 << " Excitation energy: " << loss * m_rgas[igas] << "\n"
719 << " Penning probability: " << m_rPenning[level] << "\n";
720 }
721 if (loss * m_rgas[igas] > m_minIonPot &&
722 RndmUniform() < m_rPenning[level]) {
723 // The energy of the secondary electron is assumed to be given by
724 // the difference of excitation and ionisation threshold.
725 double esec = loss * m_rgas[igas] - m_minIonPot;
726 if (esec <= 0) esec = Small;
727 // Add the secondary electron to the list.
728 dxcProd newDxcProd;
729 newDxcProd.t = 0.;
730 newDxcProd.s = 0.;
731 if (m_lambdaPenning[level] > Small) {
732 // Uniform distribution within a sphere of radius lambda
733 newDxcProd.s = m_lambdaPenning[level] * std::cbrt(RndmUniformPos());
734 }
735 newDxcProd.energy = esec;
736 newDxcProd.type = DxcProdTypeElectron;
737 m_dxcProducts.push_back(std::move(newDxcProd));
738 ndxc = 1;
739 ++m_nPenning;
740 }
741 }
742 }
743
744 if (e < loss) loss = e - 0.0001;
745
746 // Determine the scattering angle.
747 double ctheta0 = 1. - 2. * RndmUniform();
748 if (m_useAnisotropic) {
749 switch (m_scatModel[level]) {
750 case 0:
751 break;
752 case 1:
753 ctheta0 = 1. - RndmUniform() * angCut;
754 if (RndmUniform() > angPar) ctheta0 = -ctheta0;
755 break;
756 case 2:
757 ctheta0 = (ctheta0 + angPar) / (1. + angPar * ctheta0);
758 break;
759 default:
760 std::cerr << m_className << "::GetElectronCollision:\n"
761 << " Unknown scattering model.\n"
762 << " Using isotropic distribution.\n";
763 break;
764 }
765 }
766
767 const double s1 = m_rgas[igas];
768 const double s2 = (s1 * s1) / (s1 - 1.);
769 const double theta0 = acos(ctheta0);
770 const double arg = std::max(1. - s1 * loss / e, Small);
771 const double d = 1. - ctheta0 * sqrt(arg);
772
773 // Update the energy.
774 e1 = std::max(e * (1. - loss / (s1 * e) - 2. * d / s2), Small);
775 double q = std::min(sqrt((e / e1) * arg) / s1, 1.);
776 const double theta = asin(q * sin(theta0));
777 double ctheta = cos(theta);
778 if (ctheta0 < 0.) {
779 const double u = (s1 - 1.) * (s1 - 1.) / arg;
780 if (ctheta0 * ctheta0 > u) ctheta = -ctheta;
781 }
782 const double stheta = sin(theta);
783 // Calculate the direction after the collision.
784 dz = std::min(dz, 1.);
785 const double argZ = sqrt(dx * dx + dy * dy);
786
787 // Azimuth is chosen at random.
788 const double phi = TwoPi * RndmUniform();
789 const double cphi = cos(phi);
790 const double sphi = sin(phi);
791 if (argZ == 0.) {
792 dz = ctheta;
793 dx = cphi * stheta;
794 dy = sphi * stheta;
795 } else {
796 const double a = stheta / argZ;
797 const double dz1 = dz * ctheta + argZ * stheta * sphi;
798 const double dy1 = dy * ctheta + a * (dx * cphi - dy * dz * sphi);
799 const double dx1 = dx * ctheta - a * (dy * cphi + dx * dz * sphi);
800 dz = dz1;
801 dy = dy1;
802 dx = dx1;
803 }
804
805 return true;
806}
807
808bool MediumMagboltz::GetDeexcitationProduct(const unsigned int i, double& t,
809 double& s, int& type,
810 double& energy) const {
811 if (i >= m_dxcProducts.size() || !(m_useDeexcitation || m_usePenning)) {
812 return false;
813 }
814 t = m_dxcProducts[i].t;
815 s = m_dxcProducts[i].s;
816 type = m_dxcProducts[i].type;
817 energy = m_dxcProducts[i].energy;
818 return true;
819}
820
822 if (e <= 0.) {
823 std::cerr << m_className << "::GetPhotonCollisionRate: Invalid energy.\n";
824 return m_cfTotGamma[0];
825 }
826 if (e > m_eFinalGamma && m_useAutoAdjust) {
827 std::cerr << m_className << "::GetPhotonCollisionRate:\n Rate at " << e
828 << " eV is not included in the current table.\n"
829 << " Increasing energy range to " << 1.05 * e << " eV.\n";
830 SetMaxPhotonEnergy(1.05 * e);
831 }
832
833 if (m_isChanged) {
834 if (!Mixer()) {
835 PrintErrorMixer(m_className + "::GetPhotonCollisionRate");
836 return 0.;
837 }
838 m_isChanged = false;
839 }
840
841 const int iE =
842 std::min(std::max(int(e / m_eStepGamma), 0), nEnergyStepsGamma - 1);
843
844 double cfSum = m_cfTotGamma[iE];
845 if (m_useDeexcitation && m_useRadTrap && !m_deexcitations.empty()) {
846 // Loop over the excitations.
847 for (const auto& dxc : m_deexcitations) {
848 if (dxc.cf > 0. && fabs(e - dxc.energy) <= dxc.width) {
849 cfSum += dxc.cf *
850 TMath::Voigt(e - dxc.energy, dxc.sDoppler, 2 * dxc.gPressure);
851 }
852 }
853 }
854
855 return cfSum;
856}
857
858bool MediumMagboltz::GetPhotonCollision(const double e, int& type, int& level,
859 double& e1, double& ctheta, int& nsec,
860 double& esec) {
861 if (e <= 0.) {
862 std::cerr << m_className << "::GetPhotonCollision: Invalid energy.\n";
863 return false;
864 }
865 if (e > m_eFinalGamma && m_useAutoAdjust) {
866 std::cerr << m_className << "::GetPhotonCollision:\n Provided energy ("
867 << e << " eV) exceeds current energy range.\n"
868 << " Increasing energy range to " << 1.05 * e << " eV.\n";
869 SetMaxPhotonEnergy(1.05 * e);
870 }
871
872 if (m_isChanged) {
873 if (!Mixer()) {
874 PrintErrorMixer(m_className + "::GetPhotonCollision");
875 return false;
876 }
877 m_isChanged = false;
878 }
879
880 // Energy interval
881 const int iE =
882 std::min(std::max(int(e / m_eStepGamma), 0), nEnergyStepsGamma - 1);
883
884 double r = m_cfTotGamma[iE];
885 if (m_useDeexcitation && m_useRadTrap && !m_deexcitations.empty()) {
886 int nLines = 0;
887 std::vector<double> pLine(0);
888 std::vector<int> iLine(0);
889 // Loop over the excitations.
890 const unsigned int nDeexcitations = m_deexcitations.size();
891 for (unsigned int i = 0; i < nDeexcitations; ++i) {
892 const auto& dxc = m_deexcitations[i];
893 if (dxc.cf > 0. && fabs(e - dxc.energy) <= dxc.width) {
894 r += dxc.cf *
895 TMath::Voigt(e - dxc.energy, dxc.sDoppler, 2 * dxc.gPressure);
896 pLine.push_back(r);
897 iLine.push_back(i);
898 ++nLines;
899 }
900 }
901 r *= RndmUniform();
902 if (nLines > 0 && r >= m_cfTotGamma[iE]) {
903 // Photon is absorbed by a discrete line.
904 for (int i = 0; i < nLines; ++i) {
905 if (r <= pLine[i]) {
906 ++m_nPhotonCollisions[PhotonCollisionTypeExcitation];
907 int fLevel = 0;
908 ComputeDeexcitationInternal(iLine[i], fLevel);
909 type = PhotonCollisionTypeExcitation;
910 nsec = m_dxcProducts.size();
911 return true;
912 }
913 }
914 std::cerr << m_className << "::GetPhotonCollision:\n";
915 std::cerr << " Random sampling of deexcitation line failed.\n";
916 std::cerr << " Program bug!\n";
917 return false;
918 }
919 } else {
920 r *= RndmUniform();
921 }
922
923 if (r <= m_cfGamma[iE][0]) {
924 level = 0;
925 } else if (r >= m_cfGamma[iE][m_nPhotonTerms - 1]) {
926 level = m_nPhotonTerms - 1;
927 } else {
928 const auto begin = m_cfGamma[iE].cbegin();
929 level = std::lower_bound(begin, begin + m_nPhotonTerms, r) - begin;
930 }
931
932 nsec = 0;
933 esec = e1 = 0.;
934 type = csTypeGamma[level];
935 // Collision type
936 type = type % nCsTypesGamma;
937 int ngas = int(csTypeGamma[level] / nCsTypesGamma);
938 ++m_nPhotonCollisions[type];
939 // Ionising collision
940 if (type == 1) {
941 esec = std::max(e - m_ionPot[ngas], Small);
942 nsec = 1;
943 }
944
945 // Determine the scattering angle
946 ctheta = 2 * RndmUniform() - 1.;
947
948 return true;
949}
950
952 m_nCollisions.fill(0);
953 m_nCollisionsDetailed.assign(m_nTerms, 0);
954 m_nPenning = 0;
955 m_nPhotonCollisions.fill(0);
956}
957
959 return std::accumulate(std::begin(m_nCollisions), std::end(m_nCollisions), 0);
960}
961
963 unsigned int& nElastic, unsigned int& nIonisation,
964 unsigned int& nAttachment, unsigned int& nInelastic,
965 unsigned int& nExcitation, unsigned int& nSuperelastic) const {
966 nElastic = m_nCollisions[ElectronCollisionTypeElastic];
967 nIonisation = m_nCollisions[ElectronCollisionTypeIonisation];
968 nAttachment = m_nCollisions[ElectronCollisionTypeAttachment];
969 nInelastic = m_nCollisions[ElectronCollisionTypeInelastic];
970 nExcitation = m_nCollisions[ElectronCollisionTypeExcitation];
971 nSuperelastic = m_nCollisions[ElectronCollisionTypeSuperelastic];
972 return nElastic + nIonisation + nAttachment + nInelastic + nExcitation +
973 nSuperelastic;
974}
975
977 if (m_isChanged) {
978 if (!Mixer()) {
979 PrintErrorMixer(m_className + "::GetNumberOfLevels");
980 return 0;
981 }
982 m_isChanged = false;
983 }
984
985 return m_nTerms;
986}
987
988bool MediumMagboltz::GetLevel(const unsigned int i, int& ngas, int& type,
989 std::string& descr, double& e) {
990 if (m_isChanged) {
991 if (!Mixer()) {
992 PrintErrorMixer(m_className + "::GetLevel");
993 return false;
994 }
995 m_isChanged = false;
996 }
997
998 if (i >= m_nTerms) {
999 std::cerr << m_className << "::GetLevel: Index out of range.\n";
1000 return false;
1001 }
1002
1003 // Collision type
1004 type = m_csType[i] % nCsTypes;
1005 ngas = int(m_csType[i] / nCsTypes);
1006 // Description (from Magboltz)
1007 descr = m_description[i];
1008 // Threshold energy
1009 e = m_rgas[ngas] * m_energyLoss[i];
1010 if (m_debug) {
1011 std::cout << m_className << "::GetLevel:\n"
1012 << " Level " << i << ": " << descr << "\n"
1013 << " Type " << type << "\n"
1014 << " Threshold energy: " << e << " eV\n";
1015 if (type == ElectronCollisionTypeExcitation && m_usePenning &&
1016 e > m_minIonPot) {
1017 std::cout << " Penning transfer coefficient: " << m_rPenning[i]
1018 << "\n";
1019 } else if (type == ElectronCollisionTypeExcitation && m_useDeexcitation) {
1020 const int idxc = m_iDeexcitation[i];
1021 if (idxc < 0 || idxc >= (int)m_deexcitations.size()) {
1022 std::cout << " Deexcitation cascade not implemented.\n";
1023 return true;
1024 }
1025 const auto& dxc = m_deexcitations[idxc];
1026 if (dxc.osc > 0.) {
1027 std::cout << " Oscillator strength: " << dxc.osc << "\n";
1028 }
1029 std::cout << " Decay channels:\n";
1030 const int nChannels = dxc.type.size();
1031 for (int j = 0; j < nChannels; ++j) {
1032 if (dxc.type[j] == DxcTypeRad) {
1033 std::cout << " Radiative decay to ";
1034 if (dxc.final[j] < 0) {
1035 std::cout << "ground state: ";
1036 } else {
1037 std::cout << m_deexcitations[dxc.final[j]].label << ": ";
1038 }
1039 } else if (dxc.type[j] == DxcTypeCollIon) {
1040 if (dxc.final[j] < 0) {
1041 std::cout << " Penning ionisation: ";
1042 } else {
1043 std::cout << " Associative ionisation: ";
1044 }
1045 } else if (dxc.type[j] == DxcTypeCollNonIon) {
1046 if (dxc.final[j] >= 0) {
1047 std::cout << " Collision-induced transition to "
1048 << m_deexcitations[dxc.final[j]].label << ": ";
1049 } else {
1050 std::cout << " Loss: ";
1051 }
1052 }
1053 const double br = j == 0 ? dxc.p[j] : dxc.p[j] - dxc.p[j - 1];
1054 std::cout << std::setprecision(5) << br * 100. << "%\n";
1055 }
1056 }
1057 }
1058
1059 return true;
1060}
1061
1063 const unsigned int level) const {
1064 if (level >= m_nTerms) {
1065 std::cerr << m_className << "::GetNumberOfElectronCollisions: "
1066 << "Level " << level << " does not exist.\n";
1067 return 0;
1068 }
1069 return m_nCollisionsDetailed[level];
1070}
1071
1073 return std::accumulate(std::begin(m_nPhotonCollisions),
1074 std::end(m_nPhotonCollisions), 0);
1075}
1076
1078 unsigned int& nElastic, unsigned int& nIonising,
1079 unsigned int& nInelastic) const {
1080 nElastic = m_nPhotonCollisions[0];
1081 nIonising = m_nPhotonCollisions[1];
1082 nInelastic = m_nPhotonCollisions[2];
1083 return nElastic + nIonising + nInelastic;
1084}
1085
1086int MediumMagboltz::GetGasNumberMagboltz(const std::string& input) const {
1087
1088 if (input.empty()) return 0;
1089
1090 if (input == "CF4") {
1091 return 1;
1092 } else if (input == "Ar") {
1093 return 2;
1094 } else if (input == "He" || input == "He-4") {
1095 // Helium 4
1096 return 3;
1097 } else if (input == "He-3") {
1098 // Helium 3
1099 return 4;
1100 } else if (input == "Ne") {
1101 return 5;
1102 } else if (input == "Kr") {
1103 return 6;
1104 } else if (input == "Xe") {
1105 return 7;
1106 } else if (input == "CH4") {
1107 // Methane
1108 return 8;
1109 } else if (input == "C2H6") {
1110 // Ethane
1111 return 9;
1112 } else if (input == "C3H8") {
1113 // Propane
1114 return 10;
1115 } else if (input == "iC4H10") {
1116 // Isobutane
1117 return 11;
1118 } else if (input == "CO2") {
1119 return 12;
1120 } else if (input == "neoC5H12") {
1121 // Neopentane
1122 return 13;
1123 } else if (input == "H2O") {
1124 return 14;
1125 } else if (input == "O2") {
1126 return 15;
1127 } else if (input == "N2") {
1128 return 16;
1129 } else if (input == "NO") {
1130 // Nitric oxide (NO)
1131 return 17;
1132 } else if (input == "N2O") {
1133 // Nitrous oxide (N2O)
1134 return 18;
1135 } else if (input == "C2H4") {
1136 // Ethene (C2H4)
1137 return 19;
1138 } else if (input == "C2H2") {
1139 // Acetylene (C2H2)
1140 return 20;
1141 } else if (input == "H2") {
1142 // Hydrogen
1143 return 21;
1144 } else if (input == "D2") {
1145 // Deuterium
1146 return 22;
1147 } else if (input == "CO") {
1148 // Carbon monoxide (CO)
1149 return 23;
1150 } else if (input == "Methylal") {
1151 // Methylal (dimethoxymethane, CH3-O-CH2-O-CH3, "hot" version)
1152 return 24;
1153 } else if (input == "DME") {
1154 return 25;
1155 } else if (input == "Reid-Step") {
1156 return 26;
1157 } else if (input == "Maxwell-Model") {
1158 return 27;
1159 } else if (input == "Reid-Ramp") {
1160 return 28;
1161 } else if (input == "C2F6") {
1162 return 29;
1163 } else if (input == "SF6") {
1164 return 30;
1165 } else if (input == "NH3") {
1166 return 31;
1167 } else if (input == "C3H6") {
1168 // Propene
1169 return 32;
1170 } else if (input == "cC3H6") {
1171 // Cyclopropane
1172 return 33;
1173 } else if (input == "CH3OH") {
1174 // Methanol
1175 return 34;
1176 } else if (input == "C2H5OH") {
1177 // Ethanol
1178 return 35;
1179 } else if (input == "C3H7OH") {
1180 // Propanol
1181 return 36;
1182 } else if (input == "Cs") {
1183 return 37;
1184 } else if (input == "F2") {
1185 // Fluorine
1186 return 38;
1187 } else if (input == "CS2") {
1188 return 39;
1189 } else if (input == "COS") {
1190 return 40;
1191 } else if (input == "CD4") {
1192 // Deuterated methane
1193 return 41;
1194 } else if (input == "BF3") {
1195 return 42;
1196 } else if (input == "C2HF5" || input == "C2H2F4") {
1197 return 43;
1198 } else if (input == "TMA") {
1199 return 44;
1200 } else if (input == "nC3H7OH") {
1201 // n-propanol
1202 return 46;
1203 } else if (input == "paraH2") {
1204 // Para hydrogen
1205 return 47;
1206 } else if (input == "orthoD2") {
1207 // Ortho deuterium
1208 return 48;
1209 } else if (input == "CHF3") {
1210 return 50;
1211 } else if (input == "CF3Br") {
1212 return 51;
1213 } else if (input == "C3F8") {
1214 return 52;
1215 } else if (input == "O3") {
1216 // Ozone
1217 return 53;
1218 } else if (input == "Hg") {
1219 // Mercury
1220 return 54;
1221 } else if (input == "H2S") {
1222 return 55;
1223 } else if (input == "nC4H10") {
1224 // n-Butane
1225 return 56;
1226 } else if (input == "nC5H12") {
1227 // n-Pentane
1228 return 57;
1229 } else if (input == "N2 (Phelps)") {
1230 return 58;
1231 } else if (input == "GeH4") {
1232 // Germane, GeH4
1233 return 59;
1234 } else if (input == "SiH4") {
1235 // Silane, SiH4
1236 return 60;
1237 }
1238
1239 std::cerr << m_className << "::GetGasNumberMagboltz:\n"
1240 << " Gas " << input << " is not defined.\n";
1241 return 0;
1242}
1243
1244bool MediumMagboltz::Mixer(const bool verbose) {
1245 // Set constants and parameters in Magboltz common blocks.
1246 Magboltz::cnsts_.echarg = ElementaryCharge * 1.e-15;
1247 Magboltz::cnsts_.emass = ElectronMassGramme;
1248 Magboltz::cnsts_.amu = AtomicMassUnit;
1249 Magboltz::cnsts_.pir2 = BohrRadius * BohrRadius * Pi;
1250 Magboltz::inpt_.ary = RydbergEnergy;
1251
1252 Magboltz::inpt_.akt = BoltzmannConstant * m_temperature;
1253 Magboltz::inpt_.tempc = m_temperature - ZeroCelsius;
1255
1258 Magboltz::inpt_.nAniso = m_useAnisotropic ? 2 : 0;
1259
1260 for (unsigned int i = 0; i < Magboltz::nEnergySteps; ++i) {
1261 const double en = (i + 0.5) * m_eStep;
1262 Magboltz::mix2_.eg[i] = en;
1263 Magboltz::mix2_.eroot[i] = sqrt(en);
1264 Magboltz::dens_.den[i] = 0.;
1265 }
1266 constexpr int iemax = Magboltz::nEnergySteps - 1;
1267
1268 // Calculate the atomic density (ideal gas law).
1269 const double dens = GetNumberDensity();
1270 // Prefactor for calculation of scattering rate from cross-section.
1271 const double prefactor = dens * SpeedOfLight * sqrt(2. / ElectronMass);
1272
1273 m_rgas.fill(1.);
1274
1275 m_ionPot.fill(-1.);
1276 m_minIonPot = -1.;
1277
1278 m_parGreenSawada.fill({1., 0., 0., 0., 0.});
1279 m_hasGreenSawada.fill(false);
1280
1281 m_wOpalBeaty.fill(1.);
1282 m_energyLoss.fill(0.);
1283 m_csType.fill(0);
1284
1285 m_yFluorescence.fill(0.);
1286 m_nAuger1.fill(0);
1287 m_eAuger1.fill(0.);
1288 m_nAuger2.fill(0);
1289 m_eAuger2.fill(0.);
1290 m_nFluorescence.fill(0);
1291 m_eFluorescence.fill(0.);
1292
1293 m_scatModel.fill(0);
1294
1295 m_rPenning.fill(0.);
1296 m_lambdaPenning.fill(0.);
1297
1298 m_deexcitations.clear();
1299 m_iDeexcitation.fill(-1);
1300
1301 // Reset the collision rates.
1302 m_cfTot.assign(Magboltz::nEnergySteps, 0.);
1303 m_cfTotLog.assign(nEnergyStepsLog, 0.);
1304
1305 m_cf.assign(Magboltz::nEnergySteps,
1306 std::vector<double>(Magboltz::nMaxLevels, 0.));
1307 m_cfLog.assign(nEnergyStepsLog,
1308 std::vector<double>(Magboltz::nMaxLevels, 0.));
1309
1310 m_scatPar.assign(Magboltz::nEnergySteps,
1311 std::vector<double>(Magboltz::nMaxLevels, 0.5));
1312 m_scatCut.assign(Magboltz::nEnergySteps,
1313 std::vector<double>(Magboltz::nMaxLevels, 1.));
1314
1315 m_scatParLog.assign(nEnergyStepsLog,
1316 std::vector<double>(Magboltz::nMaxLevels, 0.5));
1317 m_scatCutLog.assign(nEnergyStepsLog,
1318 std::vector<double>(Magboltz::nMaxLevels, 1.));
1319
1320 // Cross-sections
1321 // 0: total, 1: elastic,
1322 // 2: ionisation, 3: attachment,
1323 // 4, 5: unused
1324 static double q[Magboltz::nEnergySteps][6];
1325 // Inelastic cross-sections
1327 // Ionisation cross-sections
1329 // Attachment cross-sections
1331 // "Null-collision" cross-sections
1332 static double qNull[Magboltz::nEnergySteps][Magboltz::nMaxNullTerms];
1333 // Parameters for scattering angular distribution
1334 static double pEqEl[Magboltz::nEnergySteps][6];
1335 // Parameters for angular distribution in inelastic collisions
1337 // Parameters for angular distribution in ionising collisions
1339 // Penning transfer parameters
1340 static double penFra[Magboltz::nMaxInelasticTerms][3];
1341 // Description of cross-section terms
1343 // Description of "null-collision" cross-section terms
1344 static char scrptn[Magboltz::nMaxNullTerms][Magboltz::nCharDescr];
1345
1346 // Check the gas composition and establish the gas numbers.
1347 int gasNumber[m_nMaxGases];
1348 for (unsigned int i = 0; i < m_nComponents; ++i) {
1349 const int ng = GetGasNumberMagboltz(m_gas[i]);
1350 if (ng <= 0) {
1351 std::cerr << m_className << "::Mixer:\n Gas " << m_gas[i]
1352 << " does not have a gas number in Magboltz.\n";
1353 return false;
1354 }
1355 gasNumber[i] = ng;
1356 }
1357
1358 if (m_debug || verbose) {
1359 std::cout << m_className << "::Mixer:\n " << Magboltz::nEnergySteps
1360 << " linear energy steps between 0 and "
1361 << std::min(m_eMax, m_eHigh) << " eV.\n";
1362 if (m_eMax > m_eHigh) {
1363 std::cout << " " << nEnergyStepsLog << " logarithmic steps between "
1364 << m_eHigh << " and " << m_eMax << " eV\n";
1365 }
1366 }
1367 m_nTerms = 0;
1368
1369 std::ofstream outfile;
1370 if (m_useCsOutput) {
1371 outfile.open("cs.txt", std::ios::out);
1372 outfile << "# energy [eV] vs. cross-section [cm2]\n";
1373 }
1374
1375 // Loop over the gases in the mixture.
1376 for (unsigned int iGas = 0; iGas < m_nComponents; ++iGas) {
1377 Magboltz::inpt_.efinal = std::min(m_eMax, m_eHigh);
1378 Magboltz::inpt_.estep = m_eStep;
1379 Magboltz::mix2_.eg[iemax] = (iemax + 0.5) * m_eStep;
1380 Magboltz::mix2_.eroot[iemax] = sqrt((iemax + 0.5) * m_eStep);
1381 char name[] = " ";
1382 // Number of inelastic cross-sections
1383 long long nIn = 0;
1384 // Number of ionisation cross-sections
1385 long long nIon = 0;
1386 // Number of attachment cross-sections
1387 long long nAtt = 1;
1388 // Number of "null-collision" cross-sections
1389 long long nNull = 0;
1390 // Virial coefficient (not used)
1391 double virial = 0.;
1392 // Thresholds/characteristic energies.
1393 std::array<double, 6> e;
1394 // Energy losses for inelastic cross-sections.
1395 std::array<double, Magboltz::nMaxInelasticTerms> eIn;
1396 // Ionisation thresholds.
1397 std::array<double, Magboltz::nMaxIonisationTerms> eIon;
1398 // Scattering algorithms
1399 std::array<long long, Magboltz::nMaxInelasticTerms> kIn;
1400 std::array<long long, 6> kEl;
1401 // Opal-Beaty parameter
1402 std::array<double, Magboltz::nMaxIonisationTerms> eoby;
1403 // Scaling factor for "null-collision" terms
1404 std::array<double, Magboltz::nMaxNullTerms> scln;
1405 // Parameters for simulation of Auger and fluorescence processes.
1406 std::array<long long, Magboltz::nMaxIonisationTerms> nc0;
1407 std::array<long long, Magboltz::nMaxIonisationTerms> ng1;
1408 std::array<long long, Magboltz::nMaxIonisationTerms> ng2;
1409 std::array<double, Magboltz::nMaxIonisationTerms> ec0;
1410 std::array<double, Magboltz::nMaxIonisationTerms> wklm;
1411 std::array<double, Magboltz::nMaxIonisationTerms> efl;
1412 std::array<double, Magboltz::nMaxIonisationTerms> eg1;
1413 std::array<double, Magboltz::nMaxIonisationTerms> eg2;
1414
1415 // Retrieve the cross-section data for this gas from Magboltz.
1416 long long ngs = gasNumber[iGas];
1418 &ngs, q[0], qIn[0], &nIn, e.data(), eIn.data(), name, &virial,
1419 eoby.data(), pEqEl[0], pEqIn[0], penFra[0], kEl.data(), kIn.data(),
1420 qIon[0], pEqIon[0], eIon.data(), &nIon, qAtt[0], &nAtt, qNull[0],
1421 &nNull, scln.data(), nc0.data(), ec0.data(), wklm.data(), efl.data(),
1422 ng1.data(), eg1.data(), ng2.data(), eg2.data(), scrpt, scrptn);
1423 if (m_debug || verbose) {
1424 const double m = (2. / e[1]) * ElectronMass / AtomicMassUnitElectronVolt;
1425 std::cout << " " << name << "\n"
1426 << " mass: " << m << " amu\n";
1427 if (nIon > 1) {
1428 std::cout << " ionisation threshold: " << eIon[0] << " eV\n";
1429 } else {
1430 std::cout << " ionisation threshold: " << e[2] << " eV\n";
1431 }
1432 if (e[3] > 0. && e[4] > 0.) {
1433 std::cout << " cross-sections at minimum ionising energy:\n"
1434 << " excitation: " << e[3] * 1.e18 << " Mbarn\n"
1435 << " ionisation: " << e[4] * 1.e18 << " Mbarn\n";
1436 }
1437 }
1438 int np0 = m_nTerms;
1439 // Make sure there is still sufficient space.
1440 if (np0 + nIn + nIon + nAtt + nNull >= Magboltz::nMaxLevels) {
1441 std::cerr << m_className << "::Mixer:\n"
1442 << " Max. number of levels (" << Magboltz::nMaxLevels
1443 << ") exceeded.\n";
1444 return false;
1445 }
1446 const double van = m_fraction[iGas] * prefactor;
1447 int np = np0;
1448 if (m_useCsOutput) {
1449 outfile << "# cross-sections for " << name << "\n";
1450 outfile << "# cross-section types:\n";
1451 outfile << "# elastic\n";
1452 }
1453 // Elastic scattering
1454 ++m_nTerms;
1455 m_scatModel[np] = kEl[1];
1456 const double r = 1. + 0.5 * e[1];
1457 m_rgas[iGas] = r;
1458 m_energyLoss[np] = 0.;
1459 m_description[np] = GetDescription(1, scrpt);
1460 m_csType[np] = nCsTypes * iGas + ElectronCollisionTypeElastic;
1461 bool withIon = false;
1462 // Ionisation
1463 if (nIon > 1) {
1464 for (int j = 0; j < nIon; ++j) {
1465 if (m_eMax < eIon[j]) continue;
1466 withIon = true;
1467 ++m_nTerms;
1468 ++np;
1469 m_scatModel[np] = kEl[2];
1470 m_energyLoss[np] = eIon[j] / r;
1471 m_wOpalBeaty[np] = eoby[j];
1472 m_yFluorescence[np] = wklm[j];
1473 m_nAuger1[np] = nc0[j];
1474 m_eAuger1[np] = ec0[j];
1475 m_nFluorescence[np] = ng1[j];
1476 m_eFluorescence[np] = eg1[j];
1477 m_nAuger2[np] = ng2[j];
1478 m_eAuger2[np] = eg2[j];
1479 m_description[np] = GetDescription(2 + j, scrpt);
1480 m_csType[np] = nCsTypes * iGas + ElectronCollisionTypeIonisation;
1481 if (m_useCsOutput) outfile << "# " << m_description[np] << "\n";
1482 }
1483 m_parGreenSawada[iGas][0] = eoby[0];
1484 m_parGreenSawada[iGas][4] = 2 * eIon[0];
1485 m_ionPot[iGas] = eIon[0];
1486 } else {
1487 if (m_eMax >= e[2]) {
1488 withIon = true;
1489 ++m_nTerms;
1490 ++np;
1491 m_scatModel[np] = kEl[2];
1492 m_energyLoss[np] = e[2] / r;
1493 m_wOpalBeaty[np] = eoby[0];
1494 m_parGreenSawada[iGas][0] = eoby[0];
1495 m_parGreenSawada[iGas][4] = 2 * e[2];
1496 m_ionPot[iGas] = e[2];
1497 m_description[np] = GetDescription(2, scrpt);
1498 m_csType[np] = nCsTypes * iGas + ElectronCollisionTypeIonisation;
1499 if (m_useCsOutput) outfile << "# ionisation (gross)\n";
1500 }
1501 }
1502 // Attachment
1503 for (int j = 0; j < nAtt; ++j) {
1504 ++m_nTerms;
1505 ++np;
1506 m_scatModel[np] = 0;
1507 m_energyLoss[np] = 0.;
1508 m_description[np] = GetDescription(2 + nIon + j, scrpt);
1509 m_csType[np] = nCsTypes * iGas + ElectronCollisionTypeAttachment;
1510 if (m_useCsOutput) outfile << "# " << m_description[np] << "\n";
1511 }
1512 // Inelastic terms
1513 int nExc = 0, nSuperEl = 0;
1514 for (int j = 0; j < nIn; ++j) {
1515 ++np;
1516 m_scatModel[np] = kIn[j];
1517 m_energyLoss[np] = eIn[j] / r;
1518 m_description[np] = GetDescription(4 + nIon + nAtt + j, scrpt);
1519 if ((m_description[np][1] == 'E' && m_description[np][2] == 'X') ||
1520 (m_description[np][0] == 'E' && m_description[np][1] == 'X') ||
1521 (m_gas[iGas] == "N2" && eIn[j] > 6.)) {
1522 // Excitation
1523 m_csType[np] = nCsTypes * iGas + ElectronCollisionTypeExcitation;
1524 ++nExc;
1525 } else if (eIn[j] < 0.) {
1526 // Super-elastic collision
1527 m_csType[np] = nCsTypes * iGas + ElectronCollisionTypeSuperelastic;
1528 ++nSuperEl;
1529 } else {
1530 // Inelastic collision
1531 m_csType[np] = nCsTypes * iGas + ElectronCollisionTypeInelastic;
1532 }
1533 if (m_useCsOutput) outfile << "# " << m_description[np] << "\n";
1534 }
1535 m_nTerms += nIn;
1536 if (nNull > 0) {
1537 for (int j = 0; j < nNull; ++j) {
1538 ++m_nTerms;
1539 ++np;
1540 m_scatModel[np] = 0;
1541 m_energyLoss[np] = 0.;
1542 m_description[np] = GetDescription(j, scrptn);
1543 m_csType[np] = nCsTypes * iGas + ElectronCollisionTypeVirtual;
1544 if (m_useCsOutput) outfile << "# " << m_description[np] << "\n";
1545 }
1546 }
1547 // Loop over the energy table.
1548 for (unsigned int iE = 0; iE < Magboltz::nEnergySteps; ++iE) {
1549 np = np0;
1550 if (m_useCsOutput) {
1551 outfile << (iE + 0.5) * m_eStep << " " << q[iE][1] << " ";
1552 }
1553 // Elastic scattering
1554 m_cf[iE][np] = q[iE][1] * van;
1555 SetScatteringParameters(m_scatModel[np], pEqEl[iE][1], m_scatCut[iE][np],
1556 m_scatPar[iE][np]);
1557 // Ionisation
1558 if (withIon) {
1559 if (nIon > 1) {
1560 for (int j = 0; j < nIon; ++j) {
1561 if (m_eMax < eIon[j]) continue;
1562 ++np;
1563 m_cf[iE][np] = qIon[iE][j] * van;
1564 SetScatteringParameters(m_scatModel[np], pEqIon[iE][j],
1565 m_scatCut[iE][np], m_scatPar[iE][np]);
1566 if (m_useCsOutput) outfile << qIon[iE][j] << " ";
1567 }
1568 } else {
1569 ++np;
1570 m_cf[iE][np] = q[iE][2] * van;
1571 SetScatteringParameters(m_scatModel[np], pEqEl[iE][2],
1572 m_scatCut[iE][np], m_scatPar[iE][np]);
1573 if (m_useCsOutput) outfile << q[iE][2] << " ";
1574 }
1575 }
1576 // Attachment
1577 for (int j = 0; j < nAtt; ++j) {
1578 ++np;
1579 m_cf[iE][np] = qAtt[iE][j] * van;
1580 // m_cf[iE][np] = q[iE][3] * van;
1581 m_scatPar[iE][np] = 0.5;
1582 if (m_useCsOutput) outfile << qAtt[iE][j] << " ";
1583 }
1584 // Inelastic terms
1585 for (int j = 0; j < nIn; ++j) {
1586 ++np;
1587 if (m_useCsOutput) outfile << qIn[iE][j] << " ";
1588 m_cf[iE][np] = qIn[iE][j] * van;
1589 // Scale the excitation cross-sections (for error estimates).
1590 m_cf[iE][np] *= m_scaleExc[iGas];
1591 if (m_cf[iE][np] < 0.) {
1592 std::cerr << m_className << "::Mixer:\n"
1593 << " Negative inelastic cross-section at "
1594 << (iE + 0.5) * m_eStep << " eV. Set to zero.\n";
1595 m_cf[iE][np] = 0.;
1596 }
1597 SetScatteringParameters(m_scatModel[np], pEqIn[iE][j],
1598 m_scatCut[iE][np], m_scatPar[iE][np]);
1599 }
1600 if ((m_debug || verbose) && nIn > 0 && iE == iemax) {
1601 std::cout << " " << nIn << " inelastic terms (" << nExc
1602 << " excitations, " << nSuperEl << " superelastic, "
1603 << nIn - nExc - nSuperEl << " other)\n";
1604 }
1605 if (nNull > 0) {
1606 for (int j = 0; j < nNull; ++j) {
1607 ++np;
1608 m_cf[iE][np] = qNull[iE][j] * van * scln[j];
1609 ;
1610 if (m_useCsOutput) outfile << qNull[iE][j] << " ";
1611 }
1612 }
1613 if (m_useCsOutput) outfile << "\n";
1614 }
1615 if (m_eMax <= m_eHigh) continue;
1616 // Fill the high-energy part (logarithmic binning).
1617 // Calculate the growth factor.
1618 const double rLog = pow(m_eMax / m_eHigh, 1. / nEnergyStepsLog);
1619 m_lnStep = log(rLog);
1620 // Set the upper limit of the first bin.
1621 double emax = m_eHigh * rLog;
1622
1623 for (int iE = 0; iE < nEnergyStepsLog; ++iE) {
1624 Magboltz::inpt_.estep = emax / (Magboltz::nEnergySteps - 0.5);
1625 Magboltz::inpt_.efinal = emax + 0.5 * Magboltz::inpt_.estep;
1626 Magboltz::mix2_.eg[iemax] = emax;
1627 Magboltz::mix2_.eroot[iemax] = sqrt(emax);
1629 &ngs, q[0], qIn[0], &nIn, e.data(), eIn.data(), name, &virial,
1630 eoby.data(), pEqEl[0], pEqIn[0], penFra[0], kEl.data(), kIn.data(),
1631 qIon[0], pEqIon[0], eIon.data(), &nIon, qAtt[0], &nAtt, qNull[0],
1632 &nNull, scln.data(), nc0.data(), ec0.data(), wklm.data(), efl.data(),
1633 ng1.data(), eg1.data(), ng2.data(), eg2.data(), scrpt, scrptn);
1634 np = np0;
1635 if (m_useCsOutput) outfile << emax << " " << q[iemax][1] << " ";
1636 // Elastic scattering
1637 m_cfLog[iE][np] = q[iemax][1] * van;
1638 SetScatteringParameters(m_scatModel[np], pEqEl[iemax][1],
1639 m_scatCutLog[iE][np], m_scatParLog[iE][np]);
1640 // Ionisation
1641 if (withIon) {
1642 if (nIon > 1) {
1643 for (int j = 0; j < nIon; ++j) {
1644 if (m_eMax < eIon[j]) continue;
1645 ++np;
1646 m_cfLog[iE][np] = qIon[iemax][j] * van;
1647 SetScatteringParameters(m_scatModel[np], pEqIon[iemax][j],
1648 m_scatCutLog[iE][np], m_scatParLog[iE][np]);
1649 if (m_useCsOutput) outfile << qIon[iemax][j] << " ";
1650 }
1651 } else {
1652 ++np;
1653 // Gross cross-section
1654 m_cfLog[iE][np] = q[iemax][2] * van;
1655 // Counting cross-section
1656 // m_cfLog[iE][np] = q[iemax][4] * van;
1657 SetScatteringParameters(m_scatModel[np], pEqEl[iemax][2],
1658 m_scatCutLog[iE][np], m_scatParLog[iE][np]);
1659 if (m_useCsOutput) outfile << q[iemax][2] << " ";
1660 }
1661 }
1662 // Attachment
1663 for (int j = 0; j < nAtt; ++j) {
1664 ++np;
1665 m_cfLog[iE][np] = qAtt[iemax][j] * van;
1666 // m_cfLog[iE][np] = q[iemax][3] * van;
1667 if (m_useCsOutput) outfile << qAtt[iemax][j] << " ";
1668 }
1669 // Inelastic terms
1670 for (int j = 0; j < nIn; ++j) {
1671 ++np;
1672 if (m_useCsOutput) outfile << qIn[iemax][j] << " ";
1673 m_cfLog[iE][np] = qIn[iemax][j] * van;
1674 // Scale the excitation cross-sections (for error estimates).
1675 m_cfLog[iE][np] *= m_scaleExc[iGas];
1676 if (m_cfLog[iE][np] < 0.) {
1677 std::cerr << m_className << "::Mixer:\n"
1678 << " Negative inelastic cross-section at " << emax
1679 << " eV. Set to zero.\n";
1680 m_cfLog[iE][np] = 0.;
1681 }
1682 SetScatteringParameters(m_scatModel[np], pEqIn[iemax][j],
1683 m_scatCutLog[iE][np], m_scatParLog[iE][np]);
1684 }
1685 if (nNull > 0) {
1686 for (int j = 0; j < nNull; ++j) {
1687 ++np;
1688 m_cfLog[iE][np] = qNull[iemax][j] * van * scln[j];
1689 if (m_useCsOutput) outfile << qNull[iemax][j] << " ";
1690 }
1691 }
1692 if (m_useCsOutput) outfile << "\n";
1693 // Increase the energy.
1694 emax *= rLog;
1695 }
1696 }
1697 if (m_useCsOutput) outfile.close();
1698
1699 // Find the smallest ionisation threshold.
1700 auto it = std::min_element(std::begin(m_ionPot),
1701 std::begin(m_ionPot) + m_nComponents);
1702 m_minIonPot = *it;
1703 std::string minIonPotGas = m_gas[std::distance(std::begin(m_ionPot), it)];
1704
1705 if (m_debug || verbose) {
1706 std::cout << m_className << "::Mixer:\n"
1707 << " Lowest ionisation threshold in the mixture: "
1708 << m_minIonPot << " eV (" << minIonPotGas << ")\n";
1709 }
1710
1711 for (unsigned int iE = 0; iE < Magboltz::nEnergySteps; ++iE) {
1712 // Calculate the total collision frequency.
1713 for (unsigned int k = 0; k < m_nTerms; ++k) {
1714 if (m_cf[iE][k] < 0.) {
1715 std::cerr << m_className << "::Mixer:\n"
1716 << " Negative collision rate at " << (iE + 0.5) * m_eStep
1717 << " eV, cross-section " << k << ". Set to zero.\n";
1718 std::cout << m_description[k] << "\n";
1719 m_cf[iE][k] = 0.;
1720 }
1721 m_cfTot[iE] += m_cf[iE][k];
1722 }
1723 // Normalise the collision probabilities.
1724 if (m_cfTot[iE] > 0.) {
1725 for (unsigned int k = 0; k < m_nTerms; ++k) m_cf[iE][k] /= m_cfTot[iE];
1726 }
1727 for (unsigned int k = 1; k < m_nTerms; ++k) {
1728 m_cf[iE][k] += m_cf[iE][k - 1];
1729 }
1730 const double ekin = m_eStep * (iE + 0.5);
1731 m_cfTot[iE] *= sqrt(ekin);
1732 // Use relativistic expression at high energies.
1733 if (ekin > 1.e3) {
1734 const double re = ekin / ElectronMass;
1735 m_cfTot[iE] *= sqrt(1. + 0.5 * re) / (1. + re);
1736 }
1737 }
1738
1739 if (m_eMax > m_eHigh) {
1740 const double rLog = pow(m_eMax / m_eHigh, 1. / nEnergyStepsLog);
1741 for (int iE = 0; iE < nEnergyStepsLog; ++iE) {
1742 // Calculate the total collision frequency.
1743 for (unsigned int k = 0; k < m_nTerms; ++k) {
1744 if (m_cfLog[iE][k] < 0.) m_cfLog[iE][k] = 0.;
1745 m_cfTotLog[iE] += m_cfLog[iE][k];
1746 }
1747 // Normalise the collision probabilities.
1748 if (m_cfTotLog[iE] > 0.) {
1749 for (unsigned int k = 0; k < m_nTerms; ++k) {
1750 m_cfLog[iE][k] /= m_cfTotLog[iE];
1751 }
1752 }
1753 for (unsigned int k = 1; k < m_nTerms; ++k) {
1754 m_cfLog[iE][k] += m_cfLog[iE][k - 1];
1755 }
1756 const double ekin = m_eHigh * pow(rLog, iE + 1);
1757 const double re = ekin / ElectronMass;
1758 m_cfTotLog[iE] *= sqrt(ekin) * sqrt(1. + re) / (1. + re);
1759 // Store the logarithm (for log-log interpolation)
1760 m_cfTotLog[iE] = log(m_cfTotLog[iE]);
1761 }
1762 }
1763
1764 // Determine the null collision frequency.
1765 m_cfNull = 0.;
1766 for (unsigned int j = 0; j < Magboltz::nEnergySteps; ++j) {
1767 if (m_cfTot[j] > m_cfNull) m_cfNull = m_cfTot[j];
1768 }
1769 if (m_eMax > m_eHigh) {
1770 for (int j = 0; j < nEnergyStepsLog; ++j) {
1771 const double r = exp(m_cfTotLog[j]);
1772 if (r > m_cfNull) m_cfNull = r;
1773 }
1774 }
1775
1776 // Reset the collision counters.
1777 m_nCollisionsDetailed.assign(m_nTerms, 0);
1778 m_nCollisions.fill(0);
1779
1780 if (m_debug || verbose) {
1781 std::cout << m_className << "::Mixer:\n"
1782 << " Energy [eV] Collision Rate [ns-1]\n";
1783 const double emax = std::min(m_eHigh, m_eMax);
1784 for (int i = 0; i < 8; ++i) {
1785 const double en = (2 * i + 1) * emax / 16;
1786 const double cf = m_cfTot[(i + 1) * Magboltz::nEnergySteps / 16];
1787 std::printf(" %10.2f %18.2f\n", en, cf);
1788 }
1789 }
1790
1791 // Set up the de-excitation channels.
1792 if (m_useDeexcitation) {
1793 ComputeDeexcitationTable(verbose);
1794 for (const auto& dxc : m_deexcitations) {
1795 if (dxc.p.size() == dxc.final.size() && dxc.p.size() == dxc.type.size())
1796 continue;
1797 std::cerr << m_className << "::Mixer:\n"
1798 << " Mismatch in deexcitation channel count. Program bug!\n"
1799 << " Deexcitation handling is switched off.\n";
1800 m_useDeexcitation = false;
1801 break;
1802 }
1803 }
1804
1805 // Fill the photon collision rates table.
1806 if (!ComputePhotonCollisionTable(verbose)) {
1807 std::cerr << m_className << "::Mixer:\n"
1808 << " Photon collision rates could not be calculated.\n";
1809 if (m_useDeexcitation) {
1810 std::cerr << " Deexcitation handling is switched off.\n";
1811 m_useDeexcitation = false;
1812 }
1813 }
1814
1815 // Reset the Penning transfer parameters.
1816 if (m_debug) {
1817 std::cout << m_className << "::Mixer: Resetting transfer probabilities.\n"
1818 << " Global: " << m_rPenningGlobal << "\n";
1819 for (unsigned int i = 0; i < m_nMaxGases; ++i) {
1820 std::cout << " Component " << i << ": " << m_rPenningGas[i] << "\n";
1821 }
1822 }
1823 if (m_rPenningGlobal > Small) {
1824 m_rPenning.fill(m_rPenningGlobal);
1825 m_lambdaPenning.fill(m_lambdaPenningGlobal);
1826 }
1827 for (unsigned int i = 0; i < m_nTerms; ++i) {
1828 int iGas = int(m_csType[i] / nCsTypes);
1829 if (m_rPenningGas[iGas] > Small) {
1830 m_rPenning[i] = m_rPenningGas[iGas];
1831 m_lambdaPenning[i] = m_lambdaPenningGas[iGas];
1832 }
1833 }
1834
1835 // Set the Green-Sawada splitting function parameters.
1836 SetupGreenSawada();
1837
1838 return true;
1839}
1840
1841void MediumMagboltz::SetupGreenSawada() {
1842 for (unsigned int i = 0; i < m_nComponents; ++i) {
1843 const double ta = 1000.;
1844 const double tb = m_parGreenSawada[i][4];
1845 m_hasGreenSawada[i] = true;
1846 if (m_gas[i] == "He" || m_gas[i] == "He-3") {
1847 m_parGreenSawada[i] = {15.5, 24.5, -2.25, ta, tb};
1848 } else if (m_gas[i] == "Ne") {
1849 m_parGreenSawada[i] = {24.3, 21.6, -6.49, ta, tb};
1850 } else if (m_gas[i] == "Ar") {
1851 m_parGreenSawada[i] = {6.92, 7.85, 6.87, ta, tb};
1852 } else if (m_gas[i] == "Kr") {
1853 m_parGreenSawada[i] = {7.95, 13.5, 3.90, ta, tb};
1854 } else if (m_gas[i] == "Xe") {
1855 m_parGreenSawada[i] = {7.93, 11.5, 3.81, ta, tb};
1856 } else if (m_gas[i] == "H2" || m_gas[i] == "D2") {
1857 m_parGreenSawada[i] = {7.07, 7.7, 1.87, ta, tb};
1858 } else if (m_gas[i] == "N2") {
1859 m_parGreenSawada[i] = {13.8, 15.6, 4.71, ta, tb};
1860 } else if (m_gas[i] == "O2") {
1861 m_parGreenSawada[i] = {18.5, 12.1, 1.86, ta, tb};
1862 } else if (m_gas[i] == "CH4") {
1863 m_parGreenSawada[i] = {7.06, 12.5, 3.45, ta, tb};
1864 } else if (m_gas[i] == "H2O") {
1865 m_parGreenSawada[i] = {12.8, 12.6, 1.28, ta, tb};
1866 } else if (m_gas[i] == "CO") {
1867 m_parGreenSawada[i] = {13.3, 14.0, 2.03, ta, tb};
1868 } else if (m_gas[i] == "C2H2") {
1869 m_parGreenSawada[i] = {9.28, 5.8, 1.37, ta, tb};
1870 } else if (m_gas[i] == "NO") {
1871 m_parGreenSawada[i] = {10.4, 9.5, -4.30, ta, tb};
1872 } else if (m_gas[i] == "CO2") {
1873 m_parGreenSawada[i] = {12.3, 13.8, -2.46, ta, tb};
1874 } else {
1875 m_parGreenSawada[i][3] = 0.;
1876 m_hasGreenSawada[i] = false;
1877 if (m_useGreenSawada) {
1878 std::cout << m_className << "::SetupGreenSawada:\n"
1879 << " Fit parameters for " << m_gas[i]
1880 << " not available.\n"
1881 << " Opal-Beaty formula is used instead.\n";
1882 }
1883 }
1884 }
1885}
1886
1887void MediumMagboltz::SetScatteringParameters(const int model,
1888 const double parIn, double& cut,
1889 double& parOut) const {
1890 cut = 1.;
1891 parOut = 0.5;
1892 if (model <= 0) return;
1893
1894 if (model >= 2) {
1895 parOut = parIn;
1896 return;
1897 }
1898
1899 // Set cuts on angular distribution and
1900 // renormalise forward scattering probability.
1901
1902 if (parIn <= 1.) {
1903 parOut = parIn;
1904 return;
1905 }
1906
1907 constexpr double rads = 2. / Pi;
1908 const double cns = parIn - 0.5;
1909 const double thetac = asin(2. * sqrt(cns - cns * cns));
1910 const double fac = (1. - cos(thetac)) / pow(sin(thetac), 2.);
1911 parOut = cns * fac + 0.5;
1912 cut = thetac * rads;
1913}
1914
1915void MediumMagboltz::ComputeDeexcitationTable(const bool verbose) {
1916 m_iDeexcitation.fill(-1);
1917 m_deexcitations.clear();
1918
1919 // Optical data (for quencher photoabsorption cs and ionisation yield)
1920 OpticalData optData;
1921
1922 // Indices of "de-excitable" gases (only Ar for the time being).
1923 int iAr = -1;
1924
1925 // Map Magboltz level names to internal ones.
1926 std::map<std::string, std::string> levelNamesAr = {
1927 {"1S5 ", "Ar_1S5"}, {"1S4 ", "Ar_1S4"},
1928 {"1S3 ", "Ar_1S3"}, {"1S2 ", "Ar_1S2"},
1929 {"2P10 ", "Ar_2P10"}, {"2P9 ", "Ar_2P9"},
1930 {"2P8 ", "Ar_2P8"}, {"2P7 ", "Ar_2P7"},
1931 {"2P6 ", "Ar_2P6"}, {"2P5 ", "Ar_2P5"},
1932 {"2P4 ", "Ar_2P4"}, {"2P3 ", "Ar_2P3"},
1933 {"2P2 ", "Ar_2P2"}, {"2P1 ", "Ar_2P1"},
1934 {"3D6 ", "Ar_3D6"}, {"3D5 ", "Ar_3D5"},
1935 {"3D3 ", "Ar_3D3"}, {"3D4! ", "Ar_3D4!"},
1936 {"3D4 ", "Ar_3D4"}, {"3D1!! ", "Ar_3D1!!"},
1937 {"2S5 ", "Ar_2S5"}, {"2S4 ", "Ar_2S4"},
1938 {"3D1! ", "Ar_3D1!"}, {"3D2 ", "Ar_3D2"},
1939 {"3S1!!!!", "Ar_3S1!!!!"}, {"3S1!! ", "Ar_3S1!!"},
1940 {"3S1!!! ", "Ar_3S1!!!"}, {"2S3 ", "Ar_2S3"},
1941 {"2S2 ", "Ar_2S2"}, {"3S1! ", "Ar_3S1!"},
1942 {"4D5 ", "Ar_4D5"}, {"3S4 ", "Ar_3S4"},
1943 {"4D2 ", "Ar_4D2"}, {"4S1! ", "Ar_4S1!"},
1944 {"3S2 ", "Ar_3S2"}, {"5D5 ", "Ar_5D5"},
1945 {"4S4 ", "Ar_4S4"}, {"5D2 ", "Ar_5D2"},
1946 {"6D5 ", "Ar_6D5"}, {"5S1! ", "Ar_5S1!"},
1947 {"4S2 ", "Ar_4S2"}, {"5S4 ", "Ar_5S4"},
1948 {"6D2 ", "Ar_6D2"}, {"HIGH ", "Ar_Higher"}};
1949
1950 std::map<std::string, int> mapLevels;
1951 // Make a mapping of all excitation levels.
1952 for (unsigned int i = 0; i < m_nTerms; ++i) {
1953 // Check if the level is an excitation.
1954 if (m_csType[i] % nCsTypes != ElectronCollisionTypeExcitation) continue;
1955 // Extract the index of the gas.
1956 const int ngas = int(m_csType[i] / nCsTypes);
1957 if (m_gas[ngas] == "Ar") {
1958 // Argon
1959 if (iAr < 0) iAr = ngas;
1960 // Get the level description (as specified in Magboltz).
1961 std::string level = " ";
1962 for (int j = 0; j < 7; ++j) level[j] = m_description[i][5 + j];
1963 if (levelNamesAr.find(level) != levelNamesAr.end()) {
1964 mapLevels[levelNamesAr[level]] = i;
1965 } else {
1966 std::cerr << m_className << "::ComputeDeexcitationTable:\n"
1967 << " Unknown Ar excitation level: " << level << "\n";
1968 }
1969 }
1970 }
1971
1972 // Count the excitation levels.
1973 unsigned int nDeexcitations = 0;
1974 std::map<std::string, int> lvl;
1975 for (auto it = mapLevels.cbegin(), end = mapLevels.cend(); it != end; ++it) {
1976 std::string level = (*it).first;
1977 lvl[level] = nDeexcitations;
1978 m_iDeexcitation[(*it).second] = nDeexcitations;
1979 ++nDeexcitations;
1980 }
1981
1982 // Conversion factor from oscillator strength to transition rate.
1983 constexpr double f2A =
1984 2. * SpeedOfLight * FineStructureConstant / (3. * ElectronMass * HbarC);
1985
1986 // Radiative de-excitation channels
1987 // Transition rates (unless indicated otherwise) are taken from:
1988 // NIST Atomic Spectra Database
1989 // Transition rates for lines missing in the NIST database:
1990 // O. Zatsarinny and K. Bartschat, J. Phys. B 39 (2006), 2145-2158
1991 // Oscillator strengths not included in the NIST database:
1992 // J. Berkowitz, Atomic and Molecular Photoabsorption (2002)
1993 // C.-M. Lee and K. T. Lu, Phys. Rev. A 8 (1973), 1241-1257
1994 for (auto it = mapLevels.cbegin(), end = mapLevels.cend(); it != end; ++it) {
1995 std::string level = (*it).first;
1996 Deexcitation dxc;
1997 dxc.gas = int(m_csType[(*it).second] / nCsTypes);
1998 dxc.level = (*it).second;
1999 dxc.label = level;
2000 // Excitation energy
2001 dxc.energy = m_energyLoss[(*it).second] * m_rgas[dxc.gas];
2002 // Oscillator strength
2003 dxc.osc = dxc.cf = 0.;
2004 dxc.sDoppler = dxc.gPressure = dxc.width = 0.;
2005 const std::vector<int> levelsAr4s = {lvl["Ar_1S5"], lvl["Ar_1S4"],
2006 lvl["Ar_1S3"], lvl["Ar_1S2"]};
2007 if (level == "Ar_1S5" || level == "Ar_1S3") {
2008 // Metastables
2009 } else if (level == "Ar_1S4") {
2010 dxc.osc = 0.0609; // NIST
2011 // Berkowitz: f = 0.058
2012 dxc.p = {0.119};
2013 dxc.final = {-1};
2014 } else if (level == "Ar_1S2") {
2015 dxc.osc = 0.25; // NIST
2016 // Berkowitz: 0.2214
2017 dxc.p = {0.51};
2018 dxc.final = {-1};
2019 } else if (level == "Ar_2P10") {
2020 dxc.p = {0.0189, 5.43e-3, 9.8e-4, 1.9e-4};
2021 dxc.final = levelsAr4s;
2022 } else if (level == "Ar_2P9") {
2023 dxc.p = {0.0331};
2024 dxc.final = {lvl["Ar_1S5"]};
2025 } else if (level == "Ar_2P8") {
2026 dxc.p = {9.28e-3, 0.0215, 1.47e-3};
2027 dxc.final = {lvl["Ar_1S5"], lvl["Ar_1S4"], lvl["Ar_1S2"]};
2028 } else if (level == "Ar_2P7") {
2029 dxc.p = {5.18e-3, 0.025, 2.43e-3, 1.06e-3};
2030 dxc.final = levelsAr4s;
2031 } else if (level == "Ar_2P6") {
2032 dxc.p = {0.0245, 4.9e-3, 5.03e-3};
2033 dxc.final = {lvl["Ar_1S5"], lvl["Ar_1S4"], lvl["Ar_1S2"]};
2034 } else if (level == "Ar_2P5") {
2035 dxc.p = {0.0402};
2036 dxc.final = {lvl["Ar_1S4"]};
2037 } else if (level == "Ar_2P4") {
2038 dxc.p = {6.25e-4, 2.2e-5, 0.0186, 0.0139};
2039 dxc.final = levelsAr4s;
2040 } else if (level == "Ar_2P3") {
2041 dxc.p = {3.8e-3, 8.47e-3, 0.0223};
2042 dxc.final = {lvl["Ar_1S5"], lvl["Ar_1S4"], lvl["Ar_1S2"]};
2043 } else if (level == "Ar_2P2") {
2044 dxc.p = {6.39e-3, 1.83e-3, 0.0117, 0.0153};
2045 dxc.final = levelsAr4s;
2046 } else if (level == "Ar_2P1") {
2047 dxc.p = {2.36e-4, 0.0445};
2048 dxc.final = {lvl["Ar_1S4"], lvl["Ar_1S2"]};
2049 } else if (level == "Ar_3D6") {
2050 // Additional line (2P7) from Bartschat
2051 dxc.p = {8.1e-3, 7.73e-4, 1.2e-4, 3.6e-4};
2052 dxc.final = {lvl["Ar_2P10"], lvl["Ar_2P7"], lvl["Ar_2P4"], lvl["Ar_2P2"]};
2053 } else if (level == "Ar_3D5") {
2054 dxc.osc = 0.0011; // Berkowitz
2055 // Additional lines (2P7, 2P6, 2P5, 2P1) from Bartschat
2056 // Transition probability to ground state calculated from osc. strength
2057 const double p0 = f2A * dxc.energy * dxc.energy * dxc.osc;
2058 dxc.p = {7.4e-3, 3.9e-5, 3.09e-4, 1.37e-3, 5.75e-4,
2059 3.2e-5, 1.4e-4, 1.7e-4, 2.49e-6, p0};
2060 dxc.final = {lvl["Ar_2P10"], lvl["Ar_2P8"],
2061 lvl["Ar_2P7"], lvl["Ar_2P6"],
2062 lvl["Ar_2P5"], lvl["Ar_2P4"],
2063 lvl["Ar_2P3"], lvl["Ar_2P2"],
2064 lvl["Ar_2P1"], -1};
2065 } else if (level == "Ar_3D3") {
2066 // Additional lines (2P9, 2P4) from Bartschat
2067 dxc.p = {4.9e-3, 9.82e-5, 1.2e-4, 2.6e-4,
2068 2.5e-3, 9.41e-5, 3.9e-4, 1.1e-4};
2069 dxc.final = {lvl["Ar_2P10"], lvl["Ar_2P9"], lvl["Ar_2P8"], lvl["Ar_2P7"],
2070 lvl["Ar_2P6"], lvl["Ar_2P4"], lvl["Ar_2P3"], lvl["Ar_2P2"]};
2071 } else if (level == "Ar_3D4!") {
2072 // Transition probability for 2P9 transition from Bartschat
2073 dxc.p = {0.01593};
2074 dxc.final = {lvl["Ar_2P9"]};
2075 } else if (level == "Ar_3D4") {
2076 // Additional lines (2P9, 2P3) from Bartschat
2077 dxc.p = {2.29e-3, 0.011, 8.8e-5, 2.53e-6};
2078 dxc.final = {lvl["Ar_2P9"], lvl["Ar_2P8"], lvl["Ar_2P6"], lvl["Ar_2P3"]};
2079 } else if (level == "Ar_3D1!!") {
2080 // Additional lines (2P10, 2P6, 2P4 - 2P2) from Bartschat
2081 dxc.p = {5.85e-6, 1.2e-4, 5.7e-3, 7.3e-3,
2082 2.e-4, 1.54e-6, 2.08e-5, 6.75e-7};
2083 dxc.final = {lvl["Ar_2P10"], lvl["Ar_2P9"], lvl["Ar_2P8"], lvl["Ar_2P7"],
2084 lvl["Ar_2P6"], lvl["Ar_2P4"], lvl["Ar_2P3"], lvl["Ar_2P2"]};
2085 } else if (level == "Ar_2S5") {
2086 dxc.p = {4.9e-3, 0.011, 1.1e-3, 4.6e-4, 3.3e-3, 5.9e-5, 1.2e-4, 3.1e-4};
2087 dxc.final = {lvl["Ar_2P10"], lvl["Ar_2P9"], lvl["Ar_2P8"], lvl["Ar_2P7"],
2088 lvl["Ar_2P6"], lvl["Ar_2P4"], lvl["Ar_2P3"], lvl["Ar_2P2"]};
2089 } else if (level == "Ar_2S4") {
2090 dxc.osc = 0.027; // NIST
2091 // Berkowitz: f = 0.026;
2092 dxc.p = {0.077, 2.44e-3, 8.9e-3, 4.6e-3, 2.7e-3,
2093 1.3e-3, 4.5e-4, 2.9e-5, 3.e-5, 1.6e-4};
2094 dxc.final = {-1,
2095 lvl["Ar_2P10"],
2096 lvl["Ar_2P8"],
2097 lvl["Ar_2P7"],
2098 lvl["Ar_2P6"],
2099 lvl["Ar_2P5"],
2100 lvl["Ar_2P4"],
2101 lvl["Ar_2P3"],
2102 lvl["Ar_2P2"],
2103 lvl["Ar_2P1"]};
2104 } else if (level == "Ar_3D1!") {
2105 // Additional line (2P6) from Bartschat
2106 dxc.p = {3.1e-3, 2.e-3, 0.015, 9.8e-6};
2107 dxc.final = {lvl["Ar_2P9"], lvl["Ar_2P8"], lvl["Ar_2P6"], lvl["Ar_2P3"]};
2108 } else if (level == "Ar_3D2") {
2109 dxc.osc = 0.0932; // NIST
2110 // Berkowitz: f = 0.09
2111 // Additional lines (2P10, 2P6, 2P4-2P1) from Bartschat
2112 dxc.p = {0.27, 1.35e-5, 9.52e-4, 0.011, 4.01e-5,
2113 4.3e-3, 8.96e-4, 4.45e-5, 5.87e-5, 8.77e-4};
2114 dxc.final = {-1,
2115 lvl["Ar_2P10"],
2116 lvl["Ar_2P8"],
2117 lvl["Ar_2P7"],
2118 lvl["Ar_2P6"],
2119 lvl["Ar_2P5"],
2120 lvl["Ar_2P4"],
2121 lvl["Ar_2P3"],
2122 lvl["Ar_2P2"],
2123 lvl["Ar_2P1"]};
2124 } else if (level == "Ar_3S1!!!!") {
2125 // Additional lines (2P10, 2P9, 2P7, 2P6, 2P2) from Bartschat
2126 dxc.p = {7.51e-6, 4.3e-5, 8.3e-4, 5.01e-5,
2127 2.09e-4, 0.013, 2.2e-3, 3.35e-6};
2128 dxc.final = {lvl["Ar_2P10"], lvl["Ar_2P9"], lvl["Ar_2P8"], lvl["Ar_2P7"],
2129 lvl["Ar_2P6"], lvl["Ar_2P4"], lvl["Ar_2P3"], lvl["Ar_2P2"]};
2130 } else if (level == "Ar_3S1!!") {
2131 // Additional lines (2P10 - 2P8, 2P4, 2P3)
2132 dxc.p = {1.89e-4, 1.52e-4, 7.21e-4, 3.69e-4,
2133 3.76e-3, 1.72e-4, 5.8e-4, 6.2e-3};
2134 dxc.final = {lvl["Ar_2P10"], lvl["Ar_2P9"], lvl["Ar_2P8"], lvl["Ar_2P7"],
2135 lvl["Ar_2P6"], lvl["Ar_2P4"], lvl["Ar_2P3"], lvl["Ar_2P2"]};
2136 } else if (level == "Ar_3S1!!!") {
2137 // Additional lines (2P9, 2P8, 2P6) from Bartschat
2138 dxc.p = {7.36e-4, 4.2e-5, 9.3e-5, 0.015};
2139 dxc.final = {lvl["Ar_2P9"], lvl["Ar_2P8"], lvl["Ar_2P6"], lvl["Ar_2P3"]};
2140 } else if (level == "Ar_2S3") {
2141 dxc.p = {3.26e-3, 2.22e-3, 0.01, 5.1e-3};
2142 dxc.final = {lvl["Ar_2P10"], lvl["Ar_2P7"], lvl["Ar_2P4"], lvl["Ar_2P2"]};
2143 } else if (level == "Ar_2S2") {
2144 dxc.osc = 0.0119; // NIST
2145 // Berkowitz: f = 0.012;
2146 dxc.p = {0.035, 1.76e-3, 2.1e-4, 2.8e-4, 1.39e-3,
2147 3.8e-4, 2.0e-3, 8.9e-3, 3.4e-3, 1.9e-3};
2148 dxc.final = {-1,
2149 lvl["Ar_2P10"],
2150 lvl["Ar_2P8"],
2151 lvl["Ar_2P7"],
2152 lvl["Ar_2P6"],
2153 lvl["Ar_2P5"],
2154 lvl["Ar_2P4"],
2155 lvl["Ar_2P3"],
2156 lvl["Ar_2P2"],
2157 lvl["Ar_2P1"]};
2158 } else if (level == "Ar_3S1!") {
2159 dxc.osc = 0.106; // NIST
2160 // Berkowitz: f = 0.106
2161 // Additional lines (2P10, 2P8, 2P7, 2P3) from Bartschat
2162 dxc.p = {0.313, 2.05e-5, 8.33e-5, 3.9e-4, 3.96e-4,
2163 4.2e-4, 4.5e-3, 4.84e-5, 7.1e-3, 5.2e-3};
2164 dxc.final = {-1,
2165 lvl["Ar_2P10"],
2166 lvl["Ar_2P8"],
2167 lvl["Ar_2P7"],
2168 lvl["Ar_2P6"],
2169 lvl["Ar_2P5"],
2170 lvl["Ar_2P4"],
2171 lvl["Ar_2P3"],
2172 lvl["Ar_2P2"],
2173 lvl["Ar_2P1"]};
2174 } else if (level == "Ar_4D5") {
2175 dxc.osc = 0.0019; // Berkowitz
2176 // Transition probability to ground state calculated from osc. strength
2177 const double p0 = f2A * dxc.energy * dxc.energy * dxc.osc;
2178 dxc.p = {2.78e-3, 2.8e-4, 8.6e-4, 9.2e-4, 4.6e-4, 1.6e-4, p0};
2179 dxc.final = {lvl["Ar_2P10"],
2180 lvl["Ar_2P8"],
2181 lvl["Ar_2P6"],
2182 lvl["Ar_2P5"],
2183 lvl["Ar_2P3"],
2184 lvl["Ar_2P2"],
2185 -1};
2186 } else if (level == "Ar_3S4") {
2187 dxc.osc = 0.0144; // Berkowitz
2188 const double p0 = f2A * dxc.energy * dxc.energy * dxc.osc;
2189 dxc.p = {4.21e-4, 2.e-3, 1.7e-3, 7.2e-4, 3.5e-4,
2190 1.2e-4, 4.2e-6, 3.3e-5, 9.7e-5, p0};
2191 dxc.final = {lvl["Ar_2P10"], lvl["Ar_2P8"],
2192 lvl["Ar_2P7"], lvl["Ar_2P6"],
2193 lvl["Ar_2P5"], lvl["Ar_2P4"],
2194 lvl["Ar_2P3"], lvl["Ar_2P2"],
2195 lvl["Ar_2P1"], -1};
2196 } else if (level == "Ar_4D2") {
2197 dxc.osc = 0.048; // Berkowitz
2198 const double p0 = f2A * dxc.energy * dxc.energy * dxc.osc;
2199 dxc.p = {1.7e-4, p0};
2200 dxc.final = {lvl["Ar_2P7"], -1};
2201 } else if (level == "Ar_4S1!") {
2202 dxc.osc = 0.0209; // Berkowitz
2203 const double p0 = f2A * dxc.energy * dxc.energy * dxc.osc;
2204 dxc.p = {1.05e-3, 3.1e-5, 2.5e-5, 4.0e-4, 5.8e-5, 1.2e-4, p0};
2205 dxc.final = {lvl["Ar_2P10"],
2206 lvl["Ar_2P8"],
2207 lvl["Ar_2P7"],
2208 lvl["Ar_2P6"],
2209 lvl["Ar_2P5"],
2210 lvl["Ar_2P3"],
2211 -1};
2212 } else if (level == "Ar_3S2") {
2213 dxc.osc = 0.0221; // Berkowitz
2214 const double p0 = f2A * dxc.energy * dxc.energy * dxc.osc;
2215 dxc.p = {2.85e-4, 5.1e-5, 5.3e-5, 1.6e-4, 1.5e-4,
2216 6.0e-4, 2.48e-3, 9.6e-4, 3.59e-4, p0};
2217 dxc.final = {lvl["Ar_2P10"], lvl["Ar_2P8"],
2218 lvl["Ar_2P7"], lvl["Ar_2P6"],
2219 lvl["Ar_2P5"], lvl["Ar_2P4"],
2220 lvl["Ar_2P3"], lvl["Ar_2P2"],
2221 lvl["Ar_2P1"], -1};
2222 } else if (level == "Ar_5D5") {
2223 dxc.osc = 0.0041; // Berkowitz
2224 const double p0 = f2A * dxc.energy * dxc.energy * dxc.osc;
2225 dxc.p = {2.2e-3, 1.1e-4, 7.6e-5, 4.2e-4, 2.4e-4,
2226 2.1e-4, 2.4e-4, 1.2e-4, p0};
2227 dxc.final = {lvl["Ar_2P10"], lvl["Ar_2P8"], lvl["Ar_2P7"],
2228 lvl["Ar_2P6"], lvl["Ar_2P5"], lvl["Ar_2P4"],
2229 lvl["Ar_2P3"], lvl["Ar_2P2"], -1};
2230 } else if (level == "Ar_4S4") {
2231 dxc.osc = 0.0139; // Berkowitz
2232 const double p0 = f2A * dxc.energy * dxc.energy * dxc.osc;
2233 dxc.p = {1.9e-4, 1.1e-3, 5.2e-4, 5.1e-4, 9.4e-5, 5.4e-5, p0};
2234 dxc.final = {lvl["Ar_2P10"],
2235 lvl["Ar_2P8"],
2236 lvl["Ar_2P7"],
2237 lvl["Ar_2P6"],
2238 lvl["Ar_2P5"],
2239 lvl["Ar_2P4"],
2240 -1};
2241 } else if (level == "Ar_5D2") {
2242 dxc.osc = 0.0426; // Berkowitz
2243 const double p0 = f2A * dxc.energy * dxc.energy * dxc.osc;
2244 dxc.p = {5.9e-5, 9.0e-6, 1.5e-4, 3.1e-5, p0};
2245 dxc.final = {lvl["Ar_2P8"], lvl["Ar_2P7"], lvl["Ar_2P5"], lvl["Ar_2P2"],
2246 -1};
2247 } else if (level == "Ar_6D5") {
2248 dxc.osc = 0.00075; // Lee and Lu
2249 // Berkowitz estimates f = 0.0062 for the sum of
2250 // all "weak" nd levels with n = 6 and higher.
2251 const double p0 = f2A * dxc.energy * dxc.energy * dxc.osc;
2252 dxc.p = {1.9e-3, 4.2e-4, 3.e-4, 5.1e-5, 6.6e-5, 1.21e-4, p0};
2253 dxc.final = {lvl["Ar_2P10"],
2254 lvl["Ar_2P6"],
2255 lvl["Ar_2P5"],
2256 lvl["Ar_2P4"],
2257 lvl["Ar_2P3"],
2258 lvl["Ar_2P1"],
2259 -1};
2260 } else if (level == "Ar_5S1!") {
2261 dxc.osc = 0.00051; // Lee and Lu
2262 // Berkowitz estimates f = 0.0562 for the sum
2263 // of all nd' levels with n = 5 and higher.
2264 const double p0 = f2A * dxc.energy * dxc.energy * dxc.osc;
2265 dxc.p = {7.7e-5, p0};
2266 dxc.final = {lvl["Ar_2P5"], -1};
2267 } else if (level == "Ar_4S2") {
2268 dxc.osc = 0.00074; // Lee and Lu
2269 // Berkowitz estimates f = 0.0069 for the sum over all
2270 // ns' levels with n = 7 and higher.
2271 const double p0 = f2A * dxc.energy * dxc.energy * dxc.osc;
2272 dxc.p = {4.5e-4, 2.e-4, 2.1e-4, 1.2e-4, 1.8e-4, 9.e-4, 3.3e-4, p0};
2273 dxc.final = {lvl["Ar_2P10"], lvl["Ar_2P8"], lvl["Ar_2P7"], lvl["Ar_2P5"],
2274 lvl["Ar_2P4"], lvl["Ar_2P3"], lvl["Ar_2P2"], -1};
2275 } else if (level == "Ar_5S4") {
2276 // dxc.osc = 0.0130; // Lee and Lu
2277 // Berkowitz estimates f = 0.0211 for the sum of all
2278 // ns levels with n = 8 and higher.
2279 dxc.osc = 0.0211;
2280 const double p0 = f2A * dxc.energy * dxc.energy * dxc.osc;
2281 dxc.p = {3.6e-4, 1.2e-4, 1.5e-4, 1.4e-4, 7.5e-5, p0};
2282 dxc.final = {lvl["Ar_2P8"], lvl["Ar_2P6"], lvl["Ar_2P4"],
2283 lvl["Ar_2P3"], lvl["Ar_2P2"], -1};
2284 } else if (level == "Ar_6D2") {
2285 // dxc.osc = 0.0290; // Lee and Lu
2286 // Berkowitz estimates f = 0.0574 for the sum of all
2287 // "strong" nd levels with n = 6 and higher.
2288 dxc.osc = 0.0574;
2289 // Additional line: 2P7
2290 const double p0 = f2A * dxc.energy * dxc.energy * dxc.osc;
2291 dxc.p = {3.33e-3, p0};
2292 dxc.final = {lvl["Ar_2P7"], -1};
2293 } else if (level == "Ar_Higher") {
2294 dxc.osc = 0.;
2295 // This (artificial) level represents the sum of higher J = 1 states.
2296 // The deeexcitation cascade is simulated by allocating it
2297 // with equal probability to one of the five nearest levels below.
2298 dxc.type.assign(5, DxcTypeCollNonIon);
2299 dxc.p = {100., 100., 100., 100., 100.};
2300 dxc.final = {lvl["Ar_6D5"], lvl["Ar_5S1!"], lvl["Ar_4S2"], lvl["Ar_5S4"],
2301 lvl["Ar_6D2"]};
2302 } else {
2303 std::cerr << m_className << "::ComputeDeexcitationTable:\n"
2304 << " Missing de-excitation data for level " << level
2305 << ". Program bug!\n";
2306 return;
2307 }
2308 if (level != "Ar_Higher") dxc.type.assign(dxc.p.size(), DxcTypeRad);
2309 m_deexcitations.push_back(std::move(dxc));
2310 }
2311
2312 if (m_debug || verbose) {
2313 std::cout << m_className << "::ComputeDeexcitationTable:\n";
2314 std::cout << " Found " << m_deexcitations.size() << " levels "
2315 << "with available radiative de-excitation data.\n";
2316 }
2317
2318 // Collisional de-excitation channels
2319 if (iAr >= 0) {
2320 // Add the Ar dimer ground state.
2321 Deexcitation dimer;
2322 dimer.label = "Ar_Dimer";
2323 dimer.level = -1;
2324 dimer.gas = iAr;
2325 dimer.energy = 14.71;
2326 dimer.osc = dimer.cf = 0.;
2327 dimer.sDoppler = dimer.gPressure = dimer.width = 0.;
2328 lvl["Ar_Dimer"] = m_deexcitations.size();
2329 m_deexcitations.push_back(std::move(dimer));
2330 ++nDeexcitations;
2331 // Add an Ar excimer level.
2332 Deexcitation excimer;
2333 excimer.label = "Ar_Excimer";
2334 excimer.level = -1;
2335 excimer.gas = iAr;
2336 excimer.energy = 14.71;
2337 excimer.osc = excimer.cf = 0.;
2338 excimer.sDoppler = excimer.gPressure = excimer.width = 0.;
2339 lvl["Ar_Excimer"] = m_deexcitations.size();
2340 m_deexcitations.push_back(std::move(excimer));
2341 ++nDeexcitations;
2342 const double nAr = GetNumberDensity() * m_fraction[iAr];
2343 // Flags for two-body and three-body collision rate constants.
2344 // Three-body collisions lead to excimer formation.
2345 // Two-body collisions give rise to collisional mixing.
2346 constexpr bool useTachibanaData = false;
2347 constexpr bool useCollMixing = true;
2348 for (auto& dxc : m_deexcitations) {
2349 const std::string level = dxc.label;
2350 if (level == "Ar_1S5") {
2351 // K. Tachibana, Phys. Rev. A 34 (1986), 1007-1015
2352 // Kolts and Setser, J. Chem. Phys. 68 (1978), 4848-4859
2353 constexpr double k3b = useTachibanaData ? 1.4e-41 : 1.1e-41;
2354 dxc.p.push_back(k3b * nAr * nAr);
2355 dxc.final.push_back(lvl["Ar_Excimer"]);
2356 if (useCollMixing) {
2357 constexpr double k2b = useTachibanaData ? 2.3e-24 : 2.1e-24;
2358 dxc.p.push_back(k2b * nAr);
2359 dxc.final.push_back(lvl["Ar_1S4"]);
2360 dxc.type.push_back(DxcTypeCollNonIon);
2361 }
2362 dxc.type.resize(dxc.p.size(), DxcTypeCollNonIon);
2363 } else if (level == "Ar_1S3") {
2364 // K. Tachibana, Phys. Rev. A 34 (1986), 1007-1015
2365 // Kolts and Setser, J. Chem. Phys. 68 (1978), 4848-4859
2366 constexpr double k3b = useTachibanaData ? 1.5e-41 : 0.83e-41;
2367 dxc.p.push_back(k3b * nAr * nAr);
2368 dxc.final.push_back(lvl["Ar_Excimer"]);
2369 if (useCollMixing) {
2370 constexpr double k2b = useTachibanaData ? 4.3e-24 : 5.3e-24;
2371 dxc.p.push_back(k2b * nAr);
2372 dxc.final.push_back(lvl["Ar_1S4"]);
2373 }
2374 dxc.type.resize(dxc.p.size(), DxcTypeCollNonIon);
2375 }
2376 const std::vector<int> levels4s = {lvl["Ar_1S5"], lvl["Ar_1S4"],
2377 lvl["Ar_1S3"], lvl["Ar_1S2"]};
2378 if (level == "Ar_2P1") {
2379 // Transfer to 4s states
2380 // Inoue, Setser, and Sadeghi, J. Chem. Phys. 75 (1982), 977-983
2381 // constexpr double k4s = 2.9e-20;
2382 // Sadeghi et al. J. Chem. Phys. 115 (2001), 3144-3154
2383 constexpr double k4s = 1.6e-20;
2384 dxc.p.resize(dxc.p.size() + levels4s.size(), 0.25 * k4s * nAr);
2385 dxc.final.insert(dxc.final.end(), levels4s.begin(), levels4s.end());
2386 dxc.type.resize(dxc.p.size(), DxcTypeCollNonIon);
2387 } else if (level == "Ar_2P2") {
2388 // Collisional population transfer within 4p levels
2389 // T. D. Nguyen and N. Sadeghi, Phys. Rev. 18 (1978), 1388-1395
2390 constexpr double k23 = 0.5e-21;
2391 dxc.p.push_back(k23 * nAr);
2392 dxc.final.push_back(lvl["Ar_2P3"]);
2393 // Transfer to 4s states
2394 // Inoue, Setser, and Sadeghi, J. Chem. Phys. 75 (1982), 977-983
2395 // constexpr double k4s = 3.8e-20;
2396 // Chang and Setser, J. Chem. Phys. 69 (1978), 3885-3897
2397 constexpr double k4s = 5.3e-20;
2398 dxc.p.resize(dxc.p.size() + levels4s.size(), 0.25 * k4s * nAr);
2399 dxc.final.insert(dxc.final.end(), levels4s.begin(), levels4s.end());
2400 dxc.type.resize(dxc.p.size(), DxcTypeCollNonIon);
2401 } else if (level == "Ar_2P3") {
2402 // Collisional population transfer within 4p levels
2403 // T. D. Nguyen and N. Sadeghi, Phys. Rev. 18 (1978), 1388-1395
2404 constexpr double k34 = 27.5e-21;
2405 constexpr double k35 = 0.3e-21;
2406 constexpr double k36 = 44.0e-21;
2407 constexpr double k37 = 1.4e-21;
2408 constexpr double k38 = 1.9e-21;
2409 constexpr double k39 = 0.8e-21;
2410 dxc.p.insert(dxc.p.end(), {k34 * nAr, k35 * nAr, k36 * nAr, k37 * nAr,
2411 k38 * nAr, k39 * nAr});
2412 dxc.final.insert(dxc.final.end(),
2413 {lvl["Ar_2P4"], lvl["Ar_2P5"], lvl["Ar_2P6"],
2414 lvl["Ar_2P7"], lvl["Ar_2P8"], lvl["Ar_2P9"]});
2415 // Transfer to 4s states
2416 // Chang and Setser, J. Chem. Phys. 69 (1978), 3885-3897
2417 constexpr double k4s = 4.7e-20;
2418 dxc.p.resize(dxc.p.size() + levels4s.size(), 0.25 * k4s * nAr);
2419 dxc.final.insert(dxc.final.end(), levels4s.begin(), levels4s.end());
2420 dxc.type.resize(dxc.p.size(), DxcTypeCollNonIon);
2421 } else if (level == "Ar_2P4") {
2422 // Collisional population transfer within 4p levels
2423 // T. D. Nguyen and N. Sadeghi, Phys. Rev. 18 (1978), 1388-1395
2424 constexpr double k43 = 23.0e-21;
2425 constexpr double k45 = 0.7e-21;
2426 constexpr double k46 = 4.8e-21;
2427 constexpr double k47 = 3.2e-21;
2428 constexpr double k48 = 1.4e-21;
2429 constexpr double k49 = 3.3e-21;
2430 dxc.p.insert(dxc.p.end(), {k43 * nAr, k45 * nAr, k46 * nAr, k47 * nAr,
2431 k48 * nAr, k49 * nAr});
2432 dxc.final.insert(dxc.final.end(),
2433 {lvl["Ar_2P3"], lvl["Ar_2P5"], lvl["Ar_2P6"],
2434 lvl["Ar_2P7"], lvl["Ar_2P8"], lvl["Ar_2P9"]});
2435 // Transfer to 4s states
2436 // Chang and Setser, J. Chem. Phys. 69 (1978), 3885-3897
2437 constexpr double k4s = 3.9e-20;
2438 dxc.p.resize(dxc.p.size() + levels4s.size(), 0.25 * k4s * nAr);
2439 dxc.final.insert(dxc.final.end(), levels4s.begin(), levels4s.end());
2440 dxc.type.resize(dxc.p.size(), DxcTypeCollNonIon);
2441 } else if (level == "Ar_2P5") {
2442 // Collisional population transfer within 4p levels
2443 // T. D. Nguyen and N. Sadeghi, Phys. Rev. 18 (1978), 1388-1395
2444 constexpr double k54 = 1.7e-21;
2445 constexpr double k56 = 11.3e-21;
2446 constexpr double k58 = 9.5e-21;
2447 dxc.p.insert(dxc.p.end(), {k54 * nAr, k56 * nAr, k58 * nAr});
2448 dxc.final.insert(dxc.final.end(),
2449 {lvl["Ar_2P4"], lvl["Ar_2P6"], lvl["Ar_2P8"]});
2450 dxc.type.resize(dxc.p.size(), DxcTypeCollNonIon);
2451 } else if (level == "Ar_2P6") {
2452 // Collisional population transfer within 4p levels
2453 // T. D. Nguyen and N. Sadeghi, Phys. Rev. 18 (1978), 1388-1395
2454 constexpr double k67 = 4.1e-21;
2455 constexpr double k68 = 6.0e-21;
2456 constexpr double k69 = 1.0e-21;
2457 dxc.p.insert(dxc.p.end(), {k67 * nAr, k68 * nAr, k69 * nAr});
2458 dxc.final.insert(dxc.final.end(),
2459 {lvl["Ar_2P7"], lvl["Ar_2P8"], lvl["Ar_2P9"]});
2460 dxc.type.resize(dxc.p.size(), DxcTypeCollNonIon);
2461 } else if (level == "Ar_2P7") {
2462 // Collisional population transfer within 4p levels
2463 // T. D. Nguyen and N. Sadeghi, Phys. Rev. 18 (1978), 1388-1395
2464 constexpr double k76 = 2.5e-21;
2465 constexpr double k78 = 14.3e-21;
2466 constexpr double k79 = 23.3e-21;
2467 dxc.p.insert(dxc.p.end(), {k76 * nAr, k78 * nAr, k79 * nAr});
2468 dxc.final.insert(dxc.final.end(),
2469 {lvl["Ar_2P6"], lvl["Ar_2P8"], lvl["Ar_2P9"]});
2470 // Transfer to 4s states
2471 // Chang and Setser, J. Chem. Phys. 69 (1978), 3885-3897
2472 constexpr double k4s = 5.5e-20;
2473 dxc.p.resize(dxc.p.size() + levels4s.size(), 0.25 * k4s * nAr);
2474 dxc.final.insert(dxc.final.end(), levels4s.begin(), levels4s.end());
2475 dxc.type.resize(dxc.p.size(), DxcTypeCollNonIon);
2476 } else if (level == "Ar_2P8") {
2477 // Collisional population transfer within 4p levels
2478 // T. D. Nguyen and N. Sadeghi, Phys. Rev. 18 (1978), 1388-1395
2479 constexpr double k86 = 0.3e-21;
2480 constexpr double k87 = 0.8e-21;
2481 constexpr double k89 = 18.2e-21;
2482 constexpr double k810 = 1.0e-21;
2483 dxc.p.insert(dxc.p.end(),
2484 {k86 * nAr, k87 * nAr, k89 * nAr, k810 * nAr});
2485 dxc.final.insert(dxc.final.end(), {lvl["Ar_2P6"], lvl["Ar_2P7"],
2486 lvl["Ar_2P9"], lvl["Ar_2P10"]});
2487 // Transfer to 4s states
2488 // Chang and Setser, J. Chem. Phys. 69 (1978), 3885-3897
2489 constexpr double k4s = 3.e-20;
2490 dxc.p.resize(dxc.p.size() + levels4s.size(), 0.25 * k4s * nAr);
2491 dxc.final.insert(dxc.final.end(), levels4s.begin(), levels4s.end());
2492 dxc.type.resize(dxc.p.size(), DxcTypeCollNonIon);
2493 } else if (level == "Ar_2P9") {
2494 // Collisional population transfer within 4p levels
2495 // T. D. Nguyen and N. Sadeghi, Phys. Rev. 18 (1978), 1388-1395
2496 constexpr double k98 = 6.8e-21;
2497 constexpr double k910 = 5.1e-21;
2498 dxc.p.insert(dxc.p.end(), {k98 * nAr, k910 * nAr});
2499 dxc.final.insert(dxc.final.end(), {lvl["Ar_2P8"], lvl["Ar_2P10"]});
2500 // Transfer to 4s states
2501 // Chang and Setser, J. Chem. Phys. 69 (1978), 3885-3897
2502 constexpr double k4s = 3.5e-20;
2503 dxc.p.resize(dxc.p.size() + levels4s.size(), 0.25 * k4s * nAr);
2504 dxc.final.insert(dxc.final.end(), levels4s.begin(), levels4s.end());
2505 dxc.type.resize(dxc.p.size(), DxcTypeCollNonIon);
2506 } else if (level == "Ar_2P10") {
2507 // Transfer to 4s states
2508 // Chang and Setser, J. Chem. Phys. 69 (1978), 3885-3897
2509 constexpr double k4s = 2.0e-20;
2510 dxc.p.resize(dxc.p.size() + levels4s.size(), 0.25 * k4s * nAr);
2511 dxc.final.insert(dxc.final.end(), levels4s.begin(), levels4s.end());
2512 dxc.type.resize(dxc.p.size(), DxcTypeCollNonIon);
2513 }
2514 const std::vector<int> levels4p = {
2515 lvl["Ar_2P10"], lvl["Ar_2P9"], lvl["Ar_2P8"], lvl["Ar_2P7"],
2516 lvl["Ar_2P6"], lvl["Ar_2P5"], lvl["Ar_2P4"], lvl["Ar_2P3"],
2517 lvl["Ar_2P2"], lvl["Ar_2P1"]};
2518 if (level == "Ar_3D6" || level == "Ar_3D5" || level == "Ar_3D3" ||
2519 level == "Ar_3D4!" || level == "Ar_3D4" || level == "Ar_3D1!!" ||
2520 level == "Ar_3D1!" || level == "Ar_3D2" || level == "Ar_3S1!!!!" ||
2521 level == "Ar_3S1!!" || level == "Ar_3S1!!!" || level == "Ar_3S1!" ||
2522 level == "Ar_2S5" || level == "Ar_2S4" || level == "Ar_2S3" ||
2523 level == "Ar_2S2") {
2524 // 3d and 5s levels
2525 // Transfer to 4p levels
2526 // Parameter to be tuned (order of magnitude guess).
2527 constexpr double k4p = 1.e-20;
2528 dxc.p.resize(dxc.p.size() + levels4p.size(), 0.1 * k4p * nAr);
2529 dxc.final.insert(dxc.final.end(), levels4p.begin(), levels4p.end());
2530 dxc.type.resize(dxc.p.size(), DxcTypeCollNonIon);
2531 } else if (level == "Ar_4D5" || level == "Ar_3S4" || level == "Ar_4D2" ||
2532 level == "Ar_4S1!" || level == "Ar_3S2" || level == "Ar_5D5" ||
2533 level == "Ar_4S4" || level == "Ar_5D2" || level == "Ar_6D5" ||
2534 level == "Ar_5S1!" || level == "Ar_4S2" || level == "Ar_5S4" ||
2535 level == "Ar_6D2") {
2536 // Transfer to 4p levels
2537 // Parameter to be tuned (order of magnitude guess).
2538 constexpr double k4p = 1.e-20;
2539 dxc.p.resize(dxc.p.size() + levels4p.size(), 0.1 * k4p * nAr);
2540 dxc.final.insert(dxc.final.end(), levels4p.begin(), levels4p.end());
2541 dxc.type.resize(dxc.p.size(), DxcTypeCollNonIon);
2542 // Hornbeck-Molnar ionisation
2543 // P. Becker and F. Lampe, J. Chem. Phys. 42 (1965), 3857-3863
2544 // A. Bogaerts and R. Gijbels, Phys. Rev. A 52 (1995), 3743-3751
2545 // This value seems high, to be checked!
2546 constexpr double kHM = 2.e-18;
2547 constexpr bool useHornbeckMolnar = true;
2548 if (useHornbeckMolnar) {
2549 dxc.p.push_back(kHM * nAr);
2550 dxc.final.push_back(lvl["Ar_Dimer"]);
2551 dxc.type.push_back(DxcTypeCollIon);
2552 }
2553 }
2554 }
2555 }
2556
2557 // Collisional deexcitation by quenching gases.
2558 int iCO2 = -1;
2559 int iCH4 = -1;
2560 int iC2H6 = -1;
2561 int iIso = -1;
2562 int iC2H2 = -1;
2563 int iCF4 = -1;
2564 for (unsigned int i = 0; i < m_nComponents; ++i) {
2565 if (m_gas[i] == "CO2")
2566 iCO2 = i;
2567 else if (m_gas[i] == "CH4")
2568 iCH4 = i;
2569 else if (m_gas[i] == "C2H6")
2570 iC2H6 = i;
2571 else if (m_gas[i] == "C2H2")
2572 iC2H2 = i;
2573 else if (m_gas[i] == "CF4")
2574 iCF4 = i;
2575 else if (m_gas[i] == "iC4H10")
2576 iIso = i;
2577 }
2578
2579 // Collision radii for hard-sphere approximation.
2580 constexpr double rAr3d = 436.e-10;
2581 constexpr double rAr5s = 635.e-10;
2582
2583 if (iAr >= 0 && iCO2 >= 0) {
2584 // Partial density of CO2
2585 const double nQ = GetNumberDensity() * m_fraction[iCO2];
2586 // Collision radius
2587 constexpr double rCO2 = 165.e-10;
2588 for (auto& dxc : m_deexcitations) {
2589 std::string level = dxc.label;
2590 // Photoabsorption cross-section and ionisation yield
2591 double pacs = 0., eta = 0.;
2592 optData.GetPhotoabsorptionCrossSection("CO2", dxc.energy, pacs, eta);
2593 const double pPenningWK = pow(eta, 0.4);
2594 if (level == "Ar_1S5") {
2595 // Rate constant from Velazco et al., J. Chem. Phys. 69 (1978)
2596 constexpr double kQ = 5.3e-19;
2597 dxc.p.push_back(kQ * nQ);
2598 dxc.type.push_back(DxcTypeCollNonIon);
2599 } else if (level == "Ar_1S4") {
2600 // Rate constant from Velazco et al., J. Chem. Phys. 69 (1978)
2601 constexpr double kQ = 5.0e-19;
2602 dxc.p.push_back(kQ * nQ);
2603 dxc.type.push_back(DxcTypeCollNonIon);
2604 } else if (level == "Ar_1S3") {
2605 constexpr double kQ = 5.9e-19;
2606 dxc.p.push_back(kQ * nQ);
2607 dxc.type.push_back(DxcTypeCollNonIon);
2608 } else if (level == "Ar_1S2") {
2609 constexpr double kQ = 7.4e-19;
2610 dxc.p.push_back(kQ * nQ);
2611 dxc.type.push_back(DxcTypeCollNonIon);
2612 } else if (level == "Ar_2P8") {
2613 // Rate constant from Sadeghi et al., J. Chem. Phys. 115 (2001)
2614 constexpr double kQ = 6.4e-19;
2615 dxc.p.push_back(kQ * nQ);
2616 dxc.type.push_back(DxcTypeCollNonIon);
2617 } else if (level == "Ar_2P6") {
2618 // Rate constant from Sadeghi et al.
2619 constexpr double kQ = 6.1e-19;
2620 dxc.p.push_back(kQ * nQ);
2621 dxc.type.push_back(DxcTypeCollNonIon);
2622 } else if (level == "Ar_2P5") {
2623 // Rate constant from Sadeghi et al.
2624 constexpr double kQ = 6.6e-19;
2625 dxc.p.push_back(kQ * nQ);
2626 dxc.type.push_back(DxcTypeCollNonIon);
2627 } else if (level == "Ar_2P1") {
2628 // Rate constant from Sadeghi et al.
2629 constexpr double kQ = 6.2e-19;
2630 dxc.p.push_back(kQ * nQ);
2631 dxc.type.push_back(DxcTypeCollNonIon);
2632 } else if (level == "Ar_2P10" || level == "Ar_2P9" || level == "Ar_2P7" ||
2633 level == "Ar_2P4" || level == "Ar_2P3" || level == "Ar_2P2") {
2634 // Average of 4p rate constants from Sadeghi et al.
2635 constexpr double kQ = 6.33e-19;
2636 dxc.p.push_back(kQ * nQ);
2637 dxc.type.push_back(DxcTypeCollNonIon);
2638 } else if (dxc.osc > 0.) {
2639 // Higher resonance levels
2640 // Calculate rate constant from Watanabe-Katsuura formula.
2641 const double kQ = RateConstantWK(dxc.energy, dxc.osc, pacs, iAr, iCO2);
2642 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2643 } else if (level == "Ar_3D6" || level == "Ar_3D3" || level == "Ar_3D4!" ||
2644 level == "Ar_3D4" || level == "Ar_3D1!!" ||
2645 level == "Ar_3D1!" || level == "Ar_3S1!!!!" ||
2646 level == "Ar_3S1!!" || level == "Ar_3S1!!!") {
2647 // Non-resonant 3d levels
2648 const double kQ = RateConstantHardSphere(rAr3d, rCO2, iAr, iCO2);
2649 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2650 } else if (level == "Ar_2S5" || level == "Ar_2S3") {
2651 // Non-resonant 5s levels
2652 const double kQ = RateConstantHardSphere(rAr5s, rCO2, iAr, iCO2);
2653 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2654 }
2655 dxc.final.resize(dxc.p.size(), -1);
2656 }
2657 }
2658 if (iAr >= 0 && iCH4 >= 0) {
2659 // Partial density of methane
2660 const double nQ = GetNumberDensity() * m_fraction[iCH4];
2661 // Collision radius
2662 constexpr double rCH4 = 190.e-10;
2663 for (auto& dxc : m_deexcitations) {
2664 std::string level = dxc.label;
2665 // Photoabsorption cross-section and ionisation yield
2666 double pacs = 0., eta = 0.;
2667 optData.GetPhotoabsorptionCrossSection("CH4", dxc.energy, pacs, eta);
2668 const double pPenningWK = pow(eta, 0.4);
2669 if (level == "Ar_1S5") {
2670 // Rate constant from Chen and Setser, J. Phys. Chem. 95 (1991)
2671 constexpr double kQ = 4.55e-19;
2672 dxc.p.push_back(kQ * nQ);
2673 dxc.type.push_back(DxcTypeCollNonIon);
2674 } else if (level == "Ar_1S4") {
2675 // Rate constant from Velazco et al., J. Chem. Phys. 69 (1978)
2676 constexpr double kQ = 4.5e-19;
2677 dxc.p.push_back(kQ * nQ);
2678 dxc.type.push_back(DxcTypeCollNonIon);
2679 } else if (level == "Ar_1S3") {
2680 // Rate constant from Chen and Setser
2681 constexpr double kQ = 5.30e-19;
2682 dxc.p.push_back(kQ * nQ);
2683 dxc.type.push_back(DxcTypeCollNonIon);
2684 } else if (level == "Ar_1S2") {
2685 // Rate constant from Velazco et al.
2686 constexpr double kQ = 5.7e-19;
2687 dxc.p.push_back(kQ * nQ);
2688 dxc.type.push_back(DxcTypeCollNonIon);
2689 } else if (level == "Ar_2P8") {
2690 // Rate constant from Sadeghi et al., J. Chem. Phys. 115 (2001)
2691 constexpr double kQ = 7.4e-19;
2692 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2693 } else if (level == "Ar_2P6") {
2694 constexpr double kQ = 3.4e-19; // Sadeghi
2695 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2696 } else if (level == "Ar_2P5") {
2697 constexpr double kQ = 6.0e-19; // Sadeghi
2698 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2699 } else if (level == "Ar_2P1") {
2700 constexpr double kQ = 9.3e-19; // Sadeghi
2701 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2702 } else if (level == "Ar_2P10" || level == "Ar_2P9" || level == "Ar_2P7" ||
2703 level == "Ar_2P4" || level == "Ar_2P3" || level == "Ar_2P2") {
2704 // Average of rate constants given by Sadeghi et al.
2705 constexpr double kQ = 6.53e-19;
2706 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2707 } else if (dxc.osc > 0.) {
2708 // Higher resonance levels
2709 // Calculate rate constant from Watanabe-Katsuura formula.
2710 const double kQ = RateConstantWK(dxc.energy, dxc.osc, pacs, iAr, iCH4);
2711 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2712 } else if (level == "Ar_3D6" || level == "Ar_3D3" || level == "Ar_3D4!" ||
2713 level == "Ar_3D4" || level == "Ar_3D1!!" ||
2714 level == "Ar_3D1!" || level == "Ar_3S1!!!!" ||
2715 level == "Ar_3S1!!" || level == "Ar_3S1!!!") {
2716 // Non-resonant 3d levels
2717 const double kQ = RateConstantHardSphere(rAr3d, rCH4, iAr, iCH4);
2718 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2719 } else if (level == "Ar_2S5" || level == "Ar_2S3") {
2720 // Non-resonant 5s levels
2721 const double kQ = RateConstantHardSphere(rAr5s, rCH4, iAr, iCH4);
2722 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2723 }
2724 dxc.final.resize(dxc.p.size(), -1);
2725 }
2726 }
2727 if (iAr >= 0 && iC2H6 >= 0) {
2728 // Partial density of ethane
2729 const double nQ = GetNumberDensity() * m_fraction[iC2H6];
2730 // Collision radius
2731 constexpr double rC2H6 = 195.e-10;
2732 for (auto& dxc : m_deexcitations) {
2733 std::string level = dxc.label;
2734 // Photoabsorption cross-section and ionisation yield
2735 double pacs = 0., eta = 0.;
2736 optData.GetPhotoabsorptionCrossSection("C2H6", dxc.energy, pacs, eta);
2737 const double pPenningWK = pow(eta, 0.4);
2738 if (level == "Ar_1S5") {
2739 // Rate constant from Chen and Setser, J. Phys. Chem. 95 (1991)
2740 constexpr double kQ = 5.29e-19;
2741 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2742 } else if (level == "Ar_1S4") {
2743 // Rate constant from Velazco et al., J. Chem. Phys. 69 (1978)
2744 constexpr double kQ = 6.2e-19;
2745 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2746 } else if (level == "Ar_1S3") {
2747 // Rate constant from Chen and Setser
2748 constexpr double kQ = 6.53e-19;
2749 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2750 } else if (level == "Ar_1S2") {
2751 // Rate constant from Velazco et al.
2752 constexpr double kQ = 10.7e-19;
2753 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2754 } else if (level == "Ar_2P8") {
2755 // Rate constant from Sadeghi et al., J. Chem. Phys. 115 (2001)
2756 constexpr double kQ = 9.2e-19;
2757 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2758 } else if (level == "Ar_2P6") {
2759 constexpr double kQ = 4.8e-19; // Sadeghi
2760 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2761 } else if (level == "Ar_2P5") {
2762 constexpr double kQ = 9.9e-19; // Sadeghi
2763 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2764 } else if (level == "Ar_2P1") {
2765 constexpr double kQ = 11.0e-19; // Sadeghi
2766 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2767 } else if (level == "Ar_2P10" || level == "Ar_2P9" || level == "Ar_2P7" ||
2768 level == "Ar_2P4" || level == "Ar_2P3" || level == "Ar_2P2") {
2769 // Average of rate constants given by Sadeghi et al.
2770 constexpr double kQ = 8.7e-19;
2771 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2772 } else if (dxc.osc > 0.) {
2773 // Higher resonance levels
2774 // Calculate rate constant from Watanabe-Katsuura formula.
2775 const double kQ = RateConstantWK(dxc.energy, dxc.osc, pacs, iAr, iC2H6);
2776 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2777 } else if (level == "Ar_3D6" || level == "Ar_3D3" || level == "Ar_3D4!" ||
2778 level == "Ar_3D4" || level == "Ar_3D1!!" ||
2779 level == "Ar_3D1!" || level == "Ar_3S1!!!!" ||
2780 level == "Ar_3S1!!" || level == "Ar_3S1!!!") {
2781 // Non-resonant 3d levels
2782 const double kQ = RateConstantHardSphere(rAr3d, rC2H6, iAr, iC2H6);
2783 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2784 } else if (level == "Ar_2S5" || level == "Ar_2S3") {
2785 // Non-resonant 5s levels
2786 const double kQ = RateConstantHardSphere(rAr5s, rC2H6, iAr, iC2H6);
2787 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2788 }
2789 dxc.final.resize(dxc.p.size(), -1);
2790 }
2791 }
2792 if (iAr >= 0 && iIso >= 0) {
2793 // Partial density of isobutane
2794 const double nQ = GetNumberDensity() * m_fraction[iIso];
2795 // Collision radius
2796 constexpr double rIso = 250.e-10;
2797 // For the 4p levels, the rate constants are estimated by scaling
2798 // the values for ethane.
2799 // Ar radius [pm]
2800 constexpr double r4p = 340.;
2801 // Molecular radii are 195 pm for ethane, 250 pm for isobutane.
2802 constexpr double fr = (r4p + 250.) / (r4p + 195.);
2803 // Masses [amu]
2804 constexpr double mAr = 39.9;
2805 constexpr double mEth = 30.1;
2806 constexpr double mIso = 58.1;
2807 // Scaling factor.
2808 const double f4p =
2809 fr * fr * sqrt((mEth / mIso) * (mAr + mIso) / (mAr + mEth));
2810 for (auto& dxc : m_deexcitations) {
2811 std::string level = dxc.label;
2812 // Photoabsorption cross-section and ionisation yield
2813 double pacs = 0., eta = 0.;
2814 // Use n-butane as approximation for isobutane.
2815 optData.GetPhotoabsorptionCrossSection("nC4H10", dxc.energy, pacs, eta);
2816 const double pPenningWK = pow(eta, 0.4);
2817 if (level == "Ar_1S5") {
2818 // Rate constant from
2819 // Piper et al., J. Chem. Phys. 59 (1973), 3323-3340
2820 constexpr double kQ = 7.1e-19;
2821 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2822 } else if (level == "Ar_1S4") {
2823 // Rate constant from Piper et al.
2824 constexpr double kQ = 6.1e-19;
2825 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2826 } else if (level == "Ar_1S3") {
2827 // Rate constant for n-butane from
2828 // Velazco et al., J. Chem. Phys. 69 (1978)
2829 constexpr double kQ = 8.5e-19;
2830 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2831 } else if (level == "Ar_1S2") {
2832 // Rate constant from Piper et al.
2833 constexpr double kQ = 11.0e-19;
2834 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2835 } else if (level == "Ar_2P8") {
2836 constexpr double kEth = 9.2e-19; // ethane
2837 AddPenningDeexcitation(dxc, f4p * kEth * nQ, pPenningWK);
2838 } else if (level == "Ar_2P6") {
2839 constexpr double kEth = 4.8e-19; // ethane
2840 AddPenningDeexcitation(dxc, f4p * kEth * nQ, pPenningWK);
2841 } else if (level == "Ar_2P5") {
2842 const double kEth = 9.9e-19; // ethane
2843 AddPenningDeexcitation(dxc, f4p * kEth * nQ, pPenningWK);
2844 } else if (level == "Ar_2P1") {
2845 const double kEth = 11.0e-19; // ethane
2846 AddPenningDeexcitation(dxc, f4p * kEth * nQ, pPenningWK);
2847 } else if (level == "Ar_2P10" || level == "Ar_2P9" || level == "Ar_2P7" ||
2848 level == "Ar_2P4" || level == "Ar_2P3" || level == "Ar_2P2") {
2849 constexpr double kEth = 5.5e-19; // ethane
2850 AddPenningDeexcitation(dxc, f4p * kEth * nQ, pPenningWK);
2851 } else if (dxc.osc > 0.) {
2852 // Higher resonance levels
2853 // Calculate rate constant from Watanabe-Katsuura formula.
2854 const double kQ = RateConstantWK(dxc.energy, dxc.osc, pacs, iAr, iIso);
2855 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2856 } else if (level == "Ar_3D6" || level == "Ar_3D3" || level == "Ar_3D4!" ||
2857 level == "Ar_3D4" || level == "Ar_3D1!!" ||
2858 level == "Ar_3D1!" || level == "Ar_3S1!!!!" ||
2859 level == "Ar_3S1!!" || level == "Ar_3S1!!!") {
2860 // Non-resonant 3d levels
2861 const double kQ = RateConstantHardSphere(rAr3d, rIso, iAr, iIso);
2862 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2863 } else if (level == "Ar_2S5" || level == "Ar_2S3") {
2864 // Non-resonant 5s levels
2865 const double kQ = RateConstantHardSphere(rAr5s, rIso, iAr, iIso);
2866 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2867 }
2868 dxc.final.resize(dxc.p.size(), -1);
2869 }
2870 }
2871 if (iAr >= 0 && iC2H2 >= 0) {
2872 // Partial density of acetylene
2873 const double nQ = GetNumberDensity() * m_fraction[iC2H2];
2874 // Collision radius
2875 constexpr double rC2H2 = 165.e-10;
2876 for (auto& dxc : m_deexcitations) {
2877 std::string level = dxc.label;
2878 // Photoabsorption cross-section and ionisation yield
2879 double pacs = 0., eta = 0.;
2880 optData.GetPhotoabsorptionCrossSection("C2H2", dxc.energy, pacs, eta);
2881 const double pPenningWK = pow(eta, 0.4);
2882 if (level == "Ar_1S5") {
2883 // Rate constant from Velazco et al., J. Chem. Phys. 69 (1978)
2884 constexpr double kQ = 5.6e-19;
2885 // Branching ratio for ionization according to
2886 // Jones et al., J. Phys. Chem. 89 (1985)
2887 // p = 0.61, p = 0.74 (agrees roughly with WK estimate)
2888 AddPenningDeexcitation(dxc, kQ * nQ, 0.61);
2889 } else if (level == "Ar_1S4") {
2890 // Rate constant from Velazco et al.
2891 constexpr double kQ = 4.6e-19;
2892 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2893 } else if (level == "Ar_1S3") {
2894 constexpr double kQ = 5.6e-19;
2895 AddPenningDeexcitation(dxc, kQ * nQ, 0.61);
2896 } else if (level == "Ar_1S2") {
2897 // Rate constant from Velazco et al.
2898 constexpr double kQ = 8.7e-19;
2899 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2900 } else if (level == "Ar_2P8") {
2901 // Rate constant from Sadeghi et al., J. Chem. Phys. 115 (2001)
2902 constexpr double kQ = 5.0e-19;
2903 AddPenningDeexcitation(dxc, kQ * nQ, 0.3);
2904 } else if (level == "Ar_2P6") {
2905 constexpr double kQ = 5.7e-19; // Sadeghi
2906 AddPenningDeexcitation(dxc, kQ * nQ, 0.3);
2907 } else if (level == "Ar_2P5") {
2908 constexpr double kQ = 6.0e-19; // Sadeghi
2909 AddPenningDeexcitation(dxc, kQ * nQ, 0.3);
2910 } else if (level == "Ar_2P1") {
2911 constexpr double kQ = 5.3e-19; // Sadeghi
2912 AddPenningDeexcitation(dxc, kQ * nQ, 0.3);
2913 } else if (level == "Ar_2P10" || level == "Ar_2P9" || level == "Ar_2P7" ||
2914 level == "Ar_2P4" || level == "Ar_2P3" || level == "Ar_2P2") {
2915 // Average of rate constants given by Sadeghi et al.
2916 constexpr double kQ = 5.5e-19;
2917 AddPenningDeexcitation(dxc, kQ * nQ, 0.3);
2918 } else if (dxc.osc > 0.) {
2919 // Higher resonance levels
2920 // Calculate rate constant from Watanabe-Katsuura formula.
2921 const double kQ = RateConstantWK(dxc.energy, dxc.osc, pacs, iAr, iC2H2);
2922 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2923 } else if (level == "Ar_3D6" || level == "Ar_3D3" || level == "Ar_3D4!" ||
2924 level == "Ar_3D4" || level == "Ar_3D1!!" ||
2925 level == "Ar_3D1!" || level == "Ar_3S1!!!!" ||
2926 level == "Ar_3S1!!" || level == "Ar_3S1!!!") {
2927 // Non-resonant 3d levels
2928 const double kQ = RateConstantHardSphere(rAr3d, rC2H2, iAr, iC2H2);
2929 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2930 } else if (level == "Ar_2S5" || level == "Ar_2S3") {
2931 // Non-resonant 5s levels
2932 const double kQ = RateConstantHardSphere(rAr5s, rC2H2, iAr, iC2H2);
2933 AddPenningDeexcitation(dxc, kQ * nQ, pPenningWK);
2934 }
2935 dxc.final.resize(dxc.p.size(), -1);
2936 }
2937 }
2938 if (iAr >= 0 && iCF4 >= 0) {
2939 // Partial density of CF4
2940 const double nQ = GetNumberDensity() * m_fraction[iCF4];
2941 // Collision radius
2942 constexpr double rCF4 = 235.e-10;
2943 for (auto& dxc : m_deexcitations) {
2944 std::string level = dxc.label;
2945 // Photoabsorption cross-section and ionisation yield
2946 double pacs = 0., eta = 0.;
2947 optData.GetPhotoabsorptionCrossSection("CF4", dxc.energy, pacs, eta);
2948 if (level == "Ar_1S5") {
2949 // Rate constant from Chen and Setser
2950 constexpr double kQ = 0.33e-19;
2951 dxc.p.push_back(kQ * nQ);
2952 } else if (level == "Ar_1S3") {
2953 // Rate constant from Chen and Setser
2954 constexpr double kQ = 0.26e-19;
2955 dxc.p.push_back(kQ * nQ);
2956 } else if (level == "Ar_2P8") {
2957 // Rate constant from Sadeghi et al.
2958 constexpr double kQ = 1.7e-19;
2959 dxc.p.push_back(kQ * nQ);
2960 } else if (level == "Ar_2P6") {
2961 constexpr double kQ = 1.7e-19; // Sadeghi
2962 dxc.p.push_back(kQ * nQ);
2963 } else if (level == "Ar_2P5") {
2964 constexpr double kQ = 1.6e-19; // Sadeghi
2965 dxc.p.push_back(kQ * nQ);
2966 } else if (level == "Ar_2P1") {
2967 constexpr double kQ = 2.2e-19; // Sadeghi
2968 dxc.p.push_back(kQ * nQ);
2969 } else if (level == "Ar_2P10" || level == "Ar_2P9" || level == "Ar_2P7" ||
2970 level == "Ar_2P4" || level == "Ar_2P3" || level == "Ar_2P2") {
2971 // Average of 4p rate constants from Sadeghi et al.
2972 constexpr double kQ = 1.8e-19;
2973 dxc.p.push_back(kQ * nQ);
2974 } else if (dxc.osc > 0.) {
2975 // Resonance levels
2976 // Calculate rate constant from Watanabe-Katsuura formula.
2977 const double kQ = RateConstantWK(dxc.energy, dxc.osc, pacs, iAr, iCF4);
2978 dxc.p.push_back(kQ * nQ);
2979 } else if (level == "Ar_3D6" || level == "Ar_3D3" || level == "Ar_3D4!" ||
2980 level == "Ar_3D4" || level == "Ar_3D1!!" ||
2981 level == "Ar_3D1!" || level == "Ar_3S1!!!!" ||
2982 level == "Ar_3S1!!" || level == "Ar_3S1!!!") {
2983 // Non-resonant 3d levels
2984 const double kQ = RateConstantHardSphere(rAr3d, rCF4, iAr, iCF4);
2985 dxc.p.push_back(kQ * nQ);
2986 } else if (level == "Ar_2S5" || level == "Ar_2S3") {
2987 // Non-resonant 5s levels
2988 const double kQ = RateConstantHardSphere(rAr5s, rCF4, iAr, iCF4);
2989 dxc.p.push_back(kQ * nQ);
2990 }
2991 dxc.type.resize(dxc.p.size(), DxcTypeCollNonIon);
2992 dxc.final.resize(dxc.p.size(), -1);
2993 }
2994 }
2995
2996 if ((m_debug || verbose) && nDeexcitations > 0) {
2997 std::cout << m_className << "::ComputeDeexcitationTable:\n"
2998 << " Level Energy [eV] Lifetimes [ns]\n"
2999 << " Total Radiative "
3000 << " Collisional\n"
3001 << " "
3002 << " Ionisation Transfer Loss\n";
3003 }
3004
3005 for (auto& dxc : m_deexcitations) {
3006 // Calculate the total decay rate of each level.
3007 dxc.rate = 0.;
3008 double fRad = 0.;
3009 double fCollIon = 0., fCollTransfer = 0., fCollLoss = 0.;
3010 const unsigned int nChannels = dxc.type.size();
3011 for (unsigned int j = 0; j < nChannels; ++j) {
3012 dxc.rate += dxc.p[j];
3013 if (dxc.type[j] == DxcTypeRad) {
3014 fRad += dxc.p[j];
3015 } else if (dxc.type[j] == DxcTypeCollIon) {
3016 fCollIon += dxc.p[j];
3017 } else if (dxc.type[j] == DxcTypeCollNonIon) {
3018 if (dxc.final[j] < 0) {
3019 fCollLoss += dxc.p[j];
3020 } else {
3021 fCollTransfer += dxc.p[j];
3022 }
3023 } else {
3024 std::cerr << m_className << "::ComputeDeexcitationTable:\n "
3025 << "Unknown type of deexcitation channel (level " << dxc.label
3026 << "). Program bug!\n";
3027 }
3028 }
3029 if (dxc.rate > 0.) {
3030 // Print the radiative and collisional decay rates.
3031 if (m_debug || verbose) {
3032 std::cout << std::setw(12) << dxc.label << " " << std::fixed
3033 << std::setprecision(3) << std::setw(7) << dxc.energy << " "
3034 << std::setw(10) << 1. / dxc.rate << " ";
3035 if (fRad > 0.) {
3036 std::cout << std::fixed << std::setprecision(3) << std::setw(10)
3037 << 1. / fRad << " ";
3038 } else {
3039 std::cout << "---------- ";
3040 }
3041 if (fCollIon > 0.) {
3042 std::cout << std::fixed << std::setprecision(3) << std::setw(10)
3043 << 1. / fCollIon << " ";
3044 } else {
3045 std::cout << "---------- ";
3046 }
3047 if (fCollTransfer > 0.) {
3048 std::cout << std::fixed << std::setprecision(3) << std::setw(10)
3049 << 1. / fCollTransfer << " ";
3050 } else {
3051 std::cout << "---------- ";
3052 }
3053 if (fCollLoss > 0.) {
3054 std::cout << std::fixed << std::setprecision(3) << std::setw(10)
3055 << 1. / fCollLoss << "\n";
3056 } else {
3057 std::cout << "---------- \n";
3058 }
3059 }
3060 // Normalise the decay branching ratios.
3061 for (unsigned int j = 0; j < nChannels; ++j) {
3062 dxc.p[j] /= dxc.rate;
3063 if (j > 0) dxc.p[j] += dxc.p[j - 1];
3064 }
3065 }
3066 }
3067}
3068
3069double MediumMagboltz::RateConstantWK(const double energy, const double osc,
3070 const double pacs, const int igas1,
3071 const int igas2) const {
3072 // Calculate rate constant from Watanabe-Katsuura formula.
3073 const double m1 = ElectronMassGramme / (m_rgas[igas1] - 1.);
3074 const double m2 = ElectronMassGramme / (m_rgas[igas2] - 1.);
3075 // Compute the reduced mass.
3076 double mR = (m1 * m2 / (m1 + m2)) / AtomicMassUnit;
3077 const double uA = (RydbergEnergy / energy) * osc;
3078 const double uQ = (2 * RydbergEnergy / energy) * pacs /
3079 (4 * Pi2 * FineStructureConstant * BohrRadius * BohrRadius);
3080 return 2.591e-19 * pow(uA * uQ, 0.4) * pow(m_temperature / mR, 0.3);
3081}
3082
3083double MediumMagboltz::RateConstantHardSphere(const double r1, const double r2,
3084 const int igas1,
3085 const int igas2) const {
3086 // Hard sphere cross-section
3087 const double r = r1 + r2;
3088 const double sigma = r * r * Pi;
3089 // Reduced mass
3090 const double m1 = ElectronMass / (m_rgas[igas1] - 1.);
3091 const double m2 = ElectronMass / (m_rgas[igas2] - 1.);
3092 const double mR = m1 * m2 / (m1 + m2);
3093 // Relative velocity
3094 const double vel =
3095 SpeedOfLight * sqrt(8. * BoltzmannConstant * m_temperature / (Pi * mR));
3096 return sigma * vel;
3097}
3098
3099void MediumMagboltz::ComputeDeexcitation(int iLevel, int& fLevel) {
3100 if (!m_useDeexcitation) {
3101 std::cerr << m_className << "::ComputeDeexcitation: Not enabled.\n";
3102 return;
3103 }
3104
3105 // Make sure that the tables are updated.
3106 if (m_isChanged) {
3107 if (!Mixer()) {
3108 PrintErrorMixer(m_className + "::ComputeDeexcitation");
3109 return;
3110 }
3111 m_isChanged = false;
3112 }
3113
3114 if (iLevel < 0 || iLevel >= (int)m_nTerms) {
3115 std::cerr << m_className << "::ComputeDeexcitation: Index out of range.\n";
3116 return;
3117 }
3118
3119 iLevel = m_iDeexcitation[iLevel];
3120 if (iLevel < 0 || iLevel >= (int)m_deexcitations.size()) {
3121 std::cerr << m_className << "::ComputeDeexcitation:\n"
3122 << " Level is not deexcitable.\n";
3123 return;
3124 }
3125
3126 ComputeDeexcitationInternal(iLevel, fLevel);
3127 if (fLevel >= 0 && fLevel < (int)m_deexcitations.size()) {
3128 fLevel = m_deexcitations[fLevel].level;
3129 }
3130}
3131
3132void MediumMagboltz::ComputeDeexcitationInternal(int iLevel, int& fLevel) {
3133 m_dxcProducts.clear();
3134
3135 double t = 0.;
3136 fLevel = iLevel;
3137 while (iLevel >= 0 && iLevel < (int)m_deexcitations.size()) {
3138 const auto& dxc = m_deexcitations[iLevel];
3139 const int nChannels = dxc.p.size();
3140 if (dxc.rate <= 0. || nChannels <= 0) {
3141 // This level is a dead end.
3142 fLevel = iLevel;
3143 return;
3144 }
3145 // Determine the de-excitation time.
3146 t += -log(RndmUniformPos()) / dxc.rate;
3147 // Select the transition.
3148 fLevel = -1;
3149 int type = DxcTypeRad;
3150 const double r = RndmUniform();
3151 for (int j = 0; j < nChannels; ++j) {
3152 if (r <= dxc.p[j]) {
3153 fLevel = dxc.final[j];
3154 type = dxc.type[j];
3155 break;
3156 }
3157 }
3158 if (type == DxcTypeRad) {
3159 // Radiative decay
3160 dxcProd photon;
3161 photon.s = 0.;
3162 photon.t = t;
3163 photon.type = DxcProdTypePhoton;
3164 photon.energy = dxc.energy;
3165 if (fLevel >= 0) {
3166 // Decay to a lower lying excited state.
3167 photon.energy -= m_deexcitations[fLevel].energy;
3168 if (photon.energy < Small) photon.energy = Small;
3169 m_dxcProducts.push_back(std::move(photon));
3170 // Proceed with the next level in the cascade.
3171 iLevel = fLevel;
3172 } else {
3173 // Decay to ground state.
3174 double delta = RndmVoigt(0., dxc.sDoppler, dxc.gPressure);
3175 while (photon.energy + delta < Small || fabs(delta) >= dxc.width) {
3176 delta = RndmVoigt(0., dxc.sDoppler, dxc.gPressure);
3177 }
3178 photon.energy += delta;
3179 m_dxcProducts.push_back(std::move(photon));
3180 // Deexcitation cascade is over.
3181 fLevel = iLevel;
3182 return;
3183 }
3184 } else if (type == DxcTypeCollIon) {
3185 // Ionisation electron
3186 dxcProd electron;
3187 electron.s = 0.;
3188 electron.t = t;
3189 electron.type = DxcProdTypeElectron;
3190 electron.energy = dxc.energy;
3191 if (fLevel >= 0) {
3192 // Associative ionisation
3193 electron.energy -= m_deexcitations[fLevel].energy;
3194 if (electron.energy < Small) electron.energy = Small;
3195 ++m_nPenning;
3196 m_dxcProducts.push_back(std::move(electron));
3197 // Proceed with the next level in the cascade.
3198 iLevel = fLevel;
3199 } else {
3200 // Penning ionisation
3201 electron.energy -= m_minIonPot;
3202 if (electron.energy < Small) electron.energy = Small;
3203 ++m_nPenning;
3204 m_dxcProducts.push_back(std::move(electron));
3205 // Deexcitation cascade is over.
3206 fLevel = iLevel;
3207 return;
3208 }
3209 } else if (type == DxcTypeCollNonIon) {
3210 // Proceed with the next level in the cascade.
3211 iLevel = fLevel;
3212 } else {
3213 std::cerr << m_className << "::ComputeDeexcitationInternal:\n"
3214 << " Unknown deexcitation type (" << type << "). Bug!\n";
3215 // Abort the calculation.
3216 fLevel = iLevel;
3217 return;
3218 }
3219 }
3220}
3221
3222bool MediumMagboltz::ComputePhotonCollisionTable(const bool verbose) {
3223 OpticalData data;
3224 double cs;
3225 double eta;
3226
3227 // Atomic density
3228 const double dens = GetNumberDensity();
3229
3230 // Reset the collision rate arrays.
3231 m_cfTotGamma.assign(nEnergyStepsGamma, 0.);
3232 m_cfGamma.assign(nEnergyStepsGamma, std::vector<double>());
3233 csTypeGamma.clear();
3234
3235 m_nPhotonTerms = 0;
3236 for (unsigned int i = 0; i < m_nComponents; ++i) {
3237 const double prefactor = dens * SpeedOfLight * m_fraction[i];
3238 // Check if optical data for this gas is available.
3239 std::string gasname = m_gas[i];
3240 if (gasname == "iC4H10") {
3241 gasname = "nC4H10";
3242 if (m_debug || verbose) {
3243 std::cout << m_className << "::ComputePhotonCollisionTable:\n"
3244 << " Photoabsorption cross-section for "
3245 << "iC4H10 not available.\n"
3246 << " Using n-butane cross-section instead.\n";
3247 }
3248 }
3249 if (!data.IsAvailable(gasname)) return false;
3250 csTypeGamma.push_back(i * nCsTypesGamma + PhotonCollisionTypeIonisation);
3251 csTypeGamma.push_back(i * nCsTypesGamma + PhotonCollisionTypeInelastic);
3252 m_nPhotonTerms += 2;
3253 for (int j = 0; j < nEnergyStepsGamma; ++j) {
3254 // Retrieve total photoabsorption cross-section and ionisation yield.
3255 data.GetPhotoabsorptionCrossSection(gasname, (j + 0.5) * m_eStepGamma, cs,
3256 eta);
3257 m_cfTotGamma[j] += cs * prefactor;
3258 // Ionisation
3259 m_cfGamma[j].push_back(cs * prefactor * eta);
3260 // Inelastic absorption
3261 m_cfGamma[j].push_back(cs * prefactor * (1. - eta));
3262 }
3263 }
3264
3265 // If requested, write the cross-sections to file.
3266 if (m_useCsOutput) {
3267 std::ofstream csfile;
3268 csfile.open("csgamma.txt", std::ios::out);
3269 for (int j = 0; j < nEnergyStepsGamma; ++j) {
3270 csfile << (j + 0.5) * m_eStepGamma << " ";
3271 for (unsigned int i = 0; i < m_nPhotonTerms; ++i)
3272 csfile << m_cfGamma[j][i] << " ";
3273 csfile << "\n";
3274 }
3275 csfile.close();
3276 }
3277
3278 // Calculate the cumulative rates.
3279 for (int j = 0; j < nEnergyStepsGamma; ++j) {
3280 for (unsigned int i = 0; i < m_nPhotonTerms; ++i) {
3281 if (i > 0) m_cfGamma[j][i] += m_cfGamma[j][i - 1];
3282 }
3283 }
3284
3285 if (m_debug || verbose) {
3286 std::cout << m_className << "::ComputePhotonCollisionTable:\n";
3287 std::cout << " Energy [eV] Mean free path [um]\n";
3288 for (int i = 0; i < 10; ++i) {
3289 const int j = (2 * i + 1) * nEnergyStepsGamma / 20;
3290 const double en = (2 * i + 1) * m_eFinalGamma / 20;
3291 const double imfp = m_cfTotGamma[j] / SpeedOfLight;
3292 if (imfp > 0.) {
3293 printf(" %10.2f %18.4f\n", en, 1.e4 / imfp);
3294 } else {
3295 printf(" %10.2f ------------\n", en);
3296 }
3297 }
3298 }
3299
3300 if (!m_useDeexcitation) return true;
3301
3302 // Conversion factor from oscillator strength to cross-section
3303 constexpr double f2cs =
3304 FineStructureConstant * 2 * Pi2 * HbarC * HbarC / ElectronMass;
3305 // Discrete absorption lines
3306 int nResonanceLines = 0;
3307 for (auto& dxc : m_deexcitations) {
3308 if (dxc.osc < Small) continue;
3309 const double prefactor = dens * SpeedOfLight * m_fraction[dxc.gas];
3310 dxc.cf = prefactor * f2cs * dxc.osc;
3311 // Compute the line width due to Doppler broadening.
3312 const double mgas = ElectronMass / (m_rgas[dxc.gas] - 1.);
3313 const double wDoppler = sqrt(BoltzmannConstant * m_temperature / mgas);
3314 dxc.sDoppler = wDoppler * dxc.energy;
3315 // Compute the half width at half maximum due to resonance broadening.
3316 // A. W. Ali and H. R. Griem, Phys. Rev. 140, 1044
3317 // A. W. Ali and H. R. Griem, Phys. Rev. 144, 366
3318 const double kResBroad = 1.92 * Pi * sqrt(1. / 3.);
3319 dxc.gPressure = kResBroad * FineStructureConstant * pow(HbarC, 3) *
3320 dxc.osc * dens * m_fraction[dxc.gas] /
3321 (ElectronMass * dxc.energy);
3322 // Make an estimate for the width within which a photon can be
3323 // absorbed by the line
3324 constexpr double nWidths = 1000.;
3325 // Calculate the FWHM of the Voigt distribution according to the
3326 // approximation formula given in
3327 // Olivero and Longbothum, J. Quant. Spectr. Rad. Trans. 17, 233-236
3328 const double fwhmGauss = dxc.sDoppler * sqrt(2. * log(2.));
3329 const double fwhmLorentz = dxc.gPressure;
3330 const double fwhmVoigt =
3331 0.5 * (1.0692 * fwhmLorentz + sqrt(0.86639 * fwhmLorentz * fwhmLorentz +
3332 4 * fwhmGauss * fwhmGauss));
3333 dxc.width = nWidths * fwhmVoigt;
3334 ++nResonanceLines;
3335 }
3336
3337 if (nResonanceLines <= 0) {
3338 std::cerr << m_className << "::ComputePhotonCollisionTable:\n"
3339 << " No resonance lines found.\n";
3340 return true;
3341 }
3342
3343 if (!(m_debug || verbose)) return true;
3344 std::cout << m_className << "::ComputePhotonCollisionTable:\n "
3345 << "Discrete absorption lines:\n Energy [eV] "
3346 << "Line width (FWHM) [eV] Mean free path [um]\n "
3347 << " Doppler Pressure (peak)\n";
3348 for (const auto& dxc : m_deexcitations) {
3349 if (dxc.osc < Small) continue;
3350 const double wp = 2 * dxc.gPressure;
3351 const double wd = 2 * sqrt(2 * log(2.)) * dxc.sDoppler;
3352 const double imfpP =
3353 (dxc.cf / SpeedOfLight) * TMath::Voigt(0., dxc.sDoppler, wp);
3354 if (imfpP > 0.) {
3355 printf(" %6.3f +/- %6.1e %6.2e %6.3e %14.4f\n", dxc.energy,
3356 dxc.width, wd, wp, 1.e4 / imfpP);
3357 } else {
3358 printf(" %6.3f +/- %6.1e %6.2e %6.3e -------------\n", dxc.energy,
3359 dxc.width, wd, wp);
3360 }
3361 }
3362 return true;
3363}
3364
3366 const double e, const double bmag, const double btheta, const int ncoll,
3367 bool verbose, double& vx, double& vy, double& vz, double& dl, double& dt,
3368 double& alpha, double& eta, double& lor, double& vxerr, double& vyerr,
3369 double& vzerr, double& dlerr, double& dterr, double& alphaerr,
3370 double& etaerr, double& lorerr, double& alphatof,
3371 std::array<double, 6>& difftens) {
3372 // Initialize the values.
3373 vx = vy = vz = 0.;
3374 dl = dt = 0.;
3375 alpha = eta = alphatof = 0.;
3376 lor = 0.;
3377 vxerr = vyerr = vzerr = 0.;
3378 dlerr = dterr = 0.;
3379 alphaerr = etaerr = 0.;
3380 lorerr = 0.;
3381
3382 // Set input parameters in Magboltz common blocks.
3384 Magboltz::inpt_.nStep = 4000;
3385 Magboltz::inpt_.nAniso = 2;
3386 if (m_autoEnergyLimit) {
3387 Magboltz::inpt_.efinal = 0.;
3388 } else {
3389 Magboltz::inpt_.efinal = std::min(m_eMax, m_eHigh);
3390 Magboltz::inpt_.estep = m_eStep;
3391 }
3392 Magboltz::inpt_.tempc = m_temperature - ZeroCelsius;
3394 Magboltz::inpt_.ipen = 0;
3395 Magboltz::setp_.nmax = ncoll;
3396
3397 Magboltz::thrm_.ithrm = m_useGasMotion ? 1 : 0;
3398
3399 Magboltz::setp_.efield = e;
3400 // Convert from Tesla to kGauss.
3401 Magboltz::bfld_.bmag = bmag * 10.;
3402 // Convert from radians to degree.
3403 Magboltz::bfld_.btheta = btheta * RadToDegree;
3404
3405 // Set the gas composition in Magboltz.
3406 for (unsigned int i = 0; i < m_nComponents; ++i) {
3407 const int ng = GetGasNumberMagboltz(m_gas[i]);
3408 if (ng <= 0) {
3409 std::cerr << m_className << "::RunMagboltz:\n Gas " << m_gas[i]
3410 << " does not have a gas number in Magboltz.\n";
3411 return;
3412 }
3413 Magboltz::gasn_.ngasn[i] = ng;
3414 Magboltz::ratio_.frac[i] = 100 * m_fraction[i];
3415 }
3416
3417 // Run Magboltz.
3419
3420 // Velocities. Convert to cm / ns.
3421 vx = Magboltz::vel_.wx * 1.e-9;
3422 vxerr = Magboltz::velerr_.dwx;
3423 vy = Magboltz::vel_.wy * 1.e-9;
3424 vyerr = Magboltz::velerr_.dwy;
3425 vz = Magboltz::vel_.wz * 1.e-9;
3426 vzerr = Magboltz::velerr_.dwz;
3427
3428 // Calculate the Lorentz angle.
3429 const double vt = sqrt(vx * vx + vy * vy);
3430 const double v2 = (vx * vx + vy * vy + vz * vz);
3431 lor = atan2(vt, vz);
3432 if (vt > 0. && v2 > 0. && fabs(lor) > 0.) {
3433 const double dvx = vx * vxerr;
3434 const double dvy = vy * vyerr;
3435 const double dvz = vz * vzerr;
3436 const double a = vz / vt;
3437 lorerr = sqrt(a * a * (vx * vx * dvx * dvx + vy * vy * dvy * dvy) +
3438 vt * vt * dvz * dvz) /
3439 v2;
3440 lorerr /= lor;
3441 }
3442
3443 // Diffusion coefficients.
3444 dt = sqrt(0.2 * 0.5 * (Magboltz::diflab_.difxx + Magboltz::diflab_.difyy) /
3445 vz) *
3446 1.e-4;
3447 dterr = 0.5 * sqrt(Magboltz::diferl_.dfter * Magboltz::diferl_.dfter +
3448 vzerr * vzerr);
3449 dl = sqrt(0.2 * Magboltz::diflab_.difzz / vz) * 1.e-4;
3450 dlerr = 0.5 * sqrt(Magboltz::diferl_.dfler * Magboltz::diferl_.dfler +
3451 vzerr * vzerr);
3452 // Diffusion tensor.
3453 difftens[0] = 0.2e-4 * Magboltz::diflab_.difzz / vz;
3454 difftens[1] = 0.2e-4 * Magboltz::diflab_.difxx / vz;
3455 difftens[2] = 0.2e-4 * Magboltz::diflab_.difyy / vz;
3456 difftens[3] = 0.2e-4 * Magboltz::diflab_.difxz / vz;
3457 difftens[4] = 0.2e-4 * Magboltz::diflab_.difyz / vz;
3458 difftens[5] = 0.2e-4 * Magboltz::diflab_.difxy / vz;
3459 // Townsend and attachment coefficients.
3460 alpha = Magboltz::ctowns_.alpha;
3461 alphaerr = Magboltz::ctwner_.alper;
3462 eta = Magboltz::ctowns_.att;
3463 etaerr = Magboltz::ctwner_.atter;
3464
3465 // Calculate effective Townsend SST coefficient from TOF results.
3466 if (fabs(Magboltz::tofout_.tofdl) > 0.) {
3467 const double wrzn = 1.e5 * Magboltz::tofout_.tofwr;
3468 const double fc1 = 0.5 * wrzn / Magboltz::tofout_.tofdl;
3469 const double fc2 = (Magboltz::tofout_.ralpha -
3470 Magboltz::tofout_.rattof) * 1.e12 /
3471 Magboltz::tofout_.tofdl;
3472 alphatof = fc1 - sqrt(fc1 * fc1 - fc2);
3473 }
3474 // Print the results.
3475 if (!(m_debug || verbose)) return;
3476 std::cout << m_className << "::RunMagboltz: Results:\n";
3477 printf(" Drift velocity along E: %12.8f cm/ns +/- %5.2f%%\n", vz, vzerr);
3478 printf(" Drift velocity along Bt: %12.8f cm/ns +/- %5.2f%%\n", vx, vxerr);
3479 printf(" Drift velocity along ExB: %12.8f cm/ns +/- %5.2f%%\n", vy, vyerr);
3480 printf(" Lorentz angle: %12.3f degree\n", lor * RadToDegree);
3481 printf(" Longitudinal diffusion: %12.8f cm1/2 +/- %5.2f%%\n", dl, dlerr);
3482 printf(" Transverse diffusion: %12.8f cm1/2 +/- %5.2f%%\n", dt, dterr);
3483 printf(" Townsend coefficient: %12.4f cm-1 +/- %5.2f%%\n", alpha,
3484 alphaerr);
3485 printf(" Attachment coefficient: %12.4f cm-1 +/- %5.2f%%\n", eta,
3486 etaerr);
3487 if (alphatof > 0.) {
3488 printf(" TOF effective Townsend: %12.4f cm-1 (alpha - eta)\n",
3489 alphatof);
3490 }
3491}
3492
3493void MediumMagboltz::GenerateGasTable(const int numColl, const bool verbose) {
3494 // Set the reference pressure and temperature.
3497
3498 // Initialize the parameter arrays.
3499 const unsigned int nEfields = m_eFields.size();
3500 const unsigned int nBfields = m_bFields.size();
3501 const unsigned int nAngles = m_bAngles.size();
3502 Init(nEfields, nBfields, nAngles, m_eVelE, 0.);
3503 Init(nEfields, nBfields, nAngles, m_eVelB, 0.);
3504 Init(nEfields, nBfields, nAngles, m_eVelX, 0.);
3505 Init(nEfields, nBfields, nAngles, m_eDifL, 0.);
3506 Init(nEfields, nBfields, nAngles, m_eDifT, 0.);
3507 Init(nEfields, nBfields, nAngles, m_eLor, 0.);
3508 Init(nEfields, nBfields, nAngles, m_eAlp, -30.);
3509 Init(nEfields, nBfields, nAngles, m_eAlp0, -30.);
3510 Init(nEfields, nBfields, nAngles, m_eAtt, -30.);
3511 Init(nEfields, nBfields, nAngles, 6, m_eDifM, 0.);
3512
3513 m_excRates.clear();
3514 m_ionRates.clear();
3515 m_excLevels.clear();
3516 m_ionLevels.clear();
3517 std::vector<unsigned int> excLevelIndex;
3518 std::vector<unsigned int> ionLevelIndex;
3519
3520 double vx = 0., vy = 0., vz = 0.;
3521 double difl = 0., dift = 0.;
3522 double alpha = 0., eta = 0.;
3523 double lor = 0.;
3524 double vxerr = 0., vyerr = 0., vzerr = 0.;
3525 double diflerr = 0., difterr = 0.;
3526 double alphaerr = 0., etaerr = 0.;
3527 double alphatof = 0.;
3528 double lorerr = 0.;
3529 std::array<double, 6> difftens;
3530
3531 // Run through the grid of E- and B-fields and angles.
3532 for (unsigned int i = 0; i < nEfields; ++i) {
3533 const double e = m_eFields[i];
3534 for (unsigned int j = 0; j < nAngles; ++j) {
3535 const double a = m_bAngles[j];
3536 for (unsigned int k = 0; k < nBfields; ++k) {
3537 const double b = m_bFields[k];
3538 std::cout << m_className << "::GenerateGasTable: E = " << e
3539 << " V/cm, B = " << b << " T, angle: " << a << " rad\n";
3540 RunMagboltz(e, b, a, numColl, verbose, vx, vy, vz, difl, dift, alpha,
3541 eta, lor, vxerr, vyerr, vzerr, diflerr, difterr, alphaerr,
3542 etaerr, lorerr, alphatof, difftens);
3543 m_eVelE[j][k][i] = vz;
3544 m_eVelX[j][k][i] = vy;
3545 m_eVelB[j][k][i] = vx;
3546 m_eDifL[j][k][i] = difl;
3547 m_eDifT[j][k][i] = dift;
3548 m_eLor[j][k][i] = lor;
3549 m_eAlp[j][k][i] = alpha > 0. ? log(alpha) : -30.;
3550 m_eAlp0[j][k][i] = alpha > 0. ? log(alpha) : -30.;
3551 m_eAtt[j][k][i] = eta > 0. ? log(eta) : -30.;
3552 for (unsigned int l = 0; l < 6; ++l) {
3553 m_eDifM[l][j][k][i] = difftens[l];
3554 }
3555 // If not done yet, retrieve the excitation and ionisation levels.
3556 if (m_excLevels.empty() && m_ionLevels.empty()) {
3557 for (long long il = 0; il < Magboltz::nMaxLevels; ++il) {
3558 if (Magboltz::large_.iarry[il] <= 0) break;
3559 // Skip levels that are not ionisations or inelastic collisions.
3560 const int cstype = (Magboltz::large_.iarry[il] - 1) % 5;
3561 if (cstype != 1 && cstype != 3) continue;
3562 const int igas = int((Magboltz::large_.iarry[il] - 1) / 5);
3563 std::string descr = GetDescription(il, Magboltz::scrip_.dscrpt);
3564 if (cstype == 3) {
3565 // Skip levels that are not excitations.
3566 if (!(descr[1] == 'E' && descr[2] == 'X') &&
3567 !(descr[0] == 'E' && descr[1] == 'X'))
3568 continue;
3569 }
3570 descr = m_gas[igas] + descr;
3571 if (cstype == 3) {
3572 ExcLevel exc;
3573 exc.label = descr;
3574 exc.energy = Magboltz::large_.ein[il];
3575 exc.prob = 0.;
3576 exc.rms = 0.;
3577 exc.dt = 0.;
3578 m_excLevels.push_back(std::move(exc));
3579 excLevelIndex.push_back(il);
3580 } else {
3581 IonLevel ion;
3582 ion.label = descr;
3583 ion.energy = Magboltz::large_.ein[il];
3584 m_ionLevels.push_back(std::move(ion));
3585 ionLevelIndex.push_back(il);
3586 }
3587 }
3588 std::cout << m_className << "::GenerateGasTable: Found "
3589 << m_excLevels.size() << " excitations and "
3590 << m_ionLevels.size() << " ionisations.\n";
3591 for (const auto& exc : m_excLevels) {
3592 std::cout << " " << exc.label << ", energy = " << exc.energy
3593 << " eV.\n";
3594 }
3595 for (const auto& ion : m_ionLevels) {
3596 std::cout << " " << ion.label << ", energy = " << ion.energy
3597 << " eV.\n";
3598 }
3599 if (!m_excLevels.empty()) {
3600 Init(nEfields, nBfields, nAngles, m_excLevels.size(),
3601 m_excRates, 0.);
3602 }
3603 if (!m_ionLevels.empty()) {
3604 Init(nEfields, nBfields, nAngles, m_ionLevels.size(),
3605 m_ionRates, 0.);
3606 }
3607 }
3608 // Retrieve the excitation and ionisation rates.
3609 const unsigned int nExc = m_excLevels.size();
3610 for (unsigned int ie = 0; ie < nExc; ++ie) {
3611 const unsigned int level = excLevelIndex[ie];
3612 m_excRates[ie][j][k][i] = Magboltz::outpt_.icoln[level];
3613 }
3614 const unsigned int nIon = m_ionLevels.size();
3615 for (unsigned int ii = 0; ii < nIon; ++ii) {
3616 const unsigned int level = ionLevelIndex[ii];
3617 m_ionRates[ii][j][k][i] = Magboltz::outpt_.icoln[level];
3618 }
3619 }
3620 }
3621 }
3622 // Set the threshold indices.
3625}
3626}
Base class for gas media.
Definition: MediumGas.hh:15
double GetNumberDensity() const override
Get the number density [cm-3].
Definition: MediumGas.cc:292
static constexpr unsigned int m_nMaxGases
Definition: MediumGas.hh:126
std::vector< std::vector< std::vector< std::vector< double > > > > m_excRates
Definition: MediumGas.hh:155
double m_lambdaPenningGlobal
Definition: MediumGas.hh:140
std::vector< IonLevel > m_ionLevels
Definition: MediumGas.hh:172
std::array< double, m_nMaxGases > m_rPenningGas
Definition: MediumGas.hh:142
std::vector< std::vector< std::vector< std::vector< double > > > > m_ionRates
Definition: MediumGas.hh:156
std::vector< ExcLevel > m_excLevels
Definition: MediumGas.hh:166
double m_temperatureTable
Definition: MediumGas.hh:149
virtual void PrintGas()
Print information about the present gas mixture and available data.
Definition: MediumGas.cc:2067
std::array< double, m_nMaxGases > m_lambdaPenningGas
Definition: MediumGas.hh:144
virtual void DisablePenningTransfer()
Switch the simulation of Penning transfers off globally.
Definition: MediumGas.cc:2450
std::string GetGasName(const int gasnumber, const int version) const
Definition: MediumGas.cc:2740
virtual bool EnablePenningTransfer(const double r, const double lambda)
Definition: MediumGas.cc:2321
std::vector< std::vector< std::vector< double > > > m_eAlp0
Definition: MediumGas.hh:152
std::array< std::string, m_nMaxGases > m_gas
Definition: MediumGas.hh:129
std::array< double, m_nMaxGases > m_fraction
Definition: MediumGas.hh:130
bool EnablePenningTransfer(const double r, const double lambda) override
void SetSplittingFunctionGreenSawada()
Sample the secondary electron energy according to the Green-Sawada model.
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
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.
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()
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 GetElectronCollision(const double e, int &type, int &level, double &e1, double &dx, double &dy, double &dz, std::vector< std::pair< int, double > > &secondaries, int &ndxc, int &band) override
Sample the collision type.
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:547
bool m_microscopic
Definition: Medium.hh:531
double m_pressure
Definition: Medium.hh:517
unsigned int SetThreshold(const std::vector< std::vector< std::vector< double > > > &tab) const
Definition: Medium.cc:1146
std::vector< std::vector< std::vector< double > > > m_eAlp
Definition: Medium.hh:558
virtual void EnableDrift(const bool on=true)
Switch electron/ion/hole on/off.
Definition: Medium.hh:67
virtual void EnablePrimaryIonisation(const bool on=true)
Make the medium ionisable or non-ionisable.
Definition: Medium.hh:69
std::vector< std::vector< std::vector< double > > > m_eVelE
Definition: Medium.hh:553
std::vector< std::vector< std::vector< double > > > m_eVelX
Definition: Medium.hh:554
std::vector< std::vector< std::vector< double > > > m_eDifL
Definition: Medium.hh:556
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:1304
std::vector< double > m_eFields
Definition: Medium.hh:546
std::vector< std::vector< std::vector< double > > > m_eAtt
Definition: Medium.hh:559
std::vector< double > m_bAngles
Definition: Medium.hh:548
std::vector< std::vector< std::vector< double > > > m_eLor
Definition: Medium.hh:560
std::vector< std::vector< std::vector< double > > > m_eDifT
Definition: Medium.hh:557
std::vector< std::vector< std::vector< double > > > m_eVelB
Definition: Medium.hh:555
unsigned int m_nComponents
Definition: Medium.hh:521
std::string m_className
Definition: Medium.hh:506
std::vector< std::vector< std::vector< std::vector< double > > > > m_eDifM
Definition: Medium.hh:562
bool m_isChanged
Definition: Medium.hh:540
double m_temperature
Definition: Medium.hh:515
struct Garfield::Magboltz::@8 scrip_
struct Garfield::Magboltz::@6 dens_
constexpr unsigned int nMaxInelasticTerms
constexpr unsigned int nEnergySteps
struct Garfield::Magboltz::@13 velerr_
constexpr unsigned int nMaxAttachmentTerms
constexpr unsigned int nCharDescr
constexpr unsigned int nMaxIonisationTerms
struct Garfield::Magboltz::@19 ctwner_
struct Garfield::Magboltz::@7 outpt_
struct Garfield::Magboltz::@11 ratio_
struct Garfield::Magboltz::@0 bfld_
struct Garfield::Magboltz::@2 setp_
void gasmix_(long long *ngs, double *q, double *qin, long long *nin, double *e, double *ei, char *name, double *virl, double *eb, double *peqel, double *peqin, double *penfra, long long *kel, long long *kin, double *qion, double *peqion, double *eion, long long *nion, double *qatt, long long *natt, double *qnull, long long *nnull, double *scln, long long *nc0, double *ec0, double *wk, double *efl, long long *ng1, double *eg1, long long *ng2, double *eg2, char scrpt[nMaxLevelsPerComponent][nCharDescr], char scrptn[nMaxNullTerms][nCharDescr])
struct Garfield::Magboltz::@12 vel_
constexpr unsigned int nMaxLevels
struct Garfield::Magboltz::@10 gasn_
struct Garfield::Magboltz::@1 inpt_
constexpr unsigned int nMaxNullTerms
struct Garfield::Magboltz::@5 mix2_
struct Garfield::Magboltz::@3 thrm_
struct Garfield::Magboltz::@17 diferl_
double cf[nMaxLevels][nEnergySteps]
struct Garfield::Magboltz::@14 diflab_
struct Garfield::Magboltz::@4 cnsts_
struct Garfield::Magboltz::@20 tofout_
constexpr unsigned int nMaxLevelsPerComponent
struct Garfield::Magboltz::@9 large_
struct Garfield::Magboltz::@18 ctowns_
double RndmUniform()
Draw a random number uniformly distributed in the range [0, 1).
Definition: Random.hh:14
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
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