#pragma TextEncoding = "UTF-8"
#pragma rtGlobals=3		// Use modern global access method and strict wave access.
#pragma IgorVersion = 6.2
#pragma ModuleName = PearlFitFuncs
#pragma version = 1.02
#include "mm-physconst"

/// @file
/// @brief various fit functions for photoelectron spectroscopy.
/// @ingroup ArpesPackage
///
/// this procedure contains various functions for curve fitting.
///
/// @author matthias muntwiler, matthias.muntwiler@psi.ch
///
/// @copyright 2013-18 Paul Scherrer Institut @n
/// Licensed under the Apache License, Version 2.0 (the "License"); @n
///   you may not use this file except in compliance with the License. @n
///   You may obtain a copy of the License at
///   http://www.apache.org/licenses/LICENSE-2.0

/// @namespace PearlFitFuncs
/// @brief various fit functions for photoelectron spectroscopy.
///
/// PearlFitFuncs is declared in @ref pearl-fitfuncs.ipf.


//------------------------------------------------------------------------------
// Gaussian shapes
//------------------------------------------------------------------------------

/// multiple gaussian peaks on a linear background fit function.
///
/// @note FWHM = width * 2 * sqrt(ln(2)) = width * 1.665
///
/// @param	w		shape parameters.
///					the length of the wave defines the number of peaks.
///	@arg	w[0] = constant coefficient of background
///	@arg	w[1] = linear coefficient of background
///	@arg	w[2 + (i-1) * 3] = amplitude of peak i
///	@arg	w[3 + (i-1) * 3] = position of peak i
///	@arg	w[4 + (i-1) * 3] = width of peak i (see note)
/// @param	x		independent variable
///
threadsafe function MultiGaussLinBG(w,x) : FitFunc
	wave w
	variable x

	variable np = numpnts(w)
	variable ip
	
	variable v = w[0] + x * w[1]
	for (ip = 2; ip < np; ip += 3)
		v += w[ip] * exp( -( (x - w[ip+1]) / w[ip+2] )^2 )
	endfor

	return v
end

/// multiple gaussian peaks on a linear background fit function (all at once).
///
/// this is the all-at-once version of @ref MultiGaussLinBG.
/// it runs about 15% faster compared to the point-by-point function
/// (measured on a 200 point spectrum with 3 peaks).
///
/// @note FWHM = width * 2 * sqrt(ln(2)) = width * 1.665
///
/// @param	pw		shape parameters.
///					the length of the wave defines the number of peaks.
///	@arg	pw[0] = constant coefficient of background
///	@arg	pw[1] = linear coefficient of background
///	@arg	pw[2 + (i-1) * 3] = amplitude of peak i
///	@arg	pw[3 + (i-1) * 3] = position of peak i
///	@arg	pw[4 + (i-1) * 3] = width of peak i (see note)
///
/// @param	yw		y (dependent) values.
///
/// @param	xw		x (independent) independent values.
///
threadsafe function MultiGaussLinBG_AO(pw, yw, xw) : FitFunc
	wave pw
	wave yw
	wave xw

	variable np = numpnts(pw)
	variable ip
	
	yw = pw[0] + xw[p] * pw[1]
	for (ip = 2; ip < np; ip += 3)
		yw += pw[ip] * exp( -( (xw[p] - pw[ip+1]) / pw[ip+2] )^2 )
	endfor
end

/// doublet gaussian peaks on a linear background fit function (all at once).
///
/// this fits two gaussian peaks.
/// peak positions are specified by center and distance rather than individually.
/// amplitude and width are specified as absolute values for the upper peak (in x),
/// and relative values for the lower peak.
///
/// @note FWHM = width * 2 * sqrt(ln(2)) = width * 1.665
///
/// @param	pw		shape parameters.
///					the length of the wave defines the number of peaks.
///	@arg	pw[0] = constant coefficient of background
///	@arg	pw[1] = linear coefficient of background
///	@arg	pw[2] = amplitude of peak 1 (higher x)
///	@arg	pw[3] = amplitude of peak 2 relative to peak 1
///	@arg	pw[4] = center position
///	@arg	pw[5] = distance between peaks (splitting)
///	@arg	pw[6] = width of peak 1 (see note)
///	@arg	pw[7] = width of peak 2 relative to peak 1
///
/// @param	yw		y (dependent) values.
///
/// @param	xw		x (independent) independent values.
///
threadsafe function DoubletGaussLinBG_AO(pw, yw, xw) : FitFunc
	wave pw
	wave yw
	wave xw

	yw = pw[0] + xw[p] * pw[1]
	yw += pw[2] * exp( -( (xw[p] - pw[4] - pw[5] /2) / pw[6] )^2 )
	yw += pw[2] * pw[3] * exp( -( (xw[p] - pw[4] + pw[5] /2) / pw[6] / pw[7] )^2 )
end

