示例#1
0
    def __init__(self,
                 extent,
                 vs,
                 dz,
                 name='',
                 interpolator=Pchip,
                 zgrad=True,
                 microslab_max_thickness=1):
        super(Spline, self).__init__()
        self.name = name
        self.microslab_max_thickness = microslab_max_thickness

        self.extent = (possibly_create_parameter(extent,
                                                 name='%s - spline extent' %
                                                 name))

        self.dz = Parameters(name='dz - spline')
        for i, z in enumerate(dz):
            p = possibly_create_parameter(z,
                                          name='%s - spline dz[%d]' %
                                          (name, i))
            p.range(0, 1)
            self.dz.append(p)

        self.vs = Parameters(name='vs - spline')
        for i, v in enumerate(vs):
            p = possibly_create_parameter(v,
                                          name='%s - spline vs[%d]' %
                                          (name, i))
            self.vs.append(p)

        if len(self.vs) != len(self.dz):
            raise ValueError("dz and vs must have same number of entries")

        self.zgrad = zgrad
        self.interpolator = interpolator

        self.__cached_interpolator = {
            'zeds': np.array([]),
            'vs': np.array([]),
            'interp': None,
            'extent': -1
        }
示例#2
0
    def parameters(self):
        p = Parameters(name=self.name)
        p.extend([
            self.Popc.s_sld, self.Popc.water_per_lipid_head,
            self.Popc.water_per_lipid_tail, self.Popc.b_heads_real,
            self.Popc.b_heads_imag, self.Popc.b_tails_real,
            self.Popc.b_tails_imag, self.Popc.vm_heads, self.Popc.vm_tails,
            self.Popg.s_sld, self.Popg.water_per_lipid_head,
            self.Popg.water_per_lipid_tail, self.Popg.b_heads_real,
            self.Popg.b_heads_imag, self.Popg.b_tails_real,
            self.Popg.b_tails_imag, self.Popg.vm_heads, self.Popg.vm_tails,
            self.apm, self.roughness_top, self.roughness_bottom, self.ratio,
            self.volFrac
        ])
        return p


#     def logp(self):
#         return 0
示例#3
0
    def __init__(self, thick, sld, rough, name='', vfsolv=0):
        super(Slab, self).__init__()
        self.thick = possibly_create_parameter(thick,
                                               name='%s - thick' % name)
        if isinstance(sld, SLD):
            self.sld = sld
        else:
            self.sld = SLD(sld)
        self.rough = possibly_create_parameter(rough,
                                               name='%s - rough' % name)
        self.vfsolv = (
            possibly_create_parameter(vfsolv,
                                      name='%s - volfrac solvent' % name))
        self.name = name

        p = Parameters(name=self.name)
        p.extend([self.thick, self.sld.real, self.sld.imag,
                  self.rough, self.vfsolv])

        self._parameters = p
示例#4
0
    def __init__(
        self,
        structures,
        scales=None,
        bkg=1e-7,
        name="",
        dq=5.0,
        threads=-1,
        quad_order=17,
        dq_type="pointwise",
    ):
        self.name = name
        self._parameters = None
        self.threads = threads
        self.quad_order = quad_order

        # all reflectometry models need a scale factor and background. Set
        # them all to 1 by default.
        pscales = Parameters(name="scale factors")

        if scales is not None and len(structures) == len(scales):
            tscales = scales
        elif scales is not None and len(structures) != len(scales):
            raise ValueError("You need to supply scale factor for each"
                             " structure")
        else:
            tscales = [1 / len(structures)] * len(structures)

        for scale in tscales:
            p_scale = possibly_create_parameter(scale, name="scale")
            pscales.append(p_scale)

        self._scales = pscales
        self._bkg = possibly_create_parameter(bkg, name="bkg")

        # we can optimize the resolution (but this is always overridden by
        # x_err if supplied. There is therefore possibly no dependence on it.
        self._dq = possibly_create_parameter(dq, name="dq - resolution")
        self.dq_type = dq_type

        self._structures = structures
示例#5
0
    def setup_method(self, tmpdir):
        self.path = os.path.dirname(os.path.abspath(__file__))
        self.tmpdir = tmpdir.strpath

        theoretical = np.loadtxt(os.path.join(self.path, 'gauss_data.txt'))
        xvals, yvals, evals = np.hsplit(theoretical, 3)
        xvals = xvals.flatten()
        yvals = yvals.flatten()
        evals = evals.flatten()

        # these best weighted values and uncertainties obtained with Igor
        self.best_weighted = [-0.00246095, 19.5299, -8.28446e-2, 1.24692]

        self.best_weighted_errors = [0.0220313708486, 1.12879436221,
                                     0.0447659158681, 0.0412022938883]

        self.best_weighted_chisqr = 77.6040960351

        self.best_unweighted = [-0.10584111872702096, 19.240347049328989,
                                0.0092623066070940396, 1.501362314145845]

        self.best_unweighted_errors = [0.34246565477, 0.689820935208,
                                       0.0411243173041, 0.0693429375282]

        self.best_unweighted_chisqr = 497.102084956

        self.p0 = np.array([0.1, 20., 0.1, 0.1])
        self.names = ['bkg', 'A', 'x0', 'width']
        self.bounds = [(-1, 1), (0, 30), (-5., 5.), (0.001, 2)]

        self.params = Parameters(name="gauss_params")
        for p, name, bound in zip(self.p0, self.names, self.bounds):
            param = Parameter(p, name=name)
            param.range(*bound)
            param.vary = True
            self.params.append(param)

        self.model = Model(self.params, fitfunc=gauss)
        self.data = Data1D((xvals, yvals, evals))
        self.objective = Objective(self.model, self.data)
        return 0
