# coding: utf-8
# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
# Distributed under the terms of "New BSD License", see the LICENSE file.
import numpy as np
from pyiron_base import DataContainer, FlattenedStorage
from pyiron_snippets.deprecate import deprecate
__author__ = "Joerg Neugebauer, Sam Waseda"
__copyright__ = (
"Copyright 2021, Max-Planck-Institut für Eisenforschung GmbH - "
"Computational Materials Design (CM) Department"
)
__version__ = "1.0"
__maintainer__ = "Sam Waseda"
__email__ = "waseda@mpie.de"
__status__ = "production"
__date__ = "Sep 1, 2017"
[docs]
class NeighborsTrajectory(DataContainer):
"""
This class generates the neighbors for a given atomistic trajectory. The resulting indices, distances, and vectors
are stored as numpy arrays.
"""
def __new__(cls, *args, **kwargs):
instance = super().__new__(cls, *args, **kwargs)
object.__setattr__(instance, "_has_structure", None)
return instance
[docs]
def __init__(
self,
init=None,
has_structure=None,
num_neighbors=12,
table_name="neighbors_traj",
store=None,
**kwargs,
):
"""
Args:
has_structure (:class:`.HasStructure`): object containing the structures to compute the neighbors on
num_neighbors (int): The cutoff for the number of neighbors
table_name (str): Table name for the base `DataContainer` (stores this object as a group in a HDF5 file with
this name)
store (FlattenedStorage): internal storage that should be used to store the neighborhood information,
creates a new one if not provided; if provided and not empty it must be compatible
with the lengths of the structures in `has_structure`, but this is *not* checked
**kwargs (dict): Additional arguments to be passed to the `get_neighbors()` routine
(eg. cutoff_radius, norm_order , etc.)
"""
super().__init__(init=init, table_name=table_name)
self._flat_store = store if store is not None else FlattenedStorage()
self._flat_store.add_array(
"indices", dtype=np.int64, shape=(num_neighbors,), per="element", fill=-1
)
self._flat_store.add_array(
"distances", dtype=np.float64, shape=(num_neighbors,), per="element"
)
self._flat_store.add_array(
"vecs", dtype=np.float64, shape=(num_neighbors, 3), per="element"
)
self._flat_store.add_array(
"shells", dtype=np.int64, shape=(num_neighbors,), per="element"
)
self._num_neighbors = num_neighbors
self._get_neighbors_kwargs = kwargs
self.has_structure = has_structure
@property
def has_structure(self):
return self._has_structure
@has_structure.setter
def has_structure(self, value):
if value is not None:
self._has_structure = value
self._compute_neighbors()
@property
def indices(self):
"""
Neighbour indices (excluding itself) of each atom computed using the get_neighbors_traj() method
If the structures have different number of atoms, the array will have -1 on indices that are invalid.
Returns:
numpy.ndarray: An int array of dimension N_steps / stride x N_atoms x N_neighbors
"""
return self._flat_store.get_array_filled("indices")
@property
def distances(self):
"""
Neighbour distances (excluding itself) of each atom computed using the get_neighbors_traj() method
If the structures have different number of atoms, the array will have NaN on indices that are invalid.
Returns:
numpy.ndarray: A float array of dimension N_steps / stride x N_atoms x N_neighbors
"""
return self._flat_store.get_array_filled("distances")
@property
def vecs(self):
"""
Neighbour vectors (excluding itself) of each atom computed using the get_neighbors_traj() method
If the structures have different number of atoms, the array will have NaN on indices that are invalid.
Returns:
numpy.ndarray: A float array of dimension N_steps / stride x N_atoms x N_neighbors x 3
"""
return self._flat_store.get_array_filled("vecs")
@property
def shells(self):
"""
Neighbor shell indices (excluding itself) of each atom computed using the get_neighbors_traj() method.
For trajectories with non constant amount of particles this array may contain -1 for invalid values, i.e.
Returns:
ndarray: An int array of dimension N_steps / stride x N_atoms x N_neighbors x 3
"""
return self._flat_store.get_array_filled("shells")
@property
def num_neighbors(self):
"""
The maximum number of neighbors to be computed
Returns:
int: The max number of neighbors
"""
return self._num_neighbors
def _compute_neighbors(self):
for i, struct in enumerate(self._has_structure.iter_structures()):
if (
i < len(self._flat_store)
and (self._flat_store["indices", i] != -1).all()
):
# store already has valid entries for this structure, so skip it
continue
# Change the `allow_ragged` based on the changes in get_neighbors()
neigh = struct.get_neighbors(
num_neighbors=self._num_neighbors,
allow_ragged=False,
**self._get_neighbors_kwargs,
)
if i >= len(self._flat_store):
self._flat_store.add_chunk(
len(struct),
indices=neigh.indices,
distances=neigh.distances,
vecs=neigh.vecs,
shells=neigh.shells,
)
else:
self._flat_store.set_array("indices", i, neigh.indices)
self._flat_store.set_array("distances", i, neigh.distances)
self._flat_store.set_array("vecs", i, neigh.vecs)
self._flat_store.set_array("shells", i, neigh.shells)
return (
self._flat_store.get_array_filled("indices"),
self._flat_store.get_array_filled("distances"),
self._flat_store.get_array_filled("vecs"),
)
[docs]
@deprecate(
"This has no effect, neighbors are automatically called on instantiation."
)
def compute_neighbors(self):
"""
Compute the neighbors across the trajectory
"""
pass