Module taurunner.particle.particle

Expand source code
import numpy as np
import proposal as pp
from importlib.resources import path

import taurunner
from taurunner.utils import units
from taurunner.cross_sections import CrossSections
from .utils import *

from proposal import Propagator
from numpy.random import RandomState
import warnings

proton_mass = ((0.9382720813+0.9395654133)/2.)*units.GeV

ID_2_name = {13:'MuMinusDef', -13:'MuPlusDef', 
             15:'TauMinusDef', -15:'TauPlusDef'}
EMIN = 1e9 # minimum energy allowed in the splines
#Particle object. 
class Particle(object):
    r'''
    This is the class that contains all relevant 
    particle information stored in an object.
    '''
    def __init__(self, 
                 ID:                  int, 
                 energy:              float, 
                 incoming_angle:      float, 
                 position:            float, 
                 rand:                RandomState,
                 xs:                  CrossSections, 
                 proposal_propagator: Propagator,
                 secondaries:         bool, 
                 no_losses:           bool
                ):
        r'''
        Class initializer. This function sets all initial conditions based 
        on the particle's incoming angle, energy, ID, and position.

        Params
        ------
        ID                  : PDG particle identifier
        energy              : Initial energy of the particle [eV]
        incoming_angle      : Incoming_angle [radians]
        position            : Affine paramter describing the distance along the track of the particle (0<x<1)
        rand                : numpy random number generator
        xs                  : TauRunner CrossSections object
        proposal_propagator : PROPOSAL propagator object for moving charged lepton
        secondaries         : Boolean telling whether to include secondary (mu and e) neutrinos from tau decay
        no_losses           : Boolean to turn off charged lepton losses
        '''
        #Set Initial Values
        self.ID              = ID
        self.initial_energy  = energy
        self.energy          = energy
        self.position        = position
        self.chargedposition = 0.0
        self.SetParticleProperties()
        self.secondaries     = secondaries
        self.survived        = True
        self.basket          = []
        self.nCC             = 0
        self.nNC             = 0
        self.ntdecay         = 0
        self.rand            = rand
        self.xs              = xs
        self.xs_model        = xs.model
        self.propagator      = proposal_propagator
        self.losses          = not no_losses
        if ID > 0:
            self.nutype = 'nu'
        elif ID < 0:
            self.nutype = 'nubar'
        
    def SetParticleProperties(self):
        r'''
        Sets particle properties, either when initializing or after an interaction.
        '''
        if np.abs(self.ID) in [12, 14, 16]:
            self.mass = 0.0          #this is not true.. and it seems to have caused quite the stir.
            self.lifetime = np.inf   #this is unclear
        if np.abs(self.ID) == 15:
            self.mass = 1.776*units.GeV
            self.lifetime = 2.9e-13*units.sec
        if np.abs(self.ID) == 13:
            self.mass = 0.105*units.GeV
            self.livetime = 2.2e-6*units.sec

    def GetParticleId(self):
        r'''
        Returns the current particle ID        
        '''
        return self.ID

    def PrintParticleProperties(self): # pragma: no cover
        print("id", self.ID, \
              "energy ", self.energy/units.GeV, " GeV", \
              "position ", self.position/units.km, " km")

    def GetLifetime(self):
        r'''
        Returns the current particle's lifetime
        '''
        return self.lifetime

    def GetMass(self):
        r'''
        Returns the current particle's mass
        '''
        return self.mass

    def GetProposedDepthStep(self, p):
        r'''
        Calculates the free-streaming column depth of your neutrino based
        on the cross section, and then samples randomly from a log-uniform
        distribution.

        Parameters
        ------------
        p:       float
            random number. the free-streaming column depth is scaled by
            the log of this number
        Returns
        -----------
        DepthStep: float
            Column depth to interaction in natural units
        '''
        #Calculate the inverse of the interaction depths.
        first_piece = (1./self.GetInteractionDepth(interaction='CC'))
        second_piece = (1./self.GetInteractionDepth(interaction='NC'))
        step = (first_piece + second_piece)

        #return the column depth to interaction - weighted by a random number
        return -np.log(p)/step

    def GetTotalInteractionDepth(self):
        return(1./(1./self.GetInteractionDepth(interaction='NC')
               + 1./self.GetInteractionDepth(interaction='CC')))

    def GetInteractionDepth(self, interaction, proton_fraction=0.5):
        r'''
        Calculates the mean column depth to interaction.

        Parameters
        -----------
        interaction: str
            str defining the interaction type (CC or NC).
        Returns
        ----------
        Interaction depth: float
            mean column depth to interaction in natural units
        '''
        if np.abs(self.ID) in [12, 14, 16]:
            return proton_mass/(self.xs.total_cross_section(
                                                            self.energy, 
                                                            self.nutype, 
                                                            interaction,
                                                            proton_fraction=proton_fraction
                                                           )
                               )
        if np.abs(self.ID) == 15:
            raise ValueError("Tau interaction length should never be sampled.")

    def GetInteractionProbability(self,ddepth,interaction):
        return 1.-np.exp(-ddepth/self.GetInteractionDepth(interaction))

    def Decay(self):
        if np.abs(self.ID) in [12, 14, 16]:
            raise ValueError("no neutrino decays.. yet")
        if np.abs(self.ID) == 15:
            if self.secondaries:
                # sample branching ratio of tau leptonic decay
                p0 = self.rand.uniform(0,1)
                if p0 < .18:
                    # sample energy of secondary antinumu
                    sample = SampleSecondariesEnergyFraction(self.rand.uniform(0,1), antinumu_cdf)
                    enu = sample*self.energy
                    # add secondary to basket, prepare propagation
                    self.basket.append({"ID" : -np.sign(self.ID)*14, "position" : self.position, "energy" : enu})
                elif p0 > .18 and p0 < .36:
                    # sample energy of secondary antinue
                    sample = SampleSecondariesEnergyFraction(self.rand.uniform(0,1), antinue_cdf)
                    enu = sample*self.energy
                    # add secondary to basket, prepare propagation
                    self.basket.append({"ID" : -np.sign(self.ID)*12,  "position" : self.position, "energy" : enu})
            self.energy = self.energy*self.rand.choice(TauDecayFractions, p=TauDecayWeights)
            self.ID = np.sign(self.ID)*16
            self.SetParticleProperties()
            return
        if np.abs(self.ID) in [11, 13]:
            self.survived=False

    def PropagateChargedLepton(self, body, track):
        r'''
        Propagate taus/mus with PROPOSAL along 'track' through 'body'
        Parameters
        ----------
        body: str 
            Cross section model to use for the photohadronic losses
        track: bool
            This can be set to False to turn off energy losses. In this case, the particle decays at rest.
        '''
        if(np.logical_or(not self.losses, np.abs(self.ID) in [11, 12])):
            return
        lep              = pp.particle.DynamicData(getattr(pp.particle, ID_2_name[self.ID])().particle_type)
        current_km_dist  = track.x_to_d(self.position)*body.radius/units.km
        total_dist       = track.x_to_d(1.-self.position)*body.radius/units.km
        current_density  = body.get_average_density(track.x_to_r(self.position))
        km_dist_to_prop  = total_dist - current_km_dist

        if(np.logical_and(np.abs(self.ID) in [13, 14], km_dist_to_prop > 100.)):
             return

        lep_length  = []
        en_at_decay = []
        lep.energy     = self.energy/units.MeV
        pos_vec        = track.x_to_pp_pos(self.position, body.radius/units.cm) # radius in cm
        dir_vec        = track.x_to_pp_dir(self.position)
        lep.position   = pos_vec
        lep.direction  = dir_vec
        #propagate
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            sec            = self.propagator.propagate(lep) #, dist_to_prop)
        particles      = sec.particles
        #update particle info
        final_vec      = (sec.position[-1] - pos_vec)
        lep_length     = final_vec.magnitude() / 1e5
        decay_products = [p for i,p in zip(range(max(len(particles)-3,0),len(particles)), particles[-3:]) if int(p.type) <= 1000000001]
        en_at_decay    = np.sum([p.energy for p in decay_products])
        if(en_at_decay==0):    #particle reached the border before decaying
            self.energy = particles[-1].parent_particle_energy*units.MeV
            self.chargedposition = float(np.ceil(lep_length))
        else:                  #particle decayed before reaching the border
            self.energy    = en_at_decay*units.MeV
            self.chargedposition  = lep_length
        return sec

    def Interact(self, interaction, body=None, track=None, proton_fraction=0.5): #  dist_to_prop=None, current_density=None):
        if np.abs(self.ID) in [12, 14, 16]:
            #Sample energy lost from differential distributions
            NeutrinoInteractionWeights = self.xs.differential_cross_section(self.energy,
                                                                            NeutrinoDifferentialEnergyFractions,
                                                                            self.nutype,
                                                                            interaction,
                                                                            proton_fraction=proton_fraction
                                                    )
            NeutrinoInteractionWeights = np.divide(NeutrinoInteractionWeights, 
                                                   np.sum(NeutrinoInteractionWeights))
            z_choice = self.rand.choice(NeutrinoDifferentialEnergyFractions,
                                                       p=NeutrinoInteractionWeights)
            self.energy = z_choice*(self.energy-EMIN)+EMIN

            if interaction == 'CC':
                #make a charged particle
                self.nCC += 1
                self.ID = np.sign(self.ID)*(np.abs(self.ID)-1)
                if(np.abs(self.ID)==11): #electrons have no chance
                    self.survived=False
                    return
                self.SetParticleProperties()
            elif interaction == 'NC':
                #continue being a neutrino
                self.ID = self.ID
                self.SetParticleProperties()
                self.nNC += 1
            
            return
        else:
            raise ValueError('Particle ID not supported by this function') 

