# Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.
#
# 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.

import warnings
import numpy as np
import paddle
from paddle_quantum.circuit import UAnsatz
from paddle_quantum.utils import NKron, partial_trace, dagger
from paddle import matmul, trace, divide, kron, add, multiply
from paddle import sin, cos, real
from math import log2, sqrt


class LoccStatus(object):
    r"""用于表示 LOCCNet 中的一个 LOCC 态节点。

    由于我们在 LOCC 中不仅关心量子态的解析形式，同时还关心得到它的概率，以及是经过怎样的测量而得到。因此该类包含三个成员变量：量子态 ``state`` 、得到这个态的概率 ``prob`` 和得到这个态的测量的测量结果是什么，即 ``measured_result`` 。

    Attributes:
        state (paddle.Tensor): 表示量子态的矩阵形式
        prob (paddle.Tensor): 表示得到这个量子态的概率
        measured_result (str): 表示得到这个态的测量函数的测量结果
    """

    def __init__(self, state=None, prob=None, measured_result=None):
        r"""构造函数，用于实例化一个 ``LoccStatus`` 对象。

        Args:
            state (paddle.Tensor): 默认为 ``None`` ，该 ``LoccStatus`` 的量子态的矩阵形式
            prob (paddle.Tensor): 默认为 ``None`` ，得到该量子态的概率
            measured_result (str): 默认为 ``None`` ，表示得到这个态的测量函数的测量结果
        """
        super(LoccStatus, self).__init__()
        if state is None and prob is None and measured_result is None:
            self.state = paddle.to_tensor(np.array([1], dtype=np.complex128))
            self.prob = paddle.to_tensor(np.array([1], dtype=np.float64))
            self.measured_result = ""
        else:
            self.state = state
            self.prob = prob
            self.measured_result = measured_result

    def clone(self):
        r"""创建一个当前对象的副本。

        Returns:
            LoccStatus: 当前对象的副本
        """
        return LoccStatus(self.state, self.prob, self.measured_result)

    def __getitem__(self, item):
        if item == 0:
            return self.state
        elif item == 1:
            return self.prob
        elif item == 2:
            return self.measured_result
        else:
            raise ValueError("too many values to unpack (expected 3)")

    def __repr__(self):
        return f"state: {self.state.numpy()}\nprob: {self.prob.numpy()[0]}\nmeasured_result: {self.measured_result}"

    def __str__(self):
        return f"state: {self.state.numpy()}\nprob: {self.prob.numpy()[0]}\nmeasured_result: {self.measured_result}"


class LoccParty(object):
    r"""LOCC 的参与方。

    Attributes:
        qubit_number (int): 参与方的量子比特数量
    """

    def __init__(self, qubit_number):
        r"""
        构造函数，用于实例化一个 ``LoccParty`` 对象。

        Args:
            qubit_number (int): 参与方的量子比特数量
        """
        super(LoccParty, self).__init__()
        self.qubit_number = qubit_number
        self.qubits = [None] * qubit_number

    def __setitem__(self, key, value):
        self.qubits[key] = value

    def __getitem__(self, item):
        return self.qubits[item]

    def __len__(self):
        return self.qubit_number


