From d439ee050cd367b973433e9c048ecc586c401432 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 22 Mar 2022 18:49:30 -0400 Subject: [PATCH 01/10] Expand instruction_supported() to optionally check parameters This commit expands the recently added instruction_supported() method of the Target class to also enable optionally checking if a parameter on an instruction is supported. This is anticipating future changes around how instruction parameters are represented in terra more generally as part of #7624 and specifically in the Target for #7797. Before we do any refactoring around this having a standard API for checking if a parameter is supported is useful. This commit expands the interface for the insturction_supported() method to check any combination of operation name, qargs, operation class, or parameters (either operation name or class is required) and the method will return True if that instruction can be run on the Target device. --- qiskit/transpiler/target.py | 96 +++++++++++++++++++++++++-- test/python/transpiler/test_target.py | 63 ++++++++++++++++++ 2 files changed, 155 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index d5eb2bc2ce76..55c391edc51d 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -10,6 +10,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +# pylint: disable=too-many-return-statements + """ A target object represents the minimum set of information the transpiler needs from a backend @@ -22,6 +24,7 @@ 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.instruction_durations import InstructionDurations @@ -502,20 +505,105 @@ def operation_names_for_qargs(self, qargs): raise KeyError(f"{qargs} not in target.") return self._qarg_gate_map[qargs] - def instruction_supported(self, operation_name, qargs): + def instruction_supported( + self, operation_name=None, qargs=None, operation_class=None, parameters=None + ): """Return whether the instruction (operation + qubits) is supported by the target Args: - operation_name (str): The name of the operation for the instruction - qargs (tuple): The tuple of qubit indices for the instruction + operation_name (str): The name of the operation for the instruction. Either + this or ``operation_class`` must be specified, if both are specified + ``operation_class`` will take priority and this argument will be ignored. + qargs (tuple): The tuple of qubit indices for the instruction. If this is + not specified then this method will return ``True`` if the specified + operation is supported on any qubits. The typical application will + always have this set (otherwise it's the same as just checking if the + target contains the operation). Normally you would not set this argument + if you wanted to check more generally that the target supports an operation + with the ``parameters`` on any qubits. + operation_class (Instruction): The operation class to check whether + the target supports a particular operation by class rather + than by name. This lookup is more expensive as it needs to + iterate over all operations in the target instead of just a + single lookup. If this is specified it will superscede the + ``operation_name`` argument. The typical use case for this + operation is to check whether a specific variant of an operation + is supported on the backend. For example, if you wanted to look + check whether a :class:`~.RXGate` was supported on a specific + qubit with a fixed angle. That fixed angle variant will + typically have a name different than the object's + :attr:`~.Instruction.name` attribute (``"rx"``) in the target. + This can be used to check if any instnaces of the class are + available in such a case. + parameters (list): A list of parameters to check if the target + supports them on the specified qubits. If the instruction + supports the parameter values specified in the list on the + operation and qargs specified this will return ``True`` but + if the parameters are not supported on the specified + instruction it will return ``False``. If this argument is not + specified this method will return ``True`` if the instruction + is supported independent of the instruction parameters. If + specified with any :class:`~.Parameter` objects in the list, + that entry will be treated as supporting any value, however parameter names + will not be checked (for example if an operation in the target + is listed as parameterized with ``"theta"`` and ``"phi"`` is + passed into this function that will return ``True``). For + exmample, if called with:: + + parameters = [Parameter("theta")] + target.instruction_supported("rx", (0,), parameters=parameters) + + will return ``True`` if an :class:`~.RXGate` is suporrted on qubit 0 + that will accept any parameter. If you need to check for a fixed numeric + value parameter this argument is typically paired with the ``operation_class`` + argument. For example:: + + target.instruction_supported("rx", (0,), RXGate, parameters=[pi / 4]) + + will return ``True`` if any RXGate Returns: bool: Returns ``True`` if the instruction is supported and ``False`` if it isn't. """ # Case a list if passed in by mistake - qargs = tuple(qargs) + if qargs is not None: + qargs = tuple(qargs) + if operation_class is not None: + for op_name, obj in self._gate_name_map.items(): + if isinstance(obj, operation_class): + if parameters is not None: + if len(parameters) != len(obj.params): + continue + for index, param in enumerate(parameters): + if isinstance(param, Parameter) and not isinstance( + obj.params[index], Parameter + ): + continue + if param != obj.params[index]: + continue + if qargs is None: + return True + if qargs in self._gate_map[op_name]: + return True + if self._gate_map[op_name] is None or None in self._gate_map[op_name]: + return self._gate_name_map[op_name].num_qubits == len(qargs) and all( + x < self.num_qubits for x in qargs + ) + return False if operation_name in self._gate_map: + if parameters is not None: + obj = self._gate_name_map[operation_name] + if len(parameters) != len(obj.params): + return False + for index, param in enumerate(parameters): + if isinstance(obj.params[index], Parameter): + return True + elif param == obj.params[index]: + return True + return False + if qargs is None: + return True 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]: diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index 99e1485ae54d..a7cbe765a8c3 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -27,6 +27,7 @@ ECRGate, UGate, CCXGate, + RZXGate, ) from qiskit.circuit.measure import Measure from qiskit.circuit.parameter import Parameter @@ -39,6 +40,7 @@ from qiskit.transpiler import InstructionProperties from qiskit.test import QiskitTestCase from qiskit.test.mock.fake_backend_v2 import FakeBackendV2 +from qiskit.test.mock.fake_mumbai_v2 import FakeMumbaiV2 class TestTarget(QiskitTestCase): @@ -931,6 +933,67 @@ def test_instruction_supported(self): self.assertFalse(self.fake_backend_target.instruction_supported("cx", (1, 0))) self.assertFalse(self.ideal_sim_target.instruction_supported("cx", (0, 1, 2))) + def test_instruction_supported_parameters(self): + mumbai = FakeMumbaiV2() + self.assertTrue( + mumbai.target.instruction_supported( + qargs=(0, 1), operation_class=RZXGate, parameters=[math.pi / 4] + ) + ) + self.assertTrue(mumbai.target.instruction_supported(qargs=(0, 1), operation_class=RZXGate)) + self.assertTrue( + mumbai.target.instruction_supported(operation_class=RZXGate, parameters=[math.pi / 4]) + ) + self.assertFalse(mumbai.target.instruction_supported("rzx", parameters=[math.pi / 4])) + self.assertTrue(mumbai.target.instruction_supported("rz", parameters=[Parameter("angle")])) + self.assertTrue( + mumbai.target.instruction_supported("rzx_45", qargs=(0, 1), parameters=[math.pi / 4]) + ) + self.assertTrue(mumbai.target.instruction_supported("rzx_45", qargs=(0, 1))) + self.assertTrue(mumbai.target.instruction_supported("rzx_45", parameters=[math.pi / 4])) + self.assertFalse(mumbai.target.instruction_supported("rzx_45", parameters=[math.pi / 6])) + self.assertFalse( + mumbai.target.instruction_supported("rzx_45", parameters=[Parameter("angle")]) + ) + self.assertTrue( + self.ideal_sim_target.instruction_supported( + qargs=(0,), operation_class=RXGate, parameters=[Parameter("angle")] + ) + ) + self.assertTrue( + self.ideal_sim_target.instruction_supported( + qargs=(0,), operation_class=RXGate, parameters=[math.pi] + ) + ) + self.assertTrue( + self.ideal_sim_target.instruction_supported( + operation_class=RXGate, parameters=[math.pi] + ) + ) + self.assertTrue( + self.ideal_sim_target.instruction_supported( + operation_class=RXGate, parameters=[Parameter("angle")] + ) + ) + self.assertTrue( + self.ideal_sim_target.instruction_supported( + "rx", qargs=(0,), parameters=[Parameter("angle")] + ) + ) + self.assertTrue( + self.ideal_sim_target.instruction_supported("rx", qargs=(0,), parameters=[math.pi]) + ) + self.assertTrue(self.ideal_sim_target.instruction_supported("rx", parameters=[math.pi])) + self.assertTrue( + self.ideal_sim_target.instruction_supported("rx", parameters=[Parameter("angle")]) + ) + + def test_instruction_supported_no_args(self): + self.assertFalse(self.ibm_target.instruction_supported()) + + def test_instruction_supported_no_operation(self): + self.assertFalse(self.ibm_target.instruction_supported(qargs=(0,), parameters=[math.pi])) + class TestPulseTarget(QiskitTestCase): def setUp(self): From d548d30d52b8c1b4df00bb5cdca82394a47aa39d Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 23 Mar 2022 08:01:58 -0400 Subject: [PATCH 02/10] Fix docs build --- qiskit/transpiler/target.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 55c391edc51d..63d3e5a5c75a 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -521,7 +521,7 @@ def instruction_supported( target contains the operation). Normally you would not set this argument if you wanted to check more generally that the target supports an operation with the ``parameters`` on any qubits. - operation_class (Instruction): The operation class to check whether + operation_class (qiskit.circuit.Instruction): The operation class to check whether the target supports a particular operation by class rather than by name. This lookup is more expensive as it needs to iterate over all operations in the target instead of just a From 9b0d240db225ad3af3388edc81408784862cad22 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 23 Mar 2022 08:10:17 -0400 Subject: [PATCH 03/10] Expand test coverage --- test/python/transpiler/test_target.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index a7cbe765a8c3..d66eecfdc59b 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -28,6 +28,7 @@ UGate, CCXGate, RZXGate, + CZGate, ) from qiskit.circuit.measure import Measure from qiskit.circuit.parameter import Parameter @@ -988,6 +989,17 @@ def test_instruction_supported_parameters(self): self.ideal_sim_target.instruction_supported("rx", parameters=[Parameter("angle")]) ) + def test_instruction_supported_arg_len_mismatch(self): + self.assertFalse( + self.ideal_sim_target.instruction_supported(operation_class=UGate, parameters=[math.pi]) + ) + self.assertFalse(self.ideal_sim_target.instruction_supported("u", parameters=[math.pi])) + + def test_instruction_supported_class_not_in_target(self): + self.assertFalse( + self.ibm_target.instruction_supported(operation_class=CZGate, parameters=[math.pi]) + ) + def test_instruction_supported_no_args(self): self.assertFalse(self.ibm_target.instruction_supported()) From c89be5e45c2593d70c618a776f364907ddd48987 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 28 Apr 2022 18:13:43 -0400 Subject: [PATCH 04/10] Apply suggestions from code review Co-authored-by: Ali Javadi-Abhari --- qiskit/transpiler/target.py | 10 +++++----- test/python/transpiler/test_target.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 63d3e5a5c75a..785720b0a055 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -525,15 +525,15 @@ def instruction_supported( the target supports a particular operation by class rather than by name. This lookup is more expensive as it needs to iterate over all operations in the target instead of just a - single lookup. If this is specified it will superscede the + single lookup. If this is specified it will supersede the ``operation_name`` argument. The typical use case for this operation is to check whether a specific variant of an operation - is supported on the backend. For example, if you wanted to look + is supported on the backend. For example, if you wanted to check whether a :class:`~.RXGate` was supported on a specific qubit with a fixed angle. That fixed angle variant will typically have a name different than the object's :attr:`~.Instruction.name` attribute (``"rx"``) in the target. - This can be used to check if any instnaces of the class are + This can be used to check if any instances of the class are available in such a case. parameters (list): A list of parameters to check if the target supports them on the specified qubits. If the instruction @@ -548,7 +548,7 @@ def instruction_supported( will not be checked (for example if an operation in the target is listed as parameterized with ``"theta"`` and ``"phi"`` is passed into this function that will return ``True``). For - exmample, if called with:: + example, if called with:: parameters = [Parameter("theta")] target.instruction_supported("rx", (0,), parameters=parameters) @@ -560,7 +560,7 @@ def instruction_supported( target.instruction_supported("rx", (0,), RXGate, parameters=[pi / 4]) - will return ``True`` if any RXGate + will return ``True`` if an RXGate(pi/4) exists on qubit 0. Returns: bool: Returns ``True`` if the instruction is supported and ``False`` if it isn't. diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index d66eecfdc59b..0dc12139a2d0 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -41,7 +41,7 @@ from qiskit.transpiler import InstructionProperties from qiskit.test import QiskitTestCase from qiskit.test.mock.fake_backend_v2 import FakeBackendV2 -from qiskit.test.mock.fake_mumbai_v2 import FakeMumbaiV2 +from qiskit.test.mock.fake_mumbai_v2 import FakeMumbaiFractionalCX class TestTarget(QiskitTestCase): From d4bdef009868a4fad4132319b181ffa980aab665 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 7 Jun 2022 09:49:56 -0400 Subject: [PATCH 05/10] Update fake backend name in tests --- test/python/transpiler/test_target.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index 0dc12139a2d0..d853b72df694 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -935,7 +935,7 @@ def test_instruction_supported(self): self.assertFalse(self.ideal_sim_target.instruction_supported("cx", (0, 1, 2))) def test_instruction_supported_parameters(self): - mumbai = FakeMumbaiV2() + mumbai = FakeMumbaiFractionalCX() self.assertTrue( mumbai.target.instruction_supported( qargs=(0, 1), operation_class=RZXGate, parameters=[math.pi / 4] From b5674357a3be783e51f709cf9213da13355eaab2 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 7 Jun 2022 10:07:13 -0400 Subject: [PATCH 06/10] Fix handling of multiple parameters in gate name case --- qiskit/transpiler/target.py | 10 +++++++--- test/python/transpiler/test_target.py | 9 +++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 2e2ad9bc1bf9..9ef88414f514 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -611,6 +611,7 @@ def instruction_supported( continue if param != obj.params[index]: continue + if qargs is None: return True if qargs in self._gate_map[op_name]: @@ -626,11 +627,14 @@ def instruction_supported( if len(parameters) != len(obj.params): return False for index, param in enumerate(parameters): + matching_param = False if isinstance(obj.params[index], Parameter): - return True + matching_param = True elif param == obj.params[index]: - return True - return False + matching_param = True + if not matching_param: + return False + return True if qargs is None: return True if qargs in self._gate_map[operation_name]: diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index d853b72df694..019b4300c3dd 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -989,6 +989,15 @@ def test_instruction_supported_parameters(self): self.ideal_sim_target.instruction_supported("rx", parameters=[Parameter("angle")]) ) + def test_instruction_supported_multiple_parameters(self): + target = Target(1) + target.add_instruction( + UGate(self.theta, self.phi, self.lam), + {(0,): InstructionProperties(duration=270.22e-9, error=0.00713)}, + ) + self.assertFalse(target.instruction_supported("u", parameters=[math.pi])) + self.assertTrue(target.instruction_supported("u", parameters=[math.pi, math.pi, math.pi])) + def test_instruction_supported_arg_len_mismatch(self): self.assertFalse( self.ideal_sim_target.instruction_supported(operation_class=UGate, parameters=[math.pi]) From edb4b35e2c53f9818a4401590cf516743b56ff35 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 7 Jun 2022 10:34:14 -0400 Subject: [PATCH 07/10] Fix parameter checking in class gate input case --- qiskit/transpiler/target.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 9ef88414f514..34884c8392e5 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -595,6 +595,15 @@ def instruction_supported( bool: Returns ``True`` if the instruction is supported and ``False`` if it isn't. """ + + def check_obj_params(parameters, obj): + for index, param in enumerate(parameters): + if isinstance(param, Parameter) and not isinstance(obj.params[index], Parameter): + return False + if param != obj.params[index] and not isinstance(obj.params[index], Parameter): + return False + return True + # Case a list if passed in by mistake if qargs is not None: qargs = tuple(qargs) @@ -604,14 +613,8 @@ def instruction_supported( if parameters is not None: if len(parameters) != len(obj.params): continue - for index, param in enumerate(parameters): - if isinstance(param, Parameter) and not isinstance( - obj.params[index], Parameter - ): - continue - if param != obj.params[index]: - continue - + if not check_obj_params(parameters, obj): + continue if qargs is None: return True if qargs in self._gate_map[op_name]: From 9f916072053f90397131eb629aba2e6e5bbf8b31 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 7 Jun 2022 14:26:15 -0400 Subject: [PATCH 08/10] Add missing test path --- test/python/transpiler/test_target.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index 019b4300c3dd..418996056b26 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -997,6 +997,14 @@ def test_instruction_supported_multiple_parameters(self): ) self.assertFalse(target.instruction_supported("u", parameters=[math.pi])) self.assertTrue(target.instruction_supported("u", parameters=[math.pi, math.pi, math.pi])) + self.assertTrue( + target.instruction_supported( + operation_class=UGate, parameters=[math.pi, math.pi, math.pi] + ) + ) + self.assertFalse( + target.instruction_supported(operation_class=UGate, parameters=[Parameter("x")]) + ) def test_instruction_supported_arg_len_mismatch(self): self.assertFalse( From 2777b1fc0f38468fb8cde26e974448133edbc218 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 22 Jun 2022 11:37:23 -0400 Subject: [PATCH 09/10] Add release note --- ...instruction-supported-c3c9a02b2faa9785.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 releasenotes/notes/expand-instruction-supported-c3c9a02b2faa9785.yaml diff --git a/releasenotes/notes/expand-instruction-supported-c3c9a02b2faa9785.yaml b/releasenotes/notes/expand-instruction-supported-c3c9a02b2faa9785.yaml new file mode 100644 index 000000000000..0ebc11504c72 --- /dev/null +++ b/releasenotes/notes/expand-instruction-supported-c3c9a02b2faa9785.yaml @@ -0,0 +1,18 @@ +--- +features: + - | + The :meth:`.Target.instruction_supported` method now supports two new + keyword arguments, ``operation_class`` and ``parameters``. Using these + arguments the :meth:`~.Target.instruction_supported` method can now be used + for checking that a specific operation with parameter values are supported + by a :class:`~.Target` object. For example, if you want to check if a + :class:`~.Target` named ``target`` supports running a :class:`~.RXGate` + with :math:`\theta = \frac{\pi}{2}` you would do something like:: + + from math import pi + from qiskit.circuit.library import RXGate + + target.instruction_supported(operation_class=RXGate, parameters=[pi/2]) + + which will return ``True`` if ``target`` supports running :class:`~.RXGate` + with :math:`\theta = \frac{\pi}{2}` and ``False if it does not. From d1637014ed2ed3a0066092042f82e910fe3b6370 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Wed, 22 Jun 2022 17:02:10 +0100 Subject: [PATCH 10/10] Fix release-note typo --- .../notes/expand-instruction-supported-c3c9a02b2faa9785.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/expand-instruction-supported-c3c9a02b2faa9785.yaml b/releasenotes/notes/expand-instruction-supported-c3c9a02b2faa9785.yaml index 0ebc11504c72..c84fac2c9b7c 100644 --- a/releasenotes/notes/expand-instruction-supported-c3c9a02b2faa9785.yaml +++ b/releasenotes/notes/expand-instruction-supported-c3c9a02b2faa9785.yaml @@ -15,4 +15,4 @@ features: target.instruction_supported(operation_class=RXGate, parameters=[pi/2]) which will return ``True`` if ``target`` supports running :class:`~.RXGate` - with :math:`\theta = \frac{\pi}{2}` and ``False if it does not. + with :math:`\theta = \frac{\pi}{2}` and ``False`` if it does not.