Skip to content

Commit 9a32bab

Browse files
committed
add SHVQE script
1 parent 44e94bd commit 9a32bab

File tree

1 file changed

+237
-0
lines changed

1 file changed

+237
-0
lines changed

examples/shvqe.py

+237
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
"""
2+
Schrodinger-Heisenberg quantum variational eigensolver (SHVQE) with DQAS-style optimization.
3+
4+
DQAS part is modified from: examples/clifford_optimization.py
5+
"""
6+
7+
import sys
8+
sys.path.insert(0, "../")
9+
10+
import numpy as np
11+
import tensorflow as tf
12+
13+
import tensorcircuit as tc
14+
from tensorcircuit.applications.vqes import construct_matrix_v3
15+
16+
ctype, rtype = tc.set_dtype("complex64")
17+
K = tc.set_backend("tensorflow")
18+
19+
n = 10 # the number of qubits (must be even for consistency later)
20+
ncz = 2 # number of cz layers in Schrodinger circuit
21+
nlayersq = ncz + 1 # Schrodinger parameter layers
22+
23+
# training setup
24+
epochs = 1000
25+
batch = 1000
26+
27+
# Hamiltonian
28+
h6h = np.load("./h6_hamiltonian.npy") # reported in 0.99 A
29+
hamiltonian = construct_matrix_v3(h6h.tolist())
30+
31+
def hybrid_ansatz(structure, paramq, preprocess="direct", train=True):
32+
"""_summary_
33+
34+
Parameters
35+
----------
36+
structure : K.Tensor, (n//2, 2)
37+
parameters to decide graph structure of Clifford circuits
38+
paramq : K.Tensor, (nlayersq, n, 3)
39+
parameters in quantum variational circuits, the last layer for Heisenberg circuits
40+
preprocess : str, optional
41+
preprocess, by default "direct"
42+
43+
Returns
44+
-------
45+
K.Tensor, [1,]
46+
loss value
47+
"""
48+
c = tc.Circuit(n)
49+
if preprocess == "softmax":
50+
structure = K.softmax(structure, axis=-1)
51+
elif preprocess == "most":
52+
structure = K.onehot(K.argmax(structure, axis=-1), num=2)
53+
elif preprocess == "direct":
54+
pass
55+
56+
structure = K.cast(structure, ctype)
57+
structure = tf.reshape(structure, shape=[n//2, 2])
58+
59+
# quantum variational in Schrodinger part, first consider a ring topol
60+
for j in range(nlayersq):
61+
if j !=0 and j!=nlayersq-1:
62+
for i in range(j%2,n,2):
63+
c.cz(i, (i+1)%n)
64+
for i in range(n):
65+
c.rx(i, theta=paramq[j, i, 0])
66+
c.ry(i, theta=paramq[j, i, 1])
67+
c.rz(i, theta=paramq[j, i, 2])
68+
69+
# Clifford part, which is actually virtual
70+
if train:
71+
for j in range(0,n//2-1):
72+
dis = j + 1
73+
for i in range(0,n):
74+
c.unitary(
75+
i,
76+
(i+dis) % n,
77+
unitary=structure[j, 0] * tc.gates.ii().tensor
78+
+ structure[j, 1] * tc.gates.cz().tensor,
79+
)
80+
81+
for i in range(0,n//2):
82+
c.unitary(
83+
i,
84+
i + n//2,
85+
unitary=structure[n//2-1, 0] * tc.gates.ii().tensor
86+
+ structure[n//2-1, 1] * tc.gates.cz().tensor,
87+
)
88+
else: # if not for training, we just put nontrivial gates
89+
for j in range(0,n//2-1):
90+
dis = j + 1
91+
for i in range(0,n):
92+
if structure[j, 1]==1:
93+
c.cz(i, (i+dis) % n)
94+
95+
for i in range(0,n//2):
96+
if structure[j, 1]==1:
97+
c.cz(i, i + n//2)
98+
99+
return c
100+
101+
def hybrid_vqe(structure, paramq, preprocess="direct"):
102+
"""_summary_
103+
104+
Parameters
105+
----------
106+
structure : K.Tensor, (n//2, 2)
107+
parameters to decide graph structure of Clifford circuits
108+
paramq : K.Tensor, (nlayersq, n, 3)
109+
parameters in quantum variational circuits, the last layer for Heisenberg circuits
110+
preprocess : str, optional
111+
preprocess, by default "direct"
112+
113+
Returns
114+
-------
115+
K.Tensor, [1,]
116+
loss value
117+
"""
118+
c = hybrid_ansatz(structure, paramq, preprocess)
119+
return tc.templates.measurements.operator_expectation(c, hamiltonian)
120+
121+
def sampling_from_structure(structures, batch=1):
122+
ch = structures.shape[-1]
123+
prob = K.softmax(K.real(structures), axis=-1)
124+
prob = K.reshape(prob, [-1, ch])
125+
p = prob.shape[0]
126+
r = np.stack(
127+
np.array(
128+
[np.random.choice(ch, p=K.numpy(prob[i]), size=[batch]) for i in range(p)]
129+
)
130+
)
131+
return r.transpose()
132+
133+
134+
@K.jit
135+
def best_from_structure(structures):
136+
return K.argmax(structures, axis=-1)
137+
138+
139+
def nmf_gradient(structures, oh):
140+
""" compute the Monte Carlo gradient with respect of naive mean-field probabilistic model
141+
142+
Parameters
143+
----------
144+
structures : K.Tensor, (n//2, ch)
145+
structure parameter for single- or two-qubit gates
146+
oh : K.Tensor, (n//2, ch), onehot
147+
a given structure sampled via strcuture parameters (in main function)
148+
149+
Returns
150+
-------
151+
K.Tensor, (n//2 * 2, ch) == (n, ch)
152+
MC gradients
153+
"""
154+
choice = K.argmax(oh, axis=-1)
155+
prob = K.softmax(K.real(structures), axis=-1)
156+
indices = K.transpose(
157+
K.stack([K.cast(tf.range(structures.shape[0]), "int64"), choice])
158+
)
159+
prob = tf.gather_nd(prob, indices)
160+
prob = K.reshape(prob, [-1, 1])
161+
prob = K.tile(prob, [1, structures.shape[-1]])
162+
163+
return K.real(
164+
tf.tensor_scatter_nd_add(
165+
tf.cast(-prob, dtype=ctype),
166+
indices,
167+
tf.ones([structures.shape[0]], dtype=ctype),
168+
)
169+
) # in oh : 1-p, not in oh : -p
170+
171+
# vmap for a batch of structures
172+
nmf_gradient_vmap = K.jit(
173+
K.vmap(nmf_gradient, vectorized_argnums=1))
174+
175+
# vvag for a batch of structures
176+
vvag_hybrid = K.jit(
177+
K.vectorized_value_and_grad(hybrid_vqe, vectorized_argnums=(0,), argnums=(1,)),
178+
static_argnums=(2,))
179+
180+
def train_hybrid(stddev=0.05, lr=None, epochs=2000, debug_step=50, batch=256, verbose=False):
181+
# params = K.implicit_randn([n//2, 2], stddev=stddev)
182+
params = K.ones([n//2, 2], dtype=float)
183+
paramq = K.implicit_randn([nlayersq, n, 3], stddev=stddev) * 2*np.pi
184+
if lr is None:
185+
lr = tf.keras.optimizers.schedules.ExponentialDecay(0.6, 100, 0.8)
186+
structure_opt = K.optimizer(tf.keras.optimizers.Adam(lr))
187+
188+
avcost = 0
189+
avcost2 = 0
190+
loss_history = []
191+
for epoch in range(epochs): # iteration to update strcuture param
192+
# random sample some structures
193+
batched_stucture = K.onehot(
194+
sampling_from_structure(params, batch=batch),
195+
num=params.shape[-1],
196+
)
197+
vs, gq = vvag_hybrid(batched_stucture, paramq, "direct")
198+
loss_history.append(np.min(vs))
199+
gq = gq[0]
200+
avcost = K.mean(vs) # average cost of the batch
201+
gs = nmf_gradient_vmap(params, batched_stucture) # \nabla lnp
202+
gs = K.mean(K.reshape(vs - avcost2, [-1, 1, 1]) * gs, axis=0)
203+
# avcost2 is averaged cost in the last epoch
204+
avcost2 = avcost
205+
206+
[params, paramq] = structure_opt.update([gs, gq], [params, paramq])
207+
if epoch % debug_step == 0 or epoch == epochs - 1:
208+
print("----------epoch %s-----------" % epoch)
209+
print(
210+
"batched average loss: ",
211+
np.mean(vs),
212+
"minimum candidate loss: ",
213+
np.min(vs),
214+
)
215+
216+
# max over choices, min over layers and qubits
217+
minp = tf.math.reduce_min(tf.math.reduce_max(tf.math.softmax(params), axis=-1))
218+
if minp > 0.5:
219+
print("probability converged")
220+
221+
if verbose:
222+
print(
223+
"strcuture parameter: \n",
224+
params.numpy()
225+
)
226+
227+
cand_preset = best_from_structure(params)
228+
print(cand_preset)
229+
print("current recommendation loss: ", hybrid_vqe(params, paramq, "most"))
230+
231+
loss_history = np.array(loss_history)
232+
return hybrid_vqe(params, paramq, "most"), params, paramq, loss_history
233+
234+
235+
print('Train hybrid.')
236+
ee, params, paramq, loss_history = train_hybrid(epochs=epochs, batch=batch, verbose=True)
237+
print('Energy:', ee)

0 commit comments

Comments
 (0)