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"
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 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"
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")
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()
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
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])
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)