source: trunk/GSASIIsasd.py @ 1277

Last change on this file since 1277 was 1277, checked in by vondreele, 8 years ago

1st working SASD fit version. Implement UnDo? option for SASD fitting.

File size: 49.5 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-01-09 11:09:53 -0600 (Thu, 09 Jan 2014) $
10# $Author: vondreele $
11# $Revision: 1186 $
12# $URL: https://subversion.xray.aps.anl.gov/pyGSAS/trunk/GSASIIsasd.py $
13# $Id: GSASIIsasd.py 1186 2014-01-09 17:09:53Z 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: 1186 $")
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################################################################################
439##### SB-MaxEnt
440################################################################################
441
442def G_matrix(q,r,contrast,FFfxn,Volfxn,args=()):
443    '''Calculates the response matrix :math:`G(Q,r)`
444   
445    :param float q: :math:`Q`
446    :param float r: :math:`r`
447    :param float contrast: :math:`|\\Delta\\rho|^2`, the scattering contrast
448    :param function FFfxn: form factor function FF(q,r,args)
449    :param function Volfxn: volume function Vol(r,args)
450    :returns float: G(Q,r)
451    '''
452    FF = FFfxn(q,r,args)
453    Vol = Volfxn(r,args)
454    return 1.e-4*(contrast*Vol*FF**2).T     #10^-20 vs 10^-24
455   
456'''
457sbmaxent
458
459Entropy maximization routine as described in the article
460J Skilling and RK Bryan; MNRAS 211 (1984) 111 - 124.
461("MNRAS": "Monthly Notices of the Royal Astronomical Society")
462
463:license: Copyright (c) 2013, UChicago Argonne, LLC
464:license: This file is distributed subject to a Software License Agreement found
465     in the file LICENSE that is included with this distribution.
466
467References:
468
4691. J Skilling and RK Bryan; MON NOT R ASTR SOC 211 (1984) 111 - 124.
4702. JA Potton, GJ Daniell, and BD Rainford; Proc. Workshop
471   Neutron Scattering Data Analysis, Rutherford
472   Appleton Laboratory, UK, 1986; ed. MW Johnson,
473   IOP Conference Series 81 (1986) 81 - 86, Institute
474   of Physics, Bristol, UK.
4753. ID Culverwell and GP Clarke; Ibid. 87 - 96.
4764. JA Potton, GK Daniell, & BD Rainford,
477   J APPL CRYST 21 (1988) 663 - 668.
4785. JA Potton, GJ Daniell, & BD Rainford,
479   J APPL CRYST 21 (1988) 891 - 897.
480
481'''
482
483class MaxEntException(Exception): 
484    '''Any exception from this module'''
485    pass
486
487def MaxEnt_SB(datum, sigma, G, base, IterMax, image_to_data=None, data_to_image=None, report=False):
488    '''
489    do the complete Maximum Entropy algorithm of Skilling and Bryan
490   
491    :param float datum[]:
492    :param float sigma[]:
493    :param float[][] G: transformation matrix
494    :param float base[]:
495    :param int IterMax:
496    :param obj image_to_data: opus function (defaults to opus)
497    :param obj data_to_image: tropus function (defaults to tropus)
498   
499    :returns float[]: :math:`f(r) dr`
500    '''
501       
502    TEST_LIMIT        = 0.05                    # for convergence
503    CHI_SQR_LIMIT     = 0.01                    # maximum difference in ChiSqr for a solution
504    SEARCH_DIRECTIONS = 3                       # <10.  This code requires value = 3
505    RESET_STRAYS      = 1                       # was 0.001, correction of stray negative values
506    DISTANCE_LIMIT_FACTOR = 0.1                 # limitation on df to constrain runaways
507   
508    MAX_MOVE_LOOPS    = 500                     # for no solution in routine: move,
509    MOVE_PASSES       = 0.001                   # convergence test in routine: move
510
511    def tropus (data, G):
512        '''
513        tropus: transform data-space -> solution-space:  [G] * data
514       
515        default definition, caller can use this definition or provide an alternative
516       
517        :param float[M] data: observations, ndarray of shape (M)
518        :param float[M][N] G: transformation matrix, ndarray of shape (M,N)
519        :returns float[N]: calculated image, ndarray of shape (N)
520        '''
521        return G.dot(data)
522
523    def opus (image, G):
524        '''
525        opus: transform solution-space -> data-space:  [G]^tr * image
526       
527        default definition, caller can use this definition or provide an alternative
528       
529        :param float[N] image: solution, ndarray of shape (N)
530        :param float[M][N] G: transformation matrix, ndarray of shape (M,N)
531        :returns float[M]: calculated data, ndarray of shape (M)
532        '''
533        return np.dot(G.T,image)    #G.transpose().dot(image)
534
535    def Dist(s2, beta):
536        '''measure the distance of this possible solution'''
537        w = 0
538        n = beta.shape[0]
539        for k in range(n):
540            z = -sum(s2[k] * beta)
541            w += beta[k] * z
542        return w
543   
544    def ChiNow(ax, c1, c2, s1, s2):
545        '''
546        ChiNow
547       
548        :returns tuple: (ChiNow computation of ``w``, beta)
549        '''
550       
551        bx = 1 - ax
552        a =   bx * c2  -  ax * s2
553        b = -(bx * c1  -  ax * s1)
554   
555        beta = ChoSol(a, b)
556        w = 1.0
557        for k in range(SEARCH_DIRECTIONS):
558            w += beta[k] * (c1[k] + 0.5*sum(c2[k] * beta))
559        return w, beta
560   
561    def ChoSol(a, b):
562        '''
563        ChoSol: ? chop the solution vectors ?
564       
565        :returns: new vector beta
566        '''
567        n = b.shape[0]
568        fl = np.zeros((n,n))
569        bl = np.zeros_like(b)
570       
571        #print_arr("ChoSol: a", a)
572        #print_vec("ChoSol: b", b)
573   
574        if (a[0][0] <= 0):
575            msg = "ChoSol: a[0][0] = " 
576            msg += str(a[0][0])
577            msg += '  Value must be positive'
578            raise MaxEntException(msg)
579   
580        # first, compute fl from a
581        # note fl is a lower triangular matrix
582        fl[0][0] = math.sqrt (a[0][0])
583        for i in (1, 2):
584            fl[i][0] = a[i][0] / fl[0][0]
585            for j in range(1, i+1):
586                z = 0.0
587                for k in range(j):
588                    z += fl[i][k] * fl[j][k]
589                    #print "ChoSol: %d %d %d  z = %lg" % ( i, j, k, z)
590                z = a[i][j] - z
591                if j == i:
592                    y = math.sqrt(z)
593                else:
594                    y = z / fl[j][j]
595                fl[i][j] = y
596        #print_arr("ChoSol: fl", fl)
597   
598        # next, compute bl from fl and b
599        bl[0] = b[0] / fl[0][0]
600        for i in (1, 2):
601            z = 0.0
602            for k in range(i):
603                z += fl[i][k] * bl[k]
604                #print "\t", i, k, z
605            bl[i] = (b[i] - z) / fl[i][i]
606        #print_vec("ChoSol: bl", bl)
607   
608        # last, compute beta from bl and fl
609        beta = np.empty((n))
610        beta[-1] = bl[-1] / fl[-1][-1]
611        for i in (1, 0):
612            z = 0.0
613            for k in range(i+1, n):
614                z += fl[k][i] * beta[k]
615                #print "\t\t", i, k, 'z=', z
616            beta[i] = (bl[i] - z) / fl[i][i]
617        #print_vec("ChoSol: beta", beta)
618   
619        return beta
620
621    def MaxEntMove(fSum, blank, chisq, chizer, c1, c2, s1, s2):
622        '''
623        move beta one step closer towards the solution
624        '''
625        a_lower, a_upper = 0., 1.          # bracket  "a"
626        cmin, beta = ChiNow (a_lower, c1, c2, s1, s2)
627        #print "MaxEntMove: cmin = %g" % cmin
628        if cmin*chisq > chizer:
629            ctarg = (1.0 + cmin)/2
630        else:
631            ctarg = chizer/chisq
632        f_lower = cmin - ctarg
633        c_upper, beta = ChiNow (a_upper, c1, c2, s1, s2)
634        f_upper = c_upper - ctarg
635   
636        fx = 2*MOVE_PASSES      # just to start off
637        loop = 1
638        while abs(fx) >= MOVE_PASSES and loop <= MAX_MOVE_LOOPS:
639            a_new = (a_lower + a_upper) * 0.5           # search by bisection
640            c_new, beta = ChiNow (a_new, c1, c2, s1, s2)
641            fx = c_new - ctarg
642            # tighten the search range for the next pass
643            if f_lower*fx > 0:
644                a_lower, f_lower = a_new, fx
645            if f_upper*fx > 0:
646                a_upper, f_upper = a_new, fx
647            loop += 1
648   
649        if abs(fx) >= MOVE_PASSES or loop > MAX_MOVE_LOOPS:
650            msg = "MaxEntMove: Loop counter = " 
651            msg += str(MAX_MOVE_LOOPS)
652            msg += '  No convergence in alpha chop'
653            raise MaxEntException(msg)
654   
655        w = Dist (s2, beta);
656        m = SEARCH_DIRECTIONS
657        if (w > DISTANCE_LIMIT_FACTOR*fSum/blank):        # invoke the distance penalty, SB eq. 17
658            for k in range(m):
659                beta[k] *= math.sqrt (fSum/(blank*w))
660        chtarg = ctarg * chisq
661        return w, chtarg, loop, a_new, fx, beta
662       
663#MaxEnt_SB starts here   
664
665    if image_to_data == None:
666        image_to_data = opus
667    if data_to_image == None:
668        data_to_image = tropus
669    n   = len(base)
670    npt = len(datum)
671
672    # Note that the order of subscripts for
673    # "xi" and "eta" has been reversed from
674    # the convention used in the FORTRAN version
675    # to enable parts of them to be passed as
676    # as vectors to "image_to_data" and "data_to_image".
677    xi      = np.zeros((SEARCH_DIRECTIONS, n))
678    eta     = np.zeros((SEARCH_DIRECTIONS, npt))
679    beta    = np.zeros((SEARCH_DIRECTIONS))
680    # s1      = np.zeros((SEARCH_DIRECTIONS))
681    # c1      = np.zeros((SEARCH_DIRECTIONS))
682    s2      = np.zeros((SEARCH_DIRECTIONS, SEARCH_DIRECTIONS))
683    c2      = np.zeros((SEARCH_DIRECTIONS, SEARCH_DIRECTIONS))
684
685    # TODO: replace blank (scalar) with base (vector)
686    blank = sum(base) / len(base)   # use the average value of base
687
688    chizer, chtarg = npt*1.0, npt*1.0
689    f = base * 1.0                              # starting distribution is base
690
691    fSum  = sum(f)                              # find the sum of the f-vector
692    z = (datum - image_to_data (f, G)) / sigma  # standardized residuals, SB eq. 3
693    chisq = sum(z*z)                            # Chi^2, SB eq. 4
694
695    for iter in range(IterMax):
696        ox = -2 * z / sigma                        # gradient of Chi^2
697
698        cgrad = data_to_image (ox, G)              # cgrad[i] = del(C)/del(f[i]), SB eq. 8
699        sgrad = -np.log(f/base) / (blank*math.exp (1.0))  # sgrad[i] = del(S)/del(f[i])
700        snorm = math.sqrt(sum(f * sgrad*sgrad))    # entropy term, SB eq. 22
701        cnorm = math.sqrt(sum(f * cgrad*cgrad))    # ChiSqr term, SB eq. 22
702        tnorm = sum(f * sgrad * cgrad)             # norm for gradient term TEST
703
704        a = 1.0
705        b = 1.0 / cnorm
706        if iter == 0:
707            test = 0.0     # mismatch between entropy and ChiSquared gradients
708        else:
709            test = math.sqrt( ( 1.0 - tnorm/(snorm*cnorm) )/2 ) # SB eq. 37?
710            a = 0.5 / (snorm * test)
711            b *= 0.5 / test
712        xi[0] = f * cgrad / cnorm
713        xi[1] = f * (a * sgrad - b * cgrad)
714
715        eta[0] = image_to_data (xi[0], G);          # image --> data
716        eta[1] = image_to_data (xi[1], G);          # image --> data
717        ox = eta[1] / (sigma * sigma)
718        xi[2] = data_to_image (ox, G);              # data --> image
719        a = 1.0 / math.sqrt(sum(f * xi[2]*xi[2]))
720        xi[2] = f * xi[2] * a
721        eta[2] = image_to_data (xi[2], G)           # image --> data
722       
723#         print_arr("MaxEnt: eta.transpose()", eta.transpose())
724#         print_arr("MaxEnt: xi.transpose()", xi.transpose())
725
726        # prepare the search directions for the conjugate gradient technique
727        c1 = xi.dot(cgrad) / chisq                          # C_mu, SB eq. 24
728        s1 = xi.dot(sgrad)                          # S_mu, SB eq. 24
729#         print_vec("MaxEnt: c1", c1)
730#         print_vec("MaxEnt: s1", s1)
731
732        for k in range(SEARCH_DIRECTIONS):
733            for l in range(k+1):
734                c2[k][l] = 2 * sum(eta[k] * eta[l] / sigma/sigma) / chisq
735                s2[k][l] = -sum(xi[k] * xi[l] / f) / blank
736#         print_arr("MaxEnt: c2", c2)
737#         print_arr("MaxEnt: s2", s2)
738
739        # reflect across the body diagonal
740        for k, l in ((0,1), (0,2), (1,2)):
741            c2[k][l] = c2[l][k]                     #  M_(mu,nu)
742            s2[k][l] = s2[l][k]                     #  g_(mu,nu)
743 
744        beta[0] = -0.5 * c1[0] / c2[0][0]
745        beta[1] = 0.0
746        beta[2] = 0.0
747        if (iter > 0):
748            w, chtarg, loop, a_new, fx, beta = MaxEntMove(fSum, blank, chisq, chizer, c1, c2, s1, s2)
749
750        f_old = f.copy()    # preserve the last image
751        f += xi.transpose().dot(beta)   # move the image towards the solution, SB eq. 25
752       
753        # As mentioned at the top of p.119,
754        # need to protect against stray negative values.
755        # In this case, set them to RESET_STRAYS * base[i]
756        #f = f.clip(RESET_STRAYS * blank, f.max())
757        for i in range(n):
758            if f[i] <= 0.0:
759                f[i] = RESET_STRAYS * base[i]
760        df = f - f_old
761        fSum = sum(f)
762        fChange = sum(df)
763
764        # calculate the normalized entropy
765        S = sum((f/fSum) * np.log(f/fSum))      # normalized entropy, S&B eq. 1
766        z = (datum - image_to_data (f, G)) / sigma  # standardized residuals
767        chisq = sum(z*z)                            # report this ChiSq
768
769        if report:
770            print " MaxEnt trial/max: %3d/%3d" % ((iter+1), IterMax)
771            print " Residual: %5.2lf%% Entropy: %8lg" % (100*test, S)
772            if iter > 0:
773                value = 100*( math.sqrt(chisq/chtarg)-1)
774            else:
775                value = 0
776      #      print " %12.5lg %10.4lf" % ( math.sqrt(chtarg/npt), value )
777            print " Function sum: %.6lg Change from last: %.2lf%%\n" % (fSum,100*fChange/fSum)
778
779        # See if we have finished our task.
780        # do the hardest test first
781        if (abs(chisq/chizer-1.0) < CHI_SQR_LIMIT) and  (test < TEST_LIMIT):
782            print ' Convergence achieved.'
783            return chisq,f,image_to_data(f, G)     # solution FOUND returns here
784    print ' No convergence! Try increasing Error multiplier.'
785    return chisq,f,image_to_data(f, G)       # no solution after IterMax iterations
786
787   
788###############################################################################
789#### IPG/TNNLS Routines
790###############################################################################
791
792def IPG(datum,sigma,G,Bins,Dbins,IterMax,Qvec=[],approach=0.8,Power=-1,report=False):
793    ''' An implementation of the Interior-Point Gradient method of
794    Michael Merritt & Yin Zhang, Technical Report TR04-08, Dept. of Comp. and
795    Appl. Math., Rice Univ., Houston, Texas 77005, U.S.A. found on the web at
796    http://www.caam.rice.edu/caam/trs/2004/TR04-08.pdf
797    Problem addressed: Total Non-Negative Least Squares (TNNLS)
798    :param float datum[]:
799    :param float sigma[]:
800    :param float[][] G: transformation matrix
801    :param int IterMax:
802    :param float Qvec: data positions for Power = 0-4
803    :param float approach: 0.8 default fitting parameter
804    :param int Power: 0-4 for Q^Power weighting, -1 to use input sigma
805   
806    '''
807    if Power < 0: 
808        GmatE = G/sigma[:np.newaxis]
809        IntE = datum/sigma
810        pwr = 0
811        QvecP = np.ones_like(datum)
812    else:
813        GmatE = G[:]
814        IntE = datum[:]
815        pwr = Power
816        QvecP = Qvec**pwr
817    Amat = GmatE*QvecP[:np.newaxis]
818    AAmat = np.inner(Amat,Amat)
819    Bvec = IntE*QvecP
820    Xw = np.ones_like(Bins)*1.e-32
821    calc = np.dot(G.T,Xw)
822    nIter = 0
823    err = 10.
824    while (nIter<IterMax) and (err > 1.):
825        #Step 1 in M&Z paper:
826        Qk = np.inner(AAmat,Xw)-np.inner(Amat,Bvec)
827        Dk = Xw/np.inner(AAmat,Xw)
828        Pk = -Dk*Qk
829        #Step 2 in M&Z paper:
830        alpSt = -np.inner(Pk,Qk)/np.inner(Pk,np.inner(AAmat,Pk))
831        alpWv = -Xw/Pk
832        AkSt = [np.where(Pk[i]<0,np.min((approach*alpWv[i],alpSt)),alpSt) for i in range(Pk.shape[0])]
833        #Step 3 in M&Z paper:
834        shift = AkSt*Pk
835        Xw += shift
836        #done IPG; now results
837        nIter += 1
838        calc = np.dot(G.T,Xw)
839        chisq = np.sum(((datum-calc)/sigma)**2)
840        err = chisq/len(datum)
841        if report:
842            print ' Iteration: %d, chisq: %.3g, sum(shift^2): %.3g'%(nIter,chisq,np.sum(shift**2))
843    return chisq,Xw,calc
844
845###############################################################################
846#### SASD Utilities
847###############################################################################
848
849def SetScale(Data,refData):
850    Profile,Limits,Sample = Data
851    refProfile,refLimits,refSample = refData
852    x,y = Profile[:2]
853    rx,ry = refProfile[:2]
854    Beg = np.max([rx[0],x[0],Limits[1][0],refLimits[1][0]])
855    Fin = np.min([rx[-1],x[-1],Limits[1][1],refLimits[1][1]])
856    iBeg = np.searchsorted(x,Beg)
857    iFin = np.searchsorted(x,Fin)
858    sum = np.sum(y[iBeg:iFin])
859    refsum = np.sum(np.interp(x[iBeg:iFin],rx,ry,0,0))
860    Sample['Scale'][0] = refSample['Scale'][0]*refsum/sum
861   
862###############################################################################
863#### Size distribution
864###############################################################################
865
866def SizeDistribution(Profile,ProfDict,Limits,Substances,Sample,data):
867    shapes = {'Spheroid':[SpheroidFF,SpheroidVol],'Cylinder':[CylinderDFF,CylinderDVol],
868        'Cylinder AR':[CylinderARFF,CylinderARVol],'Unified sphere':[UniSphereFF,UniSphereVol],
869        'Unified rod':[UniRodFF,UniRodVol],'Unified rod AR':[UniRodARFF,UniRodARVol],
870        'Unified disk':[UniDiskFF,UniDiskVol],'Sphere':[SphereFF,SphereVol],
871        'Cylinder diam':[CylinderDFF,CylinderDVol]}
872    Shape = data['Size']['Shape'][0]
873    Parms = data['Size']['Shape'][1:]
874    if data['Size']['logBins']:
875        Bins = np.logspace(np.log10(data['Size']['MinDiam']),np.log10(data['Size']['MaxDiam']),
876            data['Size']['Nbins']+1,True)/2.        #make radii
877    else:
878        Bins = np.linspace(data['Size']['MinDiam'],data['Size']['MaxDiam'],
879            data['Size']['Nbins']+1,True)/2.        #make radii
880    Dbins = np.diff(Bins)
881    Bins = Bins[:-1]+Dbins/2.
882    Contrast = Sample['Contrast'][1]
883    Scale = Sample['Scale'][0]
884    Sky = 10**data['Size']['MaxEnt']['Sky']
885    BinsBack = np.ones_like(Bins)*Sky*Scale/Contrast
886    Back = data['Back']
887    Q,Io,wt,Ic,Ib = Profile[:5]
888    Qmin = Limits[1][0]
889    Qmax = Limits[1][1]
890    wtFactor = ProfDict['wtFactor']
891    Ibeg = np.searchsorted(Q,Qmin)
892    Ifin = np.searchsorted(Q,Qmax)
893    BinMag = np.zeros_like(Bins)
894    Ic[:] = 0.
895    Gmat = G_matrix(Q[Ibeg:Ifin],Bins,Contrast,shapes[Shape][0],shapes[Shape][1],args=Parms)
896    if 'MaxEnt' == data['Size']['Method']:
897        chisq,BinMag,Ic[Ibeg:Ifin] = MaxEnt_SB(Scale*Io[Ibeg:Ifin]-Back[0],
898            Scale/np.sqrt(wtFactor*wt[Ibeg:Ifin]),Gmat,BinsBack,
899            data['Size']['MaxEnt']['Niter'],report=True)
900    elif 'IPG' == data['Size']['Method']:
901        chisq,BinMag,Ic[Ibeg:Ifin] = IPG(Scale*Io[Ibeg:Ifin]-Back[0],Scale/np.sqrt(wtFactor*wt[Ibeg:Ifin]),
902            Gmat,Bins,Dbins,data['Size']['IPG']['Niter'],Q[Ibeg:Ifin],approach=0.8,
903            Power=data['Size']['IPG']['Power'],report=True)
904    Ib[:] = Back[0]
905    Ic[Ibeg:Ifin] += Back[0]
906    print ' Final chi^2: %.3f'%(chisq)
907    Vols = shapes[Shape][1](Bins,Parms)
908    data['Size']['Distribution'] = [Bins,Dbins,BinMag/(2.*Dbins)]
909       
910################################################################################
911#### Modelling
912################################################################################
913
914def ModelFit(Profile,ProfDict,Limits,Substances,Sample,Model):
915    shapes = {'Spheroid':[SpheroidFF,SpheroidVol],'Cylinder':[CylinderDFF,CylinderDVol],
916        'Cylinder AR':[CylinderARFF,CylinderARVol],'Unified sphere':[UniSphereFF,UniSphereVol],
917        'Unified rod':[UniRodFF,UniRodVol],'Unified rod AR':[UniRodARFF,UniRodARVol],
918        'Unified disk':[UniDiskFF,UniDiskVol],'Sphere':[SphereFF,SphereVol],
919        'Unified tube':[UniTubeFF,UniTubeVol],'Cylinder diam':[CylinderDFF,CylinderDVol]}
920
921    def GetModelParms():
922        parmDict = {}
923        varyList = []
924        values = []
925        levelTypes = []
926        Back = Model['Back']
927        if Back[1]:
928            varyList += ['Back',]
929            values.append(Back[0])
930        parmDict['Back'] = Back[0]
931        partData = Model['Particle']
932        parmDict['Matrix density'] = Substances['Substances'][partData['Matrix']['Name']].get('XAnom density',0.0)
933        for i,level in enumerate(partData['Levels']):
934            cid = str(i)+':'
935            controls = level['Controls']
936            Type = controls['DistType']
937            levelTypes.append(Type)
938            if Type in ['LogNormal','Gaussian','LSW','Schulz-Zimm','Monodisperse']:
939                if Type not in ['Monodosperse',]:
940                    parmDict[cid+'NumPoints'] = controls['NumPoints']
941                    parmDict[cid+'Cutoff'] = controls['Cutoff']
942                parmDict[cid+'FormFact'] = shapes[controls['FormFact']][0]
943                parmDict[cid+'FFVolume'] = shapes[controls['FormFact']][1]
944                parmDict[cid+'XAnom density'] = Substances['Substances'][controls['Material']].get('XAnom density',0.0)
945                for item in ['Aspect ratio','Length','Thickness','Diameter',]:
946                    if item in controls['FFargs']:
947                        parmDict[cid+item] = controls['FFargs'][item][0]
948                        if controls['FFargs'][item][1]:
949                            varyList.append(cid+item)
950                            values.append(controls['FFargs'][item][0])
951            distDict = controls['DistType']
952            for item in level[distDict]:
953                parmDict[cid+item] = level[distDict][item][0]
954                if level[distDict][item][1]:
955                    values.append(level[distDict][item][0])
956                    varyList.append(cid+item)
957        return levelTypes,parmDict,varyList,values
958       
959    def SetModelParms():
960        print ' Refined parameters:'
961        if 'Back' in varyList:
962            Model['Back'][0] = parmDict['Back']
963            print %15s %15.4f esd: %15.4g'%('Background:',parmDict['Back'],sigDict['Back'])
964        partData = Model['Particle']
965        for i,level in enumerate(partData['Levels']):
966            Type = level['Controls']['DistType']
967            print ' Component %d: Type: %s:'%(i,Type)
968            cid = str(i)+':'
969            controls = level['Controls']
970            Type = controls['DistType']
971            if Type in ['LogNormal','Gaussian','LSW','Schulz-Zimm','Monodisperse']:
972                for item in ['Aspect ratio','Length','Thickness','Diameter',]:
973                    if cid+item in varyList:
974                        controls['FFargs'][item][0] = parmDict[cid+item]
975                        print ' %15s: %15.4g esd: %15.4g'%(cid+item,parmDict[cid+item],sigDict[cid+item])
976            distDict = controls['DistType']
977            for item in level[distDict]:
978                if cid+item in varyList:
979                    level[distDict][item][0] = parmDict[cid+item]
980                    print ' %15s: %15.4g esd: %15.4g'%(cid+item,parmDict[cid+item],sigDict[cid+item])
981                   
982    def calcSASD(values,Q,Io,wt,levelTypes,parmDict,varyList):
983        parmDict.update(zip(varyList,values))
984        M = np.sqrt(wt)*(getSASD(Q,levelTypes,parmDict)-Io)
985        return M
986       
987    def getSASD(Q,levelTypes,parmDict):
988        Ic = np.ones_like(Q)
989        Ic *= parmDict['Back']
990        rhoMat = parmDict['Matrix density']
991        for i,Type in enumerate(levelTypes):
992            cid = str(i)+':'
993            if Type in ['LogNormal','Gaussian','LSW','Schulz-Zimm']:
994                FFfxn = parmDict[cid+'FormFact']
995                Volfxn = parmDict[cid+'FFVolume']
996                FFargs = []
997                for item in [cid+'Aspect ratio',cid+'Length',cid+'Thickness',cid+'Diameter',]:
998                    if item in parmDict: 
999                        FFargs.append(parmDict[item])
1000                distDict = {}
1001                for item in [cid+'Volume',cid+'Mean',cid+'StdDev',cid+'MinSize',]:
1002                    if item in parmDict:
1003                        distDict[item.split(':')[1]] = parmDict[item]
1004                rho = parmDict[cid+'XAnom density']
1005                contrast = rho**2-rhoMat**2
1006                rBins,dBins,dist = MakeDiamDist(Type,parmDict[cid+'NumPoints'],parmDict[cid+'Cutoff'],distDict)
1007                Gmat = G_matrix(Q,rBins,contrast,FFfxn,Volfxn,FFargs).T
1008                dist *= parmDict[cid+'Volume']
1009                Ic += np.dot(Gmat,dist)
1010            elif 'Unified' in Type:
1011                Rg,G,B,P = parmDict[cid+'Rg'],parmDict[cid+'G'],parmDict[cid+'B'],parmDict[cid+'P']
1012                Qstar = Q/(scsp.erf(Q*Rg/np.sqrt(6)))**3
1013                Guin = G*np.exp(-(Q*Rg)**2/3)
1014                Porod = (B/Qstar**P)
1015                Ic += Guin+Porod
1016            elif 'Porod' in Type:
1017                B,P,Rgco = parmDict[cid+'B'],parmDict[cid+'P'],parmDict[cid+'Cutoff']
1018                Porod = (B/Q**P)*np.exp(-(Q*Rgco)**2/3)
1019                Ic += Porod
1020            elif 'Mono' in Type:
1021                FFfxn = parmDict[cid+'FormFact']
1022                Volfxn = parmDict[cid+'FormFact']
1023                FFargs = []
1024                for item in [cid+'Aspect ratio',cid+'Length',cid+'Thickness',cid+'Diameter',]:
1025                    if item in parmDict: 
1026                        FFargs.append(parmDict[item])
1027                rho = parmDict[cid+'XAnom density']
1028                contrast = rho**2-rhoMat**2
1029                R = parmDict[cid+'Radius']
1030                Gmat = G_matrix(Q,R,contrast,FFfxn,Volfxn,FFargs)             
1031                Ic += Gmat[0]*parmDict[cid+'Volume']
1032            elif 'Bragg' in distFxn:
1033                Ic += parmDict[cid+'PkInt']*G2pwd.getPsVoigt(parmDict[cid+'PkPos'],
1034                    parmDict[cid+'PkSig'],parmDict[cid+'PkGam'],Q)
1035        return Ic
1036       
1037    Q,Io,wt,Ic,Ib = Profile[:5]
1038    Qmin = Limits[1][0]
1039    Qmax = Limits[1][1]
1040    wtFactor = ProfDict['wtFactor']
1041    Ibeg = np.searchsorted(Q,Qmin)
1042    Ifin = np.searchsorted(Q,Qmax)
1043    Ic[:] = 0
1044    levelTypes,parmDict,varyList,values = GetModelParms()
1045    result = so.leastsq(calcSASD,values,full_output=True,   #ftol=Ftol,
1046        args=(Q[Ibeg:Ifin],Io[Ibeg:Ifin],wtFactor*wt[Ibeg:Ifin],levelTypes,parmDict,varyList))
1047    ncyc = int(result[2]['nfev']/2)
1048    chisq = np.sum(result[2]['fvec']**2)
1049    Rwp = np.sqrt(chisq/np.sum(wt[Ibeg:Ifin]*Io[Ibeg:Ifin]**2))*100.      #to %
1050    GOF = chisq/(Ifin-Ibeg-len(varyList))       #reduced chi^2
1051    parmDict.update(zip(varyList,result[0]))
1052    Ic[Ibeg:Ifin] = getSASD(Q[Ibeg:Ifin],levelTypes,parmDict)
1053    sigDict = dict(zip(varyList,np.sqrt(np.diag(result[1])*GOF)))
1054    print ' Results of small angle data modelling fit:'
1055    print 'Number of function calls:',result[2]['nfev'],' Number of observations: ',Ifin-Ibeg,' Number of parameters: ',len(varyList)
1056    print 'Rwp = %7.2f%%, chi**2 = %12.6g, reduced chi**2 = %6.2f'%(Rwp,chisq,GOF)
1057    SetModelParms()
1058   
1059def ModelFxn(Profile,ProfDict,Limits,Substances,Sample,sasdData):
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#    pdb.set_trace()
1066    partData = sasdData['Particle']
1067    rhoMat = Substances['Substances'][partData['Matrix']['Name']].get('XAnom density',0.0)
1068    matFrac = partData['Matrix']['VolFrac'] #[value,flag]       
1069    Scale = Sample['Scale']     #[value,flag]
1070    Back = sasdData['Back']
1071    Q,Io,wt,Ic,Ib = Profile[:5]
1072    Qmin = Limits[1][0]
1073    Qmax = Limits[1][1]
1074    wtFactor = ProfDict['wtFactor']
1075    Ibeg = np.searchsorted(Q,Qmin)
1076    Ifin = np.searchsorted(Q,Qmax)
1077    Ib[:] = Back[0]
1078    Ic[:] = 0
1079    Ic[Ibeg:Ifin] += Back[0]
1080    Rbins = []
1081    Dist = []
1082    for level in partData['Levels']:
1083        controls = level['Controls']
1084        distFxn = controls['DistType']
1085        if distFxn in ['LogNormal','Gaussian','LSW','Schulz-Zimm']:
1086            FFfxn = shapes[controls['FormFact']][0]
1087            Volfxn = shapes[controls['FormFact']][1]
1088            FFargs = []
1089            for item in ['Aspect ratio','Length','Thickness','Diameter',]:
1090                if item in controls['FFargs']: 
1091                    FFargs.append(controls['FFargs'][item][0])
1092            rho = Substances['Substances'][level['Controls']['Material']].get('XAnom density',0.0)
1093            contrast = rho**2-rhoMat**2
1094            parmDict = level[controls['DistType']]
1095            distDict = {}
1096            for item in parmDict:
1097                distDict[item] = parmDict[item][0]
1098            rBins,dBins,dist = MakeDiamDist(controls['DistType'],controls['NumPoints'],controls['Cutoff'],distDict)
1099            Gmat = G_matrix(Q[Ibeg:Ifin],rBins,contrast,FFfxn,Volfxn,FFargs).T
1100            dist *= level[distFxn]['Volume'][0]
1101            Ic[Ibeg:Ifin] += np.dot(Gmat,dist)
1102            Rbins.append(rBins)
1103            Dist.append(dist/(4.*dBins))
1104        elif 'Unified' in distFxn:
1105            parmDict = level[controls['DistType']]
1106            Rg,G,B,P = parmDict['Rg'][0],parmDict['G'][0],parmDict['B'][0],parmDict['P'][0]
1107            Qstar = Q[Ibeg:Ifin]/(scsp.erf(Q[Ibeg:Ifin]*Rg/np.sqrt(6)))**3
1108            Guin = G*np.exp(-(Q[Ibeg:Ifin]*Rg)**2/3)
1109            Porod = (B/Qstar**P)
1110            Ic[Ibeg:Ifin] += Guin+Porod
1111            Rbins.append([])
1112            Dist.append([])
1113        elif 'Porod' in distFxn:
1114            parmDict = level[controls['DistType']]
1115            B,P,Rgco = parmDict['B'][0],parmDict['P'][0],parmDict['Cutoff'][0]
1116            Porod = (B/Q[Ibeg:Ifin]**P)*np.exp(-(Q[Ibeg:Ifin]*Rgco)**2/3)
1117            Ic[Ibeg:Ifin] += Porod
1118            Rbins.append([])
1119            Dist.append([])
1120        elif 'Mono' in distFxn:
1121            FFfxn = shapes[controls['FormFact']][0]
1122            Volfxn = shapes[controls['FormFact']][1]
1123            FFargs = []
1124            for item in ['Aspect ratio','Length','Thickness','Diameter',]:
1125                if item in controls['FFargs']: 
1126                    FFargs.append(controls['FFargs'][item][0])
1127            rho = Substances['Substances'][level['Controls']['Material']].get('XAnom density',0.0)
1128            contrast = rho**2-rhoMat**2
1129            R = level[controls['DistType']]['Radius'][0]
1130            Gmat = G_matrix(Q[Ibeg:Ifin],R,contrast,FFfxn,Volfxn,FFargs)             
1131            Ic[Ibeg:Ifin] += Gmat[0]*level[distFxn]['Volume'][0]
1132            Rbins.append([])
1133            Dist.append([])
1134        elif 'Bragg' in distFxn:
1135            parmDict = level[controls['DistType']]
1136            Ic[Ibeg:Ifin] += parmDict['PkInt'][0]*G2pwd.getPsVoigt(parmDict['PkPos'][0],
1137                parmDict['PkSig'][0],parmDict['PkGam'][0],Q[Ibeg:Ifin])
1138            Rbins.append([])
1139            Dist.append([])           
1140    sasdData['Size Calc'] = [Rbins,Dist]
1141   
1142def MakeDiamDist(DistName,nPoints,cutoff,distDict):
1143   
1144    if 'LogNormal' in DistName:
1145        distFxn = 'LogNormalDist'
1146        cumeFxn = 'LogNormalCume'
1147        pos = distDict['MinSize']
1148        args = [distDict['Mean'],distDict['StdDev']]
1149        step = 4.*np.sqrt(np.exp(distDict['StdDev']**2)*(np.exp(distDict['StdDev']**2)-1.))
1150        mode = distDict['MinSize']+distDict['Mean']/np.exp(distDict['StdDev']**2)
1151        minX = 1. #pos
1152        maxX = 1.e4 #np.min([mode+nPoints*step*40,1.e5])
1153    elif 'Gauss' in DistName:
1154        distFxn = 'GaussDist'
1155        cumeFxn = 'GaussCume'
1156        pos = distDict['Mean']
1157        args = [distDict['StdDev']]
1158        step = 0.02*distDict['StdDev']
1159        mode = distDict['Mean']
1160        minX = np.max([mode-4.*distDict['StdDev'],1.])
1161        maxX = np.min([mode+4.*distDict['StdDev'],1.e5])
1162    elif 'LSW' in DistName:
1163        distFxn = 'LSWDist'
1164        cumeFxn = 'LSWCume'
1165        pos = distDict['Mean']
1166        args = []
1167        minX,maxX = [0.,2.*pos]
1168    elif 'Schulz' in DistName:
1169        distFxn = 'SchulzZimmDist'
1170        cumeFxn = 'SchulzZimmCume'
1171        pos = distDict['Mean']
1172        args = [distDict['StdDev']]
1173        minX = np.max([1.,pos-4.*distDict['StdDev']])
1174        maxX = np.min([pos+4.*distDict['StdDev'],1.e5])
1175    nP = 500
1176    Diam = np.logspace(0.,5.,nP,True)
1177    TCW = eval(cumeFxn+'(Diam,pos,args)')
1178    CumeTgts = np.linspace(cutoff,(1.-cutoff),nPoints+1,True)
1179    rBins = np.interp(CumeTgts,TCW,Diam,0,0)
1180    dBins = np.diff(rBins)
1181    rBins = rBins[:-1]+dBins/2.
1182    return rBins,dBins,eval(distFxn+'(rBins,pos,args)')
1183
1184################################################################################
1185#### MaxEnt testing stuff
1186################################################################################
1187
1188def print_vec(text, a):
1189    '''print the contents of a vector to the console'''
1190    n = a.shape[0]
1191    print "%s[ = (" % text,
1192    for i in range(n):
1193        s = " %g, " % a[i]
1194        print s,
1195    print ")"
1196
1197def print_arr(text, a):
1198    '''print the contents of an array to the console'''
1199    n, m = a.shape
1200    print "%s[][] = (" % text
1201    for i in range(n):
1202        print " (",
1203        for j in range(m):
1204            print " %g, " % a[i][j],
1205        print "),"
1206    print ")"
1207
1208def test_MaxEnt_SB(report=True):
1209    def readTextData(filename):
1210        '''return q, I, dI from a 3-column text file'''
1211        if not os.path.exists(filename):
1212            raise Exception("file not found: " + filename)
1213        buf = [line.split() for line in open(filename, 'r').readlines()]
1214        M = len(buf)
1215        buf = zip(*buf)         # transpose rows and columns
1216        q  = np.array(buf[0], dtype=np.float64)
1217        I  = np.array(buf[1], dtype=np.float64)
1218        dI = np.array(buf[2], dtype=np.float64)
1219        return q, I, dI
1220    print "MaxEnt_SB: "
1221    test_data_file = os.path.join( 'testinp', 'test.sas')
1222    rhosq = 100     # scattering contrast, 10^20 1/cm^-4
1223    bkg   = 0.1     #   I = I - bkg
1224    dMin, dMax, nRadii = 25, 9000, 40
1225    defaultDistLevel = 1.0e-6
1226    IterMax = 40
1227    errFac = 1.05
1228   
1229    r    = np.logspace(math.log10(dMin), math.log10(dMax), nRadii)/2
1230    dr   = r * (r[1]/r[0] - 1)          # step size
1231    f_dr = np.ndarray((nRadii)) * 0  # volume fraction histogram
1232    b    = np.ndarray((nRadii)) * 0 + defaultDistLevel  # MaxEnt "sky background"
1233   
1234    qVec, I, dI = readTextData(test_data_file)
1235    G = G_matrix(qVec,r,rhosq,SphereFF,SphereVol,args=())
1236   
1237    chisq,f_dr,Ic = MaxEnt_SB(I - bkg, dI*errFac, b, IterMax, G, report=report)
1238    if f_dr is None:
1239        print "no solution"
1240        return
1241   
1242    print "solution reached"
1243    for a,b,c in zip(r.tolist(), dr.tolist(), f_dr.tolist()):
1244        print '%10.4f %10.4f %12.4g'%(a,b,c)
1245
1246def tests():
1247    test_MaxEnt_SB(report=True)
1248
1249if __name__ == '__main__':
1250    tests()
1251
Note: See TracBrowser for help on using the repository browser.