Skip to content

layer

DEFAULTS = {'thickness': {'description': 'The thickness of the layer in angstroms', 'url': 'https://github.com/reflectivity/edu_outreach/blob/master/refl_maths/paper.tex', 'value': 10.0, 'unit': 'angstrom', 'min': 0.0, 'max': np.inf, 'fixed': True}, 'roughness': {'description': 'The interfacial roughness, Nevot-Croce, for the layer in angstroms.', 'url': 'https://doi.org/10.1051/rphysap:01980001503076100', 'value': 3.3, 'unit': 'angstrom', 'min': 0.0, 'max': np.inf, 'fixed': True}} module-attribute

BaseCore

Bases: ModelBase

Local base class for sample-tree objects (Material, Layer, assemblies).

Built on top of easyscience.base_classes.ModelBase (the replacement for the deprecated ObjBase). On top of ModelBase this class adds:

  • a name property
  • an interface property whose setter propagates the calculator interface to child objects and (re)generates bindings
  • a yaml-formatted __repr__ driven by an abstract _dict_repr
  • an _get_linkable_attributes compatibility shim used by the calculator's InterfaceFactoryTemplate.generate_bindings
  • an as_dict alias for to_dict

Subclass __init__ convention: 1. Build child Parameters / sub-objects. 2. Call super().__init__(name=..., unique_name=...). 3. Assign children to backing fields (self._sld = sld etc.) or pass them as **kwargs to this base class (transitional path; each kwarg is stored as a plain instance attribute). 4. Last: self.interface = interface (triggers generate_bindings).

