Skip to content

Commit bdd3b06

Browse files
committed
Fixed the DFTB+ workflow, added new Pythonic eigensolvers, small fixes to Bohmian module and step3 module
1 parent c80872b commit bdd3b06

File tree

8 files changed

+453
-45
lines changed

8 files changed

+453
-45
lines changed

src/libra_py/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"data_stat",
2424
"data_visualize",
2525
"dynamics_plotting",
26+
"eigensolvers",
2627
"fgr_py",
2728
"fit",
2829
"fix_motion",

src/libra_py/dynamics/bohmian/compute.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def rho_lorentzian(q, Q, sigma):
6868
return y
6969

7070

71-
def quantum_potential_orginal(Q, sigma, mass, TBF):
71+
def quantum_potential_original(Q, sigma, mass, TBF):
7272
"""
7373
Args:
7474
* Q (Tensor(ntraj, ndof)) - coordinates of all trajectories
@@ -92,6 +92,31 @@ def quantum_potential_orginal(Q, sigma, mass, TBF):
9292
return U
9393

9494

95+
def quantum_potential_original_gen(q, Q, sigma, mass, TBF):
96+
"""
97+
Args:
98+
* Q (Tensor(ntraj, ndof)) - coordinates of all trajectories
99+
* sigma (Tensor(ndof)) - width parameters for each trajectory
100+
* mass ( Tensor(1, ndof)) - masses of all DOFs, same for all trajectories
101+
* TBF (object) - basis function reference (`rho_gaussian` or `rho_lorentzian`)
102+
103+
Returns:
104+
Tensor(1) - quantum potential summed over all trajectory points
105+
"""
106+
107+
ntraj, ndof = Q.shape[0], Q.shape[1]
108+
U = torch.zeros( (1,), requires_grad=True)
109+
f = TBF(q, Q, sigma);
110+
[deriv1] = torch.autograd.grad(f, [q], create_graph=True, retain_graph=True);
111+
for i in range(ndof):
112+
[deriv2] = torch.autograd.grad(deriv1[i], [q], create_graph=True, retain_graph=True);
113+
u = -(0.25/mass[0,i])*( deriv2[i]/f - 0.5 * (deriv1[i]/f)**2 );
114+
U = U + u
115+
return U
116+
117+
118+
119+
95120
def quantum_potential(Q, sigma, mass, TBF):
96121
"""
97122
Compute quantum potential in a fully vectorized way.

