Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions paulimer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ the building blocks for stabilizer quantum mechanics and quantum error correctio
- **Clifford Unitaries**: Efficient representation enabling fast operations
- [`CliffordUnitary`]: O(n²) Pauli conjugation via binary symplectic matrix
- Supports all standard Clifford gates (H, S, CNOT, etc.)
- Decomposition into Clifford transvections (`π/4` Pauli exponents), including a
strict-minimum-length variant, via [`clifford_to_transvections`] and
[`clifford_to_transvections_minimal`]

Based on algorithms from [arXiv:2309.08676](https://arxiv.org/abs/2309.08676).

Expand Down Expand Up @@ -171,6 +174,8 @@ Key documentation:
- [`SparsePauli`](src/pauli/sparse.rs) - Sparse Pauli representation for large systems
- [`PauliGroup`](src/pauli_group.rs) - Subgroup operations and stabilizer groups
- [`CliffordUnitary`](src/clifford.rs) - Clifford gates and Pauli conjugation
- [Transvection decomposition](src/clifford/transvection.rs) - Decomposing Cliffords into `π/4`
Pauli exponents (`clifford_to_transvections`, `clifford_to_transvections_minimal`)
- [Trait documentation](src/lib.rs) - `Pauli`, `Clifford`, and other core traits

## Contributing
Expand Down
8 changes: 7 additions & 1 deletion paulimer/bindings/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ print(q * q) # Identity
h = paulimer.CliffordUnitary.from_name("Hadamard", [0], qubit_count=1)
print(h.image_of(paulimer.DensePauli("X"))) # Z

# Decompose a Clifford into pi/4 Pauli exponents (Clifford transvections)
cnot = paulimer.CliffordUnitary.from_name("ControlledX", [0, 1], qubit_count=2)
factors = cnot.to_transvections_minimal()
print(factors) # minimal-length list of transvection Paulis reproducing the symplectic action

# Stabilizer simulation
sim = paulimer.OutcomeCompleteSimulation(2)
sim.apply_unitary(paulimer.UnitaryOpcode.Hadamard, [0])
Expand All @@ -32,7 +37,8 @@ sim.measure(paulimer.SparsePauli("Z0"))
## Features

- **DensePauli / SparsePauli** - Pauli operators with phase tracking and multiplication
- **CliffordUnitary** - Clifford gates with conjugation and composition
- **CliffordUnitary** - Clifford gates with conjugation, composition, and decomposition into `π/4`
Pauli exponents (`to_transvections`, `to_transvections_minimal`)
- **PauliGroup** - Group operations including membership testing and factorization
- **Stabilizer Simulation** - Noiseless (OutcomeComplete, OutcomeFree, OutcomeSpecific) and noisy (Faulty) modes

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "a95d9b9a",
"metadata": {},
"source": [
"# Decomposing Cliffords into transvections (π/4 Pauli exponents)\n",
"\n",
"Every Clifford unitary can be written as an ordered product of **Clifford transvections** — the\n",
"`π/4` Pauli exponents $\\exp\\!\\big(i\\tfrac{\\pi}{4} P_v\\big)$. Conjugation by such an exponent acts on\n",
"Pauli operators as a **symplectic transvection**\n",
"\n",
"$$\n",
"x \\;\\mapsto\\; x + \\langle x, v\\rangle\\, v,\n",
"$$\n",
"\n",
"where $\\langle\\cdot,\\cdot\\rangle$ is the symplectic (commutation) form. `paulimer` exposes two\n",
"decompositions, following the transvection framework of\n",
"[arXiv:2102.11380](https://arxiv.org/abs/2102.11380) (Pllaha, Volanto & Tirkkonen,\n",
"*Decomposition of Clifford Gates*):\n",
"\n",
"- [`CliffordUnitary.to_transvections`](../paulimer.pyi) — a greedy reduction that always returns a\n",
" **linear** number of factors ($O(n)$),\n",
"- [`CliffordUnitary.to_transvections_minimal`](../paulimer.pyi) — the **strict minimum** number of\n",
" factors.\n",
"\n",
"Both reproduce the Clifford's **symplectic (conjugation) action** only; the Pauli-image signs and\n",
"the global phase are *not* preserved (the sign of a transvection does not change its symplectic\n",
"action)."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "9e2b876e",
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-05T18:09:15.236725Z",
"iopub.status.busy": "2026-07-05T18:09:15.236584Z",
"iopub.status.idle": "2026-07-05T18:09:15.240675Z",
"shell.execute_reply": "2026-07-05T18:09:15.239473Z"
}
},
"outputs": [],
"source": [
"import paulimer\n",
"from paulimer import CliffordUnitary, SparsePauli, DensePauli"
]
},
{
"cell_type": "markdown",
"id": "b42aee0d",
"metadata": {},
"source": [
"## A single transvection\n",
"\n",
"A `π/4` Pauli exponent *is* a Clifford transvection, so the simplest Cliffords decompose into a\n",
"single factor. The phase gate $S = \\exp(-i\\tfrac{\\pi}{4} Z)$ and the Hadamard are both single\n",
"transvections (recall the returned sign is irrelevant to the symplectic action):"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "9bbcb823",
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-05T18:09:15.242261Z",
"iopub.status.busy": "2026-07-05T18:09:15.242209Z",
"iopub.status.idle": "2026-07-05T18:09:15.244600Z",
"shell.execute_reply": "2026-07-05T18:09:15.243912Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"S -> [Z]\n",
"Hadamard -> [-𝑖Y]\n"
]
}
],
"source": [
"s_gate = CliffordUnitary.from_name(\"SqrtZ\", [0], qubit_count=1)\n",
"hadamard = CliffordUnitary.from_name(\"Hadamard\", [0], qubit_count=1)\n",
"\n",
"print(\"S ->\", s_gate.to_transvections_minimal())\n",
"print(\"Hadamard ->\", hadamard.to_transvections_minimal())"
]
},
{
"cell_type": "markdown",
"id": "c78b1510",
"metadata": {},
"source": [
"## Rebuilding a Clifford and checking the symplectic action\n",
"\n",
"Applying the returned transvections in order with\n",
"[`left_mul_pauli_exp`](../paulimer.pyi) reconstructs the original **symplectic matrix**. We compare\n",
"`symplectic_matrix` (not the full signed tableau, since signs and global phase are not tracked by\n",
"this decomposition).\n",
"\n",
"The minimal factor count is either $r$ or $r+1$, where the **residue rank**\n",
"\n",
"$$\n",
"r \\;=\\; 2n - \\dim \\operatorname{Fix}(F)\n",
"$$\n",
"\n",
"is the codimension of the space of Pauli operators fixed under conjugation. In `paulimer`,\n",
"$\\dim\\operatorname{Fix}(F)$ is the size of the Clifford's centralizer."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "ed66171a",
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-05T18:09:15.246310Z",
"iopub.status.busy": "2026-07-05T18:09:15.246264Z",
"iopub.status.idle": "2026-07-05T18:09:15.248826Z",
"shell.execute_reply": "2026-07-05T18:09:15.248499Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"residue rank r = 1\n",
"number of factors = 1\n",
"symplectic action ok: True\n"
]
}
],
"source": [
"def residue_rank(clifford):\n",
" return 2 * clifford.qubit_count - len(clifford.centralizer())\n",
"\n",
"\n",
"def rebuild(factors, qubit_count):\n",
" rebuilt = CliffordUnitary.identity(qubit_count)\n",
" for pauli in factors:\n",
" rebuilt.left_mul_pauli_exp(pauli)\n",
" return rebuilt\n",
"\n",
"\n",
"factors = s_gate.to_transvections_minimal()\n",
"rebuilt = rebuild(factors, s_gate.qubit_count)\n",
"print(\"residue rank r =\", residue_rank(s_gate))\n",
"print(\"number of factors =\", len(factors))\n",
"print(\"symplectic action ok:\", rebuilt.symplectic_matrix == s_gate.symplectic_matrix)"
]
},
{
"cell_type": "markdown",
"id": "eab0ecb9",
"metadata": {},
"source": [
"## Greedy versus minimal, and the $r+1$ case\n",
"\n",
"For many Cliffords the greedy and minimal decompositions agree, but not always. The CNOT gate has\n",
"residue rank $r = 2$ yet needs $r + 1 = 3$ transvections: its symplectic action is *hyperbolic*\n",
"($\\langle v, vF\\rangle = 0$ for all $v$), which forces one extra factor."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "00096f40",
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-05T18:09:15.250074Z",
"iopub.status.busy": "2026-07-05T18:09:15.249935Z",
"iopub.status.idle": "2026-07-05T18:09:15.252141Z",
"shell.execute_reply": "2026-07-05T18:09:15.251741Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"residue rank r = 2\n",
"greedy : [Z, ZX, IX] ( 3 factors )\n",
"minimal : [Z, ZX, IX] ( 3 factors )\n",
"symplectic action ok: True\n"
]
}
],
"source": [
"cnot = CliffordUnitary.from_name(\"ControlledX\", [0, 1], qubit_count=2)\n",
"\n",
"greedy = cnot.to_transvections()\n",
"minimal = cnot.to_transvections_minimal()\n",
"print(\"residue rank r =\", residue_rank(cnot))\n",
"print(\"greedy :\", greedy, \" (\", len(greedy), \"factors )\")\n",
"print(\"minimal :\", minimal, \" (\", len(minimal), \"factors )\")\n",
"print(\"symplectic action ok:\", rebuild(minimal, 2).symplectic_matrix == cnot.symplectic_matrix)"
]
},
{
"cell_type": "markdown",
"id": "6d7dfd52",
"metadata": {},
"source": [
"## A subtle case: non-hyperbolic maps that still need $r+1$\n",
"\n",
"The 2021 paper claims that *every* non-hyperbolic Clifford decomposes into exactly $r$ transvections.\n",
"That is **not correct over $\\mathbb{F}_2$**: some non-hyperbolic maps still require $r + 1$. The\n",
"smallest example already occurs on two qubits — the symplectic action built below (a product of the\n",
"transvections $X_0, X_1, X_0X_1, Z_0$) has residue rank $r = 3$, is non-hyperbolic, yet needs $4$\n",
"transvections. `to_transvections_minimal` returns the correct minimum. See\n",
"[`docs/transvection-minimality-correction.md`](../../../docs/transvection-minimality-correction.md)\n",
"for the full analysis and a machine-checked proof."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "3a68cc03",
"metadata": {
"execution": {
"iopub.execute_input": "2026-07-05T18:09:15.253271Z",
"iopub.status.busy": "2026-07-05T18:09:15.253223Z",
"iopub.status.idle": "2026-07-05T18:09:15.256499Z",
"shell.execute_reply": "2026-07-05T18:09:15.255166Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"residue rank r = 3\n",
"minimal factors = [IX, XX, -𝑖Y, X] ( 4 factors )\n",
"needs r + 1 : True\n",
"symplectic action ok: True\n"
]
}
],
"source": [
"example = CliffordUnitary.identity(2)\n",
"for pauli in [\"X0\", \"X1\", \"X0 X1\", \"Z0\"]:\n",
" example.left_mul_pauli_exp(SparsePauli(pauli))\n",
"\n",
"minimal = example.to_transvections_minimal()\n",
"r = residue_rank(example)\n",
"print(\"residue rank r =\", r)\n",
"print(\"minimal factors =\", minimal, \"(\", len(minimal), \"factors )\")\n",
"print(\"needs r + 1 :\", len(minimal) == r + 1)\n",
"print(\"symplectic action ok:\", rebuild(minimal, 2).symplectic_matrix == example.symplectic_matrix)"
]
},
{
"cell_type": "markdown",
"id": "992a2da1",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"- Clifford transvections are `π/4` Pauli exponents; `to_transvections` /\n",
" `to_transvections_minimal` decompose any Clifford into them, reproducing its symplectic action\n",
" with $O(n)$ factors.\n",
"- The minimal count is $r$ or $r + 1$, where $r = 2n - \\dim\\operatorname{Fix}(F)$.\n",
"- Only the symplectic action is reproduced — Pauli-image signs and the global phase are not.\n",
"\n",
"### References\n",
"\n",
"- T. Pllaha, K. Volanto, O. Tirkkonen, *Decomposition of Clifford Gates*, GLOBECOM 2021,\n",
" [arXiv:2102.11380](https://arxiv.org/abs/2102.11380).\n",
"- [`docs/transvection-minimality-correction.md`](../../../docs/transvection-minimality-correction.md)\n",
" — a correction to the paper's minimality claim, with a verified counterexample."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "paulimer",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
12 changes: 12 additions & 0 deletions paulimer/bindings/python/paulimer.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,18 @@ class CliffordUnitary:
"""
...

def to_transvections_minimal(self) -> list[SparsePauli]:
"""Decompose into a *minimal* ordered product of Clifford transvections (pi/4 Pauli exponents).

Returns Pauli operators ``[P_1, ..., P_k]`` such that applying ``exp(i pi/4 P_1)``, then
``exp(i pi/4 P_2)``, ..., then ``exp(i pi/4 P_k)`` reproduces this Clifford's symplectic
(conjugation) action, with ``k`` the minimal transvection count (``r`` or ``r + 1``, where
``r`` is the rank of the residue matrix). Pauli-image signs and the global phase are not
reproduced; :meth:`to_transvections` is the linear-time greedy variant, which may use more
factors.
"""
...

def centralizer(self) -> list[SparsePauli]:
"""Generators of the centralizer: Paulis fixed up to sign under conjugation."""
...
Expand Down
Loading