Exemplo n.º 1
0
def closest_unbonded_atoms(geo, gra=None):
    """ Determine which pair of unbonded atoms in a molecular geometry
        are closest together.

        :param geo: molecular 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
        :rtype: (frozenset(int), float)
    """

    gra = connectivity_graph(geo) if gra is None else gra
    atm_keys = automol.graph.atom_keys(gra)
    bnd_keys = automol.graph.bond_keys(gra)
    poss_bnd_keys = set(map(frozenset, itertools.combinations(atm_keys, r=2)))

    # The set of candidates includes all unbonded pairs of atoms
    cand_bnd_keys = poss_bnd_keys - bnd_keys

    min_bnd_key = None
    min_dist_val = 1000.
    for bnd_key in cand_bnd_keys:
        dist_val = distance(geo, *bnd_key)
        if dist_val < min_dist_val:
            min_dist_val = dist_val
            min_bnd_key = bnd_key

    return min_bnd_key, min_dist_val
Exemplo n.º 2
0
def angle_distances(geos, angstrom=True):
    """ return a dictionary of distances between ends of a bond angle for a
    sequence of reactant geometries, shifting the keys as needed

    :param geos: the reactant geometries
    :param angstrom: return the distances in angstroms?
    """
    dist_dct = {}

    shift = 0
    for geo in geos:
        gra = connectivity_graph(geo)
        pairs = [(k1, k3) for k1, _, k3 in automol.graph.angle_keys(gra)]
        keys = [frozenset({k1 + shift, k2 + shift}) for k1, k2 in pairs]
        dists = [distance(geo, *p, angstrom=angstrom) for p in pairs]
        dist_dct.update(dict(zip(keys, dists)))

        shift += count(geo)

    return dist_dct
Exemplo n.º 3
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
Exemplo n.º 4
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