Skip to content

multilayer

LayerCollection

Bases: BaseCollection

Source code in src/easyreflectometry/sample/collections/layer_collection.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class LayerCollection(BaseCollection):
    def __init__(
        self,
        *layers: Optional[list[Layer]],
        name: str = 'EasyLayerCollection',
        interface=None,
        unique_name: Optional[str] = None,
        populate_if_none: bool = True,
        **kwargs,
    ):
        """Init function."""
        if not layers:
            layers = []

        super().__init__(
            name,
            interface,
            *layers,
            unique_name=unique_name,
            populate_if_none=populate_if_none,
            **kwargs,
        )

    def add_layer(self, layer: Optional[Layer] = None):
        """Add a layer to the collection.

        Parameters
        ----------
        layer : Optional[Layer], optional
            Layer to add. By default, None.
        """
        if layer is None:
            layer = Layer(
                name='EasyLayer added',
                interface=self.interface,
            )
        self.append(layer)

    def duplicate_layer(self, index: int):
        """Duplicate a layer in the collection.

        Parameters
        ----------
        index : int
        layer :
            Assembly to add.
        """
        to_be_duplicated = self[index]
        duplicate = Layer.from_dict(to_be_duplicated.as_dict(skip=['unique_name']))
        duplicate.name = duplicate.name + ' duplicate'
        self.append(duplicate)

__init__(*layers, name='EasyLayerCollection', interface=None, unique_name=None, populate_if_none=True, **kwargs)

Init function.

Source code in src/easyreflectometry/sample/collections/layer_collection.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def __init__(
    self,
    *layers: Optional[list[Layer]],
    name: str = 'EasyLayerCollection',
    interface=None,
    unique_name: Optional[str] = None,
    populate_if_none: bool = True,
    **kwargs,
):
    """Init function."""
    if not layers:
        layers = []

    super().__init__(
        name,
        interface,
        *layers,
        unique_name=unique_name,
        populate_if_none=populate_if_none,
        **kwargs,
    )

add_layer(layer=None)

Add a layer to the collection.

Parameters:

Name Type Description Default
layer Optional[Layer]

Layer to add. By default, None.

None
Source code in src/easyreflectometry/sample/collections/layer_collection.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def add_layer(self, layer: Optional[Layer] = None):
    """Add a layer to the collection.

    Parameters
    ----------
    layer : Optional[Layer], optional
        Layer to add. By default, None.
    """
    if layer is None:
        layer = Layer(
            name='EasyLayer added',
            interface=self.interface,
        )
    self.append(layer)

duplicate_layer(index)

Duplicate a layer in the collection.

Parameters:

Name Type Description Default
index int
required
layer

Assembly to add.

required
Source code in src/easyreflectometry/sample/collections/layer_collection.py
49
50
51
52
53
54
55
56
57
58
59
60
61
def duplicate_layer(self, index: int):
    """Duplicate a layer in the collection.

    Parameters
    ----------
    index : int
    layer :
        Assembly to add.
    """
    to_be_duplicated = self[index]
    duplicate = Layer.from_dict(to_be_duplicated.as_dict(skip=['unique_name']))
    duplicate.name = duplicate.name + ' duplicate'
    self.append(duplicate)

Layer

Bases: BaseCore

