Exemplo n.º 1
0
def connectivity_graph(geo,
                       rqq_bond_max=3.45,
                       rqh_bond_max=2.6,
                       rhh_bond_max=1.9):
    """ Generate a molecular graph from the molecular geometry that has information
        about bond connectivity.

        :param rqq_bond_max: maximum distance between heavy atoms
        :type rqq_bond_max: float
        :param rqh_bond_max: maximum distance between heavy atoms and hydrogens
        :type rqh_bond_max: float
        :param rhh_bond_max: maximum distance between hydrogens
        :type rhh_bond_max: float
        :rtype: automol molecular graph structure
    """

    symbs = symbols(geo)
    xyzs = coordinates(geo)

    def _distance(idx_pair):
        xyz1, xyz2 = map(xyzs.__getitem__, idx_pair)
        dist = numpy.linalg.norm(numpy.subtract(xyz1, xyz2))
        return dist

    def _are_bonded(idx_pair):
        sym1, sym2 = map(symbs.__getitem__, idx_pair)
        dist = _distance(idx_pair)
        return (False if 'X' in (sym1, sym2) else
                (dist < rqh_bond_max) if 'H' in (sym1, sym2) else
                (dist < rhh_bond_max) if (sym1 == 'H' and sym2 == 'H') else
                (dist < rqq_bond_max))

    idxs = range(len(xyzs))
    atm_symb_dct = dict(enumerate(symbs))
    bnd_keys = tuple(
        map(frozenset, filter(_are_bonded, itertools.combinations(idxs, r=2))))

    bnd_ord_dct = {bnd_key: 1 for bnd_key in bnd_keys}

    gra = automol.graph.from_data(atm_symb_dct=atm_symb_dct,
                                  bnd_keys=bnd_keys,
                                  bnd_ord_dct=bnd_ord_dct)
    return gra
