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

Add AnnotatedOperation.params and fix some control issues (backport #12752) #12828

Merged
merged 1 commit into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions qiskit/circuit/annotated_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from typing import Union, List

from qiskit.circuit.operation import Operation
from qiskit.circuit.parameterexpression import ParameterValueType
from qiskit.circuit._utils import _compute_control_matrix, _ctrl_state_to_int
from qiskit.circuit.exceptions import CircuitError

Expand Down Expand Up @@ -219,6 +220,27 @@ def power(self, exponent: float, annotated: bool = False):
extended_modifiers.append(PowerModifier(exponent))
return AnnotatedOperation(self.base_op, extended_modifiers)

@property
def params(self) -> list[ParameterValueType]:
"""The params of the underlying base operation."""
return getattr(self.base_op, "params", [])

@params.setter
def params(self, value: list[ParameterValueType]):
if hasattr(self.base_op, "params"):
self.base_op.params = value
else:
raise AttributeError(
f"Cannot set attribute ``params`` on the base operation {self.base_op}."
)

def validate_parameter(self, parameter: ParameterValueType) -> ParameterValueType:
"""Validate a parameter for the underlying base operation."""
if hasattr(self.base_op, "validate_parameter"):
return self.base_op.validate_parameter(parameter)

raise AttributeError(f"Cannot validate parameters on the base operation {self.base_op}.")


def _canonicalize_modifiers(modifiers):
"""
Expand Down
15 changes: 9 additions & 6 deletions qiskit/circuit/gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,9 @@ def control(
num_ctrl_qubits: int = 1,
label: str | None = None,
ctrl_state: int | str | None = None,
annotated: bool = False,
annotated: bool | None = None,
):
"""
Return the controlled version of itself.
"""Return the controlled version of itself.

Implemented either as a controlled gate (ref. :class:`.ControlledGate`)
or as an annotated operation (ref. :class:`.AnnotatedOperation`).
Expand All @@ -118,16 +117,20 @@ def control(
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.
annotated: indicates whether the controlled gate is implemented
as an annotated gate. If ``None``, this is set to ``False``
if the controlled gate can directly be constructed, and otherwise
set to ``True``. This allows defering the construction process in case the
synthesis of the controlled gate requires more information (e.g.
values of unbound parameters).

Returns:
Controlled version of the given operation.

Raises:
QiskitError: unrecognized mode or invalid ctrl_state
"""
if not annotated:
if not annotated: # captures both None and False
# pylint: disable=cyclic-import
from .add_control import add_control

Expand Down
6 changes: 3 additions & 3 deletions qiskit/circuit/library/generalized_gates/unitary.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def control(
num_ctrl_qubits: int = 1,
label: str | None = None,
ctrl_state: int | str | None = None,
annotated: bool = False,
annotated: bool | None = None,
) -> ControlledGate | AnnotatedOperation:
"""Return controlled version of gate.

Expand All @@ -174,8 +174,8 @@ 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.
annotated: indicates whether the controlled gate should be implemented
as an annotated gate. If ``None``, this is handled as ``False``.

Returns:
Controlled version of gate.
Expand Down
13 changes: 8 additions & 5 deletions qiskit/circuit/library/standard_gates/h.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
# that they have been altered from the originals.

"""Hadamard gate."""

from __future__ import annotations

from math import sqrt, pi
from typing import Optional, Union
import numpy
Expand Down Expand Up @@ -79,9 +82,9 @@ def _define(self):
def control(
self,
num_ctrl_qubits: int = 1,
label: Optional[str] = None,
ctrl_state: Optional[Union[int, str]] = None,
annotated: bool = False,
label: str | None = None,
ctrl_state: int | str | None = None,
annotated: bool | None = None,
):
"""Return a (multi-)controlled-H gate.

Expand All @@ -92,8 +95,8 @@ def control(
label: An optional label for the gate [Default: ``None``]
ctrl_state: 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.
annotated: indicates whether the controlled gate should be implemented
as an annotated gate. If ``None``, this is handled as ``False``.

Returns:
ControlledGate: controlled version of this gate.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from typing import Optional, Union, Tuple, List
import numpy as np

from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit
from qiskit.circuit import QuantumCircuit, QuantumRegister, Qubit, ParameterExpression
from qiskit.circuit.library.standard_gates.x import MCXGate
from qiskit.circuit.library.standard_gates.u3 import _generate_gray_code
from qiskit.circuit.parameterexpression import ParameterValueType
Expand Down Expand Up @@ -258,6 +258,9 @@ def mcrx(
use_basis_gates=use_basis_gates,
)
else:
if isinstance(theta, ParameterExpression):
raise QiskitError(f"Cannot synthesize MCRX with unbound parameter: {theta}.")

cgate = _mcsu2_real_diagonal(
RXGate(theta).to_matrix(),
num_controls=len(control_qubits),
Expand All @@ -272,8 +275,8 @@ def mcry(
q_controls: Union[QuantumRegister, List[Qubit]],
q_target: Qubit,
q_ancillae: Optional[Union[QuantumRegister, Tuple[QuantumRegister, int]]] = None,
mode: str = None,
use_basis_gates=False,
mode: Optional[str] = None,
use_basis_gates: bool = False,
):
"""
Apply Multiple-Controlled Y rotation gate
Expand Down Expand Up @@ -333,6 +336,9 @@ def mcry(
use_basis_gates=use_basis_gates,
)
else:
if isinstance(theta, ParameterExpression):
raise QiskitError(f"Cannot synthesize MCRY with unbound parameter: {theta}.")

cgate = _mcsu2_real_diagonal(
RYGate(theta).to_matrix(),
num_controls=len(control_qubits),
Expand Down Expand Up @@ -383,6 +389,9 @@ def mcrz(
else:
self.append(CRZGate(lam), control_qubits + [target_qubit])
else:
if isinstance(lam, ParameterExpression):
raise QiskitError(f"Cannot synthesize MCRZ with unbound parameter: {lam}.")

cgate = _mcsu2_real_diagonal(
RZGate(lam).to_matrix(),
num_controls=len(control_qubits),
Expand Down
18 changes: 9 additions & 9 deletions qiskit/circuit/library/standard_gates/p.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def control(
num_ctrl_qubits: int = 1,
label: str | None = None,
ctrl_state: str | int | None = None,
annotated: bool = False,
annotated: bool | None = None,
):
"""Return a (multi-)controlled-Phase gate.

Expand All @@ -108,8 +108,8 @@ def control(
label: An optional label for the gate [Default: ``None``]
ctrl_state: 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.
annotated: indicates whether the controlled gate should be implemented
as an annotated gate. If ``None``, this is handled as ``False``.

Returns:
ControlledGate: controlled version of this gate.
Expand Down Expand Up @@ -255,7 +255,7 @@ def control(
num_ctrl_qubits: int = 1,
label: str | None = None,
ctrl_state: str | int | None = None,
annotated: bool = False,
annotated: bool | None = None,
):
"""Controlled version of this gate.

Expand All @@ -264,8 +264,8 @@ def control(
label: An optional label for the gate [Default: ``None``]
ctrl_state: 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.
annotated: indicates whether the controlled gate should be implemented
as an annotated gate. If ``None``, this is handled as ``False``.

Returns:
ControlledGate: controlled version of this gate.
Expand Down Expand Up @@ -396,7 +396,7 @@ def control(
num_ctrl_qubits: int = 1,
label: str | None = None,
ctrl_state: str | int | None = None,
annotated: bool = False,
annotated: bool | None = None,
):
"""Controlled version of this gate.

Expand All @@ -405,8 +405,8 @@ def control(
label: An optional label for the gate [Default: ``None``]
ctrl_state: 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.
annotated: indicates whether the controlled gate should be implemented
as an annotated gate. If ``None``, this is handled as ``False``.

Returns:
ControlledGate: controlled version of this gate.
Expand Down
22 changes: 16 additions & 6 deletions qiskit/circuit/library/standard_gates/rx.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

"""Rotation around the X axis."""

from __future__ import annotations

import math
from math import pi
from typing import Optional, Union
Expand All @@ -20,7 +22,7 @@
from qiskit.circuit.controlledgate import ControlledGate
from qiskit.circuit.gate import Gate
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit.parameterexpression import ParameterValueType
from qiskit.circuit.parameterexpression import ParameterValueType, ParameterExpression
from qiskit._accelerate.circuit import StandardGate


Expand Down Expand Up @@ -78,9 +80,9 @@ def _define(self):
def control(
self,
num_ctrl_qubits: int = 1,
label: Optional[str] = None,
ctrl_state: Optional[Union[str, int]] = None,
annotated: bool = False,
label: str | None = None,
ctrl_state: str | int | None = None,
annotated: bool | None = None,
):
"""Return a (multi-)controlled-RX gate.

Expand All @@ -89,16 +91,24 @@ def control(
label: An optional label for the gate [Default: ``None``]
ctrl_state: 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.
annotated: indicates whether the controlled gate should be implemented
as an annotated gate. If ``None``, this is set to ``True`` if
the gate contains free parameters and more than one control qubit, in which
case it cannot yet be synthesized. Otherwise it is set to ``False``.

Returns:
ControlledGate: controlled version of this gate.
"""
# deliberately capture annotated in [None, False] here
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
else:
# If the gate parameters contain free parameters, we cannot eagerly synthesize
# the controlled gate decomposition. In this case, we annotate the gate per default.
if annotated is None:
annotated = any(isinstance(p, ParameterExpression) for p in self.params)

gate = super().control(
num_ctrl_qubits=num_ctrl_qubits,
label=label,
Expand Down
38 changes: 37 additions & 1 deletion qiskit/circuit/library/standard_gates/rxx.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
# that they have been altered from the originals.

"""Two-qubit XX-rotation gate."""

from __future__ import annotations

import math
from typing import Optional
import numpy
from qiskit.circuit.gate import Gate
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit.parameterexpression import ParameterValueType
from qiskit.circuit.parameterexpression import ParameterValueType, ParameterExpression
from qiskit._accelerate.circuit import StandardGate


Expand Down Expand Up @@ -111,6 +114,39 @@ def _define(self):

self.definition = qc

def control(
self,
num_ctrl_qubits: int = 1,
label: str | None = None,
ctrl_state: str | int | None = None,
annotated: bool | None = None,
):
"""Return a (multi-)controlled-RXX gate.

Args:
num_ctrl_qubits: number of control qubits.
label: An optional label for the gate [Default: ``None``]
ctrl_state: control state expressed as integer,
string (e.g.``'110'``), or ``None``. If ``None``, use all 1s.
annotated: indicates whether the controlled gate should be implemented
as an annotated gate. If ``None``, this is set to ``True`` if
the gate contains free parameters, in which case it cannot
yet be synthesized.

Returns:
ControlledGate: controlled version of this gate.
"""
if annotated is None:
annotated = any(isinstance(p, ParameterExpression) for p in self.params)

gate = super().control(
num_ctrl_qubits=num_ctrl_qubits,
label=label,
ctrl_state=ctrl_state,
annotated=annotated,
)
return gate

def inverse(self, annotated: bool = False):
"""Return inverse RXX gate (i.e. with the negative rotation angle).

Expand Down
Loading
Loading