Computing the Best Recovery Map After Noise with Python
When a quantum state passes through a noisy channel, the information is not gone, but it is smeared across a larger Hilbert space. The question is how to undo the damage as faithfully as physics allows. The map that does this best is called the optimal recovery map (or optimal decoder). In this article we compute it for a concrete case: a logical qubit stored in four physical qubits that suffer amplitude damping. We solve for the best recovery by an iteration that is equivalent to a semidefinite program (SDP), compare it with two standard decoders, and visualize the result in 2D and 3D.
1. The problem
We store one logical qubit in four physical qubits with the Leung–Nielsen–Chuang–Yamamoto code:
$$
|0_L\rangle=\frac{|0000\rangle+|1111\rangle}{\sqrt2},\qquad
|1_L\rangle=\frac{|0011\rangle+|1100\rangle}{\sqrt2}.
$$
The encoding is the isometry $V=|0_L\rangle\langle 0|+|1_L\rangle\langle 1|:\mathbb C^2\to\mathbb C^{16}$. Each physical qubit independently undergoes amplitude damping with parameter $\gamma$:
$$
K_0=\begin{pmatrix}1&0\0&\sqrt{1-\gamma}\end{pmatrix},\qquad
K_1=\begin{pmatrix}0&\sqrt{\gamma}\0&0\end{pmatrix},
$$
so the four-qubit noise channel has $16$ Kraus operators $K_{k}=K_{k_1}\otimes K_{k_2}\otimes K_{k_3}\otimes K_{k_4}$. Combining encoding and noise gives the effective noisy encoding with Kraus operators
$$
A_k=K_kV\in\mathbb C^{16\times 2}.
$$
Goal: find a recovery channel $\mathcal R(X)=\sum_j R_jXR_j^\dagger$ with $R_j\in\mathbb C^{2\times16}$ and $\sum_jR_j^\dagger R_j=I_{16}$ that maximizes the entanglement fidelity
$$
F_e(\mathcal R)=\frac1{d^2}\sum_{j,k}\Big|\mathrm{tr}\big(R_jA_k\big)\Big|^2,\qquad d=2 .
$$
The average fidelity over all pure input states follows from $F_e$:
$$
\bar F=\frac{dF_e+1}{d+1}.
$$
For an uncoded qubit, the best we can do is the identity map, and the entanglement fidelity is
$$
F_e^{\rm raw}=\frac{\big(1+\sqrt{1-\gamma}\big)^2}{4}\approx 1-\frac{\gamma}{2}.
$$
2. The optimization problem is an SDP
Write the Choi matrix of the recovery as $J=\sum_j|R_j\rangle!\rangle\langle!\langle R_j|$, where $|X\rangle!\rangle=\sum_{a,b}X_{ab},|b\rangle|a\rangle$ is the column-stacking vectorization. Define
$$
\Omega=\sum_k|\bar A_k\rangle!\rangle\langle!\langle\bar A_k|,\qquad
|\bar A_k\rangle!\rangle=\sum_{a,b}\overline{(A_k)_{ba}},|b\rangle|a\rangle .
$$
Then $\mathrm{tr}(R_jA_k)=\langle!\langle\bar A_k|R_j\rangle!\rangle$, and the fidelity becomes linear in $J$:

