Exemple #1
0
    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))