Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions qiskit_ibm_runtime/circuit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2025.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Module for vendor-specific circuit objects."""

from .library import *
16 changes: 16 additions & 0 deletions qiskit_ibm_runtime/circuit/library/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2025.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Module for vendor-specific instructions."""


from .mid_circuit_measure import MidCircuitMeasure
32 changes: 32 additions & 0 deletions qiskit_ibm_runtime/circuit/library/mid_circuit_measure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2025.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""MidCircuitMeasure gate."""

from qiskit.circuit import Instruction


class MidCircuitMeasure(Instruction):
"""
This instruction implements an alternative 'named' measurement definition
(1 classical bit, 1 quantum bit), whose name can be used to map to a corresponding
mid-circuit measurement instruction implementation on hardware.
"""

def __init__(self, name="measure_2", label=None):
if not name.startswith("measure_"):
raise ValueError(
"Invalid name for mid-circuit measure instruction."
"The provided name must start with `measure_`"
)

super().__init__(name, 1, 1, [], label=label)
3 changes: 2 additions & 1 deletion qiskit_ibm_runtime/transpiler/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@
ConvertIdToDelay
ConvertISAToClifford
FoldRzzAngle
ConvertToMidCircuitMeasure

See :mod:`qiskit_ibm_runtime.transpiler.passes.scheduling` for a collection of scheduling passes.
"""

from .basis import ConvertIdToDelay, FoldRzzAngle
from .basis import ConvertIdToDelay, FoldRzzAngle, ConvertToMidCircuitMeasure

# circuit scheduling
from .scheduling import ASAPScheduleAnalysis
Expand Down
1 change: 1 addition & 0 deletions qiskit_ibm_runtime/transpiler/passes/basis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@
"""Passes to layout circuits to IBM backend's instruction sets."""

from .convert_id_to_delay import ConvertIdToDelay
from .convert_mid_circ_meas import ConvertToMidCircuitMeasure
from .fold_rzz_angle import FoldRzzAngle
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2025.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Pass to convert mreplaces terminal measures in the middle of the circuit with
MidCircuitMeasure instructions."""

from qiskit.transpiler.basepasses import TransformationPass
from qiskit.circuit.measure import Measure
from qiskit.transpiler.passes.utils.remove_final_measurements import calc_final_ops


class ConvertToMidCircuitMeasure(TransformationPass):
"""This pass replaces terminal measures in the middle of the circuit with
MidCircuitMeasure instructions.
"""

def __init__(self, target):
super().__init__()
self.target = target

def run(self, dag):
"""Run the pass on a dag."""
mid_circ_measure = None
for inst in self.target.instructions:
if inst[0].name.startswith("measure_"):
mid_circ_measure = inst[0]
break
if not mid_circ_measure:
return dag

final_measure_nodes = calc_final_ops(dag, {"measure"})

for node in dag.op_nodes(Measure):
if node not in final_measure_nodes:
dag.substitute_node(node, mid_circ_measure, inplace=True)

return dag
9 changes: 9 additions & 0 deletions release-notes/unreleased/2316.feat.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The `qiskit-ibm-runtime` package has been extended with two new modules: :mod:`.circuit` and
:mod:`.circuit.library`. These modules are designed to mirror the structure of the
corresponding `qiskit` SDK modules, while providing vendor-specific implementations of
circuit objects and instructions.

The first addition to this new circuit library is the :class:`.MidCircuitMeasure` class.
This class enables the creation of instructions that follow the naming convention
`measure_<identifier>`, which are mapped to specific mid-circuit measurement
hardware instructions matching that pattern.
11 changes: 11 additions & 0 deletions test/unit/circuit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2025.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
90 changes: 90 additions & 0 deletions test/unit/circuit/test_mid_circ_meas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2025.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Tests for MidCircuitMeasure instruction."""

from qiskit import QuantumCircuit, generate_preset_pass_manager
from qiskit.circuit import Instruction
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.transpiler.exceptions import TranspilerError

from qiskit_ibm_runtime.circuit import MidCircuitMeasure
from qiskit_ibm_runtime.fake_provider import FakeVigoV2

from ...ibm_test_case import IBMTestCase


class TestMidCircuitMeasure(IBMTestCase):
"""Test MidCircuitMeasure instruction."""

def test_instantiation(self):
"""Test default instantiation."""
mcm = MidCircuitMeasure()
self.assertIs(mcm.base_class, MidCircuitMeasure)
self.assertIsInstance(mcm, Instruction)
self.assertEqual(mcm.name, "measure_2")
self.assertEqual(mcm.num_qubits, 1)
self.assertEqual(mcm.num_clbits, 1)