Classes

class Particle (ID: int, energy: float, incoming_angle: float, position: float, rand: mtrand.RandomState, xs: XSModel, proposal_propagator: proposal.Propagator, secondaries: bool, no_losses: bool)

This is the class that contains all relevant particle information stored in an object.

Class initializer. This function sets all initial conditions based on the particle's incoming angle, energy, ID, and position.

Params

ID : PDG particle identifier energy : Initial energy of the particle [eV] incoming_angle : Incoming_angle [radians] position : Affine paramter describing the distance along the track of the particle (0<x<1) rand : numpy random number generator xs : TauRunner CrossSections object proposal_propagator : PROPOSAL propagator object for moving charged lepton secondaries : Boolean telling whether to include secondary (mu and e) neutrinos from tau decay no_losses : Boolean to turn off charged lepton losses

Expand source code
class Particle(object):
    r'''
    This is the class that contains all relevant 
    particle information stored in an object.
    '''
    def __init__(self, 
                 ID:                  int, 
                 energy:              float, 
                 incoming_angle:      float, 
                 position:            float, 
                 rand:                RandomState,
                 xs:                  CrossSections, 
                 proposal_propagator: Propagator,
                 secondaries:         bool, 
                 no_losses:           bool
                ):
        r'''
        Class initializer. This function sets all initial conditions based 
        on the particle's incoming angle, energy, ID, and position.

        Params
        ------
        ID                  : PDG particle identifier
        energy              : Initial energy of the particle [eV]
        incoming_angle      : Incoming_angle [radians]
        position            : Affine paramter describing the distance along the track of the particle (0<x<1)
        rand                : numpy random number generator
        xs                  : TauRunner CrossSections object
        proposal_propagator : PROPOSAL propagator object for moving charged lepton
        secondaries         : Boolean telling whether to include secondary (mu and e) neutrinos from tau decay
        no_losses           : Boolean to turn off charged lepton losses
        '''
        #Set Initial Values
        self.ID              = ID
        self.initial_energy  = energy
        self.energy          = energy
        self.position        = position
        self.chargedposition = 0.0
        self.SetParticleProperties()
        self.secondaries     = secondaries
        self.survived        = True
        self.basket          = []
        self.nCC             = 0
        self.nNC             = 0
        self.ntdecay         = 0
        self.rand            = rand
        self.xs              = xs
        self.xs_model        = xs.model
        self.propagator      = proposal_propagator
        self.losses          = not no_losses
        if ID > 0:
            self.nutype = 'nu'
        elif ID < 0:
            self.nutype = 'nubar'
        
    def SetParticleProperties(self):
        r'''
        Sets particle properties, either when initializing or after an interaction.
        '''
        if np.abs(self.ID) in [12, 14, 16]:
            self.mass = 0.0          #this is not true.. and it seems to have caused quite the stir.
            self.lifetime = np.inf   #this is unclear
        if np.abs(self.ID) == 15:
            self.mass = 1.776*units.GeV
            self.lifetime = 2.9e-13*units.sec
        if np.abs(self.ID) == 13:
            self.mass = 0.105*units.GeV
            self.livetime = 2.2e-6*units.sec

    def GetParticleId(self):
        r'''
        Returns the current particle ID        
        '''
        return self.ID

    def PrintParticleProperties(self): # pragma: no cover
        print("id", self.ID, \
              "energy ", self.energy/units.GeV, " GeV", \
              "position ", self.position/units.km, " km")

    def GetLifetime(self):
        r'''
        Returns the current particle's lifetime
        '''
        return self.lifetime

    def GetMass(self):
        r'''
        Returns the current particle's mass
        '''
        return self.mass

    def GetProposedDepthStep(self, p):
        r'''
        Calculates the free-streaming column depth of your neutrino based
        on the cross section, and then samples randomly from a log-uniform
        distribution.

        Parameters
        ------------
        p:       float
            random number. the free-streaming column depth is scaled by
            the log of this number
        Returns
        -----------
        DepthStep: float
            Column depth to interaction in natural units
        '''
        #Calculate the inverse of the interaction depths.
        first_piece = (1./self.GetInteractionDepth(interaction='CC'))
        second_piece = (1./self.GetInteractionDepth(interaction='NC'))
        step = (first_piece + second_piece)

        #return the column depth to interaction - weighted by a random number
        return -np.log(p)/step

    def GetTotalInteractionDepth(self):
        return(1./(1./self.GetInteractionDepth(interaction='NC')
               + 1./self.GetInteractionDepth(interaction='CC')))

    def GetInteractionDepth(self, interaction, proton_fraction=0.5):
        r'''
        Calculates the mean column depth to interaction.

        Parameters
        -----------
        interaction: str
            str defining the interaction type (CC or NC).
        Returns
        ----------
        Interaction depth: float
            mean column depth to interaction in natural units
        '''
        if np.abs(self.ID) in [12, 14, 16]:
            return proton_mass/(self.xs.total_cross_section(
                                                            self.energy, 
                                                            self.nutype, 
                                                            interaction,
                                                            proton_fraction=proton_fraction
                                                           )
                               )
        if np.abs(self.ID) == 15:
            raise ValueError("Tau interaction length should never be sampled.")

    def GetInteractionProbability(self,ddepth,interaction):
        return 1.-np.exp(-ddepth/self.GetInteractionDepth(interaction))

    def Decay(self):
        if np.abs(self.ID) in [12, 14, 16]:
            raise ValueError("no neutrino decays.. yet")
        if np.abs(self.ID) == 15:
            if self.secondaries:
                # sample branching ratio of tau leptonic decay
                p0 = self.rand.uniform(0,1)
                if p0 < .18:
                    # sample energy of secondary antinumu
                    sample = SampleSecondariesEnergyFraction(self.rand.uniform(0,1), antinumu_cdf)
                    enu = sample*self.energy
                    # add secondary to basket, prepare propagation
                    self.basket.append({"ID" : -np.sign(self.ID)*14, "position" : self.position, "energy" : enu})
                elif p0 > .18 and p0 < .36:
                    # sample energy of secondary antinue
                    sample = SampleSecondariesEnergyFraction(self.rand.uniform(0,1), antinue_cdf)
                    enu = sample*self.energy
                    # add secondary to basket, prepare propagation
                    self.basket.append({"ID" : -np.sign(self.ID)*12,  "position" : self.position, "energy" : enu})
            self.energy = self.energy*self.rand.choice(TauDecayFractions, p=TauDecayWeights)
            self.ID = np.sign(self.ID)*16
            self.SetParticleProperties()
            return
        if np.abs(self.ID) in [11, 13]:
            self.survived=False

    def PropagateChargedLepton(self, body, track):
        r'''
        Propagate taus/mus with PROPOSAL along 'track' through 'body'
        Parameters
        ----------
        body: str 
            Cross section model to use for the photohadronic losses
        track: bool
            This can be set to False to turn off energy losses. In this case, the particle decays at rest.
        '''
        if(np.logical_or(not self.losses, np.abs(self.ID) in [11, 12])):
            return
        lep              = pp.particle.DynamicData(getattr(pp.particle, ID_2_name[self.ID])().particle_type)
        current_km_dist  = track.x_to_d(self.position)*body.radius/units.km
        total_dist       = track.x_to_d(1.-self.position)*body.radius/units.km
        current_density  = body.get_average_density(track.x_to_r(self.position))
        km_dist_to_prop  = total_dist - current_km_dist

        if(np.logical_and(np.abs(self.ID) in [13, 14], km_dist_to_prop > 100.)):
             return

        lep_length  = []
        en_at_decay = []
        lep.energy     = self.energy/units.MeV
        pos_vec        = track.x_to_pp_pos(self.position, body.radius/units.cm) # radius in cm
        dir_vec        = track.x_to_pp_dir(self.position)
        lep.position   = pos_vec
        lep.direction  = dir_vec
        #propagate
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            sec            = self.propagator.propagate(lep) #, dist_to_prop)
        particles      = sec.particles
        #update particle info
        final_vec      = (sec.position[-1] - pos_vec)
        lep_length     = final_vec.magnitude() / 1e5
        decay_products = [p for i,p in zip(range(max(len(particles)-3,0),len(particles)), particles[-3:]) if int(p.type) <= 1000000001]
        en_at_decay    = np.sum([p.energy for p in decay_products])
        if(en_at_decay==0):    #particle reached the border before decaying
            self.energy = particles[-1].parent_particle_energy*units.MeV
            self.chargedposition = float(np.ceil(lep_length))
        else:                  #particle decayed before reaching the border
            self.energy    = en_at_decay*units.MeV
            self.chargedposition  = lep_length
        return sec

    def Interact(self, interaction, body=None, track=None, proton_fraction=0.5): #  dist_to_prop=None, current_density=None):
        if np.abs(self.ID) in [12, 14, 16]:
            #Sample energy lost from differential distributions
            NeutrinoInteractionWeights = self.xs.differential_cross_section(self.energy,
                                                                            NeutrinoDifferentialEnergyFractions,
                                                                            self.nutype,
                                                                            interaction,
                                                                            proton_fraction=proton_fraction
                                                    )
            NeutrinoInteractionWeights = np.divide(NeutrinoInteractionWeights, 
                                                   np.sum(NeutrinoInteractionWeights))
            z_choice = self.rand.choice(NeutrinoDifferentialEnergyFractions,
                                                       p=NeutrinoInteractionWeights)
            self.energy = z_choice*(self.energy-EMIN)+EMIN

            if interaction == 'CC':
                #make a charged particle
                self.nCC += 1
                self.ID = np.sign(self.ID)*(np.abs(self.ID)-1)
                if(np.abs(self.ID)==11): #electrons have no chance
                    self.survived=False
                    return
                self.SetParticleProperties()
            elif interaction == 'NC':
                #continue being a neutrino
                self.ID = self.ID
                self.SetParticleProperties()
                self.nNC += 1
            
            return
        else:
            raise ValueError('Particle ID not supported by this function') 

