示例#1
0
    def loadParameters(self, filename):
        # load parameter definitions from file and add to list:
        parDict = dict()
        filename = makeAbsolutePath(filename)
        if not os.path.exists(filename):
            # trying one level up
            dirname, basename = os.path.split(filename)
            dirname = os.path.dirname(dirname)
            filename = os.path.join(dirname, basename)
        try:
            with open(filename, 'r') as jfile:
                logging.info(
                    "Loading parameters from file: '{}'".format(filename))
                parDict = json.load(jfile)
        except IOError:
            logging.error(
                "Could not load default parameter file '{fn}'!".format(
                    fn=filename))
        for pkey in parDict.keys():
            default = parDict[pkey].pop('default')
            unitClass = parDict[pkey].pop('unitClass', None)
            displayUnit = parDict[pkey].pop('displayUnit', None)
            unitInstance = self.pickUnit(unitClass, displayUnit)

            self.parameters.append(
                Parameter(pkey, default, unit=unitInstance, **parDict[pkey]))
            logging.debug('Parameter {} ingested'.format(pkey))
示例#2
0
class Sphere(SASModel):
    """Form factor of a sphere"""
    shortName = "Sphere"
    canSmear = True
    parameters = (
        FitParameter("radius",
                     NM.toSi(10.),
                     unit=NM,
                     displayName="Sphere radius",
                     valueRange=(0., numpy.inf),
                     activeRange=NM.toSi((1., 1000.)),
                     generator=RandomUniform,
                     decimals=9),
        Parameter("sld",
                  SLD(u'Å⁻²').toSi(1e-6),
                  unit=SLD(u'Å⁻²'),
                  displayName="scattering length density difference",
                  valueRange=(0., numpy.inf),
                  decimals=9),
    )

    def __init__(self):
        super(Sphere, self).__init__()
        self.radius.setActive(True)

    def surface(self):
        r"""Calculates the surface of a sphere defined by:

        :math:`s(r) = 4 \pi r^2`
        """
        return 4. * pi * self.radius() * self.radius()

    def volume(self):
        r"""Calculates the volume of a sphere defined by:

        :math:`v(r) = {4\pi \over 3} r^3`
        """
        result = (pi * 4. / 3.) * self.radius()**3
        return result

    def absVolume(self):
        r"""Calculates the volume of a sphere taking the scattering length
        density difference :math:`\Delta\rho` into account:

        :math:`v_{abs}(r, \Delta\rho) = v_{sph}(r) \cdot \Delta\rho^2`
        """
        return self.volume() * self.sld()**2

    def formfactor(self, dataset):
        r"""Calculates the form factor of a sphere defined by:

        :math:`F(q, r) = { 3 ~ sin(qr) - qr \cdot cos(qr) \over (qr)^3 }`
        """
        q = self.getQ(dataset)
        qr = q * self.radius()
        result = 3. * (sin(qr) - qr * cos(qr)) / (qr**3.)
        return result
示例#3
0
    def loadParams(self, fname = None):
        """
        writes the default definitions and bounds for the configuration
        parameters to self.parameters
        Can also be used to update existing parameters from supplied filename
        """
        with mcopen(fname, 'r') as jfile:
            logging.info('loading parameters from file: {}'.format(fname))
            parDict = json.load(jfile)

        if self.parameters is None:
            # create if it does not exist yet
            self.parameters = lambda: None

        # now we cast this information into the Parameter class:
        for kw in list(parDict.keys()):
            subDict = parDict[kw]
            name = kw
            value = subDict.pop("value", None)
            default = subDict.pop("default", None)
            if value is None and default is not None:
                value = default
            # determine parameter class:
            cls = subDict.pop("cls", None)
            if cls == "int":
                subDict.update(cls = ParameterNumerical)
            elif cls == "float":
                subDict.update(cls = ParameterFloat)
            elif cls == "bool": 
                subDict.update(cls = ParameterBoolean)
            elif cls == "str": 
                subDict.update(cls = ParameterString)
            else:
                logging.warning('parameter type {} for parameter {} not '
                                ' understood from {}'.format(cls, kw, fname))

            if kw in self.parameterNames:
                #value exists, should be updated with supplied kwargs
                self.set(kw,**subDict)
                logging.info('successfully updated parameter: {}'.format(kw))
            else:
                #logging.info('ingesting parameter {} with value {}'.format(name, value))
                temp = Parameter(name, value, **subDict)
                setattr(self.parameters,kw,temp)
                self.parameterNames.append(kw)
                logging.info('successfully ingested parameter: {}'.format(kw))
