diff --git a/qiskit/circuit/annotated_operation.py b/qiskit/circuit/annotated_operation.py index 2a389b141ba6..c473549ad631 100644 --- a/qiskit/circuit/annotated_operation.py +++ b/qiskit/circuit/annotated_operation.py @@ -12,6 +12,8 @@ """Annotated Operations.""" +from __future__ import annotations + import dataclasses from typing import Union, List @@ -150,6 +152,35 @@ def to_matrix(self): raise CircuitError(f"Unknown modifier {modifier}.") return operator + def control( + self, + num_ctrl_qubits: int = 1, + label: str | None = None, + ctrl_state: int | str | None = None, + annotated: bool = True, + ) -> AnnotatedOperation: + """ + Return the controlled version of itself. + + Implemented as an annotated operation, see :class:`.AnnotatedOperation`. + + Args: + num_ctrl_qubits: number of controls to add to gate (default: ``1``) + label: ignored (used for consistency with other control methods) + ctrl_state: The control state in decimal or as a bitstring + (e.g. ``'111'``). If ``None``, use ``2**num_ctrl_qubits-1``. + annotated: ignored (used for consistency with other control methods) + + Returns: + Controlled version of the given operation. + """ + # pylint: disable=unused-argument + extended_modifiers = self.modifiers.copy() + extended_modifiers.append( + ControlModifier(num_ctrl_qubits=num_ctrl_qubits, ctrl_state=ctrl_state) + ) + return AnnotatedOperation(self.base_op, extended_modifiers) + def _canonicalize_modifiers(modifiers): """ diff --git a/qiskit/circuit/gate.py b/qiskit/circuit/gate.py index 2157e0928a13..af29f8033b44 100644 --- a/qiskit/circuit/gate.py +++ b/qiskit/circuit/gate.py @@ -18,6 +18,7 @@ from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.circuit.exceptions import CircuitError +from .annotated_operation import AnnotatedOperation, ControlModifier from .instruction import Instruction @@ -90,27 +91,39 @@ def control( num_ctrl_qubits: int = 1, label: str | None = None, ctrl_state: int | str | None = None, + annotated: bool = False, ): - """Return controlled version of gate. See :class:`.ControlledGate` for usage. + """ + Return the controlled version of itself. + + Implemented either as a controlled gate (ref. :class:`.ControlledGate`) + or as an annotated operation (ref. :class:`.AnnotatedOperation`). Args: num_ctrl_qubits: number of controls to add to gate (default: ``1``) - label: optional gate label - ctrl_state: The control state in decimal or as a bitstring + label: optional gate label. Ignored if implemented as an annotated + operation. + ctrl_state: the control state in decimal or as a bitstring (e.g. ``'111'``). If ``None``, use ``2**num_ctrl_qubits-1``. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: - qiskit.circuit.ControlledGate: Controlled version of gate. This default algorithm - uses ``num_ctrl_qubits-1`` ancilla qubits so returns a gate of size - ``num_qubits + 2*num_ctrl_qubits - 1``. + Controlled version of the given operation. Raises: QiskitError: unrecognized mode or invalid ctrl_state """ - # pylint: disable=cyclic-import - from .add_control import add_control + if not annotated: + # pylint: disable=cyclic-import + from .add_control import add_control - return add_control(self, num_ctrl_qubits, label, ctrl_state) + return add_control(self, num_ctrl_qubits, label, ctrl_state) + + else: + return AnnotatedOperation( + self, ControlModifier(num_ctrl_qubits=num_ctrl_qubits, ctrl_state=ctrl_state) + ) @staticmethod def _broadcast_single_argument(qarg: list) -> Iterator[tuple[list, list]]: diff --git a/qiskit/circuit/library/generalized_gates/mcmt.py b/qiskit/circuit/library/generalized_gates/mcmt.py index c6ec0bda116e..b4bffc4237af 100644 --- a/qiskit/circuit/library/generalized_gates/mcmt.py +++ b/qiskit/circuit/library/generalized_gates/mcmt.py @@ -142,11 +142,13 @@ def _identify_gate(self, gate): return base_gate - def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None, annotated=False): """Return the controlled version of the MCMT circuit.""" - if ctrl_state is None: # TODO add ctrl state implementation by adding X gates - return MCMT(self.gate, self.num_ctrl_qubits + num_ctrl_qubits, self.num_target_qubits) - return super().control(num_ctrl_qubits, label, ctrl_state) + if not annotated and ctrl_state is None: + gate = MCMT(self.gate, self.num_ctrl_qubits + num_ctrl_qubits, self.num_target_qubits) + else: + gate = super().control(num_ctrl_qubits, label, ctrl_state, annotated=annotated) + return gate def inverse(self): """Return the inverse MCMT circuit, which is itself.""" diff --git a/qiskit/circuit/library/generalized_gates/unitary.py b/qiskit/circuit/library/generalized_gates/unitary.py index 46aac9564e32..2e0f7fea369d 100644 --- a/qiskit/circuit/library/generalized_gates/unitary.py +++ b/qiskit/circuit/library/generalized_gates/unitary.py @@ -19,6 +19,7 @@ from qiskit.circuit.gate import Gate from qiskit.circuit.controlledgate import ControlledGate +from qiskit.circuit.annotated_operation import AnnotatedOperation, ControlModifier from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.circuit.quantumregister import QuantumRegister from qiskit.circuit.exceptions import CircuitError @@ -155,9 +156,10 @@ def _define(self): def control( self, num_ctrl_qubits: int = 1, - label: int | None = None, + label: str | None = None, ctrl_state: int | str | None = None, - ) -> ControlledGate: + annotated: bool = False, + ) -> ControlledGate | AnnotatedOperation: """Return controlled version of gate. Args: @@ -165,23 +167,31 @@ def control( label: Optional gate label. ctrl_state: The control state in decimal or as a bit string (e.g. ``"1011"``). If ``None``, use ``2**num_ctrl_qubits - 1``. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: Controlled version of gate. """ - mat = self.to_matrix() - cmat = _compute_control_matrix(mat, num_ctrl_qubits, ctrl_state=None) - iso = Isometry(cmat, 0, 0) - return ControlledGate( - "c-unitary", - num_qubits=self.num_qubits + num_ctrl_qubits, - params=[mat], - label=label, - num_ctrl_qubits=num_ctrl_qubits, - definition=iso.definition, - ctrl_state=ctrl_state, - base_gate=self.copy(), - ) + if not annotated: + mat = self.to_matrix() + cmat = _compute_control_matrix(mat, num_ctrl_qubits, ctrl_state=None) + iso = Isometry(cmat, 0, 0) + gate = ControlledGate( + "c-unitary", + num_qubits=self.num_qubits + num_ctrl_qubits, + params=[mat], + label=label, + num_ctrl_qubits=num_ctrl_qubits, + definition=iso.definition, + ctrl_state=ctrl_state, + base_gate=self.copy(), + ) + else: + gate = AnnotatedOperation( + self, ControlModifier(num_ctrl_qubits=num_ctrl_qubits, ctrl_state=ctrl_state) + ) + return gate def _qasm2_decomposition(self): """Return an unparameterized version of ourselves, so the OQ2 exporter doesn't choke on the diff --git a/qiskit/circuit/library/standard_gates/h.py b/qiskit/circuit/library/standard_gates/h.py index ab58194b0c04..b3050d37179b 100644 --- a/qiskit/circuit/library/standard_gates/h.py +++ b/qiskit/circuit/library/standard_gates/h.py @@ -78,6 +78,7 @@ def control( num_ctrl_qubits: int = 1, label: Optional[str] = None, ctrl_state: Optional[Union[int, str]] = None, + annotated: bool = False, ): """Return a (multi-)controlled-H gate. @@ -88,14 +89,22 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: + if not annotated and num_ctrl_qubits == 1: gate = CHGate(label=label, ctrl_state=ctrl_state, _base_label=self.label) - return gate - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) + return gate def inverse(self): r"""Return inverted H gate (itself).""" diff --git a/qiskit/circuit/library/standard_gates/p.py b/qiskit/circuit/library/standard_gates/p.py index 7a354fa4f4ab..ef9f368dcb5d 100644 --- a/qiskit/circuit/library/standard_gates/p.py +++ b/qiskit/circuit/library/standard_gates/p.py @@ -96,6 +96,7 @@ def control( num_ctrl_qubits: int = 1, label: str | None = None, ctrl_state: str | int | None = None, + annotated: bool = False, ): """Return a (multi-)controlled-Phase gate. @@ -104,19 +105,25 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: + if not annotated and num_ctrl_qubits == 1: gate = CPhaseGate(self.params[0], label=label, ctrl_state=ctrl_state) - elif ctrl_state is None and num_ctrl_qubits > 1: + gate.base_gate.label = self.label + elif not annotated and ctrl_state is None and num_ctrl_qubits > 1: gate = MCPhaseGate(self.params[0], num_ctrl_qubits, label=label) + gate.base_gate.label = self.label else: - return super().control( - num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, ) - gate.base_gate.label = self.label return gate def inverse(self): @@ -232,6 +239,7 @@ def control( num_ctrl_qubits: int = 1, label: str | None = None, ctrl_state: str | int | None = None, + annotated: bool = False, ): """Controlled version of this gate. @@ -240,15 +248,23 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - if ctrl_state is None: + if not annotated and ctrl_state is None: gate = MCPhaseGate(self.params[0], num_ctrl_qubits=num_ctrl_qubits + 1, label=label) gate.base_gate.label = self.label - return gate - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) + return gate def inverse(self): r"""Return inverted CPhase gate (:math:`CPhase(\lambda)^{\dagger} = CPhase(-\lambda)`)""" @@ -349,6 +365,7 @@ def control( num_ctrl_qubits: int = 1, label: str | None = None, ctrl_state: str | int | None = None, + annotated: bool = False, ): """Controlled version of this gate. @@ -357,17 +374,27 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - if ctrl_state is None: + if not annotated and ctrl_state is None: gate = MCPhaseGate( - self.params[0], num_ctrl_qubits=num_ctrl_qubits + self.num_ctrl_qubits, label=label + self.params[0], + num_ctrl_qubits=num_ctrl_qubits + self.num_ctrl_qubits, + label=label, ) gate.base_gate.label = self.label - return gate - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) + return gate def inverse(self): r"""Return inverted MCU1 gate (:math:`MCU1(\lambda)^{\dagger} = MCU1(-\lambda)`)""" diff --git a/qiskit/circuit/library/standard_gates/rx.py b/qiskit/circuit/library/standard_gates/rx.py index 69b8fe8fa328..05ebe6a5c3ae 100644 --- a/qiskit/circuit/library/standard_gates/rx.py +++ b/qiskit/circuit/library/standard_gates/rx.py @@ -77,6 +77,7 @@ def control( num_ctrl_qubits: int = 1, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + annotated: bool = False, ): """Return a (multi-)controlled-RX gate. @@ -85,15 +86,23 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: + if not annotated and num_ctrl_qubits == 1: gate = CRXGate(self.params[0], label=label, ctrl_state=ctrl_state) gate.base_gate.label = self.label - return gate - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) + return gate def inverse(self): r"""Return inverted RX gate. diff --git a/qiskit/circuit/library/standard_gates/ry.py b/qiskit/circuit/library/standard_gates/ry.py index 6fed9206dcb7..739f674d5110 100644 --- a/qiskit/circuit/library/standard_gates/ry.py +++ b/qiskit/circuit/library/standard_gates/ry.py @@ -76,6 +76,7 @@ def control( num_ctrl_qubits: int = 1, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + annotated: bool = False, ): """Return a (multi-)controlled-RY gate. @@ -84,15 +85,23 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: + if not annotated and num_ctrl_qubits == 1: gate = CRYGate(self.params[0], label=label, ctrl_state=ctrl_state) gate.base_gate.label = self.label - return gate - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) + return gate def inverse(self): r"""Return inverted RY gate. diff --git a/qiskit/circuit/library/standard_gates/rz.py b/qiskit/circuit/library/standard_gates/rz.py index b96fe6183869..8a3e0ec13863 100644 --- a/qiskit/circuit/library/standard_gates/rz.py +++ b/qiskit/circuit/library/standard_gates/rz.py @@ -87,6 +87,7 @@ def control( num_ctrl_qubits: int = 1, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + annotated: bool = False, ): """Return a (multi-)controlled-RZ gate. @@ -95,15 +96,23 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: + if not annotated and num_ctrl_qubits == 1: gate = CRZGate(self.params[0], label=label, ctrl_state=ctrl_state) gate.base_gate.label = self.label - return gate - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) + return gate def inverse(self): r"""Return inverted RZ gate diff --git a/qiskit/circuit/library/standard_gates/swap.py b/qiskit/circuit/library/standard_gates/swap.py index 2a7ccd16d41f..3fb193ebea25 100644 --- a/qiskit/circuit/library/standard_gates/swap.py +++ b/qiskit/circuit/library/standard_gates/swap.py @@ -89,6 +89,7 @@ def control( num_ctrl_qubits: int = 1, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + annotated: bool = False, ): """Return a (multi-)controlled-SWAP gate. @@ -99,14 +100,22 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: + if not annotated and num_ctrl_qubits == 1: gate = CSwapGate(label=label, ctrl_state=ctrl_state, _base_label=self.label) - return gate - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) + return gate def inverse(self): """Return inverse Swap gate (itself).""" diff --git a/qiskit/circuit/library/standard_gates/sx.py b/qiskit/circuit/library/standard_gates/sx.py index 88a8119e59b2..fa9b5910af83 100644 --- a/qiskit/circuit/library/standard_gates/sx.py +++ b/qiskit/circuit/library/standard_gates/sx.py @@ -93,6 +93,7 @@ def control( num_ctrl_qubits: int = 1, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + annotated: bool = False, ): """Return a (multi-)controlled-SX gate. @@ -103,14 +104,22 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: SingletonControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: + if not annotated and num_ctrl_qubits == 1: gate = CSXGate(label=label, ctrl_state=ctrl_state, _base_label=self.label) - return gate - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) + return gate def __eq__(self, other): return isinstance(other, SXGate) diff --git a/qiskit/circuit/library/standard_gates/u.py b/qiskit/circuit/library/standard_gates/u.py index 30f6ff7f735b..6170de0f2b3e 100644 --- a/qiskit/circuit/library/standard_gates/u.py +++ b/qiskit/circuit/library/standard_gates/u.py @@ -92,6 +92,7 @@ def control( num_ctrl_qubits: int = 1, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + annotated: bool = False, ): """Return a (multi-)controlled-U gate. @@ -100,11 +101,13 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: + if not annotated and num_ctrl_qubits == 1: gate = CUGate( self.params[0], self.params[1], @@ -114,8 +117,14 @@ def control( ctrl_state=ctrl_state, ) gate.base_gate.label = self.label - return gate - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) + return gate def __array__(self, dtype=complex): """Return a numpy.array for the U gate.""" diff --git a/qiskit/circuit/library/standard_gates/u1.py b/qiskit/circuit/library/standard_gates/u1.py index a948b394b0cd..08df09c1900d 100644 --- a/qiskit/circuit/library/standard_gates/u1.py +++ b/qiskit/circuit/library/standard_gates/u1.py @@ -116,6 +116,7 @@ def control( num_ctrl_qubits: int = 1, label: str | None = None, ctrl_state: str | int | None = None, + annotated: bool = False, ): """Return a (multi-)controlled-U1 gate. @@ -124,19 +125,25 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: + if not annotated and num_ctrl_qubits == 1: gate = CU1Gate(self.params[0], label=label, ctrl_state=ctrl_state) - elif ctrl_state is None and num_ctrl_qubits > 1: + gate.base_gate.label = self.label + elif not annotated and ctrl_state is None and num_ctrl_qubits > 1: gate = MCU1Gate(self.params[0], num_ctrl_qubits, label=label) + gate.base_gate.label = self.label else: - return super().control( - num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, ) - gate.base_gate.label = self.label return gate def inverse(self): @@ -245,6 +252,7 @@ def control( num_ctrl_qubits: int = 1, label: str | None = None, ctrl_state: str | int | None = None, + annotated: bool = False, ): """Controlled version of this gate. @@ -253,15 +261,23 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - if ctrl_state is None: + if not annotated and ctrl_state is None: gate = MCU1Gate(self.params[0], num_ctrl_qubits=num_ctrl_qubits + 1, label=label) gate.base_gate.label = self.label - return gate - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) + return gate def inverse(self): r"""Return inverted CU1 gate (:math:`CU1(\lambda)^{\dagger} = CU1(-\lambda)`)""" @@ -355,6 +371,7 @@ def control( num_ctrl_qubits: int = 1, label: str | None = None, ctrl_state: str | int | None = None, + annotated: bool = False, ): """Controlled version of this gate. @@ -363,19 +380,29 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) - new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state - gate = MCU1Gate( - self.params[0], - num_ctrl_qubits=num_ctrl_qubits + self.num_ctrl_qubits, - label=label, - ctrl_state=new_ctrl_state, - ) - gate.base_gate.label = self.label + if not annotated: + ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) + new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state + gate = MCU1Gate( + self.params[0], + num_ctrl_qubits=num_ctrl_qubits + self.num_ctrl_qubits, + label=label, + ctrl_state=new_ctrl_state, + ) + gate.base_gate.label = self.label + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) return gate def inverse(self): diff --git a/qiskit/circuit/library/standard_gates/u3.py b/qiskit/circuit/library/standard_gates/u3.py index c26c761fc333..2aaa9a8dad8c 100644 --- a/qiskit/circuit/library/standard_gates/u3.py +++ b/qiskit/circuit/library/standard_gates/u3.py @@ -105,6 +105,7 @@ def control( num_ctrl_qubits: int = 1, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + annotated: bool = False, ): """Return a (multi-)controlled-U3 gate. @@ -113,15 +114,23 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: + if not annotated and num_ctrl_qubits == 1: gate = CU3Gate(*self.params, label=label, ctrl_state=ctrl_state) gate.base_gate.label = self.label - return gate - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) + return gate def _define(self): from qiskit.circuit.quantumcircuit import QuantumCircuit diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index 4ea9712c3dd7..bb70f339ef04 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -97,6 +97,7 @@ def control( num_ctrl_qubits: int = 1, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + annotated: bool = False, ): """Return a (multi-)controlled-X gate. @@ -107,16 +108,26 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - gate = MCXGate( - num_ctrl_qubits=num_ctrl_qubits, - label=label, - ctrl_state=ctrl_state, - _base_label=self.label, - ) + if not annotated: + gate = MCXGate( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + _base_label=self.label, + ) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) return gate def inverse(self): @@ -221,6 +232,7 @@ def control( num_ctrl_qubits: int = 1, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + annotated: bool = False, ): """Return a controlled-X gate with more control lines. @@ -229,18 +241,28 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) - new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state - gate = MCXGate( - num_ctrl_qubits=num_ctrl_qubits + 1, - label=label, - ctrl_state=new_ctrl_state, - _base_label=self.label, - ) + if not annotated: + ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) + new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state + gate = MCXGate( + num_ctrl_qubits=num_ctrl_qubits + 1, + label=label, + ctrl_state=new_ctrl_state, + _base_label=self.label, + ) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) return gate def inverse(self): @@ -394,6 +416,7 @@ def control( num_ctrl_qubits: int = 1, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + annotated: bool = False, ): """Controlled version of this gate. @@ -402,18 +425,28 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) - new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state - gate = MCXGate( - num_ctrl_qubits=num_ctrl_qubits + 2, - label=label, - ctrl_state=new_ctrl_state, - _base_label=self.label, - ) + if not annotated: + ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) + new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state + gate = MCXGate( + num_ctrl_qubits=num_ctrl_qubits + 2, + label=label, + ctrl_state=new_ctrl_state, + _base_label=self.label, + ) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) return gate def inverse(self): @@ -714,6 +747,7 @@ def control( num_ctrl_qubits: int = 1, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + annotated: bool = False, ): """Controlled version of this gate. @@ -722,18 +756,29 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) - new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state - return MCXGate( - num_ctrl_qubits=num_ctrl_qubits + 3, - label=label, - ctrl_state=new_ctrl_state, - _base_label=self.label, - ) + if not annotated: + ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) + new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state + gate = MCXGate( + num_ctrl_qubits=num_ctrl_qubits + 3, + label=label, + ctrl_state=new_ctrl_state, + _base_label=self.label, + ) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) + return gate def inverse(self): """Invert this gate. The C4X is its own inverse.""" @@ -936,6 +981,7 @@ def control( num_ctrl_qubits: int = 1, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + annotated: bool = False, ): """Controlled version of this gate. @@ -944,18 +990,29 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) - new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state - return MCXGate( - num_ctrl_qubits=num_ctrl_qubits + 4, - label=label, - ctrl_state=new_ctrl_state, - _base_label=self.label, - ) + if not annotated: + ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits) + new_ctrl_state = (self.ctrl_state << num_ctrl_qubits) | ctrl_state + gate = MCXGate( + num_ctrl_qubits=num_ctrl_qubits + 4, + label=label, + ctrl_state=new_ctrl_state, + _base_label=self.label, + ) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) + return gate def inverse(self): """Invert this gate. The C4X is its own inverse.""" @@ -1068,6 +1125,7 @@ def control( num_ctrl_qubits: int = 1, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + annotated: bool = False, ): """Return a multi-controlled-X gate with more control lines. @@ -1076,19 +1134,23 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - if ctrl_state is None: + if not annotated and ctrl_state is None: # use __class__ so this works for derived classes - return self.__class__( + gate = self.__class__( self.num_ctrl_qubits + num_ctrl_qubits, label=label, ctrl_state=ctrl_state, _base_label=self.label, ) - return super().control(num_ctrl_qubits, label=label, ctrl_state=ctrl_state) + else: + gate = super().control(num_ctrl_qubits, label=label, ctrl_state=ctrl_state) + return gate class MCXGrayCode(MCXGate): diff --git a/qiskit/circuit/library/standard_gates/y.py b/qiskit/circuit/library/standard_gates/y.py index b57e29b5ed1f..6125e1204b96 100644 --- a/qiskit/circuit/library/standard_gates/y.py +++ b/qiskit/circuit/library/standard_gates/y.py @@ -94,6 +94,7 @@ def control( num_ctrl_qubits: int = 1, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + annotated: bool = False, ): """Return a (multi-)controlled-Y gate. @@ -104,14 +105,22 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: + if not annotated and num_ctrl_qubits == 1: gate = CYGate(label=label, ctrl_state=ctrl_state, _base_label=self.label) - return gate - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) + return gate def inverse(self): r"""Return inverted Y gate (:math:`Y^{\dagger} = Y`)""" diff --git a/qiskit/circuit/library/standard_gates/z.py b/qiskit/circuit/library/standard_gates/z.py index e79cfdd6e874..ba2e57488552 100644 --- a/qiskit/circuit/library/standard_gates/z.py +++ b/qiskit/circuit/library/standard_gates/z.py @@ -98,6 +98,7 @@ def control( num_ctrl_qubits: int = 1, label: Optional[str] = None, ctrl_state: Optional[Union[str, int]] = None, + annotated: bool = False, ): """Return a (multi-)controlled-Z gate. @@ -108,14 +109,22 @@ def control( label (str or None): An optional label for the gate [Default: None] ctrl_state (int or str or None): control state expressed as integer, string (e.g. '110'), or None. If None, use all 1s. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: + if not annotated and num_ctrl_qubits == 1: gate = CZGate(label=label, ctrl_state=ctrl_state, _base_label=self.label) - return gate - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state) + else: + gate = super().control( + num_ctrl_qubits=num_ctrl_qubits, + label=label, + ctrl_state=ctrl_state, + annotated=annotated, + ) + return gate def inverse(self): """Return inverted Z gate (itself).""" diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 3e994ca93cee..dd211f84a825 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -788,6 +788,7 @@ def control( num_ctrl_qubits: int = 1, label: str | None = None, ctrl_state: str | int | None = None, + annotated: bool = False, ) -> "QuantumCircuit": """Control this circuit on ``num_ctrl_qubits`` qubits. @@ -796,6 +797,8 @@ def control( label (str): An optional label to give the controlled operation for visualization. ctrl_state (str or int): The control state in decimal or as a bitstring (e.g. '111'). If None, use ``2**num_ctrl_qubits - 1``. + annotated: indicates whether the controlled gate can be implemented + as an annotated gate. Returns: QuantumCircuit: The controlled version of this circuit. @@ -812,7 +815,7 @@ def control( "be in the circuit for this operation." ) from ex - controlled_gate = gate.control(num_ctrl_qubits, label, ctrl_state) + controlled_gate = gate.control(num_ctrl_qubits, label, ctrl_state, annotated) control_qreg = QuantumRegister(num_ctrl_qubits) controlled_circ = QuantumCircuit( control_qreg, self.qubits, *self.qregs, name=f"c_{self.name}" diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index be1f74954dc9..bd40865772ba 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -428,8 +428,13 @@ def _synthesize_annotated_op(self, op: Operation) -> Union[Operation, None]: # This results in QuantumCircuit, DAGCircuit or Gate synthesized_op, _ = self._recursively_handle_op(op.base_op, qubits=None) - for modifier in op.modifiers: + if isinstance(synthesized_op, AnnotatedOperation): + raise TranspilerError( + "HighLevelSynthesis failed to synthesize the base operation of" + " an annotated operation." + ) + for modifier in op.modifiers: # If we have a DAGCircuit at this point, convert it to QuantumCircuit if isinstance(synthesized_op, DAGCircuit): synthesized_op = dag_to_circuit(synthesized_op, copy_operations=False) @@ -444,13 +449,18 @@ def _synthesize_annotated_op(self, op: Operation) -> Union[Operation, None]: if isinstance(synthesized_op, QuantumCircuit): synthesized_op = synthesized_op.to_gate() - # Adding control (this creates a ControlledGate) synthesized_op = synthesized_op.control( num_ctrl_qubits=modifier.num_ctrl_qubits, label=None, ctrl_state=modifier.ctrl_state, + annotated=False, ) + if isinstance(synthesized_op, AnnotatedOperation): + raise TranspilerError( + "HighLevelSynthesis failed to synthesize the control modifier." + ) + # Unrolling synthesized_op, _ = self._recursively_handle_op(synthesized_op) diff --git a/releasenotes/notes/add-annotated-arg-to-control-d9a188fe66f037ad.yaml b/releasenotes/notes/add-annotated-arg-to-control-d9a188fe66f037ad.yaml new file mode 100644 index 000000000000..d2a6a2ec6f95 --- /dev/null +++ b/releasenotes/notes/add-annotated-arg-to-control-d9a188fe66f037ad.yaml @@ -0,0 +1,16 @@ +--- +features: + - | + The methods :meth:`~qiskit.circuit.QuantumCircuit.control`, + :meth:`~qiskit.circuit.Gate.control`, as well as the similar methods + of subclasses of :class:`~qiskit.circuit.Gate` + (such as :class:`~qiskit.circuit.library.UnitaryGate` or + :class:`~qiskit.circuit.library.SwapGate`) all have an additional + argument ``annotated``. The default value of ``False`` corresponds to + the existing behavior, for example + ``SwapGate().control(1, annotated=False)`` returns a ``CSwapGate``, + while + ``SwapGate().control(2, annotated=False)`` returns a ``ControlledGate``. + The value of ``True`` returns an object of type + :class:`~.AnnotatedOperation` instead, avoiding the eager construction + of the controlled gate's definition. diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index 2ad4d4c11104..e24029696bcd 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -14,12 +14,14 @@ """Test Qiskit's controlled gate operation.""" import unittest + import numpy as np from numpy import pi from ddt import ddt, data, unpack from qiskit import QuantumRegister, QuantumCircuit, QiskitError from qiskit.circuit import ControlledGate, Parameter, Gate +from qiskit.circuit.annotated_operation import AnnotatedOperation from qiskit.circuit.singleton import SingletonControlledGate, _SingletonControlledGateOverrides from qiskit.circuit.exceptions import CircuitError from qiskit.quantum_info.operators.predicates import matrix_equal, is_unitary_matrix @@ -1593,5 +1595,129 @@ def test_control_label_1(self, gate, args): self.assertEqual(cgate.base_gate.label, "a gate") +@ddt +class TestControlledAnnotatedGate(QiskitTestCase): + """Tests for controlled gates and the AnnotatedOperation class.""" + + def test_controlled_x(self): + """Test creation of controlled x gate""" + controlled = XGate().control(annotated=False) + annotated = XGate().control(annotated=True) + self.assertNotIsInstance(controlled, AnnotatedOperation) + self.assertIsInstance(annotated, AnnotatedOperation) + self.assertEqual(Operator(controlled), Operator(annotated)) + + def test_controlled_y(self): + """Test creation of controlled y gate""" + controlled = YGate().control(annotated=False) + annotated = YGate().control(annotated=True) + self.assertNotIsInstance(controlled, AnnotatedOperation) + self.assertIsInstance(annotated, AnnotatedOperation) + self.assertEqual(Operator(controlled), Operator(annotated)) + + def test_controlled_z(self): + """Test creation of controlled z gate""" + controlled = ZGate().control(annotated=False) + annotated = ZGate().control(annotated=True) + self.assertNotIsInstance(controlled, AnnotatedOperation) + self.assertIsInstance(annotated, AnnotatedOperation) + self.assertEqual(Operator(controlled), Operator(annotated)) + + def test_controlled_h(self): + """Test the creation of a controlled H gate.""" + controlled = HGate().control(annotated=False) + annotated = HGate().control(annotated=True) + self.assertNotIsInstance(controlled, AnnotatedOperation) + self.assertIsInstance(annotated, AnnotatedOperation) + self.assertEqual(Operator(controlled), Operator(annotated)) + + def test_controlled_phase(self): + """Test the creation of a controlled U1 gate.""" + theta = 0.5 + controlled = PhaseGate(theta).control(annotated=False) + annotated = PhaseGate(theta).control(annotated=True) + self.assertNotIsInstance(controlled, AnnotatedOperation) + self.assertIsInstance(annotated, AnnotatedOperation) + self.assertEqual(Operator(controlled), Operator(annotated)) + + def test_controlled_u1(self): + """Test the creation of a controlled U1 gate.""" + theta = 0.5 + controlled = U1Gate(theta).control(annotated=False) + annotated = U1Gate(theta).control(annotated=True) + self.assertNotIsInstance(controlled, AnnotatedOperation) + self.assertIsInstance(annotated, AnnotatedOperation) + self.assertEqual(Operator(controlled), Operator(annotated)) + + def test_controlled_rz(self): + """Test the creation of a controlled RZ gate.""" + theta = 0.5 + controlled = RZGate(theta).control(annotated=False) + annotated = RZGate(theta).control(annotated=True) + self.assertNotIsInstance(controlled, AnnotatedOperation) + self.assertIsInstance(annotated, AnnotatedOperation) + self.assertEqual(Operator(controlled), Operator(annotated)) + + def test_controlled_ry(self): + """Test the creation of a controlled RY gate.""" + theta = 0.5 + controlled = RYGate(theta).control(annotated=False) + annotated = RYGate(theta).control(annotated=True) + self.assertNotIsInstance(controlled, AnnotatedOperation) + self.assertIsInstance(annotated, AnnotatedOperation) + self.assertEqual(Operator(controlled), Operator(annotated)) + + def test_controlled_rx(self): + """Test the creation of a controlled RX gate.""" + theta = 0.5 + controlled = RXGate(theta).control(annotated=False) + annotated = RXGate(theta).control(annotated=True) + self.assertNotIsInstance(controlled, AnnotatedOperation) + self.assertIsInstance(annotated, AnnotatedOperation) + self.assertEqual(Operator(controlled), Operator(annotated)) + + def test_controlled_u(self): + """Test the creation of a controlled U gate.""" + theta, phi, lamb = 0.1, 0.2, 0.3 + controlled = UGate(theta, phi, lamb).control(annotated=False) + annotated = UGate(theta, phi, lamb).control(annotated=True) + self.assertNotIsInstance(controlled, AnnotatedOperation) + self.assertIsInstance(annotated, AnnotatedOperation) + self.assertEqual(Operator(controlled), Operator(annotated)) + + def test_controlled_u3(self): + """Test the creation of a controlled U3 gate.""" + theta, phi, lamb = 0.1, 0.2, 0.3 + controlled = U3Gate(theta, phi, lamb).control(annotated=False) + annotated = U3Gate(theta, phi, lamb).control(annotated=True) + self.assertNotIsInstance(controlled, AnnotatedOperation) + self.assertIsInstance(annotated, AnnotatedOperation) + self.assertEqual(Operator(controlled), Operator(annotated)) + + def test_controlled_cx(self): + """Test creation of controlled cx gate""" + controlled = CXGate().control(annotated=False) + annotated = CXGate().control(annotated=True) + self.assertNotIsInstance(controlled, AnnotatedOperation) + self.assertIsInstance(annotated, AnnotatedOperation) + self.assertEqual(Operator(controlled), Operator(annotated)) + + def test_controlled_swap(self): + """Test creation of controlled swap gate""" + controlled = SwapGate().control(annotated=False) + annotated = SwapGate().control(annotated=True) + self.assertNotIsInstance(controlled, AnnotatedOperation) + self.assertIsInstance(annotated, AnnotatedOperation) + self.assertEqual(Operator(controlled), Operator(annotated)) + + def test_controlled_sx(self): + """Test creation of controlled SX gate""" + controlled = SXGate().control(annotated=False) + annotated = SXGate().control(annotated=True) + self.assertNotIsInstance(controlled, AnnotatedOperation) + self.assertIsInstance(annotated, AnnotatedOperation) + self.assertEqual(Operator(controlled), Operator(annotated)) + + if __name__ == "__main__": unittest.main()