src/libra_py/eigensolvers.py

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
2+
import numpy as np
3+
from scipy.linalg import cholesky, solve_triangular
4+
from scipy.linalg import eigh, eig
5+
6+
def generalized_eigensolve_scipy(A, B, hermitian=True, sort=True):
7+
"""
8+
Solve the generalized eigenvalue problem A v = λ B v using SciPy.
9+
10+
Args:
11+
A (np.ndarray): Matrix A (N x N)
12+
B (np.ndarray): Matrix B (N x N)
13+
hermitian (bool): Set True if A and B are Hermitian/symmetric.
14+
sort (bool): If True, sort eigenpairs by ascending eigenvalue.
15+
16+
Returns:
17+
eigvals (np.ndarray): Eigenvalues (N,)
18+
eigvecs (np.ndarray): Corresponding eigenvectors (N, N)
19+
20+
Example:
21+
22+
import numpy as np
23+
24+
# Symmetric A and B
25+
A = np.array([[6., 2.], [2., 3.]])
26+
B = np.array([[1., 0.], [0., 2.]])
27+
eigvals, eigvecs = generalized_eigensolve_scipy(A, B)
28+
print("Eigenvalues:", eigvals)
29+
print("Eigenvectors (columns):\n", eigvecs)
30+
31+
"""
32+
33+
if hermitian:
34+
eigvals, eigvecs = eigh(A, B)
35+
else:
36+
eigvals, eigvecs = eig(A, B)
37+
38+
if sort:
39+
idx = np.argsort(eigvals.real if not hermitian else eigvals)
40+
eigvals = eigvals[idx]
41+
eigvecs = eigvecs[:, idx]
42+
43+
# Normalize eigenvectors with respect to B: v† B v = 1
44+
for i in range(eigvecs.shape[1]):
45+
vec = eigvecs[:, i]
46+
#norm = np.sqrt(np.conj(vec).T @ B @ vec)
47+
norm = np.sqrt(np.vdot(vec, B @ vec)) # vdot does conjugation
48+
eigvecs[:, i] = vec / norm
49+
50+
51+
return eigvals, eigvecs
52+
53+
54+
55+
def generalized_eigensolve_scipy_cholesky(A, B, hermitian=True, sort=True):
56+
"""
57+
Robust solver for A v = λ B v, with B-orthonormal eigenvectors:
58+
v_i^† B v_j = δ_ij (up to numerical error)
59+
60+
For Hermitian A, B > 0.
61+
"""
62+
if not hermitian:
63+
raise NotImplementedError("Only Hermitian positive-definite B is supported in stable form")
64+
65+
# Cholesky decomposition: B = L L^H
66+
L = cholesky(B, lower=True)
67+
68+
# Transform to standard eigenvalue problem: C = L^{-1} A L^{-H}
69+
Linv_A = solve_triangular(L, A, lower=True, trans='N')
70+
C = solve_triangular(L, Linv_A.T, lower=True, trans='C').T
71+
72+
# Solve C y = λ y
73+
eigvals, y = eigh(C)
74+
75+
if sort:
76+
idx = np.argsort(eigvals)
77+
eigvals = eigvals[idx]
78+
y = y[:, idx]
79+
80+
# Recover v = L^{-H} y
81+
eigvecs = solve_triangular(L, y, lower=True, trans='C')
82+
83+
# At this point, eigvecs should satisfy v.T.conj() @ B @ v ≈ I
84+
return eigvals, eigvecs
85+
86+
87+
"""
88+
89+
Tests and conventions for this module:
90+
91+
from liblibra_core import *
92+
from libra_py import eigensolvers, data_conv
93+
import numpy as np
94+
95+
# ===== Do the solution ==========
96+
A = np.array([[0, 1.0],
97+
[1.0, 1.0]])
98+
B = np.array([[1.0, 0.0],
99+
[0.0, 1.0]])
100+
101+
eigvals, eigvecs = eigensolvers.generalized_eigensolve_scipy(A, B, hermitian=False)
102+
103+
104+
# ======= Numpy multiplications ==========
105+
E = np.diag(eigvals)
106+
C = eigvecs
107+
print( E )
108+
print( C )
109+
print("LHS = ", A @ C)
110+
print("RHS = ", B @ C @ E)
111+
112+
Selection deleted
113+
E = np.diag(eigvals)
114+
C = eigvecs
115+
print( E )
116+
print( C )
117+
print("LHS = ", A @ C)
118+
119+
print("RHS = ", B @ C @ E)
120+
121+
>>[[-0.61803399+0.j 0. +0.j]
122+
>> [ 0. +0.j 1.61803399+0.j]]
123+
>>[[ 0.85065081 0.52573111]
124+
>> [-0.52573111 0.85065081]]
125+
>>LHS = [[-0.52573111 0.85065081]
126+
>> [ 0.3249197 1.37638192]]
127+
>>RHS = [[-0.52573111+0.j 0.85065081+0.j]
128+
>> [ 0.3249197 +0.j 1.37638192+0.j]]
129+
130+
131+
# ======= Conversion to MATRIX ===========
132+
133+
H = data_conv.nparray2MATRIX(A)
134+
U = data_conv.nparray2MATRIX(C)
135+
S = data_conv.nparray2MATRIX(B)
136+
e = data_conv.nparray2MATRIX(E)
137+
138+
# ======== Libra multiplications ========
139+
140+
(H * U).show_matrix()
141+
142+
>>-0.52573111 0.85065081
143+
>>0.32491970 1.3763819
144+
145+
(S * U * e).show_matrix()
146+
147+
>>-0.52573111 0.85065081
148+
>>0.32491970 1.3763819
149+
150+
# ========= Eigenvectors ==============
151+
print("First eigenvector")
152+
print(U.get(0, 0))
153+
print(U.get(1, 0))
154+
155+
>>First eigenvector
156+
>>0.8506508083520399
157+
>>-0.5257311121191337
158+
159+
print("First eigenvector")
160+
print(C[0, 0])
161+
print(C[1, 0])
162+
163+
>>First eigenvector
164+
>>0.8506508083520399
165+
>>-0.5257311121191337
166+
167+
168+
"""
169+

