Coverage for /Users/Newville/Codes/xraylarch/larch/xsw/fluo_det.py: 0%
454 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
1"""
2Fluorescence Intensity calculations:
3 Y. Choi
5calculates fluorescence intensities for multiple elements at a fixed incident x-ray energy.
6measured fluorescence depends on net fluorescence yield and transmission.
7included are:
8 (with incident energy dependence): atomic crosssection, fluorescence yield for each edge,
9 transition probability for each emission line,
10 (with emitted fluorescence energy dependence): transmission to vortex detector, detector efficiency.
11global variables: re, Barn, Nav, pre_edge_margin, fluo_emit_min, det_res
12AtNum2f1f2Xect: needs datafile readf1f2a.py which is from chantler table
13cal_NetYield2: for XRF calculations. major and minor lines are calculated
14 output as 'Elemental_Sensitivity.txt'
16sim_spectra: keeps individual emission lines with significant intensity without weight-averaging
17 each element can have a different relative concentration.
18 makes strongest emission to 100 and scales the rest accordingly.
19 output as 'simSpectrum.txt'
20 runs sim_GaussPeaks() to emulate measure intensity as gaussian peak
21 output as 'simSpectrum.plot'
22 runs cal_NetYield2()
235/24/2010: self-absorption effect included, 4 options (top, bottom, all, surface)
24 Each option makes different elemental distribution,
25 but substrate elements are always distributed evenly.
26 get_ChemName(), nominal_density() are in readf1f2a.py
275/26/2010: incident x-ray attenuation effect added, and incident angle effect is also added.
28 refraction effect added to the angle, below critical angle, th=1e-10.
296/4/2010: SampleMatrix2 has two layers and each layer can have different composition
30 Class ElemFY is for fluorescence elements with AtSym, Conc, tag attribues,
31 inputs in SimSpect, Cal_NetFyield2 changed.
32 Concentrations are normalized to the concentration of the substrate1 molecule.
33 Ex) with CaCO3[2.71g/cc]/Fe2O3[5.26g/cc] substrate, Ca concentration is 1.0 meaning 1.63e22 Ca/cc
34 and Fe concentration is 2.43 = 2*1.98e22/1.63e22 where 1.98e22 is number density (/cc) of Fe2O3
356/8/2010: now using python2.6 instead of 2.5
364/14/2011: fluo_det.py: detection dependent part in fluorescence yield.
37 Original fluo.py was modified to fluo_new.py.
38 readf1f2a.py is used instead of original readf1f2.py
39 fluo_new.py is being split into two.
40 fluo_elem.py is for elemtnal dependence: fluorescenc yield, crosssection.
41 fluo_det.py is for detection dependnece: detector efficiency, attenuation, sample
42 * for attenuation, total cross-section([4]) is used instead of photoelectric ([2]).
43 ** for fluorescence yield, photoelectric is used.
44"""
47import math
48import numpy
49import sys
50from larch.utils.physical_constants import AVOGADRO, BARN
51from larch.xray import xray_delta_beta, chemparse
53pre_edge_margin=150. # FY calculated from 150 eV below the absorption edge.
54fluo_emit_min=500. # minimum energy for emitted fluorescence. ignore fluorescence emissions below 500eV
55det_res=100. # detector resoltuion in eV, used in sim_GaussPeaks
57'''
58----------------------------------------------------------------------------------------------------------
59class: Material, ElemFY, SampleMatrix2
60----------------------------------------------------------------------------------------------------------
61'''
62class Material:
63 def __init__(self, composition, density, thickness=1):
64 self.composition=composition # ex) SiO2
65 self.density=density # in g/cm^3
66 self.thickness=thickness # in cm
67 self.la=1.0 #absoprtion length in cm
68 self.trans=0.5 # transmission.
69 self.absrp=0.5 # absorption
70 self.delta=0.1 # index of refraction, real part
71 self.beta=0.1 # index of refraction, imaginary part
72 # MN replace
73 elements = chemparse(composition)
74 AtomList = elements.keys()
75 AtomIndex = elements.values()
76 AtomWeight=0
77 for (ii, atom) in enumerate(AtomList):
78 # MN replace...
79 AtWt= atomic_mass(atom)
80 index=AtomIndex[ii]
81 AtomWeight = AtomWeight + index*AtWt
82 NumberDensity=density*AVOGARDRO/AtomWeight
83 self.NumDen=NumberDensity # number of molecules per cm^3
84 self.AtWt=AtomWeight # weight per mole
85 def getLa(self, energy, NumLayer=1.0): # get absorption legnth for incident x-ray, NumLayer for multiple layers
86 # MN replace...
87 temp= xray_delta_beta(self.composition, self.density, energy)
88 # returns delta, beta, la_photoE, Nat, la_total
89 self.delta=temp[0]; self.beta=temp[1]
90 self.la=temp[2] # temp[4] (total) instead of temp[2] (photoelectric)
91 if NumLayer<0: NumLayer=0 # Number of layers cannot be less than zero
92 self.trans=math.exp(-self.thickness*NumLayer/self.la) #la attenuation length cm, NumLayer for multiple layers
93 self.absrp=1-self.trans
96class ElemFY: # fluorescing element
97 def __init__(self, AtomicSymbol='Fe', Concentration=10e-6, tag='dilute'):
98 temp=AtomicSymbol[0].upper() # capitalize the first letter
99 self.AtSym=temp+AtomicSymbol[1:]
100 self.Conc=Concentration
101 self.tag=tag # dilute, substrate1, substrate2
104class SampleMatrix2: # sample matrix for self-absorption correction, 6/3: two layers with different compositions
105 def __init__(self, composition1='Si', density1=2.33, thickness1=0.1, \
106 composition2='Si', density2=2.33, thickness2=0.1,
107 angle0=45.0, option='surface'):
108 self.composition1 = composition1 # ex) Fe2O3
109 # MN replace:
110 out= chemparse(composition1) # output from get_ChemName
111 self.ElemList1 = out.keys() # list of elments in matrix: 'Fe', 'O' for Fe2O3
112 self.ElemInd1 = out.values() # list of index: 2, 3 for Fe2O3
113 # self.ElemFrt1 = out[2] # list of fraction: 0.4, 0.6 for Fe2O3
114 self.density1 = density1 # in g/cm^3
115 self.thickness1 = thickness1 # top layer thickness in cm
116 self.composition2 = composition2
117 # MN replace with chemparse()
118 out=chemparse(composition2)
119 self.ElemList2 = out.keys() # for the bottom substrate
120 self.ElemInd2 = out.values()
121 # self.ElemFrt2 = out[2]
122 self.density2 = density2
123 self.thickness2 = thickness2 # bottom layer thickness in cm
124 self.angle = angle0*math.pi/180. # in radian, incident beam angle, surface normal =pi/2
125 self.option = option # option for fluorescing element location, surface/top/bottom
126 self.la1 = 1.0 # absoprtion length in cm
127 self.delta1 = 0.1 # index of refraction, real part correction
128 self.beta1 = 0.1 # index of refraction, imaginary part
129 self.Nat1 = 1.0 # atomic number density of the Fe2O3, 1e22 Fe2O3 atoms /cm^3
130 self.la2 = 1.0
131 self.delta2 = 0.1
132 self.beta2 = 0.1
133 self.Nat2 = 1.0 # atomic number density of the first element, 1e22 Fe2O3 atoms/cm^3
134 self.scale = 1.0 # weighted average over the depth range: sum of (trans*factors)
135 self.scale1 = 1.0 # sum of (trans*1.0) this is for substrate element in top layer
136 self.scale2 = 1.0 # sum of (trans*thickness2/thickness1) for substrate element in bottom layer
137 self.ElemListFY =[] # fluorescing element
138 self.ElemFrtFY =[] # fraction for fluorescing element
139 self.Nat1=1 # atomic number density in atoms/cc, for example # of Fe2O3/cm^3 for Fe2O3 substrate
140 self.Nat2=1 # atomic number density in atoms/cc
141 substrate1_material = Material(composition1, density1)
142 AtNumDen1 = substrate1_material.NumDen # substrate1
143 substrate2_material = Material(composition2, density2)
144 AtNumDen2 = substrate2_material.NumDen # substrate2, fixed 8/12/10
145 self.Nat1=AtNumDen1
146 self.Nat2=AtNumDen2
147 self.txt=''
148 text1='substrate1:%6.3e %s/cm^3' % (AtNumDen1, composition1)
149 #print(text1)
150 text2='substrate2:%6.3e %s/cm^3' % (AtNumDen2, composition2)
151 self.txt=text1+' '+text2
152 print(self.txt)
153 # atom.conc is normalized to the number density of substrate1 molecule
154 for (ii, item) in enumerate(self.ElemList1):
155 # MN replace:
156 if atomic_number(item)>=12: # ignore elements below Mg
157 #atom=ElemFY(item, self.ElemFrt1[ii], 'substrate1')
158 atom=ElemFY(item, self.ElemInd1[ii], 'substrate1')
159 # eg. for Fe2O3, Fe concentration is 2 (=2 x Fe2O3 number density)
160 self.ElemListFY.append(atom)
161 for (ii, item) in enumerate(self.ElemList2):
162 # MN replace:
163 if atomic_number(item)>=12: # ignore elements below Mg
164 #atom=ElemFY(item, self.ElemFrt2[ii], 'substrate2')
165 atom=ElemFY(item, self.ElemInd2[ii]*AtNumDen2/AtNumDen1, 'substrate2')
166 self.ElemListFY.append(atom)
167 numLayer=100 # each layer is sliced into 100 sublayers.
168 self.depths=[] # depth values for calculation
169 for ii in range(numLayer):
170 step=1.0/numLayer
171 self.depths.append(step*ii*self.thickness1)
172 for ii in range(numLayer):
173 step=1.0/numLayer
174 self.depths.append(step*ii*self.thickness2+self.thickness1)
175 self.factors=[] # 1 or pre if element present at each depth
176 pre=self.thickness2/self.thickness1 # prefactor, if two layers have different thickness
177 if self.option=='surface': # fluorescecing atoms present in only on the surface
178 for ii in range(len(self.depths)):
179 if ii==0:
180 self.factors.append(1.0)
181 else:
182 self.factors.append(0.0)
183 if self.option=='all': # fluorescecing atoms present throughout
184 for ii in range(len(self.depths)):
185 if self.depths[ii]<self.thickness1:
186 self.factors.append(1.0)
187 else:
188 self.factors.append(pre)
189 if self.option=='top': # fluorescecing atoms present only in the top layer
190 for ii in range(len(self.depths)):
191 if self.depths[ii]<self.thickness1:
192 self.factors.append(1.0)
193 else:
194 self.factors.append(0.0)
195 if self.option=='bottom': # fluorescecing atoms present only in the bottom layer
196 for ii in range(len(self.depths)):
197 if self.depths[ii]<self.thickness1:
198 self.factors.append(0.0)
199 else:
200 self.factors.append(pre)
201 # note: fluorescing substrate atoms present throughout regardless of the option.
202 self.trans=[] # transmission to surface for emitted fluorescence
203 self.absrp=[] # absorption until surface for emitted fluorescence
204 self.inten0=[] # incident x-ray intensity at each depth
205 for ii in range(len(self.depths)):
206 self.trans.append(0.5)
207 self.absrp.append(0.5)
208 self.inten0.append(0.5)
209 def getPenetration(self, energy0): # incident x-ray penetration(attenuation)
210 # refraction at air/top layer interface
211 # MN replace:
212 temp=f1f2.get_delta(self.composition1, self.density1, energy0) # energy0: incident x-ray energy
213 delta1=temp[0]; beta1=temp[1]
214 la1=temp[4] # in cm, temp[4] instead of temp[2], using total instead of photoelectric
215 self.la1=la1 # absorption length in microns at incident x-ray energy
216 angle_critical1 = (2.0*delta1)**(0.5) # in radian, critical angle for total external reflection
217 if angle_critical1>=self.angle: # below critical angle, the corrected should be zero.
218 angle_corrected1=1.0e-15 # a smaller number instead of zero
219 else: # above critical angle
220 angle_corrected1 = (self.angle**2.0 - angle_critical1**2.0)**(0.5) # in radian
221 # refraction at top/bottom layers interface
222 # MN replace:
223 temp=f1f2.get_delta(self.composition2, self.density2, energy0) # energy0: incident x-ray energy
224 delta2=temp[0]; beta2=temp[1];
225 la2=temp[4] # in cm, temp[4] instead of temp[2], using total instead of photoelectric
226 self.la2=la2 # absorption length in cm at incident x-ray energy
227 angle_corrected2 = ( 2.0-(1.0-delta1)/(1.0-delta2)*(2.0-angle_corrected1**2) )**0.5
228 # using Snell's law, assume beta effect not ignificant, in radian
229 for (ii, depth) in enumerate(self.depths):
230 if self.depths[ii]<self.thickness1: # top layer
231 beampath = depth/math.sin(angle_corrected1) # in cm
232 inten0 = math.exp(-beampath/la1) # attenuated incident beam intensity at depths
233 else: # bottom layer
234 beampath1 = self.thickness1/math.sin(angle_corrected1)
235 beampath2 = (depth-self.thickness1)/math.sin(angle_corrected2)
236 inten0 = math.exp(-beampath1/la1)*math.exp(-beampath2/la2)
237 self.inten0[ii] = inten0 # incident x-ray attenuation
238 def getLa(self, energy, NumLayer=1.0): # emitted fluorescence trasmission attenuation up to top surface
239 transmitted=1.0
240 # MN replace:
241 temp=f1f2.get_delta(self.composition1, self.density1, energy) # energy is for fluorescence
242 self.delta1 = temp[0]
243 self.beta1 = temp[1]
244 self.la1 = temp[4] # in cm, temp[4] instead of temp[2], using total instead of photoelectric
245 # absorption length in cm at emitted fluorescence energy
246 # temp[3]: atomic number density of the first element in atoms/cc
247 # MN replace:
248 temp=f1f2.get_delta(self.composition2, self.density2, energy) # energy is for fluorescence
249 self.delta2 = temp[0]
250 self.beta2 = temp[1]
251 self.la2 = temp[4] # absorption length in cm at emitted fluorescence energy
252 # in cm, temp[4] instead of temp[2], using total instead of photoelectric
253 angle_exit = (math.pi/2.0 - self.angle) # becomes 90 at a small incident angle
254 for (ii, depth) in enumerate(self.depths):
255 if self.depths[ii]<self.thickness1: # top layer
256 transmitted=math.exp(-depth/math.sin(angle_exit)*NumLayer/self.la1)
257 else: # bottom layer
258 transmitted2 = math.exp(-(depth-self.thickness1)/math.sin(angle_exit)*NumLayer/self.la2)
259 transmitted1 = math.exp(-self.thickness1/math.sin(angle_exit)*NumLayer/self.la1)
260 transmitted = transmitted2*transmitted1
261 self.trans[ii] = transmitted
262 self.absrp[ii] = 1.0 - transmitted
263 scale = 0.0; scale1=0.0; scale2=0.0
264 for (ii,trans) in enumerate(self.trans):
265 scale = scale + trans*self.inten0[ii]*self.factors[ii]
266 if self.depths[ii]<self.thickness1:
267 scale1 = scale1 + trans*self.inten0[ii]*1.0
268 else: # if thickness2 is different from thickness1, weight differently
269 scale2 = scale2 + trans*self.inten0[ii]*(self.thickness2/self.thickness1)
270 # scale, scale1, scale2: emitted fluorescence transmission and depth profile
271 self.scale = scale # sum of (trans*factors) for nonsubstrate FY
272 self.scale1 = scale1 # sum of (trans*factors) for substrate FY. factors=1, top layer
273 self.scale2 = scale2 # sum of (trans*factors) for substrate FY. factors=pre, bttom layer
276'''
277----------------------------------------------------------------------------------------------------------
278Detector efficiency, fluorescence attenuation
279----------------------------------------------------------------------------------------------------------
280'''
281# WD30 used for XSW, sample-->He-->Kapton-->Collimator-->Detector
282# WD60 used for XRM, sample-->Collimator-->Detector
283# eV1 is fluorescence energy in eV
284# xHe=1 means Helium gas from sample to WD60 collimator.
285# xAl=1 means 1 layer of 1.5mil Al foil as attenuator
286# xKapton=1 means 1 layer of 0.3 mil Kapton (
287# WD=6 means working distance of 6cm for the detector.
289def Assemble_QuadVortex(eV1):
290 # quad vortex detector efficiency. eV1: fluo energy
291 net=1.
292 BeVortex=Material('Be', 1.85, 0.00125)
293 SiO2Vortex=Material('SiO2', 2.2, 0.00001)
294 SiVortex=Material('Si', 2.33, 0.035)
295 BeVortex.getLa(eV1, 1) # one Be layer in Vortex
296 SiO2Vortex.getLa(eV1, 1) # oxide layer on Si detection layer
297 SiVortex.getLa(eV1, 1) # Si detection layer, what's absorbed is counted.
298 net=net*BeVortex.trans*SiO2Vortex.trans*SiVortex.absrp
299 if (print2screen):
300 print( '%.3f eV : BeVortex.trans=%.3e , SiO2Vortex.trans=%.3e, SiVortex.absrp=%.3e, Det_efficiency=%.3e' % (eV1, BeVortex.trans, SiO2Vortex.trans, SiVortex.absrp, net))
301 return net
304def Assemble_Collimator(eV1, xHe=1, xAl=0,xKapton=0, WD=6.0, xsw=0):
305 # from sample surface to detector
306 # xsw=0/1/-1, 6cm, 3cm, no collimator
307 # He_path depends on the collliator.
308 if xHe==1:
309 He_path=WD
310 else:
311 He_path=0.
312 air_path = WD-He_path
313 kapton_inside=0 # kapton inside collimator. no collimator, no kapton_inside
314 if xsw==0: # WD60mm collimator
315 kapton_inside=1 # collimator has thin Kapton on the second aperture
316 if xHe==1:
317 air_path = 1.51 # 1.51cm between second aperture and Be of Vortex
318 else:
319 air_path=WD
320 He_path = WD-air_path # He is between sample surface to the second aperture of xrm collimator
321 if xsw==1: # WD30mm colllimator
322 kapton_inside=1 # collimator has thin Kapton on the second aperture
323 if xHe==1: # modified 11/18/2010
324 He_path = 1.088 # from sample surface to Kapton cover/collimator
325 air_path = WD - He_path # 1.912cm between second aperture and Be of Vortex
326 xKapton = xKapton+1 # one Kapton film used to fill He from sample surface to collimator
327 else:
328 air_path = WD0; He_path=0
329 air=Material('N1.56O0.48C0.03Ar0.01Kr0.000001Xe0.0000009', 0.0013, air_path)
330 kapton=Material('C22H10O4N2', 1.42, 0.000762) # 0.3mil thick
331 HeGas=Material('He', 0.00009, He_path)
332 AlFoil=Material('Al', 2.72, 0.00381) # 1.5mil thick
333 kaptonCollimator=Material('C22H10O4N2', 1.42, 0.000762) # 0.3mil thick
334 #
335 air.getLa(eV1, 1) # 1 means number of layers here.
336 kapton.getLa(eV1, xKapton) #number of Kapton layers before collimator
337 HeGas.getLa(eV1, xHe) # number of He gas layers, default=0
338 AlFoil.getLa(eV1, xAl) # number of Al foil, default=0
339 kaptonCollimator.getLa(eV1, kapton_inside) # without collimator, no addition kapton inside
340 #
341 net=air.trans*HeGas.trans
342 net=net*kapton.trans*AlFoil.trans*kaptonCollimator.trans
343 if print2screen:
344 print('%.3f eV: air.trans=%.3e, HeGas.trans=%.3e, kapton.trans=%.3e, AlFoil.trans=%.3e, kaptonCollimator.trans=%.3e, net=%.3e' % \
345 (eV1, air.trans, HeGas.trans, kapton.trans, AlFoil.trans,kaptonCollimator.trans, net))
346 #print('%.3f eV: air.la=%.3e, HeGas.la=%.3e, kapton.la=%.3e' % (eV1, air.la, HeGas.la, kapton.la))
347 return net
350def Assemble_Detector(eV1, xHe=1, xAl=0,xKapton=0, WD=6.0, xsw=0):
351 det_efficiency=Assemble_QuadVortex(eV1)
352 trans2det=Assemble_Collimator(eV1, xHe, xAl,xKapton, WD, xsw)
353 net=det_efficiency*trans2det
354 return net
358'''
359----------------------------------------------------------------------------------------------------------
360Combine detector, transmission, sample self-absorption, and fluorescing element distribution/concentration.
361----------------------------------------------------------------------------------------------------------
362'''
363# this function is for XSW/XRM.
364def cal_NetYield2(eV0, Atoms, xHe=0, xAl=0, xKapton=0, WD=6.0, xsw=0, WriteFile='Y' , xsect_resonant=0.0, sample=''):
365 # incident energy, list of elements, experimental conditions
366 # this one tries Ka, Kb, Lg, Lb, La, Lb
367 angle0=45.; textOut=''
368 if xsw!=0 and WriteFile=='Y': print( 'XSW measurements')
369 if sample=='':
370 Include_SelfAbsorption='No'
371 else:
372 Include_SelfAbsorption='Yes'
373 angle0=sample.angle/math.pi*180.
374 textOut=sample.txt # substrate concentration
375 NetYield=[]; NetTrans=[]; Net=[]; out2=''; net=0.0
376 text0=''
377 edges =['K', 'K', 'L1', 'L1', 'L2', 'L2', 'L2', 'L3', 'L3', 'L3']
378 Fluo_lines =['Ka', 'Kb', 'Lb', 'Lg', 'Ln', 'Lb', 'Lg', 'Ll', 'La', 'Lb']
379 outputfile='Elemental_Sensitivity.txt'
380 if WriteFile=='Y':
381 fo=open(outputfile, 'w')
382 desc=' '
383 if xHe==0: desc=' not '
384 if xsw==-1:
385 out1='# 13IDC XRM/XSW using QuadVortex, incident x-ray energy at '+str(eV0)+' eV at '+str(angle0)+' degrees \n'
386 out1+='# Helium path'+desc+'used, '+str(xAl)+' Al attenuators, '+str(xKapton+1)+' Kapton attenuators, '\
387 +str(WD)+' cm working distance. \n'
388 else:
389 out1='# 13IDC XRM/XSW using QuadVortex + collimator, incident x-ray energy at '+str(eV0)+' eV at '+str(angle0)+' degrees \n'
390 out1+='# Helium path'+desc+'used, '+str(xAl)+' Al attenuators, '+str(xKapton)+' Kapton attenuators, '\
391 +str(WD)+' cm working distance. \n'
392 print( out1)
393 fo.write(out1)
394 if sample!='':
395 for stuff in Atoms:
396 text1='%6.3e %s/cm^3' % (stuff.Conc*sample.Nat1, stuff.AtSym)
397 text0=text0+' '+text1
398 textOut=textOut+' '+text0 # substrate concentration + other concentrations
399 out1='# '+text0+'\n'
400 if print2screen:
401 print( out1)
402 fo.write(out1)
403 out1='%s\t%s\t%s \t%s \t%s \t%s \t%s\n' % ('atom', 'emit', 'emit_energy', 'yield', 'transmission', 'net_sensitivity', 'sensitivity*concentration')
404 if print2screen:
405 print( out1)
406 fo.write(out1)
407 for (ii, atom) in enumerate(Atoms):
408 # MN replace:
409 atnum=f1f2.AtSym2AtNum(atom.AtSym)
410 # MN replace:
411 temp=f1f2.AtNum2f1f2Xsect(atnum, eV0)
412 xsect=temp[2] # photo-electric cross-section for fluorescence yield, temp[4] is total for attenuation
413 con=atom.Conc
414 for (nn, edge) in enumerate(edges):
415 emit=Fluo_lines[nn]
416 fy, emit_eV, emit_prob = fluo_yield(atom.AtSym, edge, emit, eV0)
417 print(emit)
418 if fy==0.0 or emit_prob==0:
419 continue # try next item if FY=0
420 else:
421 if xsect_resonant!=0.0: # use input value near edge
422 xsect=xsect_resonant # for cross-section near absoprtion edge
423 #print(xsect,fy,emit_prob)
424 net_yield=xsect*fy*emit_prob # net_yield --> cross-section, yield, emission_probability
425 # net transmission --> transmission from surface through detector
426## ------------------ [self-absorption] ------------------------------
427 trans_SelfAbsorp = 1.0 # for self-absorption.
428 if Include_SelfAbsorption=='Yes':
429 sample.getPenetration(eV0) # incident x-ray attenuation
430 if fy*emit_eV*emit_prob==0: # any one of three is zero
431 break # skip
432 sample.getLa(emit_eV)
433 # account for incident x-ray attenuation, emitted x-ray attenuation
434 trans_SelfAbsorp = sample.scale # for elements that are not part of substrate
435 if (atom in sample.ElemListFY):
436 if atom.tag=='substrate1':
437 trans_SelfAbsorp = sample.scale1 # for elements that are part of top substrate
438 if atom.tag=='substrate2':
439 trans_SelfAbsorp = sample.scale2 # for elements that are part of bottom substrate
440## ----------------------------------------------------------------------------
441 net_trans = Assemble_Detector(emit_eV, xHe, xAl,xKapton, WD, xsw)
442 net = net_yield*net_trans*trans_SelfAbsorp # elemental sensitivity
443 inten = net*con # sensitivity * concentration
444 if WriteFile=='Y':
445 out1='%s\t%s\t%6.1f \t%.3e \t%.3e \t%.3e \t%.3e\n' % (atom.AtSym+'_'+edge, emit, emit_eV, net_yield, net_trans, net, inten)
446 fo.write(out1)
447 if print2screen:
448 print('%s %s %6.1f net_yield=%.3e net_trans=%.3e net=%.3e\t' % (atom.AtSym+'_'+edge, emit, emit_eV, net_yield, net_trans, net))
449 #print(out1+' %s, depth-dependent factor= %6.4f' % (atom.tag, trans_SelfAbsorp))
450 if emit=='Kb' and fy!=0: # if above K edge, don't bother trying L edges
451 break
452 return textOut
455#def sim_spectra(eV0, Atoms, Conc, xHe=0, xAl=0, xKapton=0, WD=6.0, xsw=0, sample=''):
456def sim_spectra(eV0, Atoms, xHe=0, xAl=0, xKapton=0, WD=6.0, xsw=0, sample=''):
457 # sample=sample matrix with object attribues to add self-absorption effect
458 # Atoms is a list with elements that have attributes AtSym, Conc, tag
459 if xsw==-1: xKapton=xKapton-1 # no collimator
460 Include_SelfAbsorption='Yes'
461 Print2Screen='No'
462 if sample=='': Include_SelfAbsorption='No'
463 if xsw!=0: print('XSW measurements')
464 xx=[]; yy=[]; tag=[]; intensity_max=-10.0; LoLimit=1e-10
465 angle0=''; text1=''
466 if sample!='': # sample matrix option is used
467 angle0=str(sample.angle*180./math.pi)
468 angle0=angle0[:5]
469 for (ii, item) in enumerate(sample.ElemListFY): # add matrix elements to the lists
470 Atoms.append(item)
471 outputfile='simSpectrum_table.txt'
472 fo=open(outputfile, 'w')
473 out1='#incident x-ray at '+str(eV0)+' eV and '+angle0+' Deg.\n'
474 if Print2Screen=='Yes': print(out1)
475 fo.write(out1)
476 out1='#Emission\tenergy(eV)\tintensity \n'
477 if Print2Screen=='Yes': print(out1)
478 fo.write(out1)
479 out2='#'
480 for (ix,atom) in enumerate(Atoms):
481 # MN replace:
482 atnum=f1f2.AtSym2AtNum(atom.AtSym)
483 # con=Conc[ix]
484 con=atom.Conc
485 out2=out2+atom.AtSym+'['+str(con)+'] '
486 for edge in ['K', 'L1', 'L2', 'L3']:
487 # MN replace:
488 temp=f1f2.AtNum2f1f2Xsect(atnum, eV0)
489 xsect=temp[2] # photoelectric crosssection for each element at incident x-ray, fluorescence yield
490 # MN replace:
491 temp=elam.use_ElamFY(atom.AtSym, edge)
492 edge_eV=float(temp[2]) # absorption edge
493 if eV0>edge_eV:
494 fy=float(temp[3])
495 for EmitLine in temp[4]:
496 EmitName=EmitLine[1]
497 emit_eV=float(EmitLine[2])
498 emit_prob=float(EmitLine[3])
499 if emit_eV<fluo_emit_min: continue # ignore fluorescence below fluo_emit_min (global variable)
500 name = atom.AtSym + '_'+ EmitName
501 # net transmission --> transmission from surface through detector
502## ------------------ [self-absorption] ------------------------------
503 trans_SelfAbsorp = 1.0 # for self-absorption.
504 if Include_SelfAbsorption=='Yes':
505 sample.getPenetration(eV0) # incident x-ray attenuation
506 eV0str=str(eV0)
507 text1=' absorption_length1(%seV)= %2.2e%s \
508 absorption_length2(%seV)= %2.2e%s' % (eV0str, sample.la1*1.e4, 'microns', eV0str, sample.la2*1.e4, 'microns')
509 # text1 is added to sample.txt later
510 sample.getLa(emit_eV) # emitted fluorescence attenuation
511 trans_SelfAbsorp = sample.scale # for elements that are not part of substrate
512 if (atom in sample.ElemListFY):
513 if atom.tag=='substrate1':
514 trans_SelfAbsorp = sample.scale1 # for elements that are part of top substrate
515 if atom.tag=='substrate2':
516 trans_SelfAbsorp = sample.scale2 # for elements that are part of bottom substrate
517## ----------------------------------------------------------------------------
518 trans = Assemble_Detector(emit_eV, xHe, xAl,xKapton, WD, xsw)
519 intensity = con * fy * emit_prob * xsect * trans * trans_SelfAbsorp
520 if intensity<LoLimit: continue # skip weak emission, arbtraray limit =1e-10.
521 if intensity>intensity_max: intensity_max=intensity
522 xx.append(emit_eV); yy.append(intensity); tag.append(name)
523 for ix in range(len(yy)):
524 yy[ix]=yy[ix]/intensity_max*100.00 # makes the strongest line to 100.0
525 out1='%s\t%f\t%f \n' % (tag[ix], xx[ix], yy[ix])
526 if Print2Screen=='Yes': print(out1)
527 fo.write(out1)
528 if Print2Screen=='Yes': print(out2)
529 fo.write(out2)
530 fo.close()
531 out1=sim_GaussPeaks(xx, yy, det_res, eV0) # det_res: detector resoultion for Gaussian width (global variable)
532 if Include_SelfAbsorption=='Yes':
533 sample.txt=sample.txt+text1 # sample.txt is combined to output of cal_NetYield
534 text=cal_NetYield2(eV0, Atoms, xHe, xAl, xKapton, WD, xsw, sample=sample) # calculate net yield with weight-averaged emission
535 print(out2)
536 return text # cal_NetYield2 output is str with number densities of elements
539def sim_GaussPeaks(xx, yy, width, eV0): #xx, yy: lists, width: a peak width, eV0: incident energy
540 xline=[]; yline=[]; dX=10.0; minX=fluo_emit_min #10eV steps, lowest-->fluo_emit_min
541 amp=100 # arbitrary multiplier to shift up spectrum
542 NumOfSteps = int((eV0-minX)/dX)
543 NumOfPeaks = len(xx)
544 for ix in range(NumOfSteps+1):
545 xline.append(0); yline.append(0)
546 for (iy,peak) in enumerate(xx):
547 X0=float(peak)
548 Y0=float(yy[iy])
549 for ix in range(NumOfSteps+1):
550 energy = minX + ix*dX
551 inten = Y0*math.exp(-((energy-X0)/width)**2)*amp
552 xline[ix]=energy
553 yline[ix]+=inten
554 outputfile='simSpectrum_plot.txt'
555 fo=open(outputfile, 'w')
556# DetectorLimit=1e5 # upperlimit for total counts to 1e5 (1e5 CPS)
557 LoLimit=0.001 # low limit for each channel
558# total=0.0
559 factor=1.0
560 for ix in range(NumOfSteps+1):
561 if yline[ix]<LoLimit: yline[ix]=LoLimit
562# total+=yline[ix] # add counts
563# factor=total/DetectorLimit
564 for ix in range(NumOfSteps+1):
565 yline[ix]=yline[ix]/factor
566 out1=str(xline[ix])+'\t'+str(yline[ix])+'\n'
567 fo.write(out1)
568 fo.close()
569 #return xline, yline
572class input_param:
573 def __init__(self, eV0=14000,
574 Atoms=[],
575 xHe=0.0,
576 xAl=0,
577 xKap=0,
578 WD=6.0,
579 xsw=0):
580 if Atoms==[]:
581 atom1=ElemFY()
582 Atoms.append(atom1)
583 self.eV0=eV0
584 #incident x-ray energy in eV
585 list1=[]; list2=[]
586 for item in Atoms:
587 list1.append(item.AtSym)
588 list2.append(item.Conc)
589 self.Atoms=list1
590 #elements list
591 self.Conc=list2
592 #relative concentrations list
593 self.xHe=xHe
594 #He gas path before collimator?
595 self.xAl=xAl
596 #number of Al foils as attenuator
597 self.xKap=xKap
598 #number of Kapton foils as attenuator
599 self.WD=WD
600 #working distance in cm
601 self.xsw=xsw
602 #x-ray standing wave setup? WD=3.0 for xsw=1
606# ----------------------------------------------------------------
610if __name__=='__main__':
611 testing = 0
612 if testing:
613 #atom0='Fe'
614 #emission0='Ka'
615 #eV0=10000.
616 #edge0='K'
617 #eV1=6400.
618 #mat0=Material('SiO2', 2.2, 0.001)
619 #mat0.getLa(8000)
620 #print(mat0.la, mat0.trans)
621 #print(Assemble_QuadVortex(eV1))
622 #print(Assemble_Collimator(eV1, xHe=1, xAl=0,xKapton=0, WD=6.0, xsw=0))
623 # MN replace:
624 matrix=SampleMatrix2('CaCO3', f1f2.nominal_density('CaCO3'), 0.001,'Fe2O3', f1f2.nominal_density('Fe2O3'), 0.001, 45., 'all')
625 eV0=7500.
626 Atoms=[]
627 atom=ElemFY('La', 10e-6); Atoms.append(atom)
628 atom=ElemFY('Ce', 10e-6); Atoms.append(atom)
629 atom=ElemFY('Nd', 10e-6); Atoms.append(atom)
630 for (ii, item) in enumerate(matrix.ElemListFY):
631 pass
632 sim_spectra(eV0, Atoms, sample=matrix) # xKapton=-1 to remove WD60, -2 for WD30
633 else:
634 # March 2012 for Nov2011 beamtime
635 eV0 = 10000.
636 print2screen=0
637 Atoms = []
638 atom=ElemFY('Al', 10e-6); Atoms.append(atom)
639 atom=ElemFY('Si', 10e-6); Atoms.append(atom)
640 atom=ElemFY('Ca', 10e-6); Atoms.append(atom)
641 atom=ElemFY('Cr', 10e-6); Atoms.append(atom)
642 atom=ElemFY('Mn', 10e-6); Atoms.append(atom)
643 atom=ElemFY('Fe', 10e-6); Atoms.append(atom)
644 atom=ElemFY('Ni', 10e-6); Atoms.append(atom)
645 #sim_spectra(eV0, Atoms, xHe=1, xAl=0, xKapton=0, WD=3.0, xsw=1, sample='')
646 #Assemble_QuadVortex(1486.56)
647 #Assemble_Collimator(1486.56, 1, 0, 0, 3., 1)
648 #
649 air_path = 1.088
650 He_path = 1.912
651 air=Material('N1.56O0.48C0.03Ar0.01Kr0.000001Xe0.0000009', 0.0013, air_path)
652 kapton=Material('C22H10O4N2', 1.42, 0.000762) # 0.3mil thick
653 HeGas=Material('He', 0.00009, He_path)
654 AlFoil=Material('Al', 2.72, 0.00381) # 1.5mil thick
655 #
656 from math import *
657 emitE = [
658 1486.4, 1557.0, 1739.6, 1837.0, 3691.1, 4013.1, 5411.6,
659 5947.0, 5896.5, 6492.0, 6400.8, 7059.6, 7474.4, 8266.6
660 ]
661 for eV1 in emitE:
662 air.getLa(eV1, 1) # 1 means number of layers here.
663 print( eV1, air.la, exp(-1./air.la))
666'''
667def AtSym2FY(AtSym, Shell): #returns fluorescence yield using atomic symbol and edge(K,L1,L2,L3,M)
668 out=elam.use_ElamFY(AtSym, Shell)
669 #AtSym, edge, edge-energy, FY, [ [transition, emission, energy, probability],[]...]
670 FY=out[3]
671 return FY
673class Element:
674 def __init__(self, AtNum, AtWt, f1, f2, Xsection):
675 self.AtNum=AtNum
676 self.AtWt=AtWt #atomic weight g/mol
677 self.f1=f1 #real part of scattering factor
678 self.f2=f2 #imaginary part of scattering factor
679 self.Xsection=Xsection #atomic crossection Barns/atom
680 # f1, f2, Xsection depends on incident x-ray energy
683def setElement(AtSym, shell, eV0):
684 AtNum=AtSym2AtNum(AtSym)
685 AtWt=AtSym2AtWt(AtSym)
686 f1f2=AtNum2f1f2Xsect(AtNum, eV0)
687 AtSym=Element(AtNum, AtWt, f1f2[0], f1f2[1], f1f2[2])
688 return AtSym
691class input_param:
692 def __init__(self, eV0=14000,
693 Atoms=[],
694 xHe=0.0,
695 xAl=0,
696 xKap=0,
697 WD=6.0,
698 xsw=0):
699 if Atoms==[]:
700 atom1=ElemFY()
701 Atoms.append(atom1)
702 self.eV0=eV0
703 #incident x-ray energy in eV
704 list1=[]; list2=[]
705 for item in Atoms:
706 list1.append(item.AtSym)
707 list2.append(item.Conc)
708 self.Atoms=list1
709 #elements list
710 self.Conc=list2
711 #relative concentrations list
712 self.xHe=xHe
713 #He gas path before collimator?
714 self.xAl=xAl
715 #number of Al foils as attenuator
716 self.xKap=xKap
717 #number of Kapton foils as attenuator
718 self.WD=WD
719 #working distance in cm
720 self.xsw=xsw
721 #x-ray standing wave setup? WD=3.0 for xsw=1
722'''