Methods

def Decay(self)
Expand source code
def Decay(self):
    if np.abs(self.ID) in [12, 14, 16]:
        raise ValueError("no neutrino decays.. yet")
    if np.abs(self.ID) == 15:
        if self.secondaries:
            # sample branching ratio of tau leptonic decay
            p0 = self.rand.uniform(0,1)
            if p0 < .18:
                # sample energy of secondary antinumu
                sample = SampleSecondariesEnergyFraction(self.rand.uniform(0,1), antinumu_cdf)
                enu = sample*self.energy
                # add secondary to basket, prepare propagation
                self.basket.append({"ID" : -np.sign(self.ID)*14, "position" : self.position, "energy" : enu})
            elif p0 > .18 and p0 < .36:
                # sample energy of secondary antinue
                sample = SampleSecondariesEnergyFraction(self.rand.uniform(0,1), antinue_cdf)
                enu = sample*self.energy
                # add secondary to basket, prepare propagation
                self.basket.append({"ID" : -np.sign(self.ID)*12,  "position" : self.position, "energy" : enu})
        self.energy = self.energy*self.rand.choice(TauDecayFractions, p=TauDecayWeights)
        self.ID = np.sign(self.ID)*16
        self.SetParticleProperties()
        return
    if np.abs(self.ID) in [11, 13]:
        self.survived=False