Source code in src/easyreflectometry/sample/elements/layers/layer.py
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
class Layer(BaseCore):
    def __init__(
        self,
        material: Union[Material, None] = None,
        thickness: Union[Parameter, float, None] = None,
        roughness: Union[Parameter, float, None] = None,
        name: str = 'EasyLayer',
        unique_name: Optional[str] = None,
        interface=None,
        magnetism: Union[LayerMagnetism, None] = None,
    ):
        """Constructor.

        Parameters
        ----------
        unique_name : Optional[str], optional
            By default, None.
        material : Union[Material, None], optional
            The material for the layer. By default, None.
        thickness : Union[Parameter, float, None], optional
            Layer thickness in Angstrom. By default, None.
        roughness : Union[Parameter, float, None], optional
            Upper roughness on the layer in Angstrom. By default, None.
        magnetism : Union[LayerMagnetism, None], optional
            Magnetic properties of the layer; None for a non-magnetic layer.
            By default, None.
        name : str, optional
            Name of the layer. By default, 'EasyLayer'.
        interface :
            Interface object. By default, None.
        """
        if material is None:
            material = Material(interface=interface)

        if unique_name is None:
            unique_name = global_object.generate_unique_name(self.__class__.__name__)

        thickness_value = thickness
        thickness = get_as_parameter(
            name='thickness',
            value=thickness,
            default_dict=DEFAULTS,
            unique_name_prefix=f'{unique_name}_Thickness',
        )
        thickness.default_limits_pending = not isinstance(thickness_value, Parameter)

        roughness_value = roughness
        roughness = get_as_parameter(
            name='roughness',
            value=roughness,
            default_dict=DEFAULTS,
            unique_name_prefix=f'{unique_name}_Roughness',
        )
        roughness.default_limits_pending = not isinstance(roughness_value, Parameter)

        super().__init__(name=name, unique_name=unique_name)
        self._material = material
        self._thickness = thickness
        self._roughness = roughness
        self._magnetism = magnetism

        if interface is not None:
            self.interface = interface

    # ----- interface (override BaseCore's to switch on calculator magnetism) -----

    @BaseCore.interface.setter
    def interface(self, new_interface) -> None:
        """Set the interface; runs `generate_bindings` and, for a magnetic layer,
        enables magnetism on the calculator (raising if it does not support it).
        """
        BaseCore.interface.fset(self, new_interface)
        if new_interface is not None and self._magnetism is not None:
            self._enable_calculator_magnetism()

    def _enable_calculator_magnetism(self) -> None:
        """Turn on magnetism on the attached calculator.

        Raises `NotImplementedError` when the active calculator cannot model
        magnetic samples (e.g. refnx or bornagain).
        """
        self.interface().include_magnetism = True

    @property
    def material(self) -> Material:
        return self._material

    @material.setter
    def material(self, value: Material) -> None:
        self._material = value

    @property
    def thickness(self) -> Parameter:
        return self._thickness

    @thickness.setter
    def thickness(self, value: float) -> None:
        self._thickness.value = value

    @property
    def roughness(self) -> Parameter:
        return self._roughness

    @roughness.setter
    def roughness(self, value: float) -> None:
        self._roughness.value = value

    @property
    def magnetism(self) -> Optional[LayerMagnetism]:
        return self._magnetism

    @magnetism.setter
    def magnetism(self, value: Optional[LayerMagnetism]) -> None:
        """Attach or remove the magnetic properties of this layer.

        Attaching regenerates the calculator bindings so `rho_m`/`theta_m` become
        live on the backend; removing drops the layer's magnetic state from the
        backend (and switches calculator magnetism off entirely when this was the
        last magnetic layer).
        """
        if value is None and self._magnetism is not None:
            if self.interface is not None:
                self.interface().remove_layer_magnetism(self.unique_name)
            # Detach the calculator callbacks of the removed parameters so later
            # value changes on the detached object no longer reach the backend.
            # `property()` (fget/fset/fdel all None) is easyscience's own "no
            # callback" sentinel -- `Parameter.__copy__` sets it the same way --
            # so a later `fset` guard in `Parameter` cleanly no-ops.
            self._magnetism.rho_m._callback = property()
            self._magnetism.theta_m._callback = property()
        self._magnetism = value
        if value is not None and self.interface is not None:
            self._enable_calculator_magnetism()
            self.generate_bindings()

    def assign_material(self, material: Material) -> None:
        """Assign a material to the layer interface.

        Parameters
        ----------
        material : Material
            The material to assign to the layer.
        """
        self._material = material
        if self.interface is not None:
            self.interface().assign_material_to_layer(self.material.unique_name, self.unique_name)

    # Representation
    @property
    def _dict_repr(self) -> dict[str, str]:
        """A simplified dict representation."""
        this_dict = {
            self.name: {
                'material': self.material._dict_repr,
                'thickness': f'{self.thickness.value:.3f} {self.thickness.unit}',
                'roughness': f'{self.roughness.value:.3f} {self.roughness.unit}',
            }
        }
        if self._magnetism is not None:
            this_dict[self.name]['magnetism'] = self._magnetism._dict_repr
        return this_dict

__init__(material=None, thickness=None, roughness=None, name='EasyLayer', unique_name=None, interface=None, magnetism=None)

Constructor.

Parameters:

Name Type Description Default
unique_name Optional[str]

By default, None.

None
material Union[Material, None]

The material for the layer. By default, None.

None
thickness Union[Parameter, float, None]

Layer thickness in Angstrom. By default, None.

None
roughness Union[Parameter, float, None]

Upper roughness on the layer in Angstrom. By default, None.