class LoccAnsatz(UAnsatz):
    r"""继承 ``UAnsatz`` 类，目的是建立在 LOCC 任务上的电路模板。

    在 LOCC 任务中，每一方参与者只能在自己的量子比特上进行量子操作。因此我们只允许在每一方的量子比特上添加本地电路门。

    Attributes:
        party (LoccParty): 参与方
        m (int): 参与方的量子比特数量
    """

    def __init__(self, party, n):
        r"""构造函数，用于实例化一个 ``LoccAnsatz`` 的对象。

        Args:
            party (LoccParty): 参与方
            n (int): 全局的量子比特数量
        """
        super(LoccAnsatz, self).__init__(n)
        self.party = party
        self.m = len(self.party)
        self.measure = None
        self.expecval = None

    def run(self, status=None):
        r"""运行当前添加的电路门，并获得运行后的 LOCC 态节点。

        Args:
            status (LoccStatus or list): 作为LOCC下的量子电路的输入的 LOCC 态节点，其类型应该为 ``LoccStatus`` 或由其组成的 ``list``

        Returns:
            LoccStatus or list: 量子电路运行后得到的 LOCC 态节点,类型为 ``LoccStatus`` 或由其组成的 ``list``
        """
        if isinstance(status, LoccStatus):
            assert int(log2(sqrt(status.state.numpy().size))) == self.n, "the length of qubits should be same"
            state = super(LoccAnsatz, self).run_density_matrix(status.state)
            new_status = LoccStatus(state, status.prob, status.measured_result)
        elif isinstance(status, list):
            assert int(log2(sqrt(status[0].state.numpy().size))) == self.n, "the length of qubits should be same"
            new_status = list()
            for each_status in status:
                state = super(LoccAnsatz, self).run_density_matrix(each_status.state)
                new_status.append(LoccStatus(state, each_status.prob, each_status.measured_result))
        else:
            raise ValueError("can't recognize the input status")

        return new_status

    def rx(self, theta, which_qubit):
        r"""添加关于 x 轴的单量子比特旋转门。

        Args:
            theta (Tensor): 量子门的角度
            which_qubit (int): 添加该门量子比特编号
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).rx(theta, which_qubit)

    def ry(self, theta, which_qubit):
        r"""添加关于 y 轴的单量子比特旋转门。

        Args:
            theta (Tensor): 量子门的角度
            which_qubit (int): 添加该门量子比特编号
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).ry(theta, which_qubit)

    def rz(self, theta, which_qubit):
        r"""添加关于 z 轴的单量子比特旋转门。

        Args:
            theta (Tensor): 量子门的角度
            which_qubit (int): 添加该门量子比特编号
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).rz(theta, which_qubit)

    def cnot(self, control):
        r"""添加一个 CNOT 门。

        Args:
            control (list): 作用在的 qubit 的编号，``control[0]`` 为控制位，``control[1]`` 为目标位，其值都应该在 :math:`[0, m)` 范围内， :math:`m` 为该参与方的量子比特数
        """
        control = [self.party[which_qubit] for which_qubit in control]
        super(LoccAnsatz, self).cnot(control)

    def swap(self, control):
        r"""添加一个 SWAP 门。

        Args:
            control (list): 作用在的 qubit 的编号，``control[0]`` 和 ``control[1]`` 是想要交换的位，其值都应该在 :math:`[0, m)`范围内， :math:`m` 为该参与方的量子比特数
        """
        control = [self.party[which_qubit] for which_qubit in control]
        super(LoccAnsatz, self).swap(control)

    def x(self, which_qubit):
        r"""添加单量子比特 X 门。

        Args:
            which_qubit (int): 添加该门量子比特编号
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).x(which_qubit)

    def y(self, which_qubit):
        r"""添加单量子比特 Y 门。

        Args:
            which_qubit (int): 添加该门量子比特编号
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).y(which_qubit)

    def z(self, which_qubit):
        r"""添加单量子比特 Z 门。

        Args:
            which_qubit (int): 添加该门量子比特编号
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).z(which_qubit)

    def h(self, which_qubit):
        r"""添加单量子比特 Hadamard 门。

        Args:
            which_qubit (int): 添加该门量子比特编号
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).h(which_qubit)

    def s(self, which_qubit):
        r"""添加单量子比特 S 门。

        Args:
            which_qubit (int): 添加该门量子比特编号
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).s(which_qubit)

    def t(self, which_qubit):
        r"""添加单量子比特 T 门。

        Args:
            which_qubit (int): 添加该门量子比特编号
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).t(which_qubit)

    def u3(self, theta, phi, lam, which_qubit):
        r"""添加一个单量子比特的旋转门。

        Args:
            theta (Tensor): 旋转角度 :math:`\theta` 。
            phi (Tensor): 旋转角度 :math:`\phi` 。
            lam (Tensor): 旋转角度 :math:`\lambda` 。
            which_qubit (int): 作用在的 qubit 的编号，其值应该在 :math:`[0, m)` 范围内， :math:`m` 为该量子电路的量子比特数
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).u3(theta, phi, lam, which_qubit)

    def universal_2_qubit_gate(self, theta, which_qubits):
        r"""添加 2-qubit 通用门，这个通用门需要 15 个参数。

        Args:
            theta (Tensor): 2-qubit 通用门的参数，其维度为 ``(15, )``
            which_qubits(list): 作用的量子比特编号
        """

        super(LoccAnsatz, self).universal_2_qubit_gate(theta, which_qubits)

    def universal_3_qubit_gate(self, theta, which_qubits):
        r"""添加 3-qubit 通用门，这个通用门需要 81 个参数。

        Note:
            参考: https://cds.cern.ch/record/708846/files/0401178.pdf

        Args:
            theta (Tensor): 3-qubit 通用门的参数，其维度为 ``(81, )``
            which_qubits(list): 作用的量子比特编号
        """

        super(LoccAnsatz, self).universal_3_qubit_gate(theta, which_qubits)

    def superposition_layer(self):
        r"""添加一层 Hadamard 门。
        """
        for which_qubit in self.party.qubits:
            self.h(which_qubit)

    def weak_superposition_layer(self):
        r"""添加一层旋转角度为 :math:`\pi/4` 的 Ry 门。
        """
        _theta = paddle.to_tensor(np.array([np.pi / 4]))
        for which_qubit in self.party.qubits:
            self.ry(_theta, which_qubit)

    def linear_entangled_layer(self, theta, depth, which_qubits=None):
        r"""添加 ``depth`` 层包含 Ry 门，Rz 门和 CNOT 门的线性纠缠层。

        Attention:
            ``theta`` 的维度为 ``(depth, m, 2)`` ，最低维内容为对应的 ``ry`` 和 ``rz`` 的参数。

        Args:
            theta (Tensor): Ry 门和 Rz 门的旋转角度
            depth (int): 纠缠层的深度
            which_qubits(list): 作用的量子比特编号
        """
        assert self.m > 1, 'you need at least 2 qubits'
        assert len(theta.shape) == 3, 'the shape of theta is not right'
        assert theta.shape[2] == 2, 'the shape of theta is not right'
        # assert theta.shape[1] == self.m, 'the shape of theta is not right'
        assert theta.shape[0] == depth, 'the depth of theta has a mismatch'

        if which_qubits is None:
            which_qubits = list(range(self.m))

        for repeat in range(depth):
            for i, q in enumerate(which_qubits):
                self.ry(theta[repeat][i][0], q)
            for i in range(len(which_qubits) - 1):
                self.cnot([which_qubits[i], which_qubits[i + 1]])
            for i, q in enumerate(which_qubits):
                self.rz(theta[repeat][i][1], q)
            for i in range(len(which_qubits) - 1):
                self.cnot([which_qubits[i + 1], which_qubits[i]])

    def real_entangled_layer(self, theta, depth, which_qubits=None):
        r"""添加 ``depth`` 层包含 Ry 门和 CNOT 门的强纠缠层。

        Note:
            这一层量子门的数学表示形式为实数酉矩阵。

        Attention:
            ``theta`` 的维度为 ``(depth, m, 1)``

        Args:
            theta (Tensor): Ry 门的旋转角度
            depth (int): 纠缠层的深度
            which_qubits(list): 作用的量子比特编号
        """
        assert self.m > 1, 'you need at least 2 qubits'
        assert len(theta.shape) == 3, 'the shape of theta is not right'
        assert theta.shape[2] == 1, 'the shape of theta is not right'
        # assert theta.shape[1] == len(self.party), 'the shape of theta is not right'
        assert theta.shape[0] == depth, 'the depth of theta has a mismatch'

        if which_qubits is None:
            which_qubits = list(range(self.m))

        for repeat in range(depth):
            for i, q in enumerate(which_qubits):
                self.ry(theta[repeat][i][0], q)
            for i in range(len(which_qubits) - 1):
                self.cnot([which_qubits[i], which_qubits[i + 1]])
            self.cnot([which_qubits[-1], which_qubits[0]])

    def complex_entangled_layer(self, theta, depth, which_qubits=None):
        r"""添加 ``depth`` 层包含 U3 门和 CNOT 门的强纠缠层。

        Note:
            这一层量子门的数学表示形式为复数酉矩阵。

        Attention:
            ``theta`` 的维度为 ``(depth, m, 3)``，最低维内容为对应的 ``u3`` 的参数 ``(theta, phi, lam)``

        Args:
            theta (Tensor): U3 门的旋转角度
            depth (int): 纠缠层的深度
            which_qubits(list): 作用的量子比特编号
        """
        assert self.m > 1, 'you need at least 2 qubits'
        assert len(theta.shape) == 3, 'the shape of theta is not right'
        assert theta.shape[2] == 3, 'the shape of theta is not right'
        # assert theta.shape[1] == self.m, 'the shape of theta is not right'
        assert theta.shape[0] == depth, 'the depth of theta has a mismatch'

        if which_qubits is None:
            which_qubits = list(range(self.m))

        for repeat in range(depth):
            for i, q in enumerate(which_qubits):
                self.u3(theta[repeat][i][0], theta[repeat][i][1], theta[repeat][i][2], q)
            for i in range(len(which_qubits) - 1):
                self.cnot([which_qubits[i], which_qubits[i + 1]])
            self.cnot([which_qubits[-1], which_qubits[0]])

    def real_block_layer(self, theta, depth):
        r"""添加 ``depth`` 层包含 Ry 门和 CNOT 门的弱纠缠层。

        Note:
            这一层量子门的数学表示形式为实数酉矩阵。

        Attention:
            ``theta`` 的维度为 ``(depth, m-1, 4)`` 。

        Args:
            theta(Tensor): Ry 门的旋转角度
            depth(int): 纠缠层的深度
        """
        assert self.m > 1, 'you need at least 2 qubits'
        assert len(theta.shape) == 3, 'The dimension of theta is not right'
        _depth, _number, block = theta.shape
        assert depth > 0, 'depth must be greater than zero'
        assert _depth == depth, 'the depth of parameters has a mismatch'
        assert _number == self.m - 1 and block == 4, 'The shape of theta is not right'

        if self.m % 2 == 0:
            for i in range(depth):
                self.__add_real_layer(theta[i][:int(self.m / 2)], [0, self.m - 1])
                self.__add_real_layer(theta[i][int(self.m / 2):], [1, self.m - 2]) if self.m > 2 else None
        else:
            for i in range(depth):
                self.__add_real_layer(theta[i][:int((self.m - 1) / 2)], [0, self.m - 2])
                self.__add_real_layer(theta[i][int((self.m - 1) / 2):], [1, self.m - 1])

    def complex_block_layer(self, theta, depth):
        r"""添加 ``depth`` 层包含 U3 门和 CNOT 门的弱纠缠层。

        Note:
            这一层量子门的数学表示形式为复数酉矩阵。

        Attention:
            ``theta`` 的维度为 ``(depth, m-1, 12)`` 。

        Args:
            theta (Tensor): U3 门的角度信息
            depth (int): 纠缠层的深度
        """
        assert self.m > 1, 'you need at least 2 qubits'
        assert len(theta.shape) == 3, 'The dimension of theta is not right'
        assert depth > 0, 'depth must be greater than zero'
        _depth, _number, block = theta.shape
        assert _depth == depth, 'the depth of parameters has a mismatch'
        assert _number == self.m - 1 and block == 12, 'The shape of theta is not right'

        if self.m % 2 == 0:
            for i in range(depth):
                self.__add_complex_layer(theta[i][:int(self.m / 2)], [0, self.m - 1])
                self.__add_complex_layer(theta[i][int(self.m / 2):], [1, self.m - 2]) if self.n > 2 else None
        else:
            for i in range(depth):
                self.__add_complex_layer(theta[i][:int((self.m - 1) / 2)], [0, self.m - 2])
                self.__add_complex_layer(theta[i][int((self.m - 1) / 2):], [1, self.m - 1])

    def __add_real_block(self, theta, position):
        r"""
        Add a real block to the circuit in (position). theta is a one dimensional tensor

        Note:
            这是内部函数，你并不需要直接调用到该函数。
        """
        assert len(theta) == 4, 'the length of theta is not right'
        assert 0 <= position[0] < self.m and 0 <= position[1] < self.m, 'position is out of range'
        self.ry(theta[0], position[0])
        self.ry(theta[1], position[1])

        self.cnot([position[0], position[1]])

        self.ry(theta[2], position[0])
        self.ry(theta[3], position[1])

    def __add_complex_block(self, theta, position):
        r"""
        Add a complex block to the circuit in (position). theta is a one dimensional tensor

        Note:
            这是内部函数，你并不需要直接调用到该函数。
        """
        assert len(theta) == 12, 'the length of theta is not right'
        assert 0 <= position[0] < self.m and 0 <= position[1] < self.m, 'position is out of range'
        self.u3(theta[0], theta[1], theta[2], position[0])
        self.u3(theta[3], theta[4], theta[5], position[1])

        self.cnot([position[0], position[1]])

        self.u3(theta[6], theta[7], theta[8], position[0])
        self.u3(theta[9], theta[10], theta[11], position[1])

    def __add_real_layer(self, theta, position):
        r"""
        Add a real layer on the circuit. theta is a two dimensional tensor. position is the qubit range the layer needs to cover

        Note:
            这是内部函数，你并不需要直接调用到该函数。
        """
        assert theta.shape[1] == 4 and theta.shape[0] == (position[1] - position[0] + 1) / 2, \
            'the shape of theta is not right'
        for i in range(position[0], position[1], 2):
            self.__add_real_block(theta[int((i - position[0]) / 2)], [i, i + 1])

    def __add_complex_layer(self, theta, position):
        r"""
        Add a complex layer on the circuit.
        Theta is a two dimensional tensor.
        Position is the qubit range the layer needs to cover

        Note:
            这是内部函数，你并不需要直接调用到该函数。
        """
        assert theta.shape[1] == 12 and theta.shape[0] == (position[1] - position[0] + 1) / 2, \
            'the shape of theta is not right'
        for i in range(position[0], position[1], 2):
            self.__add_complex_block(theta[int((i - position[0]) / 2)], [i, i + 1])

    def amplitude_damping(self, gamma, which_qubit):
        r"""添加振幅阻尼信道。

        其 Kraus 算符为：

        .. math::

            E_0 = \begin{bmatrix} 1 & 0 \\ 0 & \sqrt{1-\gamma} \end{bmatrix},
            E_1 = \begin{bmatrix} 0 & \sqrt{\gamma} \\ 0 & 0 \end{bmatrix}.

        Args:
            gamma (float): 减振概率，其值应该在 :math:`[0, 1]` 区间内
            which_qubit (int): 该信道作用在的 qubit 的编号，其值应该在 :math:`[0, m)` 范围内， :math:`m` 为该参与方的量子比特数
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).amplitude_damping(gamma, which_qubit)

    def generalized_amplitude_damping(self, gamma, p, which_qubit):
        r"""添加广义振幅阻尼信道。

        其 Kraus 算符为：

        .. math::

            E_0 = \sqrt(p) \begin{bmatrix} 1 & 0 \\ 0 & \sqrt{1-\gamma} \end{bmatrix},
            E_1 = \sqrt(p) \begin{bmatrix} 0 & \sqrt{\gamma} \\ 0 & 0 \end{bmatrix},\\
            E_2 = \sqrt(1-p) \begin{bmatrix} \sqrt{1-\gamma} & 0 \\ 0 & 1 \end{bmatrix},
            E_3 = \sqrt(1-p) \begin{bmatrix} 0 & 0 \\ \sqrt{\gamma} & 0 \end{bmatrix}.

        Args:
            gamma (float): 减振概率，其值应该在 :math:`[0, 1]` 区间内
            p (float): 激发概率，其值应该在 :math:`[0, 1]` 区间内
            which_qubit (int): 该信道作用在的 qubit 的编号，其值应该在 :math:`[0, m)` 范围内， :math:`m` 为该参与方的量子比特数
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).generalized_amplitude_damping(gamma, p, which_qubit)

    def phase_damping(self, gamma, which_qubit):
        r"""添加相位阻尼信道。

        其 Kraus 算符为：

        .. math::

            E_0 = \begin{bmatrix} 1 & 0 \\ 0 & \sqrt{1-\gamma} \end{bmatrix},
            E_1 = \begin{bmatrix} 0 & 0 \\ 0 & \sqrt{\gamma} \end{bmatrix}.

        Args:
            gamma (float): phase damping 信道的参数，其值应该在 :math:`[0, 1]` 区间内
            which_qubit (int): 该信道作用在的 qubit 的编号，其值应该在 :math:`[0, m)` 范围内， :math:`m` 为该参与方的量子比特数
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).phase_damping(gamma, which_qubit)

    def bit_flip(self, p, which_qubit):
        r"""添加比特反转信道。

        其 Kraus 算符为：

        .. math::

            E_0 = \sqrt{1-p} I,
            E_1 = \sqrt{p} X.

        Args:
            p (float): 发生 bit flip 的概率，其值应该在 :math:`[0, 1]` 区间内
            which_qubit (int): 该信道作用在的 qubit 的编号，其值应该在 :math:`[0, m)` 范围内， :math:`m` 为该参与方的量子比特数
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).bit_flip(p, which_qubit)

    def phase_flip(self, p, which_qubit):
        r"""添加相位反转信道。

        其 Kraus 算符为：

        .. math::

            E_0 = \sqrt{1 - p} I,
            E_1 = \sqrt{p} Z.

        Args:
            p (float): 发生 phase flip 的概率，其值应该在 :math:`[0, 1]` 区间内
            which_qubit (int): 该信道作用在的 qubit 的编号，其值应该在 :math:`[0, m)` 范围内， :math:`m` 为该参与方的量子比特数
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).phase_flip(p, which_qubit)

    def bit_phase_flip(self, p, which_qubit):
        r"""添加比特相位反转信道。

        其 Kraus 算符为：

        .. math::

            E_0 = \sqrt{1 - p} I,
            E_1 = \sqrt{p} Y.

        Args:
            p (float): 发生 bit phase flip 的概率，其值应该在 :math:`[0, 1]` 区间内
            which_qubit (int): 该信道作用在的 qubit 的编号，其值应该在 :math:`[0, m)` 范围内， :math:`m` 为该参与方的量子比特数
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).bit_phase_flip(p, which_qubit)

    def depolarizing(self, p, which_qubit):
        r"""添加去极化信道。

        其 Kraus 算符为：

        .. math::

            E_0 = \sqrt{1-p} I,
            E_1 = \sqrt{p/3} X,
            E_2 = \sqrt{p/3} Y,
            E_3 = \sqrt{p/3} Z.

        Args:
            p (float): depolarizing 信道的参数，其值应该在 :math:`[0, 1]` 区间内
            which_qubit (int): 该信道作用在的 qubit 的编号，其值应该在 :math:`[0, m)` 范围内， :math:`m` 为该参与方的量子比特数
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).depolarizing(p, which_qubit)

    def pauli_channel(self, p_x, p_y, p_z, which_qubit):
        r"""添加泡利信道。

        Args:
            p_x (float): 泡利矩阵 X 的对应概率，其值应该在 :math:`[0, 1]` 区间内
            p_y (float): 泡利矩阵 Y 的对应概率，其值应该在 :math:`[0, 1]` 区间内
            p_z (float): 泡利矩阵 Z 的对应概率，其值应该在 :math:`[0, 1]` 区间内
            which_qubit (int): 该信道作用在的 qubit 的编号，其值应该在 :math:`[0, m)` 范围内， :math:`m` 为该参与方的量子比特数

        Note:
            三个输入的概率加起来需要小于等于 1。
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).pauli_channel(p_x, p_y, p_z, which_qubit)

    def customized_channel(self, ops, which_qubit):
        r"""添加自定义的量子信道。

        Args:
            ops (list): 表示信道的 Kraus 算符的列表
            which_qubit (int): 该信道作用在的 qubit 的编号，其值应该在 :math:`[0, m)` 范围内， :math:`m` 为该参与方的量子比特数
        """
        which_qubit = self.party[which_qubit]
        super(LoccAnsatz, self).customized_channel(ops, which_qubit)


