Exemple #1
0
def get_active_space(xyzfile, optionfile):
    """calculates the active space for a given molecule and excited state
    (see http://www.dftbaby.chemie.uni-wuerzburg.de/DFTBaby/mdwiki.html#!WIKI/main_page.md, Active Space)
    
    not implemented in CAST because no use for ground state calculations
    for ground state calculations the active space can be set as small as desired"""

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

    try:
        options = read_options(optionfile)  # read options
        atomlist = XYZ.read_xyz(xyzfile)[0]  # read structure

        init_options = extract_options(options, TD_INIT_OPTIONLIST)
        td_options = extract_options(options, TD_OPTIONLIST)
        kwds = XYZ.extract_keywords_xyz(xyzfile)

        tddftb = LR_TDDFTB(atomlist, **init_options)  # create object
        tddftb.setGeometry(atomlist, charge=kwds.get("charge", 0.0))
        tddftb.getEnergies(**td_options)  # calculate energies
        occ, virt = tddftb.determineActiveSpace()
        return str((occ, virt))
    except:
        print sys.exc_info()
        return "error"
Exemple #2
0
    def __init__(self, atomlist, Nst=2, **kwds):
        """
        The DFTB module has many parameters which can be set from the command
        line using options, e.g. --scf_conv=1.0e-10. 
        During the initialization 
           - the command line arguments are parsed for options
           - Slater-Koster tables and repulsive potentials
             are loaded for the atom pair present in the molecule

        Parameters:
        ===========
        atomlist: list of tuples (Zi,[xi,yi,zi]) for each atom in the molecule
        Nst: number of electronic states (including ground state)
        """
        usage = "Type --help to show all options for DFTB"

        parser = optparse.OptionParserFuncWrapper([
            DFTB2.__init__, DFTB2.runSCC, SolventCavity.__init__,
            LR_TDDFTB.getEnergies
        ],
                                                  usage,
                                                  section_headers=["DFTBaby"],
                                                  unknown_options="ignore")
        options, args = parser.parse_args(DFTB2.__init__)
        self.atomlist = atomlist
        self.tddftb = LR_TDDFTB(atomlist, **options)

        solvent_options, args = parser.parse_args(SolventCavity.__init__)
        solvent_cavity = SolventCavity(**solvent_options)
        self.tddftb.dftb2.setSolventCavity(solvent_cavity)

        self.grads = Gradients(self.tddftb)
        self.tddftb.setGeometry(atomlist, charge=kwds.get("charge", 0.0))
        self.options, args = parser.parse_args(self.tddftb.getEnergies)
        self.scf_options, args = parser.parse_args(self.tddftb.dftb2.runSCC)
        self.options.update(self.scf_options)
        self.Nst = Nst
        #        # always use iterative diagonalizer for lowest Nst-1 excited states
        self.options["nstates"] = Nst - 1
        # save geometry, orbitals and TD-DFT coefficients from
        # last calculation
        self.last_calculation = None
        # save transition dipoles from last calculation
        self.tdip_old = None
Exemple #3
0
 def __init__(self, atomlist, options, Nst=2, **kwds):
     usage = "Type --help to show all options for DFTB"
     parser = utils.OptionParserFuncWrapper(
         [DFTB2.__init__, DFTB2.runSCC, LR_TDDFTB.getEnergies], usage)
     self.options = options
     td_init_options = extract_options(self.options, TD_INIT_OPTIONLIST)
     self.atomlist = atomlist
     self.tddftb = LR_TDDFTB(atomlist, **td_init_options)
     self.grads = Gradients(self.tddftb)
     self.tddftb.setGeometry(atomlist, charge=kwds.get("charge", 0.0))
     self.scf_options = extract_options(self.options, SCF_OPTIONLIST)
     self.options = copy(self.scf_options)
     self.Nst = Nst
     #        # always use iterative diagonalizer for lowest Nst-1 excited states
     self.options["nstates"] = Nst - 1
     # save geometry, orbitals and TD-DFT coefficients from
     # last calculation
     self.last_calculation = None
     # save transition dipoles from last calculation
     self.tdip_old = None
Exemple #4
0
def calc_gradients(xyzfile, optionfile):
    """calculates DFTB energies and gradients for a molecule in the xyz_file
    with the options given in the optionfile"""

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

    try:
        options = read_options(optionfile)  # read options
        atomlist = XYZ.read_xyz(xyzfile)[0]  # read structure

        init_options = extract_options(options, TD_INIT_OPTIONLIST)
        td_options = extract_options(options, TD_OPTIONLIST)
        grad_options = extract_options(options, GRAD_OPTIONS)
        kwds = XYZ.extract_keywords_xyz(xyzfile)

        tddftb = LR_TDDFTB(atomlist, **init_options)  # create object
        tddftb.setGeometry(atomlist, charge=kwds.get("charge", 0.0))
        tddftb.getEnergies(**td_options)  # calculate energies

        grad = Gradients(tddftb)  # calculate gradients
        grad.getGradients(**grad_options)

        energies = list(tddftb.dftb2.getEnergies())  # get partial energies

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

        return str(energies)

    except:
        print sys.exc_info()
        return "error"
Exemple #5
0
def test_dftb_eigenvector_derivative():
    """compare analytical and numerical gradients of MO coefficients and orbital energies"""
    from DFTB.XYZ import read_xyz, extract_keywords_xyz
    from DFTB.DFTB2 import DFTB2
    from DFTB.Analyse.Cube import CubeExporterEx
    from DFTB.Molden import MoldenExporter
    from DFTB import utils
    from DFTB.LR_TDDFTB import LR_TDDFTB
    from DFTB.ExcGradients import Gradients

    import sys
    import os

    usage = "Usage: %s <xyz-file>\n" % sys.argv[0]
    usage += "   --help option will give more information\n"

    parser = utils.OptionParserFuncWrapper([\
       DFTB2.__init__, DFTB2.runSCC, \
       LR_TDDFTB.getEnergies, LR_TDDFTB.saveAbsorptionSpectrum, LR_TDDFTB.analyseParticleHole, \
       LR_TDDFTB.graphical_analysis, \
       CubeExporterEx.exportCubes, MoldenExporter.export, \
       Gradients.getGradients], \
                usage)

    (options, args) = parser.parse_args(DFTB2.__init__)

    if len(args) < 1:
        print(usage)
        exit(-1)

    xyz_file = args[0]
    atomlist = read_xyz(xyz_file)[0]
    kwds = extract_keywords_xyz(xyz_file)

    tddftb = LR_TDDFTB(atomlist, **options)

    (options, args) = parser.parse_args(tddftb.getEnergies)
    (scf_options, args) = parser.parse_args(tddftb.dftb2.runSCC)
    options.update(scf_options)
    tddftb.setGeometry(atomlist, charge=kwds.get("charge", 0.0))
    tddftb.getEnergies(**options)

    grad = Gradients(tddftb)
    grad.gradient(I=0, save_intermediates_CPKS=1)

    dE_an, dX_an = grad.getMOgradients()

    dftb2 = tddftb.dftb2
    E = dftb2.getKSEnergies()
    X = dftb2.getKSCoefficients()
    dE_num, dX_num = dftb_numerical_mo_gradients(dftb2, atomlist)
    print("eigenvalues")
    print(E)
    print("numerical eigenvalue gradients")
    print(dE_num)
    print("analytical eigenvalue gradients")
    print(dE_an)
    err_dE = la.norm(dE_num - dE_an)
    err_dX = la.norm(dX_num - dX_an)
    assert err_dE < 1.0e-3, "err(dE) = %s" % err_dE
def test_dipole_prepared_continuum():
    """
    see 
    G. Fronzoni, M. Stener, S. Furlan, P. Decleva
    Chemical Physics 273 (2001) 117-133
    """
    from DFTB.LR_TDDFTB import LR_TDDFTB
    from DFTB import XYZ
    from DFTB.Scattering import slako_tables_scattering

    # BOUND ORBITAL = H**O
    atomlist = XYZ.read_xyz("./water.xyz")[0]
    tddftb = LR_TDDFTB(atomlist)
    tddftb.setGeometry(atomlist, charge=0)
    options = {"nstates": 1}
    tddftb.getEnergies(**options)

    valorbs, radial_val = load_pseudo_atoms(atomlist)

    H**O, LUMO = tddftb.dftb2.getFrontierOrbitals()
    bound_orbs = tddftb.dftb2.getKSCoefficients()

    # polarization direction of E-field
    epol = np.array([0.0, 1.0, 0.0])

    # according to Koopman's theorem the electron is ionized from the H**O
    mo_indx = range(0, len(bound_orbs))
    nmo = len(mo_indx)
    for imo in range(0, nmo):
        mo_bound = bound_orbs[:, mo_indx[imo]]

        # CONTINUUM ORBITALS at energy E
        for E in slako_tables_scattering.energies:
            bs = AtomicScatteringBasisSet(atomlist, E)
            SKT_bf, SKT_ff = load_slako_scattering(atomlist, E)
            Dipole = ScatteringDipoleMatrix(atomlist, valorbs, SKT_bf)
            # projection of dipoles onto polarization direction
            Dipole_projected = np.zeros((Dipole.shape[0], Dipole.shape[1]))
            for xyz in [0, 1, 2]:
                Dipole_projected += Dipole[:, :, xyz] * epol[xyz]
            # unnormalized coefficients of dipole-prepared continuum orbitals
            mo_scatt = np.dot(mo_bound, Dipole_projected)
            nrm2 = np.dot(mo_scatt, mo_scatt)
            # normalized coefficients
            mo_scatt /= np.sqrt(nrm2)

            Cube.orbital2grid(atomlist, bs.bfs, mo_scatt, \
                          filename="/tmp/scattering_orbital_%d_to_%s.cube" % (imo, str(E).replace(".", "p")), dbuff=25.0)
            delattr(Cube.orbital_amplitude, "cached_grid")
def test_averaged_asymptotic_density():
    from DFTB.LR_TDDFTB import LR_TDDFTB
    from DFTB import XYZ
    from DFTB.Scattering import slako_tables_scattering
    from DFTB.Scattering import PAD

    # BOUND ORBITAL = H**O
    atomlist = XYZ.read_xyz("./water.xyz")[0]
    tddftb = LR_TDDFTB(atomlist)
    tddftb.setGeometry(atomlist, charge=0)
    options = {"nstates": 1}
    tddftb.getEnergies(**options)

    valorbs, radial_val = load_pseudo_atoms(atomlist)

    H**O, LUMO = tddftb.dftb2.getFrontierOrbitals()
    bound_orbs = tddftb.dftb2.getKSCoefficients()

    # polarization direction of E-field
    epol = np.array([0.0, 0.0, 1.0])

    # according to Koopman's theorem the electron is ionized from the H**O
    mo_indx = range(0, len(bound_orbs))
    nmo = len(mo_indx)
    for imo in range(0, nmo):
        print "IMO = %s" % imo
        import time
        time.sleep(1)
        mo_bound = bound_orbs[:, mo_indx[imo]]
        # CONTINUUM ORBITALS at energy E
        for E in slako_tables_scattering.energies[-2:-1]:
            bs = AtomicScatteringBasisSet(atomlist, E)
            SKT_bf, SKT_ff = load_slako_scattering(atomlist, E)
            Dipole = ScatteringDipoleMatrix(atomlist, valorbs, SKT_bf)

            PAD.averaged_asymptotic_density(mo_bound, Dipole, bs, 20.0, E)
