Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gradient layer #71

Merged
merged 15 commits into from
Feb 1, 2024
6 changes: 3 additions & 3 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -49,7 +49,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v3

# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
Expand All @@ -63,4 +63,4 @@ jobs:
# make release

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v3
2 changes: 1 addition & 1 deletion .github/workflows/ossar-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@ jobs:

# Upload results to the Security tab
- name: Upload OSSAR results
uses: github/codeql-action/upload-sarif@v1
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: ${{ steps.ossar.outputs.sarifFile }}
4 changes: 3 additions & 1 deletion EasyReflectometry/sample/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@
from .items.repeating_multilayer import RepeatingMultiLayer
from .items.surfactant_layer import SurfactantLayer

_ = (MultiLayer, RepeatingMultiLayer, SurfactantLayer)
from .items.gradient_layer import GradientLayer

_ = (MultiLayer, RepeatingMultiLayer, SurfactantLayer, GradientLayer)
239 changes: 239 additions & 0 deletions EasyReflectometry/sample/items/gradient_layer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
from __future__ import annotations

from easyCore.Fitting.Constraints import ObjConstraint
from numpy import arange

from EasyReflectometry.sample.layer import Layer
from EasyReflectometry.sample.material import Material

from .multilayer import MultiLayer


class GradientLayer(MultiLayer):
"""
A :py:class:`GradientLayer` constructs a gradient multilayer for the
provided initial and final material.
"""
def __init__(
self,
initial_material: Material,
final_material: Material,
thickness: float,
roughness: float,
discretisation_elements: int = 10,
name: str = 'EasyGradienLayer',
interface=None
) -> GradientLayer:
"""
:param initial_material: Material of initial "part" of the layer
:param final_material: Material of final "part" 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
:param interface: Interface to use for the layer
"""
self.initial_material = initial_material
self.final_material = final_material
self.discretisation_elements = discretisation_elements

gradient_layers = _prepare_gradient_layers(
initial_material=initial_material,
final_material=final_material,
discretisation_elements=discretisation_elements,
interface=interface)

super().__init__(
layers=gradient_layers,
name=name,
interface=interface,
type='Gradient-layer'
)

_apply_thickness_constraints(self.layers)
self.thickness = thickness
self.roughness = roughness

@property
def thickness(self) -> float:
"""
:return: Thickness of the gradient layer
"""
# Layer 0 is the deciding layer as set in _apply_thickness_constraints
return self.layers[0].thickness.raw_value * self.discretisation_elements

@thickness.setter
def thickness(self, thickness: float) -> None:
"""
:param thickness: Thickness of the gradient layer
"""
# Layer 0 is the deciding layer as set in _apply_thickness_constraints
self.layers[0].thickness.value = thickness / self.discretisation_elements
andped10 marked this conversation as resolved.
Show resolved Hide resolved

@property
def roughness(self) -> float:
"""
:return: Roughness of the gradient layer
"""
# Layer 0 ir -1 is the deciding layer
return self.layers[0].roughness.raw_value

@roughness.setter
def roughness(self, roughness: float) -> None:
"""
:param roughness: Roughness of the gradient layer
"""
# Layer 0 is the facing the beam
# Layer -1 is away from the beam
self.layers[0].roughness.value = roughness
self.layers[-1].roughness.value = roughness

# Class constructors
@classmethod
def default(cls, name: str = 'Air-Deuterium', interface=None) -> GradientLayer:
"""
Default constructor for a gradient layer object. The default is air to deuterium.

:return: Gradient layer object.
"""
initial_material = Material.from_pars(0.0, 0.0, 'Air')
final_material = Material.from_pars(6.36, 0.0, 'D2O')

return cls(
initial_material=initial_material,
final_material=final_material,
thickness=2.,
roughness=0.2,
discretisation_elements=10,
name=name,
interface=interface
)

@classmethod
def from_pars(
cls,
initial_material: Material,
final_material: Material,
thickness: float,
roughness: float,
discretisation_elements: int,
name: str = 'EasyGradientLayer',
interface=None
) -> GradientLayer:
"""
Constructor for the gradient layer where the parameters are known,
:py:attr:`initial` 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 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,
thickness=thickness,
roughness=roughness,
discretisation_elements=discretisation_elements,
name=name,
interface=interface
)

def add_layer(self, layer: Layer) -> None:
raise NotImplementedError("Cannot add layers to a gradient layer.")

def duplicate_layer(self, idx: int) -> None:
raise NotImplementedError("Cannot duplicate a layer for a gradient layer.")

def remove_layer(self, idx: int) -> None:
raise NotImplementedError("Cannot remove layer from a gradient layer.")

@property
def _dict_repr(self) -> dict[str, str]:
"""
A simplified dict representation.

