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

"""
Classes and functions to create interface models.

Copyright Schrodinger, LLC. All rights reserved."""

# Contributor: Thomas F. Hughes

import argparse
import math
from past.utils import old_div

import numpy

from schrodinger.application.desmond.constants import IS_INFINITE
from schrodinger.application.matsci import parserutils
from schrodinger.application.matsci import textlogger
from schrodinger.application.matsci.nano import slab
from schrodinger.application.matsci.nano import xtal
from schrodinger.structutils import transform

_version = '$Revision 0.0 $'

REFERENCE = 'reference'
ADSORPTION = 'epitaxial'
BOTTOM_LAYER_CHOICES = [REFERENCE, ADSORPTION]
BOTTOM_LAYER_DEFAULT = REFERENCE

STRAIN_A_DEFAULT = 0.5
STRAIN_B_DEFAULT = 0.5
STRAIN_ALPHA_DEFAULT = 5.0
STRAIN_BETA_DEFAULT = 5.0
STRAIN_GAMMA_DEFAULT = 5.0

SEPARATION_DEFAULT = 0.0

MAX_MULT = 10
MAX_EXTENTS_DEFAULT = MAX_MULT

TRANSLATE_A_DEFAULT = 0.0
TRANSLATE_B_DEFAULT = 0.0

INTERFACE = 'interface'

ENTRY_NAME_KEY = 's_m_entry_name'

# indices to individual parameters in the list of six
# lattice parameters
A_IDX, B_IDX, C_IDX, ALPHA_IDX, BETA_IDX, GAMMA_IDX = list(range(6))

CHOICE_OFF = xtal.ParserWrapper.CHOICE_OFF
CHOICE_ON = xtal.ParserWrapper.CHOICE_ON
XTAL_DICT = dict(
    list(zip(['bonding', 'bond_orders', 'translate'], 3 * [CHOICE_OFF])))

PARAM_NAMES = dict(
    list(zip(list(range(6)), ['a', 'b', 'c', 'alpha', 'beta', 'gamma'])))


