# -*- coding: utf-8 -*-
# Copyright (c) 2015-2022, Exa Analytics Development Team
# Distributed under the terms of the Apache License 2.0
"""
Frame Data
######################
The primary "coordinate" for the atomic container (:class:`~exatomic.container.Universe`)
is the "frame". The frame concept can be anything; time, step along a geometry
optimization, different functional, etc. Each frame is distinguished from other
frames by unique atomic coordinates, a different level of theory, etc.
"""
import numpy as np
from exatomic.exa import DataFrame
from exatomic.algorithms.distance import cartmag
[docs]class Frame(DataFrame):
"""
Information about the current frame; a frame is a concept that distinguishes
atomic coordinates along a molecular dynamics simulation, geometry optimization,
etc.
+-------------------+----------+-------------------------------------------+
| Column | Type | Description |
+===================+==========+===========================================+
| atom_count | int | non-unique integer (req.) |
+-------------------+----------+-------------------------------------------+
| molecule_count | int | non-unique integer |
+-------------------+----------+-------------------------------------------+
| ox | float | unit cell origin point in x |
+-------------------+----------+-------------------------------------------+
| oy | float | unit cell origin point in y |
+-------------------+----------+-------------------------------------------+
| oz | float | unit cell origin point in z |
+-------------------+----------+-------------------------------------------+
| periodic | bool | true if periodic system |
+-------------------+----------+-------------------------------------------+
"""
_index = 'frame'
_columns = ['atom_count']
# @property
# def _constructor(self):
# return Frame
[docs] def is_periodic(self, how='all'):
"""
Check if any/all frames are periodic.
Args:
how (str): Require "any" frames to be periodic ("all" default)
Returns:
result (bool): True if any/all frame are periodic
"""
if 'periodic' in self:
if how == 'all' and np.all(self['periodic'] == True):
return True
elif how == 'any' and np.any(self['periodic'] == True):
return True
return False
[docs] def is_variable_cell(self, how='all'):
"""
Check if the simulation cell (applicable to periodic simulations) varies
(e.g. variable cell molecular dynamics).
"""
if self.is_periodic:
if 'rx' not in self.columns:
self.compute_cell_magnitudes()
rx = self['rx'].min()
ry = self['ry'].min()
rz = self['rz'].min()
if np.allclose(self['rx'], rx) and np.allclose(self['ry'], ry) and np.allclose(self['rz'], rz):
return False
else:
return True
raise PeriodicUniverseError()
[docs] def compute_cell_magnitudes(self):
"""
Compute the magnitudes of the unit cell vectors (rx, ry, rz).
"""
self['rx'] = cartmag(self['xi'].values, self['yi'].values, self['zi'].values)
self['ry'] = cartmag(self['xj'].values, self['yj'].values, self['zj'].values)
self['rz'] = cartmag(self['xk'].values, self['yk'].values, self['zk'].values)
[docs] def orthorhombic(self):
if "xi" in self.columns and np.allclose(self["xj"], 0.0):
return True
return False
[docs]def compute_frame(universe):
"""
Compute (minmal) :class:`~exatomic.frame.Frame` from
:class:`~exatomic.container.Universe`.
Args:
uni (:class:`~exatomic.container.Universe`): Universe with atom table
Returns:
frame (:class:`~exatomic.frame.Frame`): Minimal frame table
"""
return compute_frame_from_atom(universe.atom)
[docs]def compute_frame_from_atom(atom):
"""
Compute :class:`~exatomic.frame.Frame` from :class:`~exatomic.atom.Atom`
(or related).
Args:
atom (:class:`~exatomic.atom.Atom`): Atom table
Returns:
frame (:class:`~exatomic.frame.Frame`): Minimal frame table
"""
frame = atom.cardinal_groupby().size().to_frame()
frame.index = frame.index.astype(np.int64)
frame.columns = ['atom_count']
return Frame(frame)