Esempio n. 1
0
    def __init__(self,
                 costh,
                 pmodel=(pm.HillasGaisser2012, 'H3a'),
                 hadr='SIBYLL2.3c',
                 barr_mods=(),
                 depth=1950 * Units.m,
                 density=('CORSIKA', ('SouthPole', 'June'))):
        """Initializes the nuVeto object for a particular costheta, CR Flux,
        hadronic model, barr parameters, and depth

        Note:
            A separate MCEq instance needs to be created for each
            combination of __init__'s arguments. To access pmodel and hadr,
            use mceq.pm_params and mceq.yields_params
        Args:
            costh (float): Cos(theta), the cosine of the neutrino zenith at the detector
            pmodel (tuple(CR model class, arguments)): CR Flux
            hadr (str): hadronic interaction model
            barr_mods: barr parameters
            depth (float): the depth at which the veto probability is computed below the ice
        """
        self.costh = costh
        self.pmodel = pmodel
        self.geom = Geometry(depth)
        theta = np.degrees(np.arccos(self.geom.cos_theta_eff(self.costh)))

        MCEq.core.dbg = 0
        MCEq.kernels.dbg = 0
        MCEq.density_profiles.dbg = 0
        MCEq.data.dbg = 0
        self.mceq = MCEqRun(
            # provide the string of the interaction model
            interaction_model=hadr,
            # atmospheric density model
            density_model=density,
            # primary cosmic ray flux model
            # support a tuple (primary model class (not instance!), arguments)
            primary_model=pmodel,
            # zenith angle \theta in degrees, measured positively from vertical direction
            theta_deg=theta,
            enable_muon_energy_loss=False,
            **mceq_config_without(['enable_muon_energy_loss',
                                   'density_model']))

        for barr_mod in barr_mods:
            # Modify proton-air -> mod[0]
            self.mceq.set_mod_pprod(2212, BARR[barr_mod[0]].pdg, barr_unc,
                                    barr_mod)
        # Populate the modifications to the matrices by re-filling the interaction matrix
        self.mceq._init_default_matrices(skip_D_matrix=True)

        X_vec = np.logspace(np.log10(2e-3),
                            np.log10(self.mceq.density_model.max_X), 12)
        self.dX_vec = np.diff(X_vec)
        self.X_vec = 10**centers(np.log10(X_vec))
Esempio n. 2
0
def plot_prpl(interp_pkl, include_mean=False, include_cbar=True):
    depth = 1950 * Units.m
    prplfn = pickle.load(open(interp_pkl, 'rb'), encoding='latin1')
    emui_edges = np.logspace(2, 8, 101)
    l_ice_edges = np.linspace(1e3, 4e4, 101)
    emui = centers(emui_edges)
    l_ice = centers(l_ice_edges)
    xx, yy = np.meshgrid(emui, l_ice)
    prpls = prplfn(list(zip(xx.flatten(), yy.flatten())))
    plt.figure()
    plt.pcolormesh(emui_edges,
                   l_ice_edges / 1e3,
                   prpls.reshape(xx.shape),
                   cmap='magma')
    if include_cbar:
        plt.colorbar()
    if include_mean:
        small_ice = l_ice[l_ice < 2.7e4]
        plt.plot(
            extsv.minimum_muon_energy(small_ice),
            small_ice / 1e3,
            'w--',
            label=
            r'$l_{\rm ice,\,median} (E_\mu^{\rm i}, E_\mu^{\rm th} = 1\,{\rm TeV})$'
        )
        leg = plt.legend(frameon=False,
                         prop={'weight': 'bold'},
                         loc='upper left')
        for text in leg.get_texts():
            plt.setp(text, color='w', fontsize='medium')
    plt.xlabel(r'$E_\mu^{\rm i}$ [GeV]')
    plt.ylabel(r'$l_{\rm ice}$ [km]')
    plt.locator_params(axis='y', nbins=8)
    # # plt.yscale('log')
    # # plt.gca().yaxis.set_major_formatter(ScalarFormatter())
    # plt.ticklabel_format(style='plain', axis='y')
    plt.gca().minorticks_off()
    plt.ylim(depth / Units.km, 40)
    # right y-axis with angles
    axr = plt.gca().twinx()
    axr.grid(False)
    geom = Geometry(depth)
    costhetas = geom.overburden_to_cos_theta(np.arange(10, 41, 10) * Units.km)
    axr.set_ylim(depth / Units.km, 40)
    axr.set_yticks(geom.overburden(costhetas) / 1e3)
    axr.set_yticklabels(np.round(costhetas, 2))
    axr.set_ylabel(r'$\cos \theta_z$', rotation=-90)
    axr.set_xscale('log')
    axr.set_xlim(1e2, 1e8)
    axr.minorticks_off()
    xlocmaj = LogLocator(base=10, numticks=12)
    axr.get_xaxis().set_major_locator(xlocmaj)
    return emui_edges, l_ice_edges, prpls.reshape(xx.shape)
