from collections import defaultdict
from solcore import ParameterSystem
import solcore
[docs]class Structure(list):
""" Subclass of lists that stores information of a 'sample' consisting of several 'layers'."""
def __init__(self, *args, **kwargs):
super(Structure, self).__init__(*args)
self.__dict__.update(kwargs)
self.labels = [None] * len(self)
[docs] def append(self, new_layer, layer_label=None, repeats=1):
# Pass the arguments to the superclass for extending
for i in range(repeats):
# Extend the structure labels
self.labels.append(layer_label)
super(Structure, self).append(new_layer)
[docs] def append_multiple(self, layers, layer_labels=None, repeats=1):
assert type(layers) == type([]), "`append_multiple` only accepts lists for the first argument."
if layer_labels is not None:
assert len(layers) == len(
layer_labels), "When using `layer_labels` keyword a label must be specified for each layer added i.e. layers and layer_labels must have the same number of elements. Either fix this or simply do not assign any labels (i.e. layer_labels=None)."
for i in range(repeats):
# Extend the structure by appending layers
self.extend(layers)
# Extend the structure labels by appending an equal number of None values
# or by appending the actual labels.
if layer_labels is None:
self.labels.extend([None] * len(layers))
else:
self.labels.extend(layer_labels)
def __str__(self):
layer_info = [" {} {}".format(
layer,
self.labels[i] if self.labels[i] is not None else "",
) for i, (layer, label) in enumerate(zip(self, self.labels))]
return "<Structure object\n{}\n{}>".format(str(self.__dict__), "\n".join(layer_info))
[docs] def width(self):
return sum([layer.width for layer in self])
[docs] def relative_widths(self):
total = 0
aggregate_widths = defaultdict(float)
for layer, comment in zip(self, self.labels):
aggregate_widths[comment] += layer.width
total += layer.width
for layername in aggregate_widths.keys():
aggregate_widths[layername] /= total
return aggregate_widths
[docs]class Layer:
""" Class that stores the information about layers of materials, such as thickness and composition.
It is the building block of the 'Structures' """
def __init__(self, width, material, role=None, geometry=None, **kwargs):
""" Layer class constructor.
:param width: Width of the layer, in SI units.
:param material: Solcore material
:param role: Role of the layer.
:param kwargs: Any other keyword parameter which will become part of the layer attributes
"""
self.width = width
self.material = material
self.role = role
self.geometry = geometry
self.__dict__.update(**kwargs)
def __str__(self):
""" Representation of the Layer object
:return: A string with a summary of the layer properties
"""
widthstring = "{:.3}nm".format(self.width * 1e9)
return "<{}layer {} {}>".format(
self.role + " " if self.role != None else "",
widthstring,
self.material,
)
[docs]class Junction(list):
""" Class that groups together the layers that make a junction. Essentially, it is just a list with attributes you can update.
"""
def __init__(self, *args, **kwargs):
self.__dict__.update(kwargs)
list.__init__(self, *args)
def __str__(self):
layer_info = ["{}".format(layer) for layer in self]
return "<Junction object \n\t{}\n\t{}>".format(str(self.__dict__), "\n\t".join(layer_info))
[docs]class TunnelJunction(Junction):
""" Class that contains the minimum definitions for a tunnel junction, ie. a series resistance in our case.
"""
def __init__(self, *args, **kwargs):
Junction.__init__(self, *args, **kwargs)
self.R = kwargs['R'] if 'R' in kwargs.keys() else 1e-16
self.pn = kwargs['pn'] if 'pn' in kwargs.keys() else True
# CONVERSION UTILITIES
# The following functions are used to move back and forth between the Solcore structures and the Device structures used
# in the PDD solver
[docs]def InLineComposition(layer):
""" Hack to use the Adachi-alfa calculator, provinding the composition as a single string
:param layer: A layer as defined in the Device structures of the PDD solver
:return: A mterial string
"""
comp = layer['properties']['composition']
if 'element' in comp.keys():
return comp['material'].replace(comp['element'], comp['element'] + str(comp['fraction']))
else:
return comp['material']
[docs]def SolcoreMaterialToStr(material_input):
""" Translate a solcore material into something than can be easily stored in a file and read
:param material_input: A solcore material
:return: A dictionary with the name, consituents and composition of the material
"""
material_string = material_input.__str__().strip('<>').split(" ")
material_name = material_string[0].strip("'")
composition = {'material': material_name}
alloy = True if len(material_input.composition) > 0 else False
if alloy:
material_composition = material_string[2].split("=")
for i, comp in enumerate(material_composition):
if comp in material_name:
composition['element'] = material_composition[i]
composition['fraction'] = float(material_composition[i + 1])
return composition
[docs]def ToSolcoreMaterial(comp, T, execute=False, **kwargs):
""" It provides a solcore material out of its string composition. The output can be a string with the command or
a solcore material itself.
:param comp: A Solcore material as a string
:param T: The temperature
:param execute: If a Solcore material must be provided or just a string that would do that.
:return: A Solcore material or a string with the command to calculate the solcore material
"""
# It provides a solcore material out of its string composition. The output can be a string with the comand or a solcore material itself.
if 'element' in comp.keys():
# A ternary material
out = 'solcore.material("%s")(T=%s, %s=%s' % (comp['material'], T, comp['element'], comp['fraction'])
else:
# A binary material
out = 'solcore.material("%s")(T=%s' % (comp['material'], T)
for key in kwargs.keys():
out = out + ', {}={}'.format(key, kwargs[key])
out = out + ') '
if execute:
return eval(out)
else:
return out
[docs]def ToLayer(width, material, role):
""" Creates a Layer based on strings containing the width, the material and the role
:param width: Width of the layer
:param material: Material of the layer, as a string
:param role: The role of the layer
:return: A Solcore Layer
"""
return eval('Layer( width=%s, material=%s, role="%s" )' % (width, material, role))
[docs]def ToStructure(device):
""" Translate the device object (as used by the PDD solver) into a list of solcore Structure that can be used in
other plugings. Only translate the composition, for now. It works only on non-nested device structures
(QW, for example, but not in devices with QW)
:param device: A solcore device structure as used by the PDD solver
:return: A Solcore Structure
"""
LayersList = []
MatList = []
for i in range(device['numlayers']):
layer = device['layers'][i]
MatList.append(ToSolcoreMaterial(layer['properties']['composition'], device['T']))
LayersList.append(ToLayer(layer['properties']['width'], MatList[i], layer['label']))
LayersList[-1].material.strained = 'True'
LayersList = Structure(LayersList)
LayersList.substrate = ToSolcoreMaterial(device['substrate'], device['T'], execute=True)
return LayersList