def cartesian2internal(self, x): """ convert cartesian to redundant internal coordinates Parameters ---------- x : cartesian coordinates, array of length 3*Nat Returns ------- q : redundant internal coordinates of length n >= 3*nat-6 """ if self.verbose > 1: print("cartesian -> internal") x = MolCo.shift_to_com(x, self.masses) # values of internal coordinates at cartesian position x qprim, Bprim = self.force_field.getRedundantInternalCoordinates(x, 0) #test_wilson_bmatrix(self.force_field, x) # q = qprim[self.active_internals] B = Bprim[self.active_internals, :] # At the cartesian position x0 we know the internal coordinates q0 exactly. # We need this information later to deduce the new cartesian coordinates x (close to x0) # which corresponds to the new internal coordinates q (also close to q0). self.q0 = q self.x0 = x self.B0 = B # projector self.P0 = self.feasibility_projector(B) return q
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 so3_quadrature(n): """ grid for averaging over Euler angles a,b,g according to Kostoloc,P. et.al. FFTs on the Rotation Group, section 2.3 """ alphas = [] betas = [] gammas = [] weights_beta = [] for i in range(0, 2*n): ai = (2.0*np.pi*i)/(2.0*n) bi = (np.pi*(2.0*i+1))/(4.0*n) gi = (2.0*np.pi*i)/(2.0*n) # weights wi = 0.0 for l in range(0, n): wi += 1.0/(2.0*l+1.0) * np.sin((2.0*l+1.0) * bi) wi *= 1.0/(1.0*n) * np.sin(bi) alphas.append(ai) betas.append(bi) gammas.append(gi) weights_beta.append(wi) # consistency check: The weights should be the solutions of the system of linear equations # sum_(k=0)^(2*n-1) wB(k) * Pm(cos(bk)) = delta_(0,m) for 0 <= m < n from scipy.special import legendre for m in range(0, n): sm = 0.0 Pm = legendre(m) for k in range(0, 2*n): sm += weights_beta[k] * Pm(np.cos(betas[k])) if m == 0: assert abs(sm-1.0) < 1.0e-10 else: assert abs(sm) < 1.0e-10 # # list of weights weights = [] # list of rotation matrices Rots = [] dV = 1.0/(2.0*n)**2 V = 0.0 for i in range(0, 2*n): ai = alphas[i] for j in range(0, 2*n): bj = betas[j] wj = weights_beta[j] for k in range(0, 2*n): gk = gammas[k] # R = MolCo.EulerAngles2Rotation(ai,bj,gk) weights.append( wj * dV ) Rots.append( R ) # V += wj * dV assert abs(V-1.0) < 1.0e-10 return weights, Rots
def rotate_atoms(atomlist, axis, angle): """ rotate all atoms around an axis """ R = MolCo.rotation_matrix(axis, angle) atomlist_rot = [] for (Z, pos) in atomlist: atomlist_rot.append((Z, np.dot(R, pos))) return atomlist_rot
def get_coord(atomlist, atom_ids): n = len(atom_ids) if n == 2: # bond length A,B = atom_ids coord = MolCo.distance(atomlist[A], atomlist[B]) coord *= AtomicData.bohr_to_angs elif n == 3: # angle A,B,C = atom_ids coord = MolCo.angle(atomlist[A], atomlist[B], atomlist[C]) coord *= 180.0/np.pi elif n == 4: # dihedral A,B,C,D = atom_ids coord = MolCo.dihedral_angle(atomlist[A], atomlist[B], atomlist[C], atomlist[D]) coord *= 180.0/np.pi else: raise ValueError("Internal coordinate should be specified by 2, 3 or 4 atom indeces!") return coord
def expected_zero_modes(x0, masses): """ How many eigen values of the Hessian should be zero because of the structure of the molecule (atom, linear, polyatomic)? """ # assume that coordinate vector with 3*N components belongs to a molecule # with N atoms assert len(x0) % 3 == 0 Nat = len(x0) / 3 # shift the origin to the center of mass x0_shift = MolCo.shift_to_com(x0, masses) # diagonalize the tensor of inertia to obtain the principal moments and # the normalized eigenvectors of I Inert = MolCo.inertial_tensor(masses, x0_shift) principle_moments, X = la.eigh(Inert) # check that the number of rotational and translational modes # is what is expected: # single atom => 3 translational modes only = 3 # dimer or linear molecule => 3 translational and 2 rotational modes = 5 # otherwise => 3 translational and 3 rotational modes = 6 # If the molecule is linear two of the principle moments of inertia # will be zero Ntrans = 3 # number of translational Nrot = 3 # and rotational modes which should be very close to zero is_linear = False pmom_abs = np.sort(abs(principle_moments)) # In a linear triatomic molecule we have Icc = Ibb > Iaa = 0 if abs(pmom_abs[2] - pmom_abs[1]) < 1.0e-6 and abs(pmom_abs[0]) < 1.0e-6: is_linear = True print("Molecule is linear") if Nat == 1: Nrot = 0 elif is_linear == True or Nat == 2: Nrot = 2 else: Nrot = 3 return (Ntrans + Nrot)
def asymptotic_density(wavefunction, Rmax, E): from PI import LebedevQuadrature k = np.sqrt(2*E) wavelength = 2.0 * np.pi/k r = np.linspace(Rmax, Rmax+wavelength, 30) n = 5810 th,phi,w = np.array(LebedevQuadrature.LebedevGridPoints[n]).transpose() x = LebedevQuadrature.outerN(r, np.sin(th)*np.cos(phi)) y = LebedevQuadrature.outerN(r, np.sin(th)*np.sin(phi)) z = LebedevQuadrature.outerN(r, np.cos(th)) wfn2 = abs(wavefunction((x,y,z), 1.0))**2 wfn2_angular = np.sum(w*wfn2, axis=0) from matplotlib.mlab import griddata import matplotlib.pyplot as plt th_resampled,phi_resampled = np.mgrid[0.0: np.pi: 30j, 0.0: 2*np.pi: 30j] resampled = griddata(th, phi, wfn2_angular, th_resampled, phi_resampled, interp="linear") # 2D PLOT plt.imshow(resampled.T, extent=(0.0, np.pi, 0.0, 2*np.pi)) plt.colorbar() from DFTB.Modeling import MolecularCoords as MolCo R = MolCo.EulerAngles2Rotation(1.0, 0.5, -0.3) th, phi = rotate_sphere(R, th, phi) plt.plot(th, phi, "r.") plt.plot(th_resampled, phi_resampled, "b.") plt.show() # SPHERICAL PLOT from mpl_toolkits.mplot3d import Axes3D x_resampled = resampled * np.sin(th_resampled) * np.cos(phi_resampled) y_resampled = resampled * np.sin(th_resampled) * np.sin(phi_resampled) z_resampled = resampled * np.cos(th_resampled) fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.plot_surface(x_resampled, y_resampled, z_resampled, rstride=1, cstride=1) ax.scatter(x_resampled, y_resampled, z_resampled, color="k", s=20) plt.show()
def cut_sphere(atomlist, R=20.0): """ remove all atoms outside sphere Parameters: =========== atomlist R: radius of sphere in bohr Returns: ======== atomlist with atoms inside sphere """ # shift to center of mass masses = AtomicData.atomlist2masses(atomlist) pos = XYZ.atomlist2vector(atomlist) pos_shifted = MolecularCoords.shift_to_com(pos, masses) atomlist = XYZ.vector2atomlist(pos_shifted, atomlist) Con = connectivity(atomlist) print("recursively remove connected atoms...") removed = [] # list of removed indeces for i,(Zi,posi) in enumerate(atomlist): print("i = %s" % i) if la.norm(posi) > R: if not (i in removed): print("remove %s%d" % (AtomicData.atom_names[Zi-1], i)) removed.append(i) # remove connect atoms connected = find_connected(Con, i) print("and connected atoms %s" % connected) removed += connected removed = set(removed) cut_atomlist = [] for i,(Zi,posi) in enumerate(atomlist): if i in removed: pass else: cut_atomlist.append( (Zi, posi) ) return cut_atomlist
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 euler_average(n): """ grid for averaging over Euler angles a,b,g Parameters: =========== n: number of grid points for each of the 3 dimensions Returns: ======== w: list of n^3 weights R: list of n^3 rotation matrices a function of a vector v, f(v), can be averaged over all roations as <f> = sum_i w[i]*f(R[i].v) """ alpha = np.linspace(0, 2.0*np.pi, n+1)[:-1] # don't count alpha=0 and alpha=2pi twice beta = np.linspace(0, np.pi, n) gamma = np.linspace(0, 2.0*np.pi, n+1)[:-1] # don't count gamma=0 and gamma=2pi twice # list of weights weights = [] # list of rotation matrices Rots = [] da = alpha[1]-alpha[0] db = beta[1]-beta[0] dg = gamma[1]-gamma[0] dV = da*db*dg / (8.0*np.pi**2) for a in alpha: for b in beta: for g in gamma: w = np.sin(b) * dV if (abs(w) > 0.0): weights += [w] R = MolCo.EulerAngles2Rotation(a,b,g) Rots += [ R ] return weights, Rots
rotation_matrices.append(orb_rot_byl[l]) # rotate orbitals i = 0 orbs_rotated = np.copy(orbs) for rot in rotation_matrices: dim = rot.shape[0] orbs_rotated[i:i+dim] = np.dot(rot, orbs[i:i+dim]) i += dim return orbs_rotated if __name__ == "__main__": a,b,g = 0.345345, 1.234, 0.56 orb_rot = orbital_rotation_matrices(a,b,g) R = MolCo.EulerAngles2Rotation(a,b,g, convention="z-y-z") print "R" print R # print np.dot(R, R.transpose()) # print "Rorb" # print orb_rot[1] mapping = {0: 1, 1: 2, 2: 0} Rmapped = np.zeros((3,3)) for i in range(0, 3): for j in range(0, 3): Rmapped[mapping[i],mapping[j]] = orb_rot[1][i,j] print "Rmapped" print Rmapped print R-Rmapped # print np.dot(orb_rot[1], orb_rot[1].transpose())
def _create_visualization(self, atomlist, charges, fragments): mlab = self.scene.mlab if self.show_flags["charges"] == True and type(charges) != type(None): self.charge_labels = [] for q,(Z,pos) in zip(charges, atomlist): x,y,z = pos if q < 0.0: color = (0.,0.,1.) elif q > 0.0: color = (1.,0.,0.) else: color = (1.,1.,1.) # color does not work so far txt = "%+2.3f" % q if self.show_flags["labels"] == True: # maybe charges should not overlap with labels txt = "%s" % txt label = mlab.text(x,y, txt, z=z, 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') self.charge_labels.append( label ) self.shown_charges = True else: self.shown_charges = False if self.show_flags["frag. charges"] == True and type(charges) != type(None): self.frag_charge_labels = [] for ifrag,(fragment_indeces, fragment_atomlist, fragment_box) in enumerate(fragments): # compute the charges on fragment qfrag = np.sum(charges[fragment_indeces]) print "Fragment charges: %s" % charges[fragment_indeces] # compute the center of the molecule, pos_frag = XYZ.atomlist2vector(fragment_atomlist) masses_frag = AtomicData.atomlist2masses(fragment_atomlist) com = MolCo.center_of_mass(masses_frag, pos_frag) # print "Fragment %d charge = %s" % (ifrag, qfrag) txt = "%+2.3f" % qfrag label = mlab.text(com[0],com[1], txt, z=com[2], line_width=0.8, 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') self.frag_charge_labels.append( label ) self.shown_frag_charges = True else: self.shown_frag_charges = False if self.show_flags["charge clouds"] == True and type(charges) != type(None): self.charge_clouds = [] vec = XYZ.atomlist2vector(atomlist) x, y, z = vec[::3], vec[1::3], vec[2::3] s = abs(charges) # The charge clouds represent surfaces of equal charge density around each atoms. # In DFTB the charge fluctuations are modelled by a Gaussian: # F(r) = 1/(2*pi*sA^2)^(3/2) * exp(-r^2/(2*sA^2)) # The radii of the charge clouds are scaled by the charge on the atom: # r = q * r0 # The radius r0 belongs to a charge cloud containing exactly 1 electron, it depends # on the hubbard parameter through sA and on the isoValue: # F(r0) = F(0) * isoValue # r0s = self.charge_cloud_radii.getRadii(atomlist) s *= r0s cloud = mlab.quiver3d(x,y,z,s,s,s, scalars=charges, mode="sphere", scale_factor=1.0, resolution=20, opacity = 0.4, figure=self.scene.mayavi_scene) # atoms are coloured by their atomic number cloud.glyph.color_mode = "color_by_scalar" cloud.glyph.glyph_source.glyph_source.center = [0,0,0] self.lut = cloud.module_manager.scalar_lut_manager.lut.table.to_array() red = np.array((255.0, 0.0, 0.0)).astype('uint8') blue = np.array((0.0, 0.0, 255.0)).astype('uint8') for i in range(0, 255): if i < 128: color = blue else: color = red self.lut[i,0:3] = color cloud.module_manager.scalar_lut_manager.lut.table = self.lut cloud.module_manager.scalar_lut_manager.data_range = (-1.0, 1.0) self.cloud = cloud self.charge_clouds.append( cloud ) self.shown_charge_clouds = True else: self.shown_charge_clouds = False if self.show_flags["dipole moment"] == True and type(charges) != type(None): self.dipole_vectors = [] # The dipole vector is placed at the center of mass pos = XYZ.atomlist2vector(atomlist) masses = AtomicData.atomlist2masses(atomlist) com = MolCo.center_of_mass(masses, pos) # compute dipole moment from charge distribution dipole = np.zeros(3) for i,q in enumerate(charges): dipole += q * pos[3*i:3*(i+1)] print "Dipole moment D = %s a.u." % dipole # For plotting the dipole vector is converted to Debye dipole *= AtomicData.ebohr_to_debye print "Dipole moment D = %s Debye" % dipole print "Length of dipole moment |D| = %s Debye" % la.norm(dipole) quiver3d = mlab.quiver3d(com[0],com[1],com[2], dipole[0], dipole[1], dipole[2], line_width=5.0, scale_mode='vector', color=(0,0,1), scale_factor=1.0) self.dipole_vectors.append(quiver3d) else: self.shown_dipole_moment = False if self.show_flags["enclosing box"] == True: self.frag_enclosing_boxes = [] for ifrag,(fragment_indeces, fragment_atomlist, fragment_box) in enumerate(fragments): box = fragment_box # plot edges of the enclosing box for edge in box.edges: l = mlab.plot3d(box.vertices[edge,0], box.vertices[edge,1], box.vertices[edge,2], color=(1,0,0), figure=self.scene.mayavi_scene) self.frag_enclosing_boxes.append(l) # plot axes for axis, color in zip(box.axes, [(1.,0.,0.),(0.,1.,0.), (0.,0.,1.)]): ax = mlab.quiver3d(float(box.center[0]), float(box.center[1]), float(box.center[2]), float(axis[0]), float(axis[1]), float(axis[2]), color=color, scale_factor=3.0, mode='arrow', resolution=20, figure=self.scene.mayavi_scene) self.frag_enclosing_boxes.append(ax) self.shown_enclosing_boxes = True else: self.shown_enclosing_boxes = False if self.show_flags["screening charges (COSMO)"] == True: # read parameter for solvent model from command line # or configuration file parser = OptionParserFuncWrapper([ImplicitSolvent.SolventCavity.__init__], "", unknown_options="ignore") # extract only arguments for constructor of solvent cavity (solvent_options, args) = parser.parse_args(ImplicitSolvent.SolventCavity.__init__) # cavity = ImplicitSolvent.SolventCavity(**solvent_options) cavity.constructSAS(atomlist) area = cavity.getSurfaceArea() print "solvent accessible surface area: %8.6f bohr^2 %8.6f Ang^2" % (area, area*AtomicData.bohr_to_angs**2) points = cavity.getSurfacePoints() x,y,z = points[:,0], points[:,1], points[:,2] if type(charges) != type(None): # If there are Mulliken charges we can compute the # induced charges on the surface of the cavity # according to COSMO cavity.constructCOSMO() induced_charges = cavity.getInducedCharges(charges) screening_energy = cavity.getScreeningEnergy(charges) print "screening energy: %10.6f Hartree %10.6f kcal/mol" \ % (screening_energy, screening_energy * AtomicData.hartree_to_kcalmol) # The surface points are colored and scaled # according to their charge # negative -> blue positive -> red points3d = mlab.points3d(x,y,z,induced_charges, colormap="blue-red", mode='2dcross', scale_factor=10) # mode='2dvertex') points3d.glyph.color_mode = "color_by_scalar" else: # points3d = mlab.points3d(x,y,z,color=(0.0,0.0,0.8), mode='2dvertex') self.sas_points.append( points3d ) else: self.shown_solvent_screening_charges = False if self.show_flags["non-adiab. coupling vectors"] == True: vec = XYZ.atomlist2vector(atomlist) x, y, z = vec[::3], vec[1::3], vec[2::3] # assume that charges are transition charges transition_charges = charges # Because we don't know the energy here, the energy difference # in the denominator is set to 1, so only the direction and relative # lengths are correct. nac = NACsApprox.coupling_vector(atomlist, transition_charges, 1.0) # directions of NAC vectors u,v,w = nac[0,:], nac[1,:], nac[2,:] # Add a NAC vector at each atom quiver3d = mlab.quiver3d(x,y,z, u,v,w, line_width=5.0) self.nac_vectors.append(quiver3d) else: self.shown_nacs = False
def transform_gradient(self, x, g_cart, max_iter=200): """ transform cartesian gradient g_cart = dE/dx to redundant internal coordinates according to B^T g_intern = g_cart The underdetermined system of linear equations is solved in a least square sense. """ if self.verbose > 0: print("transform gradient to internal coordinates") print(" dE/dx -> dE/dq") # Since the energy of a molecule depends only on the internal # coordinates q it should be possible to transform the cartesian # gradient exactly into internal coordinates according to # dE/dx(i) = sum_j dE/dq(j) dq(j)/dx(i) # = sum_j (B^T)_{i,j} dE/dq(j) # # cartesian force f_cart = -g_cart # cartesian accelerations a_cart = f_cart / self.masses # cartesian velocity dt = 1.0 vel = a_cart * dt # shift position to center of mass x = MolCo.shift_to_com(x, self.masses) # eliminate rotation and translation from a_cart I = MolCo.inertial_tensor(self.masses, x) L = MolCo.angular_momentum(self.masses, x, vel) P = MolCo.linear_momentum(self.masses, vel) omega = MolCo.angular_velocity(L, I) vel = MolCo.eliminate_translation(self.masses, vel, P) vel = MolCo.eliminate_rotation(vel, omega, x) # Check that the total linear momentum and angular momentum # indeed vanish. assert la.norm(MolCo.linear_momentum(self.masses, vel)) < 1.0e-14 assert la.norm(MolCo.angular_momentum(self.masses, x, vel)) < 1.0e-14 # go back from velocities to gradient g_cart = -vel / dt * self.masses # solve B^T . g_intern = g_cart by linear least squares. ret = sla.lstsq(self.B0.transpose(), g_cart, cond=self.cond_threshold) g_intern = ret[0] if self.verbose > 1: print(self._table_gradients(g_cart, g_intern)) # check solution err = la.norm(np.dot(self.B0.transpose(), g_intern) - g_cart) assert err < 1.0e-10, "|B^T.g_intern - g_cart|= %e" % err # Abort if gradients are not reasonable gnorm = la.norm(g_intern) if gnorm > 1.0e5: raise RuntimeError( "ERROR: Internal gradient is too large |grad|= %e" % gnorm) # Components of gradient belonging to frozen internal # coordinates are zeroed to avoid changing them. g_intern[self.frozen_internals] = 0.0 # Simply setting some components of the gradient to zero # will most likely lead to inconsistencies if the coordinates # are coupled (Maybe it's impossible to change internal coordinate X # without changing coordinate Y at the same time). These # inconsistencies are removed by applying the projection # operator repeatedly until the components of the frozen # internal coordinates have converged to 0. if self.verbose > 0: print(" apply projector P=G.G- to internal gradient") # The projector is updated everytime cartesian2internal(...) # is called. proj = self.P0 for i in range(0, max_iter): g_intern = np.dot(proj, g_intern) # gradient along frozen coordinates should be zero gnorm_frozen = la.norm(g_intern[self.frozen_internals]) if self.verbose > 0: print(" Iteration= %4.1d |grad(frozen)|= %s" % (i, gnorm_frozen)) if gnorm_frozen < 1.0e-10: break else: g_intern[self.frozen_internals] = 0.0 else: if gnorm_frozen < 1.0e-5: print( "WARNING: Projection of gradient vector in internal coordinates did not converge!" ) print( " But |grad(frozen)|= %e is not too large, so let's continue anyway." % gnorm_frozen) else: raise RuntimeError( "ERROR: Projection of gradient vector in internal coordinates did not converge! |grad(frozen)|= %e" % gnorm_frozen) if self.verbose > 1: print( "gradients after applying projector (only internal gradient changes)" ) print(self._table_gradients(g_cart, g_intern)) return g_intern
def __init__(self, atomlist, freeze=[], explicit_bonds=[], verbose=0): """ setup system of internal coordinates using valence bonds, angles and dihedrals Parameters ---------- atomlist : list of tuples (Z,[x,y,z]) with molecular geometry, connectivity defines the valence coordinates Optional -------- freeze : list of tuples of atom indices (starting at 0) corresponding to internal coordinates that should be frozen explicit_bonds : list of pairs of atom indices (starting at 0) between which artificial bonds should be inserted, i.e. [(0,1), (10,20)]. This allows to connect separate fragments. verbose : write out additional information if > 0 """ self.verbose = verbose self.atomlist = atomlist self.masses = AtomicData.atomlist2masses(self.atomlist) # Bonds, angles and torsions are constructed by the force field. # Atom types, partial charges and lattice vectors # all don't matter, so we assign atom type 6 (C_R, carbon in resonance) # to all atoms. atomtypes = [6 for atom in atomlist] partial_charges = [0.0 for atom in atomlist] lattice_vectors = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]] # But since the covalent radii are wrong, we have to provide # the connectivity matrix conmat = XYZ.connectivity_matrix(atomlist, hydrogen_bonds=True) # insert artificial bonds for (I, J) in explicit_bonds: print("explicit bond between atoms %d-%d" % (I + 1, J + 1)) conmat[I, J] = 1 # Internal coordinates only work if the molecule does not # contain disconnected fragments, since there is no way how the # interfragment distance could be expressed in terms of internal coordinates. # We need to check that there is only a single fragment. fragment_graphs = MolecularGraph.atomlist2graph(self.atomlist, conmat=conmat) nr_fragments = len(fragment_graphs) error_msg = "The molecule consists of %d disconnected fragments.\n" % nr_fragments error_msg += "Internal coordinates only work if all atoms in the molecular graph are connected.\n" error_msg += "Disconnected fragments may be joined via an artificial bond using the\n" error_msg += "`explicit_bonds` option.\n" assert nr_fragments == 1, error_msg # Frozen degrees of freedom do not necessarily correspond to physical bonds # or angles. For instance we can freeze the H-H distance in water although there # is no bond between the hydrogens. To allow the definition of such 'unphysical' # internal coordinates, we have to modify the connectivity matrix and introduce # artificial bonds. for IJKL in freeze: if len(IJKL) == 2: I, J = IJKL # create artificial bond between atoms I and J conmat[I, J] = 1 elif len(IJKL) == 3: I, J, K = IJKL # create artifical bonds I-J and J-K so that the valence angle I-J-K exists conmat[I, J] = 1 conmat[J, K] = 1 elif len(IJKL) == 4: I, J, K, L = IJKL # create artifical bonds I-J, J-K and K-L so that the dihedral angle I-J-K-L # exists conmat[I, J] = 1 conmat[J, K] = 1 conmat[K, L] = 1 # cutoff for small singular values when solving the # linear system of equations B.dx = dq in a least square # sense. self.cond_threshold = 1.0e-10 self.force_field = PeriodicForceField(atomlist, atomtypes, partial_charges, lattice_vectors, [], connectivity_matrix=conmat, verbose=1) x0 = XYZ.atomlist2vector(atomlist) # shift molecule to center of mass self.x0 = MolCo.shift_to_com(x0, self.masses) self._selectActiveInternals(freeze=freeze)
def _update_visualization(self, atomlist, charges, fragments): """ only change the underlying data but do not recreate visualization. This is faster and avoids jerky animations. """ mlab = self.scene.mlab if self.show_flags["charges"] == True: i = 0 for q,(Z,pos) in zip(charges, atomlist): x,y,z = pos if q < 0.0: color = (0.,0.,1.) elif q > 0.0: color = (1.,0.,0.) else: color = (1.,1.,1.) # color does not work so far txt = "%+2.3f" % q if self.show_flags["labels"] == True: # maybe charges should not overlap with labels txt = "%s" % txt # update label position and text label = self.charge_labels[i] label.remove() label = mlab.text(x,y, txt, z=z, figure=self.scene.mayavi_scene) self.charge_labels[i] = label label.actor.set(text_scale_mode='none', width=0.05, height=0.1) label.property.set(justification='centered', vertical_justification='centered') i += 1 if self.show_flags["frag. charges"] == True: i = 0 for ifrag,(fragment_indeces, fragment_atomlist) in enumerate(fragments): # compute the charges on fragment qfrag = np.sum(charges[fragment_indeces]) print "Fragment charges: %s" % charges[fragment_indeces] # compute the center of the molecule, pos_frag = XYZ.atomlist2vector(fragment_atomlist) masses_frag = AtomicData.atomlist2masses(fragment_atomlist) com = MolCo.center_of_mass(masses_frag, pos_frag) # print "Fragment %d charge = %s" % (ifrag, qfrag) txt = "%+2.3f" % qfrag label = self.frag_charge_labels[i] label.remove() label = mlab.text(com[0],com[1], txt, z=com[2], line_width=0.8, figure=self.scene.mayavi_scene) self.frag_charge_labels[i] = label label.actor.set(text_scale_mode='none', width=0.05, height=0.1) label.property.set(justification='centered', vertical_justification='centered') i += 1 if self.show_flags["charge clouds"] == True: vec = XYZ.atomlist2vector(atomlist) x, y, z = vec[::3], vec[1::3], vec[2::3] s = abs(charges) # The charge clouds represent surfaces of equal charge density around each atoms. # In DFTB the charge fluctuations are modelled by a Gaussian: # F(r) = 1/(2*pi*sA^2)^(3/2) * exp(-r^2/(2*sA^2)) # The radii of the charge clouds are scaled by the charge on the atom: # r = q * r0 # The radius r0 belongs to a charge cloud containing exactly 1 electron, it depends # on the hubbard parameter through sA and on the isoValue: # F(r0) = F(0) * isoValue # r0s = self.charge_cloud_radii.getRadii(atomlist) s *= r0s self.cloud.mlab_source.set(x=x,y=y,z=z,u=s,v=s,w=s, scalars=charges, scale_factor=1.0) # atoms are coloured by their atomic number self.cloud.glyph.color_mode = "color_by_scalar" self.cloud.glyph.glyph_source.glyph_source.center = [0,0,0] self.cloud.module_manager.scalar_lut_manager.lut.table = self.lut self.cloud.module_manager.scalar_lut_manager.data_range = (-1.0, 1.0)
def has_Cn(atomlist): pass def has_reflection(atomlist): pass ######################################## 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 ######################################## if __name__ == "__main__": import sys atomlist = XYZ.read_xyz(sys.argv[1])[0] atomlist_std = MolCo.standard_orientation(atomlist) group = detect_symmetry_brute_force(atomlist_std) # group = C2v() group.check_symmetry(atomlist_std) # atomlist_std_trans = group.elems[1].transform(atomlist_std)
def __init__(self, axis): """ axis is the normal to the plane of reflection """ self.axis = axis self.S = MolCo.reflection_matrix(axis)
def __init__(self, axis, angle): self.axis = axis self.angle = angle self.R = MolCo.rotation_matrix(axis, angle) self.S = MolCo.reflection_matrix(axis)
def __init__(self, atomlist, E0, vib_freq, symmetry_group, pressure=AtomicData.atm_pressure, temperature=AtomicData.satp_temperature): """ temperature in Kelvin """ self.E0 = E0 self.P = pressure self.T = temperature self.vib_freq = vib_freq self.symmetry_group = symmetry_group self.atomlist = atomlist Nat = len(atomlist) self.masses = AtomicData.atomlist2masses(self.atomlist) # compute rotational constants from tensor of inertia x0 = XYZ.atomlist2vector(atomlist) # shift the origin to the center of mass x0_shift = MolCo.shift_to_com(x0, self.masses) # diagonalize the tensor of inertia to obtain the principal moments and # the normalized eigenvectors of I Inert = MolCo.inertial_tensor(self.masses, x0_shift) principle_moments, X = la.eigh(Inert) Iaa, Ibb, Icc = np.sort(abs(principle_moments)) print("principle moments of inertia (in a.u.): %s %s %s" % (Iaa, Ibb, Icc)) # In a linear triatomic molecule we have Icc = Ibb > Iaa = 0 self.is_linear = False if abs(Icc - Ibb) / abs(Icc) < 1.0e-6 and abs(Iaa) / abs(Icc) < 1.0e-6: self.is_linear = True print("Molecule is linear") # Determine the type of rotor if self.is_linear == True: self.rotor_type = "linear rotor" else: if abs(Icc - Ibb) / abs(Icc) < 1.0e-4 and abs(Icc - Iaa) / abs( Icc) < 1.0e-4: # three equal moments of inertia self.rotor_type = "spherical rotor" elif abs(Icc - Ibb) / abs(Icc) < 1.0e-4 or abs(Ibb - Iaa) / abs( Icc) < 1.0e-4: # two equal moments of inertia self.rotor_type = "symmetric rotor" else: self.rotor_type = "asymmetric rotor" # rotational constants self.rotational_constants = 1.0 / ( 2.0 * principle_moments + 1.0e-20 ) # avoid division by zero error for a linear molecule, the invalid values are not actually used # symmetry number if symmetry_group == None: print( "Symmetry group unknown, setting rotational symmetry number to 1" ) self.sigma_rot = 1 else: self.sigma_rot = symmetry_group.rotational_symmetry_number() # beta = 1/(kB*T) kB = AtomicData.kBoltzmann self.beta = 1.0 / (kB * self.T)
def build_water_cluster(atomlist_template, orbital_names, phases): """ build a cluster orbital as a linear combination of monomer orbitals with the correct orientation. The position and orientation of the water molecules is taken from the template. Parameters: =========== atomlist_template: molecular geometry for cluster with n water molecules orbital_names: list of orbital names ('1b1', '3a1', '1b2' or '2a1') for each of the n water molecules phases: list of +1 or -1's for each orbital Returns: ======== water_cluster: molecular geometry of the cluster orb_cluster: cluster orbital, that is a linear combination of the monomer orbitals. """ # water geometry in bohr water_std = [(1, (0.0, 0.8459947982381987, -1.4473477675908173)), (8, (0.0, -0.21392561795490195, 0.0 )), (1, (0.0, 0.8459947982381987, 1.4473477675908173))] valorbs, radial_val = load_pseudo_atoms(water_std) # Orbitals for H2O monomer in standard orientation: # molecular plane = yz-plane, oxygen lies on the negative y-axis, H-H bond is parallel to z-axis # H1-1s O-2s O-2py O-2pz O-2px H2-1s monomer_orbitals = { "1b1": np.array([ 0.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000]), "3a1": np.array([ 0.7816,-0.6842, 0.6989, 0.0000, 0.0000, 0.7816]), "1b2": np.array([-0.7264, 0.0000, 0.0000, 0.9429, 0.0000, 0.7264]), "2a1": np.array([ 0.1730, 0.8662, 0.0393, 0.0000, 0.0000, 0.1730]) } # First the individual water molecules in the template are identified and their positions # and orientations are extracted. fragments = MolecularGraph.disconnected_fragments(atomlist_template) assert len(fragments) == len(orbital_names) == len(phases), "For each water fragment in the cluster you need to specify one fragment orbital ('1b1', '3a1', '1b2' or '2a1') and its phase" orb_cluster = [] # list of MO coefficients water_cluster = [] # list of atoms for i,water in enumerate(fragments): # the Euler angles (a,b,g) specify the orientation of the i-th water molecule water_std_i, (a,b,g), cm = MolCo.molecular_frame_transformation(water) print "WATER STANDARD" for (Zi,posi) in water_std: print " %s %8.6f %8.6f %8.6f" % (Zi, posi[0], posi[1], posi[2]) print "WATER STANDARD %d" % i for (Zi,posi) in water_std_i: print " %s %8.6f %8.6f %8.6f" % (Zi, posi[0], posi[1], posi[2]) # The desired orbital is placed on the i-th water molecule and is # rotated to match the orientation of the molecule. orb_monomer = monomer_orbitals[orbital_names[i]] # rotate orbital orb_monomer_rot = OrbitalRotations.rotate_orbitals(water_std, valorbs, orb_monomer, (a,b,g)) # add orbital with desired phase to cluster MOs orb_cluster += list(phases[i] * orb_monomer_rot) # rotate geometry water_rot = MolCo.transform_molecule(water_std, (a,b,g), cm) XYZ.write_xyz("/tmp/water_std.xyz", [water_std, water_rot]) water_cluster += water_rot # Assuming that the overlap between orbitals on different water molecules is negligible, # we can normalize the cluster orbital by dividing through sqrt(number of water molecules) n = np.sum(abs(np.array(phases))) # a phase of 0 indicates no orbital orb_cluster /= np.sqrt(n) return water_cluster, orb_cluster
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 __init__(self, xyz_file, dyson_file=None): super(Main, self).__init__() self.settings = Settings({ "Continuum Orbital": { "Ionization transitions": [0, ["only intra-atomic", "inter-atomic"]] }, "Averaging": { "Euler angle grid points": 5, "polar angle grid points": 1000, "sphere radius Rmax": 300.0, }, "Scan": { "nr. points": 20 }, "Cube": { "extra space / bohr": 15.0, "points per bohr": 3.0 } }) # perform DFTB calculation # BOUND ORBITAL = H**O self.atomlist = XYZ.read_xyz(xyz_file)[0] # shift molecule to center of mass print "shift molecule to center of mass" pos = XYZ.atomlist2vector(self.atomlist) masses = AtomicData.atomlist2masses(self.atomlist) pos_com = MolCo.shift_to_com(pos, masses) self.atomlist = XYZ.vector2atomlist(pos_com, self.atomlist) self.tddftb = LR_TDDFTB(self.atomlist) self.tddftb.setGeometry(self.atomlist, charge=0) options = {"nstates": 1} try: self.tddftb.getEnergies(**options) except DFTB.Solver.ExcitedStatesNotConverged: pass self.valorbs, radial_val = load_pseudo_atoms(self.atomlist) if dyson_file == None: # Kohn-Sham orbitals are taken as Dyson orbitals self.H**O, self.LUMO = self.tddftb.dftb2.getFrontierOrbitals() self.bound_orbs = self.tddftb.dftb2.getKSCoefficients() self.orbe = self.tddftb.dftb2.getKSEnergies() orbital_names = [] norb = len(self.orbe) for o in range(0, norb): if o < self.H**O: name = "occup." elif o == self.H**O: name = "H**O" elif o == self.LUMO: name = "LUMO " else: name = "virtual" name = name + " " + str(o).rjust(4) + ( " %+10.3f eV" % (self.orbe[o] * 27.211)) orbital_names.append(name) initially_selected = self.H**O else: # load coefficients of Dyson orbitals from file names, ionization_energies, self.bound_orbs = load_dyson_orbitals( dyson_file) self.orbe = np.array(ionization_energies) / 27.211 orbital_names = [] norb = len(self.orbe) for o in range(0, norb): name = names[o] + " " + str(o).rjust(4) + ( " %4.2f eV" % (self.orbe[o] * 27.211)) orbital_names.append(name) initially_selected = 0 self.photo_kinetic_energy = slako_tables_scattering.energies[0] self.epol = np.array([15.0, 0.0, 0.0]) # Build Graphical User Interface main = QtGui.QWidget() mainLayout = QtGui.QHBoxLayout(main) # selectionFrame = QtGui.QFrame() selectionFrame.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Preferred) mainLayout.addWidget(selectionFrame) selectionLayout = QtGui.QVBoxLayout(selectionFrame) # label = QtGui.QLabel(selectionFrame) label.setText("Select bound MO:") selectionLayout.addWidget(label) # bound orbitals self.orbitalSelection = QtGui.QListWidget(selectionFrame) self.orbitalSelection.itemSelectionChanged.connect( self.selectBoundOrbital) norb = len(self.orbe) self.orbital_dict = {} for o in range(0, norb): name = orbital_names[o] self.orbital_dict[name] = o item = QtGui.QListWidgetItem(name, self.orbitalSelection) if o == initially_selected: selected_orbital_item = item selectionLayout.addWidget(self.orbitalSelection) ### VIEWS center = QtGui.QWidget() mainLayout.addWidget(center) centerLayout = QtGui.QGridLayout(center) # boundFrame = QtGui.QFrame() centerLayout.addWidget(boundFrame, 1, 1) boundLayout = QtGui.QVBoxLayout(boundFrame) # "Bound Orbital" label = QtGui.QLabel(boundFrame) label.setText("Bound Orbital") boundLayout.addWidget(label) # self.boundOrbitalViewer = QCubeViewerWidget(boundFrame) boundLayout.addWidget(self.boundOrbitalViewer) # continuum orbital continuumFrame = QtGui.QFrame() centerLayout.addWidget(continuumFrame, 1, 2) continuumLayout = QtGui.QVBoxLayout(continuumFrame) # "Dipole-Prepared Continuum Orbital" label = QtGui.QLabel(continuumFrame) label.setText("Dipole-Prepared Continuum Orbital") continuumLayout.addWidget(label) self.continuumOrbitalViewer = QCubeViewerWidget(continuumFrame) continuumLayout.addWidget(self.continuumOrbitalViewer) self.efield_objects = [] self.efield_actors = [] self.selected = None # picker self.picker = self.continuumOrbitalViewer.visualization.scene.mayavi_scene.on_mouse_pick( self.picker_callback) self.picker.tolerance = 0.01 # PHOTO KINETIC ENERGY sliderFrame = QtGui.QFrame(continuumFrame) continuumLayout.addWidget(sliderFrame) sliderLayout = QtGui.QHBoxLayout(sliderFrame) # label self.pke_label = QtGui.QLabel() self.pke_label.setText("PKE: %6.4f eV" % (self.photo_kinetic_energy * 27.211)) sliderLayout.addWidget(self.pke_label) # Slider for changing the PKE self.pke_slider = QtGui.QSlider(QtCore.Qt.Horizontal) self.pke_slider.setMinimum(0) self.pke_slider.setMaximum(len(slako_tables_scattering.energies) - 1) self.pke_slider.setValue(0) self.pke_slider.sliderReleased.connect(self.changePKE) self.pke_slider.valueChanged.connect(self.searchPKE) sliderLayout.addWidget(self.pke_slider) # # molecular frame photoangular distribution mfpadFrame = QtGui.QFrame() centerLayout.addWidget(mfpadFrame, 2, 1) mfpadLayout = QtGui.QVBoxLayout(mfpadFrame) mfpadLayout.addWidget(QtGui.QLabel("Molecular Frame PAD")) mfpadTabs = QtGui.QTabWidget() mfpadLayout.addWidget(mfpadTabs) # 2D map mfpadFrame2D = QtGui.QFrame() mfpadTabs.addTab(mfpadFrame2D, "2D") mfpadLayout2D = QtGui.QVBoxLayout(mfpadFrame2D) self.MFPADfig2D = Figure() self.MFPADCanvas2D = FigureCanvas(self.MFPADfig2D) mfpadLayout2D.addWidget(self.MFPADCanvas2D) self.MFPADCanvas2D.draw() NavigationToolbar(self.MFPADCanvas2D, mfpadFrame2D, coordinates=True) # 3D mfpadFrame3D = QtGui.QFrame() mfpadTabs.addTab(mfpadFrame3D, "3D") mfpadLayout3D = QtGui.QVBoxLayout(mfpadFrame3D) self.MFPADfig3D = Figure() self.MFPADCanvas3D = FigureCanvas(self.MFPADfig3D) mfpadLayout3D.addWidget(self.MFPADCanvas3D) self.MFPADCanvas3D.draw() NavigationToolbar(self.MFPADCanvas3D, mfpadFrame3D, coordinates=True) # orientation averaged photoangular distribution avgpadFrame = QtGui.QFrame() centerLayout.addWidget(avgpadFrame, 2, 2) avgpadLayout = QtGui.QVBoxLayout(avgpadFrame) self.activate_average = QtGui.QCheckBox("Orientation Averaged PAD") self.activate_average.setToolTip( "Check this box to start averaging of the molecular frame PADs over all orientations. This can take a while." ) self.activate_average.setCheckState(QtCore.Qt.Unchecked) self.activate_average.stateChanged.connect(self.activateAveragedPAD) avgpadLayout.addWidget(self.activate_average) avgpadTabs = QtGui.QTabWidget() avgpadLayout.addWidget(avgpadTabs) # 1D map avgpadFrame1D = QtGui.QFrame() avgpadTabs.addTab(avgpadFrame1D, "1D") avgpadLayout1D = QtGui.QVBoxLayout(avgpadFrame1D) self.AvgPADfig1D = Figure() self.AvgPADCanvas1D = FigureCanvas(self.AvgPADfig1D) avgpadLayout1D.addWidget(self.AvgPADCanvas1D) self.AvgPADCanvas1D.draw() NavigationToolbar(self.AvgPADCanvas1D, avgpadFrame1D, coordinates=True) # 2D map avgpadFrame2D = QtGui.QFrame() avgpadFrame2D.setToolTip( "The averaged PAD should have no phi-dependence anymore. A phi-dependence is a sign of incomplete averaging." ) avgpadTabs.addTab(avgpadFrame2D, "2D") avgpadLayout2D = QtGui.QVBoxLayout(avgpadFrame2D) self.AvgPADfig2D = Figure() self.AvgPADCanvas2D = FigureCanvas(self.AvgPADfig2D) avgpadLayout2D.addWidget(self.AvgPADCanvas2D) self.AvgPADCanvas2D.draw() NavigationToolbar(self.AvgPADCanvas2D, avgpadFrame2D, coordinates=True) # Table avgpadFrameTable = QtGui.QFrame() avgpadTabs.addTab(avgpadFrameTable, "Table") avgpadLayoutTable = QtGui.QVBoxLayout(avgpadFrameTable) self.avgpadTable = QtGui.QTableWidget(0, 6) self.avgpadTable.setToolTip( "Activate averaging and move the PKE slider above to add a new row with beta values. After collecting betas for different energies you can save the table or plot a curve beta(PKE) for the selected orbital." ) self.avgpadTable.setHorizontalHeaderLabels( ["PKE / eV", "sigma", "beta1", "beta2", "beta3", "beta4"]) avgpadLayoutTable.addWidget(self.avgpadTable) # Buttons buttonFrame = QtGui.QFrame() avgpadLayoutTable.addWidget(buttonFrame) buttonLayout = QtGui.QHBoxLayout(buttonFrame) deleteButton = QtGui.QPushButton("Delete") deleteButton.setToolTip("clear table") deleteButton.clicked.connect(self.deletePADTable) buttonLayout.addWidget(deleteButton) buttonLayout.addSpacing(3) scanButton = QtGui.QPushButton("Scan") scanButton.setToolTip( "fill table by scanning automatically through all PKE values") scanButton.clicked.connect(self.scanPADTable) buttonLayout.addWidget(scanButton) saveButton = QtGui.QPushButton("Save") saveButton.setToolTip("save table as a text file") saveButton.clicked.connect(self.savePADTable) buttonLayout.addWidget(saveButton) plotButton = QtGui.QPushButton("Plot") plotButton.setToolTip("plot beta2 column as a function of PKE") plotButton.clicked.connect(self.plotPADTable) buttonLayout.addWidget(plotButton) """ # DOCKS self.setDockOptions(QtGui.QMainWindow.AnimatedDocks | QtGui.QMainWindow.AllowNestedDocks) # selectionDock = QtGui.QDockWidget(self) selectionDock.setWidget(selectionFrame) selectionDock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable) self.addDockWidget(QtCore.Qt.DockWidgetArea(1), selectionDock) # boundDock = QtGui.QDockWidget(self) boundDock.setWidget(boundFrame) boundDock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable) boundDock.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) self.addDockWidget(QtCore.Qt.DockWidgetArea(2), boundDock) # continuumDock = QtGui.QDockWidget(self) continuumDock.setWidget(continuumFrame) continuumDock.setFeatures(QtGui.QDockWidget.DockWidgetFloatable | QtGui.QDockWidget.DockWidgetMovable) continuumDock.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) self.addDockWidget(QtCore.Qt.DockWidgetArea(2), continuumDock) """ self.setCentralWidget(main) self.status_bar = QtGui.QStatusBar(main) self.setStatusBar(self.status_bar) self.default_message = "Click on the tip of the green arrow in the top right figure to change the orientation of the E-field" self.statusBar().showMessage(self.default_message) # Menu bar menubar = self.menuBar() exitAction = QtGui.QAction('&Exit', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('Exit program') exitAction.triggered.connect(exit) fileMenu = menubar.addMenu('&File') fileMenu.addAction(exitAction) settingsMenu = menubar.addMenu('&Edit') settingsAction = QtGui.QAction('&Settings...', self) settingsAction.setStatusTip('Edit settings') settingsAction.triggered.connect(self.editSettings) settingsMenu.addAction(settingsAction) self.loadContinuum() # select H**O selected_orbital_item.setSelected(True)