Skip to content

Commit

Permalink
Readd support for backends with directed basis gates to RB experiments (
Browse files Browse the repository at this point in the history
qiskit-community#960)

* Add tests for directed basis gates

* Readd support of backends with only directed 2q-basis
  • Loading branch information
itoko committed Nov 29, 2022
1 parent 0bebd84 commit dc8cb94
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 21 deletions.
83 changes: 62 additions & 21 deletions qiskit_experiments/library/randomized_benchmarking/rb_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from qiskit.pulse.instruction_schedule_map import CalibrationPublisher
from qiskit.quantum_info import Clifford
from qiskit.quantum_info.random import random_clifford
from qiskit.transpiler import CouplingMap
from qiskit_experiments.framework import BaseExperiment, Options
from qiskit_experiments.framework.restless_mixin import RestlessMixin
from .clifford_utils import (
Expand Down Expand Up @@ -194,29 +195,68 @@ def _sample_sequences(self) -> List[Sequence[SequenceElementType]]:
def _get_basis_gates(self) -> Optional[Tuple[str, ...]]:
"""Get sorted basis gates to use in basis transformation during circuit generation.
- Return None if this experiment is an RB with 3 or more qubits.
- Return None if no basis gates are supplied via ``backend`` or ``transpile_options``.
- Return None if all 2q-gates supported on the physical qubits of the backend are one-way
directed (e.g. cx(0, 1) is supported but cx(1, 0) is not supported).
In all those case when None are returned, basis transformation will be skipped in the
circuit generation step (i.e. :meth:`circuits`) and it will be done in the successive
transpilation step (i.e. :meth:`_transpiled_circuits`) that calls :func:`transpile`.
Returns:
Sorted basis gate names.
"""
basis_gates = self.transpile_options.get("basis_gates", None)
if not basis_gates and self.backend:
if isinstance(self.backend, BackendV2):
# Only the "global basis gates" are returned for v2 backend.
# Some non-global basis gates may be usable for some physical qubits. However,
# they are conservatively removed here because the basis gates are agnostic to
# the direction of each gate.
basis_gates = self.backend.operation_names
non_globals = self.backend.target.get_non_global_operation_names(
strict_direction=True
)
if non_globals:
basis_gates = set(basis_gates) - set(non_globals)
else:
basis_gates = self.backend.configuration().basis_gates

if basis_gates is not None:
basis_gates = tuple(sorted(basis_gates))

return basis_gates
# 3 or more qubits case: Return None (skip basis transformation in circuit generation)
if self.num_qubits > 2:
return None

# 1 qubit case: Return all basis gates (or None if no basis gates are supplied)
if self.num_qubits == 1:
basis_gates = self.transpile_options.get("basis_gates", None)
if not basis_gates and self.backend:
if isinstance(self.backend, BackendV2):
basis_gates = self.backend.operation_names
elif isinstance(self.backend, BackendV1):
basis_gates = self.backend.configuration().basis_gates
return tuple(sorted(basis_gates)) if basis_gates else None

# 2 qubits case: Return all basis gates except for one-way directed 2q-gates.
# Return None if there is no bi-directed 2q-gates in basis gates.
if self.num_qubits == 2:
basis_gates = self.transpile_options.get("basis_gates", None)
if not basis_gates and self.backend:
if isinstance(self.backend, BackendV2) and self.backend.target:
# prepare to collect one-way directed 2q-gates
supported_2q_instructions = defaultdict(list) # key: op_name, value: qargs list
for op_name, qargs_dic in self.backend.target.items():
for qargs in qargs_dic:
if self.backend.target.operation_from_name(op_name).num_qubits != 2:
continue
if qargs is None: # the 2q-gate is not available on the qargs
supported_2q_instructions[op_name] = []
elif set(qargs).issubset(self.physical_qubits):
reduced_qargs = tuple(self.physical_qubits.index(q) for q in qargs)
supported_2q_instructions[op_name].append(reduced_qargs)
# collect one-way directed 2q-gates
directed_basis_2q_gates = set() # one-way directed 2q-gates
for op_name, qargs_list in supported_2q_instructions.items():
if len(qargs_list) == 1:
directed_basis_2q_gates.add(op_name)
if len(directed_basis_2q_gates) == len(supported_2q_instructions):
return None # supported 2q-gates are all directed
# all basis gates except for one-way directed 2q-gates
basis_gates = set(self.backend.operation_names) - directed_basis_2q_gates
elif isinstance(self.backend, BackendV1):
coupling_map = self.backend.configuration().coupling_map
if coupling_map:
coupling = CouplingMap(coupling_map).reduce(self.physical_qubits)
if len(coupling.get_edges()) == 1:
return None # supported 2q-gates are all directed
basis_gates = self.backend.configuration().basis_gates
return tuple(sorted(basis_gates)) if basis_gates else None

return None

def _sequences_to_circuits(
self, sequences: List[Sequence[SequenceElementType]]
Expand Down Expand Up @@ -316,7 +356,8 @@ def _transpiled_circuits(self) -> List[QuantumCircuit]:
)
or self.transpile_options.get("optimization_level", 0) != 0
)
if self.num_qubits > 2 or has_custom_transpile_option:
has_no_undirected_2q_basis = self._get_basis_gates() is None
if self.num_qubits > 2 or has_custom_transpile_option or has_no_undirected_2q_basis:
transpiled = super()._transpiled_circuits()
else:
transpiled = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,20 @@ def test_calibrations_via_custom_backend(self):
self.assertTrue(qc.has_calibration_for((SXGate(), [qc.qubits[q] for q in qubits], [])))
self.assertEqual(qc.calibrations["sx"][(qubits, tuple())], my_sched)

def test_backend_with_directed_basis_gates(self):
"""Test if correct circuits are generated from backend with directed basis gates."""
my_backend = copy.deepcopy(self.backend)
del my_backend.target["cx"][(1, 2)] # make cx on {1, 2} one-sided

exp = rb.StandardRB(qubits=(1, 2), lengths=[3], num_samples=4, backend=my_backend)
transpiled = exp._transpiled_circuits()
for qc in transpiled:
self.assertTrue(qc.count_ops().get("cx", 0) > 0)
expected_qubits = (qc.qubits[2], qc.qubits[1])
for inst in qc:
if inst.operation.name == "cx":
self.assertEqual(inst.qubits, expected_qubits)


@ddt
class TestInterleavedRB(QiskitExperimentsTestCase, RBTestMixin):
Expand Down Expand Up @@ -392,6 +406,26 @@ def test_interleaved_circuit_is_decomposed(self):
self.assertTrue(all(not inst.operation.name.startswith("circuit") for inst in qc))
self.assertTrue(all(not inst.operation.name.startswith("Clifford") for inst in qc))

def test_interleaving_cnot_gate_with_non_supported_direction(self):
"""Test if cx(0, 1) can be interleaved for backend that support only cx(1, 0)."""
my_backend = FakeManilaV2()
del my_backend.target["cx"][(0, 1)] # make support only cx(1, 0)

exp = rb.InterleavedRB(
interleaved_element=CXGate(),
qubits=(0, 1),
lengths=[3],
num_samples=4,
backend=my_backend,
)
transpiled = exp._transpiled_circuits()
for qc in transpiled:
self.assertTrue(qc.count_ops().get("cx", 0) > 0)
expected_qubits = (qc.qubits[1], qc.qubits[0])
for inst in qc:
if inst.operation.name == "cx":
self.assertEqual(inst.qubits, expected_qubits)


class RBRunTestCase(QiskitExperimentsTestCase, RBTestMixin):
"""Base test case for running RB experiments defining a common noise model."""
Expand Down

0 comments on commit dc8cb94

Please sign in to comment.