-
Notifications
You must be signed in to change notification settings - Fork 15
/
api.py
441 lines (382 loc) · 15.1 KB
/
api.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
r"""Model Hamiltonian API."""
from abc import ABC, abstractmethod
from typing import TextIO
import numpy as np
from scipy.sparse import csr_matrix, lil_matrix
from .utils import convert_indices, get_atom_type
__all__ = [
"HamiltonianAPI",
]
class HamiltonianAPI(ABC):
r"""Hamiltonian abstract base class."""
def generate_connectivity_matrix(self):
r"""
Generate connectivity matrix.
Returns
-------
tuple
(dictionary, np.ndarray)
"""
max_site = 0
atoms_sites_lst = []
for atom1, atom2, bond in self.connectivity:
atom1_name, site1 = get_atom_type(atom1)
atom2_name, site2 = get_atom_type(atom2)
for pair in [(atom1_name, site1), (atom2_name, site2)]:
if pair not in atoms_sites_lst:
atoms_sites_lst.append(pair)
if max_site < max(site1, site2): # finding the max index of site
max_site = max(site1, site2)
self.n_sites = len(atoms_sites_lst)
if self.atom_types is None:
atom_types = [None for i in range(max_site + 1)]
for atom, site in atoms_sites_lst:
atom_types[site] = atom
self.atom_types = atom_types
connectivity_mtrx = np.zeros((max_site, max_site))
for atom1, atom2, bond in self.connectivity:
atom1_name, site1 = get_atom_type(atom1)
atom2_name, site2 = get_atom_type(atom2)
connectivity_mtrx[site1 - 1, site2 - 1] = bond
# numbering of sites starts from 1
connectivity_mtrx = np.maximum(connectivity_mtrx, connectivity_mtrx.T)
self.connectivity_matrix = csr_matrix(connectivity_mtrx)
return atoms_sites_lst, self.connectivity_matrix
@abstractmethod
def generate_zero_body_integral(self):
r"""Generate zero body integral."""
pass
@abstractmethod
def generate_one_body_integral(self, dense: bool, basis: str):
r"""
Generate one body integral in spatial or spin orbital basis.
Parameters
----------
basis: str
['spatial', 'spin orbital']
dense: bool
dense or sparse matrix; default False
Returns
-------
scipy.sparse.csr_matrix or np.ndarray
"""
pass
@abstractmethod
def generate_two_body_integral(self, sym: int, basis: str, dense: bool):
r"""
Generate two body integral in spatial or spinorbital basis.
Parameters
----------
sym: int
symmetry -- [2, 4, 8] default is 1
basis: str
['spatial', 'spin orbital']
dense: bool
dense or sparse matrix; default False
Returns
-------
scipy.sparse.csr_matrix or np.ndarray
"""
pass
def to_sparse(self, Md):
r"""
Convert dense array of integrals to sparse array in scipy csr format.
Parameters
----------
Md: np.ndarray
input matrix of the shape 2d or 4d
Returns
-------
scipy.sparse.csr_matrix
"""
# Finding indices for non-zero elements and shape of Md.
indices = np.array(np.where(Md != 0)).astype(int).T
N = Md.shape[0]
# Converting 2D array to csr_matrix
if np.ndim(Md) == 2:
return csr_matrix(Md)
# Converting 4D array to csr_matrix using convert_indices from util.py.
elif np.ndim(Md) == 4:
row = np.array([])
col = np.array([])
data = np.array([])
for ind in indices:
p, q = convert_indices(N,
int(ind[0]),
int(ind[1]),
int(ind[2]),
int(ind[3]))
row = np.append(row, p)
col = np.append(col, q)
data = np.append(data, Md[tuple(ind)])
return csr_matrix((data, (row, col)), shape=(N * N, N * N))
# Return if array dimensions incompatible.
else:
print("Incompatible dense array dimension.",
" Must be either 2 or 4 dimensions.")
return
def to_dense(self, Ms, dim=2):
r"""
Convert to dense matrix.
Convert sparse array of integrals
in scipy csr format to dense numpy array.
Parameters
----------
Ms: scipy.sparse.csr_matrix
dim: int
target dimension of output array (either 2 or 4)
Returns
-------
np.ndarray
"""
# return dense 2D array (default).
if dim == 2:
return Ms.todense() if isinstance(Ms, csr_matrix) else Ms
# Optionally, return dense 4D array for two-particle integrals.
elif dim == 4:
N = int(np.sqrt(Ms.shape[0]))
vd = np.zeros([N, N, N, N])
for p, q in np.array(Ms.nonzero()).T:
i, j, k, l_ = convert_indices(N, int(p), int(q))
vd[(i, j, k, l_)] = Ms[p, q]
return vd
# Return if target dim is not 2 or 4.
else:
print("Target output dimension must be either 2 or 4.")
return
def to_spatial(self, sym: int, dense: bool, nbody: int):
r"""
Convert one-/two- integral matrix from spin-orbital to spatial basis.
Parameters
----------
sym: int
symmetry -- [2, 4, 8] default is 1
dense: bool
dense or sparse matrix; default False
nbody: int
type of integral, one of 1 (one-body) or 2 (two-body)
Returns
-------
spatial_int: scipy.sparce.csr_matrix or np.ndarray
one-/two-body integrals in spatial basis
"""
# Assumption: spatial components of alpha and beta
# spin-orbitals are equivalent
integral = self.one_body if nbody == 1 else self.two_body
n = 2 * self.n_sites
if integral.shape[0] == 2 * self.n_sites:
spatial_int = lil_matrix((self.n_sites, self.n_sites))
spatial_int = integral[: self.n_sites, : self.n_sites]
elif integral.shape[0] == 4 * self.n_sites ** 2:
spatial_int = lil_matrix((self.n_sites ** 2, self.n_sites ** 2))
for p in range(self.n_sites):
# v_pppp = U_pppp_ab
pp, pp = convert_indices(self.n_sites, p, p, p, p)
pp_, pp_ = convert_indices(n,
p, p + self.n_sites,
p, p + self.n_sites)
spatial_int[pp, pp] = integral[(pp_, pp_)]
for q in range(p, self.n_sites):
# v_pqpq = 0.5 * (Gamma_pqpq_aa + Gamma_pqpq_bb)
pq, pq = convert_indices(self.n_sites, p, q, p, q)
pq_, pq_ = convert_indices(n, p, q, p, q)
spatial_int[pq, pq] = integral[pq_, pq_]
# v_pqpq += 0.5 * (Gamma_pqpq_ab + Gamma_pqpq_ba)
pq_, pq_ = convert_indices(n,
p, q + self.n_sites,
p, q + self.n_sites)
spatial_int[pq, pq] += integral[pq_, pq_]
# v_ppqq = Pairing_ppqq_ab
pp, qq = convert_indices(self.n_sites, p, p, q, q)
pp_, qq_ = convert_indices(n,
p, p + self.n_sites,
q, q + self.n_sites)
spatial_int[pp, qq] = integral[pp_, qq_]
else:
raise ValueError("Wrong integral input.")
spatial_int = expand_sym(sym, spatial_int, nbody)
spatial_int = spatial_int.tocsr()
if dense:
if isinstance(
spatial_int, csr_matrix
): # FixMe make sure that this works for every system
spatial_int = spatial_int.toarray()
spatial_int = np.reshape(
spatial_int, (self.n_sites,
self.n_sites,
self.n_sites,
self.n_sites)
)
else:
spatial_int = self.to_dense(spatial_int,
dim=4 if nbody == 2 else 1)
return spatial_int
def to_spinorbital(self, integral: np.ndarray, sym=1, dense=False):
r"""
Convert one-/two- integral matrix from spatial to spin-orbital basis.
Parameters
----------
integral: scipy.sparse.csr_matrix
type of integral, one of 1 (one-body) or 2 (two-body)
sym: int
symmetry -- [2, 4, 8] default is 1
dense: bool
dense or sparse matrix; default False
Returns
-------
None
"""
pass
def save_fcidump(self, f: TextIO, nelec=0, spinpol=0):
r"""
Save all parts of hamiltonian in fcidump format.
Parameters
----------
f: TextIO file
nelec: int
The number of electrons in the system
spinpol: float
The spin polarization. By default, its value is derived from the
molecular orbitals (mo attribute), as abs(nalpha - nbeta).
In this case, spinpol cannot be set.
When no molecular orbitals are present,
this attribute can be set.
Returns
-------
None
"""
# Reduce symmetry of integral
one_ints = expand_sym(self._sym, self.one_body, 1)
# Write header
nactive = one_ints.shape[0]
print(f" &FCI NORB={nactive:d},"
f"NELEC={nelec:d},MS2={spinpol:d},", file=f)
print(f" ORBSYM= {','.join('1' for v in range(nactive))},", file=f)
print(" ISYM=1", file=f)
print(" &END", file=f)
# Reduce symmetry of integrals
two_ints = expand_sym(self._sym, self.two_body, 2)
# getting nonzero elements from the 2d _sparse_ array
p_array, q_array = two_ints.nonzero()
# converting 2d indices to 4d indices
N = int(np.sqrt(two_ints.shape[0]))
for p, q in zip(p_array, q_array):
i, j, k, l_ = convert_indices(N, int(p), int(q))
# changing indexing from physical to chemical notation
j, k = k, j
if j > i and l_ > k and\
(i * (i + 1)) / 2 + j >= (k * (k + 1)) / 2 + l_:
value = two_ints[(i, k, j, l_)]
print(f"{value:23.16e} "
f"{i + 1:4d} {j + 1:4d} {k + 1:4d} "
f"{l_ + 1:4d}", file=f)
for i in range(nactive):
for j in range(i + 1):
value = one_ints[i, j]
if value != 0.0:
print(f"{value:23.16e} {i + 1:4d}"
f" {j + 1:4d} {0:4d} {0:4d}", file=f)
core_energy = self.zero_energy
if core_energy is not None:
print(f"{core_energy:23.16e} {0:4d} {0:4d} {0:4d} {0:4d}", file=f)
def save_triqs(self, fname: str, integral):
r"""
Save matrix in triqc format.
Parameters
----------
fname: str
name of the file
integral: int
type of integral, one of 1 (one-body) or 2 (two-body)
Returns
-------
None
"""
pass
def save(self, fname: str, integral, basis):
r"""Save file as regular numpy array."""
pass
def expand_sym(sym, integral, nbody):
r"""
Restore permutational symmetry of one- and two-body terms.
Parameters
----------
sym: int
integral symmetry, one of 1 (no symmetry), 2, 4 or 8.
integral: scipy.sparce.csr_matrix
2-D sparse array, the {one,two}-body integrals
nbody: int
number of particle variables in the integral,
one of 1 (one-body) or 2 (two-body)
Returns
-------
integral: scipy.sparse.csr_matrix
2d array of with the symmetry 1
Notes
-----
Given the one- or two-body Hamiltonian matrix terms,
:math:`h_{i,j}` and :math:`g_{ij,kl}` respectively,
the supported permutational symmetries are:
sym = 2:
:math:`h_{i,j} = h_{j,i}`
:math:`g_{ij,kl} = g_{kl,ij}`
sym = 4:
:math:`g_{ij,kl} = g_{kl,ij} = g_{ji,lk} = g_{lk,ji}`
sym = 8:
:math:`g_{ij,kl} = g_{kl,ij} = g_{ji,lk} = g_{lk,ji} =
g_{kj,il} = g_(il,kj) = g_(li,jk) = g_(jk,li)`
sym = 1 corresponds to no-symmetry
where it is assumed the integrals are over real orbitals.
The input Hamiltonian terms are expected to be sparse
arrays of dimensions :math:`(N,N)` or
:math:`(N^2, N^2)` for the one- and two-body integrals respectively.
:math:`N` represents the number of basis functions,
which may be either of spatial or spin-orbital type.
This function applies to the input array the
permutations indicated by the symmetry parameter `sym`
to adds the missing terms.
Phicisist notation is used for the two-body integrals:
:math:`<pq|rs>` and further details of the
permutations considered can be
found in [this site]
(http://vergil.chemistry.gatech.edu/notes/permsymm/permsymm.html).
"""
if sym not in [1, 2, 4, 8]:
raise ValueError("Wrong input symmetry")
if nbody not in [1, 2]:
raise ValueError(f"`nbody` must be an integer, "
f"either 1 or 2, but {nbody} given")
if sym == 1:
return integral
# Expanding Symmetries
if nbody == 1:
if not sym == 2:
raise ValueError("Wrong 1-body term symmetry")
h_ii = diags(integral.diagonal()).copy()
integral = integral + integral.T - h_ii
else:
# getting nonzero elements from the 2d _sparse_ array
pq_array, rs_array = integral.nonzero()
for pq, rs in zip(pq_array, rs_array):
p, q, r, s = convert_indices(n, pq, rs)
if sym >= 2:
# 1. Transpose: <pq|rs>=<rs|pq>
rs, pq = convert_indices(n, r, s, p, q)
integral[rs, pq] = integral[pq, rs]
if sym >= 4:
# 2. Permute dummy indices
# (swap variables of particles 1 and 2):
# <p_1 q_2|r_1 s_2> = <q_1 p_2|s_1 r_2>
qp, sr = convert_indices(n, q, p, s, r)
integral[qp, sr] = integral[pq, rs]
integral[sr, qp] = integral[rs, pq]
if sym == 8:
# 3. Permute orbitals of the same variable,
# e.g. <p_1 q_2|r_1 s_2> = <r_1 q_2|p_1 s_2>
rq, ps = convert_indices(n, r, q, p, s)
sp, qr = convert_indices(n, s, p, q, r)
integral[rq, ps] = integral[pq, rs]
integral[ps, rq] = integral[rs, pq]
integral[sp, qr] = integral[qp, sr]
integral[qr, sp] = integral[sr, qp]
return integral