Source code for sknano.core.atoms.lattice_atoms

# -*- coding: utf-8 -*-
"""
===============================================================================
Atom classes for crystal lattices (:mod:`sknano.core.atoms.lattice_atoms`)
===============================================================================

.. currentmodule:: sknano.core.atoms.lattice_atoms

"""
from __future__ import absolute_import, division, print_function
from __future__ import unicode_literals
__docformat__ = 'restructuredtext en'

# from operator import attrgetter
import copy
import numbers

import numpy as np

from sknano.core.math import Vector, Vectors

from .atoms import Atom, Atoms
from .mixins import PBCAtomsMixin

__all__ = ['LatticeAtom', 'LatticeAtoms']


[docs]class LatticeAtom(Atom): """An `Atom` sub-class with crystal lattice attributes. Parameters ---------- lattice : :class:`~sknano.core.crystallography.Crystal3DLattice` xs, ys, zs : :class:`~python:float` """ def __init__(self, *args, lattice=None, xs=None, ys=None, zs=None, **kwargs): super().__init__(*args, **kwargs) self.lattice = lattice if all([x is not None for x in (xs, ys, zs)]): self.rs = Vector([xs, ys, zs]) self.fmtstr = super().fmtstr + \ ", lattice={lattice!r}, xs={xs!r}, ys={ys!r}, zs={zs!r}" # ", lattice={lattice!r}, xs={xs:.6f}, ys={ys:.6f}, zs={zs:.6f}" def __eq__(self, other): if not self._is_valid_operand(other): return NotImplemented if self.rs is None or other.rs is None: return super().__eq__(other) return self.rs == other.rs and super().__eq__(other) def __le__(self, other): if not self._is_valid_operand(other): return NotImplemented if self.rs is None or other.rs is None: return super().__le__(other) if self.rs > other.rs or not super().__le__(other): return False return True # def __lt__(self, other): # """Test if `self` is *less than* `other`.""" # return (self.rs < other.rs and super().__le__(other)) or \ # (self.rs <= other.rs and super().__lt__(other)) def __lt__(self, other): if not self._is_valid_operand(other): return NotImplemented if self.rs is None or other.rs is None: return super().__lt__(other) # return ((self.rs < other.rs and self.__le__(other)) or # (self.__le__(other) and super().__lt__(other))) if self.rs >= other.rs or not super().__lt__(other): return False return True def __ge__(self, other): if not self._is_valid_operand(other): return NotImplemented if self.rs is None or other.rs is None: return super().__ge__(other) if self.rs < other.rs or not super().__ge__(other): return False return True def __gt__(self, other): if not self._is_valid_operand(other): return NotImplemented if self.rs is None or other.rs is None: return super().__gt__(other) if self.rs <= other.rs or not super().__gt__(other): return False return True def __dir__(self): attrs = super().__dir__() attrs.extend(['lattice', 'xs', 'ys', 'zs']) return attrs @property def xs(self): """Scaled :math:`x`-coordinate. Returns ------- float Scaled :math:`x`-coordinate. """ try: return self.rs.x except AttributeError: return None @xs.setter def xs(self, value): """Set `Atom` :math:`x`-coordinate. Parameters ---------- value : float :math:`x`-coordinate.. """ if not isinstance(value, numbers.Number): raise TypeError('Expected a number') try: rs = self.rs rs.x = value self._update_cartesian_coordinate(rs) except AttributeError: pass @property def ys(self): """Scaled :math:`y`-coordinate. Returns ------- float Scaled :math:`y`-coordinate. """ try: return self.rs.y except AttributeError: return None @ys.setter def ys(self, value): """Set `Atom` :math:`y`-coordinate. Parameters ---------- value : float :math:`y`-coordinate. """ if not isinstance(value, numbers.Number): raise TypeError('Expected a number') try: rs = self.rs rs.y = value self._update_cartesian_coordinate(rs) except AttributeError: pass @property def zs(self): """Scaled :math:`z`-coordinate. Returns ------- float Scaled :math:`z`-coordinate. """ try: return self.rs.z except AttributeError: return None @zs.setter def zs(self, value): """Set `Atom` :math:`z`-coordinate. Parameters ---------- value : float Scaled :math:`z`-coordinate """ if not isinstance(value, numbers.Number): raise TypeError('Expected a number') try: rs = self.rs rs.z = value self._update_cartesian_coordinate(rs) except AttributeError: pass @property def rs(self): """Scaled :math:`x, y, z` components of `Atom` position vector. Returns ------- ndarray 3-element ndarray of [:math:`x_s, y_s, z_s`] coordinates of `Atom`. """ try: return Vector(self.lattice.cartesian_to_fractional(self.r)) except AttributeError: return None @rs.setter def rs(self, value): """Set scaled :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._update_cartesian_coordinate(Vector(value, nd=3)) def _update_cartesian_coordinate(self, rs): try: self.r = self.lattice.fractional_to_cartesian(rs) except AttributeError: pass @property def lattice(self): """:class:`~sknano.core.crystallography.Crystal3DLattice`.""" return self._lattice @lattice.setter def lattice(self, value): self._lattice = copy.deepcopy(value) try: self.rs = self.lattice.cartesian_to_fractional(self.r) except AttributeError: pass
[docs] def todict(self): """Return :class:`~python:dict` of constructor parameters.""" super_dict = super().todict() super_dict.update(dict(lattice=self.lattice, xs=self.xs, ys=self.ys, zs=self.zs)) return super_dict
[docs]class LatticeAtoms(PBCAtomsMixin, Atoms): """An `Atoms` sub-class for crystal structure lattice atoms. Sub-class of `Atoms` class, and a container class for lists of :class:`~sknano.core.atoms.LatticeAtom` instances. Parameters ---------- atoms : {None, sequence, `LatticeAtoms`}, optional if not `None`, then a list of `LatticeAtom` instance objects or an existing `LatticeAtoms` instance object. """ @property def __atom_class__(self): return LatticeAtom @property def rs(self): """:class:`Vectors` of :attr:`LatticeAtom.rs` :class:`Vector`\ s""" return Vectors([atom.rs for atom in self]) @property def xs(self): """:class:`~numpy:numpy.ndarray` of :attr:`LatticeAtom.xs` values""" return self.rs.x @property def ys(self): """:class:`~numpy:numpy.ndarray` of :attr:`LatticeAtom.ys` values""" return self.rs.y @property def zs(self): """:class:`~numpy:numpy.ndarray` of :attr:`LatticeAtom.zs` values""" return self.rs.z @property def lattice(self): """Return the :attr:`LatticeAtom.lattice` of the first atom in self.""" try: return self[0].lattice except IndexError: return None @lattice.setter def lattice(self, value): [setattr(atom, 'lattice', value) for atom in self] @property def cell_matrix(self): """Return the :attr:`Crystal3DLattice.cell_matrix`.""" try: return self.lattice.cell_matrix except AttributeError: return None @property def cell(self): """Alias for :attr:`LatticeAtoms.cell_matrix`.""" return self.cell_matrix def wrap_coords(self, pbc=None): """Wrap coordinates into lattice.""" try: [setattr(atom, 'r', self.lattice.wrap_cartesian_coordinate( atom.r, pbc=pbc if pbc is not None else self.pbc)) for atom in self] except AttributeError: pass