# The original version of the following code is sourced from Qiskit circuit library:
# https://github.com/Qiskit/qiskit/blob/stable/2.3/qiskit/circuit/library/basis_change/qft.py#L23-L278
# https://github.com/Qiskit/qiskit/blob/stable/2.3/qiskit/synthesis/qft/qft_decompose_full.py
#
# Original repository link:
# https://github.com/Qiskit/qiskit/tree/f14e0b29a484795034447ea5bfb637fe845c194f
#
# This program is adapted for use as a benchmark in controlled software testing experiments.
# Modifications made to the original code include (for Apache License 2.0):
# - Refactored to expose unified program interfaces.
# - External subroutine dependencies have been made explicit.
#
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Define a Quantum Fourier Transform circuit (QFT)"""
from __future__ import annotations
import warnings
import numpy as np
from qiskit.circuit.quantumcircuit import QuantumCircuit
# ---------- benchmark registration ----------
from ..benchmark_registry import register_benchmark
from pathlib import Path
@register_benchmark(
Path(__file__).stem,
family=Path(__file__).resolve().parent.name,
description="Quantum Fourier Transform circuit (QFT)",
class_name="QFT",
source={
"repo": "https://github.com/Qiskit/qiskit/tree/f14e0b29a484795034447ea5bfb637fe845c194f",
"file": "qiskit/circuit/library/basis_change/qft.py",
"sdk": "Qiskit",
"available_doc": True
},
testability_refactoring=[
"Dependency decoupling",
"Structure reorganization"
]
)
def create_qft(num_qubits):
return QFT(num_qubits=num_qubits)
[docs]
class QFT(QuantumCircuit): # Transform the gate into a QuantumCircuit subclass for easier use
r"""
Quantum Fourier Transform (QFT) Circuit.
"""
def __init__(
self,
num_qubits: int,
do_swaps: bool = True,
approximation_degree: int = 0,
insert_barriers: bool = False,
inverse: bool = False,
name: str | None = None
):
r"""
Args:
num_qubits: Number of input qubits :math:`n`.
do_swaps: Whether to include the final layer of Swap gates. This is `True`
by default.
approximation_degree: The number of controlled rotations to omit from the
circuit. Default: `0`, i.e., no approximations.
insert_barriers: If `True`, barriers are inserted for improved visualization.
inverse: Whether to create the inverse QFT circuit :math:`U_{\text{QFT}}^\dagger`.
Default: `False`.
name: Optional name of the circuit.
"""
self._num_qubits = num_qubits
self._do_swaps = do_swaps
self._approximation_degree = approximation_degree
self._insert_barriers = insert_barriers
self._inverse_flag = inverse
super().__init__(num_qubits, name=name or "qft")
if inverse:
self.name = name or "QFT_dg"
# def __array__(self, dtype=complex, copy=None):
# """Return a numpy array for the QFTGate."""
# if copy is False:
# raise ValueError("unable to avoid copy while creating an array as requested")
# n = self.num_qubits
# nums = np.arange(2**n)
# outer = np.outer(nums, nums)
# return np.exp(2j * np.pi * outer * (0.5**n), dtype=dtype) * (0.5 ** (n / 2))
# ---------- build circuit ----------
self._build()
# Decouple the synthesis function from the gate class for easier testing
def _synth_qft_full(
self,
num_qubits: int,
do_swaps: bool = True,
approximation_degree: int = 0,
insert_barriers: bool = False,
inverse: bool = False,
name: str | None = None,
) -> QuantumCircuit:
"""Construct a circuit for the Quantum Fourier Transform using all-to-all connectivity.
.. note::
With the default value of ``do_swaps = True``, this synthesis algorithm creates a
circuit that faithfully implements the QFT operation. This circuit contains a sequence
of swap gates at the end, corresponding to reversing the order of its output qubits.
In some applications this reversal permutation can be avoided. Setting ``do_swaps = False``
creates a circuit without this reversal permutation, at the expense that this circuit
implements the "QFT-with-reversal" instead of QFT. Alternatively, the
:class:`~.ElidePermutations` transpiler pass is able to remove these swap gates.
Args:
num_qubits: The number of qubits on which the Quantum Fourier Transform acts.
do_swaps: Whether to synthesize the "QFT" or the "QFT-with-reversal" operation.
approximation_degree: The degree of approximation (0 for no approximation).
It is possible to implement the QFT approximately by ignoring
controlled-phase rotations with the angle beneath a threshold. This is discussed
in more detail in https://arxiv.org/abs/quant-ph/9601018 or
https://arxiv.org/abs/quant-ph/0403071.
insert_barriers: If ``True``, barriers are inserted for improved visualization.
inverse: If ``True``, the inverse Quantum Fourier Transform is constructed.
name: The name of the circuit.
Returns:
A circuit implementing the QFT operation.
"""
self._warn_if_precision_loss(num_qubits - approximation_degree - 1)
circuit = QuantumCircuit(num_qubits)
for j in reversed(range(num_qubits)):
circuit.h(j)
num_entanglements = max(0, j - max(0, 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 insert_barriers:
circuit.barrier()
if do_swaps:
for i in range(num_qubits // 2):
circuit.swap(i, num_qubits - i - 1)
if inverse:
circuit = circuit.inverse()
# It is important to set the name afte the circuit's generic "inverse" is called,
# since that will add ``_dg`` to the name.
if name is not None:
circuit.name = name
return circuit
def _warn_if_precision_loss(self, max_num_entanglements):
"""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.
"""
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=4,
)
def _build(self) -> None:
"""Provide a specific decomposition of the QFTGate into a quantum circuit."""
subcircuit = self._synth_qft_full(
num_qubits=self.num_qubits,
do_swaps=self._do_swaps,
inverse=self._inverse_flag,
approximation_degree=self._approximation_degree,
insert_barriers=self._insert_barriers,
name=self.name
)
self.compose(subcircuit, inplace=True) # Compose the subcircuit as a gate into this circuit