Source code for sknano.core.structures.fullerenes

# -*- coding: utf-8 -*-
"""
===============================================================================
Fullerene structure classes (:mod:`sknano.core.structures.fullerenes`)
===============================================================================

.. currentmodule:: sknano.core.structures.fullerenes

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

from collections import Counter, OrderedDict
from operator import setitem
from pkg_resources import resource_filename

import numbers
import os

import numpy as np

from sknano.core import listdir_dirnames, listdir_fnames
from sknano.core.atoms import BasisAtom, BasisAtoms
from sknano.core.crystallography import Crystal3DLattice, UnitCell
from .base import CrystalStructureBase

__all__ = ['Fullerene', 'Fullerenes', 'load_fullerene_data']


[docs]def load_fullerene_data(): """Helper function to populate :class:`~python:dict` of \ fullerene data files.""" datadir = resource_filename('sknano', 'data/fullerenes') fullerenes = \ listdir_dirnames(datadir, filterfunc=lambda name: name.startswith('C')) fullerenes = sorted(fullerenes, key=lambda s: int(s[1:])) datadict = OrderedDict() for fullerene in fullerenes: datapath = os.path.join('data', 'fullerenes', fullerene) datadir = resource_filename('sknano', datapath) dataset = datadict[fullerene] = {} files = dataset['files'] = \ sorted(listdir_fnames(datadir, include_path=True)) pg_counter = Counter() point_groups = dataset['point_groups'] = [] # isomer_numbers = dataset['isomer_numbers'] = [] for f in files: s = os.path.splitext(f)[0].split('-') if len(s) == 3: _, PG, Niso = s Niso = int(Niso) else: try: Niso = int(s[-1]) PG = None except ValueError: PG = s[-1] Niso = None pg_counter[PG] += 1 if PG not in point_groups: point_groups.append(PG) pg_dataset = dataset[PG] = {} pg_files = pg_dataset['files'] = [] pg_isomer_numbers = pg_dataset['isomer_numbers'] = [] pg_files.append(f) pg_isomer_numbers.append(Niso) pg_dataset[Niso] = f dataset['point_groups'] = \ [t[0] for t in sorted(pg_counter.most_common(), key=lambda t: (-t[1], t[0]))] [setitem(dataset, PG_, OrderedDict([('files', dataset[PG_]['files']), ('isomer_numbers', dataset[PG_]['isomer_numbers'])])) for PG_ in dataset['point_groups']] [[setitem(dataset[PG_], Niso_, dataset[PG_]['files'] [dataset[PG_]['isomer_numbers'].index(Niso_)]) for Niso_ in dataset[PG_]['isomer_numbers']] for PG_ in dataset['point_groups']] if len(point_groups) == 1 and None in point_groups: dataset['point_groups'] = None else: dataset['point_group_counter'] = pg_counter # if len(isomer_numbers) == 1 and None in isomer_numbers: # dataset['isomer_numbers'] = None return datadict
[docs]class Fullerene(CrystalStructureBase): """Fullerene structure class. The `fullerene data <http://www.nanotube.msu.edu/fullerene/fullerene-isomers.html>`_ were downloaded from the `The Nanotube Site <http://www.nanotube.msu.edu>`_. Parameters ---------- N : :class:`~python:int` The :math:`N` in :math:`C_N` where :math:`N` is the number of carbon atoms. Always an even integer. PG : :class:`~python:str`, optional Point Group symmetry of `Fullerene` isomer given in `Schoenflies notation <https://en.wikipedia.org/wiki/Schoenflies_notation>`_. Niso : :class:`~python:int`, optional Number specifying specific isomer with given `PG` symmetry. If not `None`, then `PG` must also be specified. Examples -------- """ _data = load_fullerene_data() def __init__(self, *args, N=None, PG=None, Niso=None, **kwargs): args = list(args) if N is None and len(args) == 1 and isinstance(args[0], int): N = args.pop() super().__init__(*args, **kwargs) if N is not None: self.N = N if PG is not None: self.PG = PG if Niso is not None: self.Niso = Niso else: self._N = N self._PG = PG self._Niso = Niso self.generate_unit_cell() self.fmtstr = "N={N!r}, PG={PG!r}, Niso={Niso!r}" def __str__(self): strrep = "C{N!r}".format(N=self.N) return strrep @property def data(self): """Return :class:`~python:dict` of :class:`Fullerene` data.""" try: return self._data[self.name] except KeyError: return None @property def datafile(self): """Return :class:`Fullerene` `xyz` structure data file.""" try: return self.data[self.PG][self.Niso] except TypeError: return None @property def N(self): """Number of atoms.""" return self._N @N.setter def N(self, value): if not (isinstance(value, numbers.Integral) or value > 0 or value % 2 == 0): raise TypeError('Expected an even, positive integer.') self._N = int(value) try: self._PG = self.point_groups[0] except TypeError: self._PG = None try: self._Niso = self.data[self.PG]['isomer_numbers'][0] except TypeError: self._Niso = None @property def Natoms(self): """Alias for :attr:`~Fullerene.N`.""" return self.N @property def PG(self): """Point Group symmetry in Schoenflies notation.""" return self._PG @PG.setter def PG(self, value): if ((self.point_groups is not None and value not in self.point_groups) or (self.point_groups is None and value is not None)): errmsg = 'No PG={} for {}'.format(value, self.name) raise ValueError(errmsg) self._PG = value try: self._Niso = self.data[self.PG]['isomer_numbers'][0] except TypeError: self._Niso = None @property def Niso(self): """Isomer number.""" return self._Niso @Niso.setter def Niso(self, value): if value is not None and \ value not in self.data[self.PG]['isomer_numbers']: errmsg = \ 'No isomer Niso={} for {}'.format(value, self.name) raise ValueError(errmsg) self._Niso = int(value) if isinstance(value, numbers.Integral) \ else None @property def name(self): """Return string representation if :attr:`~Fullerene.N` is not `None` \ else `None`.""" if self.N is not None: return str(self) else: return None @property def Nisomers(self): """Return number of :attr:`~Fullerene.N` isomers.""" try: return len(self.data['files']) except TypeError: return 0 @property def point_groups(self): """List of point groups.""" try: return self.data['point_groups'] except TypeError: return None
[docs] def generate_unit_cell(self): """Generate :class:`Fullerene` :class:`UnitCell`.""" from sknano.io import XYZReader atoms = XYZReader(self.datafile).atoms atoms.center_centroid() bounding_box = atoms.bounding_box a = np.sqrt(2) * (max(bounding_box.abc) + 3.4) lattice = Crystal3DLattice.cubic(a) basis = BasisAtoms() for atom in atoms: xs, ys, zs = lattice.cartesian_to_fractional(atom.r) basis.append(BasisAtom(atom.element, lattice=lattice, xs=xs, ys=ys, zs=zs)) self.unit_cell = UnitCell(lattice=lattice, basis=basis)
[docs] def todict(self): """Return :class:`~python:dict` of :class:`Fullerene` parameters.""" return dict(N=self.N, PG=self.PG, Niso=self.Niso)
[docs]class Fullerenes: data = load_fullerene_data() fullerenes = tuple(data.keys()) N = tuple(int(fullerene[1:]) for fullerene in fullerenes)