Module taurunner.main

Expand source code
#!/usr/bin/env python

import os, sys, json
import proposal as pp

from taurunner.utils import units, sample_powerlaw, is_floatable, make_propagator
import taurunner.track as track
from taurunner.body import *
from taurunner.cross_sections import CrossSections
from taurunner.Casino import *
from taurunner.particle import Particle

def initialize_parser(): # pragma: no cover
    r'''
    Helper function to parse command-line arguments
    '''
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('-s',
                        dest='seed',
                        type=int, 
                        default=None,
                        help='just an integer seed to help with output file names'
                       )
    parser.add_argument('-n', 
                        dest='nevents', 
                        type=int, 
                        default=0,
                        help='how many events do you want?'
                       )
    parser.add_argument('-flavor', 
                        dest='flavor', 
                        type=int, default=16,
                        help='neutrino flavor (default is nutau): 12 for nue 14 for numu 16 for nutau'
                       )
    parser.add_argument('--track',
                        dest='track',
                        type=str,
                        default='Chord',
                        help='Track type to use. curently only radial and chord trajectories supported.'
                       )
    # Energy arguments
    parser.add_argument('-e', 
                        dest='energy',
                        default='',
                        help='Energy in GeV if numerical value greater than 0 passed.\n\
                              Sprectral index if numerical value less than or equal to 0 passed.\n\
                              Else path to CDF to sample from.'
                       )
    parser.add_argument('--e_min',
                        type=float,
                        default=1e6,
                        help='Minimum energy to sample from if doing powerlaw sampling'
                       )
    parser.add_argument('--e_max',
                        type=float,
                        default=1e9,
                        help='Minimum energy to sample from if doing powerlaw sampling'
                       )
    
    # Angular arguments
    parser.add_argument('-t', 
                        dest='theta',
                        default='',
                        help='nadir angle in degrees if numerical value(0 is through the core).\n\
                              "range" if you want to sample from a range of thetas. Use --th_min and --th_max to specify lims.'
                       )
    parser.add_argument('--th_max',
                        dest='th_max',
                        type=float, 
                        default=90,
                        help='If doing a theta range, maximum theta value. Default 90, i.e. skimming'
                       )
    parser.add_argument('--th_min', 
                        type=float,
                        default=0,
                        help='If doing a theta range, minimum theta value. Default 0, i.e. through the core'
                       )
    
    # Saving arguments
    parser.add_argument('--save', 
                        dest='save', 
                        type=str, 
                        default='', 
                        help="If saving output, provide a path here, if not specified, output will be printed"
                       )

    # cross section args
    parser.add_argument('--xs',
                        dest='xs_model',
                        type=str,
                        default='dipole',
                        help="Enter 'CSMS' if you would like to run the simulation with a pQCD xs model"
                       )

    # Body specification args
    parser.add_argument('--body',
                        dest='body',
                        default='earth',
                        help="body to use"
                       )
    parser.add_argument('--water',
                        dest='water',
                        type=float, default=0,
                        help="If you you would like to add a water layer to the Earth model, enter it here in km."
                       )
    parser.add_argument('--radius',
                        dest='radius',
                        type=float,
                        default=0,
                        help="Raise this flag if you want to turn off tau losses. In this case, taus will decay at rest."
                       )
    parser.add_argument('--depth',
                        dest='depth',
                        type=float,
                        default=0,
                        help="Depth of the detector in km."
                       )
    
    # Options
    parser.add_argument('--no_losses',
                        dest='no_losses', 
                        default=False,
                        action='store_true',
                        help="Raise this flag if you want to turn off tau losses. In this case, taus will decay at rest."
                       )
    parser.add_argument('--no_secondaries',
                        default=False,
                        action='store_true',
                        help="Raise this flag to turn off secondaries"
                       )
    parser.add_argument('-d', 
                        dest='debug', 
                        default=False, 
                        action='store_true', 
                        help='Do you want to print out debug statments? If so, raise this flag'
                       ) 
    parser.add_argument('--e_cut',
                        dest='e_cut',
                        default=0.0,
                        type=float,
                        help='Energy at which to stop the propagation.'
                       )

    args = parser.parse_args()
    return args

