Skip to content

Commit 54489a2

Browse files
committed
Fix code and documentation of the extended GHZ example
1 parent c996ece commit 54489a2

5 files changed

Lines changed: 248 additions & 61 deletions

File tree

docs/new-sdk/ExtendGHZ.rst

Lines changed: 171 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,59 +11,205 @@ A GHZ state is a maximally entangled state shared between three (or more) partie
1111
The 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_2C}`
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

2022
The 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

2631
Alice's code
2732
------------
2833

2934
From ``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

5171
Bob'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

68214
Key concepts
69215
------------

examples/new-sdk/extendGHZ/aliceTest.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,28 @@ async def run_alice(reader: StreamReader, writer: StreamWriter) -> int:
3333
sim_conn = NetQASMConnection(this_node_name, epr_sockets=[epr_socket])
3434

3535
# Create an entangled qubit with Bob
36-
epr = epr_socket.create_keep()[0]
36+
A = epr_socket.create_keep()[0]
37+
38+
# We need to flush the EPR pair creation, so the reciever does not timeout on the other side.
39+
sim_conn.flush()
3740

3841
writer.write("receive_qubit".encode("utf-8"))
3942
answer = await reader.read(100)
4043

4144
assert answer.decode("utf-8") == "continue"
4245

43-
m1 = epr.measure()
46+
a = A.measure()
4447

4548
# flush() executes all queued quantum operations and makes measurement
46-
# results available. Before flush(), m1 is just a future/promise.
49+
# results available. Before flush(), a is just a future/promise.
4750
sim_conn.flush()
4851

49-
# int(m) extracts the measurement outcome — only valid after flush().
50-
m1_val = int(m1)
52+
# int(a) extracts the measurement outcome — only valid after flush().
53+
a_val = int(a)
5154
sim_conn.close()
52-
return m1_val
55+
56+
print(f"{node_name}: My outcome is '{a_val}'")
57+
return 0
5358

5459

5560
if __name__ == "__main__":
@@ -88,6 +93,3 @@ async def run_alice(reader: StreamReader, writer: StreamWriter) -> int:
8893
client = SimulaQronClassicalClient(classical_sockets)
8994

9095
result = client.run_client(other_node_name, run_alice)
91-
#result = run_alice(1, 0)
92-
93-
print(f"{node_name}: My outcome is '{result}'")

examples/new-sdk/extendGHZ/bobTest.py

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,38 @@
1818
# setting the simulator for NetQASM
1919
from netqasm.logging.glob import set_log_level # noqa: E402
2020
from netqasm.sdk.external import NetQASMConnection # noqa: E402
21-
from netqasm.sdk import EPRSocket # noqa: E402
21+
from netqasm.sdk import EPRSocket, Qubit # noqa: E402
2222

2323

24-
async def send_to_charlie(reader: StreamReader, writer: StreamWriter):
24+
async def send_to_charlie(reader: StreamReader, writer: StreamWriter,
25+
sim_conn: NetQASMConnection, B_1: Qubit, B_2: Qubit) -> None:
26+
# Tell Bob to receive the EPR half
2527
writer.write("receive_qubit".encode("utf-8"))
28+
29+
# Await for the green light from Charlie
2630
message = await reader.read(100)
2731
assert message.decode("utf-8") == "continue"
2832

33+
# Create the GHZ state by entangling the qubit entangled with Alice
34+
B_1.cnot(B_2)
35+
36+
# We now measure the entagled qubit with Charlie
37+
b_2 = B_2.measure()
38+
39+
# flush() executes all queued quantum operations and makes measurement
40+
# results available. Before flush(), b_2 is just a future/promise.
41+
sim_conn.flush()
42+
43+
# int(b_2) extracts the measurement outcome — only valid after flush().
44+
b_2_val = int(b_2)
45+
46+
# We send the measurement b_2 to Charlie, for corrections.
47+
writer.write(f"{b_2_val}".encode("utf-8"))
48+
49+
# We wait for green light from Charlie, again
50+
charlie_msg = await reader.read(100)
51+
assert charlie_msg.decode("utf-8") == "continue"
52+
2953

3054
async def run_bob(reader: StreamReader, writer: StreamWriter) -> int:
3155
# This is "Bob": the middle node of the GHZ chain
@@ -49,29 +73,32 @@ async def run_bob(reader: StreamReader, writer: StreamWriter) -> int:
4973
sim_conn = NetQASMConnection(this_node_name, epr_sockets=[epr_socket_alice, epr_socket_charlie])
5074

5175
# Receive an entangled qubit from Alice
52-
epr_alice = epr_socket_alice.recv_keep()[0]
76+
B_1 = epr_socket_alice.recv_keep()[0]
5377

5478
# Create a new entangled pair with Charlie
55-
epr_charlie = epr_socket_charlie.create_keep()[0]
79+
B_2 = epr_socket_charlie.create_keep()[0]
5680

57-
await charlie_client.connect_and_run(end_node_name, send_to_charlie)
81+
# We need to flush the EPR pair creation, so the reciever does not timeout on the other side.
82+
sim_conn.flush()
5883

59-
# Create the GHZ state by entangling the fresh qubit
60-
epr_alice.cnot(epr_charlie)
84+
# The next part of the protocol needs to be executed between Bob and Charlie.
85+
# In this interaction, Bob acts as client
86+
await charlie_client.connect_and_run(end_node_name, send_to_charlie, sim_conn, B_1, B_2)
6187

88+
# At this point, we have achieved |GHZ>_{AB_1C}
89+
# Tell Alice to continue
6290
writer.write("continue".encode("utf-8"))
6391

64-
# And simply measure it
65-
m1 = epr_charlie.measure()
92+
# We can measure the B_1 qubit, part of the GHZ
93+
b_1 = B_1.measure()
6694

6795
# flush() executes all queued quantum operations and makes measurement
68-
# results available. Before flush(), m1 is just a future/promise.
96+
# results available. Before flush(), c is just a future/promise.
6997
sim_conn.flush()
7098

71-
# int(m) extracts the measurement outcome — only valid after flush().
72-
m1_val = int(m1)
99+
b_1_val = int(b_1)
73100
sim_conn.close()
74-
print(f"{this_node_name}: My outcome is '{m1_val}'")
101+
print(f"{this_node_name}: My outcome is '{b_1_val}'")
75102
return 0
76103

77104

0 commit comments

Comments
 (0)