def test_instantiation_name(self):
"""Test instantiation with custom name."""
with self.subTest("measure_3"):
mcm = MidCircuitMeasure("measure_3")
self.assertIs(mcm.base_class, MidCircuitMeasure)
self.assertIsInstance(mcm, Instruction)
self.assertEqual(mcm.name, "measure_3")
self.assertEqual(mcm.num_qubits, 1)
self.assertEqual(mcm.num_clbits, 1)

with self.subTest("measure_reset"):
mcm = MidCircuitMeasure("measure_reset")
self.assertIs(mcm.base_class, MidCircuitMeasure)
self.assertIsInstance(mcm, Instruction)
self.assertEqual(mcm.name, "measure_reset")
self.assertEqual(mcm.num_qubits, 1)
self.assertEqual(mcm.num_clbits, 1)

with self.subTest("invalid_name"):
with self.assertRaises(ValueError):
mcm = MidCircuitMeasure("invalid_name")

def test_circuit_integration(self):
"""Test appending to circuit."""
mcm = MidCircuitMeasure()
qc = QuantumCircuit(1, 2)
qc.append(mcm, [0], [0])
qc.append(mcm, [0], [1])
qc.reset(0)
self.assertIs(qc.data[0].operation, mcm)
self.assertIs(qc.data[1].operation, mcm)

def test_transpiler_compat_without(self):
"""Test that default pass manager FAILS if measure_2 not in Target."""
mcm = MidCircuitMeasure()
backend = FakeVigoV2()
pm = generate_preset_pass_manager(backend=backend, seed_transpiler=0)
qc = QuantumCircuit(1, 2)
qc.append(mcm, [0], [0])
with self.assertRaises(TranspilerError):
_ = pm.run(qc)

def test_transpiler_compat_with(self):
"""Test that default pass manager PASSES if measure_2 is in Target
and doesn't modify the instruction."""
mcm = MidCircuitMeasure()
backend = GenericBackendV2(num_qubits=5, seed=0)
backend.target.add_instruction(mcm, {(i,): None for i in range(5)})
pm = generate_preset_pass_manager(backend=backend, seed_transpiler=0)
qc = QuantumCircuit(1, 2)
qc.append(mcm, [0], [0])
transpiled = pm.run(qc)
self.assertEqual(transpiled.data[0].operation.name, "measure_2")
69 changes: 69 additions & 0 deletions test/unit/transpiler/passes/basis/test_convert_mid_circ_meas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2025.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Test the conversion of terminal Measure to MidCircuitMeasure."""

from qiskit.circuit import QuantumCircuit
from qiskit.circuit.library import Measure
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.transpiler import PassManager

from qiskit_ibm_runtime.circuit import MidCircuitMeasure
from qiskit_ibm_runtime.transpiler.passes.basis.convert_mid_circ_meas import (
ConvertToMidCircuitMeasure,
)

from .....ibm_test_case import IBMTestCase


class TestConvertToMidCircuitMeasure(IBMTestCase):
"""Tests the ConvertToMidCircuitMeasure pass"""

def setUp(self):
super().setUp()

num_qubits = 5
mcm = MidCircuitMeasure()
self.target_without = GenericBackendV2(num_qubits=num_qubits, seed=0).target
self.target_with = GenericBackendV2(num_qubits=num_qubits, seed=0).target
self.target_with.add_instruction(mcm, {(i,): None for i in range(num_qubits)})

self.qc = QuantumCircuit(2, 2)
self.qc.x(0)
self.qc.append(mcm, [0], [0])
self.qc.measure([0], [0])
self.qc.measure_all()

def test_transpile_with(self):
custom_pass = ConvertToMidCircuitMeasure(self.target_with)
pm = PassManager([custom_pass])
transpiled = pm.run(self.qc)
self.assertIsInstance(transpiled.data[1].operation, MidCircuitMeasure)
self.assertIsInstance(transpiled.data[2].operation, MidCircuitMeasure)
# [3] is the barrier
self.assertNotIsInstance(transpiled.data[4].operation, MidCircuitMeasure)
self.assertIsInstance(transpiled.data[4].operation, Measure)
self.assertNotIsInstance(transpiled.data[5].operation, MidCircuitMeasure)
self.assertIsInstance(transpiled.data[5].operation, Measure)

def test_transpile_without(self):
custom_pass = ConvertToMidCircuitMeasure(self.target_without)
pm = PassManager([custom_pass])
transpiled = pm.run(self.qc)
self.assertIsInstance(transpiled.data[1].operation, MidCircuitMeasure)
self.assertNotIsInstance(transpiled.data[2].operation, MidCircuitMeasure)
self.assertIsInstance(transpiled.data[2].operation, Measure)
# [3] is the barrier
self.assertNotIsInstance(transpiled.data[4].operation, MidCircuitMeasure)
self.assertIsInstance(transpiled.data[4].operation, Measure)
self.assertNotIsInstance(transpiled.data[5].operation, MidCircuitMeasure)
self.assertIsInstance(transpiled.data[5].operation, Measure)
Loading