Пример #1
0
def linear_atoms(geo, gra=None, tol=5.):
    """ find linear atoms in a geometry (atoms with 180 degree bond angle)

        :param geo: the geometry
        :type geo: automol geometry data structure
        :param gra: the graph describing connectivity; if None, a connectivity
            graph will be generated using default distance thresholds
        :type gra: automol graph data structure
        :param tol: the tolerance threshold for linearity, in degrees
        :type tol: float
        :rtype: tuple(int)
    """

    gra = connectivity_graph(geo) if gra is None else gra
    ngb_idxs_dct = automol.graph.atoms_neighbor_atom_keys(gra)

    lin_idxs = []
    for idx in range(count(geo)):
        nidxs = ngb_idxs_dct[idx]
        if len(nidxs) >= 2:
            for nidx1, nidx2 in itertools.combinations(nidxs, 2):
                ang = central_angle(geo, nidx1, idx, nidx2, degree=True)
                if numpy.abs(ang - 180.) < tol:
                    lin_idxs.append(idx)

    lin_idxs = tuple(lin_idxs)

    return lin_idxs
Пример #2
0
def hydrogen_bonded_idxs(geo, dist_thresh=5.3, angle_thresh=1.92, grxn=None):
    """ Compare bond lengths in structure to determine if there
        is a hydrogen bond.

        :param geo: geometry object
        :type geo: geo object (tuple of tuples)
        :param grxn: reaction object (geo indexing)
        :type grxn: automol.reac.Reaction object
        :param dist_thresh: cutoff value for hbond length (Bohr)
        :type dist_thresh: float
        :param angle_thresh: cutoff value for hbond angle (Radian)
        :type angle_thresh: float
        :rtype: tuple
    """
    # Initialize the hydrogen bond list to None
    hydrogen_bond = None
    if count(geo) > 1:
        # Get the forming/breaking bond idxs if possible
        if grxn is not None:
            frm_bnd_keys = automol.graph.ts.forming_bond_keys(
                grxn.forward_ts_graph)
            brk_bnd_keys = automol.graph.ts.breaking_bond_keys(
                grxn.forward_ts_graph)
            rxn_keys = set()
            for key in frm_bnd_keys:
                rxn_keys = rxn_keys | key
            for key in brk_bnd_keys:
                rxn_keys = rxn_keys | key
            rxn_h_idxs = tuple(rxn_keys)
        else:
            rxn_h_idxs = ()

        # Get all potential indices for HB interactions
        gra = graph(geo)
        dist_mat = distance_matrix(geo)
        adj_atm_dct = automol.graph.atoms_neighbor_atom_keys(gra)
        h_idxs = automol.graph.atom_keys(gra, sym='H')
        acceptor_idxs = list(
            automol.graph.resonance_dominant_radical_atom_keys(gra))
        acceptor_idxs.extend(list(automol.graph.atom_keys(gra, sym='O')))
        # Loop over indices, ignoring H-idxs in reacting bonds
        hb_idxs = tuple(idx for idx in h_idxs if idx not in rxn_h_idxs)
        for h_idx in hb_idxs:
            for acceptor_idx in acceptor_idxs:
                donor_idx = list(adj_atm_dct[h_idx])[0]
                if acceptor_idx in adj_atm_dct[donor_idx]:
                    continue
                if dist_mat[h_idx][acceptor_idx] < dist_thresh:
                    ang = central_angle(geo, donor_idx, h_idx, acceptor_idx)
                    if ang > angle_thresh:
                        hydrogen_bond = (
                            donor_idx,
                            h_idx,
                            acceptor_idx,
                        )
                        dist_thresh = dist_mat[h_idx][acceptor_idx]
    return hydrogen_bond
Пример #3
0
    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
Пример #4
0
def ring_angles_reasonable(geo, ring_atoms, thresh=ATHRESH):
    """ Assess whether any of the angles of the prospective rings of a geometry
        are bent at unphysical angles.

        :param geo: molecular geometry
        :type geo: automol.geom object
        :param rng_atoms: idxs for atoms inside rings
        :type rng_atoms: list
        :param thresh: angular threshold for large angles
        :type thresh: float
        :rtype: bool
    """

    condition = True
    for i, _ in enumerate(ring_atoms):
        _atoms = [ring_atoms[i], ring_atoms[i - 1], ring_atoms[i - 2]]
        cangle = central_angle(geo, *_atoms, degree=False)
        if cangle < thresh:
            condition = False
            break

    return condition
