diff --git a/qiskit/circuit/library/standard_gates/i.py b/qiskit/circuit/library/standard_gates/i.py index 40e0976f69d7..8a7c3783ca02 100644 --- a/qiskit/circuit/library/standard_gates/i.py +++ b/qiskit/circuit/library/standard_gates/i.py @@ -20,12 +20,14 @@ class IGate(Gate): r"""Identity gate. - Identity gate corresponds to a single-qubit gate wait cycle, - and should not be optimized or unrolled (it is an opaque gate). + This gate is a quantum no-op and represents an identity channel on a single qubit. It will not + have a logical effect on the qubit state, and will likely be optimized away by the compiler at + all optimization levels (except zero, if ``id`` is specified in the basis of the target). + Certain hardware backends *may* choose to implement this instruction with a potentially non-zero + idle duration, for example a delay of length equal to that of a single-qubit gate. - Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` - with the :meth:`~qiskit.circuit.QuantumCircuit.i` and - :meth:`~qiskit.circuit.QuantumCircuit.id` methods. + Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` with the + :meth:`~qiskit.circuit.QuantumCircuit.i` and :meth:`~qiskit.circuit.QuantumCircuit.id` methods. **Matrix Representation:** @@ -48,6 +50,11 @@ def __init__(self, label: Optional[str] = None): """Create new Identity gate.""" super().__init__("id", 1, [], label=label) + def _define(self) -> None: + from qiskit.circuit import QuantumCircuit # pylint: disable=cyclic-import + + self.definition = QuantumCircuit(1, name=self.name) + def inverse(self): """Invert this gate.""" return IGate() # self-inverse diff --git a/releasenotes/notes/fix-igate-definition-7385cff02e663867.yaml b/releasenotes/notes/fix-igate-definition-7385cff02e663867.yaml new file mode 100644 index 000000000000..59cb5fdf1b1c --- /dev/null +++ b/releasenotes/notes/fix-igate-definition-7385cff02e663867.yaml @@ -0,0 +1,24 @@ +fixes: + - | + Fixed a bug where certain operations, such as + :meth:`.QuantumCircuit.control`, would crash if an identity operation + represented by an :class:`.IGate` was present in a :class:`.QuantumCircuit`. + For example, the following code was previously an error, but will now function + correctly:: + + from qiskit import QuantumCircuit + + qc = QuantumCircuit(1) + qc.i(0) + controlled = qc.control() +upgrade: + - | + Qiskit Terra will now generally treat the identity gate :class:`.IGate` (with corresponding + circuit method :meth:`.QuantumCircuit.i`) as a quantum no-op, and more aggressively remove it + during calls to :func:`.transpile`. This is a change to previous behaviour that supported + legacy IBM Quantum systems where the ``id`` instruction was a short-hand for a delay of the + same duration as a non-virtual single-qubit gate (usually ``sx``). + + This changes removes a lot of surprises when working with :class:`.IGate` at a high-level; + various circuit and synthesis operations would previously be unable to handle the gate because + it appeared as a "magic" opaque instruction. diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py index 2612f2cfb8c9..07cd611fcd3c 100644 --- a/test/python/circuit/test_circuit_operations.py +++ b/test/python/circuit/test_circuit_operations.py @@ -830,6 +830,27 @@ def test_control_implementation(self): self.assertEqual(ref, c_qc) + def test_control_primitive_gates(self): + """Test that circuits containing primitive gates (with no definition) can be controlled + during `QuantumCircuit.control`. + + Regression test of gh-7399.""" + qc = QuantumCircuit(2) + qc.u(0.2, 0.3, 0.4, 0) + qc.i(1) + controlled = qc.control() + + # The identity gate should have no effect. + expected = QuantumCircuit( + QuantumRegister(1, name="control"), QuantumRegister(2, name="target") + ) + expected.cu(0.2, 0.3, 0.4, 0, 0, 1) + + self.assertIsInstance(controlled, QuantumCircuit) + self.assertEqual(len(controlled.data), 1) + instruction, _, _ = controlled.data[0] + self.assertEqual(instruction.definition, expected) + @data("gate", "instruction") def test_repeat_appended_type(self, subtype): """Test repeat appends Gate if circuit contains only gates and Instructions otherwise."""