def averaged_pad_scan(xyz_file, dyson_file,
                      selected_orbitals, npts_euler, npts_theta, nskip, inter_atomic, sphere_radius):
    molecule_name = os.path.basename(xyz_file).replace(".xyz", "")
    atomlist = XYZ.read_xyz(xyz_file)[-1]
    # shift molecule to center of mass
    print "shift molecule to center of mass"
    pos = XYZ.atomlist2vector(atomlist)
    masses = AtomicData.atomlist2masses(atomlist)
    pos_com = MolCo.shift_to_com(pos, masses)
    atomlist = XYZ.vector2atomlist(pos_com, atomlist)
    # compute molecular orbitals with DFTB
    tddftb = LR_TDDFTB(atomlist)
    tddftb.setGeometry(atomlist, charge=0)
    options={"nstates": 1}
    try:
        tddftb.getEnergies(**options)
    except DFTB.Solver.ExcitedStatesNotConverged:
        pass

    valorbs, radial_val = load_pseudo_atoms(atomlist)

    if dyson_file == None:
        print "tight-binding Kohn-Sham orbitals are taken as Dyson orbitals"
        H**O, LUMO = tddftb.dftb2.getFrontierOrbitals()
        bound_orbs = tddftb.dftb2.getKSCoefficients()
        if selected_orbitals == None:
            # all orbitals
            selected_orbitals = range(0,bound_orbs.shape[1])
        else:
            selected_orbitals = eval(selected_orbitals, {}, {"H**O": H**O+1, "LUMO": LUMO+1})
            print "Indeces of selected orbitals (counting from 1): %s" % selected_orbitals
        orbital_names = ["orb_%s" % o for o in selected_orbitals]
        selected_orbitals = np.array(selected_orbitals, dtype=int)-1 # counting from 0
        dyson_orbs = bound_orbs[:,selected_orbitals]
        ionization_energies = -tddftb.dftb2.getKSEnergies()[selected_orbitals]
    else:
        print "coeffients for Dyson orbitals are read from '%s'" % dyson_file
        orbital_names, ionization_energies, dyson_orbs = load_dyson_orbitals(dyson_file)
        ionization_energies = np.array(ionization_energies) / AtomicData.hartree_to_eV

    print ""
    print "*******************************************"
    print "*  PHOTOELECTRON ANGULAR DISTRIBUTIONS    *"
    print "*******************************************"
    print ""
    
    # determine the radius of the sphere where the angular distribution is calculated. It should be
    # much larger than the extent of the molecule
    (xmin,xmax),(ymin,ymax),(zmin,zmax) = Cube.get_bbox(atomlist, dbuff=0.0)
    dx,dy,dz = xmax-xmin,ymax-ymin,zmax-zmin
    Rmax = max([dx,dy,dz]) + sphere_radius
    Npts = max(int(Rmax),1) * 50
    print "Radius of sphere around molecule, Rmax = %s bohr" % Rmax
    print "Points on radial grid, Npts = %d" % Npts
    
    nr_dyson_orbs = len(orbital_names)
    # compute PADs for all selected orbitals
    for iorb in range(0, nr_dyson_orbs):
        print "computing photoangular distribution for orbital %s" % orbital_names[iorb]
        data_file = "betas_" + molecule_name + "_" + orbital_names[iorb] + ".dat"
        pad_data = []
        print "  SCAN"
        nskip = max(1, nskip)
        # save table
        fh = open(data_file, "w")
        print "  Writing table with betas to %s" % data_file
        print>>fh, "# ionization from orbital %s   IE = %6.3f eV" % (orbital_names[iorb], ionization_energies[iorb]*AtomicData.hartree_to_eV)
        print>>fh, "# inter_atomic: %s  npts_euler: %s  npts_theta: %s  rmax: %s" % (inter_atomic, npts_euler, npts_theta, Rmax)
        print>>fh, "# PKE/eV     sigma          beta1          beta2      beta3          beta4"
        for i,E in enumerate(slako_tables_scattering.energies):
            if i % nskip != 0:
                continue
            print "    PKE = %6.6f Hartree  (%4.4f eV)" % (E, E*AtomicData.hartree_to_eV)
            k = np.sqrt(2*E)
            wavelength = 2.0 * np.pi/k
            bs_free = AtomicScatteringBasisSet(atomlist, E, rmin=0.0, rmax=Rmax+2*wavelength, Npts=Npts)
            SKT_bf, SKT_ff = load_slako_scattering(atomlist, E)
            Dipole = ScatteringDipoleMatrix(atomlist, valorbs, SKT_bf, inter_atomic=inter_atomic).real

            orientation_averaging = PAD.OrientationAveraging_small_memory(Dipole, bs_free, Rmax, E, npts_euler=npts_euler, npts_theta=npts_theta)

            pad,betasE = orientation_averaging.averaged_pad(dyson_orbs[:,iorb])
            pad_data.append( [E*AtomicData.hartree_to_eV] + list(betasE) )
            # save PAD for this energy
            print>>fh, "%10.6f   %10.6e  %+10.6e  %+10.6f  %+10.6e  %+10.6e" % tuple(pad_data[-1])
            fh.flush()
        fh.close()
def test_photoangular_distribution():
    from DFTB.LR_TDDFTB import LR_TDDFTB
    from DFTB import XYZ
    from DFTB.Scattering import slako_tables_scattering

    # BOUND ORBITAL = H**O
    atomlist = XYZ.read_xyz("./h2.xyz")[0]
    tddftb = LR_TDDFTB(atomlist)
    tddftb.setGeometry(atomlist, charge=0)
    options = {"nstates": 1}
    tddftb.getEnergies(**options)

    valorbs, radial_val = load_pseudo_atoms(atomlist)

    H**O, LUMO = tddftb.dftb2.getFrontierOrbitals()
    bound_orbs = tddftb.dftb2.getKSCoefficients()

    # according to Koopman's theorem the electron is ionized from the H**O
    mo_indx = range(0, len(bound_orbs))
    nmo = len(mo_indx)
    energies = [[] for i in range(0, nmo)]
    sigmas = [[] for i in range(0, nmo)]
    betas = [[] for i in range(0, nmo)]
    tdip2s = [[] for i in range(0, nmo)]
    for imo in range(0, nmo):
        mo_bound = bound_orbs[:, mo_indx[imo]]

        # CONTINUUM ORBITALS at energy E
        for E in slako_tables_scattering.energies:

            SKT_bf, SKT_ff = load_slako_scattering(atomlist, E)
            S_bb, H0_bb = tddftb.dftb2._constructH0andS()
            S_bf, H0_bf = ScatteringHamiltonianMatrix(atomlist, valorbs,
                                                      SKT_bf)
            #
            invS_bb = la.inv(S_bb)
            # (H-E*S)^t . Id . (H-E*S)
            HmE2 = np.dot(H0_bf.conjugate().transpose(), np.dot(invS_bb, H0_bf)) \
                   - E * np.dot( S_bf.conjugate().transpose(), np.dot(invS_bb, H0_bf)) \
                   - E * np.dot(H0_bf.conjugate().transpose(), np.dot(invS_bb,  S_bf)) \
                   + E**2 * np.dot(S_bf.conjugate().transpose(), np.dot(invS_bb, S_bf))
            #

            scat_lambdas, scat_orbs = sla.eigh(HmE2)
            print "PKE = %s" % E
            print "lambdas = %s" % scat_lambdas

            # photoangular distribution averaged over isotropically oriented ensemble of molecules
            Dipole = ScatteringDipoleMatrix(atomlist, valorbs, SKT_bf)
            sigma = 0.0
            beta = 0.0
            tdip2 = 0.0
            olap = 0.0
            for i in range(0, len(scat_lambdas)):
                if abs(scat_lambdas[i]) > 1.0e-10:
                    print "%d  lambda = %s" % (i, scat_lambdas[i])
                    mo_scatt = scat_orbs[:, i]
                    sigma_i, beta_i = angular_distribution(
                        atomlist, valorbs, mo_bound, mo_scatt, Dipole)
                    sigma += sigma_i
                    beta += sigma_i * beta_i

                    tdip_i = np.zeros(3, dtype=complex)
                    for xyz in range(0, 3):
                        tdip_i[xyz] += np.dot(
                            mo_bound, np.dot(Dipole[:, :, xyz], mo_scatt))
                    tdip2 += np.sum(abs(tdip_i)**2)

            print "tdip2 = %s" % tdip2
            beta /= sigma

            ####
            #sigma_test, beta_test = angular_distribution_old2(atomlist, valorbs, mo_bound, mo_scatt, Dipole)
            #assert abs(sigma-sigma_test) < 1.0e-10
            #assert abs(beta-beta_test) < 1.0e-10
            ####
            print "E= %s   sigma=%s   beta= %s" % (E, sigma, beta)
            energies[imo].append(E)
            sigmas[imo].append(sigma.real)
            betas[imo].append(beta.real)
            tdip2s[imo].append(tdip2)

    energies = np.array(energies)
    sigmas = np.array(sigmas)
    betas = np.array(betas)

    from matplotlib import pyplot as plt
    # total cross section
    plt.xlabel("PKE / eV")
    plt.ylabel("total photoionization cross section $\sigma$")
    for imo in range(0, nmo):
        plt.plot(energies[imo] * 27.211, sigmas[imo], lw=2)
        #plt.plot(energies[imo]*27.211, tdip2s[imo], lw=2, ls="-.")
    plt.show()
    # anisotropy
    plt.cla()

    plt.ylim((-1.0, 2.0))
    plt.xlabel("PKE / eV")
    plt.ylabel("anisotropy $\\beta_2$")
    for imo in range(0, nmo):
        plt.plot(energies[imo] * 27.211, betas[imo], lw=2, label="%d" % imo)
    plt.legend()
    plt.show()

    return energies, sigmas, betas
def test_scattering_orbitals():
    from DFTB.LR_TDDFTB import LR_TDDFTB
    from DFTB import XYZ

    atomlist = XYZ.read_xyz("h2.xyz")[0]

    tddftb = LR_TDDFTB(atomlist)
    tddftb.setGeometry(atomlist, charge=0)
    options = {"nstates": 1}
    tddftb.getEnergies(**options)

    valorbs, radial_val = load_pseudo_atoms(atomlist)

    E = 5.0 / 27.211
    bs = AtomicScatteringBasisSet(atomlist, E)
    print bs.bfs

    SKT_bf, SKT_ff = load_slako_scattering(atomlist, E)
    S_bb, H0_bb = tddftb.dftb2._constructH0andS()
    S_bf, H0_bf = ScatteringHamiltonianMatrix(atomlist, valorbs, SKT_bf)
    #
    invS_bb = la.inv(S_bb)
    # (H-E*S)^t . Id . (H-E*S)
    HmE2 = np.dot(H0_bf.conjugate().transpose(), np.dot(invS_bb, H0_bf)) \
           - E * np.dot( S_bf.conjugate().transpose(), np.dot(invS_bb, H0_bf)) \
           - E * np.dot(H0_bf.conjugate().transpose(), np.dot(invS_bb,  S_bf)) \
           + E**2 * np.dot(S_bf.conjugate().transpose(), np.dot(invS_bb, S_bf))
    Scont = continuum_flux(atomlist, SKT_bf)
    S2 = np.dot(S_bf.conjugate().transpose(), np.dot(la.inv(S_bb), S_bf))
    """
    #
    H2 = np.dot(H0_bf.transpose(), np.dot(la.inv(S_bb), H0_bf))
    S2 = np.dot( S_bf.transpose(), np.dot(la.inv(S_bb), S_bf))
    print "H2"
    print H2
    print "S2"
    print S2
    scat_orbe2, scat_orbs = sla.eig(H2) #, S2)
    print "PKE = %s" % E
    print "Energies^2 = %s" % scat_orbe2
    scat_orbe = np.sqrt(scat_orbe2)
    sort_indx = np.argsort(scat_orbe)
    scat_orbe = scat_orbe[sort_indx]
    scat_orbs = scat_orbs[:,sort_indx]
    print "Energies of scattering orbitals: %s" % scat_orbe
    orbE = np.argmin(abs(scat_orbe-E))
    """
    assert np.sum(abs(HmE2.conjugate().transpose() - HmE2)) < 1.0e-10
    assert np.sum(abs(S2.conjugate().transpose() - S2)) < 1.0e-10
    lambdas, scat_orbs = sla.eigh(HmE2)
    print "lambdas = %s" % lambdas

    from DFTB.Scattering import PAD

    for i in range(0, len(lambdas)):
        if abs(lambdas[i]) > 1.0e-8:
            print "%d  lambda = %s" % (i, lambdas[i])

            ###
            def wavefunction(grid, dV):
                # evaluate orbital
                amp = Cube.orbital_amplitude(grid,
                                             bs.bfs,
                                             scat_orbs[:, i],
                                             cache=False)
                return amp

            PAD.asymptotic_density(wavefunction, 20, E)
            ###

            for (flm, l, m) in classify_lm(bs, scat_orbs[:, i]):
                if abs(flm).max() > 1.0e-4:
                    print " %s %s     %s" % (l, m, abs(flm))
            Cube.orbital2grid(atomlist, bs.bfs, scat_orbs[:,i], \
                          filename="/tmp/scattering_orbital_%d.cube" % i, dbuff=25.0)
            delattr(Cube.orbital_amplitude, "cached_grid")
