-
Notifications
You must be signed in to change notification settings - Fork 81
/
Copy pathstabilizer_simulation.py
151 lines (124 loc) · 5.42 KB
/
stabilizer_simulation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import numpy as np
import stim
import tensorcircuit as tc
np.random.seed(0)
tc.set_dtype("complex128")
clifford_one_qubit_gates = ["H", "X", "Y", "Z", "S"]
clifford_two_qubit_gates = ["CNOT"]
clifford_gates = clifford_one_qubit_gates + clifford_two_qubit_gates
def genpair(num_qubits, count):
choice = list(range(num_qubits))
for _ in range(count):
np.random.shuffle(choice)
x, y = choice[:2]
yield (x, y)
def random_clifford_circuit_with_mid_measurement(num_qubits, depth):
c = tc.Circuit(num_qubits)
operation_list = []
for _ in range(depth):
for j, k in genpair(num_qubits, 2):
c.cnot(j, k)
operation_list.append(("CNOT", (j, k)))
for j in range(num_qubits):
gate_name = np.random.choice(clifford_one_qubit_gates)
getattr(c, gate_name)(j)
operation_list.append((gate_name, (j,)))
if np.random.uniform() < 0.2:
measured_qubit = np.random.randint(0, num_qubits - 1)
sample, p = c.measure_reference(measured_qubit, with_prob=True)
# Check if there is a non-zero probability to measure "0" for post-selection
if (sample == "0" and not np.isclose(p, 0.0)) or (
sample == "1" and not np.isclose(p, 1.0)
):
c.mid_measurement(measured_qubit, keep=0)
operation_list.append(("M", (measured_qubit,)))
return c, operation_list
def convert_operation_list_to_stim_circuit(operation_list):
stim_circuit = stim.Circuit()
for instruction in operation_list:
gate_name = instruction[0]
qubits = instruction[1]
stim_circuit.append(gate_name, qubits)
return stim_circuit
# ref: https://quantumcomputing.stackexchange.com/questions/16718/measuring-entanglement-entropy-using-a-stabilizer-circuit-simulator
def get_binary_matrix(z_stabilizers):
N = len(z_stabilizers)
binary_matrix = np.zeros((N, 2 * N))
for row_idx, row in enumerate(z_stabilizers):
for col_idx, col in enumerate(row):
if col == 3: # Pauli Z
binary_matrix[row_idx, N + col_idx] = 1
if col == 2: # Pauli Y
binary_matrix[row_idx, N + col_idx] = 1
binary_matrix[row_idx, col_idx] = 1
if col == 1: # Pauli X
binary_matrix[row_idx, col_idx] = 1
return binary_matrix
def get_cut_binary_matrix(binary_matrix, cut):
N = len(binary_matrix)
new_indices = [i for i in range(N) if i in set(cut)] + [
i + N for i in range(N) if i in set(cut)
]
return binary_matrix[:, new_indices]
# ref: https://gist.github.com/StuartGordonReid/eb59113cb29e529b8105?permalink_comment_id=3268301#gistcomment-3268301
def gf2_rank(matrix):
n = len(matrix[0])
rank = 0
for col in range(n):
j = 0
rows = []
while j < len(matrix):
if matrix[j][col] == 1:
rows += [j]
j += 1
if len(rows) >= 1:
for c in range(1, len(rows)):
for k in range(n):
matrix[rows[c]][k] = (matrix[rows[c]][k] + matrix[rows[0]][k]) % 2
matrix.pop(rows[0])
rank += 1
for row in matrix:
if sum(row) > 0:
rank += 1
return rank
# ref: https://quantumcomputing.stackexchange.com/questions/27795/exact-probabilities-of-outcomes-for-clifford-circuits-with-mid-circuit-measureme
def simulate_stim_circuit_with_mid_measurement(stim_circuit):
simulator = stim.TableauSimulator()
for instruction in stim_circuit.flattened():
if instruction.name == "M":
for t in instruction.targets_copy():
expectaction_value = simulator.peek_z(t.value) # 1, 0, -1
# there is a non-zero probability to measure "0" if expectaction_value is not -1
if expectaction_value != -1:
simulator.postselect_z(t.value, desired_value=0)
else:
simulator.do(instruction)
return simulator.current_inverse_tableau() ** -1
if __name__ == "__main__":
# Number of qubits
num_qubits = 12
# Depth of the circuit
depth = 24
# index list that is traced out to calculate the entanglement entropy
cut = [i for i in range(num_qubits // 3)]
tc_circuit, op_list = random_clifford_circuit_with_mid_measurement(
num_qubits, depth
)
print(tc_circuit.draw(output="text"))
stim_circuit = convert_operation_list_to_stim_circuit(op_list)
# Entanglement entropy calculation using stabilizer formalism
stabilizer_tableau = simulate_stim_circuit_with_mid_measurement(stim_circuit)
zs = [stabilizer_tableau.z_output(k) for k in range(len(stabilizer_tableau))]
binary_matrix = get_binary_matrix(zs)
cur_matrix = get_cut_binary_matrix(binary_matrix, cut)
stim_entropy = (gf2_rank(cur_matrix.tolist()) - len(cut)) * np.log(2)
print("Stim Entanglement Entropy:", stim_entropy)
# Entanglement entropy calculation using TensorCircuit
state_vector = tc_circuit.wavefunction()
assert np.linalg.norm(state_vector) > 0
# Normalize the state vector because mid-measurement operation is not unitary
state_vector /= np.linalg.norm(state_vector)
tc_entropy = tc.quantum.entanglement_entropy(state_vector, cut)
print("TensorCircuit Entanglement Entropy:", tc_entropy)
# Check if the entanglement entropies are close
np.testing.assert_allclose(stim_entropy, tc_entropy, atol=1e-8)