#
# Copyright (c) 2013-2014, 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/>.
#
#############################################################################
from openbandparams.iii_v.base_material import BaseType, Base
from openbandparams.algorithms import bisect
from openbandparams.utils import classinstancemethod
[docs]class Quaternary1or2Type(BaseType):
def __getattr__(self, name):
# acts like a class method for Quaternary.__getattr__
if (hasattr(self.ternaries[0], name) and
hasattr(self.ternaries[1], name) and
hasattr(self.ternaries[2], name)):
def _param_accessor(**kwargs):
return self._interpolate(name, **kwargs)
return _param_accessor
else:
raise AttributeError(name)
[docs]class Quaternary3Type(BaseType):
def __getattr__(self, name):
# acts like a class method for Quaternary3.__getattr__
if (hasattr(self.ternaries[0], name) and
hasattr(self.ternaries[1], name) and
hasattr(self.ternaries[2], name) and
hasattr(self.ternaries[3], name)):
def _param_accessor(**kwargs):
return self._interpolate(name, **kwargs)
return _param_accessor
else:
raise AttributeError(name)
[docs]class Quaternary(Base):
def __eq__(self, other):
return (type(self) == type(other) and
self._x == other._x and
self._y == other._y)
@classmethod
def _get_bowing(cls, param):
if hasattr(cls, '_bowing_%s' % param):
# bowing parameters exists - use it
C = getattr(cls, '_bowing_%s' % param)
if hasattr(C, '__call__'):
# it's trying to mix the ternary bowing parameters
return None
else:
return C
else:
return None
[docs]class Quaternary1or2(Quaternary):
'''
For alloys of the AB_{x}C_{y}D_{1-x-y} and A_{x}B_{y}C_{1-x-y}D types [1].
These require only three ternaries for interpolation.
[1] C. K. Williams, T. H. Glisson, J. R. Hauser, and M. A. Littlejohn,
"Energy bandgap and lattice constant contours of iii-v quaternary
alloys of the form Ax By Cz D or A Bx Cy Dz," JEM, vol. 7, no. 5,
pp. 639-646, Sep. 1978.
'''
__metaclass__ = Quaternary1or2Type
def __init__(self, **kwargs):
Quaternary.__init__(self)
self._x, self._y, self._z = self._get_xyz(kwargs)
def __getattr__(self, name):
if (hasattr(self.ternaries[0], name) and
hasattr(self.ternaries[1], name) and
hasattr(self.ternaries[2], name)):
def _param_accessor(**kwargs):
return self._interpolate(name, **kwargs)
return _param_accessor
else:
raise AttributeError(name)
@classmethod
def _validate_xyz(cls, x, y, z):
assert x >= 0. and x <= 1.
assert y >= 0. and y <= 1.
assert z >= 0. and z <= 1.
assert x + y + z <= 1. + 1e-5
@classmethod
def _get_xyz(cls, kwargs):
if cls._has_x(kwargs) and cls._has_y(kwargs):
x = cls._get_x(kwargs)
y = cls._get_y(kwargs)
z = round(1 - x - y, 6)
elif cls._has_x(kwargs) and cls._has_z(kwargs):
x = cls._get_x(kwargs)
z = cls._get_z(kwargs)
y = round(1 - x - z, 6)
elif cls._has_y(kwargs) and cls._has_z(kwargs):
y = cls._get_y(kwargs)
z = cls._get_z(kwargs)
x = round(1 - y - z, 6)
elif 'a' in kwargs and cls._has_x(kwargs):
T = kwargs.get('T', 300)
x, y, z = cls._lattice_match(kwargs['a'], T,
x=cls._get_x(kwargs))
elif 'a' in kwargs and cls._has_y(kwargs):
T = kwargs.get('T', 300)
x, y, z = cls._lattice_match(kwargs['a'], T,
y=cls._get_y(kwargs))
elif 'a' in kwargs and cls._has_z(kwargs):
T = kwargs.get('T', 300)
x, y, z = cls._lattice_match(kwargs['a'], T,
z=cls._get_z(kwargs))
else:
raise TypeError(
"Missing required key word argument.\n" + cls._get_usage())
cls._validate_xyz(x, y, z)
return x, y, z
@classmethod
def _lattice_match(cls, a, T, x=None, y=None, z=None):
if x is not None:
# make sure the lattice constant is in range
a1 = cls.a(x=x, y=0, T=T)
a2 = cls.a(x=x, y=(1 - x), T=T)
amin = min(a1, a2)
amax = max(a1, a2)
if a < amin or a > amax:
raise ValueError('a out of range [%.3f, %.3f]' % (amin, amax))
# find the correct y composition
y = bisect(func=lambda y: cls.a(x=x, y=y, T=T) - a, a=0, b=(1 - x))
z = round(1 - x - y, 6)
return x, y, z
elif y is not None:
# make sure the lattice constant is in range
a1 = cls.a(x=0, y=y, T=T)
a2 = cls.a(x=(1 - y), y=y, T=T)
amin = min(a1, a2)
amax = max(a1, a2)
if a < amin or a > amax:
raise ValueError('a out of range [%.3f, %.3f]' % (amin, amax))
# find the correct y composition
x = bisect(func=lambda x: cls.a(x=x, y=y, T=T) - a, a=0, b=(1 - y))
z = round(1 - x - y, 6)
return x, y, z
elif z is not None:
# make sure the lattice constant is in range
a1 = cls.a(x=0, z=z, T=T)
a2 = cls.a(x=(1 - z), z=z, T=T)
amin = min(a1, a2)
amax = max(a1, a2)
if a < amin or a > amax:
raise ValueError('a out of range [%.3f, %.3f]' % (amin, amax))
# find the correct y composition
x = bisect(func=lambda x: cls.a(x=x, z=z, T=T) - a, a=0, b=(1 - z))
y = round(1 - x - z, 6)
return x, y, z
else:
raise ValueError('Need x, y or z')
@classinstancemethod
def _interpolate(self, cls, param, **kwargs):
if self is not None:
x = self._x
y = self._y
z = self._z
else:
x, y, z = cls._get_xyz(kwargs)
vals = []
for t in [cls.ternaries[0], cls.ternaries[1], cls.ternaries[2]]:
try:
vals.append(getattr(t, param))
except AttributeError as e:
e.message += '. Ternary `%s`' % t.name
e.message += ' missing param `%s`' % param
raise e
if param[0] == '_':
# assume it's a hard coded parameter if it starts with '_'
t12 = vals[0]
t13 = vals[1]
t23 = vals[2]
else:
# otherwise it's an accessor function.
# See the reference in this class's docstring for more
# information about the interpolation method.
u = (1. + x - y) / 2.
v = (1. + y - z) / 2.
w = (1. + x - z) / 2.
new_kwargs = dict(kwargs)
new_kwargs['x'] = u
t12 = vals[0](**new_kwargs)
new_kwargs['x'] = w
t13 = vals[1](**new_kwargs)
new_kwargs['x'] = v
t23 = vals[2](**new_kwargs)
# Calculate the weights, for use in the next step
weight12 = x * y
weight13 = x * z
weight23 = y * z
num = weight12 * t12 + weight13 * t13 + weight23 * t23
denom = weight12 + weight13 + weight23
# handle these cases explicitly, so there's no divide by zero
if denom == 0.:
if x == 0.:
return t23
else:
return t13
# Check if there are bowing parameters provided
C = cls._get_bowing(param)
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
# Type 1: AB_{x}C_{y}D_{1-x-y}
# binaryies = (AB, AC, AD)
# ternaries = (ABC, ABD ,ACD)
[docs]class Quaternary1(Quaternary1or2):
'''
For alloys of the AB_{x}C_{y}D_{1-x-y} type [1], where A is the only
Group III element. These require only three ternaries for interpolation.
[1] C. K. Williams, T. H. Glisson, J. R. Hauser, and M. A. Littlejohn,
"Energy bandgap and lattice constant contours of iii-v quaternary
alloys of the form Ax By Cz D or A Bx Cy Dz," JEM, vol. 7, no. 5,
pp. 639-646, Sep. 1978.
'''
def __repr__(self):
e1 = self.elements[0]
e2 = self.elements[1]
e3 = self.elements[2]
e4 = self.elements[3]
f2 = self.elementFraction(e2)
f3 = self.elementFraction(e3)
return "{A}{B}{C}{D}({B}={:g}, {C}={:g})".format(f2, f3,
A=e1, B=e2, C=e3, D=e4)
@classinstancemethod
[docs] def LaTeX(self, cls):
if self is not None:
e1 = self.elements[0]
e2 = self.elements[1]
e3 = self.elements[2]
e4 = self.elements[3]
f2 = self.elementFraction(e2)
f3 = self.elementFraction(e3)
f4 = self.elementFraction(e4)
return "{A}{B}_{{{:g}}}{C}_{{{:g}}}{D}_{{{:g}}}".format(
f2, f3, f4,
A=e1, B=e2, C=e3, D=e4)
else:
e1 = cls.elements[0]
e2 = cls.elements[1]
e3 = cls.elements[2]
e4 = cls.elements[3]
return "{A}{B}_{{x}}{C}_{{y}}{D}_{{1-x-y}}".format(
A=e1, B=e2, C=e3, D=e4)
@classmethod
def _has_x(cls, kwargs):
'''Returns True if x is explicitly defined in kwargs'''
return ('x' in kwargs) or (cls.elements[1] in kwargs)
@classmethod
def _get_x(cls, kwargs):
'''
Returns x if it is explicitly defined in kwargs.
Otherwise, raises TypeError.
'''
if 'x' in kwargs:
return round(float(kwargs['x']), 6)
elif cls.elements[1] in kwargs:
return round(float(kwargs[cls.elements[1]]), 6)
else:
raise TypeError("Neither 'x' nor '{}' are in kwargs"
"".format(cls.elements[1]))
@classmethod
def _has_y(cls, kwargs):
'''Returns True if y is explicitly defined in kwargs'''
return ('y' in kwargs) or (cls.elements[2] in kwargs)
@classmethod
def _get_y(cls, kwargs):
'''
Returns y if it is explicitly defined in kwargs.
Otherwise, raises TypeError.
'''
if 'y' in kwargs:
return round(float(kwargs['y']), 6)
elif cls.elements[2] in kwargs:
return round(float(kwargs[cls.elements[2]]), 6)
else:
raise TypeError("Neither 'y' nor '{}' are in kwargs"
"".format(cls.elements[2]))
@classmethod
def _has_z(cls, kwargs):
'''Returns True if z is explicitly defined in kwargs'''
return ('z' in kwargs) or (cls.elements[3] in kwargs)
@classmethod
def _get_z(cls, kwargs):
'''
Returns z if it is explicitly defined in kwargs.
Otherwise, raises TypeError.
'''
if 'z' in kwargs:
return round(float(kwargs['z']), 6)
elif cls.elements[3] in kwargs:
return round(float(kwargs[cls.elements[3]]), 6)
else:
raise TypeError("Neither 'z' nor '{}' are in kwargs"
"".format(cls.elements[3]))
@classmethod
def _get_usage(cls):
return ("The supported kwarg combinations are as follows:"
"\n - ('x' or '{B}') and ('y' or '{C}')"
"\n - ('x' or '{B}') and ('z' or '{D}')"
"\n - ('y' or '{C}') and ('z' or '{D}')"
"\n - 'a' and ('x' or '{B}')"
"\n - 'a' and ('y' or '{C}')"
"\n - 'a' and ('z' or '{D}')"
"".format(A=cls.elements[0], B=cls.elements[1],
C=cls.elements[2], D=cls.elements[3]))
@classinstancemethod
[docs] def elementFraction(self, cls, element):
# AB_{x}C_{y}D_{1-x-y}
if element == cls.elements[0]:
return 1
elif element == cls.elements[1]:
return self._x
elif element == cls.elements[2]:
return self._y
elif element == cls.elements[3]:
return self._z
else:
return 0
# Type 2: A_{x}B_{y}C_{1-x-y}D
# binaries = (AD, BD, CD)
# ternaries = (ABD, ACD, BCD)
[docs]class Quaternary2(Quaternary1or2):
'''
For alloys of the A_{x}B_{y}C_{1-x-y}D type [1], where D is the only
Group V element. These require only three ternaries for interpolation.
[1] C. K. Williams, T. H. Glisson, J. R. Hauser, and M. A. Littlejohn,
"Energy bandgap and lattice constant contours of iii-v quaternary
alloys of the form Ax By Cz D or A Bx Cy Dz," JEM, vol. 7, no. 5,
pp. 639-646, Sep. 1978.
'''
def __repr__(self):
e1 = self.elements[0]
e2 = self.elements[1]
e3 = self.elements[2]
e4 = self.elements[3]
f1 = self.elementFraction(e1)
f2 = self.elementFraction(e2)
return "{A}{B}{C}{D}({A}={:g}, {B}={:g})".format(f1, f2,
A=e1, B=e2, C=e3, D=e4)
@classinstancemethod
[docs] def LaTeX(self, cls):
if self is not None:
e1 = self.elements[0]
e2 = self.elements[1]
e3 = self.elements[2]
e4 = self.elements[3]
f1 = self.elementFraction(e1)
f2 = self.elementFraction(e2)
f3 = self.elementFraction(e3)
return "{A}_{{{:g}}}{B}_{{{:g}}}{C}_{{{:g}}}{D}".format(
f1, f2, f3,
A=e1, B=e2, C=e3, D=e4)
else:
e1 = cls.elements[0]
e2 = cls.elements[1]
e3 = cls.elements[2]
e4 = cls.elements[3]
return "{A}_{{x}}{B}_{{y}}{C}_{{1-x-y}}{D}".format(
A=e1, B=e2, C=e3, D=e4)
@classmethod
def _has_x(cls, kwargs):
'''Returns True if x is explicitly defined in kwargs'''
return ('x' in kwargs) or (cls.elements[0] in kwargs)
@classmethod
def _get_x(cls, kwargs):
'''
Returns x if it is explicitly defined in kwargs.
Otherwise, raises TypeError.
'''
if 'x' in kwargs:
return round(float(kwargs['x']), 6)
elif cls.elements[0] in kwargs:
return round(float(kwargs[cls.elements[0]]), 6)
else:
raise TypeError("Neither 'x' nor '{}' are in kwargs"
"".format(cls.elements[0]))
@classmethod
def _has_y(cls, kwargs):
'''Returns True if y is explicitly defined in kwargs'''
return ('y' in kwargs) or (cls.elements[1] in kwargs)
@classmethod
def _get_y(cls, kwargs):
'''
Returns y if it is explicitly defined in kwargs.
Otherwise, raises TypeError.
'''
if 'y' in kwargs:
return round(float(kwargs['y']), 6)
elif cls.elements[1] in kwargs:
return round(float(kwargs[cls.elements[1]]), 6)
else:
raise TypeError("Neither 'y' nor '{}' are in kwargs"
"".format(cls.elements[1]))
@classmethod
def _has_z(cls, kwargs):
'''Returns True if z is explicitly defined in kwargs'''
return ('z' in kwargs) or (cls.elements[2] in kwargs)
@classmethod
def _get_z(cls, kwargs):
'''
Returns z if it is explicitly defined in kwargs.
Otherwise, raises TypeError.
'''
if 'z' in kwargs:
return round(float(kwargs['z']), 6)
elif cls.elements[2] in kwargs:
return round(float(kwargs[cls.elements[2]]), 6)
else:
raise TypeError("Neither 'z' nor '{}' are in kwargs"
"".format(cls.elements[2]))
@classmethod
def _get_usage(cls):
return ("The supported kwarg combinations are as follows:"
"\n - ('x' or '{A}') and ('y' or '{B}')"
"\n - ('x' or '{A}') and ('z' or '{C}')"
"\n - ('y' or '{B}') and ('z' or '{C}')"
"\n - 'a' and ('x' or '{A}')"
"\n - 'a' and ('y' or '{B}')"
"\n - 'a' and ('z' or '{C}')"
"".format(A=cls.elements[0], B=cls.elements[1],
C=cls.elements[2], D=cls.elements[3]))
@classinstancemethod
[docs] def elementFraction(self, cls, element):
# A_{x}B_{y}C_{1-x-y}D
if element == cls.elements[0]:
return self._x
elif element == cls.elements[1]:
return self._y
elif element == cls.elements[2]:
return self._z
elif element == cls.elements[3]:
return 1
else:
return 0
# Type 3: A_{x}B_{1-x}C_{y}D_{1-y}
# binaries = (AC, AD, BC, BD)
# ternaries = (ABC, ABD, ACD, BCD)
[docs]class Quaternary3(Quaternary):
'''
For alloys of the A_{x}B_{1-x}C_{y}D_{1-y} type [1-2]. Where A and B are
Group III elements, and C and D are Group V elements. These require
four ternaries for interpolation.
[1] T. H. Glisson, J. R. Hauser, M. A. Littlejohn, and C. K. Williams,
"Energy bandgap and lattice constant contours of iii-v quaternary
alloys," JEM, vol. 7, no. 1, pp. 1-16, Jan. 1978.
[2] I. Vurgaftman, J. R. Meyer, and L. R. Ram-Mohan, "Band parameters
for III-V compound semiconductors and their alloys," J. Appl. Phys.,
vol. 89, no. 11, pp. 5815-5875, Jun. 2001.
'''
__metaclass__ = Quaternary3Type
def __init__(self, **kwargs):
Quaternary.__init__(self)
self._x, self._y = self._get_xy(kwargs)
def __getattr__(self, name):
if (hasattr(self.ternaries[0], name) and
hasattr(self.ternaries[1], name) and
hasattr(self.ternaries[2], name) and
hasattr(self.ternaries[3], name)):
def _param_accessor(**kwargs):
return self._interpolate(name, **kwargs)
return _param_accessor
else:
raise AttributeError(name)
def __repr__(self):
e1 = self.elements[0]
e2 = self.elements[1]
e3 = self.elements[2]
e4 = self.elements[3]
f1 = self.elementFraction(e1)
f3 = self.elementFraction(e3)
return "{A}{B}{C}{D}({A}={:g}, {C}={:g})".format(f1, f3,
A=e1, B=e2, C=e3, D=e4)
@classinstancemethod
[docs] def LaTeX(self, cls):
if self is not None:
e1 = self.elements[0]
e2 = self.elements[1]
e3 = self.elements[2]
e4 = self.elements[3]
f1 = self.elementFraction(e1)
f2 = self.elementFraction(e2)
f3 = self.elementFraction(e3)
f4 = self.elementFraction(e4)
return "{A}_{{{:g}}}{B}_{{{:g}}}{C}_{{{:g}}}{D}_{{{:g}}}".format(
f1, f2, f3, f4,
A=e1, B=e2, C=e3, D=e4)
else:
e1 = cls.elements[0]
e2 = cls.elements[1]
e3 = cls.elements[2]
e4 = cls.elements[3]
return "{A}_{{x}}{B}_{{1-x}}{C}_{{y}}{D}_{{1-y}}".format(
A=e1, B=e2, C=e3, D=e4)
@classmethod
def _has_x(cls, kwargs):
'''Returns True if x is explicitly defined in kwargs'''
return (('x' in kwargs) or (cls.elements[0] in kwargs) or
(cls.elements[1] in kwargs))
@classmethod
def _get_x(cls, kwargs):
'''
Returns x if it is explicitly defined in kwargs.
Otherwise, raises TypeError.
'''
if 'x' in kwargs:
return round(float(kwargs['x']), 6)
elif cls.elements[0] in kwargs:
return round(float(kwargs[cls.elements[0]]), 6)
elif cls.elements[1] in kwargs:
return round(1 - round(float(kwargs[cls.elements[1]]), 6), 6)
else:
raise TypeError("Neither 'x', '{}' nor '{}' are in kwargs"
"".format(cls.elements[0], cls.elements[1]))
@classmethod
def _has_y(cls, kwargs):
'''Returns True if y is explicitly defined in kwargs'''
return (('y' in kwargs) or (cls.elements[2] in kwargs) or
(cls.elements[3] in kwargs))
@classmethod
def _get_y(cls, kwargs):
'''
Returns y if it is explicitly defined in kwargs.
Otherwise, raises TypeError.
'''
if 'y' in kwargs:
return round(float(kwargs['y']), 6)
elif cls.elements[2] in kwargs:
return round(float(kwargs[cls.elements[2]]), 6)
elif cls.elements[3] in kwargs:
return round(1 - round(float(kwargs[cls.elements[3]]), 6), 6)
else:
raise TypeError("Neither 'y' nor '{}' are in kwargs"
"".format(cls.elements[2]))
@classmethod
def _get_xy(cls, kwargs):
if cls._has_x(kwargs) and cls._has_y(kwargs):
x = cls._get_x(kwargs)
y = cls._get_y(kwargs)
elif 'a' in kwargs and cls._has_x(kwargs):
T = kwargs.get('T', 300)
x, y = cls._lattice_match(kwargs['a'], T,
x=cls._get_x(kwargs))
elif 'a' in kwargs and cls._has_y(kwargs):
T = kwargs.get('T', 300)
x, y = cls._lattice_match(kwargs['a'], T,
y=cls._get_y(kwargs))
else:
raise TypeError(
"Missing required key word argument.\n" + cls._get_usage())
cls._validate_xy(x, y)
return x, y
@classmethod
def _lattice_match(cls, a, T, x=None, y=None):
if x is not None:
# make sure the lattice constant is in range
a1 = cls.a(x=x, y=0, T=T)
a2 = cls.a(x=x, y=1, T=T)
amin = min(a1, a2)
amax = max(a1, a2)
if a < amin or a > amax:
raise ValueError('a out of range [%.3f, %.3f]' % (amin, amax))
# find the correct y composition
y = bisect(func=lambda y: cls.a(x=x, y=y, T=T) - a, a=0, b=1)
return x, y
elif y is not None:
# make sure the lattice constant is in range
a1 = cls.a(x=0, y=y, T=T)
a2 = cls.a(x=1, y=y, T=T)
amin = min(a1, a2)
amax = max(a1, a2)
if a < amin or a > amax:
raise ValueError('a out of range [%.3f, %.3f]' % (amin, amax))
# find the correct y composition
x = bisect(func=lambda x: cls.a(x=x, y=y, T=T) - a, a=0, b=1)
return x, y
else:
raise ValueError('Need x or y')
@classmethod
def _validate_xy(cls, x, y):
assert x >= 0. and x <= 1.
assert y >= 0. and y <= 1.
@classmethod
def _get_usage(cls):
return ("The supported kwargs combinations are as follows:"
"\n - ('x', '{A}' or '{B}') and ('y', '{C}' or '{D}')"
"\n - 'a' and ('x', '{A}' or '{B}')"
"\n - 'a' and ('y', '{C}' or '{D}')"
"".format(A=cls.elements[0], B=cls.elements[1],
C=cls.elements[2], D=cls.elements[3]))
@classinstancemethod
def _interpolate(self, cls, param, **kwargs):
if self is not None:
x = self._x
y = self._y
else:
x, y = cls._get_xy(kwargs)
vals = []
for t in [cls.ternaries[0], cls.ternaries[1],
cls.ternaries[2], cls.ternaries[3]]:
try:
vals.append(getattr(t, param))
except AttributeError as e:
e.message += '. Ternary `%s`' % t.name
e.message += ' missing param `%s`' % param
raise e
# 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
if param[0] == '_':
# assume it's a hard coded parameter if it starts with '_'
T43 = vals[0]
T12 = vals[1]
T23 = vals[2]
T14 = vals[3]
else:
# otherwise it's an accessor function
new_kwargs = dict(kwargs)
new_kwargs.pop('x', None)
T43 = vals[0](x=x, **new_kwargs)
T12 = vals[1](x=x, **new_kwargs)
T23 = vals[2](x=y, **new_kwargs)
T14 = vals[3](x=y, **new_kwargs)
# handle these cases explicitly, so there's no divide by zero
if x == 0.:
return T14
elif x == 1.:
return T23
elif y == 0.:
return T12
elif y == 1.:
return T43
xinv = 1. - x
yinv = 1. - y
xweight = x * xinv
yweight = y * yinv
num = (xweight * (yinv * T12 + y * T43) +
yweight * (xinv * T14 + x * T23))
denom = xweight + yweight
# Check if there are bowing parameters provided
C = cls._get_bowing(param)
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
@classinstancemethod
[docs] def elementFraction(self, cls, element):
if element == cls.elements[0]:
return self._x
elif element == cls.elements[1]:
return (1 - self._x)
elif element == cls.elements[2]:
return self._y
elif element == cls.elements[3]:
return (1 - self._y)
else:
return 0