示例#4
0
class SphericalCoreShell(SASModel):
    r"""Form factor for a spherical core shell structure
    as defined in the SASfit manual (par. 3.1.4, Spherical Shell III).
    One modification is the ability to specify SLD for core, shell and
    solvent, identical to the notation used in the Core-shell ellipsoid.
    Compared wiht a SASfit-generated model (both with and without distribution)
    """
    shortName = "Core-Shell Sphere"
    parameters = (
        FitParameter("radius",
                     Length(u'nm').toSi(1.),
                     unit=Length(u'nm'),
                     displayName="Core Radius",
                     generator=RandomExponential,
                     valueRange=(0., numpy.inf),
                     activeRange=Length(u'nm').toSi((0.1, 1e3))),  # preset
        FitParameter("t",
                     Length(u'nm').toSi(1.),
                     unit=Length(u'nm'),
                     displayName="Thickness of Shell",
                     generator=RandomExponential,
                     valueRange=(0., numpy.inf),
                     activeRange=Length(u'nm').toSi((0.1, 1e3))),  # preset
        Parameter("eta_c",
                  SLD(u'Å⁻²').toSi(3.16e-6),
                  unit=SLD(u'Å⁻²'),
                  displayName="Core SLD",
                  generator=RandomUniform,
                  valueRange=(0, numpy.inf)),
        Parameter("eta_s",
                  SLD(u'Å⁻²').toSi(2.53e-6),
                  unit=SLD(u'Å⁻²'),
                  displayName="Shell SLD",
                  generator=RandomUniform,
                  valueRange=(0, numpy.inf)),
        Parameter("eta_sol",
                  0.,
                  unit=SLD(u'Å⁻²'),
                  displayName="Solvent SLD",
                  generator=RandomUniform,
                  valueRange=(0, numpy.inf)),
    )

    def __init__(self):
        super(SphericalCoreShell, self).__init__()
        # some presets of parameters to fit
        self.radius.setActive(True)

    def formfactor(self, dataset):
        def k(q, r, dEta):
            # modified K, taken out the volume scaling
            qr = numpy.outer(q, r)
            k = dEta * 3 * (sin(qr) - qr * cos(qr)) / (qr)**3
            return k

        # dToR = pi / 180. #degrees to radian

        vc = 4. / 3 * pi * self.radius()**3
        vt = 4. / 3 * pi * (self.radius() + self.t())**3
        vRatio = vc / vt

        ks = k(dataset.q,
               self.radius() + self.t(),
               self.eta_s() - self.eta_sol())
        kc = k(dataset.q, self.radius(), self.eta_s() - self.eta_c())
        return (ks - vRatio * kc).flatten()

    def volume(self):
        v = 4. / 3 * pi * (self.radius() + self.t())**3
        return v

    def absVolume(self):
        return self.volume()  #TODO: check how to do this.
