1 | #/usr/bin/env python |
---|
2 | # -*- coding: utf-8 -*- |
---|
3 | ''' |
---|
4 | *GSASII small angle calculation module* |
---|
5 | ======================================= |
---|
6 | |
---|
7 | ''' |
---|
8 | ########### SVN repository information ################### |
---|
9 | # $Date: 2019-08-16 15:58:08 +0000 (Fri, 16 Aug 2019) $ |
---|
10 | # $Author: toby $ |
---|
11 | # $Revision: 4095 $ |
---|
12 | # $URL: trunk/GSASIIsasd.py $ |
---|
13 | # $Id: GSASIIsasd.py 4095 2019-08-16 15:58:08Z toby $ |
---|
14 | ########### SVN repository information ################### |
---|
15 | from __future__ import division, print_function |
---|
16 | import os |
---|
17 | import math |
---|
18 | |
---|
19 | import numpy as np |
---|
20 | import scipy.special as scsp |
---|
21 | import scipy.optimize as so |
---|
22 | #import pdb |
---|
23 | |
---|
24 | import GSASIIpath |
---|
25 | GSASIIpath.SetVersionNumber("$Revision: 4095 $") |
---|
26 | import GSASIIpwd as G2pwd |
---|
27 | |
---|
28 | # trig functions in degrees |
---|
29 | sind = lambda x: math.sin(x*math.pi/180.) |
---|
30 | asind = lambda x: 180.*math.asin(x)/math.pi |
---|
31 | tand = lambda x: math.tan(x*math.pi/180.) |
---|
32 | atand = lambda x: 180.*math.atan(x)/math.pi |
---|
33 | atan2d = lambda y,x: 180.*math.atan2(y,x)/math.pi |
---|
34 | cosd = lambda x: math.cos(x*math.pi/180.) |
---|
35 | acosd = lambda x: 180.*math.acos(x)/math.pi |
---|
36 | rdsq2d = lambda x,p: round(1.0/math.sqrt(x),p) |
---|
37 | #numpy versions |
---|
38 | npsind = lambda x: np.sin(x*np.pi/180.) |
---|
39 | npasind = lambda x: 180.*np.arcsin(x)/math.pi |
---|
40 | npcosd = lambda x: np.cos(x*math.pi/180.) |
---|
41 | npacosd = lambda x: 180.*np.arccos(x)/math.pi |
---|
42 | nptand = lambda x: np.tan(x*math.pi/180.) |
---|
43 | npatand = lambda x: 180.*np.arctan(x)/np.pi |
---|
44 | npatan2d = lambda y,x: 180.*np.arctan2(y,x)/np.pi |
---|
45 | npT2stl = lambda tth, wave: 2.0*npsind(tth/2.0)/wave |
---|
46 | npT2q = lambda tth,wave: 2.0*np.pi*npT2stl(tth,wave) |
---|
47 | |
---|
48 | ############################################################################### |
---|
49 | #### Particle form factors |
---|
50 | ############################################################################### |
---|
51 | |
---|
52 | def SphereFF(Q,R,args=()): |
---|
53 | ''' Compute hard sphere form factor - can use numpy arrays |
---|
54 | :param float Q: Q value array (usually in A-1) |
---|
55 | :param float R: sphere radius (Usually in A - must match Q-1 units) |
---|
56 | :param array args: ignored |
---|
57 | :returns: form factors as array as needed (float) |
---|
58 | ''' |
---|
59 | QR = Q[:,np.newaxis]*R |
---|
60 | return (3./(QR**3))*(np.sin(QR)-(QR*np.cos(QR))) |
---|
61 | |
---|
62 | def SphericalShellFF(Q,R,args=()): |
---|
63 | ''' Compute spherical shell form factor - can use numpy arrays |
---|
64 | :param float Q: Q value array (usually in A-1) |
---|
65 | :param float R: sphere radius (Usually in A - must match Q-1 units) |
---|
66 | :param array args: [float r]: controls the shell thickness: R_inner = min(r*R,R), R_outer = max(r*R,R) |
---|
67 | :returns float: form factors as array as needed |
---|
68 | |
---|
69 | Contributed by: L.A. Avakyan, Southern Federal University, Russia |
---|
70 | ''' |
---|
71 | r = args[0] |
---|
72 | if r < 0: # truncate to positive value |
---|
73 | r = 0 |
---|
74 | if r < 1: # r controls inner sphere radius |
---|
75 | return SphereFF(Q,R) - SphereFF(Q,R*r) |
---|
76 | else: # r controls outer sphere radius |
---|
77 | return SphereFF(Q,R*r) - SphereFF(Q,R) |
---|
78 | |
---|
79 | def SpheroidFF(Q,R,args): |
---|
80 | ''' Compute form factor of cylindrically symmetric ellipsoid (spheroid) |
---|
81 | - can use numpy arrays for R & AR; will return corresponding numpy array |
---|
82 | param float Q : Q value array (usually in A-1) |
---|
83 | param float R: radius along 2 axes of spheroid |
---|
84 | param array args: [float AR]: aspect ratio so 3rd axis = R*AR |
---|
85 | returns float: form factors as array as needed |
---|
86 | ''' |
---|
87 | NP = 50 |
---|
88 | AR = args[0] |
---|
89 | if 0.99 < AR < 1.01: |
---|
90 | return SphereFF(Q,R,0) |
---|
91 | else: |
---|
92 | cth = np.linspace(0,1.,NP) |
---|
93 | try: |
---|
94 | Rct = R[:,np.newaxis]*np.sqrt(1.+(AR**2-1.)*cth**2) |
---|
95 | except: |
---|
96 | Rct = R*np.sqrt(1.+(AR**2-1.)*cth**2) |
---|
97 | return np.sqrt(np.sum(SphereFF(Q[:,np.newaxis],Rct,0)**2,axis=2)/NP) |
---|
98 | |
---|
99 | def CylinderFF(Q,R,args): |
---|
100 | ''' Compute form factor for cylinders - can use numpy arrays |
---|
101 | param float Q: Q value array (A-1) |
---|
102 | param float R: cylinder radius (A) |
---|
103 | param array args: [float L]: cylinder length (A) |
---|
104 | returns float: form factor |
---|
105 | ''' |
---|
106 | L = args[0] |
---|
107 | NP = 200 |
---|
108 | alp = np.linspace(0,np.pi/2.,NP) |
---|
109 | QL = Q[:,np.newaxis]*L |
---|
110 | LBessArg = 0.5*QL[:,:,np.newaxis]**np.cos(alp) |
---|
111 | LBess = np.where(LBessArg<1.e-6,1.,np.sin(LBessArg)/LBessArg) |
---|
112 | QR = Q[:,np.newaxis]*R |
---|
113 | SBessArg = QR[:,:,np.newaxis]*np.sin(alp) |
---|
114 | SBess = np.where(SBessArg<1.e-6,0.5,scsp.jv(1,SBessArg)/SBessArg) |
---|
115 | LSBess = LBess*SBess |
---|
116 | return np.sqrt(2.*np.pi*np.sum(np.sin(alp)*LSBess**2,axis=2)/NP) |
---|
117 | |
---|
118 | def CylinderDFF(Q,L,args): |
---|
119 | ''' Compute form factor for cylinders - can use numpy arrays |
---|
120 | param float Q: Q value array (A-1) |
---|
121 | param float L: cylinder half length (A) |
---|
122 | param array args: [float R]: cylinder radius (A) |
---|
123 | returns float: form factor |
---|
124 | ''' |
---|
125 | R = args[0] |
---|
126 | return CylinderFF(Q,R,args=[2.*L]) |
---|
127 | |
---|
128 | def CylinderARFF(Q,R,args): |
---|
129 | ''' Compute form factor for cylinders - can use numpy arrays |
---|
130 | param float Q: Q value array (A-1) |
---|
131 | param float R: cylinder radius (A) |
---|
132 | param array args: [float AR]: cylinder aspect ratio = L/D = L/2R |
---|
133 | returns float: form factor |
---|
134 | ''' |
---|
135 | AR = args[0] |
---|
136 | return CylinderFF(Q,R,args=[2.*R*AR]) |
---|
137 | |
---|
138 | def UniSphereFF(Q,R,args=0): |
---|
139 | ''' Compute form factor for unified sphere - can use numpy arrays |
---|
140 | param float Q: Q value array (A-1) |
---|
141 | param float R: cylinder radius (A) |
---|
142 | param array args: ignored |
---|
143 | returns float: form factor |
---|
144 | ''' |
---|
145 | Rg = np.sqrt(3./5.)*R |
---|
146 | B = np.pi*1.62/(Rg**4) #are we missing *np.pi? 1.62 = 6*(3/5)**2/(4/3) sense? |
---|
147 | QstV = Q[:,np.newaxis]/(scsp.erf(Q[:,np.newaxis]*Rg/np.sqrt(6)))**3 |
---|
148 | return np.sqrt(np.exp((-Q[:,np.newaxis]**2*Rg**2)/3.)+(B/QstV**4)) |
---|
149 | |
---|
150 | def UniRodFF(Q,R,args): |
---|
151 | ''' Compute form factor for unified rod - can use numpy arrays |
---|
152 | param float Q: Q value array (A-1) |
---|
153 | param float R: cylinder radius (A) |
---|
154 | param array args: [float R]: cylinder radius (A) |
---|
155 | returns float: form factor |
---|
156 | ''' |
---|
157 | L = args[0] |
---|
158 | Rg2 = np.sqrt(R**2/2+L**2/12) |
---|
159 | B2 = np.pi/L |
---|
160 | Rg1 = np.sqrt(3.)*R/2. |
---|
161 | G1 = (2./3.)*R/L |
---|
162 | B1 = 4.*(L+R)/(R**3*L**2) |
---|
163 | QstV = Q[:,np.newaxis]/(scsp.erf(Q[:,np.newaxis]*Rg2/np.sqrt(6)))**3 |
---|
164 | FF = np.exp(-Q[:,np.newaxis]**2*Rg2**2/3.)+(B2/QstV)*np.exp(-Rg1**2*Q[:,np.newaxis]**2/3.) |
---|
165 | QstV = Q[:,np.newaxis]/(scsp.erf(Q[:,np.newaxis]*Rg1/np.sqrt(6)))**3 |
---|
166 | FF += G1*np.exp(-Q[:,np.newaxis]**2*Rg1**2/3.)+(B1/QstV**4) |
---|
167 | return np.sqrt(FF) |
---|
168 | |
---|
169 | def UniRodARFF(Q,R,args): |
---|
170 | ''' Compute form factor for unified rod of fixed aspect ratio - can use numpy arrays |
---|
171 | param float Q: Q value array (A-1) |
---|
172 | param float R: cylinder radius (A) |
---|
173 | param array args: [float AR]: cylinder aspect ratio = L/D = L/2R |
---|
174 | returns float: form factor |
---|
175 | ''' |
---|
176 | AR = args[0] |
---|
177 | return UniRodFF(Q,R,args=[2.*AR*R,]) |
---|
178 | |
---|
179 | def UniDiskFF(Q,R,args): |
---|
180 | ''' Compute form factor for unified disk - can use numpy arrays |
---|
181 | param float Q: Q value array (A-1) |
---|
182 | param float R: cylinder radius (A) |
---|
183 | param array args: [float T]: disk thickness (A) |
---|
184 | returns float: form factor |
---|
185 | ''' |
---|
186 | T = args[0] |
---|
187 | Rg2 = np.sqrt(R**2/2.+T**2/12.) |
---|
188 | B2 = 2./R**2 |
---|
189 | Rg1 = np.sqrt(3.)*T/2. |
---|
190 | RgC2 = 1.1*Rg1 |
---|
191 | G1 = (2./3.)*(T/R)**2 |
---|
192 | B1 = 4.*(T+R)/(R**3*T**2) |
---|
193 | QstV = Q[:,np.newaxis]/(scsp.erf(Q[:,np.newaxis]*Rg2/np.sqrt(6)))**3 |
---|
194 | FF = np.exp(-Q[:,np.newaxis]**2*Rg2**2/3.)+(B2/QstV**2)*np.exp(-RgC2**2*Q[:,np.newaxis]**2/3.) |
---|
195 | QstV = Q[:,np.newaxis]/(scsp.erf(Q[:,np.newaxis]*Rg1/np.sqrt(6)))**3 |
---|
196 | FF += G1*np.exp(-Q[:,np.newaxis]**2*Rg1**2/3.)+(B1/QstV**4) |
---|
197 | return np.sqrt(FF) |
---|
198 | |
---|
199 | def UniTubeFF(Q,R,args): |
---|
200 | ''' Compute form factor for unified tube - can use numpy arrays |
---|
201 | assumes that core of tube is same as the matrix/solvent so contrast |
---|
202 | is from tube wall vs matrix |
---|
203 | param float Q: Q value array (A-1) |
---|
204 | param float R: cylinder radius (A) |
---|
205 | param array args: [float L,T]: tube length & wall thickness(A) |
---|
206 | returns float: form factor |
---|
207 | ''' |
---|
208 | L,T = args[:2] |
---|
209 | Ri = R-T |
---|
210 | DR2 = R**2-Ri**2 |
---|
211 | Vt = np.pi*DR2*L |
---|
212 | Rg3 = np.sqrt(DR2/2.+L**2/12.) |
---|
213 | B1 = 4.*np.pi**2*(DR2+L*(R+Ri))/Vt**2 |
---|
214 | B2 = np.pi**2*T/Vt |
---|
215 | B3 = np.pi/L |
---|
216 | QstV = Q[:,np.newaxis]/(scsp.erf(Q[:,np.newaxis]*Rg3/np.sqrt(6)))**3 |
---|
217 | FF = np.exp(-Q[:,np.newaxis]**2*Rg3**2/3.)+(B3/QstV)*np.exp(-Q[:,np.newaxis]**2*R**2/3.) |
---|
218 | QstV = Q[:,np.newaxis]/(scsp.erf(Q[:,np.newaxis]*R/np.sqrt(6)))**3 |
---|
219 | FF += (B2/QstV**2)*np.exp(-Q[:,np.newaxis]**2*T**2/3.) |
---|
220 | QstV = Q[:,np.newaxis]/(scsp.erf(Q[:,np.newaxis]*T/np.sqrt(6)))**3 |
---|
221 | FF += B1/QstV**4 |
---|
222 | return np.sqrt(FF) |
---|
223 | |
---|
224 | ############################################################################### |
---|
225 | #### Particle volumes |
---|
226 | ############################################################################### |
---|
227 | |
---|
228 | def SphereVol(R,args=()): |
---|
229 | ''' Compute volume of sphere |
---|
230 | - numpy array friendly |
---|
231 | param float R: sphere radius |
---|
232 | param array args: ignored |
---|
233 | returns float: volume |
---|
234 | ''' |
---|
235 | return (4./3.)*np.pi*R**3 |
---|
236 | |
---|
237 | def SphericalShellVol(R,args): |
---|
238 | ''' Compute volume of spherical shell |
---|
239 | - numpy array friendly |
---|
240 | param float R: sphere radius |
---|
241 | param array args: [float r]: controls shell thickness, see SphericalShellFF description |
---|
242 | returns float: volume |
---|
243 | ''' |
---|
244 | r = args[0] |
---|
245 | if r < 0: |
---|
246 | r = 0 |
---|
247 | if r < 1: |
---|
248 | return SphereVol(R) - SphereVol(R*r) |
---|
249 | else: |
---|
250 | return SphereVol(R*r) - SphereVol(R) |
---|
251 | |
---|
252 | def SpheroidVol(R,args): |
---|
253 | ''' Compute volume of cylindrically symmetric ellipsoid (spheroid) |
---|
254 | - numpy array friendly |
---|
255 | param float R: radius along 2 axes of spheroid |
---|
256 | param array args: [float AR]: aspect ratio so radius of 3rd axis = R*AR |
---|
257 | returns float: volume |
---|
258 | ''' |
---|
259 | AR = args[0] |
---|
260 | return AR*SphereVol(R) |
---|
261 | |
---|
262 | def CylinderVol(R,args): |
---|
263 | ''' Compute cylinder volume for radius & length |
---|
264 | - numpy array friendly |
---|
265 | param float R: diameter (A) |
---|
266 | param array args: [float L]: length (A) |
---|
267 | returns float:volume (A^3) |
---|
268 | ''' |
---|
269 | L = args[0] |
---|
270 | return np.pi*L*R**2 |
---|
271 | |
---|
272 | def CylinderDVol(L,args): |
---|
273 | ''' Compute cylinder volume for length & diameter |
---|
274 | - numpy array friendly |
---|
275 | param float: L half length (A) |
---|
276 | param array args: [float D]: diameter (A) |
---|
277 | returns float:volume (A^3) |
---|
278 | ''' |
---|
279 | D = args[0] |
---|
280 | return CylinderVol(D/2.,[2.*L,]) |
---|
281 | |
---|
282 | def CylinderARVol(R,args): |
---|
283 | ''' Compute cylinder volume for radius & aspect ratio = L/D |
---|
284 | - numpy array friendly |
---|
285 | param float: R radius (A) |
---|
286 | param array args: [float AR]: =L/D=L/2R aspect ratio |
---|
287 | returns float:volume |
---|
288 | ''' |
---|
289 | AR = args[0] |
---|
290 | return CylinderVol(R,[2.*R*AR,]) |
---|
291 | |
---|
292 | def UniSphereVol(R,args=()): |
---|
293 | ''' Compute volume of sphere |
---|
294 | - numpy array friendly |
---|
295 | param float R: sphere radius |
---|
296 | param array args: ignored |
---|
297 | returns float: volume |
---|
298 | ''' |
---|
299 | return SphereVol(R) |
---|
300 | |
---|
301 | def UniRodVol(R,args): |
---|
302 | ''' Compute cylinder volume for radius & length |
---|
303 | - numpy array friendly |
---|
304 | param float R: diameter (A) |
---|
305 | param array args: [float L]: length (A) |
---|
306 | returns float:volume (A^3) |
---|
307 | ''' |
---|
308 | L = args[0] |
---|
309 | return CylinderVol(R,[L,]) |
---|
310 | |
---|
311 | def UniRodARVol(R,args): |
---|
312 | ''' Compute rod volume for radius & aspect ratio |
---|
313 | - numpy array friendly |
---|
314 | param float R: diameter (A) |
---|
315 | param array args: [float AR]: =L/D=L/2R aspect ratio |
---|
316 | returns float:volume (A^3) |
---|
317 | ''' |
---|
318 | AR = args[0] |
---|
319 | return CylinderARVol(R,[AR,]) |
---|
320 | |
---|
321 | def UniDiskVol(R,args): |
---|
322 | ''' Compute disk volume for radius & thickness |
---|
323 | - numpy array friendly |
---|
324 | param float R: diameter (A) |
---|
325 | param array args: [float T]: thickness |
---|
326 | returns float:volume (A^3) |
---|
327 | ''' |
---|
328 | T = args[0] |
---|
329 | return CylinderVol(R,[T,]) |
---|
330 | |
---|
331 | def UniTubeVol(R,args): |
---|
332 | ''' Compute tube volume for radius, length & wall thickness |
---|
333 | - numpy array friendly |
---|
334 | param float R: diameter (A) |
---|
335 | param array args: [float L,T]: tube length & wall thickness(A) |
---|
336 | returns float: volume (A^3) of tube wall |
---|
337 | ''' |
---|
338 | L,T = args[:2] |
---|
339 | return CylinderVol(R,[L,])-CylinderVol(R-T,[L,]) |
---|
340 | |
---|
341 | ################################################################################ |
---|
342 | #### Distribution functions & their cumulative fxns |
---|
343 | ################################################################################ |
---|
344 | |
---|
345 | def LogNormalDist(x,pos,args): |
---|
346 | ''' Standard LogNormal distribution - numpy friendly on x axis |
---|
347 | ref: http://www.itl.nist.gov/div898/handbook/index.htm 1.3.6.6.9 |
---|
348 | param float x: independent axis (can be numpy array) |
---|
349 | param float pos: location of distribution |
---|
350 | param float scale: width of distribution (m) |
---|
351 | param float shape: shape - (sigma of log(LogNormal)) |
---|
352 | returns float: LogNormal distribution |
---|
353 | ''' |
---|
354 | scale,shape = args |
---|
355 | return np.exp(-np.log((x-pos)/scale)**2/(2.*shape**2))/(np.sqrt(2.*np.pi)*(x-pos)*shape) |
---|
356 | |
---|
357 | def GaussDist(x,pos,args): |
---|
358 | ''' Standard Normal distribution - numpy friendly on x axis |
---|
359 | param float x: independent axis (can be numpy array) |
---|
360 | param float pos: location of distribution |
---|
361 | param float scale: width of distribution (sigma) |
---|
362 | param float shape: not used |
---|
363 | returns float: Normal distribution |
---|
364 | ''' |
---|
365 | scale = args[0] |
---|
366 | return (1./(scale*np.sqrt(2.*np.pi)))*np.exp(-(x-pos)**2/(2.*scale**2)) |
---|
367 | |
---|
368 | def LSWDist(x,pos,args=[]): |
---|
369 | ''' Lifshitz-Slyozov-Wagner Ostwald ripening distribution - numpy friendly on x axis |
---|
370 | ref: |
---|
371 | param float x: independent axis (can be numpy array) |
---|
372 | param float pos: location of distribution |
---|
373 | param float scale: not used |
---|
374 | param float shape: not used |
---|
375 | returns float: LSW distribution |
---|
376 | ''' |
---|
377 | redX = x/pos |
---|
378 | result = (81.*2**(-5/3.))*(redX**2*np.exp(-redX/(1.5-redX)))/((1.5-redX)**(11/3.)*(3.-redX)**(7/3.)) |
---|
379 | |
---|
380 | return np.nan_to_num(result/pos) |
---|
381 | |
---|
382 | def SchulzZimmDist(x,pos,args): |
---|
383 | ''' Schulz-Zimm macromolecule distribution - numpy friendly on x axis |
---|
384 | ref: http://goldbook.iupac.org/S05502.html |
---|
385 | param float x: independent axis (can be numpy array) |
---|
386 | param float pos: location of distribution |
---|
387 | param float scale: width of distribution (sigma) |
---|
388 | param float shape: not used |
---|
389 | returns float: Schulz-Zimm distribution |
---|
390 | ''' |
---|
391 | scale = args[0] |
---|
392 | b = (2.*pos/scale)**2 |
---|
393 | a = b/pos |
---|
394 | if b<70: #why bother? |
---|
395 | return (a**(b+1.))*x**b*np.exp(-a*x)/scsp.gamma(b+1.) |
---|
396 | else: |
---|
397 | return np.exp((b+1.)*np.log(a)-scsp.gammaln(b+1.)+b*np.log(x)-(a*x)) |
---|
398 | |
---|
399 | def LogNormalCume(x,pos,args): |
---|
400 | ''' Standard LogNormal cumulative distribution - numpy friendly on x axis |
---|
401 | ref: http://www.itl.nist.gov/div898/handbook/index.htm 1.3.6.6.9 |
---|
402 | param float x: independent axis (can be numpy array) |
---|
403 | param float pos: location of distribution |
---|
404 | param float scale: width of distribution (sigma) |
---|
405 | param float shape: shape parameter |
---|
406 | returns float: LogNormal cumulative distribution |
---|
407 | ''' |
---|
408 | scale,shape = args |
---|
409 | lredX = np.log((x-pos)/scale) |
---|
410 | return (scsp.erf((lredX/shape)/np.sqrt(2.))+1.)/2. |
---|
411 | |
---|
412 | def GaussCume(x,pos,args): |
---|
413 | ''' Standard Normal cumulative distribution - numpy friendly on x axis |
---|
414 | param float x: independent axis (can be numpy array) |
---|
415 | param float pos: location of distribution |
---|
416 | param float scale: width of distribution (sigma) |
---|
417 | param float shape: not used |
---|
418 | returns float: Normal cumulative distribution |
---|
419 | ''' |
---|
420 | scale = args[0] |
---|
421 | redX = (x-pos)/scale |
---|
422 | return (scsp.erf(redX/np.sqrt(2.))+1.)/2. |
---|
423 | |
---|
424 | def LSWCume(x,pos,args=[]): |
---|
425 | ''' Lifshitz-Slyozov-Wagner Ostwald ripening cumulative distribution - numpy friendly on x axis |
---|
426 | param float x: independent axis (can be numpy array) |
---|
427 | param float pos: location of distribution |
---|
428 | param float scale: not used |
---|
429 | param float shape: not used |
---|
430 | returns float: LSW cumulative distribution |
---|
431 | ''' |
---|
432 | nP = 500 |
---|
433 | xMin,xMax = [0.,2.*pos] |
---|
434 | X = np.linspace(xMin,xMax,nP,True) |
---|
435 | fxn = LSWDist(X,pos) |
---|
436 | mat = np.outer(np.ones(nP),fxn) |
---|
437 | cume = np.sum(np.tril(mat),axis=1)/np.sum(fxn) |
---|
438 | return np.interp(x,X,cume,0,1) |
---|
439 | |
---|
440 | def SchulzZimmCume(x,pos,args): |
---|
441 | ''' Schulz-Zimm cumulative distribution - numpy friendly on x axis |
---|
442 | param float x: independent axis (can be numpy array) |
---|
443 | param float pos: location of distribution |
---|
444 | param float scale: width of distribution (sigma) |
---|
445 | param float shape: not used |
---|
446 | returns float: Normal distribution |
---|
447 | ''' |
---|
448 | scale = args[0] |
---|
449 | nP = 500 |
---|
450 | xMin = np.fmax([0.,pos-4.*scale]) |
---|
451 | xMax = np.fmin([pos+4.*scale,1.e5]) |
---|
452 | X = np.linspace(xMin,xMax,nP,True) |
---|
453 | fxn = LSWDist(X,pos) |
---|
454 | mat = np.outer(np.ones(nP),fxn) |
---|
455 | cume = np.sum(np.tril(mat),axis=1)/np.sum(fxn) |
---|
456 | return np.interp(x,X,cume,0,1) |
---|
457 | |
---|
458 | return [] |
---|
459 | |
---|
460 | ################################################################################ |
---|
461 | #### Structure factors for condensed systems |
---|
462 | ################################################################################ |
---|
463 | |
---|
464 | def DiluteSF(Q,args=[]): |
---|
465 | ''' Default: no structure factor correction for dilute system |
---|
466 | ''' |
---|
467 | return np.ones_like(Q) #or return 1.0 |
---|
468 | |
---|
469 | def HardSpheresSF(Q,args): |
---|
470 | ''' Computes structure factor for not dilute monodisperse hard spheres |
---|
471 | Refs.: PERCUS,YEVICK PHYS. REV. 110 1 (1958),THIELE J. CHEM PHYS. 39 474 (1968), |
---|
472 | WERTHEIM PHYS. REV. LETT. 47 1462 (1981) |
---|
473 | |
---|
474 | param float Q: Q value array (A-1) |
---|
475 | param array args: [float R, float VolFrac]: interparticle distance & volume fraction |
---|
476 | returns numpy array S(Q) |
---|
477 | ''' |
---|
478 | |
---|
479 | R,VolFr = args |
---|
480 | denom = (1.-VolFr)**4 |
---|
481 | num = (1.+2.*VolFr)**2 |
---|
482 | alp = num/denom |
---|
483 | bet = -6.*VolFr*(1.+VolFr/2.)**2/denom |
---|
484 | gamm = 0.5*VolFr*num/denom |
---|
485 | A = 2.*Q*R |
---|
486 | A2 = A**2 |
---|
487 | A3 = A**3 |
---|
488 | A4 = A**4 |
---|
489 | Rca = np.cos(A) |
---|
490 | Rsa = np.sin(A) |
---|
491 | calp = alp*(Rsa/A2-Rca/A) |
---|
492 | cbet = bet*(2.*Rsa/A2-(A2-2.)*Rca/A3-2./A3) |
---|
493 | cgam = gamm*(-Rca/A+(4./A)*((3.*A2-6.)*Rca/A4+(A2-6.)*Rsa/A3+6./A4)) |
---|
494 | pfac = -24.*VolFr/A |
---|
495 | C = pfac*(calp+cbet+cgam) |
---|
496 | return 1./(1.-C) |
---|
497 | |
---|
498 | def SquareWellSF(Q,args): |
---|
499 | '''Computes structure factor for not dilute monodisperse hard sphere with a |
---|
500 | square well potential interaction. |
---|
501 | Refs.: SHARMA,SHARMA, PHYSICA 89A,(1977),213- |
---|
502 | |
---|
503 | :param float Q: Q value array (A-1) |
---|
504 | :param array args: [float R, float VolFrac, float depth, float width]: |
---|
505 | interparticle distance, volume fraction (<0.08), well depth (e/kT<1.5kT), |
---|
506 | well width |
---|
507 | :returns: numpy array S(Q) |
---|
508 | well depth > 0 attractive & values outside above limits nonphysical cf. |
---|
509 | Monte Carlo simulations |
---|
510 | ''' |
---|
511 | R,VolFr,Depth,Width = args |
---|
512 | eta = VolFr |
---|
513 | eta2 = eta*eta |
---|
514 | eta3 = eta*eta2 |
---|
515 | eta4 = eta*eta3 |
---|
516 | etam1 = 1. - eta |
---|
517 | etam14 = etam1**4 |
---|
518 | alp = ( ( (1. + 2.*eta)**2 ) + eta3*( eta-4.0 ) )/etam14 |
---|
519 | bet = -(eta/3.0) * ( 18. + 20.*eta - 12.*eta2 + eta4 )/etam14 |
---|
520 | gam = 0.5*eta*( (1. + 2.*eta)**2 + eta3*(eta-4.) )/etam14 |
---|
521 | |
---|
522 | SK = 2.*Q*R |
---|
523 | SK2 = SK*SK |
---|
524 | SK3 = SK*SK2 |
---|
525 | SK4 = SK3*SK |
---|
526 | T1 = alp * SK3 * ( np.sin(SK) - SK * np.cos(SK) ) |
---|
527 | T2 = bet * SK2 * ( 2.*SK*np.sin(SK) - (SK2-2.)*np.cos(SK) - 2.0 ) |
---|
528 | T3 = ( 4.0*SK3 - 24.*SK ) * np.sin(SK) |
---|
529 | T3 = T3 - ( SK4 - 12.0*SK2 + 24.0 )*np.cos(SK) + 24.0 |
---|
530 | T3 = gam*T3 |
---|
531 | T4 = -Depth*SK3*(np.sin(Width*SK) - Width*SK*np.cos(Width*SK)+ SK*np.cos(SK) - np.sin(SK) ) |
---|
532 | CK = -24.0*eta*( T1 + T2 + T3 + T4 )/SK3/SK3 |
---|
533 | return 1./(1.-CK) |
---|
534 | |
---|
535 | def StickyHardSpheresSF(Q,args): |
---|
536 | ''' Computes structure factor for not dilute monodisperse hard spheres |
---|
537 | Refs.: PERCUS,YEVICK PHYS. REV. 110 1 (1958),THIELE J. CHEM PHYS. 39 474 (1968), |
---|
538 | WERTHEIM PHYS. REV. LETT. 47 1462 (1981) |
---|
539 | |
---|
540 | param float Q: Q value array (A-1) |
---|
541 | param array args: [float R, float VolFrac]: sphere radius & volume fraction |
---|
542 | returns numpy array S(Q) |
---|
543 | ''' |
---|
544 | R,VolFr,epis,sticky = args |
---|
545 | eta = VolFr/(1.0-epis)/(1.0-epis)/(1.0-epis) |
---|
546 | sig = 2.0 * R |
---|
547 | aa = sig/(1.0 - epis) |
---|
548 | etam1 = 1.0 - eta |
---|
549 | # SOLVE QUADRATIC FOR LAMBDA |
---|
550 | qa = eta/12.0 |
---|
551 | qb = -1.0*(sticky + eta/(etam1)) |
---|
552 | qc = (1.0 + eta/2.0)/(etam1*etam1) |
---|
553 | radic = qb*qb - 4.0*qa*qc |
---|
554 | # KEEP THE SMALLER ROOT, THE LARGER ONE IS UNPHYSICAL |
---|
555 | lam1 = (-1.0*qb-np.sqrt(radic))/(2.0*qa) |
---|
556 | lam2 = (-1.0*qb+np.sqrt(radic))/(2.0*qa) |
---|
557 | lam = min(lam1,lam2) |
---|
558 | mu = lam*eta*etam1 |
---|
559 | alp = (1.0 + 2.0*eta - mu)/(etam1*etam1) |
---|
560 | bet = (mu - 3.0*eta)/(2.0*etam1*etam1) |
---|
561 | # CALCULATE THE STRUCTURE FACTOR<P></P> |
---|
562 | kk = Q*aa |
---|
563 | k2 = kk*kk |
---|
564 | k3 = kk*k2 |
---|
565 | ds = np.sin(kk) |
---|
566 | dc = np.cos(kk) |
---|
567 | aq1 = ((ds - kk*dc)*alp)/k3 |
---|
568 | aq2 = (bet*(1.0-dc))/k2 |
---|
569 | aq3 = (lam*ds)/(12.0*kk) |
---|
570 | aq = 1.0 + 12.0*eta*(aq1+aq2-aq3) |
---|
571 | |
---|
572 | bq1 = alp*(0.5/kk - ds/k2 + (1.0 - dc)/k3) |
---|
573 | bq2 = bet*(1.0/kk - ds/k2) |
---|
574 | bq3 = (lam/12.0)*((1.0 - dc)/kk) |
---|
575 | bq = 12.0*eta*(bq1+bq2-bq3) |
---|
576 | sq = 1.0/(aq*aq +bq*bq) |
---|
577 | |
---|
578 | return sq |
---|
579 | |
---|
580 | #def HayterPenfoldSF(Q,args): #big & ugly function - do later (if ever) |
---|
581 | # pass |
---|
582 | |
---|
583 | def InterPrecipitateSF(Q,args): |
---|
584 | ''' Computes structure factor for precipitates in a matrix |
---|
585 | Refs.: E-Wen Huang, Peter K. Liaw, Lionel Porcar, Yun Liu, Yee-Lang Liu, |
---|
586 | Ji-Jung Kai, and Wei-Ren Chen,APPLIED PHYSICS LETTERS 93, 161904 (2008) |
---|
587 | R. Giordano, A. Grasso, and J. Teixeira, Phys. Rev. A 43, 6894 1991 |
---|
588 | param float Q: Q value array (A-1) |
---|
589 | param array args: [float R, float VolFr]: "radius" & volume fraction |
---|
590 | returns numpy array S(Q) |
---|
591 | ''' |
---|
592 | R,VolFr = args |
---|
593 | QV2 = Q**2*VolFr**2 |
---|
594 | top = 1 - np.exp(-QV2/4)*np.cos(2.*Q*R) |
---|
595 | bot = 1-2*np.exp(-QV2/4)*np.cos(2.*Q*R) + np.exp(-QV2/2) |
---|
596 | return 2*(top/bot) - 1 |
---|
597 | |
---|
598 | ################################################################################ |
---|
599 | ##### SB-MaxEnt |
---|
600 | ################################################################################ |
---|
601 | |
---|
602 | def G_matrix(q,r,contrast,FFfxn,Volfxn,args=()): |
---|
603 | '''Calculates the response matrix :math:`G(Q,r)` |
---|
604 | |
---|
605 | :param float q: :math:`Q` |
---|
606 | :param float r: :math:`r` |
---|
607 | :param float contrast: :math:`|\\Delta\\rho|^2`, the scattering contrast |
---|
608 | :param function FFfxn: form factor function FF(q,r,args) |
---|
609 | :param function Volfxn: volume function Vol(r,args) |
---|
610 | :returns float: G(Q,r) |
---|
611 | ''' |
---|
612 | FF = FFfxn(q,r,args) |
---|
613 | Vol = Volfxn(r,args) |
---|
614 | return 1.e-4*(contrast*Vol*FF**2).T #10^-20 vs 10^-24 |
---|
615 | |
---|
616 | ''' |
---|
617 | sbmaxent |
---|
618 | |
---|
619 | Entropy maximization routine as described in the article |
---|
620 | J Skilling and RK Bryan; MNRAS 211 (1984) 111 - 124. |
---|
621 | ("MNRAS": "Monthly Notices of the Royal Astronomical Society") |
---|
622 | |
---|
623 | :license: Copyright (c) 2013, UChicago Argonne, LLC |
---|
624 | :license: This file is distributed subject to a Software License Agreement found |
---|
625 | in the file LICENSE that is included with this distribution. |
---|
626 | |
---|
627 | References: |
---|
628 | |
---|
629 | 1. J Skilling and RK Bryan; MON NOT R ASTR SOC 211 (1984) 111 - 124. |
---|
630 | 2. JA Potton, GJ Daniell, and BD Rainford; Proc. Workshop |
---|
631 | Neutron Scattering Data Analysis, Rutherford |
---|
632 | Appleton Laboratory, UK, 1986; ed. MW Johnson, |
---|
633 | IOP Conference Series 81 (1986) 81 - 86, Institute |
---|
634 | of Physics, Bristol, UK. |
---|
635 | 3. ID Culverwell and GP Clarke; Ibid. 87 - 96. |
---|
636 | 4. JA Potton, GK Daniell, & BD Rainford, |
---|
637 | J APPL CRYST 21 (1988) 663 - 668. |
---|
638 | 5. JA Potton, GJ Daniell, & BD Rainford, |
---|
639 | J APPL CRYST 21 (1988) 891 - 897. |
---|
640 | |
---|
641 | ''' |
---|
642 | |
---|
643 | class MaxEntException(Exception): |
---|
644 | '''Any exception from this module''' |
---|
645 | pass |
---|
646 | |
---|
647 | def MaxEnt_SB(datum, sigma, G, base, IterMax, image_to_data=None, data_to_image=None, report=False): |
---|
648 | ''' |
---|
649 | do the complete Maximum Entropy algorithm of Skilling and Bryan |
---|
650 | |
---|
651 | :param float datum[]: |
---|
652 | :param float sigma[]: |
---|
653 | :param float[][] G: transformation matrix |
---|
654 | :param float base[]: |
---|
655 | :param int IterMax: |
---|
656 | :param obj image_to_data: opus function (defaults to opus) |
---|
657 | :param obj data_to_image: tropus function (defaults to tropus) |
---|
658 | |
---|
659 | :returns float[]: :math:`f(r) dr` |
---|
660 | ''' |
---|
661 | |
---|
662 | TEST_LIMIT = 0.05 # for convergence |
---|
663 | CHI_SQR_LIMIT = 0.01 # maximum difference in ChiSqr for a solution |
---|
664 | SEARCH_DIRECTIONS = 3 # <10. This code requires value = 3 |
---|
665 | RESET_STRAYS = 1 # was 0.001, correction of stray negative values |
---|
666 | DISTANCE_LIMIT_FACTOR = 0.1 # limitation on df to constrain runaways |
---|
667 | |
---|
668 | MAX_MOVE_LOOPS = 5000 # for no solution in routine: move, |
---|
669 | MOVE_PASSES = 0.001 # convergence test in routine: move |
---|
670 | |
---|
671 | def tropus (data, G): |
---|
672 | ''' |
---|
673 | tropus: transform data-space -> solution-space: [G] * data |
---|
674 | |
---|
675 | default definition, caller can use this definition or provide an alternative |
---|
676 | |
---|
677 | :param float[M] data: observations, ndarray of shape (M) |
---|
678 | :param float[M][N] G: transformation matrix, ndarray of shape (M,N) |
---|
679 | :returns float[N]: calculated image, ndarray of shape (N) |
---|
680 | ''' |
---|
681 | return G.dot(data) |
---|
682 | |
---|
683 | def opus (image, G): |
---|
684 | ''' |
---|
685 | opus: transform solution-space -> data-space: [G]^tr * image |
---|
686 | |
---|
687 | default definition, caller can use this definition or provide an alternative |
---|
688 | |
---|
689 | :param float[N] image: solution, ndarray of shape (N) |
---|
690 | :param float[M][N] G: transformation matrix, ndarray of shape (M,N) |
---|
691 | :returns float[M]: calculated data, ndarray of shape (M) |
---|
692 | ''' |
---|
693 | return np.dot(G.T,image) #G.transpose().dot(image) |
---|
694 | |
---|
695 | def Dist(s2, beta): |
---|
696 | '''measure the distance of this possible solution''' |
---|
697 | w = 0 |
---|
698 | n = beta.shape[0] |
---|
699 | for k in range(n): |
---|
700 | z = -sum(s2[k] * beta) |
---|
701 | w += beta[k] * z |
---|
702 | return w |
---|
703 | |
---|
704 | def ChiNow(ax, c1, c2, s1, s2): |
---|
705 | ''' |
---|
706 | ChiNow |
---|
707 | |
---|
708 | :returns tuple: (ChiNow computation of ``w``, beta) |
---|
709 | ''' |
---|
710 | |
---|
711 | bx = 1 - ax |
---|
712 | a = bx * c2 - ax * s2 |
---|
713 | b = -(bx * c1 - ax * s1) |
---|
714 | |
---|
715 | beta = ChoSol(a, b) |
---|
716 | w = 1.0 |
---|
717 | for k in range(SEARCH_DIRECTIONS): |
---|
718 | w += beta[k] * (c1[k] + 0.5*sum(c2[k] * beta)) |
---|
719 | return w, beta |
---|
720 | |
---|
721 | def ChoSol(a, b): |
---|
722 | ''' |
---|
723 | ChoSol: ? chop the solution vectors ? |
---|
724 | |
---|
725 | :returns: new vector beta |
---|
726 | ''' |
---|
727 | n = b.shape[0] |
---|
728 | fl = np.zeros((n,n)) |
---|
729 | bl = np.zeros_like(b) |
---|
730 | |
---|
731 | #print_arr("ChoSol: a", a) |
---|
732 | #print_vec("ChoSol: b", b) |
---|
733 | |
---|
734 | if (a[0][0] <= 0): |
---|
735 | msg = "ChoSol: a[0][0] = " |
---|
736 | msg += str(a[0][0]) |
---|
737 | msg += ' Value must be positive' |
---|
738 | raise MaxEntException(msg) |
---|
739 | |
---|
740 | # first, compute fl from a |
---|
741 | # note fl is a lower triangular matrix |
---|
742 | fl[0][0] = math.sqrt (a[0][0]) |
---|
743 | for i in (1, 2): |
---|
744 | fl[i][0] = a[i][0] / fl[0][0] |
---|
745 | for j in range(1, i+1): |
---|
746 | z = 0.0 |
---|
747 | for k in range(j): |
---|
748 | z += fl[i][k] * fl[j][k] |
---|
749 | #print "ChoSol: %d %d %d z = %lg" % ( i, j, k, z) |
---|
750 | z = a[i][j] - z |
---|
751 | if j == i: |
---|
752 | y = math.sqrt(max(0.,z)) |
---|
753 | else: |
---|
754 | y = z / fl[j][j] |
---|
755 | fl[i][j] = y |
---|
756 | #print_arr("ChoSol: fl", fl) |
---|
757 | |
---|
758 | # next, compute bl from fl and b |
---|
759 | bl[0] = b[0] / fl[0][0] |
---|
760 | for i in (1, 2): |
---|
761 | z = 0.0 |
---|
762 | for k in range(i): |
---|
763 | z += fl[i][k] * bl[k] |
---|
764 | #print "\t", i, k, z |
---|
765 | bl[i] = (b[i] - z) / fl[i][i] |
---|
766 | #print_vec("ChoSol: bl", bl) |
---|
767 | |
---|
768 | # last, compute beta from bl and fl |
---|
769 | beta = np.empty((n)) |
---|
770 | beta[-1] = bl[-1] / fl[-1][-1] |
---|
771 | for i in (1, 0): |
---|
772 | z = 0.0 |
---|
773 | for k in range(i+1, n): |
---|
774 | z += fl[k][i] * beta[k] |
---|
775 | #print "\t\t", i, k, 'z=', z |
---|
776 | beta[i] = (bl[i] - z) / fl[i][i] |
---|
777 | #print_vec("ChoSol: beta", beta) |
---|
778 | |
---|
779 | return beta |
---|
780 | |
---|
781 | def MaxEntMove(fSum, blank, chisq, chizer, c1, c2, s1, s2): |
---|
782 | ''' |
---|
783 | move beta one step closer towards the solution |
---|
784 | ''' |
---|
785 | a_lower, a_upper = 0., 1. # bracket "a" |
---|
786 | cmin, beta = ChiNow (a_lower, c1, c2, s1, s2) |
---|
787 | #print "MaxEntMove: cmin = %g" % cmin |
---|
788 | if cmin*chisq > chizer: |
---|
789 | ctarg = (1.0 + cmin)/2 |
---|
790 | else: |
---|
791 | ctarg = chizer/chisq |
---|
792 | f_lower = cmin - ctarg |
---|
793 | c_upper, beta = ChiNow (a_upper, c1, c2, s1, s2) |
---|
794 | f_upper = c_upper - ctarg |
---|
795 | |
---|
796 | fx = 2*MOVE_PASSES # just to start off |
---|
797 | loop = 1 |
---|
798 | while abs(fx) >= MOVE_PASSES and loop <= MAX_MOVE_LOOPS: |
---|
799 | a_new = (a_lower + a_upper) * 0.5 # search by bisection |
---|
800 | c_new, beta = ChiNow (a_new, c1, c2, s1, s2) |
---|
801 | fx = c_new - ctarg |
---|
802 | # tighten the search range for the next pass |
---|
803 | if f_lower*fx > 0: |
---|
804 | a_lower, f_lower = a_new, fx |
---|
805 | if f_upper*fx > 0: |
---|
806 | a_upper, f_upper = a_new, fx |
---|
807 | loop += 1 |
---|
808 | |
---|
809 | if abs(fx) >= MOVE_PASSES or loop > MAX_MOVE_LOOPS: |
---|
810 | msg = "MaxEntMove: Loop counter = " |
---|
811 | msg += str(MAX_MOVE_LOOPS) |
---|
812 | msg += ' No convergence in alpha chop' |
---|
813 | raise MaxEntException(msg) |
---|
814 | |
---|
815 | w = Dist (s2, beta); |
---|
816 | m = SEARCH_DIRECTIONS |
---|
817 | if (w > DISTANCE_LIMIT_FACTOR*fSum/blank): # invoke the distance penalty, SB eq. 17 |
---|
818 | for k in range(m): |
---|
819 | beta[k] *= math.sqrt (fSum/(blank*w)) |
---|
820 | chtarg = ctarg * chisq |
---|
821 | return w, chtarg, loop, a_new, fx, beta |
---|
822 | |
---|
823 | #MaxEnt_SB starts here |
---|
824 | |
---|
825 | if image_to_data == None: |
---|
826 | image_to_data = opus |
---|
827 | if data_to_image == None: |
---|
828 | data_to_image = tropus |
---|
829 | n = len(base) |
---|
830 | npt = len(datum) |
---|
831 | |
---|
832 | # Note that the order of subscripts for |
---|
833 | # "xi" and "eta" has been reversed from |
---|
834 | # the convention used in the FORTRAN version |
---|
835 | # to enable parts of them to be passed as |
---|
836 | # as vectors to "image_to_data" and "data_to_image". |
---|
837 | xi = np.zeros((SEARCH_DIRECTIONS, n)) |
---|
838 | eta = np.zeros((SEARCH_DIRECTIONS, npt)) |
---|
839 | beta = np.zeros((SEARCH_DIRECTIONS)) |
---|
840 | # s1 = np.zeros((SEARCH_DIRECTIONS)) |
---|
841 | # c1 = np.zeros((SEARCH_DIRECTIONS)) |
---|
842 | s2 = np.zeros((SEARCH_DIRECTIONS, SEARCH_DIRECTIONS)) |
---|
843 | c2 = np.zeros((SEARCH_DIRECTIONS, SEARCH_DIRECTIONS)) |
---|
844 | |
---|
845 | # TODO: replace blank (scalar) with base (vector) |
---|
846 | blank = sum(base) / len(base) # use the average value of base |
---|
847 | |
---|
848 | chizer, chtarg = npt*1.0, npt*1.0 |
---|
849 | f = base * 1.0 # starting distribution is base |
---|
850 | |
---|
851 | fSum = sum(f) # find the sum of the f-vector |
---|
852 | z = (datum - image_to_data (f, G)) / sigma # standardized residuals, SB eq. 3 |
---|
853 | chisq = sum(z*z) # Chi^2, SB eq. 4 |
---|
854 | |
---|
855 | for iter in range(IterMax): |
---|
856 | ox = -2 * z / sigma # gradient of Chi^2 |
---|
857 | |
---|
858 | cgrad = data_to_image (ox, G) # cgrad[i] = del(C)/del(f[i]), SB eq. 8 |
---|
859 | sgrad = -np.log(f/base) / (blank*math.exp (1.0)) # sgrad[i] = del(S)/del(f[i]) |
---|
860 | snorm = math.sqrt(sum(f * sgrad*sgrad)) # entropy term, SB eq. 22 |
---|
861 | cnorm = math.sqrt(sum(f * cgrad*cgrad)) # ChiSqr term, SB eq. 22 |
---|
862 | tnorm = sum(f * sgrad * cgrad) # norm for gradient term TEST |
---|
863 | |
---|
864 | a = 1.0 |
---|
865 | b = 1.0 / cnorm |
---|
866 | if iter == 0: |
---|
867 | test = 0.0 # mismatch between entropy and ChiSquared gradients |
---|
868 | else: |
---|
869 | test = math.sqrt( ( 1.0 - tnorm/(snorm*cnorm) )/2 ) # SB eq. 37? |
---|
870 | a = 0.5 / (snorm * test) |
---|
871 | b *= 0.5 / test |
---|
872 | xi[0] = f * cgrad / cnorm |
---|
873 | xi[1] = f * (a * sgrad - b * cgrad) |
---|
874 | |
---|
875 | eta[0] = image_to_data (xi[0], G); # image --> data |
---|
876 | eta[1] = image_to_data (xi[1], G); # image --> data |
---|
877 | ox = eta[1] / (sigma * sigma) |
---|
878 | xi[2] = data_to_image (ox, G); # data --> image |
---|
879 | a = 1.0 / math.sqrt(sum(f * xi[2]*xi[2])) |
---|
880 | xi[2] = f * xi[2] * a |
---|
881 | eta[2] = image_to_data (xi[2], G) # image --> data |
---|
882 | |
---|
883 | # print_arr("MaxEnt: eta.transpose()", eta.transpose()) |
---|
884 | # print_arr("MaxEnt: xi.transpose()", xi.transpose()) |
---|
885 | |
---|
886 | # prepare the search directions for the conjugate gradient technique |
---|
887 | c1 = xi.dot(cgrad) / chisq # C_mu, SB eq. 24 |
---|
888 | s1 = xi.dot(sgrad) # S_mu, SB eq. 24 |
---|
889 | # print_vec("MaxEnt: c1", c1) |
---|
890 | # print_vec("MaxEnt: s1", s1) |
---|
891 | |
---|
892 | for k in range(SEARCH_DIRECTIONS): |
---|
893 | for l in range(k+1): |
---|
894 | c2[k][l] = 2 * sum(eta[k] * eta[l] / sigma/sigma) / chisq |
---|
895 | s2[k][l] = -sum(xi[k] * xi[l] / f) / blank |
---|
896 | # print_arr("MaxEnt: c2", c2) |
---|
897 | # print_arr("MaxEnt: s2", s2) |
---|
898 | |
---|
899 | # reflect across the body diagonal |
---|
900 | for k, l in ((0,1), (0,2), (1,2)): |
---|
901 | c2[k][l] = c2[l][k] # M_(mu,nu) |
---|
902 | s2[k][l] = s2[l][k] # g_(mu,nu) |
---|
903 | |
---|
904 | beta[0] = -0.5 * c1[0] / c2[0][0] |
---|
905 | beta[1] = 0.0 |
---|
906 | beta[2] = 0.0 |
---|
907 | if (iter > 0): |
---|
908 | w, chtarg, loop, a_new, fx, beta = MaxEntMove(fSum, blank, chisq, chizer, c1, c2, s1, s2) |
---|
909 | |
---|
910 | f_old = f.copy() # preserve the last image |
---|
911 | f += xi.transpose().dot(beta) # move the image towards the solution, SB eq. 25 |
---|
912 | |
---|
913 | # As mentioned at the top of p.119, |
---|
914 | # need to protect against stray negative values. |
---|
915 | # In this case, set them to RESET_STRAYS * base[i] |
---|
916 | #f = f.clip(RESET_STRAYS * blank, f.max()) |
---|
917 | for i in range(n): |
---|
918 | if f[i] <= 0.0: |
---|
919 | f[i] = RESET_STRAYS * base[i] |
---|
920 | df = f - f_old |
---|
921 | fSum = sum(f) |
---|
922 | fChange = sum(df) |
---|
923 | |
---|
924 | # calculate the normalized entropy |
---|
925 | S = sum((f/fSum) * np.log(f/fSum)) # normalized entropy, S&B eq. 1 |
---|
926 | z = (datum - image_to_data (f, G)) / sigma # standardized residuals |
---|
927 | chisq = sum(z*z) # report this ChiSq |
---|
928 | |
---|
929 | if report: |
---|
930 | print (" MaxEnt trial/max: %3d/%3d" % ((iter+1), IterMax)) |
---|
931 | print (" Residual: %5.2lf%% Entropy: %8lg" % (100*test, S)) |
---|
932 | print (" Function sum: %.6lg Change from last: %.2lf%%\n" % (fSum,100*fChange/fSum)) |
---|
933 | |
---|
934 | # See if we have finished our task. |
---|
935 | # do the hardest test first |
---|
936 | if (abs(chisq/chizer-1.0) < CHI_SQR_LIMIT) and (test < TEST_LIMIT): |
---|
937 | print (' Convergence achieved.') |
---|
938 | return chisq,f,image_to_data(f, G) # solution FOUND returns here |
---|
939 | print (' No convergence! Try increasing Error multiplier.') |
---|
940 | return chisq,f,image_to_data(f, G) # no solution after IterMax iterations |
---|
941 | |
---|
942 | |
---|
943 | ############################################################################### |
---|
944 | #### IPG/TNNLS Routines |
---|
945 | ############################################################################### |
---|
946 | |
---|
947 | def IPG(datum,sigma,G,Bins,Dbins,IterMax,Qvec=[],approach=0.8,Power=-1,report=False): |
---|
948 | ''' An implementation of the Interior-Point Gradient method of |
---|
949 | Michael Merritt & Yin Zhang, Technical Report TR04-08, Dept. of Comp. and |
---|
950 | Appl. Math., Rice Univ., Houston, Texas 77005, U.S.A. found on the web at |
---|
951 | http://www.caam.rice.edu/caam/trs/2004/TR04-08.pdf |
---|
952 | Problem addressed: Total Non-Negative Least Squares (TNNLS) |
---|
953 | :param float datum[]: |
---|
954 | :param float sigma[]: |
---|
955 | :param float[][] G: transformation matrix |
---|
956 | :param int IterMax: |
---|
957 | :param float Qvec: data positions for Power = 0-4 |
---|
958 | :param float approach: 0.8 default fitting parameter |
---|
959 | :param int Power: 0-4 for Q^Power weighting, -1 to use input sigma |
---|
960 | |
---|
961 | ''' |
---|
962 | if Power < 0: |
---|
963 | GmatE = G/sigma[:np.newaxis] |
---|
964 | IntE = datum/sigma |
---|
965 | pwr = 0 |
---|
966 | QvecP = np.ones_like(datum) |
---|
967 | else: |
---|
968 | GmatE = G[:] |
---|
969 | IntE = datum[:] |
---|
970 | pwr = Power |
---|
971 | QvecP = Qvec**pwr |
---|
972 | Amat = GmatE*QvecP[:np.newaxis] |
---|
973 | AAmat = np.inner(Amat,Amat) |
---|
974 | Bvec = IntE*QvecP |
---|
975 | Xw = np.ones_like(Bins)*1.e-32 |
---|
976 | calc = np.dot(G.T,Xw) |
---|
977 | nIter = 0 |
---|
978 | err = 10. |
---|
979 | while (nIter<IterMax) and (err > 1.): |
---|
980 | #Step 1 in M&Z paper: |
---|
981 | Qk = np.inner(AAmat,Xw)-np.inner(Amat,Bvec) |
---|
982 | Dk = Xw/np.inner(AAmat,Xw) |
---|
983 | Pk = -Dk*Qk |
---|
984 | #Step 2 in M&Z paper: |
---|
985 | alpSt = -np.inner(Pk,Qk)/np.inner(Pk,np.inner(AAmat,Pk)) |
---|
986 | alpWv = -Xw/Pk |
---|
987 | AkSt = [np.where(Pk[i]<0,np.min((approach*alpWv[i],alpSt)),alpSt) for i in range(Pk.shape[0])] |
---|
988 | #Step 3 in M&Z paper: |
---|
989 | shift = AkSt*Pk |
---|
990 | Xw += shift |
---|
991 | #done IPG; now results |
---|
992 | nIter += 1 |
---|
993 | calc = np.dot(G.T,Xw) |
---|
994 | chisq = np.sum(((datum-calc)/sigma)**2) |
---|
995 | err = chisq/len(datum) |
---|
996 | if report: |
---|
997 | print (' Iteration: %d, chisq: %.3g, sum(shift^2): %.3g'%(nIter,chisq,np.sum(shift**2))) |
---|
998 | return chisq,Xw,calc |
---|
999 | |
---|
1000 | ############################################################################### |
---|
1001 | #### SASD Utilities |
---|
1002 | ############################################################################### |
---|
1003 | |
---|
1004 | def SetScale(Data,refData): |
---|
1005 | Profile,Limits,Sample = Data |
---|
1006 | refProfile,refLimits,refSample = refData |
---|
1007 | x,y = Profile[:2] |
---|
1008 | rx,ry = refProfile[:2] |
---|
1009 | Beg = np.fmax([rx[0],x[0],Limits[1][0],refLimits[1][0]]) |
---|
1010 | Fin = np.fmin([rx[-1],x[-1],Limits[1][1],refLimits[1][1]]) |
---|
1011 | iBeg = np.searchsorted(x,Beg) |
---|
1012 | iFin = np.searchsorted(x,Fin)+1 #include last point |
---|
1013 | sum = np.sum(y[iBeg:iFin]) |
---|
1014 | refsum = np.sum(np.interp(x[iBeg:iFin],rx,ry,0,0)) |
---|
1015 | Sample['Scale'][0] = refSample['Scale'][0]*refsum/sum |
---|
1016 | |
---|
1017 | def Bestimate(G,Rg,P): |
---|
1018 | return (G*P/Rg**P)*np.exp(scsp.gammaln(P/2)) |
---|
1019 | |
---|
1020 | def SmearData(Ic,Q,slitLen,Back): |
---|
1021 | Np = Q.shape[0] |
---|
1022 | Qtemp = np.concatenate([Q,Q[-1]+20*Q]) |
---|
1023 | Ictemp = np.concatenate([Ic,Ic[-1]*(1-(Qtemp[Np:]-Qtemp[Np])/(20*Qtemp[Np-1]))]) |
---|
1024 | Icsm = np.zeros_like(Q) |
---|
1025 | Qsm = 2*slitLen*(np.interp(np.arange(2*Np)/2.,np.arange(Np),Q)-Q[0])/(Q[-1]-Q[0]) |
---|
1026 | Sp = np.searchsorted(Qsm,slitLen) |
---|
1027 | DQsm = np.diff(Qsm)[:Sp] |
---|
1028 | Ism = np.interp(np.sqrt(Q[:,np.newaxis]**2+Qsm**2),Qtemp,Ictemp) |
---|
1029 | Icsm = np.sum((Ism[:,:Sp]*DQsm),axis=1) |
---|
1030 | Icsm /= slitLen |
---|
1031 | return Icsm |
---|
1032 | |
---|
1033 | ############################################################################### |
---|
1034 | #### Size distribution |
---|
1035 | ############################################################################### |
---|
1036 | |
---|
1037 | def SizeDistribution(Profile,ProfDict,Limits,Sample,data): |
---|
1038 | shapes = {'Spheroid':[SpheroidFF,SpheroidVol],'Cylinder':[CylinderDFF,CylinderDVol], |
---|
1039 | 'Cylinder AR':[CylinderARFF,CylinderARVol],'Unified sphere':[UniSphereFF,UniSphereVol], |
---|
1040 | 'Unified rod':[UniRodFF,UniRodVol],'Unified rod AR':[UniRodARFF,UniRodARVol], |
---|
1041 | 'Unified disk':[UniDiskFF,UniDiskVol],'Sphere':[SphereFF,SphereVol], |
---|
1042 | 'Cylinder diam':[CylinderDFF,CylinderDVol], |
---|
1043 | 'Spherical shell': [SphericalShellFF, SphericalShellVol]} |
---|
1044 | Shape = data['Size']['Shape'][0] |
---|
1045 | Parms = data['Size']['Shape'][1:] |
---|
1046 | if data['Size']['logBins']: |
---|
1047 | Bins = np.logspace(np.log10(data['Size']['MinDiam']),np.log10(data['Size']['MaxDiam']), |
---|
1048 | data['Size']['Nbins']+1,True)/2. #make radii |
---|
1049 | else: |
---|
1050 | Bins = np.linspace(data['Size']['MinDiam'],data['Size']['MaxDiam'], |
---|
1051 | data['Size']['Nbins']+1,True)/2. #make radii |
---|
1052 | Dbins = np.diff(Bins) |
---|
1053 | Bins = Bins[:-1]+Dbins/2. |
---|
1054 | Contrast = Sample['Contrast'][1] |
---|
1055 | Scale = Sample['Scale'][0] |
---|
1056 | Sky = 10**data['Size']['MaxEnt']['Sky'] |
---|
1057 | BinsBack = np.ones_like(Bins)*Sky*Scale/Contrast |
---|
1058 | Back = data['Back'] |
---|
1059 | Q,Io,wt,Ic,Ib = Profile[:5] |
---|
1060 | Qmin = Limits[1][0] |
---|
1061 | Qmax = Limits[1][1] |
---|
1062 | wtFactor = ProfDict['wtFactor'] |
---|
1063 | Ibeg = np.searchsorted(Q,Qmin) |
---|
1064 | Ifin = np.searchsorted(Q,Qmax)+1 #include last point |
---|
1065 | BinMag = np.zeros_like(Bins) |
---|
1066 | Ic[:] = 0. |
---|
1067 | Gmat = G_matrix(Q[Ibeg:Ifin],Bins,Contrast,shapes[Shape][0],shapes[Shape][1],args=Parms) |
---|
1068 | if 'MaxEnt' == data['Size']['Method']: |
---|
1069 | chisq,BinMag,Ic[Ibeg:Ifin] = MaxEnt_SB(Scale*Io[Ibeg:Ifin]-Back[0], |
---|
1070 | Scale/np.sqrt(wtFactor*wt[Ibeg:Ifin]),Gmat,BinsBack, |
---|
1071 | data['Size']['MaxEnt']['Niter'],report=True) |
---|
1072 | elif 'IPG' == data['Size']['Method']: |
---|
1073 | chisq,BinMag,Ic[Ibeg:Ifin] = IPG(Scale*Io[Ibeg:Ifin]-Back[0],Scale/np.sqrt(wtFactor*wt[Ibeg:Ifin]), |
---|
1074 | Gmat,Bins,Dbins,data['Size']['IPG']['Niter'],Q[Ibeg:Ifin],approach=0.8, |
---|
1075 | Power=data['Size']['IPG']['Power'],report=True) |
---|
1076 | Ib[:] = Back[0] |
---|
1077 | Ic[Ibeg:Ifin] += Back[0] |
---|
1078 | print (' Final chi^2: %.3f'%(chisq)) |
---|
1079 | data['Size']['Distribution'] = [Bins,Dbins,BinMag/(2.*Dbins)] |
---|
1080 | |
---|
1081 | ################################################################################ |
---|
1082 | #### Modelling |
---|
1083 | ################################################################################ |
---|
1084 | |
---|
1085 | def PairDistFxn(Profile,ProfDict,Limits,Sample,data): |
---|
1086 | |
---|
1087 | def CalcMoore(): |
---|
1088 | |
---|
1089 | def MoorePOR(cw,r,dmax): #lines 1417-1428 |
---|
1090 | n = 0 |
---|
1091 | nmax = len(cw) |
---|
1092 | POR = np.zeros(len(r)) |
---|
1093 | while n < nmax: |
---|
1094 | POR += 4.*r*cw[n]*np.sin((n+1.)*np.pi*r/dmax) |
---|
1095 | n += 1 |
---|
1096 | return POR |
---|
1097 | |
---|
1098 | def MooreIOREFF(cw,q,dmax): #lines 1437-1448 |
---|
1099 | n = 0 |
---|
1100 | nmax = len(cw) |
---|
1101 | POR = np.zeros(len(q)) |
---|
1102 | dq = dmax*q |
---|
1103 | fpd = 8.*(np.pi**2)*dmax*np.sin(dq)/q |
---|
1104 | while n < nmax: |
---|
1105 | POR += cw[n]*(n+1.)*((-1)**n)*fpd/(((n+1.)*np.pi)**2-dq**2) |
---|
1106 | n += 1 |
---|
1107 | return POR |
---|
1108 | |
---|
1109 | def calcSASD(values,Q,Io,wt,Ifb,dmax,ifBack): |
---|
1110 | if ifBack: |
---|
1111 | M = np.sqrt(wt)*(MooreIOREFF(values[:-1],Q,dmax)+Ifb+values[-1]-Io) |
---|
1112 | else: |
---|
1113 | M = np.sqrt(wt)*(MooreIOREFF(values,Q,dmax)+Ifb-Io) |
---|
1114 | return M |
---|
1115 | |
---|
1116 | Q,Io,wt,Ic,Ib,Ifb = Profile[:6] |
---|
1117 | Qmin = Limits[1][0] |
---|
1118 | Qmax = Limits[1][1] |
---|
1119 | wtFactor = ProfDict['wtFactor'] |
---|
1120 | Back,ifBack = data['Back'] |
---|
1121 | Ibeg = np.searchsorted(Q,Qmin) |
---|
1122 | Ifin = np.searchsorted(Q,Qmax)+1 #include last point |
---|
1123 | Ic[Ibeg:Ifin] = 0 |
---|
1124 | Bins = np.linspace(0.,pairData['MaxRadius'],pairData['NBins']+1,True) |
---|
1125 | Dbins = np.diff(Bins) |
---|
1126 | Bins = Bins[:-1]+Dbins/2. |
---|
1127 | N = pairData['Moore'] |
---|
1128 | if ifBack: |
---|
1129 | N += 1 |
---|
1130 | MPV = np.ones(N)*1.e-5 |
---|
1131 | dmax = pairData['MaxRadius'] |
---|
1132 | if 'User' in pairData['Errors']: |
---|
1133 | Wt = wt[Ibeg:Ifin] |
---|
1134 | elif 'Sqrt' in pairData['Errors']: |
---|
1135 | Wt = 1./Io[Ibeg:Ifin] |
---|
1136 | elif 'Percent' in pairData['Errors']: |
---|
1137 | Wt = 1./(pairData['Percent error']*Io[Ibeg:Ifin]) |
---|
1138 | result = so.leastsq(calcSASD,MPV,full_output=True,epsfcn=1.e-8, #ftol=Ftol, |
---|
1139 | args=(Q[Ibeg:Ifin],Io[Ibeg:Ifin],wtFactor*Wt,Ifb[Ibeg:Ifin],dmax,ifBack)) |
---|
1140 | if ifBack: |
---|
1141 | MPVR = result[0][:-1] |
---|
1142 | data['Back'][0] = result[0][-1] |
---|
1143 | Back = data['Back'][0] |
---|
1144 | else: |
---|
1145 | MPVR = result[0] |
---|
1146 | Back = 0. |
---|
1147 | chisq = np.sum(result[2]['fvec']**2) |
---|
1148 | covM = result[1] |
---|
1149 | Ic[Ibeg:Ifin] = MooreIOREFF(MPVR,Q[Ibeg:Ifin],dmax)+Ifb[Ibeg:Ifin]+Back |
---|
1150 | ncalc = result[2]['nfev'] |
---|
1151 | GOF = chisq/(Ifin-Ibeg-N) |
---|
1152 | Rwp = np.sqrt(chisq/np.sum(wt[Ibeg:Ifin]*Io[Ibeg:Ifin]**2))*100. #to % |
---|
1153 | print (' Results of small angle data modelling fit of P(R):') |
---|
1154 | print ('Number of function calls: %d Number of observations: %d Number of parameters: %d'%(ncalc,Ifin-Ibeg,N)) |
---|
1155 | print ('Rwp = %7.2f%%, chi**2 = %12.6g, reduced chi**2 = %6.2f'%(Rwp,chisq,GOF)) |
---|
1156 | if len(covM): |
---|
1157 | sig = np.sqrt(np.diag(covM)*GOF) |
---|
1158 | for val,esd in zip(result[0],sig): |
---|
1159 | print(' parameter: %.4g esd: %.4g'%(val,esd)) |
---|
1160 | BinMag = MoorePOR(MPVR,Bins,dmax)/2. |
---|
1161 | return Bins,Dbins,BinMag |
---|
1162 | |
---|
1163 | pairData = data['Pair'] |
---|
1164 | |
---|
1165 | if pairData['Method'] == 'Regularization': #not used |
---|
1166 | print('Regularization calc; dummy Gaussian - TBD') |
---|
1167 | pairData['Method'] = 'Moore' |
---|
1168 | |
---|
1169 | |
---|
1170 | elif pairData['Method'] == 'Moore': |
---|
1171 | Bins,Dbins,BinMag = CalcMoore() |
---|
1172 | BinSum = np.sum(BinMag*Dbins) |
---|
1173 | BinMag /= BinSum |
---|
1174 | |
---|
1175 | data['Pair']['Distribution'] = [Bins,Dbins,BinMag] #/(2.*Dbins)] |
---|
1176 | if 'Pair Calc' in data['Pair']: |
---|
1177 | del data['Pair']['Pair Calc'] |
---|
1178 | |
---|
1179 | |
---|
1180 | |
---|
1181 | ################################################################################ |
---|
1182 | #### Modelling |
---|
1183 | ################################################################################ |
---|
1184 | |
---|
1185 | def ModelFit(Profile,ProfDict,Limits,Sample,Model): |
---|
1186 | shapes = {'Spheroid':[SpheroidFF,SpheroidVol],'Cylinder':[CylinderDFF,CylinderDVol], |
---|
1187 | 'Cylinder AR':[CylinderARFF,CylinderARVol],'Unified sphere':[UniSphereFF,UniSphereVol], |
---|
1188 | 'Unified rod':[UniRodFF,UniRodVol],'Unified rod AR':[UniRodARFF,UniRodARVol], |
---|
1189 | 'Unified disk':[UniDiskFF,UniDiskVol],'Sphere':[SphereFF,SphereVol], |
---|
1190 | 'Unified tube':[UniTubeFF,UniTubeVol],'Cylinder diam':[CylinderDFF,CylinderDVol], |
---|
1191 | 'Spherical shell':[SphericalShellFF,SphericalShellVol]} |
---|
1192 | |
---|
1193 | sfxns = {'Dilute':DiluteSF,'Hard sphere':HardSpheresSF,'Square well':SquareWellSF, |
---|
1194 | 'Sticky hard sphere':StickyHardSpheresSF,'InterPrecipitate':InterPrecipitateSF,} |
---|
1195 | |
---|
1196 | parmOrder = ['Volume','Radius','Mean','StdDev','MinSize','G','Rg','B','P','Cutoff', |
---|
1197 | 'PkInt','PkPos','PkSig','PkGam',] |
---|
1198 | |
---|
1199 | FFparmOrder = ['Aspect ratio','Length','Diameter','Thickness','Shell thickness'] |
---|
1200 | |
---|
1201 | SFparmOrder = ['Dist','VolFr','epis','Sticky','Depth','Width'] |
---|
1202 | |
---|
1203 | def GetModelParms(): |
---|
1204 | parmDict = {'Scale':Sample['Scale'][0],'SlitLen':Sample.get('SlitLen',0.0),} |
---|
1205 | varyList = [] |
---|
1206 | values = [] |
---|
1207 | levelTypes = [] |
---|
1208 | Back = Model['Back'] |
---|
1209 | if Back[1]: |
---|
1210 | varyList += ['Back',] |
---|
1211 | values.append(Back[0]) |
---|
1212 | parmDict['Back'] = Back[0] |
---|
1213 | partData = Model['Particle'] |
---|
1214 | for i,level in enumerate(partData['Levels']): |
---|
1215 | cid = str(i)+';' |
---|
1216 | controls = level['Controls'] |
---|
1217 | Type = controls['DistType'] |
---|
1218 | levelTypes.append(Type) |
---|
1219 | if Type in ['LogNormal','Gaussian','LSW','Schulz-Zimm','Monodisperse']: |
---|
1220 | if Type not in ['Monodosperse',]: |
---|
1221 | parmDict[cid+'NumPoints'] = controls['NumPoints'] |
---|
1222 | parmDict[cid+'Cutoff'] = controls['Cutoff'] |
---|
1223 | parmDict[cid+'FormFact'] = shapes[controls['FormFact']][0] |
---|
1224 | parmDict[cid+'FFVolume'] = shapes[controls['FormFact']][1] |
---|
1225 | parmDict[cid+'StrFact'] = sfxns[controls['StrFact']] |
---|
1226 | parmDict[cid+'Contrast'] = controls['Contrast'] |
---|
1227 | for item in FFparmOrder: |
---|
1228 | if item in controls['FFargs']: |
---|
1229 | parmDict[cid+item] = controls['FFargs'][item][0] |
---|
1230 | if controls['FFargs'][item][1]: |
---|
1231 | varyList.append(cid+item) |
---|
1232 | values.append(controls['FFargs'][item][0]) |
---|
1233 | for item in SFparmOrder: |
---|
1234 | if item in controls.get('SFargs',{}): |
---|
1235 | parmDict[cid+item] = controls['SFargs'][item][0] |
---|
1236 | if controls['SFargs'][item][1]: |
---|
1237 | varyList.append(cid+item) |
---|
1238 | values.append(controls['SFargs'][item][0]) |
---|
1239 | distDict = controls['DistType'] |
---|
1240 | for item in parmOrder: |
---|
1241 | if item in level[distDict]: |
---|
1242 | parmDict[cid+item] = level[distDict][item][0] |
---|
1243 | if level[distDict][item][1]: |
---|
1244 | values.append(level[distDict][item][0]) |
---|
1245 | varyList.append(cid+item) |
---|
1246 | return levelTypes,parmDict,varyList,values |
---|
1247 | |
---|
1248 | def SetModelParms(): |
---|
1249 | print (' Refined parameters: Histogram scale: %.4g'%(parmDict['Scale'])) |
---|
1250 | if 'Back' in varyList: |
---|
1251 | Model['Back'][0] = parmDict['Back'] |
---|
1252 | print (' %15s %15.4f esd: %15.4g'%('Background:',parmDict['Back'],sigDict['Back'])) |
---|
1253 | partData = Model['Particle'] |
---|
1254 | for i,level in enumerate(partData['Levels']): |
---|
1255 | controls = level['Controls'] |
---|
1256 | Type = controls['DistType'] |
---|
1257 | if Type in ['LogNormal','Gaussian','LSW','Schulz-Zimm','Monodisperse']: |
---|
1258 | print (' Component %d: Type: %s: Structure Factor: %s Contrast: %12.3f' \ |
---|
1259 | %(i,Type,controls['StrFact'],controls['Contrast'])) |
---|
1260 | else: |
---|
1261 | print (' Component %d: Type: %s: '%(i,Type,)) |
---|
1262 | cid = str(i)+';' |
---|
1263 | if Type in ['LogNormal','Gaussian','LSW','Schulz-Zimm','Monodisperse']: |
---|
1264 | for item in FFparmOrder: |
---|
1265 | if cid+item in varyList: |
---|
1266 | controls['FFargs'][item][0] = parmDict[cid+item] |
---|
1267 | print (' %15s: %15.4g esd: %15.4g'%(cid+item,parmDict[cid+item],sigDict[cid+item])) |
---|
1268 | for item in SFparmOrder: |
---|
1269 | if cid+item in varyList: |
---|
1270 | controls['SFargs'][item][0] = parmDict[cid+item] |
---|
1271 | print (' %15s: %15.4g esd: %15.4g'%(cid+item,parmDict[cid+item],sigDict[cid+item])) |
---|
1272 | distDict = controls['DistType'] |
---|
1273 | for item in level[distDict]: |
---|
1274 | if cid+item in varyList: |
---|
1275 | level[distDict][item][0] = parmDict[cid+item] |
---|
1276 | print (' %15s: %15.4g esd: %15.4g'%(cid+item,parmDict[cid+item],sigDict[cid+item])) |
---|
1277 | |
---|
1278 | def calcSASD(values,Q,Io,wt,Ifb,levelTypes,parmDict,varyList): |
---|
1279 | parmDict.update(zip(varyList,values)) |
---|
1280 | M = np.sqrt(wt)*(getSASD(Q,levelTypes,parmDict)+Ifb-parmDict['Scale']*Io) |
---|
1281 | return M |
---|
1282 | |
---|
1283 | def getSASD(Q,levelTypes,parmDict): |
---|
1284 | Ic = np.zeros_like(Q) |
---|
1285 | for i,Type in enumerate(levelTypes): |
---|
1286 | cid = str(i)+';' |
---|
1287 | if Type in ['LogNormal','Gaussian','LSW','Schulz-Zimm']: |
---|
1288 | FFfxn = parmDict[cid+'FormFact'] |
---|
1289 | Volfxn = parmDict[cid+'FFVolume'] |
---|
1290 | SFfxn = parmDict[cid+'StrFact'] |
---|
1291 | FFargs = [] |
---|
1292 | SFargs = [] |
---|
1293 | for item in [cid+'Aspect ratio',cid+'Length',cid+'Thickness',cid+'Diameter',cid+'Shell thickness']: |
---|
1294 | if item in parmDict: |
---|
1295 | FFargs.append(parmDict[item]) |
---|
1296 | for item in [cid+'Dist',cid+'VolFr',cid+'epis',cid+'Sticky',cid+'Depth',cid+'Width']: |
---|
1297 | if item in parmDict: |
---|
1298 | SFargs.append(parmDict[item]) |
---|
1299 | distDict = {} |
---|
1300 | for item in [cid+'Volume',cid+'Mean',cid+'StdDev',cid+'MinSize',]: |
---|
1301 | if item in parmDict: |
---|
1302 | distDict[item.split(';')[1]] = parmDict[item] |
---|
1303 | contrast = parmDict[cid+'Contrast'] |
---|
1304 | rBins,dBins,dist = MakeDiamDist(Type,parmDict[cid+'NumPoints'],parmDict[cid+'Cutoff'],distDict) |
---|
1305 | Gmat = G_matrix(Q,rBins,contrast,FFfxn,Volfxn,FFargs).T |
---|
1306 | dist *= parmDict[cid+'Volume'] |
---|
1307 | Ic += np.dot(Gmat,dist)*SFfxn(Q,args=SFargs) |
---|
1308 | elif 'Unified' in Type: |
---|
1309 | Rg,G,B,P,Rgco = parmDict[cid+'Rg'],parmDict[cid+'G'],parmDict[cid+'B'], \ |
---|
1310 | parmDict[cid+'P'],parmDict[cid+'Cutoff'] |
---|
1311 | Qstar = Q/(scsp.erf(Q*Rg/np.sqrt(6)))**3 |
---|
1312 | Guin = G*np.exp(-(Q*Rg)**2/3) |
---|
1313 | Porod = (B/Qstar**P)*np.exp(-(Q*Rgco)**2/3) |
---|
1314 | Ic += Guin+Porod |
---|
1315 | elif 'Porod' in Type: |
---|
1316 | B,P,Rgco = parmDict[cid+'B'],parmDict[cid+'P'],parmDict[cid+'Cutoff'] |
---|
1317 | Porod = (B/Q**P)*np.exp(-(Q*Rgco)**2/3) |
---|
1318 | Ic += Porod |
---|
1319 | elif 'Mono' in Type: |
---|
1320 | FFfxn = parmDict[cid+'FormFact'] |
---|
1321 | Volfxn = parmDict[cid+'FFVolume'] |
---|
1322 | SFfxn = parmDict[cid+'StrFact'] |
---|
1323 | FFargs = [] |
---|
1324 | SFargs = [] |
---|
1325 | for item in [cid+'Aspect ratio',cid+'Length',cid+'Thickness',cid+'Diameter',cid+'Shell thickness']: |
---|
1326 | if item in parmDict: |
---|
1327 | FFargs.append(parmDict[item]) |
---|
1328 | for item in [cid+'Dist',cid+'VolFr',cid+'epis',cid+'Sticky',cid+'Depth',cid+'Width']: |
---|
1329 | if item in parmDict: |
---|
1330 | SFargs.append(parmDict[item]) |
---|
1331 | contrast = parmDict[cid+'Contrast'] |
---|
1332 | R = parmDict[cid+'Radius'] |
---|
1333 | Gmat = G_matrix(Q,R,contrast,FFfxn,Volfxn,FFargs) |
---|
1334 | Ic += Gmat[0]*parmDict[cid+'Volume']*SFfxn(Q,args=SFargs) |
---|
1335 | elif 'Bragg' in Type: |
---|
1336 | Ic += parmDict[cid+'PkInt']*G2pwd.getPsVoigt(parmDict[cid+'PkPos'], |
---|
1337 | parmDict[cid+'PkSig'],parmDict[cid+'PkGam'],Q) |
---|
1338 | Ic += parmDict['Back'] #/parmDict['Scale'] |
---|
1339 | slitLen = Sample['SlitLen'] |
---|
1340 | if slitLen: |
---|
1341 | Ic = SmearData(Ic,Q,slitLen,parmDict['Back']) |
---|
1342 | return Ic |
---|
1343 | |
---|
1344 | Q,Io,wt,Ic,Ib,Ifb = Profile[:6] |
---|
1345 | Qmin = Limits[1][0] |
---|
1346 | Qmax = Limits[1][1] |
---|
1347 | wtFactor = ProfDict['wtFactor'] |
---|
1348 | Ibeg = np.searchsorted(Q,Qmin) |
---|
1349 | Ifin = np.searchsorted(Q,Qmax)+1 #include last point |
---|
1350 | Ic[:] = 0 |
---|
1351 | levelTypes,parmDict,varyList,values = GetModelParms() |
---|
1352 | if varyList: |
---|
1353 | result = so.leastsq(calcSASD,values,full_output=True,epsfcn=1.e-8, #ftol=Ftol, |
---|
1354 | args=(Q[Ibeg:Ifin],Io[Ibeg:Ifin],wtFactor*wt[Ibeg:Ifin],Ifb[Ibeg:Ifin],levelTypes,parmDict,varyList)) |
---|
1355 | parmDict.update(zip(varyList,result[0])) |
---|
1356 | chisq = np.sum(result[2]['fvec']**2) |
---|
1357 | ncalc = result[2]['nfev'] |
---|
1358 | covM = result[1] |
---|
1359 | else: #nothing varied |
---|
1360 | M = calcSASD(values,Q[Ibeg:Ifin],Io[Ibeg:Ifin],wtFactor*wt[Ibeg:Ifin],Ifb[Ibeg:Ifin],levelTypes,parmDict,varyList) |
---|
1361 | chisq = np.sum(M**2) |
---|
1362 | ncalc = 0 |
---|
1363 | covM = [] |
---|
1364 | sig = [] |
---|
1365 | sigDict = {} |
---|
1366 | result = [] |
---|
1367 | Rvals = {} |
---|
1368 | Rvals['Rwp'] = np.sqrt(chisq/np.sum(wt[Ibeg:Ifin]*Io[Ibeg:Ifin]**2))*100. #to % |
---|
1369 | Rvals['GOF'] = chisq/(Ifin-Ibeg-len(varyList)) #reduced chi^2 |
---|
1370 | Ic[Ibeg:Ifin] = getSASD(Q[Ibeg:Ifin],levelTypes,parmDict) |
---|
1371 | Msg = 'Failed to converge' |
---|
1372 | try: |
---|
1373 | Nans = np.isnan(result[0]) |
---|
1374 | if np.any(Nans): |
---|
1375 | name = varyList[Nans.nonzero(True)[0]] |
---|
1376 | Msg = 'Nan result for '+name+'!' |
---|
1377 | raise ValueError |
---|
1378 | Negs = np.less_equal(result[0],0.) |
---|
1379 | if np.any(Negs): |
---|
1380 | name = varyList[Negs.nonzero(True)[0]] |
---|
1381 | Msg = 'negative coefficient for '+name+'!' |
---|
1382 | raise ValueError |
---|
1383 | if len(covM): |
---|
1384 | sig = np.sqrt(np.diag(covM)*Rvals['GOF']) |
---|
1385 | sigDict = dict(zip(varyList,sig)) |
---|
1386 | print (' Results of small angle data modelling fit:') |
---|
1387 | print ('Number of function calls: %d Number of observations: %d Number of parameters: %d'%(ncalc,Ifin-Ibeg,len(varyList))) |
---|
1388 | print ('Rwp = %7.2f%%, chi**2 = %12.6g, reduced chi**2 = %6.2f'%(Rvals['Rwp'],chisq,Rvals['GOF'])) |
---|
1389 | SetModelParms() |
---|
1390 | covMatrix = covM*Rvals['GOF'] |
---|
1391 | return True,result,varyList,sig,Rvals,covMatrix,parmDict,'' |
---|
1392 | except (ValueError,TypeError): #when bad LS refinement; covM missing or with nans |
---|
1393 | return False,0,0,0,0,0,0,Msg |
---|
1394 | |
---|
1395 | def getSASDRg(Q,parmDict): |
---|
1396 | Ic = np.zeros_like(Q) |
---|
1397 | Rg,G,B = parmDict['Rg'],parmDict['G'],parmDict['B'] |
---|
1398 | Qstar = Q/(scsp.erf(Q*Rg/np.sqrt(6)))**3 |
---|
1399 | Guin = G*np.exp(-(Q*Rg)**2/3) |
---|
1400 | Porod = (B/Qstar**4) #*np.exp(-(Q*B)**2/3) |
---|
1401 | Ic += Guin+Porod+parmDict['Back'] |
---|
1402 | return Ic |
---|
1403 | |
---|
1404 | def RgFit(Profile,ProfDict,Limits,Sample,Model): |
---|
1405 | print('unified fit single Rg to data to estimate a size') |
---|
1406 | |
---|
1407 | def GetModelParms(): |
---|
1408 | parmDict = {} |
---|
1409 | varyList = [] |
---|
1410 | values = [] |
---|
1411 | Back = Model['Back'] |
---|
1412 | if Back[1]: |
---|
1413 | varyList += ['Back',] |
---|
1414 | values.append(Back[0]) |
---|
1415 | parmDict['Back'] = Back[0] |
---|
1416 | pairData = Model['Pair'] |
---|
1417 | parmDict['G'] = pairData.get('Dist G',Io[Ibeg]) |
---|
1418 | parmDict['Rg'] = pairData['MaxRadius']/2.5 |
---|
1419 | parmDict['B'] = pairData.get('Dist B',Io[Ifin-1]*Q[Ifin-1]**4) |
---|
1420 | varyList += ['G','Rg','B'] |
---|
1421 | values += [parmDict['G'],parmDict['Rg'],parmDict['B']] |
---|
1422 | return parmDict,varyList,values |
---|
1423 | |
---|
1424 | def calcSASD(values,Q,Io,wt,Ifb,parmDict,varyList): |
---|
1425 | parmDict.update(zip(varyList,values)) |
---|
1426 | M = np.sqrt(wt)*(getSASDRg(Q,parmDict)-Io) |
---|
1427 | return M |
---|
1428 | |
---|
1429 | def SetModelParms(): |
---|
1430 | print (' Refined parameters: ') |
---|
1431 | if 'Back' in varyList: |
---|
1432 | Model['Back'][0] = parmDict['Back'] |
---|
1433 | print (' %15s %15.4f esd: %15.4g'%('Background:',parmDict['Back'],sigDict['Back'])) |
---|
1434 | pairData = Model['Pair'] |
---|
1435 | pairData['Dist G'] = parmDict['G'] |
---|
1436 | pairData['MaxRadius'] = parmDict['Rg']*2.5 |
---|
1437 | pairData['Dist B'] = parmDict['B'] |
---|
1438 | for item in varyList: |
---|
1439 | print (' %15s: %15.4g esd: %15.4g'%(item,parmDict[item],sigDict[item])) |
---|
1440 | |
---|
1441 | Q,Io,wt,Ic,Ib,Ifb = Profile[:6] |
---|
1442 | Qmin = Limits[1][0] |
---|
1443 | Qmax = Limits[1][1] |
---|
1444 | wtFactor = ProfDict['wtFactor'] |
---|
1445 | Ibeg = np.searchsorted(Q,Qmin) |
---|
1446 | Ifin = np.searchsorted(Q,Qmax)+1 #include last point |
---|
1447 | Ic[:] = 0 |
---|
1448 | pairData = Model['Pair'] |
---|
1449 | if 'User' in pairData['Errors']: |
---|
1450 | Wt = wt[Ibeg:Ifin] |
---|
1451 | elif 'Sqrt' in pairData['Errors']: |
---|
1452 | Wt = 1./Io[Ibeg:Ifin] |
---|
1453 | elif 'Percent' in pairData['Errors']: |
---|
1454 | Wt = 1./(pairData['Percent error']*Io[Ibeg:Ifin]) |
---|
1455 | parmDict,varyList,values = GetModelParms() |
---|
1456 | result = so.leastsq(calcSASD,values,full_output=True,epsfcn=1.e-12,factor=0.1, #ftol=Ftol, |
---|
1457 | args=(Q[Ibeg:Ifin],Io[Ibeg:Ifin],wtFactor*Wt,Ifb[Ibeg:Ifin],parmDict,varyList)) |
---|
1458 | parmDict.update(dict(zip(varyList,result[0]))) |
---|
1459 | chisq = np.sum(result[2]['fvec']**2) |
---|
1460 | ncalc = result[2]['nfev'] |
---|
1461 | covM = result[1] |
---|
1462 | Rvals = {} |
---|
1463 | Rvals['Rwp'] = np.sqrt(chisq/np.sum(wt[Ibeg:Ifin]*Io[Ibeg:Ifin]**2))*100. #to % |
---|
1464 | Rvals['GOF'] = chisq/(Ifin-Ibeg-len(varyList)) #reduced chi^2 |
---|
1465 | Ic[Ibeg:Ifin] = getSASDRg(Q[Ibeg:Ifin],parmDict) |
---|
1466 | Msg = 'Failed to converge' |
---|
1467 | try: |
---|
1468 | Nans = np.isnan(result[0]) |
---|
1469 | if np.any(Nans): |
---|
1470 | name = varyList[Nans.nonzero(True)[0]] |
---|
1471 | Msg = 'Nan result for '+name+'!' |
---|
1472 | raise ValueError |
---|
1473 | Negs = np.less_equal(result[0],0.) |
---|
1474 | if np.any(Negs): |
---|
1475 | name = varyList[Negs.nonzero(True)[0]] |
---|
1476 | Msg = 'negative coefficient for '+name+'!' |
---|
1477 | raise ValueError |
---|
1478 | if len(covM): |
---|
1479 | sig = np.sqrt(np.diag(covM)*Rvals['GOF']) |
---|
1480 | sigDict = dict(zip(varyList,sig)) |
---|
1481 | print (' Results of Rg fit:') |
---|
1482 | print ('Number of function calls: %d Number of observations: %d Number of parameters: %d'%(ncalc,Ifin-Ibeg,len(varyList))) |
---|
1483 | print ('Rwp = %7.2f%%, chi**2 = %12.6g, reduced chi**2 = %6.2f'%(Rvals['Rwp'],chisq,Rvals['GOF'])) |
---|
1484 | SetModelParms() |
---|
1485 | covMatrix = covM*Rvals['GOF'] |
---|
1486 | return True,result,varyList,sig,Rvals,covMatrix,parmDict,'' |
---|
1487 | except (ValueError,TypeError): #when bad LS refinement; covM missing or with nans |
---|
1488 | return False,0,0,0,0,0,0,Msg |
---|
1489 | |
---|
1490 | return [None,] |
---|
1491 | |
---|
1492 | |
---|
1493 | def ModelFxn(Profile,ProfDict,Limits,Sample,sasdData): |
---|
1494 | |
---|
1495 | shapes = {'Spheroid':[SpheroidFF,SpheroidVol],'Cylinder':[CylinderDFF,CylinderDVol], |
---|
1496 | 'Cylinder AR':[CylinderARFF,CylinderARVol],'Unified sphere':[UniSphereFF,UniSphereVol], |
---|
1497 | 'Unified rod':[UniRodFF,UniRodVol],'Unified rod AR':[UniRodARFF,UniRodARVol], |
---|
1498 | 'Unified disk':[UniDiskFF,UniDiskVol],'Sphere':[SphereFF,SphereVol], |
---|
1499 | 'Unified tube':[UniTubeFF,UniTubeVol],'Cylinder diam':[CylinderDFF,CylinderDVol], |
---|
1500 | 'Spherical shell':[SphericalShellFF,SphericalShellVol]} |
---|
1501 | sfxns = {'Dilute':DiluteSF,'Hard sphere':HardSpheresSF,'Square well':SquareWellSF, |
---|
1502 | 'Sticky hard sphere':StickyHardSpheresSF,'InterPrecipitate':InterPrecipitateSF,} |
---|
1503 | |
---|
1504 | # pdb.set_trace() |
---|
1505 | partData = sasdData['Particle'] |
---|
1506 | Back = sasdData['Back'] |
---|
1507 | Q,Io,wt,Ic,Ib,Ifb = Profile[:6] |
---|
1508 | Qmin = Limits[1][0] |
---|
1509 | Qmax = Limits[1][1] |
---|
1510 | Ibeg = np.searchsorted(Q,Qmin) |
---|
1511 | Ifin = np.searchsorted(Q,Qmax)+1 #include last point |
---|
1512 | Ib[:] = Back[0] |
---|
1513 | Ic[:] = 0 |
---|
1514 | Rbins = [] |
---|
1515 | Dist = [] |
---|
1516 | for level in partData['Levels']: |
---|
1517 | controls = level['Controls'] |
---|
1518 | distFxn = controls['DistType'] |
---|
1519 | if distFxn in ['LogNormal','Gaussian','LSW','Schulz-Zimm']: |
---|
1520 | parmDict = level[controls['DistType']] |
---|
1521 | FFfxn = shapes[controls['FormFact']][0] |
---|
1522 | Volfxn = shapes[controls['FormFact']][1] |
---|
1523 | SFfxn = sfxns[controls['StrFact']] |
---|
1524 | FFargs = [] |
---|
1525 | SFargs = [] |
---|
1526 | for item in ['Dist','VolFr','epis','Sticky','Depth','Width',]: |
---|
1527 | if item in controls.get('SFargs',{}): |
---|
1528 | SFargs.append(controls['SFargs'][item][0]) |
---|
1529 | for item in ['Aspect ratio','Length','Thickness','Diameter','Shell thickness']: |
---|
1530 | if item in controls['FFargs']: |
---|
1531 | FFargs.append(controls['FFargs'][item][0]) |
---|
1532 | contrast = controls['Contrast'] |
---|
1533 | distDict = {} |
---|
1534 | for item in parmDict: |
---|
1535 | distDict[item] = parmDict[item][0] |
---|
1536 | rBins,dBins,dist = MakeDiamDist(controls['DistType'],controls['NumPoints'],controls['Cutoff'],distDict) |
---|
1537 | Gmat = G_matrix(Q[Ibeg:Ifin],rBins,contrast,FFfxn,Volfxn,FFargs).T |
---|
1538 | dist *= level[distFxn]['Volume'][0] |
---|
1539 | Ic[Ibeg:Ifin] += np.dot(Gmat,dist)*SFfxn(Q[Ibeg:Ifin],args=SFargs) |
---|
1540 | Rbins.append(rBins) |
---|
1541 | Dist.append(dist/(4.*dBins)) |
---|
1542 | elif 'Unified' in distFxn: |
---|
1543 | parmDict = level[controls['DistType']] |
---|
1544 | Rg,G,B,P,Rgco = parmDict['Rg'][0],parmDict['G'][0],parmDict['B'][0], \ |
---|
1545 | parmDict['P'][0],parmDict['Cutoff'][0] |
---|
1546 | Qstar = Q[Ibeg:Ifin]/(scsp.erf(Q[Ibeg:Ifin]*Rg/np.sqrt(6)))**3 |
---|
1547 | Guin = G*np.exp(-(Q[Ibeg:Ifin]*Rg)**2/3) |
---|
1548 | Porod = (B/Qstar**P)*np.exp(-(Q[Ibeg:Ifin]*Rgco)**2/3) |
---|
1549 | Ic[Ibeg:Ifin] += Guin+Porod |
---|
1550 | Rbins.append([]) |
---|
1551 | Dist.append([]) |
---|
1552 | elif 'Porod' in distFxn: |
---|
1553 | parmDict = level[controls['DistType']] |
---|
1554 | B,P,Rgco = parmDict['B'][0],parmDict['P'][0],parmDict['Cutoff'][0] |
---|
1555 | Porod = (B/Q[Ibeg:Ifin]**P)*np.exp(-(Q[Ibeg:Ifin]*Rgco)**2/3) |
---|
1556 | Ic[Ibeg:Ifin] += Porod |
---|
1557 | Rbins.append([]) |
---|
1558 | Dist.append([]) |
---|
1559 | elif 'Mono' in distFxn: |
---|
1560 | parmDict = level[controls['DistType']] |
---|
1561 | R = level[controls['DistType']]['Radius'][0] |
---|
1562 | FFfxn = shapes[controls['FormFact']][0] |
---|
1563 | Volfxn = shapes[controls['FormFact']][1] |
---|
1564 | SFfxn = sfxns[controls['StrFact']] |
---|
1565 | FFargs = [] |
---|
1566 | SFargs = [] |
---|
1567 | for item in ['Dist','VolFr','epis','Sticky','Depth','Width',]: |
---|
1568 | if item in controls.get('SFargs',{}): |
---|
1569 | SFargs.append(controls['SFargs'][item][0]) |
---|
1570 | for item in ['Aspect ratio','Length','Thickness','Diameter','Shell thickness']: |
---|
1571 | if item in controls['FFargs']: |
---|
1572 | FFargs.append(controls['FFargs'][item][0]) |
---|
1573 | contrast = controls['Contrast'] |
---|
1574 | Gmat = G_matrix(Q[Ibeg:Ifin],R,contrast,FFfxn,Volfxn,FFargs) |
---|
1575 | Ic[Ibeg:Ifin] += Gmat[0]*level[distFxn]['Volume'][0]*SFfxn(Q[Ibeg:Ifin],args=SFargs) |
---|
1576 | Rbins.append([]) |
---|
1577 | Dist.append([]) |
---|
1578 | elif 'Bragg' in distFxn: |
---|
1579 | parmDict = level[controls['DistType']] |
---|
1580 | Ic[Ibeg:Ifin] += parmDict['PkInt'][0]*G2pwd.getPsVoigt(parmDict['PkPos'][0], |
---|
1581 | parmDict['PkSig'][0],parmDict['PkGam'][0],Q[Ibeg:Ifin]) |
---|
1582 | Rbins.append([]) |
---|
1583 | Dist.append([]) |
---|
1584 | Ic[Ibeg:Ifin] += Back[0] |
---|
1585 | slitLen = Sample.get('SlitLen',0.) |
---|
1586 | if slitLen: |
---|
1587 | Ic[Ibeg:Ifin] = SmearData(Ic,Q,slitLen,Back[0])[Ibeg:Ifin] |
---|
1588 | sasdData['Size Calc'] = [Rbins,Dist] |
---|
1589 | |
---|
1590 | def MakeDiamDist(DistName,nPoints,cutoff,distDict): |
---|
1591 | |
---|
1592 | if 'LogNormal' in DistName: |
---|
1593 | distFxn = 'LogNormalDist' |
---|
1594 | cumeFxn = 'LogNormalCume' |
---|
1595 | pos = distDict['MinSize'] |
---|
1596 | args = [distDict['Mean'],distDict['StdDev']] |
---|
1597 | step = 4.*np.sqrt(np.exp(distDict['StdDev']**2)*(np.exp(distDict['StdDev']**2)-1.)) |
---|
1598 | mode = distDict['MinSize']+distDict['Mean']/np.exp(distDict['StdDev']**2) |
---|
1599 | minX = 1. #pos |
---|
1600 | maxX = 1.e4 #np.min([mode+nPoints*step*40,1.e5]) |
---|
1601 | elif 'Gauss' in DistName: |
---|
1602 | distFxn = 'GaussDist' |
---|
1603 | cumeFxn = 'GaussCume' |
---|
1604 | pos = distDict['Mean'] |
---|
1605 | args = [distDict['StdDev']] |
---|
1606 | mode = distDict['Mean'] |
---|
1607 | minX = np.max([mode-4.*distDict['StdDev'],1.]) |
---|
1608 | maxX = np.min([mode+4.*distDict['StdDev'],1.e5]) |
---|
1609 | elif 'LSW' in DistName: |
---|
1610 | distFxn = 'LSWDist' |
---|
1611 | cumeFxn = 'LSWCume' |
---|
1612 | pos = distDict['Mean'] |
---|
1613 | args = [] |
---|
1614 | minX,maxX = [0.,2.*pos] |
---|
1615 | elif 'Schulz' in DistName: |
---|
1616 | distFxn = 'SchulzZimmDist' |
---|
1617 | cumeFxn = 'SchulzZimmCume' |
---|
1618 | pos = distDict['Mean'] |
---|
1619 | args = [distDict['StdDev']] |
---|
1620 | minX = np.max([1.,pos-4.*distDict['StdDev']]) |
---|
1621 | maxX = np.min([pos+4.*distDict['StdDev'],1.e5]) |
---|
1622 | nP = 500 |
---|
1623 | Diam = np.logspace(0.,5.,nP,True) |
---|
1624 | TCW = eval(cumeFxn+'(Diam,pos,args)') |
---|
1625 | CumeTgts = np.linspace(cutoff,(1.-cutoff),nPoints+1,True) |
---|
1626 | rBins = np.interp(CumeTgts,TCW,Diam,0,0) |
---|
1627 | dBins = np.diff(rBins) |
---|
1628 | rBins = rBins[:-1]+dBins/2. |
---|
1629 | return rBins,dBins,eval(distFxn+'(rBins,pos,args)') |
---|
1630 | |
---|
1631 | ################################################################################ |
---|
1632 | #### MaxEnt testing stuff |
---|
1633 | ################################################################################ |
---|
1634 | |
---|
1635 | def print_vec(text, a): |
---|
1636 | '''print the contents of a vector to the console''' |
---|
1637 | n = a.shape[0] |
---|
1638 | print ("%s[ = (" % text,end='') |
---|
1639 | for i in range(n): |
---|
1640 | s = " %g, " % a[i] |
---|
1641 | print (s,end='') |
---|
1642 | print (")") |
---|
1643 | |
---|
1644 | def print_arr(text, a): |
---|
1645 | '''print the contents of an array to the console''' |
---|
1646 | n, m = a.shape |
---|
1647 | print ("%s[][] = (" % text) |
---|
1648 | for i in range(n): |
---|
1649 | print (" (",end='') |
---|
1650 | for j in range(m): |
---|
1651 | print (" %g, " % a[i][j],end='') |
---|
1652 | print ("),") |
---|
1653 | print (")") |
---|
1654 | |
---|
1655 | def test_MaxEnt_SB(report=True): |
---|
1656 | def readTextData(filename): |
---|
1657 | '''return q, I, dI from a 3-column text file''' |
---|
1658 | if not os.path.exists(filename): |
---|
1659 | raise Exception("file not found: " + filename) |
---|
1660 | buf = [line.split() for line in open(filename, 'r').readlines()] |
---|
1661 | buf = zip(*buf) # transpose rows and columns |
---|
1662 | q = np.array(buf[0], dtype=np.float64) |
---|
1663 | I = np.array(buf[1], dtype=np.float64) |
---|
1664 | dI = np.array(buf[2], dtype=np.float64) |
---|
1665 | return q, I, dI |
---|
1666 | print ("MaxEnt_SB: ") |
---|
1667 | test_data_file = os.path.join( 'testinp', 'test.sas') |
---|
1668 | rhosq = 100 # scattering contrast, 10^20 1/cm^-4 |
---|
1669 | bkg = 0.1 # I = I - bkg |
---|
1670 | dMin, dMax, nRadii = 25, 9000, 40 |
---|
1671 | defaultDistLevel = 1.0e-6 |
---|
1672 | IterMax = 40 |
---|
1673 | errFac = 1.05 |
---|
1674 | |
---|
1675 | r = np.logspace(math.log10(dMin), math.log10(dMax), nRadii)/2 |
---|
1676 | dr = r * (r[1]/r[0] - 1) # step size |
---|
1677 | f_dr = np.ndarray((nRadii)) * 0 # volume fraction histogram |
---|
1678 | b = np.ndarray((nRadii)) * 0 + defaultDistLevel # MaxEnt "sky background" |
---|
1679 | |
---|
1680 | qVec, I, dI = readTextData(test_data_file) |
---|
1681 | G = G_matrix(qVec,r,rhosq,SphereFF,SphereVol,args=()) |
---|
1682 | |
---|
1683 | chisq,f_dr,Ic = MaxEnt_SB(I - bkg, dI*errFac, b, IterMax, G, report=report) |
---|
1684 | if f_dr is None: |
---|
1685 | print ("no solution") |
---|
1686 | return |
---|
1687 | |
---|
1688 | print ("solution reached") |
---|
1689 | for a,b,c in zip(r.tolist(), dr.tolist(), f_dr.tolist()): |
---|
1690 | print ('%10.4f %10.4f %12.4g'%(a,b,c)) |
---|
1691 | |
---|
1692 | def tests(): |
---|
1693 | test_MaxEnt_SB(report=True) |
---|
1694 | |
---|
1695 | if __name__ == '__main__': |
---|
1696 | tests() |
---|
1697 | |
---|