Beispiel #1
0
def partition_kinetic_energy(atomlists, dt):
    Nt = len(atomlists)
    # determine the indeces of the molecular fragments at the first
    # time-step.
    fragments_graph = MolecularGraph.atomlist2graph(atomlists[0])
    fragments_indeces = [
        MolecularGraph.graph2indeces(g) for g in fragments_graph
    ]
    fragment_atomlists = [[[atomlist[ii] for ii in I]
                           for atomlist in atomlists]
                          for I in fragments_indeces]
    # compute vibrational and center of mass kinetic energy for each fragment
    Nfrag = len(fragment_atomlists)
    ekin_com = np.zeros((Nt, Nfrag))
    ekin_tot = np.zeros((Nt, Nfrag))
    for f in range(0, Nfrag):
        masses = AtomicData.atomlist2masses(fragment_atomlists[f][0])
        print((fragment_atomlists[f][0]))
        # total mass
        M = np.sum(masses) / 3.0
        positions, velocities = velocities_finite_diff(fragment_atomlists[f],
                                                       dt)
        for i in range(0, Nt):
            vm = velocities[i] * masses
            vel_com = np.array(
                [np.sum(vm[0::3]),
                 np.sum(vm[1::3]),
                 np.sum(vm[2::3])]) / M
            ekin_com_f = 1.0 / 2.0 * M * np.sum(vel_com**2)
            ekin_tot_f = 1.0 / 2.0 * np.sum(masses * velocities[i]**2)

            ekin_com[i, f] += ekin_com_f
            ekin_tot[i, f] += ekin_tot_f
    return ekin_com, ekin_tot
Beispiel #2
0
 def calcFragments(self):
     self.fragment_lists = []
     for atomlist in self.molecules:
         fragment_graphs = MolecularGraph.atomlist2graph(atomlist)
         fragments = []
         for g in fragment_graphs:
             fragment_indeces = MolecularGraph.graph2indeces(g)
             fragment_atomlist = MolecularGraph.graph2atomlist(g, atomlist)
             fragment_box = MoleculeBox(fragment_atomlist)
             fragments.append( (fragment_indeces, fragment_atomlist, fragment_box) )
         self.fragment_lists.append(fragments)
     self.have_fragments = True
Beispiel #3
0
def filter_fragments(atomlist_full, selected_fragments=[], filter_mode="keep"):
    """

    Parameters:
    ===========
    atomlist_full: geometry that should be filtered
    selected_fragments: list of fragment labels that should be kept or deleted
    filter_mode: 'keep' - keep selected fragments, 'delete' - delete selected fragments

    Returns:
    ========
    atomlist_filtered: geometry with atoms that passed the filter
    """
    #
    fragments = MG.disconnected_fragments(atomlist_full)
    fragment_labels = [
        MG.identifier(*MG.morgan_ordering(atomlist)) for atomlist in fragments
    ]
    atomlist_filtered = []
    #
    unique_labels = set(fragment_labels)
    print "The following fragments were found:"
    print "==================================="
    for flabel in unique_labels:
        print " %s" % flabel
    print ""
    #
    for frag, flabel in zip(fragments, fragment_labels):
        if flabel in selected_fragments:
            if filter_mode == "keep":
                atomlist_filtered += frag
        else:
            if filter_mode == "delete":
                atomlist_filtered += frag

    return atomlist_filtered