class LoccNet(paddle.nn.Layer):
    r"""用于设计我们的 LOCC 下的 protocol，并进行验证或者训练。
    """

    def __init__(self):
        r"""构造函数，用于实例化一个 LoccNet 对象。
        """
        super(LoccNet, self).__init__()
        self.parties_by_number = list()
        self.parties_by_name = dict()
        self.init_status = LoccStatus()

    def set_init_status(self, state, which_qubits):
        r"""对 LoccNet 的初始 LOCC 态节点进行初始化。

        Warning:
            该方法已弃用，请使用 ``set_init_state()`` 方法以代替。

        Args:
            state (Tensor): 输入的量子态的矩阵形式
            which_qubits (tuple or list): 该量子态所对应的量子比特，其形式为 ``(party_id, qubit_id)`` 的 ``tuple`` ，或者由其组成的 ``list``
        """
        warnings.warn('The member method set_init_status() is deprecated and please use set_init_state() instead.',
                      DeprecationWarning)

        self.set_init_state(state, which_qubits)

    def set_init_state(self, state, which_qubits):
        r"""对 LoccNet 的初始 LOCC 态节点进行初始化。

        Args:
            state (Tensor): 输入的量子态的矩阵形式
            which_qubits (tuple or list): 该量子态所对应的量子比特，其形式为 ``(party_id, qubit_id)`` 的 ``tuple`` ，或者由其组成的 ``list``
        """
        if isinstance(which_qubits, tuple):
            which_qubits = [which_qubits]
        temp_len = int(log2(sqrt(self.init_status.state.numpy().size)))
        self.init_status.state = kron(self.init_status.state, state)
        for idx, (party_id, qubit_id) in enumerate(which_qubits):
            if isinstance(party_id, str):
                self.parties_by_name[party_id][qubit_id] = (temp_len + idx)
            elif isinstance(party_id, int):
                self.parties_by_number[party_id][qubit_id] = (temp_len + idx)
            else:
                raise ValueError

    def partial_state(self, status, which_qubits, is_desired=True):
        r"""得到你想要的部分量子比特上的量子态。

        Args:
            status (LoccStatus or list): 输入的 LOCC 态节点，其类型应该为 ``LoccStatus`` 或者由其组成的 ``list``
            which_qubits (tuple or list): 指定的量子比特编号，其形式为 ``(party_id, qubit_id)`` 的 ``tuple`` ，或者由其组成的 ``list``
            is_desired (bool, optional): 默认是 ``True`` ，即返回量子比特上的部分量子态。如果为 ``False`` ，则抛弃这部分量子比特上的量子态，返回剩下的部分量子态

        Returns:
            LoccStatus or list: 得到部分量子态后的 LOCC 态节点，其类型为 ``LoccStatus`` 或者由其组成的 ``list``
        """
        if isinstance(which_qubits, tuple):
            which_qubits = [which_qubits]
        qubits_list = list()
        for party_id, qubit_id in which_qubits:
            if isinstance(party_id, str):
                qubits_list.append(self.parties_by_name[party_id][qubit_id])
            elif isinstance(party_id, int):
                qubits_list.append(self.parties_by_number[party_id][qubit_id])
            else:
                raise ValueError
        m = len(qubits_list)
        if isinstance(status, LoccStatus):
            n = int(log2(sqrt(status.state.numpy().size)))
        elif isinstance(status, list):
            n = int(log2(sqrt(status[0].state.numpy().size)))
        else:
            raise ValueError("can't recognize the input status")

        assert max(qubits_list) <= n, "qubit index out of range"
        origin_seq = list(range(0, n))
        target_seq = [idx for idx in origin_seq if idx not in qubits_list]
        target_seq = qubits_list + target_seq

        swapped = [False] * n
        swap_list = []
        for idx in range(0, n):
            if not swapped[idx]:
                next_idx = idx
                swapped[next_idx] = True
                while not swapped[target_seq[next_idx]]:
                    swapped[target_seq[next_idx]] = True
                    swap_list.append((next_idx, target_seq[next_idx]))
                    next_idx = target_seq[next_idx]

        cir = UAnsatz(n)
        for a, b in swap_list:
            cir.swap([a, b])

        if isinstance(status, LoccStatus):
            state = cir.run_density_matrix(status.state)
            if is_desired:
                state = partial_trace(state, 2 ** m, 2 ** (n - m), 2)
            else:
                state = partial_trace(state, 2 ** m, 2 ** (n - m), 1)
            new_status = LoccStatus(state, status.prob, status.measured_result)
        elif isinstance(status, list):
            new_status = list()
            for each_status in status:
                state = cir.run_density_matrix(each_status.state)
                if is_desired:
                    state = partial_trace(state, 2 ** m, 2 ** (n - m), 2)
                else:
                    state = partial_trace(state, 2 ** m, 2 ** (n - m), 1)
                new_status.append(LoccStatus(state, each_status.prob, each_status.measured_result))
        else:
            raise ValueError("can't recognize the input status")

        return new_status

    def reset_state(self, status, state, which_qubits):
        r"""用于重置你想要的量子比特上的部分量子态。

        Args:
            status (LoccStatus or list): 输入的 LOCC 态节点，其类型应该为 ``LoccStatus`` 或者由其组成的 ``list``
            state (Tensor): 输入的量子态的矩阵形式
            which_qubits (tuple or list): 指定需要被重置的量子比特编号，其形式为 ``(party_id, qubit_id)`` 的 ``tuple``，或者由其组成的 ``list``

        Returns:
            LoccStatus or list: 重置部分量子比特的量子态后的 LOCC 态节点，其类型为 ``LoccStatus`` 或者由其组成的 ``list``
        """
        if isinstance(which_qubits, tuple):
            which_qubits = [which_qubits]
        qubits_list = list()
        for party_id, qubit_id in which_qubits:
            if isinstance(party_id, str):
                qubits_list.append(self.parties_by_name[party_id][qubit_id])
            elif isinstance(party_id, int):
                qubits_list.append(self.parties_by_number[party_id][qubit_id])
            else:
                raise ValueError
        m = len(qubits_list)
        if isinstance(status, LoccStatus):
            n = int(log2(sqrt(status.state.numpy().size)))
        elif isinstance(status, list):
            n = int(log2(sqrt(status[0].state.numpy().size)))
        else:
            raise ValueError("can't recognize the input status")
        assert max(qubits_list) <= n, "qubit index out of range"

        origin_seq = list(range(0, n))
        target_seq = [idx for idx in origin_seq if idx not in qubits_list]
        target_seq = qubits_list + target_seq

        swapped = [False] * n
        swap_list = []
        for idx in range(0, n):
            if not swapped[idx]:
                next_idx = idx
                swapped[next_idx] = True
                while not swapped[target_seq[next_idx]]:
                    swapped[target_seq[next_idx]] = True
                    swap_list.append((next_idx, target_seq[next_idx]))
                    next_idx = target_seq[next_idx]

        cir0 = UAnsatz(n)
        for a, b in swap_list:
            cir0.swap([a, b])

        cir1 = UAnsatz(n)
        swap_list.reverse()
        for a, b in swap_list:
            cir1.swap([a, b])

        if isinstance(status, LoccStatus):
            _state = cir0.run_density_matrix(status.state)
            _state = partial_trace(_state, 2 ** m, 2 ** (n - m), 1)
            _state = kron(state, _state)
            _state = cir1.run_density_matrix(_state)
            new_status = LoccStatus(_state, status.prob, status.measured_result)
        elif isinstance(status, list):
            new_status = list()
            for each_status in status:
                _state = cir0.run_density_matrix(each_status.state)
                _state = partial_trace(_state, 2 ** m, 2 ** (n - m), 1)
                _state = kron(state, _state)
                _state = cir1.run_density_matrix(_state)
                new_status.append(LoccStatus(_state, each_status.prob, each_status.measured_result))
        else:
            raise ValueError("can't recognize the input status")

        return new_status

    def add_new_party(self, qubits_number, party_name=None):
        r"""添加一个新的 LOCC 的参与方。

        Args:
            qubits_number (int): 参与方的量子比特个数
            party_name (str, optional): 可选参数，默认为 ``None``，参与方的名字

        Note:
            你可以使用字符串或者数字对 party 进行索引。如果你想使用字符串索引，需要每次指定 ``party_name``；如果你想使用数字索引，则不需要指定 ``party_name``，其索引数字会从 0 开始依次增长。

        Returns:
            int or str: 该参与方的 ID，为数字或者字符串
        """
        party_id = None
        if party_name is None:
            party_id = party_name
            party_name = str(len(self.parties_by_name))
        elif isinstance(party_name, str) is False:
            raise ValueError
        if party_id is None:
            party_id = len(self.parties_by_name)

        new_party = LoccParty(qubits_number)
        self.parties_by_name[party_name] = new_party
        self.parties_by_number.append(new_party)

        return party_id

    def create_ansatz(self, party_id):
        r"""创建一个新的本地电路模板。

        Args:
            party_id (int or str): 参与方的 ID

        Returns:
            LoccAnsatz: 一个本地的量子电路
        """
        if isinstance(party_id, int):
            return LoccAnsatz(self.parties_by_number[party_id], self.get_qubit_number())
        elif isinstance(party_id, str):
            return LoccAnsatz(self.parties_by_name[party_id], self.get_qubit_number())
        else:
            raise ValueError

    def __measure_parameterized(self, state, which_qubits, result_desired, theta):
        r"""进行参数化的测量。

        Args:
            state (Tensor): 输入的量子态
            which_qubits (list): 测量作用的量子比特编号
            result_desired (str): 期望得到的测量结果
            theta (Tensor): 测量运算的参数

        Returns:
            Tensor: 测量坍塌后的量子态
            Tensor：测量坍塌得到的概率
            str: 测量得到的结果
        """
        n = self.get_qubit_number()
        assert len(which_qubits) == len(result_desired), \
            "the length of qubits wanted to be measured and the result desired should be same"
        op_list = [paddle.to_tensor(np.eye(2, dtype=np.complex128))] * n
        for idx in range(0, len(which_qubits)):
            i = which_qubits[idx]
            ele = result_desired[idx]
            if int(ele) == 0:
                basis0 = paddle.to_tensor(np.array([[1, 0], [0, 0]], dtype=np.complex128))
                basis1 = paddle.to_tensor(np.array([[0, 0], [0, 1]], dtype=np.complex128))
                rho0 = multiply(basis0, cos(theta[idx]))
                rho1 = multiply(basis1, sin(theta[idx]))
                rho = add(rho0, rho1)
                op_list[i] = rho
            elif int(ele) == 1:
                # rho = diag(concat([cos(theta[idx]), sin(theta[idx])]))
                # rho = paddle.to_tensor(rho, zeros((2, 2), dtype="float64"))
                basis0 = paddle.to_tensor(np.array([[1, 0], [0, 0]], dtype=np.complex128))
                basis1 = paddle.to_tensor(np.array([[0, 0], [0, 1]], dtype=np.complex128))
                rho0 = multiply(basis0, sin(theta[idx]))
                rho1 = multiply(basis1, cos(theta[idx]))
                rho = add(rho0, rho1)
                op_list[i] = rho
            else:
                print("cannot recognize the result_desired.")
            # rho = paddle.to_tensor(ones((2, 2), dtype="float64"), zeros((2, 2), dtype="float64"))
        measure_operator = paddle.to_tensor(op_list[0])
        if n > 1:
            for idx in range(1, len(op_list)):
                measure_operator = kron(measure_operator, op_list[idx])
        state_measured = matmul(matmul(measure_operator, state), dagger(measure_operator))
        prob = real(trace(matmul(matmul(dagger(measure_operator), measure_operator), state)))
        state_measured = divide(state_measured, prob)
        return state_measured, prob, result_desired

    def __measure_parameterless(self, state, which_qubits, result_desired):
        r"""进行 01 测量。

        Args:
            state (Tensor): 输入的量子态
            which_qubits (list): 测量作用的量子比特编号
            result_desired (str): 期望得到的测量结果

        Returns:
            Tensor: 测量坍塌后的量子态
            Tensor：测量坍塌得到的概率
            str: 测量得到的结果
        """
        n = self.get_qubit_number()
        assert len(which_qubits) == len(result_desired), \
            "the length of qubits wanted to be measured and the result desired should be same"
        op_list = [np.eye(2, dtype=np.complex128)] * n
        for i, ele in zip(which_qubits, result_desired):
            k = int(ele)
            rho = np.zeros((2, 2), dtype=np.complex128)
            rho[int(k), int(k)] = 1
            op_list[i] = rho
        if n > 1:
            measure_operator = paddle.to_tensor(NKron(*op_list))
        else:
            measure_operator = paddle.to_tensor(op_list[0])
        state_measured = matmul(matmul(measure_operator, state), dagger(measure_operator))
        prob = real(trace(matmul(matmul(dagger(measure_operator), measure_operator), state)))
        state_measured = divide(state_measured, prob)
        return state_measured, prob, result_desired

    def measure(self, status, which_qubits, results_desired, theta=None):
        r"""对 LOCC 态节点进行 01 测量或者含参测量。

        Args:
            status (LoccStatus or list): 输入的量子态，其类型应该为 ``LoccStatus`` 或者由其组成的 ``list``
            which_qubits (tuple or list): 测量作用的量子比特编号，其形式为 ``(party_id, qubit_id)`` 的 ``tuple`` ，或者由其组成的 ``list``
            results_desired (str or list): 期望得到的测量结果，用字符串进行表示，其类型为 ``str`` 或者由 ``str`` 组成的 ``list``，如 ``"0"``、``"1"`` 或者 ``["0", "1"]``
            theta (Tensor): 测量运算的参数，默认是 ``None``，表示 01 测量；若要使用含参测量则需要赋值

        Returns:
            LoccStatus or list: 测量后得到的 LOCC 态节点，其类型为 ``LoccStatus`` 或者由其组成的 ``list``
        """
        if isinstance(which_qubits, tuple):
            which_qubits = [which_qubits]
        if isinstance(results_desired, str):
            results_desired = [results_desired]
        elif not isinstance(results_desired, list):
            raise ValueError("cannot recognize the input of results_desired")

        qubits_list = list()
        for party_id, qubit_id in which_qubits:
            if isinstance(party_id, int):
                qubits_list.append((self.parties_by_number[party_id][qubit_id]))
            elif isinstance(party_id, str):
                qubits_list.append((self.parties_by_name[party_id][qubit_id]))
            else:
                raise ValueError

        if isinstance(status, LoccStatus):
            existing_result = status.measured_result
            prior_prob = status.prob
            new_status = list()
            for result_desired in results_desired:
                if theta is None:
                    result_measured = self.__measure_parameterless(status.state, qubits_list, result_desired)
                else:
                    result_measured = self.__measure_parameterized(status.state, qubits_list, result_desired, theta)
                state, prob, res = result_measured
                new_status.append(LoccStatus(state, prior_prob * prob, existing_result + res))
            if len(new_status) == 1:
                new_status = new_status[0]
        elif isinstance(status, list):
            new_status = list()
            for each_status in status:
                existing_result = each_status.measured_result
                prior_prob = each_status.prob
                for result_desired in results_desired:
                    if theta is None:
                        result_measured = self.__measure_parameterless(each_status.state, qubits_list, result_desired)
                    else:
                        result_measured = self.__measure_parameterized(each_status.state, qubits_list, result_desired, theta)
                    state, prob, res = result_measured
                    new_status.append(LoccStatus(state, prior_prob * prob, existing_result + res))
        else:
            raise ValueError("can't recognize the input status")

        return new_status

    def get_qubit_number(self):
        r"""得到该 LoccNet 的量子比特数量。

        Returns:
            int: 当前量子比特数量
        """
        qubits_number = 0
        for party in self.parties_by_number:
            qubits_number += party.qubit_number
        return qubits_number