示例#5
0
class CylindersIsotropic(SASModel):
    r"""Form factor of cylinders
    previous version (length-fixed) checked against SASfit
    """
    shortName = "Isotropic Cylinders"
    parameters = (FitParameter("radius",
                               Length(u'nm').toSi(1.),
                               unit=Length(u'nm'),
                               displayName="Cylinder Radius",
                               generator=RandomExponential,
                               valueRange=(Length(u'nm').toSi(0.1),
                                           numpy.inf)),
                  Parameter(
                      "useAspect",
                      True,
                      displayName="Use aspect ratio (checked) or length "),
                  FitParameter("length",
                               Length(u'nm').toSi(10.),
                               unit=Length(u'nm'),
                               displayName="Length L of the Cylinder",
                               generator=RandomExponential,
                               valueRange=(Length(u'nm').toSi(0.1),
                                           Length(u'nm').toSi(1e10))),
                  FitParameter("aspect",
                               10.0,
                               displayName="Aspect ratio of the Cylinder",
                               generator=RandomExponential,
                               valueRange=(1e-3, 1e3)),
                  FitParameter(
                      "psiAngle",
                      0.1,
                      unit=Angle(u'°'),
                      displayName="Internal Parameter, not user adjustable",
                      generator=RandomUniform,
                      valueRange=(0.01, 2 * pi + 0.01)),
                  Parameter("psiAngleDivisions",
                            303.,
                            displayName="Orientation Integration Divisions",
                            valueRange=(1, numpy.inf)),
                  Parameter("sld",
                            SLD(u'Å⁻²').toSi(1e-6),
                            unit=SLD(u'Å⁻²'),
                            displayName="Scattering length density difference",
                            valueRange=(0, numpy.inf)))

    def __init__(self):
        super(CylindersIsotropic, self).__init__()
        # some presets of parameters to fit
        self.radius.setActive(True)

    def formfactor(self, dataset):
        # psi and phi defined in fig. 1, Pauw et al, J. Appl. Cryst. 2010
        # used in the equation for a cylinder from Pedersen, 1997

        psiRange = self.psiAngle.valueRange()
        psi = numpy.linspace(psiRange[0], psiRange[1],
                             self.psiAngleDivisions())

        if self.useAspect():
            halfLength = self.radius() * self.aspect()
        else:
            halfLength = 0.5 * self.length()
        qRsina = numpy.outer(dataset.q, self.radius() * sin((psi)))
        qLcosa = numpy.outer(dataset.q, halfLength * cos((psi)))
        fsplit = (
            (2. * scipy.special.j1(qRsina) / qRsina * sinc(qLcosa / pi)) *
            sqrt(abs(sin((psi)))[newaxis, :] + 0. * qRsina))
        #integrate over orientation
        return numpy.sqrt(numpy.mean(fsplit**2, axis=1))  # should be length q

    def volume(self):
        if self.useAspect():
            halfLength = self.radius() * self.aspect()
        else:
            halfLength = self.length() / 2.
        v = pi * self.radius()**2 * (halfLength * 2.)
        return v

    def absVolume(self):
        return self.volume() * self.sld()**2
