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

Pending-deprecate library circuits with gate representations #13232

Merged
merged 8 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
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
6 changes: 3 additions & 3 deletions qiskit/circuit/library/fourier_checking.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from qiskit.circuit import QuantumCircuit
from qiskit.circuit.exceptions import CircuitError

from .generalized_gates.diagonal import Diagonal
from .generalized_gates.diagonal import DiagonalGate


class FourierChecking(QuantumCircuit):
Expand Down Expand Up @@ -85,11 +85,11 @@ def __init__(self, f: List[int], g: List[int]) -> None:

circuit.h(circuit.qubits)

circuit.compose(Diagonal(f), inplace=True)
circuit.append(DiagonalGate(f), circuit.qubits)

circuit.h(circuit.qubits)

circuit.compose(Diagonal(g), inplace=True)
circuit.append(DiagonalGate(g), circuit.qubits)

circuit.h(circuit.qubits)

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 was just a single angle, use that, otherwise use the correct index
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# if theta was just a single angle, use that, otherwise use the correct index
# if theta is just a single angle, use that, otherwise use the correct index

theta = thetas if isinstance(thetas, ParameterValueType) else thetas[i][j]
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe that it's this line that is currently failing in the unit tests. The error raised is:

TypeError: Subscripted generics cannot be used with class and instance checks
Maybe you can check the shape of the input instead?

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)
Loading
Loading