Beispiel #1
0
def multi_1dgauss_mpfit(xax, data, ngauss=1, err=None, params=None, 
        fixed=None, limitedmin=None, limitedmax=None, minpars=None, 
        maxpars=None, force_Sigma_bounds=False, factor_Chi2=1.01, verbose=True,
        veryverbose=False, linear_method="nnls", minSigma=None, maxSigma=None,
        mpfitprint=mpfitprint(), **fcnargs):
    """ An improvement on gaussfit.  Lets you fit multiple 1D gaussians.

    :param   xax: x axis
    :param   data: count axis
    :param   ngauss: How many gaussians to fit?  Default 1 
    :param   err: error corresponding to data
     :param  params: Fit parameters -- [width] * ngauss
         If len(params) == 0, ngauss will be set to len(params) 
         These parameters need to have length = ngauss.  If ngauss > 1 and length = 1 , they will
         be replicated ngauss times, otherwise they will be reset to defaults:
     :param  fixed: Is parameter fixed?
     :param  limitedmin/minpars: set lower limits on each parameter (default: width>0)
     :param  limitedmax/maxpars: set upper limits on each parameter
     :param  force_Sigma_bounds: force the Sigmas to be within the radii range with some margin
         default to True
     :param  factor_Chi2: if one Gaussian contribute less than (factor-1) to the Chi2, we remove it
         If set to 1, it means only zero Gaussians will be removed
         If set to default=1.01, it means any Gaussian contributing to less than 1% will be removed
     :param  minSigma, maxSigma: default to None but can be set for bounds for Sigma
     :param  linearmethod: Method used to solve the linear part of the problem
         Two methods are implemented: 
         "nnls" -> NNLS (default, included in scipy)
         "bvls" -> LLSP/BVLS in openopt (only if available)
         The variable Exist_OpenOpt is (internally) set to True if available

     :param  **fcnargs: Will be passed to MPFIT, you can for example use xtol, gtol, ftol, quiet
     :param  verbose: self-explanatory
     :param  veryverbose: self-explanatory

    :return Fit parameters:
    :return Model:
    :return Fit errors:
    :return chi2:

    """
    import copy

    ## Set up some default parameters for mpfit
    if "xtol" not in fcnargs.keys() : fcnargs["xtol"] = 1.e-10
    if "gtol" not in fcnargs.keys() : fcnargs["gtol"] = 1.e-10
    if "ftol" not in fcnargs.keys() : fcnargs["ftol"] = 1.e-10
    if "quiet" not in fcnargs.keys() : fcnargs["quiet"] = True

    ## Checking the method used for the linear part
    if linear_method == "bvls" and not Exist_OpenOpt :
        print "WARNING: you selected BVLS, but OpenOpt is not installed"
        print "WARNING: we will therefore use NNLS instead"
        linear_method == "nnls"

    ## Checking if the linear_method is implemented
    if linear_method.lower() not in dic_linear_methods.keys():
        print "ERROR: you should use one of the following linear_method: ", dic_linear_methods.keys()
        return 0, 0, 0, 0

    f_Get_Iamp = dic_linear_methods[linear_method.lower()]
    ## If no coordinates is given, create them
    if xax is None:
        xax = np.arange(len(data))

    if not isinstance(xax,np.ndarray): 
        xax = (np.asarray(xax)).ravel()

    sxax = xax[xax != 0]

    min_xax = np.min(sxax)
    max_xax = np.max(sxax)