None
magnetism Union[LayerMagnetism, None]

Magnetic properties of the layer; None for a non-magnetic layer. By default, None.

None
name str

Name of the layer. By default, 'EasyLayer'.

'EasyLayer'
interface

Interface object. By default, None.

None
Source code in src/easyreflectometry/sample/elements/layers/layer.py
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def __init__(
    self,
    material: Union[Material, None] = None,
    thickness: Union[Parameter, float, None] = None,
    roughness: Union[Parameter, float, None] = None,
    name: str = 'EasyLayer',
    unique_name: Optional[str] = None,
    interface=None,
    magnetism: Union[LayerMagnetism, None] = None,
):
    """Constructor.

    Parameters
    ----------
    unique_name : Optional[str], optional
        By default, None.
    material : Union[Material, None], optional
        The material for the layer. By default, None.
    thickness : Union[Parameter, float, None], optional
        Layer thickness in Angstrom. By default, None.
    roughness : Union[Parameter, float, None], optional
        Upper roughness on the layer in Angstrom. By default, None.
    magnetism : Union[LayerMagnetism, None], optional
        Magnetic properties of the layer; None for a non-magnetic layer.
        By default, None.
    name : str, optional
        Name of the layer. By default, 'EasyLayer'.
    interface :
        Interface object. By default, None.
    """
    if material is None:
        material = Material(interface=interface)

    if unique_name is None:
        unique_name = global_object.generate_unique_name(self.__class__.__name__)

    thickness_value = thickness
    thickness = get_as_parameter(
        name='thickness',
        value=thickness,
        default_dict=DEFAULTS,
        unique_name_prefix=f'{unique_name}_Thickness',
    )
    thickness.default_limits_pending = not isinstance(thickness_value, Parameter)

    roughness_value = roughness
    roughness = get_as_parameter(
        name='roughness',
        value=roughness,
        default_dict=DEFAULTS,
        unique_name_prefix=f'{unique_name}_Roughness',
    )
    roughness.default_limits_pending = not isinstance(roughness_value, Parameter)

    super().__init__(name=name, unique_name=unique_name)
    self._material = material
    self._thickness = thickness
    self._roughness = roughness
    self._magnetism = magnetism

    if interface is not None:
        self.interface = interface

interface(new_interface)

Set the interface; runs generate_bindings and, for a magnetic layer, enables magnetism on the calculator (raising if it does not support it).

Source code in src/easyreflectometry/sample/elements/layers/layer.py
106
107
108
109
110
111
112
113
@BaseCore.interface.setter
def interface(self, new_interface) -> None:
    """Set the interface; runs `generate_bindings` and, for a magnetic layer,
    enables magnetism on the calculator (raising if it does not support it).
    """
    BaseCore.interface.fset(self, new_interface)
    if new_interface is not None and self._magnetism is not None:
        self._enable_calculator_magnetism()

assign_material(material)

Assign a material to the layer interface.

Parameters:

Name Type Description Default
material Material

The material to assign to the layer.

required
Source code in src/easyreflectometry/sample/elements/layers/layer.py
175
176
177
178
179
180
181
182
183
184
185
def assign_material(self, material: Material) -> None:
    """Assign a material to the layer interface.

    Parameters
    ----------
    material : Material
        The material to assign to the layer.
    """
    self._material = material
    if self.interface is not None:
        self.interface().assign_material_to_layer(self.material.unique_name, self.unique_name)

BaseAssembly

Bases: BaseCore

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.

