1 import os
2 import numpy as np
3 from scipy import interpolate
4 from matplotlib.tri import Triangulation, LinearTriInterpolator
5 import chianti.data as chdata
6 import chianti.util as util
7 import chianti.io as chio
8 import chianti.constants as const
9 import chianti.Gui as chgui
10 ip = chdata.Ip
11 MasterList = chdata.MasterList
12
14 '''
15 The top level class for continuum calculations.
16
17 includes methods for the calculation of the free-free and free-bound continua.
18 '''
19 - def __init__(self, ionStr, temperature, abundance=0, abundanceName=0, em=0, verbose=0):
20 nameDict = util.convertName(ionStr)
21 self.Z = nameDict['Z']
22 self.Ion = nameDict['Ion']
23 self.IonStr = ionStr
24 self.Dielectronic = 0
25 self.Defaults = chdata.Defaults
26 self.AbundanceName = self.Defaults['abundfile']
27 self.IoneqName = self.Defaults['ioneqfile']
28
29
30
31 if self.Ion == 1:
32 if verbose:
33 print(' for %s this is the neutral ions an does not produce a continuum'%(ionStr))
34 return
35
36
37 self.Ipr = ip[self.Z-1, self.Ion-2]
38
39 if type(temperature) == float and temperature > 0.:
40 self.Temperature = np.asarray(temperature,'float64')
41 elif type(temperature) == list or type(temperature) == tuple or type(temperature) == np.ndarray:
42 temperature = np.asarray(temperature, 'float64')
43 self.Temperature = temperature
44
45 if abundance:
46 self.Abundance = abundance
47 elif abundanceName:
48 if abundanceName in sorted(chdata.Abundance.keys()):
49 self.AbundanceName = abundanceName
50 self.Abundance = chdata.Abundance[self.AbundanceName]['abundance'][self.Z-1]
51 else:
52 abundChoices = sorted(chdata.Abundance.keys())
53
54
55 abundChoice = chgui.gui.selectorDialog(abundChoices,label='Select Abundance name')
56 abundChoice_idx = abundChoice.selectedIndex
57 self.AbundanceName = abundChoices[abundChoice_idx[0]]
58 abundanceName = self.AbundanceName
59 print(' Abundance chosen: %s '%(self.AbundanceName))
60 self.Abundance = chdata.Abundance[self.AbundanceName]['abundance'][self.Z-1]
61 else:
62 self.AbundanceName = self.Defaults['abundfile']
63 self.Abundance = chdata.Abundance[self.AbundanceName]['abundance'][self.Z-1]
64
65 self.ioneqOne()
66
67 if type(em) == int and em == 0:
68 em = np.ones_like(self.Temperature, 'float64')
69 self.Em = em
70 elif type(em) == float and em > 0.:
71 em = np.ones_like(self.Temperature, 'float64')*em
72 self.Em = em
73 elif type(em) == list or type(em) == tuple or type(em) == np.ndarray:
74 em = np.asarray(em, 'float64')
75 self.Em = em
76
77
78
80 '''Calculates the free-bound (radiative recombination) continuum emissivity of an ion.
81
82 Uses the Gaunt factors of Karzas, W.J, Latter, R, 1961, ApJS, 6, 167
83 for recombination to the ground level, the photoionization cross sections of
84 Verner and Yakovlev, 1995, A&ASS, 109, 125
85 are used to develop the free-bound cross section
86 provides emissivity = ergs cm^-2 s^-1 str^-1 Angstrom ^-1 for an individual ion
87 does not include the elemental abundance or ionization fraction
88 the specified ion is the target ion'''
89
90 wvl = np.asarray(wvl, 'float64')
91 temperature = self.Temperature
92
93
94
95 if hasattr(self, 'Fblvl'):
96 fblvl = self.Fblvl
97 else:
98 fblvlname = util.zion2filename(self.Z,self.Ion-1)+'.fblvl'
99 self.Fblvl = chio.fblvlRead(fblvlname)
100 fblvl = self.Fblvl
101 if 'errorMessage' in fblvl.keys():
102 self.FreeBound = fblvl
103 return
104
105
106
107 if hasattr(self, 'rFblvl'):
108 rFblvl = self.rFblvl
109 else:
110 if self.Ion == self.Z+1:
111
112 rFblvl = {'mult':[1., 1.]}
113 else:
114 rfblvlname = util.zion2filename(self.Z,self.Ion)+'.fblvl'
115 self.rFblvl = chio.fblvlRead(rfblvlname)
116 rFblvl = self.rFblvl
117 if 'errorMessage' in rFblvl.keys():
118 self.FreeBound = rFblvl
119 return
120
121
122
123 nlvls = len(fblvl['lvl'])
124
125 pqn = fblvl['pqn']
126
127 l = fblvl['l']
128
129 ecm = fblvl['ecm']
130
131 mult = fblvl['mult']
132 multr = rFblvl['mult']
133
134
135
136
137
138
139 iprcm = self.Ipr/const.invCm2Ev
140
141
142
143
144 if hasattr(self,'Klgfb'):
145 klgfb = self.Klgfb
146 else:
147 self.Klgfb = chio.klgfbRead()
148 klgfb = self.Klgfb
149
150
151 nWvl = wvl.size
152 nTemp = temperature.size
153
154 if verner:
155 lvl1 = 1
156 else:
157 lvl1 = 0
158
159 if (nTemp > 1) and (nWvl > 1):
160 mask = np.zeros((nlvls,nTemp,nWvl),'Bool')
161 fbrate = np.zeros((nlvls,nTemp,nWvl),'float64')
162 expf = np.zeros((nlvls,nTemp,nWvl),'float64')
163 ratg = np.zeros((nlvls),'float64')
164 if verner:
165 self.vernerCross(wvl)
166 vCross = self.VernerCross
167
168 ratg[0] = float(mult[0])/float(multr[0])
169 iprLvlEv = self.Ipr - const.invCm2Ev*ecm[0]
170 iprLvlErg = const.ev2Erg*iprLvlEv
171 iprLvlCm = (iprcm - ecm[0])
172 for itemp in range(nTemp):
173 mask[0,itemp] = 1.e+8/wvl < (iprcm - ecm[0])
174 expf[0,itemp] = np.exp((iprLvlErg - 1.e+8*const.planck*const.light/wvl)/(const.boltzmann*temperature[itemp]))
175 fbrate[0,itemp] = (const.planck*const.light/(1.e-8*wvl))**5*const.verner*ratg[0]*expf[0,itemp]*vCross/temperature[itemp]**1.5
176 for ilvl in range(lvl1,nlvls):
177 iprLvlEv = self.Ipr - const.invCm2Ev*ecm[ilvl]
178 iprLvlErg = const.ev2Erg*iprLvlEv
179 scaledE = np.log(const.ev2Ang/(iprLvlEv*wvl))
180 thisGf = klgfb['klgfb'][pqn[ilvl]-1, l[ilvl]]
181 spl = interpolate.splrep(klgfb['pe'], thisGf)
182 gf = np.exp(interpolate.splev(scaledE, spl))
183 ratg[ilvl] = float(mult[ilvl])/float(multr[0])
184
185 for itemp in range(nTemp):
186 expf[ilvl,itemp] = np.exp((iprLvlErg - 1.e+8*const.planck*const.light/wvl)/(const.boltzmann*temperature[itemp]))
187 mask[ilvl,itemp] = 1.e+8/wvl < (iprcm - ecm[ilvl])
188 fbrate[ilvl,itemp] = const.freeBound*ratg[ilvl]*(iprLvlErg**2/float(pqn[ilvl]))*gf*expf[ilvl,itemp]/(temperature[itemp]**1.5*(wvl)**2)
189 fbrma = np.ma.array(fbrate)
190 fbrma.mask = mask
191 fbrma.fill_value = 0.
192 fbRate = (fbrma).sum(axis=0)
193 fbRate.fill_value = 0.
194 self.FreeBoundEmiss = {'emiss':fbRate.data, 'temperature':temperature,'wvl':wvl}
195
196 elif (nTemp == 1) and (nWvl > 1):
197 mask = np.zeros((nlvls,nWvl),'Bool')
198 fbrate = np.zeros((nlvls,nWvl),'float64')
199 expf = np.zeros((nlvls,nWvl),'float64')
200
201 if verner:
202 self.vernerCross(wvl)
203 vCross = self.VernerCross
204
205 ratg = float(mult[0])/float(multr[0])
206 iprLvlEv = self.Ipr - const.invCm2Ev*ecm[0]
207 iprLvlErg = const.ev2Erg*iprLvlEv
208 iprLvlCm = (iprcm - ecm[0])
209
210 mask[0] = 1.e+8/wvl < (iprcm - ecm[0])
211 expf[0] = np.exp((iprLvlErg - 1.e+8*const.planck*const.light/wvl)/(const.boltzmann*temperature))
212 fbrate[0] = (const.planck*const.light/(1.e-8*wvl))**5*const.verner*ratg*expf[0]*vCross/temperature**1.5
213 for ilvl in range(lvl1,nlvls):
214 iprLvlEv = self.Ipr - const.invCm2Ev*ecm[ilvl]
215 iprLvlErg = const.ev2Erg*iprLvlEv
216 iprLvlCm = (iprcm - ecm[ilvl])
217 mask[ilvl] = 1.e+8/wvl < iprLvlCm
218
219 scaledE = np.log(const.ev2Ang/(iprLvlEv*wvl))
220 thisGf = klgfb['klgfb'][pqn[ilvl]-1, l[ilvl]]
221 spl = interpolate.splrep(klgfb['pe'], thisGf)
222 gf = np.exp(interpolate.splev(scaledE, spl))
223 ratg = float(mult[ilvl])/float(multr[0])
224 expf[ilvl] = np.exp((iprLvlErg - 1.e+8*const.planck*const.light/wvl)/(const.boltzmann*temperature))
225 fbrate[ilvl] = const.freeBound*ratg*expf[ilvl]*(iprLvlErg**2/float(pqn[ilvl]))*gf/(temperature**1.5*(wvl)**2)
226 fbrma = np.ma.array(fbrate)
227 fbrma.mask = mask
228 fbrma.fill_value = 0.
229 fbRate = fbrma.sum(axis=0)
230 fbRate.fill_value = 0.
231
232
233
234
235 self.FreeBoundEmiss = {'fbRate':fbRate, 'emiss':fbRate.data, 'temperature':temperature,'wvl':wvl, 'mask':mask, 'expf':expf,'vCross':vCross}
236 else:
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271 self.FreeBoundEmiss = {'errorMessage':' this is the case of a single wavelength'}
272
273
274
276 '''
277 to calculate the free-bound (radiative recombination) continuum rate coefficient of an ion, where
278 the ion is taken to be the recombined ion,
279 including the elemental abundance and the ionization equilibrium population
280 uses the Gaunt factors of Karzas, W.J, Latter, R, 1961, ApJS, 6, 167
281 for recombination to the ground level, the photoionization cross sections of
282 Verner and Yakovlev, 1995, A&ASS, 109, 125
283 are used to develop the free-bound cross section
284 includes the elemental abundance and the ionization fraction
285 provides emissivity = ergs cm^-2 s^-1 str^-1 Angstrom ^-1
286 '''
287 wvl = np.asarray(wvl, 'float64')
288 temperature = self.Temperature
289
290 if hasattr(self, 'IoneqOne'):
291 gIoneq = self.IoneqOne
292 else:
293 self.ioneqOne()
294 gIoneq = self.IoneqOne
295
296
297 if not np.any(gIoneq) > 0:
298 self.FreeBound = {'errorMessage':' no non-zero values of ioneq'}
299 return
300
301 em = self.Em
302
303
304
305 if hasattr(self,'Fblvl'):
306 fblvl = self.Fblvl
307 else:
308 fblvlname = util.zion2filename(self.Z,self.Ion-1)+'.fblvl'
309 self.Fblvl = chio.fblvlRead(fblvlname)
310 fblvl = self.Fblvl
311
312 if 'errorMessage' in fblvl.keys():
313 self.FreeBound = fblvl
314 return
315
316
317
318 if hasattr(self,'rFblvl'):
319 rFblvl = self.rFblvl
320 else:
321 if self.Ion == self.Z + 1:
322
323 rFblvl = {'mult':[1., 1.]}
324 else:
325 rfblvlname = util.zion2filename(self.Z,self.Ion)+'.fblvl'
326 self.rFblvl = chio.fblvlRead(rfblvlname)
327 rFblvl = self.rFblvl
328 if 'errorMessage' in rFblvl.keys():
329 self.FreeBound = rFblvl
330 return
331
332
333
334
335 abund = self.Abundance
336
337
338 nlvls = len(fblvl['lvl'])
339
340 pqn = np.asarray(fblvl['pqn'], 'float64')
341
342 l = fblvl['l']
343
344 ecm = fblvl['ecm']
345
346 mult = fblvl['mult']
347 multr = rFblvl['mult']
348
349
350
351
352
353 iprcm = self.Ipr/const.invCm2Ev
354
355
356 if hasattr(self,'Klgfb'):
357 klgfb = self.Klgfb
358 else:
359 self.Klgfb = chio.klgfbRead()
360 klgfb = self.Klgfb
361
362 nWvl = wvl.size
363 nTemp = temperature.size
364
365 if verner:
366 lvl1 = 1
367 else:
368 lvl1 = 0
369
370 nWvl = wvl.size
371 nTemp = temperature.size
372
373
374 if (nTemp > 1) and (nWvl > 1):
375 mask = np.zeros((nlvls,nTemp,nWvl),'Bool')
376 fbrate = np.zeros((nlvls,nTemp,nWvl),'float64')
377 expf = np.zeros((nlvls,nTemp,nWvl),'float64')
378 ratg = np.zeros((nlvls),'float64')
379
380 if verner:
381 self.vernerCross(wvl)
382 vCross = self.VernerCross
383 ratg[0] = float(mult[0])/float(multr[0])
384 iprLvlEv = self.Ipr - const.invCm2Ev*ecm[0]
385 iprLvlErg = const.ev2Erg*iprLvlEv
386 iprLvlCm = (iprcm - ecm[0])
387 for itemp in range(nTemp):
388 mask[0,itemp] = 1.e+8/wvl < (iprcm - ecm[0])
389 expf[0,itemp] = np.exp((iprLvlErg - 1.e+8*const.planck*const.light/wvl)/(const.boltzmann*temperature[itemp]))
390 fbrate[0,itemp] = em[itemp]*(const.planck*const.light/(1.e-8*wvl))**5*const.verner*gIoneq[itemp]*ratg[0]*expf[0,itemp]*vCross/temperature[itemp]**1.5
391 for ilvl in range(lvl1,nlvls):
392 iprLvlEv = self.Ipr - const.invCm2Ev*ecm[ilvl]
393 iprLvlErg = const.ev2Erg*iprLvlEv
394 scaledE = np.log(const.ev2Ang/(iprLvlEv*wvl))
395 thisGf = klgfb['klgfb'][pqn[ilvl]-1, l[ilvl]]
396 spl = interpolate.splrep(klgfb['pe'], thisGf)
397 gf = np.exp(interpolate.splev(scaledE, spl))
398 ratg[ilvl] = float(mult[ilvl])/float(multr[0])
399
400 for itemp in range(nTemp):
401 expf[ilvl] = np.exp((iprLvlErg - 1.e+8*const.planck*const.light/wvl)/(const.boltzmann*temperature[itemp]))
402 expf[ilvl,itemp] = np.exp((iprLvlErg - 1.e+8*const.planck*const.light/wvl)/(const.boltzmann*temperature[itemp]))
403 mask[ilvl,itemp] = 1.e+8/wvl < (iprcm - ecm[ilvl])
404 fbrate[ilvl,itemp] = em[itemp]*const.freeBound*gIoneq[itemp]*ratg[ilvl]*(iprLvlErg**2/float(pqn[ilvl]))*gf*expf[ilvl,itemp]/(temperature[itemp]**1.5*(wvl)**2)
405 fbrma = np.ma.array(fbrate)
406 fbrma.mask = mask
407 fbrma.fill_value = 0.
408 fbRate = (fbrma).sum(axis=0)
409 fbRate.fill_value = 0.
410 self.FreeBound = {'rate':abund*fbRate.data, 'temperature':temperature,'wvl':wvl,'em':em}
411
412 elif (nTemp == 1) and (nWvl > 1):
413 mask = np.zeros((nlvls,nWvl),'Bool')
414 fbrate = np.zeros((nlvls,nWvl),'float64')
415 expf = np.zeros((nlvls,nWvl),'float64')
416 ratg = np.zeros((nlvls),'float64')
417 if verner:
418 self.vernerCross(wvl)
419 vCross = self.VernerCross
420
421 ratg[0] = float(mult[0])/float(multr[0])
422 iprLvlEv = self.Ipr - const.invCm2Ev*ecm[0]
423 iprLvlErg = const.ev2Erg*iprLvlEv
424 iprLvlCm = (iprcm - ecm[0])
425
426 mask[0] = 1.e+8/wvl < (iprcm - ecm[0])
427 expf[0] = np.exp((iprLvlErg - 1.e+8*const.planck*const.light/wvl)/(const.boltzmann*temperature))
428 fbrate[0] = em*(const.planck*const.light/(1.e-8*wvl))**5*const.verner*ratg[0]*expf[0]*vCross/temperature**1.5
429
430 for ilvl in range(lvl1,nlvls):
431 iprLvlEv = self.Ipr - const.invCm2Ev*ecm[ilvl]
432 iprLvlErg = const.ev2Erg*iprLvlEv
433 iprLvlCm = (iprcm - ecm[ilvl])
434
435 scaledE = np.log(const.ev2Ang/(iprLvlEv*wvl))
436 thisGf = klgfb['klgfb'][pqn[ilvl]-1, l[ilvl]]
437 spl = interpolate.splrep(klgfb['pe'], thisGf)
438 gf = np.exp(interpolate.splev(scaledE, spl))
439 mask[ilvl] = 1.e+8/wvl < iprLvlCm
440 ratg[ilvl] = float(mult[ilvl])/float(multr[0])
441 expf[ilvl] = np.exp((iprLvlErg - 1.e+8*const.planck*const.light/wvl)/(const.boltzmann*temperature))
442 fbrate[ilvl] = em*const.freeBound*ratg[ilvl]*(iprLvlErg**2/float(pqn[ilvl]))*expf[ilvl]*gf/(temperature**1.5*(wvl)**2)
443 fbrma = np.ma.array(fbrate)
444 fbrma.mask = mask
445 fbrma.fill_value = 0.
446
447 fbRate = abund*gIoneq*fbrma.sum(axis=0)
448 fbRate.fill_value = 0.
449 self.FreeBound = {'rate':fbRate.data, 'temperature':temperature,'wvl':wvl}
450
451 else:
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484 self.FreeBoundEmiss = {'errorMessage':' this is the case of a single wavelength'}
485
486
487
488
490 '''
491 to calculate the free-bound (radiative recombination) energy loss rate coefficient of an ion,
492 the ion is taken to be the recombined iion,
493 including the elemental abundance and the ionization equilibrium population
494 uses the Gaunt factors of Karzas, W.J, Latter, R, 1961, ApJS, 6, 167
495 provides rate = ergs cm^-2 s^-1
496 '''
497
498 temperature = self.Temperature
499
500 if hasattr(self, 'Fblvl'):
501 fblvl = self.Fblvl
502 else:
503 fblvlname = util.zion2filename(self.Z,self.Ion-1)+'.fblvl'
504 if os.path.isfile(fblvlname):
505 self.Fblvl = chio.fblvlRead(fblvlname)
506 fblvl = self.Fblvl
507 else:
508 self.FreeBoundLoss = {'errorMessage':' file does not exist %s .fblvl'%(fblvlname)}
509 return
510
511
512 if hasattr(self, 'rFblvl'):
513 rFblvl = self.rFblvl
514 else:
515 if self.Ion == self.Z + 1:
516
517 rFblvl = {'mult':[1., 1.]}
518 else:
519 rfblvlname = util.zion2filename(self.Z,self.Ion)+'.fblvl'
520 if os.path.isfile(rfblvlname):
521 self.rFblvl = chio.fblvlRead(rfblvlname)
522 rFblvl = self.rFblvl
523 else:
524 self.FreeBoundLoss = {'errorMessage':' file does not exist %s .fblvl'%(rfblvlname)}
525 return
526
527 gIoneq = self.IoneqOne
528
529 abund = self.Abundance
530
531
532 nlvls = len(fblvl['lvl'])
533
534 pqn = np.asarray(fblvl['pqn'], 'float64')
535
536 l = fblvl['l']
537
538 ecm = fblvl['ecm']
539
540 mult = fblvl['mult']
541 multr = rFblvl['mult']
542
543
544
545
546 iprcm = self.Ipr/const.invCm2Ev
547
548
549 if hasattr(self, 'Klgfb'):
550 klgfb = self.Klgfb
551 else:
552 self.Klgfb = chio.klgfbRead()
553 klgfb = self.Klgfb
554
555 nTemp = temperature.size
556
557
558
559
560
561
562 if 'mult' in fblvl.keys() and 'mult' in rFblvl.keys():
563
564 nlvls = len(fblvl['lvl'])
565
566 pqn = fblvl['pqn']
567
568 l = fblvl['l']
569
570 ecm = fblvl['ecm']
571 mult = fblvl['mult']
572 multr = rFblvl['mult']
573 fbrate = np.zeros((nlvls,nTemp),'float64')
574 ratg = np.zeros((nlvls),'float64')
575 for ilvl in range(nlvls):
576
577
578
579 hnuEv = 1.5*const.boltzmann*temperature/const.ev2Erg
580 iprLvlEv = self.Ipr - const.invCm2Ev*ecm[ilvl]
581 scaledE = np.log(hnuEv/iprLvlEv)
582 thisGf = klgfb['klgfb'][pqn[ilvl]-1, l[ilvl]]
583 spl = interpolate.splrep(klgfb['pe'], thisGf)
584 gf = np.exp(interpolate.splev(scaledE, spl))
585 ratg[ilvl] = float(mult[ilvl])/float(multr[0])
586 iprLvlErg = const.ev2Erg*iprLvlEv
587 fbrate[ilvl] = ratg[ilvl]*(iprLvlErg**2/float(pqn[ilvl]))*gf/np.sqrt(temperature)
588 fbRate = abund*gIoneq*const.freeBoundLoss*(fbrate.sum(axis=0))
589 else:
590 fbRate = np.zeros((nTemp),'float64')
591 self.FreeBoundLoss = {'rate':fbRate, 'temperature':temperature}
592
593
594
596 '''Calculates the photoionization cross section.
597
598 The data are from Verner and Yakovlev
599 the cross section refers to the next lower ionization stage'''
600 try:
601 vernerDat = self.VernerDat
602 except:
603 self.VernerDat = chio.vernerRead()
604 vernerDat = self.VernerDat
605 z = self.Z
606 stage = self.Ion
607
608 iprcm = self.Ipr/const.invCm2Ev
609 ecm = self.Fblvl['ecm']
610
611 en = const.ev2Ang/wvl
612 y = en/vernerDat['e0'][z,stage-1]
613 fy= vernerDat['sig0'][z,stage-1]*((y - 1.)**2 + vernerDat['yw'][z,stage-1]**2) * y**(-5.5 - vernerDat['l'][z,stage-1] + 0.5*vernerDat['p'][z,stage-1]) * (1. + np.sqrt(y/vernerDat['ya'][z,stage-1]))**(-vernerDat['p'][z,stage-1])
614
615
616 mask = (1.e+8/wvl) < (iprcm - ecm[0])
617 vCross = np.ma.array(fy)
618 vCross.mask = mask
619 vCross.fill_value = 0.
620
621 self.VernerCross = vCross*1.e-18
622
623
624
626 '''
627 Calculates the free-free emission for a single ion.
628
629 Includes elemental abundance and ionization equilibrium population
630 and the emission measure (em) if specified
631 Uses Itoh where valid and Sutherland elsewhere
632 '''
633 em=self.Em
634 temperature = self.Temperature
635 nTemp = temperature.size
636
637 if self.Ion == 1:
638 self.FreeFree = {'errorMessage':' freefree is not produced by neutrals'}
639 return
640 else:
641 wvl = np.asarray(wvl, 'float64')
642 ffs = self.sutherland(wvl)
643
644 ffi = self.itoh(wvl)
645 ff = ffs['suthFf']
646 if not 'errorMessage' in ffi.keys():
647 iff = ffi['itohFf']
648 itohMask = np.logical_not(iff.mask)
649 ff[itohMask] = iff[itohMask]
650 ff = np.asarray(ff,'float64')
651
652 if hasattr(self, 'IoneqOne'):
653 gIoneq = self.IoneqOne
654 else:
655 self.ioneqOne()
656 gIoneq = self.IoneqOne
657
658
659 if not np.any(gIoneq) > 0:
660 self.FreeFree = {'errorMessage':' no non-zero values of ioneq'}
661 return
662
663 if type(gIoneq) == float:
664
665 if gIoneq == 0.:
666 ffRate = np.zeros(wvl.size)
667 self.FreeFree = {'rate':ffRate, 'temperature':self.Temperature,'wvl':wvl}
668 return
669 else:
670 if gIoneq.sum() == 0.:
671 ffRate = np.zeros((self.Temperature.size, wvl.size), 'float64')
672 self.FreeFree = {'rate':ffRate, 'temperature':self.Temperature,'wvl':wvl}
673 return
674
675
676
677
678
679
680 if wvl.size > 1:
681
682
683
684 try:
685 abund = self.Abundance
686 except:
687 self.AbundanceAll = util.abundanceRead(abundancename = self.AbundanceName)
688 self.Abundance = self.AbundanceAll['abundance'][self.Z-1]
689 abund = self.Abundance
690
691 if nTemp == 1:
692 ffRate = em*const.freeFree*(self.Z)**2*abund*gIoneq*ff
693 elif nTemp > 1:
694 ffRate = np.zeros((nTemp,wvl.size),'float64')
695 for it in range(nTemp):
696 ffRate[it] = (em[it]*const.freeFree*(self.Z)**2*abund*gIoneq[it]*ff[it])
697 self.FreeFree = {'rate':ffRate, 'temperature':self.Temperature,'wvl':wvl,'ff':ff}
698
699
700
702 '''Calculates the free-free emissivity for a single ion.
703 does not include element abundance or ionization fraction
704 Uses Itoh where valid and Sutherland elsewhere'''
705 if self.Ion == 1:
706 self.FreeFreeEmiss = {'errorMessage':' freefree is not produced by neutrals'}
707 else:
708 wvl = np.asarray(wvl, 'float64')
709 ffs = self.sutherland(wvl)
710 ff = ffs['suthFf']
711 ffi = self.itoh(wvl)
712 if 'errorMessage' not in ffi.keys():
713 iff = ffi['itohFf']
714 itohMask = np.logical_not(iff.mask)
715 ff[itohMask] = iff[itohMask]
716
717 ffRate = (const.freeFree*(self.Z)**2*ff).squeeze()
718 self.FreeFree = {'rate':ffRate, 'temperature':self.Temperature,'wvl':wvl}
719
720
721
723 '''
724 Calculates the total free-free emission for a single ion.
725
726 Includes elemental abundance and ionization equilibrium population.
727
728 Uses Itoh where valid and Sutherland elsewhere
729 '''
730 temperature = self.Temperature
731
732
733 if self.Ion == 1:
734 self.FreeFree = {'errorMessage':' freefree is not produced by neutrals'}
735 else:
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759 gff = 1.2
760
761 if hasattr(self, 'IoneqOne'):
762 gIoneq = self.IoneqOne
763 else:
764 self.ioneqOne()
765 gIoneq = self.IoneqOne
766
767 if hasattr(self, 'Abundance'):
768 abund = self.Abundance
769 else:
770 self.AbundanceAll = util.abundanceRead(abundancename = self.AbundanceName)
771 self.Abundance = self.AbundanceAll['abundance'][self.Z-1]
772 abund = self.Abundance
773
774
775
776
777
778 ffRate = const.freeFreeLoss*(self.Z)**2*abund*gIoneq*gff*np.sqrt(temperature)
779 self.FreeFreeLoss = {'rate':ffRate, 'temperature':temperature}
780
781
782
784 '''A Python version of the CHIANTI IDL procedure karzas_xs.
785
786 Interpolates free-bound gaunt factor of Karzas and Latter, (1961, Astrophysical Journal
787 Supplement Series, 6, 167) as a function of wavelength (wvl).'''
788 try:
789 klgfb = self.Klgfb
790 except:
791 self.Klgfb = util.klgfbRead()
792 klgfb = self.Klgfb
793
794 sclE = np.log(self.Ip/(wvl*const.ev2ang))
795 thisGf = klgfb['klgfb'][n-1, l]
796 spl = interpolate.splrep(klgfb['pe'], thisGf)
797 gf = interpolate.splev(sclE, spl)
798 return gf
799
800
801
802
803 - def itoh(self, wvl):
804 '''Calculates free-free emission with the free-free gaunt factors of Itoh et al. (ApJS 128, 125, 2000).
805
806 the relativistic values are valid for 6. < log10(T) < 8.5 and -4. < log10(u) < 1.'''
807 wvl = np.array(wvl, 'float64')
808 try:
809 itohCoef = self.ItohCoef
810 except:
811 self.ItohCoef = chio.itohRead()['itohCoef'][self.Z-1].reshape(11, 11)
812 itohCoef = self.ItohCoef
813 try:
814 t = (np.log10(self.Temperature) -7.25)/1.25
815 except:
816 errorMessage = ' temperature undefined in continuum.itoh'
817 print(errorMessage)
818 return {'errorMessage':errorMessage}
819 if type(self.Temperature) == float:
820 nTemp = 1
821 else:
822 nTemp = self.Temperature.size
823
824 nWvl = wvl.size
825
826 if (nTemp > 1) and (nWvl > 1):
827 u = (const.planck*const.light*1.e+8/const.boltzmann)*np.outer(1./self.Temperature, 1./wvl )
828 lU = (np.log10(u) + 1.5)/2.5
829 lT = (np.log10(self.Temperature) -7.25)/1.25
830 g = np.zeros((nTemp, nWvl), 'float64')
831 rad = np.ma.zeros((nTemp, nWvl), 'float64')
832 for itemp in range(nTemp):
833 for j in range(11):
834 for i in range(11):
835 g[itemp]+= itohCoef[i,j]*(lT[itemp]**i)*(lU[itemp]**j)
836 rad[itemp] = (1./wvl)**2*g[itemp]*np.exp(-u[itemp])/np.sqrt(self.Temperature[itemp])
837 tArray = np.zeros((1, len(self.Temperature)), 'float64')
838 tArray[0] = self.Temperature
839 t2Array = np.repeat(tArray.transpose(), len(wvl), axis=1)
840 nonValidT1 = np.log10(t2Array) < 6.
841 nonValidT2 = np.log10(t2Array) > 8.5
842 nonValidT = np.logical_or(nonValidT1, nonValidT2)
843 nonValidU1 = np.log10(u) < -4.
844 nonValidU2 = np.log10(u) > 1.
845 nonValidU = np.logical_or(nonValidU1, nonValidU2)
846 nonValid = np.logical_or(nonValidT, nonValidU)
847 rad.mask = nonValid
848 rad.set_fill_value(0.)
849 g=np.ma.array(g, mask=nonValid, fill_value=0.)
850 return {'itohGff':g, 'itohFf':rad}
851 elif (nTemp > 1) and (nWvl == 1):
852 u = (const.planck*const.light*1.e+8/const.boltzmann)/(self.Temperature*wvl )
853 lU = (np.log10(u) + 1.5)/2.5
854 lT = (np.log10(self.Temperature) -7.25)/1.25
855 g = np.zeros((nTemp), 'float64')
856 rad = np.ma.zeros((nTemp), 'float64')
857 for itemp in range(nTemp):
858 for j in range(11):
859 for i in range(11):
860 g[itemp]+= itohCoef[i,j]*(lT[itemp]**i)*(lU[itemp]**j)
861 rad[itemp] = (1./wvl)**2*g[itemp]*np.exp(-u[itemp])/np.sqrt(self.Temperature[itemp])
862 nonValidT1 = np.log10(self.Temperature) < 6.
863 nonValidT2 = np.log10(self.Temperature) > 8.5
864 nonValidT = np.logical_or(nonValidT1, nonValidT2)
865 nonValidU1 = np.log10(u) < -4.
866 nonValidU2 = np.log10(u) > 1.
867 nonValidU = np.logical_or(nonValidU1, nonValidU2)
868 nonValid = np.logical_or(nonValidT, nonValidU)
869 rad.mask = nonValid
870 rad.set_fill_value(0.)
871 g=np.ma.array(g, mask=nonValid, fill_value=0.)
872 return {'itohGff':g, 'itohFf':rad}
873 elif (nTemp == 1) and (nWvl > 1):
874 if (np.log10(self.Temperature) < 6.) or (np.log10(self.Temperature > 8.5)):
875 errorMessage ='invalid temperature in continuum.itoh()'
876 return {'errorMessage':errorMessage}
877 else:
878 u = (const.planck*const.light*1.e+8/const.boltzmann)/(self.Temperature*wvl )
879 lU = (np.log10(u) + 1.5)/2.5
880 lT = (np.log10(self.Temperature) -7.25)/1.25
881 g = np.zeros(nWvl, 'float64')
882 rad = np.ma.zeros((nWvl), 'float64')
883 for j in range(11):
884 for i in range(11):
885 g+= itohCoef[i,j]*(lT**i)*(lU**j)
886 rad = np.ma.array((1./wvl)**2*g*np.exp(-u)/np.sqrt(self.Temperature), 'Float64')
887 nonValidU1 = np.log10(u) < -4.
888 nonValidU2 = np.log10(u) > 1.
889 nonValidU = np.logical_or(nonValidU1, nonValidU2)
890 nonValid = nonValidU
891 rad.mask = nonValid
892 rad.set_fill_value(0.)
893 g=np.ma.array(g, mask=nonValid, fill_value=0.)
894 return {'itohGff':g, 'itohFf':rad}
895 elif (nTemp == 1) and (nWvl == 1):
896 u = (const.planck*const.light*1.e+8/const.boltzmann)/(self.Temperature*wvl )
897 if (np.log10(self.Temperature) < 6.) or (np.log10(self.Temperature > 8.5)):
898 errorMessage ='invalid temperature in continuum.itoh()'
899 return {'errorMessage':errorMessage}
900 elif (np.log10(u) < -4.) or (np.log10(u) > 8.5):
901 errorMessage ='invalid temperature/wavelength in continuum.itoh()'
902 return {'errorMessage':errorMessage}
903 else:
904 u = (const.planck*const.light*1.e+8/const.boltzmann)/(self.Temperature*wvl )
905 lU = (np.log10(u) + 1.5)/2.5
906 lT = (np.log10(self.Temperature) -7.25)/1.25
907 g = np.zeros(nWvl, 'float64')
908 rad = np.ma.zeros((nWvl), 'float64')
909 for j in range(11):
910 for i in range(11):
911 g+= itohCoef[i,j]*(lT**i)*(lU**j)
912 rad = np.ma.array((1./wvl)**2*g*np.exp(-u)/np.sqrt(self.Temperature), 'Float64')
913 nonValidU1 = np.log10(u) < -4.
914 nonValidU2 = np.log10(u) > 1.
915 nonValidU = np.logical_or(nonValidU1, nonValidU2)
916 nonValid = nonValidU
917 rad.mask = nonValid
918 rad.set_fill_value(0.)
919 return {'itohGff':g, 'itohFf':rad}
920
921
922
924 '''Calculates the free-free continuum using the free-free gaunt factor calculations of Sutherland, 1998, MNRAS, 300, 321.
925
926 the wavelengths (wvl) will be sorted, first thing'''
927
928 wvl = np.array(wvl, 'float64')
929 nWvl = wvl.size
930
931 try:
932 temperature = self.Temperature
933 except:
934 errorMessage = ' temperature undefined in continuum.sutherland'
935 print(errorMessage)
936 return {'errorMessage':errorMessage}
937
938 try:
939 gffInterpolator = self.GffInterpolator
940 except:
941 self.Gff = chio.gffRead()
942 gff = self.Gff
943 iu=(np.log10(gff['u1d']) + 4.)*10.
944 ig=(np.log10(gff['g21d']) + 4.)*5.
945 gaunt = gff['gff']
946
947 tr = Triangulation(iu,ig)
948 self.GffInterpolator = LinearTriInterpolator(tr, gaunt.flatten())
949 gffInterpolator = self.GffInterpolator
950
951
952
953
954
955
956
957
958
959
960
961 gga = np.array((float(self.Z)**2*const.ryd2erg/const.boltzmann)*(1./temperature),'float64')
962 nonValidGg1 = np.log10(gga) < -4.
963 nonValidGg2 = np.log10(gga) > 4.
964 nonValidGg = np.logical_or(nonValidGg1, nonValidGg2)
965 ggOut = np.ma.array(gga, mask = nonValidGg, fill_value=True)
966 iGg = np.ma.array((np.log10(gga) + 4.)*5., mask=nonValidGg, fill_value=0.)
967
968 if nonValidGg.sum():
969 errorMessage = 'no valid temperatures in continuum.sutherland'
970 print(errorMessage)
971 return {'errorMessage':errorMessage}
972 else:
973
974
975
976
977 nWvl = wvl.size
978 nTemp = temperature.size
979
980 if (nTemp > 1) and (nWvl > 1):
981 ff = np.ma.zeros((nWvl, nTemp), 'float64')
982 gffOut1 = np.ma.zeros((nWvl, nTemp), 'float64')
983 gffOutMask = np.zeros((nWvl, nTemp), 'Bool')
984 uuOut = np.zeros((nWvl, nTemp), 'float64')
985 for iwvl in range(nWvl):
986 uu = ((const.planck*const.light*1.e+8/const.boltzmann)/(wvl[iwvl]*temperature))
987 nonValidUu1 = np.log10(uu) < -4.
988 nonValidUu2 = np.log10(uu) > 4.
989 nonValidUu = np.logical_or(nonValidUu1, nonValidUu2)
990 gffOutMask[iwvl] = nonValidUu
991 uuOut[iwvl] = np.ma.array(uu, mask=nonValidUu, fill_value=True)
992 iUu = np.ma.array((np.log10(uu) + 4.)*10., mask=nonValidUu, fill_value=0.)
993 gffOut1[iwvl] = gffInterpolator(iUu, iGg)
994 wvlt = 1./(wvl[iwvl]**2*np.sqrt(temperature))
995 ff[iwvl] = (np.exp(-uuOut[iwvl])*gffOut1[iwvl]*wvlt)
996 gffOut1.mask = gffOutMask
997 gffOut1.set_fill_value(0.)
998 gffOut = gffOut1.transpose()
999 ff.mask = gffOutMask
1000 ff.set_fill_value(0.)
1001 return {'suthFf':ff.transpose(), 'suthGff':gffOut}
1002
1003 if (nTemp == 1) and (nWvl > 1):
1004 uu = ((const.planck*const.light*1.e+8/const.boltzmann)/(wvl*temperature))
1005 nonValidUu1 = np.log10(uu) < -4.
1006 nonValidUu2 = np.log10(uu) > 4.
1007 nonValidUu = np.logical_or(nonValidUu1, nonValidUu2)
1008 gffOutMask = nonValidUu
1009 iUu = (np.log10(uu) + 4.)*10.
1010 gffOut1 = gffInterpolator(iUu, iGg.repeat(nWvl))
1011 wvlt = 1./(wvl**2*np.sqrt(temperature))
1012 ff = np.ma.array(np.exp(-uu)*gffOut1*wvlt)
1013 ff.mask=gffOutMask
1014 ff.set_fill_value(0.)
1015 gffOut = np.ma.array(gffOut1, mask=gffOutMask, fill_value=0.)
1016 return {'suthFf':ff, 'suthGff':gffOut, 'iUu':iUu, 'gffOut1':gffOut1, 'wvlt':wvlt, 'iGg':iGg.repeat(nWvl), 'gffInterpolator':gffInterpolator}
1017
1018 else:
1019
1020
1021
1022
1023
1024 uu = (const.planck*const.light*1.e+8/const.boltzmann) /(wvl*temperature).flatten()
1025 nonValidUu1 = np.log10(uu) < -4.
1026 nonValidUu2 = np.log10(uu) > 4.
1027 nonValidUu = np.logical_or(nonValidUu1, nonValidUu2)
1028 gffOutMask = nonValidUu
1029 uuOut = np.ma.array(uu, mask=nonValidUu, fill_value=True)
1030
1031 iUu = (np.log10(uu) + 4.)*10.
1032
1033 gffOut1 = gffInterpolator(iUu, iGg.flatten())
1034
1035 wvlt = 1./(wvl**2*np.sqrt(temperature))
1036 ff1 = np.exp(-uuOut)*gffOut1*wvlt
1037 ff = np.ma.array(ff1, mask=gffOutMask, fill_value=0.)
1038 gffOut = np.ma.array(gffOut1, mask=gffOutMask, fill_value=0.)
1039 return {'suthFf':ff, 'suthGff':gffOut}
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1060 '''Provide the ionization equilibrium for the selected ion as a function of temperature.
1061 returned in self.IoneqOne
1062 this is a duplicate of the method ion.ioneqOne '''
1063
1064 try:
1065 temperature = self.Temperature
1066 except:
1067 return
1068
1069 try:
1070 ioneqAll = self.IoneqAll
1071 except:
1072 self.IoneqAll = chio.ioneqRead(ioneqname = self.Defaults['ioneqfile'])
1073 ioneqAll=self.IoneqAll
1074
1075 ioneqTemperature = ioneqAll['ioneqTemperature']
1076 Z=self.Z
1077 Ion=self.Ion
1078 Dielectronic=self.Dielectronic
1079 ioneqOne = np.zeros_like(temperature)
1080
1081 thisIoneq=ioneqAll['ioneqAll'][Z-1,Ion-1-Dielectronic].squeeze()
1082
1083 gioneq=thisIoneq > 0.
1084 goodt1=self.Temperature >= ioneqTemperature[gioneq].min()
1085 goodt2=self.Temperature <= ioneqTemperature[gioneq].max()
1086 goodt=np.logical_and(goodt1,goodt2)
1087 y2=interpolate.splrep(np.log(ioneqTemperature[gioneq]),np.log(thisIoneq[gioneq]),s=0)
1088
1089 if goodt.sum() > 0:
1090 if self.Temperature.size > 1:
1091 gIoneq=interpolate.splev(np.log(self.Temperature[goodt]),y2)
1092 ioneqOne[goodt] = np.exp(gIoneq)
1093 else:
1094 gIoneq=interpolate.splev(np.log(self.Temperature),y2)
1095 ioneqOne = np.exp(gIoneq)
1096 else:
1097 ioneqOne = 0.
1098
1099 self.IoneqOne = ioneqOne
1100
1101
1102
1103