Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b86c91b
Merge pull request #2 from microsoft/main
marcusps Jun 14, 2026
bf45784
Merge branch 'microsoft:main' into main
marcusps Jun 26, 2026
f64956f
Merge branch 'microsoft:main' into main
marcusps Jun 26, 2026
aa8e258
Add Clifford -> transvection decomposition (arXiv:2102.11380)
marcusps Jul 3, 2026
666eae1
Add minimal Clifford -> transvection decomposition (arXiv:2102.11380)
marcusps Jul 4, 2026
5efbc7f
docs: correction note for arXiv:2102.11380 minimal transvection claim
marcusps Jul 5, 2026
971f6e7
docs: document and add examples for the transvection decomposition
marcusps Jul 5, 2026
362a4f2
Fix transvection span overflow found in code review
Jul 17, 2026
460e923
style(paulimer): apply cargo fmt to transvection decomposition files
marcusps Jul 18, 2026
c28610f
Merge branch 'masilv/clifford-to-pauli-exp-unphased' into masilv/clif…
marcusps Jul 18, 2026
d24db29
style(paulimer): apply cargo fmt to transvection decomposition files
marcusps Jul 18, 2026
6b73a84
Merge branch 'microsoft:main' into masilv/clifford-to-pauli-exp-unphased
marcusps Jul 24, 2026
79c66e3
Rewrite transvection proptests with a Gate strategy; derive qubit count
Jul 24, 2026
99bed33
Merge branch 'microsoft:main' into masilv/clifford-to-transvections-m…
marcusps Jul 24, 2026
ea0e631
Merge clifford-to-pauli-exp-unphased into clifford-to-transvections-m…
Jul 24, 2026
a52f5db
docs(paulimer): correct transvection minimality claims
marcusps Aug 7, 2026
d322e25
docs(paulimer): remove unavailable Lean proof links
marcusps Aug 7, 2026
164f016
Merge branch 'microsoft:main' into masilv/clifford-to-transvections-m…
marcusps Aug 11, 2026
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
}
26 changes: 26 additions & 0 deletions paulimer/bindings/python/paulimer.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,32 @@ class CliffordUnitary:
"""Get the symplectic matrix representation."""
...

def to_transvections(self) -> list[SparsePauli]:
"""Decompose into an 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, using a linear number of factors. Pauli-image signs and the global
phase are not reproduced.
"""
...

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."""
...

def qubits(self) -> slice:
"""Return a slice representing the qubit indices."""
...
Expand Down
Loading
Loading