Skip to content

Commit

Permalink
Merge pull request #121 from d-bharadwaj/add-more-twirl-gates
Browse files Browse the repository at this point in the history
Extended the gate set for pauli twirling
  • Loading branch information
kevinsung authored Oct 31, 2024
2 parents 1047612 + d16dff9 commit e341400
Show file tree
Hide file tree
Showing 2 changed files with 262 additions and 21 deletions.
128 changes: 108 additions & 20 deletions qiskit_research/utils/pauli_twirling.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,56 @@
"""Pauli twirling."""

from typing import Any, Iterable, Optional
import itertools
import cmath

import numpy as np
from qiskit.circuit import QuantumRegister
from qiskit.circuit import QuantumRegister, QuantumCircuit
from qiskit.circuit.library import (
IGate,
XGate,
YGate,
ZGate,
CXGate,
CYGate,
CZGate,
CHGate,
CSGate,
DCXGate,
CSXGate,
CSdgGate,
ECRGate,
iSwapGate,
SwapGate,
)
from qiskit.dagcircuit import DAGCircuit
from qiskit.transpiler.basepasses import BasePass, TransformationPass
from qiskit.transpiler.passes import (
CXCancellation,
Optimize1qGatesDecomposition,
)
from qiskit.quantum_info import Pauli, pauli_basis
from qiskit.quantum_info import Pauli, Operator, pauli_basis
from qiskit_research.utils.pulse_scaling import BASIS_GATES

# Single qubit Pauli gates
I = IGate()
X = XGate()
Y = YGate()
Z = ZGate()

# 2Q entangling gates
CX = CXGate() # cnot; controlled-X
CY = CYGate() # controlled-Y
CZ = CZGate() # controlled-Z
CH = CHGate() # controlled-Hadamard
CS = CSGate() # controlled-S
DCX = DCXGate() # double cnot
CSX = CSXGate() # controlled sqrt X
CSdg = CSdgGate() # controlled S^dagger
ECR = ECRGate() # echoed cross-resonance
Swap = SwapGate() # swap
iSwap = iSwapGate() # imaginary swap

# this list consists of the 2-qubit rotation gates
TWO_QUBIT_PAULI_GENERATORS = {
"rxx": Pauli("XX"),
Expand All @@ -43,31 +70,92 @@
"secr": Pauli("XZ"),
}


def match_global_phase(a, b):
"""Phase the given arrays so that their phases match at one entry.
Args:
a: A Numpy array.
b: Another Numpy array.
Returns:
A pair of arrays (a', b') that are equal if and only if a == b * exp(i phi)
for some real number phi.
"""
if a.shape != b.shape:
return a, b
# use the largest entry of one of the matrices to maximize precision
index = max(np.ndindex(*a.shape), key=lambda i: abs(b[i]))
phase_a = cmath.phase(a[index])
phase_b = cmath.phase(b[index])
return a * cmath.rect(1, -phase_a), b * cmath.rect(1, -phase_b)


def allclose_up_to_global_phase(a, b, rtol=1e-05, atol=1e-08, equal_nan=False):
"""Check if two operators are close up to a global phase."""
# Phase both operators to match their phases
phased_op1, phased_op2 = match_global_phase(a, b)
return np.allclose(phased_op1, phased_op2, rtol, atol, equal_nan)


def create_pauli_twirling_sets(two_qubit_gate):
"""Generate the Pauli twirling sets for a given 2Q gate.
Sets are ordered such that gate[0] and gate[1] are pre-rotations
applied to control and target, respectively. gate[2] and gate[3]
are post-rotations for control and target, respectively.
Parameters:
two_qubit_gate (Gate): Input two-qubit gate
Returns:
tuple: Tuple of all twirling gate sets
"""

target_unitary = np.array(two_qubit_gate)
twirling_sets = []

# Generate combinations of 4 gates from the operator list
for gates in itertools.product(itertools.product([I, X, Y, Z], repeat=2), repeat=2):
qc = _build_twirl_circuit(gates, two_qubit_gate)
qc_array = Operator.from_circuit(qc).to_matrix()
if allclose_up_to_global_phase(qc_array, target_unitary):
twirling_sets.append(gates)

return tuple(twirling_sets)


