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

Adding QFT gate to natively reason about Quantum Fourier Transforms #11463

Merged
merged 24 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
71efe1c
initial commit
alexanderivrii Dec 28, 2023
3705ba4
release notes
alexanderivrii Dec 28, 2023
68c2ccf
fixing synthesis plugin options
alexanderivrii Dec 28, 2023
c835e20
Merge branch 'main' into qft_gate
alexanderivrii Feb 4, 2024
425a6d9
finalize merge conflicts
alexanderivrii Feb 4, 2024
ef45120
fixing default option values for qft plugins'
alexanderivrii Feb 6, 2024
09086e6
additional tests for qft plugins
alexanderivrii Feb 6, 2024
f1d4215
Merge branch 'main' into qft_gate
alexanderivrii Feb 6, 2024
5d8be05
Merge branch 'main' into qft_gate
alexanderivrii May 2, 2024
eac4ed6
renaming QftGate to QFTGate
alexanderivrii May 2, 2024
cac843b
Merge branch 'main' into qft_gate
alexanderivrii Jul 4, 2024
5acfc3f
Also renaming Qft to QFT in synthesis method names
alexanderivrii Jul 4, 2024
0b605bc
Merge branch 'qft_gate' of github.com:alexanderivrii/qiskit-terra int…
alexanderivrii Jul 4, 2024
6106c4a
appplying Jake's suggestion from code review
alexanderivrii Jul 4, 2024
a76fbe7
inlining _basic_definition into _define
alexanderivrii Jul 5, 2024
0d06898
docstring improvements
alexanderivrii Jul 5, 2024
71860bf
more docstring improvements
alexanderivrii Jul 5, 2024
d496f08
renaming do_swaps to reverse_qubits in the new code
alexanderivrii Jul 5, 2024
2822d5c
typos
alexanderivrii Jul 5, 2024
6feb4fc
adding synth_qft_full to __init__
alexanderivrii Jul 7, 2024
a0ea5ba
round of suggestions from code review
alexanderivrii Jul 9, 2024
b230c58
another round of code review suggestions
alexanderivrii Jul 9, 2024
8806c27
fixes
alexanderivrii Jul 9, 2024
4ae4c9e
also adding QFTGate plugins to the docs
alexanderivrii Jul 9, 2024
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
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ sk = "qiskit.transpiler.passes.synthesis.solovay_kitaev_synthesis:SolovayKitaevS
"permutation.kms" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:KMSSynthesisPermutation"
"permutation.basic" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:BasicSynthesisPermutation"
"permutation.acg" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:ACGSynthesisPermutation"
"qft.full" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:QftSynthesisFull"
"qft.line" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:QftSynthesisLine"
"qft.default" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:QftSynthesisFull"
"permutation.token_swapper" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:TokenSwapperSynthesisPermutation"

[project.entry-points."qiskit.transpiler.init"]
Expand Down
2 changes: 2 additions & 0 deletions qiskit/circuit/library/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@
UCRXGate
UCRYGate
UCRZGate
QftGate
alexanderivrii marked this conversation as resolved.
Show resolved Hide resolved

Boolean Logic Circuits
======================
Expand Down Expand Up @@ -524,6 +525,7 @@
UCRXGate,
UCRYGate,
UCRZGate,
QftGate,
)
from .pauli_evolution import PauliEvolutionGate
from .hamiltonian_gate import HamiltonianGate
Expand Down
1 change: 1 addition & 0 deletions qiskit/circuit/library/generalized_gates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@
from .ucrz import UCRZGate
from .unitary import UnitaryGate
from .mcg_up_to_diagonal import MCGupDiag
from .qft import QftGate
72 changes: 72 additions & 0 deletions qiskit/circuit/library/generalized_gates/qft.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2021.
#
# 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.

"""QftGate: gate class for natively reasoning about Quantum Fourier Transforms."""

from __future__ import annotations
import numpy as np
from qiskit.circuit.quantumcircuit import Gate


class QftGate(Gate):
r"""Quantum Fourier Transform Circuit.

The Quantum Fourier Transform (QFT) on :math:`n` qubits is the operation

.. math::

|j\rangle \mapsto \frac{1}{2^{n/2}} \sum_{k=0}^{2^n - 1} e^{2\pi ijk / 2^n} |k\rangle

"""

def __init__(
self,
num_qubits: int,
alexanderivrii marked this conversation as resolved.
Show resolved Hide resolved
):
"""Construct a new QFT gate.

Cryoris marked this conversation as resolved.
Show resolved Hide resolved
Args:
num_qubits: The number of qubits on which the QFT acts.
"""
super().__init__(name="qft", num_qubits=num_qubits, params=[])