示例#6
0
    def __init__(self, value, name=''):
        self.name = name

        self.imag = Parameter(0, name='%s - isld' % name)
        if isinstance(value, numbers.Real):
            self.real = Parameter(value.real, name='%s - sld' % name)
        elif isinstance(value, numbers.Complex):
            self.real = Parameter(value.real, name='%s - sld' % name)
            self.imag = Parameter(value.imag, name='%s - isld' % name)
        elif isinstance(value, SLD):
            self.real = value.real
            self.imag = value.imag
        elif isinstance(value, Parameter):
            self.real = value
        elif (hasattr(value, '__len__') and isinstance(value[0], Parameter)
              and isinstance(value[1], Parameter)):
            self.real = value[0]
            self.imag = value[1]

        self._parameters = Parameters(name=name)
        self._parameters.extend([self.real, self.imag])
示例#7
0
    def structure(self, structure):
        self._structure = structure
        p = Parameters(name='instrument parameters')
        p.extend([self.scale, self.bkg, self.dq])

        self._parameters = Parameters(name=self.name)
        self._parameters.extend([p, structure.parameters])
示例#8
0
    def parameters(self):
        p = Parameters(name=self.name)
        p.extend([
            self.apm, self.b_heads_real, self.b_heads_imag, self.vm_heads,
            self.thickness_heads, self.b_tails_real, self.b_tails_imag,
            self.vm_tails, self.thickness_tails, self.rough_head_tail,
            self.rough_preceding_mono
        ])
        if self.head_solvent is not None:
            p.append(self.head_solvent.parameters)
        if self.tail_solvent is not None:
            p.append(self.tail_solvent.parameters)

        return p
示例#9
0
    def structure(self, structure):
        self._structure = structure
        p = Parameters(name="instrument parameters")
        p.extend([self.wav, self.delOffset])

        self._parameters = Parameters(name=self.name)
        self._parameters.extend([p, structure.parameters])
示例#10
0
    def structure(self, structure):
        self._structure = structure
        p = Parameters(name="instrument parameters")
        p.extend([self.scale, self.bkg, self.dq, self.q_offset])

        self._parameters = Parameters(name=self.name)
        self._parameters.extend([p, structure.parameters])
示例#11
0
 def parameters(self):
     """
     Returns
     -------
     Parameter, array_like
         An array of the parameters in the fitting.
     """
     p = Parameters(name=self.name)
     p.extend([
         self.vol[0],
         self.vol[1],
         self.realb[0],
         self.realb[1],
         self.imagb[0],
         self.imagb[1],
         self.d[0],
         self.d[1],
         self.phi[0],
         self.phi[1],
         self.sigma,
     ])
     return p
示例#12
0
    def test_or(self):
        # concatenation of Parameters
        # Parameters with Parameter
        c = self.m | self.b
        assert_equal(len(c), 3)
        assert_equal(len(c.flattened()), 3)
        assert_(c.flattened()[1] is self.b)
        assert_(c.flattened()[2] is self.b)

        # Parameters with Parameters
        c = Parameters(name='c')
        d = c | self.m
        assert_(d.name == 'c')
示例#13
0
    def parameters(self):
        """
        Parameters for the model.

        Returns:
            (refnx.analysis.Parameters) Model parameters.
        """
        para = Parameters(name=self.name)
        para.extend([
            self.b_real_h,
            self.b_imag_h,
            self.b_real_t,
            self.b_imag_t,
            self.thick_h,
            self.thick_t,
            self.mol_vol_h,
            self.mol_vol_t,
            self.rough_h_t,
            self.rough_t_a,
            self.phi_h,
            self.phi_t,
        ])
        return para
示例#14
0
    def test_ior(self):
        # concatenation of Parameters
        # Parameters with Parameter
        c = Parameters(name='c')
        c |= self.b
        assert_equal(len(c), 1)
        assert_equal(len(c.flattened()), 1)
        assert_(c.flattened()[0] is self.b)

        # Parameters with Parameters
        c = Parameters(name='c')
        c |= self.m
        assert_(c.name == 'c')
        assert_equal(len(c), 1)
        assert_equal(len(c.flattened()), 2)
        assert_(c.flattened()[1] is self.b)
示例#15
0
    def test_model_subclass(self):
        class Line(Model):
            def __init__(self, parameters):
                super(Line, self).__init__(parameters)

            def model(self, x, p=None, x_err=None):
                if p is not None:
                    self._parameters = p
                a, b = self._parameters
                return a.value + x * b.value

        a = Parameter(1.1)
        b = Parameter(2.2)
        p = Parameters([a, b])
        Line(p)
示例#16
0
    def parameters(self):
        r"""
        :class:`refnx.analysis.Parameters`, all the parameters associated with
        this structure.

        """
        p = Parameters(name='Stack - {0}'.format(self.name))
        p.append(self.repeats)
        p.extend([component.parameters for component in self.components])
        return p
示例#17
0
    def test_or(self):
        # concatenation of Parameters
        # Parameters with Parameter
        c = self.m | self.b
        assert_equal(len(c), 3)
        assert_equal(len(c.flattened()), 3)
        assert_(c.flattened()[1] is self.b)
        assert_(c.flattened()[2] is self.b)

        # Parameters with Parameters
        c = Parameters(name="c")
        d = c | self.m
        assert_(d.name == "c")

        # c and d should not be the same object
        assert c is not d
