# -*- coding: utf-8 -*-
"""
==============================================================================
Base molecule classes (:mod:`sknano.core.atoms.molecules`)
==============================================================================
.. currentmodule:: sknano.core.atoms.molecules
"""
from __future__ import absolute_import, division, print_function
from __future__ import unicode_literals
__docformat__ = 'restructuredtext en'
from operator import attrgetter
import numpy as np
from sknano.core import BaseClass, UserList
from sknano.core.math import convert_condition_str, rotation_matrix
__all__ = ['Molecule', 'Molecules']
[docs]class Molecule(BaseClass):
"""Base class for abstract representation of a molecule.
Per Wikipedia, "a molecule is an electrically neutral group of two
or more atoms held together by chemical bonds."
Parameters
----------
atoms : {None, sequence, `Atoms`}, optional
if not `None`, then a list of `Atom` instances or an
`Atoms` instance.
"""
def __init__(self, atoms=None, **kwargs):
super().__init__(**kwargs)
self.atoms = atoms
self.fmtstr = "atoms={atoms!r}"
# def __eq__(self, other):
# """Test equality of two `Molecule` object instances."""
# return self is other or self.atoms == other.atoms
# def __lt__(self, other):
# """Test if `self` is *less than* `other`."""
# return self.atoms < other.atoms
def __eq__(self, other):
if not self._is_valid_operand(other):
return NotImplemented
return self.atoms == other.atoms and super().__eq__(other)
def __le__(self, other):
if not self._is_valid_operand(other):
return NotImplemented
if not super().__le__(other) or self.atoms > other.atoms:
return False
return True
def __lt__(self, other):
if not self._is_valid_operand(other):
return NotImplemented
return (self.atoms < other.atoms and self.__le__(other))
def __ge__(self, other):
if not self._is_valid_operand(other):
return NotImplemented
if not super().__ge__(other) or self.atoms < other.atoms:
return False
return True
def __gt__(self, other):
if not self._is_valid_operand(other):
return NotImplemented
return self.atoms > other.atoms and self.__ge__(other)
[docs] def rezero(self, *args, **kwargs):
assert not hasattr(super(), 'rezero')
[docs] def rotate(self, **kwargs):
assert not hasattr(super(), 'rotate')
[docs] def translate(self, *args, **kwargs):
assert not hasattr(super(), 'translate')
[docs] def todict(self):
"""Return :class:`~python:dict` of `Molecule` constructor \
parameters."""
return dict(atoms=self.atoms)
[docs]class Molecules(UserList):
"""Base class for collection of `Molecule` objects.
Parameters
----------
molecules : {None, sequence, `Molecules`}, optional
if not `None`, then a list of `Molecule` instance objects or an
existing `Molecules` instance object.
"""
def __init__(self, molecules=None, update_item_class=True, **kwargs):
super().__init__(initlist=molecules)
self.fmtstr = "{molecules!r}"
@property
def __item_class__(self):
return Molecule
def sort(self, key=attrgetter('id'), reverse=False):
super().sort(key=key, reverse=reverse)
@property
def Nmolecules(self):
"""Number of molecules in `Molecules`."""
return len(self)
@property
def masses(self):
"""Return list of `Molecule` masses."""
return np.asarray([molecule.mass for molecule in self])
def filter(self, condition, invert=False):
"""Filter `Molecules` by `condition`.
Parameters
----------
condition : array_like, bool
invert : bool, optional
Returns
-------
filtered_molecules : `Molecules`
"""
if isinstance(condition, str):
condition = convert_condition_str(self, condition)
if invert:
condition = ~condition
try:
self.data = np.asarray(self)[condition].tolist()
except AttributeError:
self.data = np.asarray(self)[condition]
def filtered(self, condition, invert=False):
if isinstance(condition, str):
condition = convert_condition_str(self, condition)
if invert:
condition = ~condition
try:
return self.__class__(atoms=np.asarray(self)[condition].tolist(),
**self.kwargs)
except AttributeError:
return self.__class__(atoms=np.asarray(self)[condition],
**self.kwargs)
def get_molecules(self, asarray=False):
"""Return list of `Molecules`.
Parameters
----------
asarray : bool, optional
Returns
-------
sequence or ndarray
"""
if asarray:
return np.asarray(self.data)
else:
return self.data
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.
"""
[molecule.rezero(epsilon=epsilon) for molecule in self]
def rotate(self, **kwargs):
"""Rotate `Molecule` position vectors.
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`
"""
if kwargs.get('transform_matrix', None) is None:
kwargs['transform_matrix'] = rotation_matrix(**kwargs)
[molecule.rotate(**kwargs) for molecule in self]
def translate(self, t, fix_anchor_points=True):
"""Translate `Molecule` position vectors by :class:`Vector` `t`.
Parameters
----------
t : :class:`Vector`
fix_anchor_points : bool, optional
"""
[molecule.translate(t, fix_anchor_point=fix_anchor_points)
for molecule in self]
def todict(self):
"""Return :class:`~python:dict` of constructor parameters."""
return dict(molecules=self.data)