from copy import copy
from typing import Union, List
from .layers import Layer, ConstantLayer, AmbientLayer, Substrate
[docs]class MultilayerStructure:
"""Defines the structure of a multilayer sample through one or multiple :class:`Layer` objects and an ambient SLD.
Initializes with a default ambient and substrate layer.
Returns:
MultilayerStructure
"""
def __init__(self):
self._ambient_layer = AmbientLayer('undefined', 0)
self._substrate = Substrate('undefined', 0, 0)
self.layers = []
@property
def ambient_layer(self):
return self._ambient_layer
@property
def substrate(self):
return self._substrate
[docs] def set_ambient_layer(self, ambient_layer: AmbientLayer):
"""Set the ambient layer."""
self._check_layer_type(ambient_layer)
self._ambient_layer = ambient_layer
[docs] def set_substrate(self, substrate: Substrate):
"""Set the substrate layer."""
self._check_layer_type(substrate)
self._substrate = substrate
[docs] def add_layer(self, layer: Layer, index: Union[str, int] = 'next'):
"""Add layer at given index position. If ``index='next'`` (default) layer is appended (added on top)."""
self._check_layer_type(layer)
if index == 'next':
self.layers.append(layer)
elif type(index) is int:
self.layers.insert(index, layer)
else:
raise ValueError('position must be an integer index or "next"')
[docs] def rename_layer(self, layer_index, name):
"""Renames layer at given index to ``name``."""
self.layers[layer_index].name = name
[docs] def swap_layers(self, from_index: int, to_index: int):
"""Swaps the position of the two layers given by ``from_index`` and ``to_index``."""
if type(from_index) is not int or type(to_index) is not int:
raise ValueError('Indices must be integers')
swapped_value = self.layers[to_index]
self.layers[to_index] = self.layers[from_index]
self.layers[from_index] = swapped_value
[docs] def move_layer(self, from_index: int, to_index: int):
"""Moves layer with given index to new index, shifting all layers with equal or higher index up."""
if type(from_index) is not int or type(to_index) is not int:
raise ValueError('Indices must be integers')
if from_index < to_index:
self.layers.insert(to_index + 1, self.layers[from_index])
self.layers.remove(self.layers[from_index])
elif from_index > to_index:
moved_item = self.layers[from_index]
self.layers.remove(moved_item)
self.layers.insert(to_index, moved_item)
[docs] def to_dict(self) -> dict:
"""Returns a dictionary representation of the MultilayerStructure"""
sample_dict = {
'ambient_layer': self.ambient_layer.to_dict(),
'layers': [],
'substrate': self.substrate.to_dict()
}
for i in reversed(range(len(self.layers))):
sample_dict['layers'] = sample_dict['layers'] + [self.layers[i].to_dict()]
return sample_dict
[docs] def from_dict(self, sample_dict: dict):
"""Set the layers in the MultilayerStructure according to the dictionary representation."""
self.set_ambient_layer(AmbientLayer(**sample_dict['ambient_layer']))
self.set_substrate(Substrate(**sample_dict['substrate']))
for layer in reversed(sample_dict['layers']):
self.add_layer(Layer(**layer))
[docs] def copy(self):
return copy(self)
@property
def thicknesses(self) -> list:
thickness_list = []
for layer in self:
if hasattr(layer, 'thickness'):
thickness = layer.thickness.copy()
thickness.name = f'{layer.name}_{thickness.name}'
thickness_list.append(thickness)
return thickness_list
@property
def roughnesses(self) -> list:
roughness_list = []
for layer in self:
if hasattr(layer, 'roughness'):
roughness = layer.roughness.copy()
roughness.name = f'{layer.name}_{roughness.name}'
roughness_list.append(roughness)
return roughness_list
@property
def slds(self) -> list:
sld_list = []
for layer in self:
sld = layer.sld.copy()
sld.name = f'{layer.name}_{sld.name}'
sld_list.append(sld)
return sld_list
@property
def label_names(self) -> List[str]:
"""Get list of all layer names in order."""
label_names = []
for thickness in self.thicknesses:
label_names.append(thickness.name)
for roughness in self.roughnesses:
label_names.append(roughness.name)
for sld in self.slds:
label_names.append(sld.name)
return label_names
@staticmethod
def _check_layer_type(layer: Layer):
if not isinstance(layer, Layer):
raise ValueError('not of type layer')
@property
def _sample_structure(self):
return [self.substrate] + self.layers + [self.ambient_layer]
def __str__(self):
output = f'{self.ambient_layer}\n'
for i in reversed(range(len(self.layers))):
output += f'[{i}] {self.layers[i]}\n'
output += f'{self.substrate}'
return output
def __repr__(self):
return repr(f'<MultilayerStructure({[layer for layer in self]})>')
def __copy__(self):
this_copy = MultilayerStructure()
this_copy.set_ambient_layer(self._ambient_layer.copy())
this_copy.set_substrate(self._substrate.copy())
for layer in self.layers:
this_copy.add_layer(layer.copy())
return this_copy
def __getitem__(self, item):
return self._sample_structure[item]
def __len__(self):
return len(self._sample_structure)
def __list__(self):
return list(self._sample_structure)
def __iter__(self):
return (layer for layer in self._sample_structure)
[docs]class LayerOnSubstrate(MultilayerStructure):
"""Defines the structure of a multilayer sample through one or multiple :class:`Layer` objects and an ambient SLD.
Can only contain one non-constant layer of class Layer, all others must be of class ConstantLayer.
Initializes with a default ambient and substrate layer.
Returns:
MultilayerStructure
"""
[docs] def add_layer(self, layer: Union[Layer, ConstantLayer], index: Union[str, int] = 'next'):
"""Add layer at given index position. If ``index='next'`` (default) layer is appended (added on top)."""
if isinstance(layer, ConstantLayer):
self._add_layer(layer, index)
elif isinstance(layer, Layer):
if self.has_variable_layer:
raise ValueError('sample already contains a non-constant layer')
else:
self._add_layer(layer, index)
def _add_layer(self, layer: Union[Layer, ConstantLayer], index: Union[str, int] = 'next'):
if index == 'next':
self.layers.append(layer)
elif type(index) is int:
self.layers.insert(index, layer)
else:
raise ValueError('position must be an integer index or "next"')
@property
def has_variable_layer(self):
for layer in self.layers:
if isinstance(layer, Layer) and not isinstance(layer, ConstantLayer):
return True
else:
return False
@staticmethod
def _check_constant_layer_type(constant_layer: ConstantLayer):
if not isinstance(constant_layer, ConstantLayer):
raise ValueError('not of type ConstantLayer')