Source code for molsysmt.topology.get_rigid_fragments

"""Partitioning a covalent graph at explicitly selected active bonds."""

from smonitor import signal

from molsysmt._private.argdigest import arg_digest
from molsysmt._private.smonitor import ArgumentError


[docs] @signal(tags=["api", "topology"]) @arg_digest() def get_rigid_fragments( molecular_system, bond_indices=None, chemical_state="reference", structure_indices="all", skip_digestion=False, ): """Deriving rigid fragments by cutting explicitly selected covalent bonds. Parameters ---------- molecular_system : molecular system Molecular system in any supported form with a complete covalent graph. bond_indices : int, list, tuple or numpy.ndarray, default=None Source bond indices (0-based) to cut. None cuts no bonds. Repeated indices are deduplicated. Dative and ring cuts are rejected. chemical_state : str, int or None, default='reference' Chemical state supplying connectivity. 'structure' resolves the state assigned to the selected structures; these must agree on one state. structure_indices : int, list, tuple or numpy.ndarray, default='all' Structure indices (0-based) used only for chemical-state resolution. skip_digestion : bool, default=False Whether to skip argument digestion. Default is False. Returns ------- dict Packed int64 fragment_atom_indices and fragment_offsets, a fragment index for each source atom (atom_fragment_indices), selected source bond_indices and bonded_atom_pairs, and aligned fragment_pairs. Also includes atom_indices, chemical_state_index and connectivity evidence. Fragments and their memberships are sorted by source atom index. Raises ------ ArgumentError If a cut is out of range, noncovalent, or does not separate its endpoints. StructuralInconsistencyError If complete, valid chemical connectivity is unavailable. Notes ----- Rigidity here means connectivity after the supplied cuts. This function does not classify chemically rotatable bonds, impose torsion angles or root a PDBQT tree. Bond orders are not needed for explicit graph cuts. Every selected bond must be a bridge in the original covalent graph; cutting multiple ring edges does not make them eligible torsions. Coordinates and the source chemical state are not modified. See Also -------- get_covalent_blocks : Find connected atom sets after removing atom pairs. get_rings : Perceive a minimum cycle basis from declared connectivity. Examples -------- >>> import molsysmt as msm >>> import pandas as pd >>> from molsysmt.native import Topology >>> topology = Topology(n_atoms=3) >>> topology.bonds = pd.DataFrame({'atom1_index': [0, 1], ... 'atom2_index': [1, 2], 'bond_type': ['covalent', 'covalent']}) >>> topology._chemical_states[0].connectivity_completeness = 'complete' >>> result = msm.topology.get_rigid_fragments(topology, bond_indices=[1]) >>> result['fragment_atom_indices'].tolist(), result['fragment_offsets'].tolist() ([0, 1, 2], [0, 2, 3]) .. admonition:: Tutorial with more examples See :ref:`Tutorial_Rigid_Fragments` for explicit graph cuts. .. versionadded:: 1.0.0 """ import networkx as nx import numpy as np from molsysmt.topology._chemical_graph import chemical_graph_context caller = "molsysmt.topology.get_rigid_fragments" _, states, state, state_index, covalent, pairs, _ = chemical_graph_context( molecular_system, chemical_state, structure_indices, False, caller ) cuts = np.asarray([] if bond_indices is None else bond_indices) if cuts.ndim != 1 or (cuts.size and cuts.dtype.kind not in "iu"): raise ArgumentError("bond_indices", value=bond_indices, caller=caller) cuts = np.unique(cuts).astype(np.int64) if ( np.any(cuts < 0) or np.any(cuts >= len(state.bonds)) or not np.isin(cuts, covalent.index).all() ): raise ArgumentError("bond_indices", value=bond_indices, caller=caller) cut_pairs = ( state.bonds.loc[cuts, ["atom1_index", "atom2_index"]] .to_numpy(dtype=np.int64) .reshape(-1, 2) ) graph = nx.Graph() graph.add_nodes_from(range(states.n_atoms)) graph.add_edges_from(pairs) if len(cuts): bridges = {tuple(sorted(pair)) for pair in nx.bridges(graph)} if any(tuple(sorted(pair)) not in bridges for pair in cut_pairs): raise ArgumentError( "bond_indices", value=bond_indices, caller=caller, message="Each active cut must be a bridge of the original covalent graph.", ) graph.remove_edges_from(cut_pairs) blocks = nx.connected_components(graph) fragments = sorted((sorted(block) for block in blocks), key=lambda block: block[0]) packed = np.asarray([atom for block in fragments for atom in block], dtype=np.int64) offsets = np.asarray( [0, *np.cumsum([len(block) for block in fragments])], dtype=np.int64 ) labels = np.empty(states.n_atoms, dtype=np.int64) for index, block in enumerate(fragments): labels[block] = index return { "atom_indices": np.arange(states.n_atoms, dtype=np.int64), "fragment_atom_indices": packed, "fragment_offsets": offsets, "atom_fragment_indices": labels, "bond_indices": cuts, "bonded_atom_pairs": cut_pairs, "fragment_pairs": labels[cut_pairs], "chemical_state_index": state_index, "connectivity_completeness": state.connectivity_completeness, "evidence": "declared_graph_with_explicit_cuts", }