Solar neutrinos are produced in the core of the Sun via nuclear fusion reactions. Each chain produces neutrinos with a distinct energy spectrum:
- pp chain: \(p^+ + p^+ \longrightarrow {}^{2}\text{H} + e^+ + \nu_e\)
- pep chain: \(p^+ + e^- + p^+ \longrightarrow {}^{2}\text{H} + \nu_e\)
- hep chain: \({}^{3}\text{He} + p^+ \longrightarrow {}^{4}\text{He} + e^+ + \nu_e\)
- \({}^{7}\text{Be}\) chain: \({}^{7}\text{Be} + e^- \longrightarrow {}^{7}\text{Li} + \nu_e\)
- \({}^{8}\text{B}\) chain: \({}^{8}\text{B} \longrightarrow {}^{8}\text{Be} + e^+ + \nu_e\)
Data from John Bahcall's SNdata archive. Flux normalizations from the BS2021 Standard Solar Model.
Interactive reproduction · BS2021 SSM flux normalizations · Lines (⁷Be, pep) in cm⁻²s⁻¹ · Continua in cm⁻²s⁻¹MeV⁻¹
import numpy as np
import matplotlib.pyplot as plt
import mplhep as hep_style
hep_style.style.use("CMS")
label_font = dict(family='serif', color='darkred', weight='normal', size=20)
annot_font = dict(family='serif', color='black', weight='normal', size=13)
annot_font2 = dict(family='serif', color='#555555', weight='normal', size=12)
# ── total fluxes (BS2021 SSM, cm⁻²s⁻¹) ──────────────────────────
PP_TOTAL = 5.99e10
N13_TOTAL = 3.07e8
O15_TOTAL = 2.33e8
F17_TOTAL = 5.84e6
B8_TOTAL = 5.69e6
HEP_TOTAL = 7.93e3
BE7_TOTAL = 4.84e9
PEP_TOTAL = 1.40e8
# ── load spectral data ────────────────────────────────────────────
pp = np.loadtxt('pp.txt')
n13 = np.loadtxt('n13.dat')
o15 = np.loadtxt('o15.dat')
f17 = np.loadtxt('f17.dat')
b8 = np.loadtxt('b8.txt')
hep = np.loadtxt('hep.dat')
# ── normalize to absolute flux ────────────────────────────────────
pp_flux = pp[:,1] * PP_TOTAL
n13_flux = n13[:,1] * N13_TOTAL
o15_flux = o15[:,1] * O15_TOTAL
f17_flux = f17[:,1] * F17_TOTAL
b8_flux = b8[:,1] * B8_TOTAL
hep_flux = hep[:,1] * HEP_TOTAL
COLOR_PP = '#F2C4AE'
COLOR_B8 = '#C5DDB5'
COLOR_HEP = '#C0B6D4'
fig, ax = plt.subplots(figsize=(8, 8), facecolor='#fafafa')
ax.set_facecolor('#fafafa')
# ── filled spectra (layered — B8 and hep start from bottom) ──────
ax.fill_between(pp[:,0], pp_flux, where=pp_flux > 0, color=COLOR_PP, alpha=0.85, zorder=1)
ax.fill_between(b8[:,0], b8_flux, where=b8_flux > 0, color=COLOR_B8, alpha=0.80, zorder=2)
ax.fill_between(hep[:,0], hep_flux, where=hep_flux > 0, color=COLOR_HEP, alpha=0.80, zorder=3)
# ── contour lines ─────────────────────────────────────────────────
kw_cont = dict(color='black', linewidth=1.2, zorder=5)
kw_dash = dict(color='black', linewidth=0.9, linestyle=':', zorder=5)
ax.plot(pp[:,0], pp_flux, **kw_cont)
ax.plot(n13[:,0], n13_flux, **kw_dash)
ax.plot(o15[:,0], o15_flux, **kw_dash)
ax.plot(f17[:,0], f17_flux, **kw_dash)
ax.plot(b8[:,0], b8_flux, **kw_cont)
ax.plot(hep[:,0], hep_flux, **kw_cont)
# ── monochromatic lines: ⁷Be and pep ─────────────────────────────
be7_energies = [0.3843, 0.8613]
be7_fractions = [0.1050, 0.8950]
be7_fluxes = [BE7_TOTAL * f for f in be7_fractions]
for E, F in zip(be7_energies, be7_fluxes):
ax.vlines(E, 1e-2, F, color='black', linewidth=2.5, zorder=6)
ax.vlines(1.442, 1e-2, PEP_TOTAL, color='black', linewidth=2.5, zorder=6)
# ── labels ───────────────────────────────────────────────────────
ax.text(0.225, 5e10, 'pp', **annot_font)
ax.text(0.118, 5.4e7, r'$^{13}$N', **annot_font2)
ax.text(0.118, 3.5e6, r'$^{15}$O', **annot_font2)
ax.text(0.118, 3.5e5, r'$^{17}$F', **annot_font2)
ax.text(7.5, 7.8e4, r'$^8$B', **annot_font)
ax.text(10.0, 95, 'hep', **annot_font)
ax.annotate('', xy=(0.862, 2.5e3), xytext=(0.384, 2.5e3),
arrowprops=dict(arrowstyle='<->', color='black', lw=1.2))
ax.text(0.50, 5e2, r'$^7$Be', **annot_font)
ax.annotate('pep', xy=(1.442, 1.5e7), xytext=(1.442+0.35, 1.5e7),
fontsize=13, fontfamily='serif',
arrowprops=dict(arrowstyle='->', color='black', lw=1.2),
ha='left', va='center')
# ── axes ─────────────────────────────────────────────────────────
ax.set_xscale('log', base=10)
ax.set_yscale('log', base=10)
ax.set_xlim(0.1, 25)
ax.set_ylim(1e-2, 1e15)
x_ticks = [0.1, 0.2, 0.5, 1, 2, 5, 10, 20]
ax.set_xticks(x_ticks)
ax.set_xticklabels([str(v) for v in x_ticks])
ax.set_yticks([10**i for i in range(0, 16, 2)])
ax.grid(True, which='major', linestyle='--', linewidth=0.4, color='gray', alpha=0.4)
ax.grid(True, which='minor', linestyle=':', linewidth=0.2, color='gray', alpha=0.2)
ax.set_xlabel(r'$E_\nu\ \mathrm{[MeV]}$', fontdict=label_font)
ax.set_ylabel(
r'Flux at 1 AU $(\mathrm{cm}^{-2}\,\mathrm{s}^{-1}\,\mathrm{MeV}^{-1})$'
r'[for lines, $\mathrm{cm}^{-2}\,\mathrm{s}^{-1}$]',
fontdict=label_font
)
plt.savefig('solar_neutrino_flux.pdf', dpi=200, bbox_inches='tight')
plt.savefig('solar_neutrino_flux.png', dpi=200, bbox_inches='tight')
print("Saved.")
Interactive: Solar Neutrino Oscillation
By the time these neutrinos reach Earth — 1 AU ≈ 1.496×10⁸ km — quantum mixing causes them to oscillate between flavors. Parameters from NuFIT 6.0 (2024): \(\theta_{12} = 33.68°\), \(\Delta m^2_{21} = 7.49 \times 10^{-5}\,\text{eV}^2\).
P(νe→νe) at Earth
—
P(νe→νx) at Earth
—
L_osc (km)
—
oscillations in 1 AU
—
NuFIT 6.0 (2024) · θ₁₂=33.68°, Δm²₂₁=7.49×10⁻⁵eV² · L=1.496×10⁸ km · vacuum approximation