#    DlSigma = 0.5 * np.log10(max_xax / min_xax) / ngauss
    DlSigma = 0.02
    lminS = np.log10(min_xax)
    lmaxS = np.log10(max_xax)

    if minSigma is None : minSigma = min_xax
    if maxSigma is None : maxSigma = max_xax / sqrt(2.)

    if isinstance(params,np.ndarray): params=params.tolist()
    if params is not None : 
        if (len(params) > ngauss): 
            ngauss = len(params)
            if verbose :
                print "WARNING: Your input parameters do not fit the Number of input Gaussians"
                print "WARNING: the new number of input Gaussians is: ", ngauss

    ## if no input parameters are given, we set up the guess as a log spaced sigma between min and max
    default_params = np.logspace(lminS + DlSigma, lmaxS - DlSigma, ngauss)

    newdefault_minpars = copy.copy(default_minpars)
    newdefault_maxpars = copy.copy(default_maxpars)
    if force_Sigma_bounds :
        newdefault_minpars[0] = minSigma
        newdefault_maxpars[0] = maxSigma
    else :
        newdefault_minpars[0] = minSigma / 100.
        newdefault_maxpars[0] = maxSigma * 100.

    ## Set up the default parameters if needed
    params = set_parameters_and_default_1D(params, default_params, ngauss)
    fixed = set_parameters_and_default_1D(fixed, default_fixed, ngauss)
    limitedmin = set_parameters_and_default_1D(limitedmin, default_limitedmin, ngauss)
    limitedmax = set_parameters_and_default_1D(limitedmax, default_limitedmax, ngauss)
    minpars = set_parameters_and_default_1D(minpars, newdefault_minpars, ngauss)
    maxpars = set_parameters_and_default_1D(maxpars, newdefault_maxpars, ngauss)

    ## -------------------------------------------------------------------------------------
    ## mpfit function which returns the residual from the best fit N 1D Gaussians
    ## Parameters are just sigma,q,pa - the amplitudes are optimised at each step
    ## Two versions are available depending on whether BVLS or NNLS is used (and available)
    ## -------------------------------------------------------------------------------------
    def mpfitfun(p, fjac=None, x=None, err=None, data=None, f_Get_Iamp=None):
        Iamp = f_Get_Iamp(p, x, data)
        newp = (np.vstack((Iamp, p.transpose()))).transpose()
        if err is None : 
            return [0,fitn1dgauss_residuals(newp, x, data)]
        else :
            return [0,fitn1dgauss_residuals_err(newp, x, data, err)]

    ## Information about the parameters
    if veryverbose :
        print "------------------"
        print "GUESS:      Sig   "
        print "------------------"
        for i in xrange(ngauss) :
            print "GAUSS %02d: %8.3e"%(i+1, params[i])
        print "--------------------------------------"

    ## Information about the parameters
    parnames = {0:"SIGMA"}
    parinfo = [ {'n':ii, 'value':params[ii], 'limits':[minpars[ii],maxpars[ii]], 
        'limited':[limitedmin[ii],limitedmax[ii]], 'fixed':fixed[ii], 
        'parname':parnames[0]+str(ii+1), 'error':ii} for ii in xrange(len(params)) ]

    ## Fit with mpfit of q, sigma, pa on xax, yax, and data (+err)
    fa = {'x': xax, 'data': data, 'err':err, 'f_Get_Iamp':f_Get_Iamp}

    if verbose: 
        print "------ Starting the minimisation -------"
    result = mpfit(mpfitfun, functkw=fa, iterfunct=mpfitprint, nprint=1, parinfo=parinfo, **fcnargs) 
    ## Getting these best fit values into the dictionnary
    bestparinfo = [ {'n':ii, 'value':result.params[ii], 'limits':[minpars[ii],maxpars[ii]], 
        'limited':[limitedmin[ii],limitedmax[ii]], 'fixed':fixed[ii], 
        'parname':parnames[0]+str(ii), 'error':ii} for ii in xrange(len(result.params)) ]

    ## Recompute the best amplitudes to output the right parameters
    ## And renormalising them
    Iamp = f_Get_Iamp(result.params, xax, data)
    Ibestpar_array = (np.vstack((Iamp, result.params.transpose()))).transpose()

    ## Getting rid of the non-relevant Gaussians
    ## If parameters factor_Chi2 is set we use it as a threshold to remove gaussians
    ## Otherwise we just remove the zeros
    if err is None : nerr = np.ones_like(data)
    else : nerr =  err
    ## First get the Chi2 from this round
    bestChi2 = fitn1dgauss_chi2_err(Ibestpar_array, xax, data, nerr)
    result.ind = range(ngauss)

    k = 0
    Removed_Gaussians = []
    for i in xrange(ngauss) :
        ## Derive the Chi2 WITHOUT the ith Gaussian
        newChi2 = fitn1dgauss_chi2_err(np.delete(Ibestpar_array, i, 0), xax, data, nerr)
        ## If this Chi2 is smaller than factor_Chi2 times the best value, then remove
        ## It just means that Gaussian is not an important contributor
        if newChi2 <= factor_Chi2 * bestChi2 :
            val = bestparinfo.pop(k)
            result.ind.pop(k)
            Removed_Gaussians.append(i+1)
        else : k += 1

    if veryverbose :
        if len(Removed_Gaussians) != 0 :
            print "WARNING Removed Gaussians ", Removed_Gaussians
            print "WARNING: (not contributing enough to the fit)"
    ngauss = len(result.ind)

    ## New minimisation after removing all the non relevant Gaussians
    newresult = mpfit(mpfitfun, functkw=fa, iterfunct=mpfitprint, nprint=1, parinfo=bestparinfo, **fcnargs) 

    newresult.ind = range(ngauss)
    bestfit_params = newresult.params

    ## We add the Amplitudes to the array and renormalise them
    Iamp = f_Get_Iamp(bestfit_params, xax, data)
    Ibestfit_params = (np.vstack((Iamp, bestfit_params.transpose()))).transpose()
    Ibestfit_params[:,0] /= (sq2pi * Ibestfit_params[:,1])
    ## And we sort them with Sigma
    Ibestfit_params = Ibestfit_params[Ibestfit_params[:,1].argsort()]

    if newresult.status == 0:
        raise Exception(newresult.errmsg)

    if verbose :
        print "============================="
        print "FIT:      Imax       Sig   "
        print "============================="
        for i in xrange(ngauss) :
            print "GAUSS %02d: %8.3e  %8.3f"%(i+1, Ibestfit_params[i,0], Ibestfit_params[i,1])

        print "Chi2: ",newresult.fnorm," Reduced Chi2: ",newresult.fnorm/len(data)

    return Ibestfit_params, newresult, n_centred_onedgaussian_Imax(pars=Ibestfit_params)(xax)