/// two doublet gaussian peaks on a linear background fit function (all at once).
///
/// this fits four gaussian peaks.
/// peak positions are specified by center, splitting and shift rather than individually.
/// amplitudes are specified as absolute values for peaks 1 and 3,
/// and relative values for peaks 2 and 4.
///
/// can be used if a spin-orbit doublet is split by a chemical shift
/// which affects both spin-orbit peaks equally.
///
/// @note FWHM = width * 2 * sqrt(ln(2)) = width * 1.665
///
/// @param	pw		shape parameters.
///		@arg	pw[0] = constant coefficient of background
///		@arg	pw[1] = linear coefficient of background
///		@arg	pw[2] = amplitude of peak 1
///		@arg	pw[3] = amplitude of peak 2
///		@arg	pw[4] = amplitude of peak 3, relative to peak 1
///		@arg	pw[5] = amplitude of peak 4, relative to peak 2
///		@arg	pw[6] = position of peak 1
///		@arg	pw[7] = splitting (distance between peaks 1 and 3)
///		@arg	pw[8] = shift (distance between peaks 1 and 2)
///		@arg	pw[9] = width of peaks 1 and 3
///		@arg	pw[10] = width of peaks 2 and 4
///
/// @param	yw		y (dependent) values.
///
/// @param	xw		x (independent) independent values.
///
threadsafe function DblDoubletGaussLinBG_AO(pw, yw, xw) : FitFunc
	wave pw
	wave yw
	wave xw

	yw = pw[0] + xw[p] * pw[1]
	// peak 1
	yw += pw[2] * exp( -( (xw[p] - pw[6]) / pw[9] )^2 )
	// peak 2
	yw += pw[3] * exp( -( (xw[p] - pw[6] - pw[8]) / pw[10] )^2 )
	// peak 3
	yw += pw[2] * pw[4] * exp( -( (xw[p] - pw[6] - pw[7]) / pw[9] )^2 )
	// peak 4
	yw += pw[3] * pw[5] * exp( -( (xw[p] - pw[6] - pw[7] - pw[8]) / pw[10] )^2 )
end

//------------------------------------------------------------------------------
// Voigt shapes
//------------------------------------------------------------------------------

/// multiple voigt peaks on a linear background fit function.
///
///
/// @param	w		shape parameters.
///					the length of the wave defines the number of peaks.
///	@arg	w[0] = constant coefficient of background
///	@arg	w[1] = linear coefficient of background
///	@arg	w[2 + (i-1) * 4] = amplitude of peak i
///	@arg	w[3 + (i-1) * 4] = position of peak i
///	@arg	w[4 + (i-1) * 4] = width of peak i
///	@arg	w[5 + (i-1) * 4] = shape of peak i
/// @param	x		independent variable
///
threadsafe function MultiVoigtLinBG(w,x) : FitFunc
	wave w
	variable x

	variable np = numpnts(w)
	variable ip
	
	variable v = w[0] + x * w[1]
	for (ip = 2; ip < np; ip += 4)
		v += w[ip] * VoigtFunc((x - w[ip+1]) / w[ip+2], w[ip+3])
	endfor

	return v
end

/// multiple voigt peaks on a linear background fit function.
///
///
/// this is the all-at-once version of @ref MultiVoigtLinBG.
/// it runs slightly faster compared to the point-by-point function.
///
/// @param	pw		shape parameters.
///					the length of the wave defines the number of peaks.
///	@arg	pw[0] = constant coefficient of background
///	@arg	pw[1] = linear coefficient of background
///	@arg	pw[2 + (i-1) * 4] = amplitude of peak i
///	@arg	pw[3 + (i-1) * 4] = position of peak i
///	@arg	pw[4 + (i-1) * 4] = width of peak i
///	@arg	pw[5 + (i-1) * 4] = shape of peak i
///
/// @param	yw		y (dependent) values.
///
/// @param	xw		x (independent) independent values.
///
threadsafe function MultiVoigtLinBG_AO(pw, yw, xw) : FitFunc
	wave pw
	wave yw
	wave xw

	variable np = numpnts(pw)
	variable ip
	
	yw = pw[0] + xw[p] * pw[1]
	for (ip = 2; ip < np; ip += 4)
		yw += pw[ip] * VoigtFunc((xw[p] - pw[ip+1]) / pw[ip+2], pw[ip+3])
	endfor
end


//------------------------------------------------------------------------------
// Doniach-Sunjic shapes
//------------------------------------------------------------------------------

/// Doniach-Sunjic line shape
///
/// [S. Doniach, M. Sunjic, J. Phys. C 3 (1970) 285]
///
/// @param	x		independent variable
/// @param	amp		amplitude
/// @param	pos		position
/// @param	sing		singularity index (0 <= sing < 1)
/// @param	fwhm	full width at half maximum
///
threadsafe function DoniachSunjic(x, amp, pos, sing, fwhm)
	variable x
	variable amp
	variable pos
	variable sing
	variable fwhm

	variable nom, denom
	nom = cos(pi * sing / 2 + (1 - sing) * atan((x - pos) / fwhm * 2))
	denom = ((x - pos)^2 + fwhm^2 / 4)^((1 - sing) / 2)
	
	return amp * nom / denom * fwhm / 2
end

