Source code for mlreflect.data_generation.layers

from copy import copy
from typing import Union, Iterable

import numpy as np

from .parameters import Parameter, ConstantParameter


[docs]class Layer: """Defines the name and parameter ranges of a single sample layer. Args: name: User defined name of this layer. thickness: Thickness of this layer in units of Å. Can be a float for a constant vale or a tuple for a range. roughness: Roughness of this layer in units of Å. Can be a float for a constant vale or a tuple for a range. sld: Scattering length density (SLD) of this layer in units of 1e-6 1/Å^2. Can be a float for a constant vale or a tuple for a range. Returns: Layer object """ def __init__(self, name: str, thickness: Union[float, Iterable, Parameter], roughness: Union[float, Iterable, Parameter], sld: Union[float, complex, Iterable, Parameter]): self.name = name self.thickness = self._set_parameter(thickness, 'thickness') self.roughness = self._set_parameter(roughness, 'roughness') self.sld = self._set_parameter(sld, 'sld') for param in (self.thickness, self.roughness): if param.min < 0: raise ValueError(f'min for {name} cannot be negative') @property def ranges(self): return { 'min_thickness': self.thickness.min, 'max_thickness': self.thickness.max, 'min_roughness': self.roughness.min, 'max_roughness': self.roughness.max, 'min_sld': self.sld.min, 'max_sld': self.sld.max, }
[docs] def to_dict(self): layer_dict = {'name': self.name} if hasattr(self, 'thickness'): layer_dict['thickness'] = self._parse_parameter(self.thickness) if hasattr(self, 'roughness'): layer_dict['roughness'] = self._parse_parameter(self.roughness) if hasattr(self, 'sld'): layer_dict['sld'] = self._parse_parameter(self.sld) return layer_dict
[docs] def copy(self): return copy(self)
def _parse_parameter(self, parameter): if isinstance(parameter.value, tuple): return self._if_complex_to_dict(parameter.value[0]), self._if_complex_to_dict(parameter.value[1]) else: return self._if_complex_to_dict(parameter.value) @staticmethod def _if_complex_to_dict(value): if np.iscomplex(value): return {'re': np.real(value), 'im': np.imag(value)} else: return value def _set_parameter(self, param, name=None): if isinstance(param, Parameter): return param.copy() elif isinstance(param, Iterable) and not isinstance(param, dict): return Parameter(self._if_dict_to_complex(param[0]), self._if_dict_to_complex(param[1]), name) else: return ConstantParameter(self._if_dict_to_complex(param), name) @staticmethod def _if_dict_to_complex(value): if isinstance(value, dict): return value['re'] + value['im'] * 1j else: return value def __copy__(self): return Layer(self.name, self.thickness.copy(), self.roughness.copy(), self.sld.copy()) def __str__(self): return f'{self.name}:\n' \ f'\tthickness: {self.thickness} [Å]\n' \ f'\troughness: {self.roughness} [Å]\n' \ f'\tsld: {self.sld} [1e-6 1/Å^2]' def __repr__(self): return repr(f'<Layer({str(self.name)})>')
[docs]class ConstantLayer(Layer): """Defines the name and constant parameter values of a single sample layer. Args: name: User defined name of this layer. thickness: Thickness of this layer in units of Å. Must be a float or int. roughness: Roughness of this layer in units of Å. Must be a float or int. sld: Scattering length density (SLD) of this layer in units of 1e-6 1/Å^2. Must be a float or int. Returns: Layer object """ def __init__(self, name: str, thickness: Union[float, ConstantParameter], roughness: Union[float, ConstantParameter], sld: Union[float, complex, ConstantParameter]): self.name = name self.thickness = self._set_parameter(thickness, 'thickness') self.roughness = self._set_parameter(roughness, 'roughness') self.sld = self._set_parameter(sld, 'sld') for param in (self.thickness, self.roughness): if param.min < 0: raise ValueError(f'min for {param.name} cannot be negative') @property def ranges(self): return { 'min_thickness': self.thickness.min, 'max_thickness': self.thickness.max, 'min_roughness': self.roughness.min, 'max_roughness': self.roughness.max, 'min_sld': self.sld.min, 'max_sld': self.sld.max, } def _set_parameter(self, param, name=None): if isinstance(param, Parameter): return param.copy() elif isinstance(param, Iterable) and not isinstance(param, dict): ValueError(f'parameter {name} needs to be float or int') else: return ConstantParameter(self._if_dict_to_complex(param), name) def __copy__(self): return ConstantLayer(self.name, self.thickness.copy(), self.roughness.copy(), self.sld.copy()) def __repr__(self): return repr(f'<ConstantLayer({str(self.name)})>')
[docs]class Substrate(ConstantLayer): def __init__(self, name: str, roughness: Union[float, ConstantParameter], sld: Union[float, complex, ConstantParameter]): self.name = name self.roughness = self._set_parameter(roughness, 'roughness') self.sld = self._set_parameter(sld, 'sld') if self.roughness.min < 0: raise ValueError('min for roughness cannot be negative') @property def ranges(self): return { 'min_roughness': self.roughness.min, 'max_roughness': self.roughness.max, 'min_sld': self.sld.min, 'max_sld': self.sld.max, } def __copy__(self): return Substrate(self.name, self.roughness.copy(), self.sld.copy()) def __str__(self): return f'{self.name} (substrate):\n' \ f'\troughness: {self.roughness} [Å]\n' \ f'\tsld: {self.sld} [1e-6 1/Å^2]' def __repr__(self): return repr(f'<Substrate({str(self.name)})>')
[docs]class AmbientLayer(ConstantLayer): def __init__(self, name: str, sld: Union[float, complex, ConstantParameter]): self.name = name self.sld = self._set_parameter(sld, 'sld') @property def ranges(self): return { 'min_sld': self.sld.min, 'max_sld': self.sld.max, } def __copy__(self): return AmbientLayer(self.name, self.sld.copy()) def __str__(self): return f'{self.name} (ambient):\n' \ f'\tsld: {self.sld} [1e-6 1/Å^2]' def __repr__(self): return repr(f'<AmbientLayer({str(self.name)})>')