Source code for molsysmt.structure.get_center

from molsysmt._private.smonitor import NotImplementedMethodError
from smonitor import signal
from molsysmt._private.argdigest import arg_digest
from molsysmt._private import rust_backend as _kernels
from molsysmt.lib.structure._kernel_inputs import extract_coordinates_value_and_unit
from molsysmt._private.variables import is_all, is_iterable_of_iterables
from molsysmt._private.execution import Reducer
from molsysmt._private.weighted_geometry import prepare_weights
from molsysmt import pyunitwizard as puw
import numpy as np
import gc


class _CenterReducer(Reducer):
    """
    Reducer for get_center heavy path.
    Accumulates per-chunk center arrays and concatenates them on finalize.
    """

    def __init__(self, weights, atoms_per_group=None):
        self._weights = weights
        self._atoms_per_group = atoms_per_group
        self._n_groups = 1 if atoms_per_group is None else len(atoms_per_group)
        self._chunks = []

    def initialize(self, metadata):
        self._chunks = []

    def consume(self, chunk):
        coords = chunk['coordinates']  # (chunk_size, n_atoms, 3), float64, read-only
        coords_w = np.array(coords, dtype=np.float64)  # writable copy for kernel
        if self._atoms_per_group is None:
            result = _kernels.get_center(coords_w, self._weights)
        else:
            result = _kernels.get_center_groups_of_atoms(coords_w, self._atoms_per_group, self._weights)
        self._chunks.append(result)

    def finalize(self):
        return np.concatenate(self._chunks, axis=0)

    # --- optional extensions ---

    def checkpoint(self):
        return {'chunks': [c.tolist() for c in self._chunks]}

    def restore(self, state):
        self._chunks = [np.array(c, dtype=np.float64) for c in state['chunks']]

    def merge(self, other):
        self._chunks.extend(other._chunks)


from molsysmt.configure import with_configure_overrides

[docs] @signal(tags=['api', 'structure']) @arg_digest() @with_configure_overrides def get_center(molecular_system, selection='all', weights=None, structure_indices='all', syntax='MolSysMT', engine='MolSysMT', heavy_mode='auto', parallel=None, num_threads=None, skip_digestion=False): """ Computing centers (centroids or weighted centers) of atom selections. Parameters ---------- molecular_system : molecular system Input system. selection : str, list, tuple or numpy.ndarray, default 'all' Atoms (or groups of atoms) to center; nested iterables are treated as groups. weights : array-like or 'masses', optional Non-negative weights per atom (or per group) when computing centers. Use ``'masses'`` to compute centers of mass. Every group must have a positive total weight. structure_indices : 'all' or array-like, default 'all' Structures/frames over which centers are computed. syntax : str, default 'MolSysMT' Selection syntax when using strings. engine : {'MolSysMT'}, default 'MolSysMT' Backend. heavy_mode : str, default 'auto' Chunked execution mode: 'auto' | 'force' | 'off'. parallel : bool or str, optional Parallel mode override: True | False | 'auto'. num_threads : int, optional Number of threads override. skip_digestion : bool, default False Whether to skip argument digestion. Returns ------- quantity Centers as a PyUnitWizard quantity in length units. Raises ------ ArgumentError If the atom or frame selection is empty, or weights are non-finite, negative, or have zero total weight. ArgumentLengthError If the number of weights does not match the selected atoms. NotImplementedMethodError If an unsupported engine is requested. Examples -------- >>> import molsysmt as msm >>> molsys = msm.convert(msm.systems['alanine dipeptide']['alanine_dipeptide.h5msm'], to_form='molsysmt.MolSys') >>> msm.structure.get_center(molsys, weights='masses').shape (1, 1, 3) .. versionadded:: 1.0.0 """ from molsysmt.basic import select, get from molsysmt._private.structure_indices import ensure_nonempty_structure_indices ensure_nonempty_structure_indices( structure_indices, caller="molsysmt.structure.get_center", ) if engine == 'MolSysMT': atom_indices = select(molecular_system, selection=selection) if not is_iterable_of_iterables(atom_indices): n_atoms = len(np.atleast_1d(atom_indices)) if not is_all(atom_indices) else \ get(molecular_system, element='system', n_atoms=True) n_structures = get(molecular_system, element='system', n_structures=True) weights_arr = prepare_weights( weights, n_atoms, molecular_system=molecular_system, selection=atom_indices, syntax=syntax, caller="molsysmt.structure.get_center", ) from molsysmt._private.execution import ChunkedExecutor from molsysmt._private.execution.memory_policy import estimate_footprint, decide_mode from molsysmt.basic import get_form form = get_form(molecular_system) footprint = estimate_footprint(n_atoms, n_structures) mode = decide_mode(footprint, heavy_mode) if mode == 'heavy': reducer = _CenterReducer(weights=weights_arr) executor = ChunkedExecutor( molecular_system=molecular_system, form=form, operation='get_center', reducer=reducer, atom_indices=atom_indices, structure_indices=None if is_all(structure_indices) else structure_indices, heavy_mode=heavy_mode, attributes=['coordinates'], ) center_val = executor.execute() # (n_structures, 1, 3), float64, nm length_unit = puw.get_standard_units(dimensionality={'[L]': 1}) center = puw.quantity(center_val, length_unit) else: coordinates = get(molecular_system, element='atom', selection=atom_indices, structure_indices=structure_indices, coordinates=True) coordinates, length_unit = extract_coordinates_value_and_unit(coordinates) center = _kernels.get_center(coordinates, weights_arr) center = puw.quantity(center, length_unit) del coordinates, length_unit else: atoms_per_group = np.array([len(group) for group in atom_indices], dtype=np.int64) groups_of_atoms = np.concatenate(atom_indices) n_atoms_flat = len(groups_of_atoms) n_structures = get(molecular_system, element='system', n_structures=True) if weights is not None and not isinstance(weights, str): if is_iterable_of_iterables(weights): weights = np.concatenate(weights) weights_arr = prepare_weights( weights, n_atoms_flat, molecular_system=molecular_system, selection=groups_of_atoms, syntax=syntax, group_sizes=atoms_per_group, caller="molsysmt.structure.get_center", ) from molsysmt._private.execution.memory_policy import estimate_footprint, decide_mode from molsysmt.basic import get_form form = get_form(molecular_system) footprint = estimate_footprint(n_atoms_flat, n_structures) mode = decide_mode(footprint, heavy_mode) if mode == 'heavy': reducer = _CenterReducer(weights=weights_arr, atoms_per_group=atoms_per_group) from molsysmt._private.execution import ChunkedExecutor executor = ChunkedExecutor( molecular_system=molecular_system, form=form, operation='get_center', reducer=reducer, atom_indices=groups_of_atoms, structure_indices=None if is_all(structure_indices) else structure_indices, heavy_mode=heavy_mode, attributes=['coordinates'], ) center_val = executor.execute() # (n_structures, n_groups, 3) length_unit = puw.get_standard_units(dimensionality={'[L]': 1}) center = puw.quantity(center_val, length_unit) else: coordinates = get(molecular_system, element='atom', selection=groups_of_atoms, structure_indices=structure_indices, coordinates=True) coordinates, length_unit = extract_coordinates_value_and_unit(coordinates) center = _kernels.get_center_groups_of_atoms(coordinates, atoms_per_group, weights_arr) center = puw.quantity(center, length_unit) del coordinates, length_unit, groups_of_atoms, weights_arr center = puw.standardize(center) gc.collect() return center else: raise NotImplementedMethodError()