示例#18
0
    def parameters(self):
        r"""
        :class:`refnx.analysis.Parameters`, all the parameters associated with
        this structure.

        """
        p = Parameters(name='Structure - {0}'.format(self.name))
        p.extend([component.parameters for component in self.components])
        if self._solvent is not None:
            p.append(self.solvent.parameters)
        return p
示例#19
0
    def parameters(self):
        r"""
        :class:`refnx.analysis.Parameters` - parameters associated with this
        model.

        """
        p = Parameters(name='instrument parameters')
        p.extend([self.scales, self.bkg, self.dq])

        self._parameters = Parameters(name=self.name)
        self._parameters.append([p])
        self._parameters.extend(
            [structure.parameters for structure in self._structures])
        return self._parameters
示例#20
0
 def parameters(self):
     p = Parameters(name=self.name)
     p.extend([self.thick])
     # p.extend(self.sld.parameters)
     p.extend([self.rough, self.vol_protein, self.vfsolv])
     # p.name = self.name
     # p.extend([self.vol_protein,
     # self.PLratio])#,self.solventSLD
     # if isinstance(solventSLD, SLD):
     #     p.extend([self.solventSLD.parameters])
     # elif isinstance(solventSLD, complex):
     #     self.solventSLD = SLD(solventSLD)
     # else:
     #     p.extend([self.solventSLD
     return p
示例#21
0
    def test_logp(self):
        self.p[0].range(0, 10)
        assert_almost_equal(self.objective.logp(), np.log(0.1))

        # logp should set parameters
        self.objective.logp([8, 2])
        assert_equal(np.array(self.objective.parameters), [8, 2])

        # if we supply a value outside the range it should return -inf
        assert_equal(self.objective.logp([-1, 2]), -np.inf)

        # are auxiliary parameters included in log?
        assert_almost_equal(self.objective.logp([8, 2]), np.log(0.1))
        p = Parameter(2.0, bounds=(1.0, 3.0))
        self.objective.auxiliary_params = Parameters([p])
        assert len(self.objective.varying_parameters()) == 2
        assert_equal(self.objective.logp(), np.log(0.1))
        assert p in self.objective.parameters.flattened()
        p.vary = True
        assert len(self.objective.varying_parameters()) == 3
        assert_equal(self.objective.logp(), np.log(0.1) + np.log(0.5))
        assert p in self.objective.varying_parameters().flattened()
