From ec4ca988a0b98e31719a924f1918326ad60c46d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= <10402430+dieris@users.noreply.github.com> Date: Fri, 23 Jun 2023 14:25:05 -0400 Subject: [PATCH 1/7] include_clean_qubits option To apply DD on all (non-idle) qubits --- .../transpiler/passes/scheduling/dynamical_decoupling.py | 5 ++++- 1 file changed, 4 insertions(+), 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 acae82ce4..d51e5a2c7 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py @@ -122,6 +122,7 @@ def __init__( coupling_map: CouplingMap = None, alt_spacings: Optional[Union[List[List[float]], List[float]]] = None, schedule_idle_qubits: bool = False, + include_clean_qubits: bool = False, ): """Dynamical decoupling initializer. @@ -177,6 +178,7 @@ def __init__( 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. + include_clean_qubits: Apply DD regardless of whether the qubit is dirty or not Raises: TranspilerError: When invalid DD sequence is specified. TranspilerError: When pulse gate with the duration which is @@ -199,6 +201,7 @@ def __init__( self._alignment = pulse_alignment self._coupling_map = coupling_map self._coupling_coloring = None + self._include_clean_qubits = include_clean_qubits if spacings is not None: try: @@ -420,7 +423,7 @@ def _pad( ): self._dirty_qubits.remove(qubit) - if qubit not in self._dirty_qubits: + if not self._include_clean_qubits and qubit not in self._dirty_qubits: # Previous node is the start edge or reset, i.e. qubit is ground state. self._apply_scheduled_op( block_idx, t_start, Delay(time_interval, self._block_dag.unit), qubit From eb59e63d81d73be9a936c44f55c5a1f707817e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= <10402430+dieris@users.noreply.github.com> Date: Fri, 6 Oct 2023 11:59:52 -0400 Subject: [PATCH 2/7] Add tests --- .../scheduling/test_dynamical_decoupling.py | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py b/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py index ca636fbce..878489a72 100644 --- a/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py +++ b/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py @@ -1080,3 +1080,91 @@ def test_no_unused_qubits(self): dont_use = qc_dd.qubits[-1] for op in qc_dd.data: self.assertNotIn(dont_use, op.qubits) + + def test_dd_include_clean_qubits(self): + """Test DD applied to all non-idle qubits, including those + that are clean""" + + qc = QuantumCircuit(3, 1) + qc.delay(800, 1) + with qc.if_test((0, True)): + qc.x(1) + qc.delay(1000, 2) + + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + pulse_alignment=1, + sequence_min_length_ratios=[0.0], + include_clean_qubits=True, + ), + ] + ) + + qc_dd = pm.run(qc) + + expected = qc.copy_empty_like() + for ind in range(1, 3): + expected.compose(Delay(175), [ind], front=True, inplace=True) + expected.compose(XGate(), [ind], inplace=True) + expected.compose(Delay(350), [ind], inplace=True) + expected.compose(XGate(), [ind], inplace=True) + expected.compose(Delay(175), [ind], inplace=True) + expected.barrier([1, 2]) + with expected.if_test((0, True)): + expected.x(1) + expected.delay(50, 2) + expected.barrier([1, 2]) + for ind in range(1, 3): + expected.compose(Delay(225), [ind], inplace=True) + expected.compose(XGate(), [ind], inplace=True) + expected.compose(Delay(450), [ind], inplace=True) + expected.compose(XGate(), [ind], inplace=True) + expected.compose(Delay(225), [ind], inplace=True) + + self.assertEqual(qc_dd, expected) + + def test_dd_exclude_clean_qubits(self): + """Exclude DD on clean qubits (default)""" + + qc = QuantumCircuit(3, 1) + qc.delay(800, 1) + with qc.if_test((0, True)): + qc.x(1) + qc.delay(1000, 2) + + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + pulse_alignment=1, + sequence_min_length_ratios=[0.0], + ), + ] + ) + + qc_dd = pm.run(qc) + + expected = qc.copy_empty_like() + expected.compose(Delay(800), [1], front=True, inplace=True) + expected.compose(Delay(800), [2], inplace=True) + expected.barrier([1, 2]) + with expected.if_test((0, True)): + expected.x(1) + expected.delay(50, 2) + expected.barrier([1, 2]) + expected.compose(Delay(225), [1], inplace=True) + expected.compose(XGate(), [1], inplace=True) + expected.compose(Delay(450), [1], inplace=True) + expected.compose(XGate(), [1], inplace=True) + expected.compose(Delay(225), [1], inplace=True) + expected.compose(Delay(1000), [2], inplace=True) + + self.assertEqual(qc_dd, expected) From f30c81bae0bf66aa55de7da24b48621929bf5805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= <10402430+dieris@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:07:27 -0400 Subject: [PATCH 3/7] Consider reset qubits as clean --- .../transpiler/passes/scheduling/dynamical_decoupling.py | 5 ++++- .../notes/dd-include-clean-qubits-a98d98e4849e2452.yaml | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/dd-include-clean-qubits-a98d98e4849e2452.yaml diff --git a/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py index d51e5a2c7..835736b19 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py @@ -415,6 +415,9 @@ def _pad( ) return + if self._include_clean_qubits and qubit not in self._dirty_qubits: + self._dirty_qubits.update([qubit]) + if ( not isinstance(prev_node, DAGInNode) and self._skip_reset_qubits @@ -423,7 +426,7 @@ def _pad( ): self._dirty_qubits.remove(qubit) - if not self._include_clean_qubits and qubit not in self._dirty_qubits: + if qubit not in self._dirty_qubits: # Previous node is the start edge or reset, i.e. qubit is ground state. self._apply_scheduled_op( block_idx, t_start, Delay(time_interval, self._block_dag.unit), qubit diff --git a/releasenotes/notes/dd-include-clean-qubits-a98d98e4849e2452.yaml b/releasenotes/notes/dd-include-clean-qubits-a98d98e4849e2452.yaml new file mode 100644 index 000000000..2dd9155b4 --- /dev/null +++ b/releasenotes/notes/dd-include-clean-qubits-a98d98e4849e2452.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + Add ``include_clean_qubits`` optional flag to the constructor for :class:`.PadDynamicalDecoupling`. If ``True``, dynamical decoupling is applied on qubits regardless of their state, unless immediately following a reset instruction (if ``skip_reset_qubits`` is ``True``). From 244f22b9c78df1c6ca99f72b494195ac61598235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= <10402430+dieris@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:58:33 -0500 Subject: [PATCH 4/7] Update block duration --- .../transpiler/passes/scheduling/block_base_padder.py | 1 + 1 file changed, 1 insertion(+) 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 833bb5253..4f2c2fa91 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py @@ -363,6 +363,7 @@ def _visit_block( prev_block_duration = self._block_duration prev_block_idx = self._current_block_idx self._terminate_block(self._block_duration, self._current_block_idx) + new_block_dag.duration = prev_block_duration # Edge-case: Add a barrier if the final node is a fast-path if self._prev_node in self._fast_path_nodes: From 871a2d56b18e35e70a9f5bbfe43c9f08d822bcb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= <10402430+dieris@users.noreply.github.com> Date: Tue, 9 Jan 2024 10:50:35 -0500 Subject: [PATCH 5/7] Formatting --- .../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 4f2c2fa91..9456643b3 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/block_base_padder.py @@ -363,7 +363,7 @@ def _visit_block( prev_block_duration = self._block_duration prev_block_idx = self._current_block_idx self._terminate_block(self._block_duration, self._current_block_idx) - new_block_dag.duration = prev_block_duration + new_block_dag.duration = prev_block_duration # Edge-case: Add a barrier if the final node is a fast-path if self._prev_node in self._fast_path_nodes: From 4d8bee106fead8af87fcb7a36b974d69e1199c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= <10402430+dieris@users.noreply.github.com> Date: Mon, 22 Jan 2024 09:27:46 -0500 Subject: [PATCH 6/7] Fold clean qubits option into skip_reset_qubits --- .../transpiler/passes/scheduling/dynamical_decoupling.py | 6 ++---- .../notes/dd-include-clean-qubits-a98d98e4849e2452.yaml | 2 +- .../passes/scheduling/test_dynamical_decoupling.py | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py index 835736b19..0a19bf487 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py @@ -122,7 +122,6 @@ def __init__( coupling_map: CouplingMap = None, alt_spacings: Optional[Union[List[List[float]], List[float]]] = None, schedule_idle_qubits: bool = False, - include_clean_qubits: bool = False, ): """Dynamical decoupling initializer. @@ -178,7 +177,6 @@ def __init__( 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. - include_clean_qubits: Apply DD regardless of whether the qubit is dirty or not Raises: TranspilerError: When invalid DD sequence is specified. TranspilerError: When pulse gate with the duration which is @@ -201,7 +199,6 @@ def __init__( self._alignment = pulse_alignment self._coupling_map = coupling_map self._coupling_coloring = None - self._include_clean_qubits = include_clean_qubits if spacings is not None: try: @@ -415,7 +412,8 @@ def _pad( ) return - if self._include_clean_qubits and qubit not in self._dirty_qubits: + if not self._skip_reset_qubits and qubit not in self._dirty_qubits: + # mark all qubits as dirty if skip_reset_qubits is False self._dirty_qubits.update([qubit]) if ( diff --git a/releasenotes/notes/dd-include-clean-qubits-a98d98e4849e2452.yaml b/releasenotes/notes/dd-include-clean-qubits-a98d98e4849e2452.yaml index 2dd9155b4..ca59e8857 100644 --- a/releasenotes/notes/dd-include-clean-qubits-a98d98e4849e2452.yaml +++ b/releasenotes/notes/dd-include-clean-qubits-a98d98e4849e2452.yaml @@ -1,4 +1,4 @@ --- upgrade: - | - Add ``include_clean_qubits`` optional flag to the constructor for :class:`.PadDynamicalDecoupling`. If ``True``, dynamical decoupling is applied on qubits regardless of their state, unless immediately following a reset instruction (if ``skip_reset_qubits`` is ``True``). + Modify ``skip_reset_qubits`` optional flag to the constructor for :class:`.PadDynamicalDecoupling`. If ``False``, dynamical decoupling is applied on qubits regardless of their state, even on delays that are at the beginning of a circuit. This option now matches the behavior in qiskit. diff --git a/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py b/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py index 878489a72..53cbd0b27 100644 --- a/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py +++ b/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py @@ -1100,7 +1100,7 @@ def test_dd_include_clean_qubits(self): dd_sequence, pulse_alignment=1, sequence_min_length_ratios=[0.0], - include_clean_qubits=True, + skip_reset_qubits=False, ), ] ) From 19a51f06f6ae63bef2e3aeb019fdee95731e6a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= <10402430+dieris@users.noreply.github.com> Date: Mon, 22 Jan 2024 09:58:59 -0500 Subject: [PATCH 7/7] Fix test_insert_dd_ghz_everywhere apply DD also on initial delays if skip_reset_qubits = False --- .../passes/scheduling/test_dynamical_decoupling.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py b/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py index 53cbd0b27..88a274244 100644 --- a/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py +++ b/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py @@ -185,8 +185,17 @@ def test_insert_dd_ghz_everywhere(self): expected = expected.compose(YGate(), [1]) expected = expected.compose(Delay(50), [1]) - expected = expected.compose(Delay(750), [2], front=True) - expected = expected.compose(Delay(950), [3], front=True) + expected = expected.compose(Delay(162), [2], front=True) + expected = expected.compose(YGate(), [2], front=True) + expected = expected.compose(Delay(326), [2], front=True) + expected = expected.compose(YGate(), [2], front=True) + expected = expected.compose(Delay(162), [2], front=True) + + expected = expected.compose(Delay(212), [3], front=True) + expected = expected.compose(YGate(), [3], front=True) + expected = expected.compose(Delay(426), [3], front=True) + expected = expected.compose(YGate(), [3], front=True) + expected = expected.compose(Delay(212), [3], front=True) self.assertEqual(ghz4_dd, expected)