def __array__(self, dtype=complex):
alexanderivrii marked this conversation as resolved.
Show resolved Hide resolved
"""Return a numpy array for the QftGate."""
num_qubits = self.num_qubits
mat = np.empty((2**num_qubits, 2**num_qubits), dtype=dtype)
for i in range(2**num_qubits):
i_index = int(bin(i)[2:].zfill(num_qubits), 2)
for j in range(i, 2**num_qubits):
entry = np.exp(2 * np.pi * 1j * i * j / 2**num_qubits) / 2 ** (num_qubits / 2)
j_index = int(bin(j)[2:].zfill(num_qubits), 2)
mat[i_index, j_index] = entry
if i != j:
mat[j_index, i_index] = entry
return mat
Copy link
Member

Choose a reason for hiding this comment

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

Something like

def qft(n):
    pows = np.arange(2**n)
    return np.exp(2j * np.pi / n * np.outer(pows, pows)) * (0.5 ** (n/2))

is probably a fair bit faster than this. I think there might be some nicer np.power.outer tricks that might be faster, but it probably doesn't matter too much at the scale of matrices that we can generate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is cool! Done in 6106c4a. On top of this, I have utterly no idea why I was converting an integer to its binary representation and back.


def _basic_decomposition(self):
"""Provide a specific decomposition of the QFT gate into a quantum circuit.

Returns:
QuantumCircuit: A circuit implementing the evolution.
"""
from qiskit.synthesis.qft import synth_qft_full

decomposition = synth_qft_full(num_qubits=self.num_qubits)
return decomposition

def _define(self):
"""Populate self.definition with a specific decomposition of the gate.
This is used for constructing Operators from QftGates, creating qasm
representations and more.
"""
self.definition = self._basic_decomposition()
Copy link
Member

Choose a reason for hiding this comment

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

Very minor nitpicking, but is there a need to have a separate _basic_decomposition function? Can we inline it into _define (which largely implies that it's a basic definition)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, done in a76fbe7.

2 changes: 2 additions & 0 deletions qiskit/qpy/binary_io/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ def _read_instruction(
"DiagonalGate",
}:
gate = gate_class(params)
elif gate_name == "QftGate":
gate = gate_class(len(qargs), *params)
else:
if gate_name == "Barrier":
params = [len(qargs)]
Expand Down
1 change: 1 addition & 0 deletions qiskit/synthesis/qft/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
"""Module containing stabilizer QFT circuit synthesis."""

from .qft_decompose_lnn import synth_qft_line
from .qft_decompose_full import synth_qft_full
alexanderivrii marked this conversation as resolved.
Show resolved Hide resolved
61 changes: 61 additions & 0 deletions qiskit/synthesis/qft/qft_decompose_full.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2024.
#
# 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.
"""
Circuit synthesis for a QFT circuit.
"""

from typing import Optional
import numpy as np
from qiskit.circuit.quantumcircuit import QuantumCircuit


def synth_qft_full(
num_qubits: int,
do_swaps: bool = True,
approximation_degree: int = 0,
insert_barriers: bool = False,
inverse: bool = False,
name: Optional[str] = None,
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
) -> QuantumCircuit:
"""Construct a QFT circuit using all-to-all connectivity.

Args:
num_qubits: The number of qubits on which the QFT acts.
do_swaps: Whether to include the final swaps in the QFT.
alexanderivrii marked this conversation as resolved.
Show resolved Hide resolved
approximation_degree: The degree of approximation (0 for no approximation).
insert_barriers: If True, barriers are inserted as visualization improvement.
inverse: If True, the inverse Fourier transform is constructed.
name: The name of the circuit.
"""

circuit = QuantumCircuit(num_qubits, name=name)

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

return circuit
90 changes: 89 additions & 1 deletion qiskit/transpiler/passes/synthesis/high_level_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
ControlModifier,
PowerModifier,
)
from qiskit.circuit.library import QftGate
from qiskit.synthesis.clifford import (
synth_clifford_full,
synth_clifford_layers,
Expand All @@ -49,6 +50,10 @@
synth_permutation_acg,
synth_permutation_depth_lnn_kms,
)
from qiskit.synthesis.qft import (
synth_qft_full,
synth_qft_line,
)

from .plugin import HighLevelSynthesisPluginManager, HighLevelSynthesisPlugin

Expand Down Expand Up @@ -661,6 +666,90 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **
return decomposition