def run_MC(eini: np.ndarray, 
           thetas: np.ndarray,
           body: Body, 
           xs: CrossSections, 
           tracks: dict, 
           propagator: pp.Propagator,
           rand: np.random.RandomState = None,
           no_secondaries: bool = False,
           flavor: int = 16,
           no_losses: bool = False,
           condition=None
          ) -> np.ndarray:
    r'''
    Main simulation code. Propagates an ensemble of initial states and returns the output
    Params
    ______
    eini       : array containing the initial energies of particles to simulate
    thetas     : array containing the incoming angles of particles to simulate
    body       : taurunner Body object in which to propagate the particles
    xs         : taurunner CrossSections object for interactions
    tracks     : dictionary whose keys are angles and whose values are taurunner Track objects
    propagator : PROPOSAL propagator object for charged lepton propagation
    Returns
    _______
    output : Array containing the output information of the MC. This includes 
             initial and final energies, incident incoming angles, number of CC and NC interactions,
             and particle type (PDG convention)
             
    '''
    nevents     = len(eini)
    output      = []
    energies    = list(eini)
    particleIDs = np.ones(nevents, dtype=int)*flavor
    xs_model    = xs.model

    if rand is None:
        rand = np.random.RandomState()
    secondary_basket = []
    idxx             = []

    # Run the algorithm
    # All neutrinos are propagated until exiting as tau neutrino or taus.
    # If secondaries are on, then each event has a corresponding secondaries basket
    # which are propagated all at once in the end.
    for i in range(nevents):
        particle = Particle(particleIDs[i], energies[i], thetas[i], 0.0, rand, xs,
                            propagator, not no_secondaries, no_losses)
        
        my_track = tracks[thetas[i]]
        out      = Propagate(particle, my_track, body, condition=condition)
    
        if (out.survived==False):
            #these muons/electrons were absorbed. we record them in the output with outgoing energy 0
            output.append((energies[i], 0., thetas[i], out.nCC, out.nNC, out.ID, i, out.position))
        else:
            output.append((energies[i], float(out.energy), thetas[i], out.nCC, out.nNC, out.ID, i, out.position))
        if not no_secondaries:
            secondary_basket.append(np.asarray(out.basket))
            idxx = np.hstack([idxx, [i for _ in out.basket]])
        del out
        del particle
    idxx = np.asarray(idxx).astype(np.int32)
    if not no_secondaries:    
        #make muon propagator
        secondary_basket = np.concatenate(secondary_basket)
        sec_prop         = {ID:make_propagator(ID, body, xs_model) for ID in [-12, -14]}

        for sec, i in zip(secondary_basket, idxx):
            my_track = tracks[thetas[i]]
            sec_particle = Particle(
                                    sec['ID'], 
                                    sec['energy'],
                                    thetas[i], 
                                    sec['position'], 
                                    rand,
                                    xs=xs, 
                                    proposal_propagator=sec_prop[sec['ID']], 
                                    secondaries=False, 
                                    no_losses=False
                                   )
            sec_out = Propagate(sec_particle, my_track, body, condition=condition)
            if(not sec_out.survived):
                output.append((sec_out.initial_energy, 0.0, thetas[i], sec_out.nCC, sec_out.nNC, sec_out.ID, i, sec_out.position))
            else:
                output.append((sec_out.initial_energy, sec_out.energy, thetas[i], 
                                                 sec_out.nCC, sec_out.nNC, sec_out.ID, i, sec_out.position))
            del sec_particle
            del sec_out     
        
    output = np.array(output, dtype = [('Eini', float), ('Eout',float), ('Theta', float), ('nCC', int), ('nNC', int), ('PDG_Encoding', int), ('primary_tau', int), ('final_position', float)])
    output['Theta'] = np.degrees(output['Theta'])
                       
    return output

if __name__ == "__main__": # pragma: no cover

    args = initialize_parser()
    TR_specs = {}
    TR_specs['seed']           = args.seed
    TR_specs['nevents']        = args.nevents
    TR_specs['flavor']         = args.flavor
    TR_specs['track']          = args.track
    TR_specs['energy']         = args.energy
    TR_specs['e_min']          = args.e_min
    TR_specs['e_max']          = args.e_max
    TR_specs['theta']          = args.theta
    TR_specs['th_min']         = args.th_min
    TR_specs['th_max']         = args.th_max
    TR_specs['save']           = args.save
    TR_specs['water']          = args.water
    TR_specs['xs_model']       = args.xs_model
    TR_specs['no_losses']      = args.no_losses
    TR_specs['body']           = args.body
    TR_specs['radius']         = args.radius
    TR_specs['depth']          = args.depth
    TR_specs['no_secondaries'] = args.no_secondaries
    TR_specs['debug']          = ''

    if TR_specs['nevents']<=0:
        raise ValueError("We need to simulate at least one event, c'mon y'all")

    # Set up outdir and make seed if not provided
    if TR_specs['save']:
        savedir = '/'.join(TR_specs['save'].split('/')[:-1])
        if not os.path.isdir(savedir):
            raise ValueError('Savedir %s does not exist' % TR_specs['save'])
    
    # Set up a random state
    rand = np.random.RandomState(TR_specs['seed'])

    # Make body
    # TODO Make this not suck. i.e. make construct body more comprehensive
    from taurunner.utils import construct_body
    body = construct_body(TR_specs)

    # Make an array of injected energies
    from taurunner.utils.make_initial_e import make_initial_e
    eini = make_initial_e(TR_specs['nevents'], TR_specs['energy'],
                          e_min=TR_specs['e_min'], e_max=TR_specs['e_max'], rand=rand)

    # Make an array of injected incident angles
    from taurunner.utils.make_initial_thetas import make_initial_thetas
    if(TR_specs['theta']=='range'):
        theta = (TR_specs['th_min'], TR_specs['th_max'])
    else:
        theta = TR_specs['theta']
    thetas = make_initial_thetas(TR_specs['nevents'], theta, rand=rand, track_type=TR_specs['track'])

    from taurunner.utils.make_tracks import make_tracks
    tracks = make_tracks(thetas, depth=TR_specs['depth']*units.km/body.radius, track_type=TR_specs['track'])

    xs = CrossSections(TR_specs['xs_model'])

    prop = make_propagator(TR_specs['flavor'], body, TR_specs['xs_model'])

    if args.e_cut:
        condition = lambda particle: (particle.energy <= args.e_cut*units.GeV and abs(particle.ID) in [12,14,16])
    else:
        condition = None

    result = run_MC(eini, thetas, body, xs, tracks, prop, rand=rand,
                    no_secondaries=TR_specs['no_secondaries'], no_losses=TR_specs['no_losses'],
                    flavor=TR_specs['flavor'], condition=condition)

    if TR_specs['save']:
        if '.npy' not in TR_specs['save']:
            TR_specs['save'] += '.npy'
        np.save(TR_specs['save'], result)
        with open(TR_specs['save'].replace('npy', 'json'), 'w') as f:
            json.dump(TR_specs, f)
        if TR_specs['debug']:
            print(message)
    else:
        if TR_specs['debug']:
            print(message)
        try:
            from tabulate import tabulate
            headers = list(result.dtype.names)
            out_table = tabulate(result, headers, tablefmt="fancy_grid")
            print(out_table)
        except ImportError:
            print("Outgoing Particles: ")
            print(result)

Functions

def initialize_parser()

Helper function to parse command-line arguments