Beispiel #2
0
def multi_2dgauss_mpfit(xax, yax, data, ngauss=1, err=None, params=None, paramsPSF=None,
        fixed=None, limitedmin=None, limitedmax=None, minpars=None, 
        maxpars=None, force_Sigma_bounds=True, factor_Chi2=1.01, verbose=True, veryverbose=False, 
        linear_method="nnls", default_q=0.3, default_PA=0.0, samePA=True, minSigma=None, maxSigma=None, 
        mpfitprint=_mpfitprint(), **fcnargs):
    """
    An improvement on gaussfit.  Lets you fit multiple 2D gaussians.

    Inputs:
       xax - x axis
       yax - y axis
       data - count axis
       ngauss - How many gaussians to fit?  Default 1 
       err - error corresponding to data

     These parameters need to have length = 3*ngauss.  If ngauss > 1 and length = 3, they will
     be replicated ngauss times, otherwise they will be reset to defaults:
       params - Fit parameters: [width, axis ratio, pa] * ngauss
              If len(params) % 3 == 0, ngauss will be set to len(params) / 3
       fixed - Is parameter fixed?
       limitedmin/minpars - set lower limits on each parameter (default: width>0)
       limitedmax/maxpars - set upper limits on each parameter

       force_Sigma_bounds: force the Sigmas to be within the radii range with some margin
                           default to True
       factor_Chi2 : if one Gaussian contribute less than (factor-1) to the Chi2, we remove it
                     If set to 1, it means only zero Gaussians will be removed
                     If set to default=1.01, it means any Gaussian contributing to less than 1% will be
                     removed

       minSigma, maxSigma: default to None but can be set for bounds for Sigma

       linearmethod: Method used to solve the linear part of the problem
                     Two methods are implemented: 
                         "nnls" -> NNLS (default, included in scipy)
                         "bvls" -> LLSP/BVLS in openopt (only if available)
                         The variable Exist_OpenOpt is (internally) set to True if available

       **fcnargs - Will be passed to MPFIT, you can for example use: 
                   xtol, gtol, ftol, quiet

       verbose - self-explanatory
       veryverbose - self-explanatory

    Returns:
       Fit parameters
       Model
       Fit errors
       chi2
    """
    import copy

    ## Set up some default parameters for mpfit
    if "xtol" not in fcnargs.keys() : fcnargs["xtol"] = 1.e-7
    if "gtol" not in fcnargs.keys() : fcnargs["gtol"] = 1.e-7
    if "ftol" not in fcnargs.keys() : fcnargs["ftol"] = 1.e-7
    if "quiet" not in fcnargs.keys() : fcnargs["quiet"] = True

    ## Checking the method used for the linear part
    if linear_method == "bvls" and not Exist_OpenOpt :
        print "WARNING: you selected BVLS, but OpenOpt is not installed"
        print "WARNING: we will therefore use NNLS instead"
        linear_method == "nnls"

    ## Checking if the linear_method is implemented
    if linear_method.lower() not in dic_linear_methods.keys():
        print "ERROR: you should use one of the following linear_method: ", dic_linear_methods.keys()
        return 0, 0, 0, 0

    f_Get_Iamp = dic_linear_methods[linear_method.lower()]
    ## If no coordinates is given, create them
    if xax is None:
        xax = np.arange(len(data))
    if yax is None:
        yax = np.arange(len(data))

    if not isinstance(xax,np.ndarray): 
        xax = np.asarray(xax)
    if not isinstance(yax,np.ndarray): 
        yax = np.asarray(yax)
    if not isinstance(data,np.ndarray): 
        data = np.asarray(data)
    xax = xax.ravel()
    yax = yax.ravel()
    datashape = data.shape
    data = data.ravel()

    ## Polar coordinates
    r, theta = convert_xy_to_polar(xax, yax)

    selxy = (xax != 0) & (yax != 0)
    rin = sqrt(xax[selxy]**2+yax[selxy]**2)
    if minSigma is None : minSigma = np.min(rin)
    if maxSigma is None : maxSigma = np.max(rin) / sqrt(SLOPE_outer)
    lminSigma = np.log10(minSigma)
    lmaxSigma = np.log10(maxSigma)
    DlSigma = 0.5 * (lmaxSigma - lminSigma) / ngauss

    if isinstance(params,np.ndarray): params=params.tolist()
    if params is not None : 
        if len(params) != ngauss and (len(params) / 3) > ngauss: 
            ngauss = len(params) / 3 
            if verbose :
                print "WARNING: Your input parameters do not fit the Number of input Gaussians"
                print "WARNING: the new number of input Gaussians is: ", ngauss

    ## Extracting the parameters for the PSF and normalising the Imax for integral = 1
    if paramsPSF is None : 
        paramsPSF = _default_parPSF
    paramsPSF = norm_PSFParam(paramsPSF)

    ## if no input parameters are given, we set up the guess as a log spaced sigma between min and max
    default_params = np.concatenate((np.log10(np.logspace(lminSigma + DlSigma, lmaxSigma - DlSigma, ngauss)), \
            np.array([default_q]*ngauss), np.array([default_PA]*ngauss))).reshape(3,ngauss).transpose().ravel()

    newdefault_minpars = copy.copy(default_minpars)
    newdefault_maxpars = copy.copy(default_maxpars)
    if force_Sigma_bounds :
        newdefault_minpars[0] = lminSigma
        newdefault_maxpars[0] = lmaxSigma
    else :
        newdefault_minpars[0] = lminSigma - np.log10(2.)
        newdefault_maxpars[0] = lmaxSigma + np.log10(2.)

    ## Set up the default parameters if needed
    params = set_parameters_and_default(params, default_params, ngauss)
    fixed = set_parameters_and_default(fixed, default_fixed, ngauss)
    limitedmin = set_parameters_and_default(limitedmin, default_limitedmin, ngauss)
    limitedmax = set_parameters_and_default(limitedmax, default_limitedmax, ngauss)
    minpars = set_parameters_and_default(minpars, newdefault_minpars, ngauss)
    maxpars = set_parameters_and_default(maxpars, newdefault_maxpars, ngauss)

    ## -------------------------------------------------------------------------------------
    ## mpfit function which returns the residual from the best fit N 2D Gaussians
    ## Parameters are just sigma,q,pa - the amplitudes are optimised at each step
    ## Two versions are available depending on whether BVLS or NNLS is used (and available)
    ## -------------------------------------------------------------------------------------
    def mpfitfun(p, parPSF, fjac=None, r=None, theta=None, err=None, data=None, f_Get_Iamp=None, samePA=False):
        if samePA : p = regrow_PA(p)
        nGnorm, Iamp = f_Get_Iamp(p, parPSF, r, theta, data)
        if err is None : 
            return [0,fitn2dgauss_residuals1(nGnorm, Iamp)]
        else :
            return [0,fitn2dgauss_residuals_err1(err, nGnorm, Iamp)]