Пример #5
0
def _ts_compare(ref_zma, zma, zrxn):
    """ Perform a series of checks to assess the viability
        of a transition state geometry prior to saving
    """

    # Initialize viable
    viable = True

    # Get the bond dists and calculate the distance of bond being formed
    ref_geo = automol.zmat.geometry(ref_zma)
    cnf_geo = automol.zmat.geometry(zma)
    grxn = automol.reac.relabel_for_geometry(zrxn)

    frm_bnd_keys = automol.reac.forming_bond_keys(grxn)
    brk_bnd_keys = automol.reac.breaking_bond_keys(grxn)

    cnf_dist_lst = []
    ref_dist_lst = []
    bnd_key_lst = []
    cnf_ang_lst = []
    ref_ang_lst = []
    for frm_bnd_key in frm_bnd_keys:
        frm_idx1, frm_idx2 = list(frm_bnd_key)
        cnf_dist = distance(cnf_geo, frm_idx1, frm_idx2)
        ref_dist = distance(ref_geo, frm_idx1, frm_idx2)
        cnf_dist_lst.append(cnf_dist)
        ref_dist_lst.append(ref_dist)
        bnd_key_lst.append(frm_bnd_key)

    for brk_bnd_key in brk_bnd_keys:
        brk_idx1, brk_idx2 = list(brk_bnd_key)
        cnf_dist = distance(cnf_geo, brk_idx1, brk_idx2)
        ref_dist = distance(ref_geo, brk_idx1, brk_idx2)
        cnf_dist_lst.append(cnf_dist)
        ref_dist_lst.append(ref_dist)
        bnd_key_lst.append(brk_bnd_key)

    for frm_bnd_key in frm_bnd_keys:
        for brk_bnd_key in brk_bnd_keys:
            for frm_idx in frm_bnd_key:
                for brk_idx in brk_bnd_key:
                    if frm_idx == brk_idx:
                        idx2 = frm_idx
                        idx1 = list(frm_bnd_key - frozenset({idx2}))[0]
                        idx3 = list(brk_bnd_key - frozenset({idx2}))[0]
                        cnf_ang = central_angle(cnf_geo, idx1, idx2, idx3)
                        ref_ang = central_angle(ref_geo, idx1, idx2, idx3)
                        cnf_ang_lst.append(cnf_ang)
                        ref_ang_lst.append(ref_ang)

    # print('bnd_key_list', bnd_key_lst)
    # print('conf_dist', cnf_dist_lst)
    # print('ref_dist', ref_dist_lst)
    # print('conf_angle', cnf_ang_lst)
    # print('ref_angle', ref_ang_lst)

    # Set the maximum allowed displacement for a TS conformer
    max_disp = 0.6
    # better to check for bond-form length in bond scission with ring forming
    if 'addition' in grxn.class_:
        max_disp = 0.8
    if 'abstraction' in grxn.class_:
        # this was 1.4 - SJK reduced it to work for some OH abstractions
        max_disp = 1.0

    # Check forming bond angle similar to ini config
    if 'elimination' not in grxn.class_:
        for ref_angle, cnf_angle in zip(ref_ang_lst, cnf_ang_lst):
            if abs(cnf_angle - ref_angle) > .44:
                print('angle', ref_angle, cnf_angle)
                viable = False

    symbs = symbols(cnf_geo)
    lst_info = zip(ref_dist_lst, cnf_dist_lst, bnd_key_lst)
    for ref_dist, cnf_dist, bnd_key in lst_info:
        if 'add' in grxn.class_ or 'abst' in grxn.class_:
            bnd_key1, bnd_key2 = min(list(bnd_key)), max(list(bnd_key))
            symb1 = symbs[bnd_key1]
            symb2 = symbs[bnd_key2]

            if bnd_key in frm_bnd_keys:
                # Check if radical atom is closer to some atom
                # other than the bonding atom
                cls = automol.zmat.base.is_atom_closest_to_bond_atom(
                    zma, bnd_key2, cnf_dist)
                if not cls:
                    print('distance', ref_dist, cnf_dist)
                    print(' - Radical atom now has a new nearest neighbor')
                    viable = False
                # check forming bond distance
                if abs(cnf_dist - ref_dist) > max_disp:
                    print('distance', ref_dist, cnf_dist)
                    viable = False

            # Check distance relative to equi. bond
            equi_bnd = dict_.values_by_unordered_tuple(bnd.LEN_DCT,
                                                       (symb1, symb2),
                                                       fill_val=0.0)
            displace_from_equi = cnf_dist - equi_bnd
            dchk1 = abs(cnf_dist - ref_dist) > 0.1
            dchk2 = displace_from_equi < 0.2
            if dchk1 and dchk2:
                print(cnf_dist, equi_bnd)
                viable = False
        else:
            # check forming/breaking bond distance
            # if abs(cnf_dist - ref_dist) > 0.4:
            # max disp of 0.4 causes problems for bond scission w/ ring forming
            # not sure if setting it to 0.3 will cause problems for other cases
            if abs(cnf_dist - ref_dist) > 0.3:
                print('distance', ref_dist, cnf_dist)
                viable = False

    return viable
Пример #6
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