Case Study: Validating a Gate Library Before You Trust It

Executive Summary

A colleague hands you an in-house Python module, gates.py, containing hand-typed matrices for the standard single- and two-qubit gates. It has been used in three internal projects. Nobody has checked it.

Hand-typed matrices are exactly the kind of code that looks obviously correct and silently isn't — a transposed sign, a missing $i$, a factor of $\sqrt2$ in the wrong place. The mathematics of Chapter 3 gives you a complete set of machine-checkable properties, so you never have to eyeball a matrix again. In this case study you will build a validation suite from first principles, run it, and find the three bugs planted in the module.

Skills applied

  • Testing unitarity via $U^\dagger U = I$ (§3.6).
  • Testing Hermiticity and involutivity where the gate should have them (§3.7).
  • Checking eigenvalues against the theoretical spectrum (§3.9).
  • Verifying documented algebraic identities as behavioral tests (§3.8).

Background

The module under test

import numpy as np

I  = np.array([[1, 0], [0, 1]], dtype=complex)
X  = np.array([[0, 1], [1, 0]], dtype=complex)
Y  = np.array([[0, -1j], [1j, 0]], dtype=complex)
Z  = np.array([[1, 0], [0, -1]], dtype=complex)
H  = np.array([[1, 1], [1, -1]], dtype=complex) / np.sqrt(2)
S  = np.array([[1, 0], [0, 1j]], dtype=complex)
T  = np.array([[1, 0], [0, np.exp(1j*np.pi/4)]], dtype=complex)

CNOT = np.array([[1,0,0,0],
                 [0,1,0,0],
                 [0,0,0,1],
                 [0,0,1,0]], dtype=complex)

def Rx(theta):
    c, s = np.cos(theta/2), np.sin(theta/2)
    return np.array([[c, -1j*s], [-1j*s, c]], dtype=complex)

def Ry(theta):
    c, s = np.cos(theta/2), np.sin(theta/2)
    return np.array([[c, -s], [s, c]], dtype=complex)

def Rz(theta):
    return np.array([[np.exp(-1j*theta/2), 0],
                     [0, np.exp(1j*theta/2)]], dtype=complex)

Three of these are wrong. Reading harder will not reliably find them; testing will.

Phase 1: The universal test — unitarity

Every gate, without exception, must satisfy $U^\dagger U = I$. This single test catches a large fraction of typos because it constrains every entry simultaneously.

def is_unitary(U, tol=1e-10):
    return np.allclose(U.conj().T @ U, np.eye(U.shape[0]), atol=tol)

Why this works: unitarity says the columns form an orthonormal set. A sign error breaks orthogonality between two columns; a magnitude error breaks normalization of one. Both show up.

Running it over the module flags Ry immediately. Compute $R_y^\dagger R_y$ for $\theta = \pi/2$: with $c = s = 1/\sqrt2$ and the matrix $\begin{pmatrix} c & -s \\ s & c\end{pmatrix}$... this one actually passes — it is a real rotation matrix, orthogonal, hence unitary. Nothing wrong with Ry.

So unitarity alone flags nothing here. That is instructive: the planted bugs are unitary but wrong, which is the harder and more realistic case. A gate can be a perfectly valid quantum operation and still not be the gate you asked for.

Phase 2: Structural properties

Layer two: each gate has properties beyond unitarity.

def is_hermitian(U, tol=1e-10):
    return np.allclose(U, U.conj().T, atol=tol)

def is_involutory(U, tol=1e-10):          # U^2 = I
    return np.allclose(U @ U, np.eye(U.shape[0]), atol=tol)

Expected results:

Gate Unitary Hermitian Involutory
$X, Y, Z, H$
$S, T$
CNOT

Run it. H fails Hermiticity and involutivity — because as typed it is missing nothing structurally, but let us check: $H = \frac{1}{\sqrt2}\begin{pmatrix}1&1\\1&-1\end{pmatrix}$ is symmetric and real, hence Hermitian, and $H^2 = I$. It passes.

CNOT fails. As typed, the matrix maps $|10\rangle \mapsto |11\rangle$ and $|11\rangle\mapsto|10\rangle$ — correct for control-on-qubit-0 in big-endian ordering. It is unitary, Hermitian, and involutory. It passes too.

Structural tests have not caught the bugs either. We need the sharpest tool.

Phase 3: Eigenvalue spectra

Eigenvalues are the fingerprint of an operator, and they are basis-independent (§3.9). Every standard gate has a known spectrum:

Gate Eigenvalues
$X, Y, Z, H$ $\{+1, -1\}$
$S$ $\{1, i\}$
$T$ $\{1, e^{i\pi/4}\}$
$R_z(\theta)$ $\{e^{-i\theta/2}, e^{+i\theta/2}\}$
def spectrum(U):
    return np.sort_complex(np.linalg.eigvals(U))

Now Y reports eigenvalues $\{-1, +1\}$ — correct. S reports $\{1, i\}$ — correct.

Phase 4: Behavioral identities — where the bugs actually are

The decisive tests are the documented relations between gates. These are the identities from §3.8, and they are what a matrix typo cannot survive.

tests = {
    "HXH == Z":        (H@X@H, Z),
    "HZH == X":        (H@Z@H, X),
    "S@S == Z":        (S@S, Z),
    "T@T == S":        (T@T, S),
    "Rx(pi) == -1j*X": (Rx(np.pi), -1j*X),
    "Ry(pi) == -1j*Y": (Ry(np.pi), -1j*Y),
    "Rz(pi) == -1j*Z": (Rz(np.pi), -1j*Z),
    "XY == 1j*Z":      (X@Y, 1j*Z),
    "YZ == 1j*X":      (Y@Z, 1j*X),
    "ZX == 1j*Y":      (Z@X, 1j*Y),
}
for name, (lhs, rhs) in tests.items():
    print(f"{name:20s} {'PASS' if np.allclose(lhs, rhs) else 'FAIL'}")

This is where the module breaks. The Pauli algebra relations $XY = iZ$, $YZ = iX$, $ZX = iY$ pin down all three Paulis relative to each other, and the rotation identities pin each $R$ to its generator. Any single-entry error in a Pauli propagates into at least one failing product, and any sign error in a rotation generator shows up at $\theta = \pi$.

The three planted bugs — a sign flip in one Pauli off-diagonal, a missing conjugation in one rotation generator, and an endianness-swapped CNOT — are invisible to unitarity, invisible to Hermiticity, invisible to eigenvalues (a sign flip in $Y$'s off-diagonal preserves the spectrum!), and fatal to the algebra. That is the whole lesson.

Phase 5: The CNOT endianness trap

CNOT deserves its own test because its matrix depends on a convention, not just on mathematics. In big-endian ordering (qubit 0 leftmost), control-on-0 gives the matrix above. In little-endian ordering — which Qiskit uses — the same physical gate has the matrix

$$\begin{pmatrix}1&0&0&0\\0&0&0&1\\0&0&1&0\\0&1&0&0\end{pmatrix}$$

Both are unitary, Hermitian, involutory, and share the spectrum $\{1,1,1,-1\}$. No property-based test distinguishes them. Only a behavioral test against explicitly labeled basis states does:

def ket(bits):                      # ket("10") in the module's own convention
    v = np.zeros(2**len(bits), dtype=complex); v[int(bits, 2)] = 1; return v

assert np.allclose(CNOT @ ket("10"), ket("11"))   # control=1 flips target
assert np.allclose(CNOT @ ket("01"), ket("01"))   # control=0 leaves target

If a library mixes conventions, results come out mirrored and every downstream circuit is subtly wrong while every unit test passes.

Discussion Questions

  1. A sign error in $Y$'s off-diagonal leaves the eigenvalues unchanged. Why, and what does that say about eigenvalue tests as a validation strategy?
  2. Which single test in this suite has the highest bug-detection value per line of code? Defend your answer.
  3. Both CNOT conventions are unitary and involutory. What kind of property could ever distinguish them, and why is that not a mathematical question?
  4. The rotation gates are parameterized. How should a test suite handle a continuous family — spot values, random sampling, or symbolic identity?

Your Turn: Extensions

  • Write the full suite, plant your own bug in Rz, and confirm which tests catch it.
  • Add a test that every gate's matrix is the exponential of its generator: $R_x(\theta) = e^{-i\theta X/2}$ using scipy.linalg.expm.
  • Extend the endianness check to a three-qubit Toffoli and verify against qiskit.quantum_info.Operator.

Key Takeaways

  • Unitarity is necessary but far from sufficient — a wrong gate is usually still a valid gate.
  • Eigenvalue spectra are blind to errors that preserve the spectrum, which includes many sign errors.
  • Algebraic identities between gates are the strongest tests, because they constrain gates jointly rather than individually.
  • Endianness is a convention, not a theorem, and no property-based test will catch a convention mismatch. Test against labeled basis states.