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