LCOV - code coverage report
Current view: top level - src/module - NuclearDecay.cpp (source / functions) Coverage Total Hit
Test: coverage.info.cleaned Lines: 89.5 % 171 153
Test Date: 2026-07-13 06:03:10 Functions: 92.3 % 13 12

            Line data    Source code
       1              : #include "crpropa/module/NuclearDecay.h"
       2              : #include "crpropa/Common.h"
       3              : #include "crpropa/Units.h"
       4              : #include "crpropa/ParticleID.h"
       5              : #include "crpropa/ParticleMass.h"
       6              : #include "crpropa/Random.h"
       7              : 
       8              : #include <fstream>
       9              : #include <limits>
      10              : #include <cmath>
      11              : #include <stdexcept>
      12              : 
      13              : #include "kiss/logger.h"
      14              : 
      15              : namespace crpropa {
      16              : 
      17           12 : NuclearDecay::NuclearDecay(bool electrons, bool photons, bool neutrinos, double l) {
      18           12 :         haveElectrons = electrons;
      19           12 :         havePhotons = photons;
      20           12 :         haveNeutrinos = neutrinos;
      21           12 :         limit = l;
      22           12 :         setDescription("NuclearDecay");
      23              : 
      24              :         // load decay table
      25           24 :         std::string filename = getDataPath("nuclear_decay.txt");
      26           12 :         std::ifstream infile(filename.c_str());
      27           12 :         if (!infile.good())
      28              :                 throw std::runtime_error(
      29            0 :                                 "crpropa::NuclearDecay: could not open file " + filename);
      30              : 
      31           12 :         decayTable.resize((NUCLEAR_ZMAX + 1) * NUCLEAR_NSTRIDE);
      32              :         std::string line;
      33       138660 :         while (std::getline(infile,line)) {
      34       138648 :                 std::stringstream stream(line);
      35       138648 :                 if (stream.peek() == '#')
      36              :                         continue;
      37              :                 DecayMode decay;
      38              :                 int Z, N;
      39              :                 double lifetime;
      40       138624 :                 stream >> Z >> N >> decay.channel >> lifetime;
      41       138624 :                 decay.rate = 1. / lifetime / c_light; // decay rate in [1/m]
      42              :                 std::vector<double> gamma;
      43              :                 double val;
      44      1216920 :                 while (stream >> val)
      45      1078296 :                         gamma.push_back(val);
      46       677772 :                 for (int i = 0; i < gamma.size(); i += 2) {
      47       539148 :                         decay.energy.push_back(gamma[i] * keV);
      48       539148 :                         decay.intensity.push_back(gamma[i+1]);
      49              :                 }
      50       138624 :                 if (infile)
      51       138624 :                         decayTable[Z * NUCLEAR_NSTRIDE + N].push_back(decay);
      52       277272 :         }
      53           12 :         infile.close();
      54           24 : }
      55              : 
      56            2 : void NuclearDecay::setHaveElectrons(bool b) {
      57            2 :         haveElectrons = b;
      58            2 : }
      59              : 
      60            1 : void NuclearDecay::setHavePhotons(bool b) {
      61            1 :         havePhotons = b;
      62            1 : }
      63              : 
      64            1 : void NuclearDecay::setHaveNeutrinos(bool b) {
      65            1 :         haveNeutrinos = b;
      66            1 : }
      67              : 
      68            0 : void NuclearDecay::setLimit(double l) {
      69            0 :         limit = l;
      70            0 : }
      71              : 
      72          661 : void NuclearDecay::process(Candidate *candidate) const {
      73              :         // the loop should be processed at least once for limiting the next step
      74          661 :         double step = candidate->getCurrentStep();
      75          661 :         double z = candidate->getRedshift();
      76              :         do {
      77              :                 // check if nucleus
      78          665 :                 int id = candidate->current.getId();
      79          665 :                 if (not (isNucleus(id)))
      80              :                         return;
      81              : 
      82          664 :                 int A = massNumber(id);
      83          664 :                 int Z = chargeNumber(id);
      84          664 :                 int N = A - Z;
      85              : 
      86              :                 // check if particle can decay
      87          664 :                 if ((Z > NUCLEAR_ZMAX) or (N > NUCLEAR_NMAX))
      88              :                         return;
      89          663 :                 const std::vector<DecayMode> &decays = decayTable[Z * NUCLEAR_NSTRIDE + N];
      90          663 :                 if (decays.size() == 0)
      91              :                         return;
      92              : 
      93              :                 // find interaction mode with minimum random decay distance
      94            5 :                 Random &random = Random::instance();
      95              :                 double randDistance = std::numeric_limits<double>::max();
      96              :                 int channel;
      97              :                 double totalRate = 0;
      98              : 
      99           10 :                 for (size_t i = 0; i < decays.size(); i++) {
     100            5 :                         double rate = decays[i].rate;
     101            5 :                         rate /= candidate->current.getLorentzFactor();  // relativistic time dilation
     102            5 :                         rate /= (1 + z);  // rate per light travel distance -> rate per comoving distance
     103            5 :                         totalRate += rate;
     104            5 :                         double d = -log(random.rand()) / rate;
     105            5 :                         if (d > randDistance)
     106            0 :                                 continue;
     107              :                         randDistance = d;
     108            5 :                         channel = decays[i].channel;
     109              :                 }
     110              : 
     111              :                 // check if interaction doesn't happen
     112            5 :                 if (step < randDistance) {
     113              :                         // limit next step to a fraction of the mean free path
     114            1 :                         candidate->limitNextStep(limit / totalRate);
     115            1 :                         return;
     116              :                 }
     117              : 
     118              :                 // interact and repeat with remaining step
     119            4 :                 performInteraction(candidate, channel);
     120            4 :                 step -= randDistance;
     121            4 :         } while (step > 0);
     122              : }
     123              : 
     124        11568 : void NuclearDecay::performInteraction(Candidate *candidate, int channel) const {
     125              :         // interpret decay channel
     126              :         int nBetaMinus = digit(channel, 10000);
     127              :         int nBetaPlus = digit(channel, 1000);
     128              :         int nAlpha = digit(channel, 100);
     129              :         int nProton = digit(channel, 10);
     130              :         int nNeutron = digit(channel, 1);
     131              : 
     132              :         // perform decays
     133        11568 :         if (havePhotons)
     134           11 :                 gammaEmission(candidate,channel);
     135        13170 :         for (size_t i = 0; i < nBetaMinus; i++)
     136         1602 :                 betaDecay(candidate, false);
     137        12688 :         for (size_t i = 0; i < nBetaPlus; i++)
     138         1120 :                 betaDecay(candidate, true);
     139        11827 :         for (size_t i = 0; i < nAlpha; i++)
     140          259 :                 nucleonEmission(candidate, 4, 2);
     141        15063 :         for (size_t i = 0; i < nProton; i++)
     142         3495 :                 nucleonEmission(candidate, 1, 1);
     143        17400 :         for (size_t i = 0; i < nNeutron; i++)
     144         5832 :                 nucleonEmission(candidate, 1, 0);
     145        11568 : }
     146              : 
     147           11 : void NuclearDecay::gammaEmission(Candidate *candidate, int channel) const {
     148           11 :         int id = candidate->current.getId();
     149           11 :         int Z = chargeNumber(id);
     150           11 :         int N = massNumber(id) - Z;
     151              : 
     152           11 :         if ((Z > NUCLEAR_ZMAX) or (N > NUCLEAR_NMAX))
     153            0 :                 return;
     154              :         // get photon energies and emission probabilities for decay channel
     155           11 :         const std::vector<DecayMode> &decays = decayTable[Z * NUCLEAR_NSTRIDE + N];
     156              :         size_t idecay = decays.size();
     157           21 :         while (idecay-- != 0) {
     158           21 :                 if (decays[idecay].channel == channel)
     159              :                         break;
     160              :         }
     161              : 
     162              :         const std::vector<double> &energy = decays[idecay].energy;
     163              :         const std::vector<double> &intensity = decays[idecay].intensity;
     164              : 
     165              :         // check if photon emission available
     166           11 :         if (energy.size() == 0)
     167              :                 return;
     168              : 
     169           11 :         Random &random = Random::instance();
     170           11 :         Vector3d pos = random.randomInterpolatedPosition(candidate->previous.getPosition(), candidate->current.getPosition());
     171              : 
     172           26 :         for (int i = 0; i < energy.size(); ++i) {
     173              :                 // check if photon of specific energy is emitted
     174           15 :                 if (random.rand() > intensity[i])
     175            7 :                         continue;
     176              :                 // create secondary photon; boost to lab frame
     177            8 :                 double cosTheta = 2 * random.rand() - 1;
     178            8 :                 double E = energy[i] * candidate->current.getLorentzFactor() * (1. - cosTheta);
     179            8 :                 candidate->addSecondary(22, E, pos, 1., interactionTag);
     180              :         }
     181              : }
     182              : 
     183         2722 : void NuclearDecay::betaDecay(Candidate *candidate, bool isBetaPlus) const {
     184         2722 :         double gamma = candidate->current.getLorentzFactor();
     185         2722 :         int id = candidate->current.getId();
     186         2722 :         int A = massNumber(id);
     187         2722 :         int Z = chargeNumber(id);
     188              : 
     189              :         // beta- decay
     190              :         int electronId = 11; // electron
     191              :         int neutrinoId = -12; // anti-electron neutrino
     192              :         int dZ = 1;
     193              :         // beta+ decay
     194         2722 :         if (isBetaPlus) {
     195              :                 electronId = -11; // positron
     196              :                 neutrinoId = 12; // electron neutrino
     197              :                 dZ = -1;
     198              :         }
     199              : 
     200              :         // update candidate, nuclear recoil negligible
     201              :         try
     202              :         {
     203         2722 :                 candidate->current.setId(nucleusId(A, Z + dZ));
     204              :         }
     205            0 :         catch (std::runtime_error &e)
     206              :         {
     207            0 :                 KISS_LOG_ERROR<< "Something went wrong in the NuclearDecay\n" << "Please report this error on https://github.com/CRPropa/CRPropa3/issues including your simulation setup and the following random seed:\n" << Random::instance().getSeed_base64();
     208            0 :                 throw;
     209            0 :         }
     210              : 
     211         2722 :         candidate->current.setLorentzFactor(gamma);
     212              : 
     213         2722 :         if (not (haveElectrons or haveNeutrinos))
     214         2709 :                 return;
     215              : 
     216              :         // Q-value of the decay, subtract total energy of emitted photons
     217           13 :         double m1 = nuclearMass(A, Z);
     218           13 :         double m2 = nuclearMass(A, Z+dZ);
     219           13 :         double Q = (m1 - m2 - mass_electron) * c_squared;
     220              : 
     221              :         // generate cdf of electron energy, neglecting Coulomb correction
     222              :         // see Basdevant, Fundamentals in Nuclear Physics, eq. (4.92)
     223              :         // This leads to deviations from theoretical expectations at low 
     224              :         // primary energies.
     225              :         std::vector<double> energies;
     226              :         std::vector<double> densities; // cdf(E), unnormalized
     227              : 
     228           13 :         energies.reserve(51);
     229           13 :         densities.reserve(51);
     230              : 
     231              :         double me = mass_electron * c_squared;
     232           13 :         double cdf = 0;
     233          676 :         for (int i = 0; i <= 50; i++) {
     234          663 :                 double E = me + i / 50. * Q;
     235          663 :                 cdf += E * sqrt(E * E - me * me) * pow(Q + me - E, 2);
     236          663 :                 energies.push_back(E);
     237          663 :                 densities.push_back(cdf);
     238              :         }
     239              : 
     240              :         // draw random electron energy and angle
     241              :         // assumption of ultra-relativistic particles 
     242              :         // leads to deviations from theoretical predictions
     243              :         // is not problematic for usual CRPropa energies E>~TeV
     244           13 :         Random &random = Random::instance();
     245           13 :         double E = interpolate(random.rand() * cdf, densities, energies);
     246           13 :         double p = sqrt(E * E - me * me);  // p*c
     247           13 :         double cosTheta = 2 * random.rand() - 1;
     248              : 
     249              :         // boost to lab frame
     250           13 :         double Ee = gamma * (E - p * cosTheta);
     251           13 :         double Enu = gamma * (Q + me - E) * (1 + cosTheta);  // pnu*c ~ Enu
     252              : 
     253           13 :         Vector3d pos = random.randomInterpolatedPosition(candidate->previous.getPosition(), candidate->current.getPosition());
     254           13 :         if (haveElectrons)
     255           13 :                 candidate->addSecondary(electronId, Ee, pos, 1., interactionTag);
     256           13 :         if (haveNeutrinos)
     257           10 :                 candidate->addSecondary(neutrinoId, Enu, pos, 1., interactionTag);
     258           13 : }
     259              : 
     260         9586 : void NuclearDecay::nucleonEmission(Candidate *candidate, int dA, int dZ) const {
     261         9586 :         Random &random = Random::instance();
     262         9586 :         int id = candidate->current.getId();
     263         9586 :         int A = massNumber(id);
     264         9586 :         int Z = chargeNumber(id);
     265         9586 :         double EpA = candidate->current.getEnergy() / double(A);
     266              : 
     267              :         try
     268              :         {
     269         9586 :                 candidate->current.setId(nucleusId(A - dA, Z - dZ));
     270              :         }
     271            0 :         catch (std::runtime_error &e)
     272              :         {
     273            0 :                 KISS_LOG_ERROR<< "Something went wrong in the NuclearDecay\n" << "Please report this error on https://github.com/CRPropa/CRPropa3/issues including your simulation setup and the following random seed:\n" << Random::instance().getSeed_base64();
     274            0 :                 throw;
     275            0 :         }
     276              : 
     277         9586 :         candidate->current.setEnergy(EpA * (A - dA));
     278         9586 :         Vector3d pos = random.randomInterpolatedPosition(candidate->previous.getPosition(),candidate->current.getPosition());
     279              : 
     280              :         try
     281              :         {
     282         9586 :                 candidate->addSecondary(nucleusId(dA, dZ), EpA * dA, pos, 1., interactionTag);
     283              :         }
     284            0 :         catch (std::runtime_error &e)
     285              :         {
     286            0 :                 KISS_LOG_ERROR<< "Something went wrong in the NuclearDecay\n" << "Please report this error on https://github.com/CRPropa/CRPropa3/issues including your simulation setup and the following random seed:\n" << Random::instance().getSeed_base64();
     287            0 :                 throw;
     288            0 :         }
     289              : 
     290         9586 : }
     291              : 
     292            1 : double NuclearDecay::meanFreePath(int id, double gamma) {
     293            1 :         if (not (isNucleus(id)))
     294              :                 return std::numeric_limits<double>::max();
     295              : 
     296            1 :         int A = massNumber(id);
     297            1 :         int Z = chargeNumber(id);
     298            1 :         int N = A - Z;
     299              : 
     300            1 :         if ((Z > NUCLEAR_ZMAX) or (N > NUCLEAR_NMAX))
     301              :                 return std::numeric_limits<double>::max();
     302              :         // check if particle can decay
     303            1 :         const std::vector<DecayMode> &decays = decayTable[Z * NUCLEAR_NSTRIDE + N];
     304            1 :         if (decays.size() == 0)
     305              :                 return std::numeric_limits<double>::max();
     306              : 
     307              :         double totalRate = 0;
     308              : 
     309            2 :         for (size_t i = 0; i < decays.size(); i++) {
     310            1 :                 double rate = decays[i].rate;
     311            1 :                 rate /= gamma;
     312            1 :                 totalRate += rate;
     313              :         }
     314              : 
     315            1 :         return 1. / totalRate;
     316              : }
     317              : 
     318            1 : void NuclearDecay::setInteractionTag(std::string tag) {
     319            1 :         interactionTag = tag;
     320            1 : }
     321              : 
     322            2 : std::string NuclearDecay::getInteractionTag() const {
     323            2 :         return interactionTag;
     324              : }
     325              : 
     326              : } // namespace crpropa
        

Generated by: LCOV version 2.0-1