Source code in src/easyreflectometry/sample/base_core.py
 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
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
class BaseCore(ModelBase):
    """Local base class for sample-tree objects (Material, Layer, assemblies).

    Built on top of `easyscience.base_classes.ModelBase` (the replacement for
    the deprecated `ObjBase`). On top of `ModelBase` this class adds:

    - a `name` property
    - an `interface` property whose setter propagates the calculator interface
      to child objects and (re)generates bindings
    - a yaml-formatted `__repr__` driven by an abstract `_dict_repr`
    - an `_get_linkable_attributes` compatibility shim used by the calculator's
      `InterfaceFactoryTemplate.generate_bindings`
    - an `as_dict` alias for `to_dict`

    Subclass `__init__` convention:
        1. Build child Parameters / sub-objects.
        2. Call ``super().__init__(name=..., unique_name=...)``.
        3. Assign children to backing fields (``self._sld = sld`` etc.) or pass
           them as ``**kwargs`` to this base class (transitional path; each
           kwarg is stored as a plain instance attribute).
        4. Last: ``self.interface = interface`` (triggers ``generate_bindings``).
    """

    def __init__(
        self,
        name: str,
        interface: Any = None,
        unique_name: Optional[str] = None,
        display_name: Optional[str] = None,
        **kwargs: Any,
    ):
        super().__init__(unique_name=unique_name, display_name=display_name)
        self._name = name
        self._interface = None
        # `user_data` is part of the legacy `BasedBase` API — a free-form dict
        # callers stash arbitrary metadata in. Kept for back-compat with code
        # like `Project.replace_models_from_orso` which stores the ORSO sample
        # name on the model.
        self.user_data: dict = {}

        # Transitional path: subclasses still pass parameter / child objects via
        # **kwargs (legacy `ObjBase` accepted them and stashed in `_kwargs`).
        # Here we simply store each one as a plain instance attribute so
        # `obj.<key>` keeps working. Step 2 of the migration replaces this with
        # explicit assignments in each subclass.
        for key, value in kwargs.items():
            setattr(self, key, value)

        # Assign interface LAST so children exist when generate_bindings runs.
        if interface is not None:
            self.interface = interface

    # ----- name -----

    @property
    def name(self) -> str:
        """Common (display-friendly) name."""
        return self._name

    @name.setter
    def name(self, value: str) -> None:
        self._name = value

    # ----- interface -----

    @property
    def interface(self) -> Any:
        """The calculator interface attached to this object (may be None)."""
        return self._interface

    @interface.setter
    def interface(self, new_interface: Any) -> None:
        self._interface = new_interface
        if new_interface is not None:
            self.generate_bindings()

    def generate_bindings(self) -> None:
        """Propagate the interface to child objects, then bind via the calculator.

        We propagate to any child whose class advertises an ``interface`` property
        with a setter. That includes both the new `BaseCore`-based children and
        legacy `BasedBase`-derived collections (which extend `SerializerComponent`,
        not `NewBase`).
        """
        if self._interface is None:
            raise AttributeError('Interface error for generating bindings. `interface` has to be set.')
        for attr in self._iter_public_children():
            if self._has_interface_setter(type(attr)):
                attr.interface = self._interface
        self._interface.generate_bindings(self)

    def _iter_public_children(self):
        """Yield public child objects from both class-level (dir) and instance-level (__dict__) attrs.

        `NewBase.__dir__` exposes only class attributes, which means plain instance
        attributes (the transitional `setattr(self, key, value)` path in
        `__init__`) are invisible to a pure `dir()` scan. To bridge both worlds —
        legacy subclasses that still use plain attrs, and migrated subclasses that
        expose children via `@property` accessors — this helper unions the two.
        Once all subclasses migrate to `@property`-backed children with `_field`
        backing storage, the `__dict__` branch becomes a no-op (private names are
        skipped).
        """
        seen_ids = {id(self)}
        # Class-level (properties, methods named like sld/isld/material).
        for attr_name in dir(self):
            if attr_name.startswith('_') or attr_name in ('interface', 'name'):
                continue
            try:
                attr = getattr(self, attr_name, None)
            except AttributeError:
                # A subclass @property may legitimately raise AttributeError
                # mid-construction (the `_field` backing isn't set yet); skip
                # those entries silently. Other exceptions should propagate.
                continue
            if attr is None or id(attr) in seen_ids:
                continue
            seen_ids.add(id(attr))
            yield attr
        # Instance-level (plain attrs set via the transitional kwargs path).
        for attr_name, attr in list(self.__dict__.items()):
            if attr_name.startswith('_') or attr_name in ('interface', 'name'):
                continue
            if attr is None or id(attr) in seen_ids:
                continue
            seen_ids.add(id(attr))
            yield attr

    @staticmethod
    def _has_interface_setter(obj_type: type) -> bool:
        for klass in obj_type.__mro__:
            prop = klass.__dict__.get('interface')
            if isinstance(prop, property):
                return prop.fset is not None
        return False

    # ----- compatibility shims -----

    def _get_linkable_attributes(self):
        """Used by `easyscience.fitting.calculators.interface_factory.generate_bindings`.

        Returns the same set as :meth:`get_all_variables` (the modern API on
        :class:`ModelBase`). Kept under the legacy name because the calculator
        in `easyscience` core has not yet been updated.
        """
        return self.get_all_variables()

    def get_parameters(self):
        """Compatibility shim for legacy callers; prefer :meth:`get_all_parameters`."""
        return self.get_all_parameters()

    def _add_component(self, key: str, component: Any) -> None:
        """Compatibility shim for legacy `ObjBase._add_component`.

        Legacy callers (e.g. `LayerAreaPerMolecule`) used this to register an
        additional child after `super().__init__`. In the new world the
        equivalent is simply setting an attribute; we do that here so the
        existing call sites keep working until Step 2 removes them.
        """
        setattr(self, key, component)

    def get_all_variables(self):
        """Discover Parameters/Descriptors across both class-level and instance-level attrs.

        `ModelBase.get_all_variables` walks `dir(self)`, which `NewBase` restricts
        to class attributes only. During the transition some subclasses still
        store child Parameters as plain instance attributes (see the kwargs path
        in :meth:`__init__`); those are invisible to `dir()`. We therefore also
        scan `self.__dict__` for `DescriptorBase` instances and for child
        ModelBase objects whose own `get_all_variables` we recurse into.
        """
        from easyscience.variable.descriptor_base import DescriptorBase

        out: list = []
        seen_param_ids: set[int] = set()
        for attr in self._iter_public_children():
            if isinstance(attr, DescriptorBase):
                if id(attr) not in seen_param_ids:
                    seen_param_ids.add(id(attr))
                    out.append(attr)
            elif hasattr(attr, 'get_all_variables'):
                for v in attr.get_all_variables():
                    if id(v) not in seen_param_ids:
                        seen_param_ids.add(id(v))
                        out.append(v)
        return out

    def to_dict(self, skip: Optional[list[str]] = None) -> dict[str, Any]:
        """Serialize, skipping the calculator interface and unique_name by default.

        The calculator (`CalculatorFactory`) is not serializable and is not part
        of the model's persistent state — round-trip code that needs it back
        reattaches it after `from_dict`. The legacy `ObjBase`-based pipeline
        achieved the same by never including `interface` in its `_kwargs`
        encoding; we replicate that here.

        `unique_name` is also stripped by default, matching the legacy
        `BasedBase.as_dict` contract. The installed `SerializerBase` does *not*
        propagate per-object `_default_unique_name` to nested children — if
        we leave it in, child Parameters end up with explicit unique_names in
        the dict (e.g. `Parameter_0`) that subsequently collide on reload when
        the global counter restarts from 0.

        Pass a *copy* of `skip` to super since `NewBase.to_dict` mutates the
        list (appends `unique_name` / `display_name` if those are default).
        """
        skip = list(skip or [])
        if 'interface' not in skip:
            skip.append('interface')
        if 'unique_name' not in skip:
            skip.append('unique_name')
        return super().to_dict(skip=list(skip))

    def as_dict(self, skip: Optional[list[str]] = None) -> dict[str, Any]:
        """Compatibility alias for :meth:`to_dict`."""
        return self.to_dict(skip=skip)

    # ----- repr -----

    @property
    @abstractmethod
    def _dict_repr(self) -> dict[str, Any]: ...

    def __repr__(self) -> str:
        """Yaml-formatted multi-line string built from :attr:`_dict_repr`."""
        return yaml_dump(self._dict_repr)

