def get_nr_electrons(self): """ Get the number of electrons """ sym_list = list(data.keys()) self.nelec = 0. for iat in range(self.nat_qm): self.nelec += float(sym_list.index(self.symbols[iat])) self.nelec -= self.charge
def get_input(self, molecule, istep, bo_list, calc_force_only): """ Generate Columbus input files: geom, prepin, stdin, mcscfin, transmomin, etc :param object molecule: Molecule object :param integer istep: Current MD step :param integer,list bo_list: List of BO states for BO calculation :param boolean calc_force_only: Logical to decide whether calculate force only """ # Generate 'geom' file used in Columbus geom = "" for iat in range(molecule.nat_qm): atom_num = list(data.keys()).index(f"{molecule.symbols[iat]}") tmp_atom = f' {molecule.symbols[iat]:5s}{atom_num:7.2f}' \ + "".join([f'{molecule.pos[iat, isp]:15.8f}' for isp in range(molecule.nsp)]) \ + f'{molecule.mass[iat] / amu_to_au:15.8f}' + "\n" geom += tmp_atom file_name = "geom" with open(file_name, "w") as f: f.write(geom) # Scratch Block hf = False mcscf = True if (self.guess == "read"): if (istep == -1): if (os.path.isfile(self.guess_file)): # Copy guess file to currect directory shutil.copy(self.guess_file, os.path.join(self.scr_qm_dir, "mocoef")) restart = 1 else: restart = 0 hf = True elif (istep >= 0): # Move previous file to currect directory os.rename("../mocoef", os.path.join(self.scr_qm_dir, "mocoef")) restart = 1 elif (self.guess == "hf"): restart = 0 hf = True if (calc_force_only): restart = 1 hf = False mcscf = False shutil.copy(os.path.join(self.scr_qm_dir, "./MOCOEFS/mocoef_mc.sp"), \ os.path.join(self.scr_qm_dir, "mocoef")) # Generate new prepinp script shutil.copy(os.path.join(self.qm_path, "prepinp"), "prepinp_copy") file_name = "prepinp_copy" with open(file_name, "r") as f: prepinp = f.read() prepinp = prepinp.replace("( keys %sumformula )", "(sort keys %sumformula )", 1) file_name = "prepinp_fix" with open(file_name, "w") as f: f.write(prepinp) os.chmod("prepinp_fix", 0o755) # Generate 'prepin' file used in prepinp script of Columbus if (calc_force_only): # Here, y means overwritting of 'inpcol' file prepin = "1\ny\nc1\ngeom\n\n" else: prepin = "1\nc1\ngeom\n\n" prepin += self.basis_nums prepin += "\ny\n\n" file_name = "prepin" with open(file_name, "w") as f: f.write(prepin) os.system("./prepinp_fix < prepin > prepout") # Generate 'stdin' file used in colinp script of Columbus # DALTON, SCF input setting in colinp script of Columbus if (hf): stdin = f"\ny\n1\nn\nno\n2\nyes\n{self.docc_orb}\nyes\nyes\n{self.scf_max_iter}\n{self.scf_en_tol}\nno\n1\n\n" else: # Here, n in DALTON means no change of basis set; this choice appears when 'control.run' or 'mocoef' file exists stdin = f"\ny\n1\nn\nno\nn\n" # MCSCF input setting in colinp script of Columbus if (mcscf): stdin += f"3\nn\n1\n1\n{int(molecule.nelec)}\n1\n1\n0\n0\n{self.closed_orb}\n{self.active_orb}\nn\n" + "\t" * 14 + "\n" # MRCI input setting in colinp script of Columbus if (not calc_force_only): stdin += f"4\n\n2\ny\nn\n1\n{int(molecule.nelec)}\n1\n{self.frozen_core_orb}\n{self.frozen_virt_orb}\n" + \ f"{self.internal_orb}\n{self.internal_orb - self.active_orb}\n0\n2\ny\n\nn\n1\n" + "\t" * 17 + "\n" # Job control setting in colinp script of Columbus if (calc_force_only): # Start from 'mocoef' file # Here, y in job control setting means discard of already existing 'control.run' file stdin += "5\n1\ny\n1\n3\n5\n11\n1\nn\n3\nn\n8\n4\n7\n\n" else: if (restart == 1): # Start from MCSCF calculation stdin += "5\n1\n1\n3\n5\n11\n1\nn\n3\nn\n8\n4\n7\n\n" else: # Start from SCF calculation stdin += "5\n1\n1\n2\n3\n5\n11\n1\nn\n3\nn\n8\n4\n7\n\n" file_name = "stdin" with open(file_name, "w") as f: f.write(stdin) os.system(f"{self.qm_path}/colinp < stdin > stdout") if (not calc_force_only): # Manually modify input files # Modify 'mcscfin' files file_name = "mcscfin" with open(file_name, "r") as f: mcscfin = f.readlines() mcscf_length = len(mcscfin) target_line = mcscf_length - 3 new_mcscf = "" for i in range(target_line): if ("niter" in mcscfin[i]): new_mcscf += f" niter={self.mcscf_max_iter},\n" elif ("tol(1)" in mcscfin[i]): new_mcscf += f" tol(1)=1.e-{self.mcscf_en_tol},\n" else: new_mcscf += mcscfin[i] new_mcscf += f" NAVST(1) = {self.state_avg},\n" for i in range(self.state_avg): new_mcscf += f" WAVST(1,{i + 1})=1 ,\n" new_mcscf += " &end\n" os.rename("mcscfin", "mcscfin.old") file_name = "mcscfin" with open(file_name, "w") as f: f.write(new_mcscf) # Manually modify input files # Modify 'ciudgin' files file_name = "ciudgin" with open(file_name, "r") as f: ciudgin = f.readlines() ciudg_length = len(ciudgin) # For 'NVBKMN', 'NVCIMN', 'NVCIMX', 'NVRFMX', 'NVBKMX', these variables affect # the iteration process, so the default values are used. new_ciudg = "" for i in range(ciudg_length): if ("NROOT" in ciudgin[i]): new_ciudg += f" NROOT = {self.state_avg}\n" elif ("NVBKMN" in ciudgin[i]): new_ciudg += f" NVBKMN = {self.state_avg}\n" elif ("RTOLBK" in ciudgin[i]): new_ciudg += " RTOLBK = " new_ciudg += "1e-4," * self.state_avg new_ciudg += "\n" elif ("NITER" in ciudgin[i]): new_ciudg += f" NITER = {self.mrci_max_iter}\n" elif ("NVCIMN" in ciudgin[i]): new_ciudg += f" NVCIMN = {self.state_avg + 2}\n" elif ("RTOLCI" in ciudgin[i]): new_ciudg += " RTOLCI = " new_ciudg += f"1e-{self.mrci_en_tol}," * self.state_avg new_ciudg += "\n" elif ("NVCIMX" in ciudgin[i]): new_ciudg += f" NVCIMX = {self.state_avg + 5}\n" elif ("NVRFMX" in ciudgin[i]): new_ciudg += f" NVRFMX = {self.state_avg + 5}\n" elif ("NVBKMX" in ciudgin[i]): new_ciudg += f" NVBKMX = {self.state_avg + 5}\n" else: new_ciudg += ciudgin[i] os.rename("ciudgin", "ciudgin.old") file_name = "ciudgin" with open(file_name, "w") as f: f.write(new_ciudg) # Modify 'cidenin' files file_name = "cidenin" with open(file_name, "r") as f: cidenin = f.readlines() ciden_length = len(cidenin) new_ciden = "" for i in range(ciden_length): if ("lroot" in cidenin[i]): new_ciden += f" lroot = {self.state_avg}\n" else: new_ciden += cidenin[i] os.rename("cidenin", "cidenin.old") file_name = "cidenin" with open(file_name, "w") as f: f.write(new_ciden) # Manually modify input files # Modify 'cigrdin' files file_name = "cigrdin" with open(file_name, "r") as f: cigrdin = f.readlines() new_cigrd = "" for line in cigrdin: if ("nmiter" in line): new_cigrd += f" nmiter= {self.cpscf_max_iter}, print=0, fresdd=1,\n" elif ("rtol" in line): new_cigrd += f" rtol=1e-{self.cpscf_grad_tol}, dtol=1e-6,\n" else: new_cigrd += line os.rename("cigrdin", "cigrdin.old") file_name = "cigrdin" with open(file_name, "w") as f: f.write(new_cigrd) # Copy 'daltcomm' files shutil.copy("daltcomm", "daltcomm.new") # Write 'transmomin' files transmomin = "CI\n" # Gradient part for ist in bo_list: transmomin += f"1 {ist + 1} 1 {ist + 1}\n" # NAC part if (not calc_force_only): for i in range(molecule.nst): for j in range(i + 1, molecule.nst): transmomin += f"1 {i + 1} 1 {j + 1}\n" file_name = "transmomin" with open(file_name, "w") as f: f.write(transmomin)
def __init__(self, molecule, l_scc=True, scc_tol=1E-6, scc_max_iter=100, l_onsite=False, \ l_range_sep=False, lc_method="MatrixBased", l_spin_pol=False, unpaired_elec=0., guess="h0", \ guess_file="./charges.bin", elec_temp=0., mixer="Broyden", ex_symmetry="singlet", e_window=0., \ k_point=[1, 1, 1], l_periodic=False, cell_length=[0., 0., 0., 0., 0., 0., 0., 0., 0.,], \ sk_path="./", install_path="./", mpi=False, mpi_path="./", nthreads=1, version="20.1"): # Initialize DFTB+ common variables super(DFTB, self).__init__(molecule, sk_path, install_path, nthreads, version) # Initialize DFTB+ DFTB variables self.l_scc = l_scc self.scc_tol = scc_tol self.scc_max_iter = scc_max_iter self.l_onsite = l_onsite self.l_range_sep = l_range_sep self.lc_method = lc_method.lower() self.l_spin_pol = l_spin_pol self.unpaired_elec = unpaired_elec # Set initial guess for SCC term self.guess = guess.lower() self.guess_file = guess_file if not (self.guess in ["h0", "read"]): error_message = "Invalid initial guess for DFTB!" error_vars = f"guess = {self.guess}" raise ValueError( f"( {self.qm_method}.{call_name()} ) {error_message} ( {error_vars} )" ) self.elec_temp = elec_temp self.mixer = mixer.lower() self.ex_symmetry = ex_symmetry.lower() self.e_window = e_window self.k_point = k_point self.l_periodic = l_periodic self.a_axis = np.copy(cell_length[0:3]) self.b_axis = np.copy(cell_length[3:6]) self.c_axis = np.copy(cell_length[6:9]) # Check excitation symmetry in TDDFTB # TODO : Currently, allows only singlet excited states with TDDFTB # if not (self.ex_symmetry in ["singlet", "triplet"]): if (not self.ex_symmetry == "singlet"): error_message = "Invalid symmetry of excited states for TDDFTB given!" error_vars = f"ex_symmetry = {self.ex_symmetry}" raise ValueError( f"( {self.qm_method}.{call_name()} ) {error_message} ( {error_vars} )" ) self.mpi = mpi self.mpi_path = mpi_path # Set 'l_nacme' and 're_calc' with respect to the computational method # TDDFTB do not produce NACs, so we should get NACME from CIoverlap # TDDFTB cannot compute the gradient of several states simultaneously. molecule.l_nacme = True self.re_calc = True # Calculate number of basis for current system # Set new variable to decide the position of basis functions in terms of atoms # DFTB method considers only valence electrons, so core electrons should be removed core_elec = 0. self.nbasis = 0 self.check_atom = [0] for iat in range(molecule.nat_qm): # Check number of basis functions with respect to maximum angular momentum max_ang = max_l[molecule.symbols[iat]] if (max_ang == 's'): self.nbasis += 1 elif (max_ang == 'p'): self.nbasis += 4 elif (max_ang == 'd'): self.nbasis += 9 else: error_message = "Number of basis for f orbital not implemented, see '$PYUNIXMDHOME/src/qm/dftbplus/dftb.py'!" error_vars = f"current atom = {molecule.symbols[iat]}, max_ang = {max_ang}" raise NotImplementedError( f"( {self.qm_method}.{call_name()} ) {error_message} ( {error_vars} )" ) self.check_atom.append(self.nbasis) # Check number of core electrons with respect to atomic number sym_index = list(data.keys()).index(molecule.symbols[iat]) if (sym_index > 0 and sym_index <= 2): core_elec += 0. elif (sym_index > 2 and sym_index <= 10): core_elec += 2. elif (sym_index > 10 and sym_index <= 18): core_elec += 10. elif (sym_index > 18 and sym_index <= 36): core_elec += 18. elif (sym_index > 36 and sym_index <= 54): core_elec += 36. else: error_message = "Core electrons for current element not implemented, see '$PYUNIXMDHOME/src/qm/dftbplus/dftb.py'!" error_vars = f"current atom = {molecule.symbols[iat]}, sym_index = {sym_index}" raise NotImplementedError( f"( {self.qm_method}.{call_name()} ) {error_message} ( {error_vars} )" ) # Set new variable to decide the position of atoms in terms of basis functions self.check_basis = [] for ibasis in range(self.nbasis): for iat in range(molecule.nat_qm): ind_a = self.check_atom[iat] + 1 ind_b = self.check_atom[iat + 1] if (ibasis + 1 >= ind_a and ibasis + 1 <= ind_b): self.check_basis.append(iat + 1) # Initialize NACME variables # There is no core orbitals in TDDFTB (fixed occupations) # nocc is number of occupied orbitals and nvirt is number of virtual orbitals self.norb = self.nbasis self.nocc = int(int(molecule.nelec - core_elec) / 2) self.nvirt = self.norb - self.nocc # Replace norb by arrays containing the limits of the for loops. # For energy window calculations loops will not go from (0 to nocc/nvirt) or (0 to norb) # but from (nocc_min to nocc/0 to nvirt_max) or (nocc_min to norb). self.orb_ini = np.zeros(1, dtype=int) self.orb_final = np.zeros(1, dtype=int) self.orb_final[0] = self.norb if (self.e_window > eps): # Swap minimal/maximal values to replace them in reading of SPX.DAT by the minimal/maximal values. self.orb_ini[0] = self.norb self.orb_final[0] = 0 self.ao_overlap = np.zeros((self.nbasis, self.nbasis)) self.mo_coef_old = np.zeros((self.norb, self.nbasis)) self.mo_coef_new = np.zeros((self.norb, self.nbasis)) self.ci_coef_old = np.zeros((molecule.nst, self.nocc, self.nvirt)) self.ci_coef_new = np.zeros((molecule.nst, self.nocc, self.nvirt))