diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 34884c8392e5..c48731208d54 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -22,12 +22,14 @@ 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 @@ def add_instruction(self, instruction, properties=None, name=None): 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 @@ def add_instruction(self, instruction, properties=None, name=None): 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 @@ def operation_from_name(self, instruction): 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 @@ def operations_for_qargs(self, qargs): 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 @@ def check_obj_params(parameters, obj): 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 @@ def check_obj_params(parameters, obj): 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 @@ def check_obj_params(parameters, obj): 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 @@ def operations(self): @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 @@ def _build_coupling_graph(self): 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 @@ def _build_coupling_graph(self): 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 @@ def build_coupling_map(self, two_q_gate=None): 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): diff --git a/releasenotes/notes/fix-target-control-flow-representation-09520e2838f0657e.yaml b/releasenotes/notes/fix-target-control-flow-representation-09520e2838f0657e.yaml new file mode 100644 index 000000000000..51a8f906a6c2 --- /dev/null +++ b/releasenotes/notes/fix-target-control-flow-representation-09520e2838f0657e.yaml @@ -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) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index f628e3702e2d..0a9f2027edad 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -32,7 +32,18 @@ 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 @@ def test_target_ideal_gates(self, opt_level): 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.""" diff --git a/test/python/transpiler/test_dense_layout.py b/test/python/transpiler/test_dense_layout.py index aa0814d5e2df..10e5bbed9252 100644 --- a/test/python/transpiler/test_dense_layout.py +++ b/test/python/transpiler/test_dense_layout.py @@ -112,7 +112,7 @@ def test_5q_circuit_19q_target_without_noise(self): 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"] diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index 939432c33e81..dd1ca7ba0a0c 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -30,6 +30,7 @@ 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 @@ def test_timing_constraints(self): ) +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()