"""
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 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()