Flavor Transformations

This notebook produces the figures in the SNEWPY paper showing the effect of the flavor transformation prescriptions upon the nakazato-shen-z0.004-t_rev100ms-s20.0 model

[1]:
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np

from astropy import units as u

from snewpy.neutrino import Flavor, MassHierarchy, MixingParameters
from snewpy.models.ccsn import Nakazato_2013
from snewpy.flavor_transformation import AdiabaticMSW, NonAdiabaticMSWH, \
                                         TwoFlavorDecoherence, ThreeFlavorDecoherence, \
                                         NeutrinoDecay, AdiabaticMSWes, NonAdiabaticMSWes

mpl.rc('font', size=18)
%matplotlib inline

# `np.trapz` was renamed in numpy 2.0 and now raises a DeprecationWarning
if np.__version__ < "2.0":
    np.trapezoid = np.trapz
[2]:
model = Nakazato_2013(progenitor_mass=20*u.solMass, revival_time=100*u.ms, metallicity=0.004, eos='shen')
model
[2]:

Nakazato_2013 Model: nakazato-shen-z0.004-t_rev100ms-s20.0.fits

Parameter

Value

Progenitor mass

\(20\) \(\mathrm{M_{\odot}}\)

Revival time

\(100\) \(\mathrm{ms}\)

Metallicity

0.004

EOS

shen

Transformed and untransformed energy integrated flux at Earth

Compute and plot the flux at Earth for a SN at 10 kpc with no flavor transformation, and with the chosen flavor transformation.

[3]:
def plot_total_flux(model, xform_nmo, xform_imo):
    """Plot initial and oscillated neutrino luminosities.

    Parameters
    ----------
    model : SupernovaModel
        An input model from a CCSN simulation.
    flav_xform : FlavorTransformation
        A FlavorTransformation subclass; used to create an instance.
    """

    energies = np.linspace(0,60,121) * u.MeV
    d = (10*u.kpc).to('cm').value # distance to SN

    times = model.get_time()
    burst_epoch = times <= 0.1*u.s
    accretion_epoch = (times > 0.1*u.s) & (times <= 0.5*u.s)
    cooling_epoch = (times > 0.5*u.s) & (times <= 10*u.s)

    ilum = {}
    olum_nmo = {}
    olum_imo = {}

    for flavor in Flavor:
        ilum[flavor] = np.zeros(len(times))
        olum_nmo[flavor] = np.zeros(len(times))
        olum_imo[flavor] = np.zeros(len(times))

    # Compute the transformed and untransformed flux at each time.
    for i, t in enumerate(times):
        ispec = model.get_initial_spectra(t, energies)
        ospec_nmo = model.get_transformed_spectra(t, energies, xform_nmo)
        ospec_imo = model.get_transformed_spectra(t, energies, xform_imo)

        for flavor in Flavor:
            for j, E in enumerate(energies):
                ispec[flavor][j] /= (4.*np.pi*d**2)
                ospec_nmo[flavor][j] /= (4.*np.pi*d**2)
                ospec_imo[flavor][j] /= (4.*np.pi*d**2)

        for flavor in Flavor:
            ilum[flavor][i] = np.trapezoid(ispec[flavor].to('1/(erg*s)'), energies.to('erg')).value
            olum_nmo[flavor][i] = np.trapezoid(ospec_nmo[flavor].to('1/(erg*s)'), energies.to('erg')).value
            olum_imo[flavor][i] = np.trapezoid(ospec_imo[flavor].to('1/(erg*s)'), energies.to('erg')).value

    # make the figures
    fig, axes = plt.subplots(3,3, figsize=(20,12), tight_layout=True)

    smax = [0.,0.,0.]
    titles = ['Untransformed', 'Transformed (NMO)', 'Transformed (IMO)']
    for i, spec in enumerate([ilum, olum_nmo, olum_imo]):
        for j, phase in enumerate([burst_epoch, accretion_epoch, cooling_epoch]):
            ax = axes[i,j]
            timeunits = 'ms' if j==0 else 's'

            for flavor in Flavor:
                if i == 0:
                    smax[j] = np.maximum(smax[j], 1.1*np.max(spec[flavor][phase]))

                ax.plot(times[phase].to(timeunits),
                        spec[flavor][phase], label=flavor.to_tex(), lw=3,
                        color='C0' if flavor.is_electron else 'C1',
                        ls='-' if flavor.is_neutrino else ':')

            ax.set(xlim=(times[phase][0].to(timeunits).value, times[phase][-1].to(timeunits).value),
                   ylim=(0, smax[j]))

            if j==0:
               ax.set(ylabel=r'flux [$10^{16}$ erg$^{-1}$ cm$^{-2}$ s$^{-1}$]')
               ax.legend(loc='upper right', ncol=1, fontsize=18)
            if j==1:
                ax.set(title=titles[i])
            if i < 2:
                ax.set(xticklabels=[])
            else:
                ax.set(xlabel='time [{}]'.format(timeunits))

            ax.grid(ls=':')


    return fig

Untransformed and Transformed Spectra at Earth

Compute and plot the spectra at Earth for a SN at 10 kpc with no flavor transformation, and with the chosen flavor transformation.

[4]:
def plot_spectra(model, xform_nmo, xform_imo, t):
    """Plot initial and oscillated neutrino luminosities.

    Parameters
    ----------
    model : SupernovaModel
        An input model from a CCSN simulation.
    flav_xform : FlavorTransformation
        A FlavorTransformation subclass; used to create an instance.
    t : astropy.Quantity
        Time to compute the spectrum.
    """

    energies = np.linspace(0,60,121) * u.MeV
    d = (10*u.kpc).to('cm').value # distance to SN

    #get the spectra
    ispec = model.get_initial_spectra(t, energies)
    ospec_nmo = model.get_transformed_spectra(t, energies, xform_nmo)
    ospec_imo = model.get_transformed_spectra(t, energies, xform_imo)

    for flavor in Flavor:
        for j, E in enumerate(energies):
            ispec[flavor][j] /= (4.*np.pi*d**2)
            ospec_nmo[flavor][j] /= (4.*np.pi*d**2)
            ospec_imo[flavor][j] /= (4.*np.pi*d**2)

    fig, axes = plt.subplots(2,2, figsize=(18,15), sharex=True, sharey=True, tight_layout=True)

    for i, spec in enumerate([ispec, ospec_nmo]):
        axes[0][0].plot(energies, spec[Flavor.NU_E]/1e16,
                    label='Untransformed '+Flavor.NU_E.to_tex() if i==0 else 'Transformed '+Flavor.NU_E.to_tex(),
                    color='C0', ls='-' if i==0 else ':', lw=2,  alpha=0.7)
        axes[0][0].plot(energies, spec[Flavor.NU_X]/1e16,
                    label='Untransformed '+Flavor.NU_X.to_tex() if i==0 else 'Transformed '+Flavor.NU_X.to_tex(),
                    color='C1', ls='-' if i==0 else ':', lw=2,  alpha=0.7)

        axes[0][0].set(title='Neutrinos in the NMO: $t = ${:.1f}'.format(t))
        axes[0][0].grid()
        axes[0][0].legend(loc='upper right', ncol=2, fontsize=16)

        axes[0][1].plot(energies, spec[Flavor.NU_E_BAR]/1e16,
                    label='Untransformed '+Flavor.NU_E_BAR.to_tex() if i==0 else 'Transformed '+Flavor.NU_E_BAR.to_tex(),
                    color='C0', ls='-' if i==0 else ':', lw=2,  alpha=0.7)
        axes[0][1].plot(energies, spec[Flavor.NU_X_BAR]/1e16,
                    label='Untransformed '+Flavor.NU_X_BAR.to_tex() if i==0 else 'Transformed '+Flavor.NU_X_BAR.to_tex(),
                    color='C1', ls='-' if i==0 else ':', lw=2,  alpha=0.7)

        axes[0][1].set(title='Antineutrinos in the NMO: $t = ${:.1f}'.format(t))
        axes[0][1].grid()
        axes[0][1].legend(loc='upper right', ncol=2, fontsize=16)

    for i, spec in enumerate([ispec, ospec_imo]):
        axes[1][0].plot(energies, spec[Flavor.NU_E]/1e16,
                    label='Untransformed '+Flavor.NU_E.to_tex() if i==0 else 'Transformed '+Flavor.NU_E.to_tex(),
                    color='C0', ls='-' if i==0 else ':', lw=2,  alpha=0.7)
        axes[1][0].plot(energies, spec[Flavor.NU_X]/1e16,
                    label='Untransformed '+Flavor.NU_X.to_tex() if i==0 else 'Transformed '+Flavor.NU_X.to_tex(),
                    color='C1', ls='-' if i==0 else ':', lw=2,  alpha=0.7)

        axes[1][0].set(xlabel=r'$E$ [{}]'.format(energies.unit), title='Neutrinos in the IMO: $t = ${:.1f}'.format(t))
        axes[1][0].grid()
        axes[1][0].legend(loc='upper right', ncol=2, fontsize=16)

        axes[1][1].plot(energies, spec[Flavor.NU_E_BAR]/1e16,
                    label='Untransformed '+Flavor.NU_E_BAR.to_tex() if i==0 else 'Transformed '+Flavor.NU_E_BAR.to_tex(),
                    color='C0', ls='-' if i==0 else ':', lw=2,  alpha=0.7)
        axes[1][1].plot(energies, spec[Flavor.NU_X_BAR]/1e16,
                    label='Untransformed '+Flavor.NU_X_BAR.to_tex() if i==0 else 'Transformed '+Flavor.NU_X_BAR.to_tex(),
                    color='C1', ls='-' if i==0 else ':', lw=2,  alpha=0.7)

        axes[1][1].set(xlabel=r'$E$ [{}]'.format(energies.unit), title='Antineutrinos in the IMO: $t = ${:.1f}'.format(t))
        axes[1][1].grid()
        axes[1][1].legend(loc='upper right', ncol=2, fontsize=18)

    ax = axes[0][0]
    ax.set(ylabel=r'flux [$10^{16}$ erg$^{-1}$ cm$^{-2}$ s$^{-1}$]')
    ax = axes[1][0]
    ax.set(ylabel=r'flux [$10^{16}$ erg$^{-1}$ cm$^{-2}$ s$^{-1}$]')

    return fig
