import copy
from datetime import datetime
import numpy as np
import ChiantiPy
import ChiantiPy.tools.data as chdata
import ChiantiPy.tools.constants as const
import ChiantiPy.tools.filters as chfilters
import ChiantiPy.tools.util as util
import ChiantiPy.Gui as chGui
from ChiantiPy.base import ionTrails
from ChiantiPy.base import specTrails
[docs]class spectrum(ionTrails, specTrails):
'''
Calculate the emission spectrum as a function of temperature and density.
one of the convenient things is that all of the instantiated ion classes, determined
through such keywords as 'elementList', 'ionList', and 'minAbund' are kept in a
dictionary self.IonInstances where self.IonInstances['mg_7'] is the class instance of
ChiantiPy.core.ion for 'mg_7'. All its methods and attributes are available.
includes elemental abundances and ionization equilibria
the set of abundances, a file in $XUVTOP/abundance, can be set with the keyword argument 'abundanceName'
temperature and density can be arrays but, unless the size of either is unity (1),
the two must have the same size
the returned spectrum will be convolved with a filter of the specified width on the
specified wavelength array
the default filter is gaussianR with a resolving power of 1000. Other filters,
such as gaussian, box and lorentz, are available in ChiantiPy.tools.filters. When
using the box filter, the width should equal the wavelength interval to keep the units
of the continuum and line spectrum the same.
Inherited methods include 'intensityList', 'intensityRatio' (between lines of different ions),
'intensityRatioSave' and 'convolve'
A selection of elements can be make with elementList a list containing the names of elements
that are desired to be included, e.g., ['fe','ni']
A selection of ions can be make with ionList containing the names of
the desired lines in CHIANTI notation, i.e. C VI = c_6
Both elementList and ionList can not be specified at the same time
a minimum abundance can be specified so that the calculation can be speeded up
by excluding elements with a low abundance. The default of minAbund is 1.e-6
It is necessary to specify at least an elementList, an ionList, or a minAbund to select any ions
for a spectrum calculation
With solar photospheric abundances -
setting minAbund = 1.e-4 will include H, He, C, O, Ne
setting minAbund = 2.e-5 adds N, Mg, Si, S, Fe
setting minAbund = 1.e-6 adds Na, Al, Ar, Ca, Ni
Setting doLines = 0 will skip the calculation of spectral lines.
Setting doContinuum =0 will skip the continuum calculation.
Setting em [for emission measure] will multiply the spectrum at each temperature
by the value of em.
em [for emission measure] can be a float or an array of the same length as the
temperature/density
keepIons: set this to keep the ion instances that have been calculated in a dictionary
self.IonInstances with the keywords being the CHIANTI-style ion names
abundance: to select a particular set of abundances, set abundance to the name of a
CHIANTI abundance file, without the '.abund' suffix, e.g. 'sun_photospheric_1998_grevesse'
If set to a blank (''), a gui selection menu will popup and allow the selection of an
set of abundances
'''
def __init__(self, temperature, eDensity, wavelength, filter=(chfilters.gaussianR, 1000.), label=None, elementList = None, ionList = None, minAbund=None, doLines=1, doContinuum=1, em=None, keepIons=0, abundance=None, verbose=0, allLines=1):
#
wavelength = np.atleast_1d(wavelength)
if wavelength.size < 2:
print((' wavelength must have at least two values, current length %3i'%(wavelength.size)))
return
t1 = datetime.now()
# creates Intensity dict from first ion calculated
setupIntensity = 0
#
self.Defaults=chdata.Defaults
self.Temperature = np.asarray(temperature, 'float64')
nTemp = self.Temperature.size
self.EDensity = np.asarray(eDensity, 'float64')
nDen = self.EDensity.size
self.NTempDen = max([nTemp, nDen])
nTempDen = self.NTempDen
self.Wavelength = wavelength
#
if em == None:
em = np.ones(self.NTempDen, 'float64')
ylabel = r'erg cm$^{-2}$ s$^{-1}$ sr$^{-1} \AA^{-1}$ ($\int\,$ N$_e\,$N$_H\,$d${\it l}$)$^{-1}$'
elif type(em) == float and em > 0.:
em = np.ones(self.NTempDen, 'float64')*em
ylabel = r'erg cm$^{-2}$ s$^{-1}$ sr$^{-1} \AA^{-1}$ $'
elif type(em) == list or type(em) == tuple or type(em) == np.ndarray:
em = np.asarray(em, 'float64')
ylabel = r'erg cm$^{-2}$ s$^{-1}$ sr$^{-1} \AA^{-1}$ $'
self.Em = em
#
#
if self.Defaults['wavelength'] == 'angstrom':
xlabel = 'Wavelength ('+self.Defaults['wavelength'].capitalize() +')'
else:
xlabel = 'Wavelength ('+self.Defaults['wavelength'] +')'
#
#
if abundance is not None:
if type(abundance) == str:
if abundance in chdata.AbundanceList:
self.AbundanceName = abundance
else:
abundChoices = chdata.AbundanceList
abundChoice = chGui.gui.selectorDialog(abundChoices,label='Select Abundance name')
abundChoice_idx = abundChoice.selectedIndex
self.AbundanceName = abundChoices[abundChoice_idx[0]]
else:
print(' keyword abundance must be a string, either a blank (\'\') or the name of an abundance file')
return
else:
self.AbundanceName = self.Defaults['abundfile']
if hasattr(self,'AbundanceName'):
self.Abundance = chdata.Abundance[self.AbundanceName]['abundance']
#
abundAll = chdata.Abundance[self.AbundanceName]['abundance']
# needed by ionGate
self.AbundAll = abundAll
#
self.MinAbund = minAbund
wavelength = np.asarray(wavelength)
nWvl = wavelength.size
self.Wavelength = wavelength
wvlRange = [wavelength.min(), wavelength.max()]
#
freeFree = np.zeros((nTempDen, nWvl), 'float64').squeeze()
freeBound = np.zeros((nTempDen, nWvl), 'float64').squeeze()
twoPhoton = np.zeros((nTempDen, nWvl), 'float64').squeeze()
lineSpectrum = np.zeros((nTempDen, nWvl), 'float64').squeeze()
#
self.IonsCalculated = []
if keepIons:
self.IonInstances = {}
self.FfInstances = {}
self.FbInstances = {}
self.Finished = []
#
self.ionGate(elementList = elementList, ionList = ionList, minAbund=minAbund, doLines=doLines, doContinuum=doContinuum, verbose = verbose)
#
for akey in sorted(self.Todo.keys()):
zStuff = util.convertName(akey)
Z = zStuff['Z']
ionstage = zStuff['Ion']
dielectronic = zStuff['Dielectronic']
abundance = self.Abundance[Z - 1]
if verbose:
print((' %5i %5s abundance = %10.2e '%(Z, const.El[Z-1], abundance)))
if verbose:
print((' doing ion %s for the following processes %s'%(akey, self.Todo[akey])))
if 'ff' in self.Todo[akey]:
if verbose:
print((' calculating ff continuum for : %s'%(akey)))
FF = ChiantiPy.core.continuum(akey, temperature, abundance=abundance, em=em)
FF.freeFree(wavelength)
freeFree += FF.FreeFree['intensity'].squeeze()
if keepIons:
self.FfInstances[akey] = copy.deepcopy(FF)
if 'fb' in self.Todo[akey]:
if verbose:
print((' calculating fb continuum for : %s'%(akey)))
FB = ChiantiPy.core.continuum(akey, temperature, abundance=abundance, em=em)
FB.freeBound(wavelength)
if 'errorMessage' not in list(FB.FreeBound.keys()):
freeBound += FB.FreeBound['intensity']
if keepIons:
self.FbInstances[akey] = copy.deepcopy(FB)
if 'line' in self.Todo[akey]:
if verbose:
print((' calculating spectrum for : %s'%(akey)))
thisIon = ChiantiPy.core.ion(akey, temperature, eDensity, abundance=abundance, em=em)
thisIon.intensity(allLines=allLines)
self.IonsCalculated.append(akey)
if 'errorMessage' not in list(thisIon.Intensity.keys()):
self.Finished.append(akey)
thisIon.spectrum(wavelength, filter=filter, allLines=allLines)
if keepIons:
self.IonInstances[akey] = copy.deepcopy(thisIon)
if setupIntensity:
for bkey in self.Intensity:
self.Intensity[bkey] = np.hstack((copy.copy(self.Intensity[bkey]), thisIon.Intensity[bkey]))
else:
setupIntensity = 1
self.Intensity = thisIon.Intensity
lineSpectrum += thisIon.Spectrum['intensity']
else:
if verbose:
print((thisIon.Intensity['errorMessage']))
# get 2 photon emission for H and He sequences
if (Z - ionstage) in [0, 1] and not dielectronic:
thisIon.twoPhoton(wavelength)
twoPhoton += thisIon.TwoPhoton['intensity']
twoPhoton += thisIon.TwoPhoton['intensity']
self.FreeFree = {'wavelength':wavelength, 'intensity':freeFree.squeeze()}
self.FreeBound = {'wavelength':wavelength, 'intensity':freeBound.squeeze()}
self.LineSpectrum = {'wavelength':wavelength, 'intensity':lineSpectrum.squeeze()}
self.TwoPhoton = {'wavelength':wavelength, 'intensity':twoPhoton.squeeze()}
#
#
total = freeFree + freeBound + lineSpectrum + twoPhoton
self.Total = total
t2 = datetime.now()
dt=t2-t1
print((' elapsed seconds = %12.3f'%(dt.seconds)))
if nTempDen == 1:
integrated = total
else:
integrated = total.sum(axis=0)
#
if type(label) == type(''):
if hasattr(self, 'Spectrum'):
self.Spectrum[label] = {'wavelength':wavelength, 'intensity':total.squeeze(), 'filter':filter[0].__name__, 'width':filter[1], 'integrated':integrated, 'em':em, 'ions':self.IonsCalculated,
'Abundance':self.AbundanceName, 'xlabel':xlabel, 'ylabel':ylabel, 'minAbund':minAbund}
else:
self.Spectrum = {label:{'wavelength':wavelength, 'intensity':total.squeeze(), 'filter':filter[0].__name__, 'width':filter[1], 'integrated':integrated, 'em':em, 'ions':self.IonsCalculated,
'Abundance':self.AbundanceName, 'xlabel':xlabel, 'ylabel':ylabel}, 'minAbund':minAbund}
else:
self.Spectrum ={'wavelength':wavelength, 'intensity':total.squeeze(), 'filter':filter[0].__name__, 'width':filter[1], 'integrated':integrated, 'ions':self.IonsCalculated,
'Abundance':self.AbundanceName, 'xlabel':xlabel, 'ylabel':ylabel, 'minAbund':minAbund}
[docs]class bunch(ionTrails, specTrails):
'''
Calculate the emission line spectrum as a function of temperature and density.
'bunch' is very similar to 'spectrum' except that continuum is not calculated and
the spectrum is not convolved over a filter. However, this can be done with the
inherited convolve method
one of the convenient things is that all of the instantiated ion classes,
determined through such keywords as 'elementList', 'ionList', and 'minAbund' are
kept in a dictionary self.IonInstances where self.IonInstances['mg_7'] is the
class instance of ChiantiPy.core.ion for 'mg_7'. All its methods and attributes
are available.
includes elemental abundances and ionization equilibria
the set of abundances, a file in $XUVTOP/abundance, can be set with the keyword
argument 'abundanceName'
temperature and density can be arrays but, unless the size of either is one (1),
the two must have the same size
Inherited methods include 'intensityList', 'intensityRatio' (between lines of different
ions), and 'intensityRatioSave' and 'convolve'.
A selection of elements can be make with elementList a list containing the names of elements
that are desired to be included, e.g., ['fe','ni']
A selection of ions can be make with ionList containing the names of
the desired lines in Chianti notation, i.e. C VI = c_6
Both elementList and ionList can not be specified at the same time
a minimum abundance can be specified so that the calculation can be speeded up by excluding
elements with a low abundance. With solar photospheric abundances -
setting minAbund = 1.e-4 will include H, He, C, O, Ne
setting minAbund = 2.e-5 adds N, Mg, Si, S, Fe
setting minAbund = 1.e-6 adds Na, Al, Ar, Ca, Ni
At least one of elementList, ionList, or minAbund must be set in order for 'bunch' to include
any ions.
Setting em will multiply the spectrum at each temperature by the value of em.
em [for emission measure], can be a float or an array of the same length as the
temperature/density
'''
#
# ------------------------------------------------------------------------------------
#
def __init__(self, temperature, eDensity, wvlRange, elementList=None, ionList=None, minAbund=None, keepIons=0, em=None, abundanceName=None, verbose=0, allLines=1):
#
t1 = datetime.now()
# creates Intensity dict from first ion calculated
setupIntensity = 0
#
self.Defaults=chdata.Defaults
temperature = np.asarray(temperature, 'float64')
self.Temperature = temperature
eDensity = np.asarray(eDensity, 'float64')
self.EDensity = eDensity
#
self.EDensity = np.asarray(eDensity,'float64')
self.NEDens = self.EDensity.size
ndens = self.EDensity.size
ntemp = self.Temperature.size
tst1 = ndens == ntemp
tst1a = ndens != ntemp
tst2 = ntemp > 1
tst3 = ndens > 1
tst4 = ndens > 1 and ntemp > 1
if tst1 and ntemp == 1:
self.NTempDen = 1
elif tst1a and (tst2 or tst3) and not tst4:
self.NTempDen = ntemp*ndens
if ntemp == self.NTempDen and ndens != self.NTempDen:
self.EDensity = np.ones_like(self.Temperature)*self.EDensity
elif ndens == self.NTempDen and ntemp != self.NTempDen:
self.Temperature = np.ones_like(self.EDensity)*self.Temperature
elif tst1 and tst4:
self.NTempDen = ntemp
#
if em == None:
em = np.ones(self.NTempDen, 'float64')
elif type(em) == float and em > 0.:
em = np.ones(self.NTempDen, 'float64')*em
elif type(em) == list or type(em) == tuple or type(em) == np.ndarray:
em = np.asarray(em, 'float64')
self.Em = em
#
#
if abundanceName:
if abundanceName in list(chdata.Abundance.keys()):
self.AbundanceName = abundanceName
else:
abundChoices = list(chdata.Abundance.keys())
# for one in wvl[topLines]:
# wvlChoices.append('%12.3f'%(one))
abundChoice = chGui.gui.selectorDialog(abundChoices,label='Select Abundance name')
abundChoice_idx = abundChoice.selectedIndex
self.AbundanceName = abundChoices[abundChoice_idx[0]]
abundanceName = self.AbundanceName
print((' Abundance chosen: %s '%(self.AbundanceName)))
else:
self.AbundanceName = self.Defaults['abundfile']
#
abundAll = chdata.Abundance[self.AbundanceName]['abundance']
# needed by ionGate
self.AbundAll = abundAll
#
# nonzed = abundAll > 0.
# minAbundAll = abundAll[nonzed].min()
# # if minAbund is even set
# if minAbund:
# if minAbund < minAbundAll:
# minAbund = minAbundAll
# self.minAbund = minAbund
# ionInfo = chio.masterListInfo()
# #
self.IonsCalculated = []
if keepIons:
self.IonInstances = {}
self.Finished = []
#
# also needed by ionGate
self.WvlRange = np.asarray(wvlRange, 'float64')
#
self.ionGate(elementList = elementList, ionList = ionList, minAbund=minAbund, doLines=1, doContinuum=0, verbose = verbose)
#
for ionS in sorted(self.Todo.keys()):
nameStuff = util.convertName(ionS)
Z = nameStuff['Z']
if verbose:
print((' calculating %s'%(ionS)))
thisIon = ChiantiPy.core.ion(ionS, temperature, eDensity, abundance=abundAll[Z-1], em=em)
thisIon.intensity(allLines = allLines)
self.IonsCalculated.append(ionS)
#
if 'errorMessage' not in list(thisIon.Intensity.keys()):
self.Finished.append(ionS)
# thisIon.spectrum(wavelength, filter=filter)
if keepIons:
self.IonInstances[ionS] = copy.deepcopy(thisIon)
if setupIntensity:
for akey in self.Intensity:
self.Intensity[akey] = np.hstack((copy.copy(self.Intensity[akey]), thisIon.Intensity[akey]))
else:
setupIntensity = 1
# print(' creating Intensity dict from ion %s'%(ionS))
self.Intensity = thisIon.Intensity
else:
if verbose:
print((thisIon.Intensity['errorMessage']))
#
#
t2 = datetime.now()
dt=t2-t1
print((' elapsed seconds = %12.3f'%(dt.seconds)))
return