nb_protocol.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import random as rd
  2. import netsquid as ns
  3. import numpy as np
  4. from netsquid.protocols import NodeProtocol
  5. from netsquid.qubits import QFormalism, ketstates
  6. from netsquid.qubits import operators as ops
  7. from netsquid.qubits import qubitapi
  8. from netsquid.qubits.cliffords import local_cliffords
  9. from netsquid.qubits.operators import Operator
  10. from netsquid.qubits.qubitapi import create_qubits, measure, operate
  11. from scipy.optimize import curve_fit
  12. CLIFFORD_OPERATORS = [Operator(op.name, op.arr) for op in local_cliffords]
  13. ns.set_qstate_formalism(QFormalism.DM)
  14. def EXP(x, p, A):
  15. return A * p**(2 * x)
  16. def REGRESSION(bounces, mean_bm):
  17. popt_AB, pcov_AB = curve_fit(EXP, bounces, mean_bm, p0=[0.9, 0.5], maxfev=100000)
  18. # print("poptAB", popt_AB[0])
  19. return [popt_AB[0], popt_AB[1]]
  20. def GET_FIDELITY(info_qubit, gates):
  21. [ref_qubit1, ref_qubit2] = create_qubits(2)
  22. # qubitapi.assign_qstate(ref_qubit1, ketstates.s1)
  23. # qubitapi.assign_qstate(ref_qubit2, ketstates.s1)
  24. operate(ref_qubit2, ops.X)
  25. for gate_instr in gates:
  26. operate(ref_qubit1, gate_instr)
  27. operate(ref_qubit2, gate_instr)
  28. fidelity = qubitapi.exp_value(
  29. info_qubit, ops.Operator("ref", (ns.qubits.reduced_dm(ref_qubit1) - ns.qubits.reduced_dm(ref_qubit2)) / 2))
  30. # fidelity = abs(fidelity)
  31. # if fidelity < 0:
  32. # print("NEGATIVE", fidelity)
  33. # # print("A:", ns.qubits.reduced_dm(ref_qubit1))
  34. # # print("B:", ns.qubits.reduced_dm(ref_qubit2))
  35. # # print("Operator:", (ns.qubits.reduced_dm(ref_qubit1) - ns.qubits.reduced_dm(ref_qubit2)) / 2)
  36. # # Add Gaussian noise to simulate measurement noise, but we need to make sure fidelity is within a valid range
  37. # if fidelity < 0.005:
  38. # # If the fidelity is too small, adding noise will likely to make it negative, so we skip adding noise
  39. # return fidelity
  40. # while True:
  41. # # measure_error = np.random.normal(0, np.sqrt(((1 + fidelity) * (1 - fidelity))) / np.sqrt(1000))
  42. # noisy_fidelity = fidelity + np.random.normal(0, 0.015)
  43. # if noisy_fidelity >= 0 and noisy_fidelity <= 1:
  44. # break
  45. noisy_fidelity = fidelity + np.random.normal(0, 0.015)
  46. return noisy_fidelity
  47. def teleport(epr_qubit, info_qubit):
  48. """Perform teleportation and return two classical bits."""
  49. operate([epr_qubit, info_qubit], ns.CNOT)
  50. operate(epr_qubit, ns.H)
  51. m1, _ = measure(epr_qubit)
  52. m2, _ = measure(info_qubit)
  53. return [m1, m2]
  54. def correction(epr_qubit, measurement_results):
  55. """Perform correction to recover the information qubit."""
  56. if measurement_results[0]:
  57. operate(epr_qubit, ns.Z)
  58. if measurement_results[1]:
  59. operate(epr_qubit, ns.X)
  60. return epr_qubit
  61. class NBProtocolAlice(NodeProtocol):
  62. # bounce: bounce number, type: list
  63. # num_samples: repetition times for each bounce, type: dict bounce: times
  64. def __init__(self, node, bounce=[], num_samples={}, qconn=None):
  65. super().__init__(node)
  66. self._qconn = qconn
  67. if isinstance(bounce, list):
  68. self._bounce_list = bounce
  69. elif isinstance(bounce, int):
  70. self._bounce_list = [bounce]
  71. self._num_samples = num_samples
  72. self._gates = [] # record the clifford operations we used.
  73. self._data_record = {}
  74. self._target_protocol = None
  75. self._cost = 0 # The totoal number of bounces
  76. self.add_signal("ALICE_MEASUREMENT_READY")
  77. self.add_signal("BOB_MEASUREMENT_READY")
  78. self.add_signal("ENTANGLEMENT_READY")
  79. def set_target_protocol(self, bob_protocol):
  80. self._target_protocol = bob_protocol
  81. def request_ERP(self):
  82. """Generate an EPR pair by triggering the quantum source of the quantum channel."""
  83. self._qconn.subcomponents["qsource"].ports["trigger"].tx_input("trigger")
  84. yield self.await_timer(100) # Wait for Alice and Bob to receive and store their qubits
  85. def run(self):
  86. for current_bounce in self._bounce_list:
  87. current_max_sample = self._num_samples[current_bounce]
  88. if current_bounce not in self._data_record:
  89. self._data_record[current_bounce] = []
  90. # print("current bounce:", current_bounce, "bounce_list:", self._bounce_list)
  91. for current_sample in range(current_max_sample):
  92. # print("current sample:", current_sample)
  93. self._gates.clear()
  94. info_qubit = create_qubits(1)[0]
  95. # qubitapi.assign_qstate(info_qubit, ketstates.s1)
  96. for _ in range(current_bounce):
  97. # Start one bounce
  98. # clifford operation to info qubit
  99. instr = rd.choice(CLIFFORD_OPERATORS)
  100. self._gates.append(instr)
  101. operate(info_qubit, instr)
  102. # Request an ERP pair
  103. self._qconn.subcomponents["qsource"].ports["trigger"].tx_input("trigger")
  104. yield self.await_timer(100) # Wait for Alice and Bob to receive and store their qubits
  105. # Extract EPR pair
  106. # print(f"At {ns.sim_time()} {self.node} get one EPR pair and starts teleportation")
  107. epr_qubit = self.node.qmemory.pop(0)[0]
  108. # print('At', ns.sim_time(), "alice's epr pair", epr_qubit.qstate.qrepr)
  109. # Teleport info qubit to Bob using the EPR pair
  110. measurement_results = teleport(epr_qubit, info_qubit)
  111. # msg = MeasurementResult(measurement_results)
  112. self.send_signal("ALICE_MEASUREMENT_READY", result=measurement_results)
  113. self._qconn.subcomponents["qsource"].ports["trigger"].tx_input("trigger")
  114. yield self.await_timer(100) # Wait for Alice and Bob to receive and store their qubits
  115. # epr_qubit = self.node.qmemory.pop(entanglement.source_position)[0]
  116. epr_qubit = self.node.qmemory.pop(0)[0]
  117. self.send_signal("ENTANGLEMENT_READY")
  118. # print('At', ns.sim_time(), "alice's epr pair", epr_qubit.qstate.qrepr)
  119. yield self.await_signal(self._target_protocol, "BOB_MEASUREMENT_READY")
  120. measurement_results, instrfrombob = self._target_protocol.get_signal_result("BOB_MEASUREMENT_READY")
  121. self._gates.append(instrfrombob)
  122. info_qubit = correction(epr_qubit, measurement_results)
  123. self._cost += 1 # Finish one bounce
  124. fidelity = GET_FIDELITY(info_qubit, self._gates)
  125. self._data_record[current_bounce].append(fidelity)
  126. # print(f"Finished bounce {current_bounce}")
  127. # result = self.data_processing()
  128. # print(f"Estimated fidelity: {result}, cost: {self._cost}")
  129. def data_processing(self):
  130. raw_data = self._data_record
  131. bounces = list(raw_data.keys())
  132. mean_values = [np.mean(raw_data[key]) for key in bounces]
  133. # if sorted(mean_values)[0] < 0:
  134. # print("Some mean value is negative,", mean_values)
  135. # print('bounces:', bounces)
  136. # print("mean_values:", mean_values)
  137. p, _ = REGRESSION(bounces, mean_values)
  138. return p, self._cost
  139. def return_data(self):
  140. raw_data = self._data_record
  141. print(raw_data)
  142. # print()
  143. bounces = list(raw_data.keys())
  144. mean_values = [np.mean(raw_data[key]) for key in bounces]
  145. # assert len(mean_values) == len(self._bounce_list)
  146. # print('bounces:', bounces)
  147. # print("mean_values:",mean_values)
  148. return mean_values
  149. class NBProtocolBob(NodeProtocol):
  150. def __init__(self, node):
  151. super().__init__(node)
  152. self.add_signal("ALICE_MEASUREMENT_READY")
  153. self.add_signal("BOB_MEASUREMENT_READY")
  154. self.add_signal("ENTANGLEMENT_READY")
  155. def set_target_protocol(self, alice_protocol):
  156. self._target_protocol = alice_protocol
  157. def run(self):
  158. while True:
  159. yield self.await_signal(self._target_protocol, signal_label="ALICE_MEASUREMENT_READY")
  160. measurement_results_from_target = self._target_protocol.get_signal_result("ALICE_MEASUREMENT_READY")
  161. # entanglement = measurement_result.entanglement
  162. epr_qubit = self.node.qmemory.pop(0)[0]
  163. # measurement_results_from_target = measurement_result.measurement_results
  164. info_qubit = correction(epr_qubit, measurement_results_from_target)
  165. yield self.await_signal(self._target_protocol, "ENTANGLEMENT_READY")
  166. # entanglement = self._target_protocol.get_signal_result("ENTANGLEMENT_READY")
  167. epr_qubit = self.node.qmemory.pop(0)[0]
  168. # teleport the qubit to the next node Bob
  169. instr = rd.choice(CLIFFORD_OPERATORS)
  170. operate(info_qubit, instr)
  171. measurement_results = teleport(epr_qubit, info_qubit)
  172. self.send_signal("BOB_MEASUREMENT_READY", result=[measurement_results, instr])