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 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
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