Exemple #1
0
    if len(args) < 2:
        print usage
        exit(-1)

    xyz_file = args[0]
    hess_file = args[1]
    # should be options

    # optimized geometry
    atomlist = XYZ.read_xyz(xyz_file)[-1]
    xopt = XYZ.atomlist2vector(atomlist)
    # load hessian
    hess = np.loadtxt(hess_file)

    masses = AtomicData.atomlist2masses(atomlist)
    vib_freq, vib_modes = HarmonicApproximation.vibrational_analysis(xopt, hess, masses, \
                                 zero_threshold=opts.zero_threshold, is_molecule=True)

    # SAMPLE INITIAL CONDITIONS FROM WIGNER DISTRIBUTION
    qs, ps = HarmonicApproximation.initial_conditions_wigner(
        xopt,
        hess,
        masses,
        Nsample=opts.Nsample,
        zero_threshold=opts.zero_threshold)
    # make hydrogens slower
    for i in range(0, opts.Nsample):
        for A, (Z, pos) in enumerate(atomlist):
            if Z == 1:
                ps[3 * A:3 * (A + 1), i] *= 0.001
    #
    # STATISTICS
Exemple #2
0
def hessian(xyzfile, optionfile):
    """calculates hessian matrix"""

    outputfile = open("output_dftb.txt", "a")  # redirect output to file
    sys.stdout = outputfile

    try:
        I = 0  # index of electronic state (ground state)

        atomlist = XYZ.read_xyz(xyzfile)[0]  # read xyz file
        kwds = XYZ.extract_keywords_xyz(xyzfile)  # read keywords (charge)
        options = read_options(optionfile)  # read options
        scf_options = extract_options(options,
                                      SCF_OPTIONLIST)  # get scf-options

        pes = MyPES(atomlist, options, Nst=max(I + 1, 2), **kwds)  # create PES

        atomvec = XYZ.atomlist2vector(atomlist)  # convert atomlist to vector

        # FIND ENERGY MINIMUM
        # f is the objective function that should be minimized
        # it returns (f(x), f'(x))
        def f(x):
            if I == 0 and type(pes.tddftb.XmY) != type(None):
                # only ground state is needed. However, at the start
                # a single TD-DFT calculation is performed to initialize
                # all variables (e.g. X-Y), so that the program does not
                # complain about non-existing variables.
                enI, gradI = pes.getEnergyAndGradient_S0(x)
            else:
                energies, gradI = pes.getEnergiesAndGradient(x, I)
                enI = energies[I]
            return enI, gradI

        minoptions = {'gtol': 1.0e-7, 'norm': 2}
        # somehow numerical_hessian does not work without doing this mimimization before
        res = optimize.minimize(f,
                                atomvec,
                                method="CG",
                                jac=True,
                                options=minoptions)

        # COMPUTE HESSIAN AND VIBRATIONAL MODES
        # The hessian is calculated by numerical differentiation of the
        # analytical gradients
        def grad(x):
            if I == 0:
                enI, gradI = pes.getEnergyAndGradient_S0(x)
            else:
                energies, gradI = pes.getEnergiesAndGradient(x, I)
            return gradI

        print "Computing Hessian"  # calculate hessians from gradients
        hess = HarmonicApproximation.numerical_hessian_G(grad, atomvec)

        string = ""  # create string that is to be written into file
        for line in hess:
            for column in line:
                string += str(column) + " "
            string = string[:-1] + "\n"

        with open("hessian.txt",
                  "w") as hessianfile:  # write hessian matrix to file
            hessianfile.write(string)
            # this would look nicer but is not as exact
            #hessianfile.write(annotated_hessian(atomlist, hess))

        # calculate energy for optimized geometry
        dftb2 = DFTB2(atomlist, **options)  # create dftb object
        dftb2.setGeometry(atomlist, charge=kwds.get("charge", 0.0))

        dftb2.getEnergy(**scf_options)
        energies = list(dftb2.getEnergies())  # get partial energies

        if dftb2.long_range_correction == 1:  # add long range correction to partial energies
            energies.append(dftb2.E_HF_x)

        return str(energies)

    except:
        print sys.exc_info()
        return "error"
