Skip to content

material_density

MATERIAL_DEFAULTS = {'sld': {'description': 'The real scattering length density for a material in e-6 per squared angstrom.', 'url': 'https://www.ncnr.nist.gov/resources/activation/', 'value': 4.186, 'unit': '1 / angstrom^2', 'min': -np.inf, 'max': np.inf, 'fixed': True}, 'isld': {'description': 'The imaginary scattering length density for a material in e-6 per squared angstrom.', 'url': 'https://www.ncnr.nist.gov/resources/activation/', 'value': 0.0, 'unit': '1 / angstrom^2', 'min': -np.inf, 'max': np.inf, 'fixed': True}} module-attribute

DEFAULTS = {'chemical_structure': 'Si', 'density': {'description': 'The mass density of the material.', 'url': 'https://en.wikipedia.org/wiki/Density', 'value': 2.33, 'unit': 'gram / centimeter ** 3', 'min': 0, 'max': np.inf, 'fixed': True}, 'molecular_weight': {'description': 'The molecular weight of a material.', 'url': 'https://en.wikipedia.org/wiki/Molecular_mass', 'value': 28.02, 'unit': 'g / mole', 'min': -np.inf, 'max': np.inf, 'fixed': True}} module-attribute

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

MaterialDensity

Bases: Material

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

        Parameters
        ----------
        unique_name : Optional[str], optional
            By default, None.
        chemical_structure : Union[str, None], optional
            Chemical formula for the material. By default, None.
        density : Union[Parameter, float, None], optional
            Mass density for the material. By default, None.
        name : str, optional
            Identifier. By default, 'EasyMaterialDensity'.
        interface :
            Interface object. By default, None.
        """
        if unique_name is None:
            unique_name = global_object.generate_unique_name(self.__class__.__name__)

        if chemical_structure is None:
            chemical_structure = DEFAULTS['chemical_structure']

        density = get_as_parameter(
            name='density',
            value=density,
            default_dict=DEFAULTS,
            unique_name_prefix=f'{unique_name}_Density',
        )

        scattering_length = neutron_scattering_length(chemical_structure)

        mw = get_as_parameter(
            name='molecular_weight',
            value=molecular_weight(chemical_structure),
            default_dict=DEFAULTS,
            unique_name_prefix=f'{unique_name}_Mw',
        )
        scattering_length_real = get_as_parameter(
            name='scattering_length_real',
            value=scattering_length.real,
            default_dict=DEFAULTS['sld'],
            unique_name_prefix=f'{unique_name}_ScatteringLengthReal',
        )
        scattering_length_imag = get_as_parameter(
            name='scattering_length_imag',
            value=scattering_length.imag,
            default_dict=DEFAULTS['isld'],
            unique_name_prefix=f'{unique_name}_ScatteringLengthImag',
        )
        sld = get_as_parameter(
            name='sld',
            value=density_to_sld(scattering_length_real.value, mw.value, density.value),
            default_dict=DEFAULTS,
            unique_name_prefix=f'{unique_name}_Sld',
        )
        isld = get_as_parameter(
            name='isld',
            value=density_to_sld(scattering_length_imag.value, mw.value, density.value),
            default_dict=DEFAULTS,
            unique_name_prefix=f'{unique_name}_Isld',
        )

        dependency_expression = '1e-23*(0.602214076e6 * d * sl) / mw'
        dependency_map = {'d': density, 'sl': scattering_length_real, 'mw': mw}
        sld.make_dependent_on(dependency_expression=dependency_expression, dependency_map=dependency_map)

        dependency_map = {'d': density, 'sl': scattering_length_imag, 'mw': mw}
        isld.make_dependent_on(dependency_expression=dependency_expression, dependency_map=dependency_map)

        super().__init__(sld=sld, isld=isld, name=name, unique_name=unique_name, interface=None)

        self._scattering_length_real = scattering_length_real
        self._scattering_length_imag = scattering_length_imag
        self._molecular_weight = mw
        self._density = density
        self._chemical_structure = chemical_structure

        if interface is not None:
            self.interface = interface

    def _setup_sld_constraints(self) -> None:
        """Wire the derived `sld` / `isld` to depend on the current density and
        scattering-length Parameters.

        Idempotent — invoked once from `__init__` and again from `from_dict`
        after :class:`ModelBase` has swapped in the saved Parameter objects.
        """
        for derived in (self._sld, self._isld):
            if not derived.independent:
                derived.make_independent()

        dependency_expression = '1e-23*(0.602214076e6 * d * sl) / mw'
        self._sld.make_dependent_on(
            dependency_expression=dependency_expression,
            dependency_map={
                'd': self._density,
                'sl': self._scattering_length_real,
                'mw': self._molecular_weight,
            },
        )
        self._isld.make_dependent_on(
            dependency_expression=dependency_expression,
            dependency_map={
                'd': self._density,
                'sl': self._scattering_length_imag,
                'mw': self._molecular_weight,
            },
        )

    @classmethod
    def from_dict(cls, obj_dict: dict) -> 'MaterialDensity':
        """Re-attach sld/isld dependencies after deserialization.

        :class:`ModelBase.from_dict` re-points `self._density` at the
        deserialized Parameter (because `density` is a constructor argument);
        the constraint graph built in `__init__` still references the
        temporary Parameter created from the float kwarg. Rebuild here so
        `q.density = X` propagates to the derived SLDs.
        """
        instance = super().from_dict(obj_dict)
        instance._setup_sld_constraints()
        return instance

    @property
    def chemical_structure(self) -> str:
        """Get the chemical structure string."""
        return self._chemical_structure

    @chemical_structure.setter
    def chemical_structure(self, structure_string: str) -> None:
        """Set the chemical structure string.

        Parameters
        ----------
        structure_string : str
            String that defines the chemical structure.
        """
        self._chemical_structure = structure_string
        scattering_length = neutron_scattering_length(structure_string)
        self._scattering_length_real.value = scattering_length.real
        self._scattering_length_imag.value = scattering_length.imag

    @property
    def density(self) -> Parameter:
        return self._density

    @density.setter
    def density(self, value: float) -> None:
        self._density.value = value

    @property
    def molecular_weight(self) -> Parameter:
        return self._molecular_weight

    @property
    def scattering_length_real(self) -> Parameter:
        return self._scattering_length_real

    @property
    def scattering_length_imag(self) -> Parameter:
        return self._scattering_length_imag

    @property
    def _dict_repr(self) -> dict[str, str]:
        """Dictionary representation of the instance."""
        mat_dict = super()._dict_repr
        mat_dict['chemical_structure'] = self._chemical_structure
        mat_dict['density'] = f'{self.density.value:.2e} {self.density.unit}'
        return mat_dict

chemical_structure property writable

Get the chemical structure string.

__init__(chemical_structure=None, density=None, name='EasyMaterialDensity', unique_name=None, interface=None)

Constructor.

Parameters:

Name Type Description Default
unique_name Optional[str]

By default, None.

None
chemical_structure Union[str, None]

Chemical formula for the material. By default, None.

None
density Union[Parameter, float, None]

Mass density for the material. By default, None.

None
name str

Identifier. By default, 'EasyMaterialDensity'.

'EasyMaterialDensity'
interface

Interface object. By default, None.

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

    Parameters
    ----------
    unique_name : Optional[str], optional
        By default, None.
    chemical_structure : Union[str, None], optional
        Chemical formula for the material. By default, None.
    density : Union[Parameter, float, None], optional
        Mass density for the material. By default, None.
    name : str, optional
        Identifier. By default, 'EasyMaterialDensity'.
    interface :
        Interface object. By default, None.
    """
    if unique_name is None:
        unique_name = global_object.generate_unique_name(self.__class__.__name__)

    if chemical_structure is None:
        chemical_structure = DEFAULTS['chemical_structure']

    density = get_as_parameter(
        name='density',
        value=density,
        default_dict=DEFAULTS,
        unique_name_prefix=f'{unique_name}_Density',
    )

    scattering_length = neutron_scattering_length(chemical_structure)

    mw = get_as_parameter(
        name='molecular_weight',
        value=molecular_weight(chemical_structure),
        default_dict=DEFAULTS,
        unique_name_prefix=f'{unique_name}_Mw',
    )
    scattering_length_real = get_as_parameter(
        name='scattering_length_real',
        value=scattering_length.real,
        default_dict=DEFAULTS['sld'],
        unique_name_prefix=f'{unique_name}_ScatteringLengthReal',
    )
    scattering_length_imag = get_as_parameter(
        name='scattering_length_imag',
        value=scattering_length.imag,
        default_dict=DEFAULTS['isld'],
        unique_name_prefix=f'{unique_name}_ScatteringLengthImag',
    )
    sld = get_as_parameter(
        name='sld',
        value=density_to_sld(scattering_length_real.value, mw.value, density.value),
        default_dict=DEFAULTS,
        unique_name_prefix=f'{unique_name}_Sld',
    )
    isld = get_as_parameter(
        name='isld',
        value=density_to_sld(scattering_length_imag.value, mw.value, density.value),
        default_dict=DEFAULTS,
        unique_name_prefix=f'{unique_name}_Isld',
    )

    dependency_expression = '1e-23*(0.602214076e6 * d * sl) / mw'
    dependency_map = {'d': density, 'sl': scattering_length_real, 'mw': mw}
    sld.make_dependent_on(dependency_expression=dependency_expression, dependency_map=dependency_map)

    dependency_map = {'d': density, 'sl': scattering_length_imag, 'mw': mw}
    isld.make_dependent_on(dependency_expression=dependency_expression, dependency_map=dependency_map)

    super().__init__(sld=sld, isld=isld, name=name, unique_name=unique_name, interface=None)

    self._scattering_length_real = scattering_length_real
    self._scattering_length_imag = scattering_length_imag
    self._molecular_weight = mw
    self._density = density
    self._chemical_structure = chemical_structure

    if interface is not None:
        self.interface = interface

from_dict(obj_dict) classmethod

Re-attach sld/isld dependencies after deserialization.

:class:ModelBase.from_dict re-points self._density at the deserialized Parameter (because density is a constructor argument); the constraint graph built in __init__ still references the temporary Parameter created from the float kwarg. Rebuild here so q.density = X propagates to the derived SLDs.

Source code in src/easyreflectometry/sample/elements/materials/material_density.py
160
161
162
163
164
165
166
167
168
169
170
171
172
@classmethod
def from_dict(cls, obj_dict: dict) -> 'MaterialDensity':
    """Re-attach sld/isld dependencies after deserialization.

    :class:`ModelBase.from_dict` re-points `self._density` at the
    deserialized Parameter (because `density` is a constructor argument);
    the constraint graph built in `__init__` still references the
    temporary Parameter created from the float kwarg. Rebuild here so
    `q.density = X` propagates to the derived SLDs.
    """
    instance = super().from_dict(obj_dict)
    instance._setup_sld_constraints()
    return instance

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.')