/// multiple doniach-sunjic peaks on a linear background fit function.
///
///
/// @param	w		shape parameters.
///					the length of the wave defines the number of peaks.
///	@arg	w[0] = constant coefficient of background
///	@arg	w[1] = linear coefficient of background
///	@arg	w[2 + (i-1) * 4] = amplitude of peak i
///	@arg	w[3 + (i-1) * 4] = position of peak i
///	@arg	w[4 + (i-1) * 4] = width (fwhm) of peak i
///	@arg	w[5 + (i-1) * 4] = singularity index (0...1) of peak i
/// @param	x		independent variable
///
threadsafe function MultiDoniachSunjicLinBG(w,x) : FitFunc
	wave w
	variable x

	variable np = numpnts(w)
	variable ip
	
	variable v = w[0] + x * w[1]
	for (ip = 2; ip < np; ip += 4)
		v += DoniachSunjic(x, w[ip], w[ip+1], w[ip+3], w[ip+2])
	endfor

	return v
end


threadsafe function ds1_bg(w, x): FitFunc
	// Doniach-Sunjic fit function
	// 0 <= sing < 1
	wave w	// coefficients - see below
	variable x	// independent variable

	//CurveFitDialog/ These comments were created by the Curve Fitting dialog. Altering them will
	//CurveFitDialog/ make the function less convenient to work with in the Curve Fitting dialog.
	//CurveFitDialog/ Equation:
	//CurveFitDialog/ f(x) = DoniachSunjic(x, amp, pos, sing, fwhm) + bg
	//CurveFitDialog/ End of Equation
	//CurveFitDialog/ Independent Variables 1
	//CurveFitDialog/ x
	//CurveFitDialog/ Coefficients 5
	//CurveFitDialog/ w[0] = bg
	//CurveFitDialog/ w[1] = amp
	//CurveFitDialog/ w[2] = pos
	//CurveFitDialog/ w[3] = sing
	//CurveFitDialog/ w[4] = FWHM

	return DoniachSunjic(x, w[1], w[2], w[3], w[4]) + w[0]	
end

threadsafe function ds2_bg(w,x) : FitFunc
	Wave w
	Variable x

	//CurveFitDialog/ These comments were created by the Curve Fitting dialog. Altering them will
	//CurveFitDialog/ make the function less convenient to work with in the Curve Fitting dialog.
	//CurveFitDialog/ Equation:
	//CurveFitDialog/ f(x) = w_0+( w_1*cos(pi*w_3/2+(1-w_3)*atan((x-w_2)/w_4)))/(((x-w_2)^2+w_4^2)^((1-w_3)/2))   +(w_5*cos(pi*w_7/2+(1-w_7)*atan((x-(w_6))/w_8)))/(((x-w_6)^2+w_8^2)^((1-w_7)/2))
	//CurveFitDialog/ End of Equation
	//CurveFitDialog/ Independent Variables 1
	//CurveFitDialog/ x
	//CurveFitDialog/ Coefficients 9
	//CurveFitDialog/ w[0] = bg
	//CurveFitDialog/ w[1] = amp1
	//CurveFitDialog/ w[2] = pos1
	//CurveFitDialog/ w[3] = sing1
	//CurveFitDialog/ w[4] = wid1
	//CurveFitDialog/ w[5] = amp2
	//CurveFitDialog/ w[6] = pos2
	//CurveFitDialog/ w[7] = sing2
	//CurveFitDialog/ w[8] = wid2

	variable ds1 = DoniachSunjic(x, w[1], w[2], w[3], w[4])
	variable ds2 = DoniachSunjic(x, w[5], w[6], w[7], w[8])
	
	return w[0] + ds1 + ds2
End

Function ds4_bg(w,x) : FitFunc
	Wave w
	Variable x

	//CurveFitDialog/ These comments were created by the Curve Fitting dialog. Altering them will
	//CurveFitDialog/ make the function less convenient to work with in the Curve Fitting dialog.
	//CurveFitDialog/ Equation:
	//CurveFitDialog/ f(x) = w_0+( w_1*cos(pi*w_3/2+(1-w_3)*atan((x-w_2)/w_4)))/(((x-w_2)^2+w_4^2)^((1-w_3)/2))   +(w_5*cos(pi*w_7/2+(1-w_7)*atan((x-(w_6))/w_8)))/(((x-w_6)^2+w_8^2)^((1-w_7)/2))   +( w_9*cos(pi*w_11/2+(1-w_11)*atan((x-w_10)/w_12)))/(((x-w_10)^2+w_12^2)^((1-w_11)/2))      +( w_13*cos(pi*w_15/2+(1-w_15)*atan((x-w_14)/w_16)))/(((x-w_14)^2+w_16^2)^((1-w_15)/2))   
	//CurveFitDialog/ End of Equation
	//CurveFitDialog/ Independent Variables 1
	//CurveFitDialog/ x
	//CurveFitDialog/ Coefficients 17
	//CurveFitDialog/ w[0] = w_0
	//CurveFitDialog/ w[1] = w_11
	//CurveFitDialog/ w[2] = w_12
	//CurveFitDialog/ w[3] = w_13
	//CurveFitDialog/ w[4] = w_14
	//CurveFitDialog/ w[5] = w_21
	//CurveFitDialog/ w[6] = w_22
	//CurveFitDialog/ w[7] = w_23
	//CurveFitDialog/ w[8] = w_24
	//CurveFitDialog/ w[9] = w_31
	//CurveFitDialog/ w[10] = w_32
	//CurveFitDialog/ w[11] = w_33
	//CurveFitDialog/ w[12] = w_34
	//CurveFitDialog/ w[13] = w_41
	//CurveFitDialog/ w[14] = w_42
	//CurveFitDialog/ w[15] = w_43
	//CurveFitDialog/ w[16] = w_44
	Variable ds1, ds2, ds3, ds4
	ds1=( w[1]*cos(pi*w[3]/2+(1-w[3])*atan((x-w[2])/w[4])))/(((x-w[2])^2+w[4]^2)^((1-w[3])/2))
	ds2=( w[5]*cos(pi*w[7]/2+(1-w[7])*atan((x-w[6])/w[8])))/(((x-w[6])^2+w[8]^2)^((1-w[7])/2))
	ds3=( w[9]*cos(pi*w[11]/2+(1-w[11])*atan((x-w[10])/w[12])))/(((x-w[10])^2+w[12]^2)^((1-w[11])/2))
	ds4=( w[13]*cos(pi*w[15]/2+(1-w[15])*atan((x-w[14])/w[16])))/(((x-w[14])^2+w[16]^2)^((1-w[15])/2))

	
	return w[0]+ds1+ds2+ds3+ds4
	

