def bond_equivalence_class_reps(gra, bnd_keys=None, stereo=True, dummy=True): """ Identify isomorphically unique bonds, which do not transform into each other by an automorphism Optionally, a subset of bonds can be passed in to consider class representatives from within that list. :param gra: A graph :param bnd_keys: An optional list of bond keys from which to determine equivalence class representatives. If None, the full set of bond keys will be used. :param stereo: Consider stereo? :type stereo: bool :param dummy: Consider dummy atoms? :type dummy: bool :returns: The list of equivalence class reprentatives/unique bonds. :rtype: frozenset[int] """ bnd_keys = bond_keys(gra) if bnd_keys is None else bnd_keys def _equiv(bnd1_key, bnd2_key): return are_equivalent_bonds(gra, bnd1_key, bnd2_key, stereo=stereo, dummy=dummy) eq_classes = util.equivalence_partition(bnd_keys, _equiv) class_reps = frozenset(next(iter(c)) for c in eq_classes) return class_reps
def _hydrogen_layer(gra): """ AMChI hydrogen (h) layer from graph :param gra: implicit molecular graph :type gra: automol graph data structure :returns: the hydrogen layer, without prefix :rtype: str """ # Determine hydrogen counts nhyd_dct = atom_implicit_hydrogen_valences(gra) all_keys = sorted(atom_keys(gra), key=nhyd_dct.__getitem__) grps = [(nh, sorted(k + 1 for k in ks)) for nh, ks in itertools.groupby(all_keys, key=nhyd_dct.__getitem__) if nh > 0] # Build the hydrogen layer string slyrs = [] for nhyd, keys in grps: parts = util.equivalence_partition(keys, lambda x, y: y in (x - 1, x + 1)) parts = sorted(map(sorted, parts)) strs = [ '{:d}-{:d}'.format(min(p), max(p)) if len(p) > 1 else '{:d}'.format(p[0]) for p in parts ] if nhyd == 1: slyrs.append(','.join(strs) + 'H') else: slyrs.append(','.join(strs) + f'H{nhyd}') nhyd_lyr = ','.join(slyrs) return nhyd_lyr
def ring_systems_bond_keys(gra): """ bond keys for polycyclic ring systems in the graph """ def _are_connected(bnd_keys1, bnd_keys2): """ see if two rings are connected based on their bond keys """ atm_keys1 = functools.reduce(operator.or_, bnd_keys1) atm_keys2 = functools.reduce(operator.or_, bnd_keys2) common_bonds = set(bnd_keys1) & set(bnd_keys2) common_atoms = set(atm_keys1) & set(atm_keys2) return bool(common_bonds) or bool(common_atoms) rng_bnd_keys_lst = rings_bond_keys(gra) rsy_bnd_keys_lsts = util.equivalence_partition(rng_bnd_keys_lst, _are_connected) rsy_bnd_keys_lst = [frozenset(functools.reduce(operator.or_, bnd_keys_lst)) for bnd_keys_lst in rsy_bnd_keys_lsts] return rsy_bnd_keys_lst
def insert_dummies_on_linear_atoms(geo, lin_idxs=None, gra=None, dist=1., tol=5.): """ Insert dummy atoms over linear atoms in the geometry. :param geo: the geometry :type geo: automol molecular geometry data structure :param lin_idxs: the indices of the linear atoms; if None, indices are automatically determined from the geometry based on the graph :type lin_idxs: tuple(int) :param gra: the graph describing connectivity; if None, a connectivity graph will be generated using default distance thresholds :type gra: automol molecular graph data structure :param dist: distance of dummy atom from the linear atom, in angstroms :type dist: float :param tol: the tolerance threshold for linearity, in degrees :type tol: float :returns: geometry with dummy atoms inserted, along with a dictionary mapping the linear atoms onto their associated dummy atoms :rtype: automol molecular geometry data structure """ lin_idxs = linear_atoms(geo) if lin_idxs is None else lin_idxs gra = connectivity_graph(geo) if gra is None else gra dummy_ngb_idxs = set( automol.graph.dummy_atoms_neighbor_atom_key(gra).values()) assert not dummy_ngb_idxs & set(lin_idxs), ( "Attempting to add dummy atoms on atoms that already have them: {}". format(dummy_ngb_idxs & set(lin_idxs))) ngb_idxs_dct = automol.graph.atoms_sorted_neighbor_atom_keys(gra) xyzs = coordinates(geo, angstrom=True) def _perpendicular_direction(idxs): """ find a nice perpendicular direction for a series of linear atoms """ triplets = [] for idx in idxs: for n1idx in ngb_idxs_dct[idx]: for n2idx in ngb_idxs_dct[n1idx]: if n2idx != idx: ang = central_angle(geo, idx, n1idx, n2idx, degree=True) if numpy.abs(ang - 180.) > tol: triplets.append((idx, n1idx, n2idx)) if triplets: idx1, idx2, idx3 = min(triplets, key=lambda x: x[1:]) xyz1, xyz2, xyz3 = map(xyzs.__getitem__, (idx1, idx2, idx3)) r12 = util.vec.unit_direction(xyz1, xyz2) r23 = util.vec.unit_direction(xyz2, xyz3) direc = util.vec.orthogonalize(r12, r23, normalize=True) else: if len(idxs) > 1: idx1, idx2 = idxs[:2] else: idx1, = idxs idx2, = ngb_idxs_dct[idx1] xyz1, xyz2 = map(xyzs.__getitem__, (idx1, idx2)) r12 = util.vec.unit_direction(xyz1, xyz2) for i in range(3): disp = numpy.zeros((3, )) disp[i] = -1. alt = numpy.add(r12, disp) direc = util.vec.unit_perpendicular(r12, alt) if numpy.linalg.norm(direc) > 1e-2: break return direc # partition the linear atoms into adjacent groups, to be handled together lin_idxs_lst = sorted( map( sorted, util.equivalence_partition(lin_idxs, lambda x, y: x in ngb_idxs_dct[y]))) dummy_key_dct = {} for idxs in lin_idxs_lst: direc = _perpendicular_direction(idxs) for idx in idxs: xyz = numpy.add(xyzs[idx], numpy.multiply(dist, direc)) dummy_key_dct[idx] = count(geo) geo = insert(geo, 'X', xyz, angstrom=True) return geo, dummy_key_dct