Exemplo n.º 2
0
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 - 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(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 = symbols(geo1)
    syms2 = symbols(geo2)
    xyzs1 = coordinates(geo1, angstrom=angstrom)
    xyzs2 = 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
        dih, = dih
        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 = from_data(syms, xyzs, angstrom=angstrom)
    return geo
Exemplo n.º 3
0
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 - 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(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 = symbols(geo1)
    syms2 = symbols(geo2)
    xyzs1 = coordinates(geo1, angstrom=angstrom)
    xyzs2 = coordinates(geo2, angstrom=angstrom)

    xyz1 = xyzs1[key1] if key1 is not None else None
    xyz2 = xyzs1[key2]
    orig_xyz3 = xyzs2[key3]
    orig_xyz4 = xyzs2[key4] if key4 is not None else [1., 1., 1.]

    if key1 is None:
        # If the first fragment is monatomic, we can place the other one
        # anywhere we want (direction doesn't matter)
        xyz3 = numpy.add(xyz2, [0., 0., r23])
    else:
        # If the first fragment isn't monatomic, we need to take some care in
        # where we place the second one.
        # r23 and a123 are fixed, so the only degree of freedom we have to do
        # this is to optimize a dihedral angle relative to an arbitrary point
        # xyz0.
        # This dihedral angle is optimized to maximize the distance of xyz3
        # from all of the atoms in fragment 1 (xyzs1).
        xyz1 = xyzs1[key1]

        # 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
            dih, = dih
            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)

    r34 = vec.distance(orig_xyz3, orig_xyz4)

    # If 2 doen't have neighbors or 1-2-3 are linear, ignore the dihedral angle
    if key1 is None or numpy.abs(a123 * phycon.RAD2DEG - 180.) < 5.:
        xyz4 = vec.from_internals(dist=r34, xyz1=xyz3, ang=a234, xyz2=xyz2)
    else:
        xyz4 = vec.from_internals(dist=r34,
                                  xyz1=xyz3,
                                  ang=a234,
                                  xyz2=xyz2,
                                  dih=d1234,
                                  xyz3=xyz1)

    align_ = vec.aligner(orig_xyz3, orig_xyz4, xyz3, xyz4)
    xyzs2 = tuple(map(align_, xyzs2))

    syms = syms1 + syms2
    xyzs = xyzs1 + xyzs2

    geo = from_data(syms, xyzs, angstrom=angstrom)
    return geo
Exemplo n.º 4
0
def insert_dummies_on_linear_atoms(geo,
                                   lin_idxs=None,
                                   gra=None,
                                   dist=1.,
                                   tol=5.):
    """ Insert dummy atoms over linear atoms in the geometry.

        :param geo: the geometry
        :type geo: automol molecular geometry data structure
        :param lin_idxs: the indices of the linear atoms; if None, indices are
            automatically determined from the geometry based on the graph
        :type lin_idxs: tuple(int)
        :param gra: the graph describing connectivity; if None, a connectivity
            graph will be generated using default distance thresholds
        :type gra: automol molecular graph data structure
        :param dist: distance of dummy atom from the linear atom, in angstroms
        :type dist: float
        :param tol: the tolerance threshold for linearity, in degrees
        :type tol: float
        :returns: geometry with dummy atoms inserted, along with a dictionary
            mapping the linear atoms onto their associated dummy atoms
        :rtype: automol molecular geometry data structure
    """

    lin_idxs = linear_atoms(geo) if lin_idxs is None else lin_idxs
    gra = connectivity_graph(geo) if gra is None else gra

    dummy_ngb_idxs = set(
        automol.graph.dummy_atoms_neighbor_atom_key(gra).values())
    assert not dummy_ngb_idxs & set(lin_idxs), (
        "Attempting to add dummy atoms on atoms that already have them: {}".
        format(dummy_ngb_idxs & set(lin_idxs)))

    ngb_idxs_dct = automol.graph.atoms_sorted_neighbor_atom_keys(gra)

    xyzs = coordinates(geo, angstrom=True)

    def _perpendicular_direction(idxs):
        """ find a nice perpendicular direction for a series of linear atoms
        """
        triplets = []
        for idx in idxs:
            for n1idx in ngb_idxs_dct[idx]:
                for n2idx in ngb_idxs_dct[n1idx]:
                    if n2idx != idx:
                        ang = central_angle(geo,
                                            idx,
                                            n1idx,
                                            n2idx,
                                            degree=True)
                        if numpy.abs(ang - 180.) > tol:
                            triplets.append((idx, n1idx, n2idx))

        if triplets:
            idx1, idx2, idx3 = min(triplets, key=lambda x: x[1:])
            xyz1, xyz2, xyz3 = map(xyzs.__getitem__, (idx1, idx2, idx3))
            r12 = util.vec.unit_direction(xyz1, xyz2)
            r23 = util.vec.unit_direction(xyz2, xyz3)
            direc = util.vec.orthogonalize(r12, r23, normalize=True)
        else:
            if len(idxs) > 1:
                idx1, idx2 = idxs[:2]
            else:
                idx1, = idxs
                idx2, = ngb_idxs_dct[idx1]

            xyz1, xyz2 = map(xyzs.__getitem__, (idx1, idx2))
            r12 = util.vec.unit_direction(xyz1, xyz2)
            for i in range(3):
                disp = numpy.zeros((3, ))
                disp[i] = -1.
                alt = numpy.add(r12, disp)
                direc = util.vec.unit_perpendicular(r12, alt)
                if numpy.linalg.norm(direc) > 1e-2:
                    break

        return direc

    # partition the linear atoms into adjacent groups, to be handled together
    lin_idxs_lst = sorted(
        map(
            sorted,
            util.equivalence_partition(lin_idxs,
                                       lambda x, y: x in ngb_idxs_dct[y])))

    dummy_key_dct = {}

    for idxs in lin_idxs_lst:
        direc = _perpendicular_direction(idxs)
        for idx in idxs:
            xyz = numpy.add(xyzs[idx], numpy.multiply(dist, direc))
            dummy_key_dct[idx] = count(geo)

            geo = insert(geo, 'X', xyz, angstrom=True)

    return geo, dummy_key_dct
Exemplo n.º 5
0
def change_zmatrix_row_values(geo, idx, dist=None, idx1=None,
                              ang=None, idx2=None, dih=None, idx3=None,
                              angstrom=True, degree=True, gra=None):
    """ Change the z-matrix coordinates of a given atom, shifting those
        connected to it accordingly.

        :param geo: molecular geometry
        :type geo: automol geometry data structure
        :param idx: the atom to be shifted
        :type idx: int
        :param dist: the distance coordinate; if None, use the current distance
        :type dist: float
        :param idx1: the atom used to specify the distance coordinate
        :type idx1: int
        :param ang: the central angle coordinate
        :type ang: float
        :param idx2: the atom used to specify the central angle coordinate
        :type idx2: int
        :param dih: the dihedral angle coordinate
        :type ang: float
        :param idx3: the atom used to specify the dihedral angle coordinate
        :type idx3: int
        :param angstrom: are distances in angstrom? If not, assume bohr.
        :type angstrom: bool
        :param degree: are angles in degrees? If not, assume radians.
        :type degree: bool
        :param gra: molecular graph for tracking connectivity (will be
            generated if None)
        :type gra: automol molecular graph data structure
        :rtype: automol geometry data structure
    """
    gra = gra if gra is not None else connectivity_graph(geo)
    dist = dist if dist is not None or idx1 is None else (
        distance(geo, idx, idx1, angstrom=angstrom))
    ang = ang if ang is not None or idx2 is None else (
        central_angle(geo, idx, idx1, idx2, degree=degree))
    dih = dih if dih is not None or idx3 is None else (
        dihedral_angle(geo, idx, idx1, idx2, idx3, degree=degree))

    dist = dist if not angstrom else dist * phycon.ANG2BOHR
    ang = ang if not degree else ang * phycon.DEG2RAD
    dih = dih if not degree else dih * phycon.DEG2RAD
    tol = 2. * phycon.DEG2RAD
    lin = numpy.pi

    idxs = automol.graph.branch_atom_keys(gra, idx1, [idx1, idx])

    # Note that the coordinates will change throughout, but the change
    # shouldn't affect the operations so we can work in terms of the inital set
    xyzs = coordinates(geo)

    if idx1 is not None:
        # First, adjust the distance
        vec0 = numpy.subtract(xyzs[idx], xyzs[idx1])
        vec = dist * vec0 / numpy.linalg.norm(vec0)
        disp = vec - vec0
        geo = translate(geo, disp, idxs=idxs)

    if idx2 is not None:
        assert idx1 is not None, "idx1 is required if changing an angle"
        # Second, adjust the angle
        ang0 = central_angle(geo, idx, idx1, idx2)
        ang_diff = ang - ang0
        # If idx-idx1-idx2 is not linear, use the normal to this plane,
        # otherwise, use the normal to the idx1-idx2-idx3 plane
        if not numpy.abs(ang0) < tol or numpy.abs(ang0 - lin) < tol:
            axis = util.vec.unit_perpendicular(xyzs[idx], xyzs[idx2],
                                               orig_xyz=xyzs[idx1])
        else:
            axis = util.vec.unit_perpendicular(xyzs[idx1], xyzs[idx3],
                                               orig_xyz=xyzs[idx1])
        # I don't know how to figure out which way to rotate, so just try both
        # and see which one works
        for dang in [-ang_diff, ang_diff]:
            geo_ = rotate(geo, axis, dang, orig_xyz=xyzs[idx1], idxs=idxs)
            ang_out = central_angle(geo_, idx, idx1, idx2)
            abs_diff = numpy.abs(numpy.mod(ang, 2*numpy.pi) -
                                 numpy.mod(ang_out, 2*numpy.pi))
            ang_comp = numpy.abs(numpy.pi - numpy.abs(abs_diff - numpy.pi))
            if ang_comp < tol:
                geo = geo_
                break

    if idx3 is not None:
        assert idx1 is not None, "idx1 is required if changing a dihedral"
        assert idx2 is not None, "idx2 is required if changing a dihedral"
        # Third, adjust the dihedral
        dih0 = dihedral_angle(geo, idx, idx1, idx2, idx3)
        ddih = dih - dih0
        axis = numpy.subtract(xyzs[idx1], xyzs[idx2])
        geo = rotate(geo, axis, ddih, orig_xyz=xyzs[idx1], idxs=idxs)

    return geo