End

Function ds6_bg(w,x) : FitFunc
	Wave w
	Variable x

	//CurveFitDialog/ These comments were created by the Curve Fitting dialog. Altering them will
	//CurveFitDialog/ make the function less convenient to work with in the Curve Fitting dialog.
	//CurveFitDialog/ Equation:
	//CurveFitDialog/ 
	//CurveFitDialog/ Variable g, ds1, ds2, ds3, ds4, ds5, ds6
	//CurveFitDialog/ ds1=( w_11*cos(pi*w_13/2+(1-w_13)*atan((x-w_12)/w_14)))/(((x-w_12)^2+w_14^2)^((1-w_13)/2))
	//CurveFitDialog/ ds2=( w_21*cos(pi*w_23/2+(1-w_23)*atan((x-w_22)/w_24)))/(((x-w_22)^2+w_24^2)^((1-w_23)/2))
	//CurveFitDialog/ ds3=( w_31*cos(pi*w_33/2+(1-w_33)*atan((x-w_32)/w_34)))/(((x-w_32)^2+w_34^2)^((1-w_33)/2))
	//CurveFitDialog/ ds4=( w_41*cos(pi*w_43/2+(1-w_43)*atan((x-w_42)/w_44)))/(((x-w_42)^2+w_44^2)^((1-w_43)/2))
	//CurveFitDialog/ ds5=( w_51*cos(pi*w_53/2+(1-w_53)*atan((x-w_52)/w_54)))/(((x-w_52)^2+w_54^2)^((1-w_53)/2))
	//CurveFitDialog/ ds6=( w_61*cos(pi*w_63/2+(1-w_63)*atan((x-w_62)/w_64)))/(((x-w_62)^2+w_64^2)^((1-w_63)/2))
	//CurveFitDialog/ 
	//CurveFitDialog/ f(x) =w_0+ds1+ds2+ds3+ds4+ds5+ds6
	//CurveFitDialog/ 
	//CurveFitDialog/ End of Equation
	//CurveFitDialog/ Independent Variables 1
	//CurveFitDialog/ x
	//CurveFitDialog/ Coefficients 25
	//CurveFitDialog/ w[0] = w_0
	//CurveFitDialog/ w[1] = w_11
	//CurveFitDialog/ w[2] = w_12
	//CurveFitDialog/ w[3] = w_13
	//CurveFitDialog/ w[4] = w_14
	//CurveFitDialog/ w[5] = w_21
	//CurveFitDialog/ w[6] = w_22
	//CurveFitDialog/ w[7] = w_23
	//CurveFitDialog/ w[8] = w_24
	//CurveFitDialog/ w[9] = w_31
	//CurveFitDialog/ w[10] = w_32
	//CurveFitDialog/ w[11] = w_33
	//CurveFitDialog/ w[12] = w_34
	//CurveFitDialog/ w[13] = w_41
	//CurveFitDialog/ w[14] = w_42
	//CurveFitDialog/ w[15] = w_43
	//CurveFitDialog/ w[16] = w_44
	//CurveFitDialog/ w[17] = w_51
	//CurveFitDialog/ w[18] = w_52
	//CurveFitDialog/ w[19] = w_53
	//CurveFitDialog/ w[20] = w_54
	//CurveFitDialog/ w[21] = w_61
	//CurveFitDialog/ w[22] = w_62
	//CurveFitDialog/ w[23] = w_63
	//CurveFitDialog/ w[24] = w_64

	
	Variable ds1, ds2, ds3, ds4, ds5, ds6
	ds1=( w[1]*cos(pi*w[3]/2+(1-w[3])*atan((x-w[2])/w[4])))/(((x-w[2])^2+w[4]^2)^((1-w[3])/2))
	ds2=( w[5]*cos(pi*w[7]/2+(1-w[7])*atan((x-w[6])/w[8])))/(((x-w[6])^2+w[8]^2)^((1-w[7])/2))
	ds3=( w[9]*cos(pi*w[11]/2+(1-w[11])*atan((x-w[10])/w[12])))/(((x-w[10])^2+w[12]^2)^((1-w[11])/2))
	ds4=( w[13]*cos(pi*w[15]/2+(1-w[15])*atan((x-w[14])/w[16])))/(((x-w[14])^2+w[16]^2)^((1-w[15])/2))
	ds5=( w[17]*cos(pi*w[19]/2+(1-w[19])*atan((x-w[18])/w[20])))/(((x-w[18])^2+w[20]^2)^((1-w[19])/2))
	ds6=( w[21]*cos(pi*w[23]/2+(1-w[23])*atan((x-w[22])/w[24])))/(((x-w[22])^2+w[24]^2)^((1-w[23])/2))
	
	return w[0]+ds1+ds2+ds3+ds4+ds5+ds6
	
