LCFit module

Linear combination fitting (LCF) refers to the interpretation of an unknown XAFS signal as a summation of known XAFS reference signals.

For the case of the XANES region of an unknown spectrum \(\mu_s(E)\), LCF translates into obtaining the amplitude coefficients \(\alpha_i\) that minimize the residuals of the following equation:

\[\mu_s(E) = \sum_{i=1}^n \alpha_i \mu_i(E) + \epsilon(E), \quad i \in \{1,\dots,n \}\]

Considering the following set of constraints:

\[ \begin{align}\begin{aligned}0 \leq &\alpha_i \leq 1, \quad i \in \{1,\dots,n \}\\\sum_{i=1}^n &\alpha_i = 1 \quad\textrm{(optional)}\end{aligned}\end{align} \]

Where

  • \(E\) : photoelectron energy.

  • \(\mu_s(E)\) : normalized absorption of the fitted spectrum.

  • \(\mu_i(E)\) : normalized absorption of reference spectrum “i”.

  • \(\alpha_i\) : amplitude coefficient for reference spectrum “i”.

  • \(\epsilon(E)\): residuals.

  • \(n\) : number of reference spectra.

Analogously, for the case of the EXAFS region of an unknown spectrum \(\chi(k)\), LCF translates into minimizing the residuals of the following equation:

\[k^{kw}\chi_s(k) = \sum_{i=1}^n \alpha_i k^{kw}\chi_i(k) + \epsilon(k), \quad i \in \{1,\dots,n \}\]

Considering the following set of constraints:

\[ \begin{align}\begin{aligned}0 \leq &\alpha_i \leq 1, \quad i \in \{1,\dots,n \}\\\sum_{i=1}^n &\alpha_i = 1 \quad\textrm{(optional)}\end{aligned}\end{align} \]

Where

  • \(k\) : photoelectron wavenumber.

  • \(\chi_s(k)\) : EXAFS modulation of the fitted spectrum.

  • \(\chi_i(E)\) : EXAFS modulation of the reference spectrum “i”.

  • \(\epsilon(k)\): residuals.

  • \(kw\) : weighting coefficient for the photoelectron wavenumber.

The lcf module offers the following functions to perform linear combintation fitting (LCF):

Function

Description

lcf()

Performs LCF on a XAFS spectrum.

lcf_report()

Returns a formatted LCF report.

sum_references()

Sum of weighted references.

residuals()

Calculates residuals for LCF.

araucaria.fit.lcfit.lcf(collection, fit_region='xanes', fit_range=[- inf, inf], scantag='scan', reftag='ref', kweight=2, sum_one=True, method='leastsq')[source]

Performs linear combination fitting on a XAFS spectrum.

Parameters
  • collection (Collection) – Collection containing the group for LCF analysis and the groups with the reference scans.

  • fit_region (str) – XAFS region to perform the LCF. Accepted values are ‘dxanes’, ‘xanes’, or ‘exafs’. The default is ‘xanes’.

  • fit_range (list) – Domain range in absolute values. Energy units are expected for ‘dxanes’ or ‘xanes’, while wavenumber (k) units are expected for ‘exafs’. The default is [-inf, inf].

  • scantag (str) – Key to filter the scan group in the collection based on the tags attribute. The default is ‘scan’.

  • reftag (str) – Key to filter the reference groups in the collection based on the tags attribute. The default is ‘scan’.

  • kweight (int) – Exponent for weighting chi(k) by k^kweight. Only valid for fit_region='exafs'. The default is 2.

  • sum_one (bool) – Conditional to force sum of fractions to be one. The default is True.

  • method (str) – Fitting method. Currently only local optimization methods are supported. See the minimize() function of lmfit for a list of valid methods. The default is leastsq.

Return type

Dataset

Returns