Esempio n. 3
0
def test_overburden():
    geom = Geometry(1950 * Units.m)
    cosths = np.linspace(-1, 1, 100)
    assert np.all(np.diff(geom.overburden(cosths)) < 0)

    center = Geometry(geom.r_E)
    assert np.all(center.overburden(cosths) == geom.r_E / Units.m)
Esempio n. 4
0
def test_costh_effective():
    geom = Geometry(1950 * Units.m)
    cosths = np.linspace(-1, 1, 100)
    assert np.all(geom.cos_theta_eff(cosths) >= cosths)

    center = Geometry(geom.r_E)
    assert np.all(center.cos_theta_eff(cosths) == np.ones(100))
Esempio n. 5
0
class nuVeto(object):
    """Class for computing the neutrino passing fraction i.e. (1-(Veto probability))"""
    def __init__(self,
                 costh,
                 pmodel=(pm.HillasGaisser2012, 'H3a'),
                 hadr='SIBYLL2.3c',
                 barr_mods=(),
                 depth=1950 * Units.m,
                 density=('CORSIKA', ('SouthPole', 'June'))):
        """Initializes the nuVeto object for a particular costheta, CR Flux,
        hadronic model, barr parameters, and depth

        Note:
            A separate MCEq instance needs to be created for each
            combination of __init__'s arguments. To access pmodel and hadr,
            use mceq.pm_params and mceq.yields_params
        Args:
            costh (float): Cos(theta), the cosine of the neutrino zenith at the detector
            pmodel (tuple(CR model class, arguments)): CR Flux
            hadr (str): hadronic interaction model
            barr_mods: barr parameters
            depth (float): the depth at which the veto probability is computed below the ice
        """
        self.costh = costh
        self.pmodel = pmodel
        self.geom = Geometry(depth)
        theta = np.degrees(np.arccos(self.geom.cos_theta_eff(self.costh)))

        MCEq.core.dbg = 0
        MCEq.kernels.dbg = 0
        MCEq.density_profiles.dbg = 0
        MCEq.data.dbg = 0
        self.mceq = MCEqRun(
            # provide the string of the interaction model
            interaction_model=hadr,
            # atmospheric density model
            density_model=density,
            # primary cosmic ray flux model
            # support a tuple (primary model class (not instance!), arguments)
            primary_model=pmodel,
            # zenith angle \theta in degrees, measured positively from vertical direction
            theta_deg=theta,
            enable_muon_energy_loss=False,
            **mceq_config_without(['enable_muon_energy_loss',
                                   'density_model']))

        for barr_mod in barr_mods:
            # Modify proton-air -> mod[0]
            self.mceq.set_mod_pprod(2212, BARR[barr_mod[0]].pdg, barr_unc,
                                    barr_mod)
        # Populate the modifications to the matrices by re-filling the interaction matrix
        self.mceq._init_default_matrices(skip_D_matrix=True)

        X_vec = np.logspace(np.log10(2e-3),
                            np.log10(self.mceq.density_model.max_X), 12)
        self.dX_vec = np.diff(X_vec)
        self.X_vec = 10**centers(np.log10(X_vec))

    @staticmethod
    def categ_to_mothers(categ, daughter):
        """Get the parents for this category"""
        rcharge = '-' if 'anti' in daughter else '+'
        lcharge = '+' if 'anti' in daughter else '-'
        rbar = '-bar' if 'anti' in daughter else ''
        #lbar = '' if 'anti' in daughter else '-bar'
        if categ == 'conv':
            mothers = ['pi' + rcharge, 'K' + rcharge, 'K0L']
            if 'nutau' in daughter:
                mothers = []
            elif 'nue' in daughter:
                mothers.extend(['K0S', 'mu' + rcharge])
            elif 'numu' in daughter:
                mothers.extend(['mu' + lcharge])
        elif categ == 'pr':
            if 'nutau' in daughter:
                mothers = ['D' + rcharge, 'Ds' + rcharge]
            else:
                mothers = ['D' + rcharge, 'Ds' + rcharge,
                           'D0' + rbar]  #, 'Lambda0'+lbar]#, 'LambdaC+'+bar]
        elif categ == 'total':
            mothers = nuVeto.categ_to_mothers(
                'conv', daughter) + nuVeto.categ_to_mothers('pr', daughter)
        else:
            mothers = [
                categ,
            ]
        return mothers

    @staticmethod
    def esamp(enu, accuracy):
        """ returns the sampling of parent energies for a given enu
        """
        # TODO: replace 1e8 with MMC-prpl interpolated bounds
        return np.logspace(np.log10(enu), np.log10(enu + 1e8), 1000 * accuracy)

    @staticmethod
    def projectiles():
        """Get allowed pimaries"""
        pdg_ids = config['adv_set']['allowed_projectiles']
        namer = ParticleProperties.modtab.pdg2modname
        allowed = []
        for pdg_id in pdg_ids:
            allowed.append(namer[pdg_id])
            try:
                allowed.append(namer[-pdg_id])
            except KeyError:
                continue
        return allowed

    @staticmethod
    def nbody(fpath, esamp, enu, fn, l_ice):
        with np.load(fpath) as dfile:
            xmus = centers(dfile['xedges'])
            xnus = np.concatenate([xmus, [1]])
            vals = np.nan_to_num(dfile['histograms'])

            ddec = interpolate.RegularGridInterpolator((xnus, xmus),
                                                       vals,
                                                       bounds_error=False,
                                                       fill_value=None)
            emu_mat = xmus[:, None] * esamp[None, :] * Units.GeV
            pmu_mat = ddec(np.stack(np.meshgrid(enu / esamp, xmus), axis=-1))
            reaching = 1 - np.sum(pmu_mat * fn.prpl(
                np.stack([emu_mat, np.ones(emu_mat.shape) * l_ice], axis=-1)),
                                  axis=0)
            reaching[reaching < 0.] = 0.
            return reaching

    @staticmethod
    @lru_cache(2**12)
    def psib(l_ice, mother, enu, accuracy, prpl):
        """ returns the suppression factor due to the sibling muon
        """
        esamp = nuVeto.esamp(enu, accuracy)
        fn = MuonProb(prpl)
        if mother in ['D0', 'D0-bar']:
            reaching = nuVeto.nbody(
                resource_filename('nuVeto',
                                  'data/decay_distributions/D0_numu.npz'),
                esamp, enu, fn, l_ice)
        elif mother in ['D+', 'D-']:
            reaching = nuVeto.nbody(
                resource_filename('nuVeto',
                                  'data/decay_distributions/D+_numu.npz'),
                esamp, enu, fn, l_ice)
        elif mother in ['Ds+', 'Ds-']:
            reaching = nuVeto.nbody(
                resource_filename('nuVeto',
                                  'data/decay_distributions/Ds_numu.npz'),
                esamp, enu, fn, l_ice)
        elif mother == 'K0L':
            reaching = nuVeto.nbody(
                resource_filename('nuVeto',
                                  'data/decay_distributions/K0L_numu.npz'),
                esamp, enu, fn, l_ice)
        else:
            # Assuming muon energy is E_parent - E_nu
            reaching = 1. - fn.prpl(
                zip((esamp - enu) * Units.GeV, [l_ice] * len(esamp)))
        return reaching

    @lru_cache(maxsize=2**12)
    def get_dNdEE(self, mother, daughter):
        """Differential parent-->neutrino (mother--daughter) yield"""
        ihijo = 20
        e_grid = self.mceq.e_grid
        delta = self.mceq.e_widths
        x_range = e_grid[ihijo] / e_grid
        rr = ParticleProperties.rr(mother, daughter)
        dNdEE_edge = ParticleProperties.br_2body(mother, daughter) / (1 - rr)
        dN_mat = self.mceq.decays.get_d_matrix(
            ParticleProperties.pdg_id[mother],
            ParticleProperties.pdg_id[daughter])
        dNdEE = dN_mat[ihijo] * e_grid / delta
        logx = np.log10(x_range)
        logx_width = -np.diff(logx)[0]
        good = (logx + logx_width / 2 < np.log10(1 - rr)) & (x_range >= 5.e-2)

        x_low = x_range[x_range < 5e-2]
        dNdEE_low = np.array([dNdEE[good][-1]] * x_low.size)
        dNdEE_interp = lambda x_: interpolate.pchip(
            np.concatenate([[1 - rr], x_range[good], x_low])[::-1],
            np.concatenate([[dNdEE_edge], dNdEE[good], dNdEE_low])[::-1],
            extrapolate=True)(x_) * np.heaviside(1 - rr - x_, 1)
        return x_range, dNdEE, dNdEE_interp

    @lru_cache(maxsize=2**12)
    def grid_sol(self, ecr=None, particle=None):
        """MCEq grid solution for \\frac{dN_{CR,p}}_{dE_p}"""
        if ecr is not None:
            self.mceq.set_single_primary_particle(ecr, particle)
        else:
            self.mceq.set_primary_model(*self.pmodel)
        self.mceq.solve(int_grid=self.X_vec, grid_var="X")
        return self.mceq.grid_sol

    @lru_cache(maxsize=2**12)
    def nmu(self, ecr, particle, prpl='ice_allm97_step_1'):
        """Poisson probability of getting no muons"""
        grid_sol = self.grid_sol(ecr, particle)
        l_ice = self.geom.overburden(self.costh)
        mu = self.get_solution('mu-', grid_sol) + self.get_solution(
            'mu+', grid_sol)

        fn = MuonProb(prpl)
        coords = zip(self.mceq.e_grid * Units.GeV,
                     [l_ice] * len(self.mceq.e_grid))
        return np.trapz(mu * fn.prpl(coords), self.mceq.e_grid)

    @lru_cache(maxsize=2**12)
    def get_rescale_phi(self, mother, ecr=None, particle=None):
        """Flux of the mother at all heights"""
        grid_sol = self.grid_sol(
            ecr, particle
        )  # MCEq solution (fluxes tabulated as a function of height)
        dX = self.dX_vec * Units.gr / Units.cm**2
        rho = self.mceq.density_model.X2rho(
            self.X_vec) * Units.gr / Units.cm**3
        inv_decay_length_array = (
            ParticleProperties.mass_dict[mother] /
            (self.mceq.e_grid[:, None] * Units.GeV)) / (
                ParticleProperties.lifetime_dict[mother] * rho[None, :])
        rescale_phi = dX[None, :] * inv_decay_length_array * self.get_solution(
            mother, grid_sol, grid_idx=False).T
        return rescale_phi

    def get_integrand(self,
                      categ,
                      daughter,
                      enu,
                      accuracy,
                      prpl,
                      ecr=None,
                      particle=None):
        """flux*yield"""
        esamp = self.esamp(enu, accuracy)
        mothers = self.categ_to_mothers(categ, daughter)
        nums = np.zeros((len(esamp), len(self.X_vec)))
        dens = np.zeros((len(esamp), len(self.X_vec)))
        for mother in mothers:
            dNdEE = self.get_dNdEE(mother, daughter)[-1]
            rescale_phi = self.get_rescale_phi(mother, ecr, particle)
            # DEBUG
            # from matplotlib import pyplot as plt
            # plt.plot(np.log(self.mceq.e_grid[rescale_phi[:,0]>0]),
            #          np.log(rescale_phi[:,0][rescale_phi[:,0]>0]))
            # rescale_phi = np.array([interpolate.interp1d(self.mceq.e_grid, rescale_phi[:,i], kind='quadratic', bounds_error=False, fill_value=0)(esamp) for i in xrange(rescale_phi.shape[1])]).T
            ###
            # TODO: optimize to only run when esamp[0] is non-zero
            rescale_phi = np.exp(
                np.array([
                    interpolate.interp1d(
                        np.log(self.mceq.e_grid[rescale_phi[:, i] > 0]),
                        np.log(rescale_phi[:, i][rescale_phi[:, i] > 0]),
                        kind='quadratic',
                        bounds_error=False,
                        fill_value=-np.inf)(np.log(esamp))
                    for i in xrange(rescale_phi.shape[1])
                ])).T
            # DEBUG
            # print rescale_phi.min(), rescale_phi.max()
            # print np.log(esamp)
            # plt.plot(np.log(esamp),
            #          np.log(rescale_phi[:,0]), label='intp')
            # plt.legend()
            # import pdb
            # pdb.set_trace()
            ###
            if 'numu' in daughter:
                # muon accompanies numu only
                pnmsib = self.psib(self.geom.overburden(self.costh), mother,
                                   enu, accuracy, prpl)
            else:
                pnmsib = np.ones(len(esamp))
            dnde = dNdEE(enu / esamp) / esamp
            nums += (dnde * pnmsib)[:, None] * rescale_phi
            dens += (dnde)[:, None] * rescale_phi

        return nums, dens

    def get_solution(self, particle_name, grid_sol, mag=0., grid_idx=None):
        """Retrieves solution of the calculation on the energy grid.

        Args:
          particle_name (str): The name of the particle such, e.g.
            ``total_mu+`` for the total flux spectrum of positive muons or
            ``pr_antinumu`` for the flux spectrum of prompt anti muon neutrinos
          mag (float, optional): 'magnification factor': the solution is
            multiplied by ``sol`` :math:`= \\Phi \\cdot E^{mag}`
          grid_idx (int, optional): if the integrator has been configured to save
            intermediate solutions on a depth grid, then ``grid_idx`` specifies
            the index of the depth grid for which the solution is retrieved. If
            not specified the flux at the surface is returned
          integrate (bool, optional): return averge particle number instead of
          flux (multiply by bin width)

        Returns:
          (numpy.array): flux of particles on energy grid :attr:`e_grid`
        """

        # MCEq index conversion
        ref = self.mceq.pname2pref
        p_pdg = ParticleProperties.pdg_id[particle_name]
        reduce_res = True

        if grid_idx is None:  # Surface only case
            sol = np.array([grid_sol[-1]])
            xv = np.array([self.X_vec[-1]])
        elif isinstance(grid_idx,
                        bool) and not grid_idx:  # Whole solution case
            sol = np.asarray(grid_sol)
            xv = np.asarray(self.X_vec)
            reduce_res = False
        elif grid_idx >= len(self.mceq.grid_sol):  # Surface only case
            sol = np.array([grid_sol[-1]])
            xv = np.array([self.X_vec[-1]])
        else:  # Particular height case
            sol = np.array([grid_sol[grid_idx]])
            xv = np.array([self.X_vec[grid_idx]])

        # MCEq solution for particle
        direct = sol[:, ref[particle_name].lidx():ref[particle_name].uidx()]
        res = np.zeros(direct.shape)
        rho_air = 1. / self.mceq.density_model.r_X2rho(xv)

        # meson decay length
        decayl = ((self.mceq.e_grid * Units.GeV) /
                  ParticleProperties.mass_dict[particle_name] *
                  ParticleProperties.lifetime_dict[particle_name] / Units.cm)

        # number of targets per cm2
        ndens = rho_air * Units.Na / Units.mol_air
        for prim in self.projectiles():
            prim_flux = sol[:, ref[prim].lidx():ref[prim].uidx()]
            prim_xs = self.mceq.cs.get_cs(ParticleProperties.pdg_id[prim])
            try:
                int_yields = self.mceq.y.get_y_matrix(
                    ParticleProperties.pdg_id[prim], p_pdg)
                res += np.sum(int_yields[None, :, :] * prim_flux[:, None, :] *
                              prim_xs[None, None, :] * ndens[:, None, None],
                              axis=2)
            except KeyError as e:
                continue

        res *= decayl[None, :]
        # combine with direct
        res[direct != 0] = direct[direct != 0]

        if particle_name[:-1] == 'mu':
            for _ in [
                    'k_' + particle_name, 'pi_' + particle_name,
                    'pr_' + particle_name
            ]:
                res += sol[:, ref[_].lidx():ref[_].uidx()]

        res *= self.mceq.e_grid[None, :]**mag

        if reduce_res:
            res = res[0]
        return res

    def get_fluxes(self,
                   enu,
                   kind='conv_numu',
                   accuracy=3.5,
                   prpl='ice_allm97_step_1',
                   corr_only=False):
        """Returns the flux and passing fraction
        for a particular neutrino energy, flux, and p_light
        """
        # prpl = probability of reaching * probability of light
        # prpl -> None ==> median for muon reaching
        categ, daughter = kind.split('_')

        esamp = self.esamp(enu, accuracy)

        # Correlated only (no need for the unified calculation here) [really just for testing]
        passed = 0
        total = 0
        if corr_only:
            # sum performs the dX integral
            nums, dens = self.get_integrand(categ, daughter, enu, accuracy,
                                            prpl)
            num = np.sum(nums, axis=1)
            den = np.sum(dens, axis=1)
            passed = integrate.trapz(num, esamp)
            total = integrate.trapz(den, esamp)
            return passed, total

        pmodel = self.pmodel[0](self.pmodel[1])

        #loop over primary particles
        for particle in pmodel.nucleus_ids:
            # A continuous input energy range is allowed between
            # :math:`50*A~ \\text{GeV} < E_\\text{nucleus} < 10^{10}*A \\text{GeV}`.

            # ecrs --> Energy of cosmic ray primaries
            # amu --> atomic mass of primary

            # evaluation points in E_CR
            ecrs = amu(particle) * np.logspace(2, 10, 10 * accuracy)

            # pnm --> probability of no muon (just a poisson probability)
            nmu = [self.nmu(ecr, particle, prpl) for ecr in ecrs]

            # nmufn --> fine grid interpolation of pnm
            nmufn = interpolate.interp1d(ecrs,
                                         nmu,
                                         kind='linear',
                                         assume_sorted=True,
                                         bounds_error=False,
                                         fill_value=(0, np.nan))
            # nums --> numerator
            nums = []
            # dens --> denominator
            dens = []
            # istart --> integration starting point, the lowest energy index for the integral
            istart = max(0, np.argmax(ecrs > enu) - 1)
            for ecr in ecrs[istart:]:  # integral in primary energy (E_CR)
                # cr_flux --> cosmic ray flux
                # phim2 --> units of flux * m^2 (look it up in the units)
                cr_flux = pmodel.nucleus_flux(particle,
                                              ecr.item()) * Units.phim2
                # poisson exp(-Nmu) [last term in eq 12]
                pnmarr = np.exp(-nmufn(ecr - esamp))

                num_ecr = 0  # single entry in nums
                den_ecr = 0  # single entry in dens

                # dEp
                # integral in Ep
                nums_ecr, dens_ecr = self.get_integrand(
                    categ, daughter, enu, accuracy, prpl, ecr, particle)
                num_ecr = integrate.trapz(
                    np.sum(nums_ecr, axis=1) * pnmarr, esamp)
                den_ecr = integrate.trapz(np.sum(dens_ecr, axis=1), esamp)

                nums.append(num_ecr * cr_flux / Units.phicm2)
                dens.append(den_ecr * cr_flux / Units.phicm2)
            # dEcr
            passed += integrate.trapz(nums, ecrs[istart:])
            total += integrate.trapz(dens, ecrs[istart:])

        return passed, total