LCOV - code coverage report
Current view: top level - src/module - EMPairProduction.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 134 137 97.8 %
Date: 2024-04-29 14:43:01 Functions: 13 13 100.0 %

          Line data    Source code
       1             : #include "crpropa/module/EMPairProduction.h"
       2             : #include "crpropa/Units.h"
       3             : #include "crpropa/Random.h"
       4             : 
       5             : #include <fstream>
       6             : #include <limits>
       7             : #include <stdexcept>
       8             : 
       9             : 
      10             : namespace crpropa {
      11             : 
      12             : static const double mec2 = mass_electron * c_squared;
      13             : 
      14           9 : EMPairProduction::EMPairProduction(ref_ptr<PhotonField> photonField, bool haveElectrons, double thinning, double limit) {
      15           9 :         setPhotonField(photonField);
      16           9 :         setThinning(thinning);
      17           9 :         setLimit(limit);
      18           9 :         setHaveElectrons(haveElectrons);
      19           9 : }
      20             : 
      21          33 : void EMPairProduction::setPhotonField(ref_ptr<PhotonField> photonField) {
      22          33 :         this->photonField = photonField;
      23          33 :         std::string fname = photonField->getFieldName();
      24          33 :         setDescription("EMPairProduction: " + fname);
      25          99 :         initRate(getDataPath("EMPairProduction/rate_" + fname + ".txt"));
      26          99 :         initCumulativeRate(getDataPath("EMPairProduction/cdf_" + fname + ".txt"));
      27          33 : }
      28             : 
      29          14 : void EMPairProduction::setHaveElectrons(bool haveElectrons) {
      30          14 :         this->haveElectrons = haveElectrons;
      31          14 : }
      32             : 
      33           9 : void EMPairProduction::setLimit(double limit) {
      34           9 :         this->limit = limit;
      35           9 : }
      36             : 
      37          13 : void EMPairProduction::setThinning(double thinning) {
      38          13 :         this->thinning = thinning;
      39          13 : }
      40             : 
      41          33 : void EMPairProduction::initRate(std::string filename) {
      42          33 :         std::ifstream infile(filename.c_str());
      43             : 
      44          33 :         if (!infile.good())
      45           0 :                 throw std::runtime_error("EMPairProduction: could not open file " + filename);
      46             : 
      47             :         // clear previously loaded interaction rates
      48          33 :         tabEnergy.clear();
      49          33 :         tabRate.clear();
      50             : 
      51        7510 :         while (infile.good()) {
      52        7477 :                 if (infile.peek() != '#') {
      53             :                         double a, b;
      54             :                         infile >> a >> b;
      55        7345 :                         if (infile) {
      56        7312 :                                 tabEnergy.push_back(pow(10, a) * eV);
      57        7312 :                                 tabRate.push_back(b / Mpc);
      58             :                         }
      59             :                 }
      60        7477 :                 infile.ignore(std::numeric_limits < std::streamsize > ::max(), '\n');
      61             :         }
      62          33 :         infile.close();
      63          33 : }
      64             : 
      65          33 : void EMPairProduction::initCumulativeRate(std::string filename) {
      66          33 :         std::ifstream infile(filename.c_str());
      67             : 
      68          33 :         if (!infile.good())
      69           0 :                 throw std::runtime_error("EMPairProduction: could not open file " + filename);
      70             : 
      71             :         // clear previously loaded tables
      72          33 :         tabE.clear();
      73          33 :         tabs.clear();
      74          33 :         tabCDF.clear();
      75             :         
      76             :         // skip header
      77         165 :         while (infile.peek() == '#')
      78         132 :                 infile.ignore(std::numeric_limits < std::streamsize > ::max(), '\n');
      79             : 
      80             :         // read s values in first line
      81             :         double a;
      82             :         infile >> a; // skip first value
      83        3663 :         while (infile.good() and (infile.peek() != '\n')) {
      84             :                 infile >> a;
      85        3630 :                 tabs.push_back(pow(10, a) * eV * eV);
      86             :         }
      87             : 
      88             :         // read all following lines: E, cdf values
      89        7345 :         while (infile.good()) {
      90             :                 infile >> a;
      91        7345 :                 if (!infile)
      92             :                         break;  // end of file
      93        7312 :                 tabE.push_back(pow(10, a) * eV);
      94             :                 std::vector<double> cdf;
      95      811632 :                 for (int i = 0; i < tabs.size(); i++) {
      96             :                         infile >> a;
      97      804320 :                         cdf.push_back(a / Mpc);
      98             :                 }
      99        7312 :                 tabCDF.push_back(cdf);
     100             :         }
     101          33 :         infile.close();
     102          33 : }
     103             : 
     104             : // Hold an data array to interpolate the energy distribution on
     105             : class PPSecondariesEnergyDistribution {
     106             :         private:
     107             :                 std::vector<double> tab_s;
     108             :                 std::vector< std::vector<double> > data;
     109             :                 size_t N;
     110             : 
     111             :         public:
     112             :                 // differential cross section for pair production for x = Epositron/Egamma, compare Lee 96 arXiv:9604098
     113             :                 double dSigmadE_PPx(double x, double beta) {
     114     1000000 :                         double A = (x / (1. - x) + (1. - x) / x );
     115     1000000 :                         double B =  (1. / x + 1. / (1. - x) );
     116        1000 :                         double y = (1 - beta * beta);
     117     1000000 :                         return A + y * B - y * y / 4 * B * B;
     118             :                 }
     119             : 
     120           1 :                 PPSecondariesEnergyDistribution() {
     121           1 :                         N = 1000;
     122             :                         size_t Ns = 1000;
     123             :                         double s_min = 4 * mec2 * mec2;
     124             :                         double s_max = 1e23 * eV * eV;
     125             :                         double dls = log(s_max / s_min) / Ns;
     126           1 :                         data = std::vector< std::vector<double> >(Ns, std::vector<double>(N));
     127           1 :                         tab_s = std::vector<double>(Ns + 1);
     128             : 
     129        1002 :                         for (size_t i = 0; i < Ns + 1; ++i)
     130        1001 :                                 tab_s[i] = s_min * exp(i*dls); // tabulate s bin borders
     131             : 
     132        1001 :                         for (size_t i = 0; i < Ns; i++) {
     133        1000 :                                 double s = s_min * exp(i*dls + 0.5*dls);
     134        1000 :                                 double beta = sqrt(1 - s_min/s);
     135        1000 :                                 double x0 = (1 - beta) / 2;
     136        1000 :                                 double dx = log((1 + beta) / (1 - beta)) / N;
     137             : 
     138             :                                 // cumulative midpoint integration
     139        1000 :                                 std::vector<double> data_i(1000);
     140        1000 :                                 data_i[0] = dSigmadE_PPx(x0, beta) * expm1(dx);
     141     1000000 :                                 for (size_t j = 1; j < N; j++) {
     142      999000 :                                         double x = x0 * exp(j*dx + 0.5*dx);
     143      999000 :                                         double binWidth = exp((j+1)*dx)-exp(j*dx);
     144      999000 :                                         data_i[j] = dSigmadE_PPx(x, beta) * binWidth + data_i[j-1];
     145             :                                 }
     146        1000 :                                 data[i] = data_i;
     147             :                         }
     148           1 :                 }
     149             : 
     150             :                 // sample positron energy from cdf(E, s_kin)
     151         559 :                 double sample(double E0, double s) {
     152             :                         // get distribution for given s
     153         559 :                         size_t idx = std::lower_bound(tab_s.begin(), tab_s.end(), s) - tab_s.begin();
     154         559 :                         if (idx > data.size())
     155             :                                 return NAN;
     156             :                                 
     157         559 :                         std::vector<double> s0 = data[idx];
     158             : 
     159             :                         // draw random bin
     160         559 :                         Random &random = Random::instance();
     161         559 :                         size_t j = random.randBin(s0) + 1;
     162             : 
     163             :                         double s_min = 4. * mec2 * mec2;
     164         559 :                         double beta = sqrtl(1. - s_min / s);
     165         559 :                         double x0 = (1. - beta) / 2.;
     166         559 :                         double dx = log((1 + beta) / (1 - beta)) / N;
     167         559 :                         double binWidth = x0 * (exp(j*dx) - exp((j-1)*dx));
     168         559 :                         if (random.rand() < 0.5)
     169         282 :                                 return E0 * (x0 * exp((j-1) * dx) + binWidth);
     170             :                         else
     171         277 :                                 return E0 * (1 - (x0 * exp((j-1) * dx) + binWidth));
     172             :                 }
     173             : };
     174             : 
     175         559 : void EMPairProduction::performInteraction(Candidate *candidate) const {
     176             :         // scale particle energy instead of background photon energy
     177         559 :         double z = candidate->getRedshift();
     178         559 :         double E = candidate->current.getEnergy() * (1 + z);
     179             : 
     180             :         // check if secondary electron pair needs to be produced
     181         559 :         if (not haveElectrons)
     182           0 :                 return;
     183             : 
     184             :         // check if in tabulated energy range
     185         559 :         if (E < tabE.front() or (E > tabE.back()))
     186             :                 return;
     187             : 
     188             :         // sample the value of s
     189         559 :         Random &random = Random::instance();
     190         559 :         size_t i = closestIndex(E, tabE);  // find closest tabulation point
     191         559 :         size_t j = random.randBin(tabCDF[i]);
     192        1076 :         double lo = std::max(4 * mec2 * mec2, tabs[j-1]);  // first s-tabulation point below min(s_kin) = (2 me c^2)^2; ensure physical value
     193         559 :         double hi = tabs[j];
     194         559 :         double s = lo + random.rand() * (hi - lo);
     195             : 
     196             :         // sample electron / positron energy
     197         559 :         static PPSecondariesEnergyDistribution interpolation;
     198         559 :         double Ee = interpolation.sample(E, s);
     199         559 :         double Ep = E - Ee;
     200         559 :         double f = Ep / E;
     201             : 
     202             :         // for some backgrounds Ee=nan due to precision limitations.
     203         559 :         if (not std::isfinite(Ee) || not std::isfinite(Ep))
     204             :                 return;
     205             : 
     206             :         // photon is lost after interacting
     207         559 :         candidate->setActive(false);
     208             : 
     209             :         // sample random position along current step
     210         559 :         Vector3d pos = random.randomInterpolatedPosition(candidate->previous.getPosition(), candidate->current.getPosition());
     211             :         // apply sampling
     212         559 :         if (random.rand() < pow(f, thinning)) {
     213         559 :                 double w = 1. / pow(f, thinning);
     214         559 :                 candidate->addSecondary(11, Ep / (1 + z), pos, w, interactionTag);
     215             :         }
     216         559 :         if (random.rand() < pow(1 - f, thinning)){
     217         559 :                 double w = 1. / pow(1 - f, thinning);
     218         559 :                 candidate->addSecondary(-11, Ee / (1 + z), pos, w, interactionTag);  
     219             :         }
     220             : }
     221             : 
     222       66881 : void EMPairProduction::process(Candidate *candidate) const {
     223             :         // check if photon
     224       66881 :         if (candidate->current.getId() != 22)
     225             :                 return;
     226             : 
     227             :         // scale particle energy instead of background photon energy
     228         841 :         double z = candidate->getRedshift();
     229         841 :         double E = candidate->current.getEnergy() * (1 + z);
     230             : 
     231             :         // check if in tabulated energy range
     232         841 :         if ((E < tabEnergy.front()) or (E > tabEnergy.back()))
     233             :                 return;
     234             : 
     235             :         // interaction rate
     236         651 :         double rate = interpolate(E, tabEnergy, tabRate);
     237         651 :         rate *= pow_integer<2>(1 + z) * photonField->getRedshiftScaling(z);
     238             : 
     239             :         // run this loop at least once to limit the step size 
     240         651 :         double step = candidate->getCurrentStep();
     241         651 :         Random &random = Random::instance();
     242             :         do {
     243         651 :                 double randDistance = -log(random.rand()) / rate;
     244             :                 // check for interaction; if it doesn't ocurr, limit next step
     245         651 :                 if (step < randDistance) { 
     246          93 :                         candidate->limitNextStep(limit / rate);
     247             :                 } else {
     248         558 :                         performInteraction(candidate);
     249         558 :                         return;
     250             :                 }
     251          93 :                 step -= randDistance; 
     252          93 :         } while (step > 0.);
     253             : 
     254             : }
     255             : 
     256           1 : void EMPairProduction::setInteractionTag(std::string tag) {
     257           1 :         interactionTag = tag;
     258           1 : }
     259             : 
     260           2 : std::string EMPairProduction::getInteractionTag() const {
     261           2 :         return interactionTag;
     262             : }
     263             : 
     264             : } // namespace crpropa

Generated by: LCOV version 1.14