Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand instruction_supported() to optionally check parameters #7807

Merged
merged 17 commits into from
Jun 22, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 99 additions & 4 deletions qiskit/transpiler/target.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -23,6 +25,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
Expand Down Expand Up @@ -531,20 +534,112 @@ 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 (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
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
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 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
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
example, 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 an RXGate(pi/4) exists on qubit 0.

Returns:
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
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
if not check_obj_params(parameters, obj):
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):
matching_param = False
if isinstance(obj.params[index], Parameter):
matching_param = True
elif param == obj.params[index]:
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]:
return True
if self._gate_map[operation_name] is None or None in self._gate_map[operation_name]:
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
92 changes: 92 additions & 0 deletions test/python/transpiler/test_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
ECRGate,
UGate,
CCXGate,
RZXGate,
CZGate,
)
from qiskit.circuit.measure import Measure
from qiskit.circuit.parameter import Parameter
Expand All @@ -39,6 +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 FakeMumbaiFractionalCX


class TestTarget(QiskitTestCase):
Expand Down Expand Up @@ -931,6 +934,95 @@ 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 = FakeMumbaiFractionalCX()
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_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]))
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(
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())

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):
Expand Down