Exemple #3
0
    save_xyz(xopt, info="energy=%s" % Eopt)
    print "Optimized geometry written to %s" % xyz_opt

    if calc_hessian == True:
        # COMPUTE HESSIAN AND VIBRATIONAL MODES
        # The hessian is calculated by numerical differentiation of the
        # analytical gradients
        def grad(x):
            if I == 0:
                enI, gradI = pes.getEnergyAndGradient_S0(x)
            else:
                energies, gradI = pes.getEnergiesAndGradient(x, I)
            return gradI

        print "Computing Hessian"
        hess = HarmonicApproximation.numerical_hessian_G(grad, xopt)
        np.savetxt("hessian.dat", hess)
        masses = AtomicData.atomlist2masses(atomlist)
        vib_freq, vib_modes = HarmonicApproximation.vibrational_analysis(xopt, hess, masses, \
                                                                         zero_threshold=1.0e-9, is_molecule=True)
        # compute thermodynamic quantities and write summary
        thermo = Thermochemistry.Thermochemistry(
            atomlist, Eopt, vib_freq, pes.tddftb.dftb2.getSymmetryGroup())
        thermo.calculate()

        # write vibrational modes to molden file
        molden = MoldenExporterSectioned(pes.tddftb.dftb2)
        atomlist_opt = XYZ.vector2atomlist(xopt, atomlist)
        molden.addVibrations(atomlist_opt, vib_freq.real,
                             vib_modes.transpose())
        molden.export("vib.molden")
Exemple #4
0
    # output file
    state_file = args[2]

    # load minimum geometry and Hessian
    atomlist = XYZ.read_xyz(xyz_file)[-1]
    hess = np.loadtxt(hessian_file)

    print "optimized geometry read from '%s'" % xyz_file
    print "Hessian read from '%s'" % hessian_file

    # compute normal modes and frequencies
    xopt = XYZ.atomlist2vector(atomlist)
    masses = AtomicData.atomlist2masses(atomlist)
    # setting zero_threshold to -0.1 makes sure that all frequencies are returned
    # even if some of them are imaginary
    vib_freq, vib_modes = HarmonicApproximation.vibrational_analysis(xopt, hess, masses, \
                                                                     zero_threshold=-0.1, is_molecule=True)

    # sort frequencies in ascending order
    vib_freq = vib_freq.real
    sort_index = np.argsort(abs(vib_freq)**2)
    vib_freq = vib_freq[sort_index]
    vib_modes = vib_modes[:, sort_index]

    # remove lowest 6 vibrations (3 translation + 3 rotation) assuming the molecule
    # is not linear
    nat = len(atomlist)
    nvib = 3 * nat - 6
    # Modes which have frequencies exactly equal to 0 (or with imaginary parts)
    # are already removed inside `vibrational_analysis()`. Another `nzero`
    # low frequency modes have to be removed, so that only 3*nat-6 vibrational frequencies remain
    nzero = len(vib_freq) - nvib