示例#22
0
文件: spline.py 项目: llimeht/refnx
class Spline(Component):
    """
    Freeform modelling of the real part of an SLD profile using spline
    interpolation.

    Parameters
    ----------
    extent : float or Parameter
        Total extent of spline region
    vs : Sequence of float/Parameter
        the real part of the SLD values of each of the knots.
    dz : Sequence of float/Parameter
        the lateral offset between successive knots.
    left : refnx.reflect.Component
        The Component to the left of this Spline region.
    right : refnx.reflect.Component
        The Component to the right of this Spline region.
    solvent : refnx.reflect.SLD
        An SLD instance representing the solvent
    name : str
        Name of component
    interpolator : scipy.interpolate Univariate Interpolator, optional
        Which scipy.interpolate Univariate Interpolator to use.
    zgrad : bool, optional
        If true then extra control knots are placed outside this spline
        with the same SLD as the materials on the left and right. With a
        monotonic interpolator this guarantees that the gradient is zero
        at either end of the interval.
    microslab_max_thickness : float
        Maximum size of the microslabs approximating the spline.

    Notes
    -----
    This spline component only generates the real part of the SLD (thereby
    assuming that the imaginary part is negligible).
    The sequence dz are the lateral offsets of the knots normalised to a
    unit interval [0, 1]. The reason for using lateral offsets is
    so that the knots are monotonically increasing in location. When each
    dz offset is turned into a Parameter it is given bounds in [0, 1].
    Thus with an extent of 500, and dz = [0.1, 0.2, 0.2], the knots will be
    at [0, 50, 150, 250, 500]. Notice that there are two extra knots for
    the start and end of the interval (disregarding the `zgrad` control
    knots). If ``np.sum(dz) > 1``, then the knot spacings are normalised to
    1. e.g. dz of [0.1, 0.2, 0.9] would result in knots (in the normalised
    interval) of [0, 0.0833, 0.25, 1, 1].
    If `vs` is monotonic then the output spline will be monotonic. If `vs`
    is not monotonic then there may be regions of the spline larger or
    smaller than `left` or `right`.
    The slab representation of this component are approximated using a
    'microslab' representation of spline. The max thickness of each
    microslab is `microslab_max_thickness`.
    """
    def __init__(self,
                 extent,
                 vs,
                 dz,
                 left,
                 right,
                 solvent,
                 name='',
                 interpolator=Pchip,
                 zgrad=True,
                 microslab_max_thickness=1):
        self.name = name
        self.left_slab = left
        self.right_slab = right
        self.solvent = solvent
        self.microslab_max_thickness = microslab_max_thickness

        self.extent = (possibly_create_parameter(extent,
                                                 name='%s - spline extent' %
                                                 name))

        self.dz = Parameters(name='dz - spline')
        for i, z in enumerate(dz):
            p = possibly_create_parameter(z,
                                          name='%s - spline dz[%d]' %
                                          (name, i))
            p.range(0, 1)
            self.dz.append(p)

        self.vs = Parameters(name='vs - spline')
        for i, v in enumerate(vs):
            p = possibly_create_parameter(v,
                                          name='%s - spline vs[%d]' %
                                          (name, i))
            self.vs.append(p)

        if len(self.vs) != len(self.dz):
            raise ValueError("dz and vs must have same number of entries")

        self.zgrad = zgrad
        self.interpolator = interpolator

        self.__cached_interpolator = {
            'zeds': np.array([]),
            'vs': np.array([]),
            'interp': None,
            'extent': -1
        }

    def _interpolator(self):
        dz = np.array(self.dz)
        zeds = np.cumsum(dz)

        # if dz's sum to more than 1, then normalise to unit interval.
        if zeds[-1] > 1:
            zeds /= zeds[-1]
            zeds = np.clip(zeds, 0, 1)

        vs = np.array(self.vs)

        left_sld = Structure.overall_sld(
            np.atleast_2d(self.left_slab.slabs[-1]), self.solvent)[..., 1]

        right_sld = Structure.overall_sld(
            np.atleast_2d(self.right_slab.slabs[0]), self.solvent)[..., 1]

        if self.zgrad:
            zeds = np.concatenate([[-1.1, 0 - EPS], zeds, [1 + EPS, 2.1]])
            vs = np.concatenate([left_sld, left_sld, vs, right_sld, right_sld])
        else:
            zeds = np.concatenate([[0 - EPS], zeds, [1 + EPS]])
            vs = np.concatenate([left_sld, vs, right_sld])

        # cache the interpolator
        cache_zeds = self.__cached_interpolator['zeds']
        cache_vs = self.__cached_interpolator['vs']
        cache_extent = self.__cached_interpolator['extent']

        # you don't need to recreate the interpolator
        if (np.array_equal(zeds, cache_zeds) and np.array_equal(vs, cache_vs)
                and np.equal(self.extent, cache_extent)):
            return self.__cached_interpolator['interp']
        else:
            self.__cached_interpolator['zeds'] = zeds
            self.__cached_interpolator['vs'] = vs
            self.__cached_interpolator['extent'] = float(self.extent)

        # TODO make vfp zero for z > self.extent
        interpolator = self.interpolator(zeds, vs)
        self.__cached_interpolator['interp'] = interpolator
        return interpolator

    def __call__(self, z):
        """
        Calculates the spline value at z

        Parameters
        ----------
        z : float
            Distance along spline

        Returns
        -------
        sld : float
            Real part of SLD
        """
        interpolator = self._interpolator()
        vs = interpolator(z / float(self.extent))
        return vs

    @property
    def parameters(self):
        p = Parameters(name=self.name)
        p.extend([
            self.extent, self.dz, self.vs, self.left_slab.parameters,
            self.right_slab.parameters, self.solvent.parameters
        ])
        return p

    def logp(self):
        return 0

    @property
    def slabs(self):
        num_slabs = np.ceil(float(self.extent) / self.microslab_max_thickness)
        slab_thick = float(self.extent / num_slabs)
        slabs = np.zeros((int(num_slabs), 5))
        slabs[:, 0] = slab_thick

        # give last slab a miniscule roughness so it doesn't get contracted
        slabs[-1:, 3] = 0.5

        dist = np.cumsum(slabs[..., 0]) - 0.5 * slab_thick
        slabs[:, 1] = self(dist)

        return slabs
示例#23
0
 def setup_method(self):
     self.a = Parameter(1, name='a')
     self.b = Parameter(2, name='b')
     self.m = Parameters()
     self.m.append(self.a)
     self.m.append(self.b)
示例#24
0
class TestParameters(object):
    def setup_method(self):
        self.a = Parameter(1, name='a')
        self.b = Parameter(2, name='b')
        self.m = Parameters()
        self.m.append(self.a)
        self.m.append(self.b)

    def test_retrieve_by_name(self):
        p = self.m['a']
        assert_(p is self.a)

        # or by index
        p = self.m[0]
        assert_(p is self.a)

    def test_repr(self):
        p = Parameter(value=5, vary=False, name='test')
        g = Parameters(name='name')
        f = Parameters()
        f.append(p)
        f.append(g)

        q = eval(repr(f))
        assert (q.name is None)
        assert_equal(q[0].value, 5)
        assert (q[0].vary is False)
        assert (isinstance(q[1], Parameters))

    def test_set_by_name(self):
        c = Parameter(3.)
        self.m['a'] = c
        assert_(self.m[0] is c)

        # can't set an entry by name, if there isn't an existing name in this
        # Parameters instance.
        from pytest import raises
        with raises(ValueError):
            self.m['abc'] = c

    def test_parameters(self):
        # we've added two parameters
        self.a.vary = True
        self.b.vary = True

        assert_equal(len(self.m.flattened()), 2)

        # the two entries should just be the objects
        assert_(self.m.varying_parameters()[0] is self.a)
        assert_(self.m.varying_parameters()[1] is self.b)

    def test_varying_parameters(self):
        # even though we've added a twice we should still only see 2
        # varying parameters
        self.a.vary = True
        self.b.vary = True
        p = self.a | self.b | self.a
        assert_equal(len(p.varying_parameters()), 2)

    def test_pickle_parameters(self):
        # need to check that Parameters can be pickled/unpickle
        pkl = pickle.dumps(self.m)
        pickle.loads(pkl)

    def test_or(self):
        # concatenation of Parameters
        # Parameters with Parameter
        c = self.m | self.b
        assert_equal(len(c), 3)
        assert_equal(len(c.flattened()), 3)
        assert_(c.flattened()[1] is self.b)
        assert_(c.flattened()[2] is self.b)

        # Parameters with Parameters
        c = Parameters(name='c')
        d = c | self.m
        assert_(d.name == 'c')

    def test_ior(self):
        # concatenation of Parameters
        # Parameters with Parameter
        c = Parameters(name='c')
        c |= self.b
        assert_equal(len(c), 1)
        assert_equal(len(c.flattened()), 1)
        assert_(c.flattened()[0] is self.b)

        # Parameters with Parameters
        c = Parameters(name='c')
        c |= self.m
        assert_(c.name == 'c')
        assert_equal(len(c), 1)
        assert_equal(len(c.flattened()), 2)
        assert_(c.flattened()[1] is self.b)
