def bonds_of_type(gra, symb1, symb2, mbond=1): """ Determine the indices of all a specific bond specified by atom type and bond order. :param gra: molecular graph :type gra: molecular graph data structure :param symb1: symbol of atom 1 in the bond :type symb1: str :param symb2: symbol of atom 2 in the bond :type symb2: str :param mbond: bond order of desired bond type :type mbond: int :rtype: tuple(int) """ # Get the dict that relates atom indices to symbols idx_symb_dct = atom_symbols(gra) # Loop over all the bonds and build a list of ones that match _bonds_of_type = tuple() _bonds = bonds_of_order(gra, mbond=mbond) for bond in _bonds: idx1, idx2 = bond symb1, symb2 = idx_symb_dct[idx1], idx_symb_dct[idx2] if (symb1, symb2) in ((symb1, symb2), (symb2, symb1)): _bonds_of_type += ((idx1, idx2), ) return _bonds_of_type
def _radical_graph_isomorphisms(gra): """ Generate a set of graphs where the radical has been migrated to a new atom and then calculate the isomorphism between the input graph and all of the new graphs generated by moving the radical """ # Determine useful keys symbols = atom_symbols(gra) unsat_keys = unsaturated_atom_keys(gra) unsat_key = next(iter(unsat_keys)) h_atm_key = max(symbols.keys()) + 1 new_gras, isomorphisms = [], [] for aidx, symbol in enumerate(symbols.values()): # Loop over saturated (non-radical) heavy atoms if symbol != 'H' and aidx != unsat_key: # Add hydrogen atom to radical atom new_graph = add_atom_explicit_hydrogen_keys( gra, {unsat_key: [h_atm_key]}) # Remove hydrogen from saturated atom neighbors = atoms_neighbor_atom_keys(new_graph) for neigh in neighbors[aidx]: if symbols[neigh] == 'H': aneighbor = neigh break new_graph = remove_atoms(new_graph, [aneighbor]) # Build lists to return new_gras.append(new_graph) isomorphisms.append(full_isomorphism(gra, new_graph)) return tuple(zip(new_gras, isomorphisms))
def _extend_chain_to_include_anchoring_atoms(gra, keys, zma_keys): """ extend chain to include three atoms already specified in v-matrix :param gra: the graph :param keys: keys in the chain; the first atom should already be specified :param zma_keys: keys currently in the v-matrix """ ngb_keys_dct = atoms_sorted_neighbor_atom_keys(gra, symbs_first=( 'X', 'C', ), symbs_last=('H', ), ords_last=(0.1, )) symb_dct = atom_symbols(gra) key3 = keys[0] assert key3 in zma_keys key2 = next(k for k in ngb_keys_dct[key3] if k in zma_keys) if symb_dct[key2] == 'X': key1 = next(k for k in ngb_keys_dct[key3][1:] if k in zma_keys) else: key1 = next(k for k in ngb_keys_dct[key2] if k in zma_keys and k != key3) keys = ( key1, key2, ) + tuple(keys) return keys
def heavy_atom_count(gra, dummy=False): """ the number of heavy atoms """ if not dummy: gra = without_dummy_atoms(gra) atm_symb_dct = atom_symbols(gra) nhvy_atms = sum(ptab.to_number(sym) != 1 for sym in atm_symb_dct.values()) return nhvy_atms
def van_der_waals_radius(gra, key): """ van der waals radius for an atom in the graph (in angstroms) """ symb_dct = atom_symbols(gra) symb = symb_dct[key] rad = ptab.van_der_waals_radius(symb) * phycon.BOHR2ANG return rad
def rotational_bond_keys(gra, lin_keys=None, with_h_rotors=True): """ get all rotational bonds for a graph :param gra: the graph :param lin_keys: keys to linear atoms in the graph """ gra = explicit(gra) sym_dct = atom_symbols(gra) ngb_keys_dct = atoms_neighbor_atom_keys(gra) bnd_ord_dct = resonance_dominant_bond_orders(gra) rng_bnd_keys = list(itertools.chain(*rings_bond_keys(gra))) def _is_rotational_bond(bnd_key): ngb_keys_lst = [ngb_keys_dct[k] - bnd_key for k in bnd_key] is_single = max(bnd_ord_dct[bnd_key]) <= 1 has_neighbors = all(ngb_keys_lst) not_in_ring = bnd_key not in rng_bnd_keys is_h_rotor = any( set(map(sym_dct.__getitem__, ks)) == {'H'} for ks in ngb_keys_lst) return is_single and has_neighbors and not_in_ring and ( not is_h_rotor or with_h_rotors) rot_bnd_keys = frozenset(filter(_is_rotational_bond, bond_keys(gra))) lin_keys_lst = linear_segments_atom_keys(gra, lin_keys=lin_keys) dum_keys = tuple(atom_keys(gra, sym='X')) for keys in lin_keys_lst: bnd_keys = sorted((k for k in rot_bnd_keys if k & set(keys)), key=sorted) # Check whether there are neighboring atoms on either side of the # linear segment excl_keys = set(keys) | set(dum_keys) end_key1 = atom_neighbor_atom_key(gra, keys[0], excl_atm_keys=excl_keys) excl_keys |= {end_key1} end_key2 = atom_neighbor_atom_key(gra, keys[-1], excl_atm_keys=excl_keys) end_keys = {end_key1, end_key2} ngb_keys_lst = [ngb_keys_dct[k] - excl_keys for k in end_keys] has_neighbors = all(ngb_keys_lst) if not has_neighbors: rot_bnd_keys -= set(bnd_keys) else: rot_bnd_keys -= set(bnd_keys[:-1]) return rot_bnd_keys
def atom_count_by_type(gra, sym, keys=None): """ count the number of atoms with a given type (symbol) :param gra: the graph :param sym: the symbol :param keys: optionally, restrict the count to a subset of keys """ keys = atom_keys(gra) if keys is None else keys symb_dct = atom_symbols(gra) symbs = list(map(symb_dct.__getitem__, keys)) return symbs.count(sym)
def formula(gra): """ Generate a stoichiometric formula dictionary from a molecular graph. :param gra: molecular graph :type gra: automol graph data structure :type: dict[str: int] """ gra = explicit(gra) syms = atom_symbols(gra).values() fml = _util.formula(syms) return fml
def equivalent_bonds(gra, bnd_key, stereo=True, dummy=True): """ Identify sets of isomorphically equivalent bonds Two bonds are equivalent if they transform into each other under an automorphism :param gra: A graph :param bnd_key: An bond key for the graph, which may be sorted or unsorted :param backbone_only: Compare backbone atoms only? :type stereo: bool :param dummy: Consider dummy atoms? :type dummy: bool :returns: Keys to equivalent bonds :rtype: frozenset """ bnd_key = tuple(bnd_key) bnd_keys = list(map(tuple, map(sorted, bond_keys(gra)))) bnd_keys += list(map(tuple, map(reversed, bnd_keys))) assert bnd_key in bnd_keys, "{} not in {}".format(bnd_key, bnd_keys) atm_symb_dct = atom_symbols(gra) atm_ngbs_dct = atoms_neighbor_atom_keys(gra) def _symbols(bnd_key): return list(map(atm_symb_dct.__getitem__, bnd_key)) def _neighbor_symbols(bnd_key): key1, key2 = bnd_key nsymbs1 = sorted(map(atm_symb_dct.__getitem__, atm_ngbs_dct[key1])) nsymbs2 = sorted(map(atm_symb_dct.__getitem__, atm_ngbs_dct[key2])) return nsymbs1, nsymbs2 # 1. Find bonds with the same atom types bnd_symbs = _symbols(bnd_key) cand_keys = [k for k in bnd_keys if _symbols(k) == bnd_symbs] # 2. Of those, find bonds with the same neighboring atom types bnd_ngb_symbs = _neighbor_symbols(bnd_key) cand_keys = [k for k in cand_keys if _neighbor_symbols(k) == bnd_ngb_symbs] # 3. Find the equivalent bonds from the list of candidates. # Strategy: Change the atom symbols to 'Lv' and 'Ts' and check for # isomorphism. Assumes none of the compounds have element 116 or 117. ref_gra = set_atom_symbols(gra, {bnd_key[0]: 'Lv', bnd_key[1]: 'Ts'}) bnd_keys = [] for key in cand_keys: comp_gra = set_atom_symbols(gra, {key[0]: 'Lv', key[1]: 'Ts'}) if isomorphism(ref_gra, comp_gra, stereo=stereo, dummy=dummy): bnd_keys.append(key) return frozenset(bnd_keys)
def start_at(gra, key): """ start a v-matrix at a specific atom Returns the started vmatrix, along with keys to atoms whose neighbors are missing from it """ symb_dct = atom_symbols(gra) ngb_keys_dct = atoms_sorted_neighbor_atom_keys(gra, symbs_first=( 'X', 'C', ), symbs_last=('H', ), ords_last=(0.1, )) ngb_keys = ngb_keys_dct[key] if not ngb_keys: zma_keys = [] elif len(ngb_keys) == 1: # Need special handling for atoms with only one neighbor if symb_dct[key] in ('H', 'X'): key2 = ngb_keys[0] zma_keys = (key2, ) + ngb_keys_dct[key2] else: key2 = ngb_keys[0] ngb_keys = tuple(k for k in ngb_keys_dct[key2] if k != key) zma_keys = (key, key2) + ngb_keys else: zma_keys = (key, ) + ngb_keys_dct[key] vma = () for row, key_ in enumerate(zma_keys): idx1 = idx2 = idx3 = None if row > 0: key1 = next(k for k in ngb_keys_dct[key_] if k in zma_keys[:row]) idx1 = zma_keys.index(key1) if row > 1: key2 = next(k for k in ngb_keys_dct[key1] if k in zma_keys[:row] and k != key_) idx2 = zma_keys.index(key2) if row > 2: key3 = next(k for k in zma_keys[:row] if k not in (key_, key1, key2)) idx3 = zma_keys.index(key3) sym = symb_dct[key_] key_row = [idx1, idx2, idx3] vma = automol.vmat.add_atom(vma, sym, key_row) return vma, zma_keys
def geometry(gra, keys=None, ntries=5, max_dist_err=0.2): """ sample a qualitatively-correct stereo geometry :param gra: the graph, which may or may not have stereo :param keys: graph keys, in the order in which they should appear in the geometry :param ntries: number of tries for finding a valid geometry :param max_dist_err: maximum distance error convergence threshold Qualitatively-correct means it has the right connectivity and the right stero parities, but its bond lengths and bond angles may not be quantitatively realistic """ assert gra == explicit(gra), ( "Graph => geometry conversion requires explicit hydrogens!\n" "Use automol.graph.explicit() to convert to an explicit graph.") # 0. Get keys and symbols symb_dct = atom_symbols(gra) keys = sorted(atom_keys(gra)) if keys is None else keys symbs = tuple(map(symb_dct.__getitem__, keys)) # 1. Generate bounds matrices lmat, umat = distance_bounds_matrices(gra, keys) chi_dct = chirality_constraint_bounds(gra, keys) pla_dct = planarity_constraint_bounds(gra, keys) conv1_ = qualitative_convergence_checker_(gra, keys) conv2_ = embed.distance_convergence_checker_(lmat, umat, max_dist_err) def conv_(xmat, err, grad): return conv1_(xmat, err, grad) & conv2_(xmat, err, grad) # 2. Generate coordinates with correct stereo, trying a few times for _ in range(ntries): xmat = embed.sample_raw_distance_coordinates(lmat, umat, dim4=True) xmat, conv = embed.cleaned_up_coordinates( xmat, lmat, umat, pla_dct=pla_dct, chi_dct=chi_dct, conv_=conv_) if conv: break if not conv: raise error.FailedGeometryGenerationError # 3. Generate a geometry data structure from the coordinates xyzs = xmat[:, :3] geo = _create.geom.from_data(symbs, xyzs, angstrom=True) return geo
def atom_symbol_idxs(gra): """ determine the indices for each atom symbol :return: dict[symb] = [idxs] """ idx_symb_dct = atom_symbols(gra) symb_idx_dct = {} for idx, symb in idx_symb_dct.items(): if symb not in symb_idx_dct: symb_idx_dct[symb] = [idx] else: symb_idx_dct[symb].append(idx) return symb_idx_dct
def from_graph(gra): """ networkx graph object from a molecular graph """ nxg = networkx.Graph() nxg.add_nodes_from(atom_keys(gra)) nxg.add_edges_from(bond_keys(gra)) networkx.set_node_attributes(nxg, atom_symbols(gra), 'symbol') networkx.set_node_attributes(nxg, atom_implicit_hydrogen_valences(gra), 'implicit_hydrogen_valence') networkx.set_node_attributes(nxg, atom_stereo_parities(gra), 'stereo_parity') networkx.set_edge_attributes(nxg, bond_orders(gra), 'order') networkx.set_edge_attributes(nxg, bond_stereo_parities(gra), 'stereo_parity') return nxg
def terminal_heavy_atom_keys(gra): """ terminal heavy atoms, sorted by atom type and hydrogen count """ gra = implicit(gra) atm_imp_hyd_vlc_dct = atom_implicit_hydrogen_valences(gra) atm_keys = [ key for key, ngb_keys in atoms_neighbor_atom_keys(gra).items() if len(ngb_keys) == 1 ] atm_keys = sorted(atm_keys, key=atm_imp_hyd_vlc_dct.__getitem__, reverse=True) atm_symbs = dict_.values_by_key(atom_symbols(gra), atm_keys) srt = automol.formula.argsort_symbols(atm_symbs, symbs_first=('C', )) atm_keys = tuple(map(atm_keys.__getitem__, srt)) return atm_keys
def inchi_with_sort_from_geometry(gra, geo=None, geo_idx_dct=None): """ Generate an InChI string from a molecular graph. If coordinates are passed in, they are used to determine stereo. :param gra: molecular graph :type gra: automol graph data structure :param geo: molecular geometry :type geo: automol geometry data structure :param geo_idx_dct: :type geo_idx_dct: dict[:] :rtype: (str, tuple(int)) """ gra = without_dummy_atoms(gra) gra = dominant_resonance(gra) atm_keys = sorted(atom_keys(gra)) bnd_keys = list(bond_keys(gra)) atm_syms = dict_.values_by_key(atom_symbols(gra), atm_keys) atm_bnd_vlcs = dict_.values_by_key(atom_bond_valences(gra), atm_keys) atm_rad_vlcs = dict_.values_by_key(atom_unsaturated_valences(gra), atm_keys) bnd_ords = dict_.values_by_key(bond_orders(gra), bnd_keys) if geo is not None: assert geo_idx_dct is not None atm_xyzs = coordinates(geo) atm_xyzs = [ atm_xyzs[geo_idx_dct[atm_key]] if atm_key in geo_idx_dct else (0., 0., 0.) for atm_key in atm_keys ] else: atm_xyzs = None mlf, key_map_inv = _molfile.from_data(atm_keys, bnd_keys, atm_syms, atm_bnd_vlcs, atm_rad_vlcs, bnd_ords, atm_xyzs=atm_xyzs) rdm = _rdkit.from_molfile(mlf) ich, aux_info = _rdkit.to_inchi(rdm, with_aux_info=True) nums = _parse_sort_order_from_aux_info(aux_info) nums = tuple(map(key_map_inv.__getitem__, nums)) return ich, nums
def geometry(gra): """ Convert a molecular graph to a molecular geometry. :param gra: molecular graph :type gra: automol graph data structure :rtype: automol molecular geometry data structure """ symbs = atom_symbols(gra) if len(symbs) != 1: gra = explicit(gra) geo = embed_geometry(gra) else: symb = list(symbs.values())[0] # symb = list(symbs.keys())[0] geo = ((symb, (0.00, 0.00, 0.00)), ) return geo
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 equivalent_atoms(gra, atm_key, stereo=True, dummy=True): """ Identify sets of isomorphically equivalent atoms Two atoms are equivalent if they transform into each other under an automorphism :param gra: A graph :param atm_key: An atom key for the graph :param stereo: Consider stereo? :type stereo: bool :param dummy: Consider dummy atoms? :type dummy: bool :returns: Keys to equivalent atoms :rtype: frozenset """ assert atm_key in atom_keys(gra), ("{} not in {}".format( atm_key, atom_keys(gra))) atm_symb_dct = atom_symbols(gra) atm_ngbs_dct = atoms_neighbor_atom_keys(gra) def _neighbor_symbols(key): return sorted(map(atm_symb_dct.__getitem__, atm_ngbs_dct[key])) # 1. Find atoms with the same symbols atm_symb = atm_symb_dct[atm_key] cand_keys = atom_keys(gra, sym=atm_symb) # 2. Of those, find atoms with the same neighboring atom types atm_ngb_symbs = _neighbor_symbols(atm_key) cand_keys = [k for k in cand_keys if _neighbor_symbols(k) == atm_ngb_symbs] # 3. Find the equivalent atoms from the list of candidates. # Strategy: Change the atom symbol to 'Ts' and check for isomorphism. # Assumes none of the compounds have element 117. ref_gra = set_atom_symbols(gra, {atm_key: 'Ts'}) atm_keys = [] for key in cand_keys: comp_gra = set_atom_symbols(gra, {key: 'Ts'}) if isomorphism(ref_gra, comp_gra, stereo=stereo, dummy=dummy): atm_keys.append(key) return frozenset(atm_keys)
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 neighbors_of_type(gra, aidx, symb): """ For a given atom, determine the indices of all the atoms which neighbor it that are of the type specified. :param gra: molecular graph :type gra: molecular graph data structure :param aidx: index of atom for which to find neighbors :type aidx: int :param symb: symbols of desired atom types for neighbors :type symb: str """ idx_symb_dct = atom_symbols(gra) neighs = atoms_neighbor_atom_keys(gra)[aidx] neigh_symbs = atom_idx_to_symb(neighs, idx_symb_dct) idxs_of_type = tuple() for nidx, nsymb in zip(neighs, neigh_symbs): if nsymb == symb: idxs_of_type += (nidx, ) return idxs_of_type
def two_bond_idxs(gra, symb1, cent, symb2): """ Determine the triplet of indices of atoms of specified types that are connected in a chain by two bonds: (symb1_idx, cent_idx, symb2_idx). :param gra: molecular graph :type gra: molecular graph data structure :param symb1: symbol of atom at one end of chain :type symb1: str :param cent: symbol of atom in the middle of a chain :type cent: str :param symb2: symbol of atom at other end of chain :type symb2: str """ grps = tuple() neigh_dct = atoms_neighbor_atom_keys(gra) idx_symb_dct = atom_symbols(gra) symb_idx_dct = atom_symbol_idxs(gra) cent_idxs = symb_idx_dct.get(cent, tuple()) for cent_idx in cent_idxs: neighs = tuple(neigh_dct[cent_idx]) neigh_symbs = atom_idx_to_symb(neighs, idx_symb_dct) if neigh_symbs == (symb1, symb2): grp_idxs = (neighs[0], cent_idx, neighs[1]) elif neigh_symbs == (symb2, symb1): grp_idxs = (neighs[1], cent_idx, neighs[0]) else: grp_idxs = () if grp_idxs: grps += ((grp_idxs), ) return grps
def complete_branch(gra, key, vma, zma_keys, branch_keys=None): """ continue constructing a v-matrix along a chain All neighboring atoms along the chain will be included Exactly one atom in the chain must already be in the v-matrix :param gra: the graph for which the v-matrix will be constructed :param vma: a partial v-matrix from which to continue :param zma_keys: row keys for the partial v-matrix, identifying the atom specified by each row of `vma` in order :param branch_keys: optionally, restrict the v-matrix to these keys and their neighbors; if `None`, the entire branch will be included """ branch_keys = atom_keys(gra) if branch_keys is None else branch_keys keys = _extend_chain_to_include_anchoring_atoms(gra, [key], zma_keys) zma_keys = list(zma_keys) symb_dct = atom_symbols(gra) ngb_keys_dct = atoms_sorted_neighbor_atom_keys( gra, symbs_first=('X', 'C',), symbs_last=('H',), ords_last=(0.1,)) def _continue(key1, key2, key3, vma, zma_keys): k3ns = list(ngb_keys_dct[key3]) for k3n in set(k3ns) & set(zma_keys): k3ns.remove(k3n) if k3ns: key4 = k3ns.pop(0) # Add the leading atom to the v-matrix symb = symb_dct[key4] key_row = list(map(zma_keys.index, (key3, key2, key1))) vma = automol.vmat.add_atom(vma, symb, key_row) assert key4 not in zma_keys, ("Atom {:d} already in v-matrix." .format(key4)) zma_keys.append(key4) # Add the neighbors of atom 3 (if any) to the v-matrix, decoupled # from atom 1 for properly decopuled torsions for k3n in k3ns: sym = symb_dct[k3n] if symb_dct[key4] == 'X': key_row = list(map(zma_keys.index, (key3, key4, key2))) else: key_row = list(map(zma_keys.index, (key3, key2, key4))) vma = automol.vmat.add_atom(vma, sym, key_row) assert k3n not in zma_keys, ("Atom {:d} already in v-matrix." .format(k3n)) zma_keys.append(k3n) # Recursion if key4 in branch_keys: vma, zma_keys = _continue(key2, key3, key4, vma, zma_keys) if symb_dct[key4] == 'X': key2 = key4 for k3n in k3ns: if k3n in branch_keys: vma, zma_keys = _continue(key2, key3, k3n, vma, zma_keys) return vma, zma_keys key1, key2, key3 = keys[:3] vma, zma_keys = _continue(key1, key2, key3, vma, zma_keys) return vma, zma_keys
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 = _create.geom.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
def complete_branch(gra, key, vma, zma_keys, branch_keys=None): """ continue constructing a v-matrix along a chain All neighboring atoms along the chain will be included Exactly one atom in the chain must already be in the v-matrix :param gra: the graph for which the v-matrix will be constructed :param keys: the keys for atoms along the chain, which must be contiguous; the first atom must already appear in the v-matrix :param vma: a partial v-matrix from which to continue :param zma_keys: row keys for the partial v-matrix, identifying the atom specified by each row of `vma` in order :param branch_keys: optionally, restrict the v-matrix to these keys and their neighbors; if `None`, the entire branch will be included """ branch_keys = atom_keys(gra) if branch_keys is None else branch_keys keys = _extend_chain_to_include_anchoring_atoms(gra, [key], zma_keys) zma_keys = list(zma_keys) symb_dct = atom_symbols(gra) ngb_keys_dct = atoms_sorted_neighbor_atom_keys(gra, symbs_first=( 'X', 'C', ), symbs_last=('H', ), ords_last=(0.1, ), prioritize_keys=branch_keys) # If this z-matrix is being continued from a partial z-matrix, the leading # atom for a torsion may have already be defined. To handle this case, I # make a dictionary of these leading atoms and use them below where needed. lead_key_dct = {} for idx, key_row in enumerate(automol.vmat.key_matrix(vma)): axis = key_row[:2] if None not in axis: axis = tuple(map(zma_keys.__getitem__, axis)) if axis not in lead_key_dct: lead_key_dct[axis] = zma_keys[idx] def _continue(key1, key2, key3, vma, zma_keys): k3ns = list(ngb_keys_dct[key3]) for k3n in set(k3ns) & set(zma_keys): k3ns.remove(k3n) if k3ns: key4 = k3ns.pop(0) lead_key = None if (key3, key2) in lead_key_dct: lead_key = lead_key_dct[(key3, key2)] # Add the leading atom to the v-matrix symb = symb_dct[key4] dkey = key1 if lead_key is None else lead_key key_row = list(map(zma_keys.index, (key3, key2, dkey))) vma = automol.vmat.add_atom(vma, symb, key_row) assert key4 not in zma_keys, ( "Atom {:d} already in v-matrix.".format(key4)) zma_keys.append(key4) dkey = key4 if lead_key is None else lead_key # Add the neighbors of atom 3 (if any) to the v-matrix, decoupled # from atom 1 for properly decopuled torsions for k3n in k3ns: sym = symb_dct[k3n] if symb_dct[dkey] == 'X': key_row = list(map(zma_keys.index, (key3, dkey, key2))) else: key_row = list(map(zma_keys.index, (key3, key2, dkey))) vma = automol.vmat.add_atom(vma, sym, key_row) assert k3n not in zma_keys, ( "Atom {:d} already in v-matrix.".format(k3n)) zma_keys.append(k3n) # Recursion if key4 in branch_keys: vma, zma_keys = _continue(key2, key3, key4, vma, zma_keys) if symb_dct[key4] == 'X': key2 = key4 for k3n in k3ns: if k3n in branch_keys: vma, zma_keys = _continue(key2, key3, k3n, vma, zma_keys) return vma, zma_keys key1, key2, key3 = keys[:3] vma, zma_keys = _continue(key1, key2, key3, vma, zma_keys) return vma, zma_keys
def electron_count(gra, charge=0): """ the number of electrons in the molecule """ atm_symb_dct = atom_symbols(explicit(gra)) nelec = sum(map(ptab.to_number, atm_symb_dct.values())) - charge return nelec