def heuristic_bond_angle(gra, key1, key2, key3, degree=False, check=False, hyb_dct=None): """ heuristic bond angle If being reused multiple times, you can speed this up by passing in the hybridizations, so they don't need to be recalculated """ if check: assert {key1, key3} <= set(atoms_neighbor_atom_keys(gra)[key2]) if hyb_dct is None: hyb_dct = resonance_dominant_atom_hybridizations(gra) hyb2 = hyb_dct[key2] if hyb2 == 3: ang = TET_ANG elif hyb2 == 2: ang = TRI_ANG else: assert hyb2 == 1 ang = LIN_ANG ang *= 1 if degree else phycon.DEG2RAD return ang
def _atoms_missing_neighbors(gra, zma_keys): """ get atoms from the list currently in the v-matrix with neighbors that are not in the v-matrix """ ngb_keys_dct = atoms_neighbor_atom_keys(gra) keys = [] for key in zma_keys: if any(k not in zma_keys for k in ngb_keys_dct[key]): keys.append(key) keys = tuple(keys) return keys
def chirality_constraint_bounds(gra, keys): """ bounds for enforcing chirality restrictions """ ste_keys = set(atom_stereo_keys(gra)) & set(keys) par_dct = atom_stereo_parities(gra) ngb_key_dct = atoms_neighbor_atom_keys(gra) def _chirality_constraint(key): ngb_keys = ngb_key_dct[key] ngb_keys = atom_stereo_sorted_neighbor_atom_keys(gra, key, ngb_keys) idxs = tuple(map(keys.index, ngb_keys)) vol_range = (-999., -7.) if par_dct[key] else (+7., +999.) return idxs, vol_range chi_dct = dict(map(_chirality_constraint, ste_keys)) return chi_dct
def heuristic_bond_distance(gra, key1, key2, angstrom=True, check=False): """ heuristic bond distance (in angstroms) """ if check: assert key1 in atoms_neighbor_atom_keys(gra)[key2] symb_dct = atom_symbols(gra) symb1 = symb_dct[key1] symb2 = symb_dct[key2] if ptab.to_number(symb1) == 1 or ptab.to_number(symb2) == 1: dist = XH_DIST else: dist = XY_DIST dist *= 1 if angstrom else phycon.ANG2BOHR return dist
def planarity_constraint_bounds(gra, keys): """ bounds for enforcing planarity restrictions """ ngb_key_dct = atoms_neighbor_atom_keys(gra) ngb_dct = bond_neighborhoods(gra) bnd_keys = [ bnd_key for bnd_key in sp2_bond_keys(gra) if atom_keys(ngb_dct[bnd_key]) <= set(keys) ] def _planarity_constraints(bnd_key): key1, key2 = sorted(bnd_key) key1ab = sorted(ngb_key_dct[key1] - {key2}) key2ab = sorted(ngb_key_dct[key2] - {key1}) lst = [] # I don't think the order of the keys matters, but I tried to be # roughly consistent with Figure 8 in the Blaney Dixon paper if len(key1ab) == 2 and len(key2ab) == 2: lst.append(tuple(map(keys.index, key1ab + key2ab))) if len(key1ab) == 2: lst.append(tuple(map(keys.index, [key1, key2] + key1ab))) if len(key2ab) == 2: lst.append(tuple(map(keys.index, [key1, key2] + key2ab))) if (len(key1ab) == 2 and len(key2ab) == 1) or (len(key1ab) == 1 and len(key2ab) == 2): lst.append(tuple(map(keys.index, [key1] + key1ab + key2ab))) lst.append(tuple(map(keys.index, [key2] + key1ab + key2ab))) if len(key1ab) == 1 and len(key2ab) == 1: lst.append(tuple(map(keys.index, [key1, key2] + key1ab + key2ab))) return tuple(lst) const_dct = { idxs: (-0.5, +0.5) for idxs in itertools.chain(*map(_planarity_constraints, bnd_keys)) } return const_dct
def _extend_chain_to_include_terminal_hydrogens(gra, keys, start=True, end=True): """ extend each end of a chain to include terminal hydrogens, if any """ symb_dct = atom_symbols(gra) atm_ngb_dct = atoms_neighbor_atom_keys(gra) sta_ngbs = atm_ngb_dct[keys[0]] - {keys[1]} end_ngbs = atm_ngb_dct[keys[-1]] - {keys[-2]} sta_ngb = min((k for k in sta_ngbs if symb_dct[k] == 'H'), default=None) end_ngb = min((k for k in end_ngbs if symb_dct[k] == 'H'), default=None) keys = tuple(keys) if start and sta_ngb is not None: keys = (sta_ngb,) + keys if end and end_ngb is not None: keys = keys + (end_ngb,) return keys
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