示例#6
0
class CylindersRadiallyIsotropic(SASModel):
    r"""Form factor of cylinders
    which are radially isotropic (so not spherically isotropic!)
    !!!completed but not verified!!!
    """
    shortName = "Radially (in-plane) isotropic cylinders"
    parameters = (
        FitParameter("radius",
                     Length(u'nm').toSi(1.),
                     unit=Length(u'nm'),
                     displayName="Cylinder radius",
                     generator=RandomExponential,
                     valueRange=Length(u'nm').toSi((0.1, numpy.inf)),
                     activeRange=Length(u'nm').toSi((0.1, 1e3))),  # preset
        FitParameter("aspect",
                     10.0,
                     displayName="Aspect ratio L/(2R) of the cylinder",
                     generator=RandomUniform,
                     valueRange=(0.1, numpy.inf),
                     activeRange=(1.0, 20.)),  # preset
        FitParameter("psiAngle",
                     0.17,
                     unit=Angle(u'°'),
                     displayName="in-plane cylinder rotation",
                     generator=RandomUniform,
                     valueRange=(0.01, 2 * pi + 0.01)),
        Parameter(
            "psiAngleDivisions",
            303.,  #setting to int gives OverflowError
            displayName="in-plane angle divisions",
            valueRange=(1, numpy.inf)),
        Parameter("sld",
                  SLD(u'Å⁻²').toSi(1e-6),
                  unit=SLD(u'Å⁻²'),
                  displayName="scattering length density difference",
                  valueRange=(0., numpy.inf)))

    def __init__(self):
        super(CylindersRadiallyIsotropic, self).__init__()
        # some presets of parameters to fit
        self.radius.setActive(True)
        self.aspect.setActive(False)  # not expected to vary
        self.psiAngle.setActive(True)  # better when random

    def formfactor(self, dataset):
        #psi and phi defined in fig. 1, Pauw et al, J. Appl. Cryst. 2010
        #used in the equation for a cylinder from Pedersen, 1997

        # dToR = pi/180. #degrees to radian not necessary since unit conversion
        psiRange = self.psiAngle.valueRange()
        psi = numpy.linspace(psiRange[0], psiRange[1],
                             self.psiAngleDivisions())

        ##replicate so we cover all possible combinations of psi, phi and psi
        #psiLong=psi[ numpy.sort( numpy.array( range(
        #    (len(psi)*len(q))
        #    ) ) %len(psi) ) ] #indexed to 111222333444 etc
        #qLong=q[ numpy.array( range(
        #    (len(psi)*len(q))
        #    ) ) %len(q) ] #indexed to 1234123412341234 etc

        #rotation can be used to get slightly better results, but
        #ONLY FOR RADIAL SYMMETRY, NOT SPHERICAL.
        qRsina = numpy.outer(dataset.q,
                             self.radius() * sin(((psi - self.psiAngle()))))
        qLcosa = numpy.outer(
            dataset.q,
            self.radius() * self.aspect() * cos(((psi - self.psiAngle()))))
        #leave the rotation out of it for now.
        #qRsina=numpy.outer(q,radi*sin(((psi)*dToR)))
        #qLcosa=numpy.outer(q,radi*asp*cos(((psi)*dToR)))
        fsplit = (2. * scipy.special.j1(qRsina) / qRsina * sin(qLcosa) /
                  qLcosa)
        #integrate over orientation
        return numpy.sqrt(numpy.mean(fsplit**2, axis=1))  # should be length q

    def volume(self):
        v = pi * self.radius()**2 * (2. * self.radius() * self.aspect())
        return v

    def absVolume(self):
        return self.volume() * self.sld()**2
