Source code for qiskit_research.utils.pauli_twirling

# (C) Copyright IBM 2022.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Pauli twirling."""

from typing import Any, Iterable, Optional

import numpy as np
from qiskit.circuit import QuantumRegister
from qiskit.circuit.library import (
    IGate,
    XGate,
    YGate,
    ZGate,
)
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_research.utils.pulse_scaling import BASIS_GATES

I = IGate()
X = XGate()
Y = YGate()
Z = ZGate()

# this list consists of the 2-qubit rotation gates
TWO_QUBIT_PAULI_GENERATORS = {
    "rxx": Pauli("XX"),
    "ryy": Pauli("YY"),
    "rzx": Pauli("XZ"),
    "rzz": Pauli("ZZ"),
    "secr": Pauli("XZ"),
}

# 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)),
    ),
}


def parse_random_seed(seed: Any) -> np.random.Generator:
    """Parse a random number generator seed and return a Generator."""
    if isinstance(seed, np.random.Generator):
        return seed
    return np.random.default_rng(seed)


[docs] class PauliTwirl(TransformationPass): """Add Pauli twirls.""" def __init__( self, gates_to_twirl: Optional[Iterable[str]] = None, seed: Any = None, ): """ Args: gates_to_twirl: Names of gates to twirl. The default behavior is to twirl all supported gates. seed: Seed for the pseudorandom number generator. """ if gates_to_twirl is None: gates_to_twirl = TWIRL_GATES.keys() | TWO_QUBIT_PAULI_GENERATORS.keys() self.gates_to_twirl = gates_to_twirl self.rng = parse_random_seed(seed) super().__init__()
[docs] def run( self, dag: DAGCircuit, ) -> DAGCircuit: for run in dag.collect_runs(list(self.gates_to_twirl)): for node in run: if node.op.name in TWO_QUBIT_PAULI_GENERATORS: mini_dag = DAGCircuit() q0, q1 = node.qargs mini_dag.add_qubits([q0, q1]) theta = node.op.params[0] this_pauli = Pauli( self.rng.choice(pauli_basis(2).to_labels()) ).to_instruction() if TWO_QUBIT_PAULI_GENERATORS[node.op.name].anticommutes( this_pauli ): theta *= -1 new_op = node.op.copy() new_op.params[0] = theta mini_dag.apply_operation_back(this_pauli, [q0, q1]) mini_dag.apply_operation_back(new_op, [q0, q1]) if node.op.name == "secr": mini_dag.apply_operation_back(X, [q0]) mini_dag.apply_operation_back(this_pauli, [q0, q1]) if node.op.name == "secr": mini_dag.apply_operation_back(X, [q0]) dag.substitute_node_with_dag(node, mini_dag, wires=[q0, q1]) elif node.op.name in TWIRL_GATES: twirl_gates = TWIRL_GATES[node.op.name] (before0, before1), (after0, after1) = twirl_gates[ self.rng.integers(len(twirl_gates)) ] mini_dag = DAGCircuit() register = QuantumRegister(2) mini_dag.add_qreg(register) mini_dag.apply_operation_back(before0, [register[0]]) mini_dag.apply_operation_back(before1, [register[1]]) mini_dag.apply_operation_back(node.op, [register[0], register[1]]) mini_dag.apply_operation_back(after0, [register[0]]) mini_dag.apply_operation_back(after1, [register[1]]) dag.substitute_node_with_dag(node, mini_dag) else: raise TypeError(f"Unknown how to twirl Instruction {node.op}.") return dag
[docs] def pauli_transpilation_passes() -> Iterable[BasePass]: "Yield simple transpilation steps after addition of Pauli gates." yield Optimize1qGatesDecomposition(BASIS_GATES) yield CXCancellation()