def test(): # bond length in bohr dist = 2.0 # positions of protons posH1 = (0.0, 0.0, -dist / 2.0) posH2 = (0.0, 0.0, +dist / 2.0) atomlist = [(1, posH1), (1, posH2)] # Set resolution of multicenter grid settings.radial_grid_factor = 20 settings.lebedev_order = 23 # energy of continuum orbital E = 1.0 # same functional as used in the calculation of pseudo orbitals xc = XCFunctionals.libXCFunctional(Parameters.pseudo_orbital_x, Parameters.pseudo_orbital_c) dft = BasissetFreeDFT(atomlist, xc) print "initial orbital guess from DFTB calculation" orbitals = dft.getOrbitalGuess() norb = len(orbitals) # all orbitals are doubly occupied nelec = 2 * norb bound_orbitals = dft.getOrbitalGuess() # effective potential rho = density_func(bound_orbitals) veff = effective_potential_func(atomlist, rho, xc, nelec=nelec) # radius that separates inner from outer region r0 = vdw_sphere_radius(atomlist, fac=2.0) # basis sets bs_core = AtomicBasisSet(atomlist, orbital_set="core") bs_valence = AtomicBasisSet(atomlist, orbital_set="valence") bs_continuum = AtomicScatteringBasisSet(atomlist, E, lmax=1) # combine basis functions from all basis sets bfs = bs_core.bfs + bs_valence.bfs + bs_continuum.bfs A, D, S = fvvm_matrix_elements(atomlist, bfs, veff, E, r0) # solve generalized eigenvalue problem # A.C = b.D.C b, C = sla.eigh(A, D) return b, C
def __init__(self, tddftb, name=""): self.tddftb = tddftb self.dftb = tddftb.dftb2 self.name = name if self.dftb != None: self.bs = AtomicBasisSet(self.dftb.atomlist) self.ppb = 2.0 # auxiliary basis of Gaussian flucuation functions F_A(r) self.bs_aux = AuxiliaryBasisSet(self.dftb.atomlist, self.dftb.hubbard_U)
def __init__(self, tddftb, symmetry_group): self.tddftb = tddftb self.dftb = tddftb.dftb2 if self.dftb != None: self.bs = AtomicBasisSet(self.dftb.atomlist) self.symmetry_group = symmetry_group # How do I choose the test points? npts = 10 # random points in a box [-3,3]x[-3,3]x[-3,3] around the first atom pos_at1 = np.array(self.dftb.atomlist[0][1]) # position of 1st atom self.test_pts = pos_at1 + 3.0 * 2.0 * (np.random.rand(npts, 3) - 0.5)
def __init__(self, tddftb, symmetry_group): self.tddftb = tddftb self.dftb2 = tddftb.dftb2 if self.dftb2 != None: self.bs = AtomicBasisSet(self.dftb2.atomlist) self.symmetry_group = symmetry_group # How do I choose the test points? npts = 5 # random points in a box [-3,3]x[-3,3]x[-3,3] around the first atom pos_at1 = np.array(self.dftb2.atomlist[0][1]) # position of 1st atom test_pts1 = pos_at1 + 3.0 * 2.0 * (np.random.rand(npts, 3) - 0.5) pos_atN = np.array(self.dftb2.atomlist[-1][1]) # position of last atom test_ptsN = pos_atN + 3.0 * 2.0 * (np.random.rand(npts, 3) - 0.5) self.test_pts = np.vstack((test_pts1, test_ptsN)) # transform test points according to the symmetry operations # of the group xs = [] self.npts = len(self.test_pts) # Put all test points into one long list so that we # can compute the values of the transition density # for all of the in one step for x in self.test_pts: # image of x under all symmetry transformations # e.g. xts = {Id(x), sigma(x), ....} xts = self.symmetry_group.symmetry_equivalents(x) xs += xts self.xs = np.array(xs) # The coordinates of the transformed vectors belonging # to the the i-th test point can be indexed as # xs[i*ng:(i+1)*ng,:] # size of group - number of symmetry elements self.ng = self.symmetry_group.size() # number of basis functions self.nbf = len(self.bs.bfs) # evaluate basis functions on the grid of symmetry equivalent points grid = self.xs.transpose() self.bf_grid = [] for bf in self.bs.bfs: self.bf_grid.append(bf.amp(*grid)) self.bf_grid = np.array(self.bf_grid) ## classify molecular orbitals by symmetry #self.orbs_irreps = self.assign_orbital_symmetries(self.dftb2.orbs) # self.selected_irreps = ["B1U"]
def charge_fluctuation_functions(atom_name='h', confined=True): """ create charge fluctuation functions for valence shells Parameters ---------- atom_name : string with atom name Optional -------- confined : controls where confined atoms (True) or free atoms (False) are used Returns ------- ls : list of angular momenta of the valence shells Fs : list of charge fluctuation functions for each shell callable, Fs[i](x,y,z) evaluates the spherically averaged charged fluctuation function for the shell with angular momentum ls[i] """ Zat = atomic_number(atom_name) atomlist = [(Zat, (0, 0, 0))] # load atomic valence orbitals basis = AtomicBasisSet(atomlist, confined=confined) # group basis functions by angular momentum ls = [] shells = [] # contains list of basis functions for each shell for bf in basis.bfs: if len(ls) == 0 or bf.l > ls[-1]: # new shell begins ls.append(bf.l) # create new shell with current basis function as first element shells.append([bf]) else: # append basis function to current shell shells[-1].append(bf) # define charge fluctuation function for each shell Fs = [] # list of charge fluctuation functions for l, shell in zip(ls, shells): assert len(shell) == 2 * l + 1 Fs.append(spherically_averaged_density(shell, l)) return ls, Fs
def onsite_integrals(atom_name='h', confined=True): """ compute on-site Coulomb and exchange integrals for an atom Parameters ---------- atom_name : name of atom, e.g. 'h', 'c', etc. Optional -------- confined : controls where confined atoms (True) or free atoms (False) are used """ print( "computing on-site Coulomb and exchange integrals between valence orbitals of '%s'" % atom_name) Zat = atomic_number(atom_name) atomlist = [(Zat, (0, 0, 0))] basis = AtomicBasisSet(atomlist, confined=confined) density = AtomicDensitySuperposition(atomlist, confined=confined) #xc_functional = XCFunctionals.libXCFunctional("lda_x", "lda_c_pw") xc_functional = XCFunctionals.libXCFunctional("gga_x_pbe", "gga_c_pbe") for a, bfA in enumerate(basis.bfs): stra = "%d%s" % (bfA.n, angmom_to_xyz[(bfA.l, bfA.m)] ) # name of valence orbital, e.g. 1s, 2px, etc. for b, bfB in enumerate(basis.bfs): strb = "%d%s" % (bfB.n, angmom_to_xyz[(bfB.l, bfB.m)]) # Coulomb integral (aa|(1/r12 + fxc[rho0])|bb) eri_aabb = electron_repulsion_integral(atomlist, bfA, bfA, bfB, bfB, density, xc_functional) print("Coulomb (%s,%s|{1/r12+f_xc[rho0]}|%s,%s)= %e" % (stra, stra, strb, strb, eri_aabb)) if a != b: # exchange integral (ab|(1/r12 + fxc[rho0])|ab) eri_abab = electron_repulsion_integral(atomlist, bfA, bfB, bfA, bfB, density, xc_functional) print("exchange (%s,%s|{1/r12+f_xc[rho0]}|%s,%s)= %e" % (stra, strb, stra, strb, eri_abab))
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 __init__(self, atomlist, hubbard_U): """ Parameters: =========== bfs: list of numeric basis functions bfs_aux: list of auxiliary basis functions """ self.atomlist = atomlist self.bfs = AtomicBasisSet(atomlist).bfs self.bfs_aux = AuxiliaryBasisSet(atomlist, hubbard_U).bfs # create a list of points that should have the symmetry # of the molecule on which the true density should agree # with the model density Xs,Ys,Zs = [], [], [] # atomic centers for Zi,posi in atomlist: x,y,z = posi Xs.append(x) Ys.append(y) Zs.append(z) # points between atoms for i,(Zi,posi) in enumerate(atomlist): for j,(Zj,posj) in enumerate(atomlist): if i == j: continue Rij = np.array(posj)-np.array(posi) x,y,z = np.array(posi) + 0.33*Rij Xs.append(x) Ys.append(y) Zs.append(z) x,y,z = np.array(posi) - 0.33*Rij Xs.append(x) Ys.append(y) Zs.append(z) # points perpendicular to plane between 3 atoms for i,(Zi,posi) in enumerate(atomlist): for j,(Zj,posj) in enumerate(atomlist): if i == j: continue for k,(Zk,posk) in enumerate(atomlist): if i == k: continue if i == j: continue Rij = np.array(posj) - np.array(posi) Rik = np.array(posk) - np.array(posi) x,y,z = np.array(posi) + 0.25*np.cross(Rij, Rik) Xs.append(x) Ys.append(y) Zs.append(z) Xs,Ys,Zs = np.mgrid[-6.0:6.0:30j,-6.0:6.0:30j,-6.0:6.0:30j] Xs = Xs.ravel() Ys = Ys.ravel() Zs = Zs.ravel() grid = np.array(Xs), np.array(Ys), np.array(Zs) # number of fit parameters self.Nfit = len(self.bfs_aux) assert self.Nfit % 4 == 0 # number of basis functions self.Nbfs = len(self.bfs) # number of sample points self.Npts = len(Xs) assert self.Npts > self.Nfit # save grid to xyz file gridlist = atomlist[:] for i in range(0, self.Npts): gridlist.append( (1, (Xs[i], Ys[i], Zs[i])) ) XYZ.write_xyz("/tmp/fitgrid.xyz", [gridlist]) # evaluate all basis functions on the grid self.bf_grid = np.zeros((self.Npts, self.Nbfs)) for m,bfm in enumerate(self.bfs): self.bf_grid[:,m] = bfm.amp(*grid) # evaluate fit functions on the grid self.bf_aux_grid = np.zeros((self.Npts, self.Nfit)) for m,bfm_aux in enumerate(self.bfs_aux): self.bf_aux_grid[:,m] = bfm_aux.amp(*grid) # M = self.bf_aux_grid self.M = M # constrained C.x - Q = 0 Id4 = np.eye(4) C = np.hstack([Id4 for i in range(0, self.Nfit/4)]) Ct = C.transpose() # Mt = M.transpose() MtM = np.dot(Mt, M) MtMinv = la.inv(MtM) # self.A1 = np.dot(C,np.dot(MtMinv, Mt)) self.A2 = np.dot(C,np.dot(MtMinv, Ct)) self.A3 = np.dot(MtMinv, Ct) self.A4 = np.dot(MtMinv,Mt)
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 onsite_integrals_unique(atom_name='h', confined=True): """ compute the 5 unique on-site electron integrals for an atom. Only s- and p-valence orbitals are considered. The 5 integrals are (ss|ss) (sp|sp) = (s px|s px) = (s py|s py) = (s pz|s pz) (ss|pp) = (s s |pxpx) = (s s |pypy) = (s s |pzpz) (pp|pp) = (pxpx|pxpx) = (pypy|pypy) = (pzpz|pzpz) (pp'|pp') = (pxpy|pxpy) = (pxpz|pxpz) = (pypz|pypz) Optional -------- confined : controls where confined atoms (True) or free atoms (False) are used Returns ------- unique_integrals : numpy array with unique on-site integrals [(ss|ss), (sp|sp), (ss|pp), (pp|pp), (pp'|pp')] References ---------- [1] http://openmopac.net/manual/1c2e.html """ print( "computing unique on-site electron integrals between valence orbitals of '%s'" % atom_name) print("only s- and p-functions are considered") Zat = atomic_number(atom_name) atomlist = [(Zat, (0, 0, 0))] basis = AtomicBasisSet(atomlist, confined=confined) density = AtomicDensitySuperposition(atomlist, confined=confined) #xc_functional = XCFunctionals.libXCFunctional("lda_x", "lda_c_pw") xc_functional = XCFunctionals.libXCFunctional("gga_x_pbe", "gga_c_pbe") unique_integrals = np.zeros(5) for a, bfA in enumerate(basis.bfs): for b, bfB in enumerate(basis.bfs): # choose index into unique_integrals to which the integrals is written if (bfA.l == 0 and bfB.l == 0): # (ss|ss) i = 0 elif (bfA.l == 0 and bfB.l == 1 and bfB.m == 0): # (sp|sp) and (ss|pp) i = 1 elif (bfA.l == 1 and bfA.m == 0 and bfB.l == 1 and bfB.m == 0): # (pp|pp) i = 3 elif (bfA.l == 1 and bfA.m == 0 and bfB.l == 1 and bfB.m == 1): # (pp'|pp') i = 4 else: continue # compute Coulomb integral (ab|(1/r12 + fxc[rho0])|ab) eri_abab = electron_repulsion_integral(atomlist, bfA, bfB, bfA, bfB, density, xc_functional) unique_integrals[i] = eri_abab if (i == 1): # in addition to (sp|sp) we also need to calculated (ss|pp) eri_aabb = electron_repulsion_integral(atomlist, bfA, bfA, bfB, bfB, density, xc_functional) unique_integrals[2] = eri_aabb elif (i == 4): # in addition to (pp'|pp') we also compute (pp|p'p') and verify the identity # (pp|p'p') = (pp|pp) - 2 (pp'|pp') eri_aabb = electron_repulsion_integral(atomlist, bfA, bfA, bfB, bfB, density, xc_functional) assert ( eri_aabb - (unique_integrals[3] - 2 * unique_integrals[4])) < 1.0e-5 print("unique 1-center integrals (in eV) for atom '%s'" % atom_name) print(" (ss|ss) (sp|sp) (ss|pp) (pp|pp) (pp'|pp')") print(" %+.7f %+.7f %+.7f %+.7f %+.7f" % tuple(unique_integrals * hartree_to_eV)) return unique_integrals
#!/usr/bin/env python """ This script demonstrates how to change the resolution of the multicenter grid. """ # routine for integration from DFTB.MolecularIntegrals.Ints1e import kinetic from DFTB.BasisSets import AtomicBasisSet if __name__ == "__main__": # default settings for grid size from DFTB.MolecularIntegrals import settings # increase radial grid settings.radial_grid_factor = 10 settings.lebedev_order = 23 atomlist = [(6, (0, 0, 0))] basis = AtomicBasisSet(atomlist) # matrix elements of kinetic energy computed on a fine radial grid for a, bfA in enumerate(basis.bfs): for b, bfB in enumerate(basis.bfs): t = kinetic(atomlist, bfA, bfB) print "(%d|T|%d) = %e" % (a, b, t)
def __init__(self, tddftb, parent=None): self.tddftb = tddftb QtGui.QWidget.__init__(self, parent) layout = QtGui.QVBoxLayout(self) # upperFrame = QtGui.QFrame() layout.addWidget(upperFrame) upperLayout = QtGui.QHBoxLayout(upperFrame) # table with MOs and Kohn-Sham energies tableFrame = QtGui.QFrame() upperLayout.addWidget(tableFrame) tableLayout = QtGui.QVBoxLayout(tableFrame) tableLayout.addWidget(QtGui.QLabel("Molecular Orbitals:")) self.tableMOs = QtGui.QTableWidget() self.tableMOs.setToolTip( "Select a row to highlight the respective orbital in the density of states" ) self.tableMOs.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) self.tableMOs.itemSelectionChanged.connect(self.plotDOS) tableLayout.addWidget(self.tableMOs) cubeFrame = QtGui.QFrame() tableLayout.addWidget(cubeFrame) cubeLayout = QtGui.QHBoxLayout(cubeFrame) self.ppbGrid = QtGui.QComboBox() self.ppbGrid.addItems( ["crude: 1.0", "coarse: 2.0", "medium: 3.0", "fine: 4.0"]) self.ppbGrid.setCurrentIndex(1) self.ppbGrid.setToolTip( "The number of points per bohr determines the resolution of the grid on which the cubes are calculated" ) self.ppbGrid.currentIndexChanged.connect(self.recalculateCubes) cubeLayout.addWidget(self.ppbGrid) calcCubesButton = QtGui.QPushButton("Calc. cubes") calcCubesButton.setToolTip("Calculate cubes for the selected orbitals") calcCubesButton.clicked.connect(self.calculateCubes) cubeLayout.addWidget(calcCubesButton) # MOs moFrame = QtGui.QFrame() upperLayout.addWidget(moFrame) moLayout = QtGui.QVBoxLayout(moFrame) self.moLabel = QtGui.QLabel("Ground State:") moLayout.addWidget(self.moLabel) moTab = QtGui.QTabWidget() moLayout.addWidget(moTab) # 3D cubes - MOs self.bs = AtomicBasisSet(self.tddftb.dftb2.atomlist) self.molecularOrbitalViewer = QCubeViewerWidget() self.molecularOrbitalViewer.setIsoValue(0.02) moTab.addTab(self.molecularOrbitalViewer, "3D Molecular Orbital") # 3D cubes - MOs # Mulliken Charges self.chargesViewer = QCubeViewerWidget() moTab.addTab(self.chargesViewer, "Mulliken Charges") atomlist = self.tddftb.dftb2.getGeometry() partial_charges = -self.tddftb.dftb2.getPartialCharges() self.chargesViewer.setGeometriesAndCharges([atomlist], [partial_charges]) self.chargesViewer.selectShowOptions(options=["charges"]) # figure with DOS dosFrame = QtGui.QFrame() layout.addWidget(dosFrame) dosLayout = QtGui.QVBoxLayout(dosFrame) self.figDOS = Figure() self.canvasDOS = FigureCanvas(self.figDOS) dosLayout.addWidget(self.canvasDOS) NavigationToolbar(self.canvasDOS, dosFrame, coordinates=True) # controls controlFrame = QtGui.QFrame() dosLayout.addWidget(controlFrame) controlLayout = QtGui.QHBoxLayout(controlFrame) self.energyUnits = QtGui.QComboBox() self.energyUnits.addItems(["Hartree", "eV", "cm-1"]) self.energyUnits.currentIndexChanged.connect(self.plotDOS) controlLayout.addWidget(QtGui.QLabel("units:")) controlLayout.addWidget(self.energyUnits) controlLayout.addWidget(QtGui.QLabel("broadening:")) self.broadening = QtGui.QLineEdit() self.broadening.setText("0.01") self.broadening.editingFinished.connect(self.plotDOS) self.broadening.setToolTip( "The sticks are convolved with a Gaussian to simulated a temperature broadened DOS" ) controlLayout.addWidget(self.broadening) # load KS orbitals tab = self.tableMOs dftb = self.tddftb.dftb2 orbe = dftb.getKSEnergies() f = dftb.getOccupation() self.H**O, self.LUMO = dftb.getFrontierOrbitals() tab.setRowCount(len(orbe)) headers = [ "N", "name", "occ.", "orb. en. / hartree", "orb. en. / eV", "Cube" ] tab.setColumnCount(len(headers)) tab.setHorizontalHeaderLabels(headers) self.mo_names = [] for i in range(0, len(orbe)): if i == self.H**O: name = "H**O" elif i == self.LUMO: name = "LUMO" else: name = "" self.mo_names.append(name) row = [str.rjust(str(i+1),5), \ str.rjust(name,5), str.rjust(str(f[i]),5), str.rjust("%.7f" % orbe[i],20), \ str.rjust("%.7f" % (orbe[i]*AtomicData.hartree_to_eV), 17) ] for j, r in enumerate(row): tab.setItem(i, j, QtGui.QTableWidgetItem("%s" % r)) tab.resizeColumnsToContents() self.plotDOS() # cubes with MOs self.mo_cubes = {} # select H**O self.tableMOs.setCurrentCell(self.H**O, 0)
def __init__(self, tddftb, parent=None): self.tddftb = tddftb QtGui.QWidget.__init__(self, parent) layout = QtGui.QVBoxLayout(self) # upperFrame = QtGui.QFrame() layout.addWidget(upperFrame) upperLayout = QtGui.QHBoxLayout(upperFrame) # table with excitation energies and states tableFrame = QtGui.QFrame() upperLayout.addWidget(tableFrame) tableLayout = QtGui.QVBoxLayout(tableFrame) tableLayout.addWidget(QtGui.QLabel("Excited States:")) self.tableStates = QtGui.QTableWidget() self.tableStates.setToolTip( "Select a row to highlight the respective state in the absorption spectrum" ) self.tableStates.setSelectionBehavior( QtGui.QAbstractItemView.SelectRows) self.tableStates.itemSelectionChanged.connect(self.plotSpectrum) tableLayout.addWidget(self.tableStates) cubeFrame = QtGui.QFrame() tableLayout.addWidget(cubeFrame) cubeLayout = QtGui.QHBoxLayout(cubeFrame) self.ppbGrid = QtGui.QComboBox() self.ppbGrid.addItems( ["crude: 1.0", "coarse: 2.0", "medium: 3.0", "fine: 4.0"]) self.ppbGrid.setCurrentIndex(1) self.ppbGrid.setToolTip( "The number of points per bohr determines the resolution of the grid on which the cubes are calculated" ) self.ppbGrid.currentIndexChanged.connect(self.recalculateCubes) cubeLayout.addWidget(self.ppbGrid) calcCubesButton = QtGui.QPushButton("Calc. cubes") calcCubesButton.setToolTip( "Calculate cubes with the transition densities for the selected states" ) calcCubesButton.clicked.connect(self.calculateCubes) cubeLayout.addWidget(calcCubesButton) # transition densities tdenseFrame = QtGui.QFrame() upperLayout.addWidget(tdenseFrame) tdenseLayout = QtGui.QVBoxLayout(tdenseFrame) self.tdenseLabel = QtGui.QLabel("Excitated State:") tdenseLayout.addWidget(self.tdenseLabel) tdenseTab = QtGui.QTabWidget() tdenseLayout.addWidget(tdenseTab) # 3D cubes - transition density self.bs = AtomicBasisSet(self.tddftb.dftb2.atomlist) self.transitionDensityViewer = QCubeViewerWidget() tdenseTab.addTab(self.transitionDensityViewer, "3D Transition Density") # 3D cubes - density difference self.bs_aux = AuxiliaryBasisSet(self.tddftb.dftb2.atomlist, self.tddftb.dftb2.hubbard_U) self.differenceDensityViewer = QCubeViewerWidget() tdenseTab.addTab(self.differenceDensityViewer, "3D Difference Density") # 2D occ-virt plane exvec2dFrame = QtGui.QFrame() exvec2dLayout = QtGui.QVBoxLayout(exvec2dFrame) self.figExvec = Figure() self.canvasExvec = FigureCanvas(self.figExvec) exvec2dLayout.addWidget(self.canvasExvec) tdenseTab.addTab(exvec2dFrame, "2D Excitation Vector") NavigationToolbar(self.canvasExvec, exvec2dFrame, coordinates=True) # atomic charges in the excited state self.chargesViewer = QCubeViewerWidget() tdenseTab.addTab(self.chargesViewer, "Partial Charges") atomlist = self.tddftb.dftb2.getGeometry() # partial_charges = -self.tddftb.dftb2.getPartialCharges() # self.chargesViewer.setGeometriesAndCharges([atomlist], [partial_charges]) self.chargesViewer.selectShowOptions(options=["charges"]) # transition charges between ground and excited state self.trans_chargesViewer = QCubeViewerWidget() tdenseTab.addTab(self.trans_chargesViewer, "Transition Charges") atomlist = self.tddftb.dftb2.getGeometry() # # for S0->S0 transition, the transition charges are equal to the partial charges # transition_charges = -self.tddftb.dftb2.getPartialCharges() # self.trans_chargesViewer.setGeometriesAndCharges([atomlist], [transition_charges]) self.trans_chargesViewer.selectShowOptions( options=["charges", "transition charges"]) # figure with spectrum spectrumFrame = QtGui.QFrame() layout.addWidget(spectrumFrame) spectrumLayout = QtGui.QVBoxLayout(spectrumFrame) self.figSpectrum = Figure() self.canvasSpectrum = FigureCanvas(self.figSpectrum) spectrumLayout.addWidget(self.canvasSpectrum) NavigationToolbar(self.canvasSpectrum, spectrumFrame, coordinates=True) # controls controlFrame = QtGui.QFrame() spectrumLayout.addWidget(controlFrame) controlLayout = QtGui.QHBoxLayout(controlFrame) self.energyUnits = QtGui.QComboBox() self.energyUnits.addItems(["Hartree", "eV", "nm", "cm-1"]) self.energyUnits.currentIndexChanged.connect(self.plotSpectrum) controlLayout.addWidget(QtGui.QLabel("units:")) controlLayout.addWidget(self.energyUnits) controlLayout.addWidget(QtGui.QLabel("broadening:")) self.broadening = QtGui.QLineEdit() self.broadening.setText("0.0") self.broadening.editingFinished.connect(self.plotSpectrum) self.broadening.setToolTip( "The stick spectrum is convolved with a Gaussian to simulated a temperature broadened spectrum" ) controlLayout.addWidget(self.broadening) # load spectrum nr_dominant_ex = 2 tab = self.tableStates tab.setRowCount(len(tddftb.Omega)) headers = [ "N", "Spin", "Sym", "exc. en. / hartree", "exc. en. / eV", "exc. en. / nm", "osc. strength", "Lambda diagn.", "Cube" ] tab.setColumnCount(len(headers)) tab.setHorizontalHeaderLabels(headers) for I in range(0, len(tddftb.Omega)): row = [str.rjust(str(I+1),5), \ tddftb.multiplicity, \ str.rjust(tddftb.Irreps[I], 3), \ str.rjust("%.7f" % tddftb.Omega[I],20), \ str.rjust("%.7f" % (tddftb.Omega[I]*AtomicData.hartree_to_eV), 17), \ str.rjust("%.7f" % (AtomicData.hartree_to_nm / tddftb.Omega[I]), 17), \ str.rjust("%.7f" % tddftb.oscillator_strength[I], 12), \ str.rjust("%.4f" % tddftb.Lambda2[I], 7)] for j, r in enumerate(row): tab.setItem(I, j, QtGui.QTableWidgetItem("%s" % r)) tab.resizeColumnsToContents() self.plotSpectrum() # cubes with transition densities and difference densities for each state if calculated self.tdense_cubes = {} self.difdense_cubes = {} self.partial_charges = {} # partial charges on excited states
def __init__(self, dftb, name=""): self.dftb = dftb self.name = name if self.dftb != None: self.bs = AtomicBasisSet(self.dftb.atomlist) self.ppb = 2.0
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"