tequila-basic 1.9.6__py3-none-any.whl → 1.9.8__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 +2 -5
- tequila/hamiltonian/paulis.py +4 -3
- tequila/quantumchemistry/encodings.py +2 -2
- tequila/quantumchemistry/orbital_optimizer.py +9 -2
- tequila/quantumchemistry/qc_base.py +32 -19
- tequila/simulators/simulator_api.py +25 -9
- tequila/simulators/simulator_base.py +46 -15
- 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 +86 -49
- tequila/simulators/simulator_qiskit_gpu.py +9 -0
- tequila/simulators/simulator_qlm.py +3 -3
- tequila/simulators/simulator_qulacs.py +24 -19
- tequila/simulators/simulator_qulacs_gpu.py +6 -4
- tequila/simulators/simulator_symbolic.py +15 -23
- tequila/utils/bitstrings.py +25 -11
- tequila/version.py +1 -1
- tequila/wavefunction/qubit_wavefunction.py +338 -244
- {tequila_basic-1.9.6.dist-info → tequila_basic-1.9.8.dist-info}/METADATA +11 -13
- {tequila_basic-1.9.6.dist-info → tequila_basic-1.9.8.dist-info}/RECORD +25 -24
- {tequila_basic-1.9.6.dist-info → tequila_basic-1.9.8.dist-info}/WHEEL +1 -1
- {tequila_basic-1.9.6.dist-info → tequila_basic-1.9.8.dist-info}/LICENSE +0 -0
- {tequila_basic-1.9.6.dist-info → tequila_basic-1.9.8.dist-info}/top_level.txt +0 -0
@@ -1,302 +1,410 @@
|
|
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
|
-
|
60
|
-
|
61
|
-
@property
|
62
|
-
def state(self):
|
63
|
-
if self._state is None:
|
64
|
-
return dict()
|
65
|
-
else:
|
66
|
-
return self._state
|
67
|
-
|
68
|
-
@state.setter
|
69
|
-
def state(self, other: Dict[BitString, complex]):
|
70
|
-
assert (isinstance(other, dict))
|
71
|
-
self._state = other
|
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
|
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.
|
87
81
|
|
88
|
-
|
89
|
-
|
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
|
90
94
|
|
91
|
-
|
92
|
-
|
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.
|
93
100
|
|
94
|
-
|
95
|
-
|
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
|
96
114
|
|
97
|
-
@
|
98
|
-
def
|
99
|
-
|
100
|
-
|
101
|
-
elif isinstance(key, str):
|
102
|
-
return BitString.from_binary(binary=key, nbits=n_qubits)
|
103
|
-
else:
|
104
|
-
return key
|
115
|
+
@classmethod
|
116
|
+
def from_string(cls, string: str, numbering: BitNumbering = BitNumbering.MSB) -> QubitWaveFunction:
|
117
|
+
"""
|
118
|
+
Create a sparse wavefunction from a string.
|
105
119
|
|
106
|
-
|
107
|
-
|
108
|
-
return
|
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")
|
109
138
|
|
110
|
-
|
139
|
+
@classmethod
|
140
|
+
def convert_from(cls, n_qubits: int, val: Union[QubitWaveFunction, int, str, numpy.ndarray]):
|
111
141
|
"""
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
-------
|
119
|
-
Return the amplitude or measurement occurence of a bitstring
|
142
|
+
Convert a value to a QubitWaveFunction.
|
143
|
+
Accepts QubitWaveFunction, int, str, and numpy.ndarray.
|
144
|
+
|
145
|
+
:param n_qubits: Number of qubits.
|
146
|
+
:param val: Value to convert.
|
147
|
+
:return: The converted value.
|
120
148
|
"""
|
121
|
-
|
122
|
-
|
123
|
-
|
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)
|
124
157
|
else:
|
125
|
-
|
158
|
+
raise TequilaException(f"Cannot initialize QubitWaveFunction from type {type(val)}")
|
126
159
|
|
160
|
+
@property
|
161
|
+
def n_qubits(self) -> int:
|
162
|
+
"""
|
163
|
+
Returns number of qubits in the wavefunction.
|
164
|
+
"""
|
165
|
+
return self._n_qubits
|
127
166
|
|
167
|
+
@property
|
168
|
+
def numbering(self) -> BitNumbering:
|
169
|
+
"""
|
170
|
+
Returns the bit numbering of the wavefunction.
|
171
|
+
"""
|
172
|
+
return self._numbering
|
128
173
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
return self.
|
135
|
-
|
136
|
-
def __len__(self):
|
137
|
-
return len(self.state)
|
138
|
-
|
139
|
-
@classmethod
|
140
|
-
def from_array(cls, arr: numpy.ndarray, keymap=None, threshold: float = 1.e-6,
|
141
|
-
numbering: BitNumbering = BitNumbering.MSB, n_qubits: int = None):
|
142
|
-
arr = numpy.asarray(arr)
|
143
|
-
assert (len(arr.shape) == 1)
|
144
|
-
state = dict()
|
145
|
-
maxkey = len(arr) - 1
|
146
|
-
maxbit = initialize_bitstring(integer=maxkey, numbering_in=numbering, numbering_out=cls.numbering).nbits
|
147
|
-
for ii, v in enumerate(arr):
|
148
|
-
i = initialize_bitstring(integer=ii, nbits=maxbit, numbering_in=numbering, numbering_out=cls.numbering)
|
149
|
-
if not numpy.isclose(abs(v), 0.0, atol=threshold):
|
150
|
-
key = i if keymap is None else keymap(i)
|
151
|
-
state[key] = v
|
152
|
-
result = QubitWaveFunction(state, n_qubits=n_qubits)
|
153
|
-
|
154
|
-
if cls.numbering != numbering:
|
155
|
-
if cls.numbering == BitNumbering.MSB:
|
156
|
-
result.apply_keymap(keymap=KeyMapLSB2MSB())
|
157
|
-
else:
|
158
|
-
result.apply_keymap(keymap=KeyMapMSB2LSB())
|
159
|
-
|
160
|
-
return result
|
174
|
+
@property
|
175
|
+
def dense(self) -> bool:
|
176
|
+
"""
|
177
|
+
Returns whether the wavefunction is dense.
|
178
|
+
"""
|
179
|
+
return self._dense
|
161
180
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
181
|
+
def to_array(self, out_numbering: BitNumbering = BitNumbering.MSB, copy: bool = True) -> npt.NDArray[complex]:
|
182
|
+
"""
|
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.
|
191
|
+
"""
|
192
|
+
if self._dense and self._numbering == out_numbering:
|
193
|
+
return self._state.copy() if copy else self._state
|
166
194
|
else:
|
167
|
-
|
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.
|
168
206
|
|
169
|
-
|
170
|
-
|
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.
|
171
210
|
"""
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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:
|
178
263
|
"""
|
179
|
-
|
180
|
-
state = dict()
|
181
|
-
string = string.replace(" ", "")
|
182
|
-
string = string.replace("*", "")
|
183
|
-
string = string.replace("+-", "-")
|
184
|
-
string = string.replace("-+", "-")
|
185
|
-
terms = (string + "terminate").split('>')
|
186
|
-
for term in terms:
|
187
|
-
if term == 'terminate':
|
188
|
-
break
|
189
|
-
tmp = term.split("|")
|
190
|
-
coeff = tmp[0]
|
191
|
-
if coeff == '':
|
192
|
-
coeff = 1.0
|
193
|
-
else:
|
194
|
-
coeff = complex(coeff)
|
195
|
-
basis_state = BitString.from_binary(binary=tmp[1])
|
264
|
+
Check if two wavefunctions are close, up to a global phase.
|
196
265
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
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)
|
206
275
|
|
207
|
-
|
208
|
-
|
209
|
-
for k, v in self.items():
|
210
|
-
result += number_to_string(number=v) + "|" + str(k.binary) + "> "
|
211
|
-
return result
|
276
|
+
return (np.isclose(abs(cosine_similarity), 1.0, rtol, atol)
|
277
|
+
and np.isclose(self_norm, other_norm, rtol, atol))
|
212
278
|
|
213
|
-
def
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
atol : float=1e-8) -> bool:
|
221
|
-
"""Return whether this wavefunction is similar to the target wavefunction."""
|
222
|
-
over1 = complex(self.inner(other))
|
223
|
-
over2 = numpy.sqrt(complex(self.inner(self) * other.inner(other)))
|
224
|
-
# Explicit casts to complex() is required if self or other are sympy
|
225
|
-
# wavefunction with sympy-typed amplitudes
|
226
|
-
|
227
|
-
# Check if the two numbers are equal.
|
228
|
-
return numpy.isclose(over1, over2, rtol=rtol, atol=atol)
|
229
|
-
|
230
|
-
def __add__(self, other):
|
231
|
-
result = QubitWaveFunction(state=copy.deepcopy(self._state))
|
232
|
-
for k, v in other.items():
|
233
|
-
if k in result._state:
|
234
|
-
result._state[k] += v
|
235
|
-
else:
|
236
|
-
result._state[k] = v
|
237
|
-
return result
|
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
|
238
286
|
|
239
|
-
def
|
240
|
-
|
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
|
296
|
+
|
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
|
241
304
|
|
242
|
-
def
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
self.
|
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
|
248
313
|
return self
|
249
314
|
|
250
|
-
def __rmul__(self, other):
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
315
|
+
def __rmul__(self, other: complex) -> QubitWaveFunction:
|
316
|
+
if self._dense:
|
317
|
+
return QubitWaveFunction.from_array(other * self._state, self.numbering, copy=False)
|
318
|
+
else:
|
319
|
+
result = QubitWaveFunction.from_wavefunction(self)
|
320
|
+
result *= other
|
321
|
+
return result
|
255
322
|
|
256
|
-
def
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
return
|
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
|
263
330
|
|
264
|
-
def
|
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:
|
265
350
|
"""
|
266
|
-
|
267
|
-
|
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.
|
268
355
|
"""
|
269
|
-
|
270
|
-
|
271
|
-
|
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)
|
362
|
+
|
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 _ in self.raw_items())
|
369
|
+
|
370
|
+
def __repr__(self):
|
371
|
+
result = str()
|
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
|
+
result += f"{coeff} |{index:0{self._n_qubits}b}> "
|
377
|
+
# If the wavefunction contains no states
|
378
|
+
if not result:
|
379
|
+
result = "empty wavefunction"
|
380
|
+
return result
|
272
381
|
|
273
|
-
def compute_expectationvalue(self, operator:
|
382
|
+
def compute_expectationvalue(self, operator: QubitHamiltonian) -> numbers.Real:
|
274
383
|
tmp = self.apply_qubitoperator(operator=operator)
|
275
384
|
E = self.inner(other=tmp)
|
276
|
-
if hasattr(E, "imag") and
|
385
|
+
if hasattr(E, "imag") and np.isclose(E.imag, 0.0, atol=1.e-6):
|
277
386
|
return float(E.real)
|
278
387
|
else:
|
279
388
|
return E
|
280
389
|
|
281
|
-
def apply_qubitoperator(self, operator:
|
390
|
+
def apply_qubitoperator(self, operator: QubitHamiltonian) -> QubitWaveFunction:
|
282
391
|
"""
|
283
392
|
Inefficient function which computes the action of a QubitHamiltonian on this wfn
|
284
393
|
:param operator: QubitOperator
|
285
394
|
:return: resulting Qubitwavefunction
|
286
395
|
"""
|
287
|
-
result = QubitWaveFunction()
|
396
|
+
result = QubitWaveFunction(self.n_qubits, self._numbering)
|
288
397
|
for ps in operator.paulistrings:
|
289
398
|
result += self.apply_paulistring(paulistring=ps)
|
290
|
-
result = result.simplify()
|
291
399
|
return result
|
292
400
|
|
293
|
-
def apply_paulistring(self, paulistring:
|
401
|
+
def apply_paulistring(self, paulistring: PauliString) -> QubitWaveFunction:
|
294
402
|
"""
|
295
403
|
Inefficient function which computes action of a single paulistring
|
296
404
|
:param paulistring: PauliString
|
297
405
|
:return: Expectation Value
|
298
406
|
"""
|
299
|
-
result = QubitWaveFunction()
|
407
|
+
result = QubitWaveFunction(self._n_qubits, self._numbering)
|
300
408
|
for k, v in self.items():
|
301
409
|
arr = k.array
|
302
410
|
c = v
|
@@ -312,17 +420,3 @@ class QubitWaveFunction:
|
|
312
420
|
raise TequilaException("unknown pauli: " + str(p))
|
313
421
|
result[BitString.from_array(array=arr)] = c
|
314
422
|
return paulistring.coeff * result
|
315
|
-
|
316
|
-
def to_array(self):
|
317
|
-
result = numpy.zeros(shape=2 ** self.n_qubits, dtype=complex)
|
318
|
-
for k, v in self.items():
|
319
|
-
result[int(k)] = v
|
320
|
-
return result
|
321
|
-
|
322
|
-
def simplify(self, threshold = 1.e-8):
|
323
|
-
state = {}
|
324
|
-
for k, v in self.state.items():
|
325
|
-
if not numpy.isclose(v, 0.0, atol=threshold):
|
326
|
-
state[k] = v
|
327
|
-
return QubitWaveFunction(state=state)
|
328
|
-
|