示例#7
0
class EllipsoidalCoreShell(SASModel):
    r"""Form factor for an ellipsoidal core shell structure
    as defined in the SASfit manual (par. 3.2.3)
    Tested 2014-01-21 against SASfit function with good agreement.
    """
    shortName = "Core-Shell Ellipsoid"
    parameters = (
            FitParameter("a", Length(u'nm').toSi(1.), unit = Length(u'nm'),
                    displayName = "Principal Core Radius",
                    generator = RandomExponential,
                    valueRange = (0., numpy.inf),
                    activeRange = Length(u'nm').toSi((0.1, 1e3))), # preset
            FitParameter("b", Length(u'nm').toSi(10.), unit = Length(u'nm'),
                    displayName = "Equatorial Core Radius",
                    generator = RandomExponential,
                    valueRange = (0., numpy.inf),
                    activeRange = Length(u'nm').toSi((1.0, 1e4))), # preset
            FitParameter("t", Length(u'nm').toSi(1.), unit = Length(u'nm'),
                    displayName = "Thickness of Shell",
                    generator = RandomExponential,
                    valueRange = (0., numpy.inf),
                    activeRange = Length(u'nm').toSi((0.1, 1e3))), # preset
            Parameter("eta_c", SLD(u'Å⁻²').toSi(3.15e-6), unit = SLD(u'Å⁻²'),
                    displayName = "Core SLD",
                    generator = RandomUniform,
                    valueRange = (0, numpy.inf)),
            Parameter("eta_s", SLD(u'Å⁻²').toSi(2.53e-6), unit = SLD(u'Å⁻²'),
                    displayName = "Shell SLD",
                    generator = RandomUniform,
                    valueRange = (0, numpy.inf)),
            Parameter("eta_sol", 0., unit = SLD(u'Å⁻²'),
                    displayName = "Solvent SLD",
                    generator = RandomUniform,
                    valueRange = (0, numpy.inf)),
            Parameter("intDiv", 100,
                    displayName = "Orientation Integration Divisions",
                    generator = RandomUniform,
                    valueRange = (0, 1e4)),
    )

    def __init__(self):
        super(EllipsoidalCoreShell, self).__init__()
        # some presets of parameters to fit
        self.a.setActive(True)

    def formfactor(self, dataset):
        def j1(x):
            return ( sin(x) - x * cos(x) ) / (x**2)

        def calcXc(q, a, b, mu):
            sfact = sqrt(
                    a**2 * mu**2 + b**2 * (1 - mu**2)
                    )
            return numpy.outer(q, sfact)

        def calcXt(q, a, b, t, mu):
            sfact = sqrt(
                    (a + t)**2 * mu**2 + (b + t)**2 * (1 - mu**2)
                    )
            return numpy.outer(q, sfact)

        intVal = numpy.linspace(0., 1., self.intDiv())

        vc = 4./3. * pi *  self.a() * self.b() **2.
        vt = 4./3. * pi * (self.a() + self.t()) * (self.b() + self.t()) **2.
        vRatio = vc / vt

        xc = calcXc(dataset.q, self.a(), self.b(), intVal)
        xt = calcXt(dataset.q, self.a(), self.b(), self.t(), intVal)
        fsplit = (
                (self.eta_c() - self.eta_s()) * vRatio *
                ( 3 * j1( xc ) / xc ) +
                (self.eta_s() - self.eta_sol()) * 1. *
                ( 3 * j1( xt ) / xt )
                )
        # integrate over orientation
        return numpy.sqrt(numpy.mean(fsplit**2, axis=1)) # should be length q

    def volume(self):
        v = 4./3 * pi * (self.a() + self.t()) * (self.b() + self.t())**2
        return v

    def absVolume(self):
        return self.volume() #TODO: check how to do this.
