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
@@ -6,16 +6,27 @@ from tequila.hamiltonian import QubitHamiltonian
|
|
6
6
|
from tequila.hamiltonian.paulis import Sp, Sm, Zero
|
7
7
|
|
8
8
|
from tequila.circuit import QCircuit, gates
|
9
|
-
from tequila.objective.objective import Variable, Variables, ExpectationValue
|
9
|
+
from tequila.objective.objective import Variable, Variables, ExpectationValue, Objective
|
10
|
+
from tequila import QTensor
|
10
11
|
|
11
12
|
from tequila.simulators.simulator_api import simulate
|
12
13
|
from tequila.utils import to_float
|
13
|
-
from .chemistry_tools import
|
14
|
-
|
14
|
+
from .chemistry_tools import (
|
15
|
+
ActiveSpaceData,
|
16
|
+
FermionicGateImpl,
|
17
|
+
prepare_product_state,
|
18
|
+
ClosedShellAmplitudes,
|
19
|
+
Amplitudes,
|
20
|
+
ParametersQC,
|
21
|
+
NBodyTensor,
|
22
|
+
IntegralManager,
|
23
|
+
)
|
15
24
|
|
16
25
|
from .encodings import known_encodings
|
17
26
|
|
18
|
-
import typing
|
27
|
+
import typing
|
28
|
+
import numpy
|
29
|
+
import numbers
|
19
30
|
from itertools import product
|
20
31
|
import tequila.grouping.fermionic_functions as ff
|
21
32
|
|
@@ -26,13 +37,16 @@ try:
|
|
26
37
|
# otherwise replace with from openfermion.hamiltonians import MolecularData
|
27
38
|
import openfermion
|
28
39
|
from openfermion.chem import MolecularData
|
29
|
-
except:
|
40
|
+
except Exception:
|
30
41
|
try:
|
31
42
|
from openfermion.hamiltonians import MolecularData
|
32
43
|
except Exception as E:
|
33
44
|
raise Exception("{}\nIssue with Tequila Chemistry: Please update openfermion".format(str(E)))
|
34
45
|
import warnings
|
46
|
+
|
35
47
|
OPTIMIZED_ORDERING = "Optimized"
|
48
|
+
|
49
|
+
|
36
50
|
class QuantumChemistryBase:
|
37
51
|
"""
|
38
52
|
Base Class for tequila chemistry functionality
|
@@ -41,15 +55,18 @@ class QuantumChemistryBase:
|
|
41
55
|
Derived classes interface specific backends (e.g. Psi4, PySCF and Madness). See PACKAGE_interface.py for more
|
42
56
|
"""
|
43
57
|
|
44
|
-
def __init__(
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
58
|
+
def __init__(
|
59
|
+
self,
|
60
|
+
parameters: ParametersQC,
|
61
|
+
transformation: typing.Union[str, typing.Callable] = None,
|
62
|
+
active_orbitals: list = None,
|
63
|
+
frozen_orbitals: list = None,
|
64
|
+
orbital_type: str = None,
|
65
|
+
reference_orbitals: list = None,
|
66
|
+
orbitals: list = None,
|
67
|
+
*args,
|
68
|
+
**kwargs,
|
69
|
+
):
|
53
70
|
"""
|
54
71
|
Parameters
|
55
72
|
----------
|
@@ -64,14 +81,14 @@ class QuantumChemistryBase:
|
|
64
81
|
"""
|
65
82
|
|
66
83
|
self.parameters = parameters
|
67
|
-
n_electrons = parameters.
|
84
|
+
n_electrons = parameters.total_n_electrons
|
68
85
|
if "n_electrons" in kwargs:
|
69
86
|
n_electrons = kwargs["n_electrons"]
|
70
87
|
|
71
88
|
if reference_orbitals is None:
|
72
89
|
reference_orbitals = [i for i in range(n_electrons // 2)]
|
73
90
|
self._reference_orbitals = reference_orbitals
|
74
|
-
|
91
|
+
|
75
92
|
if orbital_type is None:
|
76
93
|
orbital_type = "unknown"
|
77
94
|
|
@@ -79,33 +96,36 @@ class QuantumChemistryBase:
|
|
79
96
|
overriding_freeze_instruction = orbital_type is not None and orbital_type.lower() == "native"
|
80
97
|
# determine frozen core automatically if set
|
81
98
|
# only if molecule is computed from scratch and not passed down from above
|
82
|
-
overriding_freeze_instruction = overriding_freeze_instruction or n_electrons != parameters.
|
99
|
+
overriding_freeze_instruction = overriding_freeze_instruction or n_electrons != parameters.total_n_electrons
|
83
100
|
overriding_freeze_instruction = overriding_freeze_instruction or frozen_orbitals is not None
|
84
101
|
if not overriding_freeze_instruction and self.parameters.frozen_core:
|
85
102
|
n_core_electrons = self.parameters.get_number_of_core_electrons()
|
86
103
|
if frozen_orbitals is None:
|
87
|
-
frozen_orbitals = [i for i in range(n_core_electrons//2)]
|
88
|
-
|
104
|
+
frozen_orbitals = [i for i in range(n_core_electrons // 2)]
|
89
105
|
|
90
106
|
# initialize integral manager
|
91
107
|
if "integral_manager" in kwargs:
|
92
108
|
self.integral_manager = kwargs["integral_manager"]
|
93
109
|
else:
|
94
|
-
self.integral_manager = self.initialize_integral_manager(
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
110
|
+
self.integral_manager = self.initialize_integral_manager(
|
111
|
+
active_orbitals=active_orbitals,
|
112
|
+
reference_orbitals=reference_orbitals,
|
113
|
+
orbitals=orbitals,
|
114
|
+
frozen_orbitals=frozen_orbitals,
|
115
|
+
orbital_type=orbital_type,
|
116
|
+
basis_name=self.parameters.basis_set,
|
117
|
+
*args,
|
118
|
+
**kwargs,
|
119
|
+
)
|
120
|
+
|
99
121
|
if orbital_type is not None and orbital_type.lower() == "native":
|
100
122
|
self.integral_manager.transform_to_native_orbitals()
|
101
123
|
|
102
|
-
|
103
124
|
self.transformation = self._initialize_transformation(transformation=transformation, *args, **kwargs)
|
104
125
|
|
105
126
|
self._rdm1 = None
|
106
127
|
self._rdm2 = None
|
107
128
|
|
108
|
-
|
109
129
|
@classmethod
|
110
130
|
def from_tequila(cls, molecule, transformation=None, *args, **kwargs):
|
111
131
|
c = molecule.integral_manager.constant_term
|
@@ -120,16 +140,20 @@ class QuantumChemistryBase:
|
|
120
140
|
if transformation is None:
|
121
141
|
transformation = molecule.transformation
|
122
142
|
parameters = molecule.parameters
|
123
|
-
return cls(
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
143
|
+
return cls(
|
144
|
+
nuclear_repulsion=c,
|
145
|
+
one_body_integrals=h1,
|
146
|
+
two_body_integrals=h2,
|
147
|
+
overlap_integrals=S,
|
148
|
+
orbital_coefficients=molecule.integral_manager.orbital_coefficients,
|
149
|
+
active_orbitals=active_orbitals,
|
150
|
+
transformation=transformation,
|
151
|
+
orbital_type=molecule.integral_manager._orbital_type,
|
152
|
+
parameters=parameters,
|
153
|
+
reference_orbitals=molecule.integral_manager.active_space.reference_orbitals,
|
154
|
+
*args,
|
155
|
+
**kwargs,
|
156
|
+
)
|
133
157
|
|
134
158
|
def supports_ucc(self):
|
135
159
|
"""
|
@@ -156,8 +180,9 @@ class QuantumChemistryBase:
|
|
156
180
|
transformation = "JordanWigner"
|
157
181
|
|
158
182
|
# filter out arguments to the transformation
|
159
|
-
trafo_args = {
|
160
|
-
|
183
|
+
trafo_args = {
|
184
|
+
k.split("__")[1]: v for k, v in kwargs.items() if (hasattr(k, "lower") and "transformation__" in k.lower())
|
185
|
+
}
|
161
186
|
|
162
187
|
trafo_args["n_electrons"] = self.n_electrons
|
163
188
|
trafo_args["n_orbitals"] = self.n_orbitals
|
@@ -170,16 +195,21 @@ class QuantumChemistryBase:
|
|
170
195
|
transformation = encodings[transformation](**trafo_args)
|
171
196
|
else:
|
172
197
|
raise TequilaException(
|
173
|
-
"Unkown Fermion-to-Qubit encoding {}. Try something like: {}".format(
|
174
|
-
|
198
|
+
"Unkown Fermion-to-Qubit encoding {}. Try something like: {}".format(
|
199
|
+
transformation, list(encodings.keys())
|
200
|
+
)
|
201
|
+
)
|
175
202
|
|
176
203
|
return transformation
|
177
204
|
|
178
205
|
@classmethod
|
179
|
-
def from_openfermion(
|
180
|
-
|
181
|
-
|
182
|
-
|
206
|
+
def from_openfermion(
|
207
|
+
cls,
|
208
|
+
molecule: openfermion.MolecularData,
|
209
|
+
transformation: typing.Union[str, typing.Callable] = None,
|
210
|
+
*args,
|
211
|
+
**kwargs,
|
212
|
+
):
|
183
213
|
"""
|
184
214
|
Initialize direclty from openfermion MolecularData object
|
185
215
|
|
@@ -191,15 +221,19 @@ class QuantumChemistryBase:
|
|
191
221
|
-------
|
192
222
|
The Tequila molecule
|
193
223
|
"""
|
194
|
-
parameters = ParametersQC(
|
195
|
-
|
196
|
-
|
224
|
+
parameters = ParametersQC(
|
225
|
+
basis_set=molecule.basis,
|
226
|
+
geometry=molecule.geometry,
|
227
|
+
units="angstrom",
|
228
|
+
description=molecule.description,
|
229
|
+
multiplicity=molecule.multiplicity,
|
230
|
+
charge=molecule.charge,
|
231
|
+
)
|
197
232
|
return cls(parameters=parameters, transformation=transformation, molecule=molecule, *args, **kwargs)
|
198
233
|
|
199
|
-
def make_excitation_generator(
|
200
|
-
|
201
|
-
|
202
|
-
remove_constant_term: bool = True) -> QubitHamiltonian:
|
234
|
+
def make_excitation_generator(
|
235
|
+
self, indices: typing.Iterable[typing.Tuple[int, int]], form: str = None, remove_constant_term: bool = True
|
236
|
+
) -> QubitHamiltonian:
|
203
237
|
"""
|
204
238
|
Notes
|
205
239
|
----------
|
@@ -226,17 +260,21 @@ class QuantumChemistryBase:
|
|
226
260
|
"""
|
227
261
|
|
228
262
|
if not self.supports_ucc():
|
229
|
-
raise TequilaException(
|
263
|
+
raise TequilaException(
|
264
|
+
"Molecule with transformation {} does not support general UCC operations".format(self.transformation)
|
265
|
+
)
|
230
266
|
|
231
267
|
# check indices and convert to list of tuples if necessary
|
232
268
|
if len(indices) == 0:
|
233
269
|
raise TequilaException("make_excitation_operator: no indices given")
|
234
270
|
elif not isinstance(indices[0], typing.Iterable):
|
235
271
|
if len(indices) % 2 != 0:
|
236
|
-
raise TequilaException(
|
237
|
-
|
238
|
-
|
239
|
-
|
272
|
+
raise TequilaException(
|
273
|
+
"make_excitation_generator: unexpected input format of indices\n"
|
274
|
+
"use list of tuples as [(a_0, i_0),(a_1, i_1) ...]\n"
|
275
|
+
"or list as [a_0, i_0, a_1, i_1, ... ]\n"
|
276
|
+
"you gave: {}".format(indices)
|
277
|
+
)
|
240
278
|
converted = [(indices[2 * i], indices[2 * i + 1]) for i in range(len(indices) // 2)]
|
241
279
|
else:
|
242
280
|
converted = indices
|
@@ -249,15 +287,17 @@ class QuantumChemistryBase:
|
|
249
287
|
ofi = []
|
250
288
|
dag = []
|
251
289
|
for pair in converted:
|
252
|
-
assert
|
253
|
-
ofi += [
|
254
|
-
|
290
|
+
assert len(pair) == 2
|
291
|
+
ofi += [
|
292
|
+
(int(pair[0]), 1),
|
293
|
+
(int(pair[1]), 0),
|
294
|
+
] # openfermion does not take other types of integers like numpy.int64
|
255
295
|
dag += [(int(pair[0]), 0), (int(pair[1]), 1)]
|
256
296
|
|
257
|
-
op = openfermion.FermionOperator(tuple(ofi), 1.
|
258
|
-
op += openfermion.FermionOperator(tuple(reversed(dag)), -1.
|
297
|
+
op = openfermion.FermionOperator(tuple(ofi), 1.0j) # 1j makes it hermitian
|
298
|
+
op += openfermion.FermionOperator(tuple(reversed(dag)), -1.0j)
|
259
299
|
|
260
|
-
if isinstance(form, str) and form.lower() !=
|
300
|
+
if isinstance(form, str) and form.lower() != "fermionic":
|
261
301
|
# indices for all the Na operators
|
262
302
|
Na = [x for pair in converted for x in [(pair[0], 1), (pair[0], 0)]]
|
263
303
|
# indices for all the Ma operators (Ma = 1 - Na)
|
@@ -291,8 +331,7 @@ class QuantumChemistryBase:
|
|
291
331
|
op += openfermion.FermionOperator(Na + Mi, -1.0)
|
292
332
|
op += openfermion.FermionOperator(Ni + Ma, -1.0)
|
293
333
|
else:
|
294
|
-
raise TequilaException(
|
295
|
-
"Unknown generator form {}, supported are G, P+, P-, G+, G- and P0".format(form))
|
334
|
+
raise TequilaException("Unknown generator form {}, supported are G, P+, P-, G+, G- and P0".format(form))
|
296
335
|
|
297
336
|
qop = self.transformation(op)
|
298
337
|
|
@@ -310,13 +349,17 @@ class QuantumChemistryBase:
|
|
310
349
|
qop = qop.simplify()
|
311
350
|
|
312
351
|
if len(qop) == 0:
|
313
|
-
warnings.warn(
|
314
|
-
|
315
|
-
|
352
|
+
warnings.warn(
|
353
|
+
"Excitation generator is a unit operator.\n"
|
354
|
+
"Non-standard transformations might not work with general fermionic operators\n"
|
355
|
+
"indices = " + str(indices),
|
356
|
+
category=TequilaWarning,
|
357
|
+
)
|
316
358
|
return qop
|
317
359
|
|
318
|
-
def make_hardcore_boson_excitation_gate(
|
319
|
-
|
360
|
+
def make_hardcore_boson_excitation_gate(
|
361
|
+
self, indices, angle, control=None, assume_real=True, compile_options="optimize"
|
362
|
+
):
|
320
363
|
"""
|
321
364
|
Make excitation generator in the hardcore-boson approximation (all electrons are forced to spin-pairs)
|
322
365
|
use only in combination with make_hardcore_boson_hamiltonian()
|
@@ -333,22 +376,32 @@ class QuantumChemistryBase:
|
|
333
376
|
-------
|
334
377
|
|
335
378
|
"""
|
379
|
+
U = QCircuit()
|
336
380
|
target = []
|
337
381
|
for pair in indices:
|
338
382
|
assert len(pair) == 2
|
339
|
-
|
383
|
+
if "TAPEREDBINARY" in self.transformation.name.upper() and self.n_orbitals - 1 in pair:
|
384
|
+
p = [i for i in pair if i != self.n_orbitals - 1]
|
385
|
+
U += gates.Ry(angle=angle, target=p, assume_real=assume_real, control=control)
|
386
|
+
else:
|
387
|
+
target += [self.transformation.up(pair[0]), self.transformation.up(pair[1])]
|
340
388
|
if self.transformation.up_then_down:
|
341
389
|
consistency = [x < self.n_orbitals for x in target]
|
342
390
|
else:
|
343
|
-
consistency = [x % 2 == 0
|
391
|
+
consistency = [x % 2 == 0 for x in target]
|
344
392
|
if not all(consistency):
|
345
393
|
raise TequilaException(
|
346
394
|
"make_hardcore_boson_excitation_gate: Inconsistencies in indices={} for encoding: {}".format(
|
347
|
-
indices, self.transformation
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
395
|
+
indices, self.transformation
|
396
|
+
)
|
397
|
+
)
|
398
|
+
if len(target):
|
399
|
+
U += gates.QubitExcitation(
|
400
|
+
angle=angle, target=target, assume_real=assume_real, control=control, compile_options=compile_options
|
401
|
+
)
|
402
|
+
return U
|
403
|
+
|
404
|
+
def UR(self, i, j, angle=None, label=None, control=None, assume_real=True, *args, **kwargs):
|
352
405
|
"""
|
353
406
|
Convenience function for orbital rotation circuit (rotating spatial orbital i and j) with standard naming of variables
|
354
407
|
See arXiv:2207.12421 Eq.6 for UR(0,1)
|
@@ -368,22 +421,26 @@ class QuantumChemistryBase:
|
|
368
421
|
Assume that the wavefunction will always stay real.
|
369
422
|
Will reduce potential gradient costs by a factor of 2
|
370
423
|
"""
|
371
|
-
i,j = self.format_excitation_indices([(i,j)])[0]
|
424
|
+
i, j = self.format_excitation_indices([(i, j)])[0]
|
372
425
|
if angle is None:
|
373
426
|
if label is None:
|
374
|
-
angle = Variable(name=("R",i,j))*numpy.pi
|
427
|
+
angle = Variable(name=("R", i, j)) * numpy.pi
|
375
428
|
else:
|
376
|
-
angle = Variable(name=("R",i,j,label))*numpy.pi
|
377
|
-
|
378
|
-
circuit = self.make_excitation_gate(
|
379
|
-
|
429
|
+
angle = Variable(name=("R", i, j, label)) * numpy.pi
|
430
|
+
|
431
|
+
circuit = self.make_excitation_gate(
|
432
|
+
indices=[(2 * i, 2 * j)], angle=angle, assume_real=assume_real, control=control, *args, **kwargs
|
433
|
+
)
|
434
|
+
circuit += self.make_excitation_gate(
|
435
|
+
indices=[(2 * i + 1, 2 * j + 1)], angle=angle, assume_real=assume_real, control=control, *args, **kwargs
|
436
|
+
)
|
380
437
|
return circuit
|
381
|
-
|
382
|
-
def UC(self,i,j,angle=None, label=None, control=None, assume_real=True, *args, **kwargs):
|
438
|
+
|
439
|
+
def UC(self, i, j, angle=None, label=None, control=None, assume_real=True, *args, **kwargs):
|
383
440
|
"""
|
384
441
|
Convenience function for orbital correlator circuit (correlating spatial orbital i and j through a spin-paired double excitation) with standard naming of variables
|
385
442
|
See arXiv:2207.12421 Eq.22 for UC(1,2)
|
386
|
-
|
443
|
+
|
387
444
|
Parameters:
|
388
445
|
----------
|
389
446
|
indices:
|
@@ -400,22 +457,35 @@ class QuantumChemistryBase:
|
|
400
457
|
Assume that the wavefunction will always stay real.
|
401
458
|
Will reduce potential gradient costs by a factor of 2
|
402
459
|
"""
|
403
|
-
i,j = self.format_excitation_indices([(i,j)])[0]
|
460
|
+
i, j = self.format_excitation_indices([(i, j)])[0]
|
404
461
|
if angle is None:
|
405
462
|
if label is None:
|
406
|
-
angle = Variable(name=("C",i,j))*numpy.pi
|
463
|
+
angle = Variable(name=("C", i, j)) * numpy.pi
|
407
464
|
else:
|
408
|
-
angle = Variable(name=("C",i,j,label))*numpy.pi
|
465
|
+
angle = Variable(name=("C", i, j, label)) * numpy.pi
|
409
466
|
if "jordanwigner" in self.transformation.name.lower() and not self.transformation.up_then_down:
|
410
467
|
# for JW we can use the optimized form shown in arXiv:2207.12421 Eq.22
|
411
|
-
return gates.QubitExcitation(
|
468
|
+
return gates.QubitExcitation(
|
469
|
+
target=[2 * i, 2 * j, 2 * i + 1, 2 * j + 1],
|
470
|
+
angle=angle,
|
471
|
+
control=control,
|
472
|
+
assume_real=assume_real,
|
473
|
+
*args,
|
474
|
+
**kwargs,
|
475
|
+
)
|
412
476
|
else:
|
413
|
-
return self.make_excitation_gate(
|
414
|
-
|
415
|
-
|
477
|
+
return self.make_excitation_gate(
|
478
|
+
indices=[(2 * i, 2 * j), (2 * i + 1, 2 * j + 1)],
|
479
|
+
angle=angle,
|
480
|
+
control=control,
|
481
|
+
assume_real=assume_real,
|
482
|
+
*args,
|
483
|
+
**kwargs,
|
484
|
+
)
|
485
|
+
|
486
|
+
def make_orbital_rotation_gate(self, indices: tuple, *args, **kwargs):
|
416
487
|
# backward compatibility
|
417
|
-
return self.UR(indices[0],indices[1], *args, **kwargs)
|
418
|
-
|
488
|
+
return self.UR(indices[0], indices[1], *args, **kwargs)
|
419
489
|
|
420
490
|
def make_excitation_gate(self, indices, angle, control=None, assume_real=True, **kwargs):
|
421
491
|
"""
|
@@ -441,20 +511,32 @@ class QuantumChemistryBase:
|
|
441
511
|
"""
|
442
512
|
|
443
513
|
if not self.supports_ucc():
|
444
|
-
raise TequilaException(
|
514
|
+
raise TequilaException(
|
515
|
+
"Molecule with transformation {} does not support general UCC operations".format(self.transformation)
|
516
|
+
)
|
445
517
|
|
446
518
|
generator = self.make_excitation_generator(indices=indices, remove_constant_term=control is None)
|
447
519
|
p0 = self.make_excitation_generator(indices=indices, form="P0", remove_constant_term=control is None)
|
448
520
|
if self.transformation.up_then_down:
|
449
521
|
idx = []
|
450
522
|
for pair in indices:
|
451
|
-
idx.append(
|
452
|
-
|
523
|
+
idx.append(
|
524
|
+
(pair[0] // 2 + (pair[0] % 2) * self.n_orbitals, pair[1] // 2 + (pair[1] % 2) * self.n_orbitals)
|
525
|
+
)
|
526
|
+
else:
|
527
|
+
idx = indices
|
453
528
|
return QCircuit.wrap_gate(
|
454
|
-
FermionicGateImpl(
|
455
|
-
|
456
|
-
|
457
|
-
|
529
|
+
FermionicGateImpl(
|
530
|
+
angle=angle,
|
531
|
+
generator=generator,
|
532
|
+
p0=p0,
|
533
|
+
transformation=type(self.transformation).__name__.lower(),
|
534
|
+
indices=idx,
|
535
|
+
assume_real=assume_real,
|
536
|
+
control=control,
|
537
|
+
**kwargs,
|
538
|
+
)
|
539
|
+
)
|
458
540
|
|
459
541
|
def make_molecule(self, *args, **kwargs) -> MolecularData:
|
460
542
|
"""Creates a molecule in openfermion format by running psi4 and extracting the data
|
@@ -487,7 +569,7 @@ class QuantumChemistryBase:
|
|
487
569
|
if do_compute:
|
488
570
|
molecule = self.do_make_molecule(*args, **kwargs)
|
489
571
|
|
490
|
-
#molecule.save()
|
572
|
+
# molecule.save()
|
491
573
|
return molecule
|
492
574
|
|
493
575
|
def initialize_integral_manager(self, *args, **kwargs):
|
@@ -504,12 +586,12 @@ class QuantumChemistryBase:
|
|
504
586
|
- result of self.get_integrals()
|
505
587
|
"""
|
506
588
|
|
507
|
-
n_electrons = self.parameters.
|
589
|
+
n_electrons = self.parameters.total_n_electrons
|
508
590
|
if "n_electrons" in kwargs:
|
509
591
|
n_electrons = kwargs["n_electrons"]
|
510
592
|
|
511
|
-
assert
|
512
|
-
assert
|
593
|
+
assert "one_body_integrals" in kwargs
|
594
|
+
assert "two_body_integrals" in kwargs
|
513
595
|
one_body_integrals = kwargs["one_body_integrals"]
|
514
596
|
kwargs.pop("one_body_integrals")
|
515
597
|
two_body_integrals = kwargs["two_body_integrals"]
|
@@ -534,7 +616,6 @@ class QuantumChemistryBase:
|
|
534
616
|
kwargs.pop("nuclear_repulsion")
|
535
617
|
|
536
618
|
if "active_space" not in kwargs:
|
537
|
-
|
538
619
|
active_orbitals = [i for i in range(one_body_integrals.shape[0])]
|
539
620
|
if "active_orbitals" in kwargs and kwargs["active_orbitals"] is not None:
|
540
621
|
active_orbitals = kwargs["active_orbitals"]
|
@@ -547,15 +628,21 @@ class QuantumChemistryBase:
|
|
547
628
|
if "reference_orbitals" in kwargs and kwargs["reference_orbitals"] is not None:
|
548
629
|
reference_orbitals = kwargs["reference_orbitals"]
|
549
630
|
|
550
|
-
active_space = ActiveSpaceData(
|
551
|
-
|
631
|
+
active_space = ActiveSpaceData(
|
632
|
+
active_orbitals=sorted(active_orbitals), reference_orbitals=sorted(reference_orbitals)
|
633
|
+
)
|
552
634
|
kwargs["active_space"] = active_space
|
553
635
|
|
554
636
|
if "basis_name" not in kwargs:
|
555
637
|
kwargs["basis_name"] = self.parameters.basis_set
|
556
638
|
|
557
|
-
manager = IntegralManager(
|
558
|
-
|
639
|
+
manager = IntegralManager(
|
640
|
+
one_body_integrals=one_body_integrals,
|
641
|
+
two_body_integrals=two_body_integrals,
|
642
|
+
constant_term=constant_part,
|
643
|
+
*args,
|
644
|
+
**kwargs,
|
645
|
+
)
|
559
646
|
|
560
647
|
return manager
|
561
648
|
|
@@ -581,30 +668,177 @@ class QuantumChemistryBase:
|
|
581
668
|
if ignore_active_space:
|
582
669
|
U = orbital_coefficients
|
583
670
|
else:
|
584
|
-
for kk,k in enumerate(active_indices):
|
585
|
-
for ll,l in enumerate(active_indices):
|
671
|
+
for kk, k in enumerate(active_indices):
|
672
|
+
for ll, l in enumerate(active_indices):
|
586
673
|
U[k][l] = orbital_coefficients[kk][ll]
|
587
674
|
|
588
675
|
# can not be an instance of a specific backend (otherwise we get inconsistencies with classical methods in the backend)
|
589
676
|
integral_manager = copy.deepcopy(self.integral_manager)
|
590
677
|
integral_manager.transform_orbitals(U=U, name=name)
|
591
|
-
result = QuantumChemistryBase(
|
678
|
+
result = QuantumChemistryBase(
|
679
|
+
parameters=self.parameters, integral_manager=integral_manager, transformation=self.transformation
|
680
|
+
)
|
592
681
|
return result
|
593
|
-
|
682
|
+
|
594
683
|
def orthonormalize_basis_orbitals(self):
|
595
684
|
# backward compatibility
|
596
685
|
return self.use_native_orbitals()
|
597
|
-
|
598
|
-
def use_native_orbitals(self, inplace=False):
|
686
|
+
|
687
|
+
def use_native_orbitals(self, inplace=False, core: list = None, *args, **kwargs):
|
599
688
|
"""
|
689
|
+
Parameters
|
690
|
+
----------
|
691
|
+
inplace:
|
692
|
+
update current molecule or return a new instance
|
693
|
+
core:
|
694
|
+
list of core orbital indices (optional) — orbitals will be frozen and treated as doubly occupied. The orbitals correspond to
|
695
|
+
the currently used orbitals of the molecule (default is usually canonical HF), see mol.print_basis_info() if unsure. Providing core
|
696
|
+
orbitals is optional; the default is inherited from the active space set in self.integral_manager. If core is provided, the
|
697
|
+
corresponding active native orbitals will be chosen based on their overlap with the core orbitals.
|
698
|
+
active(in kwargs):
|
699
|
+
list the active orbital indices (optional, in kwargs) - on the Native Orbital schema. Default: All orbitals, if core (see above) is provided,
|
700
|
+
then the default is to automatically select the active orbitals based on their overlap with the provided core orbitals (selectint the N-|core|
|
701
|
+
orbitals that have smallest overlap with coree).
|
702
|
+
As an example, Assume the input geometry was H, He, H. active=[0,1,2] is selecting the (orthonormalized) atomic 1s (left H), 1s (He), 1s (right H).
|
703
|
+
If core=[0] and active is not set, then active=[0,2] will be selected automatically (as the 1s He atomic orbital will have the largest overlap
|
704
|
+
with the lowest energy HF orbital).
|
600
705
|
Returns
|
601
706
|
-------
|
602
707
|
New molecule in the native (orthonormalized) basis given
|
603
708
|
e.g. for standard basis sets the orbitals are orthonormalized Gaussian Basis Functions
|
604
709
|
"""
|
605
|
-
|
606
|
-
|
710
|
+
c = copy.deepcopy(self.integral_manager.orbital_coefficients)
|
711
|
+
s = self.integral_manager.overlap_integrals
|
712
|
+
d = self.integral_manager.get_orthonormalized_orbital_coefficients()
|
713
|
+
|
714
|
+
def inner(a, b, s):
|
715
|
+
return numpy.sum(numpy.multiply(numpy.outer(a, b), s))
|
607
716
|
|
717
|
+
def orthogonalize(c, d, s):
|
718
|
+
"""
|
719
|
+
:return: orthogonalized orbitals with core HF orbitals and active Orthongonalized Native orbitals.
|
720
|
+
"""
|
721
|
+
### Computing Core-Active overlap Matrix
|
722
|
+
# sbar_{ki} = \langle \phi_k | \varphi_i \rangle = \sum_{m,n} c_{nk}d_{mi}\langle \chi_n | \chi_m \rangle
|
723
|
+
# c_{nk} = HF coeffs, d_{mi} = nat orb coef s_{mn} = Atomic Overlap Matrix
|
724
|
+
# k \in active orbs, i \in core orbs, m,n \in basis coeffs
|
725
|
+
# sbar = np.einsum('nk,mi,nm->ki', c, d, s) #only works if active == to_active
|
726
|
+
c = c.T
|
727
|
+
d = d.T
|
728
|
+
sbar = numpy.zeros(shape=s.shape)
|
729
|
+
for k in active:
|
730
|
+
for i in core:
|
731
|
+
sbar[i][to_active[k]] = inner(c[i], d[k], s)
|
732
|
+
### Projecting out Core orbitals from the Native ones
|
733
|
+
# dbar_{ji} = d_{ji} - \sum_k sbar_{ki}c_{jk}
|
734
|
+
# k \in active, i \in core, j in basis coeffs
|
735
|
+
dbar = numpy.zeros(shape=s.shape)
|
736
|
+
|
737
|
+
for j in active:
|
738
|
+
dbar[to_active[j]] = d[j]
|
739
|
+
for i in core:
|
740
|
+
temp = sbar[i][to_active[j]] * c[i]
|
741
|
+
dbar[to_active[j]] -= temp
|
742
|
+
### Projected-out Nat Orbs Normalization
|
743
|
+
for i in to_active.values():
|
744
|
+
norm = numpy.sqrt(numpy.sum(numpy.multiply(numpy.outer(dbar[i], dbar[i]), s.T)))
|
745
|
+
if not numpy.isclose(norm, 0):
|
746
|
+
dbar[i] = dbar[i] / norm
|
747
|
+
### Reintroducing the New Coeffs on the HF coeff matrix
|
748
|
+
for j in to_active.values():
|
749
|
+
c[j] = dbar[j]
|
750
|
+
### Compute new orbital overlap matrix:
|
751
|
+
sprima = numpy.eye(len(c))
|
752
|
+
for idx, i in enumerate(to_active.values()):
|
753
|
+
for j in [*to_active.values()][idx:]:
|
754
|
+
sprima[i][j] = inner(c[i], c[j], s)
|
755
|
+
sprima[j][i] = sprima[i][j]
|
756
|
+
### Symmetric orthonormalization
|
757
|
+
lam_s, l_s = numpy.linalg.eigh(sprima)
|
758
|
+
lam_s = lam_s * numpy.eye(len(lam_s))
|
759
|
+
lam_sqrt_inv = numpy.sqrt(numpy.linalg.inv(lam_s))
|
760
|
+
symm_orthog = numpy.dot(l_s, numpy.dot(lam_sqrt_inv, l_s.T))
|
761
|
+
return symm_orthog.dot(c).T
|
762
|
+
|
763
|
+
def get_active(core):
|
764
|
+
ov = numpy.zeros(shape=(len(self.integral_manager.orbitals)))
|
765
|
+
for i in core:
|
766
|
+
for j in range(len(d)):
|
767
|
+
ov[j] += numpy.abs(inner(c.T[i], d.T[j], s))
|
768
|
+
act = []
|
769
|
+
for i in range(len(self.integral_manager.orbitals) - len(core)):
|
770
|
+
idx = numpy.argmin(ov)
|
771
|
+
act.append(idx)
|
772
|
+
ov[idx] = 1 * len(core)
|
773
|
+
act.sort()
|
774
|
+
return act
|
775
|
+
|
776
|
+
def get_core(active):
|
777
|
+
ov = numpy.zeros(shape=(len(self.integral_manager.orbitals)))
|
778
|
+
for i in active:
|
779
|
+
for j in range(len(d)):
|
780
|
+
ov[j] += numpy.abs(inner(d.T[i], c.T[j], s))
|
781
|
+
co = []
|
782
|
+
for i in range(len(self.integral_manager.orbitals) - len(active)):
|
783
|
+
idx = numpy.argmin(ov)
|
784
|
+
co.append(idx)
|
785
|
+
ov[idx] = 1 * len(active)
|
786
|
+
co.sort()
|
787
|
+
return co
|
788
|
+
|
789
|
+
active = None
|
790
|
+
if not self.integral_manager.active_space_is_trivial() and core is None:
|
791
|
+
core = [i.idx_total for i in self.integral_manager.orbitals if i.idx is None]
|
792
|
+
if "active" in kwargs:
|
793
|
+
active = kwargs["active"]
|
794
|
+
kwargs.pop("active")
|
795
|
+
if core is None:
|
796
|
+
core = get_core(active)
|
797
|
+
else:
|
798
|
+
if active is None:
|
799
|
+
if core is None:
|
800
|
+
core = []
|
801
|
+
active = [i for i in range(len(self.integral_manager.orbitals))]
|
802
|
+
else:
|
803
|
+
if isinstance(core, int):
|
804
|
+
core = [core]
|
805
|
+
active = get_active(core)
|
806
|
+
assert len(active) + len(core) == len(self.integral_manager.orbitals)
|
807
|
+
to_active = [i for i in range(len(self.integral_manager.orbitals)) if i not in core]
|
808
|
+
to_active = {active[i]: to_active[i] for i in range(len(active))}
|
809
|
+
if len(core):
|
810
|
+
coeff = orthogonalize(c, d, s)
|
811
|
+
if inplace:
|
812
|
+
self.integral_manager = self.initialize_integral_manager(
|
813
|
+
one_body_integrals=self.integral_manager.one_body_integrals,
|
814
|
+
two_body_integrals=self.integral_manager.two_body_integrals,
|
815
|
+
constant_term=self.integral_manager.constant_term,
|
816
|
+
active_orbitals=[*to_active.values()],
|
817
|
+
reference_orbitals=[i.idx_total for i in self.integral_manager.reference_orbitals],
|
818
|
+
frozen_orbitals=core,
|
819
|
+
orbital_coefficients=coeff,
|
820
|
+
overlap_integrals=s,
|
821
|
+
)
|
822
|
+
return self
|
823
|
+
else:
|
824
|
+
integral_manager = self.initialize_integral_manager(
|
825
|
+
one_body_integrals=self.integral_manager.one_body_integrals,
|
826
|
+
two_body_integrals=self.integral_manager.two_body_integrals,
|
827
|
+
constant_term=self.integral_manager.constant_term,
|
828
|
+
active_orbitals=[*to_active.values()],
|
829
|
+
reference_orbitals=[i.idx_total for i in self.integral_manager.reference_orbitals],
|
830
|
+
frozen_orbitals=core,
|
831
|
+
orbital_coefficients=coeff,
|
832
|
+
overlap_integrals=s,
|
833
|
+
)
|
834
|
+
parameters = copy.deepcopy(self.parameters)
|
835
|
+
result = QuantumChemistryBase(
|
836
|
+
parameters=parameters,
|
837
|
+
integral_manager=integral_manager,
|
838
|
+
transformation=self.transformation,
|
839
|
+
active_orbitals=[*to_active.values()],
|
840
|
+
)
|
841
|
+
return result
|
608
842
|
# can not be an instance of a specific backend (otherwise we get inconsistencies with classical methods in the backend)
|
609
843
|
if inplace:
|
610
844
|
self.integral_manager.transform_to_native_orbitals()
|
@@ -612,10 +846,11 @@ class QuantumChemistryBase:
|
|
612
846
|
else:
|
613
847
|
integral_manager = copy.deepcopy(self.integral_manager)
|
614
848
|
integral_manager.transform_to_native_orbitals()
|
615
|
-
result = QuantumChemistryBase(
|
849
|
+
result = QuantumChemistryBase(
|
850
|
+
parameters=self.parameters, integral_manager=integral_manager, transformation=self.transformation
|
851
|
+
)
|
616
852
|
return result
|
617
853
|
|
618
|
-
|
619
854
|
def do_make_molecule(self, *args, **kwargs):
|
620
855
|
"""
|
621
856
|
Called by self.make_molecule with args and kwargs passed through
|
@@ -626,7 +861,7 @@ class QuantumChemistryBase:
|
|
626
861
|
constant_term, one_body_integrals, two_body_integrals = self.integral_manager.get_integrals(ordering="of")
|
627
862
|
two_body_integrals = two_body_integrals.reorder(to="of")
|
628
863
|
|
629
|
-
if
|
864
|
+
if "n_orbitals" in kwargs:
|
630
865
|
n_orbitals = kwargs["n_orbitals"]
|
631
866
|
else:
|
632
867
|
n_orbitals = one_body_integrals.shape[0]
|
@@ -679,8 +914,8 @@ class QuantumChemistryBase:
|
|
679
914
|
Compute annihilation operator on spin-orbital in qubit representation
|
680
915
|
Spin-orbital order is always (up,down,up,down,...)
|
681
916
|
"""
|
682
|
-
assert orbital<=self.n_orbitals*2
|
683
|
-
aop = openfermion.ops.FermionOperator(f
|
917
|
+
assert orbital <= self.n_orbitals * 2
|
918
|
+
aop = openfermion.ops.FermionOperator(f"{orbital}", coefficient)
|
684
919
|
return self.transformation(aop)
|
685
920
|
|
686
921
|
def make_creation_op(self, orbital, coefficient=1.0):
|
@@ -688,8 +923,8 @@ class QuantumChemistryBase:
|
|
688
923
|
Compute creation operator on spin-orbital in qubit representation
|
689
924
|
Spin-orbital order is always (up,down,up,down,...)
|
690
925
|
"""
|
691
|
-
assert orbital<=self.n_orbitals*2
|
692
|
-
cop = openfermion.ops.FermionOperator(f
|
926
|
+
assert orbital <= self.n_orbitals * 2
|
927
|
+
cop = openfermion.ops.FermionOperator(f"{orbital}^", coefficient)
|
693
928
|
return self.transformation(cop)
|
694
929
|
|
695
930
|
def make_number_op(self, orbital):
|
@@ -699,7 +934,7 @@ class QuantumChemistryBase:
|
|
699
934
|
"""
|
700
935
|
num_op = self.make_creation_op(orbital) * self.make_annihilation_op(orbital)
|
701
936
|
return num_op
|
702
|
-
|
937
|
+
|
703
938
|
def make_sz_op(self):
|
704
939
|
"""
|
705
940
|
Compute the spin_z operator of the molecule in qubit representation
|
@@ -707,8 +942,8 @@ class QuantumChemistryBase:
|
|
707
942
|
sz = QubitHamiltonian()
|
708
943
|
for i in range(0, self.n_orbitals * 2, 2):
|
709
944
|
one = 0.5 * self.make_creation_op(i) * self.make_annihilation_op(i)
|
710
|
-
two = 0.5 * self.make_creation_op(i+1) * self.make_annihilation_op(i+1)
|
711
|
-
sz +=
|
945
|
+
two = 0.5 * self.make_creation_op(i + 1) * self.make_annihilation_op(i + 1)
|
946
|
+
sz += one - two
|
712
947
|
return sz
|
713
948
|
|
714
949
|
def make_sp_op(self):
|
@@ -717,7 +952,7 @@ class QuantumChemistryBase:
|
|
717
952
|
"""
|
718
953
|
sp = QubitHamiltonian()
|
719
954
|
for i in range(self.n_orbitals):
|
720
|
-
sp += self.make_creation_op(i*2) * self.make_annihilation_op(i*2 + 1)
|
955
|
+
sp += self.make_creation_op(i * 2) * self.make_annihilation_op(i * 2 + 1)
|
721
956
|
return sp
|
722
957
|
|
723
958
|
def make_sm_op(self):
|
@@ -726,7 +961,7 @@ class QuantumChemistryBase:
|
|
726
961
|
"""
|
727
962
|
sm = QubitHamiltonian()
|
728
963
|
for i in range(self.n_orbitals):
|
729
|
-
sm += self.make_creation_op(i*2 + 1) * self.make_annihilation_op(i*2)
|
964
|
+
sm += self.make_creation_op(i * 2 + 1) * self.make_annihilation_op(i * 2)
|
730
965
|
return sm
|
731
966
|
|
732
967
|
def make_s2_op(self):
|
@@ -751,7 +986,8 @@ class QuantumChemistryBase:
|
|
751
986
|
# warnings for backward comp
|
752
987
|
if "active_indices" in kwargs:
|
753
988
|
warnings.warn(
|
754
|
-
"active space can't be changed in molecule. Will ignore active_orbitals passed to make_hamiltonian"
|
989
|
+
"active space can't be changed in molecule. Will ignore active_orbitals passed to make_hamiltonian"
|
990
|
+
)
|
755
991
|
|
756
992
|
of_molecule = self.make_molecule()
|
757
993
|
fop = of_molecule.get_molecular_hamiltonian()
|
@@ -781,7 +1017,7 @@ class QuantumChemistryBase:
|
|
781
1017
|
for p in range(n_orbitals):
|
782
1018
|
h[p, p] += 2 * obt[p, p]
|
783
1019
|
for q in range(n_orbitals):
|
784
|
-
h[p, q] += +
|
1020
|
+
h[p, q] += +tbt.elems[p, p, q, q]
|
785
1021
|
if p != q:
|
786
1022
|
g[p, q] += 2 * tbt.elems[p, q, q, p] - tbt.elems[p, q, p, q]
|
787
1023
|
|
@@ -793,7 +1029,7 @@ class QuantumChemistryBase:
|
|
793
1029
|
H += h[p, q] * Sm(up) * Sp(uq) + g[p, q] * Sm(up) * Sp(up) * Sm(uq) * Sp(uq)
|
794
1030
|
|
795
1031
|
if not self.transformation.up_then_down and not condensed:
|
796
|
-
alpha_map = {k.idx:self.transformation.up(k.idx) for k in self.orbitals}
|
1032
|
+
alpha_map = {k.idx: self.transformation.up(k.idx) for k in self.orbitals}
|
797
1033
|
H = H.map_qubits(alpha_map)
|
798
1034
|
return H
|
799
1035
|
|
@@ -804,7 +1040,9 @@ class QuantumChemistryBase:
|
|
804
1040
|
Create a MolecularHamiltonian as openfermion Class
|
805
1041
|
(used internally here, not used in tequila)
|
806
1042
|
"""
|
807
|
-
return self.make_molecule().get_molecular_hamiltonian(
|
1043
|
+
return self.make_molecule().get_molecular_hamiltonian(
|
1044
|
+
occupied_indices=occupied_indices, active_indices=active_indices
|
1045
|
+
)
|
808
1046
|
|
809
1047
|
def get_integrals(self, *args, **kwargs):
|
810
1048
|
"""
|
@@ -825,7 +1063,7 @@ class QuantumChemistryBase:
|
|
825
1063
|
return self.integral_manager.get_integrals(*args, **kwargs)
|
826
1064
|
|
827
1065
|
def compute_one_body_integrals(self):
|
828
|
-
"""
|
1066
|
+
"""convenience function"""
|
829
1067
|
c, h1, h2 = self.get_integrals()
|
830
1068
|
return h1
|
831
1069
|
|
@@ -902,7 +1140,9 @@ class QuantumChemistryBase:
|
|
902
1140
|
if not all(consistency):
|
903
1141
|
warnings.warn(
|
904
1142
|
"hcb_to_me: given circuit is not defined on all first {} qubits. Is this a HCB circuit?".format(
|
905
|
-
self.n_orbitals
|
1143
|
+
self.n_orbitals
|
1144
|
+
)
|
1145
|
+
)
|
906
1146
|
|
907
1147
|
# map to alpha qubits
|
908
1148
|
if condensed:
|
@@ -914,37 +1154,37 @@ class QuantumChemistryBase:
|
|
914
1154
|
UX = self.transformation.hcb_to_me()
|
915
1155
|
if UX is None:
|
916
1156
|
raise TequilaException(
|
917
|
-
"transformation={} has no hcb_to_me function implemented".format(self.transformation)
|
1157
|
+
"transformation={} has no hcb_to_me function implemented".format(self.transformation)
|
1158
|
+
)
|
918
1159
|
return alpha_U + UX
|
919
1160
|
|
920
|
-
def get_pair_specific_indices(
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
list of indices with pair-specific ansatz
|
1161
|
+
def get_pair_specific_indices(
|
1162
|
+
self, pair_info: str = None, include_singles: bool = True, general_excitations: bool = True
|
1163
|
+
) -> list:
|
1164
|
+
"""
|
1165
|
+
Assuming a pair-specific model, create a pair-specific index list
|
1166
|
+
to be used in make_upccgsd_ansatz(indices = ... )
|
1167
|
+
Excite from a set of references (i) to any pair coming from (i),
|
1168
|
+
i.e. any (i,j)/(j,i). If general excitations are allowed, also
|
1169
|
+
allow excitations from pairs to appendant pairs and reference.
|
1170
|
+
|
1171
|
+
Parameters
|
1172
|
+
----------
|
1173
|
+
pair_info
|
1174
|
+
file or list including information about pair structure
|
1175
|
+
references single number, pair double
|
1176
|
+
example: as file: "0,1,11,11,00,10" (hand over file name)
|
1177
|
+
in file, skip first row assuming some text with information
|
1178
|
+
as list:['0','1`','11','11','00','10']
|
1179
|
+
~> two reference orbitals 0 and 1,
|
1180
|
+
then two orbitals from pair 11, one from 00, one mixed 10
|
1181
|
+
include_singles
|
1182
|
+
include single excitations
|
1183
|
+
general_excitations
|
1184
|
+
allow general excitations
|
1185
|
+
Returns
|
1186
|
+
-------
|
1187
|
+
list of indices with pair-specific ansatz
|
948
1188
|
"""
|
949
1189
|
|
950
1190
|
if pair_info is None:
|
@@ -962,12 +1202,13 @@ class QuantumChemistryBase:
|
|
962
1202
|
generalized = 0
|
963
1203
|
for idx, p in enumerate(pairs):
|
964
1204
|
if len(p) == 1:
|
965
|
-
connect[idx] = [i for i in range(len(pairs))
|
966
|
-
if ((len(pairs[i]) == 2) and (str(idx) in pairs[i]))]
|
1205
|
+
connect[idx] = [i for i in range(len(pairs)) if ((len(pairs[i]) == 2) and (str(idx) in pairs[i]))]
|
967
1206
|
elif (len(p) == 2) and general_excitations:
|
968
|
-
connect[idx] = [
|
969
|
-
|
970
|
-
|
1207
|
+
connect[idx] = [
|
1208
|
+
i
|
1209
|
+
for i in range(len(pairs))
|
1210
|
+
if (((p[0] in pairs[i]) or (p[1] in pairs[i]) or str(i) in p) and not (i == idx))
|
1211
|
+
]
|
971
1212
|
elif len(p) > 2:
|
972
1213
|
raise TequilaException("Invalid reference of pair id.")
|
973
1214
|
|
@@ -996,7 +1237,6 @@ class QuantumChemistryBase:
|
|
996
1237
|
return tuple(idx)
|
997
1238
|
|
998
1239
|
def make_upccgsd_indices(self, key, reference_orbitals=None, *args, **kwargs):
|
999
|
-
|
1000
1240
|
if reference_orbitals is None:
|
1001
1241
|
reference_orbitals = [x.idx for x in self.reference_orbitals]
|
1002
1242
|
indices = []
|
@@ -1006,8 +1246,12 @@ class QuantumChemistryBase:
|
|
1006
1246
|
# ensures local connectivity
|
1007
1247
|
indices = [[(n, n + 1)] for n in range(self.n_orbitals - 1)]
|
1008
1248
|
elif hasattr(key, "lower") and "g" not in key.lower():
|
1009
|
-
indices = [
|
1010
|
-
|
1249
|
+
indices = [
|
1250
|
+
[(n, m)]
|
1251
|
+
for n in reference_orbitals
|
1252
|
+
for m in range(self.n_orbitals)
|
1253
|
+
if n < m and m not in reference_orbitals
|
1254
|
+
]
|
1011
1255
|
elif hasattr(key, "lower") and "g" in key.lower():
|
1012
1256
|
indices = [[(n, m)] for n in range(self.n_orbitals) for m in range(self.n_orbitals) if n < m]
|
1013
1257
|
else:
|
@@ -1016,68 +1260,122 @@ class QuantumChemistryBase:
|
|
1016
1260
|
|
1017
1261
|
return indices
|
1018
1262
|
|
1019
|
-
def make_hardcore_boson_upccgd_layer(
|
1020
|
-
|
1021
|
-
|
1022
|
-
assume_real: bool = True,
|
1023
|
-
*args, **kwargs):
|
1024
|
-
|
1263
|
+
def make_hardcore_boson_upccgd_layer(
|
1264
|
+
self, indices: list = "UpCCGD", label: str = None, assume_real: bool = True, *args, **kwargs
|
1265
|
+
):
|
1025
1266
|
if hasattr(indices, "lower"):
|
1026
1267
|
indices = self.make_upccgsd_indices(key=indices.lower())
|
1027
1268
|
|
1028
1269
|
UD = QCircuit()
|
1029
1270
|
for idx in indices:
|
1030
|
-
UD += self.make_hardcore_boson_excitation_gate(
|
1031
|
-
|
1271
|
+
UD += self.make_hardcore_boson_excitation_gate(
|
1272
|
+
indices=idx, angle=(idx, "D", label), assume_real=assume_real
|
1273
|
+
)
|
1032
1274
|
|
1033
1275
|
return UD
|
1034
|
-
|
1035
|
-
def make_spa_ansatz(self, edges, hcb=False,
|
1276
|
+
|
1277
|
+
def make_spa_ansatz(self, edges, hcb=False, use_units_of_pi=False, label=None, optimize=None, ladder=True):
|
1036
1278
|
"""
|
1037
1279
|
Separable Pair Ansatz (SPA) for general molecules
|
1038
|
-
see arxiv:
|
1280
|
+
see arxiv:
|
1039
1281
|
edges: a list of tuples that contain the orbital indices for the specific pairs
|
1040
1282
|
one example: edges=[(0,), (1,2,3), (4,5)] are three pairs, one with a single orbital [0], one with three orbitals [1,2,3] and one with two orbitals [4,5]
|
1041
1283
|
hcb: spa ansatz in the hcb (hardcore-boson) space without transforming to current transformation (e.g. JordanWigner), use this for example in combination with the self.make_hardcore_boson_hamiltonian() and see the article above for more info
|
1042
1284
|
use_units_of_pi: circuit angles in units of pi
|
1043
1285
|
label: label the variables in the circuit
|
1044
1286
|
optimize: optimize the circuit construction (see article). Results in shallow circuit from Ry and CNOT gates
|
1045
|
-
ladder: if true the excitation pattern will be local. E.g. in the pair from orbitals (1,2,3) we will have the excitations 1->2 and 2->3, if set to false we will have standard coupled-cluster style excitations - in this case this would be 1->2 and 1->3
|
1287
|
+
ladder: if true the excitation pattern will be local. E.g. in the pair from orbitals (1,2,3) we will have the excitations 1->2 and 2->3, if set to false we will have standard coupled-cluster style excitations - in this case this would be 1->2 and 1->3
|
1046
1288
|
"""
|
1289
|
+
|
1290
|
+
def _make_spa_tappered(edges, hcb=False, use_units_of_pi=False, label=None, optimize=False, ladder=True):
|
1291
|
+
U = QCircuit()
|
1292
|
+
cedges = []
|
1293
|
+
for edge in edges:
|
1294
|
+
if self.n_orbitals - 1 in edge:
|
1295
|
+
cedge = [i for i in edge if i != self.n_orbitals - 1] + [self.n_orbitals - 1]
|
1296
|
+
cedges.append(cedge)
|
1297
|
+
else:
|
1298
|
+
cedges.append(edge)
|
1299
|
+
if optimize:
|
1300
|
+
for edge_orbitals in cedges:
|
1301
|
+
edge_qubits = [self.transformation.up(i) for i in edge_orbitals]
|
1302
|
+
if not edge_qubits[0] == self.transformation.up(self.n_orbitals - 1):
|
1303
|
+
U += gates.X(edge_qubits[0])
|
1304
|
+
if len(edge_qubits) == 1:
|
1305
|
+
continue
|
1306
|
+
for i in range(1, len(edge_qubits)):
|
1307
|
+
q1 = edge_qubits[i]
|
1308
|
+
c = edge_qubits[i - 1]
|
1309
|
+
if not ladder:
|
1310
|
+
c = edge_qubits[0]
|
1311
|
+
angle = Variable(name=((edge_orbitals[i - 1], edge_orbitals[i]), "D", label))
|
1312
|
+
if use_units_of_pi:
|
1313
|
+
angle = angle * numpy.pi
|
1314
|
+
if (i - 1 == 0) and not (q1 == self.transformation.up(self.n_orbitals - 1)):
|
1315
|
+
U += gates.Ry(angle=angle, target=q1, control=None)
|
1316
|
+
elif (i - 1 == 0) and (q1 == self.transformation.up(self.n_orbitals - 1)):
|
1317
|
+
U += gates.Ry(angle=angle, target=c, control=None)
|
1318
|
+
else:
|
1319
|
+
U += gates.Ry(angle=angle, target=q1, control=c)
|
1320
|
+
if q1 != self.transformation.up(self.n_orbitals - 1):
|
1321
|
+
U += gates.CNOT(q1, c)
|
1322
|
+
if not hcb:
|
1323
|
+
U += self.hcb_to_me()
|
1324
|
+
return U
|
1325
|
+
else:
|
1326
|
+
return self.make_spa_ansatz(
|
1327
|
+
cedges, hcb=hcb, use_units_of_pi=use_units_of_pi, label=label, optimize=True, ladder=ladder
|
1328
|
+
)
|
1329
|
+
|
1047
1330
|
if edges is None:
|
1048
|
-
raise TequilaException(
|
1049
|
-
|
1331
|
+
raise TequilaException(
|
1332
|
+
"SPA ansatz within a standard orbital basis needs edges. Please provide with the keyword edges.\nExample: edges=[(0,1,2),(3,4)] would correspond to two edges created from orbitals (0,1,2) and (3,4), note that orbitals can only be assigned to a single edge"
|
1333
|
+
)
|
1334
|
+
|
1050
1335
|
# sanity checks
|
1051
1336
|
# current SPA implementation needs even number of electrons
|
1052
1337
|
if self.n_electrons % 2 != 0:
|
1053
|
-
raise TequilaException(
|
1338
|
+
raise TequilaException(
|
1339
|
+
"need even number of electrons for SPA ansatz.\n{} active electrons".format(self.n_electrons)
|
1340
|
+
)
|
1054
1341
|
# making sure that enough edges are assigned
|
1055
1342
|
n_edges = len(edges)
|
1056
|
-
if len(edges) != self.n_electrons//2:
|
1057
|
-
raise TequilaException(
|
1343
|
+
if len(edges) != self.n_electrons // 2:
|
1344
|
+
raise TequilaException(
|
1345
|
+
"number of edges need to be equal to number of active electrons//2\n{} edges given\n{} active electrons\nfrozen core is {}".format(
|
1346
|
+
len(edges), self.n_electrons, self.parameters.frozen_core
|
1347
|
+
)
|
1348
|
+
)
|
1058
1349
|
# making sure that orbitals are uniquely assigned to edges
|
1059
1350
|
for edge_qubits in edges:
|
1060
1351
|
for q1 in edge_qubits:
|
1061
1352
|
for edge2 in edges:
|
1062
|
-
if edge2==edge_qubits:
|
1353
|
+
if edge2 == edge_qubits:
|
1063
1354
|
continue
|
1064
1355
|
elif q1 in edge2:
|
1065
|
-
raise TequilaException(
|
1066
|
-
|
1356
|
+
raise TequilaException(
|
1357
|
+
"make_spa_ansatz: faulty list of edges, orbitals are overlapping e.g. orbital {} is in edge {} and edge {}".format(
|
1358
|
+
q1, edge_qubits, edge2
|
1359
|
+
)
|
1360
|
+
)
|
1361
|
+
|
1067
1362
|
# auto assign if the circuit construction is optimized
|
1068
1363
|
# depending on the current qubit encoding (if hcb_to_me is implemnented we can optimize)
|
1069
1364
|
if optimize is None:
|
1070
1365
|
try:
|
1071
1366
|
have_hcb_to_me = self.hcb_to_me() is not None
|
1072
|
-
except:
|
1367
|
+
except Exception:
|
1073
1368
|
have_hcb_to_me = False
|
1074
|
-
if have_hcb_to_me:
|
1075
|
-
optimize=True
|
1369
|
+
if have_hcb_to_me:
|
1370
|
+
optimize = True
|
1076
1371
|
else:
|
1077
|
-
optimize=False
|
1372
|
+
optimize = False
|
1078
1373
|
|
1079
1374
|
U = QCircuit()
|
1080
|
-
|
1375
|
+
if "TAPEREDBINARY" in self.transformation.name.upper():
|
1376
|
+
return _make_spa_tappered(
|
1377
|
+
edges=edges, hcb=hcb, use_units_of_pi=use_units_of_pi, label=label, optimize=optimize, ladder=ladder
|
1378
|
+
)
|
1081
1379
|
# construction of the optimized circuit
|
1082
1380
|
if optimize:
|
1083
1381
|
# circuit in HCB representation
|
@@ -1087,58 +1385,50 @@ class QuantumChemistryBase:
|
|
1087
1385
|
for edge_orbitals in edges:
|
1088
1386
|
edge_qubits = [self.transformation.up(i) for i in edge_orbitals]
|
1089
1387
|
U += gates.X(edge_qubits[0])
|
1090
|
-
if len(edge_qubits)==1:
|
1388
|
+
if len(edge_qubits) == 1:
|
1091
1389
|
continue
|
1092
|
-
for i in range(1,len(edge_qubits)):
|
1093
|
-
q1=edge_qubits[i]
|
1094
|
-
c=edge_qubits[i-1]
|
1390
|
+
for i in range(1, len(edge_qubits)):
|
1391
|
+
q1 = edge_qubits[i]
|
1392
|
+
c = edge_qubits[i - 1]
|
1095
1393
|
if not ladder:
|
1096
|
-
c=edge_qubits[0]
|
1097
|
-
angle=Variable(name=((edge_orbitals[i-1], edge_orbitals[i]), "D"
|
1394
|
+
c = edge_qubits[0]
|
1395
|
+
angle = Variable(name=((edge_orbitals[i - 1], edge_orbitals[i]), "D", label))
|
1098
1396
|
if use_units_of_pi:
|
1099
|
-
angle=angle*numpy.pi
|
1100
|
-
if i-1 == 0:
|
1397
|
+
angle = angle * numpy.pi
|
1398
|
+
if i - 1 == 0:
|
1101
1399
|
U += gates.Ry(angle=angle, target=q1, control=None)
|
1102
1400
|
else:
|
1103
1401
|
U += gates.Ry(angle=angle, target=q1, control=c)
|
1104
|
-
U += gates.CNOT(q1,c)
|
1105
|
-
|
1402
|
+
U += gates.CNOT(q1, c)
|
1106
1403
|
|
1107
1404
|
if not hcb:
|
1108
1405
|
U += self.hcb_to_me()
|
1109
1406
|
else:
|
1110
|
-
|
1407
|
+
orbs = [edge[0] for edge in edges]
|
1111
1408
|
if hcb:
|
1112
|
-
U = self.
|
1409
|
+
U = gates.X([self.transformation.up(i) for i in orbs])
|
1113
1410
|
else:
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
if self.orbitals[edge_qubits[0]] not in reference_orbitals:
|
1120
|
-
sane=False
|
1121
|
-
if len(edge_qubits)>1:
|
1122
|
-
for q1 in edge_qubits[1:]:
|
1123
|
-
if self.orbitals[q1] in reference_orbitals:
|
1124
|
-
sane=False
|
1125
|
-
if not sane:
|
1126
|
-
raise TequilaException("Non-Optimized SPA (e.g. with encodings that are not JW) will only work if the first orbitals of all SPA edges are occupied reference orbitals and all others are not. You gave edges={} and reference_orbitals are {}".format(edges, reference_orbitals))
|
1127
|
-
|
1411
|
+
state = [0] * 2 * self.n_orbitals
|
1412
|
+
for i in orbs:
|
1413
|
+
state[2 * i] = 1
|
1414
|
+
state[2 * i + 1] = 1
|
1415
|
+
U = self.prepare_reference(state)
|
1128
1416
|
for edge_qubits in edges:
|
1129
1417
|
previous = edge_qubits[0]
|
1130
|
-
if len(edge_qubits)>1:
|
1418
|
+
if len(edge_qubits) > 1:
|
1131
1419
|
for q1 in edge_qubits[1:]:
|
1132
1420
|
c = previous
|
1133
1421
|
if not ladder:
|
1134
1422
|
c = edge_qubits[0]
|
1135
|
-
angle = Variable(name=((c,q1), "D"
|
1423
|
+
angle = Variable(name=((c, q1), "D", label))
|
1136
1424
|
if use_units_of_pi:
|
1137
|
-
angle=angle*numpy.pi
|
1425
|
+
angle = angle * numpy.pi
|
1138
1426
|
if hcb:
|
1139
|
-
U += self.make_hardcore_boson_excitation_gate(indices=[(q1,c)],angle=angle)
|
1427
|
+
U += self.make_hardcore_boson_excitation_gate(indices=[(q1, c)], angle=angle)
|
1140
1428
|
else:
|
1141
|
-
U += self.make_excitation_gate(
|
1429
|
+
U += self.make_excitation_gate(
|
1430
|
+
indices=[(2 * c, 2 * q1), (2 * c + 1, 2 * q1 + 1)], angle=angle
|
1431
|
+
)
|
1142
1432
|
previous = q1
|
1143
1433
|
return U
|
1144
1434
|
|
@@ -1167,8 +1457,10 @@ class QuantumChemistryBase:
|
|
1167
1457
|
if "label" in kwargs:
|
1168
1458
|
label = kwargs["label"]
|
1169
1459
|
kwargs.pop("label")
|
1170
|
-
for i,subpart in enumerate(subparts[1:]):
|
1171
|
-
U += self.make_ansatz(
|
1460
|
+
for i, subpart in enumerate(subparts[1:]):
|
1461
|
+
U += self.make_ansatz(
|
1462
|
+
name=subpart, *args, label=(label, i), include_reference=False, hcb_optimization=False, **kwargs
|
1463
|
+
)
|
1172
1464
|
return U
|
1173
1465
|
|
1174
1466
|
if name == "uccsd":
|
@@ -1178,24 +1470,27 @@ class QuantumChemistryBase:
|
|
1178
1470
|
hcb = False
|
1179
1471
|
if "hcb" in name.lower():
|
1180
1472
|
hcb = True
|
1181
|
-
kwargs["hcb"]=hcb
|
1473
|
+
kwargs["hcb"] = hcb
|
1182
1474
|
return self.make_spa_ansatz(*args, **kwargs)
|
1183
1475
|
elif "d" in name or "s" in name:
|
1184
1476
|
return self.make_upccgsd_ansatz(name=name, *args, **kwargs)
|
1185
1477
|
else:
|
1186
1478
|
raise TequilaException("unknown ansatz with name={}".format(name))
|
1187
1479
|
|
1188
|
-
def make_upccgsd_ansatz(
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1480
|
+
def make_upccgsd_ansatz(
|
1481
|
+
self,
|
1482
|
+
include_reference: bool = True,
|
1483
|
+
name: str = "UpCCGSD",
|
1484
|
+
label: str = None,
|
1485
|
+
order: int = None,
|
1486
|
+
assume_real: bool = True,
|
1487
|
+
hcb_optimization: bool = None,
|
1488
|
+
spin_adapt_singles: bool = True,
|
1489
|
+
neglect_z: bool = False,
|
1490
|
+
mix_sd: bool = False,
|
1491
|
+
*args,
|
1492
|
+
**kwargs,
|
1493
|
+
):
|
1199
1494
|
"""
|
1200
1495
|
UpGCCSD Ansatz similar as described by Lee et. al.
|
1201
1496
|
|
@@ -1241,7 +1536,7 @@ class QuantumChemistryBase:
|
|
1241
1536
|
order = int(name.split("-")[0])
|
1242
1537
|
else:
|
1243
1538
|
order = 1
|
1244
|
-
except:
|
1539
|
+
except Exception:
|
1245
1540
|
order = 1
|
1246
1541
|
|
1247
1542
|
indices = self.make_upccgsd_indices(key=name)
|
@@ -1251,9 +1546,8 @@ class QuantumChemistryBase:
|
|
1251
1546
|
try:
|
1252
1547
|
if self.transformation.hcb_to_me() is None:
|
1253
1548
|
have_hcb_trafo = False
|
1254
|
-
except:
|
1549
|
+
except Exception:
|
1255
1550
|
have_hcb_trafo = False
|
1256
|
-
|
1257
1551
|
|
1258
1552
|
# consistency checks for optimization
|
1259
1553
|
if have_hcb_trafo and hcb_optimization is None and include_reference:
|
@@ -1262,13 +1556,17 @@ class QuantumChemistryBase:
|
|
1262
1556
|
hcb_optimization = True
|
1263
1557
|
if hcb_optimization and not have_hcb_trafo and "HCB" not in name:
|
1264
1558
|
raise TequilaException(
|
1265
|
-
"use_hcb={} but transformation={} has no
|
1266
|
-
hcb_optimization, self.transformation
|
1559
|
+
"use_hcb={} but transformation={} has no 'hcb_to_me' function. Try transformation='ReorderedJordanWigner'".format(
|
1560
|
+
hcb_optimization, self.transformation
|
1561
|
+
)
|
1562
|
+
)
|
1267
1563
|
if "S" in name and "HCB" in name:
|
1268
1564
|
if "HCB" in name and "S" in name:
|
1269
1565
|
raise Exception(
|
1270
1566
|
"name={}, Singles can't be realized without mapping back to the standard encoding leave S or HCB out of the name".format(
|
1271
|
-
name
|
1567
|
+
name
|
1568
|
+
)
|
1569
|
+
)
|
1272
1570
|
if hcb_optimization and mix_sd:
|
1273
1571
|
raise TequilaException("Mixed SD can not be employed together with HCB Optimization")
|
1274
1572
|
# convenience
|
@@ -1280,36 +1578,67 @@ class QuantumChemistryBase:
|
|
1280
1578
|
U = QCircuit()
|
1281
1579
|
if include_reference:
|
1282
1580
|
U = self.prepare_reference()
|
1283
|
-
U += self.make_upccgsd_layer(
|
1284
|
-
|
1285
|
-
|
1581
|
+
U += self.make_upccgsd_layer(
|
1582
|
+
include_singles=S,
|
1583
|
+
include_doubles=D,
|
1584
|
+
indices=indices,
|
1585
|
+
assume_real=assume_real,
|
1586
|
+
label=(label, 0),
|
1587
|
+
mix_sd=mix_sd,
|
1588
|
+
spin_adapt_singles=spin_adapt_singles,
|
1589
|
+
*args,
|
1590
|
+
**kwargs,
|
1591
|
+
)
|
1286
1592
|
else:
|
1287
1593
|
U = QCircuit()
|
1288
1594
|
if include_reference:
|
1289
1595
|
U = self.prepare_hardcore_boson_reference()
|
1290
1596
|
if D:
|
1291
|
-
U += self.make_hardcore_boson_upccgd_layer(
|
1292
|
-
|
1597
|
+
U += self.make_hardcore_boson_upccgd_layer(
|
1598
|
+
indices=indices, assume_real=assume_real, label=(label, 0), *args, **kwargs
|
1599
|
+
)
|
1293
1600
|
|
1294
1601
|
if "HCB" not in name and (include_reference or D):
|
1295
1602
|
U = self.hcb_to_me(U=U)
|
1296
1603
|
|
1297
1604
|
if S:
|
1298
|
-
U += self.make_upccgsd_singles(
|
1299
|
-
|
1300
|
-
|
1605
|
+
U += self.make_upccgsd_singles(
|
1606
|
+
indices=indices,
|
1607
|
+
assume_real=assume_real,
|
1608
|
+
label=(label, 0),
|
1609
|
+
spin_adapt_singles=spin_adapt_singles,
|
1610
|
+
neglect_z=neglect_z,
|
1611
|
+
*args,
|
1612
|
+
**kwargs,
|
1613
|
+
)
|
1301
1614
|
|
1302
1615
|
for k in range(1, order):
|
1303
|
-
U += self.make_upccgsd_layer(
|
1304
|
-
|
1616
|
+
U += self.make_upccgsd_layer(
|
1617
|
+
include_singles=S,
|
1618
|
+
include_doubles=D,
|
1619
|
+
indices=indices,
|
1620
|
+
label=(label, k),
|
1621
|
+
spin_adapt_singles=spin_adapt_singles,
|
1622
|
+
neglect_z=neglect_z,
|
1623
|
+
mix_sd=mix_sd,
|
1624
|
+
)
|
1305
1625
|
|
1306
1626
|
return U
|
1307
1627
|
|
1308
|
-
def make_upccgsd_layer(
|
1309
|
-
|
1310
|
-
|
1311
|
-
|
1312
|
-
|
1628
|
+
def make_upccgsd_layer(
|
1629
|
+
self,
|
1630
|
+
indices,
|
1631
|
+
include_singles: bool = True,
|
1632
|
+
include_doubles: bool = True,
|
1633
|
+
assume_real: bool = True,
|
1634
|
+
label=None,
|
1635
|
+
spin_adapt_singles: bool = True,
|
1636
|
+
angle_transform=None,
|
1637
|
+
mix_sd: bool = False,
|
1638
|
+
neglect_z: bool = False,
|
1639
|
+
*args,
|
1640
|
+
**kwargs,
|
1641
|
+
):
|
1313
1642
|
U = QCircuit()
|
1314
1643
|
for idx in indices:
|
1315
1644
|
assert len(idx) == 1
|
@@ -1318,29 +1647,56 @@ class QuantumChemistryBase:
|
|
1318
1647
|
if include_doubles:
|
1319
1648
|
if "jordanwigner" in self.transformation.name.lower() and not self.transformation.up_then_down:
|
1320
1649
|
# we can optimize with qubit excitations for the JW representation
|
1321
|
-
target = [
|
1322
|
-
|
1650
|
+
target = [
|
1651
|
+
self.transformation.up(idx[0]),
|
1652
|
+
self.transformation.up(idx[1]),
|
1653
|
+
self.transformation.down(idx[0]),
|
1654
|
+
self.transformation.down(idx[1]),
|
1655
|
+
]
|
1323
1656
|
U += gates.QubitExcitation(angle=angle, target=target, assume_real=assume_real, **kwargs)
|
1324
1657
|
else:
|
1325
|
-
U += self.make_excitation_gate(
|
1326
|
-
|
1327
|
-
|
1658
|
+
U += self.make_excitation_gate(
|
1659
|
+
angle=angle,
|
1660
|
+
indices=((2 * idx[0], 2 * idx[1]), (2 * idx[0] + 1, 2 * idx[1] + 1)),
|
1661
|
+
assume_real=assume_real,
|
1662
|
+
**kwargs,
|
1663
|
+
)
|
1328
1664
|
if include_singles and mix_sd:
|
1329
|
-
U += self.make_upccgsd_singles(
|
1330
|
-
|
1331
|
-
|
1665
|
+
U += self.make_upccgsd_singles(
|
1666
|
+
indices=[(idx,)],
|
1667
|
+
assume_real=assume_real,
|
1668
|
+
label=label,
|
1669
|
+
spin_adapt_singles=spin_adapt_singles,
|
1670
|
+
angle_transform=angle_transform,
|
1671
|
+
neglect_z=neglect_z,
|
1672
|
+
)
|
1332
1673
|
|
1333
1674
|
if include_singles and not mix_sd:
|
1334
|
-
U += self.make_upccgsd_singles(
|
1335
|
-
|
1336
|
-
|
1675
|
+
U += self.make_upccgsd_singles(
|
1676
|
+
indices=indices,
|
1677
|
+
assume_real=assume_real,
|
1678
|
+
label=label,
|
1679
|
+
spin_adapt_singles=spin_adapt_singles,
|
1680
|
+
angle_transform=angle_transform,
|
1681
|
+
neglect_z=neglect_z,
|
1682
|
+
)
|
1337
1683
|
return U
|
1338
1684
|
|
1339
|
-
def make_upccgsd_singles(
|
1340
|
-
|
1685
|
+
def make_upccgsd_singles(
|
1686
|
+
self,
|
1687
|
+
indices="UpCCGSD",
|
1688
|
+
spin_adapt_singles=True,
|
1689
|
+
label=None,
|
1690
|
+
angle_transform=None,
|
1691
|
+
assume_real=True,
|
1692
|
+
neglect_z=False,
|
1693
|
+
*args,
|
1694
|
+
**kwargs,
|
1695
|
+
):
|
1341
1696
|
if neglect_z and "jordanwigner" not in self.transformation.name.lower():
|
1342
1697
|
raise TequilaException(
|
1343
|
-
"neglegt-z approximation in UpCCGSD singles needs the (Reversed)JordanWigner representation"
|
1698
|
+
"neglegt-z approximation in UpCCGSD singles needs the (Reversed)JordanWigner representation"
|
1699
|
+
)
|
1344
1700
|
if hasattr(indices, "lower"):
|
1345
1701
|
indices = self.make_upccgsd_indices(key=indices)
|
1346
1702
|
|
@@ -1358,10 +1714,12 @@ class QuantumChemistryBase:
|
|
1358
1714
|
U += gates.QubitExcitation(angle=angle, target=targeta, assume_real=assume_real, **kwargs)
|
1359
1715
|
U += gates.QubitExcitation(angle=angle, target=targetb, assume_real=assume_real, **kwargs)
|
1360
1716
|
else:
|
1361
|
-
U += self.make_excitation_gate(
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1717
|
+
U += self.make_excitation_gate(
|
1718
|
+
angle=angle, indices=[(2 * idx[0], 2 * idx[1])], assume_real=assume_real, **kwargs
|
1719
|
+
)
|
1720
|
+
U += self.make_excitation_gate(
|
1721
|
+
angle=angle, indices=[(2 * idx[0] + 1, 2 * idx[1] + 1)], assume_real=assume_real, **kwargs
|
1722
|
+
)
|
1365
1723
|
else:
|
1366
1724
|
angle1 = (idx, "SU", label)
|
1367
1725
|
angle2 = (idx, "SD", label)
|
@@ -1374,21 +1732,27 @@ class QuantumChemistryBase:
|
|
1374
1732
|
U += gates.QubitExcitation(angle=angle1, target=targeta, assume_real=assume_real, *kwargs)
|
1375
1733
|
U += gates.QubitExcitation(angle=angle2, target=targetb, assume_real=assume_real, *kwargs)
|
1376
1734
|
else:
|
1377
|
-
U += self.make_excitation_gate(
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1735
|
+
U += self.make_excitation_gate(
|
1736
|
+
angle=angle1, indices=[(2 * idx[0], 2 * idx[1])], assume_real=assume_real, **kwargs
|
1737
|
+
)
|
1738
|
+
U += self.make_excitation_gate(
|
1739
|
+
angle=angle2, indices=[(2 * idx[0] + 1, 2 * idx[1] + 1)], assume_real=assume_real, **kwargs
|
1740
|
+
)
|
1381
1741
|
|
1382
1742
|
return U
|
1383
1743
|
|
1384
|
-
def make_uccsd_ansatz(
|
1385
|
-
|
1386
|
-
|
1387
|
-
|
1388
|
-
|
1389
|
-
|
1390
|
-
|
1391
|
-
|
1744
|
+
def make_uccsd_ansatz(
|
1745
|
+
self,
|
1746
|
+
trotter_steps: int = 1,
|
1747
|
+
initial_amplitudes: typing.Union[str, Amplitudes, ClosedShellAmplitudes] = None,
|
1748
|
+
include_reference_ansatz=True,
|
1749
|
+
parametrized=True,
|
1750
|
+
threshold=1.0e-8,
|
1751
|
+
add_singles=None,
|
1752
|
+
screening=True,
|
1753
|
+
*args,
|
1754
|
+
**kwargs,
|
1755
|
+
) -> QCircuit:
|
1392
1756
|
"""
|
1393
1757
|
|
1394
1758
|
Parameters
|
@@ -1420,8 +1784,9 @@ class QuantumChemistryBase:
|
|
1420
1784
|
if initial_amplitudes.lower() == "mp2" and add_singles is None:
|
1421
1785
|
add_singles = True
|
1422
1786
|
elif initial_amplitudes is not None and add_singles is not None:
|
1423
|
-
warnings.warn(
|
1424
|
-
|
1787
|
+
warnings.warn(
|
1788
|
+
"make_uccsd_anstatz: add_singles has no effect when explicit amplitudes are passed down", TequilaWarning
|
1789
|
+
)
|
1425
1790
|
elif add_singles is None:
|
1426
1791
|
add_singles = True
|
1427
1792
|
|
@@ -1446,13 +1811,13 @@ class QuantumChemistryBase:
|
|
1446
1811
|
amplitudes = self.compute_amplitudes(method=initial_amplitudes.lower())
|
1447
1812
|
except Exception as exc:
|
1448
1813
|
raise TequilaException(
|
1449
|
-
"{}\nDon't know how to initialize
|
1814
|
+
"{}\nDon't know how to initialize '{}' amplitudes".format(exc, initial_amplitudes)
|
1815
|
+
)
|
1450
1816
|
if amplitudes is None:
|
1451
1817
|
tia = None
|
1452
|
-
if add_singles:
|
1453
|
-
|
1454
|
-
|
1455
|
-
tIA=tia)
|
1818
|
+
if add_singles:
|
1819
|
+
tia = numpy.zeros(shape=[nocc, nvirt])
|
1820
|
+
amplitudes = ClosedShellAmplitudes(tIjAb=numpy.zeros(shape=[nocc, nocc, nvirt, nvirt]), tIA=tia)
|
1456
1821
|
screening = False
|
1457
1822
|
|
1458
1823
|
closed_shell = isinstance(amplitudes, ClosedShellAmplitudes)
|
@@ -1465,10 +1830,9 @@ class QuantumChemistryBase:
|
|
1465
1830
|
amplitudes = amplitudes.make_parameter_dictionary(threshold=threshold, screening=screening)
|
1466
1831
|
amplitudes = dict(sorted(amplitudes.items(), key=lambda x: numpy.fabs(x[1]), reverse=True))
|
1467
1832
|
for key, t in amplitudes.items():
|
1468
|
-
assert
|
1833
|
+
assert len(key) % 2 == 0
|
1469
1834
|
if not numpy.isclose(t, 0.0, atol=threshold) or not screening:
|
1470
1835
|
if closed_shell:
|
1471
|
-
|
1472
1836
|
if len(key) == 2 and add_singles:
|
1473
1837
|
# singles
|
1474
1838
|
angle = 2.0 * t
|
@@ -1503,8 +1867,12 @@ class QuantumChemistryBase:
|
|
1503
1867
|
for idx, angle in indices.items():
|
1504
1868
|
converted = [(idx[2 * i], idx[2 * i + 1]) for i in range(len(idx) // 2)]
|
1505
1869
|
UCCSD += self.make_excitation_gate(indices=converted, angle=factor * angle)
|
1506
|
-
if
|
1507
|
-
|
1870
|
+
if (
|
1871
|
+
hasattr(initial_amplitudes, "lower")
|
1872
|
+
and initial_amplitudes.lower() == "mp2"
|
1873
|
+
and parametrized
|
1874
|
+
and add_singles
|
1875
|
+
):
|
1508
1876
|
# mp2 has no singles, need to initialize them here (if not parametrized initializling as 0.0 makes no sense though)
|
1509
1877
|
UCCSD += self.make_upccgsd_layer(indices="upccsd", include_singles=True, include_doubles=False)
|
1510
1878
|
return Uref + UCCSD
|
@@ -1559,16 +1927,23 @@ class QuantumChemistryBase:
|
|
1559
1927
|
H = self.make_hamiltonian()
|
1560
1928
|
E = ExpectationValue(H=H, U=U)
|
1561
1929
|
from tequila import minimize
|
1930
|
+
|
1562
1931
|
return minimize(objective=E, *args, **kwargs).energy
|
1563
1932
|
else:
|
1564
1933
|
from tequila.quantumchemistry import INSTALLED_QCHEMISTRY_BACKENDS
|
1934
|
+
|
1565
1935
|
if "pyscf" not in INSTALLED_QCHEMISTRY_BACKENDS:
|
1566
1936
|
raise TequilaException(
|
1567
|
-
"PySCF needs to be installed to compute {}/{}".format(method, self.parameters.basis_set)
|
1937
|
+
"PySCF needs to be installed to compute {}/{}".format(method, self.parameters.basis_set)
|
1938
|
+
)
|
1568
1939
|
else:
|
1569
1940
|
from tequila.quantumchemistry import QuantumChemistryPySCF
|
1941
|
+
|
1570
1942
|
molx = QuantumChemistryPySCF.from_tequila(self)
|
1571
|
-
return molx.compute_energy(method=method)
|
1943
|
+
return molx.compute_energy(method=method, **kwargs)
|
1944
|
+
|
1945
|
+
def compute_fci(self, *args, **kwargs):
|
1946
|
+
raise NotImplementedError("compute_fci only implemented for the 'pyscf' backend")
|
1572
1947
|
|
1573
1948
|
def compute_fock_matrix(self):
|
1574
1949
|
c, h, g = self.get_integrals()
|
@@ -1579,10 +1954,10 @@ class QuantumChemistryBase:
|
|
1579
1954
|
F = numpy.zeros(shape=h.shape)
|
1580
1955
|
for k in range(F.shape[0]):
|
1581
1956
|
for l in range(F.shape[1]):
|
1582
|
-
tmp = h[k,l]
|
1957
|
+
tmp = h[k, l]
|
1583
1958
|
for ii in self.reference_orbitals:
|
1584
1959
|
i = ii.idx
|
1585
|
-
tmp +=
|
1960
|
+
tmp += 2.0 * g.elems[k, i, l, i] - g.elems[k, i, i, l]
|
1586
1961
|
F[k, l] = tmp
|
1587
1962
|
return F
|
1588
1963
|
|
@@ -1603,7 +1978,7 @@ class QuantumChemistryBase:
|
|
1603
1978
|
-------
|
1604
1979
|
|
1605
1980
|
"""
|
1606
|
-
c,h,g = self.get_integrals()
|
1981
|
+
c, h, g = self.get_integrals()
|
1607
1982
|
fi = self.compute_fock_matrix()
|
1608
1983
|
self.is_canonical(verify=True, fock_matrix=fi)
|
1609
1984
|
fi = numpy.diag(fi)
|
@@ -1612,13 +1987,18 @@ class QuantumChemistryBase:
|
|
1612
1987
|
ei = fi[:nocc]
|
1613
1988
|
ai = fi[nocc:]
|
1614
1989
|
abgij = g.elems[nocc:, nocc:, :nocc, :nocc]
|
1615
|
-
amplitudes =
|
1616
|
-
|
1990
|
+
amplitudes = (
|
1991
|
+
abgij
|
1992
|
+
* 1.0
|
1993
|
+
/ (ei.reshape(1, 1, -1, 1) + ei.reshape(1, 1, 1, -1) - ai.reshape(-1, 1, 1, 1) - ai.reshape(1, -1, 1, 1))
|
1994
|
+
)
|
1617
1995
|
|
1618
|
-
result = ClosedShellAmplitudes(tIjAb=numpy.einsum(
|
1996
|
+
result = ClosedShellAmplitudes(tIjAb=numpy.einsum("abij -> ijab", amplitudes, optimize="greedy"))
|
1619
1997
|
|
1620
1998
|
if return_energy:
|
1621
|
-
E = 2.0 * numpy.einsum(
|
1999
|
+
E = 2.0 * numpy.einsum("abij,abij->", amplitudes, abgij) - numpy.einsum(
|
2000
|
+
"abji,abij", amplitudes, abgij, optimize="greedy"
|
2001
|
+
)
|
1622
2002
|
return result, E
|
1623
2003
|
else:
|
1624
2004
|
return result
|
@@ -1632,6 +2012,7 @@ class QuantumChemistryBase:
|
|
1632
2012
|
@dataclass
|
1633
2013
|
class ResultCIS:
|
1634
2014
|
""" """
|
2015
|
+
|
1635
2016
|
omegas: typing.List[numbers.Real] # excitation energies [omega0, ...]
|
1636
2017
|
amplitudes: typing.List[ClosedShellAmplitudes] # corresponding amplitudes [x_{ai}_0, ...]
|
1637
2018
|
|
@@ -1693,7 +2074,9 @@ class QuantumChemistryBase:
|
|
1693
2074
|
if fock_matrix is None:
|
1694
2075
|
fock_matrix = self.compute_fock_matrix()
|
1695
2076
|
|
1696
|
-
is_diagonal = numpy.isclose(
|
2077
|
+
is_diagonal = numpy.isclose(
|
2078
|
+
numpy.linalg.norm(fock_matrix - numpy.diag(numpy.diag(fock_matrix))), 0.0, atol=1.0e-4
|
2079
|
+
)
|
1697
2080
|
|
1698
2081
|
if not is_diagonal:
|
1699
2082
|
canonical = False
|
@@ -1707,14 +2090,17 @@ class QuantumChemistryBase:
|
|
1707
2090
|
canonical = False
|
1708
2091
|
|
1709
2092
|
if verify and not canonical:
|
1710
|
-
data={"reference_orbitals":refo, "fock_matrix":fock_matrix}
|
2093
|
+
data = {"reference_orbitals": refo, "fock_matrix": fock_matrix}
|
1711
2094
|
raise TequilaException(
|
1712
|
-
"orbitals are not canonical or can not be verified as such -> implemented method only works for standard orbitals (preferably from psi4)\n{}".format(
|
2095
|
+
"orbitals are not canonical or can not be verified as such -> implemented method only works for standard orbitals (preferably from psi4)\n{}".format(
|
2096
|
+
data
|
2097
|
+
)
|
2098
|
+
)
|
1713
2099
|
return canonical
|
1714
2100
|
|
1715
2101
|
@property
|
1716
2102
|
def rdm1(self):
|
1717
|
-
"""
|
2103
|
+
"""
|
1718
2104
|
Returns RMD1 if computed with compute_rdms function before
|
1719
2105
|
"""
|
1720
2106
|
if self._rdm1 is not None:
|
@@ -1735,9 +2121,18 @@ class QuantumChemistryBase:
|
|
1735
2121
|
print("2-RDM has not been computed. Return None for 2-RDM.")
|
1736
2122
|
return None
|
1737
2123
|
|
1738
|
-
def compute_rdms(
|
1739
|
-
|
1740
|
-
|
2124
|
+
def compute_rdms(
|
2125
|
+
self,
|
2126
|
+
U: QCircuit = None,
|
2127
|
+
variables: Variables = None,
|
2128
|
+
spin_free: bool = True,
|
2129
|
+
get_rdm1: bool = True,
|
2130
|
+
get_rdm2: bool = True,
|
2131
|
+
ordering="dirac",
|
2132
|
+
use_hcb: bool = False,
|
2133
|
+
rdm_trafo: QubitHamiltonian = None,
|
2134
|
+
evaluate=True,
|
2135
|
+
):
|
1741
2136
|
"""
|
1742
2137
|
Computes the one- and two-particle reduced density matrices (rdm1 and rdm2) given
|
1743
2138
|
a unitary U. This method uses the standard ordering in physics as denoted below.
|
@@ -1777,45 +2172,54 @@ class QuantumChemistryBase:
|
|
1777
2172
|
"""
|
1778
2173
|
# Check whether unitary circuit is not 0
|
1779
2174
|
if U is None:
|
1780
|
-
raise TequilaException(
|
2175
|
+
raise TequilaException("Need to specify a Quantum Circuit.")
|
1781
2176
|
# Check whether transformation is BKSF.
|
1782
2177
|
# Issue here: when a single operator acts only on a subset of qubits, BKSF might not yield the correct
|
1783
2178
|
# transformation, because it computes the number of qubits incorrectly in this case.
|
1784
2179
|
# A hotfix such as for symmetry_conserving_bravyi_kitaev would require deeper changes, thus omitted for now
|
1785
2180
|
if type(self.transformation).__name__ == "BravyiKitaevFast":
|
1786
2181
|
raise TequilaException(
|
1787
|
-
"The Bravyi-Kitaev-Superfast transformation does not support general FermionOperators yet."
|
2182
|
+
"The Bravyi-Kitaev-Superfast transformation does not support general FermionOperators yet."
|
2183
|
+
)
|
1788
2184
|
# Set up number of spin-orbitals and molecular orbitals respectively
|
1789
2185
|
n_SOs = 2 * self.n_orbitals
|
1790
2186
|
n_MOs = self.n_orbitals
|
1791
2187
|
|
1792
2188
|
# Check whether unitary circuit is not 0
|
1793
2189
|
if U is None:
|
1794
|
-
raise TequilaException(
|
2190
|
+
raise TequilaException("Need to specify a Quantum Circuit.")
|
1795
2191
|
|
1796
2192
|
def _get_hcb_op(op_tuple):
|
1797
|
-
|
1798
|
-
if
|
2193
|
+
"""Build the hardcore boson operators: b^\dagger_ib_j + h.c. in qubit encoding"""
|
2194
|
+
if len(op_tuple) == 2:
|
1799
2195
|
return 2 * Sm(op_tuple[0][0]) * Sp(op_tuple[1][0])
|
1800
|
-
elif
|
1801
|
-
if (
|
2196
|
+
elif len(op_tuple) == 4:
|
2197
|
+
if (op_tuple[0][0] == op_tuple[1][0]) and (op_tuple[2][0] == op_tuple[3][0]): # iijj uddu+duud
|
1802
2198
|
return Sm(op_tuple[0][0]) * Sp(op_tuple[2][0]) + Sm(op_tuple[2][0]) * Sp(op_tuple[0][0])
|
1803
|
-
if (
|
1804
|
-
|
2199
|
+
if (
|
2200
|
+
(op_tuple[0][0] == op_tuple[2][0])
|
2201
|
+
and (op_tuple[1][0] == op_tuple[3][0])
|
2202
|
+
and (op_tuple[0][0] != op_tuple[1][0])
|
2203
|
+
and (op_tuple[2][0] != op_tuple[3][0])
|
2204
|
+
): # ijij uuuu+dddd
|
1805
2205
|
return 4 * Sm(op_tuple[0][0]) * Sm(op_tuple[1][0]) * Sp(op_tuple[2][0]) * Sp(op_tuple[3][0])
|
1806
|
-
if (
|
1807
|
-
|
2206
|
+
if (
|
2207
|
+
(op_tuple[0][0] == op_tuple[3][0])
|
2208
|
+
and (op_tuple[1][0] == op_tuple[2][0])
|
2209
|
+
and (op_tuple[0][0] != op_tuple[1][0])
|
2210
|
+
and (op_tuple[2][0] != op_tuple[3][0])
|
2211
|
+
): # ijji abba
|
1808
2212
|
return -2 * Sm(op_tuple[0][0]) * Sm(op_tuple[1][0]) * Sp(op_tuple[2][0]) * Sp(op_tuple[3][0])
|
1809
2213
|
else:
|
1810
2214
|
return Zero()
|
1811
2215
|
|
1812
2216
|
def _get_of_op(operator_tuple):
|
1813
|
-
"""
|
2217
|
+
"""Returns operator given by a operator tuple as OpenFermion - Fermion operator"""
|
1814
2218
|
op = openfermion.FermionOperator(operator_tuple)
|
1815
2219
|
return op
|
1816
2220
|
|
1817
2221
|
def _get_qop_hermitian(of_operator) -> QubitHamiltonian:
|
1818
|
-
"""
|
2222
|
+
"""Returns Hermitian part of Fermion operator as QubitHamiltonian"""
|
1819
2223
|
qop = self.transformation(of_operator)
|
1820
2224
|
# qop = QubitHamiltonian(self.transformation(of_operator))
|
1821
2225
|
real, imag = qop.split(hermitian=True)
|
@@ -1823,10 +2227,11 @@ class QuantumChemistryBase:
|
|
1823
2227
|
return real
|
1824
2228
|
elif not real:
|
1825
2229
|
raise TequilaException(
|
1826
|
-
"Qubit Hamiltonian does not have a Hermitian part. Operator ={}".format(of_operator)
|
2230
|
+
"Qubit Hamiltonian does not have a Hermitian part. Operator ={}".format(of_operator)
|
2231
|
+
)
|
1827
2232
|
|
1828
2233
|
def _build_1bdy_operators_spinful() -> list:
|
1829
|
-
"""
|
2234
|
+
"""Returns spinful one-body operators as a symmetry-reduced list of QubitHamiltonians"""
|
1830
2235
|
# Exploit symmetry pq = qp
|
1831
2236
|
ops = []
|
1832
2237
|
for p in range(n_SOs):
|
@@ -1838,7 +2243,7 @@ class QuantumChemistryBase:
|
|
1838
2243
|
return ops
|
1839
2244
|
|
1840
2245
|
def _build_2bdy_operators_spinful() -> list:
|
1841
|
-
"""
|
2246
|
+
"""Returns spinful two-body operators as a symmetry-reduced list of QubitHamiltonians"""
|
1842
2247
|
# Exploit symmetries pqrs = -pqsr = -qprs = qpsr
|
1843
2248
|
# and = rspq
|
1844
2249
|
ops = []
|
@@ -1854,7 +2259,7 @@ class QuantumChemistryBase:
|
|
1854
2259
|
return ops
|
1855
2260
|
|
1856
2261
|
def _build_1bdy_operators_spinfree() -> list:
|
1857
|
-
"""
|
2262
|
+
"""Returns spinfree one-body operators as a symmetry-reduced list of QubitHamiltonians"""
|
1858
2263
|
# Exploit symmetry pq = qp (not changed by spin-summation)
|
1859
2264
|
ops = []
|
1860
2265
|
for p in range(n_MOs):
|
@@ -1870,26 +2275,35 @@ class QuantumChemistryBase:
|
|
1870
2275
|
return ops
|
1871
2276
|
|
1872
2277
|
def _build_2bdy_operators_spinfree() -> list:
|
1873
|
-
"""
|
2278
|
+
"""Returns spinfree two-body operators as a symmetry-reduced list of QubitHamiltonians"""
|
1874
2279
|
# Exploit symmetries pqrs = qpsr (due to spin summation, '-pqsr = -qprs' drops out)
|
1875
2280
|
# and = rspq
|
1876
2281
|
ops = []
|
1877
2282
|
for p, q, r, s in product(range(n_MOs), repeat=4):
|
1878
2283
|
if p * n_MOs + q >= r * n_MOs + s and (p >= q or r >= s):
|
1879
2284
|
# Spin aaaa
|
1880
|
-
op_tuple = ((2 * p, 1), (2 * q, 1), (2 * s, 0), (2 * r, 0)) if (p != q and r != s) else
|
2285
|
+
op_tuple = ((2 * p, 1), (2 * q, 1), (2 * s, 0), (2 * r, 0)) if (p != q and r != s) else "0.0 []"
|
1881
2286
|
op = _get_of_op(op_tuple)
|
1882
2287
|
# Spin abab
|
1883
|
-
op_tuple = (
|
1884
|
-
|
2288
|
+
op_tuple = (
|
2289
|
+
((2 * p, 1), (2 * q + 1, 1), (2 * s + 1, 0), (2 * r, 0))
|
2290
|
+
if (2 * p != 2 * q + 1 and 2 * r != 2 * s + 1)
|
2291
|
+
else "0.0 []"
|
2292
|
+
)
|
1885
2293
|
op += _get_of_op(op_tuple)
|
1886
2294
|
# Spin baba
|
1887
|
-
op_tuple = (
|
1888
|
-
|
2295
|
+
op_tuple = (
|
2296
|
+
((2 * p + 1, 1), (2 * q, 1), (2 * s, 0), (2 * r + 1, 0))
|
2297
|
+
if (2 * p + 1 != 2 * q and 2 * r + 1 != 2 * s)
|
2298
|
+
else "0.0 []"
|
2299
|
+
)
|
1889
2300
|
op += _get_of_op(op_tuple)
|
1890
2301
|
# Spin bbbb
|
1891
|
-
op_tuple = (
|
1892
|
-
|
2302
|
+
op_tuple = (
|
2303
|
+
((2 * p + 1, 1), (2 * q + 1, 1), (2 * s + 1, 0), (2 * r + 1, 0))
|
2304
|
+
if (p != q and r != s)
|
2305
|
+
else "0.0 []"
|
2306
|
+
)
|
1893
2307
|
op += _get_of_op(op_tuple)
|
1894
2308
|
ops += [op]
|
1895
2309
|
return ops
|
@@ -1913,7 +2327,7 @@ class QuantumChemistryBase:
|
|
1913
2327
|
return rdm1
|
1914
2328
|
|
1915
2329
|
def _assemble_rdm2_spinful(evals, rdm2=None) -> numpy.ndarray:
|
1916
|
-
"""
|
2330
|
+
"""Returns spin-ful two-particle RDM built by symmetry conditions"""
|
1917
2331
|
ctr: int = 0
|
1918
2332
|
if rdm2 is None:
|
1919
2333
|
rdm2 = numpy.zeros([n_SOs, n_SOs, n_SOs, n_SOs])
|
@@ -1939,7 +2353,7 @@ class QuantumChemistryBase:
|
|
1939
2353
|
return rdm2
|
1940
2354
|
|
1941
2355
|
def _assemble_rdm2_spinfree(evals, rdm2=None) -> numpy.ndarray:
|
1942
|
-
"""
|
2356
|
+
"""Returns spin-free two-particle RDM built by symmetry conditions"""
|
1943
2357
|
ctr: int = 0
|
1944
2358
|
if rdm2 is None:
|
1945
2359
|
rdm2 = numpy.zeros([n_MOs, n_MOs, n_MOs, n_MOs])
|
@@ -1958,13 +2372,13 @@ class QuantumChemistryBase:
|
|
1958
2372
|
return rdm2
|
1959
2373
|
|
1960
2374
|
def _build_1bdy_operators_hcb() -> list:
|
1961
|
-
"""
|
2375
|
+
"""Returns hcb one-body operators as a symmetry-reduced list of QubitHamiltonians"""
|
1962
2376
|
# Exploit symmetry pq = qp (not changed by spin-summation)
|
1963
2377
|
ops = []
|
1964
2378
|
for p in range(n_MOs):
|
1965
2379
|
for q in range(p + 1):
|
1966
|
-
if
|
1967
|
-
if
|
2380
|
+
if p == q:
|
2381
|
+
if self.transformation.up_then_down:
|
1968
2382
|
op_tuple = ((p, 1), (p, 0))
|
1969
2383
|
op = _get_hcb_op(op_tuple)
|
1970
2384
|
else:
|
@@ -1976,7 +2390,7 @@ class QuantumChemistryBase:
|
|
1976
2390
|
return ops
|
1977
2391
|
|
1978
2392
|
def _build_2bdy_operators_hcb() -> list:
|
1979
|
-
"""
|
2393
|
+
"""Returns hcb two-body operators as a symmetry-reduced list of QubitHamiltonians"""
|
1980
2394
|
# Exploit symmetries pqrs = qpsr (due to spin summation, '-pqsr = -qprs' drops out)
|
1981
2395
|
# and = rspq
|
1982
2396
|
ops = []
|
@@ -1986,16 +2400,25 @@ class QuantumChemistryBase:
|
|
1986
2400
|
for p, q, r, s in product(range(n_MOs), repeat=4):
|
1987
2401
|
if p * n_MOs + q >= r * n_MOs + s and (p >= q or r >= s):
|
1988
2402
|
# Spin abba+ baab allow p=q=r=s orb iijj
|
1989
|
-
op_tuple = (
|
1990
|
-
|
2403
|
+
op_tuple = (
|
2404
|
+
((scale * p, 1), (scale * q, 1), (scale * r, 0), (scale * s, 0))
|
2405
|
+
if (p == q and s == r)
|
2406
|
+
else "0.0 []"
|
2407
|
+
)
|
1991
2408
|
op = _get_hcb_op(op_tuple)
|
1992
2409
|
# Spin abba+ baab dont allow p=q=r=s orb ijij
|
1993
|
-
op_tuple = (
|
1994
|
-
|
2410
|
+
op_tuple = (
|
2411
|
+
((scale * p, 1), (scale * q, 1), (scale * r, 0), (scale * s, 0))
|
2412
|
+
if (p != q and r != s and p == r and s == q)
|
2413
|
+
else "0.0 []"
|
2414
|
+
)
|
1995
2415
|
op += _get_hcb_op(op_tuple)
|
1996
2416
|
# Spin aaaa+ bbbb dont allow p=q=r=s orb ijji
|
1997
|
-
op_tuple = (
|
1998
|
-
|
2417
|
+
op_tuple = (
|
2418
|
+
((scale * p, 1), (scale * q, 1), (scale * r, 0), (scale * s, 0))
|
2419
|
+
if (p != q and r != s and p == s and q == r)
|
2420
|
+
else "0.0 []"
|
2421
|
+
)
|
1999
2422
|
op += _get_hcb_op(op_tuple)
|
2000
2423
|
ops += [op]
|
2001
2424
|
return ops
|
@@ -2011,29 +2434,31 @@ class QuantumChemistryBase:
|
|
2011
2434
|
else:
|
2012
2435
|
if use_hcb:
|
2013
2436
|
raise TequilaException(
|
2014
|
-
"compute_rdms: spin_free={} and use_hcb={} are not compatible".format(spin_free, use_hcb)
|
2437
|
+
"compute_rdms: spin_free={} and use_hcb={} are not compatible".format(spin_free, use_hcb)
|
2438
|
+
)
|
2015
2439
|
qops += _build_1bdy_operators_spinful() if get_rdm1 else []
|
2016
2440
|
qops += _build_2bdy_operators_spinful() if get_rdm2 else []
|
2017
2441
|
|
2018
2442
|
# Transform operator lists to QubitHamiltonians
|
2019
|
-
if
|
2443
|
+
if not use_hcb:
|
2020
2444
|
qops = [_get_qop_hermitian(op) for op in qops]
|
2021
2445
|
|
2022
2446
|
# Compute expected values
|
2023
2447
|
rdm1 = None
|
2024
2448
|
rdm2 = None
|
2025
2449
|
from tequila import QTensor
|
2450
|
+
|
2026
2451
|
if evaluate:
|
2027
2452
|
if rdm_trafo is None:
|
2028
2453
|
evals = simulate(ExpectationValue(H=qops, U=U, shape=[len(qops)]), variables=variables)
|
2029
2454
|
else:
|
2030
|
-
qops = [rdm_trafo.dagger()*qops[i]*rdm_trafo for i in range(len(qops))]
|
2455
|
+
qops = [rdm_trafo.dagger() * qops[i] * rdm_trafo for i in range(len(qops))]
|
2031
2456
|
evals = simulate(ExpectationValue(H=qops, U=U, shape=[len(qops)]), variables=variables)
|
2032
2457
|
else:
|
2033
2458
|
if rdm_trafo is None:
|
2034
2459
|
evals = [ExpectationValue(H=x, U=U) for x in qops]
|
2035
2460
|
N = n_MOs if spin_free else n_SOs
|
2036
|
-
rdm1 = QTensor(shape=[N,N])
|
2461
|
+
rdm1 = QTensor(shape=[N, N])
|
2037
2462
|
rdm2 = QTensor(shape=[N, N, N, N])
|
2038
2463
|
else:
|
2039
2464
|
raise TequilaException("compute_rdms: rdm_trafo was set but evaluate flag is False (not supported)")
|
@@ -2133,9 +2558,15 @@ class QuantumChemistryBase:
|
|
2133
2558
|
|
2134
2559
|
return rdm1_spinsum, rdm2_spinsum
|
2135
2560
|
|
2136
|
-
def perturbative_f12_correction(
|
2137
|
-
|
2138
|
-
|
2561
|
+
def perturbative_f12_correction(
|
2562
|
+
self,
|
2563
|
+
rdm1: numpy.ndarray = None,
|
2564
|
+
rdm2: numpy.ndarray = None,
|
2565
|
+
gamma: float = 1.4,
|
2566
|
+
n_ri: int = None,
|
2567
|
+
external_info: dict = None,
|
2568
|
+
**kwargs,
|
2569
|
+
) -> float:
|
2139
2570
|
"""
|
2140
2571
|
Computes the spin-free [2]_R12 correction, needing only the 1- and 2-RDM of a reference method
|
2141
2572
|
Requires either 1-RDM, 2-RDM or information to compute them in kwargs
|
@@ -2165,12 +2596,14 @@ class QuantumChemistryBase:
|
|
2165
2596
|
the f12 correction for the energy
|
2166
2597
|
"""
|
2167
2598
|
from .f12_corrections._f12_correction_base import ExplicitCorrelationCorrection
|
2168
|
-
|
2169
|
-
|
2599
|
+
|
2600
|
+
correction = ExplicitCorrelationCorrection(
|
2601
|
+
mol=self, rdm1=rdm1, rdm2=rdm2, gamma=gamma, n_ri=n_ri, external_info=external_info, **kwargs
|
2602
|
+
)
|
2170
2603
|
return correction.compute()
|
2171
2604
|
|
2172
2605
|
def n_rotation(self, i, phi):
|
2173
|
-
|
2606
|
+
"""
|
2174
2607
|
Creates a quantum circuit that applies a phase rotation based on phi to both components (up and down) of a given qubit.
|
2175
2608
|
|
2176
2609
|
Parameters:
|
@@ -2179,21 +2612,21 @@ class QuantumChemistryBase:
|
|
2179
2612
|
|
2180
2613
|
Returns:
|
2181
2614
|
- QCircuit: A quantum circuit object containing the sequence of rotations applied to the up and down components of the specified qubit.
|
2182
|
-
|
2615
|
+
"""
|
2183
2616
|
|
2184
2617
|
# Generate number operators for the up and down components of the qubit.
|
2185
|
-
n_up = self.make_number_op(2*i)
|
2186
|
-
n_down = self.make_number_op(2*i+1)
|
2618
|
+
n_up = self.make_number_op(2 * i)
|
2619
|
+
n_down = self.make_number_op(2 * i + 1)
|
2187
2620
|
|
2188
2621
|
# Start a new circuit and apply rotations to each component.
|
2189
|
-
circuit = gates.GeneralizedRotation(generator
|
2190
|
-
circuit += gates.GeneralizedRotation(generator
|
2622
|
+
circuit = gates.GeneralizedRotation(generator=n_up, angle=-2 * phi)
|
2623
|
+
circuit += gates.GeneralizedRotation(generator=n_down, angle=-2 * phi)
|
2191
2624
|
return circuit
|
2192
|
-
|
2193
|
-
def get_givens_circuit(self, unitary, tol
|
2194
|
-
|
2625
|
+
|
2626
|
+
def get_givens_circuit(self, unitary, tol=1e-12, ordering=OPTIMIZED_ORDERING):
|
2627
|
+
"""
|
2195
2628
|
Constructs a quantum circuit from a given real unitary matrix using Givens rotations.
|
2196
|
-
|
2629
|
+
|
2197
2630
|
This method decomposes a unitary matrix into a series of Givens and Rz (phase) rotations,
|
2198
2631
|
then constructs and returns a quantum circuit that implements this sequence of rotations.
|
2199
2632
|
|
@@ -2204,7 +2637,7 @@ class QuantumChemistryBase:
|
|
2204
2637
|
|
2205
2638
|
Returns:
|
2206
2639
|
- QCircuit: A quantum circuit implementing the series of rotations decomposed from the unitary.
|
2207
|
-
|
2640
|
+
"""
|
2208
2641
|
# Decompose the unitary matrix into Givens and phase (Rz) rotations.
|
2209
2642
|
theta_list, phi_list = get_givens_decomposition(unitary, tol, ordering)
|
2210
2643
|
|
@@ -2217,11 +2650,10 @@ class QuantumChemistryBase:
|
|
2217
2650
|
|
2218
2651
|
# Add all Givens rotations to the circuit.
|
2219
2652
|
for theta in reversed(theta_list):
|
2220
|
-
circuit += self.UR(theta[1], theta[2], theta[0]*2)
|
2653
|
+
circuit += self.UR(theta[1], theta[2], theta[0] * 2)
|
2221
2654
|
|
2222
2655
|
return circuit
|
2223
2656
|
|
2224
|
-
|
2225
2657
|
def print_basis_info(self):
|
2226
2658
|
return self.integral_manager.print_basis_info()
|
2227
2659
|
|
@@ -2242,11 +2674,12 @@ class QuantumChemistryBase:
|
|
2242
2674
|
|
2243
2675
|
return result
|
2244
2676
|
|
2677
|
+
|
2245
2678
|
def givens_matrix(n, p, q, theta):
|
2246
|
-
|
2679
|
+
"""
|
2247
2680
|
Construct a complex Givens rotation matrix of dimension n by theta between rows/columns p and q.
|
2248
|
-
|
2249
|
-
|
2681
|
+
"""
|
2682
|
+
"""
|
2250
2683
|
Generates a Givens rotation matrix of size n x n to rotate by angle theta in the (p, q) plane. This matrix can be complex
|
2251
2684
|
|
2252
2685
|
Parameters:
|
@@ -2257,10 +2690,14 @@ def givens_matrix(n, p, q, theta):
|
|
2257
2690
|
|
2258
2691
|
Returns:
|
2259
2692
|
- numpy.array: The Givens rotation matrix.
|
2260
|
-
|
2261
|
-
matrix = numpy.eye(n) # Matrix to hold complex numbers
|
2262
|
-
|
2263
|
-
|
2693
|
+
"""
|
2694
|
+
matrix = QTensor(shape=(n, n), objective_list=numpy.eye(n).reshape(n * n)) # Matrix to hold complex numbers
|
2695
|
+
if isinstance(theta, (Variable, Objective)):
|
2696
|
+
cos_theta = theta.apply(numpy.cos)
|
2697
|
+
sin_theta = theta.apply(numpy.sin)
|
2698
|
+
else:
|
2699
|
+
cos_theta = numpy.cos(theta)
|
2700
|
+
sin_theta = numpy.sin(theta)
|
2264
2701
|
|
2265
2702
|
# Directly assign cosine and sine without complex phase adjustment
|
2266
2703
|
matrix[p, p] = cos_theta
|
@@ -2270,8 +2707,9 @@ def givens_matrix(n, p, q, theta):
|
|
2270
2707
|
|
2271
2708
|
return matrix
|
2272
2709
|
|
2273
|
-
|
2274
|
-
|
2710
|
+
|
2711
|
+
def get_givens_decomposition(unitary, tol=1e-12, ordering=OPTIMIZED_ORDERING, return_diagonal=False):
|
2712
|
+
"""
|
2275
2713
|
Decomposes a real unitary matrix into Givens rotations (theta) and Rz rotations (phi).
|
2276
2714
|
|
2277
2715
|
Parameters:
|
@@ -2284,9 +2722,9 @@ def get_givens_decomposition(unitary, tol = 1e-12, ordering = OPTIMIZED_ORDERING
|
|
2284
2722
|
- list: A list of tuples, each representing a Givens rotation. Each tuple contains the rotation angle theta and indices (i,j) of the rotation.
|
2285
2723
|
- list: A list of tuples, each representing an Rz rotation. Each tuple contains the rotation angle phi and the index (i) of the rotation.
|
2286
2724
|
- numpy.array (optional): The diagonal matrix after applying all Givens rotations, returned if return_diagonal is True.
|
2287
|
-
|
2288
|
-
U = unitary
|
2289
|
-
U[abs(U) < tol] = 0 # Zeroing out the small elements as per the tolerance level.
|
2725
|
+
"""
|
2726
|
+
U = unitary # no need to copy as we don't modify the original
|
2727
|
+
# U[abs(U) < tol] = 0 # Zeroing out the small elements as per the tolerance level. #comented out, its being considered latter again
|
2290
2728
|
n = U.shape[0]
|
2291
2729
|
|
2292
2730
|
# Determine optimized ordering if specified.
|
@@ -2297,48 +2735,53 @@ def get_givens_decomposition(unitary, tol = 1e-12, ordering = OPTIMIZED_ORDERING
|
|
2297
2735
|
phi_list = []
|
2298
2736
|
|
2299
2737
|
def calcTheta(U, c, r):
|
2300
|
-
|
2301
|
-
t =
|
2302
|
-
theta_list.append((t, r, r-1))
|
2303
|
-
g = givens_matrix(n,r,r-1,t)
|
2304
|
-
U =
|
2305
|
-
|
2738
|
+
"""Calculate and apply the Givens rotation for a specific matrix element."""
|
2739
|
+
t = arctan2(-U[r, c], U[r - 1, c])
|
2740
|
+
theta_list.append((t, r, r - 1))
|
2741
|
+
g = givens_matrix(n, r, r - 1, t) # is a QTensor
|
2742
|
+
U = g.dot(U)
|
2306
2743
|
return U
|
2307
2744
|
|
2308
2745
|
# Apply and store Givens rotations as per the given or computed ordering.
|
2309
2746
|
if ordering is None:
|
2310
2747
|
for c in range(n):
|
2311
|
-
for r in range(n-1, c, -1):
|
2748
|
+
for r in range(n - 1, c, -1):
|
2312
2749
|
U = calcTheta(U, c, r)
|
2313
2750
|
else:
|
2314
2751
|
for r, c in ordering:
|
2315
2752
|
U = calcTheta(U, c, r)
|
2316
|
-
|
2317
2753
|
# Calculating the Rz rotations based on the phases of the diagonal elements.
|
2318
2754
|
# For real elements this means a 180 degree shift, i.e. a sign change.
|
2319
2755
|
for i in range(n):
|
2320
|
-
|
2321
|
-
|
2322
|
-
|
2756
|
+
if isinstance(U[i, i], (Variable, Objective)):
|
2757
|
+
if len(U[i, i].args):
|
2758
|
+
phi_list.append((U[i, i].apply(numpy.angle), i))
|
2759
|
+
else:
|
2760
|
+
phi_list.append((numpy.angle(U[i, i]), i))
|
2323
2761
|
# Filtering out rotations without significance.
|
2324
2762
|
theta_list_new = []
|
2325
2763
|
for i, theta in enumerate(theta_list):
|
2326
|
-
if
|
2764
|
+
if isinstance(theta[0], (Variable, Objective)):
|
2765
|
+
if len(theta[0].args):
|
2766
|
+
theta_list_new.append(theta)
|
2767
|
+
elif abs(theta[0] % (2 * numpy.pi)) > tol:
|
2327
2768
|
theta_list_new.append(theta)
|
2328
|
-
|
2329
2769
|
phi_list_new = []
|
2330
2770
|
for i, phi in enumerate(phi_list):
|
2331
|
-
if
|
2771
|
+
if isinstance(phi[0], (Variable, Objective)):
|
2772
|
+
if len(phi[0].args):
|
2773
|
+
phi_list_new.append(phi)
|
2774
|
+
elif abs(phi[0]) > tol:
|
2332
2775
|
phi_list_new.append(phi)
|
2333
|
-
|
2334
2776
|
if return_diagonal:
|
2335
2777
|
# Optionally return the resulting diagonal
|
2336
2778
|
return theta_list_new, phi_list_new, U
|
2337
2779
|
else:
|
2338
2780
|
return theta_list_new, phi_list_new
|
2339
|
-
|
2340
|
-
|
2341
|
-
|
2781
|
+
|
2782
|
+
|
2783
|
+
def reconstruct_matrix_from_givens(n, theta_list, phi_list, to_real_if_possible=True, tol=1e-12):
|
2784
|
+
"""
|
2342
2785
|
Reconstructs a matrix from given Givens rotations and Rz diagonal rotations.
|
2343
2786
|
This function is effectively an inverse of get_givens_decomposition, and therefore only works with data in the same format as its output.
|
2344
2787
|
|
@@ -2351,10 +2794,10 @@ def reconstruct_matrix_from_givens(n, theta_list, phi_list, to_real_if_possible
|
|
2351
2794
|
|
2352
2795
|
Returns:
|
2353
2796
|
- numpy.ndarray: The reconstructed complex or real matrix, depending on the `to_real_if_possible` flag and matrix composition.
|
2354
|
-
|
2797
|
+
"""
|
2355
2798
|
# Start with an identity matrix
|
2356
2799
|
reconstructed = numpy.eye(n, dtype=complex)
|
2357
|
-
|
2800
|
+
|
2358
2801
|
# Apply Rz rotations for diagonal elements
|
2359
2802
|
for phi in phi_list:
|
2360
2803
|
angle, i = phi
|
@@ -2363,12 +2806,12 @@ def reconstruct_matrix_from_givens(n, theta_list, phi_list, to_real_if_possible
|
|
2363
2806
|
reconstructed[i, i] *= -1
|
2364
2807
|
else:
|
2365
2808
|
reconstructed[i, i] *= numpy.exp(1j * angle)
|
2366
|
-
|
2809
|
+
|
2367
2810
|
# Apply Givens rotations in reverse order
|
2368
2811
|
for theta in reversed(theta_list):
|
2369
2812
|
angle, i, j = theta
|
2370
2813
|
g = givens_matrix(n, i, j, angle)
|
2371
|
-
reconstructed = numpy.dot(g.conj().T, reconstructed)
|
2814
|
+
reconstructed = numpy.dot(g.conj().T, reconstructed) # Transpose of Givens matrix applied to the left
|
2372
2815
|
|
2373
2816
|
# Convert matrix to real if its imaginary part is negligible unless disabled via to_real_if_possible
|
2374
2817
|
if to_real_if_possible:
|
@@ -2378,3 +2821,12 @@ def reconstruct_matrix_from_givens(n, theta_list, phi_list, to_real_if_possible
|
|
2378
2821
|
reconstructed = reconstructed.real
|
2379
2822
|
|
2380
2823
|
return reconstructed
|
2824
|
+
|
2825
|
+
|
2826
|
+
def arctan2(x1, x2, *args, **kwargs):
|
2827
|
+
if isinstance(x1, (Variable, Objective)) or isinstance(x2, (Variable, Objective)):
|
2828
|
+
return Objective().binary_operator(left=1 * x1, right=1 * x2, op=numpy.arctan2)
|
2829
|
+
elif not isinstance(x1, numbers.Complex) and not isinstance(x2, numbers.Complex):
|
2830
|
+
return numpy.arctan2(x1, x2)
|
2831
|
+
else:
|
2832
|
+
return numpy.arctan2(x1.imag, x2.imag) + numpy.arctan2(x1.real, x2.real)
|