def zmatrix_with_conversion_info(geo, ts_bnds=()): """ Generate a corresponding Z-Matrix for a molecular geometry using internal autochem procedures. :param geo: molecular geometry :type geo: automol geometry data structure :param ts_bnds: keys for the breaking/forming bonds in a TS :type ts_bnds: tuple(frozenset(int)) :returns: automol Z-Matrix data structure, Z-Matrix atom ordering, and a dictionary mapping linear atoms onto their associated dummy atoms """ if ts_bnds: raise NotImplementedError if is_atom(geo): symbs = symbols(geo) key_mat = [[None, None, None]] val_mat = [[None, None, None]] zma = automol.zmat.base.from_data(symbs, key_mat, val_mat) zma_keys = [0] dummy_key_dct = {} else: geo, dummy_key_dct = insert_dummies_on_linear_atoms(geo) gra = connectivity_graph(geo) bnd_keys = tuple(dummy_key_dct.items()) ord_dct = {k: 0 for k in bnd_keys} gra = automol.graph.add_bonds(gra, bnd_keys, ord_dct=ord_dct) vma, zma_keys = automol.graph.vmat.vmatrix(gra) geo = from_subset(geo, zma_keys) zma = automol.zmat.base.from_geometry(vma, geo) return zma, zma_keys, dummy_key_dct
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
def x2z_atom_ordering(geo, ts_bnds=()): """ Generate a dictionary which maps the order of atoms from the input molecular geometry to the order of atoms of the resulting Z-Matrix that is generated by the x2z interface. :param geo: molecular geometry :type geo: automol geometry data structure :param ts_bnds: keys for the breaking/forming bonds in a TS :type ts_bnds: tuple(frozenset(int)) :rtype: dict[int: int] """ symbs = symbols(geo) if len(symbs) == 1: idxs = {0: 0} else: x2m = _pyx2z.from_geometry(geo, ts_bnds=ts_bnds) idxs = _pyx2z.zmatrix_atom_ordering(x2m) return idxs
def x2z_torsion_coordinate_names(geo, ts_bnds=()): """ Generate a list of torsional coordinates using x2z interface. These names corresond to the Z-Matrix generated using the same algorithm. :param geo: molecular geometry :type geo: automol geometry data structure :param ts_bnds: keys for the breaking/forming bonds in a TS :type ts_bnds: tuple(frozenset(int)) :rtype: tuple(str) """ symbs = symbols(geo) if len(symbs) == 1: names = () else: x2m = _pyx2z.from_geometry(geo, ts_bnds=ts_bnds) names = _pyx2z.zmatrix_torsion_coordinate_names(x2m) zma = _pyx2z.to_zmatrix(x2m) name_dct = automol.zmat.base.standard_names(zma) names = tuple(map(name_dct.__getitem__, names)) return names
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
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 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