Skip to content

Commit

Permalink
Refactor RB module for future extensions (#898)
Browse files Browse the repository at this point in the history
* Refactor RB module easier to handle non-Clifford RB

* Speed up by caching circuit instructions

* Support serialization of TwirlingGroup

* Add nobs for further speed-up

* Backe to support only Clifford RB

* Change to compute only the difference from previous sequence in no full sampling case

* Refactor implementation of InterleavedRB

* Fix a bug in lru_cache usage and function names

* Revert API changes in clifford_utils

* Apply suggestions from code review

Co-authored-by: Naoki Kanazawa <[email protected]>

* Updates based on reviewer comment

* Make full_sampling be an experiment option

* Stabilize a unittest

Co-authored-by: Naoki Kanazawa <[email protected]>

* Updates following reviewer's suggestions

* Fix a tiny bug in docstring

* Change a protected function to be private

* Update test/library/randomized_benchmarking/test_randomized_benchmarking.py

Co-authored-by: Naoki Kanazawa <[email protected]>

* tox -eblack

* Fix double barriers before measurement to single

Co-authored-by: Naoki Kanazawa <[email protected]>
  • Loading branch information
itoko and nkanazawa1989 authored Sep 12, 2022
1 parent df82248 commit 0359f4c
Show file tree
Hide file tree
Showing 4 changed files with 264 additions and 178 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,28 @@
Utilities for using the Clifford group in randomized benchmarking
"""

from typing import Optional, Union
from functools import lru_cache
from numbers import Integral
from typing import Optional, Union

from numpy.random import Generator, default_rng
from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit import Gate
from qiskit.circuit.library import SdgGate, HGate, SGate, SXdgGate

from qiskit.circuit import Gate, Instruction
from qiskit.circuit import QuantumCircuit, QuantumRegister
from qiskit.circuit.library import SdgGate, HGate, SGate
from qiskit.quantum_info import Clifford, random_clifford


@lru_cache(maxsize=None)
def _clifford_1q_int_to_instruction(num: Integral) -> Instruction:
return CliffordUtils.clifford_1_qubit_circuit(num).to_instruction()


@lru_cache(maxsize=11520)
def _clifford_2q_int_to_instruction(num: Integral) -> Instruction:
return CliffordUtils.clifford_2_qubit_circuit(num).to_instruction()


class VGate(Gate):
"""V Gate used in Clifford synthesis."""

Expand Down Expand Up @@ -65,17 +78,21 @@ class CliffordUtils:
(2, 2, 3, 3, 4, 4),
]

def clifford_1_qubit(self, num):
@classmethod
@lru_cache(maxsize=24)
def clifford_1_qubit(cls, num):
"""Return the 1-qubit clifford element corresponding to `num`
where `num` is between 0 and 23.
"""
return Clifford(self.clifford_1_qubit_circuit(num), validate=False)
return Clifford(cls.clifford_1_qubit_circuit(num), validate=False)

def clifford_2_qubit(self, num):
@classmethod
@lru_cache(maxsize=11520)
def clifford_2_qubit(cls, num):
"""Return the 2-qubit clifford element corresponding to `num`
where `num` is between 0 and 11519.
"""
return Clifford(self.clifford_2_qubit_circuit(num), validate=False)
return Clifford(cls.clifford_2_qubit_circuit(num), validate=False)

def random_cliffords(
self, num_qubits: int, size: int = 1, rng: Optional[Union[int, Generator]] = None
Expand Down Expand Up @@ -117,38 +134,38 @@ def random_clifford_circuits(
samples = rng.integers(11520, size=size)
return [self.clifford_2_qubit_circuit(i) for i in samples]

@classmethod
@lru_cache(maxsize=24)
def clifford_1_qubit_circuit(self, num):
def clifford_1_qubit_circuit(cls, num):
"""Return the 1-qubit clifford circuit corresponding to `num`
where `num` is between 0 and 23.
"""
# pylint: disable=unbalanced-tuple-unpacking
# This is safe since `_unpack_num` returns list the size of the sig
(i, j, p) = self._unpack_num(num, self.CLIFFORD_1_QUBIT_SIG)
qr = QuantumRegister(1)
qc = QuantumCircuit(qr)
unpacked = cls._unpack_num(num, (2, 3, 4))
i, j, p = unpacked[0], unpacked[1], unpacked[2]
qc = QuantumCircuit(1, name=f"Clifford-1Q({num})")
if i == 1:
qc.h(0)
if j == 1:
qc._append(SXdgGate(), [qr[0]], [])
qc.sxdg(0)
if j == 2:
qc._append(SGate(), [qr[0]], [])
qc.s(0)
if p == 1:
qc.x(0)
if p == 2:
qc.y(0)
if p == 3:
qc.z(0)

return qc

@classmethod
@lru_cache(maxsize=11520)
def clifford_2_qubit_circuit(self, num):
def clifford_2_qubit_circuit(cls, num):
"""Return the 2-qubit clifford circuit corresponding to `num`
where `num` is between 0 and 11519.
"""
vals = self._unpack_num_multi_sigs(num, self.CLIFFORD_2_QUBIT_SIGS)
qr = QuantumRegister(2)
qc = QuantumCircuit(qr)
vals = cls._unpack_num_multi_sigs(num, cls.CLIFFORD_2_QUBIT_SIGS)
qc = QuantumCircuit(2, name=f"Clifford-2Q({num})")
if vals[0] == 0 or vals[0] == 3:
(form, i0, i1, j0, j1, p0, p1) = vals
else:
Expand All @@ -172,15 +189,18 @@ def clifford_2_qubit_circuit(self, num):
if form == 3:
qc.cx(0, 1)
if form in (1, 2):
if k0 == 1:
qc._append(VGate(), [qr[0]], [])
if k0 == 2:
qc._append(WGate(), [qr[0]], [])
if k1 == 1:
qc._append(VGate(), [qr[1]], [])
if k1 == 2:
qc._append(VGate(), [qr[1]], [])
qc._append(VGate(), [qr[1]], [])
if k0 == 1: # V gate
qc.sdg(0)
qc.h(0)
if k0 == 2: # W gate
qc.h(0)
qc.s(0)
if k1 == 1: # V gate
qc.sdg(1)
qc.h(1)
if k1 == 2: # W gate
qc.h(1)
qc.s(1)
if p0 == 1:
qc.x(0)
if p0 == 2:
Expand All @@ -193,9 +213,11 @@ def clifford_2_qubit_circuit(self, num):
qc.y(1)
if p1 == 3:
qc.z(1)

return qc

def _unpack_num(self, num, sig):
@staticmethod
def _unpack_num(num, sig):
r"""Returns a tuple :math:`(a_1, \ldots, a_n)` where
:math:`0 \le a_i \le \sigma_i` where
sig=:math:`(\sigma_1, \ldots, \sigma_n)` and num is the sequential
Expand All @@ -207,7 +229,8 @@ def _unpack_num(self, num, sig):
num //= k
return res

def _unpack_num_multi_sigs(self, num, sigs):
@staticmethod
def _unpack_num_multi_sigs(num, sigs):
"""Returns the result of `_unpack_num` on one of the
signatures in `sigs`
"""
Expand All @@ -216,6 +239,6 @@ def _unpack_num_multi_sigs(self, num, sigs):
for k in sig:
sig_size *= k
if num < sig_size:
return [i] + self._unpack_num(num, sig)
return [i] + CliffordUtils._unpack_num(num, sig)
num -= sig_size
return None
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from qiskit.exceptions import QiskitError
from qiskit.providers.backend import Backend

from .rb_experiment import StandardRB
from .rb_experiment import StandardRB, SequenceElementType
from .interleaved_rb_analysis import InterleavedRBAnalysis


Expand Down Expand Up @@ -75,8 +75,20 @@ def __init__(
all lengths. If False for sample of lengths longer
sequences are constructed by appending additional
Clifford samples to shorter sequences.
Raises:
QiskitError: the interleaved_element is not convertible to Clifford object.
"""
self._set_interleaved_element(interleaved_element)
try:
self._interleaved_elem = Clifford(interleaved_element)
except QiskitError as err:
raise QiskitError(
f"Interleaved element {interleaved_element.name} could not be converted to Clifford."
) from err
# Convert interleaved element to operation
self._interleaved_op = interleaved_element
if not isinstance(interleaved_element, Instruction):
self._interleaved_op = interleaved_element.to_instruction()
super().__init__(
qubits,
lengths,
Expand All @@ -88,56 +100,44 @@ def __init__(
self.analysis = InterleavedRBAnalysis()
self.analysis.set_options(outcome="0" * self.num_qubits)

def _sample_circuits(self, lengths, rng):
circuits = []
for length in lengths if self._full_sampling else [lengths[-1]]:
elements = self._clifford_utils.random_clifford_circuits(self.num_qubits, length, rng)
element_lengths = [len(elements)] if self._full_sampling else lengths
std_circuits = self._generate_circuit(elements, element_lengths)
for circuit in std_circuits:
circuit.metadata["interleaved"] = False
circuits += std_circuits

int_elements = self._interleave(elements)
int_elements_lengths = [length * 2 for length in element_lengths]
int_circuits = self._generate_circuit(int_elements, int_elements_lengths)
for circuit in int_circuits:
circuit.metadata["interleaved"] = True
circuit.metadata["xval"] = circuit.metadata["xval"] // 2
circuits += int_circuits
return circuits

def _interleave(self, element_list: List) -> List:
"""Interleaving the interleaved element inside the element list.
Args:
element_list: The list of elements we add the interleaved element to.
def circuits(self) -> List[QuantumCircuit]:
"""Return a list of RB circuits.
Returns:
The new list with the element interleaved.
"""
new_element_list = []
for element in element_list:
new_element_list.append(element)
new_element_list.append(self._interleaved_element)
return new_element_list

def _set_interleaved_element(self, interleaved_element):
"""Handle the various types of the interleaved element
Args:
interleaved_element: The element to interleave
Raises:
QiskitError: if there is no known conversion of interleaved_element
to a Clifford group element
A list of :class:`QuantumCircuit`.
"""
try:
interleaved_element_op = Clifford(interleaved_element)
self._interleaved_element = (interleaved_element, interleaved_element_op)
except QiskitError as error:
raise QiskitError(
"Interleaved element {} could not be converted to Clifford element".format(
interleaved_element.name
)
) from error
# Build circuits of reference sequences
reference_sequences = self._sample_sequences()
reference_circuits = self._sequences_to_circuits(reference_sequences)
for circ, seq in zip(reference_circuits, reference_sequences):
circ.metadata = {
"experiment_type": self._type,
"xval": len(seq),
"group": "Clifford",
"physical_qubits": self.physical_qubits,
"interleaved": False,
}
# Build circuits of interleaved sequences
interleaved_sequences = []
for seq in reference_sequences:
new_seq = []
for elem in seq:
new_seq.append(elem)
new_seq.append(self._interleaved_elem)
interleaved_sequences.append(new_seq)
interleaved_circuits = self._sequences_to_circuits(interleaved_sequences)
for circ, seq in zip(interleaved_circuits, reference_sequences):
circ.metadata = {
"experiment_type": self._type,
"xval": len(seq), # set length of the reference sequence
"group": "Clifford",
"physical_qubits": self.physical_qubits,
"interleaved": True,
}
return reference_circuits + interleaved_circuits

def _to_instruction(self, elem: SequenceElementType) -> Instruction:
if elem is self._interleaved_elem:
return self._interleaved_op

return super()._to_instruction(elem)
Loading

0 comments on commit 0359f4c

Please sign in to comment.