Esempio n. 1
0
    def test_pymc3(self):
        # test objective logl against pymc3

        # don't run this test if pymc3 is not installed
        try:
            import pymc3 as pm
        except ImportError:
            return

        logl = self.objective.logl()

        from refnx.analysis import pymc_objective
        from refnx.analysis.objective import _to_pymc3_distribution

        mod = pymc_objective(self.objective)
        with mod:
            pymc_logl = mod.logp({
                'p0': self.p[0].value,
                'p1': self.p[1].value
            })

        assert_allclose(logl, pymc_logl)

        # now check some of the distributions
        with pm.Model():
            p = Parameter(1, bounds=(1, 10))
            d = _to_pymc3_distribution('a', p)
            assert_almost_equal(d.distribution.logp(2).eval(), p.logp(2))
            assert_(np.isneginf(d.distribution.logp(-1).eval()))

            q = Parameter(1, bounds=PDF(stats.uniform(1, 9)))
            d = _to_pymc3_distribution('b', q)
            assert_almost_equal(d.distribution.logp(2).eval(), q.logp(2))
            assert_(np.isneginf(d.distribution.logp(-1).eval()))

            p = Parameter(1, bounds=PDF(stats.uniform))
            d = _to_pymc3_distribution('c', p)
            assert_almost_equal(d.distribution.logp(0.5).eval(), p.logp(0.5))

            p = Parameter(1, bounds=PDF(stats.norm))
            d = _to_pymc3_distribution('d', p)
            assert_almost_equal(d.distribution.logp(2).eval(), p.logp(2))

            p = Parameter(1, bounds=PDF(stats.norm(1, 10)))
            d = _to_pymc3_distribution('e', p)
            assert_almost_equal(d.distribution.logp(2).eval(), p.logp(2))
Esempio n. 2
0
    def test_parameter_bounds(self):
        x = Parameter(4, bounds=Interval(-4, 4))
        assert_equal(x.logp(), uniform.logpdf(0, -4, 8))

        x.bounds = None
        assert_(isinstance(x._bounds, Interval))
        assert_equal(x.bounds.lb, -np.inf)
        assert_equal(x.bounds.ub, np.inf)
        assert_equal(x.logp(), 0)

        x.setp(bounds=norm(0, 1))
        assert_almost_equal(x.logp(1), norm.logpdf(1, 0, 1))

        # all created parameters were mistakenly being given the same
        # default bounds instance!
        x = Parameter(4)
        y = Parameter(5)
        assert_(id(x.bounds) != id(y.bounds))
Esempio n. 3
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