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 __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
Exemplo n.º 4
0
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
Exemplo n.º 5
0
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
Exemplo n.º 6
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 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()
Exemplo n.º 8
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.º 9
0
    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])
Exemplo n.º 10
0
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
Exemplo n.º 11
0
            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())
Exemplo n.º 12
0
    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
Exemplo n.º 13
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.º 14
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.º 15
0
    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)
Exemplo n.º 16
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)
Exemplo n.º 17
0
 def __init__(self, axis):
     """
     axis is the normal to the plane of reflection
     """
     self.axis = axis
     self.S = MolCo.reflection_matrix(axis)
Exemplo n.º 18
0
 def __init__(self, axis, angle):
     self.axis = axis
     self.angle = angle
     self.R = MolCo.rotation_matrix(axis, angle)
     self.S = MolCo.reflection_matrix(axis)
Exemplo n.º 19
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.º 20
0
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()
Exemplo n.º 22
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)