From 9d475cb0d6dcf2c835a097c5e11778fe51123e8e Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Thu, 12 Jan 2023 18:50:39 -0500 Subject: [PATCH 01/25] add siswap gate to standard gates --- qiskit/circuit/library/__init__.py | 1 + .../library/standard_gates/__init__.py | 3 + .../circuit/library/standard_gates/siswap.py | 73 +++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 qiskit/circuit/library/standard_gates/siswap.py diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index 5657604c1ccd..724fb64956d1 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -110,6 +110,7 @@ SdgGate SwapGate iSwapGate + SiSwapGate SXGate SXdgGate TGate diff --git a/qiskit/circuit/library/standard_gates/__init__.py b/qiskit/circuit/library/standard_gates/__init__.py index 3dca1abb6201..2b44614f03c5 100644 --- a/qiskit/circuit/library/standard_gates/__init__.py +++ b/qiskit/circuit/library/standard_gates/__init__.py @@ -59,6 +59,7 @@ CSdgGate SwapGate iSwapGate + SiSwapGate SXGate SXdgGate TGate @@ -90,6 +91,7 @@ from .s import SGate, SdgGate, CSGate, CSdgGate from .swap import SwapGate, CSwapGate from .iswap import iSwapGate +from .sqisw import SiSwapGate from .sx import SXGate, SXdgGate, CSXGate from .dcx import DCXGate from .t import TGate, TdgGate @@ -158,6 +160,7 @@ def get_standard_gate_name_mapping(): CSdgGate(), SwapGate(), iSwapGate(), + SiSwapGate(), SXdgGate(), TGate(), TdgGate(), diff --git a/qiskit/circuit/library/standard_gates/siswap.py b/qiskit/circuit/library/standard_gates/siswap.py new file mode 100644 index 000000000000..f6616eb97c27 --- /dev/null +++ b/qiskit/circuit/library/standard_gates/siswap.py @@ -0,0 +1,73 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""sqrt(iSWAP) gate.""" + +import numpy as np +from qiskit.circuit.gate import Gate +from qiskit.circuit.quantumregister import QuantumRegister + + +class SiSwapGate(Gate): + r"""sqrt(iSWAP) gate. + + A 2-qubit symmetric gate from the iSWAP (or XY) family. + It has Weyl chamber coordinates (π/8, π/8, 0). + + Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` + with the :meth:`~qiskit.circuit.QuantumCircuit.sqisw` method. + + .. parsed-literal:: + + ┌────────────┐┌────────────┐ + q_0: ┤0 ├┤0 ├ + │ Rxx(-π/4) ││ Ryy(-π/4) │ + q_1: ┤1 ├┤1 ├ + └────────────┘└────────────┘ + + .. math:: + B\ q_0, q_1 = + \begin{pmatrix} + 1 & 0 & 0 & 0 \\ + 0 & \frac{1}{sqrt(2)} & \frac{i}{sqrt(2)} & 0 \\ + 0 & \frac{i}{sqrt(2)} & \frac{1}{sqrt(2)} & 0 \\ + 0 & 0 & 0 & 1 + \end{pmatrix} + """ + + def __init__(self): + """Create new SiSwap gate.""" + super().__init__("sqisw", 2, []) + + def _define(self): + """ + gate SQiSW a, b { rxx(-pi/4) a, b; ryy(-pi/4) a, b; } + """ + # pylint: disable=cyclic-import + from qiskit.circuit.quantumcircuit import QuantumCircuit + from .rxx import RXXGate + from .ryy import RYYGate + + q = QuantumRegister(2, "q") + qc = QuantumCircuit(q, name=self.name) + rules = [(RXXGate(-pi/4), [q[0], q[1]], []), (RYYGate(-pi/4), [q[1], q[0]], [])] + for instr, qargs, cargs in rules: + qc._append(instr, qargs, cargs) + + self.definition = qc + + def __array__(self, dtype=None): + """Return a numpy.array for the DCX gate.""" + return np.array([[1, 0, 0, 0], + [0, 1/np.sqrt(2), 1j/np.sqrt(2), 0], + [0, 1j/np.sqrt(2), 1/np.sqrt(2), 0], + [0, 0, 0, 1]], dtype=dtype) From c9ac910a3201254ad720b971de3e4e2a9780e1fe Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 13 Jan 2023 09:28:55 -0500 Subject: [PATCH 02/25] add sqrt(iswap) decomposition method --- .../library/standard_gates/__init__.py | 2 +- .../circuit/library/standard_gates/siswap.py | 6 +- qiskit/quantum_info/synthesis/__init__.py | 1 + .../synthesis/siswap_decompose.py | 253 ++++++++++++++++++ 4 files changed, 258 insertions(+), 4 deletions(-) create mode 100644 qiskit/quantum_info/synthesis/siswap_decompose.py diff --git a/qiskit/circuit/library/standard_gates/__init__.py b/qiskit/circuit/library/standard_gates/__init__.py index 2b44614f03c5..b472a70b93b9 100644 --- a/qiskit/circuit/library/standard_gates/__init__.py +++ b/qiskit/circuit/library/standard_gates/__init__.py @@ -91,7 +91,7 @@ from .s import SGate, SdgGate, CSGate, CSdgGate from .swap import SwapGate, CSwapGate from .iswap import iSwapGate -from .sqisw import SiSwapGate +from .siswap import SiSwapGate from .sx import SXGate, SXdgGate, CSXGate from .dcx import DCXGate from .t import TGate, TdgGate diff --git a/qiskit/circuit/library/standard_gates/siswap.py b/qiskit/circuit/library/standard_gates/siswap.py index f6616eb97c27..8a9f51e291c0 100644 --- a/qiskit/circuit/library/standard_gates/siswap.py +++ b/qiskit/circuit/library/standard_gates/siswap.py @@ -46,11 +46,11 @@ class SiSwapGate(Gate): def __init__(self): """Create new SiSwap gate.""" - super().__init__("sqisw", 2, []) + super().__init__("siswap", 2, []) def _define(self): """ - gate SQiSW a, b { rxx(-pi/4) a, b; ryy(-pi/4) a, b; } + gate SiSwap a, b { rxx(-pi/4) a, b; ryy(-pi/4) a, b; } """ # pylint: disable=cyclic-import from qiskit.circuit.quantumcircuit import QuantumCircuit @@ -59,7 +59,7 @@ def _define(self): q = QuantumRegister(2, "q") qc = QuantumCircuit(q, name=self.name) - rules = [(RXXGate(-pi/4), [q[0], q[1]], []), (RYYGate(-pi/4), [q[1], q[0]], [])] + rules = [(RXXGate(-np.pi/4), [q[0], q[1]], []), (RYYGate(-np.pi/4), [q[1], q[0]], [])] for instr, qargs, cargs in rules: qc._append(instr, qargs, cargs) diff --git a/qiskit/quantum_info/synthesis/__init__.py b/qiskit/quantum_info/synthesis/__init__.py index b0e3cb6036ac..59f82ce20e58 100644 --- a/qiskit/quantum_info/synthesis/__init__.py +++ b/qiskit/quantum_info/synthesis/__init__.py @@ -17,3 +17,4 @@ from .quaternion import Quaternion from .clifford_decompose import decompose_clifford from .xx_decompose.decomposer import XXDecomposer +from .siswap_decompose import SiSwapDecomposer diff --git a/qiskit/quantum_info/synthesis/siswap_decompose.py b/qiskit/quantum_info/synthesis/siswap_decompose.py new file mode 100644 index 000000000000..080f609bdc10 --- /dev/null +++ b/qiskit/quantum_info/synthesis/siswap_decompose.py @@ -0,0 +1,253 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023 +# +# 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. + +"""Synthesis of two-qubit unitaries using at most 3 applications of the sqrt(iSWAP) gate.""" + + +import numpy as np +import cmath + +from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.quantum_info.operators import Operator +from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylDecomposition +from qiskit.circuit.library import (SiSwapGate, RXXGate, RYYGate, RZZGate, RZGate, RYGate, + XGate, YGate, ZGate, SGate, SdgGate) + + +_EPS = 1e-10 + + +class SiSwapDecomposer: + """ + A class for decomposing 2-qubit unitaries into at most 3 uses of the sqrt(iSWAP) gate. + + Args: + euler_basis (list(str)): single-qubit gates basis in the decomposition + + Reference: + 1. C. Huang et al, + *Towards ultra-high fidelity quantum operations: + SQiSW gate as a native two-qubit gate (2021)* + `arXiv:2105.06074 `_ + """ + + def __init__(self, euler_basis: list = ['u']): + # decomposer for the local single-qubit gates + from qiskit.transpiler.passes import Optimize1qGatesDecomposition + self._decomposer1q = Optimize1qGatesDecomposition(euler_basis) + + def __call__(self, unitary, basis_fidelity=1.0, approximate=True): + """Decompose a two-qubit unitary into using the sqrt(iSWAP) gate. + + Args: + unitary (Operator or ndarray): a 4x4 unitary to synthesize. + basis_fidelity (float): Fidelity of the B gate. + approximate (bool): Approximates if basis fidelities are less than 1.0. + + Returns: + QuantumCircuit: Synthesized circuit. + """ + u_decomp = TwoQubitWeylDecomposition(Operator(unitary)) + + x = u_decomp.a + y = u_decomp.b + z = u_decomp.c + + A1 = Operator(u_decomp.K1r) + A2 = Operator(u_decomp.K1l) + B1 = Operator(u_decomp.K2r) + B2 = Operator(u_decomp.K2l) + + # in the case that 2 x SiSwap gates are needed + if abs(z) <= x - y + _EPS: + V = _interleaving_single_qubit_gates(x, y, z) + + v_decomp = TwoQubitWeylDecomposition(Operator(V)) + + D1 = Operator(v_decomp.K1r) + D2 = Operator(v_decomp.K1l) + E1 = Operator(v_decomp.K2r) + E2 = Operator(v_decomp.K2l) + + ret_c = QuantumCircuit(2) + ret_c.append(B1, [0]) + ret_c.append(B2, [1]) + + ret_c.append(E1.adjoint(), [0]) + ret_c.append(E2.adjoint(), [1]) + ret_c.compose(V, inplace=True) + ret_c.append(D1.adjoint(), [0]) + ret_c.append(D2.adjoint(), [1]) + + ret_c.append(A1, [0]) + ret_c.append(A2, [1]) + + # in the case that 3 SiSwap gates are needed + else: + # CAN(x, y, z) ~ CAN(x, y, -z)† + # so we decompose the adjoint, replace SiSwap with a template in + # terms of SiSwap†, then invert the whole thing + if z < 0: + inverse_decomposition = self.__call__(Operator(unitary).adjoint()) + inverse_decomposition_with_siswap_dg = QuantumCircuit(2) + for instruction in inverse_decomposition: + if isinstance(instruction.operation, SiSwapGate): + inverse_decomposition_with_siswap_dg.z(0) + inverse_decomposition_with_siswap_dg.append(SiSwapGate().inverse(), [0, 1]) + inverse_decomposition_with_siswap_dg.z(0) + else: + inverse_decomposition_with_siswap_dg.append(instruction) + + ret_c = inverse_decomposition_with_siswap_dg.inverse() + # follow unitary u with a circuit consisting of 1 x SiSwap + # that takes the coordinate into the red region + else: + # first remove the post-rotation to u to be able to + # play with angles of RXX.RYY.RZZ by appending gates + nonred = QuantumCircuit(2) + nonred.append(Operator(unitary), [0, 1]) + nonred.append(A1.adjoint(), [0]) + nonred.append(A2.adjoint(), [1]) + + # make a circuit that changes the angles of RXX.RYY.RZZ as desired + # here we actually make the inverse of the circuit because we want + # the final result to have SQiSW not SQiSW\dg + follow = QuantumCircuit(2) + # canonical gate: (x, y, z) --> (x-pi/8, y-pi/8, z) + follow = follow.compose(SiSwapGate(), [0, 1]) + + eigenphase_crossing = False + + if x > np.pi/8: + # (x, y, z) --> (x, y-pi/8, z-pi/8) + follow = follow.compose(YGate().power(1/2), [0], front=True) + follow = follow.compose(YGate().power(1/2), [1], front=True) + follow = follow.compose(YGate().power(-1/2), [0]) + follow = follow.compose(YGate().power(-1/2), [1]) + # eigenphase crossing: a_2 - pi/4 < a_3 + pi/4 + # (x, y, z) --> (x, z-pi/8, y-pi/8) + if y + z < np.pi/4: + eigenphase_crossing = True + else: + # (x, y, z) --> (x+pi/8, y, z-pi/8) + follow = follow.compose(XGate().power(1/2), [0], front=True) + follow = follow.compose(XGate().power(1/2), [1], front=True) + follow = follow.compose(XGate().power(-1/2), [0]) + follow = follow.compose(XGate().power(-1/2), [1]) + follow = follow.compose(ZGate(), [0], front=True) + follow = follow.compose(ZGate(), [0]) + # eigenphase crossing: a_2 - pi/4 < a_3 + # (x, y, z) --> (x+pi/8, z-pi/8, y) + if y + z < np.pi/8: + eigenphase_crossing = True + + # eigenphase crossing: + if eigenphase_crossing: + follow = follow.compose(XGate().power(1/2), [0], front=True) + follow = follow.compose(XGate().power(1/2), [1], front=True) + follow = follow.compose(XGate().power(-1/2), [0]) + follow = follow.compose(XGate().power(-1/2), [1]) + + # now the operator in the red region can be decomposed using 2 x SQiSW + red = nonred.compose(follow.inverse(), [0, 1], inplace=False) + c_2_sqisw = self.__call__(Operator(red)) + + # now write u in terms of 3 x SQiSW + ret_c = QuantumCircuit(2) + ret_c = ret_c.compose(c_2_sqisw, [0, 1]) + ret_c = ret_c.compose(follow, [0, 1]) + ret_c.append(A1, [0]) + ret_c.append(A2, [1]) + + phase_diff = cmath.phase(Operator(unitary).data[0][0] / Operator(ret_c).data[0][0]) + ret_c.global_phase += phase_diff + + return self._decomposer1q(ret_c) + + +def _interleaving_single_qubit_gates(x, y, z): + """ + Find the single-qubit gates given the interaction coefficients + (x, y, z) ∈ W′ when sandwiched by two SQiSW gates. + Return the SQiSW sandwich. + """ + C = np.sin(x + y - z) * np.sin(x - y + z) * np.sin(-x - y - z) * np.sin(-x + y + z) + C = max(C, 0) + + α = np.arccos(np.cos(2 * x) - np.cos(2 * y) + np.cos(2 * z) + 2 * np.sqrt(C)) + + β = np.arccos(np.cos(2 * x) - np.cos(2 * y) + np.cos(2 * z) - 2 * np.sqrt(C)) + + s = 4 * (np.cos(x) ** 2) * (np.cos(z) ** 2) * (np.sin(y) ** 2) + t = np.cos(2 * x) * np.cos(2 * y) * np.cos(2 * z) + sign_z = 1 if z >= 0 else -1 + γ = np.arccos(sign_z * np.sqrt(s / (s + t))) + + # create V operator + V = QuantumCircuit(2) + V.append(SiSwapGate(), [0, 1]) + V.rz(γ, 0) + V.rx(α, 0) + V.rz(γ, 0) + V.rx(β, 1) + V.append(SiSwapGate(), [0, 1]) + + # the returned circuit is the SQiSW sandwich + return V + + +def _canonicalize(a, b, c): + """ + Decompose an arbitrary gate into one SQiSW and one L(x′, y′, z′) + where (x′, y′, z′) ∈ W′ and output the coefficients (x′, y′, z′) + and the interleaving single qubit rotations. + """ + A1 = Operator(IGate()) + A2 = Operator(IGate()) + B1 = Operator(RYGate(-np.pi/2)) + B2 = Operator(RYGate(np.pi/2)) + C1 = Operator(RYGate(np.pi/2)) + C2 = Operator(RYGate(-np.pi/2)) + + s = 1 if c >= 0 else -1 + + # a_ corresponds to a' in the paper, and so on + a_ = a + b_ = b + c_ = abs(c) + + if a > np.pi/8: + b_ -= np.pi/8 + c_ -= np.pi/8 + B1 = Operator(RZGate(np.pi/2)) @ B1 + B2 = Operator(RZGate(-np.pi/2)) @ B2 + C1 = C1 @ Operator(RZGate(-np.pi/2)) + C2 = C2 @ Operator(RZGate(np.pi/2)) + else: + a_ += np.pi/8 + c_ -= np.pi/8 + + if abs(b_) < abs(c_): + b_, c_ = -c_, -b_ + A1 = Operator(RXGate(np.pi/2)) + A2 = Operator(RXGate(-np.pi/2)) + B1 = Operator(RXGate(-np.pi/2)) @ B1 + B2 = Operator(RXGate(np.pi/2)) @ B2 + if s < 0: + c_ = -c_ + A1 = Operator(ZGate()) @ A1 @ Operator(ZGate()) + B1 = Operator(ZGate()) @ B1 @ Operator(ZGate()) + C1 = Operator(ZGate()) @ C1 @ Operator(ZGate()) + + return a_,b_,c_, A1, A2, B1, B2, C1, C2 + + From d10f8c5a2d6580a105423d222657043b359be0fb Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 13 Jan 2023 10:50:33 -0500 Subject: [PATCH 03/25] documentation --- qiskit/circuit/library/__init__.py | 2 +- .../library/standard_gates/__init__.py | 6 +- .../standard_gates/{siswap.py => sqisw.py} | 6 +- ...siswap_decompose.py => sqisw_decompose.py} | 146 ++++++------------ 4 files changed, 54 insertions(+), 106 deletions(-) rename qiskit/circuit/library/standard_gates/{siswap.py => sqisw.py} (95%) rename qiskit/quantum_info/synthesis/{siswap_decompose.py => sqisw_decompose.py} (61%) diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index 724fb64956d1..17226f958f52 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -110,7 +110,7 @@ SdgGate SwapGate iSwapGate - SiSwapGate + SQiSWGate SXGate SXdgGate TGate diff --git a/qiskit/circuit/library/standard_gates/__init__.py b/qiskit/circuit/library/standard_gates/__init__.py index b472a70b93b9..a7c28b90f2c0 100644 --- a/qiskit/circuit/library/standard_gates/__init__.py +++ b/qiskit/circuit/library/standard_gates/__init__.py @@ -59,7 +59,7 @@ CSdgGate SwapGate iSwapGate - SiSwapGate + SQiSWGate SXGate SXdgGate TGate @@ -91,7 +91,7 @@ from .s import SGate, SdgGate, CSGate, CSdgGate from .swap import SwapGate, CSwapGate from .iswap import iSwapGate -from .siswap import SiSwapGate +from .siswap import SQiSWGate from .sx import SXGate, SXdgGate, CSXGate from .dcx import DCXGate from .t import TGate, TdgGate @@ -160,7 +160,7 @@ def get_standard_gate_name_mapping(): CSdgGate(), SwapGate(), iSwapGate(), - SiSwapGate(), + SQiSWGate(), SXdgGate(), TGate(), TdgGate(), diff --git a/qiskit/circuit/library/standard_gates/siswap.py b/qiskit/circuit/library/standard_gates/sqisw.py similarity index 95% rename from qiskit/circuit/library/standard_gates/siswap.py rename to qiskit/circuit/library/standard_gates/sqisw.py index 8a9f51e291c0..9adf8c3fae05 100644 --- a/qiskit/circuit/library/standard_gates/siswap.py +++ b/qiskit/circuit/library/standard_gates/sqisw.py @@ -17,7 +17,7 @@ from qiskit.circuit.quantumregister import QuantumRegister -class SiSwapGate(Gate): +class SQiSWGate(Gate): r"""sqrt(iSWAP) gate. A 2-qubit symmetric gate from the iSWAP (or XY) family. @@ -45,12 +45,12 @@ class SiSwapGate(Gate): """ def __init__(self): - """Create new SiSwap gate.""" + """Create new SQiSW gate.""" super().__init__("siswap", 2, []) def _define(self): """ - gate SiSwap a, b { rxx(-pi/4) a, b; ryy(-pi/4) a, b; } + gate SQiSW a, b { rxx(-pi/4) a, b; ryy(-pi/4) a, b; } """ # pylint: disable=cyclic-import from qiskit.circuit.quantumcircuit import QuantumCircuit diff --git a/qiskit/quantum_info/synthesis/siswap_decompose.py b/qiskit/quantum_info/synthesis/sqisw_decompose.py similarity index 61% rename from qiskit/quantum_info/synthesis/siswap_decompose.py rename to qiskit/quantum_info/synthesis/sqisw_decompose.py index 080f609bdc10..6d0a34567ad8 100644 --- a/qiskit/quantum_info/synthesis/siswap_decompose.py +++ b/qiskit/quantum_info/synthesis/sqisw_decompose.py @@ -19,14 +19,14 @@ from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.quantum_info.operators import Operator from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylDecomposition -from qiskit.circuit.library import (SiSwapGate, RXXGate, RYYGate, RZZGate, RZGate, RYGate, +from qiskit.circuit.library import (SQiSWGate, RXXGate, RYYGate, RZZGate, RZGate, RYGate, XGate, YGate, ZGate, SGate, SdgGate) _EPS = 1e-10 -class SiSwapDecomposer: +class SQiSWDecomposer: """ A class for decomposing 2-qubit unitaries into at most 3 uses of the sqrt(iSWAP) gate. @@ -67,8 +67,8 @@ def __call__(self, unitary, basis_fidelity=1.0, approximate=True): B1 = Operator(u_decomp.K2r) B2 = Operator(u_decomp.K2l) - # in the case that 2 x SiSwap gates are needed - if abs(z) <= x - y + _EPS: + # in the case that 2 x SQiSW gates are needed + if abs(z) <= x - y + _EPS: # red region V = _interleaving_single_qubit_gates(x, y, z) v_decomp = TwoQubitWeylDecomposition(Operator(V)) @@ -78,37 +78,37 @@ def __call__(self, unitary, basis_fidelity=1.0, approximate=True): E1 = Operator(v_decomp.K2r) E2 = Operator(v_decomp.K2l) - ret_c = QuantumCircuit(2) - ret_c.append(B1, [0]) - ret_c.append(B2, [1]) + circuit = QuantumCircuit(2) + circuit.append(B1, [0]) + circuit.append(B2, [1]) - ret_c.append(E1.adjoint(), [0]) - ret_c.append(E2.adjoint(), [1]) - ret_c.compose(V, inplace=True) - ret_c.append(D1.adjoint(), [0]) - ret_c.append(D2.adjoint(), [1]) + circuit.append(E1.adjoint(), [0]) + circuit.append(E2.adjoint(), [1]) + circuit.compose(V, inplace=True) + circuit.append(D1.adjoint(), [0]) + circuit.append(D2.adjoint(), [1]) - ret_c.append(A1, [0]) - ret_c.append(A2, [1]) + circuit.append(A1, [0]) + circuit.append(A2, [1]) - # in the case that 3 SiSwap gates are needed + # in the case that 3 SQiSW gates are needed else: - # CAN(x, y, z) ~ CAN(x, y, -z)† - # so we decompose the adjoint, replace SiSwap with a template in - # terms of SiSwap†, then invert the whole thing - if z < 0: + if z < 0: # blue region + # CAN(x, y, z) ~ CAN(x, y, -z)† + # so we decompose the adjoint, replace SQiSW with a template in + # terms of SQiSW†, then invert the whole thing inverse_decomposition = self.__call__(Operator(unitary).adjoint()) inverse_decomposition_with_siswap_dg = QuantumCircuit(2) for instruction in inverse_decomposition: - if isinstance(instruction.operation, SiSwapGate): + if isinstance(instruction.operation, SQiSWGate): inverse_decomposition_with_siswap_dg.z(0) - inverse_decomposition_with_siswap_dg.append(SiSwapGate().inverse(), [0, 1]) + inverse_decomposition_with_siswap_dg.append(SQiSWGate().inverse(), [0, 1]) inverse_decomposition_with_siswap_dg.z(0) else: inverse_decomposition_with_siswap_dg.append(instruction) - ret_c = inverse_decomposition_with_siswap_dg.inverse() - # follow unitary u with a circuit consisting of 1 x SiSwap + circuit = inverse_decomposition_with_siswap_dg.inverse() + # follow unitary u with a circuit consisting of 1 x SQiSW # that takes the coordinate into the red region else: # first remove the post-rotation to u to be able to @@ -119,59 +119,55 @@ def __call__(self, unitary, basis_fidelity=1.0, approximate=True): nonred.append(A2.adjoint(), [1]) # make a circuit that changes the angles of RXX.RYY.RZZ as desired - # here we actually make the inverse of the circuit because we want - # the final result to have SQiSW not SQiSW\dg follow = QuantumCircuit(2) - # canonical gate: (x, y, z) --> (x-pi/8, y-pi/8, z) - follow = follow.compose(SiSwapGate(), [0, 1]) - eigenphase_crossing = False + # starting with a single sqrt(iSWAP) gate: RXX(pi/4).RYY(pi/4).RZZ(0) + follow = follow.compose(SQiSWGate(), [0, 1]) - if x > np.pi/8: - # (x, y, z) --> (x, y-pi/8, z-pi/8) + # figure out the appropriate conjugations that change RXX/RYY/RZZ angles + eigenphase_crossing = False + if x > np.pi/8: # green region + # RXX(0).RYY(pi/4).RZZ(pi/4) follow = follow.compose(YGate().power(1/2), [0], front=True) follow = follow.compose(YGate().power(1/2), [1], front=True) follow = follow.compose(YGate().power(-1/2), [0]) follow = follow.compose(YGate().power(-1/2), [1]) - # eigenphase crossing: a_2 - pi/4 < a_3 + pi/4 - # (x, y, z) --> (x, z-pi/8, y-pi/8) - if y + z < np.pi/4: + # RXX(0).RYY(pi/4).RZZ(pi/4) + if y + z < np.pi/4: # eigenphase crossing condition: a_2 - pi/4 < a_3 + pi/4 eigenphase_crossing = True - else: - # (x, y, z) --> (x+pi/8, y, z-pi/8) + else: # purple region + # RXX(-pi/4).RYY(0).RZZ(pi/4) follow = follow.compose(XGate().power(1/2), [0], front=True) follow = follow.compose(XGate().power(1/2), [1], front=True) follow = follow.compose(XGate().power(-1/2), [0]) follow = follow.compose(XGate().power(-1/2), [1]) follow = follow.compose(ZGate(), [0], front=True) follow = follow.compose(ZGate(), [0]) - # eigenphase crossing: a_2 - pi/4 < a_3 - # (x, y, z) --> (x+pi/8, z-pi/8, y) - if y + z < np.pi/8: + # RXX(-pi/4).RYY(pi/4).RZZ(0) + if y + z < np.pi/8: # eigenphase crossing condition: a_2 - pi/4 < a_3 eigenphase_crossing = True - # eigenphase crossing: if eigenphase_crossing: follow = follow.compose(XGate().power(1/2), [0], front=True) follow = follow.compose(XGate().power(1/2), [1], front=True) follow = follow.compose(XGate().power(-1/2), [0]) follow = follow.compose(XGate().power(-1/2), [1]) - # now the operator in the red region can be decomposed using 2 x SQiSW + # now we can land in the red region which can be decomposed using 2 x SQiSW red = nonred.compose(follow.inverse(), [0, 1], inplace=False) - c_2_sqisw = self.__call__(Operator(red)) + red_decomp = self.__call__(Operator(red)) # now write u in terms of 3 x SQiSW - ret_c = QuantumCircuit(2) - ret_c = ret_c.compose(c_2_sqisw, [0, 1]) - ret_c = ret_c.compose(follow, [0, 1]) - ret_c.append(A1, [0]) - ret_c.append(A2, [1]) + circuit = QuantumCircuit(2) + circuit = circuit.compose(red_decomp, [0, 1]) + circuit = circuit.compose(follow, [0, 1]) + circuit.append(A1, [0]) + circuit.append(A2, [1]) - phase_diff = cmath.phase(Operator(unitary).data[0][0] / Operator(ret_c).data[0][0]) - ret_c.global_phase += phase_diff + phase_diff = cmath.phase(Operator(unitary).data[0][0] / Operator(circuit).data[0][0]) + circuit.global_phase += phase_diff - return self._decomposer1q(ret_c) + return self._decomposer1q(circuit) def _interleaving_single_qubit_gates(x, y, z): @@ -194,60 +190,12 @@ def _interleaving_single_qubit_gates(x, y, z): # create V operator V = QuantumCircuit(2) - V.append(SiSwapGate(), [0, 1]) + V.append(SQiSWGate(), [0, 1]) V.rz(γ, 0) V.rx(α, 0) V.rz(γ, 0) V.rx(β, 1) - V.append(SiSwapGate(), [0, 1]) + V.append(SQiSWGate(), [0, 1]) # the returned circuit is the SQiSW sandwich return V - - -def _canonicalize(a, b, c): - """ - Decompose an arbitrary gate into one SQiSW and one L(x′, y′, z′) - where (x′, y′, z′) ∈ W′ and output the coefficients (x′, y′, z′) - and the interleaving single qubit rotations. - """ - A1 = Operator(IGate()) - A2 = Operator(IGate()) - B1 = Operator(RYGate(-np.pi/2)) - B2 = Operator(RYGate(np.pi/2)) - C1 = Operator(RYGate(np.pi/2)) - C2 = Operator(RYGate(-np.pi/2)) - - s = 1 if c >= 0 else -1 - - # a_ corresponds to a' in the paper, and so on - a_ = a - b_ = b - c_ = abs(c) - - if a > np.pi/8: - b_ -= np.pi/8 - c_ -= np.pi/8 - B1 = Operator(RZGate(np.pi/2)) @ B1 - B2 = Operator(RZGate(-np.pi/2)) @ B2 - C1 = C1 @ Operator(RZGate(-np.pi/2)) - C2 = C2 @ Operator(RZGate(np.pi/2)) - else: - a_ += np.pi/8 - c_ -= np.pi/8 - - if abs(b_) < abs(c_): - b_, c_ = -c_, -b_ - A1 = Operator(RXGate(np.pi/2)) - A2 = Operator(RXGate(-np.pi/2)) - B1 = Operator(RXGate(-np.pi/2)) @ B1 - B2 = Operator(RXGate(np.pi/2)) @ B2 - if s < 0: - c_ = -c_ - A1 = Operator(ZGate()) @ A1 @ Operator(ZGate()) - B1 = Operator(ZGate()) @ B1 @ Operator(ZGate()) - C1 = Operator(ZGate()) @ C1 @ Operator(ZGate()) - - return a_,b_,c_, A1, A2, B1, B2, C1, C2 - - From c6eb0383b2744d00c5e659cae1cd813f640ec6c1 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 13 Jan 2023 14:54:38 -0500 Subject: [PATCH 04/25] release notes and tests --- .../library/standard_gates/__init__.py | 2 +- .../circuit/library/standard_gates/sqisw.py | 17 +- .../quantum_info/synthesis/sqisw_decompose.py | 201 ------------------ qiskit/synthesis/__init__.py | 8 + 4 files changed, 20 insertions(+), 208 deletions(-) delete mode 100644 qiskit/quantum_info/synthesis/sqisw_decompose.py diff --git a/qiskit/circuit/library/standard_gates/__init__.py b/qiskit/circuit/library/standard_gates/__init__.py index a7c28b90f2c0..9757f96a6bcc 100644 --- a/qiskit/circuit/library/standard_gates/__init__.py +++ b/qiskit/circuit/library/standard_gates/__init__.py @@ -91,7 +91,7 @@ from .s import SGate, SdgGate, CSGate, CSdgGate from .swap import SwapGate, CSwapGate from .iswap import iSwapGate -from .siswap import SQiSWGate +from .sqisw import SQiSWGate from .sx import SXGate, SXdgGate, CSXGate from .dcx import DCXGate from .t import TGate, TdgGate diff --git a/qiskit/circuit/library/standard_gates/sqisw.py b/qiskit/circuit/library/standard_gates/sqisw.py index 9adf8c3fae05..efc5b4f0d605 100644 --- a/qiskit/circuit/library/standard_gates/sqisw.py +++ b/qiskit/circuit/library/standard_gates/sqisw.py @@ -46,7 +46,7 @@ class SQiSWGate(Gate): def __init__(self): """Create new SQiSW gate.""" - super().__init__("siswap", 2, []) + super().__init__("sqisw", 2, []) def _define(self): """ @@ -59,7 +59,7 @@ def _define(self): q = QuantumRegister(2, "q") qc = QuantumCircuit(q, name=self.name) - rules = [(RXXGate(-np.pi/4), [q[0], q[1]], []), (RYYGate(-np.pi/4), [q[1], q[0]], [])] + rules = [(RXXGate(-np.pi / 4), [q[0], q[1]], []), (RYYGate(-np.pi / 4), [q[1], q[0]], [])] for instr, qargs, cargs in rules: qc._append(instr, qargs, cargs) @@ -67,7 +67,12 @@ def _define(self): def __array__(self, dtype=None): """Return a numpy.array for the DCX gate.""" - return np.array([[1, 0, 0, 0], - [0, 1/np.sqrt(2), 1j/np.sqrt(2), 0], - [0, 1j/np.sqrt(2), 1/np.sqrt(2), 0], - [0, 0, 0, 1]], dtype=dtype) + return np.array( + [ + [1, 0, 0, 0], + [0, 1 / np.sqrt(2), 1j / np.sqrt(2), 0], + [0, 1j / np.sqrt(2), 1 / np.sqrt(2), 0], + [0, 0, 0, 1], + ], + dtype=dtype, + ) diff --git a/qiskit/quantum_info/synthesis/sqisw_decompose.py b/qiskit/quantum_info/synthesis/sqisw_decompose.py deleted file mode 100644 index 6d0a34567ad8..000000000000 --- a/qiskit/quantum_info/synthesis/sqisw_decompose.py +++ /dev/null @@ -1,201 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023 -# -# 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. - -"""Synthesis of two-qubit unitaries using at most 3 applications of the sqrt(iSWAP) gate.""" - - -import numpy as np -import cmath - -from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.quantum_info.operators import Operator -from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylDecomposition -from qiskit.circuit.library import (SQiSWGate, RXXGate, RYYGate, RZZGate, RZGate, RYGate, - XGate, YGate, ZGate, SGate, SdgGate) - - -_EPS = 1e-10 - - -class SQiSWDecomposer: - """ - A class for decomposing 2-qubit unitaries into at most 3 uses of the sqrt(iSWAP) gate. - - Args: - euler_basis (list(str)): single-qubit gates basis in the decomposition - - Reference: - 1. C. Huang et al, - *Towards ultra-high fidelity quantum operations: - SQiSW gate as a native two-qubit gate (2021)* - `arXiv:2105.06074 `_ - """ - - def __init__(self, euler_basis: list = ['u']): - # decomposer for the local single-qubit gates - from qiskit.transpiler.passes import Optimize1qGatesDecomposition - self._decomposer1q = Optimize1qGatesDecomposition(euler_basis) - - def __call__(self, unitary, basis_fidelity=1.0, approximate=True): - """Decompose a two-qubit unitary into using the sqrt(iSWAP) gate. - - Args: - unitary (Operator or ndarray): a 4x4 unitary to synthesize. - basis_fidelity (float): Fidelity of the B gate. - approximate (bool): Approximates if basis fidelities are less than 1.0. - - Returns: - QuantumCircuit: Synthesized circuit. - """ - u_decomp = TwoQubitWeylDecomposition(Operator(unitary)) - - x = u_decomp.a - y = u_decomp.b - z = u_decomp.c - - A1 = Operator(u_decomp.K1r) - A2 = Operator(u_decomp.K1l) - B1 = Operator(u_decomp.K2r) - B2 = Operator(u_decomp.K2l) - - # in the case that 2 x SQiSW gates are needed - if abs(z) <= x - y + _EPS: # red region - V = _interleaving_single_qubit_gates(x, y, z) - - v_decomp = TwoQubitWeylDecomposition(Operator(V)) - - D1 = Operator(v_decomp.K1r) - D2 = Operator(v_decomp.K1l) - E1 = Operator(v_decomp.K2r) - E2 = Operator(v_decomp.K2l) - - circuit = QuantumCircuit(2) - circuit.append(B1, [0]) - circuit.append(B2, [1]) - - circuit.append(E1.adjoint(), [0]) - circuit.append(E2.adjoint(), [1]) - circuit.compose(V, inplace=True) - circuit.append(D1.adjoint(), [0]) - circuit.append(D2.adjoint(), [1]) - - circuit.append(A1, [0]) - circuit.append(A2, [1]) - - # in the case that 3 SQiSW gates are needed - else: - if z < 0: # blue region - # CAN(x, y, z) ~ CAN(x, y, -z)† - # so we decompose the adjoint, replace SQiSW with a template in - # terms of SQiSW†, then invert the whole thing - inverse_decomposition = self.__call__(Operator(unitary).adjoint()) - inverse_decomposition_with_siswap_dg = QuantumCircuit(2) - for instruction in inverse_decomposition: - if isinstance(instruction.operation, SQiSWGate): - inverse_decomposition_with_siswap_dg.z(0) - inverse_decomposition_with_siswap_dg.append(SQiSWGate().inverse(), [0, 1]) - inverse_decomposition_with_siswap_dg.z(0) - else: - inverse_decomposition_with_siswap_dg.append(instruction) - - circuit = inverse_decomposition_with_siswap_dg.inverse() - # follow unitary u with a circuit consisting of 1 x SQiSW - # that takes the coordinate into the red region - else: - # first remove the post-rotation to u to be able to - # play with angles of RXX.RYY.RZZ by appending gates - nonred = QuantumCircuit(2) - nonred.append(Operator(unitary), [0, 1]) - nonred.append(A1.adjoint(), [0]) - nonred.append(A2.adjoint(), [1]) - - # make a circuit that changes the angles of RXX.RYY.RZZ as desired - follow = QuantumCircuit(2) - - # starting with a single sqrt(iSWAP) gate: RXX(pi/4).RYY(pi/4).RZZ(0) - follow = follow.compose(SQiSWGate(), [0, 1]) - - # figure out the appropriate conjugations that change RXX/RYY/RZZ angles - eigenphase_crossing = False - if x > np.pi/8: # green region - # RXX(0).RYY(pi/4).RZZ(pi/4) - follow = follow.compose(YGate().power(1/2), [0], front=True) - follow = follow.compose(YGate().power(1/2), [1], front=True) - follow = follow.compose(YGate().power(-1/2), [0]) - follow = follow.compose(YGate().power(-1/2), [1]) - # RXX(0).RYY(pi/4).RZZ(pi/4) - if y + z < np.pi/4: # eigenphase crossing condition: a_2 - pi/4 < a_3 + pi/4 - eigenphase_crossing = True - else: # purple region - # RXX(-pi/4).RYY(0).RZZ(pi/4) - follow = follow.compose(XGate().power(1/2), [0], front=True) - follow = follow.compose(XGate().power(1/2), [1], front=True) - follow = follow.compose(XGate().power(-1/2), [0]) - follow = follow.compose(XGate().power(-1/2), [1]) - follow = follow.compose(ZGate(), [0], front=True) - follow = follow.compose(ZGate(), [0]) - # RXX(-pi/4).RYY(pi/4).RZZ(0) - if y + z < np.pi/8: # eigenphase crossing condition: a_2 - pi/4 < a_3 - eigenphase_crossing = True - - if eigenphase_crossing: - follow = follow.compose(XGate().power(1/2), [0], front=True) - follow = follow.compose(XGate().power(1/2), [1], front=True) - follow = follow.compose(XGate().power(-1/2), [0]) - follow = follow.compose(XGate().power(-1/2), [1]) - - # now we can land in the red region which can be decomposed using 2 x SQiSW - red = nonred.compose(follow.inverse(), [0, 1], inplace=False) - red_decomp = self.__call__(Operator(red)) - - # now write u in terms of 3 x SQiSW - circuit = QuantumCircuit(2) - circuit = circuit.compose(red_decomp, [0, 1]) - circuit = circuit.compose(follow, [0, 1]) - circuit.append(A1, [0]) - circuit.append(A2, [1]) - - phase_diff = cmath.phase(Operator(unitary).data[0][0] / Operator(circuit).data[0][0]) - circuit.global_phase += phase_diff - - return self._decomposer1q(circuit) - - -def _interleaving_single_qubit_gates(x, y, z): - """ - Find the single-qubit gates given the interaction coefficients - (x, y, z) ∈ W′ when sandwiched by two SQiSW gates. - Return the SQiSW sandwich. - """ - C = np.sin(x + y - z) * np.sin(x - y + z) * np.sin(-x - y - z) * np.sin(-x + y + z) - C = max(C, 0) - - α = np.arccos(np.cos(2 * x) - np.cos(2 * y) + np.cos(2 * z) + 2 * np.sqrt(C)) - - β = np.arccos(np.cos(2 * x) - np.cos(2 * y) + np.cos(2 * z) - 2 * np.sqrt(C)) - - s = 4 * (np.cos(x) ** 2) * (np.cos(z) ** 2) * (np.sin(y) ** 2) - t = np.cos(2 * x) * np.cos(2 * y) * np.cos(2 * z) - sign_z = 1 if z >= 0 else -1 - γ = np.arccos(sign_z * np.sqrt(s / (s + t))) - - # create V operator - V = QuantumCircuit(2) - V.append(SQiSWGate(), [0, 1]) - V.rz(γ, 0) - V.rx(α, 0) - V.rz(γ, 0) - V.rx(β, 1) - V.append(SQiSWGate(), [0, 1]) - - # the returned circuit is the SQiSW sandwich - return V diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index 1618e7346b50..fe6549eab676 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -78,6 +78,13 @@ SolovayKitaevDecomposition +Two Qubit Synthesis +======================== + +.. autosummary:: + :toctree: ../stubs/ + + SQiSWDecomposer """ from .evolution import ( @@ -108,3 +115,4 @@ synth_cnotdihedral_general, ) from .discrete_basis import SolovayKitaevDecomposition +from .two_qubit import SQiSWDecomposer From 84853cdbf55b83483977d4c1a0ebe34ea6277410 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 13 Jan 2023 15:15:45 -0500 Subject: [PATCH 05/25] adding files --- .../circuit/library/standard_gates/sqisw.py | 5 +- qiskit/synthesis/two_qubit/__init__.py | 15 ++ qiskit/synthesis/two_qubit/sqisw_decompose.py | 213 ++++++++++++++++++ .../sqisw-decomposition-2478b9f0b7c54044.yaml | 12 + test/python/synthesis/test_sqisw_synthesis.py | 66 ++++++ 5 files changed, 308 insertions(+), 3 deletions(-) create mode 100644 qiskit/synthesis/two_qubit/__init__.py create mode 100644 qiskit/synthesis/two_qubit/sqisw_decompose.py create mode 100644 releasenotes/notes/sqisw-decomposition-2478b9f0b7c54044.yaml create mode 100644 test/python/synthesis/test_sqisw_synthesis.py diff --git a/qiskit/circuit/library/standard_gates/sqisw.py b/qiskit/circuit/library/standard_gates/sqisw.py index efc5b4f0d605..0eecaf14be31 100644 --- a/qiskit/circuit/library/standard_gates/sqisw.py +++ b/qiskit/circuit/library/standard_gates/sqisw.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2020. +# (C) Copyright IBM 2017, 2023. # # 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 @@ -14,7 +14,6 @@ import numpy as np from qiskit.circuit.gate import Gate -from qiskit.circuit.quantumregister import QuantumRegister class SQiSWGate(Gate): @@ -53,7 +52,7 @@ def _define(self): gate SQiSW a, b { rxx(-pi/4) a, b; ryy(-pi/4) a, b; } """ # pylint: disable=cyclic-import - from qiskit.circuit.quantumcircuit import QuantumCircuit + from qiskit.circuit.quantumcircuit import QuantumRegister, QuantumCircuit from .rxx import RXXGate from .ryy import RYYGate diff --git a/qiskit/synthesis/two_qubit/__init__.py b/qiskit/synthesis/two_qubit/__init__.py new file mode 100644 index 000000000000..6bf7652a024e --- /dev/null +++ b/qiskit/synthesis/two_qubit/__init__.py @@ -0,0 +1,15 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# 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. + +"""Two-qubit unitary synthesis methods.""" + +from .sqisw_decompose import SQiSWDecomposer diff --git a/qiskit/synthesis/two_qubit/sqisw_decompose.py b/qiskit/synthesis/two_qubit/sqisw_decompose.py new file mode 100644 index 000000000000..85a97ed1838e --- /dev/null +++ b/qiskit/synthesis/two_qubit/sqisw_decompose.py @@ -0,0 +1,213 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023 +# +# 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. + +"""Synthesis of two-qubit unitaries using at most 3 applications of the sqrt(iSWAP) gate.""" + + +import numpy as np +import cmath + +from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.quantum_info.operators import Operator +from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylDecomposition +from qiskit.circuit.library import ( + SQiSWGate, + RXXGate, + RYYGate, + RZZGate, + RZGate, + RYGate, + XGate, + YGate, + ZGate, + SGate, + SdgGate, +) + + +_EPS = 1e-10 + + +class SQiSWDecomposer: + """ + A class for decomposing 2-qubit unitaries into at most 3 uses of the sqrt(iSWAP) gate. + + Args: + euler_basis (list(str)): single-qubit gates basis in the decomposition + + Reference: + 1. C. Huang et al, + *Towards ultra-high fidelity quantum operations: + SQiSW gate as a native two-qubit gate (2021)* + `arXiv:2105.06074 `_ + """ + + def __init__(self, euler_basis: list = ["u"]): + # decomposer for the local single-qubit gates + from qiskit.transpiler.passes import Optimize1qGatesDecomposition + + self._decomposer1q = Optimize1qGatesDecomposition(euler_basis) + + def __call__(self, unitary, basis_fidelity=1.0, approximate=True): + """Decompose a two-qubit unitary into using the sqrt(iSWAP) gate. + + Args: + unitary (Operator or ndarray): a 4x4 unitary to synthesize. + basis_fidelity (float): Fidelity of the B gate. + approximate (bool): Approximates if basis fidelities are less than 1.0. + + Returns: + QuantumCircuit: Synthesized circuit. + """ + u_decomp = TwoQubitWeylDecomposition(Operator(unitary)) + + x = u_decomp.a + y = u_decomp.b + z = u_decomp.c + + A1 = Operator(u_decomp.K1r) + A2 = Operator(u_decomp.K1l) + B1 = Operator(u_decomp.K2r) + B2 = Operator(u_decomp.K2l) + + # in the case that 2 x SQiSW gates are needed + if abs(z) <= x - y + _EPS: # red region + V = _interleaving_single_qubit_gates(x, y, z) + + v_decomp = TwoQubitWeylDecomposition(Operator(V)) + + D1 = Operator(v_decomp.K1r) + D2 = Operator(v_decomp.K1l) + E1 = Operator(v_decomp.K2r) + E2 = Operator(v_decomp.K2l) + + circuit = QuantumCircuit(2) + circuit.append(B1, [0]) + circuit.append(B2, [1]) + + circuit.append(E1.adjoint(), [0]) + circuit.append(E2.adjoint(), [1]) + circuit.compose(V, inplace=True) + circuit.append(D1.adjoint(), [0]) + circuit.append(D2.adjoint(), [1]) + + circuit.append(A1, [0]) + circuit.append(A2, [1]) + + # in the case that 3 SQiSW gates are needed + else: + if z < 0: # blue region + # CAN(x, y, z) ~ CAN(x, y, -z)† + # so we decompose the adjoint, replace SQiSW with a template in + # terms of SQiSW†, then invert the whole thing + inverse_decomposition = self.__call__(Operator(unitary).adjoint()) + inverse_decomposition_with_sqisw_dg = QuantumCircuit(2) + for instruction in inverse_decomposition: + if isinstance(instruction.operation, SQiSWGate): + inverse_decomposition_with_sqisw_dg.z(0) + inverse_decomposition_with_sqisw_dg.append(SQiSWGate().inverse(), [0, 1]) + inverse_decomposition_with_sqisw_dg.z(0) + else: + inverse_decomposition_with_sqisw_dg.append(instruction) + + circuit = inverse_decomposition_with_sqisw_dg.inverse() + # follow unitary u with a circuit consisting of 1 x SQiSW + # that takes the coordinate into the red region + else: + # first remove the post-rotation to u to be able to + # play with angles of RXX.RYY.RZZ by appending gates + nonred = QuantumCircuit(2) + nonred.append(Operator(unitary), [0, 1]) + nonred.append(A1.adjoint(), [0]) + nonred.append(A2.adjoint(), [1]) + + # make a circuit that changes the angles of RXX.RYY.RZZ as desired + follow = QuantumCircuit(2) + + # starting with a single sqrt(iSWAP) gate: RXX(pi/4).RYY(pi/4).RZZ(0) + follow = follow.compose(SQiSWGate(), [0, 1]) + + # figure out the appropriate conjugations that change RXX/RYY/RZZ angles + eigenphase_crossing = False + if x > np.pi / 8: # green region + # RXX(0).RYY(pi/4).RZZ(pi/4) + follow = follow.compose(YGate().power(1 / 2), [0], front=True) + follow = follow.compose(YGate().power(1 / 2), [1], front=True) + follow = follow.compose(YGate().power(-1 / 2), [0]) + follow = follow.compose(YGate().power(-1 / 2), [1]) + # RXX(0).RYY(pi/4).RZZ(pi/4) + if y + z < np.pi / 4: # eigenphase crossing condition: a_2 - pi/4 < a_3 + pi/4 + eigenphase_crossing = True + else: # purple region + # RXX(-pi/4).RYY(0).RZZ(pi/4) + follow = follow.compose(XGate().power(1 / 2), [0], front=True) + follow = follow.compose(XGate().power(1 / 2), [1], front=True) + follow = follow.compose(XGate().power(-1 / 2), [0]) + follow = follow.compose(XGate().power(-1 / 2), [1]) + follow = follow.compose(ZGate(), [0], front=True) + follow = follow.compose(ZGate(), [0]) + # RXX(-pi/4).RYY(pi/4).RZZ(0) + if y + z < np.pi / 8: # eigenphase crossing condition: a_2 - pi/4 < a_3 + eigenphase_crossing = True + + if eigenphase_crossing: + follow = follow.compose(XGate().power(1 / 2), [0], front=True) + follow = follow.compose(XGate().power(1 / 2), [1], front=True) + follow = follow.compose(XGate().power(-1 / 2), [0]) + follow = follow.compose(XGate().power(-1 / 2), [1]) + + # now we can land in the red region which can be decomposed using 2 x SQiSW + red = nonred.compose(follow.inverse(), [0, 1], inplace=False) + red_decomp = self.__call__(Operator(red)) + + # now write u in terms of 3 x SQiSW + circuit = QuantumCircuit(2) + circuit = circuit.compose(red_decomp, [0, 1]) + circuit = circuit.compose(follow, [0, 1]) + circuit.append(A1, [0]) + circuit.append(A2, [1]) + + phase_diff = cmath.phase(Operator(unitary).data[0][0] / Operator(circuit).data[0][0]) + circuit.global_phase += phase_diff + + return self._decomposer1q(circuit) + + +def _interleaving_single_qubit_gates(x, y, z): + """ + Find the single-qubit gates given the interaction coefficients + (x, y, z) ∈ W′ when sandwiched by two SQiSW gates. + Return the SQiSW sandwich. + """ + C = np.sin(x + y - z) * np.sin(x - y + z) * np.sin(-x - y - z) * np.sin(-x + y + z) + C = max(C, 0) + + α = np.arccos(np.cos(2 * x) - np.cos(2 * y) + np.cos(2 * z) + 2 * np.sqrt(C)) + + β = np.arccos(np.cos(2 * x) - np.cos(2 * y) + np.cos(2 * z) - 2 * np.sqrt(C)) + + s = 4 * (np.cos(x) ** 2) * (np.cos(z) ** 2) * (np.sin(y) ** 2) + t = np.cos(2 * x) * np.cos(2 * y) * np.cos(2 * z) + sign_z = 1 if z >= 0 else -1 + γ = np.arccos(sign_z * np.sqrt(s / (s + t))) + + # create V operator + V = QuantumCircuit(2) + V.append(SQiSWGate(), [0, 1]) + V.rz(γ, 0) + V.rx(α, 0) + V.rz(γ, 0) + V.rx(β, 1) + V.append(SQiSWGate(), [0, 1]) + + # the returned circuit is the SQiSW sandwich + return V diff --git a/releasenotes/notes/sqisw-decomposition-2478b9f0b7c54044.yaml b/releasenotes/notes/sqisw-decomposition-2478b9f0b7c54044.yaml new file mode 100644 index 000000000000..adce29121396 --- /dev/null +++ b/releasenotes/notes/sqisw-decomposition-2478b9f0b7c54044.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Added a new gate to the standard_gates library :class:`.SQiSWGate` + to represent the square-root of an iSWAP. This gate has the desirable + properties of being faster/higher-fidelity than a full iSWAP, but also + synthesizing the volume of two-qubit unitaries with fewer applications. + + An associated decomposition method that synthesizes arbitrary two-qubit + unitaries into two or three applications of SQiSW has been added in + :class:`.SQiSWDecomposer`. + diff --git a/test/python/synthesis/test_sqisw_synthesis.py b/test/python/synthesis/test_sqisw_synthesis.py new file mode 100644 index 000000000000..db9802b5cb3b --- /dev/null +++ b/test/python/synthesis/test_sqisw_synthesis.py @@ -0,0 +1,66 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2019. +# +# 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. + +"""Test synthesis of two-qubit unitaries into SQiSW gates.""" + +import unittest +from test import combine +from ddt import ddt + +from qiskit.synthesis.two_qubit import SQiSWDecomposer +from qiskit.quantum_info import random_unitary, Operator +from qiskit.circuit.library import SwapGate, iSwapGate, CXGate, IGate +from qiskit.test import QiskitTestCase + + +@ddt +class TestSQiSWSynth(QiskitTestCase): + """Test the Gray-Synth algorithm.""" + + @combine(seed=range(50)) + def test_sqisw_random(self, seed): + """Test synthesis of 50 random SU(4)s.""" + u = random_unitary(4, seed=seed) + decomposer = SQiSWDecomposer(euler_basis=["rz", "ry"]) + circuit = decomposer(u) + self.assertLessEqual(circuit.count_ops().get("sqisw", None), 3) + self.assertEqual(Operator(circuit), Operator(u)) + + @combine(corner=[SwapGate(), SwapGate().power(1 / 2), SwapGate().power(1 / 32)]) + def test_sqisw_corners_weyl(self, corner): + """Test synthesis of some special corner cases.""" + u = Operator(corner) + decomposer = SQiSWDecomposer(euler_basis=["rz", "ry"]) + circuit = decomposer(u) + self.assertEqual(circuit.count_ops().get("sqisw", None), 3) + self.assertEqual(Operator(circuit), Operator(u)) + + @combine( + corner=[ + iSwapGate(), + iSwapGate().power(1 / 2), + CXGate(), + CXGate().power(-1 / 2), + Operator(IGate()) ^ Operator(IGate()), + ] + ) + def test_sqisw_corners_red(self, corner): + """Test synthesis of some special corner cases.""" + u = Operator(corner) + decomposer = SQiSWDecomposer(euler_basis=["u"]) + circuit = decomposer(u) + self.assertEqual(circuit.count_ops().get("sqisw", None), 2) + self.assertEqual(Operator(circuit), Operator(u)) + + +if __name__ == "__main__": + unittest.main() From 6d00e34558f13770994a68665f8d74bf826f7f3f Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Tue, 24 Jan 2023 18:29:20 -0500 Subject: [PATCH 06/25] lint --- qiskit/circuit/library/pauli_evolution.py | 2 +- .../synthesis/two_qubit_decompose.py | 15 +++++++------- qiskit/synthesis/two_qubit/sqisw_decompose.py | 20 +++++++++---------- test/python/synthesis/test_sqisw_synthesis.py | 2 ++ 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/qiskit/circuit/library/pauli_evolution.py b/qiskit/circuit/library/pauli_evolution.py index 3fa52c00569f..e949291b0398 100644 --- a/qiskit/circuit/library/pauli_evolution.py +++ b/qiskit/circuit/library/pauli_evolution.py @@ -19,7 +19,7 @@ from qiskit.circuit.gate import Gate from qiskit.circuit.parameterexpression import ParameterExpression -from qiskit.synthesis import EvolutionSynthesis, LieTrotter +from qiskit.synthesis.evolution import EvolutionSynthesis, LieTrotter from qiskit.quantum_info import Pauli, SparsePauliOp diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index 960e144a63f2..923bc6eca64e 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -34,7 +34,7 @@ import numpy as np -from qiskit.circuit import QuantumRegister, QuantumCircuit, Gate +from qiskit.circuit.quantumcircuit import QuantumCircuit, Gate from qiskit.circuit.library.standard_gates import CXGate, RXGate, RYGate, RZGate from qiskit.exceptions import QiskitError from qiskit.quantum_info.operators import Operator @@ -1135,19 +1135,18 @@ def __call__( raise # do default decomposition - q = QuantumRegister(2) decomposition_euler = [self._decomposer1q._decompose(x) for x in decomposition] - return_circuit = QuantumCircuit(q) + return_circuit = QuantumCircuit(2) return_circuit.global_phase = target_decomposed.global_phase return_circuit.global_phase -= best_nbasis * self.basis.global_phase if best_nbasis == 2: return_circuit.global_phase += np.pi for i in range(best_nbasis): - return_circuit.compose(decomposition_euler[2 * i], [q[0]], inplace=True) - return_circuit.compose(decomposition_euler[2 * i + 1], [q[1]], inplace=True) - return_circuit.append(self.gate, [q[0], q[1]]) - return_circuit.compose(decomposition_euler[2 * best_nbasis], [q[0]], inplace=True) - return_circuit.compose(decomposition_euler[2 * best_nbasis + 1], [q[1]], inplace=True) + return_circuit.compose(decomposition_euler[2 * i], [0], inplace=True) + return_circuit.compose(decomposition_euler[2 * i + 1], [1], inplace=True) + return_circuit.append(self.gate, [0, 1]) + return_circuit.compose(decomposition_euler[2 * best_nbasis], [0], inplace=True) + return_circuit.compose(decomposition_euler[2 * best_nbasis + 1], [1], inplace=True) return return_circuit def _pulse_optimal_chooser(self, best_nbasis, decomposition, target_decomposed): diff --git a/qiskit/synthesis/two_qubit/sqisw_decompose.py b/qiskit/synthesis/two_qubit/sqisw_decompose.py index 85a97ed1838e..7f8840525be6 100644 --- a/qiskit/synthesis/two_qubit/sqisw_decompose.py +++ b/qiskit/synthesis/two_qubit/sqisw_decompose.py @@ -10,27 +10,22 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Synthesis of two-qubit unitaries using at most 3 applications of the sqrt(iSWAP) gate.""" +# pylint: disable=invalid-name, non-ascii-name +"""Synthesis of two-qubit unitaries using at most 3 applications of the sqrt(iSWAP) gate.""" -import numpy as np import cmath +from typing import Optional, List +import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.quantum_info.operators import Operator from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylDecomposition from qiskit.circuit.library import ( SQiSWGate, - RXXGate, - RYYGate, - RZZGate, - RZGate, - RYGate, XGate, YGate, ZGate, - SGate, - SdgGate, ) @@ -51,10 +46,13 @@ class SQiSWDecomposer: `arXiv:2105.06074 `_ """ - def __init__(self, euler_basis: list = ["u"]): + def __init__(self, euler_basis: Optional[List[str]]): # decomposer for the local single-qubit gates - from qiskit.transpiler.passes import Optimize1qGatesDecomposition + from qiskit.transpiler.passes.optimization.optimize_1q_decomposition import ( + Optimize1qGatesDecomposition, # pylint: disable=cyclic-import + ) + euler_basis = euler_basis or ["u"] self._decomposer1q = Optimize1qGatesDecomposition(euler_basis) def __call__(self, unitary, basis_fidelity=1.0, approximate=True): diff --git a/test/python/synthesis/test_sqisw_synthesis.py b/test/python/synthesis/test_sqisw_synthesis.py index db9802b5cb3b..36cb9492715f 100644 --- a/test/python/synthesis/test_sqisw_synthesis.py +++ b/test/python/synthesis/test_sqisw_synthesis.py @@ -10,6 +10,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +# pylint: disable=invalid-name + """Test synthesis of two-qubit unitaries into SQiSW gates.""" import unittest From 071f070fa794be2c6ed162ca1db0b1abb1945807 Mon Sep 17 00:00:00 2001 From: Ali Javadi-Abhari Date: Thu, 2 Feb 2023 19:51:28 -0500 Subject: [PATCH 07/25] Update qiskit/circuit/library/standard_gates/sqisw.py Co-authored-by: Julien Gacon --- qiskit/circuit/library/standard_gates/sqisw.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/sqisw.py b/qiskit/circuit/library/standard_gates/sqisw.py index 0eecaf14be31..3d2b574a30dd 100644 --- a/qiskit/circuit/library/standard_gates/sqisw.py +++ b/qiskit/circuit/library/standard_gates/sqisw.py @@ -37,8 +37,8 @@ class SQiSWGate(Gate): B\ q_0, q_1 = \begin{pmatrix} 1 & 0 & 0 & 0 \\ - 0 & \frac{1}{sqrt(2)} & \frac{i}{sqrt(2)} & 0 \\ - 0 & \frac{i}{sqrt(2)} & \frac{1}{sqrt(2)} & 0 \\ + 0 & \frac{1}{\sqrt(2)} & \frac{i}{\sqrt(2)} & 0 \\ + 0 & \frac{i}{\sqrt(2)} & \frac{1}{\sqrt(2)} & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} """ From ccb293cbc070644d5f8b801456a58544c36fb41b Mon Sep 17 00:00:00 2001 From: Ali Javadi-Abhari Date: Thu, 2 Feb 2023 19:51:59 -0500 Subject: [PATCH 08/25] Update qiskit/circuit/library/standard_gates/sqisw.py Co-authored-by: Julien Gacon --- qiskit/circuit/library/standard_gates/sqisw.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/circuit/library/standard_gates/sqisw.py b/qiskit/circuit/library/standard_gates/sqisw.py index 3d2b574a30dd..fb35c5f02d65 100644 --- a/qiskit/circuit/library/standard_gates/sqisw.py +++ b/qiskit/circuit/library/standard_gates/sqisw.py @@ -58,7 +58,7 @@ def _define(self): q = QuantumRegister(2, "q") qc = QuantumCircuit(q, name=self.name) - rules = [(RXXGate(-np.pi / 4), [q[0], q[1]], []), (RYYGate(-np.pi / 4), [q[1], q[0]], [])] + rules = [(RXXGate(-np.pi / 4), [q[0], q[1]], []), (RYYGate(-np.pi / 4), [q[0], q[1]], [])] for instr, qargs, cargs in rules: qc._append(instr, qargs, cargs) From d9dde5d6fd97677c5274dc241e77c34d50660f1f Mon Sep 17 00:00:00 2001 From: Ali Javadi-Abhari Date: Thu, 2 Feb 2023 19:52:08 -0500 Subject: [PATCH 09/25] Update qiskit/synthesis/__init__.py Co-authored-by: Julien Gacon --- qiskit/synthesis/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index fe6549eab676..36bfaf6d61f5 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -79,7 +79,7 @@ SolovayKitaevDecomposition Two Qubit Synthesis -======================== +=================== .. autosummary:: :toctree: ../stubs/ From 99300796308cd5807c1f65d3146349466a7bec9c Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Thu, 2 Feb 2023 20:08:29 -0500 Subject: [PATCH 10/25] rename sqisw to siswap --- qiskit/circuit/library/__init__.py | 2 +- .../library/standard_gates/__init__.py | 6 +-- .../standard_gates/{sqisw.py => siswap.py} | 14 +++--- qiskit/quantum_info/synthesis/__init__.py | 1 - qiskit/synthesis/__init__.py | 8 ++-- .../synthesis/{two_qubit => su4}/__init__.py | 2 +- .../siswap_decompose.py} | 44 +++++++++---------- .../sqisw-decomposition-2478b9f0b7c54044.yaml | 6 +-- ..._synthesis.py => test_siswap_synthesis.py} | 24 +++++----- 9 files changed, 53 insertions(+), 54 deletions(-) rename qiskit/circuit/library/standard_gates/{sqisw.py => siswap.py} (85%) rename qiskit/synthesis/{two_qubit => su4}/__init__.py (91%) rename qiskit/synthesis/{two_qubit/sqisw_decompose.py => su4/siswap_decompose.py} (86%) rename test/python/synthesis/{test_sqisw_synthesis.py => test_siswap_synthesis.py} (71%) diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py index 17226f958f52..724fb64956d1 100644 --- a/qiskit/circuit/library/__init__.py +++ b/qiskit/circuit/library/__init__.py @@ -110,7 +110,7 @@ SdgGate SwapGate iSwapGate - SQiSWGate + SiSwapGate SXGate SXdgGate TGate diff --git a/qiskit/circuit/library/standard_gates/__init__.py b/qiskit/circuit/library/standard_gates/__init__.py index 9757f96a6bcc..b472a70b93b9 100644 --- a/qiskit/circuit/library/standard_gates/__init__.py +++ b/qiskit/circuit/library/standard_gates/__init__.py @@ -59,7 +59,7 @@ CSdgGate SwapGate iSwapGate - SQiSWGate + SiSwapGate SXGate SXdgGate TGate @@ -91,7 +91,7 @@ from .s import SGate, SdgGate, CSGate, CSdgGate from .swap import SwapGate, CSwapGate from .iswap import iSwapGate -from .sqisw import SQiSWGate +from .siswap import SiSwapGate from .sx import SXGate, SXdgGate, CSXGate from .dcx import DCXGate from .t import TGate, TdgGate @@ -160,7 +160,7 @@ def get_standard_gate_name_mapping(): CSdgGate(), SwapGate(), iSwapGate(), - SQiSWGate(), + SiSwapGate(), SXdgGate(), TGate(), TdgGate(), diff --git a/qiskit/circuit/library/standard_gates/sqisw.py b/qiskit/circuit/library/standard_gates/siswap.py similarity index 85% rename from qiskit/circuit/library/standard_gates/sqisw.py rename to qiskit/circuit/library/standard_gates/siswap.py index fb35c5f02d65..97fc50211367 100644 --- a/qiskit/circuit/library/standard_gates/sqisw.py +++ b/qiskit/circuit/library/standard_gates/siswap.py @@ -16,14 +16,14 @@ from qiskit.circuit.gate import Gate -class SQiSWGate(Gate): +class SiSwapGate(Gate): r"""sqrt(iSWAP) gate. A 2-qubit symmetric gate from the iSWAP (or XY) family. It has Weyl chamber coordinates (π/8, π/8, 0). Can be applied to a :class:`~qiskit.circuit.QuantumCircuit` - with the :meth:`~qiskit.circuit.QuantumCircuit.sqisw` method. + with the :meth:`~qiskit.circuit.QuantumCircuit.SiSwap` method. .. parsed-literal:: @@ -36,20 +36,20 @@ class SQiSWGate(Gate): .. math:: B\ q_0, q_1 = \begin{pmatrix} - 1 & 0 & 0 & 0 \\ + 1 & 0 & 0 & 0 \\ 0 & \frac{1}{\sqrt(2)} & \frac{i}{\sqrt(2)} & 0 \\ 0 & \frac{i}{\sqrt(2)} & \frac{1}{\sqrt(2)} & 0 \\ - 0 & 0 & 0 & 1 + 0 & 0 & 0 & 1 \end{pmatrix} """ def __init__(self): - """Create new SQiSW gate.""" - super().__init__("sqisw", 2, []) + """Create new SiSwap gate.""" + super().__init__("siswap", 2, []) def _define(self): """ - gate SQiSW a, b { rxx(-pi/4) a, b; ryy(-pi/4) a, b; } + gate siswap a, b { rxx(-pi/4) a, b; ryy(-pi/4) a, b; } """ # pylint: disable=cyclic-import from qiskit.circuit.quantumcircuit import QuantumRegister, QuantumCircuit diff --git a/qiskit/quantum_info/synthesis/__init__.py b/qiskit/quantum_info/synthesis/__init__.py index 59f82ce20e58..b0e3cb6036ac 100644 --- a/qiskit/quantum_info/synthesis/__init__.py +++ b/qiskit/quantum_info/synthesis/__init__.py @@ -17,4 +17,3 @@ from .quaternion import Quaternion from .clifford_decompose import decompose_clifford from .xx_decompose.decomposer import XXDecomposer -from .siswap_decompose import SiSwapDecomposer diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index 36bfaf6d61f5..c1e5d49d4e86 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -78,13 +78,13 @@ SolovayKitaevDecomposition -Two Qubit Synthesis -=================== +SU(4) Synthesis +=============== .. autosummary:: :toctree: ../stubs/ - SQiSWDecomposer + SiSwapDecomposer """ from .evolution import ( @@ -115,4 +115,4 @@ synth_cnotdihedral_general, ) from .discrete_basis import SolovayKitaevDecomposition -from .two_qubit import SQiSWDecomposer +from .su4 import SiSwapDecomposer diff --git a/qiskit/synthesis/two_qubit/__init__.py b/qiskit/synthesis/su4/__init__.py similarity index 91% rename from qiskit/synthesis/two_qubit/__init__.py rename to qiskit/synthesis/su4/__init__.py index 6bf7652a024e..1203e78389fd 100644 --- a/qiskit/synthesis/two_qubit/__init__.py +++ b/qiskit/synthesis/su4/__init__.py @@ -12,4 +12,4 @@ """Two-qubit unitary synthesis methods.""" -from .sqisw_decompose import SQiSWDecomposer +from .siswap_decompose import SiSwapDecomposer diff --git a/qiskit/synthesis/two_qubit/sqisw_decompose.py b/qiskit/synthesis/su4/siswap_decompose.py similarity index 86% rename from qiskit/synthesis/two_qubit/sqisw_decompose.py rename to qiskit/synthesis/su4/siswap_decompose.py index 7f8840525be6..db36d1d3d272 100644 --- a/qiskit/synthesis/two_qubit/sqisw_decompose.py +++ b/qiskit/synthesis/su4/siswap_decompose.py @@ -22,7 +22,7 @@ from qiskit.quantum_info.operators import Operator from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylDecomposition from qiskit.circuit.library import ( - SQiSWGate, + SiSwapGate, XGate, YGate, ZGate, @@ -32,7 +32,7 @@ _EPS = 1e-10 -class SQiSWDecomposer: +class SiSwapDecomposer: """ A class for decomposing 2-qubit unitaries into at most 3 uses of the sqrt(iSWAP) gate. @@ -77,7 +77,7 @@ def __call__(self, unitary, basis_fidelity=1.0, approximate=True): B1 = Operator(u_decomp.K2r) B2 = Operator(u_decomp.K2l) - # in the case that 2 x SQiSW gates are needed + # in the case that 2 x SiSwap gates are needed if abs(z) <= x - y + _EPS: # red region V = _interleaving_single_qubit_gates(x, y, z) @@ -101,24 +101,24 @@ def __call__(self, unitary, basis_fidelity=1.0, approximate=True): circuit.append(A1, [0]) circuit.append(A2, [1]) - # in the case that 3 SQiSW gates are needed + # in the case that 3 SiSwap gates are needed else: if z < 0: # blue region # CAN(x, y, z) ~ CAN(x, y, -z)† - # so we decompose the adjoint, replace SQiSW with a template in - # terms of SQiSW†, then invert the whole thing + # so we decompose the adjoint, replace SiSwap with a template in + # terms of SiSwap†, then invert the whole thing inverse_decomposition = self.__call__(Operator(unitary).adjoint()) - inverse_decomposition_with_sqisw_dg = QuantumCircuit(2) + inverse_decomposition_with_siswap_dg = QuantumCircuit(2) for instruction in inverse_decomposition: - if isinstance(instruction.operation, SQiSWGate): - inverse_decomposition_with_sqisw_dg.z(0) - inverse_decomposition_with_sqisw_dg.append(SQiSWGate().inverse(), [0, 1]) - inverse_decomposition_with_sqisw_dg.z(0) + if isinstance(instruction.operation, SiSwapGate): + inverse_decomposition_with_siswap_dg.z(0) + inverse_decomposition_with_siswap_dg.append(SiSwapGate().inverse(), [0, 1]) + inverse_decomposition_with_siswap_dg.z(0) else: - inverse_decomposition_with_sqisw_dg.append(instruction) + inverse_decomposition_with_siswap_dg.append(instruction) - circuit = inverse_decomposition_with_sqisw_dg.inverse() - # follow unitary u with a circuit consisting of 1 x SQiSW + circuit = inverse_decomposition_with_siswap_dg.inverse() + # follow unitary u with a circuit consisting of 1 x SiSwap # that takes the coordinate into the red region else: # first remove the post-rotation to u to be able to @@ -132,7 +132,7 @@ def __call__(self, unitary, basis_fidelity=1.0, approximate=True): follow = QuantumCircuit(2) # starting with a single sqrt(iSWAP) gate: RXX(pi/4).RYY(pi/4).RZZ(0) - follow = follow.compose(SQiSWGate(), [0, 1]) + follow = follow.compose(SiSwapGate(), [0, 1]) # figure out the appropriate conjugations that change RXX/RYY/RZZ angles eigenphase_crossing = False @@ -163,11 +163,11 @@ def __call__(self, unitary, basis_fidelity=1.0, approximate=True): follow = follow.compose(XGate().power(-1 / 2), [0]) follow = follow.compose(XGate().power(-1 / 2), [1]) - # now we can land in the red region which can be decomposed using 2 x SQiSW + # now we can land in the red region which can be decomposed using 2 x SiSwap red = nonred.compose(follow.inverse(), [0, 1], inplace=False) red_decomp = self.__call__(Operator(red)) - # now write u in terms of 3 x SQiSW + # now write u in terms of 3 x SiSwap circuit = QuantumCircuit(2) circuit = circuit.compose(red_decomp, [0, 1]) circuit = circuit.compose(follow, [0, 1]) @@ -183,8 +183,8 @@ def __call__(self, unitary, basis_fidelity=1.0, approximate=True): def _interleaving_single_qubit_gates(x, y, z): """ Find the single-qubit gates given the interaction coefficients - (x, y, z) ∈ W′ when sandwiched by two SQiSW gates. - Return the SQiSW sandwich. + (x, y, z) ∈ W′ when sandwiched by two SiSwap gates. + Return the SiSwap sandwich. """ C = np.sin(x + y - z) * np.sin(x - y + z) * np.sin(-x - y - z) * np.sin(-x + y + z) C = max(C, 0) @@ -200,12 +200,12 @@ def _interleaving_single_qubit_gates(x, y, z): # create V operator V = QuantumCircuit(2) - V.append(SQiSWGate(), [0, 1]) + V.append(SiSwapGate(), [0, 1]) V.rz(γ, 0) V.rx(α, 0) V.rz(γ, 0) V.rx(β, 1) - V.append(SQiSWGate(), [0, 1]) + V.append(SiSwapGate(), [0, 1]) - # the returned circuit is the SQiSW sandwich + # the returned circuit is the SiSwap sandwich return V diff --git a/releasenotes/notes/sqisw-decomposition-2478b9f0b7c54044.yaml b/releasenotes/notes/sqisw-decomposition-2478b9f0b7c54044.yaml index adce29121396..8f8a23ad6ef9 100644 --- a/releasenotes/notes/sqisw-decomposition-2478b9f0b7c54044.yaml +++ b/releasenotes/notes/sqisw-decomposition-2478b9f0b7c54044.yaml @@ -1,12 +1,12 @@ --- features: - | - Added a new gate to the standard_gates library :class:`.SQiSWGate` + Added a new gate to the standard_gates library :class:`.SiSwapGate` to represent the square-root of an iSWAP. This gate has the desirable properties of being faster/higher-fidelity than a full iSWAP, but also synthesizing the volume of two-qubit unitaries with fewer applications. An associated decomposition method that synthesizes arbitrary two-qubit - unitaries into two or three applications of SQiSW has been added in - :class:`.SQiSWDecomposer`. + unitaries into two or three applications of SiSwap has been added in + :class:`.SiSwapDecomposer`. diff --git a/test/python/synthesis/test_sqisw_synthesis.py b/test/python/synthesis/test_siswap_synthesis.py similarity index 71% rename from test/python/synthesis/test_sqisw_synthesis.py rename to test/python/synthesis/test_siswap_synthesis.py index 36cb9492715f..82400112837b 100644 --- a/test/python/synthesis/test_sqisw_synthesis.py +++ b/test/python/synthesis/test_siswap_synthesis.py @@ -12,38 +12,38 @@ # pylint: disable=invalid-name -"""Test synthesis of two-qubit unitaries into SQiSW gates.""" +"""Test synthesis of two-qubit unitaries into SiSwap gates.""" import unittest from test import combine from ddt import ddt -from qiskit.synthesis.two_qubit import SQiSWDecomposer +from qiskit.synthesis.su4 import SiSwapDecomposer from qiskit.quantum_info import random_unitary, Operator from qiskit.circuit.library import SwapGate, iSwapGate, CXGate, IGate from qiskit.test import QiskitTestCase @ddt -class TestSQiSWSynth(QiskitTestCase): +class TestSiSwapSynth(QiskitTestCase): """Test the Gray-Synth algorithm.""" @combine(seed=range(50)) - def test_sqisw_random(self, seed): + def test_siswap_random(self, seed): """Test synthesis of 50 random SU(4)s.""" u = random_unitary(4, seed=seed) - decomposer = SQiSWDecomposer(euler_basis=["rz", "ry"]) + decomposer = SiSwapDecomposer(euler_basis=["rz", "ry"]) circuit = decomposer(u) - self.assertLessEqual(circuit.count_ops().get("sqisw", None), 3) + self.assertLessEqual(circuit.count_ops().get("siswap", None), 3) self.assertEqual(Operator(circuit), Operator(u)) @combine(corner=[SwapGate(), SwapGate().power(1 / 2), SwapGate().power(1 / 32)]) - def test_sqisw_corners_weyl(self, corner): + def test_siswap_corners_weyl(self, corner): """Test synthesis of some special corner cases.""" u = Operator(corner) - decomposer = SQiSWDecomposer(euler_basis=["rz", "ry"]) + decomposer = SiSwapDecomposer(euler_basis=["rz", "ry"]) circuit = decomposer(u) - self.assertEqual(circuit.count_ops().get("sqisw", None), 3) + self.assertEqual(circuit.count_ops().get("siswap", None), 3) self.assertEqual(Operator(circuit), Operator(u)) @combine( @@ -55,12 +55,12 @@ def test_sqisw_corners_weyl(self, corner): Operator(IGate()) ^ Operator(IGate()), ] ) - def test_sqisw_corners_red(self, corner): + def test_siswap_corners_red(self, corner): """Test synthesis of some special corner cases.""" u = Operator(corner) - decomposer = SQiSWDecomposer(euler_basis=["u"]) + decomposer = SiSwapDecomposer(euler_basis=["u"]) circuit = decomposer(u) - self.assertEqual(circuit.count_ops().get("sqisw", None), 2) + self.assertEqual(circuit.count_ops().get("siswap", None), 2) self.assertEqual(Operator(circuit), Operator(u)) From c523180479af4d854651bfbed28b23c67d19d86e Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Wed, 8 Feb 2023 15:04:30 -0500 Subject: [PATCH 11/25] adding approximation for siswap decomposition --- .../synthesis/two_qubit_decompose.py | 25 ++-- .../synthesis/xx_decompose/decomposer.py | 22 +--- qiskit/synthesis/su4/siswap_decompose.py | 117 +++++++++++++++--- qiskit/synthesis/su4/utils.py | 84 +++++++++++++ .../python/synthesis/test_siswap_synthesis.py | 36 ++++-- 5 files changed, 225 insertions(+), 59 deletions(-) create mode 100644 qiskit/synthesis/su4/utils.py diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index 923bc6eca64e..35da30d51ea0 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -34,7 +34,7 @@ import numpy as np -from qiskit.circuit.quantumcircuit import QuantumCircuit, Gate +from qiskit.circuit import QuantumRegister, QuantumCircuit, Gate from qiskit.circuit.library.standard_gates import CXGate, RXGate, RYGate, RZGate from qiskit.exceptions import QiskitError from qiskit.quantum_info.operators import Operator @@ -1092,17 +1092,17 @@ def __call__( basis_fidelity: Optional[float] = None, approximate: bool = True, *, - _num_basis_uses: int = None, + _num_basis_uses: Optional[int] = None, ) -> QuantumCircuit: """Decompose a two-qubit `unitary` over fixed basis + SU(2) using the best approximation given that each basis application has a finite `basis_fidelity`. Args: - unitary (Operator or ndarray): 4x4 unitary to synthesize. - basis_fidelity (float or None): Fidelity to be assumed for applications of KAK Gate. + unitary: 4x4 unitary to synthesize. + basis_fidelity: Fidelity to be assumed for applications of KAK Gate. If given, overrides basis_fidelity given at init. - approximate (bool): Approximates if basis fidelities are less than 1.0. - _num_basis_uses (int): force a particular approximation by passing a number in [0, 3]. + approximate: Approximates if basis fidelities are less than 1.0. + _num_basis_uses: force a particular approximation by passing a number in [0, 3]. Returns: QuantumCircuit: Synthesized circuit. Raises: @@ -1135,18 +1135,19 @@ def __call__( raise # do default decomposition + q = QuantumRegister(2) decomposition_euler = [self._decomposer1q._decompose(x) for x in decomposition] - return_circuit = QuantumCircuit(2) + return_circuit = QuantumCircuit(q) return_circuit.global_phase = target_decomposed.global_phase return_circuit.global_phase -= best_nbasis * self.basis.global_phase if best_nbasis == 2: return_circuit.global_phase += np.pi for i in range(best_nbasis): - return_circuit.compose(decomposition_euler[2 * i], [0], inplace=True) - return_circuit.compose(decomposition_euler[2 * i + 1], [1], inplace=True) - return_circuit.append(self.gate, [0, 1]) - return_circuit.compose(decomposition_euler[2 * best_nbasis], [0], inplace=True) - return_circuit.compose(decomposition_euler[2 * best_nbasis + 1], [1], inplace=True) + return_circuit.compose(decomposition_euler[2 * i], [q[0]], inplace=True) + return_circuit.compose(decomposition_euler[2 * i + 1], [q[1]], inplace=True) + return_circuit.append(self.gate, [q[0], q[1]]) + return_circuit.compose(decomposition_euler[2 * best_nbasis], [q[0]], inplace=True) + return_circuit.compose(decomposition_euler[2 * best_nbasis + 1], [q[1]], inplace=True) return return_circuit def _pulse_optimal_chooser(self, best_nbasis, decomposition, target_decomposed): diff --git a/qiskit/quantum_info/synthesis/xx_decompose/decomposer.py b/qiskit/quantum_info/synthesis/xx_decompose/decomposer.py index 3d5714b5c2ab..859fefe0235f 100644 --- a/qiskit/quantum_info/synthesis/xx_decompose/decomposer.py +++ b/qiskit/quantum_info/synthesis/xx_decompose/decomposer.py @@ -31,25 +31,7 @@ from .circuits import apply_reflection, apply_shift, canonical_xx_circuit from .utilities import EPSILON from .polytopes import XXPolytope - - -def _average_infidelity(p, q): - """ - Computes the infidelity distance between two points p, q expressed in positive canonical - coordinates. - """ - - a0, b0, c0 = p - a1, b1, c1 = q - - return 1 - 1 / 20 * ( - 4 - + 16 - * ( - math.cos(a0 - a1) ** 2 * math.cos(b0 - b1) ** 2 * math.cos(c0 - c1) ** 2 - + math.sin(a0 - a1) ** 2 * math.sin(b0 - b1) ** 2 * math.sin(c0 - c1) ** 2 - ) - ) +from qiskit.synthesis.su4.utils import average_infidelity class XXDecomposer: @@ -155,7 +137,7 @@ def _best_decomposition(canonical_coordinate, available_strengths): strength_polytope = XXPolytope.from_strengths(*[x / 2 for x in sequence]) candidate_point = strength_polytope.nearest(canonical_coordinate) - candidate_cost = sequence_cost + _average_infidelity( + candidate_cost = sequence_cost + average_infidelity( canonical_coordinate, candidate_point ) diff --git a/qiskit/synthesis/su4/siswap_decompose.py b/qiskit/synthesis/su4/siswap_decompose.py index db36d1d3d272..c6d5c4b3ae70 100644 --- a/qiskit/synthesis/su4/siswap_decompose.py +++ b/qiskit/synthesis/su4/siswap_decompose.py @@ -15,21 +15,27 @@ """Synthesis of two-qubit unitaries using at most 3 applications of the sqrt(iSWAP) gate.""" import cmath -from typing import Optional, List +from typing import Optional, List, Union import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.quantum_info.operators import Operator from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylDecomposition -from qiskit.circuit.library import ( - SiSwapGate, - XGate, - YGate, - ZGate, -) +from qiskit.circuit.library import SiSwapGate, XGate, YGate, ZGate, SGate, SdgGate +from qiskit.synthesis.su4.utils import find_min_point, average_infidelity -_EPS = 1e-10 +_EPS = 1e-12 + +# the polytope accessible by two applications of the SiSwapGate +# (red region in paper) +_ID = np.array([0, 0, 0]) +_CNOT = np.array([np.pi / 4, 0, 0]) +_ISWAP = np.array([np.pi / 4, np.pi / 4, 0]) +_MID = np.array([np.pi / 4, np.pi / 8, np.pi / 8]) +_MIDDG = np.array([np.pi / 4, np.pi / 8, -np.pi / 8]) +_SISWAP = np.array([np.pi / 8, np.pi / 8, 0]) +_POLYTOPE = np.array([_ID, _CNOT, _ISWAP, _MID, _MIDDG]) class SiSwapDecomposer: @@ -55,17 +61,28 @@ def __init__(self, euler_basis: Optional[List[str]]): euler_basis = euler_basis or ["u"] self._decomposer1q = Optimize1qGatesDecomposition(euler_basis) - def __call__(self, unitary, basis_fidelity=1.0, approximate=True): + def __call__( + self, + unitary: Union[Operator, np.ndarray], + basis_fidelity: Optional[float] = 1.0, + approximate: bool = True, + *, + _num_basis_uses: Optional[int] = None, + ) -> QuantumCircuit: """Decompose a two-qubit unitary into using the sqrt(iSWAP) gate. Args: - unitary (Operator or ndarray): a 4x4 unitary to synthesize. - basis_fidelity (float): Fidelity of the B gate. - approximate (bool): Approximates if basis fidelities are less than 1.0. + unitary: a 4x4 unitary to synthesize. + basis_fidelity: Fidelity of the iSWAP gate. + approximate: Approximates if basis fidelities are less than 1.0. + _num_basis_uses: force a particular approximation by passing a number in [0, 3]. Returns: QuantumCircuit: Synthesized circuit. """ + if not approximate: + basis_fidelity = 1.0 + u_decomp = TwoQubitWeylDecomposition(Operator(unitary)) x = u_decomp.a @@ -77,11 +94,69 @@ def __call__(self, unitary, basis_fidelity=1.0, approximate=True): B1 = Operator(u_decomp.K2r) B2 = Operator(u_decomp.K2l) - # in the case that 2 x SiSwap gates are needed - if abs(z) <= x - y + _EPS: # red region - V = _interleaving_single_qubit_gates(x, y, z) + p = np.array([x, y, z]) + + print('p: ', p) + + if abs(z) <= x - y + _EPS: + polytope_projection = p + else: + polytope_projection = find_min_point([v - p for v in _POLYTOPE]) + p + + print('q: ', polytope_projection) + + candidate_points = [ + _ID, # 0 applications + _SISWAP, # 1 application + polytope_projection, # 2 applications + p, # 3 applications + ] + + if _num_basis_uses is None: + expected_fidelities = [ + (1 - average_infidelity(p, q)) * basis_fidelity**i + for i, q in enumerate(candidate_points) + ] + best_nbasis = int(np.argmax(expected_fidelities)) # tiebreaks with smallest + else: + best_nbasis = _num_basis_uses + + p = candidate_points[best_nbasis] + + x = p[0] + y = p[1] + z = p[2] + + # in the case that 0 SiSwap gate is needed + if best_nbasis == 0: + circuit = QuantumCircuit(2) + circuit.append(B1, [0]) + circuit.append(B2, [1]) + circuit.append(A1, [0]) + circuit.append(A2, [1]) + # in the case that 1 SiSwap gate is needed + elif best_nbasis == 1: + circuit = QuantumCircuit(2) + circuit.append(B1, [0]) + circuit.append(B2, [1]) + circuit.append(SGate(), [0]) + circuit.append(SGate(), [1]) + circuit.append(SiSwapGate(), [0, 1]) + circuit.append(SdgGate(), [0]) + circuit.append(SdgGate(), [1]) + circuit.append(A1, [0]) + circuit.append(A2, [1]) + + # in the case that 2 SiSwap gates are needed + elif best_nbasis == 2: # red region + V = _interleaving_single_qubit_gates(x, y, z) v_decomp = TwoQubitWeylDecomposition(Operator(V)) + if not all(np.isclose([x, y, z], [v_decomp.a, v_decomp.b, v_decomp.c])): + print(" ** deviant sandwich ** ") + print('original: ', [x, y, z]) + print('V: ', [v_decomp.a, v_decomp.b, v_decomp.c]) + print(V) D1 = Operator(v_decomp.K1r) D2 = Operator(v_decomp.K1l) @@ -107,7 +182,9 @@ def __call__(self, unitary, basis_fidelity=1.0, approximate=True): # CAN(x, y, z) ~ CAN(x, y, -z)† # so we decompose the adjoint, replace SiSwap with a template in # terms of SiSwap†, then invert the whole thing - inverse_decomposition = self.__call__(Operator(unitary).adjoint()) + inverse_decomposition = self.__call__( + Operator(unitary).adjoint() + ) inverse_decomposition_with_siswap_dg = QuantumCircuit(2) for instruction in inverse_decomposition: if isinstance(instruction.operation, SiSwapGate): @@ -174,7 +251,9 @@ def __call__(self, unitary, basis_fidelity=1.0, approximate=True): circuit.append(A1, [0]) circuit.append(A2, [1]) - phase_diff = cmath.phase(Operator(unitary).data[0][0] / Operator(circuit).data[0][0]) + # FIXME: there must be a cleaner way to track global phase + i = np.where(~np.isclose(np.ravel(Operator(circuit).data), 0.0))[0][0] + phase_diff = cmath.phase(Operator(unitary).data.flat[i] / Operator(circuit).data.flat[i]) circuit.global_phase += phase_diff return self._decomposer1q(circuit) @@ -186,8 +265,10 @@ def _interleaving_single_qubit_gates(x, y, z): (x, y, z) ∈ W′ when sandwiched by two SiSwap gates. Return the SiSwap sandwich. """ + assert abs(z) <= x - y + _EPS and np.pi/4 >= x and x >= y and y >= abs(z) C = np.sin(x + y - z) * np.sin(x - y + z) * np.sin(-x - y - z) * np.sin(-x + y + z) - C = max(C, 0) + if abs(C) < _EPS: + C = 0. α = np.arccos(np.cos(2 * x) - np.cos(2 * y) + np.cos(2 * z) + 2 * np.sqrt(C)) diff --git a/qiskit/synthesis/su4/utils.py b/qiskit/synthesis/su4/utils.py new file mode 100644 index 000000000000..dbd2e4963fee --- /dev/null +++ b/qiskit/synthesis/su4/utils.py @@ -0,0 +1,84 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023 +# +# 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. + +import numpy as np + + +_EPS = 1e-12 + + +def find_min_point(P): + """Find the closest point on convex polytope P to the origin (in Euclidean distance). + + P is given as a list of points forming its vertices. + + Function from + https://scipy-cookbook.readthedocs.io/items/Finding_Convex_Hull_Minimum_Point.html + """ + if len(P) == 1: + return P[0] + + P = [np.array([i for i in p]) for p in P] + + # Step 0. Choose a point from C(P) + x = P[np.array([np.dot(p, p) for p in P]).argmin()] + + while True: + # Step 1. \alpha_k := min{x_{k-1}^T p | p \in P} + p_alpha = P[np.array([np.dot(x, p) for p in P]).argmin()] + + if np.dot(x, x - p_alpha) < _EPS: + return np.array([float(i) for i in x]) + + Pk = [p for p in P if abs(np.dot(x, p - p_alpha)) < _EPS] + + # Step 2. P_k := { p | p \in P and x_{k-1}^T p = \alpha_k} + P_Pk = [p for p in P if not np.array([(p == q).all() for q in Pk]).any()] + + if len(Pk) == len(P): + return np.array([float(i) for i in x]) + + y = find_min_point(Pk) + + p_beta = P_Pk[np.array([np.dot(y, p) for p in P_Pk]).argmin()] + + if np.dot(y, y - p_beta) < _EPS: + return np.array([float(i) for i in y]) + + # Step 4. + P_aux = [p for p in P_Pk if (np.dot(y - x, y - p) > _EPS) and (np.dot(x, y - p) != 0)] + p_lambda = P_aux[np.array([np.dot(y, y - p) / np.dot(x, y - p) for p in P_aux]).argmin()] + lam = np.dot(x, p_lambda - y) / np.dot(y - x, y - p_lambda) + + x += lam * (y - x) + + +def average_infidelity(p, q): + """Computes the infidelity distance between two points p, q expressed in + positive canonical coordinates. + + Average gate fidelity is :math:`Fbar = (d + |Tr (Utarget \\cdot U^dag)|^2) / d(d+1)` + + [1] M. Horodecki, P. Horodecki and R. Horodecki, PRA 60, 1888 (1999) + """ + + a0, b0, c0 = p + a1, b1, c1 = q + + return 1 - 1 / 20 * ( + 4 + + 16 + * ( + np.cos(a0 - a1) ** 2 * np.cos(b0 - b1) ** 2 * np.cos(c0 - c1) ** 2 + + np.sin(a0 - a1) ** 2 * np.sin(b0 - b1) ** 2 * np.sin(c0 - c1) ** 2 + ) + ) diff --git a/test/python/synthesis/test_siswap_synthesis.py b/test/python/synthesis/test_siswap_synthesis.py index 82400112837b..f3349f8347dd 100644 --- a/test/python/synthesis/test_siswap_synthesis.py +++ b/test/python/synthesis/test_siswap_synthesis.py @@ -17,11 +17,12 @@ import unittest from test import combine from ddt import ddt +import numpy as np from qiskit.synthesis.su4 import SiSwapDecomposer from qiskit.quantum_info import random_unitary, Operator -from qiskit.circuit.library import SwapGate, iSwapGate, CXGate, IGate from qiskit.test import QiskitTestCase +from qiskit.circuit.library import SwapGate, iSwapGate, CXGate, IGate, SGate, XGate, RXXGate, RYYGate, RZZGate @ddt @@ -34,7 +35,7 @@ def test_siswap_random(self, seed): u = random_unitary(4, seed=seed) decomposer = SiSwapDecomposer(euler_basis=["rz", "ry"]) circuit = decomposer(u) - self.assertLessEqual(circuit.count_ops().get("siswap", None), 3) + self.assertLessEqual(circuit.count_ops().get("siswap", 0), 3) self.assertEqual(Operator(circuit), Operator(u)) @combine(corner=[SwapGate(), SwapGate().power(1 / 2), SwapGate().power(1 / 32)]) @@ -43,24 +44,41 @@ def test_siswap_corners_weyl(self, corner): u = Operator(corner) decomposer = SiSwapDecomposer(euler_basis=["rz", "ry"]) circuit = decomposer(u) - self.assertEqual(circuit.count_ops().get("siswap", None), 3) + self.assertEqual(circuit.count_ops().get("siswap", 0), 3) + self.assertEqual(Operator(circuit), Operator(u)) + + @combine(corner=[iSwapGate(), CXGate(), CXGate().power(-1 / 2), + Operator(RXXGate(-2*np.pi/4)) @ Operator(RYYGate(-2*np.pi/8)) @ Operator(RZZGate(2*np.pi/8))] + ) + def test_siswap_corners_2_uses(self, corner): + """Test synthesis of some special corner cases.""" + u = Operator(corner) + decomposer = SiSwapDecomposer(euler_basis=["u"]) + circuit = decomposer(u) + self.assertEqual(circuit.count_ops().get("siswap", 0), 2) self.assertEqual(Operator(circuit), Operator(u)) @combine( corner=[ - iSwapGate(), iSwapGate().power(1 / 2), - CXGate(), - CXGate().power(-1 / 2), - Operator(IGate()) ^ Operator(IGate()), + iSwapGate().power(-1 / 2), ] ) - def test_siswap_corners_red(self, corner): + def test_siswap_corners_1_use(self, corner): + """Test synthesis of some special corner cases.""" + u = Operator(corner) + decomposer = SiSwapDecomposer(euler_basis=["u"]) + circuit = decomposer(u) + self.assertEqual(circuit.count_ops().get("siswap", 0), 1) + self.assertEqual(Operator(circuit), Operator(u)) + + @combine(corner=[Operator(IGate()) ^ Operator(IGate()), Operator(IGate()) ^ Operator(XGate())]) + def test_siswap_corners_0_use(self, corner): """Test synthesis of some special corner cases.""" u = Operator(corner) decomposer = SiSwapDecomposer(euler_basis=["u"]) circuit = decomposer(u) - self.assertEqual(circuit.count_ops().get("siswap", None), 2) + self.assertEqual(circuit.count_ops().get("siswap", 0), 0) self.assertEqual(Operator(circuit), Operator(u)) From cc0b349da0aa73c1f982535578143fcb65eb88c2 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 17 Feb 2023 02:10:49 -0500 Subject: [PATCH 12/25] working corner case of CAN(pi/4, y, -z) which happens often in approximation --- qiskit/synthesis/su4/siswap_decompose.py | 31 ++++++++++--------- .../python/synthesis/test_siswap_synthesis.py | 25 ++++++++++++--- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/qiskit/synthesis/su4/siswap_decompose.py b/qiskit/synthesis/su4/siswap_decompose.py index c6d5c4b3ae70..44cec5dc7e49 100644 --- a/qiskit/synthesis/su4/siswap_decompose.py +++ b/qiskit/synthesis/su4/siswap_decompose.py @@ -96,15 +96,11 @@ def __call__( p = np.array([x, y, z]) - print('p: ', p) - if abs(z) <= x - y + _EPS: polytope_projection = p else: polytope_projection = find_min_point([v - p for v in _POLYTOPE]) + p - print('q: ', polytope_projection) - candidate_points = [ _ID, # 0 applications _SISWAP, # 1 application @@ -152,11 +148,13 @@ def __call__( elif best_nbasis == 2: # red region V = _interleaving_single_qubit_gates(x, y, z) v_decomp = TwoQubitWeylDecomposition(Operator(V)) - if not all(np.isclose([x, y, z], [v_decomp.a, v_decomp.b, v_decomp.c])): - print(" ** deviant sandwich ** ") - print('original: ', [x, y, z]) - print('V: ', [v_decomp.a, v_decomp.b, v_decomp.c]) - print(V) + + # Due to the symmetry of Weyl chamber CAN(pi/4, y, z) ~ CAN(pi/4, y, -z) + # we might get a V operator that implements CAN(pi/4, y, -z) instead + # we catch this case and fix it by local gates + deviant = False + if not np.isclose(v_decomp.c, z): + deviant = True D1 = Operator(v_decomp.K1r) D2 = Operator(v_decomp.K1l) @@ -167,11 +165,16 @@ def __call__( circuit.append(B1, [0]) circuit.append(B2, [1]) + if deviant: + circuit.x(0) + circuit.z(1) circuit.append(E1.adjoint(), [0]) circuit.append(E2.adjoint(), [1]) circuit.compose(V, inplace=True) circuit.append(D1.adjoint(), [0]) circuit.append(D2.adjoint(), [1]) + if deviant: + circuit.y(1) circuit.append(A1, [0]) circuit.append(A2, [1]) @@ -182,9 +185,7 @@ def __call__( # CAN(x, y, z) ~ CAN(x, y, -z)† # so we decompose the adjoint, replace SiSwap with a template in # terms of SiSwap†, then invert the whole thing - inverse_decomposition = self.__call__( - Operator(unitary).adjoint() - ) + inverse_decomposition = self.__call__(Operator(unitary).adjoint()) inverse_decomposition_with_siswap_dg = QuantumCircuit(2) for instruction in inverse_decomposition: if isinstance(instruction.operation, SiSwapGate): @@ -265,10 +266,10 @@ def _interleaving_single_qubit_gates(x, y, z): (x, y, z) ∈ W′ when sandwiched by two SiSwap gates. Return the SiSwap sandwich. """ - assert abs(z) <= x - y + _EPS and np.pi/4 >= x and x >= y and y >= abs(z) + assert abs(z) <= x - y + _EPS and np.pi / 4 >= x and x >= y and y >= abs(z) C = np.sin(x + y - z) * np.sin(x - y + z) * np.sin(-x - y - z) * np.sin(-x + y + z) if abs(C) < _EPS: - C = 0. + C = 0.0 α = np.arccos(np.cos(2 * x) - np.cos(2 * y) + np.cos(2 * z) + 2 * np.sqrt(C)) @@ -279,7 +280,7 @@ def _interleaving_single_qubit_gates(x, y, z): sign_z = 1 if z >= 0 else -1 γ = np.arccos(sign_z * np.sqrt(s / (s + t))) - # create V operator + # create V (sandwich) operator V = QuantumCircuit(2) V.append(SiSwapGate(), [0, 1]) V.rz(γ, 0) diff --git a/test/python/synthesis/test_siswap_synthesis.py b/test/python/synthesis/test_siswap_synthesis.py index f3349f8347dd..9c28d0b5f69b 100644 --- a/test/python/synthesis/test_siswap_synthesis.py +++ b/test/python/synthesis/test_siswap_synthesis.py @@ -22,7 +22,17 @@ from qiskit.synthesis.su4 import SiSwapDecomposer from qiskit.quantum_info import random_unitary, Operator from qiskit.test import QiskitTestCase -from qiskit.circuit.library import SwapGate, iSwapGate, CXGate, IGate, SGate, XGate, RXXGate, RYYGate, RZZGate +from qiskit.circuit.library import ( + SwapGate, + iSwapGate, + CXGate, + IGate, + SGate, + XGate, + RXXGate, + RYYGate, + RZZGate, +) @ddt @@ -47,9 +57,16 @@ def test_siswap_corners_weyl(self, corner): self.assertEqual(circuit.count_ops().get("siswap", 0), 3) self.assertEqual(Operator(circuit), Operator(u)) - @combine(corner=[iSwapGate(), CXGate(), CXGate().power(-1 / 2), - Operator(RXXGate(-2*np.pi/4)) @ Operator(RYYGate(-2*np.pi/8)) @ Operator(RZZGate(2*np.pi/8))] - ) + @combine( + corner=[ + iSwapGate(), + CXGate(), + CXGate().power(-1 / 2), + Operator(RXXGate(-2 * np.pi / 4)) + @ Operator(RYYGate(-2 * np.pi / 8)) + @ Operator(RZZGate(2 * np.pi / 8)), + ] + ) def test_siswap_corners_2_uses(self, corner): """Test synthesis of some special corner cases.""" u = Operator(corner) From e4f8b3554177d7cc1594bcd2f615d5b9dcd3783a Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 17 Feb 2023 12:25:03 -0500 Subject: [PATCH 13/25] adding more tests --- .../synthesis/xx_decompose/decomposer.py | 3 +- qiskit/synthesis/su4/siswap_decompose.py | 1 - qiskit/synthesis/su4/utils.py | 6 ++- .../python/synthesis/test_siswap_synthesis.py | 50 +++++++++++++++---- 4 files changed, 45 insertions(+), 15 deletions(-) diff --git a/qiskit/quantum_info/synthesis/xx_decompose/decomposer.py b/qiskit/quantum_info/synthesis/xx_decompose/decomposer.py index 859fefe0235f..55da6caaf2f3 100644 --- a/qiskit/quantum_info/synthesis/xx_decompose/decomposer.py +++ b/qiskit/quantum_info/synthesis/xx_decompose/decomposer.py @@ -15,7 +15,6 @@ """ from __future__ import annotations import heapq -import math from operator import itemgetter from typing import Callable @@ -27,11 +26,11 @@ from qiskit.quantum_info.operators import Operator from qiskit.quantum_info.synthesis.one_qubit_decompose import ONE_QUBIT_EULER_BASIS_GATES from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylDecomposition +from qiskit.synthesis.su4.utils import average_infidelity from .circuits import apply_reflection, apply_shift, canonical_xx_circuit from .utilities import EPSILON from .polytopes import XXPolytope -from qiskit.synthesis.su4.utils import average_infidelity class XXDecomposer: diff --git a/qiskit/synthesis/su4/siswap_decompose.py b/qiskit/synthesis/su4/siswap_decompose.py index 44cec5dc7e49..747c849b451d 100644 --- a/qiskit/synthesis/su4/siswap_decompose.py +++ b/qiskit/synthesis/su4/siswap_decompose.py @@ -266,7 +266,6 @@ def _interleaving_single_qubit_gates(x, y, z): (x, y, z) ∈ W′ when sandwiched by two SiSwap gates. Return the SiSwap sandwich. """ - assert abs(z) <= x - y + _EPS and np.pi / 4 >= x and x >= y and y >= abs(z) C = np.sin(x + y - z) * np.sin(x - y + z) * np.sin(-x - y - z) * np.sin(-x + y + z) if abs(C) < _EPS: C = 0.0 diff --git a/qiskit/synthesis/su4/utils.py b/qiskit/synthesis/su4/utils.py index dbd2e4963fee..3890057e0e9b 100644 --- a/qiskit/synthesis/su4/utils.py +++ b/qiskit/synthesis/su4/utils.py @@ -10,6 +10,10 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +# pylint: disable=invalid-name + +"""Utilities for SU(4) synthesis: Weyl chamber, polytopes, distances, etc.""" + import numpy as np @@ -27,7 +31,7 @@ def find_min_point(P): if len(P) == 1: return P[0] - P = [np.array([i for i in p]) for p in P] + P = [np.array(p) for p in P] # Step 0. Choose a point from C(P) x = P[np.array([np.dot(p, p) for p in P]).argmin()] diff --git a/test/python/synthesis/test_siswap_synthesis.py b/test/python/synthesis/test_siswap_synthesis.py index 9c28d0b5f69b..eae5e151a1ca 100644 --- a/test/python/synthesis/test_siswap_synthesis.py +++ b/test/python/synthesis/test_siswap_synthesis.py @@ -20,24 +20,24 @@ import numpy as np from qiskit.synthesis.su4 import SiSwapDecomposer -from qiskit.quantum_info import random_unitary, Operator +from qiskit.quantum_info import random_unitary, Operator, average_gate_fidelity from qiskit.test import QiskitTestCase from qiskit.circuit.library import ( SwapGate, iSwapGate, CXGate, IGate, - SGate, XGate, RXXGate, RYYGate, RZZGate, ) +_EPS = 1e-12 @ddt class TestSiSwapSynth(QiskitTestCase): - """Test the Gray-Synth algorithm.""" + """Test synthesis of SU(4)s over SiSwap basis.""" @combine(seed=range(50)) def test_siswap_random(self, seed): @@ -48,9 +48,18 @@ def test_siswap_random(self, seed): self.assertLessEqual(circuit.count_ops().get("siswap", 0), 3) self.assertEqual(Operator(circuit), Operator(u)) + @combine(seed=range(50, 100), basis_fidelity=[0.98, 0.99, 1.0]) + def test_siswap_random_approx(self, seed, basis_fidelity): + """Test synthesis of 50 random SU(4)s with approximation.""" + u = random_unitary(4, seed=seed) + decomposer = SiSwapDecomposer(euler_basis=["rz", "ry"]) + circuit = decomposer(u, basis_fidelity=basis_fidelity) + self.assertLessEqual(circuit.count_ops().get("siswap", 0), 3) + self.assertGreaterEqual(average_gate_fidelity(Operator(circuit), u), basis_fidelity - _EPS) + @combine(corner=[SwapGate(), SwapGate().power(1 / 2), SwapGate().power(1 / 32)]) - def test_siswap_corners_weyl(self, corner): - """Test synthesis of some special corner cases.""" + def test_siswap_corners_3_use(self, corner): + """Test synthesis of some 3-use special corner cases.""" u = Operator(corner) decomposer = SiSwapDecomposer(euler_basis=["rz", "ry"]) circuit = decomposer(u) @@ -62,13 +71,10 @@ def test_siswap_corners_weyl(self, corner): iSwapGate(), CXGate(), CXGate().power(-1 / 2), - Operator(RXXGate(-2 * np.pi / 4)) - @ Operator(RYYGate(-2 * np.pi / 8)) - @ Operator(RZZGate(2 * np.pi / 8)), ] ) def test_siswap_corners_2_uses(self, corner): - """Test synthesis of some special corner cases.""" + """Test synthesis of some 2-use special corner cases.""" u = Operator(corner) decomposer = SiSwapDecomposer(euler_basis=["u"]) circuit = decomposer(u) @@ -82,7 +88,7 @@ def test_siswap_corners_2_uses(self, corner): ] ) def test_siswap_corners_1_use(self, corner): - """Test synthesis of some special corner cases.""" + """Test synthesis of some 1-use special corner cases.""" u = Operator(corner) decomposer = SiSwapDecomposer(euler_basis=["u"]) circuit = decomposer(u) @@ -91,13 +97,35 @@ def test_siswap_corners_1_use(self, corner): @combine(corner=[Operator(IGate()) ^ Operator(IGate()), Operator(IGate()) ^ Operator(XGate())]) def test_siswap_corners_0_use(self, corner): - """Test synthesis of some special corner cases.""" + """Test synthesis of some 0-use special corner cases.""" u = Operator(corner) decomposer = SiSwapDecomposer(euler_basis=["u"]) circuit = decomposer(u) self.assertEqual(circuit.count_ops().get("siswap", 0), 0) self.assertEqual(Operator(circuit), Operator(u)) + @combine( + p=[ + [np.pi / 4, np.pi / 6, -np.pi / 12], # red and blue intersection (bottom face) + [np.pi / 4, np.pi / 6, np.pi / 12], # inverse of above (and locally equivalent) + [np.pi / 4, np.pi / 8, np.pi / 8], # half-way between CX and SWAP (peak red polytope) + [np.pi / 4, np.pi / 8, -np.pi / 8], # inverse of above (and locally equivalent) + [np.pi / 4, np.pi / 16, np.pi / 16], # quarter-way between CX and SWAP + [np.pi / 4, np.pi / 16, np.pi / 16], # inverse of above (and locally equivalent) + [np.pi / 6, np.pi / 8, np.pi / 24], # red and blue and green intersection + [np.pi / 6, np.pi / 8, -np.pi / 24], # inverse of above (not locally equivalent) + [np.pi / 16, np.pi / 24, np.pi / 48], # red and blue and purple intersection + [np.pi / 16, np.pi / 24, -np.pi / 48], # inverse of above (not locally equivalent) + [np.pi / 6, np.pi / 12, -np.pi / 16], # inside red polytope + ] + ) + def test_siswap_special_points(self, p): + """Test special points in the Weyl chamber related to SiSwap polytopes.""" + u = Operator(RXXGate(-2*p[0])) & Operator(RYYGate(-2*p[1])) & Operator(RZZGate(-2*p[2])) + decomposer = SiSwapDecomposer(euler_basis=["u"]) + circuit = decomposer(u) + self.assertEqual(circuit.count_ops().get("siswap", 0), 2) + self.assertEqual(Operator(circuit), Operator(u)) if __name__ == "__main__": unittest.main() From 88b07579cdbe537e98b646ae21cb9b7338231a99 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 17 Feb 2023 12:31:38 -0500 Subject: [PATCH 14/25] adding more tests --- qiskit/synthesis/su4/siswap_decompose.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qiskit/synthesis/su4/siswap_decompose.py b/qiskit/synthesis/su4/siswap_decompose.py index 747c849b451d..fa21e7f4dfa6 100644 --- a/qiskit/synthesis/su4/siswap_decompose.py +++ b/qiskit/synthesis/su4/siswap_decompose.py @@ -202,6 +202,8 @@ def __call__( # first remove the post-rotation to u to be able to # play with angles of RXX.RYY.RZZ by appending gates nonred = QuantumCircuit(2) + nonred.append(B1.adjoint(), [0]) + nonred.append(B2.adjoint(), [1]) nonred.append(Operator(unitary), [0, 1]) nonred.append(A1.adjoint(), [0]) nonred.append(A2.adjoint(), [1]) @@ -247,6 +249,8 @@ def __call__( # now write u in terms of 3 x SiSwap circuit = QuantumCircuit(2) + circuit.append(B1, [0]) + circuit.append(B2, [1]) circuit = circuit.compose(red_decomp, [0, 1]) circuit = circuit.compose(follow, [0, 1]) circuit.append(A1, [0]) From 016059204de18889ae150bee77a640937a6013ad Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 17 Feb 2023 13:23:25 -0500 Subject: [PATCH 15/25] adding siswap in standard equivalence library --- .../standard_gates/equivalence_library.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/qiskit/circuit/library/standard_gates/equivalence_library.py b/qiskit/circuit/library/standard_gates/equivalence_library.py index f1e7efe896e1..875397da185a 100644 --- a/qiskit/circuit/library/standard_gates/equivalence_library.py +++ b/qiskit/circuit/library/standard_gates/equivalence_library.py @@ -742,6 +742,23 @@ def_iswap.append(inst, qargs, cargs) _sel.add_equivalence(iSwapGate(), def_iswap) +# SiSwapGate +# +# ┌──────────┐ ┌────────────┐┌────────────┐ +# q_0: ┤0 ├ q_0: ┤0 ├┤0 ├ +# │ Siswap │ ≡ │ Rxx(-π/4) ││ Ryy(-π/4) │ +# q_1: ┤1 ├ q_1: ┤1 ├┤1 ├ +# └──────────┘ └────────────┘└────────────┘ + +q = QuantumRegister(2, "q") +def_iswap = QuantumCircuit(q) +for inst, qargs, cargs in [ + (RXXGate(-np.pi / 4), [q[0], q[1]], []), + (RYYGate(-np.pi / 4), [q[1], q[1]], []), +]: + def_iswap.append(inst, qargs, cargs) +_sel.add_equivalence(iSwapGate(), def_iswap) + # SXGate # global phase: π/4 # ┌────┐ ┌─────┐┌───┐┌─────┐ From 48ce32c8356a8c182de1e0257149c62d7d621768 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Fri, 17 Feb 2023 13:28:38 -0500 Subject: [PATCH 16/25] docstrings --- .../library/standard_gates/equivalence_library.py | 11 ++++++----- qiskit/circuit/library/standard_gates/siswap.py | 4 ++-- qiskit/synthesis/su4/siswap_decompose.py | 6 +++++- test/python/synthesis/test_siswap_synthesis.py | 10 ++++++++-- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/equivalence_library.py b/qiskit/circuit/library/standard_gates/equivalence_library.py index 875397da185a..a326d1da13ee 100644 --- a/qiskit/circuit/library/standard_gates/equivalence_library.py +++ b/qiskit/circuit/library/standard_gates/equivalence_library.py @@ -41,6 +41,7 @@ SwapGate, CSwapGate, iSwapGate, + SiSwapGate, SXGate, SXdgGate, CSXGate, @@ -751,13 +752,13 @@ # └──────────┘ └────────────┘└────────────┘ q = QuantumRegister(2, "q") -def_iswap = QuantumCircuit(q) +def_siswap = QuantumCircuit(q) for inst, qargs, cargs in [ - (RXXGate(-np.pi / 4), [q[0], q[1]], []), - (RYYGate(-np.pi / 4), [q[1], q[1]], []), + (RXXGate(-pi / 4), [q[0], q[1]], []), + (RYYGate(-pi / 4), [q[0], q[1]], []), ]: - def_iswap.append(inst, qargs, cargs) -_sel.add_equivalence(iSwapGate(), def_iswap) + def_siswap.append(inst, qargs, cargs) +_sel.add_equivalence(SiSwapGate(), def_siswap) # SXGate # global phase: π/4 diff --git a/qiskit/circuit/library/standard_gates/siswap.py b/qiskit/circuit/library/standard_gates/siswap.py index 97fc50211367..34f365400207 100644 --- a/qiskit/circuit/library/standard_gates/siswap.py +++ b/qiskit/circuit/library/standard_gates/siswap.py @@ -37,8 +37,8 @@ class SiSwapGate(Gate): B\ q_0, q_1 = \begin{pmatrix} 1 & 0 & 0 & 0 \\ - 0 & \frac{1}{\sqrt(2)} & \frac{i}{\sqrt(2)} & 0 \\ - 0 & \frac{i}{\sqrt(2)} & \frac{1}{\sqrt(2)} & 0 \\ + 0 & \frac{1}{\sqrt{2}} & \frac{i}{\sqrt{2}} & 0 \\ + 0 & \frac{i}{\sqrt{2}} & \frac{1}{\sqrt{2}} & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} """ diff --git a/qiskit/synthesis/su4/siswap_decompose.py b/qiskit/synthesis/su4/siswap_decompose.py index fa21e7f4dfa6..db02daf10c13 100644 --- a/qiskit/synthesis/su4/siswap_decompose.py +++ b/qiskit/synthesis/su4/siswap_decompose.py @@ -42,8 +42,12 @@ class SiSwapDecomposer: """ A class for decomposing 2-qubit unitaries into at most 3 uses of the sqrt(iSWAP) gate. + This basis is attractive in that it is shorter/cheaper than iSWAP and generates a large + volume (~79%) of Haar-random SU(4) unitaries with only two applications, and an even + larger volume when approximation is enabled. + Args: - euler_basis (list(str)): single-qubit gates basis in the decomposition + euler_basis (list(str)): single-qubit gates basis in the decomposition. Reference: 1. C. Huang et al, diff --git a/test/python/synthesis/test_siswap_synthesis.py b/test/python/synthesis/test_siswap_synthesis.py index eae5e151a1ca..8dc87075ecd2 100644 --- a/test/python/synthesis/test_siswap_synthesis.py +++ b/test/python/synthesis/test_siswap_synthesis.py @@ -35,6 +35,7 @@ _EPS = 1e-12 + @ddt class TestSiSwapSynth(QiskitTestCase): """Test synthesis of SU(4)s over SiSwap basis.""" @@ -112,7 +113,7 @@ def test_siswap_corners_0_use(self, corner): [np.pi / 4, np.pi / 8, -np.pi / 8], # inverse of above (and locally equivalent) [np.pi / 4, np.pi / 16, np.pi / 16], # quarter-way between CX and SWAP [np.pi / 4, np.pi / 16, np.pi / 16], # inverse of above (and locally equivalent) - [np.pi / 6, np.pi / 8, np.pi / 24], # red and blue and green intersection + [np.pi / 6, np.pi / 8, np.pi / 24], # red and blue and green intersection [np.pi / 6, np.pi / 8, -np.pi / 24], # inverse of above (not locally equivalent) [np.pi / 16, np.pi / 24, np.pi / 48], # red and blue and purple intersection [np.pi / 16, np.pi / 24, -np.pi / 48], # inverse of above (not locally equivalent) @@ -121,11 +122,16 @@ def test_siswap_corners_0_use(self, corner): ) def test_siswap_special_points(self, p): """Test special points in the Weyl chamber related to SiSwap polytopes.""" - u = Operator(RXXGate(-2*p[0])) & Operator(RYYGate(-2*p[1])) & Operator(RZZGate(-2*p[2])) + u = ( + Operator(RXXGate(-2 * p[0])) + & Operator(RYYGate(-2 * p[1])) + & Operator(RZZGate(-2 * p[2])) + ) decomposer = SiSwapDecomposer(euler_basis=["u"]) circuit = decomposer(u) self.assertEqual(circuit.count_ops().get("siswap", 0), 2) self.assertEqual(Operator(circuit), Operator(u)) + if __name__ == "__main__": unittest.main() From 8797d8124a84d51c22cc52329bc56a2b0825efb0 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Sat, 18 Feb 2023 22:04:58 -0500 Subject: [PATCH 17/25] simplify code --- qiskit/synthesis/su4/siswap_decompose.py | 260 +++++++++++------------ 1 file changed, 123 insertions(+), 137 deletions(-) diff --git a/qiskit/synthesis/su4/siswap_decompose.py b/qiskit/synthesis/su4/siswap_decompose.py index db02daf10c13..212b9d93db08 100644 --- a/qiskit/synthesis/su4/siswap_decompose.py +++ b/qiskit/synthesis/su4/siswap_decompose.py @@ -123,144 +123,13 @@ def __call__( p = candidate_points[best_nbasis] - x = p[0] - y = p[1] - z = p[2] - - # in the case that 0 SiSwap gate is needed - if best_nbasis == 0: - circuit = QuantumCircuit(2) - circuit.append(B1, [0]) - circuit.append(B2, [1]) - circuit.append(A1, [0]) - circuit.append(A2, [1]) - - # in the case that 1 SiSwap gate is needed - elif best_nbasis == 1: - circuit = QuantumCircuit(2) - circuit.append(B1, [0]) - circuit.append(B2, [1]) - circuit.append(SGate(), [0]) - circuit.append(SGate(), [1]) - circuit.append(SiSwapGate(), [0, 1]) - circuit.append(SdgGate(), [0]) - circuit.append(SdgGate(), [1]) - circuit.append(A1, [0]) - circuit.append(A2, [1]) - - # in the case that 2 SiSwap gates are needed - elif best_nbasis == 2: # red region - V = _interleaving_single_qubit_gates(x, y, z) - v_decomp = TwoQubitWeylDecomposition(Operator(V)) + circuit = _can_circuit(*p) + circuit = circuit.compose(B1.to_instruction(), [0], front=True) + circuit = circuit.compose(B2.to_instruction(), [1], front=True) + circuit = circuit.compose(A1.to_instruction(), [0]) + circuit = circuit.compose(A2.to_instruction(), [1]) - # Due to the symmetry of Weyl chamber CAN(pi/4, y, z) ~ CAN(pi/4, y, -z) - # we might get a V operator that implements CAN(pi/4, y, -z) instead - # we catch this case and fix it by local gates - deviant = False - if not np.isclose(v_decomp.c, z): - deviant = True - - D1 = Operator(v_decomp.K1r) - D2 = Operator(v_decomp.K1l) - E1 = Operator(v_decomp.K2r) - E2 = Operator(v_decomp.K2l) - - circuit = QuantumCircuit(2) - circuit.append(B1, [0]) - circuit.append(B2, [1]) - - if deviant: - circuit.x(0) - circuit.z(1) - circuit.append(E1.adjoint(), [0]) - circuit.append(E2.adjoint(), [1]) - circuit.compose(V, inplace=True) - circuit.append(D1.adjoint(), [0]) - circuit.append(D2.adjoint(), [1]) - if deviant: - circuit.y(1) - - circuit.append(A1, [0]) - circuit.append(A2, [1]) - - # in the case that 3 SiSwap gates are needed - else: - if z < 0: # blue region - # CAN(x, y, z) ~ CAN(x, y, -z)† - # so we decompose the adjoint, replace SiSwap with a template in - # terms of SiSwap†, then invert the whole thing - inverse_decomposition = self.__call__(Operator(unitary).adjoint()) - inverse_decomposition_with_siswap_dg = QuantumCircuit(2) - for instruction in inverse_decomposition: - if isinstance(instruction.operation, SiSwapGate): - inverse_decomposition_with_siswap_dg.z(0) - inverse_decomposition_with_siswap_dg.append(SiSwapGate().inverse(), [0, 1]) - inverse_decomposition_with_siswap_dg.z(0) - else: - inverse_decomposition_with_siswap_dg.append(instruction) - - circuit = inverse_decomposition_with_siswap_dg.inverse() - # follow unitary u with a circuit consisting of 1 x SiSwap - # that takes the coordinate into the red region - else: - # first remove the post-rotation to u to be able to - # play with angles of RXX.RYY.RZZ by appending gates - nonred = QuantumCircuit(2) - nonred.append(B1.adjoint(), [0]) - nonred.append(B2.adjoint(), [1]) - nonred.append(Operator(unitary), [0, 1]) - nonred.append(A1.adjoint(), [0]) - nonred.append(A2.adjoint(), [1]) - - # make a circuit that changes the angles of RXX.RYY.RZZ as desired - follow = QuantumCircuit(2) - - # starting with a single sqrt(iSWAP) gate: RXX(pi/4).RYY(pi/4).RZZ(0) - follow = follow.compose(SiSwapGate(), [0, 1]) - - # figure out the appropriate conjugations that change RXX/RYY/RZZ angles - eigenphase_crossing = False - if x > np.pi / 8: # green region - # RXX(0).RYY(pi/4).RZZ(pi/4) - follow = follow.compose(YGate().power(1 / 2), [0], front=True) - follow = follow.compose(YGate().power(1 / 2), [1], front=True) - follow = follow.compose(YGate().power(-1 / 2), [0]) - follow = follow.compose(YGate().power(-1 / 2), [1]) - # RXX(0).RYY(pi/4).RZZ(pi/4) - if y + z < np.pi / 4: # eigenphase crossing condition: a_2 - pi/4 < a_3 + pi/4 - eigenphase_crossing = True - else: # purple region - # RXX(-pi/4).RYY(0).RZZ(pi/4) - follow = follow.compose(XGate().power(1 / 2), [0], front=True) - follow = follow.compose(XGate().power(1 / 2), [1], front=True) - follow = follow.compose(XGate().power(-1 / 2), [0]) - follow = follow.compose(XGate().power(-1 / 2), [1]) - follow = follow.compose(ZGate(), [0], front=True) - follow = follow.compose(ZGate(), [0]) - # RXX(-pi/4).RYY(pi/4).RZZ(0) - if y + z < np.pi / 8: # eigenphase crossing condition: a_2 - pi/4 < a_3 - eigenphase_crossing = True - - if eigenphase_crossing: - follow = follow.compose(XGate().power(1 / 2), [0], front=True) - follow = follow.compose(XGate().power(1 / 2), [1], front=True) - follow = follow.compose(XGate().power(-1 / 2), [0]) - follow = follow.compose(XGate().power(-1 / 2), [1]) - - # now we can land in the red region which can be decomposed using 2 x SiSwap - red = nonred.compose(follow.inverse(), [0, 1], inplace=False) - red_decomp = self.__call__(Operator(red)) - - # now write u in terms of 3 x SiSwap - circuit = QuantumCircuit(2) - circuit.append(B1, [0]) - circuit.append(B2, [1]) - circuit = circuit.compose(red_decomp, [0, 1]) - circuit = circuit.compose(follow, [0, 1]) - circuit.append(A1, [0]) - circuit.append(A2, [1]) - - # FIXME: there must be a cleaner way to track global phase + # FIXME: cleaner to track global phase during construction i = np.where(~np.isclose(np.ravel(Operator(circuit).data), 0.0))[0][0] phase_diff = cmath.phase(Operator(unitary).data.flat[i] / Operator(circuit).data.flat[i]) circuit.global_phase += phase_diff @@ -268,6 +137,103 @@ def __call__( return self._decomposer1q(circuit) +def _can_circuit(x, y, z): + """ + Find a circuit that implements the canonical gate + CAN(x, y, z) := RXX(x) . RYY(y) . RZZ(z) + """ + circuit = QuantumCircuit(2) + if np.allclose([x, y, z], _ID): + pass + elif np.allclose([x, y, z], _SISWAP): + circuit.append(SiSwapGate(), [0, 1]) + elif abs(z) <= x - y + _EPS: # red region + V = _interleaving_single_qubit_gates(x, y, z) + v_decomp = TwoQubitWeylDecomposition(Operator(V)) + can = _remove_pre_post(V, v_decomp) + circuit = circuit.compose(can, [0, 1]) + + # Due to the symmetry of Weyl chamber CAN(pi/4, y, z) ~ CAN(pi/4, y, -z) + # we might get a V operator that implements CAN(pi/4, y, -z) instead + # we catch this case and fix it by local gates. + if not np.isclose(v_decomp.c, z): + circuit = circuit.compose(XGate(), [0], front=True) + circuit = circuit.compose(ZGate(), [1], front=True) + circuit = circuit.compose(YGate(), [1]) + else: + if z < 0: # blue region + # CAN(x, y, z) ~ CAN(x, y, -z)† + # so we decompose the adjoint, replace SiSwap with a template in + # terms of SiSwap†, then invert the whole thing. + inverse_circuit = _can_circuit(x, y, -z) + inverse_circuit_with_siswap_dg = QuantumCircuit(2) + for instruction in inverse_circuit: + if isinstance(instruction.operation, SiSwapGate): + inverse_circuit_with_siswap_dg.z(0) + inverse_circuit_with_siswap_dg.append(SiSwapGate().inverse(), [0, 1]) + inverse_circuit_with_siswap_dg.z(0) + else: + inverse_circuit_with_siswap_dg.append(instruction) + + V = inverse_circuit_with_siswap_dg.inverse() + v_decomp = TwoQubitWeylDecomposition(Operator(V)) + can = _remove_pre_post(V, v_decomp) + circuit = circuit.compose(can, [0, 1]) + else: + # make a circuit using 1 SiSwap that is able to bring a red point to here + follow = QuantumCircuit(2) + + # x -> x + pi/8 + # y -> y + pi/8 + follow = follow.compose(SiSwapGate(), [0, 1]) + + eigenphase_crossing = False + if x > np.pi / 8: # green region + # y -> y + pi/8 + # z -> z + pi/8 + follow = follow.compose(YGate().power(1 / 2), [0], front=True) + follow = follow.compose(YGate().power(1 / 2), [1], front=True) + follow = follow.compose(YGate().power(-1 / 2), [0]) + follow = follow.compose(YGate().power(-1 / 2), [1]) + if y + z < np.pi / 4: # eigenphase crossing condition: a_2 - pi/4 < a_3 + pi/4 + eigenphase_crossing = True + # corresponding red coordinates + y -= np.pi / 8 + z -= np.pi / 8 + else: # purple region + # x -> x - pi/8 + # z -> z + pi/8 + follow = follow.compose(XGate().power(1 / 2), [0], front=True) + follow = follow.compose(XGate().power(1 / 2), [1], front=True) + follow = follow.compose(XGate().power(-1 / 2), [0]) + follow = follow.compose(XGate().power(-1 / 2), [1]) + follow = follow.compose(ZGate(), [0], front=True) + follow = follow.compose(ZGate(), [0]) + if y + z < np.pi / 8: # eigenphase crossing condition: a_2 - pi/4 < a_3 + eigenphase_crossing = True + # corresponding red coordinates + x += np.pi / 8 + z -= np.pi / 8 + + if eigenphase_crossing: + y, z = -z, -y + + # final 3xSiSwap circuit: red --> fix crossing --> green or purple + red_decomp = _can_circuit(x, y, z) + circuit = circuit.compose(red_decomp, [0, 1]) + if eigenphase_crossing: + # y, z -> -z, -y + circuit = circuit.compose(XGate().power(1 / 2), [0], front=True) + circuit = circuit.compose(XGate().power(1 / 2), [1], front=True) + circuit = circuit.compose(XGate().power(-1 / 2), [0]) + circuit = circuit.compose(XGate().power(-1 / 2), [1]) + circuit = circuit.compose(XGate(), [0], front=True) + circuit = circuit.compose(XGate(), [0]) + circuit = circuit.compose(follow, [0, 1]) + + return circuit + + def _interleaving_single_qubit_gates(x, y, z): """ Find the single-qubit gates given the interaction coefficients @@ -298,3 +264,23 @@ def _interleaving_single_qubit_gates(x, y, z): # the returned circuit is the SiSwap sandwich return V + + +def _remove_pre_post(circuit, decomp): + """ + Given a circuit and its Weyl decomposition, multiply local gates before and after + it to get a new circuit which is equivalent to RXX.RYY.RZZ (up to global phase). + """ + D1 = Operator(decomp.K1r) + D2 = Operator(decomp.K1l) + E1 = Operator(decomp.K2r) + E2 = Operator(decomp.K2l) + + new_circuit = QuantumCircuit(2) + new_circuit.append(E1.adjoint(), [0]) + new_circuit.append(E2.adjoint(), [1]) + new_circuit.compose(circuit, inplace=True) + new_circuit.append(D1.adjoint(), [0]) + new_circuit.append(D2.adjoint(), [1]) + + return new_circuit From e0181480fd503387103efd88e0d91aea7239c091 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Sat, 18 Feb 2023 22:51:56 -0500 Subject: [PATCH 18/25] lint --- qiskit/synthesis/su4/siswap_decompose.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/synthesis/su4/siswap_decompose.py b/qiskit/synthesis/su4/siswap_decompose.py index 212b9d93db08..daa519a650f5 100644 --- a/qiskit/synthesis/su4/siswap_decompose.py +++ b/qiskit/synthesis/su4/siswap_decompose.py @@ -21,7 +21,7 @@ from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.quantum_info.operators import Operator from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylDecomposition -from qiskit.circuit.library import SiSwapGate, XGate, YGate, ZGate, SGate, SdgGate +from qiskit.circuit.library import SiSwapGate, XGate, YGate, ZGate from qiskit.synthesis.su4.utils import find_min_point, average_infidelity @@ -170,7 +170,7 @@ def _can_circuit(x, y, z): for instruction in inverse_circuit: if isinstance(instruction.operation, SiSwapGate): inverse_circuit_with_siswap_dg.z(0) - inverse_circuit_with_siswap_dg.append(SiSwapGate().inverse(), [0, 1]) + inverse_circuit_with_siswap_dg.append(SiSwapdgGate(), [0, 1]) inverse_circuit_with_siswap_dg.z(0) else: inverse_circuit_with_siswap_dg.append(instruction) From 512a86a61865c8fe12eb01fde5f60b2052f90e87 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Sat, 18 Feb 2023 23:34:06 -0500 Subject: [PATCH 19/25] lint --- qiskit/synthesis/su4/siswap_decompose.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/synthesis/su4/siswap_decompose.py b/qiskit/synthesis/su4/siswap_decompose.py index daa519a650f5..d08b92942b8a 100644 --- a/qiskit/synthesis/su4/siswap_decompose.py +++ b/qiskit/synthesis/su4/siswap_decompose.py @@ -170,7 +170,7 @@ def _can_circuit(x, y, z): for instruction in inverse_circuit: if isinstance(instruction.operation, SiSwapGate): inverse_circuit_with_siswap_dg.z(0) - inverse_circuit_with_siswap_dg.append(SiSwapdgGate(), [0, 1]) + inverse_circuit_with_siswap_dg.append(SiSwapGate().inverse(), [0, 1]) inverse_circuit_with_siswap_dg.z(0) else: inverse_circuit_with_siswap_dg.append(instruction) From 83ea3b662e92c766e53f50997c6e6257b2500116 Mon Sep 17 00:00:00 2001 From: Ali Javadi-Abhari Date: Wed, 22 Feb 2023 21:09:00 -0500 Subject: [PATCH 20/25] Update qiskit/synthesis/__init__.py --- qiskit/synthesis/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index 556b2ba7b9f9..2c43ee991658 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -79,7 +79,7 @@ SolovayKitaevDecomposition generate_basic_approximations -SU(4) Synthesis +SU(4) (2-qubit unitary) Synthesis =============== .. autosummary:: From ae38e99be4dd22912c73fe566d349394b077d680 Mon Sep 17 00:00:00 2001 From: Ali Javadi-Abhari Date: Thu, 23 Feb 2023 11:00:13 -0500 Subject: [PATCH 21/25] Update qiskit/synthesis/__init__.py --- qiskit/synthesis/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index 2c43ee991658..ed31a1ca0032 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -80,7 +80,7 @@ generate_basic_approximations SU(4) (2-qubit unitary) Synthesis -=============== +========================= .. autosummary:: :toctree: ../stubs/ From 5ae2fb3a341969e713568047dc714fd55b9df346 Mon Sep 17 00:00:00 2001 From: Ali Javadi-Abhari Date: Thu, 23 Feb 2023 22:14:39 -0500 Subject: [PATCH 22/25] Update qiskit/synthesis/__init__.py --- qiskit/synthesis/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/synthesis/__init__.py b/qiskit/synthesis/__init__.py index ed31a1ca0032..a8090c10a49d 100644 --- a/qiskit/synthesis/__init__.py +++ b/qiskit/synthesis/__init__.py @@ -80,7 +80,7 @@ generate_basic_approximations SU(4) (2-qubit unitary) Synthesis -========================= +================================= .. autosummary:: :toctree: ../stubs/ From 52c779870e717f9d8e454b3e4c0bc4d87034bb69 Mon Sep 17 00:00:00 2001 From: Ali Javadi-Abhari Date: Fri, 24 Feb 2023 09:33:00 -0500 Subject: [PATCH 23/25] Update qiskit/circuit/library/standard_gates/siswap.py Co-authored-by: Julien Gacon --- qiskit/circuit/library/standard_gates/siswap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/circuit/library/standard_gates/siswap.py b/qiskit/circuit/library/standard_gates/siswap.py index 34f365400207..1c0ff5f0d5fb 100644 --- a/qiskit/circuit/library/standard_gates/siswap.py +++ b/qiskit/circuit/library/standard_gates/siswap.py @@ -34,7 +34,7 @@ class SiSwapGate(Gate): └────────────┘└────────────┘ .. math:: - B\ q_0, q_1 = + \sqrt{\mathrm{iSwap}}\ q_0, q_1 = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & \frac{1}{\sqrt{2}} & \frac{i}{\sqrt{2}} & 0 \\ From 99b2c1f0f5fb187c2a887a2de689c26c300acda7 Mon Sep 17 00:00:00 2001 From: Ali Javadi-Abhari Date: Fri, 24 Feb 2023 09:33:23 -0500 Subject: [PATCH 24/25] Update qiskit/synthesis/su4/siswap_decompose.py Co-authored-by: Julien Gacon --- qiskit/synthesis/su4/siswap_decompose.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/synthesis/su4/siswap_decompose.py b/qiskit/synthesis/su4/siswap_decompose.py index d08b92942b8a..8604d8c8f9fe 100644 --- a/qiskit/synthesis/su4/siswap_decompose.py +++ b/qiskit/synthesis/su4/siswap_decompose.py @@ -51,8 +51,8 @@ class SiSwapDecomposer: Reference: 1. C. Huang et al, - *Towards ultra-high fidelity quantum operations: - SQiSW gate as a native two-qubit gate (2021)* + Towards ultra-high fidelity quantum operations: + SQiSW gate as a native two-qubit gate (2021) `arXiv:2105.06074 `_ """ From eac72e4344962df472910c8f2583b13b84b686a6 Mon Sep 17 00:00:00 2001 From: Ali Javadi-Abhari Date: Fri, 24 Feb 2023 09:34:03 -0500 Subject: [PATCH 25/25] Update qiskit/synthesis/su4/siswap_decompose.py Co-authored-by: Julien Gacon --- qiskit/synthesis/su4/siswap_decompose.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/synthesis/su4/siswap_decompose.py b/qiskit/synthesis/su4/siswap_decompose.py index 8604d8c8f9fe..6ed5a7e4584a 100644 --- a/qiskit/synthesis/su4/siswap_decompose.py +++ b/qiskit/synthesis/su4/siswap_decompose.py @@ -76,10 +76,10 @@ def __call__( """Decompose a two-qubit unitary into using the sqrt(iSWAP) gate. Args: - unitary: a 4x4 unitary to synthesize. + unitary: A 4x4 unitary to synthesize. basis_fidelity: Fidelity of the iSWAP gate. approximate: Approximates if basis fidelities are less than 1.0. - _num_basis_uses: force a particular approximation by passing a number in [0, 3]. + _num_basis_uses: Force a particular approximation by passing a number in [0, 3]. Returns: QuantumCircuit: Synthesized circuit.