def GetInteractionDepth(self, interaction, proton_fraction=0.5)

Calculates the mean column depth to interaction.

Parameters

interaction : str
str defining the interaction type (CC or NC).

Returns

Interaction depth: float
mean column depth to interaction in natural units
Expand source code
def GetInteractionDepth(self, interaction, proton_fraction=0.5):
    r'''
    Calculates the mean column depth to interaction.

    Parameters
    -----------
    interaction: str
        str defining the interaction type (CC or NC).
    Returns
    ----------
    Interaction depth: float
        mean column depth to interaction in natural units
    '''
    if np.abs(self.ID) in [12, 14, 16]:
        return proton_mass/(self.xs.total_cross_section(
                                                        self.energy, 
                                                        self.nutype, 
                                                        interaction,
                                                        proton_fraction=proton_fraction
                                                       )
                           )
    if np.abs(self.ID) == 15:
        raise ValueError("Tau interaction length should never be sampled.")
def GetInteractionProbability(self, ddepth, interaction)
Expand source code
def GetInteractionProbability(self,ddepth,interaction):
    return 1.-np.exp(-ddepth/self.GetInteractionDepth(interaction))
def GetLifetime(self)

Returns the current particle's lifetime

Expand source code
def GetLifetime(self):
    r'''
    Returns the current particle's lifetime
    '''
    return self.lifetime