Exemple #11
0
def atomic_pz_orbital(Z, data_file):
    atomlist = [(Z, (0.0, 0.0, 0.0))]

    # compute molecular orbitals with DFTB
    print "compute molecular orbitals with tight-binding DFT"
    tddftb = LR_TDDFTB(atomlist)
    tddftb.setGeometry(atomlist, charge=0)
    options = {"nstates": 1}
    try:
        tddftb.getEnergies(**options)
    except DFTB.Solver.ExcitedStatesNotConverged:
        pass

    valorbs, radial_val = load_pseudo_atoms(atomlist)
    bound_orbs = tddftb.dftb2.getKSCoefficients()
    # order of orbitals s, py,pz,px, dxy,dyz,dz2,dzx,dx2y2, so the pz-orbital has index 2
    mo_bound = bound_orbs[:, 2]
    tdipole_data = []
    for iE, E in enumerate(slako_tables_scattering.energies):
        print "PKE = %s" % E
        try:
            SKT_bf, SKT_ff = load_slako_scattering(atomlist, E)
        except ImportError:
            break
        Dipole = ScatteringDipoleMatrix(atomlist, valorbs, SKT_bf).real

        # dipole between bound orbital and the continuum AO basis orbitals
        dipole_bf = np.tensordot(mo_bound, Dipole, axes=(0, 0))
        tdip_pz_to_s = dipole_bf[0, 2]  # points along z-axis
        tdip_pz_to_dyz = dipole_bf[5, 1]  # points along y-axis
        tdip_pz_to_dz2 = dipole_bf[6, 2]  # points along z-axis
        tdip_pz_to_dzx = dipole_bf[7, 0]  # points along x-axis

        tdipole_data.append(
            [E * AtomicData.hartree_to_eV] +
            [tdip_pz_to_s, tdip_pz_to_dyz, tdip_pz_to_dz2, tdip_pz_to_dzx])

        ####
        # determine the radius of the sphere where the angular distribution is calculated. It should be
        # much larger than the extent of the molecule
        (xmin, xmax), (ymin, ymax), (zmin, zmax) = Cube.get_bbox(atomlist,
                                                                 dbuff=0.0)
        dx, dy, dz = xmax - xmin, ymax - ymin, zmax - zmin
        Rmax = max([dx, dy, dz]) + 500.0
        print "Radius of sphere around molecule, Rmax = %s bohr" % Rmax

        k = np.sqrt(2 * E)
        wavelength = 2.0 * np.pi / k
        print "wavelength = %s" % wavelength
        valorbsE, radial_valE = load_pseudo_atoms_scattering(atomlist,
                                                             E,
                                                             rmin=0.0,
                                                             rmax=Rmax +
                                                             2 * wavelength,
                                                             Npts=90000)

        # Plot radial wavefunctions
        import matplotlib.pyplot as plt
        plt.ion()
        r = np.linspace(0.0, Rmax + 2 * wavelength, 5000)
        for i, (Zi, posi) in enumerate(atomlist):
            for indx, (ni, li, mi) in enumerate(valorbsE[Zi]):
                # only plot the dz2 continuum orbital
                if li == 2 and mi == 0 and (iE % 10 == 0):
                    R_spl = radial_valE[Zi][indx]
                    radial_orbital_wfn = R_spl(r)
                    plt.plot(r,
                             radial_orbital_wfn,
                             label="Z=%s n=%s l=%s m=%s E=%s" %
                             (Zi, ni, li, mi, E))
                    plt.plot(r, np.sin(k * r) / r, ls="-.")
                    #plt.plot(r, np.sin(k*r + 1.0/k * np.log(2*k*r))/r, ls="--", lw=2)
                    plt.plot(r,
                             np.array([
                                 float(mpmath.coulombf(li, -1.0 / k, k * rx)) /
                                 rx for rx in r
                             ]),
                             ls="--",
                             lw=2,
                             label="CoulombF l=%s E=%s" % (li, E))
        plt.draw()
        ####

    # save table
    fh = open(data_file, "w")
    print >> fh, "# PKE/eV                   pz->s                    pz->dyz                  pz->dz2                  pz->dzx"
    np.savetxt(fh, tdipole_data)
    fh.close()
    print "Wrote table with transition dipoles to %s" % data_file

    # show radial wavefunctions
    plt.ioff()
    plt.show()
Exemple #12
0
def test_dftb_charge_derivative():
    """compare analytical and numerical gradients of Mulliken charges"""
    from DFTB.XYZ import read_xyz, extract_keywords_xyz
    from DFTB.DFTB2 import DFTB2
    from DFTB.Analyse.Cube import CubeExporterEx
    from DFTB.Molden import MoldenExporter
    from DFTB import utils
    from DFTB.LR_TDDFTB import LR_TDDFTB
    from DFTB.ExcGradients import Gradients

    import sys
    import os

    usage = "Usage: %s <xyz-file>\n" % sys.argv[0]
    usage += "   --help option will give more information\n"

    parser = utils.OptionParserFuncWrapper([\
       DFTB2.__init__, DFTB2.runSCC, \
       LR_TDDFTB.getEnergies, LR_TDDFTB.saveAbsorptionSpectrum, LR_TDDFTB.analyseParticleHole, \
       LR_TDDFTB.graphical_analysis, \
       CubeExporterEx.exportCubes, MoldenExporter.export, \
       Gradients.getGradients], \
                usage)

    (options, args) = parser.parse_args(DFTB2.__init__)

    if len(args) < 1:
        print usage
        exit(-1)

    xyz_file = args[0]
    atomlist = read_xyz(xyz_file)[0]
    kwds = extract_keywords_xyz(xyz_file)

    tddftb = LR_TDDFTB(atomlist, **options)

    (options, args) = parser.parse_args(tddftb.getEnergies)
    (scf_options, args) = parser.parse_args(tddftb.dftb2.runSCC)
    options.update(scf_options)
    tddftb.setGeometry(atomlist, charge=kwds.get("charge", 0.0))
    tddftb.getEnergies(**options)

    grad = Gradients(tddftb)
    grad.gradient(I=0, save_intermediates_CPKS=1)

    dQdp_ana = grad.getChargeGradients()

    dftb2 = tddftb.dftb2
    dQdp_num = dftb_numerical_charge_gradients(dftb2, atomlist)
    print "partial Mulliken charges"
    print dftb2.getPartialCharges()
    print "numerical charge gradients"
    print dQdp_num
    print "analytical charge gradients"
    print dQdp_ana
    print "difference"
    print dQdp_num - dQdp_ana

    err_dQ = la.norm(dQdp_num - dQdp_ana)
    print "err(dQdp) = %s" % err_dQ
    #assert err_dQ < 1.0e-4, "err(dQdp) = %s" % err_dQ

    # show timings
    print T
