diff --git a/pyproject.toml b/pyproject.toml index 2f62557aa15..b1f7b039e40 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" "permutation.token_swapper" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:TokenSwapperSynthesisPermutation" [project.entry-points."qiskit.transpiler.init"] diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index a9ae005d982..39266cf4ca1 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -226,6 +226,7 @@ :template: autosummary/class_no_inherited_members.rst QFT + QFTGate Arithmetic Circuits =================== @@ -523,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 c2b8896608d..16b7e53cc2d 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 a8816ad470a..2ec6dd69cb7 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/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index 0e2045d5be5..142639a4e16 100644 --- a/qiskit/qpy/binary_io/circuits.py +++ b/qiskit/qpy/binary_io/circuits.py @@ -397,6 +397,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/__init__.py b/qiskit/synthesis/__init__.py index b46c8eac545..cfe5f0b304c 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 diff --git a/qiskit/synthesis/qft/__init__.py b/qiskit/synthesis/qft/__init__.py index 99bd2f7da9b..8140bf4a74e 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 00000000000..9038ea6589c --- /dev/null +++ b/qiskit/synthesis/qft/qft_decompose_full.py @@ -0,0 +1,79 @@ +# 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 __future__ import annotations +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: str | None = None, +) -> QuantumCircuit: + """Construct a circuit for the Quantum Fourier Transform using all-to-all connectivity. + + .. note:: + + With the default value of ``do_swaps = True``, this synthesis algorithm creates a + circuit that faithfully implements the QFT operation. This circuit contains a sequence + of swap gates at the end, corresponding to reversing the order of its output qubits. + In some applications this reversal permutation can be avoided. Setting ``do_swaps = False`` + creates a circuit without this reversal permutation, at the expense that this circuit + implements the "QFT-with-reversal" instead of QFT. Alternatively, the + :class:`~.ElidePermutations` transpiler pass is able to remove these swap gates. + + Args: + num_qubits: The number of qubits on which the Quantum Fourier Transform acts. + do_swaps: Whether to synthesize the "QFT" or the "QFT-with-reversal" operation. + approximation_degree: The degree of approximation (0 for no approximation). + It is possible to implement the QFT approximately by ignoring + controlled-phase rotations with the angle beneath a threshold. This is discussed + in more detail in https://arxiv.org/abs/quant-ph/9601018 or + https://arxiv.org/abs/quant-ph/0403071. + insert_barriers: If ``True``, barriers are inserted for improved visualization. + inverse: If ``True``, the inverse Quantum Fourier Transform is constructed. + name: The name of the circuit. + + Returns: + A circuit implementing the QFT operation. + + """ + + 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/synthesis/qft/qft_decompose_lnn.py b/qiskit/synthesis/qft/qft_decompose_lnn.py index a54be481f51..f1a0876a0c3 100644 --- a/qiskit/synthesis/qft/qft_decompose_lnn.py +++ b/qiskit/synthesis/qft/qft_decompose_lnn.py @@ -21,21 +21,29 @@ 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. + 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. + 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, diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index bbc98662105..f1de2b8f213 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 @@ -157,6 +183,7 @@ ControlModifier, PowerModifier, ) +from qiskit.circuit.library import QFTGate from qiskit.synthesis.clifford import ( synth_clifford_full, synth_clifford_layers, @@ -176,6 +203,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 @@ -887,6 +918,107 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** 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`. + + The plugin supports the following additional options: + + * 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 [1] or [2]. + * 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): + """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." + ) + + 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) + name = options.get("name", None) + + decomposition = synth_qft_full( + num_qubits=high_level_object.num_qubits, + do_swaps=not reverse_qubits, + 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: + + * 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 [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): + """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." + ) + + 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=not reverse_qubits, + approximation_degree=approximation_degree, + ) + return decomposition + + class TokenSwapperSynthesisPermutation(HighLevelSynthesisPlugin): """The permutation synthesis plugin based on the token swapper algorithm. @@ -917,7 +1049,6 @@ class TokenSwapperSynthesisPermutation(HighLevelSynthesisPlugin): For more details on the token swapper algorithm, see to the paper: `arXiv:1902.09102 `__. - """ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): diff --git a/releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml b/releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml new file mode 100644 index 00000000000..46b60c8b702 --- /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. diff --git a/test/python/circuit/library/test_qft.py b/test/python/circuit/library/test_qft.py index 078b5af04ea..1f5c9715dd7 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,15 +12,19 @@ """Test library of QFT circuits.""" +import io + import unittest import warnings import numpy as np from ddt import ddt, data, unpack 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 from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -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 c5df22a0e8a..950b0c478ed 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -294,6 +294,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 diff --git a/test/python/synthesis/test_qft_synthesis.py b/test/python/synthesis/test_qft_synthesis.py index d208d8c9fcd..6cfe111ae07 100644 --- a/test/python/synthesis/test_qft_synthesis.py +++ b/test/python/synthesis/test_qft_synthesis.py @@ -15,10 +15,10 @@ import unittest from test import combine -from ddt import ddt +from ddt import ddt, data 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 from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -57,5 +57,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 a76ab08d90e..fd6ae6a01cd 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, @@ -40,19 +41,21 @@ UGate, CU3Gate, CU1Gate, + QFTGate, ) from qiskit.circuit.library.generalized_gates import LinearFunction from qiskit.quantum_info import Clifford from qiskit.synthesis.linear import random_invertible_binary_matrix -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 from qiskit.transpiler import PassManager, TranspilerError, CouplingMap, Target 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 qiskit.circuit.annotated_operation import ( AnnotatedOperation, @@ -2098,5 +2101,41 @@ def test_leave_store_alone_with_target(self): self.assertEqual(pass_(qc), expected) +@ddt +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) + + @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()