#        newp = (np.vstack((Iamp, p.reshape(ngauss,3).transpose()))).transpose()
#        if err is None : 
#            return [0,fitn2dgauss_residuals(newp, parPSF, r, theta, data)]
#        else :
#            return [0,fitn2dgauss_residuals_err(newp, parPSF, r, theta, data, err)]
    ## -------------------------------------------------------------------------------------

    ## Information about the parameters
    if verbose :
        print "--------------------------------------"
        print "GUESS:      Sig         Q         PA"
        print "--------------------------------------"
        for i in xrange(ngauss) :
            print "GAUSS %02d: %8.3e  %8.3f  %8.3f"%(i+1, 10**(params[3*i]), params[3*i+1], params[3*i+2])
    print "--------------------------------------"

    ## Information about the parameters
    parnames = {0:"LOGSIGMA",1:"AXIS RATIO",2:"POSITION ANGLE"}
    parinfo = [ {'n':ii, 'value':params[ii], 'limits':[minpars[ii],maxpars[ii]], 
        'limited':[limitedmin[ii],limitedmax[ii]], 'fixed':fixed[ii], 
        'parname':parnames[ii%3]+str(ii/3+1)} for ii in xrange(len(params)) ]

    ## If samePA we remove all PA parameters except the last one
    ## We could use the 'tied' approach but we prefer setting up just one parameter
    if samePA : parinfo = shrink_PA(parinfo)

    ## Fit with mpfit of q, sigma, pa on xax, yax, and data (+err)
    fa = {'parPSF':paramsPSF, 'r': r, 'theta': theta, 'data': data, 'err':err, 'f_Get_Iamp':f_Get_Iamp, 'samePA':samePA}

    result = mpfit(mpfitfun, functkw=fa, iterfunct=mpfitprint, nprint=10, parinfo=parinfo, **fcnargs) 

    ## Getting these best fit values into the dictionnary
    if samePA : result.params = regrow_PA(result.params)
    bestparinfo = [ {'n':ii, 'value':result.params[ii], 'limits':[minpars[ii],maxpars[ii]], 
        'limited':[limitedmin[ii],limitedmax[ii]], 'fixed':fixed[ii], 
        'parname':parnames[ii%3]+str(ii/3)} for ii in xrange(len(result.params)) ]

    ## Recompute the best amplitudes to output the right parameters
    ## And renormalising them
    nGnorm, Iamp = f_Get_Iamp(result.params, paramsPSF, r, theta, data)
#    Ibestpar_array = (np.vstack((Iamp, result.params.reshape(ngauss,3).transpose()))).transpose()
    bestpar_array = result.params.reshape(ngauss,3)

    ## Getting rid of the non-relevant Gaussians
    ## If parameters factor_Chi2 is set we use it as a threshold to remove gaussians
    ## Otherwise we just remove the zeros
    if err is None : nerr = np.ones_like(data)
    else : nerr =  err
    ## First get the Chi2 from this round
#    bestChi2 = fitn2dgauss_chi2_err(Ibestpar_array, paramsPSF, r, theta, data, nerr)
    bestChi2 = np.sum(fitn2dgauss_residuals1(nGnorm, Iamp)**2)
    result.ind = range(ngauss)

    k = 0
    Removed_Gaussians = []
    for i in xrange(ngauss) :
        ## Derive the Chi2 WITHOUT the ith Gaussian
        new_nGnorm, new_Iamp = f_Get_Iamp(np.delete(bestpar_array, i, 0), paramsPSF, r, theta, data)
        newChi2 = np.sum(fitn2dgauss_residuals1(new_nGnorm, new_Iamp)**2)