src/libra_py/packages/dftbplus/methods.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ def get_dftb_matrices(filename, act_sp1=None, act_sp2=None, correction=0, tol=0.
144144
"""
145145

146146
# Copy the content of the file to a temporary location
147+
print(F"Reading file {filename}")
147148
f = open(filename, "r")
148149
A = f.readlines()
149150
f.close()
@@ -167,6 +168,8 @@ def get_dftb_matrices(filename, act_sp1=None, act_sp2=None, correction=0, tol=0.
167168
X = []
168169
norbs2 = int(norbs / 2)
169170

171+
print(F"norbs = {norbs}, norbs2 = {norbs2}, act_sp1 = {act_sp1}, act_sp2 = {act_sp2}")
172+
170173
# For each k-point
171174
for ikpt in range(0, nkpts):
172175

@@ -176,6 +179,8 @@ def get_dftb_matrices(filename, act_sp1=None, act_sp2=None, correction=0, tol=0.
176179
B = A[5 + ikpt * norbs: 5 + (1 + ikpt) * norbs]
177180

178181
f1 = open(filename + "_tmp", "w")
182+
x = MATRIX(norbs, norbs)
183+
179184
for i in range(0, norbs):
180185

181186
tmp = B[i].split()
@@ -186,18 +191,23 @@ def get_dftb_matrices(filename, act_sp1=None, act_sp2=None, correction=0, tol=0.
186191
if tmp[j] == "NaN" or tmp[j] == "NAN":
187192
z = 1000.0
188193
# z = float(tmp[int(j%norbs2)]) #-1000.0
194+
sys.exit(0)
189195
else:
190196
try:
191197
z = float(tmp[j])
198+
x.set(i,j, z)
192199
except ValueError:
193-
z = 1000.0
200+
sys.exit(0)
201+
#z = 1000.0
194202
# z = float(tmp[int(j%norbs2)]) #-1000.0
195-
pass
203+
#pass
196204
except TypeError:
197-
z = 1000.0
205+
sys.exit(0)
206+
#z = 1000.0
198207
# z = float(tmp[int(j%norbs2)]) #-1000.0
199-
pass
208+
#pass
200209

210+
"""
201211
if correction == 1: # if we want to enforce correction of the matrix elements in blocks 01 and 10
202212
# norbs2 norbs
203213
# ______________
@@ -225,16 +235,18 @@ def get_dftb_matrices(filename, act_sp1=None, act_sp2=None, correction=0, tol=0.
225235
z = 0.0
226236
else:
227237
pass
228-
229-
line = line + "%10.8f " % (z)
238+
"""
239+
#line = line + "%10.8f " % (z)
240+
line = F"{line} {z}"
230241
line = line + "\n"
231242

232243
f1.write(line)
233244
f1.close()
234245

235246
# Read in the temporary file - get the entire matrix
236-
x = MATRIX(norbs, norbs)
237-
x.Load_Matrix_From_File(filename + "_tmp")
247+
#x = MATRIX(norbs, norbs)
248+
#x.Load_Matrix_From_File(filename + "_tmp")
249+
238250

239251
# Extract the sub-matrix of interest
240252
x_sub = MATRIX(nstates1, nstates2)

src/libra_py/workflows/nbra/mapping.py

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -215,17 +215,20 @@ def energy_arb(SD, e):
215215
This is the approximation of the SD energy
216216
217217
"""
218+
if isinstance(e, np.ndarray)==True:
219+
nbasis = e.shape[0]
220+
sd = sd2indx(SD, nbasis)
221+
res = 0.0
222+
for i in sd:
223+
res = res + e[i, i]
218224

219-
# nbasis = e.num_of_rows
220-
nbasis = e.shape[0]
221225

222-
sd = sd2indx(SD, nbasis)
223-
# res = 0.0+0.0j
224-
res = 0.0
225-
226-
for i in sd:
227-
# res = res + e.get(i,i)
228-
res = res + e[i, i]
226+
elif isinstance(e, CMATRIX)==True or isinstance(e, MATRIX)==True:
227+
nbasis = e.num_of_rows
228+
sd = sd2indx(SD, nbasis)
229+
res = 0.0+0.0j
230+
for i in sd:
231+
res = res + e.get(i,i)
229232

230233
return res
231234

@@ -246,14 +249,21 @@ def energy_mat_arb(SD, e, dE):
246249
CMATRIX(N,N): the matrix of energies in the SD basis. Here, N = len(SD) - the number of SDs.
247250
248251
"""
249-
n = len(SD)
250-
# E = CMATRIX(n,n)
251-
E = np.zeros((n, n))
252-
253-
E0 = energy_arb(SD[0], e) + dE[0] # *(1.0+0.0j)
254-
for i in range(0, n):
255-
# E.set(i,i, energy_arb(SD[i], e) + dE[i]*(1.0+0.0j) - E0 )
256-
E[i, i] = energy_arb(SD[i], e) + dE[i] - E0
252+
E = None
253+
254+
if isinstance(e, np.ndarray)==True:
255+
n = len(SD)
256+
E = np.zeros((n, n))
257+
E0 = energy_arb(SD[0], e) + dE[0]
258+
for i in range(0, n):
259+
E[i, i] = energy_arb(SD[i], e) + dE[i] - E0
260+
261+
elif isinstance(e, CMATRIX)==True or isinstance(e, MATRIX)==True:
262+
n = len(SD)
263+
E = CMATRIX(n,n)
264+
E0 = energy_arb(SD[0], e) + dE[0]*(1.0+0.0j)
265+
for i in range(0, n):
266+
E.set(i,i, energy_arb(SD[i], e) + dE[i]*(1.0+0.0j) - E0 )
257267

258268
return E
259269

0 commit comments

Comments
 (0)