|
| 1 | +import numpy as np |
| 2 | +from tqdm import tqdm |
| 3 | +import tensorcircuit as tc |
| 4 | + |
| 5 | +xx = tc.gates._xx_matrix |
| 6 | +yy = tc.gates._yy_matrix |
| 7 | +zz = tc.gates._zz_matrix |
| 8 | + |
| 9 | +nqubits, nlayers = 8, 6 |
| 10 | +# number of qubits and number of even-odd brick layers in the circuit |
| 11 | +K = tc.set_backend("tensorflow") |
| 12 | + |
| 13 | + |
| 14 | +def ansatz(param, gate_param): |
| 15 | + # gate_param here is a 2D-vector for theta and phi in the XXZ gate |
| 16 | + c = tc.Circuit(nqubits) |
| 17 | + # customize the circuit structure as you like |
| 18 | + for j in range(nlayers): |
| 19 | + for i in range(nqubits): |
| 20 | + c.ry(i, theta=param[j, 0, i, 0]) |
| 21 | + c.rz(i, theta=param[j, 0, i, 1]) |
| 22 | + c.ry(i, theta=param[j, 0, i, 2]) |
| 23 | + for i in range(0, nqubits - 1, 2): # even brick |
| 24 | + c.exp( |
| 25 | + i, |
| 26 | + i + 1, |
| 27 | + theta=1.0, |
| 28 | + unitary=gate_param[0] * (xx + yy) + gate_param[1] * zz, |
| 29 | + ) |
| 30 | + for i in range(nqubits): |
| 31 | + c.ry(i, theta=param[j, 1, i, 0]) |
| 32 | + c.rz(i, theta=param[j, 1, i, 1]) |
| 33 | + c.ry(i, theta=param[j, 1, i, 2]) |
| 34 | + for i in range(1, nqubits - 1, 2): # odd brick with OBC |
| 35 | + c.exp( |
| 36 | + i, |
| 37 | + i + 1, |
| 38 | + theta=1.0, |
| 39 | + unitary=gate_param[0] * (xx + yy) + gate_param[1] * zz, |
| 40 | + ) |
| 41 | + return c |
| 42 | + |
| 43 | + |
| 44 | +def measure_z(param, gate_param, i): |
| 45 | + c = ansatz(param, gate_param) |
| 46 | + # K.real(c.expectation_ps(x=[i, i+1])) for measure on <X_iX_i+1> |
| 47 | + return K.real(c.expectation_ps(z=[i])) |
| 48 | + |
| 49 | + |
| 50 | +gradf = K.jit(K.vvag(measure_z, argnums=0, vectorized_argnums=0), static_argnums=2) |
| 51 | +# vectorized parallel the computation on circuit gradients for different circuit parameters |
| 52 | + |
| 53 | +if __name__ == "__main__": |
| 54 | + batch = 100 |
| 55 | + # tune batch as large as possible if the memory allows |
| 56 | + reps = 20 |
| 57 | + measure_on = nqubits // 2 |
| 58 | + # which qubit the sigma_z observable is on |
| 59 | + |
| 60 | + # we will average `reps*batch` different unitaries |
| 61 | + rlist = [] |
| 62 | + gate_param = np.array([0, np.pi / 4]) |
| 63 | + gate_param = tc.array_to_tensor(gate_param) |
| 64 | + for _ in tqdm(range(reps)): |
| 65 | + param = np.random.uniform(0, 2 * np.pi, size=[batch, nlayers, 2, nqubits, 3]) |
| 66 | + param = tc.array_to_tensor(param, dtype="float32") |
| 67 | + _, gs = gradf(param, gate_param, measure_on) |
| 68 | + # gs.shape = [batch, nlayers, 2, nqubits, 3] |
| 69 | + gs = K.abs(gs) ** 2 |
| 70 | + rlist.append(gs) |
| 71 | + gs2 = K.stack(rlist) |
| 72 | + # gs2.shape = [reps, batch, nlayers, 2, nqubits, 3] |
| 73 | + gs2 = K.reshape(gs2, [-1, nlayers, 2, nqubits, 3]) |
| 74 | + gs2_mean = K.numpy( |
| 75 | + K.mean(gs2, axis=0) |
| 76 | + ) # numpy array for the averaged abs square for gradient of each element |
| 77 | + print(gs2_mean) |
0 commit comments