forward_function ml_nnl, hessian, derivee2xy, ml_distfit
function ML_NNL, P
common ML_DISTFIT_CB, DATA, FN, CONSTRAINTP, CONSTRAINTFUNC
bignum=1e10
; if we're in a constrained part of parameter space, get the hell out!
if constraintp then if ~call_function(constraintfunc,p) then return, bignum
f = call_function(fn,data,p)
; if there are any negative values, get out of there.
if total(f lt 0.) gt 0 then return, bignum
; don't include zeros
nonzero = where(f ne 0.0,nok)
if nok gt 0 then nnl=-total(alog(f[nonzero])) else nnl=bignum
return, nnl
end
; uses derivee2xy to calculate partial derivatives, then inverts
; (based on MLEfit.pro)
pro hessian, name, param, correl
; compute the matrix elements
Ndim=N_elements(param)
temp=dblarr(Ndim,Ndim)
for i=0,Ndim-1 do begin
for j=i,Ndim-1 do begin
temp[i,j]=derivee2xy(name,param,i,j)
temp[j,i]=temp[i,j]
endfor
endfor
correl=invert(temp)
end
; calculate partial derivatives
; (based on MLEfit.pro, but with the brackets fixed up so it's readable)
function derivee2xy, name, xparam, nx, ny
common ML_DISTFIT_CB, DATA, FN, CONSTRAINTP, CONSTRAINTFUNC
temp=xparam
;
if (nx EQ ny) then begin
;
; 2nd derivative with respect to the same nx-ieme
; variable
;
h=abs(xparam[nx]*0.001d0)
if (h LT 0.01) then begin
h=0.01d0
endif
;
f00=call_function(name,temp)
;
temp[nx]=xparam[nx]+h
fp10=call_function(name,temp)
;
temp[nx]=xparam[nx]-h
fm10=call_function(name,temp)
;
return,(fp10+fm10-2.d0*f00)/h^2
endif else begin
;
; 2nd partial derivative with respect to the different
; variables
hx=abs(xparam[nx]*0.001d0)
hy=abs(xparam[ny]*0.001d0)
if (hx LT 0.01) then begin
hx=0.01d0
endif
if (hy LT 0.01) then begin
hy=0.01d0
endif
;
temp[nx]=xparam[nx]+hx
temp[ny]=xparam[ny]+hy
fp1p1=call_function(name,temp)
;
temp[nx]=xparam[nx]+hx
temp[ny]=xparam[ny]-hy
fp1m1=call_function(name,temp)
;
temp[nx]=xparam[nx]-hx
temp[ny]=xparam[ny]+hy
fm1p1=call_function(name,temp)
;
temp[nx]=xparam[nx]-hx
temp[ny]=xparam[ny]-hy
fm1m1=call_function(name,temp)
return,(fp1p1+fm1m1-fp1m1-fm1p1)/4.d0/hx/hy
endelse
end
;+
;
; NAME:
; ML_DISTFIT
;
; PURPOSE:
; Performs maximum likelihood fitting of a distribution.
;
; CATEGORY:
; Math
;
; CALLING SEQUENCE:
; ML_DISTFIT, X, Parm, Function_Name, ConfRegion
;
; INPUTS:
; X: Array of input data values. This is passed straight to the
; user-supplied function, so complicated data structures that
; encompass multi-dimensional information for each data
; point can be used.
;
; Parm: Variable containing initial guesses for parameters on input
; and best fit values on output.
;
; Function_Name: Name of user-supplied function defining the distribution.
; The function must accept 2 arguments, X and Parm, and
; return a vector containing the likelihood values for
; each data point in X for the point in parameter space
; given by Parm. The likelihood must be normalized so
; that its total integral over all possible values of X
; is a constant, regardless of Parm (it makes the most
; sense to normalize this integral to unity, but that
; is not strictly required).
;
; OPTIONAL OUTPUTS:
; ConfRegion: Lower and upper error estimates of each parameter,
; marginalized over the other parameters.
; I.e. ConfRegion[*,0] returns [low0,high0]
; where low0 <= parm[0] <= high0
;
; KEYWORD PARAMETERS:
; FITA: Vector of which parameters should be fit (1 for each
; parameter to be fit, 0 for each parameter to be held
; constant).
; THERE IS A BUG IN THE IMPLEMENTATION. DO NOT USE.
;
; CONSTRAINT: Name of a user-supplied function that takes a parameter
; vector as input and returns 1 if the point in parameter
; space is permitted and 0 if it is not permitted.
;
; LIKELIHOOD: Outputs an M-dimensional array with the likelihood
; values over the range of parameter space probed. M is
; the number of parameters that are fitted, which can be
; less than the length of Parm if FITA is used.
;
; LIKERANGE: 2xM dimensional array containing the bounds of the
; LIKELIHOOD array.
;
; EXAMPLE:
; Fit the width and offset of a zero-centered Gaussian plus constant
; distribution.
;
; First, define the distribution function:
; FUNCTION gauss_plus_const, X, Parm
; ; Parm[0]=constant offset, Parm[1]=width sigma
; vmax = 2000.
; normalization = Parm[1]*SQRT(!pi/2.)*ERF(vmax/(SQRT(2.)*Parm[1])) $
; + vmax*Parm[0]
; distribution = EXP(-X^2/(2.*Parm[1]^2)) + Parm[0]
; RETURN, distribution/normalization
; END
;
; Then generate some data that should adhere to this distribution,
; with a width of 250 and a constant term containing 10% of the points.
; IDL> data = [250*RANDOMN(seed, 900), 4000*(RANDOMU(seed, 100) - 0.5)]
;
; And finally fit the distribution:
; IDL> parm = [0., 100.]
; IDL> ML_DISTFIT, data, parm, 'gauss_plus_const', parmconf
; IDL> PRINT, parm
; 0.0625345 223.94577
; IDL> PRINT, parmconf
; 0.057511609 0.0973332
; 207.087 243.841
;
; MODIFICATION HISTORY:
; Written by: Jeremy Bailin. Thanks to the writers of MLEfit.pro, which
; furnished the Hessian routines, Peder Norberg for useful
; discussions, and Nicolas Petitclerc for additional testing.
; 27 Nov 2008 Release in JBIU.
;
;-
pro ML_DISTFIT, X, Parm, Function_Name, ConfRegion, FITA=FITA, $
LIKELIHOOD=LIKELIHOOD, LIKERANGE=LIKERANGE, CONSTRAINT=CONSTRAINT
common ML_DISTFIT_CB, DATA, FN, CONSTRAINTP, CONSTRAINTFUNC
ftol=1e-8
minfracerr=0.01
maxit=20
nptsperparmside=30
data=x
fn=function_name
nparm = (size(parm,/dimen))[0]
if n_elements(constraintfunc) eq 0 then constraintp=0 else begin
constraintp=1
constraintfunc=constraint
endelse
if n_elements(fita) gt 0 then begin
if n_elements(fita) ne nparm then message, 'FITA and PARM must have the same number of elements'
endif else begin
fita=replicate(1.0,nparm)
endelse
whichfitparms = where(fita ne 0, complement=whichconstparms, nfitparms, $
ncomp=nconstparms)
if nfitparms gt 0 then begin ; don't bother if we're not fitting anything
identmatrix=identity(nparm,/double)
; note that if nparm=1, this doesn't appear square to Powell
powell, parm, reform(identmatrix,nparm,nparm), ftol, fmin, 'ML_NNL', /DOUBLE
min_likelihood = ml_nnl(parm)
; initial estimate of confidence limits using covariance matrix
hessian, 'ML_NNL', parm, covar
sigma = sqrt(covar[indgen(nparm),indgen(nparm)])
; now do it for real using the full likelihood
; only do it for parameters that we're actually fitting
nsigslow = replicate(3.0,nfitparms)
nsigshigh = replicate(3.0,nfitparms)
likelihood=dblarr(replicate(2*nptsperparmside+1,nfitparms))
; if there are any that are ridiculously small sigmas, set them to minfracerr
lowsig = where((sigma lt minfracerr*parm) or (sigma ne sigma), ntoolow)
if ntoolow gt 0 then sigma[lowsig] = (minfracerr*parm)[lowsig]
incrementslow = replicate(1B,nfitparms) & ninclow=nfitparms
incrementshigh = replicate(1B,nfitparms) & ninchigh=nfitparms
for sigi=1,maxit do begin
lowrange = parm[whichfitparms] - sigma[whichfitparms]*nsigslow
highrange = parm[whichfitparms] + sigma[whichfitparms]*nsigshigh
deltaparmlow = nsigslow*sigma[whichfitparms]/nptsperparmside
deltaparmhigh = nsigshigh*sigma[whichfitparms]/nptsperparmside
indexmap = dblarr(nfitparms,2*nptsperparmside+1)
indexmap[*,0:nptsperparmside]=rebin(lowrange,nfitparms,nptsperparmside+1) + $
deltaparmlow#findgen(nptsperparmside+1)
indexmap[*,nptsperparmside:*]=rebin(parm[whichfitparms],nfitparms,nptsperparmside+1) $
+ deltaparmhigh#findgen(nptsperparmside+1)
likerange = [[lowrange],[highrange]]
for i=0L,n_elements(likelihood)-1 do begin
likind = array_indices(likelihood,i)
; don't bother recalculating sections of the likelihood array
; that haven't changed
skipcalc=1
if (ninclow gt 0) then begin
if total((likind le nptsperparmside)[incrementslow]) ne 0 then skipcalc=0
endif
if (ninchigh gt 0) then begin
if total((likind ge nptsperparmside)[incrementshigh]) ne 0 then skipcalc=0
endif
if skipcalc then continue
ptest = parm
ptest[whichfitparms] = indexmap[lindgen(nfitparms),likind]
likelihood[i] = ml_nnl(ptest)
endfor
xi21 = where(likelihood le min_likelihood+1.0)
xi21_ind = reform(array_indices(likelihood,xi21),nfitparms,n_elements(xi21))
confregion = fltarr(2,nparm)
minmaxinds = minmax(xi21_ind, dimen=2)
confregion[0,whichfitparms] = transpose(indexmap[lindgen(nfitparms),minmaxinds[0,*]])
confregion[1,whichfitparms] = transpose(indexmap[lindgen(nfitparms),minmaxinds[1,*]])
; if we haven't reached the edge of the confidence region in a given
; parameter, expand the range we're looking at
incrementslow = where(confregion[0,whichfitparms] le indexmap[*,0], ninclow, $
complement=noinclow)
if ninclow gt 0 then nsigslow[incrementslow] *= 3.0
incrementshigh = where(confregion[1,whichfitparms] ge indexmap[*,2*nptsperparmside], $
ninchigh, complement=noinchigh)
if ninchigh gt 0 then nsigshigh[incrementshigh] *= 3.0
if ninclow+ninchigh eq 0 then break
endfor
if sigi ge maxit then print, 'ML_DISTFIT: Maximum number of iterations reached. Errors are probably underestimated.'
end ;if there are fit parameters
; the confidence region of a constant parameter is just the value given
if nconstparms gt 0 then $
confregion[*,whichconstparms] = rebin(transpose(parm[whichconstparms]), $
2,nconstparms)
end