#        newChi2 = fitn2dgauss_chi2_err(np.delete(Ibestpar_array, i, 0), paramsPSF, r, theta, data, nerr)
        ## If this Chi2 is smaller than factor_Chi2 times the best value, then remove
        ## It just means that Gaussian is not an important contributor
        if newChi2 <= factor_Chi2 * bestChi2 :
            val = bestparinfo.pop(3*k)
            val = bestparinfo.pop(3*k)
            val = bestparinfo.pop(3*k)
            result.ind.pop(k)
            Removed_Gaussians.append(i+1)
        else : k += 1

    if veryverbose :
        if len(Removed_Gaussians) != 0 :
            print "WARNING Removed Gaussians ", Removed_Gaussians
            print "WARNING: (not contributing enough to the fit)"
    ngauss = len(result.ind)

    ## New minimisation after removing all the non relevant Gaussians
    if samePA : bestparinfo = shrink_PA(bestparinfo)
    newresult = mpfit(mpfitfun, functkw=fa, iterfunct=mpfitprint, nprint=10, parinfo=bestparinfo, **fcnargs) 

    newresult.ind = range(ngauss)
    if samePA : newresult.params = regrow_PA(newresult.params)
    bestfit_params = newresult.params.reshape(ngauss, 3)

    ## We add the Amplitudes to the array and renormalise them
    nGnorm, Iamp = f_Get_Iamp(bestfit_params, paramsPSF, r, theta, data)
    Ibestfit_params = (np.vstack((Iamp, bestfit_params.transpose()))).transpose()
    ## Going back to sigma from logsigma
    Ibestfit_params[:,1] = 10**(Ibestfit_params[:,1])
    Ibestfit_params[:,0] /= (2.0 * Ibestfit_params[:,1]**2 * Ibestfit_params[:,2] * pi)
    ## And we sort them with Sigma
    Ibestfit_params = Ibestfit_params[Ibestfit_params[:,1].argsort()]

    if newresult.status == 0:
        raise Exception(newresult.errmsg)

    if verbose :
        print "=================================================="
        print "FIT:      Imax       Sig         Q           PA"
        print "=================================================="
        for i in xrange(ngauss) :
            print "GAUSS %02d: %8.3e  %8.3f  %8.3f  %8.3f"%(i+1, Ibestfit_params[i,0], Ibestfit_params[i,1], Ibestfit_params[i,2], Ibestfit_params[i,3])

        print "Chi2: ",newresult.fnorm," Reduced Chi2: ",newresult.fnorm/len(data)

    return Ibestfit_params, newresult, n_centred_twodgaussian_Imax(pars=Ibestfit_params, parPSF=paramsPSF)(r, theta).reshape(datashape)