Exemple #13
0
class PotentialEnergySurfaces(object):
    def __init__(self, atomlist, Nst=2, **kwds):
        """
        The DFTB module has many parameters which can be set from the command
        line using options, e.g. --scf_conv=1.0e-10. 
        During the initialization 
           - the command line arguments are parsed for options
           - Slater-Koster tables and repulsive potentials
             are loaded for the atom pair present in the molecule

        Parameters:
        ===========
        atomlist: list of tuples (Zi,[xi,yi,zi]) for each atom in the molecule
        Nst: number of electronic states (including ground state)
        """
        usage = "Type --help to show all options for DFTB"

        parser = optparse.OptionParserFuncWrapper([
            DFTB2.__init__, DFTB2.runSCC, SolventCavity.__init__,
            LR_TDDFTB.getEnergies
        ],
                                                  usage,
                                                  section_headers=["DFTBaby"],
                                                  unknown_options="ignore")
        options, args = parser.parse_args(DFTB2.__init__)
        self.atomlist = atomlist
        self.tddftb = LR_TDDFTB(atomlist, **options)

        solvent_options, args = parser.parse_args(SolventCavity.__init__)
        solvent_cavity = SolventCavity(**solvent_options)
        self.tddftb.dftb2.setSolventCavity(solvent_cavity)

        self.grads = Gradients(self.tddftb)
        self.tddftb.setGeometry(atomlist, charge=kwds.get("charge", 0.0))
        self.options, args = parser.parse_args(self.tddftb.getEnergies)
        self.scf_options, args = parser.parse_args(self.tddftb.dftb2.runSCC)
        self.options.update(self.scf_options)
        self.Nst = Nst
        #        # always use iterative diagonalizer for lowest Nst-1 excited states
        self.options["nstates"] = Nst - 1
        # save geometry, orbitals and TD-DFT coefficients from
        # last calculation
        self.last_calculation = None
        # save transition dipoles from last calculation
        self.tdip_old = None

    def getEnergies(self, x):
        """
        This functions computes the adiabatic energies ONLY. It should
        not be used during a dynamics simulation
        """
        self.atomlist = XYZ.vector2atomlist(x, self.atomlist)
        self.tddftb.setGeometry(self.atomlist,
                                keep_charge=True,
                                update_symmetry=False)
        self.tddftb.getEnergies(**self.options)
        # total ground state energy
        E0 = self.tddftb.dftb2.E_tot
        # excitation energies
        Exc = self.tddftb.getExcEnergies()
        # total energies of ground and excited states
        energies = np.hstack(([E0], E0 + Exc))
        return energies[:self.Nst]

    def getEnergy_S0(self, x):
        """
        This function compute the ground state energy ONLY.
        """
        self.atomlist = XYZ.vector2atomlist(x, self.atomlist)
        self.tddftb.setGeometry(self.atomlist,
                                keep_charge=True,
                                update_symmetry=False)
        E0 = self.tddftb.dftb2.getEnergy(**self.scf_options)
        energies = np.array([E0])  # only ground state energy
        return energies

    @T.timer
    def getEnergiesAndGradient(self, x, gradient_state):
        """
        This function computes the adiabatic electronic energies
        for the nuclear geometry contained in 'x' and the gradient
        of the total energy of state 'gradient_state'.

        Parameters:
        ===========
        x: a numpy array with the nuclear coordinates, 
           x[3*i:3*(i+1)] should contain the cartesian coordinates of atom i
        gradient_state: index of the electronic state, whose gradient should 
           be calculated. 0 stands for the ground state, 1 for the 1st excited
           state, etc.

        Returns:
        ========
        energies: numpy array with total energies (in Hartree) for all electronic
           states
        gradTot: total gradient of the selected state,
           gradTot[3*i:3*(i+1)] contains the energy derivative w/r/t the 3 cartesian
           coordinates of atom i.
        """
        self.atomlist = XYZ.vector2atomlist(x, self.atomlist)
        self.tddftb.setGeometry(self.atomlist,
                                keep_charge=True,
                                update_symmetry=False)
        self.tddftb.getEnergies(**self.options)
        # total ground state energy
        E0 = self.tddftb.dftb2.E_tot
        # excitation energies
        Exc = self.tddftb.getExcEnergies()
        # total energies of ground and excited states
        energies = np.hstack(([E0], E0 + Exc))

        gradVrep, gradE0, gradExc = self.grads.gradient(
            I=gradient_state, save_intermediates_CPKS=1)
        gradTot = gradVrep + gradE0 + gradExc
        # QM/MM
        if self.tddftb.dftb2.qmmm != None:
            # expand gradient to the full system
            gradTot = self.tddftb.dftb2.qmmm.getGradientFull(gradTot)
        # confining cavity
        if self.tddftb.dftb2.cavity != None:
            if self.tddftb.dftb2.qmmm != None:
                atomlist = self.tddftb.dftb2.qmmm.getGeometryFull()
            else:
                atomlist = self.atomlist
            gradTot += self.tddftb.dftb2.cavity.getGradient(atomlist)
        return energies, gradTot

    def getEnergyAndGradient_S0(self, x, has_scf=False):
        """
        obtain the ground state energy and gradient while skipping an excited state calculation.

        This function should be called with has_scf=True directly after a failed TD-DFTB calculation. Then the scf calculation is skipped as well, assuming that the converged ground state orbitals are available.

        Returns:
        ========
        E0: ground state energy
        gradTot: gradient on ground state
        """
        # ground state energy
        if has_scf == True:
            # SCF calculation has been done already
            E0 = self.tddftb.dftb2.E_tot
        else:
            self.atomlist = XYZ.vector2atomlist(x, self.atomlist)
            self.tddftb.setGeometry(self.atomlist,
                                    keep_charge=True,
                                    update_symmetry=False)
            # SCF calculation
            E0 = self.tddftb.dftb2.getEnergy(**self.scf_options)
        # try to compute gradient on ground state
        gradVrep, gradE0, gradExc = self.grads.gradient(
            I=0, save_intermediates_CPKS=1)
        gradTot = gradVrep + gradE0 + gradExc
        # QM/MM
        if self.tddftb.dftb2.qmmm != None:
            # expand gradient to the full system
            gradTot = self.tddftb.dftb2.qmmm.getGradientFull(gradTot)
        # confining cavity
        if self.tddftb.dftb2.cavity != None:
            if self.tddftb.dftb2.qmmm != None:
                atomlist = self.tddftb.dftb2.qmmm.getGeometryFull()
            else:
                atomlist = self.atomlist
            gradTot += self.tddftb.dftb2.cavity.getGradient(atomlist)

        return E0, gradTot

    def resetXY(self):
        # reset excitation vectors, so that in the next iteration
        # we start with a clean guess
        self.tddftb.XmY = None
        self.tddftb.XpY = None

    def resetSCF(self):
        # set Mulliken partial charges to zero and density matrix P to
        # reference density matrix P0, so that in the next iteration
        # we start with a clean guess
        self.tddftb.dftb2.dq *= 0.0
        self.tddftb.dftb2.P = self.tddftb.dftb2.density_matrix_ref()

    def getScalarCouplings(self, threshold=0.01):
        """
        Parameters:
        ===========
        threshold: smaller excitation coefficients are neglected in the calculation
           of overlaps

        Returns:
        ========
        coupl: antisymmetric matrix with scalar non-adiabatic coupling
          coupl[A,B] ~ <Psi_A|d/dt Psi_B>*dt

        The coupling matrix has to be divided by the nuclear time step dt

        It is important that this function is called
        exactly once after calling 'getEnergiesAndGradient'
        """
        atomlist2 = self.atomlist
        orbs2 = self.tddftb.dftb2.orbs
        C2 = self.tddftb.Cij[:self.Nst -
                             1, :, :]  # only save the states of interest
        # off-diagonal elements of electronic hamiltonian
        SC = ScalarCoupling(self.tddftb)
        # retrieve last calculation
        if self.last_calculation == None:
            # This is the first calculation, so use the same data
            atomlist1, orbs1, C1 = atomlist2, orbs2, C2
        else:
            atomlist1, orbs1, C1 = self.last_calculation
        Sci = SC.ci_overlap(atomlist1,
                            orbs1,
                            C1,
                            atomlist2,
                            orbs2,
                            C2,
                            self.Nst,
                            threshold=threshold)
        # align phases
        # The eigenvalue solver produces vectors with arbitrary global phases
        # (+1 or -1). The orbitals of the ground state can also change their signs.
        # Eigen states from neighbouring geometries should change continuously.
        signs = np.sign(np.diag(Sci))
        self.P = np.diag(signs)
        # align C2 with C1
        C2 = np.tensordot(self.P[1:, :][:, 1:], C2, axes=(0, 0))
        # align overlap matrix
        Sci = np.dot(Sci, self.P)
        # The relative signs for the overlap between the ground and excited states at different geometries
        # cannot be deduced from the diagonal elements of Sci. The phases are chosen such that the coupling
        # between S0 and S1-SN changes smoothly for most of the states.
        if hasattr(self, "last_coupl"):
            s = np.sign(self.last_coupl[0, 1:] / Sci[0, 1:])
            w = np.abs(Sci[0, 1:] - self.last_coupl[0, 1:])
            mean_sign = np.sign(np.sum(w * s) / np.sum(w))
            sign0I = mean_sign
            for I in range(1, self.Nst):
                Sci[0, I] *= sign0I
                Sci[I, 0] *= sign0I
        #
        state_labels = [("S%d" % I) for I in range(0, self.Nst)]
        if self.tddftb.dftb2.verbose > 0:
            print "Overlap <Psi(t)|Psi(t+dt)>"
            print "=========================="
            print utils.annotated_matrix(Sci, state_labels, state_labels)
        # overlap between wavefunctions at different time steps
        olap = np.copy(Sci)
        coupl = Sci
        # coupl[A,B] = <Psi_A(t)|Psi_B(t+dt)> - delta_AB
        #            ~ <Psi_A(t)|d/dR Psi_B(t)>*dR/dt dt
        # TEST
        # The scalar coupling matrix should be more or less anti-symmetric
        # provided the time-step is small enough
        # set diagonal elements of coupl to zero
        coupl[np.diag_indices_from(coupl)] = 0.0
        err = np.sum(abs(coupl + coupl.transpose()))
        if err > 1.0e-1:
            print "WARNING: Scalar coupling matrix is not antisymmetric, error = %s" % err
        #
        # Because of the finite time-step it will not be completely antisymmetric,
        # so antisymmetrize it
        coupl = 0.5 * (coupl - coupl.transpose())
        # save coupling for establishing relative phases
        self.last_coupl = coupl
        state_labels = [("S%d" % I) for I in range(0, self.Nst)]
        if self.tddftb.dftb2.verbose > 0:
            print "Scalar Coupling"
            print "==============="
            print utils.annotated_matrix(coupl, state_labels, state_labels)

        # save last calculation
        self.last_calculation = (atomlist2, orbs2, C2)
        return coupl, olap

    def saveLastCalculation(self):
        """
        save geometry, MO coefficients and TD-DFTB coefficients of last calculation.
        This data is needed to align the phases between neighbouring steps.
        """
        atomlist = self.atomlist
        orbs = self.tddftb.dftb2.orbs
        # only save the states of interest
        C = self.tddftb.Cij[:self.Nst - 1, :, :]
        # save last calculation
        self.last_calculation = (atomlist, orbs, C)

    def alignCoefficients(self, c):
        """
        align phases of coefficients of adiabatic states
        """
        if hasattr(self, "P"):
            c = np.dot(self.P[1:, :][:, 1:], c)
        return c

    def getTransitionDipoles(self):
        """
        computes and aligns transition dipoles between all Nst states
        This function should be called only once directly after getScalarCouplings!
        """
        tdip = self.tddftb.getTransitionDipolesExc(self.Nst)
        # eigenvectors can have arbitrary phases
        # align transition dipoles with old ones
        tdip_old = self.tdip_old
        if type(tdip_old) == type(None):
            tdip_old = tdip
        for A in range(0, self.Nst):
            for B in range(0, self.Nst):
                sign = np.dot(tdip_old[A, B, :], tdip[A, B, :])
                if sign < 0.0:
                    tdip[A, B, :] *= -1.0
        self.tdip_old = tdip
        if self.tddftb.dftb2.verbose > 0:
            state_labels = [("S%d" % I) for I in range(0, self.Nst)]
            print "Transition Dipoles"
            print "=================="
            print "X-Component"
            print utils.annotated_matrix(tdip[:, :, 0], state_labels,
                                         state_labels)
            print "Y-Component"
            print utils.annotated_matrix(tdip[:, :, 1], state_labels,
                                         state_labels)
            print "Z-Component"
            print utils.annotated_matrix(tdip[:, :, 2], state_labels,
                                         state_labels)
        #
        return tdip
def calculate_charge_derivative():
    """compute analytical gradients of Mulliken charges"""
    #
    # This long prologue serves for reading all options
    # from the command line or the dftbaby.cfg file.
    # The command-line option
    #
    #    --cpks_solver='direct' or 'iterative'
    #
    # determines whether the full CPKS matrix should be constructed ('direct')
    # or whether the system of linear equations should be solved iteratively using the Krylov
    # subspace method.
    #
    from DFTB.XYZ import read_xyz, extract_keywords_xyz
    from DFTB.DFTB2 import DFTB2
    from DFTB import utils
    from DFTB.LR_TDDFTB import LR_TDDFTB
    from DFTB.ExcGradients import Gradients

    import sys
    import os

    usage = "Usage: %s <xyz-file>\n" % sys.argv[0]
    usage += "   --help option will give more information\n"

    parser = utils.OptionParserFuncWrapper([\
       DFTB2.__init__, DFTB2.runSCC, \
       LR_TDDFTB.getEnergies, LR_TDDFTB.saveAbsorptionSpectrum, LR_TDDFTB.analyseParticleHole, \
       Gradients.getGradients], \
                usage)

    (options, args) = parser.parse_args(DFTB2.__init__)

    if len(args) < 1:
        print usage
        exit(-1)

    xyz_file = args[0]
    atomlist = read_xyz(xyz_file)[0]
    # number of atoms
    Nat = len(atomlist)
    kwds = extract_keywords_xyz(xyz_file)

    tddftb = LR_TDDFTB(atomlist, **options)

    (options, args) = parser.parse_args(tddftb.getEnergies)
    (scf_options, args) = parser.parse_args(tddftb.dftb2.runSCC)
    options.update(scf_options)
    tddftb.setGeometry(atomlist, charge=kwds.get("charge", 0.0))
    tddftb.getEnergies(**options)

    #
    # The important part starts here.
    #
    grad = Gradients(tddftb)
    # A CPKS calculation has to be preceeded by a gradient calculation.
    # The option `save_intermediates_CPKS=1` tells the program to save intermediate
    # variables that are needed later during the CPKS calculation.
    grad.gradient(I=0, save_intermediates_CPKS=1)

    # This runs a CPKS calculation and computes the gradients of the charges
    dQdp = grad.getChargeGradients()
    # The gradient of the charge on atom B w/r/t the position of atom A is
    #   d(Q_B)/dR_A = dQdp[3*A:3*(A+1), B]

    A = 0  # first atom
    B = Nat - 1  # last atom
    print "Gradient of Mulliken charge on atom A=%d w/r/t nucleus B=%d  d(Q_B)/dR_A = %s" \
        % (A, B, dQdp[3*A:3*(A+1), B])
