<jupyter_start><jupyter_text>Entanglement Distillation -- Protocol Design with LOCCNet Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.   OverviewQuantum entanglement plays a vital role in quantum communication, quantum computing, and many other quantum technologies. Therefore, detecting, transmitting, and distributing quantum entanglement reliably are essential tasks if we want to build real applications in those fields. However, errors are inevitable in the real world. They could come from imperfect equipment when we create entanglement (preparation errors), or the quantum channel used to transmit entanglement is noisy, and we gradually lose the degree of entanglement as the transmission distance increases. The aim of entanglement distillation is to compensate for those losses and restore a **maximally entangled state** at the cost of many noisy entangled states. In this sense, one could also refer entanglement distillation as a purification/error-correction protocol. This process often involves two remote parties Alice and Bob such that only Local Operations and Classical Communication (LOCC) are allowed [1]. Many distillation protocols have been proposed since 1996, including the famous BBPSSW [2] and the DEJMPS protocol [3]. However, the BBPSSW and DEJMPS distillation protocols are designed for specific types of noisy states (i.e., isotropic states and Bell-diagonal states, respectively). It is nearly impossible to find a single distillation protocol that could purify all kinds of noises. Due to the complicated mathematical structure of LOCC, designing a new distillation protocol is time-consuming with paper and pencil only. LOCCNet, a machine learning framework for LOCC protocols, is designed to reduce the effort as much as possible. With LOCCNet, it will be easier to design new distillation protocols as long as we can characterize the mathematical form of noise introduced to the entanglement resources.   PreliminaryIn the context of entanglement distillation, we usually use the **state fidelity** $F$ between the distilled state $\rho_{out}$ and the maximally entangled Bell state $|\Phi^+\rangle$ to quantify the performance of a distillation protocol, where$$F(\rho_{out}, \Phi^+) \equiv \langle \Phi^+|\rho_{out}|\Phi^+\rangle.\tag{1}$$**Note:** In general, LOCC distillation protocols are probabilistic, and hence we are also interested in the success rate $p_{succ}$.  Protocol design logicIn this tutorial, we will go through an example that distills four identical **isotropic states** (or Werner state) $\rho_{in}= \rho_{\text{iso}}$ into a single final state $\rho_{out}$ with a higher state fidelity $F$ (closer to the Bell state $|\Phi^+\rangle$). We call this type of protocol as the $4\rightarrow 1$ LOCC distillation class, while the original BBPSSW and DEJMPS protocols belong to the $2\rightarrow 1$ LOCC distillation class. The isotropic state is a parametrized family of mixed states consist of $|\Phi^+\rangle$ and the completely mixed state (white noise) $I/4$,$$\rho_{\text{iso}}(p) = p\lvert\Phi^+\rangle \langle\Phi^+\rvert + (1-p)\frac{I}{4}, \quad p \in [0,1]\tag{2}$$In our example, we set $p=0.7$ and the input state becomes:$$\rho_{in} = \rho_{\text{iso}}(0.7)= 0.7\lvert\Phi^+\rangle \langle\Phi^+\rvert + 0.075 I.\tag{3}$$To fulfill the task of distillation through LOCC, we introduce two remote parties, $A$ (Alice) and $B$ (Bob). At the very beginning, they share four copies of entangled qubit pairs $\rho_{A_0B_0}, \rho_{A_1B_1}, \rho_{A_2B_2}$ and $\rho_{A_3B_3}$. Each copy is initialized as $\rho_{in} = \rho_{\text{iso}}(p =0.7)$.  Alice holds four qubits $A_0, A_1, A_2, A_3$ in one place and Bob holds $B_0, B_1, B_2, B_3$ in another place. With these initial setups, Alice and Bob could choose the communication rounds $r$, which indicates how many times they would measure their subsystems and transmit classical data with each other. For simplicity, it is natural to start with $r=1$. Then, Alice and Bob can use LOCCNet to find out the correct local operations (encoded as quantum neural networks, QNNs) before communication by following the steps below:1. Design a general QNN architecture $U(\boldsymbol\theta)$ as shown in Figure 1, where each $R(\theta)$ represents a general rotation operation on the Bloch sphere. They are referred as the `u3(theta, phi, lam, which_qubit)` gate in Paddle Quantum.2. After implementing the QNN, Alice and Bob measure all of the qubits except $A_0$ and $B_0$ in the computational basis. Then the measurement results $M = \{m_{A_1}m_{B_1}, m_{A_2}m_{B_2}, m_{A_3}m_{B_3}\}$ will be exchanged through a classical channel.3. If the measurement results of all qubit pairs are the same (each pair $m_{A_1}m_{B_1}, m_{A_2}m_{B_2}, m_{A_3}m_{B_3}$ is either 00 or 11), the distillation is successful, and the pair $A_0B_0$ is reserved as the memory qubit pair to store the purified entanglement; If the measurement results are different, the protocol fails. Discard the quantum pair $A_0B_0$. At this point, we obtain the purified quantum pair $A_0B_0$ probabilistically, and its state is denoted as $\rho_{AB}'$.4. Define the accumulated loss function $L = \sum_{m_{A_j}m_{B_j}\in \{00,11\}} \big(1- \text{Tr}(\rho_{tar}\rho_{AB}')\big)$ over the successful distillations, where $\text{Tr}(\rho_{tar}\rho_{AB}')$ is the state overlap between the current state $\rho_{AB}'$ and the target state $\rho_{tar}=\lvert\Phi^+\rangle \langle \Phi^+\rvert$.5. Use gradient-based optimization methods to update parameters in QNN and minimize the loss function.6. Repeat steps 1-5 until the loss function converges.7. Output the purified state $\rho_{out} = \rho'_{A_0B_0}$.**Note:** The QNN structure used is merely an illustration. Readers are welcomed to try their own designs.Figure 1: Schematic diagram of a distillation protocol designed with LOCCNet.   Simulation with Paddle QuantumFirst, we import all the dependencies:<jupyter_code>import numpy as np
from paddle import matmul, trace
import paddle
from paddle_quantum.locc import LoccAnsatz, LoccNet, LoccParty, LoccStatus
from paddle_quantum.state import isotropic_state, bell_state
from paddle_quantum.utils import logarithmic_negativity<jupyter_output><empty_output><jupyter_text>Secondly, we define the QNN and loss function:<jupyter_code>class LOCC(LoccNet):
    def __init__(self):
        super(LOCC, self).__init__()
        
        # Add the first party Alice 
        # The first parameter 4 stands for how many qubits A holds
        # The second parameter records the name of this qubit holder
        self.add_new_party(4, party_name="Alice")
        
        # Add the second party Bob
        # The first parameter 4 stands for how many qubits B holds
        # The second parameter records the name of this qubit holder
        self.add_new_party(4, party_name="Bob")
        
        # Alice‘s params
        self.theta_1 = self.create_parameter(shape=[8, 3], default_initializer=paddle.nn.initializer.Uniform(low=0.0, high=2 * np.pi), dtype="float64")
        # Bob's params
        self.theta_2 = self.create_parameter(shape=[8, 3], default_initializer=paddle.nn.initializer.Uniform(low=0.0, high=2 * np.pi), dtype="float64")
        
        # Generate input isotropic states
        _state = paddle.to_tensor(isotropic_state(2, 0.7))
        
        # Distribute the pre-shared entangled states
        self.set_init_state(_state, [["Alice", 0], ["Bob", 0]])
        self.set_init_state(_state, [["Alice", 1], ["Bob", 1]])
        self.set_init_state(_state, [["Alice", 2], ["Bob", 2]])
        self.set_init_state(_state, [["Alice", 3], ["Bob", 3]])

    def QNN(self, cir, theta):
        '''
        Define the QNN illustrated in Figure 1
        '''
        cir.u3(*theta[0], 0)
        cir.u3(*theta[1], 1)
        cir.u3(*theta[2], 2)
        cir.u3(*theta[3], 3)
        cir.cnot([0, 1])
        cir.cnot([1, 2])
        cir.cnot([2, 3])
        cir.cnot([3, 0])
        cir.u3(*theta[4], 0)
        cir.u3(*theta[5], 1)
        cir.u3(*theta[6], 2)
        cir.u3(*theta[7], 3)

    def New_Protocol(self):
        status = self.init_status

        # Create Alice's circuit
        cir1 = self.create_ansatz("Alice")
        self.QNN(cir1, self.theta_1)

        # Create Bob's circuit
        cir2 = self.create_ansatz("Bob")
        self.QNN(cir2, self.theta_2)

        # Execute Alice's circuit
        status = cir1.run(status)
        
        # Execute Bob's circuit
        status = cir2.run(status)
        
        # Measure qubits，["000000", "000011","001100","110000","001111","111100","110011","111111"] represent successful cases
        status1 = self.measure(status, [["Alice", 1], ["Bob", 1],["Alice", 2], ["Bob", 2], ["Alice", 3], ["Bob", 3]], 
                               ["000000", "000011", "001100", "110000", "001111", "111100", "110011", "111111"])
        
        # Trace out all the qubits but A_0 and B_0
        status_fin = self.partial_state(status1, [["Alice", 0], ["Bob", 0]])
        target_state = paddle.to_tensor(bell_state(2))
        
        # Calculate loss function
        loss = 0
        for idx in range(0, len(status_fin)):
            loss += 1 - paddle.real(trace(matmul(target_state, status_fin[idx].state)))[0]
        return loss, status_fin<jupyter_output><empty_output><jupyter_text>Lastly, minimize the loss function with gradient-based optimization methods.<jupyter_code>ITR = 100    # Number of iterations
LR = 0.2     # Learning rate
paddle.seed(999)

net = LOCC()

# Choose Adam optimizer
opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())
loss_list = []               