Beispiel #3
0
def multi_1dgauss_mpfit(xax,
                        data,
                        ngauss=1,
                        err=None,
                        params=None,
                        fixed=None,
                        limitedmin=None,
                        limitedmax=None,
                        minpars=None,
                        maxpars=None,
                        force_Sigma_bounds=False,
                        factor_Chi2=1.01,
                        verbose=True,
                        veryverbose=False,
                        linear_method="nnls",
                        minSigma=None,
                        maxSigma=None,
                        mpfitprint=mpfitprint(),
                        **fcnargs):
    """ An improvement on gaussfit.  Lets you fit multiple 1D gaussians.

    :param   xax: x axis
    :param   data: count axis
    :param   ngauss: How many gaussians to fit?  Default 1 
    :param   err: error corresponding to data
     :param  params: Fit parameters -- [width] * ngauss
         If len(params) == 0, ngauss will be set to len(params) 
         These parameters need to have length = ngauss.  If ngauss > 1 and length = 1 , they will
         be replicated ngauss times, otherwise they will be reset to defaults:
     :param  fixed: Is parameter fixed?
     :param  limitedmin/minpars: set lower limits on each parameter (default: width>0)
     :param  limitedmax/maxpars: set upper limits on each parameter
     :param  force_Sigma_bounds: force the Sigmas to be within the radii range with some margin
         default to True
     :param  factor_Chi2: if one Gaussian contribute less than (factor-1) to the Chi2, we remove it
         If set to 1, it means only zero Gaussians will be removed
         If set to default=1.01, it means any Gaussian contributing to less than 1% will be removed
     :param  minSigma, maxSigma: default to None but can be set for bounds for Sigma
     :param  linearmethod: Method used to solve the linear part of the problem
         Two methods are implemented: 
         "nnls" -> NNLS (default, included in scipy)
         "bvls" -> LLSP/BVLS in openopt (only if available)
         The variable Exist_OpenOpt is (internally) set to True if available

     :param  **fcnargs: Will be passed to MPFIT, you can for example use xtol, gtol, ftol, quiet
     :param  verbose: self-explanatory
     :param  veryverbose: self-explanatory

    :return Fit parameters:
    :return Model:
    :return Fit errors:
    :return chi2:

    """
    import copy

    ## Set up some default parameters for mpfit
    if "xtol" not in fcnargs.keys(): fcnargs["xtol"] = 1.e-10
    if "gtol" not in fcnargs.keys(): fcnargs["gtol"] = 1.e-10
    if "ftol" not in fcnargs.keys(): fcnargs["ftol"] = 1.e-10
    if "quiet" not in fcnargs.keys(): fcnargs["quiet"] = True

    ## Checking the method used for the linear part
    if linear_method == "bvls" and not Exist_OpenOpt:
        print "WARNING: you selected BVLS, but OpenOpt is not installed"
        print "WARNING: we will therefore use NNLS instead"
        linear_method == "nnls"

    ## Checking if the linear_method is implemented
    if linear_method.lower() not in dic_linear_methods.keys():
        print "ERROR: you should use one of the following linear_method: ", dic_linear_methods.keys(
        )
        return 0, 0, 0, 0

    f_Get_Iamp = dic_linear_methods[linear_method.lower()]
    ## If no coordinates is given, create them
    if xax is None:
        xax = np.arange(len(data))

    if not isinstance(xax, np.ndarray):
        xax = (np.asarray(xax)).ravel()

    sxax = xax[xax != 0]

    min_xax = np.min(sxax)
    max_xax = np.max(sxax)
    #    DlSigma = 0.5 * np.log10(max_xax / min_xax) / ngauss
    DlSigma = 0.02
    lminS = np.log10(min_xax)
    lmaxS = np.log10(max_xax)

    if minSigma is None: minSigma = min_xax
    if maxSigma is None: maxSigma = max_xax / sqrt(2.)

    if isinstance(params, np.ndarray): params = params.tolist()
    if params is not None:
        if (len(params) > ngauss):
            ngauss = len(params)
            if verbose:
                print "WARNING: Your input parameters do not fit the Number of input Gaussians"
                print "WARNING: the new number of input Gaussians is: ", ngauss

    ## if no input parameters are given, we set up the guess as a log spaced sigma between min and max
    default_params = np.logspace(lminS + DlSigma, lmaxS - DlSigma, ngauss)

    newdefault_minpars = copy.copy(default_minpars)
    newdefault_maxpars = copy.copy(default_maxpars)
    if force_Sigma_bounds:
        newdefault_minpars[0] = minSigma
        newdefault_maxpars[0] = maxSigma
    else:
        newdefault_minpars[0] = minSigma / 100.
        newdefault_maxpars[0] = maxSigma * 100.

    ## Set up the default parameters if needed
    params = set_parameters_and_default_1D(params, default_params, ngauss)
    fixed = set_parameters_and_default_1D(fixed, default_fixed, ngauss)
    limitedmin = set_parameters_and_default_1D(limitedmin, default_limitedmin,
                                               ngauss)
    limitedmax = set_parameters_and_default_1D(limitedmax, default_limitedmax,
                                               ngauss)
    minpars = set_parameters_and_default_1D(minpars, newdefault_minpars,
                                            ngauss)
    maxpars = set_parameters_and_default_1D(maxpars, newdefault_maxpars,
                                            ngauss)

    ## -------------------------------------------------------------------------------------
    ## mpfit function which returns the residual from the best fit N 1D Gaussians
    ## Parameters are just sigma,q,pa - the amplitudes are optimised at each step
    ## Two versions are available depending on whether BVLS or NNLS is used (and available)
    ## -------------------------------------------------------------------------------------
    def mpfitfun(p, fjac=None, x=None, err=None, data=None, f_Get_Iamp=None):
        Iamp = f_Get_Iamp(p, x, data)
        newp = (np.vstack((Iamp, p.transpose()))).transpose()
        if err is None:
            return [0, fitn1dgauss_residuals(newp, x, data)]
        else:
            return [0, fitn1dgauss_residuals_err(newp, x, data, err)]

    ## Information about the parameters
    if veryverbose:
        print "------------------"
        print "GUESS:      Sig   "
        print "------------------"
        for i in xrange(ngauss):
            print "GAUSS %02d: %8.3e" % (i + 1, params[i])
        print "--------------------------------------"

    ## Information about the parameters
    parnames = {0: "SIGMA"}
    parinfo = [{
        'n': ii,
        'value': params[ii],
        'limits': [minpars[ii], maxpars[ii]],
        'limited': [limitedmin[ii], limitedmax[ii]],
        'fixed': fixed[ii],
        'parname': parnames[0] + str(ii + 1),
        'error': ii
    } for ii in xrange(len(params))]

    ## Fit with mpfit of q, sigma, pa on xax, yax, and data (+err)
    fa = {'x': xax, 'data': data, 'err': err, 'f_Get_Iamp': f_Get_Iamp}

    if verbose:
        print "------ Starting the minimisation -------"
    result = mpfit(mpfitfun,
                   functkw=fa,
                   iterfunct=mpfitprint,
                   nprint=1,
                   parinfo=parinfo,
                   **fcnargs)
    ## Getting these best fit values into the dictionnary
    bestparinfo = [{
        'n': ii,
        'value': result.params[ii],
        'limits': [minpars[ii], maxpars[ii]],
        'limited': [limitedmin[ii], limitedmax[ii]],
        'fixed': fixed[ii],
        'parname': parnames[0] + str(ii),
        'error': ii
    } for ii in xrange(len(result.params))]

    ## Recompute the best amplitudes to output the right parameters
    ## And renormalising them
    Iamp = f_Get_Iamp(result.params, xax, data)
    Ibestpar_array = (np.vstack((Iamp, result.params.transpose()))).transpose()

    ## Getting rid of the non-relevant Gaussians
    ## If parameters factor_Chi2 is set we use it as a threshold to remove gaussians
    ## Otherwise we just remove the zeros
    if err is None: nerr = np.ones_like(data)
    else: nerr = err
    ## First get the Chi2 from this round
    bestChi2 = fitn1dgauss_chi2_err(Ibestpar_array, xax, data, nerr)
    result.ind = range(ngauss)

    k = 0
    Removed_Gaussians = []
    for i in xrange(ngauss):
        ## Derive the Chi2 WITHOUT the ith Gaussian
        newChi2 = fitn1dgauss_chi2_err(np.delete(Ibestpar_array, i, 0), xax,
                                       data, nerr)
        ## If this Chi2 is smaller than factor_Chi2 times the best value, then remove
        ## It just means that Gaussian is not an important contributor
        if newChi2 <= factor_Chi2 * bestChi2:
            val = bestparinfo.pop(k)
            result.ind.pop(k)
            Removed_Gaussians.append(i + 1)
        else:
            k += 1

    if veryverbose:
        if len(Removed_Gaussians) != 0:
            print "WARNING Removed Gaussians ", Removed_Gaussians
            print "WARNING: (not contributing enough to the fit)"
    ngauss = len(result.ind)

    ## New minimisation after removing all the non relevant Gaussians
    newresult = mpfit(mpfitfun,
                      functkw=fa,
                      iterfunct=mpfitprint,
                      nprint=1,
                      parinfo=bestparinfo,
                      **fcnargs)

    newresult.ind = range(ngauss)
    bestfit_params = newresult.params

    ## We add the Amplitudes to the array and renormalise them
    Iamp = f_Get_Iamp(bestfit_params, xax, data)
    Ibestfit_params = (np.vstack(
        (Iamp, bestfit_params.transpose()))).transpose()
    Ibestfit_params[:, 0] /= (sq2pi * Ibestfit_params[:, 1])
    ## And we sort them with Sigma
    Ibestfit_params = Ibestfit_params[Ibestfit_params[:, 1].argsort()]

    if newresult.status == 0:
        raise Exception(newresult.errmsg)

    if verbose:
        print "============================="
        print "FIT:      Imax       Sig   "
        print "============================="
        for i in xrange(ngauss):
            print "GAUSS %02d: %8.3e  %8.3f" % (i + 1, Ibestfit_params[i, 0],
                                                Ibestfit_params[i, 1])

        print "Chi2: ", newresult.fnorm, " Reduced Chi2: ", newresult.fnorm / len(
            data)

    resultMultiGaussian1D = BaseMultiGaussian1D(Ibestfit_params[:, 0],
                                                Ibestfit_params[:, 1])

    return resultMultiGaussian1D, newresult, resultMultiGaussian1D.evaluate(
        xax)