Exemple #15
0
    def __init__(self, xyz_file, dyson_file=None):
        super(Main, self).__init__()
        self.settings = Settings({
            "Continuum Orbital": {
                "Ionization transitions":
                [0, ["only intra-atomic", "inter-atomic"]]
            },
            "Averaging": {
                "Euler angle grid points": 5,
                "polar angle grid points": 1000,
                "sphere radius Rmax": 300.0,
            },
            "Scan": {
                "nr. points": 20
            },
            "Cube": {
                "extra space / bohr": 15.0,
                "points per bohr": 3.0
            }
        })
        # perform DFTB calculation

        # BOUND ORBITAL = H**O
        self.atomlist = XYZ.read_xyz(xyz_file)[0]
        # shift molecule to center of mass
        print "shift molecule to center of mass"
        pos = XYZ.atomlist2vector(self.atomlist)
        masses = AtomicData.atomlist2masses(self.atomlist)
        pos_com = MolCo.shift_to_com(pos, masses)
        self.atomlist = XYZ.vector2atomlist(pos_com, self.atomlist)

        self.tddftb = LR_TDDFTB(self.atomlist)
        self.tddftb.setGeometry(self.atomlist, charge=0)
        options = {"nstates": 1}
        try:
            self.tddftb.getEnergies(**options)
        except DFTB.Solver.ExcitedStatesNotConverged:
            pass

        self.valorbs, radial_val = load_pseudo_atoms(self.atomlist)

        if dyson_file == None:
            # Kohn-Sham orbitals are taken as Dyson orbitals
            self.H**O, self.LUMO = self.tddftb.dftb2.getFrontierOrbitals()
            self.bound_orbs = self.tddftb.dftb2.getKSCoefficients()
            self.orbe = self.tddftb.dftb2.getKSEnergies()
            orbital_names = []
            norb = len(self.orbe)
            for o in range(0, norb):
                if o < self.H**O:
                    name = "occup."
                elif o == self.H**O:
                    name = "H**O"
                elif o == self.LUMO:
                    name = "LUMO "
                else:
                    name = "virtual"
                name = name + "  " + str(o).rjust(4) + (
                    "   %+10.3f eV" % (self.orbe[o] * 27.211))
                orbital_names.append(name)
            initially_selected = self.H**O
        else:
            # load coefficients of Dyson orbitals from file
            names, ionization_energies, self.bound_orbs = load_dyson_orbitals(
                dyson_file)
            self.orbe = np.array(ionization_energies) / 27.211
            orbital_names = []
            norb = len(self.orbe)
            for o in range(0, norb):
                name = names[o] + "  " + str(o).rjust(4) + (
                    "   %4.2f eV" % (self.orbe[o] * 27.211))
                orbital_names.append(name)
            initially_selected = 0

        self.photo_kinetic_energy = slako_tables_scattering.energies[0]
        self.epol = np.array([15.0, 0.0, 0.0])

        # Build Graphical User Interface
        main = QtGui.QWidget()
        mainLayout = QtGui.QHBoxLayout(main)
        #
        selectionFrame = QtGui.QFrame()
        selectionFrame.setSizePolicy(QtGui.QSizePolicy.Fixed,
                                     QtGui.QSizePolicy.Preferred)
        mainLayout.addWidget(selectionFrame)
        selectionLayout = QtGui.QVBoxLayout(selectionFrame)
        #
        label = QtGui.QLabel(selectionFrame)
        label.setText("Select bound MO:")
        selectionLayout.addWidget(label)

        # bound orbitals
        self.orbitalSelection = QtGui.QListWidget(selectionFrame)
        self.orbitalSelection.itemSelectionChanged.connect(
            self.selectBoundOrbital)
        norb = len(self.orbe)
        self.orbital_dict = {}
        for o in range(0, norb):
            name = orbital_names[o]
            self.orbital_dict[name] = o
            item = QtGui.QListWidgetItem(name, self.orbitalSelection)
            if o == initially_selected:
                selected_orbital_item = item
            selectionLayout.addWidget(self.orbitalSelection)

        ### VIEWS
        center = QtGui.QWidget()
        mainLayout.addWidget(center)
        centerLayout = QtGui.QGridLayout(center)
        #
        boundFrame = QtGui.QFrame()

        centerLayout.addWidget(boundFrame, 1, 1)
        boundLayout = QtGui.QVBoxLayout(boundFrame)
        # "Bound Orbital"
        label = QtGui.QLabel(boundFrame)
        label.setText("Bound Orbital")
        boundLayout.addWidget(label)
        #
        self.boundOrbitalViewer = QCubeViewerWidget(boundFrame)
        boundLayout.addWidget(self.boundOrbitalViewer)

        # continuum orbital
        continuumFrame = QtGui.QFrame()
        centerLayout.addWidget(continuumFrame, 1, 2)
        continuumLayout = QtGui.QVBoxLayout(continuumFrame)
        # "Dipole-Prepared Continuum Orbital"
        label = QtGui.QLabel(continuumFrame)
        label.setText("Dipole-Prepared Continuum Orbital")
        continuumLayout.addWidget(label)

        self.continuumOrbitalViewer = QCubeViewerWidget(continuumFrame)
        continuumLayout.addWidget(self.continuumOrbitalViewer)

        self.efield_objects = []
        self.efield_actors = []
        self.selected = None
        # picker
        self.picker = self.continuumOrbitalViewer.visualization.scene.mayavi_scene.on_mouse_pick(
            self.picker_callback)
        self.picker.tolerance = 0.01

        # PHOTO KINETIC ENERGY
        sliderFrame = QtGui.QFrame(continuumFrame)
        continuumLayout.addWidget(sliderFrame)
        sliderLayout = QtGui.QHBoxLayout(sliderFrame)
        # label
        self.pke_label = QtGui.QLabel()
        self.pke_label.setText("PKE: %6.4f eV" %
                               (self.photo_kinetic_energy * 27.211))
        sliderLayout.addWidget(self.pke_label)
        # Slider for changing the PKE
        self.pke_slider = QtGui.QSlider(QtCore.Qt.Horizontal)
        self.pke_slider.setMinimum(0)
        self.pke_slider.setMaximum(len(slako_tables_scattering.energies) - 1)
        self.pke_slider.setValue(0)
        self.pke_slider.sliderReleased.connect(self.changePKE)
        self.pke_slider.valueChanged.connect(self.searchPKE)
        sliderLayout.addWidget(self.pke_slider)

        #

        # molecular frame photoangular distribution
        mfpadFrame = QtGui.QFrame()
        centerLayout.addWidget(mfpadFrame, 2, 1)
        mfpadLayout = QtGui.QVBoxLayout(mfpadFrame)
        mfpadLayout.addWidget(QtGui.QLabel("Molecular Frame PAD"))
        mfpadTabs = QtGui.QTabWidget()
        mfpadLayout.addWidget(mfpadTabs)
        # 2D map
        mfpadFrame2D = QtGui.QFrame()
        mfpadTabs.addTab(mfpadFrame2D, "2D")
        mfpadLayout2D = QtGui.QVBoxLayout(mfpadFrame2D)
        self.MFPADfig2D = Figure()
        self.MFPADCanvas2D = FigureCanvas(self.MFPADfig2D)
        mfpadLayout2D.addWidget(self.MFPADCanvas2D)
        self.MFPADCanvas2D.draw()
        NavigationToolbar(self.MFPADCanvas2D, mfpadFrame2D, coordinates=True)
        # 3D
        mfpadFrame3D = QtGui.QFrame()
        mfpadTabs.addTab(mfpadFrame3D, "3D")
        mfpadLayout3D = QtGui.QVBoxLayout(mfpadFrame3D)
        self.MFPADfig3D = Figure()
        self.MFPADCanvas3D = FigureCanvas(self.MFPADfig3D)
        mfpadLayout3D.addWidget(self.MFPADCanvas3D)
        self.MFPADCanvas3D.draw()
        NavigationToolbar(self.MFPADCanvas3D, mfpadFrame3D, coordinates=True)

        # orientation averaged photoangular distribution
        avgpadFrame = QtGui.QFrame()
        centerLayout.addWidget(avgpadFrame, 2, 2)
        avgpadLayout = QtGui.QVBoxLayout(avgpadFrame)
        self.activate_average = QtGui.QCheckBox("Orientation Averaged PAD")
        self.activate_average.setToolTip(
            "Check this box to start averaging of the molecular frame PADs over all orientations. This can take a while."
        )
        self.activate_average.setCheckState(QtCore.Qt.Unchecked)
        self.activate_average.stateChanged.connect(self.activateAveragedPAD)
        avgpadLayout.addWidget(self.activate_average)

        avgpadTabs = QtGui.QTabWidget()
        avgpadLayout.addWidget(avgpadTabs)
        # 1D map
        avgpadFrame1D = QtGui.QFrame()
        avgpadTabs.addTab(avgpadFrame1D, "1D")
        avgpadLayout1D = QtGui.QVBoxLayout(avgpadFrame1D)
        self.AvgPADfig1D = Figure()
        self.AvgPADCanvas1D = FigureCanvas(self.AvgPADfig1D)
        avgpadLayout1D.addWidget(self.AvgPADCanvas1D)
        self.AvgPADCanvas1D.draw()
        NavigationToolbar(self.AvgPADCanvas1D, avgpadFrame1D, coordinates=True)
        # 2D map
        avgpadFrame2D = QtGui.QFrame()
        avgpadFrame2D.setToolTip(
            "The averaged PAD should have no phi-dependence anymore. A phi-dependence is a sign of incomplete averaging."
        )
        avgpadTabs.addTab(avgpadFrame2D, "2D")
        avgpadLayout2D = QtGui.QVBoxLayout(avgpadFrame2D)
        self.AvgPADfig2D = Figure()
        self.AvgPADCanvas2D = FigureCanvas(self.AvgPADfig2D)
        avgpadLayout2D.addWidget(self.AvgPADCanvas2D)
        self.AvgPADCanvas2D.draw()
        NavigationToolbar(self.AvgPADCanvas2D, avgpadFrame2D, coordinates=True)
        # Table
        avgpadFrameTable = QtGui.QFrame()
        avgpadTabs.addTab(avgpadFrameTable, "Table")
        avgpadLayoutTable = QtGui.QVBoxLayout(avgpadFrameTable)
        self.avgpadTable = QtGui.QTableWidget(0, 6)
        self.avgpadTable.setToolTip(
            "Activate averaging and move the PKE slider above to add a new row with beta values. After collecting betas for different energies you can save the table or plot a curve beta(PKE) for the selected orbital."
        )
        self.avgpadTable.setHorizontalHeaderLabels(
            ["PKE / eV", "sigma", "beta1", "beta2", "beta3", "beta4"])
        avgpadLayoutTable.addWidget(self.avgpadTable)
        # Buttons
        buttonFrame = QtGui.QFrame()
        avgpadLayoutTable.addWidget(buttonFrame)
        buttonLayout = QtGui.QHBoxLayout(buttonFrame)
        deleteButton = QtGui.QPushButton("Delete")
        deleteButton.setToolTip("clear table")
        deleteButton.clicked.connect(self.deletePADTable)
        buttonLayout.addWidget(deleteButton)
        buttonLayout.addSpacing(3)
        scanButton = QtGui.QPushButton("Scan")
        scanButton.setToolTip(
            "fill table by scanning automatically through all PKE values")
        scanButton.clicked.connect(self.scanPADTable)
        buttonLayout.addWidget(scanButton)
        saveButton = QtGui.QPushButton("Save")
        saveButton.setToolTip("save table as a text file")
        saveButton.clicked.connect(self.savePADTable)
        buttonLayout.addWidget(saveButton)
        plotButton = QtGui.QPushButton("Plot")
        plotButton.setToolTip("plot beta2 column as a function of PKE")
        plotButton.clicked.connect(self.plotPADTable)
        buttonLayout.addWidget(plotButton)
        """
        # DOCKS
        self.setDockOptions(QtGui.QMainWindow.AnimatedDocks | QtGui.QMainWindow.AllowNestedDocks)
        #
        selectionDock = QtGui.QDockWidget(self)
        selectionDock.setWidget(selectionFrame)
        selectionDock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable)
        self.addDockWidget(QtCore.Qt.DockWidgetArea(1), selectionDock)
        #
        boundDock = QtGui.QDockWidget(self)
        boundDock.setWidget(boundFrame)
        boundDock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable)
        boundDock.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        self.addDockWidget(QtCore.Qt.DockWidgetArea(2), boundDock)
        # 
        continuumDock = QtGui.QDockWidget(self)
        continuumDock.setWidget(continuumFrame)
        continuumDock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable)
        continuumDock.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        self.addDockWidget(QtCore.Qt.DockWidgetArea(2), continuumDock)
        """
        self.setCentralWidget(main)

        self.status_bar = QtGui.QStatusBar(main)
        self.setStatusBar(self.status_bar)
        self.default_message = "Click on the tip of the green arrow in the top right figure to change the orientation of the E-field"
        self.statusBar().showMessage(self.default_message)

        # Menu bar
        menubar = self.menuBar()
        exitAction = QtGui.QAction('&Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit program')
        exitAction.triggered.connect(exit)
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(exitAction)

        settingsMenu = menubar.addMenu('&Edit')
        settingsAction = QtGui.QAction('&Settings...', self)
        settingsAction.setStatusTip('Edit settings')
        settingsAction.triggered.connect(self.editSettings)
        settingsMenu.addAction(settingsAction)

        self.loadContinuum()
        # select H**O
        selected_orbital_item.setSelected(True)