Source code in src/easyreflectometry/sample/assemblies/base_assembly.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
class BaseAssembly(BaseCore):
    """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.
    """

    def __init__(
        self,
        name: str,
        type: str,
        interface,
        layers: LayerCollection,
        unique_name: Optional[str] = None,
    ):
        super().__init__(name=name, unique_name=unique_name)
        self._layers = layers

        # Type is needed when fitting in easyscience
        self._type = type
        self._roughness_constraints_setup = False
        self._thickness_constraints_setup = False

        if interface is not None:
            self.interface = interface

    @property
    def layers(self) -> LayerCollection:
        return self._layers

    @layers.setter
    def layers(self, value: LayerCollection) -> None:
        self._layers = value

    @property
    def type(self) -> str:
        """Get type of the assembly.

        Needed by the GUI.
        """
        return self._type

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

    @front_layer.setter
    def front_layer(self, layer: Layer) -> None:
        """Set the front layer in the assembly.

        Parameters
        ----------
        layer : Layer
            Layer to set as the front layer.
        """
        if len(self.layers) == 0:
            self.layers.append(layer)
        else:
            self.layers[0] = layer

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

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

    @back_layer.setter
    def back_layer(self, layer: Layer) -> None:
        """Set the back layer in the assembly.

        Parameters
        ----------
        layer : Layer
            Layer to set as the back layer.
        """

        if len(self.layers) == 0:
            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

    # ----- public constraint toggles -----

    def _layers_follow_front(self, attribute: str) -> bool:
        """True when every layer's `attribute` is tied *equal* to the front layer's.

        Only the conformal idiom counts (expression ``'a'`` over ``{'a': leader}``,
        as set up by ``_setup_*_constraints``); a parameter that merely *uses*
        the front layer's value in some other expression is not conformal.
        """
        if len(self.layers) < 2:
            return False
        leader = getattr(self.front_layer, attribute)
        for layer in list(self.layers)[1:]:
            follower = getattr(layer, attribute)
            if not follows_equal(follower, leader):
                return False
        return True

    @property
    def conformal_thickness(self) -> bool:
        """Whether every layer shares the front layer's thickness."""
        return self._layers_follow_front('thickness')

    @conformal_thickness.setter
    def conformal_thickness(self, status: bool) -> None:
        """Tie (or release) every layer's thickness to the front layer's."""
        if status:
            self._setup_thickness_constraints()
        elif self._layers_follow_front('thickness'):
            for layer in list(self.layers)[1:]:
                layer.thickness.make_independent()

    @property
    def conformal_roughness(self) -> bool:
        """Whether every layer shares the front layer's roughness."""
        return self._layers_follow_front('roughness')

    @conformal_roughness.setter
    def conformal_roughness(self, status: bool) -> None:
        """Tie (or release) every layer's roughness to the front layer's."""
        if status:
            self._setup_roughness_constraints()
        elif self._layers_follow_front('roughness'):
            self._disable_roughness_constraints()

    def _setup_thickness_constraints(self) -> None:
        """Setup thickness constraint, front layer is the deciding layer."""
        independent_param = self.front_layer.thickness
        for i in range(1, len(self.layers)):
            self.layers[i].thickness.make_dependent_on(dependency_expression='a', dependency_map={'a': independent_param})
        self._thickness_constraints_setup = True

    def _enable_thickness_constraints(self):
        """Enable the thickness constraint."""
        if self._thickness_constraints_setup:
            # Make sure that the thickness constraint is enabled
            self._setup_thickness_constraints()
            # Make sure that the thickness parameter is enabled
        else:
            raise Exception('Thickness constraints not setup')

    def _disable_thickness_constraints(self):
        """Disable the thickness constraint."""
        if self._thickness_constraints_setup:
            for i in range(1, len(self.layers)):
                self.layers[i].thickness.make_independent()
        else:
            raise Exception('Thickness constraints not setup')

    def _setup_roughness_constraints(self) -> None:
        """Setup roughness constraint, front layer is the deciding layer."""
        independent_parameter = self.front_layer.roughness
        for i in range(1, len(self.layers)):
            self.layers[i].roughness.make_dependent_on(dependency_expression='a', dependency_map={'a': independent_parameter})
        self._roughness_constraints_setup = True

    def _enable_roughness_constraints(self):
        """Enable the roughness constraint."""
        independent_parameter = self.front_layer.roughness
        for i in range(1, len(self.layers)):
            self.layers[i].roughness.make_dependent_on(dependency_expression='a', dependency_map={'a': independent_parameter})

    def _disable_roughness_constraints(self):
        """Disable the roughness constraint."""
        for i in range(1, len(self.layers)):
            self.layers[i].roughness.make_independent()

type property

Get type of the assembly.

Needed by the GUI.

front_layer property writable

Get the front layer in the assembly.

back_layer property writable

Get the back layer in the assembly.

conformal_thickness property writable

Whether every layer shares the front layer's thickness.

conformal_roughness property writable

Whether every layer shares the front layer's roughness.

Multilayer

Bases: BaseAssembly

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_

.. _multilayer documentation: ../sample/assemblies_library.html#multilayer

