Source code for refnx.reflect._functional_form
import numpy as np
from refnx.analysis import (
possibly_create_parameter,
Parameter,
is_parameter,
Parameters,
)
from refnx.reflect import Component, Structure, SLD
[docs]
class FunctionalForm(Component):
"""
Component used to describe an analytic SLD profile.
Parameters
----------
extent : Parameter or float
The total extent of the functional region
profile : callable
callable returning the SLD of the functional form. Has the signature
``profile(z, extent, left_sld, right_sld, **kwds)`` where ``z`` is an
array specifying the distances at which the functional form should be
calculated, ``extent`` is a float specifying the total extent of the
Component, ``left_sld`` and ``right_sld`` are the complex SLDs of the
Components preceding and following this Component. ``kwds`` are extra
parameters used to calculate the shape of the functional form.
The function should return a tuple, ``(slds, vfsolv)`` where ``slds``
is an array (possibly of type np.complex128) of shape `(len(z),)`.
Similarly, ``vfsolv`` is an array of shape `(len(z))` specifying the
volume fraction of solvent at each distance in ``z``. If ``vfsolv`` is
not necessary then return ``(slds, None)``.
name : str
Name of component
microslab_max_thickness : float, optional
Thickness of microslicing of spline for reflectivity calculation
kwds : dict
Named keywords that supply extra Parameters to the ``profile`` callable.
These parameters are passed numerically, not as Parameter objects.
Examples
--------
A linear ramp. Note that the `dummy_param` is not actually used anywhere.
>>> def line(z, extent, left_sld, right_sld, dummy_param=None):
... grad = (right_sld - left_sld) / extent
... intercept = left_sld
... # we don't calculate the volume fraction of solvent
... return z*grad*dummy_param + intercept, None
>>> si = SLD(2.07)
>>> d2o = SLD(6.36)
>>> p = Parameter(1)
>>> form = FunctionalForm(100, line, dummy_param=p)
>>> s = si | form | d2o(0, 3)
A quadratic example that goes through the two end points
>>> def quadratic(z, extent, left_sld, right_sld, x=None, y=None):
... res = np.polyfit(
... [0., x, extent],
... [np.real(left_sld), y, np.real(right_sld)],
... deg=2
... )
... return np.polyval(res, z), None
>>> si = SLD(2.07)
>>> d2o = SLD(6.36)
>>> x = Parameter(4.)
>>> y = Parameter(5.)
>>> quad = FunctionalForm(100., quadratic, x=x, y=y)
>>> s = si | quad | d2o(0, 3)
"""
def __init__(
self, extent, profile, name=None, microslab_max_thickness=1, **kwds
):
super(FunctionalForm, self).__init__(name=name)
self.profile = profile
self.other_params = {k: v for k, v in kwds.items() if is_parameter(v)}
self.extent = possibly_create_parameter(extent)
self.microslab_max_thickness = microslab_max_thickness
@property
def parameters(self):
p = Parameters(name=self.name)
p.extend([self.extent])
p.extend(list(self.other_params.values()))
return p
[docs]
def slabs(self, structure=None):
assert (
structure is not None
), "In order to calculate slab representation please provide a Structure"
try:
loc = structure.index(self)
# figure out SLDs for the bracketing Components.
left_component = structure[loc - 1]
right_component = structure[(loc + 1) % len(structure)]
except ValueError:
raise ValueError(
"FunctionalForm didn't appear to be part of a super structure"
)
left_slab = structure.overall_sld(
np.atleast_2d(left_component.slabs(structure)[-1]),
structure.solvent,
)
left_sld = complex(left_slab[..., 1][0], left_slab[..., 2][0])
right_slab = structure.overall_sld(
np.atleast_2d(right_component.slabs(structure)[0]),
structure.solvent,
)
right_sld = complex(right_slab[..., 1][0], right_slab[..., 2][0])
num_slabs = int(
np.ceil(float(self.extent) / self.microslab_max_thickness)
)
slab_thick = self.extent.value / num_slabs
slabs = np.zeros((num_slabs, 5))
slabs[..., 0] = slab_thick
dist = np.cumsum(slabs[..., 0]) - 0.5 * slab_thick
pars = {k: float(v) for k, v in self.other_params.items()}
res, vfsolv = self.profile(
dist, self.extent.value, left_sld, right_sld, **pars
)
slabs[..., 1] = np.real(res)
slabs[..., 2] = np.imag(res)
if vfsolv is not None:
slabs[..., 4] = np.clip(vfsolv, 0, 1)
return slabs