def _connected_inchi_with_graph_stereo(ich, gra, nums): """ For a connected inchi/graph, check if the inchi is missing stereo; If so, add stereo based on the graph. Currently only checks for missing bond stereo, since this is all we have seen so far, but could be generalized. :param ich: the inchi string :param gra: the graph :param nums: graph indices to backbone atoms in canonical inchi order :type nums: tuple[int] """ # First, do a check to see if the InChI is missing bond stereo # relative to the graph. ich_ste_keys = automol.inchi.stereo_bonds(ich) our_ste_keys = bond_stereo_keys(gra) miss_ich_ste_keys = automol.inchi.unassigned_stereo_bonds(ich) if len(ich_ste_keys) > len(our_ste_keys): raise Exception("Our code is missing stereo bonds") if len(ich_ste_keys) < len(our_ste_keys) or miss_ich_ste_keys: # Convert to implicit graph and relabel based on InChI sort atm_key_dct = dict(map(reversed, enumerate(nums))) gra = relabel(gra, atm_key_dct) gra = explicit(gra) exp_h_keys = explicit_hydrogen_keys(gra) exp_h_key_dct = {k: -k for k in exp_h_keys} gra = relabel(gra, exp_h_key_dct) # Translate internal stereo parities into InChI stereo parities # and generate the appropriate b-layer string for the InChI ste_dct = bond_stereo_parities(gra) ste_keys = tuple( sorted(tuple(reversed(sorted(k))) for k in bond_stereo_keys(gra))) blyr_strs = [] for atm1_key, atm2_key in ste_keys: our_par = ste_dct[frozenset({atm1_key, atm2_key})] our_srt1, our_srt2 = bond_stereo_sorted_neighbor_atom_keys( gra, atm1_key, atm2_key) ich_srt1 = tuple(reversed(sorted(our_srt1))) ich_srt2 = tuple(reversed(sorted(our_srt2))) if not ((our_srt1 != ich_srt1) ^ (our_srt2 != ich_srt2)): ich_par = our_par else: ich_par = not our_par blyr_strs.append( f"{atm1_key+1}-{atm2_key+1}{'-' if ich_par else '+'}") # After forming the b-layer string, generate the new InChI blyr_str = ','.join(blyr_strs) ste_dct = {'b': blyr_str} # print(ste_dct) ich = automol.inchi.standard_form(ich, ste_dct=ste_dct) # print('out:', ich) return ich
def path_distance_bounds_(gra): """ upper distance bound between two ends of a path :param gra: molecular graph :param path: the shortest path between two atoms :type path: list or tuple """ hyb_dct = resonance_dominant_atom_hybridizations(gra) rng_keys_lst = rings_atom_keys(gra) atm_ngb_keys = atoms_neighbor_atom_keys(gra) ste_bnd_keys = bond_stereo_keys(gra) bnd_par_dct = bond_stereo_parities(gra) def _distance_bounds(path): # if the path is 0, the atoms are disconnected and could be arbitrarily # far apart rsz = shared_ring_size(path, rng_keys_lst) if len(path) == 1: ldist = udist = 0 elif len(path) == 2: ldist = udist = heuristic_bond_distance(gra, *path) elif len(path) == 3: if rsz == 0: ldist = udist = heuristic_bond_angle_distance(gra, *path, hyb_dct=hyb_dct) else: a123 = (rsz - 2.) * 180. / rsz la123 = a123 - 10. ua123 = a123 + 10. lrdist = heuristic_bond_angle_distance(gra, *path, a123=la123) urdist = heuristic_bond_angle_distance(gra, *path, a123=ua123) odist = heuristic_bond_angle_distance(gra, *path, hyb_dct=hyb_dct) ldist = min(lrdist, odist) udist = max(urdist, odist) elif len(path) == 4: if rsz == 0: key2, key3 = path[1:3] bnd_key23 = frozenset({key2, key3}) # handle bond stereo here if bnd_key23 in ste_bnd_keys: key2_ngbs = atom_stereo_sorted_neighbor_atom_keys( gra, key2, atm_ngb_keys[key2] - {key3}) key3_ngbs = atom_stereo_sorted_neighbor_atom_keys( gra, key3, atm_ngb_keys[key3] - {key2}) pos2 = key2_ngbs.index(path[0]) pos3 = key3_ngbs.index(path[-1]) cis = bnd_par_dct[bnd_key23] != (pos2 != pos3) dih = 0. if cis else 180. ldist = udist = heuristic_torsion_angle_distance( gra, *path, d1234=dih, degree=True, hyb_dct=hyb_dct) else: ldist = heuristic_torsion_angle_distance(gra, *path, d1234=0., degree=True, hyb_dct=hyb_dct) udist = heuristic_torsion_angle_distance(gra, *path, d1234=180., degree=True, hyb_dct=hyb_dct) else: ang = (rsz - 2.) * 180. / rsz rdist = heuristic_torsion_angle_distance(gra, *path, d1234=0., degree=True, a123=ang, a234=ang) cdist = heuristic_torsion_angle_distance(gra, *path, d1234=0., degree=True, hyb_dct=hyb_dct) tdist = heuristic_torsion_angle_distance(gra, *path, d1234=180., degree=True, hyb_dct=hyb_dct) ldist = min(rdist, cdist) udist = max(rdist, tdist) # otherwise, just do the sum of the distances between atoms along the # path else: # we can't handle disconnected points, because in that case the # path is [] and there is no way to recover the keys assert len(path) > 2 ldist = closest_approach(gra, path[0], path[-1]) udist = 999. return ldist, udist return _distance_bounds
def qualitative_convergence_checker_(gra, keys, rqq_bond_max=1.8, rqh_bond_max=1.3, rhh_bond_max=1.1, bond_nobond_diff=0.3): """ a convergence checker for error minimization, checking that the geometry is qualitatively correct (correct connectivity and stereo) """ symb_dct = atom_symbols(gra) pairs = set(map(frozenset, itertools.combinations(keys, 2))) bnd_keys = pairs & bond_keys(gra) nob_keys = pairs - bond_keys(gra) nob_symbs = tuple( tuple(map(symb_dct.__getitem__, nob_key)) for nob_key in nob_keys) bnd_symbs = tuple( tuple(map(symb_dct.__getitem__, bnd_key)) for bnd_key in bnd_keys) nob_idxs = tuple(tuple(map(keys.index, nob_key)) for nob_key in nob_keys) bnd_idxs = tuple(tuple(map(keys.index, bnd_key)) for bnd_key in bnd_keys) bnd_udists = tuple( (rqq_bond_max if 'H' not in symb else rhh_bond_max if set(symb) == {'H'} else rqh_bond_max) for symb in bnd_symbs) diff = bond_nobond_diff nob_ldists = tuple( (rqq_bond_max + diff if 'H' not in symb else rhh_bond_max + diff if set(symb) == {'H'} else rqh_bond_max + diff) for symb in nob_symbs) bnd_idxs += tuple(map(tuple, map(reversed, bnd_idxs))) bnd_idx_vecs = tuple(map(list, zip(*bnd_idxs))) bnd_udists *= 2 nob_idxs += tuple(map(tuple, map(reversed, nob_idxs))) nob_idx_vecs = tuple(map(list, zip(*nob_idxs))) nob_ldists *= 2 symbs = tuple(map(symb_dct.__getitem__, keys)) geo_idx_dct = dict(map(reversed, enumerate(keys))) atm_ste_keys = atom_stereo_keys(gra) & set(keys) bnd_ste_keys = bond_stereo_keys(gra) & bnd_keys atm_ste_par_dct = atom_stereo_parities(gra) bnd_ste_par_dct = bond_stereo_parities(gra) def _is_converged(xmat, err, grad): assert err and numpy.any(grad) xyzs = xmat[:, :3] dmat = embed.distance_matrix_from_coordinates(xyzs) # check for correct connectivity connectivity_check = ((numpy.all( dmat[bnd_idx_vecs] < bnd_udists) if bnd_udists else True) and (numpy.all(dmat[nob_idx_vecs] > nob_ldists) if nob_ldists else True)) # check for correct stereo parities geo = automol.geom.base.from_data(symbs, xyzs, angstrom=True) atom_stereo_check = all((atom_stereo_parity_from_geometry( gra, atm_key, geo, geo_idx_dct) == atm_ste_par_dct[atm_key]) for atm_key in atm_ste_keys) bond_stereo_check = all((bond_stereo_parity_from_geometry( gra, bnd_key, geo, geo_idx_dct) == bnd_ste_par_dct[bnd_key]) for bnd_key in bnd_ste_keys) return connectivity_check and atom_stereo_check and bond_stereo_check return _is_converged