示例#8
0
class TrapezoidSmearing(SmearingConfig):
    parameters = (
        Parameter(
            "umbra",
            0.,
            unit=NoUnit(),  # unit set outside
            displayName="top width of <br />trapezoidal beam profile",
            description="full top width of the trapezoidal beam profile "
            "(horizontal for slit-collimated systems, "
            "circularly averaged for 2D pinhole and "
            "rectangular slit)",
            valueRange=(0., np.inf),
            decimals=9),
        Parameter(
            "penumbra",
            0.,
            unit=NoUnit(),  # unit set outside
            displayName="bottom width of <br />trapezoidal beam profile",
            description="full bottom width of the trapezoidal beam profile "
            "horizontal for slit-collimated systems, circularly "
            "averaged for 2D pinhole and rectangular slit)",
            valueRange=(0., np.inf),
            decimals=9),
    )

    def inputValid(self):
        # returns True if the input values are valid
        return (self.umbra() > 0.) and (self.penumbra > self.umbra())

    @property
    def showParams(self):
        lst = ["umbra", "penumbra"]
        return [
            name for name in super(TrapezoidSmearing, self).showParams
            if name not in lst
        ] + lst

    def halfTrapzPDF(self, x, c, d):
        # this trapezoidal PDF is only defined from X >= 0, and is assumed
        # to be mirrored around that point.
        # Note that the integral of this PDF from X>0 will be 0.5.
        # source: van Dorp and Kotz, Metrika 2003, eq (1)
        # using a = -d, b = -c
        logging.debug("halfTrapzPDF called")
        assert (d > 0.)
        x = abs(x)
        pdf = x * 0.
        pdf[x < c] = 1.
        if d > c:
            pdf[(c <= x) & (x < d)] = (1. /
                                       (d - c)) * (d - x[(c <= x) & (x < d)])
        norm = 1. / (d + c)
        pdf *= norm
        return pdf, norm

    def setIntPoints(self, q):
        """ sets smearing profile integration points for trapezoidal slit. 
        Top (umbra) of trapezoid has full width xt, bottom of trapezoid 
        (penumbra) has full width.
        Since the smearing function is assumed to be symmetrical, the 
        integration parameters are calculated in the interval [0, xb/2]
        """
        n, xt, xb = self.nSteps(), self.umbra(), self.penumbra()
        logging.debug("setIntPoints called with n = {}".format(n))

        # following qOffset is used for Pinhole and Rectangular
        qOffset = np.logspace(np.log10(q.min() / 5.),
                              np.log10(xb / 2.),
                              num=np.ceil(n / 2.))
        qOffset = np.concatenate((-qOffset[::-1], [
            0,
        ], qOffset))
        if not self.twoDColl():
            # overwrite prepared integration steps qOffset:
            qOffset = np.logspace(np.log10(q.min() / 5.),
                                  np.log10(xb / 2.),
                                  num=n)
            # tack on a zero at the beginning
            qOffset = np.concatenate(([
                0,
            ], qOffset))

        y, dummy = self.halfTrapzPDF(qOffset, xt, xb)

        # volume fraction still off by a factor of two (I think). Can be
        # fixed by multiplying y with 0.5, but need to find it first in eqns.
        # volume fraction still off by a factor of two (I think). Can be
        # fixed by multiplying y with 0.5, but need to find it first in eqns.
        self._qOffset, self._weights = qOffset, y

    def updateQUnit(self, newUnit):
        super(TrapezoidSmearing, self).updateQUnit(newUnit)
        self.umbra.setUnit(newUnit)
        self.penumbra.setUnit(newUnit)

    def updateQLimits(self, qLimit):
        qLow, qHigh = qLimit
        self.umbra.setValueRange((0., 2. * qHigh))
        self.penumbra.setValueRange((0., 2. * qHigh))

    def updatePUnit(self, newUnit):
        super(TrapezoidSmearing, self).updatePUnit(newUnit)
        # TODO

    def updatePLimits(self, pLimit):
        pLow, pHigh = pLimit
        # TODO

    def updateSmearingLimits(self, q):
        super(TrapezoidSmearing, self).updateSmearingLimits(q)
        low, high = np.absolute(np.diff(q)).min(), q.max()
        self.umbra.setValueRange((low, 2. * high))
        self.penumbra.setValueRange((low, 2. * high))

    def __init__(self):
        super(TrapezoidSmearing, self).__init__()
        self.umbra.setOnValueUpdate(self.onUmbraUpdate)

    def onUmbraUpdate(self):
        """Value in umbra will not exceed available q."""
        # value in penumbra must not be smaller than umbra
        self.penumbra.setValueRange((self.umbra(), self.penumbra.max()))
