Skip to content

Commit 3bc284b

Browse files
committed
add vbe example
1 parent 43088d8 commit 3bc284b

File tree

11 files changed

+454
-11
lines changed

11 files changed

+454
-11
lines changed

example/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,7 @@
1414
- `simple_uccsd.py`: Simple UCCSD calculation.
1515
- `switch_backend.py`: Switch backend at runtime.
1616
- `ucc_classes.py`: UCC subclasses share the same interface.
17+
- `vbe_lib.py`: The library for variational basis state encoder.
18+
- `vbe_td.py`: Variational basis state encoder for the dynamics of the spin-boson model.
19+
- `vbe_ti.py`: Variational basis state encoder for the ground state of the Holstein model.
1720
- `water_pes.py`: Water potential energy curve with 6-31G(d) basis set, enabled by GPU acceleration.

example/vbe_lib.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Copyright (c) 2023. The TenCirChem Developers. All Rights Reserved.
2+
#
3+
# This file is distributed under ACADEMIC PUBLIC LICENSE
4+
# and WITHOUT ANY WARRANTY. See the LICENSE file for details.
5+
6+
"""
7+
Library for variational basis state encoder
8+
https://arxiv.org/abs/2301.01442
9+
"""
10+
import numpy as np
11+
from opt_einsum import contract
12+
13+
14+
def get_psi_indices(dof_nature, b_dof_pidx, n_qubit_per_mode):
15+
b_dof_vidx = []
16+
for i in range(max(dof_nature) + 1):
17+
b_dof_vidx.append(np.where(dof_nature == i)[0])
18+
19+
# indices used for tensor contraction
20+
psi_idx = []
21+
for i in range(max(dof_nature) + 1 + list(dof_nature).count(-1)):
22+
if i not in b_dof_pidx:
23+
psi_idx.extend([f"p-{i}"])
24+
else:
25+
psi_idx.extend([f"v-{i}-{j}" for j in range(n_qubit_per_mode)])
26+
psi_idx_top = [f"{i}-top" for i in psi_idx]
27+
psi_idx_bottom = [f"{i}-bottom" for i in psi_idx]
28+
29+
return psi_idx_top, psi_idx_bottom, b_dof_vidx
30+
31+
32+
def get_contracted_mpo(h_mpo, b_array, n_qubit_per_mode, b_dof_pidx, psi_indices):
33+
nbas = b_array.shape[-1]
34+
b_shape = tuple([2] * n_qubit_per_mode + [nbas])
35+
args = []
36+
for i in range(len(h_mpo)):
37+
args.append(h_mpo[i])
38+
args.append([f"mpo-{i}", f"p-{i}-top", f"p-{i}-bottom", f"mpo-{i + 1}"])
39+
for i in range(len(b_array)):
40+
dof_pidx = b_dof_pidx[i]
41+
args.append(b_array[i].reshape(b_shape).conj())
42+
args.append([f"v-{dof_pidx}-{j}-top" for j in range(n_qubit_per_mode)] + [f"p-{dof_pidx}-top"])
43+
args.append(b_array[i].reshape(b_shape))
44+
args.append([f"v-{dof_pidx}-{j}-bottom" for j in range(n_qubit_per_mode)] + [f"p-{dof_pidx}-bottom"])
45+
out = psi_indices.copy()
46+
out.extend(["mpo-0", f"mpo-{len(h_mpo)}"])
47+
args.append(out)
48+
size = round(2 ** (len(psi_indices) // 2))
49+
h_contracted = contract(*args).reshape(size, size)
50+
return h_contracted
51+
52+
53+
def get_contract_args(psi, h_mpo, b_array, i, n_qubit_per_mode, psi_idx_top, psi_idx_bottom, b_dof_pidx):
54+
nbas = b_array.shape[-1]
55+
psi_shape2 = [2] * len(psi_idx_top)
56+
b_shape = tuple([2] * n_qubit_per_mode + [nbas])
57+
# psi
58+
args = [psi.reshape(psi_shape2).conj(), psi_idx_top] + [psi.reshape(psi_shape2), psi_idx_bottom]
59+
# H
60+
for j in range(len(h_mpo)):
61+
args.append(h_mpo[j])
62+
args.append([f"mpo-{j}", f"p-{j}-top", f"p-{j}-bottom", f"mpo-{j + 1}"])
63+
# b
64+
for j in range(len(b_array)):
65+
if j == i:
66+
continue
67+
k = b_dof_pidx[j]
68+
indices = [f"v-{k}-{l}-bottom" for l in range(n_qubit_per_mode)] + [f"p-{k}-bottom"]
69+
args.extend([b_array[j].reshape(b_shape), indices])
70+
indices = [f"v-{k}-{l}-top" for l in range(n_qubit_per_mode)] + [f"p-{k}-top"]
71+
args.extend([b_array[j].reshape(b_shape).conj(), indices])
72+
return args

example/vbe_td.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# Copyright (c) 2023. The TenCirChem Developers. All Rights Reserved.
2+
#
3+
# This file is distributed under ACADEMIC PUBLIC LICENSE
4+
# and WITHOUT ANY WARRANTY. See the LICENSE file for details.
5+
6+
"""
7+
Variational basis state encoder for the dynamics of the spin-boson model.
8+
2 qubit or each phonon mode.
9+
https://arxiv.org/abs/2301.01442
10+
"""
11+
12+
import numpy as np
13+
from scipy.integrate import solve_ivp
14+
from opt_einsum import contract
15+
import tensorcircuit as tc
16+
from renormalizer import Op, Mpo, Model, OpSum
17+
18+
from tencirchem import set_backend
19+
from tencirchem.dynamic import get_ansatz, get_deriv, get_jacobian_func, qubit_encode_basis, sbm
20+
21+
from vbe_lib import get_psi_indices, get_contracted_mpo, get_contract_args
22+
23+
set_backend("jax")
24+
25+
epsilon = 0
26+
delta = 1
27+
omega_list = [0.5, 1]
28+
g_list = [0.25, 1]
29+
30+
nmode = len(omega_list)
31+
assert nmode == len(g_list)
32+
33+
# two qubit for each mode
34+
n_qubit_per_mode = 2
35+
nbas_v = 1 << n_qubit_per_mode
36+
37+
# -1 for electron dof, natural numbers for phonon dof
38+
dof_nature = np.array([-1] + [0] * n_qubit_per_mode + [1] * n_qubit_per_mode)
39+
b_dof_pidx = np.array([1, 2])
40+
41+
n_dof = len(dof_nature)
42+
psi_shape2 = [2] * n_dof
43+
44+
psi_idx_top, psi_idx_bottom, b_dof_vidx = get_psi_indices(dof_nature, b_dof_pidx, n_qubit_per_mode)
45+
46+
47+
def get_model(epsilon, delta, nmode, omega_list, g_list, nlevels):
48+
ham_terms = sbm.get_ham_terms(epsilon, delta, nmode, omega_list, g_list)
49+
basis = sbm.get_basis(omega_list, nlevels)
50+
return Model(basis, ham_terms)
51+
52+
53+
nbas = 16
54+
55+
b_shape = tuple([2] * n_qubit_per_mode + [nbas])
56+
57+
assert len(omega_list) == nmode
58+
assert len(g_list) == nmode
59+
model = get_model(epsilon, delta, nmode, omega_list, g_list, [nbas] * nmode)
60+
61+
h_mpo = Mpo(model)
62+
63+
circuit = tc.Circuit(1 + nmode * n_qubit_per_mode)
64+
psi0 = circuit.state()
65+
n_layers = 3
66+
67+
68+
def get_vha_terms():
69+
basis = sbm.get_basis(omega_list, [nbas_v] * nmode)
70+
spin_basis = qubit_encode_basis(basis, "gray")
71+
72+
spin_ham_terms = OpSum([Op("X", ["spin"], 1.0)])
73+
for i in range(nmode):
74+
complete_list = []
75+
for j in range(n_qubit_per_mode):
76+
complete = OpSum()
77+
dof = (f"v{i}", f"TCCQUBIT-{j}")
78+
for symbol in "IXYZ":
79+
complete += Op(symbol, dof)
80+
complete_list.append(complete)
81+
complete_real = complete_list[0]
82+
for c in complete_list[1:]:
83+
complete_real = complete_real * c
84+
spin_ham_terms.extend(complete_real)
85+
spin_ham_terms.extend(Op("Z", "spin") * complete_real)
86+
spin_ham_terms = OpSum([op.squeeze_identity() for op in spin_ham_terms.simplify() if not op.is_identity]).simplify()
87+
return spin_ham_terms, spin_basis
88+
89+
90+
spin_ham_terms, spin_basis = get_vha_terms()
91+
92+
93+
theta0 = np.zeros(n_layers * len(spin_ham_terms), dtype=np.float64)
94+
ansatz = get_ansatz(spin_ham_terms, spin_basis, n_layers, psi0)
95+
jacobian_func = get_jacobian_func(ansatz)
96+
97+
98+
def deriv_fun(t, theta_and_b):
99+
theta = theta_and_b[: len(theta0)]
100+
psi = ansatz(theta)
101+
b_array = theta_and_b[len(theta0) :].reshape(nmode, nbas_v, nbas)
102+
103+
h_contracted = get_contracted_mpo(h_mpo, b_array, n_qubit_per_mode, b_dof_pidx, psi_idx_top + psi_idx_bottom)
104+
theta_deriv = get_deriv(ansatz, jacobian_func, theta, h_contracted)
105+
106+
psi = psi.reshape(psi_shape2)
107+
b_deriv_list = []
108+
for i in range(nmode):
109+
b = b_array[i]
110+
# calculate rho
111+
indices_base = [("contract", ii) for ii in range(n_dof)]
112+
psi_top_indices = indices_base.copy()
113+
psi_bottom_indices = indices_base.copy()
114+
for j in b_dof_vidx[i]:
115+
psi_top_indices[j] = ("top", j)
116+
psi_bottom_indices[j] = ("bottom", j)
117+
out_indices = [("top", j) for j in b_dof_vidx[i]] + [("bottom", j) for j in b_dof_vidx[i]]
118+
args = [psi.conj(), psi_top_indices, psi, psi_bottom_indices, out_indices]
119+
rho = contract(*args).reshape(1 << n_qubit_per_mode, 1 << n_qubit_per_mode)
120+
# rho_inv = regularized_inversion(rho, 1e-6)
121+
from scipy.linalg import pinv
122+
123+
rho += np.eye(len(rho)) * 1e-5
124+
rho_inv = pinv(rho)
125+
126+
b = b.reshape(nbas_v, nbas)
127+
# projector
128+
proj = b.conj().T @ b
129+
130+
# derivative
131+
args = get_contract_args(psi, h_mpo, b_array, i, n_qubit_per_mode, psi_idx_top, psi_idx_bottom, b_dof_pidx)
132+
k = b_dof_pidx[i]
133+
args.append(b_array[i].reshape(b_shape))
134+
args.append([f"v-{k}-{l}-bottom" for l in range(n_qubit_per_mode)] + [f"p-{k}-bottom"])
135+
# output indices
136+
args.append([f"v-{k}-{l}-top" for l in range(n_qubit_per_mode)] + [f"p-{k}-top", "mpo-0", f"mpo-{len(h_mpo)}"])
137+
138+
# take transpose to be compatible with previous code
139+
b_deriv = contract(*args).squeeze().reshape(nbas_v, nbas).T
140+
b_deriv = np.einsum("bf, bg -> fg", b_deriv, np.eye(nbas) - proj)
141+
b_deriv = -1j * np.einsum("fg, fh -> hg", b_deriv, rho_inv.T)
142+
b_deriv_list.append(b_deriv)
143+
return np.concatenate([theta_deriv, np.array(b_deriv_list).ravel()])
144+
145+
146+
def main():
147+
b_list = []
148+
for _ in range(nmode):
149+
b = np.eye(nbas)[:nbas_v] # nbas_v * nbas
150+
b_list.append(b)
151+
theta_and_b = np.concatenate([theta0, np.array(b_list).ravel()]).astype(complex)
152+
z_list = [1]
153+
x_list = [0]
154+
155+
tau = 0.1
156+
steps = 100
157+
158+
dummy_model = get_model(epsilon, delta, nmode, omega_list, g_list, [nbas_v] * nmode)
159+
z_op = Mpo(dummy_model, Op("Z", "spin", factor=1)).todense()
160+
x_op = Mpo(dummy_model, Op("X", "spin", factor=1)).todense()
161+
162+
for n in range(steps):
163+
print(n)
164+
sol = solve_ivp(deriv_fun, [n * tau, (n + 1) * tau], theta_and_b)
165+
theta_and_b = sol.y[:, -1]
166+
theta = theta_and_b[: len(theta0)]
167+
psi = ansatz(theta)
168+
z = psi.conj().T @ (z_op @ psi)
169+
x = psi.conj().T @ (x_op @ psi)
170+
z_list.append(z.real)
171+
x_list.append(x.real)
172+
173+
print(z_list)
174+
175+
176+
if __name__ == "__main__":
177+
main()

0 commit comments

Comments
 (0)