Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(notebooks): add Pauli twirling guide #19

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
363 changes: 363 additions & 0 deletions docs/notebooks/twirling.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,363 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Abdullah Ash Saki** \n",
"Enabling Technologies Researcher @ IBM Quantum \n",
"saki@ibm.com\n",
"\n",
"**Pedro Rivero** \n",
"Technical Lead @ IBM Quantum \n",
"pedro.rivero@ibm.com"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Pauli Twirling"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## What is twirling?\n",
"```\n",
"MISSING SCIENTIFIC/THEORETICAL EXPLANATION\n",
"```\n",
"Let us begin by introducing an auxiliary class `PauliTwirl`, to represent a single Pauli twirl:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from numpy import exp, ndarray\n",
"from numpy.typing import ArrayLike\n",
"from qiskit.circuit import QuantumRegister\n",
"from qiskit.circuit.library import PauliGate\n",
"from qiskit.dagcircuit import DAGCircuit, DAGOpNode\n",
"\n",
"\n",
"class PauliTwirl:\n",
" \"\"\"Pauli twirl.\n",
"\n",
" This class holds information about a Pauli twirl, independently\n",
" of what operation it is later applied to. Therefore, applying\n",
" the represented twirl to an arbitrary operation has no guaranty\n",
" of preserving such operation's original action.\n",
"\n",
" Args:\n",
" pre: Pauli gate to apply before the twirled operation.\n",
" post: Pauli gate to apply after the twirled operation.\n",
" phase: global phase induced by the twirling.\n",
" \"\"\"\n",
"\n",
" def __init__(self, pre: str, post: str, phase: float = 0.0) -> None:\n",
" self.pre = PauliGate(pre)\n",
" self.post = PauliGate(post)\n",
" self.phase = float(phase)\n",
" if self.pre.num_qubits != self.post.num_qubits:\n",
" raise ValueError(\n",
" \"Twirling pre and post operations don't apply to the same number of qubits.\"\n",
" )\n",
"\n",
" @property\n",
" def num_qubits(self) -> int:\n",
" \"\"\"Number of qubits that the twirl applies to.\"\"\"\n",
" return self.pre.num_qubits\n",
"\n",
" def apply_to_node(self, node: DAGOpNode) -> DAGCircuit:\n",
" \"\"\"Apply twirl to input DAG operation node.\"\"\"\n",
" dag = DAGCircuit()\n",
" qubits = QuantumRegister(self.num_qubits)\n",
" dag.add_qreg(qubits)\n",
" dag.apply_operation_back(self.pre, qubits)\n",
" dag.apply_operation_back(node.op, qubits)\n",
" dag.apply_operation_back(self.post, qubits)\n",
" dag.global_phase += self.phase\n",
" return dag\n",
"\n",
" def apply_to_unitary(self, unitary: ArrayLike) -> ndarray:\n",
" \"\"\"Apply twirl to input unitary.\"\"\"\n",
" pre = self.pre.to_matrix()\n",
" post = self.post.to_matrix()\n",
" phase_factor = exp(1j * self.phase)\n",
" return (post @ unitary @ pre) * phase_factor\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Operation-preserving twirls\n",
"```\n",
"MISSING SCIENTIFIC/THEORETICAL EXPLANATION\n",
"```\n",
"Next we will create a helper function `generate_pauli_twirls` to compute all operation-preserving twirls for a given unitary matrix numerically:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from collections.abc import Iterator\n",
"from itertools import product\n",
"from numpy import allclose, angle, eye, isclose, ndarray\n",
"\n",
"\n",
"def generate_pauli_twirls(unitary: ndarray) -> Iterator[PauliTwirl]:\n",
" \"\"\"Generate operation-preserving Pauli twirls for input unitary.\n",
"\n",
" Args:\n",
" unitary: the unitary to compute twirls for.\n",
"\n",
" Yields:\n",
" Twirls preserving the unitary operation. Qubit order is given by the input.\n",
" \"\"\"\n",
" dimension = unitary.shape[0]\n",
" num_qubits = dimension.bit_length() - 1 # Note: dimension == 2**num_qubits\n",
" n_qubit_paulis = (\"\".join(pauli) for pauli in product(\"IXYZ\", repeat=num_qubits))\n",
" for pre, post in product(n_qubit_paulis, repeat=2):\n",
" twirl = PauliTwirl(pre, post, phase=0.0)\n",
" twirled = twirl.apply_to_unitary(unitary)\n",
" check = twirled.conj().T @ unitary\n",
" phase_factor = check[0, 0]\n",
" if not isclose(phase_factor, 0) and allclose(check / phase_factor, eye(dimension)):\n",
" yield PauliTwirl(pre=pre, post=post, phase=angle(phase_factor))\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Random twirling\n",
"```\n",
"MISSING SCIENTIFIC/THEORETICAL EXPLANATION\n",
"```\n",
"Finally, with these tools, we can create a simple [transpiler pass](https://docs.quantum.ibm.com/transpile/custom-transpiler-pass#transpiler-passes) `TwoQubitPauliTwirlPass` to apply Pauli twirling to an input `DAGCircuit`:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"from numpy.random import default_rng\n",
"from qiskit.circuit import Operation, Gate\n",
"from qiskit.dagcircuit import DAGCircuit\n",
"from qiskit.transpiler import TransformationPass\n",
"\n",
"\n",
"class TwoQubitPauliTwirlPass(TransformationPass):\n",
" \"\"\"Pauli twirl two-qubit gates in input circuit randomly.\n",
"\n",
" Both non-unitary and parametrized gates are not supported and will be skipped.\n",
"\n",
" Args:\n",
" seed: seed for random number generator.\n",
" \"\"\"\n",
"\n",
" def __init__(self, *, seed: int | None = None):\n",
" super().__init__()\n",
" self._rng = default_rng(seed)\n",
"\n",
" def run(self, dag: DAGCircuit):\n",
" \"\"\"Pauli twirl target gates randomly for input DAGCircuit inplace.\"\"\"\n",
" target_nodes = (node for node in dag.op_nodes() if self._is_target_op(node.op))\n",
" for node in target_nodes:\n",
" twirl = self._get_random_twirl(node.op)\n",
" twirl_dag = twirl.apply_to_node(node)\n",
" dag.substitute_node_with_dag(node, twirl_dag)\n",
" return dag\n",
"\n",
" def _is_target_op(self, op: Operation) -> bool:\n",
" \"\"\"Check whether operation should be twirled or not.\"\"\"\n",
" if op.num_qubits != 2:\n",
" return False # Note: Only twirl two-qubit gates\n",
" if not isinstance(op, Gate):\n",
" return False # Note: Skip non-gate nodes (e.g. barriers, measurements)\n",
" if op.is_parameterized():\n",
" return False # Note: Skip parametrized gates\n",
" return True\n",
"\n",
" def _get_random_twirl(self, gate: Gate) -> PauliTwirl:\n",
" \"\"\"Get random twirl for the input gate.\"\"\"\n",
" twirls = generate_pauli_twirls(gate.to_matrix())\n",
" return self._rng.choice(list(twirls))\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Usage\n",
"Using Qiskit's `PassManager` we can now run a simple example:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"global phase: π\n",
" ┌────────────┐ ┌────────────┐\n",
"q_0: ┤0 ├──■──┤0 ├\n",
" │ Pauli(ZX) │┌─┴─┐│ Pauli(YY) │\n",
"q_1: ┤1 ├┤ X ├┤1 ├\n",
" └────────────┘└───┘└────────────┘\n"
]
}
],
"source": [
"from qiskit import QuantumCircuit\n",
"from qiskit.transpiler import PassManager\n",
"\n",
"circuit = QuantumCircuit(2)\n",
"circuit.cx(0, 1)\n",
"\n",
"pass_manager = PassManager(TwoQubitPauliTwirlPass(seed=0))\n",
"twirled_circuit = pass_manager.run(circuit)\n",
"\n",
"print(twirled_circuit)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Which can be further decomposed into single-qubit paulis:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"global phase: π\n",
" ┌───┐ ┌───┐\n",
"q_0: ┤ X ├──■──┤ Y ├\n",
" ├───┤┌─┴─┐├───┤\n",
"q_1: ┤ Z ├┤ X ├┤ Y ├\n",
" └───┘└───┘└───┘\n"
]
}
],
"source": [
"print(twirled_circuit.decompose(\"pauli\"))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And we can see how the unitary is preserved:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Original: \n",
" [[1. 0. 0. 0.]\n",
" [0. 0. 0. 1.]\n",
" [0. 0. 1. 0.]\n",
" [0. 1. 0. 0.]]\n",
"Twirled: \n",
" [[ 1. -0. -0. -0.]\n",
" [-0. -0. -0. 1.]\n",
" [-0. -0. 1. -0.]\n",
" [-0. 1. -0. -0.]]\n"
]
}
],
"source": [
"from numpy import isclose, pi\n",
"from qiskit.circuit.library import CXGate\n",
"\n",
"twirl = PauliTwirl(\"ZX\", \"YY\", phase=pi)\n",
"\n",
"cx_unitary = CXGate().to_matrix()\n",
"twirled_unitary = twirl.apply_to_unitary(cx_unitary)\n",
"\n",
"print(\"Original: \\n\", cx_unitary.real)\n",
"print(\"Twirled: \\n\", twirled_unitary.real)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Mask (isclose): \n",
" [[ True True True True]\n",
" [ True True True True]\n",
" [ True True True True]\n",
" [ True True True True]]\n"
]
}
],
"source": [
"print(\"Mask (isclose): \\n\", isclose(twirled_unitary, cx_unitary))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## References\n",
"1. Wallman et al., _Noise tailoring for scalable quantum computation via randomized compiling_, [Phys. Rev. A 94, 052325](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.94.052325)\n",
"2. Minev, _A tutorial on tailoring quantum noise - Twirling 101_, [Online](https://www.zlatko-minev.com/blog/twirling)\n",
"3. ..."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "quantum-enablement",
"language": "python",
"name": "quantum-enablement"
},
"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.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Loading