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

1""" 

2Fluorescence Intensity calculations: 

3 Y. Choi 

4 

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' 

15 

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""" 

45 

46 

47import math 

48import numpy 

49import sys 

50from larch.utils.physical_constants import AVOGADRO, BARN 

51from larch.xray import xray_delta_beta, chemparse 

52 

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 

56 

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 

94 

95 

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 

102 

103 

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 

274 

275 

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. 

288 

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 

302 

303 

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 

348 

349 

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 

355 

356 

357 

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 

453 

454 

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 

537 

538 

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 

570 

571 

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 

603 

604 

605 

606# ---------------------------------------------------------------- 

607 

608 

609 

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)) 

664 

665 

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 

672 

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 

681 

682 

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 

689 

690 

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'''