source: sasmodels/example/weights/maier_saupe.py @ b50e28b

Last change on this file since b50e28b was b50e28b, checked in by Paul Kienzle <pkienzle@…>, 6 years ago

update maier_saupe docs

  • Property mode set to 100644
File size: 4.1 KB
Line 
1from __future__ import print_function
2import numpy as np
3from numpy import exp, sin, degrees, radians, pi, sqrt
4
5from sasmodels.weights import Dispersion as BaseDispersion
6
7class Dispersion(BaseDispersion):
8    r"""
9    Maier-Saupe dispersion on orientation.
10
11    .. math:
12
13        w(\theta) = e^{a{\cos^2 \theta}}
14
15    This provides a close match to the gaussian distribution for
16    low angles, but the tails are limited to $\pm 90^\circ$.  For $a \ll 1$
17    the distribution is approximately uniform.  The usual polar coordinate
18    projection applies, with $\theta$ weights scaled by $\cos \theta$
19    and $\phi$ weights unscaled.
20
21    This is equivalent to a cyclic gaussian distribution
22    $w(\theta) = e^{-sin^2(\theta)/(2\a^2)}.
23
24    Note that the incorrect distribution is used for $a=0$.  With the
25    distribution parameter labelled as *width*, the value *width=0* is
26    is assumed to be completely oriented and no polydispersity distribution
27    is generated.  However a value of $a=0$ should be completely unoriented.
28    Any small value (e.g., 0.01 or lower) should suffice.
29
30    The order parameter $P_2$ is defined as
31
32    .. math:
33
34        P(a, \beta) = \frac{e^{a \cos^2 \beta}}{
35                       4\pi \int_0^{\pi/2} e^{a \cos^2 \beta} \sin \beta\,d\beta}
36
37        P_2 = 4\pi \int_0^{\pi/2} \frac{3 \cos^2 \beta - 1)}{2}
38                        P(a, \beta) \sin \beta\,d\beta
39
40    where $a$ is the distribution width $\sigma$ handed to the weights function.
41    There is a somewhat complicated closed form solution
42
43    .. math:
44
45        P_2 = \frac{3e^a}{2\sqrt{\pi a} E_a} - \frac{3}{4a} - \frac{1}{2}
46
47    where $E_a = \mathrm{erfi}(\sqrt{a})$ is the imaginary error function,
48    which can be coded in python as::
49
50        from numpy import pi, sqrt, exp
51        from scipy.special import erfi
52
53        def P_2(a):
54            # protect against overflow with a Taylor series at infinity
55            if a <= 700:
56                r = exp(a)/sqrt(pi*a)/erfi(sqrt(a))
57            else:
58                r = 1/((((6.5525/a + 1.875)/a + 0.75)/a + 0.5)/a + 1)
59            return 1.5*r - 0.75/a - 0.5
60
61    References
62    ----------
63
64    [1] Hardouin, et al., 1995. SANS study of a semiflexible main chain
65    liquid crystalline polyether. *Macromolecules* 28, 5427-5433.
66    """
67    type = "maier_saupe"
68    default = dict(npts=35, width=1, nsigmas=None)
69
70    # Note: center is always zero for orientation distributions
71    def _weights(self, center, sigma, lb, ub):
72        # use the width parameter as the value for Maier-Saupe "a"
73        # and find the equivalent width sigma
74        a = sigma
75        sigma = 1./sqrt(2.*a)
76
77        # Limit width to +/- 90 degrees
78        width = min(self.nsigmas*sigma, pi/2)
79        x = np.linspace(-width, width, self.npts)
80
81        # Truncate the distribution in case the parameter value is limited
82        x[(x >= radians(lb)) & (x <= radians(ub))]
83
84        # Return orientation in degrees with Maier-Saupe weights
85        # Note: weights are normalized to sum to 1, so we can scale
86        # by an arbitrary scale factor c = exp(m) to get:
87        #     w = exp(m*cos(x)**2)/c = exp(-m*sin(x)**2)
88        return degrees(x), exp(-a*sin(x)**2)
89
90
91
92def P_2(a):
93    from numpy import pi, sqrt, exp
94    from scipy.special import erfi
95
96    # Double precision e^x overflows so do a Taylor expansion around infinity
97    # for large values of a.  With five terms it is accurate to 12 digits
98    # at a = 700, and 7 digits at a = 75.
99    if a <= 700:
100        r = exp(a)/sqrt(pi*a)/erfi(sqrt(a))
101    else:
102        r = 1/((((6.5525/a + 1.875)/a + 0.75)/a + 0.5)/a + 1)
103    return 1.5*r - 0.75/a - 0.5
104
105def P_2_numerical(a):
106    from scipy.integrate import romberg
107    from numpy import cos, sin, pi, exp
108    def num(beta):
109        return (1.5 * cos(beta)**2 - 0.5) * exp(a * cos(beta)**2) * sin(beta)
110    def denom(beta):
111        return exp(a * cos(beta)**2) * sin(beta)
112    return romberg(num, 0, pi/2) / romberg(denom, 0, pi/2)
113
114if __name__ == "__main__":
115    import sys
116    a = float(sys.argv[1])
117    #print("P_2", P_2(a), "difference from integral", P_2(a) - P_2_numerical(a))
118    print("P_2", P_2(a))
Note: See TracBrowser for help on using the repository browser.