Exemplo n.º 1
0
    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
Exemplo n.º 2
0
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
Exemplo n.º 3
0
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()
Exemplo n.º 5
0
    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
Exemplo n.º 6
0
    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)
Exemplo n.º 7
0
    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)
Exemplo n.º 8
0
    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)