示例#25
0
class SLD(object):
    """
    Object representing freely varying SLD of a material

    Parameters
    ----------
    value : float, complex, Parameter, Parameters
        Scattering length density of a material.
        Units (10**-6 Angstrom**-2)
    name : str, optional
        Name of material.

    Notes
    -----
    An SLD object can be used to create a Slab:

    >>> # an SLD object representing Silicon Dioxide
    >>> sio2 = SLD(3.47, name='SiO2')
    >>> # create a Slab of SiO2 20 A in thickness, with a 3 A roughness
    >>> sio2_layer = sio2(20, 3)

    The SLD object can also be made from a complex number, or from Parameters

    >>> sio2 = SLD(3.47+0.01j)
    >>> re = Parameter(3.47)
    >>> im = Parameter(0.01)
    >>> sio2 = SLD(re)
    >>> sio2 = SLD([re, im])
    """
    def __init__(self, value, name=''):
        self.name = name

        self.imag = Parameter(0, name='%s - isld' % name)
        if isinstance(value, numbers.Real):
            self.real = Parameter(value.real, name='%s - sld' % name)
        elif isinstance(value, numbers.Complex):
            self.real = Parameter(value.real, name='%s - sld' % name)
            self.imag = Parameter(value.imag, name='%s - isld' % name)
        elif isinstance(value, SLD):
            self.real = value.real
            self.imag = value.imag
        elif isinstance(value, Parameter):
            self.real = value
        elif (hasattr(value, '__len__') and isinstance(value[0], Parameter)
              and isinstance(value[1], Parameter)):
            self.real = value[0]
            self.imag = value[1]

        self._parameters = Parameters(name=name)
        self._parameters.extend([self.real, self.imag])

    def __repr__(self):
        return ("SLD([{real!r}, {imag!r}],"
                " name={name!r})".format(**self.__dict__))

    def __str__(self):
        sld = complex(self.real.value, self.imag.value)
        return 'SLD = {0} x10**-6 Å**-2'.format(sld)

    def __complex__(self):
        return complex(self.real.value, self.imag.value)

    def __call__(self, thick=0, rough=0):
        """
        Create a :class:`Slab`.

        Parameters
        ----------
        thick: refnx.analysis.Parameter or float
            Thickness of slab in Angstrom
        rough: refnx.analysis.Parameter or float
            Roughness of slab in Angstrom

        Returns
        -------
        slab : refnx.reflect.Slab
            The newly made Slab.

        Example
        --------

        >>> # an SLD object representing Silicon Dioxide
        >>> sio2 = SLD(3.47, name='SiO2')
        >>> # create a Slab of SiO2 20 A in thickness, with a 3 A roughness
        >>> sio2_layer = sio2(20, 3)

        """
        return Slab(thick, self, rough, name=self.name)

    def __or__(self, other):
        # c = self | other
        slab = self()
        return slab | other

    @property
    def parameters(self):
        """
        :class:`refnx.analysis.Parameters` associated with this component

        """
        self._parameters.name = self.name
        return self._parameters
示例#26
0
 def parameters(self):
     p = Parameters(name=self.name)
     p.extend([self.gamma, self.dz, self.vf, self.polymer_sld.parameters])
     p.extend([slab.parameters for slab in self.left_slabs])
     p.extend([slab.parameters for slab in self.right_slabs])
     return p
