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 support to basis, optimization, scheduling, and util passes #9343

Merged
merged 10 commits into from
Jan 20, 2023
58 changes: 42 additions & 16 deletions qiskit/transpiler/passes/basis/unroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,19 @@ class Unroller(TransformationPass):
to a desired basis, using decomposition rules defined for each instruction.
"""

def __init__(self, basis):
def __init__(self, basis=None, target=None):
"""Unroller initializer.

Args:
basis (list[str] or None): Target basis names to unroll to, e.g. `['u3', 'cx']` . If
None, does not unroll any gate.
target (Target): The :class:`~.Target` representing the target backend, if both
``basis`` and this are specified then this argument will take
precedence and ``basis`` will be ignored.
"""
super().__init__()
self.basis = basis
self.target = target

def run(self, dag):
"""Run the Unroller pass on `dag`.
Expand All @@ -49,26 +53,42 @@ def run(self, dag):
Returns:
DAGCircuit: output unrolled dag
"""
if self.basis is None:
if self.basis is None and self.target is None:
return dag
qubit_mapping = {}
if self.target is not None:
qubit_mapping = {bit: index for index, bit in enumerate(dag.qubits)}
# Walk through the DAG and expand each non-basis node
basic_insts = ["measure", "reset", "barrier", "snapshot", "delay"]
for node in dag.op_nodes():
if getattr(node.op, "_directive", False):
continue

if node.name in basic_insts:
# TODO: this is legacy behavior.Basis_insts should be removed that these
# instructions should be part of the device-reported basis. Currently, no
# backend reports "measure", for example.
continue

if node.name in self.basis: # If already a base, ignore.
if isinstance(node.op, ControlledGate) and node.op._open_ctrl:
pass
else:
run_qubits = None
if self.target is not None:
run_qubits = tuple(qubit_mapping[x] for x in node.qargs)
if (
self.target.instruction_supported(node.op.name, qargs=run_qubits)
or node.op.name == "barrier"
):
print("blue")
if isinstance(node.op, ControlledGate) and node.op._open_ctrl:
pass
else:
continue
else:
if node.name in basic_insts:
# TODO: this is legacy behavior.Basis_insts should be removed that these
# instructions should be part of the device-reported basis. Currently, no
# backend reports "measure", for example.
continue

if node.name in self.basis: # If already a base, ignore.
if isinstance(node.op, ControlledGate) and node.op._open_ctrl:
pass
else:
continue

if isinstance(node.op, ControlFlowOp):
node.op = control_flow.map_blocks(self.run, node.op)
continue
Expand All @@ -87,10 +107,16 @@ def run(self, dag):
# to substitute_node_with_dag if an the width of the definition is
# different that the width of the node.
while rule and len(rule) == 1 and len(node.qargs) == len(rule[0].qubits) == 1:
if rule[0].operation.name in self.basis:
dag.global_phase += phase
dag.substitute_node(node, rule[0].operation, inplace=True)
break
if self.target is not None:
if self.target.instruction_supported(rule[0].operation.name, run_qubits):
dag.global_phase += phase
dag.substitute_node(node, rule[0].operation, inplace=True)
break
else:
if rule[0].operation.name in self.basis:
dag.global_phase += phase
dag.substitute_node(node, rule[0].operation, inplace=True)
break
try:
phase += rule[0].operation.definition.global_phase
rule = rule[0].operation.definition.data
Expand Down
9 changes: 8 additions & 1 deletion qiskit/transpiler/passes/calibration/pulse_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
ScheduleBlock,
)
from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap
from qiskit.transpiler.target import Target

from .base_builder import CalibrationBuilder

Expand Down Expand Up @@ -50,15 +51,21 @@ class PulseGates(CalibrationBuilder):

def __init__(
self,
inst_map: InstructionScheduleMap,
inst_map: InstructionScheduleMap = None,
target: Target = None,
):
"""Create new pass.

Args:
inst_map: Instruction schedule map that user may override.
target: The :class:`~.Target` representing the target backend, if both
``inst_map`` and this are specified then this argument will take
precedence and ``inst_map`` will be ignored.
"""
super().__init__()
self.inst_map = inst_map
if target:
self.inst_map = target.instruction_schedule_map()

def supported(self, node_op: CircuitInst, qubits: List) -> bool:
"""Determine if a given node supports the calibration.
Expand Down
7 changes: 7 additions & 0 deletions qiskit/transpiler/passes/calibration/rzx_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from qiskit.pulse import builder
from qiskit.pulse.filters import filter_instructions
from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap
from qiskit.transpiler.target import Target

