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
@@ -2,37 +2,41 @@ import numpy as np
|
|
2
2
|
from tequila.grouping.binary_utils import sorted_insertion_grouping, term_commutes_with_group
|
3
3
|
from copy import deepcopy
|
4
4
|
|
5
|
+
|
5
6
|
class OverlappingAuxiliary:
|
6
|
-
|
7
|
+
"""
|
7
8
|
Class required for passing cov_dict and number of iterations to
|
8
9
|
OverlappingGroups. Eventually, this may also be used for building cov_dict from
|
9
10
|
approximate wavefunction if the user provides only the latter.
|
10
|
-
|
11
|
+
"""
|
12
|
+
|
11
13
|
def __init__(self, cov_dict, n_iter=5):
|
12
14
|
self.cov_dict = cov_dict
|
13
15
|
self.n_iter = n_iter
|
14
16
|
|
17
|
+
|
15
18
|
class OverlappingGroupsWoFixed:
|
16
|
-
|
17
|
-
In the overlapping grouping methods, the same Pauli string term can be grouped into
|
18
|
-
multiple different fragments. However, for this procedure to reduce the total number of
|
19
|
-
required measurements, the coefficients in different fragments have to be optimized with
|
19
|
+
"""
|
20
|
+
In the overlapping grouping methods, the same Pauli string term can be grouped into
|
21
|
+
multiple different fragments. However, for this procedure to reduce the total number of
|
22
|
+
required measurements, the coefficients in different fragments have to be optimized with
|
20
23
|
the constraint that the sum of the fragments equal the original operator.
|
21
24
|
This Class is required for this optimization process. The overlapping grouping methods
|
22
25
|
starts from some nonoverlapping groups N_alpha; Pauli strings are then added to several
|
23
|
-
compatible fragments with coefficients c_alpha. Because the nonoverlapping groups sum to
|
26
|
+
compatible fragments with coefficients c_alpha. Because the nonoverlapping groups sum to
|
24
27
|
the original operator, the sum of coefficients c_alpha for a given Pauli string has to be
|
25
28
|
zero. This is ensured by fixing one of these c_alpha's as -(sum over the rest of c_alpha).
|
26
29
|
See Eq. (14) of arXiv: 2201.01471 (2022).
|
27
|
-
|
30
|
+
"""
|
31
|
+
|
28
32
|
def __init__(self, o_groups, o_terms, term_exists_in):
|
29
33
|
def exclude_fixed_coeffs(o_groups, o_terms, term_exists_in):
|
30
|
-
|
34
|
+
"""
|
31
35
|
Remove fixed coefficients from o_groups and term_exists_in.
|
32
36
|
These fixed coefficients are determined from other Pauli coefficients in order that
|
33
37
|
the condition sum_alpha c_alpha = 0 is satisfied [see comment for OverlappingGroupsWoFixed
|
34
38
|
and Eq. (14) of arXiv: 2201.01471 (2022)].
|
35
|
-
|
39
|
+
"""
|
36
40
|
fixed_grp = []
|
37
41
|
o_groups_wo_fixed = deepcopy(o_groups)
|
38
42
|
term_exists_in_wo_fixed = deepcopy(term_exists_in)
|
@@ -45,10 +49,10 @@ class OverlappingGroupsWoFixed:
|
|
45
49
|
return fixed_grp, o_groups_wo_fixed, term_exists_in_wo_fixed, n_coeff_grp, init_idx
|
46
50
|
|
47
51
|
def get_term_idxs(o_terms, o_groups_wo_fixed, term_exists_in_wo_fixed, init_idx):
|
48
|
-
|
52
|
+
"""
|
49
53
|
Obtain a dictionary (over i) of dictionaries (over alpha) containing
|
50
54
|
the index corresponding to a Pauli coefficient c_{i}^{alpha}.
|
51
|
-
|
55
|
+
"""
|
52
56
|
term_idxs = {}
|
53
57
|
for term_idx, term in enumerate(o_terms):
|
54
58
|
cur_idxs = {}
|
@@ -57,55 +61,61 @@ class OverlappingGroupsWoFixed:
|
|
57
61
|
term_idxs[term.binary_tuple()] = cur_idxs
|
58
62
|
return term_idxs
|
59
63
|
|
60
|
-
self.fixed_grp, self.o_groups, self.term_exists_in, self.n_coeff_grp, self.init_idx
|
61
|
-
|
64
|
+
self.fixed_grp, self.o_groups, self.term_exists_in, self.n_coeff_grp, self.init_idx = exclude_fixed_coeffs(
|
65
|
+
o_groups, o_terms, term_exists_in
|
66
|
+
)
|
62
67
|
self.term_idxs = get_term_idxs(o_terms, self.o_groups, self.term_exists_in, self.init_idx)
|
63
68
|
return None
|
64
69
|
|
70
|
+
|
65
71
|
def get_cov(term1, term2, cov_dict):
|
66
|
-
|
72
|
+
"""
|
67
73
|
Return the covariance between Pauli string term 1 and 2 from a dictionary
|
68
74
|
under the assumption that Pauli strings term1 and term2 commute.
|
69
|
-
|
75
|
+
"""
|
70
76
|
if (term1.binary_tuple(), term2.binary_tuple()) in cov_dict:
|
71
77
|
return cov_dict[(term1.binary_tuple(), term2.binary_tuple())]
|
72
78
|
if (term2.binary_tuple(), term1.binary_tuple()) in cov_dict:
|
73
79
|
return cov_dict[(term2.binary_tuple(), term1.binary_tuple())]
|
74
80
|
|
81
|
+
|
75
82
|
def cov_term_w_group(term, group, cov_dict):
|
76
|
-
|
83
|
+
"""
|
77
84
|
Compute covariance between a Pauli string term and a group of Pauli strings.
|
78
|
-
|
85
|
+
"""
|
79
86
|
cov = 0.0
|
80
87
|
for grp_term in group:
|
81
88
|
cov += grp_term.get_coeff() * get_cov(term, grp_term, cov_dict)
|
82
89
|
return cov
|
83
90
|
|
91
|
+
|
84
92
|
def get_opt_sample_size(groups, cov_dict):
|
85
|
-
|
93
|
+
"""
|
86
94
|
Allocate sample_size optimally based on variance.
|
87
|
-
|
95
|
+
"""
|
88
96
|
weights = np.zeros(len(groups))
|
89
97
|
for idx, group in enumerate(groups):
|
90
|
-
cur_var = 0.
|
98
|
+
cur_var = 0.0
|
91
99
|
for term1 in group:
|
92
100
|
for term2 in group:
|
93
101
|
cur_var += term1.coeff * term2.coeff * get_cov(term1, term2, cov_dict)
|
94
102
|
weights[idx] = np.sqrt(np.real(cur_var))
|
95
103
|
return weights / np.sum(weights)
|
96
104
|
|
105
|
+
|
97
106
|
class OverlappingGroups:
|
98
|
-
|
107
|
+
"""
|
99
108
|
Class required for performing overlapping grouping techniques:
|
100
|
-
coefficient splitting (implemented), ghost paulis (todo), and iterative
|
101
|
-
measurement allocation (todo). Unlike nonoverlapping grouping methods, in the overlapping methods,
|
109
|
+
coefficient splitting (implemented), ghost paulis (todo), and iterative
|
110
|
+
measurement allocation (todo). Unlike nonoverlapping grouping methods, in the overlapping methods,
|
102
111
|
the same Pauli string term can appear in multiple compatible groups. The optimization of the associated
|
103
|
-
Pauli string coefficients is performed using the estimated covariances from cov_dict.
|
104
|
-
|
112
|
+
Pauli string coefficients is performed using the estimated covariances from cov_dict.
|
113
|
+
"""
|
114
|
+
|
105
115
|
def __init__(self, no_groups, o_terms, term_exists_in):
|
106
|
-
self.no_groups = no_groups
|
107
|
-
self.o_terms = o_terms
|
108
|
-
self.term_exists_in = term_exists_in
|
116
|
+
self.no_groups = no_groups # Non-overlapping groups: required as a starting point.
|
117
|
+
self.o_terms = o_terms # List of Pauli products that are compatible in multiple groups.
|
118
|
+
self.term_exists_in = term_exists_in # List of indices indicating where overlapping Pauli products appear.
|
109
119
|
self.o_groups = [[] for i in range(len(no_groups))]
|
110
120
|
for idx, term in enumerate(o_terms):
|
111
121
|
for grup_idx in term_exists_in[idx]:
|
@@ -114,20 +124,22 @@ class OverlappingGroups:
|
|
114
124
|
return None
|
115
125
|
|
116
126
|
@classmethod
|
117
|
-
def init_from_binary_terms(cls, terms, condition=
|
118
|
-
|
127
|
+
def init_from_binary_terms(cls, terms, condition="fc"):
|
128
|
+
"""
|
119
129
|
Obtain a list of Pauli operators that are compatible in more than one group
|
120
130
|
by using the overlapping version of the sorted insertion algorithm.
|
121
|
-
|
131
|
+
"""
|
122
132
|
nonoverlapping_groups = sorted_insertion_grouping(terms, condition=condition)
|
123
133
|
newly_added = [[] for i in range(len(nonoverlapping_groups))]
|
124
134
|
sorted_terms = sorted(terms, key=lambda x: np.abs(x.coeff), reverse=True)
|
125
135
|
overlapping_terms = []
|
126
|
-
term_exists_in = []
|
136
|
+
term_exists_in = [] # List of group indices where overlapping terms are present.
|
127
137
|
for term in sorted_terms:
|
128
138
|
grup_idxs = []
|
129
139
|
for idx, group in enumerate(nonoverlapping_groups):
|
130
|
-
commute = term_commutes_with_group(term, group, condition) and term_commutes_with_group(
|
140
|
+
commute = term_commutes_with_group(term, group, condition) and term_commutes_with_group(
|
141
|
+
term, newly_added[idx], condition
|
142
|
+
)
|
131
143
|
if commute:
|
132
144
|
grup_idxs.append(idx)
|
133
145
|
newly_added[idx].append(term)
|
@@ -138,31 +150,34 @@ class OverlappingGroups:
|
|
138
150
|
return group
|
139
151
|
|
140
152
|
def optimize_pauli_coefficients(self, cov_dict, sample_size):
|
141
|
-
|
153
|
+
"""
|
142
154
|
Build the equation matrix @ x = b and solve it in order to obtain the
|
143
|
-
optimal overlapping coefficients for the measurable fragments
|
155
|
+
optimal overlapping coefficients for the measurable fragments
|
144
156
|
[see Eq. (14) on arXiv:2201.01471].
|
145
|
-
|
157
|
+
"""
|
158
|
+
|
146
159
|
def prep_mat_single_row(term, group_index):
|
147
|
-
|
148
|
-
Build the matrix row corresponding to Pauli term.
|
149
|
-
|
160
|
+
"""
|
161
|
+
Build the matrix row corresponding to Pauli term.
|
162
|
+
"""
|
150
163
|
mat_single = np.zeros(np.sum(self.wo_fixed.n_coeff_grp))
|
151
164
|
for term2_idx, term2 in enumerate(self.o_groups[group_index]):
|
152
165
|
term2_idx_dict = self.wo_fixed.term_idxs[term2.binary_tuple()]
|
153
166
|
cov = np.real_if_close(get_cov(term, term2, cov_dict))
|
154
167
|
if term2 in self.wo_fixed.o_groups[group_index]:
|
155
|
-
mat_single[term2_idx_dict[group_index]] -= cov/sample_size[group_index]
|
168
|
+
mat_single[term2_idx_dict[group_index]] -= cov / sample_size[group_index]
|
156
169
|
else:
|
157
170
|
for idx in term2_idx_dict.values():
|
158
|
-
mat_single[idx] += cov/sample_size[group_index]
|
171
|
+
mat_single[idx] += cov / sample_size[group_index]
|
159
172
|
return mat_single
|
160
173
|
|
161
174
|
def prep_b_single_row(term, group_index):
|
162
|
-
|
163
|
-
Build b row corresponding to Pauli term.
|
164
|
-
|
165
|
-
return np.real_if_close(
|
175
|
+
"""
|
176
|
+
Build b row corresponding to Pauli term.
|
177
|
+
"""
|
178
|
+
return np.real_if_close(
|
179
|
+
cov_term_w_group(term, self.no_groups[group_index], cov_dict) / sample_size[group_index]
|
180
|
+
)
|
166
181
|
|
167
182
|
mat_size = np.sum(self.wo_fixed.n_coeff_grp)
|
168
183
|
matrix = np.zeros((mat_size, mat_size))
|
@@ -181,17 +196,19 @@ class OverlappingGroups:
|
|
181
196
|
return sol.T[0]
|
182
197
|
|
183
198
|
def overlapping_groups_from_coeff(self, coeff):
|
184
|
-
|
199
|
+
"""
|
185
200
|
Find fixed coefficients from the optimal coefficients and
|
186
201
|
return the optimal overlapping groups to be measured.
|
187
|
-
|
202
|
+
"""
|
203
|
+
|
188
204
|
def add_coeff_times_term(coeff, term, group_index):
|
189
205
|
added = False
|
190
206
|
for igrp, group_term in enumerate(final_overlapping_groups[group_index]):
|
191
207
|
if group_term.binary_tuple() == term.binary_tuple():
|
192
|
-
final_overlapping_groups[group_index][igrp].set_coeff(
|
208
|
+
final_overlapping_groups[group_index][igrp].set_coeff(coeff + group_term.get_coeff())
|
193
209
|
added = True
|
194
|
-
if not(added):
|
210
|
+
if not (added):
|
211
|
+
final_overlapping_groups[group_index].append(term.term_w_coeff(coeff))
|
195
212
|
return None
|
196
213
|
|
197
214
|
final_overlapping_groups = deepcopy(self.no_groups)
|
@@ -207,9 +224,9 @@ class OverlappingGroups:
|
|
207
224
|
return final_overlapping_groups
|
208
225
|
|
209
226
|
def optimal_overlapping_groups(self, overlap_aux):
|
210
|
-
|
227
|
+
"""
|
211
228
|
Find a set of overlapping groups with optimized coefficients.
|
212
|
-
|
229
|
+
"""
|
213
230
|
cur_groups = self.no_groups
|
214
231
|
for i in range(overlap_aux.n_iter):
|
215
232
|
cur_sample_size = get_opt_sample_size(cur_groups, overlap_aux.cov_dict)
|
tequila/hamiltonian/paulis.py
CHANGED
@@ -3,6 +3,7 @@ Convenience initialization
|
|
3
3
|
of Pauli Operators. Resulting structures can be added and multiplied together.
|
4
4
|
Currently uses OpenFermion as backend (QubitOperators)
|
5
5
|
"""
|
6
|
+
|
6
7
|
import typing
|
7
8
|
from tequila.hamiltonian import QubitHamiltonian
|
8
9
|
from tequila import BitString, TequilaException
|
@@ -10,9 +11,11 @@ from tequila.wavefunction.qubit_wavefunction import QubitWaveFunction
|
|
10
11
|
from tequila.tools import list_assignment
|
11
12
|
import numpy
|
12
13
|
|
14
|
+
|
13
15
|
def from_string(string, openfermion_format=False):
|
14
16
|
return QubitHamiltonian.from_string(string=string, openfermion_format=openfermion_format)
|
15
17
|
|
18
|
+
|
16
19
|
def pauli(qubit, type) -> QubitHamiltonian:
|
17
20
|
"""
|
18
21
|
Parameters
|
@@ -198,7 +201,7 @@ def Sp(qubit) -> QubitHamiltonian:
|
|
198
201
|
qubit = list_assignment(qubit)
|
199
202
|
result = I()
|
200
203
|
for q in qubit:
|
201
|
-
result *= 0.5 * (X(qubit=q) + 1.
|
204
|
+
result *= 0.5 * (X(qubit=q) + 1.0j * Y(qubit=q))
|
202
205
|
return result
|
203
206
|
|
204
207
|
|
@@ -224,7 +227,7 @@ def Sm(qubit) -> QubitHamiltonian:
|
|
224
227
|
qubit = list_assignment(qubit)
|
225
228
|
result = I()
|
226
229
|
for q in qubit:
|
227
|
-
result *= 0.5 * (X(qubit=q) - 1.
|
230
|
+
result *= 0.5 * (X(qubit=q) - 1.0j * Y(qubit=q))
|
228
231
|
return result
|
229
232
|
|
230
233
|
|
@@ -264,12 +267,13 @@ def Projector(wfn, threshold=0.0, n_qubits=None) -> QubitHamiltonian:
|
|
264
267
|
c = v1.conjugate() * v2
|
265
268
|
if not numpy.isclose(c, 0.0, atol=threshold):
|
266
269
|
H += c * decompose_transfer_operator(bra=k1, ket=k2)
|
267
|
-
assert
|
270
|
+
assert H.is_hermitian()
|
268
271
|
return H
|
269
272
|
|
270
273
|
|
271
|
-
def KetBra(
|
272
|
-
|
274
|
+
def KetBra(
|
275
|
+
ket: QubitWaveFunction, bra: QubitWaveFunction, hermitian: bool = False, threshold: float = 1.0e-6, n_qubits=None
|
276
|
+
):
|
273
277
|
"""
|
274
278
|
Notes
|
275
279
|
----------
|
@@ -348,12 +352,7 @@ def decompose_transfer_operator(ket: BitString, bra: BitString, qubits: typing.L
|
|
348
352
|
|
349
353
|
"""
|
350
354
|
|
351
|
-
opmap = {
|
352
|
-
(0, 0): Qp,
|
353
|
-
(0, 1): Sp,
|
354
|
-
(1, 0): Sm,
|
355
|
-
(1, 1): Qm
|
356
|
-
}
|
355
|
+
opmap = {(0, 0): Qp, (0, 1): Sp, (1, 0): Sm, (1, 1): Qm}
|
357
356
|
|
358
357
|
nbits = None
|
359
358
|
if qubits is not None:
|
@@ -366,13 +365,13 @@ def decompose_transfer_operator(ket: BitString, bra: BitString, qubits: typing.L
|
|
366
365
|
|
367
366
|
b_arr = bra.array
|
368
367
|
k_arr = ket.array
|
369
|
-
assert
|
368
|
+
assert len(b_arr) == len(k_arr)
|
370
369
|
n_qubits = len(k_arr)
|
371
370
|
|
372
371
|
if qubits is None:
|
373
372
|
qubits = range(n_qubits)
|
374
373
|
|
375
|
-
assert
|
374
|
+
assert n_qubits <= len(qubits)
|
376
375
|
|
377
376
|
result = QubitHamiltonian.unit()
|
378
377
|
for q, b in enumerate(b_arr):
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from tequila.hamiltonian import PauliString
|
2
2
|
|
3
3
|
if __name__ == "__main__":
|
4
|
-
testdata = {0:
|
4
|
+
testdata = {0: "x", 1: "y", 2: "z"}
|
5
5
|
test = PauliString(data=testdata, coeff=2)
|
6
6
|
print("test=", test)
|
7
7
|
print("test: openfermion_key = ", test.key_openfermion())
|
@@ -1,3 +1,9 @@
|
|
1
|
+
"""
|
2
|
+
Explicit matrix forms for the Pauli operators for the tomatrix method
|
3
|
+
For sparse matrices use the openfermion tool
|
4
|
+
get the openfermion object with hamiltonian.hamiltonian
|
5
|
+
"""
|
6
|
+
|
1
7
|
import numbers
|
2
8
|
import typing
|
3
9
|
|
@@ -10,20 +16,15 @@ from functools import reduce
|
|
10
16
|
|
11
17
|
from collections import namedtuple
|
12
18
|
|
13
|
-
BinaryPauli = namedtuple("BinaryPauli", "coeff, binary")
|
14
|
-
|
15
|
-
"""
|
16
|
-
Explicit matrix forms for the Pauli operators for the tomatrix method
|
17
|
-
For sparse matrices use the openfermion tool
|
18
|
-
get the openfermion object with hamiltonian.hamiltonian
|
19
|
-
"""
|
20
19
|
import numpy as np
|
21
20
|
|
21
|
+
BinaryPauli = namedtuple("BinaryPauli", "coeff, binary")
|
22
|
+
|
22
23
|
pauli_matrices = {
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
"I": np.array([[1, 0], [0, 1]], dtype=complex),
|
25
|
+
"Z": np.array([[1, 0], [0, -1]], dtype=complex),
|
26
|
+
"X": np.array([[0, 1], [1, 0]], dtype=complex),
|
27
|
+
"Y": np.array([[0, -1j], [1j, 0]], dtype=complex),
|
27
28
|
}
|
28
29
|
|
29
30
|
|
@@ -98,12 +99,19 @@ class PauliString:
|
|
98
99
|
# <psi|op|psi> = |a|**2<0|op|0> + (a*)*b<1|op|0> + (b*)*a<0|op|1> + |b|**2<1|op|1>
|
99
100
|
|
100
101
|
if states is None:
|
101
|
-
states = [(1.0, 0.0)]*len(qubits)
|
102
|
+
states = [(1.0, 0.0)] * len(qubits)
|
102
103
|
|
103
104
|
def make_coeff_vec(state_tuple):
|
104
|
-
return np.asarray(
|
105
|
-
|
106
|
-
|
105
|
+
return np.asarray(
|
106
|
+
[
|
107
|
+
np.abs(state_tuple[0]) ** 2,
|
108
|
+
state_tuple[0].conjugate() * state_tuple[1],
|
109
|
+
state_tuple[1].conjugate() * state_tuple[0],
|
110
|
+
np.abs(state_tuple[1]) ** 2,
|
111
|
+
]
|
112
|
+
)
|
113
|
+
|
114
|
+
factor = 1.0
|
107
115
|
for q, state in zip(qubits, states):
|
108
116
|
if q in self.keys():
|
109
117
|
matrix = pauli_matrices[self[q].upper()].reshape([4])
|
@@ -112,9 +120,8 @@ class PauliString:
|
|
112
120
|
if factor == 0.0:
|
113
121
|
break
|
114
122
|
|
115
|
-
new_data = {k:v for k,v in self.items() if k not in qubits}
|
116
|
-
return PauliString(data=new_data, coeff=self.coeff*factor)
|
117
|
-
|
123
|
+
new_data = {k: v for k, v in self.items() if k not in qubits}
|
124
|
+
return PauliString(data=new_data, coeff=self.coeff * factor)
|
118
125
|
|
119
126
|
def map_qubits(self, qubit_map: dict):
|
120
127
|
"""
|
@@ -153,13 +160,13 @@ class PauliString:
|
|
153
160
|
"""
|
154
161
|
data = dict()
|
155
162
|
string = string.strip()
|
156
|
-
for part in string.split(
|
163
|
+
for part in string.split(")"):
|
157
164
|
part = part.strip()
|
158
165
|
if part == "":
|
159
166
|
break
|
160
|
-
pauli_dim = part.split(
|
167
|
+
pauli_dim = part.split("(")
|
161
168
|
string = pauli_dim[0].upper()
|
162
|
-
if not
|
169
|
+
if string not in ["X", "Y", "Z"]:
|
163
170
|
raise TequilaException("PauliString.from_string initialization failed, unknown pauliterm: " + string)
|
164
171
|
data[int(pauli_dim[1])] = string
|
165
172
|
|
@@ -223,8 +230,11 @@ class PauliString:
|
|
223
230
|
|
224
231
|
if n_qubits < maxq:
|
225
232
|
raise TequilaException(
|
226
|
-
"PauliString acts on qubit number larger than n_qubits given\n PauliString="
|
227
|
-
|
233
|
+
"PauliString acts on qubit number larger than n_qubits given\n PauliString="
|
234
|
+
+ self.__repr__()
|
235
|
+
+ ", n_qubits="
|
236
|
+
+ str(n_qubits)
|
237
|
+
)
|
228
238
|
|
229
239
|
binary = np.zeros(2 * n_qubits)
|
230
240
|
for k, v in self._data.items():
|
@@ -310,9 +320,9 @@ class QubitHamiltonian:
|
|
310
320
|
else:
|
311
321
|
self._qubit_operator = qubit_operator
|
312
322
|
|
313
|
-
assert
|
323
|
+
assert isinstance(self._qubit_operator, QubitOperator)
|
314
324
|
|
315
|
-
def trace_out_qubits(self, qubits, states: list=None, *args, **kwargs):
|
325
|
+
def trace_out_qubits(self, qubits, states: list = None, *args, **kwargs):
|
316
326
|
"""
|
317
327
|
Tracing out qubits with the assumption that they are in the |0> (default) or |1> state
|
318
328
|
|
@@ -328,7 +338,7 @@ class QubitHamiltonian:
|
|
328
338
|
"""
|
329
339
|
|
330
340
|
if states is None:
|
331
|
-
states = [(1.0,0.0)]*len(qubits)
|
341
|
+
states = [(1.0, 0.0)] * len(qubits)
|
332
342
|
else:
|
333
343
|
assert len(states) == len(qubits)
|
334
344
|
# states should be given as list of individual tq.QubitWaveFunctions
|
@@ -400,14 +410,14 @@ class QubitHamiltonian:
|
|
400
410
|
string = string.replace("Y", " Y")
|
401
411
|
string = string.replace("Z", " Z")
|
402
412
|
string += " "
|
403
|
-
terms = string.split(
|
413
|
+
terms = string.split("+")
|
404
414
|
for term in terms:
|
405
415
|
if term.strip() == "":
|
406
416
|
continue
|
407
417
|
|
408
418
|
coeff = term.split(" ")[0]
|
409
419
|
if coeff.strip() == "" or coeff[0] in ["X", "Y", "Z"]:
|
410
|
-
coeff =
|
420
|
+
coeff = "1.0"
|
411
421
|
ps = term
|
412
422
|
else:
|
413
423
|
ps = term.replace(coeff, " ").replace(" ", "")
|
@@ -418,7 +428,7 @@ class QubitHamiltonian:
|
|
418
428
|
if "@" in coeff:
|
419
429
|
coeff = coeff.replace("@", "e-")
|
420
430
|
coeff = complex(coeff)
|
421
|
-
except Exception
|
431
|
+
except Exception:
|
422
432
|
raise Exception("failed to convert coefficient : {}".format(coeff))
|
423
433
|
|
424
434
|
if coeff.imag == 0.0:
|
@@ -490,7 +500,7 @@ class QubitHamiltonian:
|
|
490
500
|
return self.__neg__().__add__(other=other)
|
491
501
|
|
492
502
|
def __pow__(self, power):
|
493
|
-
return QubitHamiltonian(qubit_operator=self.qubit_operator
|
503
|
+
return QubitHamiltonian(qubit_operator=self.qubit_operator**power)
|
494
504
|
|
495
505
|
def __neg__(self):
|
496
506
|
return self.__mul__(other=-1.0)
|
@@ -524,7 +534,7 @@ class QubitHamiltonian:
|
|
524
534
|
anti_hermitian = QubitHamiltonian.zero()
|
525
535
|
for k, v in self.qubit_operator.terms.items():
|
526
536
|
hermitian.qubit_operator.terms[k] = v.real
|
527
|
-
anti_hermitian.qubit_operator.terms[k] = 1.
|
537
|
+
anti_hermitian.qubit_operator.terms[k] = 1.0j * v.imag
|
528
538
|
|
529
539
|
return hermitian.simplify(), anti_hermitian.simplify()
|
530
540
|
|
@@ -575,10 +585,10 @@ class QubitHamiltonian:
|
|
575
585
|
|
576
586
|
Returns a dense 2**N x 2**N matrix representation of this
|
577
587
|
QubitHamiltonian. Watch for memory usage when N is >12!
|
578
|
-
|
588
|
+
|
579
589
|
Args:
|
580
590
|
ignore_unused_qubits: If no non-trivial operator is defined on a qubits this qubit will be ignored in the matrix construction.
|
581
|
-
Take for example X(1).
|
591
|
+
Take for example X(1).
|
582
592
|
If False the operator X(1) will get mapped to X(0)
|
583
593
|
and the function will return the matrix for X(0)
|
584
594
|
otherwise the function will return the matrix 1 \otimes X(1)
|
@@ -590,10 +600,10 @@ class QubitHamiltonian:
|
|
590
600
|
if ignore_unused_qubits:
|
591
601
|
nq = len(qubits)
|
592
602
|
else:
|
593
|
-
nq = max(qubits)+1
|
603
|
+
nq = max(qubits) + 1
|
594
604
|
|
595
605
|
I = np.eye(2, dtype=complex)
|
596
|
-
Hm = np.zeros((2
|
606
|
+
Hm = np.zeros((2**nq, 2**nq), dtype=complex)
|
597
607
|
|
598
608
|
for key, val in self.items():
|
599
609
|
term = [I] * nq
|
tequila/ml/__init__.py
CHANGED
tequila/ml/interface_torch.py
CHANGED
@@ -24,11 +24,12 @@ def get_torch_function(objective: Objective, compile_args: dict = None, input_va
|
|
24
24
|
the requisite pytorch autograd function, alongside necessary information for higher level classes.
|
25
25
|
"""
|
26
26
|
|
27
|
-
if isinstance(objective,tuple) or isinstance(objective,list) or isinstance(objective,Objective):
|
27
|
+
if isinstance(objective, tuple) or isinstance(objective, list) or isinstance(objective, Objective):
|
28
28
|
objective = vectorize(list_assignment(objective))
|
29
|
-
comped_objective, compile_args, input_vars, weight_vars, i_grads, w_grads, first, second
|
30
|
-
|
31
|
-
|
29
|
+
comped_objective, compile_args, input_vars, weight_vars, i_grads, w_grads, first, second = preamble(
|
30
|
+
objective, compile_args, input_vars
|
31
|
+
)
|
32
|
+
samples = compile_args["samples"]
|
32
33
|
|
33
34
|
def tensor_fix(tensor, angles, first, second):
|
34
35
|
"""
|
@@ -94,6 +95,7 @@ def get_torch_function(objective: Objective, compile_args: dict = None, input_va
|
|
94
95
|
takes a tensor and an (int: Variable) dict and returns a (Variable: float) dict.
|
95
96
|
|
96
97
|
"""
|
98
|
+
|
97
99
|
@staticmethod
|
98
100
|
def forward(ctx, inputs, angles):
|
99
101
|
"""
|
@@ -106,8 +108,8 @@ def get_torch_function(objective: Objective, compile_args: dict = None, input_va
|
|
106
108
|
if not isinstance(result, np.ndarray):
|
107
109
|
# this happens if the Objective is a scalar since that's usually more convenient for pure quantum stuff.
|
108
110
|
result = np.array(result)
|
109
|
-
if hasattr(inputs,
|
110
|
-
if inputs.device ==
|
111
|
+
if hasattr(inputs, "device"):
|
112
|
+
if inputs.device == "cuda":
|
111
113
|
r = torch.from_numpy(result).to(inputs.device)
|
112
114
|
else:
|
113
115
|
r = torch.from_numpy(result)
|
@@ -122,7 +124,7 @@ def get_torch_function(objective: Objective, compile_args: dict = None, input_va
|
|
122
124
|
call_args = tensor_fix(inputs, angles, first, second)
|
123
125
|
back_d = grad_backward.get_device()
|
124
126
|
# build up weight and input gradient matrices... see what needs to be done to them.
|
125
|
-
grad_outs = [None,None]
|
127
|
+
grad_outs = [None, None]
|
126
128
|
for i, grads in enumerate([i_grads, w_grads]):
|
127
129
|
if grads != {}:
|
128
130
|
g_keys = [j for j in grads.keys()]
|
@@ -138,7 +140,7 @@ def get_torch_function(objective: Objective, compile_args: dict = None, input_va
|
|
138
140
|
else:
|
139
141
|
g_tensor = torch.as_tensor(arr, dtype=grad_backward.dtype)
|
140
142
|
|
141
|
-
b = grad_backward.reshape(-1,1)
|
143
|
+
b = grad_backward.reshape(-1, 1)
|
142
144
|
jvp = torch.matmul(g_tensor, b)
|
143
145
|
jvp_out = jvp.flatten()
|
144
146
|
jvp_out.requires_grad_(True)
|
@@ -186,9 +188,9 @@ class TorchLayer(torch.nn.Module):
|
|
186
188
|
super().__init__()
|
187
189
|
|
188
190
|
self._objective = objective
|
189
|
-
self.function,
|
191
|
+
self.function, weight_vars, compile_args = get_torch_function(objective, compile_args, input_vars)
|
190
192
|
self._input_len = len(objective.extract_variables()) - len(weight_vars)
|
191
|
-
inits = compile_args[
|
193
|
+
inits = compile_args["initial_values"]
|
192
194
|
self.weights = {}
|
193
195
|
if inits is not None:
|
194
196
|
for v in weight_vars:
|
@@ -197,7 +199,7 @@ class TorchLayer(torch.nn.Module):
|
|
197
199
|
self.register_parameter(str(v), self.weights[str(v)])
|
198
200
|
else:
|
199
201
|
for v in weight_vars:
|
200
|
-
self.weights[str(v)] = torch.nn.Parameter(torch.nn.init.uniform(torch.Tensor(1), a=0.0, b=2*np.pi)[0])
|
202
|
+
self.weights[str(v)] = torch.nn.Parameter(torch.nn.init.uniform(torch.Tensor(1), a=0.0, b=2 * np.pi)[0])
|
201
203
|
self.register_parameter(str(v), self.weights[str(v)])
|
202
204
|
|
203
205
|
def forward(self, x=None):
|
@@ -231,7 +233,9 @@ class TorchLayer(torch.nn.Module):
|
|
231
233
|
f = None
|
232
234
|
if x is not None:
|
233
235
|
if len(x) != self._input_len:
|
234
|
-
raise TequilaMLException(
|
236
|
+
raise TequilaMLException(
|
237
|
+
"Received input of len {} when Objective takes {} inputs.".format(len(x), self._input_len)
|
238
|
+
)
|
235
239
|
return self.function.apply(x, f)
|
236
240
|
|
237
241
|
def extra_repr(self) -> str:
|
@@ -241,8 +245,7 @@ class TorchLayer(torch.nn.Module):
|
|
241
245
|
str:
|
242
246
|
Information used by print(TorchLayer).
|
243
247
|
"""
|
244
|
-
string =
|
245
|
-
string +=
|
246
|
-
string +=
|
248
|
+
string = "Tequila TorchLayer. Represents: \n"
|
249
|
+
string += "{} \n".format(str(self._objective))
|
250
|
+
string += "Current Weights: {}".format(self.weights)
|
247
251
|
return string
|
248
|
-
|