Expand source code
def initialize_parser(): # pragma: no cover
    r'''
    Helper function to parse command-line arguments
    '''
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('-s',
                        dest='seed',
                        type=int, 
                        default=None,
                        help='just an integer seed to help with output file names'
                       )
    parser.add_argument('-n', 
                        dest='nevents', 
                        type=int, 
                        default=0,
                        help='how many events do you want?'
                       )
    parser.add_argument('-flavor', 
                        dest='flavor', 
                        type=int, default=16,
                        help='neutrino flavor (default is nutau): 12 for nue 14 for numu 16 for nutau'
                       )
    parser.add_argument('--track',
                        dest='track',
                        type=str,
                        default='Chord',
                        help='Track type to use. curently only radial and chord trajectories supported.'
                       )
    # Energy arguments
    parser.add_argument('-e', 
                        dest='energy',
                        default='',
                        help='Energy in GeV if numerical value greater than 0 passed.\n\
                              Sprectral index if numerical value less than or equal to 0 passed.\n\
                              Else path to CDF to sample from.'
                       )
    parser.add_argument('--e_min',
                        type=float,
                        default=1e6,
                        help='Minimum energy to sample from if doing powerlaw sampling'
                       )
    parser.add_argument('--e_max',
                        type=float,
                        default=1e9,
                        help='Minimum energy to sample from if doing powerlaw sampling'
                       )
    
    # Angular arguments
    parser.add_argument('-t', 
                        dest='theta',
                        default='',
                        help='nadir angle in degrees if numerical value(0 is through the core).\n\
                              "range" if you want to sample from a range of thetas. Use --th_min and --th_max to specify lims.'
                       )
    parser.add_argument('--th_max',
                        dest='th_max',
                        type=float, 
                        default=90,
                        help='If doing a theta range, maximum theta value. Default 90, i.e. skimming'
                       )
    parser.add_argument('--th_min', 
                        type=float,
                        default=0,
                        help='If doing a theta range, minimum theta value. Default 0, i.e. through the core'
                       )
    
    # Saving arguments
    parser.add_argument('--save', 
                        dest='save', 
                        type=str, 
                        default='', 
                        help="If saving output, provide a path here, if not specified, output will be printed"
                       )

    # cross section args
    parser.add_argument('--xs',
                        dest='xs_model',
                        type=str,
                        default='dipole',
                        help="Enter 'CSMS' if you would like to run the simulation with a pQCD xs model"
                       )

    # Body specification args
    parser.add_argument('--body',
                        dest='body',
                        default='earth',
                        help="body to use"
                       )
    parser.add_argument('--water',
                        dest='water',
                        type=float, default=0,
                        help="If you you would like to add a water layer to the Earth model, enter it here in km."
                       )
    parser.add_argument('--radius',
                        dest='radius',
                        type=float,
                        default=0,
                        help="Raise this flag if you want to turn off tau losses. In this case, taus will decay at rest."
                       )
    parser.add_argument('--depth',
                        dest='depth',
                        type=float,
                        default=0,
                        help="Depth of the detector in km."
                       )
    
    # Options
    parser.add_argument('--no_losses',
                        dest='no_losses', 
                        default=False,
                        action='store_true',
                        help="Raise this flag if you want to turn off tau losses. In this case, taus will decay at rest."
                       )
    parser.add_argument('--no_secondaries',
                        default=False,
                        action='store_true',
                        help="Raise this flag to turn off secondaries"
                       )
    parser.add_argument('-d', 
                        dest='debug', 
                        default=False, 
                        action='store_true', 
                        help='Do you want to print out debug statments? If so, raise this flag'
                       ) 
    parser.add_argument('--e_cut',
                        dest='e_cut',
                        default=0.0,
                        type=float,
                        help='Energy at which to stop the propagation.'
                       )

    args = parser.parse_args()
    return args
def run_MC(eini: numpy.ndarray, thetas: numpy.ndarray, body: Body, xs: XSModel, tracks: dict, propagator: proposal.Propagator, rand: mtrand.RandomState = None, no_secondaries: bool = False, flavor: int = 16, no_losses: bool = False, condition=None) ‑> numpy.ndarray

Main simulation code. Propagates an ensemble of initial states and returns the output Params


eini : array containing the initial energies of particles to simulate thetas : array containing the incoming angles of particles to simulate body : taurunner Body object in which to propagate the particles xs : taurunner CrossSections object for interactions tracks : dictionary whose keys are angles and whose values are taurunner Track objects propagator : PROPOSAL propagator object for charged lepton propagation Returns


