From 71efe1c80a131bfe45e61b89fbbdce3facf2ab12 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 28 Dec 2023 13:13:02 +0200 Subject: [PATCH 01/19] initial commit --- pyproject.toml | 3 + qiskit/circuit/library/__init__.py | 2 + .../library/generalized_gates/__init__.py | 1 + .../circuit/library/generalized_gates/qft.py | 72 +++++++++++ qiskit/qpy/binary_io/circuits.py | 2 + qiskit/synthesis/qft/__init__.py | 1 + qiskit/synthesis/qft/qft_decompose_full.py | 61 +++++++++ .../passes/synthesis/high_level_synthesis.py | 62 +++++++++ test/python/circuit/library/test_qft.py | 119 +++++++++++++++++- test/python/circuit/test_gate_definitions.py | 1 + test/python/synthesis/test_qft_synthesis.py | 51 +++++++- .../transpiler/test_high_level_synthesis.py | 14 ++- 12 files changed, 383 insertions(+), 6 deletions(-) create mode 100644 qiskit/circuit/library/generalized_gates/qft.py create mode 100644 qiskit/synthesis/qft/qft_decompose_full.py diff --git a/pyproject.toml b/pyproject.toml index 04bafbf3f995..b97becd6eafa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" [project.entry-points."qiskit.transpiler.init"] default = "qiskit.transpiler.preset_passmanagers.builtin_plugins:DefaultInitPassManager" diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index 76b06aa99aba..7a6368944505 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -206,6 +206,7 @@ UCRXGate UCRYGate UCRZGate + QftGate Boolean Logic Circuits ====================== @@ -524,6 +525,7 @@ UCRXGate, UCRYGate, UCRZGate, + QftGate, ) from .pauli_evolution import PauliEvolutionGate from .hamiltonian_gate import HamiltonianGate diff --git a/qiskit/circuit/library/generalized_gates/__init__.py b/qiskit/circuit/library/generalized_gates/__init__.py index 6a704cdef286..d6930ac17b91 100644 --- a/qiskit/circuit/library/generalized_gates/__init__.py +++ b/qiskit/circuit/library/generalized_gates/__init__.py @@ -28,3 +28,4 @@ from .ucrz import UCRZGate from .unitary import UnitaryGate from .mcg_up_to_diagonal import MCGupDiag +from .qft import QftGate diff --git a/qiskit/circuit/library/generalized_gates/qft.py b/qiskit/circuit/library/generalized_gates/qft.py new file mode 100644 index 000000000000..78bc5a9593db --- /dev/null +++ b/qiskit/circuit/library/generalized_gates/qft.py @@ -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, + ): + """Construct a new QFT gate. + + 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): + """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 + + 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() diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index 6d910746a89a..9502bdfa0ae8 100644 --- a/qiskit/qpy/binary_io/circuits.py +++ b/qiskit/qpy/binary_io/circuits.py @@ -336,6 +336,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)] diff --git a/qiskit/synthesis/qft/__init__.py b/qiskit/synthesis/qft/__init__.py index 99bd2f7da9b2..8140bf4a74e2 100644 --- a/qiskit/synthesis/qft/__init__.py +++ b/qiskit/synthesis/qft/__init__.py @@ -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 diff --git a/qiskit/synthesis/qft/qft_decompose_full.py b/qiskit/synthesis/qft/qft_decompose_full.py new file mode 100644 index 000000000000..a07cf9b868b3 --- /dev/null +++ b/qiskit/synthesis/qft/qft_decompose_full.py @@ -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, +) -> 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. + 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 diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index da10a8102887..bc5b65a4b1e0 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -32,6 +32,7 @@ ControlModifier, PowerModifier, ) +from qiskit.circuit.library import QftGate from qiskit.synthesis.clifford import ( synth_clifford_full, synth_clifford_layers, @@ -46,6 +47,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 @@ -644,3 +649,60 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** """Run synthesis for the given Permutation.""" decomposition = synth_permutation_acg(high_level_object.pattern) return decomposition + + +class QftSynthesisFull(HighLevelSynthesisPlugin): + """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`. + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + """Run synthesis for the given QftGate. + + ToDo: add description of supported options. + """ + if not isinstance(high_level_object, QftGate): + raise TranspilerError( + "The synthesis plugin 'qft.full` only applies to objects of type QftGate." + ) + + 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=high_level_object.do_swaps, + approximation_degree=high_level_object.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`. + """ + + def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): + """Run synthesis for the given QftGate. + + ToDo: add description of supported options. + """ + if not isinstance(high_level_object, QftGate): + raise TranspilerError( + "The synthesis plugin 'qft.line` only applies to objects of type QftGate." + ) + + decomposition = synth_qft_line( + num_qubits=high_level_object.num_qubits, + do_swaps=high_level_object.do_swaps, + approximation_degree=high_level_object.approximation_degree, + ) + return decomposition diff --git a/test/python/circuit/library/test_qft.py b/test/python/circuit/library/test_qft.py index fa5305aa3631..fd1a6802533b 100644 --- a/test/python/circuit/library/test_qft.py +++ b/test/python/circuit/library/test_qft.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2020. +# (C) Copyright IBM 2017, 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 @@ -12,6 +12,8 @@ """Test library of QFT circuits.""" +import io + import unittest import warnings import numpy as np @@ -19,9 +21,11 @@ from qiskit.test.base import QiskitTestCase from qiskit import transpile -from qiskit.circuit import QuantumCircuit -from qiskit.circuit.library import QFT +from qiskit.circuit import QuantumCircuit, QuantumRegister +from qiskit.circuit.library import QFT, QftGate from qiskit.quantum_info import Operator +from qiskit.qpy import dump, load +from qiskit.qasm2 import dumps @ddt @@ -206,5 +210,114 @@ def __init__(self, *_args, **_kwargs): qft._build() +@ddt +class TestQFTGate(QiskitTestCase): + """Test the QFT Gate.""" + + @data(2, 3, 4, 5, 6) + def test_array_equivalent_to_decomposition(self, num_qubits): + """Test that the provided __array__ method and that the provided basic + definition are equivalent. + """ + qft_gate = QftGate(num_qubits=num_qubits) + qft_gate_decomposition = qft_gate.definition + self.assertEqual(Operator(qft_gate), Operator(qft_gate_decomposition)) + + @data(2, 3, 4, 5, 6) + def test_gate_equivalent_to_original(self, num_qubits): + """Test that the Operator can be constructed out of a QFT gate, and is + equivalent to the Operator constructed out of a QFT circuit. + """ + qft_gate = QftGate(num_qubits=num_qubits) + qft_circuit = QFT(num_qubits=num_qubits) + self.assertEqual(Operator(qft_gate), Operator(qft_circuit)) + + def test_append_to_circuit(self): + """Test adding a QftGate to a quantum circuit.""" + qc = QuantumCircuit(5) + qc.append(QftGate(4), [1, 2, 0, 4]) + self.assertIsInstance(qc.data[0].operation, QftGate) + + @data(2, 3, 4, 5, 6) + def test_circuit_with_gate_equivalent_to_original(self, num_qubits): + """Test that the Operator can be constructed out of a circuit containing a QFT gate, and is + equivalent to the Operator constructed out of a QFT circuit. + """ + qft_gate = QftGate(num_qubits=num_qubits) + circuit_with_qft_gate = QuantumCircuit(num_qubits) + circuit_with_qft_gate.append(qft_gate, range(num_qubits)) + qft_circuit = QFT(num_qubits=num_qubits) + self.assertEqual(Operator(circuit_with_qft_gate), Operator(qft_circuit)) + + def test_inverse(self): + """Test that inverse can be constructed for a circuit with a QftGate.""" + qc = QuantumCircuit(5) + qc.append(QftGate(4), [1, 2, 0, 4]) + qci = qc.inverse() + self.assertEqual(Operator(qci), Operator(qc).adjoint()) + + def test_reverse_ops(self): + """Test reverse_ops works for a circuit with a QftGate.""" + qc = QuantumCircuit(5) + qc.cx(1, 3) + qc.append(QftGate(4), [1, 2, 0, 4]) + qc.h(0) + qcr = qc.reverse_ops() + expected = QuantumCircuit(5) + expected.h(0) + expected.append(QftGate(4), [1, 2, 0, 4]) + expected.cx(1, 3) + self.assertEqual(qcr, expected) + + def test_conditional(self): + """Test adding conditional to a QftGate.""" + qc = QuantumCircuit(5, 1) + qc.append(QftGate(4), [1, 2, 0, 4]).c_if(0, 1) + self.assertIsNotNone(qc.data[0].operation.condition) + + def test_qasm(self): + """Test qasm for circuits with QftGates.""" + qr = QuantumRegister(5, "q0") + qc = QuantumCircuit(qr) + qc.append(QftGate(num_qubits=4), [1, 2, 0, 4]) + qc.append(QftGate(num_qubits=3), [0, 1, 2]) + qc.h(qr[0]) + qc_qasm = dumps(qc) + reconstructed = QuantumCircuit.from_qasm_str(qc_qasm) + self.assertEqual(Operator(qc), Operator(reconstructed)) + + def test_qpy(self): + """Test qpy for circuits with QftGates.""" + qc = QuantumCircuit(6, 1) + qc.append(QftGate(num_qubits=4), [1, 2, 0, 4]) + qc.append(QftGate(num_qubits=3), [0, 1, 2]) + qc.h(0) + + qpy_file = io.BytesIO() + dump(qc, qpy_file) + qpy_file.seek(0) + new_circuit = load(qpy_file)[0] + self.assertEqual(qc, new_circuit) + + def test_gate_equality(self): + """Test checking equality of QftGates.""" + self.assertEqual(QftGate(num_qubits=3), QftGate(num_qubits=3)) + self.assertNotEqual(QftGate(num_qubits=3), QftGate(num_qubits=4)) + + def test_circuit_with_gate_equality(self): + """Test checking equality of circuits with QftGates.""" + qc1 = QuantumCircuit(5) + qc1.append(QftGate(num_qubits=3), [1, 2, 0]) + + qc2 = QuantumCircuit(5) + qc2.append(QftGate(num_qubits=3), [1, 2, 0]) + + qc3 = QuantumCircuit(5) + qc3.append(QftGate(num_qubits=4), [1, 2, 0, 4]) + + self.assertEqual(qc1, qc2) + self.assertNotEqual(qc1, qc3) + + if __name__ == "__main__": unittest.main() diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index c12253f81f99..91380bcd549e 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -288,6 +288,7 @@ class TestGateEquivalenceEqual(QiskitTestCase): "_DefinedGate", "_SingletonGateOverrides", "_SingletonControlledGateOverrides", + "QftGate", } # Amazingly, Python's scoping rules for class bodies means that this is the closest we can get # to a "natural" comprehension or functional iterable definition: diff --git a/test/python/synthesis/test_qft_synthesis.py b/test/python/synthesis/test_qft_synthesis.py index 2ea8e6033cb9..dd7f8d8b673d 100644 --- a/test/python/synthesis/test_qft_synthesis.py +++ b/test/python/synthesis/test_qft_synthesis.py @@ -15,11 +15,11 @@ import unittest from test import combine -from ddt import ddt +from ddt import ddt, data from qiskit.test import QiskitTestCase from qiskit.circuit.library import QFT -from qiskit.synthesis.qft import synth_qft_line +from qiskit.synthesis.qft import synth_qft_line, synth_qft_full from qiskit.quantum_info import Operator from qiskit.synthesis.linear.linear_circuits_utils import check_lnn_connectivity @@ -58,5 +58,52 @@ def test_qft_lnn_approximated(self, num_qubits, do_swaps, approximation_degree): self.assertTrue(check_lnn_connectivity(qft_lnn)) +@ddt +class TestQFTFull(QiskitTestCase): + """Tests for QFT synthesis using all-to-all connectivity.""" + + @data(2, 3, 4, 5, 6) + def test_synthesis_default(self, num_qubits): + """Assert that the original and synthesized QFT circuits are the same.""" + original = QFT(num_qubits) + synthesized = synth_qft_full(num_qubits) + self.assertEqual(Operator(original), Operator(synthesized)) + + @combine(num_qubits=[5, 6, 7, 8], do_swaps=[False, True]) + def test_synthesis_do_swaps(self, num_qubits, do_swaps): + """Assert that the original and synthesized QFT circuits are the same.""" + original = QFT(num_qubits, do_swaps=do_swaps) + synthesized = synth_qft_full(num_qubits, do_swaps=do_swaps) + self.assertEqual(Operator(original), Operator(synthesized)) + + @combine(num_qubits=[5, 6, 7, 8], approximation_degree=[0, 1, 2, 3]) + def test_synthesis_arpproximate(self, num_qubits, approximation_degree): + """Assert that the original and synthesized QFT circuits are the same.""" + original = QFT(num_qubits, approximation_degree=approximation_degree) + synthesized = synth_qft_full(num_qubits, approximation_degree=approximation_degree) + self.assertEqual(Operator(original), Operator(synthesized)) + + @combine(num_qubits=[5, 6, 7, 8], inverse=[False, True]) + def test_synthesis_inverse(self, num_qubits, inverse): + """Assert that the original and synthesized QFT circuits are the same.""" + original = QFT(num_qubits, inverse=inverse) + synthesized = synth_qft_full(num_qubits, inverse=inverse) + self.assertEqual(Operator(original), Operator(synthesized)) + + @combine(num_qubits=[5, 6, 7, 8], insert_barriers=[False, True]) + def test_synthesis_insert_barriers(self, num_qubits, insert_barriers): + """Assert that the original and synthesized QFT circuits are the same.""" + original = QFT(num_qubits, insert_barriers=insert_barriers) + synthesized = synth_qft_full(num_qubits, insert_barriers=insert_barriers) + self.assertEqual(Operator(original), Operator(synthesized)) + + @data(5, 6, 7, 8) + def test_synthesis_name(self, num_qubits): + """Assert that the original and synthesized QFT circuits are the same.""" + original = QFT(num_qubits, name="SomeRandomName") + synthesized = synth_qft_full(num_qubits, name="SomeRandomName") + self.assertEqual(original.name, synthesized.name) + + if __name__ == "__main__": unittest.main() diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py index 962a3b87b66e..7278fc20de2f 100644 --- a/test/python/transpiler/test_high_level_synthesis.py +++ b/test/python/transpiler/test_high_level_synthesis.py @@ -47,7 +47,10 @@ from qiskit.converters import dag_to_circuit, circuit_to_dag, circuit_to_instruction from qiskit.transpiler import PassManager, TranspilerError, CouplingMap, Target from qiskit.transpiler.passes.basis import BasisTranslator -from qiskit.transpiler.passes.synthesis.plugin import HighLevelSynthesisPlugin +from qiskit.transpiler.passes.synthesis.plugin import ( + HighLevelSynthesisPlugin, + high_level_synthesis_plugin_names, +) from qiskit.transpiler.passes.synthesis.high_level_synthesis import HighLevelSynthesis, HLSConfig from qiskit.circuit.annotated_operation import ( AnnotatedOperation, @@ -1652,5 +1655,14 @@ def test_unroll_empty_definition_with_phase(self): self.assertEqual(pass_(qc), expected) +class TestQftSynthesisPlugins(QiskitTestCase): + """Tests related to plugins for QftGate.""" + + def test_supported_names(self): + """Test that there is a default synthesis plugin for QftGates.""" + supported_plugin_names = high_level_synthesis_plugin_names("qft") + self.assertIn("default", supported_plugin_names) + + if __name__ == "__main__": unittest.main() From 3705ba4188e23eb7802ac8bf10aae4f3c96f9da4 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 28 Dec 2023 13:42:09 +0200 Subject: [PATCH 02/19] release notes --- .../notes/add-qft-gate-fd4e08f6721a9da4.yaml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml diff --git a/releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml b/releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml new file mode 100644 index 000000000000..701a5a7c685e --- /dev/null +++ b/releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml @@ -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. From 68c2ccf42de9a2f8a71a821f83f651f19570946d Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 28 Dec 2023 14:31:29 +0200 Subject: [PATCH 03/19] fixing synthesis plugin options --- .../passes/synthesis/high_level_synthesis.py | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index bc5b65a4b1e0..a83873cf70d4 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -656,26 +656,41 @@ class QftSynthesisFull(HighLevelSynthesisPlugin): 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 + 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. + * 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. - - ToDo: add description of supported 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") + approximation_degree = options.get("approximation_degree") 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=high_level_object.do_swaps, - approximation_degree=high_level_object.approximation_degree, + do_swaps=do_swaps, + approximation_degree=approximation_degree, insert_barriers=insert_barriers, inverse=inverse, name=name, @@ -688,21 +703,34 @@ class QftSynthesisLine(HighLevelSynthesisPlugin): 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 + 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. + """ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): - """Run synthesis for the given QftGate. - - ToDo: add description of supported 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") + approximation_degree = options.get("approximation_degree") + decomposition = synth_qft_line( num_qubits=high_level_object.num_qubits, - do_swaps=high_level_object.do_swaps, - approximation_degree=high_level_object.approximation_degree, + do_swaps=do_swaps, + approximation_degree=approximation_degree, ) return decomposition From 425a6d90da678252b6485c6cb80fc3f4148797a7 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sun, 4 Feb 2024 17:20:00 +0200 Subject: [PATCH 04/19] finalize merge conflicts --- qiskit/transpiler/passes/synthesis/high_level_synthesis.py | 3 ++- test/python/transpiler/test_high_level_synthesis.py | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index a9b210f553ff..caff5dfb6ea1 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -731,6 +731,7 @@ class QftSynthesisLine(HighLevelSynthesisPlugin): in more detail in https://arxiv.org/abs/quant-ph/9601018 or https://arxiv.org/abs/quant-ph/0403071. """ + 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): @@ -748,7 +749,7 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** ) return decomposition - + class TokenSwapperSynthesisPermutation(HighLevelSynthesisPlugin): """The permutation synthesis plugin based on the token swapper algorithm. diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py index 34ced9c1d56a..83738be8e4e6 100644 --- a/test/python/transpiler/test_high_level_synthesis.py +++ b/test/python/transpiler/test_high_level_synthesis.py @@ -41,10 +41,6 @@ ) from qiskit.circuit.library.generalized_gates import LinearFunction from qiskit.quantum_info import Clifford -from qiskit.transpiler.passes.synthesis.plugin import ( - HighLevelSynthesisPlugin, - HighLevelSynthesisPluginManager, -) from qiskit.compiler import transpile from qiskit.exceptions import QiskitError from qiskit.converters import dag_to_circuit, circuit_to_dag, circuit_to_instruction @@ -52,6 +48,7 @@ from qiskit.transpiler.passes.basis import BasisTranslator from qiskit.transpiler.passes.synthesis.plugin import ( HighLevelSynthesisPlugin, + HighLevelSynthesisPluginManager, high_level_synthesis_plugin_names, ) from qiskit.transpiler.passes.synthesis.high_level_synthesis import HighLevelSynthesis, HLSConfig From ef45120578593541d6c1cda8c64fbd1a6fb55649 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 6 Feb 2024 10:50:50 +0200 Subject: [PATCH 05/19] fixing default option values for qft plugins' --- .../transpiler/passes/synthesis/high_level_synthesis.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index caff5dfb6ea1..7daf64638d8e 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -696,8 +696,8 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** "The synthesis plugin 'qft.full` only applies to objects of type QftGate." ) - do_swaps = options.get("do_swaps") - approximation_degree = options.get("approximation_degree") + 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) @@ -739,8 +739,8 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** "The synthesis plugin 'qft.line` only applies to objects of type QftGate." ) - do_swaps = options.get("do_swaps") - approximation_degree = options.get("approximation_degree") + 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, From 09086e6ae4071944fb2ef6a066e753ef16b6f2b7 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 6 Feb 2024 11:16:22 +0200 Subject: [PATCH 06/19] additional tests for qft plugins --- .../transpiler/test_high_level_synthesis.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py index 83738be8e4e6..f874f8d48d50 100644 --- a/test/python/transpiler/test_high_level_synthesis.py +++ b/test/python/transpiler/test_high_level_synthesis.py @@ -16,6 +16,7 @@ import itertools import unittest.mock import numpy as np +from ddt import ddt, data from qiskit.circuit import ( QuantumCircuit, @@ -38,6 +39,7 @@ U1Gate, CU3Gate, CU1Gate, + QftGate, ) from qiskit.circuit.library.generalized_gates import LinearFunction from qiskit.quantum_info import Clifford @@ -1842,6 +1844,7 @@ def test_unroll_empty_definition_with_phase(self): self.assertEqual(pass_(qc), expected) +@ddt class TestQftSynthesisPlugins(QiskitTestCase): """Tests related to plugins for QftGate.""" @@ -1850,6 +1853,32 @@ def test_supported_names(self): supported_plugin_names = high_level_synthesis_plugin_names("qft") self.assertIn("default", supported_plugin_names) + @data("line", "full") + def test_qft_plugins_qft(self, qft_plugin_name): + """Test QftSynthesisLine plugin for circuits with QftGates.""" + qc = QuantumCircuit(4) + qc.append(QftGate(3), [0, 1, 2]) + qc.cx(1, 3) + qc.append(QftGate(3).inverse(), [0, 1, 2]) + hls_config = HLSConfig(qft=[qft_plugin_name]) + basis_gates = ["cx", "u"] + qct = transpile(qc, hls_config=hls_config, basis_gates=basis_gates) + self.assertEqual(Operator(qc), Operator(qct)) + ops = set(qct.count_ops().keys()) + self.assertEqual(ops, {"u", "cx"}) + + @data("line", "full") + def test_qft_line_plugin_annotated_qft(self, qft_plugin_name): + """Test QftSynthesisLine plugin for circuits with annotated QftGates.""" + qc = QuantumCircuit(4) + qc.append(QftGate(3).inverse(annotated=True).control(annotated=True), [0, 1, 2, 3]) + hls_config = HLSConfig(qft=[qft_plugin_name]) + basis_gates = ["cx", "u"] + qct = transpile(qc, hls_config=hls_config, basis_gates=basis_gates) + self.assertEqual(Operator(qc), Operator(qct)) + ops = set(qct.count_ops().keys()) + self.assertEqual(ops, {"u", "cx"}) + if __name__ == "__main__": unittest.main() From eac4ed67a85e809f106c23d635e61b035de4f43c Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 2 May 2024 13:51:07 +0300 Subject: [PATCH 07/19] renaming QftGate to QFTGate --- qiskit/circuit/library/__init__.py | 4 +- .../library/generalized_gates/__init__.py | 2 +- .../circuit/library/generalized_gates/qft.py | 8 +-- qiskit/qpy/binary_io/circuits.py | 2 +- .../passes/synthesis/high_level_synthesis.py | 22 ++++---- .../notes/add-qft-gate-fd4e08f6721a9da4.yaml | 4 +- test/python/circuit/library/test_qft.py | 54 +++++++++---------- test/python/circuit/test_gate_definitions.py | 2 +- .../transpiler/test_high_level_synthesis.py | 16 +++--- 9 files changed, 57 insertions(+), 57 deletions(-) diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index c2600d911c3d..87d7d098b219 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -206,7 +206,7 @@ UCRXGate UCRYGate UCRZGate - QftGate + QFTGate Boolean Logic Circuits ====================== @@ -525,7 +525,7 @@ UCRXGate, UCRYGate, UCRZGate, - QftGate, + QFTGate, ) from .pauli_evolution import PauliEvolutionGate from .hamiltonian_gate import HamiltonianGate diff --git a/qiskit/circuit/library/generalized_gates/__init__.py b/qiskit/circuit/library/generalized_gates/__init__.py index d6930ac17b91..c8ddb3fff8c0 100644 --- a/qiskit/circuit/library/generalized_gates/__init__.py +++ b/qiskit/circuit/library/generalized_gates/__init__.py @@ -28,4 +28,4 @@ from .ucrz import UCRZGate from .unitary import UnitaryGate from .mcg_up_to_diagonal import MCGupDiag -from .qft import QftGate +from .qft import QFTGate diff --git a/qiskit/circuit/library/generalized_gates/qft.py b/qiskit/circuit/library/generalized_gates/qft.py index 78bc5a9593db..3b641ab9b342 100644 --- a/qiskit/circuit/library/generalized_gates/qft.py +++ b/qiskit/circuit/library/generalized_gates/qft.py @@ -10,14 +10,14 @@ # 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.""" +"""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): +class QFTGate(Gate): r"""Quantum Fourier Transform Circuit. The Quantum Fourier Transform (QFT) on :math:`n` qubits is the operation @@ -40,7 +40,7 @@ def __init__( super().__init__(name="qft", num_qubits=num_qubits, params=[]) def __array__(self, dtype=complex): - """Return a numpy array for the QftGate.""" + """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): @@ -66,7 +66,7 @@ def _basic_decomposition(self): def _define(self): """Populate self.definition with a specific decomposition of the gate. - This is used for constructing Operators from QftGates, creating qasm + This is used for constructing Operator from QFTGate, creating qasm representations and more. """ self.definition = self._basic_decomposition() diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index 87a403703e64..0a7857e94c19 100644 --- a/qiskit/qpy/binary_io/circuits.py +++ b/qiskit/qpy/binary_io/circuits.py @@ -340,7 +340,7 @@ def _read_instruction( "DiagonalGate", }: gate = gate_class(params) - elif gate_name == "QftGate": + elif gate_name == "QFTGate": gate = gate_class(len(qargs), *params) else: if gate_name == "Barrier": diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 7daf64638d8e..4d5fdb3d08ca 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -35,7 +35,7 @@ ControlModifier, PowerModifier, ) -from qiskit.circuit.library import QftGate +from qiskit.circuit.library import QFTGate from qiskit.synthesis.clifford import ( synth_clifford_full, synth_clifford_layers, @@ -675,9 +675,9 @@ class QftSynthesisFull(HighLevelSynthesisPlugin): The plugin supports the following additional options: * do_swaps (bool): Whether to include Swap gates at the end of the synthesized - circuit. Some implementation of the ``QftGate`` include a layer of Swap gates + 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. + 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 @@ -690,10 +690,10 @@ class QftSynthesisFull(HighLevelSynthesisPlugin): """ 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): + """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." + "The synthesis plugin 'qft.full` only applies to objects of type QFTGate." ) do_swaps = options.get("do_swaps", True) @@ -722,9 +722,9 @@ class QftSynthesisLine(HighLevelSynthesisPlugin): The plugin supports the following additional options: * do_swaps (bool): whether to include Swap gates at the end of the synthesized - circuit. Some implementation of the ``QftGate`` include a layer of Swap gates + 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. + 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 @@ -733,10 +733,10 @@ class QftSynthesisLine(HighLevelSynthesisPlugin): """ 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): + """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." + "The synthesis plugin 'qft.line` only applies to objects of type QFTGate." ) do_swaps = options.get("do_swaps", True) diff --git a/releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml b/releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml index 701a5a7c685e..656eb7a1fb91 100644 --- a/releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml +++ b/releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml @@ -1,7 +1,7 @@ --- features: - | - Added a new class :class:`~qiskit.circuit.library.QftGate` for + 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. @@ -15,7 +15,7 @@ features: assuming a fully-connected architecture. - | Added two high-level-synthesis plugins for synthesizing a - :class:`~qiskit.circuit.library.QftGate`. + :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 diff --git a/test/python/circuit/library/test_qft.py b/test/python/circuit/library/test_qft.py index 2aa5faf2b894..21cb013ac78f 100644 --- a/test/python/circuit/library/test_qft.py +++ b/test/python/circuit/library/test_qft.py @@ -21,7 +21,7 @@ from qiskit import transpile from qiskit.circuit import QuantumCircuit, QuantumRegister -from qiskit.circuit.library import QFT, QftGate +from qiskit.circuit.library import QFT, QFTGate from qiskit.quantum_info import Operator from qiskit.qpy import dump, load from qiskit.qasm2 import dumps @@ -219,7 +219,7 @@ def test_array_equivalent_to_decomposition(self, num_qubits): """Test that the provided __array__ method and that the provided basic definition are equivalent. """ - qft_gate = QftGate(num_qubits=num_qubits) + qft_gate = QFTGate(num_qubits=num_qubits) qft_gate_decomposition = qft_gate.definition self.assertEqual(Operator(qft_gate), Operator(qft_gate_decomposition)) @@ -228,69 +228,69 @@ def test_gate_equivalent_to_original(self, num_qubits): """Test that the Operator can be constructed out of a QFT gate, and is equivalent to the Operator constructed out of a QFT circuit. """ - qft_gate = QftGate(num_qubits=num_qubits) + qft_gate = QFTGate(num_qubits=num_qubits) qft_circuit = QFT(num_qubits=num_qubits) self.assertEqual(Operator(qft_gate), Operator(qft_circuit)) def test_append_to_circuit(self): - """Test adding a QftGate to a quantum circuit.""" + """Test adding a QFTGate to a quantum circuit.""" qc = QuantumCircuit(5) - qc.append(QftGate(4), [1, 2, 0, 4]) - self.assertIsInstance(qc.data[0].operation, QftGate) + qc.append(QFTGate(4), [1, 2, 0, 4]) + self.assertIsInstance(qc.data[0].operation, QFTGate) @data(2, 3, 4, 5, 6) def test_circuit_with_gate_equivalent_to_original(self, num_qubits): """Test that the Operator can be constructed out of a circuit containing a QFT gate, and is equivalent to the Operator constructed out of a QFT circuit. """ - qft_gate = QftGate(num_qubits=num_qubits) + qft_gate = QFTGate(num_qubits=num_qubits) circuit_with_qft_gate = QuantumCircuit(num_qubits) circuit_with_qft_gate.append(qft_gate, range(num_qubits)) qft_circuit = QFT(num_qubits=num_qubits) self.assertEqual(Operator(circuit_with_qft_gate), Operator(qft_circuit)) def test_inverse(self): - """Test that inverse can be constructed for a circuit with a QftGate.""" + """Test that inverse can be constructed for a circuit with a QFTGate.""" qc = QuantumCircuit(5) - qc.append(QftGate(4), [1, 2, 0, 4]) + qc.append(QFTGate(4), [1, 2, 0, 4]) qci = qc.inverse() self.assertEqual(Operator(qci), Operator(qc).adjoint()) def test_reverse_ops(self): - """Test reverse_ops works for a circuit with a QftGate.""" + """Test reverse_ops works for a circuit with a QFTGate.""" qc = QuantumCircuit(5) qc.cx(1, 3) - qc.append(QftGate(4), [1, 2, 0, 4]) + qc.append(QFTGate(4), [1, 2, 0, 4]) qc.h(0) qcr = qc.reverse_ops() expected = QuantumCircuit(5) expected.h(0) - expected.append(QftGate(4), [1, 2, 0, 4]) + expected.append(QFTGate(4), [1, 2, 0, 4]) expected.cx(1, 3) self.assertEqual(qcr, expected) def test_conditional(self): - """Test adding conditional to a QftGate.""" + """Test adding conditional to a QFTGate.""" qc = QuantumCircuit(5, 1) - qc.append(QftGate(4), [1, 2, 0, 4]).c_if(0, 1) + qc.append(QFTGate(4), [1, 2, 0, 4]).c_if(0, 1) self.assertIsNotNone(qc.data[0].operation.condition) def test_qasm(self): - """Test qasm for circuits with QftGates.""" + """Test qasm for circuits with QFTGates.""" qr = QuantumRegister(5, "q0") qc = QuantumCircuit(qr) - qc.append(QftGate(num_qubits=4), [1, 2, 0, 4]) - qc.append(QftGate(num_qubits=3), [0, 1, 2]) + qc.append(QFTGate(num_qubits=4), [1, 2, 0, 4]) + qc.append(QFTGate(num_qubits=3), [0, 1, 2]) qc.h(qr[0]) qc_qasm = dumps(qc) reconstructed = QuantumCircuit.from_qasm_str(qc_qasm) self.assertEqual(Operator(qc), Operator(reconstructed)) def test_qpy(self): - """Test qpy for circuits with QftGates.""" + """Test qpy for circuits with QFTGates.""" qc = QuantumCircuit(6, 1) - qc.append(QftGate(num_qubits=4), [1, 2, 0, 4]) - qc.append(QftGate(num_qubits=3), [0, 1, 2]) + qc.append(QFTGate(num_qubits=4), [1, 2, 0, 4]) + qc.append(QFTGate(num_qubits=3), [0, 1, 2]) qc.h(0) qpy_file = io.BytesIO() @@ -300,20 +300,20 @@ def test_qpy(self): self.assertEqual(qc, new_circuit) def test_gate_equality(self): - """Test checking equality of QftGates.""" - self.assertEqual(QftGate(num_qubits=3), QftGate(num_qubits=3)) - self.assertNotEqual(QftGate(num_qubits=3), QftGate(num_qubits=4)) + """Test checking equality of QFTGates.""" + self.assertEqual(QFTGate(num_qubits=3), QFTGate(num_qubits=3)) + self.assertNotEqual(QFTGate(num_qubits=3), QFTGate(num_qubits=4)) def test_circuit_with_gate_equality(self): - """Test checking equality of circuits with QftGates.""" + """Test checking equality of circuits with QFTGates.""" qc1 = QuantumCircuit(5) - qc1.append(QftGate(num_qubits=3), [1, 2, 0]) + qc1.append(QFTGate(num_qubits=3), [1, 2, 0]) qc2 = QuantumCircuit(5) - qc2.append(QftGate(num_qubits=3), [1, 2, 0]) + qc2.append(QFTGate(num_qubits=3), [1, 2, 0]) qc3 = QuantumCircuit(5) - qc3.append(QftGate(num_qubits=4), [1, 2, 0, 4]) + qc3.append(QFTGate(num_qubits=4), [1, 2, 0, 4]) self.assertEqual(qc1, qc2) self.assertNotEqual(qc1, qc3) diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index 987f5b4d0e91..c846702b9302 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -287,7 +287,7 @@ class TestGateEquivalenceEqual(QiskitTestCase): "_DefinedGate", "_SingletonGateOverrides", "_SingletonControlledGateOverrides", - "QftGate", + "QFTGate", } # Amazingly, Python's scoping rules for class bodies means that this is the closest we can get diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py index f874f8d48d50..3a526526aac4 100644 --- a/test/python/transpiler/test_high_level_synthesis.py +++ b/test/python/transpiler/test_high_level_synthesis.py @@ -39,7 +39,7 @@ U1Gate, CU3Gate, CU1Gate, - QftGate, + QFTGate, ) from qiskit.circuit.library.generalized_gates import LinearFunction from qiskit.quantum_info import Clifford @@ -1846,20 +1846,20 @@ def test_unroll_empty_definition_with_phase(self): @ddt class TestQftSynthesisPlugins(QiskitTestCase): - """Tests related to plugins for QftGate.""" + """Tests related to plugins for QFTGate.""" def test_supported_names(self): - """Test that there is a default synthesis plugin for QftGates.""" + """Test that there is a default synthesis plugin for QFTGates.""" supported_plugin_names = high_level_synthesis_plugin_names("qft") self.assertIn("default", supported_plugin_names) @data("line", "full") def test_qft_plugins_qft(self, qft_plugin_name): - """Test QftSynthesisLine plugin for circuits with QftGates.""" + """Test QftSynthesisLine plugin for circuits with QFTGates.""" qc = QuantumCircuit(4) - qc.append(QftGate(3), [0, 1, 2]) + qc.append(QFTGate(3), [0, 1, 2]) qc.cx(1, 3) - qc.append(QftGate(3).inverse(), [0, 1, 2]) + qc.append(QFTGate(3).inverse(), [0, 1, 2]) hls_config = HLSConfig(qft=[qft_plugin_name]) basis_gates = ["cx", "u"] qct = transpile(qc, hls_config=hls_config, basis_gates=basis_gates) @@ -1869,9 +1869,9 @@ def test_qft_plugins_qft(self, qft_plugin_name): @data("line", "full") def test_qft_line_plugin_annotated_qft(self, qft_plugin_name): - """Test QftSynthesisLine plugin for circuits with annotated QftGates.""" + """Test QftSynthesisLine plugin for circuits with annotated QFTGates.""" qc = QuantumCircuit(4) - qc.append(QftGate(3).inverse(annotated=True).control(annotated=True), [0, 1, 2, 3]) + qc.append(QFTGate(3).inverse(annotated=True).control(annotated=True), [0, 1, 2, 3]) hls_config = HLSConfig(qft=[qft_plugin_name]) basis_gates = ["cx", "u"] qct = transpile(qc, hls_config=hls_config, basis_gates=basis_gates) From 5acfc3fa4da07cad7e3475c7134b8de62bc826fc Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 4 Jul 2024 14:15:39 +0300 Subject: [PATCH 08/19] Also renaming Qft to QFT in synthesis method names --- pyproject.toml | 6 +++--- qiskit/transpiler/passes/synthesis/high_level_synthesis.py | 4 ++-- releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml | 4 ++-- test/python/transpiler/test_high_level_synthesis.py | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f4f1d89f40d6..bc1284fa2c6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,9 +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" +"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"] diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 4d5fdb3d08ca..fc84baf73737 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -666,7 +666,7 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** return decomposition -class QftSynthesisFull(HighLevelSynthesisPlugin): +class QFTSynthesisFull(HighLevelSynthesisPlugin): """Synthesis plugin for QFT gates using all-to-all connectivity. This plugin name is :``qft.full`` which can be used as the key on @@ -713,7 +713,7 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** return decomposition -class QftSynthesisLine(HighLevelSynthesisPlugin): +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 diff --git a/releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml b/releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml index 656eb7a1fb91..46b60c8b7025 100644 --- a/releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml +++ b/releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml @@ -16,7 +16,7 @@ features: - | 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 + 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 + The class :class:`.QFTSynthesisLine` is based on :func:`.synth_qft_line` and synthesizes a QFT gate assuming linear nearest neighbor connectivity. diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py index 3a526526aac4..64645378aca5 100644 --- a/test/python/transpiler/test_high_level_synthesis.py +++ b/test/python/transpiler/test_high_level_synthesis.py @@ -1845,7 +1845,7 @@ def test_unroll_empty_definition_with_phase(self): @ddt -class TestQftSynthesisPlugins(QiskitTestCase): +class TestQFTSynthesisPlugins(QiskitTestCase): """Tests related to plugins for QFTGate.""" def test_supported_names(self): @@ -1855,7 +1855,7 @@ def test_supported_names(self): @data("line", "full") def test_qft_plugins_qft(self, qft_plugin_name): - """Test QftSynthesisLine plugin for circuits with QFTGates.""" + """Test QFTSynthesisLine plugin for circuits with QFTGates.""" qc = QuantumCircuit(4) qc.append(QFTGate(3), [0, 1, 2]) qc.cx(1, 3) @@ -1869,7 +1869,7 @@ def test_qft_plugins_qft(self, qft_plugin_name): @data("line", "full") def test_qft_line_plugin_annotated_qft(self, qft_plugin_name): - """Test QftSynthesisLine plugin for circuits with annotated QFTGates.""" + """Test QFTSynthesisLine plugin for circuits with annotated QFTGates.""" qc = QuantumCircuit(4) qc.append(QFTGate(3).inverse(annotated=True).control(annotated=True), [0, 1, 2, 3]) hls_config = HLSConfig(qft=[qft_plugin_name]) From 6106c4a19b2205c2b42b9ba3fbcf0310f80aaecf Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 4 Jul 2024 16:51:30 +0300 Subject: [PATCH 09/19] appplying Jake's suggestion from code review --- qiskit/circuit/library/generalized_gates/qft.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/qiskit/circuit/library/generalized_gates/qft.py b/qiskit/circuit/library/generalized_gates/qft.py index 3b641ab9b342..947309a1edcd 100644 --- a/qiskit/circuit/library/generalized_gates/qft.py +++ b/qiskit/circuit/library/generalized_gates/qft.py @@ -41,17 +41,10 @@ def __init__( def __array__(self, dtype=complex): """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 + 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)) def _basic_decomposition(self): """Provide a specific decomposition of the QFT gate into a quantum circuit. From a76fbe7e69dbe2ac1233b50616a94ab7bb452a95 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 5 Jul 2024 09:47:22 +0300 Subject: [PATCH 10/19] inlining _basic_definition into _define --- .../circuit/library/generalized_gates/qft.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/qiskit/circuit/library/generalized_gates/qft.py b/qiskit/circuit/library/generalized_gates/qft.py index 947309a1edcd..02b9b2fad6e6 100644 --- a/qiskit/circuit/library/generalized_gates/qft.py +++ b/qiskit/circuit/library/generalized_gates/qft.py @@ -46,20 +46,8 @@ def __array__(self, dtype=complex): outer = np.outer(nums, nums) return np.exp(2j * np.pi * outer * (0.5**n), dtype=dtype) * (0.5 ** (n / 2)) - def _basic_decomposition(self): - """Provide a specific decomposition of the QFT gate into a quantum circuit. - - Returns: - QuantumCircuit: A circuit implementing the evolution. - """ + def _define(self): + """Provide a specific decomposition of the QFTgate into a quantum circuit.""" 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 Operator from QFTGate, creating qasm - representations and more. - """ - self.definition = self._basic_decomposition() + self.definition = synth_qft_full(num_qubits=self.num_qubits) From 0d0689807a359d0172a7412a9e46b1c6fb626401 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 5 Jul 2024 11:02:25 +0300 Subject: [PATCH 11/19] docstring improvements --- .../circuit/library/generalized_gates/qft.py | 2 +- qiskit/synthesis/qft/qft_decompose_full.py | 24 +++++++++++++++---- qiskit/synthesis/qft/qft_decompose_lnn.py | 22 ++++++++++------- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/qiskit/circuit/library/generalized_gates/qft.py b/qiskit/circuit/library/generalized_gates/qft.py index 02b9b2fad6e6..459eea44dfd2 100644 --- a/qiskit/circuit/library/generalized_gates/qft.py +++ b/qiskit/circuit/library/generalized_gates/qft.py @@ -47,7 +47,7 @@ def __array__(self, dtype=complex): return np.exp(2j * np.pi * outer * (0.5**n), dtype=dtype) * (0.5 ** (n / 2)) def _define(self): - """Provide a specific decomposition of the QFTgate into a quantum circuit.""" + """Provide a specific decomposition of the QFTGate into a quantum circuit.""" from qiskit.synthesis.qft import synth_qft_full self.definition = synth_qft_full(num_qubits=self.num_qubits) diff --git a/qiskit/synthesis/qft/qft_decompose_full.py b/qiskit/synthesis/qft/qft_decompose_full.py index a07cf9b868b3..702ec925b977 100644 --- a/qiskit/synthesis/qft/qft_decompose_full.py +++ b/qiskit/synthesis/qft/qft_decompose_full.py @@ -26,15 +26,29 @@ def synth_qft_full( inverse: bool = False, name: Optional[str] = None, ) -> QuantumCircuit: - """Construct a QFT circuit using all-to-all connectivity. + """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 QFT acts. - do_swaps: Whether to include the final swaps in the QFT. + 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). - insert_barriers: If True, barriers are inserted as visualization improvement. - inverse: If True, the inverse Fourier transform is constructed. + 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. + """ circuit = QuantumCircuit(num_qubits, name=name) diff --git a/qiskit/synthesis/qft/qft_decompose_lnn.py b/qiskit/synthesis/qft/qft_decompose_lnn.py index a54be481f51b..c3a2b9bad704 100644 --- a/qiskit/synthesis/qft/qft_decompose_lnn.py +++ b/qiskit/synthesis/qft/qft_decompose_lnn.py @@ -21,21 +21,25 @@ def synth_qft_line( num_qubits: int, do_swaps: bool = True, approximation_degree: int = 0 ) -> QuantumCircuit: - """Synthesis of a QFT circuit for a linear nearest neighbor connectivity. - Based on Fig 2.b in Fowler et al. [1]. + """Construct a circuit for the Quantum Fourier Transform using linear + neighbor connectivity. - Note that this method *reverts* the order of qubits in the circuit, - compared to the original :class:`.QFT` code. - Hence, the default value of the ``do_swaps`` parameter is ``True`` - since it produces a circuit with fewer CX gates. + The construction is based on Fig 2.b in Fowler et al. [1]. + + .. note:: + + With the default value of ``do_swaps = True``, this synthesis algorithm creates a + circuit that faithfully implements the QFT operation. When ``do_swaps = False``, + this synthesis algorithm creates a circuit that corresponds to "QFT-with-reversal": + applying the QFT and reversing the order of its output qubits. Args: - num_qubits: The number of qubits on which the QFT acts. + num_qubits: The number of qubits on which the Quantum Fourier Transform acts. approximation_degree: The degree of approximation (0 for no approximation). - do_swaps: Whether to include the final swaps in the QFT. + do_swaps: Whether to synthesize the "QFT" or the "QFT-with-reversal" operation. Returns: - A circuit implementation of the QFT circuit. + A circuit implementing the QFT operation. References: 1. A. G. Fowler, S. J. Devitt, and L. C. L. Hollenberg, From 71860bf4395b874366c5a4fc0c0587c90b8eb312 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 5 Jul 2024 11:13:45 +0300 Subject: [PATCH 12/19] more docstring improvements --- qiskit/synthesis/qft/qft_decompose_full.py | 4 ++++ qiskit/synthesis/qft/qft_decompose_lnn.py | 4 ++++ .../passes/synthesis/high_level_synthesis.py | 24 +++++++++---------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/qiskit/synthesis/qft/qft_decompose_full.py b/qiskit/synthesis/qft/qft_decompose_full.py index 702ec925b977..e951a041b192 100644 --- a/qiskit/synthesis/qft/qft_decompose_full.py +++ b/qiskit/synthesis/qft/qft_decompose_full.py @@ -42,6 +42,10 @@ def synth_qft_full( 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 impossible 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. diff --git a/qiskit/synthesis/qft/qft_decompose_lnn.py b/qiskit/synthesis/qft/qft_decompose_lnn.py index c3a2b9bad704..359554b62db6 100644 --- a/qiskit/synthesis/qft/qft_decompose_lnn.py +++ b/qiskit/synthesis/qft/qft_decompose_lnn.py @@ -36,6 +36,10 @@ def synth_qft_line( Args: num_qubits: The number of qubits on which the Quantum Fourier Transform acts. approximation_degree: The degree of approximation (0 for no approximation). + It is impossible 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. do_swaps: Whether to synthesize the "QFT" or the "QFT-with-reversal" operation. Returns: diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index e6b020a8a88f..a8c92fc8907c 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -900,13 +900,13 @@ class QFTSynthesisFull(HighLevelSynthesisPlugin): The plugin supports the following additional options: - * do_swaps (bool): Whether to include Swap gates at the end of the synthesized - 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. + * do_swaps (bool): Whether to synthesize the "QFT" operation (default) or the + "QFT-with-reversal" operation (non-default). Some implementation of the ``QFTGate`` + include a layer of swap gates at the end of the synthesized circuit, which can 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 + It is impossible 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 (bool): If True, barriers are inserted as visualization improvement. @@ -947,13 +947,13 @@ class QFTSynthesisLine(HighLevelSynthesisPlugin): The plugin supports the following additional options: - * do_swaps (bool): whether to include Swap gates at the end of the synthesized - 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. + * do_swaps (bool): Whether to synthesize the "QFT" operation (default) or the + "QFT-with-reversal" operation (non-default). Some implementation of the ``QFTGate`` + include a layer of swap gates at the end of the synthesized circuit, which can 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 + It is impossible 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. """ From d496f08c9218b5f73d01efb884de15f8244b8da2 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 5 Jul 2024 11:21:42 +0300 Subject: [PATCH 13/19] renaming do_swaps to reverse_qubits in the new code --- .../passes/synthesis/high_level_synthesis.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index a8c92fc8907c..eacabb592ae6 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -900,7 +900,7 @@ class QFTSynthesisFull(HighLevelSynthesisPlugin): The plugin supports the following additional options: - * do_swaps (bool): Whether to synthesize the "QFT" operation (default) or the + * reverse_qubits (bool): Whether to synthesize the "QFT" operation (default) or the "QFT-with-reversal" operation (non-default). Some implementation of the ``QFTGate`` include a layer of swap gates at the end of the synthesized circuit, which can in principle be dropped if the ``QFTGate`` itself is the last gate in the circuit. @@ -922,7 +922,7 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** "The synthesis plugin 'qft.full` only applies to objects of type QFTGate." ) - do_swaps = options.get("do_swaps", True) + reverse_qubits = options.get("reverse_qubits", True) approximation_degree = options.get("approximation_degree", 0) insert_barriers = options.get("insert_barriers", False) inverse = options.get("inverse", False) @@ -930,7 +930,7 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** decomposition = synth_qft_full( num_qubits=high_level_object.num_qubits, - do_swaps=do_swaps, + do_swaps=reverse_qubits, approximation_degree=approximation_degree, insert_barriers=insert_barriers, inverse=inverse, @@ -947,7 +947,7 @@ class QFTSynthesisLine(HighLevelSynthesisPlugin): The plugin supports the following additional options: - * do_swaps (bool): Whether to synthesize the "QFT" operation (default) or the + * reverse_qubits (bool): Whether to synthesize the "QFT" operation (default) or the "QFT-with-reversal" operation (non-default). Some implementation of the ``QFTGate`` include a layer of swap gates at the end of the synthesized circuit, which can in principle be dropped if the ``QFTGate`` itself is the last gate in the circuit. @@ -965,12 +965,12 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** "The synthesis plugin 'qft.line` only applies to objects of type QFTGate." ) - do_swaps = options.get("do_swaps", True) + reverse_qubits = options.get("reverse_qubits", True) approximation_degree = options.get("approximation_degree", 0) decomposition = synth_qft_line( num_qubits=high_level_object.num_qubits, - do_swaps=do_swaps, + do_swaps=reverse_qubits, approximation_degree=approximation_degree, ) return decomposition From 2822d5c44aaf6b69117f52358ed3849a74343333 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Fri, 5 Jul 2024 11:31:33 +0300 Subject: [PATCH 14/19] typos --- qiskit/synthesis/qft/qft_decompose_full.py | 2 +- qiskit/synthesis/qft/qft_decompose_lnn.py | 2 +- qiskit/transpiler/passes/synthesis/high_level_synthesis.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/synthesis/qft/qft_decompose_full.py b/qiskit/synthesis/qft/qft_decompose_full.py index e951a041b192..6f72f8134d96 100644 --- a/qiskit/synthesis/qft/qft_decompose_full.py +++ b/qiskit/synthesis/qft/qft_decompose_full.py @@ -42,7 +42,7 @@ def synth_qft_full( 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 impossible to implement the QFT approximately by ignoring + 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. diff --git a/qiskit/synthesis/qft/qft_decompose_lnn.py b/qiskit/synthesis/qft/qft_decompose_lnn.py index 359554b62db6..f1a0876a0c3f 100644 --- a/qiskit/synthesis/qft/qft_decompose_lnn.py +++ b/qiskit/synthesis/qft/qft_decompose_lnn.py @@ -36,7 +36,7 @@ def synth_qft_line( Args: num_qubits: The number of qubits on which the Quantum Fourier Transform acts. approximation_degree: The degree of approximation (0 for no approximation). - It is impossible to implement the QFT approximately by ignoring + 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. diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index eacabb592ae6..dd9e158f9810 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -905,7 +905,7 @@ class QFTSynthesisFull(HighLevelSynthesisPlugin): include a layer of swap gates at the end of the synthesized circuit, which can 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 to implement the QFT approximately by ignoring + 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. @@ -952,7 +952,7 @@ class QFTSynthesisLine(HighLevelSynthesisPlugin): include a layer of swap gates at the end of the synthesized circuit, which can 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 to implement the QFT approximately by ignoring + 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. From 6feb4fc027e461a17b4fab68776b18bbf13603f3 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Sun, 7 Jul 2024 12:29:59 +0300 Subject: [PATCH 15/19] adding synth_qft_full to __init__ --- qiskit/synthesis/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index b46c8eac5459..cfe5f0b304cb 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -91,6 +91,7 @@ ====================== .. autofunction:: synth_qft_line +.. autofunction:: synth_qft_full Unitary Synthesis ================= @@ -162,7 +163,7 @@ synth_circuit_from_stabilizers, ) from .discrete_basis import SolovayKitaevDecomposition, generate_basic_approximations -from .qft import synth_qft_line +from .qft import synth_qft_line, synth_qft_full from .unitary.qsd import qs_decomposition from .unitary import aqc from .one_qubit import OneQubitEulerDecomposer From a0ea5ba0b3ed534a43fbea679f6090ea834f95f2 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 9 Jul 2024 11:12:48 +0300 Subject: [PATCH 16/19] round of suggestions from code review --- qiskit/circuit/library/__init__.py | 3 +- .../circuit/library/basis_change/__init__.py | 2 +- qiskit/circuit/library/basis_change/qft.py | 46 +++++++++++++--- .../library/generalized_gates/__init__.py | 1 - .../circuit/library/generalized_gates/qft.py | 53 ------------------- qiskit/synthesis/qft/qft_decompose_full.py | 4 +- 6 files changed, 44 insertions(+), 65 deletions(-) delete mode 100644 qiskit/circuit/library/generalized_gates/qft.py diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index 0436f1248fef..6f9038fcd71c 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -515,7 +515,6 @@ UCRXGate, UCRYGate, UCRZGate, - QFTGate, ) from .pauli_evolution import PauliEvolutionGate from .hamiltonian_gate import HamiltonianGate @@ -525,7 +524,7 @@ XOR, InnerProduct, ) -from .basis_change import QFT +from .basis_change import QFT, QFTGate from .arithmetic import ( FunctionalPauliRotations, LinearPauliRotations, diff --git a/qiskit/circuit/library/basis_change/__init__.py b/qiskit/circuit/library/basis_change/__init__.py index c2b8896608d4..16b7e53cc2d3 100644 --- a/qiskit/circuit/library/basis_change/__init__.py +++ b/qiskit/circuit/library/basis_change/__init__.py @@ -12,4 +12,4 @@ """The basis change circuits.""" -from .qft import QFT +from .qft import QFT, QFTGate diff --git a/qiskit/circuit/library/basis_change/qft.py b/qiskit/circuit/library/basis_change/qft.py index a8816ad470a2..2ec6dd69cb79 100644 --- a/qiskit/circuit/library/basis_change/qft.py +++ b/qiskit/circuit/library/basis_change/qft.py @@ -10,14 +10,13 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Quantum Fourier Transform Circuit.""" +"""Define a Quantum Fourier Transform circuit (QFT) and a native gate (QFTGate).""" -from typing import Optional +from __future__ import annotations import warnings import numpy as np -from qiskit.circuit import QuantumCircuit, QuantumRegister, CircuitInstruction - +from qiskit.circuit.quantumcircuit import QuantumCircuit, QuantumRegister, CircuitInstruction, Gate from ..blueprintcircuit import BlueprintCircuit @@ -75,12 +74,12 @@ class QFT(BlueprintCircuit): def __init__( self, - num_qubits: Optional[int] = None, + num_qubits: int | None = None, approximation_degree: int = 0, do_swaps: bool = True, inverse: bool = False, insert_barriers: bool = False, - name: Optional[str] = None, + name: str | None = None, ) -> None: """Construct a new QFT circuit. @@ -293,3 +292,38 @@ def _build(self) -> None: wrapped = circuit.to_instruction() if self.insert_barriers else circuit.to_gate() self.compose(wrapped, qubits=self.qubits, inplace=True) + + +class QFTGate(Gate): + r"""Quantum Fourier Transform Gate. + + 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, + ): + """ + 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): + """Return a numpy array for the QFTGate.""" + 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)) + + def _define(self): + """Provide a specific decomposition of the QFTGate into a quantum circuit.""" + from qiskit.synthesis.qft import synth_qft_full + + self.definition = synth_qft_full(num_qubits=self.num_qubits) diff --git a/qiskit/circuit/library/generalized_gates/__init__.py b/qiskit/circuit/library/generalized_gates/__init__.py index c8ddb3fff8c0..6a704cdef286 100644 --- a/qiskit/circuit/library/generalized_gates/__init__.py +++ b/qiskit/circuit/library/generalized_gates/__init__.py @@ -28,4 +28,3 @@ from .ucrz import UCRZGate from .unitary import UnitaryGate from .mcg_up_to_diagonal import MCGupDiag -from .qft import QFTGate diff --git a/qiskit/circuit/library/generalized_gates/qft.py b/qiskit/circuit/library/generalized_gates/qft.py deleted file mode 100644 index 459eea44dfd2..000000000000 --- a/qiskit/circuit/library/generalized_gates/qft.py +++ /dev/null @@ -1,53 +0,0 @@ -# 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, - ): - """Construct a new QFT gate. - - 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): - """Return a numpy array for the QFTGate.""" - 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)) - - def _define(self): - """Provide a specific decomposition of the QFTGate into a quantum circuit.""" - from qiskit.synthesis.qft import synth_qft_full - - self.definition = synth_qft_full(num_qubits=self.num_qubits) diff --git a/qiskit/synthesis/qft/qft_decompose_full.py b/qiskit/synthesis/qft/qft_decompose_full.py index 6f72f8134d96..9038ea6589c3 100644 --- a/qiskit/synthesis/qft/qft_decompose_full.py +++ b/qiskit/synthesis/qft/qft_decompose_full.py @@ -13,7 +13,7 @@ Circuit synthesis for a QFT circuit. """ -from typing import Optional +from __future__ import annotations import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit @@ -24,7 +24,7 @@ def synth_qft_full( approximation_degree: int = 0, insert_barriers: bool = False, inverse: bool = False, - name: Optional[str] = None, + name: str | None = None, ) -> QuantumCircuit: """Construct a circuit for the Quantum Fourier Transform using all-to-all connectivity. From b230c589886489be2536f0a4f6ab97b01a2d3251 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 9 Jul 2024 11:39:53 +0300 Subject: [PATCH 17/19] another round of code review suggestions --- .../passes/synthesis/high_level_synthesis.py | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index dd9e158f9810..b4eafaeebefd 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -900,19 +900,28 @@ class QFTSynthesisFull(HighLevelSynthesisPlugin): The plugin supports the following additional options: - * reverse_qubits (bool): Whether to synthesize the "QFT" operation (default) or the - "QFT-with-reversal" operation (non-default). Some implementation of the ``QFTGate`` - include a layer of swap gates at the end of the synthesized circuit, which can in - principle be dropped if the ``QFTGate`` itself is the last gate in the circuit. + * reverse_qubits (bool): Whether to synthesize the "QFT" operation (if ``False``, + which is the default) or the "QFT-with-reversal" operation (if ``True``). + Some implementation of the ``QFTGate`` include a layer of swap gates at the end + of the synthesized circuit, which can 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 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 + in more detail in [1] or [2]. https://arxiv.org/abs/quant-ph/0403071. * 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. + References: + 1. Adriano Barenco, Artur Ekert, Kalle-Antti Suominen, and Päivi Törmä, + *Approximate Quantum Fourier Transform and Decoherence*, + Physical Review A (1996). + `arXiv:quant-ph/9601018 [quant-ph] `_ + 2. Donny Cheung, + *Improved Bounds for the Approximate QFT* (2004), + `arXiv:quant-ph/0403071 [quant-ph] `_ """ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): @@ -922,7 +931,7 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** "The synthesis plugin 'qft.full` only applies to objects of type QFTGate." ) - reverse_qubits = options.get("reverse_qubits", True) + reverse_qubits = options.get("reverse_qubits", False) approximation_degree = options.get("approximation_degree", 0) insert_barriers = options.get("insert_barriers", False) inverse = options.get("inverse", False) @@ -930,7 +939,7 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** decomposition = synth_qft_full( num_qubits=high_level_object.num_qubits, - do_swaps=reverse_qubits, + do_swaps=not reverse_qubits, approximation_degree=approximation_degree, insert_barriers=insert_barriers, inverse=inverse, @@ -947,15 +956,24 @@ class QFTSynthesisLine(HighLevelSynthesisPlugin): The plugin supports the following additional options: - * reverse_qubits (bool): Whether to synthesize the "QFT" operation (default) or the - "QFT-with-reversal" operation (non-default). Some implementation of the ``QFTGate`` - include a layer of swap gates at the end of the synthesized circuit, which can in - principle be dropped if the ``QFTGate`` itself is the last gate in the circuit. + * reverse_qubits (bool): Whether to synthesize the "QFT" operation (if ``False``, + which is the default) or the "QFT-with-reversal" operation (if ``True``). + Some implementation of the ``QFTGate`` include a layer of swap gates at the end + of the synthesized circuit, which can 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 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. + in more detail in [1] or [2]. + + References: + 1. Adriano Barenco, Artur Ekert, Kalle-Antti Suominen, and Päivi Törmä, + *Approximate Quantum Fourier Transform and Decoherence*, + Physical Review A (1996). + `arXiv:quant-ph/9601018 [quant-ph] `_ + 2. Donny Cheung, + *Improved Bounds for the Approximate QFT* (2004), + `arXiv:quant-ph/0403071 [quant-ph] `_ """ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): @@ -965,12 +983,12 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** "The synthesis plugin 'qft.line` only applies to objects of type QFTGate." ) - reverse_qubits = options.get("reverse_qubits", True) + reverse_qubits = options.get("reverse_qubits", False) approximation_degree = options.get("approximation_degree", 0) decomposition = synth_qft_line( num_qubits=high_level_object.num_qubits, - do_swaps=reverse_qubits, + do_swaps=not reverse_qubits, approximation_degree=approximation_degree, ) return decomposition From 8806c277120894233e29bb330bc50da40f1bcdbd Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 9 Jul 2024 14:07:43 +0300 Subject: [PATCH 18/19] fixes --- qiskit/circuit/library/__init__.py | 2 +- qiskit/transpiler/passes/synthesis/high_level_synthesis.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index 6f9038fcd71c..39266cf4ca17 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -196,7 +196,6 @@ UCRXGate UCRYGate UCRZGate - QFTGate Boolean Logic Circuits ====================== @@ -227,6 +226,7 @@ :template: autosummary/class_no_inherited_members.rst QFT + QFTGate Arithmetic Circuits =================== diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index b4eafaeebefd..b03aaf831f03 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -909,7 +909,6 @@ class QFTSynthesisFull(HighLevelSynthesisPlugin): 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 [1] or [2]. - https://arxiv.org/abs/quant-ph/0403071. * 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. From 4ae4c9eef02e2e566a1cac87c9b55c068881cf68 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Tue, 9 Jul 2024 16:31:49 +0300 Subject: [PATCH 19/19] also adding QFTGate plugins to the docs --- .../passes/synthesis/high_level_synthesis.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index b03aaf831f03..f1de2b8f2136 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -131,6 +131,32 @@ ACGSynthesisPermutation KMSSynthesisPermutation TokenSwapperSynthesisPermutation + + +QFT Synthesis +''''''''''''' + +.. list-table:: Plugins for :class:`.QFTGate` (key = ``"qft"``) + :header-rows: 1 + + * - Plugin name + - Plugin class + - Targeted connectivity + * - ``"full"`` + - :class:`~.QFTSynthesisFull` + - all-to-all + * - ``"line"`` + - :class:`~.QFTSynthesisLine` + - linear + * - ``"default"`` + - :class:`~.QFTSynthesisFull` + - all-to-all + +.. autosummary:: + :toctree: ../stubs/ + + QFTSynthesisFull + QFTSynthesisLine """ from typing import Optional, Union, List, Tuple, Callable