voigt_profile.py

Go to the documentation of this file.
00001 """generate voigt functions and their derivatives with respect to parameters"""
00002 _rcsid="$Id: voigt_profile.py,v 1.7 2007/01/25 12:44:05 mendenhall Exp $"
00003 
00004 ##\file
00005 ##Provides the analysis.voigt_profile package.
00006 ##\package analysis.voigt_profile
00007 #This is a function which efficiently computes Voigt profiles (convolutions of Lorentzian and Gaussian functions) which are useful for many types of spectroscopy.
00008 #\verbatim version $Id: voigt_profile.py,v 1.7 2007/01/25 12:44:05 mendenhall Exp $ \endverbatim
00009 #
00010 #Developed by Marcus H. Mendenhall, Vanderbilt University Keck Free Electron Laser Center, Nashville, TN USA
00011 #
00012 #email: mendenhall@users.sourceforge.net
00013 #
00014 #Work supported by the US DoD  MFEL program under grant FA9550-04-1-0045
00015 #
00016 
00017 try:
00018     import numpy as Numeric
00019     from numpy import fft
00020     from numpy.fft import irfft as inverse_real_fft
00021     _numeric_float=Numeric.float64
00022     
00023 except ImportError: 
00024     import Numeric
00025     from FFT import inverse_real_fft
00026     _numeric_float=Numeric.Float64
00027 
00028 import math
00029 
00030 ## 
00031 ## the class allows one to set up multiple instances of the Voigt function calculator, each with its own private cache
00032 ## so that cache thrashing is avoided if one is computing for many peaks with widely varying parameters
00033 
00034 class Voigt_calculator:
00035     
00036     ## 
00037     ## Construct the function instance, and create an empty cache
00038     def __init__(self):
00039         ##
00040         ## This is a cache which is used to store the most recently computed data grids, so similar computations can be done very quickly.
00041         self.saved_params=(0,0, None, None, None, None) #k_points, xfullwidth, kvals, xvals, k2, cosarray, x2
00042 
00043     ##
00044     ## Compute a block of Voigt function values
00045     ## \param sigma The Gaussian width for the convolution, such that the Gaussian is y=exp[-x^2 / (2 sigma^2)]
00046     ## \param alpha The half-width of the Lorentzian, i.e. y=1/(alpha^2 + x^2), suitably normalized
00047     ## \param k_points The number of points in k-space to compute.  If it is None, autorange based on kexptail (see below)
00048     ## \param xfullwidth The maximum value of X for which the function is calculated.  The domain is (-xfullwidth, xfullwidth)
00049     ## \param kexptail The number of exponential decrements to compute in k-space.  kexptail=25 gives about 10^-11 amplitude at the Nyquist frequency.  
00050     ##  Larger values give finer sampling of the peak in x-space.
00051     ##
00052     ## \retval xvals The grid of x values on which the function is computed.
00053     ## \retval yft The array of function values corresponding to the xvals given.  The function is normalized so that sum(yft*dx)=1
00054     ## \retval dsig The derivative of the Voigt function with respect to sigma
00055     ## \retval dalpha The derivative of the Voigt function with respect to alpha.
00056     ##
00057     def voigt_with_derivs(self, sigma, alpha, k_points=None, xfullwidth=10.0, kexptail=25):
00058         """return an x grid, voigt function with specified params, and d/dsigma and d/dalpha of this.  
00059             Function is normalized to unit integral of points"""
00060         if k_points is None:
00061             #autorange k by solving for k such that 0.5*sigma**2*k**2+alpha*k==kexptail 
00062             # so exp(-kexptail) is smallest point in spectrum
00063             q=-0.5*(alpha+math.sqrt(alpha*alpha+4.0*kexptail*(0.5*sigma**2))) #the Numerical Recipes quadratic trick
00064             k1=-kexptail/q
00065             kp=k1*xfullwidth/math.pi
00066             k_points=2**int(1+math.floor(math.log(kp)/math.log(2.0))) #next power of two up
00067             
00068         if self.saved_params[:2] != (k_points, xfullwidth):
00069             kmax=math.pi*k_points/(xfullwidth)
00070             kvals=Numeric.array(range(k_points+1), _numeric_float)*(kmax/k_points)
00071             dx=xfullwidth/(2.0*k_points)
00072             xvals=Numeric.array(range(2*k_points), _numeric_float)*(xfullwidth/float(k_points))-xfullwidth
00073             k2=kvals*kvals
00074             x2=xvals*xvals
00075             cosarray=Numeric.cos((math.pi/xfullwidth)*xvals)
00076             self.saved_params=(k_points, xfullwidth, kvals, xvals, k2, cosarray, x2)
00077         else:
00078             k_points, xfullwidth, kvals, xvals, k2, cosarray, x2=self.saved_params
00079             kmax=math.pi*k_points/(xfullwidth)
00080             dx=xfullwidth/(2.0*k_points)
00081             
00082         yvals=Numeric.exp(Numeric.clip((-0.5*sigma*sigma)*k2 - alpha*kvals, -100, 0))
00083         yvals[1::2]*=-1.0 #modulate phase to put peak in center for convenience
00084         yft=inverse_real_fft(yvals)
00085         yft *= 0.5/dx #scale to unit area
00086     
00087         #apply periodic boundary correction to function
00088         delta=2*xfullwidth
00089         k1=2*math.pi*alpha/delta
00090         shk1=math.sinh(k1)/delta
00091         chk1=math.cosh(k1)
00092         uu1=1.0/(chk1-cosarray ) 
00093         uu2=Numeric.array(1.0/(x2+alpha**2))
00094         deltav=shk1*uu1-(alpha/math.pi)*uu2
00095     
00096         #this is an empirical correction on top of the analytical one, to get the next order correct
00097         #it is derived from a scaling argument (much hand waving) about 
00098         #the effect of a gaussian convolution on a second derivative
00099         yft -= deltav*(1+(2*sigma**2/xfullwidth**4)*x2)
00100         
00101         #transform of df/d(alpha) = d(transform of f)/d(alpha) = -k*transform   
00102         yvals*=kvals
00103         dalpha=inverse_real_fft(yvals)
00104         dalpha*= -0.5/dx
00105         
00106         #apply periodicity correction to d/dalpha
00107         deltad=(-2.0*math.pi*shk1*shk1)*(uu1*uu1) 
00108         deltad +=  ( 2.0*math.pi*chk1/(delta*delta))*uu1
00109         deltad += (alpha*alpha - x2)*(uu2*uu2)/math.pi
00110         dalpha -= deltad
00111         
00112         #transform of df/d(sigma) = d(transform of f)/d(sigma) = -k*k*sigma*transform   
00113         yvals*=kvals
00114         dsig=inverse_real_fft(yvals)
00115         dsig*= -0.5*sigma/dx
00116         
00117         #d/dsigma is very localized... no correction needed
00118         
00119         return (xvals,  yft, dsig, dalpha)
00120 
00121 
00122 ##
00123 ## A singleton of the class for convenience
00124 _voigt_instance=Voigt_calculator()
00125 ## create one global function so the module can be used directly, without instantiating a class, in the style of the random module
00126 ## See documentation for Voigt_calculator.voigt_with_derivs for usage.
00127 def voigt_with_derivs(*args, **kwargs):
00128     return _voigt_instance.voigt_with_derivs(*args, **kwargs)
00129 ##
00130 
00131 ##
00132 ##\cond NEVER
00133 ##
00134 if __name__=='__main__':
00135     from graceplot import GracePlot
00136     
00137     datasets=[]
00138     
00139     stylecolors=[GracePlot.green,  GracePlot.blue, GracePlot.red, GracePlot.orange, GracePlot.magenta, GracePlot.black]
00140     s1, s2, s3, s4, s5, s6 =[GracePlot.Symbol(symbol=GracePlot.circle, fillcolor=sc, size=0.5, linestyle=GracePlot.none) for sc in stylecolors]
00141     l1, l2, l3, l4, l5, l6=linestyles=[GracePlot.Line(type=GracePlot.solid, color=sc, linewidth=2.0) for sc in stylecolors]     
00142     noline=GracePlot.Line(type=GracePlot.none)              
00143     
00144     
00145     if 0:
00146         alpha0=.005
00147         sigma0=0.5
00148         xvals, vgta1, dsig, dalpha = voigt_with_derivs(sigma0, alpha0+0.0001)
00149         xvals, vgta2, dsig, dalpha = voigt_with_derivs(sigma0, alpha0-0.0001)
00150         
00151         xvals, vgts1, dsig, dalpha = voigt_with_derivs(sigma0+0.0001, alpha0)
00152         xvals, vgts2, dsig, dalpha = voigt_with_derivs(sigma0-0.0001, alpha0)
00153         
00154         xvals, vgt, dsig, dalpha = voigt_with_derivs(sigma0, alpha0)
00155         
00156         scale=1.0
00157         
00158         datasets.append(GracePlot.Data(x=xvals, y=vgt*scale,  type='xy', line=l1))
00159         datasets.append(GracePlot.Data(x=xvals, y=dsig*sigma0,  type='xy', line=l2))
00160         datasets.append(GracePlot.Data(x=xvals, y=dalpha*alpha0,  type='xy', line=l3))
00161         datasets.append(GracePlot.Data(x=xvals, y=5000.0*alpha0*(vgta1-vgta2),  type='xy', symbol=s3, line=noline))
00162         datasets.append(GracePlot.Data(x=xvals, y=5000.0*sigma0*(vgts1-vgts2),  type='xy', symbol=s2, line=noline))
00163     else:
00164         from analysis import C2Functions
00165         class gauss(C2Functions.C2Function):
00166             scale=1.0/math.sqrt(2.0*math.pi)
00167             sigma=1.0
00168             
00169             def set_width(self, wid):
00170                 self.sigma=wid
00171 
00172             def value_with_derivatives(self, x):
00173                 x=x/self.sigma
00174                 a=math.exp(-x*x/2)*self.scale/self.sigma
00175                 return a, -x*a/self.sigma, (x*x-1)*a/self.sigma**2
00176         
00177         class lorentz(C2Functions.C2Function):
00178             def set_center(self, center):
00179                 self.center=center
00180             def set_width(self, wid):
00181                 self.width=wid
00182                         
00183             def value_with_derivatives(self, x):
00184                 x=(x-self.center)/self.width
00185                 scale=1/(self.width*math.pi)
00186                 a=1.0/(1.0+x*x)
00187                 return a*scale, -2*x*a*a*scale/self.width, (6*x*x-2)*a*a*a*scale/self.width**2
00188         
00189         ll=lorentz()
00190         gg=gauss()
00191         prod=ll*gg
00192         
00193         alpha0=1.0
00194         import time
00195         
00196         for pidx, (xfw, sigma0) in enumerate(((500., 1.), (500., 50.), (2000., 50.))):
00197             t0=time.time()
00198             for i in range(100):
00199                 xvals1, vgta1, dsig, dalpha = voigt_with_derivs(sigma0, alpha0, kexptail=25, xfullwidth=xfw)
00200             t1=time.time()
00201             
00202             convlist=[]
00203             ll.set_width(alpha0)
00204             gg.set_width(sigma0)
00205             for x in xvals1:
00206                 ll.set_center(float(x)) #need to cast to float if numpy is used to avod horrible numpy-objects as the float
00207                 convlist.append(prod.integral(-10.0*sigma0, 10.0*sigma0, relative_error_tolerance=1e-13, absolute_error_tolerance=1e-16))
00208             
00209             t2=time.time()
00210             
00211             diff=vgta1-convlist
00212             print len(xvals1), (t1-t0)/100.0, t2-t1, math.sqrt(sum(diff*diff)/len(diff))
00213             
00214             datasets.append(GracePlot.Data(x=xvals1, y=vgta1,  type='xy', line=linestyles[2*pidx], legend=r"Function: \xs\f{} = %.0f, \xD\f{} = %.0f"%(sigma0, 2*xfw) ))    
00215             datasets.append(GracePlot.Data(x=xvals1, y=diff/vgta1*1e3,  type='xy', line=linestyles[2*pidx+1], legend=r"10\S3\N \x\#{b4}\f{} Relative Error: \xs\f{} = %.0f, \xD\f{} = %.0f"%(sigma0, 2*xfw)))
00216 
00217     try:
00218         graceSession.clear()
00219     except:
00220         class myGrace(GracePlot.GracePlot):
00221             
00222             def write(self, command):
00223                 "make a graceSession look like a file"
00224                 self._send(command)
00225                 self._flush()
00226         
00227             def send_commands(self, *commands):
00228                 "send a list of commands, and then flush"
00229                 for c in commands:
00230                     self._send(c)
00231                 self._flush()
00232 
00233         graceSession=myGrace(width=11, height=7)
00234     
00235     g=graceSession[0]
00236     
00237     g.plot(datasets[::2]+datasets[1::2])    
00238     g.legend()
00239     g.xaxis(label=GracePlot.Label('offset'), xmin=0, xmax=500)
00240     g.yaxis(label=GracePlot.Label('Amplitude'), scale='logarithmic', ymin=1e-6, ymax=1,
00241             tick=GracePlot.Tick(major=10, minorticks=9))
00242 
00243     graceSession.send_commands('hardcopy device "PDF"', 'print to "voigt_errors.pdf"')
00244 
00245 ##
00246 ##\endcond
00247 ##
00248         

Generated on Wed Nov 21 10:18:31 2007 for analysis by  doxygen 1.5.4