Training A Fault-Tolerant Quantum Kernel For 1D Phase Slip Classification
Written by
Zed Qubit
I got pulled into a weird corner of quantum machine learning after trying to classify a very simple 1D physical signal: whether a field configuration contains a phase slip (a localized jump in phase). It sounds like physics trivia, but it turns into a surprisingly sharp ML problem: the label depends on subtle interference patterns, and classical features miss them unless you engineer a lot.
My goal was to build a quantum kernel classifier (a model that measures similarity in a quantum feature space) that would still behave reasonably under the kind of errors you face on real hardware. The niche part: I used a kernel designed specifically for phase-slip signatures via interleaved phase and random “echo” layers, then I trained it in a hybrid classical-quantum loop while explicitly simulating a small fault-tolerant regime.
Below is what I learned and the working code I used.
The niche problem: 1D phase slip vs. smooth phase ramps
I model each sample as a 1D array of phases on a discretized “wire”:
- Class 0: smooth ramp, phase changes gradually
- Class 1: ramp plus a localized discontinuity (phase slip), wrapped into ([0, 2\pi))
The data is tiny and synthetic, but the quantum kernel is the interesting part.
Quantum kernel basics (in the way I needed them)
A quantum kernel builds a similarity matrix (K) between inputs (x) and (x'):
- Encode each input as a quantum state (|\psi(x)\rangle).
- Compute the overlap (often implemented as a fidelity estimate): [ k(x, x') = |\langle \psi(x) \mid \psi(x')\rangle|^2 ]
- Train a classical classifier (here: an SVM) on the kernel matrix.
I’m not assuming you already know any of this: the main point is that the “learning” happens classically (SVM), while the quantum device provides the pairwise similarities.
Why “echo layers” for phase-slip robustness
Phase slips produce interference localized in “position.” If you just encode phases into a straightforward circuit, noise smears the interference and the kernel loses discriminative power.
The trick I used is conceptually simple:
- Encode the phase ramp using controlled phase rotations.
- Add echo-like random layers: repeat a structured block and apply a random Pauli-(Z) “sign flip” pattern between blocks.
- Average the kernel over a small number of echo realizations.
In effect, the encoding becomes less sensitive to some coherent error patterns and more sensitive to the phase-slip-induced interference.
Circuit design: phase-slip kernel feature map
I used Cirq to build the circuit and statevector simulation with optional noise sampling.
What the feature map does
For an input (x \in \mathbb{R}^n) (the phase samples), the circuit:
- Prepares an equal superposition on (n) qubits.
- Applies phase rotations based on (x).
- Uses an interleaved echo block (E(\omega)) repeated twice:
- first controlled phase layer
- then a randomly sampled (Z)-flip pattern
- then a second controlled phase layer
- Measures via overlap estimation (kernel uses a state fidelity computation in simulation mode; on hardware you’d estimate via a swap test variant).
Working code (end-to-end)
This code:
- Generates the phase-slip dataset
- Builds a quantum kernel with echo averaging
- Trains an SVM using that kernel
- Evaluates accuracy
- Includes a simple noise simulation toggle
Dependencies:
numpy,scikit-learn,cirq
# quantum_phase_slip_kernel.py import numpy as np import cirq from sklearn.svm import SVC from sklearn.metrics import accuracy_score from dataclasses import dataclass def wrap_to_2pi(phases): return np.mod(phases, 2 * np.pi) def make_sample(n, phase_slip=False, rng=None): """ Build a 1D phase signal sampled at n points. Class 0: smooth ramp Class 1: ramp plus a localized phase slip (a jump by ~pi). """ rng = np.random.default_rng() if rng is None else rng # Smooth ramp: choose slope and intercept, add small noise slope = rng.uniform(0.5, 1.5) intercept = rng.uniform(0, 2 * np.pi) t = np.linspace(0, 1, n) phases = intercept + 2 * np.pi * slope * t # Optional phase slip at a random position if phase_slip: slip_pos = rng.integers(low=1, high=n - 1) # Add a jump of ~pi (with a little randomness), like a localized discontinuity slip_amount = np.pi + rng.normal(0, 0.15) phases[slip_pos:] = phases[slip_pos:] + slip_amount # Add wrapping + small measurement-like noise phases = phases + rng.normal(0, 0.05, size=n) return wrap_to_2pi(phases) def make_dataset(n_qubits=6, n_train=30, n_test=20, rng_seed=7): rng = np.random.default_rng(rng_seed) def gen_split(count, slip_prob): X = [] y = [] for _ in range(count): slip = rng.random() < slip_prob X.append(make_sample(n_qubits, phase_slip=slip, rng=rng)) y.append(1 if slip else 0) return np.array(X, dtype=np.float64), np.array(y, dtype=np.int64) # Balanced-ish split X_train, y_train = gen_split(n_train, slip_prob=0.5) X_test, y_test = gen_split(n_test, slip_prob=0.5) return X_train, y_train, X_test, y_test @dataclass class EchoKernelConfig: reps: int = 2 # number of echo repeats inside the feature map echo_draws: int = 6 # how many random echo realizations to average use_simple_noise: bool = False noise_p: float = 0.01 # probability for a Z-flip (depolarizing-like simplification) class PhaseSlipFeatureMap: """ Feature map |psi(x)> that emphasizes phase-slip interference. Uses interleaved echo blocks with random Z-flip patterns. I simulate the state and compute fidelity overlaps directly. (On real hardware, overlap is estimated; the circuit structure is still useful.) """ def __init__(self, n_qubits: int, cfg: EchoKernelConfig, seed: int = 1234): self.n = n_qubits self.cfg = cfg self.rng = np.random.default_rng(seed) self.qubits = cirq.LineQubit.range(n_qubits) def _noise_maybe(self, moment_index, p, builder): """ Very simple noise: with prob p, apply an extra Z gate on a random subset. This is not an exact device model, but it breaks ideal interference in a way that makes the echo averaging matter. """ if not self.cfg.use_simple_noise or p <= 0: return mask = self.rng.random(self.n) < p if np.any(mask): for q_i, m in enumerate(mask): if m: builder.append(cirq.Z(self.qubits[q_i])) def _build_state_circuit(self, x, echo_bits): """ Build a circuit that prepares |psi(x)>. echo_bits: array of ±1 represented as {0,1} for Z-flip decisions. """ x = np.asarray(x, dtype=np.float64) if x.shape != (self.n,): raise ValueError(f"Expected x shape {(self.n,)}, got {x.shape}") circuit = cirq.Circuit() # Start in |+...+> circuit.append(cirq.H.on_each(*self.qubits)) # Echo structure: two phase layers with random Z pattern in between. # Use parameterization that makes "jumps" affect interference more. # A standard choice: Rz(theta) encodes phase; theta depends on input and position. # Here: theta_i = alpha * (x_i - x_0) to make slip relative. alpha = 1.7 theta = alpha * (x - x[0]) # First phase layer circuit.append([cirq.rz(theta[i]).on(self.qubits[i]) for i in range(self.n)]) # Apply a Z-flip pattern (echo) # echo_bits[i]=1 means apply Z on qubit i, else do nothing. for i in range(self.n): if echo_bits[i] == 1: circuit.append(cirq.Z(self.qubits[i])) # Optional noise insertion point (simulation-only) self._noise_maybe(0, self.cfg.noise_p, circuit) # Second phase layer (intentionally structured; depends on reps) for _ in range(self.cfg.reps): # Add another layer with a slightly different scaling to avoid trivial cancellation beta = 0.8 circuit.append([cirq.rz(beta * theta[i]).on(self.qubits[i]) for i in range(self.n)]) # Another echo-like flip between repeats for i in range(self.n): if echo_bits[i] == 1: circuit.append(cirq.Z(self.qubits[i])) self._noise_maybe(1, self.cfg.noise_p, circuit) return circuit def state(self, x): """ Prepare the average state over echo draws by averaging fidelities later. For kernel computations, it's more convenient to compute fidelity per draw. """ # Not used directly; kernel computes per draw overlaps. raise NotImplementedError def kernel_value(self, x, xp): """ Compute k(x, xp) = average over echo draws of |<psi(x)|psi(x')>|^2. """ # We sample echo patterns deterministically per call by drawing new bits. # That keeps the code simple; averaging is what matters. total = 0.0 for _ in range(self.cfg.echo_draws): echo_bits = (self.rng.random(self.n) < 0.5).astype(np.int64) c1 = self._build_state_circuit(x, echo_bits) c2 = self._build_state_circuit(xp, echo_bits) # Simulate both states (statevector) sim = cirq.Simulator(dtype=np.complex128) s1 = sim.simulate(c1) s2 = sim.simulate(c2) v1 = s1.final_state_vector v2 = s2.final_state_vector # Fidelity = |<v1|v2>|^2 inner = np.vdot(v1, v2) fid = float(np.abs(inner) ** 2) total += fid return total / self.cfg.echo_draws def build_kernel_matrix(X, feature_map: PhaseSlipFeatureMap): """ Build symmetric kernel matrix for training. """ n = len(X) K = np.zeros((n, n), dtype=np.float64) for i in range(n): K[i, i] = feature_map.kernel_value(X[i], X[i]) for j in range(i + 1, n): val = feature_map.kernel_value(X[i], X[j]) K[i, j] = val K[j, i] = val return K def build_kernel_matrix_test_train(X_test, X_train, feature_map: PhaseSlipFeatureMap): """ Kernel for SVM prediction: returns K_test_train of shape (n_test, n_train) where K[t, i] = k(X_test[t], X_train[i]) """ n_test = len(X_test) n_train = len(X_train) K = np.zeros((n_test, n_train), dtype=np.float64) for t in range(n_test): for i in range(n_train): K[t, i] = feature_map.kernel_value(X_test[t], X_train[i]) return K def main(): n_qubits = 6 X_train, y_train, X_test, y_test = make_dataset(n_qubits=n_qubits, n_train=24, n_test=12, rng_seed=3) # Kernel config: # - Increase echo_draws to reduce variance from randomness # - Turn on simple noise to see whether echo averaging helps cfg = EchoKernelConfig( reps=2, echo_draws=6, use_simple_noise=False, # toggle to True to simulate basic noise noise_p=0.02 ) fmap = PhaseSlipFeatureMap(n_qubits=n_qubits, cfg=cfg, seed=999) print("Building training kernel matrix...") K_train = build_kernel_matrix(X_train, fmap) # Train SVM with precomputed kernel # SVC(kernel='precomputed') expects K_train and uses it directly. clf = SVC(kernel="precomputed", C=5.0, gamma="auto") clf.fit(K_train, y_train) print("Building test kernel matrix...") K_test_train = build_kernel_matrix_test_train(X_test, X_train, fmap) # SVC expects a kernel matrix with the same columns as training samples y_pred = clf.predict(K_test_train) acc = accuracy_score(y_test, y_pred) print(f"Test accuracy: {acc:.3f}") print("True labels: ", y_test.tolist()) print("Pred labels: ", y_pred.tolist()) if __name__ == "__main__": main()
What happens when I ran it
With use_simple_noise=False, the classifier typically achieves near-perfect separation on this small dataset because the kernel “sees” phase-slip interference cleanly.
When I toggled:
use_simple_noise=True, noise_p=0.02
accuracy dipped, but it didn’t collapse. More importantly, increasing echo_draws from 6 to 12 usually made results noticeably more stable, because the random echo patterns effectively average away some noise-specific quirks in the feature map.
That behavior—stability from echo averaging under an imperfect simulator—was the practical “fault-tolerant-ish” lesson I took away, even though this is still a simulation and not a full quantum error-correcting code.
Where fault-tolerant ideas enter (without pretending this is full QEC)
In real fault-tolerant quantum computing (FTEC), you don’t just “run a circuit”; you protect it with error-correcting codes and repeated measurements (syndromes) so errors don’t accumulate.
My experiment didn’t implement QEC, but the design mirrors a key engineering pattern:
- Make the feature map more robust to structured errors by using redundancy/echo.
- Use a hybrid loop (classical optimizer/classifier + quantum similarity evaluation).
- Rely on averaging over randomness to reduce sensitivity.
In a real FTEC pipeline, the fidelity estimate in the kernel would come from error-corrected circuits or logical qubit abstractions, but the ML loop structure would remain very similar.
Closing thoughts
I built a quantum kernel classifier specialized for a very specific signal property—1D phase slips—and the biggest practical takeaway was that kernel performance isn’t only about “quantumness.” It’s about engineering the feature map to preserve the interference pattern you care about, then using echo averaging to make the similarity measure stable under noise. The result was a hybrid classical-quantum workflow that behaved sensibly in a fault-tolerant mindset: robustness through structure and averaging, not just ideal circuits.