Fix transpile() for control flow operations with a Target/BackendV2 (#8852)

* Fix transpile() for control flow operations with a Target/BackendV2

This commit fixes an issue when compiling with circuits that have
control flow operations and are running transpile() with a BackendV2
instance or a custom Target. In these cases the transpile() operation
would fail to run because the Target didn't have a provision to
recognize a global variable width operation as part of the target.
Previously all operations in the target needed to have an instance of an
Operation so that the parameters and number of qubits could be verified.
Each of these operation instances would be assigned a unique name. But,
for control flow operations they're defined over a variable number of
qubits and all have the same name (this is a similar problem for gates
like mcx too). Not being able to fully represent the control flow
operations in a target was preventing running transpile() on a circuit
with control flow. This commit fixes this by adding support to the
target to represent globally defined operations by passing the class
into Target.add_instruction instead of an instance of that class. When
the class is received the Target class will treat that operation
name and class as always being valid for the target for all qubits and
parameters. This can then be used to represent control flow operations
in the target and run transpile() with control flow operations.

Fixes #8824

* Simplify test slightly

* Add release note

* Add coupling map target test

* Raise if instruction class and properties are both set

* Only return global gates if they exist

* Change UserWarning to a comment

* Revert change to instructions() getter

* Add release note about coupling map api change

* Fix lint and logic bug

* Add back nested while_loop to transpile() test
This commit is contained in:
Matthew Treinish 2022-10-12 18:40:59 -04:00 committed by GitHub
parent b3cf64f49c
commit ad07847081
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 633 additions and 20 deletions

View File

@ -22,12 +22,14 @@ from collections import defaultdict
import datetime
import io
import logging
import inspect
import retworkx as rx
from qiskit.circuit.parameter import Parameter
from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap
from qiskit.transpiler.coupling import CouplingMap
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.instruction_durations import InstructionDurations
from qiskit.transpiler.timing_constraints import TimingConstraints
@ -295,7 +297,11 @@ class Target(Mapping):
Args:
instruction (qiskit.circuit.Instruction): The operation object to add to the map. If it's
paramerterized any value of the parameter can be set
paramerterized any value of the parameter can be set. Optionally for variable width
instructions (such as control flow operations such as :class:`~.ForLoop` or
:class:`~MCXGate`) you can specify the class. If the class is specified than the
``name`` argument must be specified. When a class is used the gate is treated as global
and not having any properties set.
properties (dict): A dictionary of qarg entries to an
:class:`~qiskit.transpiler.InstructionProperties` object for that
instruction implementation on the backend. Properties are optional
@ -319,19 +325,37 @@ class Target(Mapping):
documentation for the :class:`~qiskit.transpiler.Target` class).
Raises:
AttributeError: If gate is already in map
TranspilerError: If an operation class is passed in for ``instruction`` and no name
is specified or ``properties`` is set.
"""
is_class = inspect.isclass(instruction)
if not is_class:
instruction_name = name or instruction.name
else:
# Invalid to have class input without a name with characters set "" is not a valid name
if not name:
raise TranspilerError(
"A name must be specified when defining a supported global operation by class"
)
if properties is not None:
raise TranspilerError(
"An instruction added globally by class can't have properties set."
)
instruction_name = name
if properties is None:
properties = {None: None}
instruction_name = name or instruction.name
if instruction_name in self._gate_map:
raise AttributeError("Instruction %s is already in the target" % instruction_name)
self._gate_name_map[instruction_name] = instruction
qargs_val = {}
for qarg in properties:
if qarg is not None:
self.num_qubits = max(self.num_qubits, max(qarg) + 1)
qargs_val[qarg] = properties[qarg]
self._qarg_gate_map[qarg].add(instruction_name)
if is_class:
qargs_val = {None: None}
else:
qargs_val = {}
for qarg in properties:
if qarg is not None:
self.num_qubits = max(self.num_qubits, max(qarg) + 1)
qargs_val[qarg] = properties[qarg]
self._qarg_gate_map[qarg].add(instruction_name)
self._gate_map[instruction_name] = qargs_val
self._coupling_graph = None
self._instruction_durations = None
@ -494,7 +518,9 @@ class Target(Mapping):
instruction (str): The instruction name to get the
:class:`~qiskit.circuit.Instruction` instance for
Returns:
qiskit.circuit.Instruction: The Instruction instance corresponding to the name
qiskit.circuit.Instruction: The Instruction instance corresponding to the
name. This also can also be the class for globally defined variable with
operations.
"""
return self._gate_name_map[instruction]
@ -507,14 +533,18 @@ class Target(Mapping):
instructions that apply to qubit 0.
Returns:
list: The list of :class:`~qiskit.circuit.Instruction` instances
that apply to the specified qarg.
that apply to the specified qarg. This may also be a class if
a variable width operation is globally defined.
Raises:
KeyError: If qargs is not in target
"""
if qargs not in self._qarg_gate_map:
raise KeyError(f"{qargs} not in target.")
return [self._gate_name_map[x] for x in self._qarg_gate_map[qargs]]
res = [self._gate_name_map[x] for x in self._qarg_gate_map[qargs]]
if None in self._qarg_gate_map:
res += [self._gate_name_map[x] for x in self._qarg_gate_map[None]]
return res
def operation_names_for_qargs(self, qargs):
"""Get the operation names for a specified qargs tuple
@ -609,6 +639,20 @@ class Target(Mapping):
qargs = tuple(qargs)
if operation_class is not None:
for op_name, obj in self._gate_name_map.items():
if inspect.isclass(obj):
if obj != operation_class:
continue
# If no qargs a operation class is supported
if qargs is None:
return True
# If qargs set then validate no duplicates and all indices are valid on device
elif all(qarg <= self.num_qubits for qarg in qargs) and len(set(qargs)) == len(
qargs
):
return True
else:
return False
if isinstance(obj, operation_class):
if parameters is not None:
if len(parameters) != len(obj.params):
@ -627,6 +671,23 @@ class Target(Mapping):
if operation_name in self._gate_map:
if parameters is not None:
obj = self._gate_name_map[operation_name]
if inspect.isclass(obj):
# The parameters argument was set and the operation_name specified is
# defined as a globally supported class in the target. This means
# there is no available validation (including whether the specified
# operation supports parameters), the returned value will not factor
# in the argument `parameters`,
# If no qargs a operation class is supported
if qargs is None:
return True
# If qargs set then validate no duplicates and all indices are valid on device
elif all(qarg <= self.num_qubits for qarg in qargs) and len(set(qargs)) == len(
qargs
):
return True
else:
return False
if len(parameters) != len(obj.params):
return False
for index, param in enumerate(parameters):
@ -643,9 +704,21 @@ class Target(Mapping):
if qargs in self._gate_map[operation_name]:
return True
if self._gate_map[operation_name] is None or None in self._gate_map[operation_name]:
return self._gate_name_map[operation_name].num_qubits == len(qargs) and all(
x < self.num_qubits for x in qargs
)
obj = self._gate_name_map[operation_name]
if inspect.isclass(obj):
if qargs is None:
return True
# If qargs set then validate no duplicates and all indices are valid on device
elif all(qarg <= self.num_qubits for qarg in qargs) and len(set(qargs)) == len(
qargs
):
return True
else:
return False
else:
return self._gate_name_map[operation_name].num_qubits == len(qargs) and all(
x < self.num_qubits for x in qargs
)
return False
@property
@ -661,7 +734,12 @@ class Target(Mapping):
@property
def instructions(self):
"""Get the list of tuples ``(:class:`~qiskit.circuit.Instruction`, (qargs))``
for the target"""
for the target
For globally defined variable width operations the tuple will be of the form
``(class, None)`` where class is the actual operation class that
is globally defined.
"""
return [
(self._gate_name_map[op], qarg) for op in self._gate_map for qarg in self._gate_map[op]
]
@ -711,6 +789,8 @@ class Target(Mapping):
self._coupling_graph.add_nodes_from([{} for _ in range(self.num_qubits)])
for gate, qarg_map in self._gate_map.items():
for qarg, properties in qarg_map.items():
if qarg is None:
continue
if len(qarg) == 1:
self._coupling_graph[qarg[0]] = properties
elif len(qarg) == 2:
@ -719,6 +799,8 @@ class Target(Mapping):
edge_data[gate] = properties
except rx.NoEdgeBetweenNodes:
self._coupling_graph.add_edge(*qarg, {gate: properties})
if self._coupling_graph.num_edges() == 0 and any(x is None for x in self._qarg_gate_map):
self._coupling_graph = None
def build_coupling_map(self, two_q_gate=None):
"""Get a :class:`~qiskit.transpiler.CouplingMap` from this target.
@ -761,9 +843,14 @@ class Target(Mapping):
if self._coupling_graph is None:
self._build_coupling_graph()
cmap = CouplingMap()
cmap.graph = self._coupling_graph
return cmap
# if there is no connectivity constraints in the coupling graph treat it as not
# existing and return
if self._coupling_graph is not None:
cmap = CouplingMap()
cmap.graph = self._coupling_graph
return cmap
else:
return None
@property
def physical_qubits(self):