def localize_pipek_mezey(atomlist, orbs, orbe, focc, S, valorbs):
    """
    localize orbitals onto diconnected fragments using the Pipek-Mezey method.

    Parameters
    ----------
    atomlist   : list of tuples (Zat, [xi,yi,zi]) with molecular geometry
    orbs       : MO coefficients, orbs[:,i] are the coefficients of orbital i
    orbe       : MO energies, orbe[i] is the Kohn-Sham energy of the i-th orbital
    focc       : occupation numbers, if focc[i] > 0, the i-th orbital is occupied
    S          : overlap matrix in AO basis
    valorbs    : dictionary with list of quantum numbers of valence
                 orbitals for each atom

    Returns
    -------
    orbs_loc   : MO coefficients of localized orbitals, the sets of
                 occupied and virtual orbitals are localized independently
    orbe_loc   : expectation values of the energy for the localized orbitals,
                 orbe_loc[i] = <i|H|i>. The Kohn-Sham Hamiltonian is not
                 diagonal in the basis of localized orbitals, but the total
                 energy of the ground state Slater determinant is not changed
                 by the unitary transformation that localizes the occupied
                 orbitals.
    U          : numpy array of shape (nmo,nmo) with unitary transformation
                 that localizes both occupied and virtual orbitals
    frags      : Each localized orbital is assigned to a fragment, frags[i]
                 gives the index (0-based) of the fragment to which orbital
                 i belongs
    """
    # number of atoms
    nat = len(atomlist)
    # identify disconnected fragments
    fragment_graphs = MG.atomlist2graph(atomlist, hydrogen_bonds=False)
    # number of fragments
    nfrag = len(fragment_graphs)
    # list of indices into atomlist of atoms belonging to each fragment
    fragment_indices = [MG.graph2indeces(g) for g in fragment_graphs]
    # mapping from atom indices to fragment index
    atom2frag = np.zeros(nat, dtype=int)
    for ifrag, atom_indices in enumerate(fragment_indices):
        atom2frag[atom_indices] = ifrag

    # Show which atom indices belong to which fragment
    print("")
    print("  Fragments for Orbital Localization")
    print("  ==================================")
    print("  For each disconnected fragment the indices (1-based) ")
    print("  of the atoms belonging to that fragment are listed.")
    print("")
    for ifrag, atom_indices in enumerate(fragment_indices):
        print("  Fragment %2.0d :   " % (ifrag + 1), end=' ')
        for i, iatom in enumerate(atom_indices):
            print("%d " % (iatom + 1), end=' ')
            if (i + 1) % 10 == 0:
                print("")
                print("                   ", end=' ')
        print("")

    # occupied and virtual orbitals are localized separately
    # MO coefficients
    orbs_occ = orbs[:, focc > 0]
    orbs_virt = orbs[:, focc == 0.0]
    # MO eigenenergies
    orbe_occ = orbe[focc > 0]
    orbe_virt = orbe[focc == 0.0]

    # localize occupied orbitals
    Uocc, orbs_occ_loc, Q_occ_loc = _localize_orbital_set(
        atomlist, orbs_occ, S, valorbs, atom2frag, nfrag)
    nao, nocc = orbs_occ_loc.shape
    # localize virtual orbitals
    Uvirt, orbs_virt_loc, Q_virt_loc = _localize_orbital_set(
        atomlist, orbs_virt, S, valorbs, atom2frag, nfrag)
    nao, nvirt = orbs_virt_loc.shape

    orbs_loc = np.zeros(orbs.shape)
    orbs_loc[:, focc > 0] = orbs_occ_loc
    orbs_loc[:, focc == 0.0] = orbs_virt_loc

    # expectation values of energy for localized orbitals
    #  ~   ~                 *                *
    # <i|H|i> = sum <k|H|j> U   U   = sum e  U   U
    #           k,j          ki  ji    j   j  ji  ji
    orbe_occ_loc = np.zeros(orbe_occ.shape)
    orbe_virt_loc = np.zeros(orbe_virt.shape)
    # Each localized orbital can be assign to one fragment, the one for
    # which Q_ii^F is largest
    frags_occ = np.zeros(nocc, dtype=int)
    frags_virt = np.zeros(nvirt, dtype=int)
    for i in range(0, nocc):
        orbe_occ_loc[i] = np.sum(Uocc[:, i].conjugate() * orbe_occ *
                                 Uocc[:, i])
        frags_occ[i] = np.argmax(Q_occ_loc[i, i, :])
    for a in range(0, nvirt):
        orbe_virt_loc[a] = np.sum(Uvirt[:, a].conjugate() * orbe_virt *
                                  Uvirt[:, a])
        frags_virt[a] = np.argmax(Q_virt_loc[a, a, :])
    # combine occupied and virtual orbitals again
    orbe_loc = np.zeros(orbe.shape)
    orbe_loc[focc > 0] = orbe_occ_loc
    orbe_loc[focc == 0.0] = orbe_virt_loc
    frags = np.zeros(nocc + nvirt, dtype=int)
    frags[focc > 0] = frags_occ
    frags[focc == 0.0] = frags_virt

    # combine unitary transformations for occupied and virtual orbitals
    #      ( Uocc  0     )
    #  U = (             )
    #      (  0    Uvirt )
    nmo = nocc + nvirt
    Uloc = np.zeros((nmo, nmo))
    Uloc[np.ix_(focc > 0, focc > 0)] = Uocc
    Uloc[np.ix_(focc == 0.0, focc == 0.0)] = Uvirt

    print("")
    print(" Pipek-Mezey localization of orbitals")
    print(" ------------------------------------")
    print("     MO(F)        Fragment Charges Q_{i,i}^F          ")
    print("            i   ", end=' ')
    for ifrag in range(0, nfrag):
        print("      F=%2.1d    " % (ifrag + 1), end=' ')
    print("")
    for imo in range(0, nocc):
        ifrag = np.argmax(Q_occ_loc[imo, imo, :])
        print(" occ(%d)   %3.1d" % (ifrag + 1, imo + 1), end=' ')
        for ifrag in range(0, nfrag):
            print("        %+4.3f" % Q_occ_loc[imo, imo, ifrag], end=' ')
        print("")
    for imo in range(0, nvirt):
        ifrag = np.argmax(Q_virt_loc[imo, imo, :])
        print(" virt(%d)  %3.1d" % (ifrag + 1, nocc + imo + 1), end=' ')
        for ifrag in range(0, nfrag):
            print("        %+4.3f" % Q_virt_loc[imo, imo, ifrag], end=' ')
        print("")
    print("")

    return orbs_loc, orbe_loc, Uloc, frags
    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)
