# -*- coding: utf-8 -*-
"""
=============================================================================
Base classes for structure data (:mod:`sknano.io.base`)
=============================================================================
.. currentmodule:: sknano.io.base
"""
from __future__ import absolute_import, division, print_function
from __future__ import unicode_literals
__docformat__ = 'restructuredtext en'
from abc import ABCMeta, abstractmethod
import copy
import os
from sknano.core import BaseClass, TabulateMixin, get_fpath
from sknano.core.atoms import MDAtoms as Atoms
from sknano.core.structures import StructureBase
# from sknano.utils.analysis import StructureAnalyzer
from sknano.version import version
default_comment_line = \
'Structure data generated using scikit-nano version {}'.format(version)
default_structure_format = 'xyz'
supported_structure_formats = ('xyz', 'data', 'dump')
__all__ = ['StructureData',
'StructureDataConverter',
'StructureDataFormatter',
'StructureDataError',
'StructureDataMixin',
'StructureDataReader',
'StructureDataWriter',
'StructureDataReaderMixin',
'StructureDataWriterMixin',
'StructureIO',
'StructureIOConverter',
'StructureIOFormatter',
'StructureIOError',
'StructureIOMixin',
'StructureIOReader',
'StructureIOWriter',
'StructureIOReaderMixin',
'StructureIOWriterMixin',
'StructureReader',
'StructureWriter',
'StructureReaderMixin',
'StructureWriterMixin',
'StructureFormatSpec',
'default_comment_line',
'default_structure_format',
'supported_structure_formats']
[docs]class StructureDataReaderMixin:
"""Mixin class for reading structure data."""
[docs] def read(self, *args, fname=None, structure_format=None, **kwargs):
"""Read structure data."""
if fname is None:
if len(args) == 0:
raise ValueError('`fname` is required')
else:
fname = list(args).pop()
if fname.endswith(supported_structure_formats) and \
structure_format is None:
for ext in supported_structure_formats:
if fname.endswith(ext):
structure_format = ext
break
if not fname.endswith(structure_format):
fname += '.' + structure_format
if structure_format is None:
raise ValueError(('Unknown structure format: ' +
'{}'.format(structure_format)))
getattr(self, 'read_' + structure_format)(fname=fname, **kwargs)
[docs] def read_data(self, *args, **kwargs):
"""Read LAMMPS Data file.
Returns
-------
:class:`~sknano.io.DATAReader`
"""
from sknano.io import DATAReader
return DATAReader(*args, **kwargs)
[docs] def read_dump(self, *args, **kwargs):
"""Read LAMMPS Dump file.
Returns
-------
:class:`~sknano.io.DUMPReader`
"""
from sknano.io import DUMPReader
return DUMPReader(*args, **kwargs)
[docs] def read_pdb(self, *args, **kwargs):
"""Read PDB file.
Returns
-------
:class:`~sknano.io.PDBReader`
"""
from sknano.io import PDBReader
return PDBReader(*args, **kwargs)
[docs] def read_xyz(self, *args, **kwargs):
"""Read XYZ file.
Returns
-------
:class:`~sknano.io.XYZReader`
"""
from sknano.io import XYZReader
return XYZReader.read(*args, **kwargs)
StructureReaderMixin = StructureIOReaderMixin = StructureDataReaderMixin
[docs]class StructureDataWriterMixin:
"""Mixin class for saving structure data."""
def _update_atoms(self, deepcopy=True, center_centroid=True,
center_com=False, region_bounds=None,
filter_condition=None, rotation_parameters=None,
**kwargs):
atoms_copy = self._atoms[:]
if deepcopy:
atoms_copy = copy.deepcopy(atoms_copy)
self._atoms_copy = atoms_copy
atoms = self._atoms
if any([kw in kwargs for kw
in ('center_CM', 'center_center_of_mass')]):
if 'center_CM' in kwargs:
center_com = kwargs['center_CM']
del kwargs['center_CM']
else:
center_com = kwargs['center_center_of_mass']
del kwargs['center_center_of_mass']
if region_bounds is not None:
atoms.clip_bounds(region_bounds)
if center_centroid:
atoms.center_centroid()
elif center_com:
atoms.center_com()
if filter_condition is not None:
atoms.filter(filter_condition)
# atoms = atoms.filtered(filter_condition)
rotation_kwargs = ['rotation_angle', 'angle', 'rot_axis', 'axis',
'anchor_point', 'deg2rad', 'degrees', 'rot_point',
'from_vector', 'to_vector', 'transform_matrix']
if rotation_parameters is None and \
any([kw in kwargs for kw in rotation_kwargs]):
rotation_parameters = {kw: kwargs[kw] for kw in rotation_kwargs
if kw in kwargs}
if 'rotation_angle' in rotation_parameters:
rotation_parameters['angle'] = \
rotation_parameters['rotation_angle']
del rotation_parameters['rotation_angle']
if 'rot_axis' in rotation_parameters:
rotation_parameters['axis'] = rotation_parameters['rot_axis']
del rotation_parameters['rot_axis']
if 'deg2rad' in rotation_parameters:
rotation_parameters['degrees'] = rotation_parameters['deg2rad']
del rotation_parameters['deg2rad']
kwargs = {k: v for k, v in kwargs.items()
if k not in rotation_kwargs}
if rotation_parameters is not None and \
isinstance(rotation_parameters, dict):
atoms.rotate(**rotation_parameters)
atoms.rezero()
self._atoms = atoms
def _update_fpath(self, fname=None, outpath=None, structure_format=None,
**kwargs):
if fname.endswith(supported_structure_formats) and \
structure_format is None:
for ext in supported_structure_formats:
if fname.endswith(ext):
structure_format = ext
break
elif structure_format is None or \
structure_format not in supported_structure_formats or \
(not fname.endswith(supported_structure_formats) and
structure_format not in supported_structure_formats):
structure_format = default_structure_format
self.structure_format = structure_format
if not fname.endswith(structure_format):
fname += '.' + structure_format
self.fname = fname
if outpath is not None:
fpath = os.path.join(outpath, fname)
else:
fpath = os.path.join(os.getcwd(), fname)
self.fpath = fpath
[docs] def write(self, *args, **kwargs):
"""Write structure data to file.
Parameters
----------
fname : {None, str}, optional
file name string
outpath : str, optional
Output path for structure data file.
structure_format : {None, 'xyz', 'pdb', 'data', 'dump'}, optional
chemical file format of saved structure data. Must be one of:
- `xyz`
- `pdb`
- `data`
- `dump`
If `None`, then guess based on `fname` file extension or
default to `xyz` format.
center_centroid : bool, optional
Center centroid on origin.
center_com : bool, optional
Center center-of-mass on origin.
region_bounds : :class:`GeometricRegion`, optional
An instance of a
:class:`~sknano.core.geometric_regions.GeometricRegion` having
a method
:meth:`~sknano.core.geometric_regions.GeometricRegion.contains`
to filter the atoms not contained within the
`GeometricRegion`.
filter_condition : array_like, optional
"""
self._update_fpath(**kwargs)
# self._update_atoms(**kwargs)
structure_format = self.structure_format
print('writing {}'.format(kwargs['fname']))
getattr(self, 'write_' + structure_format)(structure=self, **kwargs)
[docs] def write_data(self, **kwargs):
"""Write LAMMPS data file."""
from sknano.io import DATAWriter
DATAWriter.write(**kwargs)
[docs] def write_dump(self, **kwargs):
"""Write LAMMPS dump file."""
from sknano.io import DUMPWriter
DUMPWriter.write(**kwargs)
[docs] def write_pdb(self, **kwargs):
"""Write pdb file."""
from sknano.io import PDBWriter
PDBWriter.write(**kwargs)
[docs] def write_xyz(self, **kwargs):
"""Write xyz file."""
from sknano.io import XYZWriter
XYZWriter.write(**kwargs)
StructureWriterMixin = StructureIOWriterMixin = StructureDataWriterMixin
[docs]class StructureDataMixin(StructureDataReaderMixin, StructureDataWriterMixin):
"""Mixin class providing I/O methods for structure data."""
pass
StructureIOMixin = StructureDataMixin
[docs]class StructureData(StructureDataMixin, StructureBase, BaseClass):
"""Base class for structure data file input and output.
Parameters
----------
fpath : {None, str}, optional
"""
def __init__(self, fpath=None, fname=None, formatter=None, **kwargs):
super().__init__(**kwargs)
self._atoms = Atoms()
self.comment_line = default_comment_line
if fpath is None and fname is not None:
fpath = fname
self.fpath = fpath
self.formatter = formatter
self.kwargs = kwargs
fmtstr = "fpath={fpath!r}"
if isinstance(formatter, StructureDataFormatter):
fmtstr = ', '.join((fmtstr, formatter.fmtstr))
self.fmtstr = fmtstr
def __str__(self):
# strrep = self._table_title_str()
strrep = self._obj_mro_str()
atoms = self.atoms
# xtal_cell = self.crystal_cell
if atoms.data:
title = '.'.join((strrep, atoms.__class__.__name__))
strrep = '\n'.join((title, str(atoms)))
# formatter = self.formatter
# if formatter is not None:
# strrep = '\n'.join((strrep, str(formatter)))
return strrep
@property
def comment_line(self):
"""Comment line."""
return self._comment_line
@comment_line.setter
def comment_line(self, value):
"""Set the comment line string.
Parameters
----------
value : str
"""
if not isinstance(value, str):
raise TypeError('Expected a string.')
self._comment_line = value
@comment_line.deleter
def comment_line(self):
del self._comment_line
def __deepcopy__(self, memo):
from copy import deepcopy
cp = self.__class__(None)
memo[id(self)] = cp
for attr in dir(self):
if not attr.startswith('_'):
setattr(cp, attr, deepcopy(getattr(self, attr), memo))
return cp
[docs] def todict(self):
"""Return :class:`~python:dict` of constructor parameters."""
return dict(fpath=self.fpath, formatter=self.formatter)
StructureIO = StructureData
[docs]class StructureDataReader:
"""Structure data reader base class."""
@classmethod
[docs] def read(cls, fpath, structure_format=None, **kwargs):
"""Read structure data from file.
Parameters
----------
fpath : str
structure_format : {None, str}
"""
if (structure_format is None and fpath.endswith('.data')) or \
structure_format == 'data':
from .lammps_data import DATAReader
return DATAReader(fpath, **kwargs)
elif (structure_format is None and fpath.endswith('.dump')) or \
structure_format == 'dump':
from .lammps_dump import DUMPReader
return DUMPReader(fpath, **kwargs)
elif (structure_format is None and fpath.endswith('.pdb')) or \
structure_format == 'pdb':
from .pdb import PDBReader
return PDBReader.read(fpath)
elif (structure_format is None and fpath.endswith('.xyz')) or \
structure_format == 'xyz':
from .xyz import XYZReader
return XYZReader.read(fpath)
else:
raise StructureDataError("Unable to determine `structure_format`")
StructureReader = StructureIOReader = StructureDataReader
[docs]class StructureDataWriter:
"""Structure data writer base class."""
@classmethod
[docs] def write(cls, fname=None, structure_format=None, **kwargs):
"""Write structure data to file.
Parameters
----------
fname : str, optional
structure_format : {None, str}, optional
"""
if fname is None and structure_format is None:
structure_format = default_structure_format
if fname is None:
fname = get_fpath(fname='structure_data', ext=structure_format,
add_fnum=True)
if (structure_format is None and fname.endswith('.data')) or \
structure_format == 'data':
from .lammps_data import DATAWriter
DATAWriter.write(fname=fname, **kwargs)
elif (structure_format is None and fname.endswith('.dump')) or \
structure_format == 'dump':
from .lammps_dump import DUMPWriter
DUMPWriter.write(fname=fname, **kwargs)
elif (structure_format is None and fname.endswith('.pdb')) or \
structure_format == 'pdb':
from .pdb import PDBWriter
PDBWriter.write(fname=fname, **kwargs)
# elif (structure_format is None and fname.endswith('.xyz')) or \
# structure_format == 'xyz':
else:
from .xyz import XYZWriter
XYZWriter.write(fname=fname, **kwargs)
StructureWriter = StructureIOWriter = StructureDataWriter
[docs]class StructureDataConverter(BaseClass, metaclass=ABCMeta):
"""Abstract base class for converting structure data.
Parameters
----------
infile : str
outfile : str
"""
def __init__(self, infile=None, outfile=None, **kwargs):
self.infile = infile
self.outfile = outfile
self.kwargs = kwargs
self.fmtstr = "infile={infile!r}, outfile={outfile!r}"
@abstractmethod
[docs] def convert(self):
"""Convert structure data from one format to another format."""
raise NotImplementedError('Subclasses of `StructureConverter` need '
'to implement the `convert` method.')
[docs] def todict(self):
"""Return :class:`~python:dict` of constructor parameters."""
return dict(infile=self.infile, outfile=self.outfile)
StructureConverter = StructureIOConverter = StructureDataConverter
StructureFormatSpec = StructureIOFormatter = StructureDataFormatter
[docs]class StructureDataError(Exception):
"""Base class for `StructureData` errors."""
pass
StructureIOError = StructureDataError