Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update gate.control() to return AnnotatedOperation #11433

Merged
merged 11 commits into from
Jan 31, 2024
31 changes: 31 additions & 0 deletions qiskit/circuit/annotated_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

"""Annotated Operations."""

from __future__ import annotations

import dataclasses
from typing import Union, List

Expand Down Expand Up @@ -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):
"""
Expand Down
31 changes: 22 additions & 9 deletions qiskit/circuit/gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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]]:
Expand Down
10 changes: 6 additions & 4 deletions qiskit/circuit/library/generalized_gates/mcmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
40 changes: 25 additions & 15 deletions qiskit/circuit/library/generalized_gates/unitary.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -155,33 +156,42 @@ 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:
num_ctrl_qubits: Number of controls to add to gate (default is 1).
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
Expand Down
15 changes: 12 additions & 3 deletions qiskit/circuit/library/standard_gates/h.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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)."""
Expand Down
51 changes: 39 additions & 12 deletions qiskit/circuit/library/standard_gates/p.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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):
Expand Down Expand Up @@ -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.

Expand All @@ -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)`)"""
Expand Down Expand Up @@ -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.

Expand All @@ -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)`)"""
Expand Down
15 changes: 12 additions & 3 deletions qiskit/circuit/library/standard_gates/rx.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.
Expand Down
Loading
Loading