[docs]def get_formulas_basename(unit_cell_1, unit_cell_2): """ Return a formatted basename containing the two unit cell formulas for the given two unit cells. :type unit_cell_1: `schrodinger.structure.Structure` :param unit_cell_1: the first unit cell :type unit_cell_2: `schrodinger.structure.Structure` :param unit_cell_2: the second unit cell :rtype: str :return: the formatted basename containing the two unit cell formulas """ get_formula = \ lambda x: xtal.get_unit_cell_formula(x).replace(' ', '') formulas = \ '_'.join(map(get_formula, [unit_cell_1, unit_cell_2])) return formulas + '_' + INTERFACE
[docs]def lcm_approx(smaller, larger, tolerance=0.0, max_mult=MAX_MULT, threshold=1.0e-10): """ From the two given numbers return two least common multiples (LCMs) that are within the specified tolerance of each other. If the numbers are integers and the tolerance is zero then this function is equivalent to the traditional LCM and the two returned LCMs will be identical integers. Raise a ValueError if either integer multiplier is larger than the given maximum. :type smaller: float :param smaller: the smaller number :type larger: float :param larger: the larger number :type tolerance: float :param tolerance: a parameter controlling the extent to which two floats may be considered equivalent :type max_mult: int :param max_mult: the maximum allowable multiplier, supported to avoid potentially very long loops given that the inputs are floats compared using a specified tolerance :type threshold: float :param threshold: a parameter controlling numerical precision :raise ValueError: if either integer multipler is larger than the given maximum :rtype: tuple, tuple :return: the first tuple contains the two LCMs, the second contains the two integer multipliers, i.e. those integers which when multiplied by the inputs produce the LCMs """ flip = smaller > larger nums = [smaller, larger] if flip: nums = nums[::-1] tmp_smaller, tmp_larger = nums var_smaller, var_larger = tmp_smaller, tmp_larger mult_smaller, mult_larger = 1, 1 while True: if var_smaller < var_larger - tolerance: var_smaller += tmp_smaller mult_smaller += 1 if var_smaller > var_larger + tolerance: var_larger += tmp_larger mult_larger += 1 if mult_smaller > max_mult or mult_larger > max_mult: raise ValueError if abs(var_larger - var_smaller) <= tolerance + threshold: break avars = (var_smaller, var_larger) amults = (mult_smaller, mult_larger) if flip: avars = avars[::-1] amults = amults[::-1] return avars, amults
[docs]def list_to_string(alist, accuracy=3, separator=', '): """ Return a formatted string containing the floats in the given list rounded to the given accuracy and separated by the given separator. :type alist: list :param alist: list of floats :type accuracy: int :param accuracy: used to round the floats :type separator: str :param separator: used to separate the rounded floats :rtype: str :return: the formatted string """ return separator.join((str(round(x, accuracy)) for x in alist))
[docs]class ParserWrapper(object): """ Manages the argparse module to parse user command line arguments. """
[docs] def __init__(self, scriptname, description): """ Create a ParserWrapper instance and process it. :type scriptname: str :param scriptname: name of this script :type description: str :param description: description of this script """ name = '$SCHRODINGER/run ' + scriptname self.parserobj = argparse.ArgumentParser( prog=name, description=description, add_help=False, formatter_class=argparse.ArgumentDefaultsHelpFormatter)
[docs] def loadIt(self): """ Load ParserWrapper with all options. """ self.loadRequired() self.loadOptions() self.loadCommon()
[docs] def loadRequired(self): """ Load ParserWrapper with required options. """ ref_layer_help = """Specify a Maestro file containing the ASU for the reference layer of the interface model. The epitaxial layer (see -ads_layer) is adsorbed onto this reference layer which has a fixed geometry.""" self.parserobj.add_argument('-ref_layer', type=str, required=True, help=ref_layer_help) ads_layer_help = """Specify a Maestro file containing the ASU for the epitaxial layer of the interface model. This epitaxial layer is adsorbed onto the reference layer (see -ref_layer) and can have a strained geometry according to the -strain_a, -strain_b, -strain_alpha, -strain_beta, and -strain_gamma options.""" self.parserobj.add_argument('-ads_layer', type=str, required=True, help=ads_layer_help)
[docs] def loadOptions(self): """ Load ParserWrapper with options. """ bot_layer_help = """Specify which layer, %s or %s, should be the bottom layer of the interface model. The layers are stacked along the c (or thickness dimension for slab models) lattice vector.""" self.parserobj.add_argument('-bot_layer', type=str, choices=BOTTOM_LAYER_CHOICES, default=BOTTOM_LAYER_DEFAULT, help=bot_layer_help % tuple(BOTTOM_LAYER_CHOICES)) strain_a_help = """Specify by how much (in units of Angstrom) the epitaxial layer is allowed to be strained along its a (or u for slab models) lattice vector. By strain we mean either compression or expansion along the a lattice vector.""" self.parserobj.add_argument('-strain_a', type=parserutils.type_nonnegative_float, default=STRAIN_A_DEFAULT, help=strain_a_help) strain_b_help = """Specify by how much (in units of Angstrom) the epitaxial layer is allowed to be strained along its b (or v for slab models) lattice vector. By strain we mean either compression or expansion along the b lattice vector.""" self.parserobj.add_argument('-strain_b', type=parserutils.type_nonnegative_float, default=STRAIN_B_DEFAULT, help=strain_b_help) strain_alpha_help = """Specify by how much (in units of degrees) the epitaxial layer is allowed to be strained along its alpha angle (the angle between the b (or v) and c (or w) lattice vectors). By strain we mean either compression or expansion along the alpha angle.""" self.parserobj.add_argument('-strain_alpha', type=parserutils.type_nonnegative_float, default=STRAIN_ALPHA_DEFAULT, help=strain_alpha_help) strain_beta_help = """Specify by how much (in units of degrees) the epitaxial layer is allowed to be strained along its beta angle (the angle between the a (or u) and c (or w) lattice vectors). By strain we mean either compression or expansion along the beta angle.""" self.parserobj.add_argument('-strain_beta', type=parserutils.type_nonnegative_float, default=STRAIN_BETA_DEFAULT, help=strain_beta_help) strain_gamma_help = """Specify by how much (in units of degrees) the epitaxial layer is allowed to be strained along its gamma angle (the angle between the a (or u) and b (or v) lattice vectors). By strain we mean either compression or expansion along the gamma angle.""" self.parserobj.add_argument('-strain_gamma', type=parserutils.type_nonnegative_float, default=STRAIN_GAMMA_DEFAULT, help=strain_gamma_help) separation_help = """Specify the separation of the epitaxial and reference layers in units of Angstrom. The layers are separated along the c (or thickness dimension for slab models) lattice vector. If the ASUs for the reference or epitaxial layers contain vacuum space then it may be necessary to set a negative value for this option.""" self.parserobj.add_argument('-separation', type=float, default=SEPARATION_DEFAULT, help=separation_help) max_extents_help = """In order to obtain the most commensurate interface model the reference and epitaxial layers are extended along the a and b lattice vectors until their extended lengths are within the specified strain parameters, i.e. -strain_a and/or -strain_b. Depending on the input parameters the necessary extents can be very large which may not be the user's intent. This parameter controls the build by exiting if any of the extents are larger than this value.""" self.parserobj.add_argument('-max_extents', type=int, default=MAX_EXTENTS_DEFAULT, help=max_extents_help) translate_a_help = """Specify a value in units of Angstrom by which the top layer will be translated, relative to the bottom layer, along the a lattice vector. This option can be negative.""" self.parserobj.add_argument('-translate_a', type=float, default=TRANSLATE_A_DEFAULT, help=translate_a_help) translate_b_help = """Specify a value in units of Angstrom by which the top layer will be translated, relative to the bottom layer, along the b lattice vector. This option can be negative.""" self.parserobj.add_argument('-translate_b', type=float, default=TRANSLATE_B_DEFAULT, help=translate_b_help) base_name_help = """Specify a base name that is used to name various output, for example the output Maestro and log files, the output structure title, etc. By default a concatenation of the unit cell formulas of the two layers will be used.""" self.parserobj.add_argument('-base_name', type=str, help=base_name_help)
[docs] def loadCommon(self): """ Load ParserWrapper with common options. """ self.parserobj.add_argument('-verbose', action='store_true', help='Turn on verbose printing.') self.parserobj.add_argument('-help', '-h', action='help', default=argparse.SUPPRESS, help='Show this help message and exit.') self.parserobj.add_argument( '-version', '-v', action='version', default=argparse.SUPPRESS, version=_version, help="""Show the script's version number and exit.""")
[docs] def parseArgs(self, args): """ Parse the command line arguments. :type args: tuple :param args: command line arguments """ self.options = self.parserobj.parse_args(args)
[docs]class Interface(object): """ Manage the building of an interface model. """ MSGWIDTH = 100
[docs] def __init__(self, ref_layer, ads_layer, bot_layer=BOTTOM_LAYER_DEFAULT, strain_a=STRAIN_A_DEFAULT, strain_b=STRAIN_B_DEFAULT, strain_alpha=STRAIN_ALPHA_DEFAULT, strain_beta=STRAIN_BETA_DEFAULT, strain_gamma=STRAIN_GAMMA_DEFAULT, separation=SEPARATION_DEFAULT, max_extents=MAX_EXTENTS_DEFAULT, translate_a=TRANSLATE_A_DEFAULT, translate_b=TRANSLATE_B_DEFAULT, base_name=None, logger=None): """ Create an instance. :type ref_layer: `schrodinger.structure.Structure` :param ref_layer: the reference layer used to define the interface model :type ads_layer: `schrodinger.structure.Structure` :param ads_layer: the adsorption layer used to define the interface model :type bot_layer: str :param bot_layer: specifies which layer, reference or adsorption, is the bottom layer of the interface model :type strain_a: float :param strain_a: the amount of strain allowed along the a lattice vector of the adsorption layer in units of Angstrom :type strain_b: float :param strain_b: the amount of strain allowed along the b lattice vector of the adsorption layer in units of Angstrom :type strain_alpha: float :param strain_alpha: the amount of strain allowed along the alpha angle of the adsorption layer in units of degrees :type strain_beta: float :param strain_beta: the amount of strain allowed along the beta angle of the adsorption layer in units of degrees :type strain_gamma: float :param strain_gamma: the amount of strain allowed along the gamma angle of the adsorption layer in units of degrees :type separation: float :param separation: the separation between the reference and adsorption layers in units of Angstrom :type max_extents: int :param max_extents: the maximum allowable extent :type translate_a: float :param translate_a: the amount by which to translate the top layer, relative to the bottom layer, along the a lattice vector :type translate_b: float :param translate_b: the amount by which to translate the top layer, relative to the bottom layer, along the b lattice vector :type base_name: str :param base_name: a base name used to name output :type logger: logging.Logger :param logger: output logger """ self.ref_layer = ref_layer self.ads_layer = ads_layer self.bot_layer = bot_layer self.strain_a = strain_a self.strain_b = strain_b self.strain_alpha = strain_alpha self.strain_beta = strain_beta self.strain_gamma = strain_gamma self.separation = separation self.max_extents = max_extents self.translate_a = translate_a self.translate_b = translate_b self.base_name = base_name self.logger = logger self.ref_lattice_params = None self.ads_lattice_params = None self.ref_hkl = None self.ads_hkl = None self.ref_ncella = None self.ref_ncellb = None self.ads_ncella = None self.ads_ncellb = None self.strained_ads_lattice_params = None self.interface_lattice_params = None self.interface = None self.ref_is_infinite = None self.ads_is_infinite = None
[docs] def setIsInfinite(self): """ Set the is_infinite attributes. """ def set_it(cell): is_infinite = cell.property.get(IS_INFINITE) if is_infinite is not None: is_infinite = bool(is_infinite) return is_infinite self.ref_is_infinite = set_it(self.ref_layer) self.ads_is_infinite = set_it(self.ads_layer)
[docs] def setBaseName(self): """ Set a base name. """ if self.base_name is None: self.base_name = \ get_formulas_basename(self.ref_layer, self.ads_layer)
[docs] def setLatticeProperties(self): """ Set lattice parameter attributes for both the reference and adsorption layers. """ afunc = lambda x: list(xtal.get_lattice_param_properties(x)) self.ref_lattice_params, self.ads_lattice_params = \ list(map(afunc, [self.ref_layer, self.ads_layer]))
[docs] def setHKL(self): """ Set hkl for both the reference and adsorption layers. """ self.ref_hkl, self.ads_hkl = \ list(map(slab.get_hkl, [self.ref_layer, self.ads_layer]))
[docs] def getExtentsAndStrainedLength(self, index, strain): """ For the lattice vector specified with the given index return (1) the reference and adsorption layer extents necessary to bring the extended lengths to within the specified amount of strain and (2) the strained adsorption layer length. :type index: int :param index: an index used to specify along which direction to get the extents, i.e. 0 is a, 1 is b, 2 is c :type strain: float :param strain: the amount of strain allowed along the given lattice vector of the adsorption layer in units of Angstrom :raise ValueError: if either extent is larger than the allowable maximum :rtype: tuple, float :return: the tuple contains the reference and adsorption layer integer extents, the float is the strained adsorption layer length in units of Angstrom """ lens = [ x[index] for x in [self.ref_lattice_params, self.ads_lattice_params] ] try: extended_lens, ncells = lcm_approx(*lens, tolerance=strain, max_mult=self.max_extents) except ValueError: msg = ('At least one extent is greater than the maximum ' 'allowable extent, i.e. %s. You can either increase ' 'the maximum allowable extent or increase the amount ' 'of allowable strain to prevent this error message.') raise ValueError(msg % self.max_extents) # positive and negative deltas, i.e. extended_lens[0] - # extended_lens[1], indicate expansion and compression # strains respectively, the delta is applied to the # extended adsorption layer so the final strained adsorption # layer length is just extended_lens[0] strained_len = extended_lens[0] return ncells, strained_len
[docs] def getStrainedAngle(self, index, strain): """ Return the strained adsorption layer angle. :type index: int :param index: the angle index :type strain: float :param strain: the allowable amount of strain in degrees :raise ValueError: if the reference and adsorption layer angles are not within the specified amount of strain :rtype: float :return: strained adsorption layer angle in units of degree """ ref_angle, ads_angle = \ self.ref_lattice_params[index], self.ads_lattice_params[index] if abs(ref_angle - ads_angle) > strain: msg = ('The absolute difference in %s angles between the ' 'reference and epitaxial layers, %s and %s degrees, ' 'respectively, is larger than the given acceptable amount ' 'of strain or %s degrees. You can increase the amount of ' 'allowable strain to prevent this error message.') raise ValueError(msg % (PARAM_NAMES[index], ref_angle, ads_angle, strain)) strained_angle = ref_angle return strained_angle
[docs] def setStrainedAdsorptionLatticeProperties(self, strained_a, strained_b, strained_alpha, strained_beta, strained_gamma): """ Set the strained lattice parameter attributes for the adsorption layer. :type strained_a: float :param strained_a: the strained lattice a parameter in units of Angstrom :type strained_b: float :param strained_b: the strained lattice b parameter in units of Angstrom :type strained_alpha: float :param strained_alpha: the strained lattice alpha parameter in units of degree :type strained_beta: float :param strained_beta: the strained lattice beta parameter in units of degree :type strained_gamma: float :param strained_gamma: the strained lattice gamma parameter in units of degree """ self.strained_ads_lattice_params = list(self.ads_lattice_params) self.strained_ads_lattice_params[A_IDX] = strained_a self.strained_ads_lattice_params[B_IDX] = strained_b self.strained_ads_lattice_params[ALPHA_IDX] = strained_alpha self.strained_ads_lattice_params[BETA_IDX] = strained_beta self.strained_ads_lattice_params[GAMMA_IDX] = strained_gamma
[docs] def setInterfaceLatticeProperties(self): """ Set the lattice parameter attributes for the interface model. """ a_vec, b_vec, c_vec = xtal.get_lattice_vectors(*self.ref_lattice_params) normal = numpy.cross(a_vec, b_vec) normal_angle = transform.get_angle_between_vectors(normal, c_vec) separation = old_div(self.separation, math.cos(normal_angle)) self.interface_lattice_params = list(self.ref_lattice_params) self.interface_lattice_params[A_IDX] *= self.ref_ncella self.interface_lattice_params[B_IDX] *= self.ref_ncellb olength = self.interface_lattice_params[C_IDX] nlength = olength + self.strained_ads_lattice_params[C_IDX] + separation if nlength <= olength: if self.logger: msg = ( f'The separation of {separation} Ang. gives a lattice ' f'c-vector length for the interface cell of {nlength} Ang. ' f'that is smaller than that of the base layer length of ' f'{olength} Ang. Instead proceeding with a length of ' f'{olength} Ang.') self.logger.warning(msg) nlength = olength self.interface_lattice_params[C_IDX] = nlength
[docs] def logParams(self): """ Log the parameters. """ if self.logger is None: return HEADER = 'Interface Parameters' base_name = \ textlogger.get_param_string('Base name', self.base_name, self.MSGWIDTH) ref_lattice_params = \ textlogger.get_param_string('Ref. layer lattice params', \ list_to_string(self.ref_lattice_params), self.MSGWIDTH) ads_lattice_params = \ textlogger.get_param_string('Ads. layer lattice params', \ list_to_string(self.ads_lattice_params), self.MSGWIDTH) ref_hkl = \ textlogger.get_param_string('Ref. layer hkl', \ self.ref_hkl, self.MSGWIDTH) ads_hkl = \ textlogger.get_param_string('Ads. layer hkl', \ self.ads_hkl, self.MSGWIDTH) bot_layer = textlogger.get_param_string('Bottom layer', self.bot_layer, self.MSGWIDTH) strain_a = textlogger.get_param_string('Strain a / Ang.', self.strain_a, self.MSGWIDTH) strain_b = textlogger.get_param_string('Strain b / Ang.', self.strain_b, self.MSGWIDTH) strain_alpha = textlogger.get_param_string('Strain alpha / degrees', self.strain_alpha, self.MSGWIDTH) strain_beta = textlogger.get_param_string('Strain beta / degrees', self.strain_beta, self.MSGWIDTH) strain_gamma = textlogger.get_param_string('Strain gamma / degrees', self.strain_gamma, self.MSGWIDTH) separation = textlogger.get_param_string('Separation / Ang.', self.separation, self.MSGWIDTH) max_extents = textlogger.get_param_string('Max extents', self.max_extents, self.MSGWIDTH) translate_a = textlogger.get_param_string('Translate a / Ang.', self.translate_a, self.MSGWIDTH) translate_b = textlogger.get_param_string('Translate b / Ang.', self.translate_b, self.MSGWIDTH) ref_ncella = textlogger.get_param_string('Ref. layer a extents', self.ref_ncella, self.MSGWIDTH) ref_ncellb = textlogger.get_param_string('Ref. layer b extents', self.ref_ncellb, self.MSGWIDTH) ads_ncella = textlogger.get_param_string('Ads. layer a extents', self.ads_ncella, self.MSGWIDTH) ads_ncellb = textlogger.get_param_string('Ads. layer b extents', self.ads_ncellb, self.MSGWIDTH) strained_ads_lattice_params = \ textlogger.get_param_string('Strained ads. layer lattice params', \ list_to_string(self.strained_ads_lattice_params), self.MSGWIDTH) interface_lattice_params = \ textlogger.get_param_string('Interface lattice params', \ list_to_string(self.interface_lattice_params), self.MSGWIDTH) ref_is_infinite = textlogger.get_param_string('Ref. is infinite', self.ref_is_infinite, self.MSGWIDTH) ads_is_infinite = textlogger.get_param_string('Ads. is infinite', self.ads_is_infinite, self.MSGWIDTH) self.logger.info(HEADER) self.logger.info('-' * len(HEADER)) self.logger.info(base_name) self.logger.info(ref_lattice_params) self.logger.info(ads_lattice_params) self.logger.info(ref_hkl) self.logger.info(ads_hkl) self.logger.info(bot_layer) self.logger.info(strain_a) self.logger.info(strain_b) self.logger.info(strain_alpha) self.logger.info(strain_beta) self.logger.info(strain_gamma) self.logger.info(separation) self.logger.info(max_extents) self.logger.info(translate_a) self.logger.info(translate_b) self.logger.info(ref_ncella) self.logger.info(ref_ncellb) self.logger.info(ads_ncella) self.logger.info(ads_ncellb) self.logger.info(strained_ads_lattice_params) self.logger.info(interface_lattice_params) self.logger.info(ref_is_infinite) self.logger.info(ads_is_infinite) self.logger.info('')
[docs] def getOrigin(self, lattice_params): """ Return the origin in units of Angstrom for the given lattice parameters. :type lattice_params: list :param lattice_params: the six lattice parameters :rtype: numpy.array :return: the origin in units of Angstrom """ origin_a = old_div(self.translate_a, lattice_params[A_IDX]) origin_b = old_div(self.translate_b, lattice_params[B_IDX]) origin_c = \ old_div(self.interface_lattice_params[C_IDX], lattice_params[C_IDX]) - 1.0 origin = numpy.array([origin_a, origin_b, origin_c]) origin = xtal.trans_fract_to_cart(origin, *(lattice_params)) return origin
[docs] def translateTopLayer(self, ref_layer, ads_layer): """ Translate the top layer. :type ref_layer: `schrodinger.structure.Structure` :param ref_layer: the reference layer :type ads_layer: `schrodinger.structure.Structure` :param ads_layer: the adsorption layer """ if self.bot_layer == REFERENCE: origin = self.getOrigin(self.strained_ads_lattice_params) transform.translate_structure(ads_layer, *origin) else: origin = self.getOrigin(self.ref_lattice_params) transform.translate_structure(ref_layer, *origin)
[docs] def getInterface(self, ref_layer, ads_layer): """ Build the interface model from the two finalized layers and return it. :type ref_layer: `schrodinger.structure.Structure` :param ref_layer: the final reference layer :type ads_layer: `schrodinger.structure.Structure` :param ads_layer: the final adsorption layer :rtype: `schrodinger.structure.Structure` :return: the interface model """ if self.bot_layer == REFERENCE: interface = ref_layer.copy() interface.extend(ads_layer) else: interface = ads_layer.copy() interface.extend(ref_layer) chorus_params = xtal.get_chorus_from_params( self.interface_lattice_params) xtal.set_pbc_properties(interface, chorus_params) interface = xtal.make_p1(interface) return interface
[docs] def setInterfaceProperties(self): """ Set some interface properties. """ crystal_keys = [ xtal.Crystal.UNIT_CELL_FORMULA_KEY, xtal.Crystal.UNIT_CELL_VOLUME_KEY, xtal.Crystal.UNIT_CELL_DENSITY_KEY ] for key in crystal_keys: self.interface.property.pop(key, None) self.interface.title = self.base_name self.interface.property[ENTRY_NAME_KEY] = self.base_name self.interface.property[IS_INFINITE] = \ any([self.ref_is_infinite, self.ads_is_infinite]) self.interface.property[xtal.PBC_POSITION_KEY] = \ xtal.ANCHOR_PBC_POSITION % ('0', '0', '0')
[docs] def getUpdatedCParams(self, in_params): """ Return the set of lattice parameters with an updated c parameter. :type in_params: list :param in_params: the six lattice parameters :rtype: list :return: the six lattice parameters with c updated """ out_params = list(in_params) out_params[C_IDX] = self.interface_lattice_params[C_IDX] return out_params
[docs] def buildLayer(self, cell, in_params, extents, is_infinite): """ Build a layer by extending the input slab cell, also eliminate any PBC bonds along the c lattice vector. :type cell: `schrodinger.structure.Structure` :param cell: slab cell to extend :type in_params: list :param in_params: the six lattice parameters :type extents: list :param extents: extents for layer :type is_infinite: bool :param is_infinite: whether the slab cell is infinite :rtype: `schrodinger.structure.Structure` :return: extended layer """ out_params = self.getUpdatedCParams(in_params) # extend the slab ASU to obtain the layer, # for infinite systems enable bonding but use existing # PBC bonds so that adjacent cells are properly connected, # but remove PBC bonds along the c-vector, note that this # will present a problem for infinite polymers where the # PBC bonds cross the c-vector xtal_kwargs = dict(XTAL_DICT) if is_infinite: xtal_kwargs['bonding'] = CHOICE_ON xtal_kwargs['use_existing_pbc_bonds'] = True xtal.delete_pbc_bonds(cell, along_vector_idxs=[2]) layer = xtal.get_cell(cell, lattice_params=out_params, \ extents=extents, xtal_kwargs=xtal_kwargs) # create a new P1 cell from the now extended super cell, # this will synchronize the PDB and chorus properties, and # remove the fractional coordinate information layer = xtal.make_p1(layer) return layer
[docs] def buildLayers(self, ref_extents, ads_extents): """ Build the reference and adsorption layers by extending the input slab cells, also eliminate any PBC bonds along the c lattice vector. :type ref_extents: list :param ref_extents: extents for reference layer :type ads_extents: list :param ads_extents: extents for adsorption layer :rtype: `schrodinger.structure.Structure`, `schrodinger.structure.Structure` :return: extended reference and adsorption layers """ ref_layer = self.buildLayer(self.ref_layer.copy(), self.ref_lattice_params, ref_extents, self.ref_is_infinite) ads_layer = self.buildLayer(self.ads_layer.copy(), self.ads_lattice_params, ads_extents, self.ads_is_infinite) return ref_layer, ads_layer
[docs] def getStrainedAdsorptionLayer(self, ads_layer): """ Return the strained adsorption layer. :type ads_layer: `schrodinger.structure.Structure` :param ads_layer: the extended unstrained adsorption layer :rtype: `schrodinger.structure.Structure` :return: the strained adsorption layer """ strained_ads_lattice_params = \ self.getUpdatedCParams(self.strained_ads_lattice_params) # to build the strained adsorption layer we just # need to use the original fractional coordinates # but with the strained lattice parameters vecs = numpy.array( xtal.get_lattice_vectors( *xtal.get_lattice_param_properties(ads_layer))) fracs = xtal.trans_cart_to_frac_from_vecs(ads_layer.getXYZ(), *vecs) chorus_params = xtal.get_chorus_from_params(strained_ads_lattice_params) xtal.set_pbc_properties(ads_layer, chorus_params) strained_vecs = numpy.array(xtal.get_vectors_from_chorus(ads_layer)) ads_layer.setXYZ( xtal.trans_frac_to_cart_from_vecs(fracs, *strained_vecs)) return ads_layer
[docs] def runIt(self): """ Create the interface model. """ self.setIsInfinite() # set a base name self.setBaseName() # set lattice parameters for both layers self.setLatticeProperties() # set hkl for both layers self.setHKL() # get extents for both layers and strained lengths (self.ref_ncella, self.ads_ncella), strained_a = \ self.getExtentsAndStrainedLength(A_IDX, self.strain_a) (self.ref_ncellb, self.ads_ncellb), strained_b = \ self.getExtentsAndStrainedLength(B_IDX, self.strain_b) ref_extents = [self.ref_ncella, self.ref_ncellb, 1] ads_extents = [self.ads_ncella, self.ads_ncellb, 1] # get strained angles strained_alpha = self.getStrainedAngle(ALPHA_IDX, self.strain_alpha) strained_beta = self.getStrainedAngle(BETA_IDX, self.strain_beta) strained_gamma = self.getStrainedAngle(GAMMA_IDX, self.strain_gamma) # set strained lattice parameters for the adsorption layer self.setStrainedAdsorptionLatticeProperties(strained_a, strained_b, strained_alpha, strained_beta, strained_gamma) # set lattice parameters for the interface model self.setInterfaceLatticeProperties() # log interface properties self.logParams() # build layers ref_layer, ads_layer = self.buildLayers(ref_extents, ads_extents) # strain adsorption layer ads_layer = self.getStrainedAdsorptionLayer(ads_layer) # translate the top layer self.translateTopLayer(ref_layer, ads_layer) # get the interface model self.interface = self.getInterface(ref_layer, ads_layer) # set interface properties self.setInterfaceProperties()