Skip to content
Merged
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
123 changes: 105 additions & 18 deletions qiskit/transpiler/target.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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)
Comment thread
mtreinish marked this conversation as resolved.
self._gate_map[instruction_name] = qargs_val
self._coupling_graph = None
self._instruction_durations = None
Expand Down Expand Up @@ -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]

Expand All @@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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
Expand All @@ -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]
]
Expand Down Expand Up @@ -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
Comment thread
mtreinish marked this conversation as resolved.
if len(qarg) == 1:
self._coupling_graph[qarg[0]] = properties
elif len(qarg) == 2:
Expand All @@ -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.
Expand Down Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
Loading