Here
is the partial trace over the two-dimensional output factor, and the constraint is exactly trace preservation, $\sum_jR_j^\dagger R_j=I_{16}$.
3. A solver-free algorithm: polar-decomposition iteration
Stack all Kraus operators into one tall matrix $W=[R_1;R_2;\dots;R_m]\in\mathbb C^{2m\times16}$. Trace preservation says $W^\dagger W=I_{16}$, so $W$ is an isometry. The fidelity is a convex quadratic function of $W$:
$$
T_{jk}=\mathrm{tr}(R_jA_k),\qquad
f(W)=\frac1{d^2}\sum_{jk}|T_{jk}|^2 .
$$
Its gradient (with respect to $\bar W$, up to the factor $1/d^2$) has blocks
$$
G_j=\sum_kT_{jk},A_k^\dagger .
$$
The update maximizes the linearization of $f$ over all isometries. With the thin singular value decomposition $G=U\Sigma V_h$, the maximizer of $\mathrm{Re},\mathrm{tr}(G^\dagger W)$ over isometries is
$$
W_{\rm new}=U,V_h .
$$
Because $f$ is convex, $f(W_{\rm new})\ge f(W)+\frac{2}{d^2}\mathrm{Re},\mathrm{tr}\big[G^\dagger(W_{\rm new}-W)\big]\ge f(W)$, so the fidelity increases monotonically. Choosing $m=32$ Kraus operators is fully general, because the Choi matrix is $32\times32$.
4. Two reference decoders
Naive decoding. Project onto the code space and undo the encoding with $V^\dagger$. Anything outside the code space is replaced by the maximally mixed qubit so that the map is trace preserving.
Petz recovery map. With the reference state $\sigma=VV^\dagger/2$ and the noise channel $\mathcal N$,
$$
\mathcal R_{P}(X)=\sigma^{1/2},\mathcal N^\dagger!\Big(\mathcal N(\sigma)^{-1/2},X,\mathcal N(\sigma)^{-1/2}\Big),\sigma^{1/2},
$$
followed by the decoding $V^\dagger$. In Kraus form, $R_k=V^\dagger\sigma^{1/2}K_k^\dagger,\mathcal N(\sigma)^{-1/2}$. The Petz map is a famous near-optimal recovery map, which makes it a strong benchmark.
5. Complete source code
1 | import time |
6. Code walkthrough
Section 1: code space and noise
ket builds a computational basis vector of the 16-dimensional space from a bit string. Stacking zero_L and one_L as columns gives the isometry V of shape $16\times2$. amp_damp_kraus returns the two single-qubit Kraus operators, and multi_qubit_kraus forms all $2^4=16$ tensor products with reduce(np.kron, ...), which is the Kraus set of the four-qubit noise channel. Later, np.matmul(K_phys, V) broadcasts over the Kraus index and produces the effective operators $A_k=K_kV$ in a single call.
Section 2: the fidelity function
entanglement_fidelity is the formula $F_e=\frac1{d^2}\sum_{jk}|\mathrm{tr}(R_jA_k)|^2$. The einsum string "jab,kba->jk" evaluates
for every pair $(j,k)$ at once. The same function scores every strategy, which keeps the comparison fair: the uncoded qubit uses $R=I$ with $A_k=K_k$, and the coded cases use $16\times2$ operators $A_k$.
Section 3: the optimizer
This is the heart of the article. random_isometry produces a random starting $W$ by taking the QR factorization of a complex Gaussian matrix, which gives orthonormal columns. In each iteration of optimal_recovery:
W.reshape(m, 2, n_out)unstacks $W$ into the $m$ Kraus operators $R_j$.Tis the matrix $T_{jk}=\mathrm{tr}(R_jA_k)$, and its squared magnitude sum is the current fidelity, which is stored inhist.G = einsum("jk,kab->jab", T, A_dag)computes $G_j=\sum_kT_{jk}A_k^\dagger$, andreshapestacks the blocks back into a $64\times16$ matrix.- The thin SVD
G = U Σ Vhgives the closest isometryW = U @ Vh, which is the polar factor and the exact maximizer of the linearized objective.
The loop stops early if the fidelity changes by less than tol. Three random starts are run, and the best final fidelity is kept. If all starts agree, that is strong numerical evidence that the iteration has found the global optimum of the SDP.
Speed. Everything is vectorized with einsum, and the only decomposition per iteration is the SVD of a $64\times16$ matrix. A complete $\gamma$ sweep with 28 values and 3 random starts each finishes within a few seconds on a standard CPU, so no further acceleration is needed.
Section 4: baselines
complete_tp takes any set of Kraus operators whose $\sum R^\dagger R$ is dominated by the identity and appends extra operators that send the missing part of the space to the maximally mixed qubit. It does this with the eigendecomposition of $D=I-\sum R^\dagger R$: for every eigenvector $|\phi\rangle$ with eigenvalue $\lambda$, it adds $\sqrt{\lambda/2},|a\rangle\langle\phi|$ for $a=0,1$, which restores trace preservation exactly. naive_recovery is just $V^\dagger$ plus this completion. psd_power raises a positive semidefinite matrix to a fractional power on its support, which is used for $\sigma^{1/2}$ and the pseudo-inverse square root $\mathcal N(\sigma)^{-1/2}$. petz_recovery builds $R_k=V^\dagger\sigma^{1/2}K_k^\dagger\mathcal N(\sigma)^{-1/2}$ for every Kraus operator of the noise.
Section 5: Bloch-sphere tools
Any qubit channel acts affinely on Bloch vectors, $\vec n\mapsto M\vec n+\vec t$. bloch_affine extracts $M$ and $\vec t$ from a Kraus set using the Pauli matrices: $\vec t_i=\tfrac12\mathrm{tr}(\sigma_i\sum_mE_mE_m^\dagger)$ and $M_{ij}=\tfrac12\mathrm{tr}(\sigma_i\sum_mE_m\sigma_jE_m^\dagger)$. effective_kraus composes recovery and noisy encoding, $E_{jk}=R_jA_k$, so that the whole “encode, damp, recover” pipeline becomes one qubit-to-qubit channel that can be drawn.
Section 6: the $\gamma$ sweep
For each damping value the loop builds the noise Kraus operators, scores the uncoded qubit, the naive decoder, the Petz map and the optimal map, and stores the Bloch-affine parameters for the 3D plots. tp_error records the worst violation of $\sum_jR_j^\dagger R_j=I$ over the sweep, which certifies that the computed optimal map is a legitimate channel.
Section 7: console report
The report prints sanity checks (isometry, trace preservation of noise and recovery), a fidelity table, the fitted scaling exponents of the infidelity, and a detailed run at $\gamma=0.1$ with the final fidelity of each random start. The exponents come from a straight-line fit of $\log(1-F_e)$ against $\log\gamma$ over the eight smallest damping values.
Section 8: the figure
All six panels live in a single figure built with add_gridspec(2, 3). The first row has three 2D panels: fidelity versus $\gamma$, the log-log infidelity, and the convergence of the iteration. The second row has three 3D panels: the image of the Bloch sphere without coding, the image under the optimal recovery, and a fidelity surface over $(\gamma,\theta)$. For a pure input with Bloch vector $\vec n_{\rm in}=(\sin\theta,0,\cos\theta)$ and output vector $\vec n_{\rm out}=M\vec n_{\rm in}+\vec t$, the state fidelity is
$$
F(\theta,\gamma)=\frac{1+\vec n_{\rm in}\cdot\vec n_{\rm out}}{2},
$$
which is computed directly without building any density matrix.
7. Execution results

