Source code for mchammer.data_containers.kinetic_data_container
"""
This module provides the KineticDataContainer class.
"""
from typing import Any
from .base_data_container import BaseDataContainer
[docs]
class KineticDataContainer(BaseDataContainer):
"""Data container for kinetic Monte Carlo simulations.
In addition to the machinery of :class:`BaseDataContainer` this
container persists the physical time and the particle tracking state,
which cannot be reconstructed from stored occupations.
A restart that lost either would silently corrupt every transport
observable.
The step counter :attr:`mctrial` counts hops, and the physical time in
seconds is an ordinary column of the ensemble data.
"""
def _update_last_state(self,
last_step: int,
occupations: list[int],
accepted_trials: int,
random_state: Any,
generator_state: Any,
time: float,
particle_ids: list[int],
displacements: list[list[float]],
hop_counts: dict,
clamp_count: int) -> None:
"""Updates the last state of the kinetic Monte Carlo simulation.
Every field is required, since a restart reads all of them and a
state that carries only some of them is of no use.
Parameters
----------
last_step
Last step (hop counter).
occupations
Occupation vector observed during the last step.
accepted_trials
Number of accepted steps since the last write of ensemble
data, from which the acceptance ratio of an interval is
computed.
Every kinetic Monte Carlo step is accepted, so the number
equals the number of steps of the interval.
random_state
State of the generator the :mod:`random` module shares.
generator_state
State of the generator of the ensemble, which is what the
sequence of a run is drawn from.
time
Physical time in seconds.
particle_ids
Particle identity at each site.
displacements
Accumulated unwrapped displacement of each particle.
hop_counts
Accumulated number of hops per chemical symbol.
clamp_count
Number of rate evaluations so far in which the kinetically
resolved activation energy was raised to the clamp at half
the magnitude of the energy difference.
"""
super()._update_last_state(
last_step=last_step,
occupations=occupations,
accepted_trials=accepted_trials,
random_state=random_state)
self._last_state['generator_state'] = generator_state
self._last_state['time'] = time
self._last_state['particle_ids'] = particle_ids
self._last_state['displacements'] = displacements
self._last_state['hop_counts'] = hop_counts
self._last_state['clamp_count'] = clamp_count
[docs]
def get_mean_squared_displacement(self, species: str) -> tuple:
"""Returns time and mean squared displacement for a species.
Parameters
----------
species
Chemical symbol of the species.
"""
tag = f'msd_{species}'
if tag not in self.data.columns:
raise ValueError(f'No mean squared displacement data for {species}.'
f' Available columns: {list(self.data.columns)}')
rows = self.data[['time', tag]].dropna()
return rows['time'].to_numpy(), rows[tag].to_numpy()
[docs]
def get_hop_counts(self) -> dict:
"""Returns the accumulated number of hops per species."""
counts = {}
for column in self.data.columns:
if column.startswith('hops_'):
rows = self.data[column].dropna()
if len(rows):
counts[column.removeprefix('hops_')] = int(rows.iloc[-1])
return counts