Source code for easyreflectometry.sample.assemblies.surfactant_layer

from __future__ import annotations

from typing import Optional

from easyscience.Constraints import ObjConstraint
from easyscience.Objects.new_variable import Parameter

from ..elements.layers.layer_area_per_molecule import LayerAreaPerMolecule
from ..elements.layers.layer_collection import LayerCollection
from ..elements.materials.material import Material
from .base_assembly import BaseAssembly


[docs] class SurfactantLayer(BaseAssembly): """A surfactant layer constructs a series of layers representing the head and tail groups of a surfactant. This assembly allows the definition of a surfactant or lipid using the chemistry of the head (head_layer) and tail (tail_layer) regions, additionally this approach will make the application of constraints such as conformal roughness or area per molecule more straight forward. More information about the usage of this assembly is available in the `surfactant documentation`_ .. _`surfactant documentation`: ../sample/assemblies_library.html#surfactantlayer """
[docs] def __init__( self, tail_layer: Optional[LayerAreaPerMolecule] = None, head_layer: Optional[LayerAreaPerMolecule] = None, name: str = 'EasySurfactantLayer', constrain_area_per_molecule: bool = False, conformal_roughness: bool = False, interface=None, ): """Constructor. :param tail_layer: Layer representing the tail part of the surfactant layer. :param head_layer: Layer representing the head part of the surfactant layer. :param name: Name for surfactant layer, defaults to 'EasySurfactantLayer'. :param constrain_area_per_molecule: Constrain the area per molecule, defaults to `False`. :param conformal_roughness: Constrain the roughness to be the same for both layers, defaults to `False`. :param interface: Calculator interface, defaults to `None`. """ if tail_layer is None: air = Material(0, 0, 'Air') tail_layer = LayerAreaPerMolecule( molecular_formula='C32D64', thickness=16, solvent=air, solvent_fraction=0.0, area_per_molecule=48.2, roughness=3, name='DPPC Tail', ) if head_layer is None: d2o = Material(6.36, 0, 'D2O') head_layer = LayerAreaPerMolecule( molecular_formula='C10H18NO8P', thickness=10.0, solvent=d2o, solvent_fraction=0.2, area_per_molecule=48.2, roughness=3.0, name='DPPC Head', ) surfactant = LayerCollection(tail_layer, head_layer, name=name) super().__init__( name=name, type='Surfactant Layer', layers=surfactant, interface=interface, ) self.interface = interface self.head_layer._area_per_molecule.enabled = True area_per_molecule = ObjConstraint( dependent_obj=self.head_layer._area_per_molecule, operator='', independent_obj=self.tail_layer._area_per_molecule, ) self.tail_layer._area_per_molecule.user_constraints['area_per_molecule'] = area_per_molecule self.tail_layer._area_per_molecule.user_constraints['area_per_molecule'].enabled = constrain_area_per_molecule self._setup_roughness_constraints() if conformal_roughness: self._enable_roughness_constraints()
@property def tail_layer(self) -> Optional[LayerAreaPerMolecule]: """Get the tail layer of the surfactant surface.""" return self.front_layer @tail_layer.setter def tail_layer(self, layer: LayerAreaPerMolecule) -> None: """Set the tail layer of the surfactant surface.""" self.front_layer = layer @property def head_layer(self) -> Optional[LayerAreaPerMolecule]: """Get the head layer of the surfactant surface.""" return self.back_layer @head_layer.setter def head_layer(self, layer: LayerAreaPerMolecule) -> None: """Set the head layer of the surfactant surface.""" self.back_layer = layer @property def constrain_area_per_molecule(self) -> bool: """Get the area per molecule constraint status.""" return self.tail_layer._area_per_molecule.user_constraints['area_per_molecule'].enabled @constrain_area_per_molecule.setter def constrain_area_per_molecule(self, status: bool): """Set the status for the area per molecule constraint such that the head and tail layers have the same area per molecule. :param status: Boolean description the wanted of the constraint. """ self.tail_layer._area_per_molecule.user_constraints['area_per_molecule'].enabled = status if status: # Apply the constraint by running it self.tail_layer._area_per_molecule.user_constraints['area_per_molecule']() @property def conformal_roughness(self) -> bool: """Get the roughness constraint status.""" return self.tail_layer.roughness.user_constraints['roughness_1'].enabled @conformal_roughness.setter def conformal_roughness(self, status: bool): """Set the status for the roughness to be the same for both layers. :param status: Boolean description the wanted of the constraint. """ if status: self._enable_roughness_constraints() else: self._disable_roughness_constraints()
[docs] def constrain_solvent_roughness(self, solvent_roughness: Parameter): """Add the constraint to the solvent roughness. :param solvent_roughness: The solvent roughness parameter. """ if not self.conformal_roughness: raise ValueError('Roughness must be conformal to use this function.') solvent_roughness.value = self.tail_layer.roughness.value rough = ObjConstraint(solvent_roughness, '', self.tail_layer.roughness) self.tail_layer.roughness.user_constraints['solvent_roughness'] = rough
[docs] def constain_multiple_contrast( self, another_contrast: SurfactantLayer, head_layer_thickness: bool = True, tail_layer_thickness: bool = True, head_layer_area_per_molecule: bool = True, tail_layer_area_per_molecule: bool = True, head_layer_fraction: bool = True, tail_layer_fraction: bool = True, ): """Constrain structural parameters between surfactant layer objects. :param another_contrast: The surfactant layer to constrain """ if head_layer_thickness: head_layer_thickness_constraint = ObjConstraint( dependent_obj=self.head_layer.thickness, operator='', independent_obj=another_contrast.head_layer.thickness, ) another_contrast.head_layer.thickness.user_constraints[f'{another_contrast.name}'] = ( head_layer_thickness_constraint ) if tail_layer_thickness: tail_layer_thickness_constraint = ObjConstraint( dependent_obj=self.tail_layer.thickness, operator='', independent_obj=another_contrast.tail_layer.thickness ) another_contrast.tail_layer.thickness.user_constraints[f'{another_contrast.name}'] = ( tail_layer_thickness_constraint ) if head_layer_area_per_molecule: head_layer_area_per_molecule_constraint = ObjConstraint( dependent_obj=self.head_layer._area_per_molecule, operator='', independent_obj=another_contrast.head_layer._area_per_molecule, ) another_contrast.head_layer._area_per_molecule.user_constraints[f'{another_contrast.name}'] = ( head_layer_area_per_molecule_constraint ) if tail_layer_area_per_molecule: tail_layer_area_per_molecule_constraint = ObjConstraint( dependent_obj=self.tail_layer._area_per_molecule, operator='', independent_obj=another_contrast.tail_layer._area_per_molecule, ) another_contrast.tail_layer._area_per_molecule.user_constraints[f'{another_contrast.name}'] = ( tail_layer_area_per_molecule_constraint ) if head_layer_fraction: head_layer_fraction_constraint = ObjConstraint( dependent_obj=self.head_layer.material._fraction, operator='', independent_obj=another_contrast.head_layer.material._fraction, ) another_contrast.head_layer.material._fraction.user_constraints[f'{another_contrast.name}'] = ( head_layer_fraction_constraint ) if tail_layer_fraction: tail_layer_fraction_constraint = ObjConstraint( dependent_obj=self.tail_layer.material._fraction, operator='', independent_obj=another_contrast.tail_layer.material._fraction, ) another_contrast.tail_layer.material._fraction.user_constraints[f'{another_contrast.name}'] = ( tail_layer_fraction_constraint )
@property def _dict_repr(self) -> dict: """A simplified dict representation.""" return { self.name: { 'head_layer': self.head_layer._dict_repr, 'tail_layer': self.tail_layer._dict_repr, 'area per molecule constrained': self.constrain_area_per_molecule, 'conformal roughness': self.conformal_roughness, } }
[docs] def as_dict(self, skip: Optional[list[str]] = None) -> dict: """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['tail_layer'] = self.tail_layer.as_dict(skip=skip) this_dict['head_layer'] = self.head_layer.as_dict(skip=skip) this_dict['constrain_area_per_molecule'] = self.constrain_area_per_molecule this_dict['conformal_roughness'] = self.conformal_roughness del this_dict['layers'] return this_dict