def _coulomb_matrix(geo): """ Calculate the Coulomb matrix wich describes the electrostatic interactions between nuclei: M[i,j] = 0.5Z_i^2.4 (i=j), Z_iZ_j/R_ij (i!=j :param geo: molecular geometry :type geo: automol molecular geometry data structure :rtype: tuple(tuple(float)) """ nums = numpy.array(list(map(ptab.to_number, geom_base.symbols(geo)))) xyzs = numpy.array(geom_base.coordinates(geo)) _ = numpy.newaxis natms = len(nums) diag_idxs = numpy.diag_indices(natms) tril_idxs = numpy.tril_indices(natms, -1) triu_idxs = numpy.triu_indices(natms, 1) zxz = numpy.outer(nums, nums) rmr = numpy.linalg.norm(xyzs[:, _, :] - xyzs[_, :, :], axis=2) mat = numpy.zeros((natms, natms)) mat[diag_idxs] = nums**2.4 / 2. mat[tril_idxs] = zxz[tril_idxs] / rmr[tril_idxs] mat[triu_idxs] = zxz[triu_idxs] / rmr[triu_idxs] return mat
def permutation(geo, ref_geo, thresh=1e-4): """ Determine the permutation of one geometry that reproduces another (if there isn't one -- the geometries are not aligned, return None). :param geo: molecular geometry :type geo: automol molecular geometry data structure :param ref_geo: molecular geometry :type ref_geo: automol molecular geometry data structure :param thresh: theshold for assessing if permutation exists :type thresh: float :rtype: tuple(int) """ natms = geom_base.count(geo) symbs = geom_base.symbols(geo) xyzs = geom_base.coordinates(geo) perm_idxs = [None] * natms for idx, (symb, xyz) in enumerate(zip(symbs, xyzs)): # Loop over atoms in the reference geometry with the same symbol ref_idxs = geom_base.atom_indices(ref_geo, symb=symb) ref_xyzs = geom_base.coordinates(ref_geo, idxs=ref_idxs) perm_idx = next( (ref_idx for ref_idx, ref_xyz in zip(ref_idxs, ref_xyzs) if util.vec.distance(xyz, ref_xyz) < thresh), None) perm_idxs[idx] = perm_idx perm_idxs = tuple(perm_idxs) if any(perm_idx is None for perm_idx in perm_idxs): perm_idxs = None return perm_idxs
def almost_equal(geo1, geo2, rtol=2e-3): """ Assess if the coordinates of two molecular geometries are numerically equal. :param geo1: molecular geometry 1 :type geo1: automol molecular geometry data structure :param geo2: molecular geometry 2 :type geo2: automol molecular geometry data structure :param rtol: Relative tolerance for the distances :type rtol: float :rtype: tuple(tuple(float)) """ ret = False if geom_base.symbols(geo1) == geom_base.symbols(geo2): ret = numpy.allclose(geom_base.coordinates(geo1), geom_base.coordinates(geo2), rtol=rtol) return ret
def displace(geo, xyzs): """ Displace the coordinates of a geometry along a vector. :param geo: molecular geometry :type geo: automol molecular geometry data structure :param xyzs: vector to displace along :type xyzs: tuple(float) :rtype: automol molecular geometry data structure """ symbs = geom_base.symbols(geo) orig_xyzs = geom_base.coordinates(geo) xyzs = numpy.add(orig_xyzs, xyzs) return automol.create.geom.from_data(symbs, xyzs)
def transform_by_matrix(geo, mat): """ Transform the coordinates of a molecular geometry by multiplying it by some input transfomration matrix. :param geo: molecular geometry :type geo: automol molecular geometry data structure :param mat: transformation matrix :type mat: tuple(tuple(float)) :rtype: automol moleculer geometry data structure """ symbs = geom_base.symbols(geo) xyzs = geom_base.coordinates(geo) xyzs = numpy.dot(xyzs, numpy.transpose(mat)) return automol.create.geom.from_data(symbs, xyzs)
def move_atom(geo, idx1, idx2): """ Move an atom to a different position in the geometry :param geo: molecular geometry :type geo: automol molecular geometry data structure :param idx1: index of the atom to be moved :type idx1: int :param idx2: new position that the atom should be moved to :type idx2: int :returns: the transformed geometry :rtype: molecular geometry """ symbs = list(geom_base.symbols(geo)) xyzs = list(geom_base.coordinates(geo)) symbs.insert(idx2, symbs.pop(idx1)) xyzs.insert(idx2, xyzs.pop(idx1)) return automol.create.geom.from_data(symbs, xyzs)
def transform(geo, func, idxs=None): """ Transform the coordinates of a geometry by a function. A set of `idxs` can be supplied to transform a subset of coordinates. :param geo: molecular geometry :type geo: automol geometry data structure :param func: transformation function :type func: function object :param idxs: indices representing the subset of atoms :type idxs: tuple(int) """ idxs = list(range(geom_base.count(geo))) if idxs is None else idxs symbs = geom_base.symbols(geo) xyzs = geom_base.coordinates(geo) xyzs = [func(xyz) if idx in idxs else xyz for idx, xyz in enumerate(xyzs)] return automol.create.geom.from_data(symbs, xyzs)
def reorder_coordinates(geo, idx_dct): """ Reorder the atoms of a molecular geometry using the mapping of an input dictionary. :param geo: The geometry :param idx_dct: The new order of the atoms, by index :type idx_dct: dict :rtype: automol geometry data structure """ symbs = geom_base.symbols(geo) xyzs = geom_base.coordinates(geo) idxs = [idx for idx, _ in sorted(idx_dct.items(), key=lambda x: x[1])] assert len(symbs) == len(xyzs) == len(idxs) symbs = [symbs[idx] for idx in idxs] xyzs = [xyzs[idx] for idx in idxs] return automol.create.geom.from_data(symbs, xyzs)
def masses(geo, amu=True): """ Build a list of the atomic masses that corresponds to the list of atomic sybmols of a molecular geometry. :param geo: molecular geometry :type geo: automol geometry data structure :param amu: parameter to control electron mass -> amu conversion :type amu: bool :rtype: tuple(float) """ symbs = geom_base.symbols(geo) amas = list(map(ptab.to_mass, symbs)) if not amu: amas = numpy.multiply(amas, phycon.AMU2EMASS) amas = tuple(amas) return amas
def is_linear(geo, tol=2.*phycon.DEG2RAD): """ Determine if the molecular geometry is linear. :param geo: molecular geometry :type geo: automol geometry data structure :param tol: tolerance of bond angle(s) for determing linearity :type tol: float :rtype: bool """ ret = True if len(geo) == 1: ret = False elif len(geo) == 2: ret = True else: keys = range(len(geom_base.symbols(geo))) for key1, key2, key3 in mit.windowed(keys, 3): cangle = numpy.abs(geom_base.central_angle(geo, key1, key2, key3)) if not (numpy.abs(cangle) < tol or numpy.abs(cangle - numpy.pi) < tol): ret = False return ret
def join(geo1, geo2, key2, key3, r23, a123=85., a234=85., d1234=85., key1=None, key4=None, angstrom=True, degree=True): """ join two geometries based on four of their atoms, two on the first and two on the second Variables set the coordinates for 1-2...3-4 where 1-2 are bonded atoms in geo1 and 3-4 are bonded atoms in geo2. """ key3 = key3 - geom_base.count(geo1) a123 *= phycon.DEG2RAD if degree else 1 a234 *= phycon.DEG2RAD if degree else 1 d1234 *= phycon.DEG2RAD if degree else 1 gra1, gra2 = map(automol.convert.geom.connectivity_graph, (geo1, geo2)) key1 = (automol.graph.atom_neighbor_atom_key(gra1, key2) if key1 is None else key1) key4 = (automol.graph.atom_neighbor_atom_key(gra2, key3) if key4 is None else key4) syms1 = geom_base.symbols(geo1) syms2 = geom_base.symbols(geo2) xyzs1 = geom_base.coordinates(geo1, angstrom=angstrom) xyzs2 = geom_base.coordinates(geo2, angstrom=angstrom) xyz1 = xyzs1[key1] xyz2 = xyzs1[key2] orig_xyz3 = xyzs2[key3] if key4 is not None: orig_xyz4 = xyzs2[key4] else: orig_xyz4 = [1., 1., 1.] r34 = vec.distance(orig_xyz3, orig_xyz4) # Place xyz3 as far away from the atoms in geo1 as possible by optimizing # the undetermined dihedral angle xyz0 = vec.arbitrary_unit_perpendicular(xyz2, orig_xyz=xyz1) def _distance_norm(dih): # objective function for minimization xyz3 = vec.from_internals(dist=r23, xyz1=xyz2, ang=a123, xyz2=xyz1, dih=dih, xyz3=xyz0) dist_norm = numpy.linalg.norm(numpy.subtract(xyzs1, xyz3)) # return the negative norm so that minimum value gives maximum distance return -dist_norm res = scipy.optimize.basinhopping(_distance_norm, 0.) dih = res.x[0] # Now, get the next position with the optimized dihedral angle xyz3 = vec.from_internals(dist=r23, xyz1=xyz2, ang=a123, xyz2=xyz1, dih=dih, xyz3=xyz0) # Don't use the dihedral angle if 1-2-3 are linear if numpy.abs(a123 * phycon.RAD2DEG - 180.) > 5.: xyz4 = vec.from_internals(dist=r34, xyz1=xyz3, ang=a234, xyz2=xyz2, dih=d1234, xyz3=xyz1) else: xyz4 = vec.from_internals(dist=r34, xyz1=xyz3, ang=a234, xyz2=xyz2) align_ = vec.aligner(orig_xyz3, orig_xyz4, xyz3, xyz4) xyzs2 = tuple(map(align_, xyzs2)) syms = syms1 + syms2 xyzs = xyzs1 + xyzs2 geo = automol.create.geom.from_data(syms, xyzs, angstrom=angstrom) return geo