Skip to content

Commit

Permalink
Adding annotated argument to power methods (Qiskit#12101)
Browse files Browse the repository at this point in the history
* adding annotated argument to power methods

* release notes

* Apply suggestions from code review

---------

Co-authored-by: Matthew Treinish <[email protected]>
  • Loading branch information
alexanderivrii and mtreinish authored Apr 23, 2024
1 parent 4d95d94 commit cf57a98
Show file tree
Hide file tree
Showing 23 changed files with 143 additions and 64 deletions.
18 changes: 18 additions & 0 deletions qiskit/circuit/annotated_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,24 @@ def inverse(self, annotated: bool = True):
extended_modifiers.append(InverseModifier())
return AnnotatedOperation(self.base_op, extended_modifiers)

def power(self, exponent: float, annotated: bool = False):
"""
Raise this gate to the power of ``exponent``.
Implemented as an annotated operation, see :class:`.AnnotatedOperation`.
Args:
exponent: the power to raise the gate to
annotated: ignored (used for consistency with other power methods)
Returns:
An operation implementing ``gate^exponent``
"""
# pylint: disable=unused-argument
extended_modifiers = self.modifiers.copy()
extended_modifiers.append(PowerModifier(exponent))
return AnnotatedOperation(self.base_op, extended_modifiers)


def _canonicalize_modifiers(modifiers):
"""
Expand Down
27 changes: 20 additions & 7 deletions qiskit/circuit/gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.circuit.exceptions import CircuitError
from .annotated_operation import AnnotatedOperation, ControlModifier
from .annotated_operation import AnnotatedOperation, ControlModifier, PowerModifier
from .instruction import Instruction


Expand Down Expand Up @@ -62,23 +62,36 @@ def to_matrix(self) -> np.ndarray:
return self.__array__(dtype=complex)
raise CircuitError(f"to_matrix not defined for this {type(self)}")

def power(self, exponent: float):
"""Creates a unitary gate as `gate^exponent`.
def power(self, exponent: float, annotated: bool = False):
"""Raise this gate to the power of ``exponent``.
Implemented either as a unitary gate (ref. :class:`~.library.UnitaryGate`)
or as an annotated operation (ref. :class:`.AnnotatedOperation`). In the case of several standard
gates, such as :class:`.RXGate`, when the power of a gate can be expressed in terms of another
standard gate that is returned directly.
Args:
exponent (float): Gate^exponent
exponent (float): the power to raise the gate to
annotated (bool): indicates whether the power gate can be implemented
as an annotated operation. In the case of several standard
gates, such as :class:`.RXGate`, this argument is ignored when
the power of a gate can be expressed in terms of another
standard gate.
Returns:
.library.UnitaryGate: To which `to_matrix` is self.to_matrix^exponent.
An operation implementing ``gate^exponent``
Raises:
CircuitError: If Gate is not unitary
CircuitError: If gate is not unitary
"""
# pylint: disable=cyclic-import
from qiskit.quantum_info.operators import Operator
from qiskit.circuit.library.generalized_gates.unitary import UnitaryGate

return UnitaryGate(Operator(self).power(exponent), label=f"{self.name}^{exponent}")
if not annotated:
return UnitaryGate(Operator(self).power(exponent), label=f"{self.name}^{exponent}")
else:
return AnnotatedOperation(self, PowerModifier(exponent))

def __pow__(self, exponent: float) -> "Gate":
return self.power(exponent)
Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/i.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ def inverse(self, annotated: bool = False):
."""
return IGate() # self-inverse

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
return IGate()

def __eq__(self, other):
Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/iswap.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,7 @@ def _define(self):

self.definition = qc

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
return XXPlusYYGate(-np.pi * exponent)

def __eq__(self, other):
Expand Down
6 changes: 2 additions & 4 deletions qiskit/circuit/library/standard_gates/p.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,7 @@ def __array__(self, dtype=None):
lam = float(self.params[0])
return numpy.array([[1, 0], [0, exp(1j * lam)]], dtype=dtype)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
(theta,) = self.params
return PhaseGate(exponent * theta)

Expand Down Expand Up @@ -289,8 +288,7 @@ def __array__(self, dtype=None):
)
return numpy.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, eith, 0], [0, 0, 0, 1]], dtype=dtype)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
(theta,) = self.params
return CPhaseGate(exponent * theta)

Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/r.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ def __array__(self, dtype=None):
exp_p = exp(1j * phi)
return numpy.array([[cos, -1j * exp_m * sin], [-1j * exp_p * sin, cos]], dtype=dtype)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
theta, phi = self.params
return RGate(exponent * theta, phi)

Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/rx.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,7 @@ def __array__(self, dtype=None):
sin = math.sin(self.params[0] / 2)
return numpy.array([[cos, -1j * sin], [-1j * sin, cos]], dtype=dtype)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
(theta,) = self.params
return RXGate(exponent * theta)

Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/rxx.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,7 @@ def __array__(self, dtype=None):
dtype=dtype,
)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
(theta,) = self.params
return RXXGate(exponent * theta)

Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/ry.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,7 @@ def __array__(self, dtype=None):
sin = math.sin(self.params[0] / 2)
return numpy.array([[cos, -sin], [sin, cos]], dtype=dtype)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
(theta,) = self.params
return RYGate(exponent * theta)

Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/ryy.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,7 @@ def __array__(self, dtype=None):
dtype=dtype,
)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
(theta,) = self.params
return RYYGate(exponent * theta)

Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/rz.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,7 @@ def __array__(self, dtype=None):
ilam2 = 0.5j * float(self.params[0])
return np.array([[exp(-ilam2), 0], [0, exp(ilam2)]], dtype=dtype)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
(theta,) = self.params
return RZGate(exponent * theta)

Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/rzx.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,7 @@ def __array__(self, dtype=None):
dtype=dtype,
)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
(theta,) = self.params
return RZXGate(exponent * theta)

Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/rzz.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,7 @@ def __array__(self, dtype=None):
dtype=dtype,
)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
(theta,) = self.params
return RZZGate(exponent * theta)

Expand Down
12 changes: 4 additions & 8 deletions qiskit/circuit/library/standard_gates/s.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@ def inverse(self, annotated: bool = False):
"""
return SdgGate()

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
from .p import PhaseGate

return PhaseGate(0.5 * numpy.pi * exponent)
Expand Down Expand Up @@ -172,8 +171,7 @@ def inverse(self, annotated: bool = False):
"""
return SGate()

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
from .p import PhaseGate

return PhaseGate(-0.5 * numpy.pi * exponent)
Expand Down Expand Up @@ -259,8 +257,7 @@ def inverse(self, annotated: bool = False):
"""
return CSdgGate(ctrl_state=self.ctrl_state)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
from .p import CPhaseGate

return CPhaseGate(0.5 * numpy.pi * exponent)
Expand Down Expand Up @@ -345,8 +342,7 @@ def inverse(self, annotated: bool = False):
"""
return CSGate(ctrl_state=self.ctrl_state)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
from .p import CPhaseGate

return CPhaseGate(-0.5 * numpy.pi * exponent)
Expand Down
6 changes: 2 additions & 4 deletions qiskit/circuit/library/standard_gates/t.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ def inverse(self, annotated: bool = False):
"""
return TdgGate()

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
return PhaseGate(0.25 * numpy.pi * exponent)

def __eq__(self, other):
Expand Down Expand Up @@ -168,8 +167,7 @@ def inverse(self, annotated: bool = False):
"""
return TGate()

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
return PhaseGate(-0.25 * numpy.pi * exponent)

def __eq__(self, other):
Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/xx_minus_yy.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,7 @@ def __array__(self, dtype=complex):
dtype=dtype,
)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
theta, beta = self.params
return XXMinusYYGate(exponent * theta, beta)

Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/xx_plus_yy.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,7 @@ def __array__(self, dtype=complex):
dtype=dtype,
)

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
theta, beta = self.params
return XXPlusYYGate(exponent * theta, beta)

Expand Down
3 changes: 1 addition & 2 deletions qiskit/circuit/library/standard_gates/z.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,7 @@ def inverse(self, annotated: bool = False):
"""
return ZGate() # self-inverse

def power(self, exponent: float):
"""Raise gate to a power."""
def power(self, exponent: float, annotated: bool = False):
return PhaseGate(numpy.pi * exponent)

def __eq__(self, other):
Expand Down
36 changes: 24 additions & 12 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -777,26 +777,38 @@ def repeat(self, reps: int) -> "QuantumCircuit":

return repeated_circ

def power(self, power: float, matrix_power: bool = False) -> "QuantumCircuit":
def power(
self, power: float, matrix_power: bool = False, annotated: bool = False
) -> "QuantumCircuit":
"""Raise this circuit to the power of ``power``.
If ``power`` is a positive integer and ``matrix_power`` is ``False``, this implementation
defaults to calling ``repeat``. Otherwise, if the circuit is unitary, the matrix is
computed to calculate the matrix power.
If ``power`` is a positive integer and both ``matrix_power`` and ``annotated``
are ``False``, this implementation defaults to calling ``repeat``. Otherwise,
the circuit is converted into a gate, and a new circuit, containing this gate
raised to the given power, is returned. The gate raised to the given power is
implemented either as a unitary gate if ``annotated`` is ``False`` or as an
annotated operation if ``annotated`` is ``True``.
Args:
power (float): The power to raise this circuit to.
matrix_power (bool): If True, the circuit is converted to a matrix and then the
matrix power is computed. If False, and ``power`` is a positive integer,
the implementation defaults to ``repeat``.
matrix_power (bool): indicates whether the inner power gate can be implemented
as a unitary gate.
annotated (bool): indicates whether the inner power gate can be implemented
as an annotated operation.
Raises:
CircuitError: If the circuit needs to be converted to a gate but it is not unitary.
CircuitError: If the circuit needs to be converted to a unitary gate, but is
not unitary.
Returns:
QuantumCircuit: A circuit implementing this circuit raised to the power of ``power``.
"""
if power >= 0 and isinstance(power, (int, np.integer)) and not matrix_power:
if (
power >= 0
and isinstance(power, (int, np.integer))
and not matrix_power
and not annotated
):
return self.repeat(power)

# attempt conversion to gate
Expand All @@ -812,12 +824,12 @@ def power(self, power: float, matrix_power: bool = False) -> "QuantumCircuit":
except QiskitError as ex:
raise CircuitError(
"The circuit contains non-unitary operations and cannot be "
"controlled. Note that no qiskit.circuit.Instruction objects may "
"be in the circuit for this operation."
"raised to a power. Note that no qiskit.circuit.Instruction "
"objects may be in the circuit for this operation."
) from ex

power_circuit = QuantumCircuit(self.qubits, self.clbits, *self.qregs, *self.cregs)
power_circuit.append(gate.power(power), list(range(gate.num_qubits)))
power_circuit.append(gate.power(power, annotated=annotated), list(range(gate.num_qubits)))
return power_circuit

def control(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
features:
- |
The methods :meth:`~qiskit.circuit.QuantumCircuit.power`,
:meth:`~qiskit.circuit.Gate.power`, as well as the similar methods
of subclasses of :class:`~qiskit.circuit.Gate`
(such as of :class:`~qiskit.circuit.library.SGate`) all have an additional
argument ``annotated``.
The default value of ``False`` corresponds to the existing behavior.
Furthermore, for standard gates with an explicitly defined ``power`` method,
the argument ``annotated`` has no effect, for example both
``SGate().power(1.5, annotated=False)`` and ``SGate().power(1.5, annotated=True)``
return a ``PhaseGate``.
The difference manifests for gates without an explicitly defined
power method. The value of ``False`` returns a
:class:`~.library.UnitaryGate`, just as before, while the value of ``True``
returns an :class:`~.AnnotatedOperation` that represents the instruction
modified with the "power modifier".
2 changes: 1 addition & 1 deletion test/python/circuit/test_annotated_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from test import QiskitTestCase # pylint: disable=wrong-import-order


class TestAnnotatedOperationlass(QiskitTestCase):
class TestAnnotatedOperationClass(QiskitTestCase):
"""Testing qiskit.circuit.AnnotatedOperation"""

def test_create_gate_with_modifier(self):
Expand Down
Loading

0 comments on commit cf57a98

Please sign in to comment.