tequila-basic 1.9.7__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.
@@ -1,296 +1,410 @@
1
1
  from __future__ import annotations
2
- import copy
2
+
3
3
  import typing
4
- from typing import Dict, Union
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
- from tequila.utils.bitstrings import BitNumbering, BitString, initialize_bitstring
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
- # from __future__ import annotations # can use that in python 3.7+ to get rid of string type hints
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
- # don't need those structures, just for convenient type hinting
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
- Store Wavefunction as dictionary of comp. basis state and complex numbers
23
- Use the same structure for Measurments results with int instead of complex numbers (counts)
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
- def apply_keymap(self, keymap, initial_state: BitString = None):
29
- self.n_qubits = keymap.n_qubits
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
- @property
42
- def n_qubits(self) -> int:
43
- if self._n_qubits is None:
44
- return self.min_qubits()
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
- return max(self._n_qubits, self.min_qubits())
48
+ self._state = None
47
49
 
48
- def min_qubits(self) -> int:
49
- if len(self.state) > 0:
50
- maxk = max(self.state.keys())
51
- return maxk.nbits
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 0
74
+ return deepcopy(wfn)
54
75
 
55
- @n_qubits.setter
56
- def n_qubits(self, n_qubits):
57
- if n_qubits is not None:
58
- self._n_qubits = max(n_qubits, self.min_qubits())
59
- return self
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
- def items(self):
89
- return self.state.items()
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
- def keys(self):
92
- return self.state.keys()
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
- def values(self):
95
- return self.state.values()
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
- @staticmethod
98
- def convert_bitstring(key: Union[BitString, numbers.Integral], n_qubits):
99
- if isinstance(key, numbers.Integral):
100
- return BitString.from_int(integer=key, nbits=n_qubits)
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
- def __getitem__(self, item: BitString):
107
- key = self.convert_bitstring(item, self.n_qubits)
108
- return self.state[key]
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
- def __call__(self, key, *args, **kwargs) -> numbers.Number:
139
+ @classmethod
140
+ def convert_from(cls, n_qubits: int, val: Union[QubitWaveFunction, int, str, numpy.ndarray]):
111
141
  """
112
- Like getitem but returns zero if key is not there
113
-
114
- Parameters
115
- ----------
116
- key: bitstring (or int or str)
117
- Returns
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
- ckey = self.convert_bitstring(key, self.n_qubits)
122
- if ckey in self.state:
123
- return self.state[ckey]
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
- return 0.0
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
- def __setitem__(self, key: BitString, value: numbers.Number):
130
- self._state[self.convert_bitstring(key, self.n_qubits)] = value
131
- return self
132
-
133
- def __contains__(self, item: BitString):
134
- return self.convert_bitstring(item, self.n_qubits) in self.keys()
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
- 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)
153
-
154
- return result
174
+ @property
175
+ def dense(self) -> bool:
176
+ """
177
+ Returns whether the wavefunction is dense.
178
+ """
179
+ return self._dense
155
180
 
156
- @classmethod
157
- def from_int(cls, i: int, coeff=1, n_qubits: int = None):
158
- if isinstance(i, BitString):
159
- return QubitWaveFunction(state={i: coeff}, n_qubits=n_qubits)
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
160
194
  else:
161
- return QubitWaveFunction(state={BitString.from_int(integer=i, nbits=n_qubits): coeff}, n_qubits=n_qubits)
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.
162
206
 
163
- @classmethod
164
- def from_string(cls, string: str, n_qubits: int = None):
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.
165
210
  """
