Skip to content

Commit

Permalink
Merge pull request #117 from EasyScience/116-ensure-that-the-terms-su…
Browse files Browse the repository at this point in the history
…per-and-sub-phase-is-used-correctly-in-notebooks

116 ensure that the terms super and sub phase is used correctly in notebooks
  • Loading branch information
andped10 authored Mar 15, 2024
2 parents a080373 + 8ee7a7b commit 35e8737
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 137 deletions.
60 changes: 33 additions & 27 deletions EasyReflectometry/sample/assemblies/base_assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@


class BaseAssembly(BaseObj):
"""Assembly of layers.
The front layer (front_layer) is the layer the neutron beam starts in, it has an index of 0.
The back layer (back_layer) is the final layer from which the unreflected neutron beam is transmitted,
its index number depends on the number of finite layers in the system, but it might be accessed at index -1.
"""

# Added in super().__init__
#: Name of the assembly.
name: str
Expand Down Expand Up @@ -53,57 +59,57 @@ def __repr__(self) -> str:
return yaml.dump(self._dict_repr, sort_keys=False)

@property
def bottom_layer(self) -> Optional[Layer]:
"""Get the bottom layer in the assembly."""
def front_layer(self) -> Optional[Layer]:
"""Get the front layer in the assembly."""
if len(self.layers) == 0:
return None
return self.layers[0]

@bottom_layer.setter
def bottom_layer(self, layer: Layer) -> None:
"""Set the bottom layer in the assembly.
@front_layer.setter
def front_layer(self, layer: Layer) -> None:
"""Set the front layer in the assembly.
:param layer: Layer to set as the bottom layer.
:param layer: Layer to set as the front layer.
"""
if len(self.layers) == 0:
self.layers.append(layer)
else:
self.layers[0] = layer

@property
def top_layer(self) -> Optional[None]:
"""Get the top layer in the assembly."""
def back_layer(self) -> Optional[None]:
"""Get the back layer in the assembly."""

if len(self.layers) < 2:
return None
return self.layers[-1]

@top_layer.setter
def top_layer(self, layer: Layer) -> None:
"""Set the top layer in the assembly.
@back_layer.setter
def back_layer(self, layer: Layer) -> None:
"""Set the back layer in the assembly.
:param layer: Layer to set as the top layer.
:param layer: Layer to set as the back layer.
"""

if len(self.layers) == 0:
raise Exception('There is no bottom layer to add the top layer to. Please add a bottom layer first.')
raise Exception('There is no front layer to add the back layer to. Please add a front layer first.')
if len(self.layers) == 1:
self.layers.append(layer)
else:
self.layers[-1] = layer

def _setup_thickness_constraints(self) -> None:
"""
Setup thickness constraint, bottom layer is the deciding layer
Setup thickness constraint, front layer is the deciding layer
"""
for i in range(1, len(self.layers)):
layer_constraint = ObjConstraint(
dependent_obj=self.layers[i].thickness,
operator='',
independent_obj=self.bottom_layer.thickness,
independent_obj=self.front_layer.thickness,
)
self.bottom_layer.thickness.user_constraints[f'thickness_{i}'] = layer_constraint
self.bottom_layer.thickness.user_constraints[f'thickness_{i}'].enabled = False
self.front_layer.thickness.user_constraints[f'thickness_{i}'] = layer_constraint
self.front_layer.thickness.user_constraints[f'thickness_{i}'].enabled = False
self._thickness_constraints_setup = True

def _enable_thickness_constraints(self):
Expand All @@ -113,11 +119,11 @@ def _enable_thickness_constraints(self):
if self._thickness_constraints_setup:
# Make sure that the thickness constraint is enabled
for i in range(1, len(self.layers)):
self.bottom_layer.thickness.user_constraints[f'thickness_{i}'].enabled = True
self.front_layer.thickness.user_constraints[f'thickness_{i}'].enabled = True
# Make sure that the thickness parameter is enabled
for i in range(len(self.layers)):
self.layers[i].thickness.enabled = True
self.bottom_layer.thickness.value = self.bottom_layer.thickness.raw_value
self.front_layer.thickness.value = self.front_layer.thickness.raw_value
else:
raise Exception('Roughness constraints not setup')

