next up previous contents
Next: 6 XAFS Analysis Up: IFEFFIT Tutorial Previous: 4 Plotting Data

Subsections



5 XAFS Data Processing

IFEFFIT's main job is to help you analyze XAFS data, and IFEFFIT tries to make it easy to do simple XAFS analysis tasks. These simple tasks include such things as converting beamline data in $ \mu$(E), doing pre-edge subtraction, determining E0, fitting a post-edge (spline) subtraction to determine $ \mu_{{0}}^{}$(E) and $ \chi$(k), and doing XAFS Fourier transforms to look at data in R-space. These standard tasks of XAFS data manipulation are described in this section.


5.1 Data Reduction

The general algebraic manipulation of array data within IFEFFIT makes the conversion of raw beamline data to XAFS $ \mu$(E) very easy, and also allows the averaging and re-scaling (if necessary) of data. For example, reading raw beamline transmission and fluorescence data and converting this to $ \mu$(E) might look like this:

  Ifeffit> read_data(file = cu_expt.dat, group= 'cu',
                    label = 'energy i0 i1 if')
  Ifeffit> set cu.xmu_t = log(cu.i1 / cu.i0)
  Ifeffit> set cu.xmu_f = (cu.if / cu.i0)
If you've collected data in fluorescence using a multi-element-detector, you may need to sum several arrays before dividing by I0, something like this will do the trick:
  Ifeffit> read_data(file= med_fluor.dat, group='med', type='raw')
  Ifeffit> set med.if= (med.4  + med.5 + med.6 + med.7 + med.8 +
                        med.9  +med.10 +med.11 +med.12 +med.13 )
  Ifeffit> set med.xmu= (med.if / med.2)


5.2 Pre-Edge Subtraction, E0 determination, and Normalization

Pre-edge subtraction removes the baseline from the EXAFS $ \mu$(E), determines the edge energy (used as the origin of k), and normalizes the above-edge $ \mu$(E) to 1. All of this is done by the pre_edge() command which takes arrays of energy and absorption $ \mu$(E):

  Ifeffit> pre_edge(cu.energy, cu.xmu)
Like most IFEFFIT command, this simple-looking command actually causes a fair amount of data processing behind the scenes. It also creates several program variables, especially arrays and scalars detailing the pre-edge subtraction. Here's a list of the most important program variables that pre_edge() sets:

