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 minimum_distance(geo1, geo2): """ get the minimum distance between atoms in geo1 and those in geo2 :param geo1: molecular geometry 1 :type geo1: automol molecular geometry data structure :param geo2: molecular geometry 2 :type geo2: automol molecular geometry data structure :rtype: float """ xyzs1 = geom_base.coordinates(geo1) xyzs2 = geom_base.coordinates(geo2) return min( util.vec.distance(xyz1, xyz2) for xyz1, xyz2 in itertools.product(xyzs1, xyzs2))
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 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 center_of_mass(geo): """ Determine the center-of-mass for a molecular geometry. :param geo: molecular geometry :type geo: automol geometry data structure :rtype: tuple(float) """ xyzs = geom_base.coordinates(geo) amas = masses(geo) cm_xyz = tuple( sum(numpy.multiply(xyz, ama) for xyz, ama in zip(xyzs, amas)) / sum(amas)) return cm_xyz
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 perturb(geo, atm_idx, pert_xyz): """ Perturb the position of one atom by changing the value of an xyz coord by some amount. """ # Get the xyz coordinates of the atom to perturb atm_coords = list(geom_base.coordinates(geo)[atm_idx]) # Get the perturbed set of atomic coordinates for idx, val in enumerate(pert_xyz): atm_coords[idx] += val pert_dct = {atm_idx: atm_coords} # Perturb the coordinates of the atom pert_geo = geom_base.set_coordinates(geo, pert_dct) return pert_geo
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 inertia_tensor(geo, amu=True): """ Build the moment-of-inertia tensor for 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(tuple(float)) """ geo = mass_centered(geo) amas = masses(geo, amu=amu) xyzs = geom_base.coordinates(geo) ine = tuple(map(tuple, sum( ama * (numpy.vdot(xyz, xyz) * numpy.eye(3) - numpy.outer(xyz, xyz)) for ama, xyz in zip(amas, xyzs)))) return ine
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 reflect_coordinates(geo, idxs, axes): """ Reflect a specified set of coordinates of a molecular geometry about some each of the requested axes. A set of `idxs` can be supplied to transform a subset of coordinates. :param geo: molecular geometry :type geo: automol geometry data structure :param idxs: indices of atoms whose coordinates are to be reflected :type idxs: tuple(int) :param axes: axes to reflect about :type axes: tuple(str) :rtype: automol geometry data structure """ # check input assert all(idx < len(geo) for idx in idxs) assert all(axis in ('x', 'y', 'z') for axis in axes) # get coords coords = geom_base.coordinates(geo) # convert x,y,z to nums axes = [AXIS_DCT[axis] for axis in axes] # build set atom dct with relected coords reflect_dct = {} for idx in idxs: coord_lst = list(coords[idx]) for axis in axes: coord_lst[axis] *= -1.0 reflect_dct[idx] = coord_lst # Reflect coords with dct geo_reflected = geom_base.set_coordinates(geo, reflect_dct) return geo_reflected
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