Fit group with the following arguments:

  • energy : array with energy values. Returned only if fit_region='xanes' or fit_region='dxanes'.

  • k : array with wavenumber values. Returned only if fit_region='exafs'.

  • scangroup: name of the group containing the fitted spectrum.

  • refgroups: list with names of groups containing reference spectra.

  • scan : array with values of the fitted spectrum.

  • ref : array with interpolated values for each reference spectrum.

  • fit : array with fit result.

  • min_pars : object with the optimized parameters and goodness-of-fit statistics.

  • lcf_pars : dictionary with lcf parameters.

Raises
  • TypeError – If collection is not a valid Collection instance.

  • AttributeError – If collection has no tags attribute.

  • AttributeError – If groups have no energy or norm attribute. Only verified if fit_region='dxanes' or fit_region='xanes'.

  • AttributeError – If groups have no k or chi attribute. Only verified if and fit_region='exafs'.

  • KeyError – If scantag or refttag are not keys of the tags attribute.

  • ValueError – If fit_region is not recognized.

  • ValueError – If fit_range is outside the doamin of a reference group.

Important

If more than one group in collection is tagged with scantag, a warning will be raised and only the first group will be fitted.

Notes

The min_pars object is returned by the minimize() function of lmfit, and contains the following attributes (non-exhaustive list):

  • params : dictionary with the optimized parameters.

  • var_names : ordered list of parameter names used in optimization.

  • covar : covariance matrix from minimization.

  • init_vals : list of initial values for variable parameters using var_names.

  • success : True if the fit succeeded, otherwise False.

  • nvarys : number of variables.

  • ndata : number of data points.

  • chisqr : chi-square.

  • redchi : reduced chi-square.

  • residual : array with fit residuals.

Example

>>> from numpy.random import seed, normal
>>> from numpy import arange, sin, pi
>>> from araucaria import Group, Dataset, Collection
>>> from araucaria.fit import lcf
>>> from araucaria.utils import check_objattrs
>>> seed(1234)  # seed of random values
>>> k    = arange(0,  12,   0.05)
>>> eps  = normal(0, 0.1, len(k))
>>> f1   = 1.2  # freq 1
>>> f2   = 2.6  # freq 2
>>> amp1 = 0.4  # amp 1
>>> amp2 = 0.6  # amp 2
>>> group1 = Group(**{'name': 'group1', 'k': k, 'chi': sin(2*pi*f1*k)})
>>> group2 = Group(**{'name': 'group2', 'k': k, 'chi': sin(2*pi*f2*k)})
>>> group3 = Group(**{'name': 'group3', 'k': k,
...                   'chi' : amp1 * group1.chi + amp2 * group2.chi + eps})
>>> collection = Collection()
>>> tags = ['ref', 'ref', 'scan']
>>> for i, group in enumerate((group1,group2, group3)):
...     collection.add_group(group, tag=tags[i])
>>> # performing lcf
>>> out = lcf(collection, fit_region='exafs', fit_range=[3,10],
...           kweight=0, sum_one=False)
>>> check_objattrs(out, Dataset,
... attrlist=['k', 'scangroup', 'refgroups',
... 'scan', 'ref1', 'ref2', 'fit', 'min_pars', 'lcf_pars'])
[True, True, True, True, True, True, True, True, True]
>>> for key, val in out.min_pars.params.items():
...     print('%1.4f +/- %1.4f' % (val.value, val.stderr))
0.4003 +/- 0.0120
0.5943 +/- 0.0120
araucaria.fit.lcfit.lcf_report(out)[source]

Returns a formatted LCF Report to sys.stdout.

Parameters

out (Dataset) – Valid Dataset from lcf().

Return type

str

Returns

LCF report.

Raises
  • TypeError – If out is not a valid Dataset instance.

  • AttributeError – If attribute min_pars, lcf_pars, scangroup, or refgroups does not exist in group.

Notes

lcf_report() is a wrapper for fit_report() of lmfit, that writes additional information on group names and calling parameters.

Example

