# -*- coding: utf-8 -*-
"""
===============================================================================
Atom classes with force attributes (:mod:`sknano.core.atoms.force_atoms`)
===============================================================================
.. currentmodule:: sknano.core.atoms.force_atoms
"""
from __future__ import absolute_import, division, print_function
from __future__ import unicode_literals
__docformat__ = 'restructuredtext en'
from operator import attrgetter
import numbers
import numpy as np
from sknano.core.math import Vector, Vectors
from .atoms import Atom, Atoms
__all__ = ['ForceAtom', 'ForceAtoms']
[docs]class ForceAtom(Atom):
"""An `Atom` sub-class with force attributes.
Parameters
----------
element : {str, int}, optional
A string representation of the element symbol or an integer specifying
an element atomic number.
fx, fy, fz : float, optional
:math:`f_x, f_y, f_z` components of `ForceAtom` velocity.
"""
def __init__(self, *args, fx=None, fy=None, fz=None, **kwargs):
super().__init__(*args, **kwargs)
self._f = Vector([fx, fy, fz])
self.fmtstr = super().fmtstr + \
", fx={fx:.6f}, fy={fy:.6f}, fz={fz:.6f}"
# def __eq__(self, other):
# return self.f == other.f and super().__eq__(other)
# def __lt__(self, other):
# return (self.f < other.f and super().__le__(other)) or \
# (self.f <= other.f and super().__lt__(other))
def __eq__(self, other):
if not self._is_valid_operand(other):
return NotImplemented
return self.f == other.f and super().__eq__(other)
def __le__(self, other):
if not self._is_valid_operand(other):
return NotImplemented
if self.f > other.f or not super().__le__(other):
return False
return True
def __lt__(self, other):
if not self._is_valid_operand(other):
return NotImplemented
if self.f >= other.f or not super().__lt__(other):
return False
return True
def __ge__(self, other):
if not self._is_valid_operand(other):
return NotImplemented
if self.f < other.f or not super().__ge__(other):
return False
return True
def __gt__(self, other):
if not self._is_valid_operand(other):
return NotImplemented
if self.f <= other.f or not super().__gt__(other):
return False
return True
def __dir__(self):
attrs = super().__dir__()
attrs.extend(['fx', 'fy', 'fz'])
return attrs
@property
def fx(self):
""":math:`x` component of `ForceAtom` force vector"""
return self._f.x
@fx.setter
def fx(self, value):
"""Set :math:`f_x`.
Set :math:`f_x`, the :math:`x` component of `ForceAtom` force vector.
Parameters
----------
value : float
:math:`f_x` component of force vector.
"""
if not isinstance(value, numbers.Number):
raise TypeError('Expected a number')
self._f.x = value
@property
def fy(self):
""":math:`x` component of `ForceAtom` force vector"""
return self._f.y
@fy.setter
def fy(self, value):
"""Set :math:`f_y`.
Set :math:`f_y`, the :math:`y` component of `ForceAtom` force vector.
Parameters
----------
value : float
:math:`f_y` component of force vector.
"""
if not isinstance(value, numbers.Number):
raise TypeError('Expected a number')
self._f.y = value
@property
def fz(self):
""":math:`z` component of `ForceAtom` force vector"""
return self._f.z
@fz.setter
def fz(self, value):
"""Set :math:`f_z`.
Set :math:`f_z`, the :math:`z` component of `ForceAtom` force vector.
Parameters
----------
value : float
:math:`f_z` component of force vector.
"""
if not isinstance(value, numbers.Number):
raise TypeError('Expected a number')
self._f.z = value
@property
def f(self):
""":math:`f_x, f_y, f_z` array of force vector components.
Returns
-------
ndarray
3-element ndarray of [:math:`f_x`, :math:`f_y`, :math:`f_z`]
force vector components of `ForceAtom`.
"""
return self._f
@f.setter
def f(self, value):
"""Set :math:`x, y, z` components of `ForceAtom` force vector.
Parameters
----------
value : array_like
3-element ndarray of [:math:`f_x`, :math:`f_y`, :math:`f_z`]
force components of `ForceAtom`.
"""
if not isinstance(value, (list, np.ndarray)):
raise TypeError('Expected an array_like object')
self._f[:] = Vector(value, nd=3)
[docs] def rezero(self, epsilon=1.0e-10):
"""Re-zero position vector components.
Set position vector components with absolute value less than
`epsilon` to zero.
Parameters
----------
epsilon : float
smallest allowed absolute value of any :math:`x,y,z` component.
"""
self.f.rezero(epsilon)
super().rezero(epsilon)
[docs] def rotate(self, **kwargs):
"""Rotate `Atom` position vector.
Parameters
----------
angle : float
axis : :class:`~sknano.core.math.Vector`, optional
anchor_point : :class:`~sknano.core.math.Point`, optional
rot_point : :class:`~sknano.core.math.Point`, optional
from_vector, to_vector : :class:`~sknano.core.math.Vector`, optional
degrees : bool, optional
transform_matrix : :class:`~numpy:numpy.ndarray`
"""
self.f.rotate(**kwargs)
super().rotate(**kwargs)
[docs] def todict(self):
"""Return :class:`~python:dict` of constructor parameters."""
super_dict = super().todict()
super_dict.update(dict(fx=self.fx, fy=self.fy, fz=self.fz))
return super_dict
[docs]class ForceAtoms(Atoms):
"""An `Atoms` sub-class for `ForceAtom`\ s.
Sub-class of `Atoms` class, and a container class for lists of
:class:`~sknano.core.atoms.ForceAtom` instances.
Parameters
----------
atoms : {None, sequence, `ForceAtoms`}, optional
if not `None`, then a list of `ForceAtom` instance objects or an
existing `ForceAtoms` instance object.
"""
@property
def __atom_class__(self):
return ForceAtom
def sort(self, key=attrgetter('f'), reverse=False):
super().sort(key=key, reverse=reverse)
@property
def f(self):
""":class:`Vectors` of `ForceAtom` forces."""
return Vectors([atom.f for atom in self])
@property
def forces(self):
"""Alias for :attr:`~ForceAtoms.f`."""
return self.f
@property
def fx(self):
""":class:`~numpy:numpy.ndarray` of `Atom`\ s :math:`f_x` \
components."""
return self.f.x
@property
def fy(self):
""":class:`~numpy:numpy.ndarray` of `Atom`\ s :math:`f_y` \
components."""
return self.f.y
@property
def fz(self):
""":class:`~numpy:numpy.ndarray` of `Atom`\ s :math:`f_z` \
components."""
return self.f.z