def GetMass(self)

Returns the current particle's mass

Expand source code
def GetMass(self):
    r'''
    Returns the current particle's mass
    '''
    return self.mass
def GetParticleId(self)

Returns the current particle ID

Expand source code
def GetParticleId(self):
    r'''
    Returns the current particle ID        
    '''
    return self.ID
def GetProposedDepthStep(self, p)

Calculates the free-streaming column depth of your neutrino based on the cross section, and then samples randomly from a log-uniform distribution.

Parameters

p :  float
random number. the free-streaming column depth is scaled by the log of this number

Returns

DepthStep : float
Column depth to interaction in natural units
Expand source code
def GetProposedDepthStep(self, p):
    r'''
    Calculates the free-streaming column depth of your neutrino based
    on the cross section, and then samples randomly from a log-uniform
    distribution.

    Parameters
    ------------
    p:       float
        random number. the free-streaming column depth is scaled by
        the log of this number
    Returns
    -----------
    DepthStep: float
        Column depth to interaction in natural units
    '''
    #Calculate the inverse of the interaction depths.
    first_piece = (1./self.GetInteractionDepth(interaction='CC'))
    second_piece = (1./self.GetInteractionDepth(interaction='NC'))
    step = (first_piece + second_piece)

    #return the column depth to interaction - weighted by a random number
    return -np.log(p)/step