:return: Simple dictionary
"""
return {
'type': self.type,
'thickness': self.thickness,
'discretisation_elements': self.discretisation_elements,
'initial_layer': self.layers[0]._dict_repr,
'final_layer': self.layers[-1]._dict_repr
}

def as_dict(self, skip: list = None) -> dict:
"""
Custom as_dict method to skip generated layers.

:return: Cleaned dictionary.
"""
if skip is None:
skip = []
this_dict = super().as_dict(skip=skip)
del this_dict['layers']
return this_dict


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


def _prepare_gradient_layers(
initial_material: Material,
final_material: Material,
discretisation_elements: int,
interface=None
) -> list[Layer]:
gradient_sld = _linear_gradient(
init_value=initial_material.sld.raw_value,
final_value=final_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,
discretisation_elements=discretisation_elements
)
gradient_layers = []
for i in range(discretisation_elements):
layer = Layer.from_pars(
material=Material.from_pars(gradient_sld[i], gradient_isld[i]),
thickness=0.0,
roughness=0.0,
name=str(i),
interface=interface
)
gradient_layers.append(layer)
return gradient_layers


def _apply_thickness_constraints(layers) -> None:
# Add thickness constraint, layer 0 is the deciding layer
for i in range(len(layers)):
if i != 0:
andped10 marked this conversation as resolved.
Show resolved Hide resolved
layers[i].thickness.enabled = True
layer_constraint = ObjConstraint(
dependent_obj=layers[i].thickness,
operator='',
independent_obj=layers[0].thickness
)
layers[0].thickness.user_constraints[f'thickness_{i}'] = layer_constraint
layers[0].thickness.user_constraints[f'thickness_{i}'].enabled = True

layers[0].thickness.enabled = True

24 changes: 15 additions & 9 deletions EasyReflectometry/sample/items/multilayer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from typing import Union, List
import yaml
from __future__ import annotations

from typing import List, Union

import yaml
from easyCore.Objects.ObjectClasses import BaseObj

from EasyReflectometry.sample.layer import Layer
from EasyReflectometry.sample.layers import Layers

Expand All @@ -20,21 +23,24 @@ class MultiLayer(BaseObj):
.. _`item library documentation`: ./item_library.html#multilayer
"""

def __init__(self,
layers: Union[Layers, Layer, List[Layer]],
name: str = 'EasyMultiLayer',
interface=None):
def __init__(
self,
layers: Union[Layers, Layer, List[Layer]],
name: str = 'EasyMultiLayer',
interface=None,
type: str='Multi-layer'
):
if isinstance(layers, Layer):
layers = Layers(layers, name=layers.name)
elif isinstance(layers, list):
layers = Layers(*layers, name='/'.join([layer.name for layer in layers]))
self.type = 'Multi-layer'
self.type = type
super().__init__(name, layers=layers)
self.interface = interface

# Class constructors
@classmethod
def default(cls, interface=None) -> "MultiLayer":
def default(cls, interface=None) -> MultiLayer:
"""
Default constructor for a multi-layer item.

Expand All @@ -48,7 +54,7 @@ def default(cls, interface=None) -> "MultiLayer":
def from_pars(cls,
layers: Layers,
name: str = "EasyMultiLayer",
interface=None) -> "MultiLayer":
interface=None) -> MultiLayer:
"""
Constructor of a multi-layer item where the parameters are known.

Expand Down
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "EasyReflectometryLib"
version = "0.0.3"
version = "0.0.4-dev"
description = "A reflectometry python package built on the EasyScience framework."
readme = "README.rst"
authors = [
Expand Down Expand Up @@ -99,4 +99,8 @@ deps = coverage
commands =
pip install -e '.[dev]'
pytest --cov --cov-report=xml
"""
"""

[tool.isort]
multi_line_output = 3
include_trailing_comma = true
Loading
Loading