@@ -11,59 +11,205 @@ A GHZ state is a maximally entangled state shared between three (or more) partie
1111The protocol
1212------------
1313
14- 1. Alice creates an EPR pair with Bob and tells Bob to proceed.
15- 2. Bob receives his half of an EPR pair that Alice created with him, creates a *new * EPR pair with Charlie,
16- and applies a CNOT to extend the entanglement into a GHZ state.
17- 3. Charlie receives his half of an EPR pair that Bob created with him.
18- 4. All three measure — their outcomes are correlated.
14+ #. Alice creates an EPR pair in the state :math: `|\Phi ^{+}\rangle _{AB_1 }` with Bob and tells Bob to proceed.
15+ #. Bob receives his half of an EPR pair that Alice created with him, creates a *new * EPR pair :math: `|\Phi ^{+}\rangle _{B_2 C}`
16+ with Charlie, and applies a CNOT with :math: `B_1 ` as control and :math: `B_2 ` as target.
17+ #. Bob measures :math: `B_2 ` in the standard basis and sends the outcome :math: `b_2 ` to Charlie.
18+ #. Charlie receives his half of an EPR pair that Bob created with him and the measurement outcome :math: `b_2 `.
19+ #. Charlie performs an X correction depending on :math: `b_2 `, :math: `A`, :math: `B_1 ` and :math: `C` now share a GHZ state.
20+ #. All three measure their remaining qubits — their outcomes :math: `a`, :math: `b_1 ` and :math: `c` are correlated.
1921
2022The communication flow is::
2123
22- Alice ──EPR──► Bob ──EPR──► Charlie
23- Alice ──msg──► Bob ──msg──► Charlie
24- Alice ◄──msg── Bob ◄──msg── Charlie
24+ Alice ──EPR_1──► Bob
25+ Bob ──EPR_2──► Charlie
26+ Charlie ──continue──► Bob
27+ Bob ──b_2──► Charlie
28+ Charlie ──continue──► Bob
29+ Bob ──continue──► Alice
2530
2631Alice's code
2732------------
2833
2934From ``aliceTest.py ``::
3035
3136 async def run_alice(reader: StreamReader, writer: StreamWriter) -> int:
32- epr_socket = EPRSocket("Bob")
37+ # This is "Alice": the start node of the GHZ chain
38+ this_node_name = "Alice"
39+ remote_node_name = "Bob" # A node with this name *must* exist in "simulaqron_network.json"
3340
34- # sim_conn is our connection to the quantum backend, not to Bob.
35- sim_conn = NetQASMConnection("Alice", epr_sockets=[epr_socket])
41+ epr_socket = EPRSocket(remote_node_name)
42+
43+ # sim_conn is our connection to the quantum backend (SimulaQron), not to Bob.
44+ # Bob is reached via EPRSocket for quantum and reader/writer for classical.
45+ sim_conn = NetQASMConnection(this_node_name, epr_sockets=[epr_socket])
3646
3747 # Create an entangled qubit with Bob
38- epr = epr_socket.create_keep()[0]
48+ A = epr_socket.create_keep()[0]
49+
50+ # We need to flush the EPR pair creation, so the reciever does not timeout on the other side.
51+ sim_conn.flush()
3952
40- # Tell Bob to proceed
4153 writer.write("receive_qubit".encode("utf-8"))
4254 answer = await reader.read(100)
55+
4356 assert answer.decode("utf-8") == "continue"
4457
45- m1 = epr.measure()
58+ a = A.measure()
59+
60+ # flush() executes all queued quantum operations and makes measurement
61+ # results available. Before flush(), a is just a future/promise.
4662 sim_conn.flush()
47- m1_val = int(m1)
63+
64+ # int(a) extracts the measurement outcome — only valid after flush().
65+ a_val = int(a)
4866 sim_conn.close()
49- return m1_val
67+
68+ print(f"{node_name}: My outcome is '{a_val}'")
69+ return 0
5070
5171Bob's code
5272----------
5373
54- Bob is the key node — he has EPR sockets to *both * Alice and Charlie::
74+ Bob is the key node — he has EPR sockets to *both * Alice and Charlie. However, Bob also has 2 roles in the protocol.
75+
76+ Bob acts as a *server * when communicating with Alice::
77+
78+ async def run_bob(reader: StreamReader, writer: StreamWriter) -> int:
79+ # This is "Bob": the middle node of the GHZ chain
80+ this_node_name = "Bob"
81+ start_node_name = "Alice" # A node with this name *must* exist in "simulaqron_network.json"
82+ end_node_name = "Charlie" # A node with this name *must* exist in "simulaqron_network.json"
83+
84+ message = await reader.read(100)
85+ assert message.decode("utf-8") == "receive_qubit"
86+
87+ epr_socket_alice = EPRSocket(start_node_name)
88+ epr_socket_charlie = EPRSocket(end_node_name)
89+
90+ sockets = SocketsConfig(network_config, "default", NodeConfigType.APP)
91+
92+ charlie_client = SimulaQronClassicalClient(sockets)
93+
94+ # sim_conn is our connection to the quantum backend (SimulaQron), not to
95+ # Alice or Charlie. They are reached via EPRSockets for quantum and
96+ # reader/writer for classical.
97+ sim_conn = NetQASMConnection(this_node_name, epr_sockets=[epr_socket_alice, epr_socket_charlie])
98+
99+ # Receive an entangled qubit from Alice
100+ B_1 = epr_socket_alice.recv_keep()[0]
101+
102+ # Create a new entangled pair with Charlie
103+ B_2 = epr_socket_charlie.create_keep()[0]
104+
105+ # We need to flush the EPR pair creation, so the reciever does not timeout on the other side.
106+ sim_conn.flush()
107+
108+ # The next part of the protocol needs to be executed between Bob and Charlie.
109+ # In this interaction, Bob acts as client
110+ await charlie_client.connect_and_run(end_node_name, send_to_charlie, sim_conn, B_1, B_2)
111+
112+ # At this point, we have achieved |GHZ>_{AB_1C}
113+ # Tell Alice to continue
114+ writer.write("continue".encode("utf-8"))
115+
116+ # We can measure the B_1 qubit, part of the GHZ
117+ b_1 = B_1.measure()
118+
119+ # flush() executes all queued quantum operations and makes measurement
120+ # results available. Before flush(), c is just a future/promise.
121+ sim_conn.flush()
122+
123+ b_1_val = int(b_1)
124+ sim_conn.close()
125+ print(f"{this_node_name}: My outcome is '{b_1_val}'")
126+ return 0
127+
128+ However, Bob acts as a *client * when comunicating with Charlie::
55129
56- sim_conn = NetQASMConnection("Bob",
57- epr_sockets=[epr_socket_alice, epr_socket_charlie])
130+ async def send_to_charlie(reader: StreamReader, writer: StreamWriter,
131+ sim_conn: NetQASMConnection, B_1: Qubit, B_2: Qubit) -> None:
132+ # Tell Bob to receive the EPR half
133+ writer.write("receive_qubit".encode("utf-8"))
134+
135+ # Await for the green light from Charlie
136+ message = await reader.read(100)
137+ assert message.decode("utf-8") == "continue"
138+
139+ # Create the GHZ state by entangling the qubit entangled with Alice
140+ B_1.cnot(B_2)
141+
142+ # We now measure the entagled qubit with Charlie
143+ b_2 = B_2.measure()
144+
145+ # flush() executes all queued quantum operations and makes measurement
146+ # results available. Before flush(), b_2 is just a future/promise.
147+ sim_conn.flush()
148+
149+ # int(b_2) extracts the measurement outcome — only valid after flush().
150+ b_2_val = int(b_2)
151+
152+ # We send the measurement b_2 to Charlie, for corrections.
153+ writer.write(f"{b_2_val}".encode("utf-8"))
154+
155+ # We wait for green light from Charlie, again
156+ charlie_msg = await reader.read(100)
157+ assert charlie_msg.decode("utf-8") == "continue"
158+
159+ Charlie's code
160+ --------------
161+
162+ Charlie is the end receiver of the GHZ state. It only needs to communicate with Bob::
163+
164+ async def run_charlie(reader: StreamReader, writer: StreamWriter) -> int:
165+ # This is "Charlie": the end node of the GHZ chain
166+ this_node_name = "Charlie"
167+ remote_node_name = "Bob"
168+ message = await reader.read(100)
169+
170+ assert message.decode("utf-8") == "receive_qubit"
171+ epr_socket = EPRSocket(remote_node_name)
58172
59- # Receive entangled qubit from Alice
60- epr_alice = epr_socket_alice.recv_keep()[0]
173+ # sim_conn is our connection to the quantum backend (SimulaQron), not to Bob.
174+ # Bob is reached via EPRSocket for quantum and reader/writer for classical.
175+ sim_conn = NetQASMConnection(this_node_name, epr_sockets=[epr_socket])
61176
62- # Create new entangled pair with Charlie
63- epr_charlie = epr_socket_charlie.create_keep()[0]
177+ # Receive an entangled qubit
178+ C = epr_socket.recv_keep()[0]
179+
180+ # We need to flush the EPR pair creation, so the reciever does not timeout on the other side.
181+ sim_conn.flush()
182+
183+ # Signal Bob to send us the b_2 measurement
184+ writer.write("continue".encode("utf-8"))
185+
186+ # Receive b_2 measurement from Bob
187+ b_2_bytes: bytes = await reader.read(100)
188+ b_2_val = int(b_2_bytes.decode("utf-8"))
189+
190+ # Perform an X correction depending on Bob's measurement
191+ if b_2_val == 1:
192+ C.X()
193+
194+ sim_conn.flush()
195+
196+ # At this point, we have achieved |GHZ>_{AB_1C}
197+ # Tell Bob to continue
198+ writer.write("continue".encode("utf-8"))
199+
200+ # We can measure the C qubit, part of the GHZ
201+ c = C.measure()
202+
203+ # flush() executes all queued quantum operations and makes measurement
204+ # results available. Before flush(), c is just a future/promise.
205+ sim_conn.flush()
206+
207+ # int(c) extracts the measurement outcome — only valid after flush().
208+ c_val = int(c)
209+ sim_conn.close()
210+ print(f"{this_node_name}: My outcome is '{c_val}'")
211+ return 0
64212
65- # Extend into GHZ: CNOT from Alice's qubit onto Charlie's
66- epr_alice.cnot(epr_charlie)
67213
68214Key concepts
69215------------
0 commit comments