def _build_twirl_circuit(gates, two_qubit_gate):
"""Build the twirled quantum circuit with specified gates."""
qc = QuantumCircuit(2)

qc.append(gates[0][0], [0])
qc.append(gates[0][1], [1])
qc.append(two_qubit_gate, [0, 1])
qc.append(gates[1][0], [0])
qc.append(gates[1][1], [1])

return qc


# this dictionary stores the twirl sets for each supported gate
# each key is the name of a supported gate
# each value is a tuple that represents the twirl set for the gate
# the twirl set is a list of (before, after) pairs describing twirl gates
# "before" and "after" are tuples of single-qubit gates to be applied
# before and after the gate to be twirled
TWIRL_GATES = {
"cx": (
((I, I), (I, I)),
((I, X), (I, X)),
((I, Y), (Z, Y)),
((I, Z), (Z, Z)),
((X, I), (X, X)),
((X, X), (X, I)),
((X, Y), (Y, Z)),
((X, Z), (Y, Y)),
((Y, I), (Y, X)),
((Y, X), (Y, I)),
((Y, Y), (X, Z)),
((Y, Z), (X, Y)),
((Z, I), (Z, I)),
((Z, X), (Z, X)),
((Z, Y), (I, Y)),
((Z, Z), (I, Z)),
),
"cx": create_pauli_twirling_sets(CX),
"cy": create_pauli_twirling_sets(CY),
"cz": create_pauli_twirling_sets(CZ),
"ch": create_pauli_twirling_sets(CH),
"cs": create_pauli_twirling_sets(CS),
"dcx": create_pauli_twirling_sets(DCX),
"csx": create_pauli_twirling_sets(CSX),
"csdg": create_pauli_twirling_sets(CSdg),
"ecr": create_pauli_twirling_sets(ECR),
"swap": create_pauli_twirling_sets(Swap),
"iswap": create_pauli_twirling_sets(iSwap),
}


Expand Down
155 changes: 154 additions & 1 deletion test/utils/test_pauli_twirling.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,19 @@

import numpy as np
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.circuit.library import CXGate
from qiskit.circuit.library import (
CXGate,
CYGate,
CZGate,
CHGate,
CSGate,
DCXGate,
CSXGate,
CSdgGate,
ECRGate,
iSwapGate,
SwapGate,
)
from qiskit.quantum_info import Operator
from qiskit_research.utils.convenience import add_pauli_twirls
from qiskit_research.utils.gates import SECRGate
Expand All @@ -27,6 +39,7 @@ class TestPauliTwirling(unittest.TestCase):
def test_twirl_gates_cnot(self):
"""Test twirling CNOT."""
twirl_gates = TWIRL_GATES["cx"]
self.assertEqual(len(twirl_gates), 16)
operator = Operator(CXGate())
for (a, b), (c, d) in twirl_gates:
circuit = QuantumCircuit(2)
Expand All @@ -37,6 +50,146 @@ def test_twirl_gates_cnot(self):
circuit.append(d, [1])
self.assertTrue(Operator(circuit).equiv(operator))

def test_twirl_gates_cy(self):
"""Test twirling CY"""
twirl_gates = TWIRL_GATES["cy"]
self.assertEqual(len(twirl_gates), 16)
operator = Operator(CYGate())
for (a, b), (c, d) in twirl_gates:
circuit = QuantumCircuit(2)
circuit.append(a, [0])
circuit.append(b, [1])
circuit.append(CYGate(), [0, 1])
circuit.append(c, [0])
circuit.append(d, [1])
self.assertTrue(Operator(circuit).equiv(operator))

def test_twirl_gates_cz(self):
"""Test twirling CZ."""
twirl_gates = TWIRL_GATES["cz"]
self.assertEqual(len(twirl_gates), 16)
operator = Operator(CZGate())
for (a, b), (c, d) in twirl_gates:
circuit = QuantumCircuit(2)
circuit.append(a, [0])
circuit.append(b, [1])
circuit.append(CZGate(), [0, 1])
circuit.append(c, [0])
circuit.append(d, [1])
self.assertTrue(Operator(circuit).equiv(operator))

