Source code for sknano.core.atoms._xyz_atoms

# -*- coding: utf-8 -*-
"""
===============================================================================
Atom classes with x, y, z attributes (:mod:`sknano.core.atoms._xyz_atoms`)
===============================================================================

.. currentmodule:: sknano.core.atoms._xyz_atoms

"""
from __future__ import absolute_import, division, print_function
from __future__ import unicode_literals

__docformat__ = 'restructuredtext en'

from collections import OrderedDict
from functools import total_ordering
from operator import attrgetter
import numbers
import numpy as np

from sknano.core import xyz
from sknano.core.math import Vector
from sknano.core.geometric_regions import Cuboid  # , Rectangle

from ._atoms import Atom, Atoms

__all__ = ['XYZAtom', 'XYZAtoms']


@total_ordering
[docs]class XYZAtom(Atom): """An `Atom` class with x, y, z attributes. Parameters ---------- element : {str, int}, optional A string representation of the element symbol or an integer specifying an element atomic number. x, y, z : float, optional :math:`x, y, z` components of `XYZAtom` position vector relative to origin. """ def __init__(self, *args, x=None, y=None, z=None, **kwargs): super().__init__(*args, **kwargs) self._r0 = Vector([x, y, z]) self._r = Vector([x, y, z]) self.fmtstr = super().fmtstr + ", x={x:.6f}, y={y:.6f}, z={z:.6f}" def __eq__(self, other): return self.r == other.r and super().__eq__(other) def __lt__(self, other): return (self.r < other.r and super().__le__(other)) or \ (self.r <= other.r and super().__lt__(other)) def __dir__(self): attrs = super().__dir__() attrs.extend(['x', 'y', 'z']) return attrs @property def x(self): """:math:`x`-coordinate in units of **Angstroms**. Returns ------- float :math:`x`-coordinate in units of **Angstroms**. """ return self._r.x @x.setter def x(self, value): """Set `Atom` :math:`x`-coordinate in units of **Angstroms**. Parameters ---------- value : float :math:`x`-coordinate in units of **Angstroms**. """ if not isinstance(value, numbers.Number): raise TypeError('Expected a number') self._r.x = value @property def y(self): """:math:`y`-coordinate in units of **Angstroms**. Returns ------- float :math:`y`-coordinate in units of **Angstroms**. """ return self._r.y @y.setter def y(self, value): """Set `Atom` :math:`y`-coordinate in units of **Angstroms**. Parameters ---------- value : float :math:`y`-coordinate in units of **Angstroms**. """ if not isinstance(value, numbers.Number): raise TypeError('Expected a number') self._r.y = value @property def z(self): """:math:`z`-coordinate in units of **Angstroms**. Returns ------- float :math:`z`-coordinate in units of **Angstroms**. """ return self._r.z @z.setter def z(self, value): """Set `Atom` :math:`z`-coordinate in units of **Angstroms**. Parameters ---------- value : float :math:`z`-coordinate in units of **Angstroms**. """ if not isinstance(value, numbers.Number): raise TypeError('Expected a number') self._r.z = value @property def r(self): """:math:`x, y, z` components of `Atom` position vector. Returns ------- ndarray 3-element ndarray of [:math:`x, y, z`] coordinates of `Atom`. """ return self._r @r.setter def r(self, value): """Set :math:`x, y, z` components of `Atom` position vector. Parameters ---------- value : array_like :math:`x, y, z` coordinates of `Atom` position vector relative to the origin. """ if not isinstance(value, (list, np.ndarray)): raise TypeError('Expected an array_like object') self._r[:] = Vector(value, nd=3) @property def r0(self): """:math:`x, y, z` components of `Atom` position vector at t=0. Returns ------- ndarray 3-element ndarray of [:math:`x, y, z`] coordinates of `Atom`. """ return self._r0 @r0.setter def r0(self, value): """Set :math:`x, y, z` components of `Atom` position vector at t=0. Parameters ---------- value : array_like :math:`x, y, z` coordinates of `Atom` position vector relative to the origin. """ if not isinstance(value, (list, np.ndarray)): raise TypeError('Expected an array_like object') self._r0[:] = Vector(value, nd=3) @property def dr(self): """:math:`x, y, z` components of `Atom` displacement vector. Returns ------- ndarray 3-element ndarray of [:math:`x, y, z`] coordinates of `Atom`. """ return self.r - self.r0
[docs] def get_coords(self, asdict=False): """Return atom coords. Parameters ---------- asdict : bool, optional Returns ------- coords : :class:`~python:collections.OrderedDict` or ndarray """ if asdict: return OrderedDict(list(zip(xyz, self.r))) else: return self.r
[docs] def rezero(self, epsilon=1.0e-10): """Alias for :meth:`Atom.rezero_xyz`, but calls `super` class \ `rezero` method as well.""" self.r.rezero(epsilon) super().rezero(epsilon)
[docs] def rezero_coords(self, epsilon=1.0e-10): """Alias for :meth:`Atom.rezero_xyz`.""" self.rezero_xyz(epsilon=epsilon)
[docs] def rezero_xyz(self, epsilon=1.0e-10): """Re-zero position vector components. Set position vector components with absolute value less than `epsilon` to zero. Unlike the :meth:`Atom.rezero` method, this method does **not** call the super class `rezero` method. Parameters ---------- epsilon : float smallest allowed absolute value of any :math:`x,y,z` component. """ self.r.rezero(epsilon=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.r.rotate(**kwargs) self.r0.rotate(**kwargs) super().rotate(**kwargs)
[docs] def translate(self, t, fix_anchor_point=True): """Translate `Atom` position vector by :class:`Vector` `t`. Parameters ---------- t : :class:`Vector` fix_anchor_point : bool, optional """ # TODO compare timing benchmarks for translation of position vector. self.r.translate(t, fix_anchor_point=fix_anchor_point) self.r0.translate(t, fix_anchor_point=fix_anchor_point) super().translate(t, fix_anchor_point=fix_anchor_point)
# self.r += t
[docs] def todict(self): super_dict = super().todict() super_dict.update(dict(x=self.x, y=self.y, z=self.z)) return super_dict
[docs]class XYZAtoms(Atoms): """An `Atoms` sub-class for `XYZAtom`\ s. A container class for :class:`~sknano.core.atoms.XYZAtom` objects. Parameters ---------- atoms : {None, sequence, `XYZAtoms`}, optional if not `None`, then a list of `XYZAtom` instance objects or an existing `XYZAtoms` instance object. """ @property def __atom_class__(self): return XYZAtom
[docs] def sort(self, key=attrgetter('r'), reverse=False): super().sort(key=key, reverse=reverse)
@property def center_of_mass(self): """Center-of-Mass coordinates of `Atoms`. Computes the position vector of the center-of-mass coordinates: .. math:: \\mathbf{R}_{COM} = \\frac{1}{M}\\sum_{i=1}^{N_{\\mathrm{atoms}}} m_i\\mathbf{r}_i Returns ------- com : :class:`~sknano.core.math.Vector` The position vector of the center of mass coordinates. """ masses = np.asarray([self.masses]) coords = self.coords MxR = masses.T * coords com = Vector(np.sum(MxR, axis=0) / np.sum(masses)) com.rezero() return com @property def com(self): """Alias for :attr:`~XYZAtoms.center_of_mass`.""" return self.center_of_mass @property def CM(self): """Alias for :attr:`~XYZAtoms.center_of_mass`.""" return self.center_of_mass @property def centroid(self): """Centroid of `Atoms`. Computes the position vector of the centroid of the `Atoms` coordinates. .. math:: \\mathbf{C} = \\frac{\\sum_{i=1}^{N_{\\mathrm{atoms}}} \\mathbf{r}_i}{N_{\\mathrm{atoms}}} Returns ------- C : :class:`~sknano.core.math.Vector` The position vector of the centroid coordinates. """ C = Vector(np.mean(self.coords, axis=0)) C.rezero() return C @property def bounds(self): """Bounds of `Atoms`. Returns ------- :class:`~sknano.core.geometric_regions.Cuboid`""" return Cuboid(pmin=[self.x.min(), self.y.min(), self.z.min()], pmax=[self.x.max(), self.y.max(), self.z.max()]) @property def coords(self): """Alias for :attr:`Atoms.r`.""" return self.r @property def r(self): """:class:`~numpy:numpy.ndarray` of :attr:`Atom.r` position \ `Vector`\ s""" return np.asarray([atom.r for atom in self]) @property def dr(self): """:class:`~numpy:numpy.ndarray` of :attr:`Atom.dr` displacement \ `Vector`\ s""" return np.asarray([atom.dr for atom in self]) @property def x(self): """:class:`~numpy:numpy.ndarray` of `Atom`\ s :math:`x` coordinates.""" return self.r[:, 0] @property def y(self): """:class:`~numpy:numpy.ndarray` of `Atom`\ s :math:`y` coordinates.""" return self.r[:, 1] @property def z(self): """:class:`~numpy:numpy.ndarray` of `Atom`\ s :math:`z` coordinates.""" return self.r[:, 2] @property def inertia_tensor(self): """Inertia tensor.""" Ixx = (self.masses * (self.y**2 + self.z**2)).sum() Iyy = (self.masses * (self.x**2 + self.z**2)).sum() Izz = (self.masses * (self.x**2 + self.y**2)).sum() Ixy = Iyx = (-self.masses * self.x * self.y).sum() Ixz = Izx = (-self.masses * self.x * self.z).sum() Iyz = Izy = (-self.masses * self.y * self.z).sum() return np.array([[Ixx, Ixy, Ixz], [Iyx, Iyy, Iyz], [Izx, Izy, Izz]])
[docs] def center_center_of_mass(self, axis=None): """Center atoms on center-of-mass coordinates.""" self.translate(-self.center_of_mass)
[docs] def center_com(self, axis=None): """Alias for :attr:`~XYZAtoms.center_center_of_mass`.""" self.center_center_of_mass(axis=axis)
[docs] def center_CM(self, axis=None): """Alias for :attr:`~XYZAtoms.center_center_of_mass`.""" self.center_center_of_mass(axis=axis)
[docs] def center_centroid(self): """Center :attr:`~XYZAtoms.centroid` on origin.""" self.translate(-self.centroid)
[docs] def clip_bounds(self, region, center_before_clipping=False): """Remove atoms outside the given region. Parameters ---------- region : :class:`~sknano.core.geometric_regions.`GeometricRegion` """ centroid0 = None if center_before_clipping: centroid0 = self.centroid self.translate(-centroid0) self.data = [atom for atom in self if region.contains(atom.r)] if centroid0 is not None: self.translate(centroid0)
[docs] def get_coords(self, asdict=False): """Return atom coords. Parameters ---------- asdict : :class:`~python:bool`, optional Returns ------- coords : :class:`~python:collections.OrderedDict` or \ :class:`~numpy:numpy.ndarray` """ coords = self.coords if asdict: return OrderedDict(list(zip(xyz, coords.T))) else: return coords
[docs] def rezero_coords(self, epsilon=1.0e-10): """Alias for :meth:`Atoms.rezero_xyz`.""" self.rezero(epsilon=epsilon)
[docs] def rezero_xyz(self, epsilon=1.0e-10): """Rezero position vector components with absolute value less than \ `epsilon`. Calls the :meth:`XYZAtom.rezero_xyz` method on each `atom` in `self`. Parameters ---------- epsilon : :class:`~python:float` values with absolute value less than `epsilon` are set to zero. """ [atom.rezero_xyz(epsilon=epsilon) for atom in self]