# -*- coding: utf-8 -*-
"""
===============================================================================
Graphene structure class (:mod:`sknano.core.structures.graphene`)
===============================================================================
.. currentmodule:: sknano.core.structures.graphene
"""
from __future__ import absolute_import, division, print_function
from __future__ import unicode_literals
__docformat__ = 'restructuredtext en'
# import itertools
import numbers
import numpy as np
# from sknano.core.atoms import Atom
from sknano.core.crystallography import Crystal3DLattice, UnitCell
from sknano.core.math import Vector, vector as vec
from sknano.core.refdata import aCC # , grams_per_Da
from .base import NanoStructureBase, r_CC_vdw
__all__ = ['GraphenePrimitiveCell', 'GrapheneConventionalCell',
'GrapheneMixin', 'Graphene', 'GrapheneBase',
'PrimitiveCellGraphene', 'HexagonalGraphene',
'HexagonalCellGraphene', 'ConventionalCellGraphene',
'RectangularGraphene', 'RectangularCellGraphene',
'GrapheneNanoribbon']
[docs]class GraphenePrimitiveCell(UnitCell):
"""Primitive graphene unit cell with 2 atom basis.
Parameters
----------
bond : :class:`~python:float`, optional
a : :class:`~python:float`, optional
gamma : {60, 120}, optional
basis : {:class:`~python:list`, :class:`~sknano.core.atoms.BasisAtoms`}, \
optional
coords : {:class:`~python:list`}, optional
cartesian : {:class:`~python:bool`}, optional
"""
def __init__(self, bond=aCC, a=np.sqrt(3) * aCC, c=2 * r_CC_vdw,
gamma=60, basis=['C', 'C'],
coords=[[0, 0, r_CC_vdw], [aCC, 0, r_CC_vdw]],
cartesian=True):
lattice = Crystal3DLattice(a=a, b=a, c=c, alpha=90., beta=90.,
gamma=gamma)
lattice.rotate(angle=-np.pi / 6, axis='z')
super().__init__(lattice, basis, coords, cartesian)
[docs]class GrapheneConventionalCell(UnitCell):
"""Conventional (rectangular) graphene unit cell with 4 atom basis.
Parameters
----------
bond : :class:`~python:float`, optional
a, b : :class:`~python:float`, optional
basis : {:class:`~python:list`, :class:`~sknano.core.atoms.BasisAtoms`}, \
optional
coords : {:class:`~python:list`}, optional
cartesian : {:class:`~python:bool`}, optional
"""
def __init__(self, bond=aCC, a=3 * aCC, b=np.sqrt(3) * aCC, c=2 * r_CC_vdw,
basis=4 * ['C'],
coords=[[0, 0, r_CC_vdw], [aCC, 0, r_CC_vdw],
[3 / 2 * aCC, np.sqrt(3) / 2 * aCC, r_CC_vdw],
[5 / 2 * aCC, np.sqrt(3) / 2 * aCC, r_CC_vdw]],
cartesian=True):
lattice = Crystal3DLattice.orthorhombic(a, b, c)
super().__init__(lattice, basis, coords, cartesian)
[docs]class GrapheneMixin:
"""Mixin class for graphene structure classes."""
@property
def n1(self):
"""Number of unit cells along :attr:`Crystal3DLattice.a1`."""
try:
return self._n1
except AttributeError:
return int(np.ceil(self.l1 / self.unit_cell.a1.length))
@n1.setter
def n1(self, value):
self._n1 = int(value)
@property
def n2(self):
"""Number of unit cells along :attr:`Crystal3DLattice.a2`."""
try:
return self._n2
except AttributeError:
return int(np.ceil(self.l2 / self.unit_cell.a2.length))
@n2.setter
def n2(self, value):
self._n2 = int(value)
@property
def r1(self):
"""Vector :attr:`GrapheneMixin.n1` :math:`\\times` \
:attr:`Crystal3DLattice.a1`."""
return self.n1 * self.unit_cell.a1
@property
def r2(self):
"""Vector :attr:`GrapheneMixin.n2` :math:`\\times` \
:attr:`Crystal3DLattice.a2`."""
return self.n2 * self.unit_cell.a2
@property
def area(self):
"""Total area of graphene supercell."""
r1 = self.r1
r2 = self.r2
a = r1.norm * r2.norm * np.sin(vec.angle(r1, r2))
return a
@property
def N(self):
"""Number of graphene unit cells.
.. math::
N = \\frac{A_{\\mathrm{sheet}}}{A_{\\mathrm{cell}}}
"""
unit_cell = self.unit_cell
a1 = unit_cell.a1
a2 = unit_cell.a2
unit_cell_area = a1.norm * a2.norm * np.sin(vec.angle(a1, a2))
return int(self.area / unit_cell_area)
@property
def Natoms(self):
"""Total number of atoms."""
return self.nlayers * self.Natoms_per_layer
@property
def Natoms_per_layer(self):
"""Number of atoms per layer."""
return self.N * self.Natoms_per_unit_cell
@property
def Natoms_per_unit_cell(self):
"""Number of atoms per unit cell."""
return self.unit_cell.basis.Natoms
[docs]class GrapheneBase(GrapheneMixin, NanoStructureBase):
"""Graphene base structure class.
.. versionadded:: 0.3.11
Parameters
----------
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'])
bond : float, optional
:math:`\\mathrm{a}_{\\mathrm{CC}} =` distance between
nearest neighbor atoms. Must be in units of **Angstroms**.
nlayers : int, optional
Number of graphene layers (default: 1)
layer_spacing : float, optional
Distance between layers in **Angstroms** (default: 3.4).
stacking_order : {'AA', 'AB'}, optional
Stacking order of graphene 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.
verbose : bool, optional
verbose output
"""
def __init__(self, nlayers=1, layer_spacing=2 * r_CC_vdw,
layer_rotation_angles=None, layer_rotation_increment=None,
stacking_order='AB', degrees=True, **kwargs):
degrees = kwargs.pop('deg2rad', degrees)
layer_rotation_angles = kwargs.pop('layer_rotation_angle',
layer_rotation_angles)
super().__init__(**kwargs)
self.layers = []
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':
self.layer_shift.x = self.bond
self.fmtstr = \
", ".join((super().fmtstr, 'nlayers={nlayers!r}',
'layer_spacing={layer_spacing!r}',
'layer_rotation_angles={layer_rotation_angles!r}',
'stacking_order={stacking_order!r}'))
def __str__(self):
strrep = super().__str__()
strrep += '\n'
strrep += 'area: {:.2f} \u212b^2\n'.format(self.area)
strrep += 'N atoms/unit cell: {:d}\n'.format(self.Natoms_per_unit_cell)
strrep += 'N unit cells: {:d}\n'.format(self.N)
strrep += 'N atoms/layer: {:d}\n'.format(self.Natoms_per_layer)
strrep += 'N layers: {:d}\n'.format(self.nlayers)
return strrep
[docs] def todict(self):
"""Return :class:`~python:dict` of constructor parameters."""
attr_dict = super().todict()
attr_dict.update(dict(nlayers=self.nlayers,
layer_spacing=self.layer_spacing,
layer_rotation_angles=self.layer_rotation_angles,
stacking_order=self.stacking_order))
return attr_dict
[docs]class PrimitiveCellGraphene(GrapheneBase):
"""Graphene structure class built from a primitive unit cell.
.. versionadded:: 0.3.11
Parameters
----------
edge_length : float, optional
length of graphene edges in **Angstroms**.
.. versionchanged:: 0.4.0
Changed units from nanometers to **Angstroms**
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'])
bond : float, optional
:math:`\\mathrm{a}_{\\mathrm{CC}} =` distance between
nearest neighbor atoms. Must be in units of **Angstroms**.
nlayers : int, optional
Number of graphene layers (default: 1)
layer_spacing : float, optional
Distance between layers in **Angstroms** (default: 3.4).
stacking_order : {'AA', 'AB'}, optional
Stacking order of graphene 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.
verbose : bool, optional
verbose output
"""
def __init__(self, edge_length=None, ncells=None, **kwargs):
self.l1 = self.l2 = self.edge_length = edge_length
super().__init__(**kwargs)
self.unit_cell = \
GraphenePrimitiveCell(bond=self.bond, basis=self.basis)
self.scaling_matrix = [self.n1, self.n2, self.nlayers]
self.fmtstr = ', '.join((super().fmtstr,
'edge_length={edge_length!r}'))
[docs] def todict(self):
"""Return :class:`~python:dict` of constructor parameters."""
attr_dict = super().todict()
attr_dict.update(dict(edge_length=self.edge_length))
return attr_dict
HexagonalGraphene = HexagonalCellGraphene = PrimitiveCellGraphene
[docs]class ConventionalCellGraphene(GrapheneBase):
"""Graphene structure class built from a conventional unit cell.
.. versionadded:: 0.3.11
Parameters
----------
armchair_edge_length : float, optional
Length of armchair edge in **Angstroms**
.. versionchanged:: 0.4.0
Changed units from nanometers to **Angstroms**
zigzag_edge_length : float, optional
Length of zigzag edge in **Angstroms**
.. versionchanged:: 0.4.0
Changed units from nanometers to **Angstroms**
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'])
bond : float, optional
:math:`\\mathrm{a}_{\\mathrm{CC}} =` distance between
nearest neighbor atoms. Must be in units of **Angstroms**.
nlayers : int, optional
Number of graphene layers (default: 1)
layer_spacing : float, optional
Distance between layers in **Angstroms** (default: 3.4).
stacking_order : {'AA', 'AB'}, optional
Stacking order of graphene 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.
verbose : bool, optional
verbose output
"""
def __init__(self, l1=None, l2=None, n1=None, n2=None, **kwargs):
self.l1 = self.armchair_edge_length = \
kwargs.pop('armchair_edge_length', kwargs.pop('length', l1))
self.l2 = self.zigzag_edge_length = \
kwargs.pop('zigzag_edge_length', kwargs.pop('width', l2))
super().__init__(**kwargs)
self.unit_cell = GrapheneConventionalCell(bond=self.bond,
basis=2 * self.basis)
self.scaling_matrix = [self.n1, self.n2, self.nlayers]
self.fmtstr = ', '.join((super().fmtstr, 'l1={l1!r}', 'l2={l2!r}',
'n1={n1!r}', 'n2={n2!r}'))
[docs] def todict(self):
"""Return :class:`~python:dict` of constructor parameters."""
attr_dict = super().todict()
attr_dict.update(dict(l1=self.l1, l2=self.l2, n1=self.n1, n2=self.n2))
return attr_dict
RectangularGraphene = RectangularCellGraphene = ConventionalCellGraphene
[docs]class GrapheneNanoribbon(ConventionalCellGraphene):
"""Graphene nanoribbon structure class.
.. versionadded:: 0.3.11
"""
def __init__(self, **kwargs):
super().__init__(nlayers=1, **kwargs)
[docs]class Graphene(ConventionalCellGraphene):
"""Graphene structure class.
.. versionchanged:: 0.3.11
`Graphene` is now a sub-class of the `ConventionalCellGraphene`
class to maintain backwards compatibility and also includes 2 new
classmethods: :meth:`~Graphene.from_primitive_cell`
and :meth:`~Graphene.from_conventional_cell`.
Parameters
----------
armchair_edge_length : float, optional
Length of armchair edge in **Angstroms**
.. versionadded:: 0.3.10
.. versionchanged:: 0.4.0
Changed units from nanometers to **Angstroms**
zigzag_edge_length : float, optional
Length of zigzag edge in **Angstroms**
.. versionadded:: 0.3.10
.. versionchanged:: 0.4.0
Changed units from nanometers to **Angstroms**
length : float, optional
Length of armchair edge in **Angstroms**
.. deprecated:: 0.3.10
Use `armchair_edge_length` instead
width : float, optional
Width of graphene sheet in **Angstroms**
.. deprecated:: 0.3.10
Use `zigzag_edge_length` instead
edge : {'AC', 'armchair', 'ZZ', 'zigzag'}, optional
**A**\ rm\ **C**\ hair or **Z**\ ig\ **Z**\ ag edge along
the `length` of the sheet.
.. deprecated:: 0.3.10
No longer used!
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` 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 graphene layers (default: 1)
layer_spacing : float, optional
Distance between layers in **Angstroms** (default: 3.4).
stacking_order : {'AA', 'AB'}, optional
Stacking order of graphene 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.
verbose : bool, optional
verbose output
"""
@classmethod
[docs] def from_primitive_cell(cls, **kwargs):
"""See the `PrimitiveCellGraphene` structure class documentation."""
return PrimitiveCellGraphene(**kwargs)
@classmethod
[docs] def from_conventional_cell(cls, **kwargs):
"""See the `ConventionalCellGraphene` structure class documentation."""
return ConventionalCellGraphene(**kwargs)