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.
- module_sdstate-0.1.0/LICENSE +22 -0
- module_sdstate-0.1.0/PKG-INFO +43 -0
- module_sdstate-0.1.0/README.md +25 -0
- module_sdstate-0.1.0/pyproject.toml +18 -0
- module_sdstate-0.1.0/src/module_sdstate/__init__.py +3 -0
- module_sdstate-0.1.0/src/module_sdstate/lanczos_utils.py +167 -0
- module_sdstate-0.1.0/src/module_sdstate/sdstate_utils.py +267 -0
|
@@ -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,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
|
+
|