output : Array containing the output information of the MC. This includes initial and final energies, incident incoming angles, number of CC and NC interactions, and particle type (PDG convention)

Expand source code
def run_MC(eini: np.ndarray, 
           thetas: np.ndarray,
           body: Body, 
           xs: CrossSections, 
           tracks: dict, 
           propagator: pp.Propagator,
           rand: np.random.RandomState = None,
           no_secondaries: bool = False,
           flavor: int = 16,
           no_losses: bool = False,
           condition=None
          ) -> np.ndarray:
    r'''
    Main simulation code. Propagates an ensemble of initial states and returns the output
    Params
    ______
    eini       : array containing the initial energies of particles to simulate
    thetas     : array containing the incoming angles of particles to simulate
    body       : taurunner Body object in which to propagate the particles
    xs         : taurunner CrossSections object for interactions
    tracks     : dictionary whose keys are angles and whose values are taurunner Track objects
    propagator : PROPOSAL propagator object for charged lepton propagation
    Returns
    _______
    output : Array containing the output information of the MC. This includes 
             initial and final energies, incident incoming angles, number of CC and NC interactions,
             and particle type (PDG convention)
             
    '''
    nevents     = len(eini)
    output      = []
    energies    = list(eini)
    particleIDs = np.ones(nevents, dtype=int)*flavor
    xs_model    = xs.model

    if rand is None:
        rand = np.random.RandomState()
    secondary_basket = []
    idxx             = []

    # Run the algorithm
    # All neutrinos are propagated until exiting as tau neutrino or taus.
    # If secondaries are on, then each event has a corresponding secondaries basket
    # which are propagated all at once in the end.
    for i in range(nevents):
        particle = Particle(particleIDs[i], energies[i], thetas[i], 0.0, rand, xs,
                            propagator, not no_secondaries, no_losses)
        
        my_track = tracks[thetas[i]]
        out      = Propagate(particle, my_track, body, condition=condition)
    
        if (out.survived==False):
            #these muons/electrons were absorbed. we record them in the output with outgoing energy 0
            output.append((energies[i], 0., thetas[i], out.nCC, out.nNC, out.ID, i, out.position))
        else:
            output.append((energies[i], float(out.energy), thetas[i], out.nCC, out.nNC, out.ID, i, out.position))
        if not no_secondaries:
            secondary_basket.append(np.asarray(out.basket))
            idxx = np.hstack([idxx, [i for _ in out.basket]])
        del out
        del particle
    idxx = np.asarray(idxx).astype(np.int32)
    if not no_secondaries:    
        #make muon propagator
        secondary_basket = np.concatenate(secondary_basket)
        sec_prop         = {ID:make_propagator(ID, body, xs_model) for ID in [-12, -14]}

        for sec, i in zip(secondary_basket, idxx):
            my_track = tracks[thetas[i]]
            sec_particle = Particle(
                                    sec['ID'], 
                                    sec['energy'],
                                    thetas[i], 
                                    sec['position'], 
                                    rand,
                                    xs=xs, 
                                    proposal_propagator=sec_prop[sec['ID']], 
                                    secondaries=False, 
                                    no_losses=False
                                   )
            sec_out = Propagate(sec_particle, my_track, body, condition=condition)
            if(not sec_out.survived):
                output.append((sec_out.initial_energy, 0.0, thetas[i], sec_out.nCC, sec_out.nNC, sec_out.ID, i, sec_out.position))
            else:
                output.append((sec_out.initial_energy, sec_out.energy, thetas[i], 
                                                 sec_out.nCC, sec_out.nNC, sec_out.ID, i, sec_out.position))
            del sec_particle
            del sec_out     
        
    output = np.array(output, dtype = [('Eini', float), ('Eout',float), ('Theta', float), ('nCC', int), ('nNC', int), ('PDG_Encoding', int), ('primary_tau', int), ('final_position', float)])
    output['Theta'] = np.degrees(output['Theta'])
                       
    return output