Expand All @@ -127,22 +133,22 @@ def _disable_thickness_constraints(self):
"""
if self._thickness_constraints_setup:
for i in range(1, len(self.layers)):
self.bottom_layer.thickness.user_constraints[f'thickness_{i}'].enabled = False
self.front_layer.thickness.user_constraints[f'thickness_{i}'].enabled = False
else:
raise Exception('Roughness constraints not setup')

def _setup_roughness_constraints(self) -> None:
"""
Setup roughness constraint, bottom layer is the deciding layer
Setup roughness constraint, front layer is the deciding layer
"""
for i in range(1, len(self.layers)):
layer_constraint = ObjConstraint(
dependent_obj=self.layers[i].roughness,
operator='',
independent_obj=self.bottom_layer.roughness,
independent_obj=self.front_layer.roughness,
)
self.bottom_layer.roughness.user_constraints[f'roughness_{i}'] = layer_constraint
self.bottom_layer.roughness.user_constraints[f'roughness_{i}'].enabled = False
self.front_layer.roughness.user_constraints[f'roughness_{i}'] = layer_constraint
self.front_layer.roughness.user_constraints[f'roughness_{i}'].enabled = False
self._roughness_constraints_setup = True

def _enable_roughness_constraints(self):
Expand All @@ -152,11 +158,11 @@ def _enable_roughness_constraints(self):
if self._roughness_constraints_setup:
# Make sure that the roughness constraint is enabled
for i in range(1, len(self.layers)):
self.bottom_layer.roughness.user_constraints[f'roughness_{i}'].enabled = True
self.front_layer.roughness.user_constraints[f'roughness_{i}'].enabled = True
# Make sure that the roughness parameter is enabled
for i in range(len(self.layers)):
self.layers[i].roughness.enabled = True
self.bottom_layer.roughness.value = self.bottom_layer.roughness.raw_value
self.front_layer.roughness.value = self.front_layer.roughness.raw_value
else:
raise Exception('Roughness constraints not setup')

Expand All @@ -166,6 +172,6 @@ def _disable_roughness_constraints(self):
"""
if self._roughness_constraints_setup:
for i in range(1, len(self.layers)):
self.bottom_layer.roughness.user_constraints[f'roughness_{i}'].enabled = False
self.front_layer.roughness.user_constraints[f'roughness_{i}'].enabled = False
else:
raise Exception('Roughness constraints not setup')
83 changes: 43 additions & 40 deletions EasyReflectometry/sample/assemblies/gradient_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@


class GradientLayer(BaseAssembly):
"""A set of discrete gradient layers changing from the initial to the final material."""
"""A set of discrete gradient layers changing from the front to the back material.
The front layer is where the neutron beam starts in, it has an index of 0.
"""

