def _overlap_AB_f90(atomlist1, atomlist2, valorbs1, valorbs2, SKT): """ This function calls an external Fortran function that computes the matrix elements. Since hashes and tuples cannot be used easily in Fortran, the arguments are brought into a form that can be fed into the Fortran function. """ atom_type_dic, spline_deg, tab_filled_SH0, tab_filled_D, \ (S_knots, S_coefs, H_knots, H_coefs, D_knots, D_coefs) = \ combine_slako_tables_f90(SKT) # number of atoms Nat1 = len(atomlist1) Nat2 = len(atomlist2) # orbitals atom_indeces1, atom_types1, ls1, ms1 = atomlist2orbitals( atomlist1, valorbs1, atom_type_dic) atom_indeces2, atom_types2, ls2, ms2 = atomlist2orbitals( atomlist2, valorbs2, atom_type_dic) assert atom_indeces1 == atom_indeces2, "atomlist1 and atomlist2 should contain the same atoms in the same order!" # count valence orbitals Norb1 = len(ls1) Norb2 = len(ls2) # distances and direction pos1 = XYZ.atomlist2vector(atomlist1) pos1 = np.reshape( pos1, (Nat1, 3)).transpose() # pos(:,i) is the 3d position of atom i pos2 = XYZ.atomlist2vector(atomlist2) pos2 = np.reshape(pos2, (Nat2, 3)).transpose() r, x, y, z = slako.slako.directional_cosines(pos1, pos2) # call Fortran function S = slako.slako.overlap12(atom_indeces1, atom_types1, ls1, ms1, atom_indeces2, atom_types2, ls2, ms2, r, x, y, z, spline_deg, tab_filled_SH0, S_knots, S_coefs) return S
def velocities_finite_diff(atomlists, dt): """ compute the velocities from finite differences between two time-steps Parameters: =========== atomlists: list of geometries along trajectories for each time step dt: nuclear time step in a.u. Returns: ======== positions: list of vectors with positions for each time step velocities: list of vectors with velocities for each time step """ Nt = len(atomlists) positions = [] velocities = [] pos0 = XYZ.atomlist2vector(atomlists[0]) positions.append(pos0) for i in range(1, Nt): pos1 = XYZ.atomlist2vector(atomlists[i]) vel = (pos1 - pos0) / dt positions.append(pos1) velocities.append(vel) pos0 = pos1 velocities.append(vel) return positions, velocities
def add_polyatomic_curve(self, geometries, forces_wo_frep, forces_with_frep, \ curve_name="polyatomic", weight=1.0, max_error=0.01, active_atoms=None): """ compute generalized forces that act along the bond lengths and calculate repulsive forces for arbitrary non-linear molecule with more than 2 atoms. Parameters: =========== geometries: list of geometries (in atomlist format) forces_wo_frep: list of forces (in atomlist format) containing the DFTB forces only from the electronic hamiltonian forces_with_frep: list of forces (in atomlist format) containing the forces from a higher level method (including nuclear repulsion) curve_name: some identifier for plotting weight: assign a weight of this data set during fitting max_error: for large bond lengths and repulsive potentials that cannot be represented as a sum of pairwise potentials, the error incurred from a pairwise decomposition can be large. Those geometries where the error per atom exceeds max_error are not included in the fit. active_atoms: list of atomic indeces. Only atoms in this list take part in the fit. If set to [], all atoms are included. """ for atomlist, f_wo_rep, f_with_rep in zip(geometries, forces_wo_frep, forces_with_frep): # repulsive potential is the difference between true forces and electronic DFTB forces # in cartesian coordinates frep = XYZ.atomlist2vector(f_with_rep) - XYZ.atomlist2vector( f_wo_rep) forcelist = XYZ.vector2atomlist(frep, atomlist) from numpy.linalg.linalg import LinAlgError if active_atoms == []: # by default all atoms are included into the fit active_atoms = [i for i in range(0, len(atomlist))] try: bond_lengths, pair_forces, error = pairwise_decomposition( atomlist, forcelist) except LinAlgError: print "LINALG ERROR => do not include this geometry" continue if error > max_error: print "Error (%s) from pairwise decomposition too large (threshold: %s) => do not include this geometry!" % ( error, max_error) continue for (i, j) in bond_lengths.keys(): if (i not in active_atoms) or (j not in active_atoms): continue Zi = atomlist[i][0] Zj = atomlist[j][0] if (Zi == self.Z1 and Zj == self.Z2) or (Zi == self.Z2 and Zj == self.Z1): self.nuclear_separation.append(bond_lengths[(i, j)]) self.repulsive_gradient.append(-pair_forces[(i, j)]) # each point is weighted by 1/sigma self.weights.append(weight / max(error, 1.0e-2)) self.curve_ID.append(self.curve_counter) self.curve_names.append(curve_name) self.curve_counter += 1
def load_fitpaths_it(self, fit_dir): """ load geometries of all fit paths from the folder `fit_dir` and return an iterator to the fit steps. Each fit step consists of the tuple (atomlist, en_rep, force_rep) The meaning of the elements of a fit step are as follows: atomlist : geometry of of fit step, list of tuples (Zi,[xi,yi,zi]) en_rep : energy difference (E_ref - E_elec) in Hartree force_rep: vector with force difference (F_ref - F_elec) in Hartree/bohr, force_rep[3*i:3*(i+1)] is the force [Fx(i),Fy(i),Fz(i)] on atom i. """ self.names = [ os.path.basename(xyz)[:-4] for xyz in glob.glob("%s/FIT_PATHS/*.xyz" % fit_dir) ] for name in self.names: print("Loading fit path '%s' " % name) geom_xyz = os.path.join(fit_dir, "FIT_PATHS", "%s.xyz" % name) en_dftb_dat = os.path.join(fit_dir, "DFTB", "%s.energies.dat" % name) en_ref_dat = os.path.join(fit_dir, "REFERENCE", "%s.energies.dat" % name) forces_dftb_xyz = os.path.join(fit_dir, "DFTB", "%s.forces.xyz" % name) forces_ref_xyz = os.path.join(fit_dir, "REFERENCE", "%s.forces.xyz" % name) try: geometries = XYZ.read_xyz(geom_xyz) energies_dftb = np.loadtxt(en_dftb_dat) energies_ref = np.loadtxt(en_ref_dat) forces_dftb = XYZ.read_xyz(forces_dftb_xyz) forces_ref = XYZ.read_xyz(forces_ref_xyz) for atomlist, en_dftb, en_ref, f_dftb, f_ref in zip( geometries, energies_dftb, energies_ref, forces_dftb, forces_ref): # compute E_rep = E_ref - E_el en_rep = en_ref - en_dftb # compute E_rep = F_ref - F_el force_rep = XYZ.atomlist2vector( f_ref) - XYZ.atomlist2vector(f_dftb) yield atomlist, en_rep, force_rep except IOError as e: print("Loading of fit path '%s' failed!" % name)
def wigner_from_G09_hessian(g09_file, Nsample=100, zero_threshold=1.0e-9): """ create Wigner ensemble based on hessian matrix from Gaussian 09 calculation """ suffix = g09_file.split(".")[-1] if suffix in ["out", "log"]: print("Reading Gaussian 09 log file %s" % g09_file) atomlist = Gaussian.read_geometry(g09_file) forces = Gaussian.read_forces(g09_file) hess = Gaussian.read_force_constants(g09_file) elif suffix in ["fchk"]: print("Reading formatted Gaussian 09 checkpoint file %s" % g09_file) Data = Checkpoint.parseCheckpointFile(g09_file) # cartesian coordinates pos = Data["_Current_cartesian_coordinates"] atnos = Data["_Atomic_numbers"] # forces frc = -Data["_Cartesian_Gradient"] atomlist = [] forces = [] for i, Zi in enumerate(atnos): atomlist.append((Zi, tuple(pos[3 * i:3 * (i + 1)]))) forces.append((Zi, tuple(frc[3 * i:3 * (i + 1)]))) # Hessian hess = Data["_Cartesian_Force_Constants"] masses = np.array(AtomicData.atomlist2masses(atomlist)) x0 = XYZ.atomlist2vector(atomlist) x0 = shift_to_com(x0, masses) grad = -XYZ.atomlist2vector(forces) grad_nrm = la.norm(grad) print(" gradient norm = %s" % grad_nrm) # assert grad_nrm < 1.0e-3, "Gradient norm too large for minimum!" vib_freq, vib_modes = vibrational_analysis(hess, masses, zero_threshold=zero_threshold) Aw, Bw = wigner_distribution(x0, hess, masses, zero_threshold=zero_threshold) gw = GaussianWavepacket.Gaussian(Aw, Bw) qs, ps = gw.sample(Nsample) mx = np.outer(masses, np.ones(Nsample)) * qs avg_com = np.mean(np.sum(mx[::3, :], axis=0)), np.mean( np.sum(mx[1::3, :], axis=0)), np.mean(np.sum(mx[2::3, :], axis=0)) print(avg_com) geometries = [ XYZ.vector2atomlist(qs[:, i], atomlist) for i in range(0, Nsample) ] return geometries
def molecular_frame_transformation(atomlist): """ The molecule is shifted to the center of mass and its principle axes of inertia are aligned with the coordinate axes. This standard orientation defines the molecular frame. The translation vector and Euler angles needed to transform the geometry from the molecular frame to the original frame are also returned. Returns: ======== atomlist_std: molecular geometry in standard orientation (a,b,g): Euler angles in z-y-z convention cm: 3D vector with center of mass """ pos = XYZ.atomlist2vector(atomlist) masses = AtomicData.atomlist2masses(atomlist) # shift center of mass to origin Nat = len(atomlist) pos_std = np.zeros(pos.shape) cm = center_of_mass(masses, pos) for i in range(0, Nat): pos_std[3*i:3*i+3] = pos[3*i:3*i+3] - cm (a,b,g) = euler_angles_inertia(masses, pos_std) R = EulerAngles2Rotation(a,b,g) for i in range(0, Nat): pos_std[3*i:3*i+3] = np.dot(R, pos_std[3*i:3*i+3]) atomlist_std = XYZ.vector2atomlist(pos_std, atomlist) # The MolecularCoord module uses a strange convention for Euler angles. # Therefore we extract the Euler angles in z-y-z convention directly # from the rotation matrix, that rotates the molecule from the standard # orientation into the original orientation Rinv = R.transpose() a,b,g = rotation2EulerAngles(Rinv, convention="z-y-z") return atomlist_std, (a,b,g), cm
def remove_overlapping_atoms(atomlist, tol=1.0e-4): print("remove overlapping atoms") nat = len(atomlist) # The i-th atom should be removed if duplicate[i] == 1 # Fortran rs = XYZ.atomlist2vector(atomlist) rs = np.reshape(rs, (nat, 3)) duplicate = thomson.thomson.remove_overlapping(rs, tol) # python """ duplicate_py = np.zeros(nat, dtype=int) for i in range(0, nat): for j in range(i+1,nat): posi = np.array(atomlist[i][1]) posj = np.array(atomlist[j][1]) Rij = la.norm(posi-posj) if Rij < tol: duplicate_py[j] = 1 assert np.sum(abs(duplicate - duplicate_py)) < 1.0e-10 """ # only copy unique atoms atomlist_unique = [] for i in range(0, nat): if duplicate[i] == 0: atomlist_unique.append( atomlist[i] ) return atomlist_unique
def __init__(self, atomlist): # convert the list of atoms into a list of 3d points nat = len(atomlist) pos = XYZ.atomlist2vector(atomlist) points = [] for i in range(0, nat): points.append(pos[3 * i:3 * (i + 1)]) # QHull needs at least 4 point to construct the initial # simplex. if nat < 4: # If there are less than 4 atoms, # we add the center of mass as an additional point masses = AtomicData.atomlist2masses(atomlist) com = MolCo.center_of_mass(masses, pos) points.append(com) if nat < 3: # If there are less than 3 atoms we add # an arbitrary point on the x-axis points.append(com + np.array([0.0005, 0.0, 0.0])) if nat < 2: # If there is only one atom, we add another arbitrary # point on the y-axis points.append(com + np.array([0.0, 0.0005, 0.0])) # We add small random numbers to the input coordinates, so that # we get a 3D convex hull even if the molecule is planar points = np.array(points) + 0.0001 * np.random.rand(len(points), 3) # find the convex hull using the qhull code hull = ConvexHull(points, qhull_options="QbB Qt") # call the constructor of the parent class (MinimalEnclosingBox) super(MoleculeBox, self).__init__(hull)
def getCenterOfMass(self): pos = XYZ.atomlist2vector(self.atomlist) com = center_of_mass(self.masses, pos) print("Center of Mass") print("==============") print(com) return com
def _electrostatics(self): """ computes the electrostatic interaction between the MM charges, enCoul = - sum_i<j qi*qj/|Ri-Rj| and the gradient w/r/t to the positions of the individual atoms """ Nat = len(self.atomlist) x = XYZ.atomlist2vector(self.atomlist) # enCoul = 0.0 gradCoul = np.zeros(3 * Nat) # gradient for i in range(0, Nat): for j in range(i + 1, Nat): Rij_vec = x[3 * i:3 * (i + 1)] - x[3 * j:3 * (j + 1)] Rij = la.norm(Rij_vec) # contribution to energy Vij = self.mm_charges[i] * self.mm_charges[j] / Rij enCoul += Vij # contribution to the gradient eij = Rij_vec / Rij # unit vector gij = Vij / Rij * eij gradCoul[3 * i:3 * (i + 1)] -= gij gradCoul[3 * j:3 * (j + 1)] += gij if self.verbose > 0: print "MM electrostatic energy: %e" % enCoul return enCoul, gradCoul
def write_atomlist(atomlist): Nat = len(atomlist) pos = XYZ.atomlist2vector(atomlist) txt = " in Angstrom:\n" txt += "Atom X Y Z\n" for i in range(0, Nat): txt += (" %s " % i).rjust(3) txt += "%+4.7f %+4.7f %+4.7f\n" % tuple(pos[3*i:3*(i+1)]*AtomicData.bohr_to_angs) txt += "\n" return txt
def grad_cartesian2internal(atomlist, gradients): mol = MolecularCoords(atomlist) jac, jac_inv = mol.getJacobian() for I,gradI in enumerate(gradients): gradIvec = XYZ.atomlist2vector(gradI) gradIinternal = dot(jac_inv, gradIvec) print("Gradient on state %s" % I) print("in cartesian coordinates") print(gradIvec) print("in internal coordinates") print(gradIinternal)
def f(x): thomson_cap = XYZ.vector2atomlist(x, thomson_cap_ref) # thomson_cap = enforce_constaints(thomson_cap) print "thomson_cap" print thomson_cap thomson_pts = thomson_tube + thomson_cap XYZ.write_xyz("/tmp/thomson_minimization.xyz", [thomson_cap], mode="a") en = potential_energy(thomson_pts) # gradient of potential energy grad = XYZ.atomlist2vector(gradient_energy(thomson_pts)) print "en = %s" % en return en #, grad
def build_xyz(self, a1, a2, a3, unitcell, meso, beta): n, m = self.grid.shape center = (n / 2, m / 2) flake_atomlist = [] unitcell_vec = XYZ.atomlist2vector(unitcell) for i in range(0, n): for j in range(0, m): if self.grid[i, j] == 1: if self.orientation[i, j] != 0: axis, angle = self.rotations[self.orientation[i, j]] Rot = axis_angle2rotation(axis, angle) else: Rot = np.identity(3) R = (i - n / 2) * a1 + (j - m / 2) * a2 + 0.0 * a3 shifted_unitcell_vec = np.zeros(unitcell_vec.shape) for at in range(0, len(unitcell)): # rotate and translate shifted_unitcell_vec[3*at:3*(at+1)] = \ np.dot(Rot, unitcell_vec[3*at:3*(at+1)]) + R flake_atomlist += XYZ.vector2atomlist( shifted_unitcell_vec, unitcell) # hydrogenize or add meso/beta substituents for (lr, tb) in self.moves: i_hyd = i + lr j_hyd = j + tb direction = lr * a1 + tb * a2 direction /= la.norm(direction) if 0 <= i_hyd < n and 0 <= j_hyd < m: if self.grid[i_hyd, j_hyd] != 1: # no neighbouring porphine in that direction => add both meso and beta hydrogens hydrogens = meso + beta # hydrogens can also contain protecting groups, so it's not only H-atoms! elif self.grid[i_hyd,j_hyd] == 1 \ and (((self.orientation[i,j] == 0) and (self.orientation[i_hyd,j_hyd] in [5,6])) \ or ((self.orientation[i_hyd,j_hyd] == 0) and (self.orientation[i,j] in [5,6]))): hydrogens = beta else: hydrogens = [] # add hydrogens hydadd = [] for at in range(0, len(hydrogens)): (Zat, pos) = hydrogens[at] hyddir = np.array(pos) hyddir /= la.norm(hyddir) angle = np.arccos(np.dot(hyddir, direction)) if abs(angle) < np.pi / 4.0: hydadd += [ (Zat, np.dot(Rot, np.array(pos)) + R) ] flake_atomlist += hydadd # meso-meso return flake_atomlist
def _DipoleMatrix_f90(atomlist1, atomlist2, valorbs, SKT, Mproximity, S): """ This function calls an external Fortran function that computes the matrix elements. Since hashes and tuples cannot be used easily in Fortran, the arguments are brought into a form that can be fed into the Fortran function. """ atom_type_dic, spline_deg, tab_filled_SH0, tab_filled_D, \ (S_knots, S_coefs, H_knots, H_coefs, D_knots, D_coefs) = \ combine_slako_tables_f90(SKT) # number of atoms Nat1 = len(atomlist1) Nat2 = len(atomlist2) # orbitals atom_indeces1,atom_types1,ls1,ms1 = atomlist2orbitals(atomlist1, valorbs, atom_type_dic) atom_indeces2,atom_types2,ls2,ms2 = atomlist2orbitals(atomlist2, valorbs, atom_type_dic) assert atom_indeces1 == atom_indeces2, "atomlist1 and atomlist2 should contain the same atoms in the same order!" # count valence orbitals Norb1 = len(ls1) Norb2 = len(ls2) # distances and direction pos1 = XYZ.atomlist2vector(atomlist1) pos1 = np.reshape(pos1,(Nat1,3)).transpose() # pos(:,i) is the 3d position of atom i pos2 = XYZ.atomlist2vector(atomlist2) pos2 = np.reshape(pos2,(Nat2,3)).transpose() r,x,y,z = slako.slako.directional_cosines(pos1,pos2) Dipole = slako.slako.dipolematrix(atom_indeces1,atom_types1,ls1,ms1, atom_indeces2,atom_types2,ls2,ms2, r,x,y,z,pos1, Mproximity, S, spline_deg, tab_filled_D, D_knots, D_coefs) # In fortran the faster indeces come first, roll axes D(xyz,mu,nu) -> D(mu,nu,xyz) Dipole = np.rollaxis(Dipole,0,3) return Dipole
def __init__(self, atomlist, ring_atoms=[], units="bohr"): """ creates a molecule with a ring Parameters ---------- atomlist: list of tupes (Zi,[xi,yi,zi]) with atomic positions Optional -------- ring_atoms: indeces of atoms forming the ring bohr: units of coordinates [xi,yi,zi] """ if units == "Angstrom": x = XYZ.atomlist2vector(atomlist) / AtomicData.bohr_to_angs self.atomlist = XYZ.vector2atomlist(x, atomlist) else: self.atomlist = atomlist self.ring_atoms = ring_atoms
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 getGeneralizedForces(self, cartesian_forces): """ transform forces into internal coordinates using Jacobian FQ_i = dV/dq_i = sum_j dV/dx_j*dx_j/dq_i = sum_j J_ji*F_j F_j are cartesian forces, FQ_i are generalized forces Parameters: =========== cartesian_forces: list of forces on each atom [(Z1,[F1_x,F1_y, F1_z]), ..., (ZN,[FN_x,FN_y,FN_z)] """ forces = XYZ.atomlist2vector(cartesian_forces) print "Cartesian forces:" print forces J = self.getJacobian() Q = dot(J.transpose(),forces) print "Generalized forces:" print Q return Q
def nact_cartesian2internal(atomlist, nact): mol = MolecularCoords(atomlist) jac, jac_inv = mol.getJacobian() for (I,J),nactIJ in nact.items(): # RxGrad = zeros(3) Grad = zeros(3) for i,(Zi, posi) in enumerate(atomlist): RxGrad += cross(array(posi), array(nactIJ[i][1])) Grad += array(nactIJ[i][1]) print("Grad") print(Grad) print("RxGrad") print(RxGrad) # nactIJvec = XYZ.atomlist2vector(nactIJ) nactIJinternal = dot(jac_inv, nactIJvec) print("Non-adiabatic coupling vectors between state %s and %s" % (I,J)) print("in cartesian coordinates") print(nactIJvec) print("in internal coordinates") print(nactIJinternal)
def dftb_numerical_charge_gradients(dftb2, atomlist, dp=0.0000001): """ compute gradients of Mulliken charges numerically Optional: --------- dp: step size for numerical differentiation """ from DFTB import XYZ dftb2.setGeometry(atomlist) dftb2.getEnergy(density_mixer=None, scf_conv=1.0e-14, start_from_previous=0) charges0 = dftb2.getPartialCharges() # atomlist0 = dftb2.getGeometry() vec = XYZ.atomlist2vector(atomlist0) Nat = len(atomlist) # charge gradient dQdp = np.zeros((3 * Nat, Nat)) for i in range(0, 3 * Nat): # vec + dp vec_dp = np.copy(vec) vec_dp[i] += dp atomlist = XYZ.vector2atomlist(vec_dp, atomlist0) dftb2.setGeometry(atomlist) dftb2.getEnergy(density_mixer=None, scf_conv=1.0e-14, start_from_previous=0) charges1 = dftb2.getPartialCharges() dQdp[i, :] = (charges1 - charges0) / dp return dQdp
def plotLabels(self): #print "visualize labels" # draw atom labels if self.show_flags["labels"] == True: mlab = self.scene.mlab labels = [] molecules = [] for cube in self.cubes: molecules.append(cube.atomlist) for atomlist in self.molecules: molecules.append(atomlist) for atomlist in molecules: nat = len(atomlist) vec = XYZ.atomlist2vector(atomlist) x, y, z = vec[::3], vec[1::3], vec[2::3] atom_names = [AtomicData.atom_names[Z-1] for (Z,pos) in atomlist] for i in range(0, nat): name = atom_names[i].upper() + "-%d" % i label = mlab.text(x[i],y[i],name, z=z[i], figure=self.scene.mayavi_scene) label.actor.set(text_scale_mode='none', width=0.05, height=0.1) label.property.set(justification='centered', vertical_justification='centered') labels.append(label) self.label_objects += labels
def _update_visualization(self, atomlist): """ only change the underlying data but do not recreate visualization. This is faster and avoids jerky animations. """ vec = XYZ.atomlist2vector(atomlist) x, y, z = vec[::3], vec[1::3], vec[2::3] s = self.atom_radii if self.show_flags["atoms"] == False: # make atoms so small that one does not see them scale_factor = 0.01 self.shown_atoms = False else: scale_factor = 0.3 self.shown_atoms = True # update atom positions self.atoms.mlab_source.set(x=x,y=y,z=z,u=s,v=s,w=s, scalars=self.Zs, scale_factor=scale_factor) # atoms are coloured by their atomic number self.atoms.glyph.color_mode = "color_by_scalar" self.atoms.glyph.glyph_source.glyph_source.center = [0,0,0] self.atoms.module_manager.scalar_lut_manager.lut.table = self.lut self.atoms.module_manager.scalar_lut_manager.data_range = (0.0, 255.0) # update bonds self.bonds.mlab_source.reset(x=x,y=y,z=z, scale_factor=1.0) C = XYZ.connectivity_matrix(atomlist) Nat = len(atomlist) connections = [] for i in range(0, Nat): Zi, posi = atomlist[i] for j in range(i+1,Nat): if C[i,j] == 1: Zj, posj = atomlist[j] connections.append( (i,j) ) self.bonds.mlab_source.dataset.lines = np.array(connections) self.bonds.mlab_source.dataset.modified()
def dftb_numerical_mo_gradients(dftb2, atomlist, dp=0.0000001): """ compute gradients of orbital energies and MO coefficients numerically by finite differences Optional: --------- dp: step size for numerical differentiation """ from DFTB import XYZ dftb2.setGeometry(atomlist) dftb2.getEnergy() orbe0 = dftb2.getKSEnergies() orbs0 = dftb2.getKSCoefficients() # atomlist0 = dftb2.getGeometry() vec = XYZ.atomlist2vector(atomlist0) Nat = len(atomlist) Norb, Norb = orbs0.shape dorbe = np.zeros((3 * Nat, Norb)) dorbs = np.zeros((3 * Nat, Norb, Norb)) for i in range(0, 3 * Nat): vec_dp = np.copy(vec) vec_dp[i] += dp atomlist = XYZ.vector2atomlist(vec_dp, atomlist0) dftb2.setGeometry(atomlist) dftb2.getEnergy() orbe1 = dftb2.getKSEnergies() orbs1 = dftb2.getKSCoefficients() dorbe[i, :] = (orbe1 - orbe0) / dp dorbs[i, :, :] = (orbs1 - orbs0) / dp return dorbe, dorbs
def check_analytic_gradients(atomlist0): """ compare analytical gradients for gamma_solvent with numerical ones """ from DFTB import utils from DFTB import XYZ cavity = SolventCavity(points_per_sphere=6) nats = len(atomlist0) x0 = XYZ.atomlist2vector(atomlist0) # ... analytical gradients cavity.constructSAS(atomlist0) cavity.constructCOSMO() gamma_solvent, grad_ana = cavity.constructCOSMOgradient() # ... numerical gradients grad_num = np.zeros((3 * nats, nats, nats)) for i in range(0, nats): for j in range(0, nats): # define function for computing gamma_solvent[i,j] def f(x): atomlist = XYZ.vector2atomlist(x, atomlist0) cavity.constructSAS(atomlist) gamma_solvent = cavity.constructCOSMO() return gamma_solvent[i, j] grad_num[:, i, j] = utils.numerical_gradient(f, x0) print("analytical gradient") print(np.round(grad_ana, 6)) print("numerical gradient") print(np.round(grad_num, 6)) err = la.norm(grad_ana - grad_num) print("|(grad g_solvent)_num - (grad g_solvent)_ana|= %e" % err) assert err < 1.0e-5
print("Compute forces with DFTB") print("========================") print("") fh_en = open(energy_file, "w") print("# ELECTRONIC ENERGY / HARTREE", file=fh_en) # first geometry atomlist = XYZ.read_xyz(geom_file)[0] # read charge from title line in .xyz file kwds = XYZ.extract_keywords_xyz(geom_file) charge = kwds.get("charge", opts.charge) pes = PotentialEnergySurfaces(atomlist, charge=charge) # dftbaby needs one excited states calculation to set all variables x = XYZ.atomlist2vector(atomlist) pes.getEnergies(x) for i, atomlist in enumerate(XYZ.read_xyz(geom_file)): # compute electronic ground state forces with DFTB x = XYZ.atomlist2vector(atomlist) en = pes.getEnergy_S0(x) # total ground state energy including repulsive potential en_tot = en[0] print("Structure %d enTot= %s Hartree" % (i, en_tot)) # electronic energy without repulsive potential en_elec = pes.tddftb.dftb2.getEnergies()[2] gradVrep, gradE0, gradExc = pes.grads.gradient(I=0) # exclude repulsive potential from forces grad = gradE0 + gradExc forces = XYZ.vector2atomlist(-grad, atomlist)
default="[]") (opts, args) = parser.parse_args() if len(args) < 4: parser.print_help() exit(-1) xyz0 = args[0] xyz1 = args[1] N = int(args[2]) xyz_interp = args[3] # read initial geometry atomlist0 = XYZ.read_xyz(xyz0)[0] x0 = XYZ.atomlist2vector(atomlist0) # read final geometry atomlist1 = XYZ.read_xyz(xyz1)[0] x1 = XYZ.atomlist2vector(atomlist1) # interpolation parameter rs = np.linspace(0.0, 1.0, N) geometries_interp = [] if opts.coord_system == "cartesian": for r in rs: xr = x0 + r * (x1 - x0) geometries_interp.append(XYZ.vector2atomlist(xr, atomlist0)) elif opts.coord_system == "internal":
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 get_2d_bbox(atomlist): pos = XYZ.atomlist2vector(atomlist) x, y, z = pos[0::3], pos[1::3], pos[2::3] return x.min(), y.min(), x.max(), y.max()
def writeTimeSeries(self, fish, time_series=[]): atomlist = self.pes.tddftb.dftb2.getGeometry() Nat = len(atomlist) # number of QM atoms x = XYZ.atomlist2vector(atomlist) coordinates = np.reshape(x, (Nat, 3)) symbols = [AtomicData.atom_names[Z - 1] for (Z, pos) in atomlist] # fish is an instance of the class fish.moleculardynamics if "particle-hole charges" in time_series: # ph charges are weighted by quantum populations |C(I)|^2 particle_charges, hole_charges = self.getAvgParticleHoleCharges( fish.c) # append charges to file outfile = open("particle_hole_charges.xyz", "a") outfile.write("%d\n" % Nat) outfile.write(" time: %s fs\n" % (fish.time / fish.fs_to_au)) tmp = fish.au_to_ang * coordinates for i in range(0, Nat): outfile.write("%s %20.12f %20.12f %20.12f %5.7f %5.7f\n" \ %(symbols[i],tmp[i][0],tmp[i][1],tmp[i][2],particle_charges[i], hole_charges[i])) outfile.close() if "particle-hole charges current" in time_series: # ph charges of the current electronic state if fish.state == 0: # ground state particle_charges = np.zeros(Nat) hole_charges = np.zeros(Nat) else: # excited state particle_charges, hole_charges = self.pes.tddftb.ParticleHoleCharges( fish.state - 1) # append charges to file outfile = open("particle_hole_charges_current.xyz", "a") outfile.write("%d\n" % Nat) outfile.write(" time: %s fs\n" % (fish.time / fish.fs_to_au)) tmp = fish.au_to_ang * coordinates for i in range(0, Nat): outfile.write("%s %20.12f %20.12f %20.12f %5.7f %5.7f\n" \ %(symbols[i],tmp[i][0],tmp[i][1],tmp[i][2],particle_charges[i], hole_charges[i])) outfile.close() if "Lambda2 current" in time_series: if fish.state == 0: # ground state, Lambda2 descriptor is only defined for excited states Lambda2 = -1.0 else: # excited state Lambda2 = self.pes.tddftb.Lambda2[fish.state - 1] if not os.path.isfile("lambda2_current.dat"): outfile = open("lambda2_current.dat", "w") # write header print >> outfile, "# TIME / fs LAMBDA2 " print >> outfile, "# 0 - charge transfer state" print >> outfile, "# 1 - local excitation " print >> outfile, "# -1 - ground state (Lambda2 undefined)" else: outfile = open("lambda2_current.dat", "a") print >> outfile, "%14.8f %+7.4f" % (fish.time / fish.fs_to_au, Lambda2) outfile.close() if "Lambda2" in time_series: # Lambda2 descriptors for all excited states if not os.path.isfile("lambda2.dat"): outfile = open("lambda2.dat", "w") # write header print >> outfile, "# TIME / fs LAMBDA2's OF EXCITED STATES" print >> outfile, "# 0 - charge transfer state" print >> outfile, "# 1 - local excitation " print >> outfile, "# -1 - ground state (Lambda2 undefined)" else: outfile = open("lambda2.dat", "a") print >> outfile, "%14.8f " % (fish.time / fish.fs_to_au), for I in range(1, self.nstates): print >> outfile, " %+7.4f" % (self.pes.tddftb.Lambda2[I - 1]), print >> outfile, "" outfile.close() if "transition charges current" in time_series: # transition charges for S0 -> current electronic state S_n if fish.state == 0: # ground state transition_charges = np.zeros(Nat) else: # excited state transition_charges = self.pes.tddftb.TransitionChargesState( fish.state - 1) # append charges to file outfile = open("transition_charges_current.xyz", "a") outfile.write("%d\n" % Nat) outfile.write(" time: %s fs\n" % (fish.time / fish.fs_to_au)) tmp = fish.au_to_ang * coordinates for i in range(0, Nat): outfile.write("%s %20.12f %20.12f %20.12f %5.7f\n" \ %(symbols[i],tmp[i][0],tmp[i][1],tmp[i][2], transition_charges[i])) outfile.close()
help= "Normal modes with very low frequencies (freq^2 < zero_threshold) are treated as zero modes. This allows to freeze modes that would lead to large unrealistic displacements. [default: %default]" ) (opts, args) = parser.parse_args() if len(args) < 2: print usage exit(-1) xyz_file = args[0] hess_file = args[1] # should be options # optimized geometry atomlist = XYZ.read_xyz(xyz_file)[-1] xopt = XYZ.atomlist2vector(atomlist) # load hessian hess = np.loadtxt(hess_file) masses = AtomicData.atomlist2masses(atomlist) vib_freq, vib_modes = HarmonicApproximation.vibrational_analysis(xopt, hess, masses, \ zero_threshold=opts.zero_threshold, is_molecule=True) # SAMPLE INITIAL CONDITIONS FROM WIGNER DISTRIBUTION qs, ps = HarmonicApproximation.initial_conditions_wigner( xopt, hess, masses, Nsample=opts.Nsample, zero_threshold=opts.zero_threshold) # make hydrogens slower