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.
Files changed (86) hide show
  1. tequila/__init__.py +29 -14
  2. tequila/apps/__init__.py +14 -5
  3. tequila/apps/_unary_state_prep_impl.py +145 -112
  4. tequila/apps/adapt/__init__.py +9 -1
  5. tequila/apps/adapt/adapt.py +154 -113
  6. tequila/apps/krylov/__init__.py +1 -1
  7. tequila/apps/krylov/krylov.py +23 -21
  8. tequila/apps/robustness/helpers.py +10 -6
  9. tequila/apps/robustness/interval.py +238 -156
  10. tequila/apps/unary_state_prep.py +29 -23
  11. tequila/autograd_imports.py +8 -5
  12. tequila/circuit/__init__.py +2 -1
  13. tequila/circuit/_gates_impl.py +135 -67
  14. tequila/circuit/circuit.py +163 -79
  15. tequila/circuit/compiler.py +114 -105
  16. tequila/circuit/gates.py +288 -120
  17. tequila/circuit/gradient.py +35 -23
  18. tequila/circuit/noise.py +83 -74
  19. tequila/circuit/postselection.py +120 -0
  20. tequila/circuit/pyzx.py +10 -6
  21. tequila/circuit/qasm.py +201 -83
  22. tequila/circuit/qpic.py +63 -61
  23. tequila/grouping/binary_rep.py +148 -146
  24. tequila/grouping/binary_utils.py +84 -75
  25. tequila/grouping/compile_groups.py +334 -230
  26. tequila/grouping/ev_utils.py +77 -41
  27. tequila/grouping/fermionic_functions.py +383 -308
  28. tequila/grouping/fermionic_methods.py +170 -123
  29. tequila/grouping/overlapping_methods.py +69 -52
  30. tequila/hamiltonian/paulis.py +12 -13
  31. tequila/hamiltonian/paulistring.py +1 -1
  32. tequila/hamiltonian/qubit_hamiltonian.py +45 -35
  33. tequila/ml/__init__.py +1 -0
  34. tequila/ml/interface_torch.py +19 -16
  35. tequila/ml/ml_api.py +11 -10
  36. tequila/ml/utils_ml.py +12 -11
  37. tequila/objective/__init__.py +8 -3
  38. tequila/objective/braket.py +55 -47
  39. tequila/objective/objective.py +87 -55
  40. tequila/objective/qtensor.py +36 -27
  41. tequila/optimizers/__init__.py +31 -23
  42. tequila/optimizers/_containers.py +11 -7
  43. tequila/optimizers/optimizer_base.py +111 -83
  44. tequila/optimizers/optimizer_gd.py +258 -231
  45. tequila/optimizers/optimizer_gpyopt.py +56 -42
  46. tequila/optimizers/optimizer_scipy.py +157 -112
  47. tequila/quantumchemistry/__init__.py +66 -38
  48. tequila/quantumchemistry/chemistry_tools.py +393 -209
  49. tequila/quantumchemistry/encodings.py +121 -13
  50. tequila/quantumchemistry/madness_interface.py +170 -96
  51. tequila/quantumchemistry/orbital_optimizer.py +86 -41
  52. tequila/quantumchemistry/psi4_interface.py +166 -97
  53. tequila/quantumchemistry/pyscf_interface.py +70 -23
  54. tequila/quantumchemistry/qc_base.py +866 -414
  55. tequila/simulators/__init__.py +0 -3
  56. tequila/simulators/simulator_api.py +247 -105
  57. tequila/simulators/simulator_aqt.py +102 -0
  58. tequila/simulators/simulator_base.py +147 -53
  59. tequila/simulators/simulator_cirq.py +58 -42
  60. tequila/simulators/simulator_cudaq.py +600 -0
  61. tequila/simulators/simulator_ddsim.py +390 -0
  62. tequila/simulators/simulator_mqp.py +30 -0
  63. tequila/simulators/simulator_pyquil.py +190 -171
  64. tequila/simulators/simulator_qibo.py +95 -87
  65. tequila/simulators/simulator_qiskit.py +119 -107
  66. tequila/simulators/simulator_qlm.py +52 -26
  67. tequila/simulators/simulator_qulacs.py +74 -52
  68. tequila/simulators/simulator_spex.py +95 -60
  69. tequila/simulators/simulator_symbolic.py +6 -5
  70. tequila/simulators/test_spex_simulator.py +8 -11
  71. tequila/tools/convenience.py +4 -4
  72. tequila/tools/qng.py +72 -64
  73. tequila/tools/random_generators.py +38 -34
  74. tequila/utils/bitstrings.py +7 -7
  75. tequila/utils/exceptions.py +19 -5
  76. tequila/utils/joined_transformation.py +8 -10
  77. tequila/utils/keymap.py +0 -5
  78. tequila/utils/misc.py +6 -4
  79. tequila/version.py +1 -1
  80. tequila/wavefunction/qubit_wavefunction.py +47 -28
  81. {tequila_basic-1.9.9.dist-info → tequila_basic-1.9.10.dist-info}/METADATA +13 -16
  82. tequila_basic-1.9.10.dist-info/RECORD +93 -0
  83. {tequila_basic-1.9.9.dist-info → tequila_basic-1.9.10.dist-info}/WHEEL +1 -1
  84. tequila_basic-1.9.9.dist-info/RECORD +0 -88
  85. {tequila_basic-1.9.9.dist-info → tequila_basic-1.9.10.dist-info}/licenses/LICENSE +0 -0
  86. {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 ActiveSpaceData, FermionicGateImpl, prepare_product_state, ClosedShellAmplitudes, \
14
- Amplitudes, ParametersQC, NBodyTensor, IntegralManager
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, numpy, numbers
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__(self, parameters: ParametersQC,
45
- transformation: typing.Union[str, typing.Callable] = None,
46
- active_orbitals: list = None,
47
- frozen_orbitals: list = None,
48
- orbital_type: str = None,
49
- reference_orbitals: list = None,
50
- orbitals: list = None,
51
- *args,
52
- **kwargs):
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.n_electrons
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.n_electrons
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(active_orbitals=active_orbitals,
95
- reference_orbitals=reference_orbitals,
96
- orbitals=orbitals, frozen_orbitals=frozen_orbitals, orbital_type=orbital_type, basis_name=self.parameters.basis_set, *args,
97
- **kwargs)
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(nuclear_repulsion=c,
124
- one_body_integrals=h1,
125
- two_body_integrals=h2,
126
- overlap_integrals = S,
127
- orbital_coefficients = molecule.integral_manager.orbital_coefficients,
128
- active_orbitals= active_orbitals,
129
- transformation=transformation,
130
- orbital_type=molecule.integral_manager._orbital_type,
131
- parameters=parameters,
132
- reference_orbitals= molecule.integral_manager.active_space.reference_orbitals,*args, **kwargs)
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 = {k.split("__")[1]: v for k, v in kwargs.items() if
160
- (hasattr(k, "lower") and "transformation__" in k.lower())}
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(transformation,
174
- list(encodings.keys())))
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(cls, molecule: openfermion.MolecularData,
180
- transformation: typing.Union[str, typing.Callable] = None,
181
- *args,
182
- **kwargs):
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(basis_set=molecule.basis, geometry=molecule.geometry,
195
- description=molecule.description, multiplicity=molecule.multiplicity,
196
- charge=molecule.charge)
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(self,
200
- indices: typing.Iterable[typing.Tuple[int, int]],
201
- form: str = None,
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("Molecule with transformation {} does not support general UCC operations".format(self.transformation))
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("make_excitation_generator: unexpected input format of indices\n"
237
- "use list of tuples as [(a_0, i_0),(a_1, i_1) ...]\n"
238
- "or list as [a_0, i_0, a_1, i_1, ... ]\n"
239
- "you gave: {}".format(indices))
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 (len(pair) == 2)
253
- ofi += [(int(pair[0]), 1),
254
- (int(pair[1]), 0)] # openfermion does not take other types of integers like numpy.int64
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.j) # 1j makes it hermitian
258
- op += openfermion.FermionOperator(tuple(reversed(dag)), -1.j)
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() != 'fermionic':
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("Excitation generator is a unit operator.\n"
314
- "Non-standard transformations might not work with general fermionic operators\n"
315
- "indices = " + str(indices), category=TequilaWarning)
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(self, indices, angle, control=None, assume_real=True,
319
- compile_options="optimize"):
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
- target += [self.transformation.up(pair[0]), self.transformation.up(pair[1])]
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 for x in target]
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
- return gates.QubitExcitation(angle=angle, target=target, assume_real=assume_real, control=control,
349
- compile_options=compile_options)
350
-
351
- def UR(self,i,j,angle=None, label=None, control=None, assume_real=True, *args, **kwargs):
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(indices=[(2*i,2*j)], angle=angle, assume_real=assume_real, control=control, *args, **kwargs)
379
- circuit+= self.make_excitation_gate(indices=[(2*i+1,2*j+1)], angle=angle, assume_real=assume_real, control=control, *args, **kwargs)
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(target=[2*i,2*j,2*i+1,2*j+1], angle=angle, control=control, assume_real=assume_real, *args, **kwargs)
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(indices=[(2*i,2*j),(2*i+1,2*j+1)], angle=angle, control=control, assume_real=assume_real, *args, **kwargs)
414
-
415
- def make_orbital_rotation_gate(self, indices:tuple, *args, **kwargs):
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("Molecule with transformation {} does not support general UCC operations".format(self.transformation))
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((pair[0]//2+(pair[0]%2)*self.n_orbitals,pair[1]//2+(pair[1]%2)*self.n_orbitals))
452
- else:idx = indices
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(angle=angle, generator=generator, p0=p0,
455
- transformation=type(self.transformation).__name__.lower(), indices=idx,
456
- assume_real=assume_real,
457
- control=control, **kwargs))
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.n_electrons
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 ("one_body_integrals" in kwargs)
512
- assert ("two_body_integrals" in kwargs)
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(active_orbitals=sorted(active_orbitals),
551
- reference_orbitals=sorted(reference_orbitals))
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(one_body_integrals=one_body_integrals, two_body_integrals=two_body_integrals,
558
- constant_term=constant_part, *args, **kwargs)
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(parameters=self.parameters, integral_manager=integral_manager, transformation=self.transformation)
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
- if not self.integral_manager.active_space_is_trivial():
606
- warnings.warn("orthonormalize_basis_orbitals: active space is set and might lead to inconsistent behaviour", TequilaWarning)
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(parameters=self.parameters, integral_manager=integral_manager, transformation=self.transformation)
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 ("n_orbitals" in kwargs):
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'{orbital}', coefficient)
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'{orbital}^', coefficient)
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 += (one - two)
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] += + tbt.elems[p, p, q, 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(occupied_indices=occupied_indices, active_indices=active_indices)
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
- """ convenience function """
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(self,
921
- pair_info: str = None,
922
- include_singles: bool = True,
923
- general_excitations: bool = True) -> list:
924
- """
925
- Assuming a pair-specific model, create a pair-specific index list
926
- to be used in make_upccgsd_ansatz(indices = ... )
927
- Excite from a set of references (i) to any pair coming from (i),
928
- i.e. any (i,j)/(j,i). If general excitations are allowed, also
929
- allow excitations from pairs to appendant pairs and reference.
930
-
931
- Parameters
932
- ----------
933
- pair_info
934
- file or list including information about pair structure
935
- references single number, pair double
936
- example: as file: "0,1,11,11,00,10" (hand over file name)
937
- in file, skip first row assuming some text with information
938
- as list:['0','1`','11','11','00','10']
939
- ~> two reference orbitals 0 and 1,
940
- then two orbitals from pair 11, one from 00, one mixed 10
941
- include_singles
942
- include single excitations
943
- general_excitations
944
- allow general excitations
945
- Returns
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] = [i for i in range(len(pairs))
969
- if (((p[0] in pairs[i]) or (p[1] in pairs[i]) or str(i) in p)
970
- and not (i == idx))]
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 = [[(n, m)] for n in reference_orbitals for m in range(self.n_orbitals) if
1010
- n < m and m not in reference_orbitals]
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(self,
1020
- indices: list = "UpCCGD",
1021
- label: str = None,
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(indices=idx, angle=(idx, "D", label),
1031
- assume_real=assume_real)
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, use_units_of_pi=False, label=None, optimize=None, ladder=True):
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("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")
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("need even number of electrons for SPA ansatz.\n{} active electrons".format(self.n_electrons))
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("number of edges need to be equal to number of active electrons//2\n{} edges given\n{} active electrons\nfrozen core is {}".format(len(edges), self.n_electrons, self.parameters.frozen_core))
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("make_spa_ansatz: faulty list of edges, orbitals are overlapping e.g. orbital {} is in edge {} and edge {}".format(q1, edge_qubits, edge2))
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" ,label))
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
- # construction of the non-optimized circuit (UpCCD with paired doubles according to edges)
1407
+ orbs = [edge[0] for edge in edges]
1111
1408
  if hcb:
1112
- U = self.prepare_hardcore_boson_reference()
1409
+ U = gates.X([self.transformation.up(i) for i in orbs])
1113
1410
  else:
1114
- U = self.prepare_reference()
1115
- # will only work if the first orbitals in the edges are the reference orbitals
1116
- sane = True
1117
- reference_orbitals = self.reference_orbitals
1118
- for edge_qubits in edges:
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" ,label))
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(indices=[(2*c,2*q1),(2*c+1,2*q1+1)], angle=angle)
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(name=subpart, *args, label=(label,i), include_reference=False, hcb_optimization=False, **kwargs)
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(self,
1189
- include_reference: bool = True,
1190
- name: str = "UpCCGSD",
1191
- label: str = None,
1192
- order: int = None,
1193
- assume_real: bool = True,
1194
- hcb_optimization: bool = None,
1195
- spin_adapt_singles: bool = True,
1196
- neglect_z: bool = False,
1197
- mix_sd: bool = False,
1198
- *args, **kwargs):
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 \'hcb_to_me\' function. Try transformation=\'ReorderedJordanWigner\'".format(
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(include_singles=S, include_doubles=D, indices=indices, assume_real=assume_real,
1284
- label=(label, 0), mix_sd=mix_sd, spin_adapt_singles=spin_adapt_singles, *args,
1285
- **kwargs)
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(indices=indices, assume_real=assume_real, label=(label, 0),
1292
- *args, **kwargs)
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(indices=indices, assume_real=assume_real, label=(label, 0),
1299
- spin_adapt_singles=spin_adapt_singles, neglect_z=neglect_z, *args,
1300
- **kwargs)
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(include_singles=S, include_doubles=D, indices=indices, label=(label, k),
1304
- spin_adapt_singles=spin_adapt_singles, neglect_z=neglect_z, mix_sd=mix_sd)
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(self, indices, include_singles: bool = True, include_doubles: bool = True,
1309
- assume_real: bool = True, label=None,
1310
- spin_adapt_singles: bool = True, angle_transform=None, mix_sd: bool = False,
1311
- neglect_z: bool = False, *args,
1312
- **kwargs):
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 = [self.transformation.up(idx[0]), self.transformation.up(idx[1]),
1322
- self.transformation.down(idx[0]), self.transformation.down(idx[1])]
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(angle=angle,
1326
- indices=((2 * idx[0], 2 * idx[1]), (2 * idx[0] + 1, 2 * idx[1] + 1)),
1327
- assume_real=assume_real, **kwargs)
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(indices=[(idx,)], assume_real=assume_real, label=label,
1330
- spin_adapt_singles=spin_adapt_singles, angle_transform=angle_transform,
1331
- neglect_z=neglect_z)
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(indices=indices, assume_real=assume_real, label=label,
1335
- spin_adapt_singles=spin_adapt_singles, angle_transform=angle_transform,
1336
- neglect_z=neglect_z)
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(self, indices="UpCCGSD", spin_adapt_singles=True, label=None, angle_transform=None,
1340
- assume_real=True, neglect_z=False, *args, **kwargs):
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(angle=angle, indices=[(2 * idx[0], 2 * idx[1])],
1362
- assume_real=assume_real, **kwargs)
1363
- U += self.make_excitation_gate(angle=angle, indices=[(2 * idx[0] + 1, 2 * idx[1] + 1)],
1364
- assume_real=assume_real, **kwargs)
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(angle=angle1, indices=[(2 * idx[0], 2 * idx[1])],
1378
- assume_real=assume_real, **kwargs)
1379
- U += self.make_excitation_gate(angle=angle2, indices=[(2 * idx[0] + 1, 2 * idx[1] + 1)],
1380
- assume_real=assume_real, **kwargs)
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(self, trotter_steps: int = 1,
1385
- initial_amplitudes: typing.Union[str, Amplitudes, ClosedShellAmplitudes] = None,
1386
- include_reference_ansatz=True,
1387
- parametrized=True,
1388
- threshold=1.e-8,
1389
- add_singles=None,
1390
- screening=True,
1391
- *args, **kwargs) -> QCircuit:
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("make_uccsd_anstatz: add_singles has no effect when explicit amplitudes are passed down",
1424
- TequilaWarning)
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 \'{}\' amplitudes".format(exc, initial_amplitudes))
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: tia = numpy.zeros(shape=[nocc, nvirt])
1453
- amplitudes = ClosedShellAmplitudes(
1454
- tIjAb=numpy.zeros(shape=[nocc, nocc, nvirt, nvirt]),
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 (len(key) % 2 == 0)
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 hasattr(initial_amplitudes,
1507
- "lower") and initial_amplitudes.lower() == "mp2" and parametrized and add_singles:
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 += (2.0*g.elems[k, i, l, i] - g.elems[k, i, i, l])
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 = abgij * 1.0 / (
1616
- ei.reshape(1, 1, -1, 1) + ei.reshape(1, 1, 1, -1) - ai.reshape(-1, 1, 1, 1) - ai.reshape(1, -1, 1, 1))
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('abij -> ijab', amplitudes, optimize='greedy'))
1996
+ result = ClosedShellAmplitudes(tIjAb=numpy.einsum("abij -> ijab", amplitudes, optimize="greedy"))
1619
1997
 
1620
1998
  if return_energy:
1621
- E = 2.0 * numpy.einsum('abij,abij->', amplitudes, abgij) - numpy.einsum('abji,abij', amplitudes, abgij,optimize='greedy')
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(numpy.linalg.norm(fock_matrix - numpy.diag(numpy.diag(fock_matrix))), 0.0, atol=1.e-4)
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(data))
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(self, U: QCircuit = None, variables: Variables = None, spin_free: bool = True,
1739
- get_rdm1: bool = True, get_rdm2: bool = True, ordering="dirac", use_hcb: bool = False,
1740
- rdm_trafo: QubitHamiltonian = None, evaluate=True):
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('Need to specify a Quantum Circuit.')
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('Need to specify a Quantum Circuit.')
2190
+ raise TequilaException("Need to specify a Quantum Circuit.")
1795
2191
 
1796
2192
  def _get_hcb_op(op_tuple):
1797
- '''Build the hardcore boson operators: b^\dagger_ib_j + h.c. in qubit encoding '''
1798
- if (len(op_tuple) == 2):
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 (len(op_tuple) == 4):
1801
- if ((op_tuple[0][0] == op_tuple[1][0]) and (op_tuple[2][0] == op_tuple[3][0])): # iijj uddu+duud
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 ((op_tuple[0][0] == op_tuple[2][0]) and (op_tuple[1][0] == op_tuple[3][0]) and (
1804
- op_tuple[0][0] != op_tuple[1][0]) and (op_tuple[2][0] != op_tuple[3][0])): # ijij uuuu+dddd
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 ((op_tuple[0][0] == op_tuple[3][0]) and (op_tuple[1][0] == op_tuple[2][0]) and (
1807
- op_tuple[0][0] != op_tuple[1][0]) and (op_tuple[2][0] != op_tuple[3][0])): # ijji abba
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
- """ Returns operator given by a operator tuple as OpenFermion - Fermion operator """
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
- """ Returns Hermitian part of Fermion operator as QubitHamiltonian """
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
- """ Returns spinful one-body operators as a symmetry-reduced list of QubitHamiltonians """
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
- """ Returns spinful two-body operators as a symmetry-reduced list of QubitHamiltonians """
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
- """ Returns spinfree one-body operators as a symmetry-reduced list of QubitHamiltonians """
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
- """ Returns spinfree two-body operators as a symmetry-reduced list of QubitHamiltonians """
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 '0.0 []'
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 = ((2 * p, 1), (2 * q + 1, 1), (2 * s + 1, 0), (2 * r, 0)) if (
1884
- 2 * p != 2 * q + 1 and 2 * r != 2 * s + 1) else '0.0 []'
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 = ((2 * p + 1, 1), (2 * q, 1), (2 * s, 0), (2 * r + 1, 0)) if (
1888
- 2 * p + 1 != 2 * q and 2 * r + 1 != 2 * s) else '0.0 []'
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 = ((2 * p + 1, 1), (2 * q + 1, 1), (2 * s + 1, 0), (2 * r + 1, 0)) if (
1892
- p != q and r != s) else '0.0 []'
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
- """ Returns spin-ful two-particle RDM built by symmetry conditions """
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
- """ Returns spin-free two-particle RDM built by symmetry conditions """
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
- """ Returns hcb one-body operators as a symmetry-reduced list of QubitHamiltonians """
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 (p == q):
1967
- if (self.transformation.up_then_down):
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
- """ Returns hcb two-body operators as a symmetry-reduced list of QubitHamiltonians """
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 = ((scale * p, 1), (scale * q, 1), (scale * r, 0), (scale * s, 0)) if (
1990
- p == q and s == r) else '0.0 []'
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 = ((scale * p, 1), (scale * q, 1), (scale * r, 0), (scale * s, 0)) if (
1994
- p != q and r != s and p == r and s == q) else '0.0 []'
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 = ((scale * p, 1), (scale * q, 1), (scale * r, 0), (scale * s, 0)) if (
1998
- p != q and r != s and p == s and q == r) else '0.0 []'
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 (not use_hcb):
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(self, rdm1: numpy.ndarray = None, rdm2: numpy.ndarray = None,
2137
- gamma: float = 1.4, n_ri: int = None,
2138
- external_info: dict = None, **kwargs) -> float:
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
- correction = ExplicitCorrelationCorrection(mol=self, rdm1=rdm1, rdm2=rdm2, gamma=gamma,
2169
- n_ri=n_ri, external_info=external_info, **kwargs)
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 = n_up, angle=-2*phi)
2190
- circuit += gates.GeneralizedRotation(generator = n_down, angle=-2*phi)
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 = 1e-12, ordering = OPTIMIZED_ORDERING):
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
- cos_theta = numpy.cos(theta)
2263
- sin_theta = numpy.sin(theta)
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
- def get_givens_decomposition(unitary, tol = 1e-12, ordering = OPTIMIZED_ORDERING, return_diagonal = False):
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 # no need to copy as we don't modify the original
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
- '''Calculate and apply the Givens rotation for a specific matrix element.'''
2301
- t = numpy.arctan2(-U[r,c], U[r-1,c])
2302
- theta_list.append((t, r, r-1))
2303
- g = givens_matrix(n,r,r-1,t)
2304
- U = numpy.dot(g, 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
- ph = numpy.angle(U[i,i])
2321
- phi_list.append((ph, i))
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 abs(theta[0] % (2*numpy.pi)) > tol:
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 abs(phi[0]) > tol:
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
- def reconstruct_matrix_from_givens(n, theta_list, phi_list, to_real_if_possible = True, tol = 1e-12):
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) # Transpose of Givens matrix applied to the left
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)