166
- Complex values like (x+iy)|...> will currently not work, you need to type Real and imaginary separately
167
- Or improve this constructor :-)
168
- e.g instead of (0.5+1.0j)|0101> do 0.5|0101> + 1.0j|0101>
169
- :param paths:
170
- :param string:
171
- :return:
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:
172
263
  """
173
- try:
174
- state = dict()
175
- string = string.replace(" ", "")
176
- string = string.replace("*", "")
177
- string = string.replace("+-", "-")
178
- string = string.replace("-+", "-")
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])
264
+ Check if two wavefunctions are close, up to a global phase.
190
265
 
191
- state[basis_state] = coeff
192
- except ValueError:
193
- raise TequilaException("Failed to initialize QubitWaveFunction from string:" + string + "\n"
194
- "did you try complex values?\n"
195
- "currently you need to type real and imaginary parts separately\n"
196
- "e.g. instead of (0.5+1.0j)|0101> do 0.5|0101> + 1.0j|0101>")
197
- except:
198
- raise TequilaException("Failed to initialize QubitWaveFunction from string:" + string)
199
- return QubitWaveFunction(state=state, n_qubits=n_qubits)
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)
200
275
 
201
- def __repr__(self):
202
- result = str()
203
- for k, v in self.items():
204
- result += number_to_string(number=v) + "|" + str(k.binary) + "> "
205
- return result
276
+ return (np.isclose(abs(cosine_similarity), 1.0, rtol, atol)
277
+ and np.isclose(self_norm, other_norm, rtol, atol))
206
278
 
207
- def __eq__(self, other):
208
- raise TequilaException("Wavefunction equality is not well-defined. Consider using inner"
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
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
232
286
 
233
- def __sub__(self, other):
234
- return self + -1.0 * other
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
235
304
 
236
- def __iadd__(self, other):
237
- for k, v in other.items():
238
- if k in self._state:
239
- self._state[k] += v
240
- else:
241
- self._state[k] = v
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
242
313
  return self
243
314
 
244
- def __rmul__(self, other):
245
- result = QubitWaveFunction(state=copy.deepcopy(self._state))
246
- for k, v in result._state.items():
247
- result._state[k] *= other
248
- return result
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
249
322
 
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]
256
- return result
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
257
330
 
258
- def normalize(self):
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:
259
350
  """
260
- NOT AN Inplace operation
261
- :return: Normalizes the wavefunction/countrate
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.
262
355
  """
263
- norm2 = self.inner(other=self)
264
- normalized = 1.0 / numpy.sqrt(norm2) * self
265
- return normalized
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
266
381
 
267
- def compute_expectationvalue(self, operator: 'QubitHamiltonian') -> numbers.Real:
382
+ def compute_expectationvalue(self, operator: QubitHamiltonian) -> numbers.Real:
268
383
  tmp = self.apply_qubitoperator(operator=operator)
269
384
  E = self.inner(other=tmp)
270
- if hasattr(E, "imag") and numpy.isclose(E.imag, 0.0, atol=1.e-6):
385
+ if hasattr(E, "imag") and np.isclose(E.imag, 0.0, atol=1.e-6):
271
386
  return float(E.real)
272
387
  else:
273
388
  return E
274
389
 
275
- def apply_qubitoperator(self, operator: 'QubitHamiltonian'):
390
+ def apply_qubitoperator(self, operator: QubitHamiltonian) -> QubitWaveFunction:
276
391
  """
277
392
  Inefficient function which computes the action of a QubitHamiltonian on this wfn
278
393
  :param operator: QubitOperator
279
394
  :return: resulting Qubitwavefunction
280
395
  """
281
- result = QubitWaveFunction()
396
+ result = QubitWaveFunction(self.n_qubits, self._numbering)
282
397
  for ps in operator.paulistrings:
283
398
  result += self.apply_paulistring(paulistring=ps)
284
- result = result.simplify()
285
399
  return result
286
400
 
287
- def apply_paulistring(self, paulistring: 'PauliString'):
401
+ def apply_paulistring(self, paulistring: PauliString) -> QubitWaveFunction:
288
402
  """
289
403
  Inefficient function which computes action of a single paulistring
290
404
  :param paulistring: PauliString
291
405
  :return: Expectation Value
292
406
  """
293
- result = QubitWaveFunction()
407
+ result = QubitWaveFunction(self._n_qubits, self._numbering)
294
408
  for k, v in self.items():
295
409
  arr = k.array
296
410
  c = v
@@ -306,17 +420,3 @@ class QubitWaveFunction:
306
420
  raise TequilaException("unknown pauli: " + str(p))
307
421
  result[BitString.from_array(array=arr)] = c
308
422
  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
-