Note
Go to the end to download the full example code.
Kobayashi and Maskawa: the CKM matrix and three quark generations#
Cronin and Fitch’s CP violation had no accepted explanation until
Kobayashi and Maskawa (1973) showed that a third generation of quarks
– at the time, an unconfirmed extrapolation – forces the resulting
three-generation quark-mixing matrix to admit exactly one physical
complex phase, making CP violation an unavoidable feature of the weak
interaction rather than an ad hoc addition. No dedicated CKM function
exists in physicskit.particle (the package’s other CP-violation
model, the neutral-kaon system, is phenomenological rather than
built from an underlying mixing matrix); this example builds the
standard three-generation unitary mixing matrix directly from Euler-like
mixing angles and one phase – the same parametrization structure as the
PMNS lepton-mixing matrix – checks its unitarity, and shows that the
single complex phase is exactly what a two-generation (Cabibbo)
matrix cannot admit.
import matplotlib.pyplot as plt
import numpy as np
The two-generation Cabibbo matrix: always real, no CP violation possible#
A single mixing angle gives an orthogonal (real) 2x2 rotation – no room for a complex phase at all, since any phase on a 2x2 unitary matrix’s entries can be rotated away by redefining the quark fields’ overall phases.
theta_c = np.radians(13.02) # the real Cabibbo angle
V_cabibbo = np.array([[np.cos(theta_c), np.sin(theta_c)], [-np.sin(theta_c), np.cos(theta_c)]])
print("Two-generation Cabibbo matrix (always real):")
print(np.round(V_cabibbo, 6))
print(f"is it unitary? max|V^dagger V - I| = {np.max(np.abs(V_cabibbo.T @ V_cabibbo - np.eye(2))):.2e}")
Two-generation Cabibbo matrix (always real):
[[ 0.974291 0.225291]
[-0.225291 0.974291]]
is it unitary? max|V^dagger V - I| = 9.59e-19
The Jarlskog invariant: a basis-independent measure of the CP phase#
A single number that vanishes if and only if there is no CP violation – for the real 2x2 Cabibbo matrix it is identically zero; for the CKM matrix with delta != 0 it is not.
J = np.imag(V_ckm[0, 0] * V_ckm[1, 1] * np.conj(V_ckm[0, 1]) * np.conj(V_ckm[1, 0]))
print(f"\nJarlskog invariant J = {J:.3e} (nonzero -- CP violation is unavoidable once three generations mix)")
V_ckm_no_phase = ckm_matrix(theta12, theta23, theta13, 0.0)
J_at_delta0 = np.imag(V_ckm_no_phase[0, 0] * V_ckm_no_phase[1, 1] * np.conj(V_ckm_no_phase[0, 1]) * np.conj(V_ckm_no_phase[1, 0]))
print(f"Jarlskog invariant at delta=0 (hypothetically no phase): {J_at_delta0:.3e} (vanishes -- delta is exactly what makes J nonzero)")
Jarlskog invariant J = 2.983e-05 (nonzero -- CP violation is unavoidable once three generations mix)
Jarlskog invariant at delta=0 (hypothetically no phase): -0.000e+00 (vanishes -- delta is exactly what makes J nonzero)
Visualizing the mixing strengths#
fig, ax = plt.subplots(figsize=(5.5, 5))
im = ax.imshow(np.abs(V_ckm), cmap="viridis", vmin=0, vmax=1)
ax.set_xticks([0, 1, 2])
ax.set_xticklabels(["d", "s", "b"])
ax.set_yticks([0, 1, 2])
ax.set_yticklabels(["u", "c", "t"])
for i in range(3):
for j in range(3):
ax.text(j, i, f"{np.abs(V_ckm[i, j]):.3f}", ha="center", va="center", color="white" if np.abs(V_ckm[i, j]) < 0.6 else "black")
fig.colorbar(im, ax=ax, label="|V_ij|")
ax.set_title("CKM matrix magnitudes: strongly diagonal, small cross-generation mixing")
fig.tight_layout()
plt.show()

Total running time of the script: (0 minutes 0.037 seconds)