示例#27
0
class FreeformVFPextent(Component):
    """
    Freeform volume fraction profiles for a polymer brush. The extent of the
    brush is used as a fitting parameter.

    Parameters
    ----------
    extent : Parameter or float
        The total extent of the spline region
    vf: sequence of Parameter or float
        Absolute volume fraction at each of the spline knots
    dz : sequence of Parameter or float
        Separation of successive knots, expressed as a fraction of
        `extent`.
    polymer_sld : SLD or float
        SLD of polymer
    name : str
        Name of component
    gamma : Parameter
        The dry adsorbed amount of polymer
    left_slabs : sequence of Slab
        Slabs to the left of the spline
    right_slabs : sequence of Slab
        Slabs to the right of the spline
    interpolator : scipy interpolator
        The interpolator for the spline
    zgrad : bool, optional
        Set to `True` to force the gradient of the volume fraction to zero
        at each end of the spline.
    monotonic_penalty : number, optional
        The penalty added to the log-probability to penalise non-monotonic
        spline knots.
        Set to a very large number (e.g. 1e250) to enforce a monotonically
        decreasing volume fraction spline.
        Set to a very negative number (e.g. -1e250) to enforce a
        monotonically increasing volume fraction spline.
        Set to zero (default) to apply no penalty.
        Note - the absolute value of `monotonic_penalty` is subtracted from
        the overall log-probability, the sign is only used to determine the
        direction that is requested.
    microslab_max_thickness : float
        Thickness of microslicing of spline for reflectivity calculation.

    """
    def __init__(self,
                 extent,
                 vf,
                 dz,
                 polymer_sld,
                 name='',
                 gamma=None,
                 left_slabs=(),
                 right_slabs=(),
                 interpolator=Pchip,
                 zgrad=True,
                 monotonic_penalty=0,
                 microslab_max_thickness=1):

        self.name = name

        if isinstance(polymer_sld, SLD):
            self.polymer_sld = polymer_sld
        else:
            self.polymer_sld = SLD(polymer_sld)

        # left and right slabs are other areas where the same polymer can
        # reside
        self.left_slabs = [
            slab for slab in left_slabs if isinstance(slab, Slab)
        ]
        self.right_slabs = [
            slab for slab in right_slabs if isinstance(slab, Slab)
        ]

        self.microslab_max_thickness = microslab_max_thickness

        self.extent = (possibly_create_parameter(extent,
                                                 name='%s - spline extent' %
                                                 name))

        # dz are the spatial spacings of the spline knots
        self.dz = Parameters(name='dz - spline')
        for i, z in enumerate(dz):
            p = possibly_create_parameter(z,
                                          name='%s - spline dz[%d]' %
                                          (name, i))
            p.range(0, 1)
            self.dz.append(p)

        # vf are the volume fraction values of each of the spline knots
        self.vf = Parameters(name='vf - spline')
        for i, v in enumerate(vf):
            p = possibly_create_parameter(v,
                                          name='%s - spline vf[%d]' %
                                          (name, i))
            p.range(0, 1)
            self.vf.append(p)

        if len(self.vf) != len(self.dz):
            raise ValueError("dz and vs must have same number of entries")

        self.monotonic_penalty = monotonic_penalty
        self.zgrad = zgrad
        self.interpolator = interpolator

        if gamma is not None:
            self.gamma = possibly_create_parameter(gamma, 'gamma')
        else:
            self.gamma = Parameter(0, 'gamma')

        self.__cached_interpolator = {
            'zeds': np.array([]),
            'vf': np.array([]),
            'interp': None,
            'extent': -1
        }

    def _vfp_interpolator(self):
        """
        The spline based volume fraction profile interpolator

        Returns
        -------
        interpolator : scipy.interpolate.Interpolator
        """
        dz = np.array(self.dz)
        zeds = np.cumsum(dz)

        # if dz's sum to more than 1, then normalise to unit interval.
        # clipped to 0 and 1 because we pad on the LHS, RHS later
        # and we need the array to be monotonically increasing
        if zeds[-1] > 1:
            zeds /= zeds[-1]
            zeds = np.clip(zeds, 0, 1)

        vf = np.array(self.vf)

        # use the volume fraction of the last left_slab as the initial vf of
        # the spline
        if len(self.left_slabs):
            left_end = 1 - self.left_slabs[-1].vfsolv.value
        else:
            left_end = vf[0]

        # in contrast use a vf = 0 for the last vf of
        # the spline, unless right_slabs is specified
        if len(self.right_slabs):
            right_end = 1 - self.right_slabs[0].vfsolv.value
        else:
            right_end = 0

        # do you require zero gradient at either end of the spline?
        if self.zgrad:
            zeds = np.concatenate([[-1.1, 0 - EPS], zeds, [1 + EPS, 2.1]])
            vf = np.concatenate([[left_end, left_end], vf,
                                 [right_end, right_end]])
        else:
            zeds = np.concatenate([[0 - EPS], zeds, [1 + EPS]])
            vf = np.concatenate([[left_end], vf, [right_end]])

        # cache the interpolator
        cache_zeds = self.__cached_interpolator['zeds']
        cache_vf = self.__cached_interpolator['vf']
        cache_extent = self.__cached_interpolator['extent']

        # you don't need to recreate the interpolator
        if (np.array_equal(zeds, cache_zeds) and np.array_equal(vf, cache_vf)
                and np.equal(self.extent, cache_extent)):
            return self.__cached_interpolator['interp']
        else:
            self.__cached_interpolator['zeds'] = zeds
            self.__cached_interpolator['vf'] = vf
            self.__cached_interpolator['extent'] = float(self.extent)

        # TODO make vfp zero for z > self.extent
        interpolator = self.interpolator(zeds, vf)
        self.__cached_interpolator['interp'] = interpolator
        return interpolator

    def __call__(self, z):
        """
        Calculates the volume fraction profile of the spline

        Parameters
        ----------
        z : float
            Distance along vfp

        Returns
        -------
        vfp : float
            Volume fraction
        """
        interpolator = self._vfp_interpolator()
        vfp = interpolator(z / float(self.extent))
        return vfp

    def moment(self, moment=1):
        """
        Calculates the n'th moment of the profile

        Parameters
        ----------
        moment : int
            order of moment to be calculated

        Returns
        -------
        moment : float
            n'th moment
        """
        zed, profile = self.profile()
        profile *= zed**moment
        val = simps(profile, zed)
        area = self.profile_area()
        return val / area

    @property
    def parameters(self):
        p = Parameters(name=self.name)
        p.extend([
            self.extent, self.dz, self.vf, self.polymer_sld.parameters,
            self.gamma
        ])
        p.extend([slab.parameters for slab in self.left_slabs])
        p.extend([slab.parameters for slab in self.right_slabs])
        return p

    def logp(self):
        logp = 0
        # you're trying to enforce monotonicity
        if self.monotonic_penalty:
            monotonic, direction = _is_monotonic(self.vf)

            # if left slab has a lower vf than first spline then profile is
            # not monotonic
            if self.vf[0] > (1 - self.left_slabs[-1].vfsolv):
                monotonic = False

            if not monotonic:
                # you're not monotonic so you have to have the penalty
                # anyway
                logp -= np.abs(self.monotonic_penalty)
            else:
                # you are monotonic, but might be in the wrong direction
                if self.monotonic_penalty > 0 and direction > 0:
                    # positive penalty means you want decreasing
                    logp -= np.abs(self.monotonic_penalty)
                elif self.monotonic_penalty < 0 and direction < 0:
                    # negative penalty means you want increasing
                    logp -= np.abs(self.monotonic_penalty)

        # log-probability for area under profile
        logp += self.gamma.logp(self.profile_area())
        return logp

    def profile_area(self):
        """
        Calculates integrated area of volume fraction profile

        Returns
        -------
        area: integrated area of volume fraction profile
        """
        interpolator = self._vfp_interpolator()
        area = interpolator.integrate(0, 1) * float(self.extent)

        for slab in self.left_slabs:
            _slabs = slab.slabs()
            area += _slabs[0, 0] * (1 - _slabs[0, 4])
        for slab in self.right_slabs:
            _slabs = slab.slabs()
            area += _slabs[0, 0] * (1 - _slabs[0, 4])

        return area

    def slabs(self, structure=None):
        num_slabs = np.ceil(float(self.extent) / self.microslab_max_thickness)
        slab_thick = float(self.extent / num_slabs)
        slabs = np.zeros((int(num_slabs), 5))
        slabs[:, 0] = slab_thick

        # give last slab a miniscule roughness so it doesn't get contracted
        slabs[-1:, 3] = 0.5

        dist = np.cumsum(slabs[..., 0]) - 0.5 * slab_thick
        slabs[:, 1] = self.polymer_sld.real.value
        slabs[:, 2] = self.polymer_sld.imag.value
        slabs[:, 4] = 1 - self(dist)

        return slabs

    def profile(self, extra=False):
        """
        Calculates the volume fraction profile

        Returns
        -------
        z, vfp : np.ndarray
            Distance from the interface, volume fraction profile
        """
        s = Structure()
        s |= SLD(0)

        m = SLD(1.)

        for i, slab in enumerate(self.left_slabs):
            layer = m(slab.thick.value, slab.rough.value)
            if not i:
                layer.rough.value = 0
            layer.vfsolv.value = slab.vfsolv.value
            s |= layer

        polymer_slabs = self.slabs()
        offset = np.sum(s.slabs()[:, 0])

        for i in range(np.size(polymer_slabs, 0)):
            layer = m(polymer_slabs[i, 0], polymer_slabs[i, 3])
            layer.vfsolv.value = polymer_slabs[i, -1]
            s |= layer

        for i, slab in enumerate(self.right_slabs):
            layer = m(slab.thick.value, slab.rough.value)
            layer.vfsolv.value = 1 - slab.vfsolv.value
            s |= layer

        s |= SLD(0, 0)

        # now calculate the VFP.
        total_thickness = np.sum(s.slabs()[:, 0])
        zed = np.linspace(0, total_thickness, total_thickness + 1)
        # SLD profile puts a very small roughness on the interfaces with zero
        # roughness.
        zed[0] = 0.01
        z, s = s.sld_profile(z=zed)
        s[0] = s[1]

        # perhaps you'd like to plot the knot locations
        zeds = np.cumsum(self.dz)
        if np.sum(self.dz) > 1:
            zeds /= np.sum(self.dz)
            zeds = np.clip(zeds, 0, 1)

        zed_knots = zeds * float(self.extent) + offset

        if extra:
            return z, s, zed_knots, np.array(self.vf)
        else:
            return z, s