>>> from numpy.random import seed, normal
>>> from numpy import arange, sin, pi
>>> from araucaria import Group, Collection
>>> from araucaria.fit import lcf, lcf_report
>>> seed(1234)  # seed of random values
>>> k    = arange(0,  12,   0.05)
>>> eps  = normal(0, 0.1, len(k))
>>> f1   = 1.2  # freq 1
>>> f2   = 2.6  # freq 2
>>> amp1 = 0.4  # amp 1
>>> amp2 = 0.6  # amp 2
>>> group1 = Group(**{'name': 'group1', 'k': k, 'chi': sin(2*pi*f1*k)})
>>> group2 = Group(**{'name': 'group2', 'k': k, 'chi': sin(2*pi*f2*k)})
>>> group3 = Group(**{'name': 'group3', 'k': k,
...                   'chi' : amp1 * group1.chi + amp2 * group2.chi + eps})
>>> collection = Collection()
>>> tags = ['ref', 'ref', 'scan']
>>> for i,group in enumerate((group1,group2, group3)):
...     collection.add_group(group, tag=tags[i])
>>> # performing lcf
>>> out = lcf(collection, fit_region='exafs', fit_range=[3,10],
...           kweight=0, sum_one=False)
>>> print(lcf_report(out))
[[Parameters]]
    fit_region         = exafs
    fit_range          = [3, 10]
    sum_one            = False
    kweight            = 0
[[Groups]]
    scan               = group3
    ref1               = group1
    ref2               = group2
[[Fit Statistics]]
    # fitting method   = leastsq
    # function evals   = 10
    # data points      = 141
    # variables        = 2
    chi-square         = 1.40551323
    reduced chi-square = 0.01011161
    Akaike info crit   = -645.778389
    Bayesian info crit = -639.880869
[[Variables]]
    amp1:  0.40034377 +/- 0.01195335 (2.99%) (init = 0.5)
    amp2:  0.59428689 +/- 0.01199230 (2.02%) (init = 0.5)
araucaria.fit.lcfit.sum_references(pars, data)[source]

Returns the sum of references weighted by amplitude coefficients.

Parameters
  • pars (Parameter) – Parameter object from lmfit containing the amplitude coefficients for each reference spectrum. At least attribute ‘amp1’ should exist in the object.

  • data (dict) – Dictionary with the reference arrays. At leasr key ‘ref1’ should exist in the dictionary.

Return type

ndarray

Returns

Sum of references weighted by amplitude coefficients.

Important

The number of ‘amp’ attributes in pars should match the number of ‘ref’ keys in data.

Example

>>> from numpy import allclose
>>> from lmfit import Parameters
>>> from araucaria.fit import sum_references
>>> pars = Parameters()
>>> pars.add('amp1', value=0.4)
>>> pars.add('amp2', value=0.7)
>>> data = {'ref1': 1.0, 'ref2': 2.0}
>>> allclose(sum_references(pars, data), 1.8)
True
araucaria.fit.lcfit.residuals(pars, data)[source]

Residuals between a spectrum and a linear combination of references.

Parameters
  • pars (Parameter) – Parameter object from lmfit containing the amplitude coefficients for each reference spectrum. At least attribute ‘amp1’ should exist in the object.

  • data (dict) – Dictionary with the scan and reference arrays. At least keys ‘scan’ and ‘ref1’ should exist in the dictionary.

Return type

ndarray

Returns

Array with residuals.

Important

The number of ‘amp’ attributes in pars should match the number of ‘ref’ keys in data.

Example

>>> from numpy import allclose
>>> from lmfit import Parameters
>>> from araucaria.fit import residuals
>>> pars = Parameters()
>>> pars.add('amp1', value=0.4)
>>> pars.add('amp2', value=0.7)
>>> data = {'scan': 2, 'ref1': 1.0, 'ref2': 2.0}
>>> allclose(residuals(pars, data), 0.2)
True
>>> data['scan'] = 1.6
>>> allclose(residuals(pars, data), -0.2)
True