class Kholodenko(SASModel): r"""Form factor of a worm-like structure after [Kholodenko93]_ .. [Kholodenko93] `A. L. Kholodenko. Analytical calculation of the scattering function for polymers of arbitrary flexibility using the dirac propagator. Macromolecules, 26:4179–4183, 1993. <http://dx.doi.org/10.1021/ma00068a017>`_ """ shortName = "Kholodenko Worm" parameters = ( FitParameter("radius", Length(u'nm').toSi(1.), unit=Length(u'nm'), displayName="Radius", generator=RandomExponential, valueRange=(0., numpy.inf), activeRange=Length(u'nm').toSi((1, 5))), # preset FitParameter("lenKuhn", Length(u'nm').toSi(1.), unit=Length(u'nm'), displayName="kuhn length", generator=RandomUniform, valueRange=(0., numpy.inf), activeRange=Length(u'nm').toSi((10, 50))), # preset FitParameter("lenContour", Length(u'nm').toSi(2.), unit=Length(u'nm'), displayName="contour length", generator=RandomUniform, valueRange=(0., numpy.inf), activeRange=Length(u'nm').toSi((100, 1000))), # preset ) def __init__(self): super(Kholodenko, self).__init__() # some presets of parameters to fit self.radius.setActive(True) self.lenKuhn.setActive(True) self.lenContour.setActive(True) def formfactor(self, dataset): # vectorized data and arguments qr = dataset.q * self.radius() # a vector pcs = vectorizedPcs(qr) x = 3. * self.lenContour() / self.lenKuhn() p0 = vectorizedCoreIntegral(dataset.q, self.lenKuhn(), x) if len(LASTMSG): logging.warning("\n".join(["numpy.quad integration messages: "] + list(LASTMSG))) return p0 * pcs # non-squared as opposed to SASfit def volume(self): volume = numpy.pi * self.lenContour() * self.radius()**2 return volume
def __init__(self, **kwargs): super(SASData, self).__init__(**kwargs) # self._h5LocAdd = "sasdata01" # overwriting DataObj default; DOES NOTHING # process rawArray for new DataVector instances: rawArray = kwargs.pop('rawArray', None) if rawArray is None: logging.error('SASData must be called with a rawArray provided') self.x0 = DataVector(u'q', rawArray[:, 0], unit=ScatteringVector(u"nm⁻¹")) self.f = DataVector(u'I', rawArray[:, 1], rawU=rawArray[:, 2], unit=ScatteringIntensity(u"(m sr)⁻¹")) # sanitized uncertainty, we should use self._e.copy logging.info("Init SASData: " + self.qLimsString) if (rawArray.shape[1] > 3 and rawArray[:, 3].min() != rawArray[:, 3].max()): # psi column is present and contains some data self.x1 = DataVector(u'ψ', rawArray[:, 3], unit=Angle(u"°")) logging.info(self.pLimsString) #set unit definitions for display and internal units self._rUnit = Length(u"nm") # init config as early as possible to get properties ready which # depend on it (qlow/qhigh?) # (should be moved to DataObj but the DataVectors have to be set earlier) self.initConfig()
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.
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
class CylindersIsotropic(SASModel): r"""Form factor of cylinders which are radially isotropic (so not spherically isotropic!) !!!completed but not verified!!! """ shortName = "Cylinders defined by aspect ratio" parameters = ( FitParameter("radius", Length("nm").toSi(1.), unit=Length("nm"), displayName="Cylinder radius", valueRange=(0., numpy.inf), activeRange=Length("nm").toSi((0.1, 1e3)), suffix="nm"), FitParameter("aspect", 10.0, displayName="Aspect ratio L/(2R) of the cylinder", valueRange=(0., numpy.inf), activeRange=(1.0, 20), suffix="-"), FitParameter("psiAngle", Angle(u"°").toSi(10.), unit=Angle(u"°"), displayName="in-plane cylinder rotation", valueRange=(0., Angle(u"°").toSi(180.))), FitParameter("psiAngleDivisions", 303., displayName="in-plane angle divisions", valueRange=(0, numpy.inf), suffix="-"), ) def __init__(self): super(CylindersIsotropic, 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 self.psiAngleDivisions.setActive(False) # not expected to vary 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 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 #this is wrong for spherical symmetry: #qRsina=numpy.outer(q,radi*sin(((psi-psiA)*dToR)%180)) #qLcosa=numpy.outer(q,radi*asp*cos(((psi-psiA)*dToR)%180)) #fsplit=( 2*scipy.special.j1(qRsina)/qRsina * sin(qLcosa)/qLcosa )*sqrt((sin((psi-psiA)*dToR))[newaxis,:]%180+0*qRsina) qRsina = numpy.outer(dataset.q, self.radius() * sin((psi * dToR) % 180.)) qLcosa = numpy.outer( dataset.q, self.radius() * self.aspect() * cos((psi * dToR) % 180.)) fsplit = ( (2 * scipy.special.j1(qRsina) / qRsina * sin(qLcosa) / qLcosa) * sqrt((sin(psi * dToR))[newaxis, :] % 180 + 0. * qRsina)) #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
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
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.
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
class GaussianChain(SASModel): r"""Form factor of flexible polymer chains which are not selfavoiding and obey Gaussian statistics after [Debye47]_ See also: http://sasfit.sf.net/manual/Gaussian_Chain#Gauss_2 .. [Debye47] `P. Debye, Mollecular-weight determination by light scattering, Journal of Physical and Colloid Chemistry, 51:18--32, 1947. <http://dx.doi.org/10.1021/j150451a002>`_ I_0 = (bp - (k * Rg^2) * eta_s)^2 with k = 1 nm. k * Rg^2 = volume approximation """ shortName = "Gaussian Chain" parameters = ( FitParameter("rg", Length(u'nm').toSi(1.), unit=Length(u'nm'), displayName="radius of gyration, Rg", generator=RandomExponential, valueRange=(0., numpy.inf), activeRange=Length(u'nm').toSi((1.0, 1e2))), # preset FitParameter("bp", Length(u'nm').toSi(100.), unit=Length(u'nm'), displayName="scattering length of the polymer", generator=RandomUniform, valueRange=(0., numpy.inf), activeRange=Length(u'nm').toSi((0.1, 1e3))), # preset FitParameter("etas", SLD(u'Å⁻²').toSi(1e-6), unit=SLD(u'Å⁻²'), displayName="scattering length density of the solvent", generator=RandomUniform, valueRange=(0., numpy.inf), activeRange=SLD(u'Å⁻²').toSi((0.1, 10.))), # preset FitParameter("k", 1.0, displayName="volumetric scaling factor of Rg", generator=RandomUniform, valueRange=(0., numpy.inf), activeRange=(0.1, 10.)), # preset ) def __init__(self): super(GaussianChain, self).__init__() # some presets of parameters to fit self.rg.setActive(True) def formfactor(self, dataset): # vectorized data beta = self.bp() - (self.k() * self.rg()**2) * self.etas() u = (dataset.q * self.rg())**2 result = numpy.sqrt(2.) * numpy.sqrt(numpy.expm1(-u) + u) / u result *= beta result[dataset.q <= 0.0] = beta return result def volume(self): v = self.k() * self.rg()**2 return v @mixedmethod def fixTestParams(self, params): # order and meaning differs from sasfit Gauss2 model vol = params['etas'] params['etas'] = params['k'] params['k'] = vol / params['rg']**2. return params