[5]:
fig = plot_total_flux(model, AdiabaticMSW(), AdiabaticMSW(mh=MassHierarchy.INVERTED) )
fig.show()
# fig.savefig('flux_adiabaticmsw.pdf')
fig = plot_spectra(model, AdiabaticMSW(), AdiabaticMSW(mh=MassHierarchy.INVERTED), 100*u.ms)
fig.show()
# fig.savefig('spectra_adiabaticmsw.pdf')
../_images/nb_FlavorTransformation_7_0.png
../_images/nb_FlavorTransformation_7_1.png
[6]:
mp_nmo = MixingParameters()
angles_nmo = mp_nmo.get_mixing_angles() + (10*u.deg,)
xf_nmo = AdiabaticMSWes(angles_nmo)

mp_imo = MixingParameters(mh=MassHierarchy.INVERTED)
angles_imo = mp_imo.get_mixing_angles() + (10*u.deg,)
xf_imo = AdiabaticMSWes(angles_imo)

fig = plot_total_flux(model, xf_nmo, xf_imo)
fig.show()
# fig.savefig('flux_adiabaticmswes.pdf')
fig = plot_spectra(model, xf_nmo, xf_imo, 100*u.ms)
fig.show()
# fig.savefig('spectra_adiabaticmswes.pdf')
../_images/nb_FlavorTransformation_8_0.png
../_images/nb_FlavorTransformation_8_1.png