def vmatrix(gra, keys=None, rng_keys=None): """ v-matrix for a connected graph :param gra: the graph :param keys: restrict the v-matrix to a subset of keys, which must span a connected graph :param rng_keys: keys for a ring to start from """ if keys is not None: gra = subgraph(gra, keys) assert is_connected(gra), "Graph must be connected!" # Start with the ring systems and their connections. If there aren't any, # start with the first terminal atom if ring_systems(gra): vma, zma_keys = connected_ring_systems(gra, rng_keys=rng_keys) else: term_keys = sorted(terminal_heavy_atom_keys(gra)) if term_keys: start_key = term_keys[0] else: start_key = sorted(atom_keys(gra))[0] vma, zma_keys = start_at(gra, start_key) rem_keys = atom_keys(gra) - set(zma_keys) vma, zma_keys = continue_vmatrix(gra, rem_keys, vma, zma_keys) return vma, zma_keys
def ring_arc_complement_atom_keys(gra, rng): """ non-intersecting arcs from a ring that shares segments with a graph """ gra_atm_bnd_dct = atoms_bond_keys(gra) rng_atm_bnd_dct = atoms_bond_keys(rng) # 1. find divergence points, given by the atom at which the divergence # occurs and the bond followed by the ring as it diverges div_dct = {} for atm_key in atom_keys(gra) & atom_keys(rng): div = rng_atm_bnd_dct[atm_key] - gra_atm_bnd_dct[atm_key] if div: bnd_key, = div div_dct[atm_key] = bnd_key # 2. cycle through the ring atoms; if you meet a starting divergence, start # an arc; extend the arc until you meet an ending divergence; repeat until # all divergences are accounted for atm_keys = sorted_ring_atom_keys_from_bond_keys(bond_keys(rng)) arcs = [] arc = [] for atm_key, next_atm_key in mit.windowed(itertools.cycle(atm_keys), 2): bnd_key = frozenset({atm_key, next_atm_key}) # if we haven't started an arc, see if we are at a starting divergence; # if so, start the arc now and cross the divergence from our list if not arc: if atm_key in div_dct and div_dct[atm_key] == bnd_key: div_dct.pop(atm_key) arc.append(atm_key) # if we've started an arc, extend it; then, check if we are at an # ending divergence; if so, end the arc and cross the divergence from # our list; add it to our list of arcs else: arc.append(atm_key) if next_atm_key in div_dct and div_dct[next_atm_key] == bnd_key: div_dct.pop(next_atm_key) arc.append(next_atm_key) arcs.append(arc) arc = [] # if no divergences are left, break out of the loop if not div_dct: break arcs = tuple(map(tuple, arcs)) return arcs
def union(gra1, gra2, check=True): """ a union of two graphs """ if check: assert not atom_keys(gra1) & atom_keys(gra2) atm_dct = {} atm_dct.update(atoms(gra1)) atm_dct.update(atoms(gra2)) bnd_dct = {} bnd_dct.update(bonds(gra1)) bnd_dct.update(bonds(gra2)) return _create.graph.from_atoms_and_bonds(atm_dct, bnd_dct)
def standard_keys_for_sequence(gras): """ assigns non-overlapping keys to a sequence of graphs (returns a series of key maps for each) """ atm_key_dcts = [] shift = 0 for gra in gras: natms = atom_count(gra, dummy=True, with_implicit=False) atm_key_dct = { atm_key: idx + shift for idx, atm_key in enumerate(sorted(atom_keys(gra))) } atm_key_dcts.append(atm_key_dct) shift += natms gras = [ relabel(gra, atm_key_dct) for gra, atm_key_dct in zip(gras, atm_key_dcts) ] return gras, atm_key_dcts
def linear_segments_atom_keys(gra, lin_keys=None): """ atom keys for linear segments in the graph """ ngb_keys_dct = atoms_neighbor_atom_keys(without_dummy_atoms(gra)) lin_keys = (dummy_atoms_neighbor_atom_key(gra).values() if lin_keys is None else lin_keys) lin_keys = [k for k in lin_keys if len(ngb_keys_dct[k]) <= 2] lin_segs = connected_components(subgraph(gra, lin_keys)) lin_keys_lst = [] for lin_seg in lin_segs: lin_seg_keys = atom_keys(lin_seg) if len(lin_seg_keys) == 1: key, = lin_seg_keys lin_keys_lst.append([key]) else: end_key1, end_key2 = sorted([ key for key, ngb_keys in atoms_neighbor_atom_keys(lin_seg).items() if len(ngb_keys) == 1 ]) ngb_keys_dct = atoms_neighbor_atom_keys(lin_seg) key = None keys = [end_key1] while key != end_key2: key, = ngb_keys_dct[keys[-1]] - set(keys) keys.append(key) lin_keys_lst.append(keys) lin_keys_lst = tuple(map(tuple, lin_keys_lst)) return lin_keys_lst
def insert_bonded_atom(gra, sym, atm_key, bnd_atm_key=None, imp_hyd_vlc=None, atm_ste_par=None, bnd_ord=None, bnd_ste_par=None): """ insert a single atom with a bond to an atom already in the graph Keys will be standardized upon insertion """ keys = sorted(atom_keys(gra)) bnd_atm_key_ = max(keys) + 1 gra = add_bonded_atom(gra, sym, atm_key, bnd_atm_key=bnd_atm_key_, imp_hyd_vlc=imp_hyd_vlc, atm_ste_par=atm_ste_par, bnd_ord=bnd_ord, bnd_ste_par=bnd_ste_par) if bnd_atm_key != bnd_atm_key_: assert bnd_atm_key in keys idx = keys.index(bnd_atm_key) key_dct = {} key_dct.update({k: k for k in keys[:idx]}) key_dct[bnd_atm_key_] = bnd_atm_key key_dct.update({k: k + 1 for k in keys[idx:]}) gra = relabel(gra, key_dct) return gra
def closest_unbonded_atoms(geo, gra=None): """ Determine which pair of unbonded atoms in a molecular geometry are closest together. :param geo: molecular geometry :type geo: automol geometry data structure :param gra: the graph describing connectivity; if None, a connectivity graph will be generated using default distance thresholds :type gra: automol graph data structure :rtype: (frozenset(int), float) """ gra = _connectivity_graph(geo) if gra is None else gra atm_keys = atom_keys(gra) bnd_keys = bond_keys(gra) poss_bnd_keys = set(map(frozenset, itertools.combinations(atm_keys, r=2))) # The set of candidates includes all unbonded pairs of atoms cand_bnd_keys = poss_bnd_keys - bnd_keys min_bnd_key = None min_dist_val = 1000. for bnd_key in cand_bnd_keys: dist_val = distance(geo, *bnd_key) if dist_val < min_dist_val: min_dist_val = dist_val min_bnd_key = bnd_key return min_bnd_key, min_dist_val
def angle_key_filler_(gra, keys=None, check=True): """ returns a function that fills in the first or last element of an angle key in a dictionary with a neighboring atom (works for central or dihedral angles) """ keys = atom_keys(gra) if keys is None else keys def _fill_in_angle_key(ang_key): end1_key = ang_key[0] end2_key = ang_key[-1] mid_keys = list(ang_key[1:-1]) assert not any(k is None for k in mid_keys) if end1_key is None: end1_key = atom_neighbor_atom_key( gra, mid_keys[0], excl_atm_keys=[end2_key]+mid_keys, incl_atm_keys=keys) if end2_key is None: end2_key = atom_neighbor_atom_key( gra, mid_keys[-1], excl_atm_keys=[end1_key]+mid_keys, incl_atm_keys=keys) ang_key = [end1_key] + mid_keys + [end2_key] if any(k is None for k in ang_key): if check: raise ValueError("Angle key {} couldn't be filled in" .format(str(ang_key))) ang_key = None else: ang_key = tuple(ang_key) return ang_key return _fill_in_angle_key
def set_stereo_from_geometry(gra, geo, geo_idx_dct=None): """ set graph stereo from a geometry (coordinate distances need not match connectivity -- what matters is the relative positions at stereo sites) """ gra = without_stereo_parities(gra) last_gra = None atm_keys = sorted(atom_keys(gra)) geo_idx_dct = (geo_idx_dct if geo_idx_dct is not None else {atm_key: idx for idx, atm_key in enumerate(atm_keys)}) # set atom and bond stereo, iterating to self-consistency atm_keys = set() bnd_keys = set() while last_gra != gra: last_gra = gra atm_keys.update(stereogenic_atom_keys(gra)) bnd_keys.update(stereogenic_bond_keys(gra)) gra = _set_atom_stereo_from_geometry(gra, atm_keys, geo, geo_idx_dct) gra = _set_bond_stereo_from_geometry(gra, bnd_keys, geo, geo_idx_dct) return gra
def _bond_stereo_corrected_geometry(gra, bnd_ste_par_dct, geo, geo_idx_dct): """ correct the bond stereo parities of a geometry, for a subset of bonds """ bnd_keys = list(bnd_ste_par_dct.keys()) for bnd_key in bnd_keys: par = bnd_ste_par_dct[bnd_key] curr_par = _bond_stereo_parity_from_geometry(gra, bnd_key, geo, geo_idx_dct) if curr_par != par: xyzs = automol.geom.coordinates(geo) atm1_key, atm2_key = bnd_key atm1_xyz = xyzs[geo_idx_dct[atm1_key]] atm2_xyz = xyzs[geo_idx_dct[atm2_key]] rot_axis = numpy.subtract(atm2_xyz, atm1_xyz) rot_atm_keys = atom_keys( branch(gra, atm1_key, {atm1_key, atm2_key})) rot_idxs = list(map(geo_idx_dct.__getitem__, rot_atm_keys)) geo = automol.geom.rotate(geo, rot_axis, numpy.pi, orig_xyz=atm1_xyz, idxs=rot_idxs) assert _bond_stereo_parity_from_geometry(gra, bnd_key, geo, geo_idx_dct) == par gra = set_bond_stereo_parities(gra, {bnd_key: par}) return geo, gra
def _atom_stereo_corrected_geometry(gra, atm_ste_par_dct, geo, geo_idx_dct): """ correct the atom stereo parities of a geometry, for a subset of atoms """ ring_atm_keys = set(itertools.chain(*rings_atom_keys(gra))) atm_ngb_keys_dct = atoms_neighbor_atom_keys(gra) atm_keys = list(atm_ste_par_dct.keys()) for atm_key in atm_keys: par = atm_ste_par_dct[atm_key] curr_par = _atom_stereo_parity_from_geometry(gra, atm_key, geo, geo_idx_dct) if curr_par != par: atm_ngb_keys = atm_ngb_keys_dct[atm_key] # for now, we simply exclude rings from the pivot keys # (will not work for stereo atom at the intersection of two rings) atm_piv_keys = list(atm_ngb_keys - ring_atm_keys)[:2] assert len(atm_piv_keys) == 2 atm3_key, atm4_key = atm_piv_keys # get coordinates xyzs = coordinates(geo) atm_xyz = xyzs[geo_idx_dct[atm_key]] atm3_xyz = xyzs[geo_idx_dct[atm3_key]] atm4_xyz = xyzs[geo_idx_dct[atm4_key]] # do the rotation rot_axis = util.vec.unit_bisector(atm3_xyz, atm4_xyz, orig_xyz=atm_xyz) rot_atm_keys = ( atom_keys(branch(gra, atm_key, {atm_key, atm3_key})) | atom_keys(branch(gra, atm_key, {atm_key, atm4_key}))) rot_idxs = list(map(geo_idx_dct.__getitem__, rot_atm_keys)) geo = automol.geom.rotate(geo, rot_axis, numpy.pi, orig_xyz=atm_xyz, idxs=rot_idxs) assert _atom_stereo_parity_from_geometry(gra, atm_key, geo, geo_idx_dct) == par gra = set_atom_stereo_parities(gra, {atm_key: par}) return geo, gra
def longest_chain(gra): """ longest chain in the graph """ atm_keys = atom_keys(gra) max_chain = max((atom_longest_chain(gra, atm_key) for atm_key in atm_keys), key=len) return max_chain
def ring_system_decomposed_atom_keys(rsy, rng_keys=None, check=True): """ decomposed atom keys for a polycyclic ring system in a graph The ring system is decomposed into a ring and a series of arcs that can be used to successively construct the system :param rsy: the ring system :param rng_keys: keys for the first ring in the decomposition; if None, the smallest ring in the system will be chosen """ if rng_keys is None: rng = sorted(rings(rsy), key=atom_count)[0] rng_keys = sorted_ring_atom_keys(rng) # check the arguments, if requested if check: # check that the graph is connected assert is_connected(rsy), "Ring system can't be disconnected." # check that the graph is actually a ring system assert is_ring_system(rsy), ( "This is not a ring system graph:\n{:s}".format(string(rsy))) # check that rng is a subgraph of rsy assert set(rng_keys) <= atom_keys(rsy), ( "{}\n^ Rings system doesn't contain ring as subgraph:\n{}".format( string(rsy, one_indexed=False), str(rng_keys))) bnd_keys = list(mit.windowed(rng_keys + rng_keys[:1], 2)) # Remove bonds for the ring rsy = remove_bonds(rsy, bnd_keys) keys_lst = [rng_keys] done_keys = set(rng_keys) while bond_keys(rsy): # Determine shortest paths for the graph with one more ring/arc deleted sp_dct = atom_shortest_paths(rsy) # The shortest path will be the next shortest arc in the system arc_keys = min((sp_dct[i][j] for i, j in itertools.combinations(done_keys, 2) if j in sp_dct[i]), key=len) # Add this arc to the list keys_lst.append(arc_keys) # Add these keys to the list of done keys done_keys |= set(arc_keys) # Delete tbond keys for the new arc and continue to the next iteration bnd_keys = list(map(frozenset, mit.windowed(arc_keys, 2))) rsy = remove_bonds(rsy, bnd_keys) keys_lst = tuple(map(tuple, keys_lst)) return keys_lst
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_longest_chains(gra): """ longest chains, by atom """ atm_keys = atom_keys(gra) long_chain_dct = { atm_key: atom_longest_chain(gra, atm_key) for atm_key in atm_keys } return long_chain_dct
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 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 nonresonant_radical_atom_keys(rgr): """ keys for radical atoms that are not in resonance """ rgr = without_fractional_bonds(rgr) atm_keys = list(atom_keys(rgr)) atm_rad_vlcs_by_res = [ dict_.values_by_key(atom_unsaturated_valences(dom_rgr), atm_keys) for dom_rgr in dominant_resonances(rgr) ] atm_rad_vlcs = [min(rad_vlcs) for rad_vlcs in zip(*atm_rad_vlcs_by_res)] atm_rad_keys = frozenset( atm_key for atm_key, atm_rad_vlc in zip(atm_keys, atm_rad_vlcs) if atm_rad_vlc) return atm_rad_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 sing_res_dom_radical_atom_keys(rgr): """ resonance-dominant radical atom keys,for one resonance """ rgr = without_fractional_bonds(rgr) atm_keys = list(atom_keys(rgr)) atm_rad_vlcs_by_res = [ dict_.values_by_key(atom_unsaturated_valences(dom_rgr), atm_keys) for dom_rgr in dominant_resonances(rgr) ] first_atm_rad_val = [atm_rad_vlcs_by_res[0]] atm_rad_vlcs = [max(rad_vlcs) for rad_vlcs in zip(*first_atm_rad_val)] atm_rad_keys = frozenset( atm_key for atm_key, atm_rad_vlc in zip(atm_keys, atm_rad_vlcs) if atm_rad_vlc) return atm_rad_keys
def resonance_dominant_radical_atom_keys(rgr): """ resonance-dominant radical atom keys (keys of resonance-dominant radical sites) """ rgr = without_fractional_bonds(rgr) atm_keys = list(atom_keys(rgr)) atm_rad_vlcs_by_res = [ dict_.values_by_key(atom_unsaturated_valences(dom_rgr), atm_keys) for dom_rgr in dominant_resonances(rgr) ] atm_rad_vlcs = [max(rad_vlcs) for rad_vlcs in zip(*atm_rad_vlcs_by_res)] atm_rad_keys = frozenset( atm_key for atm_key, atm_rad_vlc in zip(atm_keys, atm_rad_vlcs) if atm_rad_vlc) return atm_rad_keys
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 add_dummy_atoms(gra, dummy_key_dct): """ add dummy atoms to the graph, with dummy bonds to particular atoms :param dummy_key_dct: keys are atoms in the graph on which to place a dummy atom; values are the desired keys of the dummy atoms themselves, which must not overlap with already existing atoms """ atm_keys = atom_keys(gra) assert set(dummy_key_dct.keys()) <= atm_keys, ( "Keys must be existing atoms in the graph.") assert not set(dummy_key_dct.values()) & atm_keys, ( "Dummy atom keys cannot overlap with existing atoms.") for key, dummy_key in sorted(dummy_key_dct.items()): gra = add_bonded_atom(gra, 'X', key, bnd_atm_key=dummy_key, bnd_ord=0) return gra
def relabel_for_geometry(gra): """ relabel a z-matrix graph to line up with a geometry The result will line up with a geometry converted from the z-matrix, with dummy atoms removed. Removes dummy atoms and relabels in geometry order. Graph keys should correspond to the z-matrix used for conversion. :param gra: the graph """ dummy_keys = sorted(atom_keys(gra, sym='X')) for dummy_key in reversed(dummy_keys): gra = _shift_remove_dummy_atom(gra, dummy_key) return gra
def frozen(gra): """ hashable, sortable, immutable container of graph data """ atm_keys = sorted(atom_keys(gra)) bnd_keys = sorted(bond_keys(gra), key=sorted) # make it sortable by replacing Nones with -infinity atm_vals = numpy.array(dict_.values_by_key(atoms(gra), atm_keys), dtype=numpy.object) bnd_vals = numpy.array(dict_.values_by_key(bonds(gra), bnd_keys), dtype=numpy.object) atm_vals[numpy.equal(atm_vals, None)] = -numpy.inf bnd_vals[numpy.equal(bnd_vals, None)] = -numpy.inf frz_atms = tuple(zip(atm_keys, map(tuple, atm_vals))) frz_bnds = tuple(zip(bnd_keys, map(tuple, bnd_vals))) return (frz_atms, frz_bnds)
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 radical_dissociation_prods(gra, pgra1): """ given a dissociation product, determine the other product """ gra = without_fractional_bonds(gra) pgra2 = None rads = sing_res_dom_radical_atom_keys(gra) adj_atms = atoms_neighbor_atom_keys(gra) # adj_idxs = tuple(adj_atms[rad] for rad in rads) for rad in rads: for adj in adj_atms[rad]: for group in atom_groups(gra, adj): if full_isomorphism(explicit(group), explicit(pgra1)): pgra2 = remove_atoms(gra, atom_keys(group)) # pgra2 = remove_bonds(pgra2, bond_keys(group)) if bond_keys(group) in pgra2: pgra2 = remove_bonds(pgra2, bond_keys(group)) return (pgra1, pgra2)
def add_bonded_atom(gra, sym, atm_key, bnd_atm_key=None, imp_hyd_vlc=None, atm_ste_par=None, bnd_ord=None, bnd_ste_par=None): """ add a single atom with a bond to an atom already in the graph """ atm_keys = atom_keys(gra) bnd_atm_key = max(atm_keys) + 1 if bnd_atm_key is None else bnd_atm_key symb_dct = {bnd_atm_key: sym} imp_hyd_vlc_dct = ({ bnd_atm_key: imp_hyd_vlc } if imp_hyd_vlc is not None else None) atm_ste_par_dct = ({ bnd_atm_key: atm_ste_par } if atm_ste_par is not None else None) gra = add_atoms(gra, symb_dct, imp_hyd_vlc_dct=imp_hyd_vlc_dct, ste_par_dct=atm_ste_par_dct) bnd_key = frozenset({bnd_atm_key, atm_key}) bnd_ord_dct = {bnd_key: bnd_ord} if bnd_ord is not None else None bnd_ste_par_dct = ({ bnd_key: bnd_ste_par } if bnd_ste_par is not None else None) gra = add_bonds(gra, [bnd_key], ord_dct=bnd_ord_dct, ste_par_dct=bnd_ste_par_dct) return gra
def from_yaml_dictionary(yaml_gra_dct, one_indexed=True): """ read the graph from a yaml dictionary """ atm_dct = yaml_gra_dct['atoms'] bnd_dct = yaml_gra_dct['bonds'] atm_dct = dict_.transform_values( atm_dct, lambda x: tuple(map(x.__getitem__, ATM_PROP_NAMES))) bnd_dct = dict_.transform_keys(bnd_dct, lambda x: frozenset(map(int, x.split('-')))) bnd_dct = dict_.transform_values( bnd_dct, lambda x: tuple(map(x.__getitem__, BND_PROP_NAMES))) gra = _create.from_atoms_and_bonds(atm_dct, bnd_dct) if one_indexed: # revert one-indexing if the input is one-indexed atm_key_dct = {atm_key: atm_key - 1 for atm_key in atom_keys(gra)} gra = relabel(gra, atm_key_dct) return gra
def connected_ring_systems(gra, rng_keys=None, check=True): """ generate a v-matrix covering a graph's ring systems and the connections between them """ if check: assert is_connected(gra), "Graph must be connected!" rsys = sorted(ring_systems(gra), key=atom_count) # Construct the v-matrix for the first ring system, choosing which ring # to start from if rng_keys is None: rsy = rsys.pop(0) rngs = sorted(rings(rsy), key=atom_count) rng_keys = sorted_ring_atom_keys(rngs.pop(0)) else: idx = next((i for i, ks in enumerate(map(atom_keys, rsys)) if set(rng_keys) <= ks), None) assert idx is not None, ( "The ring {} is not in this graph:\n{}".format( str(rng_keys), string(gra, one_indexed=False))) rsy = rsys.pop(idx) keys_lst = list(ring_system_decomposed_atom_keys(rsy, rng_keys=rng_keys)) vma, zma_keys = ring_system(gra, keys_lst) keys = atom_keys(gra) - set(zma_keys) vma, zma_keys = continue_connected_ring_systems(gra, keys, vma, zma_keys, rsys=rsys, check=False) return vma, zma_keys