def test_twirl_gates_ch(self):
"""Test twirling CH."""
twirl_gates = TWIRL_GATES["ch"]
self.assertEqual(len(twirl_gates), 4)
operator = Operator(CHGate())
for (a, b), (c, d) in twirl_gates:
circuit = QuantumCircuit(2)
circuit.append(a, [0])
circuit.append(b, [1])
circuit.append(CHGate(), [0, 1])
circuit.append(c, [0])
circuit.append(d, [1])
self.assertTrue(Operator(circuit).equiv(operator))

def test_twirl_gates_cs(self):
"""Test twirling CS."""
twirl_gates = TWIRL_GATES["cs"]
self.assertEqual(len(twirl_gates), 4)
operator = Operator(CSGate())
for (a, b), (c, d) in twirl_gates:
circuit = QuantumCircuit(2)
circuit.append(a, [0])
circuit.append(b, [1])
circuit.append(CSGate(), [0, 1])
circuit.append(c, [0])
circuit.append(d, [1])
self.assertTrue(Operator(circuit).equiv(operator))

def test_twirl_gates_dcx(self):
"""Test twirling DCX."""
twirl_gates = TWIRL_GATES["dcx"]
self.assertEqual(len(twirl_gates), 16)
operator = Operator(DCXGate())
for (a, b), (c, d) in twirl_gates:
circuit = QuantumCircuit(2)
circuit.append(a, [0])
circuit.append(b, [1])
circuit.append(DCXGate(), [0, 1])
circuit.append(c, [0])
circuit.append(d, [1])
self.assertTrue(Operator(circuit).equiv(operator))

def test_twirl_gates_csx(self):
"""Test twirling CSX."""
twirl_gates = TWIRL_GATES["csx"]
self.assertEqual(len(twirl_gates), 4)
operator = Operator(CSXGate())
for (a, b), (c, d) in twirl_gates:
circuit = QuantumCircuit(2)
circuit.append(a, [0])
circuit.append(b, [1])
circuit.append(CSXGate(), [0, 1])
circuit.append(c, [0])
circuit.append(d, [1])
self.assertTrue(Operator(circuit).equiv(operator))

def test_twirl_gates_csdg(self):
"""Test twirling CSdg."""
twirl_gates = TWIRL_GATES["csdg"]
self.assertEqual(len(twirl_gates), 4)
operator = Operator(CSdgGate())
for (a, b), (c, d) in twirl_gates:
circuit = QuantumCircuit(2)
circuit.append(a, [0])
circuit.append(b, [1])
circuit.append(CSdgGate(), [0, 1])
circuit.append(c, [0])
circuit.append(d, [1])
self.assertTrue(Operator(circuit).equiv(operator))

def test_twirl_gates_ecr(self):
"""Test twirling ECR."""
twirl_gates = TWIRL_GATES["ecr"]
self.assertEqual(len(twirl_gates), 16)
operator = Operator(ECRGate())
for (a, b), (c, d) in twirl_gates:
circuit = QuantumCircuit(2)
circuit.append(a, [0])
circuit.append(b, [1])
circuit.append(ECRGate(), [0, 1])
circuit.append(c, [0])
circuit.append(d, [1])
self.assertTrue(Operator(circuit).equiv(operator))

def test_twirl_gates_swap(self):
"""Test twirling Swap."""
twirl_gates = TWIRL_GATES["swap"]
self.assertEqual(len(twirl_gates), 16)
operator = Operator(SwapGate())
for (a, b), (c, d) in twirl_gates:
circuit = QuantumCircuit(2)
circuit.append(a, [0])
circuit.append(b, [1])
circuit.append(SwapGate(), [0, 1])
circuit.append(c, [0])
circuit.append(d, [1])
self.assertTrue(Operator(circuit).equiv(operator))

def test_twirl_gates_iswap(self):
"""Test twirling iSwap."""
twirl_gates = TWIRL_GATES["iswap"]
self.assertEqual(len(twirl_gates), 16)
operator = Operator(iSwapGate())
for (a, b), (c, d) in twirl_gates:
circuit = QuantumCircuit(2)
circuit.append(a, [0])
circuit.append(b, [1])
circuit.append(iSwapGate(), [0, 1])
circuit.append(c, [0])
circuit.append(d, [1])
self.assertTrue(Operator(circuit).equiv(operator))

def test_add_pauli_twirls(self):
"""Test adding Pauli twirls."""
rng = np.random.default_rng()
Expand Down

0 comments on commit e341400

Please sign in to comment.