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 getJacobian(self): pos = XYZ.atomlist2vector(self.atomlist) # pos = self.standard_orientation() jac, jac_inv = jacobian_cartesian2internal(self.masses, pos, self.atomlist) print("Jacobian J_ij = d( q_j )/d( x_i )") print("=====================================") print(utils.annotated_matrix(jac,["x_%s" % i for i in range(0, len(jac[:,0]))], ["q_%s" % j for j in range(0, len(jac[0,:]))])) print("Jacobian J^(-1)_ij = d( x_j )/d( q_i )") print("=====================================") # jac_inv = inv(jac) print(utils.annotated_matrix(jac_inv,["q_%s" % i for i in range(0, len(jac_inv[:,0]))], ["x_%s" % j for j in range(0, len(jac_inv[0,:]))])) # print("Jacobian J^(-1)_ij = d( x_j )/d( q_i )") # print("=====================================") # jac_inv = inv(jac) # print(utils.annotated_matrix(jac_inv,["q_%s" % i for i in range(0, len(pos))], ["x_%s" % j for j in range(0, len(pos))])) return jac, jac_inv
def _table_gradients(self, g_cart, g_intern): """ make tables with gradient vectors in cartesian and internal coordinates """ txt = "" txt += " =========================== \n" txt += " Cartesian Gradient dE/dx(i) \n" txt += " =========================== \n" txt += utils.annotated_matrix(np.reshape(g_cart, (len(g_cart), 1)), self.cartesian_labels, ["grad E"]) txt += " cartesian gradient norm : %e \n" % la.norm(g_cart) txt += "\n" txt += " ========================== \n" txt += " Internal Gradient dE/dq(i) \n" txt += " ========================== \n" txt += utils.annotated_matrix(np.reshape(g_intern, (len(g_intern), 1)), self.internal_labels, ["grad E"]) txt += " internal gradient norm : %e \n" % la.norm(g_intern) txt += "\n" return txt
def getJacobian(self, h=1.0e-10): """ numerically find Jacobian matrix for transformation from internal to cartesian coordinates J_ij = d( x_i )/d( chi_j ) where x_1,x_2,...,x_3N are the cartesian coordinates of N atoms and chi_1,chi_2,...,chi_3N are the internal coordinates Parameters: =========== h: step for difference quotient Returns: ======== 3Nx3N numpy array with J_ij """ pos = [array(posi) for (Zi,posi) in self.atomlist] x = _lst2vec(pos) ipos = cartesian2internal(pos) Jacobian = zeros((3*len(pos),3*len(pos))) for j in range(0, 3*len(pos)): # iterate over atoms # symmetric difference quotient chiplus = _lst2vec(ipos) chiplus[j] += h chiminus = _lst2vec(ipos) chiminus[j] -= h xplus = _lst2vec(internal2cartesian(_vec2lst(chiplus))) xminus = _lst2vec(internal2cartesian(_vec2lst(chiminus))) Jacobian[:,j] = (xplus - xminus) / (2*h) # forward difference quotient # Jacobian[:,j] = (xplus - x) / h print "Jacobian:" from DFTB.utils import annotated_matrix print annotated_matrix(Jacobian,["x_%s" % i for i in range(0, 3*len(pos))], ["q_%s" % j for j in range(0, 3*len(pos))]) return Jacobian
def _table_Bmatrix(self, B): """ make table with rows of Wilson's B-matrix belonging to the active internal coordinates """ txt = " ======================================================================\n" txt += " Wilson's B-matrix B(i,j) = dq(i)/dx(j) for active internal coordinates\n" txt += " B - bond, A - valence angle, D - dihedral, I - inversion \n" txt += " ======================================================================\n" txt += utils.annotated_matrix(B, self.internal_labels, self.cartesian_labels) txt += "\n" return txt
print "Linear polymer consists of %d monomeric units" % nunits for I in range(0, Nst_poly): PI = Ptrans_poly[I] # plt.matshow(PI) print "Analyse state %d of polymer" % I MI = np.zeros((Nst_mono, nunits)) for n in range(0, nunits): # extract diagonal block of the n-th subunit Pn = PI[n * nAOm:(n + 1) * nAOm, n * nAOm:(n + 1) * nAOm] vvec = Pn.flatten() for Im in range(0, Nst_mono): bvec = Ptrans_mono[Im].flatten() MI[Im, n] = np.dot(bvec, vvec) unit_labels = ["U-%d" % nu for nu in range(0, nunits)] state_labels = ["St-%d" % st for st in range(0, Nst_mono)] print utils.annotated_matrix(MI, state_labels, unit_labels) plt.matshow(MI, interpolation="nearest", cmap=plt.cm.jet) #plt.show() plt.show() """ plt.matshow(Ptrans_mono[0], interpolation="nearest", cmap=plt.cm.jet) for Ptrans in Ptrans_poly: plt.matshow(Ptrans, interpolation="nearest", cmap=plt.cm.jet) plt.show() """
def Gouterman_matelems(): """ computes overlap and hamiltonian matrix elements between the 4 frontier orbitals (a1u,a2u,eg,eg) of neighbouring porphyrine squares in a porphyrine flake (polynomino). The matrix elements between vertically and horizontally fused porphyrins will be related by a rotation of 90 degrees and will be different. Returns: ========: orbe: energies of the four orbitals Sh,Sv: two 4x4 matrices with overlaps between frontier orbitals of horizontally and vertically fused porphyrins. H0h,H0v: two 4x4 matrices with matrix elements of the 0th-order DFTB hamiltonian (neglecting interaction between partial charges) """ # perform DFTB calculation on the monomer to obtain the MO coefficients for the frontier # orbitals. lat, unitcell, meso, beta = zn_porphene_lattice(substitutions=[]) monomer = unitcell + meso + beta dftb = DFTB2(monomer) dftb.setGeometry(monomer) dftb.getEnergy() orbs = dftb.getKSCoefficients() orbe = dftb.getKSEnergies() H**O, LUMO = dftb.getFrontierOrbitals() # In DFTB the orbital symmetries are: # H-2 a1u # H-1 rubbish # H a2u # L eg # L+1 eg # The H-1 belongs to the sigma-framework and has the wrong energetic position in DFTB. Gouterman_orbitals = np.array([H**O - 2, H**O, LUMO, LUMO + 1], dtype=int) # indeces of G. orbitals # remove orbitals belonging to hydrogen valorbs = dftb.getValorbs() mu = 0 # iterates over all orbitals hydrogen_orbs = [ ] # indeces of orbitals belonging to hydrogen that should be removed for i, (Zi, posi) in enumerate(monomer): for (ni, li, mi) in valorbs[Zi]: if (Zi == 1): hydrogen_orbs.append(mu) mu += 1 # remove rows or columns belonging to hydrogen orbs = np.delete(orbs, hydrogen_orbs, 0) orbs = np.delete(orbs, hydrogen_orbs, 1) nmo, nmo = orbs.shape a1, a2, a3 = lat.getLatticeVectors() C = orbs[:, Gouterman_orbitals] # energies of Gouterman orbitals orbe = orbe[Gouterman_orbitals] # horizontally fused # matrix elements between AOs H0h, Sh = dftb._constructH0andS_AB(nmo, nmo, unitcell, shift_atomlist(unitcell, a1)) # transform to the basis of frontier orbitals Sh = np.dot(C.transpose(), np.dot(Sh, C)) H0h = np.dot(C.transpose(), np.dot(H0h, C)) # vertically fused # matrix elements between AOs H0v, Sv = dftb._constructH0andS_AB(nmo, nmo, unitcell, shift_atomlist(unitcell, a2)) # transform to the basis of frontier orbitals Sv = np.dot(C.transpose(), np.dot(Sv, C)) H0v = np.dot(C.transpose(), np.dot(H0v, C)) # orb_names = ["a1u", "a2u", "eg", "eg"] print "Lattice vectors" print " a1 = %s bohr" % a1 print " a2 = %s bohr" % a2 print "orbital energies:" for name, en in zip(orb_names, orbe * AtomicData.hartree_to_eV): print " energy(%3s) = %8.4f eV" % (name, en) print "matrix elements with neighbouring porphyrin" print "along lattice vector a1 = %s" % a1 print "overlap Sh" print utils.annotated_matrix(Sh, orb_names, orb_names) print "hamiltonian H0h" print utils.annotated_matrix(H0h, orb_names, orb_names) print "along lattice vector a2 = %s" % a2 print "overlap Sv" print utils.annotated_matrix(Sv, orb_names, orb_names) print "hamiltonian H0v" print utils.annotated_matrix(H0v, orb_names, orb_names) return orbe, Sh, Sv, H0h, H0v
def asymptotic_Ylm(continuum_orbs, energy, rmax=2500.0, npts_r=300, lebedev_order=65): """ decompose continuum orbitals asymptotically into spherical harmonics a wfn = sum R (r) Y (th,ph) a L,M L,M L,M a /rmax+2pi/k 2 | a |2 C = | r dr | R | L,M /rmax | L,M | Parameters ---------- continuum_orbitals : list of continuum orbitals energy : photokinetic energy of continuum orbitals, E = 1/2 k^2 Optional -------- rmax : radius at which the molecular potential is indistinguishable from the asymptotic potential npts_r : number of points for integrating over radial interval [rmax,rmax+2pi/k] lebedev_order : integer, order of Lebedev grid Returns ------- Cs : Cs[i,a] is the contribution of spherical harmonic Y_(Li,Mi) to orbital a at large distance LMs : list of tuples (L,M) in the same order as in the 1st axis of `Cs`. """ # wave number for kinetic energy E=1/2 k^2 k = np.sqrt(2 * energy) # wavelength wavelength = 2.0 * np.pi / k # radial grid # sample points and weights for Gauss-Legendre quadrature on the interval [-1,1] leggauss_pts, leggauss_weights = legendre.leggauss(npts_r) # For Gaussian quadrature the integral [-1,1] has to be changed into [rmax,rmax+2pi/k]. # new endpoints of interval a = rmax b = rmax + wavelength # transformed sampling points and weights r = 0.5 * (b - a) * leggauss_pts + 0.5 * (a + b) dr = 0.5 * (b - a) * leggauss_weights # Lebedev grid for spherical wave expansion th, ph, weights_angular = get_lebedev_grid(lebedev_order) # The lebedev quadrature rule is # / # I[f] = | dOmega f(Omega) = 4 pi sum weights_angular[i] * f(th[i],ph[i]) # / i # For convencience we multiply the factor of 4*pi into the weights weights_angular *= 4 * np.pi # evaluate spherical harmonics for L=0,1,2,3 M=-L,..,L on angular Lebedev grid Lmax = 3 Ys, LMs = spherical_harmonics_vec(th, ph, Lmax) Ys = np.array(Ys) # cartesian coordinates of radial and angular grids x = outerN(r, np.sin(th) * np.cos(ph)) y = outerN(r, np.sin(th) * np.sin(ph)) z = outerN(r, np.cos(th)) # differential for r-integration r2dr = r**2 * dr # volume element r^2 dr dOmega dV = outerN(r2dr, weights_angular) # add r-axis to angular weights dOmega = outerN(np.ones(npts_r), weights_angular) # nc: number of continuum wavefunctions nc = len(continuum_orbs) # number of angular components (L,M) for Lmax=2 LMdim = Lmax * (Lmax + 2) + 1 assert LMdim == len(LMs) # set up array for expansion coefficients Cs = np.zeros((LMdim, nc)) for a in range(0, nc): # loop over continuum orbitals wfn_a = continuum_orbs[a].amp(x, y, z) # normalization constant nrm2 = np.sum(dV * abs(wfn_a)**2) wfn_a /= np.sqrt(nrm2) for iLM, (l, m) in enumerate(LMs): # add r-axis to spherical harmonics Y_(l,m)(r,th,ph) = Y_(l,m)(th,ph) Ylm = outerN(np.ones(npts_r), Ys[iLM]) # radial wavefunction of continuum orbital belonging to L,M channel # a /pi /2pi * # R (r) = | sin(th) dth | dph Y (th,ph) wfn (r,th,ph) # L,M /0 /0 L,M a # # a # wfn_a = sum_(L,M) R (r) Y (th,ph) # L,M L,M # integrate over angles Rlm = np.sum(dOmega * Ylm.conjugate() * wfn_a, axis=1) # integrate over r # a /rmax+k/2pi 2 | a |2 # C = | r dr | R | 1 / (4 pi) # L,M /rmax | L,M | Cs[iLM, a] = np.sum(r2dr * abs(Rlm)**2) print " Asymptotic Decomposition Y_(l,m)" print " ================================" row_labels = ["orb. %2.1d" % a for a in range(0, nc)] col_labels = ["Y(%d,%+d)" % (l, m) for (l, m) in LMs] txt = annotated_matrix(Cs.transpose(), row_labels, col_labels) print txt return Cs, LMs
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 angular_product_distribution(continuum_orbs, energy, rmax=2500.0, npts_r=300, lebedev_order=65): """ expand the angular distribution of the product of two molecular orbitals wfn_a(r) and wfn_b(r) into spherical harmonics a,b /rmax+2pi/k 2 /pi /2pi * * A = | r dr | sin(th) dth | dph Y (th,ph) wfn (r,th,ph) wfn (r,th,ph) L,M /rmax /0 /0 L,M a b Parameters ---------- continuum_orbitals : list of instances of WrapperWavefunction energy : photokinetic energy of continuum orbitals, E = 1/2 k^2 Optional -------- rmax : radius at which the molecular potential is indistinguishable from the asymptotic potential npts_r : number of points for integrating over radial interval [rmax,rmax+2pi/k] lebedev_order : integer, order of Lebedev grid Returns ------- Amo : 3d numpy array with angular components for each pair of molecular orbitals a,b Amo[LM,a,b] = A L,M LMs : list of tuples (L,M) in the same order as in the 1st axis of `Amo`. """ # wave number for kinetic energy E=1/2 k^2 k = np.sqrt(2 * energy) # wavelength wavelength = 2.0 * np.pi / k # radial grid # sample points and weights for Gauss-Legendre quadrature on the interval [-1,1] leggauss_pts, leggauss_weights = legendre.leggauss(npts_r) # For Gaussian quadrature the integral [-1,1] has to be changed into [rmax,rmax+2pi/k]. # new endpoints of interval a = rmax b = rmax + wavelength # transformed sampling points and weights r = 0.5 * (b - a) * leggauss_pts + 0.5 * (a + b) dr = 0.5 * (b - a) * leggauss_weights # Lebedev grid for spherical wave expansion th, ph, weights_angular = get_lebedev_grid(lebedev_order) # The lebedev quadrature rule is # / # I[f] = | dOmega f(Omega) = 4 pi sum weights_angular[i] * f(th[i],ph[i]) # / i # For convencience we multiply the factor of 4*pi into the weights weights_angular *= 4 * np.pi # evaluate spherical harmonics for L=0,1,2, M=-L,..,L on angular Lebedev grid Lmax = 2 Ys, LMs = spherical_harmonics_vec(th, ph, Lmax) Ys = np.array(Ys) # cartesian coordinates of radial and angular grids x = outerN(r, np.sin(th) * np.cos(ph)) y = outerN(r, np.sin(th) * np.sin(ph)) z = outerN(r, np.cos(th)) # make sure r**2*dr has the same shape as x,y and z r2dr = np.repeat(np.reshape(r**2 * dr, (len(r), 1)), len(th), axis=1) # nc: number of continuum wavefunctions nc = len(continuum_orbs) # number of angular components (L,M) for Lmax=2 LMdim = Lmax * (Lmax + 2) + 1 assert LMdim == len(LMs) # set up array for expansion coefficients Amo = np.zeros((LMdim, nc, nc), dtype=complex) for a in range(0, nc): # loop over continuum orbitals print " %d of %d done" % (a, nc) wfn_a = continuum_orbs[a].amp(x, y, z) for b in range(0, nc): # loop over continuum orbitals wfn_b = continuum_orbs[b].amp(x, y, z) # /rmax+2pi/k 2 * # PAD (th,ph)= | r dr wfn (r,th,ph) wfn (r,th,ph) # a,b /rmax a b PAD_ab = np.sum(r2dr * wfn_a.conjugate() * wfn_b, axis=0) """ ### DEBUG # plot PAD for orbitals a and b import matplotlib.pyplot as plt from scipy.interpolate import griddata th_interp, ph_interp = np.mgrid[0.0:np.pi:100j, 0.0:2*np.pi:100j] PAD_ab_interp = griddata((th,ph), PAD_ab, (th_interp, ph_interp)) plt.cla() plt.title(r"PAD$_{%d,%d}$" % (a,b)) plt.xlabel(r"$\theta$ / $\pi$") plt.ylabel(r"$\phi$ / $\pi$") plt.imshow(PAD_ab_interp.T, extend=(0.0, 1.0, 0.0, 2.0)) plt.colorbar() plt.show() ### """ Amo[:, a, b] = np.dot(Ys.conjugate(), weights_angular * PAD_ab) print "normalize Amo's" # Continuum wavefunctions are not normalized, we normalize them in such a way # the integrating the product of two continuum orbitals # # /rmax+2pi/k 2 / * # | r dr | dOmega wfn (r,Omega) wfn (r,Omega) = delta # /rmax / a b a,b # # over one wavelength at a very large r gives exactly 1, if a=b, and 0 otherwise. # The A's are the expansion coefficients of # # M=+L a,b # PAD_{a,b}(Omega) = sum sum A Y (Omega) # L=0 M=-L L,M L,M # # Therefore we need to normalize the A's such that # # / a,b a,b ! # | dOmega PAD_{a,b}(Omega) = A 4*pi Y = A sqrt(4 pi) = 1 # / 0,0 0,0 0,0 # a,b # which implies A = 1/sqrt(4*pi) delta # 0,0 a,b nrm = np.zeros(nc, dtype=complex) for a in range(0, nc): nrm[a] = np.sqrt(Amo[0, a, a]) # normalize Amo's for a in range(0, nc): for b in range(0, nc): Amo[:, a, b] /= nrm[a] * nrm[b] Amo /= np.sqrt(4.0 * np.pi) print " PAD_ab(th,ph) - real part " print "==========================================" print "expansion into spherical harmonics " print " a,b " print "PAD (th,ph) = sum A Y (th,hp) " print " a,b L,M L,M L,M " print " " for iLM, (L, M) in enumerate(LMs): print " L=%d M=%+d " % (L, M) print " a,b " print " A " print " %d,%+d" % (L, M) row_labels = ["a %2.1d" % a for a in range(0, nc)] col_labels = ["b %2.1d" % b for b in range(0, nc)] txt = annotated_matrix(Amo[iLM, :, :].real, row_labels, col_labels) print txt """ ### DEBUG Amo_test = np.zeros((LMdim,nc,nc), dtype=complex) print "plot normalized wavefunctions" for a in range(0, nc): # loop over continuum orbitals print " %d of %d done" % (a, nc) wfn_a = continuum_orbs[a].amp(x,y,z) / nrm[a] for b in range(0, nc): # loop over continuum orbitals wfn_b = continuum_orbs[b].amp(x,y,z) / nrm[b] ### DEBUG import matplotlib.pyplot as plt plt.cla() plt.xlabel("r / bohr") plt.plot(r, wfn_a[:,0], label=r"$\phi_{%d}(r;\theta=\theta_0)$" % a) plt.plot(r, wfn_b[:,0], label=r"$\phi_{%d}(r;\theta=\theta_0)$" % b) plt.plot(r, wfn_a[:,len(th)/2], label=r"$\phi_{%d}(r;\theta=\theta_1)$" % a) plt.plot(r, wfn_b[:,len(th)/2], label=r"$\phi_{%d}(r;\theta=\theta_1)$" % b) plt.legend() plt.show() ### # /rmax+2pi/k 2 * # PAD (th,ph)= | r dr wfn (r,th,ph) wfn (r,th,ph) # a,b /rmax a b PAD_ab = np.sum(r2dr * wfn_a.conjugate() * wfn_b, axis=0) Amo_test[:,a,b] = np.dot(Ys.conjugate(), weights_angular * PAD_ab) print "a= %d b= %d" % (a,b) print "A2m (L=0,M=0)= %s" % Amo_test[0,a,b] print "A2m (numerical) = %s" % Amo_test[-5:,a,b] ### """ return Amo, LMs
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 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 pairwise_decomposition_analytical(atomlist, forcelist): Nat = len(atomlist) active_atoms = [i for i in range(0, Nat)] print "Nat = %s" % Nat nx = zeros((Nat, Nat)) ny = zeros((Nat, Nat)) nz = zeros((Nat, Nat)) Fx = zeros(Nat) Fy = zeros(Nat) Fz = zeros(Nat) distances = zeros((Nat, Nat)) for l, active_l in enumerate(active_atoms): (Zl, posl) = atomlist[active_l] # unit vectors along bond lengths for i, active_i in enumerate(active_atoms): (Zi, posi) = atomlist[active_i] if i == l: continue r_li = array(posl) - array(posi) nrm = norm(r_li) distances[l, i] = nrm n_li = r_li / nrm nx[l, i] = n_li[0] ny[l, i] = n_li[1] nz[l, i] = n_li[2] # cartesian forces Fx[l] = forcelist[l][1][0] Fy[l] = forcelist[l][1][1] Fz[l] = forcelist[l][1][2] dim = (pow(Nat, 2) - Nat) / 2 M = zeros((dim, dim)) Msym = [["" for i in range(0, dim)] for j in range(0, dim)] visited = zeros((dim, dim), dtype=int) R = zeros((dim, dim)) I = SymmatIndeces(Nat) for i in range(0, dim): ai, bi = I.i2p(i) assert ai < bi M[i, i] = 2.0 Msym[i][i] = " 2 " for j in range(i + 1, dim): aj, bj = I.i2p(j) assert aj < bj ridrj = nx[ai,bi] * nx[aj,bj] \ + ny[ai,bi] * ny[aj,bj] \ + nz[ai,bi] * nz[aj,bj] if ai != aj and bi != aj and bi == bj: M[i, j] += ridrj Msym[i][j] += " r_%d%d*r_%d%d " % (ai + 1, bi + 1, aj + 1, bj + 1) visited[i, j] += 1 visited[j, i] += 1 if ai == aj and ai != bj and bi != bj: M[i, j] += ridrj Msym[i][j] += " r_%d%d*r_%d%d " % (ai + 1, bi + 1, aj + 1, bj + 1) visited[i, j] += 1 visited[j, i] += 1 if ai == bj and aj != bi and ai != aj: M[i, j] -= ridrj Msym[i][j] += "-r_%d%d*r_%d%d " % (ai + 1, bi + 1, aj + 1, bj + 1) visited[i, j] += 1 visited[j, i] += 1 if bi == aj and ai != bj and ai != aj: M[i, j] -= ridrj Msym[i][j] += "-r_%d%d*r_%d%d " % (ai + 1, bi + 1, aj + 1, bj + 1) visited[i, j] += 1 visited[j, i] += 1 M[j, i] = M[i, j] Msym[j][i] = Msym[i][j] print "Msym" for i in range(0, dim): for j in range(0, dim): if Msym[i][j] == "": print " 0 ", else: print Msym[i][j], print "" """ for alpha in range(0, Nat): for beta in range(alpha+1, Nat): M[symmat_index(alpha,beta,Nat),symmat_index(alpha,beta,Nat)] = 2.0 visited[symmat_index(alpha,beta,Nat),symmat_index(alpha,beta,Nat)] = 1 for i in range(0, Nat): if i == alpha or i == beta: continue # M[symmat_index(alpha,beta,Nat), symmat_index(i,beta, Nat)] \ # += nx[alpha,beta] * nx[i,beta] \ # + ny[alpha,beta] * ny[i,beta] \ # + nz[alpha,beta] * nz[i,beta] # M[symmat_index(alpha,beta,Nat), symmat_index(i,alpha, Nat)] \ # -= nx[alpha,beta] * nx[i,alpha] \ # + ny[alpha,beta] * ny[i,alpha] \ # + nz[alpha,beta] * nz[i,alpha] M[symmat_index(alpha,beta,Nat), symmat_index(i,beta, Nat)] \ = nx[alpha,beta] * nx[i,beta] \ + ny[alpha,beta] * ny[i,beta] \ + nz[alpha,beta] * nz[i,beta] visited[symmat_index(alpha,beta,Nat), symmat_index(i,beta, Nat)] = 1 M[symmat_index(alpha,beta,Nat), symmat_index(i,alpha, Nat)] \ = -nx[alpha,beta] * nx[i,alpha] \ - ny[alpha,beta] * ny[i,alpha] \ - nz[alpha,beta] * nz[i,alpha] visited[symmat_index(alpha,beta,Nat), symmat_index(i,alpha, Nat)] = 1 """ print "Which matrix elements were not set?" txt = "" for i in range(0, dim): for j in range(0, dim): if visited[i, j] != 0: txt += "%d" % visited[i, j] else: txt += "0" txt += "\n" print txt # """ b = zeros(dim) for alpha in range(0, len(atomlist)): for beta in range(alpha+1, len(atomlist)): b[symmat_index(alpha,beta,Nat)] -= \ + (Fx[beta] - Fx[alpha])*nx[alpha,beta] \ + (Fy[beta] - Fy[alpha])*ny[alpha,beta] \ + (Fz[beta] - Fz[alpha])*nz[alpha,beta] """ b = zeros(dim) for i in range(0, dim): ai, bi = I.i2p(i) b[i] = -( \ (Fx[bi] - Fx[ai])*nx[ai,bi] \ +(Fy[bi] - Fy[ai])*ny[ai,bi] \ +(Fz[bi] - Fz[ai])*nz[ai,bi]) print "M = \n" labels = ["r_%d%d" % I.i2p(i) for i in range(0, dim)] print utils.annotated_matrix(M, labels, labels) print "det(M) = %s" % det(M) print "b = \n" print b # check that M is symmetric for i in range(0, dim): for j in range(0, dim): assert M[i, j] == M[j, i] # """ if det(M) != 0.0: print "solve" pair_force_opt = lapack.solve(M,b) else: print "lstsq" pair_force_opt = lstsq(M,b)[0] """ pair_force_opt, residuals, rank, singvals = lstsq(M, b) print "residuals" print residuals print "rank" print rank print "singular values" print singvals print "x = \n" print pair_force_opt bond_lengths = {} pair_forces = {} for i, active_i in enumerate(active_atoms): (Zi, posi) = atomlist[active_i] for j, active_j in enumerate(active_atoms): (Zj, posj) = atomlist[active_j] if active_i < active_j: bond_lengths[(active_i, active_j)] = norm(array(posi) - array(posj)) pair_forces[(active_i, active_j)] = pair_force_opt[symmat_index( i, j, Nat)] import string print " Pairwise Forces:" print " ================" print " atom i - atom j bond length [bohr] bond length [AA] force F_ij [hartree/bohr] force F_ij [eV/AA]" for active_i, active_j in pair_forces: print " %s- %s %s %s %s %s" % \ (string.ljust("%s%d" % (atom_names[atomlist[active_i][0]-1], active_i+1), 4), \ string.ljust("%s%d" % (atom_names[atomlist[active_j][0]-1], active_j+1), 4), \ string.rjust("%.7f bohr" % bond_lengths[(active_i,active_j)], 20), \ string.rjust("%.7f AA" % (bond_lengths[(active_i,active_j)]*bohr_to_angs), 20), \ string.rjust("%.7f hartree/bohr" % pair_forces[(active_i,active_j)], 30), \ string.rjust("%.7f eV/AA" % (pair_forces[(active_i,active_j)]*hartree_to_eV/bohr_to_angs), 30)) def error(pair_force): """ Not every potential can be decomposed into a sum a pair potentials. This function determines the error incurred by making the decomposition. """ dVpair = zeros((Nat, Nat)) for i in range(0, Nat): for j in range(i, Nat): if i == j: # dVpair[i,i] = 0 continue # F_ij = - d/dr V_ij(r) dVpair[i, j] = -pair_force[symmat_index(i, j, Nat)] dVpair[j, i] = dVpair[i, j] Sx = 0.0 Sy = 0.0 Sz = 0.0 for l in range(0, Nat): dSx = Fx[l] dSy = Fy[l] dSz = Fz[l] for i in range(0, Nat): if i == l: continue dSx += dVpair[l, i] * nx[l, i] dSy += dVpair[l, i] * ny[l, i] dSz += dVpair[l, i] * nz[l, i] Sx += abs(dSx) Sy += abs(dSy) Sz += abs(dSz) S = Sx + Sy + Sz return S e = error(pair_force_opt) error = e / float(Nat) print "ERROR PER ATOM = %s" % error return bond_lengths, pair_forces, error