#
# Copyright (c) 2013-2015, Scott J Maddox
#
# This file is part of openbandparams.
#
# openbandparams is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# openbandparams is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with openbandparams. If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
__all__ = ['IIIVZincBlendeQuaternary']
from .iii_v_zinc_blende_mixed_alloy import IIIVZincBlendeMixedAlloy
from .algorithms import bisect
[docs]class IIIVZincBlendeQuaternary(IIIVZincBlendeMixedAlloy):
'''
The base class for all III-V zinc blende quaternary alloys.
'''
def __init__(self, name, elements, ternaries, parameters=None,
x=None, y=None, z=None):
if (len(ternaries) == 3 and
ternaries[0].elements[0] == ternaries[1].elements[0] and # A
ternaries[0].elements[0] == ternaries[2].elements[0] and # A
ternaries[0].elements[1] == ternaries[1].elements[1] and # B
ternaries[0].elements[2] == ternaries[2].elements[1] and # C
ternaries[1].elements[2] == ternaries[2].elements[2]): # D
# Type 1: AB_{x}C_{y}D_{1-x-y}
# binaries = (AB, AC, AD)
# ternaries = (ABC, ABD ,ACD)
self._type = 1
self._element_w = ternaries[0].elements[0]
self._element_x = ternaries[0].elements[1]
self._element_y = ternaries[0].elements[2]
self._element_z = ternaries[1].elements[2]
self.binaries = (ternaries[0].binaries[0],
ternaries[0].binaries[1],
ternaries[1].binaries[1],)
calc_elements = (ternaries[0].elements[0],
ternaries[0].elements[1],
ternaries[0].elements[2],
ternaries[1].elements[2])
elif (len(ternaries) == 3 and
ternaries[0].elements[0] == ternaries[1].elements[0] and # A
ternaries[0].elements[1] == ternaries[2].elements[0] and # B
ternaries[1].elements[1] == ternaries[2].elements[1] and # C
ternaries[0].elements[2] == ternaries[1].elements[2] and # D
ternaries[0].elements[2] == ternaries[2].elements[2]): # D
# Type 2: A_{x}B_{y}C_{1-x-y}D
# binaries = (AD, BD, CD)
# ternaries = (ABD, ACD, BCD)
self._type = 2
self._element_x = ternaries[0].elements[0]
self._element_y = ternaries[2].elements[0]
self._element_z = ternaries[2].elements[1]
self._element_w = ternaries[2].elements[2]
self.binaries = (ternaries[0].binaries[0],
ternaries[0].binaries[1],
ternaries[1].binaries[1],)
calc_elements = (ternaries[0].elements[0],
ternaries[2].elements[0],
ternaries[2].elements[1],
ternaries[2].elements[2])
elif (len(ternaries) == 4 and
ternaries[0].elements[0] == ternaries[1].elements[0] and # A
ternaries[0].elements[0] == ternaries[2].elements[0] and # A
ternaries[0].elements[1] == ternaries[1].elements[1] and # B
ternaries[0].elements[1] == ternaries[3].elements[0] and # B
ternaries[0].elements[2] == ternaries[2].elements[1] and # C
ternaries[0].elements[2] == ternaries[3].elements[1] and # C
ternaries[1].elements[2] == ternaries[2].elements[2] and # D
ternaries[1].elements[2] == ternaries[3].elements[2]): # D
# Type 3: A_{x}B_{1-x}C_{y}D_{1-y}
# binaries = (AC, AD, BC, BD)
# ternaries = (ABC, ABD, ACD, BCD)
self._type = 3
self._element_x = ternaries[0].elements[0]
self._element_1mx = ternaries[0].elements[1]
self._element_y = ternaries[2].elements[1]
self._element_1my = ternaries[2].elements[2]
self.binaries = (ternaries[2].binaries[0],
ternaries[2].binaries[1],
ternaries[3].binaries[0],
ternaries[3].binaries[1],)
calc_elements = (ternaries[0].elements[0],
ternaries[0].elements[1],
ternaries[2].elements[1],
ternaries[2].elements[2])
else:
raise ValueError()
assert elements == calc_elements
super(IIIVZincBlendeQuaternary, self).__init__(name, elements,
parameters=parameters)
self.ternaries = ternaries
if x is not None or y is not None or z is not None:
self._xyz = self._parse_xyz(x, y, z)
else:
self._xyz = None
def __eq__(self, other):
return (type(self) == type(other) and
self.name == other.name and
self.elements == other.elements,
self.ternaries == other.ternaries,
self._parameters == other._parameters,
self._xyz == other._xyz)
def _parse_xyz(self, x, y, z):
if self._type == 1 or self._type == 2:
# Type 1: AB_{x}C_{y}D_{1-x-y}
# binaries = (AB, AC, AD)
# ternaries = (ABC, ABD ,ACD)
# Type 2: A_{x}B_{y}C_{1-x-y}D
# binaries = (AD, BD, CD)
# ternaries = (ABD, ACD, BCD)
if x is not None and y is not None and z is None:
x = round(float(x), 6)
y = round(float(y), 6)
z = round(1. - x - y, 6)
elif x is not None and y is None and z is not None:
x = round(float(x), 6)
z = round(float(z), 6)
y = round(1. - x - z, 6)
elif x is None and y is not None and z is not None:
y = round(float(y), 6)
z = round(float(z), 6)
x = round(1. - y - z, 6)
else:
raise ValueError()
elif self._type == 3:
# Type 3: A_{x}B_{1-x}C_{y}D_{1-y}
# binaries = (AC, AD, BC, BD)
# ternaries = (ABC, ABD, ACD, BCD)
if x is not None and y is not None and z is None:
x = round(float(x), 6)
y = round(float(y), 6)
z = None
else:
raise ValueError()
else:
raise RuntimeError()
if (not (0. <= x <= 1.) or not (0. <= y <= 1.) or
z is not None and not (0. <= z <= 1.) ):
raise ValueError('The alloy fractions must be between 0 and 1')
return x, y, z
def _instance(self, x=None, y=None, z=None):
instance = IIIVZincBlendeQuaternary(self.name, self.elements,
self.ternaries, x=x, y=y, z=z)
for parameter in self._parameters.values():
instance.set_parameter(parameter)
return instance
def _has_x(self, kwargs):
'''Returns True if x is explicitly defined in kwargs'''
return (('x' in kwargs) or (self._element_x in kwargs) or
(self._type == 3 and self._element_1mx in kwargs))
def _get_x(self, kwargs):
'''
Returns x if it is explicitly defined in kwargs.
Otherwise, raises TypeError.
'''
if 'x' in kwargs:
return round(float(kwargs['x']), 6)
elif self._element_x in kwargs:
return round(float(kwargs[self._element_x]), 6)
elif self._type == 3 and self._element_1mx in kwargs:
return round(1. - float(kwargs[self._element_1mx]), 6)
else:
raise TypeError()
def _has_y(self, kwargs):
'''Returns True if y is explicitly defined in kwargs'''
return (('y' in kwargs) or (self._element_y in kwargs) or
(self._type == 3 and self._element_1my in kwargs))
def _get_y(self, kwargs):
'''
Returns y if it is explicitly defined in kwargs.
Otherwise, raises TypeError.
'''
if 'y' in kwargs:
return round(float(kwargs['y']), 6)
elif self._element_y in kwargs:
return round(float(kwargs[self._element_y]), 6)
elif self._type == 3 and self._element_1my in kwargs:
return round(1. - float(kwargs[self._element_1my]), 6)
else:
raise TypeError()
def _has_z(self, kwargs):
'''
Returns True if type is 1 or 2 and z is explicitly defined in kwargs.
'''
return ((self._type == 1 or self._type ==2) and
(('z' in kwargs) or (self._element_z in kwargs)))
def _get_z(self, kwargs):
'''
Returns z if type is 1 or 2 and z is explicitly defined in kwargs.
Otherwise, raises TypeError.
'''
if self._type == 1 or self._type == 2:
if 'z' in kwargs:
return round(float(kwargs['z']), 6)
elif self._element_z in kwargs:
return round(float(kwargs[self._element_z]), 6)
raise TypeError()
def __call__(self, **kwargs):
'''
Used to specify the alloy composition.
'''
if self._has_x(kwargs) and self._has_y(kwargs):
x = self._get_x(kwargs)
y = self._get_y(kwargs)
z = None
elif self._has_x(kwargs) and self._has_z(kwargs):
x = self._get_x(kwargs)
y = None
z = self._get_z(kwargs)
elif self._has_y(kwargs) and self._has_z(kwargs):
x = None
y = self._get_y(kwargs)
z = self._get_z(kwargs)
elif 'a' in kwargs and (self._has_x(kwargs) or
self._has_y(kwargs) or
self._has_z(kwargs) ):
# lattice match to the given lattice constant
a = kwargs['a']
T = kwargs.get('T', 300.)
# make sure the lattice constant is available
if self._has_x(kwargs):
x = self._get_x(kwargs)
ymax = round(1. - x, 6)
z = None
a0 = self(x=x, y=0.).a(T=T)
a1 = self(x=x, y=ymax).a(T=T)
amin = min(a0, a1)
amax = max(a0, a1)
if not (amin <= a <= amax):
raise ValueError('a of {:g} out of range [{:g}, {:g}]'
''.format(a, amin, amax))
# find the correct composition, x
y = bisect(func=lambda y: self(x=x, y=y).a(T=T) - a,
a=0, b=ymax)
elif self._has_y(kwargs):
y = self._get_y(kwargs)
xmax = round(1. - y, 6)
z = None
a0 = self(x=0., y=y).a(T=T)
a1 = self(x=xmax, y=y).a(T=T)
amin = min(a0, a1)
amax = max(a0, a1)
if not (amin <= a <= amax):
raise ValueError('a of {:g} out of range [{:g}, {:g}]'
''.format(a, amin, amax))
# find the correct composition, x
x = bisect(func=lambda x: self(x=x, y=y).a(T=T) - a,
a=0, b=xmax)
elif self._has_z(kwargs):
y = None
z = self._get_z(kwargs)
xmax = round(1. - z, 6)
a0 = self(x=0., z=z).a(T=T)
a1 = self(x=xmax, z=z).a(T=T)
amin = min(a0, a1)
amax = max(a0, a1)
if not (amin <= a <= amax):
raise ValueError('a of {:g} out of range [{:g}, {:g}]'
''.format(a, amin, amax))
# find the correct composition, x
x = bisect(func=lambda x: self(x=x, z=z).a(T=T) - a,
a=0, b=xmax)
else:
raise TypeError(
"Missing required key word argument.\n" + self._get_usage())
return self._instance(x=x, y=y, z=z)
def _get_usage(self):
if self._type == 1 or self._type == 2:
return ("The supported kwarg combinations are as follows:"
"\n - ('x' or '{x}') and ('y' or '{y}')"
"\n - ('x' or '{x}') and ('z' or '{z}')"
"\n - ('y' or '{y}') and ('z' or '{z}')"
"\n - 'a' [and 'T'] and ('x' or '{x}')"
"\n - 'a' [and 'T'] and ('y' or '{y}')"
"\n - 'a' [and 'T'] and ('z' or '{z}')"
"".format(x=self._element_x, y=self._element_y,
z=self._element_z))
elif self._type == 3:
return ("The supported kwargs combinations are as follows:"
"\n - ('x', '{x}' or '{_1mx}') and "
"('y', '{y}' or '{_1my}')"
"\n - 'a' [and 'T'] and ('x', '{x}' or '{_1mx}')"
"\n - 'a' [and 'T'] and ('y', '{y}' or '{_1my}')"
"".format(x=self._element_x, _1mx=self._element_1mx,
y=self._element_y, _1my=self._element_1my))
else:
raise RuntimeError()
def __repr__(self):
if self._xyz is None:
return '{}'.format(self.name)
elif self._type in [1, 2, 3]:
x, y, _ = self._xyz
return '{}({}={}, {}={})'.format(self.name,
self._element_x, x,
self._element_y, y)
else:
raise RuntimeError()
[docs] def latex(self):
e = {'A':self.elements[0],
'B':self.elements[1],
'C':self.elements[2],
'D':self.elements[3]}
if self._type == 1:
if self._xyz is None:
return ("{A}{B}_{{x}}{C}_{{y}}{D}_{{1-x-y}}"
"".format(**e))
else:
x, y, z = self._xyz
return ("{A}{B}_{{{:g}}}{C}_{{{:g}}}{D}_{{{:g}}}"
"".format(x, y, z, **e))
elif self._type == 2:
if self._xyz is None:
return ("{A}_{{x}}{B}_{{y}}{C}_{{1-x-y}}{D}"
"".format(**e))
else:
x, y, z = self._xyz
return ("{A}_{{{:g}}}{B}_{{{:g}}}{C}_{{{:g}}}{D}"
"".format(x, y, z, **e))
elif self._type == 3:
if self._xyz is None:
return ("{A}_{{x}}{B}_{{1-x}}{C}_{{y}}{D}_{{1-y}}"
"".format(**e))
else:
x, y, z = self._xyz
_1mx = round(1. - x, 6)
_1my = round(1. - y, 6)
return ("{A}_{{{:g}}}{B}_{{{:g}}}{C}_{{{:g}}}{D}_{{{:g}}}"
"".format(x, _1mx, y, _1my, **e))
else:
raise RuntimeError()
[docs] def element_fraction(self, element):
if self._xyz is None:
raise TypeError('Alloy composition has not been specified.')
if self._type == 1 or self._type == 2:
# AB_{x}C_{y}D_{1-x-y}
if element == self._element_w:
return 1
elif element == self._element_x:
return self._x
elif element == self._element_y:
return self._y
elif element == self._element_z:
return self._z
else:
return 0
elif self._type == 3:
if element == self._element_x:
return self._x
elif element == self._element_1mx:
return (1 - self._x)
elif element == self._element_y:
return self._y
elif element == self._element_1my:
return (1 - self._y)
else:
return 0
else:
raise RuntimeError()
def _get_bowing(self, name, kwargs):
p = self.get_parameter(name+'_bowing', default=None)
if p is None:
return None
x, y, z = self._xyz
return p(x=x, y=y, z=z, **kwargs)
def _interpolate(self, name, kwargs):
if self._xyz is None:
raise TypeError('Alloy composition has not been specified.')
if self._type == 1 or self._type == 2:
return self._interpolate1or2(name, kwargs)
elif self._type == 3:
return self._interpolate3(name, kwargs)
else:
raise RuntimeError()
def _interpolate1or2(self, name, kwargs):
x, y, z = self._xyz
u = (1. + x - y) / 2.
v = (1. + y - z) / 2.
w = (1. + x - z) / 2.
t12 = self.ternaries[0](x=u)
t13 = self.ternaries[1](x=w)
t23 = self.ternaries[2](x=v)
p12 = t12.get_parameter(name)
if p12 is None:
raise AttributeError('"{}" is missing a required parameter: "{}".'
''.format(t12.name, name))
p13 = t13.get_parameter(name)
if p13 is None:
raise AttributeError('"{}" is missing a required parameter: "{}".'
''.format(t13.name, name))
p23 = t23.get_parameter(name)
if p23 is None:
raise AttributeError('"{}" is missing a required parameter: "{}".'
''.format(t23.name, name))
v12 = p12(**kwargs)
v13 = p13(**kwargs)
v23 = p23(**kwargs)
# Calculate the weights, for use in the next step
weight12 = x * y
weight13 = x * z
weight23 = y * z
num = weight12 * v12 + weight13 * v13 + weight23 * v23
denom = weight12 + weight13 + weight23
# handle these cases explicitly, so there's no divide by zero
if denom == 0.:
if x == 0.:
return v23
else:
return v13
# Check if there are bowing parameters provided
C = self._get_bowing(name, kwargs)
if C is not None:
# a bowing parameter exists - use it
# Note: this is an experimental mixing formula for
# adding additional quaternary-induced bowing
return num / denom - C * x * (1-x) * y * (1-y) * z * (1-z)
else:
# otherwise, use a weighted average of the ternary bowing
# parameters
return num / denom
def _interpolate3(self, name, kwargs):
x, y, _ = self._xyz
# B1 = Q(0, 0) = BD = InSb
# B2 = Q(1, 0) = BC = InAs
# B3 = Q(1, 1) = AC = AlAs
# B4 = Q(0, 1) = AD = AlSb
# t12 = Q(x, 0) = ABD = AlInSb
# t23 = Q(1, y) = ACD = AlAsSb
# t43 = Q(x, 1) = ABC = AlInAs
# t14 = Q(0, y) = BCD = InAsSb
t43 = self.ternaries[0](x=x)
t12 = self.ternaries[1](x=x)
t23 = self.ternaries[2](x=y)
t14 = self.ternaries[3](x=y)
p12 = t12.get_parameter(name)
if p12 is None:
raise AttributeError('"{}" is missing a required parameter: "{}".'
''.format(t12.name, name))
p23 = t23.get_parameter(name)
if p23 is None:
raise AttributeError('"{}" is missing a required parameter: "{}".'
''.format(t23.name, name))
p43 = t43.get_parameter(name)
if p43 is None:
raise AttributeError('"{}" is missing a required parameter: "{}".'
''.format(t43.name, name))
p14 = t14.get_parameter(name)
if p14 is None:
raise AttributeError('"{}" is missing a required parameter: "{}".'
''.format(t14.name, name))
v12 = p12(**kwargs)
v23 = p23(**kwargs)
v43 = p43(**kwargs)
v14 = p14(**kwargs)
# handle these cases explicitly, so there's no divide by zero
if x == 0.:
return v14
elif x == 1.:
return v23
elif y == 0.:
return v12
elif y == 1.:
return v43
xinv = 1. - x
yinv = 1. - y
xweight = x * xinv
yweight = y * yinv
num = (xweight * (yinv * v12 + y * v43) +
yweight * (xinv * v14 + x * v23))
denom = xweight + yweight
# Check if there are bowing parameters provided
C = self._get_bowing(name, kwargs)
if C is not None:
# a bowing parameter exists - use it
# Note: this is a new, experimental mixing formula for
# adding additional quaternary-induced bowing
return num / denom - C * xweight * yweight
else:
return num / denom