Skip to content

Commit

Permalink
Add AnnotatedOperation.params and fix some control issues (#12752)
Browse files Browse the repository at this point in the history
* AnnotatedOp.params support and Gate.control fix

* add reno

* lint

* update reno

* review comments

- use attribute error
- more clearly state the new None arg in reno and Gate class

* review from Elena

* Fix ``AttributeError`` test

* lint

* Apply suggestions from code review

Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com>

---------

Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com>
  • Loading branch information
Cryoris and ElePT committed Jul 26, 2024
1 parent edf0b53 commit 1512535
Show file tree
Hide file tree
Showing 28 changed files with 585 additions and 92 deletions.
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

0 comments on commit 1512535

Please sign in to comment.