from .base_builder import CalibrationBuilder
from .exceptions import CalibrationNotAvailable
Expand Down Expand Up @@ -63,6 +64,7 @@ def __init__(
instruction_schedule_map: InstructionScheduleMap = None,
qubit_channel_mapping: List[List[str]] = None,
verbose: bool = True,
target: Target = None,
):
"""
Initializes a RZXGate calibration builder.
Expand All @@ -73,6 +75,9 @@ def __init__(
qubit_channel_mapping: The list mapping qubit indices to the list of
channel names that apply on that qubit.
verbose: Set True to raise a user warning when RZX schedule cannot be built.
target: The :class:`~.Target` representing the target backend, if both
``instruction_schedule_map`` and this are specified then this argument will take
precedence and ``instruction_schedule_map`` will be ignored.

Raises:
QiskitError: Instruction schedule map is not provided.
Expand All @@ -90,6 +95,8 @@ def __init__(

self._inst_map = instruction_schedule_map
self._verbose = verbose
if target:
self._inst_map = target.instruction_schedule_map()
Copy link
Contributor

Choose a reason for hiding this comment

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

Alternatively you can store target instead of the legacy instmap if available, or I can do it in follow up.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I was thinking we could optimize some of these passes to work natively with a target in the future. For this PR just having the target option is sufficient I think because my goal for 0.24.0 is to update all the preset pass managers to work solely with a target so we can start to unify the transpiler passes to only need a target in the future.


def supported(self, node_op: CircuitInst, qubits: List) -> bool:
"""Determine if a given node supports the calibration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class CommutativeCancellation(TransformationPass):
H, X, Y, Z, CX, CY, CZ
"""

def __init__(self, basis_gates=None):
def __init__(self, basis_gates=None, target=None):
"""
CommutativeCancellation initializer.

Expand All @@ -47,12 +47,17 @@ def __init__(self, basis_gates=None):
``['u3', 'cx']``. For the effects of this pass, the basis is
the set intersection between the ``basis_gates`` parameter
and the gates in the dag.
target (Target): The :class:`~.Target` representing the target backend, if both
``basis_gates`` and this are specified then this argument will take
precedence and ``basis_gates`` will be ignored.
"""
super().__init__()
if basis_gates:
self.basis = set(basis_gates)
else:
self.basis = set()
if target is not None:
self.basis = set(target.operation_names)

self._var_z_map = {"rz": RZGate, "p": PhaseGate, "u1": U1Gate}
self.requires.append(CommutationAnalysis())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from itertools import chain, combinations

from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.target import target_to_backend_properties
from qiskit.dagcircuit import DAGCircuit
from qiskit.circuit.library.standard_gates import U1Gate, U2Gate, U3Gate, CXGate
from qiskit.circuit import Measure
Expand All @@ -49,7 +50,9 @@
class CrosstalkAdaptiveSchedule(TransformationPass):
"""Crosstalk mitigation through adaptive instruction scheduling."""

def __init__(self, backend_prop, crosstalk_prop, weight_factor=0.5, measured_qubits=None):
def __init__(
self, backend_prop, crosstalk_prop, weight_factor=0.5, measured_qubits=None, target=None
):
"""CrosstalkAdaptiveSchedule initializer.