End

structure DoniachSunjicStruct
	// data structure for DoniachSunjicBroadS structural function fit

	// waves populated by the FuncFit operation	
	wave pw
	wave yw
	wave xw

	// convolution parameters to be set upon creation of the structure
	variable precision
	variable oversampling

	// auxiliary fields used internally by DoniachSunjicBroadS
	// do not touch these
	wave xdw
	wave model
	wave broadening
	wave convolution
EndStructure

//------------------------------------------------------------------------------
threadsafe function DoniachSunjicBroadS(s) : FitFunc
//------------------------------------------------------------------------------
	// Two-peak (bulk + surface) Doniach-Sunjic line shape with Gaussian broadening (convolution).
	// Hold parameter 5 at 0 to fit just one peak.
	
	// Structural fit function for efficient fitting in procedures.
	// Calculating the convolution requires auxiliary waves and additional, non-fitting parameters.
	// To eliminate the time-consuming overhead of creating and killing the auxiliary waves,
	// these waves are held in the fitting structure.
	
	// Caution: The function on its own is thread-safe.
	// However, since FuncFit uses the same structure in all threads, the fitting cannot run in parallel.
	// Set /NTHR=1.
	
	// See also Fit_DoniachSunjicBroad (example), DoniachSunjicBroad (conventional fit function)
	Struct DoniachSunjicStruct &s

	// pw[0] = bulk amplitude
	// pw[1] = bulk position
	// pw[2] = Lorentzian FWHM
	// pw[3] = Donjach-Sunjic singularity index (0..1)
	// pw[4] = surface shift
	// pw[5] = surface/bulk ratio
	// pw[6] = Gaussian FWHM
	// pw[7] = constant background
	// pw[8] = linear background
	
	wave xw = s.xw
	wave yw = s.yw
	wave pw = s.pw
	
	variable precision = s.precision
	variable oversampling = s.oversampling

	if (WaveExists(s.xdw))
		wave xdw = s.xdw
		wave model = s.model
		wave broadening = s.broadening
		wave convolution = s.convolution
	else
		make /n=0 /free xdw, model, broadening, convolution
		redimension /d xdw, model, broadening, convolution
		wave fs.xdw = xdw
		wave fs.model = model
		wave fs.broadening = broadening
		wave fs.convolution = convolution
	endif

	// calculate wave spacing based on minimum spacing of desired x points
	differentiate /p xw /d=xdw
	xdw = abs(xdw)
	variable xd = wavemin(xdw) / oversampling
	
	// calculate broadening wave size based on width and precision variable
	variable x0b = pw[6] * precision
	variable nb = 2 * floor(x0b / xd) + 1
	
	// calculate model size based on desired range for yw
	variable x0m = max(abs(wavemax(xw) - pw[1]), abs(wavemin(xw) - pw[1])) + x0b
	variable nm = 2 * floor(x0m / xd) + 1
	nb = min(nb, nm * 10)	// limit wave size to avoid runtime errors for unphysically large parameter
	
	// create and calculate initial waves, normalize exponential
	redimension /n=(nb) broadening
	redimension /n=(nm) model
	setscale/i x -x0b, x0b, "", broadening
	setscale/i x -x0m, x0m, "", model
	
	broadening = exp( - (x / pw[6])^2 * 4 * ln(2))
	variable nrm = area(broadening)
	broadening /= nrm
	model = DoniachSunjic(x, 1, 0, pw[3], pw[2]) // bulk
	model += DoniachSunjic(x, pw[5], pw[4], pw[3], pw[2]) // surface
	
	// calculate the convolution
	Convolve /a broadening, model
	variable scale = pw[0] / wavemax(model)
	model *= scale
	
	// prepare output
	nm = numpnts(model)
	x0m = xd * (nm - 1) / 2
	setscale/i x -x0m, x0m, "", model
	
	yw = model(xw[p] - pw[1]) + pw[7] + pw[8] * xw[p]
	yw = numtype(yw) ? 0 : yw
end

