igor-public/pearl/fermi-edge-analysis.ipf

321 lines
9.2 KiB
Igor

#pragma rtGlobals=3 // Use modern global access method and strict wave access.
#include "pearl-area-profiles"
// $Id$
/// @file
/// @brief tools for analysing the Fermi edge measured by the Scienta EW4000 analyser.
/// @ingroup ArpesPackage
///
/// proposed procedure
///
/// * angular normalization
/// * fit curved fermi function
/// * calculate corrected energy coordinates and map to single independent variable
/// * fit normal fermi function
///
/// @author matthias muntwiler, matthias.muntwiler@psi.ch
/// @author thomas dienel
///
/// @copyright 2013-15 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
function analyse_curved_edge(data)
wave data // 2D counts data, x-scale = kinetic energy, y-scale = analyser angle
variable guess_EF // initial guess of the Fermi level
dfref savedf = GetDataFolderDFR()
dfref datadf = GetWavesDataFolderDFR(data)
setdatafolder datadf
make /n=5 /d /o w_coef_int
make /n=7 /d /o w_coef_curved
// 1) integrate data
wave xint = ad_profile_x(data, -inf, inf, "", noavg=1)
duplicate /free xint, xint_sig
xint_sig = sqrt(xint * 4) / 4
variable xmin = wavemin(xint)
variable xmax = wavemax(xint)
variable xmean = mean(xint)
// 2) fit integrated data for reference
w_coef_int[0] = xmin
w_coef_int[1] = 0
w_coef_int[2] = xmax - xmin
w_coef_int[3] = dimoffset(xint, 0) + dimdelta(xint, 0) * dimsize(xint, 0) / 2
w_coef_int[4] = 100
FuncFit /NTHR=0 FermiFuncLinDOS w_coef_int xint /D /I=1 /W=xint_sig
wave w_sigma
duplicate /o w_sigma, w_sigma_int
// 3) normalize data
wave yavg = ad_profile_y(data, -inf, inf, "")
variable ymean = mean(yavg)
duplicate /o data, data_norm, data_sig
data_sig = sqrt(data * 4) / 4
data_norm = data_norm * ymean / yavg[q]
data_sig = data_sig * ymean / yavg[q]
// 4) fit normalized data
wave xavg = ad_profile_x(data, -inf, inf, "")
xmin = wavemin(xavg)
xmax = wavemax(xavg)
xmean = mean(xavg)
w_coef_curved[0] = {0, 0, 1, 95.5, 100, 0, -0.0001}
w_coef_curved[0] = xmin
w_coef_curved[2] = xmax - xmin
w_coef_curved[3] = w_coef_int[3]
w_coef_curved[4] = w_coef_int[4]
//variable box = min(11, numpnts(xavg) / 5)
//FindLevel /B=(box) /EDGE=2 /Q xavg, (xmin + xmax) / 2
//if (v_flag == 0)
// w_coef_curved[3] = v_levelx
//else
// w_coef_curved[3] = dimoffset(data, 0) + dimdelta(data, 0) * dimsize(data, 0) / 2
//endif
duplicate /o data_norm, fit_data_norm
FuncFitMD /X=1 /NTHR=0 FermiFuncLinDOS_2Dcorr w_coef_curved data_norm /D=fit_data_norm /I=1 /W=data_sig
wave w_sigma
duplicate /o w_sigma, w_sigma_curved
display /k=1; appendimage data_norm
ModifyImage data_norm ctab= {xmin,xmax,Grays,0}
AppendMatrixContour fit_data_norm
setdatafolder savedf
return 0
end
function record_results(index)
variable index
dfref savedf = GetDataFolderDFR()
wave /sdfr=root: Tint, Tint_sig, Tcurv, Tcurv_sig, EFcurv, EFcurv_sig
wave w_coef_int, w_coef_curved
wave w_sigma_int, w_sigma_curved
Tint[index] = w_coef_int[4]
Tint_sig[index] = w_sigma_int[4]
Tcurv[index] = w_coef_curved[4]
Tcurv_sig[index] = w_sigma_curved[4]
EFcurv[index] = w_coef_curved[3]
EFcurv_sig[index] = w_sigma_curved[3]
setdatafolder savedf
end
function integrate_curved_edge(data, data_sig)
wave data
wave /z data_sig
//wave coef
string name = nameofwave(data)
duplicate /o data, data_out
redimension /n=(dimsize(data,0)) data_out
data_out = 0
if (waveexists(data_sig))
duplicate /o data_sig, sig_out
redimension /n=(dimsize(data,0)) sig_out
sig_out = 0
endif
wave ywgt = ad_profile_y(data, -inf, inf, "", noavg=1)
ywgt = 1 / ywgt / 4
//ywgt = 1 / 4
variable sum_ywgt = sum(ywgt)
variable nx = dimsize(data, 0)
variable ny = dimsize(data, 1)
variable iy
variable yy
variable dx
variable dy
variable dp
variable dp_min = 0
wave PassEnergy = :attr:PassEnergy
wave NumSlices = :attr:NumSlices
for (iy = 0; iy < ny; iy += 1)
dy = dimoffset(data, 1) + dimdelta(data, 1) * iy
dy = dy / dimdelta(data, 1)
dx = slit_shift(dy * 902 / NumSlices[0], PassEnergy[0])
dp = round(dx / dimdelta(data, 0)) // <= 0
dp_min = min(dp_min, dp)
data_out[] = p+dp >= 0 ? data_out + data[p+dp][iy] * ywgt[iy] : data_out
if (waveexists(data_sig))
sig_out = p+dp >= 0 ? sig_out + data_sig[p+dp][iy] * ywgt[iy] : sig_out
endif
endfor
data_out /= sum_ywgt
data_out[0, -dp_min][] = nan
if (waveexists(sig_out))
sig_out /= sum_ywgt
//sig_out[0, -dp_min] = nan
endif
end
function slit_correction(data, data_out, epass)
wave data // must be image with original dimensions (no cropping!)
wave /z data_out // 2D or 1D wave to receive the result
// X dimension must be identical to the one of data
// if 2D, Y dimension must be either identical to the one of data
// if 1D, the result will be the sum of the corrected slices
variable epass // pass energy
if (!WaveExists(data_out))
string name = nameofwave(data) + "_corr"
duplicate /o data, $name
wave data_out = $name
//redimension /n=(dimsize(data,0)) data_out
endif
data_out = 0
variable nx = dimsize(data, 0)
variable ny = dimsize(data, 1)
variable iy
variable yy
variable dx
variable dy
variable dp
variable dp_min = 0
for (iy = 0; iy < ny; iy += 1)
dy = dimoffset(data, 1) + dimdelta(data, 1) * iy
dy = dy / dimdelta(data, 1)
dx = slit_shift(dy * 902 / ny, epass)
dp = round(dx / dimdelta(data, 0)) // <= 0
dp_min = min(dp_min, dp)
if (wavedims(data_out) >= 2)
data_out[][iy] = p+dp >= 0 ? data_out + data[p+dp][iy] : data_out
else
data_out = p+dp >= 0 ? data_out + data[p+dp][iy] : data_out
endif
endfor
data_out[0, -dp_min][] = nan
end
//------------------------------------------------------------------------------
threadsafe Function FermiFuncLinDOS2D_corr(w,x,y) : FitFunc
// linear density of states below Fermi level
// 2D data with corrections:
// - straight slit (slit shift)
// - transmission function (polynomial)
//------------------------------------------------------------------------------
Wave w; Variable x; variable y
// w[0] = background far above the fermi level
// w[1] = slope of the linear background
// w[2] = amplitude
// w[3] = fermi level in eV
// w[4] = temperature in K
// w[5] = transmission - linear term
// w[6] = transmission - quadratic term
// w[7] = transmission - cubic term
// w[8] = pass energy (hold this value)
// w[9] = y scale: pixels / unit of y
variable pos = w[3] + slit_shift(y * w[9], w[8])
variable transm = 1 + w[5] * y + w[6] * y^2 + w[7] * y^3
variable fermi = (w[1] * min(x - pos, 0) + w[2]) / ( exp( (x - pos) / (kBoltzmann * w[4]) ) + 1.0 )
return transm * (fermi + w[0])
end
//------------------------------------------------------------------------------
threadsafe Function FermiFuncLinDOS_2Dcorr_old(w,x,y) : FitFunc
// linear density of states below Fermi level
// 2D data with a polynomial shift of the Fermi level in the second dimension
//------------------------------------------------------------------------------
Wave w; Variable x; variable y
// w[0] = background far above the fermi level
// w[1] = slope of the linear background
// w[2] = amplitude
// w[3] = fermi level in eV
// w[4] = temperature in K
// w[5] = shift - linear term
// w[6] = shift - quadratic term
variable pos = w[3] + w[5] * y + w[6] * y^2
return w[0] + (w[1] * min(x - pos, 0) + w[2]) / ( exp( (x - pos) / (kBoltzmann * w[4]) ) + 1.0 )
end
/// MCP radius seen by the camera in pixels
static constant mcp_radius_pix = 555
/// physical size (radius) of the MCP in mm
static constant mcp_radius_mm = 20
/// physical size (radius) of the hemisphere in mm
static constant hemi_radius_mm = 200
/// energy range imaged on MCP relative to the pass energy
static constant mcp_radius_epass = 0.04
threadsafe function slit_shift(ypix, epass)
// calculates the energy shift due to straight slit
// the radius of the curve is 1/2 of the hemisphere radius
variable ypix // vertical (angle/position) pixels distance from center
// = slice coordinate * 902 / number of slices
variable epass // pass energy
variable rpix = mcp_radius_pix * hemi_radius_mm / mcp_radius_mm
//variable rpix = mcp_radius_pix * hemi_radius_mm / 2 / mcp_radius_mm
variable rene = epass * mcp_radius_epass * hemi_radius_mm / 2 / mcp_radius_mm
variable isin = asin(ypix / rpix)
variable dene = rene * (cos(isin) - 1)
return dene
end
function show_shift(data)
wave data
variable epass
variable ny = dimsize(data, 1)
make /o /n=(ny) shift_x
setscale /i x -ny/2, ny/2, "", shift_x
wave PassEnergy = :attr:PassEnergy
wave NumSlices = :attr:NumSlices
shift_x = slit_shift(x * 902 / NumSlices[0], PassEnergy[0])
variable ecenter = dimoffset(data, 0) + dimdelta(data, 0) * dimsize(data, 0) / 2
shift_x += ecenter
end
/// calculate the energy resolution of the analyser
///
/// @param epass pass energy in eV
/// @param slit analyser entrance slit in mm
///
/// @return energy resolution (FWHM)
function analyser_energy_resolution(epass, slit)
variable epass
variable slit
variable respow
if (epass < 4)
respow = 1110
elseif (epass < 8)
respow = 1400
else
respow = 1750
endif
return epass * max(0.2, slit) / 0.2 / respow
end