Source code for pyqt_fit.curve_fitting

"""
:Author: Pierre Barbier de Reuille <pierre.barbierdereuille@gmail.com>

This module specifically implement the curve fitting, wrapping the default
scipy.optimize.leastsq function. It allows for parameter value fixing,
different kind of residual and added constraints function.
"""

from __future__ import division, print_function, absolute_import
from scipy import optimize
from numpy import array, inf
from .compat import lrange


[docs]class CurveFitting(object): r""" Fit a curve using the :py:func:`scipy.optimize.leastsq` function :type xdata: ndarray :param xdata: Explaining values :type ydata: ndarray :param ydata: Target values :type p0: tuple :param p0: Initial estimates for the parameters of fct :type fct: callable :param fct: Function to optimize. The call will be equivalent to ``fct(p0, xdata, *args)`` :type args: tuple :param args: Additional arguments for the function :type residuals: callable or None :param residuals: Function computing the residuals. The call is equivalent to ``residuals(y, fct(x))`` and it should return a ndarray. If None, residuals are simply the difference between the computed and expected values. :type fix_params: tuple of int :param fix_params: List of indices for the parameters in p0 that shouldn't change :type Dfun: callable :param Dfun: Function computing the jacobian of fct w.r.t. the parameters. The call will be equivalent to ``Dfun(p0, xdata, *args)`` :type Dres: callable :param Dres: Function computing the jacobian of the residuals w.r.t. the parameters. The call will be equivalent to ``Dres(y, fct(x), DFun(x))``. If None, residuals must also be None. :type col_deriv: int :param col_deriv: Define if Dfun returns the derivatives by row or column. With n = len(xdata) and m = len(p0), the shape of output of Dfun must be (n,m) if 0, and (m,n) if non-0. :type constraints: callable :param constraints: If not None, this is a function that should always return a list ofvalues (the same), to add penalties for bad parameters. The function call is equivalent to: ``constraints(p0)`` :type lsq_args: tuple :param lsq_args: List of unnamed arguments passed to ``optimize.leastsq``, starting with ``ftol`` :type lsq_kword: dict :param lsq_kword: Dictionnary of named arguments passed to py:func:`scipy.optimize.leastsq`, starting with ``ftol`` Once constructed, the following variables contain the result of the fitting: :ivar ndarray popt: The solution (or the result of the last iteration for an unsuccessful call) :ivar ndarray pcov: The estimated covariance of popt. The diagonals provide the variance of the parameter estimate. :ivar ndarray res: Final residuals :ivar dict infodict: a dictionary of outputs with the keys: ``nfev`` the number of function calls ``fvec`` the function evaluated at the output ``fjac`` A permutation of the R matrix of a QR factorization of the final approximate Jacobian matrix, stored column wise. Together with ipvt, the covariance of the estimate can be approximated. ``ipvt`` an integer array of length N which defines a permutation matrix, ``p``, such that ``fjac*p = q*r``, where ``r`` is upper triangular with diagonal elements of nonincreasing magnitude. Column ``j`` of ``p`` is column ``ipvt(j)`` of the identity matrix. ``qtf`` the vector ``(transpose(q) * fvec)`` ``CI`` list of tuple of parameters, each being the lower and upper bounds for the confidence interval in the CI argument at the same position. ``est_jacobian`` True if the jacobian is estimated, false if the user-provided functions have been used .. note:: In this implementation, residuals are supposed to be a generalisation of the notion of difference. In the end, the mathematical expression of this minimisation is: .. math:: \hat{\theta} = \argmin_{\theta\in \mathbb{R}^p} \sum_i r(y_i, f(\theta, x_i))^2 Where :math:`\theta` is the vector of :math:`p` parameters to optimise, :math:`r` is the residual function and :math:`f` is the function being fitted. """ def __init__(self, xdata, ydata, p0, fct, args=(), residuals=None, fix_params=(), Dfun=None, Dres = None, col_deriv=1, constraints = None, *lsq_args, **lsq_kword): self.fct = fct if residuals is None: residuals = lambda x, y: (x - y) Dres = lambda y1, y0, dy: -dy use_derivs = (Dres is not None) and (Dfun is not None) df = None if fix_params: fix_params = tuple(fix_params) p_save = array(p0, dtype=float) change_params = lrange(len(p0)) try: for i in fix_params: change_params.remove(i) except ValueError: raise ValueError("List of parameters to fix is incorrect: " "contains either duplicates or values " "out of range.") p0 = p_save[change_params] def f(p, *args): p1 = array(p_save) p1[change_params] = p y0 = fct(p1, xdata, *args) return residuals(ydata, y0) if use_derivs: def df(p, *args): p1 = array(p_save) p1[change_params] = p y0 = fct(p1, xdata, *args) dfct = Dfun(p1, xdata, *args) result = Dres(ydata, y0, dfct) if col_deriv != 0: return result[change_params] else: return result[:, change_params] return result else: def f(p, *args): # noqa y0 = fct(p, xdata, *args) return residuals(ydata, y0) if use_derivs: def df(p, *args): # noqa dfct = Dfun(p, xdata, *args) y0 = fct(p, xdata, *args) return Dres(ydata, y0, dfct) optim = optimize.leastsq(f, p0, args, full_output=1, Dfun=df, col_deriv=col_deriv, *lsq_args, **lsq_kword) popt, pcov, infodict, mesg, ier = optim #infodict['est_jacobian'] = not use_derivs if fix_params: p_save[change_params] = popt popt = p_save if not ier in [1, 2, 3, 4]: raise RuntimeError("Unable to determine number of fit parameters. " "Error returned by scipy.optimize.leastsq:\n%s" % (mesg,)) res = residuals(ydata, fct(popt, xdata, *args)) if (len(res) > len(p0)) and pcov is not None: s_sq = (res ** 2).sum() / (len(ydata) - len(p0)) pcov = pcov * s_sq else: pcov = inf self.popt = popt self.pcov = pcov self.res = res self.infodict = infodict
[docs] def __call__(self, xdata): """ Return the value of the fitted function for each of the points in ``xdata`` """ return self.fct(self.popt, xdata)