Source code for molsysmt.pbc.wrap_to_pbc

import numpy as np

from molsysmt import pyunitwizard as puw
from molsysmt._private import rust_backend as _kernels
from molsysmt._private.argdigest import arg_digest


[docs] @arg_digest() def wrap_to_pbc( molecular_system, selection="all", structure_indices="all", box_origin="[0,0,0] nanometers", box_center=None, center_of_selection=None, weights=None, center_coordinates="[0,0,0] nanometers", compact="component", syntax="MolSysMT", engine="MolSysMT", in_place=False, skip_digestion=False, ): """ Wrap coordinates into the primary periodic box. Parameters ---------- molecular_system : molecular system Molecular system in any supported MolSysMT format. selection : str, list, tuple, or numpy.ndarray, default='all' Selection string or boolean/integer array specifying elements. structure_indices : int, list, tuple, or numpy.ndarray, default='all' Structure indices (0-based) to include or process. box_origin : PyUnitWizard quantity, default='[0,0,0] nanometers' Periodic box origin in units of length. box_center : PyUnitWizard quantity or None, default=None Periodic box center in units of length; `None` derives it from the box. center_of_selection : str, list, tuple, or numpy.ndarray, or None, default=None Atoms whose center defines the translation reference. weights : numpy.ndarray, list, or tuple, default=None Atomic mass weights array for center calculation. center_coordinates : PyUnitWizard quantity or None, default='[0,0,0] nanometers' Coordinates to which the selected center is translated, in units of length. compact : False or str, default='component' Element kept whole when wrapping. ``'component'`` translates each covalent component as one piece, so no bond is stretched across the cell. ``False`` wraps every atom independently, which is what a simulation engine does and what leaves a molecule split across opposite faces. syntax : str, default='MolSysMT' Selection syntax used to evaluate `selection` (e.g., 'MolSysMT', 'MDTraj'). engine : str, default='MolSysMT' Backend used to perform the calculation. in_place : bool, default=False Whether to modify the input molecular system in place. skip_digestion : bool, default=False Whether to skip MolSysMT's internal argument digestion mechanism. Returns ------- molecular system or None Wrapped system when `in_place=False`, otherwise `None`. Raises ------ NotImplementedMethodError If an unsupported engine is requested. StructuralInconsistencyError If box vectors are invalid, or if ``compact`` names an element and bonds are unavailable. Notes ----- With ``compact=False``, atoms are wrapped independently. With ``compact='component'``, bonded blocks are reconstructed with minimum-image bond displacements and each block center is wrapped into the requested cell. .. versionadded:: 1.0.0 """ if engine == "MolSysMT": from molsysmt.basic import copy, get, select, set from molsysmt.structure import center atom_indices = select(molecular_system, selection=selection, syntax=syntax) if center_of_selection is not None: molecular_system = center( molecular_system, selection=atom_indices, center_of_selection=center_of_selection, weights=weights, center_coordinates=center_coordinates, syntax=syntax, in_place=False, skip_digestion=True, ) coordinates = get( molecular_system, element="atom", selection=atom_indices, structure_indices=structure_indices, coordinates=True, skip_digestion=True, ) box = get( molecular_system, element="system", structure_indices=structure_indices, box=True, skip_digestion=True, ) original_length_units = puw.get_unit(coordinates) coordinates, length_units = puw.get_value_and_unit( coordinates, standardized=True ) coordinates = np.asarray(coordinates, dtype=np.float64) from molsysmt._private.pbc_validation import validate_box_array if box is None: validate_box_array( box, coordinates.shape[0], caller="molsysmt.pbc.wrap_to_pbc", ) box = puw.get_value(box, standardized=True) box = validate_box_array( box, coordinates.shape[0], caller="molsysmt.pbc.wrap_to_pbc", ) # With no bonds every atom is a covalent component of its own, so compacting by # component is atom-wise wrapping. That is the degenerate answer rather than a # fallback, and it is taken through the atom-wise kernel because walking one # block per atom would cost far more than the operation it stands for. compact_pairs = None if compact: from molsysmt._private.pbc_reconstruction import localize_bonded_pairs from molsysmt._private.smonitor import ( NotImplementedConversionError, NotImplementedMethodError, NotSupportedFormError, StructuralInconsistencyError, ) try: bonded_pairs = get( molecular_system, element="atom", selection=atom_indices, inner_bonded_atom_pairs=True, skip_digestion=True, ) except ( NotImplementedConversionError, NotImplementedMethodError, NotSupportedFormError, ) as error: raise StructuralInconsistencyError( reason="compact requires a topology that provides bonds.", caller="molsysmt.pbc.wrap_to_pbc", ) from error if bonded_pairs is not None and len(bonded_pairs) > 0: compact_pairs = localize_bonded_pairs(atom_indices, bonded_pairs) if compact_pairs is not None: from molsysmt._private.pbc_reconstruction import ( reconstruct_and_wrap_covalent_blocks, ) bonded_pairs = compact_pairs if box_center is None: origin = np.asarray( puw.get_value(box_origin, standardized=True), dtype=np.float64 ) mode = "pbc" else: origin = np.asarray( puw.get_value(box_center, standardized=True), dtype=np.float64 ) mode = "pbc_center" reconstruct_and_wrap_covalent_blocks( coordinates, box, bonded_pairs, origin=origin, mode=mode, ) elif box_center is None: box_origin = puw.get_value(box_origin, standardized=True) box_origin = np.asarray(box_origin, dtype=np.float64) if np.all(np.isclose(box_origin, 0, atol=1e-4)): box_origin = np.zeros((3), dtype=np.float64) _kernels.wrap_to_pbc(coordinates, box, box_origin) else: box_center = puw.get_value(box_center, standardized=True) box_center = np.asarray(box_center, dtype=np.float64) if np.all(np.isclose(box_center, 0, atol=1e-4)): box_origin = np.zeros((3), dtype=np.float64) _kernels.wrap_to_pbc_center(coordinates, box, box_center) coordinates = puw.quantity(coordinates, length_units) coordinates = puw.convert(coordinates, to_unit=original_length_units) del box else: raise NotImplementedMethodError() if in_place: set( molecular_system, selection=atom_indices, structure_indices=structure_indices, syntax=syntax, coordinates=coordinates, ) del (coordinates, atom_indices, structure_indices) pass else: tmp_molecular_system = copy(molecular_system) set( tmp_molecular_system, selection=atom_indices, structure_indices=structure_indices, syntax=syntax, coordinates=coordinates, ) del (coordinates, atom_indices, structure_indices) return tmp_molecular_system