diff --git a/qiskit/transpiler/passes/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/alap.py index 935df96a063f..5059656058f3 100644 --- a/qiskit/transpiler/passes/scheduling/alap.py +++ b/qiskit/transpiler/passes/scheduling/alap.py @@ -137,7 +137,7 @@ def run(self, dag): for bit in node.qargs: delta = t0 - idle_before[bit] - if delta > 0: + if delta > 0 and self._delay_supported(bit_indices[bit]): new_dag.apply_operation_front(Delay(delta, time_unit), [bit], []) idle_before[bit] = t1 @@ -148,7 +148,8 @@ def run(self, dag): delta = circuit_duration - before if not (delta > 0 and isinstance(bit, Qubit)): continue - new_dag.apply_operation_front(Delay(delta, time_unit), [bit], []) + if self._delay_supported(bit_indices[bit]): + new_dag.apply_operation_front(Delay(delta, time_unit), [bit], []) new_dag.name = dag.name new_dag.metadata = dag.metadata diff --git a/qiskit/transpiler/passes/scheduling/asap.py b/qiskit/transpiler/passes/scheduling/asap.py index 331a0fcd42f7..b404e73d00a7 100644 --- a/qiskit/transpiler/passes/scheduling/asap.py +++ b/qiskit/transpiler/passes/scheduling/asap.py @@ -150,7 +150,7 @@ def run(self, dag): # Add delay to qubit wire for bit in node.qargs: delta = t0 - idle_after[bit] - if delta > 0 and isinstance(bit, Qubit): + if delta > 0 and isinstance(bit, Qubit) and self._delay_supported(bit_indices[bit]): new_dag.apply_operation_back(Delay(delta, time_unit), [bit], []) idle_after[bit] = t1 @@ -161,7 +161,8 @@ def run(self, dag): delta = circuit_duration - after if not (delta > 0 and isinstance(bit, Qubit)): continue - new_dag.apply_operation_back(Delay(delta, time_unit), [bit], []) + if self._delay_supported(bit_indices[bit]): + new_dag.apply_operation_back(Delay(delta, time_unit), [bit], []) new_dag.name = dag.name new_dag.metadata = dag.metadata diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py index bc96aacc2d57..9f851d56b7e0 100644 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/base_scheduler.py @@ -245,15 +245,16 @@ def __init__( """ super().__init__() self.durations = durations + # Ensure op node durations are attached and in consistent unit + if target is not None: + self.durations = target.durations() + self.requires.append(TimeUnitConversion(self.durations)) # Control flow constraints. self.clbit_write_latency = clbit_write_latency self.conditional_latency = conditional_latency - # Ensure op node durations are attached and in consistent unit - self.requires.append(TimeUnitConversion(durations)) - if target is not None: - self.durations = target.durations() + self.target = target @staticmethod def _get_node_duration( @@ -281,5 +282,11 @@ def _get_node_duration( return duration + def _delay_supported(self, qarg: int) -> bool: + """Delay operation is supported on the qubit (qarg) or not.""" + if self.target is None or self.target.instruction_supported("delay", qargs=(qarg,)): + return True + return False + def run(self, dag: DAGCircuit): raise NotImplementedError diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py index 9dfeb8454161..fa0b2b29187e 100644 --- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py @@ -16,8 +16,7 @@ import warnings import numpy as np -from qiskit.circuit.delay import Delay -from qiskit.circuit.reset import Reset +from qiskit.circuit import Gate, Delay, Reset from qiskit.circuit.library.standard_gates import IGate, UGate, U3Gate from qiskit.dagcircuit import DAGOpNode, DAGInNode from qiskit.quantum_info.operators.predicates import matrix_equal @@ -128,8 +127,14 @@ def __init__( self._qubits = qubits self._spacing = spacing self._skip_reset_qubits = skip_reset_qubits + self._target = target if target is not None: self._durations = target.durations() + for gate in dd_sequence: + if gate.name not in target.operation_names: + raise TranspilerError( + f"{gate.name} in dd_sequence is not supported in the target" + ) def run(self, dag): """Run the DynamicalDecoupling pass on dag. @@ -178,18 +183,22 @@ def run(self, dag): end = mid / 2 self._spacing = [end] + [mid] * (num_pulses - 1) + [end] - new_dag = dag.copy_empty_like() + for qarg in list(self._qubits): + for gate in self._dd_sequence: + if not self.__gate_supported(gate, qarg): + self._qubits.discard(qarg) + break - qubit_index_map = {qubit: index for index, qubit in enumerate(new_dag.qubits)} index_sequence_duration_map = {} - for qubit in new_dag.qubits: - physical_qubit = qubit_index_map[qubit] + for physical_qubit in self._qubits: dd_sequence_duration = 0 for gate in self._dd_sequence: gate.duration = self._durations.get(gate, physical_qubit) dd_sequence_duration += gate.duration index_sequence_duration_map[physical_qubit] = dd_sequence_duration + new_dag = dag.copy_empty_like() + qubit_index_map = {qubit: index for index, qubit in enumerate(new_dag.qubits)} for nd in dag.topological_op_nodes(): if not isinstance(nd.op, Delay): new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) @@ -252,6 +261,12 @@ def run(self, dag): return new_dag + def __gate_supported(self, gate: Gate, qarg: int) -> bool: + """A gate is supported on the qubit (qarg) or not.""" + if self._target is None or self._target.instruction_supported(gate.name, qargs=(qarg,)): + return True + return False + def _mod_2pi(angle: float, atol: float = 0): """Wrap angle into interval [-π,π). If within atol of the endpoint, clamp to -π""" diff --git a/qiskit/transpiler/passes/scheduling/padding/base_padding.py b/qiskit/transpiler/passes/scheduling/padding/base_padding.py index 0e3126d74f50..5fb25790cfbb 100644 --- a/qiskit/transpiler/passes/scheduling/padding/base_padding.py +++ b/qiskit/transpiler/passes/scheduling/padding/base_padding.py @@ -12,6 +12,7 @@ """Padding pass to fill empty timeslot.""" +import logging from typing import List, Optional, Union from qiskit.circuit import Qubit, Clbit, Instruction @@ -19,6 +20,9 @@ from qiskit.dagcircuit import DAGCircuit, DAGNode from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.target import Target + +logger = logging.getLogger(__name__) class BasePadding(TransformationPass): @@ -49,6 +53,20 @@ class BasePadding(TransformationPass): which may result in violation of hardware alignment constraints. """ + def __init__( + self, + target: Target = None, + ): + """BasePadding initializer. + + Args: + target: The :class:`~.Target` representing the target backend. + If it supplied and it does not support delay instruction on a qubit, + padding passes do not pad any idle time of the qubit. + """ + super().__init__() + self.target = target + def run(self, dag: DAGCircuit): """Run the padding pass on ``dag``. @@ -83,6 +101,7 @@ def run(self, dag: DAGCircuit): new_dag.calibrations = dag.calibrations new_dag.global_phase = dag.global_phase + bit_indices = {q: index for index, q in enumerate(dag.qubits)} idle_after = {bit: 0 for bit in dag.qubits} # Compute fresh circuit duration from the node start time dictionary and op duration. @@ -104,9 +123,8 @@ def run(self, dag: DAGCircuit): continue for bit in node.qargs: - # Fill idle time with some sequence - if t0 - idle_after[bit] > 0: + if t0 - idle_after[bit] > 0 and self.__delay_supported(bit_indices[bit]): # Find previous node on the wire, i.e. always the latest node on the wire prev_node = next(new_dag.predecessors(new_dag.output_map[bit])) self._pad( @@ -129,7 +147,7 @@ def run(self, dag: DAGCircuit): # Add delays until the end of circuit. for bit in new_dag.qubits: - if circuit_duration - idle_after[bit] > 0: + if circuit_duration - idle_after[bit] > 0 and self.__delay_supported(bit_indices[bit]): node = new_dag.output_map[bit] prev_node = next(new_dag.predecessors(node)) self._pad( @@ -145,6 +163,12 @@ def run(self, dag: DAGCircuit): return new_dag + def __delay_supported(self, qarg: int) -> bool: + """Delay operation is supported on the qubit (qarg) or not.""" + if self.target is None or self.target.instruction_supported("delay", qargs=(qarg,)): + return True + return False + def _pre_runhook(self, dag: DAGCircuit): """Extra routine inserted before running the padding pass. @@ -159,6 +183,12 @@ def _pre_runhook(self, dag: DAGCircuit): f"The input circuit {dag.name} is not scheduled. Call one of scheduling passes " f"before running the {self.__class__.__name__} pass." ) + for qarg, _ in enumerate(dag.qubits): + if not self.__delay_supported(qarg): + logger.debug( + "No padding on qubit %d as delay is not supported on it", + qarg, + ) def _apply_scheduled_op( self, diff --git a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py index f0a24ea810a9..e3923ed8b26e 100644 --- a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py @@ -12,6 +12,7 @@ """Dynamical Decoupling insertion pass.""" +import logging from typing import List, Optional import numpy as np @@ -29,6 +30,8 @@ from .base_padding import BasePadding +logger = logging.getLogger(__name__) + class PadDynamicalDecoupling(BasePadding): """Dynamical decoupling insertion pass. @@ -152,7 +155,7 @@ def __init__( non-multiple of the alignment constraint value is found. TypeError: If ``dd_sequence`` is not specified """ - super().__init__() + super().__init__(target=target) self._durations = durations if dd_sequence is None: raise TypeError("required argument 'dd_sequence' is not specified") @@ -163,10 +166,16 @@ def __init__( self._spacing = spacing self._extra_slack_distribution = extra_slack_distribution + self._no_dd_qubits = set() self._dd_sequence_lengths = {} self._sequence_phase = 0 if target is not None: self._durations = target.durations() + for gate in dd_sequence: + if gate.name not in target.operation_names: + raise TranspilerError( + f"{gate.name} in dd_sequence is not supported in the target" + ) def _pre_runhook(self, dag: DAGCircuit): super()._pre_runhook(dag) @@ -200,10 +209,18 @@ def _pre_runhook(self, dag: DAGCircuit): raise TranspilerError("The DD sequence does not make an identity operation.") self._sequence_phase = np.angle(noop[0][0]) + # Compute no DD qubits on which any gate in dd_sequence is not supported in the target + for qarg, _ in enumerate(dag.qubits): + for gate in self._dd_sequence: + if not self.__gate_supported(gate, qarg): + self._no_dd_qubits.add(qarg) + logger.debug( + "No DD on qubit %d as gate %s is not supported on it", qarg, gate.name + ) + break # Precompute qubit-wise DD sequence length for performance - for qubit in dag.qubits: - physical_index = dag.qubits.index(qubit) - if self._qubits and physical_index not in self._qubits: + for physical_index, qubit in enumerate(dag.qubits): + if not self.__is_dd_qubit(physical_index): continue sequence_lengths = [] @@ -231,6 +248,20 @@ def _pre_runhook(self, dag: DAGCircuit): gate.duration = gate_length self._dd_sequence_lengths[qubit] = sequence_lengths + def __gate_supported(self, gate: Gate, qarg: int) -> bool: + """A gate is supported on the qubit (qarg) or not.""" + if self.target is None or self.target.instruction_supported(gate.name, qargs=(qarg,)): + return True + return False + + def __is_dd_qubit(self, qubit_index: int) -> bool: + """DD can be inserted in the qubit or not.""" + if (qubit_index in self._no_dd_qubits) or ( + self._qubits and qubit_index not in self._qubits + ): + return False + return True + def _pad( self, dag: DAGCircuit, @@ -268,7 +299,7 @@ def _pad( # Y3: 288 dt + 160 dt + 80 dt = 528 dt (33 x 16 dt) # Y4: 368 dt + 160 dt + 80 dt = 768 dt (48 x 16 dt) # - # As you can see, constraints on t0 are all satified without explicit scheduling. + # As you can see, constraints on t0 are all satisfied without explicit scheduling. time_interval = t_end - t_start if time_interval % self._alignment != 0: raise TranspilerError( @@ -277,7 +308,7 @@ def _pad( f"on qargs {next_node.qargs}." ) - if self._qubits and dag.qubits.index(qubit) not in self._qubits: + if not self.__is_dd_qubit(dag.qubits.index(qubit)): # Target physical qubit is not the target of this DD sequence. self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag.unit), qubit) return diff --git a/qiskit/transpiler/passes/scheduling/padding/pad_delay.py b/qiskit/transpiler/passes/scheduling/padding/pad_delay.py index c0a12267211a..f1517900874f 100644 --- a/qiskit/transpiler/passes/scheduling/padding/pad_delay.py +++ b/qiskit/transpiler/passes/scheduling/padding/pad_delay.py @@ -15,6 +15,7 @@ from qiskit.circuit import Qubit from qiskit.circuit.delay import Delay from qiskit.dagcircuit import DAGCircuit, DAGNode, DAGOutNode +from qiskit.transpiler.target import Target from .base_padding import BasePadding @@ -50,13 +51,16 @@ class PadDelay(BasePadding): See :class:`BasePadding` pass for details. """ - def __init__(self, fill_very_end: bool = True): + def __init__(self, fill_very_end: bool = True, target: Target = None): """Create new padding delay pass. Args: fill_very_end: Set ``True`` to fill the end of circuit with delay. + target: The :class:`~.Target` representing the target backend. + If it supplied and it does not support delay instruction on a qubit, + padding passes do not pad any idle time of the qubit. """ - super().__init__() + super().__init__(target=target) self.fill_very_end = fill_very_end def _pad( diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 7cd3221c84ae..d8d3bc400e50 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -534,7 +534,7 @@ def _require_alignment(property_set): ) if scheduling_method: # Call padding pass if circuit is scheduled - scheduling.append(PadDelay()) + scheduling.append(PadDelay(target=target)) return scheduling diff --git a/releasenotes/notes/fix-delay-padding-75937bda37ebc3fd.yaml b/releasenotes/notes/fix-delay-padding-75937bda37ebc3fd.yaml new file mode 100644 index 000000000000..34c42ab5baf8 --- /dev/null +++ b/releasenotes/notes/fix-delay-padding-75937bda37ebc3fd.yaml @@ -0,0 +1,13 @@ +--- +fixes: + - | + Fixed an issue in tranpiler passes for padding delays, which did not respect target's constraints + and inserted delays even for qubits not supporting :class:`~.circuit.Delay` instruction. + :class:`~.PadDelay` and :class:`~.PadDynamicalDecoupling` are fixed + so that they do not pad any idle time of qubits such that the target does not support + ``Delay`` instructions for the qubits. + Also legacy scheduling passes ``ASAPSchedule`` and ``ALAPSchedule``, + which pad delays internally, are fixed in the same way. + In addition, :func:`transpile` is fixed to call ``PadDelay`` with a ``target`` object + so that it works correctly when called with ``scheduling_method`` option. + Fixed `#9993 `__ diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 238a77ea699e..2f03fd5112b1 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -2910,3 +2910,51 @@ def test_transpile_does_not_affect_backend_coupling(self, opt_level): original_map = copy.deepcopy(backend.coupling_map) transpile(qc, backend, optimization_level=opt_level) self.assertEqual(original_map, backend.coupling_map) + + @combine( + optimization_level=[0, 1, 2, 3], + scheduling_method=["asap", "alap"], + ) + def test_transpile_target_with_qubits_without_delays_with_scheduling( + self, optimization_level, scheduling_method + ): + """Test qubits without operations aren't ever used.""" + no_delay_qubits = [1, 3, 4] + target = Target(num_qubits=5, dt=1) + target.add_instruction( + XGate(), {(i,): InstructionProperties(duration=160) for i in range(4)} + ) + target.add_instruction( + HGate(), {(i,): InstructionProperties(duration=160) for i in range(4)} + ) + target.add_instruction( + CXGate(), + { + edge: InstructionProperties(duration=800) + for edge in [(0, 1), (1, 2), (2, 0), (2, 3)] + }, + ) + target.add_instruction( + Delay(Parameter("t")), {(i,): None for i in range(4) if i not in no_delay_qubits} + ) + qc = QuantumCircuit(4) + qc.x(0) + qc.cx(0, 1) + qc.cx(0, 2) + qc.cx(1, 3) + qc.cx(0, 3) + tqc = transpile( + qc, + target=target, + optimization_level=optimization_level, + scheduling_method=scheduling_method, + ) + invalid_qubits = { + 4, + } + self.assertEqual(tqc.num_qubits, 5) + for inst in tqc.data: + for bit in inst.qubits: + self.assertNotIn(tqc.find_bit(bit).index, invalid_qubits) + if isinstance(inst.operation, Delay): + self.assertNotIn(tqc.find_bit(bit).index, no_delay_qubits) diff --git a/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py b/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py index f195143d32d4..2f375c46f67b 100644 --- a/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py +++ b/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py @@ -15,11 +15,16 @@ import unittest from ddt import ddt, data, unpack + from qiskit import QuantumCircuit +from qiskit.circuit import Delay, Parameter +from qiskit.circuit.library.standard_gates import XGate, YGate, CXGate from qiskit.test import QiskitTestCase +from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.instruction_durations import InstructionDurations -from qiskit.transpiler.passes import ASAPSchedule, ALAPSchedule +from qiskit.transpiler.passes import ASAPSchedule, ALAPSchedule, DynamicalDecoupling from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.target import Target, InstructionProperties @ddt @@ -718,6 +723,89 @@ def test_dag_introduces_extra_dependency_between_conditionals(self): self.assertEqual(expected, scheduled) + @data(ALAPSchedule, ASAPSchedule) + def test_respect_target_instruction_constraints(self, schedule_pass): + """Test if ALAP/ASAP does not pad delays for qubits that do not support delay instructions. + See: https://github.com/Qiskit/qiskit-terra/issues/9993 + """ + target = Target(dt=1) + target.add_instruction(XGate(), {(1,): InstructionProperties(duration=200)}) + # delays are not supported + + qc = QuantumCircuit(2) + qc.x(1) + + pm = PassManager(schedule_pass(target=target)) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2) + expected.x(1) + # no delay on qubit 0 + + self.assertEqual(expected, scheduled) + + def test_dd_respect_target_instruction_constraints(self): + """Test if DD pass does not pad delays for qubits that do not support delay instructions + and does not insert DD gates for qubits that do not support necessary gates. + See: https://github.com/Qiskit/qiskit-terra/issues/9993 + """ + qc = QuantumCircuit(3) + qc.cx(0, 1) + qc.cx(1, 2) + + target = Target(dt=1) + # Y is partially supported (not supported on qubit 2) + target.add_instruction( + XGate(), {(q,): InstructionProperties(duration=100) for q in range(2)} + ) + target.add_instruction( + CXGate(), + { + (0, 1): InstructionProperties(duration=1000), + (1, 2): InstructionProperties(duration=1000), + }, + ) + # delays are not supported + + # No DD instructions nor delays are padded due to no delay support in the target + pm_xx = PassManager( + [ + ALAPSchedule(target=target), + DynamicalDecoupling(durations=None, dd_sequence=[XGate(), XGate()], target=target), + ] + ) + scheduled = pm_xx.run(qc) + self.assertEqual(qc, scheduled) + + # Fails since Y is not supported in the target + with self.assertRaises(TranspilerError): + PassManager( + [ + ALAPSchedule(target=target), + DynamicalDecoupling( + durations=None, + dd_sequence=[XGate(), YGate(), XGate(), YGate()], + target=target, + ), + ] + ) + + # Add delay support to the target + target.add_instruction(Delay(Parameter("t")), {(q,): None for q in range(3)}) + # No error but no DD on qubit 2 (just delay is padded) since X is not supported on it + scheduled = pm_xx.run(qc) + + expected = QuantumCircuit(3) + expected.delay(1000, [2]) + expected.cx(0, 1) + expected.cx(1, 2) + expected.delay(200, [0]) + expected.x([0]) + expected.delay(400, [0]) + expected.x([0]) + expected.delay(200, [0]) + self.assertEqual(expected, scheduled) + if __name__ == "__main__": unittest.main() diff --git a/test/python/transpiler/test_dynamical_decoupling.py b/test/python/transpiler/test_dynamical_decoupling.py index ca57a82239ed..bd8c44ab0973 100644 --- a/test/python/transpiler/test_dynamical_decoupling.py +++ b/test/python/transpiler/test_dynamical_decoupling.py @@ -195,6 +195,7 @@ def test_insert_dd_ghz_with_target(self): target.add_instruction( Reset(), {(x,): InstructionProperties(duration=1500) for x in range(4)} ) + target.add_instruction(Delay(Parameter("t")), {(x,): None for x in range(4)}) dd_sequence = [XGate(), XGate()] pm = PassManager( [ @@ -822,6 +823,66 @@ def test_dd_can_sequentially_called(self): self.assertEqual(circ1, circ2) + def test_respect_target_instruction_constraints(self): + """Test if DD pass does not pad delays for qubits that do not support delay instructions + and does not insert DD gates for qubits that do not support necessary gates. + See: https://github.com/Qiskit/qiskit-terra/issues/9993 + """ + qc = QuantumCircuit(3) + qc.cx(0, 1) + qc.cx(1, 2) + + target = Target(dt=1) + # Y is partially supported (not supported on qubit 2) + target.add_instruction( + XGate(), {(q,): InstructionProperties(duration=100) for q in range(2)} + ) + target.add_instruction( + CXGate(), + { + (0, 1): InstructionProperties(duration=1000), + (1, 2): InstructionProperties(duration=1000), + }, + ) + # delays are not supported + + # No DD instructions nor delays are padded due to no delay support in the target + pm_xx = PassManager( + [ + ALAPScheduleAnalysis(target=target), + PadDynamicalDecoupling(dd_sequence=[XGate(), XGate()], target=target), + ] + ) + scheduled = pm_xx.run(qc) + self.assertEqual(qc, scheduled) + + # Fails since Y is not supported in the target + with self.assertRaises(TranspilerError): + PassManager( + [ + ALAPScheduleAnalysis(target=target), + PadDynamicalDecoupling( + dd_sequence=[XGate(), YGate(), XGate(), YGate()], target=target + ), + ] + ) + + # Add delay support to the target + target.add_instruction(Delay(Parameter("t")), {(q,): None for q in range(3)}) + # No error but no DD on qubit 2 (just delay is padded) since X is not supported on it + scheduled = pm_xx.run(qc) + + expected = QuantumCircuit(3) + expected.delay(1000, [2]) + expected.cx(0, 1) + expected.cx(1, 2) + expected.delay(200, [0]) + expected.x([0]) + expected.delay(400, [0]) + expected.x([0]) + expected.delay(200, [0]) + self.assertEqual(expected, scheduled) + if __name__ == "__main__": unittest.main() diff --git a/test/python/transpiler/test_scheduling_padding_pass.py b/test/python/transpiler/test_scheduling_padding_pass.py index 29d086a44163..c981f98649b6 100644 --- a/test/python/transpiler/test_scheduling_padding_pass.py +++ b/test/python/transpiler/test_scheduling_padding_pass.py @@ -851,6 +851,23 @@ def test_no_pad_very_end_of_circuit(self): self.assertEqual(scheduled, qc) + @data(ALAPScheduleAnalysis, ASAPScheduleAnalysis) + def test_respect_target_instruction_constraints(self, schedule_pass): + """Test if DD pass does not pad delays for qubits that do not support delay instructions. + See: https://github.com/Qiskit/qiskit-terra/issues/9993 + """ + qc = QuantumCircuit(3) + qc.cx(1, 2) + + target = Target(dt=1) + target.add_instruction(CXGate(), {(1, 2): InstructionProperties(duration=1000)}) + # delays are not supported + + pm = PassManager([schedule_pass(target=target), PadDelay(target=target)]) + scheduled = pm.run(qc) + + self.assertEqual(qc, scheduled) + if __name__ == "__main__": unittest.main()