From e7ea549e62920ac1196da8937ea3633a2077202a Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 12 Sep 2023 14:02:01 -0400 Subject: [PATCH] Add option to no pad idle qubits in scheduling passes This commit adds a new flag to the scheduling padding passes, schedule_idle_qubits, which is used to opt-in to scheduling idle qubits. By default these passes will only schedule qubits which are active. This is a change in behavior from before where the previous default is all qubits were scheduled. This was undesireable because it's adding uneceesary instructions to the job payload which will need to be processed which are effectively a no-op. The only real use case for adding delays to idle wires is visualization, so it's left as an option to re-enable the previous default. Fixes #723 Co-authored-by: Thomas Alexander --- .../passes/scheduling/block_base_padder.py | 15 +- .../passes/scheduling/dynamical_decoupling.py | 4 + .../transpiler/passes/scheduling/pad_delay.py | 8 +- .../scheduling-no-idle-bbadd1e95d2a71b8.yaml | 20 ++ test/unit/test_account.py | 2 +- .../scheduling/test_dynamical_decoupling.py | 100 ++++++- .../passes/scheduling/test_scheduler.py | 251 ++++++++++++++---- 7 files changed, 325 insertions(+), 75 deletions(-) create mode 100644 releasenotes/notes/scheduling-no-idle-bbadd1e95d2a71b8.yaml diff --git a/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py b/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py index 00ce1094b..6702c5c34 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py @@ -62,7 +62,7 @@ class BlockBasePadder(TransformationPass): which may result in violation of hardware alignment constraints. """ - def __init__(self) -> None: + def __init__(self, schedule_idle_qubits: bool = False) -> None: self._node_start_time = None self._node_block_dags = None self._idle_after: Optional[Dict[Qubit, int]] = None @@ -84,7 +84,8 @@ def __init__(self) -> None: self._dirty_qubits: Set[Qubit] = set() # Qubits that are dirty in the circuit. - + self._schedule_idle_qubits = schedule_idle_qubits + self._idle_qubits = set() super().__init__() def run(self, dag: DAGCircuit) -> DAGCircuit: @@ -100,6 +101,10 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: TranspilerError: When a particular node is not scheduled, likely some transform pass is inserted before this node is called. """ + if not self._schedule_idle_qubits: + self._idle_qubits = set( + wire for wire in dag.idle_wires() if isinstance(wire, Qubit) + ) self._pre_runhook(dag) self._init_run(dag) @@ -420,7 +425,7 @@ def _visit_control_flow_op(self, node: DAGNode) -> None: self._add_block_terminating_barrier(block_idx, t0, node) # Only pad non-fast path nodes - fast_path_node = node in self._fast_path_nodes + fast_path_node = node in self._fast_path_nodes or not self._schedule_idle_qubits # TODO: This is a hack required to tie nodes of control-flow # blocks across the scheduler and block_base_padder. This is @@ -503,6 +508,8 @@ def _visit_generic(self, node: DAGNode) -> None: self._block_duration = max(self._block_duration, t1) for bit in self._map_wires(node.qargs): + if bit in self._idle_qubits: + continue # Fill idle time with some sequence if t0 - self._idle_after.get(bit, 0) > 0: # Find previous node on the wire, i.e. always the latest node on the wire @@ -549,6 +556,8 @@ def _terminate_block(self, block_duration: int, block_idx: int) -> None: def _pad_until_block_end(self, block_duration: int, block_idx: int) -> None: # Add delays until the end of circuit. for bit in self._block_dag.qubits: + if bit in self._idle_qubits: + continue idle_after = self._idle_after.get(bit, 0) if block_duration - idle_after > 0: node = self._block_dag.output_map[bit] diff --git a/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py index 54845454c..e7ee2b94b 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py @@ -121,6 +121,7 @@ def __init__( insert_multiple_cycles: bool = False, coupling_map: CouplingMap = None, alt_spacings: Optional[Union[List[List[float]], List[float]]] = None, + schedule_idle_qubits: bool = False, ): """Dynamical decoupling initializer. @@ -173,6 +174,9 @@ def __init__( alt_spacings: A list of lists of spacings between the DD gates, for the second subcircuit, as determined by the coupling map. If None, a balanced spacing that is staggered with respect to the first subcircuit will be used [d, d, d, ..., d, d, 0]. + schedule_idle_qubits: Set to true if you'd like a delay inserted on idle qubits. + This is useful for timeline visualizations, but may cause issues + for execution on large backends. Raises: TranspilerError: When invalid DD sequence is specified. TranspilerError: When pulse gate with the duration which is diff --git a/qiskit_ibm_provider/transpiler/passes/scheduling/pad_delay.py b/qiskit_ibm_provider/transpiler/passes/scheduling/pad_delay.py index 27abfa544..7233e7497 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/pad_delay.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/pad_delay.py @@ -50,13 +50,17 @@ class PadDelay(BlockBasePadder): See :class:`BlockBasePadder` pass for details. """ - def __init__(self, fill_very_end: bool = True): + def __init__(self, fill_very_end: bool = True, schedule_idle_qubits: bool = False): """Create new padding delay pass. Args: fill_very_end: Set ``True`` to fill the end of circuit with delay. + sce + schedule_idle_qubits: Set to true if you'd like a delay inserted on idle qubits. + This is useful for timeline visualizations, but may cause issues for execution + on large backends. """ - super().__init__() + super().__init__(schedule_idle_qubits=schedule_idle_qubits) self.fill_very_end = fill_very_end def _pad( diff --git a/releasenotes/notes/scheduling-no-idle-bbadd1e95d2a71b8.yaml b/releasenotes/notes/scheduling-no-idle-bbadd1e95d2a71b8.yaml new file mode 100644 index 000000000..feb734ee9 --- /dev/null +++ b/releasenotes/notes/scheduling-no-idle-bbadd1e95d2a71b8.yaml @@ -0,0 +1,20 @@ +--- +features: + - | + Added a new flag, ``schedule_idle_qubits`` to the constructor for the + :class:`.PadDelay` and :class:`.PadDynamicalDecoupling` passes. This + flag when set to ``True`` will have the scheduling passes insert a full + circuit duration delay on any idle qubits in the circuit. +upgrade: + - | + The default behavior of the :class:`.PadDelay` and + :class:`.PadDynamicalDecoupling` passes for idle qubits in the circuit + have changed. Previously, by default the passes would schedule any idle + qubits in the circuit by inserting a delay equal to the full circuit + duration. This has been changed so by default only active qubits are + scheduled. This change was made because the extra delays were additional + overhead in the job payload that were effectively a no-op so they added + extra overhead to job submission for no gain. If you need to restore + the previous behavior you can instantiate :class:`.PadDelay` or + :class:`.PadDynamicalDecoupling` with the keyword argument + ``schedule_idle_qubits=True`` which will restore the previous behavior. diff --git a/test/unit/test_account.py b/test/unit/test_account.py index 31b60c99c..e2b462612 100644 --- a/test/unit/test_account.py +++ b/test/unit/test_account.py @@ -275,7 +275,6 @@ def test_list(self): ).to_saved_format(), } ), self.subTest("filtered list of accounts"): - accounts = list(AccountManager.list(channel="ibm_quantum").keys()) self.assertEqual(len(accounts), 2) self.assertListEqual(accounts, ["key2", _DEFAULT_ACCOUNT_NAME_IBM_QUANTUM]) @@ -321,6 +320,7 @@ def test_delete_auth(self): "urls": {"https": "127.0.0.1", "username_ntlm": "", "password_ntlm": ""} } + # TODO: update and reenable test cases to work with qiskit-ibm-provider # NamedTemporaryFiles not supported in Windows @skipIf(os.name == "nt", "Test not supported in Windows") diff --git a/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py b/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py index eca3780bd..2f7a87d87 100644 --- a/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py +++ b/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py @@ -90,6 +90,7 @@ def test_insert_dd_ghz(self): dd_sequence, pulse_alignment=1, sequence_min_length_ratios=[1.0], + schedule_idle_qubits=True, ), ] ) @@ -122,7 +123,11 @@ def test_insert_dd_ghz_one_qubit(self): [ ASAPScheduleAnalysis(self.durations), PadDynamicalDecoupling( - self.durations, dd_sequence, qubits=[0], pulse_alignment=1 + self.durations, + dd_sequence, + qubits=[0], + pulse_alignment=1, + schedule_idle_qubits=True, ), ] ) @@ -158,6 +163,7 @@ def test_insert_dd_ghz_everywhere(self): skip_reset_qubits=False, pulse_alignment=1, sequence_min_length_ratios=[0.0], + schedule_idle_qubits=True, ), ] ) @@ -195,6 +201,7 @@ def test_insert_dd_ghz_xy4(self): dd_sequence, pulse_alignment=1, sequence_min_length_ratios=[1.0], + schedule_idle_qubits=True, ), ] ) @@ -234,7 +241,12 @@ def test_insert_midmeas_hahn(self): pm = PassManager( [ ASAPScheduleAnalysis(self.durations), - PadDynamicalDecoupling(self.durations, dd_sequence, pulse_alignment=1), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + pulse_alignment=1, + schedule_idle_qubits=True, + ), ] ) @@ -291,6 +303,7 @@ def uhrig(k): spacings=spacing, sequence_min_length_ratios=[0.0], pulse_alignment=1, + schedule_idle_qubits=True, ), ] ) @@ -332,7 +345,11 @@ def test_asymmetric_xy4_in_t2(self): [ ASAPScheduleAnalysis(self.durations), PadDynamicalDecoupling( - self.durations, dd_sequence, pulse_alignment=1, spacings=spacing + self.durations, + dd_sequence, + pulse_alignment=1, + spacings=spacing, + schedule_idle_qubits=True, ), ] ) @@ -375,6 +392,7 @@ def test_dd_after_reset(self): skip_reset_qubits=True, pulse_alignment=1, sequence_min_length_ratios=[0.0], + schedule_idle_qubits=True, ), ] ) @@ -410,7 +428,9 @@ def test_insert_dd_bad_sequence(self): pm = PassManager( [ ASAPScheduleAnalysis(self.durations), - PadDynamicalDecoupling(self.durations, dd_sequence), + PadDynamicalDecoupling( + self.durations, dd_sequence, schedule_idle_qubits=True + ), ] ) @@ -444,7 +464,9 @@ def test_dd_with_calibrations_with_parameters(self, param_value): pm = PassManager( [ ASAPScheduleAnalysis(durations), - PadDynamicalDecoupling(durations, dd_sequence), + PadDynamicalDecoupling( + durations, dd_sequence, schedule_idle_qubits=True + ), ] ) dd_circuit = pm.run(circ) @@ -466,6 +488,7 @@ def test_insert_dd_ghz_xy4_with_alignment(self): pulse_alignment=10, extra_slack_distribution="edges", sequence_min_length_ratios=[1.0], + schedule_idle_qubits=True, ), ] ) @@ -510,8 +533,12 @@ def test_dd_can_sequentially_called(self): pm1 = PassManager( [ ASAPScheduleAnalysis(self.durations), - PadDynamicalDecoupling(self.durations, dd_sequence, qubits=[0]), - PadDynamicalDecoupling(self.durations, dd_sequence, qubits=[1]), + PadDynamicalDecoupling( + self.durations, dd_sequence, qubits=[0], schedule_idle_qubits=True + ), + PadDynamicalDecoupling( + self.durations, dd_sequence, qubits=[1], schedule_idle_qubits=True + ), ] ) circ1 = pm1.run(self.ghz4) @@ -519,7 +546,12 @@ def test_dd_can_sequentially_called(self): pm2 = PassManager( [ ASAPScheduleAnalysis(self.durations), - PadDynamicalDecoupling(self.durations, dd_sequence, qubits=[0, 1]), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + qubits=[0, 1], + schedule_idle_qubits=True, + ), ] ) circ2 = pm2.run(self.ghz4) @@ -538,6 +570,7 @@ def test_back_to_back_if_test(self): dd_sequence, pulse_alignment=1, sequence_min_length_ratios=[0.0], + schedule_idle_qubits=True, ), ] ) @@ -594,6 +627,7 @@ def test_dd_if_test(self): dd_sequence, pulse_alignment=1, sequence_min_length_ratios=[0.0], + schedule_idle_qubits=True, ), ] ) @@ -677,14 +711,18 @@ def test_reproducible(self): pm0 = PassManager( [ ASAPScheduleAnalysis(self.durations), - PadDynamicalDecoupling(self.durations, dd_sequence), + PadDynamicalDecoupling( + self.durations, dd_sequence, schedule_idle_qubits=True + ), ] ) pm1 = PassManager( [ ASAPScheduleAnalysis(self.durations), - PadDynamicalDecoupling(self.durations, dd_sequence), + PadDynamicalDecoupling( + self.durations, dd_sequence, schedule_idle_qubits=True + ), ] ) qc_dd0 = pm0.run(qc) @@ -704,6 +742,7 @@ def test_nested_block_dd(self): dd_sequence, pulse_alignment=1, sequence_min_length_ratios=[0.0], + schedule_idle_qubits=True, ), ] ) @@ -760,6 +799,7 @@ def test_multiple_dd_sequences(self): dd_sequence, pulse_alignment=1, sequence_min_length_ratios=[1.5, 0.0], + schedule_idle_qubits=True, ), ] ) @@ -838,6 +878,7 @@ def test_multiple_dd_sequence_cycles(self): pulse_alignment=1, sequence_min_length_ratios=[10.0], insert_multiple_cycles=True, + schedule_idle_qubits=True, ), ] ) @@ -869,6 +910,7 @@ def test_staggered_dd(self): dd_sequence, coupling_map=self.coupling_map, alt_spacings=[0.1, 0.8, 0.1], + schedule_idle_qubits=True, ), ] ) @@ -988,6 +1030,7 @@ def test_disjoint_coupling_map(self): self.durations, dd_sequence, coupling_map=CouplingMap([[0, 1], [1, 2], [3, 4]]), + schedule_idle_qubits=True, ), ] ) @@ -1003,3 +1046,40 @@ def test_disjoint_coupling_map(self): self.assertNotEqual(delay_dict[1], delay_dict[2]) self.assertNotEqual(delay_dict[3], delay_dict[4]) self.assertEqual(delay_dict[0], delay_dict[2]) + + def test_no_unused_qubits(self): + """Test DD with if_test circuit that unused qubits are untouched and not scheduled. + + This ensures that programs don't have unnecessary information for unused qubits. + Which might hurt performance in later executon stages. + """ + + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + pulse_alignment=1, + sequence_min_length_ratios=[0.0], + ), + ] + ) + + qc = QuantumCircuit(3, 1) + qc.measure(0, 0) + qc.x(1) + with qc.if_test((0, True)): + qc.x(1) + qc.measure(0, 0) + with qc.if_test((0, True)): + qc.x(0) + qc.x(1) + + qc_dd = pm.run(qc) + + dont_use = qc_dd.qubits[-1] + for op in qc_dd.data: + if op.operation.name != "barrier": + self.assertNotIn(dont_use, op.qubits) diff --git a/test/unit/transpiler/passes/scheduling/test_scheduler.py b/test/unit/transpiler/passes/scheduling/test_scheduler.py index d86b6de51..7e55abecb 100644 --- a/test/unit/transpiler/passes/scheduling/test_scheduler.py +++ b/test/unit/transpiler/passes/scheduling/test_scheduler.py @@ -51,7 +51,9 @@ def test_if_test_gate_after_measure(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -75,7 +77,9 @@ def test_c_if_raises(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubit=True)] + ) with self.assertRaises(TranspilerError): pm.run(qc) @@ -88,7 +92,11 @@ def test_c_if_conversion(self): [("x", None, 200), ("measure", None, 840)] ) pm = PassManager( - [ConvertConditionsToIfOps(), ASAPScheduleAnalysis(durations), PadDelay()] + [ + ConvertConditionsToIfOps(), + ASAPScheduleAnalysis(durations), + PadDelay(schedule_idle_qubits=True), + ] ) scheduled = pm.run(qc) @@ -110,7 +118,9 @@ def test_measure_after_measure(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -133,7 +143,9 @@ def test_measure_block_not_end(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -162,7 +174,9 @@ def test_reset_block_end(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840), ("reset", None, 840)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -190,7 +204,9 @@ def test_c_if_on_different_qubits(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -218,7 +234,9 @@ def test_shorter_measure_after_measure(self): durations = DynamicCircuitInstructionDurations( [("measure", [0], 840), ("measure", [1], 540)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -240,7 +258,9 @@ def test_measure_after_c_if(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -272,7 +292,9 @@ def test_parallel_gate_different_length(self): [("x", [0], 200), ("x", [1], 400), ("measure", None, 840)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 2) @@ -297,7 +319,9 @@ def test_parallel_gate_different_length_with_barrier(self): [("x", [0], 200), ("x", [1], 400), ("measure", None, 840)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 2) @@ -334,7 +358,7 @@ def test_active_reset_circuit(self): scheduled = PassManager( [ ASAPScheduleAnalysis(durations), - PadDelay(), + PadDelay(schedule_idle_qubits=True), ] ).run(qc) @@ -368,7 +392,9 @@ def test_dag_introduces_extra_dependency_between_conditionals(self): qc.x(1) durations = DynamicCircuitInstructionDurations([("x", None, 160)]) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -398,7 +424,9 @@ def test_scheduling_with_calibration(self): durations = DynamicCircuitInstructionDurations( [("x", None, 160), ("cx", None, 600)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2) @@ -438,7 +466,7 @@ def test_no_pad_very_end_of_circuit(self): scheduled = PassManager( [ ASAPScheduleAnalysis(durations), - PadDelay(fill_very_end=False), + PadDelay(fill_very_end=False, schedule_idle_qubits=True), ] ).run(qc) @@ -474,7 +502,9 @@ def test_reset_terminates_block(self): ("measure", [1], 540), ] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -518,7 +548,9 @@ def test_reset_merged_with_measure(self): ("measure", [1], 540), ] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -554,14 +586,14 @@ def test_scheduling_is_idempotent(self): scheduled0 = PassManager( [ ASAPScheduleAnalysis(durations), - PadDelay(), + PadDelay(schedule_idle_qubits=True), ] ).run(qc) scheduled1 = PassManager( [ ASAPScheduleAnalysis(durations), - PadDelay(), + PadDelay(schedule_idle_qubits=True), ] ).run(scheduled0) @@ -577,7 +609,9 @@ def test_gate_on_measured_qubit(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -602,7 +636,9 @@ def test_grouped_measurements_prior_control_flow(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 3) @@ -641,7 +677,9 @@ def test_back_to_back_c_if(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -681,7 +719,9 @@ def test_nested_control_scheduling(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(4, 3) @@ -728,7 +768,9 @@ def test_while_loop(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -756,7 +798,9 @@ def test_for_loop(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -783,7 +827,11 @@ def test_registers(self): [("x", None, 200), ("measure", None, 840)] ) pm = PassManager( - [ConvertConditionsToIfOps(), ASAPScheduleAnalysis(durations), PadDelay()] + [ + ConvertConditionsToIfOps(), + ASAPScheduleAnalysis(durations), + PadDelay(schedule_idle_qubits=True), + ] ) scheduled = pm.run(qc) @@ -808,7 +856,9 @@ def test_c_if_plugin_conversion_with_transpile(self): backend.configuration().basis_gates.append("if_else") durations = DynamicCircuitInstructionDurations.from_backend(backend) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) qr0 = QuantumRegister(1, name="q") cr = ClassicalRegister(1, name="c") @@ -846,7 +896,9 @@ def test_alap(self): qc.measure(0, 0) qc.x(1) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -870,7 +922,9 @@ def test_if_test_gate_after_measure(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -896,7 +950,9 @@ def test_classically_controlled_gate_after_measure(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -921,7 +977,9 @@ def test_measure_after_measure(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -945,7 +1003,9 @@ def test_measure_block_not_end(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -975,7 +1035,9 @@ def test_reset_block_end(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840), ("reset", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -1003,7 +1065,9 @@ def test_c_if_on_different_qubits(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -1031,7 +1095,9 @@ def test_shorter_measure_after_measure(self): durations = DynamicCircuitInstructionDurations( [("measure", [0], 840), ("measure", [1], 540)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -1053,7 +1119,9 @@ def test_measure_after_c_if(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -1084,7 +1152,9 @@ def test_parallel_gate_different_length(self): [("x", [0], 200), ("x", [1], 400), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 2) @@ -1109,7 +1179,9 @@ def test_parallel_gate_different_length_with_barrier(self): [("x", [0], 200), ("x", [1], 400), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 2) @@ -1146,7 +1218,7 @@ def test_active_reset_circuit(self): scheduled = PassManager( [ ALAPScheduleAnalysis(durations), - PadDelay(), + PadDelay(schedule_idle_qubits=True), ] ).run(qc) @@ -1180,7 +1252,9 @@ def test_dag_introduces_extra_dependency_between_conditionals(self): qc.x(1) durations = DynamicCircuitInstructionDurations([("x", None, 160)]) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -1210,7 +1284,9 @@ def test_scheduling_with_calibration(self): durations = DynamicCircuitInstructionDurations( [("x", None, 160), ("cx", None, 600)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2) @@ -1250,7 +1326,7 @@ def test_no_pad_very_end_of_circuit(self): scheduled = PassManager( [ ALAPScheduleAnalysis(durations), - PadDelay(fill_very_end=False), + PadDelay(fill_very_end=False, schedule_idle_qubits=True), ] ).run(qc) @@ -1290,7 +1366,9 @@ def test_reset_terminates_block(self): ("measure", [1], 540), ] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -1334,7 +1412,9 @@ def test_reset_merged_with_measure(self): ("measure", [1], 540), ] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -1375,7 +1455,7 @@ def test_already_scheduled(self): scheduled = PassManager( [ ALAPScheduleAnalysis(durations), - PadDelay(), + PadDelay(schedule_idle_qubits=True), ] ).run(qc) @@ -1399,14 +1479,14 @@ def test_scheduling_is_idempotent(self): scheduled0 = PassManager( [ ALAPScheduleAnalysis(durations), - PadDelay(), + PadDelay(schedule_idle_qubits=True), ] ).run(qc) scheduled1 = PassManager( [ ALAPScheduleAnalysis(durations), - PadDelay(), + PadDelay(schedule_idle_qubits=True), ] ).run(scheduled0) @@ -1422,7 +1502,9 @@ def test_gate_on_measured_qubit(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -1447,7 +1529,9 @@ def test_grouped_measurements_prior_control_flow(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 3) @@ -1494,7 +1578,9 @@ def test_fast_path_eligible_scheduling(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(4, 3) @@ -1540,7 +1626,9 @@ def test_back_to_back_c_if(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -1595,7 +1683,9 @@ def test_issue_458_extra_idle_bug_0(self): [("x", None, 160), ("cx", None, 700), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(4, 3) @@ -1644,7 +1734,9 @@ def test_issue_458_extra_idle_bug_1(self): [("rz", None, 0), ("cx", None, 700), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(3, 3) @@ -1673,7 +1765,9 @@ def test_nested_control_scheduling(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(4, 3) @@ -1720,7 +1814,9 @@ def test_while_loop(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -1748,7 +1844,9 @@ def test_for_loop(self): durations = DynamicCircuitInstructionDurations( [("x", None, 200), ("measure", None, 840)] ) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -1771,7 +1869,9 @@ def test_transpile_mock_backend(self): backend.configuration().basis_gates.append("while_loop") durations = DynamicCircuitInstructionDurations.from_backend(backend) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) qr = QuantumRegister(3) cr = ClassicalRegister(2) @@ -1816,7 +1916,9 @@ def test_transpile_both_paths(self): backend.configuration().basis_gates.append("if_else") durations = DynamicCircuitInstructionDurations.from_backend(backend) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) qr = QuantumRegister(3) cr = ClassicalRegister(2) @@ -1868,7 +1970,9 @@ def test_c_if_plugin_conversion_with_transpile(self): backend.configuration().basis_gates.append("if_else") durations = DynamicCircuitInstructionDurations.from_backend(backend) - pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + pm = PassManager( + [ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)] + ) qr0 = QuantumRegister(1, name="q") cr = ClassicalRegister(1, name="c") @@ -1892,3 +1996,32 @@ def test_c_if_plugin_conversion_with_transpile(self): expected.delay(160, qr1[6]) self.assertEqual(expected, scheduled) + + def test_no_unused_qubits(self): + """Test DD with if_test circuit that unused qubits are untouched and not scheduled. + + This ensures that programs don't have unnecessary information for unused qubits. + Which might hurt performance in later executon stages. + """ + + durations = DynamicCircuitInstructionDurations( + [("x", None, 200), ("measure", None, 840)] + ) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + + qc = QuantumCircuit(3, 1) + qc.measure(0, 0) + qc.x(1) + with qc.if_test((0, True)): + qc.x(1) + qc.measure(0, 0) + with qc.if_test((0, True)): + qc.x(0) + qc.x(1) + + scheduled = pm.run(qc) + + dont_use = scheduled.qubits[-1] + for op in scheduled.data: + if op.operation.name != "barrier": + self.assertNotIn(dont_use, op.qubits)