Source code for mindquantum.algorithm.compiler.decompose.universal_decompose.two_qubit_decompose

# Copyright 2021 Huawei Technologies Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================
"""Two-qubit gate decomposition."""
# pylint: disable=invalid-name
from math import pi, sqrt

import numpy as np
from scipy import linalg

from mindquantum.core import gates
from mindquantum.core.circuit import Circuit
from mindquantum.core.gates import QuantumGate

from .. import utils
from ..fixed_decompose import crx_decompose, cry_decompose, crz_decompose
from ..utils import (
    M_DAG,
    A,
    M,
    is_tensor_prod,
    kron_decomp,
    kron_factor_4x4_to_2x2s,
    optimize_circuit,
    params_abc,
    params_u3,
    params_zyz,
)


[docs]def tensor_product_decompose(gate: QuantumGate, return_u3: bool = True) -> Circuit: """ Tensor product decomposition of a 2-qubit gate. Args: gate (:class:`~.core.gates.QuantumGate`): 2-qubit gate composed by tensor product. return_u3 (bool): return gates in form of :class:`~.core.gates.U3` if ``True``, otherwise return :class:`~.core.gates.UnivMathGate`. Default: ``True``. Returns: :class:`~.core.circuit.Circuit`, including two single-qubit gates. Examples: >>> import numpy as np >>> import mindquantum as mq >>> from mindquantum.algorithm.compiler.decompose import tensor_product_decompose >>> g = mq.UnivMathGate('XY', np.kron(mq.X.matrix(), mq.Y.matrix())).on([0, 1]) >>> print(mq.Circuit() + g) ┏━━━━┓ q0: ──┨ ┠─── ┃ ┃ ┃ XY ┃ q1: ──┨ ┠─── ┗━━━━┛ >>> circ_decomposed = tensor_product_decompose(g) >>> print(circ_decomposed) ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ q0: ──┨ U3(θ=π, φ=π/2, λ=-π/2) ┠─── ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ ┏━━━━━━━━━━━━━━━━━━━┓ q1: ──┨ U3(θ=π, φ=0, λ=0) ┠──────── ┗━━━━━━━━━━━━━━━━━━━┛ """ if len(gate.obj_qubits) != 2 or gate.ctrl_qubits: raise ValueError(f'{gate} is not a 2-qubit gate with designated qubits') if not is_tensor_prod(gate.matrix()): raise ValueError(f'{gate} is not a tensor-product unitary gate.') u0, u1 = kron_decomp(gate.matrix()) circ = Circuit() if return_u3: circ += gates.U3(*params_u3(u0)).on(gate.obj_qubits[0]) # pylint: disable=no-value-for-parameter circ += gates.U3(*params_u3(u1)).on(gate.obj_qubits[1]) # pylint: disable=no-value-for-parameter else: circ += gates.UnivMathGate('U0', u0).on(gate.obj_qubits[0]) circ += gates.UnivMathGate('U1', u1).on(gate.obj_qubits[1]) return optimize_circuit(circ)
[docs]def abc_decompose(gate: QuantumGate, return_u3: bool = True) -> Circuit: """ Decompose two-qubit controlled gate via ABC decomposition. Args: gate (:class:`~.core.gates.QuantumGate`): quantum gate with 1 control bit and 1 target bit. return_u3 (bool): return gates in form of :class:`~.core.gates.U3` if ``True``, otherwise return :class:`~.core.gates.UnivMathGate`. Default: ``True``. Returns: :class:`~.core.circuit.Circuit`, including at most 2 CNOT gates and 4 single-qubit gates. Examples: >>> import mindquantum as mq >>> from mindquantum.algorithm.compiler.decompose import abc_decompose >>> from scipy.stats import unitary_group >>> g = mq.UnivMathGate('U', unitary_group.rvs(2, random_state=123)).on(1, 0) >>> print(mq.Circuit() + g) q0: ────■───── ┏━┻━┓ q1: ──┨ U ┠─── ┗━━━┛ >>> circ_decomposed = abc_decompose(g) >>> print(circ_decomposed) ┏━━━━━━━━━━━━┓ q0: ───────────────────■──────────────────────────────────────■───┨ RZ(1.1469) ┠──────────────────── ┃ ┃ ┗━━━━━━━━━━━━┛ ┏━━━━━━━━━━━━┓ ┏━┻━┓ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┏━┻━┓ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ q1: ──┨ RZ(2.6016) ┠─┨╺╋╸┠─┨ U3(θ=1.1043, φ=π, λ=-0.6572) ┠─┨╺╋╸┠─┨ U3(θ=1.1043, φ=-5.086, λ=0) ┠─── ┗━━━━━━━━━━━━┛ ┗━━━┛ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┗━━━┛ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ """ if len(gate.ctrl_qubits) != 1 or len(gate.obj_qubits) != 1: raise ValueError(f'{gate} is not a two-qubit controlled gate with designated qubits') if isinstance(gate, gates.RX): return crx_decompose(gate)[0] if isinstance(gate, gates.RY): return cry_decompose(gate)[0] if isinstance(gate, gates.RZ): return crz_decompose(gate)[0] cq = gate.ctrl_qubits[0] tq = gate.obj_qubits[0] _, (_, phi, lam) = params_zyz(gate.matrix()) alpha, (a, b, c) = params_abc(gate.matrix()) circ = Circuit() if return_u3: # regardless global phases circ += gates.RZ((lam - phi) / 2).on(tq) circ += gates.X.on(tq, cq) circ += gates.U3(*params_u3(b)).on(tq) # pylint: disable=no-value-for-parameter circ += gates.X.on(tq, cq) circ += gates.U3(*params_u3(a)).on(tq) # pylint: disable=no-value-for-parameter circ += gates.RZ(alpha).on(cq) else: circ += gates.UnivMathGate('C', c).on(tq) circ += gates.X.on(tq, cq) circ += gates.UnivMathGate('B', b).on(tq) circ += gates.X.on(tq, cq) circ += gates.UnivMathGate('A', a).on(tq) circ += gates.PhaseShift(alpha).on(cq) return optimize_circuit(circ)
# pylint: disable=too-many-locals
[docs]def kak_decompose(gate: QuantumGate, return_u3: bool = True) -> Circuit: r""" KAK decomposition (CNOT basis) of an arbitrary two-qubit gate. For more detail, please refer to `An Introduction to Cartan's KAK Decomposition for QC Programmers <https://arxiv.org/abs/quant-ph/0406176>`_. Args: gate (:class:`~.core.gates.QuantumGate`): 2-qubit quantum gate. return_u3 (bool): return gates in form of :class:`~.core.gates.U3` if ``True``, otherwise return :class:`~.core.gates.UnivMathGate`. Default: ``True``. Returns: :class:`~.core.circuit.Circuit`, including at most 3 CNOT gates and 6 single-qubit gates. Examples: >>> import mindquantum as mq >>> from mindquantum.algorithm.compiler.decompose import kak_decompose >>> from scipy.stats import unitary_group >>> g = mq.UnivMathGate('U', unitary_group.rvs(4, random_state=123)).on([0, 1]) >>> print(mq.Circuit() + g) ┏━━━┓ q0: ──┨ ┠─── ┃ ┃ ┃ U ┃ q1: ──┨ ┠─── ┗━━━┛ >>> circ_decomposed = kak_decompose(g) >>> print(circ_decomposed) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ q0: ──┨ U3(θ=2.2601, φ=-3.602, λ=2.4907) ┠───■───┨ U3(θ=π/2, φ=-0.2573, λ=-π) ┠───■───↯─ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┏━┻━┓ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┏━┻━┓ q1: ──┨ U3(θ=1.846, φ=-2.9209, λ=0.5375) ┠─┨╺╋╸┠─┨ U3(θ=0, φ=-0.19, λ=-0.19) ┠──┨╺╋╸┠─↯─ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┗━━━┛ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┗━━━┛ ┏━━━━━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ q0: ──┨ U3(θ=π/2, φ=0, λ=π) ┠─────────■───┨ U3(θ=2.273, φ=-1.8708, λ=0.7431) ┠─── ┗━━━━━━━━━━━━━━━━━━━━━┛ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┏━┻━┓ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ q1: ──┨ U3(θ=0, φ=0.358, λ=0.358) ┠─┨╺╋╸┠─┨ U3(θ=2.7317, φ=1.8583, λ=0.6685) ┠─── ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┗━━━┛ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ """ if len(gate.obj_qubits) != 2 or gate.ctrl_qubits: raise ValueError(f'{gate} is not an arbitrary 2-qubit gate with designated qubits') pauli_i = gates.I.matrix() pauli_x = gates.X.matrix() pauli_z = gates.Z.matrix() # construct a new matrix replacing U u_su4 = M_DAG @ utils.remove_glob_phase(gate.matrix()) @ M # ensure the decomposed object is in SU(4) ur = np.real(u_su4) # real part of u_su4 ui = np.imag(u_su4) # imagine part of u_su4 # simultaneous SVD decomposition (q_left, q_right), (dr, di) = utils.simult_svd(ur, ui) d = dr + 1j * di _, a1, a0 = kron_factor_4x4_to_2x2s(M @ q_left @ M_DAG) _, b1, b0 = kron_factor_4x4_to_2x2s(M @ q_right.T @ M_DAG) k = linalg.inv(A) @ np.angle(np.diag(d)) h1, h2, h3 = -k[1:] u0 = 1j / sqrt(2) * (pauli_x + pauli_z) @ linalg.expm(-1j * (h1 - pi / 4) * pauli_x) v0 = -1j / sqrt(2) * (pauli_x + pauli_z) u1 = linalg.expm(-1j * h3 * pauli_z) v1 = linalg.expm(1j * h2 * pauli_z) w = (pauli_i - 1j * pauli_x) / sqrt(2) # list of operators rots1 = [b0, u0, v0, a0 @ w] # rotation gate on idx1 rots2 = [b1, u1, v1, a1 @ w.conj().T] idx1, idx2 = gate.obj_qubits circ = Circuit() if return_u3: circ += gates.U3(*params_u3(rots1[0])).on(idx1) # pylint: disable=no-value-for-parameter circ += gates.U3(*params_u3(rots2[0])).on(idx2) # pylint: disable=no-value-for-parameter circ += gates.X.on(idx2, idx1) circ += gates.U3(*params_u3(rots1[1])).on(idx1) # pylint: disable=no-value-for-parameter circ += gates.U3(*params_u3(rots2[1])).on(idx2) # pylint: disable=no-value-for-parameter circ += gates.X.on(idx2, idx1) circ += gates.U3(*params_u3(rots1[2])).on(idx1) # pylint: disable=no-value-for-parameter circ += gates.U3(*params_u3(rots2[2])).on(idx2) # pylint: disable=no-value-for-parameter circ += gates.X.on(idx2, idx1) circ += gates.U3(*params_u3(rots1[3])).on(idx1) # pylint: disable=no-value-for-parameter circ += gates.U3(*params_u3(rots2[3])).on(idx2) # pylint: disable=no-value-for-parameter else: circ += gates.UnivMathGate('B0', rots1[0]).on(idx1) circ += gates.UnivMathGate('B1', rots2[0]).on(idx2) circ += gates.X.on(idx2, idx1) circ += gates.UnivMathGate('U0', rots1[1]).on(idx1) circ += gates.UnivMathGate('U1', rots2[1]).on(idx2) circ += gates.X.on(idx2, idx1) circ += gates.UnivMathGate('V0', rots1[2]).on(idx1) circ += gates.UnivMathGate('V1', rots2[2]).on(idx2) circ += gates.X.on(idx2, idx1) circ += gates.UnivMathGate('W0', rots1[3]).on(idx1) circ += gates.UnivMathGate('W1', rots2[3]).on(idx2) return optimize_circuit(circ)