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"
示例#3
0
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