LCOV - code coverage report
Current view: top level - src/module - EMPairProduction.cpp (source / functions) Coverage Total Hit
Test: coverage.info.cleaned Lines: 91.9 % 148 136
Test Date: 2026-06-18 09:49:19 Functions: 100.0 % 13 13

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

Generated by: LCOV version 2.0-1