Extending a GHZ State¶
This example creates a three-party GHZ (Greenberger-Horne-Zeilinger) state
across Alice, Bob, and Charlie. Found in examples/new-sdk/extendGHZ/.
A GHZ state is a maximally entangled state shared between three (or more) parties:
The protocol¶
Alice creates an EPR pair in the state \(|\Phi^{+}\rangle_{AB_1}\) with Bob and tells Bob to proceed.
Bob receives his half of an EPR pair that Alice created with him, creates a new EPR pair \(|\Phi^{+}\rangle_{B_2C}\) with Charlie, and applies a CNOT with \(B_1\) as control and \(B_2\) as target.
Bob measures \(B_2\) in the standard basis and sends the outcome \(b_2\) to Charlie.
Charlie receives his half of an EPR pair that Bob created with him and the measurement outcome \(b_2\).
Charlie performs an X correction depending on \(b_2\), \(A\), \(B_1\) and \(C\) now share a GHZ state.
All three measure their remaining qubits — their outcomes \(a\), \(b_1\) and \(c\) are correlated.
The communication flow is:
Alice ──EPR_1──► Bob
Bob ──EPR_2──► Charlie
Charlie ──continue──► Bob
Bob ──b_2──► Charlie
Charlie ──continue──► Bob
Bob ──continue──► Alice
Alice’s code¶
From aliceTest.py:
async def run_alice(reader: StreamReader, writer: StreamWriter) -> int:
# This is "Alice": the start node of the GHZ chain
this_node_name = "Alice"
remote_node_name = "Bob" # A node with this name *must* exist in "simulaqron_network.json"
epr_socket = EPRSocket(remote_node_name)
# sim_conn is our connection to the quantum backend (SimulaQron), not to Bob.
# Bob is reached via EPRSocket for quantum and reader/writer for classical.
sim_conn = NetQASMConnection(this_node_name, epr_sockets=[epr_socket])
# Create an entangled qubit with Bob
A = epr_socket.create_keep()[0]
# We need to flush the EPR pair creation, so the reciever does not timeout on the other side.
sim_conn.flush()
writer.write("receive_qubit".encode("utf-8"))
answer = await reader.read(100)
assert answer.decode("utf-8") == "continue"
a = A.measure()
# flush() executes all queued quantum operations and makes measurement
# results available. Before flush(), a is just a future/promise.
sim_conn.flush()
# int(a) extracts the measurement outcome — only valid after flush().
a_val = int(a)
sim_conn.close()
print(f"{node_name}: My outcome is '{a_val}'")
return 0
Bob’s code¶
Bob is the key node — he has EPR sockets to both Alice and Charlie. However, Bob also has 2 roles in the protocol.
Bob acts as a server when communicating with Alice:
async def run_bob(reader: StreamReader, writer: StreamWriter) -> int:
# This is "Bob": the middle node of the GHZ chain
this_node_name = "Bob"
start_node_name = "Alice" # A node with this name *must* exist in "simulaqron_network.json"
end_node_name = "Charlie" # A node with this name *must* exist in "simulaqron_network.json"
message = await reader.read(100)
assert message.decode("utf-8") == "receive_qubit"
epr_socket_alice = EPRSocket(start_node_name)
epr_socket_charlie = EPRSocket(end_node_name)
sockets = SocketsConfig(network_config, "default", NodeConfigType.APP)
charlie_client = SimulaQronClassicalClient(sockets)
# sim_conn is our connection to the quantum backend (SimulaQron), not to
# Alice or Charlie. They are reached via EPRSockets for quantum and
# reader/writer for classical.
sim_conn = NetQASMConnection(this_node_name, epr_sockets=[epr_socket_alice, epr_socket_charlie])
# Receive an entangled qubit from Alice
B_1 = epr_socket_alice.recv_keep()[0]
# Create a new entangled pair with Charlie
B_2 = epr_socket_charlie.create_keep()[0]
# We need to flush the EPR pair creation, so the reciever does not timeout on the other side.
sim_conn.flush()
# The next part of the protocol needs to be executed between Bob and Charlie.
# In this interaction, Bob acts as client
await charlie_client.connect_and_run(end_node_name, send_to_charlie, sim_conn, B_1, B_2)
# At this point, we have achieved |GHZ>_{AB_1C}
# Tell Alice to continue
writer.write("continue".encode("utf-8"))
# We can measure the B_1 qubit, part of the GHZ
b_1 = B_1.measure()
# flush() executes all queued quantum operations and makes measurement
# results available. Before flush(), c is just a future/promise.
sim_conn.flush()
b_1_val = int(b_1)
sim_conn.close()
print(f"{this_node_name}: My outcome is '{b_1_val}'")
return 0
However, Bob acts as a client when comunicating with Charlie:
async def send_to_charlie(reader: StreamReader, writer: StreamWriter,
sim_conn: NetQASMConnection, B_1: Qubit, B_2: Qubit) -> None:
# Tell Bob to receive the EPR half
writer.write("receive_qubit".encode("utf-8"))
# Await for the green light from Charlie
message = await reader.read(100)
assert message.decode("utf-8") == "continue"
# Create the GHZ state by entangling the qubit entangled with Alice
B_1.cnot(B_2)
# We now measure the entagled qubit with Charlie
b_2 = B_2.measure()
# flush() executes all queued quantum operations and makes measurement
# results available. Before flush(), b_2 is just a future/promise.
sim_conn.flush()
# int(b_2) extracts the measurement outcome — only valid after flush().
b_2_val = int(b_2)
# We send the measurement b_2 to Charlie, for corrections.
writer.write(f"{b_2_val}".encode("utf-8"))
# We wait for green light from Charlie, again
charlie_msg = await reader.read(100)
assert charlie_msg.decode("utf-8") == "continue"
Charlie’s code¶
Charlie is the end receiver of the GHZ state. It only needs to communicate with Bob:
async def run_charlie(reader: StreamReader, writer: StreamWriter) -> int:
# This is "Charlie": the end node of the GHZ chain
this_node_name = "Charlie"
remote_node_name = "Bob"
message = await reader.read(100)
assert message.decode("utf-8") == "receive_qubit"
epr_socket = EPRSocket(remote_node_name)
# sim_conn is our connection to the quantum backend (SimulaQron), not to Bob.
# Bob is reached via EPRSocket for quantum and reader/writer for classical.
sim_conn = NetQASMConnection(this_node_name, epr_sockets=[epr_socket])
# Receive an entangled qubit
C = epr_socket.recv_keep()[0]
# We need to flush the EPR pair creation, so the reciever does not timeout on the other side.
sim_conn.flush()
# Signal Bob to send us the b_2 measurement
writer.write("continue".encode("utf-8"))
# Receive b_2 measurement from Bob
b_2_bytes: bytes = await reader.read(100)
b_2_val = int(b_2_bytes.decode("utf-8"))
# Perform an X correction depending on Bob's measurement
if b_2_val == 1:
C.X()
sim_conn.flush()
# At this point, we have achieved |GHZ>_{AB_1C}
# Tell Bob to continue
writer.write("continue".encode("utf-8"))
# We can measure the C qubit, part of the GHZ
c = C.measure()
# flush() executes all queued quantum operations and makes measurement
# results available. Before flush(), c is just a future/promise.
sim_conn.flush()
# int(c) extracts the measurement outcome — only valid after flush().
c_val = int(c)
sim_conn.close()
print(f"{this_node_name}: My outcome is '{c_val}'")
return 0
Key concepts¶
Multiple EPR sockets: A single
NetQASMConnectioncan hold EPR sockets to multiple remote nodes.Three-party coordination: Bob acts as both a server (for Alice) and a client (to Charlie), using both
SimulaQronClassicalServerandSimulaQronClassicalClient.CNOT extends entanglement: Applying CNOT between two entangled qubits from different pairs creates a GHZ state.
Running¶
cd examples/new-sdk/extendGHZ
bash run.sh