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
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
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
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