//------------------------------------------------------------------------------
function DoniachSunjicBroad(pw, yw, xw) : FitFunc
//------------------------------------------------------------------------------
	// Two-peak (bulk + surface) Doniach-Sunjic line shape with Gaussian broadening (convolution).
	// Hold parameter 5 at 0 to fit just one peak.
	// Conventional fit function for use with the curve-fitting dialog.
	// Compared to DoniachSunjicBroadS this function incurs extra overhead
	// because auxiliary waves are created and killed between function calls.
	// See also DoniachSunjicBroadS (optimized structural fit function)
	Wave pw
	Wave yw
	Wave xw

	// pw[0] = bulk amplitude
	// pw[1] = bulk position
	// pw[2] = Lorentzian FWHM
	// pw[3] = Donjach-Sunjic singularity index (0..1)
	// pw[4] = surface shift
	// pw[5] = surface/bulk ratio
	// pw[6] = Gaussian FWHM
	// pw[7] = constant background
	// pw[8] = linear background
	
	// set up data structure
	struct DoniachSunjicStruct fs
	fs.precision = 5
	fs.oversampling = 4
	
	wave fs.pw = pw
	wave fs.xw = xw
	wave fs.yw = yw
	
	// create temporary calculation waves in a global folder
	dfref df = root:packages:pearl_fitfuncs:doniach_sunjic
	if (DataFolderRefStatus(df) == 0)
		newdatafolder root:packages:pearl_fitfuncs:doniach_sunjic
		dfref df = root:packages:pearl_fitfuncs:doniach_sunjic
	endif
	
	wave /z /sdfr=df fs.xdw = xdw
	wave /z /sdfr=df fs.model = model
	wave /z /sdfr=df fs.broadening = broadening
	wave /z /sdfr=df fs.convolution = convolution

	if (WaveExists(fs.xdw) == 0)
		dfref savedf = GetDataFolderDFR()
		setdatafolder df
		make /n=0 /d xdw, model, broadening, convolution
		wave fs.xdw = xdw
		wave fs.model = model
		wave fs.broadening = broadening
		wave fs.convolution = convolution
		setdatafolder savedf
	endif

	// calculate
	DoniachSunjicBroadS(fs)
	
	yw = fs.yw
end

//------------------------------------------------------------------------------
function Calc_DoniachSunjicBroad(pw, yw)
//------------------------------------------------------------------------------
	// Calculate the DoniachSunjicBroadS line shape
	Wave pw // coefficient wave
	Wave yw // output wave, correct x-scaling required on input
	
	struct DoniachSunjicStruct fs
	fs.precision = 5
	fs.oversampling = 4
	
	duplicate /free pw, fs.pw
	duplicate /free yw, fs.xw
	fs.xw = x
	duplicate /free yw, fs.yw
	
	DoniachSunjicBroadS(fs)
	
	yw = fs.yw
end

//------------------------------------------------------------------------------
Function Fit_DoniachSunjicBroad(pw, yw, xw, ww)
//------------------------------------------------------------------------------
	// Fit the DoniachSunjicBroadS line shape.
	// The function applies constraints which assume that the energy scale is in eV.
	// Returns chi^2.
	wave pw // coefficient wave- pre-load it with initial guess
	wave yw
	wave /z xw
	wave /z ww // weights (standard deviation)
	
	struct DoniachSunjicStruct fs
	fs.precision = 5
	fs.oversampling = 4
	
	duplicate /free pw, fs.pw
	if (WaveExists(xw))
		duplicate /free xw, fs.xw
	else
		duplicate /free yw, fs.xw
		fs.xw = x
	endif
	duplicate /free yw, fs.yw
	
	variable v_chisq = nan
	variable V_FitMaxIters = 100
	make /n=1 /t /free constraints = {"K0 >= 0", "K2 > 0", "K2 < 10", "K3 >= 0", "K3 < 1", "K4 >= -10", "K4 <= 10", "K5 >= 0", "K5 <= 1", "K6 >= 0", "K6 < 10"}
	// note: only single thread allowed
	FuncFit /NTHR=1 DoniachSunjicBroadS, pw, yw /X=xw /D /STRC=fs /C=constraints /NWOK /I=1 /W=ww
	
	return v_chisq
End

//------------------------------------------------------------------------------
// peak-specific fit functions
//------------------------------------------------------------------------------

function Au4f(w, x): fitfunc
	// fit function for a nitrogen 1s-pi* absorption spectrum
	// modelled as multiple Voigt shapes on a constant background
	// similar to the Igor VoigtFit function
	// but with a constant gaussian width (instrumental broadening) for all peaks
	// gaussian and lorentzian widths are specified as FWHM
	wave w // parameters
		// w[0] constant background
		// w[1] linear background
		// w[2] global gaussian FWHM
		// w[3 + 0 + (n-1) * 3] peak n area
		// w[3 + 1 + (n-1) * 3] peak n position
		// w[3 + 2 + (n-1) * 3] peak n lorentzian FWHM
		// length of wave defines number of peaks
		
		// for compatibility with older code the linear background term can be omitted.
		// if the number of parameters divides by 3, the linear background term is added,
		// otherwise only the constant background.
	variable x
	
	variable np = numpnts(w)
	variable ip, ip0
	
	variable bg = w[0]
	variable v = bg
	if (mod(np, 3) == 0)
		v += w[1] * x
		ip0 = 3
	else
		ip0 = 2
	endif

	variable vc1, vc2, vc3, vc4
	vc2 = 2 * sqrt(ln(2)) / w[ip0-1]
	for (ip = ip0; ip < np; ip += 3)
		vc1 = w[ip] / sqrt(pi) * vc2
		vc3 = w[ip+1]
		vc4 = vc2 * w[ip+2] / 2
		v += vc1 * VoigtFunc(vc2 * (x - vc3), vc4)
	endfor

	return v
	
end

