Source code for schrodinger.application.matsci.nano.tube

"""
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 CheckInput(check.CheckInput): """ Check user input. """
[docs] def checkAll(self, element1, element2, bondlength, nindex, mindex, ncells, no_double_bonds, termfrag, min_term_frags, up_to_nindex, up_to_mindex, nwalls, wallsep, logger=None, is_coarse_grain=False): """ Manage all checks. :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 no_double_bonds: bool :param no_double_bonds: disable the formation of double bonds :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 for a multi-wall nanotube :type logger: logging.getLogger :param logger: output logger :type is_coarse_grain: bool :param is_coarse_grain: Whether a coarse grain structure is being created """ # by default argparse will check basic information about the input # but we will need to check some details about the input plus # cover the situation where a user imports this module if is_coarse_grain: self.element1, self.element2 = element1, element2 self.termfrag = termfrag else: self.element1, self.element2 = \ self.checkElements(element1, element2, logger) self.termfrag = self.checkTermFrag(termfrag, logger) self.bondlength = self.checkBondlength(bondlength, logger) self.nindex, self.mindex = \ self.checkIndicies(nindex, mindex, logger) self.ncells = self.checkNumCells(ncells, logger) self.no_double_bonds = no_double_bonds self.min_term_frags = min_term_frags self.up_to_nindex, self.up_to_mindex = \ self.checkUpToIndex(up_to_nindex, up_to_mindex, logger) self.nwalls = self.checkNumWalls(nwalls, logger) self.wallsep = self.checkWallSep(wallsep, logger)
[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