From fa3d6df04f57d6a5bcc658a7f1a6e5a99984b949 Mon Sep 17 00:00:00 2001 From: Alexander Ivrii Date: Wed, 10 Jul 2024 11:27:55 +0300 Subject: [PATCH 01/12] Adding QFT gate to natively reason about Quantum Fourier Transforms (#11463) * initial commit * release notes * fixing synthesis plugin options * finalize merge conflicts * fixing default option values for qft plugins' * additional tests for qft plugins * renaming QftGate to QFTGate * Also renaming Qft to QFT in synthesis method names * appplying Jake's suggestion from code review * inlining _basic_definition into _define * docstring improvements * more docstring improvements * renaming do_swaps to reverse_qubits in the new code * typos * adding synth_qft_full to __init__ * round of suggestions from code review * another round of code review suggestions * fixes * also adding QFTGate plugins to the docs --- pyproject.toml | 3 + qiskit/circuit/library/__init__.py | 3 +- .../circuit/library/basis_change/__init__.py | 2 +- qiskit/circuit/library/basis_change/qft.py | 46 +++++- qiskit/qpy/binary_io/circuits.py | 2 + qiskit/synthesis/__init__.py | 3 +- qiskit/synthesis/qft/__init__.py | 1 + qiskit/synthesis/qft/qft_decompose_full.py | 79 +++++++++++ qiskit/synthesis/qft/qft_decompose_lnn.py | 26 ++-- .../passes/synthesis/high_level_synthesis.py | 133 +++++++++++++++++- .../notes/add-qft-gate-fd4e08f6721a9da4.yaml | 22 +++ 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 | 47 ++++++- 15 files changed, 510 insertions(+), 28 deletions(-) create mode 100644 qiskit/synthesis/qft/qft_decompose_full.py create mode 100644 releasenotes/notes/add-qft-gate-fd4e08f6721a9da4.yaml diff --git a/pyproject.toml b/pyproject.toml index 2f62557aa15b..b1f7b039e407 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 a9ae005d982d..39266cf4ca17 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 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/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index 0e2045d5be5d..142639a4e164 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 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 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..9038ea6589c3 --- /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 a54be481f51b..f1a0876a0c3f 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 bbc986621050..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 @@ -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 000000000000..46b60c8b7025 --- /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 078b5af04ea9..1f5c9715dd7a 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 c5df22a0e8a1..950b0c478ed8 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 d208d8c9fcd2..6cfe111ae07f 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 a76ab08d90e1..fd6ae6a01cda 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() From 1e8205e43d72cf2a186a8013072aa6b2c5cd1196 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 10 Jul 2024 09:48:08 -0400 Subject: [PATCH 02/12] Avoid Python op creation in BasisTranslator (#12705) This commit updates the BasisTranslator transpiler pass. It builds off of #12692 and #12701 to adjust access patterns in the python transpiler path to avoid eagerly creating a Python space operation object. The goal of this PR is to mitigate the performance regression introduced by the extra conversion cost of #12459 on the BasisTranslator. --- crates/circuit/src/dag_node.rs | 5 ++ .../passes/basis/basis_translator.py | 61 +++++++++++-------- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/crates/circuit/src/dag_node.rs b/crates/circuit/src/dag_node.rs index 55a40c83dc39..f347ec72c811 100644 --- a/crates/circuit/src/dag_node.rs +++ b/crates/circuit/src/dag_node.rs @@ -270,6 +270,11 @@ impl DAGOpNode { self.instruction.params.to_object(py) } + #[setter] + fn set_params(&mut self, val: smallvec::SmallVec<[crate::operations::Param; 3]>) { + self.instruction.params = val; + } + pub fn is_parameterized(&self) -> bool { self.instruction.is_parameterized() } diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index f2e752dd94f5..e69887a3b940 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -30,11 +30,13 @@ QuantumCircuit, ParameterExpression, ) -from qiskit.dagcircuit import DAGCircuit +from qiskit.dagcircuit import DAGCircuit, DAGOpNode from qiskit.converters import circuit_to_dag, dag_to_circuit from qiskit.circuit.equivalence import Key, NodeData from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError +from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES +from qiskit._accelerate.circuit import StandardGate logger = logging.getLogger(__name__) @@ -253,7 +255,7 @@ def apply_translation(dag, wire_map): node_qargs = tuple(wire_map[bit] for bit in node.qargs) qubit_set = frozenset(node_qargs) if node.name in target_basis or len(node.qargs) < self._min_qubits: - if isinstance(node.op, ControlFlowOp): + if node.name in CONTROL_FLOW_OP_NAMES: flow_blocks = [] for block in node.op.blocks: dag_block = circuit_to_dag(block) @@ -281,7 +283,7 @@ def apply_translation(dag, wire_map): continue if qubit_set in extra_instr_map: self._replace_node(dag, node, extra_instr_map[qubit_set]) - elif (node.op.name, node.op.num_qubits) in instr_map: + elif (node.name, node.num_qubits) in instr_map: self._replace_node(dag, node, instr_map) else: raise TranspilerError(f"BasisTranslator did not map {node.name}.") @@ -298,20 +300,29 @@ def apply_translation(dag, wire_map): return dag def _replace_node(self, dag, node, instr_map): - target_params, target_dag = instr_map[node.op.name, node.op.num_qubits] - if len(node.op.params) != len(target_params): + target_params, target_dag = instr_map[node.name, node.num_qubits] + if len(node.params) != len(target_params): raise TranspilerError( "Translation num_params not equal to op num_params." - f"Op: {node.op.params} {node.op.name} Translation: {target_params}\n{target_dag}" + f"Op: {node.params} {node.name} Translation: {target_params}\n{target_dag}" ) - if node.op.params: - parameter_map = dict(zip(target_params, node.op.params)) + if node.params: + parameter_map = dict(zip(target_params, node.params)) bound_target_dag = target_dag.copy_empty_like() for inner_node in target_dag.topological_op_nodes(): - if any(isinstance(x, ParameterExpression) for x in inner_node.op.params): + new_op = inner_node._raw_op + if not isinstance(inner_node._raw_op, StandardGate): new_op = inner_node.op.copy() + new_node = DAGOpNode( + new_op, + qargs=inner_node.qargs, + cargs=inner_node.cargs, + params=inner_node.params, + dag=bound_target_dag, + ) + if any(isinstance(x, ParameterExpression) for x in inner_node.params): new_params = [] - for param in new_op.params: + for param in new_node.params: if not isinstance(param, ParameterExpression): new_params.append(param) else: @@ -325,10 +336,10 @@ def _replace_node(self, dag, node, instr_map): if not new_value.parameters: new_value = new_value.numeric() new_params.append(new_value) - new_op.params = new_params - else: - new_op = inner_node.op - bound_target_dag.apply_operation_back(new_op, inner_node.qargs, inner_node.cargs) + new_node.params = new_params + if not isinstance(new_op, StandardGate): + new_op.params = new_params + bound_target_dag._apply_op_node_back(new_node) if isinstance(target_dag.global_phase, ParameterExpression): old_phase = target_dag.global_phase bind_dict = {x: parameter_map[x] for x in old_phase.parameters} @@ -353,7 +364,7 @@ def _replace_node(self, dag, node, instr_map): dag_op = bound_target_dag.op_nodes()[0].op # dag_op may be the same instance as other ops in the dag, # so if there is a condition, need to copy - if getattr(node.op, "condition", None): + if getattr(node, "condition", None): dag_op = dag_op.copy() dag.substitute_node(node, dag_op, inplace=True) @@ -370,8 +381,8 @@ def _extract_basis(self, circuit): def _(self, dag: DAGCircuit): for node in dag.op_nodes(): if not dag.has_calibration_for(node) and len(node.qargs) >= self._min_qubits: - yield (node.name, node.op.num_qubits) - if isinstance(node.op, ControlFlowOp): + yield (node.name, node.num_qubits) + if node.name in CONTROL_FLOW_OP_NAMES: for block in node.op.blocks: yield from self._extract_basis(block) @@ -412,10 +423,10 @@ def _extract_basis_target( frozenset(qargs).issuperset(incomplete_qargs) for incomplete_qargs in self._qargs_with_non_global_operation ): - qargs_local_source_basis[frozenset(qargs)].add((node.name, node.op.num_qubits)) + qargs_local_source_basis[frozenset(qargs)].add((node.name, node.num_qubits)) else: - source_basis.add((node.name, node.op.num_qubits)) - if isinstance(node.op, ControlFlowOp): + source_basis.add((node.name, node.num_qubits)) + if node.name in CONTROL_FLOW_OP_NAMES: for block in node.op.blocks: block_dag = circuit_to_dag(block) source_basis, qargs_local_source_basis = self._extract_basis_target( @@ -628,7 +639,7 @@ def _compose_transforms(basis_transforms, source_basis, source_dag): doomed_nodes = [ node for node in dag.op_nodes() - if (node.op.name, node.op.num_qubits) == (gate_name, gate_num_qubits) + if (node.name, node.num_qubits) == (gate_name, gate_num_qubits) ] if doomed_nodes and logger.isEnabledFor(logging.DEBUG): @@ -642,9 +653,7 @@ def _compose_transforms(basis_transforms, source_basis, source_dag): for node in doomed_nodes: - replacement = equiv.assign_parameters( - dict(zip_longest(equiv_params, node.op.params)) - ) + replacement = equiv.assign_parameters(dict(zip_longest(equiv_params, node.params))) replacement_dag = circuit_to_dag(replacement) @@ -666,8 +675,8 @@ def _get_example_gates(source_dag): def recurse(dag, example_gates=None): example_gates = example_gates or {} for node in dag.op_nodes(): - example_gates[(node.op.name, node.op.num_qubits)] = node.op - if isinstance(node.op, ControlFlowOp): + example_gates[(node.name, node.num_qubits)] = node + if node.name in CONTROL_FLOW_OP_NAMES: for block in node.op.blocks: example_gates = recurse(circuit_to_dag(block), example_gates) return example_gates From 61805c8e166800687aa64ef3bf8d7da7540f5f2f Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 11 Jul 2024 12:36:14 +0200 Subject: [PATCH 03/12] Some formatting issues in Pauli docs (#12753) * some formatting issues in Pauli docs * Update qiskit/quantum_info/operators/symplectic/pauli.py Co-authored-by: Jake Lishman --------- Co-authored-by: Jake Lishman --- .../operators/symplectic/pauli.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/qiskit/quantum_info/operators/symplectic/pauli.py b/qiskit/quantum_info/operators/symplectic/pauli.py index 867867eeb98a..44ca095f8ed6 100644 --- a/qiskit/quantum_info/operators/symplectic/pauli.py +++ b/qiskit/quantum_info/operators/symplectic/pauli.py @@ -77,7 +77,7 @@ class Pauli(BasePauli): An :math:`n`-qubit Pauli may be represented by a string consisting of :math:`n` characters from ``['I', 'X', 'Y', 'Z']``, and optionally phase - coefficient in :math:`['', '-i', '-', 'i']`. For example: ``XYZ`` or + coefficient in ``['', '-i', '-', 'i']``. For example: ``'XYZ'`` or ``'-iZIZ'``. In the string representation qubit-0 corresponds to the right-most @@ -160,21 +160,23 @@ class initialization (``Pauli('-iXYZ')``). A ``Pauli`` object can be _CANONICAL_PHASE_LABEL = {"": 0, "-i": 1, "-": 2, "i": 3} def __init__(self, data: str | tuple | Pauli | ScalarOp | QuantumCircuit | None = None): - """Initialize the Pauli. + r"""Initialize the Pauli. When using the symplectic array input data both z and x arguments must be provided, however the first (z) argument can be used alone for string - label, Pauli operator, or ScalarOp input data. + label, Pauli operator, or :class:`.ScalarOp` input data. Args: data (str or tuple or Pauli or ScalarOp): input data for Pauli. If input is - a tuple it must be of the form ``(z, x)`` or (z, x, phase)`` where - ``z`` and ``x`` are boolean Numpy arrays, and phase is an integer from Z_4. + a tuple it must be of the form ``(z, x)`` or ``(z, x, phase)`` where + ``z`` and ``x`` are boolean Numpy arrays, and phase is an integer from + :math:`\mathbb{Z}_4`. If input is a string, it must be a concatenation of a phase and a Pauli string - (e.g. 'XYZ', '-iZIZ') where a phase string is a combination of at most three - characters from ['+', '-', ''], ['1', ''], and ['i', 'j', ''] in this order, - e.g. '', '-1j' while a Pauli string is 1 or more characters of 'I', 'X', 'Y' or 'Z', - e.g. 'Z', 'XIYY'. + (e.g. ``'XYZ', '-iZIZ'``) where a phase string is a combination of at most three + characters from ``['+', '-', '']``, ``['1', '']``, and ``['i', 'j', '']`` in this order, + e.g. ``''``, ``'-1j'`` while a Pauli string is 1 or more + characters of ``'I'``, ``'X'``, ``'Y'``, or ``'Z'``, + e.g. ``'Z'``, ``'XIYY'``. Raises: QiskitError: if input array is invalid shape. From 4c9ca6eddaec717b85a9b49798c2fe3f09a44fbc Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 11 Jul 2024 13:27:56 +0200 Subject: [PATCH 04/12] Randomized errors are failing because Aer uses deprecated functionality (#12722) --- test/randomized/test_transpiler_equivalence.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/randomized/test_transpiler_equivalence.py b/test/randomized/test_transpiler_equivalence.py index 3bd09d89348b..76bbf9ef08e3 100644 --- a/test/randomized/test_transpiler_equivalence.py +++ b/test/randomized/test_transpiler_equivalence.py @@ -48,6 +48,8 @@ """ import os +import warnings + from test.utils.base import dicts_almost_equal from math import pi @@ -292,7 +294,16 @@ def equivalent_transpile(self, kwargs): # Note that there's no transpilation here, which is why the gates are limited to only ones # that Aer supports natively. - aer_counts = self.backend.run(self.qc, shots=shots).result().get_counts() + with warnings.catch_warnings(): + # Safe to remove once https://github.com/Qiskit/qiskit-aer/pull/2179 is in a release version + # of Aer. + warnings.filterwarnings( + "default", + category=DeprecationWarning, + module="qiskit_aer", + message="Treating CircuitInstruction as an iterable", + ) + aer_counts = self.backend.run(self.qc, shots=shots).result().get_counts() try: xpiled_qc = transpile(self.qc, **kwargs) From 99ae318d89337fdc04673b47435680141ecfc06a Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 11 Jul 2024 07:58:47 -0400 Subject: [PATCH 05/12] Use rustworkx 0.15.0 features in DAGCircuit.remove_op_node (#12756) This commit updates the minimum rustworkx version to 0.15.0 to pull in the new PyDiGraph.remove_node_retain_edges_by_id() method introduced in that release. This new function is used for the DAGCircuit.remove_op_node() method instead of the PyDiGraph.remove_node_retain_edges() function. This new method has much better scaling characteristics and should improve the performance characteristics of removing very wide operations from a DAGCircuit. Fixes #11677 Part of #12156 --- qiskit/dagcircuit/dagcircuit.py | 4 +--- .../update-rustworkx-min-version-4f07aacfebccae80.yaml | 7 +++++++ requirements.txt | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/update-rustworkx-min-version-4f07aacfebccae80.yaml diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 5d6739d72198..796e0bc2b700 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -1986,9 +1986,7 @@ def remove_op_node(self, node): "node type was wrongly provided." ) - self._multi_graph.remove_node_retain_edges( - node._node_id, use_outgoing=False, condition=lambda edge1, edge2: edge1 == edge2 - ) + self._multi_graph.remove_node_retain_edges_by_id(node._node_id) self._decrement_op(node.name) def remove_ancestors_of(self, node): diff --git a/releasenotes/notes/update-rustworkx-min-version-4f07aacfebccae80.yaml b/releasenotes/notes/update-rustworkx-min-version-4f07aacfebccae80.yaml new file mode 100644 index 000000000000..7661f82aee8b --- /dev/null +++ b/releasenotes/notes/update-rustworkx-min-version-4f07aacfebccae80.yaml @@ -0,0 +1,7 @@ +--- +upgrade_misc: + - | + The minimum version of rustworkx required to run this release has been + increased from 0.14.0 to 0.15.0. This is required because Qiskit is now + using new functionality added in the rustworkx 0.15.0 release which + improves performance. diff --git a/requirements.txt b/requirements.txt index 539f9587994d..2dd5e49e2b3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -rustworkx>=0.14.0 +rustworkx>=0.15.0 numpy>=1.17,<3 scipy>=1.5 sympy>=1.3 From a306cb67c8fbaab5e3329fa40613d22980aaea29 Mon Sep 17 00:00:00 2001 From: Sebastian Brandhofer <148463728+sbrandhsn@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:40:01 +0200 Subject: [PATCH 06/12] Barebone generic backend options (#12747) * Update generic_backend_v2.py * reno * Update barebone-backend-option-675c86df4382a443.yaml * ... * suggestions from code review * Update barebone-backend-option-675c86df4382a443.yaml * tests, typo --- .../fake_provider/generic_backend_v2.py | 63 +++++++++++++------ ...ebone-backend-option-675c86df4382a443.yaml | 8 +++ .../fake_provider/test_generic_backend_v2.py | 20 ++++++ 3 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 releasenotes/notes/barebone-backend-option-675c86df4382a443.yaml diff --git a/qiskit/providers/fake_provider/generic_backend_v2.py b/qiskit/providers/fake_provider/generic_backend_v2.py index 214754080e57..0da1df7eab65 100644 --- a/qiskit/providers/fake_provider/generic_backend_v2.py +++ b/qiskit/providers/fake_provider/generic_backend_v2.py @@ -112,6 +112,8 @@ def __init__( calibrate_instructions: bool | InstructionScheduleMap | None = None, dtm: float | None = None, seed: int | None = None, + pulse_channels: bool = True, + noise_info: bool = True, ): """ Args: @@ -159,6 +161,10 @@ def __init__( None by default. seed: Optional seed for generation of default values. + + pulse_channels: If true, sets default pulse channel information on the backend. + + noise_info: If true, associates gates and qubits with default noise information. """ super().__init__( @@ -175,6 +181,10 @@ def __init__( self._control_flow = control_flow self._calibrate_instructions = calibrate_instructions self._supported_gates = get_standard_gate_name_mapping() + self._noise_info = noise_info + + if calibrate_instructions and not noise_info: + raise QiskitError("Must set parameter noise_info when calibrating instructions.") if coupling_map is None: self._coupling_map = CouplingMap().from_full(num_qubits) @@ -198,7 +208,10 @@ def __init__( self._basis_gates.append(name) self._build_generic_target() - self._build_default_channels() + if pulse_channels: + self._build_default_channels() + else: + self.channels_map = {} @property def target(self): @@ -340,22 +353,31 @@ def _build_generic_target(self): """ # the qubit properties are sampled from default ranges properties = _QUBIT_PROPERTIES - self._target = Target( - description=f"Generic Target with {self._num_qubits} qubits", - num_qubits=self._num_qubits, - dt=properties["dt"], - qubit_properties=[ - QubitProperties( - t1=self._rng.uniform(properties["t1"][0], properties["t1"][1]), - t2=self._rng.uniform(properties["t2"][0], properties["t2"][1]), - frequency=self._rng.uniform( - properties["frequency"][0], properties["frequency"][1] - ), - ) - for _ in range(self._num_qubits) - ], - concurrent_measurements=[list(range(self._num_qubits))], - ) + if not self._noise_info: + self._target = Target( + description=f"Generic Target with {self._num_qubits} qubits", + num_qubits=self._num_qubits, + dt=properties["dt"], + qubit_properties=None, + concurrent_measurements=[list(range(self._num_qubits))], + ) + else: + self._target = Target( + description=f"Generic Target with {self._num_qubits} qubits", + num_qubits=self._num_qubits, + dt=properties["dt"], + qubit_properties=[ + QubitProperties( + t1=self._rng.uniform(properties["t1"][0], properties["t1"][1]), + t2=self._rng.uniform(properties["t2"][0], properties["t2"][1]), + frequency=self._rng.uniform( + properties["frequency"][0], properties["frequency"][1] + ), + ) + for _ in range(self._num_qubits) + ], + concurrent_measurements=[list(range(self._num_qubits))], + ) # Generate instruction schedule map with calibrations to add to target. calibration_inst_map = None @@ -380,8 +402,11 @@ def _build_generic_target(self): f"Provided basis gate {name} needs more qubits than {self.num_qubits}, " f"which is the size of the backend." ) - noise_params = self._get_noise_defaults(name, gate.num_qubits) - self._add_noisy_instruction_to_target(gate, noise_params, calibration_inst_map) + if self._noise_info: + noise_params = self._get_noise_defaults(name, gate.num_qubits) + self._add_noisy_instruction_to_target(gate, noise_params, calibration_inst_map) + else: + self._target.add_instruction(gate, properties=None, name=name) if self._control_flow: self._target.add_instruction(IfElseOp, name="if_else") diff --git a/releasenotes/notes/barebone-backend-option-675c86df4382a443.yaml b/releasenotes/notes/barebone-backend-option-675c86df4382a443.yaml new file mode 100644 index 000000000000..912970845042 --- /dev/null +++ b/releasenotes/notes/barebone-backend-option-675c86df4382a443.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Added two parameters to :class:`.GenericBackendV2` to exclude error (`noise_info`) and + pulse channel information (`pulse_channels`) from the construction of the backend. These parameters + are true by default, replicating the initial default behavior of the constructor. A memory-sensitive + user may set these options to `False` to reduce the memory overhead by 40x when transpiling on large- + scale :class:`.GenericBackendV2`. diff --git a/test/python/providers/fake_provider/test_generic_backend_v2.py b/test/python/providers/fake_provider/test_generic_backend_v2.py index cd7c611b2212..33bf57cf3903 100644 --- a/test/python/providers/fake_provider/test_generic_backend_v2.py +++ b/test/python/providers/fake_provider/test_generic_backend_v2.py @@ -45,6 +45,26 @@ def test_ccx_2Q(self): with self.assertRaises(QiskitError): GenericBackendV2(num_qubits=2, basis_gates=["ccx", "id"]) + def test_calibration_no_noise_info(self): + """Test failing with a backend with calibration and no noise info""" + with self.assertRaises(QiskitError): + GenericBackendV2( + num_qubits=2, + basis_gates=["ccx", "id"], + calibrate_instructions=True, + noise_info=False, + ) + + def test_no_noise(self): + """Test no noise info when parameter is false""" + backend = GenericBackendV2(num_qubits=2, noise_info=False) + self.assertEqual(backend.target.qubit_properties, None) + + def test_no_pulse_channels(self): + """Test no/empty pulse channels when parameter is false""" + backend = GenericBackendV2(num_qubits=2, pulse_channels=False) + self.assertTrue(len(backend.channels_map) == 0) + def test_operation_names(self): """Test that target basis gates include "delay", "measure" and "reset" even if not provided by user.""" From 41267ecf5d20bf022a8d4fcb1580a88f56d1843c Mon Sep 17 00:00:00 2001 From: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:10:27 +0300 Subject: [PATCH 07/12] Add clifford gates to collect_cliffords (#12750) * add clifford gates to collect_cliffords * replace hard coded clifford names by clifford_circuit names * move import * replace hard coded clifford names in random_clifford_circuit * add release notes * add permutation to collect_clifford gate list --- qiskit/circuit/random/utils.py | 6 ++++-- .../passes/optimization/collect_cliffords.py | 21 ++++++------------- ...fix-collect-clifford-83af26d98b8c69e8.yaml | 6 ++++++ 3 files changed, 16 insertions(+), 17 deletions(-) create mode 100644 releasenotes/notes/fix-collect-clifford-83af26d98b8c69e8.yaml diff --git a/qiskit/circuit/random/utils.py b/qiskit/circuit/random/utils.py index 3bcdbeaef4ac..5caba8ca2ae0 100644 --- a/qiskit/circuit/random/utils.py +++ b/qiskit/circuit/random/utils.py @@ -18,6 +18,7 @@ from qiskit.circuit import Reset from qiskit.circuit.library import standard_gates from qiskit.circuit.exceptions import CircuitError +from qiskit.quantum_info.operators.symplectic.clifford_circuits import _BASIS_1Q, _BASIS_2Q def random_circuit( @@ -312,8 +313,9 @@ def random_clifford_circuit(num_qubits, num_gates, gates="all", seed=None): QuantumCircuit: constructed circuit """ - gates_1q = ["i", "x", "y", "z", "h", "s", "sdg", "sx", "sxdg"] - gates_2q = ["cx", "cz", "cy", "swap", "iswap", "ecr", "dcx"] + gates_1q = list(set(_BASIS_1Q.keys()) - {"v", "w", "id", "iden", "sinv"}) + gates_2q = list(_BASIS_2Q.keys()) + if gates == "all": if num_qubits == 1: gates = gates_1q diff --git a/qiskit/transpiler/passes/optimization/collect_cliffords.py b/qiskit/transpiler/passes/optimization/collect_cliffords.py index c0e9641923cd..40acd21c6855 100644 --- a/qiskit/transpiler/passes/optimization/collect_cliffords.py +++ b/qiskit/transpiler/passes/optimization/collect_cliffords.py @@ -22,6 +22,7 @@ ) from qiskit.quantum_info.operators import Clifford +from qiskit.quantum_info.operators.symplectic.clifford_circuits import _BASIS_1Q, _BASIS_2Q class CollectCliffords(CollectAndCollapse): @@ -69,21 +70,11 @@ def __init__( ) -clifford_gate_names = [ - "x", - "y", - "z", - "h", - "s", - "sdg", - "cx", - "cy", - "cz", - "swap", - "clifford", - "linear_function", - "pauli", -] +clifford_gate_names = ( + list(_BASIS_1Q.keys()) + + list(_BASIS_2Q.keys()) + + ["clifford", "linear_function", "pauli", "permutation"] +) def _is_clifford_gate(node): diff --git a/releasenotes/notes/fix-collect-clifford-83af26d98b8c69e8.yaml b/releasenotes/notes/fix-collect-clifford-83af26d98b8c69e8.yaml new file mode 100644 index 000000000000..48eac19acc9d --- /dev/null +++ b/releasenotes/notes/fix-collect-clifford-83af26d98b8c69e8.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Add more Clifford gates to the :class:`.CollectCliffords()` transpiler pass. + In particular, we have added the gates :class:`ECRGate()`, :class:`DCXGate()`, + :class:`iSWAPGate()`, :class:`SXGate()` and :class:`SXdgGate()` to this transpiler pass. From 99032fc042b5f4f57956f451578c3476a672ddb9 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Thu, 11 Jul 2024 17:40:08 +0200 Subject: [PATCH 08/12] Remove some of the entries in allow_DeprecationWarning (#12721) * remove some of the entries * fixing obscure expcetion handleing for comparison * remove all the modules * ignore "Treating CircuitInstruction as an iterable is deprecated" in Aer * remove allow_DeprecationWarning_module and revert ignore/default * revert --- qiskit/circuit/instruction.py | 7 ++++--- test/utils/base.py | 22 ---------------------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index 1b5fca3f738b..4bebf812e185 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -188,11 +188,12 @@ def __eq__(self, other): return False for self_param, other_param in zip_longest(self.params, other.params): - try: + if isinstance(self_param, numpy.ndarray): + if numpy.array_equal(self_param, other_param): + continue + else: if self_param == other_param: continue - except ValueError: - pass try: self_asarray = numpy.asarray(self_param) diff --git a/test/utils/base.py b/test/utils/base.py index e4dbd7f67072..0b02f3e58488 100644 --- a/test/utils/base.py +++ b/test/utils/base.py @@ -129,30 +129,8 @@ def setUpClass(cls): module=r"qiskit_aer(\.[a-zA-Z0-9_]+)*", ) - allow_DeprecationWarning_modules = [ - "test.python.pulse.test_builder", - "test.python.pulse.test_block", - "test.python.quantum_info.operators.symplectic.test_legacy_pauli", - "qiskit.quantum_info.operators.pauli", - "pybobyqa", - "numba", - "qiskit.utils.measurement_error_mitigation", - "qiskit.circuit.library.standard_gates.x", - "qiskit.pulse.schedule", - "qiskit.pulse.instructions.instruction", - "qiskit.pulse.instructions.play", - "qiskit.pulse.library.parametric_pulses", - "qiskit.quantum_info.operators.symplectic.pauli", - ] - for mod in allow_DeprecationWarning_modules: - warnings.filterwarnings("default", category=DeprecationWarning, module=mod) allow_DeprecationWarning_message = [ - r"elementwise comparison failed.*", - r"The jsonschema validation included in qiskit-terra.*", - r"The DerivativeBase.parameter_expression_grad method.*", r"The property ``qiskit\.circuit\.bit\.Bit\.(register|index)`` is deprecated.*", - # Caused by internal scikit-learn scipy usage - r"The 'sym_pos' keyword is deprecated and should be replaced by using", ] for msg in allow_DeprecationWarning_message: warnings.filterwarnings("default", category=DeprecationWarning, message=msg) From 59a62d9e2e91fcc3642d392c2e28efceeb642cc1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 12:38:41 +0000 Subject: [PATCH 09/12] Bump thiserror from 1.0.61 to 1.0.62 (#12768) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.61 to 1.0.62. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.61...1.0.62) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 125ce6abe876..cc146f07a058 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1527,18 +1527,18 @@ checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" dependencies = [ "proc-macro2", "quote", From 2a2b806c1ba800498f8f9659455325fcef40856c Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Fri, 12 Jul 2024 12:54:23 -0400 Subject: [PATCH 10/12] Make `CircuitData` inner data accessible in Rust (#12766) * Add: `iter()` method to `CircuitData` - Make `PackedInstruction` public. * Docs: Remove "rust-native method" from docstring. --- crates/circuit/src/circuit_data.rs | 5 +++++ crates/circuit/src/circuit_instruction.rs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index 501645415874..0b4d60b6c91a 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -1167,4 +1167,9 @@ impl CircuitData { py_op: RefCell::new(inst.py_op.clone()), }) } + + /// Returns an iterator over all the instructions present in the circuit. + pub fn iter(&self) -> impl Iterator { + self.data.iter() + } } diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index 501105f9f17e..ecb7a1623a20 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -44,7 +44,7 @@ pub struct ExtraInstructionAttributes { /// Private type used to store instructions with interned arg lists. #[derive(Clone, Debug)] -pub(crate) struct PackedInstruction { +pub struct PackedInstruction { /// The Python-side operation instance. pub op: OperationType, /// The index under which the interner has stored `qubits`. From 3e2a6e8dcfdc894f40bf8a4bd90fd2cc0af8febb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Fri, 12 Jul 2024 23:07:41 +0200 Subject: [PATCH 11/12] Finalize Rust representation for standard gates (#12709) * Add missing gate definitions * Reorder gates, following number of qubits and a sort of alphabetical order. Make definitions and matrices consistent with new gate order. Remove C4XGate (second mcx) from list. --- crates/circuit/src/gate_matrix.rs | 175 +- crates/circuit/src/imports.rs | 176 +- crates/circuit/src/operations.rs | 1501 +++++++++--------- test/python/circuit/test_rust_equivalence.py | 7 - 4 files changed, 935 insertions(+), 924 deletions(-) diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 6f527af2e30f..a38783ef7e7f 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -46,28 +46,21 @@ macro_rules! make_n_controlled_gate { }}; } +pub static H_GATE: GateArray1Q = [ + [c64(FRAC_1_SQRT_2, 0.), c64(FRAC_1_SQRT_2, 0.)], + [c64(FRAC_1_SQRT_2, 0.), c64(-FRAC_1_SQRT_2, 0.)], +]; + pub static X_GATE: GateArray1Q = [[C_ZERO, C_ONE], [C_ONE, C_ZERO]]; pub static Z_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, C_M_ONE]]; pub static Y_GATE: GateArray1Q = [[C_ZERO, M_IM], [IM, C_ZERO]]; -pub static H_GATE: GateArray1Q = [ - [c64(FRAC_1_SQRT_2, 0.), c64(FRAC_1_SQRT_2, 0.)], - [c64(FRAC_1_SQRT_2, 0.), c64(-FRAC_1_SQRT_2, 0.)], -]; - pub static S_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, IM]]; pub static SDG_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, M_IM]]; -pub static T_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, c64(FRAC_1_SQRT_2, FRAC_1_SQRT_2)]]; - -pub static TDG_GATE: GateArray1Q = [ - [C_ONE, C_ZERO], - [C_ZERO, c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)], -]; - pub static SX_GATE: GateArray1Q = [ [c64(0.5, 0.5), c64(0.5, -0.5)], [c64(0.5, -0.5), c64(0.5, 0.5)], @@ -78,27 +71,27 @@ pub static SXDG_GATE: GateArray1Q = [ [c64(0.5, 0.5), c64(0.5, -0.5)], ]; -pub static CX_GATE: GateArray2Q = make_n_controlled_gate!(X_GATE, 1); - -pub static CZ_GATE: GateArray2Q = make_n_controlled_gate!(Z_GATE, 1); - -pub static CY_GATE: GateArray2Q = make_n_controlled_gate!(Y_GATE, 1); - -pub static CCX_GATE: GateArray3Q = make_n_controlled_gate!(X_GATE, 2); - -pub static CCZ_GATE: GateArray3Q = make_n_controlled_gate!(Z_GATE, 2); - -pub static C3X_GATE: GateArray4Q = make_n_controlled_gate!(X_GATE, 3); +pub static T_GATE: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, c64(FRAC_1_SQRT_2, FRAC_1_SQRT_2)]]; -pub static C3SX_GATE: GateArray4Q = make_n_controlled_gate!(SX_GATE, 3); +pub static TDG_GATE: GateArray1Q = [ + [C_ONE, C_ZERO], + [C_ZERO, c64(FRAC_1_SQRT_2, -FRAC_1_SQRT_2)], +]; pub static CH_GATE: GateArray2Q = make_n_controlled_gate!(H_GATE, 1); -pub static CS_GATE: GateArray2Q = make_n_controlled_gate!(S_GATE, 1); +pub static CX_GATE: GateArray2Q = make_n_controlled_gate!(X_GATE, 1); -pub static CSDG_GATE: GateArray2Q = make_n_controlled_gate!(SDG_GATE, 1); +pub static CY_GATE: GateArray2Q = make_n_controlled_gate!(Y_GATE, 1); -pub static CSX_GATE: GateArray2Q = make_n_controlled_gate!(SX_GATE, 1); +pub static CZ_GATE: GateArray2Q = make_n_controlled_gate!(Z_GATE, 1); + +pub static DCX_GATE: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, C_ONE], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], +]; pub static ECR_GATE: GateArray2Q = [ [ @@ -133,6 +126,7 @@ pub static SWAP_GATE: GateArray2Q = [ [C_ZERO, C_ONE, C_ZERO, C_ZERO], [C_ZERO, C_ZERO, C_ZERO, C_ONE], ]; + pub static ISWAP_GATE: GateArray2Q = [ [C_ONE, C_ZERO, C_ZERO, C_ZERO], [C_ZERO, C_ZERO, IM, C_ZERO], @@ -140,6 +134,16 @@ pub static ISWAP_GATE: GateArray2Q = [ [C_ZERO, C_ZERO, C_ZERO, C_ONE], ]; +pub static CS_GATE: GateArray2Q = make_n_controlled_gate!(S_GATE, 1); + +pub static CSDG_GATE: GateArray2Q = make_n_controlled_gate!(SDG_GATE, 1); + +pub static CSX_GATE: GateArray2Q = make_n_controlled_gate!(SX_GATE, 1); + +pub static CCX_GATE: GateArray3Q = make_n_controlled_gate!(X_GATE, 2); + +pub static CCZ_GATE: GateArray3Q = make_n_controlled_gate!(Z_GATE, 2); + pub static CSWAP_GATE: GateArray3Q = [ [ C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, @@ -167,13 +171,6 @@ pub static CSWAP_GATE: GateArray3Q = [ ], ]; -pub static DCX_GATE: GateArray2Q = [ - [C_ONE, C_ZERO, C_ZERO, C_ZERO], - [C_ZERO, C_ZERO, C_ZERO, C_ONE], - [C_ZERO, C_ONE, C_ZERO, C_ZERO], - [C_ZERO, C_ZERO, C_ONE, C_ZERO], -]; - pub static RCCX_GATE: GateArray3Q = [ [ C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, @@ -197,6 +194,10 @@ pub static RCCX_GATE: GateArray3Q = [ [C_ZERO, C_ZERO, C_ZERO, IM, C_ZERO, C_ZERO, C_ZERO, C_ZERO], ]; +pub static C3X_GATE: GateArray4Q = make_n_controlled_gate!(X_GATE, 3); + +pub static C3SX_GATE: GateArray4Q = make_n_controlled_gate!(SX_GATE, 3); + pub static RC3X_GATE: GateArray4Q = [ [ C_ONE, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, C_ZERO, @@ -274,6 +275,41 @@ pub fn phase_gate(lam: f64) -> GateArray1Q { [[C_ONE, C_ZERO], [C_ZERO, c64(0., lam).exp()]] } +#[inline] +pub fn r_gate(theta: f64, phi: f64) -> GateArray1Q { + let half_theta = theta / 2.; + let cost = c64(half_theta.cos(), 0.); + let sint = half_theta.sin(); + let cosphi = phi.cos(); + let sinphi = phi.sin(); + [ + [cost, c64(-sint * sinphi, -sint * cosphi)], + [c64(sint * sinphi, -sint * cosphi), cost], + ] +} + +#[inline] +pub fn rx_gate(theta: f64) -> GateArray1Q { + let half_theta = theta / 2.; + let cos = c64(half_theta.cos(), 0.); + let isin = c64(0., -half_theta.sin()); + [[cos, isin], [isin, cos]] +} + +#[inline] +pub fn ry_gate(theta: f64) -> GateArray1Q { + let half_theta = theta / 2.; + let cos = c64(half_theta.cos(), 0.); + let sin = c64(half_theta.sin(), 0.); + [[cos, -sin], [sin, cos]] +} + +#[inline] +pub fn rz_gate(theta: f64) -> GateArray1Q { + let ilam2 = c64(0., 0.5 * theta); + [[(-ilam2).exp(), C_ZERO], [C_ZERO, ilam2.exp()]] +} + #[inline] pub fn u_gate(theta: f64, phi: f64, lam: f64) -> GateArray1Q { let cos = (theta / 2.).cos(); @@ -323,6 +359,24 @@ pub fn cp_gate(lam: f64) -> GateArray2Q { ] } +#[inline] +pub fn crx_gate(theta: f64) -> GateArray2Q { + let gate_matrix = rx_gate(theta); + make_n_controlled_gate!(gate_matrix, 1) +} + +#[inline] +pub fn cry_gate(theta: f64) -> GateArray2Q { + let gate_matrix = ry_gate(theta); + make_n_controlled_gate!(gate_matrix, 1) +} + +#[inline] +pub fn crz_gate(theta: f64) -> GateArray2Q { + let gate_matrix = rz_gate(theta); + make_n_controlled_gate!(gate_matrix, 1) +} + #[inline] pub fn cu_gate(theta: f64, phi: f64, lam: f64, gamma: f64) -> GateArray2Q { let cos_theta = (theta / 2.).cos(); @@ -357,59 +411,6 @@ pub fn cu3_gate(theta: f64, phi: f64, lam: f64) -> GateArray2Q { make_n_controlled_gate!(gate_matrix, 1) } -#[inline] -pub fn r_gate(theta: f64, phi: f64) -> GateArray1Q { - let half_theta = theta / 2.; - let cost = c64(half_theta.cos(), 0.); - let sint = half_theta.sin(); - let cosphi = phi.cos(); - let sinphi = phi.sin(); - [ - [cost, c64(-sint * sinphi, -sint * cosphi)], - [c64(sint * sinphi, -sint * cosphi), cost], - ] -} - -#[inline] -pub fn rx_gate(theta: f64) -> GateArray1Q { - let half_theta = theta / 2.; - let cos = c64(half_theta.cos(), 0.); - let isin = c64(0., -half_theta.sin()); - [[cos, isin], [isin, cos]] -} - -#[inline] -pub fn ry_gate(theta: f64) -> GateArray1Q { - let half_theta = theta / 2.; - let cos = c64(half_theta.cos(), 0.); - let sin = c64(half_theta.sin(), 0.); - [[cos, -sin], [sin, cos]] -} - -#[inline] -pub fn rz_gate(theta: f64) -> GateArray1Q { - let ilam2 = c64(0., 0.5 * theta); - [[(-ilam2).exp(), C_ZERO], [C_ZERO, ilam2.exp()]] -} - -#[inline] -pub fn crx_gate(theta: f64) -> GateArray2Q { - let gate_matrix = rx_gate(theta); - make_n_controlled_gate!(gate_matrix, 1) -} - -#[inline] -pub fn cry_gate(theta: f64) -> GateArray2Q { - let gate_matrix = ry_gate(theta); - make_n_controlled_gate!(gate_matrix, 1) -} - -#[inline] -pub fn crz_gate(theta: f64) -> GateArray2Q { - let gate_matrix = rz_gate(theta); - make_n_controlled_gate!(gate_matrix, 1) -} - #[inline] pub fn rxx_gate(theta: f64) -> GateArray2Q { let (sint, cost) = (theta / 2.0).sin_cos(); diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index d277dea9f89c..153b66392083 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -86,121 +86,119 @@ pub static WARNINGS_WARN: ImportOnceCell = ImportOnceCell::new("warnings", "warn /// index of it's entry in this table. This is all done statically for performance // TODO: replace placeholders with actual implementation static STDGATE_IMPORT_PATHS: [[&str; 2]; STANDARD_GATE_SIZE] = [ - // ZGate = 0 - ["qiskit.circuit.library.standard_gates.z", "ZGate"], - // YGate = 1 - ["qiskit.circuit.library.standard_gates.y", "YGate"], - // XGate = 2 - ["qiskit.circuit.library.standard_gates.x", "XGate"], - // CZGate = 3 - ["qiskit.circuit.library.standard_gates.z", "CZGate"], - // CYGate = 4 - ["qiskit.circuit.library.standard_gates.y", "CYGate"], - // CXGate = 5 - ["qiskit.circuit.library.standard_gates.x", "CXGate"], - // CCXGate = 6 - ["qiskit.circuit.library.standard_gates.x", "CCXGate"], - // RXGate = 7 - ["qiskit.circuit.library.standard_gates.rx", "RXGate"], - // RYGate = 8 - ["qiskit.circuit.library.standard_gates.ry", "RYGate"], - // RZGate = 9 - ["qiskit.circuit.library.standard_gates.rz", "RZGate"], - // ECRGate = 10 - ["qiskit.circuit.library.standard_gates.ecr", "ECRGate"], - // SwapGate = 11 - ["qiskit.circuit.library.standard_gates.swap", "SwapGate"], - // SXGate = 12 - ["qiskit.circuit.library.standard_gates.sx", "SXGate"], - // GlobalPhaseGate = 13 + // GlobalPhaseGate = 0 [ "qiskit.circuit.library.standard_gates.global_phase", "GlobalPhaseGate", ], - // IGate = 14 - ["qiskit.circuit.library.standard_gates.i", "IGate"], - // HGate = 15 + // HGate = 1 ["qiskit.circuit.library.standard_gates.h", "HGate"], - // PhaseGate = 16 + // IGate = 2 + ["qiskit.circuit.library.standard_gates.i", "IGate"], + // XGate = 3 + ["qiskit.circuit.library.standard_gates.x", "XGate"], + // YGate = 4 + ["qiskit.circuit.library.standard_gates.y", "YGate"], + // ZGate = 5 + ["qiskit.circuit.library.standard_gates.z", "ZGate"], + // PhaseGate = 6 ["qiskit.circuit.library.standard_gates.p", "PhaseGate"], - // UGate = 17 - ["qiskit.circuit.library.standard_gates.u", "UGate"], - // SGate = 18 + // RGate 7 + ["qiskit.circuit.library.standard_gates.r", "RGate"], + // RXGate = 8 + ["qiskit.circuit.library.standard_gates.rx", "RXGate"], + // RYGate = 9 + ["qiskit.circuit.library.standard_gates.ry", "RYGate"], + // RZGate = 10 + ["qiskit.circuit.library.standard_gates.rz", "RZGate"], + // SGate = 11 ["qiskit.circuit.library.standard_gates.s", "SGate"], - // SdgGate = 19 + // SdgGate = 12 ["qiskit.circuit.library.standard_gates.s", "SdgGate"], - // TGate = 20 + // SXGate = 13 + ["qiskit.circuit.library.standard_gates.sx", "SXGate"], + // SXdgGate = 14 + ["qiskit.circuit.library.standard_gates.sx", "SXdgGate"], + // TGate = 15 ["qiskit.circuit.library.standard_gates.t", "TGate"], - // TdgGate = 21 + // TdgGate = 16 ["qiskit.circuit.library.standard_gates.t", "TdgGate"], - // SXdgGate = 22 - ["qiskit.circuit.library.standard_gates.sx", "SXdgGate"], - // iSWAPGate = 23 - ["qiskit.circuit.library.standard_gates.iswap", "iSwapGate"], - // XXMinusYYGate = 24 - [ - "qiskit.circuit.library.standard_gates.xx_minus_yy", - "XXMinusYYGate", - ], - // XXPlusYYGate = 25 - [ - "qiskit.circuit.library.standard_gates.xx_plus_yy", - "XXPlusYYGate", - ], - // U1Gate = 26 + // UGate = 17 + ["qiskit.circuit.library.standard_gates.u", "UGate"], + // U1Gate = 18 ["qiskit.circuit.library.standard_gates.u1", "U1Gate"], - // U2Gate = 27 + // U2Gate = 19 ["qiskit.circuit.library.standard_gates.u2", "U2Gate"], - // U3Gate = 28 + // U3Gate = 20 ["qiskit.circuit.library.standard_gates.u3", "U3Gate"], - // CRXGate = 29 + // CHGate = 21 + ["qiskit.circuit.library.standard_gates.h", "CHGate"], + // CXGate = 22 + ["qiskit.circuit.library.standard_gates.x", "CXGate"], + // CYGate = 23 + ["qiskit.circuit.library.standard_gates.y", "CYGate"], + // CZGate = 24 + ["qiskit.circuit.library.standard_gates.z", "CZGate"], + // DCXGate = 25 + ["qiskit.circuit.library.standard_gates.dcx", "DCXGate"], + // ECRGate = 26 + ["qiskit.circuit.library.standard_gates.ecr", "ECRGate"], + // SwapGate = 27 + ["qiskit.circuit.library.standard_gates.swap", "SwapGate"], + // iSWAPGate = 28 + ["qiskit.circuit.library.standard_gates.iswap", "iSwapGate"], + // CPhaseGate = 29 + ["qiskit.circuit.library.standard_gates.p", "CPhaseGate"], + // CRXGate = 30 ["qiskit.circuit.library.standard_gates.rx", "CRXGate"], - // CRYGate = 30 + // CRYGate = 31 ["qiskit.circuit.library.standard_gates.ry", "CRYGate"], - // CRZGate = 31 + // CRZGate = 32 ["qiskit.circuit.library.standard_gates.rz", "CRZGate"], - // RGate 32 - ["qiskit.circuit.library.standard_gates.r", "RGate"], - // CHGate = 33 - ["qiskit.circuit.library.standard_gates.h", "CHGate"], - // CPhaseGate = 34 - ["qiskit.circuit.library.standard_gates.p", "CPhaseGate"], - // CSGate = 35 + // CSGate = 33 ["qiskit.circuit.library.standard_gates.s", "CSGate"], - // CSdgGate = 36 + // CSdgGate = 34 ["qiskit.circuit.library.standard_gates.s", "CSdgGate"], - // CSXGate = 37 + // CSXGate = 35 ["qiskit.circuit.library.standard_gates.sx", "CSXGate"], - // CSwapGate = 38 - ["qiskit.circuit.library.standard_gates.swap", "CSwapGate"], - // CUGate = 39 + // CUGate = 36 ["qiskit.circuit.library.standard_gates.u", "CUGate"], - // CU1Gate = 40 + // CU1Gate = 37 ["qiskit.circuit.library.standard_gates.u1", "CU1Gate"], - // CU3Gate = 41 + // CU3Gate = 38 ["qiskit.circuit.library.standard_gates.u3", "CU3Gate"], - // C3XGate = 42 - ["qiskit.circuit.library.standard_gates.x", "C3XGate"], - // C3SXGate = 43 - ["qiskit.circuit.library.standard_gates.x", "C3SXGate"], - // C4XGate = 44 - ["qiskit.circuit.library.standard_gates.x", "C4XGate"], - // DCXGate = 45 - ["qiskit.circuit.library.standard_gates.dcx", "DCXGate"], - // CCZGate = 46 - ["qiskit.circuit.library.standard_gates.z", "CCZGate"], - // RCCXGate = 47 - ["qiskit.circuit.library.standard_gates.x", "RCCXGate"], - // RC3XGate = 48 - ["qiskit.circuit.library.standard_gates.x", "RC3XGate"], - // RXXGate = 49 + // RXXGate = 39 ["qiskit.circuit.library.standard_gates.rxx", "RXXGate"], - // RYYGate = 50 + // RYYGate = 40 ["qiskit.circuit.library.standard_gates.ryy", "RYYGate"], - // RZZGate = 51 + // RZZGate = 41 ["qiskit.circuit.library.standard_gates.rzz", "RZZGate"], - // RZXGate = 52 + // RZXGate = 42 ["qiskit.circuit.library.standard_gates.rzx", "RZXGate"], + // XXMinusYYGate = 43 + [ + "qiskit.circuit.library.standard_gates.xx_minus_yy", + "XXMinusYYGate", + ], + // XXPlusYYGate = 44 + [ + "qiskit.circuit.library.standard_gates.xx_plus_yy", + "XXPlusYYGate", + ], + // CCXGate = 45 + ["qiskit.circuit.library.standard_gates.x", "CCXGate"], + // CCZGate = 46 + ["qiskit.circuit.library.standard_gates.z", "CCZGate"], + // CSwapGate = 47 + ["qiskit.circuit.library.standard_gates.swap", "CSwapGate"], + // RCCXGate = 48 + ["qiskit.circuit.library.standard_gates.x", "RCCXGate"], + // C3XGate = 49 + ["qiskit.circuit.library.standard_gates.x", "C3XGate"], + // C3SXGate = 50 + ["qiskit.circuit.library.standard_gates.x", "C3SXGate"], + // RC3XGate = 51 + ["qiskit.circuit.library.standard_gates.x", "RC3XGate"], ]; /// A mapping from the enum variant in crate::operations::StandardGate to the python object for the diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index fcbfddb72181..1eed72347da6 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -187,59 +187,58 @@ impl ToPyObject for Param { #[derive(Clone, Debug, Copy, Eq, PartialEq, Hash)] #[pyclass(module = "qiskit._accelerate.circuit")] pub enum StandardGate { - ZGate = 0, - YGate = 1, - XGate = 2, - CZGate = 3, - CYGate = 4, - CXGate = 5, - CCXGate = 6, - RXGate = 7, - RYGate = 8, - RZGate = 9, - ECRGate = 10, - SwapGate = 11, - SXGate = 12, - GlobalPhaseGate = 13, - IGate = 14, - HGate = 15, - PhaseGate = 16, + GlobalPhaseGate = 0, + HGate = 1, + IGate = 2, + XGate = 3, + YGate = 4, + ZGate = 5, + PhaseGate = 6, + RGate = 7, + RXGate = 8, + RYGate = 9, + RZGate = 10, + SGate = 11, + SdgGate = 12, + SXGate = 13, + SXdgGate = 14, + TGate = 15, + TdgGate = 16, UGate = 17, - SGate = 18, - SdgGate = 19, - TGate = 20, - TdgGate = 21, - SXdgGate = 22, - ISwapGate = 23, - XXMinusYYGate = 24, - XXPlusYYGate = 25, - U1Gate = 26, - U2Gate = 27, - U3Gate = 28, - CRXGate = 29, - CRYGate = 30, - CRZGate = 31, - RGate = 32, - CHGate = 33, - CPhaseGate = 34, - CSGate = 35, - CSdgGate = 36, - CSXGate = 37, - CSwapGate = 38, - CUGate = 39, - CU1Gate = 40, - CU3Gate = 41, - C3XGate = 42, - C3SXGate = 43, - C4XGate = 44, - DCXGate = 45, + U1Gate = 18, + U2Gate = 19, + U3Gate = 20, + CHGate = 21, + CXGate = 22, + CYGate = 23, + CZGate = 24, + DCXGate = 25, + ECRGate = 26, + SwapGate = 27, + ISwapGate = 28, + CPhaseGate = 29, + CRXGate = 30, + CRYGate = 31, + CRZGate = 32, + CSGate = 33, + CSdgGate = 34, + CSXGate = 35, + CUGate = 36, + CU1Gate = 37, + CU3Gate = 38, + RXXGate = 39, + RYYGate = 40, + RZZGate = 41, + RZXGate = 42, + XXMinusYYGate = 43, + XXPlusYYGate = 44, + CCXGate = 45, CCZGate = 46, - RCCXGate = 47, - RC3XGate = 48, - RXXGate = 49, - RYYGate = 50, - RZZGate = 51, - RZXGate = 52, + CSwapGate = 47, + RCCXGate = 48, + C3XGate = 49, + C3SXGate = 50, + RC3XGate = 51, } impl ToPyObject for StandardGate { @@ -249,77 +248,76 @@ impl ToPyObject for StandardGate { } static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ - 1, 1, 1, 2, 2, 2, 3, 1, 1, 1, // 0-9 - 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, // 10-19 - 1, 1, 1, 2, 2, 2, 1, 1, 1, 2, // 20-29 - 2, 2, 1, 2, 2, 2, 2, 2, 3, 2, // 30-39 - 2, 2, 4, 4, 5, 2, 3, 3, 4, 2, // 40-49 - 2, 2, 2, // 50-52 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0-9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 10-19 + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 20-29 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 30-39 + 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, // 40-49 + 4, 4, // 50-51 ]; static STANDARD_GATE_NUM_PARAMS: [u32; STANDARD_GATE_SIZE] = [ - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, // 0-9 - 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, // 10-19 - 0, 0, 0, 0, 2, 2, 1, 2, 3, 1, // 20-29 - 1, 1, 2, 0, 1, 0, 0, 0, 0, 4, // 30-39 - 1, 3, 0, 0, 0, 0, 0, 0, 0, 1, // 40-49 - 1, 1, 1, // 50-52 + 1, 0, 0, 0, 0, 0, 1, 2, 1, 1, // 0-9 + 1, 0, 0, 0, 0, 0, 0, 3, 1, 2, // 10-19 + 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, // 20-29 + 1, 1, 1, 0, 0, 0, 4, 1, 3, 1, // 30-39 + 1, 1, 1, 2, 2, 0, 0, 0, 0, 0, // 40-49 + 0, 0, // 50-51 ]; static STANDARD_GATE_NAME: [&str; STANDARD_GATE_SIZE] = [ - "z", // 0 - "y", // 1 - "x", // 2 - "cz", // 3 - "cy", // 4 - "cx", // 5 - "ccx", // 6 - "rx", // 7 - "ry", // 8 - "rz", // 9 - "ecr", // 10 - "swap", // 11 - "sx", // 12 - "global_phase", // 13 - "id", // 14 - "h", // 15 - "p", // 16 + "global_phase", // 0 + "h", // 1 + "id", // 2 + "x", // 3 + "y", // 4 + "z", // 5 + "p", // 6 + "r", // 7 + "rx", // 8 + "ry", // 9 + "rz", // 10 + "s", // 11 + "sdg", // 12 + "sx", // 13 + "sxdg", // 14 + "t", // 15 + "tdg", // 16 "u", // 17 - "s", // 18 - "sdg", // 19 - "t", // 20 - "tdg", // 21 - "sxdg", // 22 - "iswap", // 23 - "xx_minus_yy", // 24 - "xx_plus_yy", // 25 - "u1", // 26 - "u2", // 27 - "u3", // 28 - "crx", // 29 - "cry", // 30 - "crz", // 31 - "r", // 32 - "ch", // 33 - "cp", // 34 - "cs", // 35 - "csdg", // 36 - "csx", // 37 - "cswap", // 38 - "cu", // 39 - "cu1", // 40 - "cu3", // 41 - "mcx", // 42 ("c3x") - "c3sx", // 43 - "mcx", // 44 ("c4x") - "dcx", // 45 + "u1", // 18 + "u2", // 19 + "u3", // 20 + "ch", // 21 + "cx", // 22 + "cy", // 23 + "cz", // 24 + "dcx", // 25 + "ecr", // 26 + "swap", // 27 + "iswap", // 28 + "cp", // 29 + "crx", // 30 + "cry", // 31 + "crz", // 32 + "cs", // 33 + "csdg", // 34 + "csx", // 35 + "cu", // 36 + "cu1", // 37 + "cu3", // 38 + "rxx", // 39 + "ryy", // 40 + "rzz", // 41 + "rzx", // 42 + "xx_minus_yy", // 43 + "xx_plus_yy", // 44 + "ccx", // 45 "ccz", // 46 - "rccx", // 47 - "rcccx", // 48 ("rc3x") - "rxx", // 49 - "ryy", // 50 - "rzz", // 51 - "rzx", // 52 + "cswap", // 47 + "rccx", // 48 + "mcx", // 49 ("c3x") + "c3sx", // 50 + "rcccx", // 51 ("rc3x") ]; #[pymethods] @@ -368,7 +366,7 @@ impl StandardGate { // // Remove this when std::mem::variant_count() is stabilized (see // https://github.com/rust-lang/rust/issues/73662 ) -pub const STANDARD_GATE_SIZE: usize = 53; +pub const STANDARD_GATE_SIZE: usize = 52; impl Operation for StandardGate { fn name(&self) -> &str { @@ -397,32 +395,40 @@ impl Operation for StandardGate { fn matrix(&self, params: &[Param]) -> Option> { match self { - Self::ZGate => match params { - [] => Some(aview2(&gate_matrix::Z_GATE).to_owned()), + Self::GlobalPhaseGate => match params { + [Param::Float(theta)] => { + Some(aview2(&gate_matrix::global_phase_gate(*theta)).to_owned()) + } _ => None, }, - Self::YGate => match params { - [] => Some(aview2(&gate_matrix::Y_GATE).to_owned()), + Self::HGate => match params { + [] => Some(aview2(&gate_matrix::H_GATE).to_owned()), + _ => None, + }, + Self::IGate => match params { + [] => Some(aview2(&gate_matrix::ONE_QUBIT_IDENTITY).to_owned()), _ => None, }, Self::XGate => match params { [] => Some(aview2(&gate_matrix::X_GATE).to_owned()), _ => None, }, - Self::CZGate => match params { - [] => Some(aview2(&gate_matrix::CZ_GATE).to_owned()), + Self::YGate => match params { + [] => Some(aview2(&gate_matrix::Y_GATE).to_owned()), _ => None, }, - Self::CYGate => match params { - [] => Some(aview2(&gate_matrix::CY_GATE).to_owned()), + Self::ZGate => match params { + [] => Some(aview2(&gate_matrix::Z_GATE).to_owned()), _ => None, }, - Self::CXGate => match params { - [] => Some(aview2(&gate_matrix::CX_GATE).to_owned()), + Self::PhaseGate => match params { + [Param::Float(theta)] => Some(aview2(&gate_matrix::phase_gate(*theta)).to_owned()), _ => None, }, - Self::CCXGate => match params { - [] => Some(aview2(&gate_matrix::CCX_GATE).to_owned()), + Self::RGate => match params { + [Param::Float(theta), Param::Float(phi)] => { + Some(aview2(&gate_matrix::r_gate(*theta, *phi)).to_owned()) + } _ => None, }, Self::RXGate => match params { @@ -437,24 +443,12 @@ impl Operation for StandardGate { [Param::Float(theta)] => Some(aview2(&gate_matrix::rz_gate(*theta)).to_owned()), _ => None, }, - Self::CRXGate => match params { - [Param::Float(theta)] => Some(aview2(&gate_matrix::crx_gate(*theta)).to_owned()), - _ => None, - }, - Self::CRYGate => match params { - [Param::Float(theta)] => Some(aview2(&gate_matrix::cry_gate(*theta)).to_owned()), - _ => None, - }, - Self::CRZGate => match params { - [Param::Float(theta)] => Some(aview2(&gate_matrix::crz_gate(*theta)).to_owned()), - _ => None, - }, - Self::ECRGate => match params { - [] => Some(aview2(&gate_matrix::ECR_GATE).to_owned()), + Self::SGate => match params { + [] => Some(aview2(&gate_matrix::S_GATE).to_owned()), _ => None, }, - Self::SwapGate => match params { - [] => Some(aview2(&gate_matrix::SWAP_GATE).to_owned()), + Self::SdgGate => match params { + [] => Some(aview2(&gate_matrix::SDG_GATE).to_owned()), _ => None, }, Self::SXGate => match params { @@ -465,38 +459,6 @@ impl Operation for StandardGate { [] => Some(aview2(&gate_matrix::SXDG_GATE).to_owned()), _ => None, }, - Self::GlobalPhaseGate => match params { - [Param::Float(theta)] => { - Some(aview2(&gate_matrix::global_phase_gate(*theta)).to_owned()) - } - _ => None, - }, - Self::IGate => match params { - [] => Some(aview2(&gate_matrix::ONE_QUBIT_IDENTITY).to_owned()), - _ => None, - }, - Self::HGate => match params { - [] => Some(aview2(&gate_matrix::H_GATE).to_owned()), - _ => None, - }, - Self::PhaseGate => match params { - [Param::Float(theta)] => Some(aview2(&gate_matrix::phase_gate(*theta)).to_owned()), - _ => None, - }, - Self::UGate => match params { - [Param::Float(theta), Param::Float(phi), Param::Float(lam)] => { - Some(aview2(&gate_matrix::u_gate(*theta, *phi, *lam)).to_owned()) - } - _ => None, - }, - Self::SGate => match params { - [] => Some(aview2(&gate_matrix::S_GATE).to_owned()), - _ => None, - }, - Self::SdgGate => match params { - [] => Some(aview2(&gate_matrix::SDG_GATE).to_owned()), - _ => None, - }, Self::TGate => match params { [] => Some(aview2(&gate_matrix::T_GATE).to_owned()), _ => None, @@ -505,19 +467,9 @@ impl Operation for StandardGate { [] => Some(aview2(&gate_matrix::TDG_GATE).to_owned()), _ => None, }, - Self::ISwapGate => match params { - [] => Some(aview2(&gate_matrix::ISWAP_GATE).to_owned()), - _ => None, - }, - Self::XXMinusYYGate => match params { - [Param::Float(theta), Param::Float(beta)] => { - Some(aview2(&gate_matrix::xx_minus_yy_gate(*theta, *beta)).to_owned()) - } - _ => None, - }, - Self::XXPlusYYGate => match params { - [Param::Float(theta), Param::Float(beta)] => { - Some(aview2(&gate_matrix::xx_plus_yy_gate(*theta, *beta)).to_owned()) + Self::UGate => match params { + [Param::Float(theta), Param::Float(phi), Param::Float(lam)] => { + Some(aview2(&gate_matrix::u_gate(*theta, *phi, *lam)).to_owned()) } _ => None, }, @@ -537,42 +489,54 @@ impl Operation for StandardGate { } _ => None, }, - Self::CUGate => match params { - [Param::Float(theta), Param::Float(phi), Param::Float(lam), Param::Float(gamma)] => { - Some(aview2(&gate_matrix::cu_gate(*theta, *phi, *lam, *gamma)).to_owned()) - } + Self::CHGate => match params { + [] => Some(aview2(&gate_matrix::CH_GATE).to_owned()), _ => None, }, - Self::CU1Gate => match params[0] { - Param::Float(lam) => Some(aview2(&gate_matrix::cu1_gate(lam)).to_owned()), + Self::CXGate => match params { + [] => Some(aview2(&gate_matrix::CX_GATE).to_owned()), _ => None, }, - Self::CU3Gate => match params { - [Param::Float(theta), Param::Float(phi), Param::Float(lam)] => { - Some(aview2(&gate_matrix::cu3_gate(*theta, *phi, *lam)).to_owned()) - } + Self::CYGate => match params { + [] => Some(aview2(&gate_matrix::CY_GATE).to_owned()), _ => None, }, - Self::C3XGate => match params { - [] => Some(aview2(&gate_matrix::C3X_GATE).to_owned()), + Self::CZGate => match params { + [] => Some(aview2(&gate_matrix::CZ_GATE).to_owned()), _ => None, }, - Self::C3SXGate => match params { - [] => Some(aview2(&gate_matrix::C3SX_GATE).to_owned()), + Self::DCXGate => match params { + [] => Some(aview2(&gate_matrix::DCX_GATE).to_owned()), _ => None, }, - Self::CCZGate => match params { - [] => Some(aview2(&gate_matrix::CCZ_GATE).to_owned()), + Self::ECRGate => match params { + [] => Some(aview2(&gate_matrix::ECR_GATE).to_owned()), _ => None, }, - Self::CHGate => match params { - [] => Some(aview2(&gate_matrix::CH_GATE).to_owned()), + Self::SwapGate => match params { + [] => Some(aview2(&gate_matrix::SWAP_GATE).to_owned()), + _ => None, + }, + Self::ISwapGate => match params { + [] => Some(aview2(&gate_matrix::ISWAP_GATE).to_owned()), _ => None, }, Self::CPhaseGate => match params { [Param::Float(lam)] => Some(aview2(&gate_matrix::cp_gate(*lam)).to_owned()), _ => None, }, + Self::CRXGate => match params { + [Param::Float(theta)] => Some(aview2(&gate_matrix::crx_gate(*theta)).to_owned()), + _ => None, + }, + Self::CRYGate => match params { + [Param::Float(theta)] => Some(aview2(&gate_matrix::cry_gate(*theta)).to_owned()), + _ => None, + }, + Self::CRZGate => match params { + [Param::Float(theta)] => Some(aview2(&gate_matrix::crz_gate(*theta)).to_owned()), + _ => None, + }, Self::CSGate => match params { [] => Some(aview2(&gate_matrix::CS_GATE).to_owned()), _ => None, @@ -585,21 +549,22 @@ impl Operation for StandardGate { [] => Some(aview2(&gate_matrix::CSX_GATE).to_owned()), _ => None, }, - Self::CSwapGate => match params { - [] => Some(aview2(&gate_matrix::CSWAP_GATE).to_owned()), + Self::CUGate => match params { + [Param::Float(theta), Param::Float(phi), Param::Float(lam), Param::Float(gamma)] => { + Some(aview2(&gate_matrix::cu_gate(*theta, *phi, *lam, *gamma)).to_owned()) + } _ => None, }, - Self::RGate => match params { - [Param::Float(theta), Param::Float(phi)] => { - Some(aview2(&gate_matrix::r_gate(*theta, *phi)).to_owned()) - } + Self::CU1Gate => match params[0] { + Param::Float(lam) => Some(aview2(&gate_matrix::cu1_gate(lam)).to_owned()), _ => None, }, - Self::DCXGate => match params { - [] => Some(aview2(&gate_matrix::DCX_GATE).to_owned()), + Self::CU3Gate => match params { + [Param::Float(theta), Param::Float(phi), Param::Float(lam)] => { + Some(aview2(&gate_matrix::cu3_gate(*theta, *phi, *lam)).to_owned()) + } _ => None, }, - Self::C4XGate => todo!(), Self::RXXGate => match params[0] { Param::Float(theta) => Some(aview2(&gate_matrix::rxx_gate(theta)).to_owned()), _ => None, @@ -616,10 +581,42 @@ impl Operation for StandardGate { Param::Float(theta) => Some(aview2(&gate_matrix::rzx_gate(theta)).to_owned()), _ => None, }, + Self::XXMinusYYGate => match params { + [Param::Float(theta), Param::Float(beta)] => { + Some(aview2(&gate_matrix::xx_minus_yy_gate(*theta, *beta)).to_owned()) + } + _ => None, + }, + Self::XXPlusYYGate => match params { + [Param::Float(theta), Param::Float(beta)] => { + Some(aview2(&gate_matrix::xx_plus_yy_gate(*theta, *beta)).to_owned()) + } + _ => None, + }, + Self::CCXGate => match params { + [] => Some(aview2(&gate_matrix::CCX_GATE).to_owned()), + _ => None, + }, + Self::CCZGate => match params { + [] => Some(aview2(&gate_matrix::CCZ_GATE).to_owned()), + _ => None, + }, + Self::CSwapGate => match params { + [] => Some(aview2(&gate_matrix::CSWAP_GATE).to_owned()), + _ => None, + }, Self::RCCXGate => match params { [] => Some(aview2(&gate_matrix::RCCX_GATE).to_owned()), _ => None, }, + Self::C3XGate => match params { + [] => Some(aview2(&gate_matrix::C3X_GATE).to_owned()), + _ => None, + }, + Self::C3SXGate => match params { + [] => Some(aview2(&gate_matrix::C3SX_GATE).to_owned()), + _ => None, + }, Self::RC3XGate => match params { [] => Some(aview2(&gate_matrix::RC3X_GATE).to_owned()), _ => None, @@ -629,14 +626,20 @@ impl Operation for StandardGate { fn definition(&self, params: &[Param]) -> Option { match self { - Self::ZGate => Python::with_gil(|py| -> Option { + Self::GlobalPhaseGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates(py, 0, [], params[0].clone()) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::HGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, 1, [( - Self::PhaseGate, - smallvec![Param::Float(PI)], + Self::UGate, + smallvec![Param::Float(PI / 2.), FLOAT_ZERO, Param::Float(PI)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -644,18 +647,15 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::YGate => Python::with_gil(|py| -> Option { + Self::IGate => None, + Self::XGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, 1, [( Self::UGate, - smallvec![ - Param::Float(PI), - Param::Float(PI / 2.), - Param::Float(PI / 2.), - ], + smallvec![Param::Float(PI), FLOAT_ZERO, Param::Float(PI)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -663,14 +663,18 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::XGate => Python::with_gil(|py| -> Option { + Self::YGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, 1, [( Self::UGate, - smallvec![Param::Float(PI), Param::Float(0.), Param::Float(PI)], + smallvec![ + Param::Float(PI), + Param::Float(PI / 2.), + Param::Float(PI / 2.), + ], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -678,69 +682,47 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::CZGate => Python::with_gil(|py| -> Option { - let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; + + Self::ZGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, - 2, - [ - (Self::HGate, smallvec![], q1.clone()), - (Self::CXGate, smallvec![], q0_1), - (Self::HGate, smallvec![], q1), - ], + 1, + [( + Self::PhaseGate, + smallvec![Param::Float(PI)], + smallvec![Qubit(0)], + )], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), ) }), - Self::CYGate => Python::with_gil(|py| -> Option { - let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; + Self::PhaseGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, - 2, - [ - (Self::SdgGate, smallvec![], q1.clone()), - (Self::CXGate, smallvec![], q0_1), - (Self::SGate, smallvec![], q1), - ], + 1, + [( + Self::UGate, + smallvec![FLOAT_ZERO, FLOAT_ZERO, params[0].clone()], + smallvec![Qubit(0)], + )], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), ) }), - Self::CXGate => None, - Self::CCXGate => Python::with_gil(|py| -> Option { - let q0 = smallvec![Qubit(0)]; - let q1 = smallvec![Qubit(1)]; - let q2 = smallvec![Qubit(2)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; - let q0_2 = smallvec![Qubit(0), Qubit(2)]; - let q1_2 = smallvec![Qubit(1), Qubit(2)]; + Self::RGate => Python::with_gil(|py| -> Option { + let theta_expr = clone_param(¶ms[0], py); + let phi_expr1 = add_param(¶ms[1], -PI / 2., py); + let phi_expr2 = multiply_param(&phi_expr1, -1.0, py); + let defparams = smallvec![theta_expr, phi_expr1, phi_expr2]; Some( CircuitData::from_standard_gates( py, - 3, - [ - (Self::HGate, smallvec![], q2.clone()), - (Self::CXGate, smallvec![], q1_2.clone()), - (Self::TdgGate, smallvec![], q2.clone()), - (Self::CXGate, smallvec![], q0_2.clone()), - (Self::TGate, smallvec![], q2.clone()), - (Self::CXGate, smallvec![], q1_2), - (Self::TdgGate, smallvec![], q2.clone()), - (Self::CXGate, smallvec![], q0_2), - (Self::TGate, smallvec![], q1.clone()), - (Self::TGate, smallvec![], q2.clone()), - (Self::HGate, smallvec![], q2), - (Self::CXGate, smallvec![], q0_1.clone()), - (Self::TGate, smallvec![], q0), - (Self::TdgGate, smallvec![], q1), - (Self::CXGate, smallvec![], q0_1), - ], + 1, + [(Self::UGate, defparams, smallvec![Qubit(0)])], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), @@ -770,7 +752,7 @@ impl Operation for StandardGate { 1, [( Self::RGate, - smallvec![theta.clone(), Param::Float(PI / 2.0)], + smallvec![theta.clone(), Param::Float(PI / 2.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -794,105 +776,31 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::CRXGate => Python::with_gil(|py| -> Option { - let theta = ¶ms[0]; - Some( - CircuitData::from_standard_gates( - py, - 2, - [ - ( - Self::PhaseGate, - smallvec![Param::Float(PI / 2.)], - smallvec![Qubit(1)], - ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), - ( - Self::UGate, - smallvec![ - multiply_param(theta, -0.5, py), - Param::Float(0.0), - Param::Float(0.0) - ], - smallvec![Qubit(1)], - ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), - ( - Self::UGate, - smallvec![ - multiply_param(theta, 0.5, py), - Param::Float(-PI / 2.), - Param::Float(0.0) - ], - smallvec![Qubit(1)], - ), - ], - Param::Float(0.0), - ) - .expect("Unexpected Qiskit Python bug!"), - ) - }), - Self::CRYGate => Python::with_gil(|py| -> Option { - let theta = ¶ms[0]; - Some( - CircuitData::from_standard_gates( - py, - 2, - [ - ( - Self::RYGate, - smallvec![multiply_param(theta, 0.5, py)], - smallvec![Qubit(1)], - ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), - ( - Self::RYGate, - smallvec![multiply_param(theta, -0.5, py)], - smallvec![Qubit(1)], - ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), - ], - Param::Float(0.0), - ) - .expect("Unexpected Qiskit Python bug!"), - ) - }), - Self::CRZGate => Python::with_gil(|py| -> Option { - let theta = ¶ms[0]; + Self::SGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, - 2, - [ - ( - Self::RZGate, - smallvec![multiply_param(theta, 0.5, py)], - smallvec![Qubit(1)], - ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), - ( - Self::RZGate, - smallvec![multiply_param(theta, -0.5, py)], - smallvec![Qubit(1)], - ), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), - ], - Param::Float(0.0), + 1, + [( + Self::PhaseGate, + smallvec![Param::Float(PI / 2.)], + smallvec![Qubit(0)], + )], + FLOAT_ZERO, ) - .expect("Unexpected Qiskit Python bug!"), + .expect("Unexpected Qiskit python bug"), ) }), - Self::ECRGate => todo!("Add when we have RZX"), - Self::SwapGate => Python::with_gil(|py| -> Option { + Self::SdgGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, - 2, - [ - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), - (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(0)]), - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), - ], + 1, + [( + Self::PhaseGate, + smallvec![Param::Float(-PI / 2.)], + smallvec![Qubit(0)], + )], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), @@ -928,21 +836,14 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::GlobalPhaseGate => Python::with_gil(|py| -> Option { - Some( - CircuitData::from_standard_gates(py, 0, [], params[0].clone()) - .expect("Unexpected Qiskit python bug"), - ) - }), - Self::IGate => None, - Self::HGate => Python::with_gil(|py| -> Option { + Self::TGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, 1, [( - Self::UGate, - smallvec![Param::Float(PI / 2.), Param::Float(0.), Param::Float(PI)], + Self::PhaseGate, + smallvec![Param::Float(PI / 4.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -950,14 +851,14 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::PhaseGate => Python::with_gil(|py| -> Option { + Self::TdgGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, 1, [( - Self::UGate, - smallvec![Param::Float(0.), Param::Float(0.), params[0].clone()], + Self::PhaseGate, + smallvec![Param::Float(-PI / 4.)], smallvec![Qubit(0)], )], FLOAT_ZERO, @@ -1011,61 +912,110 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::SGate => Python::with_gil(|py| -> Option { + Self::CHGate => Python::with_gil(|py| -> Option { + let q1 = smallvec![Qubit(1)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; Some( CircuitData::from_standard_gates( py, - 1, - [( - Self::PhaseGate, - smallvec![Param::Float(PI / 2.)], - smallvec![Qubit(0)], - )], + 2, + [ + (Self::SGate, smallvec![], q1.clone()), + (Self::HGate, smallvec![], q1.clone()), + (Self::TGate, smallvec![], q1.clone()), + (Self::CXGate, smallvec![], q0_1), + (Self::TdgGate, smallvec![], q1.clone()), + (Self::HGate, smallvec![], q1.clone()), + (Self::SdgGate, smallvec![], q1), + ], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), ) }), - Self::SdgGate => Python::with_gil(|py| -> Option { + + Self::CXGate => None, + Self::CYGate => Python::with_gil(|py| -> Option { + let q1 = smallvec![Qubit(1)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; Some( CircuitData::from_standard_gates( py, - 1, - [( - Self::PhaseGate, - smallvec![Param::Float(-PI / 2.)], - smallvec![Qubit(0)], - )], + 2, + [ + (Self::SdgGate, smallvec![], q1.clone()), + (Self::CXGate, smallvec![], q0_1), + (Self::SGate, smallvec![], q1), + ], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), ) }), - Self::TGate => Python::with_gil(|py| -> Option { + Self::CZGate => Python::with_gil(|py| -> Option { + let q1 = smallvec![Qubit(1)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; Some( CircuitData::from_standard_gates( py, - 1, - [( - Self::PhaseGate, - smallvec![Param::Float(PI / 4.)], - smallvec![Qubit(0)], - )], + 2, + [ + (Self::HGate, smallvec![], q1.clone()), + (Self::CXGate, smallvec![], q0_1), + (Self::HGate, smallvec![], q1), + ], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), ) }), - Self::TdgGate => Python::with_gil(|py| -> Option { + Self::DCXGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( py, - 1, - [( - Self::PhaseGate, - smallvec![Param::Float(-PI / 4.)], - smallvec![Qubit(0)], - )], + 2, + [ + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(0)]), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::ECRGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + ( + Self::RZXGate, + smallvec![Param::Float(PI / 4.)], + smallvec![Qubit(0), Qubit(1)], + ), + (Self::XGate, smallvec![], smallvec![Qubit(0)]), + ( + Self::RZXGate, + smallvec![Param::Float(-PI / 4.)], + smallvec![Qubit(0), Qubit(1)], + ), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::SwapGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(0)]), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), @@ -1089,82 +1039,185 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::XXMinusYYGate => Python::with_gil(|py| -> Option { + Self::CPhaseGate => Python::with_gil(|py| -> Option { let q0 = smallvec![Qubit(0)]; let q1 = smallvec![Qubit(1)]; let q0_1 = smallvec![Qubit(0), Qubit(1)]; - let theta = ¶ms[0]; - let beta = ¶ms[1]; Some( CircuitData::from_standard_gates( py, 2, [ ( - Self::RZGate, - smallvec![multiply_param(beta, -1.0, py)], - q1.clone(), + Self::PhaseGate, + smallvec![multiply_param(¶ms[0], 0.5, py)], + q0, ), - (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q0.clone()), - (Self::SXGate, smallvec![], q0.clone()), - (Self::RZGate, smallvec![Param::Float(PI / 2.)], q0.clone()), - (Self::SGate, smallvec![], q1.clone()), (Self::CXGate, smallvec![], q0_1.clone()), + ( + Self::PhaseGate, + smallvec![multiply_param(¶ms[0], -0.5, py)], + q1.clone(), + ), + (Self::CXGate, smallvec![], q0_1), + ( + Self::PhaseGate, + smallvec![multiply_param(¶ms[0], 0.5, py)], + q1, + ), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::CRXGate => Python::with_gil(|py| -> Option { + let theta = ¶ms[0]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + ( + Self::PhaseGate, + smallvec![Param::Float(PI / 2.)], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::UGate, + smallvec![ + multiply_param(theta, -0.5, py), + Param::Float(0.0), + Param::Float(0.0) + ], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::UGate, + smallvec![ + multiply_param(theta, 0.5, py), + Param::Float(-PI / 2.), + Param::Float(0.0) + ], + smallvec![Qubit(1)], + ), + ], + Param::Float(0.0), + ) + .expect("Unexpected Qiskit Python bug!"), + ) + }), + Self::CRYGate => Python::with_gil(|py| -> Option { + let theta = ¶ms[0]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ ( Self::RYGate, smallvec![multiply_param(theta, 0.5, py)], - q0.clone(), + smallvec![Qubit(1)], ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), ( Self::RYGate, smallvec![multiply_param(theta, -0.5, py)], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ], + Param::Float(0.0), + ) + .expect("Unexpected Qiskit Python bug!"), + ) + }), + Self::CRZGate => Python::with_gil(|py| -> Option { + let theta = ¶ms[0]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + ( + Self::RZGate, + smallvec![multiply_param(theta, 0.5, py)], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ( + Self::RZGate, + smallvec![multiply_param(theta, -0.5, py)], + smallvec![Qubit(1)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), + ], + Param::Float(0.0), + ) + .expect("Unexpected Qiskit Python bug!"), + ) + }), + Self::CSGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::PhaseGate, smallvec![Param::Float(PI / 4.)], q0), + (Self::CXGate, smallvec![], q0_1.clone()), + ( + Self::PhaseGate, + smallvec![Param::Float(-PI / 4.)], q1.clone(), ), (Self::CXGate, smallvec![], q0_1), - (Self::SdgGate, smallvec![], q1.clone()), - (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q0.clone()), - (Self::SXdgGate, smallvec![], q0.clone()), - (Self::RZGate, smallvec![Param::Float(PI / 2.)], q0), - (Self::RZGate, smallvec![beta.clone()], q1), + (Self::PhaseGate, smallvec![Param::Float(PI / 4.)], q1), ], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), ) }), - Self::XXPlusYYGate => Python::with_gil(|py| -> Option { + Self::CSdgGate => Python::with_gil(|py| -> Option { let q0 = smallvec![Qubit(0)]; let q1 = smallvec![Qubit(1)]; - let q1_0 = smallvec![Qubit(1), Qubit(0)]; - let theta = ¶ms[0]; - let beta = ¶ms[1]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; Some( CircuitData::from_standard_gates( py, 2, [ - (Self::RZGate, smallvec![beta.clone()], q0.clone()), - (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q1.clone()), - (Self::SXGate, smallvec![], q1.clone()), - (Self::RZGate, smallvec![Param::Float(PI / 2.)], q1.clone()), - (Self::SGate, smallvec![], q0.clone()), - (Self::CXGate, smallvec![], q1_0.clone()), + (Self::PhaseGate, smallvec![Param::Float(-PI / 4.)], q0), + (Self::CXGate, smallvec![], q0_1.clone()), ( - Self::RYGate, - smallvec![multiply_param(theta, -0.5, py)], + Self::PhaseGate, + smallvec![Param::Float(PI / 4.)], q1.clone(), ), - ( - Self::RYGate, - smallvec![multiply_param(theta, -0.5, py)], - q0.clone(), - ), - (Self::CXGate, smallvec![], q1_0), - (Self::SdgGate, smallvec![], q0.clone()), - (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q1.clone()), - (Self::SXdgGate, smallvec![], q1.clone()), - (Self::RZGate, smallvec![Param::Float(PI / 2.)], q1), - (Self::RZGate, smallvec![multiply_param(beta, -1.0, py)], q0), + (Self::CXGate, smallvec![], q0_1), + (Self::PhaseGate, smallvec![Param::Float(-PI / 4.)], q1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::CSXGate => Python::with_gil(|py| -> Option { + let q1 = smallvec![Qubit(1)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::HGate, smallvec![], q1.clone()), + (Self::CPhaseGate, smallvec![Param::Float(PI / 2.)], q0_1), + (Self::HGate, smallvec![], q1), ], FLOAT_ZERO, ) @@ -1212,7 +1265,7 @@ impl Operation for StandardGate { Self::UGate, smallvec![ multiply_param(¶ms[0], -0.5, py), - Param::Float(0.), + FLOAT_ZERO, param_first_u ], smallvec![Qubit(1)], @@ -1223,7 +1276,7 @@ impl Operation for StandardGate { smallvec![ multiply_param(¶ms[0], 0.5, py), params[1].clone(), - Param::Float(0.) + FLOAT_ZERO ], smallvec![Qubit(1)], ), @@ -1233,27 +1286,6 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::CHGate => Python::with_gil(|py| -> Option { - let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; - Some( - CircuitData::from_standard_gates( - py, - 2, - [ - (Self::SGate, smallvec![], q1.clone()), - (Self::HGate, smallvec![], q1.clone()), - (Self::TGate, smallvec![], q1.clone()), - (Self::CXGate, smallvec![], q0_1), - (Self::TdgGate, smallvec![], q1.clone()), - (Self::HGate, smallvec![], q1.clone()), - (Self::SdgGate, smallvec![], q1), - ], - FLOAT_ZERO, - ) - .expect("Unexpected Qiskit python bug"), - ) - }), Self::CU1Gate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( @@ -1283,38 +1315,6 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::CPhaseGate => Python::with_gil(|py| -> Option { - let q0 = smallvec![Qubit(0)]; - let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; - Some( - CircuitData::from_standard_gates( - py, - 2, - [ - ( - Self::PhaseGate, - smallvec![multiply_param(¶ms[0], 0.5, py)], - q0, - ), - (Self::CXGate, smallvec![], q0_1.clone()), - ( - Self::PhaseGate, - smallvec![multiply_param(¶ms[0], -0.5, py)], - q1.clone(), - ), - (Self::CXGate, smallvec![], q0_1), - ( - Self::PhaseGate, - smallvec![multiply_param(¶ms[0], 0.5, py)], - q1, - ), - ], - FLOAT_ZERO, - ) - .expect("Unexpected Qiskit python bug"), - ) - }), Self::CU3Gate => Python::with_gil(|py| -> Option { let param_first_u1 = radd_param( multiply_param(¶ms[2], 0.5, py), @@ -1347,7 +1347,7 @@ impl Operation for StandardGate { Self::U3Gate, smallvec![ multiply_param(¶ms[0], -0.5, py), - Param::Float(0.), + FLOAT_ZERO, param_first_u3 ], smallvec![Qubit(1)], @@ -1358,7 +1358,7 @@ impl Operation for StandardGate { smallvec![ multiply_param(¶ms[0], 0.5, py), params[1].clone(), - Param::Float(0.) + FLOAT_ZERO ], smallvec![Qubit(1)], ), @@ -1368,33 +1368,276 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::CSGate => Python::with_gil(|py| -> Option { + Self::RXXGate => Python::with_gil(|py| -> Option { let q0 = smallvec![Qubit(0)]; let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let q0_q1 = smallvec![Qubit(0), Qubit(1)]; + let theta = ¶ms[0]; Some( CircuitData::from_standard_gates( py, 2, [ - (Self::PhaseGate, smallvec![Param::Float(PI / 4.)], q0), - (Self::CXGate, smallvec![], q0_1.clone()), - ( - Self::PhaseGate, - smallvec![Param::Float(-PI / 4.)], - q1.clone(), - ), - (Self::CXGate, smallvec![], q0_1), - (Self::PhaseGate, smallvec![Param::Float(PI / 4.)], q1), + (Self::HGate, smallvec![], q0.clone()), + (Self::HGate, smallvec![], q1.clone()), + (Self::CXGate, smallvec![], q0_q1.clone()), + (Self::RZGate, smallvec![theta.clone()], q1.clone()), + (Self::CXGate, smallvec![], q0_q1), + (Self::HGate, smallvec![], q1), + (Self::HGate, smallvec![], q0), ], FLOAT_ZERO, ) .expect("Unexpected Qiskit python bug"), ) }), - Self::C3XGate => Python::with_gil(|py| -> Option { - Some( - CircuitData::from_standard_gates( + Self::RYYGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q0_q1 = smallvec![Qubit(0), Qubit(1)]; + let theta = ¶ms[0]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::RXGate, smallvec![Param::Float(PI / 2.)], q0.clone()), + (Self::RXGate, smallvec![Param::Float(PI / 2.)], q1.clone()), + (Self::CXGate, smallvec![], q0_q1.clone()), + (Self::RZGate, smallvec![theta.clone()], q1.clone()), + (Self::CXGate, smallvec![], q0_q1), + (Self::RXGate, smallvec![Param::Float(-PI / 2.)], q0), + (Self::RXGate, smallvec![Param::Float(-PI / 2.)], q1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::RZZGate => Python::with_gil(|py| -> Option { + let q1 = smallvec![Qubit(1)]; + let q0_q1 = smallvec![Qubit(0), Qubit(1)]; + let theta = ¶ms[0]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::CXGate, smallvec![], q0_q1.clone()), + (Self::RZGate, smallvec![theta.clone()], q1), + (Self::CXGate, smallvec![], q0_q1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::RZXGate => Python::with_gil(|py| -> Option { + let q1 = smallvec![Qubit(1)]; + let q0_q1 = smallvec![Qubit(0), Qubit(1)]; + let theta = ¶ms[0]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::HGate, smallvec![], q1.clone()), + (Self::CXGate, smallvec![], q0_q1.clone()), + (Self::RZGate, smallvec![theta.clone()], q1.clone()), + (Self::CXGate, smallvec![], q0_q1), + (Self::HGate, smallvec![], q1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::XXMinusYYGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let theta = ¶ms[0]; + let beta = ¶ms[1]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + ( + Self::RZGate, + smallvec![multiply_param(beta, -1.0, py)], + q1.clone(), + ), + (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q0.clone()), + (Self::SXGate, smallvec![], q0.clone()), + (Self::RZGate, smallvec![Param::Float(PI / 2.)], q0.clone()), + (Self::SGate, smallvec![], q1.clone()), + (Self::CXGate, smallvec![], q0_1.clone()), + ( + Self::RYGate, + smallvec![multiply_param(theta, 0.5, py)], + q0.clone(), + ), + ( + Self::RYGate, + smallvec![multiply_param(theta, -0.5, py)], + q1.clone(), + ), + (Self::CXGate, smallvec![], q0_1), + (Self::SdgGate, smallvec![], q1.clone()), + (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q0.clone()), + (Self::SXdgGate, smallvec![], q0.clone()), + (Self::RZGate, smallvec![Param::Float(PI / 2.)], q0), + (Self::RZGate, smallvec![beta.clone()], q1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::XXPlusYYGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q1_0 = smallvec![Qubit(1), Qubit(0)]; + let theta = ¶ms[0]; + let beta = ¶ms[1]; + Some( + CircuitData::from_standard_gates( + py, + 2, + [ + (Self::RZGate, smallvec![beta.clone()], q0.clone()), + (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q1.clone()), + (Self::SXGate, smallvec![], q1.clone()), + (Self::RZGate, smallvec![Param::Float(PI / 2.)], q1.clone()), + (Self::SGate, smallvec![], q0.clone()), + (Self::CXGate, smallvec![], q1_0.clone()), + ( + Self::RYGate, + smallvec![multiply_param(theta, -0.5, py)], + q1.clone(), + ), + ( + Self::RYGate, + smallvec![multiply_param(theta, -0.5, py)], + q0.clone(), + ), + (Self::CXGate, smallvec![], q1_0), + (Self::SdgGate, smallvec![], q0.clone()), + (Self::RZGate, smallvec![Param::Float(-PI / 2.)], q1.clone()), + (Self::SXdgGate, smallvec![], q1.clone()), + (Self::RZGate, smallvec![Param::Float(PI / 2.)], q1), + (Self::RZGate, smallvec![multiply_param(beta, -1.0, py)], q0), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::CCXGate => Python::with_gil(|py| -> Option { + let q0 = smallvec![Qubit(0)]; + let q1 = smallvec![Qubit(1)]; + let q2 = smallvec![Qubit(2)]; + let q0_1 = smallvec![Qubit(0), Qubit(1)]; + let q0_2 = smallvec![Qubit(0), Qubit(2)]; + let q1_2 = smallvec![Qubit(1), Qubit(2)]; + Some( + CircuitData::from_standard_gates( + py, + 3, + [ + (Self::HGate, smallvec![], q2.clone()), + (Self::CXGate, smallvec![], q1_2.clone()), + (Self::TdgGate, smallvec![], q2.clone()), + (Self::CXGate, smallvec![], q0_2.clone()), + (Self::TGate, smallvec![], q2.clone()), + (Self::CXGate, smallvec![], q1_2), + (Self::TdgGate, smallvec![], q2.clone()), + (Self::CXGate, smallvec![], q0_2), + (Self::TGate, smallvec![], q1.clone()), + (Self::TGate, smallvec![], q2.clone()), + (Self::HGate, smallvec![], q2), + (Self::CXGate, smallvec![], q0_1.clone()), + (Self::TGate, smallvec![], q0), + (Self::TdgGate, smallvec![], q1), + (Self::CXGate, smallvec![], q0_1), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + + Self::CCZGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 3, + [ + (Self::HGate, smallvec![], smallvec![Qubit(2)]), + ( + Self::CCXGate, + smallvec![], + smallvec![Qubit(0), Qubit(1), Qubit(2)], + ), + (Self::HGate, smallvec![], smallvec![Qubit(2)]), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::CSwapGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( + py, + 3, + [ + (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(1)]), + ( + Self::CCXGate, + smallvec![], + smallvec![Qubit(0), Qubit(1), Qubit(2)], + ), + (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(1)]), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + + Self::RCCXGate => Python::with_gil(|py| -> Option { + let q2 = smallvec![Qubit(2)]; + let q0_2 = smallvec![Qubit(0), Qubit(2)]; + let q1_2 = smallvec![Qubit(1), Qubit(2)]; + Some( + CircuitData::from_standard_gates( + py, + 3, + [ + ( + Self::U2Gate, + smallvec![FLOAT_ZERO, Param::Float(PI)], + q2.clone(), + ), + (Self::U1Gate, smallvec![Param::Float(PI / 4.)], q2.clone()), + (Self::CXGate, smallvec![], q1_2.clone()), + (Self::U1Gate, smallvec![Param::Float(-PI / 4.)], q2.clone()), + (Self::CXGate, smallvec![], q0_2), + (Self::U1Gate, smallvec![Param::Float(PI / 4.)], q2.clone()), + (Self::CXGate, smallvec![], q1_2), + (Self::U1Gate, smallvec![Param::Float(-PI / 4.)], q2.clone()), + (Self::U2Gate, smallvec![FLOAT_ZERO, Param::Float(PI)], q2), + ], + FLOAT_ZERO, + ) + .expect("Unexpected Qiskit python bug"), + ) + }), + Self::C3XGate => Python::with_gil(|py| -> Option { + Some( + CircuitData::from_standard_gates( py, 4, [ @@ -1495,30 +1738,7 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::CSdgGate => Python::with_gil(|py| -> Option { - let q0 = smallvec![Qubit(0)]; - let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; - Some( - CircuitData::from_standard_gates( - py, - 2, - [ - (Self::PhaseGate, smallvec![Param::Float(-PI / 4.)], q0), - (Self::CXGate, smallvec![], q0_1.clone()), - ( - Self::PhaseGate, - smallvec![Param::Float(PI / 4.)], - q1.clone(), - ), - (Self::CXGate, smallvec![], q0_1), - (Self::PhaseGate, smallvec![Param::Float(-PI / 4.)], q1), - ], - FLOAT_ZERO, - ) - .expect("Unexpected Qiskit python bug"), - ) - }), + Self::C3SXGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( @@ -1586,207 +1806,6 @@ impl Operation for StandardGate { .expect("Unexpected Qiskit python bug"), ) }), - Self::CSXGate => Python::with_gil(|py| -> Option { - let q1 = smallvec![Qubit(1)]; - let q0_1 = smallvec![Qubit(0), Qubit(1)]; - Some( - CircuitData::from_standard_gates( - py, - 2, - [ - (Self::HGate, smallvec![], q1.clone()), - (Self::CPhaseGate, smallvec![Param::Float(PI / 2.)], q0_1), - (Self::HGate, smallvec![], q1), - ], - FLOAT_ZERO, - ) - .expect("Unexpected Qiskit python bug"), - ) - }), - Self::CCZGate => Python::with_gil(|py| -> Option { - Some( - CircuitData::from_standard_gates( - py, - 3, - [ - (Self::HGate, smallvec![], smallvec![Qubit(2)]), - ( - Self::CCXGate, - smallvec![], - smallvec![Qubit(0), Qubit(1), Qubit(2)], - ), - (Self::HGate, smallvec![], smallvec![Qubit(2)]), - ], - FLOAT_ZERO, - ) - .expect("Unexpected Qiskit python bug"), - ) - }), - Self::CSwapGate => Python::with_gil(|py| -> Option { - Some( - CircuitData::from_standard_gates( - py, - 3, - [ - (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(1)]), - ( - Self::CCXGate, - smallvec![], - smallvec![Qubit(0), Qubit(1), Qubit(2)], - ), - (Self::CXGate, smallvec![], smallvec![Qubit(2), Qubit(1)]), - ], - FLOAT_ZERO, - ) - .expect("Unexpected Qiskit python bug"), - ) - }), - Self::RGate => Python::with_gil(|py| -> Option { - let theta_expr = clone_param(¶ms[0], py); - let phi_expr1 = add_param(¶ms[1], -PI / 2., py); - let phi_expr2 = multiply_param(&phi_expr1, -1.0, py); - let defparams = smallvec![theta_expr, phi_expr1, phi_expr2]; - Some( - CircuitData::from_standard_gates( - py, - 1, - [(Self::UGate, defparams, smallvec![Qubit(0)])], - FLOAT_ZERO, - ) - .expect("Unexpected Qiskit python bug"), - ) - }), - Self::C4XGate => todo!(), - Self::DCXGate => Python::with_gil(|py| -> Option { - Some( - CircuitData::from_standard_gates( - py, - 2, - [ - (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(1)]), - (Self::CXGate, smallvec![], smallvec![Qubit(1), Qubit(0)]), - ], - FLOAT_ZERO, - ) - .expect("Unexpected Qiskit python bug"), - ) - }), - Self::RXXGate => Python::with_gil(|py| -> Option { - let q0 = smallvec![Qubit(0)]; - let q1 = smallvec![Qubit(1)]; - let q0_q1 = smallvec![Qubit(0), Qubit(1)]; - let theta = ¶ms[0]; - Some( - CircuitData::from_standard_gates( - py, - 2, - [ - (Self::HGate, smallvec![], q0.clone()), - (Self::HGate, smallvec![], q1.clone()), - (Self::CXGate, smallvec![], q0_q1.clone()), - (Self::RZGate, smallvec![theta.clone()], q1.clone()), - (Self::CXGate, smallvec![], q0_q1), - (Self::HGate, smallvec![], q1), - (Self::HGate, smallvec![], q0), - ], - FLOAT_ZERO, - ) - .expect("Unexpected Qiskit python bug"), - ) - }), - Self::RYYGate => Python::with_gil(|py| -> Option { - let q0 = smallvec![Qubit(0)]; - let q1 = smallvec![Qubit(1)]; - let q0_q1 = smallvec![Qubit(0), Qubit(1)]; - let theta = ¶ms[0]; - Some( - CircuitData::from_standard_gates( - py, - 2, - [ - (Self::RXGate, smallvec![Param::Float(PI / 2.)], q0.clone()), - (Self::RXGate, smallvec![Param::Float(PI / 2.)], q1.clone()), - (Self::CXGate, smallvec![], q0_q1.clone()), - (Self::RZGate, smallvec![theta.clone()], q1.clone()), - (Self::CXGate, smallvec![], q0_q1), - (Self::RXGate, smallvec![Param::Float(-PI / 2.)], q0), - (Self::RXGate, smallvec![Param::Float(-PI / 2.)], q1), - ], - FLOAT_ZERO, - ) - .expect("Unexpected Qiskit python bug"), - ) - }), - Self::RZZGate => Python::with_gil(|py| -> Option { - let q1 = smallvec![Qubit(1)]; - let q0_q1 = smallvec![Qubit(0), Qubit(1)]; - let theta = ¶ms[0]; - Some( - CircuitData::from_standard_gates( - py, - 2, - [ - (Self::CXGate, smallvec![], q0_q1.clone()), - (Self::RZGate, smallvec![theta.clone()], q1), - (Self::CXGate, smallvec![], q0_q1), - ], - FLOAT_ZERO, - ) - .expect("Unexpected Qiskit python bug"), - ) - }), - Self::RZXGate => Python::with_gil(|py| -> Option { - let q1 = smallvec![Qubit(1)]; - let q0_q1 = smallvec![Qubit(0), Qubit(1)]; - let theta = ¶ms[0]; - Some( - CircuitData::from_standard_gates( - py, - 2, - [ - (Self::HGate, smallvec![], q1.clone()), - (Self::CXGate, smallvec![], q0_q1.clone()), - (Self::RZGate, smallvec![theta.clone()], q1.clone()), - (Self::CXGate, smallvec![], q0_q1), - (Self::HGate, smallvec![], q1), - ], - FLOAT_ZERO, - ) - .expect("Unexpected Qiskit python bug"), - ) - }), - Self::RCCXGate => Python::with_gil(|py| -> Option { - let q2 = smallvec![Qubit(2)]; - let q0_2 = smallvec![Qubit(0), Qubit(2)]; - let q1_2 = smallvec![Qubit(1), Qubit(2)]; - Some( - CircuitData::from_standard_gates( - py, - 3, - [ - ( - Self::U2Gate, - smallvec![Param::Float(0.), Param::Float(PI)], - q2.clone(), - ), - (Self::U1Gate, smallvec![Param::Float(PI / 4.)], q2.clone()), - (Self::CXGate, smallvec![], q1_2.clone()), - (Self::U1Gate, smallvec![Param::Float(-PI / 4.)], q2.clone()), - (Self::CXGate, smallvec![], q0_2), - (Self::U1Gate, smallvec![Param::Float(PI / 4.)], q2.clone()), - (Self::CXGate, smallvec![], q1_2), - (Self::U1Gate, smallvec![Param::Float(-PI / 4.)], q2.clone()), - ( - Self::U2Gate, - smallvec![Param::Float(0.), Param::Float(PI)], - q2, - ), - ], - FLOAT_ZERO, - ) - .expect("Unexpected Qiskit python bug"), - ) - }), Self::RC3XGate => Python::with_gil(|py| -> Option { Some( CircuitData::from_standard_gates( @@ -1795,7 +1814,7 @@ impl Operation for StandardGate { [ ( Self::U2Gate, - smallvec![Param::Float(0.), Param::Float(PI)], + smallvec![FLOAT_ZERO, Param::Float(PI)], smallvec![Qubit(3)], ), ( @@ -1811,7 +1830,7 @@ impl Operation for StandardGate { ), ( Self::U2Gate, - smallvec![Param::Float(0.), Param::Float(PI)], + smallvec![FLOAT_ZERO, Param::Float(PI)], smallvec![Qubit(3)], ), (Self::CXGate, smallvec![], smallvec![Qubit(0), Qubit(3)]), @@ -1840,7 +1859,7 @@ impl Operation for StandardGate { ), ( Self::U2Gate, - smallvec![Param::Float(0.), Param::Float(PI)], + smallvec![FLOAT_ZERO, Param::Float(PI)], smallvec![Qubit(3)], ), ( @@ -1856,7 +1875,7 @@ impl Operation for StandardGate { ), ( Self::U2Gate, - smallvec![Param::Float(0.), Param::Float(PI)], + smallvec![FLOAT_ZERO, Param::Float(PI)], smallvec![Qubit(3)], ), ], diff --git a/test/python/circuit/test_rust_equivalence.py b/test/python/circuit/test_rust_equivalence.py index 29cc0723bb60..c226ae004896 100644 --- a/test/python/circuit/test_rust_equivalence.py +++ b/test/python/circuit/test_rust_equivalence.py @@ -24,7 +24,6 @@ from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping from qiskit.quantum_info import Operator -SKIP_LIST = {"rx", "ry", "ecr"} CUSTOM_NAME_MAPPING = {"mcx": C3XGate()} @@ -62,9 +61,6 @@ def test_definitions(self): """Test definitions are the same in rust space.""" for name, gate_class in self.standard_gates.items(): standard_gate = getattr(gate_class, "_standard_gate", None) - if name in SKIP_LIST: - # gate does not have a rust definition yet - continue if standard_gate is None: # gate is not in rust yet continue @@ -157,9 +153,6 @@ def test_name(self): if standard_gate is None: # gate is not in rust yet continue - if gate_class.name == "mcx": - # ambiguous gate name - continue with self.subTest(name=name): self.assertEqual(gate_class.name, standard_gate.name) From 1191fcbc7277216ce03d30f1bf0d2767ea681cd9 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 12 Jul 2024 22:58:10 -0400 Subject: [PATCH 12/12] Oxidize two qubit local invariance functions (#12739) * Oxidize two qubit local invariance functions This commit migrates the two functions in the private module `qiskit.synthesis.two_qubit.local_invariance` to primarily be implemented in rust. Since the two_qubit_local_invariants() function is being used in #12727 premptively porting these functions to rust will both potentially speed up the new transpiler pass in that PR and also facilitate a future porting of that pass to rust. The two python space functions continue to exist as a small wrapper to do input type checking/conversion and rounding of the result (since the python API for rounding is simpler). There is no release note included for these functions as they are internal utilities in Qiskit and not exposed as a public interface. * Add docstring to rust function with citation * Store adjoint magic array statically * Use arraview1 instead of slice for local_equivalence() * Fix rustfmt --- crates/accelerate/src/two_qubit_decompose.rs | 103 +++++++++++++++++- .../synthesis/two_qubit/local_invariance.py | 46 ++------ 2 files changed, 110 insertions(+), 39 deletions(-) diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index 568206925c2b..ac2cc1d2e50e 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -31,8 +31,8 @@ use faer_ext::{IntoFaer, IntoFaerComplex, IntoNdarray, IntoNdarrayComplex}; use ndarray::linalg::kron; use ndarray::prelude::*; use ndarray::Zip; -use numpy::PyReadonlyArray2; use numpy::{IntoPyArray, ToPyArray}; +use numpy::{PyReadonlyArray1, PyReadonlyArray2}; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; @@ -1883,9 +1883,110 @@ impl TwoQubitBasisDecomposer { } } +static MAGIC: GateArray2Q = [ + [ + c64(FRAC_1_SQRT_2, 0.), + C_ZERO, + C_ZERO, + c64(0., FRAC_1_SQRT_2), + ], + [ + C_ZERO, + c64(0., FRAC_1_SQRT_2), + c64(FRAC_1_SQRT_2, 0.), + C_ZERO, + ], + [ + C_ZERO, + c64(0., FRAC_1_SQRT_2), + c64(-FRAC_1_SQRT_2, 0.), + C_ZERO, + ], + [ + c64(FRAC_1_SQRT_2, 0.), + C_ZERO, + C_ZERO, + c64(0., -FRAC_1_SQRT_2), + ], +]; + +static MAGIC_DAGGER: GateArray2Q = [ + [ + c64(FRAC_1_SQRT_2, 0.), + C_ZERO, + C_ZERO, + c64(FRAC_1_SQRT_2, 0.), + ], + [ + C_ZERO, + c64(0., -FRAC_1_SQRT_2), + c64(0., -FRAC_1_SQRT_2), + C_ZERO, + ], + [ + C_ZERO, + c64(FRAC_1_SQRT_2, 0.), + c64(-FRAC_1_SQRT_2, 0.), + C_ZERO, + ], + [ + c64(0., -FRAC_1_SQRT_2), + C_ZERO, + C_ZERO, + c64(0., FRAC_1_SQRT_2), + ], +]; + +/// Computes the local invariants for a two-qubit unitary. +/// +/// Based on: +/// +/// Y. Makhlin, Quant. Info. Proc. 1, 243-252 (2002). +/// +/// Zhang et al., Phys Rev A. 67, 042313 (2003). +#[pyfunction] +pub fn two_qubit_local_invariants(unitary: PyReadonlyArray2) -> [f64; 3] { + let mat = unitary.as_array(); + // Transform to bell basis + let bell_basis_unitary = aview2(&MAGIC_DAGGER).dot(&mat.dot(&aview2(&MAGIC))); + // Get determinate since +- one is allowed. + let det_bell_basis = bell_basis_unitary + .view() + .into_faer_complex() + .determinant() + .to_num_complex(); + let m = bell_basis_unitary.t().dot(&bell_basis_unitary); + let mut m_tr2 = m.diag().sum(); + m_tr2 *= m_tr2; + // Table II of Ref. 1 or Eq. 28 of Ref. 2. + let g1 = m_tr2 / (16. * det_bell_basis); + let g2 = (m_tr2 - m.dot(&m).diag().sum()) / (4. * det_bell_basis); + + // Here we split the real and imag pieces of G1 into two so as + // to better equate to the Weyl chamber coordinates (c0,c1,c2) + // and explore the parameter space. + // Also do a FP trick -0.0 + 0.0 = 0.0 + [g1.re + 0., g1.im + 0., g2.re + 0.] +} + +#[pyfunction] +pub fn local_equivalence(weyl: PyReadonlyArray1) -> PyResult<[f64; 3]> { + let weyl = weyl.as_array(); + let weyl_2_cos_squared_product: f64 = weyl.iter().map(|x| (x * 2.).cos().powi(2)).product(); + let weyl_2_sin_squared_product: f64 = weyl.iter().map(|x| (x * 2.).sin().powi(2)).product(); + let g0_equiv = weyl_2_cos_squared_product - weyl_2_sin_squared_product; + let g1_equiv = weyl.iter().map(|x| (x * 4.).sin()).product::() / 4.; + let g2_equiv = 4. * weyl_2_cos_squared_product + - 4. * weyl_2_sin_squared_product + - weyl.iter().map(|x| (4. * x).cos()).product::(); + Ok([g0_equiv + 0., g1_equiv + 0., g2_equiv + 0.]) +} + #[pymodule] pub fn two_qubit_decompose(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(_num_basis_gates))?; + m.add_wrapped(wrap_pyfunction!(two_qubit_local_invariants))?; + m.add_wrapped(wrap_pyfunction!(local_equivalence))?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/qiskit/synthesis/two_qubit/local_invariance.py b/qiskit/synthesis/two_qubit/local_invariance.py index 346febdddf9f..7b20c2e5655e 100644 --- a/qiskit/synthesis/two_qubit/local_invariance.py +++ b/qiskit/synthesis/two_qubit/local_invariance.py @@ -15,17 +15,9 @@ of two-qubit unitary operators. """ from __future__ import annotations -from math import sqrt import numpy as np - -INVARIANT_TOL = 1e-12 - -# Bell "Magic" basis -MAGIC = ( - 1.0 - / sqrt(2) - * np.array([[1, 0, 0, 1j], [0, 1j, 1, 0], [0, 1j, -1, 0], [1, 0, 0, -1j]], dtype=complex) -) +from qiskit._accelerate.two_qubit_decompose import two_qubit_local_invariants as tqli_rs +from qiskit._accelerate.two_qubit_decompose import local_equivalence as le_rs def two_qubit_local_invariants(U: np.ndarray) -> np.ndarray: @@ -44,28 +36,11 @@ def two_qubit_local_invariants(U: np.ndarray) -> np.ndarray: Y. Makhlin, Quant. Info. Proc. 1, 243-252 (2002). Zhang et al., Phys Rev A. 67, 042313 (2003). """ - U = np.asarray(U) + U = np.asarray(U, dtype=complex) if U.shape != (4, 4): raise ValueError("Unitary must correspond to a two-qubit gate.") - - # Transform to bell basis - Um = MAGIC.conj().T.dot(U.dot(MAGIC)) - # Get determinate since +- one is allowed. - det_um = np.linalg.det(Um) - M = Um.T.dot(Um) - # trace(M)**2 - m_tr2 = M.trace() - m_tr2 *= m_tr2 - - # Table II of Ref. 1 or Eq. 28 of Ref. 2. - G1 = m_tr2 / (16 * det_um) - G2 = (m_tr2 - np.trace(M.dot(M))) / (4 * det_um) - - # Here we split the real and imag pieces of G1 into two so as - # to better equate to the Weyl chamber coordinates (c0,c1,c2) - # and explore the parameter space. - # Also do a FP trick -0.0 + 0.0 = 0.0 - return np.round([G1.real, G1.imag, G2.real], 12) + 0.0 + (a, b, c) = tqli_rs(U) + return np.array([round(a, 12), round(b, 12), round(c, 12)]) def local_equivalence(weyl: np.ndarray) -> np.ndarray: @@ -83,11 +58,6 @@ def local_equivalence(weyl: np.ndarray) -> np.ndarray: but we multiply weyl coordinates by 2 since we are working in the reduced chamber. """ - g0_equiv = np.prod(np.cos(2 * weyl) ** 2) - np.prod(np.sin(2 * weyl) ** 2) - g1_equiv = np.prod(np.sin(4 * weyl)) / 4 - g2_equiv = ( - 4 * np.prod(np.cos(2 * weyl) ** 2) - - 4 * np.prod(np.sin(2 * weyl) ** 2) - - np.prod(np.cos(4 * weyl)) - ) - return np.round([g0_equiv, g1_equiv, g2_equiv], 12) + 0.0 + mat = np.asarray(weyl, dtype=float) + (a, b, c) = le_rs(mat) + return np.array([round(a, 12), round(b, 12), round(c, 12)])