# -*- coding: utf-8 -*-
# mcsas/backgroundscalingfit.py
# Find the reST syntax at http://sphinx-doc.org/rest.html
from __future__ import absolute_import # PEP328
from __future__ import division
from builtins import object
from scipy import optimize
from bases.model import SASModel
[docs]class BackgroundScalingFit(object):
"""
Chi-squared convergence calculation happens here.
Optimizes the scaling and background factor to match *intCalc* closest
to intObs.
Returns an array with scaling factors. Input initial guess *sc* has
to be a two-element array with the scaling and background.
**Input arguments:**
:arg intObs: An array of *measured*, respectively *observed*
intensities
:arg intCalc: An array of intensities which should be scaled to match
*intObs*
:arg intError: An array of uncertainties to match *intObs*
:arg sc: A 2-element array of initial guesses for scaling
factor and background
:arg ver: *(optional)* Can be set to 1 for old version, more robust
but slow, default 2 for new version,
10x faster than version 1, requires decent starting values
:arg outputIntensity: *(optional)* Return the scaled intensity as
third output argument, default: False
:arg background: *(optional)* Enables a flat background contribution,
default: True
:returns: (*sc*, *conval*): A tuple of an array containing the
intensity scaling factor and background and the reduced
chi-squared value.
"""
_findBackground = None # True: find optimal background as well
def __init__(self, findBackground, *args):
self._findBackground = bool(findBackground)
@staticmethod
[docs] def chi(sc, dataMeas, dataErr, dataCalc):
"""Chi calculation, difference of measured and calculated signal.
"""
return (dataMeas - sc[0] * dataCalc - sc[1]) / dataErr
@staticmethod
[docs] def chiNoBg(sc, dataMeas, dataErr, dataCalc):
"""Chi calculation, difference of measured and calculated signal,
scaling only, no backgrund.
"""
return (dataMeas - sc[0] * dataCalc) / dataErr
@staticmethod
[docs] def chiSqr(dataMeas, dataErr, dataCalc):
"""Reduced Chi-squared calculation, size of parameter-space not taken
into account; for data with known intError.
"""
return sum(((dataMeas - dataCalc)/dataErr)**2) / len(dataMeas)
@staticmethod
[docs] def aGoFsAlpha(dataMeas, dataErr, dataCalc):
"""The alternative Goodness-of-Fit value without alpha, i.e. multiplied
by alpha, according to [Henn 2016]
( http://dx.doi.org/10.1107/S2053273316013206 )."""
return sum((dataMeas - dataCalc)**2) / sum(dataErr**2)
[docs] def dataScaled(self, data, sc):
"""Returns the input data scaled by the provided factor and background
level applied if requested."""
if self._findBackground:
return (data * sc[0]) + sc[1] # apply background on request
# else:
return (data * sc[0])
[docs] def fitLM(self, dataMeas, dataErr, dataCalc, sc):
func = self.chiNoBg
if self._findBackground:
func = self.chi
sc, success = optimize.leastsq(func, sc, args = (dataMeas, dataErr, dataCalc),
full_output = False)
return sc
[docs] def fitSimplex(self, dataMeas, dataErr, dataCalc, sc):
def residual(xsc):
return self.chiSqr(dataMeas, dataErr, self.dataScaled(dataCalc, xsc))
sc = optimize.fmin(residual, sc, full_output = False, disp = 0)
return sc
[docs] def calc(self, data, modelData, sc, ver = 2):
dataMeas = data.f.binnedData.flatten()
dataErr = 1.
if data.f.binnedDataU is not None:
dataErr = data.f.binnedDataU.flatten()
dataErr[dataErr == 0.0] = 1. # prevent division by zero
dataCalc = modelData.chisqrInt
if not len(dataMeas): # all data filtered
return sc, 1., dataCalc, 1.
# different data fit approaches: speed vs. stability (?)
if ver == 2:
sc = self.fitLM(dataMeas, dataErr, dataCalc, sc)
else:
sc = self.fitSimplex(dataMeas, dataErr, dataCalc, sc)
if not self._findBackground:
sc[1] = 0.0
# calculate convergence value
conval = self.chiSqr(dataMeas, dataErr, self.dataScaled(dataCalc, sc))
aGoFs = self.aGoFsAlpha(dataMeas, dataErr, self.dataScaled(dataCalc, sc))
# multiplied by the reciprocal of alpha
aGoFs *= len(dataMeas) / (len(dataMeas) - modelData.numParams )
return sc, conval, dataCalc, aGoFs
# vim: set ts=4 sts=4 sw=4 tw=0: