|
| 1 | +# Pauli Conventions |
| 2 | + |
| 3 | +This page pins down how Pauli operators, their phases, and stabilizer groups are |
| 4 | +represented in `mqt.qecc.codes.core.pauli`. The conventions matter: a phase that |
| 5 | +is read as if it were a sign bit produces silently wrong results rather than an |
| 6 | +error. |
| 7 | + |
| 8 | +## The representation |
| 9 | + |
| 10 | +A `Pauli` on $n$ qubits is stored as a binary symplectic support $(x \mid z)$ |
| 11 | +together with a **phase exponent** $p \in \{0,1,2,3\}$: |
| 12 | + |
| 13 | +$$ |
| 14 | +P = i^{p}\, X^{x} Z^{z} |
| 15 | +\qquad\text{where}\qquad |
| 16 | +X^{x} Z^{z} = \bigotimes_{j=1}^{n} X^{x_j} Z^{z_j} |
| 17 | +$$ |
| 18 | + |
| 19 | +The exponent is stored in `Pauli.phase_exponent`; for a `PauliTableau` the |
| 20 | +per-row exponents are in `PauliTableau.phase_exponents`. |
| 21 | + |
| 22 | +The single-qubit letters follow from this with $Y = iXZ$: |
| 23 | + |
| 24 | +| letter | $(x_j \mid z_j)$ | contribution to $p$ | |
| 25 | +| ------ | ---------------- | ------------------- | |
| 26 | +| `I` | $(0 \mid 0)$ | 0 | |
| 27 | +| `X` | $(1 \mid 0)$ | 0 | |
| 28 | +| `Z` | $(0 \mid 1)$ | 0 | |
| 29 | +| `Y` | $(1 \mid 1)$ | 1 | |
| 30 | + |
| 31 | +So a Hermitian Pauli with a `+` sign has $p = x \cdot z$, which is the number of |
| 32 | +`Y` letters modulo four — **not** zero. `Pauli(support)` with no explicit |
| 33 | +exponent picks exactly this canonical positive Hermitian choice. |
| 34 | + |
| 35 | +## Exponents are not sign bits |
| 36 | + |
| 37 | +`phase_exponent` carries four values, not two. The two are related by |
| 38 | + |
| 39 | +$$ |
| 40 | +P = (-1)^{r}\, i^{\,x \cdot z}\, X^{x} Z^{z}, |
| 41 | +\qquad |
| 42 | +r = \frac{p - x \cdot z}{2} \bmod 2 |
| 43 | +$$ |
| 44 | + |
| 45 | +Use the explicit converters rather than reaching for the raw exponent: |
| 46 | + |
| 47 | +- `Pauli.sign()` / `PauliTableau.signs()` return the binary sign $r$, and raise |
| 48 | + `InvalidPauliError` on a non-Hermitian operator, which has no real sign. |
| 49 | +- `Pauli.from_symplectic_and_sign(support, sign)` and |
| 50 | + `PauliTableau.phase_from_signs(matrix, signs)` go the other way. |
| 51 | +- `Pauli.is_hermitian()` / `PauliTableau.is_hermitian()` test whether a sign |
| 52 | + exists at all. |
| 53 | + |
| 54 | +## Multiplication needs a correction term |
| 55 | + |
| 56 | +Because $Z^{z_1} X^{x_2} = (-1)^{z_1 \cdot x_2} X^{x_2} Z^{z_1}$, the product of |
| 57 | +two Paulis is |
| 58 | + |
| 59 | +$$ |
| 60 | +P_1 P_2 = i^{\,p_1 + p_2 + 2 (z_1 \cdot x_2)} |
| 61 | +X^{x_1 \oplus x_2} Z^{z_1 \oplus z_2} |
| 62 | +$$ |
| 63 | + |
| 64 | +The extra $2(z_1 \cdot x_2)$ is why |
| 65 | +**XOR-ing symplectic rows and XOR-ing their signs is not Pauli multiplication**. |
| 66 | +Concretely: |
| 67 | + |
| 68 | +$$(X \otimes X)(Z \otimes Z) = -\,Y \otimes Y$$ |
| 69 | + |
| 70 | +while XOR-ing the two `+` signs would predict $+\,Y \otimes Y$. Note the |
| 71 | +correction depends on the number of qubits: $(XXXX)(ZZZZ) = +YYYY$. |
| 72 | + |
| 73 | +Consequences for anyone combining rows of a signed tableau: |
| 74 | + |
| 75 | +- Use `PauliTableau.multiply_rows(target, source)`, never a raw XOR on |
| 76 | + `tableau.tableau.data` followed by an XOR on the phases. |
| 77 | +- Use `pauli_row_echelon`, not `mod2.row_echelon`, whenever phases must survive |
| 78 | + the reduction. A plain mod-2 reduction is only safe on a CSS tableau, where |
| 79 | + the pivoting never combines an X-type row with a Z-type row and the correction |
| 80 | + term vanishes. |
| 81 | +- `PauliTableau.independent_rows()` selects rows by support only and is |
| 82 | + explicitly phase-insensitive; do not use it to decide anything about signs. |
| 83 | + |
| 84 | +## Which layer enforces what |
| 85 | + |
| 86 | +The two layers deliberately allow different things: |
| 87 | + |
| 88 | +- **`Pauli` / `PauliTableau` represent the full $n$-qubit Pauli group |
| 89 | + $\mathfrak{P}_n$.** Non-Hermitian elements such as `+iX` are legal and |
| 90 | + necessary: row reduction genuinely produces them as intermediates, since |
| 91 | + $X \cdot Z = -iY$. |
| 92 | +- **`StabilizerCode` enforces the stabilizer conditions.** Its constructor |
| 93 | + rejects generators that do not commute, are not Hermitian, or together |
| 94 | + generate $-I$. Those checks — not the Pauli layer — are what guarantee a valid |
| 95 | + code. |
| 96 | + |
| 97 | +Generators need **not** be independent. A redundant generating set is accepted |
| 98 | +and kept as given, so `CSSCode` preserves the check matrices you pass in, |
| 99 | +including redundant rows that matter for single-shot decoding and meta-checks. |
| 100 | +Group-level comparisons (`equal_stabilizer_group`, `stabilizer_equivalent`, |
| 101 | +`is_stabilizer`) compare the generated groups and are unaffected by redundancy. |
| 102 | + |
| 103 | +## Subgroups and rank |
| 104 | + |
| 105 | +`pauli_row_echelon` returns a `PauliRowEchelon`, whose `rank` is $\log_2 |G|$ |
| 106 | +for the generated subgroup $G$. This includes the central scalars, so it can |
| 107 | +exceed the number of pivot columns: |
| 108 | + |
| 109 | +| generators | generated subgroup | order | `rank` | |
| 110 | +| ------------- | ------------------------------------------------- | ----- | ------ | |
| 111 | +| `["XX","ZZ"]` | $\{I, XX, ZZ, -YY\}$ — no scalars beyond $I$ | 4 | 2 | |
| 112 | +| `["+iX"]` | $\{I, iX, -I, -iX\}$ — one pivot, scalars $\pm I$ | 4 | 2 | |
| 113 | +| `["X","Z"]` | $\{\pm I, \pm X, \pm Z, \pm iY\}$ — anticommuting | 8 | 3 | |
| 114 | + |
| 115 | +The middle row has a single pivot column yet rank 2: the generator squares to |
| 116 | +$-I$, so the subgroup contains scalars the support alone cannot account for. The |
| 117 | +last row picks up $-I$ from the anticommutator. |
| 118 | + |
| 119 | +To test many Paulis against one subgroup, compute the echelon once and call |
| 120 | +`pauli_in_reduced_subgroup`; `PauliTableau.is_in_subgroup` redoes the |
| 121 | +elimination on every call. |
0 commit comments