Skip to content

Commit

Permalink
Fix Instruction.repeat with conditionals (Qiskit#11940)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

---------

Co-authored-by: Luciano Bello <[email protected]>
  • Loading branch information
2 people authored and IsmaelCesar committed Mar 13, 2024
1 parent f35cfd5 commit c126b45
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 13 deletions.
33 changes: 20 additions & 13 deletions qiskit/circuit/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
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
Expand Down Expand Up @@ -560,7 +559,13 @@ def _return_repeat(self, exponent):
)

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
Expand All @@ -577,22 +582,24 @@ def repeat(self, n):
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
Expand Down
Original file line number Diff line number Diff line change
@@ -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>`__.
36 changes: 36 additions & 0 deletions test/python/circuit/test_instruction_repeat.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ def test_standard_1Q_one(self):
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"""
Expand Down Expand Up @@ -83,6 +95,18 @@ def test_standard_2Q_one(self):
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"""
Expand Down Expand Up @@ -118,6 +142,18 @@ def test_measure_one(self):
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."""
Expand Down

0 comments on commit c126b45

Please sign in to comment.