示例#28
0
    def __init__(self,
                 gamma,
                 vf,
                 dz,
                 polymer_sld,
                 name='',
                 left_slabs=(),
                 right_slabs=(),
                 interpolator=Pchip,
                 zgrad=True,
                 monotonic_penalty=0,
                 microslab_max_thickness=1):
        self.name = name

        self.gamma = (possibly_create_parameter(gamma,
                                                name='%s - adsorbed amount' %
                                                name))

        if isinstance(polymer_sld, SLD):
            self.polymer_sld = polymer_sld
        else:
            self.polymer_sld = SLD(polymer_sld)

        # left and right slabs are other areas where the same polymer can
        # reside
        self.left_slabs = [
            slab for slab in left_slabs if isinstance(slab, Slab)
        ]
        self.right_slabs = [
            slab for slab in right_slabs if isinstance(slab, Slab)
        ]

        self.microslab_max_thickness = microslab_max_thickness

        # dz are the spatial spacings of the spline knots
        self.dz = Parameters(name='dz - spline')
        for i, z in enumerate(dz):
            p = possibly_create_parameter(z,
                                          name='%s - spline dz[%d]' %
                                          (name, i))
            p.range(0, 1)
            self.dz.append(p)

        # vf are the volume fraction values of each of the spline knots
        self.vf = Parameters(name='vf - spline')
        for i, v in enumerate(vf):
            p = possibly_create_parameter(v,
                                          name='%s - spline vf[%d]' %
                                          (name, i))
            p.range(0, 2.)
            self.vf.append(p)

        if len(self.vf) != len(self.dz):
            raise ValueError("dz and vs must have same number of entries")

        self.monotonic_penalty = monotonic_penalty
        self.zgrad = zgrad
        self.interpolator = interpolator

        self.__cached_interpolator = {
            'zeds': np.array([]),
            'vf': np.array([]),
            'interp': None
        }
