From faa1a75df25cd949d46a0524e4dc0f0e3e9d026c Mon Sep 17 00:00:00 2001 From: Hunter Kemeny <43501602+hunterkemeny@users.noreply.github.com> Date: Thu, 8 Feb 2024 13:05:33 -0500 Subject: [PATCH 1/7] fix mid-circuit measure problem --- .../passes/scheduling/alignments/reschedule.py | 7 ++++--- .../passes/scheduling/base_scheduler.py | 16 ++++++++++++++-- .../add-scheduler-warnings-da6968a39fd8e6e7.yaml | 9 +++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml diff --git a/qiskit/transpiler/passes/scheduling/alignments/reschedule.py b/qiskit/transpiler/passes/scheduling/alignments/reschedule.py index b53d0f864cef..8aa89601b84a 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/reschedule.py +++ b/qiskit/transpiler/passes/scheduling/alignments/reschedule.py @@ -17,6 +17,7 @@ from qiskit.circuit.gate import Gate from qiskit.circuit.delay import Delay from qiskit.circuit.measure import Measure +from qiskit.circuit.reset import Reset from qiskit.dagcircuit import DAGCircuit, DAGOpNode, DAGOutNode from qiskit.transpiler.basepasses import AnalysisPass from qiskit.transpiler.exceptions import TranspilerError @@ -113,7 +114,7 @@ def _push_node_back(self, dag: DAGCircuit, node: DAGOpNode): if isinstance(node.op, Gate): alignment = self.pulse_align - elif isinstance(node.op, Measure): + elif isinstance(node.op, (Measure, Reset)): alignment = self.acquire_align elif isinstance(node.op, Delay) or getattr(node.op, "_directive", False): # Directive or delay. These can start at arbitrary time. @@ -135,7 +136,7 @@ def _push_node_back(self, dag: DAGCircuit, node: DAGOpNode): # Compute shifted t1 of this node separately for qreg and creg new_t1q = this_t0 + node.op.duration this_qubits = set(node.qargs) - if isinstance(node.op, Measure): + if isinstance(node.op, (Measure, Reset)): # creg access ends at the end of instruction new_t1c = new_t1q this_clbits = set(node.cargs) @@ -153,7 +154,7 @@ def _push_node_back(self, dag: DAGCircuit, node: DAGOpNode): # Compute next node start time separately for qreg and creg next_t0q = node_start_time[next_node] next_qubits = set(next_node.qargs) - if isinstance(next_node.op, Measure): + if isinstance(next_node.op, (Measure, Reset)): # creg access starts after write latency next_t0c = next_t0q + clbit_write_latency next_clbits = set(next_node.cargs) diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py index 78e2660e505d..1b7ef1167de5 100644 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/base_scheduler.py @@ -14,8 +14,8 @@ from qiskit.transpiler import InstructionDurations from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.passes.scheduling.time_unit_conversion import TimeUnitConversion -from qiskit.dagcircuit import DAGOpNode, DAGCircuit -from qiskit.circuit import Delay, Gate +from qiskit.dagcircuit import DAGOpNode, DAGCircuit, DAGOutNode +from qiskit.circuit import Delay, Gate, Measure, Reset from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.target import Target @@ -265,6 +265,18 @@ def _get_node_duration( if dag.has_calibration_for(node): # If node has calibration, this value should be the highest priority cal_key = tuple(indices), tuple(float(p) for p in node.op.params) + if isinstance(node.op, Reset): + raise RuntimeWarning( + "Reset durations reported from IBM backends are currently untrustworthy. Do not rely on on scheduling output." + ) + elif isinstance(node.op, Measure): + is_mid_circuit = not any( + isinstance(x, DAGOutNode) for x in dag.quantum_successors(node) + ) + if is_mid_circuit: + raise RuntimeWarning( + "Mid-circuit measurement durations reported from IBM backends are currently untrustworthy. Do not rely on on scheduling output." + ) duration = dag.calibrations[node.op.name][cal_key].duration else: duration = node.op.duration diff --git a/releasenotes/notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml b/releasenotes/notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml new file mode 100644 index 000000000000..1b8e12f366c2 --- /dev/null +++ b/releasenotes/notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml @@ -0,0 +1,9 @@ +fixes: + - | + Fixed an issue where the :class:`.ConstrainedReschedule` transpiler pass would previously error + if the circuit contained a :class:`~.circuit.Reset` instruction. This has been corrected so that the + pass no longer errors, however as the durations reported by IBM backends are not always + trustworthy for mid-circuit measurements and resets a ``RuntimeWarning`` is now raised if + the :class:`.ConstrainedReschedule` is run on a circuit containing either. + Fixed `#10354 `__ + From 1eb963be57813a4d34ddb1d498ba09fb31781b03 Mon Sep 17 00:00:00 2001 From: Hunter Kemeny <43501602+hunterkemeny@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:03:01 -0500 Subject: [PATCH 2/7] Update qiskit/transpiler/passes/scheduling/base_scheduler.py Co-authored-by: Naoki Kanazawa --- qiskit/transpiler/passes/scheduling/base_scheduler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py index 1b7ef1167de5..2496251a6adf 100644 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/base_scheduler.py @@ -267,7 +267,9 @@ def _get_node_duration( cal_key = tuple(indices), tuple(float(p) for p in node.op.params) if isinstance(node.op, Reset): raise RuntimeWarning( - "Reset durations reported from IBM backends are currently untrustworthy. Do not rely on on scheduling output." + "Qiskit scheduler assumes Reset works similarly to Measure instruction. " + "Actual behavior depends on the control system of your quantum backend. " + "Your backend may provide a plugin scheduler pass." ) elif isinstance(node.op, Measure): is_mid_circuit = not any( From f5ac24483c8e72d59ec2d816feb83858ce7f16f2 Mon Sep 17 00:00:00 2001 From: Hunter Kemeny <43501602+hunterkemeny@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:03:32 -0500 Subject: [PATCH 3/7] Update releasenotes/notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml Co-authored-by: Naoki Kanazawa --- .../notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/releasenotes/notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml b/releasenotes/notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml index 1b8e12f366c2..be2c94140300 100644 --- a/releasenotes/notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml +++ b/releasenotes/notes/add-scheduler-warnings-da6968a39fd8e6e7.yaml @@ -2,8 +2,10 @@ fixes: - | Fixed an issue where the :class:`.ConstrainedReschedule` transpiler pass would previously error if the circuit contained a :class:`~.circuit.Reset` instruction. This has been corrected so that the - pass no longer errors, however as the durations reported by IBM backends are not always - trustworthy for mid-circuit measurements and resets a ``RuntimeWarning`` is now raised if - the :class:`.ConstrainedReschedule` is run on a circuit containing either. + pass no longer errors, however an actual hardware may behave differently from + what Qiskit scheduler assumes especially for + mid-circuit measurements and resets. + Qiskit scheduler raises ``RuntimeWarning`` if + it encounters circuit containing either. Fixed `#10354 `__ From a24cda4aa41cce093369757205d52c7de9e3ab83 Mon Sep 17 00:00:00 2001 From: Hunter Kemeny <43501602+hunterkemeny@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:03:54 -0500 Subject: [PATCH 4/7] Update qiskit/transpiler/passes/scheduling/base_scheduler.py Co-authored-by: Naoki Kanazawa --- qiskit/transpiler/passes/scheduling/base_scheduler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py index 2496251a6adf..7792a6f4b67f 100644 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/base_scheduler.py @@ -277,7 +277,9 @@ def _get_node_duration( ) if is_mid_circuit: raise RuntimeWarning( - "Mid-circuit measurement durations reported from IBM backends are currently untrustworthy. Do not rely on on scheduling output." + "Qiskit scheduler assumes mid-circuit measurement works as a standard instruction. " + "Actual backend may apply custom scheduling. " + "Your backend may provide a plugin scheduler pass." ) duration = dag.calibrations[node.op.name][cal_key].duration else: From eaca5891a4e4f3644d6fb378bdf0372ab8b378f4 Mon Sep 17 00:00:00 2001 From: Hunter Kemeny <43501602+hunterkemeny@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:21:08 -0500 Subject: [PATCH 5/7] Move runtime warning about reset and measurement durations outside of calibration if statement --- .../passes/scheduling/base_scheduler.py | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py index 7792a6f4b67f..17d51558ba8c 100644 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/base_scheduler.py @@ -265,26 +265,27 @@ def _get_node_duration( if dag.has_calibration_for(node): # If node has calibration, this value should be the highest priority cal_key = tuple(indices), tuple(float(p) for p in node.op.params) - if isinstance(node.op, Reset): - raise RuntimeWarning( - "Qiskit scheduler assumes Reset works similarly to Measure instruction. " - "Actual behavior depends on the control system of your quantum backend. " - "Your backend may provide a plugin scheduler pass." - ) - elif isinstance(node.op, Measure): - is_mid_circuit = not any( - isinstance(x, DAGOutNode) for x in dag.quantum_successors(node) - ) - if is_mid_circuit: - raise RuntimeWarning( - "Qiskit scheduler assumes mid-circuit measurement works as a standard instruction. " - "Actual backend may apply custom scheduling. " - "Your backend may provide a plugin scheduler pass." - ) duration = dag.calibrations[node.op.name][cal_key].duration else: duration = node.op.duration + if isinstance(node.op, Reset): + raise RuntimeWarning( + "Qiskit scheduler assumes Reset works similarly to Measure instruction. " + "Actual behavior depends on the control system of your quantum backend. " + "Your backend may provide a plugin scheduler pass." + ) + elif isinstance(node.op, Measure): + is_mid_circuit = not any( + isinstance(x, DAGOutNode) for x in dag.quantum_successors(node) + ) + if is_mid_circuit: + raise RuntimeWarning( + "Qiskit scheduler assumes mid-circuit measurement works as a standard instruction. " + "Actual backend may apply custom scheduling. " + "Your backend may provide a plugin scheduler pass." + ) + if isinstance(duration, ParameterExpression): raise TranspilerError( f"Parameterized duration ({duration}) " From eb56cd3d86afad55e319da3706a19b4f9f0973ca Mon Sep 17 00:00:00 2001 From: Hunter Kemeny <43501602+hunterkemeny@users.noreply.github.com> Date: Tue, 27 Feb 2024 13:25:25 -0500 Subject: [PATCH 6/7] Replace raise with warn --- qiskit/transpiler/passes/scheduling/base_scheduler.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py index 17d51558ba8c..8929a8acfb96 100644 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/base_scheduler.py @@ -20,6 +20,8 @@ from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.target import Target +import warnings + class BaseSchedulerTransform(TransformationPass): """Base scheduler pass. @@ -270,7 +272,7 @@ def _get_node_duration( duration = node.op.duration if isinstance(node.op, Reset): - raise RuntimeWarning( + warnings.warn( "Qiskit scheduler assumes Reset works similarly to Measure instruction. " "Actual behavior depends on the control system of your quantum backend. " "Your backend may provide a plugin scheduler pass." @@ -280,7 +282,7 @@ def _get_node_duration( isinstance(x, DAGOutNode) for x in dag.quantum_successors(node) ) if is_mid_circuit: - raise RuntimeWarning( + warnings.warn( "Qiskit scheduler assumes mid-circuit measurement works as a standard instruction. " "Actual backend may apply custom scheduling. " "Your backend may provide a plugin scheduler pass." From 27ac90a8060f47638ed19242732b383330a45701 Mon Sep 17 00:00:00 2001 From: Hunter Kemeny <43501602+hunterkemeny@users.noreply.github.com> Date: Tue, 27 Feb 2024 14:35:18 -0500 Subject: [PATCH 7/7] Switch import orders --- qiskit/transpiler/passes/scheduling/base_scheduler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py index 8929a8acfb96..4085844a4709 100644 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/base_scheduler.py @@ -11,6 +11,8 @@ # that they have been altered from the originals. """Base circuit scheduling pass.""" +import warnings + from qiskit.transpiler import InstructionDurations from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.passes.scheduling.time_unit_conversion import TimeUnitConversion @@ -20,8 +22,6 @@ from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.target import Target -import warnings - class BaseSchedulerTransform(TransformationPass): """Base scheduler pass.