source: sasmodels/explore/bccpy.py @ 8db25bf

core_shell_microgelsmagnetic_modelticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since 8db25bf was 7e0b281, checked in by Paul Kienzle <pkienzle@…>, 8 years ago

adjust paracrystal equations to better match terminology in Matsuoka paper

  • Property mode set to 100644
File size: 7.2 KB
Line 
1"""
2The current 1D calculations for BCC paracrystal are very wrong at low q, orders
3of magnitude wrong.  The integration fails to capture a very narrow,
4very steep ridge.
5
6Uncomment the plot() line at the bottom of the code to show an image of the
7set of S(q, theta, phi) values that must be summed to form the 1D S(q=0.001)
8for the BCC paracrystal form.  Note particularly that this is a log scale
9image spanning 10 orders of magnitude.  This pattern repeats itself 8 times
10over the entire 4 pi surface integral.
11
12You can explore various integration options by uncommenting more lines.
13Adaptive integration using scpy.integrate.dbsquad is very slow.  Romberg
14didn't even complete in the time I gave it. Accurate brute force calculation
15requires a 4000x4000 grid to get enough precision.
16
17We may need a specialized integrator for low q which can identify and integrate
18the ridges properly.
19
20This program need sasmodels on the path so it is inserted automatically,
21assuming that the explore/bccpy.py is beside sasmodels/special.py in the
22source tree.  Run from the sasmodels directory using:
23
24    python explore/bccpy.py
25"""
26
27from __future__ import print_function, division
28
29import os, sys
30sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
31
32import numpy as np
33from numpy import pi, sin, cos, exp, expm1, degrees, log10
34from scipy.integrate import dblquad, simps, romb, romberg
35import pylab
36
37from sasmodels.special import square
38from sasmodels.special import Gauss20Wt, Gauss20Z
39from sasmodels.special import Gauss76Wt, Gauss76Z
40from sasmodels.special import Gauss150Wt, Gauss150Z
41
42Q = 0.001
43DNN = 220.
44D_FACTOR = 0.06
45RADIUS = 40.0
46SLD = 3.0
47SLD_SOLVENT = 6.3
48
49# Note: using Matsuoka 1990; this is different from what
50# is in the sasmodels/models code  (see bcc vs bcc_old).
51# The difference is that the sign of phi and theta seem to be
52# negative in the old vs. the new, yielding a pattern that is
53# swapped left to right and top to bottom.
54def sc(qa, qb, qc):
55    return qa, qb, qc
56
57def bcc(qa, qb, qc):
58    a1 = (+qa + qb + qc)/2
59    a2 = (-qa - qb + qc)/2
60    a3 = (-qa + qb - qc)/2
61    return a1, a2, a3
62
63def bcc_old(qa, qb, qc):
64    a1 = (+qa + qb - qc)/2.0
65    a2 = (+qa - qb + qc)/2.0
66    a3 = (-qa + qb + qc)/2.0
67    return a1, a2, a3
68
69def fcc(qa, qb, qc):
70    a1 = ( 0. + qb + qc)/2
71    a2 = (-qa + 0. + qc)/2
72    a3 = (-qa + qb + 0.)/2
73    return a1, a2, a3
74
75def fcc_old(qa, qb, qc):
76    a1 = ( qa + qb + 0.)/2
77    a2 = ( qa + 0. + qc)/2
78    a3 = ( 0. + qb + qc)/2
79    return a1, a2, a3
80
81KERNEL = bcc
82
83def kernel(q, dnn, d_factor, theta, phi):
84    """
85    S(q) kernel for paracrystal forms.
86    """
87    qab = q*sin(theta)
88    qa = qab*cos(phi)
89    qb = qab*sin(phi)
90    qc = q*cos(theta)
91
92    a1, a2, a3 = KERNEL(qa, qb, qc)
93
94    # Note: paper says that different directions can have different distortion
95    # factors.  Easy enough to add to the code.  This would definitely break
96    # 8-fold symmetry.
97    arg = -0.5*square(dnn*d_factor)*(a1**2 + a2**2 + a3**2)
98    exp_arg = exp(arg)
99    den = [((exp_arg - 2*cos(dnn*a))*exp_arg + 1.0) for a in (a1, a2, a3)]
100    Zq = -expm1(2*arg)**3/np.prod(den, axis=0)
101    return Zq
102
103
104def scipy_dblquad(q=Q, dnn=DNN, d_factor=D_FACTOR):
105    """
106    Compute the integral using scipy dblquad.  This gets the correct answer
107    eventually, but it is slow.
108    """
109    evals = [0]
110    def integrand(theta, phi):
111        evals[0] += 1
112        Zq = kernel(q=q, dnn=dnn, d_factor=d_factor, theta=theta, phi=phi)
113        return Zq*sin(theta)
114    ans = dblquad(integrand, 0, pi/2, lambda x: 0, lambda x: pi/2)[0]*8/(4*pi)
115    print("dblquad evals =", evals[0])
116    return ans
117
118
119def scipy_romberg_2d(q=Q, dnn=DNN, d_factor=D_FACTOR):
120    """
121    Compute the integral using romberg integration.  This function does not
122    complete in a reasonable time.  No idea if it is accurate.
123    """
124    def inner(phi, theta):
125        return kernel(q=q, dnn=dnn, d_factor=d_factor, theta=theta, phi=phi)
126    def outer(theta):
127        return romberg(inner, 0, pi/2, divmax=100, args=(theta,))*sin(theta)
128    return romberg(outer, 0, pi/2, divmax=100)*8/(4*pi)
129
130
131def semi_romberg(q=Q, dnn=DNN, d_factor=D_FACTOR, n=100):
132    """
133    Use 1D romberg integration in phi and regular simpsons rule in theta.
134    """
135    evals = [0]
136    def inner(phi, theta):
137        evals[0] += 1
138        return kernel(q=q, dnn=dnn, d_factor=d_factor, theta=theta, phi=phi)
139    theta = np.linspace(0, pi/2, n)
140    f_phi = [romberg(inner, 0, pi/2, divmax=100, args=(t,))
141             for t in theta]
142    ans = simps(sin(theta)*np.array(f_phi), dx=theta[1]-theta[0])
143    print("semi romberg evals =", evals[0])
144    return ans*8/(4*pi)
145
146def gauss_quad(q=Q, dnn=DNN, d_factor=D_FACTOR, n=150):
147    """
148    Compute the integral using gaussian quadrature for n = 20, 76 or 150.
149    """
150    if n == 20:
151        z, w = Gauss20Z, Gauss20Wt
152    elif n == 76:
153        z, w = Gauss76Z, Gauss76Wt
154    else:
155        z, w = Gauss150Z, Gauss150Wt
156    theta = pi/4*(z + 1)
157    phi = pi/4*(z + 1)
158    Atheta, Aphi = np.meshgrid(theta, phi)
159    Aw = w[None, :] * w[:, None]
160    sin_theta = np.fmax(abs(sin(Atheta)), 1e-6)
161    Zq = kernel(q=q, dnn=dnn, d_factor=d_factor, theta=Atheta, phi=Aphi)
162    print("gauss %d evals ="%n, n**2)
163    return np.sum(Zq*Aw*sin_theta)*8/(4*pi)
164
165
166def gridded_integrals(q=0.001, dnn=DNN, d_factor=D_FACTOR, n=300):
167    """
168    Compute the integral on a regular grid using rectangular, trapezoidal,
169    simpsons, and romberg integration.  Romberg integration requires that
170    the grid be of size n = 2**k + 1.
171    """
172    theta = np.linspace(0, pi/2, n)
173    phi = np.linspace(0, pi/2, n)
174    Atheta, Aphi = np.meshgrid(theta, phi)
175    Zq = kernel(q=Q, dnn=dnn, d_factor=d_factor, theta=Atheta, phi=Aphi)
176    Zq *= abs(sin(Atheta))
177    dx, dy = theta[1]-theta[0], phi[1]-phi[0]
178    print("rect", n, np.sum(Zq)*dx*dy*8/(4*pi))
179    print("trapz", n, np.trapz(np.trapz(Zq, dx=dx), dx=dy)*8/(4*pi))
180    print("simpson", n, simps(simps(Zq, dx=dx), dx=dy)*8/(4*pi))
181    print("romb", n, romb(romb(Zq, dx=dx), dx=dy)*8/(4*pi))
182    print("gridded %d evals ="%n, n**2)
183
184def plot(q=0.001, dnn=DNN, d_factor=D_FACTOR, n=300):
185    """
186    Plot the 2D surface that needs to be integrated in order to compute
187    the BCC S(q) at a particular q, dnn and d_factor.  *n* is the number
188    of points in the grid.
189    """
190    theta = np.linspace(0, pi, n)
191    phi = np.linspace(0, 2*pi, n)
192    #theta = np.linspace(0, pi/2, n)
193    #phi = np.linspace(0, pi/2, n)
194    Atheta, Aphi = np.meshgrid(theta, phi)
195    Zq = kernel(q=Q, dnn=dnn, d_factor=d_factor, theta=Atheta, phi=Aphi)
196    Zq *= abs(sin(Atheta))
197    pylab.pcolor(degrees(theta), degrees(phi), log10(np.fmax(Zq, 1.e-6)))
198    pylab.axis('tight')
199    pylab.title("%s Z(q) for q=%g, dnn=%g d_factor=%g"
200                % (KERNEL.__name__, q, dnn, d_factor))
201    pylab.xlabel("theta (degrees)")
202    pylab.ylabel("phi (degrees)")
203    cbar = pylab.colorbar()
204    cbar.set_label('log10 S(q)')
205    pylab.show()
206
207if __name__ == "__main__":
208    #print("gauss", 20, gauss_quad(n=20))
209    #print("gauss", 76, gauss_quad(n=76))
210    #print("gauss", 150, gauss_quad(n=150))
211    #print("dblquad", scipy_dblquad())
212    #print("semi romberg", semi_romberg())
213    #gridded_integrals(n=2**8+1)
214    #gridded_integrals(n=2**10+1)
215    #gridded_integrals(n=2**13+1)
216    #print("romberg", scipy_romberg())
217    plot(n=400)
Note: See TracBrowser for help on using the repository browser.