示例#29
0
 def parameters(self):
     p = Parameters(name=self.name)
     p.extend([self.extent, self.decay, self.rough])
     return p
示例#30
0
class RI(Scatterer):
    """
    Object representing a materials wavelength-dependent refractive index.

    A concern is how it needs to be linked to a model. This is to get around
    a major rewrite of refnx, but isn't the most elegant system.

    Another issue is that optical parameters are supplied in units of micro
    meters ('cause thats what seems to be used in refractive index repos and
    cauchy models), the wavelength of the incident radiation is supplied in
    nanometers (thats typical) and the fitting is done in angstroms. Very
    unpleasent.

    Parameters
    ----------
    value : tuple, string
        Scattering length density of a material.
        Units (10**-6 Angstrom**-2)
    A : float or parameter
        Cauchy parameter A. If not none RI will use the cauchy model.
        Default None.
    B : float or parameter
        Cauchy parameter B in um^2. Default 0.
    C : float or parameter
        Cauchy parameter C in um^4. Default 0.
    name : str, optional
        Name of material.

    Notes
    -----
    An SLD object can be used to create a Slab:

    >>> # an SLD object representing Silicon Dioxide
    >>> sio2 = SLD(3.47, name='SiO2')
    >>> # create a Slab of SiO2 20 A in thickness, with a 3 A roughness
    >>> sio2_layer = sio2(20, 3)

    The SLD object can also be made from a complex number, or from Parameters

    >>> sio2 = SLD(3.47+0.01j)
    >>> re = Parameter(3.47)
    >>> im = Parameter(0.01)
    >>> sio2 = SLD(re)
    >>> sio2 = SLD([re, im])
    """
    def __init__(self, value=None, A=None, B=0, C=0, name=""):
        if (type(value) is str
                and name == ""):  # if there is no name get it from the path
            name = os.path.basename(value).split(".")[0]

        super(RI, self).__init__(name=name)

        assert np.logical_xor(
            value is None,
            A is None), "Supply either values or cauchy parameters"

        if value is not None:
            if type(value) is str:

                try:
                    self._wav, self._RI, self._EC = np.loadtxt(
                        value, skiprows=1, delimiter=",", encoding="utf8").T
                except ValueError:
                    self._wav, self._RI = np.loadtxt(
                        value,
                        skiprows=1,
                        delimiter=",",
                        usecols=[0, 1],
                        encoding="utf8",
                    ).T
                    self._EC = np.zeros_like(self._wav)

            elif len(value) == 2:
                self._RI, self._EC = value
                self._wav = None
            elif len(value) == 3:
                self._wav, self._RI, self._EC = value
            else:
                raise TypeError("format not recognised")
            # convert wavelength from um to nm
            self._wav = self._wav * 1000
        else:
            self._wav = None
            self._RI = None
            self._EC = None

        self.model = None
        self.set_wav = None
        self._default_wav = 658
        self._parameters = Parameters(name=name)

        if A is not None:
            self.A = possibly_create_parameter(A, name=f"{name} - cauchy A")
            self.B = possibly_create_parameter(B, name=f"{name} - cauchy B")
            self.C = possibly_create_parameter(C, name=f"{name} - cauchy C")
            self._parameters.extend([self.A, self.B, self.C])

        # The RI needs access to the model to calculate the refractive index.
        # Can't think of a better way of doing this
        # reflect_modelSE is going to auto-link this when its called.

    @property
    def real(self):
        """Refractive index, n."""

        if self.model is not None:
            wavelength = self.model.wav
        elif self.set_wav is not None:
            wavelength = self.set_wav
        else:
            wavelength = self._default_wav
            warnings.warn("Using default wavelength (model not linked)")

        if np.any(self._wav):
            # TODO - raise a warning if the wavelength supplied is outside the
            # wavelength range covered by the data file.

            return Parameter(np.interp(wavelength, self._wav, self._RI))

        elif self.A is not None:
            return Parameter(self.A.value + (self.B.value * 1000**2) /
                             (wavelength**2) + (self.C.value**1000**4) /
                             (wavelength**4))
        else:
            return Parameter(value=self._RI)

    @property
    def imag(self, wavelength=None):
        """Extinction coefficent, k."""

        if self.model is not None:
            wavelength = self.model.wav
        elif self.set_wav is not None:
            wavelength = self.set_wav
        else:
            wavelength = self._default_wav
            warnings.warn("Using default wavelength (model not linked)")

        if np.any(self._wav):
            # TODO - raise a warning if the wavelength supplied is outside the
            # wavelength range covered by the data file.

            return Parameter(np.interp(wavelength, self._wav, self._EC))
        elif self.A is not None:
            return Parameter(0)
        else:
            return Parameter(value=self._EC)

    @property
    def parameters(self):
        return self._parameters

    def __repr__(self):
        return str(f"n: {self.real.value}, k: {self.imag.value}")

    def __complex__(self):
        sldc = complex(self.real.value, self.imag.value)
        return sldc