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 #12752

Merged
merged 10 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
24 changes: 24 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,29 @@ 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 NotImplementedError(
f"Cannot set attribute ``params`` on the base operation {self.base_op}."
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
)

def validate_parameter(self, parameter: ParameterValueType) -> ParameterValueType:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small question, would it be more coherent if we called it validate_param? I am a bit on the fence, I tend to prefer full words but I find it strange that the other methods say params and this one says parameter

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is coming from Instruction.validate_parameter, to have AnnotatedOperation work as drop-in-replacement 🙂

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

raise NotImplementedError(
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
f"Cannot set validate parameters on the base operation {self.base_op}."
)


def _canonicalize_modifiers(modifiers):
"""
Expand Down
11 changes: 5 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,16 @@ 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 handled as ``False``.
Cryoris marked this conversation as resolved.
Show resolved Hide resolved

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
4 changes: 2 additions & 2 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 @@ -175,7 +175,7 @@ def control(
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.
as an annotated gate. If ``None``, this is handled as ``False``.

Returns:
Controlled version of gate.
Expand Down
11 changes: 7 additions & 4 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 @@ -93,7 +96,7 @@ def control(
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.
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}.")

Cryoris marked this conversation as resolved.
Show resolved Hide resolved
cgate = _mcsu2_real_diagonal(
RZGate(lam).to_matrix(),
num_controls=len(control_qubits),
Expand Down
12 changes: 6 additions & 6 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 @@ -109,7 +109,7 @@ def control(
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.
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 @@ -265,7 +265,7 @@ def control(
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.
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 @@ -406,7 +406,7 @@ def control(
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.
as an annotated gate. If ``None``, this is handled as ``False``.

Returns:
ControlledGate: controlled version of this gate.
Expand Down
20 changes: 15 additions & 5 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 @@ -90,15 +92,23 @@ def control(
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
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
as an annotated gate.
as an annotated gate. If ``None``, this is set to ``False``, unless the
gate contains free parameters and has more than one control qubit,
in which case it cannot yet be synthesized.
Cryoris marked this conversation as resolved.
Show resolved Hide resolved

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,
):
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
"""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 can 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.
Cryoris marked this conversation as resolved.
Show resolved Hide resolved

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
20 changes: 15 additions & 5 deletions qiskit/circuit/library/standard_gates/ry.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@

"""Rotation around the Y axis."""

from __future__ import annotations

import math
from math import pi
from typing import Optional, Union
import numpy
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 @@ -77,9 +79,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-RY gate.

Expand All @@ -89,15 +91,23 @@ def control(
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.
as an annotated gate. If ``None``, this is set to ``False``, unless the
gate contains free parameters and has more than one control qubit,
in which case it cannot yet be synthesized.

Returns:
ControlledGate: controlled version of this gate.
"""
# deliberately capture annotated in [None, False] here
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
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
Loading
Loading