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

Handle deprecations in DD passes #2131

Closed
wants to merge 2 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from qiskit.circuit.library import Barrier
from qiskit.circuit.delay import Delay
from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.circuit.controlflow import condition_resources
from qiskit.converters import dag_to_circuit
from qiskit.dagcircuit import DAGCircuit, DAGNode, DAGOpNode
from qiskit.transpiler.basepasses import TransformationPass
Expand Down Expand Up @@ -197,14 +198,8 @@ def _empty_dag_like(

new_dag.name = dag.name
new_dag.metadata = dag.metadata
new_dag.unit = self.property_set["time_unit"] or "dt"
if new_dag.unit != "dt":
raise TranspilerError(
'All blocks must have time units of "dt". '
"Please run TimeUnitConversion pass prior to padding."
)

new_dag.calibrations = dag.calibrations
new_dag._calibrations_prop = dag._calibrations_prop
new_dag.global_phase = dag.global_phase
return new_dag

Expand Down Expand Up @@ -262,17 +257,13 @@ def _pad(

def _get_node_duration(self, node: DAGNode) -> int:
"""Get the duration of a node."""
if node.op.condition_bits or isinstance(node.op, ControlFlowOp):
# As we cannot currently schedule through conditionals model
# as zero duration to avoid padding.
return 0

indices = [self._bit_indices[qarg] for qarg in self._map_wires(node.qargs)]

if self._block_dag.has_calibration_for(node):
if self._block_dag._has_calibration_for(node):
# If node has calibration, this value should be the highest priority
cal_key = tuple(indices), tuple(float(p) for p in node.op.params)
duration = self._block_dag.calibrations[node.op.name][cal_key].duration
duration = self._block_dag._calibrations_prop[node.op.name][cal_key].duration

else:
duration = self._durations.get(node.op, indices, unit="dt")

Expand Down Expand Up @@ -410,7 +401,6 @@ def _visit_node(self, node: DAGNode, enable_dd: bool = False) -> None:
def _visit_if_else_op(self, node: DAGNode) -> None:
"""check if is fast-path eligible otherwise fall back
to standard ControlFlowOp handling."""

if self._will_use_fast_path(node):
self._fast_path_nodes.add(node)
self._visit_control_flow_op(node)
Expand All @@ -422,7 +412,7 @@ def _will_use_fast_path(self, node: DAGNode) -> bool:
2. The operation only operates on the qubit that is measured.
"""
# Verify IfElseOp has a direct measurement predecessor
condition_bits = node.op.condition_bits
condition_bits = list(condition_resources(node.op.condition).clbits)
# Fast-path valid only with a single bit.
if not condition_bits or len(condition_bits) > 1:
return False
Expand Down Expand Up @@ -517,8 +507,6 @@ def _visit_delay(self, node: DAGNode) -> None:
self._terminate_block(self._block_duration, self._current_block_idx)
self._add_block_terminating_barrier(block_idx, t0, node)

self._conditional_block = bool(node.op.condition_bits)

self._current_block_idx = block_idx

t1 = t0 + self._get_node_duration(node) # pylint: disable=invalid-name
Expand All @@ -537,7 +525,6 @@ def _visit_generic(self, node: DAGNode, enable_dd: bool = False) -> None:

# This block will not be padded as it is conditional.
# See TODO below.
self._conditional_block = bool(node.op.condition_bits)

# Now set the current block index.
self._current_block_idx = block_idx
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import numpy as np
import rustworkx as rx
from qiskit.circuit import Qubit, Gate
from qiskit.circuit import Qubit, Gate, ParameterExpression
from qiskit.circuit.delay import Delay
from qiskit.circuit.library.standard_gates import IGate, UGate, U3Gate
from qiskit.circuit.reset import Reset
Expand Down Expand Up @@ -350,7 +350,14 @@ def _pre_runhook(self, dag: DAGCircuit) -> None:
for index, gate in enumerate(seq):
try:
# Check calibration.
gate_length = dag.calibrations[gate.name][(physical_index, gate.params)]
params = self._resolve_params(gate)
with warnings.catch_warnings():
warnings.simplefilter(action="ignore", category=DeprecationWarning)
# `schedule.duration` emits pulse deprecation warnings which we don't want
# to see here
gate_length = dag._calibrations_prop[gate.name][
((physical_index,), params)
].duration
if gate_length % self._alignment != 0:
# This is necessary to implement lightweight scheduling logic for this pass.
# Usually the pulse alignment constraint and pulse data chunk size take
Expand Down Expand Up @@ -421,10 +428,12 @@ def _pad(

if self._qubits and self._block_dag.qubits.index(qubit) not in self._qubits:
# Target physical qubit is not the target of this DD sequence.
self._apply_scheduled_op(
block_idx, t_start, Delay(time_interval, self._block_dag.unit), qubit
)
return
with warnings.catch_warnings():
warnings.simplefilter(action="ignore", category=DeprecationWarning)
self._apply_scheduled_op(
block_idx, t_start, Delay(time_interval, self._block_dag.unit), qubit
)
return

if not self._skip_reset_qubits and qubit not in self._dirty_qubits:
# mark all qubits as dirty if skip_reset_qubits is False
Expand All @@ -441,10 +450,12 @@ def _pad(
if qubit not in self._dirty_qubits or (self._dd_barrier and not enable_dd):
# Previous node is the start edge or reset, i.e. qubit is ground state;
# or dd to be applied before named barrier only
self._apply_scheduled_op(
block_idx, t_start, Delay(time_interval, self._block_dag.unit), qubit
)
return
with warnings.catch_warnings():
warnings.simplefilter(action="ignore", category=DeprecationWarning)
self._apply_scheduled_op(
block_idx, t_start, Delay(time_interval, self._block_dag.unit), qubit
)
return

for sequence_idx, _ in enumerate(self._dd_sequences):
dd_sequence = self._dd_sequences[sequence_idx]
Expand Down Expand Up @@ -513,13 +524,15 @@ def _pad(
sequence_gphase += phase
else:
# Don't do anything if there's no single-qubit gate to absorb the inverse
self._apply_scheduled_op(
block_idx,
t_start,
Delay(time_interval, self._block_dag.unit),
qubit,
)
return
with warnings.catch_warnings():
warnings.simplefilter(action="ignore", category=DeprecationWarning)
self._apply_scheduled_op(
block_idx,
t_start,
Delay(time_interval, self._block_dag.unit),
qubit,
)
return

def _constrained_length(values: np.array) -> np.array:
return self._alignment * np.floor(values / self._alignment)
Expand Down Expand Up @@ -560,10 +573,12 @@ def _constrained_length(values: np.array) -> np.array:
# Interleave delays with DD sequence operations
for tau_idx, tau in enumerate(taus):
if tau > 0:
self._apply_scheduled_op(
block_idx, idle_after, Delay(tau, self._dag.unit), qubit
)
idle_after += tau
with warnings.catch_warnings():
warnings.simplefilter(action="ignore", category=DeprecationWarning)
self._apply_scheduled_op(
block_idx, idle_after, Delay(tau, self._dag.unit), qubit
)
idle_after += tau

# Detect if we are on a sequence boundary
# If so skip insert of sequence to allow delays to combine
Expand All @@ -583,7 +598,20 @@ def _constrained_length(values: np.array) -> np.array:
return

# DD could not be applied, delay instead
self._apply_scheduled_op(
block_idx, t_start, Delay(time_interval, self._block_dag.unit), qubit
)
return
with warnings.catch_warnings():
warnings.simplefilter(action="ignore", category=DeprecationWarning)
self._apply_scheduled_op(
block_idx, t_start, Delay(time_interval, self._block_dag.unit), qubit
)
return

@staticmethod
def _resolve_params(gate: Gate) -> tuple:
"""Return gate params with any bound parameters replaced with floats"""
params = []
for p in gate.params:
if isinstance(p, ParameterExpression) and not p.parameters:
params.append(float(p))
else:
params.append(p)
return tuple(params)
12 changes: 3 additions & 9 deletions qiskit_ibm_runtime/transpiler/passes/scheduling/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,6 @@ def _visit_block(self, block: DAGCircuit, wire_map: Dict[Qubit, Qubit]) -> None:
def _visit_node(self, node: DAGNode) -> None:
if isinstance(node.op, ControlFlowOp):
self._visit_control_flow_op(node)
elif node.op.condition_bits:
raise TranspilerError(
"c_if control-flow is not supported by this pass. "
'Please apply "ConvertConditionsToIfOps" to convert these '
"conditional operations to new-style Qiskit control-flow."
)
else:
if isinstance(node.op, Measure):
self._visit_measure(node)
Expand Down Expand Up @@ -208,7 +202,7 @@ def _init_run(self, dag: DAGCircuit) -> None:
self._bit_indices = {q: index for index, q in enumerate(dag.qubits)}

def _get_duration(self, node: DAGNode, dag: Optional[DAGCircuit] = None) -> int:
if node.op.condition_bits or isinstance(node.op, ControlFlowOp):
if isinstance(node.op, ControlFlowOp):
# As we cannot currently schedule through conditionals model
# as zero duration to avoid padding.
return 0
Expand All @@ -218,10 +212,10 @@ def _get_duration(self, node: DAGNode, dag: Optional[DAGCircuit] = None) -> int:
# Fall back to current block dag if not specified.
dag = dag or self._block_dag

if dag.has_calibration_for(node):
if dag._has_calibration_for(node):
# If node has calibration, this value should be the highest priority
cal_key = tuple(indices), tuple(float(p) for p in node.op.params)
duration = dag.calibrations[node.op.name][cal_key].duration
duration = dag._calibrations_prop[node.op.name][cal_key].duration

op = node.op.to_mutable()
op.duration = duration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from numpy import pi

from ddt import ddt, data
from qiskit import pulse
from qiskit.circuit import QuantumCircuit, Delay
from qiskit.circuit.library import XGate, YGate, RXGate, UGate
from qiskit.quantum_info import Operator
Expand Down Expand Up @@ -474,41 +473,6 @@ def test_insert_dd_bad_sequence(self):
with self.assertRaises(TranspilerError):
pm.run(self.ghz4)

@data(0.5, 1.5)
def test_dd_with_calibrations_with_parameters(self, param_value):
"""Check that calibrations in a circuit with parameters work fine."""

circ = QuantumCircuit(2)
circ.x(0)
circ.cx(0, 1)
circ.rx(param_value, 1)

rx_duration = int(param_value * 1000)

with pulse.build() as rx:
pulse.play(
pulse.Gaussian(rx_duration, 0.1, rx_duration // 4),
pulse.DriveChannel(1),
)

circ.add_calibration("rx", (1,), rx, params=[param_value])

durations = DynamicCircuitInstructionDurations([("x", None, 100), ("cx", None, 300)])

dd_sequence = [XGate(), XGate()]
pm = PassManager(
[
ASAPScheduleAnalysis(durations),
PadDynamicalDecoupling(durations, dd_sequence, schedule_idle_qubits=True),
]
)
dd_circuit = pm.run(circ)

for instruction in dd_circuit.data:
op = instruction.operation
if isinstance(op, RXGate):
self.assertEqual(op._params, [param_value])

def test_insert_dd_ghz_xy4_with_alignment(self):
"""Test DD with pulse alignment constraints."""
dd_sequence = [XGate(), YGate(), XGate(), YGate()]
Expand Down
Loading