def _computeMO(self, i): cube = CubeData() atomlist = self.tddftb.dftb2.getGeometry() (xmin, xmax), (ymin, ymax), (zmin, zmax) = Cube.get_bbox(atomlist, dbuff=5.0) dx, dy, dz = xmax - xmin, ymax - ymin, zmax - zmin ppb_text = self.ppbGrid.itemText(self.ppbGrid.currentIndex()) ppb = float(ppb_text.split()[1]) # 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) # orbs = self.tddftb.dftb2.getKSCoefficients() moGrid = Cube.orbital_amplitude(grid, self.bs.bfs, orbs[:, i], cache=True) mo_cube = CubeData() mo_cube.data = moGrid.real mo_cube.grid = grid mo_cube.atomlist = atomlist return mo_cube
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 _computeTransitionAndDifferenceDensity(self, I): Ptrans = self.tddftb.TransitionDensityMatrix(I) P0, PI = self.tddftb.ExcitedDensityMatrix(I) Pdif = PI - P0 cube = CubeData() atomlist = self.tddftb.dftb2.getGeometry() (xmin, xmax), (ymin, ymax), (zmin, zmax) = Cube.get_bbox(atomlist, dbuff=5.0) dx, dy, dz = xmax - xmin, ymax - ymin, zmax - zmin ppb_text = self.ppbGrid.itemText(self.ppbGrid.currentIndex()) ppb = float(ppb_text.split()[1]) # points per bohr #print "points per bohr: %s" % ppb 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) # transition density tdenseGrid = Cube.electron_density(grid, self.bs.bfs, Ptrans) tdense_cube = CubeData() tdense_cube.data = tdenseGrid tdense_cube.grid = grid tdense_cube.atomlist = atomlist ## approximate difference density based on particle and hole charges #dqI = self.partial_charges[I] #difdenseGrid = self.bs_aux.partial_density(dqI, 0.0*self.tddftb.dftb2.ddip, x,y,z) # exact difference density difdenseGrid = Cube.electron_density(grid, self.bs.bfs, Pdif) difdense_cube = CubeData() difdense_cube.data = difdenseGrid difdense_cube.grid = grid difdense_cube.atomlist = atomlist return tdense_cube, difdense_cube
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 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 transform_turbomole_orbitals(tm_dir, dyson_file, method="atomwise", selected_orbitals=None): """ The molecular orbitals from a Turbomole calculation are transformed into the basis used by DFTB. Since in DFTB there are much less atomic orbitals this transformation is not bijective and does not conserve the normalization and orthogonality among the MOs. Parameters: =========== tm_dir: directory with coord, basis and mos files dyson_file: transformed MO coefficients for DFTB are written to this file method: Method used to find the coefficients in the minimal basis 'atomwise': overlaps are only calculated between orbitals on the same atom (very fast). 'grid': the DFT orbital mo(r) and the minimal DFT atomic orbitals ao_i(r) are calculated on a grid and the coefficients are obtained by minimizing the deviation error(c_i) = integral |mo(r) - sum_i c_i ao_i(r)|^2 dr in a least square sense (slower, but more accurate). selected_orbitals: list of indeces (starting at 1, e.g. '[1,2,3]') of orbitals that should be transformed. If not set all orbitals are transformed. """ print "Reading geometry and basis from Turbomole directory %s" % tm_dir Data = Turbomole.parseTurbomole(tm_dir + "/coord") atomlist = Data["coord"] Data = Turbomole.parseTurbomole(tm_dir + "/basis") basis_data = Data["basis"] bs_gaussian = GaussianBasisSet(atomlist, basis_data) # load Turbomole orbitals try: Data = Turbomole.parseTurbomole(tm_dir + "/mos") orbe,orbs_turbomole = Data["scfmo"] except IOError: print "'mos' file not found! Maybe this is an open-shell calculation. Trying to read file 'alpha'" Data = Turbomole.parseTurbomole(tm_dir + "/alpha") orbe,orbs_turbomole = Data["uhfmo_alpha"] # Which orbitals should be transformed? if selected_orbitals == None: selected_mos = np.array(range(0, len(orbe)), dtype=int) else: selected_mos = np.array(eval(selected_orbitals), dtype=int)-1 nmo = len(selected_mos) # transform them to DFTB basis if method == "atomwise": T = bs_gaussian.getTransformation_GTO_to_DFTB() orbs = np.dot(T, orbs_turbomole[:,selected_mos]) elif method == "grid": # define grid for integration (xmin,xmax),(ymin,ymax),(zmin,zmax) = Cube.get_bbox(atomlist, dbuff=7.0) dx,dy,dz = xmax-xmin,ymax-ymin,zmax-zmin ppb = 5.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) dV = dx/float(nx-1) * dy/float(ny-1) * dz/float(nz-1) # numerical atomic DFTB orbitals on the grid bs_atomic = AtomicBasisSet(atomlist) aos = [bf.amp(x,y,z) for bf in bs_atomic.bfs] nao = len(aos) # overlaps S2_mn = <m|n> S2 = np.zeros((nao,nao)) for m in range(0, nao): S2[m,m] = np.sum(aos[m]*aos[m]*dV) for n in range(m+1,nao): S2[m,n] = np.sum(aos[m]*aos[n]*dV) S2[n,m] = S2[m,n] # DFT molecular orbitals on the grid mos = [Cube.orbital_amplitude(grid, bs_gaussian.bfs, orbs_turbomole[:,i]).real for i in selected_mos] # overlaps S1_mi = <m|i>, m is an atomic DFTB orbital, i a molecular DFT orbital S1 = np.zeros((nao,nmo)) for m in range(0, nao): for i in range(0, nmo): S1[m,i] = np.sum(aos[m]*mos[i]*dV) # Linear regression leads to the matrix equation # sum_n S2_mn c_ni = S1_mi # which has to be solved for the coefficients c_ni. orbs = la.solve(S2, S1) else: raise ValueError("Method should be 'atomwise' or 'grid' but not '%s'!" % method) # normalize orbitals, due to the incomplete transformation they are not necessarily # orthogonal dftb2 = DFTB2(atomlist) dftb2.setGeometry(atomlist) S = dftb2.getOverlapMatrix() for i in range(0, nmo): norm2 = np.dot(orbs[:,i], np.dot(S, orbs[:,i])) orbs[:,i] /= np.sqrt(norm2) # save orbitals xyz_file = "geometry.xyz" XYZ.write_xyz(xyz_file, [atomlist]) print "Molecular geometry was written to '%s'." % xyz_file ionization_energies = -orbe[selected_mos] save_dyson_orbitals(dyson_file, ["%d" % (i+1) for i in selected_mos], ionization_energies, orbs, mode="w") print "MO coefficients for DFTB were written to '%s'." % dyson_file
def h2_ion_averaged_pad_scan(energy_range, data_file, npts_r=60, rmax=300.0, lebedev_order=23, radial_grid_factor=3, units="eV-Mb", tdip_threshold=1.0e-5): """ compute the photoelectron angular distribution for an ensemble of istropically oriented H2+ ions. Parameters ---------- energy_range : numpy array with photoelectron kinetic energies (PKE) for which the PAD should be calculated data_file : path to file, a table with PKE, SIGMA and BETA is written Optional -------- npts_r : number of radial grid points for integration on interval [rmax,rmax+2pi/k] rmax : large radius at which the continuum orbitals can be matched with the asymptotic solution lebedev_order : order of Lebedev grid for angular integrals radial_grid_factor : factor by which the number of grid points is increased for integration on the interval [0,+inf] units : units for energies and photoionization cross section in output, 'eV-Mb' (eV and Megabarn) or 'a.u.' tdip_threshold : continuum orbitals |f> are neglected if their transition dipole moments mu = |<i|r|f>| to the initial orbital |i> are below this threshold. """ print "" print "*******************************************" print "* PHOTOELECTRON ANGULAR DISTRIBUTIONS *" print "*******************************************" print "" # bond length in bohr R = 2.0 # two protons Za = 1.0 Zb = 1.0 atomlist = [(1, (0, 0, -R / 2.0)), (1, (0, 0, +R / 2.0))] # 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 # increase rmax by the size of the molecule rmax += max([dx, dy, dz]) Npts = max(int(rmax), 1) * 50 print "Radius of sphere around molecule, rmax = %s bohr" % rmax print "Points on radial grid, Npts = %d" % Npts # load separation constants or tabulate them if the file 'separation_constants.dat' # does not exist Lsep = SeparationConstants(R, Za, Zb) try: Lsep.load_separation_constants() except IOError as err: nmax = 10 mmax = 3 print "generating separation constants..." nc2 = 200 energy_range = np.linspace(-35.0, 20.0, nc2) * 2.0 / R**2 Lsep.tabulate_separation_constants(energy_range, nmax=nmax, mmax=mmax) Lsep.load_separation_constants() ### DEBUG #Lsep.mmax = 1 #Lsep.nmax = 1 ### # wfn = DimerWavefunctions(R, Za, Zb) # compute the bound orbitals without any radial nor angular nodes, # the 1sigma_g orbital m, n, q = 0, 0, 0 trig = 'cos' energy, (Rfunc1, Sfunc1, Pfunc1), wavefunction1 = wfn.getBoundOrbital(m, n, trig, q) print "Energy: %s Hartree" % energy bound_orbital = WrapperWavefunction(wavefunction1) ### DEBUG # save cube file for initial bound orbital Cube.function_to_cubefile(atomlist, wavefunction1, filename="/tmp/h2+_bound_orbital_%d_%d_%d.cube" % (m, n, q), dbuff=10.0) ### # compute PADs for all energies pad_data = [] print " SCAN" print " Writing table with PAD to %s" % data_file # table headers header = "" header += "# npts_r: %s rmax: %s" % (npts_r, rmax) + '\n' header += "# lebedev_order: %s radial_grid_factor: %s" % ( lebedev_order, radial_grid_factor) + '\n' if units == "eV-Mb": header += "# PKE/eV SIGMA/Mb BETA2" + '\n' else: header += "# PKE/Eh SIGMA/bohr^2 BETA2" + '\n' # save table access_mode = MPI.MODE_WRONLY | MPI.MODE_CREATE fh = MPI.File.Open(comm, data_file, access_mode) if rank == 0: # only process 0 write the header fh.Write_ordered(header) else: fh.Write_ordered('') for i, energy in enumerate(energy_range): if i % size == rank: print " PKE = %6.6f Hartree (%4.4f eV)" % ( energy, energy * AtomicData.hartree_to_eV) # continuum orbitals at a given energy continuum_orbitals = [] # quantum numbers of continuum orbitals (m,n,trig) quantum_numbers = [] phase_shifts = [] # transition dipoles nc = 2 * (Lsep.mmax + 1) * ( Lsep.nmax + 1) - 1 # number of continuum orbitals at each energy dipoles_RSP = np.zeros((1, nc, 3)) # print "Continuum orbital m n P phase shift (in \pi rad)" print " # nodes in P cos(m*pi) or sin(m*pi) " print "-------------------------------------------------------------------------------------------------------" ic = 0 # enumerate continuum orbitals for m in range(0, Lsep.mmax + 1): if m > 0: # for m > 0, there are two solutions for P(phi): sin(m*phi) and cos(m*phi) Psolutions = ['cos', 'sin'] else: Psolutions = ['cos'] for trig in Psolutions: for n in range(0, Lsep.nmax + 1): Delta, ( Rfunc2, Sfunc2, Pfunc2), wavefunction2 = wfn.getContinuumOrbital( m, n, trig, energy) #print "energy= %s eV m= %d n= %d phase shift= %s \pi rad" % (energy*AtomicData.hartree_to_eV, m,n,Delta / np.pi) print " %3.1d %2.1d %2.1d %s %8.6f" % ( ic, m, n, trig, Delta) continuum_orbital = WrapperWavefunction(wavefunction2) continuum_orbitals.append(continuum_orbital) quantum_numbers.append((m, n, trig)) phase_shifts.append(Delta) ### DEBUG # save cube file for continuum orbital Cube.function_to_cubefile( atomlist, wavefunction2, filename="/tmp/h2+_continuum_orbital_%d_%d_%s.cube" % (m, n, trig), dbuff=10.0) ### # compute transition dipoles exploiting the factorization # of the wavefunction as R(xi)*S(eta)*P(phi). These integrals # are probably more accurate then those using Becke's integration scheme dipoles_RSP[0, ic, :] = wfn.transition_dipoles( Rfunc1, Sfunc1, Pfunc1, Rfunc2, Sfunc2, Pfunc2) ic += 1 print "" # transition dipoles between bound and free orbitals dipoles = transition_dipole_integrals( atomlist, [bound_orbital], continuum_orbitals, radial_grid_factor=radial_grid_factor, lebedev_order=lebedev_order) # Continuum orbitals with vanishing transition dipole moments to the initial orbital # do not contribute to the PAD. Filter out those continuum orbitals |f> for which # mu^2 = |<i|r|f>|^2 < threshold mu = np.sqrt(np.sum(dipoles[0, :, :]**2, axis=-1)) dipoles_important = dipoles[0, mu > tdip_threshold, :] continuum_orbitals_important = [ continuum_orbitals[i] for i in range(0, len(continuum_orbitals)) if mu[i] > tdip_threshold ] quantum_numbers_important = [ quantum_numbers[i] for i in range(0, len(continuum_orbitals)) if mu[i] > tdip_threshold ] phase_shifts_important = [[ phase_shifts[i] ] for i in range(0, len(continuum_orbitals)) if mu[i] > tdip_threshold] print " %d of %d continuum orbitals have non-vanishing transition dipoles " \ % (len(continuum_orbitals_important), len(continuum_orbitals)) print " to initial orbital (|<i|r|f>| > %e)" % tdip_threshold print " H2+ Quantum Numbers" print " ===================" row_labels = [ "orb. %2.1d" % a for a in range(0, len(quantum_numbers_important)) ] col_labels = ["M", "N", "Trig"] txt = annotated_matrix(np.array(quantum_numbers_important), row_labels, col_labels, format="%s") print txt print " Phase Shifts (in units of pi)" print " =============================" row_labels = [ "orb. %2.1d" % a for a in range(0, len(phase_shifts_important)) ] col_labels = ["Delta"] txt = annotated_matrix(np.array(phase_shifts_important) / np.pi, row_labels, col_labels, format=" %8.6f ") print txt print " Transition Dipoles" print " ==================" print " threshold = %e" % tdip_threshold row_labels = [ "orb. %2.1d" % a for a in range(0, len(quantum_numbers_important)) ] col_labels = ["X", "Y", "Z"] txt = annotated_matrix(dipoles_important, row_labels, col_labels) print txt # compare transition dipoles from two integration methods print "check transition dipoles between bound and continuum orbitals" ic = 0 # enumerate continuum orbitals for m in range(0, Lsep.mmax + 1): if m > 0: # for m > 0, there are two solutions for P(phi): sin(m*phi) and cos(m*phi) Psolutions = ['cos', 'sin'] else: Psolutions = ['cos'] for trig in Psolutions: for n in range(0, Lsep.nmax + 1): print "continuum orbital %d (m= %d n= %d trig= %s)" % ( ic, m, n, trig) print " from factorization R*S*P : %s" % dipoles_RSP[ 0, ic, :] print " Becke's integration : %s" % dipoles[ 0, ic, :] ic += 1 print "" # Cs, LMs = asymptotic_Ylm(continuum_orbitals_important, energy, rmax=rmax, npts_r=npts_r, lebedev_order=lebedev_order) # expand product of continuum orbitals into spherical harmonics of order L=0,2 Amo, LMs = angular_product_distribution( continuum_orbitals_important, energy, rmax=rmax, npts_r=npts_r, lebedev_order=lebedev_order) # compute PAD for ionization from orbital 0 (the bound orbital) sigma, beta = photoangular_distribution(dipoles_important, Amo, LMs, energy) if units == "eV-Mb": energy *= AtomicData.hartree_to_eV # convert cross section sigma from bohr^2 to Mb sigma *= AtomicData.bohr2_to_megabarn pad_data.append([energy, sigma, beta]) # save row with PAD for this energy to table row = "%10.6f %10.6e %+10.6e" % tuple(pad_data[-1]) + '\n' fh.Write_ordered(row) fh.Close() print " Photoelectron Angular Distribution" print " ==================================" print " units: %s" % units row_labels = [" " for en in energy_range] col_labels = ["energy", "sigma", "beta2"] txt = annotated_matrix(np.array(pad_data).real, row_labels, col_labels) print txt print "FINISHED"
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 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 atomic_ion_averaged_pad_scan(energy_range, data_file, npts_r=60, rmax=300.0, lebedev_order=23, radial_grid_factor=3, units="eV-Mb", tdip_threshold=1.0e-5): """ compute the photoelectron angular distribution for an ensemble of istropically oriented atomic ions. Parameters ---------- energy_range : numpy array with photoelectron kinetic energies (PKE) for which the PAD should be calculated data_file : path to file, a table with PKE, SIGMA and BETA is written Optional -------- npts_r : number of radial grid points for integration on interval [rmax,rmax+2pi/k] rmax : large radius at which the continuum orbitals can be matched with the asymptotic solution lebedev_order : order of Lebedev grid for angular integrals radial_grid_factor : factor by which the number of grid points is increased for integration on the interval [0,+inf] units : units for energies and photoionization cross section in output, 'eV-Mb' (eV and Megabarn) or 'a.u.' tdip_threshold : continuum orbitals |f> are neglected if their transition dipole moments mu = |<i|r|f>| to the initial orbital |i> are below this threshold. """ print "" print "*******************************************" print "* PHOTOELECTRON ANGULAR DISTRIBUTIONS *" print "*******************************************" print "" Z = 1 atomlist = [(Z, (0.0, 0.0, 0.0))] # 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 # increase rmax by the size of the molecule rmax += max([dx, dy, dz]) Npts = max(int(rmax), 1) * 50 print "Radius of sphere around molecule, rmax = %s bohr" % rmax print "Points on radial grid, Npts = %d" % Npts # load bound pseudoatoms basis = AtomicBasisSet(atomlist, confined=False) bound_orbital = basis.bfs[0] # compute PADs for all energies pad_data = [] print " SCAN" print " Writing table with PAD to %s" % data_file # table headers header = "" header += "# npts_r: %s rmax: %s" % (npts_r, rmax) + '\n' header += "# lebedev_order: %s radial_grid_factor: %s" % ( lebedev_order, radial_grid_factor) + '\n' if units == "eV-Mb": header += "# PKE/eV SIGMA/Mb BETA2" + '\n' else: header += "# PKE/Eh SIGMA/bohr^2 BETA2" + '\n' # save table access_mode = MPI.MODE_WRONLY | MPI.MODE_CREATE fh = MPI.File.Open(comm, data_file, access_mode) if rank == 0: # only process 0 write the header fh.Write_ordered(header) else: fh.Write_ordered('') for i, energy in enumerate(energy_range): if i % size == rank: print " PKE = %6.6f Hartree (%4.4f eV)" % ( energy, energy * AtomicData.hartree_to_eV) # continuum orbitals at a given energy continuum_orbitals = [] # quantum numbers of continuum orbitals (n,l,m) quantum_numbers = [] phase_shifts = [] print "compute atomic continuum orbitals ..." valorbs, radial_val, phase_shifts_val = load_pseudo_atoms_scattering( atomlist, energy, rmin=0.0, rmax=2 * rmax, Npts=100000, lmax=3) pos = np.array([0.0, 0.0, 0.0]) for indx, (n, l, m) in enumerate(valorbs[Z]): continuum_orbital = AtomicBasisFunction( Z, pos, n, l, m, radial_val[Z][indx], 0) continuum_orbitals.append(continuum_orbital) quantum_numbers.append((n, l, m)) phase_shifts.append(phase_shifts_val[Z][indx]) # transition dipoles between bound and free orbitals dipoles = transition_dipole_integrals( atomlist, [bound_orbital], continuum_orbitals, radial_grid_factor=radial_grid_factor, lebedev_order=lebedev_order) # Continuum orbitals with vanishing transition dipole moments to the initial orbital # do not contribute to the PAD. Filter out those continuum orbitals |f> for which # mu^2 = |<i|r|f>|^2 < threshold mu = np.sqrt(np.sum(dipoles[0, :, :]**2, axis=-1)) dipoles_important = dipoles[0, mu > tdip_threshold, :] continuum_orbitals_important = [ continuum_orbitals[i] for i in range(0, len(continuum_orbitals)) if mu[i] > tdip_threshold ] quantum_numbers_important = [ quantum_numbers[i] for i in range(0, len(continuum_orbitals)) if mu[i] > tdip_threshold ] phase_shifts_important = [[ phase_shifts[i] ] for i in range(0, len(continuum_orbitals)) if mu[i] > tdip_threshold] print " %d of %d continuum orbitals have non-vanishing transition dipoles " \ % (len(continuum_orbitals_important), len(continuum_orbitals)) print " to initial orbital (|<i|r|f>| > %e)" % tdip_threshold print " Quantum Numbers" print " ===============" row_labels = [ "orb. %2.1d" % a for a in range(0, len(quantum_numbers_important)) ] col_labels = ["N", "L", "M"] txt = annotated_matrix(np.array(quantum_numbers_important), row_labels, col_labels, format="%s") print txt print " Phase Shifts (in units of pi)" print " =============================" row_labels = [ "orb. %2.1d" % a for a in range(0, len(phase_shifts_important)) ] col_labels = ["Delta"] txt = annotated_matrix(np.array(phase_shifts_important) / np.pi, row_labels, col_labels, format=" %8.6f ") print txt print " Transition Dipoles" print " ==================" print " threshold = %e" % tdip_threshold row_labels = [ "orb. %2.1d" % a for a in range(0, len(quantum_numbers_important)) ] col_labels = ["X", "Y", "Z"] txt = annotated_matrix(dipoles_important, row_labels, col_labels) print txt # Cs, LMs = asymptotic_Ylm(continuum_orbitals_important, energy, rmax=rmax, npts_r=npts_r, lebedev_order=lebedev_order) # expand product of continuum orbitals into spherical harmonics of order L=0,2 Amo, LMs = angular_product_distribution( continuum_orbitals_important, energy, rmax=rmax, npts_r=npts_r, lebedev_order=lebedev_order) # compute PAD for ionization from orbital 0 (the bound orbital) sigma, beta = photoangular_distribution(dipoles_important, Amo, LMs, energy) if units == "eV-Mb": energy *= AtomicData.hartree_to_eV # convert cross section sigma from bohr^2 to Mb sigma *= AtomicData.bohr2_to_megabarn pad_data.append([energy, sigma, beta]) # save row with PAD for this energy to table row = "%10.6f %10.6e %+10.6e" % tuple(pad_data[-1]) + '\n' fh.Write_ordered(row) fh.Close() print " Photoelectron Angular Distribution" print " ==================================" print " units: %s" % units row_labels = [" " for en in energy_range] col_labels = ["energy", "sigma", "beta2"] txt = annotated_matrix(np.array(pad_data).real, row_labels, col_labels) print txt print "FINISHED"
def averaged_pad_scan(res, initial_orbital, energy_range, data_file, npts_euler=5, npts_theta=50, npts_r=60, rmax=300.0, lmax=1, lebedev_order=23, radial_grid_factor=3, units="eV-Mb"): """ compute the photoelectron angular distribution for an ensemble of istropically oriented molecules. Parameters ---------- res : instance of G09ResultsDFT, contains basis set, MO coefficients of bound orbitals initial_orbital : index (starting from 0) of occupied orbital from which the electron is ionized, only the alpha orbitals are used energy_range : numpy array with photoelectron kinetic energies (PKE) for which the PAD should be calculated data_file : path to file, a table with PKE, SIGMA and BETAs is written Optional -------- npts_euler : number of grid points N for numerical integration over molecular orientations for each Euler angle a,b,c npts_theta : number of grid points for theta angle. The molecular frame PAD is computed on the rotated grid for each orientation and averaged. npts_r : number of radial grid points for integration on interval [rmax,rmax+2pi/k] rmax : large radius at which the continuum orbitals can be matched with the asymptotic solution lmax : maximal angular momentum of atomic continuum orbitals and asymptotic solution. lebedev_order : order of Lebedev grid for angular integrals radial_grid_factor : factor by which the number of grid points is increased for integration on the interval [0,+inf] units : units for energies and photoionization cross section in output, 'eV-Mb' (eV and Megabarn) or 'a.u.' """ # convert geometry to atomlist format atomlist = [] for i in range(0, res.nat): atomlist.append((res.atomic_numbers[i], res.coordinates[:, i])) 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 # increase rmax by the size of the molecule rmax += max([dx, dy, dz]) Npts = max(int(rmax), 1) * 50 print "Radius of sphere around molecule, rmax = %s bohr" % rmax print "Points on radial grid, Npts = %d" % Npts #assert res.nelec_alpha == res.nelec_beta # create wavefunction of bound initial orbital # MO coefficients of initial orbital orbs_initial = res.orbs_alpha[:, initial_orbital] # bound_orbital = GaussianMolecularOrbital(res.basis, orbs_initial) # compute PADs for all energies pad_data = [] print " SCAN" print " Writing table with betas to %s" % data_file # table headers header = "" header += "# formatted checkpoint file: %s" % fchk_file + '\n' header += "# initial orbital: %s" % initial_orbital + '\n' header += "# npts_euler: %s npts_theta: %s npts_r: %s rmax: %s lmax: %s" % ( npts_euler, npts_theta, npts_r, rmax, lmax) + '\n' header += "# lebedev_order: %s radial_grid_factor: %s" % ( lebedev_order, radial_grid_factor) + '\n' if units == "eV-Mb": header += "# PKE/eV SIGMA/Mb BETA1 BETA2 BETA3 BETA4" + '\n' else: header += "# PKE/Eh SIGMA/bohr^2 BETA1 BETA2 BETA3 BETA4" + '\n' # save table access_mode = MPI.MODE_WRONLY | MPI.MODE_CREATE fh = MPI.File.Open(comm, data_file, access_mode) if rank == 0: # only process 0 write the header fh.Write_ordered(header) else: fh.Write_ordered('') for i, energy in enumerate(energy_range): if i % size == rank: print " PKE = %6.6f Hartree (%4.4f eV)" % ( energy, energy * AtomicData.hartree_to_eV) # molecular continuum orbitals at energy E continuum_orbitals, phase_shifts, lms = variational_kohn( atomlist, energy, lmax=lmax, rmax=rmax, npts_r=npts_r, radial_grid_factor=radial_grid_factor, lebedev_order=lebedev_order) # transition dipoles between bound and free orbitals dipoles = transition_dipole_integrals( atomlist, [bound_orbital], continuum_orbitals, radial_grid_factor=radial_grid_factor, lebedev_order=lebedev_order) ### DEBUG analyze_dipoles_angmom(dipoles, lms) ### # compute PAD for ionization from orbital 0 (the bound orbital) betasE = orientation_averaged_pad(dipoles[0, :, :], continuum_orbitals, energy, rmax=rmax, npts_euler=npts_euler, npts_r=npts_r, npts_theta=npts_theta) if units == "eV-Mb": energy *= AtomicData.hartree_to_eV # convert cross section sigma from bohr^2 to Mb betasE[0] *= AtomicData.bohr2_to_megabarn pad_data.append([energy] + list(betasE)) # save row with PAD for this energy to table row = "%10.6f %10.6e %+10.6e %+10.6f %+10.6e %+10.6e" % tuple( pad_data[-1]) + '\n' fh.Write_ordered(row) fh.Close() print "FINISHED"