interface property writable

The calculator interface attached to this object (may be None).

name property writable

Common (display-friendly) name.

generate_bindings()

Propagate the interface to child objects, then bind via the calculator.

We propagate to any child whose class advertises an interface property with a setter. That includes both the new BaseCore-based children and legacy BasedBase-derived collections (which extend SerializerComponent, not NewBase).

Source code in src/easyreflectometry/sample/base_core.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def generate_bindings(self) -> None:
    """Propagate the interface to child objects, then bind via the calculator.

    We propagate to any child whose class advertises an ``interface`` property
    with a setter. That includes both the new `BaseCore`-based children and
    legacy `BasedBase`-derived collections (which extend `SerializerComponent`,
    not `NewBase`).
    """
    if self._interface is None:
        raise AttributeError('Interface error for generating bindings. `interface` has to be set.')
    for attr in self._iter_public_children():
        if self._has_interface_setter(type(attr)):
            attr.interface = self._interface
    self._interface.generate_bindings(self)

get_parameters()

Compatibility shim for legacy callers; prefer :meth:get_all_parameters.

Source code in src/easyreflectometry/sample/base_core.py
162
163
164
def get_parameters(self):
    """Compatibility shim for legacy callers; prefer :meth:`get_all_parameters`."""
    return self.get_all_parameters()

get_all_variables()

Discover Parameters/Descriptors across both class-level and instance-level attrs.

ModelBase.get_all_variables walks dir(self), which NewBase restricts to class attributes only. During the transition some subclasses still store child Parameters as plain instance attributes (see the kwargs path in :meth:__init__); those are invisible to dir(). We therefore also scan self.__dict__ for DescriptorBase instances and for child ModelBase objects whose own get_all_variables we recurse into.

Source code in src/easyreflectometry/sample/base_core.py
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
def get_all_variables(self):
    """Discover Parameters/Descriptors across both class-level and instance-level attrs.

    `ModelBase.get_all_variables` walks `dir(self)`, which `NewBase` restricts
    to class attributes only. During the transition some subclasses still
    store child Parameters as plain instance attributes (see the kwargs path
    in :meth:`__init__`); those are invisible to `dir()`. We therefore also
    scan `self.__dict__` for `DescriptorBase` instances and for child
    ModelBase objects whose own `get_all_variables` we recurse into.
    """
    from easyscience.variable.descriptor_base import DescriptorBase

    out: list = []
    seen_param_ids: set[int] = set()
    for attr in self._iter_public_children():
        if isinstance(attr, DescriptorBase):
            if id(attr) not in seen_param_ids:
                seen_param_ids.add(id(attr))
                out.append(attr)
        elif hasattr(attr, 'get_all_variables'):
            for v in attr.get_all_variables():
                if id(v) not in seen_param_ids:
                    seen_param_ids.add(id(v))
                    out.append(v)
    return out

to_dict(skip=None)

Serialize, skipping the calculator interface and unique_name by default.

The calculator (CalculatorFactory) is not serializable and is not part of the model's persistent state — round-trip code that needs it back reattaches it after from_dict. The legacy ObjBase-based pipeline achieved the same by never including interface in its _kwargs encoding; we replicate that here.

