def opt(xyzfile, optionfile): """performs an optimization""" 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 atomlist kwds = XYZ.extract_keywords_xyz( xyzfile) # read keywords from xyz-file (charge) options = read_options(optionfile) # read options scf_options = extract_options(options, SCF_OPTIONLIST) # get scf-options # optimization (taken from optimize.py) pes = MyPES(atomlist, options, Nst=max(I + 1, 2), **kwds) x0 = XYZ.atomlist2vector(atomlist) #convert geometry to a vector def f(x): save_xyz(x) # also save geometries from line searches 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] print "E = %2.7f" % (enI) return enI, gradI xyz_trace = xyzfile.replace(".xyz", "_trace.xyz") # This is a callback function that is executed by numpy for each optimization step. # It appends the current geometry to an xyz-file. def save_xyz(x, mode="a"): atomlist_opt = XYZ.vector2atomlist(x, atomlist) XYZ.write_xyz(xyz_trace, [atomlist_opt], title="charge=%s" % kwds.get("charge", 0), mode=mode) save_xyz(x0, mode="w") # write original geometry Nat = len(atomlist) min_options = {'gtol': 1.0e-7, 'norm': 2} # The "BFGS" method is probably better than "CG", but the line search in BFGS is expensive. res = optimize.minimize(f, x0, method="CG", jac=True, callback=save_xyz, options=min_options) # res = optimize.minimize(f, x0, method="BFGS", jac=True, callback=save_xyz, options=options) xopt = res.x save_xyz(xopt) print "Intermediate geometries written into file {}".format(xyz_trace) # write optimized geometry into file atomlist_opt = XYZ.vector2atomlist(xopt, atomlist) xyz_opt = xyzfile.replace(".xyz", "_opt.xyz") XYZ.write_xyz(xyz_opt, [atomlist_opt], title="charge=%s" % kwds.get("charge", 0), mode="w") # calculate energy for optimized geometry dftb2 = DFTB2(atomlist_opt, **options) # create dftb object dftb2.setGeometry(atomlist_opt, 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"
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"
def Gouterman_matelems(): """ computes overlap and hamiltonian matrix elements between the 4 frontier orbitals (a1u,a2u,eg,eg) of neighbouring porphyrine squares in a porphyrine flake (polyomino). The matrix elements between vertically and horizontally fused porphyrins will be related by a rotation of 90 degrees and will be different. Returns: ========: orbe: energies of the four orbitals Sh,Sv: two 4x4 matrices with overlaps between frontier orbitals of horizontally and vertically fused porphyrins. H0h,H0v: two 4x4 matrices with matrix elements of the 0th-order DFTB hamiltonian (neglecting interaction between partial charges) """ # perform DFTB calculation on the monomer to obtain the MO coefficients for the frontier # orbitals. lat, unitcell, meso, beta = zn_porphene_lattice(substitutions=[]) monomer = unitcell + meso + beta dftb = DFTB2(monomer, missing_reppots="dummy") dftb.setGeometry(monomer) dftb.getEnergy() orbs = dftb.getKSCoefficients() orbe = dftb.getKSEnergies() H**O, LUMO = dftb.getFrontierOrbitals() # save DFTB MOs so that we can identify the symmetries of the orbitals. molden = MoldenExporter(dftb, title="Gouterman orbitals") molden.export(molden_file="/tmp/Gouterman_orbitals.molden") # In DFTB the orbital symmetries are: # H-2 a1u # H-1 rubbish # H a2u # L eg # L+1 eg # The H-1 belongs to the sigma-framework and has the wrong energetic position in DFTB. Gouterman_orbitals = np.array([H**O - 2, H**O, LUMO, LUMO + 1], dtype=int) # indeces of G. orbitals # remove orbitals belonging to hydrogen valorbs = dftb.getValorbs() mu = 0 # iterates over all orbitals hydrogen_orbs = [ ] # indeces of orbitals belonging to hydrogen that should be removed for i, (Zi, posi) in enumerate(monomer): for (ni, li, mi) in valorbs[Zi]: if (Zi == 1): hydrogen_orbs.append(mu) mu += 1 # remove rows or columns belonging to hydrogen orbs = np.delete(orbs, hydrogen_orbs, 0) orbs = np.delete(orbs, hydrogen_orbs, 1) nmo, nmo = orbs.shape a1, a2, a3 = lat.getLatticeVectors() C = orbs[:, Gouterman_orbitals] # energies of Gouterman orbitals orbe = orbe[Gouterman_orbitals] # horizontally fused # matrix elements between AOs H0h, Sh = dftb._constructH0andS_AB(nmo, nmo, unitcell, shift_atomlist(unitcell, a1)) # transform to the basis of frontier orbitals Sh = np.dot(C.transpose(), np.dot(Sh, C)) H0h = np.dot(C.transpose(), np.dot(H0h, C)) # vertically fused # matrix elements between AOs H0v, Sv = dftb._constructH0andS_AB(nmo, nmo, unitcell, shift_atomlist(unitcell, a2)) # transform to the basis of frontier orbitals Sv = np.dot(C.transpose(), np.dot(Sv, C)) H0v = np.dot(C.transpose(), np.dot(H0v, C)) # orb_names = ["a1u", "a2u", "eg", "eg"] print "Lattice vectors" print " a1 = %s bohr" % a1 print " a2 = %s bohr" % a2 print "orbital energies:" for name, en in zip(orb_names, orbe * AtomicData.hartree_to_eV): print " energy(%3s) = %8.4f eV" % (name, en) print "matrix elements with neighbouring porphyrin" print "along lattice vector a1 = %s" % a1 print "overlap Sh" print utils.annotated_matrix(Sh, orb_names, orb_names) print "hamiltonian H0h" print utils.annotated_matrix(H0h, orb_names, orb_names) print "along lattice vector a2 = %s" % a2 print "overlap Sv" print utils.annotated_matrix(Sv, orb_names, orb_names) print "hamiltonian H0v" print utils.annotated_matrix(H0v, orb_names, orb_names) return orbe, Sh, Sv, H0h, H0v