Source code in src/easyreflectometry/sample/assemblies/multilayer.py
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
class Multilayer(BaseAssembly):
    """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`_

    .. _`multilayer documentation`: ../sample/assemblies_library.html#multilayer
    """

    def __init__(
        self,
        layers: Union[Layer, list[Layer], LayerCollection, None] = None,
        name: str = 'EasyMultilayer',
        unique_name: Optional[str] = None,
        interface=None,
        type: str = 'Multi-layer',
        populate_if_none: Optional[bool] = True,
        conformal_thickness: bool = False,
        conformal_roughness: bool = False,
    ):
        """Constructor.

        Parameters
        ----------
        populate_if_none : Optional[bool], optional
            By default, True.
        unique_name : Optional[str], optional
            By default, None.
        layers : Union[Layer, list[Layer], LayerCollection, None], optional
            The layers that make up the multi-layer. By default, None.
        name : str, optional
            Name for multi layer. By default, 'EasyMultilayer'.
        interface :
            Calculator interface. By default, None.
        type : str, optional
            Type of the constructed instance. By default, 'Multi-layer'.
        conformal_thickness : bool, optional
            Tie every layer's thickness to the front layer's. Serialization
            reads the current graph state through the matching property, so
            the ties are rebuilt on ``from_dict``. By default, False.
        conformal_roughness : bool, optional
            Tie every layer's roughness to the front layer's, likewise
            persistent. By default, False.
        """
        if layers is None:
            if populate_if_none:
                layers = LayerCollection([Layer(interface=interface)])
            else:
                layers = LayerCollection()
        elif isinstance(layers, Layer):
            layers = LayerCollection(layers, name=layers.name)
        elif isinstance(layers, list):
            layers = LayerCollection(*layers, name='/'.join([layer.name for layer in layers]))
        # Needed to ensure an empty list is created when saving and instatiating the object as_dict -> from_dict
        # Else collisions might occur in global_object.map
        self.populate_if_none = False

        super().__init__(
            name=name,
            type=type,
            interface=interface,
            layers=layers,
            unique_name=unique_name,
        )
        if conformal_thickness:
            self.conformal_thickness = True
        if conformal_roughness:
            self.conformal_roughness = True

    def add_layer(self, *layers: tuple[Layer]) -> None:
        """Add a layer to the multi layer.

        Parameters
        ----------
        *layers : tuple[Layer]
            Layers to add to the multi layer.
        """
        for arg in layers:
            if issubclass(arg.__class__, Layer):
                self.layers.append(arg)
                if self.interface is not None:
                    self.interface().add_layer_to_item(arg.unique_name, self.unique_name)

    def duplicate_layer(self, idx: int) -> None:
        """Duplicate a given layer.

        Parameters
        ----------
        idx : int
            Index of layer to duplicate.
        """
        to_duplicate = self.layers[idx]
        duplicate_layer = Layer(
            material=to_duplicate.material,
            thickness=to_duplicate.thickness.value,
            roughness=to_duplicate.roughness.value,
            name=to_duplicate.name + ' duplicate',
        )
        self.add_layer(duplicate_layer)

    def remove_layer(self, idx: int) -> None:
        """Remove a layer from the item.

        Parameters
        ----------
        idx : int
            Index of layer to remove.
        """
        if self.interface is not None:
            self.interface().remove_layer_from_item(self.layers[idx].unique_name, self.unique_name)
        del self.layers[idx]

    # Representation
    @property
    def _dict_repr(self) -> dict:
        """A simplified dict representation."""
        return {self.name: self.layers._dict_repr}

    @classmethod
    def from_dict(cls, data: dict) -> Multilayer:
        """Create a Multilayer from a dictionary."""
        multilayer = super().from_dict(data)
        return multilayer

__init__(layers=None, name='EasyMultilayer', unique_name=None, interface=None, type='Multi-layer', populate_if_none=True, conformal_thickness=False, conformal_roughness=False)

Constructor.

Parameters:

Name Type Description Default
populate_if_none Optional[bool]

By default, True.

True
unique_name Optional[str]

By default, None.

None
layers Union[Layer, list[Layer], LayerCollection, None]

The layers that make up the multi-layer. By default, None.

None
name str

Name for multi layer. By default, 'EasyMultilayer'.

'EasyMultilayer'
interface

Calculator interface. By default, None.

None
type str

Type of the constructed instance. By default, 'Multi-layer'.

'Multi-layer'
conformal_thickness bool

Tie every layer's thickness to the front layer's. Serialization reads the current graph state through the matching property, so the ties are rebuilt on from_dict. By default, False.

False
conformal_roughness bool

