# -*- coding: utf-8 -*-
"""
==============================================================================
Unrolled SWNT structure class (:mod:`sknano.structures._unrolled_swnt`)
==============================================================================
.. currentmodule:: sknano.structures._unrolled_swnt
"""
from __future__ import absolute_import, division, print_function
from __future__ import unicode_literals
__docformat__ = 'restructuredtext en'
import numbers
import numpy as np
from sknano.core.atoms import BasisAtom as Atom, BasisAtoms as Atoms
from sknano.core.crystallography import Crystal3DLattice, UnitCell
from sknano.core.math import Vector
from sknano.core.refdata import aCC
from ._base import NanoStructureBase, r_CC_vdw
from ._extras import get_chiral_indices
from ._swnt import NanotubeMixin, compute_Ch, compute_T, compute_chiral_angle
__all__ = ['UnrolledSWNTMixin', 'UnrolledSWNT']
[docs]class UnrolledSWNTMixin:
"""Mixin class for unrolled nanotubes."""
@property
def Lx(self):
return self.nx * self.Ch / 10
@property
def Ly(self):
return self.nlayers * self.layer_spacing / 10
@property
def nx(self):
"""Number of unit cells 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 real positive number.')
self._nx = int(value)
@property
def nlayers(self):
"""Number of layers."""
return self._nlayers
@nlayers.setter
def nlayers(self, value):
"""Set :attr:`nlayers`."""
if not (isinstance(value, numbers.Number) or value > 0):
raise TypeError('Expected a real positive number.')
self._nlayers = int(value)
@nlayers.deleter
def nlayers(self):
del self._nlayers
@property
def fix_Lx(self):
return self._fix_Lx
@fix_Lx.setter
def fix_Lx(self, value):
if not isinstance(value, bool):
raise TypeError('Expected `True` or `False`')
self._fix_Lx = value
self._integral_nx = False if self.fix_Lx else True
class UnrolledSWNT(UnrolledSWNTMixin, NanotubeMixin, NanoStructureBase):
"""Unrolled SWNT structure class.
Parameters
----------
*Ch : {:class:`python:tuple` or :class:`python:int`\ s}
Either a 2-tuple of integers (i.e., *Ch = ((n, m)) or
2 integers (i.e., *Ch = (n, m) specifying the chiral indices
of the nanotube chiral vector
:math:`\\mathbf{C}_h = n\\mathbf{a}_1 + m\\mathbf{a}_2 = (n, m)`.
nx : :class:`python:int`, optional
Number of repeat unit cells in the :math:`x` direction, along
the *unrolled* chiral vector.
nz : :class:`python:int`, optional
Number of repeat unit cells in the :math:`z` direction, along
the *length* of the nanotube.
basis : {:class:`python:list`}, optional
List of :class:`python:str`\ s of element symbols or atomic number
of the two atom basis (default: ['C', 'C'])
.. versionadded:: 0.3.10
element1, element2 : {str, int}, optional
Element symbol or atomic number of basis
:class:`~sknano.core.Atom`\ s 1 and 2
.. deprecated:: 0.3.10
Use `basis` instead
bond : float, optional
:math:`\\mathrm{a}_{\\mathrm{CC}} =` distance between
nearest neighbor atoms. Must be in units of **Angstroms**.
nlayers : int, optional
Number of layers (default: 1)
layer_spacing : float, optional
Distance between layers in **Angstroms** (default: 3.35).
stacking_order : {'AA', 'AB'}, optional
Stacking order of layers.
layer_rotation_angles : list, optional
list of rotation angles for each layer in **degrees** if
`degrees` is `True` (default), otherwise in radians.
The list length must equal the number of layers.
layer_rotation_increment : float, optional
incremental layer rotation angle in **degrees** if
`degrees` is `True` (default), otherwise in radians.
Each subsequent layer will
be rotated by `layer_rotation_increment` relative to the layer
below it.
Lx : float, optional
Length of the unrolled swnt sheet along the chiral vector
in units of **nanometers**. Overrides the `nx` value.
fix_Lx : bool, optional
Generate the unrolled swnt sheet with the length along the
chiral vector as close to the specified :math:`L_x` as possible.
If `True`, then non integer :math:`n_x` cells are permitted.
Lz : float, optional
Length of the unrolled swnt sheet along the translation vector
in units of **nanometers**. Overrides the `nz` value.
fix_Lz : bool, optional
Generate the unrolled swnt sheet with the length along the
translation vector as close to the specified :math:`L_z` as possible.
If `True`, then non integer :math:`n_z` cells are permitted.
verbose : bool, optional
if `True`, show verbose output
Examples
--------
>>> from sknano.structures import UnrolledSWNT
"""
def __init__(self, *Ch, nx=1, nz=1, basis=['C', 'C'], bond=aCC,
gutter=None, nlayers=1, layer_spacing=2 * r_CC_vdw,
layer_rotation_angles=None,
layer_rotation_increment=None, degrees=True,
stacking_order='AB', Lx=None, fix_Lx=False,
Lz=None, fix_Lz=False, wrap_coords=False, **kwargs):
n, m, kwargs = get_chiral_indices(*Ch, **kwargs)
super().__init__(basis=basis, bond=bond, **kwargs)
if gutter is None:
gutter = self.vdw_radius
self.gutter = gutter
self.wrap_coords = wrap_coords
self.n = n
self.m = m
self.fix_Lx = fix_Lx
if Lx is not None:
self.nx = 10 * float(Lx) / self.Ch
elif nx is not None:
self.nx = nx
else:
self.nx = 1
self.fix_Lz = fix_Lz
if Lz is not None:
self.nz = 10 * float(Lz) / self.T
elif nz is not None:
self.nz = nz
else:
self.nz = 1
self.nlayers = nlayers
self.layer_spacing = layer_spacing
if layer_rotation_increment is not None and \
layer_rotation_angles is None:
layer_rotation_angles = layer_rotation_increment * \
np.arange(self.nlayers)
elif isinstance(layer_rotation_angles, numbers.Number):
layer_rotation_angles = layer_rotation_angles * \
np.ones(self.nlayers)
elif layer_rotation_angles is None or \
isinstance(layer_rotation_angles, (tuple, list, np.ndarray)) \
and len(layer_rotation_angles) != self.nlayers:
layer_rotation_angles = np.zeros(self.nlayers)
degrees = False
if degrees:
layer_rotation_angles = \
np.radians(np.asarray(layer_rotation_angles)).tolist()
self.layer_rotation_angles = \
np.asarray(layer_rotation_angles).tolist()
self.layer_shift = Vector()
self.stacking_order = stacking_order
if nlayers > 1 and stacking_order == 'AB':
chiral_angle = compute_chiral_angle(self.n, self.m, degrees=False)
self.layer_shift.x = self.bond * np.cos(np.pi/6 - chiral_angle)
self.layer_shift.z = -self.bond * np.sin(np.pi/6 - chiral_angle)
self.generate_unit_cell()
self.crystal_cell.scaling_matrix = \
[int(np.ceil(self.nx)), self.nlayers, int(np.ceil(self.nz))]
self.fmtstr = "{Ch!r}, nx={nx!r}, nz={nz!r}, bond={bond!r}, " + \
"basis={basis!r}, nlayers={nlayers!r}, " + \
"layer_spacing={layer_spacing!r}, " + \
"stacking_order={stacking_order!r}"
def generate_unit_cell(self):
"""Generate the nanotube unit cell."""
eps = 0.01
e1 = self.element1
e2 = self.element2
N = self.N
T = self.T
rt = self.rt
psi, tau, dpsi, dtau = self.unit_cell_symmetry_params
a = compute_Ch(self.n, self.m, bond=self.bond)
b = self.layer_spacing
c = compute_T(self.n, self.m, bond=self.bond, length=True)
lattice = Crystal3DLattice.orthorhombic(a, b, c)
basis = Atoms()
if self.verbose:
print('dpsi: {}'.format(dpsi))
print('dtau: {}\n'.format(dtau))
for i in range(N):
for j, element in enumerate((e1, e2), start=1):
theta = i * psi
h = i * tau
if j == 2:
theta += dpsi
h -= dtau
x = rt * theta
z = h
while z > T - eps:
z -= T
if z < 0:
z += T
xs, ys, zs = \
lattice.cartesian_to_fractional([x, 0, z])
if self.wrap_coords:
xs, ys, zs = \
lattice.wrap_fractional_coordinate([xs, ys, zs])
if self.debug:
print('i={}: x, z = ({:.6f}, {:.6f})'.format(i, x, z))
atom = Atom(element, lattice=lattice, xs=xs, ys=ys, zs=zs)
atom.rezero()
if self.verbose:
print('Basis Atom:\n{}'.format(atom))
basis.append(atom)
self.unit_cell = UnitCell(lattice=lattice, basis=basis)
def todict(self):
"""Return :class:`~python:dict` of `SWNT` attributes."""
return dict(Ch=(self.n, self.m), nx=self.nx, nz=self.nz,
bond=self.bond, basis=self.basis,
nlayers=self.nlayers, layer_spacing=self.layer_spacing,
stacking_order=self.stacking_order)