The GHZ game involves three players (Alice, Barbara, and Candida), and one referee (Rosa). Rosa randomly chooses one of four possible binary strings:
From her choice, she sends the first bit \(a\) to Alice, the second bit \(b\) to Barbara, and the third bit \(c\) to Candida. Each of the players uses a deterministic function to produce three bits \(x\), \(y\), and \(z\):
The players then send their outputs back to Rosa. The game is won if the following condition is met:
Here \(\lor\) is the OR logic operator and \(\oplus\) is the XOR logic operator.
Note that
Thus, if Rosa picks \(000\), then you need
This is achieved if either one variable is 0, or the three variables are 0. Thus, there are four scenarios:
Otherwise, you need
This is achieved if either two variables are 0, or none of the variables are 0. Thus, again, there are four scenarios:
But, it is more likely for Rosa to choose this outcome.
Each player has four possible deterministic ways to produce their corresponding bits. There are two constant functions: either always return 0, or always return 1. Then you can either return whatever you are given, or the negation of what you are given.
Here is some code to simulate this classical game. First, you do some imports:
from itertools import product
import numpy as np
np.random.seed(0)
Then you introduce some variables:
booleans = np.array([False, True])
rosa_option = np.arange(4)
alice_option = np.array([0, 0, 1, 1])
barbara_option = np.array([0, 1, 0, 1])
candida_option = np.array([0, 1, 1, 0])
N = 64000
r = np.random.choice(rosa_option, size=N)
a = alice_option[r]
b = barbara_option[r]
c = candida_option[r]
The you define some functions for playing the game:
def deterministic(i, strategy, option):
return option[strategy[i]]
def fraction_of_wins(a, b, c,
f, g, h,
option_a, option_b, option_c,
n):
X = deterministic(a, f, option_a)
Y = deterministic(b, g, option_b)
Z = deterministic(c, h, option_c)
A = booleans[a]
B = booleans[b]
C = booleans[c]
wins = np.equal(
np.bitwise_or(np.bitwise_or(A, B), C),
np.bitwise_xor(np.bitwise_xor(X, Y), Z),
)
return np.sum(wins) / n
Here is some code for printing the probability of winning when using different combination of strategies:
strategy = np.array([p for p in product((0, 1), repeat=2)])
M = 4
p = np.zeros((M, M, M), dtype=float)
for i in range(M):
for j in range(M):
for k in range(M):
p[i, j, k] = fraction_of_wins(a, b, c,
strategy[i], strategy[j], strategy[k],
alice_option, barbara_option, candida_option,
N)
print(p)
As before, each deterministic strategy correspond to one of the four possible binary 2-tuples. The output is a \(4 \times 4 \times 4\) array:
[[[ 0.25003125 0.75132813 0.24867187 0.74996875]
[ 0.74964062 0.7490625 0.2509375 0.25035937]
[ 0.25035937 0.2509375 0.7490625 0.74964062]
[ 0.74996875 0.24867187 0.75132813 0.25003125]]
[[ 0.25003125 0.75132813 0.24867187 0.74996875]
[ 0.74964062 0.7490625 0.2509375 0.25035937]
[ 0.25035937 0.2509375 0.7490625 0.74964062]
[ 0.74996875 0.24867187 0.75132813 0.25003125]]
[[ 0.25003125 0.75132813 0.24867187 0.74996875]
[ 0.74964062 0.7490625 0.2509375 0.25035937]
[ 0.25035937 0.2509375 0.7490625 0.74964062]
[ 0.74996875 0.24867187 0.75132813 0.25003125]]
[[ 0.25003125 0.75132813 0.24867187 0.74996875]
[ 0.74964062 0.7490625 0.2509375 0.25035937]
[ 0.25035937 0.2509375 0.7490625 0.74964062]
[ 0.74996875 0.24867187 0.75132813 0.25003125]]]
That is, there are two kinds of strategies: one allows you to win 75% of the time. The other allows you to win only 25% of the time.