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

Add Target methods to query the target for instruction properties #9158

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
239 changes: 239 additions & 0 deletions qiskit/transpiler/target.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,245 @@ def operation_names_for_qargs(self, qargs):
raise KeyError(f"{qargs} not in target.")
return res

def get_instruction_properties_by_class(self, operation_class, qargs, parameters=None):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is nitpicky but the other methods in this class don't start with get_* so for consistency I would drop get_* here too

"""Return the instruction properties for matching instructions by operation class

This method and looking up by class is in general more expensive to compute as it
needs to iterate over all operations in the target instead of just a single lookup
by name as is done in :meth:`~.get_instruction_properties_by_name`. This method can also
return multiple properties as there might be multiple implementations of a class in the
target. The typical use case for this operation is to get the properties of a specific
variant of an operation on the backend. For example, if you wanted to get the properties
of all the instructions capable of implementing a :class:`~.RXGate` on a specific qubit
with a fixed angle.

Args:
operation_class (qiskit.circuit.Operation): The operation class to lookup the
properties for
qargs (tuple): The tuple of qubit indices for the instruction.
parameters (list): A list of parameters to filter the returned properties on
whether an operation supports them on the specified qubits. If the instruction
supports the parameter values specified in the list on the operation and qargs
specified the properties for that instruction will be included in the return.
If this argument is not specified this method will return the properties for
any instruction that 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 properties for that
instruction). For example, if called with::

parameters = [Parameter("theta")]
target.get_instruction_properties_by_class(RXGate, (0,), parameters=parameters)

will return the properties of any :class:`~.RXGate` instruction that is suported on
qubit 0 which will accept any parameter (excluding fixed angle variants).

Returns:
dict: A dictionary mapping all the operation names to instruction properties for all
matching instructions. If there are no properties available the value will be ``None``

Raises:
KeyError: If the instruction isn't supported
"""

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)
out_dict = {}
for op_name, obj in self._gate_name_map.items():
if inspect.isclass(obj):
if obj != operation_class:
continue
# If qargs set then validate no duplicates and all indices are valid on device
if all(qarg <= self.num_qubits for qarg in qargs) and len(set(qargs)) == len(qargs):
out_dict[op_name] = None
elif 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 in self._gate_map[op_name]:
out_dict[op_name] = self._gate_map[op_name][qargs]
if None in self._gate_map[op_name]:
out_dict[op_name] = self._gate_map[op_name][None]
if self._gate_map[op_name] is None:
out_dict[op_name] = None
if not out_dict:
raise KeyError(
f"Operation class {operation_class} on qargs: {qargs} with parameters "
f"{parameters} is not supported"
)
return out_dict

def get_instruction_errors_by_class(self, operation_class, qargs, parameters=None):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

personally I would be ok not having the special methods to get error and duration.. Since error and duration can be easily accessed from properties, I don't think they need dedicated lookups (and they are just two properties, there could be more). This would reduce the extra added methods from 6 to 2.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the purpose of this PR is to provide wrapper of these attributes. Note that InstructionProperties is not exposed to developers who don't update gate properties by themselves. So hiding its implementation with these methods makes sense to me.

In transpiler passes requiring these information, the pattern of getattr(inst_props, "error", 0.0) or 0.0 is often used, so it seems to me like developers assume the case the dict value is not InstructionProperties (personally, I think they should use .error instead).

Anyways, there are several cases you cannot get error

  • instruction is not defined (1)
  • qargs is not defined for the instruction (2)
  • dict value is missing for (instruction, qargs): No instruction set provided (3)
  • dict value is provided but not InstructionProperties instance: Edge case (4)
  • dict value is provided but error is None value: Calibration is provided without benchmark, e.g. during gate bring-up (5)

This method returns None in all above cases. So this method reduces the complexity to access gate fidelity.

In accordance with convention, when None is returned, transpiler passes treat the instruction as ideal gate (i.e. zero-error) in the fidelity metric. I'm not sure if this is correct behavior in the case of (5). In this case, we could calculate coherence limit with device T1 and gate .duration, because we should know gate duration after calibration. This method can implement such logic since Target knows qubit T1.

"""Return the error rates for instructions by operation class

This method and looking up by class is in general more expensive to compute as it
needs to iterate over all operations in the target instead of just a single lookup
by name as is done in :meth:`~.get_instruction_error`. This method can also
return multiple properties as there might be multiple implementations of a class in the
target. The typical use case for this operation is to get the properties of a specific
variant of an operation on the backend. For example, if you wanted to get the properties
of all the instructions capable of implementing a :class:`~.RXGate` on a specific qubit
with a fixed angle.

Args:
operation_class (qiskit.circuit.Operation): The operation class to lookup the
properties for
qargs (tuple): The tuple of qubit indices for the instruction.
parameters (list): A list of parameters to filter the returned error rates on
whether an operation supports them on the specified qubits. If the instruction
supports the parameter values specified in the list on the operation and qargs
specified the properties for that instruction will be included in the return.
If this argument is not specified this method will return the properties for
any instruction that 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 the error rate for
that instruction). For example, if called with::

parameters = [Parameter("theta")]
target.get_errors_by_class(RXGate, (0,), parameters=parameters)

will return the properties of any :class:`~.RXGate` instruction that is suporrted on
qubit 0 which will accept any parameter (excluding fixed angle variants).

Returns:
dict: A dictionary mapping all the operation names to error rates for all matching
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how this method will be used in practice. Even if we get multiple errors for different gate implementations, transpiler passes don't choose most efficient pulse gate , i.e. .add_calibration, so fidelity metric of optimized sequence doesn't match. I think we can drop these methods if we don't have practical use case. User can still get the same function by creating the reverse mapping of Target._gate_name_map.

instructions with errors defined. If there are no properties available with errors
the return will be ``None``.

Raises:
KeyError: If the instruction isn't supported
"""
props_dict = self.get_instruction_properties_by_class(operation_class, qargs, parameters)
out_dict = {}
for op_name, prop in props_dict.items():
error = getattr(prop, "error", None)
if error is not None:
out_dict[op_name] = error
if not out_dict:
return None
return out_dict

