source: trunk/GSASIIsasd.py @ 1315

Last change on this file since 1315 was 1315, checked in by vondreele, 7 years ago

new controls parameter 'Contrast' & remove Substances from arguments list

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 56.0 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-01 20:20:21 +0000 (Thu, 01 May 2014) $
10# $Author: vondreele $
11# $Revision: 1315 $
12# $URL: trunk/GSASIIsasd.py $
13# $Id: GSASIIsasd.py 1315 2014-05-01 20:20:21Z 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: 1315 $")
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   
1003###############################################################################
1004#### Size distribution
1005###############################################################################
1006
1007def SizeDistribution(Profile,ProfDict,Limits,Sample,data):
1008    shapes = {'Spheroid':[SpheroidFF,SpheroidVol],'Cylinder':[CylinderDFF,CylinderDVol],
1009        'Cylinder AR':[CylinderARFF,CylinderARVol],'Unified sphere':[UniSphereFF,UniSphereVol],
1010        'Unified rod':[UniRodFF,UniRodVol],'Unified rod AR':[UniRodARFF,UniRodARVol],
1011        'Unified disk':[UniDiskFF,UniDiskVol],'Sphere':[SphereFF,SphereVol],
1012        'Cylinder diam':[CylinderDFF,CylinderDVol]}
1013    Shape = data['Size']['Shape'][0]
1014    Parms = data['Size']['Shape'][1:]
1015    if data['Size']['logBins']:
1016        Bins = np.logspace(np.log10(data['Size']['MinDiam']),np.log10(data['Size']['MaxDiam']),
1017            data['Size']['Nbins']+1,True)/2.        #make radii
1018    else:
1019        Bins = np.linspace(data['Size']['MinDiam'],data['Size']['MaxDiam'],
1020            data['Size']['Nbins']+1,True)/2.        #make radii
1021    Dbins = np.diff(Bins)
1022    Bins = Bins[:-1]+Dbins/2.
1023    Contrast = Sample['Contrast'][1]
1024    Scale = Sample['Scale'][0]
1025    Sky = 10**data['Size']['MaxEnt']['Sky']
1026    BinsBack = np.ones_like(Bins)*Sky*Scale/Contrast
1027    Back = data['Back']
1028    Q,Io,wt,Ic,Ib = Profile[:5]
1029    Qmin = Limits[1][0]
1030    Qmax = Limits[1][1]
1031    wtFactor = ProfDict['wtFactor']
1032    Ibeg = np.searchsorted(Q,Qmin)
1033    Ifin = np.searchsorted(Q,Qmax)
1034    BinMag = np.zeros_like(Bins)
1035    Ic[:] = 0.
1036    Gmat = G_matrix(Q[Ibeg:Ifin],Bins,Contrast,shapes[Shape][0],shapes[Shape][1],args=Parms)
1037    if 'MaxEnt' == data['Size']['Method']:
1038        chisq,BinMag,Ic[Ibeg:Ifin] = MaxEnt_SB(Scale*Io[Ibeg:Ifin]-Back[0],
1039            Scale/np.sqrt(wtFactor*wt[Ibeg:Ifin]),Gmat,BinsBack,
1040            data['Size']['MaxEnt']['Niter'],report=True)
1041    elif 'IPG' == data['Size']['Method']:
1042        chisq,BinMag,Ic[Ibeg:Ifin] = IPG(Scale*Io[Ibeg:Ifin]-Back[0],Scale/np.sqrt(wtFactor*wt[Ibeg:Ifin]),
1043            Gmat,Bins,Dbins,data['Size']['IPG']['Niter'],Q[Ibeg:Ifin],approach=0.8,
1044            Power=data['Size']['IPG']['Power'],report=True)
1045    Ib[:] = Back[0]
1046    Ic[Ibeg:Ifin] += Back[0]
1047    print ' Final chi^2: %.3f'%(chisq)
1048    Vols = shapes[Shape][1](Bins,Parms)
1049    data['Size']['Distribution'] = [Bins,Dbins,BinMag/(2.*Dbins)]
1050       
1051################################################################################
1052#### Modelling
1053################################################################################
1054
1055def ModelFit(Profile,ProfDict,Limits,Sample,Model):
1056    shapes = {'Spheroid':[SpheroidFF,SpheroidVol],'Cylinder':[CylinderDFF,CylinderDVol],
1057        'Cylinder AR':[CylinderARFF,CylinderARVol],'Unified sphere':[UniSphereFF,UniSphereVol],
1058        'Unified rod':[UniRodFF,UniRodVol],'Unified rod AR':[UniRodARFF,UniRodARVol],
1059        'Unified disk':[UniDiskFF,UniDiskVol],'Sphere':[SphereFF,SphereVol],
1060        'Unified tube':[UniTubeFF,UniTubeVol],'Cylinder diam':[CylinderDFF,CylinderDVol]}
1061           
1062    sfxns = {'Dilute':DiluteSF,'Hard sphere':HardSpheresSF,'Square well':SquareWellSF,
1063            'Sticky hard sphere':StickyHardSpheresSF,'InterPrecipitate':InterPrecipitateSF,}
1064               
1065    parmOrder = ['Volume','Radius','Mean','StdDev','MinSize','G','Rg','B','P','Cutoff',
1066        'PkInt','PkPos','PkSig','PkGam',]
1067       
1068    FFparmOrder = ['Aspect ratio','Length','Diameter','Thickness']
1069   
1070    SFparmOrder = ['Dist','VolFr','epis','Sticky','Depth','Width']
1071
1072    def GetModelParms():
1073        parmDict = {'Scale':Sample['Scale'][0]}
1074        varyList = []
1075        values = []
1076        levelTypes = []
1077        Back = Model['Back']
1078        if Back[1]:
1079            varyList += ['Back',]
1080            values.append(Back[0])
1081        parmDict['Back'] = Back[0]
1082        partData = Model['Particle']
1083        for i,level in enumerate(partData['Levels']):
1084            cid = str(i)+':'
1085            controls = level['Controls']
1086            Type = controls['DistType']
1087            levelTypes.append(Type)
1088            if Type in ['LogNormal','Gaussian','LSW','Schulz-Zimm','Monodisperse']:
1089                if Type not in ['Monodosperse',]:
1090                    parmDict[cid+'NumPoints'] = controls['NumPoints']
1091                    parmDict[cid+'Cutoff'] = controls['Cutoff']
1092                parmDict[cid+'FormFact'] = shapes[controls['FormFact']][0]
1093                parmDict[cid+'FFVolume'] = shapes[controls['FormFact']][1]
1094                parmDict[cid+'StrFact'] = sfxns[controls['StrFact']]
1095                parmDict[cid+'Contrast'] = controls['Contrast']
1096                for item in FFparmOrder:
1097                    if item in controls['FFargs']:
1098                        parmDict[cid+item] = controls['FFargs'][item][0]
1099                        if controls['FFargs'][item][1]:
1100                            varyList.append(cid+item)
1101                            values.append(controls['FFargs'][item][0])
1102                for item in SFparmOrder:
1103                    if item in controls.get('SFargs',{}):
1104                        parmDict[cid+item] = controls['SFargs'][item][0]
1105                        if controls['SFargs'][item][1]:
1106                            varyList.append(cid+item)
1107                            values.append(controls['SFargs'][item][0])
1108            distDict = controls['DistType']
1109            for item in parmOrder:
1110                if item in level[distDict]:
1111                    parmDict[cid+item] = level[distDict][item][0]
1112                    if level[distDict][item][1]:
1113                        values.append(level[distDict][item][0])
1114                        varyList.append(cid+item)
1115        return levelTypes,parmDict,varyList,values
1116       
1117    def SetModelParms():
1118        print ' Refined parameters: Histogram scale: %.4g'%(parmDict['Scale'])
1119        if 'Back' in varyList:
1120            Model['Back'][0] = parmDict['Back']
1121            print %15s %15.4f esd: %15.4g'%('Background:',parmDict['Back'],sigDict['Back'])
1122        partData = Model['Particle']
1123        for i,level in enumerate(partData['Levels']):
1124            controls = level['Controls']
1125            Type = controls['DistType']
1126            if Type in ['LogNormal','Gaussian','LSW','Schulz-Zimm','Monodisperse']:
1127                print ' Component %d: Type: %s: Structure Factor: %s Contrast: %12.3f'  \
1128                    %(i,Type,controls['StrFact'],controls['Contrast'])               
1129            else:
1130                print ' Component %d: Type: %s: '%(i,Type,)
1131            cid = str(i)+':'
1132            if Type in ['LogNormal','Gaussian','LSW','Schulz-Zimm','Monodisperse']:
1133                for item in FFparmOrder:
1134                    if cid+item in varyList:
1135                        controls['FFargs'][item][0] = parmDict[cid+item]
1136                        print ' %15s: %15.4g esd: %15.4g'%(cid+item,parmDict[cid+item],sigDict[cid+item])
1137                for item in SFparmOrder:
1138                    if cid+item in varyList:
1139                        controls['SFargs'][item][0] = parmDict[cid+item]
1140                        print ' %15s: %15.4g esd: %15.4g'%(cid+item,parmDict[cid+item],sigDict[cid+item])
1141            distDict = controls['DistType']
1142            for item in level[distDict]:
1143                if cid+item in varyList:
1144                    level[distDict][item][0] = parmDict[cid+item]
1145                    print ' %15s: %15.4g esd: %15.4g'%(cid+item,parmDict[cid+item],sigDict[cid+item])
1146                   
1147    def calcSASD(values,Q,Io,wt,Ifb,levelTypes,parmDict,varyList):
1148        parmDict.update(zip(varyList,values))
1149        M = np.sqrt(wt)*(getSASD(Q,levelTypes,parmDict)+Ifb-parmDict['Scale']*Io)
1150        return M
1151       
1152    def getSASD(Q,levelTypes,parmDict):
1153        Ic = np.zeros_like(Q)
1154        for i,Type in enumerate(levelTypes):
1155            cid = str(i)+':'
1156            if Type in ['LogNormal','Gaussian','LSW','Schulz-Zimm']:
1157                FFfxn = parmDict[cid+'FormFact']
1158                Volfxn = parmDict[cid+'FFVolume']
1159                SFfxn = parmDict[cid+'StrFact']
1160                FFargs = []
1161                SFargs = []
1162                for item in [cid+'Aspect ratio',cid+'Length',cid+'Thickness',cid+'Diameter']:
1163                    if item in parmDict: 
1164                        FFargs.append(parmDict[item])
1165                for item in [cid+'Dist',cid+'VolFr',cid+'epis',cid+'Sticky',cid+'Depth',cid+'Width']:
1166                    if item in parmDict: 
1167                        SFargs.append(parmDict[item])
1168                distDict = {}
1169                for item in [cid+'Volume',cid+'Mean',cid+'StdDev',cid+'MinSize',]:
1170                    if item in parmDict:
1171                        distDict[item.split(':')[1]] = parmDict[item]
1172                contrast = parmDict[cid+'Contrast']
1173                rBins,dBins,dist = MakeDiamDist(Type,parmDict[cid+'NumPoints'],parmDict[cid+'Cutoff'],distDict)
1174                Gmat = G_matrix(Q,rBins,contrast,FFfxn,Volfxn,FFargs).T
1175                dist *= parmDict[cid+'Volume']
1176                Ic += np.dot(Gmat,dist)*SFfxn(Q,args=SFargs)
1177            elif 'Unified' in Type:
1178                Rg,G,B,P,Rgco = parmDict[cid+'Rg'],parmDict[cid+'G'],parmDict[cid+'B'], \
1179                    parmDict[cid+'P'],parmDict[cid+'Cutoff']
1180                Qstar = Q/(scsp.erf(Q*Rg/np.sqrt(6)))**3
1181                Guin = G*np.exp(-(Q*Rg)**2/3)
1182                Porod = (B/Qstar**P)*np.exp(-(Q*Rgco)**2/3)
1183                Ic += Guin+Porod
1184            elif 'Porod' in Type:
1185                B,P,Rgco = parmDict[cid+'B'],parmDict[cid+'P'],parmDict[cid+'Cutoff']
1186                Porod = (B/Q**P)*np.exp(-(Q*Rgco)**2/3)
1187                Ic += Porod
1188            elif 'Mono' in Type:
1189                FFfxn = parmDict[cid+'FormFact']
1190                Volfxn = parmDict[cid+'FFVolume']
1191                SFfxn = parmDict[cid+'StrFact']
1192                FFargs = []
1193                SFargs = []
1194                for item in [cid+'Aspect ratio',cid+'Length',cid+'Thickness',cid+'Diameter',]:
1195                    if item in parmDict: 
1196                        FFargs.append(parmDict[item])
1197                for item in [cid+'Dist',cid+'VolFr',cid+'epis',cid+'Sticky',cid+'Depth',cid+'Width']:
1198                    if item in parmDict: 
1199                        SFargs.append(parmDict[item])
1200                contrast = parmDict[cid+'Contrast']
1201                R = parmDict[cid+'Radius']
1202                Gmat = G_matrix(Q,R,contrast,FFfxn,Volfxn,FFargs)             
1203                Ic += Gmat[0]*parmDict[cid+'Volume']*SFfxn(Q,args=SFargs)
1204            elif 'Bragg' in distFxn:
1205                Ic += parmDict[cid+'PkInt']*G2pwd.getPsVoigt(parmDict[cid+'PkPos'],
1206                    parmDict[cid+'PkSig'],parmDict[cid+'PkGam'],Q)
1207        Ic += parmDict['Back']  #/parmDict['Scale']
1208        return Ic
1209       
1210    Q,Io,wt,Ic,Ib,Ifb = Profile[:6]
1211    Qmin = Limits[1][0]
1212    Qmax = Limits[1][1]
1213    wtFactor = ProfDict['wtFactor']
1214    Ibeg = np.searchsorted(Q,Qmin)
1215    Ifin = np.searchsorted(Q,Qmax)
1216    Ic[:] = 0
1217    levelTypes,parmDict,varyList,values = GetModelParms()
1218    if varyList:
1219        result = so.leastsq(calcSASD,values,full_output=True,epsfcn=1.e-8,   #ftol=Ftol,
1220            args=(Q[Ibeg:Ifin],Io[Ibeg:Ifin],wtFactor*wt[Ibeg:Ifin],Ifb[Ibeg:Ifin],levelTypes,parmDict,varyList))
1221        ncyc = int(result[2]['nfev']/2)
1222        parmDict.update(zip(varyList,result[0]))
1223        chisq = np.sum(result[2]['fvec']**2)
1224        ncalc = result[2]['nfev']
1225        covM = result[1]
1226    else:   #nothing varied
1227        M = calcSASD(values,Q[Ibeg:Ifin],Io[Ibeg:Ifin],wtFactor*wt[Ibeg:Ifin],Ifb[Ibeg:Ifin],levelTypes,parmDict,varyList)
1228        chisq = np.sum(M**2)
1229        ncalc = 0
1230        covM = []
1231        sig = []
1232        sigDict = {}
1233        result = []
1234    Rvals = {}
1235    Rvals['Rwp'] = np.sqrt(chisq/np.sum(wt[Ibeg:Ifin]*Io[Ibeg:Ifin]**2))*100.      #to %
1236    Rvals['GOF'] = chisq/(Ifin-Ibeg-len(varyList))       #reduced chi^2
1237    Ic[Ibeg:Ifin] = getSASD(Q[Ibeg:Ifin],levelTypes,parmDict)
1238    try:
1239        if len(covM):
1240            sig = np.sqrt(np.diag(covM)*Rvals['GOF'])
1241            sigDict = dict(zip(varyList,sig))
1242        print ' Results of small angle data modelling fit:'
1243        print 'Number of function calls:',ncalc,' Number of observations: ',Ifin-Ibeg,' Number of parameters: ',len(varyList)
1244        print 'Rwp = %7.2f%%, chi**2 = %12.6g, reduced chi**2 = %6.2f'%(Rvals['Rwp'],chisq,Rvals['GOF'])
1245        SetModelParms()
1246        covMatrix = covM*Rvals['GOF']
1247        return True,result,varyList,sig,Rvals,covMatrix
1248    except ValueError:
1249        return False,0,0,0,0,0
1250   
1251def ModelFxn(Profile,ProfDict,Limits,Sample,sasdData):
1252   
1253    shapes = {'Spheroid':[SpheroidFF,SpheroidVol],'Cylinder':[CylinderDFF,CylinderDVol],
1254        'Cylinder AR':[CylinderARFF,CylinderARVol],'Unified sphere':[UniSphereFF,UniSphereVol],
1255        'Unified rod':[UniRodFF,UniRodVol],'Unified rod AR':[UniRodARFF,UniRodARVol],
1256        'Unified disk':[UniDiskFF,UniDiskVol],'Sphere':[SphereFF,SphereVol],
1257        'Unified tube':[UniTubeFF,UniTubeVol],'Cylinder diam':[CylinderDFF,CylinderDVol]}
1258    sfxns = {'Dilute':DiluteSF,'Hard sphere':HardSpheresSF,'Square well':SquareWellSF,
1259            'Sticky hard sphere':StickyHardSpheresSF,'InterPrecipitate':InterPrecipitateSF,}
1260
1261#    pdb.set_trace()
1262    partData = sasdData['Particle']
1263    matFrac = partData['Matrix']['VolFrac'] #[value,flag]       
1264    Scale = Sample['Scale']     #[value,flag]
1265    Back = sasdData['Back']
1266    Q,Io,wt,Ic,Ib,Ifb = Profile[:6]
1267    Qmin = Limits[1][0]
1268    Qmax = Limits[1][1]
1269    wtFactor = ProfDict['wtFactor']
1270    Ibeg = np.searchsorted(Q,Qmin)
1271    Ifin = np.searchsorted(Q,Qmax)
1272    Ib[:] = Back[0]
1273    Ic[:] = 0
1274    Rbins = []
1275    Dist = []
1276    for level in partData['Levels']:
1277        controls = level['Controls']
1278        distFxn = controls['DistType']
1279        if distFxn in ['LogNormal','Gaussian','LSW','Schulz-Zimm']:
1280            parmDict = level[controls['DistType']]
1281            FFfxn = shapes[controls['FormFact']][0]
1282            Volfxn = shapes[controls['FormFact']][1]
1283            SFfxn = sfxns[controls['StrFact']]
1284            FFargs = []
1285            SFargs = []
1286            for item in ['Dist','VolFr','epis','Sticky','Depth','Width',]:
1287                if item in controls.get('SFargs',{}):
1288                    SFargs.append(controls['SFargs'][item][0])
1289            for item in ['Aspect ratio','Length','Thickness','Diameter',]:
1290                if item in controls['FFargs']: 
1291                    FFargs.append(controls['FFargs'][item][0])
1292            contrast = controls['Contrast']
1293            distDict = {}
1294            for item in parmDict:
1295                distDict[item] = parmDict[item][0]
1296            rBins,dBins,dist = MakeDiamDist(controls['DistType'],controls['NumPoints'],controls['Cutoff'],distDict)
1297            Gmat = G_matrix(Q[Ibeg:Ifin],rBins,contrast,FFfxn,Volfxn,FFargs).T
1298            dist *= level[distFxn]['Volume'][0]
1299            Ic[Ibeg:Ifin] += np.dot(Gmat,dist)*SFfxn(Q[Ibeg:Ifin],args=SFargs)
1300            Rbins.append(rBins)
1301            Dist.append(dist/(4.*dBins))
1302        elif 'Unified' in distFxn:
1303            parmDict = level[controls['DistType']]
1304            Rg,G,B,P,Rgco = parmDict['Rg'][0],parmDict['G'][0],parmDict['B'][0],    \
1305                parmDict['P'][0],parmDict['Cutoff'][0]
1306            Qstar = Q[Ibeg:Ifin]/(scsp.erf(Q[Ibeg:Ifin]*Rg/np.sqrt(6)))**3
1307            Guin = G*np.exp(-(Q[Ibeg:Ifin]*Rg)**2/3)
1308            Porod = (B/Qstar**P)*np.exp(-(Q[Ibeg:Ifin]*Rgco)**2/3)
1309            Ic[Ibeg:Ifin] += Guin+Porod
1310            Rbins.append([])
1311            Dist.append([])
1312        elif 'Porod' in distFxn:
1313            parmDict = level[controls['DistType']]
1314            B,P,Rgco = parmDict['B'][0],parmDict['P'][0],parmDict['Cutoff'][0]
1315            Porod = (B/Q[Ibeg:Ifin]**P)*np.exp(-(Q[Ibeg:Ifin]*Rgco)**2/3)
1316            Ic[Ibeg:Ifin] += Porod
1317            Rbins.append([])
1318            Dist.append([])
1319        elif 'Mono' in distFxn:
1320            parmDict = level[controls['DistType']]
1321            R = level[controls['DistType']]['Radius'][0]
1322            FFfxn = shapes[controls['FormFact']][0]
1323            Volfxn = shapes[controls['FormFact']][1]
1324            SFfxn = sfxns[controls['StrFact']]
1325            FFargs = []
1326            SFargs = []
1327            for item in ['Dist','VolFr','epis','Sticky','Depth','Width',]:
1328                if item in controls.get('SFargs',{}):
1329                    SFargs.append(controls['SFargs'][item][0])
1330            for item in ['Aspect ratio','Length','Thickness','Diameter',]:
1331                if item in controls['FFargs']: 
1332                    FFargs.append(controls['FFargs'][item][0])
1333            contrast = controls['Contrast']
1334            Gmat = G_matrix(Q[Ibeg:Ifin],R,contrast,FFfxn,Volfxn,FFargs)             
1335            Ic[Ibeg:Ifin] += Gmat[0]*level[distFxn]['Volume'][0]*SFfxn(Q[Ibeg:Ifin],args=SFargs)
1336            Rbins.append([])
1337            Dist.append([])
1338        elif 'Bragg' in distFxn:
1339            parmDict = level[controls['DistType']]
1340            Ic[Ibeg:Ifin] += parmDict['PkInt'][0]*G2pwd.getPsVoigt(parmDict['PkPos'][0],
1341                parmDict['PkSig'][0],parmDict['PkGam'][0],Q[Ibeg:Ifin])
1342            Rbins.append([])
1343            Dist.append([])
1344    Ic[Ibeg:Ifin] += Back[0]
1345    sasdData['Size Calc'] = [Rbins,Dist]
1346   
1347def MakeDiamDist(DistName,nPoints,cutoff,distDict):
1348   
1349    if 'LogNormal' in DistName:
1350        distFxn = 'LogNormalDist'
1351        cumeFxn = 'LogNormalCume'
1352        pos = distDict['MinSize']
1353        args = [distDict['Mean'],distDict['StdDev']]
1354        step = 4.*np.sqrt(np.exp(distDict['StdDev']**2)*(np.exp(distDict['StdDev']**2)-1.))
1355        mode = distDict['MinSize']+distDict['Mean']/np.exp(distDict['StdDev']**2)
1356        minX = 1. #pos
1357        maxX = 1.e4 #np.min([mode+nPoints*step*40,1.e5])
1358    elif 'Gauss' in DistName:
1359        distFxn = 'GaussDist'
1360        cumeFxn = 'GaussCume'
1361        pos = distDict['Mean']
1362        args = [distDict['StdDev']]
1363        step = 0.02*distDict['StdDev']
1364        mode = distDict['Mean']
1365        minX = np.max([mode-4.*distDict['StdDev'],1.])
1366        maxX = np.min([mode+4.*distDict['StdDev'],1.e5])
1367    elif 'LSW' in DistName:
1368        distFxn = 'LSWDist'
1369        cumeFxn = 'LSWCume'
1370        pos = distDict['Mean']
1371        args = []
1372        minX,maxX = [0.,2.*pos]
1373    elif 'Schulz' in DistName:
1374        distFxn = 'SchulzZimmDist'
1375        cumeFxn = 'SchulzZimmCume'
1376        pos = distDict['Mean']
1377        args = [distDict['StdDev']]
1378        minX = np.max([1.,pos-4.*distDict['StdDev']])
1379        maxX = np.min([pos+4.*distDict['StdDev'],1.e5])
1380    nP = 500
1381    Diam = np.logspace(0.,5.,nP,True)
1382    TCW = eval(cumeFxn+'(Diam,pos,args)')
1383    CumeTgts = np.linspace(cutoff,(1.-cutoff),nPoints+1,True)
1384    rBins = np.interp(CumeTgts,TCW,Diam,0,0)
1385    dBins = np.diff(rBins)
1386    rBins = rBins[:-1]+dBins/2.
1387    return rBins,dBins,eval(distFxn+'(rBins,pos,args)')
1388
1389################################################################################
1390#### MaxEnt testing stuff
1391################################################################################
1392
1393def print_vec(text, a):
1394    '''print the contents of a vector to the console'''
1395    n = a.shape[0]
1396    print "%s[ = (" % text,
1397    for i in range(n):
1398        s = " %g, " % a[i]
1399        print s,
1400    print ")"
1401
1402def print_arr(text, a):
1403    '''print the contents of an array to the console'''
1404    n, m = a.shape
1405    print "%s[][] = (" % text
1406    for i in range(n):
1407        print " (",
1408        for j in range(m):
1409            print " %g, " % a[i][j],
1410        print "),"
1411    print ")"
1412
1413def test_MaxEnt_SB(report=True):
1414    def readTextData(filename):
1415        '''return q, I, dI from a 3-column text file'''
1416        if not os.path.exists(filename):
1417            raise Exception("file not found: " + filename)
1418        buf = [line.split() for line in open(filename, 'r').readlines()]
1419        M = len(buf)
1420        buf = zip(*buf)         # transpose rows and columns
1421        q  = np.array(buf[0], dtype=np.float64)
1422        I  = np.array(buf[1], dtype=np.float64)
1423        dI = np.array(buf[2], dtype=np.float64)
1424        return q, I, dI
1425    print "MaxEnt_SB: "
1426    test_data_file = os.path.join( 'testinp', 'test.sas')
1427    rhosq = 100     # scattering contrast, 10^20 1/cm^-4
1428    bkg   = 0.1     #   I = I - bkg
1429    dMin, dMax, nRadii = 25, 9000, 40
1430    defaultDistLevel = 1.0e-6
1431    IterMax = 40
1432    errFac = 1.05
1433   
1434    r    = np.logspace(math.log10(dMin), math.log10(dMax), nRadii)/2
1435    dr   = r * (r[1]/r[0] - 1)          # step size
1436    f_dr = np.ndarray((nRadii)) * 0  # volume fraction histogram
1437    b    = np.ndarray((nRadii)) * 0 + defaultDistLevel  # MaxEnt "sky background"
1438   
1439    qVec, I, dI = readTextData(test_data_file)
1440    G = G_matrix(qVec,r,rhosq,SphereFF,SphereVol,args=())
1441   
1442    chisq,f_dr,Ic = MaxEnt_SB(I - bkg, dI*errFac, b, IterMax, G, report=report)
1443    if f_dr is None:
1444        print "no solution"
1445        return
1446   
1447    print "solution reached"
1448    for a,b,c in zip(r.tolist(), dr.tolist(), f_dr.tolist()):
1449        print '%10.4f %10.4f %12.4g'%(a,b,c)
1450
1451def tests():
1452    test_MaxEnt_SB(report=True)
1453
1454if __name__ == '__main__':
1455    tests()
1456
Note: See TracBrowser for help on using the repository browser.