Source code for easyreflectometry.sample.elements.materials.material_mixture

from typing import Optional
from typing import Union

from easyscience import global_object
from easyscience.Constraints import FunctionalConstraint
from easyscience.Objects.new_variable import Parameter

from easyreflectometry.parameter_utils import get_as_parameter
from easyreflectometry.special.calculations import weighted_average

from ...base_core import BaseCore
from .material import DEFAULTS as MATERIAL_DEFAULTS
from .material import Material

DEFAULTS = {
    'fraction': {
        'description': 'The fraction of material b in material a',
        'value': 0.5,
        'unit': 'dimensionless',
        'min': 0,
        'max': 1,
        'fixed': True,
    }
}
DEFAULTS.update(MATERIAL_DEFAULTS)


[docs] class MaterialMixture(BaseCore): # Added in super().__init__ _material_a: Material _material_b: Material _fraction: Parameter
[docs] def __init__( self, material_a: Union[Material, None] = None, material_b: Union[Material, None] = None, fraction: Union[Parameter, float, None] = None, name: Union[str, None] = None, unique_name: Optional[str] = None, interface=None, ): """Constructor. :param material_a: The first material. :param material_b: The second material. :param fraction: The fraction of material_b in material_a. :param name: Name of the material, defaults to None that causes the name to be constructed. :param interface: Calculator interface, defaults to `None`. """ if unique_name is None: unique_name = global_object.generate_unique_name(self.__class__.__name__) if material_a is None: material_a = Material(interface=interface) if material_b is None: material_b = Material(interface=interface) fraction = get_as_parameter( name='fraction', value=fraction, default_dict=DEFAULTS, unique_name_prefix=f'{unique_name}_Fraction', ) sld = weighted_average( a=material_a.sld.value, b=material_b.sld.value, p=fraction.value, ) isld = weighted_average( a=material_a.isld.value, b=material_b.isld.value, p=fraction.value, ) self._sld = get_as_parameter( name='sld', value=sld, default_dict=DEFAULTS, unique_name_prefix=f'{unique_name}_Sld', ) self._isld = get_as_parameter( name='isld', value=isld, default_dict=DEFAULTS, unique_name_prefix=f'{unique_name}_Isld', ) # To avoid problems when setting the interface # self._sld and self._isld need to be declared before calling the super constructor super().__init__( name, _material_a=material_a, _material_b=material_b, _fraction=fraction, interface=interface, ) if name is None: self._update_name() self._materials_constraints() self.interface = interface
def _get_linkable_attributes(self): return [self._sld, self._isld] @property def sld(self) -> float: return self._sld.value @property def isld(self) -> float: return self._isld.value def _materials_constraints(self): self._sld.enabled = True self._isld.enabled = True constraint = FunctionalConstraint( dependent_obj=self._sld, func=weighted_average, independent_objs=[self._material_a.sld, self._material_b.sld, self._fraction], ) self._material_a.sld.user_constraints['sld'] = constraint self._material_b.sld.user_constraints['sld'] = constraint self._fraction.user_constraints['sld'] = constraint constraint() iconstraint = FunctionalConstraint( dependent_obj=self._isld, func=weighted_average, independent_objs=[self._material_a.isld, self._material_b.isld, self._fraction], ) self._material_a.isld.user_constraints['isld'] = iconstraint self._material_b.isld.user_constraints['isld'] = iconstraint self._fraction.user_constraints['isld'] = iconstraint iconstraint() @property def fraction(self) -> float: """Get the fraction of material_b.""" return self._fraction.value @fraction.setter def fraction(self, fraction: float) -> None: """Setter for fraction of material_b. :param fraction: The fraction of material_b in material_a. """ if not isinstance(fraction, float): raise ValueError('fraction must be a float') self._fraction.value = fraction @property def material_a(self) -> Material: """Getter for material_a.""" return self._material_a @material_a.setter def material_a(self, new_material_a: Material) -> None: """Setter for material_a :param new_material_a: New Material for material_a """ self._material_a = new_material_a self._materials_constraints() if self.interface is not None: self.interface.generate_bindings(self) self._update_name() @property def material_b(self) -> Material: """Getter for material_b.""" return self._material_b @material_b.setter def material_b(self, new_material_b: Material) -> None: """Setter for material_b :param new_material_b: New Materialfor material_b """ self._material_b = new_material_b self._materials_constraints() if self.interface is not None: self.interface.generate_bindings(self) self._update_name() def _update_name(self) -> None: self.name = self._material_a.name + '/' + self._material_b.name # Representation @property def _dict_repr(self) -> dict[str, str]: """A simplified dict representation.""" return { self.name: { 'fraction': f'{self._fraction.value:.3f} {self._fraction.unit}', 'sld': f'{self._sld.value:.3f}e-6 {self._sld.unit}', 'isld': f'{self._isld.value:.3f}e-6 {self._isld.unit}', 'material_a': self._material_a._dict_repr, 'material_b': self._material_b._dict_repr, } }
[docs] def as_dict(self, skip: Optional[list[str]] = None) -> dict[str, str]: """Produces a cleaned dict using a custom as_dict method to skip necessary things. The resulting dict matches the parameters in __init__ :param skip: List of keys to skip, defaults to `None`. """ this_dict = super().as_dict(skip=skip) this_dict['material_a'] = self._material_a.as_dict(skip=skip) this_dict['material_b'] = self._material_b.as_dict(skip=skip) this_dict['fraction'] = self._fraction.as_dict(skip=skip) return this_dict