示例#9
0
class GaussianSmearing(SmearingConfig):
    parameters = (
        Parameter(
            "variance",
            0.,
            unit=NoUnit(),  # unit set outside
            displayName=u"Variance (σ²) of <br /> Gaussian beam profile",
            #displayName = u"Variance (&sigma;<sup>2</sup>)",
            description="Full width at half maximum of the Gaussian beam"
            "profile (horizontal for slit-collimated systems, "
            "circularly averaged for 2D pinhole and rectangular "
            "slit)",
            valueRange=(0., np.inf),
            decimals=9), )

    def inputValid(self):
        # returns True if the input values are valid
        return (self.variance() > 0.)

    @property
    def showParams(self):
        lst = ["variance"]
        return [
            name for name in super(GaussianSmearing, self).showParams
            if name not in lst
        ] + lst

    def setIntPoints(self, q):
        """Sets smearing profile integration points for trapezoidal slit.
        Top (umbra) of trapezoid has full width xt, bottom of trapezoid
        (penumbra) has full width.
        Since the smearing function is assumed to be symmetrical, the
        integration parameters are calculated in the interval [0, xb/2]
        """
        n, GVar = self.nSteps(), self.variance()
        logging.debug("setIntPoints called with n = {}".format(n))

        # following qOffset is used for Pinhole and Rectangular
        qOffset = np.logspace(np.log10(q.min() / 3.),
                              np.log10(2.5 * GVar),
                              num=np.ceil(n / 2.))
        qOffset = np.concatenate((-qOffset[::-1], [
            0,
        ], qOffset))
        if not self.twoDColl():
            # overwrite prepared integration steps qOffset:
            qOffset = np.logspace(np.log10(q.min() / 3.),
                                  np.log10(2.5 * GVar),
                                  num=n)
            # tack on a zero at the beginning
            qOffset = np.concatenate(([
                0,
            ], qOffset))

        y = stats.norm.pdf(qOffset, scale=GVar)

        logging.debug("qOffset: {}, y: {}".format(qOffset, y))
        self._qOffset, self._weights = qOffset, y

    def updateQUnit(self, newUnit):
        super(GaussianSmearing, self).updateQUnit(newUnit)
        self.variance.setUnit(newUnit)

    def updateQLimits(self, qLimit):
        qLow, qHigh = qLimit
        self.variance.setValueRange((0., 2. * qHigh))

    def updatePUnit(self, newUnit):
        super(GaussianSmearing, self).updatePUnit(newUnit)
        # TODO

    def updatePLimits(self, pLimit):
        pLow, pHigh = pLimit
        # TODO

    def updateSmearingLimits(self, q):
        super(GaussianSmearing, self).updateSmearingLimits(q)
        # it seems, diff(q) can be negative
        low, high = np.absolute(np.diff(q)).min(), q.max()
        self.variance.setValueRange((low, 2. * high))

    def __init__(self):
        super(GaussianSmearing, self).__init__()
示例#10
0
class SmearingConfig(with_metaclass(ABCMeta, AlgorithmBase)):
    """Abstract base class, can't be instantiated."""
    _qOffset = None  # integration point positions, depends on beam profile
    _weights = None  # integration weight per position, depends on beam profile
    shortName = "SAS smearing configuration"
    parameters = (
        Parameter(
            "doSmear",
            False,
            unit=NoUnit(),
            displayName="Apply smearing correction",
        ),
        Parameter("nSteps",
                  25,
                  unit=NoUnit(),
                  displayName="number of smearing points around each q",
                  valueRange=(0, 1000)),
        # 2-d collimated systems require a different smearing than
        # slit-collimated data
        Parameter(
            "twoDColl",
            False,
            unit=NoUnit(),
            displayName="Slit-smeared data (unchecked), or 2D-averaged "
            "data (checked)",
        ),
        #        Parameter("collType", u"Slit", unit = NoUnit(),
        #            displayName = "Type of collimation leading to smearing",
        #            valueRange = [u"Slit", u"Pinhole", u"Rectangular", u"None"])
    )

    def updateQUnit(self, newUnit):
        assert isinstance(newUnit, ScatteringVector)

    def updateQLimits(self, qLimit):
        pass

    def updatePUnit(self, newUnit):
        assert isinstance(newUnit, Angle)

    def updatePLimits(self, pLimit):
        pass

    def updateSmearingLimits(self, q):
        pass

    @property
    def qOffset(self):
        return self._qOffset

    @property
    def weights(self):
        return self._weights

    @property
    def prepared(self):
        return self._qOffset, self._weights

    def __str__(self):
        s = [str(id(self)) + " " + super(SmearingConfig, self).__str__()]
        s.append("  qOffset: {}".format(self.qOffset))
        s.append("  weights: {}".format(self.weights))
        return "\n".join(s)

    def hdfWrite(self, hdf):
        super(SmearingConfig, self).hdfWrite(hdf)
        hdf.writeMembers(self, 'qOffset', 'weights')
