Source code for sknano.core.atoms._extended_atoms

# -*- coding: utf-8 -*-
"""
===============================================================================
Extended Atoms class feature set (:mod:`sknano.core.atoms._extended_atoms`)
===============================================================================

An "eXtended" `Atoms` class for structure analysis.

.. currentmodule:: sknano.core.atoms._extended_atoms

"""
from __future__ import absolute_import, division, print_function
from six.moves import zip
__docformat__ = 'restructuredtext en'

from collections import OrderedDict
from operator import attrgetter

import numpy as np

from sknano.core import xyz
from sknano.core.math import Vector, transformation_matrix
from sknano.utils.geometric_shapes import Cuboid  # , Rectangle
from ._atoms import Atoms

__all__ = ['XAtoms']


[docs]class XAtoms(Atoms): """An eXtended `Atoms` class. Sub-class of `Atoms` class, and a container class for lists of :class:`~sknano.core.atoms.XAtom` instances. Parameters ---------- atoms : {None, sequence, `XAtoms`}, optional if not `None`, then a list of `XAtom` instance objects or an existing `XAtoms` instance object. copylist : bool, optional perform shallow copy of atoms list deepcopy : bool, optional perform deepcopy of atoms list """ _atomattrs = Atoms._atomattrs + \ ['atomID', 'moleculeID', 'atomtype', 'q', 'dr', 'r', 'x', 'y', 'z', 'v', 'vx', 'vy', 'vz', 'f', 'fx', 'fy', 'fz', 'n', 'nx', 'ny', 'nz'] def __init__(self, atoms=None, copylist=True, deepcopy=False): super(XAtoms, self).__init__(atoms=atoms, copylist=copylist, deepcopy=deepcopy) self._atomtypes = {}
[docs] def sort(self, key=None, reverse=False): if key is None: self.data.sort(key=attrgetter('element', 'Z', 'atomtype', 'moleculeID', 'atomID', 'z'), reverse=reverse) else: self.data.sort(key=key, reverse=reverse)
@property def CM(self): """Center-of-Mass coordinates of `Atoms`. Computes the position vector of the center-of-mass coordinates: .. math:: \\mathbf{R}_{CM} = \\frac{1}{M}\\sum_{i=1}^{N_{\\mathrm{atoms}}} m_i\\mathbf{r}_i Returns ------- CM : :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 CM = Vector(np.sum(MxR, axis=0) / np.sum(masses)) CM.rezero() return CM @property def Ntypes(self): """Number of :attr:`~XAtoms.atomtypes`.""" return len(list(self.atomtypes.keys())) @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}}} m_i\\mathbf{r}_i}{\\sum_{i=1}^{N_{\\mathrm{atoms}}}m_i} 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.utils.geometric_shapes.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): """:class:`~numpy:numpy.ndarray` of `Atom`\ s \ :math:`x, y, z` coordinates.""" return np.asarray([atom.r for atom in self]) @property def x(self): """:class:`~numpy:numpy.ndarray` of `Atom`\ s :math:`x` coordinates.""" return self.coords[:,0] @property def y(self): """:class:`~numpy:numpy.ndarray` of `Atom`\ s :math:`y` coordinates.""" return self.coords[:,1] @property def z(self): """:class:`~numpy:numpy.ndarray` of `Atom`\ s :math:`z` coordinates.""" return self.coords[:,2] @property def inertia_tensor(self): """Return the 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]]) @property def atomtypes(self): """:attr:`~XAtoms.atomtypes` :class:`python:dict`.""" self._update_atomtypes() return self._atomtypes def _update_atomtypes(self): for atom in self: if atom.atomtype not in self._atomtypes: self._atomtypes[atom.atomtype] = {} self._atomtypes[atom.atomtype]['mass'] = atom.m self._atomtypes[atom.atomtype]['q'] = atom.q @property def atom_ids(self): """Return array of `XAtom` IDs.""" if len(set([atom.atomID for atom in self])) != self.Natoms: self.assign_unique_ids() return np.asarray([atom.atomID for atom in self]) @property def charges(self): """Return array of `XAtom` charges.""" return np.asarray([atom.q for atom in self]) @property def q(self): """Return the total net charge of `XAtoms`.""" return self.charges.sum() @property def velocities(self): """Return array of `XAtom` velocities.""" return np.asarray([atom.v for atom in self])
[docs] def add_atomtype(self, atom): """Add atom type to :attr:`~XAtoms.atomtypes`. Parameters ---------- atom : :class:`~sknano.core.atoms.XAtom` A :class:`~sknano.core.atoms.XAtom` instance. """ if atom.atomtype not in self._atomtypes: self._atomtypes[atom.atomtype] = {} self._atomtypes[atom.atomtype]['mass'] = atom.m self._atomtypes[atom.atomtype]['q'] = atom.q
[docs] def add_atomtypes(self, atoms=None): """Add atomtype for each atom in atoms to atomtypes dictionary. Parameters ---------- atoms : sequence a list of `XAtom` object instances """ try: [self.add_atomtype(atom) for atom in atoms] except TypeError: print('Expected an iterable sequence of `XAtom` objects.')
[docs] def assign_unique_ids(self, starting_id=1): """Assign unique ID to each `XAtom` in `XAtoms`.""" [setattr(atom, 'atomID', i) for i, atom in enumerate(self, start=starting_id)]
[docs] def center_CM(self, axes=None): """Center atoms on CM coordinates.""" dr = -self.CM self.translate(dr)
[docs] def clip_bounds(self, region, center_before_clipping=False): """Remove atoms outside the given limits along given dimension. Parameters ---------- region : :class:`~sknano.utils.geometric_shapes.`GeometricRegion` """ CM0 = None if center_before_clipping: CM0 = self.CM self.translate(-CM0) self.data = \ np.asarray(self)[np.logical_and( np.logical_and( self.x <= region.limits['x']['max'], np.logical_and( self.y <= region.limits['y']['max'], self.z <= region.limits['z']['max'])), np.logical_and( self.x >= region.limits['x']['min'], np.logical_and( self.y >= region.limits['y']['min'], self.z >= region.limits['z']['min'])))].tolist() #for dim, limits in region.limits.iteritems(): # atoms = atoms[np.where(getattr(self, dim) <= limits['max'])] # atoms = atoms[np.where(getattr(self, dim) >= limits['min'])] # self = atoms.tolist() if CM0 is not None: self.translate(CM0)
[docs] def filter_ids(self, atom_ids, invert=False): """Return `Atoms` by :attr:`XAtoms.atom_ids` in `atom_ids`. Parameters ---------- filter_ids : array_like invert : bool, optional Returns ------- filtered_atoms : `Atoms` An instance of `Atoms` (sub)class. """ filtered_atoms = \ np.asarray(self)[np.in1d( self.atom_ids, atom_ids, invert=invert).nonzero()].tolist() return self.__class__(atoms=filtered_atoms)
[docs] def get_atom(self, atomID): """Get `XAtom` with :attr:`Xatom.atomID` == `atomID`. Parameters ---------- atomID : int Returns ------- `XAtom` instance with :attr:`XAtom.atomID` == `atomID` or `None` if no :attr:`XAtom.atomID` == `atomID`. """ try: return self[np.where(self.atom_ids == atomID)[0]] except TypeError: print('No atom with atomID = {}'.format(atomID)) return None
[docs] def get_coords(self, asdict=False): """Return atom coords. Parameters ---------- asdict : bool, optional Returns ------- coords : :py:class:`python:~collections.OrderedDict` or ndarray """ coords = self.coords if asdict: return OrderedDict(list(zip(xyz, coords.T))) else: return coords
[docs] def getatomattr(self, attr): """Get :class:`~numpy:numpy.ndarray` of atom attributes `attr`. Parameters ---------- attr : str Name of attribute to pass to `getattr` from each `XAtom` in `XAtoms`. Returns ------- :class:`~numpy:numpy.ndarray` Raises ------ `ValueError` if `attr` not in list of class atomattrs. """ if attr not in self._atomattrs: errmsg = '{} not in list of allowed attributes:\n{}' raise ValueError(errmsg.format(attr, self._atomattrs)) return np.asarray([getattr(atom, attr) for atom in self])
[docs] def mapatomattr(self, attr, from_attr, attrmap): """Set/update atom attribute from another atom attribute with dict. Parameters ---------- attr, from_attr : :class:`python:str` attrmap : :class:`python:dict` Examples -------- Suppose you have an `XAtoms` instance named ``atoms`` that has `XAtom` instances of two atomtypes `1` and `2` and we want to set all `XAtom`\ s with `atomtype=1` to Nitrogen and all `XAtom`\ s with `atomtype=2` to Argon. We'd call this method like so:: >>> atoms.mapatomattr('element', 'atomtype', {1: 'N', 2: 'Ar'}) """ [setattr(atom, attr, attrmap[getattr(atom, from_attr)]) for atom in self if getattr(atom, from_attr) is not None]
[docs] def rezero_coords(self, epsilon=1.0e-10): """Alias for :meth:`Atoms.rezero`.""" self.rezero(epsilon=epsilon)
[docs] def rezero_xyz(self, epsilon=1.0e-10): """Alias for :meth:`Atoms.rezero`.""" self.rezero(epsilon=epsilon)
[docs] def rezero(self, epsilon=1.0e-10): """Set really really small coordinates to zero. Set all coordinates with absolute value less than epsilon to zero. Parameters ---------- epsilon : float smallest allowed absolute value of any :math:`x,y,z` component. """ [atom.rezero(epsilon=epsilon) for atom in self]
[docs] def rotate(self, angle=None, rot_axis=None, anchor_point=None, rot_point=None, from_vector=None, to_vector=None, deg2rad=False, transform_matrix=None, verbose=False): """Rotate `Atom` position vectors. Parameters ---------- angle : float rot_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 deg2rad : bool, optional transform_matrix : :class:`~numpy:numpy.ndarray` """ if transform_matrix is None: transform_matrix = \ transformation_matrix(angle=angle, rot_axis=rot_axis, anchor_point=anchor_point, rot_point=rot_point, from_vector=from_vector, to_vector=to_vector, deg2rad=deg2rad, verbose=verbose) [atom.rotate(transform_matrix=transform_matrix) for atom in self]
[docs] def select(self, cmd): pass
[docs] def select_within(self, volume): pass
[docs] def translate(self, t, fix_anchor_points=True): """Translate `Atom` position vectors by :class:`Vector` `t`. Parameters ---------- t : :class:`Vector` fix_anchor_points : bool, optional """ [atom.translate(t, fix_anchor_point=fix_anchor_points) for atom in self]