Source code for directupsampling.sampling.settings

"""Settings for MD snapshot sampling."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import asdict, dataclass, field, replace
from typing import TYPE_CHECKING, Any

import ase.units
import numpy as np

from directupsampling.parallel import DummyMPI
from directupsampling.thermostats import LangevinGB

if TYPE_CHECKING:
    import pathlib
    from collections.abc import Callable

    from ase.md.md import MolecularDynamics


[docs] @dataclass class Setting:
[docs] def replace(self, **changes) -> Setting: return replace(self, **changes)
[docs] def todict(self) -> dict: return asdict(self)
[docs] @dataclass class ThermostatSetting(Setting): class_type: type[MolecularDynamics] = LangevinGB timestep: float = 1 * ase.units.fs friction: float = 0.01 / ase.units.fs fixcm: bool = True
[docs] def get_kwargs(self) -> dict: dct = self.todict() del dct["class_type"] return dct
[docs] @dataclass class MDLogSetting(Setting): interval: int = 1000 logfile: str = "-" header: bool = True stress: bool = False peratom: bool = True mode: str = "a" comm: Any = DummyMPI()
[docs] @dataclass class SamplingSetting(Setting): """Setting for MD snapshot sampling. Attributes ---------- seed : int, optional Random seed for sampling. If None, a random seed is generated. rng : np.random.Generator, optional Random number generator for sampling. If None, a new generator is created using the seed. sampling_interval : int Interval for MD timesteps to take samples. sampling_offset : int Offset for MD timesteps for equilibration. sample_atoms : bool Whether to sample the atomic positions and forces (through a copy of the ASE `Atoms` instance). If False, only kinetic and potential energy are sampled. file : str or pathlib.Path, optional File path to save the sampled snapshots. If None, snapshots are not saved to a file. If specified, snapshots are buffered and written to the file every `buffer_for_writing` samples. The written snapshots are discarded from the container to keep memory usage low. buffer_for_writing : int Number of samples to buffer before writing to the file. Ignored if `file` is None. thermostat_setting : ThermostatSetting Setting for the ASE style thermostat used in the MD simulation. log_md : bool or int Whether to log the MD simulation using ASE's MDLogger. If True, logging is done every `mdlogger_setting.interval` steps. If False, no logging is done. If an integer, logging is done every that many steps. mdlogger_setting : MDLogSetting Setting for the MDLogger used to log the MD simulation. attach : Callable or list of Callable Function(s) to attach to the MD simulation, passed through to the `attach` method of ASE's MD class. Notes ----- If `file` is specified, but the sampled snapshots are desired after the run, the file needs to be read with `SnapshotContainer.read`. """ seed: int | None = None rng: np.random.Generator = None sampling_offset: int = 1000 sampling_interval: int = 100 sample_atoms: bool = True file: str | pathlib.Path | None = None buffer_for_writing: int = 10 thermostat_setting: ThermostatSetting = field(default_factory=ThermostatSetting) log_md: bool | int = False mdlogger_setting: MDLogSetting = field(default_factory=MDLogSetting) attach: Callable | list[Callable] = field(default_factory=list) def __post_init__(self) -> None: """Post-initialization processing for SamplingSetting.""" if isinstance(self.thermostat_setting, dict): self.thermostat_setting = ThermostatSetting(**self.thermostat_setting) if isinstance(self.mdlogger_setting, dict): self.mdlogger_setting = MDLogSetting(**self.mdlogger_setting) if self.seed is None: self.seed = np.random.SeedSequence().entropy % 2**32 if self.rng is None: self.rng = np.random.default_rng(self.seed) if not isinstance(self.attach, list): self.attach = [self.attach]
[docs] @classmethod def from_any( cls, dct: dict | SamplingSetting | None = None, **kwargs: dict, ) -> SamplingSetting: """Create a SamplingSetting from a dict, an existing instance, or None. Parameters ---------- dct : dict, SamplingSetting, or None Source settings. Nested dicts for `thermostat_setting` and `mdlogger_setting` are automatically cast to their dataclass types. If already a SamplingSetting, returned as-is. \*\*kwargs Additional keyword arguments merged into `dct` (ignored when `dct` is already a SamplingSetting instance). Returns ------- SamplingSetting The created SamplingSetting instance. """ if isinstance(dct, cls): return dct if dct is None: dct = {} dct = dct.copy() dct.update(kwargs) return cls(**dct)