osc = {
const cx = (re, im = 0) => ({re, im});
const cadd = (a, b) => ({re: a.re + b.re, im: a.im + b.im});
const cmul = (a, b) => ({re: a.re * b.re - a.im * b.im, im: a.re * b.im + a.im * b.re});
const cconj = (a) => ({re: a.re, im: -a.im});
const cexp = (t) => ({re: Math.cos(t), im: Math.sin(t)}); // e^{i t}
const K = 1.267; // for Δm² in eV², L in km, E in GeV
// Two-flavor transition (appearance) probability. E in GeV.
function P2(L, E, dm2, s2t) {
return s2t * Math.sin(K * dm2 * L / E) ** 2;
}
// Standard PMNS matrix (rows = e,μ,τ ; cols = mass states 1,2,3) as complex entries.
function pmns(th12, th13, th23, dcp) {
const s12 = Math.sin(th12), c12 = Math.cos(th12);
const s13 = Math.sin(th13), c13 = Math.cos(th13);
const s23 = Math.sin(th23), c23 = Math.cos(th23);
const em = cexp(-dcp), ep = cexp(dcp);
return [
[cx(c12 * c13), cx(s12 * c13), cmul(cx(s13), em)],
[cadd(cx(-s12 * c23), cmul(cx(-c12 * s23 * s13), ep)),
cadd(cx(c12 * c23), cmul(cx(-s12 * s23 * s13), ep)),
cx(s23 * c13)],
[cadd(cx(s12 * s23), cmul(cx(-c12 * c23 * s13), ep)),
cadd(cx(-c12 * s23), cmul(cx(-s12 * c23 * s13), ep)),
cx(c23 * c13)]
];
}
// Three-flavor vacuum oscillation P(ν_a → ν_b). a,b ∈ {0:e,1:μ,2:τ}. E in GeV.
function P3(a, b, L, E, p) {
const U = pmns(p.th12, p.th13, p.th23, p.anti ? -p.dcp : p.dcp);
let P = a === b ? 1 : 0;
const pairs = [[1, 0, p.dm21], [2, 0, p.dm31], [2, 1, p.dm31 - p.dm21]];
for (const [i, j, dm] of pairs) {
const D = K * dm * L / E;
const term = cmul(cmul(cconj(U[a][i]), U[b][i]), cmul(U[a][j], cconj(U[b][j])));
P += -4 * term.re * Math.sin(D) ** 2 + 2 * term.im * Math.sin(2 * D);
}
return P;
}
return {P2, P3};
}
// Energy is stored internally in GeV (the 1.267 factor needs GeV) but shown in MeV.
toMeV = (Egev) => Egev * 1000
fmtMeV = (Egev) => `${+(Egev * 1000).toPrecision(3)}`
// A round slider step (1/2/5 × 10^k) ~ span/200, so preset defaults land on clean values.
niceStep = (span) => {
const mag = 10 ** Math.floor(Math.log10(span / 200));
const n = span / 200 / mag;
return (n < 1.5 ? 1 : n < 3 ? 2 : n < 7 ? 5 : 10) * mag;
}
// Two-line plot title: bold heading on top, parameter value (no brackets) below.
plotTitle = (main, sub) => html`<span style="display:block; line-height:1.25">${main}<br>${sub}</span>`
// Lay a set of Inputs out in a responsive multi-column grid instead of one tall stack.
// Widen the inputs (and trim the label column) so the numeric value boxes show the
// full number rather than truncating to "0....".
gridForm = (inputs) => {
const form = Inputs.form(inputs);
form.style.display = "grid";
form.style.gridTemplateColumns = "repeat(auto-fit, minmax(340px, 1fr))";
form.style.columnGap = "28px";
form.style.rowGap = "4px";
form.style.setProperty("--label-width", "108px");
form.style.setProperty("--input-width", "210px");
// Drive a --pct custom property on each range so the blue fill renders in every
// browser (WebKit does not paint the lower track from accent-color).
form.querySelectorAll('input[type="range"]').forEach((inp) => {
const paint = () => {
const min = +inp.min, max = +inp.max;
inp.style.setProperty("--pct", (max > min ? (100 * (+inp.value - min)) / (max - min) : 0) + "%");
};
paint();
inp.addEventListener("input", paint);
});
return form;
}
// Render two plots side by side, each boxed at a fixed width so a long title cannot
// stretch the figure and force a wrap. Wraps to a single column on narrow screens.
panelW = (w) => Math.max(300, Math.min(620, Math.floor(((w || 900) - 40) / 2)))
panel = (fig, w) => html`<div style="width:${w}px; max-width:100%; overflow:hidden">${fig}</div>`
sidebyside = (figA, figB, w) => html`<div style="display:flex; flex-wrap:wrap; gap:24px; justify-content:center; align-items:flex-start">${panel(figA, w)}${panel(figB, w)}</div>`3.5 Neutrino Oscillations
A neutrino produced in one flavor (\(\nu_e\), \(\nu_\mu\), or \(\nu_\tau\)) can be detected later as a different flavor. This is neutrino oscillation, and it is direct evidence that neutrinos have mass. The reason is quantum mechanical: the flavor states a neutrino is produced and detected in are not the same as the mass states that propagate through space. A flavor state is a superposition of mass states, each travelling with a slightly different phase, and the interference between those phases is what we observe as a changing flavor composition along the beam.
The two sections below let you build intuition interactively:
- Probability vs neutrino energy \(E_\nu\) at a fixed baseline \(L\) (left): this is what a real experiment with a fixed detector distance actually measures, the oscillation pattern imprinted on the energy spectrum.
- Probability vs baseline \(L\) at a fixed energy (right): the complementary view, showing how the flavor composition develops as the neutrino travels.
Start with the two-flavor model for the core idea, then explore the realistic three-flavor picture that the long-baseline experiments (T2K, NOvA, DUNE, Hyper-Kamiokande) actually measure.
3.5.1 The two-flavor picture
With only two flavors, oscillation is governed by a single mixing angle \(\theta\) and a single mass-squared splitting \(\Delta m^2\). The probability that a neutrino born as flavor \(\alpha\) is detected as the other flavor after travelling a baseline \(L\) with energy \(E\) is
\[ P_{\alpha \to \beta} = \sin^2(2\theta)\,\sin^2\!\left(1.267\,\frac{\Delta m^2\,[\text{eV}^2]\;L\,[\text{km}]}{E\,[\text{GeV}]}\right), \]
and the survival probability is \(P_{\alpha\to\alpha} = 1 - P_{\alpha\to\beta}\). Two things set the picture: \(\sin^2(2\theta)\) fixes the amplitude of the oscillation (how much flavor can be converted), and \(\Delta m^2 / E\) fixes the wavelength. Pick an experiment preset, then tweak the sliders.
The energy view (left) is the one to study: an experiment sits at a fixed baseline and reconstructs the neutrino energy event by event, so the dip in the survival curve is the oscillation signal it measures. The first (deepest) dip, the “oscillation maximum”, sits at \(E \approx 0.8\,\Delta m^2\,L\), which is exactly why each beam energy is matched to its baseline. Set sin²(2θ) = 1 and the dip reaches zero (full conversion); lower it and the dip becomes shallower.
3.5.2 The three-flavor picture
Nature has three neutrino flavors, and the full description uses the PMNS matrix \(U\), parameterized by three mixing angles (\(\theta_{12}, \theta_{13}, \theta_{23}\)), two independent mass splittings (\(\Delta m^2_{21}, \Delta m^2_{31}\)), and a CP-violating phase \(\delta_{CP}\). The vacuum oscillation probability is
\[ P_{\alpha\to\beta} = \delta_{\alpha\beta} - 4\sum_{i>j}\mathrm{Re}\!\left(U^*_{\alpha i}U_{\beta i}U_{\alpha j}U^*_{\beta j}\right)\sin^2\Delta_{ij} + 2\sum_{i>j}\mathrm{Im}\!\left(U^*_{\alpha i}U_{\beta i}U_{\alpha j}U^*_{\beta j}\right)\sin 2\Delta_{ij}, \]
with \(\Delta_{ij} = 1.267\,\Delta m^2_{ij}\,L/E\). The experiment preset sets the baseline and energy; the mixing parameters start from current global-fit values and stay put when you change preset. The plots show all three channels for a \(\nu_\mu\) beam: the survival (\(\nu_\mu\to\nu_\mu\)) plus the two appearance channels. The \(\nu_\mu\to\nu_e\) appearance channel is the one that carries the CP signal.
The \(\nu_\mu\to\nu_e\) appearance channel is small (a few percent) but it is the keystone measurement for the next generation of experiments. On the energy plot, drag the \(\delta_{CP}\) slider and watch the \(\nu_\mu\to\nu_e\) spectrum shift, then flip the antineutrino toggle and watch it shift the other way. That difference between neutrinos and antineutrinos is CP violation in the lepton sector, and measuring it is one of the headline goals of DUNE and Hyper-Kamiokande. Notice too that \(\theta_{13}\) controls how large the appearance can ever get (set it to zero and the channel essentially vanishes), while \(\theta_{23}\) governs the depth of the \(\nu_\mu\to\nu_\mu\) survival dip.
Note
This calculator uses vacuum oscillation. Real long-baseline neutrinos travel through the Earth, where coherent forward scattering off electrons (the MSW matter effect) modifies the probabilities. The vacuum curves capture the essential physics and the role of every parameter, but are not a substitute for a full matter-effect calculation.