Args:
Expand Down Expand Up @@ -85,6 +88,9 @@ def __init__(self, backend_prop, crosstalk_prop, weight_factor=0.5, measured_qub
The arg is useful when a subsequent module such as state_tomography_circuits
inserts the measure gates. If CrosstalkAdaptiveSchedule is made aware of those
measurements, it is included in the optimization.
target (Target): A target representing the target backend, if both
``backend_prop`` and this are specified then this argument will take
precedence and ``coupling_map`` will be ignored.
Raises:
ImportError: if unable to import z3 solver

Expand All @@ -93,6 +99,8 @@ def __init__(self, backend_prop, crosstalk_prop, weight_factor=0.5, measured_qub

super().__init__()
self.backend_prop = backend_prop
if target is not None:
self.backend_prop = target_to_backend_properties(target)
self.crosstalk_prop = crosstalk_prop
self.weight_factor = weight_factor
if measured_qubits is None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,20 @@ class EchoRZXWeylDecomposition(TransformationPass):
Each pair of RZXGates forms an echoed RZXGate.
"""

def __init__(self, instruction_schedule_map):
def __init__(self, instruction_schedule_map=None, target=None):
"""EchoRZXWeylDecomposition pass.

Args:
instruction_schedule_map (InstructionScheduleMap): the mapping from circuit
:class:`~.circuit.Instruction` names and arguments to :class:`.Schedule`\\ s.
target (Target): The :class:`~.Target` representing the target backend, if both
``instruction_schedule_map`` and this are specified then this argument will take
precedence and ``instruction_schedule_map`` will be ignored.
"""
super().__init__()
self._inst_map = instruction_schedule_map
if target is not None:
self._inst_map = target.instruction_schedule_map()

def _is_native(self, qubit_pair: Tuple) -> bool:
"""Return the direction of the qubit pair that is native, i.e. with the shortest schedule."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,20 @@ class Optimize1qGatesSimpleCommutation(TransformationPass):
# NOTE: A run from `dag.collect_1q_runs` is always nonempty, so we sometimes use an empty list
# to signify the absence of a run.

def __init__(self, basis=None, run_to_completion=False):
def __init__(self, basis=None, run_to_completion=False, target=None):
"""
Args:
basis (List[str]): See also `Optimize1qGatesDecomposition`.
run_to_completion (bool): If `True`, this pass retries until it is unable to do any more
work. If `False`, it finds and performs one optimization, and for full optimization
the user is obligated to re-call the pass until the output stabilizes.
target (Target): The :class:`~.Target` representing the target backend, if both
``basis`` and this are specified then this argument will take
precedence and ``basis`` will be ignored.
"""
super().__init__()

self._basis = basis
self._optimize1q = Optimize1qGatesDecomposition(basis)
self._optimize1q = Optimize1qGatesDecomposition(basis=basis, target=target)
self._run_to_completion = run_to_completion

@staticmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ def _resynthesize_run(self, run, qubit=None):
operator = gate.op.to_matrix().dot(operator)

if self._target:
qubits_tuple = (qubit,)
if qubit is not None:
qubits_tuple = (qubit,)
else:
qubits_tuple = None
if qubits_tuple in self._local_decomposers_cache:
decomposers = self._local_decomposers_cache[qubits_tuple]
else:
Expand Down
60 changes: 45 additions & 15 deletions qiskit/transpiler/passes/optimization/optimize_1q_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,22 @@
class Optimize1qGates(TransformationPass):
"""Optimize chains of single-qubit u1, u2, u3 gates by combining them into a single gate."""

def __init__(self, basis=None, eps=1e-15):
def __init__(self, basis=None, eps=1e-15, target=None):
"""Optimize1qGates initializer.

Args:
basis (list[str]): Basis gates to consider, e.g. `['u3', 'cx']`. For the effects
of this pass, the basis is the set intersection between the `basis` parameter and
the set `{'u1','u2','u3', 'u', 'p'}`.
eps (float): EPS to check against
target (Target): The :class:`~.Target` representing the target backend, if both
``basis`` and this are specified then this argument will take
precedence and ``basis`` will be ignored.
"""
super().__init__()
self.basis = basis if basis else ["u1", "u2", "u3"]
self.basis = set(basis) if basis else {"u1", "u2", "u3"}
self.eps = eps
self.target = target

def run(self, dag):
"""Run the Optimize1qGates pass on `dag`.
Expand All @@ -62,12 +66,24 @@ def run(self, dag):
use_u = "u" in self.basis
use_p = "p" in self.basis
runs = dag.collect_runs(["u1", "u2", "u3", "u", "p"])
qubit_mapping = {}
if self.target is not None:
qubit_mapping = {bit: index for index, bit in enumerate(dag.qubits)}
runs = _split_runs_on_parameters(runs)
for run in runs:
if use_p:
right_name = "p"
run_qubits = None
if self.target is not None:
run_qubits = tuple(qubit_mapping[x] for x in run[0].qargs)

if self.target.instruction_supported("p", run_qubits):
right_name = "p"
else:
right_name = "u1"
else:
right_name = "u1"
if use_p:
right_name = "p"
else:
right_name = "u1"
right_parameters = (0, 0, 0) # (theta, phi, lambda)
right_global_phase = 0
for current_node in run:
Expand Down Expand Up @@ -256,16 +272,30 @@ def run(self, dag):
):
right_name = "nop"

if right_name == "u2" and "u2" not in self.basis:
if use_u:
right_name = "u"
else:
right_name = "u3"
if right_name in ("u1", "p") and right_name not in self.basis:
if use_u:
right_name = "u"
else:
right_name = "u3"
if self.target is not None:
if right_name == "u2" and not self.target.instruction_supported("u2", run_qubits):
if self.target.instruction_supported("u", run_qubits):
right_name = "u"
else:
right_name = "u3"
if right_name in ("u1", "p") and not self.target.instruction_supported(
right_name, run_qubits
):
if self.target.instruction_supported("u", run_qubits):
right_name = "u"
else:
right_name = "u3"
else:
if right_name == "u2" and "u2" not in self.basis:
if use_u:
right_name = "u"
else:
right_name = "u3"
if right_name in ("u1", "p") and right_name not in self.basis:
if use_u:
right_name = "u"
else:
right_name = "u3"

new_op = Gate(name="", num_qubits=1, params=[])
if right_name == "u1":
Expand Down
Loading