Variable Description
e0 Energy Origin (found near maximum derivative of $ \mu$(E)
edge_step Edge Step / normalization constant
pre_slope slope of pre-edge line
pre_offset offset of pre-edge line
$group.pre array of pre-edge subtracted $ \mu$(E)
$group.norm array of pre-edge subtracted and normalized $ \mu$(E)

where $group is the 'group name' taken from the input $ \mu$(E) array - 'cu' in this case. Using pre_edge() on another array would generate new arrays of pre-edge subtracted and normalized $ \mu$(E) for the corresponding group. Note, however, that it will overwrite the scalar values from the earlier invocation of pre_edge().

There are several optional arguments to pre_edge() not mentioned here. These arguments can be use to set the ranges over which to fit the pre-edge line and post-edge curve, whether or not to perform every part of the calculation, and so forth. These optional arguments would normally be used like this:

  Ifeffit> pre_edge(cu.energy, cu.xmu, e0=8980.5)
which would force the value of E0 to be 8980.5 and prevent pre_edge() from trying to determine E0 by itself. As for all commands, the complete set of the optional parameters for pre_edge() are given in the Reference Guide.


5.3 Post-Edge Background Subtraction

Post-edge background subtraction involves drawing a ``smooth background'' ( $ \mu_{{0}}^{}$(E) through the oscillatory part of the XAFS and extracting $ \chi$(k) using this and the formula

$\displaystyle \chi$(E) = $\displaystyle {{\mu(E) - \mu_0(E)}\over{\Delta \mu(E_0)}}$

where $ \mu_{0}^{}$(E) is ``the smooth background'' of $ \mu$(E) and $ \Delta$$ \mu$(E0) is the edge-jump. Determining the XAFS background function $ \mu_{{0}}^{}$(E) generally receives quite a bit of attention in the XAFS community. IFEFFIT uses the AUTOBK algorithm, which simply asserts and then implements a rather common-sense approach to background subtraction: only the low-frequency components of $ \mu$(E) should make up $ \mu_{{0}}^{}$(E).

The implementation of AUTOBK in IFEFFIT is encompassed in the spline() command, which takes arrays of energy and $ \mu$, and several optional parameters, and writes out $ \chi$(k), $ \mu_{{0}}^{}$(E), and several scalars. A basic use of spline() would look like this

  Ifeffit> spline(cu.energy, cu.xmu, rbkg=1.0)
Like pre_edge(), this simple-looking command does quite a bit of data processing behind the scenes, and creates or writes several variables with IFEFFIT. For one thing, spline() will execute pre_edge() unless it's obvious that it shouldn't3. That means that all the output parameters of pre_edge() will be created (or overwritten) by spline(), and E0 and the edge jump $ \Delta$$ \mu$(E0) will be determined if needed.

To say much more about the spline() command, I'd have to discuss the details of the AUTOBK algorithm. I'll try to be brief: spline() chooses a smooth background spline such that the low-R portion of the resulting EXAFS $ \chi$ are minimized. That means that spline() needs to do a Fourier transform of the ${\ensuremath{\chi(k)}}$ it generates. Because of this, the spline() function takes many command arguments that resemble Fourier transform parameters, so that in addition to the arguments of pre_edge() the important arguments that spline() takes are:

Variable Description
rbkg Rbkg, the highest R value to consider background
kmin kmin, the starting k for the Fourier transform
kweight w, the k-weight factor for the Fourier transform.

Please note that the Fourier transforms appropriate for spline() are not the same as those appropriate for structural analysis. Usually, values of kmin $ \approx$ 0 and w = 1 are appropriate for spline().

Like pre_edge(), the spline() command also creates new arrays, most importantly:

Variable Description
$group.bkg $ \mu_{{0}}^{}$(E), the background function itself
$group.k k, the array of wavenumbers
$group.chi $ \chi$(k), the (unweighted) EXAFS


5.4 Fourier Transforms

Fourier transforms are an integral part of XAFS analysis, and the ability to perform them quickly and easily is very important. IFEFFIT has different commands for forward ( k $ \rightarrow$ R) and reverse forward ( R $ \rightarrow$ q: I'll use q to refer to back-transformed k-space data) Fourier transforms. They're very similar to one another, so I'll first discuss forward Fourier transforms with fftf() in detail, then reverse Fourier transforms with fftr() more quickly.

IFEFFIT use a simple Fast Fourier transform (FFT), which places some demands on the data transformed by these commands. In addition, XAFS analysis usually imposes some conventions on Fourier transforms, so that the commands discussed here are not general purpose Fourier transform functions, but are really XAFS Fourier transforms. The most obvious difference between 'normal' and 'XAFS' Fourier transforms is that the latter transforms k to R, while a 'normal' FT would transform k to 2R. Aside from a scale factor, this changes the normalization constants used. In addition, the XAFS Fourier transform (at least as IFEFFIT implements it) allows a variety of smoothing window functions, and a weighting factor. More details can be found in XAFS Analysis with IFEFFIT .


5.4.1 Forward Fourier Transforms

The Forward XAFS Fourier transform command is fftf(). It's primary input is an array of $ \chi$(k) data. The requirements of the FFT mean that the input $ \chi$(k) data must be an array that is on an even k-grid with grid spacing of k = 0.05 Å-1, and starting at k = 0.

These are stringent requirements. Fortunately, the spline() command of section 5.3 writes its output $ \chi$(k) array according to these rules. If you're importing $ \chi$(k) data written from another program, you'll have to make sure the data is moved to this k-grid. There are two ways to this: either interpolate the data yourself (see the interpolation functions in the Reference Guide) or specify the k-array corresponding to your $ \chi$ data in the fftf() command and let it do the interpolation for you.

Properly aligned $ \chi$(k) data can be transformed to R-space using a command like this:

  Ifeffit> fftf(cu.chi, kmin=2.0, kmax=17.0, dk=1.0, kweight=2)
while data not on the expected grid would be Fourier transformed like this:
  Ifeffit> fftf(cu.chi, k=cu.k,
                kmin=2.0, kmax=17.0, dk=1.0, kweight=2)
The keywords kmin, kmax, and dk help define the window function, and kweight sets the k-weighting factor. You can also specify the form of the window function. The full list of window types and their functional form is given in the Reference Guide, but the most useful ones are the Hanning window ( kwindow=hanning - the default window function) which ramps up to one on either end of the k-range as cos2, and the Kaiser-Bessel window (kwindow=kaiser), which is often thought to give superior peak resolution.

Because it is often necessary to do many Fourier transforms with exactly the same parameters, the fftf() command, can also read Fourier transform parameters from appropriately named program variables. This is actually a feature of many commands, but it seems most useful for the Fourier transform commands. It works like this: setting scalars kmin, kmax, and so forth will have the same effect as setting those arguments to the fftf() command.

  Ifeffit> kmin=2.0, kmax=17.0, dk=1.0, kweight=2
  Ifeffit> fftf(cu.chi)
would have the result as
  Ifeffit> fftf(cu.chi, kmin=2.0, kmax=17.0, dk=1.0, kweight=2)
In fact, the fftf() command will first read the value for the parameter kmin from the program variable kmin (and so on for the other Fourier transform parameters), and then from the command argument kmin, so that the command argument will always override the program variable value. Furthermore, fftf() will itself set the value of the program variable kmin, possibly overwriting any value you had previously set. Thus
  Ifeffit> kmin=2.0, kmax=17.0, dk=1.0, kweight=2
  Ifeffit> fftf(cu.chi, kmin=3.)
  Ifeffit> fftf(other.chi)
will use the same Fourier transform parameters (that is kmin = 3Å-1) for both transforms.

A Fourier transform inherently deals with complex data, and a minor complication arises from the inconvenience that the measured XAFS $ \chi$(k) is strictly a real function. In fftf() then, there is an ambiguity of whether to use the measured XAFS as the real or imaginary part of the complex XAFS function. Since the measured XAFS is typically described as the imaginary part of a complex fine-structure function $ \tilde{\chi}$, one might be tempted to say that the data $ \chi$(k) ought to be set to the imaginary part of $ \tilde{\chi}$. IFEFFIT usually assumes that the data $ \chi$(k) is the real part of the complex $ \tilde{\chi}$ and sets the imaginary part to zero - this is in keeping with the convention of older programs from the University of Washington, but it is purely a matter of convention. You can explicitly specify the behavior by saying fftf(imag = data.chi,...) or fftf(real = data.chi,...). If you don't specify, the data will be taken as the real part. This is almost never important - at least not until you want to compare unfiltered $ \chi$(k) with filtered $ \chi$(k). The output arrays generated by the fftf() command are

Variable Description
$group.win The k-space window function used
$group.r R, the array of distances
$group.chir_mag |$ \chi$(R)|, the magnitude of $ \chi$(R)
$group.chir_pha the phase of $ \chi$(R)
$group.chir_re Re[$ \chi$(R)], the real part of $ \chi$(R)
$group.chir_im Im[$ \chi$(R)], the imaginary part of $ \chi$(R)

It is possible to do ``phase-corrected'' Fourier transforms with IFEFFIT using the theoretical phases from FEFF calculations, but that is beyond the scope of this tutorial.


5.4.2 Reverse Fourier Transforms

fftr() is the Reverse XAFS Fourier transform command, mainly used to filter $ \chi$(R) data to backtransformed $ \chi$(k). Following the convention of FEFFIT, backtransformed k-space is called q to avoid confusion with the original k-space data. The fftr() command then transforms $ \chi$(R) to $ \chi$(q). As with the forward Fourier transform, the data is requirements of the FFT mean that the input $ \chi$(k) data must be given as an array that is evenly spaced in R with a fixed grid spacing of R = $ \pi$/1024 $ \approx$ 0.03068 Å, and starting at R = 0. To further complicate matters, there is ambiguity as to whether to transform just the real part, just the imaginary part of $ \chi$(R), or both. In general, both parts are transformed to ensure that the overall amplitude scale is preserved.

fftr() has an almost identical command set to fftf(), with k replaced by r in the parameter names. That is, the FT window parameters are defined with rmin, rmax, dr, and so on. The window functional form is set by rwindow. A typical use might look like this

  Ifeffit> fftr(real=cu.chir_re, imag=cu.chir_im,
                rmin=1.70, rmax=3.0, dk=0.1)

The output arrays generated by the fftf() command are

Variable Description
$group.rwin The R-space window function used
$group.q q, the array of wavenumbers
$group.chiq_mag |$ \chi$(q)|, the magnitude of $ \chi$(q)
$group.chiq_pha the phase of $ \chi$(q)
$group.chiq_re Re[$ \chi$(q)], the real part of $ \chi$(q)
$group.chiq_im Im[$ \chi$(q)], the imaginary part of $ \chi$(q)

Before ending this section, let me say a few words about ``Fourier filtering''. IFEFFIT's approach to Fourier transforms is fairly general, and the use of two Fourier transforms to ``isolate a shell'' is not a trivial process with IFEFFIT. This partly reflects my experience and belief that ``Fourier filtering'' can not be made a trivial process.

Comparing filtered with unfiltered data is a common desire. Given the ambiguities in how to handle the real and imaginary parts of the data, getting the details right for this are not trivial. Though macros won't be discussed until section 9, using a macro to get the details right is a good idea. Such a macro for Fourier filtering might look like this:

  macro filter group  "kweight=2,kmin=3"   "dr=0"
    fftf(real=$1.chi, $2)
    fftr(real=$1.chir_re, imag=$1.chir_im,  $3)
    set $1.chik   = $1.chi  * $1.k^kweight
    set $1.chik_w = $1.chik * $1.win
    set $1.chiq   = $1.chiq_re / ($1.k^kweight)
  end macro
With this macro definition (which takes arguments as group name, k parameters, and R parameters), you could perform a filter, and overplot filtered and unfiltered data with
  filter data "kweight=2,kmin=3,kmax=15,dk=1" "rmin=1.6,rmax=3."
  newplot(data.q, data.chiq_re )
  plot(   data.k, data.chik_w )
Note that the convention used here is that the data $ \chi$(k) is the real part for the Forward transform, so that the real part of $ \chi$(q) is the appropriate choice for comparison. Of course, that should be compared to the k-weighted, windowed $ \chi$(k).


next up previous contents
Next: 6 XAFS Analysis Up: IFEFFIT Tutorial Previous: 4 Plotting Data
Matt Newville
2001-10-05