aboutsummaryrefslogtreecommitdiff
path: root/utils/plot-atmospheric-fluxes
blob: 042981c8bb36fd738ab5ba3019b52a40178b52f7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#!/usr/bin/env python
"""
Script for plotting solar fluxes from
http://www-pnp.physics.ox.ac.uk/~barr/fluxfiles/0403i/index.html.
"""
from __future__ import print_function, division
import numpy as np
import os
from sddm.plot import despine
from sddm import splitext

# Data is in the form: Fluxes in bins of neutrino energy (equally spaced bins
# in logE with 10 bins per decade with the low edge of the first bin at 100
# MeV) and zenith angle (20 bins equally spaced in cos(zenith) with bin width
# 0.1), integrated over azimuth. Note logE means log to base e. Fluxes below 10
# GeV are from the 3D calculation.

# The files all have the same format. After the initial comment lines (starting
# with a # character), the files contain one line per bin. No smoothoing
# between bins has been done. The columns are:
# 
#     1. Energy at centre of bin in GeV
#     2. Zenith files: Cos(zenith) at centre of bin
#        Azimuth files: Azimuth at centre of bin (degrees)
#     3. Flux in dN/dlogE in /m**2/steradian/sec
#     4. MC Statistical error on the flux
#     5. Number of unweighted events entering the bin (not too useful)

if __name__ == '__main__':
    import argparse
    import matplotlib
    import glob
    from sddm import setup_matplotlib

    parser = argparse.ArgumentParser("plot solar fluxes")
    parser.add_argument("filenames", nargs='+', help="filenames of flux files")
    parser.add_argument("--save", action="store_true", default=False, help="save plots")
    args = parser.parse_args()

    setup_matplotlib(args.save)

    import matplotlib.pyplot as plt

    fig = plt.figure()

    colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
    linestyles = ['-','--']

    def key(filename):
        head, tail = os.path.split(filename)

        k = 0
        if tail.startswith('fmax'):
            k += 1
        if 'nue' in tail:
            k += 10
        elif 'nbe' in tail:
            k += 20
        elif 'num' in tail:
            k += 30
        elif 'nbm' in tail:
            k += 40
        elif 'nut' in tail:
            k += 50
        elif 'nbt' in tail:
            k += 60

        return k

    for filename in sorted(args.filenames,key=key):
        head, tail = os.path.split(filename)

        print(filename)

        data = np.genfromtxt(filename)

        shape1 = len(np.unique(data[:,0]))

        x = data[:,0].reshape((-1,shape1))
        y = data[:,1].reshape((-1,shape1))
        z = data[:,2].reshape((-1,shape1))

        # Convert to MeV
        x *= 1000.0
        z /= 1000.0

        zbins = np.linspace(-1,1,21)
        dz = zbins[1] - zbins[0]

        x = x[0]
        # Integrate over cos(theta) and multiply by 2*pi to convert 3D flux to
        # a total flux
        y = np.sum(z*dz,axis=0)*2*np.pi

        if 'sno_nue' in tail:
            plt.plot(x,y,color=colors[0],linestyle=linestyles[0],label=r'$\nu_e$')
        elif 'sno_nbe' in tail:
            plt.plot(x,y,color=colors[0],linestyle=linestyles[1],label=r'$\overline{\nu}_e$')
        elif 'sno_num' in tail:
            plt.plot(x,y,color=colors[1],linestyle=linestyles[0],label=r'$\nu_\mu$')
        elif 'sno_nbm' in tail:
            plt.plot(x,y,color=colors[1],linestyle=linestyles[1],label=r'$\overline{\nu}_\mu$')
        elif 'sno_nut' in tail:
            plt.plot(x,y,color=colors[2],linestyle=linestyles[0],label=r'$\nu_\tau$')
        elif 'sno_nbt' in tail:
            plt.plot(x,y,color=colors[2],linestyle=linestyles[1],label=r'$\overline{\nu}_\tau$')

    plt.gca().set_xscale("log")
    plt.gca().set_yscale("log")
    despine(fig,trim=True)
    plt.xlabel("$E$ (MeV)")
    plt.ylabel(r"$\mathrm{d}\Phi/\mathrm{d}E$ (1/$\mathrm{m}^2$/sec/MeV)")
    plt.legend()
    plt.tight_layout()

    if args.save:
        plt.savefig("irc01_atmospheric_flux.pdf")
        plt.savefig("irc01_atmospheric_flux.eps")

    plt.show()
in oscillation probabilities nue_osc_prob = np.genfromtxt(join(args.osc_dir,"nue_osc_prob.txt")) nbe_osc_prob = np.genfromtxt(join(args.osc_dir,"nbe_osc_prob.txt")) num_osc_prob = np.genfromtxt(join(args.osc_dir,"num_osc_prob.txt")) nbm_osc_prob = np.genfromtxt(join(args.osc_dir,"nbm_osc_prob.txt")) # Reshape oscillation probability arrays. They should now look like: # # [[[0.01, -1, P(nue), P(num), P(nut)], # [0.01, -0.8, P(nue), P(num), P(nut)], # ... # [0.01, 1.0, P(nue), P(num), P(nut)]], # [[0.02, -1, P(nue), P(num), P(nut)], # [0.02, -0.8, P(nue), P(num), P(nut)], # ... # [0.02, 1.0, P(nue), P(num), P(nut)]], # ... # [[10.0, -1, P(nue), P(num), P(nut)], # [10.0, -0.8, P(nue), P(num), P(nut)], # ... # [10.0, 1.0, P(nue), P(num), P(nut)]]] shape0 = len(np.unique(nue_osc_prob[:,0])) nue_osc_prob = nue_osc_prob.reshape((shape0,-1,5)) nbe_osc_prob = nbe_osc_prob.reshape((shape0,-1,5)) num_osc_prob = num_osc_prob.reshape((shape0,-1,5)) nbm_osc_prob = nbm_osc_prob.reshape((shape0,-1,5)) # convert dn/dlnE -> dn/dE nue_barr[:,2] /= nue_barr[:,0] nbe_barr[:,2] /= nbe_barr[:,0] num_barr[:,2] /= num_barr[:,0] nbm_barr[:,2] /= nbm_barr[:,0] # Reshape Barr flux arrays. They should now look like: # # [[[0.1, -1, flux,...], # [0.2, -1, flux,...], # ... # [10.0, -1, flux,...]], # # [[0.1, -0.9, flux,...], # [0.2, -0.9, flux,...], # ... # [10.0, -0.9, flux,...]], # ... # ... # [[0.1, 1.0, flux,...], # [0.2, 1.0, flux,...], # ... # [10.0, 1.0, flux,...]]] shape1 = len(np.unique(nue_barr[:,0])) nue_barr = nue_barr.reshape((-1,shape1,5)) nbe_barr = nbe_barr.reshape((-1,shape1,5)) num_barr = num_barr.reshape((-1,shape1,5)) nbm_barr = nbm_barr.reshape((-1,shape1,5)) nue_zenith_dist_x = nue_barr[:,0,1].copy() nue_zenith_dist_y = nue_barr[:,0,2].copy() nue_zenith_dist_y /= np.trapz(nue_zenith_dist_y,x=nue_zenith_dist_x) nbe_zenith_dist_x = nbe_barr[:,0,1].copy() nbe_zenith_dist_y = nbe_barr[:,0,2].copy() nbe_zenith_dist_y /= np.trapz(nbe_zenith_dist_y,x=nbe_zenith_dist_x) num_zenith_dist_x = num_barr[:,0,1].copy() num_zenith_dist_y = num_barr[:,0,2].copy() num_zenith_dist_y /= np.trapz(num_zenith_dist_y,x=num_zenith_dist_x) nbm_zenith_dist_x = nbm_barr[:,0,1].copy() nbm_zenith_dist_y = nbm_barr[:,0,2].copy() nbm_zenith_dist_y /= np.trapz(nbm_zenith_dist_y,x=nbm_zenith_dist_x) nue_battistoniz = np.empty((nue_barr.shape[0],nue_battistoni.shape[0],4)) nue_battistoniz[:,:,0] = nue_battistoni[:,0] nue_battistoniz[:,:,1] = nue_zenith_dist_x[:,np.newaxis] nue_battistoniz[:,:,2] = nue_battistoni[:,1]*nue_zenith_dist_y[:,np.newaxis]/(2*np.pi) nue_battistoniz[:,:,3] = nue_battistoniz[:,:,2]*nue_battistoniz[:,:,0] nbe_battistoniz = np.empty((nbe_barr.shape[0],nbe_battistoni.shape[0],4)) nbe_battistoniz[:,:,0] = nbe_battistoni[:,0] nbe_battistoniz[:,:,1] = nbe_zenith_dist_x[:,np.newaxis] nbe_battistoniz[:,:,2] = nbe_battistoni[:,1]*nbe_zenith_dist_y[:,np.newaxis]/(2*np.pi) nbe_battistoniz[:,:,3] = nbe_battistoniz[:,:,2]*nbe_battistoniz[:,:,0] num_battistoniz = np.empty((num_barr.shape[0],num_battistoni.shape[0],4)) num_battistoniz[:,:,0] = num_battistoni[:,0] num_battistoniz[:,:,1] = num_zenith_dist_x[:,np.newaxis] num_battistoniz[:,:,2] = num_battistoni[:,1]*num_zenith_dist_y[:,np.newaxis]/(2*np.pi) num_battistoniz[:,:,3] = num_battistoniz[:,:,2]*num_battistoniz[:,:,0] nbm_battistoniz = np.empty((nbm_barr.shape[0],nbm_battistoni.shape[0],4)) nbm_battistoniz[:,:,0] = nbm_battistoni[:,0] nbm_battistoniz[:,:,1] = nbm_zenith_dist_x[:,np.newaxis] nbm_battistoniz[:,:,2] = nbm_battistoni[:,1]*nbm_zenith_dist_y[:,np.newaxis]/(2*np.pi) nbm_battistoniz[:,:,3] = nbm_battistoniz[:,:,2]*nbm_battistoniz[:,:,0] nue_total = np.empty((nue_barr.shape[0],nue_barr.shape[1]+nue_battistoniz.shape[1],4)) nue_total[:,:nue_battistoniz.shape[1],:] = nue_battistoniz nue_total[:,nue_battistoniz.shape[1]:,:] = nue_barr[:,:,:4] nbe_total = np.empty((nbe_barr.shape[0],nbe_barr.shape[1]+nbe_battistoniz.shape[1],4)) nbe_total[:,:nbe_battistoniz.shape[1],:] = nbe_battistoniz nbe_total[:,nbe_battistoniz.shape[1]:,:] = nbe_barr[:,:,:4] num_total = np.empty((num_barr.shape[0],num_barr.shape[1]+num_battistoniz.shape[1],4)) num_total[:,:num_battistoniz.shape[1],:] = num_battistoniz num_total[:,num_battistoniz.shape[1]:,:] = num_barr[:,:,:4] nbm_total = np.empty((nbm_barr.shape[0],nbm_barr.shape[1]+nbm_battistoniz.shape[1],4)) nbm_total[:,:nbm_battistoniz.shape[1],:] = nbm_battistoniz nbm_total[:,nbm_battistoniz.shape[1]:,:] = nbm_barr[:,:,:4] flux_arrays = {12: nue_total, -12: nbe_total, 14: num_total, -14: nbm_total} # Save unoscillated fluxes for p in [12,-12,14,-14]: np.savetxt("fmax20_i0403z_plus_lowe.sno_%s" % pdg_code_to_string(p),flux_arrays[p].reshape((flux_arrays[p].shape[0]*flux_arrays[p].shape[1],-1)), fmt='%10.4f %10.3f %20.8g %20.8g', header="Energy (GeV), Cos(zenith), Flux (m^-2 s^-1 Gev^-1 steradian^-1), Flux (dn/dlnE)") osc_prob = {12: nue_osc_prob, -12: nbe_osc_prob, 14: num_osc_prob, -14: nbm_osc_prob} def get_flux_interp(nu_type): flux_array = flux_arrays[nu_type] flux = flux_array[:,:,2] e = flux_array[0,:,0] cos_theta = flux_array[:,0,1] # Use first order splines here since we need to enforce the fact that # the flux is not negative return RectBivariateSpline(e,cos_theta,flux.T,kx=1,ky=1) def get_osc_interp(nu_type1,nu_type2): prob = osc_prob[nu_type1] e = prob[:,0,0] cos_theta = prob[0,:,1] if np.sign(nu_type1) != np.sign(nu_type2): raise ValueError("asking for oscillation probability from %s -> %s!" % tuple(map(pdg_code_to_string,(nu_type1,nu_type2)))) # Use second order splines here since oscillation probabilities are # pretty smooth if abs(nu_type2) == 12: return RectBivariateSpline(e,cos_theta,prob[:,:,2],kx=2,ky=2) elif abs(nu_type2) == 14: return RectBivariateSpline(e,cos_theta,prob[:,:,3],kx=2,ky=2) elif abs(nu_type2) == 16: return RectBivariateSpline(e,cos_theta,prob[:,:,4],kx=2,ky=2) def get_oscillated_flux(nu_type1, nu_type2, elo, emid, ehi, zlo, zmid, zhi): """ Returns the average flux of neutrinos coming from `nu_type1` neutrinos oscillating to `nu_type2` neutrinos for neutrinos in the bin between energies elo and ehi and with a cosine of the zenith angle between zlo and zhi. Note: We also pass the midpoint of the bin for both energy and cos(theta) as `emid` and `zmid`. The reason for this is that at least for the Barr fluxes, the midpoint of the bin was chosen as the midpoint of the log of the left and right edge instead of the actual middle of the bin. Therefore to be consistent when interpolating these fluxes we should use the same value. The returned flux has units of 1/(m^2 sec steradian GeV). """ f_flux1 = get_flux_interp(nu_type1) f_osc1 = get_osc_interp(nu_type1,nu_type2) def f_flux(e,z): # Not sure exactly how scipy deals with boundary issues in # RectBivariateSpline so we just make sure everything is within the # range of the values given in the Barr and Battistoni fluxes if e < 0.0106: e = 0.0106 if z < -0.95: z = -0.95 if z > 0.95: z = 0.95 return f_flux1(e,z) def f_osc(e,z): # FIXME: Should remove this if e < 0.02: e = 0.02 return f_osc1(e,z) # Here we have two options for calculating the oscillated flux: # # 1. We interpolate the value of the flux and then multiply by an # average oscillation probability. This is more correct in the sense # that the original flux values are actually a histogram and so it # makes sense to just sample the flux at the middle of the bin. # # 2. We integrate the product of the flux and the oscillation # probability over the whole bin. This is technically more correct # since it will weight the oscillation probability by the flux, but it # may have issues near the edge of the first and last bin since we have # to extrapolate. # # For now, we do 1 since it is much faster. To do the second you can # just comment the next line. return f_flux(emid,zmid)*f_osc1.integral(elo,ehi,zlo, zhi)/(ehi-elo)/(zhi-zlo) return nquad(lambda e, z: f_flux(e,z)*f_osc(e,z), [(elo,ehi), (zlo, zhi)], opts={'limit':1000,'epsrel':1e-3})[0]/(ehi-elo)/(zhi-zlo) data = {nu_type: np.zeros((cos_theta_bins.shape[0]-1,energy_bins.shape[0]-1,4)) for nu_type in [12,-12,14,-14,16,-16]} for nu_type1 in [12,-12,14,-14]: if nu_type1 > 0: nu_type2s = [12,14,16] else: nu_type2s = [-12,-14,-16] for nu_type2 in nu_type2s: print("Calculating neutrino flux for %s -> %s" % tuple(map(pdg_code_to_string,(nu_type1,nu_type2)))) for i, (elo, ehi) in enumerate(zip(energy_bins[:-1],energy_bins[1:])): for j, (zlo, zhi) in enumerate(zip(cos_theta_bins[:-1],cos_theta_bins[1:])): print("\r%i/%i" % (i*(len(cos_theta_bins)-1)+j+1,(len(cos_theta_bins)-1)*(len(energy_bins)-1)),end="") sys.stdout.flush() # We'll follow the Barr convention here and take the midpoint in log space emid = np.exp((np.log(elo)+np.log(ehi))/2) zmid = (zlo + zhi)/2 data[nu_type2][j,i,0] = emid data[nu_type2][j,i,1] = zmid data[nu_type2][j,i,2] += get_oscillated_flux(nu_type1,nu_type2,elo,emid,ehi,zlo,zmid,zhi) print() for nu_type2 in data: for i, (elo, ehi) in enumerate(zip(energy_bins[:-1],energy_bins[1:])): for j, (zlo, zhi) in enumerate(zip(cos_theta_bins[:-1],cos_theta_bins[1:])): data[nu_type2][j,i,3] = data[nu_type2][j,i,2]*data[nu_type2][j,i,0] for p in [12,-12,14,-14,16,-16]: np.savetxt("fmax20_i0403z_plus_lowe.sno_%s.osc" % pdg_code_to_string(p),data[p].reshape((data[p].shape[0]*data[p].shape[1],-1)), fmt='%10.4f %10.3f %20.8g %20.8g', header="Energy (GeV), Cos(zenith), Flux (m^-2 s^-1 Gev^-1 steradian^-1), Flux (dn/dlnE), Junk")