LCOV - code coverage report
Current view: top level - src/module - NuclearDecay.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 138 168 82.1 %
Date: 2024-04-29 14:43:01 Functions: 11 13 84.6 %

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

Generated by: LCOV version 1.14