View File

@ -0,0 +1,105 @@
---
features:
- |
Add support for representing an operation that has a variable width
to the :class:`~.Target` class. Previously, a :class:`~.Target` object
needed to have an instance of :class:`~Operation` defined for each
operation supported in the target. This was used for both validation
of arguments and parameters of the operation. However, for operations
that have a variable width this wasn't possible because each instance
of an :class:`~Operation` class can only have a fixed number of qubits.
For cases where a backend supports variable width operations the
instruction can be added with the class of the operation instead of an
instance. In such cases the operation will be treated as globally
supported on all qubits. For example, if building a target like::
from qiskit.transpiler import Target
ibm_target = Target()
i_props = {
(0,): InstructionProperties(duration=35.5e-9, error=0.000413),
(1,): InstructionProperties(duration=35.5e-9, error=0.000502),
(2,): InstructionProperties(duration=35.5e-9, error=0.0004003),
(3,): InstructionProperties(duration=35.5e-9, error=0.000614),
(4,): InstructionProperties(duration=35.5e-9, error=0.006149),
}
ibm_target.add_instruction(IGate(), i_props)
rz_props = {
(0,): InstructionProperties(duration=0, error=0),
(1,): InstructionProperties(duration=0, error=0),
(2,): InstructionProperties(duration=0, error=0),
(3,): InstructionProperties(duration=0, error=0),
(4,): InstructionProperties(duration=0, error=0),
}
ibm_target.add_instruction(RZGate(theta), rz_props)
sx_props = {
(0,): InstructionProperties(duration=35.5e-9, error=0.000413),
(1,): InstructionProperties(duration=35.5e-9, error=0.000502),
(2,): InstructionProperties(duration=35.5e-9, error=0.0004003),
(3,): InstructionProperties(duration=35.5e-9, error=0.000614),
(4,): InstructionProperties(duration=35.5e-9, error=0.006149),
}
ibm_target.add_instruction(SXGate(), sx_props)
x_props = {
(0,): InstructionProperties(duration=35.5e-9, error=0.000413),
(1,): InstructionProperties(duration=35.5e-9, error=0.000502),
(2,): InstructionProperties(duration=35.5e-9, error=0.0004003),
(3,): InstructionProperties(duration=35.5e-9, error=0.000614),
(4,): InstructionProperties(duration=35.5e-9, error=0.006149),
}
ibm_target.add_instruction(XGate(), x_props)
cx_props = {
(3, 4): InstructionProperties(duration=270.22e-9, error=0.00713),
(4, 3): InstructionProperties(duration=305.77e-9, error=0.00713),
(3, 1): InstructionProperties(duration=462.22e-9, error=0.00929),
(1, 3): InstructionProperties(duration=497.77e-9, error=0.00929),
(1, 2): InstructionProperties(duration=227.55e-9, error=0.00659),
(2, 1): InstructionProperties(duration=263.11e-9, error=0.00659),
(0, 1): InstructionProperties(duration=519.11e-9, error=0.01201),
(1, 0): InstructionProperties(duration=554.66e-9, error=0.01201),
}
ibm_target.add_instruction(CXGate(), cx_props)
measure_props = {
(0,): InstructionProperties(duration=5.813e-6, error=0.0751),
(1,): InstructionProperties(duration=5.813e-6, error=0.0225),
(2,): InstructionProperties(duration=5.813e-6, error=0.0146),
(3,): InstructionProperties(duration=5.813e-6, error=0.0215),
(4,): InstructionProperties(duration=5.813e-6, error=0.0333),
}
ibm_target.add_instruction(Measure(), measure_props)
ibm_target.add_instruction(IfElseOp, name="if_else")
ibm_target.add_instruction(ForLoopOp, name="for_loop")
ibm_target.add_instruction(WhileLoopOp, name="while_loop")
The :class:`~.IfElseOp`, :class:`~.ForLoopOp`, and :class:`~.WhileLoopOp`
operations are globally supported for any number of qubits. This is then
reflected by other calls in the :class:`~.Target` API such as
:meth:`~.Target.instruction_supported`::
ibm_target.instruction_supported(operation_class=WhileLoopOp, qargs=(0, 2, 3, 4))
ibm_target.instruction_supported('if_else', qargs=(0, 1))
both return ``True``.
upgrade:
- |
For :class:`~.Target` objects that only contain globally defined 2 qubit
operations without any connectivity constaints the return from the
:meth:`.Target.build_coupling_map` method will now return ``None`` instead
of a :class:`~.CouplingMap` object that contains ``num_qubits`` nodes
and no edges. This change was made to better reflect the actual
connectivity constraints of the :class:`~.Target` because in this case
there are no connectivity constraints on the backend being modeled by
the :class:`~.Target`, not a lack of connecitvity. If you desire the
previous behavior for any reason you can reproduce it by checking for a
``None`` and manually building a coupling map, for example::
from qiskit.transpiler import Target
from qiskit.circuit.library import CXGate
target = Target(num_qubits=3)
target.add_instruction(CXGate())
cmap = target.build_coupling_map()
if cmap is None:
cmap = CouplingMap()
for i in range(target.num_qubits):
cmap.add_physical_qubit(i)