Beispiel #4
0
def fitGH_mpfit(xax, data, degGH=2, err=None, params=None, fixed=None, limitedmin=None, 
        limitedmax=None, minpars=None, maxpars=None, verbose=True, veryverbose=False, **fcnargs):
    """ Fitting routine for a Gauss-Hermite function using mpfit

    :param xax: x axis
    :param data: count axis
    :param err: error corresponding to data
    :param degGH: order of the Gauss-Hermite moments to fit. Must be >=2
        If =2 (default), the programme will only fit a single Gaussian
        If > 2, it will add as many h_n as needed.
    :param params: Input fit parameters: Vgauss, Sgauss, h3, h4, h5...
        Length should correspond to degGH. If not, a guess will be used.
    :param fixed: Is parameter fixed?
    :param limitedmin/minpars: set lower limits on each parameter (default: width>0)
    :param limitedmax/maxpars: set upper limits on each parameter
    :param iprint: if > 0 and verbose, print every iprint iterations of lmfit. default is 50
    :param lmfit_method: method to pass on to lmfit ('leastsq', 'lbfgsb', 'anneal').
        Default is leastsq (most efficient for the problem)
    :param **fcnargs: Will be passed to MPFIT, you can for example use xtol, gtol, ftol, quiet
    :param verbose: self-explanatory
    :param veryverbose: self-explanatory

    :returns   Fit parameters:
    :returns   Model:
    :returns   Fit errors:
    :returns   chi2:

    """
    ## Set up some default parameters for mpfit
    if "xtol" not in fcnargs.keys() : fcnargs["xtol"] = 1.e-10
    if "gtol" not in fcnargs.keys() : fcnargs["gtol"] = 1.e-10
    if "ftol" not in fcnargs.keys() : fcnargs["ftol"] = 1.e-10
    if "quiet" not in fcnargs.keys() : fcnargs["quiet"] = True

    ## If no coordinates is given, create them and use pixel as a unit
    ## The x axis will be from 0 to N-1, N being the number of data points
    ## which will be considered as "ordered" on a regular 1d grid
    if xax is None:
        xax = np.arange(len(data))
    else :
        ## Make sure that x is an array (if given)
        if not isinstance(xax,np.ndarray): 
            xax = (np.asarray(xax)).ravel()

    ## Compute the moments of the distribution for later purposes
    ## This provides Imax, V and Sigma from 1d moments
    momProf = moment1d(xax, data)

    if isinstance(params,np.ndarray): params=params.tolist()
    if params is not None : 
        if (len(params) > degGH): 
            print "ERROR: input parameter array (params) is larger than expected"
            print "ERROR: It is %d while it should be smaller or equal to %s (degGH)", len(params), degGH
            return 0., 0., 0., 0.
        elif (len(params) < degGH): 
            if verbose :
                print "WARNING: Your input parameters do not fit the Degre set up for the GH moments"
                print "WARNING: the given value of degGH (%d) will be kept ", degGH
                print "WARNING: A guess will be used for the input fitting parameters "

    default_params= np.zeros(degGH, dtype=np.float64) + 0.02
    default_params[:2] = momProf[1:]
    default_minpars= np.zeros(degGH, dtype=np.float64) - 0.2
    default_minpars[:2] = [np.min(xax), np.min(np.diff(xax)) / 3.]
    default_maxpars= np.zeros(degGH, dtype=np.float64) + 0.2
    default_maxpars[:2] = [np.max(xax), (np.max(xax) - np.min(xax)) / 3.]
    default_limitedmin = [True] * degGH
    default_limitedmax = [True] * degGH
    default_fixed = [False] * degGH

    ## Set up the default parameters if needed
    params = _set_GHparameters(params, default_params, degGH)
    fixed = _set_GHparameters(fixed, default_fixed, degGH)
    limitedmin = _set_GHparameters(limitedmin, default_limitedmin, degGH)
    limitedmax = _set_GHparameters(limitedmax, default_limitedmax, degGH)
    minpars = _set_GHparameters(minpars, default_minpars, degGH)
    maxpars = _set_GHparameters(maxpars, default_maxpars, degGH)

    ## -------------------------------------------------------------------------------------
    ## mpfit function which returns the residual from the best fit Gauss-Hermite
    ## Parameters are just V, Sigma, H3,... Hn - the amplitudes are optimised at each step
    ## -------------------------------------------------------------------------------------
    def mpfitfun(p, fjac=None, x=None, err=None, data=None):
        GH = np.concatenate(([1.0], p))
        ufit = GaussHermite(x, GH)
        GH[0] = _Solve_Amplitude(data, ufit, err)
        if err is None : 
            return [0, _fitGH_residuals(GH, x, data)]
        else :
            return [0, _fitGH_residuals_err(GH, x, data, err)]
    ## -------------------------------------------------------------------------------------
    ## Printing routine for mpfit
    ## -------------------------------------------------------------------------------------
    def mpfitprint(mpfitfun, p, iter, fnorm, functkw=None, parinfo=None, quiet=0, dof=None) :
        print "Chi2 = ", fnorm
    ## -------------------------------------------------------------------------------------

    ## Information about the parameters
    parnames = {0:"V", 1:"S"}
    for i in xrange(2, degGH) : parnames[i] = "H_%02d"%(i+1)

    ## Information about the parameters
    if veryverbose :
        print "--------------------------------------"
        print "GUESS:            "
        print "------"
        for i in xrange(degGH) :
            print " %s :  %8.3f"%(parnames[i], params[i])
        print "--------------------------------------"

    parinfo = [ {'n':ii, 'value':params[ii], 'limits':[minpars[ii],maxpars[ii]], 
        'limited':[limitedmin[ii],limitedmax[ii]], 'fixed':fixed[ii], 
        'parname':parnames[ii], 'error':ii} for ii in xrange(len(params)) ]

    ## Fit with mpfit of q, sigma, pa on xax, yax, and data (+err)
    fa = {'x': xax, 'data': data, 'err':err}

    if verbose: 
        print "------ Starting the minimisation -------"
    result = mpfit(mpfitfun, functkw=fa, iterfunct=mpfitprint, nprint=1, parinfo=parinfo, **fcnargs) 
    ## Recompute the best amplitudes to output the right parameters
    ## And renormalising them
    GH = np.concatenate(([1.0], result.params))
    ufit = GaussHermite(xax, GH)
    Iamp = _Solve_Amplitude(data, ufit, err)
    Ibestpar_array = np.concatenate(([Iamp], result.params))

    if result.status == 0:
        raise Exception(result.errmsg)

    if verbose :
        print "====="
        print "FIT: "
        print "================================="
        print "        I         V         Sig   "
        print "   %8.3f  %8.3f   %8.3f "%(Ibestpar_array[0], Ibestpar_array[1], Ibestpar_array[2])
        print "================================="
        for i in xrange(2, degGH) :
            print "GH %02d: %8.4f "%(i+1, Ibestpar_array[i+1])
        print "================================="

        print "Chi2: ",result.fnorm," Reduced Chi2: ",result.fnorm/len(data)

    return Ibestpar_array, result, GaussHermite(xax, Ibestpar_array)