Beispiel #6
0
        type=str,
        default="isomer_classification.dat")

    (opts, args) = parser.parse_args()
    if len(args) != 2:
        print usage
        exit(-1)

    dynamics_file = args[0]
    isomer_file = args[1]

    isomers = XYZ.read_xyz(isomer_file)
    # determined canonical connectivities of isomers
    isomer_connectivities = []
    for isomer in isomers:
        isomer_can, A_can = MolecularGraph.morgan_ordering(isomer,
                                                           hydrogen_bonds=True)
        isomer_connectivities.append(A_can)
    # all isomers should have different connectivities
    n = len(isomers)
    for i in range(0, n):
        for j in range(i + 1, n):
            if np.sum(
                (isomer_connectivities[i] - isomer_connectivities[j])**2) == 0:
                print "WARNING: Isomer %d and %d in file '%s' have the same adjacency matrices!" % (
                    i + 1, j + 1, isomer_file)

    fh = open(opts.out_file, "w")

    print >> fh, "# isomer indeces refer to the geometries in '%s'" % isomer_file
    print >> fh, "# GEOMETRY        ISOMER(S)"
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
Beispiel #8
0
                ret = os.system(
                    "obminimize -ff UFF %s %s | sed -e \"/^WARNING:/ d\" >  %s"
                    % (dynamic_file, redirect, opt_file))
                assert ret == 0, "Optimization with obminimize failed!"
            else:
                print "found optimized %s" % opt_file
            dynamic_file = opt_file

        print "Identifying and classifying fragments in %s" % dynamic_file
        try:
            geometries = XYZ.read_xyz(dynamic_file)
        except IOError:
            print "WARNING: could not open %s. - skipped" % dynamic_file
            continue
        geometries = [geometries[-1]]
        fragtraj, fragdic = MolecularGraph.fragment_trajectory(geometries)
        fragment_geometries.update(fragdic)
        # length of the i-th trajectory
        nt = max(len(fragtraj), len(ntraj))
        # extend fraction list so that it has as many time steps as the longest trajectory
        if len(ntraj) < nt:
            nadd = nt - len(ntraj)
            ntraj = np.hstack((ntraj, np.zeros(nadd)))
        ntraj[:len(fragtraj)] += 1
        # fragments
        for k, fracs in fragment_fractions.iteritems():
            if len(fracs) < nt:
                nadd = nt - len(fracs)
                fragment_fractions[k] = np.hstack((fracs, np.zeros(nadd)))
        # channels
        for k, fracs in channel_fractions.iteritems():