;PROCEDURE xfer_parameters
;PURPOSE:
; transfers values between an array and a structure.
; Used only by the FIT procedure.
;USAGE:
; xfer_parameters,struct,names,array,/array_to_struct
;or:
; xfer_parameters,struct,names,array,/struct_to_array
;
pro xfer_parameters,params,names, a, fullnames=fullnames,varname=varname, num_params=pos ,$
struct_to_array=struct_to_array, $
array_to_struct=array_to_struct
if size(/type,params) ne 8 then return
tags = tag_names(params)
if keyword_set(names) then begin
if size(/type,names) le 3 then p_names=tags[names] else begin
if size(/n_dimen,names) eq 0 then p_names=strsplit(names,' ',/extract) else p_names=names
endelse
endif else begin
; dts = data_type(params,/struct)
; w = where(dts eq 5 or dts eq 8,c)
; if c ne 0 then p_names = tags[w]
p_names = tags
endelse
a_to_s = keyword_set(array_to_struct)
np = n_elements(p_names)
if a_to_s eq 0 then a = 0.d
fullnames = ''
pos = 0
if not keyword_set(varname) then varname=''
for nstr =0,n_elements(params)-1 do begin
for n=0,np-1 do begin
name = strupcase(p_names[n])
dotpos = strpos(name,'.',0)
basename = strmid(name,0,uint(dotpos)) ; parse strings
if dotpos gt 0 then begin
subnames = strmid(name,dotpos+1,100)
subnames = strsplit(subnames,':',/extract)
endif else subnames = ''
parenpos1 = strpos(basename,'[',0)
if parenpos1 gt 0 then begin
parenpos2 = strpos(basename,']',parenpos1)
indnum= strmid(basename,parenpos1+1,parenpos2-parenpos1-1)
indnum= strsplit(indnum,',',/extract) ; returns an array!
indnum = fix(indnum)
basename2 = strmid(name,0,parenpos1)
endif else begin
indnum=0
basename2 = basename
endelse
ind = (where(basename2 eq tags,count))[0]
if count ge 1 then begin
v = params[nstr].(ind)
dt = size(/type,v)
if dt eq 8 then begin ; structure elements handled recursively
ns = n_elements(v)
if ns gt 1 then indnums = string(indgen(ns),format="('[',i0.0,']')") else indnums = ''
if size(/n_dimen,indnum) ne 0 then begin
v = v[indnum]
indnums = indnums[indnum]
endif else indnum = indgen(ns)
prefix = basename2+indnums+'.'
at = a[pos:*]
xfer_parameters,v,subnames,at,fullnames=elnames,num_p=ns ,array_to_struct=a_to_s,varname=prefix
if a_to_s then begin
; (params[nstr].(ind))[indnum] = v ; tough to get IDL to do this!
param = params[nstr]
vt = param.(ind)
for i=0, n_elements(indnum)-1 do vt[indnum[i]] = v[i]
param.(ind) = vt
params[nstr] = param
endif else a=[a,at]
fullnames = [fullnames,elnames]
pos = pos + ns
endif else begin ; extract numerical values
if not keyword_set(names) and dt ne 5 then continue
ns = n_elements(v)
if ns gt 1 then indnums = string(indgen(ns),format="('[',i0.0,']')") else indnums = ''
if size(/n_dimen,indnum) ne 0 then begin
v = v[indnum]
indnums = indnums[indnum]
endif else indnum = indgen(ns)
ns = n_elements(indnums)
if a_to_s then v = a[pos:pos+ns-1]
if a_to_s then params[nstr].(ind)[indnum] = v[*] else a=[a,v[*]]
fullnames = [fullnames,varname[nstr mod n_elements(varname)]+basename2+indnums]
pos = pos + ns
endelse
endif else dprint,verbose=verbose,dlevel=0,'index not found'
endfor
endfor
if pos gt 0 then begin
if a_to_s eq 0 then a = a[1:*]
fullnames= fullnames[1:*]
endif
return
end
;+
; NAME:
; FIT
;
; PURPOSE:
; Non-linear least squares fit to a user defined function.
; This procedure is an improved version of CURVEFIT that allows fitting
; to a subset of the function parameters.
; The function may be any non-linear function.
; If available, partial derivatives can be calculated by
; the user function, else this routine will estimate partial derivatives
; with a forward difference approximation.
;
; CATEGORY:
; E2 - Curve and Surface Fitting.
;
; CALLING SEQUENCE:
; FIT,X, Y, PARAMETERS=par, NAMES=string, $
; FUNCTION_NAME=string, ITMAX=ITMAX, ITER=ITER, TOL=TOL, $
; /NODERIVATIVE
;
; INPUTS:
; X: A row vector of independent variables. The FIT routine does
; not manipulate or use X, it simply passes X
; to the user-written function.
;
; Y: A row vector containing the dependent variable.
;
; KEYWORD INPUTS:
;
; FUNCTION_NAME: The name of the function to fit.
; If omitted, "FUNC" is used. The procedure must be written as
; described under RESTRICTIONS, below.
;
; PARAMETERS: A structure containing the starting parameter values
; for the function. Final values are also passed back through
; this variable. The fitting function must accept this keyword.
; If omitted, this structure is obtained from the user defined
; function.
;
; NAMES: The parameters to be fit. Several options exist:
; A string with parameter names delimited by spaces.
; A string array specifying which parameters to fit.
; An integer array corresponding to elements within the PARAMETERS structure.
; If undefined, then FIT will attemp to fit to all double precision
; elements of the PARAMETERS structure.
;
; WEIGHT: A row vector of weights, the same length as Y.
; For no weighting,
; w(i) = 1.0.
; For instrumental weighting,
; w(i) = 1.0/y(i), etc.
; if not set then w is set to all one's (equal weighting)
;
; DY: A row vector of errors in Y. If set, then WEIGHTS are set to:
; W = 1/DY^2 and previous values of the WEIGHTS are replaced.
;
; ERROR_FACTOR: set this keyword to have DY set to ERROR_FACTOR * Y.
;
; ITMAX: Maximum number of iterations. Default = 20.
;
; TOL: The convergence tolerance. The routine returns when the
; relative decrease in chi-squared is less than TOL in an
; interation. Default = 1.e-5.
;
; NODERIVATIVE: (optional)
; If set to 1 then the partial derivatives will be estimated in CURVEFIT using
; forward differences.
; If set to 0 then the user function is forced to provide
; partial derivatives.
; If not provided then partial derivatives will be determined
; from the user function only if it has the proper keyword
; arguments.
;
; VERBOSE: Verbose level (0: prints only errors, 1: prints results, 2: prints each iteration)
; (see "DPRINT" for more info)
; MAXPRINT: Maximum number of parameters to display while iterating
; (Default is 8)
; SILENT: Equivalent to VERBOSE=0
;
; KEYWORD OUTPUTS:
; ITER: The actual number of iterations which were performed.
;
; CHI2: The value of chi-squared on exit.
;
; FULLNAMES: A string array containing the parameter names.
;
; P_VALUES: A vector with same dimensions as FULLNAMES, that
; contains the final values for each parameter. These values
; will be the same as the values returned in PARAMETERS.
;
; P_SIGMA: A vector containing the estimated uncertainties in P_VALUES.
;
; FITVALUES: The fitted function values:
;
; OUTPUT
; Returns a vector of calculated values.
;
; COMMON BLOCKS:
; NONE.
;
; RESTRICTIONS:
; The function to be fit must be defined and called FUNC,
; unless the FUNCTION_NAME keyword is supplied. This function,
; must accept values of X (the independent variable), the keyword
; PARAMETERS, and return F (the function's value at X).
; if the NODERIV keyword is not set. then the function must also accept
; the keywords: P_NAMES and PDER (a 2d array of partial derivatives).
; For an example, see "GAUSS2".
;
; The calling sequence is:
;
; CASE 1: (NODERIV is set)
; F = FUNC(X,PAR=par) ; if NODERIV is set or:
; where:
; X = Variable passed into function. It is the job of the user-written
; function to interpret this variable. FIT does NOT use X.
; PAR = structure containing function parameters, input.
; F = Vector of NPOINT values of function, y(i) = funct(x), output.
;
; CASE 2: (NODERIV is not set)
; F = FUNC(X,PAR=par,NAMES=names,PDER=pder)
; where:
; NAMES = string array of parameters to be fit.
; PDER = Array, (NPOINT, NTERMS), of partial derivatives of FUNC.
; PDER(I,J) = Derivative of function at ith point with
; respect to jth parameter. Optional output parameter.
; PDER should not be calculated if P_NAMES is not
; supplied in call. If the /NODERIVATIVE keyword is set in the
; call to FIT then the user routine will never need to
; calculate PDER.
;
; PROCEDURE:
; Adapted from "CURVEFIT", least squares fit to a non-linear
; function, pages 237-239, Bevington, Data Reduction and Error
; Analysis for the Physical Sciences.
;
; "This method is the Gradient-expansion algorithm which
; combines the best features of the gradient search with
; the method of linearizing the fitting function."
;
; Iterations are performed until the chi square changes by
; only TOL or until ITMAX iterations have been performed.
;
; The initial guess of the parameter values should be
; as close to the actual values as possible or the solution
; may not converge.
;
;EXAMPLE: Fit to a gaussian plus a quadratic background:
; Here is the user-written procedure to return F(x) and the partials, given x:
;
;See the function "GAUSSIAN" for an example function to fit to.
;
;x=findgen(10)-4.5 ; Initialize independent variables.
;y=[1.7,1.9,2.1,2.7,4.6,5.5,4.4,1.7,0.5,0.3]; Initialize dependent variables.
;plot,x,y,psym=4 ; Plot data.
;xv = findgen(100)/10.-5. ; get better resolution abscissa.
;oplot,xv,gaussian(xv,par=p) ; Plot initial guess.
;help,p,/structure ; Display initial guess.
;fit,x,y,func='gaussian',par=p,fit=f ; Fit to all parameters.
;oplot,x,f,psym=1 ; Use '+' to plot fitted values.
;oplot,xv,gaussian(xv,par=p) ; Plot fitted function.
;help,p,/structure ; Display new parameter values.
;
;names =tag_names(p) ; Obtain parameter names.
;p.a2 = 0 ; set quadratic term to 0.
;names = names([0,1,2,3,4]) ; Choose a subset of parameters.
;print,names ; Display subset of names
;fit,x,y,func='gaussian',par=p,names=names ; Fit to subset.
;
; Please Note: Typically the initial guess for parameters must be reasonably
; good, otherwise the routine will not converge. In this example the data
; was selected so that the default parameters would converge.
;
;The following functions can be used with FIT:
; "gaussian", "polycurve", "power_law", "exponential", "gauss2", "igauss"
;
;KNOWN BUGS:
; Do NOT trust the P_SIGMA Values (uncertainty in the parameters) if the
; the value of flambda gets large. I believe
; That some error (relating to flambda) was carried over from CURVEFIT. -Davin
;
;MODIFICATION HISTORY:
; Function adapted from CURVEFIT Written, DMS, RSI, September, 1982
; and last modified by Mark Rivers, U. of Chicago, Febuary 12, 1995.
; Davin Larson, U of California, November 1995, MAJOR MODIFICATIONS:
; - Changed FUNCTION_NAME to a function (instead of procedure) that
; accepts a structure to hold the parameters. This makes the usage
; much more user friendly, and allows a subset of parameters to
; be fit.
; - Allowed vectors and recursively searched structures to be fit as well.
;-
pro fit, x, yt, $
FUNCTION_NAME = Function_Name, $
weight = w, $
dy =dy, $
error_factor = error_fac, $
parameters = params, $
names = p_names, $
p_values = a, $
p_sigma = sigma, $
p_limits = p_limits, $
fullnames = fullnames, $
itmax=itmax, iter=iter, tol=tol, chi2=chi2, $
maxprint=maxprint, $
noderivative=noderivative, $
logfit = logfit, $
debug = debug, $
testname = testname, $
minresolution = minres, $
result = result, $
fitvalues = fitvalues, $
overplot = overplot, $
silent = silent, $
verbose = verbose, $
summary = summary, $
qflag = qflag ;added ajh
common fit_com, function_name_com,b,c,d,e
str_element,params,'func',function_name_com
if keyword_set(function_name) then function_name_com=function_name
if not keyword_set(function_name_com) then function_name_com="FUNC"
logf = keyword_set(logfit)
fitvalues = 0
result = 0
qflag = 0 ;added ajh
; SET ALL DEFAULTS:
;Name of function to fit
if n_elements(tol) eq 0 then tol = 1.e-6 ;Convergence tolerance
if n_elements(itmax) eq 0 then itmax = 20 ;Maximum # iterations
chi2= !values.f_nan
;derstr +=" parameter names= '"+string(strupcase(p_names),/print)+"'"
;check for derivative option:
if n_elements(NODERIVATIVE) eq 0 then begin
wh = where(routine_info(/functions) eq strupcase(function_name_com), compiled)
if compiled eq 0 then resolve_routine,/is_function,function_name_com
args = routine_info(function_name_com,/funct,/param)
kw_args = strmid(args.kw_args,0,4)
wh = where((kw_args eq 'PDER') or (kw_args eq 'P_NA'),c)
NODERIVATIVE = c ne 2
endif
if keyword_set(silent) then verbose = 0
derstr = "FUNCTION= '"+strupcase(function_name_com)+"' "
derstr +='Partial derivatives computed '+ [ 'analytically','numerically' ]
if keyword_set(dy) then derstr += ' (weighted fit)'
printdat,p_names,/value,output=str
dprint,verbose=verbose,dlevel=1,derstr[NODERIVATIVE]+' '+str
; If we will be estimating partial derivatives then compute machine
; precision
if NODERIVATIVE then begin
res = machar(DOUBLE=1)
eps = sqrt(res.eps)
endif
;Get params structure if not defined
if not keyword_set(params) then $
yfit = call_function( Function_name_com, param=params)
type = size(/type,params)
if type ne 8 then begin
message,'PARAMETERS must be a structure.'
endif
if n_elements(yt) eq 0 then begin
dprint,dlevel=1,'No data to fit to!'
return
endif
; wh = where(finite(yt),ngood)
; if ngood ne n_elements(yt) then begin
; if ngood eq 0 then begin
; message,/info,'No valid data!'
; return
; endif
; message,/info,'Warning! Invalid data is ignored.'
; x = x
; endif
y = yt[*]
if keyword_set(error_fac) then dy = error_fac*y
if keyword_set(dy) then begin
if logf then w = (y/dy[*])^2 else w=1/(dy[*])^2
endif
if logf then y = alog(y)
if n_elements(w) eq 0 then w = replicate(1.d, n_elements(y) )
flambda = 0.001 ;Initial lambda
if not keyword_set(maxprint) then maxprint = 10
nterms_last = 0
nformat='(a4,40(" ",a11))'
gformat='(a4,40(" ",g11.4))'
vformat='(i3,":",20(" ",g11.4))'
sformat='("Unc:",20(" ",g11.4))'
xfer_parameters,params,p_names,a,fullnames=fullnames,num_p=nterms,/struct_to_array
if nterms eq 0 then begin
message,/info,'No parameters to fit'
return
endif
if keyword_set(p_limits) then begin
xfer_parameters,p_limits[0],p_names,a_min,/struct_to_array
xfer_parameters,p_limits[1],p_names,a_max,/struct_to_array
a = a_min > a < a_max
xfer_parameters,params,p_names,a,/array_to_struct
dprint,dlevel=3,'LLIM',0.,a_min,format = gformat ; 'Limiting parameter range'
dprint,dlevel=3,'ULIM',0.,a_max,format = gformat ; 'Limiting parameter range'
endif else begin
a_min = replicate( -!values.d_infinity, nterms)
a_max = replicate( !values.d_infinity, nterms)
p_min= params
xfer_parameters,p_min,p_names,a_min,/array_to_struct
p_max= params
xfer_parameters,p_max,p_names,a_max,/array_to_struct
p_limits = [p_min,p_max]
endelse
for iter = 1, itmax do begin ; Iteration loop
xfer_parameters,params,p_names,a,fullnames=fullnames,num_p=nterms,/struct_to_arr
pder = dblarr(n_elements(y),nterms)
if keyword_set(NODERIVATIVE) then begin ; Evaluate function and estimate partial derivatives
yfit = (call_function( Function_name_com, x, param=params))(*)
if logf then yfit = alog(yfit)
xfer_parameters,params,p_names,a,/struct_to_array
for term=0, nterms-1 do begin
p = a ; Copy current parameters
; Increment size for forward difference derivative
inc = eps * abs(p[term])
if (inc eq 0.) then inc = eps
if keyword_set(minres) then inc = inc > minres
p[term] = p[term] + inc
tparams = params
xfer_parameters,tparams,p_names,p,/array_to_struct
yfit1 = (call_function( Function_name_com, x, param=tparams))(*)
if logf then yfit1 = alog(yfit1)
pder[*,term] = (yfit1-yfit)/inc
endfor
endif else begin ; The user's procedure will compute partial derivatives
yfit = (call_function(Function_name_com, x, param=params, $
p_na=fullnames,pder = pder))(*)
if logf then begin
pder = pder / (yfit # replicate(1.,nterms) )
yfit = alog(yfit)
endif
xfer_parameters,params,p_names,a,/struct_to_array
endelse
mp = (nterms < maxprint)-1
if nterms ne nterms_last then $
dprint,verbose=verbose,dlevel=1,'Iter','Chi',fullnames[0:mp],'Lambda',format=nformat
nterms_last = nterms
if not keyword_set(testname) then begin
pderthresh = 1e-12
pdernz = total(abs(pder) gt pderthresh,1) gt 0
wpdernz = where(pdernz,npdernz)
wpderaz = where(pdernz eq 0,nz)
if npdernz ne nterms then begin
dprint,verbose=verbose,'Warning: Not fitting the following parameters: ',fullnames[wpderaz]
endif
endif else begin
wpdernz=indgen(nterms)
npdernz = nterms
endelse
if npdernz le 0 then begin
dprint,verbose=verbose,dlevel=0,'No free parameters to fit!'
return
endif
nfree = n_elements(y) - npdernz ; Degrees of freedom
if nfree lt 0 then dprint,verbose=verbose,dlevel=0, 'Not enough data points.'
if nfree eq 0 then begin
dprint,verbose=verbose,dlevel=1,'Warning: No Degrees of Freedom'
nfree = 0.5d
endif
diag = lindgen(npdernz)*(npdernz+1) ; Subscripts of diagonal elements
; Evaluate alpha and beta matricies.
ds = ((y-yfit)*w)[*]
if npdernz gt 1 then beta = ds # pder[*,wpdernz] $
else beta = [total(ds * pder[*,wpdernz])]
alpha = transpose(pder[*,wpdernz]) # (w[*] # replicate(1.,npdernz) * pder[*,wpdernz])
chisq1 = total(w*(y-yfit)^2)/nfree ; Present chi squared.
dprint,verbose=verbose,dlevel=2,strtrim(iter,2),sqrt(chisq1),a[0:mp],flambda,format=gformat
; If a good fit, no need to iterate
all_done = chisq1 lt total(abs(y))/1e7/nfree
;
; Invert modified curvature matrix to find new parameters.
flambda=1e-5
tparams = params
repeat begin
flambda = flambda*10.
c = sqrt(alpha[diag] # alpha[diag])
array = alpha/c
array[diag] = array[diag]*(1.+flambda)
; if nterms gt 1 then array = invert(array) $
; else array = 1/array
array = invert(array)
b = a
b[wpdernz] = a[wpdernz]+ array/c # transpose(beta) ; New params
b = a_min > b < a_max ; limit range of parameters
xfer_parameters,tparams,p_names,b,/array_to_struct
yfit = (call_function( Function_name_com, x, param=tparams))(*)
if logf then yfit = alog(yfit)
chisqr = total(w*(y-yfit)^2)/nfree ; New chisqr
if finite(chisqr) eq 0 then begin
qflag = 4 ;added ajh
dprint,dlevel=0,verbose=verbose,'Invalid Data or Parameters in function '+function_name_com+' Aborting'
dprint,dlevel=0,verbose=verbose,iter,sqrt(chisq1),b[0:mp],format=vformat
; dprint,'Determinant=',determ(array,/check)
if keyword_set(debug) then stop
goto,done2
endif
if flambda ge 1e5 then begin
qflag = 2 ;added ajh
dprint,dlevel=1,verbose=verbose,'flambda too large (',flambda,') for ',function_name_com,' Aborting'
dprint,dlevel=1,verbose=verbose,iter,sqrt(chisq1),b[0:mp],format=vformat
; print,'Determinant=',determ(array,/check)
if keyword_set(debug) then stop
goto,done2
endif
; if all_done then goto, done
if keyword_set(debug) then stop
endrep until chisqr le chisq1
; flambda = flambda/10. ; Decrease flambda by factor of 10
a=b ; Save new parameter estimate.
params = tparams
if ((chisq1-chisqr)/chisq1) le tol then goto,done ; Finished?
endfor ;iteration loop
qflag = 3 ;added ajh
dprint,verbose=verbose,dlevel=1, 'Failed to converge'
done2:
done:
sigma = replicate(!values.d_nan,nterms)
sigma[wpdernz] = sqrt(array[diag]/alpha[diag] * sqrt(chisqr)) ; Return sigma's
; sigma[wpdernz] = sqrt(array[diag]/alpha[diag] * (1+flambda)) ; Return sigma's
; sigma[wpdernz] = sqrt((invert(alpha/c))[diag]/alpha[diag] ) ; Return sigma's
chi2 = chisqr ; Return chi-squared
; IF iter NE itmax+1 THEN qflag = 1 ;added ajh
dprint,verbose=verbose,dlevel=1,strtrim(iter+1,2),sqrt(chi2),a[0:mp],flambda,format=gformat
dprint,verbose=verbose,dlevel=1,'Unc:',sqrt(chi2),sigma[0:mp],flambda,format=gformat
dprint,verbose=verbose,dlevel=4,'Chi2 =',chi2,' flambda =',flambda
if logf then yfit = exp(yfit)
fitvalues = yfit
if arg_present(summary) then begin
summary ={ p_names:fullnames, p_values:a, p_sigma:sigma, chi:sqrt(chi2), its:iter, qflag:qflag}
endif
if arg_present(result) then begin
nul_params = fill_nan(params)
dparams = nul_params
xfer_parameters,dparams,p_names,sigma,/array_to_struct
; if size(/type,result) eq 8 then begin
; result.par = params
; result.dpar = dparams
; result.chi2 = chi2
; result.its = iter
; result.qflag = qflag
; endif else begin
result = {par:params, dpar:dparams, $
chi:sqrt(chi2), its:iter , qflag:qflag }
; endelse
if qflag ge 4 then begin
result.par = nul_params
result.dpar = nul_params
endif
endif
if keyword_set(overplot) then begin
xv = dgen()
oplot,xv,call_function(Function_name_com,xv, param=params)
endif
return
END