Source code for sknano.core.structures.nanotube_bundle

# -*- coding: utf-8 -*-
"""
==============================================================================
Nanotube bundle classes (:mod:`sknano.core.structures.nanotube_bundle`)
==============================================================================

.. currentmodule:: sknano.core.structures.nanotube_bundle

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

import numbers
import re

import numpy as np

from sknano.core.atoms import Atom, vdw_radius_from_basis
from sknano.core.refdata import aCC, grams_per_Da
from sknano.core.math import Vector
from .base import NanoStructureBase
from .extras import get_chiral_indices

__all__ = ['compute_bundle_density', 'NanotubeBundleMixin',
           'NanotubeBundleBase']


[docs]def compute_bundle_density(*Ch, r_vdw=None, bond=None, element1=None, element2=None): """Compute nanotube bundle mass density \ :math:`\\rho_{\\mathrm{bundle}}(n, m)` in :math:`\\mathrm{g/cm^3}`. .. math:: \\rho_{\\mathrm{bundle}}(n, m) = \\frac{8\\pi^2 m_{\\mathrm{C}} \\sqrt{n^2 + m^2 + nm}}{9\\sqrt{3}a_{\\mathrm{CC}}^3 \\times \\left(\\sqrt{n^2 + m^2 + nm} + \\frac{\\pi d_{\\mathrm{vdW}}}{\\sqrt{3}a_{\\mathrm{CC}}}\\right)^2} Parameters ---------- *Ch : {:class:`python:tuple` or :class:`python:int`\ s} Either a 2-tuple of ints or 2 integers giving the chiral indices of the nanotube chiral vector :math:`\\mathbf{C}_h = n\\mathbf{a}_1 + m\\mathbf{a}_2 = (n, m)`. r_vdw : int van der Waals radius of nanotube atoms bond : float, optional Bond length. Returns ------- float :math:`\\rho_{\\mathrm{bundle}}` in units of :math:`\\mathrm{\\frac{g}{cm^3}}` """ n, m, _ = get_chiral_indices(*Ch) if bond is None: bond = aCC if element1 is None: element1 = 'C' if element2 is None: element2 = 'C' if r_vdw is None: r_vdw = vdw_radius_from_basis(element1, element2) if element1 == element2: bundle_density = 8 * np.pi ** 2 * Atom(element1).mass * \ np.sqrt(n ** 2 + m ** 2 + n * m) / \ (9 * np.sqrt(3) * bond ** 3 * (np.sqrt(n ** 2 + m ** 2 + n * m) + 2 * np.pi * r_vdw / (np.sqrt(3) * bond)) ** 2) else: bundle_density = 0 # there are 1.6605e-24 grams / Da and 1e-8 cm / angstrom bundle_density *= grams_per_Da / (1e-8) ** 3 return bundle_density
[docs]class NanotubeBundleMixin: """Mixin class for nanotube bundles.""" @property def bundle_density(self): """Compute the nanotube bundle density.""" return compute_bundle_density(self.n, self.m, r_vdw=self.vdw_radius, bond=self.bond, basis=self.basis) @property def nx(self): """Number of nanotubes along the :math:`x`-axis.""" return self._nx @nx.setter def nx(self, value): """Set :math:`n_x`""" if not (isinstance(value, numbers.Number) or value > 0): raise TypeError('Expected a positive integer.') self._nx = int(value) @nx.deleter def nx(self): del self._nx @property def ny(self): """Number of nanotubes along the :math:`y`-axis.""" return self._ny @ny.setter def ny(self, value): """Set :math:`n_y`""" if not (isinstance(value, numbers.Number) or value > 0): raise TypeError('Expected a positive integer.') self._ny = int(value) @ny.deleter def ny(self): del self._ny @property def Lx(self): """Axis-aligned length along the `x`-axis in **Angstroms**. Calculated as: .. math:: L_x = n_x * (d_t + 2 r_{\\mathrm{vdW}}) """ return self.nx * (self.dt + 2 * self.vdw_radius) @property def Ly(self): """Axis-aligned length along the `y`-axis in **Angstroms**. Calculated as: .. math:: L_y = n_y * (d_t + 2 r_{\\mathrm{vdW}}) """ return self.ny * (self.dt + 2 * self.vdw_radius) @property def bundle_geometry(self): """Bundle geometry.""" return self._bundle_geometry @bundle_geometry.setter def bundle_geometry(self, value): if value is not None and value not in self._bundle_geometries: print('Unrecognized `bundle_geometry`: {!r}'.format(value)) value = None self._bundle_geometry = value @property def bundle_packing(self): """Bundle packing.""" return self._bundle_packing @bundle_packing.setter def bundle_packing(self, value): if value is None and \ self.bundle_geometry in ('square', 'rectangle'): value = 'ccp' elif value is None and \ self.bundle_geometry in ('triangle', 'hexagon'): value = 'hcp' if value is not None and value not in ('ccp', 'hcp'): raise ValueError('Expected value to be `hcp` or `ccp`') self._bundle_packing = value # self.generate_bundle_coords() @bundle_packing.deleter def bundle_packing(self): del self._bundle_packing @property def mass(self): """Bundle mass.""" return self.Ntubes * super().mass @property def bundle_mass(self): """An alias for :attr:`~NanotubeBundleMixin.mass`.""" return self.mass @property def Natoms(self): """Number of atoms in nanotube bundle. **Returns total number of atoms in nanotube bundle.** Use :attr:`~NanotubeBundleMixin.Natoms_per_tube` to get a list of the number of atoms in each nanotube in the bundle. """ if self.is_bundle: return sum(self.Natoms_list) return super().Natoms @property def Natoms_per_bundle(self): """Alias for :attr:`~NanotubeBundleMixin.Natoms`.""" return self.Natoms @property def Natoms_list(self): """:class:`~python:list` of `Natoms` per nanotube in bundle.""" if self.is_bundle: return [nanotube.Natoms for nanotube in self.bundle_list] return super().Natoms_list @property def Ntubes(self): """Number of nanotubes in bundle.""" try: return len(self.bundle_coords) except AttributeError: return 1 @property def Natoms_per_tube(self): """Alias for :attr:`~NanotubeBundleMixin.Natoms_list`.""" try: val = self.Natoms_list[:] return val if len(val) > 1 else val[0] except AttributeError: return super().Natoms_per_tube
[docs] def init_bundle_parameters(self): """Initialize bundle attributes.""" self.bundle_list = [] self.generate_bundle_coords() fmtstr = super().fmtstr match = re.search('[nL]z=', fmtstr) if match: self.fmtstr = fmtstr[:match.start()] + \ "nx={nx!r}, ny={ny!r}, " + fmtstr[match.start():] + \ ", bundle_packing={bundle_packing!r}, " + \ "bundle_geometry={bundle_geometry!r}"
[docs] def generate_bundle_coords(self): """Generate coordinates of bundle tubes.""" self.r1 = Vector() self.r2 = Vector() self.bundle_coords = [] self.r1.x = self.dt + 2 * self.vdw_radius if self.bundle_packing in ('cubic', 'ccp'): self.r2.y = self.r1.x else: self.r2.x = self.r1.x * np.cos(2 * np.pi / 3) self.r2.y = self.r1.x * np.sin(2 * np.pi / 3) if self.bundle_packing is None: self._bundle_packing = 'hcp' if self.bundle_geometry == 'hexagon': nrows = max(self.nx, self.ny, 3) if nrows % 2 != 1: nrows += 1 ntubes_per_end_rows = int((nrows + 1) / 2) row = 0 ntubes_per_row = nrows while ntubes_per_row >= ntubes_per_end_rows: if row == 0: for n in range(ntubes_per_row): dr = n * self.r1 self.bundle_coords.append(dr) else: for nx in range(ntubes_per_row): for ny in (-row, row): dr = Vector() dr.x = abs(ny * self.r2.x) dr.y = ny * self.r2.y dr = nx * self.r1 + dr self.bundle_coords.append(dr) row += 1 ntubes_per_row = nrows - row elif self.bundle_geometry == 'rectangle': Lx = self.Lx for nx in range(self.nx): for ny in range(self.ny): dr = nx * self.r1 + ny * self.r2 while dr.x < 0: dr.x += Lx self.bundle_coords.append(dr) elif self.bundle_geometry == 'square': pass elif self.bundle_geometry == 'triangle': pass else: for nx in range(self.nx): for ny in range(self.ny): dr = nx * self.r1 + ny * self.r2 self.bundle_coords.append(dr)
[docs]class NanotubeBundleBase(NanotubeBundleMixin, NanoStructureBase): """Nanotube bundle structure base class. Parameters ---------- nx, ny : :class:`~python:int` bundle_packing : {None, 'hcp', 'ccp'}, optional bundle_geometry : {None, 'hexagon', 'rectangle'}, optional """ _bundle_geometries = ['square', 'rectangle', 'hexagon'] def __init__(self, *args, nx=1, ny=1, bundle_packing=None, bundle_geometry=None, **kwargs): super().__init__(*args, **kwargs) self.nx = nx self.ny = ny self.bundle_geometry = bundle_geometry self.bundle_packing = bundle_packing self.is_bundle = False if nx != 1 or ny != 1 or bundle_geometry is not None: self.is_bundle = True self.init_bundle_parameters()
[docs] def todict(self): """Return :class:`~python:dict` of constructor parameters.""" attrdict = super().todict() if self.is_bundle: attrdict.update(dict(nx=self.nx, ny=self.ny, bundle_packing=self.bundle_packing, bundle_geometry=self.bundle_geometry)) return attrdict