tequila-basic 1.9.7__py3-none-any.whl → 1.9.9__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/apps/unary_state_prep.py +1 -2
- tequila/circuit/circuit.py +20 -15
- tequila/hamiltonian/paulis.py +4 -3
- tequila/objective/objective.py +5 -2
- tequila/quantumchemistry/chemistry_tools.py +8 -1
- tequila/quantumchemistry/encodings.py +2 -2
- tequila/quantumchemistry/orbital_optimizer.py +10 -2
- tequila/simulators/simulator_api.py +35 -9
- tequila/simulators/simulator_base.py +58 -28
- tequila/simulators/simulator_cirq.py +6 -6
- tequila/simulators/simulator_pyquil.py +3 -3
- tequila/simulators/simulator_qibo.py +14 -16
- tequila/simulators/simulator_qiskit.py +88 -53
- tequila/simulators/simulator_qiskit_gpu.py +9 -0
- tequila/simulators/simulator_qlm.py +3 -3
- tequila/simulators/simulator_qulacs.py +37 -28
- tequila/simulators/simulator_qulacs_gpu.py +6 -4
- tequila/simulators/simulator_spex.py +429 -0
- tequila/simulators/simulator_symbolic.py +15 -23
- tequila/simulators/test_spex_simulator.py +211 -0
- tequila/utils/bitstrings.py +14 -3
- tequila/version.py +1 -1
- tequila/wavefunction/qubit_wavefunction.py +340 -237
- {tequila_basic-1.9.7.dist-info → tequila_basic-1.9.9.dist-info}/METADATA +13 -9
- {tequila_basic-1.9.7.dist-info → tequila_basic-1.9.9.dist-info}/RECORD +28 -25
- {tequila_basic-1.9.7.dist-info → tequila_basic-1.9.9.dist-info}/WHEEL +1 -1
- {tequila_basic-1.9.7.dist-info → tequila_basic-1.9.9.dist-info/licenses}/LICENSE +0 -0
- {tequila_basic-1.9.7.dist-info → tequila_basic-1.9.9.dist-info}/top_level.txt +0 -0
@@ -1,296 +1,413 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
|
2
|
+
|
3
3
|
import typing
|
4
|
-
from
|
4
|
+
from copy import deepcopy
|
5
|
+
from math import log2
|
6
|
+
from typing import Union, Generator
|
7
|
+
|
5
8
|
import numpy
|
9
|
+
import numpy as np
|
10
|
+
import numpy.typing as npt
|
6
11
|
import numbers
|
7
12
|
|
8
|
-
|
9
|
-
from tequila import TequilaException
|
10
|
-
from tequila.utils.keymap import KeyMapLSB2MSB, KeyMapMSB2LSB
|
11
|
-
from tequila.tools import number_to_string
|
13
|
+
import sympy
|
12
14
|
|
13
|
-
|
15
|
+
from tequila.utils.bitstrings import BitString, reverse_int_bits
|
16
|
+
from tequila import TequilaException, BitNumbering, initialize_bitstring
|
17
|
+
from tequila.utils.keymap import KeyMapABC
|
14
18
|
|
15
19
|
if typing.TYPE_CHECKING:
|
16
|
-
#
|
20
|
+
# Don't need those structures, just for convenient type hinting
|
17
21
|
from tequila.hamiltonian.qubit_hamiltonian import QubitHamiltonian, PauliString
|
18
22
|
|
19
23
|
|
20
24
|
class QubitWaveFunction:
|
21
25
|
"""
|
22
|
-
|
23
|
-
|
26
|
+
Represents a wavefunction.
|
27
|
+
Amplitudes are either stored in a Numpy array for dense wavefunctions, or a dictionary for sparse wavefunctions.
|
28
|
+
Does not enforce normalization.
|
24
29
|
"""
|
25
30
|
|
26
|
-
numbering = BitNumbering.MSB
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
mapped_state = dict()
|
31
|
-
for k, v in self.state.items():
|
32
|
-
mapped_key=keymap(input_state=k, initial_state=initial_state)
|
33
|
-
if mapped_key in mapped_state:
|
34
|
-
mapped_state[mapped_key] += v
|
35
|
-
else:
|
36
|
-
mapped_state[mapped_key] = v
|
37
|
-
|
38
|
-
self.state = mapped_state
|
39
|
-
return self
|
31
|
+
def __init__(self, n_qubits: int, numbering: BitNumbering = BitNumbering.MSB, dense: bool = False,
|
32
|
+
init_state: bool = True) -> None:
|
33
|
+
"""
|
34
|
+
Initialize a QubitWaveFunction with all amplitudes set to zero.
|
40
35
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
36
|
+
:param n_qubits: Number of qubits.
|
37
|
+
:param numbering: Whether the first qubit is the most or least significant.
|
38
|
+
:param dense: Whether to store the amplitudes in a Numpy array instead of a dictionary.
|
39
|
+
:param init_state: Whether to initialize the state array.
|
40
|
+
If False, set_state must be called immediately after the constructor.
|
41
|
+
"""
|
42
|
+
self._n_qubits: int = n_qubits
|
43
|
+
self._numbering = numbering
|
44
|
+
self._dense = dense
|
45
|
+
if init_state:
|
46
|
+
self._state = np.zeros(2 ** self._n_qubits, dtype=complex) if dense else dict()
|
45
47
|
else:
|
46
|
-
|
48
|
+
self._state = None
|
47
49
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
@classmethod
|
51
|
+
def from_wavefunction(cls, wfn: QubitWaveFunction, keymap: KeyMapABC = None, n_qubits: int = None,
|
52
|
+
initial_state: BitString = None) -> QubitWaveFunction:
|
53
|
+
"""
|
54
|
+
Create a copy of a wavefunction.
|
55
|
+
|
56
|
+
:param wfn: The wavefunction to copy.
|
57
|
+
:param keymap: A keymap to apply to the wavefunction.
|
58
|
+
:param n_qubits: Number of qubits of the new wavefunction.
|
59
|
+
Must not be None if keymap is not None.
|
60
|
+
:param initial_state: Initial state to pass to the keymap.
|
61
|
+
:return: The copied wavefunction.
|
62
|
+
"""
|
63
|
+
if keymap is not None:
|
64
|
+
result = QubitWaveFunction(n_qubits, numbering=wfn._numbering, dense=wfn._dense)
|
65
|
+
# Change amplitudes to sympy objects
|
66
|
+
if wfn._dense and wfn._state.dtype == object:
|
67
|
+
result = sympy.Integer(1) * result
|
68
|
+
for index, coeff in wfn.raw_items():
|
69
|
+
key = initialize_bitstring(index, wfn._n_qubits, numbering_in=wfn._numbering, numbering_out=keymap.numbering)
|
70
|
+
key = keymap(key, initial_state)
|
71
|
+
result[key] += coeff
|
72
|
+
return result
|
52
73
|
else:
|
53
|
-
return
|
74
|
+
return deepcopy(wfn)
|
54
75
|
|
55
|
-
@
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
76
|
+
@classmethod
|
77
|
+
def from_array(cls, array: npt.NDArray[complex], numbering: BitNumbering = BitNumbering.MSB,
|
78
|
+
copy: bool = True) -> QubitWaveFunction:
|
79
|
+
"""
|
80
|
+
Create a dense wavefunction from a Numpy array.
|
60
81
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
def __init__(self, state: Dict[BitString, complex] = None, n_qubits=None):
|
74
|
-
if state is None:
|
75
|
-
self._state = dict()
|
76
|
-
elif isinstance(state, int):
|
77
|
-
self._state = self.from_int(i=state, n_qubits=n_qubits).state
|
78
|
-
elif isinstance(state, str):
|
79
|
-
self._state = self.from_string(string=state, n_qubits=n_qubits).state
|
80
|
-
elif isinstance(state, numpy.ndarray) or isinstance(state, list):
|
81
|
-
self._state = self.from_array(arr=state, n_qubits=n_qubits).state
|
82
|
-
elif hasattr(state, "state"):
|
83
|
-
self._state = state.state
|
84
|
-
else:
|
85
|
-
self._state = state
|
86
|
-
self._n_qubits = n_qubits
|
82
|
+
:param array: Array of amplitudes.
|
83
|
+
:param numbering: Whether the first qubit is the most or least significant.
|
84
|
+
:param copy: Whether to copy the array or use it directly.
|
85
|
+
If False, the array must not be modified after the constructor.
|
86
|
+
:return: The created wavefunction.
|
87
|
+
"""
|
88
|
+
if not log2(len(array)).is_integer():
|
89
|
+
raise ValueError(f"Array length must be a power of 2, received {len(array)}")
|
90
|
+
n_qubits = int(log2(len(array)))
|
91
|
+
result = QubitWaveFunction(n_qubits, numbering, dense=True, init_state=False)
|
92
|
+
result.set_state(array, copy)
|
93
|
+
return result
|
87
94
|
|
88
|
-
|
89
|
-
|
95
|
+
@classmethod
|
96
|
+
def from_basis_state(cls, n_qubits: int, basis_state: Union[int, BitString],
|
97
|
+
numbering: BitNumbering = BitNumbering.MSB) -> QubitWaveFunction:
|
98
|
+
"""
|
99
|
+
Create a sparse wavefunction that is a basis state.
|
90
100
|
|
91
|
-
|
92
|
-
|
101
|
+
:param n_qubits: Number of qubits.
|
102
|
+
:param basis_state: Index of the basis state.
|
103
|
+
:param numbering: Whether the first qubit is the most or least significant.
|
104
|
+
:return: The created wavefunction.
|
105
|
+
"""
|
106
|
+
if 2 ** n_qubits <= basis_state:
|
107
|
+
raise ValueError(f"Number of qubits {n_qubits} insufficient for basis state {basis_state}")
|
108
|
+
if isinstance(basis_state, BitString):
|
109
|
+
basis_state = reverse_int_bits(basis_state.integer,
|
110
|
+
basis_state.nbits) if numbering != basis_state.numbering else basis_state.integer
|
111
|
+
result = QubitWaveFunction(n_qubits, numbering)
|
112
|
+
result[basis_state] = 1.0
|
113
|
+
return result
|
93
114
|
|
94
|
-
|
95
|
-
|
115
|
+
@classmethod
|
116
|
+
def from_string(cls, string: str, numbering: BitNumbering = BitNumbering.MSB) -> QubitWaveFunction:
|
117
|
+
"""
|
118
|
+
Create a sparse wavefunction from a string.
|
119
|
+
|
120
|
+
:param string: String representation of the wavefunction.
|
121
|
+
:param numbering: Whether the first qubit is the most or least significant.
|
122
|
+
:return: The created wavefunction.
|
123
|
+
"""
|
124
|
+
try:
|
125
|
+
string = string.replace(" ", "")
|
126
|
+
string = string.replace("*", "")
|
127
|
+
terms = string.split(">")[:-1]
|
128
|
+
n_qubits = len(terms[0].split("|")[-1])
|
129
|
+
result = QubitWaveFunction(n_qubits, numbering)
|
130
|
+
for term in terms:
|
131
|
+
coeff, index = term.split("|")
|
132
|
+
coeff = complex(coeff) if coeff != "" else 1.0
|
133
|
+
index = int(index, 2)
|
134
|
+
result[index] = coeff
|
135
|
+
return result
|
136
|
+
except ValueError:
|
137
|
+
raise TequilaException(f"Failed to initialize QubitWaveFunction from string:\n\"{string}\"\n")
|
138
|
+
|
139
|
+
@classmethod
|
140
|
+
def convert_from(cls, n_qubits: int, val: Union[QubitWaveFunction, int, str, numpy.ndarray]):
|
141
|
+
"""
|
142
|
+
Convert a value to a QubitWaveFunction.
|
143
|
+
Accepts QubitWaveFunction, int, str, and numpy.ndarray.
|
96
144
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
return
|
145
|
+
:param n_qubits: Number of qubits.
|
146
|
+
:param val: Value to convert.
|
147
|
+
:return: The converted value.
|
148
|
+
"""
|
149
|
+
if isinstance(val, QubitWaveFunction):
|
150
|
+
return val
|
151
|
+
elif isinstance(val, int):
|
152
|
+
return cls.from_basis_state(n_qubits=n_qubits, basis_state=val)
|
153
|
+
elif isinstance(val, str):
|
154
|
+
return cls.from_string(val)
|
155
|
+
elif isinstance(val, numpy.ndarray):
|
156
|
+
return cls.from_array(val)
|
103
157
|
else:
|
104
|
-
|
158
|
+
raise TequilaException(f"Cannot initialize QubitWaveFunction from type {type(val)}")
|
159
|
+
|
160
|
+
@property
|
161
|
+
def n_qubits(self) -> int:
|
162
|
+
"""
|
163
|
+
Returns number of qubits in the wavefunction.
|
164
|
+
"""
|
165
|
+
return self._n_qubits
|
105
166
|
|
106
|
-
|
107
|
-
|
108
|
-
|
167
|
+
@property
|
168
|
+
def numbering(self) -> BitNumbering:
|
169
|
+
"""
|
170
|
+
Returns the bit numbering of the wavefunction.
|
171
|
+
"""
|
172
|
+
return self._numbering
|
173
|
+
|
174
|
+
@property
|
175
|
+
def dense(self) -> bool:
|
176
|
+
"""
|
177
|
+
Returns whether the wavefunction is dense.
|
178
|
+
"""
|
179
|
+
return self._dense
|
109
180
|
|
110
|
-
def
|
181
|
+
def to_array(self, out_numbering: BitNumbering = BitNumbering.MSB, copy: bool = True) -> npt.NDArray[complex]:
|
111
182
|
"""
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
183
|
+
Returns array of amplitudes.
|
184
|
+
|
185
|
+
:param out_numbering: Whether the first qubit is the most or least significant in the output array indices.
|
186
|
+
For dense wavefunctions, this operation is significantly cheaper when this is the same as the numbering
|
187
|
+
of the wavefunction.
|
188
|
+
:param copy: Whether to copy the array or use it directly for dense Wavefunctions.
|
189
|
+
If False, changes to the array or wavefunction will affect each other.
|
190
|
+
:return: Array of amplitudes.
|
120
191
|
"""
|
121
|
-
|
122
|
-
|
123
|
-
return self.state[ckey]
|
192
|
+
if self._dense and self._numbering == out_numbering:
|
193
|
+
return self._state.copy() if copy else self._state
|
124
194
|
else:
|
125
|
-
|
195
|
+
result = np.zeros(2 ** self._n_qubits, dtype=complex)
|
196
|
+
for k, v in self.raw_items():
|
197
|
+
if self._numbering != out_numbering:
|
198
|
+
k = reverse_int_bits(k, self._n_qubits)
|
199
|
+
result[k] = v
|
200
|
+
return result
|
201
|
+
|
202
|
+
def set_state(self, value: npt.NDArray[complex], copy: bool = True) -> None:
|
203
|
+
"""
|
204
|
+
Sets the state to an array.
|
205
|
+
After this call, the wavefunction will be dense.
|
126
206
|
|
207
|
+
:param value: Array of amplitudes. Length must be 2 ** n_qubits.
|
208
|
+
:param copy: Whether to copy the array or use it directly.
|
209
|
+
If False, changes to the array or wavefunction will affect each other.
|
210
|
+
"""
|
211
|
+
if len(value) != 2 ** self._n_qubits:
|
212
|
+
raise ValueError(f"Wavefunction of {self._n_qubits} qubits must have {2 ** self._n_qubits} amplitudes, "
|
213
|
+
f"received {len(value)}")
|
214
|
+
self._dense = True
|
215
|
+
if copy:
|
216
|
+
self._state = value.copy()
|
217
|
+
else:
|
218
|
+
self._state = value
|
219
|
+
|
220
|
+
def __getitem__(self, key: Union[int, BitString]) -> complex:
|
221
|
+
if isinstance(key, BitString):
|
222
|
+
key = reverse_int_bits(key.integer, key.nbits) if self._numbering != key.numbering else key.integer
|
223
|
+
return self._state[key] if self._dense else self._state.get(key, 0)
|
224
|
+
|
225
|
+
def __setitem__(self, key: Union[int, BitString], value: complex) -> None:
|
226
|
+
if isinstance(key, BitString):
|
227
|
+
key = reverse_int_bits(key.integer, key.nbits) if self._numbering != key.numbering else key.integer
|
228
|
+
self._state[key] = value
|
229
|
+
|
230
|
+
def __contains__(self, item: Union[int, BitString]) -> bool:
|
231
|
+
if isinstance(item, BitString):
|
232
|
+
item = reverse_int_bits(item.integer, item.nbits) if self._numbering != item.numbering else item.integer
|
233
|
+
return abs(self[item]) > 1e-6
|
234
|
+
|
235
|
+
def raw_items(self) -> Generator[tuple[int, complex]]:
|
236
|
+
"""Returns a generator of non-zero amplitudes with integer indices."""
|
237
|
+
return ((k, v) for k, v in (enumerate(self._state) if self._dense else self._state.items()))
|
238
|
+
|
239
|
+
def items(self) -> Generator[tuple[BitString, complex]]:
|
240
|
+
"""Returns a generator of non-zero amplitudes with BitString indices."""
|
241
|
+
return ((initialize_bitstring(k, self._n_qubits, self._numbering), v)
|
242
|
+
for k, v in self.raw_items()
|
243
|
+
if isinstance(v, sympy.Basic) or abs(v) > 1e-6)
|
244
|
+
|
245
|
+
def keys(self) -> Generator[BitString]:
|
246
|
+
"""Returns a generator of BitString indices of non-zero amplitudes."""
|
247
|
+
return (k for k, v in self.items())
|
248
|
+
|
249
|
+
def values(self) -> Generator[complex]:
|
250
|
+
"""Returns a generator of non-zero amplitudes."""
|
251
|
+
return (v for k, v in self.items())
|
252
|
+
|
253
|
+
def __eq__(self, other) -> bool:
|
254
|
+
if not isinstance(other, QubitWaveFunction):
|
255
|
+
return False
|
256
|
+
|
257
|
+
raise TequilaException("Wavefunction equality is not well-defined. Consider using isclose.")
|
258
|
+
|
259
|
+
def isclose(self: QubitWaveFunction,
|
260
|
+
other: QubitWaveFunction,
|
261
|
+
rtol: float = 1e-5,
|
262
|
+
atol: float = 1e-8) -> bool:
|
263
|
+
"""
|
264
|
+
Check if two wavefunctions are close, up to a global phase.
|
127
265
|
|
266
|
+
:param other: The other wavefunction.
|
267
|
+
:param rtol: Relative tolerance.
|
268
|
+
:param atol: Absolute tolerance.
|
269
|
+
:return: Whether the wavefunctions are close.
|
270
|
+
"""
|
271
|
+
inner = self.inner(other)
|
272
|
+
self_norm = self.norm()
|
273
|
+
other_norm = other.norm()
|
274
|
+
cosine_similarity = inner / (self_norm * other_norm)
|
128
275
|
|
129
|
-
|
130
|
-
|
131
|
-
return self
|
276
|
+
return (np.isclose(abs(cosine_similarity), 1.0, rtol, atol)
|
277
|
+
and np.isclose(self_norm, other_norm, rtol, atol))
|
132
278
|
|
133
|
-
def
|
134
|
-
|
279
|
+
def __add__(self, other: QubitWaveFunction) -> QubitWaveFunction:
|
280
|
+
if self._dense and other._dense and self._numbering == other._numbering:
|
281
|
+
return QubitWaveFunction.from_array(self._state + other._state, self.numbering, copy=False)
|
282
|
+
else:
|
283
|
+
result = QubitWaveFunction.from_wavefunction(self)
|
284
|
+
result += other
|
285
|
+
return result
|
135
286
|
|
136
|
-
def
|
137
|
-
|
287
|
+
def __iadd__(self, other: QubitWaveFunction) -> QubitWaveFunction:
|
288
|
+
if self._dense and other._dense and self._numbering == other._numbering:
|
289
|
+
self._state += other._state
|
290
|
+
else:
|
291
|
+
for k, v in other.raw_items():
|
292
|
+
if self._numbering != other._numbering:
|
293
|
+
k = reverse_int_bits(k, self._n_qubits)
|
294
|
+
self[k] += v
|
295
|
+
return self
|
138
296
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
maxbit = initialize_bitstring(integer=maxkey, numbering_in=numbering, numbering_out=cls.numbering).nbits
|
147
|
-
for ii, v in enumerate(arr):
|
148
|
-
if abs(v) > threshold:
|
149
|
-
i = initialize_bitstring(integer=ii, nbits=maxbit, numbering_in=numbering, numbering_out=cls.numbering)
|
150
|
-
key = i if keymap is None else keymap(i)
|
151
|
-
state[key] = v
|
152
|
-
result = QubitWaveFunction(state, n_qubits=n_qubits)
|
297
|
+
def __sub__(self, other: QubitWaveFunction) -> QubitWaveFunction:
|
298
|
+
if self._dense and other._dense and self._numbering == other._numbering:
|
299
|
+
return QubitWaveFunction.from_array(self._state - other._state, self.numbering, copy=False)
|
300
|
+
else:
|
301
|
+
result = QubitWaveFunction.from_wavefunction(self)
|
302
|
+
result -= other
|
303
|
+
return result
|
153
304
|
|
154
|
-
|
305
|
+
def __isub__(self, other: QubitWaveFunction) -> QubitWaveFunction:
|
306
|
+
if self._dense and other._dense and self._numbering == other._numbering:
|
307
|
+
self._state -= other._state
|
308
|
+
else:
|
309
|
+
for k, v in other.raw_items():
|
310
|
+
if self._numbering != other._numbering:
|
311
|
+
k = reverse_int_bits(k, self._n_qubits)
|
312
|
+
self[k] -= v
|
313
|
+
return self
|
155
314
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
return QubitWaveFunction(state={i: coeff}, n_qubits=n_qubits)
|
315
|
+
def __rmul__(self, other: complex) -> QubitWaveFunction:
|
316
|
+
if self._dense:
|
317
|
+
return QubitWaveFunction.from_array(other * self._state, self.numbering, copy=False)
|
160
318
|
else:
|
161
|
-
|
319
|
+
result = QubitWaveFunction.from_wavefunction(self)
|
320
|
+
result *= other
|
321
|
+
return result
|
162
322
|
|
163
|
-
|
164
|
-
|
323
|
+
def __imul__(self, other: complex) -> QubitWaveFunction:
|
324
|
+
if self._dense:
|
325
|
+
self._state *= other
|
326
|
+
else:
|
327
|
+
for k, v in self.raw_items():
|
328
|
+
self[k] = other * v
|
329
|
+
return self
|
330
|
+
|
331
|
+
def inner(self, other: QubitWaveFunction) -> complex:
|
332
|
+
"""Returns the inner product with another wavefunction."""
|
333
|
+
if self._dense and other._dense and self._numbering == other._numbering:
|
334
|
+
return np.inner(self._state.conjugate(), other._state)
|
335
|
+
else:
|
336
|
+
result = 0
|
337
|
+
for k, v in self.raw_items():
|
338
|
+
if self._numbering != other._numbering:
|
339
|
+
k = reverse_int_bits(k, self._n_qubits)
|
340
|
+
result += v.conjugate() * other[k]
|
341
|
+
if isinstance(result, sympy.Basic):
|
342
|
+
result = complex(result)
|
343
|
+
return result
|
344
|
+
|
345
|
+
def norm(self) -> float:
|
346
|
+
"""Returns the norm of the wavefunction."""
|
347
|
+
return np.sqrt(self.inner(self))
|
348
|
+
|
349
|
+
def normalize(self, inplace: bool = False) -> QubitWaveFunction:
|
165
350
|
"""
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
:
|
170
|
-
:param string:
|
171
|
-
:return:
|
351
|
+
Normalizes the wavefunction.
|
352
|
+
|
353
|
+
:param inplace: Whether to normalize the wavefunction in place or return a new one.
|
354
|
+
:return: The normalized wavefunction.
|
172
355
|
"""
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
terms = (string + "terminate").split('>')
|
180
|
-
for term in terms:
|
181
|
-
if term == 'terminate':
|
182
|
-
break
|
183
|
-
tmp = term.split("|")
|
184
|
-
coeff = tmp[0]
|
185
|
-
if coeff == '':
|
186
|
-
coeff = 1.0
|
187
|
-
else:
|
188
|
-
coeff = complex(coeff)
|
189
|
-
basis_state = BitString.from_binary(binary=tmp[1])
|
356
|
+
norm = self.norm()
|
357
|
+
if inplace:
|
358
|
+
self *= 1.0 / norm
|
359
|
+
return self
|
360
|
+
else:
|
361
|
+
return (1.0 / norm) * QubitWaveFunction.from_wavefunction(self)
|
190
362
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
except:
|
198
|
-
raise TequilaException("Failed to initialize QubitWaveFunction from string:" + string)
|
199
|
-
return QubitWaveFunction(state=state, n_qubits=n_qubits)
|
363
|
+
# It would be nice to call this __len__, however for some reason this causes infinite loops
|
364
|
+
# when multiplying wave functions with some types of numbers from the right sight, likely
|
365
|
+
# because the __mul__ implementation of the number tries to perform some sort of array
|
366
|
+
# operation.
|
367
|
+
def length(self):
|
368
|
+
return sum(1 for (k, v) in self.raw_items() if abs(v) > 1e-6)
|
200
369
|
|
201
370
|
def __repr__(self):
|
202
371
|
result = str()
|
203
|
-
for
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
+ " product equality, wf1.isclose(wf2).")
|
210
|
-
|
211
|
-
def isclose(self : 'QubitWaveFunction',
|
212
|
-
other : 'QubitWaveFunction',
|
213
|
-
rtol : float=1e-5,
|
214
|
-
atol : float=1e-8) -> bool:
|
215
|
-
"""Return whether this wavefunction is similar to the target wavefunction."""
|
216
|
-
over1 = complex(self.inner(other))
|
217
|
-
over2 = numpy.sqrt(complex(self.inner(self) * other.inner(other)))
|
218
|
-
# Explicit casts to complex() is required if self or other are sympy
|
219
|
-
# wavefunction with sympy-typed amplitudes
|
220
|
-
|
221
|
-
# Check if the two numbers are equal.
|
222
|
-
return numpy.isclose(over1, over2, rtol=rtol, atol=atol)
|
223
|
-
|
224
|
-
def __add__(self, other):
|
225
|
-
result = QubitWaveFunction(state=copy.deepcopy(self._state))
|
226
|
-
for k, v in other.items():
|
227
|
-
if k in result._state:
|
228
|
-
result._state[k] += v
|
229
|
-
else:
|
230
|
-
result._state[k] = v
|
231
|
-
return result
|
232
|
-
|
233
|
-
def __sub__(self, other):
|
234
|
-
return self + -1.0 * other
|
235
|
-
|
236
|
-
def __iadd__(self, other):
|
237
|
-
for k, v in other.items():
|
238
|
-
if k in self._state:
|
239
|
-
self._state[k] += v
|
372
|
+
for index, coeff in self.items():
|
373
|
+
index = index.integer
|
374
|
+
if self.numbering == BitNumbering.LSB:
|
375
|
+
index = reverse_int_bits(index, self._n_qubits)
|
376
|
+
if np.isclose(coeff.imag, 0.0):
|
377
|
+
result += f"{coeff.real:+2.4f} |{index:0{self._n_qubits}b}> "
|
240
378
|
else:
|
241
|
-
self.
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
result = QubitWaveFunction(state=copy.deepcopy(self._state))
|
246
|
-
for k, v in result._state.items():
|
247
|
-
result._state[k] *= other
|
248
|
-
return result
|
249
|
-
|
250
|
-
def inner(self, other):
|
251
|
-
# currently very slow and not optimized in any way
|
252
|
-
result = 0.0
|
253
|
-
for k, v in self.items():
|
254
|
-
if k in other._state:
|
255
|
-
result += v.conjugate() * other._state[k]
|
379
|
+
result += f"({coeff.real:+2.4f} + {coeff.imag:+2.4f}i) |{index:0{self._n_qubits}b}> "
|
380
|
+
# If the wavefunction contains no states
|
381
|
+
if not result:
|
382
|
+
result = "empty wavefunction"
|
256
383
|
return result
|
257
384
|
|
258
|
-
def
|
259
|
-
"""
|
260
|
-
NOT AN Inplace operation
|
261
|
-
:return: Normalizes the wavefunction/countrate
|
262
|
-
"""
|
263
|
-
norm2 = self.inner(other=self)
|
264
|
-
normalized = 1.0 / numpy.sqrt(norm2) * self
|
265
|
-
return normalized
|
266
|
-
|
267
|
-
def compute_expectationvalue(self, operator: 'QubitHamiltonian') -> numbers.Real:
|
385
|
+
def compute_expectationvalue(self, operator: QubitHamiltonian) -> numbers.Real:
|
268
386
|
tmp = self.apply_qubitoperator(operator=operator)
|
269
387
|
E = self.inner(other=tmp)
|
270
|
-
if hasattr(E, "imag") and
|
388
|
+
if hasattr(E, "imag") and np.isclose(E.imag, 0.0, atol=1.e-6):
|
271
389
|
return float(E.real)
|
272
390
|
else:
|
273
391
|
return E
|
274
392
|
|
275
|
-
def apply_qubitoperator(self, operator:
|
393
|
+
def apply_qubitoperator(self, operator: QubitHamiltonian) -> QubitWaveFunction:
|
276
394
|
"""
|
277
395
|
Inefficient function which computes the action of a QubitHamiltonian on this wfn
|
278
396
|
:param operator: QubitOperator
|
279
397
|
:return: resulting Qubitwavefunction
|
280
398
|
"""
|
281
|
-
result = QubitWaveFunction()
|
399
|
+
result = QubitWaveFunction(self.n_qubits, self._numbering)
|
282
400
|
for ps in operator.paulistrings:
|
283
401
|
result += self.apply_paulistring(paulistring=ps)
|
284
|
-
result = result.simplify()
|
285
402
|
return result
|
286
403
|
|
287
|
-
def apply_paulistring(self, paulistring:
|
404
|
+
def apply_paulistring(self, paulistring: PauliString) -> QubitWaveFunction:
|
288
405
|
"""
|
289
406
|
Inefficient function which computes action of a single paulistring
|
290
407
|
:param paulistring: PauliString
|
291
408
|
:return: Expectation Value
|
292
409
|
"""
|
293
|
-
result = QubitWaveFunction()
|
410
|
+
result = QubitWaveFunction(self._n_qubits, self._numbering)
|
294
411
|
for k, v in self.items():
|
295
412
|
arr = k.array
|
296
413
|
c = v
|
@@ -306,17 +423,3 @@ class QubitWaveFunction:
|
|
306
423
|
raise TequilaException("unknown pauli: " + str(p))
|
307
424
|
result[BitString.from_array(array=arr)] = c
|
308
425
|
return paulistring.coeff * result
|
309
|
-
|
310
|
-
def to_array(self):
|
311
|
-
result = numpy.zeros(shape=2 ** self.n_qubits, dtype=complex)
|
312
|
-
for k, v in self.items():
|
313
|
-
result[int(k)] = v
|
314
|
-
return result
|
315
|
-
|
316
|
-
def simplify(self, threshold = 1.e-8):
|
317
|
-
state = {}
|
318
|
-
for k, v in self.state.items():
|
319
|
-
if not numpy.isclose(v, 0.0, atol=threshold):
|
320
|
-
state[k] = v
|
321
|
-
return QubitWaveFunction(state=state)
|
322
|
-
|