Exemple #16
0
class Main(QtGui.QMainWindow):
    def __init__(self, xyz_file, dyson_file=None):
        super(Main, self).__init__()
        self.settings = Settings({
            "Continuum Orbital": {
                "Ionization transitions":
                [0, ["only intra-atomic", "inter-atomic"]]
            },
            "Averaging": {
                "Euler angle grid points": 5,
                "polar angle grid points": 1000,
                "sphere radius Rmax": 300.0,
            },
            "Scan": {
                "nr. points": 20
            },
            "Cube": {
                "extra space / bohr": 15.0,
                "points per bohr": 3.0
            }
        })
        # perform DFTB calculation

        # BOUND ORBITAL = H**O
        self.atomlist = XYZ.read_xyz(xyz_file)[0]
        # shift molecule to center of mass
        print "shift molecule to center of mass"
        pos = XYZ.atomlist2vector(self.atomlist)
        masses = AtomicData.atomlist2masses(self.atomlist)
        pos_com = MolCo.shift_to_com(pos, masses)
        self.atomlist = XYZ.vector2atomlist(pos_com, self.atomlist)

        self.tddftb = LR_TDDFTB(self.atomlist)
        self.tddftb.setGeometry(self.atomlist, charge=0)
        options = {"nstates": 1}
        try:
            self.tddftb.getEnergies(**options)
        except DFTB.Solver.ExcitedStatesNotConverged:
            pass

        self.valorbs, radial_val = load_pseudo_atoms(self.atomlist)

        if dyson_file == None:
            # Kohn-Sham orbitals are taken as Dyson orbitals
            self.H**O, self.LUMO = self.tddftb.dftb2.getFrontierOrbitals()
            self.bound_orbs = self.tddftb.dftb2.getKSCoefficients()
            self.orbe = self.tddftb.dftb2.getKSEnergies()
            orbital_names = []
            norb = len(self.orbe)
            for o in range(0, norb):
                if o < self.H**O:
                    name = "occup."
                elif o == self.H**O:
                    name = "H**O"
                elif o == self.LUMO:
                    name = "LUMO "
                else:
                    name = "virtual"
                name = name + "  " + str(o).rjust(4) + (
                    "   %+10.3f eV" % (self.orbe[o] * 27.211))
                orbital_names.append(name)
            initially_selected = self.H**O
        else:
            # load coefficients of Dyson orbitals from file
            names, ionization_energies, self.bound_orbs = load_dyson_orbitals(
                dyson_file)
            self.orbe = np.array(ionization_energies) / 27.211
            orbital_names = []
            norb = len(self.orbe)
            for o in range(0, norb):
                name = names[o] + "  " + str(o).rjust(4) + (
                    "   %4.2f eV" % (self.orbe[o] * 27.211))
                orbital_names.append(name)
            initially_selected = 0

        self.photo_kinetic_energy = slako_tables_scattering.energies[0]
        self.epol = np.array([15.0, 0.0, 0.0])

        # Build Graphical User Interface
        main = QtGui.QWidget()
        mainLayout = QtGui.QHBoxLayout(main)
        #
        selectionFrame = QtGui.QFrame()
        selectionFrame.setSizePolicy(QtGui.QSizePolicy.Fixed,
                                     QtGui.QSizePolicy.Preferred)
        mainLayout.addWidget(selectionFrame)
        selectionLayout = QtGui.QVBoxLayout(selectionFrame)
        #
        label = QtGui.QLabel(selectionFrame)
        label.setText("Select bound MO:")
        selectionLayout.addWidget(label)

        # bound orbitals
        self.orbitalSelection = QtGui.QListWidget(selectionFrame)
        self.orbitalSelection.itemSelectionChanged.connect(
            self.selectBoundOrbital)
        norb = len(self.orbe)
        self.orbital_dict = {}
        for o in range(0, norb):
            name = orbital_names[o]
            self.orbital_dict[name] = o
            item = QtGui.QListWidgetItem(name, self.orbitalSelection)
            if o == initially_selected:
                selected_orbital_item = item
            selectionLayout.addWidget(self.orbitalSelection)

        ### VIEWS
        center = QtGui.QWidget()
        mainLayout.addWidget(center)
        centerLayout = QtGui.QGridLayout(center)
        #
        boundFrame = QtGui.QFrame()

        centerLayout.addWidget(boundFrame, 1, 1)
        boundLayout = QtGui.QVBoxLayout(boundFrame)
        # "Bound Orbital"
        label = QtGui.QLabel(boundFrame)
        label.setText("Bound Orbital")
        boundLayout.addWidget(label)
        #
        self.boundOrbitalViewer = QCubeViewerWidget(boundFrame)
        boundLayout.addWidget(self.boundOrbitalViewer)

        # continuum orbital
        continuumFrame = QtGui.QFrame()
        centerLayout.addWidget(continuumFrame, 1, 2)
        continuumLayout = QtGui.QVBoxLayout(continuumFrame)
        # "Dipole-Prepared Continuum Orbital"
        label = QtGui.QLabel(continuumFrame)
        label.setText("Dipole-Prepared Continuum Orbital")
        continuumLayout.addWidget(label)

        self.continuumOrbitalViewer = QCubeViewerWidget(continuumFrame)
        continuumLayout.addWidget(self.continuumOrbitalViewer)

        self.efield_objects = []
        self.efield_actors = []
        self.selected = None
        # picker
        self.picker = self.continuumOrbitalViewer.visualization.scene.mayavi_scene.on_mouse_pick(
            self.picker_callback)
        self.picker.tolerance = 0.01

        # PHOTO KINETIC ENERGY
        sliderFrame = QtGui.QFrame(continuumFrame)
        continuumLayout.addWidget(sliderFrame)
        sliderLayout = QtGui.QHBoxLayout(sliderFrame)
        # label
        self.pke_label = QtGui.QLabel()
        self.pke_label.setText("PKE: %6.4f eV" %
                               (self.photo_kinetic_energy * 27.211))
        sliderLayout.addWidget(self.pke_label)
        # Slider for changing the PKE
        self.pke_slider = QtGui.QSlider(QtCore.Qt.Horizontal)
        self.pke_slider.setMinimum(0)
        self.pke_slider.setMaximum(len(slako_tables_scattering.energies) - 1)
        self.pke_slider.setValue(0)
        self.pke_slider.sliderReleased.connect(self.changePKE)
        self.pke_slider.valueChanged.connect(self.searchPKE)
        sliderLayout.addWidget(self.pke_slider)

        #

        # molecular frame photoangular distribution
        mfpadFrame = QtGui.QFrame()
        centerLayout.addWidget(mfpadFrame, 2, 1)
        mfpadLayout = QtGui.QVBoxLayout(mfpadFrame)
        mfpadLayout.addWidget(QtGui.QLabel("Molecular Frame PAD"))
        mfpadTabs = QtGui.QTabWidget()
        mfpadLayout.addWidget(mfpadTabs)
        # 2D map
        mfpadFrame2D = QtGui.QFrame()
        mfpadTabs.addTab(mfpadFrame2D, "2D")
        mfpadLayout2D = QtGui.QVBoxLayout(mfpadFrame2D)
        self.MFPADfig2D = Figure()
        self.MFPADCanvas2D = FigureCanvas(self.MFPADfig2D)
        mfpadLayout2D.addWidget(self.MFPADCanvas2D)
        self.MFPADCanvas2D.draw()
        NavigationToolbar(self.MFPADCanvas2D, mfpadFrame2D, coordinates=True)
        # 3D
        mfpadFrame3D = QtGui.QFrame()
        mfpadTabs.addTab(mfpadFrame3D, "3D")
        mfpadLayout3D = QtGui.QVBoxLayout(mfpadFrame3D)
        self.MFPADfig3D = Figure()
        self.MFPADCanvas3D = FigureCanvas(self.MFPADfig3D)
        mfpadLayout3D.addWidget(self.MFPADCanvas3D)
        self.MFPADCanvas3D.draw()
        NavigationToolbar(self.MFPADCanvas3D, mfpadFrame3D, coordinates=True)

        # orientation averaged photoangular distribution
        avgpadFrame = QtGui.QFrame()
        centerLayout.addWidget(avgpadFrame, 2, 2)
        avgpadLayout = QtGui.QVBoxLayout(avgpadFrame)
        self.activate_average = QtGui.QCheckBox("Orientation Averaged PAD")
        self.activate_average.setToolTip(
            "Check this box to start averaging of the molecular frame PADs over all orientations. This can take a while."
        )
        self.activate_average.setCheckState(QtCore.Qt.Unchecked)
        self.activate_average.stateChanged.connect(self.activateAveragedPAD)
        avgpadLayout.addWidget(self.activate_average)

        avgpadTabs = QtGui.QTabWidget()
        avgpadLayout.addWidget(avgpadTabs)
        # 1D map
        avgpadFrame1D = QtGui.QFrame()
        avgpadTabs.addTab(avgpadFrame1D, "1D")
        avgpadLayout1D = QtGui.QVBoxLayout(avgpadFrame1D)
        self.AvgPADfig1D = Figure()
        self.AvgPADCanvas1D = FigureCanvas(self.AvgPADfig1D)
        avgpadLayout1D.addWidget(self.AvgPADCanvas1D)
        self.AvgPADCanvas1D.draw()
        NavigationToolbar(self.AvgPADCanvas1D, avgpadFrame1D, coordinates=True)
        # 2D map
        avgpadFrame2D = QtGui.QFrame()
        avgpadFrame2D.setToolTip(
            "The averaged PAD should have no phi-dependence anymore. A phi-dependence is a sign of incomplete averaging."
        )
        avgpadTabs.addTab(avgpadFrame2D, "2D")
        avgpadLayout2D = QtGui.QVBoxLayout(avgpadFrame2D)
        self.AvgPADfig2D = Figure()
        self.AvgPADCanvas2D = FigureCanvas(self.AvgPADfig2D)
        avgpadLayout2D.addWidget(self.AvgPADCanvas2D)
        self.AvgPADCanvas2D.draw()
        NavigationToolbar(self.AvgPADCanvas2D, avgpadFrame2D, coordinates=True)
        # Table
        avgpadFrameTable = QtGui.QFrame()
        avgpadTabs.addTab(avgpadFrameTable, "Table")
        avgpadLayoutTable = QtGui.QVBoxLayout(avgpadFrameTable)
        self.avgpadTable = QtGui.QTableWidget(0, 6)
        self.avgpadTable.setToolTip(
            "Activate averaging and move the PKE slider above to add a new row with beta values. After collecting betas for different energies you can save the table or plot a curve beta(PKE) for the selected orbital."
        )
        self.avgpadTable.setHorizontalHeaderLabels(
            ["PKE / eV", "sigma", "beta1", "beta2", "beta3", "beta4"])
        avgpadLayoutTable.addWidget(self.avgpadTable)
        # Buttons
        buttonFrame = QtGui.QFrame()
        avgpadLayoutTable.addWidget(buttonFrame)
        buttonLayout = QtGui.QHBoxLayout(buttonFrame)
        deleteButton = QtGui.QPushButton("Delete")
        deleteButton.setToolTip("clear table")
        deleteButton.clicked.connect(self.deletePADTable)
        buttonLayout.addWidget(deleteButton)
        buttonLayout.addSpacing(3)
        scanButton = QtGui.QPushButton("Scan")
        scanButton.setToolTip(
            "fill table by scanning automatically through all PKE values")
        scanButton.clicked.connect(self.scanPADTable)
        buttonLayout.addWidget(scanButton)
        saveButton = QtGui.QPushButton("Save")
        saveButton.setToolTip("save table as a text file")
        saveButton.clicked.connect(self.savePADTable)
        buttonLayout.addWidget(saveButton)
        plotButton = QtGui.QPushButton("Plot")
        plotButton.setToolTip("plot beta2 column as a function of PKE")
        plotButton.clicked.connect(self.plotPADTable)
        buttonLayout.addWidget(plotButton)
        """
        # DOCKS
        self.setDockOptions(QtGui.QMainWindow.AnimatedDocks | QtGui.QMainWindow.AllowNestedDocks)
        #
        selectionDock = QtGui.QDockWidget(self)
        selectionDock.setWidget(selectionFrame)
        selectionDock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable)
        self.addDockWidget(QtCore.Qt.DockWidgetArea(1), selectionDock)
        #
        boundDock = QtGui.QDockWidget(self)
        boundDock.setWidget(boundFrame)
        boundDock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable)
        boundDock.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        self.addDockWidget(QtCore.Qt.DockWidgetArea(2), boundDock)
        # 
        continuumDock = QtGui.QDockWidget(self)
        continuumDock.setWidget(continuumFrame)
        continuumDock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable)
        continuumDock.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        self.addDockWidget(QtCore.Qt.DockWidgetArea(2), continuumDock)
        """
        self.setCentralWidget(main)

        self.status_bar = QtGui.QStatusBar(main)
        self.setStatusBar(self.status_bar)
        self.default_message = "Click on the tip of the green arrow in the top right figure to change the orientation of the E-field"
        self.statusBar().showMessage(self.default_message)

        # Menu bar
        menubar = self.menuBar()
        exitAction = QtGui.QAction('&Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit program')
        exitAction.triggered.connect(exit)
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(exitAction)

        settingsMenu = menubar.addMenu('&Edit')
        settingsAction = QtGui.QAction('&Settings...', self)
        settingsAction.setStatusTip('Edit settings')
        settingsAction.triggered.connect(self.editSettings)
        settingsMenu.addAction(settingsAction)

        self.loadContinuum()
        # select H**O
        selected_orbital_item.setSelected(True)

    def loadContinuum(self):
        E = self.photo_kinetic_energy
        k = np.sqrt(2 * E)
        wavelength = 2.0 * np.pi / k
        # determine the radius of the sphere where the angular distribution is calculated. It should be
        # much larger than the extent of the molecule
        (xmin, xmax), (ymin, ymax), (zmin, zmax) = Cube.get_bbox(self.atomlist,
                                                                 dbuff=0.0)
        dx, dy, dz = xmax - xmin, ymax - ymin, zmax - zmin
        Rmax0 = self.settings.getOption("Averaging", "sphere radius Rmax")
        Rmax = max([dx, dy, dz]) + Rmax0
        Npts = max(int(Rmax), 1) * 50
        print "Radius of sphere around molecule, Rmax = %s bohr" % Rmax
        print "Points on radial grid, Npts = %d" % Npts

        self.bs_free = AtomicScatteringBasisSet(self.atomlist,
                                                E,
                                                rmin=0.0,
                                                rmax=Rmax + 2 * wavelength,
                                                Npts=Npts)
        self.SKT_bf, SKT_ff = load_slako_scattering(self.atomlist, E)
        if self.settings.getOption("Continuum Orbital",
                                   "Ionization transitions") == "inter-atomic":
            inter_atomic = True
        else:
            inter_atomic = False
        print "inter-atomic transitions: %s" % inter_atomic
        self.Dipole = ScatteringDipoleMatrix(self.atomlist,
                                             self.valorbs,
                                             self.SKT_bf,
                                             inter_atomic=inter_atomic).real
        #
        if self.activate_average.isChecked():
            print "ORIENTATION AVERAGING"
            npts_euler = self.settings.getOption("Averaging",
                                                 "Euler angle grid points")
            npts_theta = self.settings.getOption("Averaging",
                                                 "polar angle grid points")
            self.orientation_averaging = PAD.OrientationAveraging_small_memory(
                self.Dipole,
                self.bs_free,
                Rmax,
                E,
                npts_euler=npts_euler,
                npts_theta=npts_theta)
        else:
            print "NO AVERAGING"

    def searchPKE(self):
        self.photo_kinetic_energy = slako_tables_scattering.energies[
            self.pke_slider.value()]
        self.pke_label.setText("PKE: %6.4f eV" %
                               (self.photo_kinetic_energy * 27.211))
        #self.pke_label.update()
    @busy
    def changePKE(self):
        self.photo_kinetic_energy = slako_tables_scattering.energies[
            self.pke_slider.value()]
        self.pke_label.setText("PKE: %6.4f eV" %
                               (self.photo_kinetic_energy * 27.211))
        self.loadContinuum()
        self.plotContinuumOrbital()
        self.plotMFPAD()
        self.plotAveragedPAD()

    @busy
    def selectBoundOrbital(self):
        self.plotBoundOrbital()
        self.plotContinuumOrbital()
        self.plotMFPAD()
        self.deletePADTable()
        self.plotAveragedPAD()

    def plotBoundOrbital(self):
        selected = self.orbitalSelection.selectedItems()
        assert len(selected) == 1
        selected_orbital = self.orbital_dict[str(selected[0].text())]

        self.mo_bound = self.bound_orbs[:, selected_orbital]
        # shift geometry so that the expectation value of the dipole operator vanishes
        # dipole matrix
        dipole = np.tensordot(self.mo_bound,
                              np.tensordot(self.tddftb.dftb2.D,
                                           self.mo_bound,
                                           axes=(1, 0)),
                              axes=(0, 0))
        print "expectation value of dipole: %s" % dipole
        # shift molecule R -> R - dipole
        self.atomlist = MolCo.transform_molecule(
            self.tddftb.dftb2.getGeometry(), (0, 0, 0), -dipole)
        # load bound basis functions
        self.bs_bound = AtomicBasisSet(self.atomlist)
        # plot selected orbital
        (xmin, xmax), (ymin, ymax), (zmin, zmax) = Cube.get_bbox(self.atomlist,
                                                                 dbuff=5.0)
        dx, dy, dz = xmax - xmin, ymax - ymin, zmax - zmin
        ppb = 3.0  # Points per bohr
        nx, ny, nz = int(dx * ppb), int(dy * ppb), int(dz * ppb)
        x, y, z = np.mgrid[xmin:xmax:nx * 1j, ymin:ymax:ny * 1j,
                           zmin:zmax:nz * 1j]
        grid = (x, y, z)
        amplitude_bound = Cube.orbital_amplitude(grid,
                                                 self.bs_bound.bfs,
                                                 self.mo_bound,
                                                 cache=False)

        bound_cube = CubeData()
        bound_cube.data = amplitude_bound.real
        bound_cube.grid = grid
        bound_cube.atomlist = self.atomlist

        self.boundOrbitalViewer.setCubes([bound_cube])

    def plotContinuumOrbital(self):
        dbuff = self.settings["Cube"]["extra space / bohr"]
        ppb = self.settings["Cube"]["points per bohr"]
        (xmin, xmax), (ymin, ymax), (zmin, zmax) = Cube.get_bbox(self.atomlist,
                                                                 dbuff=dbuff)
        dx, dy, dz = xmax - xmin, ymax - ymin, zmax - zmin
        nx, ny, nz = int(dx * ppb), int(dy * ppb), int(dz * ppb)
        x, y, z = np.mgrid[xmin:xmax:nx * 1j, ymin:ymax:ny * 1j,
                           zmin:zmax:nz * 1j]
        grid = (x, y, z)

        # plot continuum orbital
        # projection of dipoles onto polarization direction
        Dipole_projected = np.zeros(
            (self.Dipole.shape[0], self.Dipole.shape[1]))
        # normalize polarization
        epol_unit = self.epol / np.sqrt(np.dot(self.epol, self.epol))
        print "Polarization direction of E-field:"
        print "E (normalized) = %s" % epol_unit
        for xyz in [0, 1, 2]:
            Dipole_projected += self.Dipole[:, :, xyz] * epol_unit[xyz]
        #print "Dipole projected"
        #print Dipole_projected
        # unnormalized coefficients of dipole-prepared continuum orbitals
        self.mo_free = np.dot(self.mo_bound, Dipole_projected)
        nrm2 = np.dot(self.mo_free.conjugate(), self.mo_free)
        #print "nrm2 = %s" % nrm2
        # normalized coefficients
        self.mo_free /= np.sqrt(nrm2)

        amplitude_continuum = Cube.orbital_amplitude(grid,
                                                     self.bs_free.bfs,
                                                     self.mo_free,
                                                     cache=False)

        continuum_cube = CubeData()
        continuum_cube.data = amplitude_continuum.real
        continuum_cube.grid = grid
        continuum_cube.atomlist = self.atomlist

        self.continuumOrbitalViewer.setCubes([continuum_cube])

        # plot E-field
        for o in self.efield_objects:
            o.remove()
        mlab = self.continuumOrbitalViewer.visualization.scene.mlab
        #        self.efield_arrow = mlab.quiver3d(0,0,0, self.epol[0], self.epol[1], self.epol[2],
        self.efield_arrow = mlab.quiver3d(0,
                                          0,
                                          0,
                                          float(self.epol[0]),
                                          float(self.epol[1]),
                                          float(self.epol[2]),
                                          color=(0.0, 1.0, 0.0),
                                          scale_factor=1.0,
                                          mode='arrow',
                                          resolution=20,
                                          figure=self.continuumOrbitalViewer.
                                          visualization.scene.mayavi_scene)
        self.efield_text = mlab.text(self.epol[0],
                                     self.epol[1],
                                     "E-field",
                                     z=self.epol[2],
                                     figure=self.continuumOrbitalViewer.
                                     visualization.scene.mayavi_scene)
        self.efield_text.actor.set(text_scale_mode='none',
                                   width=0.05,
                                   height=0.1)
        self.efield_text.property.set(justification='centered',
                                      vertical_justification='centered')

        self.efield_head = mlab.points3d([self.epol[0]], [self.epol[1]],
                                         [self.epol[2]],
                                         scale_factor=0.5,
                                         mode='cube',
                                         resolution=20,
                                         color=(0.0, 1.0, 0.0),
                                         figure=self.continuumOrbitalViewer.
                                         visualization.scene.mayavi_scene)
        self.efield_head.glyph.glyph_source.glyph_source.center = [0, 0, 0]
        self.efield_outline = mlab.outline(line_width=3,
                                           figure=self.continuumOrbitalViewer.
                                           visualization.scene.mayavi_scene)
        self.efield_outline.outline_mode = 'cornered'
        w = 0.1
        self.efield_outline.bounds = (self.epol[0] - w, self.epol[0] + w,
                                      self.epol[1] - w, self.epol[1] + w,
                                      self.epol[2] - w, self.epol[2] + w)

        self.efield_objects = [
            self.efield_arrow, self.efield_text, self.efield_head,
            self.efield_outline
        ]
        self.efield_actors = [self.efield_head.actor.actors]

    def picker_callback(self, picker):
        for actors in self.efield_actors:
            if picker.actor in actors:
                mlab = self.continuumOrbitalViewer.visualization.scene.mlab
                self.selected = "arrow"
                w = 1.0
                self.efield_outline.bounds = (self.epol[0] - w, self.epol[0] +
                                              w, self.epol[1] - w,
                                              self.epol[1] + w, self.epol[2] -
                                              w, self.epol[2] + w)
                break
        else:
            #
            if self.selected != None:
                w = 0.1
                self.efield_outline.bounds = (self.epol[0] - w, self.epol[0] +
                                              w, self.epol[1] - w,
                                              self.epol[1] + w, self.epol[2] -
                                              w, self.epol[2] + w)

                self.epol = np.array(picker.pick_position)
                self.plotContinuumOrbital()
                self.plotMFPAD()
                self.selected = None

    def plotMFPAD(self):
        Rmax = 80.0
        npts = 30

        E = self.photo_kinetic_energy
        k = np.sqrt(2 * E)
        wavelength = 2.0 * np.pi / k

        # spherical grid
        rs, thetas, phis = np.mgrid[Rmax:(Rmax + wavelength):30j,
                                    0.0:np.pi:npts * 1j,
                                    0.0:2 * np.pi:npts * 1j]
        # transformed into cartesian coordinates
        xs = rs * np.sin(thetas) * np.cos(phis)
        ys = rs * np.sin(thetas) * np.sin(phis)
        zs = rs * np.cos(thetas)

        grid = (xs, ys, zs)
        amplitude_continuum = Cube.orbital_amplitude(grid,
                                                     self.bs_free.bfs,
                                                     self.mo_free,
                                                     cache=False)
        # integrate continuum orbital along radial-direction for 1 wavelength
        wfn2 = abs(amplitude_continuum)**2
        #
        dr = wavelength / 30.0
        wfn2_angular = np.sum(wfn2 * dr, axis=0)

        # SPHERICAL PLOT
        self.MFPADfig3D.clf()

        xs = wfn2_angular * np.sin(thetas[0, :, :]) * np.cos(phis[0, :, :])
        ys = wfn2_angular * np.sin(thetas[0, :, :]) * np.sin(phis[0, :, :])
        zs = wfn2_angular * np.cos(thetas[0, :, :])

        ax = self.MFPADfig3D.add_subplot(111, projection='3d')

        rmax = wfn2_angular.max() * 1.5
        ax.set_xlim((-rmax, rmax))
        ax.set_ylim((-rmax, rmax))
        ax.set_zlim((-rmax, rmax))

        ax.set_xlabel("x")
        ax.set_ylabel("y")
        ax.set_zlabel("z")
        # draw efield
        arrow_vec = wfn2_angular.max() * 1.2 * self.epol / la.norm(self.epol)
        arrow = Arrow3D([0.0, arrow_vec[0]], [0.0, arrow_vec[1]],
                        [0.0, arrow_vec[2]],
                        color=(0.0, 1.0, 0.0),
                        mutation_scale=20,
                        lw=2,
                        arrowstyle="-|>")
        ax.add_artist(arrow)
        ax.text(arrow_vec[0],
                arrow_vec[1],
                arrow_vec[2],
                "E-field",
                color=(0.0, 1.0, 0.0))

        ax.plot_surface(xs, ys, zs, rstride=1, cstride=1)
        ax.scatter(xs, ys, zs, color="k", s=20)
        ax.get_xaxis().set_ticks([])
        ax.get_yaxis().set_ticks([])
        ax.w_zaxis.set_ticks([])

        self.MFPADCanvas3D.draw()

        # 2D PLOT
        self.MFPADfig2D.clf()

        ax = self.MFPADfig2D.add_subplot(111)

        image = ax.imshow(np.fliplr(wfn2_angular.transpose()),
                          extent=[0.0, np.pi, 0.0, 2 * np.pi],
                          aspect=0.5,
                          origin='lower')
        ax.set_xlim((0.0, np.pi))
        ax.set_ylim((0.0, 2 * np.pi))
        ax.set_xlabel("$\\theta$")
        ax.set_ylabel("$\phi$")
        self.MFPADfig2D.colorbar(image)

        # show piercing points of E-field vector
        # ... coming out of the plane
        r = la.norm(self.epol)
        th_efield = np.arccos(self.epol[2] / r)
        phi_efield = np.arctan2(self.epol[1], self.epol[0]) + np.pi
        print "th, phi = %s %s" % (th_efield, phi_efield)
        ax.plot([th_efield], [phi_efield],
                "o",
                markersize=10,
                color=(0.0, 1.0, 0.0))
        ax.text(th_efield,
                phi_efield,
                "E-field",
                color=(0.0, 1.0, 0.0),
                ha="center",
                va="top")
        # ... going into the plane
        th_efield = np.arccos(-self.epol[2] / r)
        phi_efield = np.arctan2(-self.epol[1], -self.epol[0]) + np.pi
        print "- th, phi = %s %s" % (th_efield, phi_efield)
        ax.plot([th_efield], [phi_efield],
                "x",
                markersize=10,
                color=(0.0, 1.0, 0.0))

        self.MFPADCanvas2D.draw()

    @busy
    def activateAveragedPAD(self, s):
        print "s = %s" % s
        self.loadContinuum()
        self.plotAveragedPAD()

    @busy
    def plotAveragedPAD(self):
        if self.activate_average.isChecked() == False:
            for fig in [self.AvgPADfig1D, self.AvgPADfig2D]:
                fig.clf()
                ax = fig.add_subplot(111)
                ax.set_xlim((0.0, 1.0))
                ax.set_ylim((0.0, 1.0))
                ax.text(0.2, 0.6, "click checkbox to", fontsize=20)
                ax.text(0.2, 0.3, "activate averaging", fontsize=20)
                #self.AvgPADfig1D.draw()
        else:
            pad, betas = self.orientation_averaging.averaged_pad(self.mo_bound)

            # 1D plot
            # cut along any phi
            self.AvgPADfig1D.clf()
            ax1d = self.AvgPADfig1D.add_subplot(111, projection='polar')
            nx, ny = pad.shape
            thetas = np.linspace(0.0, np.pi, nx)
            for i in range(0, ny):
                ax1d.plot(thetas, pad[:, i], color="black")
                ax1d.plot(thetas + np.pi, pad[::-1, i], color="black")
            # plot PAD = sigma/4pi * (1 + beta2 * P2(cos(theta))) in read
            pad2 = betas[0] / (4.0 * np.pi) * (1.0 + betas[2] * 0.5 *
                                               (3 * np.cos(thetas)**2 - 1.0))
            print "max(pad) / max(pad2) = %s" % (pad.max() / pad2.max())
            ax1d.plot(thetas, pad2, color="red", ls="-.")
            ax1d.plot(thetas + np.pi, pad2[::-1], color="red", ls="-.")

            self.AvgPADCanvas1D.draw()
            # 2D plot
            self.AvgPADfig2D.clf()
            ax2d = self.AvgPADfig2D.add_subplot(111)
            image = ax2d.imshow(np.fliplr(pad.transpose()),
                                extent=[0.0, np.pi, 0.0, 2 * np.pi],
                                aspect=0.5,
                                origin='lower')
            ax2d.set_xlim((0.0, np.pi))
            ax2d.set_ylim((0.0, 2 * np.pi))
            ax2d.set_xlabel("$\\theta$")
            ax2d.set_ylabel("$\phi$")
            self.AvgPADfig2D.colorbar(image)
            self.AvgPADCanvas2D.draw()
            # Table
            n = self.avgpadTable.rowCount()
            self.avgpadTable.insertRow(n)
            self.avgpadTable.setItem(
                n, 0,
                QtGui.QTableWidgetItem("%6.4f" %
                                       (self.photo_kinetic_energy * 27.211)))
            for i, b in enumerate(betas):
                if i == 0:
                    # sigma
                    self.avgpadTable.setItem(
                        n, i + 1, QtGui.QTableWidgetItem("%e" % betas[i]))
                else:
                    self.avgpadTable.setItem(
                        n, i + 1, QtGui.QTableWidgetItem("%6.4f" % betas[i]))

    def deletePADTable(self):
        n = self.avgpadTable.rowCount()
        for i in range(0, n):
            self.avgpadTable.removeRow(0)

    def scanPADTable(self):
        if self.activate_average.isChecked() == False:
            self.statusBar().showMessage(
                "You have to activate averaging first before performing a scan!"
            )
            return
        self.deletePADTable()
        # scan through all PKE's and fill table
        print "SCAN"
        nskip = len(slako_tables_scattering.energies) / int(
            self.settings.getOption("Scan", "nr. points"))
        nskip = max(1, nskip)
        print "nskip = %s" % nskip
        for i, pke in enumerate(slako_tables_scattering.energies):
            if i % nskip != 0:
                continue
            print "*** PKE = %s Hartree ***" % pke
            self.pke_slider.setValue(i)
            self.changePKE()

    def getPADTable(self):
        m, n = self.avgpadTable.rowCount(), self.avgpadTable.columnCount()
        pad_data = np.zeros((m, n))
        for i in range(0, m):
            for j in range(0, n):
                item = self.avgpadTable.item(i, j)
                if item is not None:
                    pad_data[i, j] = float(item.text())
        return pad_data

    def savePADTable(self):
        data_file = QtGui.QFileDialog.getSaveFileName(self, 'Save Table', '',
                                                      '*')[0]
        pad_data = self.getPADTable()
        if str(data_file) != "":
            fh = open(data_file, "w")
            print >> fh, "# PKE/eV     sigma    beta1   beta2   beta3   beta4"
            np.savetxt(fh, pad_data)
            fh.close()
            print "Wrote table with betas to %s" % data_file

    def plotPADTable(self):
        pad_data = self.getPADTable()
        self.plot_window = FigurePopup(pad_data[:, 0], pad_data[:, 1],
                                       pad_data[:, 3])

    def editSettings(self):
        self.settings_window = SettingsPopup(self.settings)