class QftSynthesisFull(HighLevelSynthesisPlugin):
alexanderivrii marked this conversation as resolved.
Show resolved Hide resolved
"""Synthesis plugin for QFT gates using all-to-all connectivity.

This plugin name is :``qft.full`` which can be used as the key on
an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`.

The plugin supports the following additional options:

* do_swaps (bool): Whether to include Swap gates at the end of the synthesized
alexanderivrii marked this conversation as resolved.
Show resolved Hide resolved
circuit. Some implementation of the ``QftGate`` include a layer of Swap gates
at the end of the synthesized circuit, which cna in principle be dropped if
the ``QftGate`` itself is the last gate in the circuit.
* approximation_degree (int): The degree of approximation (0 for no approximation).
It is impossible ti implement the QFT approximately by ignoring
controlled-phase rotations with the angle is 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.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe add them as references?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, I am slightly inclined to keep it a bit less formal: IMHO, a good reference section would first cite a paper defining a QFT operation, then cite papers that introduce different synthesis methods for QFT, and only then papers that discuss approximation.

Copy link
Contributor

Choose a reason for hiding this comment

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

Personally, I would also prefer the references written out, because of (1) consistency with other code and (2) being able to read authors & title w/o clicking the link (but maybe that's just me being lazy 😛 )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Due to popular demand, the references are now written out.

Copy link
Member

Choose a reason for hiding this comment

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

thanks, I think that you should still remove line 912

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, forgot to delete this line, done now.

* insert_barriers (bool): If True, barriers are inserted as visualization improvement.
* inverse (bool): If True, the inverse Fourier transform is constructed.
* name (str): The name of the circuit.

"""

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
"""Run synthesis for the given QftGate."""
if not isinstance(high_level_object, QftGate):
raise TranspilerError(
"The synthesis plugin 'qft.full` only applies to objects of type QftGate."
)

do_swaps = options.get("do_swaps", True)
approximation_degree = options.get("approximation_degree", 0)
insert_barriers = options.get("insert_barriers", False)
inverse = options.get("inverse", False)
name = options.get("name", None)

decomposition = synth_qft_full(
num_qubits=high_level_object.num_qubits,
do_swaps=do_swaps,
approximation_degree=approximation_degree,
insert_barriers=insert_barriers,
inverse=inverse,
name=name,
)
return decomposition


class QftSynthesisLine(HighLevelSynthesisPlugin):
"""Synthesis plugin for QFT gates using linear connectivity.

This plugin name is :``qft.line`` which can be used as the key on
an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`.

The plugin supports the following additional options:

* do_swaps (bool): whether to include Swap gates at the end of the synthesized
alexanderivrii marked this conversation as resolved.
Show resolved Hide resolved
circuit. Some implementation of the ``QftGate`` include a layer of Swap gates
at the end of the synthesized circuit, which cna in principle be dropped if
the ``QftGate`` itself is the last gate in the circuit.
* approximation_degree (int): the degree of approximation (0 for no approximation).
It is impossible ti implement the QFT approximately by ignoring
controlled-phase rotations with the angle is 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.
ShellyGarion marked this conversation as resolved.
Show resolved Hide resolved
"""

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
"""Run synthesis for the given QftGate."""
if not isinstance(high_level_object, QftGate):
raise TranspilerError(
"The synthesis plugin 'qft.line` only applies to objects of type QftGate."
)

do_swaps = options.get("do_swaps", True)
approximation_degree = options.get("approximation_degree", 0)

decomposition = synth_qft_line(
num_qubits=high_level_object.num_qubits,
do_swaps=do_swaps,
approximation_degree=approximation_degree,
)
return decomposition


class TokenSwapperSynthesisPermutation(HighLevelSynthesisPlugin):
"""The permutation synthesis plugin based on the token swapper algorithm.

Expand Down Expand Up @@ -691,7 +780,6 @@ class TokenSwapperSynthesisPermutation(HighLevelSynthesisPlugin):

For more details on the token swapper algorithm, see to the paper:
`arXiv:1902.09102 <https://arxiv.org/abs/1902.09102>`__.

"""

def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options):
Expand Down
22 changes: 22 additions & 0 deletions releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
features:
- |
Added a new class :class:`~qiskit.circuit.library.QftGate` for
natively representing Quantum Fourier Transforms (QFTs). The older way
of representing QFTs via quantum circuits, see
:class:`~qiskit.circuit.library.QFT`, remains for backward compatibility.
The new way of representing a QFT via a gate avoids synthesizing its
definition circuit when the gate is declared, delaying the actual synthesis to
the transpiler. It also allows to easily choose between several different
algorithms for synthesizing QFTs, which are available as high-level-synthesis
plugins.
- |
Added a synthesis method :func:`.synth_qft_full` for constructing a QFT circuit
assuming a fully-connected architecture.
- |
Added two high-level-synthesis plugins for synthesizing a
:class:`~qiskit.circuit.library.QftGate`.
The class :class:`.QftSynthesisFull` is based on :func:`.synth_qft_full` and synthesizes
a QFT gate assuming all-to-all connectivity.
The class :class:`.QftSynthesisLine` is based on :func:`.synth_qft_line` and synthesizes
a QFT gate assuming linear nearest neighbor connectivity.
Loading
Loading