"""
Classes and functions for building single- and multi-walled nanotubes.
Copyright Schrodinger, LLC. All rights reserved."""
# Contributor: Thomas F. Hughes
import math
import numpy
from schrodinger.application.matsci import textlogger as tlog
from schrodinger.application.matsci.coarsegrain import set_as_coarse_grain
from schrodinger.application.matsci.nano import check
from schrodinger.application.matsci.nano import constants
from schrodinger.application.matsci.nano import sheet
from schrodinger.application.matsci.nano import slab
from schrodinger.application.matsci.nano import util
from schrodinger.application.matsci.nano import xtal
from schrodinger.infra import mm
from schrodinger.infra.mmerr import ErrorHandler
from schrodinger.structutils import build
from schrodinger.structutils import transform
_version = '$Revision 0.0 $'
TWOPI = 2 * math.pi
[docs]class Rectangle(object):
"""
Manage the properties of a rectangle.
"""
INSIDETHRESH = 0.00001
[docs] def __init__(self, origin, bottom, left, end):
"""
Create an instance.
:type origin: numpy.array
:param origin: lower left point
:type bottom: numpy.array
:param bottom: lower right point
:type left: numpy.array
:param left: upper left point
:type end: numpy.array
:param end: upper right point
"""
self.origin = origin
self.bottom = bottom
self.left = left
self.end = end
[docs] def linear_equation(self, ixy, fxy, x):
"""
Return y = m*x + b for m and b from the line formed by
initial point ixy and final point fxy.
:type ixy: numpy.array
:param ixy: initial point on line
:type fxy: numpy.array
:param fxy: final point on line
:type x: float
:param x: domain argument
:rtype: float
:return: y, range value
"""
ix, iy = ixy
fx, fy = fxy
denom = fx - ix
numer = fy - iy
slope = numer / denom
numer = fx * iy - ix * fy
interc = numer / denom
y = slope * x + interc
return y
[docs] def insideRectangle(self, xy, logger=None):
"""
Return boolean specifying if the provided plane coordinates
lie within the boundary.
:type xy: numpy.array
:param xy: plane coordinates
:type logger: logging.getLogger
:param logger: output logger
:rtype: bool, bool
:return: insidex, insidey, inside the x-boundary or not, same for
y-boundary
"""
x, y = xy
# given (x, y) find points on the lines defined by the boundaries of
# the rectangle, handle the case of potentially vertical left and right
# sides by reversing input vectors and passing x = f(y) rather than
# y = f(x)
xleft = self.linear_equation(self.origin[::-1], self.left[::-1], y)
xright = self.linear_equation(self.bottom[::-1], self.end[::-1], y)
ybottom = self.linear_equation(self.origin, self.bottom, x)
ytop = self.linear_equation(self.left, self.end, x)
# provide a little buffer area for float comparison
xleft += -1 * self.INSIDETHRESH
xright += self.INSIDETHRESH
ybottom += -1 * self.INSIDETHRESH
ytop += self.INSIDETHRESH
# is x inside the boundary, same for y
insidex = False
insidey = False
if xleft <= x <= xright:
insidex = True
if ybottom <= y <= ytop:
insidey = True
return insidex, insidey
[docs]class NanoSheet(object):
"""
Create a sheet.HoneycombLattice that is large enough
so that the nanotube sheet can be cut out from it.
"""
ANGLEMEDIUM = sheet.HoneycombUnitCell.ANGLEMEDIUM
[docs] def __init__(self, nanotube_sheet_obj):
"""
Create an instance.
:type nanotube_sheet_obj: NanoTubeSheet
:param nanotube_sheet_obj: contains parameters of the
nanotube sheet
"""
self.element1 = nanotube_sheet_obj.element1
self.element2 = nanotube_sheet_obj.element2
self.bondlength = nanotube_sheet_obj.bondlength
self.ncell1 = None
self.edgetype1 = constants.Constants.ARMCHAIR
self.ncell2 = None
self.edgetype2 = constants.Constants.ARMCHAIR
self.termfrag = constants.Constants.TERMFRAGS[0]
self.min_term_frags = constants.Constants.MIN_TERM_FRAGS
self.is_coarse_grain = nanotube_sheet_obj.is_coarse_grain
self.nanotube_sheet_obj = nanotube_sheet_obj
self.nanosheet_obj = sheet.HoneycombLattice(
self.element1, self.element2, self.bondlength, self.ncell1,
self.edgetype1, self.ncell2, self.edgetype2, self.termfrag,
self.min_term_frags, self.is_coarse_grain)
[docs] def defineVectors(self):
"""
Define HoneycombLattice and NanoTubeSheet lattice, etc. vectors.
:rtype: numpy.array, numpy.array
:return: lattvec1, lattvec2, the HoneycombLattice lattice vectors
"""
# get HoneycombLattice lattice vectors
bondlength = self.nanosheet_obj.bondlength
lattvec1, lattvec2 = sheet.get_lattice_vectors(bondlength)
# Make another handle for ease of typing/readability
tube_sheet = self.nanotube_sheet_obj
# get NanoTubeSheet lattice vectors
tube_sheet.lattvec1, tube_sheet.lattvec2 = \
tube_sheet.redefineLatticeVecs(lattvec1, lattvec2)
# get NanoTubeSheet basis vectors, i.e. chiral and translation vectors
tube_sheet.chiral, tube_sheet.translat = \
get_tube_vectors(ncells=tube_sheet.ncells,
nindex=tube_sheet.nindex,
mindex=tube_sheet.mindex,
lattvec1=tube_sheet.lattvec1,
lattvec2=tube_sheet.lattvec2)
return lattvec1, lattvec2
[docs] def getGrowParams(self, lattvec1, lattvec2):
"""
Get HoneycombLattice grow parameters.
:type lattvec1: numpy.array
:param lattvec1: lattice vector 1
:type lattvec2: numpy.array
:param lattvec2: lattice vector 2
:rtype: float, numpy.array, float, numpy.array
:return: grow1len, grow1unit, grow2len, grow2unit,
the lengths and unit vectors of the grow vectors
"""
# get grow vectors
grow = sheet.Grow(self.edgetype1, self.edgetype2, self.ncell1,
self.ncell2)
growvec1, growvec2 = grow.getGrowVectors(lattvec1, lattvec2)
# save length
grow1len = numpy.linalg.norm(growvec1)
grow2len = numpy.linalg.norm(growvec2)
# rotate grow vectors so that the first one falls on the x-axis
angle_to_xaxis = transform.get_angle_between_vectors(
growvec1, numpy.array(transform.X_AXIS))
growvec1 = util.get_rotated_vector(growvec1, angle_to_xaxis)
growvec2 = util.get_rotated_vector(growvec2, angle_to_xaxis)
# get unit vectors
grow1unit = transform.get_normalized_vector(growvec1)
grow2unit = transform.get_normalized_vector(growvec2)
grow1unit = numpy.delete(grow1unit, 2)
grow2unit = numpy.delete(grow2unit, 2)
return grow1len, grow1unit, grow2len, grow2unit
[docs] def changeBasis(self, grow1unit, grow2unit):
"""
Change the basis of the NanoTubeSheet to that of the NanoSheet.
:type grow1unit: numpy.array
:param grow1unit: unit vector of first grow vector
:type grow2unit: numpy.array
:param grow2unit: unit vector of second grow vector
:rtype: float, float
:return: coef1, coef2, coefficients of the end vector in the grow
basis
"""
# build projection matrix
dot11 = numpy.dot(grow1unit, grow1unit)
dot12 = numpy.dot(grow1unit, grow2unit)
dot21 = numpy.dot(grow2unit, grow1unit)
dot22 = numpy.dot(grow2unit, grow2unit)
projmatrix = numpy.matrix([[dot11, dot12], [dot21, dot22]])
# the end vector spans the area of the NanoTubeSheet, project
# that vector onto the grow vectors to get the required dimensions
# of the NanoSheet
end = self.nanotube_sheet_obj.chiral + self.nanotube_sheet_obj.translat
dot1end = numpy.dot(grow1unit, end)
dot2end = numpy.dot(grow2unit, end)
vec = numpy.matrix([dot1end, dot2end])
# solve matrix equation
coefs = projmatrix.I * vec.T
coef1, coef2 = coefs.A1
return coef1, coef2
[docs] def defineDimensions(self, coef1, grow1len, coef2, grow2len):
"""
Define the dimensions of the NanoSheet.
:type coef1: float
:param coef1: coefficient of end vector on first grow vector
:type grow1len: float
:param grow1len: length of first grow vector
:type coef2: float
:param coef2: coefficient of end vector on second grow vector
:type grow2len: float
:param grow2len: length of second grow vector
"""
# do the math to find the values of ncell1 and ncell2 needed to
# build a big enough NanoSheet to contain the requested NanoTubeSheet
ncell1 = coef1 / grow1len
ncell1 = math.ceil(ncell1)
self.nanosheet_obj.ncell1 = int(ncell1) + 1
ncell2 = coef2 / grow2len
ncell2 = math.ceil(ncell2)
self.nanosheet_obj.ncell2 = int(ncell2) + 1
[docs] def rotateNanoSheet(self):
"""
Rotate the nanosheet so that lattice edge 1 is along the x-axis.
"""
axis = transform.Z_AXIS
rotmatrix = transform.get_rotation_matrix(axis, self.ANGLEMEDIUM)
transform.transform_structure(self.nanosheet_obj.structure, rotmatrix)
[docs] def getNanoSheet(self, logger=None):
"""
Get the sheet.HoneycombLattice from which the
nanotube sheet will be cut.
:type logger: logging.getLogger
:param logger: output logger
"""
# define vectors
lattvec1, lattvec2 = self.defineVectors()
# get grow parameters
grow1len, grow1unit, grow2len, grow2unit = self.getGrowParams(
lattvec1, lattvec2)
# change basis of NanoTubeSheet to NanoSheet
coef1, coef2 = self.changeBasis(grow1unit, grow2unit)
# define NanoSheet dimensions
self.defineDimensions(coef1, grow1len, coef2, grow2len)
# build NanoSheet
self.nanosheet_obj.buildLattice()
# remove PBCs
remove_pbc(self.nanosheet_obj.structure)
# rotate NanoSheet
self.rotateNanoSheet()
[docs]class NanoTubeSheet(object):
"""
Preprocess a nanosheet into a nanotube sheet which will be rolled
up into a nanotube.
"""
ZEROVEC = numpy.zeros(2)
[docs] def __init__(self, element1, element2, bondlength, nindex, mindex, ncells,
is_coarse_grain):
"""
Create an instance.
:type element1: str
:param element1: elemental symbol of the first atom
:type element2: str
:param element2: elemental symbol of the second atom
:type bondlength: float
:param bondlength: bond length between the first and
second atoms in Angstrom
:type nindex: int
:param nindex: first chiral index
:type mindex: int
:param mindex: second chiral index
:type ncells: int
:param ncells: number of unit cells
:type is_coarse_grain: bool
:param is_coarse_grain: Whether a coarse grain structure is being
created
"""
self.element1 = element1
self.element2 = element2
self.bondlength = bondlength
self.is_coarse_grain = is_coarse_grain
self.nindex = nindex
self.mindex = mindex
self.ncells = ncells
self.lattvec1 = None
self.lattvec2 = None
self.chiral = None
self.translat = None
self.termatoms = None
self.matchleft = None
self.matchright = None
self.nanosheet_st = None
self.nanotube_sheet_st = None
self.nanosheet_obj = None
[docs] @staticmethod
def redefineLatticeVecs(lattvec1, lattvec2):
"""
Redefine lattice vectors according to Dresselhaus.
:type lattvec1: numpy.array
:param lattvec1: first lattice vector
:type lattvec2: numpy.array
:param lattvec2: second lattice vector
:rtype: numpy.array, numpy.array
:return: nlattvec1, nlattvec2, first and second lattice
vectors redefined
"""
# reverse lattice vectors
nlattvec1 = numpy.delete(lattvec2, 2)
nlattvec2 = numpy.delete(lattvec1, 2)
return nlattvec1, nlattvec2
[docs] def renumberAtomLists(self, renumbermap):
"""
Apply the given renumbering map to the terminating and matching atom
lists.
:type renumbermap: dict
:param renumbermap: maps old indicies into new indicies
"""
self.matchleft = [renumbermap[index] for index in self.matchleft \
if renumbermap[index] is not None]
self.matchright = [renumbermap[index] for index in self.matchright \
if renumbermap[index] is not None]
self.termatoms = [renumbermap[index] for index in self.termatoms \
if renumbermap[index] is not None]
[docs] def cutOutNanoSheet(self, logger=None):
"""
Cut out the nanotube sheet from the nanosheet.
:type logger: logging.getLogger
:param logger: output logger
"""
def process_atom(atom, unit, rect, logger=None):
"""
Determine which boundary constraints are satisfied for the
target atom.
"""
atomxy = numpy.array(atom.xyz[:-1])
projlen = abs(numpy.dot(atomxy, unit))
insidex, insidey = rect.insideRectangle(atomxy)
inside = insidex and insidey
return projlen, inside, insidex, insidey
# define rectangle
origin = self.ZEROVEC
end = self.chiral + self.translat
rect = Rectangle(origin, self.chiral, self.translat, end)
# get a few parameters
chiralunit = transform.get_normalized_vector(self.chiral)
half_chiral_len = numpy.linalg.norm(self.chiral) / 2
# copy nanosheet structure to nanotubesheet structure
self.nanotube_sheet_st = self.nanosheet_st.copy()
# initialize a few iterables to hold atomic indicies of (1) atoms
# outside of the rectangle to be deleted, (2) atoms to be terminated,
# i.e. at the ends of the nanotube, and (3) atoms from the left and
# right sides of the nanotube which are to be matched up when the
# nanotube is rolled up. The relative left and right match index
# ordering is correct as is because of the way in which atom indexing
# is done row-wise in sheet.HoneycombLattice.
outsideatoms = set()
termatoms = set()
matchleft, matchright = set(), set()
for bond in self.nanotube_sheet_st.bond:
# get some descriptors about atoms in target bond
atom1, atom2 = bond.atom1, bond.atom2
len1, in1, inx1, iny1 = process_atom(atom1, chiralunit, rect)
len2, in2, inx2, iny2 = process_atom(atom2, chiralunit, rect)
# handle both outside
if not in1 and not in2:
outsideatoms.add(atom1.index)
outsideatoms.add(atom2.index)
# handle bond across boundary, classify
# the inside atom as terminating or matching
# left or right
elif in1 and not in2:
outsideatoms.add(atom2.index)
if inx2:
termatoms.add(atom1.index)
else:
if len1 < half_chiral_len:
matchleft.add(atom1.index)
else:
matchright.add(atom1.index)
# handle bond across boundary, classify
# the inside atom as terminating or matching
# left or right
elif not in1 and in2:
outsideatoms.add(atom1.index)
if inx1:
termatoms.add(atom2.index)
else:
if len2 < half_chiral_len:
matchleft.add(atom2.index)
else:
matchright.add(atom2.index)
# define class attrs, be sure to sort those for which order matters
self.termatoms = sorted(termatoms)
self.matchleft = sorted(matchleft)
self.matchright = sorted(matchright)
# delete outside atoms and renumber terminating and matching atoms
renumbermap = self.nanotube_sheet_st.deleteAtoms(
list(outsideatoms), True)
self.renumberAtomLists(renumbermap)
[docs] def delDanglingTermAtoms(self):
"""
Remove dangling atoms from the top and bottom of the nanotube sheet.
"""
# dangling terminating atoms that are also match atoms are needed so
# skip them
matches = set(self.matchleft + self.matchright)
termatoms = [index for index in self.termatoms if index not in matches]
dangling_atoms = []
new_term_atoms = []
for index in termatoms:
atom = self.nanotube_sheet_st.atom[index]
if atom.bond_total == 1:
dangling_atoms.append(atom.index)
new_term_atoms.append(next(atom.bonded_atoms).index)
# delete the dangling atoms
renumbermap = self.nanotube_sheet_st.deleteAtoms(dangling_atoms, True)
# extend the list of terminating atoms with those atoms that were bonded
# to the dangling atoms, no sorting necessary
self.termatoms.extend(new_term_atoms)
# apply renumbering map
self.renumberAtomLists(renumbermap)
[docs] def delZigZagMatchAtoms(self, logger=None):
"""
Remove overlapping match atoms for the zigzag case.
:type logger: logging.getLogger
:param logger: output logger
"""
new_match_left = []
for index in self.matchleft:
atom1 = self.nanotube_sheet_st.atom[index]
for atom2 in atom1.bonded_atoms:
if atom2.index not in self.matchleft:
new_match_left.append(atom2.index)
# delete the overlapping atoms
renumbermap = self.nanotube_sheet_st.deleteAtoms(self.matchleft, True)
# make the list of left matching atoms equal to those atoms that
# were bonded to the overlapping atoms, no sorting needed here
self.matchleft = list(new_match_left)
# apply renumbering map
self.renumberAtomLists(renumbermap)
[docs] def delChiralMatchAtoms(self, logger=None):
"""
Remove overlapping match atoms for the chiral case.
:type logger: logging.getLogger
:param logger: output logger
"""
def check_atom(atom, index, match, overlapping, new):
"""
Check if this atom is an overlapping atom.
"""
if atom.bond_total == 1:
before = self.nanotube_sheet_st.atom[match[index - 1]]
current = self.nanotube_sheet_st.atom[match[index]]
after = self.nanotube_sheet_st.atom[match[index + 1]]
if before.bond_total != 1 and current.bond_total != 1 and \
after.bond_total != 1:
overlapping.append(atom.index)
new.append(next(atom.bonded_atoms).index)
# start loop one in and end loop one out because index-1, index, and index+1
# are used
overlapping_atoms = []
new_match_left, new_match_right = [], []
for index, pair in enumerate(
zip(self.matchleft[1:-1], self.matchright[1:-1]), 1):
left, right = pair
leftatom = self.nanotube_sheet_st.atom[left]
rightatom = self.nanotube_sheet_st.atom[right]
check_atom(leftatom, index, self.matchright, overlapping_atoms,
new_match_left)
check_atom(rightatom, index, self.matchleft, overlapping_atoms,
new_match_right)
# delete the overlapping atoms
renumbermap = self.nanotube_sheet_st.deleteAtoms(
overlapping_atoms, True)
# extend the lists of left and right matching atoms with those atoms that were
# bonded to the overlapping atoms, sort the lists to maintain congruence with
# each other
self.matchleft.extend(new_match_left)
self.matchleft.sort()
self.matchright.extend(new_match_right)
self.matchright.sort()
# apply renumbering map
self.renumberAtomLists(renumbermap)
[docs] def buildNanoTubeSheet(self, termfrag, use_finite_bos=True, logger=None):
"""
Build the nanotube sheet.
:type termfrag: str
:param termfrag: terminate the lattice with a given fragment
:type use_finite_bos: bool
:param use_finite_bos: use a bond order protocol meant for finite
molecules
:type logger: logging.getLogger
:param logger: output logger
"""
# get the nanosheet
#
# pass this instance thus far to NanoSheet so that the NanoSheet
# class can use its properties and define some new properties for it
# as well, save the nanosheet instance for later
asheet = NanoSheet(self)
asheet.getNanoSheet(logger)
self.nanosheet_obj = asheet.nanosheet_obj
self.nanosheet_st = asheet.nanosheet_obj.structure
# cut out the nanotube sheet from the nanosheet
self.cutOutNanoSheet(logger)
# clean up the ends of the nanotube sheet
if use_finite_bos:
self.delDanglingTermAtoms()
# clean up the sides of the nanotube sheet for zigzag and chiral cases
if self.mindex == 0:
self.delZigZagMatchAtoms()
elif self.nindex != self.mindex:
self.delChiralMatchAtoms()
[docs]class NanoTube(object):
"""
Create a nanotube by rolling up a nanotube sheet.
"""
TITLEKEY = 's_m_title'
ENTRYKEY = 's_m_entry_name'
TITLENAME = 'nanotube'
NINDEX = 'i_matsci_N_Index'
MINDEX = 'i_matsci_M_Index'
NCELLS = 'i_matsci_N_Cells'
RADIUS = 'r_matsci_Radius/Ang.'
LENGTH = 'r_matsci_Length/Ang.'
MSGWIDTH = 50
NUMDECIMAL = 3
[docs] def __init__(self, element1, element2, bondlength, no_double_bonds, nindex,
mindex, ncells, termfrag, min_term_frags, is_coarse_grain):
"""
Create an instance.
:type element1: str
:param element1: elemental symbol of the first atom
:type element2: str
:param element2: elemental symbol of the second atom
:type bondlength: float
:param bondlength: bond length between the first and
second atoms in Angstrom
:type no_double_bonds: bool
:param no_double_bonds: disable the formation of double bonds
:type nindex: int
:param nindex: first chiral index
:type mindex: int
:param mindex: second chiral index
:type ncells: int
:param ncells: number of unit cells
:type termfrag: str
:param termfrag: terminate the lattice with a given fragment
:type min_term_frags: bool
:param min_term_frags: minimize the geometry of terminating
fragments
:type is_coarse_grain: bool
:param is_coarse_grain: Whether a coarse grain structure is being
created
"""
self.element1 = element1
self.element2 = element2
self.bondlength = bondlength
self.no_double_bonds = no_double_bonds
self.nindex = nindex
self.mindex = mindex
self.ncells = ncells
self.termfrag = termfrag
self.min_term_frags = min_term_frags
self.is_coarse_grain = is_coarse_grain
self.title = None
self.lattvec1 = None
self.lattvec2 = None
self.chiral = None
self.translat = None
self.radius = None
self.length = None
self.chiral_angle = None
self.termatoms = None
self.matchleft = None
self.matchright = None
self.frozen_tube_atoms = None
self.frozenatoms = None
self.bomsg = None
self.nanosheet_st = None
self.nanotube_sheet_st = None
self.nanotube_st = None
self.rotmatrix = None
self.atomic_number1 = None
self.atomic_number2 = None
[docs] def getChiralAngle(self, logger=None):
"""
Determine the chiral angle of the tube in degrees where
the chiral angle is angle(lattvec1, chiral) and is in
[0.0, 30.0], 0.0 for zigzag and 30.0 for armchair and the
rest are chiral.
:type logger: logging.getLogger
:param logger: output logger
"""
latt_vec_len = numpy.linalg.norm(self.lattvec1)
chiral_len = numpy.linalg.norm(self.chiral)
chiral_angle_denom = 2 * chiral_len
chiral_angle_numer = math.sqrt(3) * self.mindex * latt_vec_len
self.chiral_angle = math.asin(chiral_angle_numer / chiral_angle_denom)
self.chiral_angle = math.degrees(self.chiral_angle)
[docs] def tubularizeNanoTubeSheet(self, logger=None):
"""
Tubularize the nanotube sheet.
:type logger: logging.getLogger
:param logger: output logger
"""
self.nanotube_st = self.nanotube_sheet_st.copy()
chiralunit = transform.get_normalized_vector(self.chiral)
translatunit = transform.get_normalized_vector(self.translat)
# get r coordinate, i.e. radius of tube
self.length, self.radius = get_tube_dimensions(self.chiral,
self.translat)
# perform cylindrical transformation on atomic positions
for atom in self.nanotube_st.atom:
# these are the plane coords
cart = numpy.array(atom.xyz)
cart = numpy.delete(cart, 2)
# project atomic position onto tube vectors
cart_proj_chiral = numpy.dot(cart, chiralunit)
cart_proj_translat = numpy.dot(cart, translatunit)
# get angular and z coordinate
thetacoord = cart_proj_chiral / self.radius
zcoord = cart_proj_translat
# do r, theta, z -> x, y, z such that y is the tube axis and
# such that z is shifted so that the origin is not at the center
# but rather on the +z side of the tube. Do this for better
# visualization when viewed together with the nanotube sheet and
# nanosheet.
x = self.radius * math.sin(thetacoord)
y = zcoord
z = self.radius * math.cos(thetacoord) - self.radius
# relocate atom
atom.xyz = [x, y, z]
[docs] def rotateTube(self, logger=None):
"""
Rotate the nanotube so that the tube axis is the translation
vector.
"""
yaxis = numpy.array(transform.Y_AXIS)
translat = numpy.append(self.translat, 0.0)
self.rotmatrix = transform.get_alignment_matrix(yaxis, translat)
transform.transform_structure(self.nanotube_st, self.rotmatrix)
[docs] def preprocessMatchAtoms(self, inmatch):
"""
Dangling match atoms require two bonding partners so
make those atom indicies redundant in the list.
:type inmatch: list of ints
:param inmatch: non-redundant list
:rtype: list of ints
:return: outmatch, redundant list
"""
outmatch = []
for index in inmatch:
atom = self.nanotube_st.atom[index]
outmatch.append(atom.index)
if atom.bond_total == 1 and atom.index not in self.termatoms:
outmatch.append(atom.index)
return outmatch
[docs] def bondMatchingEdges(self, matchleft, matchright):
"""
Properly bond the left and right edges which meet each other
after rolling.
:type matchleft: list of ints
:param matchleft: indicies of atoms on the left
:type matchright: list of ints
:param matchright: indicies of atoms on the right
"""
for leftatom, rightatom in zip(matchleft, matchright):
self.nanotube_st.addBond(leftatom, rightatom, 1)
[docs] def doTermination(self, nanosheet_obj, fragment):
"""
Terminate the nanotube. Do this by hijacking the HoneycombLattice
instance and overwriting some attributes.
:type nanosheet_obj: sheet.HoneycombLattice
:param nanosheet_obj: contains information shared between this
instance and the nanotube instance
:type fragment: str
:param fragment: fragment name
:rtype: list of ints
:return: nanosheet_obj.frozenatoms, those fragment atoms bound to
the nanotube
"""
# overwrite nanosheet attrs
nanosheet_obj.terminatingatoms = list(self.termatoms)
nanosheet_obj.structure = self.nanotube_st.copy()
nanosheet_obj.frozenatoms = []
nanosheet_obj.termfrag = fragment
# terminate the nanotube
nanosheet_obj.terminateLattice()
# overwrite local nanotube attrs
self.nanotube_st = nanosheet_obj.structure.copy()
return nanosheet_obj.frozenatoms
[docs] def doBondOrders(self, logger=None):
"""
Assign bond orders to the nanotube.
:type logger: logging.getLogger
:param logger: output logger
"""
bomsg = """A Kekule structure can not be found for this (%d,%d) nanotube,
i.e. a single double bond could not be defined for all tube atoms.
Returning a tube containing only single bonds."""
mm.mmlewis_initialize(mm.MMERR_DEFAULT_HANDLER)
old_handler = mm.mmlewis_get_error_handler()
new_handler = ErrorHandler(queued=True, silent=True)
new_handler.push_level(6)
mm.mmlewis_set_error_handler(new_handler.handle)
# see MATSCI-1838 and SHARED-2801 for details, this is to
# specify that a breadth first algorithm be used for bond
# order assignment rather than the older protocol which
# made use of atom ordering, etc.
mm.mmlewis_set_option(mm.MMLEWIS_OPTION_BOND_PLACEMENT, \
mm.MMLEWIS_BOND_PLACEMENT_BFS, 0.0, '')
try:
mm.mmlewis_apply(self.nanotube_st)
except:
self.bomsg = bomsg % (self.nindex, self.mindex)
mm.mmlewis_set_error_handler(old_handler)
mm.mmlewis_terminate()
[docs] def removeHydrogens(self):
"""
Remove all hydrogens from the structure.
"""
build.delete_hydrogens(self.nanotube_st)
[docs] def minTerminatingFrags(self, nanosheet_obj):
"""
Minimize terminating fragments. Do this by hijacking
the HoneycombLattice instance and overwriting some attributes.
:type nanosheet_obj: sheet.HoneycombLattice
:param nanosheet_obj: contains information shared between this
instance and the nanotube instance
"""
# overwrite nanosheet attrs
nanosheet_obj.structure = self.nanotube_st.copy()
nanosheet_obj.frozenatoms = list(self.frozenatoms)
# minimize the terminating atoms
nanosheet_obj.minTerminatingFragments()
# overwrite local nanotube attrs
self.nanotube_st = nanosheet_obj.structure.copy()
[docs] def handleProps(self, chorus_properties):
"""
Handle the structure properties of the tube.
:type chorus_properties: list
:param chorus_properties: contains the nine chorus properties,
i.e. ax, ay, az, bx, ..., cz
"""
# delete some unimportant properties that where inherited from
# the sheets
ncell1 = sheet.HoneycombLattice.NCELL1
ncell2 = sheet.HoneycombLattice.NCELL2
self.nanotube_st.property.pop(ncell1, None)
self.nanotube_st.property.pop(ncell2, None)
# set title and entry names
tag = '%dx(%d,%d)' % (self.ncells, self.nindex, self.mindex)
self.title = self.TITLENAME + '-' + tag
self.nanotube_st.property[self.TITLEKEY] = self.title
self.nanotube_st.property[self.ENTRYKEY] = self.title
# set dimensions
self.nanotube_st.property[self.NINDEX] = self.nindex
self.nanotube_st.property[self.MINDEX] = self.mindex
self.nanotube_st.property[self.NCELLS] = self.ncells
self.nanotube_st.property[self.RADIUS] = self.radius
self.nanotube_st.property[self.LENGTH] = self.length
# if not terminating then set up PBC
if self.termfrag == constants.Constants.TERMFRAGS[0]:
xtal.set_pbc_properties(self.nanotube_st, chorus_properties)
self.nanotube_st.property[xtal.SPACE_GROUP_KEY] = \
xtal.P1_SPACE_GROUP_SYMBOL
[docs] def printProps(self, logger=None):
"""
Print the properties of this nanotube.
:type logger: logging.getLogger
:param logger: output logger
"""
# get parameters
latt_vec_len = numpy.linalg.norm(self.lattvec1)
chiral_len = numpy.linalg.norm(self.chiral)
# get formatted strings
title = tlog.get_param_string('Title', self.title, self.MSGWIDTH)
latt_vec_len = tlog.get_param_string(
'Lattice vector length / Ang.',
round(latt_vec_len, self.NUMDECIMAL), self.MSGWIDTH)
nindex = tlog.get_param_string('First chiral index', self.nindex,
self.MSGWIDTH)
mindex = tlog.get_param_string('Second chiral index', self.mindex,
self.MSGWIDTH)
ncells = tlog.get_param_string('Number of unit cells', self.ncells,
self.MSGWIDTH)
chiral_len = tlog.get_param_string('Chiral vector length / Ang.',
round(chiral_len, self.NUMDECIMAL),
self.MSGWIDTH)
translat_len = tlog.get_param_string(
'Translation vector length / Ang.',
round(self.length, self.NUMDECIMAL), self.MSGWIDTH)
radius = tlog.get_param_string('Tube radius / Ang.',
round(self.radius, self.NUMDECIMAL),
self.MSGWIDTH)
chiral_angle = tlog.get_param_string(
'Chiral angle / Deg.', round(self.chiral_angle, self.NUMDECIMAL),
self.MSGWIDTH)
# print formatted strings
logger.info(title)
logger.info(latt_vec_len)
logger.info(nindex)
logger.info(mindex)
logger.info(ncells)
logger.info(chiral_len)
logger.info(translat_len)
logger.info(radius)
logger.info(chiral_angle)
logger.info('')
[docs] def getChorusPBC(self):
"""
Return the chorus box PBC.
:rtype: list
:return: contains the nine chorus properties, i.e. ax, ay, az, bx, ..., cz
"""
a_vec, b_vec, c_vec = get_tube_unit_cell(self.radius, self.length)
a_vec, b_vec, c_vec = [transform.transform_atom_coordinates(x, \
self.rotmatrix) for x in [a_vec, b_vec, c_vec]]
return list(a_vec) + list(b_vec) + list(c_vec)
[docs] def buildTube(self, use_finite_bos=True, logger=None):
"""
Build a tube.
:type use_finite_bos: bool
:param use_finite_bos: use a bond order protocol meant for finite
molecules
:type logger: logging.getLogger
:param logger: output logger
"""
# get the nanotube sheet to be rolled up
nanotube_sheet_obj = NanoTubeSheet(self.element1, self.element2,
self.bondlength, self.nindex,
self.mindex, self.ncells,
self.is_coarse_grain)
nanotube_sheet_obj.buildNanoTubeSheet(self.termfrag, use_finite_bos,
logger)
self.atomic_number1 = nanotube_sheet_obj.nanosheet_obj.atomic_number1
self.atomic_number2 = nanotube_sheet_obj.nanosheet_obj.atomic_number2
# bring important things to this class
self.lattvec1 = nanotube_sheet_obj.lattvec1
self.lattvec2 = nanotube_sheet_obj.lattvec2
self.chiral = nanotube_sheet_obj.chiral
self.translat = nanotube_sheet_obj.translat
self.termatoms = nanotube_sheet_obj.termatoms
self.matchleft = nanotube_sheet_obj.matchleft
self.matchright = nanotube_sheet_obj.matchright
self.nanosheet_st = nanotube_sheet_obj.nanosheet_st
self.nanotube_sheet_st = nanotube_sheet_obj.nanotube_sheet_st
nanosheet_obj = nanotube_sheet_obj.nanosheet_obj
# get chiral angle
self.getChiralAngle()
# tubularize the nanotube sheet
self.tubularizeNanoTubeSheet(logger)
# rotate tube so that the axis is along the translation vector
self.rotateTube(logger)
# make dangling atoms redundant in matching lists
matchleft = self.preprocessMatchAtoms(self.matchleft)
matchright = self.preprocessMatchAtoms(self.matchright)
# bond match up atoms to close the tube
self.bondMatchingEdges(matchleft, matchright)
# if bond orders are desired then do the bond order
# assignment but help out by temporarily adding hydrogens
# MATSCI-11559: bond orders is not supported for coarse grain structures
if (not self.no_double_bonds and use_finite_bos and
not self.is_coarse_grain):
frozenfrag = self.doTermination(nanosheet_obj,
constants.Constants.TERMFRAG)
self.doBondOrders(logger)
self.removeHydrogens()
# terminate the nanotube, if a minimization is desired and there
# are minimizable terminating atoms then do the minimization
if self.termfrag != constants.Constants.TERMFRAGS[0]:
self.frozen_tube_atoms = list(
range(1,
len(self.nanotube_st.atom) + 1))
frozenfrag = self.doTermination(nanosheet_obj, self.termfrag)
self.frozenatoms = self.frozen_tube_atoms + frozenfrag
if len(self.frozenatoms) < len(self.nanotube_st.atom) and \
self.min_term_frags:
self.minTerminatingFrags(nanosheet_obj)
# handle properties of the tube
chorus_properties = self.getChorusPBC()
self.handleProps(chorus_properties)
[docs]class MultiWalledNanoTube(object):
"""
Build a multi-walled nanotube by assembling specific NanoTubes.
"""
NWALLS = 'i_matsci_N_Walls'
WALLSEP = 'r_matsci_Wall_Sep./Ang.'
MSGWIDTH = 50
[docs] def __init__(self, innertube, nwalls, wallsep):
"""
Create an instance.
:type innertube: NanoTube
:param innertube: tube object of inner most tube
:type nwalls: int
:param nwalls: number of walls in the multi-walled tube
:type wallsep: float
:param wallsep: wall separation in Angstrom for the multi-walled
tube.
"""
self.element1 = innertube.element1
self.element2 = innertube.element2
self.bondlength = innertube.bondlength
self.no_double_bonds = innertube.no_double_bonds
self.ncells = innertube.ncells
self.termfrag = innertube.termfrag
self.min_term_frags = innertube.min_term_frags
self.is_coarse_grain = innertube.is_coarse_grain
self.lattvec1 = innertube.lattvec1
self.lattvec2 = innertube.lattvec2
self.chiral_inner = innertube.chiral
self.nindex_inner = innertube.nindex
self.mindex_inner = innertube.mindex
self.atomic_number1 = innertube.atomic_number1
self.atomic_number2 = innertube.atomic_number2
self.nwalls = nwalls
self.wallsep = wallsep
self.tmax = None
self.tubes = [innertube]
self.tubespacings = []
self.nanotube_st = None
[docs] def areSameLength(self):
"""
Return True if the tubes in the multi-walled nanotube
are the same length, False otherwise.
:rtype: bool
:return: True if the tubes in the multi-walled nanotube
are the same length, False otherwise
"""
return self.nindex_inner == self.mindex_inner or self.mindex_inner == 0
[docs] def getRadius(self):
"""
Return the radius in Ang.
:rtype: float
:return: the radius in Ang.
"""
return self.tubes[-1].radius
[docs] def getTerminatingIdxs(self):
"""
Return the indices of terminating atoms.
:rtype: list
:return: indices of terminating atoms
"""
offset = 0
term_idxs = []
for tube in self.tubes:
for term_idx in tube.termatoms:
term_idxs.append(term_idx + offset)
offset += tube.nanotube_st.atom_total
return term_idxs
[docs] def getOuterChiralIndicies(self, wallindex, logger=None):
"""
Get the chiral indicies for this outer tube.
:type wallindex: int
:param wallindex: index of this outer tube
:type logger: logging.getLogger
:param logger: output logger
:rtype: int, int
:rtype: nindex, mindex, chiral indicies for outer tube
"""
# get outer tube length
innerlen = numpy.linalg.norm(self.chiral_inner)
delta = (wallindex - 1) * TWOPI * self.wallsep
outerlen = innerlen + delta
# it might not be possible to find an outer tube with integer
# (n, m) that has the desired outer tube length however
# there are several outer tubes with lengths that are close
# to the desired length, i.e.
# length ~ math.sqrt(n**2 + m**2 + nm), we will by
# default choose an outer tube with the same chiral angle
# as the inside tube, this will likely result in non-integer
# (n, m) which will then be rounded to the nearest integer
# thus resulting in an outer tube of slightly different length
# than that desired, in the equation below for mindex one
# can show that:
# mindex = self.mindex_inner*(1 + self.wallsep/radiusinner)
# which means that in order for mindex to be integer the
# wall separation parameter must be a multiple of the radius
# of the inner tube
mindex = outerlen * self.mindex_inner / innerlen
# solve well behaved quadratic equation for nindex and take
# the positive root
latt_vec_len = numpy.linalg.norm(self.lattvec1)
amo = latt_vec_len * mindex
nindex = 4 * outerlen**2 - 3 * amo**2
nindex = math.sqrt(nindex)
nindex = nindex - amo
nindex = nindex / latt_vec_len
nindex = nindex / 2
# round to nearest integer
nindex = int(round(nindex))
mindex = int(round(mindex))
return nindex, mindex
[docs] def getOuterTubeVectors(self, nindex, mindex):
"""
Return the tube vectors for the given (n, m).
:type nindex: int
:param nindex: first chiral index
:type mindex: int
:param mindex: second chiral index
:rtype: numpy.array, numpy.array
:return: chiral, translat, the tube vectors
"""
asheet = NanoTubeSheet(self.element1, self.element2, self.bondlength,
nindex, mindex, self.ncells,
self.is_coarse_grain)
asheet.lattvec1 = self.lattvec1
asheet.lattvec2 = self.lattvec2
chiral, translat = get_tube_vectors(ncells=asheet.ncells,
nindex=asheet.nindex,
mindex=asheet.mindex,
lattvec1=asheet.lattvec1,
lattvec2=asheet.lattvec2)
return chiral, translat
[docs] def findLargestTranslat(self):
"""
Return the length of the wall with the longest translation vector.
:rtype: float
:return: tmax, length of longest vector in Angstrom
"""
tlengths = [numpy.linalg.norm(tube.translat) for tube in self.tubes]
tmax = max(tlengths)
return tmax
[docs] def getNumUnitCells(self, translat):
"""
Return the number of unit cells to use for the given wall.
:type translat: numpy.array
:param translat: translation vector of the given wall
:rtype: int
:return: ncells, the number of cells to use for the given wall
"""
translat_len = numpy.linalg.norm(translat)
ncells = self.tmax / translat_len
ncells = int(round(ncells))
return ncells
[docs] def alignCenterCollect(self):
"""
Align and center the tubes and collect tubes into a single
structure.
"""
inner_translat = numpy.append(self.tubes[0].translat, 0.0)
# get centroid of outer tube, ignore fragment atoms, use outer
# rather than inner to avoid down stream issues regarding the choice
# of origin
outer_centroid = transform.get_centroid(
self.tubes[-1].nanotube_st, self.tubes[-1].frozen_tube_atoms)
# rotate and translate then collect
for idx, tube in enumerate(self.tubes):
tubest = tube.nanotube_st.copy()
tube_translat = numpy.append(tube.translat, 0.0)
rotmatrix = transform.get_alignment_matrix(tube_translat,
inner_translat)
transform.transform_structure(tubest, rotmatrix)
tube_centroid = transform.get_centroid(tubest,
tube.frozen_tube_atoms)
diff = outer_centroid - tube_centroid
transform.translate_structure(tubest, diff[0], diff[1], diff[2])
if not idx:
self.nanotube_st = tubest.copy()
else:
self.nanotube_st.extend(tubest)
[docs] def getTubeSpacings(self):
"""
Determine actual tube spacings in units of Ang.
"""
# start with inner most tube and determine spacings
# working our way outward
innerrad = self.tubes[0].radius
for tube in self.tubes[1:]:
spacing = tube.radius - innerrad
self.tubespacings.append(spacing)
innerrad = tube.radius
[docs] def handleProps(self):
"""
Handle the structure properties of the multi-walled tube.
"""
# keep props inherited from inner tube but set some extra
# dimensions
self.nanotube_st.property[self.NWALLS] = self.nwalls
self.nanotube_st.property[self.WALLSEP] = self.wallsep
if self.termfrag == constants.Constants.TERMFRAGS[
0] and self.areSameLength():
# set the PBC for the outermost tube
chorus_properties = xtal.get_chorus_properties(
self.tubes[-1].nanotube_st)
xtal.set_pbc_properties(self.nanotube_st, chorus_properties)
else:
remove_pbc(self.nanotube_st)
[docs] def printProps(self, logger=None):
"""
Print the properties of this multi-walled nanotube.
:type logger: logging.getLogger
:param logger: output logger
"""
logger.info('')
for index, spacing in enumerate(self.tubespacings, 2):
description = 'Spacing for tubes %s:%s / Ang.' % (index - 1, index)
spacing = tlog.get_param_string(description,
round(spacing, NanoTube.NUMDECIMAL),
self.MSGWIDTH)
logger.info(spacing)
logger.info('')
[docs] def buildMultiWallTube(self, use_finite_bos=True, logger=None):
"""
Assemble the multi-walled tube.
:type use_finite_bos: bool
:param use_finite_bos: use a bond order protocol meant for finite
molecules
:type logger: logging.getLogger
:param logger: output logger
"""
# build the multi-walled nanotube in such a way that the walls are
# of comparable length, for each wall store a nanotube instance
# with special (n, m) (locked in from the given wall separation)
# and the requested number of unit cells, define the chiral and
# translation vectors for each (n, m) wall, the wall with the
# largest translation vector will determine the number of unit
# cells to use for each of the walls, that number of cells then
# scaled by the requested number of cells in order to allow for
# repeating the entire multi-walled tube
for wallindex in range(2, self.nwalls + 1):
nindex, mindex = self.getOuterChiralIndicies(wallindex, logger)
tube = NanoTube(self.element1, self.element2, self.bondlength,
self.no_double_bonds, nindex, mindex, self.ncells,
self.termfrag, self.min_term_frags,
self.is_coarse_grain)
tube.chiral, tube.translat = self.getOuterTubeVectors(
nindex, mindex)
self.tubes.append(tube)
# find length of wall with longest translat
self.tmax = self.findLargestTranslat()
# find number of unit cells, scale ncells, and build tubes
for tube in self.tubes:
ncells = self.getNumUnitCells(tube.translat)
tube.ncells = self.ncells * ncells
tube.buildTube(use_finite_bos, logger)
# due to the rounding of (n, m) the tubes should be aligned along
# the translation vector of the inner tube, also center the tubes,
# and collect them into a single structure
self.alignCenterCollect()
# determine tube spacings
self.getTubeSpacings()
# handle properties of the multi-walled tube
self.handleProps()
[docs]class NanoTubes(object):
"""
Main class for making nanotubes.
"""
MSGWIDTH = 50
[docs] def __init__(self,
element1=constants.Constants.ELEMENT1,
element2=constants.Constants.ELEMENT2,
bondlength=constants.Constants.BONDLENGTH,
no_double_bonds=constants.Constants.NO_DOUBLE_BONDS,
nindex=constants.Constants.NINDEX,
mindex=constants.Constants.MINDEX,
ncells=constants.Constants.NCELLS,
termfrag=constants.Constants.TERMFRAG,
min_term_frags=constants.Constants.MIN_TERM_FRAGS,
up_to_nindex=constants.Constants.UP_TO_NINDEX,
up_to_mindex=constants.Constants.UP_TO_MINDEX,
nwalls=constants.Constants.NWALLS,
wallsep=constants.Constants.WALLSEP,
orient=False,
logger=None,
is_coarse_grain=False):
"""
:type element1: str
:param element1: elemental symbol of the first atom
:type element2: str
:param element2: elemental symbol of the second atom
:type bondlength: float
:param bondlength: bond length between the first and
second atoms in Angstrom
:type no_double_bonds: bool
:param no_double_bonds: disable the formation of double bonds
:type nindex: int
:param nindex: first chiral index
:type mindex: int
:param mindex: second chiral index
:type ncells: int
:param ncells: number of unit cells
:type termfrag: str
:param termfrag: terminate the lattice with a given fragment
:type min_term_frags: bool
:param min_term_frags: minimize the geometry of terminating
fragments
:type up_to_nindex: bool
:param up_to_nindex: enumerate nanotube structures on the n-index
:type up_to_mindex: bool
:param up_to_mindex: enumerate nanotube structures on the m-index
:type nwalls: int
:param nwalls: number of walls in a multi-wall nanotube
:type wallsep: float
:param wallsep: wall separation in Angstrom in a multi-wall
nanotube
:type orient: bool
:param orient: whether to orient the sheets for Maestro
:type logger: logging.getLogger
:param logger: output
:type is_coarse_grain: bool
:param is_coarse_grain: Whether a coarse grain structure is being
created
"""
# check user options
chkobj = CheckInput()
chkobj.checkAll(element1, element2, bondlength, nindex, mindex, ncells,
no_double_bonds, termfrag, min_term_frags, up_to_nindex,
up_to_mindex, nwalls, wallsep, logger, is_coarse_grain)
self.element1 = chkobj.element1
self.element2 = chkobj.element2
self.bondlength = chkobj.bondlength
self.no_double_bonds = chkobj.no_double_bonds
self.nindex = chkobj.nindex
self.mindex = chkobj.mindex
self.ncells = chkobj.ncells
self.termfrag = chkobj.termfrag
self.min_term_frags = chkobj.min_term_frags
self.up_to_nindex = chkobj.up_to_nindex
self.up_to_mindex = chkobj.up_to_mindex
self.nwalls = chkobj.nwalls
self.wallsep = chkobj.wallsep
self.orient = orient
self.is_coarse_grain = is_coarse_grain
self.structures = []
# print job parameters
if logger:
self.printJobParams(logger)
# make single-walled nanotubes
use_finite_bos = self.termfrag != constants.Constants.TERMFRAGS[0]
self.nanotube_objs = self.makeSingleWalledTubes(use_finite_bos, logger)
# make multi-walled nanotubes (potentially skip PBC stuff,
# see MATSCI-2721 for strain model)
if self.nwalls == 1:
if logger:
self.printSingleWalledTubes(logger)
else:
self.nanotube_objs = self.makeMultiWalledTubes(
use_finite_bos, logger)
if logger:
self.printMultiWalledTubes(logger)
# if terminating then return
if not (self.orient and
self.termfrag == constants.Constants.TERMFRAGS[0]):
for tube in self.nanotube_objs:
self.structures.append(tube.nanotube_st)
return
# handle periodicity
for tube in self.nanotube_objs:
if self.nwalls == 1:
has_pbc = True
radius = tube.radius
termatoms = tube.termatoms
else:
has_pbc = tube.areSameLength()
radius = tube.getRadius()
termatoms = tube.getTerminatingIdxs()
if not has_pbc:
self.structures.append(tube.nanotube_st)
continue
vectors = xtal.get_vectors_from_chorus(tube.nanotube_st)
tube.nanotube_st, origin, a_vec, b_vec, c_vec = \
slab.maestro_rotate_cell(tube.nanotube_st,
numpy.array(xtal.ParserWrapper.ORIGIN), *vectors)
chorus_properties = list(a_vec) + list(b_vec) + list(c_vec)
xtal.set_pbc_properties(tube.nanotube_st, chorus_properties)
translate_tube(tube.nanotube_st, radius)
tube.nanotube_st = sheet.build_cell(tube.nanotube_st,
termatoms,
tube.atomic_number1,
tube.atomic_number2,
self.bondlength,
no_bonding_along=[0, 2])
if self.is_coarse_grain:
set_as_coarse_grain(tube.nanotube_st)
elif not self.no_double_bonds:
tube.nanotube_st = xtal.assign_bond_orders_w_mmlewis(
tube.nanotube_st, fix_metals=False, logger=logger)
self.structures.append(tube.nanotube_st)
[docs] def printJobParams(self, logger=None):
"""
Print job parameters.
:type logger: logging.getLogger
:param logger: output logger
"""
# build formatted strings first
element1 = tlog.get_param_string('Element 1', self.element1,
self.MSGWIDTH)
element2 = tlog.get_param_string('Element 2', self.element2,
self.MSGWIDTH)
bondlength = tlog.get_param_string('Bond length / Ang.',
self.bondlength, self.MSGWIDTH)
no_double_bonds = tlog.get_param_string('No double bonds',
self.no_double_bonds,
self.MSGWIDTH)
nindex = tlog.get_param_string('First chiral index', self.nindex,
self.MSGWIDTH)
mindex = tlog.get_param_string('Second chiral index', self.mindex,
self.MSGWIDTH)
ncells = tlog.get_param_string('Number of unit cells', self.ncells,
self.MSGWIDTH)
termfrag = tlog.get_param_string('Terminating fragment', self.termfrag,
self.MSGWIDTH)
min_term_frags = tlog.get_param_string('Minimize terminating fragments',
self.min_term_frags,
self.MSGWIDTH)
up_to_nindex = tlog.get_param_string('Enumerate on n-index',
self.up_to_nindex, self.MSGWIDTH)
up_to_mindex = tlog.get_param_string('Enumerate on m-index',
self.up_to_mindex, self.MSGWIDTH)
nwalls = tlog.get_param_string('Number of walls', self.nwalls,
self.MSGWIDTH)
wallsep = tlog.get_param_string('Wall separation / Ang.', self.wallsep,
self.MSGWIDTH)
SEPARATOR = self.MSGWIDTH * '='
# print formatted strings
logger.info('Job Parameters:')
logger.info('')
logger.info(SEPARATOR)
logger.info('')
logger.info('Nanotube')
logger.info('--------')
logger.info('')
logger.info(element1)
logger.info(element2)
logger.info(bondlength)
logger.info(no_double_bonds)
logger.info(nindex)
logger.info(mindex)
logger.info(ncells)
logger.info(termfrag)
logger.info(min_term_frags)
logger.info('')
if self.up_to_nindex or self.up_to_mindex:
logger.info('Single-walled nanotubes')
logger.info('-----------------------')
logger.info('')
logger.info(up_to_nindex)
logger.info(up_to_mindex)
logger.info('')
if self.nwalls > 1:
logger.info('Multi-walled nanotubes')
logger.info('----------------------')
logger.info('')
logger.info(nwalls)
logger.info(wallsep)
logger.info('')
logger.info(SEPARATOR)
logger.info('')
[docs] def makeSingleWalledTubes(self, use_finite_bos=True, logger=None):
"""
Make single-walled nanotubes.
:type use_finite_bos: bool
:param use_finite_bos: use a bond order protocol meant for finite
molecules
:type logger: logging.getLogger
:param logger: output logger
:rtype: list of NanoTube
:return: singletubes, contains all created single-walled tubes
"""
# enumerate (n, m) lists
nindicies = [self.nindex]
mindicies = [self.mindex]
if self.up_to_nindex:
nindicies = list(range(self.mindex, self.nindex + 1))
elif self.up_to_mindex:
mindicies = list(range(0, self.mindex + 1))
# for each (n, m) make a single tube
singletubes = []
for nindex in nindicies:
for mindex in mindicies:
singletube = NanoTube(self.element1, self.element2,
self.bondlength, self.no_double_bonds,
nindex, mindex, self.ncells,
self.termfrag, self.min_term_frags,
self.is_coarse_grain)
singletube.buildTube(use_finite_bos, logger)
singletubes.append(singletube)
return singletubes
[docs] def printSingleWalledTubes(self, logger=None):
"""
Formatted print of single-walled tubes.
:type logger: logging.getLogger
:param logger: output logger
"""
# get the number of characters in the length of the list
width = len(str(len(self.nanotube_objs)))
# print header
logger.info('Single-walled Nanotube Parameters:')
logger.info('')
# loop over tubes and print header, bond order msgs, and tube
# properties
for index, tube in enumerate(self.nanotube_objs, 1):
header = 'SW Nanotube %s' % str(index).rjust(width)
logger.info(header)
logger.info('-' * len(header))
if tube.bomsg:
logger.info('')
logger.warning(tube.bomsg)
tube.printProps(logger)
[docs] def makeMultiWalledTubes(self, use_finite_bos=True, logger=None):
"""
Make multi-walled nanotubes.
:type use_finite_bos: bool
:param use_finite_bos: use a bond order protocol meant for finite
molecules
:type logger: logging.getLogger
:param logger: output logger
:rtype: list of MultiWalledNanoTube
:return: multitubes, contains all created multi-walled tubes
"""
# for each inner tube create a multi-walled tube with the given specs
multitubes = []
for innertube in self.nanotube_objs:
multitube = MultiWalledNanoTube(innertube, self.nwalls,
self.wallsep)
if not use_finite_bos and not multitube.areSameLength():
use_finite_bos = True
multitube.buildMultiWallTube(use_finite_bos, logger)
multitubes.append(multitube)
return multitubes
[docs] def printMultiWalledTubes(self, logger=None):
"""
Formatted print of multi-walled tubes.
:type logger: logging.getLogger
:param logger: output logger
"""
# get the number of characters in the length of the list
mwwidth = len(str(len(self.nanotube_objs)))
# print header
logger.info('Multi-walled Nanotube Parameters:')
logger.info('')
# loop over multi-walled tubes and print header and multi-tube props
for mwindex, multitube in enumerate(self.nanotube_objs, 1):
mwheader = 'MW Nanotube %s' % str(mwindex).rjust(mwwidth)
logger.info(mwheader)
logger.info('-' * len(mwheader))
multitube.printProps(logger)
# get the number of characters in the length of the list
swwidth = len(str(len(multitube.tubes)))
# loop over single-walled tubes in the multi-walled tube and
# print header, bond order msgs, and tube props
for swindex, tube in enumerate(multitube.tubes, 1):
swheader = 'SW Nanotube %s' % str(swindex).rjust(swwidth)
logger.info(swheader)
logger.info('-' * len(swheader))
if tube.bomsg:
logger.info('')
logger.warning(tube.bomsg)
tube.printProps(logger)
[docs]def remove_pbc(astructure):
"""
Remove the PBC definitions from the given structure.
:type astructure: schrodinger.structure.Structure
:param astructure: the structure for which to remove the PBC
"""
cry = xtal.Crystal
keys = cry.CHORUS_BOX_KEYS[:]
keys.extend([cry.A_KEY, cry.B_KEY, cry.C_KEY, \
cry.ALPHA_KEY, cry.BETA_KEY, cry.GAMMA_KEY, cry.SPACE_GROUP_KEY])
list(map(lambda x: astructure.property.pop(x, None), keys))
[docs]def translate_tube(tube, radius):
"""
Translate the tube so that it is inside the box.
:type tube: schrodinger.structure.Structure
:param tube: the tube structure
:type radius: float
:param radius: the radius in Ang.
"""
transvec = radius * numpy.array(transform.X_AXIS) + \
2 * radius * numpy.array(transform.Z_AXIS)
transform.translate_structure(tube, *transvec)
[docs]def get_tube_unit_cell(radius, length):
"""
Get the a, b, and c vectors of the unit cell for a tube of given length and
radius.
:param float radius: The radius of the tube in Angstroms
:param float length: The length of the tube in Angstroms
:return tuple(numpy.array): Three numpy arrays each of length 3,
corresponding to the a, b, and c vectors of the unit cell (in
Angstroms)
"""
vacuum = constants.Constants.WALLSEP
a_vec = numpy.array([2 * radius + vacuum, 0.0, 0.0])
b_vec = numpy.array([0.0, length, 0.0])
c_vec = numpy.array([0.0, 0.0, 2 * radius + vacuum])
return a_vec, b_vec, c_vec
[docs]def get_tube_dimensions(chiral, translat):
"""
Calculate the length and radius of a nanotube
:param numpy.array chiral: The chiral vector of the nanutube
:param numpy.array translat: The translat vector of the nanutube
:return tuple(float, float): The length and radius of the nanotube,
respectively. Units are in Angstroms.
"""
length = numpy.linalg.norm(translat)
radius = numpy.linalg.norm(chiral) / TWOPI
return length, radius
[docs]def get_tube_vectors(ncells, nindex, mindex, lattvec1, lattvec2):
"""
Return chiral and translation vectors for a nanotube sheet.
:param int ncells: number of unit cells
:param int nindex: first chiral index
:param int mindex: second chiral index
:param numpy.array lattvec1: first lattice vector
:param numpy.array lattvec2: second lattice vector
:return tuple(numpy.array, numpy.array): The chiral and translat tube
vectors, respectively
"""
chiral = nindex * lattvec1 + mindex * lattvec2
t1coef = 2 * mindex + nindex
t2coef = 2 * nindex + mindex
translat = t1coef * lattvec1 - t2coef * lattvec2
gcd = xtal.gcd(t1coef, t2coef)
# scale translation vector by the desired number of cells
translat = ncells * translat / gcd
return chiral, translat