Program Listing for File NuclearDecay.cpp

Return to documentation for file (src/module/NuclearDecay.cpp)

#include "crpropa/module/NuclearDecay.h"
#include "crpropa/Units.h"
#include "crpropa/ParticleID.h"
#include "crpropa/ParticleMass.h"
#include "crpropa/Random.h"

#include <fstream>
#include <limits>
#include <cmath>
#include <stdexcept>

#include "kiss/logger.h"

namespace crpropa {

NuclearDecay::NuclearDecay(bool electrons, bool photons, bool neutrinos, double l) {
        haveElectrons = electrons;
        havePhotons = photons;
        haveNeutrinos = neutrinos;
        limit = l;
        setDescription("NuclearDecay");

        // load decay table
        std::string filename = getDataPath("nuclear_decay.txt");
        std::ifstream infile(filename.c_str());
        if (!infile.good())
                throw std::runtime_error(
                                "crpropa::NuclearDecay: could not open file " + filename);

        decayTable.resize(27 * 31);
        std::string line;
        while (std::getline(infile,line)) {
                std::stringstream stream(line);
                if (stream.peek() == '#')
                        continue;
                DecayMode decay;
                int Z, N;
                double lifetime;
                stream >> Z >> N >> decay.channel >> lifetime;
                decay.rate = 1. / lifetime / c_light; // decay rate in [1/m]
                std::vector<double> gamma;
                double val;
                while (stream >> val)
                        gamma.push_back(val);
                for (int i = 0; i < gamma.size(); i += 2) {
                        decay.energy.push_back(gamma[i] * keV);
                        decay.intensity.push_back(gamma[i+1]);
                }
                if (infile)
                        decayTable[Z * 31 + N].push_back(decay);
        }
        infile.close();
}

void NuclearDecay::setHaveElectrons(bool b) {
        haveElectrons = b;
}

void NuclearDecay::setHavePhotons(bool b) {
        havePhotons = b;
}

void NuclearDecay::setHaveNeutrinos(bool b) {
        haveNeutrinos = b;
}

void NuclearDecay::setLimit(double l) {
        limit = l;
}

void NuclearDecay::process(Candidate *candidate) const {
        // the loop should be processed at least once for limiting the next step
        double step = candidate->getCurrentStep();
        double z = candidate->getRedshift();
        do {
                // check if nucleus
                int id = candidate->current.getId();
                if (not (isNucleus(id)))
                        return;

                int A = massNumber(id);
                int Z = chargeNumber(id);
                int N = A - Z;

                // check if particle can decay
                const std::vector<DecayMode> &decays = decayTable[Z * 31 + N];
                if (decays.size() == 0)
                        return;

                // find interaction mode with minimum random decay distance
                Random &random = Random::instance();
                double randDistance = std::numeric_limits<double>::max();
                int channel;
                double totalRate = 0;

                for (size_t i = 0; i < decays.size(); i++) {
                        double rate = decays[i].rate;
                        rate /= candidate->current.getLorentzFactor();  // relativistic time dilation
                        rate /= (1 + z);  // rate per light travel distance -> rate per comoving distance
                        totalRate += rate;
                        double d = -log(random.rand()) / rate;
                        if (d > randDistance)
                                continue;
                        randDistance = d;
                        channel = decays[i].channel;
                }

                // check if interaction doesn't happen
                if (step < randDistance) {
                        // limit next step to a fraction of the mean free path
                        candidate->limitNextStep(limit / totalRate);
                        return;
                }

                // interact and repeat with remaining step
                performInteraction(candidate, channel);
                step -= randDistance;
        } while (step > 0);
}

void NuclearDecay::performInteraction(Candidate *candidate, int channel) const {
        // interpret decay channel
        int nBetaMinus = digit(channel, 10000);
        int nBetaPlus = digit(channel, 1000);
        int nAlpha = digit(channel, 100);
        int nProton = digit(channel, 10);
        int nNeutron = digit(channel, 1);

        // perform decays
        if (havePhotons)
                gammaEmission(candidate,channel);
        for (size_t i = 0; i < nBetaMinus; i++)
                betaDecay(candidate, false);
        for (size_t i = 0; i < nBetaPlus; i++)
                betaDecay(candidate, true);
        for (size_t i = 0; i < nAlpha; i++)
                nucleonEmission(candidate, 4, 2);
        for (size_t i = 0; i < nProton; i++)
                nucleonEmission(candidate, 1, 1);
        for (size_t i = 0; i < nNeutron; i++)
                nucleonEmission(candidate, 1, 0);
}

void NuclearDecay::gammaEmission(Candidate *candidate, int channel) const {
        int id = candidate->current.getId();
        int Z = chargeNumber(id);
        int N = massNumber(id) - Z;

        // get photon energies and emission probabilities for decay channel
        const std::vector<DecayMode> &decays = decayTable[Z * 31 + N];
        size_t idecay = decays.size();
        while (idecay-- != 0) {
                if (decays[idecay].channel == channel)
                        break;
        }

        const std::vector<double> &energy = decays[idecay].energy;
        const std::vector<double> &intensity = decays[idecay].intensity;

        // check if photon emission available
        if (energy.size() == 0)
                return;

        Random &random = Random::instance();
        Vector3d pos = random.randomInterpolatedPosition(candidate->previous.getPosition(), candidate->current.getPosition());

        for (int i = 0; i < energy.size(); ++i) {
                // check if photon of specific energy is emitted
                if (random.rand() > intensity[i])
                        continue;
                // create secondary photon; boost to lab frame
                double cosTheta = 2 * random.rand() - 1;
                double E = energy[i] * candidate->current.getLorentzFactor() * (1. - cosTheta);
                candidate->addSecondary(22, E, pos, 1., interactionTag);
        }
}

void NuclearDecay::betaDecay(Candidate *candidate, bool isBetaPlus) const {
        double gamma = candidate->current.getLorentzFactor();
        int id = candidate->current.getId();
        int A = massNumber(id);
        int Z = chargeNumber(id);

        // beta- decay
        int electronId = 11; // electron
        int neutrinoId = -12; // anti-electron neutrino
        int dZ = 1;
        // beta+ decay
        if (isBetaPlus) {
                electronId = -11; // positron
                neutrinoId = 12; // electron neutrino
                dZ = -1;
        }

        // update candidate, nuclear recoil negligible
        try
        {
                candidate->current.setId(nucleusId(A, Z + dZ));
        }
        catch (std::runtime_error &e)
        {
                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();
                throw;
        }

        candidate->current.setLorentzFactor(gamma);

        if (not (haveElectrons or haveNeutrinos))
                return;

        // Q-value of the decay, subtract total energy of emitted photons
        double m1 = nuclearMass(A, Z);
        double m2 = nuclearMass(A, Z+dZ);
        double Q = (m1 - m2 - mass_electron) * c_squared;

        // generate cdf of electron energy, neglecting Coulomb correction
        // see Basdevant, Fundamentals in Nuclear Physics, eq. (4.92)
        // This leads to deviations from theoretical expectations at low
        // primary energies.
        std::vector<double> energies;
        std::vector<double> densities; // cdf(E), unnormalized

        energies.reserve(51);
        densities.reserve(51);

        double me = mass_electron * c_squared;
        double cdf = 0;
        for (int i = 0; i <= 50; i++) {
                double E = me + i / 50. * Q;
                cdf += E * sqrt(E * E - me * me) * pow(Q + me - E, 2);
                energies.push_back(E);
                densities.push_back(cdf);
        }

        // draw random electron energy and angle
        // assumption of ultra-relativistic particles
        // leads to deviations from theoretical predictions
        // is not problematic for usual CRPropa energies E>~TeV
        Random &random = Random::instance();
        double E = interpolate(random.rand() * cdf, densities, energies);
        double p = sqrt(E * E - me * me);  // p*c
        double cosTheta = 2 * random.rand() - 1;

        // boost to lab frame
        double Ee = gamma * (E - p * cosTheta);
        double Enu = gamma * (Q + me - E) * (1 + cosTheta);  // pnu*c ~ Enu

        Vector3d pos = random.randomInterpolatedPosition(candidate->previous.getPosition(), candidate->current.getPosition());
        if (haveElectrons)
                candidate->addSecondary(electronId, Ee, pos, 1., interactionTag);
        if (haveNeutrinos)
                candidate->addSecondary(neutrinoId, Enu, pos, 1., interactionTag);
}

void NuclearDecay::nucleonEmission(Candidate *candidate, int dA, int dZ) const {
        Random &random = Random::instance();
        int id = candidate->current.getId();
        int A = massNumber(id);
        int Z = chargeNumber(id);
        double EpA = candidate->current.getEnergy() / double(A);

        try
        {
                candidate->current.setId(nucleusId(A - dA, Z - dZ));
        }
        catch (std::runtime_error &e)
        {
                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();
                throw;
        }

        candidate->current.setEnergy(EpA * (A - dA));
        Vector3d pos = random.randomInterpolatedPosition(candidate->previous.getPosition(),candidate->current.getPosition());

        try
        {
                candidate->addSecondary(nucleusId(dA, dZ), EpA * dA, pos, 1., interactionTag);
        }
        catch (std::runtime_error &e)
        {
                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();
                throw;
        }

}

double NuclearDecay::meanFreePath(int id, double gamma) {
        if (not (isNucleus(id)))
                return std::numeric_limits<double>::max();

        int A = massNumber(id);
        int Z = chargeNumber(id);
        int N = A - Z;

        // check if particle can decay
        const std::vector<DecayMode> &decays = decayTable[Z * 31 + N];
        if (decays.size() == 0)
                return std::numeric_limits<double>::max();

        double totalRate = 0;

        for (size_t i = 0; i < decays.size(); i++) {
                double rate = decays[i].rate;
                rate /= gamma;
                totalRate += rate;
        }

        return 1. / totalRate;
}

void NuclearDecay::setInteractionTag(std::string tag) {
        interactionTag = tag;
}

std::string NuclearDecay::getInteractionTag() const {
        return interactionTag;
}

} // namespace crpropa