def get_instruction_durations_by_class(self, operation_class, qargs, parameters=None):
"""Return the durations for instructions by operation class

This method and looking up by class is in general more expensive to compute as it
needs to iterate over all operations in the target instead of just a single lookup
by name as is done in :meth:`~.get_instruction_duration`. This method can also
return multiple properties as there might be multiple implementations of a class in the
target. The typical use case for this operation is to get the properties of a specific
variant of an operation on the backend. For example, if you wanted to get the properties
of all the instructions capable of implementing a :class:`~.RXGate` on a specific qubit
with a fixed angle.

Args:
operation_class (qiskit.circuit.Operation): The operation class to lookup the
properties for
qargs (tuple): The tuple of qubit indices for the instruction.
parameters (list): A list of parameters to filter the returned durations on
whether an operation supports them on the specified qubits. If the instruction
supports the parameter values specified in the list on the operation and qargs
specified the properties for that instruction will be included in the return.
If this argument is not specified this method will return the properties for
any instruction that 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 the duration for that
instruction). For example, if called with::

parameters = [Parameter("theta")]
target.get_durations_by_class(RXGate, (0,), parameters=parameters)

will return the properties of any :class:`~.RXGate` instruction that is suporrted on
qubit 0 which will accept any parameter (excluding fixed angle variants).

Returns:
dict: A dictionary mapping all the operation names to durations for all matching
instructions with durations defined. If there are no properties available with
durations the return will be ``None``.

Raises:
KeyError: If the instruction isn't supported
"""
props_dict = self.get_instruction_properties_by_class(operation_class, qargs, parameters)
out_dict = {}
for op_name, prop in props_dict.items():
error = getattr(prop, "duration", None)
if error is not None:
out_dict[op_name] = error
if not out_dict:
return None
return out_dict

def get_instruction_properties(self, operation_name, qargs):
"""Return the properties for a given instruction (operation + qubits) in the target

Args:
operation_name (str): The name of the operation for the instruction.
qargs (tuple): The tuple of qubit indices for the instruction.

Returns:
InstructionProperties: The instruction properties of the given instruction if available,
otherwise ``None``

Raises:
KeyError: If the instruction isn't supported
"""
if operation_name not in self._gate_map:
raise KeyError(f"Operation name {operation_name} is not supported.")
if self._gate_map[operation_name] is None:
return None
if qargs in self._gate_map[operation_name]:
return self._gate_map[operation_name][qargs]
if None in self._gate_map[operation_name]:
return self._gate_map[operation_name][None]
raise KeyError(f"Operation name {operation_name} is not supported on qargs: {qargs}")

def get_instruction_error(self, operation_name, qargs):
"""Return the error rate for an instruction (operation + qubits) in the target

Args:
operation_name (str): The name of the operation for the instruction.
qargs (tuple): The tuple of qubit indices for the instruction.

Returns:
float: The error rate if available, otherwise ``None``

Raises:
KeyError: If the instruction isn't supported
"""
props = self.get_instruction_properties(operation_name, qargs)
return getattr(props, "error", None)

def get_instruction_duration(self, operation_name, qargs):
"""Return the duration for an instruction (operation + qubits) in the target

Args:
operation_name (str): The name of the operation for the instruction.
qargs (tuple): The tuple of qubit indices for the instruction.

Returns:
float: The instruction duration in seconds if available, otherwise ``None``

Raises:
KeyError: If the instruction isn't supported
"""
props = self.get_instruction_properties(operation_name, qargs)
return getattr(props, "duration", None)

def instruction_supported(
self, operation_name=None, qargs=None, operation_class=None, parameters=None
):
Expand Down
24 changes: 24 additions & 0 deletions releasenotes/notes/add-target-lookup-methods-5a3170e90bb5015b.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
features:
- |
Added 3 new lookup methods, :meth:`~.Target.get_instruction_properties`,
:meth:~.Target.get_instruction_error`, and :meth:`~.Target.get_instruction_duration`
to the :class:`~.Target` class. These methods are used to query the target
for the :class:`~.InstructionProperties`, error rate, or duration of a given
instruction in the target. Previously this was possible using the mapping protocol
(i.e. ``target[op_name][qargs]``) however it resulted in a great deal of duplicated
logic to handle edge cases around ideal and/or global gates which depending on the
type of operation are represented with ``None`` at various levels in a
:class:`~.Target`. THese methods provide a simple entry point that will either return
the desired result, ``None`` if the instruction doesn't have the property defined, or raise
a ``KeyError`` if the instruction isn't supported by the :class:`~.Target`.
- |
Added 3 new lookup methods, :meth:`~.Target.get_instruction_properties_by_class`,
:meth:~.Target.get_instruction_errors_by_class`, and :meth:`~.Target.get_instruction_durations_by_class`
to the :class:`~.Target` class. These methods are used to query the target
for the :class:`~.InstructionProperties`, error rate, or duration of a given
instruction in the target by the operation class instead of the canonical name for the
operation. These methods provide a simple entry point that will either return
a dictionary mapping the canonical operation names which match the input parameters
to desired result, ``None`` if the instruction doesn't have the property defined, or raise
a ``KeyError`` if the instruction isn't supported by the :class:`~.Target`.
Loading