unique_name is also stripped by default, matching the legacy BasedBase.as_dict contract. The installed SerializerBase does not propagate per-object _default_unique_name to nested children — if we leave it in, child Parameters end up with explicit unique_names in the dict (e.g. Parameter_0) that subsequently collide on reload when the global counter restarts from 0.

Pass a copy of skip to super since NewBase.to_dict mutates the list (appends unique_name / display_name if those are default).

Source code in src/easyreflectometry/sample/base_core.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
def to_dict(self, skip: Optional[list[str]] = None) -> dict[str, Any]:
    """Serialize, skipping the calculator interface and unique_name by default.

    The calculator (`CalculatorFactory`) is not serializable and is not part
    of the model's persistent state — round-trip code that needs it back
    reattaches it after `from_dict`. The legacy `ObjBase`-based pipeline
    achieved the same by never including `interface` in its `_kwargs`
    encoding; we replicate that here.

    `unique_name` is also stripped by default, matching the legacy
    `BasedBase.as_dict` contract. The installed `SerializerBase` does *not*
    propagate per-object `_default_unique_name` to nested children — if
    we leave it in, child Parameters end up with explicit unique_names in
    the dict (e.g. `Parameter_0`) that subsequently collide on reload when
    the global counter restarts from 0.

    Pass a *copy* of `skip` to super since `NewBase.to_dict` mutates the
    list (appends `unique_name` / `display_name` if those are default).
    """
    skip = list(skip or [])
    if 'interface' not in skip:
        skip.append('interface')
    if 'unique_name' not in skip:
        skip.append('unique_name')
    return super().to_dict(skip=list(skip))

as_dict(skip=None)

Compatibility alias for :meth:to_dict.

Source code in src/easyreflectometry/sample/base_core.py
228
229
230
def as_dict(self, skip: Optional[list[str]] = None) -> dict[str, Any]:
    """Compatibility alias for :meth:`to_dict`."""
    return self.to_dict(skip=skip)

__repr__()

Yaml-formatted multi-line string built from :attr:_dict_repr.

Source code in src/easyreflectometry/sample/base_core.py
238
239
240
def __repr__(self) -> str:
    """Yaml-formatted multi-line string built from :attr:`_dict_repr`."""
    return yaml_dump(self._dict_repr)

Material

Bases: BaseCore

Source code in src/easyreflectometry/sample/elements/materials/material.py
 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
class Material(BaseCore):
    def __init__(
        self,
        sld: Union[Parameter, float, None] = None,
        isld: Union[Parameter, float, None] = None,
        name: str = 'EasyMaterial',
        unique_name: Optional[str] = None,
        interface=None,
    ):
        """Constructor.

        Parameters
        ----------
        unique_name : Optional[str], optional
            By default, None.
        sld : Union[Parameter, float, None], optional
            Real scattering length density. By default, None.
        isld : Union[Parameter, float, None], optional
            Imaginary scattering length density. By default, None.
        name : str, optional
            Name of the material. By default, 'EasyMaterial'.
        interface :
            Calculator interface. By default, None.
        """
        if unique_name is None:
            unique_name = global_object.generate_unique_name(self.__class__.__name__)

        sld = get_as_parameter(
            name='sld',
            value=sld,
            default_dict=DEFAULTS,
            unique_name_prefix=f'{unique_name}_Sld',
        )
        apply_default_limits(sld, 'sld')

        isld = get_as_parameter(
            name='isld',
            value=isld,
            default_dict=DEFAULTS,
            unique_name_prefix=f'{unique_name}_Isld',
        )
        apply_default_limits(isld, 'isld')

        super().__init__(name=name, unique_name=unique_name)
        self._sld = sld
        self._isld = isld

        if interface is not None:
            self.interface = interface

    @property
    def sld(self) -> Parameter:
        return self._sld

    @sld.setter
    def sld(self, value: float) -> None:
        self._sld.value = value

    @property
    def isld(self) -> Parameter:
        return self._isld

    @isld.setter
    def isld(self, value: float) -> None:
        self._isld.value = value

    # Representation
    @property
    def _dict_repr(self) -> dict[str, str]:
        """A simplified dict representation."""
        return {
            self.name: {
                'sld': f'{self._sld.value:.3f}e-6 {self._sld.unit}',
                'isld': f'{self._isld.value:.3f}e-6 {self._isld.unit}',
            }
        }