def GetTotalInteractionDepth(self)
Expand source code
def GetTotalInteractionDepth(self):
    return(1./(1./self.GetInteractionDepth(interaction='NC')
           + 1./self.GetInteractionDepth(interaction='CC')))
def Interact(self, interaction, body=None, track=None, proton_fraction=0.5)
Expand source code
def Interact(self, interaction, body=None, track=None, proton_fraction=0.5): #  dist_to_prop=None, current_density=None):
    if np.abs(self.ID) in [12, 14, 16]:
        #Sample energy lost from differential distributions
        NeutrinoInteractionWeights = self.xs.differential_cross_section(self.energy,
                                                                        NeutrinoDifferentialEnergyFractions,
                                                                        self.nutype,
                                                                        interaction,
                                                                        proton_fraction=proton_fraction
                                                )
        NeutrinoInteractionWeights = np.divide(NeutrinoInteractionWeights, 
                                               np.sum(NeutrinoInteractionWeights))
        z_choice = self.rand.choice(NeutrinoDifferentialEnergyFractions,
                                                   p=NeutrinoInteractionWeights)
        self.energy = z_choice*(self.energy-EMIN)+EMIN

        if interaction == 'CC':
            #make a charged particle
            self.nCC += 1
            self.ID = np.sign(self.ID)*(np.abs(self.ID)-1)
            if(np.abs(self.ID)==11): #electrons have no chance
                self.survived=False
                return
            self.SetParticleProperties()
        elif interaction == 'NC':
            #continue being a neutrino
            self.ID = self.ID
            self.SetParticleProperties()
            self.nNC += 1
        
        return
    else:
        raise ValueError('Particle ID not supported by this function') 
