Skip to content

Commit

Permalink
Pending-deprecate library circuits with gate representations (#13232)
Browse files Browse the repository at this point in the history
* Pending deprecations for circuit library

* Don't change annotated ops flow

* apply the uno-reverse card on GMS

* lint + update to using DiagonalGate

* keep using Diagonal

which is pending deprecation, but that's fine as GroverOperator will also pend deprecation in the same timeframe for 1.3

* fix tests
  • Loading branch information
Cryoris authored Nov 5, 2024
1 parent c883c26 commit 3fe73d9
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 127 deletions.
6 changes: 3 additions & 3 deletions qiskit/circuit/library/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,12 @@
.. code-block::
from qiskit.circuit.library import Diagonal
from qiskit.circuit.library import DiagonalGate
diagonal = Diagonal([1, 1])
diagonal = DiagonalGate([1, 1j])
print(diagonal.num_qubits)
diagonal = Diagonal([1, 1, 1, 1])
diagonal = DiagonalGate([1, 1, 1, -1])
print(diagonal.num_qubits)
.. code-block:: text
Expand Down
58 changes: 20 additions & 38 deletions qiskit/circuit/library/basis_change/qft.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
"""Define a Quantum Fourier Transform circuit (QFT) and a native gate (QFTGate)."""

from __future__ import annotations
import warnings
import numpy as np

from qiskit.circuit.quantumcircuit import QuantumCircuit, QuantumRegister, CircuitInstruction, Gate
from qiskit.circuit.quantumcircuit import QuantumRegister, CircuitInstruction, Gate
from qiskit.utils.deprecation import deprecate_func
from ..blueprintcircuit import BlueprintCircuit


Expand Down Expand Up @@ -72,6 +72,14 @@ class QFT(BlueprintCircuit):
"""

@deprecate_func(
since="1.3",
additional_msg=(
"Use qiskit.circuit.library.QFTGate or qiskit.synthesis.qft.synth_qft_full instead, "
"for access to all previous arguments.",
),
pending=True,
)
def __init__(
self,
num_qubits: int | None = None,
Expand Down Expand Up @@ -232,30 +240,13 @@ def inverse(self, annotated: bool = False) -> "QFT":
inverted._inverse = not self._inverse
return inverted

def _warn_if_precision_loss(self):
"""Issue a warning if constructing the circuit will lose precision.
If we need an angle smaller than ``pi * 2**-1022``, we start to lose precision by going into
the subnormal numbers. We won't lose _all_ precision until an exponent of about 1075, but
beyond 1022 we're using fractional bits to represent leading zeros."""
max_num_entanglements = self.num_qubits - self.approximation_degree - 1
if max_num_entanglements > -np.finfo(float).minexp: # > 1022 for doubles.
warnings.warn(
"precision loss in QFT."
f" The rotation needed to represent {max_num_entanglements} entanglements"
" is smaller than the smallest normal floating-point number.",
category=RuntimeWarning,
stacklevel=3,
)

def _check_configuration(self, raise_on_failure: bool = True) -> bool:
"""Check if the current configuration is valid."""
valid = True
if self.num_qubits is None:
valid = False
if raise_on_failure:
raise AttributeError("The number of qubits has not been set.")
self._warn_if_precision_loss()
return valid

def _build(self) -> None:
Expand All @@ -270,25 +261,16 @@ def _build(self) -> None:
if num_qubits == 0:
return

circuit = QuantumCircuit(*self.qregs, name=self.name)
for j in reversed(range(num_qubits)):
circuit.h(j)
num_entanglements = max(0, j - max(0, self.approximation_degree - (num_qubits - j - 1)))
for k in reversed(range(j - num_entanglements, j)):
# Use negative exponents so that the angle safely underflows to zero, rather than
# using a temporary variable that overflows to infinity in the worst case.
lam = np.pi * (2.0 ** (k - j))
circuit.cp(lam, j, k)

if self.insert_barriers:
circuit.barrier()

if self._do_swaps:
for i in range(num_qubits // 2):
circuit.swap(i, num_qubits - i - 1)

if self._inverse:
circuit = circuit.inverse()
from qiskit.synthesis.qft import synth_qft_full

circuit = synth_qft_full(
num_qubits,
do_swaps=self._do_swaps,
insert_barriers=self._insert_barriers,
approximation_degree=self._approximation_degree,
inverse=self._inverse,
name=self.name,
)

wrapped = circuit.to_instruction() if self.insert_barriers else circuit.to_gate()
self.compose(wrapped, qubits=self.qubits, inplace=True)
Expand Down
96 changes: 45 additions & 51 deletions qiskit/circuit/library/generalized_gates/diagonal.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,37 @@
from qiskit.circuit.gate import Gate
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.exceptions import CircuitError
from qiskit.circuit.annotated_operation import AnnotatedOperation, InverseModifier
from qiskit.utils.deprecation import deprecate_func

from .ucrz import UCRZGate

_EPS = 1e-10


class Diagonal(QuantumCircuit):
r"""Diagonal circuit.
"""Circuit implementing a diagonal transformation."""

Circuit symbol:
@deprecate_func(since="1.3", additional_msg="Use DiagonalGate instead.", pending=True)
def __init__(self, diag: Sequence[complex]) -> None:
r"""
Args:
diag: List of the :math:`2^k` diagonal entries (for a diagonal gate on :math:`k` qubits).
Raises:
CircuitError: if the list of the diagonal entries or the qubit list is in bad format;
if the number of diagonal entries is not :math:`2^k`, where :math:`k` denotes the
number of qubits.
"""
DiagonalGate._check_input(diag)
num_qubits = int(math.log2(len(diag)))

super().__init__(num_qubits, name="Diagonal")
self.append(DiagonalGate(diag), self.qubits)

.. code-block:: text

┌───────────┐
q_0: ┤0 ├
│ │
q_1: ┤1 Diagonal ├
│ │
q_2: ┤2 ├
└───────────┘
class DiagonalGate(Gate):
r"""A generic diagonal quantum gate.
Matrix form:
Expand Down Expand Up @@ -80,67 +91,36 @@ class Diagonal(QuantumCircuit):
def __init__(self, diag: Sequence[complex]) -> None:
r"""
Args:
diag: List of the :math:`2^k` diagonal entries (for a diagonal gate on :math:`k` qubits).
Raises:
CircuitError: if the list of the diagonal entries or the qubit list is in bad format;
if the number of diagonal entries is not :math:`2^k`, where :math:`k` denotes the
number of qubits.
diag: list of the :math:`2^k` diagonal entries (for a diagonal gate on :math:`k` qubits).
"""
self._check_input(diag)
num_qubits = int(math.log2(len(diag)))

circuit = QuantumCircuit(num_qubits, name="Diagonal")
super().__init__("diagonal", num_qubits, diag)

def _define(self):
# Since the diagonal is a unitary, all its entries have absolute value
# one and the diagonal is fully specified by the phases of its entries.
diag_phases = [cmath.phase(z) for z in diag]
n = len(diag)
diag_phases = [cmath.phase(z) for z in self.params]
n = len(diag_phases)
circuit = QuantumCircuit(self.num_qubits)

while n >= 2:
angles_rz = []
for i in range(0, n, 2):
diag_phases[i // 2], rz_angle = _extract_rz(diag_phases[i], diag_phases[i + 1])
angles_rz.append(rz_angle)
num_act_qubits = int(math.log2(n))
ctrl_qubits = list(range(num_qubits - num_act_qubits + 1, num_qubits))
target_qubit = num_qubits - num_act_qubits
ctrl_qubits = list(range(self.num_qubits - num_act_qubits + 1, self.num_qubits))
target_qubit = self.num_qubits - num_act_qubits

ucrz = UCRZGate(angles_rz)
circuit.append(ucrz, [target_qubit] + ctrl_qubits)

n //= 2
circuit.global_phase += diag_phases[0]

super().__init__(num_qubits, name="Diagonal")
self.append(circuit.to_gate(), self.qubits)

@staticmethod
def _check_input(diag):
"""Check if ``diag`` is in valid format."""
if not isinstance(diag, (list, np.ndarray)):
raise CircuitError("Diagonal entries must be in a list or numpy array.")
num_qubits = math.log2(len(diag))
if num_qubits < 1 or not num_qubits.is_integer():
raise CircuitError("The number of diagonal entries is not a positive power of 2.")
if not np.allclose(np.abs(diag), 1, atol=_EPS):
raise CircuitError("A diagonal element does not have absolute value one.")


class DiagonalGate(Gate):
"""Gate implementing a diagonal transformation."""

def __init__(self, diag: Sequence[complex]) -> None:
r"""
Args:
diag: list of the :math:`2^k` diagonal entries (for a diagonal gate on :math:`k` qubits).
"""
Diagonal._check_input(diag)
num_qubits = int(math.log2(len(diag)))

super().__init__("diagonal", num_qubits, diag)

def _define(self):
self.definition = Diagonal(self.params).decompose()
self.definition = circuit

def validate_parameter(self, parameter):
"""Diagonal Gate parameter should accept complex
Expand All @@ -152,8 +132,22 @@ def validate_parameter(self, parameter):

def inverse(self, annotated: bool = False):
"""Return the inverse of the diagonal gate."""
if annotated:
return AnnotatedOperation(self.copy(), InverseModifier)

return DiagonalGate([np.conj(entry) for entry in self.params])

@staticmethod
def _check_input(diag):
"""Check if ``diag`` is in valid format."""
if not isinstance(diag, (list, np.ndarray)):
raise CircuitError("Diagonal entries must be in a list or numpy array.")
num_qubits = math.log2(len(diag))
if num_qubits < 1 or not num_qubits.is_integer():
raise CircuitError("The number of diagonal entries is not a positive power of 2.")
if not np.allclose(np.abs(diag), 1, atol=_EPS):
raise CircuitError("A diagonal element does not have absolute value one.")


def _extract_rz(phi1, phi2):
"""
Expand Down
79 changes: 66 additions & 13 deletions qiskit/circuit/library/generalized_gates/gms.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
Global Mølmer–Sørensen gate.
"""

from typing import Union, List
from __future__ import annotations
from collections.abc import Sequence

import numpy as np
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit.parameterexpression import ParameterValueType
from qiskit.circuit.library.standard_gates import RXXGate
from qiskit.circuit.gate import Gate
from qiskit.utils.deprecation import deprecate_func


class GMS(QuantumCircuit):
Expand Down Expand Up @@ -74,7 +77,8 @@ class GMS(QuantumCircuit):
`arXiv:1707.06356 <https://arxiv.org/abs/1707.06356>`_
"""

def __init__(self, num_qubits: int, theta: Union[List[List[float]], np.ndarray]) -> None:
@deprecate_func(since="1.3", additional_msg="Use the MSGate instead.", pending=True)
def __init__(self, num_qubits: int, theta: list[list[float]] | np.ndarray) -> None:
"""Create a new Global Mølmer–Sørensen (GMS) gate.
Args:
Expand All @@ -94,28 +98,77 @@ def __init__(self, num_qubits: int, theta: Union[List[List[float]], np.ndarray])


class MSGate(Gate):
"""MSGate has been deprecated.
Please use ``GMS`` in ``qiskit.circuit.generalized_gates`` instead.
r"""The Mølmer–Sørensen gate.
Global Mølmer–Sørensen gate.
The Mølmer–Sørensen gate is native to ion-trap systems. The global MS
can be applied to multiple ions to entangle multiple qubits simultaneously [1].
The Mølmer–Sørensen gate is native to ion-trap systems. The global MS can be
applied to multiple ions to entangle multiple qubits simultaneously.
In the two-qubit case, this is equivalent to an XX interaction,
and is thus reduced to the :class:`.RXXGate`. The global MS gate is a sum of XX
interactions on all pairs [2].
In the two-qubit case, this is equivalent to an XX(theta) interaction,
and is thus reduced to the RXXGate.
.. math::
MS(\chi_{12}, \chi_{13}, ..., \chi_{n-1 n}) =
exp(-i \sum_{i=1}^{n} \sum_{j=i+1}^{n} X{\otimes}X \frac{\chi_{ij}}{2})
Example::
import numpy as np
from qiskit.circuit.library import MSGate
from qiskit.quantum_info import Operator
gate = MSGate(num_qubits=3, theta=[[0, np.pi/4, np.pi/8],
[0, 0, np.pi/2],
[0, 0, 0]])
print(Operator(gate))
**References:**
[1] Sørensen, A. and Mølmer, K., Multi-particle entanglement of hot trapped ions.
Physical Review Letters. 82 (9): 1835–1838.
`arXiv:9810040 <https://arxiv.org/abs/quant-ph/9810040>`_
[2] Maslov, D. and Nam, Y., Use of global interactions in efficient quantum circuit
constructions. New Journal of Physics, 20(3), p.033018.
`arXiv:1707.06356 <https://arxiv.org/abs/1707.06356>`_
"""

def __init__(self, num_qubits, theta, label=None):
"""Create new MS gate."""
def __init__(
self,
num_qubits: int,
theta: ParameterValueType | Sequence[Sequence[ParameterValueType]],
label: str | None = None,
):
"""
Args:
num_qubits: The number of qubits the MS gate acts on.
theta: The XX rotation angles. If a single value, the same angle is used on all
interactions. Alternatively an upper-triangular, square matrix with width
``num_qubits`` can be provided with interaction angles for each qubit pair.
label: A gate label.
"""
super().__init__("ms", num_qubits, [theta], label=label)

def _define(self):
theta = self.params[0]
q = QuantumRegister(self.num_qubits, "q")
thetas = self.params[0]
q = QuantumRegister(self.num_qubits, name="q")
qc = QuantumCircuit(q, name=self.name)
for i in range(self.num_qubits):
for j in range(i + 1, self.num_qubits):
# if theta is just a single angle, use that, otherwise use the correct index
theta = thetas if not isinstance(thetas, Sequence) else thetas[i][j]
qc._append(RXXGate(theta), [q[i], q[j]], [])

self.definition = qc

def validate_parameter(self, parameter):
if isinstance(parameter, Sequence):
# pylint: disable=super-with-arguments
return [
[super(MSGate, self).validate_parameter(theta) for theta in row]
for row in parameter
]

return super().validate_parameter(parameter)
4 changes: 2 additions & 2 deletions qiskit/circuit/library/generalized_gates/isometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from qiskit.quantum_info.operators.predicates import is_isometry
from qiskit._accelerate import isometry as isometry_rs

from .diagonal import Diagonal
from .diagonal import DiagonalGate
from .uc import UCGate
from .mcg_up_to_diagonal import MCGupDiag

Expand Down Expand Up @@ -167,7 +167,7 @@ def _gates_to_uncompute(self):
if len(diag) > 1 and not isometry_rs.diag_is_identity_up_to_global_phase(
diag, self._epsilon
):
diagonal = Diagonal(np.conj(diag))
diagonal = DiagonalGate(np.conj(diag))
circuit.append(diagonal, q_input)
return circuit

Expand Down
Loading

0 comments on commit 3fe73d9

Please sign in to comment.