tequila-basic 1.9.4__py3-none-any.whl → 1.9.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tequila/circuit/gates.py +1 -1
- tequila/circuit/qasm.py +25 -11
- tequila/circuit/qpic.py +1 -1
- tequila/grouping/binary_rep.py +1 -1
- tequila/quantumchemistry/__init__.py +2 -0
- tequila/quantumchemistry/chemistry_tools.py +146 -19
- tequila/quantumchemistry/encodings.py +141 -47
- tequila/quantumchemistry/madness_interface.py +4 -1
- tequila/quantumchemistry/orbital_optimizer.py +10 -4
- tequila/quantumchemistry/pyscf_interface.py +1 -0
- tequila/quantumchemistry/qc_base.py +325 -24
- tequila/simulators/simulator_api.py +6 -1
- tequila/simulators/simulator_qiskit.py +14 -6
- tequila/tools/random_generators.py +17 -0
- tequila/version.py +1 -1
- {tequila_basic-1.9.4.dist-info → tequila_basic-1.9.6.dist-info}/METADATA +12 -10
- {tequila_basic-1.9.4.dist-info → tequila_basic-1.9.6.dist-info}/RECORD +20 -20
- {tequila_basic-1.9.4.dist-info → tequila_basic-1.9.6.dist-info}/WHEEL +1 -1
- {tequila_basic-1.9.4.dist-info → tequila_basic-1.9.6.dist-info}/LICENSE +0 -0
- {tequila_basic-1.9.4.dist-info → tequila_basic-1.9.6.dist-info}/top_level.txt +0 -0
tequila/circuit/gates.py
CHANGED
@@ -940,7 +940,7 @@ def QubitExcitation(angle: typing.Union[numbers.Real, Variable, typing.Hashable]
|
|
940
940
|
except:
|
941
941
|
raise Exception("QubitExcitation: Needs an even number of targets")
|
942
942
|
|
943
|
-
return QCircuit.wrap_gate(QubitExcitationImpl(angle=angle, target=target, assume_real=assume_real, compile_options=compile_options))
|
943
|
+
return QCircuit.wrap_gate(QubitExcitationImpl(angle=angle, target=target, assume_real=assume_real, compile_options=compile_options, control=control))
|
944
944
|
|
945
945
|
|
946
946
|
"""
|
tequila/circuit/qasm.py
CHANGED
@@ -313,19 +313,33 @@ def parse_command(command: str, custom_gates_map: Dict[str, QCircuit], qregister
|
|
313
313
|
return apply_custom_gate(custom_circuit=custom_circuit, qregisters_values=qregisters_values)
|
314
314
|
|
315
315
|
if name in ("x", "y", "z", "h", "cx", "cy", "cz", "ch"):
|
316
|
-
|
317
|
-
|
318
|
-
|
316
|
+
target = get_qregister(args[0], qregisters)
|
317
|
+
control = None
|
318
|
+
if name[0].lower() == 'c':
|
319
|
+
control = get_qregister(args[0], qregisters)
|
320
|
+
target = get_qregister(args[1], qregisters)
|
321
|
+
name = name[1]
|
322
|
+
G = getattr(gates, name.upper())
|
323
|
+
return G(control=control, target=target)
|
324
|
+
|
319
325
|
if name in ("ccx", "ccy", "ccz"):
|
320
|
-
|
321
|
-
|
322
|
-
|
326
|
+
G = getattr(gates, name[2].upper())
|
327
|
+
control = [get_qregister(args[0], qregisters), get_qregister(args[1], qregisters)]
|
328
|
+
target = get_qregister(args[2], qregisters)
|
329
|
+
return G(control=control, target=target)
|
330
|
+
|
323
331
|
if name.startswith("rx(") or name.startswith("ry(") or name.startswith("rz(") or \
|
324
332
|
name.startswith("crx(") or name.startswith("cry(") or name.startswith("crz("):
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
333
|
+
angle = get_angle(name)[0]
|
334
|
+
i = name.find('(')
|
335
|
+
name = name[0:i]
|
336
|
+
name = name.upper()
|
337
|
+
name = [x for x in name]
|
338
|
+
name[-1] = name[-1].lower()
|
339
|
+
name = "".join(name)
|
340
|
+
G = getattr(gates, name)
|
341
|
+
return G(angle=angle,control=get_qregister(args[0], qregisters) if name[0] == 'C' else None,target=get_qregister(args[1 if name[0] == 'C' else 0], qregisters))
|
342
|
+
|
329
343
|
if name.startswith("U("):
|
330
344
|
angles = get_angle(name)
|
331
345
|
return gates.U(theta=angles[0], phi=angles[1], lambd=angles[2],
|
@@ -362,7 +376,7 @@ def parse_command(command: str, custom_gates_map: Dict[str, QCircuit], qregister
|
|
362
376
|
control=get_qregister(args[0], qregisters),
|
363
377
|
target=get_qregister(args[1], qregisters))
|
364
378
|
if name in ("s", "t", "sdg", "tdg"):
|
365
|
-
g = gates.Phase(pi / (2 if name.startswith("s") else 4),
|
379
|
+
g = gates.Phase(angle=pi / (2 if name.startswith("s") else 4),
|
366
380
|
control=None,
|
367
381
|
target=get_qregister(args[0], qregisters))
|
368
382
|
if name.find("dg") != -1:
|
tequila/circuit/qpic.py
CHANGED
@@ -224,7 +224,7 @@ def export_to(circuit,
|
|
224
224
|
'always_use_generators': True,
|
225
225
|
'group_together': "BARRIER"
|
226
226
|
}
|
227
|
-
elif not hasattr(
|
227
|
+
elif not hasattr(style, "items"):
|
228
228
|
raise Exception(
|
229
229
|
"style needs to be `tequila`, or `standard` or `generators` or a dictionary, you gave: {}".format(
|
230
230
|
str(style)))
|
tequila/grouping/binary_rep.py
CHANGED
@@ -26,7 +26,7 @@ class BinaryHamiltonian:
|
|
26
26
|
del Hof.terms[()]
|
27
27
|
hamiltonian = QubitHamiltonian.from_openfermion(Hof)
|
28
28
|
if n_qubits is None:
|
29
|
-
n_qubits = hamiltonian.
|
29
|
+
n_qubits = max(hamiltonian.qubits)+1
|
30
30
|
binary_terms = [
|
31
31
|
BinaryPauliString(
|
32
32
|
p.binary(n_qubits).binary,
|
@@ -95,6 +95,8 @@ def Molecule(geometry: str = None,
|
|
95
95
|
if backend is None:
|
96
96
|
if basis_set is None or basis_set.lower() in ["madness", "mra", "pno"]:
|
97
97
|
backend = "madness"
|
98
|
+
basis_set = "mra"
|
99
|
+
parameters.basis_set = basis_set
|
98
100
|
if orbital_type is not None and orbital_type.lower() not in ["pno", "mra-pno"]:
|
99
101
|
warnings.warn("only PNOs supported as orbital_type without basis set. Setting to pno - You gave={}".format(orbital_type), TequilaWarning)
|
100
102
|
orbital_type = "pno"
|
@@ -2,12 +2,12 @@ import os
|
|
2
2
|
import typing
|
3
3
|
import warnings
|
4
4
|
from dataclasses import dataclass
|
5
|
-
|
5
|
+
from copy import deepcopy
|
6
|
+
from numbers import Real
|
6
7
|
import numpy
|
7
8
|
|
8
|
-
from tequila import BitString, QCircuit, TequilaException
|
9
|
+
from tequila import BitString, QCircuit, TequilaException,Variable,compile_circuit
|
9
10
|
from tequila.circuit import gates
|
10
|
-
|
11
11
|
try:
|
12
12
|
from openfermion.ops.representations import get_active_space_integrals # needs openfermion 1.3
|
13
13
|
except ImportError as E:
|
@@ -50,16 +50,132 @@ class FermionicGateImpl(gates.QubitExcitationImpl):
|
|
50
50
|
self._name = "FermionicExcitation"
|
51
51
|
self.transformation = transformation
|
52
52
|
self.indices = indices
|
53
|
-
|
53
|
+
if not hasattr(indices[0],"__len__"):
|
54
|
+
self.indices = [(indices[2 * i], indices[2 * i+1]) for i in range(len(indices) // 2)]
|
55
|
+
self.sign = self.format_excitation_variables(self.indices)
|
56
|
+
self.indices = self.format_excitation_indices(self.indices)
|
54
57
|
def compile(self, *args, **kwargs):
|
55
58
|
if self.is_convertable_to_qubit_excitation():
|
56
59
|
target = []
|
57
60
|
for x in self.indices:
|
58
61
|
for y in x:
|
59
62
|
target.append(y)
|
60
|
-
return gates.QubitExcitation(target=target, angle
|
63
|
+
return gates.QubitExcitation(target=target, angle=self.parameter, control=self.control)
|
61
64
|
else:
|
62
|
-
|
65
|
+
if self.transformation.lower().strip("_") == "jordanwigner":
|
66
|
+
return self.fermionic_excitation(angle=self.sign*self.parameter, indices=self.indices, control=self.control,opt=False)
|
67
|
+
else:
|
68
|
+
return gates.Trotterized(generator=self.generator, control=self.control, angle=self.parameter, steps=1)
|
69
|
+
def format_excitation_indices(self, idx):
|
70
|
+
"""
|
71
|
+
Consistent formatting of excitation indices
|
72
|
+
idx = [(p0,q0),(p1,q1),...,(pn,qn)]
|
73
|
+
sorted as: p0<p1<pn and pi<qi
|
74
|
+
:param idx: list of index tuples describing a single(!) fermionic excitation
|
75
|
+
:return: list of index tuples
|
76
|
+
"""
|
77
|
+
|
78
|
+
idx = [tuple(sorted(x)) for x in idx]
|
79
|
+
idx = sorted(idx, key=lambda x: x[0])
|
80
|
+
return list(idx)
|
81
|
+
def format_excitation_variables(self, idx):
|
82
|
+
"""
|
83
|
+
Consistent formatting of excitation variable
|
84
|
+
idx = [(p0,q0),(p1,q1),...,(pn,qn)]
|
85
|
+
sorted as: pi<qi and p0 < p1 < p2
|
86
|
+
:param idx: list of index tuples describing a single(!) fermionic excitation
|
87
|
+
:return: sign of the variable with re-ordered indices
|
88
|
+
"""
|
89
|
+
sig = 1
|
90
|
+
for pair in idx:
|
91
|
+
if pair[1]>pair[0]:
|
92
|
+
sig *= -1
|
93
|
+
for pair in range(len(idx)-1):
|
94
|
+
if idx[pair+1][0]>idx[pair][0]:
|
95
|
+
sig *= -1
|
96
|
+
return sig
|
97
|
+
def cCRy(self, target: int, dcontrol: typing.Union[list, int], control: typing.Union[list, int],
|
98
|
+
angle: typing.Union[Real, Variable, typing.Hashable], case: int = 1) -> QCircuit:
|
99
|
+
'''
|
100
|
+
Compilation of CRy as on https://doi.org/10.1103/PhysRevA.102.062612
|
101
|
+
If not control passed, Ry returned
|
102
|
+
Parameters
|
103
|
+
----------
|
104
|
+
case: if 1 employs eq. 12 from the paper, if 0 eq. 13
|
105
|
+
'''
|
106
|
+
if control is not None and not len(control):
|
107
|
+
control = None
|
108
|
+
if isinstance(dcontrol, int):
|
109
|
+
dcontrol = [dcontrol]
|
110
|
+
if not len(dcontrol):
|
111
|
+
return compile_circuit(gates.Ry(angle=angle, target=target, control=control))
|
112
|
+
else:
|
113
|
+
if isinstance(angle, str):
|
114
|
+
angle = Variable(angle)
|
115
|
+
U = QCircuit()
|
116
|
+
aux = dcontrol[0]
|
117
|
+
ctr = deepcopy(dcontrol)
|
118
|
+
ctr.pop(0)
|
119
|
+
if case:
|
120
|
+
U += self.cCRy(target=target, dcontrol=ctr, angle=angle / 2, case=1, control=control) + gates.H(
|
121
|
+
aux) + gates.CNOT(target, aux)
|
122
|
+
U += self.cCRy(target=target, dcontrol=ctr, angle=-angle / 2, case=0, control=control) + gates.CNOT(
|
123
|
+
target, aux) + gates.H(aux)
|
124
|
+
else:
|
125
|
+
U += gates.H(aux) + gates.CNOT(target, aux) + self.cCRy(target=target, dcontrol=ctr, angle=-angle / 2,
|
126
|
+
case=0, control=control)
|
127
|
+
U += gates.CNOT(target, aux) + gates.H(aux) + self.cCRy(target=target, dcontrol=ctr, angle=angle / 2,
|
128
|
+
case=1, control=control)
|
129
|
+
return U
|
130
|
+
|
131
|
+
def fermionic_excitation(self, angle: typing.Union[Real, Variable, typing.Hashable], indices: typing.List,
|
132
|
+
control: typing.Union[int, typing.List] = None, opt: bool = True) -> QCircuit:
|
133
|
+
'''
|
134
|
+
Excitation [(i,j),(k,l)],... compiled following https://doi.org/10.1103/PhysRevA.102.062612
|
135
|
+
opt: whether to optimized CNOT H CNOT --> Rz Rz CNOT Rz
|
136
|
+
'''
|
137
|
+
lto = []
|
138
|
+
lfrom = []
|
139
|
+
if isinstance(indices,tuple) and not hasattr(indices[0],"__len__"):
|
140
|
+
indices = [(indices[2 * i], indices[2 * i + 1]) for i in range(len(indices) // 2)]
|
141
|
+
for pair in indices:
|
142
|
+
lfrom.append(pair[0])
|
143
|
+
lto.append(pair[1])
|
144
|
+
Upair = QCircuit()
|
145
|
+
if isinstance(angle, str) or isinstance(angle, tuple):
|
146
|
+
angle = Variable(angle)
|
147
|
+
for i in range(len(lfrom) - 1):
|
148
|
+
Upair += gates.CNOT(lfrom[i + 1], lfrom[i])
|
149
|
+
Upair += gates.CNOT(lto[i + 1], lto[i])
|
150
|
+
Upair += gates.X(lto[i]) + gates.X(lfrom[i])
|
151
|
+
Upair += gates.CNOT(lto[-1], lfrom[-1])
|
152
|
+
crt = lfrom[::-1] + lto
|
153
|
+
Uladder = QCircuit()
|
154
|
+
pairs = lfrom + lto
|
155
|
+
pairs.sort()
|
156
|
+
orbs = []
|
157
|
+
for o in range(len(pairs) // 2):
|
158
|
+
orbs += [*range(pairs[2 * o] + 1, pairs[2 * o + 1])]
|
159
|
+
if len(orbs):
|
160
|
+
for o in range(len(orbs) - 1):
|
161
|
+
Uladder += gates.CNOT(orbs[o], orbs[o + 1])
|
162
|
+
Uladder += compile_circuit(gates.CZ(orbs[-1], lto[-1]))
|
163
|
+
crt.pop(-1)
|
164
|
+
if control is not None and (isinstance(control, int) or len(control) == 1):
|
165
|
+
if isinstance(control, int):
|
166
|
+
crt.append(control)
|
167
|
+
else:
|
168
|
+
crt = crt + control
|
169
|
+
control = []
|
170
|
+
Ur = self.cCRy(target=lto[-1], dcontrol=crt, angle=angle, control=control)
|
171
|
+
Upair2 = Upair.dagger()
|
172
|
+
if opt:
|
173
|
+
Ur.gates.pop(-1)
|
174
|
+
Ur.gates.pop(-1)
|
175
|
+
Upair2.gates.pop(0)
|
176
|
+
Ur += gates.Rz(numpy.pi / 2, target=lto[-1]) + gates.Rz(-numpy.pi / 2, target=lfrom[-1])
|
177
|
+
Ur += gates.CNOT(lto[-1], lfrom[-1]) + gates.Rz(numpy.pi / 2, target=lfrom[-1]) + gates.H(lfrom[-1])
|
178
|
+
return Upair + Uladder + Ur + Uladder.dagger() + Upair2
|
63
179
|
|
64
180
|
def __str(self):
|
65
181
|
if self.indices is not None:
|
@@ -804,20 +920,18 @@ class IntegralManager:
|
|
804
920
|
_one_body_integrals: numpy.ndarray = None
|
805
921
|
_two_body_integrals: NBodyTensor = None
|
806
922
|
_constant_term: float = None
|
807
|
-
_basis_type: str = "unknown"
|
808
923
|
_basis_name: str = "unknown"
|
809
924
|
_orbital_type: str = "unknown" # e.g. "HF", "PNO", "native"
|
810
925
|
_orbital_coefficients: numpy.ndarray = None
|
811
926
|
_active_space: ActiveSpaceData = None
|
812
927
|
_orbitals: typing.List[OrbitalData] = None
|
813
928
|
|
814
|
-
def __init__(self, one_body_integrals, two_body_integrals,
|
929
|
+
def __init__(self, one_body_integrals, two_body_integrals,
|
815
930
|
basis_name="unknown", orbital_type="unknown",
|
816
931
|
constant_term=0.0, orbital_coefficients=None, active_space=None, overlap_integrals=None, orbitals=None, *args, **kwargs):
|
817
932
|
self._one_body_integrals = one_body_integrals
|
818
933
|
self._two_body_integrals = two_body_integrals
|
819
934
|
self._constant_term = constant_term
|
820
|
-
self._basis_type = basis_type
|
821
935
|
self._basis_name = basis_name
|
822
936
|
self._orbital_type = orbital_type
|
823
937
|
|
@@ -956,9 +1070,16 @@ class IntegralManager:
|
|
956
1070
|
"""
|
957
1071
|
c = self.get_orthonormalized_orbital_coefficients()
|
958
1072
|
self.orbital_coefficients=c
|
959
|
-
self._orbital_type="orthonormalized-{}-basis".format(self.
|
1073
|
+
self._orbital_type="orthonormalized-{}-basis".format(self._basis_name)
|
1074
|
+
|
1075
|
+
def is_unitary(self, U):
|
1076
|
+
if len(U.shape) != 2: return False
|
1077
|
+
if U.shape[0] != U.shape[1]: return False
|
1078
|
+
test = (U.conj().T).dot(U) - numpy.eye(U.shape[0])
|
1079
|
+
if not numpy.isclose(numpy.linalg.norm(test), 0.0): return False
|
1080
|
+
return True
|
960
1081
|
|
961
|
-
def transform_orbitals(self, U):
|
1082
|
+
def transform_orbitals(self, U, name=None):
|
962
1083
|
"""
|
963
1084
|
Transform orbitals
|
964
1085
|
Parameters
|
@@ -969,10 +1090,12 @@ class IntegralManager:
|
|
969
1090
|
-------
|
970
1091
|
updates the structure with new orbitals: c = cU
|
971
1092
|
"""
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
1093
|
+
assert self.is_unitary(U)
|
1094
|
+
self.orbital_coefficients = numpy.einsum("ix, xj -> ij", self.orbital_coefficients, U, optimize="greedy")
|
1095
|
+
if name is None:
|
1096
|
+
self._orbital_type += "-transformed"
|
1097
|
+
else:
|
1098
|
+
self._orbital_type = name
|
976
1099
|
|
977
1100
|
def get_integrals(self, orbital_coefficients=None, ordering="openfermion", ignore_active_space=False, *args, **kwargs):
|
978
1101
|
"""
|
@@ -1001,7 +1124,9 @@ class IntegralManager:
|
|
1001
1124
|
active_integrals = get_active_space_integrals(one_body_integrals=h, two_body_integrals=g,
|
1002
1125
|
occupied_indices=self._active_space.frozen_reference_orbitals,
|
1003
1126
|
active_indices=self._active_space.active_orbitals)
|
1127
|
+
|
1004
1128
|
c = active_integrals[0] + c
|
1129
|
+
|
1005
1130
|
h = active_integrals[1]
|
1006
1131
|
g = NBodyTensor(elems=active_integrals[2], ordering="openfermion")
|
1007
1132
|
g.reorder(to=ordering)
|
@@ -1069,14 +1194,16 @@ class IntegralManager:
|
|
1069
1194
|
result += str(x) + "\n"
|
1070
1195
|
return result
|
1071
1196
|
|
1072
|
-
def print_basis_info(self, *args, **kwargs) -> None:
|
1073
|
-
print("{:15} : {}".format("basis_type", self._basis_type), *args, **kwargs)
|
1197
|
+
def print_basis_info(self, print_coefficients=True, *args, **kwargs) -> None:
|
1074
1198
|
print("{:15} : {}".format("basis_name", self._basis_name), *args, **kwargs)
|
1075
1199
|
print("{:15} : {}".format("orbital_type", self._orbital_type), *args, **kwargs)
|
1076
|
-
print("{:15} : {}".format("orthogonal", self.basis_is_orthogonal()), *args, **kwargs)
|
1077
|
-
print("{:15} : {}".format("functions", self.one_body_integrals.shape[0]), *args, **kwargs)
|
1200
|
+
print("{:15} : {}".format("orthogonal basis", self.basis_is_orthogonal()), *args, **kwargs)
|
1201
|
+
print("{:15} : {}".format("basis functions", self.one_body_integrals.shape[0]), *args, **kwargs)
|
1202
|
+
print("{:15} : {}".format("active orbitals", [o.idx_total for o in self.active_orbitals]), *args, **kwargs)
|
1078
1203
|
print("{:15} : {}".format("reference", [x.idx_total for x in self.reference_orbitals]), *args, **kwargs)
|
1079
1204
|
|
1205
|
+
if not print_coefficients: return
|
1206
|
+
|
1080
1207
|
print("Current Orbitals", *args, **kwargs)
|
1081
1208
|
for i,x in enumerate(self.orbitals):
|
1082
1209
|
print(x, *args, **kwargs)
|
@@ -2,16 +2,21 @@
|
|
2
2
|
Collections of Fermion-to-Qubit encodings known to tequila
|
3
3
|
Most are Interfaces to OpenFermion
|
4
4
|
"""
|
5
|
+
import abc
|
6
|
+
|
7
|
+
from tequila import TequilaException
|
5
8
|
from tequila.circuit.circuit import QCircuit
|
6
|
-
from tequila.circuit.gates import X
|
9
|
+
from tequila.circuit.gates import X, CNOT
|
7
10
|
from tequila.hamiltonian.qubit_hamiltonian import QubitHamiltonian
|
8
11
|
import openfermion
|
12
|
+
import numpy
|
13
|
+
|
9
14
|
|
10
15
|
def known_encodings():
|
11
16
|
# convenience for testing and I/O
|
12
|
-
encodings= {
|
13
|
-
"JordanWigner":JordanWigner,
|
14
|
-
"BravyiKitaev":BravyiKitaev,
|
17
|
+
encodings = {
|
18
|
+
"JordanWigner": JordanWigner,
|
19
|
+
"BravyiKitaev": BravyiKitaev,
|
15
20
|
"BravyiKitaevFast": BravyiKitaevFast,
|
16
21
|
"BravyiKitaevTree": BravyiKitaevTree,
|
17
22
|
"TaperedBravyiKitaev": TaperedBravyKitaev
|
@@ -22,14 +27,14 @@ def known_encodings():
|
|
22
27
|
"ReorderedBravyiKitaev": lambda **kwargs: BravyiKitaev(up_then_down=True, **kwargs),
|
23
28
|
"ReorderedBravyiKitaevTree": lambda **kwargs: BravyiKitaevTree(up_then_down=True, **kwargs),
|
24
29
|
}
|
25
|
-
return {k.replace("_","").replace("-","").upper():v for k,v in encodings.items()}
|
30
|
+
return {k.replace("_", "").replace("-", "").upper(): v for k, v in encodings.items()}
|
26
31
|
|
27
|
-
class EncodingBase:
|
28
32
|
|
33
|
+
class EncodingBase(metaclass=abc.ABCMeta):
|
29
34
|
# true if the encoding is fully integrated
|
30
35
|
# false: can only do special things (like building the Hamiltionian)
|
31
36
|
# but is not consistent with UCC gate generation
|
32
|
-
_ucc_support=False
|
37
|
+
_ucc_support = False
|
33
38
|
|
34
39
|
@property
|
35
40
|
def supports_ucc(self):
|
@@ -37,20 +42,20 @@ class EncodingBase:
|
|
37
42
|
|
38
43
|
@property
|
39
44
|
def name(self):
|
40
|
-
prefix=""
|
45
|
+
prefix = ""
|
41
46
|
if self.up_then_down:
|
42
|
-
prefix="Reordered"
|
47
|
+
prefix = "Reordered"
|
43
48
|
if hasattr(self, "_name"):
|
44
|
-
return prefix+self._name
|
49
|
+
return prefix + self._name
|
45
50
|
else:
|
46
|
-
return prefix+type(self).__name__
|
51
|
+
return prefix + type(self).__name__
|
47
52
|
|
48
53
|
def __init__(self, n_electrons, n_orbitals, up_then_down=False, *args, **kwargs):
|
49
54
|
self.n_electrons = n_electrons
|
50
55
|
self.n_orbitals = n_orbitals
|
51
56
|
self.up_then_down = up_then_down
|
52
57
|
|
53
|
-
def __call__(self, fermion_operator:openfermion.FermionOperator, *args, **kwargs) -> QubitHamiltonian:
|
58
|
+
def __call__(self, fermion_operator: openfermion.FermionOperator, *args, **kwargs) -> QubitHamiltonian:
|
54
59
|
"""
|
55
60
|
:param fermion_operator:
|
56
61
|
an openfermion FermionOperator
|
@@ -58,7 +63,8 @@ class EncodingBase:
|
|
58
63
|
The openfermion QubitOperator of this class ecoding
|
59
64
|
"""
|
60
65
|
if self.up_then_down:
|
61
|
-
op = openfermion.reorder(operator=fermion_operator, order_function=openfermion.up_then_down,
|
66
|
+
op = openfermion.reorder(operator=fermion_operator, order_function=openfermion.up_then_down,
|
67
|
+
num_modes=2 * self.n_orbitals)
|
62
68
|
else:
|
63
69
|
op = fermion_operator
|
64
70
|
|
@@ -73,18 +79,18 @@ class EncodingBase:
|
|
73
79
|
if self.up_then_down:
|
74
80
|
return i
|
75
81
|
else:
|
76
|
-
return 2*i
|
82
|
+
return 2 * i
|
77
83
|
|
78
84
|
def down(self, i):
|
79
85
|
if self.up_then_down:
|
80
|
-
return i+self.n_orbitals
|
86
|
+
return i + self.n_orbitals
|
81
87
|
else:
|
82
|
-
return 2*i+1
|
88
|
+
return 2 * i + 1
|
83
89
|
|
84
|
-
def do_transform(self, fermion_operator:openfermion.FermionOperator, *args, **kwargs) -> openfermion.QubitOperator:
|
90
|
+
def do_transform(self, fermion_operator: openfermion.FermionOperator, *args, **kwargs) -> openfermion.QubitOperator:
|
85
91
|
raise Exception("{}::do_transform: called base class".format(type(self).__name__))
|
86
92
|
|
87
|
-
def map_state(self, state:list, *args, **kwargs) -> list:
|
93
|
+
def map_state(self, state: list, *args, **kwargs) -> list:
|
88
94
|
"""
|
89
95
|
Expects a state in spin-orbital ordering
|
90
96
|
Returns the corresponding qubit state in the class encoding
|
@@ -112,7 +118,7 @@ class EncodingBase:
|
|
112
118
|
# default is a lazy workaround, but it workds
|
113
119
|
n_qubits = 2 * self.n_orbitals
|
114
120
|
|
115
|
-
spin_orbitals = sorted([i for i,x in enumerate(state) if int(x)==1])
|
121
|
+
spin_orbitals = sorted([i for i, x in enumerate(state) if int(x) == 1])
|
116
122
|
|
117
123
|
string = "1.0 ["
|
118
124
|
for i in spin_orbitals:
|
@@ -128,26 +134,82 @@ class EncodingBase:
|
|
128
134
|
key = list(wfn.keys())[0].array
|
129
135
|
return key
|
130
136
|
|
131
|
-
|
132
|
-
|
137
|
+
@abc.abstractmethod
|
138
|
+
def me_to_jw(self) -> QCircuit:
|
139
|
+
"""
|
140
|
+
This method needs to be implemented to enable default conversions via Jordan-Wigner
|
141
|
+
"""
|
142
|
+
pass
|
143
|
+
|
144
|
+
# independent conversion methods, these are used for default conversions
|
145
|
+
# arXiv:1808.10402 IV. B. 2, Eq. 57
|
146
|
+
# original: https://doi.org/10.1063/1.4768229
|
147
|
+
def _jw_to_bk(self) -> QCircuit:
|
148
|
+
U = QCircuit() # Constructs empty circuit
|
149
|
+
|
150
|
+
flipper = False
|
151
|
+
for i in range(self.n_orbitals * 2):
|
152
|
+
# even qubits only hold their own value
|
153
|
+
if i % 2 == 0:
|
154
|
+
continue
|
155
|
+
|
156
|
+
# sum always includes the last qubit
|
157
|
+
U += CNOT(control=i - 1, target=i)
|
158
|
+
|
159
|
+
# every second odd qubit ties together with the last odd qubit
|
160
|
+
if flipper:
|
161
|
+
U += CNOT(control=i - 2, target=i)
|
162
|
+
|
163
|
+
flipper = not flipper
|
164
|
+
|
165
|
+
# we have now created the 4x4 blocks on the diagonal of this operators matrix
|
166
|
+
|
167
|
+
# every power of 2 connects to the last power of 2
|
168
|
+
# this corresponds to the last row in the recursive definitions being all 1s
|
169
|
+
x = numpy.log2(i + 1)
|
170
|
+
if x.is_integer() and x >= 3:
|
171
|
+
x = int(x)
|
172
|
+
U += CNOT(control=2 ** (x - 1) - 1, target=i)
|
173
|
+
|
174
|
+
return U
|
175
|
+
|
176
|
+
def _hcb_to_jw(self):
|
177
|
+
U = QCircuit()
|
178
|
+
for i in range(self.n_orbitals):
|
179
|
+
U += X(target=self.down(i), control=self.up(i))
|
180
|
+
return U
|
181
|
+
|
182
|
+
# Convenience Methods
|
183
|
+
def jw_to_me(self) -> QCircuit:
|
184
|
+
return self.me_to_jw().dagger()
|
185
|
+
|
186
|
+
def me_to_bk(self) -> QCircuit:
|
187
|
+
return self.me_to_jw() + self._jw_to_bk()
|
188
|
+
|
189
|
+
def bk_to_me(self) -> QCircuit:
|
190
|
+
return self.me_to_bk().dagger()
|
191
|
+
|
192
|
+
def hcb_to_me(self) -> QCircuit:
|
193
|
+
return self._hcb_to_jw() + self.jw_to_me()
|
133
194
|
|
134
195
|
def __str__(self):
|
135
196
|
return type(self).__name__
|
136
197
|
|
198
|
+
|
137
199
|
class JordanWigner(EncodingBase):
|
138
200
|
"""
|
139
201
|
OpenFermion::jordan_wigner
|
140
202
|
"""
|
141
|
-
_ucc_support=True
|
203
|
+
_ucc_support = True
|
142
204
|
|
143
|
-
def do_transform(self, fermion_operator:openfermion.FermionOperator, *args, **kwargs) -> openfermion.QubitOperator:
|
205
|
+
def do_transform(self, fermion_operator: openfermion.FermionOperator, *args, **kwargs) -> openfermion.QubitOperator:
|
144
206
|
return openfermion.jordan_wigner(fermion_operator, *args, **kwargs)
|
145
207
|
|
146
|
-
def map_state(self, state:list, *args, **kwargs):
|
147
|
-
state = state + [0]*(self.n_orbitals-len(state))
|
148
|
-
result = [0]*len(state)
|
208
|
+
def map_state(self, state: list, *args, **kwargs):
|
209
|
+
state = state + [0] * (self.n_orbitals - len(state))
|
210
|
+
result = [0] * len(state)
|
149
211
|
if self.up_then_down:
|
150
|
-
return [state[2*i] for i in range(self.n_orbitals)] + [state[2*i+1] for i in range(self.n_orbitals)]
|
212
|
+
return [state[2 * i] for i in range(self.n_orbitals)] + [state[2 * i + 1] for i in range(self.n_orbitals)]
|
151
213
|
else:
|
152
214
|
return state
|
153
215
|
|
@@ -157,15 +219,34 @@ class JordanWigner(EncodingBase):
|
|
157
219
|
U += X(target=self.down(i), control=self.up(i))
|
158
220
|
return U
|
159
221
|
|
222
|
+
def me_to_jw(self) -> QCircuit:
|
223
|
+
return QCircuit()
|
224
|
+
|
225
|
+
def jw_to_me(self) -> QCircuit:
|
226
|
+
return QCircuit()
|
227
|
+
|
228
|
+
|
160
229
|
class BravyiKitaev(EncodingBase):
|
161
230
|
"""
|
162
231
|
Uses OpenFermion::bravyi_kitaev
|
163
232
|
"""
|
164
233
|
|
165
|
-
_ucc_support=True
|
234
|
+
_ucc_support = True
|
235
|
+
|
236
|
+
def do_transform(self, fermion_operator: openfermion.FermionOperator, *args, **kwargs) -> openfermion.QubitOperator:
|
237
|
+
return openfermion.bravyi_kitaev(fermion_operator, n_qubits=self.n_orbitals * 2)
|
238
|
+
|
239
|
+
def me_to_jw(self) -> QCircuit:
|
240
|
+
return self._jw_to_bk().dagger()
|
166
241
|
|
167
|
-
def
|
168
|
-
return
|
242
|
+
def jw_to_me(self) -> QCircuit:
|
243
|
+
return self._jw_to_bk()
|
244
|
+
|
245
|
+
def bk_to_me(self) -> QCircuit:
|
246
|
+
return QCircuit()
|
247
|
+
|
248
|
+
def me_to_bk(self) -> QCircuit:
|
249
|
+
return QCircuit()
|
169
250
|
|
170
251
|
|
171
252
|
class BravyiKitaevTree(EncodingBase):
|
@@ -173,28 +254,37 @@ class BravyiKitaevTree(EncodingBase):
|
|
173
254
|
Uses OpenFermion::bravyi_kitaev_tree
|
174
255
|
"""
|
175
256
|
|
176
|
-
_ucc_support=True
|
257
|
+
_ucc_support = True
|
258
|
+
|
259
|
+
def do_transform(self, fermion_operator: openfermion.FermionOperator, *args, **kwargs) -> openfermion.QubitOperator:
|
260
|
+
return openfermion.bravyi_kitaev_tree(fermion_operator, n_qubits=self.n_orbitals * 2)
|
261
|
+
|
262
|
+
def me_to_jw(self) -> QCircuit:
|
263
|
+
raise TequilaException("{}::me_to_jw: unimplemented".format(type(self).__name__))
|
177
264
|
|
178
|
-
def do_transform(self, fermion_operator:openfermion.FermionOperator, *args, **kwargs) -> openfermion.QubitOperator:
|
179
|
-
return openfermion.bravyi_kitaev_tree(fermion_operator, n_qubits=self.n_orbitals*2)
|
180
265
|
|
181
266
|
class BravyiKitaevFast(EncodingBase):
|
182
267
|
"""
|
183
268
|
Uses OpenFermion::bravyi_kitaev_tree
|
184
269
|
"""
|
185
270
|
|
186
|
-
_ucc_support=False
|
271
|
+
_ucc_support = False
|
187
272
|
|
188
|
-
def do_transform(self, fermion_operator:openfermion.FermionOperator, *args, **kwargs) -> openfermion.QubitOperator:
|
273
|
+
def do_transform(self, fermion_operator: openfermion.FermionOperator, *args, **kwargs) -> openfermion.QubitOperator:
|
189
274
|
n_qubits = openfermion.count_qubits(fermion_operator)
|
190
|
-
if n_qubits != self.n_orbitals*2:
|
191
|
-
raise Exception(
|
275
|
+
if n_qubits != self.n_orbitals * 2:
|
276
|
+
raise Exception(
|
277
|
+
"BravyiKitaevFast transformation currently only possible for full Hamiltonians (no UCC generators).\nfermion_operator was {}".format(
|
278
|
+
fermion_operator))
|
192
279
|
op = openfermion.get_interaction_operator(fermion_operator)
|
193
280
|
return openfermion.bravyi_kitaev_fast(op)
|
194
281
|
|
195
|
-
|
282
|
+
def me_to_jw(self) -> QCircuit:
|
283
|
+
raise TequilaException("{}::me_to_jw: unimplemented".format(type(self).__name__))
|
196
284
|
|
197
|
-
|
285
|
+
|
286
|
+
class TaperedBravyKitaev(EncodingBase):
|
287
|
+
_ucc_support = False
|
198
288
|
|
199
289
|
"""
|
200
290
|
Uses OpenFermion::symmetry_conserving_bravyi_kitaev (tapered bravyi_kitaev_tree arxiv:1701.07072)
|
@@ -202,6 +292,7 @@ class TaperedBravyKitaev(EncodingBase):
|
|
202
292
|
See OpenFermion Documentation for more
|
203
293
|
Does not work for UCC generators yet
|
204
294
|
"""
|
295
|
+
|
205
296
|
def __init__(self, n_electrons, n_orbitals, active_fermions=None, active_orbitals=None, *args, **kwargs):
|
206
297
|
if active_fermions is None:
|
207
298
|
self.active_fermions = n_electrons
|
@@ -209,7 +300,7 @@ class TaperedBravyKitaev(EncodingBase):
|
|
209
300
|
self.active_fermions = active_fermions
|
210
301
|
|
211
302
|
if active_orbitals is None:
|
212
|
-
self.active_orbitals = n_orbitals*2
|
303
|
+
self.active_orbitals = n_orbitals * 2 # in openfermion those are spin-orbitals
|
213
304
|
else:
|
214
305
|
self.active_orbitals = active_orbitals
|
215
306
|
|
@@ -217,17 +308,20 @@ class TaperedBravyKitaev(EncodingBase):
|
|
217
308
|
raise Exception("Don't pass up_then_down argument to {}, it can't be changed".format(type(self).__name__))
|
218
309
|
super().__init__(n_orbitals=n_orbitals, n_electrons=n_electrons, up_then_down=False, *args, **kwargs)
|
219
310
|
|
220
|
-
def do_transform(self, fermion_operator:openfermion.FermionOperator, *args, **kwargs) -> openfermion.QubitOperator:
|
221
|
-
if openfermion.count_qubits(fermion_operator) != self.n_orbitals*2:
|
311
|
+
def do_transform(self, fermion_operator: openfermion.FermionOperator, *args, **kwargs) -> openfermion.QubitOperator:
|
312
|
+
if openfermion.count_qubits(fermion_operator) != self.n_orbitals * 2:
|
222
313
|
raise Exception("TaperedBravyiKitaev not ready for UCC generators yet")
|
223
|
-
return openfermion.symmetry_conserving_bravyi_kitaev(fermion_operator, active_orbitals=self.active_orbitals,
|
314
|
+
return openfermion.symmetry_conserving_bravyi_kitaev(fermion_operator, active_orbitals=self.active_orbitals,
|
315
|
+
active_fermions=self.active_fermions)
|
224
316
|
|
225
|
-
def map_state(self, state:list, *args, **kwargs):
|
226
|
-
non_tapered_trafo = BravyiKitaevTree(up_then_down=True, n_electrons=self.n_electrons,
|
317
|
+
def map_state(self, state: list, *args, **kwargs):
|
318
|
+
non_tapered_trafo = BravyiKitaevTree(up_then_down=True, n_electrons=self.n_electrons,
|
319
|
+
n_orbitals=self.n_orbitals)
|
227
320
|
key = non_tapered_trafo.map_state(state=state, *args, **kwargs)
|
228
|
-
n_qubits = self.n_orbitals*2
|
321
|
+
n_qubits = self.n_orbitals * 2
|
229
322
|
active_qubits = [i for i in range(n_qubits) if i not in [n_qubits - 1, n_qubits // 2 - 1]]
|
230
323
|
key = [key[i] for i in active_qubits]
|
231
324
|
return key
|
232
325
|
|
233
|
-
|
326
|
+
def me_to_jw(self) -> QCircuit:
|
327
|
+
raise TequilaException("{}::me_to_jw: unimplemented".format(type(self).__name__))
|