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 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 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 averaged_pad_scan(xyz_file, dyson_file, selected_orbitals, npts_euler, npts_theta, nskip, inter_atomic, sphere_radius): molecule_name = os.path.basename(xyz_file).replace(".xyz", "") atomlist = XYZ.read_xyz(xyz_file)[-1] # shift molecule to center of mass print "shift molecule to center of mass" pos = XYZ.atomlist2vector(atomlist) masses = AtomicData.atomlist2masses(atomlist) pos_com = MolCo.shift_to_com(pos, masses) atomlist = XYZ.vector2atomlist(pos_com, atomlist) # compute molecular orbitals with DFTB tddftb = LR_TDDFTB(atomlist) tddftb.setGeometry(atomlist, charge=0) options={"nstates": 1} try: tddftb.getEnergies(**options) except DFTB.Solver.ExcitedStatesNotConverged: pass valorbs, radial_val = load_pseudo_atoms(atomlist) if dyson_file == None: print "tight-binding Kohn-Sham orbitals are taken as Dyson orbitals" H**O, LUMO = tddftb.dftb2.getFrontierOrbitals() bound_orbs = tddftb.dftb2.getKSCoefficients() if selected_orbitals == None: # all orbitals selected_orbitals = range(0,bound_orbs.shape[1]) else: selected_orbitals = eval(selected_orbitals, {}, {"H**O": H**O+1, "LUMO": LUMO+1}) print "Indeces of selected orbitals (counting from 1): %s" % selected_orbitals orbital_names = ["orb_%s" % o for o in selected_orbitals] selected_orbitals = np.array(selected_orbitals, dtype=int)-1 # counting from 0 dyson_orbs = bound_orbs[:,selected_orbitals] ionization_energies = -tddftb.dftb2.getKSEnergies()[selected_orbitals] else: print "coeffients for Dyson orbitals are read from '%s'" % dyson_file orbital_names, ionization_energies, dyson_orbs = load_dyson_orbitals(dyson_file) ionization_energies = np.array(ionization_energies) / AtomicData.hartree_to_eV print "" print "*******************************************" print "* PHOTOELECTRON ANGULAR DISTRIBUTIONS *" print "*******************************************" print "" # determine the radius of the sphere where the angular distribution is calculated. It should be # much larger than the extent of the molecule (xmin,xmax),(ymin,ymax),(zmin,zmax) = Cube.get_bbox(atomlist, dbuff=0.0) dx,dy,dz = xmax-xmin,ymax-ymin,zmax-zmin Rmax = max([dx,dy,dz]) + sphere_radius Npts = max(int(Rmax),1) * 50 print "Radius of sphere around molecule, Rmax = %s bohr" % Rmax print "Points on radial grid, Npts = %d" % Npts nr_dyson_orbs = len(orbital_names) # compute PADs for all selected orbitals for iorb in range(0, nr_dyson_orbs): print "computing photoangular distribution for orbital %s" % orbital_names[iorb] data_file = "betas_" + molecule_name + "_" + orbital_names[iorb] + ".dat" pad_data = [] print " SCAN" nskip = max(1, nskip) # save table fh = open(data_file, "w") print " Writing table with betas to %s" % data_file print>>fh, "# ionization from orbital %s IE = %6.3f eV" % (orbital_names[iorb], ionization_energies[iorb]*AtomicData.hartree_to_eV) print>>fh, "# inter_atomic: %s npts_euler: %s npts_theta: %s rmax: %s" % (inter_atomic, npts_euler, npts_theta, Rmax) print>>fh, "# PKE/eV sigma beta1 beta2 beta3 beta4" for i,E in enumerate(slako_tables_scattering.energies): if i % nskip != 0: continue print " PKE = %6.6f Hartree (%4.4f eV)" % (E, E*AtomicData.hartree_to_eV) k = np.sqrt(2*E) wavelength = 2.0 * np.pi/k bs_free = AtomicScatteringBasisSet(atomlist, E, rmin=0.0, rmax=Rmax+2*wavelength, Npts=Npts) SKT_bf, SKT_ff = load_slako_scattering(atomlist, E) Dipole = ScatteringDipoleMatrix(atomlist, valorbs, SKT_bf, inter_atomic=inter_atomic).real orientation_averaging = PAD.OrientationAveraging_small_memory(Dipole, bs_free, Rmax, E, npts_euler=npts_euler, npts_theta=npts_theta) pad,betasE = orientation_averaging.averaged_pad(dyson_orbs[:,iorb]) pad_data.append( [E*AtomicData.hartree_to_eV] + list(betasE) ) # save PAD for this energy print>>fh, "%10.6f %10.6e %+10.6e %+10.6f %+10.6e %+10.6e" % tuple(pad_data[-1]) fh.flush() fh.close()
def transform_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 __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 __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)