function Au4f_2p2(w, x): fitfunc
	// Au 4f 5/2 and 7/2 2-component Voigt fit with a common gaussian width
	// gaussian and lorentzian widths are specified as FWHM
	wave w // parameters
		// w[0] constant background
		// w[1] linear background
		// w[2] global gaussian FWHM
		// w[3] 5/2 bulk area
		// w[4] 5/2 bulk position
		// w[5] 5/2 lorentzian FWHM
		// w[6] 7/2 bulk area
		// w[7] 7/2 bulk position
		// w[8] 7/2 lorentzian FWHM
		// w[9] surface/bulk area ratio
		// w[10] surface core level shift
	variable x

	variable bg = w[0] + w[1] * x
	variable v = bg

	variable vc1 // amplitude
	variable vc2 // width
	variable vc3 // position
	variable vc4 // shape
	vc2 = 2 * sqrt(ln(2)) / w[2]

	// 5/2 bulk
	vc1 = w[3] / sqrt(pi) * vc2
	vc3 = w[4]
	vc4 = vc2 * w[5] / 2
	v += vc1 * VoigtFunc(vc2 * (x - vc3), vc4)

	// 5/2 surface
	vc1 = w[3] / sqrt(pi) * vc2 * w[9]
	vc3 = w[4] + w[10]
	vc4 = vc2 * w[5] / 2
	v += vc1 * VoigtFunc(vc2 * (x - vc3), vc4)

	// 7/2 bulk
	vc1 = w[6] / sqrt(pi) * vc2
	vc3 = w[7]
	vc4 = vc2 * w[8] / 2
	v += vc1 * VoigtFunc(vc2 * (x - vc3), vc4)

	// 7/2 surface
	vc1 = w[6] / sqrt(pi) * vc2 * w[9]
	vc3 = w[7] + w[10]
	vc4 = vc2 * w[8] / 2
	v += vc1 * VoigtFunc(vc2 * (x - vc3), vc4)

	return v
	
end

function ShowComponents_Au4f_2p2(coef_wave, fit_wave)
	wave coef_wave
	wave fit_wave

	duplicate /free coef_wave, coef1, coef2
	coef1[9] = 0
	coef2[3] *= coef_wave[9]
	coef2[4] += coef_wave[10]
	coef2[6] *= coef_wave[9]
	coef2[7] += coef_wave[10]
	coef2[9] = 0
	
	string s_fit_wave = NameOfWave(fit_wave)
	string s_fit_p1 = s_fit_wave + "_p1"
	string s_fit_p2 = s_fit_wave + "_p2"
	duplicate /o fit_wave, $(s_fit_p1) /wave=fit_p1
	duplicate /o fit_wave, $(s_fit_p2) /wave=fit_p2
	
	fit_p1 = Au4f_2p2(coef1, x)
	fit_p2 = Au4f_2p2(coef2, x)

	string traces = TraceNameList("", ";", 1)
	if ((WhichListItem(s_fit_wave, traces, ";") >= 0) && (WhichListItem(s_fit_p1, traces, ";") < 0))
		appendtograph fit_p1, fit_p2
		ModifyGraph lstyle($s_fit_p1)=2
		ModifyGraph lstyle($s_fit_p2)=2
		ModifyGraph rgb($s_fit_p1)=(0,0,65280)
		ModifyGraph rgb($s_fit_p2)=(0,0,65280)
	endif
end

function Au4f_2p3(w, x): fitfunc
	// Au 4f 5/2 and 7/2 3-component Voigt fit with a common gaussian width
	// gaussian and lorentzian widths are specified as FWHM
	wave w // parameters
		// w[0] constant background
		// w[1] linear background
		// w[2] global gaussian FWHM
		// w[3] 5/2 bulk area
		// w[4] 5/2 bulk position
		// w[5] 5/2 lorentzian FWHM
		// w[6] 7/2 bulk area
		// w[7] 7/2 bulk position
		// w[8] 7/2 lorentzian FWHM
		// w[9] surface/bulk area ratio
		// w[10] surface core level shift
		// w[11] 2nd layer/bulk area ratio
		// w[12] 2nd layer core level shift
	variable x

	variable bg = w[0] + w[1] * x
	variable v = bg

	variable vc1 // amplitude
	variable vc2 // width
	variable vc3 // position
	variable vc4 // shape
	vc2 = 2 * sqrt(ln(2)) / w[2]

	// 5/2 bulk
	vc1 = w[3] / sqrt(pi) * vc2
	vc3 = w[4]
	vc4 = vc2 * w[5] / 2
	v += vc1 * VoigtFunc(vc2 * (x - vc3), vc4)

	// 5/2 surface
	vc1 = w[3] / sqrt(pi) * vc2 * w[9]
	vc3 = w[4] + w[10]
	vc4 = vc2 * w[5] / 2
	v += vc1 * VoigtFunc(vc2 * (x - vc3), vc4)

	// 5/2 2nd layer
	vc1 = w[3] / sqrt(pi) * vc2 * w[11]
	vc3 = w[4] + w[12]
	vc4 = vc2 * w[5] / 2
	v += vc1 * VoigtFunc(vc2 * (x - vc3), vc4)

	// 7/2 bulk
	vc1 = w[6] / sqrt(pi) * vc2
	vc3 = w[7]
	vc4 = vc2 * w[8] / 2
	v += vc1 * VoigtFunc(vc2 * (x - vc3), vc4)

	// 7/2 surface
	vc1 = w[6] / sqrt(pi) * vc2 * w[9]
	vc3 = w[7] + w[10]
	vc4 = vc2 * w[8] / 2
	v += vc1 * VoigtFunc(vc2 * (x - vc3), vc4)

	// 7/2 2nd layer
	vc1 = w[6] / sqrt(pi) * vc2 * w[11]
	vc3 = w[7] + w[12]
	vc4 = vc2 * w[8] / 2
	v += vc1 * VoigtFunc(vc2 * (x - vc3), vc4)

	return v
	
