tequila-basic 1.9.9__py3-none-any.whl → 1.9.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tequila/__init__.py +29 -14
- tequila/apps/__init__.py +14 -5
- tequila/apps/_unary_state_prep_impl.py +145 -112
- tequila/apps/adapt/__init__.py +9 -1
- tequila/apps/adapt/adapt.py +154 -113
- tequila/apps/krylov/__init__.py +1 -1
- tequila/apps/krylov/krylov.py +23 -21
- tequila/apps/robustness/helpers.py +10 -6
- tequila/apps/robustness/interval.py +238 -156
- tequila/apps/unary_state_prep.py +29 -23
- tequila/autograd_imports.py +8 -5
- tequila/circuit/__init__.py +2 -1
- tequila/circuit/_gates_impl.py +135 -67
- tequila/circuit/circuit.py +163 -79
- tequila/circuit/compiler.py +114 -105
- tequila/circuit/gates.py +288 -120
- tequila/circuit/gradient.py +35 -23
- tequila/circuit/noise.py +83 -74
- tequila/circuit/postselection.py +120 -0
- tequila/circuit/pyzx.py +10 -6
- tequila/circuit/qasm.py +201 -83
- tequila/circuit/qpic.py +63 -61
- tequila/grouping/binary_rep.py +148 -146
- tequila/grouping/binary_utils.py +84 -75
- tequila/grouping/compile_groups.py +334 -230
- tequila/grouping/ev_utils.py +77 -41
- tequila/grouping/fermionic_functions.py +383 -308
- tequila/grouping/fermionic_methods.py +170 -123
- tequila/grouping/overlapping_methods.py +69 -52
- tequila/hamiltonian/paulis.py +12 -13
- tequila/hamiltonian/paulistring.py +1 -1
- tequila/hamiltonian/qubit_hamiltonian.py +45 -35
- tequila/ml/__init__.py +1 -0
- tequila/ml/interface_torch.py +19 -16
- tequila/ml/ml_api.py +11 -10
- tequila/ml/utils_ml.py +12 -11
- tequila/objective/__init__.py +8 -3
- tequila/objective/braket.py +55 -47
- tequila/objective/objective.py +87 -55
- tequila/objective/qtensor.py +36 -27
- tequila/optimizers/__init__.py +31 -23
- tequila/optimizers/_containers.py +11 -7
- tequila/optimizers/optimizer_base.py +111 -83
- tequila/optimizers/optimizer_gd.py +258 -231
- tequila/optimizers/optimizer_gpyopt.py +56 -42
- tequila/optimizers/optimizer_scipy.py +157 -112
- tequila/quantumchemistry/__init__.py +66 -38
- tequila/quantumchemistry/chemistry_tools.py +393 -209
- tequila/quantumchemistry/encodings.py +121 -13
- tequila/quantumchemistry/madness_interface.py +170 -96
- tequila/quantumchemistry/orbital_optimizer.py +86 -41
- tequila/quantumchemistry/psi4_interface.py +166 -97
- tequila/quantumchemistry/pyscf_interface.py +70 -23
- tequila/quantumchemistry/qc_base.py +866 -414
- tequila/simulators/__init__.py +0 -3
- tequila/simulators/simulator_api.py +247 -105
- tequila/simulators/simulator_aqt.py +102 -0
- tequila/simulators/simulator_base.py +147 -53
- tequila/simulators/simulator_cirq.py +58 -42
- tequila/simulators/simulator_cudaq.py +600 -0
- tequila/simulators/simulator_ddsim.py +390 -0
- tequila/simulators/simulator_mqp.py +30 -0
- tequila/simulators/simulator_pyquil.py +190 -171
- tequila/simulators/simulator_qibo.py +95 -87
- tequila/simulators/simulator_qiskit.py +119 -107
- tequila/simulators/simulator_qlm.py +52 -26
- tequila/simulators/simulator_qulacs.py +74 -52
- tequila/simulators/simulator_spex.py +95 -60
- tequila/simulators/simulator_symbolic.py +6 -5
- tequila/simulators/test_spex_simulator.py +8 -11
- tequila/tools/convenience.py +4 -4
- tequila/tools/qng.py +72 -64
- tequila/tools/random_generators.py +38 -34
- tequila/utils/bitstrings.py +7 -7
- tequila/utils/exceptions.py +19 -5
- tequila/utils/joined_transformation.py +8 -10
- tequila/utils/keymap.py +0 -5
- tequila/utils/misc.py +6 -4
- tequila/version.py +1 -1
- tequila/wavefunction/qubit_wavefunction.py +47 -28
- {tequila_basic-1.9.9.dist-info → tequila_basic-1.9.10.dist-info}/METADATA +13 -16
- tequila_basic-1.9.10.dist-info/RECORD +93 -0
- {tequila_basic-1.9.9.dist-info → tequila_basic-1.9.10.dist-info}/WHEEL +1 -1
- tequila_basic-1.9.9.dist-info/RECORD +0 -88
- {tequila_basic-1.9.9.dist-info → tequila_basic-1.9.10.dist-info}/licenses/LICENSE +0 -0
- {tequila_basic-1.9.9.dist-info → tequila_basic-1.9.10.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,17 @@
|
|
1
1
|
import numpy as np
|
2
2
|
import tequila as tq
|
3
3
|
import openfermion as of
|
4
|
-
from openfermion import
|
4
|
+
from openfermion import (
|
5
|
+
FermionOperator,
|
6
|
+
QubitOperator,
|
7
|
+
expectation,
|
8
|
+
get_sparse_operator,
|
9
|
+
jordan_wigner,
|
10
|
+
reverse_jordan_wigner,
|
11
|
+
normal_ordered,
|
12
|
+
count_qubits,
|
13
|
+
variance,
|
14
|
+
)
|
5
15
|
from itertools import product
|
6
16
|
import scipy as sp
|
7
17
|
import tequila.grouping.ev_utils as evu
|
@@ -9,8 +19,9 @@ from functools import partial
|
|
9
19
|
import multiprocessing as mp
|
10
20
|
from tequila import TequilaException
|
11
21
|
|
22
|
+
|
12
23
|
def get_obt_tbt(h_ferm, spin_orb=True):
|
13
|
-
|
24
|
+
"""
|
14
25
|
Parameters
|
15
26
|
----------
|
16
27
|
mol_name -
|
@@ -27,56 +38,57 @@ def get_obt_tbt(h_ferm, spin_orb=True):
|
|
27
38
|
(obt, tbt) Tuple of one- and two-body integrals.
|
28
39
|
h_ferm Fermionic Hamiltonian of the molecular system.
|
29
40
|
num_elecs Number of electrons in the molecular system.
|
30
|
-
|
41
|
+
"""
|
31
42
|
no_h_ferm = normal_ordered(h_ferm)
|
32
|
-
tbt = get_tbt(no_h_ferm, spin_orb
|
43
|
+
tbt = get_tbt(no_h_ferm, spin_orb=spin_orb)
|
33
44
|
h1b = no_h_ferm - tbt_to_ferm(tbt, spin_orb)
|
34
45
|
h1b = normal_ordered(of_simplify(h1b))
|
35
46
|
obt = get_obt(h1b, spin_orb=spin_orb)
|
36
47
|
return (obt, tbt)
|
37
48
|
|
49
|
+
|
38
50
|
def of_simplify(op):
|
39
|
-
|
51
|
+
"""
|
40
52
|
Simplifies fermionic operator by converting to Qubit and back again.
|
41
|
-
|
53
|
+
"""
|
42
54
|
return reverse_jordan_wigner(jordan_wigner(op))
|
43
55
|
|
44
|
-
|
45
|
-
|
56
|
+
|
57
|
+
def get_spin_orbitals(H: FermionOperator):
|
58
|
+
"""
|
46
59
|
Obtain the number of spin orbitals of H
|
47
|
-
|
60
|
+
"""
|
48
61
|
n = -1
|
49
62
|
for term, val in H.terms.items():
|
50
63
|
if len(term) == 4:
|
51
|
-
n = max([
|
52
|
-
n, term[0][0], term[1][0],
|
53
|
-
term[2][0], term[3][0]
|
54
|
-
])
|
64
|
+
n = max([n, term[0][0], term[1][0], term[2][0], term[3][0]])
|
55
65
|
elif len(term) == 2:
|
56
|
-
n = max([
|
57
|
-
n, term[0][0], term[1][0]])
|
66
|
+
n = max([n, term[0][0], term[1][0]])
|
58
67
|
n += 1
|
59
68
|
return n
|
60
69
|
|
61
|
-
|
62
|
-
|
70
|
+
|
71
|
+
def get_tbt(H: FermionOperator, n=None, spin_orb=False):
|
72
|
+
"""
|
63
73
|
Obtain the 4-rank tensor that represents two body interaction in H.
|
64
74
|
In chemist ordering a^ a a^ a.
|
65
75
|
In addition, simplify tensor assuming symmetry between alpha/beta coefficients
|
66
|
-
|
76
|
+
"""
|
67
77
|
if n is None:
|
68
78
|
n = get_spin_orbitals(H)
|
69
79
|
|
70
80
|
phy_tbt = np.zeros((n, n, n, n))
|
71
81
|
for term, val in H.terms.items():
|
72
82
|
if len(term) == 4:
|
73
|
-
phy_tbt[
|
74
|
-
term[0][0], term[1][0],
|
75
|
-
term[2][0], term[3][0]
|
76
|
-
] = np.real_if_close(val)
|
83
|
+
phy_tbt[term[0][0], term[1][0], term[2][0], term[3][0]] = np.real_if_close(val)
|
77
84
|
|
78
85
|
chem_tbt = np.transpose(phy_tbt, [0, 3, 1, 2])
|
79
|
-
chem_tbt_sym = (
|
86
|
+
chem_tbt_sym = (
|
87
|
+
chem_tbt
|
88
|
+
- np.transpose(chem_tbt, [0, 3, 2, 1])
|
89
|
+
+ np.transpose(chem_tbt, [2, 3, 0, 1])
|
90
|
+
- np.transpose(chem_tbt, [2, 1, 0, 3])
|
91
|
+
) / 4.0
|
80
92
|
|
81
93
|
# Spin-orbital to orbital
|
82
94
|
n_orb = phy_tbt.shape[0]
|
@@ -84,30 +96,30 @@ def get_tbt(H : FermionOperator, n = None, spin_orb=False):
|
|
84
96
|
alpha_indices = list(range(0, n_orb * 2, 2))
|
85
97
|
beta_indices = list(range(1, n_orb * 2, 2))
|
86
98
|
|
87
|
-
chem_tbt_orb =
|
88
|
-
|
99
|
+
chem_tbt_orb = chem_tbt_sym[np.ix_(alpha_indices, alpha_indices, beta_indices, beta_indices)] - np.transpose(
|
100
|
+
chem_tbt_sym[np.ix_(alpha_indices, beta_indices, beta_indices, alpha_indices)], [0, 3, 2, 1]
|
101
|
+
)
|
89
102
|
if spin_orb:
|
90
103
|
chem_tbt = np.zeros_like(chem_tbt_sym)
|
91
104
|
n = chem_tbt_orb.shape[0]
|
92
105
|
for i, j, k, l in product(range(n), repeat=4):
|
93
106
|
for a, b in product(range(2), repeat=2):
|
94
|
-
chem_tbt[(2*i+a, 1), (2*j+a, 0), (2*k+b, 1), (2*l+b, 0)] = chem_tbt_orb[i,j,k,l]
|
107
|
+
chem_tbt[(2 * i + a, 1), (2 * j + a, 0), (2 * k + b, 1), (2 * l + b, 0)] = chem_tbt_orb[i, j, k, l]
|
95
108
|
return chem_tbt
|
96
109
|
else:
|
97
110
|
return chem_tbt_orb
|
98
111
|
|
99
112
|
|
100
|
-
|
101
|
-
|
102
|
-
'''
|
113
|
+
def get_obt(H: FermionOperator, n=None, spin_orb=False, tiny=1e-12):
|
114
|
+
"""
|
103
115
|
Obtain the 2-rank tensor that represents one body interaction in H.
|
104
116
|
In addition, simplify tensor assuming symmetry between alpha/beta coefficients
|
105
|
-
|
117
|
+
"""
|
106
118
|
# getting N^2 phy_tbt and then (N/2)^2 chem_tbt
|
107
119
|
if n is None:
|
108
120
|
n = get_spin_orbitals(H)
|
109
121
|
|
110
|
-
obt = np.zeros((n,n))
|
122
|
+
obt = np.zeros((n, n))
|
111
123
|
for term, val in H.terms.items():
|
112
124
|
if len(term) == 2:
|
113
125
|
if term[0][1] == 1 and term[1][1] == 0:
|
@@ -131,13 +143,15 @@ def get_obt(H : FermionOperator, n = None, spin_orb=False, tiny=1e-12):
|
|
131
143
|
obt_red_du = np.zeros((n_orb, n_orb))
|
132
144
|
for i in range(n_orb):
|
133
145
|
for j in range(n_orb):
|
134
|
-
obt_red_uu[i,j] = obt[2*i, 2*j]
|
135
|
-
obt_red_dd[i,j] = obt[2*i+1, 2*j+1]
|
136
|
-
obt_red_ud = obt[2*i, 2*j+1]
|
137
|
-
obt_red_du = obt[2*i+1, 2*j]
|
146
|
+
obt_red_uu[i, j] = obt[2 * i, 2 * j]
|
147
|
+
obt_red_dd[i, j] = obt[2 * i + 1, 2 * j + 1]
|
148
|
+
obt_red_ud = obt[2 * i, 2 * j + 1]
|
149
|
+
obt_red_du = obt[2 * i + 1, 2 * j]
|
138
150
|
|
139
151
|
if np.sum(np.abs(obt_red_du)) + np.sum(np.abs(obt_red_ud)) != 0:
|
140
|
-
print(
|
152
|
+
print(
|
153
|
+
"Warning, operator to one-body transformation ran with spin_orb=false, but spin-orbit couplings are not 0!"
|
154
|
+
)
|
141
155
|
if np.sum(np.abs(obt_red_uu - obt_red_dd)) > tiny:
|
142
156
|
print("Warning, operator to one-body transformation ran with spin_orb=false, but isn't symmetric to spin-flips")
|
143
157
|
print("obt_uu - obt_dd = {}".format(obt_red_uu - obt_red_dd))
|
@@ -146,11 +160,12 @@ def get_obt(H : FermionOperator, n = None, spin_orb=False, tiny=1e-12):
|
|
146
160
|
|
147
161
|
return obt
|
148
162
|
|
163
|
+
|
149
164
|
def get_cartan_ferm_op(tsr, spin_orb=False):
|
150
|
-
|
165
|
+
"""
|
151
166
|
Return the corresponding fermionic operators in Cartan subalgebra
|
152
167
|
based on the tensor. This tensor can index over spin-orbtals or orbitals
|
153
|
-
|
168
|
+
"""
|
154
169
|
if len(tsr.shape) == 4:
|
155
170
|
n = tsr.shape[0]
|
156
171
|
op = FermionOperator.zero()
|
@@ -158,18 +173,11 @@ def get_cartan_ferm_op(tsr, spin_orb=False):
|
|
158
173
|
if not spin_orb:
|
159
174
|
for a, b in product(range(2), repeat=2):
|
160
175
|
op += FermionOperator(
|
161
|
-
term
|
162
|
-
|
163
|
-
(2*j+b, 1), (2*j+b, 0)
|
164
|
-
), coefficient=tsr[i, i, j, j]
|
176
|
+
term=((2 * i + a, 1), (2 * i + a, 0), (2 * j + b, 1), (2 * j + b, 0)),
|
177
|
+
coefficient=tsr[i, i, j, j],
|
165
178
|
)
|
166
179
|
else:
|
167
|
-
op += FermionOperator(
|
168
|
-
term=(
|
169
|
-
(i, 1), (i, 0),
|
170
|
-
(j, 1), (j, 0)
|
171
|
-
), coefficient=tsr[i, i, j, j]
|
172
|
-
)
|
180
|
+
op += FermionOperator(term=((i, 1), (i, 0), (j, 1), (j, 0)), coefficient=tsr[i, i, j, j])
|
173
181
|
return op
|
174
182
|
elif len(tsr.shape) == 2:
|
175
183
|
n = tsr.shape[0]
|
@@ -177,24 +185,17 @@ def get_cartan_ferm_op(tsr, spin_orb=False):
|
|
177
185
|
for i in range(n):
|
178
186
|
if not spin_orb:
|
179
187
|
for a in range(2):
|
180
|
-
op += FermionOperator(
|
181
|
-
term = (
|
182
|
-
(2*i+a, 1), (2*i+a, 0)
|
183
|
-
), coefficient=tsr[i, i]
|
184
|
-
)
|
188
|
+
op += FermionOperator(term=((2 * i + a, 1), (2 * i + a, 0)), coefficient=tsr[i, i])
|
185
189
|
else:
|
186
|
-
op += FermionOperator(
|
187
|
-
term = (
|
188
|
-
(i, 1), (i, 0)
|
189
|
-
), coefficient=tsr[i, i]
|
190
|
-
)
|
190
|
+
op += FermionOperator(term=((i, 1), (i, 0)), coefficient=tsr[i, i])
|
191
191
|
return op
|
192
192
|
|
193
|
+
|
193
194
|
def get_ferm_op(tsr, spin_orb=False):
|
194
|
-
|
195
|
+
"""
|
195
196
|
Return the corresponding fermionic operators based on the tensor
|
196
197
|
This tensor can index over spin-orbtals or orbitals
|
197
|
-
|
198
|
+
"""
|
198
199
|
if len(tsr.shape) == 4:
|
199
200
|
n = tsr.shape[0]
|
200
201
|
op = FermionOperator.zero()
|
@@ -202,18 +203,11 @@ def get_ferm_op(tsr, spin_orb=False):
|
|
202
203
|
if not spin_orb:
|
203
204
|
for a, b in product(range(2), repeat=2):
|
204
205
|
op += FermionOperator(
|
205
|
-
term
|
206
|
-
|
207
|
-
(2*k+b, 1), (2*l+b, 0)
|
208
|
-
), coefficient=tsr[i, j, k, l]
|
206
|
+
term=((2 * i + a, 1), (2 * j + a, 0), (2 * k + b, 1), (2 * l + b, 0)),
|
207
|
+
coefficient=tsr[i, j, k, l],
|
209
208
|
)
|
210
209
|
else:
|
211
|
-
op += FermionOperator(
|
212
|
-
term=(
|
213
|
-
(i, 1), (j, 0),
|
214
|
-
(k, 1), (l, 0)
|
215
|
-
), coefficient=tsr[i, j, k, l]
|
216
|
-
)
|
210
|
+
op += FermionOperator(term=((i, 1), (j, 0), (k, 1), (l, 0)), coefficient=tsr[i, j, k, l])
|
217
211
|
return op
|
218
212
|
elif len(tsr.shape) == 2:
|
219
213
|
n = tsr.shape[0]
|
@@ -222,154 +216,161 @@ def get_ferm_op(tsr, spin_orb=False):
|
|
222
216
|
for j in range(n):
|
223
217
|
if not spin_orb:
|
224
218
|
for a in range(2):
|
225
|
-
op += FermionOperator(
|
226
|
-
term = (
|
227
|
-
(2*i+a, 1), (2*j+a, 0)
|
228
|
-
), coefficient=tsr[i, j]
|
229
|
-
)
|
219
|
+
op += FermionOperator(term=((2 * i + a, 1), (2 * j + a, 0)), coefficient=tsr[i, j])
|
230
220
|
else:
|
231
|
-
op += FermionOperator(
|
232
|
-
term = (
|
233
|
-
(i, 1), (j, 0)
|
234
|
-
), coefficient=tsr[i, j]
|
235
|
-
)
|
221
|
+
op += FermionOperator(term=((i, 1), (j, 0)), coefficient=tsr[i, j])
|
236
222
|
return op
|
237
223
|
|
238
|
-
|
239
|
-
|
224
|
+
|
225
|
+
def tbt_to_ferm(tbt: np.array, spin_orb, norm_ord=False):
|
226
|
+
"""
|
240
227
|
Converts two body tensor into Fermion Operator.
|
241
|
-
|
242
|
-
if norm_ord
|
228
|
+
"""
|
229
|
+
if norm_ord:
|
243
230
|
return normal_ordered(get_ferm_op(tbt, spin_orb))
|
244
231
|
else:
|
245
232
|
return get_ferm_op(tbt, spin_orb)
|
246
233
|
|
247
|
-
|
248
|
-
|
234
|
+
|
235
|
+
def cartan_tbt_to_ferm(ctbt: np.array, spin_orb, norm_ord=False):
|
236
|
+
"""
|
249
237
|
Converts two body tensor into Fermion Operator.
|
250
|
-
|
251
|
-
if norm_ord
|
238
|
+
"""
|
239
|
+
if norm_ord:
|
252
240
|
return normal_ordered(get_cartan_ferm_op(ctbt, spin_orb))
|
253
241
|
else:
|
254
242
|
return get_cartan_ferm_op(ctbt, spin_orb)
|
255
243
|
|
256
|
-
|
257
|
-
|
244
|
+
|
245
|
+
def obt_to_ferm(obt: np.array, spin_orb=False, norm_ord=False):
|
246
|
+
"""
|
258
247
|
Converts one body tensor into Fermion Operator.
|
259
|
-
|
260
|
-
if norm_ord
|
248
|
+
"""
|
249
|
+
if norm_ord:
|
261
250
|
return of.normal_ordered(get_ferm_op(obt, spin_orb))
|
262
251
|
else:
|
263
252
|
return get_ferm_op(obt, spin_orb)
|
264
253
|
|
254
|
+
|
265
255
|
def tbt_orb_to_so(tbt):
|
266
|
-
|
256
|
+
"""
|
267
257
|
Converts two body term to spin orbitals.
|
268
|
-
|
258
|
+
"""
|
269
259
|
n = tbt.shape[0]
|
270
260
|
|
271
|
-
tbt_so = np.zeros([2*n,2*n,2*n,2*n])
|
261
|
+
tbt_so = np.zeros([2 * n, 2 * n, 2 * n, 2 * n])
|
272
262
|
for i1, i2, i3, i4 in product(range(n), repeat=4):
|
273
|
-
for a in [0,1]:
|
274
|
-
for b in [0,1]:
|
275
|
-
tbt_so[2*i1+a,2*i2+a,2*i3+b,2*i4+b] = tbt[i1,i2,i3,i4]
|
263
|
+
for a in [0, 1]:
|
264
|
+
for b in [0, 1]:
|
265
|
+
tbt_so[2 * i1 + a, 2 * i2 + a, 2 * i3 + b, 2 * i4 + b] = tbt[i1, i2, i3, i4]
|
276
266
|
return tbt_so
|
277
267
|
|
268
|
+
|
278
269
|
def obt_orb_to_so(obt):
|
279
|
-
|
270
|
+
"""
|
280
271
|
Converts one body term to spin orbitals.
|
281
|
-
|
272
|
+
"""
|
282
273
|
n = obt.shape[0]
|
283
274
|
|
284
|
-
obt_so = np.zeros([2*n,2*n])
|
275
|
+
obt_so = np.zeros([2 * n, 2 * n])
|
285
276
|
for i1, i2 in product(range(n), repeat=2):
|
286
|
-
for a in [0,1]:
|
287
|
-
obt_so[2*i1+a,2*i2+a] = obt[i1,i2]
|
277
|
+
for a in [0, 1]:
|
278
|
+
obt_so[2 * i1 + a, 2 * i2 + a] = obt[i1, i2]
|
288
279
|
|
289
280
|
return obt_so
|
290
281
|
|
282
|
+
|
291
283
|
def convert_u_to_so(U):
|
292
|
-
|
284
|
+
"""
|
293
285
|
Converts unitary matrix to spin orbitals
|
294
|
-
|
286
|
+
"""
|
295
287
|
nf = U.shape[0]
|
296
288
|
n = 2 * U.shape[1]
|
297
289
|
U_so = np.zeros([nf, n, n])
|
298
290
|
for i in range(nf):
|
299
|
-
U_so[i
|
291
|
+
U_so[i, :, :] = obt_orb_to_so(U[i, :, :])
|
300
292
|
return U_so
|
301
293
|
|
302
|
-
|
303
|
-
|
294
|
+
|
295
|
+
def convert_tbts_to_frags(tbts, spin_orb=False):
|
296
|
+
"""
|
304
297
|
Converts two body terms to fermionic fragments.
|
305
|
-
|
298
|
+
"""
|
306
299
|
ops = []
|
307
300
|
nf = tbts.shape[0]
|
308
301
|
for i in range(nf):
|
309
|
-
ops.append(tbt_to_ferm(tbts[i
|
302
|
+
ops.append(tbt_to_ferm(tbts[i, :, :, :, :], spin_orb))
|
310
303
|
return ops
|
311
304
|
|
312
|
-
|
313
|
-
|
305
|
+
|
306
|
+
def symmetric(M, tol=1e-8, ret_op=False):
|
307
|
+
"""
|
314
308
|
if ret_op = False, checks whether a given matrix is symmetric.
|
315
309
|
if ret_op = True, returns the symmetrc form of the given matrix.
|
316
|
-
|
310
|
+
"""
|
317
311
|
M_res = np.tril(M) + np.triu(M.T, 1)
|
318
|
-
if ret_op
|
312
|
+
if not ret_op:
|
319
313
|
if np.sum(np.abs(M - M_res)) > tol:
|
320
314
|
return False
|
321
315
|
return True
|
322
316
|
else:
|
323
317
|
return M_res
|
324
318
|
|
319
|
+
|
325
320
|
def check_tbt_symmetry(tbt):
|
326
|
-
|
321
|
+
"""
|
327
322
|
Returns symmetric form of tbt.
|
328
|
-
|
323
|
+
"""
|
329
324
|
N = tbt.shape[0] ** 2
|
330
|
-
tbt_res = np.reshape(tbt, (N,N))
|
325
|
+
tbt_res = np.reshape(tbt, (N, N))
|
331
326
|
if not symmetric(tbt_res):
|
332
327
|
print("Non-symmetric two-body tensor as input for SVD routine, calculations might have errors...")
|
333
328
|
else:
|
334
|
-
tbt_res = symmetric(tbt_res, ret_op
|
329
|
+
tbt_res = symmetric(tbt_res, ret_op=True)
|
335
330
|
return tbt_res
|
336
331
|
|
332
|
+
|
337
333
|
def diagonalizing_tbt(tbt_res):
|
338
|
-
|
334
|
+
"""
|
339
335
|
Returns diagonal form and unitary rotation to diagonalize tbt_res
|
340
|
-
|
336
|
+
"""
|
341
337
|
print("Diagonalizing two-body tensor")
|
342
338
|
lamda, U = np.linalg.eig(tbt_res)
|
343
339
|
ind = np.argsort(np.abs(lamda))[::-1]
|
344
340
|
lamda = lamda[ind]
|
345
|
-
U = U[:,ind]
|
341
|
+
U = U[:, ind]
|
346
342
|
return lamda, U
|
347
343
|
|
348
|
-
|
349
|
-
|
344
|
+
|
345
|
+
def fragmentization(lamda, U, n, tol=1e-8):
|
346
|
+
"""
|
350
347
|
Returns fragments of tbt
|
351
|
-
|
348
|
+
"""
|
352
349
|
N = n**2
|
353
350
|
L_mats = []
|
354
351
|
flags = np.zeros(N)
|
355
352
|
|
356
353
|
for i in range(N):
|
357
354
|
if abs(lamda[i]) < tol:
|
358
|
-
print(
|
355
|
+
print(
|
356
|
+
"Truncating SVD for coefficients with magnitude smaller or equal to {}, using {} fragments".format(
|
357
|
+
abs(lamda[i]), (i)
|
358
|
+
)
|
359
|
+
)
|
359
360
|
break
|
360
|
-
cur_l = np.reshape(U[:, i], (n,n))
|
361
|
+
cur_l = np.reshape(U[:, i], (n, n))
|
361
362
|
if not symmetric(cur_l):
|
362
363
|
flags[i] = 1
|
363
364
|
else:
|
364
|
-
cur_l = symmetric(cur_l, ret_op
|
365
|
+
cur_l = symmetric(cur_l, ret_op=True)
|
365
366
|
L_mats.append(cur_l)
|
366
367
|
return L_mats, flags
|
367
368
|
|
368
369
|
|
369
|
-
def lr_decomp(tbt
|
370
|
-
|
370
|
+
def lr_decomp(tbt: np.array, spin_orb=True, tiny=1e-8):
|
371
|
+
"""
|
371
372
|
Singular Value Decomposition of the two-body tensor term
|
372
|
-
|
373
|
+
"""
|
373
374
|
print("Starting SVD routine")
|
374
375
|
n = tbt.shape[0]
|
375
376
|
|
@@ -379,46 +380,49 @@ def lr_decomp(tbt : np.array, spin_orb=True, tiny=1e-8):
|
|
379
380
|
num_ops = len(L_mats)
|
380
381
|
|
381
382
|
TBTS = np.zeros((num_ops, n, n, n, n))
|
382
|
-
CARTAN_TBTS = np.zeros((
|
383
|
-
U_ARR = np.zeros((num_ops, n,n))
|
383
|
+
CARTAN_TBTS = np.zeros((num_ops, n, n, n, n))
|
384
|
+
U_ARR = np.zeros((num_ops, n, n))
|
384
385
|
|
385
386
|
for i in range(num_ops):
|
386
387
|
if flags[i] == 0:
|
387
388
|
omega_l, U_l = np.linalg.eigh(L_mats[i])
|
388
389
|
|
389
|
-
tbt_svd_CSA = np.zeros((n,n,n,n))
|
390
|
+
tbt_svd_CSA = np.zeros((n, n, n, n))
|
390
391
|
|
391
392
|
for j in range(n):
|
392
|
-
for k in range(j,n):
|
393
|
-
tbt_svd_CSA[j,j,k,k] = omega_l[j]*omega_l[k]
|
394
|
-
tbt_svd_CSA[k,k,j,j] = tbt_svd_CSA[j,j,k,k]
|
395
|
-
|
393
|
+
for k in range(j, n):
|
394
|
+
tbt_svd_CSA[j, j, k, k] = omega_l[j] * omega_l[k]
|
395
|
+
tbt_svd_CSA[k, k, j, j] = tbt_svd_CSA[j, j, k, k]
|
396
396
|
|
397
397
|
tbt_svd_CSA = lamda[i] * tbt_svd_CSA
|
398
398
|
tbt_svd = unitary_cartan_rotation_from_matrix(U_l, tbt_svd_CSA)
|
399
|
-
TBTS[i
|
400
|
-
CARTAN_TBTS[i
|
399
|
+
TBTS[i, :, :, :, :] = tbt_svd
|
400
|
+
CARTAN_TBTS[i, :, :, :, :] = tbt_svd_CSA
|
401
401
|
else:
|
402
402
|
if np.sum(np.abs(L_mats[i] + L_mats[i].T)) > tiny:
|
403
|
-
raise TequilaException(
|
403
|
+
raise TequilaException(
|
404
|
+
"SVD operator {} if neither Hermitian or anti-Hermitian, cannot do double factorization into Hermitian fragment!".format(
|
405
|
+
i
|
406
|
+
)
|
407
|
+
)
|
404
408
|
|
405
409
|
temp_l = 1j * L_mats[i]
|
406
|
-
cur_l = (temp_l + temp_l.conj().T)/2
|
410
|
+
cur_l = (temp_l + temp_l.conj().T) / 2
|
407
411
|
omega_l, U_l = np.linalg.eigh(cur_l)
|
408
412
|
|
409
|
-
tbt_svd_CSA = np.zeros((n,n,n,n))
|
413
|
+
tbt_svd_CSA = np.zeros((n, n, n, n))
|
410
414
|
|
411
415
|
for j in range(n):
|
412
|
-
for k in range(j,n):
|
413
|
-
tbt_svd_CSA[j,j,k,k] = -omega_l[j]*omega_l[k]
|
414
|
-
tbt_svd_CSA[k,k,j,j] = tbt_svd_CSA[j,j,k,k]
|
416
|
+
for k in range(j, n):
|
417
|
+
tbt_svd_CSA[j, j, k, k] = -omega_l[j] * omega_l[k]
|
418
|
+
tbt_svd_CSA[k, k, j, j] = tbt_svd_CSA[j, j, k, k]
|
415
419
|
|
416
420
|
tbt_svd_CSA = lamda[i] * tbt_svd_CSA
|
417
421
|
tbt_svd = unitary_cartan_rotation_from_matrix(U_l, tbt_svd_CSA)
|
418
|
-
TBTS[i
|
419
|
-
CARTAN_TBTS[i
|
422
|
+
TBTS[i, :, :, :, :] = tbt_svd
|
423
|
+
CARTAN_TBTS[i, :, :, :, :] = tbt_svd_CSA
|
420
424
|
|
421
|
-
U_ARR[i
|
425
|
+
U_ARR[i, :, :] = U_l
|
422
426
|
print("Finished SVD routine")
|
423
427
|
|
424
428
|
L_ops = []
|
@@ -428,19 +432,22 @@ def lr_decomp(tbt : np.array, spin_orb=True, tiny=1e-8):
|
|
428
432
|
L_ops.append(lamda[i] * op_1d * op_1d)
|
429
433
|
return CARTAN_TBTS, TBTS, L_ops, U_ARR
|
430
434
|
|
431
|
-
|
432
|
-
|
435
|
+
|
436
|
+
def unitary_cartan_rotation_from_matrix(Umat, tbt: np.array):
|
437
|
+
"""
|
433
438
|
Rotates Cartan tbt using orbital rotation matrix U_mat
|
434
|
-
|
435
|
-
rotated_tbt = np.einsum(
|
439
|
+
"""
|
440
|
+
rotated_tbt = np.einsum("al,bl,cm,dm,llmm", Umat, Umat, Umat, Umat, tbt)
|
436
441
|
return rotated_tbt
|
437
442
|
|
443
|
+
|
438
444
|
def qubit_number(op):
|
439
|
-
|
445
|
+
"""
|
440
446
|
Returns number of qubits in the operator
|
441
|
-
|
447
|
+
"""
|
442
448
|
return count_qubits(op)
|
443
449
|
|
450
|
+
|
444
451
|
def get_occ_no(n_elec, n_qubits):
|
445
452
|
"""
|
446
453
|
Given some molecule, find the reference occupation number state.
|
@@ -449,21 +456,46 @@ def get_occ_no(n_elec, n_qubits):
|
|
449
456
|
Returns:
|
450
457
|
occ_no (str): Occupation no. vector.
|
451
458
|
"""
|
452
|
-
occ_no =
|
459
|
+
occ_no = "1" * n_elec + "0" * (n_qubits - n_elec)
|
453
460
|
|
454
461
|
return occ_no
|
455
462
|
|
463
|
+
|
456
464
|
def n_elec(mol):
|
457
|
-
|
465
|
+
"""
|
458
466
|
Given some molecule, find the reference occupation number state.
|
459
467
|
Args:
|
460
468
|
mol (str): H2, LiH, BeH2, H2O, NH3
|
461
469
|
Returns:
|
462
470
|
Number of electrons (int)
|
463
|
-
|
464
|
-
n_electrons = {
|
471
|
+
"""
|
472
|
+
n_electrons = {
|
473
|
+
"h2": 2,
|
474
|
+
"lih": 4,
|
475
|
+
"beh2": 6,
|
476
|
+
"h2o": 10,
|
477
|
+
"nh3": 10,
|
478
|
+
"n2": 14,
|
479
|
+
"hf": 10,
|
480
|
+
"ch4": 10,
|
481
|
+
"co": 14,
|
482
|
+
"h4": 4,
|
483
|
+
"ch2": 8,
|
484
|
+
"heh": 2,
|
485
|
+
"h6": 6,
|
486
|
+
"nh": 8,
|
487
|
+
"h3": 2,
|
488
|
+
"h4sq": 4,
|
489
|
+
"h2ost": 10,
|
490
|
+
"beh2st": 6,
|
491
|
+
"h2ost2": 10,
|
492
|
+
"beh2st2": 6,
|
493
|
+
"li2frco": 2,
|
494
|
+
"beh2frco": 4,
|
495
|
+
}
|
465
496
|
return n_electrons[mol.lower()]
|
466
497
|
|
498
|
+
|
467
499
|
def create_hamiltonian_in_subspace(indices, Hq, n_qubits):
|
468
500
|
"""
|
469
501
|
Given some basis states, create the Hamiltonian within the span of those basis states.
|
@@ -479,11 +511,11 @@ def create_hamiltonian_in_subspace(indices, Hq, n_qubits):
|
|
479
511
|
row_idx = []
|
480
512
|
col_idx = []
|
481
513
|
H_mat_elements = []
|
482
|
-
elements_sum = np.zeros((len(indices),len(indices)))
|
514
|
+
elements_sum = np.zeros((len(indices), len(indices)))
|
483
515
|
op_sum = QubitOperator.zero()
|
484
516
|
for prog, op in enumerate(Hq):
|
485
517
|
op_sum += op
|
486
|
-
if (prog + 1)%350 == 0 or prog == len(Hq.terms) - 1:
|
518
|
+
if (prog + 1) % 350 == 0 or prog == len(Hq.terms) - 1:
|
487
519
|
opspar = of.get_sparse_operator(op_sum, n_qubits)
|
488
520
|
op_sum = of.QubitOperator.zero()
|
489
521
|
for iidx, iindx in enumerate(indices):
|
@@ -495,9 +527,10 @@ def create_hamiltonian_in_subspace(indices, Hq, n_qubits):
|
|
495
527
|
row_idx.append(iidx)
|
496
528
|
col_idx.append(jidx)
|
497
529
|
H_mat_elements.append(elements_sum[iidx, jidx])
|
498
|
-
H_mat_sub = sp.sparse.coo_matrix((H_mat_elements, (row_idx, col_idx)), shape
|
530
|
+
H_mat_sub = sp.sparse.coo_matrix((H_mat_elements, (row_idx, col_idx)), shape=(subspace_dim, subspace_dim))
|
499
531
|
return H_mat_sub
|
500
532
|
|
533
|
+
|
501
534
|
def get_jw_cisd_basis_states(n_elec, n_qubits):
|
502
535
|
"""
|
503
536
|
Given some molecule, find the all BK basis vectors that correspond to occupation numbers that are achieved by single and double excitations.
|
@@ -511,6 +544,7 @@ def get_jw_cisd_basis_states(n_elec, n_qubits):
|
|
511
544
|
indices = get_jw_cisd_basis_states_wrap(ref_occ_nos, n_qubits)
|
512
545
|
return indices
|
513
546
|
|
547
|
+
|
514
548
|
def get_jw_cisd_basis_states_wrap(ref_occ_nos, n_qubits):
|
515
549
|
"""
|
516
550
|
Given some occupation number, find the all other occupation numbers that are achieved by single and double excitations.
|
@@ -522,27 +556,28 @@ def get_jw_cisd_basis_states_wrap(ref_occ_nos, n_qubits):
|
|
522
556
|
|
523
557
|
indices = [find_index(get_jw_basis_states(ref_occ_nos))]
|
524
558
|
for occidx, occ_orbitals in enumerate(ref_occ_nos):
|
525
|
-
if occ_orbitals ==
|
559
|
+
if occ_orbitals == "1":
|
526
560
|
annihilated_state = list(ref_occ_nos)
|
527
|
-
annihilated_state[occidx] =
|
528
|
-
#Singles
|
561
|
+
annihilated_state[occidx] = "0"
|
562
|
+
# Singles
|
529
563
|
for virtidx, virtual_orbs in enumerate(ref_occ_nos):
|
530
|
-
if virtual_orbs ==
|
564
|
+
if virtual_orbs == "0":
|
531
565
|
new_state = annihilated_state[:]
|
532
|
-
new_state[virtidx] =
|
533
|
-
indices.append(find_index(get_jw_basis_states(
|
534
|
-
#Doubles
|
535
|
-
for occ2idx in range(occidx +1, n_qubits):
|
536
|
-
if ref_occ_nos[occ2idx] ==
|
566
|
+
new_state[virtidx] = "1"
|
567
|
+
indices.append(find_index(get_jw_basis_states("".join(new_state))))
|
568
|
+
# Doubles
|
569
|
+
for occ2idx in range(occidx + 1, n_qubits):
|
570
|
+
if ref_occ_nos[occ2idx] == "1":
|
537
571
|
annihilated_state_double = new_state[:]
|
538
|
-
annihilated_state_double[occ2idx] =
|
539
|
-
for virt2idx in range(virtidx +1, n_qubits):
|
540
|
-
if ref_occ_nos[virt2idx] ==
|
572
|
+
annihilated_state_double[occ2idx] = "0"
|
573
|
+
for virt2idx in range(virtidx + 1, n_qubits):
|
574
|
+
if ref_occ_nos[virt2idx] == "0":
|
541
575
|
new_state_double = annihilated_state_double[:]
|
542
|
-
new_state_double[virt2idx] =
|
543
|
-
indices.append(find_index(get_jw_basis_states(
|
576
|
+
new_state_double[virt2idx] = "1"
|
577
|
+
indices.append(find_index(get_jw_basis_states("".join(new_state_double))))
|
544
578
|
return indices
|
545
579
|
|
580
|
+
|
546
581
|
def find_index(basis_state):
|
547
582
|
"""
|
548
583
|
Given some qubit/fermionic basis state, find the index of the a wavefunction that corresponds to that array.
|
@@ -554,10 +589,11 @@ def find_index(basis_state):
|
|
554
589
|
index = 0
|
555
590
|
n_qubits = len(basis_state)
|
556
591
|
for j in range(n_qubits):
|
557
|
-
index += int(basis_state[j])*2**(n_qubits - j - 1)
|
592
|
+
index += int(basis_state[j]) * 2 ** (n_qubits - j - 1)
|
558
593
|
|
559
594
|
return index
|
560
595
|
|
596
|
+
|
561
597
|
def get_jw_basis_states(occ_no_list):
|
562
598
|
"""
|
563
599
|
Implementation from arXiv:quant-ph/0003137 and https://doi.org/10.1021/acs.jctc.8b00450. Given some reference occupation no's in the fermionic space, find the corresponding BK basis state in the qubit space.
|
@@ -568,14 +604,15 @@ def get_jw_basis_states(occ_no_list):
|
|
568
604
|
"""
|
569
605
|
jw_list = []
|
570
606
|
for occ_no in occ_no_list:
|
571
|
-
qubit_state = np.array(list(occ_no), dtype
|
607
|
+
qubit_state = np.array(list(occ_no), dtype=int)
|
572
608
|
jw_list.append(qubit_state)
|
573
609
|
return jw_list
|
574
610
|
|
611
|
+
|
575
612
|
def expectation_value(op, psi, n, trunc=False):
|
576
|
-
|
613
|
+
"""
|
577
614
|
Calculates expectation of op over psi.
|
578
|
-
|
615
|
+
"""
|
579
616
|
opq = jordan_wigner(op)
|
580
617
|
if trunc is False:
|
581
618
|
e_val = expectation(get_sparse_operator(opq, n_qubits=n), psi)
|
@@ -583,86 +620,103 @@ def expectation_value(op, psi, n, trunc=False):
|
|
583
620
|
e_val = evu.op_ev_multiple_bases(opq, psi[0], psi[1], n)
|
584
621
|
return real_round(e_val)
|
585
622
|
|
586
|
-
|
587
|
-
|
623
|
+
|
624
|
+
def variance_value(op, psi, n, trunc=False, neg_tol=1e-7):
|
625
|
+
"""
|
588
626
|
Calculates variance of op over psi.
|
589
|
-
|
627
|
+
"""
|
590
628
|
opq = jordan_wigner(op)
|
591
629
|
if trunc is False:
|
592
630
|
var_val = variance(get_sparse_operator(opq, n_qubits=n), psi)
|
593
631
|
else:
|
594
|
-
var_val =
|
632
|
+
var_val = (
|
633
|
+
evu.op_ev_multiple_bases(opq * opq, psi[0], psi[1], n)
|
634
|
+
- evu.op_ev_multiple_bases(opq, psi[0], psi[1], n) ** 2
|
635
|
+
)
|
595
636
|
var_val = real_round(var_val)
|
596
637
|
if -neg_tol <= var_val < 0:
|
597
638
|
var_val = 0
|
598
639
|
if var_val < -neg_tol:
|
599
|
-
raise TequilaException(
|
640
|
+
raise TequilaException("Variance of an observable should not be negative")
|
600
641
|
return var_val
|
601
642
|
|
643
|
+
|
602
644
|
def real_round(x, tol=1e-10):
|
603
|
-
|
645
|
+
"""
|
604
646
|
Returns real part of complex numbers if the imaginary part is negligible.
|
605
|
-
|
606
|
-
if abs(x.imag)/abs(x) >= tol:
|
607
|
-
print(
|
647
|
+
"""
|
648
|
+
if abs(x.imag) / abs(x) >= tol:
|
649
|
+
print(
|
650
|
+
"Warning, rounding x={} to abs(x). Complex component is above tolerance (relative magnitude: {:.2f})".format(
|
651
|
+
x, abs(x.imag) / abs(x)
|
652
|
+
)
|
653
|
+
)
|
608
654
|
return abs(x)
|
609
655
|
return x.real
|
610
656
|
|
657
|
+
|
611
658
|
def get_E(psi, n, n_qubits, trunc=False):
|
612
|
-
|
659
|
+
"""
|
613
660
|
Returns the dictionary of one electron excitations over psi
|
614
|
-
|
661
|
+
"""
|
615
662
|
gEd = partial(get_E_dummy, psi, n, n_qubits, trunc)
|
616
663
|
with mp.Pool(mp.cpu_count()) as pool:
|
617
|
-
sq = pool.map(gEd, range(n
|
664
|
+
sq = pool.map(gEd, range(n**2))
|
618
665
|
pool.close()
|
619
666
|
pool.join()
|
620
667
|
return np.array(sq)
|
621
668
|
|
669
|
+
|
622
670
|
def get_E_dummy(psi, n, n_qubits, trunc, ind):
|
623
|
-
|
671
|
+
"""
|
624
672
|
Returns the expectation value of one electron excitation over psi
|
625
|
-
|
673
|
+
"""
|
626
674
|
j = ind % n
|
627
675
|
i = ind // n
|
628
676
|
op = FermionOperator(((i, 1), (j, 0)), coefficient=1.0)
|
629
677
|
return expectation_value(op, psi, n_qubits, trunc)
|
630
678
|
|
679
|
+
|
631
680
|
def get_EE(psi, n, n_qubits, trunc=False):
|
632
|
-
|
681
|
+
"""
|
633
682
|
Returns the dictionary value of two electron excitations over psi
|
634
|
-
|
683
|
+
"""
|
635
684
|
gEEd = partial(get_EE_dummy, psi, n, n_qubits, trunc)
|
636
685
|
with mp.Pool(mp.cpu_count()) as pool:
|
637
|
-
sq = pool.map(gEEd, range(n
|
686
|
+
sq = pool.map(gEEd, range(n**4))
|
638
687
|
pool.close()
|
639
688
|
pool.join()
|
640
689
|
return np.array(sq)
|
641
690
|
|
691
|
+
|
642
692
|
def get_EE_dummy(psi, n, n_qubits, trunc, ind):
|
643
|
-
|
693
|
+
"""
|
644
694
|
Returns the expectation value of two electron excitation over psi
|
645
|
-
|
695
|
+
"""
|
646
696
|
l = ind % n
|
647
|
-
k = ind % n
|
648
|
-
j = ind % n
|
649
|
-
i = ind // n
|
697
|
+
k = ind % n**2 // n
|
698
|
+
j = ind % n**3 // n**2
|
699
|
+
i = ind // n**3
|
650
700
|
op = FermionOperator(((i, 1), (j, 0), (k, 1), (l, 0)), coefficient=1.0)
|
651
701
|
return expectation_value(op, psi, n_qubits, trunc=trunc)
|
652
702
|
|
703
|
+
|
653
704
|
def reorganize(n, ev_dict_E, ev_dict_EE):
|
654
|
-
|
705
|
+
"""
|
655
706
|
-----------
|
656
|
-
|
657
|
-
ev_dict_ob_ob = np.zeros([n,n,n,n])
|
707
|
+
"""
|
708
|
+
ev_dict_ob_ob = np.zeros([n, n, n, n])
|
658
709
|
for i, j, k, l in product(range(n), repeat=4):
|
659
|
-
ev_dict_ob_ob[i, j, k, l] =
|
710
|
+
ev_dict_ob_ob[i, j, k, l] = (
|
711
|
+
ev_dict_EE[(n**3) * (i) + (n**2) * (j) + n * (k) + l] - ev_dict_E[n * (i) + j] * ev_dict_E[n * (k) + l]
|
712
|
+
)
|
660
713
|
return ev_dict_ob_ob
|
661
714
|
|
715
|
+
|
662
716
|
def compute_covk(op1, op2, psi, n_qubits, trunc=False):
|
663
|
-
|
717
|
+
"""
|
664
718
|
---------------------
|
665
|
-
|
719
|
+
"""
|
666
720
|
cko = partial(covk_one, op1, op2, psi, n_qubits, trunc)
|
667
721
|
length = len(op1)
|
668
722
|
with mp.Pool(mp.cpu_count()) as pool:
|
@@ -671,17 +725,19 @@ def compute_covk(op1, op2, psi, n_qubits, trunc=False):
|
|
671
725
|
pool.join()
|
672
726
|
return np.array(covs)
|
673
727
|
|
728
|
+
|
674
729
|
def covk_one(op1, op2, psi, n_qubits, trunc, ind):
|
675
|
-
|
730
|
+
"""
|
676
731
|
----------------------
|
677
|
-
|
732
|
+
"""
|
678
733
|
cov = covariance(op1[ind], op2, psi, n_qubits, trunc) + covariance(op2, op1[ind], psi, n_qubits, trunc)
|
679
734
|
return cov
|
680
735
|
|
736
|
+
|
681
737
|
def covariance(op1, op2, psi, n_qubits, trunc):
|
682
|
-
|
738
|
+
"""
|
683
739
|
Returns covariance between op1 and op2
|
684
|
-
|
740
|
+
"""
|
685
741
|
op1q = of.jordan_wigner(op1)
|
686
742
|
op2q = of.jordan_wigner(op2)
|
687
743
|
|
@@ -698,18 +754,18 @@ def covariance(op1, op2, psi, n_qubits, trunc):
|
|
698
754
|
exp_op2 = evu.op_ev_multiple_bases(op2q, psi[0], psi[1], n_qubits)
|
699
755
|
return cross - exp_op1 * exp_op2
|
700
756
|
|
701
|
-
def covariance_ob_ob(obt1, obt2, ev_dict_ob_ob):
|
702
|
-
'''
|
703
757
|
|
704
|
-
|
758
|
+
def covariance_ob_ob(obt1, obt2, ev_dict_ob_ob):
|
759
|
+
""" """
|
705
760
|
my_cov = 0.0 + 0.0j
|
706
|
-
my_cov = np.einsum(
|
761
|
+
my_cov = np.einsum("ij,kl,ijkl", obt1, obt2, ev_dict_ob_ob)
|
707
762
|
return my_cov
|
708
763
|
|
764
|
+
|
709
765
|
def fff_1_iter(obt, tbts, var, c0, ck, fff_var):
|
710
|
-
|
766
|
+
"""
|
711
767
|
Computes a single iteration of FFF optimization (arXiv:2208.14490v3 - Section 2.2)
|
712
|
-
|
768
|
+
"""
|
713
769
|
m0 = compute_meas_alloc(var, obt, tbts, fff_var.nq, fff_var.mix)
|
714
770
|
lambda_opt = compute_lambda_optimum(fff_var.coo, c0, ck, m0, fff_var.nf, fff_var.n)
|
715
771
|
new_obt, new_tbts = modify_ops(obt, tbts, lambda_opt, fff_var.o_t, fff_var.nf, fff_var.n, fff_var.uops)
|
@@ -717,10 +773,11 @@ def fff_1_iter(obt, tbts, var, c0, ck, fff_var):
|
|
717
773
|
new_c0, new_ck = modify_c(fff_var.coo, c0, ck, lambda_opt, fff_var.nf, fff_var.n)
|
718
774
|
return new_obt, new_tbts, new_var, new_c0, new_ck
|
719
775
|
|
776
|
+
|
720
777
|
def compute_lambda_optimum(Coo, C0, Ck, m0, nf, n):
|
721
|
-
|
778
|
+
"""
|
722
779
|
Computes the optimum value of lamda
|
723
|
-
|
780
|
+
"""
|
724
781
|
symCoo = np.zeros_like(Coo)
|
725
782
|
diagCoo = np.zeros_like(Coo)
|
726
783
|
mat = np.zeros_like(Coo)
|
@@ -728,63 +785,65 @@ def compute_lambda_optimum(Coo, C0, Ck, m0, nf, n):
|
|
728
785
|
nall = Coo.shape[0]
|
729
786
|
for i in range(nall):
|
730
787
|
for j in range(i, nall):
|
731
|
-
symCoo[i,j] = Coo[i,j] + Coo[j,i]
|
732
|
-
symCoo[j,i] = symCoo[i,j]
|
788
|
+
symCoo[i, j] = Coo[i, j] + Coo[j, i]
|
789
|
+
symCoo[j, i] = symCoo[i, j]
|
733
790
|
for k in range(nf):
|
734
791
|
for p in range(n):
|
735
792
|
for q in range(n):
|
736
793
|
ind1 = n * k + p
|
737
794
|
ind2 = n * k + q
|
738
|
-
diagCoo[ind1, ind2] = symCoo[ind1, ind2]/m0[k + 1]
|
739
|
-
mat = (1/m0[0]) * symCoo + diagCoo
|
795
|
+
diagCoo[ind1, ind2] = symCoo[ind1, ind2] / m0[k + 1]
|
796
|
+
mat = (1 / m0[0]) * symCoo + diagCoo
|
740
797
|
for k in range(nf):
|
741
798
|
for p in range(n):
|
742
799
|
ind = n * k + p
|
743
|
-
vec[ind] = Ck[ind]/m0[k + 1]
|
744
|
-
vec = vec - (1/m0[0]) * C0
|
800
|
+
vec[ind] = Ck[ind] / m0[k + 1]
|
801
|
+
vec = vec - (1 / m0[0]) * C0
|
745
802
|
lam = solve_Axb(mat, vec)
|
746
803
|
return lam
|
747
804
|
|
805
|
+
|
748
806
|
def var_avg(n_qubits):
|
749
|
-
"""
|
750
|
-
|
751
|
-
|
807
|
+
"""Haar average variance of a Pauli product."""
|
808
|
+
return 1.0 - 1.0 / (2**n_qubits + 1)
|
809
|
+
|
752
810
|
|
753
811
|
def get_avg_variances(ops, n_qubits):
|
754
|
-
"""
|
755
|
-
"""
|
812
|
+
"""Compute Haar average variances of ops."""
|
756
813
|
return np.array(list(map(lambda x: get_avg_variance(x, n_qubits), ops)))
|
757
814
|
|
815
|
+
|
758
816
|
def get_avg_variance(op, n_qubits):
|
759
|
-
"""
|
760
|
-
"""
|
817
|
+
"""Compute Haar average variance of op."""
|
761
818
|
pws = get_pauliword_list(jordan_wigner(op))
|
762
819
|
c_sq = np.sum(list(map(lambda x: np.abs(evu.get_pauli_word_coefficient(x)) ** 2, pws)))
|
763
820
|
return c_sq * var_avg(n_qubits)
|
764
821
|
|
822
|
+
|
765
823
|
def get_pauliword_list(H: QubitOperator):
|
766
|
-
"""Obtain a list of pauli words in H.
|
767
|
-
"""
|
824
|
+
"""Obtain a list of pauli words in H."""
|
768
825
|
pws = []
|
769
826
|
for pw, val in H.terms.items():
|
770
|
-
if len(pw) != 0:
|
827
|
+
if len(pw) != 0:
|
828
|
+
pws.append(QubitOperator(term=pw, coefficient=val))
|
771
829
|
return pws
|
772
830
|
|
773
831
|
|
774
832
|
def solve_Axb(A, b):
|
775
|
-
|
833
|
+
"""
|
776
834
|
Provides asolution frox x, Ax = b.
|
777
|
-
|
835
|
+
"""
|
778
836
|
b_tmp = np.zeros((len(b), 1))
|
779
|
-
b_tmp[:,0] = b
|
837
|
+
b_tmp[:, 0] = b
|
780
838
|
sol = np.linalg.lstsq(A, b_tmp, rcond=None)
|
781
839
|
x0 = sol[0]
|
782
840
|
return x0.T[0]
|
783
841
|
|
842
|
+
|
784
843
|
def modify_ops(obt, tbts, lambda_opt, O_t, nf, n, uops):
|
785
|
-
|
844
|
+
"""
|
786
845
|
Computes new values of obt and tbt after a single FFF iteration
|
787
|
-
|
846
|
+
"""
|
788
847
|
ntmp = obt.shape[0]
|
789
848
|
new_tbts = np.zeros([nf, ntmp, ntmp, ntmp, ntmp])
|
790
849
|
sig_o = np.zeros([ntmp, ntmp])
|
@@ -794,37 +853,40 @@ def modify_ops(obt, tbts, lambda_opt, O_t, nf, n, uops):
|
|
794
853
|
ind = n * i + j
|
795
854
|
obt_tmp += lambda_opt[ind] * O_t[i, j, :, :]
|
796
855
|
sig_o += obt_tmp
|
797
|
-
new_tbts[i
|
856
|
+
new_tbts[i, :, :, :, :] = tbts[i, :, :, :, :] - my_obt_to_tbt(obt_tmp, uops[i, :, :])
|
798
857
|
new_obt = obt + sig_o
|
799
858
|
return new_obt, new_tbts
|
800
859
|
|
860
|
+
|
801
861
|
def my_obt_to_tbt(obt, uop):
|
802
|
-
|
862
|
+
"""
|
803
863
|
Converts the repartioned one body term into two body terms.
|
804
|
-
|
864
|
+
"""
|
805
865
|
nsize = obt.shape[0]
|
806
866
|
rot_obt = np.zeros([nsize, nsize])
|
807
867
|
tbt_from_obt = np.zeros([nsize, nsize, nsize, nsize])
|
808
|
-
rot_obt = np.einsum(
|
809
|
-
if np.sum(np.abs(np.diag(np.diag(rot_obt)) - rot_obt))
|
868
|
+
rot_obt = np.einsum("pa,kb,pk", np.conjugate(uop), uop, obt)
|
869
|
+
if np.sum(np.abs(np.diag(np.diag(rot_obt)) - rot_obt)) > 1e-5:
|
810
870
|
print("Warning:", np.sum(np.abs(np.diag(np.diag(rot_obt)) - rot_obt)))
|
811
|
-
tbt_from_obt = np.einsum(
|
871
|
+
tbt_from_obt = np.einsum("al,bl,cl,dl,ll", uop, np.conjugate(uop), uop, np.conjugate(uop), rot_obt)
|
812
872
|
return tbt_from_obt
|
813
873
|
|
874
|
+
|
814
875
|
def modify_var(var, coo, c0, ck, lam, n, tol=1e-10):
|
815
|
-
|
876
|
+
"""
|
816
877
|
Computes the variances of the repartitioned fragments
|
817
|
-
|
878
|
+
"""
|
818
879
|
nvar = len(var)
|
819
880
|
new_var = np.zeros(len(var))
|
820
881
|
delta_var1 = 0.0
|
821
882
|
for l1 in range(len(lam)):
|
822
883
|
for l2 in range(len(lam)):
|
823
|
-
delta_var1 += lam[l1] * lam[l2] * coo[l1,l2]
|
884
|
+
delta_var1 += lam[l1] * lam[l2] * coo[l1, l2]
|
824
885
|
delta_var1 += lam[l1] * c0[l1]
|
825
886
|
new_var[0] = var[0] + delta_var1
|
826
|
-
if new_var[0] < tol:
|
827
|
-
|
887
|
+
if new_var[0] < tol:
|
888
|
+
new_var[0] = 0.0
|
889
|
+
for k in range(nvar - 1):
|
828
890
|
delta_var_k = 0.0
|
829
891
|
for p in range(n):
|
830
892
|
ind1 = n * k + p
|
@@ -832,14 +894,16 @@ def modify_var(var, coo, c0, ck, lam, n, tol=1e-10):
|
|
832
894
|
ind2 = n * k + q
|
833
895
|
delta_var_k += lam[ind1] * lam[ind2] * coo[ind1, ind2]
|
834
896
|
delta_var_k -= lam[ind1] * ck[ind1]
|
835
|
-
new_var[k+1] = var[k+1] + delta_var_k
|
836
|
-
if new_var[k+1] < tol:
|
897
|
+
new_var[k + 1] = var[k + 1] + delta_var_k
|
898
|
+
if new_var[k + 1] < tol:
|
899
|
+
new_var[k + 1] = 0.0
|
837
900
|
return new_var
|
838
901
|
|
902
|
+
|
839
903
|
def modify_c(coo, c0, ck, lam, nf, n):
|
840
|
-
|
904
|
+
"""
|
841
905
|
Computes the covariances of the repartitioned fragments
|
842
|
-
|
906
|
+
"""
|
843
907
|
new_c0 = np.copy(c0)
|
844
908
|
new_ck = np.copy(ck)
|
845
909
|
for ind1 in range(len(lam)):
|
@@ -853,28 +917,30 @@ def modify_c(coo, c0, ck, lam, nf, n):
|
|
853
917
|
new_ck[ind1] -= coo[ind1, ind2] * lam[ind2] + coo[ind2, ind1] * lam[ind2]
|
854
918
|
return new_c0, new_ck
|
855
919
|
|
920
|
+
|
856
921
|
def compute_meas_alloc(varbs, obt=None, tbts=None, n_qubits=None, mix=0.0):
|
857
|
-
|
922
|
+
"""
|
858
923
|
Computes the measurement allocations based on the variances of repartitioned fragments.
|
859
|
-
|
924
|
+
"""
|
860
925
|
if mix > 1e-6:
|
861
926
|
all_ops = [obt_to_ferm(obt, True)]
|
862
927
|
ops = convert_tbts_to_frags(tbts, True)
|
863
928
|
for i in range(len(ops)):
|
864
929
|
all_ops.append(ops[i])
|
865
930
|
avg_vars = get_avg_variances(all_ops, n_qubits)
|
866
|
-
vtmp = mix * avg_vars + (1-mix) * varbs
|
931
|
+
vtmp = mix * avg_vars + (1 - mix) * varbs
|
867
932
|
else:
|
868
933
|
vtmp = varbs
|
869
934
|
sqrt_vars = np.sqrt(vtmp)
|
870
|
-
meas_alloc = sqrt_vars/np.sum(sqrt_vars)
|
935
|
+
meas_alloc = sqrt_vars / np.sum(sqrt_vars)
|
871
936
|
for i in range(len(meas_alloc)):
|
872
937
|
if meas_alloc[i] < 1e-6:
|
873
938
|
meas_alloc[i] = 1e-6
|
874
|
-
return np.real(
|
939
|
+
return np.real(meas_alloc / np.sum(meas_alloc))
|
940
|
+
|
875
941
|
|
876
942
|
def depth_eff_order_mf(N):
|
877
|
-
|
943
|
+
"""
|
878
944
|
Returns index ordering for linear depth circuit
|
879
945
|
|
880
946
|
For example N = 6 gives elimination order
|
@@ -884,51 +950,52 @@ def depth_eff_order_mf(N):
|
|
884
950
|
[ 3. 8. 12. 0. 0. 0.]
|
885
951
|
[ 2. 6. 11. 14. 0. 0.]
|
886
952
|
[ 1. 4. 9. 13. 15. 0.]
|
887
|
-
|
953
|
+
"""
|
888
954
|
l = []
|
889
|
-
for c in range(0, N-1):
|
955
|
+
for c in range(0, N - 1):
|
890
956
|
for r in range(1, N):
|
891
957
|
if r - c > 0:
|
892
|
-
l.append([r, c, 2*c - r + N])
|
958
|
+
l.append([r, c, 2 * c - r + N])
|
893
959
|
l.sort(key=lambda x: x[2])
|
894
960
|
return [(a[0], a[1]) for a in l]
|
895
961
|
|
896
|
-
|
897
|
-
|
962
|
+
|
963
|
+
def get_orb_rot(U, qubit_list=[], method="short", tol=1e-12):
|
964
|
+
"""
|
898
965
|
Construct sequence of orbital rotations that implement mean-field unitary given by NxN unitary U
|
899
966
|
Currently supported only for real U
|
900
|
-
|
901
|
-
|
967
|
+
"""
|
968
|
+
|
902
969
|
N = len(U)
|
903
970
|
C = tq.QCircuit()
|
904
|
-
|
971
|
+
|
905
972
|
if qubit_list == []:
|
906
973
|
qubit_list = list(range(N))
|
907
|
-
|
908
|
-
assert len(qubit_list) >= len(U),
|
909
|
-
|
974
|
+
|
975
|
+
assert len(qubit_list) >= len(U), "Insufficient qubits for orbital rotation" # check if sufficient qubits
|
976
|
+
|
910
977
|
U[abs(U) < tol] = 0
|
911
978
|
|
912
|
-
if method ==
|
979
|
+
if method == "naive":
|
913
980
|
theta_list, phi_list = given_rotation(U, tol)
|
914
|
-
elif method ==
|
981
|
+
elif method == "short":
|
915
982
|
ordering = depth_eff_order_mf(N)
|
916
983
|
theta_list, phi_list = given_rotation(U, tol, ordering)
|
917
|
-
|
918
|
-
#filter
|
984
|
+
|
985
|
+
# filter
|
919
986
|
theta_list_new = []
|
920
987
|
for i, theta in enumerate(theta_list):
|
921
|
-
if abs(theta[0] % (2*np.pi)) > tol:
|
988
|
+
if abs(theta[0] % (2 * np.pi)) > tol:
|
922
989
|
theta_list_new.append(theta)
|
923
|
-
|
990
|
+
|
924
991
|
phi_list_new = []
|
925
992
|
for i, phi in enumerate(phi_list):
|
926
993
|
if abs(phi[0]) > tol:
|
927
994
|
phi_list_new.append(phi)
|
928
|
-
|
995
|
+
|
929
996
|
for phi in phi_list_new:
|
930
997
|
C += n_rotation(qubit_list[phi[1]], phi[0])
|
931
|
-
|
998
|
+
|
932
999
|
gates = []
|
933
1000
|
for theta in theta_list_new:
|
934
1001
|
gates.append(orbital_rotation(qubit_list[theta[1]], qubit_list[theta[2]], -theta[0]))
|
@@ -938,25 +1005,32 @@ def get_orb_rot(U, qubit_list = [], method = 'short', tol = 1e-12):
|
|
938
1005
|
C += gate
|
939
1006
|
return C
|
940
1007
|
|
1008
|
+
|
941
1009
|
def orbital_rotation(i, j, theta):
|
942
|
-
|
1010
|
+
"""
|
943
1011
|
Implements exp(theta(a^_i a_j - a^_j a_i))
|
944
1012
|
Right now restricted to |i-j| <= 1 and jordan wigner transform.
|
945
|
-
|
946
|
-
if abs(i-j) <= 1:
|
947
|
-
return
|
1013
|
+
"""
|
1014
|
+
if abs(i - j) <= 1:
|
1015
|
+
return (
|
1016
|
+
tq.gates.CNOT(control=i, target=j)
|
1017
|
+
+ tq.gates.Ry(angle=2 * theta, target=i, control=j)
|
1018
|
+
+ tq.gates.CNOT(control=i, target=j)
|
1019
|
+
)
|
1020
|
+
|
948
1021
|
|
949
1022
|
def n_rotation(i, phi):
|
950
|
-
return tq.gates.Rz(angle
|
1023
|
+
return tq.gates.Rz(angle=phi, target=i)
|
951
1024
|
|
952
|
-
|
953
|
-
|
1025
|
+
|
1026
|
+
def given_rotation(U, tol=1e-12, ordering=None):
|
1027
|
+
"""
|
954
1028
|
Decomposes the Unitary into a set of Rz by angle phi and Givens Rotations by angle theta.
|
955
1029
|
Input:
|
956
1030
|
U (np.array): Rotation matrix
|
957
1031
|
tol: tolerance for U elements
|
958
|
-
|
959
|
-
|
1032
|
+
"""
|
1033
|
+
|
960
1034
|
U[abs(U) < tol] = 0
|
961
1035
|
n = U.shape[0]
|
962
1036
|
|
@@ -964,33 +1038,34 @@ def given_rotation(U, tol = 1e-12, ordering = None):
|
|
964
1038
|
phi = []
|
965
1039
|
if ordering is None:
|
966
1040
|
for c in range(n):
|
967
|
-
for r in range(n-1, c, -1):
|
968
|
-
t = np.arctan2(-U[r,c], U[r-1,c])
|
969
|
-
theta.append((t, r, r-1))
|
970
|
-
|
971
|
-
g = givens_matrix(n,r,r-1,t)
|
1041
|
+
for r in range(n - 1, c, -1):
|
1042
|
+
t = np.arctan2(-U[r, c], U[r - 1, c])
|
1043
|
+
theta.append((t, r, r - 1))
|
1044
|
+
|
1045
|
+
g = givens_matrix(n, r, r - 1, t)
|
972
1046
|
U = np.dot(g, U)
|
973
1047
|
else:
|
974
1048
|
for r, c in ordering:
|
975
|
-
t = np.arctan2(-U[r,c], U[r-1,c])
|
976
|
-
theta.append((t, r, r-1))
|
977
|
-
|
978
|
-
g = givens_matrix(n,r,r-1,t)
|
1049
|
+
t = np.arctan2(-U[r, c], U[r - 1, c])
|
1050
|
+
theta.append((t, r, r - 1))
|
1051
|
+
|
1052
|
+
g = givens_matrix(n, r, r - 1, t)
|
979
1053
|
U = np.dot(g, U)
|
980
|
-
|
1054
|
+
|
981
1055
|
for i in range(n):
|
982
|
-
ph = np.angle(U[i,i])
|
1056
|
+
ph = np.angle(U[i, i])
|
983
1057
|
phi.append((ph, i))
|
984
|
-
|
1058
|
+
|
985
1059
|
return theta, phi
|
986
1060
|
|
987
|
-
|
988
|
-
|
1061
|
+
|
1062
|
+
def givens_matrix(n, p, q, theta): # verified
|
1063
|
+
"""
|
989
1064
|
Returns the n dimension givens rotation matrix by theta between rows p and q.
|
990
|
-
|
1065
|
+
"""
|
991
1066
|
g = np.eye(n)
|
992
|
-
g[p,p] = np.cos(theta)
|
993
|
-
g[q,q] = np.cos(theta)
|
994
|
-
g[p,q] = np.sin(theta)
|
995
|
-
g[q,p] = -
|
1067
|
+
g[p, p] = np.cos(theta)
|
1068
|
+
g[q, q] = np.cos(theta)
|
1069
|
+
g[p, q] = np.sin(theta)
|
1070
|
+
g[q, p] = -np.sin(theta)
|
996
1071
|
return g
|