Isometry check max|V^dag V - I| = 2.220446049250313e-16 Noise TP check max|sum K^dag K - I| = 4.440892098500626e-16 Recovery TP check (worst over sweep) max|sum R^dag R - I| = 2.4424906541753444e-15 Entanglement fidelity F_e gamma no code naive Petz optimal 0.0030 0.998499 0.995509 0.999984 0.999989 0.0050 0.997496 0.992519 0.999956 0.999969 0.0083 0.995822 0.987548 0.999878 0.999913 0.0139 0.993025 0.979306 0.999660 0.999758 0.0232 0.988352 0.965695 0.999053 0.999326 0.0387 0.980531 0.943367 0.997359 0.998124 0.0646 0.967414 0.907160 0.992639 0.994781 0.1078 0.945324 0.849589 0.979520 0.985493 0.1798 0.907851 0.761122 0.943525 0.959785 0.3000 0.843330 0.633250 0.849138 0.889663 Scaling exponent of 1 - F_e at small gamma ( no code): 1.001 Scaling exponent of 1 - F_e at small gamma ( naive): 0.996 Scaling exponent of 1 - F_e at small gamma ( Petz): 2.001 Scaling exponent of 1 - F_e at small gamma ( optimal): 2.000 gamma = 0.1: optimal F_e = 0.98751670, average state fidelity = 0.99167780 Final fidelity of each random start: [0.9875167, 0.9875167, 0.9875167] Iterations used by each start: [400, 400, 400] Elapsed time: 10.7 s
8. How to read the results
Panel 1: fidelity versus noise strength
Four curves start near $F_e=1$ at small $\gamma$ and fall as the noise grows. The optimal recovery sits on top, the Petz map follows closely behind, and the uncoded qubit comes next. The naive decoder is the lowest curve, and it is even worse than not encoding at all. The reason is that the four-qubit code is designed so that a single damping jump can be detected and corrected, but the “no-jump” evolution, in which nothing visibly happens, still distorts the code words at first order in $\gamma$. A decoder that merely projects and decodes ignores that distortion. The optimal map compensates for it.
Panel 2: error scaling on a log-log plot
This panel contains the main message. On a log-log plot, a power law $1-F_e\propto\gamma^p$ is a straight line of slope $p$. The uncoded qubit and the naive decoder both have slope $p\approx1$, matching $1-F_e^{\rm raw}\approx\gamma/2$. The Petz map and the optimal map have slope $p\approx2$: the code together with a good recovery cancels all first-order errors, and only second-order processes (two damping events, or a jump combined with a distortion) survive. The optimal curve lies consistently below the Petz curve by a roughly constant factor, so Petz is excellent but not exactly optimal. At small noise, the benefit of coding is therefore not a few percent but orders of magnitude.
Panel 3: convergence of the iteration
The vertical axis shows the gap between each iterate’s fidelity and the converged value. Starting from random isometries, the gap collapses by many orders of magnitude within the first handful of iterations, then flattens at a level of about $10^{-9}$ or below. The three starts reach the same fidelity to roughly eight significant digits, which is what we expect when the iteration lands on the global optimum of the underlying SDP. As a cross-check, a generic interior-point SDP solver gives the same optimum to about eight digits. The small plateau differences between starts are caused by directions along which the objective is almost flat.
Panels 4 and 5: the Bloch sphere before and after
The faint wireframe is the original Bloch sphere. A channel maps it to an ellipsoid, and a perfect channel would leave it untouched. Without coding, amplitude damping squeezes the sphere toward the north pole $|0\rangle$: the equatorial radius shrinks to $\sqrt{1-\gamma}$, the polar axis shrinks to $1-\gamma$, and the whole shape shifts upward by $\gamma$. This is the physical signature of energy relaxation, which loses coherence and also biases the state toward the ground state. With the code and the optimal recovery at the same $\gamma=0.30$, the image is almost a centered sphere. A small residual shrinkage remains because even the optimal recovery cannot fully undo second-order processes.
Panel 6: the fidelity surface
The surface shows the fidelity of a single pure input as a function of the damping strength and the input polar angle $\theta$, where $\theta=0$ is $|0\rangle$ and $\theta=\pi$ is $|1\rangle$. The warm surface (no code) is perfectly flat at the value 1 along $\theta=0$, because $|0\rangle$ is the fixed point of amplitude damping. It collapses at $\theta=\pi$, because $|1\rangle$ is the state that decays. The cool surface (optimal recovery) is much more uniform in $\theta$. Because the recovery is optimized for the average entanglement fidelity, it gives up a small amount at the naturally protected state $|0\rangle$ in exchange for large gains at the vulnerable states. This uniformity is exactly what we want from a quantum memory, which must preserve every state equally well, not just the convenient ones.
9. Summary
- The optimal recovery map is the solution of a semidefinite program: maximize a linear function of the Choi matrix subject to positivity and trace preservation.
- Exploiting the structure of the entanglement fidelity, a simple polar-decomposition iteration solves the problem monotonically using only NumPy.
- For the four-qubit amplitude-damping code, the optimal recovery has infidelity scaling as $\gamma^2$, the Petz map has the same scaling with a slightly larger prefactor, and naive project-and-decode decoding scales only as $\gamma$ and is worse than doing nothing.
- The recovery map matters as much as the code itself: the same encoded state can be restored with first-order or second-order errors depending solely on how we decode.









. The dark-to-bright scale makes solver-level errors visible.





first and are then pushed further by the logarithmic growth of $x^{*}$. Expensive resources appear mainly at the ridge peaks, and cheap resources also cover the quiet hours.