# Optimization loop
for itr in range(ITR):
    loss, status_fin = net.New_Protocol()
    # Backpropagation
    loss.backward()          
    opt.minimize(loss)
    # Clean gradients
    opt.clear_grad()    
    loss_list.append(loss.numpy()[0])

    # Print training result
    if itr % 10 == 0:
        print("itr " + str(itr) + ":", loss.numpy()[0])
# Calculate input state fidelity
fidelity_in = (3 * 0.7 + 1) / 4
# Calculate output state fidelity
fidelity = (len(status_fin) - loss) / len(status_fin)
# Calculate successful rate
suc_rate = sum([s.prob for s in status_fin])

print("The fidelity of the input quantum state is：%.5f" % fidelity_in)
print("The fidelity of the purified quantum state is： %.5f" % fidelity.numpy()[0])
print("The probability of successful purification is：%.5f" % suc_rate.numpy()[0])
rho_out = status_fin[0].state.numpy()
print("========================================================")
print(f"The output state is:\n {np.around(rho_out, 4)}")
print(f"The initial logarithmic negativity is: {logarithmic_negativity(isotropic_state(2, 0.7))}")
print(f"The final logarithmic negativity is: {logarithmic_negativity(rho_out)}")<jupyter_output>itr 0: 5.7033257831039315
itr 10: 1.552207114278755
itr 20: 0.8980303153930879
itr 30: 0.7061224566044214
itr 40: 0.5713956826398692
itr 50: 0.5278055638406681
itr 60: 0.5121543497136843
itr 70: 0.5075981613869213
itr 80: 0.5052044047709767
itr 90: 0.5049323155027947
The fidelity of the input quantum state is：0.77500
The fidelity of the purified quantum state is： 0.93690
The probability of successful purification is：0.38654
========================================================
The output state is:
 [[ 4.790e-01+0.j     -1.000e-04-0.j      1.000e-04-0.j
   4.579e-01+0.0005j]
 [-1.000e-04+0.j      2.100e-02+0.j      0.000e+00+0.j
  -1.000e-04+0.j    ]
 [ 1.000e-04+0.j      0.000e+00-0.j      2.100e-02+0.j
   1.000e-04+0.0001j]
 [ 4.579e-01-0.0005j -1.000e-04-0.j      1.000e-04-0.0001j
   4.790e-01+0.j    ]]
The initial logarithmic negativity is: 0.6322682154995127
The final logarithmic negativity is: 0.9059639124992761
{'fraction_non_alphanumeric': 0.11962696057651548, 'fraction_numerical': 0.05731242051716829, 'mean_word_length': 3.9007062733693396, 'pattern_counts': {'":': 1, '<': 12, '<?xml version=': 0, '>': 12, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '3', 'dir': 'jupyter-structured-clean-dedup', 'id': '457563', 'license': 'Apache-2.0', 'n_tokens_mistral': 4072, 'n_tokens_neox': 3499, 'n_words': 1293, 'path': 'tutorial/locc/EntanglementDistillation_LOCCNET_EN.ipynb', 'repo_name': 'imppresser/Quantum-1', 'size': '11795'}