end

function ShowComponents_Au4f_2p3(coef_wave, fit_wave)
	wave coef_wave
	wave fit_wave

	duplicate /free coef_wave, coef1, coef2, coef3
	coef1[9] = 0
	coef1[11] = 0
	
	coef2[3] *= coef_wave[9]
	coef2[4] += coef_wave[10]
	coef2[6] *= coef_wave[9]
	coef2[7] += coef_wave[10]
	coef2[9] = 0
	coef2[11] = 0
	
	coef3[3] *= coef_wave[11]
	coef3[4] += coef_wave[12]
	coef3[6] *= coef_wave[11]
	coef3[7] += coef_wave[12]
	coef3[9] = 0
	coef3[11] = 0
	
	string s_fit_wave = NameOfWave(fit_wave)
	string s_fit_p1 = s_fit_wave + "_p1"
	string s_fit_p2 = s_fit_wave + "_p2"
	string s_fit_p3 = s_fit_wave + "_p3"
	duplicate /o fit_wave, $(s_fit_p1) /wave=fit_p1
	duplicate /o fit_wave, $(s_fit_p2) /wave=fit_p2
	duplicate /o fit_wave, $(s_fit_p3) /wave=fit_p3
	
	fit_p1 = Au4f_2p2(coef1, x)
	fit_p2 = Au4f_2p2(coef2, x)
	fit_p3 = Au4f_2p2(coef3, x)

	string traces = TraceNameList("", ";", 1)
	if ((WhichListItem(s_fit_wave, traces, ";") >= 0) && (WhichListItem(s_fit_p1, traces, ";") < 0))
		appendtograph fit_p1, fit_p2, fit_p3
		ModifyGraph lstyle($s_fit_p1)=2
		ModifyGraph lstyle($s_fit_p2)=2
		ModifyGraph lstyle($s_fit_p3)=2
		ModifyGraph rgb($s_fit_p1)=(0,0,65280)
		ModifyGraph rgb($s_fit_p2)=(0,0,65280)
		ModifyGraph rgb($s_fit_p3)=(0,0,65280)
	endif
end

/// convolution of Fermi-Dirac distribution and a Gaussian.
///
/// @arg pw[0] = constant background
/// @arg pw[1] = linear background
/// @arg pw[2] = amplitude
/// @arg pw[3] = Fermi level in eV
/// @arg pw[4] = temperature in K
/// @arg pw[5] = gaussian width = FWHM / 1.66511
///
function FermiGaussConv(pw, yw, xw) : FitFunc
	WAVE pw, yw, xw
	
	// half width of temporary gaussian wave is pw[5] multiplied by this factor (may be fractional)
	variable precision_g = 5
	variable oversampling = 4
	
	// calculate wave spacing based on minimum spacing of desired x points
	duplicate /free xw, xdw
	differentiate /p xw /d=xdw
	xdw = abs(xdw)
	variable xd = wavemin(xdw) / oversampling
	
	// calculate gausswave size based on pw[5] and precision variable
	variable x0g = abs(pw[5]) * precision_g
	variable ng = 2 * floor(x0g / xd) + 1
	
	// calculate fermiwave size based on desired range for yw
	variable emax = wavemax(xw)
	variable emin = wavemin(xw)
	variable x0f = max(abs(emax - pw[3]), abs(emin - pw[3])) + x0g
	variable ne = 2 * floor(x0f / xd) + 1
	
	// create and calculate initial waves, normalize exponential
	make /d /n=(ng) /free gausswave
	make /d /n=(ne) /free fermiwave
	setscale/i x -x0g, x0g, "", gausswave
	setscale/i x -x0f, x0f, "", fermiwave
	
	gausswave = exp( - (x / pw[5] )^2 )
	fermiwave = 1 / (exp( x / (kBoltzmann * pw[4])) + 1.0 )

	// calculate the convolution
	duplicate /free fermiwave, resultwave
	Convolve /a gausswave, resultwave
	variable rmax = wavemax(resultwave)
	resultwave /= rmax
	
	// prepare output
	ng = numpnts(resultwave)
	x0g = xd * (ng - 1) / 2
	setscale/i x -x0g, x0g, "", resultwave
	
	yw = pw[2] * resultwave(xw[p] - pw[3]) + pw[0] + pw[1] * xw[p]
end


/// calculate the shirley background
///
///
function ShirleyBG(w, bg, p1, p2)
	wave w
	wave bg
	variable p1, p2
	
	duplicate /o w, bg
	integrate /meth=1 w /d=bg

	variable bg1 = bg[p1]
	variable bg2 = bg[p2]
	bg -= bg1
	bg /= bg2 - bg1
	bg *= w[p2] - w[p1]
	bg += w[p1]
end