def __init__(
self,
initial_material: Material,
final_material: Material,
front_material: Material,
back_material: Material,
thickness: float,
roughness: float,
discretisation_elements: int = 10,
Expand All @@ -23,23 +26,23 @@ def __init__(
) -> GradientLayer:
"""Constructor.
:param initial_material: Material of initial "part" of the layer
:param final_material: Material of final "part" of the layer
:param front_material: Material of front of the layer
:param back_material: Material of back of the layer
:param thickness: Thicknkess of the layer
:param roughness: Roughness of the layer
:param discretisation_elements: Number of discrete layers
:param name: Name for gradient layer, defaults to 'EasyGradienLayer'.
:param interface: Calculator interface, defaults to :py:attr:`None`.
:param interface: Calculator interface, defaults to `None`.
"""
self._initial_material = initial_material
self._final_material = final_material
self._front_material = front_material
self._back_material = back_material
if discretisation_elements < 2:
raise ValueError('Discretisation elements must be greater than 2.')
self._discretisation_elements = discretisation_elements

gradient_layers = _prepare_gradient_layers(
initial_material=initial_material,
final_material=final_material,
front_material=front_material,
back_material=back_material,
discretisation_elements=discretisation_elements,
interface=interface,
)
Expand All @@ -66,14 +69,14 @@ def default(cls, name: str = 'EasyGradientLayer', interface=None) -> GradientLay
"""Default instance for a gradient layer object. The default is air to deuterium.
:param name: Name for gradient layer, defaults to 'EasyGradienLayer'.
:param interface: Calculator interface, defaults to :py:attr:`None`.
:param interface: Calculator interface, defaults to `None`.
"""
initial_material = Material.from_pars(0.0, 0.0, 'Air')
final_material = Material.from_pars(6.36, 0.0, 'D2O')
front_material = Material.from_pars(0.0, 0.0, 'Air')
back_material = Material.from_pars(6.36, 0.0, 'D2O')

return cls(
initial_material=initial_material,
final_material=final_material,
front_material=front_material,
back_material=back_material,
thickness=2.0,
roughness=0.2,
discretisation_elements=10,
Expand All @@ -84,28 +87,28 @@ def default(cls, name: str = 'EasyGradientLayer', interface=None) -> GradientLay
@classmethod
def from_pars(
cls,
initial_material: Material,
final_material: Material,
front_material: Material,
back_material: Material,
thickness: float,
roughness: float,
discretisation_elements: int,
name: str = 'EasyGradientLayer',
interface=None,
) -> GradientLayer:
"""Instance for the gradient layer where the parameters are known,
:py:attr:`initial` is facing the neutron beam.
`front` is facing the neutron beam.
:param initial_material: Material of initial "part" of the layer
:param final_material: Material of final "part" of the layer
:param front_material: Material of front of the layer
:param back_material: Material of back of the layer
:param thickness: Thicknkess of the layer
:param roughness: Roughness of the layer
:param discretisation_elements: Number of dicrete layers
:param name: Name for gradient layer
"""

return cls(
initial_material=initial_material,
final_material=final_material,
front_material=front_material,
back_material=back_material,
thickness=thickness,
roughness=roughness,
discretisation_elements=discretisation_elements,
Expand All @@ -116,37 +119,37 @@ def from_pars(
@property
def thickness(self) -> float:
"""Get the thickness of the gradient layer in Angstrom."""
return self.bottom_layer.thickness.raw_value * self._discretisation_elements
return self.front_layer.thickness.raw_value * self._discretisation_elements

@thickness.setter
def thickness(self, thickness: float) -> None:
"""Set the thickness of the gradient layer.
:param thickness: Thickness of the gradient layer in Angstroms.
"""
self.bottom_layer.thickness.value = thickness / self._discretisation_elements
self.front_layer.thickness.value = thickness / self._discretisation_elements

@property
def roughness(self) -> float:
"""Get the Roughness of the gradient layer in Angstrom."""
return self.bottom_layer.roughness.raw_value
return self.front_layer.roughness.raw_value

@roughness.setter
def roughness(self, roughness: float) -> None:
"""Set the roughness of the gradient layer.
:param roughness: Roughness of the gradient layer in Angstroms.
"""
self.bottom_layer.roughness.value = roughness
self.front_layer.roughness.value = roughness

@property
def _dict_repr(self) -> dict[str, str]:
"""A simplified dict representation."""
return {
'thickness': self.thickness,
'discretisation_elements': self._discretisation_elements,
'top_layer': self.top_layer._dict_repr,
'bottom_layer': self.bottom_layer._dict_repr,
'back_layer': self.back_layer._dict_repr,
'front_layer': self.front_layer._dict_repr,
}

def as_dict(self, skip: list = None) -> dict:
Expand All @@ -161,33 +164,33 @@ def as_dict(self, skip: list = None) -> dict:


def _linear_gradient(
init_value: float,
final_value: float,
front_value: float,
back_value: float,
discretisation_elements: int,
) -> list[float]:
discrete_step = (final_value - init_value) / discretisation_elements
discrete_step = (back_value - front_value) / discretisation_elements
if discrete_step != 0:
# Both initial and final values are included
gradient = arange(init_value, final_value + discrete_step, discrete_step)
# Both front and back values are included
gradient = arange(front_value, back_value + discrete_step, discrete_step)
else:
gradient = [init_value] * discretisation_elements
gradient = [front_value] * discretisation_elements
return gradient


def _prepare_gradient_layers(
initial_material: Material,
final_material: Material,
front_material: Material,
back_material: Material,
discretisation_elements: int,
interface=None,
) -> LayerCollection:
gradient_sld = _linear_gradient(
init_value=initial_material.sld.raw_value,
final_value=final_material.sld.raw_value,
front_value=front_material.sld.raw_value,
back_value=back_material.sld.raw_value,
discretisation_elements=discretisation_elements,
)
gradient_isld = _linear_gradient(
init_value=initial_material.isld.raw_value,
final_value=final_material.isld.raw_value,
front_value=front_material.isld.raw_value,
back_value=back_material.isld.raw_value,
discretisation_elements=discretisation_elements,
)
gradient_layers = []
Expand Down
14 changes: 7 additions & 7 deletions EasyReflectometry/sample/assemblies/multilayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@


class Multilayer(BaseAssembly):
"""A multi layer is build from a single or a list of :py:class:`Layer` or :py:class:`LayerCollection`.
The multi layer will arrange the layers as slabs, one on top of another,
allowing the reflectometry to be determined from them.
"""A multi layer is build from a single or a list of `Layer` or `LayerCollection`.
The multi layer will arrange the layers as slabs, allowing the reflectometry to be determined from them.
The front layer is where the neutron beam starts in, it has an index of 0.
More information about the usage of this assembly is available in the
`multilayer documentation`_
Expand All @@ -27,7 +27,7 @@ def __init__(
:param layers: The layers that make up the multi-layer.
:param name: Name for multi layer, defaults to 'EasyMultilayer'.
:param interface: Calculator interface, defaults to :py:attr:`None`.
:param interface: Calculator interface, defaults to `None`.
:param type: Type of the constructed instance, defaults to 'Multi-layer'
"""
if isinstance(layers, Layer):
Expand All @@ -41,7 +41,7 @@ def __init__(
def default(cls, interface=None) -> Multilayer:
"""Default instance of a multi-layer.
:param interface: Calculator interface, defaults to :py:attr:`None`.
:param interface: Calculator interface, defaults to `None`.
"""
layers = LayerCollection.default()
return cls(layers, interface=interface)
Expand All @@ -57,7 +57,7 @@ def from_pars(
:param layers: The layers in the multi-layer.
:param name: Name of the layer, defaults to 'EasyMultilayer'.
:param interface: Calculator interface, defaults to :py:attr:`None`.
:param interface: Calculator interface, defaults to `None`.
"""
return cls(
layers=layers,
Expand Down Expand Up @@ -105,5 +105,5 @@ def remove_layer(self, idx: int) -> None:
def _dict_repr(self) -> dict:
"""A simplified dict representation."""
if len(self.layers) == 1:
return self.bottom_layer._dict_repr
return self.front_layer._dict_repr
return {self.name: self.layers._dict_repr}
Loading

0 comments on commit 35e8737

Please sign in to comment.