From 3036f21f7cd0cd1ece2aaa8e6c8c5249ac6e601d Mon Sep 17 00:00:00 2001 From: MohammadShakiba Date: Mon, 13 May 2024 10:05:45 -0400 Subject: [PATCH 01/48] minor addition and fixes --- src/libra_py/packages/cp2k/methods.py | 2 +- src/libra_py/workflows/nbra/ml_map.py | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/libra_py/packages/cp2k/methods.py b/src/libra_py/packages/cp2k/methods.py index 8feec7285..7ed7ccdf7 100755 --- a/src/libra_py/packages/cp2k/methods.py +++ b/src/libra_py/packages/cp2k/methods.py @@ -320,7 +320,7 @@ def read_cp2k_tddfpt_log_file( params ): ci_coefficient = float( tmp_splitted_line[4] ) if ci_coefficient**2 > tolerance: - if int(tmp_splitted_line[0])>=params["lowest_orbital"] and int(tmp_splitted_line[1])<=params["highest_orbital"]: + if int(tmp_splitted_line[0])>params["lowest_orbital"] and int(tmp_splitted_line[1]) for # eigenvectors and that property will be saved. If we're about to save the eigenvectors, it will occupy # a lot of disk space - np.save(f"../error_data/E_ref_{step}.npy", eigenvalues_ref[lowest_orbital-1:highest_orbital]) - ml_ref_overlap = compute_mo_overlaps(params, eigenvectors_ref, eigenvectors, step, step)[lowest_orbital-1:highest_orbital,:][:,lowest_orbital-1:highest_orbital] + if params["save_ref_eigenvalues"] or params["save_ref_eigenvectors"]: + if not os.path.exists(f"{params['path_to_save_ref_mos']}"): + os.system(f"mkdir {params['path_to_save_ref_mos']}") + if params["save_ref_eigenvalues"]: + np.save(f"{params['path_to_save_ref_mos']}/E_ref_{step}.npy", eigenvalues_ref) # [lowest_orbital-1:highest_orbital]) + if params["save_ref_eigenvectors"]: + np.save(f"{params['path_to_save_ref_mos']}/mos_ref_{step}.npy", eigenvectors_ref) #[lowest_orbital-1:highest_orbital,:][:,lowest_orbital-1:highest_orbital]) + ml_ref_overlap = compute_mo_overlaps(params, eigenvectors_ref, eigenvectors, step, step) #[lowest_orbital-1:highest_orbital,:][:,lowest_orbital-1:highest_orbital] np.save(f"../error_data/epsilon_{step}.npy", np.diag(ml_ref_overlap)) # Only the diagonal elements # The other error measurement is the absolute value of the Hamiltonian matrices difference ham_diff = np.abs(ks_ham_mat-ks_ham_mat_ref) @@ -695,11 +701,20 @@ def compute_properties(params, models, input_scalers, output_scalers): os.system(F"sed -i '/SCF_GUESS/c\ SCF_GUESS RESTART' input_{tmp_prefix}_{step}.inp") # Now let's run the calculations --- We only need the Total energy so I can simply grep it to # not to use a lot of disk space but for now, I keep it this way - os.system(F"{data_gen_params_1['reference_mpi_exe']} -np {params['nprocs']} {data_gen_params_1['reference_software_exe']} -i input_{tmp_prefix}_{step}.inp -o output_{tmp_prefix}_{step}.log") + # ================= Only for ML assessment project I grep the output files so that the log file size is small + #os.system(F"{data_gen_params_1['reference_mpi_exe']} -np {params['nprocs']} {data_gen_params_1['reference_software_exe']} -i input_{tmp_prefix}_{step}.inp -o output_{tmp_prefix}_{step}.log") + os.system(F"{data_gen_params_1['reference_mpi_exe']} -np {params['nprocs']} {data_gen_params_1['reference_software_exe']} -i input_{tmp_prefix}_{step}.inp | grep -A 30 'SCF WAVEFUNCTION OPTIMIZATION' > output_{tmp_prefix}_{step}.out") + # ================= These files can be large... we can setup a flag to remove them but for ML assessment I remove them + os.system(f"rm {output_name}") # The algorithm for running the calculations #if params["compute_total_energy"]: # we have to make a cp2k input file based on the reference input # and then run it. Then since the files are large we need to remove them + os.system("rm *.log *.npy *.wfn* *.inp *.xyz") + os.system("mkdir ../ml_total_energy") + os.system("mv output*.out ../ml_total_energy/.") + os.system("rm *.out") + #os.chdir("../") #os.system(f"rm -rf tmp_guess_ham_{params['job']}") def distribute_jobs(params): From 14cfbc74e542a24fce8bea8f22a363c275f75319 Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Mon, 20 May 2024 20:48:32 -0400 Subject: [PATCH 02/48] Compute the nonclassical term in QTSH --- src/dyn/Dynamics.cpp | 12 ++++---- src/dyn/dyn_hop_proposal.cpp | 5 +++- src/dyn/dyn_methods.h | 2 +- src/dyn/dyn_methods_qtsh.cpp | 55 ++++++++++++++++++++++++++++++++++++ src/dyn/libdyn.cpp | 3 ++ 5 files changed, 70 insertions(+), 7 deletions(-) diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index af3906a83..28e0b2919 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -1351,6 +1351,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, update_forces(prms, dyn_var, ham); + if(prms.tsh_method == 9){ update_forces_qtsh(dyn_var, ham, prms); } if(prms.decoherence_algo == 6 and prms.use_xf_force == 1){ update_forces_xf(dyn_var, ham, ham_aux); @@ -1486,18 +1487,19 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, // Adiabatic dynamics if(prms.tsh_method==-1){ ;; } - // FSSH, GFSH, MSSH, LZ, ZN, DISH, MASH, FSSH2, FSSH3 + // FSSH, GFSH, MSSH, LZ, ZN, DISH, MASH, FSSH2, FSSH3, QTSH else if(prms.tsh_method == 0 || prms.tsh_method == 1 || prms.tsh_method == 2 || prms.tsh_method == 3 || prms.tsh_method == 4 || prms.tsh_method == 5 || prms.tsh_method == 6 || prms.tsh_method == 7 - || prms.tsh_method == 8 ){ + || prms.tsh_method == 8 || prms.tsh_method == 9 ){ vector old_states(dyn_var.act_states); //========================== Hop proposal and acceptance ================================ - // FSSH (0), GFSH (1), MSSH (2), LZ(3), ZN (4), MASH(6), FSSH2(7), FSSH3(8) + // FSSH (0), GFSH (1), MSSH (2), LZ(3), ZN (4), MASH(6), FSSH2(7), FSSH3(8), QTSH(9) if(prms.tsh_method == 0 || prms.tsh_method == 1 || prms.tsh_method == 2 || prms.tsh_method == 3 - || prms.tsh_method == 4 || prms.tsh_method == 6 || prms.tsh_method == 7 || prms.tsh_method == 8){ + || prms.tsh_method == 4 || prms.tsh_method == 6 || prms.tsh_method == 7 || prms.tsh_method == 8 + || prms.tsh_method == 9){ /// Compute hop proposal probabilities from the active state of each trajectory to all other states /// of that trajectory @@ -1564,7 +1566,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, // Update vib Hamiltonian to reflect the change of the momentum update_Hamiltonian_variables(prms, dyn_var, ham, ham_aux, py_funct, params, 1); - }// tsh_method == 0, 1, 2, 3, 4, 5, 6, 7, 8 + }// tsh_method == 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 else{ cout<<"tsh_method == "<& old_states, vector& proposed_st MATRIX compute_dkinemat(dyn_variables& dyn_var, nHamiltonian& ham); - +void update_forces_qtsh(dyn_variables& dyn_var, nHamiltonian& ham, dyn_control_params& prms); }// namespace libdyn diff --git a/src/dyn/dyn_methods_qtsh.cpp b/src/dyn/dyn_methods_qtsh.cpp index ba31966ee..bc65bf422 100644 --- a/src/dyn/dyn_methods_qtsh.cpp +++ b/src/dyn/dyn_methods_qtsh.cpp @@ -70,5 +70,60 @@ MATRIX compute_dkinemat(dyn_variables& dyn_var, nHamiltonian& ham){ } +void update_forces_qtsh(dyn_variables& dyn_var, nHamiltonian& ham, dyn_control_params& prms){ + /** + Add the nonclassical contribution from the kinematic mometum in QTSH + */ + + int ntraj = dyn_var.ntraj; + int ndof = dyn_var.ndof; + int nst = dyn_var.nadi; + MATRIX& invM = *dyn_var.iM; + + CMATRIX C(nst, 1); + CMATRIX Coeff(nst, ntraj); + + MATRIX f_nc(ndof,ntraj); + + // termporaries for d1ham without diagonal elements and f_nc + vector temp; + for(int idof=0; idofget_ham_adi(); + + CMATRIX d1ham_adi(nst, nst); + CMATRIX dc1_adi(nst, nst); + CMATRIX tmp(nst, nst); + for(int idof=0; idofget_dc1_adi(idof); + tmp = dc1_adi.H() * ham_adi; + tmp = tmp + tmp.H(); + + d1ham_adi = ham.children[traj]->get_d1ham_adi(idof); + temp[idof].dot_product(hollow, d1ham_adi - tmp ); + } + + for(int idof=0; idof Date: Thu, 23 May 2024 04:46:13 -0400 Subject: [PATCH 03/48] Set init for QTSH and add the decoherence factor calculation --- src/dyn/Dynamics.cpp | 2 +- src/dyn/dyn_hop_proposal.cpp | 5 ++- src/dyn/dyn_methods.h | 1 + src/dyn/dyn_methods_qtsh.cpp | 59 +++++++++++++++++++++++----- src/dyn/dyn_variables.cpp | 44 +++++++++++++++++++++ src/dyn/dyn_variables.h | 31 +++++++++++++++ src/dyn/libdyn.cpp | 7 +++- src/libra_py/dynamics/tsh/compute.py | 2 + 8 files changed, 138 insertions(+), 13 deletions(-) diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index 28e0b2919..79e941b7b 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -1348,7 +1348,6 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, // For now, this function also accounts for the kinetic energy adjustments to reflect the adiabatic evolution if(prms.thermally_corrected_nbra==1){ apply_thermal_correction(dyn_var, ham, ham_aux, old_states, prms, rnd); } - update_forces(prms, dyn_var, ham); if(prms.tsh_method == 9){ update_forces_qtsh(dyn_var, ham, prms); } @@ -1388,6 +1387,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, //ham_aux.copy_content(ham); update_Hamiltonian_variables(prms, dyn_var, ham, ham_aux, py_funct, params, 1); + if(prms.tsh_method == 9){ update_deco_factor_qtsh(dyn_var, ham, prms); } //============== Begin the TSH part =================== diff --git a/src/dyn/dyn_hop_proposal.cpp b/src/dyn/dyn_hop_proposal.cpp index c630d3af4..b2454cd18 100755 --- a/src/dyn/dyn_hop_proposal.cpp +++ b/src/dyn/dyn_hop_proposal.cpp @@ -1206,7 +1206,10 @@ nHamiltonian& ham, nHamiltonian& ham_prev){ g[traj] = hopping_probabilities_fssh3(prms, dm, dm_prev_trans, dyn_var.act_states[traj], dyn_var.fssh3_errors[traj]); } else if(prms.tsh_method == 9){ // QTSH - g[traj] = hopping_probabilities_fssh(prms, dm, Hvib, dyn_var.act_states[traj]); + CMATRIX dm_adj(nst, nst); + dm_adj.dot_product(dm, CMATRIX(*dyn_var.qtsh_deco_factor)); + g[traj] = hopping_probabilities_fssh(prms, dm_adj, Hvib, dyn_var.act_states[traj]); + //g[traj] = hopping_probabilities_fssh(prms, dm, Hvib, dyn_var.act_states[traj]); } else{ diff --git a/src/dyn/dyn_methods.h b/src/dyn/dyn_methods.h index e16187600..fcd1ca98d 100755 --- a/src/dyn/dyn_methods.h +++ b/src/dyn/dyn_methods.h @@ -53,6 +53,7 @@ void dish_project_out_collapse(vector& old_states, vector& proposed_st MATRIX compute_dkinemat(dyn_variables& dyn_var, nHamiltonian& ham); void update_forces_qtsh(dyn_variables& dyn_var, nHamiltonian& ham, dyn_control_params& prms); +void update_deco_factor_qtsh(dyn_variables& dyn_var, nHamiltonian& ham, dyn_control_params& prms); }// namespace libdyn diff --git a/src/dyn/dyn_methods_qtsh.cpp b/src/dyn/dyn_methods_qtsh.cpp index bc65bf422..9ac3b8d97 100644 --- a/src/dyn/dyn_methods_qtsh.cpp +++ b/src/dyn/dyn_methods_qtsh.cpp @@ -85,11 +85,6 @@ void update_forces_qtsh(dyn_variables& dyn_var, nHamiltonian& ham, dyn_control_p MATRIX f_nc(ndof,ntraj); - // termporaries for d1ham without diagonal elements and f_nc - vector temp; - for(int idof=0; idofget_dc1_adi(idof); tmp = dc1_adi.H() * ham_adi; tmp = tmp + tmp.H(); d1ham_adi = ham.children[traj]->get_d1ham_adi(idof); - temp[idof].dot_product(hollow, d1ham_adi - tmp ); - } - - for(int idof=0; idofget_ham_adi(); + + for(int i=0; iadd(i,j, omega*dt); + } + } + } + + // Compute the global decoherence factor + MATRIX avg_ph(nadi, nadi); + MATRIX avg_ph2(nadi, nadi); + MATRIX temp(nadi, nadi); + + for(int traj=0; trajset(i,j, -0.5*(avg_ph2.get(i,j) - pow(avg_ph.get(i,j), 2.0)) ); + } + } + +} + }// libdyn }// liblibra diff --git a/src/dyn/dyn_variables.cpp b/src/dyn/dyn_variables.cpp index 3cc3cbff7..a5be5a3eb 100755 --- a/src/dyn/dyn_variables.cpp +++ b/src/dyn/dyn_variables.cpp @@ -109,6 +109,9 @@ dyn_variables::dyn_variables(int _ndia, int _nadi, int _ndof, int _ntraj){ ///================= MQCXF ==================== mqcxf_vars_status = 0; + + ///================= QTSH ==================== + qtsh_vars_status = 0; } @@ -281,6 +284,23 @@ void dyn_variables::allocate_tcnbra(){ }// allocate_tcnbra + +void dyn_variables::allocate_qtsh(){ + + if(qtsh_vars_status==0){ + + qtsh_proxy_phase = vector(ntraj); + + for(int itraj=0; itraj tcnbra_ekin; + + + ///================= For QTSH =================== + /** + Status of the QTSH vars + + 0 - not allocated; + 1 - allocated + */ + int qtsh_vars_status; + + + /** + Proxy phase in QTSH + + Options: + vector + */ + vector qtsh_proxy_phase; + + + /** + Global decoherence factor in QTSH + + Options: + MATRIX(nadi, nadi) + */ + MATRIX* qtsh_deco_factor; ///================= Misc =================== @@ -500,6 +528,7 @@ class dyn_variables{ void allocate_shxf(); void allocate_tcnbra(); void allocate_mqcxf(); + void allocate_qtsh(); dyn_variables(int _ndia, int _nadi, int _ndof, int _ntraj); dyn_variables(const dyn_variables& x); @@ -533,6 +562,8 @@ class dyn_variables{ MATRIX get_coords_aux(int i){ return *q_aux[i]; } MATRIX get_momenta_aux(int i){ return *p_aux[i]; } MATRIX get_nab_phase(int i){ return *nab_phase[i]; } + MATRIX get_qtsh_proxy_phase(int i){ return *qtsh_proxy_phase[i]; } + MATRIX get_qtsh_deco_factor(){ return *qtsh_deco_factor; } void get_current_timestep(bp::dict params){ std::string key; diff --git a/src/dyn/libdyn.cpp b/src/dyn/libdyn.cpp index 7035bf85a..a9d348a19 100755 --- a/src/dyn/libdyn.cpp +++ b/src/dyn/libdyn.cpp @@ -175,6 +175,8 @@ void export_dyn_variables_objects(){ MATRIX (dyn_variables::*expt_get_coords_aux)(int i) = &dyn_variables::get_coords_aux; MATRIX (dyn_variables::*expt_get_momenta_aux)(int i) = &dyn_variables::get_momenta_aux; MATRIX (dyn_variables::*expt_get_nab_phase)(int i) = &dyn_variables::get_nab_phase; + + MATRIX (dyn_variables::*expt_get_qtsh_proxy_phase)(int i) = &dyn_variables::get_qtsh_proxy_phase; // Arbitrary wavefunction void (dyn_variables::*expt_set_parameters_v1)(boost::python::dict params) = &dyn_variables::set_parameters; @@ -237,6 +239,7 @@ void export_dyn_variables_objects(){ .def_readwrite("mqcxf_vars_status", &dyn_variables::mqcxf_vars_status) .def_readwrite("tcnbra_thermostats", &dyn_variables::tcnbra_thermostats) .def_readwrite("tcnbra_ekin", &dyn_variables::tcnbra_ekin) + .def_readwrite("qtsh_vars_status", &dyn_variables::qtsh_vars_status) .def("set_parameters", expt_set_parameters_v1) @@ -249,6 +252,7 @@ void export_dyn_variables_objects(){ .def("allocate_shxf", &dyn_variables::allocate_shxf) .def("allocate_tcnbra", &dyn_variables::allocate_tcnbra) .def("allocate_mqcxf", &dyn_variables::allocate_mqcxf) + .def("allocate_qtsh", &dyn_variables::allocate_qtsh) .def("set_q", &dyn_variables::set_q) .def("set_p", &dyn_variables::set_p) @@ -273,6 +277,7 @@ void export_dyn_variables_objects(){ .def("get_coords_aux", expt_get_coords_aux) .def("get_momenta_aux", expt_get_momenta_aux) .def("get_nab_phase", expt_get_nab_phase) + .def("get_qtsh_proxy_phase", expt_get_qtsh_proxy_phase) .def("init_nuclear_dyn_var", &dyn_variables::init_nuclear_dyn_var) .def("compute_average_kinetic_energy", expt_compute_average_kinetic_energy_v1) @@ -496,7 +501,7 @@ void export_dyn_decoherence_objects(){ MATRIX (*expt_compute_dkinemat_v1) (dyn_variables& dyn_var, nHamiltonian& ham) = &compute_dkinemat; def("compute_dkinemat", expt_compute_dkinemat_v1); - + void (*expt_update_forces_qtsh_v1) (dyn_variables& dyn_var, nHamiltonian& ham, dyn_control_params& prms) = &update_forces_qtsh; def("update_forces_qtsh", expt_update_forces_qtsh_v1); diff --git a/src/libra_py/dynamics/tsh/compute.py b/src/libra_py/dynamics/tsh/compute.py index c70b373c0..a69c136c6 100755 --- a/src/libra_py/dynamics/tsh/compute.py +++ b/src/libra_py/dynamics/tsh/compute.py @@ -695,6 +695,8 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): if tsh_method==7 or tsh_method==8: # FSSH2 or FSSH3 dyn_var.allocate_fssh2() dyn_var.save_curr_dm_into_prev() + if tsh_method==9: # QTSH + dyn_var.allocate_qtsh() if thermally_corrected_nbra==1: dyn_var.allocate_tcnbra() From cab55481015e50b74d6f48a51c9dbeda0a918a18 Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Thu, 23 May 2024 10:40:22 -0400 Subject: [PATCH 04/48] Revise the global decoherence factor in QTSH --- src/dyn/dyn_control_params.cpp | 9 +++++++++ src/dyn/dyn_control_params.h | 3 +++ src/dyn/dyn_hop_proposal.cpp | 12 ++++++++---- src/dyn/dyn_methods_qtsh.cpp | 24 ++++++++---------------- src/dyn/libdyn.cpp | 4 ++++ src/libra_py/dynamics/tsh/compute.py | 3 +++ src/libra_py/dynamics/tsh/save.py | 10 ++++++++++ 7 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/dyn/dyn_control_params.cpp b/src/dyn/dyn_control_params.cpp index ab62344f7..361f6bdea 100755 --- a/src/dyn/dyn_control_params.cpp +++ b/src/dyn/dyn_control_params.cpp @@ -77,6 +77,9 @@ dyn_control_params::dyn_control_params(){ fssh3_dt = 0.001; fssh3_max_steps = 1000; fssh3_err_tol = 1e-7; + + ///================= QTSH specific ==================== + use_qtsh_deco_factor = 0; ///================= Decoherence options ========================================= decoherence_algo = -1; @@ -177,6 +180,9 @@ dyn_control_params::dyn_control_params(const dyn_control_params& x){ fssh3_dt = x.fssh3_dt; fssh3_max_steps = x.fssh3_max_steps; fssh3_err_tol = x.fssh3_err_tol; + + ///================= QTSH specific ==================== + use_qtsh_deco_factor = x.use_qtsh_deco_factor; ///================= Decoherence options ========================================= decoherence_algo = x.decoherence_algo; @@ -368,6 +374,9 @@ void dyn_control_params::set_parameters(bp::dict params){ else if(key=="fssh3_dt"){ fssh3_dt = bp::extract(params.values()[i]); } else if(key=="fssh3_max_steps"){ fssh3_max_steps = bp::extract(params.values()[i]); } else if(key=="fssh3_err_tol"){ fssh3_err_tol = bp::extract(params.values()[i]); } + + ///================= QTSH specific ==================== + else if(key=="use_qtsh_deco_factor"){ use_qtsh_deco_factor = bp::extract(params.values()[i]); } ///================= Decoherence options ========================================= else if(key=="decoherence_algo"){ decoherence_algo = bp::extract(params.values()[i]); } diff --git a/src/dyn/dyn_control_params.h b/src/dyn/dyn_control_params.h index 8e87eb973..44a8762a9 100755 --- a/src/dyn/dyn_control_params.h +++ b/src/dyn/dyn_control_params.h @@ -456,6 +456,9 @@ class dyn_control_params{ double fssh3_err_tol; + //=========== QTSH options ========== + int use_qtsh_deco_factor; + ///=============================================================================== ///================= Decoherence options ========================================= ///=============================================================================== diff --git a/src/dyn/dyn_hop_proposal.cpp b/src/dyn/dyn_hop_proposal.cpp index b2454cd18..c87cfad3a 100755 --- a/src/dyn/dyn_hop_proposal.cpp +++ b/src/dyn/dyn_hop_proposal.cpp @@ -1206,10 +1206,14 @@ nHamiltonian& ham, nHamiltonian& ham_prev){ g[traj] = hopping_probabilities_fssh3(prms, dm, dm_prev_trans, dyn_var.act_states[traj], dyn_var.fssh3_errors[traj]); } else if(prms.tsh_method == 9){ // QTSH - CMATRIX dm_adj(nst, nst); - dm_adj.dot_product(dm, CMATRIX(*dyn_var.qtsh_deco_factor)); - g[traj] = hopping_probabilities_fssh(prms, dm_adj, Hvib, dyn_var.act_states[traj]); - //g[traj] = hopping_probabilities_fssh(prms, dm, Hvib, dyn_var.act_states[traj]); + if(prms.use_qtsh_deco_factor==1){ + CMATRIX dm_adj(nst, nst); + dm_adj.dot_product(dm, CMATRIX(*dyn_var.qtsh_deco_factor)); + g[traj] = hopping_probabilities_fssh(prms, dm_adj, Hvib, dyn_var.act_states[traj]); + } + else{ + g[traj] = hopping_probabilities_fssh(prms, dm, Hvib, dyn_var.act_states[traj]); + } } else{ diff --git a/src/dyn/dyn_methods_qtsh.cpp b/src/dyn/dyn_methods_qtsh.cpp index 9ac3b8d97..e5e41c2a7 100644 --- a/src/dyn/dyn_methods_qtsh.cpp +++ b/src/dyn/dyn_methods_qtsh.cpp @@ -85,12 +85,6 @@ void update_forces_qtsh(dyn_variables& dyn_var, nHamiltonian& ham, dyn_control_p MATRIX f_nc(ndof,ntraj); - CMATRIX hollow(nst, nst); - hollow.set(-1, -1, 1.0); - for(int i=0; iget_ham_adi(); - CMATRIX d1ham_adi(nst, nst); CMATRIX dc1_adi(nst, nst); - CMATRIX tmp(nst, nst); - CMATRIX F_off(nst, nst); for(int idof=0; idofget_dc1_adi(idof); - tmp = dc1_adi.H() * ham_adi; - tmp = tmp + tmp.H(); - - d1ham_adi = ham.children[traj]->get_d1ham_adi(idof); - F_off.dot_product(hollow, d1ham_adi - tmp ); - f_nc.set(idof, traj, -(C.H() * F_off * C).get(0,0).real() ); + for(int i=0; iget(i,j).real() ); + } + } } }//traj @@ -158,7 +150,7 @@ void update_deco_factor_qtsh(dyn_variables& dyn_var, nHamiltonian& ham, dyn_cont for(int i=0; iset(i,j, -0.5*(avg_ph2.get(i,j) - pow(avg_ph.get(i,j), 2.0)) ); + dyn_var.qtsh_deco_factor->set(i,j, exp( -0.5*(avg_ph2.get(i,j) - pow(avg_ph.get(i,j), 2.0)) ) ); } } diff --git a/src/dyn/libdyn.cpp b/src/dyn/libdyn.cpp index a9d348a19..2e4516756 100755 --- a/src/dyn/libdyn.cpp +++ b/src/dyn/libdyn.cpp @@ -100,6 +100,9 @@ void export_dyn_control_params_objects(){ .def_readwrite("fssh3_max_steps", &dyn_control_params::fssh3_max_steps) .def_readwrite("fssh3_err_tol", &dyn_control_params::fssh3_err_tol) + ///================= FSSH3 specific ==================== + .def_readwrite("use_qtsh_deco_factor", &dyn_control_params::use_qtsh_deco_factor) + ///================= Decoherence options ========================================= .def_readwrite("decoherence_algo", &dyn_control_params::decoherence_algo) .def_readwrite("sdm_norm_tolerance", &dyn_control_params::sdm_norm_tolerance) @@ -277,6 +280,7 @@ void export_dyn_variables_objects(){ .def("get_coords_aux", expt_get_coords_aux) .def("get_momenta_aux", expt_get_momenta_aux) .def("get_nab_phase", expt_get_nab_phase) + .def("get_qtsh_deco_factor", &dyn_variables::get_qtsh_deco_factor) .def("get_qtsh_proxy_phase", expt_get_qtsh_proxy_phase) .def("init_nuclear_dyn_var", &dyn_variables::init_nuclear_dyn_var) diff --git a/src/libra_py/dynamics/tsh/compute.py b/src/libra_py/dynamics/tsh/compute.py index a69c136c6..fe104a8ee 100755 --- a/src/libra_py/dynamics/tsh/compute.py +++ b/src/libra_py/dynamics/tsh/compute.py @@ -566,6 +566,9 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): default_params.update( {"fssh3_size_option":1, "fssh3_approach_option":0, "fssh3_decomp_option":3, "fssh3_dt":0.001, "fssh3_max_steps":1000, "fssh3_err_tol":1e-7 } ) + + #================= QTSH specific ==================== + default_params.update( {"use_qtsh_deco_factor":0 } ) #================= Decoherence options ========================================= default_params.update( { "decoherence_algo":-1, "sdm_norm_tolerance":0.0, diff --git a/src/libra_py/dynamics/tsh/save.py b/src/libra_py/dynamics/tsh/save.py index ac9d350e5..8e09260ad 100755 --- a/src/libra_py/dynamics/tsh/save.py +++ b/src/libra_py/dynamics/tsh/save.py @@ -201,6 +201,10 @@ def init_tsh_data(saver, output_level, _nsteps, _ntraj, _ndof, _nadi, _ndia): # Trajectory-resolved decoherence forces based on XF if "f_xf" in saver.keywords: # and "f_xf" in saver.np_data.keys(): saver.add_dataset("f_xf", (_nsteps, _ntraj, _ndof), "R") + + # Global decoherence factor in QTSH + if "qtsh_deco_factor" in saver.keywords: # and "qtsh_deco_factor" in saver.np_data.keys(): + saver.add_dataset("qtsh_deco_factor", (_nsteps, _nadi, _nadi), "R") if output_level>=4: @@ -668,6 +672,12 @@ def save_hdf5_3D_new(saver, i, dyn_var, txt_type=0): if "f_xf" in saver.keywords and "f_xf" in saver.np_data.keys(): f_xf = dyn_var.get_f_xf() saver.save_matrix(t, "f_xf", f_xf.T()) + + # Global decoherence factor in QTSH + # Format: saver.add_dataset("qtsh_deco_factor", (_nsteps, _nadi, _nadi), "R") + if "qtsh_deco_factor" in saver.keywords and "qtsh_deco_factor" in saver.np_data.keys(): + qtsh_deco_factor = dyn_var.get_qtsh_deco_factor() + saver.save_matrix(t, "qtsh_deco_factor", qtsh_deco_factor) From 273b6ef9c9dd327f7cf8e9162a46bc34a0ce4a1d Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Mon, 27 May 2024 20:10:44 -0400 Subject: [PATCH 05/48] Update the QTSH force in the level of Hamiltonian class --- src/dyn/Dynamics.cpp | 43 +- src/dyn/Energy_and_Forces.cpp | 36 +- src/dyn/dyn_control_params.cpp | 3 + src/dyn/dyn_control_params.h | 6 + src/dyn/dyn_ham.cpp | 7 +- src/dyn/dyn_hop_proposal.cpp | 12 +- src/dyn/dyn_variables.cpp | 6 + src/dyn/dyn_variables.h | 23 + src/dyn/dyn_variables_nuclear.cpp | 22 + src/dyn/libdyn.cpp | 7 + src/libra_py/dynamics/tsh/compute.py | 7 +- src/libra_py/dynamics/tsh/save.py | 31 +- src/nhamiltonian/libnhamiltonian.cpp | 24 + src/nhamiltonian/nHamiltonian.h | 18 + .../nHamiltonian_compute_QTSH_forces.cpp | 411 ++++++++++++++++++ 15 files changed, 618 insertions(+), 38 deletions(-) create mode 100644 src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index 79e941b7b..524efc897 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -1214,6 +1214,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, int nadi = dyn_var.nadi; int ndia = dyn_var.ndia; + if(prms.rep_tdse==0 || prms.rep_tdse==2 ){ nst = ndia; } else if(prms.rep_tdse==1 || prms.rep_tdse==3 ){ nst = nadi; } @@ -1267,6 +1268,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, } *dyn_var.p = *dyn_var.p + 0.5 * prms.dt * (*dyn_var.f); + if(prms.use_qtsh==1){ *dyn_var.qtsh_p_k = *dyn_var.p + 0.5 * prms.dt * (*dyn_var.qtsh_f_nc); } // Kinetic constraint for(cdof = 0; cdof < prms.constrained_dofs.size(); cdof++){ @@ -1280,7 +1282,10 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, for(traj=0; trajadd(dof, traj, invM.get(dof,0) * dyn_var.p->get(dof,traj) * prms.dt ); - + + if(prms.use_qtsh==1){ + dyn_var.q->add(dof, traj, 0.5*invM.get(dof,0) * dyn_var.qtsh_f_nc->get(dof, traj) * pow(prms.dt, 2.0) ); + } if(prms.entanglement_opt==22){ dyn_var.q->add(dof, traj, invM.get(dof,0) * gamma.get(dof,traj) * prms.dt ); } @@ -1303,7 +1308,12 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, update_proj_adi(prms, dyn_var, ham); // Recompute NAC, Hvib, etc. in response to change of p - update_Hamiltonian_variables(prms, dyn_var, ham, ham_aux, py_funct, params, 1); + if(prms.use_qtsh==0){ + update_Hamiltonian_variables(prms, dyn_var, ham, ham_aux, py_funct, params, 1); + } + else{ + update_Hamiltonian_variables(prms, dyn_var, ham, ham_aux, py_funct, params, 3); + } // Update wave packet width in XF algorithm if(prms.decoherence_algo == 5 or prms.decoherence_algo == 6){ @@ -1349,8 +1359,6 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, if(prms.thermally_corrected_nbra==1){ apply_thermal_correction(dyn_var, ham, ham_aux, old_states, prms, rnd); } update_forces(prms, dyn_var, ham); - - if(prms.tsh_method == 9){ update_forces_qtsh(dyn_var, ham, prms); } if(prms.decoherence_algo == 6 and prms.use_xf_force == 1){ update_forces_xf(dyn_var, ham, ham_aux); @@ -1368,6 +1376,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, } *dyn_var.p = *dyn_var.p + 0.5*prms.dt* (*dyn_var.f); + if(prms.use_qtsh==1){ *dyn_var.qtsh_p_k = *dyn_var.p + 0.5*prms.dt* (*dyn_var.qtsh_f_nc); } // Kinetic constraint for(cdof=0; cdof old_states(dyn_var.act_states); //========================== Hop proposal and acceptance ================================ - // FSSH (0), GFSH (1), MSSH (2), LZ(3), ZN (4), MASH(6), FSSH2(7), FSSH3(8), QTSH(9) + // FSSH (0), GFSH (1), MSSH (2), LZ(3), ZN (4), MASH(6), FSSH2(7), FSSH3(8) if(prms.tsh_method == 0 || prms.tsh_method == 1 || prms.tsh_method == 2 || prms.tsh_method == 3 - || prms.tsh_method == 4 || prms.tsh_method == 6 || prms.tsh_method == 7 || prms.tsh_method == 8 - || prms.tsh_method == 9){ + || prms.tsh_method == 4 || prms.tsh_method == 6 || prms.tsh_method == 7 || prms.tsh_method == 8){ /// Compute hop proposal probabilities from the active state of each trajectory to all other states /// of that trajectory @@ -1564,9 +1577,11 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, if(prms.thermally_corrected_nbra==1 && prms.tcnbra_do_nac_scaling==1){ remove_thermal_correction(dyn_var, ham, prms); } // Update vib Hamiltonian to reflect the change of the momentum - update_Hamiltonian_variables(prms, dyn_var, ham, ham_aux, py_funct, params, 1); - - }// tsh_method == 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + if(prms.use_qtsh!=1){ + update_Hamiltonian_variables(prms, dyn_var, ham, ham_aux, py_funct, params, 1); + } + + }// tsh_method == 0, 1, 2, 3, 4, 5, 6, 7, 8 else{ cout<<"tsh_method == "< potential_energies(dyn_control_params& prms, dyn_variables& dyn_v }// NBRA */ - if(prms.force_method==0 || prms.force_method==1){ // State-specific forces + if(prms.force_method==0 || prms.force_method==1 || prms.force_method==3){ // State-specific forces // TSH or adiabatic (including excited states) // state-specific forces @@ -326,7 +326,41 @@ void update_forces(dyn_control_params& prms, dyn_variables& dyn_vars, nHamiltoni */ } }// Ehrenfest + else if(prms.force_method==3){ // QTSH forces + // Effective force determined by the TSH procedure + vector effective_states(dyn_vars.act_states); + + if(prms.enforce_state_following==1){ // NBRA-like enforcement: adiabatic dynamics, in terms of forces + for(int itraj=0; itraj(params.values()[i]); } ///================= QTSH specific ==================== + else if(key=="use_qtsh"){ use_qtsh = bp::extract(params.values()[i]); } else if(key=="use_qtsh_deco_factor"){ use_qtsh_deco_factor = bp::extract(params.values()[i]); } ///================= Decoherence options ========================================= diff --git a/src/dyn/dyn_control_params.h b/src/dyn/dyn_control_params.h index 44a8762a9..60d4c7759 100755 --- a/src/dyn/dyn_control_params.h +++ b/src/dyn/dyn_control_params.h @@ -457,6 +457,12 @@ class dyn_control_params{ //=========== QTSH options ========== + /** + Whether to use QTSH + */ + int use_qtsh; + + int use_qtsh_deco_factor; ///=============================================================================== diff --git a/src/dyn/dyn_ham.cpp b/src/dyn/dyn_ham.cpp index f459ae738..4430e8578 100644 --- a/src/dyn/dyn_ham.cpp +++ b/src/dyn/dyn_ham.cpp @@ -39,6 +39,7 @@ void update_Hamiltonian_variables(dyn_control_params& prms, dyn_variables& dyn_v - 0: in response to changed q - 1: in response to changed p - 2: in response to changed electronic amplitudes + - 3: in response to changed p_k in QTSH The key parameters in the `prms` are: @@ -145,7 +146,7 @@ void update_Hamiltonian_variables(dyn_control_params& prms, dyn_variables& dyn_v //exit(0); - if(update_type==0 || update_type==1){ + if(update_type==0 || update_type==1 || update_type==3){ //========================== Couplings =============================== // Don't update NACs - they may have been read in step 1 @@ -161,7 +162,9 @@ void update_Hamiltonian_variables(dyn_control_params& prms, dyn_variables& dyn_v int n_active_dof = prms.quantum_dofs.size(); int ndof = dyn_var.ndof; int ntraj = dyn_var.ntraj; - + + if(update_type==3){ p = *dyn_var.qtsh_p_k; } + MATRIX p_quantum_dof(ndof, ntraj); for(auto dof: prms.quantum_dofs){ for(int itraj = 0; itraj < ntraj; itraj++){ p_quantum_dof.set(dof, itraj, p.get(dof, itraj) ); } diff --git a/src/dyn/dyn_hop_proposal.cpp b/src/dyn/dyn_hop_proposal.cpp index c87cfad3a..9fe81d83b 100755 --- a/src/dyn/dyn_hop_proposal.cpp +++ b/src/dyn/dyn_hop_proposal.cpp @@ -1205,19 +1205,9 @@ nHamiltonian& ham, nHamiltonian& ham_prev){ // However, for the GFSH option to work properly, we should not be using the transformation of the DM here g[traj] = hopping_probabilities_fssh3(prms, dm, dm_prev_trans, dyn_var.act_states[traj], dyn_var.fssh3_errors[traj]); } - else if(prms.tsh_method == 9){ // QTSH - if(prms.use_qtsh_deco_factor==1){ - CMATRIX dm_adj(nst, nst); - dm_adj.dot_product(dm, CMATRIX(*dyn_var.qtsh_deco_factor)); - g[traj] = hopping_probabilities_fssh(prms, dm_adj, Hvib, dyn_var.act_states[traj]); - } - else{ - g[traj] = hopping_probabilities_fssh(prms, dm, Hvib, dyn_var.act_states[traj]); - } - } else{ - cout<<"Error in tsh1: tsh_method can be -1, 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9. Other values are not defined\n"; + cout<<"Error in tsh1: tsh_method can be -1, 0, 1, 2, 3, 4, 5, 6, 7, or 8. Other values are not defined\n"; cout<<"Exiting...\n"; exit(0); } diff --git a/src/dyn/dyn_variables.cpp b/src/dyn/dyn_variables.cpp index a5be5a3eb..40af89e55 100755 --- a/src/dyn/dyn_variables.cpp +++ b/src/dyn/dyn_variables.cpp @@ -296,6 +296,8 @@ void dyn_variables::allocate_qtsh(){ } qtsh_deco_factor = new MATRIX(nadi, nadi); + qtsh_p_k = new MATRIX(ndof, ntraj); + qtsh_f_nc = new MATRIX(ndof, ntraj); qtsh_vars_status = 1; } @@ -445,6 +447,8 @@ dyn_variables::dyn_variables(const dyn_variables& x){ *qtsh_proxy_phase[itraj] = *x.qtsh_proxy_phase[itraj]; } *qtsh_deco_factor = *x.qtsh_deco_factor; + *qtsh_p_k = *x.qtsh_p_k; + *qtsh_f_nc = *x.qtsh_f_nc; }// if QTSH vars @@ -598,6 +602,8 @@ dyn_variables::~dyn_variables(){ qtsh_proxy_phase.clear(); delete qtsh_deco_factor; + delete qtsh_p_k; + delete qtsh_f_nc; qtsh_vars_status = 0; } diff --git a/src/dyn/dyn_variables.h b/src/dyn/dyn_variables.h index 11966efb4..26ed7410c 100755 --- a/src/dyn/dyn_variables.h +++ b/src/dyn/dyn_variables.h @@ -507,6 +507,25 @@ class dyn_variables{ MATRIX(nadi, nadi) */ MATRIX* qtsh_deco_factor; + + + /** + Kinematic momentum in QTSH + + Options: + MATRIX(ndof, ntraj) + */ + MATRIX* qtsh_p_k; + + + /** + nonclassical force in QTSH + + Options: + MATRIX(ndof, ntraj) + */ + MATRIX* qtsh_f_nc; + ///================= Misc =================== @@ -564,6 +583,8 @@ class dyn_variables{ MATRIX get_nab_phase(int i){ return *nab_phase[i]; } MATRIX get_qtsh_proxy_phase(int i){ return *qtsh_proxy_phase[i]; } MATRIX get_qtsh_deco_factor(){ return *qtsh_deco_factor; } + MATRIX get_qtsh_p_k(){ return *qtsh_p_k; } + MATRIX get_qtsh_f_nc(){ return *qtsh_f_nc; } void get_current_timestep(bp::dict params){ std::string key; @@ -585,6 +606,8 @@ class dyn_variables{ double compute_kinetic_energy(int itraj, vector& which_dofs); vector compute_kinetic_energies(); vector compute_kinetic_energies(vector& which_dofs); + double compute_average_kinetic_energy_qtsh(); + double compute_average_kinetic_energy_qtsh(vector& which_dofs); ///====================== In dyn_variables_electronic.cpp ===================== diff --git a/src/dyn/dyn_variables_nuclear.cpp b/src/dyn/dyn_variables_nuclear.cpp index db2ec21d9..7d276e0cf 100644 --- a/src/dyn/dyn_variables_nuclear.cpp +++ b/src/dyn/dyn_variables_nuclear.cpp @@ -271,6 +271,28 @@ double dyn_variables::compute_average_kinetic_energy(vector& which_dofs){ return 0.5*res / float(which_dofs.size() ); } +double dyn_variables::compute_average_kinetic_energy_qtsh(){ + double res = 0.0; + + for(int itraj = 0; itraj < ntraj; itraj++){ + for(int idof = 0; idof < ndof; idof++){ + res += qtsh_p_k->get(idof, itraj) * qtsh_p_k->get(idof, itraj) * iM->get(idof, 0); + } + } + return 0.5*res/ float(ntraj); +} + +double dyn_variables::compute_average_kinetic_energy_qtsh(vector& which_dofs){ + double res = 0.0; + + for(int itraj = 0; itraj < ntraj; itraj++){ + for(auto idof: which_dofs){ + res += qtsh_p_k->get(idof, itraj) * qtsh_p_k->get(idof, itraj) * iM->get(idof, 0); + } + } + return 0.5*res / float(which_dofs.size() ); +} + double dyn_variables::compute_kinetic_energy(int itraj){ double res = 0.0; diff --git a/src/dyn/libdyn.cpp b/src/dyn/libdyn.cpp index 2e4516756..8f30900a7 100755 --- a/src/dyn/libdyn.cpp +++ b/src/dyn/libdyn.cpp @@ -101,6 +101,7 @@ void export_dyn_control_params_objects(){ .def_readwrite("fssh3_err_tol", &dyn_control_params::fssh3_err_tol) ///================= FSSH3 specific ==================== + .def_readwrite("use_qtsh", &dyn_control_params::use_qtsh) .def_readwrite("use_qtsh_deco_factor", &dyn_control_params::use_qtsh_deco_factor) ///================= Decoherence options ========================================= @@ -215,6 +216,8 @@ void export_dyn_variables_objects(){ vector (dyn_variables::*expt_compute_kinetic_energies_v1)() = &dyn_variables::compute_kinetic_energies; vector (dyn_variables::*expt_compute_kinetic_energies_v2)(vector& which_dofs) = &dyn_variables::compute_kinetic_energies; + double (dyn_variables::*expt_compute_average_kinetic_energy_qtsh_v1)() = &dyn_variables::compute_average_kinetic_energy_qtsh; + double (dyn_variables::*expt_compute_average_kinetic_energy_qtsh_v2)(vector& which_dofs) = &dyn_variables::compute_average_kinetic_energy_qtsh; void (dyn_variables::*expt_update_active_states_v1)(int direction, int property) = &dyn_variables::update_active_states; void (dyn_variables::*expt_update_active_states_v2)() = &dyn_variables::update_active_states; @@ -282,6 +285,8 @@ void export_dyn_variables_objects(){ .def("get_nab_phase", expt_get_nab_phase) .def("get_qtsh_deco_factor", &dyn_variables::get_qtsh_deco_factor) .def("get_qtsh_proxy_phase", expt_get_qtsh_proxy_phase) + .def("get_qtsh_p_k", &dyn_variables::get_qtsh_p_k) + .def("get_qtsh_f_nc", &dyn_variables::get_qtsh_f_nc) .def("init_nuclear_dyn_var", &dyn_variables::init_nuclear_dyn_var) .def("compute_average_kinetic_energy", expt_compute_average_kinetic_energy_v1) @@ -290,6 +295,8 @@ void export_dyn_variables_objects(){ .def("compute_kinetic_energy", expt_compute_kinetic_energy_v2) .def("compute_kinetic_energies", expt_compute_kinetic_energies_v1) .def("compute_kinetic_energies", expt_compute_kinetic_energies_v2) + .def("compute_average_kinetic_energy_qtsh", expt_compute_average_kinetic_energy_qtsh_v1) + .def("compute_average_kinetic_energy_qtsh", expt_compute_average_kinetic_energy_qtsh_v2) .def("update_amplitudes", expt_update_amplitudes_v1) diff --git a/src/libra_py/dynamics/tsh/compute.py b/src/libra_py/dynamics/tsh/compute.py index fe104a8ee..27bab4a85 100755 --- a/src/libra_py/dynamics/tsh/compute.py +++ b/src/libra_py/dynamics/tsh/compute.py @@ -568,6 +568,7 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): } ) #================= QTSH specific ==================== + default_params.update( {"use_qtsh":0 } ) default_params.update( {"use_qtsh_deco_factor":0 } ) #================= Decoherence options ========================================= @@ -633,6 +634,7 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): tsh_method = dyn_params["tsh_method"] thermally_corrected_nbra = dyn_params["thermally_corrected_nbra"] total_energy = dyn_params["total_energy"] + use_qtsh = dyn_params["use_qtsh"] states = intList() @@ -698,11 +700,12 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): if tsh_method==7 or tsh_method==8: # FSSH2 or FSSH3 dyn_var.allocate_fssh2() dyn_var.save_curr_dm_into_prev() - if tsh_method==9: # QTSH - dyn_var.allocate_qtsh() if thermally_corrected_nbra==1: dyn_var.allocate_tcnbra() + + if use_qtsh==1: # QTSH + dyn_var.allocate_qtsh() ham_aux = nHamiltonian(ham) diff --git a/src/libra_py/dynamics/tsh/save.py b/src/libra_py/dynamics/tsh/save.py index 8e09260ad..a702e8837 100755 --- a/src/libra_py/dynamics/tsh/save.py +++ b/src/libra_py/dynamics/tsh/save.py @@ -107,6 +107,10 @@ def init_tsh_data(saver, output_level, _nsteps, _ntraj, _ndof, _nadi, _ndia): # TC-NBRA thermostat energy if "tcnbra_thermostat_energy" in saver.keywords: saver.add_dataset("tcnbra_thermostat_energy", (_nsteps,), "R") + + # Average kinetic energy in QTSH + if "Ekin_ave_qtsh" in saver.keywords: + saver.add_dataset("Ekin_ave_qtsh", (_nsteps,) , "R") if output_level>=2: @@ -202,9 +206,9 @@ def init_tsh_data(saver, output_level, _nsteps, _ntraj, _ndof, _nadi, _ndia): if "f_xf" in saver.keywords: # and "f_xf" in saver.np_data.keys(): saver.add_dataset("f_xf", (_nsteps, _ntraj, _ndof), "R") - # Global decoherence factor in QTSH - if "qtsh_deco_factor" in saver.keywords: # and "qtsh_deco_factor" in saver.np_data.keys(): - saver.add_dataset("qtsh_deco_factor", (_nsteps, _nadi, _nadi), "R") + ## Global decoherence factor in QTSH + #if "qtsh_deco_factor" in saver.keywords: # and "qtsh_deco_factor" in saver.np_data.keys(): + # saver.add_dataset("qtsh_deco_factor", (_nsteps, _nadi, _nadi), "R") if output_level>=4: @@ -435,6 +439,11 @@ def save_hdf5_1D_new(saver, i, params, dyn_var, ham, txt_type=0): if "tcnbra_thermostat_energy" in saver.keywords and "tcnbra_thermostat_energy" in saver.np_data.keys(): tcnbra_thermostat_energy = dyn_var.compute_tcnbra_thermostat_energy(); saver.save_scalar(t, "tcnbra_thermostat_energy", tcnbra_thermostat_energy) + + # QTSH average kinetic energy from kinematic momentum + if "Ekin_ave_qtsh" in saver.keywords and "Ekin_ave_qtsh" in saver.np_data.keys(): + Ekin_qtsh = dyn_var.compute_average_kinetic_energy_qtsh(); + saver.save_scalar(t, "Ekin_ave_qtsh", Ekin_qtsh) def save_hdf5_2D(saver, i, states, txt_type=0): @@ -673,11 +682,17 @@ def save_hdf5_3D_new(saver, i, dyn_var, txt_type=0): f_xf = dyn_var.get_f_xf() saver.save_matrix(t, "f_xf", f_xf.T()) - # Global decoherence factor in QTSH - # Format: saver.add_dataset("qtsh_deco_factor", (_nsteps, _nadi, _nadi), "R") - if "qtsh_deco_factor" in saver.keywords and "qtsh_deco_factor" in saver.np_data.keys(): - qtsh_deco_factor = dyn_var.get_qtsh_deco_factor() - saver.save_matrix(t, "qtsh_deco_factor", qtsh_deco_factor) + # Kinematic mometum in QTSH + # Format: saver.add_dataset("qtsh_p_k", (_nsteps, _ntraj, _dof), "R") + if "qtsh_p_k" in saver.keywords and "qtsh_p_k" in saver.np_data.keys(): + qtsh_p_k = dyn_var.get_qtsh_p_k() + saver.save_matrix(t, "qtsh_p_k", qtsh_p_k.T()) + + # Nonclassical force in QTSH + # Format: saver.add_dataset("qtsh_f_nc", (_nsteps, _ntraj, _dof), "R") + if "qtsh_f_nc" in saver.keywords and "qtsh_f_nc" in saver.np_data.keys(): + qtsh_f_nc = dyn_var.get_qtsh_f_nc() + saver.save_matrix(t, "qtsh_f_nc", qtsh_f_nc.T()) diff --git a/src/nhamiltonian/libnhamiltonian.cpp b/src/nhamiltonian/libnhamiltonian.cpp index bd6271eae..6208fc121 100644 --- a/src/nhamiltonian/libnhamiltonian.cpp +++ b/src/nhamiltonian/libnhamiltonian.cpp @@ -288,6 +288,22 @@ void export_nhamiltonian_objects(){ vector (nHamiltonian::*expt_Ehrenfest_forces_tens_dia_v2)(CMATRIX& ampl_dia, vector& id_) = &nHamiltonian::Ehrenfest_forces_tens_dia; + //CMATRIX (nHamiltonian::*expt_QTSH_forces_adi_v1)(CMATRIX& ampl_adi, int lvl, int option, CMATRIX& transform) + //= &nHamiltonian::QTSH_forces_adi; + CMATRIX (nHamiltonian::*expt_QTSH_forces_adi_v1)(CMATRIX& ampl_adi, int lvl, int option) + = &nHamiltonian::QTSH_forces_adi; + CMATRIX (nHamiltonian::*expt_QTSH_forces_adi_v2)(CMATRIX& ampl_adi, int lvl) + = &nHamiltonian::QTSH_forces_adi; + +// CMATRIX (nHamiltonian::*expt_QTSH_forces_adi_v2)(CMATRIX& ampl_adi, vector& id_) +// = &nHamiltonian::QTSH_forces_adi; + CMATRIX (nHamiltonian::*expt_QTSH_forces_dia_v1)(CMATRIX& ampl_dia, int lvl, int option) + = &nHamiltonian::QTSH_forces_dia; + CMATRIX (nHamiltonian::*expt_QTSH_forces_dia_v2)(CMATRIX& ampl_dia, int lvl) + = &nHamiltonian::QTSH_forces_dia; +// CMATRIX (nHamiltonian::*expt_QTSH_forces_dia_v2)(CMATRIX& ampl_dia, vector& id_) +// = &nHamiltonian::QTSH_forces_dia; + void (nHamiltonian::*expt_add_ethd_dia_v1)(const MATRIX& q, const MATRIX& invM, int der_lvl) = &nHamiltonian::add_ethd_dia; void (nHamiltonian::*expt_add_ethd_adi_v1)(const MATRIX& q, const MATRIX& invM, int der_lvl) = &nHamiltonian::add_ethd_adi; @@ -540,6 +556,14 @@ void export_nhamiltonian_objects(){ .def("Ehrenfest_forces_dia", expt_Ehrenfest_forces_dia_v1) .def("Ehrenfest_forces_dia", expt_Ehrenfest_forces_dia_v2) // .def("Ehrenfest_forces_dia", expt_Ehrenfest_forces_dia_v2) + + .def("QTSH_forces_adi", expt_QTSH_forces_adi_v1) + .def("QTSH_forces_adi", expt_QTSH_forces_adi_v2) +// .def("QTSH_forces_adi", expt_QTSH_forces_adi_v3) +// .def("QTSH_forces_adi", expt_QTSH_forces_adi_v2) + .def("QTSH_forces_dia", expt_QTSH_forces_dia_v1) + .def("QTSH_forces_dia", expt_QTSH_forces_dia_v2) +// .def("QTSH_forces_dia", expt_QTSH_forces_dia_v2) ; diff --git a/src/nhamiltonian/nHamiltonian.h b/src/nhamiltonian/nHamiltonian.h index c7fb79999..be963d5ca 100644 --- a/src/nhamiltonian/nHamiltonian.h +++ b/src/nhamiltonian/nHamiltonian.h @@ -572,6 +572,24 @@ class nHamiltonian{ + ///< In nHamiltonian_compute_QTSH_forces.cpp + CMATRIX QTSH_forces_dia_unit(CMATRIX& ampl_dia, int option); ///< QTSH forces in diabatic basis + CMATRIX QTSH_forces_dia_unit(CMATRIX& ampl_dia); ///< QTSH forces in diabatic basis + CMATRIX QTSH_forces_dia(CMATRIX& ampl_dia, int lvl, int option); ///< QTSH forces in diabatic basis + CMATRIX QTSH_forces_dia(CMATRIX& ampl_dia, int lvl); ///< QTSH forces in diabatic basis + + + ///< In nHamiltonian_compute_QTSH_forces.cpp + CMATRIX QTSH_forces_adi_unit(CMATRIX& ampl_adi, int option, CMATRIX& transform); ///< QTSH forces in adiabatic basis + CMATRIX QTSH_forces_adi_unit(CMATRIX& ampl_adi, int option); ///< QTSH forces in adiabatic basis + CMATRIX QTSH_forces_adi_unit(CMATRIX& ampl_adi); ///< QTSH forces in adiabatic basis + CMATRIX QTSH_forces_adi(CMATRIX& ampl_adi, int lvl, int option, vector& transforms); ///< QTSH forces in adiabatic basis + CMATRIX QTSH_forces_adi(CMATRIX& ampl_adi, int lvl, int option); ///< QTSH forces in adiabatic basis + CMATRIX QTSH_forces_adi(CMATRIX& ampl_adi, int lvl); ///< QTSH forces in adiabatic basis + + + + friend bool operator == (const nHamiltonian& h1, const nHamiltonian& h2){ return &h1 == &h2; } diff --git a/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp b/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp new file mode 100644 index 000000000..cfc0be807 --- /dev/null +++ b/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp @@ -0,0 +1,411 @@ +/********************************************************************************* +* Copyright (C) 2017-2023 Alexey V. Akimov +* +* This file is distributed under the terms of the GNU General Public License +* as published by the Free Software Foundation, either version 3 of +* the License, or (at your option) any later version. +* See the file LICENSE in the root directory of this distribution +* or . +* +*********************************************************************************/ +/** + \file nHamiltonian_compute_QTSH_forces.cpp + \brief The file implements the calculations of various variants of the QTSH forces +*/ + + +#if defined(USING_PCH) +#include "../pch.h" +#else +#include +#endif + +#include "nHamiltonian.h" +#include "../math_meigen/libmeigen.h" + + +/// liblibra namespace +namespace liblibra{ + +/// libnhamiltonian namespace +namespace libnhamiltonian{ + + +using namespace liblinalg; +using namespace libmeigen; + + + + +CMATRIX nHamiltonian::QTSH_forces_dia_unit(CMATRIX& ampl_dia, int option){ +/** + \param[in] ampl_dia: MATRIX(ndia, 1) diabatic amplitudes for one trajectory + + Returns: + MATRIX(ndof, 1) - QTSH nonclassical forces in diabatic representation, for a single trajectory + + The nonclassical force in QTSH has the form of the off-diagonal terms in the Ehrenfest force. + +*/ + + if(ovlp_dia_mem_status==0){ cout<<"Error in QTSH_forces_dia_unit(): the overlap matrix in the diabatic basis is not allocated \ + but it is needed for the calculations\n"; exit(0); } + + if(ham_dia_mem_status==0){ cout<<"Error in QTSH_forces_dia_unit(): the diabatic Hamiltonian matrix is not allocated \ + but it is needed for the calculations\n"; exit(0); } + + + CMATRIX res(nnucl,1); + CMATRIX* dtilda; dtilda = new CMATRIX(nadi,nadi); + CMATRIX* invS; invS = new CMATRIX(nadi, nadi); + + FullPivLU_inverse(*ovlp_dia, *invS); + + complex norm = ( ampl_dia.H() * (*ovlp_dia) * ampl_dia ).M[0]; + + CMATRIX* temp_diag = new CMATRIX(nadi, nadi); + + for(int n=0;nset(i,i, (*d1ham_dia[n] - *dtilda ).get(i,i)); + } + res.M[n] = -( ampl_dia.H() * (*d1ham_dia[n] - *dtilda - *temp_diag ) * ampl_dia ).M[0]; + } + else if(option==1){ + for(int i=0;iset(i,i, d1ham_dia[n]->get(i,i)); + } + res.M[n] = -( ampl_dia.H() * (*d1ham_dia[n] - *temp_diag ) * ampl_dia ).M[0]; + } + + + }// for n + + res /= norm; + + + delete dtilda; + delete invS; + delete temp_diag; + + return res; + +} + + + +CMATRIX nHamiltonian::QTSH_forces_dia_unit(CMATRIX& ampl_dia){ + return QTSH_forces_dia_unit(ampl_dia, 0); +} + + + +CMATRIX nHamiltonian::QTSH_forces_dia(CMATRIX& ampl_dia, int lvl, int option){ +/** + \brief Computes the QTSH forces in the diabatic basis + + \param[in] ampl_dia [ndia x ntraj] matrix of diabatic amplitudes for + one or many (ntraj) trajectories + \param[in] lvl [0 or 1] - 0 - use the present level for all trajectories, 1 - use the next + level for the trajectories (one sub-Hamiltonian per each trajectory) + + There are 2 possible use cases: + a) lvl = 0 ampl_dia is a ndia x ntraj matrix and is meant to + be handled by the current Hamiltonian (e.g. like the NBRA) + + b) lvl = 1 ampl_adi is a ndia x ntraj matrix (ntraj trajectories) and + each trajectory is meant to be handled by a separate sub-Hamiltonian + + Returns: + MATRIX(ndof, ntraj) - QTSH forces in diabatic representation, for multiple trajectories + + +*/ + int i; + + if(lvl==1){ + // Check whether we have enough sub-Hamiltonians + if(children.size()!=ampl_dia.n_cols){ + cout<<"ERROR in CMATRIX nHamiltonian::QTSH_forces_dia(CMATRIX& ampl_dia):\n"; + cout<<"The number of columns of the ampl_dia ("< stenc_ampl(ampl_dia.n_rows, 0); + vector stenc_frc(nnucl, 0); + vector stenc_col(1, 0); + + // The indicies in stenc_frc reflects which indicies are to be updated in the final F matrix + for(i=0;iQTSH_forces_dia_unit(ampl_tmp, option); + } + + push_submatrix(F, frc_tmp, stenc_frc, stenc_col); + + }// for all children + + return F; + +} + + +CMATRIX nHamiltonian::QTSH_forces_dia(CMATRIX& ampl_dia, int lvl){ + return QTSH_forces_dia(ampl_dia, lvl, 0); +} + + + + +/* +CMATRIX nHamiltonian::QTSH_forces_dia(CMATRIX& ampl_dia, vector& id_){ +// +// See the description of the QTSH_forces_dia(CMATRIX& ampl_dia) function +// + if(id_.size()==1){ + if(id_[0]==id){ return QTSH_forces_dia(ampl_dia); } + else{ cout<<"ERROR in QTSH_forces_dia: No Hamiltonian matching the requested id\n"; exit(0); } + } + else{ + vector next(id_.begin()+1,id_.end()); + return children[id_[1]]->QTSH_forces_dia(ampl_dia, next); + } +} +*/ + + + +CMATRIX nHamiltonian::QTSH_forces_adi_unit(CMATRIX& ampl_adi, int option, CMATRIX& transform){ +/** + \param[in] ampl_adi: MATRIX(nadi, 1) diabatic amplitudes for one trajectory + + \params[in] option [0 or 1] - option 0 keeps all the terms in the Ehrenfest-like force expression, including NAC + option 1 removes all the derivative NACs - this is to enforce the local diabatization approximation, to be + consistent with it + + The nonclassical force in QTSH has the form of the off-diagonal terms in the Ehrenfest force. + + Returns: + MATRIX(ndof, 1) - QTSH forces in adiabatic representation, for single trajectory + + +*/ + + if(ham_adi_mem_status==0){ cout<<"Error in QTSH_forces_adi(): the adiabatic Hamiltonian matrix is not allocated \ + but it is needed for the calculations\n"; exit(0); } + + + complex norm = (ampl_adi.H() * ampl_adi).M[0]; + + + CMATRIX res(nnucl,1); + CMATRIX tmp(nadi, nadi); + +// CMATRIX& T = transform; + CMATRIX T(transform); T.identity(); + + CMATRIX temp_diag(nadi, nadi); + + for(int n=0;nget(i,i) ); + } + res.M[n] = -( ampl_adi.H() * T.H() * ( *d1ham_adi[n] - temp_diag ) * T * ampl_adi ).M[0]; + } + + }// for n + + res /= norm; + //delete tmp; + + return res; +} + +CMATRIX nHamiltonian::QTSH_forces_adi_unit(CMATRIX& ampl_adi, int option){ + CMATRIX I(nadi, nadi); I.identity(); + return QTSH_forces_adi_unit(ampl_adi,option, I); +} + +CMATRIX nHamiltonian::QTSH_forces_adi_unit(CMATRIX& ampl_adi){ + CMATRIX I(nadi, nadi); I.identity(); + return QTSH_forces_adi_unit(ampl_adi, 0, I); +} + + +CMATRIX nHamiltonian::QTSH_forces_adi(CMATRIX& ampl_adi, int lvl, int option, vector& transforms){ +/** + \brief Computes the QTSH forces in the adiabatic basis + + \param[in] ampl_adi [nadi x ntraj] matrix of adiabatic amplitudes for + one of many (ntraj) trajectories + \param[in] lvl [0 or 1] - 0 - use the present level for all trajectories, 1 - use the next + level for the trajectories (one sub-Hamiltonian per each trajectory) + + There are 2 possible use cases: + a) lvl = 0 ampl_adi is a nadi x ntraj matrix and is meant to + be handled by the current Hamiltonian (e.g. like the NBRA) + + b) lvl = 1 ampl_adi is a nadi x ntraj matrix (ntraj trajectories) and + each trajectory is meant to be handled by a separate sub-Hamiltonian + + \params[in] option [0 or 1] - option 0 keeps all the terms in the QTSH force expression, including NAC + option 1 removes all the derivative NACs - this is to enforce the local diabatization approximation, to be + consistent with it + + Returns: + MATRIX(ndof, ntraj) - QTSH forces in adiabatic representation, for multiple trajectories + +*/ + int i; + + if(lvl==1){ + // Check whether we have enough sub-Hamiltonians + if(children.size()!=ampl_adi.n_cols){ + cout<<"ERROR in CMATRIX nHamiltonian::QTSH_forces_adi(CMATRIX& ampl_adi):\n"; + cout<<"The number of columns of the ampl_adi ("< stenc_ampl(ampl_adi.n_rows, 0); + vector stenc_frc(nnucl, 0); + vector stenc_col(1, 0); + + // The indicies in stenc_frc reflects which indicies are to be updated in the final F matrix + for(i=0;iQTSH_forces_adi_unit(ampl_tmp, option, *transforms[i] ); + } + + push_submatrix(F, frc_tmp, stenc_frc, stenc_col); + + }// for all children + + return F; +} + +CMATRIX nHamiltonian::QTSH_forces_adi(CMATRIX& ampl_adi, int lvl, int option){ + int ntraj = ampl_adi.n_cols; + vector I(ntraj); + + for(int itraj=0; itrajload_identity(); + } + + CMATRIX F(nnucl, ampl_adi.n_cols); + + F = QTSH_forces_adi(ampl_adi, lvl, option, I); + + for(int itraj=0; itraj I(ntraj); + + for(int itraj=0; itrajload_identity(); + } + + CMATRIX F(nnucl, ampl_adi.n_cols); + + F = QTSH_forces_adi(ampl_adi, lvl, 0, I); + + for(int itraj=0; itraj& id_){ +// +// See the description of the QTSH_forces_adi(CMATRIX& ampl_adi) function +// + if(id_.size()==1){ + if(id_[0]==id){ return QTSH_forces_adi(ampl_adi); } + else{ cout<<"ERROR in QTSH_forces_adi: No Hamiltonian matching the requested id\n"; exit(0); } + } + else{ + vector next(id_.begin()+1,id_.end()); + return children[id_[1]]->QTSH_forces_adi(ampl_adi, next); + } +} +*/ + + + + +}// namespace libnhamiltonian +}// liblibra + From c9a260acdcb25dc138b566524a2923b431a94d7d Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Mon, 27 May 2024 22:20:18 -0400 Subject: [PATCH 06/48] Adjust the momentum when the QTSH is applied to SDM and SHXF --- src/dyn/Dynamics.cpp | 9 +++-- src/dyn/dyn_decoherence_methods.cpp | 27 ++++++++++---- src/dyn/dyn_variables.h | 4 +++ src/dyn/dyn_variables_nuclear.cpp | 53 ++++++++++++++++++++++++++++ src/dyn/libdyn.cpp | 8 +++++ src/libra_py/dynamics/tsh/compute.py | 2 +- 6 files changed, 93 insertions(+), 10 deletions(-) diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index 524efc897..1dfc8549e 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -1416,8 +1416,13 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, } /// Compute the dephasing rates according the original energy-based formalism else if(prms.decoherence_times_type==1){ - Eadi = get_Eadi(ham); - Ekin = dyn_var.compute_kinetic_energies(); + Eadi = get_Eadi(ham); + if(prms.use_qtsh==0){ + Ekin = dyn_var.compute_kinetic_energies(); + } + else{ + Ekin = dyn_var.compute_kinetic_energies_qtsh(); + } decoherence_rates = edc_rates(Eadi, Ekin, prms.decoherence_C_param, prms.decoherence_eps_param, prms.isNBRA); } diff --git a/src/dyn/dyn_decoherence_methods.cpp b/src/dyn/dyn_decoherence_methods.cpp index 8b20e34e1..c5f8392c6 100755 --- a/src/dyn/dyn_decoherence_methods.cpp +++ b/src/dyn/dyn_decoherence_methods.cpp @@ -1089,8 +1089,7 @@ void xf_create_AT(dyn_variables& dyn_var, double threshold){ // Prevent spurious decoherence if (nr_mixed < 2){ - is_mixed.assign(nadi, 0); - is_first.assign(nadi, 0); + xf_init_AT(dyn_var, traj, -1); } } //traj @@ -1181,14 +1180,19 @@ void shxf(dyn_variables& dyn_var, nHamiltonian& ham, nHamiltonian& ham_prev, dyn p_aux_old.set(i, idof, p_aux.get(i, idof)); } } + + if(prms.use_qtsh==0){ + p_real = dyn_var.p->col(traj); + } + else{ + p_real = dyn_var.qtsh_p_k->col(traj); + } int is_tp = 0; double alpha; for(int i=0; icol(traj); if(i==a){ alpha = compute_kinetic_energy(p_real, invM); @@ -1214,15 +1218,24 @@ void shxf(dyn_variables& dyn_var, nHamiltonian& ham, nHamiltonian& ham_prev, dyn alpha /= compute_kinetic_energy(p_real, invM); for(int idof=0; idofget(idof, traj) * sqrt(alpha)); + p_aux.set(i, idof, p_real.get(idof, 0) * sqrt(alpha)); } // Check the turning point if (is_first[i] == 0 and prms.tp_algo != 0){ MATRIX Fa(ndof, 1); - for(int idof=0; idofget(idof,0) ); + + if(prms.use_qtsh==0){ + for(int idof=0; idofget(idof,0) ); + } } + else{ // QTSH + for(int idof=0; idofget(idof,0) + dyn_var.qtsh_f_nc->get(idof,0) ); + } + } + double dp_old = 0.0; double dp_new = 0.0; diff --git a/src/dyn/dyn_variables.h b/src/dyn/dyn_variables.h index 26ed7410c..35b168e2b 100755 --- a/src/dyn/dyn_variables.h +++ b/src/dyn/dyn_variables.h @@ -608,6 +608,10 @@ class dyn_variables{ vector compute_kinetic_energies(vector& which_dofs); double compute_average_kinetic_energy_qtsh(); double compute_average_kinetic_energy_qtsh(vector& which_dofs); + double compute_kinetic_energy_qtsh(int itraj); + double compute_kinetic_energy_qtsh(int itraj, vector& which_dofs); + vector compute_kinetic_energies_qtsh(); + vector compute_kinetic_energies_qtsh(vector& which_dofs); ///====================== In dyn_variables_electronic.cpp ===================== diff --git a/src/dyn/dyn_variables_nuclear.cpp b/src/dyn/dyn_variables_nuclear.cpp index 7d276e0cf..f05d0f55a 100644 --- a/src/dyn/dyn_variables_nuclear.cpp +++ b/src/dyn/dyn_variables_nuclear.cpp @@ -348,6 +348,59 @@ vector dyn_variables::compute_kinetic_energies(vector& which_dofs){ } +double dyn_variables::compute_kinetic_energy_qtsh(int itraj){ + double res = 0.0; + + for(int idof = 0; idof < ndof; idof++){ + res += qtsh_p_k->get(idof, itraj) * qtsh_p_k->get(idof, itraj) * iM->get(idof, 0); + } + res *= 0.5; + + return res; +} + + +double dyn_variables::compute_kinetic_energy_qtsh(int itraj, vector& which_dofs){ + double res = 0.0; + + for(auto idof : which_dofs){ + res += qtsh_p_k->get(idof, itraj) * qtsh_p_k->get(idof, itraj) * iM->get(idof, 0); + } + res *= 0.5; + + return res; +} + + +vector dyn_variables::compute_kinetic_energies_qtsh(){ + vector res(ntraj, 0.0); + + for(int itraj = 0; itraj < ntraj; itraj++){ + for(int idof = 0; idof < ndof; idof++){ + res[itraj] += qtsh_p_k->get(idof, itraj) * qtsh_p_k->get(idof, itraj) * iM->get(idof, 0); + } + res[itraj] *= 0.5; + } + + return res; + +} + +vector dyn_variables::compute_kinetic_energies_qtsh(vector& which_dofs){ + vector res(ntraj, 0.0); + + for(int itraj = 0; itraj < ntraj; itraj++){ + for(auto idof : which_dofs){ + res[itraj] += qtsh_p_k->get(idof, itraj) * qtsh_p_k->get(idof, itraj) * iM->get(idof, 0); + } + res[itraj] *= 0.5; + } + + return res; + +} + + double dyn_variables::compute_tcnbra_ekin(){ double res = 0.0; diff --git a/src/dyn/libdyn.cpp b/src/dyn/libdyn.cpp index 8f30900a7..a9a280cf2 100755 --- a/src/dyn/libdyn.cpp +++ b/src/dyn/libdyn.cpp @@ -218,6 +218,10 @@ void export_dyn_variables_objects(){ double (dyn_variables::*expt_compute_average_kinetic_energy_qtsh_v1)() = &dyn_variables::compute_average_kinetic_energy_qtsh; double (dyn_variables::*expt_compute_average_kinetic_energy_qtsh_v2)(vector& which_dofs) = &dyn_variables::compute_average_kinetic_energy_qtsh; + double (dyn_variables::*expt_compute_kinetic_energy_qtsh_v1)(int itraj) = &dyn_variables::compute_kinetic_energy_qtsh; + double (dyn_variables::*expt_compute_kinetic_energy_qtsh_v2)(int itraj, vector& which_dofs) = &dyn_variables::compute_kinetic_energy_qtsh; + vector (dyn_variables::*expt_compute_kinetic_energies_qtsh_v1)() = &dyn_variables::compute_kinetic_energies_qtsh; + vector (dyn_variables::*expt_compute_kinetic_energies_qtsh_v2)(vector& which_dofs) = &dyn_variables::compute_kinetic_energies_qtsh; void (dyn_variables::*expt_update_active_states_v1)(int direction, int property) = &dyn_variables::update_active_states; void (dyn_variables::*expt_update_active_states_v2)() = &dyn_variables::update_active_states; @@ -297,6 +301,10 @@ void export_dyn_variables_objects(){ .def("compute_kinetic_energies", expt_compute_kinetic_energies_v2) .def("compute_average_kinetic_energy_qtsh", expt_compute_average_kinetic_energy_qtsh_v1) .def("compute_average_kinetic_energy_qtsh", expt_compute_average_kinetic_energy_qtsh_v2) + .def("compute_kinetic_energy_qtsh", expt_compute_kinetic_energy_qtsh_v1) + .def("compute_kinetic_energy_qtsh", expt_compute_kinetic_energy_qtsh_v2) + .def("compute_kinetic_energies_qtsh", expt_compute_kinetic_energies_qtsh_v1) + .def("compute_kinetic_energies_qtsh", expt_compute_kinetic_energies_qtsh_v2) .def("update_amplitudes", expt_update_amplitudes_v1) diff --git a/src/libra_py/dynamics/tsh/compute.py b/src/libra_py/dynamics/tsh/compute.py index 27bab4a85..3b9ebd300 100755 --- a/src/libra_py/dynamics/tsh/compute.py +++ b/src/libra_py/dynamics/tsh/compute.py @@ -581,7 +581,7 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): "ave_gaps":MATRIX(nstates,nstates), "schwartz_decoherence_inv_alpha": MATRIX(dyn_var.ndof, 1), "schwartz_interaction_width": MATRIX(dyn_var.ndof, 1), "wp_width":MATRIX(dyn_var.ndof, 1), "wp_v":MATRIX(dyn_var.ndof, 1), "coherence_threshold":0.01, "e_mask": 0.0001, - "use_xf_force": 0, "project_out_aux": 0, "tp_algo": 1, "use_td_width": 0 + "use_xf_force": 0, "project_out_aux": 1, "tp_algo": 1, "use_td_width": 0 } ) #================= Entanglement of trajectories ================================ From fccc1a0c7419dda79bf6a24030e9e9fdb757aca5 Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Mon, 27 May 2024 22:35:27 -0400 Subject: [PATCH 07/48] Remove global deco factor and local QTSH force functions that are no longer used --- src/dyn/Dynamics.cpp | 2 - src/dyn/dyn_control_params.cpp | 3 - src/dyn/dyn_control_params.h | 1 - src/dyn/dyn_methods.h | 3 - src/dyn/dyn_methods_qtsh.cpp | 87 ---------------------------- src/dyn/dyn_variables.cpp | 16 ----- src/dyn/dyn_variables.h | 20 ------- src/dyn/libdyn.cpp | 9 --- src/libra_py/dynamics/tsh/compute.py | 1 - src/libra_py/dynamics/tsh/save.py | 4 -- 10 files changed, 146 deletions(-) diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index 1dfc8549e..1c10e6b94 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -1400,8 +1400,6 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, else{ update_Hamiltonian_variables(prms, dyn_var, ham, ham_aux, py_funct, params, 3); } - - //if(prms.tsh_method == 9){ update_deco_factor_qtsh(dyn_var, ham, prms); } //============== Begin the TSH part =================== diff --git a/src/dyn/dyn_control_params.cpp b/src/dyn/dyn_control_params.cpp index 27ecfe742..2c2873933 100755 --- a/src/dyn/dyn_control_params.cpp +++ b/src/dyn/dyn_control_params.cpp @@ -80,7 +80,6 @@ dyn_control_params::dyn_control_params(){ ///================= QTSH specific ==================== use_qtsh = 0; - use_qtsh_deco_factor = 0; ///================= Decoherence options ========================================= decoherence_algo = -1; @@ -184,7 +183,6 @@ dyn_control_params::dyn_control_params(const dyn_control_params& x){ ///================= QTSH specific ==================== use_qtsh = x.use_qtsh; - use_qtsh_deco_factor = x.use_qtsh_deco_factor; ///================= Decoherence options ========================================= decoherence_algo = x.decoherence_algo; @@ -379,7 +377,6 @@ void dyn_control_params::set_parameters(bp::dict params){ ///================= QTSH specific ==================== else if(key=="use_qtsh"){ use_qtsh = bp::extract(params.values()[i]); } - else if(key=="use_qtsh_deco_factor"){ use_qtsh_deco_factor = bp::extract(params.values()[i]); } ///================= Decoherence options ========================================= else if(key=="decoherence_algo"){ decoherence_algo = bp::extract(params.values()[i]); } diff --git a/src/dyn/dyn_control_params.h b/src/dyn/dyn_control_params.h index 60d4c7759..bf9e68d54 100755 --- a/src/dyn/dyn_control_params.h +++ b/src/dyn/dyn_control_params.h @@ -463,7 +463,6 @@ class dyn_control_params{ int use_qtsh; - int use_qtsh_deco_factor; ///=============================================================================== ///================= Decoherence options ========================================= diff --git a/src/dyn/dyn_methods.h b/src/dyn/dyn_methods.h index fcd1ca98d..52be330b2 100755 --- a/src/dyn/dyn_methods.h +++ b/src/dyn/dyn_methods.h @@ -52,9 +52,6 @@ void dish_project_out_collapse(vector& old_states, vector& proposed_st MATRIX compute_dkinemat(dyn_variables& dyn_var, nHamiltonian& ham); -void update_forces_qtsh(dyn_variables& dyn_var, nHamiltonian& ham, dyn_control_params& prms); -void update_deco_factor_qtsh(dyn_variables& dyn_var, nHamiltonian& ham, dyn_control_params& prms); - }// namespace libdyn }// liblibra diff --git a/src/dyn/dyn_methods_qtsh.cpp b/src/dyn/dyn_methods_qtsh.cpp index e5e41c2a7..1c9013588 100644 --- a/src/dyn/dyn_methods_qtsh.cpp +++ b/src/dyn/dyn_methods_qtsh.cpp @@ -69,92 +69,5 @@ MATRIX compute_dkinemat(dyn_variables& dyn_var, nHamiltonian& ham){ return dp; } - -void update_forces_qtsh(dyn_variables& dyn_var, nHamiltonian& ham, dyn_control_params& prms){ - /** - Add the nonclassical contribution from the kinematic mometum in QTSH - */ - - int ntraj = dyn_var.ntraj; - int ndof = dyn_var.ndof; - int nst = dyn_var.nadi; - MATRIX& invM = *dyn_var.iM; - - CMATRIX C(nst, 1); - CMATRIX Coeff(nst, ntraj); - - MATRIX f_nc(ndof,ntraj); - - Coeff = *dyn_var.ampl_adi; - - for(int traj=0; trajget_ham_adi(); - - CMATRIX dc1_adi(nst, nst); - for(int idof=0; idofget_dc1_adi(idof); - for(int i=0; iget(i,j).real() ); - } - } - } - }//traj - - *dyn_var.f += f_nc; - -} - -void update_deco_factor_qtsh(dyn_variables& dyn_var, nHamiltonian& ham, dyn_control_params& prms){ -/** - This function computes the global decoherence factor used to adjust the hopping probability in QTSH -*/ - int ndof = dyn_var.ndof; - int ntraj = dyn_var.ntraj; - int nadi = dyn_var.nadi; - - double dt = prms.dt; - - // Compute the proxy phase of each trajectory - for(int traj=0; trajget_ham_adi(); - - for(int i=0; iadd(i,j, omega*dt); - } - } - } - - // Compute the global decoherence factor - MATRIX avg_ph(nadi, nadi); - MATRIX avg_ph2(nadi, nadi); - MATRIX temp(nadi, nadi); - - for(int traj=0; trajset(i,j, exp( -0.5*(avg_ph2.get(i,j) - pow(avg_ph.get(i,j), 2.0)) ) ); - } - } - -} - }// libdyn }// liblibra diff --git a/src/dyn/dyn_variables.cpp b/src/dyn/dyn_variables.cpp index 40af89e55..f893c10d7 100755 --- a/src/dyn/dyn_variables.cpp +++ b/src/dyn/dyn_variables.cpp @@ -289,13 +289,6 @@ void dyn_variables::allocate_qtsh(){ if(qtsh_vars_status==0){ - qtsh_proxy_phase = vector(ntraj); - - for(int itraj=0; itraj - */ - vector qtsh_proxy_phase; - - - /** - Global decoherence factor in QTSH - - Options: - MATRIX(nadi, nadi) - */ - MATRIX* qtsh_deco_factor; - /** Kinematic momentum in QTSH @@ -581,8 +563,6 @@ class dyn_variables{ MATRIX get_coords_aux(int i){ return *q_aux[i]; } MATRIX get_momenta_aux(int i){ return *p_aux[i]; } MATRIX get_nab_phase(int i){ return *nab_phase[i]; } - MATRIX get_qtsh_proxy_phase(int i){ return *qtsh_proxy_phase[i]; } - MATRIX get_qtsh_deco_factor(){ return *qtsh_deco_factor; } MATRIX get_qtsh_p_k(){ return *qtsh_p_k; } MATRIX get_qtsh_f_nc(){ return *qtsh_f_nc; } diff --git a/src/dyn/libdyn.cpp b/src/dyn/libdyn.cpp index a9a280cf2..a755b4692 100755 --- a/src/dyn/libdyn.cpp +++ b/src/dyn/libdyn.cpp @@ -102,7 +102,6 @@ void export_dyn_control_params_objects(){ ///================= FSSH3 specific ==================== .def_readwrite("use_qtsh", &dyn_control_params::use_qtsh) - .def_readwrite("use_qtsh_deco_factor", &dyn_control_params::use_qtsh_deco_factor) ///================= Decoherence options ========================================= .def_readwrite("decoherence_algo", &dyn_control_params::decoherence_algo) @@ -179,8 +178,6 @@ void export_dyn_variables_objects(){ MATRIX (dyn_variables::*expt_get_coords_aux)(int i) = &dyn_variables::get_coords_aux; MATRIX (dyn_variables::*expt_get_momenta_aux)(int i) = &dyn_variables::get_momenta_aux; MATRIX (dyn_variables::*expt_get_nab_phase)(int i) = &dyn_variables::get_nab_phase; - - MATRIX (dyn_variables::*expt_get_qtsh_proxy_phase)(int i) = &dyn_variables::get_qtsh_proxy_phase; // Arbitrary wavefunction void (dyn_variables::*expt_set_parameters_v1)(boost::python::dict params) = &dyn_variables::set_parameters; @@ -287,8 +284,6 @@ void export_dyn_variables_objects(){ .def("get_coords_aux", expt_get_coords_aux) .def("get_momenta_aux", expt_get_momenta_aux) .def("get_nab_phase", expt_get_nab_phase) - .def("get_qtsh_deco_factor", &dyn_variables::get_qtsh_deco_factor) - .def("get_qtsh_proxy_phase", expt_get_qtsh_proxy_phase) .def("get_qtsh_p_k", &dyn_variables::get_qtsh_p_k) .def("get_qtsh_f_nc", &dyn_variables::get_qtsh_f_nc) @@ -520,10 +515,6 @@ void export_dyn_decoherence_objects(){ MATRIX (*expt_compute_dkinemat_v1) (dyn_variables& dyn_var, nHamiltonian& ham) = &compute_dkinemat; def("compute_dkinemat", expt_compute_dkinemat_v1); - - void (*expt_update_forces_qtsh_v1) - (dyn_variables& dyn_var, nHamiltonian& ham, dyn_control_params& prms) = &update_forces_qtsh; - def("update_forces_qtsh", expt_update_forces_qtsh_v1); } diff --git a/src/libra_py/dynamics/tsh/compute.py b/src/libra_py/dynamics/tsh/compute.py index 3b9ebd300..11077fc4e 100755 --- a/src/libra_py/dynamics/tsh/compute.py +++ b/src/libra_py/dynamics/tsh/compute.py @@ -569,7 +569,6 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): #================= QTSH specific ==================== default_params.update( {"use_qtsh":0 } ) - default_params.update( {"use_qtsh_deco_factor":0 } ) #================= Decoherence options ========================================= default_params.update( { "decoherence_algo":-1, "sdm_norm_tolerance":0.0, diff --git a/src/libra_py/dynamics/tsh/save.py b/src/libra_py/dynamics/tsh/save.py index a702e8837..b441fc0e9 100755 --- a/src/libra_py/dynamics/tsh/save.py +++ b/src/libra_py/dynamics/tsh/save.py @@ -205,10 +205,6 @@ def init_tsh_data(saver, output_level, _nsteps, _ntraj, _ndof, _nadi, _ndia): # Trajectory-resolved decoherence forces based on XF if "f_xf" in saver.keywords: # and "f_xf" in saver.np_data.keys(): saver.add_dataset("f_xf", (_nsteps, _ntraj, _ndof), "R") - - ## Global decoherence factor in QTSH - #if "qtsh_deco_factor" in saver.keywords: # and "qtsh_deco_factor" in saver.np_data.keys(): - # saver.add_dataset("qtsh_deco_factor", (_nsteps, _nadi, _nadi), "R") if output_level>=4: From f779c91455398be7e6557e895f21acde102c4cfe Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Mon, 27 May 2024 23:56:14 -0400 Subject: [PATCH 08/48] Save total energy with the kinematic momentum (needs to debug further) --- src/dyn/dyn_ham.cpp | 13 ++++++++++--- src/libra_py/dynamics/tsh/save.py | 8 ++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/dyn/dyn_ham.cpp b/src/dyn/dyn_ham.cpp index 4430e8578..36ab4fe98 100644 --- a/src/dyn/dyn_ham.cpp +++ b/src/dyn/dyn_ham.cpp @@ -163,11 +163,18 @@ void update_Hamiltonian_variables(dyn_control_params& prms, dyn_variables& dyn_v int ndof = dyn_var.ndof; int ntraj = dyn_var.ntraj; - if(update_type==3){ p = *dyn_var.qtsh_p_k; } + //if(update_type==3){ p = *dyn_var.qtsh_p_k; } MATRIX p_quantum_dof(ndof, ntraj); - for(auto dof: prms.quantum_dofs){ - for(int itraj = 0; itraj < ntraj; itraj++){ p_quantum_dof.set(dof, itraj, p.get(dof, itraj) ); } + if(update_type==1){ + for(auto dof: prms.quantum_dofs){ + for(int itraj = 0; itraj < ntraj; itraj++){ p_quantum_dof.set(dof, itraj, p.get(dof, itraj) ); } + } + } + else if(update_type==3){ + for(auto dof: prms.quantum_dofs){ + for(int itraj = 0; itraj < ntraj; itraj++){ p_quantum_dof.set(dof, itraj, dyn_var.qtsh_p_k->get(dof, itraj) ); } + } } ham.compute_nac_dia(p_quantum_dof, iM, 0, 1); diff --git a/src/libra_py/dynamics/tsh/save.py b/src/libra_py/dynamics/tsh/save.py index b441fc0e9..c57322330 100755 --- a/src/libra_py/dynamics/tsh/save.py +++ b/src/libra_py/dynamics/tsh/save.py @@ -111,6 +111,10 @@ def init_tsh_data(saver, output_level, _nsteps, _ntraj, _ndof, _nadi, _ndia): # Average kinetic energy in QTSH if "Ekin_ave_qtsh" in saver.keywords: saver.add_dataset("Ekin_ave_qtsh", (_nsteps,) , "R") + + # Average total energy in QTSH + if "Etot_ave_qtsh" in saver.keywords: + saver.add_dataset("Etot_ave_qtsh", (_nsteps,) , "R") if output_level>=2: @@ -441,6 +445,10 @@ def save_hdf5_1D_new(saver, i, params, dyn_var, ham, txt_type=0): Ekin_qtsh = dyn_var.compute_average_kinetic_energy_qtsh(); saver.save_scalar(t, "Ekin_ave_qtsh", Ekin_qtsh) + if "Etot_ave_qtsh" in saver.keywords and "Etot_ave_qtsh" in saver.np_data.keys(): + Ekin_qtsh = dyn_var.compute_average_kinetic_energy_qtsh(); + Etot_qtsh = Ekin_qtsh + Epot + saver.save_scalar(t, "Etot_ave_qtsh", Etot_qtsh) def save_hdf5_2D(saver, i, states, txt_type=0): """ From 73526bb4a9365938cb6c43490591ba685ba1ef69 Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Wed, 29 May 2024 00:28:49 -0400 Subject: [PATCH 09/48] Add the ham methods for the QTSH energy and refine the QTSH force routines --- src/dyn/Dynamics.cpp | 2 +- src/dyn/Energy_and_Forces.cpp | 37 ++++- src/libra_py/dynamics/tsh/save.py | 17 +-- src/nhamiltonian/libnhamiltonian.cpp | 14 ++ src/nhamiltonian/nHamiltonian.h | 10 +- .../nHamiltonian_compute_QTSH.cpp | 139 ++++++++++++++++++ .../nHamiltonian_compute_QTSH_forces.cpp | 12 +- 7 files changed, 208 insertions(+), 23 deletions(-) create mode 100644 src/nhamiltonian/nHamiltonian_compute_QTSH.cpp diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index 1c10e6b94..b1de9144c 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -1580,7 +1580,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, if(prms.thermally_corrected_nbra==1 && prms.tcnbra_do_nac_scaling==1){ remove_thermal_correction(dyn_var, ham, prms); } // Update vib Hamiltonian to reflect the change of the momentum - if(prms.use_qtsh!=1){ + if(prms.use_qtsh==0){ update_Hamiltonian_variables(prms, dyn_var, ham, ham_aux, py_funct, params, 1); } diff --git a/src/dyn/Energy_and_Forces.cpp b/src/dyn/Energy_and_Forces.cpp index b5378a080..b78587223 100755 --- a/src/dyn/Energy_and_Forces.cpp +++ b/src/dyn/Energy_and_Forces.cpp @@ -183,7 +183,7 @@ vector potential_energies(dyn_control_params& prms, dyn_variables& dyn_v }// NBRA */ - if(prms.force_method==0 || prms.force_method==1 || prms.force_method==3){ // State-specific forces + if(prms.force_method==0 || prms.force_method==1){ // State-specific forces // TSH or adiabatic (including excited states) // state-specific forces @@ -239,6 +239,41 @@ vector potential_energies(dyn_control_params& prms, dyn_variables& dyn_v }// Ehrenfest + else if(prms.force_method==3){ // QTSH + + vector effective_states(dyn_vars.act_states); + + if(prms.enforce_state_following==1){ + for(itraj=0; itrajcol(itraj); + res[itraj] += ham.QTSH_energy_dia(coeff, id).real(); // coherence energy + } + } + // Adiabatic + else if(prms.rep_force==1){ + CMATRIX coeff(nadi, 1); + for(itraj=0; itrajcol(itraj); + res[itraj] += ham.QTSH_energy_adi(coeff, id, *(dyn_vars.proj_adi[itraj]) ).real(); // coherence energy + }// for itraj + }// rep_force == 1 + + } + return res; } diff --git a/src/libra_py/dynamics/tsh/save.py b/src/libra_py/dynamics/tsh/save.py index c57322330..927ac2ea2 100755 --- a/src/libra_py/dynamics/tsh/save.py +++ b/src/libra_py/dynamics/tsh/save.py @@ -111,10 +111,6 @@ def init_tsh_data(saver, output_level, _nsteps, _ntraj, _ndof, _nadi, _ndia): # Average kinetic energy in QTSH if "Ekin_ave_qtsh" in saver.keywords: saver.add_dataset("Ekin_ave_qtsh", (_nsteps,) , "R") - - # Average total energy in QTSH - if "Etot_ave_qtsh" in saver.keywords: - saver.add_dataset("Etot_ave_qtsh", (_nsteps,) , "R") if output_level>=2: @@ -209,6 +205,14 @@ def init_tsh_data(saver, output_level, _nsteps, _ntraj, _ndof, _nadi, _ndia): # Trajectory-resolved decoherence forces based on XF if "f_xf" in saver.keywords: # and "f_xf" in saver.np_data.keys(): saver.add_dataset("f_xf", (_nsteps, _ntraj, _ndof), "R") + + # Trajectory-resolved kinematic momentum in QTSH + if "qtsh_p_k" in saver.keywords: # and "f_xf" in saver.np_data.keys(): + saver.add_dataset("qtsh_p_k", (_nsteps, _ntraj, _ndof), "R") + + # Trajectory-resolved nonclassical forces in QTSH + if "qtsh_f_nc" in saver.keywords: # and "f_xf" in saver.np_data.keys(): + saver.add_dataset("qtsh_f_nc", (_nsteps, _ntraj, _ndof), "R") if output_level>=4: @@ -445,11 +449,6 @@ def save_hdf5_1D_new(saver, i, params, dyn_var, ham, txt_type=0): Ekin_qtsh = dyn_var.compute_average_kinetic_energy_qtsh(); saver.save_scalar(t, "Ekin_ave_qtsh", Ekin_qtsh) - if "Etot_ave_qtsh" in saver.keywords and "Etot_ave_qtsh" in saver.np_data.keys(): - Ekin_qtsh = dyn_var.compute_average_kinetic_energy_qtsh(); - Etot_qtsh = Ekin_qtsh + Epot - saver.save_scalar(t, "Etot_ave_qtsh", Etot_qtsh) - def save_hdf5_2D(saver, i, states, txt_type=0): """ saver - can be either hdf5_saver or mem_saver diff --git a/src/nhamiltonian/libnhamiltonian.cpp b/src/nhamiltonian/libnhamiltonian.cpp index 6208fc121..52af847c4 100644 --- a/src/nhamiltonian/libnhamiltonian.cpp +++ b/src/nhamiltonian/libnhamiltonian.cpp @@ -288,6 +288,15 @@ void export_nhamiltonian_objects(){ vector (nHamiltonian::*expt_Ehrenfest_forces_tens_dia_v2)(CMATRIX& ampl_dia, vector& id_) = &nHamiltonian::Ehrenfest_forces_tens_dia; + complex (nHamiltonian::*expt_QTSH_energy_adi_v1)(CMATRIX& ampl_adi) + = &nHamiltonian::QTSH_energy_adi; + complex (nHamiltonian::*expt_QTSH_energy_adi_v2)(CMATRIX& ampl_adi, vector& id_) + = &nHamiltonian::QTSH_energy_adi; + complex (nHamiltonian::*expt_QTSH_energy_dia_v1)(CMATRIX& ampl_dia) + = &nHamiltonian::QTSH_energy_dia; + complex (nHamiltonian::*expt_QTSH_energy_dia_v2)(CMATRIX& ampl_dia, vector& id_) + = &nHamiltonian::QTSH_energy_dia; + //CMATRIX (nHamiltonian::*expt_QTSH_forces_adi_v1)(CMATRIX& ampl_adi, int lvl, int option, CMATRIX& transform) //= &nHamiltonian::QTSH_forces_adi; CMATRIX (nHamiltonian::*expt_QTSH_forces_adi_v1)(CMATRIX& ampl_adi, int lvl, int option) @@ -557,6 +566,11 @@ void export_nhamiltonian_objects(){ .def("Ehrenfest_forces_dia", expt_Ehrenfest_forces_dia_v2) // .def("Ehrenfest_forces_dia", expt_Ehrenfest_forces_dia_v2) + .def("QTSH_energy_adi", expt_QTSH_energy_adi_v1) + .def("QTSH_energy_adi", expt_QTSH_energy_adi_v2) + .def("QTSH_energy_dia", expt_QTSH_energy_dia_v1) + .def("QTSH_energy_dia", expt_QTSH_energy_dia_v2) + .def("QTSH_forces_adi", expt_QTSH_forces_adi_v1) .def("QTSH_forces_adi", expt_QTSH_forces_adi_v2) // .def("QTSH_forces_adi", expt_QTSH_forces_adi_v3) diff --git a/src/nhamiltonian/nHamiltonian.h b/src/nhamiltonian/nHamiltonian.h index be963d5ca..0b6f514af 100644 --- a/src/nhamiltonian/nHamiltonian.h +++ b/src/nhamiltonian/nHamiltonian.h @@ -572,14 +572,20 @@ class nHamiltonian{ - ///< In nHamiltonian_compute_QTSH_forces.cpp + ///< In nHamiltonian_compute_QTSH.cpp and nHamiltonian_compute_QTSH_forces.cpp + complex QTSH_energy_dia(CMATRIX& ampl_dia); + complex QTSH_energy_dia(CMATRIX& ampl_dia, vector& id_); CMATRIX QTSH_forces_dia_unit(CMATRIX& ampl_dia, int option); ///< QTSH forces in diabatic basis CMATRIX QTSH_forces_dia_unit(CMATRIX& ampl_dia); ///< QTSH forces in diabatic basis CMATRIX QTSH_forces_dia(CMATRIX& ampl_dia, int lvl, int option); ///< QTSH forces in diabatic basis CMATRIX QTSH_forces_dia(CMATRIX& ampl_dia, int lvl); ///< QTSH forces in diabatic basis - ///< In nHamiltonian_compute_QTSH_forces.cpp + ///< In nHamiltonian_compute_QTSH.cpp and nHamiltonian_compute_QTSH_forces.cpp + complex QTSH_energy_adi(CMATRIX& ampl_adi, CMATRIX& transform); + complex QTSH_energy_adi(CMATRIX& ampl_adi); + complex QTSH_energy_adi(CMATRIX& ampl_adi, vector& id_, CMATRIX& transform); + complex QTSH_energy_adi(CMATRIX& ampl_adi, vector& id_); CMATRIX QTSH_forces_adi_unit(CMATRIX& ampl_adi, int option, CMATRIX& transform); ///< QTSH forces in adiabatic basis CMATRIX QTSH_forces_adi_unit(CMATRIX& ampl_adi, int option); ///< QTSH forces in adiabatic basis CMATRIX QTSH_forces_adi_unit(CMATRIX& ampl_adi); ///< QTSH forces in adiabatic basis diff --git a/src/nhamiltonian/nHamiltonian_compute_QTSH.cpp b/src/nhamiltonian/nHamiltonian_compute_QTSH.cpp new file mode 100644 index 000000000..eda259a07 --- /dev/null +++ b/src/nhamiltonian/nHamiltonian_compute_QTSH.cpp @@ -0,0 +1,139 @@ +/********************************************************************************* +* Copyright (C) 2017-2022 Alexey V. Akimov +* +* This file is distributed under the terms of the GNU General Public License +* as published by the Free Software Foundation, either version 3 of +* the License, or (at your option) any later version. +* See the file LICENSE in the root directory of this distribution +* or . +* +*********************************************************************************/ +/** + \file nHamiltonian_compute_QTSH.cpp + \brief The file implements the calculations of the QTSH-related properties: + energies, forces and state-resolved force contributions + +*/ + +#if defined(USING_PCH) +#include "../pch.h" +#else +#include +#endif + +#include "nHamiltonian.h" +#include "../math_meigen/libmeigen.h" + + +/// liblibra namespace +namespace liblibra{ + +/// libnhamiltonian namespace +namespace libnhamiltonian{ + + +using namespace liblinalg; +using namespace libmeigen; + + + +complex nHamiltonian::QTSH_energy_dia(CMATRIX& ampl_dia){ +/** + Compute the coherence energy of QTSH in the diabatic representation. + The coherence energy in QTSH has the form of the off-diagonal terms of the Ehrenfest energy. +*/ + + if(ovlp_dia_mem_status==0){ cout<<"Error in QTSH_energy_dia(): the overlap matrix in the diabatic basis is not allocated \ + but it is needed for the calculations\n"; exit(0); } + + if(ham_dia_mem_status==0){ cout<<"Error in QTSH_energy_dia(): the diabatic Hamiltonian matrix is not allocated \ + but it is needed for the calculations\n"; exit(0); } + +// if(ampl_dia_mem_status==0){ cout<<"Error in QTSH_energy_dia(): the amplitudes of the diabatic states are\ +// not allocated, but they are needed for the calculations\n"; exit(0); } + + complex res; + + complex norm = (ampl_dia.H() * (*ovlp_dia) * ampl_dia).M[0]; + + CMATRIX* temp_diag = new CMATRIX(nadi, nadi); + for(int i=0;iset(i,i,(*ham_dia).get(i,i)); + } + + res = (ampl_dia.H() * (*ham_dia - *temp_diag) * ampl_dia).M[0]/norm; + + delete temp_diag; + + return res; + +} + + + +complex nHamiltonian::QTSH_energy_dia(CMATRIX& ampl_dia, vector& id_){ +/** + See the description of the QTSH_energy_dia(CMATRIX& ampl_dia) function +*/ + if(id_.size()==1){ + if(id_[0]==id){ return QTSH_energy_dia(ampl_dia); } + else{ cout<<"ERROR in force_dia: No Hamiltonian matching the requested id\n"; exit(0); } + } + else{ + vector next(id_.begin()+1,id_.end()); + return children[id_[1]]->QTSH_energy_dia(ampl_dia, next); + } +} + + + + +complex nHamiltonian::QTSH_energy_adi(CMATRIX& ampl_adi, CMATRIX& transform){ +/** + Compute the coherence energy of QTSH in the adiabatic representation. + The coherence energy in QTSH has the form of the off-diagonal terms of the Ehrenfest energy. +*/ + + if(ham_adi_mem_status==0){ cout<<"Error in QTSH_energy_adi(): the adiabatic Hamiltonian matrix is not allocated \ + but it is needed for the calculations\n"; exit(0); } + +// if(ampl_adi_mem_status==0){ cout<<"Error in QTSH_energy_adi(): the amplitudes of the adiabatic states are\ +// not allocated, but they are needed for the calculations\n"; exit(0); } + +// CMATRIX& T = transform; + CMATRIX T(transform); T.identity(); + + complex norm = (ampl_adi.H() * ampl_adi).M[0]; + + return (ampl_adi.H() * ( T.H() * complex(0.0, -1.0)* (*nac_adi) * T) * ampl_adi).M[0] / norm; + +} + +complex nHamiltonian::QTSH_energy_adi(CMATRIX& ampl_adi){ + CMATRIX I(nadi, nadi); I.identity(); + return QTSH_energy_adi(ampl_adi, I); +} + + +complex nHamiltonian::QTSH_energy_adi(CMATRIX& ampl_adi, vector& id_, CMATRIX& transform){ +/** + See the description of the QTSH_energy_adi(CMATRIX& ampl_adi) function +*/ + if(id_.size()==1){ + if(id_[0]==id){ return QTSH_energy_adi(ampl_adi); } + else{ cout<<"ERROR in force_dia: No Hamiltonian matching the requested id\n"; exit(0); } + } + else{ + vector next(id_.begin()+1,id_.end()); + return children[id_[1]]->QTSH_energy_adi(ampl_adi, next, transform); + } +} + +complex nHamiltonian::QTSH_energy_adi(CMATRIX& ampl_adi, vector& id_){ + CMATRIX I(nadi, nadi); I.identity(); + return QTSH_energy_adi(ampl_adi, id_, I); +} + +}// namespace libnhamiltonian +}// liblibra + diff --git a/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp b/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp index cfc0be807..c095daaf7 100644 --- a/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp +++ b/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp @@ -233,8 +233,6 @@ CMATRIX nHamiltonian::QTSH_forces_adi_unit(CMATRIX& ampl_adi, int option, CMATRI // CMATRIX& T = transform; CMATRIX T(transform); T.identity(); - CMATRIX temp_diag(nadi, nadi); - for(int n=0;nget(i,i) ); - } - res.M[n] = -( ampl_adi.H() * T.H() * ( *d1ham_adi[n] - temp_diag ) * T * ampl_adi ).M[0]; + res.M[n] = 0.0; } }// for n From b0ba21077bf4bdb4d1a7d245bbdedf3c5cb7e393 Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Thu, 30 May 2024 19:31:15 -0400 Subject: [PATCH 10/48] Resolve the Ham initialization problem --- src/dyn/Dynamics.cpp | 14 ++------------ src/dyn/dyn_ham.cpp | 6 +++--- src/nhamiltonian/nHamiltonian_compute_QTSH.cpp | 13 +------------ 3 files changed, 6 insertions(+), 27 deletions(-) diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index b1de9144c..85a02b1e1 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -1308,12 +1308,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, update_proj_adi(prms, dyn_var, ham); // Recompute NAC, Hvib, etc. in response to change of p - if(prms.use_qtsh==0){ - update_Hamiltonian_variables(prms, dyn_var, ham, ham_aux, py_funct, params, 1); - } - else{ - update_Hamiltonian_variables(prms, dyn_var, ham, ham_aux, py_funct, params, 3); - } + update_Hamiltonian_variables(prms, dyn_var, ham, ham_aux, py_funct, params, 1); // Update wave packet width in XF algorithm if(prms.decoherence_algo == 5 or prms.decoherence_algo == 6){ @@ -1394,12 +1389,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, } //ham_aux.copy_content(ham); - if(prms.use_qtsh==0){ - update_Hamiltonian_variables(prms, dyn_var, ham, ham_aux, py_funct, params, 1); - } - else{ - update_Hamiltonian_variables(prms, dyn_var, ham, ham_aux, py_funct, params, 3); - } + update_Hamiltonian_variables(prms, dyn_var, ham, ham_aux, py_funct, params, 1); //============== Begin the TSH part =================== diff --git a/src/dyn/dyn_ham.cpp b/src/dyn/dyn_ham.cpp index 36ab4fe98..ffac02774 100644 --- a/src/dyn/dyn_ham.cpp +++ b/src/dyn/dyn_ham.cpp @@ -146,7 +146,7 @@ void update_Hamiltonian_variables(dyn_control_params& prms, dyn_variables& dyn_v //exit(0); - if(update_type==0 || update_type==1 || update_type==3){ + if(update_type==0 || update_type==1){ //========================== Couplings =============================== // Don't update NACs - they may have been read in step 1 @@ -166,12 +166,12 @@ void update_Hamiltonian_variables(dyn_control_params& prms, dyn_variables& dyn_v //if(update_type==3){ p = *dyn_var.qtsh_p_k; } MATRIX p_quantum_dof(ndof, ntraj); - if(update_type==1){ + if(prms.use_qtsh==0 or dyn_var.qtsh_vars_status==0){ for(auto dof: prms.quantum_dofs){ for(int itraj = 0; itraj < ntraj; itraj++){ p_quantum_dof.set(dof, itraj, p.get(dof, itraj) ); } } } - else if(update_type==3){ + else{ for(auto dof: prms.quantum_dofs){ for(int itraj = 0; itraj < ntraj; itraj++){ p_quantum_dof.set(dof, itraj, dyn_var.qtsh_p_k->get(dof, itraj) ); } } diff --git a/src/nhamiltonian/nHamiltonian_compute_QTSH.cpp b/src/nhamiltonian/nHamiltonian_compute_QTSH.cpp index eda259a07..29aa250ec 100644 --- a/src/nhamiltonian/nHamiltonian_compute_QTSH.cpp +++ b/src/nhamiltonian/nHamiltonian_compute_QTSH.cpp @@ -52,20 +52,9 @@ complex nHamiltonian::QTSH_energy_dia(CMATRIX& ampl_dia){ // if(ampl_dia_mem_status==0){ cout<<"Error in QTSH_energy_dia(): the amplitudes of the diabatic states are\ // not allocated, but they are needed for the calculations\n"; exit(0); } - complex res; - complex norm = (ampl_dia.H() * (*ovlp_dia) * ampl_dia).M[0]; - CMATRIX* temp_diag = new CMATRIX(nadi, nadi); - for(int i=0;iset(i,i,(*ham_dia).get(i,i)); - } - - res = (ampl_dia.H() * (*ham_dia - *temp_diag) * ampl_dia).M[0]/norm; - - delete temp_diag; - - return res; + return (ampl_dia.H() * complex(0.0, -1.0)* (*nac_dia) * ampl_dia).M[0] / norm; } From 50ea0bc0992d1595e49ae957f6c561b42b61abad Mon Sep 17 00:00:00 2001 From: MohammadShakiba Date: Thu, 6 Jun 2024 21:56:50 -0400 Subject: [PATCH 11/48] bug fixes in step3 for single particle energies compuations --- src/libra_py/data_conv.py | 30 ++++++++++++++++++++- src/libra_py/packages/cp2k/methods.py | 37 ++++++++++++++++++++++++-- src/libra_py/workflows/nbra/mapping.py | 3 +-- src/libra_py/workflows/nbra/step3.py | 17 +++++++++--- 4 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/libra_py/data_conv.py b/src/libra_py/data_conv.py index 34bc780cd..b069ddf2f 100755 --- a/src/libra_py/data_conv.py +++ b/src/libra_py/data_conv.py @@ -578,4 +578,32 @@ def vasp_to_xyz(filename): f.close() - +def adf_to_xyz(filename, step=0): + """ + This function reads the ADF input file and return the coordinates + written in that file + Args: + filename (string): The path to the ADF input file + step (integer): This value is arbitray and just for naming of the + output coordinate file. + Returns: + None + """ + f = open(filename,'r') + lines = f.readlines() + f.close() + coordinates = [] + for i in range(len(lines)): + if 'atoms' in lines[i].lower(): + atoms_line = i + break + for i in range(atoms_line+1, len(lines)): + if 'end' in lines[i].lower().split(): + break + else: + coordinates.append(lines[i]) + f = open(f'coord-{step}.xyz','w') + f.write(f'{len(coordinates)}\n\n') + for i in range(len(coordinates)): + f.write(coordinates[i]) + f.close() diff --git a/src/libra_py/packages/cp2k/methods.py b/src/libra_py/packages/cp2k/methods.py index 7ed7ccdf7..7e6df67ea 100755 --- a/src/libra_py/packages/cp2k/methods.py +++ b/src/libra_py/packages/cp2k/methods.py @@ -320,7 +320,7 @@ def read_cp2k_tddfpt_log_file( params ): ci_coefficient = float( tmp_splitted_line[4] ) if ci_coefficient**2 > tolerance: - if int(tmp_splitted_line[0])>params["lowest_orbital"] and int(tmp_splitted_line[1])=params["lowest_orbital"] and int(tmp_splitted_line[1])<=params["highest_orbital"]: # We need to remove the paranthesis from the 2nd element of the temporary splitted line tmp_spin.append( tmp_splitted_line[1].replace('(','').replace(')','') ) tmp_state.append( [ int( tmp_splitted_line[0] ), int( tmp_splitted_line[2] ) ] ) @@ -331,7 +331,7 @@ def read_cp2k_tddfpt_log_file( params ): else: ci_coefficient = float( tmp_splitted_line[2] ) if ci_coefficient**2 > tolerance: - if int(tmp_splitted_line[0])>params["lowest_orbital"] and int(tmp_splitted_line[1])=params["lowest_orbital"] and int(tmp_splitted_line[1])<=params["highest_orbital"]: tmp_spin.append( "alp" ) tmp_state.append( [ int( tmp_splitted_line[0] ), int( tmp_splitted_line[1] ) ] ) tmp_state_coefficients.append( ci_coefficient ) @@ -2278,3 +2278,36 @@ def atom_components_cp2k(filename): return indices +def read_efg_data(filename): + """ + This function reads the electric-field gradient data in .efg files + Args: + filename (string): The path to efg file + Returns: + atoms (list): A list containing the atoms in the EFG file + efg_tensor (numpy array): A numpy array containing the EFG tensor + for all atoms in the system + """ + f = open(filename, 'r') + lines = f.readlines() + f.close() + + efg_lines = [] + for i in range(len(lines)): + if 'efg tensor' in lines[i].lower() and len(lines[i].split())==7: + efg_lines.append(i) + atoms = [] + efg_tensor = [] + for i in efg_lines: + tmp = lines[i].split() + atoms.append(tmp[1]) + tmp_tensor = [float(tmp[4]), float(tmp[5]), float(tmp[6])] + for j in [i+1,i+2]: + tmp = lines[j].split() + tmp_tensor.append(float(tmp[0])) + tmp_tensor.append(float(tmp[1])) + tmp_tensor.append(float(tmp[2])) + efg_tensor.append(tmp_tensor) + efg_tensor = np.array(efg_tensor) + return atoms, efg_tensor + diff --git a/src/libra_py/workflows/nbra/mapping.py b/src/libra_py/workflows/nbra/mapping.py index dc3809fd0..faacecd64 100755 --- a/src/libra_py/workflows/nbra/mapping.py +++ b/src/libra_py/workflows/nbra/mapping.py @@ -252,12 +252,11 @@ def energy_mat_arb(SD, e, dE): CMATRIX(N,N): the matrix of energies in the SD basis. Here, N = len(SD) - the number of SDs. """ - n = len(SD) #E = CMATRIX(n,n) E = np.zeros((n,n)) - E0 = 0.0 #energy_arb(SD[0], e) + dE[0]*(1.0+0.0j) + E0 = energy_arb(SD[0], e) + dE[0] #*(1.0+0.0j) for i in range(0,n): #E.set(i,i, energy_arb(SD[i], e) + dE[i]*(1.0+0.0j) - E0 ) E[i,i] = energy_arb(SD[i], e) + dE[i] - E0 diff --git a/src/libra_py/workflows/nbra/step3.py b/src/libra_py/workflows/nbra/step3.py index e7dcc8c24..3466b7531 100755 --- a/src/libra_py/workflows/nbra/step3.py +++ b/src/libra_py/workflows/nbra/step3.py @@ -1855,6 +1855,7 @@ def sort_unique_SD_basis_scipy( step, sd_states_unique, sd_states_reindexed, _p #E_ks_MATRIX = data_conv.nparray2MATRIX( E_ks ) #E_this_sd = mapping.energy_mat_arb( sd_states_reindexed, E_ks_MATRIX, SD_energy_corr ) E_this_sd = mapping.energy_mat_arb( sd_states_reindexed, E_ks, SD_energy_corr ) + #print('Flag E_this_sd before:', E_this_sd) # Make a list for the final ordering of the sd_states_unique. # This will not contain the ground state, which we will manually add later. @@ -1869,9 +1870,10 @@ def sort_unique_SD_basis_scipy( step, sd_states_unique, sd_states_reindexed, _p # Obtain the indexing fo the SDs by their energies reindex = np.argsort(e) # Turning the CMATRIX into numpy - E_this_sd = E_this_sd.real + E_this_sd = E_this_sd.real#() # Only the diagonals are needed #E_this_sd = np.diag( data_conv.MATRIX2nparray(E_this_sd) ) + E_this_sd = np.diag( E_this_sd ) if sorting_type == "identity": @@ -1903,7 +1905,9 @@ def sort_unique_SD_basis_scipy( step, sd_states_unique, sd_states_reindexed, _p for i in range(1,len(reindex)): sd_states_unique_sorted.append( sd_states_unique[ int(reindex[i])-1 ] ) - + #print('Flag E_this_sd:',E_this_sd.shape) + #print('Flag E_this_sd itself:',E_this_sd) + #print('Flag np.diag(E_this_sd):',np.diag(E_this_sd)) E_this_sd_sparse = sp.csc_matrix( np.diag(E_this_sd) ) return E_this_sd_sparse, sd_states_unique_sorted, sd_states_reindexed_sorted, reindex_nsteps @@ -2289,7 +2293,7 @@ def run_step3_sd_nacs_libint(params): # number of occupied and unoccupied KS states num_occ = ks_homo_index - min_band+1 - num_unocc = max_band-ks_homo_index + 1 + num_unocc = max_band-ks_homo_index # + 1 # The new KS active space and HOMO index ks_active_space, ks_homo_index_1 = make_active_space(num_occ, @@ -2297,7 +2301,11 @@ def run_step3_sd_nacs_libint(params): params['data_dim'], params['npz_file_ks_homo_index']) params['active_space'] = ks_active_space - + #print('Flag ks_active_space:', ks_active_space) + #print('Flag num_occ:',num_occ) + #print('Flag num_unocc',num_unocc) + #print('Flag params[data_dim]',params['data_dim']) + #print('Flag npz file ks homo index',params['npz_file_ks_homo_index']) # The KS orbital indices ks_orbital_indicies = range(min_band, max_band+1) @@ -2414,6 +2422,7 @@ def run_step3_sd_nacs_libint(params): if step > start_time: E_midpoint = 0.5*(E_sd_step+E_sd_step_plus) + #print('Flag E_midpoint.shape:', E_midpoint.shape) sp.save_npz(F'{params["path_to_save_sd_Hvibs"]}/Hvib_sd_{step-1}_re.npz',E_midpoint) E_sd_step_plus = E_sd_step print('Done with sorting and computing the SDs energies. Elapsed time:',time.time()-t2) From e15f771ce20dfb3319684ab88b23025c4b9db000 Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Fri, 7 Jun 2024 12:40:11 -0400 Subject: [PATCH 12/48] Propagate p_k with the direct expression --- src/dyn/Dynamics.cpp | 9 ++++++--- src/dyn/dyn_methods_qtsh.cpp | 4 +++- src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp | 11 ++++++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index 85a02b1e1..318c98458 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -1268,7 +1268,8 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, } *dyn_var.p = *dyn_var.p + 0.5 * prms.dt * (*dyn_var.f); - if(prms.use_qtsh==1){ *dyn_var.qtsh_p_k = *dyn_var.p + 0.5 * prms.dt * (*dyn_var.qtsh_f_nc); } + //if(prms.use_qtsh==1){ *dyn_var.qtsh_p_k = *dyn_var.p + 0.5 * prms.dt * (*dyn_var.qtsh_f_nc); } + if(prms.use_qtsh==1){ *dyn_var.qtsh_p_k = *dyn_var.p + 0.5 * compute_dkinemat(dyn_var, ham); } // Kinetic constraint for(cdof = 0; cdof < prms.constrained_dofs.size(); cdof++){ @@ -1284,7 +1285,8 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, dyn_var.q->add(dof, traj, invM.get(dof,0) * dyn_var.p->get(dof,traj) * prms.dt ); if(prms.use_qtsh==1){ - dyn_var.q->add(dof, traj, 0.5*invM.get(dof,0) * dyn_var.qtsh_f_nc->get(dof, traj) * pow(prms.dt, 2.0) ); + //dyn_var.q->add(dof, traj, 0.5*invM.get(dof,0) * dyn_var.qtsh_f_nc->get(dof, traj) * pow(prms.dt, 2.0) ); + dyn_var.q->add(dof, traj, invM.get(dof,0) * compute_dkinemat(dyn_var, ham).get(dof, traj) * prms.dt ); } if(prms.entanglement_opt==22){ dyn_var.q->add(dof, traj, invM.get(dof,0) * gamma.get(dof,traj) * prms.dt ); @@ -1371,7 +1373,8 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, } *dyn_var.p = *dyn_var.p + 0.5*prms.dt* (*dyn_var.f); - if(prms.use_qtsh==1){ *dyn_var.qtsh_p_k = *dyn_var.p + 0.5*prms.dt* (*dyn_var.qtsh_f_nc); } + //if(prms.use_qtsh==1){ *dyn_var.qtsh_p_k = *dyn_var.p + 0.5*prms.dt* (*dyn_var.qtsh_f_nc); } + if(prms.use_qtsh==1){ *dyn_var.qtsh_p_k = *dyn_var.p + 0.5* compute_dkinemat(dyn_var, ham); } // Kinetic constraint for(cdof=0; cdofdc1_adi[idof]->get(k, n).real() * dyn_var.dm_adi[itraj]->get(k, n).imag(); + //sum += ham.children[itraj]->dc1_adi[idof]->get(k, n).real() * dyn_var.dm_adi[itraj]->get(k, n).imag(); + sum += (T.H() * (*ham.children[itraj]->dc1_adi[idof]) * T).get(k, n).real() * (T.H() * (*dyn_var.dm_adi[itraj]) * T ).get(k, n).imag(); }// for k }// for n diff --git a/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp b/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp index c095daaf7..bb24962ee 100644 --- a/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp +++ b/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp @@ -247,7 +247,16 @@ CMATRIX nHamiltonian::QTSH_forces_adi_unit(CMATRIX& ampl_adi, int option, CMATRI tmp = (T.H() * (*dc1_adi[n]) * T ).H() * (T.H() * (*ham_adi) * T); tmp = tmp + tmp.H(); - res.M[n] = +( ampl_adi.H() * tmp * ampl_adi ).M[0]; + res.M[n] = ( ampl_adi.H() * tmp * ampl_adi ).M[0]; + + //for(int i=0; i Date: Mon, 10 Jun 2024 10:12:42 -0400 Subject: [PATCH 13/48] Bug fixes for step3; removing the scratch and do more steps in data generation for ml mapping approach --- src/libra_py/packages/cp2k/methods.py | 55 ++++------------ src/libra_py/workflows/nbra/generate_data.py | 69 ++++++++++---------- src/libra_py/workflows/nbra/ml_map.py | 47 ++++++------- 3 files changed, 75 insertions(+), 96 deletions(-) diff --git a/src/libra_py/packages/cp2k/methods.py b/src/libra_py/packages/cp2k/methods.py index 7e6df67ea..b89025603 100755 --- a/src/libra_py/packages/cp2k/methods.py +++ b/src/libra_py/packages/cp2k/methods.py @@ -324,7 +324,7 @@ def read_cp2k_tddfpt_log_file( params ): # We need to remove the paranthesis from the 2nd element of the temporary splitted line tmp_spin.append( tmp_splitted_line[1].replace('(','').replace(')','') ) tmp_state.append( [ int( tmp_splitted_line[0] ), int( tmp_splitted_line[2] ) ] ) - tmp_state_coefficients.append( ci_coefficient ) + tmp_state_coefficients.append( ci_coefficient ) # Here, we have the spin-unpolarize Kohn-Sham basis # For this case, spin-components will just return all alpha @@ -1694,7 +1694,7 @@ def gaussian_function_vector(a_vec, mu_vec, sigma, num_points, x_min, x_max): -def aux_pdos(c1, atoms, pdos_files, margin, homo_occ, orbitals_cols, sigma, npoints, ave_pdos_convolved_all, labels): +def aux_pdos(c1, atoms, pdos_files, margin, homo_occ, orbitals_cols, sigma, npoints, ave_pdos_convolved_all, orbitals, labels): """ c1 - index of the atom kinds atoms - the @@ -1722,6 +1722,8 @@ def aux_pdos(c1, atoms, pdos_files, margin, homo_occ, orbitals_cols, sigma, npoi e_max = np.max(pdos_ave[:,1]) + margin homo_level = np.max(np.where(pdos_ave[:,2]==homo_occ)) homo_energy = pdos_ave[:,1][homo_level] + if len(labels)==0: + labels = [] for c3, orbital_cols in enumerate(orbitals_cols): try: @@ -1730,10 +1732,12 @@ def aux_pdos(c1, atoms, pdos_files, margin, homo_occ, orbitals_cols, sigma, npoi ave_pdos_convolved_all.append(ave_pdos_convolved) pdos_label = F"{atoms[1][c1]}, {orbitals[c3]}" labels.append(pdos_label) +# print('here') except: +# print('in the pass') pass - return ave_energy_grid, homo_energy, ave_pdos_convolved_all + return ave_energy_grid, homo_energy, ave_pdos_convolved_all, labels def pdos(params): @@ -1779,18 +1783,20 @@ def pdos(params): sigma = params['sigma'] shift = params['shift'] is_spin_polarized = params["is_spin_polarized"] - + labels = [] + labels_alp = [] + labels_bet = [] if is_spin_polarized == 0: # non-polarized case homo_occ = 2.0 - labels = [] + #labels = [] ave_pdos_convolved_all = [] for c1,i in enumerate(atoms[0]): # Finding all the pdos files pdos_files = glob.glob(path_to_all_pdos+F'/*k{i}*.pdos') - ave_energy_grid, homo_energy, ave_pdos_convolved_all = aux_pdos(c1, atoms, pdos_files, shift, homo_occ, orbitals_cols, sigma, npoints, ave_pdos_convolved_all, labels) + ave_energy_grid, homo_energy, ave_pdos_convolved_all, labels = aux_pdos(c1, atoms, pdos_files, shift, homo_occ, orbitals_cols, sigma, npoints, ave_pdos_convolved_all, orbitals, labels) """ # Finding all the pdos files @@ -1837,9 +1843,9 @@ def pdos(params): pdos_files_alp = glob.glob(path_to_all_pdos+F'/*ALPHA*k{i}*.pdos') pdos_files_bet = glob.glob(path_to_all_pdos+F'/*BETA*k{i}*.pdos') - ave_energy_grid_alp, homo_energy_alp, ave_pdos_convolved_all_alp = aux_pdos(c1, atoms, pdos_files_alp, shift, homo_occ, orbitals_cols, sigma, npoints, ave_pdos_convolved_all_alp, labels_alp) + ave_energy_grid_alp, homo_energy_alp, ave_pdos_convolved_all_alp, labels_alp = aux_pdos(c1, atoms, pdos_files_alp, shift, homo_occ, orbitals_cols, sigma, npoints, ave_pdos_convolved_all_alp, orbitals, labels_alp) - ave_energy_grid_bet, homo_energy_bet, ave_pdos_convolved_all_bet = aux_pdos(c1, atoms, pdos_files_bet, shift, homo_occ, orbitals_cols, sigma, npoints, ave_pdos_convolved_all_bet, labels_bet) + ave_energy_grid_bet, homo_energy_bet, ave_pdos_convolved_all_bet, labels_bet = aux_pdos(c1, atoms, pdos_files_bet, shift, homo_occ, orbitals_cols, sigma, npoints, ave_pdos_convolved_all_bet, orbitals, labels_bet) ave_pdos_convolved_all_alp = np.array(ave_pdos_convolved_all_alp) @@ -2278,36 +2284,3 @@ def atom_components_cp2k(filename): return indices -def read_efg_data(filename): - """ - This function reads the electric-field gradient data in .efg files - Args: - filename (string): The path to efg file - Returns: - atoms (list): A list containing the atoms in the EFG file - efg_tensor (numpy array): A numpy array containing the EFG tensor - for all atoms in the system - """ - f = open(filename, 'r') - lines = f.readlines() - f.close() - - efg_lines = [] - for i in range(len(lines)): - if 'efg tensor' in lines[i].lower() and len(lines[i].split())==7: - efg_lines.append(i) - atoms = [] - efg_tensor = [] - for i in efg_lines: - tmp = lines[i].split() - atoms.append(tmp[1]) - tmp_tensor = [float(tmp[4]), float(tmp[5]), float(tmp[6])] - for j in [i+1,i+2]: - tmp = lines[j].split() - tmp_tensor.append(float(tmp[0])) - tmp_tensor.append(float(tmp[1])) - tmp_tensor.append(float(tmp[2])) - efg_tensor.append(tmp_tensor) - efg_tensor = np.array(efg_tensor) - return atoms, efg_tensor - diff --git a/src/libra_py/workflows/nbra/generate_data.py b/src/libra_py/workflows/nbra/generate_data.py index 5b3829ba7..56d18f74b 100644 --- a/src/libra_py/workflows/nbra/generate_data.py +++ b/src/libra_py/workflows/nbra/generate_data.py @@ -247,10 +247,10 @@ def distribute_jobs(params): Returns: None """ - critical_params = ['guess_input_template', 'trajectory_xyz_file', 'reference_input_template', 'istep', - 'fstep', 'submit_template', 'software_load_instructions'] + critical_params = ['guess_input_template', 'trajectory_xyz_file', 'reference_input_template', 'user_steps', + 'submit_template', 'software_load_instructions'] default_params = {'prefix': 'libra', 'scratch': True, 'do_more': False, 'do_more_steps': 10, 'random_selection': True, - 'guess_dir': './guess', 'do_guess': True, 'reference_dir': './ref', 'do_ref': True, 'user_steps': [], + 'guess_dir': './guess', 'do_guess': True, 'reference_dir': './ref', 'do_ref': True, 'guess_software': 'cp2k', 'guess_software_exe': 'cp2k.psmp', 'guess_mpi_exe': 'mpirun', 'reference_software': 'cp2k', 'reference_software_exe': 'cp2k.psmp', 'reference_mpi_exe': 'mpirun', 'reference_steps': 10, 'njobs': 2, 'nprocs': 2, 'remove_raw_outputs': True, 'submit_exe': 'sbatch' @@ -258,52 +258,55 @@ def distribute_jobs(params): # First load the default parameters etc comn.check_input(params, default_params, critical_params) # Now let's create the random numbers - nsteps = params["fstep"]-params["istep"] + # nsteps = params["fstep"]-params["istep"] + nsteps = len(params["user_steps"]) # + params["istep"] = min(params["user_steps"]) + params["fstep"] = max(params["user_steps"]) ref_steps = params["reference_steps"] if params["scratch"]==params["do_more"]: raise("'scratch' and 'do_more' cannot get the same value at the same time!") if params["do_guess"]==False and params["do_ref"]==False: raise("do_guess and do_ref cannot get False value at the same time!") - if params["scratch"]: + #if params["scratch"]: # Remove evrything including the data - os.system("rm -rf job* sample_files shuffled_indices.npy") # Removes the job folders and the random indices + #os.system("rm -rf job* sample_files")# shuffled_indices.npy") # Removes the job folders and the random indices #os.system('rm find . -name "*.npy" ') # This seems to be brutal! It removes everything :)) - shuffled_indices = np.arange(params["istep"], params["fstep"]) - params["steps"] = list(range(params["istep"], params["fstep"])) - if params["random_selection"]: - np.random.shuffle(shuffled_indices) - np.save('shuffled_indices.npy', shuffled_indices) - params["reference_steps"] = shuffled_indices[0:ref_steps].tolist() + #shuffled_indices = np.arange(params["istep"], params["fstep"]) + #params["steps"] = list(range(params["istep"], params["fstep"])) + #if params["random_selection"]: + # np.random.shuffle(shuffled_indices) + #np.save('shuffled_indices.npy', shuffled_indices) + #params["reference_steps"] = shuffled_indices[0:ref_steps].tolist() # Here we add the first and last geometries # since this is an interpolation - if params["istep"] not in params["reference_steps"]: - params["reference_steps"].append(params["istep"]) - if params["fstep"] not in params["reference_steps"]: - params["reference_steps"].append(params["fstep"]) + #if params["istep"] not in params["reference_steps"]: + # params["reference_steps"].append(params["istep"]) + #if params["fstep"] not in params["reference_steps"]: + # params["reference_steps"].append(params["fstep"]) - elif params["do_more"]: - # find how many steps were done in the reference_dir - ref_files = glob.glob(f'{params["reference_dir"]}/*.npy') - steps_done = find_steps(ref_files) - #print(steps_done) - if len(steps_done)==0: - raise("Doing more steps is requested but the reference directory is empty!") - ref_steps = len(steps_done) - #print(ref_steps) - more_steps = params["do_more_steps"] - shuffled_indices = np.load("shuffled_indices.npy") - params["reference_steps"] = shuffled_indices[ref_steps:ref_steps+more_steps].tolist() - #print(params['reference_steps']) - # For do_more, we don't need to add the first and last step since they are already computed in "scratch" - params["steps"] = np.sort(params["reference_steps"]).tolist() # shuffled_indices[ref_steps:ref_steps+more_steps].tolist() + #elif params["do_more"]: + # # find how many steps were done in the reference_dir + # ref_files = glob.glob(f'{params["reference_dir"]}/*.npy') + # steps_done = find_steps(ref_files) + # #print(steps_done) + # if len(steps_done)==0: + # raise("Doing more steps is requested but the reference directory is empty!") + # ref_steps = len(steps_done) + # #print(ref_steps) + # more_steps = params["do_more_steps"] + # shuffled_indices = np.load("shuffled_indices.npy") + # params["reference_steps"] = shuffled_indices[ref_steps:ref_steps+more_steps].tolist() + # #print(params['reference_steps']) + # # For do_more, we don't need to add the first and last step since they are already computed in "scratch" + # params["steps"] = np.sort(params["reference_steps"]).tolist() # shuffled_indices[ref_steps:ref_steps+more_steps].tolist() # Create guess and reference directories os.system(f'mkdir {params["reference_dir"]} {params["guess_dir"]}') - if (len(params["steps"])/params["njobs"])<2: - raise("The division of steps/njobs is less than 2! Please select smaller number of jobs.") if len(params["user_steps"])>0: params["steps"] = params["user_steps"] params["reference_steps"] = params["user_steps"] + if (len(params["steps"])/params["njobs"])<2: + raise("The division of steps/njobs is less than 2! Please select smaller number of jobs.") steps_split = np.array_split(params["steps"], params["njobs"]) #os.system(F"mkdir {res_directory}") # Read the submit file diff --git a/src/libra_py/workflows/nbra/ml_map.py b/src/libra_py/workflows/nbra/ml_map.py index eea7c50b5..4f90b0f47 100644 --- a/src/libra_py/workflows/nbra/ml_map.py +++ b/src/libra_py/workflows/nbra/ml_map.py @@ -622,25 +622,28 @@ def compute_properties(params, models, input_scalers, output_scalers): # Do the error analysis only for case the output is Kohn-Sham Hamiltonian matrix #if params["output_property"]!="kohn_sham" or params["output_property"]!="hamiltonian": # raise("Error analysis can be done only for the case 'output_property' is set to the Hamiltonian 'kohn_sham' or 'hamiltonian'...") - ks_ham_mat_ref = np.load(f'{params["path_to_output_mats"]}/{params["prefix"]}_ref_{params["output_property"]}_{step}.npy') - eigenvalues_ref, eigenvectors_ref = CP2K_methods.compute_energies_coeffs(ks_ham_mat_ref, atomic_overlap) - # We only save the eigenvalues but not the eigenvectors of the reference calculations - # The first reason is because we want to plot them and then we'll do the error analysis of all - # molecular orbitals. The second reason is that we compute the \epsilon_i=<\psi_{i_{ref}}|\psi_{i_{ml}}> for - # eigenvectors and that property will be saved. If we're about to save the eigenvectors, it will occupy - # a lot of disk space - if params["save_ref_eigenvalues"] or params["save_ref_eigenvectors"]: - if not os.path.exists(f"{params['path_to_save_ref_mos']}"): - os.system(f"mkdir {params['path_to_save_ref_mos']}") - if params["save_ref_eigenvalues"]: - np.save(f"{params['path_to_save_ref_mos']}/E_ref_{step}.npy", eigenvalues_ref) # [lowest_orbital-1:highest_orbital]) - if params["save_ref_eigenvectors"]: - np.save(f"{params['path_to_save_ref_mos']}/mos_ref_{step}.npy", eigenvectors_ref) #[lowest_orbital-1:highest_orbital,:][:,lowest_orbital-1:highest_orbital]) - ml_ref_overlap = compute_mo_overlaps(params, eigenvectors_ref, eigenvectors, step, step) #[lowest_orbital-1:highest_orbital,:][:,lowest_orbital-1:highest_orbital] - np.save(f"../error_data/epsilon_{step}.npy", np.diag(ml_ref_overlap)) # Only the diagonal elements - # The other error measurement is the absolute value of the Hamiltonian matrices difference - ham_diff = np.abs(ks_ham_mat-ks_ham_mat_ref) - np.save(f"../error_data/Ham_diff_ave_{step}.npy", np.average(ham_diff)) + try: + ks_ham_mat_ref = np.load(f'{params["path_to_output_mats"]}/{params["prefix"]}_ref_{params["output_property"]}_{step}.npy') + eigenvalues_ref, eigenvectors_ref = CP2K_methods.compute_energies_coeffs(ks_ham_mat_ref, atomic_overlap) + # We only save the eigenvalues but not the eigenvectors of the reference calculations + # The first reason is because we want to plot them and then we'll do the error analysis of all + # molecular orbitals. The second reason is that we compute the \epsilon_i=<\psi_{i_{ref}}|\psi_{i_{ml}}> for + # eigenvectors and that property will be saved. If we're about to save the eigenvectors, it will occupy + # a lot of disk space + if params["save_ref_eigenvalues"] or params["save_ref_eigenvectors"]: + if not os.path.exists(f"{params['path_to_save_ref_mos']}"): + os.system(f"mkdir {params['path_to_save_ref_mos']}") + if params["save_ref_eigenvalues"]: + np.save(f"{params['path_to_save_ref_mos']}/E_ref_{step}.npy", eigenvalues_ref) # [lowest_orbital-1:highest_orbital]) + if params["save_ref_eigenvectors"]: + np.save(f"{params['path_to_save_ref_mos']}/mos_ref_{step}.npy", eigenvectors_ref) #[lowest_orbital-1:highest_orbital,:][:,lowest_orbital-1:highest_orbital]) + ml_ref_overlap = compute_mo_overlaps(params, eigenvectors_ref, eigenvectors, step, step) #[lowest_orbital-1:highest_orbital,:][:,lowest_orbital-1:highest_orbital] + np.save(f"../error_data/epsilon_{step}.npy", np.diag(ml_ref_overlap)) # Only the diagonal elements + # The other error measurement is the absolute value of the Hamiltonian matrices difference + ham_diff = np.abs(ks_ham_mat-ks_ham_mat_ref) + np.save(f"../error_data/Ham_diff_ave_{step}.npy", np.average(ham_diff)) + except: + pass if params["save_ml_ham"]: if not os.path.exists("../ml_hams"): os.system(f"mkdir ../ml_hams") @@ -702,10 +705,10 @@ def compute_properties(params, models, input_scalers, output_scalers): # Now let's run the calculations --- We only need the Total energy so I can simply grep it to # not to use a lot of disk space but for now, I keep it this way # ================= Only for ML assessment project I grep the output files so that the log file size is small - #os.system(F"{data_gen_params_1['reference_mpi_exe']} -np {params['nprocs']} {data_gen_params_1['reference_software_exe']} -i input_{tmp_prefix}_{step}.inp -o output_{tmp_prefix}_{step}.log") - os.system(F"{data_gen_params_1['reference_mpi_exe']} -np {params['nprocs']} {data_gen_params_1['reference_software_exe']} -i input_{tmp_prefix}_{step}.inp | grep -A 30 'SCF WAVEFUNCTION OPTIMIZATION' > output_{tmp_prefix}_{step}.out") + os.system(F"{data_gen_params_1['reference_mpi_exe']} -np {params['nprocs']} {data_gen_params_1['reference_software_exe']} -i input_{tmp_prefix}_{step}.inp -o output_{tmp_prefix}_{step}.out") + #os.system(F"{data_gen_params_1['reference_mpi_exe']} -np {params['nprocs']} {data_gen_params_1['reference_software_exe']} -i input_{tmp_prefix}_{step}.inp | grep -A 30 'SCF WAVEFUNCTION OPTIMIZATION' > output_{tmp_prefix}_{step}.out") # ================= These files can be large... we can setup a flag to remove them but for ML assessment I remove them - os.system(f"rm {output_name}") + #os.system(f"rm {output_name}") # The algorithm for running the calculations #if params["compute_total_energy"]: # we have to make a cp2k input file based on the reference input From fbc1c2e1d333c47199f7a1a9742acae27d64ea13 Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Tue, 11 Jun 2024 10:02:40 -0400 Subject: [PATCH 14/48] Debug QTSH in the adiabatic representation --- src/dyn/Dynamics.cpp | 16 +++++----------- src/dyn/Energy_and_Forces.cpp | 8 ++++---- src/dyn/dyn_control_params.h | 5 +++++ src/dyn/dyn_ham.cpp | 14 ++------------ src/dyn/libdyn.cpp | 2 +- 5 files changed, 17 insertions(+), 28 deletions(-) diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index 318c98458..17d8cae87 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -1268,8 +1268,8 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, } *dyn_var.p = *dyn_var.p + 0.5 * prms.dt * (*dyn_var.f); - //if(prms.use_qtsh==1){ *dyn_var.qtsh_p_k = *dyn_var.p + 0.5 * prms.dt * (*dyn_var.qtsh_f_nc); } - if(prms.use_qtsh==1){ *dyn_var.qtsh_p_k = *dyn_var.p + 0.5 * compute_dkinemat(dyn_var, ham); } + //if(prms.use_qtsh==1){ *dyn_var.p = *dyn_var.p + 0.5 * compute_dkinemat(dyn_var, ham); } + if(prms.use_qtsh==1){ *dyn_var.p = *dyn_var.p + 0.5 * prms.dt * (*dyn_var.qtsh_f_nc); } // Kinetic constraint for(cdof = 0; cdof < prms.constrained_dofs.size(); cdof++){ @@ -1284,10 +1284,6 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, for(dof=0; dofadd(dof, traj, invM.get(dof,0) * dyn_var.p->get(dof,traj) * prms.dt ); - if(prms.use_qtsh==1){ - //dyn_var.q->add(dof, traj, 0.5*invM.get(dof,0) * dyn_var.qtsh_f_nc->get(dof, traj) * pow(prms.dt, 2.0) ); - dyn_var.q->add(dof, traj, invM.get(dof,0) * compute_dkinemat(dyn_var, ham).get(dof, traj) * prms.dt ); - } if(prms.entanglement_opt==22){ dyn_var.q->add(dof, traj, invM.get(dof,0) * gamma.get(dof,traj) * prms.dt ); } @@ -1373,8 +1369,8 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, } *dyn_var.p = *dyn_var.p + 0.5*prms.dt* (*dyn_var.f); - //if(prms.use_qtsh==1){ *dyn_var.qtsh_p_k = *dyn_var.p + 0.5*prms.dt* (*dyn_var.qtsh_f_nc); } - if(prms.use_qtsh==1){ *dyn_var.qtsh_p_k = *dyn_var.p + 0.5* compute_dkinemat(dyn_var, ham); } + //if(prms.use_qtsh==1){ *dyn_var.p = *dyn_var.p + 0.5* compute_dkinemat(dyn_var, ham); } + if(prms.use_qtsh==1){ *dyn_var.p = *dyn_var.p + 0.5 * prms.dt * (*dyn_var.qtsh_f_nc); } // Kinetic constraint for(cdof=0; cdof potential_energies(dyn_control_params& prms, dyn_variables& dyn_v } // Adiabatic else if(prms.rep_force==1){ - CMATRIX coeff(nadi, 1); + //CMATRIX coeff(nadi, 1); for(itraj=0; itrajcol(itraj); - res[itraj] += ham.QTSH_energy_adi(coeff, id, *(dyn_vars.proj_adi[itraj]) ).real(); // coherence energy + // coeff = dyn_vars.ampl_adi->col(itraj); + // res[itraj] += ham.QTSH_energy_adi(coeff, id, *(dyn_vars.proj_adi[itraj]) ).real(); // coherence energy }// for itraj }// rep_force == 1 @@ -377,7 +377,7 @@ void update_forces(dyn_control_params& prms, dyn_variables& dyn_vars, nHamiltoni *dyn_vars.f = ham.forces_adi(effective_states).real(); }// rep_force == 1 - // Non-classical term calculations + // Off-diagonal Hellmann-Feynman force calculations if(prms.rep_force==0){ *dyn_vars.qtsh_f_nc = ham.QTSH_forces_dia(*dyn_vars.ampl_dia, 1).real(); } diff --git a/src/dyn/dyn_control_params.h b/src/dyn/dyn_control_params.h index bf9e68d54..95df49499 100755 --- a/src/dyn/dyn_control_params.h +++ b/src/dyn/dyn_control_params.h @@ -459,6 +459,11 @@ class dyn_control_params{ //=========== QTSH options ========== /** Whether to use QTSH + + Options: + + - 0: don't apply [ default ] + - 1: use it */ int use_qtsh; diff --git a/src/dyn/dyn_ham.cpp b/src/dyn/dyn_ham.cpp index ffac02774..7348b29ad 100644 --- a/src/dyn/dyn_ham.cpp +++ b/src/dyn/dyn_ham.cpp @@ -39,7 +39,6 @@ void update_Hamiltonian_variables(dyn_control_params& prms, dyn_variables& dyn_v - 0: in response to changed q - 1: in response to changed p - 2: in response to changed electronic amplitudes - - 3: in response to changed p_k in QTSH The key parameters in the `prms` are: @@ -163,18 +162,9 @@ void update_Hamiltonian_variables(dyn_control_params& prms, dyn_variables& dyn_v int ndof = dyn_var.ndof; int ntraj = dyn_var.ntraj; - //if(update_type==3){ p = *dyn_var.qtsh_p_k; } - MATRIX p_quantum_dof(ndof, ntraj); - if(prms.use_qtsh==0 or dyn_var.qtsh_vars_status==0){ - for(auto dof: prms.quantum_dofs){ - for(int itraj = 0; itraj < ntraj; itraj++){ p_quantum_dof.set(dof, itraj, p.get(dof, itraj) ); } - } - } - else{ - for(auto dof: prms.quantum_dofs){ - for(int itraj = 0; itraj < ntraj; itraj++){ p_quantum_dof.set(dof, itraj, dyn_var.qtsh_p_k->get(dof, itraj) ); } - } + for(auto dof: prms.quantum_dofs){ + for(int itraj = 0; itraj < ntraj; itraj++){ p_quantum_dof.set(dof, itraj, p.get(dof, itraj) ); } } ham.compute_nac_dia(p_quantum_dof, iM, 0, 1); diff --git a/src/dyn/libdyn.cpp b/src/dyn/libdyn.cpp index a755b4692..923cc0236 100755 --- a/src/dyn/libdyn.cpp +++ b/src/dyn/libdyn.cpp @@ -100,7 +100,7 @@ void export_dyn_control_params_objects(){ .def_readwrite("fssh3_max_steps", &dyn_control_params::fssh3_max_steps) .def_readwrite("fssh3_err_tol", &dyn_control_params::fssh3_err_tol) - ///================= FSSH3 specific ==================== + ///================= QTSH specific ==================== .def_readwrite("use_qtsh", &dyn_control_params::use_qtsh) ///================= Decoherence options ========================================= From 79c81da5c1f8ff9737c8736635c026aae1c64377 Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Tue, 11 Jun 2024 18:47:21 -0400 Subject: [PATCH 15/48] Debug QTSH in the adiabatic representation --- src/dyn/Energy_and_Forces.cpp | 1 + src/nhamiltonian/nHamiltonian_compute_QTSH.cpp | 12 +++++++++++- .../nHamiltonian_compute_QTSH_forces.cpp | 6 +++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/dyn/Energy_and_Forces.cpp b/src/dyn/Energy_and_Forces.cpp index 271030b0a..936eecb45 100755 --- a/src/dyn/Energy_and_Forces.cpp +++ b/src/dyn/Energy_and_Forces.cpp @@ -267,6 +267,7 @@ vector potential_energies(dyn_control_params& prms, dyn_variables& dyn_v int ist = effective_states[itraj]; res[itraj] = ham.get_ham_adi(id).get(ist,ist).real(); // diagonal energy + // No need to add the coherence term directly in the adiabatic representation due to the kinetic energy contribution of the kinematic momentum // coeff = dyn_vars.ampl_adi->col(itraj); // res[itraj] += ham.QTSH_energy_adi(coeff, id, *(dyn_vars.proj_adi[itraj]) ).real(); // coherence energy }// for itraj diff --git a/src/nhamiltonian/nHamiltonian_compute_QTSH.cpp b/src/nhamiltonian/nHamiltonian_compute_QTSH.cpp index 29aa250ec..e846258ff 100644 --- a/src/nhamiltonian/nHamiltonian_compute_QTSH.cpp +++ b/src/nhamiltonian/nHamiltonian_compute_QTSH.cpp @@ -52,9 +52,19 @@ complex nHamiltonian::QTSH_energy_dia(CMATRIX& ampl_dia){ // if(ampl_dia_mem_status==0){ cout<<"Error in QTSH_energy_dia(): the amplitudes of the diabatic states are\ // not allocated, but they are needed for the calculations\n"; exit(0); } + // To obtain the off-diagonal part of the diabatic Hamiltonian matrix + CMATRIX* H_diag; H_diag = new CMATRIX(nadi,nadi); + CMATRIX* V; V = new CMATRIX(nadi,nadi); + for(int i=0; iset(i,i, (*ham_dia).get(i,i) ); } + *V = *ham_dia - *H_diag; + complex norm = (ampl_dia.H() * (*ovlp_dia) * ampl_dia).M[0]; + complex res = (ampl_dia.H() * ( (*V) + complex(0.0, -1.0)* (*nac_dia) ) * ampl_dia).M[0] / norm; + + delete H_diag; + delete V; - return (ampl_dia.H() * complex(0.0, -1.0)* (*nac_dia) * ampl_dia).M[0] / norm; + return res; } diff --git a/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp b/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp index bb24962ee..d30dc2006 100644 --- a/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp +++ b/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp @@ -42,9 +42,9 @@ CMATRIX nHamiltonian::QTSH_forces_dia_unit(CMATRIX& ampl_dia, int option){ \param[in] ampl_dia: MATRIX(ndia, 1) diabatic amplitudes for one trajectory Returns: - MATRIX(ndof, 1) - QTSH nonclassical forces in diabatic representation, for a single trajectory + MATRIX(ndof, 1) - QTSH off-diagonal Hellmann-Feynman forces in diabatic representation, for a single trajectory - The nonclassical force in QTSH has the form of the off-diagonal terms in the Ehrenfest force. + The off-diagonal Hellmann-Feynman force in QTSH has the form of the off-diagonal terms in the Ehrenfest force. */ @@ -212,7 +212,7 @@ CMATRIX nHamiltonian::QTSH_forces_adi_unit(CMATRIX& ampl_adi, int option, CMATRI option 1 removes all the derivative NACs - this is to enforce the local diabatization approximation, to be consistent with it - The nonclassical force in QTSH has the form of the off-diagonal terms in the Ehrenfest force. + The off-diagonal Hellmann-Feynman force in QTSH has the form of the off-diagonal terms in the Ehrenfest force. Returns: MATRIX(ndof, 1) - QTSH forces in adiabatic representation, for single trajectory From 1db65a9b886334cfd6973216ee4801df7854fb73 Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Tue, 11 Jun 2024 21:47:53 -0400 Subject: [PATCH 16/48] Remove the kinematic momentum dependency in decoherence methods --- src/dyn/Dynamics.cpp | 7 +------ src/dyn/dyn_decoherence_methods.cpp | 18 +++--------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index 17d8cae87..b964e0791 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -1404,12 +1404,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, /// Compute the dephasing rates according the original energy-based formalism else if(prms.decoherence_times_type==1){ Eadi = get_Eadi(ham); - if(prms.use_qtsh==0){ - Ekin = dyn_var.compute_kinetic_energies(); - } - else{ - Ekin = dyn_var.compute_kinetic_energies_qtsh(); - } + Ekin = dyn_var.compute_kinetic_energies(); decoherence_rates = edc_rates(Eadi, Ekin, prms.decoherence_C_param, prms.decoherence_eps_param, prms.isNBRA); } diff --git a/src/dyn/dyn_decoherence_methods.cpp b/src/dyn/dyn_decoherence_methods.cpp index c5f8392c6..bb934639e 100755 --- a/src/dyn/dyn_decoherence_methods.cpp +++ b/src/dyn/dyn_decoherence_methods.cpp @@ -1181,12 +1181,7 @@ void shxf(dyn_variables& dyn_var, nHamiltonian& ham, nHamiltonian& ham_prev, dyn } } - if(prms.use_qtsh==0){ - p_real = dyn_var.p->col(traj); - } - else{ - p_real = dyn_var.qtsh_p_k->col(traj); - } + p_real = dyn_var.p->col(traj); int is_tp = 0; double alpha; @@ -1225,15 +1220,8 @@ void shxf(dyn_variables& dyn_var, nHamiltonian& ham, nHamiltonian& ham_prev, dyn if (is_first[i] == 0 and prms.tp_algo != 0){ MATRIX Fa(ndof, 1); - if(prms.use_qtsh==0){ - for(int idof=0; idofget(idof,0) ); - } - } - else{ // QTSH - for(int idof=0; idofget(idof,0) + dyn_var.qtsh_f_nc->get(idof,0) ); - } + for(int idof=0; idofget(idof,0) ); } double dp_old = 0.0; From 5a2fca7b140cc7ea16c158c7b3cc16acf038bef5 Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Tue, 11 Jun 2024 22:07:04 -0400 Subject: [PATCH 17/48] Remove the redundant kinematic momentum routines --- src/dyn/dyn_variables.cpp | 3 -- src/dyn/dyn_variables.h | 16 ------- src/dyn/dyn_variables_nuclear.cpp | 77 ------------------------------- src/dyn/libdyn.cpp | 14 ------ src/libra_py/dynamics/tsh/save.py | 15 ------ 5 files changed, 125 deletions(-) diff --git a/src/dyn/dyn_variables.cpp b/src/dyn/dyn_variables.cpp index f893c10d7..5c83bd2ba 100755 --- a/src/dyn/dyn_variables.cpp +++ b/src/dyn/dyn_variables.cpp @@ -289,7 +289,6 @@ void dyn_variables::allocate_qtsh(){ if(qtsh_vars_status==0){ - qtsh_p_k = new MATRIX(ndof, ntraj); qtsh_f_nc = new MATRIX(ndof, ntraj); qtsh_vars_status = 1; @@ -436,7 +435,6 @@ dyn_variables::dyn_variables(const dyn_variables& x){ allocate_qtsh(); // Copy content - *qtsh_p_k = *x.qtsh_p_k; *qtsh_f_nc = *x.qtsh_f_nc; }// if QTSH vars @@ -586,7 +584,6 @@ dyn_variables::~dyn_variables(){ if(qtsh_vars_status==1){ - delete qtsh_p_k; delete qtsh_f_nc; qtsh_vars_status = 0; diff --git a/src/dyn/dyn_variables.h b/src/dyn/dyn_variables.h index b94ebeb1b..9cc9b7b45 100755 --- a/src/dyn/dyn_variables.h +++ b/src/dyn/dyn_variables.h @@ -491,15 +491,6 @@ class dyn_variables{ int qtsh_vars_status; - /** - Kinematic momentum in QTSH - - Options: - MATRIX(ndof, ntraj) - */ - MATRIX* qtsh_p_k; - - /** nonclassical force in QTSH @@ -563,7 +554,6 @@ class dyn_variables{ MATRIX get_coords_aux(int i){ return *q_aux[i]; } MATRIX get_momenta_aux(int i){ return *p_aux[i]; } MATRIX get_nab_phase(int i){ return *nab_phase[i]; } - MATRIX get_qtsh_p_k(){ return *qtsh_p_k; } MATRIX get_qtsh_f_nc(){ return *qtsh_f_nc; } void get_current_timestep(bp::dict params){ @@ -586,12 +576,6 @@ class dyn_variables{ double compute_kinetic_energy(int itraj, vector& which_dofs); vector compute_kinetic_energies(); vector compute_kinetic_energies(vector& which_dofs); - double compute_average_kinetic_energy_qtsh(); - double compute_average_kinetic_energy_qtsh(vector& which_dofs); - double compute_kinetic_energy_qtsh(int itraj); - double compute_kinetic_energy_qtsh(int itraj, vector& which_dofs); - vector compute_kinetic_energies_qtsh(); - vector compute_kinetic_energies_qtsh(vector& which_dofs); ///====================== In dyn_variables_electronic.cpp ===================== diff --git a/src/dyn/dyn_variables_nuclear.cpp b/src/dyn/dyn_variables_nuclear.cpp index f05d0f55a..1bfe64fd3 100644 --- a/src/dyn/dyn_variables_nuclear.cpp +++ b/src/dyn/dyn_variables_nuclear.cpp @@ -271,29 +271,6 @@ double dyn_variables::compute_average_kinetic_energy(vector& which_dofs){ return 0.5*res / float(which_dofs.size() ); } -double dyn_variables::compute_average_kinetic_energy_qtsh(){ - double res = 0.0; - - for(int itraj = 0; itraj < ntraj; itraj++){ - for(int idof = 0; idof < ndof; idof++){ - res += qtsh_p_k->get(idof, itraj) * qtsh_p_k->get(idof, itraj) * iM->get(idof, 0); - } - } - return 0.5*res/ float(ntraj); -} - -double dyn_variables::compute_average_kinetic_energy_qtsh(vector& which_dofs){ - double res = 0.0; - - for(int itraj = 0; itraj < ntraj; itraj++){ - for(auto idof: which_dofs){ - res += qtsh_p_k->get(idof, itraj) * qtsh_p_k->get(idof, itraj) * iM->get(idof, 0); - } - } - return 0.5*res / float(which_dofs.size() ); -} - - double dyn_variables::compute_kinetic_energy(int itraj){ double res = 0.0; @@ -347,60 +324,6 @@ vector dyn_variables::compute_kinetic_energies(vector& which_dofs){ } - -double dyn_variables::compute_kinetic_energy_qtsh(int itraj){ - double res = 0.0; - - for(int idof = 0; idof < ndof; idof++){ - res += qtsh_p_k->get(idof, itraj) * qtsh_p_k->get(idof, itraj) * iM->get(idof, 0); - } - res *= 0.5; - - return res; -} - - -double dyn_variables::compute_kinetic_energy_qtsh(int itraj, vector& which_dofs){ - double res = 0.0; - - for(auto idof : which_dofs){ - res += qtsh_p_k->get(idof, itraj) * qtsh_p_k->get(idof, itraj) * iM->get(idof, 0); - } - res *= 0.5; - - return res; -} - - -vector dyn_variables::compute_kinetic_energies_qtsh(){ - vector res(ntraj, 0.0); - - for(int itraj = 0; itraj < ntraj; itraj++){ - for(int idof = 0; idof < ndof; idof++){ - res[itraj] += qtsh_p_k->get(idof, itraj) * qtsh_p_k->get(idof, itraj) * iM->get(idof, 0); - } - res[itraj] *= 0.5; - } - - return res; - -} - -vector dyn_variables::compute_kinetic_energies_qtsh(vector& which_dofs){ - vector res(ntraj, 0.0); - - for(int itraj = 0; itraj < ntraj; itraj++){ - for(auto idof : which_dofs){ - res[itraj] += qtsh_p_k->get(idof, itraj) * qtsh_p_k->get(idof, itraj) * iM->get(idof, 0); - } - res[itraj] *= 0.5; - } - - return res; - -} - - double dyn_variables::compute_tcnbra_ekin(){ double res = 0.0; diff --git a/src/dyn/libdyn.cpp b/src/dyn/libdyn.cpp index 923cc0236..49204e0d4 100755 --- a/src/dyn/libdyn.cpp +++ b/src/dyn/libdyn.cpp @@ -213,13 +213,6 @@ void export_dyn_variables_objects(){ vector (dyn_variables::*expt_compute_kinetic_energies_v1)() = &dyn_variables::compute_kinetic_energies; vector (dyn_variables::*expt_compute_kinetic_energies_v2)(vector& which_dofs) = &dyn_variables::compute_kinetic_energies; - double (dyn_variables::*expt_compute_average_kinetic_energy_qtsh_v1)() = &dyn_variables::compute_average_kinetic_energy_qtsh; - double (dyn_variables::*expt_compute_average_kinetic_energy_qtsh_v2)(vector& which_dofs) = &dyn_variables::compute_average_kinetic_energy_qtsh; - double (dyn_variables::*expt_compute_kinetic_energy_qtsh_v1)(int itraj) = &dyn_variables::compute_kinetic_energy_qtsh; - double (dyn_variables::*expt_compute_kinetic_energy_qtsh_v2)(int itraj, vector& which_dofs) = &dyn_variables::compute_kinetic_energy_qtsh; - vector (dyn_variables::*expt_compute_kinetic_energies_qtsh_v1)() = &dyn_variables::compute_kinetic_energies_qtsh; - vector (dyn_variables::*expt_compute_kinetic_energies_qtsh_v2)(vector& which_dofs) = &dyn_variables::compute_kinetic_energies_qtsh; - void (dyn_variables::*expt_update_active_states_v1)(int direction, int property) = &dyn_variables::update_active_states; void (dyn_variables::*expt_update_active_states_v2)() = &dyn_variables::update_active_states; @@ -284,7 +277,6 @@ void export_dyn_variables_objects(){ .def("get_coords_aux", expt_get_coords_aux) .def("get_momenta_aux", expt_get_momenta_aux) .def("get_nab_phase", expt_get_nab_phase) - .def("get_qtsh_p_k", &dyn_variables::get_qtsh_p_k) .def("get_qtsh_f_nc", &dyn_variables::get_qtsh_f_nc) .def("init_nuclear_dyn_var", &dyn_variables::init_nuclear_dyn_var) @@ -294,12 +286,6 @@ void export_dyn_variables_objects(){ .def("compute_kinetic_energy", expt_compute_kinetic_energy_v2) .def("compute_kinetic_energies", expt_compute_kinetic_energies_v1) .def("compute_kinetic_energies", expt_compute_kinetic_energies_v2) - .def("compute_average_kinetic_energy_qtsh", expt_compute_average_kinetic_energy_qtsh_v1) - .def("compute_average_kinetic_energy_qtsh", expt_compute_average_kinetic_energy_qtsh_v2) - .def("compute_kinetic_energy_qtsh", expt_compute_kinetic_energy_qtsh_v1) - .def("compute_kinetic_energy_qtsh", expt_compute_kinetic_energy_qtsh_v2) - .def("compute_kinetic_energies_qtsh", expt_compute_kinetic_energies_qtsh_v1) - .def("compute_kinetic_energies_qtsh", expt_compute_kinetic_energies_qtsh_v2) .def("update_amplitudes", expt_update_amplitudes_v1) diff --git a/src/libra_py/dynamics/tsh/save.py b/src/libra_py/dynamics/tsh/save.py index 927ac2ea2..c997cbdac 100755 --- a/src/libra_py/dynamics/tsh/save.py +++ b/src/libra_py/dynamics/tsh/save.py @@ -206,10 +206,6 @@ def init_tsh_data(saver, output_level, _nsteps, _ntraj, _ndof, _nadi, _ndia): if "f_xf" in saver.keywords: # and "f_xf" in saver.np_data.keys(): saver.add_dataset("f_xf", (_nsteps, _ntraj, _ndof), "R") - # Trajectory-resolved kinematic momentum in QTSH - if "qtsh_p_k" in saver.keywords: # and "f_xf" in saver.np_data.keys(): - saver.add_dataset("qtsh_p_k", (_nsteps, _ntraj, _ndof), "R") - # Trajectory-resolved nonclassical forces in QTSH if "qtsh_f_nc" in saver.keywords: # and "f_xf" in saver.np_data.keys(): saver.add_dataset("qtsh_f_nc", (_nsteps, _ntraj, _ndof), "R") @@ -443,11 +439,6 @@ def save_hdf5_1D_new(saver, i, params, dyn_var, ham, txt_type=0): if "tcnbra_thermostat_energy" in saver.keywords and "tcnbra_thermostat_energy" in saver.np_data.keys(): tcnbra_thermostat_energy = dyn_var.compute_tcnbra_thermostat_energy(); saver.save_scalar(t, "tcnbra_thermostat_energy", tcnbra_thermostat_energy) - - # QTSH average kinetic energy from kinematic momentum - if "Ekin_ave_qtsh" in saver.keywords and "Ekin_ave_qtsh" in saver.np_data.keys(): - Ekin_qtsh = dyn_var.compute_average_kinetic_energy_qtsh(); - saver.save_scalar(t, "Ekin_ave_qtsh", Ekin_qtsh) def save_hdf5_2D(saver, i, states, txt_type=0): """ @@ -685,12 +676,6 @@ def save_hdf5_3D_new(saver, i, dyn_var, txt_type=0): f_xf = dyn_var.get_f_xf() saver.save_matrix(t, "f_xf", f_xf.T()) - # Kinematic mometum in QTSH - # Format: saver.add_dataset("qtsh_p_k", (_nsteps, _ntraj, _dof), "R") - if "qtsh_p_k" in saver.keywords and "qtsh_p_k" in saver.np_data.keys(): - qtsh_p_k = dyn_var.get_qtsh_p_k() - saver.save_matrix(t, "qtsh_p_k", qtsh_p_k.T()) - # Nonclassical force in QTSH # Format: saver.add_dataset("qtsh_f_nc", (_nsteps, _ntraj, _dof), "R") if "qtsh_f_nc" in saver.keywords and "qtsh_f_nc" in saver.np_data.keys(): From 3225a9a897487b4d2530cce348f0c35f6e9a15e6 Mon Sep 17 00:00:00 2001 From: MohammadShakiba Date: Thu, 20 Jun 2024 19:38:35 -0400 Subject: [PATCH 18/48] Modified the distribution of jobs, added the scipy version of file reading for BLLZ tutorial --- src/libra_py/data_conv.py | 3 +++ src/libra_py/packages/cp2k/methods.py | 30 +++++++++++++-------------- src/libra_py/workflows/nbra/step4.py | 9 ++++---- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/libra_py/data_conv.py b/src/libra_py/data_conv.py index b069ddf2f..202267e58 100755 --- a/src/libra_py/data_conv.py +++ b/src/libra_py/data_conv.py @@ -607,3 +607,6 @@ def adf_to_xyz(filename, step=0): for i in range(len(coordinates)): f.write(coordinates[i]) f.close() + + + diff --git a/src/libra_py/packages/cp2k/methods.py b/src/libra_py/packages/cp2k/methods.py index b89025603..b995d7708 100755 --- a/src/libra_py/packages/cp2k/methods.py +++ b/src/libra_py/packages/cp2k/methods.py @@ -1577,12 +1577,15 @@ def distribute_cp2k_libint_jobs(submit_template: str, run_python_file: str, iste lines = file.readlines() file.close() - nsteps_job = int((fstep-istep)/njobs) + #nsteps_job = int((fstep-istep)/njobs) + nsteps_job = np.linspace(istep, fstep, njobs+1, endpoint=True, dtype=int).tolist() for njob in range(njobs): - istep_job = njob*nsteps_job+istep - fstep_job = (njob+1)*nsteps_job+istep+1 - if njob==(njobs-1): - fstep_job = fstep + #istep_job = njob*nsteps_job+istep + #fstep_job = (njob+1)*nsteps_job+istep+1 + istep_job = nsteps_job[njob] + fstep_job = nsteps_job[njob+1]+1 + #if njob==(njobs-1): + # fstep_job = fstep print('Submitting job',njob+1) print('Job',njob,'istep',istep_job,'fstep',fstep_job,'nsteps',fstep_job-istep_job) @@ -1694,7 +1697,7 @@ def gaussian_function_vector(a_vec, mu_vec, sigma, num_points, x_min, x_max): -def aux_pdos(c1, atoms, pdos_files, margin, homo_occ, orbitals_cols, sigma, npoints, ave_pdos_convolved_all, orbitals, labels): +def aux_pdos(c1, atoms, pdos_files, margin, homo_occ, orbitals_cols, sigma, npoints, ave_pdos_convolved_all): """ c1 - index of the atom kinds atoms - the @@ -1722,8 +1725,7 @@ def aux_pdos(c1, atoms, pdos_files, margin, homo_occ, orbitals_cols, sigma, npoi e_max = np.max(pdos_ave[:,1]) + margin homo_level = np.max(np.where(pdos_ave[:,2]==homo_occ)) homo_energy = pdos_ave[:,1][homo_level] - if len(labels)==0: - labels = [] + labels = [] for c3, orbital_cols in enumerate(orbitals_cols): try: @@ -1732,9 +1734,7 @@ def aux_pdos(c1, atoms, pdos_files, margin, homo_occ, orbitals_cols, sigma, npoi ave_pdos_convolved_all.append(ave_pdos_convolved) pdos_label = F"{atoms[1][c1]}, {orbitals[c3]}" labels.append(pdos_label) -# print('here') except: -# print('in the pass') pass return ave_energy_grid, homo_energy, ave_pdos_convolved_all, labels @@ -1783,9 +1783,7 @@ def pdos(params): sigma = params['sigma'] shift = params['shift'] is_spin_polarized = params["is_spin_polarized"] - labels = [] - labels_alp = [] - labels_bet = [] + if is_spin_polarized == 0: # non-polarized case homo_occ = 2.0 @@ -1796,7 +1794,7 @@ def pdos(params): # Finding all the pdos files pdos_files = glob.glob(path_to_all_pdos+F'/*k{i}*.pdos') - ave_energy_grid, homo_energy, ave_pdos_convolved_all, labels = aux_pdos(c1, atoms, pdos_files, shift, homo_occ, orbitals_cols, sigma, npoints, ave_pdos_convolved_all, orbitals, labels) + ave_energy_grid, homo_energy, ave_pdos_convolved_all, labels = aux_pdos(c1, atoms, pdos_files, shift, homo_occ, orbitals_cols, sigma, npoints, ave_pdos_convolved_all) """ # Finding all the pdos files @@ -1843,9 +1841,9 @@ def pdos(params): pdos_files_alp = glob.glob(path_to_all_pdos+F'/*ALPHA*k{i}*.pdos') pdos_files_bet = glob.glob(path_to_all_pdos+F'/*BETA*k{i}*.pdos') - ave_energy_grid_alp, homo_energy_alp, ave_pdos_convolved_all_alp, labels_alp = aux_pdos(c1, atoms, pdos_files_alp, shift, homo_occ, orbitals_cols, sigma, npoints, ave_pdos_convolved_all_alp, orbitals, labels_alp) + ave_energy_grid_alp, homo_energy_alp, ave_pdos_convolved_all_alp, labels_alp = aux_pdos(c1, atoms, pdos_files_alp, shift, homo_occ, orbitals_cols, sigma, npoints, ave_pdos_convolved_all_alp) - ave_energy_grid_bet, homo_energy_bet, ave_pdos_convolved_all_bet, labels_bet = aux_pdos(c1, atoms, pdos_files_bet, shift, homo_occ, orbitals_cols, sigma, npoints, ave_pdos_convolved_all_bet, orbitals, labels_bet) + ave_energy_grid_bet, homo_energy_bet, ave_pdos_convolved_all_bet, labels_bet = aux_pdos(c1, atoms, pdos_files_bet, shift, homo_occ, orbitals_cols, sigma, npoints, ave_pdos_convolved_all_bet) ave_pdos_convolved_all_alp = np.array(ave_pdos_convolved_all_alp) diff --git a/src/libra_py/workflows/nbra/step4.py b/src/libra_py/workflows/nbra/step4.py index b1924ca61..c23e0eaca 100755 --- a/src/libra_py/workflows/nbra/step4.py +++ b/src/libra_py/workflows/nbra/step4.py @@ -63,6 +63,7 @@ import util.libutil as comn import libra_py.data_read as data_read +from libra_py import data_conv from . import decoherence_times as dectim #import libra_py.tsh as tsh import libra_py.tsh_stat as tsh_stat @@ -1125,21 +1126,21 @@ def get_Hvib_scipy(params): step += params['init_times'] try: - file_name = params['data_set_paths'][0] + params['Hvib_re_prefix'] + str(step) + params['Hvib_re_suffix'] + file_name = params['data_set_paths'][0] + params['Hvib_re_prefix'] + str(step) + params['Hvib_re_suffix'] + '.npz' tmp_real = sp.load_npz(file_name).real except: print(F'File {file_name} not found! Please check the path to Hvibs. Setting zero matrix into hvib...') tmp_real = sp.csc_matrix( np.zeros((params['nstates'], params['nstates'])) ) try: - file_name = params['data_set_paths'][0] + params['Hvib_im_prefix'] + str(step) + params['Hvib_im_suffix'] + file_name = params['data_set_paths'][0] + params['Hvib_im_prefix'] + str(step) + params['Hvib_im_suffix'] + '.npz' tmp_imag = sp.load_npz(file_name).real except: print(F'File {file_name} not found! Please check the path to Hvibs. Setting zero matrix into hvib...') tmp_imag = sp.csc_matrix( np.zeros((params['nstates'], params['nstates'])) ) - real_MATRIX = scipynpz2MATRIX(tmp_real) - imag_MATRIX = scipynpz2MATRIX(tmp_imag) + real_MATRIX = data_conv.scipynpz2MATRIX(tmp_real) + imag_MATRIX = data_conv.scipynpz2MATRIX(tmp_imag) hvib[i].append( CMATRIX(real_MATRIX, imag_MATRIX) ) return hvib From a1380f1ddc0d913098aee0a36c325edb8d81d4e8 Mon Sep 17 00:00:00 2001 From: Alexey Akimov Date: Tue, 25 Jun 2024 09:58:13 -0400 Subject: [PATCH 19/48] Revised MASH - added computation of the MASH estimators (not only with MASH method), added some references to the MASH hop proposal method, added functions for saving and plotting MASH populaitons, updated some info on QTSH in the dyn_conntrol_params --- src/dyn/dyn_control_params.h | 1 + src/dyn/dyn_hop_proposal.cpp | 23 ++++++++- src/dyn/dyn_variables.h | 1 + src/dyn/dyn_variables_electronic.cpp | 36 +++++++++++++ src/dyn/libdyn.cpp | 1 + src/libra_py/dynamics/tsh/plot.py | 51 ++++++++++++++++--- src/libra_py/dynamics/tsh/save.py | 25 +++++++++ .../nHamiltonian_compute_QTSH_forces.cpp | 12 +++++ 8 files changed, 141 insertions(+), 9 deletions(-) diff --git a/src/dyn/dyn_control_params.h b/src/dyn/dyn_control_params.h index 95df49499..90d57f3a8 100755 --- a/src/dyn/dyn_control_params.h +++ b/src/dyn/dyn_control_params.h @@ -145,6 +145,7 @@ class dyn_control_params{ - 0: don't compute forces at all - e.g. we do not really need them - 1: state-specific as in the TSH or adiabatic (including adiabatic excited states) [ default ] - 2: Ehrenfest + - 3: QTSH force */ int force_method; diff --git a/src/dyn/dyn_hop_proposal.cpp b/src/dyn/dyn_hop_proposal.cpp index 9fe81d83b..b2ae9220b 100755 --- a/src/dyn/dyn_hop_proposal.cpp +++ b/src/dyn/dyn_hop_proposal.cpp @@ -675,7 +675,6 @@ vector hopping_probabilities_mssh(dyn_control_params& prms, CMATRIX& den } - //MATRIX compute_hopping_probabilities_lz(nHamiltonian* ham, int rep, MATRIX& p, const MATRIX& invM, MATRIX& prev_ham_dia){ vector hopping_probabilities_lz(nHamiltonian* ham, nHamiltonian* ham_prev, int act_state_indx, int rep, MATRIX& p, const MATRIX& invM){ /** @@ -967,6 +966,28 @@ vector hopping_probabilities_zn(nHamiltonian& ham, nHamiltonian& ham_pre vector hopping_probabilities_mash(dyn_control_params& prms, CMATRIX& denmat){ +/** + \brief Compute the MASH surface hopping probabilities + This is the version taking the minimal amount of input information + + The function returns the matrix g with hopping probabilities + Abbreviation: MASH - mapped surface hopping + + Hopping with 100% probability to the state with the largest population + + References: + (1)Mannouch, J. R.; Richardson, J. O. A Mapping Approach to Surface Hopping. J. Chem. Phys. 2023, 158 (10), 104111. https://doi.org/10.1063/5.0139734. + (2)Runeson, J. E.; Manolopoulos, D. E. A Multi-State Mapping Approach to Surface Hopping. The Journal of Chemical Physics 2023, 159 (9), 094115. https://doi.org/10.1063/5.0158147. + (3)E. Runeson, J.; P. Fay, T.; E. Manolopoulos, D. Exciton Dynamics from the Mapping Approach to Surface Hopping: Comparison with Förster and Redfield Theories. Physical Chemistry Chemical Physics 2024, 26 (6), 4929–4938. https://doi.org/10.1039/D3CP05926J. + + \param[in] key parameters needed for this type of calculations + - dt - integration timestep [a.u.] + \param[in] denmat - [nstates x nstates] - density matrix + + Returns: A nstates-vector of hopping probabilities to all states from the current active state + +*/ + int nstates = denmat.n_rows; vector g(nstates, 0.0); diff --git a/src/dyn/dyn_variables.h b/src/dyn/dyn_variables.h index 9cc9b7b45..351cdbc36 100755 --- a/src/dyn/dyn_variables.h +++ b/src/dyn/dyn_variables.h @@ -606,6 +606,7 @@ class dyn_variables{ CMATRIX compute_average_dm(int rep); vector compute_average_se_pop(int rep); vector compute_average_sh_pop(int rep); + vector compute_average_mash_pop(int rep); double compute_tcnbra_ekin(); diff --git a/src/dyn/dyn_variables_electronic.cpp b/src/dyn/dyn_variables_electronic.cpp index b97113cf4..b5e18d983 100644 --- a/src/dyn/dyn_variables_electronic.cpp +++ b/src/dyn/dyn_variables_electronic.cpp @@ -1051,6 +1051,42 @@ vector dyn_variables::compute_average_se_pop(int rep){ return res; } + + +vector dyn_variables::compute_average_mash_pop(int rep){ +/** + Computing the MASH population estimators based on: + + (1) E. Runeson, J.; P. Fay, T.; E. Manolopoulos, D. Exciton Dynamics from the Mapping Approach to Surface Hopping: Comparison with Förster and Redfield Theories. Physical Chemistry Chemical Physics 2024, 26 (6), 4929–4938. https://doi.org/10.1039/D3CP05926J. + +*/ + + //======= First, compute the SE populations ======== + int sz,i; + + if(rep==0 || rep==2){ sz = ndia; } + else if(rep==1 || rep==3){ sz = nadi; } + + MATRIX ave(sz, sz); + ave = compute_average_dm(rep).real(); + + vector res(sz, 0.0); + + for(i=0; i dyn_variables::compute_average_sh_pop(int rep){ diff --git a/src/dyn/libdyn.cpp b/src/dyn/libdyn.cpp index 49204e0d4..ad1486fac 100755 --- a/src/dyn/libdyn.cpp +++ b/src/dyn/libdyn.cpp @@ -313,6 +313,7 @@ void export_dyn_variables_objects(){ .def("compute_average_dm", &dyn_variables::compute_average_dm) .def("compute_average_se_pop", &dyn_variables::compute_average_se_pop) .def("compute_average_sh_pop", &dyn_variables::compute_average_sh_pop) + .def("compute_average_mash_pop", &dyn_variables::compute_average_mash_pop) .def("compute_tcnbra_ekin", &dyn_variables::compute_tcnbra_ekin) .def("compute_tcnbra_thermostat_energy", &dyn_variables::compute_tcnbra_thermostat_energy) diff --git a/src/libra_py/dynamics/tsh/plot.py b/src/libra_py/dynamics/tsh/plot.py index 108ab0829..d59a106a4 100755 --- a/src/libra_py/dynamics/tsh/plot.py +++ b/src/libra_py/dynamics/tsh/plot.py @@ -625,12 +625,19 @@ def add_populations(plt, hdf_file, plot_params_, pop_type ): - "D_dia" - "D_adi" - "SH_pop" + - "se_pop_adi" + - "se_pop_dia" + - "sh_pop_adi" + - "sh_pop_dia" + - "mash_pop_adi" + - "mash_pop_dia" Call it after adding a sub-plot """ - possible_options = ["D_dia_raw", "D_adi_raw", "SH_pop_raw", "D_dia", "D_adi", "SH_pop", "se_pop_adi", "se_pop_dia", "sh_pop_adi", "sh_pop_dia"] + possible_options = ["D_dia_raw", "D_adi_raw", "SH_pop_raw", "D_dia", "D_adi", "SH_pop", "se_pop_adi", "se_pop_dia", + "sh_pop_adi", "sh_pop_dia", "mash_pop_adi", "mash_pop_dia"] if pop_type not in possible_options: print(F"Error in add_populations - the pop_type argument {pop_type} is invalid\n") print(F"Must be one of the following options: {possible_options}\nExiting") @@ -688,6 +695,16 @@ def add_populations(plt, hdf_file, plot_params_, pop_type ): nstates = hdf_file["sh_pop_adi/data"].shape[1] which_states = plot_params["which_adi_states"] + elif pop_type in ["mash_pop_dia"]: # diabatic MASH populations + if "mash_pop_dia/data" in hdf_file.keys(): + nstates = hdf_file["mash_pop_dia/data"].shape[1] + which_states = plot_params["which_dia_states"] + + elif pop_type in ["mash_pop_adi"]: # adiabatic MASH populations + if "mash_pop_adi/data" in hdf_file.keys(): + nstates = hdf_file["mash_pop_adi/data"].shape[1] + which_states = plot_params["which_adi_states"] + titles = { "D_dia": "Diabatic SE populations", "D_dia_raw": "Diabatic SE populations (raw)", @@ -698,7 +715,9 @@ def add_populations(plt, hdf_file, plot_params_, pop_type ): "SH_pop": "Adiabatic SH populations", "SH_pop_raw": "Adiabatic SH populations (raw)", "sh_pop_dia": "Diabatic SH populations", - "sh_pop_adi": "Adiabatic SH populations" + "sh_pop_adi": "Adiabatic SH populations", + "mash_pop_dia": "Diabatic MASH populations", + "mash_pop_adi": "Adiabatic MASH populations" } if xlim!=None: @@ -737,7 +756,7 @@ def add_populations(plt, hdf_file, plot_params_, pop_type ): label=F"state {istate}", linewidth=Lw, color = colors[clrs_index[indx] ]) - elif pop_type in ["se_pop_adi", "se_pop_dia", "sh_pop_adi", "sh_pop_dia"]: + elif pop_type in ["se_pop_adi", "se_pop_dia", "sh_pop_adi", "sh_pop_dia", "mash_pop_adi", "mash_pop_dia"]: if F"{pop_type}/data" in hdf_file.keys(): res = 1 indx = -1 @@ -1019,11 +1038,27 @@ def plot_dynamics(plot_params_): res = add_populations(plt, f, plot_params_, "sh_pop_dia") if plot_params["save_figures"]==1 and res==1: plt.savefig(F"{out_prefix}/sh_pop_dia.png", dpi=plot_params["dpi"]) + + if "mash_pop_adi" in what_to_plot: + plt.figure(num=11, figsize=plot_params["figsize"], dpi=plot_params["dpi"], + edgecolor='black', frameon=plot_params["frameon"]) + plt.subplot(1,1,1) + res = add_populations(plt, f, plot_params_, "mash_pop_adi") + if plot_params["save_figures"]==1 and res==1: + plt.savefig(F"{out_prefix}/mash_pop_adi.png", dpi=plot_params["dpi"]) + + if "mash_pop_dia" in what_to_plot: + plt.figure(num=12, figsize=plot_params["figsize"], dpi=plot_params["dpi"], + edgecolor='black', frameon=plot_params["frameon"]) + plt.subplot(1,1,1) + res = add_populations(plt, f, plot_params_, "mash_pop_dia") + if plot_params["save_figures"]==1 and res==1: + plt.savefig(F"{out_prefix}/mash_pop_dia.png", dpi=plot_params["dpi"]) #===== Trajectory-resolved adiabatic energies ========= if "traj_resolved_adiabatic_ham" in what_to_plot: - plt.figure(num=11, figsize=plot_params["figsize"], dpi=plot_params["dpi"], + plt.figure(num=13, figsize=plot_params["figsize"], dpi=plot_params["dpi"], edgecolor='black', frameon=plot_params["frameon"]) plt.subplot(1,1,1) res = add_trajectory_resolved_ham_property(plt, f, plot_params, "hvib_adi") @@ -1031,7 +1066,7 @@ def plot_dynamics(plot_params_): plt.savefig(F"{out_prefix}/hvib_adi.png", dpi=plot_params["dpi"]) if "traj_resolved_adiabatic_ham" in what_to_plot: - plt.figure(num=12, figsize=plot_params["figsize"], dpi=plot_params["dpi"], + plt.figure(num=14, figsize=plot_params["figsize"], dpi=plot_params["dpi"], edgecolor='black', frameon=plot_params["frameon"]) plt.subplot(1,1,1) res = add_trajectory_resolved_ham_property(plt, f, plot_params, "hvib_dia") @@ -1041,7 +1076,7 @@ def plot_dynamics(plot_params_): #===== Time-overlaps and projectors ========= if "time_overlaps" in what_to_plot: - plt.figure(num=13, figsize=plot_params["figsize"], dpi=plot_params["dpi"], + plt.figure(num=15, figsize=plot_params["figsize"], dpi=plot_params["dpi"], edgecolor='black', frameon=plot_params["frameon"]) plt.subplot(1,1,1) res = add_time_overlaps_projectors(plt, f, plot_params, "St") @@ -1050,7 +1085,7 @@ def plot_dynamics(plot_params_): if "projector" in what_to_plot: - plt.figure(num=14, figsize=plot_params["figsize"], dpi=plot_params["dpi"], + plt.figure(num=16, figsize=plot_params["figsize"], dpi=plot_params["dpi"], edgecolor='black', frameon=plot_params["frameon"]) plt.subplot(1,1,1) res = add_time_overlaps_projectors(plt, f, plot_params, "projector") @@ -1060,7 +1095,7 @@ def plot_dynamics(plot_params_): #===== Basis transforms ========= if "basis_transform" in what_to_plot: - plt.figure(num=15, figsize=plot_params["figsize"], dpi=plot_params["dpi"], + plt.figure(num=17, figsize=plot_params["figsize"], dpi=plot_params["dpi"], edgecolor='black', frameon=plot_params["frameon"]) plt.subplot(1,1,1) res = add_basis_transform(plt, f, plot_params) diff --git a/src/libra_py/dynamics/tsh/save.py b/src/libra_py/dynamics/tsh/save.py index c997cbdac..b81703d63 100755 --- a/src/libra_py/dynamics/tsh/save.py +++ b/src/libra_py/dynamics/tsh/save.py @@ -135,6 +135,14 @@ def init_tsh_data(saver, output_level, _nsteps, _ntraj, _ndof, _nadi, _ndia): if "sh_pop_dia" in saver.keywords: # and "states" in saver.np_data.keys(): saver.add_dataset("sh_pop_dia", (_nsteps, _ndia), "R") + # Average adiabatic MASH populations + if "mash_pop_adi" in saver.keywords: # and "states" in saver.np_data.keys(): + saver.add_dataset("mash_pop_adi", (_nsteps, _nadi), "R") + + # Average diabatic MASH populations + if "mash_pop_dia" in saver.keywords: # and "states" in saver.np_data.keys(): + saver.add_dataset("mash_pop_dia", (_nsteps, _ndia), "R") + # Average errors from FSSH3 if "fssh3_average_errors" in saver.keywords: # and "fssh3_average_errors" in saver.np_data.keys(): saver.add_dataset("fssh3_average_errors", (_nsteps, 5), "R") @@ -513,6 +521,23 @@ def save_hdf5_2D_new(saver, i, dyn_var, ham, txt_type=0): for ist in range(nadi): saver.save_multi_scalar(t, ist, "sh_pop_adi", pops_sh1[ist]) + if "mash_pop_dia" in saver.keywords and "mash_pop_dia" in saver.np_data.keys(): + # Average diabatic MASH populations + # Format: saver.add_dataset("mash_pop_dia", (_nsteps, _ndia), "R") + pops_sh0 = dyn_var.compute_average_mash_pop(0) + ndia = dyn_var.ndia + for ist in range(ndia): + saver.save_multi_scalar(t, ist, "mash_pop_dia", pops_sh0[ist]) + + if "mash_pop_adi" in saver.keywords and "mash_pop_adi" in saver.np_data.keys(): + # Average adiabatic MASH populations + # Format: saver.add_dataset("mash_pop_adi", (_nsteps, _nadi), "R") + pops_sh1 = dyn_var.compute_average_mash_pop(1) + nadi = dyn_var.nadi + for ist in range(nadi): + saver.save_multi_scalar(t, ist, "mash_pop_adi", pops_sh1[ist]) + + if "fssh3_average_errors" in saver.keywords and "fssh3_average_errors" in saver.np_data.keys(): # Average errors in FSSH3 diff --git a/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp b/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp index d30dc2006..76a8706a7 100644 --- a/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp +++ b/src/nhamiltonian/nHamiltonian_compute_QTSH_forces.cpp @@ -241,6 +241,18 @@ CMATRIX nHamiltonian::QTSH_forces_adi_unit(CMATRIX& ampl_adi, int option, CMATRI if(dc1_adi_mem_status[n]==0){ cout<<"Error in QTSH_forces_adi_unit(): the derivatives couplings matrix in the adiabatic \ basis w.r.t. the nuclear DOF "< Date: Wed, 3 Jul 2024 19:08:34 -0400 Subject: [PATCH 20/48] Added an option to the check_input function not to print too many warning messages --- src/util/libutil.cpp | 4 +++- src/util/util.cpp | 15 ++++++++++++--- src/util/util.h | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/util/libutil.cpp b/src/util/libutil.cpp index d8b203762..00c16ccc3 100755 --- a/src/util/libutil.cpp +++ b/src/util/libutil.cpp @@ -99,7 +99,9 @@ void export_util_objects(){ void (*expt_check_input_v1)(boost::python::dict params, boost::python::dict default_params, boost::python::list critical_params) = &check_input; - def("check_input", expt_check_input_v1); + void (*expt_check_input_v2)(boost::python::dict params, boost::python::dict default_params, boost::python::list critical_params, int verbose) = &check_input; + def("check_input", expt_check_input_v1); + def("check_input", expt_check_input_v2); //std::string int2str(int inp); //int find_section(vector& A,std::string marker_beg,std::string marker_end,int min_line,int max_line,int& beg,int& end); diff --git a/src/util/util.cpp b/src/util/util.cpp index ab8a2ffcc..848ae738a 100755 --- a/src/util/util.cpp +++ b/src/util/util.cpp @@ -477,7 +477,7 @@ vector< vector< vector > > allocate_3D(int sz1, int sz2, int sz3){ -void check_input(boost::python::dict params, boost::python::dict default_params, boost::python::list critical_params){ +void check_input(boost::python::dict params, boost::python::dict default_params, boost::python::list critical_params, int verbose){ /***************************************************************** This function checks the input Python dictionary and verifies if it contains @@ -582,15 +582,24 @@ void check_input(boost::python::dict params, boost::python::dict default_params, key = boost::python::extract(default_params.keys()[i]); if(!params.has_key(key)){ + + if(verbose){ std::cout<<"WARNING: Parameter "<< key<< " is not defined! in the input parameters" <<"Use the default value \n"; //<< default_params.values()[i]<<"\n"; + } - params.setdefault(key, default_params.values()[i]); - } + params.setdefault(key, default_params.values()[i]); + } } } +void check_input(boost::python::dict params, boost::python::dict default_params, boost::python::list critical_params){ + + check_input(params, default_params, critical_params, 1); + +} + }// libutil diff --git a/src/util/util.h b/src/util/util.h index c40c7ee7f..699d77e76 100755 --- a/src/util/util.h +++ b/src/util/util.h @@ -84,7 +84,7 @@ vector< vector< vector > > allocate_3D(int sz1, int sz2, int sz3); void check_input(boost::python::dict params, boost::python::dict default_params, boost::python::list critical_params); - +void check_input(boost::python::dict params, boost::python::dict default_params, boost::python::list critical_params, int verbose); }// libutil From 4c1da7139da409f7dab2a57c0ef1b2ba6221a923 Mon Sep 17 00:00:00 2001 From: Alexey Akimov Date: Wed, 3 Jul 2024 19:14:47 -0400 Subject: [PATCH 21/48] Misc changes to the dynamic_plotting function --- src/libra_py/dynamics_plotting.py | 85 ++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 25 deletions(-) diff --git a/src/libra_py/dynamics_plotting.py b/src/libra_py/dynamics_plotting.py index ddae59156..e725f8deb 100755 --- a/src/libra_py/dynamics_plotting.py +++ b/src/libra_py/dynamics_plotting.py @@ -49,7 +49,7 @@ #from matplotlib.mlab import griddata -def plot_pes_properties(comp_model, model_params, pes_params_, plot_params_): +def plot_pes_properties(comp_model, _model_params, _pes_params, _plot_params): """ Args: @@ -113,7 +113,8 @@ def plot_pes_properties(comp_model, model_params, pes_params_, plot_params_): """ - pes_params = dict(pes_params_) + #pes_params = dict(_pes_params) + pes_params = _pes_params pes_params_critical = [ "rep_tdse", "rep_ham" ] pes_params_default = { "ndia":2, "nadi":2, "ndof":1, "active_dof":0, @@ -163,7 +164,8 @@ def plot_pes_properties(comp_model, model_params, pes_params_, plot_params_): clrs_index_ = ["11", "21", "31", "41", "12", "22", "32", "13","23", "14", "24"] - plot_params = dict(plot_params_) + #plot_params = dict(_plot_params) + plot_params = _plot_params plot_params_critical = [ ] plot_params_default = { "which_ham_dia":[], "which_ham_adi":[], "which_d1ham_dia":[], "which_d1ham_adi":[], @@ -248,6 +250,9 @@ def plot_pes_properties(comp_model, model_params, pes_params_, plot_params_): dyn_var = dyn_variables(ndia, nadi, ndof, 1) + #model_params = dict(_model_params) + model_params = _model_params + for step in range(nsteps): q = None if coord_mapping == None: @@ -293,8 +298,7 @@ def plot_pes_properties(comp_model, model_params, pes_params_, plot_params_): for it_indx, it in enumerate(which_dc1_adi): dc1_adi[it_indx].append( ham.get_dc1_adi(it[0],tid).get(it[1], it[2]).real ) - - + plt.rc('axes', titlesize=38) # fontsize of the axes title plt.rc('axes', labelsize=38) # fontsize of the x and y labels @@ -310,7 +314,9 @@ def plot_pes_properties(comp_model, model_params, pes_params_, plot_params_): #======== Now lets plot what we have computed =========== - plt.figure(1, figsize=(36, 18)) # dpi=300, frameon=False) + plt.figure(figsize=(36, 18)) # dpi=300, frameon=False) + #findx = plt.gcf().number + #plt.figure(findx + 1, figsize=(36, 18)) # dpi=300, frameon=False) plt.subplot(1,2,1) plt.title('Diabatic energies') plt.xlabel('Coordinate, a.u.') @@ -327,9 +333,12 @@ def plot_pes_properties(comp_model, model_params, pes_params_, plot_params_): plt.plot(grid, ham_adi[it_indx], label='$H_{%i, %i}$' % (it[0], it[1]), linewidth=5, color = colors[ clrs_index[it_indx] ]) plt.legend() plt.show() + #plt.clf() plt.close() - plt.figure(2, figsize=(36, 18)) # dpi=300, frameon=False) + plt.figure(figsize=(36, 18)) # dpi=300, frameon=False) + #findx = plt.gcf().number + #plt.figure(findx + 1, figsize=(36, 18)) # dpi=300, frameon=False) plt.subplot(1,2,1) plt.title('Derivatives of diabatic energies') plt.xlabel('Coordinate, a.u.') @@ -346,10 +355,13 @@ def plot_pes_properties(comp_model, model_params, pes_params_, plot_params_): plt.plot(grid, d1ham_adi[it_indx], label='$dH_{%i, %i} / dR_{%i}$' % (it[1],it[2], it[0]), linewidth=5, color = colors[ clrs_index[it_indx] ]) plt.legend() plt.show() + #plt.clf() plt.close() - plt.figure(3, figsize=(36, 18)) # dpi=300, frameon=False) + plt.figure(figsize=(36, 18)) # dpi=300, frameon=False) + #findx = plt.gcf().number + #plt.figure(findx + 1, figsize=(36, 18)) # dpi=300, frameon=False) plt.subplot(1,2,1) plt.title('Derivatives couplings, diabatic rep.') plt.xlabel('Coordinate, a.u.') @@ -365,16 +377,15 @@ def plot_pes_properties(comp_model, model_params, pes_params_, plot_params_): for it_indx, it in enumerate(which_dc1_adi): plt.plot(grid, dc1_adi[it_indx], label='$< \psi_{%i} | dR_{%i} | \psi_{%i} >$' % (it[1], it[0], it[2]), linewidth=5, color = colors[ clrs_index[it_indx] ]) plt.legend() - - plt.show() + #plt.clf() plt.close() return scan_path -def plot_surfaces(_compute_model, _param_sets, states_of_interest, xmin, xmax, dx, plot_params,\ +def plot_surfaces(_compute_model, _param_sets, states_of_interest, xmin, xmax, dx, _plot_params,\ _ndof=1, _active_dof=0, _all_coordinates=[0.0]): """ Args: @@ -433,6 +444,8 @@ def plot_surfaces(_compute_model, _param_sets, states_of_interest, xmin, xmax, d """ + # I know - this is a super-bad practice, but let's do it this way + #import matplotlib.pyplot as plt colors_ = {} @@ -453,7 +466,7 @@ def plot_surfaces(_compute_model, _param_sets, states_of_interest, xmin, xmax, d clrs_index_ = ["11", "21", "31", "41", "12", "22", "32", "13","23", "14", "24"] - + plot_params = dict(_plot_params) # Parameters and dimensions critical_params = [ ] @@ -517,8 +530,9 @@ def plot_surfaces(_compute_model, _param_sets, states_of_interest, xmin, xmax, d for iset in range(sz): # The "nstates" field must be specified - comn.check_input(_param_sets[iset], {}, ["nstates"]) - n = _param_sets[iset]["nstates"] + model_params = dict(_param_sets[iset]) + comn.check_input(model_params, {}, ["nstates"]) + n = model_params["nstates"] nstates = n ham = nHamiltonian(nstates, nstates, _ndof) # ndia, nadi, nnucl @@ -544,14 +558,13 @@ def plot_surfaces(_compute_model, _param_sets, states_of_interest, xmin, xmax, d for i in range(nsteps): - scan_coord = MATRIX(_ndof, 1); for j in range(_ndof): scan_coord.set(j, 0, _all_coordinates[j]) scan_coord.set(_active_dof, 0, X[i]) # Diabatic properties - ham.compute_diabatic(_compute_model, scan_coord, _param_sets[iset]) + ham.compute_diabatic(_compute_model, scan_coord, model_params ) # Adiabatic properties ham.compute_adiabatic(1); @@ -569,10 +582,16 @@ def plot_surfaces(_compute_model, _param_sets, states_of_interest, xmin, xmax, d nac[k1][k2].append(nac_k1_k2) nac_abs[k1][k2].append( abs(nac_k1_k2) ) + #print(hadi) + if plotting_option==0: # Plot diabatic and adiabatic surfaces separately, plot projections too + + findx = plt.gcf().number + print("Current figre index is ", findx) - #plt.figure(2*iset, figsize=fig_size ) # dpi=300, frameon=False) + #==================== Diabatic and Adiabatic surfaces ============== + #plt.figure(findx + 1, figsize=fig_size ) # dpi=300, frameon=False) plt.figure(figsize=fig_size ) # dpi=300, frameon=False) plt.subplot(1, 2, 1) @@ -601,16 +620,26 @@ def plot_surfaces(_compute_model, _param_sets, states_of_interest, xmin, xmax, d if not os.path.exists(F"{prefix}"): os.system(F"mkdir {prefix}") plt.savefig(F"{prefix}/Ham_dia_E_adi_set_{iset}.png", dpi=dpi_value) - - - #plt.figure(2*iset+1, figsize=fig_size ) # dpi=300, frameon=False) + + if do_show: + plt.show() + + #plt.clf() + #plt.close() + + #=================== Projections ============================ + findx = plt.gcf().number + print("Current figre index is ", findx) + #plt.figure( findx + 1, figsize=fig_size ) # dpi=300, frameon=False) plt.figure(figsize=fig_size ) # dpi=300, frameon=False) sz1 = len(states_of_interest) for k2 in states_of_interest: indx = states_of_interest.index(k2) - plt.figure(2*iset+1+indx, figsize=(36, 18)) # dpi=300, frameon=False) + #plt.figure(2*iset+1+indx, figsize=(36, 18)) # dpi=300, frameon=False) + #plt.figure(2*iset+1+indx, figsize=fig_size) # dpi=300, frameon=False) + plt.subplot(sz1, 1, indx+1) #plt.subplot(1, sz1, 1+indx) #plt.subplot(1, 1, 1+indx) @@ -630,13 +659,15 @@ def plot_surfaces(_compute_model, _param_sets, states_of_interest, xmin, xmax, d if do_show: plt.show() - - plt.close() + + #plt.clf() + #plt.close() elif plotting_option==1: # Plot diabatic and adiabatic surfaces in one picture, using dashed lines for diabatic # Plot NACs in a separate panel - plt.figure(iset, figsize=fig_size) # dpi=300, frameon=False) + findx = plt.gcf().number + plt.figure(figsize=fig_size) # dpi=300, frameon=False) plt.subplot(1, 2, 1) plt.ylim(ylim[0], ylim[1]) @@ -676,5 +707,9 @@ def plot_surfaces(_compute_model, _param_sets, states_of_interest, xmin, xmax, d if do_show: plt.show() + #plt.clf() + #plt.close() + #plt.close() + + - plt.close() From 1137514a5ffa51d434b7a2b61ff8dc79b9dfb174 Mon Sep 17 00:00:00 2001 From: MohammadShakiba Date: Wed, 3 Jul 2024 23:13:39 -0400 Subject: [PATCH 22/48] added the function limit_active_space for step 3 --- src/libra_py/workflows/nbra/step3.py | 47 ++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/libra_py/workflows/nbra/step3.py b/src/libra_py/workflows/nbra/step3.py index 3466b7531..17bdc6bd1 100755 --- a/src/libra_py/workflows/nbra/step3.py +++ b/src/libra_py/workflows/nbra/step3.py @@ -934,6 +934,53 @@ def do_phase_corr(cum_phase1, St, cum_phase2, phase_i): St.scale(a,b, fab) +def limit_active_space(params): + """ + This function limits the active space of Kohn-Sham matrices + generated in step 2 based on the number of + occupied and unoccupied orbitals specified by user. + Args: + params (dict): + num_occ_orbitals (integer): The number of occupied orbitals + num_unocc_orbitals (integer): The number of unoccupied orbitals + path_to_npz_files (string): The full path to res directory from step 2 + path_to_save_npz_files (string): The full path to save the Kohn-Sham files + logfile_directory (string): The full path to all log files + lowest_orbital (integer): The lowest_orbital defined in step 2 + highest_orbital (integer): The highest_orbital defined in step 2 + Returns: + None but it prints the new value of lowest_orbital and highest_orbital + """ + nocc = params['num_occ_orbitals'] + nunocc = params['num_unocc_orbitals'] + l1 = params['lowest_orbital'] + h1 = params['highest_orbital'] + sample_logfile = glob.glob(f'{params["logfile_directory"]}/*.log')[0] + os.system(f'mkdir {params["path_to_save_npz_files"]}') + # currently working only for restricted KS + homo_index = CP2K_methods.read_homo_index(sample_logfile, isUKS=False) + l2 = homo_index-nocc+1 + h2 = homo_index+nunocc + l2_index = l2-l1 + h2_index = h2-l1+1 + norbitals = nocc + nunocc + zero_mat = np.zeros((norbitals, norbitals)) + for prop in ['S', 'St', 'E']: + files = glob.glob(f'{params["path_to_npz_files"]}/{prop}_*.npz') + print(f'Restricting the active space for {prop}..') + for file in files: + mat = sp.load_npz(file).todense().real + new_mat = mat[l2_index:h2_index,:][:,l2_index:h2_index] + new_mat_two_spinor = data_conv.form_block_matrix(new_mat, zero_mat, zero_mat, new_mat) + new_mat_sparse = sp.csc_matrix(new_mat_two_spinor) + step = int(file.split('/')[-1].replace(prop,'').replace('_','').replace('ks','').replace('.','').replace('npz','')) + sp.save_npz(f'{params["path_to_save_npz_files"]}/{prop}_ks_{step}.npz', new_mat_sparse) + print('Done with limiting the active space') + print(f'Your new path_to_npz_files is now: {params["path_to_save_npz_files"]}') + print(f'Use the new lowest_orbital: {l2} and highest_orbital: {h2}') + + return l2, h2 + def apply_phase_correction(St): """Performs the phase correction according to: From 58f7eb845dd14ca31c988674b8d1526d38cccb40 Mon Sep 17 00:00:00 2001 From: MohammadShakiba Date: Wed, 3 Jul 2024 23:39:36 -0400 Subject: [PATCH 23/48] Minor fix to function comments --- src/libra_py/workflows/nbra/step3.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libra_py/workflows/nbra/step3.py b/src/libra_py/workflows/nbra/step3.py index 17bdc6bd1..0d7d9080b 100755 --- a/src/libra_py/workflows/nbra/step3.py +++ b/src/libra_py/workflows/nbra/step3.py @@ -949,7 +949,8 @@ def limit_active_space(params): lowest_orbital (integer): The lowest_orbital defined in step 2 highest_orbital (integer): The highest_orbital defined in step 2 Returns: - None but it prints the new value of lowest_orbital and highest_orbital + l2 (integer): The new value of lowest_orbital + h2 (integer): the new value of highest_orbital """ nocc = params['num_occ_orbitals'] nunocc = params['num_unocc_orbitals'] From e42725dfd3b0da3722d65d5f8fa73811857c0cae Mon Sep 17 00:00:00 2001 From: Alexey Akimov Date: Sat, 13 Jul 2024 22:00:23 -0400 Subject: [PATCH 24/48] Modified the xyz reading function in the cp2k module to also return atom names and coordinates, added the methods for interfacing with MOPAC code --- src/libra_py/packages/__init__.py | 3 +- src/libra_py/packages/cp2k/methods.py | 17 +- src/libra_py/packages/mopac/__init__.py | 12 ++ src/libra_py/packages/mopac/methods.py | 273 ++++++++++++++++++++++++ 4 files changed, 302 insertions(+), 3 deletions(-) create mode 100755 src/libra_py/packages/mopac/__init__.py create mode 100755 src/libra_py/packages/mopac/methods.py diff --git a/src/libra_py/packages/__init__.py b/src/libra_py/packages/__init__.py index 2025191c7..dc8905153 100755 --- a/src/libra_py/packages/__init__.py +++ b/src/libra_py/packages/__init__.py @@ -1,5 +1,5 @@ #*********************************************************** -# * Copyright (C) 2023 Alexey V. Akimov +# * Copyright (C) 2023-2024 Alexey V. Akimov # * This file is distributed under the terms of the # * GNU General Public License as published by the # * Free Software Foundation; either version 3 of the @@ -12,6 +12,7 @@ "ergo", "gaussian", "lammps", + "mopac", "qe" ] diff --git a/src/libra_py/packages/cp2k/methods.py b/src/libra_py/packages/cp2k/methods.py index b995d7708..8714a4d33 100755 --- a/src/libra_py/packages/cp2k/methods.py +++ b/src/libra_py/packages/cp2k/methods.py @@ -421,7 +421,7 @@ def read_trajectory_xyz_file(file_name: str, step: int): Returns: - None + (list, MATRIX(ndof, 1)): labels of all atoms, and their coordinates, ndof = 3 * natoms """ @@ -432,6 +432,8 @@ def read_trajectory_xyz_file(file_name: str, step: int): # The number of atoms for each time step in the .xyz file of the trajectory. number_of_atoms = int(lines[0].split()[0]) + q = MATRIX(3*number_of_atoms, 1) + # Write the coordinates of the 't' th step in file coord-t.xyz f = open('coord-%d'%step+'.xyz','w') @@ -443,7 +445,18 @@ def read_trajectory_xyz_file(file_name: str, step: int): f.write( lines[i] ) f.close() - + labels = [] + for i in range(number_of_atoms): + tmp = lines[ n * step + 2 + i ].split() + labels.append( tmp[0]) + x = float( tmp[1]) * units.Angst # convert Angstrom to Bohr + y = float( tmp[2]) * units.Angst # convert Angstrom to Bohr + z = float( tmp[3]) * units.Angst # convert Angstrom to Bohr + q.set(3*i + 0, 0, x) + q.set(3*i + 1, 0, y) + q.set(3*i + 2, 0, z) + + return labels, q diff --git a/src/libra_py/packages/mopac/__init__.py b/src/libra_py/packages/mopac/__init__.py new file mode 100755 index 000000000..45edaa5ae --- /dev/null +++ b/src/libra_py/packages/mopac/__init__.py @@ -0,0 +1,12 @@ +#*********************************************************** +# * Copyright (C) 2023-2024 Alexey V. Akimov +# * This file is distributed under the terms of the +# * GNU General Public License as published by the +# * Free Software Foundation; either version 3 of the +# * License, or (at your option) any later version. +# * http://www.gnu.org/copyleft/gpl.txt +#***********************************************************/ + +__all__ = ["methods" + ] + diff --git a/src/libra_py/packages/mopac/methods.py b/src/libra_py/packages/mopac/methods.py new file mode 100755 index 000000000..ea4cddc93 --- /dev/null +++ b/src/libra_py/packages/mopac/methods.py @@ -0,0 +1,273 @@ +#********************************************************************************* +#* Copyright (C) 2024 Alexey V. Akimov +#* +#* This file is distributed under the terms of the GNU General Public License +#* as published by the Free Software Foundation, either version 3 of +#* the License, or (at your option) any later version. +#* See the file LICENSE in the root directory of this distribution +#* or . +#* +#*********************************************************************************/ +""" +.. module:: methods + :platform: Unix, Windows + :synopsis: This module implements functions for interfacing Libra to MOPAC package + +.. moduleauthor:: + Alexey V. Akimov + +""" + + +import os +import sys +import math +import re +import numpy as np + +if sys.platform=="cygwin": + from cyglibra_core import * +elif sys.platform=="linux" or sys.platform=="linux2": + from liblibra_core import * +import util.libutil as comn + +from libra_py import units +from libra_py import scan +from libra_py import regexlib as rgl + +import libra_py.packages.cp2k.methods as CP2K_methods + + +def make_mopac_input(mopac_input_filename, mopac_run_params, labels, coords): + """ + This function creates an input file for MOPAC package using the + parameters passed in the `mopac_input_params` dictionary + + Args: + * mopac_input_filename ( string ): the name of the input file to create + + * mopac_run_params ( string ): the string containing the specification for the MOPAC run. + E.g. one can use: "INDO C.I.=(6,3) CHARGE=0 RELSCF=0.000001 ALLVEC WRTCONF=0.00" + + * labels (list of stings): element symbols for atoms in the system (N items), e.g. + ["C", "H", "H", "H", "H"] for methane + + * coords ( MATRIX(3N, 1) ): Cartesian coordinates of all atoms ordered in triples x, y, z [ units: Bohr ] + + Returns: + None : just creates the files + + """ + + + # Create the actual output file + mopac_input = open(mopac_input_filename,"w"); + + mopac_input.write(F"{mopac_run_params}\n\n") + + nat = len(labels) # how many atoms + for i in range(nat): + x = coords.get(3*i+0, 0)/units.Angst + y = coords.get(3*i+0, 1)/units.Angst + z = coords.get(3*i+0, 2)/units.Angst + mopac_input.write(F"{labels[i]} {x} 1 {y} 1 {z} 1\n") + + mopac_input.close() + + +class tmp: + pass + +def run_mopac_adi(q, params_, full_id): + """ + + This function executes the MOPAC quantum chemistry calculations and + returns the key properties needed for dynamical calculations. + + Args: + q ( MATRIX(ndof, ntraj) ): coordinates of the particle [ units: Bohr ] + params ( dictionary ): model parameters + + * **params_["labels"]** ( list of strings ): the labels of atomic symbolc - for all atoms, + and in a order that is consistent with the coordinates (in triples) stored in `q`. + The number of this labels is `natoms`, such that `ndof` = 3 * `natoms`. [ Required ] + * **params["nstates"]** ( int ): the total number of electronic states + in this model [ default: 1 - just the ground state] + * **params_["mopac_exe"]** ( string ): the full path to `the mopac` executable [ defaut: "mopac" ] + * **params_["mopac_run_params"]** ( string ): the control string to define the MOPAC job + [default: "INDO C.I.=(6,3) CHARGE=0 RELSCF=0.000001 ALLVEC WRTCONF=0.00"] + * **params_["mopac_working_directory"]** ( string ) [ default: "mopac_wd"] + * **params_["mopac_jobid"]** ( string ) [ default: "job_0000" ] + * **params_["mopac_input_prefix"]** ( string ) [ default: "input_" ] + * **params_["mopac_output_prefix"]** ( string ) [ default: "output_" ] + + Returns: + PyObject: obj, with the members: + + * obj.ham_adi ( CMATRIX(nstates,nstates) ): adiabatic Hamiltonian + * obj.d1ham_adi ( list of ndof CMATRIX(nstates, nstates) objects ): + derivatives of the adiabatic Hamiltonian w.r.t. the nuclear coordinate + + + string method_id="HAMILTONIAN:"; + string time_step_id="TIME STEPS:"; + string trajectory_id="TRAJECTORY FILE:"; + string trajectory_directory_id="TRAJECTORY DIRECTORY:"; + string charge_id="CHARGE:"; + string convergence_id="SCF CONVERGENCE CRITERIA:"; + string mopac_prefix_id="MOPAC PREFIX:"; + + """ + + params = dict(params_) + + critical_params = [ "labels" ] + default_params = { "nstates":1, + "mopac_exe":"mopac", + "mopac_run_params":"INDO C.I.=(6,3) CHARGE=0 RELSCF=0.000001 ALLVEC WRTCONF=0.00", + "mopac_working_directory":"mopac_wd", + "mopac_jobid":"job_0000", + "mopac_input_prefix":"input_", "mopac_output_prefix":"output_" + } + comn.check_input(params, default_params, critical_params) + + labels = params["labels"] + nstates = params["nstates"] + mopac_exe = params["mopac_exe"] + mopac_run_params = params["mopac_run_params"] + mopac_wd = params["mopac_working_directory"] + mopac_jobid = params["mopac_jobid"] + mopac_input_prefix = params["mopac_input_prefix"] + mopac_output_prefix = params["mopac_output_prefix"] + + natoms = len(labels) + ndof = 3 * natoms + + obj = tmp() + obj.ham_adi = CMATRIX(nstates, nstates) + obj.nac_adi = CMATRIX(nstates, nstates) + obj.hvib_adi = CMATRIX(nstates, nstates) + obj.basis_transform = CMATRIX(nstates, nstates) + obj.time_overlap_adi = CMATRIX(nstates, nstates) + obj.d1ham_adi = CMATRIXList(); + obj.dc1_adi = CMATRIXList(); + for idof in range(ndof): + obj.d1ham_adi.append( CMATRIX(nstates, nstates) ) + obj.dc1_adi.append( CMATRIX(nstates, nstates) ) + + + Id = Cpp2Py(full_id) + indx = Id[-1] + + coords = q.col(indx) + + # Create working directory, if doesn't exist + if not os.path.exists(mopac_wd): + os.mkdir(mopac_wd) + + # Go into that directory + os.chdir(mopac_wd) + + # Create input + mopac_input_filename = F"{mopac_input_prefix}{mopac_jobid}" + make_mopac_input(mopac_input_filename, mopac_run_params, labels, coords) + + # Run the MOPAC job + mopac_output_filename = F"{mopac_output_prefix}{mopac_jobid}" + os.system( F"{mopac_exe} {mopac_input_filename} > {mopac_output_filename}") + + # Go back to the original directory + os.chdir("../") + + """ + # At this point, we should have the "detailed.out" file created, so lets read it + E, grad = read_dftb_output(natoms, istate) + + # Now, populate the allocated matrices + obj.ham_adi.set(istate, istate, E * (1.0+0.0j) ) + obj.hvib_adi.set(istate, istate, E * (1.0+0.0j) ) + obj.basis_transform.set(istate, istate, 1.0+0.0j ) + obj.time_overlap_adi.set(istate, istate, 1.0+0.0j ) + for idof in range(ndof): + obj.d1ham_adi[idof].set(istate, istate, grad.get(idof, 0) * (1.0+0.0j) ) + """ + + return obj + + +def mopac_nbra_workflow(params_): + pass + #labels, q = cp2k.read_trajectory_xyz_file("1_ring-pos-1.xyz", 0) + #run_mopac_adi(q, params_, full_id) + + + +def read_mopac_output(natoms, istate): + """ + TO DO: This function is yet to be implemented + This file reads in the total energy (essentially the reference, ground state energy), + the excitation energies, and the corresponding forces on all atoms and return them in + a digital format for further processing. + + Args: + + natoms ( int ): the number of atoms in the systemm + istate ( int ): the index of the calculation - just for archiving purposes + + Returns: + double, MATRIX(ndof, 1): the total energy of a given electronic state, + and the corresponding gradients + """ + + # Check the successful completion of the calculations like this: + if os.path.isfile('detailed.out'): + #print("Found detailed.out") + os.system(F"cp detailed.out detailed_{istate}.out") + else: + print("\nCannot find the file detailed.out") + print(F"Hint: Current working directory is: {os.getcwd()}") + print("Is this where you expect the file detailed.out to be found?") + print("Exiting program...\n") + sys.exit(0) + + + ndof = 3 * natoms + grad = MATRIX(ndof, 1) + + + f = open("detailed.out") + output = f.readlines() + f.close() + + E = 0.0 + nlines = len(output) + + for i in range( nlines ): + + output_line = output[i].split() + + if len( output_line ) >= 2: + + if output_line[0] == "Total" and output_line[1] == "Forces": + + for j in range( natoms ): + + next_lines = output[i+j+1].split() + + grad.set( 3*j+0, 0, -float(next_lines[1]) ) + grad.set( 3*j+1, 0, -float(next_lines[2]) ) + grad.set( 3*j+2, 0, -float(next_lines[3]) ) + + if output_line[0] == "Excitation" and output_line[1] == "Energy:" : + + E += float(output_line[2]) # energy in a.u. + #print(output_line[2]) + + if output_line[0] == "Total" and output_line[1] == "energy:" : + + E += float(output_line[2]) # energy in a.u. + #print(output_line[2]) + + #print(F"Final energy = {E}") + return E, grad + From 688ae7b3916e35bf4e6ccc76ea20f8dc5d8903e0 Mon Sep 17 00:00:00 2001 From: Alexey Akimov Date: Sun, 14 Jul 2024 13:23:00 -0400 Subject: [PATCH 25/48] Fixed the MOPAC input file creation, added the function to read MOs and orbital energies from a file --- src/libra_py/packages/mopac/methods.py | 150 +++++++++++++++---------- 1 file changed, 89 insertions(+), 61 deletions(-) diff --git a/src/libra_py/packages/mopac/methods.py b/src/libra_py/packages/mopac/methods.py index ea4cddc93..57649613d 100755 --- a/src/libra_py/packages/mopac/methods.py +++ b/src/libra_py/packages/mopac/methods.py @@ -63,7 +63,7 @@ def make_mopac_input(mopac_input_filename, mopac_run_params, labels, coords): # Create the actual output file mopac_input = open(mopac_input_filename,"w"); - mopac_input.write(F"{mopac_run_params}\n\n") + mopac_input.write(F"{mopac_run_params}\n\n\n") nat = len(labels) # how many atoms for i in range(nat): @@ -71,7 +71,7 @@ def make_mopac_input(mopac_input_filename, mopac_run_params, labels, coords): y = coords.get(3*i+0, 1)/units.Angst z = coords.get(3*i+0, 2)/units.Angst mopac_input.write(F"{labels[i]} {x} 1 {y} 1 {z} 1\n") - + mopac_input.write("\n") mopac_input.close() @@ -107,15 +107,6 @@ def run_mopac_adi(q, params_, full_id): * obj.ham_adi ( CMATRIX(nstates,nstates) ): adiabatic Hamiltonian * obj.d1ham_adi ( list of ndof CMATRIX(nstates, nstates) objects ): derivatives of the adiabatic Hamiltonian w.r.t. the nuclear coordinate - - - string method_id="HAMILTONIAN:"; - string time_step_id="TIME STEPS:"; - string trajectory_id="TRAJECTORY FILE:"; - string trajectory_directory_id="TRAJECTORY DIRECTORY:"; - string charge_id="CHARGE:"; - string convergence_id="SCF CONVERGENCE CRITERIA:"; - string mopac_prefix_id="MOPAC PREFIX:"; """ @@ -202,72 +193,109 @@ def mopac_nbra_workflow(params_): -def read_mopac_output(natoms, istate): +def read_mopac_mos(params_): """ - TO DO: This function is yet to be implemented - This file reads in the total energy (essentially the reference, ground state energy), - the excitation energies, and the corresponding forces on all atoms and return them in - a digital format for further processing. + This function reads the MOs from the output files Args: - natoms ( int ): the number of atoms in the systemm - istate ( int ): the index of the calculation - just for archiving purposes + params_ ( dict ): the dictionary containing key parameters + + * **params_["filename"]** ( string ) : the name of the file to read Returns: - double, MATRIX(ndof, 1): the total energy of a given electronic state, - and the corresponding gradients + MATRIX(nao, nmo): the matrix of the MO-LCAO coefficients + """ + params = dict(params_) + + critical_params = [ ] + default_params = { "filename":"output" } + comn.check_input(params, default_params, critical_params) + + out_file = params["filename"] + # Check the successful completion of the calculations like this: - if os.path.isfile('detailed.out'): - #print("Found detailed.out") - os.system(F"cp detailed.out detailed_{istate}.out") + if os.path.isfile(out_file): + pass else: - print("\nCannot find the file detailed.out") + print(F"Cannot find the file {out_file}") print(F"Hint: Current working directory is: {os.getcwd()}") print("Is this where you expect the file detailed.out to be found?") print("Exiting program...\n") sys.exit(0) - - - ndof = 3 * natoms - grad = MATRIX(ndof, 1) - - - f = open("detailed.out") + + f = open(out_file) output = f.readlines() f.close() - - E = 0.0 nlines = len(output) - - for i in range( nlines ): - - output_line = output[i].split() - if len( output_line ) >= 2: - - if output_line[0] == "Total" and output_line[1] == "Forces": - - for j in range( natoms ): - - next_lines = output[i+j+1].split() - - grad.set( 3*j+0, 0, -float(next_lines[1]) ) - grad.set( 3*j+1, 0, -float(next_lines[2]) ) - grad.set( 3*j+2, 0, -float(next_lines[3]) ) - - if output_line[0] == "Excitation" and output_line[1] == "Energy:" : - - E += float(output_line[2]) # energy in a.u. - #print(output_line[2]) - - if output_line[0] == "Total" and output_line[1] == "energy:" : - - E += float(output_line[2]) # energy in a.u. - #print(output_line[2]) - - #print(F"Final energy = {E}") - return E, grad + # First, let's find where the MOs are and count how many of them we have + ibeg, iend, nmo = 0, nlines-1, 0 + for i in range(nlines): + line = output[i] + + if line.find("MOLECULAR ORBITALS") != -1: + ibeg = i + if line.find("Reference determinate nber") != -1: + iend = i + + if line.find("ROOT NO.") != -1: + nmo = int(float(line.split()[-1])) + + if False: # Make True, for debugging + print("nmo = ", nmo) + print(output[ibeg:iend]) + + nao = nmo + + # Find the line indices that contain "ROOT NO." keyword + # the last one will be the `iend` + break_lines = [] + for i in range(ibeg, iend): + line = output[i] + if line.find("ROOT NO.") != -1: + break_lines.append(i) + break_lines.append(iend) + nblocks = len(break_lines) + + # Now read energies: + E = [] + Es = MATRIX(nmo, nmo) + for j in range(nblocks-1): + i = break_lines[j] + print(output[i+2]) + tmp = output[i+2].split() + for e in tmp: + ener = float(e) + E.append( ener ) + for i in range( nmo ): + Es.set(i,i, E[i]) + + if False: # Make True, for debugging + print(E) + + # Now read MOs: + mo_indx = 0 + MOs = MATRIX(nao, nmo) + for j in range(nblocks-1): + i = break_lines[j] + tmp = output[i+2].split() + ncols = len(tmp) + ao_indx = 0 + for i in range( break_lines[j]+5, break_lines[j+1]): + tmp = output[i].split() + sz = len(tmp) + if( sz == ncols + 3 ): + for a in range(ncols): + coeff = float(tmp[3+a]) + MOs.set(ao_indx, mo_indx + a, coeff) + ao_indx += 1 + mo_indx += ncols + + if False: # Make True, for debugging + MOs.show_matrix("MO.txt") + + return Es, MOs From 5ceac5adeeb4e6dc180e6a1c34dbb6127852d9b5 Mon Sep 17 00:00:00 2001 From: Alexey Akimov Date: Sun, 14 Jul 2024 14:39:12 -0400 Subject: [PATCH 26/48] Finalized the function that reads all the orbital, configurations, and CI information from the MOPAC output file and returns the corresponding objects --- src/libra_py/packages/mopac/methods.py | 94 +++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 8 deletions(-) diff --git a/src/libra_py/packages/mopac/methods.py b/src/libra_py/packages/mopac/methods.py index 57649613d..a49feba8d 100755 --- a/src/libra_py/packages/mopac/methods.py +++ b/src/libra_py/packages/mopac/methods.py @@ -193,9 +193,9 @@ def mopac_nbra_workflow(params_): -def read_mopac_mos(params_): +def read_mopac_orbital_info(params_): """ - This function reads the MOs from the output files + This function reads the MOs, configurations, and CI from the output files Args: @@ -204,7 +204,22 @@ def read_mopac_mos(params_): * **params_["filename"]** ( string ) : the name of the file to read Returns: - MATRIX(nao, nmo): the matrix of the MO-LCAO coefficients + (Es, MOs, E_CI, CI, configs): + + * Es - MATRIX(nmo, nmo): the matrix of the MO energies + * MOs - MATRIX(nao, nmo): the matrix of MO-LCAO coefficients + * E_CI - MATRIX(nci, nci): the matrix of CI energies + * CI - MATRIX(nconf, nci): the matrix of CI coefficients in the basis of spin-adapted configurations + * configs - (list of lists): information about the configurations, the first list is [-1, -1] - it corresponds + to the ground state configurations, other lists are of the kinds [i, j] which corresponds to i->j excitation + The indices i and j are enumerated starting from 0 (Python/C++ convention), which is different from the + raw MOPAC output, where MO indexing starts from 1 + Notes: + * nao - the number of AOs, it is the same as nmo + * nmo - the number of MOs + * nconf - the number of configurations (spin-adapted Slater determinants) + * nci - the number of CI states + """ params = dict(params_) @@ -244,7 +259,7 @@ def read_mopac_mos(params_): if line.find("ROOT NO.") != -1: nmo = int(float(line.split()[-1])) - if False: # Make True, for debugging + if False: # Make True for debugging print("nmo = ", nmo) print(output[ibeg:iend]) @@ -265,7 +280,6 @@ def read_mopac_mos(params_): Es = MATRIX(nmo, nmo) for j in range(nblocks-1): i = break_lines[j] - print(output[i+2]) tmp = output[i+2].split() for e in tmp: ener = float(e) @@ -273,7 +287,7 @@ def read_mopac_mos(params_): for i in range( nmo ): Es.set(i,i, E[i]) - if False: # Make True, for debugging + if False: # Make True for debugging print(E) # Now read MOs: @@ -294,8 +308,72 @@ def read_mopac_mos(params_): ao_indx += 1 mo_indx += ncols - if False: # Make True, for debugging + if False: # Make True for debugging MOs.show_matrix("MO.txt") - return Es, MOs + + # Find the configurations + nconfig = 0 + configs = [] + for i in range(nlines): + line = output[i] + if line.find("The lowest") != -1 and line.find("spin-adapted configurations of multiplicity= 1"): + tmp = line.split() + if( len(tmp) > 3): + nconfig = int( float(tmp[2]) ) + #========= Found the configurations info, now read the configurations ======== + for iconfig in range(nconfig): + tmp = output[i + 4 + iconfig].split() + if len(tmp) == 12: + configs.append( [-1, -1]) # this is the ground state determinants + if len(tmp) == 13: + i_orb = int(float(tmp[10].split(")->(")[0])) + j_orb = int(float(tmp[11])) + configs.append( [ i_orb-1, j_orb-1]) # orbital indexing from 0 + + + if False: # Make True for debugging + print(F"The number of spin-adapted configurations = {nconfig}") + print(F"configs are = {configs}") + + + # Find the line indices that contain the beginning and end of the CI information + ci_beg, ci_end = [], [] + for i in range(nlines): + line = output[i] + if line.find("State") != -1 and line.find("CI coeff") != -1 and line.find("CI percent") != -1: + ci_beg.append(i) + if line.find("Total coeff printed") != -1: + ci_end.append(i) + + nci = len(ci_beg) + + if False: # Make True for debugging + print(F"The number of CI states = {nci}") + for i in range(nci): + print(F"CI block {i}") + print(output[ci_beg[i]:ci_end[i]]) + + + # Now, read the information about CI states + E_CI = MATRIX(nci, nci) + CI = MATRIX(nconfig, nci) + for i in range(nci): + for j in range(ci_beg[i], ci_end[i]): + tmp = output[j].split() + sz = len(tmp) + if sz==7: + ener = float( tmp[2] ) * units.ev2au # convert to a.u. + E_CI.set(i,i, ener) + elif sz==4: + if tmp[0]=="Config": + iconf = int(float(tmp[1])) - 1 + coeff = float(tmp[2]) + CI.set(iconf, i, coeff) + + + return Es, MOs, E_CI, CI, configs + + + From 3a65edaac2c86be8d73e4b01a5f6b8022681f24f Mon Sep 17 00:00:00 2001 From: Alexey Akimov Date: Sun, 14 Jul 2024 19:43:20 -0400 Subject: [PATCH 27/48] added construction of the SD configurations --- src/libra_py/packages/mopac/methods.py | 84 +++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/src/libra_py/packages/mopac/methods.py b/src/libra_py/packages/mopac/methods.py index a49feba8d..3ed589399 100755 --- a/src/libra_py/packages/mopac/methods.py +++ b/src/libra_py/packages/mopac/methods.py @@ -193,6 +193,62 @@ def mopac_nbra_workflow(params_): +def make_ref(nelec): + """ + Makes the reference determinant based on the number of electrons + + Args: + nelec (int) : the total number of electrons in the system + + Returns: + list of ints: representation of the reference (ground-state) determinant + + E.g. [1, -1, 2, -2] is the determinant of 4 electrons with 2 lowest orbitals doubly-filled + Here, the indexing starts with 1, and negative values correspond to the beta-spin electron + while positive values - to the alpha-spin electron + """ + + res, orb = [], 1 + for i in range(nelec): + if i%2==0: + res.append(orb) + else: + res.append(-orb) + orb = orb + 1 + return res + +def make_alpha_excitation(ref_determinant, config): + """ + This function creates an excitation of the alpha electron in the reference determinant + exciting the electron from and to orbitals determined by the `config` argument + + Args: + + ref_determinant (list of ints): representation of the reference determinants + + config (list of 2 ints): [src, targt] - source and target orbitals for single excitation, the + indexing starts from 1, not from 0. + + Returns: + list of ints: the representation of the excited configuration + + Example: + make_alpha_excitation([1, -1, 2, -2], [2, 3]) corresponds to 2->3 excitation and should return + + [1, -1, 3, -2] + """ + src = config[0] + trgt = config[1] + + res = list(ref_determinant) + nelec = len(res) + for i in range(nelec): + if res[i]==src: + res[i] = trgt + + return res + + def read_mopac_orbital_info(params_): """ This function reads the MOs, configurations, and CI from the output files @@ -210,15 +266,15 @@ def read_mopac_orbital_info(params_): * MOs - MATRIX(nao, nmo): the matrix of MO-LCAO coefficients * E_CI - MATRIX(nci, nci): the matrix of CI energies * CI - MATRIX(nconf, nci): the matrix of CI coefficients in the basis of spin-adapted configurations - * configs - (list of lists): information about the configurations, the first list is [-1, -1] - it corresponds - to the ground state configurations, other lists are of the kinds [i, j] which corresponds to i->j excitation - The indices i and j are enumerated starting from 0 (Python/C++ convention), which is different from the - raw MOPAC output, where MO indexing starts from 1 + * sd_basis - (list of lists of `nelec` ints): representation of the Slater determinants of `nelec`-electronic + states. The indexing starts from 1. + Notes: * nao - the number of AOs, it is the same as nmo * nmo - the number of MOs * nconf - the number of configurations (spin-adapted Slater determinants) * nci - the number of CI states + * nelec - the number of electrons """ @@ -246,6 +302,14 @@ def read_mopac_orbital_info(params_): f.close() nlines = len(output) + # Determine the number of electrons: + nocc = 0 + for i in range(nlines): + line = output[i] + if line.find("RHF CALCULATION, NO. OF DOUBLY OCCUPIED LEVELS") != -1: + nocc = int( float(line.split()[8]) ) + nelec = 2 * nocc + # First, let's find where the MOs are and count how many of them we have ibeg, iend, nmo = 0, nlines-1, 0 for i in range(nlines): @@ -325,11 +389,17 @@ def read_mopac_orbital_info(params_): for iconfig in range(nconfig): tmp = output[i + 4 + iconfig].split() if len(tmp) == 12: - configs.append( [-1, -1]) # this is the ground state determinants + configs.append( [1, 1]) # this is the ground state determinant if len(tmp) == 13: i_orb = int(float(tmp[10].split(")->(")[0])) j_orb = int(float(tmp[11])) - configs.append( [ i_orb-1, j_orb-1]) # orbital indexing from 0 + configs.append( [ i_orb, j_orb]) # orbital indexing from 1 - as in MOPAC + + sd_basis = [] + ref = make_ref(nelec); + for cnf in configs: + sd = make_alpha_excitation(ref, cnf) + sd_basis.append(sd) if False: # Make True for debugging @@ -372,7 +442,7 @@ def read_mopac_orbital_info(params_): CI.set(iconf, i, coeff) - return Es, MOs, E_CI, CI, configs + return Es, MOs, E_CI, CI, sd_basis From 086745df29434980eafc0da49d49d4aba42133f8 Mon Sep 17 00:00:00 2001 From: Alexey Akimov Date: Tue, 16 Jul 2024 01:38:11 -0400 Subject: [PATCH 28/48] More improvements of the MOPAC functions, added the function to run the workflow calculations --- src/libra_py/packages/mopac/methods.py | 225 ++++++++++++++++++------- 1 file changed, 167 insertions(+), 58 deletions(-) diff --git a/src/libra_py/packages/mopac/methods.py b/src/libra_py/packages/mopac/methods.py index 3ed589399..df61a888f 100755 --- a/src/libra_py/packages/mopac/methods.py +++ b/src/libra_py/packages/mopac/methods.py @@ -36,7 +36,7 @@ from libra_py import regexlib as rgl import libra_py.packages.cp2k.methods as CP2K_methods - +import libra_py.workflows.nbra.mapping2 as mapping2 def make_mopac_input(mopac_input_filename, mopac_run_params, labels, coords): """ @@ -47,7 +47,7 @@ def make_mopac_input(mopac_input_filename, mopac_run_params, labels, coords): * mopac_input_filename ( string ): the name of the input file to create * mopac_run_params ( string ): the string containing the specification for the MOPAC run. - E.g. one can use: "INDO C.I.=(6,3) CHARGE=0 RELSCF=0.000001 ALLVEC WRTCONF=0.00" + E.g. one can use: "INDO C.I.=(6,3) CHARGE=0 RELSCF=0.000001 ALLVEC WRTCONF=0.00 WRTCI=2" * labels (list of stings): element symbols for atoms in the system (N items), e.g. ["C", "H", "H", "H", "H"] for methane @@ -78,52 +78,43 @@ def make_mopac_input(mopac_input_filename, mopac_run_params, labels, coords): class tmp: pass -def run_mopac_adi(q, params_, full_id): +def run_mopac(coords, params_): """ - This function executes the MOPAC quantum chemistry calculations and - returns the key properties needed for dynamical calculations. + This function executes the MOPAC quantum chemistry calculations Args: - q ( MATRIX(ndof, ntraj) ): coordinates of the particle [ units: Bohr ] + coords ( MATRIX(ndof, 1) ): coordinates of the particle [ units: Bohr ] params ( dictionary ): model parameters * **params_["labels"]** ( list of strings ): the labels of atomic symbolc - for all atoms, and in a order that is consistent with the coordinates (in triples) stored in `q`. The number of this labels is `natoms`, such that `ndof` = 3 * `natoms`. [ Required ] - * **params["nstates"]** ( int ): the total number of electronic states - in this model [ default: 1 - just the ground state] * **params_["mopac_exe"]** ( string ): the full path to `the mopac` executable [ defaut: "mopac" ] * **params_["mopac_run_params"]** ( string ): the control string to define the MOPAC job - [default: "INDO C.I.=(6,3) CHARGE=0 RELSCF=0.000001 ALLVEC WRTCONF=0.00"] + [default: "INDO C.I.=(6,3) CHARGE=0 RELSCF=0.000001 ALLVEC WRTCONF=0.00 WRTCI=2"] * **params_["mopac_working_directory"]** ( string ) [ default: "mopac_wd"] * **params_["mopac_jobid"]** ( string ) [ default: "job_0000" ] * **params_["mopac_input_prefix"]** ( string ) [ default: "input_" ] * **params_["mopac_output_prefix"]** ( string ) [ default: "output_" ] Returns: - PyObject: obj, with the members: - - * obj.ham_adi ( CMATRIX(nstates,nstates) ): adiabatic Hamiltonian - * obj.d1ham_adi ( list of ndof CMATRIX(nstates, nstates) objects ): - derivatives of the adiabatic Hamiltonian w.r.t. the nuclear coordinate + None """ params = dict(params_) critical_params = [ "labels" ] - default_params = { "nstates":1, - "mopac_exe":"mopac", - "mopac_run_params":"INDO C.I.=(6,3) CHARGE=0 RELSCF=0.000001 ALLVEC WRTCONF=0.00", + default_params = { "mopac_exe":"mopac", + "mopac_run_params":"INDO C.I.=(6,3) CHARGE=0 RELSCF=0.000001 ALLVEC WRTCONF=0.00 WRTCI=2", "mopac_working_directory":"mopac_wd", "mopac_jobid":"job_0000", "mopac_input_prefix":"input_", "mopac_output_prefix":"output_" } comn.check_input(params, default_params, critical_params) - labels = params["labels"] - nstates = params["nstates"] + labels = params["labels"] mopac_exe = params["mopac_exe"] mopac_run_params = params["mopac_run_params"] mopac_wd = params["mopac_working_directory"] @@ -134,24 +125,6 @@ def run_mopac_adi(q, params_, full_id): natoms = len(labels) ndof = 3 * natoms - obj = tmp() - obj.ham_adi = CMATRIX(nstates, nstates) - obj.nac_adi = CMATRIX(nstates, nstates) - obj.hvib_adi = CMATRIX(nstates, nstates) - obj.basis_transform = CMATRIX(nstates, nstates) - obj.time_overlap_adi = CMATRIX(nstates, nstates) - obj.d1ham_adi = CMATRIXList(); - obj.dc1_adi = CMATRIXList(); - for idof in range(ndof): - obj.d1ham_adi.append( CMATRIX(nstates, nstates) ) - obj.dc1_adi.append( CMATRIX(nstates, nstates) ) - - - Id = Cpp2Py(full_id) - indx = Id[-1] - - coords = q.col(indx) - # Create working directory, if doesn't exist if not os.path.exists(mopac_wd): os.mkdir(mopac_wd) @@ -169,27 +142,6 @@ def run_mopac_adi(q, params_, full_id): # Go back to the original directory os.chdir("../") - - """ - # At this point, we should have the "detailed.out" file created, so lets read it - E, grad = read_dftb_output(natoms, istate) - - # Now, populate the allocated matrices - obj.ham_adi.set(istate, istate, E * (1.0+0.0j) ) - obj.hvib_adi.set(istate, istate, E * (1.0+0.0j) ) - obj.basis_transform.set(istate, istate, 1.0+0.0j ) - obj.time_overlap_adi.set(istate, istate, 1.0+0.0j ) - for idof in range(ndof): - obj.d1ham_adi[idof].set(istate, istate, grad.get(idof, 0) * (1.0+0.0j) ) - """ - - return obj - - -def mopac_nbra_workflow(params_): - pass - #labels, q = cp2k.read_trajectory_xyz_file("1_ring-pos-1.xyz", 0) - #run_mopac_adi(q, params_, full_id) @@ -446,4 +398,161 @@ def read_mopac_orbital_info(params_): +def mopac_compute_adi(q, params, full_id): + """ + + This function creates an input for MOPAC, runs such calculations, extracts the current information on + state energies and CI vectors, computes the required properties (such as time-overlaps, or NACs, etc.) + and stores the current information in the "previous variables", so that we could compute the dependent properties + on the next time-step + + Args: + q ( MATRIX(ndof, ntraj) ): coordinates of the particle [ units: Bohr ] + params ( list of dictionaries ): model parameters, for each trajectory; this parameters variable will be used to + also store the previous calculations, but that has to be done separately for each trajectory, i = full_id[-1]. That's why we + are making it into a list of dictionaries + + * **params[i]["timestep"]** (int): the index of the timestep for trajectory i [ Required ] + * **params[i]["is_first_time"]** (int): the flag indicating if this is the new calculation for this trajectory or not + if it is True (1), the current values will be used as if they were previous; if False (0) - the previously stored values + will be used [ default: True ] + * **params[i]["labels"]** ( list of strings ): the labels of atomic symbolc - for all atoms, + and in a order that is consistent with the coordinates (in triples) stored in `q`. + The number of this labels is `natoms`, such that `ndof` = 3 * `natoms`. [ Required ] + * **params[i]["mopac_exe"]** ( string ): the full path to `the mopac` executable [ defaut: "mopac" ] + * **params_[i]["mopac_run_params"]** ( string ): the control string to define the MOPAC job + [default: "INDO C.I.=(6,3) CHARGE=0 RELSCF=0.000001 ALLVEC WRTCONF=0.00 WRTCI=2"] + * **params[i]["mopac_working_directory"]** ( string ) [ default: "mopac_wd"] + * **params[i]["mopac_jobid"]** ( string ) [ default: F"timestep_{timestep}_traj_{i}" ] + * **params[i]["mopac_input_prefix"]** ( string ) [ default: "input_" ] + * **params[i]["mopac_output_prefix"]** ( string ) [ default: "output_" ] + * **params[i]["dt"]** ( float ) - the time interval between the snapshots [ units: a.u.; default: 41 a.u. = 1 fs ] + + Returns: + PyObject: obj, with the members: + + * obj.ham_adi ( CMATRIX(nstates,nstates) ): adiabatic Hamiltonian, in the CI basis + * obj.hvib_adi ( CMATRIX(nstates,nstates) ): adiabatic vibronic Hamiltonian, in the CI basis + * obj.basis_transform ( CMATRIX(nstates,nstates) ): assumed the identity - don't use it yet! + * obj.time_overlap_adi. ( CMATRIX(nstates,nstates) ): time-overlap in the CI basis + + """ + + #params = dict(params_) + + Id = Cpp2Py(full_id) + itraj = Id[-1] + coords = q.col(itraj) + + + critical_params = [ "labels", "timestep" ] + default_params = { "mopac_exe":"mopac", "is_first_time":True, + "mopac_run_params":"INDO C.I.=(6,3) CHARGE=0 RELSCF=0.000001 ALLVEC WRTCONF=0.00 WRTCI=2", + "mopac_working_directory":"mopac_wd", + "mopac_input_prefix":"input_", "mopac_output_prefix":"output_", + "dt":1.0*units.fs2au + } + comn.check_input(params[itraj], default_params, critical_params) + + + timestep = params[itraj]["timestep"] + labels = params[itraj]["labels"] + is_first_time = params[itraj]["is_first_time"] + mopac_exe = params[itraj]["mopac_exe"] + mopac_run_params = params[itraj]["mopac_run_params"] + mopac_wd = params[itraj]["mopac_working_directory"] + mopac_jobid = params[itraj]["mopac_jobid"] = F"timestep_{timestep}_traj_{itraj}" + mopac_input_prefix = params[itraj]["mopac_input_prefix"] + mopac_output_prefix = params[itraj]["mopac_output_prefix"] + dt = params[itraj]["dt"] + + natoms = len(labels) + ndof = 3 * natoms + + # Run the calculations + #print("================ RUN MOPAC =================\n") + run_mopac(coords, params[itraj]) + + # Read the current output + filename = F"{mopac_wd}/{mopac_input_prefix}{mopac_jobid}.out" + + #print("================ READ MOPAC =================\n") + #print("cwd = ", os.getcwd(), " reading the file ", filename) + E_curr, MO_curr, E_CI_curr, CI_curr, configs_curr = read_mopac_orbital_info({"filename":filename}) + + # Get the properties at the previous time-step + E_prev, MO_prev, E_CI_prev, CI_prev, configs_prev = None, None, None, None, None + + #print("================ THE REST =================\n") + if is_first_time: + # On the first step, assume the current properties are as the previous + E_prev, MO_prev = MATRIX(E_curr), MATRIX(MO_curr) + E_CI_prev, CI_prev = MATRIX(E_CI_curr), MATRIX(CI_curr) + configs_prev = list(configs_curr) + else: + # Otherwise, retrieve the previously-stored data + E_prev = params[itraj]["E_prev"] + MO_prev = params[itraj]["MO_prev"] + E_CI_prev = params[itraj]["E_CI_prev"] + CI_prev = params[itraj]["CI_prev"] + configs_prev = params[itraj]["configs_prev"] + + + nstates = CI_curr.num_of_cols + #print("nstates = ", nstates) + + # Do the calculations - time-overlaps, energies, and Hvib + obj = tmp() + obj.ham_adi = CMATRIX(nstates, nstates) + obj.nac_adi = CMATRIX(nstates, nstates) + obj.hvib_adi = CMATRIX(nstates, nstates) + obj.basis_transform = CMATRIX(nstates, nstates) + obj.time_overlap_adi = CMATRIX(nstates, nstates) + # Don't do the derivatives yet + #obj.d1ham_adi = CMATRIXList(); + #obj.dc1_adi = CMATRIXList(); + #for idof in range(ndof): + # obj.d1ham_adi.append( CMATRIX(nstates, nstates) ) + # obj.dc1_adi.append( CMATRIX(nstates, nstates) ) + + # Time-overlap in the MO basis + mo_st = MO_prev.T() * MO_curr + + # Time-overlap in the SD basis + time_ovlp_sd = mapping2.ovlp_mat_arb(configs_prev, configs_curr, mo_st, False).real() + + # Time-overlap in the CI basis + time_ovlp_ci = CI_prev.T() * time_ovlp_sd * CI_curr + + # Now, populate the allocated matrices + for istate in range(nstates): + energ = 0.5*( E_CI_curr.get(istate, istate) + E_CI_prev.get(istate, istate) ) + obj.ham_adi.set(istate, istate, energ * (1.0+0.0j) ) + obj.hvib_adi.set(istate, istate, energ * (1.0+0.0j) ) + obj.basis_transform.set(istate, istate, 1.0+0.0j ) # assume identity + + for jstate in range(nstates): + obj.time_overlap_adi.set(istate, jstate, time_ovlp_ci.get(istate, jstate) * (1.0+0.0j) ) + # Don't do this yet + #for idof in range(ndof): + # obj.d1ham_adi[idof].set(istate, istate, grad.get(idof, 0) * (1.0+0.0j) ) + # Update the Hvib: + for istate in range(nstates): + for jstate in range(istate+1, nstates): + dij = (obj.time_overlap_adi.get(istate, jstate) - obj.time_overlap_adi.get(jstate, istate) )/(2.0*dt) + obj.hvib_adi.set(istate, jstate, dij * (0.0-1.0j) ) + obj.hvib_adi.set(jstate, istate, dij * (0.0+1.0j) ) + + # Now, make the current the previous and reset the flag `is_first_time` to False + # Note - we directly modify the input parameters + params[itraj]["E_prev"] = E_curr + params[itraj]["MO_prev"] = MO_curr + params[itraj]["E_CI_prev"] = E_CI_curr + params[itraj]["CI_prev"] = CI_curr + params[itraj]["configs_prev"] = configs_curr + params[itraj]["is_first_time"] = False + + + return obj + From 277a646945d4bd767a9e311477b417c3a7306daf Mon Sep 17 00:00:00 2001 From: Alexey Akimov Date: Wed, 17 Jul 2024 14:51:41 -0400 Subject: [PATCH 29/48] Added the capability to use the active space --- src/libra_py/packages/mopac/methods.py | 110 +++++++++++++++++++------ 1 file changed, 84 insertions(+), 26 deletions(-) diff --git a/src/libra_py/packages/mopac/methods.py b/src/libra_py/packages/mopac/methods.py index df61a888f..81cc977fd 100755 --- a/src/libra_py/packages/mopac/methods.py +++ b/src/libra_py/packages/mopac/methods.py @@ -145,28 +145,42 @@ def run_mopac(coords, params_): -def make_ref(nelec): +def make_ref(nelec, active_space=None): """ Makes the reference determinant based on the number of electrons Args: nelec (int) : the total number of electrons in the system + active_space (list of ints): the indices of allowed orbitals, starting from 1 [default: None] Returns: list of ints: representation of the reference (ground-state) determinant E.g. [1, -1, 2, -2] is the determinant of 4 electrons with 2 lowest orbitals doubly-filled Here, the indexing starts with 1, and negative values correspond to the beta-spin electron - while positive values - to the alpha-spin electron + while positive values - to the alpha-spin electron. + + If the active_space is [2], then the above reference determinant will become just [2, -2] """ res, orb = [], 1 - for i in range(nelec): - if i%2==0: - res.append(orb) - else: - res.append(-orb) - orb = orb + 1 + if active_space==None: + for i in range(nelec): + if i%2==0: + res.append(orb) + else: + res.append(-orb) + orb = orb + 1 + + else: + for i in range(nelec): + if i%2==0: + if orb in active_space: + res.append(orb) + else: + if orb in active_space: + res.append(-orb) + orb = orb + 1 return res def make_alpha_excitation(ref_determinant, config): @@ -210,18 +224,23 @@ def read_mopac_orbital_info(params_): params_ ( dict ): the dictionary containing key parameters * **params_["filename"]** ( string ) : the name of the file to read + * **params_["active_space"]** (list of ints): the orbital numbers to be includes, the indexing starts with 1, not 0 [default: None] Returns: (Es, MOs, E_CI, CI, configs): - * Es - MATRIX(nmo, nmo): the matrix of the MO energies - * MOs - MATRIX(nao, nmo): the matrix of MO-LCAO coefficients + * Es - MATRIX(nact, nact): the matrix of the MO energies for active MOs + * MOs - MATRIX(nao, nact): the matrix of MO-LCAO coefficients for active MOs * E_CI - MATRIX(nci, nci): the matrix of CI energies * CI - MATRIX(nconf, nci): the matrix of CI coefficients in the basis of spin-adapted configurations - * sd_basis - (list of lists of `nelec` ints): representation of the Slater determinants of `nelec`-electronic - states. The indexing starts from 1. + * sd_basis - (list of lists of `nact` ints): representation of the Slater determinants of `nelec`-electronic + states but with the restriction of using only the orbitals belonging to the active space. This is the reduced basis + The indexing starts from 1. + * sd_basis_raw - (list of lists of `nelec` ints): representation of the Slater determinants of `nelec`-electronic + states. The indexing starts from 1. This is the original (raw) basis without the restriction. Notes: + * nact - the number of active MOs to be included * nao - the number of AOs, it is the same as nmo * nmo - the number of MOs * nconf - the number of configurations (spin-adapted Slater determinants) @@ -233,7 +252,7 @@ def read_mopac_orbital_info(params_): params = dict(params_) critical_params = [ ] - default_params = { "filename":"output" } + default_params = { "filename":"output", "active_space":None } comn.check_input(params, default_params, critical_params) out_file = params["filename"] @@ -280,6 +299,13 @@ def read_mopac_orbital_info(params_): print(output[ibeg:iend]) nao = nmo + active_space = range(1, nmo+1); + if params["active_space"] == None: + pass # defualt - use all orbitals + else: + active_space = list(params["active_space"]) + + nact = len(active_space) # Find the line indices that contain "ROOT NO." keyword # the last one will be the `iend` @@ -293,22 +319,27 @@ def read_mopac_orbital_info(params_): # Now read energies: E = [] - Es = MATRIX(nmo, nmo) for j in range(nblocks-1): i = break_lines[j] tmp = output[i+2].split() for e in tmp: ener = float(e) E.append( ener ) - for i in range( nmo ): - Es.set(i,i, E[i]) + + #Es = MATRIX(nmo, nmo) + Es = MATRIX(nact, nact) + for i in range( nact ): + j = active_space[i] - 1 + Es.set(i,i, E[j]) if False: # Make True for debugging print(E) # Now read MOs: mo_indx = 0 - MOs = MATRIX(nao, nmo) + #MOs = MATRIX(nao, nmo) + MOs = MATRIX(nao, nact) + for j in range(nblocks-1): i = break_lines[j] tmp = output[i+2].split() @@ -320,7 +351,10 @@ def read_mopac_orbital_info(params_): if( sz == ncols + 3 ): for a in range(ncols): coeff = float(tmp[3+a]) - MOs.set(ao_indx, mo_indx + a, coeff) + #MOs.set(ao_indx, mo_indx + a, coeff) + if mo_indx+a+1 in active_space: + indx = active_space.index(mo_indx+a+1) + MOs.set(ao_indx, indx, coeff) ao_indx += 1 mo_indx += ncols @@ -347,12 +381,24 @@ def read_mopac_orbital_info(params_): j_orb = int(float(tmp[11])) configs.append( [ i_orb, j_orb]) # orbital indexing from 1 - as in MOPAC - sd_basis = [] - ref = make_ref(nelec); + # Raw SD basis - using the indixes (starting with 1) used in the original N-electron system + sd_basis_raw = [] + ref = make_ref(nelec, active_space); # make a restricted reference determinant for cnf in configs: sd = make_alpha_excitation(ref, cnf) - sd_basis.append(sd) + sd_basis_raw.append(sd) + # Make the SD basis restructed - so reindex everything according to active_space + sd_basis = [] + for cnf_raw in sd_basis_raw: + cnf = [] + for a in cnf_raw: + sign = 1 + if a<0.0: + sign = -1 + b = active_space.index(abs(a)) + 1 + cnf.append( sign * b) + sd_basis.append(cnf) if False: # Make True for debugging print(F"The number of spin-adapted configurations = {nconfig}") @@ -394,7 +440,7 @@ def read_mopac_orbital_info(params_): CI.set(iconf, i, coeff) - return Es, MOs, E_CI, CI, sd_basis + return Es, MOs, E_CI, CI, sd_basis, sd_basis_raw @@ -412,6 +458,7 @@ def mopac_compute_adi(q, params, full_id): also store the previous calculations, but that has to be done separately for each trajectory, i = full_id[-1]. That's why we are making it into a list of dictionaries + * **params[i]["active_space"]** (list of ints): the orbital numbers to be includes, the indexing starts with 1, not 0 [default: None] * **params[i]["timestep"]** (int): the index of the timestep for trajectory i [ Required ] * **params[i]["is_first_time"]** (int): the flag indicating if this is the new calculation for this trajectory or not if it is True (1), the current values will be used as if they were previous; if False (0) - the previously stored values @@ -436,6 +483,15 @@ def mopac_compute_adi(q, params, full_id): * obj.basis_transform ( CMATRIX(nstates,nstates) ): assumed the identity - don't use it yet! * obj.time_overlap_adi. ( CMATRIX(nstates,nstates) ): time-overlap in the CI basis + Also, the following key-value pairs right in the input parameters dictionaries will be created/updated: + + * **params[i]["E_prev"]** (MATRIX(nact, nact)): the diagonal matrix of MO energies in the MO basis belonging to the active space + * **params[i]["MO_prev"]** (MATRIX(nao, nact)): the MOs belonging to the active space + * **params[i]["E_CI_prev"]** (MATRIX(nstates, nstates)): the diagonal matrix of the CI state energies + * **params[i]["CI_prev"]** (MATRIX(nconf, nstates)): the CI eigenvectors in the basis of configuration functions + * **params[i]["configs_prev"]** (list of lists): the list of reduced-notation configurations + * **params[i]["configs_raw_prev"]** (list of lists): the list of full-notation configurations + """ #params = dict(params_) @@ -446,7 +502,7 @@ def mopac_compute_adi(q, params, full_id): critical_params = [ "labels", "timestep" ] - default_params = { "mopac_exe":"mopac", "is_first_time":True, + default_params = { "mopac_exe":"mopac", "is_first_time":True, "active_space":None, "mopac_run_params":"INDO C.I.=(6,3) CHARGE=0 RELSCF=0.000001 ALLVEC WRTCONF=0.00 WRTCI=2", "mopac_working_directory":"mopac_wd", "mopac_input_prefix":"input_", "mopac_output_prefix":"output_", @@ -478,10 +534,10 @@ def mopac_compute_adi(q, params, full_id): #print("================ READ MOPAC =================\n") #print("cwd = ", os.getcwd(), " reading the file ", filename) - E_curr, MO_curr, E_CI_curr, CI_curr, configs_curr = read_mopac_orbital_info({"filename":filename}) + E_curr, MO_curr, E_CI_curr, CI_curr, configs_curr, configs_raw_curr = read_mopac_orbital_info({"filename":filename}) # Get the properties at the previous time-step - E_prev, MO_prev, E_CI_prev, CI_prev, configs_prev = None, None, None, None, None + E_prev, MO_prev, E_CI_prev, CI_prev, configs_prev, configs_raw_prev = None, None, None, None, None, None #print("================ THE REST =================\n") if is_first_time: @@ -489,6 +545,7 @@ def mopac_compute_adi(q, params, full_id): E_prev, MO_prev = MATRIX(E_curr), MATRIX(MO_curr) E_CI_prev, CI_prev = MATRIX(E_CI_curr), MATRIX(CI_curr) configs_prev = list(configs_curr) + configs_raw_prev = list(configs_raw_curr) else: # Otherwise, retrieve the previously-stored data E_prev = params[itraj]["E_prev"] @@ -496,7 +553,7 @@ def mopac_compute_adi(q, params, full_id): E_CI_prev = params[itraj]["E_CI_prev"] CI_prev = params[itraj]["CI_prev"] configs_prev = params[itraj]["configs_prev"] - + configs_raw_prev = params[itraj]["configs_raw_prev"] nstates = CI_curr.num_of_cols #print("nstates = ", nstates) @@ -550,6 +607,7 @@ def mopac_compute_adi(q, params, full_id): params[itraj]["E_CI_prev"] = E_CI_curr params[itraj]["CI_prev"] = CI_curr params[itraj]["configs_prev"] = configs_curr + params[itraj]["configs_raw_prev"] = configs_raw_curr params[itraj]["is_first_time"] = False From 719f99a8997edbd52dd362515a63e78254ccc38e Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Wed, 17 Jul 2024 19:27:56 -0400 Subject: [PATCH 30/48] Re-use the rep_sh param and set the diabatic active state directly --- src/dyn/Dynamics.cpp | 39 +++++--- src/dyn/Energy_and_Forces.cpp | 16 ++- src/dyn/dyn_hop_proposal.cpp | 17 +++- src/dyn/dyn_variables.cpp | 3 + src/dyn/dyn_variables.h | 9 ++ src/dyn/dyn_variables_electronic.cpp | 143 ++++++++++++++++++--------- src/dyn/libdyn.cpp | 1 + 7 files changed, 159 insertions(+), 69 deletions(-) diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index b964e0791..0c69fc604 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -1220,6 +1220,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, vector act_states(dyn_var.act_states); // = dyn_var.act_states; + vector act_states_dia(dyn_var.act_states_dia); MATRIX p(*dyn_var.p); MATRIX& invM = *dyn_var.iM; @@ -1495,6 +1496,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, vector old_states(dyn_var.act_states); + vector old_states_dia(dyn_var.act_states_dia); //========================== Hop proposal and acceptance ================================ // FSSH (0), GFSH (1), MSSH (2), LZ(3), ZN (4), MASH(6), FSSH2(7), FSSH3(8) @@ -1507,15 +1509,21 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, g = hop_proposal_probabilities(prms, dyn_var, ham, ham_aux); // Propose new discrete states for all trajectories - vector prop_states( propose_hops(g, dyn_var.act_states, rnd) ); + vector prop_states(ntraj, 0); + if(prms.rep_sh==1){ + prop_states = propose_hops(g, dyn_var.act_states, rnd); - // Decide if to accept the transitions (and then which) - // Here, it is okay to use the local copies of the q, p, etc. variables, since we don't change the actual variables - // 10/21/2023 - deprecate this version - //act_states = accept_hops(prms, *dyn_var.q, *dyn_var.p, invM, *dyn_var.ampl_adi, ham, prop_states, dyn_var.act_states, rnd); - // in favor of this: - act_states = accept_hops(dyn_var, ham, prop_states, dyn_var.act_states, prms, rnd); - + // Decide if to accept the transitions (and then which) + // Here, it is okay to use the local copies of the q, p, etc. variables, since we don't change the actual variables + // 10/21/2023 - deprecate this version + //act_states = accept_hops(prms, *dyn_var.q, *dyn_var.p, invM, *dyn_var.ampl_adi, ham, prop_states, dyn_var.act_states, rnd); + // in favor of this: + act_states = accept_hops(dyn_var, ham, prop_states, dyn_var.act_states, prms, rnd); + } + else{ + prop_states = propose_hops(g, dyn_var.act_states, rnd); + act_states_dia = accept_hops(dyn_var, ham, prop_states, dyn_var.act_states_dia, prms, rnd); + } //=== Post-hop decoherence options === @@ -1536,7 +1544,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, else if(prms.decoherence_algo==5 or prms.decoherence_algo==6){ xf_hop_reset(dyn_var, act_states, old_states); } // SHXF or MQCXF else if(prms.decoherence_algo==7){ ;; } // DISH rev 2023, nothing to do here - // Experimental: instantaneous decoherence in diabatic basis + // Experimental: instantaneous decoherence in diabatic basis | TODO: check the compatibility with rep_sh==0 else if(prms.decoherence_algo==8){ if(prms.rep_tdse==1){ instantaneous_decoherence_dia(*dyn_var.ampl_adi, ham, act_states, prop_states, old_states, @@ -1554,11 +1562,18 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, }// DISH - //====================== Momenta adjustment after successful/frustrated hops =================== // Velocity rescaling: however here we may be changing velocities - handle_hops_nuclear(dyn_var, ham, act_states, old_states, prms); - dyn_var.act_states = act_states; + if(prms.rep_sh==1){ + handle_hops_nuclear(dyn_var, ham, act_states, old_states, prms); + dyn_var.act_states = act_states; + } + else{ + handle_hops_nuclear(dyn_var, ham, act_states_dia, old_states_dia, prms); + dyn_var.act_states_dia = act_states_dia; + } + + // TODO: Update the active states between representations // Re-scale (back) couplings and time-overlaps, if the TC-NBRA was used if(prms.thermally_corrected_nbra==1 && prms.tcnbra_do_nac_scaling==1){ remove_thermal_correction(dyn_var, ham, prms); } diff --git a/src/dyn/Energy_and_Forces.cpp b/src/dyn/Energy_and_Forces.cpp index 936eecb45..58be7e1df 100755 --- a/src/dyn/Energy_and_Forces.cpp +++ b/src/dyn/Energy_and_Forces.cpp @@ -188,7 +188,9 @@ vector potential_energies(dyn_control_params& prms, dyn_variables& dyn_v // TSH or adiabatic (including excited states) // state-specific forces - vector effective_states(dyn_vars.act_states); + vector effective_states(ntraj, 0); + if(prms.rep_sh==1){ effective_states = dyn_vars.act_states; } + else{ effective_states = dyn_vars.act_states_dia; } if(prms.enforce_state_following==1){ for(itraj=0; itraj potential_energies(dyn_control_params& prms, dyn_variables& dyn_v else if(prms.force_method==3){ // QTSH - vector effective_states(dyn_vars.act_states); + vector effective_states(ntraj, 0); + if(prms.rep_sh==1){ effective_states = dyn_vars.act_states; } + else{ effective_states = dyn_vars.act_states_dia; } if(prms.enforce_state_following==1){ for(itraj=0; itraj effective_states(dyn_vars.act_states); + vector effective_states(ntraj, 0); + if(prms.rep_sh==1){ effective_states = dyn_vars.act_states; } + else{ effective_states = dyn_vars.act_states_dia; } if(prms.enforce_state_following==1){ // NBRA-like enforcement: adiabatic dynamics, in terms of forces for(int itraj=0; itraj effective_states(dyn_vars.act_states); + vector effective_states(ntraj, 0); + if(prms.rep_sh==1){ effective_states = dyn_vars.act_states; } + else{ effective_states = dyn_vars.act_states_dia; } if(prms.enforce_state_following==1){ // NBRA-like enforcement: adiabatic dynamics, in terms of forces for(int itraj=0; itrajget_hvib_adi(); + if(prms.rep_tdse==0 || prms.rep_tdse==2){ Hvib = ham.children[traj]->get_hvib_dia(); } } if(prms.tsh_method == 0){ // FSSH - g[traj] = hopping_probabilities_fssh(prms, dm, Hvib, dyn_var.act_states[traj]); - + if(prms.rep_sh==1){ + g[traj] = hopping_probabilities_fssh(prms, dm, Hvib, dyn_var.act_states[traj]); + } + else{ + g[traj] = hopping_probabilities_fssh(prms, dm, Hvib, dyn_var.act_states_dia[traj]); + } } else if(prms.tsh_method == 1){ // GFSH - g[traj] = hopping_probabilities_gfsh(prms, dm, Hvib, dyn_var.act_states[traj]); - + if(prms.rep_sh==1){ + g[traj] = hopping_probabilities_gfsh(prms, dm, Hvib, dyn_var.act_states[traj]); + } + else{ + g[traj] = hopping_probabilities_gfsh(prms, dm, Hvib, dyn_var.act_states_dia[traj]); + } } else if(prms.tsh_method == 2){ // MSSH diff --git a/src/dyn/dyn_variables.cpp b/src/dyn/dyn_variables.cpp index 5c83bd2ba..525d7532e 100755 --- a/src/dyn/dyn_variables.cpp +++ b/src/dyn/dyn_variables.cpp @@ -45,6 +45,7 @@ void dyn_variables::allocate_electronic_vars(){ } act_states = vector(ntraj, 0); + act_states_dia = vector(ntraj, 0); electronic_vars_status = 1; } @@ -318,6 +319,7 @@ dyn_variables::dyn_variables(const dyn_variables& x){ *basis_transform[itraj] = *x.basis_transform[itraj]; } act_states = x.act_states; + act_states_dia = x.act_states_dia; } @@ -470,6 +472,7 @@ dyn_variables::~dyn_variables(){ delete ampl_adi; act_states.clear(); + act_states_dia.clear(); electronic_vars_status = 0; } diff --git a/src/dyn/dyn_variables.h b/src/dyn/dyn_variables.h index 351cdbc36..8a3ad028d 100755 --- a/src/dyn/dyn_variables.h +++ b/src/dyn/dyn_variables.h @@ -159,6 +159,15 @@ class dyn_variables{ vector act_states(ntraj) */ vector act_states; + + + /** + Diabatic active states for each trajectory + + Options: + vector act_states_dia(ntraj) + */ + vector act_states_dia; /** diff --git a/src/dyn/dyn_variables_electronic.cpp b/src/dyn/dyn_variables_electronic.cpp index b5e18d983..2a16b4af6 100644 --- a/src/dyn/dyn_variables_electronic.cpp +++ b/src/dyn/dyn_variables_electronic.cpp @@ -753,6 +753,7 @@ void dyn_variables::init_active_states(bp::dict _params, Random& rnd){ bp::list lst; lst.append(1.0); default_params["istates"] = lst; default_params["rep"] = 1; + default_params["rep_sh"] = 1; default_params["verbosity"] = 0; check_input(params, default_params, critical_params); @@ -762,7 +763,7 @@ void dyn_variables::init_active_states(bp::dict _params, Random& rnd){ int init_type; int istate; vector istates; - int rep; + int rep, rep_sh; int verbosity; @@ -775,6 +776,7 @@ void dyn_variables::init_active_states(bp::dict _params, Random& rnd){ else if(key=="istate") { istate = bp::extract(params.values()[i]); } else if(key=="istates") { istates = liblibra::libconverters::Py2Cpp( bp::extract< bp::list >(params.values()[i]) ); } else if(key=="rep") { rep = bp::extract(params.values()[i]); } + else if(key=="rep_sh") { rep = bp::extract(params.values()[i]); } else if(key=="verbosity") { verbosity = bp::extract(params.values()[i]); } } @@ -825,46 +827,84 @@ void dyn_variables::init_active_states(bp::dict _params, Random& rnd){ ///================= Actual calculations ================================ act_states.clear(); + act_states_dia.clear(); //# Dynamical variables - //================ For adiabatic - set up them directly ================= - for(traj=0; traj dia_act_states(act_states); + //============= For adiabatic: more complicated ========================= + if(rep==0){ + act_states_dia = act_states; - CMATRIX pop_adi(nadi, nadi); - CMATRIX pop_dia(ndia, ndia); + CMATRIX pop_adi(nadi, nadi); + CMATRIX pop_dia(ndia, ndia); - for(traj=0; traj(1.0, 0.0) ); + for(traj=0; traj(1.0, 0.0) ); - // The following transformation is correct only for S_dia = 1 - pop_adi = (*basis_transform[traj]).H() * pop_dia * (*basis_transform[traj]); + // The following transformation is correct only for S_dia = 1 + pop_adi = (*basis_transform[traj]).H() * pop_dia * (*basis_transform[traj]); - vector res(nadi, 0.0); - for(i=0; i res(nadi, 0.0); + for(i=0; i(1.0, 0.0) ); + + pop_dia = (*basis_transform[traj]) * pop_adi * (*basis_transform[traj]).H(); + + vector res(nadi, 0.0); + for(i=0; i dyn_variables::compute_average_sh_pop(int rep){ } vector res(sz, 0.0); - vector effective_states( act_states ); if(rep==0){ - // ===== For diabatic SH populations: use the prescription of ====== - // Tempelaar, R.; Reichman, D. R. Generalization of Fewest-Switches Surface Hopping for Coherences. - // The Journal of Chemical Physics 2018, 148 (10), 102309. https://doi.org/10.1063/1.5000843 - - CMATRIX pop_adi(nadi, nadi); - CMATRIX pop_dia(ndia, ndia); - CMATRIX U(ndia, nadi); - - for(traj=0; traj(0.0, 0.0) ); } - pop_adi.set(i, i, complex(1.0, 0.0) ); - - // The following transformation is correct only for S_dia = 1 - U = (*basis_transform[traj]);// * (*proj_adi[traj]); - - pop_dia = U * pop_adi * U.H(); - for(j=0; j effective_states( act_states_dia ); + for(traj=0; traj(0.0, 0.0) ); } +// pop_adi.set(i, i, complex(1.0, 0.0) ); +// +// // The following transformation is correct only for S_dia = 1 +// U = (*basis_transform[traj]);// * (*proj_adi[traj]); +// +// pop_dia = U * pop_adi * U.H(); +// for(j=0; j effective_states( act_states ); for(traj=0; traj Date: Thu, 18 Jul 2024 00:34:14 -0400 Subject: [PATCH 31/48] Adjust the dm and hvib in the hop proposal according to the rep_sh and make the active states in both reps updated in real time --- src/dyn/Dynamics.cpp | 5 +- src/dyn/dyn_hop_proposal.cpp | 20 +++- src/dyn/dyn_variables.h | 1 + src/dyn/dyn_variables_electronic.cpp | 160 +++++++++++++++++++-------- src/dyn/libdyn.cpp | 4 + 5 files changed, 138 insertions(+), 52 deletions(-) diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index 0c69fc604..3f5d2f173 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -1342,7 +1342,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, // In the interval [t, t + dt], we may have experienced the basis reordering, so we need to // change the active adiabatic state - if(prms.tsh_method == 3 or prms.tsh_method == 4 ){ + if(prms.tsh_method == 3 or prms.tsh_method == 4 or prms.rep_sh == 0 ){ // Don't update states based on amplitudes, in the LZ method ;; } @@ -1573,7 +1573,8 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, dyn_var.act_states_dia = act_states_dia; } - // TODO: Update the active states between representations + // Set the active states in the other representation through the tranformation matrix + dyn_var.set_active_states_diff_rep(prms.rep_sh); // Re-scale (back) couplings and time-overlaps, if the TC-NBRA was used if(prms.thermally_corrected_nbra==1 && prms.tcnbra_do_nac_scaling==1){ remove_thermal_correction(dyn_var, ham, prms); } diff --git a/src/dyn/dyn_hop_proposal.cpp b/src/dyn/dyn_hop_proposal.cpp index 546b12e5b..e7fc1c685 100755 --- a/src/dyn/dyn_hop_proposal.cpp +++ b/src/dyn/dyn_hop_proposal.cpp @@ -1137,6 +1137,7 @@ nHamiltonian& ham, nHamiltonian& ham_prev){ vector< vector > g(ntraj, vector(nst,0.0) ); /// the hopping probability for all trajectories MATRIX p_traj(ndof, 1); CMATRIX coeff(nst, 1); + CMATRIX dm(nst, nst); CMATRIX Hvib(nst, nst); vector fstates(ntraj,0); @@ -1144,8 +1145,14 @@ nHamiltonian& ham, nHamiltonian& ham_prev){ // Proposed hops probabilities for(traj=0; trajget_hvib_adi(); - if(prms.rep_tdse==0 || prms.rep_tdse==2){ Hvib = ham.children[traj]->get_hvib_dia(); } + if(prms.rep_sh==1){ + Hvib = ham.children[traj]->get_hvib_adi(); + if(prms.rep_tdse==0 || prms.rep_tdse==2){ Hvib = ham.children[traj]->get_hvib_dia(); } + } + else{ + Hvib = ham.children[traj]->get_hvib_dia(); + } } if(prms.tsh_method == 0){ // FSSH diff --git a/src/dyn/dyn_variables.h b/src/dyn/dyn_variables.h index 8a3ad028d..e4bd05c5c 100755 --- a/src/dyn/dyn_variables.h +++ b/src/dyn/dyn_variables.h @@ -603,6 +603,7 @@ class dyn_variables{ void update_active_states(int direction, int property); void update_active_states(); + void set_active_states_diff_rep(int rep_sh); void update_basis_transform(nHamiltonian& ham); diff --git a/src/dyn/dyn_variables_electronic.cpp b/src/dyn/dyn_variables_electronic.cpp index 2a16b4af6..852fb77f1 100644 --- a/src/dyn/dyn_variables_electronic.cpp +++ b/src/dyn/dyn_variables_electronic.cpp @@ -753,7 +753,6 @@ void dyn_variables::init_active_states(bp::dict _params, Random& rnd){ bp::list lst; lst.append(1.0); default_params["istates"] = lst; default_params["rep"] = 1; - default_params["rep_sh"] = 1; default_params["verbosity"] = 0; check_input(params, default_params, critical_params); @@ -763,7 +762,7 @@ void dyn_variables::init_active_states(bp::dict _params, Random& rnd){ int init_type; int istate; vector istates; - int rep, rep_sh; + int rep; int verbosity; @@ -776,7 +775,6 @@ void dyn_variables::init_active_states(bp::dict _params, Random& rnd){ else if(key=="istate") { istate = bp::extract(params.values()[i]); } else if(key=="istates") { istates = liblibra::libconverters::Py2Cpp( bp::extract< bp::list >(params.values()[i]) ); } else if(key=="rep") { rep = bp::extract(params.values()[i]); } - else if(key=="rep_sh") { rep = bp::extract(params.values()[i]); } else if(key=="verbosity") { verbosity = bp::extract(params.values()[i]); } } @@ -829,8 +827,7 @@ void dyn_variables::init_active_states(bp::dict _params, Random& rnd){ act_states.clear(); act_states_dia.clear(); - //# Dynamical variables - if(rep_sh==1){ + if(rep==1){ //================ For adiabatic - set up them directly ================= for(traj=0; traj(1.0, 0.0) ); - for(traj=0; traj(1.0, 0.0) ); + // The following transformation is correct only for S_dia = 1 + pop_dia = (*basis_transform[traj]) * pop_adi * (*basis_transform[traj]).H(); - // The following transformation is correct only for S_dia = 1 - pop_adi = (*basis_transform[traj]).H() * pop_dia * (*basis_transform[traj]); + vector res(ndia, 0.0); + for(i=0; i res(nadi, 0.0); - for(i=0; i(1.0, 0.0) ); - for(traj=0; traj(1.0, 0.0) ); + // The following transformation is correct only for S_dia = 1 + pop_adi = (*basis_transform[traj]).H() * pop_dia * (*basis_transform[traj]); - pop_dia = (*basis_transform[traj]) * pop_adi * (*basis_transform[traj]).H(); + vector res(nadi, 0.0); + for(i=0; i res(nadi, 0.0); - for(i=0; i(0.0, 0.0) ); } + pop_adi.set(i, i, complex(1.0, 0.0) ); + + // The following transformation is correct only for S_dia = 1 + U = (*basis_transform[traj]);// * (*proj_adi[traj]); + + pop_dia = U * pop_adi * U.H(); + + // Set the state having the largest diagonal element to the active state + int max_diag_indx = 0; + double max_diag = pop_dia.get(0,0).real(); + for(int i=1; i max_diag){ max_diag_indx = i; max_diag = el_diag; } + } + + act_states_dia[traj] = max_diag_indx; + } + } + + // Obtain the adiabatic active states from the diabatic ones + else{ + for(traj=0; traj(0.0, 0.0) ); } + pop_dia.set(i, i, complex(1.0, 0.0) ); + + // The following transformation is correct only for S_dia = 1 + U = (*basis_transform[traj]);// * (*proj_adi[traj]); + + pop_adi = U.H() * pop_dia * U; + + // Set the state having the largest diagonal element to the active state + int max_diag_indx = 0; + double max_diag = pop_adi.get(0,0).real(); + for(int i=1; i max_diag){ max_diag_indx = i; max_diag = el_diag; } + } + + act_states[traj] = max_diag_indx; + } + } +} /* diff --git a/src/dyn/libdyn.cpp b/src/dyn/libdyn.cpp index 33fb862fc..895bb0720 100755 --- a/src/dyn/libdyn.cpp +++ b/src/dyn/libdyn.cpp @@ -215,6 +215,8 @@ void export_dyn_variables_objects(){ void (dyn_variables::*expt_update_active_states_v1)(int direction, int property) = &dyn_variables::update_active_states; void (dyn_variables::*expt_update_active_states_v2)() = &dyn_variables::update_active_states; + + void (dyn_variables::*expt_set_active_states_diff_rep_v1)(int rep_sh) = &dyn_variables::set_active_states_diff_rep; class_("dyn_variables",init()) @@ -303,6 +305,8 @@ void export_dyn_variables_objects(){ .def("update_active_states", expt_update_active_states_v1) .def("update_active_states", expt_update_active_states_v2) + + .def("set_active_states_diff_rep", expt_set_active_states_diff_rep_v1) .def("update_basis_transform", &dyn_variables::update_basis_transform) From 95916be960af1bcab964bd760814c3bab6867524 Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Thu, 18 Jul 2024 00:55:02 -0400 Subject: [PATCH 32/48] Save the diabatic active state --- src/libra_py/dynamics/tsh/compute.py | 3 +++ src/libra_py/dynamics/tsh/save.py | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/libra_py/dynamics/tsh/compute.py b/src/libra_py/dynamics/tsh/compute.py index 11077fc4e..6b1f79241 100755 --- a/src/libra_py/dynamics/tsh/compute.py +++ b/src/libra_py/dynamics/tsh/compute.py @@ -915,6 +915,9 @@ def generic_recipe(_dyn_params, compute_model, _model_params,_init_elec, _init_n print("Active states (adiabatic)") print(Cpp2Py(dyn_var.act_states)) + + print("Active states (diabatic)") + print(Cpp2Py(dyn_var.act_states_dia)) print("Initial adiabatic populations") pops_sh1 = dyn_var.compute_average_sh_pop(1) diff --git a/src/libra_py/dynamics/tsh/save.py b/src/libra_py/dynamics/tsh/save.py index b81703d63..04c4e2311 100755 --- a/src/libra_py/dynamics/tsh/save.py +++ b/src/libra_py/dynamics/tsh/save.py @@ -118,6 +118,10 @@ def init_tsh_data(saver, output_level, _nsteps, _ntraj, _ndof, _nadi, _ndia): # Trajectory-resolved instantaneous adiabatic states if "states" in saver.keywords: # and "states" in saver.np_data.keys(): saver.add_dataset("states", (_nsteps, _ntraj), "I") + + # Trajectory-resolved instantaneous adiabatic states + if "states_dia" in saver.keywords: # and "states_dia" in saver.np_data.keys(): + saver.add_dataset("states_dia", (_nsteps, _ntraj), "I") # Average adiabatic SE populations if "se_pop_adi" in saver.keywords: # and "states" in saver.np_data.keys(): @@ -488,6 +492,13 @@ def save_hdf5_2D_new(saver, i, dyn_var, ham, txt_type=0): ntraj = dyn_var.ntraj for itraj in range(ntraj): saver.save_multi_scalar(t, itraj, "states", dyn_var.act_states[itraj]) + + if "states_dia" in saver.keywords and "states_dia" in saver.np_data.keys(): + # Trajectory-resolved instantaneous diabatic states + # Format: saver.add_dataset("states", (_nsteps, _ntraj), "I") + ntraj = dyn_var.ntraj + for itraj in range(ntraj): + saver.save_multi_scalar(t, itraj, "states_dia", dyn_var.act_states_dia[itraj]) if "se_pop_dia" in saver.keywords and "se_pop_dia" in saver.np_data.keys(): # Average diabatic SE populations From a9e2daa5e7e6b2f527db9fb30a0da70b4927320c Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Thu, 18 Jul 2024 01:00:40 -0400 Subject: [PATCH 33/48] Exception handling on the XF methods (This would need to be done for the other methods) --- src/dyn/Dynamics.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index 3f5d2f173..25cb22be2 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -1541,7 +1541,12 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, }// AFSSH else if(prms.decoherence_algo==3){ ;; } // BCSH of Linjun Wang, nothing to do here else if(prms.decoherence_algo==4){ ;; } // MF-SD of Bedard-Hearn, Larsen, Schwartz, nothing to do here - else if(prms.decoherence_algo==5 or prms.decoherence_algo==6){ xf_hop_reset(dyn_var, act_states, old_states); } // SHXF or MQCXF + else if(prms.decoherence_algo==5 or prms.decoherence_algo==6){ + if(prms.rep_sh==1){ + xf_hop_reset(dyn_var, act_states, old_states); + } + else{ cout<<"ERROR: Independent-trajectory XF methods require rep_sh = 1\nExiting now...\n"; exit(0); } + } // SHXF or MQCXF else if(prms.decoherence_algo==7){ ;; } // DISH rev 2023, nothing to do here // Experimental: instantaneous decoherence in diabatic basis | TODO: check the compatibility with rep_sh==0 From 9d13cab17b5c8405a76750b3f7a9ac156f4f377e Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Thu, 18 Jul 2024 17:56:40 -0400 Subject: [PATCH 34/48] Make the hopping process consistent, that is, in rep_sp=1, use adiabatic quantities for hops and vice versa --- src/dyn/dyn_hop_proposal.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dyn/dyn_hop_proposal.cpp b/src/dyn/dyn_hop_proposal.cpp index e7fc1c685..20c7e0a56 100755 --- a/src/dyn/dyn_hop_proposal.cpp +++ b/src/dyn/dyn_hop_proposal.cpp @@ -1169,7 +1169,8 @@ nHamiltonian& ham, nHamiltonian& ham_prev){ // Compute the Hvib for all traj if(prms.rep_sh==1){ Hvib = ham.children[traj]->get_hvib_adi(); - if(prms.rep_tdse==0 || prms.rep_tdse==2){ Hvib = ham.children[traj]->get_hvib_dia(); } + //if(prms.rep_tdse==0 || prms.rep_tdse==2){ Hvib = ham.children[traj]->get_hvib_dia(); } + if(prms.rep_tdse==2){ Hvib = ham.children[traj]->get_hvib_dia(); } } else{ Hvib = ham.children[traj]->get_hvib_dia(); From b16825730a09bd62a599de31817781cb91e9689f Mon Sep 17 00:00:00 2001 From: Alexey Akimov Date: Fri, 19 Jul 2024 15:41:56 -0400 Subject: [PATCH 35/48] Added the do_Lowdin option to the MOPAC workflow - it is quite inefficient yet since has many redundant steps --- src/libra_py/packages/mopac/methods.py | 31 +++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/libra_py/packages/mopac/methods.py b/src/libra_py/packages/mopac/methods.py index 81cc977fd..d1f4d40fb 100755 --- a/src/libra_py/packages/mopac/methods.py +++ b/src/libra_py/packages/mopac/methods.py @@ -37,6 +37,7 @@ import libra_py.packages.cp2k.methods as CP2K_methods import libra_py.workflows.nbra.mapping2 as mapping2 +import libra_py.workflows.nbra.step3 as step3 def make_mopac_input(mopac_input_filename, mopac_run_params, labels, coords): """ @@ -474,6 +475,7 @@ def mopac_compute_adi(q, params, full_id): * **params[i]["mopac_input_prefix"]** ( string ) [ default: "input_" ] * **params[i]["mopac_output_prefix"]** ( string ) [ default: "output_" ] * **params[i]["dt"]** ( float ) - the time interval between the snapshots [ units: a.u.; default: 41 a.u. = 1 fs ] + * **params[i]["do_Lowdin"]** ( bool or int): 0 - don't do - use the raw inputs [ defualt ]; 1 - do it - correct for rounding errors Returns: PyObject: obj, with the members: @@ -506,7 +508,7 @@ def mopac_compute_adi(q, params, full_id): "mopac_run_params":"INDO C.I.=(6,3) CHARGE=0 RELSCF=0.000001 ALLVEC WRTCONF=0.00 WRTCI=2", "mopac_working_directory":"mopac_wd", "mopac_input_prefix":"input_", "mopac_output_prefix":"output_", - "dt":1.0*units.fs2au + "dt":1.0*units.fs2au, "do_Lowdin":0 } comn.check_input(params[itraj], default_params, critical_params) @@ -521,6 +523,7 @@ def mopac_compute_adi(q, params, full_id): mopac_input_prefix = params[itraj]["mopac_input_prefix"] mopac_output_prefix = params[itraj]["mopac_output_prefix"] dt = params[itraj]["dt"] + do_Lowdin = params[itraj]["do_Lowdin"] natoms = len(labels) ndof = 3 * natoms @@ -572,14 +575,40 @@ def mopac_compute_adi(q, params, full_id): # obj.d1ham_adi.append( CMATRIX(nstates, nstates) ) # obj.dc1_adi.append( CMATRIX(nstates, nstates) ) + # MO overlaps - needed for Lowdin orthonormalization + S_prev, S_curr, U_prev, U_curr = None, None, None, None + if do_Lowdin: + S_prev = MO_prev.T() * MO_prev; im = MATRIX(S_prev); im *= 0.0; S_prev = CMATRIX(S_prev, im); + S_curr = MO_curr.T() * MO_curr; im = MATRIX(S_curr); im *= 0.0; S_curr = CMATRIX(S_curr, im); + U_prev = step3.get_Lowdin_general(S_prev).real() + U_curr = step3.get_Lowdin_general(S_curr).real() + # Time-overlap in the MO basis mo_st = MO_prev.T() * MO_curr + if do_Lowdin: + mo_st = U_prev.T() * mo_st * U_curr # Lowdin correction on MOs + + # Overlaps in the SD basis - for the Lowdin: + if do_Lowdin: + ident_curr = U_curr.T() * S_curr.real() * U_curr; + ovlp_sd_curr = mapping2.ovlp_mat_arb(configs_curr, configs_curr, ident_curr, False).real() + + ident_prev = U_prev.T() * S_prev.real() * U_prev; + ovlp_sd_prev = mapping2.ovlp_mat_arb(configs_prev, configs_prev, ident_prev, False).real() + + ovlp_ci_curr = CI_curr.T() * ovlp_sd_curr * CI_curr; im = MATRIX(ovlp_ci_curr); im *= 0.0; ovlp_ci_curr = CMATRIX(ovlp_ci_curr, im); + ovlp_ci_prev = CI_prev.T() * ovlp_sd_prev * CI_prev; im = MATRIX(ovlp_ci_prev); im *= 0.0; ovlp_ci_prev = CMATRIX(ovlp_ci_prev, im); + + U_prev = step3.get_Lowdin_general(ovlp_ci_prev).real() + U_curr = step3.get_Lowdin_general(ovlp_ci_curr).real() # Time-overlap in the SD basis time_ovlp_sd = mapping2.ovlp_mat_arb(configs_prev, configs_curr, mo_st, False).real() # Time-overlap in the CI basis time_ovlp_ci = CI_prev.T() * time_ovlp_sd * CI_curr + if do_Lowdin: + time_ovlp_ci = U_prev.T() * time_ovlp_ci * U_curr # Lowdin correction on CIs # Now, populate the allocated matrices for istate in range(nstates): From 6d78c972f41bc68de87ca99765673707293d7233 Mon Sep 17 00:00:00 2001 From: Alexey Akimov Date: Fri, 19 Jul 2024 16:01:47 -0400 Subject: [PATCH 36/48] Added SAC coefficient in the time-overlaps, added return of the CI overlaps --- src/libra_py/packages/mopac/methods.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/libra_py/packages/mopac/methods.py b/src/libra_py/packages/mopac/methods.py index d1f4d40fb..7bf0418b7 100755 --- a/src/libra_py/packages/mopac/methods.py +++ b/src/libra_py/packages/mopac/methods.py @@ -497,6 +497,7 @@ def mopac_compute_adi(q, params, full_id): """ #params = dict(params_) + sqt2 = math.sqrt(2.0) Id = Cpp2Py(full_id) itraj = Id[-1] @@ -568,6 +569,7 @@ def mopac_compute_adi(q, params, full_id): obj.hvib_adi = CMATRIX(nstates, nstates) obj.basis_transform = CMATRIX(nstates, nstates) obj.time_overlap_adi = CMATRIX(nstates, nstates) + obj.overlap_adi = CMATRIX(nstates, nstates) # Don't do the derivatives yet #obj.d1ham_adi = CMATRIXList(); #obj.dc1_adi = CMATRIXList(); @@ -589,12 +591,15 @@ def mopac_compute_adi(q, params, full_id): mo_st = U_prev.T() * mo_st * U_curr # Lowdin correction on MOs # Overlaps in the SD basis - for the Lowdin: + ci_ovlp_curr = None if do_Lowdin: ident_curr = U_curr.T() * S_curr.real() * U_curr; ovlp_sd_curr = mapping2.ovlp_mat_arb(configs_curr, configs_curr, ident_curr, False).real() + ovlp_sd_curr.scale(-1, 0, sqt2); ovlp_sd_curr.scale(0, -1, sqt2); ovlp_sd_curr.scale(0, 0, 0.5) ident_prev = U_prev.T() * S_prev.real() * U_prev; ovlp_sd_prev = mapping2.ovlp_mat_arb(configs_prev, configs_prev, ident_prev, False).real() + ovlp_sd_prev.scale(-1, 0, sqt2); ovlp_sd_prev.scale(0, -1, sqt2); ovlp_sd_prev.scale(0, 0, 0.5) ovlp_ci_curr = CI_curr.T() * ovlp_sd_curr * CI_curr; im = MATRIX(ovlp_ci_curr); im *= 0.0; ovlp_ci_curr = CMATRIX(ovlp_ci_curr, im); ovlp_ci_prev = CI_prev.T() * ovlp_sd_prev * CI_prev; im = MATRIX(ovlp_ci_prev); im *= 0.0; ovlp_ci_prev = CMATRIX(ovlp_ci_prev, im); @@ -602,8 +607,12 @@ def mopac_compute_adi(q, params, full_id): U_prev = step3.get_Lowdin_general(ovlp_ci_prev).real() U_curr = step3.get_Lowdin_general(ovlp_ci_curr).real() + ci_ovlp_curr = U_curr.T() * ovlp_ci_curr.real() * U_curr + # Time-overlap in the SD basis time_ovlp_sd = mapping2.ovlp_mat_arb(configs_prev, configs_curr, mo_st, False).real() + # Scaling to account for SAC prefactors: + time_ovlp_sd.scale(-1, 0, sqt2); time_ovlp_sd.scale(0, -1, sqt2); time_ovlp_sd.scale(0, 0, 0.5) # Time-overlap in the CI basis time_ovlp_ci = CI_prev.T() * time_ovlp_sd * CI_curr @@ -619,9 +628,13 @@ def mopac_compute_adi(q, params, full_id): for jstate in range(nstates): obj.time_overlap_adi.set(istate, jstate, time_ovlp_ci.get(istate, jstate) * (1.0+0.0j) ) + if ci_ovlp_curr != None: + obj.overlap_adi.set( istate, jstate, ci_ovlp_curr.get(istate, jstate) * (1.0+0.0j) ) # Don't do this yet #for idof in range(ndof): # obj.d1ham_adi[idof].set(istate, istate, grad.get(idof, 0) * (1.0+0.0j) ) + + # Update the Hvib: for istate in range(nstates): for jstate in range(istate+1, nstates): From e811c72c3a41bbd2ca3be67b4f4986160128d790 Mon Sep 17 00:00:00 2001 From: Alexey Akimov Date: Fri, 19 Jul 2024 16:12:33 -0400 Subject: [PATCH 37/48] Fixed the computations of the CI overlap when no Lowdin transformation is requested --- src/libra_py/packages/mopac/methods.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libra_py/packages/mopac/methods.py b/src/libra_py/packages/mopac/methods.py index 7bf0418b7..87fdac353 100755 --- a/src/libra_py/packages/mopac/methods.py +++ b/src/libra_py/packages/mopac/methods.py @@ -608,6 +608,11 @@ def mopac_compute_adi(q, params, full_id): U_curr = step3.get_Lowdin_general(ovlp_ci_curr).real() ci_ovlp_curr = U_curr.T() * ovlp_ci_curr.real() * U_curr + else: + S_curr = MO_curr.T() * MO_curr; + ovlp_sd_curr = mapping2.ovlp_mat_arb(configs_curr, configs_curr, S_curr, False).real() + ovlp_sd_curr.scale(-1, 0, sqt2); ovlp_sd_curr.scale(0, -1, sqt2); ovlp_sd_curr.scale(0, 0, 0.5) + ci_ovlp_curr = CI_curr.T() * ovlp_sd_curr * CI_curr # Time-overlap in the SD basis time_ovlp_sd = mapping2.ovlp_mat_arb(configs_prev, configs_curr, mo_st, False).real() From c3abf1a026a0b25c1f620a28ba36e286c918cc6f Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Mon, 29 Jul 2024 22:58:28 -0400 Subject: [PATCH 38/48] Use act_states_dia for the diabatic hop --- src/dyn/Dynamics.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index 25cb22be2..1d4bceef0 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -1520,8 +1520,8 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, // in favor of this: act_states = accept_hops(dyn_var, ham, prop_states, dyn_var.act_states, prms, rnd); } - else{ - prop_states = propose_hops(g, dyn_var.act_states, rnd); + else if(prms.rep_sh==0){ + prop_states = propose_hops(g, dyn_var.act_states_dia, rnd); act_states_dia = accept_hops(dyn_var, ham, prop_states, dyn_var.act_states_dia, prms, rnd); } From 3243efc09991126ce04fb6ba2e66f4a6e96462c5 Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Mon, 29 Jul 2024 23:01:11 -0400 Subject: [PATCH 39/48] Use else if for clarity --- src/dyn/Energy_and_Forces.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dyn/Energy_and_Forces.cpp b/src/dyn/Energy_and_Forces.cpp index 58be7e1df..4c653bcff 100755 --- a/src/dyn/Energy_and_Forces.cpp +++ b/src/dyn/Energy_and_Forces.cpp @@ -190,7 +190,7 @@ vector potential_energies(dyn_control_params& prms, dyn_variables& dyn_v vector effective_states(ntraj, 0); if(prms.rep_sh==1){ effective_states = dyn_vars.act_states; } - else{ effective_states = dyn_vars.act_states_dia; } + else if(prms.rep_sh==0){ effective_states = dyn_vars.act_states_dia; } if(prms.enforce_state_following==1){ for(itraj=0; itraj potential_energies(dyn_control_params& prms, dyn_variables& dyn_v vector effective_states(ntraj, 0); if(prms.rep_sh==1){ effective_states = dyn_vars.act_states; } - else{ effective_states = dyn_vars.act_states_dia; } + else if(prms.rep_sh==0){ effective_states = dyn_vars.act_states_dia; } if(prms.enforce_state_following==1){ for(itraj=0; itraj effective_states(ntraj, 0); if(prms.rep_sh==1){ effective_states = dyn_vars.act_states; } - else{ effective_states = dyn_vars.act_states_dia; } + else if(prms.rep_sh==0){ effective_states = dyn_vars.act_states_dia; } if(prms.enforce_state_following==1){ // NBRA-like enforcement: adiabatic dynamics, in terms of forces for(int itraj=0; itraj effective_states(ntraj, 0); if(prms.rep_sh==1){ effective_states = dyn_vars.act_states; } - else{ effective_states = dyn_vars.act_states_dia; } + else if(prms.rep_sh==0){ effective_states = dyn_vars.act_states_dia; } if(prms.enforce_state_following==1){ // NBRA-like enforcement: adiabatic dynamics, in terms of forces for(int itraj=0; itraj Date: Tue, 30 Jul 2024 04:17:34 -0400 Subject: [PATCH 40/48] Separate the active-state initialization according to the hop rep --- src/dyn/dyn_variables.h | 1 + src/dyn/dyn_variables_electronic.cpp | 182 ++++++++++++++++++++------- src/dyn/libdyn.cpp | 1 + src/libra_py/dynamics/tsh/compute.py | 35 +++--- 4 files changed, 160 insertions(+), 59 deletions(-) diff --git a/src/dyn/dyn_variables.h b/src/dyn/dyn_variables.h index e4bd05c5c..a8f56809c 100755 --- a/src/dyn/dyn_variables.h +++ b/src/dyn/dyn_variables.h @@ -610,6 +610,7 @@ class dyn_variables{ void init_amplitudes(bp::dict params, Random& rnd); void init_density_matrix(bp::dict _params); void init_active_states(bp::dict _params, Random& rnd); + void init_active_states_dia(bp::dict _params, Random& rnd); void init_electronic_dyn_var(bp::dict params, Random& rnd); diff --git a/src/dyn/dyn_variables_electronic.cpp b/src/dyn/dyn_variables_electronic.cpp index 852fb77f1..7e0189917 100644 --- a/src/dyn/dyn_variables_electronic.cpp +++ b/src/dyn/dyn_variables_electronic.cpp @@ -825,77 +825,171 @@ void dyn_variables::init_active_states(bp::dict _params, Random& rnd){ ///================= Actual calculations ================================ act_states.clear(); - act_states_dia.clear(); - if(rep==1){ - //================ For adiabatic - set up them directly ================= - for(traj=0; traj dia_act_states_temp(act_states); - // Set the diabatic active state CMATRIX pop_adi(nadi, nadi); CMATRIX pop_dia(ndia, ndia); for(traj=0; traj(1.0, 0.0) ); + i = dia_act_states_temp[traj]; // active diabatic state + pop_dia *= 0.0; pop_dia.set(i, i, complex(1.0, 0.0) ); // The following transformation is correct only for S_dia = 1 - pop_dia = (*basis_transform[traj]) * pop_adi * (*basis_transform[traj]).H(); + pop_adi = (*basis_transform[traj]).H() * pop_dia * (*basis_transform[traj]); - vector res(ndia, 0.0); - for(i=0; i res(nadi, 0.0); + for(i=0; i istates; + int rep; + int verbosity; + + + std::string key; + for(int i=0;i(params.keys()[i]); + + ///================= Computing Hamiltonian-related properties ==================== + if(key=="init_type") { init_type = bp::extract(params.values()[i]); } + else if(key=="istate") { istate = bp::extract(params.values()[i]); } + else if(key=="istates") { istates = liblibra::libconverters::Py2Cpp( bp::extract< bp::list >(params.values()[i]) ); } + else if(key=="rep") { rep = bp::extract(params.values()[i]); } + else if(key=="verbosity") { verbosity = bp::extract(params.values()[i]); } + + } + + + ///================= Sanity check ================================ + if(! ((rep==0) || (rep==1)) ){ + cout<<"WARNINIG in init_active_states_dia:\ + the rep = "<=ndia && rep==0){ + cout<<"ERROR in init_active_states_dia: the istate is= "<=ndia && rep==1){ + cout<<"ERROR in init_active_states_dia: the istate is= "< 1e-5 ){ + cout<<"ERROR in init_active_states_dia: the sum of the entries in the istates array is "< act_states_temp(act_states_dia); + CMATRIX pop_adi(nadi, nadi); CMATRIX pop_dia(ndia, ndia); for(traj=0; traj(1.0, 0.0) ); + i = act_states_temp[traj]; // active diabatic state + pop_adi *= 0.0; pop_adi.set(i, i, complex(1.0, 0.0) ); // The following transformation is correct only for S_dia = 1 - pop_adi = (*basis_transform[traj]).H() * pop_dia * (*basis_transform[traj]); + pop_dia = (*basis_transform[traj]) * pop_adi * (*basis_transform[traj]).H(); - vector res(nadi, 0.0); - for(i=0; i res(ndia, 0.0); + for(i=0; i Date: Wed, 31 Jul 2024 10:25:40 -0400 Subject: [PATCH 41/48] Call a random number to determine the active state in the other rep --- src/dyn/Dynamics.cpp | 2 +- src/dyn/dyn_variables.h | 2 +- src/dyn/dyn_variables_electronic.cpp | 33 +++++++++++----------------- src/dyn/libdyn.cpp | 2 +- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index 1d4bceef0..1b1929bb0 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -1579,7 +1579,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, } // Set the active states in the other representation through the tranformation matrix - dyn_var.set_active_states_diff_rep(prms.rep_sh); + dyn_var.set_active_states_diff_rep(prms.rep_sh, rnd); // Re-scale (back) couplings and time-overlaps, if the TC-NBRA was used if(prms.thermally_corrected_nbra==1 && prms.tcnbra_do_nac_scaling==1){ remove_thermal_correction(dyn_var, ham, prms); } diff --git a/src/dyn/dyn_variables.h b/src/dyn/dyn_variables.h index a8f56809c..4b51c68a0 100755 --- a/src/dyn/dyn_variables.h +++ b/src/dyn/dyn_variables.h @@ -603,7 +603,7 @@ class dyn_variables{ void update_active_states(int direction, int property); void update_active_states(); - void set_active_states_diff_rep(int rep_sh); + void set_active_states_diff_rep(int rep_sh, Random& rnd); void update_basis_transform(nHamiltonian& ham); diff --git a/src/dyn/dyn_variables_electronic.cpp b/src/dyn/dyn_variables_electronic.cpp index 7e0189917..42becd161 100644 --- a/src/dyn/dyn_variables_electronic.cpp +++ b/src/dyn/dyn_variables_electronic.cpp @@ -1072,7 +1072,7 @@ void dyn_variables::update_active_states(){ -void dyn_variables:: set_active_states_diff_rep(int rep_sh){ +void dyn_variables:: set_active_states_diff_rep(int rep_sh, Random& rnd){ /* Set the active state in the other representation through the transformation matrix @@ -1084,7 +1084,8 @@ void dyn_variables:: set_active_states_diff_rep(int rep_sh){ Tempelaar, R.; Reichman, D. R. Generalization of Fewest-Switches Surface Hopping for Coherences. The Journal of Chemical Physics 2018, 148 (10), 102309. https://doi.org/10.1063/1.5000843 */ - int sz, i, j, traj; + int sz, i, j, traj; + double ksi; if(rep_sh==0){ sz = ndia; } else if(rep_sh==1){ sz = nadi; } else{ @@ -1111,15 +1112,11 @@ void dyn_variables:: set_active_states_diff_rep(int rep_sh){ pop_dia = U * pop_adi * U.H(); - // Set the state having the largest diagonal element to the active state - int max_diag_indx = 0; - double max_diag = pop_dia.get(0,0).real(); - for(int i=1; i max_diag){ max_diag_indx = i; max_diag = el_diag; } - } - - act_states_dia[traj] = max_diag_indx; + vector diag_els(ndia, 0.0); + for(i=0;i max_diag){ max_diag_indx = i; max_diag = el_diag; } - } - - act_states[traj] = max_diag_indx; + vector diag_els(nadi, 0.0); + for(i=0;i("dyn_variables",init()) From 6ce8753a04fa31afca18ffb10289c4c395f80a72 Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Thu, 1 Aug 2024 02:06:28 -0400 Subject: [PATCH 42/48] Use separate functions to compute the average SH pop based on the probabilistic sampling --- src/dyn/dyn_variables.h | 1 + src/dyn/dyn_variables_electronic.cpp | 75 +++++++++++++++++++--------- src/dyn/libdyn.cpp | 1 + src/libra_py/dynamics/tsh/plot.py | 36 +++++++++++-- src/libra_py/dynamics/tsh/save.py | 24 +++++++++ 5 files changed, 111 insertions(+), 26 deletions(-) diff --git a/src/dyn/dyn_variables.h b/src/dyn/dyn_variables.h index 4b51c68a0..259882947 100755 --- a/src/dyn/dyn_variables.h +++ b/src/dyn/dyn_variables.h @@ -618,6 +618,7 @@ class dyn_variables{ vector compute_average_se_pop(int rep); vector compute_average_sh_pop(int rep); vector compute_average_mash_pop(int rep); + vector compute_average_sh_sample_pop(int rep); double compute_tcnbra_ekin(); diff --git a/src/dyn/dyn_variables_electronic.cpp b/src/dyn/dyn_variables_electronic.cpp index 42becd161..dad434407 100644 --- a/src/dyn/dyn_variables_electronic.cpp +++ b/src/dyn/dyn_variables_electronic.cpp @@ -1295,33 +1295,31 @@ vector dyn_variables::compute_average_sh_pop(int rep){ } vector res(sz, 0.0); + vector effective_states( act_states ); if(rep==0){ - //===== For diabatic populations ======= - vector effective_states( act_states_dia ); - for(traj=0; traj(0.0, 0.0) ); } -// pop_adi.set(i, i, complex(1.0, 0.0) ); -// -// // The following transformation is correct only for S_dia = 1 -// U = (*basis_transform[traj]);// * (*proj_adi[traj]); -// -// pop_dia = U * pop_adi * U.H(); -// for(j=0; j(0.0, 0.0) ); } + pop_adi.set(i, i, complex(1.0, 0.0) ); + + // The following transformation is correct only for S_dia = 1 + U = (*basis_transform[traj]);// * (*proj_adi[traj]); + + pop_dia = U * pop_adi * U.H(); + for(j=0; j dyn_variables::compute_average_sh_pop(int rep){ return res; } +vector dyn_variables::compute_average_sh_sample_pop(int rep){ +/** + Computing the SH population with the probabilistic sampling. + If rep is set to the hop representation (rep_sh), the result is the same as that from the compute_average_sh_pop method +*/ + + int sz, i, j, traj; + if(rep==0){ sz = ndia; } + else if(rep==1){ sz = nadi; } + else{ + cout<<"Can not compute SH population for representation = "< res(sz, 0.0); + + if(rep==0){ + vector effective_states( act_states_dia ); + for(traj=0; traj effective_states( act_states ); + for(traj=0; traj Date: Thu, 1 Aug 2024 03:58:27 -0400 Subject: [PATCH 43/48] Separate compute_average_sh_hop into the two versions according to rep_sh --- src/dyn/dyn_variables.h | 3 +- src/dyn/dyn_variables_electronic.cpp | 52 ++++++++++++++++++++++++++-- src/dyn/libdyn.cpp | 3 +- src/libra_py/dynamics/tsh/compute.py | 5 +-- src/libra_py/dynamics/tsh/save.py | 40 ++++++++++++++------- 5 files changed, 84 insertions(+), 19 deletions(-) diff --git a/src/dyn/dyn_variables.h b/src/dyn/dyn_variables.h index 259882947..34e5d4216 100755 --- a/src/dyn/dyn_variables.h +++ b/src/dyn/dyn_variables.h @@ -616,7 +616,8 @@ class dyn_variables{ CMATRIX compute_average_dm(int rep); vector compute_average_se_pop(int rep); - vector compute_average_sh_pop(int rep); + vector compute_average_sh_pop_rep_sh1(int rep); + vector compute_average_sh_pop_rep_sh0(int rep); vector compute_average_mash_pop(int rep); vector compute_average_sh_sample_pop(int rep); diff --git a/src/dyn/dyn_variables_electronic.cpp b/src/dyn/dyn_variables_electronic.cpp index dad434407..ea51b4abb 100644 --- a/src/dyn/dyn_variables_electronic.cpp +++ b/src/dyn/dyn_variables_electronic.cpp @@ -1283,8 +1283,7 @@ vector dyn_variables::compute_average_mash_pop(int rep){ } - -vector dyn_variables::compute_average_sh_pop(int rep){ +vector dyn_variables::compute_average_sh_pop_rep_sh1(int rep){ int sz, i, j, traj; if(rep==0){ sz = ndia; } @@ -1333,6 +1332,55 @@ vector dyn_variables::compute_average_sh_pop(int rep){ return res; } + + +vector dyn_variables::compute_average_sh_pop_rep_sh0(int rep){ + + int sz, i, j, traj; + if(rep==0){ sz = ndia; } + else if(rep==1){ sz = nadi; } + else{ + cout<<"Can not compute SH population for representation = "< res(sz, 0.0); + vector effective_states( act_states_dia ); + + if(rep==1){ + + // Now, the active states in both representations are updated in each MD step + // ===== For adiabatic SH populations: analoguous to the compute_average_sh_pop_rep_sh1 ====== + + CMATRIX pop_dia(ndia, ndia); + CMATRIX pop_adi(nadi, nadi); + CMATRIX U(ndia, nadi); + + for(traj=0; traj(0.0, 0.0) ); } + pop_dia.set(i, i, complex(1.0, 0.0) ); + + // The following transformation is correct only for S_dia = 1 + U = (*basis_transform[traj]);// * (*proj_adi[traj]); + + pop_adi = U.H() * pop_dia * U; + for(j=0; j effective_states( act_states_dia ); + for(traj=0; traj dyn_variables::compute_average_sh_sample_pop(int rep){ /** Computing the SH population with the probabilistic sampling. diff --git a/src/dyn/libdyn.cpp b/src/dyn/libdyn.cpp index 222cafad1..ae8ec5d77 100755 --- a/src/dyn/libdyn.cpp +++ b/src/dyn/libdyn.cpp @@ -318,7 +318,8 @@ void export_dyn_variables_objects(){ .def("compute_average_dm", &dyn_variables::compute_average_dm) .def("compute_average_se_pop", &dyn_variables::compute_average_se_pop) - .def("compute_average_sh_pop", &dyn_variables::compute_average_sh_pop) + .def("compute_average_sh_pop_rep_sh1", &dyn_variables::compute_average_sh_pop_rep_sh1) + .def("compute_average_sh_pop_rep_sh0", &dyn_variables::compute_average_sh_pop_rep_sh0) .def("compute_average_mash_pop", &dyn_variables::compute_average_mash_pop) .def("compute_average_sh_sample_pop", &dyn_variables::compute_average_sh_sample_pop) diff --git a/src/libra_py/dynamics/tsh/compute.py b/src/libra_py/dynamics/tsh/compute.py index e036c9fe9..955501c36 100755 --- a/src/libra_py/dynamics/tsh/compute.py +++ b/src/libra_py/dynamics/tsh/compute.py @@ -922,14 +922,15 @@ def generic_recipe(_dyn_params, compute_model, _model_params,_init_elec, _init_n print(Cpp2Py(dyn_var.act_states)) print("Initial adiabatic populations") - pops_sh1 = dyn_var.compute_average_sh_pop(1) + pops_sh1 = dyn_var.compute_average_sh_pop_rep_sh1(1) print( Cpp2Py(pops_sh1)) + elif dyn_params["rep_sh"] == 0: print("Active states (diabatic)") print(Cpp2Py(dyn_var.act_states_dia)) print("Initial diabatic populations") - pops_sh0 = dyn_var.compute_average_sh_pop(0) + pops_sh0 = dyn_var.compute_average_sh_pop_rep_sh0(0) print( Cpp2Py(pops_sh0)) # Finally, start the dynamics calculations diff --git a/src/libra_py/dynamics/tsh/save.py b/src/libra_py/dynamics/tsh/save.py index 7a495afce..f13d912a4 100755 --- a/src/libra_py/dynamics/tsh/save.py +++ b/src/libra_py/dynamics/tsh/save.py @@ -481,13 +481,15 @@ def save_hdf5_2D(saver, i, states, txt_type=0): -def save_hdf5_2D_new(saver, i, dyn_var, ham, txt_type=0): +def save_hdf5_2D_new(saver, i, params, dyn_var, ham, txt_type=0): """ saver - can be either hdf5_saver or mem_saver txt_type ( int ): 0 - standard, all the timesteps, 1 - only the current one """ + + rep_sh = params["rep_sh"] t = 0 if txt_type==0: @@ -527,18 +529,30 @@ def save_hdf5_2D_new(saver, i, dyn_var, ham, txt_type=0): if "sh_pop_dia" in saver.keywords and "sh_pop_dia" in saver.np_data.keys(): # Average diabatic SH populations # Format: saver.add_dataset("sh_pop_dia", (_nsteps, _ndia), "R") - pops_sh0 = dyn_var.compute_average_sh_pop(0) - ndia = dyn_var.ndia - for ist in range(ndia): - saver.save_multi_scalar(t, ist, "sh_pop_dia", pops_sh0[ist]) + if rep_sh==1: + pops_sh0 = dyn_var.compute_average_sh_pop_rep_sh1(0) + ndia = dyn_var.ndia + for ist in range(ndia): + saver.save_multi_scalar(t, ist, "sh_pop_dia", pops_sh0[ist]) + elif rep_sh==0: + pops_sh0 = dyn_var.compute_average_sh_pop_rep_sh0(0) + ndia = dyn_var.ndia + for ist in range(ndia): + saver.save_multi_scalar(t, ist, "sh_pop_dia", pops_sh0[ist]) if "sh_pop_adi" in saver.keywords and "sh_pop_adi" in saver.np_data.keys(): # Average adiabatic SH populations # Format: saver.add_dataset("sh_pop_adi", (_nsteps, _nadi), "R") - pops_sh1 = dyn_var.compute_average_sh_pop(1) - nadi = dyn_var.nadi - for ist in range(nadi): - saver.save_multi_scalar(t, ist, "sh_pop_adi", pops_sh1[ist]) + if rep_sh==1: + pops_sh1 = dyn_var.compute_average_sh_pop_rep_sh1(1) + nadi = dyn_var.nadi + for ist in range(nadi): + saver.save_multi_scalar(t, ist, "sh_pop_adi", pops_sh1[ist]) + elif rep_sh==0: + pops_sh1 = dyn_var.compute_average_sh_pop_rep_sh0(1) + nadi = dyn_var.nadi + for ist in range(nadi): + saver.save_multi_scalar(t, ist, "sh_pop_adi", pops_sh1[ist]) if "mash_pop_dia" in saver.keywords and "mash_pop_dia" in saver.np_data.keys(): # Average diabatic MASH populations @@ -918,19 +932,19 @@ def save_tsh_data_1234_new(_savers, params, i, dyn_var, ham): # =========== Using : def save_hdf5_2D_new(saver, i, dyn_var, txt_type=0) ===================== if hdf5_output_level>=2 and _savers["hdf5_saver"]!=None: #save_hdf5_2D(_savers["hdf5_saver"], i, states) - save_hdf5_2D_new(_savers["hdf5_saver"], i, dyn_var, ham) + save_hdf5_2D_new(_savers["hdf5_saver"], i, params, dyn_var, ham) if mem_output_level>=2 and _savers["mem_saver"]!=None: #save_hdf5_2D(_savers["mem_saver"], i, states) - save_hdf5_2D_new(_savers["mem_saver"], i, dyn_var, ham) + save_hdf5_2D_new(_savers["mem_saver"], i, params, dyn_var, ham) if txt_output_level>=2 and _savers["txt_saver"]!=None: #save_hdf5_2D(_savers["txt_saver"], i, states) - save_hdf5_2D_new(_savers["txt_saver"], i, dyn_var, ham) + save_hdf5_2D_new(_savers["txt_saver"], i, params, dyn_var, ham) if txt2_output_level>=2 and _savers["txt2_saver"]!=None: #save_hdf5_2D(_savers["txt2_saver"], i, states, 1) - save_hdf5_2D_new(_savers["txt2_saver"], i, dyn_var, ham, 1) + save_hdf5_2D_new(_savers["txt2_saver"], i, params, dyn_var, ham, 1) # ============ Using: def save_hdf5_3D_new(saver, i, dyn_var, txt_type=0) ========================== From 2f706f54262d66b3a67d858d2671760d1951f111 Mon Sep 17 00:00:00 2001 From: DaehoHan Date: Thu, 1 Aug 2024 16:52:07 -0400 Subject: [PATCH 44/48] Narrow down the SH pop methods to two routines: the direct calculation and Tempelaar and Reichman method --- src/dyn/dyn_variables.h | 5 +- src/dyn/dyn_variables_electronic.cpp | 107 ++++++++++----------------- src/dyn/libdyn.cpp | 5 +- src/libra_py/dynamics/tsh/compute.py | 4 +- src/libra_py/dynamics/tsh/plot.py | 58 ++++++++------- src/libra_py/dynamics/tsh/save.py | 87 +++++++++------------- 6 files changed, 111 insertions(+), 155 deletions(-) diff --git a/src/dyn/dyn_variables.h b/src/dyn/dyn_variables.h index 34e5d4216..95ffd81f3 100755 --- a/src/dyn/dyn_variables.h +++ b/src/dyn/dyn_variables.h @@ -616,10 +616,9 @@ class dyn_variables{ CMATRIX compute_average_dm(int rep); vector compute_average_se_pop(int rep); - vector compute_average_sh_pop_rep_sh1(int rep); - vector compute_average_sh_pop_rep_sh0(int rep); + vector compute_average_sh_pop(int rep); + vector compute_average_sh_pop_TR(int rep); vector compute_average_mash_pop(int rep); - vector compute_average_sh_sample_pop(int rep); double compute_tcnbra_ekin(); diff --git a/src/dyn/dyn_variables_electronic.cpp b/src/dyn/dyn_variables_electronic.cpp index ea51b4abb..02f6e3d6e 100644 --- a/src/dyn/dyn_variables_electronic.cpp +++ b/src/dyn/dyn_variables_electronic.cpp @@ -1282,8 +1282,13 @@ vector dyn_variables::compute_average_mash_pop(int rep){ } - -vector dyn_variables::compute_average_sh_pop_rep_sh1(int rep){ + + +vector dyn_variables::compute_average_sh_pop(int rep){ +/** + Computing the SH population with the active state of the input rep. + If rep is different from rep_sh, the resultant SH population is computed indirectly through the sampling based on diagonal elments of the density matrix +*/ int sz, i, j, traj; if(rep==0){ sz = ndia; } @@ -1294,35 +1299,13 @@ vector dyn_variables::compute_average_sh_pop_rep_sh1(int rep){ } vector res(sz, 0.0); - vector effective_states( act_states ); if(rep==0){ - - // Now, the active states in both representations are updated in each MD step - // ===== For diabatic SH populations: use the prescription of ====== - // Tempelaar, R.; Reichman, D. R. Generalization of Fewest-Switches Surface Hopping for Coherences. - // The Journal of Chemical Physics 2018, 148 (10), 102309. https://doi.org/10.1063/1.5000843 - - CMATRIX pop_adi(nadi, nadi); - CMATRIX pop_dia(ndia, ndia); - CMATRIX U(ndia, nadi); - - for(traj=0; traj(0.0, 0.0) ); } - pop_adi.set(i, i, complex(1.0, 0.0) ); - - // The following transformation is correct only for S_dia = 1 - U = (*basis_transform[traj]);// * (*proj_adi[traj]); - - pop_dia = U * pop_adi * U.H(); - for(j=0; j effective_states( act_states_dia ); + for(traj=0; traj effective_states( act_states ); for(traj=0; traj dyn_variables::compute_average_sh_pop_rep_sh1(int rep){ -vector dyn_variables::compute_average_sh_pop_rep_sh0(int rep){ - +vector dyn_variables::compute_average_sh_pop_TR(int rep){ +/** + Computing the SH population based on the following work: + Tempelaar, R.; Reichman, D. R. Generalization of Fewest-Switches Surface Hopping for Coherences. + The Journal of Chemical Physics 2018, 148 (10), 102309. https://doi.org/10.1063/1.5000843 +*/ int sz, i, j, traj; if(rep==0){ sz = ndia; } else if(rep==1){ sz = nadi; } @@ -1345,15 +1332,33 @@ vector dyn_variables::compute_average_sh_pop_rep_sh0(int rep){ } vector res(sz, 0.0); - vector effective_states( act_states_dia ); - if(rep==1){ - - // Now, the active states in both representations are updated in each MD step - // ===== For adiabatic SH populations: analoguous to the compute_average_sh_pop_rep_sh1 ====== + if(rep==0){ + vector effective_states( act_states ); + CMATRIX pop_adi(nadi, nadi); CMATRIX pop_dia(ndia, ndia); + CMATRIX U(ndia, nadi); + + for(traj=0; traj(0.0, 0.0) ); } + pop_adi.set(i, i, complex(1.0, 0.0) ); + + // The following transformation is correct only for S_dia = 1 + U = (*basis_transform[traj]);// * (*proj_adi[traj]); + + pop_dia = U * pop_adi * U.H(); + for(j=0; j effective_states( act_states_dia ); + CMATRIX pop_adi(nadi, nadi); + CMATRIX pop_dia(ndia, ndia); CMATRIX U(ndia, nadi); for(traj=0; traj dyn_variables::compute_average_sh_pop_rep_sh0(int rep){ } }// rep == 1 - else if(rep==0){ - //===== For diabatic populations, we just use the active diabatic states ======= - vector effective_states( act_states_dia ); - for(traj=0; traj dyn_variables::compute_average_sh_sample_pop(int rep){ -/** - Computing the SH population with the probabilistic sampling. - If rep is set to the hop representation (rep_sh), the result is the same as that from the compute_average_sh_pop method -*/ - - int sz, i, j, traj; - if(rep==0){ sz = ndia; } - else if(rep==1){ sz = nadi; } - else{ - cout<<"Can not compute SH population for representation = "< res(sz, 0.0); - - if(rep==0){ - vector effective_states( act_states_dia ); - for(traj=0; traj effective_states( act_states ); - for(traj=0; traj=2 and _savers["hdf5_saver"]!=None: #save_hdf5_2D(_savers["hdf5_saver"], i, states) - save_hdf5_2D_new(_savers["hdf5_saver"], i, params, dyn_var, ham) + save_hdf5_2D_new(_savers["hdf5_saver"], i, dyn_var, ham) if mem_output_level>=2 and _savers["mem_saver"]!=None: #save_hdf5_2D(_savers["mem_saver"], i, states) - save_hdf5_2D_new(_savers["mem_saver"], i, params, dyn_var, ham) + save_hdf5_2D_new(_savers["mem_saver"], i, dyn_var, ham) if txt_output_level>=2 and _savers["txt_saver"]!=None: #save_hdf5_2D(_savers["txt_saver"], i, states) - save_hdf5_2D_new(_savers["txt_saver"], i, params, dyn_var, ham) + save_hdf5_2D_new(_savers["txt_saver"], i, dyn_var, ham) if txt2_output_level>=2 and _savers["txt2_saver"]!=None: #save_hdf5_2D(_savers["txt2_saver"], i, states, 1) - save_hdf5_2D_new(_savers["txt2_saver"], i, params, dyn_var, ham, 1) + save_hdf5_2D_new(_savers["txt2_saver"], i, dyn_var, ham, 1) # ============ Using: def save_hdf5_3D_new(saver, i, dyn_var, txt_type=0) ========================== From 654e6c30f12f01a2dea930b9bb462b8c66b4442d Mon Sep 17 00:00:00 2001 From: Alexey Akimov Date: Sat, 3 Aug 2024 10:40:19 -0400 Subject: [PATCH 45/48] Added the experimental energy and force-based state tracking and new algorithm for NAC phase correction --- src/dyn/Dynamics.cpp | 2 +- src/dyn/dyn_control_params.cpp | 3 +- src/dyn/dyn_control_params.h | 1 + src/dyn/dyn_ham.cpp | 49 +++++++++++++- src/dyn/dyn_ham.h | 3 +- src/dyn/dyn_projectors.cpp | 116 ++++++++++++++++++++++++++++++++- src/dyn/dyn_projectors.h | 1 + src/dyn/libdyn.cpp | 15 +++++ 8 files changed, 183 insertions(+), 7 deletions(-) diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index b964e0791..17498e5a8 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -1303,7 +1303,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, // Recompute the orthogonalized reprojection matrices, stored in dyn_var.proj_adi // this calculaitons used ham.children[i].time_overlap matrix, updated in the previous step - update_proj_adi(prms, dyn_var, ham); + update_proj_adi(prms, dyn_var, ham, ham_aux); // Recompute NAC, Hvib, etc. in response to change of p update_Hamiltonian_variables(prms, dyn_var, ham, ham_aux, py_funct, params, 1); diff --git a/src/dyn/dyn_control_params.cpp b/src/dyn/dyn_control_params.cpp index 2c2873933..e897878d0 100755 --- a/src/dyn/dyn_control_params.cpp +++ b/src/dyn/dyn_control_params.cpp @@ -268,7 +268,8 @@ void dyn_control_params::sanity_check(){ if(state_tracking_algo==-1 || state_tracking_algo==0 || state_tracking_algo==1 || state_tracking_algo==2 || state_tracking_algo==3 || - state_tracking_algo==32 || state_tracking_algo==33){ ; ; } + state_tracking_algo==32 || state_tracking_algo==33 || + state_tracking_algo==4){ ; ; } else{ std::cout<<"Error in dyn_control_params::sanity_check: state_tracking_algo = " <nac_adi->get(i,j).real(); + double x2 = ham_prev.children[traj]->nac_adi->get(i,j).real(); + double sng = SIGN(x1) * SIGN(x2); + ham.children[traj]->nac_adi->scale(i,j, sng); + ham.children[traj]->nac_adi->scale(j,i, sng); + }// for j + }// for i + }// for traj + }// if correction + //========================== Vibronic Hamiltonian =============================== // Don't update Hvib - perhaps because they are read from files in step 1 @@ -268,21 +282,32 @@ void update_time_overlaps(dyn_control_params& prms, dyn_variables& dyn_var, nHam } -void update_proj_adi(dyn_control_params& prms, dyn_variables& dyn_var, nHamiltonian& Ham){ // nHamiltonian& Ham_prev){ +void update_proj_adi(dyn_control_params& prms, dyn_variables& dyn_var, nHamiltonian& Ham, nHamiltonian& Ham_prev){ /** Just re-compute the proj_adi matrices */ //======= Parameters of the dyn variables ========== int ntraj = dyn_var.ntraj; + vector traj_id(1,0); + + CMATRIX f_prev(dyn_var.nadi, dyn_var.ndof); + CMATRIX f_curr(dyn_var.nadi, dyn_var.ndof); + MATRIX iM(dyn_var.get_imass()); + MATRIX momenta(dyn_var.get_momenta()); + MATRIX p(dyn_var.ndof, 1); + double dt = prms.dt; double diff = 0.0; for(int itraj=0; itraj=100 && method <200){ traj1 = 0; } + traj_id[0] = traj1; nHamiltonian* ham = Ham.children[traj1]; - //nHamiltonian* ham_prev = Ham_prev.children[traj1]; + nHamiltonian* ham_prev = Ham_prev.children[traj1]; + p = momenta.col(traj1); + int act_state = dyn_var.act_states[traj1]; //================= Basis re-expansion =================== CMATRIX P(ham->nadi, ham->nadi); @@ -301,11 +326,26 @@ void update_proj_adi(dyn_control_params& prms, dyn_variables& dyn_var, nHamilton } T_new = orthogonalized_T( T_new ); } - else{ // This is based on reordering + phase correction + else if(prms.state_tracking_algo==1 || prms.state_tracking_algo==2 || prms.state_tracking_algo==3 || + prms.state_tracking_algo==32 || prms.state_tracking_algo==33){ // This is based on reordering + phase correction CMATRIX Eadi(ham->get_ham_adi()); T_new = P; T_new = compute_projector(prms, Eadi, T_new); } + else if(prms.state_tracking_algo==4){ // new experimental approach - based on forces + //exit(0); + CMATRIX Eadi(ham->get_ham_adi()); + MATRIX e_curr(ham->get_ham_adi().real()); + MATRIX e_prev(ham_prev->get_ham_adi().real()); + f_curr = ham->all_forces_adi(traj_id); + f_prev = ham_prev->all_forces_adi(traj_id); + //ham->get_hvib_adi().show_matrix(); + T_new = compute_F_cost_matrix(f_curr, f_prev, e_curr, e_prev, p, iM, dt, act_state); + //T_new.show_matrix(); + //exit(0); + T_new = compute_projector(prms, Eadi, T_new); // CMATRIX compute_projector(dyn_control_params& prms, CMATRIX& Eadi, CMATRIX& St){ + // T_new = orthogonalized_T( T_new ); + } *dyn_var.proj_adi[itraj] = T_new; @@ -314,6 +354,9 @@ void update_proj_adi(dyn_control_params& prms, dyn_variables& dyn_var, nHamilton }// reproject_basis +void update_proj_adi(dyn_control_params& prms, dyn_variables& dyn_var, nHamiltonian& Ham){ + update_proj_adi(prms, dyn_var, Ham, Ham); +} diff --git a/src/dyn/dyn_ham.h b/src/dyn/dyn_ham.h index a7a19be95..5e89ad056 100644 --- a/src/dyn/dyn_ham.h +++ b/src/dyn/dyn_ham.h @@ -47,7 +47,8 @@ void update_Hamiltonian_variables(bp::dict prms, dyn_variables& dyn_var, void update_time_overlaps(dyn_control_params& prms, dyn_variables& dyn_var, nHamiltonian& ham, nHamiltonian& ham_prev); -void update_proj_adi(dyn_control_params& prms, dyn_variables& dyn_var, nHamiltonian& Ham); // nHamiltonian& Ham_prev); +void update_proj_adi(dyn_control_params& prms, dyn_variables& dyn_var, nHamiltonian& Ham, nHamiltonian& Ham_prev); +void update_proj_adi(dyn_control_params& prms, dyn_variables& dyn_var, nHamiltonian& Ham); diff --git a/src/dyn/dyn_projectors.cpp b/src/dyn/dyn_projectors.cpp index 2988fce9b..ae48f2e0a 100755 --- a/src/dyn/dyn_projectors.cpp +++ b/src/dyn/dyn_projectors.cpp @@ -623,6 +623,9 @@ void update_projectors(dyn_control_params& prms, vector& projectors, if(prms.state_tracking_algo==33){ perm_t = get_stochastic_reordering3(st, rnd, prms.convergence, prms.max_number_attempts, prms.min_probability_reordering, 0); } + if(prms.state_tracking_algo==4){ // same as option 2, but the input for St argument will be different + perm_t = Munkres_Kuhn(st, Eadi[traj], prms.MK_alpha, prms.MK_verbosity); + } // P -> P * perm CMATRIX p_i(nst, nst); @@ -780,7 +783,7 @@ CMATRIX compute_projector(dyn_control_params& prms, CMATRIX& Eadi, CMATRIX& St){ if(prms.state_tracking_algo==1){ perm_t = get_reordering(st); } - else if(prms.state_tracking_algo==2){ perm_t = Munkres_Kuhn(st, Eadi, prms.MK_alpha, prms.MK_verbosity); } + else if(prms.state_tracking_algo==2 || prms.state_tracking_algo==4){ perm_t = Munkres_Kuhn(st, Eadi, prms.MK_alpha, prms.MK_verbosity); } /* else if(prms.state_tracking_algo==3){ perm_t = get_stochastic_reordering(st, rnd); } else if(prms.state_tracking_algo==32){ perm_t = get_stochastic_reordering2(st, rnd); } @@ -952,6 +955,117 @@ CMATRIX dynconsyst_to_raw(CMATRIX& amplitudes, vector& projectors){ } +CMATRIX compute_F_cost_matrix(CMATRIX& F_curr, CMATRIX& F_prev, MATRIX& e_curr, MATRIX& e_prev, MATRIX& _p, MATRIX& iM, double dt, int act_state){ +/** F_curr, F_prev - CMATRIX(nadi, ndof) + p - MATRIX(ndof, 1) - current momentum + iM - MATRIX(ndof, 1) - inverse matrices + dt - time step + act_state - the current active state +*/ + int i,j; + int ndof = F_curr.n_cols; + int nst = F_curr.n_rows; + CMATRIX res(nst, nst); + MATRIX fi_curr(1, ndof); + MATRIX fj_curr(1, ndof); + MATRIX fi_prev(1, ndof); + MATRIX fj_prev(1, ndof); + MATRIX dq_i(ndof,1); + MATRIX dq_j(ndof,1); + MATRIX p(_p); + + // correction, because the current p is already a half-step propagated using old forces + //p -= dt * F_prev.row(act_state).real().T(); + + MATRIX vel(ndof, 1); vel.dot_product(iM, p); + MATRIX tmp(ndof, 1); + +// cout<<"Forces:\n"; +// F_prev.real().show_matrix(); +// F_curr.real().show_matrix(); + + double val = 0.0; + for(i=0; i Date: Sat, 3 Aug 2024 19:06:46 -0400 Subject: [PATCH 46/48] Added the nac_phase_correction as an option rather than a hard-coded variant, cleaned up the F-tracking approach, removed some redundant code (already commented) and ham_rep option. Significantly revised the documentation of the tsh.compute main function - now it is consistent with the description of the parameters in the dyn_control_params class --- src/dyn/Dynamics.cpp | 83 +---- src/dyn/dyn_control_params.cpp | 3 + src/dyn/dyn_control_params.h | 132 +++++--- src/dyn/dyn_ham.cpp | 2 +- src/dyn/dyn_projectors.cpp | 61 +--- src/libra_py/dynamics/tsh/compute.py | 481 +++++++++++++++++++-------- 6 files changed, 437 insertions(+), 325 deletions(-) diff --git a/src/dyn/Dynamics.cpp b/src/dyn/Dynamics.cpp index 6dac160c6..679b70790 100755 --- a/src/dyn/Dynamics.cpp +++ b/src/dyn/Dynamics.cpp @@ -268,45 +268,6 @@ void apply_afssh(dyn_variables& dyn_var, CMATRIX& C, vector& act_states, MA } - -/* -void compute_dynamics(MATRIX& q, MATRIX& p, MATRIX& invM, CMATRIX& C, vector& projectors, - vector& act_states, - nHamiltonian& ham, bp::object py_funct, bp::dict params, bp::dict dyn_params, Random& rnd){ - -// This is a version to maintain the backward-compatibility - - dyn_control_params prms; - prms.set_parameters(dyn_params); - - int ntraj = q.n_cols; - vector therm(ntraj, Thermostat(prms.thermostat_params)); - - compute_dynamics(q, p, invM, C, projectors, act_states, ham, py_funct, params, dyn_params, rnd, therm); - -} - -void compute_dynamics(MATRIX& q, MATRIX& p, MATRIX& invM, CMATRIX& C, vector& projectors, - vector& act_states, - nHamiltonian& ham, bp::object py_funct, bp::dict& params, bp::dict& dyn_params, Random& rnd, - vector& therm){ - - int ndof = q.n_rows; - int ntraj = q.n_cols; - int nst = C.n_rows; - - dyn_variables dyn_var(nst, nst, ndof, ntraj); - compute_dynamics(q, p, invM, C, projectors, act_states, ham, py_funct, params, dyn_params, rnd, therm, dyn_var); - -} - -void compute_dynamics(MATRIX& q, MATRIX& p, MATRIX& invM, CMATRIX& C, vector& projectors, - vector& act_states, - nHamiltonian& ham, bp::object py_funct, bp::dict& params, bp::dict& dyn_params, Random& rnd, - vector& therm, dyn_variables& dyn_var){ -} -*/ - void apply_projectors(CMATRIX& C, vector& proj){ int ntraj = proj.size(); @@ -660,38 +621,6 @@ void update_wp_width(dyn_variables& dyn_var, dyn_control_params& prms){ } } -/* -void update_proj_adi(dyn_variables& dyn_var, nHamiltonian* Ham, nHamiltonian* Ham_prev, dyn_control_params& prms){ - - Just re-compute the proj_adi matrices - - - - //======= Parameters of the dyn variables ========== - int ntraj = dyn_var.ntraj; - - for(int itraj=0; itraj=100 && method <200){ traj1 = 0; } - - nHamiltonian* ham = Ham->children[traj1]; - nHamiltonian* ham_prev = Ham_prev->children[traj1]; - - //================= Basis re-expansion =================== - CMATRIX P(ham->nadi, ham->nadi); - CMATRIX T(*dyn_var.proj_adi[itraj]); T.load_identity(); - CMATRIX T_new(ham->nadi, ham->nadi); - - P = ham->get_time_overlap_adi(); // U_old.H() * U; - - // More consistent with the new derivations: - FullPivLU_inverse(P, T_new); - T_new = orthogonalized_T( T_new ); - - *dyn_var.proj_adi[itraj] = T_new; - }//for ntraj - -}// reproject_basis -*/ void propagate_electronic(dyn_variables& dyn_var, nHamiltonian* Ham, nHamiltonian* Ham_prev, dyn_control_params& prms){ @@ -1333,9 +1262,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, } // Recompute density matrices in response to the updated amplitudes -// dyn_var.update_amplitudes(prms, ham); dyn_var.update_amplitudes(prms); -// dyn_var.update_density_matrix(prms, ham, 1); dyn_var.update_density_matrix(prms); vector old_states( dyn_var.act_states); @@ -1479,9 +1406,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, // Update amplitudes and density matrices in response to decoherence corrections -// dyn_var.update_amplitudes(prms, ham); dyn_var.update_amplitudes(prms); -// dyn_var.update_density_matrix(prms, ham, 1); dyn_var.update_density_matrix(prms); @@ -1513,11 +1438,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, if(prms.rep_sh==1){ prop_states = propose_hops(g, dyn_var.act_states, rnd); - // Decide if to accept the transitions (and then which) - // Here, it is okay to use the local copies of the q, p, etc. variables, since we don't change the actual variables - // 10/21/2023 - deprecate this version - //act_states = accept_hops(prms, *dyn_var.q, *dyn_var.p, invM, *dyn_var.ampl_adi, ham, prop_states, dyn_var.act_states, rnd); - // in favor of this: + // Decide if to accept the transitions (and then which) act_states = accept_hops(dyn_var, ham, prop_states, dyn_var.act_states, prms, rnd); } else if(prms.rep_sh==0){ @@ -1594,9 +1515,7 @@ void compute_dynamics(dyn_variables& dyn_var, bp::dict dyn_params, // Update the amplitudes and DM, in response to state hopping and other changes in the TSH part // so that we have them consistent in the output -// dyn_var.update_amplitudes(prms, ham); dyn_var.update_amplitudes(prms); -// dyn_var.update_density_matrix(prms, ham, 1); dyn_var.update_density_matrix(prms); diff --git a/src/dyn/dyn_control_params.cpp b/src/dyn/dyn_control_params.cpp index e897878d0..957fb6888 100755 --- a/src/dyn/dyn_control_params.cpp +++ b/src/dyn/dyn_control_params.cpp @@ -51,6 +51,7 @@ dyn_control_params::dyn_control_params(){ hvib_update_method = 1; do_ssy = 0; do_phase_correction = 1; + do_nac_phase_correction = 0; phase_correction_tol = 1e-3; state_tracking_algo = -1; MK_alpha = 0.0; @@ -154,6 +155,7 @@ dyn_control_params::dyn_control_params(const dyn_control_params& x){ hvib_update_method = x.hvib_update_method; do_ssy = x.do_ssy; do_phase_correction = x.do_phase_correction; + do_nac_phase_correction = x.do_nac_phase_correction; phase_correction_tol = x.phase_correction_tol; state_tracking_algo = x.state_tracking_algo; MK_alpha = x.MK_alpha; @@ -350,6 +352,7 @@ void dyn_control_params::set_parameters(bp::dict params){ else if(key=="do_ssy") { do_ssy = bp::extract(params.values()[i]); } else if(key=="do_phase_correction") { do_phase_correction = bp::extract(params.values()[i]); } else if(key=="phase_correction_tol") { phase_correction_tol = bp::extract(params.values()[i]); } + else if(key=="do_nac_phase_correction"){ do_nac_phase_correction = bp::extract(params.values()[i]); } else if(key=="state_tracking_algo"){ state_tracking_algo = bp::extract(params.values()[i]); } else if(key=="MK_alpha") { MK_alpha = bp::extract(params.values()[i]); } else if(key=="MK_verbosity") { MK_verbosity = bp::extract(params.values()[i]); } diff --git a/src/dyn/dyn_control_params.h b/src/dyn/dyn_control_params.h index 62e4e7f32..198e0535a 100755 --- a/src/dyn/dyn_control_params.h +++ b/src/dyn/dyn_control_params.h @@ -62,20 +62,6 @@ class dyn_control_params{ int rep_tdse; - /** - The representation of the Hamiltonian update. This is the representation in which the - computed properties are assumed to be. For instance, we may have set it to 1, to read the - adiabatic energies and nonadiabatic couplings, to bypass the diabatic-to-adiabatic transformation, - which may be useful in some atomistic calculations, or with the NBRA - - Options: - - 0: diabatic representation [ default ] - - 1: adiabatic representation - - */ - //int rep_ham; /// TO BE REPLACED BY the ham_update_method - - /** How to update Hamiltonian and which type of Hamiltonian to update @@ -242,6 +228,18 @@ class dyn_control_params{ */ double phase_correction_tol; + + /** + New phase correction, directly applied to NACs. Intended to be used mostly with state_tracking_algo == 4, + although can be useful with other state treacking algorithms. Should not be used together with + `do_phase_correction` + + Options: + - 0: no correction [ default ] + - 1: do this correction + */ + int do_nac_phase_correction; + /** State tracking algorithm: @@ -311,7 +309,7 @@ class dyn_control_params{ - 5: DISH - 6: MASH - 7: FSSH2 - - 8: FSSH3 - experimental + - 8: FSSH3 */ int tsh_method; @@ -335,7 +333,7 @@ class dyn_control_params{ - 32: accept hops with the probability taken from the classical Maxwell-Boltzmann distribution - 33: accept hops with the probability taken from the updated quantum Boltzmann distribution (experimental) - - 40: based on possibility to conserve energy using tcnbra_ekin variables (experimental for TC-NBRA) + - 40: based on possibility to conserve energy using tcnbra_ekin variables (for TC-NBRA) */ int hop_acceptance_algo; @@ -426,7 +424,8 @@ class dyn_control_params{ int fssh3_approach_option; /** - The matrix decomposition method for solving the least-squares problem + The matrix decomposition method for solving the least-squares problem. + In the present implementation, is not used. Options: - 0: bdcSvd @@ -439,7 +438,7 @@ class dyn_control_params{ /** The time-step of the optimization procedure in the FSSH3 calculations - Default: 0.001 + Default: 0.001 a.u. */ double fssh3_dt; @@ -608,78 +607,110 @@ class dyn_control_params{ /** - MATRIX(ndof, 1) of (initial) wave packet widths for the decoherence from SHXF & MQCXF - [ default : NULL ] + MATRIX(ndof, 1) Widths of all DOFs for Gaussian function as an + approximation to adiabatic wave packets. According to the choice of the Gaussian width approximation, + this parameter has different meanings: + + - A constant width in the fixed-width approximation, that is, `use_td_width == 0` + - The initial width in the free-particle Gaussian wave packet approximation, that is, `use_td_width == 1` + - The interaction width in the Schwarz scheme, that is, `use_td_width == 2` + - No influence on the dynamics since the width will be determined by internal variables in the Subotnik scheme, + that is, `use_td_width == 3` + + Only used with independent-trajectory XF methods, that is, `decoherence_algo == 5 or 6` + [ default : NULL ] */ MATRIX* wp_width; /** - MATRIX(ndof, 1) of wave packet velocities for the decoherence from SHXF & MQCXF - This value is applied when use_td_width = 1 - [ default : NULL ] + MATRIX(ndof, 1) The velocity of Gaussian wave packet in the free-particle Gaussian approximation, + that is, `use_td_width == 1` Only used with independent-trajectory XF methods, that is, + with `decoherence_algo == 5 or 6` [ default : NULL ] */ MATRIX* wp_v; /** - The criterion whether the electronic state is in a coherence - [ default : 0.01 ] + A population threshold for creating/destroying auxiliary trajectories. [ default: 0.01 ]. + Only used with independent-trajectory XF methods, that is, with `decoherence_algo == 5 or 6` */ double coherence_threshold; /** - The masking parameter for computing nabla phase vectors in the MQCXF - [ default : 0.0001 ] + The masking parameter for computing nabla phase vectors. [ default: 0.0001 Ha ] + Only used with the MQCXF method, that is, `decoherence_algo == 5` */ double e_mask; /** - Whether to use the decoherence force in MQCXF - The corresponding electronic propagation is adjusted for the energy conservation - [ default : 0 ] + Whether to use the decoherence force in MQCXF. The corresponding electronic propagation + is adjusted for the energy conservation. Only used with `decoherence_algo == 6` + + Options: + - 0: don't use it, so for XF-methods this is only Ehrenfest-like force; EhXF [ default ] + - 1: The whole force including the XF-correction; MQCXF + */ int use_xf_force; /** - Whether to project out the density on an auxiliary trajectory when its motion is classically forbidden - [ default : 0 ] + Whether to project out the density on an auxiliary trajectory when its motion is classically + forbidden. Only used with independent-trajectory XF methods, that is, `decoherence_algo == 5 or 6` + + Options: + - 0: don't [default] + - 1: do */ int project_out_aux; /** - Turning-point algorithm for auxiliary trajectories + Turning-point algorithm for auxiliary trajectories. Only used with independent-trajectory XF methods, + that is, `decoherence_algo == 5 or 6` - Options: - - 0: no treatment of a turning point - - 1: collapse to the active state [default] - - 2: fix auxiliary positions of adiabatic states except for the active state - - 3: keep auxiliary momenta of adiabatic states except for the active state + Options: + - 0: no treatment of a turning point + - 1: collapse to the active state [default] + - 2: fix auxiliary positions of adiabatic states except for the active state + - 3: keep auxiliary momenta of adiabatic states except for the active state */ int tp_algo; /** - Whether to use the td Gaussian width for the nuclear wave packet approximation - This option can be considered when it comes to unbounded systems. - This approximation is based on a nuclear wave packet on a free surface: + Whether to use the td Gaussian width for the nuclear wave packet approximation + This option can be considered when it comes to unbounded systems. + This approximation is based on a nuclear wave packet on a free surface: \sigma_x(t)=\sqrt[\sigma_x(0)^2 + (wp_v * t)^2] - [ default : 0 ] + Only used with independent-trajectory XF methods, that is, `decoherence_algo == 5 or 6` + + Options: + - 0: no td width; use the fixed-width Gaussian approximation [ default ] + - 1: the td Gaussian width from a free particle Gaussian wave packet, \sigma(t)=\sqrt[\sigma(0)^2 + (wp_v * t)^2] + - 2: the Schwarz scheme where the width depends on the instantaneous de Broglie + wavelength, \sigma(t)^(-2) = [\sigma(0)^2 * P/ (4 * PI) ]^2 + - 3: the Subotnik scheme where the width is given as a sum of pairwise widths depending on the auxiliary + variables, \sigma_ij(t)^2 = |R_i - R_j| / |P_i - P_j| + */ int use_td_width; + ///=============================================================================== + ///================= NBRA options ========================================= + ///=============================================================================== + /** - A flag for NBRA calculations. Since in NBRA, the Hamiltonian is the same for all the trajectories - we can only compute the Hamiltonian related properties once for one trajectory and increase the speed of calculations. - If we set the value to 1 it will consider the NBRA type calculations and other integers the Hamiltonian related properties - are computed for all trajectories. + A flag for NBRA calculations. Since in NBRA, the Hamiltonian is the same for all the trajectories + we can only compute the Hamiltonian related properties once for one trajectory and increase the speed of calculations. + If we set the value to 1 it will consider the NBRA type calculations and other integers the Hamiltonian related properties + are computed for all trajectories. - Options: + Options: - 0: no NBRA - Hamiltonians for all trajectories are computed explicitly [ default ] - 1: the NBRA is involved - the calculations of the Hamiltonian are conducted for only 1 trajectory, and re-used by all other SH trajectories. @@ -860,8 +891,11 @@ class dyn_control_params{ If set to True (1), we will force the reprojection matrix T_new to be the identity matrix. This effectively removes basis-reprojection (local diabatization) approach and turns on the "naive" approach where no trivial crossings exist. - Default: No (0) - we do want to use the LD approaches by default. - If Yes (1), one may need to turn on additional state tracking and phase correction methods + + Options: + + - 0: No - we do want to use the LD approaches by default. [ default] + - 1: Yes - one may need to turn on additional state tracking and phase correction methods */ int assume_always_consistent; diff --git a/src/dyn/dyn_ham.cpp b/src/dyn/dyn_ham.cpp index d947e7eff..b3c10cbf2 100644 --- a/src/dyn/dyn_ham.cpp +++ b/src/dyn/dyn_ham.cpp @@ -207,7 +207,7 @@ void update_Hamiltonian_variables(dyn_control_params& prms, dyn_variables& dyn_v }// for nac_update_method == 2 - if(1){ // Experimental option to fix the phase of NACs: + if(prms.do_nac_phase_correction==1){ // Experimental option to fix the phase of NACs: for(int traj=0; trajadiabatic according to internal diagonalization [ default ] + - 2: diabatic->adiabatic according to internally stored basis transformation matrix + - 3: adiabatic->diabatic according to internally stored basis transformation matrix + - 4: adiabatic->diabatic according to local diabatization method * **dyn_params["rep_sh"]** ( int ): selects the representation which is @@ -99,8 +102,11 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): * **dyn_params["rep_lz"]** ( int ): The representation to compute LZ probabilitieis: - - 0: diabatic [ default ] - - 1: adiabatic + - 0: diabatic, Eq. 1 of the Belyaev-Lebedev paper [ default ] + - 1: adiabatic, Eq. 3 of the Belyaev-Lebedev paper, crossing point is determined + by the sign change of the diabatic gap + - 2: adiabatic, Eq. 3 of the Belyaev-Lebedev paper, crossing point is determined + by the sign change of the NAC * **dyn_params["rep_force"]** ( int ): In which representation to compute forces. @@ -118,13 +124,29 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): - 0: don't compute forces at all - e.g. we do not really need them - 1: state-specific as in the TSH or adiabatic (including adiabatic excited states) [ default ] - 2: Ehrenfest + - 3: QTSH force + + + * **dyn_params["enforce_state_following"]** ( int ): Wheather we want to enforce nuclear dynamics + to be on a given state, regardlenss of the TSH transitions. Note: only matters is `force_method == 1` + + - 0: no [ default ] + - 1: yes + + + * **dyn_params["enforced_state_index"]** (int): If we enforce the nuclear dynamics to be on a given state, + what is the index of that state [any integer >- 0, default = 0 ] + + The default value of 0 enforces the nuclear dynamics to be on the ground state. + This is a convenient way of doing NBRA calculations with model systems without the need for + pre-computing the trajectories * **dyn_params["time_overlap_method"]** ( int ): the flag to select how the time-overlaps are computed: - - 0: on-the-fly, based on the wavefunction ("basis_transform") [ default ] - - 1: read externally ( via "time_overlap_adi" ), use this option with the NBRA, but don't forget - to set up its update in the Hamiltonian update functions (aka "compute_model") + - 0: don't compute it (perhaps because it was already pre-computed or read) + - 1: explicitly compute it from the wavefunctions (the Hamiltonian shall have the basis_transform + variables updated) [ default ] * **dyn_params["nac_update_method"]** ( int ): How to update NACs and vibronic Hamiltonian before @@ -132,6 +154,28 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): - 0: don't update them (e.g. for simplest NAC) - 1: update according to changed momentum and existing derivative couplings [ default ] + - 2: update according to time-overlaps (only time-derivative NACs) + + + * **dyn_params["nac_algo"]** ( int ): How to compute time-derivative NACs + + - -1: don't, e.g. we use NACs from somewhere else [ default ] + - 0: use HST formula (if nac_update_method==2) + - 1: use NPI of Meek and Levine (if nac_update_method==2) + + + * **dyn_params["hvib_update_method"]** ( int ): How to update Hvib + + - 0: don't update them (e.g. if it is read externally) + - 1: update according to regular formula: Hvib = Ham - i * hbar * NAC [ default ] + + + * **dyn_params["do_ssy"]** ( int ): Whether to modify the Hamiltonian in the dynamics according + the Shenvi-Subotnik-Yang (SSY) method, see my Chapter Eq. 3.27 Note, that this is only applied + in the adiabatic representation + + - 0: don't [ default ] + - 1: do * **dyn_params["do_phase_correction"]** ( int ): the algorithm to correct phases on adiabatic states @@ -145,14 +189,24 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): care about the phase, but if it is not, then this parameter sets out threshold for when we do. [ default: 1e-3 ] + * **dyn_params["do_nac_phase_correction"]** ( int ): New phase correction, directly applied to NACs. + Intended to be used mostly with state_tracking_algo == 4, although can be useful with other state + tracking algorithms. Should not be used together with `do_phase_correction` + + - 0: no correction [ default ] + - 1: do this correction + + * **dyn_params["state_tracking_algo"]** ( int ): the algorithm to keep track of the states' identities + - -1: use LD approach, it includes phase correction too [ default ] - 0: no state tracking - - 1: Sato - - 2: using the mincost, Munkres-Kuhn [ default ] + - 1: method of Kosuke Sato (may fail by getting trapped into an infinite loop) + - 2: Munkres-Kuhn (Hungarian) algorithm - 3: experimental stochastic algorithm, the original version with elimination (known problems) - 32: experimental stochastic algorithms with all permutations (too expensive) - 33: the improved stochastic algorithm with good scaling and performance, on par with the mincost + - 4: new, experimental force-based tracking * **dyn_params["MK_alpha"]** ( double ): Munkres-Kuhn alpha @@ -164,16 +218,19 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): - 0: no extra output [ default ] - 1: print details for debugging + * **dyn_params["convergence"]** ( int ): A swtich for stochastic reordering algorithm 3 (and variants) to choose what happens when an acceptable permutation isn't generated in the set number of attempts: - 0: returns the identity permutation (does not require convergence) [ default ] - 1: exits and prints an error (requires convergence) + * **dyn_params["max_number_attempts"]** ( int ): The maximum number of hops that an be attempted before either choosing the identity or exiting in stochastic reordering algorithm 3 (and variants). Default: 100 + * **dyn_params["min_probability_reordering"]** ( double ): The probability threshold for stochastic state reordering algorithm. If a probability for a multi-state stransition is below this value, it will be disregarded and set to 0 @@ -185,14 +242,19 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): ///================= Surface hopping: proposal, acceptance ======================= ///=============================================================================== - - * **dyn_params["tsh_method"]** ( int ): Formula for computing SH probabilities: + * **dyn_params["tsh_method"]** ( int ): Surface hop proposal methodology: - - -1: adiabatic - no hops [ default ] - - 0: FSSH - - 1: GFSH - - 2: MSSH - - 3: DISH + - [-1]: adiabatic dynamics, no hops [ default ] + - 0: Fewest Switches Surface Hopping (FSSH) + - 1: Global Flux Surface Hopping (GFSH) + - 2: Markov-State Surface Hopping (MSSH) + - 3: Landau-Zener (LZ) options + - 4: Zhu-Nakamura (ZN) options + - 5: DISH + - 6: MASH + - 7: FSSH2 + - 8: FSSH3 + * **dyn_params["hop_acceptance_algo"]** ( int ): Options to control the acceptance of the proposed hops: @@ -211,6 +273,8 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): - 32: accept hops with the probability taken from the classical Maxwell-Boltzmann distribution - 33: accept hops with the probability taken from the updated quantum Boltzmann distribution (experimental) + - 40: based on possibility to conserve energy using tcnbra_ekin variables (for TC-NBRA) + * **dyn_params["momenta_rescaling_algo"]** ( int ): Options to control nuclear momenta changes upon successful or frustrated hops. @@ -227,6 +291,18 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): - 210: along difference of state-specific forces, don't reverse on frustrated hops - 211: along difference of state-specific forces, reverse on frustrated hops + - 40: does not rescale velocities, but rescales tcnbra_ekin variables + + + * **dyn_params["use_Jasper_Truhlar_criterion"]** ( int ): A flag to turn on/off the Jasper-Truhlar criterion + for reversal of velocities on frustrated hops. According to: Jasper, A. W.; Truhlar, D. G. Chem. Phys. Lett. + 2003, 369, 60− 67 + + - 0: don't use this criterion (naive handling) + - 1: use it [ default ] - the velocities are reversed along the direction d_{a,j} if + a) (F_a * d_{a,j}) * (F_j * d_{a,j}) < 0 and b) (v * d_{a,j}) * (F_j * d_{a,j}) < 0 + where a - is the active state index; Only in effect, if `momenta_rescaling_algo == 201` + * **dyn_params["use_boltz_factor"]** ( int ): A flag to scale the proposed hopping probabilities by the Boltzmann factor. This is needed for the libra_py/workflows/nbra calculations, where the hop proposal @@ -236,25 +312,84 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): - 1: do scale + //=========== FSSH2 options ========== + + * **dyn_params["fssh2_revision"]** ( int ): Whether to use the revised FSSH2 + + - 0: use the FSSH2 as it was formulated in the original paper [ default ] + - 1: apply the revised version + + + //=========== FSSH3 options ========== + + * **dyn_params["fssh3_size_option"]** ( int ): The size of the vectorized density matrix in equations + to determine hopping probabilites/fluxes + + - 0: N elements - only populations; the matrices are overdetermined + - 1: N^2 elements - first N elements are populations, then Re and Im parts of upper-triangular coherences + that is rho_{0,1}, rho_{0,2}, ... rho_{0,N-1}, rho_{1,2}, ... rho_{1,N-1}, ... rho_{N-2,N-1} [ default ] + + + * **dyn_params["fssh3_approach_option"]** ( int ): The approach to determine the hopping probabilities + + - 0: based on master equation, rho(t+dt) = J * rho(t); J matrix contains hopping probabilities directly [ defualt ] + - 1: based on kinetic approach, drho/dt = J * rho; J matrix contains fluxes + + + * **dyn_params["fssh3_decomp_option"]** ( int ): The matrix decomposition method for solving the least-squares problem. + In the present implementation, is not used + + - 0: bdcSvd + - 1: fullPivLu + - 2: fullPivHouseholderQr + - 3: completeOrthogonalDecomposition [ default ] + + * **dyn_params["fssh3_dt"]** ( double ): The time-step of the optimization procedure in the FSSH3 calculations + Default: 0.001 a.u. + + + * **dyn_params["fssh3_max_steps"]** ( int ): The maximal number of steps in the FSSH3 optimization step + Default: 1000 + + + * **dyn_params["fssh3_err_tol"]** ( double ): FSSH3 error tolerance + Default: 1e-7 + + //=========== QTSH options ========== + + * **dyn_params["use_qtsh"]** ( int ): Whether to use QTSH + + - 0: don't apply [ default ] + - 1: use it + ///=============================================================================== ///================= Decoherence options ========================================= ///=============================================================================== - * **dyn_params["decoherence_algo"]** ( int ): selector of the method to incorporate decoherence: + * **dyn_params["decoherence_algo"]** ( int ): Selector of the method to incorporate decoherence. - [-1]: no decoherence [ default ] - 0: SDM and alike - 1: instantaneous decoherence options (ID-S, ID-A, ID-C) + - 2: AFSSH + - 3: BCSH of Linjun Wang + - 4: MF-SD of Bedard-Hearn, Larsen, Schwartz + - 5: SHXF of Min + - 6: MQCXF + - 7: DISH, rev2023 + - 8: diabatic IDA, experimental - * **dyn_params["sdm_norm_tolerance"]** ( double ): Corresponds to the "tol" parameter in the sdm function. It controls - how much the norm of the old state can be larger than 1.0 before the code stops with the error message [ default : 0.0 ] + * **dyn_params["sdm_norm_tolerance"]** ( double ): Corresponds to the "tol" parameter in the sdm function. + It controls how much the norm of the old state can be larger than 1.0 before the code stops + with the error message [ default : 0.0 ] Note: only matters if decoherence_algo == 0 - * **dyn_params["dish_decoherence_event_option"]** ( int ): Selects the how to sample decoherence events in the DISH: + * **dyn_params["dish_decoherence_event_option"]** ( int ): Selects the how to sample decoherence + events in the DISH. Note: only matters if dyn_params["tsh_method"] == 5 - 0: compare the coherence time counter with the decoherence time (simplified DISH) - 1: compare the coherence time counter with the time drawn from the exponential distribution @@ -262,19 +397,29 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): the statistics of wait times between the Poisson-distributed events (decoherence) This is what the original DISH meant to do [ default ] - Note: only matters if dyn_params["tsh_method"] == 3 - * **dyn_params["decoherence_times_type"]** ( int ): Type of dephasing times/rates calculation: - - 0: use the rates read out from the input, need `decoherence_rates` input [default] + - -1: set all dephasing rates to zero [ default ] + - 0: use the rates read out from the input - 1: use the energy-based decoherence method (EDC) + - 2: Schwartz - mean-field Force-based decoherence + - 3: Schwartz - pair-wise-based decoherences + + * **dyn_params["schwartz_decoherence_inv_alpha"]** ( MATRIX(ndof, 1) ): a matrix of 1/alpha - the parameters + used in GWP in computing decoherence rates [ default: NULL ] - * **dyn_params["decoherence_C_param"]** ( double ): An empirical parameter used in the EDC method: [ default: 1.0 Ha] + * **dyn_params["schwartz_interaction_width"]** ( MATRIX(ndof, 1) ): the parameters for the spatial extent + of NAC in computing decoherence rates [ default: NULL ] + + + * **dyn_params["decoherence_C_param"]** ( double ): An empirical parameter used in the EDC method [ default: 1.0 Ha] + + + * **dyn_params["decoherence_eps_param"]** ( double ): An empirical parameter used in the EDC method [ default: 0.1 Ha] - * **dyn_params["decoherence_eps_param"]** ( double ): An empirical parameter used in the EDC method: [ default: 0.1 Ha] * **dyn_params["dephasing_informed"]** ( int ): A flag to apply the dephasing-informed approach of Sifain et al. to correct dephasing times: @@ -283,12 +428,17 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): - 1: use it, will need also the `ave_gaps` input - * **dyn_params["instantaneous_decoherence_variant"]** ( int ): Option to control the instantaneous decoherence methodology, - only used with `decoherence_algo == 1` + * **dyn_params["instantaneous_decoherence_variant"]** ( int ): Option to control the instantaneous + decoherence methodology, only used with decoherence_algo == 1 - 0: ID-S - - 1: ID-A [ default ] - - 2: ID-C - consistent ID (experimental) + - 1: ID-A [default] - if the proposed hop is not successful, we project back to the initial state + if the proposed hop is accepted - we project onto that state + - 2: ID-C - consistent ID - an experimental algorithm + - 3: ID-A, new: if the proposed hop is not successful, we project out the proposed states + if the proposed hop is accepted - we project onto that state + - 4: ID-F, new: if the proposed hop is not successful, we project out the proposed states + but we don't do anything if the hop is successful * **dyn_params["collapse_option"]** ( int ): How to collapse wavefunction amplitudes in the decoherence schemes: @@ -300,41 +450,51 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): * **dyn_params["decoherence_rates"]** ( MATRIX(ntates, nstates) ): the matrix of dephasing rates [ units: a.u. of time ^-1 ] - * **dyn_params["ave_gaps"]** ( MATRIX(ntates, nstates) ): A matrix that contains the averaged moduli of the energy gaps: - E_ij = <|E_i - E_j|>. It is needed when dephasing_informed option is used + * **dyn_params["ave_gaps"]** ( MATRIX(ntates, nstates) ): A matrix that contains the averaged + moduli of the energy gaps: E_ij = <|E_i - E_j|>. It is needed when dephasing_informed option is used - * **dyn_params["wp_width"]** ( MATRIX(ndof, 1) ): A width of a Gaussian function as an approximation to adiabatic wave packets. - According to the choice of the Gaussian width approximation, this parameter has different meanings + * **dyn_params["wp_width"]** ( MATRIX(ndof, 1) ): Widths of all DOFs for Gaussian function as an + approximation to adiabatic wave packets. According to the choice of the Gaussian width approximation, + this parameter has different meanings: + - A constant width in the fixed-width approximation, that is, `use_td_width == 0` - The initial width in the free-particle Gaussian wave packet approximation, that is, `use_td_width == 1` - The interaction width in the Schwarz scheme, that is, `use_td_width == 2` - - No influence on the dynamics since the width will be determined by internal variables in the Subotnik scheme, that is, `use_td_width == 3` + - No influence on the dynamics since the width will be determined by internal variables in the Subotnik scheme, + that is, `use_td_width == 3` Only used with independent-trajectory XF methods, that is, `decoherence_algo == 5 or 6` - * **dyn_params["wp_v"]** ( MATRIX(ndof,1) ): The velocity of Gaussian wave packet in the free-particle Gaussian approximation, that is, `use_td_width == 1` - Only used with independent-trajectory XF methods, that is, `decoherence_algo == 5 or 6` + * **dyn_params["wp_v"]** ( MATRIX(ndof,1) ): The velocity of Gaussian wave packet in the free-particle Gaussian + approximation, that is, `use_td_width == 1` Only used with independent-trajectory XF methods, that is, + with `decoherence_algo == 5 or 6` - * **dyn_params["coherence_threshold"]** ( double ): A population threshold for creating/destroying auxiliary trajectories. [ default: 0.01 ] - Only used with independent-trajectory XF methods, that is, `decoherence_algo == 5 or 6` + * **dyn_params["coherence_threshold"]** ( double ): A population threshold for creating/destroying auxiliary + trajectories. [ default: 0.01 ]. Only used with independent-trajectory XF methods, that is, with + `decoherence_algo == 5 or 6` * **dyn_params["e_mask"]** ( double ): The masking parameter for computing nabla phase vectors. [ default: 0.0001 Ha ] Only used with the MQCXF method, that is, `decoherence_algo == 5` - * **dyn_params["use_xf_force"]** (int): Whether to use the XF-based force. + * **dyn_params["use_xf_force"]** (int): Whether to use the decoherence force in MQCXF. + The corresponding electronic propagation is adjusted for the energy conservation. Only used with `decoherence_algo == 6` - - 0: Only Ehrenfest-like force; EhXF [ default ] + - 0: don't use it, so for XF-methods this is only Ehrenfest-like force; EhXF [ default ] - 1: The whole force including the XF-correction; MQCXF - * **dyn_params["project_out_aux"]** (int): Whether to project out the density on an auxiliary trajectory when its motion is classically forbidden. [ default: 0] - Only used with independent-trajectory XF methods, that is, `decoherence_algo == 5 or 6` + * **dyn_params["project_out_aux"]** (int): Whether to project out the density on an auxiliary + trajectory when its motion is classically forbidden. Only used with independent-trajectory XF methods, + that is, `decoherence_algo == 5 or 6` + + - 0: don't [default] + - 1: do * **dyn_params["tp_algo"]** (int): Turning-point algorithm for auxiliary trajectories @@ -346,20 +506,37 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): - 3: keep auxiliary momenta of adiabatic states except for the active state - * **dyn_params["use_td_width"]** (int): Options for the td Gaussian width approximations [ default : 0 ] + * **dyn_params["use_td_width"]** (int): Whether to use the td Gaussian width for the nuclear + wave packet approximation. This option can be considered when it comes to unbounded systems. + This approximation is based on a nuclear wave packet on a free surface: + \sigma_x(t)=\sqrt[\sigma_x(0)^2 + (wp_v * t)^2] Only used with independent-trajectory XF methods, that is, `decoherence_algo == 5 or 6` - - 0: no td width; use the fixed-width Gaussian approximation + - 0: no td width; use the fixed-width Gaussian approximation [ default ] - 1: the td Gaussian width from a free particle Gaussian wave packet, \sigma(t)=\sqrt[\sigma(0)^2 + (wp_v * t)^2] - - 2: the Schwarz scheme where the width depends on the instantaneous de Broglie wavelength, \sigma(t)^(-2) = [\sigma(0)^2 * P/ (4 * PI) ]^2 - - 3: the Subotnik scheme where the width is given as a sum of pairwise widths depending on the auxiliary variables, \sigma_ij(t)^2 = |R_i - R_j| / |P_i - P_j| + - 2: the Schwarz scheme where the width depends on the instantaneous de Broglie + wavelength, \sigma(t)^(-2) = [\sigma(0)^2 * P/ (4 * PI) ]^2 + - 3: the Subotnik scheme where the width is given as a sum of pairwise widths depending on the auxiliary + variables, \sigma_ij(t)^2 = |R_i - R_j| / |P_i - P_j| + + ///=============================================================================== + ///================= NBRA options ========================================= + ///=============================================================================== + + * **dyn_params["isNBRA"]** (int): A flag for NBRA calculations. Since in NBRA, the Hamiltonian is + the same for all the trajectories we can only compute the Hamiltonian related properties once for one + trajectory and increase the speed of calculations. If we set the value to 1 it will consider the NBRA + type calculations and other integers the Hamiltonian related properties are computed for all trajectories. + + - 0: no NBRA - Hamiltonians for all trajectories are computed explicitly [ default ] + - 1: the NBRA is involved - the calculations of the Hamiltonian are conducted for only 1 trajectory, + and re-used by all other SH trajectories. ///=============================================================================== ///================= Entanglement of trajectories ================================ ///=============================================================================== - * **dyn_params["entanglement_opt"]** ( int ): A selector of a method to couple the trajectories in this ensemble: - 0: no coupling across trajectories [ default ] @@ -377,9 +554,20 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): ///=============================================================================== - ///================= Bath, Constraints, and Dynamical controls =================== + ///================= QTAG parameters ============================================= ///=============================================================================== + * **dyn_params["qtag_pot_approx_method"]** ( int ): How to approximate the Hamiltonian matrix elements + for trajectories that belong to different (or same) surfaces + + - 0 : BAT [ default ] + - 1 : LHA + - 2 : LHAe + - 3 : BATe + + ///=============================================================================== + ///================= Bath, Constraints, and Dynamical controls =================== + ///=============================================================================== * **dyn_params["Temperature"]** ( double ): Temperature of the system. This parameter could be used even in the NVE simulations e.g. as a parameters to compute hop @@ -420,6 +608,93 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): timesteps [ units: a.u. of time, default: 41.0 a.u. = 1 fs ] + * **dyn_params["num_electronic_substeps"]** ( int ): the number of electronic integration substeps per + a nuclear step, such that dt_el = dt_nucl / num_electronic_substeps + + * **dyn_params["electronic_integrator"]** ( int ): the method for electronic TD-SE integration: + + rep_tdse = 0 (diabatic): 1** - with NBRA + + -1 - No propagation + + 0 - Lowdin exp_ with 2-point Hvib_dia + 1 - based on QTAG propagator + 2 - based on modified QTAG propagator (Z at two times) + 3 - non-Hermitian integrator with 2-point Hvib_dia + + rep_tdse = 1 (adiabatic): 1** - with NBRA + + -1 - No propagation + + 0 - ld, with crude splitting, with exp_ [ default ] + 1 - ld, with symmetric splitting, with exp_ + 2 - ld, original, with exp_ + 3 - 1-point, Hvib integration, with exp_ + 4 - 2-points, Hvib integration, with exp_ + 5 - 3-points, Hvib, integration with the second-point correction of Hvib, with exp_ + 6 - same as 4, but without projection matrices (T_new = I) + + 10 - same as 0, but with rotations + 11 - same as 1, but with rotations + 12 - same as 2, but with rotations + 13 - same as 3, but with rotations + 14 - same as 4, but with rotations + 15 - same as 5, but with rotations + + rep_tdse = 2 ( diabatic, density matrix formalism): 1** - with NBRA + + 0 - mid-point Hvib with the second-point correction of Hvib + + rep_tdse = 3 ( adiabatic, density matrix formalism): 1** - with NBRA + + 0 - mid-point Hvib with the second-point correction of Hvib + 1 - Zhu Liouvillian + + 10 - same as 0, but with rotations + + + * **dyn_params["ampl_transformation_method"]** (int): Whether transform the amplitudes by the T + transformation matrix: + + - 0: do not transform by the T matrix (naive, but potentially correct approach) + - 1: do transform it (as in LD, but maybe not needed if we directly transform basis) + + + * **dyn_params["assume_always_consistent"]** (int): If set to True (1), we will force the reprojection matrix + T_new to be the identity matrix. This effectively removes basis-reprojection (local diabatization) approach + and turns on the "naive" approach where no trivial crossings exist. + + - 0: No - we do want to use the LD approaches by default. [ default] + - 1: Yes - one may need to turn on additional state tracking and phase correction methods + + + * **dyn_params["thermally_corrected_nbra"]** (int): Flag setting to use the thermal correction to NBRA: + + - 0: No [default] + - 1: Yes, rescale NACs + + + * **dyn_params["total_energy"]** (double): Total energy of the system - should be set according to the + initial condition of the experiment. Used by the nbra rescaling approach (experimental method) [ units: a.u. ] + [Default = 0.01 a.u.] + + + * **dyn_params["tcnbra_nu_therm"]** (double): Frequency of the auxiliary thermostats in the TC-NBRA method + [Default = 0.001] + + + * **dyn_params["tcnbra_nhc_size"]** (int): Length of the auxiliary NHC thermostat in the TC-NBRA method [units: unitless] + [Default = 1] + + + * **dyn_params["tcnbra_do_nac_scaling"]** (int): Whether to rescale NACs to reflect the fact that the + instantaneous velocities may be different from those in the ground state sampling. This would rescale off-diagonal + elements of NAC, Hvib, and time-overlap matrices. Does not rescale derivative coupling vectors cause the chances + are - they won't be used in NBRA or if they are used, it is not NBRA anymore. + + - 0: - do not rescale NACs etc. + - 1: - do rescale them [ default ] + ///=============================================================================== ///================= Variables specific to Python version: saving ================ ///=============================================================================== @@ -484,6 +759,7 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): "hvib_adi", "hvib_dia", "St", "basis_transform", "projector" ] + ham (nHamiltonian): object controlling Hamiltonian-related calculations compute_model ( PyObject ): the pointer to the Python function that performs the Hamiltonian calculations @@ -493,38 +769,8 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): rnd ( Random ): random numbers generator object - Returns: - tuple: with the elements of the tuple listed below. - - * obs_T ( list of `nsteps` doubles ): time [units: a.u.] - * obs_q ( list of `nsteps` MATRIX(nnucl, ntraj) ): coordinates of all trajectories [ units: Bohr ] - * obs_p ( list of `nsteps` MATRIX(nnucl, ntraj) ): momenta of all trajectories [ units: a.u. ] - * obs_Ekin ( list of `nsteps` doubles ): average kinetic energy of an ensemble of trajectories [units: a.u.] - * obs_Epot ( list of `nsteps` doubles ): average potential energy of an ensemble of trajectories [units: a.u.] - * obs_Etot ( list of `nsteps` doubles ): average total energy of an ensemble of trajectories [units: a.u.] - * obs_dEkin ( list of `nsteps` doubles ): standard deviation of kinetic energy of an ensemble of trajectories [units: a.u.] - * obs_dEpot ( list of `nsteps` doubles ): standard deviation of potential energy of an ensemble of trajectories [units: a.u.] - * obs_dEtot ( list of `nsteps` doubles ): standard deviation of total energy of an ensemble of trajectories [units: a.u.] - * obs_Cadi ( list of `nsteps` CMATRIX(nadi, ntraj) ): amplitudes of adiabatic electronic states of all trajectories - * obs_Cdia ( list of `nsteps` CMATRIX(ndia, ntraj) ): amplitudes of diabatic electronic states of all trajectories - * obs_dm_adi ( list of `nsteps` CMATRIX(nadi, nadi) ): ensemble-averaged density matrix in adiabatic basis - * obs_dm_dia ( list of `nsteps` CMATRIX(ndia, ndia) ): ensemble-averaged density matrix in diabatic basis - * obs_pop ( list of `nsteps` MATRIX(nadi, 1) ): ensemble-averaged TSH populations of adiabatic states - * obs_states ( list of `nsteps` of lists of `ntraj` ints): # indices of the quantum states of each trajectory - * obs_hvib_adi ( list of `ntraj` lists, each being a list of `nsteps` objects CMATRIX(nadi, nadi) ): trajectory-resolved - vibronic Hamiltonians for each timestep in the adiabatic representation - * obs_hvib_dia ( list of `ntraj` lists, each being a list of `nsteps` objects CMATRIX(ndia, ndia) ): trajectory-resolved - vibronic Hamiltonians for each timestep in the diabatic representation - * obs_St ( list of `ntraj` lists, each being a list of `nsteps` objects CMATRIX(nadi, nadi) ): trajectory-resolved - time-overlaps of the adiabatic wavefunctions - * obs_U ( list of `ntraj` lists, each being a list of `nsteps` objects CMATRIX(ndia, nadi) ): trajectory-resolved - diabatic-to-adiabatic transformation matrix - - - .. note:: - the elements are None, if they are excluded by the input options - + None: saves the data to files """ @@ -541,12 +787,13 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): critical_params = [ ] default_params = {} #================= Computing Hamiltonian-related properties ==================== - default_params.update( { "rep_tdse":1, "rep_ham":0, "ham_update_method":1, "ham_transform_method":1, + default_params.update( { "rep_tdse":1, "ham_update_method":1, "ham_transform_method":1, "rep_sh":1, "rep_lz":0, "rep_force":1, "force_method":1, "enforce_state_following":0, "enforced_state_index":0, "time_overlap_method":0, "nac_update_method":1, "nac_algo":0, "hvib_update_method":1, "do_ssy":0, "do_phase_correction":1, "phase_correction_tol":1e-3, + "do_nac_phase_correction":0, "state_tracking_algo":-1, "MK_alpha":0.0, "MK_verbosity":0, "convergence":0, "max_number_attempts":100, "min_probability_reordering":0.0, "is_nbra":0, "icond":0, "nfiles":-1, "thermally_corrected_nbra":0, "total_energy":0.01, @@ -610,7 +857,7 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): prefix = dyn_params["prefix"] prefix2 = dyn_params["prefix2"] rep_tdse = dyn_params["rep_tdse"] - rep_ham = dyn_params["rep_ham"] + # rep_ham = dyn_params["rep_ham"] nsteps = dyn_params["nsteps"] dt = dyn_params["dt"] phase_correction_tol = dyn_params["phase_correction_tol"] @@ -648,16 +895,6 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): dyn_params["quantum_dofs"] = list(range(nnucl)) - if is_nbra == 1: - for i in range(nstates): - pass #states.append(_states[i]) - #projectors.append(CMATRIX(_projectors[0])) - else: - for i in range(nstates): - pass #states.append(_states[i]) - #projectors.append(CMATRIX(_projectors[i])) - - # Initialize savers _savers = save.init_tsh_savers(dyn_params, model_params, nsteps, ntraj, nnucl, nadi, ndia) @@ -669,16 +906,10 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): _savers["txt2_saver"].save_data_txt( F"{prefix2}", properties_to_save, "w", 0) - #sys.exit(0) model_params.update({"timestep":icond}) - #update_Hamiltonian_q(dyn_params, dyn_var, ham, compute_model, model_params) - #update_Hamiltonian_p(dyn_params, dyn_var, ham) update_Hamiltonian_variables( dyn_params, dyn_var, ham, ham, compute_model, model_params, 0) - #sys.exit(0) update_Hamiltonian_variables( dyn_params, dyn_var, ham, ham, compute_model, model_params, 1) - - therm = ThermostatList(); if ensemble==1: for traj in range(ntraj): @@ -713,23 +944,8 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): # Energies Ekin, Epot, Etot, dEkin, dEpot, dEtot = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 Etherm, E_NHC = 0.0, 0.0 - - """ - if is_nbra != 1: - if force_method in [0, 1]: - Ekin, Epot, Etot, dEkin, dEpot, dEtot = tsh_stat.compute_etot_tsh(ham, p, Cdia, Cadi, projectors, states, iM, rep_tdse) - elif force_method in [2]: - Ekin, Epot, Etot, dEkin, dEpot, dEtot = tsh_stat.compute_etot(ham, p, Cdia, Cadi, projectors, iM, rep_tdse) - - for bath in therm: - Etherm += bath.energy() - Etherm = Etherm / float(ntraj) - E_NHC = Etot + Etherm - - """ save.save_tsh_data_1234_new(_savers, dyn_params, i, dyn_var, ham) - #============ Propagate =========== index = i + icond if nfiles > 0: @@ -737,10 +953,6 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): model_params.update({"timestep":index}) compute_dynamics(dyn_var, dyn_params, ham, ham_aux, compute_model, model_params, rnd, therm); - - #dyn_var.update_amplitudes(dyn_params, ham); - #dyn_var.update_density_matrix(dyn_params, ham, 1); - if _savers["txt_saver"]!=None: _savers["txt_saver"].save_data_txt( F"{prefix}", properties_to_save, "a", i) @@ -748,7 +960,6 @@ def run_dynamics(dyn_var, _dyn_params, ham, compute_model, _model_params, rnd): if _savers["txt2_saver"]!=None: _savers["txt2_saver"].save_data_txt( F"{prefix2}", properties_to_save, "a", 0) - if _savers["mem_saver"]!=None: _savers["mem_saver"].save_data( F"{prefix}/mem_data.hdf", properties_to_save, "w") return _savers["mem_saver"] From 57e49cea735f70a7562dfdffe76f3a4ced3916d2 Mon Sep 17 00:00:00 2001 From: MohammadShakiba Date: Mon, 5 Aug 2024 11:54:54 -0400 Subject: [PATCH 47/48] Bug fixes for step3 many-body --- src/libra_py/packages/cp2k/methods.py | 31 +++++++ src/libra_py/packages/dftbplus/methods.py | 2 +- src/libra_py/packages/gaussian/methods.py | 2 +- src/libra_py/workflows/nbra/generate_data.py | 2 +- src/libra_py/workflows/nbra/ml_map.py | 98 +++++++++++++++++++- src/libra_py/workflows/nbra/step2.py | 2 +- src/libra_py/workflows/nbra/step3.py | 14 +-- 7 files changed, 138 insertions(+), 13 deletions(-) diff --git a/src/libra_py/packages/cp2k/methods.py b/src/libra_py/packages/cp2k/methods.py index 8714a4d33..63ddea74e 100755 --- a/src/libra_py/packages/cp2k/methods.py +++ b/src/libra_py/packages/cp2k/methods.py @@ -23,6 +23,7 @@ import re import numpy as np import scipy.sparse as sp +import scipy.linalg import time import glob from libra_py.workflows.nbra import step2_many_body @@ -2126,6 +2127,36 @@ def compute_energies_coeffs(ks_mat, overlap): eigenvectors = eigenvectors[:,sorted_indices].T + return eigenvalues[sorted_indices], eigenvectors + + +def compute_energies_coeffs_scipy(ks_mat, overlap): + """ + This function solves the general eigenvalue problem described above using a Cholesky decomposition + of the overlap matrix. The eigenvalues are sorted. + More information: https://doi.org/10.1016/j.cpc.2004.12.014 + Args: + ks_mat (numpy array): The Kohn-Sham matrix + overlap (numpy array): The atomic orbital overlap matrix + Returns: + eigenvalues (numpy array): The energies (eigenvalues) + eigenvectors (numpy array): The MO coefficients + """ + # Cholesky decomposition of the overlap matrix + U = scipy.linalg.cholesky( overlap ).T + # One ca also use the following as well but it is computationally more demanding + # U = scipy.linalg.fractional_matrix_power(S, 0.5) + U_inv = scipy.linalg.inv( U ) + UT_inv = scipy.linalg.inv( U.T ) + #K_prime = scipy.linalg.multi_dot( [UT_inv, ks_mat, U_inv] ) + K_prime = UT_inv @ ks_mat @ U_inv + eigenvalues, eigenvectors = scipy.linalg.eig( K_prime ) + # Transform back the coefficients + eigenvectors = U_inv @ eigenvectors + sorted_indices = np.argsort(eigenvalues) + eigenvectors = eigenvectors[:,sorted_indices].T + + return eigenvalues[sorted_indices], eigenvectors def compute_density_matrix(eigenvectors, homo_index): diff --git a/src/libra_py/packages/dftbplus/methods.py b/src/libra_py/packages/dftbplus/methods.py index 7021eed54..dbf9b95da 100755 --- a/src/libra_py/packages/dftbplus/methods.py +++ b/src/libra_py/packages/dftbplus/methods.py @@ -685,7 +685,7 @@ def dftb_distribute( istep, fstep, nsteps_this_job, trajectory_xyz_file, dftb_in for step in range( nsteps_this_job ): # extract the curr_step xyz coordianates from the trajectory file and write it to another xyz file - CP2K_methods.read_trajectory_xyz_file( trajectory_xyz_file, curr_step ) + _, _ = CP2K_methods.read_trajectory_xyz_file( trajectory_xyz_file, curr_step ) curr_step += 1 # Go back to the main directory diff --git a/src/libra_py/packages/gaussian/methods.py b/src/libra_py/packages/gaussian/methods.py index aeff16b0d..5346c7482 100755 --- a/src/libra_py/packages/gaussian/methods.py +++ b/src/libra_py/packages/gaussian/methods.py @@ -289,7 +289,7 @@ def gaussian_distribute( istep, fstep, nsteps_this_job, trajectory_xyz_file, gau for step in range( nsteps_this_job ): # Extract the coordinates and write them to a xyz file - CP2K_methods.read_trajectory_xyz_file( trajectory_xyz_file, curr_step ) + _, _ = CP2K_methods.read_trajectory_xyz_file( trajectory_xyz_file, curr_step ) # Now, we need to edit the gaussian_input file by adding the # coordinates to the input file diff --git a/src/libra_py/workflows/nbra/generate_data.py b/src/libra_py/workflows/nbra/generate_data.py index 56d18f74b..b99635585 100644 --- a/src/libra_py/workflows/nbra/generate_data.py +++ b/src/libra_py/workflows/nbra/generate_data.py @@ -46,7 +46,7 @@ def make_input(prefix, input_template, software, trajectory_xyz_file, step): lines_input = f.readlines() f.close() - CP2K_methods.read_trajectory_xyz_file(trajectory_xyz_file, step) + _, _ = CP2K_methods.read_trajectory_xyz_file(trajectory_xyz_file, step) if software.lower()=="cp2k": f = open(F"input_{prefix}_{step}.inp", "w") diff --git a/src/libra_py/workflows/nbra/ml_map.py b/src/libra_py/workflows/nbra/ml_map.py index 4f90b0f47..b8557b0f1 100644 --- a/src/libra_py/workflows/nbra/ml_map.py +++ b/src/libra_py/workflows/nbra/ml_map.py @@ -34,7 +34,8 @@ import matplotlib.pyplot as plt from sklearn.preprocessing import StandardScaler, MinMaxScaler from sklearn.kernel_ridge import KernelRidge -from sklearn.metrics import mean_squared_error, accuracy_score, mean_absolute_error, r2_score +from sklearn.cluster import KMeans +from sklearn.metrics import mean_squared_error, accuracy_score, mean_absolute_error, r2_score, pairwise_distances from liblibra_core import * import libra_py.packages.cp2k.methods as CP2K_methods import libra_py.packages.dftbplus.methods as DFTB_methods @@ -517,6 +518,76 @@ def find_indices_inputs(params): return list(indices) +def read_trajectory_xyz_file(file_name: str, istep: int, fstep: int): + """ + """ + f = open(file_name,'r') + lines = f.readlines() + f.close() + # The number of atoms for each time step in the .xyz file of the trajectory. + number_of_atoms = int(lines[0].split()[0]) + + # This is used to skip the first two lines for each time step. + n = number_of_atoms+2 + + # Write the coordinates of the 'step'th time step into the file + coords = [] + for step in range(istep, fstep+1): + coord = [] + for i in range( n * step + 2, n * ( step + 1 ) ): + tmp = lines[ i ].split() +# print(tmp) + x = float( tmp[1]) + y = float( tmp[2]) + z = float( tmp[3]) + coord.append([x,y,z]) + coords.append(coord) + + coords = np.array(coords) + labels = [] + for i in range(2, number_of_atoms+2): + tmp = lines[i].split() + labels.append( tmp[0]) + + return labels, coords + +def rmsd(p1, p2): + """ + Calculate RMSD between two geometries + """ + return np.sqrt(np.mean((p1 - p2)**2)) + + +def find_kmeans_indices(trajectory_file, istep, fstep, ncluster=10, random_state=0): + """ + """ + # Read the XYZ trajectory file + t1 = time.time() + labels, coords = read_trajectory_xyz_file(trajectory_file, istep, fstep) + print('Finished reading trajectory file: ', time.time()-t1) + # Vectorize the coordinates nparray + flattened_coords = coords.reshape(coords.shape[0], -1) + t1 = time.time() + rmsd_matrix = pairwise_distances(flattened_coords, metric=rmsd) + print('Finished computing the distance matrix with RMSD metric: ', time.time()-t1) + # Do the K-means clustering + t1 = time.time() + kmeans = KMeans(n_clusters=ncluster, random_state=random_state).fit(rmsd_matrix) + print(f'Finished clustering for ncluster={ncluster}: ', time.time()-t1) + clusters = kmeans.labels_ + indices = [] + for cluster_id in range(ncluster): + cluster_members = np.where(clusters == cluster_id)[0] + # Select the first member of the cluster as representative + indices.append(np.sort(cluster_members)[0]) + # Sort the indices + indices = list(np.sort(indices)) + # Print the geometries indices + print("Selected geometries indices are:", indices) + + return indices + + def rebuild_matrix_from_partitions(params, partitions, output_shape): """ This function is one of the most important here. It will @@ -598,24 +669,43 @@ def compute_properties(params, models, input_scalers, output_scalers): for i, step in enumerate(indices): print("======================== \n Performing calculations for step ", step) print("*** Generating guess Hamiltonian for step ", step) + tt = time.time() generate_data.gen_data(data_gen_params, step) + print('data generation time:', time.time()-tt, ' seconds') input_mat = np.load(f'{params["path_to_input_mats"]}/{params["prefix"]}_{params["input_property"]}_{step}.npy') if i==0: ref_mat_files = glob.glob(f'{params["path_to_output_mats"]}/{params["prefix"]}_ref_{params["output_property"]}_*.npy') #output_mat = np.load(f'{params["path_to_output_mats"]}/{params["prefix"]}_ref_{params["output_property"]}_{step}.npy') output_mat = np.load(ref_mat_files[0]) params["input_partition"] = True + tt = time.time() partitioned_input = partition_matrix(params, input_mat) + print('input partitioning time:', time.time()-tt, ' seconds') # Now apply the models to each partition + tt = time.time() outputs = [] for j in range(len(input_scalers)): input_scaled = input_scalers[j].transform(np.array(partitioned_input[j]).reshape(1,-1)) output_scaled = models[j].predict(input_scaled)#.reshape(1,-1)) output = output_scalers[j].inverse_transform(output_scaled) outputs.append(output.reshape(output.shape[1])) + print('scaling data time:', time.time()-tt, ' seconds') + tt = time.time() ks_ham_mat = rebuild_matrix_from_partitions(params, outputs, output_mat.shape) + print('rebuilding matrix from partitions time:', time.time()-tt, ' seconds') + tt = time.time() atomic_overlap = compute_atomic_orbital_overlap_matrix(params, step) + print('atomic orbital overlap calculation time:', time.time()-tt, ' seconds') + tt = time.time() + #os.environ['OMP_NUM_THREADS'] = '%d'%params['nprocs'] + #print(type(ks_ham_mat)) + #print(type(atomic_overlap)) + #np.save('k.npy', ks_ham_mat) + #np.save('s.npy', atomic_overlap) eigenvalues, eigenvectors = CP2K_methods.compute_energies_coeffs(ks_ham_mat, atomic_overlap) + #eigenvalues, eigenvectors = CP2K_methods.compute_energies_coeffs_scipy(ks_ham_mat, atomic_overlap) + #os.environ['OMP_NUM_THREADS'] = '1' + print('diagonalizing the KS Hamiltonian matrix time:', time.time()-tt, ' seconds') if params["do_error_analysis"]: if not os.path.exists("../error_data"): os.system(f"mkdir ../error_data") @@ -625,6 +715,7 @@ def compute_properties(params, models, input_scalers, output_scalers): try: ks_ham_mat_ref = np.load(f'{params["path_to_output_mats"]}/{params["prefix"]}_ref_{params["output_property"]}_{step}.npy') eigenvalues_ref, eigenvectors_ref = CP2K_methods.compute_energies_coeffs(ks_ham_mat_ref, atomic_overlap) + #eigenvalues_ref, eigenvectors_ref = CP2K_methods.compute_energies_coeffs_scipy(ks_ham_mat_ref, atomic_overlap) # We only save the eigenvalues but not the eigenvectors of the reference calculations # The first reason is because we want to plot them and then we'll do the error analysis of all # molecular orbitals. The second reason is that we compute the \epsilon_i=<\psi_{i_{ref}}|\psi_{i_{ml}}> for @@ -635,6 +726,7 @@ def compute_properties(params, models, input_scalers, output_scalers): os.system(f"mkdir {params['path_to_save_ref_mos']}") if params["save_ref_eigenvalues"]: np.save(f"{params['path_to_save_ref_mos']}/E_ref_{step}.npy", eigenvalues_ref) # [lowest_orbital-1:highest_orbital]) + np.save(f"{params['path_to_save_ref_mos']}/E_ml_{step}.npy", eigenvalues) # [lowest_orbital-1:highest_orbital]) if params["save_ref_eigenvectors"]: np.save(f"{params['path_to_save_ref_mos']}/mos_ref_{step}.npy", eigenvectors_ref) #[lowest_orbital-1:highest_orbital,:][:,lowest_orbital-1:highest_orbital]) ml_ref_overlap = compute_mo_overlaps(params, eigenvectors_ref, eigenvectors, step, step) #[lowest_orbital-1:highest_orbital,:][:,lowest_orbital-1:highest_orbital] @@ -713,10 +805,10 @@ def compute_properties(params, models, input_scalers, output_scalers): #if params["compute_total_energy"]: # we have to make a cp2k input file based on the reference input # and then run it. Then since the files are large we need to remove them - os.system("rm *.log *.npy *.wfn* *.inp *.xyz") + #os.system("rm *.log *.npy *.wfn* *.inp *.xyz") os.system("mkdir ../ml_total_energy") os.system("mv output*.out ../ml_total_energy/.") - os.system("rm *.out") + #os.system("rm *.out") #os.chdir("../") #os.system(f"rm -rf tmp_guess_ham_{params['job']}") diff --git a/src/libra_py/workflows/nbra/step2.py b/src/libra_py/workflows/nbra/step2.py index 2f9407672..449fe0d2d 100755 --- a/src/libra_py/workflows/nbra/step2.py +++ b/src/libra_py/workflows/nbra/step2.py @@ -395,7 +395,7 @@ def run_cp2k_libint_step2(params): # Now try to get parameters from the input critical_params = [ "cp2k_ot_input_template", "cp2k_diag_input_template", "trajectory_xyz_filename" ] - default_params = {"res_dir": os.getcwd() + "/res", "all_logfiles": os.getcwd() + "/all_logfiles", "all_pdosfiles": os.getcwd() + "/all_pdosfiles", "all_images": os.getcwd() + "/all_images", "image_format": 'bmp', "istep": 0, "fstep": 2, "lowest_orbital": 1, "highest_orbital": 2, "is_spherical": True, "isXTB": False, "isUKS": False, "remove_molden": True, "nprocs": 2, "cp2k_exe": "cp2k.psmp", "mpi_executable": "mpirun", "cube_visualization": False, "vmd_input_template": "vmd.tcl", "states_to_plot": [1], "plot_phase_corrected": True, "vmd_exe": "vmd", "tachyon_exe": "tachyon_LINIXAMD64", "x_pixels": 1024, "y_pixels": 1024, "remove_cube": True, 'together_mode': False} + default_params = {"res_dir": os.getcwd() + "/res", "all_logfiles": os.getcwd() + "/all_logfiles", "all_pdosfiles": os.getcwd() + "/all_pdosfiles", "all_images": os.getcwd() + "/all_images", "image_format": 'bmp', "istep": 0, "fstep": 2, "lowest_orbital": 1, "highest_orbital": 2, "is_spherical": True, "isXTB": False, "isUKS": False, "remove_molden": True, "nprocs": 2, "cp2k_exe": "cp2k.psmp", "mpi_executable": "mpirun", "cube_visualization": False, "vmd_input_template": "vmd.tcl", "states_to_plot": [1], "plot_phase_corrected": True, "vmd_exe": "vmd", "tachyon_exe": "tachyon_LINIXAMD64", "x_pixels": 1024, "y_pixels": 1024, "remove_cube": True, 'together_mode': False, 'restart_file': True} comn.check_input(params, default_params, critical_params) diff --git a/src/libra_py/workflows/nbra/step3.py b/src/libra_py/workflows/nbra/step3.py index 0d7d9080b..9ebf024dd 100755 --- a/src/libra_py/workflows/nbra/step3.py +++ b/src/libra_py/workflows/nbra/step3.py @@ -2545,11 +2545,12 @@ def run_step3_sd_nacs_libint(params): # Since we have performed state-reordering we need to # convert to scipy npz format now t2 = time.time() - for i in range(len(St_sds_cmatrix)): + for i in range(len(St_sds_cmatrix)-1): St_sds[i] = data_conv.MATRIX2scipynpz( St_sds_cmatrix[i].real() ) - sd2ci = SD2CI[i] + sd2ci_prev = SD2CI[i] + sd2ci_curr = SD2CI[i+1] # Compute the St_ci - St_ci = np.linalg.multi_dot([sd2ci.T, St_sds[i].todense().real, sd2ci]) + St_ci = np.linalg.multi_dot([sd2ci_prev.T, St_sds[i].todense().real, sd2ci_curr]) St_cis.append(sp.csc_matrix(St_ci)) # Now we need to apply state-reordering to St_cis @@ -2580,10 +2581,11 @@ def run_step3_sd_nacs_libint(params): St_cis[i] = data_conv.MATRIX2scipynpz( St_cis_cmatrix[i].real() ) else: - for i in range(len(St_sds)): - sd2ci = SD2CI[i] + for i in range(len(St_sds)-1): + sd2ci_prev = SD2CI[i] + sd2ci_curr = SD2CI[i+1] # Compute the St_ci - St_ci = np.linalg.multi_dot([sd2ci.T, St_sds[i].todense().real, sd2ci]) + St_ci = np.linalg.multi_dot([sd2ci_prev.T, St_sds[i].todense().real, sd2ci_curr]) St_cis.append(sp.csc_matrix(St_ci)) sp.save_npz(F'{params["path_to_save_sd_Hvibs"]}/St_ci_{step+start_time}_re.npz', sp.csc_matrix(St_ci)) From a79a1b208e21abbf93ed95b5771b574724569832 Mon Sep 17 00:00:00 2001 From: Alexey Akimov Date: Tue, 6 Aug 2024 01:45:42 -0400 Subject: [PATCH 48/48] Added zenodo helper json --- .zenodo.json | 239 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 .zenodo.json diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 000000000..15b9dc371 --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,239 @@ +{ + "access": { + "embargo": { + "active": false, + "reason": null + }, + "files": "public", + "record": "public", + "status": "open" + }, + "metadata": { + "creators": [ + { + "affiliations": [ + { + "id": "01y64my43", + "name": "University at Buffalo, State University of New York" + } + ], + "person_or_org": { + "family_name": "Akimov", + "given_name": "Alexey", + "name": "Akimov, Alexey", + "type": "personal" + }, + "orcid": "0000-0002-7815-3731" + }, + { + "affiliations": [ + { + "id": "01y64my43", + "name": "University at Buffalo, State University of New York" + } + ], + "person_or_org": { + "family_name": "Shakiba", + "given_name": "Mohammad", + "name": "Shakiba, Mohammad", + "type": "personal" + } + }, + { + "affiliations": [ + { + "id": "01y64my43", + "name": "University at Buffalo, State University of New York" + } + ], + "person_or_org": { + "family_name": "Smith", + "given_name": "Brendan", + "name": "Smith, Brendan", + "type": "personal" + } + }, + { + "affiliations": [ + { + "id": "02b6qw903", + "name": "University of South Carolina" + } + ], + "person_or_org": { + "family_name": "Dutra", + "given_name": "Matthew", + "name": "Dutra, Matthew", + "type": "personal" + } + }, + { + "affiliations": [ + { + "id": "01y64my43", + "name": "University at Buffalo, State University of New York" + } + ], + "person_or_org": { + "family_name": "Han", + "given_name": "Daeho", + "name": "Han, Daeho", + "type": "personal" + } + }, + { + "affiliations": [ + { + "id": "01y64my43", + "name": "University at Buffalo, State University of New York" + } + ], + "person_or_org": { + "family_name": "Sato", + "given_name": "Kosuke", + "name": "Sato, Kosuke", + "type": "personal" + } + }, + { + "affiliations": [ + { + "id": "01y64my43", + "name": "University at Buffalo, State University of New York" + } + ], + "person_or_org": { + "family_name": "Temen", + "given_name": "Story", + "name": "Temen, Story", + "type": "personal" + } + }, + { + "affiliations": [ + { + "id": "01dzed356", + "name": "Hunan Agricultural University" + } + ], + "person_or_org": { + "family_name": "Li", + "given_name": "Wei", + "name": "Li, Wei", + "type": "personal" + } + }, + { + "affiliations": [ + { + "id": "046rm7j60", + "name": "University of California, Los Angeles" + } + ], + "person_or_org": { + "family_name": "Khvorost", + "given_name": "Taras", + "name": "Khvorost, Taras", + "type": "personal" + } + }, + { + "affiliations": [ + { + "id": "02vpsdb40", + "name": "New York University Shanghai" + } + ], + "person_or_org": { + "family_name": "Sun", + "given_name": "Xiang", + "name": "Sun, Xiang", + "type": "personal" + } + }, + { + "affiliations": [ + { + "id": "02b6qw903", + "name": "University of Southern California" + } + ], + "person_or_org": { + "family_name": "Stippell", + "given_name": "Liz", + "name": "Stippell, Liz", + "type": "personal" + } + }, + { + "affiliations": [ + { + "id": "02qyf5152", + "name": "Indian Institute of Technology Bombay" + } + ], + "person_or_org": { + "family_name": "Jain", + "given_name": "Amber", + "name": "Jain, Amber", + "type": "personal" + } + } + ], + "resource_type": { + "id": "software", + "title": { + "de": "Software", + "en": "Software" + } + }, + "rights": [ + { + "description": { + "en": "Permissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights." + }, + "id": "gpl-3.0-or-later", + "props": { + "scheme": "spdx", + "url": "https://www.gnu.org/licenses/gpl-3.0-standalone.html" + }, + "title": { + "en": "GNU General Public License v3.0 or later" + } + } + ], + "subjects": [ + { + "subject": "nonadiabatic dynamics" + }, + { + "subject": "trajectory surface hopping" + }, + { + "subject": "nonadiabatic molecular dynamics" + }, + { + "subject": "molecular dynamics" + }, + { + "subject": "excited states" + }, + { + "subject": "computational chemistry" + }, + { + "subject": "charge transfer" + }, + { + "subject": "quantum dynamics" + }, + { + "subject": "solid state" + }, + { + "subject": "molecular simulations" + } + ] + } +} +