View File

@ -32,7 +32,18 @@ from qiskit.circuit import Parameter, Gate, Qubit, Clbit
from qiskit.compiler import transpile
from qiskit.dagcircuit import DAGOutNode
from qiskit.converters import circuit_to_dag
from qiskit.circuit.library import CXGate, U3Gate, U2Gate, U1Gate, RXGate, RYGate, RZGate, UGate
from qiskit.circuit.library import (
CXGate,
U3Gate,
U2Gate,
U1Gate,
RXGate,
RYGate,
RZGate,
UGate,
CZGate,
)
from qiskit.circuit import IfElseOp, WhileLoopOp, ForLoopOp, ControlFlowOp
from qiskit.circuit.measure import Measure
from qiskit.test import QiskitTestCase
from qiskit.providers.fake_provider import (
@ -1469,6 +1480,79 @@ class TestTranspile(QiskitTestCase):
expected.measure(qubit_reg, clbit_reg)
self.assertEqual(result, expected)
# TODO: Add optimization level 2 and 3 after they support control flow
# compilation
@data(0, 1)
def test_transpile_with_custom_control_flow_target(self, opt_level):
"""Test transpile() with a target and constrol flow ops."""
target = FakeMumbaiV2().target
target.add_instruction(ForLoopOp, name="for_loop")
target.add_instruction(WhileLoopOp, name="while_loop")
target.add_instruction(IfElseOp, name="if_else")
class CustomCX(Gate):
"""Custom CX"""
def __init__(self):
super().__init__("custom_cx", 2, [])
def _define(self):
self._definition = QuantumCircuit(2)
self._definition.cx(0, 1)
circuit = QuantumCircuit(6, 1)
circuit.h(0)
circuit.measure(0, 0)
circuit.cx(0, 1)
circuit.cz(0, 2)
circuit.append(CustomCX(), [1, 2], [])
with circuit.for_loop((1,)):
circuit.cx(0, 1)
circuit.cz(0, 2)
circuit.append(CustomCX(), [1, 2], [])
with circuit.if_test((circuit.clbits[0], True)) as else_:
circuit.cx(0, 1)
circuit.cz(0, 2)
circuit.append(CustomCX(), [1, 2], [])
with else_:
circuit.cx(3, 4)
circuit.cz(3, 5)
circuit.append(CustomCX(), [4, 5], [])
with circuit.while_loop((circuit.clbits[0], True)):
circuit.cx(3, 4)
circuit.cz(3, 5)
circuit.append(CustomCX(), [4, 5], [])
transpiled = transpile(
circuit, optimization_level=opt_level, target=target, seed_transpiler=12434
)
# Tests of the complete validity of a circuit are mostly done at the indiviual pass level;
# here we're just checking that various passes do appear to have run.
self.assertIsInstance(transpiled, QuantumCircuit)
# Assert layout ran.
self.assertIsNot(getattr(transpiled, "_layout", None), None)
def _visit_block(circuit, qubit_mapping=None):
for instruction in circuit:
qargs = tuple(qubit_mapping[x] for x in instruction.qubits)
self.assertTrue(target.instruction_supported(instruction.operation.name, qargs))
if isinstance(instruction.operation, ControlFlowOp):
for block in instruction.operation.blocks:
new_mapping = {
inner: qubit_mapping[outer]
for outer, inner in zip(instruction.qubits, block.qubits)
}
_visit_block(block, new_mapping)
# Assert unrolling ran.
self.assertNotIsInstance(instruction.operation, CustomCX)
# Assert translation ran.
self.assertNotIsInstance(instruction.operation, CZGate)
# Assert routing ran.
_visit_block(
transpiled,
qubit_mapping={qubit: index for index, qubit in enumerate(transpiled.qubits)},
)
class StreamHandlerRaiseException(StreamHandler):
"""Handler class that will raise an exception on formatting errors."""

View File

@ -112,7 +112,7 @@ class TestDenseLayout(QiskitTestCase):
dag = circuit_to_dag(circuit)
instruction_props = {edge: None for edge in CouplingMap.from_heavy_hex(3).get_edges()}
noiseless_target = Target()
noiseless_target.add_instruction(CXGate, instruction_props)
noiseless_target.add_instruction(CXGate(), instruction_props)
pass_ = DenseLayout(target=noiseless_target)
pass_.run(dag)
layout = pass_.property_set["layout"]

View File

@ -30,6 +30,7 @@ from qiskit.circuit.library import (
RZXGate,
CZGate,
)
from qiskit.circuit import IfElseOp, ForLoopOp, WhileLoopOp
from qiskit.circuit.measure import Measure
from qiskit.circuit.parameter import Parameter
from qiskit import pulse
@ -1165,6 +1166,342 @@ Instructions:
)
class TestGlobalVariableWidthOperations(QiskitTestCase):
def setUp(self):
super().setUp()
self.theta = Parameter("theta")
self.phi = Parameter("phi")
self.lam = Parameter("lambda")
self.target_global_gates_only = Target(num_qubits=5)
self.target_global_gates_only.add_instruction(CXGate())
self.target_global_gates_only.add_instruction(UGate(self.theta, self.phi, self.lam))
self.target_global_gates_only.add_instruction(Measure())
self.target_global_gates_only.add_instruction(IfElseOp, name="if_else")
self.target_global_gates_only.add_instruction(ForLoopOp, name="for_loop")
self.target_global_gates_only.add_instruction(WhileLoopOp, name="while_loop")
self.ibm_target = Target()
i_props = {
(0,): InstructionProperties(duration=35.5e-9, error=0.000413),
(1,): InstructionProperties(duration=35.5e-9, error=0.000502),
(2,): InstructionProperties(duration=35.5e-9, error=0.0004003),
(3,): InstructionProperties(duration=35.5e-9, error=0.000614),
(4,): InstructionProperties(duration=35.5e-9, error=0.006149),
}
self.ibm_target.add_instruction(IGate(), i_props)
rz_props = {
(0,): InstructionProperties(duration=0, error=0),
(1,): InstructionProperties(duration=0, error=0),
(2,): InstructionProperties(duration=0, error=0),
(3,): InstructionProperties(duration=0, error=0),
(4,): InstructionProperties(duration=0, error=0),
}
self.ibm_target.add_instruction(RZGate(self.theta), rz_props)
sx_props = {
(0,): InstructionProperties(duration=35.5e-9, error=0.000413),
(1,): InstructionProperties(duration=35.5e-9, error=0.000502),
(2,): InstructionProperties(duration=35.5e-9, error=0.0004003),
(3,): InstructionProperties(duration=35.5e-9, error=0.000614),
(4,): InstructionProperties(duration=35.5e-9, error=0.006149),
}
self.ibm_target.add_instruction(SXGate(), sx_props)
x_props = {
(0,): InstructionProperties(duration=35.5e-9, error=0.000413),
(1,): InstructionProperties(duration=35.5e-9, error=0.000502),
(2,): InstructionProperties(duration=35.5e-9, error=0.0004003),
(3,): InstructionProperties(duration=35.5e-9, error=0.000614),
(4,): InstructionProperties(duration=35.5e-9, error=0.006149),
}
self.ibm_target.add_instruction(XGate(), x_props)
cx_props = {
(3, 4): InstructionProperties(duration=270.22e-9, error=0.00713),
(4, 3): InstructionProperties(duration=305.77e-9, error=0.00713),
(3, 1): InstructionProperties(duration=462.22e-9, error=0.00929),
(1, 3): InstructionProperties(duration=497.77e-9, error=0.00929),
(1, 2): InstructionProperties(duration=227.55e-9, error=0.00659),
(2, 1): InstructionProperties(duration=263.11e-9, error=0.00659),
(0, 1): InstructionProperties(duration=519.11e-9, error=0.01201),
(1, 0): InstructionProperties(duration=554.66e-9, error=0.01201),
}
self.ibm_target.add_instruction(CXGate(), cx_props)
measure_props = {
(0,): InstructionProperties(duration=5.813e-6, error=0.0751),
(1,): InstructionProperties(duration=5.813e-6, error=0.0225),
(2,): InstructionProperties(duration=5.813e-6, error=0.0146),
(3,): InstructionProperties(duration=5.813e-6, error=0.0215),
(4,): InstructionProperties(duration=5.813e-6, error=0.0333),
}
self.ibm_target.add_instruction(Measure(), measure_props)
self.ibm_target.add_instruction(IfElseOp, name="if_else")
self.ibm_target.add_instruction(ForLoopOp, name="for_loop")
self.ibm_target.add_instruction(WhileLoopOp, name="while_loop")
self.aqt_target = Target(description="AQT Target")
rx_props = {
(0,): None,
(1,): None,
(2,): None,
(3,): None,
(4,): None,
}
self.aqt_target.add_instruction(RXGate(self.theta), rx_props)
ry_props = {
(0,): None,
(1,): None,
(2,): None,
(3,): None,
(4,): None,
}
self.aqt_target.add_instruction(RYGate(self.theta), ry_props)
rz_props = {
(0,): None,
(1,): None,
(2,): None,
(3,): None,
(4,): None,
}
self.aqt_target.add_instruction(RZGate(self.theta), rz_props)
r_props = {
(0,): None,
(1,): None,
(2,): None,
(3,): None,
(4,): None,
}
self.aqt_target.add_instruction(RGate(self.theta, self.phi), r_props)
rxx_props = {
(0, 1): None,
(0, 2): None,
(0, 3): None,
(0, 4): None,
(1, 0): None,
(2, 0): None,
(3, 0): None,
(4, 0): None,
(1, 2): None,
(1, 3): None,
(1, 4): None,
(2, 1): None,
(3, 1): None,
(4, 1): None,
(2, 3): None,
(2, 4): None,
(3, 2): None,
(4, 2): None,
(3, 4): None,
(4, 3): None,
}
self.aqt_target.add_instruction(RXXGate(self.theta), rxx_props)
measure_props = {
(0,): None,
(1,): None,
(2,): None,
(3,): None,
(4,): None,
}
self.aqt_target.add_instruction(Measure(), measure_props)
self.aqt_target.add_instruction(IfElseOp, name="if_else")
self.aqt_target.add_instruction(ForLoopOp, name="for_loop")
self.aqt_target.add_instruction(WhileLoopOp, name="while_loop")
def test_qargs(self):
expected_ibm = {
(0,),
(1,),
(2,),
(3,),
(4,),
(3, 4),
(4, 3),
(3, 1),
(1, 3),
(1, 2),
(2, 1),
(0, 1),
(1, 0),
}
self.assertEqual(expected_ibm, self.ibm_target.qargs)
expected_aqt = {
(0,),
(1,),
(2,),
(3,),
(4,),
(0, 1),
(0, 2),
(0, 3),
(0, 4),
(1, 0),
(2, 0),
(3, 0),
(4, 0),
(1, 2),
(1, 3),
(1, 4),
(2, 1),
(3, 1),
(4, 1),
(2, 3),
(2, 4),
(3, 2),
(4, 2),
(3, 4),
(4, 3),
}
self.assertEqual(expected_aqt, self.aqt_target.qargs)
self.assertEqual(None, self.target_global_gates_only.qargs)
def test_qargs_for_operation_name(self):
self.assertEqual(
self.ibm_target.qargs_for_operation_name("rz"), {(0,), (1,), (2,), (3,), (4,)}
)
self.assertEqual(
self.aqt_target.qargs_for_operation_name("rz"), {(0,), (1,), (2,), (3,), (4,)}
)
self.assertIsNone(self.target_global_gates_only.qargs_for_operation_name("cx"))
self.assertIsNone(self.ibm_target.qargs_for_operation_name("if_else"))
self.assertIsNone(self.aqt_target.qargs_for_operation_name("while_loop"))
def test_instruction_names(self):
self.assertEqual(
self.ibm_target.operation_names,
{"rz", "id", "sx", "x", "cx", "measure", "if_else", "while_loop", "for_loop"},
)
self.assertEqual(
self.aqt_target.operation_names,
{"rz", "ry", "rx", "rxx", "r", "measure", "if_else", "while_loop", "for_loop"},
)
self.assertEqual(
self.target_global_gates_only.operation_names,
{"u", "cx", "measure", "if_else", "while_loop", "for_loop"},
)
def test_operations(self):
ibm_expected = [
RZGate(self.theta),
IGate(),
SXGate(),
XGate(),
CXGate(),
Measure(),
WhileLoopOp,
IfElseOp,
ForLoopOp,
]
for gate in ibm_expected:
self.assertIn(gate, self.ibm_target.operations)
aqt_expected = [
RZGate(self.theta),
RXGate(self.theta),
RYGate(self.theta),
RGate(self.theta, self.phi),
RXXGate(self.theta),
ForLoopOp,
IfElseOp,
WhileLoopOp,
]
for gate in aqt_expected:
self.assertIn(gate, self.aqt_target.operations)
fake_expected = [
UGate(self.theta, self.phi, self.lam),
CXGate(),
Measure(),
ForLoopOp,
WhileLoopOp,
IfElseOp,
]
for gate in fake_expected:
self.assertIn(gate, self.target_global_gates_only.operations)
def test_instructions(self):
ibm_expected = [
(IGate(), (0,)),
(IGate(), (1,)),
(IGate(), (2,)),
(IGate(), (3,)),
(IGate(), (4,)),
(RZGate(self.theta), (0,)),
(RZGate(self.theta), (1,)),
(RZGate(self.theta), (2,)),
(RZGate(self.theta), (3,)),
(RZGate(self.theta), (4,)),
(SXGate(), (0,)),
(SXGate(), (1,)),
(SXGate(), (2,)),
(SXGate(), (3,)),
(SXGate(), (4,)),
(XGate(), (0,)),
(XGate(), (1,)),
(XGate(), (2,)),
(XGate(), (3,)),
(XGate(), (4,)),
(CXGate(), (3, 4)),
(CXGate(), (4, 3)),
(CXGate(), (3, 1)),
(CXGate(), (1, 3)),
(CXGate(), (1, 2)),
(CXGate(), (2, 1)),
(CXGate(), (0, 1)),
(CXGate(), (1, 0)),
(Measure(), (0,)),
(Measure(), (1,)),
(Measure(), (2,)),
(Measure(), (3,)),
(Measure(), (4,)),
(IfElseOp, None),
(ForLoopOp, None),
(WhileLoopOp, None),
]
self.assertEqual(ibm_expected, self.ibm_target.instructions)
ideal_sim_expected = [
(CXGate(), None),
(UGate(self.theta, self.phi, self.lam), None),
(Measure(), None),
(IfElseOp, None),
(ForLoopOp, None),
(WhileLoopOp, None),
]
self.assertEqual(ideal_sim_expected, self.target_global_gates_only.instructions)
def test_instruction_supported(self):
self.assertTrue(self.aqt_target.instruction_supported("r", (0,)))
self.assertFalse(self.aqt_target.instruction_supported("cx", (0, 1)))
self.assertTrue(self.target_global_gates_only.instruction_supported("cx", (0, 1)))
self.assertFalse(self.target_global_gates_only.instruction_supported("cx", (0, 524)))
self.assertFalse(self.target_global_gates_only.instruction_supported("cx", (0, 1, 2)))
self.assertTrue(self.aqt_target.instruction_supported("while_loop", (0, 1, 2, 3)))
self.assertTrue(
self.aqt_target.instruction_supported(operation_class=WhileLoopOp, qargs=(0, 1, 2, 3))
)
self.assertFalse(
self.ibm_target.instruction_supported(
operation_class=IfElseOp, qargs=(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
)
)
self.assertFalse(
self.ibm_target.instruction_supported(operation_class=IfElseOp, qargs=(0, 425))
)
self.assertFalse(self.ibm_target.instruction_supported("for_loop", qargs=(0, 425)))
def test_coupling_map(self):
self.assertIsNone(self.target_global_gates_only.build_coupling_map())
self.assertEqual(
set(CouplingMap.from_full(5).get_edges()),
set(self.aqt_target.build_coupling_map().get_edges()),
)
self.assertEqual(
{
(3, 4),
(4, 3),
(3, 1),
(1, 3),
(1, 2),
(2, 1),
(0, 1),
(1, 0),
},
set(self.ibm_target.build_coupling_map().get_edges()),
)
class TestInstructionProperties(QiskitTestCase):
def test_empty_repr(self):
properties = InstructionProperties()