source: trunk/GSASIIsasd.py @ 1327

Last change on this file since 1327 was 1327, checked in by vondreele, 9 years ago

Better error trapping for SASD fitting LS.

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