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
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"
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")
# 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
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 """