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 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 connected_components(gra): """ connected components in the graph """ cmp_gra_atm_keys_lst = connected_components_atom_keys(gra) cmp_gras = tuple(subgraph(gra, cmp_gra_atm_keys, stereo=True) for cmp_gra_atm_keys in cmp_gra_atm_keys_lst) return cmp_gras
def continue_vmatrix(gra, keys, vma, zma_keys): """ continue a v-matrix for a subset of keys, starting from a partial v-matrix """ gra = subgraph(gra, set(keys) | set(zma_keys)) vma, zma_keys = continue_connected_ring_systems(gra, keys, vma, zma_keys) # Complete any incomplete branches branch_keys = _atoms_missing_neighbors(gra, zma_keys) for key in branch_keys: vma, zma_keys = complete_branch(gra, key, vma, zma_keys) return vma, zma_keys
def distance_bounds_matrices(gra, keys, sp_dct=None): """ initial distance bounds matrices :param gra: molecular graph :param keys: atom keys specifying the order of indices in the matrix :param sp_dct: a 2d dictionary giving the shortest path between any pair of atoms in the graph """ assert set(keys) <= set(atom_keys(gra)) sub_gra = subgraph(gra, keys, stereo=True) sp_dct = atom_shortest_paths(sub_gra) if sp_dct is None else sp_dct bounds_ = path_distance_bounds_(gra) natms = len(keys) umat = numpy.zeros((natms, natms)) lmat = numpy.zeros((natms, natms)) for (idx1, key1), (idx2, key2) in itertools.combinations( enumerate(keys), 2): if key2 in sp_dct[key1]: path = sp_dct[key1][key2] ldist, udist = bounds_(path) lmat[idx1, idx2] = lmat[idx2, idx1] = ldist umat[idx1, idx2] = umat[idx2, idx1] = udist else: # they are disconnected lmat[idx1, idx2] = lmat[idx2, idx1] = closest_approach( gra, key1, key2) umat[idx1, idx2] = umat[idx2, idx1] = 999 assert lmat[idx1, idx2] <= umat[idx1, idx2], ( "Lower bound exceeds upper bound. This is a bug!\n" "{}\npath: {}\n" .format(string(gra, one_indexed=False), str(path))) return lmat, umat
def continue_connected_ring_systems(gra, keys, vma, zma_keys, rsys=None, check=True): """ generate the connected ring systems for a subset of keys, continuing on from a partial v-matrix The subset must have at least one neighbor that already exists in the v-matrix :param gra: the graph for which the v-matrix will be constructed :param keys: the subset of keys to be added to 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 rsys: optionally, pass the ring systems in to avoid recalculating """ gra = subgraph(gra, set(keys) | set(zma_keys)) sub = subgraph(gra, keys) if check: assert is_connected(gra), "Graph must be connected!" if rsys is None: rsys = sorted(ring_systems(sub), key=atom_count) rsys = list(rsys) while rsys: # Find the next ring system with a connection to the current # v-vmatrix and connect them conn = False for idx, rsy_keys in enumerate(map(atom_keys, rsys)): if set(zma_keys) & rsy_keys: # ring systems are connected by one bond -- no chain needed keys = set(zma_keys) & rsy_keys assert len(keys) == 1, ( "Attempting to add redundant keys to v-matrix: {}".format( str(keys))) key, = keys conn = True else: # see if the ring systems are connected by a chain keys = shortest_path_between_groups(gra, zma_keys, rsy_keys) # if so, build a bridge from the current v-matrix to this next # ring system vma, zma_keys = continue_chain(gra, keys[:-1], vma, zma_keys, term_hydrogens=False) key = keys[-1] conn = bool(keys is not None) if conn: rsy = rsys.pop(idx) break assert keys is not None, "This is a disconnected graph!" # 2. Decompose the ring system with the connecting ring first rng_keys = next(rks for rks in rings_atom_keys(rsy) if key in rks) keys_lst = ring_system_decomposed_atom_keys(rsy, rng_keys=rng_keys) # 3. Build the next ring system vma, zma_keys = continue_ring_system(gra, keys_lst, vma, zma_keys) return vma, zma_keys