Fix `Instruction.repeat` with conditionals (#11940)

* Fix `Instruction.repeat` with conditionals

We can't put register conditionals within an `Instruction.definition`
field; the data model of `QuantumCircuit` doesn't permit closing over
registers from within definitions.  This commit moves a condition to the
outer `Instruction` that's returned.

* Avoid 'to_mutable' if not needed

* Add proviso on repeated conditionals in documentation

* Update wording in release note

Co-authored-by: Luciano Bello <bel@zurich.ibm.com>

---------

Co-authored-by: Luciano Bello <bel@zurich.ibm.com>
This commit is contained in:
Jake Lishman 2024-03-06 12:18:20 +00:00 committed by GitHub
parent 3e7f8b8018
commit 6ebb7aa577
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 64 additions and 13 deletions

View File

@ -41,7 +41,6 @@ from typing import List, Type
import numpy
from qiskit.circuit.exceptions import CircuitError
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit.classicalregister import ClassicalRegister, Clbit
from qiskit.qobj.qasm_qobj import QasmQobjInstruction
from qiskit.circuit.parameter import ParameterExpression
@ -560,7 +559,13 @@ class Instruction(Operation):
)
def repeat(self, n):
"""Creates an instruction with `gate` repeated `n` amount of times.
"""Creates an instruction with ``self`` repeated :math`n` times.
If this operation has a conditional, the output instruction will have the same conditional
and the inner repeated operations will be unconditional; instructions within a compound
definition cannot be conditioned on registers within Qiskit's data model. This means that
it is not valid to apply a repeated instruction to a clbit that it both writes to and reads
from in its condition.
Args:
n (int): Number of times to repeat the instruction
@ -577,22 +582,24 @@ class Instruction(Operation):
n = int(n)
instruction = self._return_repeat(n)
qargs = [] if self.num_qubits == 0 else QuantumRegister(self.num_qubits, "q")
cargs = [] if self.num_clbits == 0 else ClassicalRegister(self.num_clbits, "c")
if instruction.definition is None:
# pylint: disable=cyclic-import
from qiskit.circuit import QuantumCircuit, CircuitInstruction
qc = QuantumCircuit()
if qargs:
qc.add_register(qargs)
if cargs:
qc.add_register(cargs)
circuit_instruction = CircuitInstruction(self, qargs, cargs)
qc = QuantumCircuit(self.num_qubits, self.num_clbits)
qargs = tuple(qc.qubits)
cargs = tuple(qc.clbits)
base = self.copy()
if self.condition:
# Condition is handled on the outer instruction.
base = base.to_mutable()
base.condition = None
for _ in [None] * n:
qc._append(circuit_instruction)
instruction.definition = qc
qc._append(CircuitInstruction(base, qargs, cargs))
instruction.definition = qc
if self.condition:
instruction = instruction.c_if(*self.condition)
return instruction
@property

View File

@ -0,0 +1,8 @@
---
fixes:
- |
The method :meth:`.Instruction.repeat` now moves a set :attr:`~.Instruction.condition` to the
outer returned :class:`~.circuit.Instruction` and leave the inner gates of its definition
unconditional. Previously, the method would leave :class:`.ClassicalRegister` instances within
the inner definition, which was an invalid state, and would manifest itself as seemingly unrelated
bugs later, such as during transpilation or export. Fixed `#11935 <https://github.com/Qiskit/qiskit/issues/11935>`__.

View File

@ -52,6 +52,18 @@ class TestRepeatInt1Q(QiskitTestCase):
self.assertEqual(result.definition, expected.definition)
self.assertIsInstance(result, Gate)
def test_conditional(self):
"""Test that repetition works with a condition."""
cr = ClassicalRegister(3, "cr")
gate = SGate().c_if(cr, 7).repeat(5)
self.assertEqual(gate.condition, (cr, 7))
defn = QuantumCircuit(1)
for _ in range(5):
# No conditions on the inner bit.
defn.s(0)
self.assertEqual(gate.definition, defn)
class TestRepeatInt2Q(QiskitTestCase):
"""Test gate_q2.repeat() with integer"""
@ -83,6 +95,18 @@ class TestRepeatInt2Q(QiskitTestCase):
self.assertEqual(result.definition, expected.definition)
self.assertIsInstance(result, Gate)
def test_conditional(self):
"""Test that repetition works with a condition."""
cr = ClassicalRegister(3, "cr")
gate = CXGate().c_if(cr, 7).repeat(5)
self.assertEqual(gate.condition, (cr, 7))
defn = QuantumCircuit(2)
for _ in range(5):
# No conditions on the inner bit.
defn.cx(0, 1)
self.assertEqual(gate.definition, defn)
class TestRepeatIntMeasure(QiskitTestCase):
"""Test Measure.repeat() with integer"""
@ -118,6 +142,18 @@ class TestRepeatIntMeasure(QiskitTestCase):
self.assertIsInstance(result, Instruction)
self.assertNotIsInstance(result, Gate)
def test_measure_conditional(self):
"""Test conditional measure moves condition to the outside."""
cr = ClassicalRegister(3, "cr")
measure = Measure().c_if(cr, 7).repeat(5)
self.assertEqual(measure.condition, (cr, 7))
defn = QuantumCircuit(1, 1)
for _ in range(5):
# No conditions on the inner bit.
defn.measure(0, 0)
self.assertEqual(measure.definition, defn)
class TestRepeatErrors(QiskitTestCase):
"""Test when Gate.repeat() should raise."""