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
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
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)
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
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():