Exemple #5
0
    def minimize(self):
        I = self.state

        # convert geometry to a vector
        x0 = XYZ.atomlist2vector(self.atomlist)

        # This member variable holds the last energy of the state
        # of interest.
        self.enI = 0.0
        # last available energies of all electronic states that were
        # calculated
        self.energies = None

        # FIND ENERGY MINIMUM
        # f is the objective function that should be minimized
        # it returns (f(x), f'(x))
        def f_cart(x):
            #
            if I == 0 and type(self.pes.tddftb.XmY) != type(None):
                # Only ground state is needed. However, at the start
                # a single TD-DFT calculation is performed to initialize
                # all variables (e.g. X-Y), so that the program does not
                # complain about non-existing variables.
                enI, gradI = self.pes.getEnergyAndGradient_S0(x)
                energies = np.array([enI])
            else:
                energies, gradI = self.pes.getEnergiesAndGradient(x, I)
                enI = energies[I]
            self.enI = enI
            self.energies = energies
            print("E = %2.7f     |grad| = %2.7f" % (enI, la.norm(gradI)))
            #
            # also save geometries from line searches
            save_xyz(x)

            return enI, gradI

        print("Intermediate geometries will be written to %s" % self.xyz_opt)

        # This is a callback function that is executed for each optimization step.
        # It appends the current geometry to an xyz-file.
        def save_xyz(x, mode="a"):
            self.atomlist = XYZ.vector2atomlist(x, self.atomlist)
            XYZ.write_xyz(self.xyz_opt, [self.atomlist], \
                          title="charge=%s energy= %s" % (self.geom_kwds.get("charge",0), self.enI),\
                          mode=mode)
            return x

        Nat = len(self.atomlist)

        if self.coord_system == "cartesian":
            print(
                "optimization is performed directly in cartesian coordinates")
            q0 = x0
            objective_func = f_cart
            save_geometry = save_xyz
            max_steplen = None
        elif self.coord_system == "internal":
            print(
                "optimization is performed in redundant internal coordinates")
            # transform cartesian to internal coordinates, x0 ~ q0
            q0 = self.IC.cartesian2internal(x0)

            # define functions that wrap the cartesian<->internal transformations
            def objective_func(q):
                # transform back from internal to cartesian coordinates
                x = self.IC.internal2cartesian(q)
                self.IC.cartesian2internal(x)
                # compute energy and gradient in cartesian coordinates
                en, grad_cart = f_cart(x)
                # transform gradient to internal coordinates
                grad = self.IC.transform_gradient(x, grad_cart)

                return en, grad

            def save_geometry(q, **kwds):
                # transform back from internal to cartesian coordinates
                x = self.IC.internal2cartesian(q)
                # save cartesian coordinates
                save_xyz(x, **kwds)
                return x

            def max_steplen(q0, v):
                """
                find a step size `a` such that the internal->cartesian
                transformation converges for the point q = q0+a*v
                """
                a = 1.0
                for i in range(0, 7):
                    q = q0 + a * v
                    try:
                        x = self.IC.internal2cartesian(q)
                    except NotConvergedError as e:
                        # reduce step size by factor of 1/2
                        a /= 2.0
                        continue
                    break
                else:
                    raise RuntimeError(
                        "Could not find a step size for which the transformation from internal to cartesian coordinates would work for q=q0+a*v! Last step size a= %e  |v|= %e  |a*v|= %e"
                        % (a, la.norm(v), la.norm(a * v)))
                return a

        else:
            raise ValueError("Unknown coordinate system '%s'!" %
                             self.coord_system)
        # save initial energy and geometry
        objective_func(q0)
        save_geometry(q0, mode="w")

        options = {
            'gtol': self.grad_tol,
            'maxiter': self.maxiter,
            'gtol': self.grad_tol,
            'norm': 2
        }
        if self.method == 'CG':
            # The "BFGS" method is probably better than "CG", but the line search in BFGS is expensive.
            res = optimize.minimize(objective_func,
                                    q0,
                                    method="CG",
                                    jac=True,
                                    callback=save_geometry,
                                    options=options)
            #res = optimize.minimize(objective_func, q0, method="BFGS", jac=True, callback=save_geometry, options=options)

        elif self.method in ['Steepest Descent', 'Newton', 'BFGS']:
            # My own implementation of optimization algorithms
            res = minimize(
                objective_func,
                q0,
                method=self.method,
                #line_search_method="largest",
                callback=save_geometry,
                max_steplen=max_steplen,
                maxiter=self.maxiter,
                gtol=self.grad_tol,
                ftol=self.func_tol)
        else:
            raise ValueError("Unknown optimization algorithm '%s'!" %
                             self.method)

        # save optimized geometry
        qopt = res.x
        Eopt = res.fun
        xopt = save_geometry(qopt)
        print("Optimized geometry written to %s" % self.xyz_opt)

        if self.calc_hessian == 1:
            # COMPUTE HESSIAN AND VIBRATIONAL MODES
            # The hessian is calculated by numerical differentiation of the
            # analytical cartesian gradients
            def grad(x):
                en, grad_cart = f_cart(x)
                return grad_cart

            print("Computing Hessian")
            hess = HarmonicApproximation.numerical_hessian_G(grad, xopt)
            np.savetxt("hessian.dat", hess)
            masses = AtomicData.atomlist2masses(atomlist)
            vib_freq, vib_modes = HarmonicApproximation.vibrational_analysis(xopt, hess, masses, \
                                                                             zero_threshold=1.0e-9, is_molecule=True)
            # compute thermodynamic quantities and write summary
            thermo = Thermochemistry.Thermochemistry(
                atomlist, Eopt, vib_freq,
                self.pes.tddftb.dftb2.getSymmetryGroup())
            thermo.calculate()

            # write vibrational modes to molden file
            molden = MoldenExporterSectioned(self.pes.tddftb.dftb2)
            atomlist_opt = XYZ.vector2atomlist(xopt, atomlist)
            molden.addVibrations(atomlist_opt, vib_freq.real,
                                 vib_modes.transpose())
            molden.export("vib.molden")

        ## It's better to use the script initial_conditions.py for sampling from the Wigner
        ## distribution
        """