Tie every layer's roughness to the front layer's, likewise persistent. By default, False.

False
Source code in src/easyreflectometry/sample/assemblies/multilayer.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def __init__(
    self,
    layers: Union[Layer, list[Layer], LayerCollection, None] = None,
    name: str = 'EasyMultilayer',
    unique_name: Optional[str] = None,
    interface=None,
    type: str = 'Multi-layer',
    populate_if_none: Optional[bool] = True,
    conformal_thickness: bool = False,
    conformal_roughness: bool = False,
):
    """Constructor.

    Parameters
    ----------
    populate_if_none : Optional[bool], optional
        By default, True.
    unique_name : Optional[str], optional
        By default, None.
    layers : Union[Layer, list[Layer], LayerCollection, None], optional
        The layers that make up the multi-layer. By default, None.
    name : str, optional
        Name for multi layer. By default, 'EasyMultilayer'.
    interface :
        Calculator interface. By default, None.
    type : str, optional
        Type of the constructed instance. By default, 'Multi-layer'.
    conformal_thickness : bool, optional
        Tie every layer's thickness to the front layer's. Serialization
        reads the current graph state through the matching property, so
        the ties are rebuilt on ``from_dict``. By default, False.
    conformal_roughness : bool, optional
        Tie every layer's roughness to the front layer's, likewise
        persistent. By default, False.
    """
    if layers is None:
        if populate_if_none:
            layers = LayerCollection([Layer(interface=interface)])
        else:
            layers = LayerCollection()
    elif isinstance(layers, Layer):
        layers = LayerCollection(layers, name=layers.name)
    elif isinstance(layers, list):
        layers = LayerCollection(*layers, name='/'.join([layer.name for layer in layers]))
    # Needed to ensure an empty list is created when saving and instatiating the object as_dict -> from_dict
    # Else collisions might occur in global_object.map
    self.populate_if_none = False

    super().__init__(
        name=name,
        type=type,
        interface=interface,
        layers=layers,
        unique_name=unique_name,
    )
    if conformal_thickness:
        self.conformal_thickness = True
    if conformal_roughness:
        self.conformal_roughness = True

add_layer(*layers)

Add a layer to the multi layer.

Parameters:

Name Type Description Default
*layers tuple[Layer]

Layers to add to the multi layer.

()
Source code in src/easyreflectometry/sample/assemblies/multilayer.py
86
87
88
89
90
91
92
93
94
95
96
97
98
def add_layer(self, *layers: tuple[Layer]) -> None:
    """Add a layer to the multi layer.

    Parameters
    ----------
    *layers : tuple[Layer]
        Layers to add to the multi layer.
    """
    for arg in layers:
        if issubclass(arg.__class__, Layer):
            self.layers.append(arg)
            if self.interface is not None:
                self.interface().add_layer_to_item(arg.unique_name, self.unique_name)

duplicate_layer(idx)

Duplicate a given layer.

Parameters:

Name Type Description Default
idx int

Index of layer to duplicate.

required
Source code in src/easyreflectometry/sample/assemblies/multilayer.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def duplicate_layer(self, idx: int) -> None:
    """Duplicate a given layer.

    Parameters
    ----------
    idx : int
        Index of layer to duplicate.
    """
    to_duplicate = self.layers[idx]
    duplicate_layer = Layer(
        material=to_duplicate.material,
        thickness=to_duplicate.thickness.value,
        roughness=to_duplicate.roughness.value,
        name=to_duplicate.name + ' duplicate',
    )
    self.add_layer(duplicate_layer)

remove_layer(idx)

Remove a layer from the item.

Parameters:

Name Type Description Default
idx int

Index of layer to remove.

required
Source code in src/easyreflectometry/sample/assemblies/multilayer.py
117
118
119
120
121
122
123
124
125
126
127
def remove_layer(self, idx: int) -> None:
    """Remove a layer from the item.

    Parameters
    ----------
    idx : int
        Index of layer to remove.
    """
    if self.interface is not None:
        self.interface().remove_layer_from_item(self.layers[idx].unique_name, self.unique_name)
    del self.layers[idx]

from_dict(data) classmethod

Create a Multilayer from a dictionary.

Source code in src/easyreflectometry/sample/assemblies/multilayer.py
135
136
137
138
139
@classmethod
def from_dict(cls, data: dict) -> Multilayer:
    """Create a Multilayer from a dictionary."""
    multilayer = super().from_dict(data)
    return multilayer