Skip to content

Commit 0e27094

Browse files
authored
Merge pull request Quantum-Dynamics-Hub#258 from MohammadShakiba/devel
Revision of step 3 mapping function and printing of average decoherence rate
2 parents c41f2e1 + 34fb18e commit 0e27094

File tree

9 files changed

+188
-12
lines changed

9 files changed

+188
-12
lines changed

src/dyn/Dynamics.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1635,7 +1635,10 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params,
16351635
// Saves the current density matrix into the previous - needed for FSSH2 and GFSH (original)
16361636
dyn_var.save_curr_dm_into_prev();
16371637

1638-
1638+
for(traj=0; traj<ntraj; traj++){
1639+
*dyn_var.ave_decoherence_rates += decoherence_rates[traj];
1640+
}
1641+
*dyn_var.ave_decoherence_rates /= ntraj;
16391642

16401643
}
16411644

src/dyn/dyn_decoherence_time.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,6 @@ vector<MATRIX> schwartz_1(dyn_control_params& prms, CMATRIX& amplitudes, MATRIX&
391391
double tau_inv2 = 0.0;
392392
for(int idof=0; idof<ndof; idof++){
393393
double dF = F_mf.get(idof, itraj) - F_st.get(idof, itraj);
394-
395394
w2 = pow(w.get(idof,0), 2.0);
396395
tau_inv2 += 0.25 * pow(4*M_PI/(w2*p.get(idof, itraj)), 2.0) * dF * dF;
397396
}

src/dyn/dyn_variables.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ void dyn_variables::allocate_electronic_vars(){
3333
ampl_adi = new CMATRIX(nadi, ntraj);
3434
q_mm = new MATRIX(nadi, ntraj);
3535
p_mm = new MATRIX(nadi, ntraj);
36+
ave_decoherence_rates = new MATRIX(nadi, nadi);
3637
proj_adi = vector<CMATRIX*>(ntraj);
3738
dm_dia = vector<CMATRIX*>(ntraj);
3839
dm_adi = vector<CMATRIX*>(ntraj);
@@ -348,6 +349,7 @@ dyn_variables::dyn_variables(const dyn_variables& x){
348349
*ampl_adi = *x.ampl_adi;
349350
*q_mm = *x.q_mm;
350351
*p_mm = *x.p_mm;
352+
*ave_decoherence_rates = *x.ave_decoherence_rates;
351353
for(itraj=0; itraj<ntraj; itraj++){
352354
*proj_adi[itraj] = *x.proj_adi[itraj];
353355
*dm_dia[itraj] = *x.dm_dia[itraj];
@@ -524,6 +526,7 @@ dyn_variables::~dyn_variables(){
524526
delete ampl_adi;
525527
delete q_mm;
526528
delete p_mm;
529+
delete ave_decoherence_rates;
527530

528531
act_states.clear();
529532
act_states_dia.clear();

src/dyn/dyn_variables.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,12 @@ class dyn_variables{
576576
*/
577577
vector< vector< vector<double> > > coherence_factors;
578578

579+
///====================== For average decoherence rates
580+
/*
581+
Stores the decoherence rates between states averaged over all trajectories
582+
*/
583+
584+
MATRIX* ave_decoherence_rates;
579585

580586
///====================== In dyn_variables.cpp =====================
581587

@@ -628,6 +634,7 @@ class dyn_variables{
628634
MATRIX get_momenta_aux(int i){ return *p_aux[i]; }
629635
MATRIX get_nab_phase(int i){ return *nab_phase[i]; }
630636
MATRIX get_qtsh_f_nc(){ return *qtsh_f_nc; }
637+
MATRIX get_ave_decoherence_rates(){ return *ave_decoherence_rates; }
631638

632639
void get_current_timestep(bp::dict params){
633640
std::string key;

src/dyn/libdyn.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ void export_dyn_variables_objects(){
272272
.def("get_ampl_dia", &dyn_variables::get_ampl_dia)
273273
.def("get_q_mm", &dyn_variables::get_q_mm)
274274
.def("get_p_mm", &dyn_variables::get_p_mm)
275+
.def("get_ave_decoherence_rates", &dyn_variables::get_ave_decoherence_rates)
275276
.def("get_proj_adi", &dyn_variables::get_proj_adi)
276277
.def("get_dm_adi", expt_get_dm_adi_v1)
277278
.def("get_dm_adi", expt_get_dm_adi_v2)

src/libra_py/dynamics/tsh/save.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,10 @@ def init_tsh_data(saver, output_level, _nsteps, _ntraj, _ndof, _nadi, _ndia):
239239
if "qtsh_f_nc" in saver.keywords: # and "f_xf" in saver.np_data.keys():
240240
saver.add_dataset("qtsh_f_nc", (_nsteps, _ntraj, _ndof), "R")
241241

242+
# Trajectory-resolved decoherence rates
243+
if "ave_decoherence_rates" in saver.keywords: # decoherence time:
244+
saver.add_dataset("ave_decoherence_rates", (_nsteps, _nadi, _nadi), "R")
245+
242246
if output_level >= 4:
243247

244248
# Trajectory-resolved vibronic Hamiltoninans in the adiabatic representation
@@ -755,6 +759,12 @@ def save_hdf5_3D_new(saver, i, dyn_var, txt_type=0):
755759
qtsh_f_nc = dyn_var.get_qtsh_f_nc()
756760
saver.save_matrix(t, "qtsh_f_nc", qtsh_f_nc.T())
757761

762+
# Average decoherence rate
763+
# Format: saver.add_dataset("ave_decoherence_rates", (_nsteps, _nadi, _nadi), "R")
764+
if "ave_decoherence_rates" in saver.keywords and "ave_decoherence_rates" in saver.np_data.keys():
765+
ave_decoherence_rates = dyn_var.get_ave_decoherence_rates()
766+
saver.save_matrix(t, "ave_decoherence_rates", ave_decoherence_rates.T())
767+
758768

759769
def save_hdf5_4D(
760770
saver,

src/libra_py/packages/cp2k/methods.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@
3535
from liblibra_core import *
3636
import util.libutil as comn
3737

38-
from libra_py import data_outs, data_stat
39-
from libra_py import units
38+
from libra_py import data_outs, data_stat, data_conv
39+
from libra_py import units, molden_methods
4040

4141

4242
def ndigits(integer_number: int):
@@ -2354,3 +2354,45 @@ def atom_components_cp2k(filename):
23542354
index = list(np.where(atoms == i)[0])
23552355
indices.append(index)
23562356
return indices
2357+
2358+
2359+
def compute_mo_overlap(params):
2360+
"""
2361+
This function computes the molecular orbital overlap calculations between
2362+
two geometries: $\langle\Phi_{i,R}\|\Phi_{j,R'}\rangle$. It takes a dictionary as parameters.
2363+
Args:
2364+
params (dictionary):
2365+
molden_file_1 (string): The path to the first geometry molden file
2366+
molden_file_2 (string): The path to the second geometry molden file
2367+
is_spherical (bool): The spherical coordinate boolean flga
2368+
nprocs (integer): The number of pocessors
2369+
Returns:
2370+
mo_overlap_matrix (numpy array): The overlap between molecular orbitals
2371+
"""
2372+
# Create Libint integral shells for each molden file
2373+
shell_1, l_vals_1 = molden_methods.molden_file_to_libint_shell(params['molden_file_1'], params['is_spherical'])
2374+
shell_2, l_vals_2 = molden_methods.molden_file_to_libint_shell(params['molden_file_2'], params['is_spherical'])
2375+
new_indices = resort_molog_eigenvectors(l_vals_1)
2376+
eig_vect_1, energies_1 = molden_methods.eigenvectors_molden(params['molden_file_1'], nbasis(shell_1), l_vals_1)
2377+
eig_vect_2, energies_2 = molden_methods.eigenvectors_molden(params['molden_file_2'], nbasis(shell_2), l_vals_2)
2378+
2379+
# The new eigenvectors
2380+
resortted_eig_vect_1 = []
2381+
resortted_eig_vect_2 = []
2382+
for j in range(len(eig_vect_1)):
2383+
# the new and sorted eigenvector
2384+
eigenvector1 = eig_vect_1[j]
2385+
eigenvector1 = eigenvector1[new_indices]
2386+
eigenvector2 = eig_vect_2[j]
2387+
eigenvector2 = eigenvector2[new_indices]
2388+
# append it to the eigenvectors list
2389+
resortted_eig_vect_1.append(eigenvector1)
2390+
resortted_eig_vect_2.append(eigenvector2)
2391+
resortted_eig_vect_1 = np.array(resortted_eig_vect_1)
2392+
resortted_eig_vect_2 = np.array(resortted_eig_vect_2)
2393+
ao_matrix = compute_overlaps(shell_1, shell_2, params['nprocs'])
2394+
ao_matrix = data_conv.MATRIX2nparray(ao_matrix)
2395+
mo_overlap_matrix = np.linalg.multi_dot([resortted_eig_vect_1, ao_matrix,
2396+
resortted_eig_vect_2.T])
2397+
return mo_overlap_matrix
2398+

src/libra_py/workflows/nbra/mapping.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ def ovlp_arb(SD1, SD2, S, use_minimal=False, user_notation=0):
344344
sd2 = sd2indx(SD2, nbasis, False, user_notation)
345345

346346
# Compute the phase using the original determinants in the internal notation
347-
phase = (-1)**(num_of_perms(sd1) + num_of_perms(sd2))
347+
#phase = (-1)**(count_inversions(sd1) + count_inversions(sd2)) #(num_of_perms(sd1) + num_of_perms(sd2))
348348

349349
# Now reduce the determinants for faster calculations
350350
if use_minimal:
@@ -354,19 +354,32 @@ def ovlp_arb(SD1, SD2, S, use_minimal=False, user_notation=0):
354354
sd1 = sd2indx(SD1, nbasis, False, user_notation)
355355
sd2 = sd2indx(SD2, nbasis, False, user_notation)
356356

357+
# What about beta indices?! We should add `nbasis/2` to them. This is added on 7/22/2025
358+
# With this, we don't need to worry about alpha-beta excitations since their corresponding elements
359+
# in the KS matrices is already zero
360+
beta_indices = np.where(np.array(SD1) < 0)
361+
sd1 = np.array(sd1)
362+
sd1[beta_indices] += int(nbasis/2)
363+
364+
beta_indices = np.where(np.array(SD2) < 0)
365+
sd2 = np.array(sd2)
366+
sd2[beta_indices] += int(nbasis/2)
367+
357368
res = 0.0 + 0j
358369
if len(sd1) > 0 and len(sd2) > 0:
359370
if len(sd1) == len(sd2):
371+
""" No need for this, the explanation are given in mapping3 module
360372
# It is a numpy translation of the code below
361373
# much faster and more accurate than MO approach
362374
# Checked with the code commented below and the result match
363375
# This will also remove the bottleneck we had before for computing
364376
# the overlap of many SDs
365377
tmp = np.sign(np.tensordot(SD1, SD2, axes=0))
366378
indices = np.where(tmp < 0)
379+
"""
367380
s = S[sd1, :][:, sd2]
368-
s[indices] = 0.0
369-
res = np.linalg.det(s) * phase
381+
# s[indices] = 0.0
382+
res = np.linalg.det(s) # * phase
370383
# # Now apply the determinant formula
371384
# s = CMATRIX(len(sd1),len(sd2))
372385
# # Forming the overlap of the SDs
@@ -421,9 +434,18 @@ def ovlp_arb_mo(SD1, SD2, S, user_notation=0):
421434
sd1 = sd2indx(SD1, nbasis, False, user_notation)
422435
sd2 = sd2indx(SD2, nbasis, False, user_notation)
423436

437+
# What about beta indices?! We should add `nbasis/2` to them. This is added on 7/22/2025
438+
beta_indices = np.where(np.array(SD1) < 0)
439+
sd1 = np.array(sd1)
440+
sd1[beta_indices] += int(nbasis/2)
441+
442+
beta_indices = np.where(np.array(SD2) < 0)
443+
sd2 = np.array(sd2)
444+
sd2[beta_indices] += int(nbasis/2)
445+
424446
res = delta(Py2Cpp_int(sd1), Py2Cpp_int(sd2))
425447

426-
phase = (-1)**(num_of_perms(sd1) + num_of_perms(sd2))
448+
#phase = (-1)**(count_inversions(sd1) + count_inversions(sd2)) #(num_of_perms(sd1) + num_of_perms(sd2))
427449

428450
s = 0.0
429451
# The SDs are coupled
@@ -445,7 +467,7 @@ def ovlp_arb_mo(SD1, SD2, S, user_notation=0):
445467
s = det(x)
446468

447469
# s = 1.0+0.0j
448-
return s * phase
470+
return s #* phase
449471

450472

451473
def ovlp_mat_arb(SD1, SD2, S, use_minimal=True, use_mo_approach=False, user_notation=0):

src/libra_py/workflows/nbra/mapping3.py

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import os, sys, math
2323
import numpy as np
24+
from .mapping import sd2indx
2425

2526
# Fisrt, we add the location of the library to test to the PYTHON path
2627
if sys.platform == "cygwin":
@@ -29,10 +30,10 @@
2930
from liblibra_core import *
3031

3132

32-
3333
def ovlp_arb(_SD1, _SD2, S, active_space=None, verbose=False):
3434
"""Compute the overlap of two generic SDs: <SD1|SD2>
35-
35+
This functions translates the explicit use of two Python `for` loops
36+
into a more efficient way using numpy
3637
Args:
3738
3839
_SD1 ( lists of ints ): first SD, such that:
@@ -41,7 +42,7 @@ def ovlp_arb(_SD1, _SD2, S, active_space=None, verbose=False):
4142
_SD2 ( lists of ints ): second SD, such that:
4243
SeeAlso: ```inp``` in the ```sd2indx(inp)``` function
4344
44-
S ( CMATRIX(N,N) ): is the matrix in the space of 1-el orbital
45+
S ( numpy array(N,N) ): is the matrix in the space of 1-el orbital
4546
4647
active_space ( list of ints ): indices of the orbitals (starting from 1) to
4748
include into consideration. If None - all the orbitals will be used [ default: None ]
@@ -77,7 +78,95 @@ def ovlp_arb(_SD1, _SD2, S, active_space=None, verbose=False):
7778
# Apply the determinant formula
7879
det_size = len(SD1)
7980
s = np.zeros( (det_size, det_size), dtype=np.float64);
81+
""" Commented on 7/22/2025
82+
# ============================== The numpy version of the above double-for-loops
83+
# Find the tensor product of the SD1 and SD2
84+
SD_tensor_product = np.tensordot(SD1, SD2, axes=0)
85+
# Next, find the sign of these elementwise multiplications
86+
SD_tensor_product_sign = np.sign(SD_tensor_product)
87+
# Now, find where we have alpha-beta indices so that the
88+
negative_sign_indices = np.where(SD_tensor_product_sign < 0)
89+
s[negative_sign_indices] = 0
90+
"""
91+
# Let's build the matrix related to sd1 and sd2 from the KS orbitals
92+
# For this, we reuire to turn each element into matrix indices
93+
sd1 = sd2indx(SD1, nbasis, False, 0) # adopted from the `mapping` module
94+
sd2 = sd2indx(SD2, nbasis, False, 0)
95+
# What about beta indices?! We should add `nbasis/2` to them. This is added on 7/22/2025
96+
# With this simple approach, there is no need for tensor product or the `if else` clause brought
97+
# previously since the elements corresponding to the alpha-beta orbitals are already
98+
# zero in the two-spinor format of the KS matrices :)
99+
beta_indices = np.where(np.array(SD1) < 0)
100+
sd1 = np.array(sd1)
101+
sd1[beta_indices] += int(nbasis/2)
102+
103+
beta_indices = np.where(np.array(SD2) < 0)
104+
sd2 = np.array(sd2)
105+
sd2[beta_indices] += int(nbasis/2)
106+
107+
108+
# Making the numpy array
109+
s = S[sd1,:][:,sd2]
80110

111+
if verbose==True:
112+
print(s)
113+
res = np.linalg.det(s) #* phase
114+
115+
return res
116+
117+
118+
119+
def ovlp_arb_2(_SD1, _SD2, S, active_space=None, verbose=False):
120+
"""Compute the overlap of two generic SDs: <SD1|SD2>
121+
The difference of this function with the above one is that it uses explicitly
122+
two Pythonic `for` loops which are not optimized for very large sets
123+
124+
Args:
125+
126+
_SD1 ( lists of ints ): first SD, such that:
127+
SeeAlso: ```inp``` in the ```sd2indx(inp)``` function
128+
129+
_SD2 ( lists of ints ): second SD, such that:
130+
SeeAlso: ```inp``` in the ```sd2indx(inp)``` function
131+
132+
S ( numpy array(N,N) ): is the matrix in the space of 1-el orbital
133+
134+
active_space ( list of ints ): indices of the orbitals (starting from 1) to
135+
include into consideration. If None - all the orbitals will be used [ default: None ]
136+
137+
verbose ( Bool ): whether to print some extra info [ default: False]
138+
139+
Returns:
140+
complex: the overlap of the two determinants <SD1|SD2>
141+
142+
"""
143+
144+
nbasis = S.shape[0] #num_of_rows
145+
146+
SD1, SD2 = [], []
147+
148+
if active_space == None:
149+
SD1, SD2 = _SD1, _SD2
150+
else:
151+
for a in _SD1:
152+
if abs(a) in active_space:
153+
SD1.append(a)
154+
for a in _SD2:
155+
if abs(a) in active_space:
156+
SD2.append(a)
157+
158+
res = 0.0 + 0j
159+
if len(SD1) != len(SD2):
160+
print("Trying to compute an overlap of the SDs with different number of electrons")
161+
print("Exiting...")
162+
sys.exit(0)
163+
164+
165+
# Apply the determinant formula
166+
det_size = len(SD1)
167+
s = np.zeros( (det_size, det_size), dtype=np.float64);
168+
# ============================== The explicit version with for loops
169+
# Note: It seems that this explicit vesion doesn't consider the unrestricted spin calculations
81170
# Forming the overlap of the SDs
82171
for indx_I, I in enumerate(SD1):
83172
for indx_J, J in enumerate(SD2):

0 commit comments

Comments
 (0)