Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

point spacing of polydispersity should not always be linear. #633

Open
butlerpd opened this issue Feb 20, 2025 · 1 comment
Open

point spacing of polydispersity should not always be linear. #633

butlerpd opened this issue Feb 20, 2025 · 1 comment

Comments

@butlerpd
Copy link
Member

A NIST user recently noticed that they were getting incorrect answers when using the lognormal distribution with a very large dispersity. After a lot of work to track down they realized it is because all polydispersity distributions use a linear point spacing for the integration when in fact something like lognormal (or the Schulz-Zim) distribution should be using a log spacing of points, particularly with large dispersity values.

It may be that for low values of the dispersity the spacing should still be linear. This needs a bit of investigation. In the meantime the user fixed their problem by altering the sasmodels code. I include the code snippets here as they look good to me and could make for a quick patch?

Below are the distribution points for a lognormal distribution (using the default sphere radius to be polydisperse) for a pd of 2, 1 and 0.5 respectively

PD 2.0
Image

PD 1.0
Image

PD 0.5
Image

CODE

First add a new _logspace method to the Dispersion class in weights.py immediately following the existing _linspace method (and mostly copied form it)

def _logspace(self, center, sigma, lb, ub):
"""helper function to provide log(e) spaced weight points within range"""
npts, nsigmas = self.npts, self.nsigmas
# sigma in the lognormal function is in ln(R) space, thus needs converting
sig=np.fabs(sigma/center)
x = np.logspace(np.log(center)-nsigmas*sig, np.log(center)+nsigmas*sig, npts,base=np.e)
x = x[(x >= lb) & (x <= ub)]
return x

Next, replace the call to _linspace with a call to _logspace in the LogNormalDispersion class. The same should probably be done for the SchulzDispersion class. Note that he also added 4 new lines at the end just before the return to transform x and px which should probably be folded directly into the way x and px are calculated?

class LogNormalDispersion(Dispersion):
r"""
log Gaussian dispersion, with 1-$\sigma$ width.

.. math::

w = \frac{\exp\left(-\tfrac12 (\ln x - c)^2/\sigma^2\right)}{x\sigma}
"""
type = "lognormal"
default = dict(npts=80, width=0, nsigmas=8)
def _weights(self, center, sigma, lb, ub):
x = self._logspace(center, sigma, max(lb, 1e-8), max(ub, 1e-8))
# sigma in the lognormal function is in ln(R) space, thus needs converting
sig = np.fabs(sigma/center)
px = np.exp(-0.5*((np.log(x)-np.log(center))/sig)**2)/(x*sig)
x1 = (x[1:] + x[:-1])*0.5
px1 = 0.5*(px[1:]+px[:-1])*(x[1:]-x[:-1])
norm = sum(px1)
px1 = px1/norm
return x1, px1
@pkienzle
Copy link
Contributor

Which models were they looking at and how did they determine the correct values?

For the polydispersity integral ∫p(r) F²(q;r) dr with lognormal p and sphere F we can plot the integrand p(r) F²(q;r) to see how it behaves. The result suggests we do want linear spacing, and further that we want to restrict PD to be less than about 0.5.

plot ( exp( -((ln(r)-ln(100))/σ)^2/2 )/(σr*sqrt(2π)) (r^2/10 j1(r/10))^2 ) from 1 to 600 for σ = ΔR/R = 0.5, 1.0 and 2.0

Image Image Image

With R=100, ΔR/R = 0.5, nsigma = 8, the integral ranges from 0 to 500, which covers most of the function. Unfortunately the integrand is positive everywhere so we will always underestimate the true result.

See also the discussion in #585

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants