From ba5e5870b7e25c18389b22a9e30a6ef89084f4ef Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 12 Sep 2023 14:02:01 -0400 Subject: [PATCH 1/8] 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 | 7 +- .../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, 324 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..fd61f8c49 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/pad_delay.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/pad_delay.py @@ -50,13 +50,16 @@ 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. + 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..5ec2e85fc 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_qubits=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) From 29b9e7b5470d2d30a7b70cb94a82d14f9cefd58d Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 12 Sep 2023 15:29:16 -0400 Subject: [PATCH 2/8] Add missing inline type hint --- .../transpiler/passes/scheduling/block_base_padder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6702c5c34..352f15258 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py @@ -85,7 +85,7 @@ def __init__(self, schedule_idle_qubits: bool = False) -> 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() + self._idle_qubits: Set[Qubit] = set() super().__init__() def run(self, dag: DAGCircuit) -> DAGCircuit: From 49bc1efbb7e0ae7aae5f1515f39178cc50b4762b Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 12 Sep 2023 16:20:12 -0400 Subject: [PATCH 3/8] Don't put barriers on idle qubits either --- .../passes/scheduling/block_base_padder.py | 18 ++++++++++++++---- .../scheduling/test_dynamical_decoupling.py | 5 +---- .../passes/scheduling/test_scheduler.py | 3 +-- 3 files changed, 16 insertions(+), 10 deletions(-) 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 352f15258..336f5072c 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py @@ -160,10 +160,13 @@ def _empty_dag_like( # not be equivalent to one written manually as bits will not # be defined on registers like in the test case. - source_wire_dag = self._root_dag if pad_wires else dag + if not self._schedule_idle_qubits: + source_wire_dag = dag + else: + source_wire_dag = self._root_dag if pad_wires else dag # trivial wire map if not provided, or if the top-level dag is used - if not wire_map or pad_wires: + if not wire_map or (pad_wires and self._schedule_idle_qubits): wire_map = {wire: wire for wire in source_wire_dag.wires} if dag.qregs: for qreg in source_wire_dag.qregs.values(): @@ -311,11 +314,18 @@ def _add_block_terminating_barrier( if needs_terminating_barrier: # Terminate with a barrier to ensure topological ordering does not slide past + if self._schedule_idle_qubits: + barrier = Barrier(self._block_dag.num_qubits()) + qubits = self._block_dag.qubits + else: + barrier = Barrier(self._block_dag.num_qubits() - len(self._idle_qubits)) + qubits = [x for x in self._block_dag.qubits if x not in self._idle_qubits] + barrier_node = self._apply_scheduled_op( block_idx, time, - Barrier(self._block_dag.num_qubits()), - self._block_dag.qubits, + barrier, + qubits, [], ) barrier_node.op.duration = 0 diff --git a/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py b/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py index 2f7a87d87..ca636fbce 100644 --- a/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py +++ b/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py @@ -1076,10 +1076,7 @@ def test_no_unused_qubits(self): 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) + 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 5ec2e85fc..4b50fda37 100644 --- a/test/unit/transpiler/passes/scheduling/test_scheduler.py +++ b/test/unit/transpiler/passes/scheduling/test_scheduler.py @@ -2023,5 +2023,4 @@ def test_no_unused_qubits(self): dont_use = scheduled.qubits[-1] for op in scheduled.data: - if op.operation.name != "barrier": - self.assertNotIn(dont_use, op.qubits) + self.assertNotIn(dont_use, op.qubits) From 49390e03a7a62b128d60b8cd30f12a35ad0708a9 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 12 Sep 2023 16:23:20 -0400 Subject: [PATCH 4/8] Fix formatting --- .../transpiler/passes/scheduling/block_base_padder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 336f5072c..5d473527e 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py @@ -319,7 +319,9 @@ def _add_block_terminating_barrier( qubits = self._block_dag.qubits else: barrier = Barrier(self._block_dag.num_qubits() - len(self._idle_qubits)) - qubits = [x for x in self._block_dag.qubits if x not in self._idle_qubits] + qubits = [ + x for x in self._block_dag.qubits if x not in self._idle_qubits + ] barrier_node = self._apply_scheduled_op( block_idx, From 6d474042b0456a915c8e80a9588341514d606316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= <10402430+dieris@users.noreply.github.com> Date: Thu, 21 Sep 2023 11:12:45 -0400 Subject: [PATCH 5/8] Add schedule_idle_qubits to parent constructor --- .../transpiler/passes/scheduling/dynamical_decoupling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py index e7ee2b94b..b4ffaaf56 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py @@ -184,7 +184,7 @@ def __init__( TranspilerError: When the coupling map is not supported (i.e., if degree > 3) """ - super().__init__() + super().__init__(schedule_idle_qubits=schedule_idle_qubits) self._durations = durations # Enforce list of DD sequences From 964c416b0844b73482aceddebad1c2f5b71e43b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= <10402430+dieris@users.noreply.github.com> Date: Thu, 21 Sep 2023 11:13:31 -0400 Subject: [PATCH 6/8] Reneable padding in control flow --- .../passes/scheduling/block_base_padder.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) 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 5d473527e..09d9ee0c2 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py @@ -160,13 +160,10 @@ def _empty_dag_like( # not be equivalent to one written manually as bits will not # be defined on registers like in the test case. - if not self._schedule_idle_qubits: - source_wire_dag = dag - else: - source_wire_dag = self._root_dag if pad_wires else dag + source_wire_dag = self._root_dag if pad_wires else dag # trivial wire map if not provided, or if the top-level dag is used - if not wire_map or (pad_wires and self._schedule_idle_qubits): + if not wire_map or pad_wires: wire_map = {wire: wire for wire in source_wire_dag.wires} if dag.qregs: for qreg in source_wire_dag.qregs.values(): @@ -437,7 +434,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 or not self._schedule_idle_qubits + fast_path_node = node in self._fast_path_nodes # TODO: This is a hack required to tie nodes of control-flow # blocks across the scheduler and block_base_padder. This is @@ -472,11 +469,17 @@ def _visit_control_flow_op(self, node: DAGNode) -> None: # Enforce that this control-flow operation contains all wires since it has now been padded # such that each qubit is scheduled within each block. Don't added all cargs as these will not # be padded. + if fast_path_node: + padded_qubits = node.qargs + elif not self._schedule_idle_qubits: + padded_qubits = [q for q in self._block_dag.qubits if q not in self._idle_qubits] + else: + padded_qubits = self._block_dag.qubits self._apply_scheduled_op( block_idx, t0, new_control_flow_op, - self._map_wires(node.qargs) if fast_path_node else self._block_dag.qubits, + padded_qubits, self._map_wires(node.cargs), ) From 8a9ee5cba3d1e3d35b7fb4b3f7c46732c7701e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= <10402430+dieris@users.noreply.github.com> Date: Thu, 21 Sep 2023 11:14:11 -0400 Subject: [PATCH 7/8] Omit idle qubits in control flow --- .../transpiler/passes/scheduling/block_base_padder.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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 09d9ee0c2..99214fe79 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py @@ -143,6 +143,7 @@ def _empty_dag_like( dag: DAGCircuit, pad_wires: bool = True, wire_map: Optional[Dict[Qubit, Qubit]] = None, + ignore_idle: bool = False ) -> DAGCircuit: """Create an empty dag like the input dag.""" new_dag = DAGCircuit() @@ -165,11 +166,11 @@ def _empty_dag_like( # trivial wire map if not provided, or if the top-level dag is used if not wire_map or pad_wires: wire_map = {wire: wire for wire in source_wire_dag.wires} - if dag.qregs: + if dag.qregs and self._schedule_idle_qubits or not ignore_idle: for qreg in source_wire_dag.qregs.values(): new_dag.add_qreg(qreg) else: - new_dag.add_qubits([wire_map[qubit] for qubit in source_wire_dag.qubits]) + new_dag.add_qubits([wire_map[qubit] for qubit in source_wire_dag.qubits if qubit not in self._idle_qubits or not ignore_idle]) # Don't add root cargs as these will not be padded. # Just focus on current block dag. @@ -330,7 +331,7 @@ def _add_block_terminating_barrier( barrier_node.op.duration = 0 def _visit_block( - self, block: DAGCircuit, wire_map: Dict[Qubit, Qubit], pad_wires: bool = True + self, block: DAGCircuit, wire_map: Dict[Qubit, Qubit], pad_wires: bool = True, ignore_idle: bool = False ) -> DAGCircuit: # Push the previous block dag onto the stack prev_node = self._prev_node @@ -339,7 +340,7 @@ def _visit_block( prev_block_dag = self._block_dag self._block_dag = new_block_dag = self._empty_dag_like( - block, pad_wires, wire_map=wire_map + block, pad_wires, wire_map=wire_map, ignore_idle=ignore_idle ) self._block_duration = 0 @@ -457,7 +458,7 @@ def _visit_control_flow_op(self, node: DAGNode) -> None: } new_node_block_dags.append( self._visit_block( - block_dag, pad_wires=not fast_path_node, wire_map=inner_wire_map + block_dag, pad_wires=not fast_path_node, wire_map=inner_wire_map, ignore_idle=True ) ) From bc5893cfd72eeb0859f336633a1484e5d76a6088 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 21 Sep 2023 19:40:01 -0400 Subject: [PATCH 8/8] Run black --- .../passes/scheduling/block_base_padder.py | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) 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 99214fe79..833bb5253 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py @@ -143,7 +143,7 @@ def _empty_dag_like( dag: DAGCircuit, pad_wires: bool = True, wire_map: Optional[Dict[Qubit, Qubit]] = None, - ignore_idle: bool = False + ignore_idle: bool = False, ) -> DAGCircuit: """Create an empty dag like the input dag.""" new_dag = DAGCircuit() @@ -170,7 +170,13 @@ def _empty_dag_like( for qreg in source_wire_dag.qregs.values(): new_dag.add_qreg(qreg) else: - new_dag.add_qubits([wire_map[qubit] for qubit in source_wire_dag.qubits if qubit not in self._idle_qubits or not ignore_idle]) + new_dag.add_qubits( + [ + wire_map[qubit] + for qubit in source_wire_dag.qubits + if qubit not in self._idle_qubits or not ignore_idle + ] + ) # Don't add root cargs as these will not be padded. # Just focus on current block dag. @@ -331,7 +337,11 @@ def _add_block_terminating_barrier( barrier_node.op.duration = 0 def _visit_block( - self, block: DAGCircuit, wire_map: Dict[Qubit, Qubit], pad_wires: bool = True, ignore_idle: bool = False + self, + block: DAGCircuit, + wire_map: Dict[Qubit, Qubit], + pad_wires: bool = True, + ignore_idle: bool = False, ) -> DAGCircuit: # Push the previous block dag onto the stack prev_node = self._prev_node @@ -458,7 +468,10 @@ def _visit_control_flow_op(self, node: DAGNode) -> None: } new_node_block_dags.append( self._visit_block( - block_dag, pad_wires=not fast_path_node, wire_map=inner_wire_map, ignore_idle=True + block_dag, + pad_wires=not fast_path_node, + wire_map=inner_wire_map, + ignore_idle=True, ) ) @@ -473,7 +486,9 @@ def _visit_control_flow_op(self, node: DAGNode) -> None: if fast_path_node: padded_qubits = node.qargs elif not self._schedule_idle_qubits: - padded_qubits = [q for q in self._block_dag.qubits if q not in self._idle_qubits] + padded_qubits = [ + q for q in self._block_dag.qubits if q not in self._idle_qubits + ] else: padded_qubits = self._block_dag.qubits self._apply_scheduled_op(