module_sdstate 0.1.0__tar.gz

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.
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024, Linjun Wang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.1
2
+ Name: module_sdstate
3
+ Version: 0.1.0
4
+ Summary: Implementation of Slater Determinant states as dictionaries of states and coefficient pairs. Each state is represented with an integar treated as binary, allowing applying of Excitation operators on the state efficiently. Memory-efficient implementation of Lanczos iteration for estimating Hamiltonian spectrum range.
5
+ License: MIT
6
+ Author: Linjun Wang
7
+ Requires-Python: >=3.9,<4.0
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Dist: numpy (>=1.26.4,<2.0.0)
15
+ Requires-Dist: openfermion (>=1.6.1,<2.0.0)
16
+ Description-Content-Type: text/markdown
17
+
18
+ # sdstate
19
+
20
+ Implementation of Slater Determinant states as dictionaries of states and coefficient pairs. Each state is represented with an integar treated as binary, allowing applying of Excitation operators on the state efficiently. Memory-efficient implementation of Lanczos iteration for estimating Hamiltonian spectrum range.
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ $ pip install module_sdstate
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ - Memory efficient implementation of Slater-Derterminant states.
31
+ - Compute expectation value of Hamiltonian on Slater-Determinant states states.
32
+ - Compute Hartree-Fock energy estimation of a Hamiltonian.
33
+ - Efficient estimation of ground state energy and spectrum range of Hamiltonian.
34
+ - Compactable with Hamiltonian represented by openfermion.FermionOperator and a tuple of 1-electron and 2-electron tensor.
35
+
36
+ ## License
37
+
38
+ `sdstate` was created by Linjun Wang <linjun.wang@mail.utoronto.ca>. It is licensed under the terms of the MIT license.
39
+
40
+ ## Credits
41
+
42
+ `sdstate` was created with [`cookiecutter`](https://cookiecutter.readthedocs.io/en/latest/) and the `py-pkgs-cookiecutter` [template](https://github.com/py-pkgs/py-pkgs-cookiecutter).
43
+
@@ -0,0 +1,25 @@
1
+ # sdstate
2
+
3
+ Implementation of Slater Determinant states as dictionaries of states and coefficient pairs. Each state is represented with an integar treated as binary, allowing applying of Excitation operators on the state efficiently. Memory-efficient implementation of Lanczos iteration for estimating Hamiltonian spectrum range.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ $ pip install module_sdstate
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ - Memory efficient implementation of Slater-Derterminant states.
14
+ - Compute expectation value of Hamiltonian on Slater-Determinant states states.
15
+ - Compute Hartree-Fock energy estimation of a Hamiltonian.
16
+ - Efficient estimation of ground state energy and spectrum range of Hamiltonian.
17
+ - Compactable with Hamiltonian represented by openfermion.FermionOperator and a tuple of 1-electron and 2-electron tensor.
18
+
19
+ ## License
20
+
21
+ `sdstate` was created by Linjun Wang <linjun.wang@mail.utoronto.ca>. It is licensed under the terms of the MIT license.
22
+
23
+ ## Credits
24
+
25
+ `sdstate` was created with [`cookiecutter`](https://cookiecutter.readthedocs.io/en/latest/) and the `py-pkgs-cookiecutter` [template](https://github.com/py-pkgs/py-pkgs-cookiecutter).
@@ -0,0 +1,18 @@
1
+ [tool.poetry]
2
+ name = "module_sdstate"
3
+ version = "0.1.0"
4
+ description = "Implementation of Slater Determinant states as dictionaries of states and coefficient pairs. Each state is represented with an integar treated as binary, allowing applying of Excitation operators on the state efficiently. Memory-efficient implementation of Lanczos iteration for estimating Hamiltonian spectrum range."
5
+ authors = ["Linjun Wang"]
6
+ license = "MIT"
7
+ readme = "README.md"
8
+
9
+ [tool.poetry.dependencies]
10
+ python = "^3.9"
11
+ numpy = "^1.26.4"
12
+ openfermion = "^1.6.1"
13
+
14
+ [tool.poetry.dev-dependencies]
15
+
16
+ [build-system]
17
+ requires = ["poetry-core>=1.0.0"]
18
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,3 @@
1
+ # read version from installed package
2
+ from importlib.metadata import version
3
+ __version__ = version("module_sdstate")
@@ -0,0 +1,167 @@
1
+ """Implements Hartree-Fock and Lanczos iterations to approximate energy range of an electronic Hamiltonian"""
2
+ import numpy as np
3
+ import copy
4
+ from scipy.linalg import eigh_tridiagonal
5
+ from multiprocessing import Pool
6
+ import openfermion as of
7
+ import os
8
+ from module_sdstate.sdstate_utils import sdstate
9
+
10
+ def HF_energy(Hf, n, ne):
11
+ """Find the energy of largest and smallest slater determinant states with Hf as Fermionic Hamiltonian and
12
+ number of electrons as ne.
13
+ """
14
+ # <low|H|low>
15
+ lstate = sdstate(((1 << ne) - 1) << (n-ne), n_qubit = n)
16
+ E_low = lstate.exp(Hf)
17
+ # <high|H|high>
18
+ hstate = sdstate((1 << ne) - 1, n_qubit = n)
19
+ E_high = hstate.exp(Hf)
20
+ return E_high, E_low
21
+
22
+ def HF_spectrum_range(Hf, multiprocessing = True):
23
+ """Compute the naive Hartree-Fock energy range of the Hamiltonian 2e tensor Hf for all number of electrons.
24
+ Multiprocessing parameter is set to parallelize computations for the states with different number of electrons.
25
+ Warning: This is not the actual Hartree-Fock energy range, which takes exponential time to compute
26
+ """
27
+ n = of.utils.count_qubits(Hf)
28
+ if multiprocessing:
29
+ num_processes = os.cpu_count()
30
+ with Pool(processes=num_processes) as pool:
31
+ res = pool.starmap(HF_energy, [(Hf, n, ne) for ne in range(n)])
32
+ low = 1e10
33
+ low_state = ""
34
+ high = -1e10
35
+ high_state = ""
36
+ for ne in range(len(res)):
37
+ E_high = res[ne][0]
38
+ E_low = res[ne][1]
39
+ if E_low < low:
40
+ low_state = (1 << ne) - 1
41
+ low = E_low
42
+ if E_high > high:
43
+ high_state = ((1 << ne) - 1) << (n-ne)
44
+ high = E_high
45
+ else:
46
+ low = 1e10
47
+ low_state = ""
48
+ high = -1e10
49
+ high_state = ""
50
+ for ne in range(n):
51
+ low_int = (1 << ne) - 1
52
+ lstate = sdstate(low_int, n_qubit = n)
53
+ # <low|H|low>
54
+ E_low = lstate.exp(Hf)
55
+ high_int = ((1 << ne) - 1) << (n-ne)
56
+ hstate = sdstate(high_int, n_qubit = n)
57
+ # <high|H|high>
58
+ E_high = hstate.exp(Hf)
59
+ if E_low < low:
60
+ low_state = low_int
61
+ low = E_low
62
+ if E_high > high:
63
+ high_state = high_int
64
+ high = E_high
65
+ high_str = bin(high_state)[2:][::-1]
66
+ low_str = bin(low_state)[2:][::-1]
67
+ high_str = "0" * (n - len(high_str)) + high_str
68
+ low_str += "0" * (n - len(low_str))
69
+ print("HF E_max: {}".format(high))
70
+ print("HF E_min: {}".format(low))
71
+ return high_str, low_str, high, low
72
+
73
+
74
+ def lanczos(Hf, steps, state = None, ne = None):
75
+ """Applies lanczos iteration on the given FermionOperator Hf, or a tuple of 1e and 2e tensor with number of steps
76
+ given by steps,
77
+ with initial state as input or number of electrons as input ne.
78
+ Returns normalized states in each iteration, and a tridiagonal matrix with main diagonal in A and sub-diagonal in B.
79
+ """
80
+ flag_tuple = False
81
+ if isinstance(Hf, tuple):
82
+ assert len(Hf) == 2, "Incorrect input of Hf"
83
+ flag_tuple = True
84
+ n_qubits = Hf[0].shape[0]
85
+ else:
86
+ n_qubits = of.utils.count_qubits(Hf)
87
+
88
+ if state == None:
89
+ if ne == None:
90
+ ne = n_qubits // 2
91
+ state = sdstate(int("1"*ne + "0"*(n_qubits - ne), 2), n_qubit = n_qubits)
92
+ state += sdstate(int("0"*(n_qubits - ne) + "1" * ne, 2), n_qubit = n_qubits)
93
+ state.normalize()
94
+ if flag_tuple:
95
+ tmp = state.tensor_state(Hf[0]) + state.tensor_state(Hf[1])
96
+ else:
97
+ tmp = state.Hf_state(Hf)
98
+ ai = tmp @ state
99
+ tmp -= ai * state
100
+ A = [ai]
101
+ B = []
102
+ states = [state]
103
+ vi = tmp
104
+ for i in range(1,steps):
105
+ bi = tmp.norm()
106
+ if bi != 0:
107
+ vi = tmp / bi
108
+ if flag_tuple:
109
+ tmp = vi.tensor_state(Hf[0]) + vi.tensor_state(Hf[1])
110
+ else:
111
+ tmp = vi.Hf_state(Hf)
112
+ ai = vi @ tmp
113
+ tmp -= ai * vi
114
+ tmp -= bi * states[i - 1]
115
+ states.append(vi)
116
+ A.append(ai)
117
+ B.append(bi)
118
+ return states, A, B
119
+
120
+ def lanczos_range(Hf, steps, state = None, ne = None):
121
+ """ Returns the largest and the smallest eigenvalue from Lanczos iterations with given number of steps,
122
+ number of electrons or initial state.
123
+ Strongly recommend to input the number of electrons in the ground state to find for correct spetral range
124
+ """
125
+ _, A, B = lanczos(Hf, steps = steps, state = state, ne = ne)
126
+ eigs, _ = eigh_tridiagonal(A,B)
127
+ return max(eigs), min(eigs)
128
+
129
+ def lanczos_total_range(Hf, steps = 2, states = [], e_nums = [], multiprocessing = True):
130
+ """Returns the largest and the smallest eigenvalue from Lanczos iterations with given number of steps,
131
+ for all possible number of electrons. Multiprocessing will parallelize the computation for all possible
132
+ number of electrons. states specifies the initial states for the Hamiltonian.
133
+ e_nums is an indicator for the number of electrons subspaces to check for the highest and lowest energy. If
134
+ not specified states or e_nums, the function will search through all possible values. Steps is set to 2 on default as
135
+ experimented that 2 iterations is capable of estimating within an accuracy of about 95%.
136
+ """
137
+ if isinstance(Hf, of.FermionOperator):
138
+ n = of.utils.count_qubits(Hf)
139
+ else:
140
+ n = Hf[0].shape[0]
141
+ if multiprocessing:
142
+ num_processes = os.cpu_count()
143
+ with Pool(processes=num_processes) as pool:
144
+ if len(states) != 0:
145
+ res = pool.starmap(lanczos_range, [(Hf, steps, st, None) for st in states])
146
+ elif len(e_nums) != 0:
147
+ res = pool.starmap(lanczos_range, [(Hf, steps, None, ne) for ne in e_nums])
148
+ else:
149
+ res = pool.starmap(lanczos_range, [(Hf, steps, None, ne) for ne in range(n)])
150
+ E_max = max([i[0] for i in res])
151
+ E_min = min([i[1] for i in res])
152
+ else:
153
+ E_max = -1e10
154
+ E_min = 1e10
155
+ if len(states) != 0:
156
+ for st in states:
157
+ states, A, B = lanczos(Hf, steps = steps, state = state)
158
+ eigs, _ = eigh_tridiagonal(A,B)
159
+ E_max = max(max(eigs), E_max)
160
+ E_min = min(min(eigs), E_min)
161
+ else:
162
+ for ne in range(n):
163
+ states, A, B = lanczos(Hf, steps = steps, ne = ne)
164
+ eigs, _ = eigh_tridiagonal(A,B)
165
+ E_max = max(max(eigs), E_max)
166
+ E_min = min(min(eigs), E_min)
167
+ return E_max, E_min
@@ -0,0 +1,267 @@
1
+ """Defines an implementation of the Slater Determinant states,with a dictionary to represent the occupied states with the corresponding constants.
2
+ """
3
+ # Defines an implementation of the Slater Determinant states,with a dictionary to represent the occupied states with the corresponding constants.
4
+ import numpy as np
5
+ from itertools import product
6
+ import copy
7
+ from scipy.linalg import eigh_tridiagonal
8
+ from multiprocessing import Pool
9
+ import openfermion as of
10
+ import os
11
+
12
+ # Parallelized adding of sdstates, to be further improved
13
+ def reducer(a,b):
14
+ return a+b
15
+ def parallel_reduce_partial(results):
16
+ num_processes = min(len(results), os.cpu_count())
17
+
18
+ # If there's only one or two results, no need for further parallel processing
19
+ if num_processes <= 2:
20
+ return reduce(reducer, results)
21
+
22
+ with Pool(processes=num_processes) as pool:
23
+ # Combine the results into pairs and reduce each pair
24
+ # If the number of results is odd, one will be left out and added at the end
25
+ paired_results = [(results[i], results[i+1]) for i in range(0, len(results)-1, 2)]
26
+ reduced_pairs = pool.starmap(reducer, paired_results)
27
+
28
+ # If there was an odd result out, add it to the list
29
+ if len(results) % 2 != 0:
30
+ reduced_pairs.append(results[-1])
31
+
32
+ # Further reduce if necessary
33
+ return parallel_reduce_partial(reduced_pairs)
34
+
35
+ def int_to_str(k, n_qubits):
36
+ return str(bin(k))[2:][::-1] + "0" * (n_qubits - k.bit_length())
37
+
38
+ class sdstate:
39
+ eps = 1e-8
40
+ dic = {}
41
+ n_qubit = 0
42
+ def __init__(self, s = None, coeff = 1, n_qubit = 0, eps = 1e-8):
43
+ self.dic = {}
44
+ self.eps = eps
45
+ if n_qubit:
46
+ self.n_qubit = n_qubit
47
+ if s:
48
+ self.dic[s] = coeff
49
+ if not self.n_qubit:
50
+ self.n_qubit = len(str(bin(s)))- 2
51
+
52
+ def norm(self):
53
+ # Return the norm of the current state
54
+ return np.sqrt(self @ self)
55
+
56
+ def remove_zeros(self):
57
+ # Remove zeros in the state
58
+ self.dic = {k: coeff for k, coeff in self.dic.items() if coeff != 0}
59
+ return None
60
+
61
+ def normalize(self):
62
+ # Normalize the current state
63
+ n = self.norm()
64
+ self.remove_zeros()
65
+ for i in self.dic:
66
+ self.dic[i] /= n
67
+ return None
68
+
69
+
70
+ def __add__(self, other):
71
+ # Create a new instance with the updated number of qubits
72
+ result = sdstate(n_qubit=max(self.n_qubit, other.n_qubit))
73
+ # Copy the dictionary from self
74
+ result.dic = self.dic.copy()
75
+
76
+ # Iterate over other.dic to add or update the states in result.dic
77
+ for s, coeff in other.dic.items():
78
+ result.dic[s] = result.dic.get(s, 0) + coeff
79
+
80
+ return result
81
+
82
+ def __sub__(self, other):
83
+ # Create a new instance with the updated number of qubits
84
+ result = sdstate(n_qubit=max(self.n_qubit, other.n_qubit))
85
+ # Copy the dictionary from self
86
+ result.dic = self.dic.copy()
87
+ # Iterate over other.dic to add or update the states in result.dic
88
+ for s, coeff in other.dic.items():
89
+ result.dic[s] = result.dic.get(s, 0) - coeff
90
+ return result
91
+
92
+ def __mul__(self, n):
93
+ # Defines constant multiplication
94
+ result = copy.deepcopy(self)
95
+ for s in result.dic:
96
+ result.dic[s] *= n
97
+ return result
98
+
99
+ def __rmul__(self, n):
100
+ return self.__mul__(n)
101
+
102
+ def __truediv__(self, n: int):
103
+ return self.__mul__(1/n)
104
+
105
+ def __matmul__(self, other):
106
+ if isinstance(other, of.FermionOperator):
107
+ return self.Hf_state(other)
108
+ if self.n_qubit == 0 or other.n_qubit == 0:
109
+ return 0
110
+ count = 0
111
+ # assert self.n_qubit == other.n_qubit, "qubit number mismatch"
112
+ lis = list(set(list(self.dic.keys())) & set(list(other.dic.keys())))
113
+ for s in lis:
114
+ count += np.conjugate(self.dic[s]) * other.dic[s]
115
+ return count
116
+
117
+ def __str__(self):
118
+ self.remove_zeros()
119
+ return str({int_to_str(k, self.n_qubit): self.dic[k] for k in self.dic})
120
+
121
+ def exp(self, Hf):
122
+ """Return the expectation value Hamiltonian on the current state with Hamiltonian in the form of either
123
+ of.FermionOperator, 1e or 2e tensor, or a tuple of (1e, 2e) tensor
124
+ """
125
+ if isinstance(Hf, of.FermionOperator):
126
+ return np.real(self @ self.Hf_state(Hf))
127
+ elif isinstance(Hf, np.ndarray):
128
+ return np.real(self @ self.tensor_state(Hf))
129
+ # Handles a tuple of 1e and 2e tensor
130
+ elif isinstance(Hf, tuple):
131
+ return self.exp(Hf[0]) + self.exp(Hf[1])
132
+ print("Invalid input of Hf")
133
+ return -1
134
+
135
+ def tensor_state(self, tbt):
136
+ n = tbt.shape[0]
137
+ assert len(tbt.shape) == 2 or len(tbt.shape) == 4, "Invalid tensor shape"
138
+ re_state = sdstate(n_qubit = self.n_qubit)
139
+ if len(tbt.shape) == 4:
140
+ for p, q, r, s in product(range(n), repeat = 4):
141
+ if tbt[p, q, r, s] != 0:
142
+ re_state += tbt[p, q, r, s] * self.Epqrs(self.n_qubit - 1 - p, self.n_qubit - 1 -q, self.n_qubit - 1 - r, self.n_qubit - 1 -s)
143
+ elif len(tbt.shape) == 2:
144
+ for p, q in product(range(n), repeat = 2):
145
+ if tbt[p,q] != 0:
146
+ re_state += tbt[p, q] * self.Epq(self.n_qubit - 1 - p, self.n_qubit - 1 - q)
147
+ return re_state
148
+
149
+ def concatenate(self, st):
150
+ # Return the direct product of two sdstates
151
+ if len(self.dic) == 0:
152
+ return st
153
+ elif len(st.dic) == 0:
154
+ return self
155
+ n2 = st.n_qubit
156
+ n = self.n_qubit + st.n_qubit
157
+ tmp = sdstate(n_qubit = n)
158
+ for s1 in self.dic:
159
+ for s2 in st.dic:
160
+ tmp += sdstate(s = s1 << n2 | s2, coeff = self.dic[s1] * st.dic[s2], n_qubit = n)
161
+ return tmp
162
+
163
+ def Epq(self, p, q):
164
+ """
165
+ Return the action of a_p^a_q on the current state.
166
+ """
167
+ tmp = sdstate(n_qubit = self.n_qubit)
168
+ for n in self.dic:
169
+ if actable_pq(n, p, q):
170
+ t = n ^ (1 << p) ^ (1 << q)
171
+ tmp += sdstate(t, self.dic[n] * (-1) ** parity_pq(n, p, q), n_qubit = self.n_qubit)
172
+ return tmp
173
+
174
+ def Epqrs(self, p, q, r, s):
175
+ # To be changed or improved? Current implementation based on Epq
176
+ """
177
+ Return the action of a_p^a_q on the current state.
178
+ """
179
+ tmp = sdstate(n_qubit = self.n_qubit)
180
+ for n in self.dic:
181
+ if actable_pq(n, r, s):
182
+ t = n ^ (1 << r) ^ (1 << s)
183
+ if actable_pq(t, p, q):
184
+ k = t ^ (1 << p) ^ (1 << q)
185
+ tmp += sdstate(k, self.dic[n] * (-1) ** (parity_pq(n, r, s) + parity_pq(k, p, q)), n_qubit = self.n_qubit)
186
+ return tmp
187
+
188
+ # Function to process a batch of terms
189
+ def process_batch(self,batch):
190
+ partial_state = sdstate(n_qubit=self.n_qubit)
191
+ for t, coeff in batch:
192
+ if coeff != 0:
193
+ partial_state += self.op_state(t, coeff)
194
+ return partial_state
195
+
196
+ def Hf_state(self, Hf: of.FermionOperator, multiprocessing = False):
197
+ """Apply a Hamiltonian in FermionOperator on the current state. multiprocessing can be used
198
+ to parallelize the process of applying each Excitation operator in the Hamiltonian. The general
199
+ cost is given by O(N^4M), for N as the qubit dimension and M as the size of the current state.
200
+ """
201
+ H = of.transforms.chemist_ordered(Hf)
202
+ re_state = sdstate(n_qubit = self.n_qubit)
203
+ if multiprocessing:
204
+ # Convert FermionOperator terms to a list of tuples for easy batch processing
205
+ terms_list = [(t, H.terms[t]) for t in H.terms]
206
+
207
+ # Determine the optimal number of batches
208
+ num_processes = min(len(terms_list), os.cpu_count())
209
+ batch_size = len(terms_list) // num_processes
210
+
211
+ # Create batches and distribute them across processes
212
+ batches = [terms_list[i:i + batch_size] for i in range(0, len(terms_list), batch_size)]
213
+
214
+ with Pool(processes=num_processes) as pool:
215
+ results = pool.map(self.process_batch, batches)
216
+
217
+ # Efficiently combine the results
218
+ for partial_state in results:
219
+ re_state += partial_state
220
+ else:
221
+ for t in H.terms:
222
+ re_state += self.op_state(t, H.terms[t])
223
+ return re_state
224
+
225
+ def op_state(self, t, coef):
226
+ if coef != 0:
227
+ if len(t) == 4:
228
+ return coef * self.Epqrs(self.n_qubit - 1 - t[0][0], self.n_qubit - 1 - t[1][0],
229
+ self.n_qubit - 1 - t[2][0], self.n_qubit - 1 - t[3][0])
230
+ elif len(t) == 2:
231
+ return coef * self.Epq(self.n_qubit - 1 - t[0][0], self.n_qubit - 1 - t[1][0])
232
+ elif len(t) == 0:
233
+ return coef * self
234
+ return sdstate(n_qubit = self.n_qubit)
235
+
236
+ def to_vec(self):
237
+ vec = np.zeros(2 ** self.n_qubit)
238
+ for i in self.dic:
239
+ vec[i] = self.dic[i]
240
+ return vec
241
+
242
+ def parity_pq(number: int, a, b):
243
+ """Count the number of electrons between p and q bits (p+1, p+2, ... ,q-1),
244
+ return a binary number representing the parity of the substring in the binary representation of number
245
+ """
246
+ if abs(a - b) < 2:
247
+ return 0
248
+ p = min(a,b)
249
+ q = max(a,b)
250
+ # Create a mask with 1s between p/
251
+ mask = ((1 << q) - 1)
252
+
253
+ # Apply the mask to truncate the first q bits, and drop the last p bits.
254
+ result = (number & mask ) >> (p + 1)
255
+ # Compute the parity
256
+ parity = 0
257
+ while result:
258
+ parity ^= 1
259
+ result &= result - 1 # Drops the lowest set bit
260
+ return parity
261
+
262
+ def actable_pq(n, p, q):
263
+ """
264
+ Determines if a_p^a_q annihilates the current state given by n, for n as an index in fock space
265
+ """
266
+ return (p == q and (n & 1 << q) != 0) or ((n & 1 << p) == 0 and (n & 1 << q) != 0)
267
+