__init__(sld=None, isld=None, name='EasyMaterial', unique_name=None, interface=None)

Constructor.

Parameters:

Name Type Description Default
unique_name Optional[str]

By default, None.

None
sld Union[Parameter, float, None]

Real scattering length density. By default, None.

None
isld Union[Parameter, float, None]

Imaginary scattering length density. By default, None.

None
name str

Name of the material. By default, 'EasyMaterial'.

'EasyMaterial'
interface

Calculator interface. By default, None.

None
Source code in src/easyreflectometry/sample/elements/materials/material.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
def __init__(
    self,
    sld: Union[Parameter, float, None] = None,
    isld: Union[Parameter, float, None] = None,
    name: str = 'EasyMaterial',
    unique_name: Optional[str] = None,
    interface=None,
):
    """Constructor.

    Parameters
    ----------
    unique_name : Optional[str], optional
        By default, None.
    sld : Union[Parameter, float, None], optional
        Real scattering length density. By default, None.
    isld : Union[Parameter, float, None], optional
        Imaginary scattering length density. By default, None.
    name : str, optional
        Name of the material. By default, 'EasyMaterial'.
    interface :
        Calculator interface. By default, None.
    """
    if unique_name is None:
        unique_name = global_object.generate_unique_name(self.__class__.__name__)

    sld = get_as_parameter(
        name='sld',
        value=sld,
        default_dict=DEFAULTS,
        unique_name_prefix=f'{unique_name}_Sld',
    )
    apply_default_limits(sld, 'sld')

    isld = get_as_parameter(
        name='isld',
        value=isld,
        default_dict=DEFAULTS,
        unique_name_prefix=f'{unique_name}_Isld',
    )
    apply_default_limits(isld, 'isld')

    super().__init__(name=name, unique_name=unique_name)
    self._sld = sld
    self._isld = isld

    if interface is not None:
        self.interface = interface

Layer

Bases: BaseCore

Source code in src/easyreflectometry/sample/elements/layers/layer.py
 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
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,
    ):
        """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.
        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

        if interface is not None:
            self.interface = interface

    @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

    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."""
        return {
            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}',
            }
        }

__init__(material=None, thickness=None, roughness=None, name='EasyLayer', unique_name=None, interface=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
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
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
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,
):
    """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.
    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

    if interface is not None:
        self.interface = interface

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
122
123
124
125
126
127
128
129
130
131
132
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)

get_as_parameter(name, value, default_dict, unique_name_prefix=None)

This function creates a parameter for the variable name.

A parameter has a value and metadata.

If the value already is a parameter, it is returned. If the value is a number, a parameter is created with this value and metadata from the dictionary. If the value is None, a parameter is created with the default value and metadata from the dictionary.

param value: The value to use for the parameter.  If None, the default value in the dictionary is used.
param name: The name of the parameter
param default_dict: Dictionary with entry for `name` containing the default value and metadata for the parameter
Source code in src/easyreflectometry/utils.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
def get_as_parameter(
    name: str,
    value: Union[Parameter, Number, None],
    default_dict: dict,
    unique_name_prefix: Optional[str] = None,
) -> Parameter:
    """This function creates a parameter for the variable `name`.

        A parameter has a value and metadata.
    If the value already is a parameter, it is returned.
        If the value is a number, a parameter is created with this value and metadata from the dictionary.
        If the value is None, a parameter is created with the default value and metadata from the dictionary.

        param value: The value to use for the parameter.  If None, the default value in the dictionary is used.
        param name: The name of the parameter
        param default_dict: Dictionary with entry for `name` containing the default value and metadata for the parameter
    """
    # This is a parameter, return it
    if isinstance(value, Parameter):
        return value

    # Ensure we got the dictionary for the parameter with the given name
    # Should leave the passed dictionary unchanged
    if name not in default_dict:
        parameter_dict = deepcopy(default_dict)
    else:
        parameter_dict = deepcopy(default_dict[name])

    # Add specific unique name prefix if requested
    if unique_name_prefix is not None:
        parameter_dict['unique_name'] = global_object.generate_unique_name(unique_name_prefix + 'Parameter')

    if value is None:
        # Create a default parameter using both value and metadata from dictionary
        return Parameter(name, **parameter_dict)
    elif isinstance(value, Number):
        # Create a parameter using provided value and metadata from dictionary
        del parameter_dict['value']
        return Parameter(name, value, **parameter_dict)

    raise ValueError(f'{name} must be a Parameter, a number, or None.')