示例#11
0
class LMADenseSphere(SASModel):
    """Form factor of a sphere convoluted with a structure factor,
    equations 15-17 from Pedersen, J. Appl. Cryst. 27 (1994), 595--608. 
    Correct eqn given in Kinning and Thomas, Macromolecules 17 (1984) 1712.
    Internally set parameters are volume fraction of the hard spheres,
    and the multiplication factor /mf/ for an additional stand-off distance
    between the hard spheres: Rh=mf*R where Rh is the hard-sphere radius
    ("interaction radius") used in the structure factor, R is the radius of
    the sphere, and mf is the multiplication factor.
    """
    canSmear = True
    shortName = "LMADenseSphere"
    parameters = (
            FitParameter("radius", 
                    Length(u"nm").toSi(1.), 
                    unit = Length(u"nm"),
                    displayName = "Sphere radius",
                    valueRange = (0., np.inf),
                    generator = RandomUniform,
                    decimals = 9),
            FitParameter("volFrac", 
                    Fraction(u"%").toSi(10), 
                    unit = Fraction(u"%"),
                    displayName = "Volume fraction of spheres",
                    valueRange = (Fraction(u"%").toSi(0.001), 
                        Fraction(u"%").toSi(100.)),
                    generator = RandomUniform,
                    decimals = 9),
            Parameter("mf", 
                    -1., # auto
                    displayName = "standoff multiplier (-1 = auto)",
                    valueRange = (-1., 1.e6),
                    unit = NoUnit(u''),
                    decimals = 9,
                    displayValues = {-1.: "auto"}),
            Parameter("sld", 
                    SLD(u'Å⁻²').toSi(1e-6), 
                    unit = SLD(u'Å⁻²'),
                    displayName = "Scattering length density difference",
                    valueRange = (0., np.inf),
                    decimals = 9)
            )

    def __init__(self):
        super(LMADenseSphere, self).__init__()
        # some presets of parameters to fit
        self.radius.setActive(True)

    def volume(self):
        result = (pi*4./3.) * self.radius()**3
        return result
    
    def absVolume(self):
        return self.volume() * self.sld()**2

    def formfactor(self, dataset):
        
        q = self.getQ(dataset)
        SFmu = self.volFrac()
        SFmf = self.mf()
        if SFmf == -1:
            SFmf = (0.634 / SFmu) **(1. / 3)

        def SFG(A, SFmu):
            alpha = ( 1 + 2 * SFmu )**2 / ( 1 - SFmu )**4
            beta = -6 * SFmu * ( 1 + SFmu / 2 )**2 / ( 1 - SFmu )**4
            gamma = SFmu * alpha / 2
            G = (
                    alpha * ( sin(A) - A * cos(A) ) / A**2 +
                    beta *(2 * A * sin(A) + (2 - A**2) * cos(A) - 2) / A**3 +
                    gamma*( -1 * A**4 * cos(A) + 4 * ((3 * A**2 - 6) * cos(A)
                        + (A**3 - 6 * A) * sin(A) + 6 ) ) / A**5
                    )
            return G


        qr = q * self.radius()
        result = 3. * (sin(qr) - qr * cos(qr)) / (qr**3.)
        #now we introduce the structure factor
        rhsq = 2. * q * (SFmf * self.radius())
        G = SFG(rhsq, SFmu)
        S = (( 1. + 24. * SFmu * G / rhsq ))**(-1)
        #print (S < 0).sum()
        # the above structure factor needs to be the square root as it is
        # taken into the form factor. Eventually, we want to calculate the
        # scattering as FF**2 * S, which we are now achieving as
        # (FF * S**0.5)**2. The minus sign in the exponent of the above
        # equation comes from the original equation in the literature.
        result = np.sqrt(result**2 * S)
        return result