def PrintParticleProperties(self)
Expand source code
def PrintParticleProperties(self): # pragma: no cover
    print("id", self.ID, \
          "energy ", self.energy/units.GeV, " GeV", \
          "position ", self.position/units.km, " km")
def PropagateChargedLepton(self, body, track)

Propagate taus/mus with PROPOSAL along 'track' through 'body' Parameters


body : str
Cross section model to use for the photohadronic losses
track : bool
This can be set to False to turn off energy losses. In this case, the particle decays at rest.
Expand source code
def PropagateChargedLepton(self, body, track):
    r'''
    Propagate taus/mus with PROPOSAL along 'track' through 'body'
    Parameters
    ----------
    body: str 
        Cross section model to use for the photohadronic losses
    track: bool
        This can be set to False to turn off energy losses. In this case, the particle decays at rest.
    '''
    if(np.logical_or(not self.losses, np.abs(self.ID) in [11, 12])):
        return
    lep              = pp.particle.DynamicData(getattr(pp.particle, ID_2_name[self.ID])().particle_type)
    current_km_dist  = track.x_to_d(self.position)*body.radius/units.km
    total_dist       = track.x_to_d(1.-self.position)*body.radius/units.km
    current_density  = body.get_average_density(track.x_to_r(self.position))
    km_dist_to_prop  = total_dist - current_km_dist

    if(np.logical_and(np.abs(self.ID) in [13, 14], km_dist_to_prop > 100.)):
         return

    lep_length  = []
    en_at_decay = []
    lep.energy     = self.energy/units.MeV
    pos_vec        = track.x_to_pp_pos(self.position, body.radius/units.cm) # radius in cm
    dir_vec        = track.x_to_pp_dir(self.position)
    lep.position   = pos_vec
    lep.direction  = dir_vec
    #propagate
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        sec            = self.propagator.propagate(lep) #, dist_to_prop)
    particles      = sec.particles
    #update particle info
    final_vec      = (sec.position[-1] - pos_vec)
    lep_length     = final_vec.magnitude() / 1e5
    decay_products = [p for i,p in zip(range(max(len(particles)-3,0),len(particles)), particles[-3:]) if int(p.type) <= 1000000001]
    en_at_decay    = np.sum([p.energy for p in decay_products])
    if(en_at_decay==0):    #particle reached the border before decaying
        self.energy = particles[-1].parent_particle_energy*units.MeV
        self.chargedposition = float(np.ceil(lep_length))
    else:                  #particle decayed before reaching the border
        self.energy    = en_at_decay*units.MeV
        self.chargedposition  = lep_length
    return sec
def SetParticleProperties(self)

Sets particle properties, either when initializing or after an interaction.

Expand source code
def SetParticleProperties(self):
    r'''
    Sets particle properties, either when initializing or after an interaction.
    '''
    if np.abs(self.ID) in [12, 14, 16]:
        self.mass = 0.0          #this is not true.. and it seems to have caused quite the stir.
        self.lifetime = np.inf   #this is unclear
    if np.abs(self.ID) == 15:
        self.mass = 1.776*units.GeV
        self.lifetime = 2.9e-13*units.sec
    if np.abs(self.ID) == 13:
        self.mass = 0.105*units.GeV
        self.livetime = 2.2e-6*units.sec