def plotRatio(self, stageN, stageD, tRange=0, yr=0, label=1, title=1, bw=0, semilogx = 1, verbose=0): ''' Plots the ratio of the ionization equilibria of two stages of a given element self.plotRatio(stageN, stageD) stages = sequence of ions to be plotted, neutral == 1, fully stripped == Z+1 stageN = numerator stageD = denominator tRange = temperature range, yr = ion fraction range ''' ionN = util.zion2name(self.Z, stageN) ionD = util.zion2name(self.Z, stageD) ionNS = util.zion2spectroscopic(self.Z, stageN) ionDS = util.zion2spectroscopic(self.Z, stageD) atitle = 'ratio of %s to %s '%(ionN, ionD) alabel = 'ratio of %s to %s '%(ionNS, ionDS) print(atitle) if not hasattr(self, 'Ioneq'): print(' must first load or calculate an ionization equilibrium') return if bw: linestyle = ['k-','k--', 'k-.', 'k:'] plt.rcParams['font.size'] = 16. lw = 2 else: linestyle = ['b-','r--', 'g-.', 'm:'] plt.rcParams['font.size'] = 14. lw = 2 # goodTn = self.Ioneq[stageN - 1, :] > 0. goodTd = self.Ioneq[stageD -1, : ] > 0. realGoodT = np.logical_and(goodTn, goodTd) goodT = self.Temperature[realGoodT] goodR = self.Ioneq[stageN - 1, realGoodT]/self.Ioneq[stageD - 1, realGoodT] if not tRange: tRange = [goodT.min(), goodT.max()] if not yr: yr = [0.01, 1.1] xyr = list(tRange) xyr.extend(list(yr)) # if semilogx: plt.semilogx(goodT, goodR, linestyle[0], lw=lw, label=alabel) else: plt.loglog(goodT, goodR, linestyle[0], lw=lw, label=alabel) plt.xlabel('Temperature (K)') plt.ylabel('Ratio') if title: atitle = 'CHIANTI Ionization Equilibrium' plt.title(atitle) plt.legend(loc='lower right') plt.tight_layout() self.Ratio={'Temperature':goodT, 'Ratio':goodR, 'label':alabel}
def freeBound(self, wavelength, include_abundance=True, include_ioneq=True, use_verner=True, **kwargs): """ Calculate the free-bound emission of an ion. The result is returned as a 2D array to the `free_bound_emission` attribute. The total free-bound continuum emissivity is given by, .. math:: \\frac{dW}{dtdVd\lambda} = \\frac{1}{4\pi}\\frac{2}{hk_Bc^3m_e\sqrt{2\pi k_Bm_e}}\\frac{E^5}{T^{3/2}}\sum_i\\frac{\omega_i}{\omega_0}\sigma_i^{bf}\exp\left(-\\frac{E - I_i}{k_BT}\\right) where :math:`E=hc/\lambda` is the photon energy, :math:`\omega_i` and :math:`\omega_0` are the statistical weights of the :math:`i^{\mathrm{th}}` level of the recombined ion and the ground level of the recombing ion, respectively, :math:`\sigma_i^{bf}` is the photoionization cross-section, and :math:`I_i` is the ionization potential of level :math:`i`. This expression comes from Eq. 12 of [3]_. For more information about the free-bound continuum calculation, see `Peter Young's notes on free-bound continuum`_. The photoionization cross-sections are calculated using the methods of [2]_ for the transitions to the ground state and [1]_ for all other transitions. See `verner_cross_section` and `karzas_cross_section` for more details. .. _Peter Young's notes on free-bound continuum: http://www.pyoung.org/chianti/freebound.pdf The free-bound emission is in units of erg :math:`\mathrm{cm}^3\mathrm{s}^{-1}\mathrm{\mathring{A}}^{-1}\mathrm{str}^{-1}`. If the emission measure has been set, the units will be multiplied by :math:`\mathrm{cm}^{-5}` or :math:`\mathrm{cm}^{-3}`, depending on whether it is the line-of-sight or volumetric emission measure, respectively. Parameters ---------- wavelength : array-like In units of angstroms include_abundance : `bool`, optional If True, include the ion abundance in the final output. include_ioneq : `bool`, optional If True, include the ionization equilibrium in the final output use_verner : `bool`, optional If True, cross-sections of ground-state transitions using [2]_, i.e. `verner_cross_section` Raises ------ ValueError If no .fblvl file is available for this ion References ---------- .. [1] Karzas and Latter, 1961, ApJSS, `6, 167 <http://adsabs.harvard.edu/abs/1961ApJS....6..167K>`_ .. [2] Verner & Yakovlev, 1995, A&AS, `109, 125 <http://adsabs.harvard.edu/abs/1995A%26AS..109..125V>`_ .. [3] Young et al., 2003, ApJSS, `144, 135 <http://adsabs.harvard.edu/abs/2003ApJS..144..135Y>`_ """ wavelength = np.atleast_1d(wavelength) if wavelength.size < 2: print(' wavelength must have at least two values, current length %3i'%(wavelength.size)) return self.NWavelength = wavelength.size # calculate the photon energy in erg photon_energy = ch_const.planck*(1.e8*ch_const.light)/wavelength prefactor = (2./np.sqrt(2.*np.pi)/(4.*np.pi)/(ch_const.planck*(ch_const.light**3) * (ch_const.emass*ch_const.boltzmann)**(3./2.))) # read the free-bound level information for the recombined and recombining ion recombining_fblvl = ch_io.fblvlRead(self.ion_string) # get the multiplicity of the ground state of the recombining ion if 'errorMessage' in recombining_fblvl: omega_0 = 1. else: omega_0 = recombining_fblvl['mult'][0] self.Recombined_fblvl = ch_io.fblvlRead(self.nameDict['lower']) if 'errorMessage' in self.Recombined_fblvl: # raise ValueError('No free-bound information available for {}'.format(ch_util.zion2name(self.Z, self.stage))) errorMessage = 'No free-bound information available for {}'.format(ch_util.zion2name(self.Z, self.stage)) fb_emiss = np.zeros((self.NTemperature, self.NWavelength), 'float64') # self.free_bound_emission = fb_emiss.squeeze() self.FreeBound = {'intensity':fb_emiss, 'temperature':self.Temperature,'wvl':wavelength,'em':self.Em, 'errorMessage':errorMessage} return energy_over_temp_factor = np.outer(1./(self.Temperature**1.5), photon_energy**5.).squeeze() # if self.NWavelength > 1: # print(' energy shape %5i %5i'%(energy_over_temp_factor.shape[0],energy_over_temp_factor.shape[1])) # else: # print(' energy size %5i'%(energy_over_temp_factor.size)) # sum over levels of the recombined ion sum_factor = np.zeros((len(self.Temperature), len(wavelength))) for i,omega_i in enumerate(self.Recombined_fblvl['mult']): # ionization potential for level i # ip = self.ionization_potential - recombined_fblvl['ecm'][i]*ch_const.planck*ch_const.light ip = self.IprErg - self.Recombined_fblvl['ecm'][i]*ch_const.planck*ch_const.light # skip level if photon energy is not sufficiently high if ip < 0. or np.all(np.max(photon_energy) < (self.ionization_potential - ip)): continue # calculate cross-section if i == 0 and use_verner: cross_section = self.verner_cross_section(photon_energy) else: cross_section = self.karzas_cross_section(photon_energy, ip, self.Recombined_fblvl['pqn'][i], self.Recombined_fblvl['l'][i]) scaled_energy = np.outer(1./(ch_const.boltzmann*self.Temperature), photon_energy - ip) # the exponential term can go to infinity for low temperatures # but if the cross-section is zero this does not matter scaled_energy[:,np.where(cross_section == 0.0)] = 0.0 sum_factor += omega_i/omega_0*np.exp(-scaled_energy)*cross_section # combine factors fb_emiss = prefactor*energy_over_temp_factor*sum_factor.squeeze() # if self.NWavelength > 1: # print(' fb emiss.shape %5i %5i'%(fb_emiss.shape[0], fb_emiss.shape[1])) # else: # print(' fb emiss.size %5i'%(fb_emiss.size)) # include abundance, ionization equilibrium, photon conversion, emission measure if include_abundance: fb_emiss *= self.abundance if include_ioneq: if self.NTemperature > 1: if self.NWavelength > 1: # fb_emiss *= self.ioneq_one(self.stage, **kwargs)[:,np.newaxis] fb_emiss *= self.IoneqOne[:,np.newaxis] else: fb_emiss *= self.IoneqOne else: fb_emiss *= self.IoneqOne if self.Em is not None: if self.Em.size > 1: fb_emiss *= self.Em[:,np.newaxis] else: fb_emiss *= self.Em if ch_data.Defaults['flux'] == 'photon': fb_emiss /= photon_energy # the final units should be per angstrom fb_emiss /= 1e8 # self.free_bound_emission = fb_emiss.squeeze() self.FreeBound = {'intensity':fb_emiss.squeeze(), 'temperature':self.Temperature,'wvl':wavelength,'em':self.Em, 'ions':self.ion_string}
def calculate(self, temperature): """ Calculate ion fractions for given temperature array using the total ionization and recombination rates. """ self.Temperature = np.array(temperature, np.float64) if self.Temperature.size == 1: print(' temperature must be an array') return ionList = [] chIons = [] z = self.Z for stage in range(1, z+2): ionStr = util.zion2name(z, stage) ionList.append(ionStr) atom = ion(ionStr, temperature = self.Temperature, setup=0) atom.setupIonrec() atom.ionizRate() atom.recombRate() chIons.append(atom) ntemp = chIons[0].IonizRate['temperature'].size if ntemp == 1: ioneq = np.zeros((z+1), np.float64) factor = [] for anIon in chIons: if hasattr(anIon, 'RecombRate') and hasattr(anIon, 'IonizRate'): rat = anIon.IonizRate['rate']/anIon.RecombRate['rate'] factor.append(rat**2 + rat**(-2)) else: factor.append(0.) factor[0] = max(factor) factor[-1] = max(factor) ionmax = factor.index(min(factor)) ioneq[ionmax] = 1. for iz in range(ionmax+1, z+1): ionrate = chIons[iz-1].IonizRate['rate'] recrate = chIons[iz].RecombRate['rate'] ioneq[iz] = ionrate*ioneq[iz-1]/recrate for iz in range(ionmax-1, -1, -1): ionrate = chIons[iz].IonizRate['rate'] recrate = chIons[iz+1].RecombRate['rate'] ioneq[iz] = recrate*ioneq[iz+1]/ionrate ionsum = ioneq.sum() ioneq = ioneq/ionsum self.Ioneq = ioneq else: ioneq = np.zeros((z+1,ntemp ), np.float64) for it in range(ntemp): factor = [] for anIon in chIons: if anIon.IonizRate is not None and anIon.RecombRate is not None: ioniz = anIon.IonizRate['rate'][it] recomb = anIon.RecombRate['rate'][it] if ioniz == 0. or recomb == 0.: rat = 1.e-100 else: rat = anIon.IonizRate['rate'][it]/anIon.RecombRate['rate'][it] try: factor.append(rat**2 + rat**(-2)) except: factor.append(0.) else: factor.append(0.) factor[0] = max(factor) factor[-1] = max(factor) ionmax = factor.index(min(factor)) ioneq[ionmax, it] = 1. for iz in range(ionmax+1, z+1): ionrate = chIons[iz-1].IonizRate['rate'][it] recrate = chIons[iz].RecombRate['rate'][it] if recrate != 0.: ioneq[iz, it] = ionrate*ioneq[iz-1, it]/recrate else: ioneq[iz, it] = 0. for iz in range(ionmax-1, -1, -1): ionrate = chIons[iz].IonizRate['rate'][it] recrate = chIons[iz+1].RecombRate['rate'][it] if ionrate != 0.: ioneq[iz, it] = recrate*ioneq[iz+1, it]/ionrate else: ioneq[iz, it] = 0. ionsum = ioneq[:, it].sum() ioneq[:, it] = ioneq[:, it]/ionsum self.Ioneq = ioneq
def freeBound(self, wavelength, includeAbundance=True, includeIoneq=True, useVerner=True, **kwargs): """ Calculate the free-bound emission of an ion. The result is returned as a 2D array to the `free_bound_emission` attribute. The total free-bound continuum emissivity is given by, .. math:: \\frac{dW}{dtdVd\lambda} = \\frac{1}{4\pi}\\frac{2}{hk_Bc^3m_e\sqrt{2\pi k_Bm_e}}\\frac{E^5}{T^{3/2}}\sum_i\\frac{\omega_i}{\omega_0}\sigma_i^{bf}\exp\left(-\\frac{E - I_i}{k_BT}\\right) where :math:`E=hc/\lambda` is the photon energy, :math:`\omega_i` and :math:`\omega_0` are the statistical weights of the :math:`i^{\mathrm{th}}` level of the recombined ion and the ground level of the recombing ion, respectively, :math:`\sigma_i^{bf}` is the photoionization cross-section, and :math:`I_i` is the ionization potential of level :math:`i`. This expression comes from Eq. 12 of [3]_. For more information about the free-bound continuum calculation, see `Peter Young's notes on free-bound continuum`_. The photoionization cross-sections are calculated using the methods of [2]_ for the transitions to the ground state and [1]_ for all other transitions. See `verner_cross_section` and `karzas_cross_section` for more details. .. _Peter Young's notes on free-bound continuum: http://www.pyoung.org/chianti/freebound.pdf The free-bound emission is in units of erg :math:`\mathrm{cm}^3\mathrm{s}^{-1}\mathrm{\mathring{A}}^{-1}\mathrm{str}^{-1}`. If the emission measure has been set, the units will be multiplied by :math:`\mathrm{cm}^{-5}` or :math:`\mathrm{cm}^{-3}`, depending on whether it is the line-of-sight or volumetric emission measure, respectively. Parameters ---------- wavelength : array-like In units of angstroms include_abundance : `bool`, optional If True, include the ion abundance in the final output. include_ioneq : `bool`, optional If True, include the ionization equilibrium in the final output use_verner : `bool`, optional If True, cross-sections of ground-state transitions using [2]_, i.e. `verner_cross_section` Raises ------ ValueError If no .fblvl file is available for this ion References ---------- .. [1] Karzas and Latter, 1961, ApJSS, `6, 167 <http://adsabs.harvard.edu/abs/1961ApJS....6..167K>`_ .. [2] Verner & Yakovlev, 1995, A&AS, `109, 125 <http://adsabs.harvard.edu/abs/1995A%26AS..109..125V>`_ .. [3] Young et al., 2003, ApJSS, `144, 135 <http://adsabs.harvard.edu/abs/2003ApJS..144..135Y>`_ """ wavelength = np.atleast_1d(wavelength) if wavelength.size < 2: print( ' wavelength must have at least two values, current length %3i' % (wavelength.size)) return self.NWavelength = wavelength.size # calculate the photon energy in erg photon_energy = ch_const.planck * (1.e8 * ch_const.light) / wavelength prefactor = (2. / np.sqrt(2. * np.pi) / (4. * np.pi) / (ch_const.planck * (ch_const.light**3) * (ch_const.emass * ch_const.boltzmann)**(3. / 2.))) # read the free-bound level information for the recombined and recombining ion recombining_fblvl = ch_io.fblvlRead(self.ion_string) # get the multiplicity of the ground state of the recombining ion if 'errorMessage' in recombining_fblvl: omega_0 = 1. else: omega_0 = recombining_fblvl['mult'][0] self.Recombined_fblvl = ch_io.fblvlRead(self.nameDict['lower']) if 'errorMessage' in self.Recombined_fblvl: # raise ValueError('No free-bound information available for {}'.format(ch_util.zion2name(self.Z, self.stage))) errorMessage = 'No free-bound information available for {}'.format( ch_util.zion2name(self.Z, self.stage)) fb_emiss = np.zeros((self.NTemperature, self.NWavelength), 'float64') # self.free_bound_emission = fb_emiss.squeeze() self.FreeBound = { 'intensity': fb_emiss, 'temperature': self.Temperature, 'wvl': wavelength, 'em': self.Em, 'errorMessage': errorMessage } return energy_over_temp_factor = np.outer(1. / (self.Temperature**1.5), photon_energy**5.).squeeze() # if self.NWavelength > 1: # print(' energy shape %5i %5i'%(energy_over_temp_factor.shape[0],energy_over_temp_factor.shape[1])) # else: # print(' energy size %5i'%(energy_over_temp_factor.size)) # sum over levels of the recombined ion sum_factor = np.zeros((len(self.Temperature), len(wavelength))) for i, omega_i in enumerate(self.Recombined_fblvl['mult']): # ionization potential for level i # ip = self.ionization_potential - recombined_fblvl['ecm'][i]*ch_const.planck*ch_const.light ip = self.IprErg - self.Recombined_fblvl['ecm'][ i] * ch_const.planck * ch_const.light # skip level if photon energy is not sufficiently high if ip < 0. or np.all( np.max(photon_energy) < (self.ionization_potential - ip)): continue # calculate cross-section if i == 0 and useVerner: cross_section = self.verner_cross_section(photon_energy) else: cross_section = self.karzas_cross_section( photon_energy, ip, self.Recombined_fblvl['pqn'][i], self.Recombined_fblvl['l'][i]) scaled_energy = np.outer( 1. / (ch_const.boltzmann * self.Temperature), photon_energy - ip) # the exponential term can go to infinity for low temperatures # but if the cross-section is zero this does not matter scaled_energy[:, np.where(cross_section == 0.0)] = 0.0 sum_factor += omega_i / omega_0 * np.exp( -scaled_energy) * cross_section # combine factors fb_emiss = prefactor * energy_over_temp_factor * sum_factor.squeeze() # if self.NWavelength > 1: # print(' fb emiss.shape %5i %5i'%(fb_emiss.shape[0], fb_emiss.shape[1])) # else: # print(' fb emiss.size %5i'%(fb_emiss.size)) # include abundance, ionization equilibrium, photon conversion, emission measure if includeAbundance: fb_emiss *= self.Abundance includeAbundance = self.Abundance if includeIoneq: if self.NTemperature > 1: if self.NWavelength > 1: # fb_emiss *= self.ioneq_one(self.stage, **kwargs)[:,np.newaxis] fb_emiss *= self.IoneqOne[:, np.newaxis] includeAbundance = self.IoneqOne[:, np.newaxis] else: fb_emiss *= self.IoneqOne includeAbundance = self.IoneqOne else: fb_emiss *= self.IoneqOne includeAbundance = self.IoneqOne if self.Em is not None: if self.Em.size > 1: fb_emiss *= self.Em[:, np.newaxis] else: fb_emiss *= self.Em if ch_data.Defaults['flux'] == 'photon': fb_emiss /= photon_energy # the final units should be per angstrom fb_emiss /= 1e8 # self.free_bound_emission = fb_emiss.squeeze() self.FreeBound = { 'intensity': fb_emiss.squeeze(), 'temperature': self.Temperature, 'wvl': wavelength, 'em': self.Em, 'ions': self.ion_string, 'abundance': includeAbundance, 'ioneq': includeIoneq }
def ionGate(self, elementList=None, ionList=None, minAbund=None, doLines=1, doContinuum=1, doWvlTest=1, verbose=0): ''' creates a list of ions for free-free, free-bound, and line intensity calculations if doing the radiative losses, accept all wavelength -> doWvlTest=0 the list is a dictionary self.Todo ''' # masterlist = chdata.MasterList abundAll = self.AbundAll # nonzed = abundAll > 0. minAbundAll = abundAll[nonzed].min() if minAbund: if minAbund < minAbundAll: minAbund = minAbundAll ionInfo = chio.masterListInfo() # if hasattr(self, 'Wavelength'): wvlRange = [self.Wavelength.min(), self.Wavelength.max()] elif hasattr(self, 'WvlRange'): wvlRange = self.WvlRange else: print(' need a wavelength range in ionGate ') # temperature = self.Temperature # # use the ionList but make sure the ions are in the database self.Todo = {} # # if elementList: for i, element in enumerate(elementList): elementList[i] = element.lower() if verbose: print('el = %s' % (element)) z = const.El.index(element.lower()) + 1 for one in masterlist: nameDict = util.convertName(one) if nameDict['Element'].lower() in elementList: if verbose: print(' ion = %s' % (one)) if doLines: self.Todo[one] = 'line' for stage in range(2, z + 2): name = util.zion2name(z, stage) if doContinuum and not nameDict['Dielectronic']: if name not in self.Todo.keys(): self.Todo[name] = 'ff' else: self.Todo[name] += '_ff' self.Todo[name] += '_fb' if ionList: for one in ionList: nameDict = util.convertName(one) if masterlist.count(one): if doLines: self.Todo[one] = 'line' else: if verbose: pstring = ' %s not in CHIANTI database' % (one) print(pstring) if doContinuum and not nameDict['Dielectronic']: if one not in self.Todo.keys(): self.Todo[one] = 'ff' else: self.Todo[one] += '_ff' self.Todo[one] += '_fb' # # # if minAbund: for iz in range(1, 31): abundance = chdata.Abundance[self.AbundanceName]['abundance'][ iz - 1] if abundance >= minAbund: if verbose: print(' %5i %5s abundance = %10.2e ' % (iz, const.El[iz - 1], abundance)) # for ionstage in range(1, iz + 1): ionS = util.zion2name(iz, ionstage) masterListTest = ionS in masterlist masterListInfoTest = ionS in sorted(ionInfo.keys()) if masterListTest or masterListInfoTest: if masterListTest or masterListInfoTest: if doWvlTest: wvlTestMin = wvlRange[0] <= ionInfo[ionS][ 'wmax'] wvlTestMax = wvlRange[1] >= ionInfo[ionS][ 'wmin'] else: wvlTestMin = 1 wvlTestMax = 1 ioneqTest = ( temperature.max() >= ionInfo[ionS]['tmin'] ) and (temperature.min() <= ionInfo[ionS]['tmax']) # construct similar test for the dielectronic files ionSd = util.zion2name(iz, ionstage, dielectronic=1) masterListTestD = ionSd in masterlist masterListInfoTestD = ionSd in sorted(ionInfo.keys()) if masterListTestD or masterListInfoTestD: if doWvlTest: wvlTestMinD = wvlRange[0] <= ionInfo[ionSd][ 'wmax'] wvlTestMaxD = wvlRange[1] >= ionInfo[ionSd][ 'wmin'] else: wvlTestMinD = 1 wvlTestMaxD = 1 ioneqTestD = ( temperature.max() >= ionInfo[ionSd]['tmin'] ) and (temperature.min() <= ionInfo[ionSd]['tmax']) # if masterListTest and wvlTestMin and wvlTestMax and ioneqTest and doLines: if ionS in sorted(self.Todo.keys()): self.Todo[ionS] += '_line' else: self.Todo[ionS] = 'line' # get dielectronic lines if verbose: print(' for ion %s do : %s' % (ionS, self.Todo[ionS])) if masterListTestD and wvlTestMinD and wvlTestMaxD and ioneqTestD and doLines: if ionSd in sorted(self.Todo.keys()): self.Todo[ionSd] += '_line' else: self.Todo[ionSd] = 'line' if verbose: print(' for ion %s do : %s' % (ionSd, self.Todo[ionSd])) # # for ionstage in range(2, iz + 2): ionS = util.zion2name(iz, ionstage) if ionS in ionInfo.keys(): ioneqTest = ( temperature.max() >= ionInfo[ionS]['tmin'] ) and (temperature.min() <= ionInfo[ionS]['tmax']) else: ioneqTest = 1 # construct similar test for the dielectronic files if ioneqTest and doContinuum: # ionS is the target ion, cannot be the neutral for the continuum # if verbose: # print(' setting up continuum calculation for %s '%(ionS)) if ionS in sorted(self.Todo.keys()): self.Todo[ionS] += '_ff_fb' else: self.Todo[ionS] = 'ff_fb' if verbose: print(' for ion %s do : %s' % (ionS, self.Todo[ionS])) if len(self.Todo.keys()) == 0: print(' no elements have been selected') print( ' it is necessary to provide an elementList, an ionList, or set minAbund > 0.' ) return
def populateNew(self, popCorrect=1, verbose=0, **kwargs): """ Calculate level populations for specified ion. This is a new version that will enable the calculation of dielectronic satellite lines without resorting to the dielectronic ions, such as c_5d possible keyword arguments include temperature, eDensity, pDensity, radTemperature and rStar this is a developmental method for using the autoionizing A-values to determine level resolved dielectronic recombination rates """ # # for one in kwargs.keys(): if one not in chdata.keywordArgs: print(' keyword is not understood - %s' % (one)) # nlvls = self.Nlvls nwgfa = self.Nwgfa nscups = self.Nscups npsplups = self.Npsplups # if kwargs.has_key('temperature'): self.Temperature = np.asarray(kwargs['temperature']) temperature = self.Temperature elif hasattr(self, 'Temperature'): temperature = self.Temperature else: print(' no temperature values have been set') return {'errorMessage': ' no temperature values have been set'} # if kwargs.has_key('eDensity'): self.EDensity = np.asarray(kwargs['eDensity']) eDensity = self.EDensity elif hasattr(self, 'EDensity'): eDensity = self.EDensity else: print(' no eDensity values have been set') return {'errorMessage': ' no eDensity values have been set'} # if kwargs.has_key('pDensity'): if kwargs['pDensity'] == 'default': self.p2eRatio() protonDensity = self.ProtonDensityRatio * self.EDensity else: try: self.PDensity = np.asarray(kwargs['pDensity']) except: print(' could not interpret value for keyword pDensity') print(' should be either "default" or a number or array') return else: if hasattr(self, 'PDensity'): protonDensity = self.PDensity else: self.p2eRatio() self.PDensity = self.ProtonDensityRatio * self.EDensity protonDensity = self.PDensity print(' proton density not specified, set to "default"') # if 'radTemperature' in kwargs.keys() and 'rStar' in kwargs.keys(): self.RadTemperature = np.asarray(kwargs['radTemperature']) radTemperature = np.array(self.RadTemperature) self.RStar = np.asarray(kwargs['rStar']) rStar = np.asarray(self.RStar) elif hasattr(self, 'RadTemperature') and hasattr(self, 'RStar'): radTemperature = self.RadTemperature rStar = self.RStar # # # if self.Ncilvl: ci = 1 cilvl = self.Cilvl # if hasattr(self, 'CilvlRate'): # cilvlRate = self.CilvlRate # else: # self.cireclvlDescale('cilvl') # cilvlRate = self.CilvlRate self.recombRate() # lowers = util.zion2name(self.Z, self.Ion - 1) # get the lower ionization stage self.Lower = ion(lowers, temperature=self.Temperature, eDensity=self.EDensity) self.Lower.ionizRate() # need to get multiplicity of lower ionization stage lowMult = self.Lower.Elvlc['mult'] else: ci = 0 # evetually will be looking for just an recLvl attribute # # if the higher ion does not exist in the database, rec=0 highers = util.zion2name(self.Z, self.Ion + 1) if self.Nreclvl: reclvl = self.Reclvl if hasattr(self, 'ReclvlRate'): reclvlRate = self.ReclvlRate else: self.cireclvlDescale('reclvl') reclvlRate = self.ReclvlRate if hasattr(self, 'Auto'): self.drRateLvl() if self.Nreclvl or hasattr(self, 'Auto'): rec = 1 # get ionization rate of this ion self.ionizRate() # get the higher ionization stage else: rec = 0 # if the higher ion does not exist in the database, rec=0 highers = util.zion2name(self.Z, self.Ion + 1) # if highers in chdata.MasterList: self.Higher = ion(highers, temperature=self.Temperature, eDensity=self.EDensity) self.Higher.recombRate() # else: # # rad = np.zeros( (nlvls + ci + rec, nlvls + ci + rec), "float64") # the populating matrix for radiative transitions # # for iwgfa in range(nwgfa): l1 = self.Wgfa["lvl1"][iwgfa] - 1 l2 = self.Wgfa["lvl2"][iwgfa] - 1 rad[l1 + ci, l2 + ci] += self.Wgfa["avalue"][iwgfa] rad[l2 + ci, l2 + ci] -= self.Wgfa["avalue"][iwgfa] # photo-excitation and stimulated emission if self.RadTemperature: if not self.RStar: dilute = 0.5 else: dilute = util.dilute(self.RStar) # next - don't include autoionization lines if abs(self.Wgfa['wvl'][iwgfa]) > 0.: if self.Elvlc['ecm'][l2] >= 0.: ecm2 = self.Elvlc['ecm'][l2] else: ecm2 = self.Elvlc['ecmth'][l2] # if self.Elvlc['ecm'][l1] >= 0.: ecm1 = self.Elvlc['ecm'][l1] else: ecm1 = self.Elvlc['ecmth'][l1] de = const.invCm2Erg * (ecm2 - ecm1) dekt = de / (const.boltzmann * self.RadTemperature) # photoexcitation phexFactor = dilute * (float(self.Elvlc['mult'][l2]) / float( self.Elvlc['mult'][l1])) / (np.exp(dekt) - 1.) rad[l2 + ci, l1 + ci] += self.Wgfa["avalue"][iwgfa] * phexFactor rad[l1 + ci, l1 + ci] -= self.Wgfa["avalue"][iwgfa] * phexFactor # stimulated emission stemFactor = dilute / (np.exp(-dekt) - 1.) rad[l1 + ci, l2 + ci] += self.Wgfa["avalue"][iwgfa] * stemFactor rad[l2 + ci, l2 + ci] -= self.Wgfa["avalue"][iwgfa] * stemFactor if hasattr(self, 'Auto'): # as of 7/2012, we only consider the ground level in the next higher ionization stage # hence, requiring lvl1 = 1 or, l1 = 0 for iauto, avalue in enumerate(self.Auto['avalue']): l1 = self.Auto["lvl1"][iauto] - 1 l2 = self.Auto["lvl2"][iauto] - 1 # for now only consider a single level for upper/higher ion if l1 == 0 and rec: rad[l1 + ci + nlvls, l2 + ci] += avalue rad[l2 + ci, l2 + ci] -= avalue # if the higher ion is not in the database, decay to the ground level elif l1 == 0: rad[l1 + ci, l2 + ci] += avalue rad[l2 + ci, l2 + ci] -= avalue # # if self.Nscups: # print(' Nscups = %10i'%(self.Nscups)) self.upsilonDescale() ups = self.Upsilon['upsilon'] exRate = self.Upsilon['exRate'] dexRate = self.Upsilon['dexRate'] # if self.Npsplups: self.upsilonDescaleSplups(prot=1) # pups = self.PUpsilon['upsilon'] pexRate = self.PUpsilon['exRate'] pdexRate = self.PUpsilon['dexRate'] # temp = temperature ntemp = temp.size # cc = const.collision * self.EDensity ndens = cc.size if self.Npsplups: cp = const.collision * protonDensity if ntemp > 1 and ndens > 1 and ntemp != ndens: print(' unless temperature or eDensity are single values') print(' the number of temperatures values must match the ') print(' the number of eDensity values') return # # get corrections for recombination and excitation # nscups = self.Nscups # # first, for ntemp=ndens=1 if ndens == 1 and ntemp == 1: popmat = np.copy(rad) for iscups in range(0, nscups): l1 = self.Scups["lvl1"][iscups] - 1 l2 = self.Scups["lvl2"][iscups] - 1 # popmat[l1 + ci, l2 + ci] += self.EDensity * dexRate[iscups] popmat[l2 + ci, l1 + ci] += self.EDensity * exRate[iscups] popmat[l1 + ci, l1 + ci] -= self.EDensity * exRate[iscups] popmat[l2 + ci, l2 + ci] -= self.EDensity * dexRate[iscups] # for isplups in range(0, npsplups): l1 = self.Psplups["lvl1"][isplups] - 1 l2 = self.Psplups["lvl2"][isplups] - 1 # popmat[l1 + ci, l2 + ci] += self.PDensity * pdexRate[isplups] popmat[l2 + ci, l1 + ci] += self.PDensity * pexRate[isplups] popmat[l1 + ci, l1 + ci] -= self.PDensity * pexRate[isplups] popmat[l2 + ci, l2 + ci] -= self.PDensity * pdexRate[isplups] # now include ionization rate from if ci: # # the ciRate can be computed for all temperatures # ciTot = 0. for itrans in range(len(cilvl['lvl1'])): lvl1 = cilvl['lvl1'][itrans] - 1 lvl2 = cilvl['lvl2'][itrans] - 1 # this is kind of double booking the ionization rate components popmat[lvl2 + ci, lvl1] += self.EDensity * self.CilvlRate['rate'][itrans] popmat[lvl1, lvl1] -= self.EDensity * self.CilvlRate['rate'][itrans] ciTot += self.EDensity * self.CilvlRate['rate'][itrans] # popmat[1, 0] += (self.EDensity * self.Lower.IonizRate['rate'] - ciTot) popmat[0, 0] -= (self.EDensity * self.Lower.IonizRate['rate'] - ciTot) popmat[0, 1] += self.EDensity * self.RecombRate['rate'] popmat[1, 1] -= self.EDensity * self.RecombRate['rate'] if rec: # ntemp=ndens=1 # if hasattr(self, 'DrRateLvl'): # branch = np.zeros(self.Ndielsplups, 'float64') for idr, rate in enumerate(self.DrRateLvl['rate']): l1 = self.Auto["lvl1"][idr] - 1 l2 = self.Auto["lvl2"][idr] - 1 # popmat[l2+ci,-1] += self.EDensity*self.DrRateLvl['rate'][idr] # popmat[-1, -1] -= self.EDensity*self.DrRateLvl['rate'][idr] popmat[l2 + ci, -1] += self.EDensity * rate popmat[-1, -1] -= self.EDensity * rate # dielTot = self.DrRateLvl['totalRate'][0] else: dielTot = 0. # # # for itrans in range(self.Nreclvl): # lvl1 = reclvl['lvl1'][itrans]-1 lvl2 = reclvl['lvl2'][itrans] - 1 popmat[lvl2 + ci, -1] += self.EDensity * reclvlRate['rate'][itrans] popmat[-1, -1] -= self.EDensity * reclvlRate['rate'][itrans] if self.Nreclvl: recTot = reclvlRate['rate'].sum(axis=0) else: recTot = 0. # # popmat[-1, ci] += self.EDensity * self.IonizRate['rate'] popmat[ci, ci] -= self.EDensity * self.IonizRate['rate'] # # next 2 line take care of overbooking netRecomb = self.EDensity * (self.Higher.RecombRate['rate'][0] - recTot - dielTot) # if netRecomb > 0.: popmat[ci, -1] += netRecomb popmat[-1, -1] -= netRecomb # # normalize to unity norm = np.ones(nlvls + ci + rec, 'float64') if ci: norm[0] = 0. if rec: norm[nlvls + ci + rec - 1] = 0. popmata = np.copy(popmat) normRow = (nlvls + ci + rec - 1) / 2 #popmata[nlvls+ci+rec-1]=norm popmata[normRow] = norm b = np.zeros(nlvls + ci + rec, 'float64') #b[nlvls+ci+rec-1]=1. b[normRow] = 1. try: fullpop = np.linalg.solve(popmata, b) pop = fullpop[ci:ci + nlvls] if rec: popHigher = fullpop[-1] else: popHigher = 0. except np.linalg.LinAlgError: pop = np.zeros(nlvls, 'float64') popHigher = 0. # print ' error in matrix inversion, setting populations to zero at T = ', ('%8.2e')%(temperature) # # ------------- ntemp = 1 --------------------------------------------------------- # # elif ndens == 1: pop = np.zeros((ntemp, nlvls), "float64") popHigher = np.zeros(ntemp, 'float64') # pop=np.zeros((ntemp,ci + nlvls + rec),"float64") for itemp in range(ntemp): popmat = np.copy(rad) for iscups in range(0, nscups): l1 = self.Scups["lvl1"][iscups] - 1 l2 = self.Scups["lvl2"][iscups] - 1 popmat[l1 + ci, l2 + ci] += self.EDensity * dexRate[iscups, itemp] popmat[l2 + ci, l1 + ci] += self.EDensity * exRate[iscups, itemp] popmat[l1 + ci, l1 + ci] -= self.EDensity * exRate[iscups, itemp] popmat[l2 + ci, l2 + ci] -= self.EDensity * dexRate[iscups, itemp] for isplups in range(0, npsplups): l1 = self.Psplups["lvl1"][isplups] - 1 l2 = self.Psplups["lvl2"][isplups] - 1 # for proton excitation, the levels are all below the ionization potential # popmat[l1 + ci, l2 + ci] += self.PDensity[itemp] * pdexRate[isplups, itemp] popmat[l2 + ci, l1 + ci] += self.PDensity[itemp] * pexRate[isplups, itemp] popmat[l1 + ci, l1 + ci] -= self.PDensity[itemp] * pexRate[isplups, itemp] popmat[l2 + ci, l2 + ci] -= self.PDensity[itemp] * pdexRate[isplups, itemp] # now include ionization rate from if ci: # # the ciRate can be computed for all temperatures # ciTot = 0. for itrans in range(len(cilvl['lvl1'])): lvl1 = cilvl['lvl1'][itrans] - 1 lvl2 = cilvl['lvl2'][itrans] - 1 #mult = lowMult[lvl1-1] popmat[lvl2 + ci, lvl1] += self.EDensity * self.CilvlRate['rate'][ itrans, itemp] popmat[lvl1, lvl1] -= self.EDensity * self.CilvlRate['rate'][ itrans, itemp] ciTot += self.EDensity * self.CilvlRate['rate'][itrans, itemp] # popmat[1, 0] += ( self.EDensity * self.Lower.IonizRate['rate'][itemp] - ciTot) popmat[0, 0] -= ( self.EDensity * self.Lower.IonizRate['rate'][itemp] - ciTot) popmat[0, 1] += self.EDensity * self.RecombRate['rate'][itemp] popmat[1, 1] -= self.EDensity * self.RecombRate['rate'][itemp] if rec: # # ndens=1 if hasattr(self, 'DrRateLvl'): for idr, rate in enumerate(self.DrRateLvl['rate']): l1 = self.Auto["lvl1"][idr] - 1 l2 = self.Auto["lvl2"][idr] - 1 popmat[l2 + ci, -1] += self.EDensity * rate[itemp] popmat[-1, -1] -= self.EDensity * rate[itemp] # # DrRateLvl['totalRate'] includes the branching ratio dielTot = self.DrRateLvl['totalRate'][itemp] else: dielTot = 0. # # for itrans in range(self.Nreclvl): lvl1 = reclvl['lvl1'][itrans] - 1 lvl2 = reclvl['lvl2'][itrans] - 1 popmat[lvl2 + ci, -1] += self.EDensity * self.ReclvlRate['rate'][ itrans, itemp] popmat[-1, -1] -= self.EDensity * self.ReclvlRate['rate'][ itrans, itemp] # if self.Nreclvl: recTot = self.ReclvlRate['rate'][:, itemp].sum() else: recTot = 0. # # popmat[-1, ci] += self.EDensity * self.IonizRate['rate'][itemp] popmat[ci, ci] -= self.EDensity * self.IonizRate['rate'][itemp] # netRecomb = self.EDensity * ( self.Higher.RecombRate['rate'][itemp] - recTot - dielTot) # if netRecomb > 0.: popmat[ci, -1] += netRecomb popmat[-1, -1] -= netRecomb # # normalize to unity # ndens = 1 norm = np.ones(nlvls + ci + rec, 'float64') if ci: norm[0] = 0. if rec: norm[-1] = 0. popmata = np.copy(popmat) normRow = (nlvls + ci + rec - 1) / 2 #popmata[nlvls+ci+rec-1]=norm popmata[normRow] = norm b = np.zeros(nlvls + ci + rec, 'float64') #b[nlvls+ci+rec-1]=1. b[normRow] = 1. #popmata[nlvls+ci+rec-1]=norm #b=np.zeros(nlvls+ci+rec,'float64') #b[nlvls+ci+rec-1] = 1. # b[-1] = 1. try: thispop = np.linalg.solve(popmata, b) pop[itemp] = thispop[ci:ci + nlvls] popHigher[itemp] = thispop[-1] except np.linalg.LinAlgError: pop[itemp] = np.zeros(nlvls, 'float64') popHigher[itemp] = 0. # print ' error in matrix inversion, setting populations to zero at T = ', ('%8.2e')%(temperature[itemp]) # elif ntemp == 1: pop = np.zeros((ndens, nlvls), "float64") popHigher = np.zeros(ndens, 'float64') for idens in range(0, ndens): popmat = np.copy(rad) for iscups in range(0, nscups): l1 = self.Scups["lvl1"][iscups] - 1 l2 = self.Scups["lvl2"][iscups] - 1 # popmat[l1 + ci, l2 + ci] += self.EDensity[idens] * dexRate[iscups] popmat[l2 + ci, l1 + ci] += self.EDensity[idens] * exRate[iscups] popmat[l1 + ci, l1 + ci] -= self.EDensity[idens] * exRate[iscups] popmat[l2 + ci, l2 + ci] -= self.EDensity[idens] * dexRate[iscups] # for isplups in range(0, npsplups): l1 = self.Psplups["lvl1"][isplups] - 1 l2 = self.Psplups["lvl2"][isplups] - 1 # popmat[l1 + ci, l2 + ci] += self.PDensity[idens] * pdexRate[isplups] popmat[l2 + ci, l1 + ci] += self.PDensity[idens] * pexRate[isplups] popmat[l1 + ci, l1 + ci] -= self.PDensity[idens] * pexRate[isplups] popmat[l2 + ci, l2 + ci] -= self.PDensity[idens] * pdexRate[isplups] # now include ionization rate from if ci: # # ciTot = 0. for itrans in range(len(cilvl['lvl1'])): lvl1 = cilvl['lvl1'][itrans] - 1 lvl2 = cilvl['lvl2'][itrans] - 1 popmat[lvl2 + ci, lvl1] += self.EDensity[ idens] * self.CilvlRate['rate'][itrans] popmat[lvl1, lvl1] -= self.EDensity[ idens] * self.CilvlRate['rate'][itrans] ciTot += self.EDensity[idens] * self.CilvlRate['rate'][ itrans] popmat[1, 0] += ( self.EDensity[idens] * self.Lower.IonizRate['rate'] - ciTot) popmat[0, 0] -= ( self.EDensity[idens] * self.Lower.IonizRate['rate'] - ciTot) popmat[0, 1] += self.EDensity[idens] * self.RecombRate['rate'] popmat[1, 1] -= self.EDensity[idens] * self.RecombRate['rate'] if rec: #ntemp = 1 if hasattr(self, 'DrRateLvl'): for idr, rate in enumerate(self.DrRateLvl['rate']): l1 = self.Auto["lvl1"][idr] - 1 l2 = self.Auto["lvl2"][idr] - 1 # popmat[l2+ci,l1+ci+nlvls] += self.EDensity[idens]*self.DrRateLvl['rate'][idr] # popmat[l1+ci,l1+ci] -= self.EDensity[idens]*self.DrRateLvl['rate'][idr] popmat[l2 + ci, -1] += self.EDensity[idens] * rate popmat[-1, -1] -= self.EDensity[idens] * rate # dielTot = self.DrRateLvl['totalRate'][0] else: dielTot = 0. if self.Nreclvl: recTot = self.ReclvlRate['rate'].sum() else: recTot = 0. # popmat[-1, ci] += self.EDensity[idens] * self.IonizRate['rate'] popmat[ci, ci] -= self.EDensity[idens] * self.IonizRate['rate'] # netRecomb = self.EDensity[idens] * ( self.Higher.RecombRate['rate'] - recTot - dielTot) if netRecomb > 0.: popmat[ci, -1] += netRecomb popmat[-1, -1] -= netRecomb # # for itrans in range(len(rrlvl['lvl1'])): for itrans in range(self.Nreclvl): lvl1 = reclvl['lvl1'][itrans] - 1 lvl2 = reclvl['lvl2'][itrans] - 1 popmat[lvl2 + ci, -1] += self.EDensity[ idens] * self.ReclvlRate['rate'][itrans] popmat[-1, -1] -= self.EDensity[idens] * self.ReclvlRate[ 'rate'][itrans] # # normalize to unity norm = np.ones(nlvls + ci + rec, 'float64') if ci: norm[0] = 0. if rec: norm[-1] = 0. popmata = np.copy(popmat) popmata[nlvls + ci + rec - 1] = norm # popmat[nlvls+ci+rec-1] = norm b = np.zeros(nlvls + ci + rec, 'float64') b[nlvls + ci + rec - 1] = 1. try: thispop = np.linalg.solve(popmata, b) pop[idens] = thispop[ci:ci + nlvls] popHigher[idens] = thispop[-1] except np.linalg.LinAlgError: pop[idens] = np.zeros(nlvls, 'float64') popHigher[idens] = 0. # print ' error in matrix inversion, setting populations to zero at eDensity = ', ('%8.2e')%(eDensity[idens]) # elif ntemp > 1 and ntemp == ndens: pop = np.zeros((ntemp, nlvls), "float64") popHigher = np.zeros(ntemp, 'float64') for itemp in range(0, ntemp): temp = self.Temperature[itemp] popmat = np.copy(rad) for iscups in range(0, nscups): l1 = self.Scups["lvl1"][iscups] - 1 l2 = self.Scups["lvl2"][iscups] - 1 # popmat[l1 + ci, l2 + ci] += self.EDensity[itemp] * dexRate[iscups, itemp] popmat[l2 + ci, l1 + ci] += self.EDensity[itemp] * exRate[iscups, itemp] popmat[l1 + ci, l1 + ci] -= self.EDensity[itemp] * exRate[iscups, itemp] popmat[l2 + ci, l2 + ci] -= self.EDensity[itemp] * dexRate[iscups, itemp] # proton rates for isplups in range(0, npsplups): l1 = self.Psplups["lvl1"][isplups] - 1 l2 = self.Psplups["lvl2"][isplups] - 1 # for proton excitation, the levels are all below the ionization potential # popmat[l1 + ci, l2 + ci] += self.PDensity[itemp] * pdexRate[isplups, itemp] popmat[l2 + ci, l1 + ci] += self.PDensity[itemp] * pexRate[isplups, itemp] popmat[l1 + ci, l1 + ci] -= self.PDensity[itemp] * pexRate[isplups, itemp] popmat[l2 + ci, l2 + ci] -= self.PDensity[itemp] * pdexRate[isplups, itemp] # now include ionization rate from if ci: # # the ciRate can be computed for all temperatures # ciTot = 0. for itrans in range(len(cilvl['lvl1'])): lvl1 = cilvl['lvl1'][itrans] - 1 lvl2 = cilvl['lvl2'][itrans] - 1 popmat[lvl2 + ci, lvl1] += self.EDensity[ itemp] * self.CilvlRate['rate'][itrans, itemp] popmat[lvl1, lvl1] -= self.EDensity[ itemp] * self.CilvlRate['rate'][itrans, itemp] ciTot += self.EDensity[itemp] * self.CilvlRAte['rate'][ itrans, itemp] popmat[1, 0] += (self.EDensity[itemp] * self.Lower.IonizRate['rate'][itemp] - ciTot) popmat[0, 0] -= (self.EDensity[itemp] * self.Lower.IonizRate['rate'][itemp] - ciTot) popmat[ 0, 1] += self.EDensity[itemp] * self.RecombRate['rate'][itemp] popmat[ 1, 1] -= self.EDensity[itemp] * self.RecombRate['rate'][itemp] if rec: # if hasattr(self, 'DrRateLvl'): # branch = np.zeros(self.Ndielsplups, 'float64') for idr, rate in enumerate(self.DrRateLvl['rate']): l1 = self.Auto["lvl1"][idr] - 1 l2 = self.Auto["lvl2"][idr] - 1 popmat[l2 + ci, l1 + ci + nlvls] += self.EDensity[itemp] * rate[itemp] popmat[-1, -1] -= self.EDensity[itemp] * rate[itemp] # dielTot = self.DrRateLvl['totalRate'][itemp] else: dielTot = 0. # if self.Nreclvl: recTot = self.RrlvlRate['rate'][:, itemp].sum() else: recTot = 0. # # popmat[ -1, ci] += self.EDensity[itemp] * self.IonizRate['rate'][itemp] popmat[ ci, ci] -= self.EDensity[itemp] * self.IonizRate['rate'][itemp] # netRecomb = self.EDensity[itemp] * ( self.Higher.RecombRate['rate'][itemp] - recTot - dielTot) if netRecomb > 0.: popmat[ci, -1] += netRecomb popmat[-1, -1] -= netRecomb # for itrans in range(self.Nreclvl): lvl1 = reclvl['lvl1'][itrans] - 1 lvl2 = reclvl['lvl2'][itrans] - 1 popmat[lvl2 + ci, -1] += self.EDensity[ itemp] * self.RrlvlRate['rate'][itrans, itemp] popmat[-1, -1] -= self.EDensity[itemp] * self.RrlvlRate[ 'rate'][itrans, itemp] # # normalize to unity norm = np.ones(nlvls + ci + rec, 'float64') if ci: norm[0] = 0. if rec: norm[-1] = 0. popmata = np.copy(popmat) popmata[nlvls + ci + rec - 1] = norm # popmat[nlvls+ci+rec-1] = norm b = np.zeros(nlvls + ci + rec, 'float64') b[nlvls + ci + rec - 1] = 1. try: thispop = np.linalg.solve(popmata, b) pop[itemp] = thispop[ci:ci + nlvls] popHigher[itemp] = thispop[-1] except np.linalg.LinAlgError: pop[itemp] = np.zeros(nlvls, 'float64') popHigher[itemp] = 0. # print ' error in matrix inversion, setting populations to zero at T = ', ('%8.2e')%(temperature[itemp]) # pop = np.where(pop > 0., pop, 0.) self.Population = { "temperature": temperature, "eDensity": eDensity, "population": pop, "protonDensity": protonDensity, "ci": ci, "rec": rec, 'popmat': popmata, 'b': b, 'rad': rad } if rec: self.Population['popHigher'] = popHigher self.Population['higher'] = self.Higher self.Population['recTot'] = recTot self.Population['dielTot'] = dielTot self.Population['netRecomb'] = netRecomb # return
def ionGate(self, elementList = None, ionList = None, minAbund=None, doLines=1, doContinuum=1, doWvlTest=1, verbose=0): ''' creates a list of ions for free-free, free-bound, and line intensity calculations if doing the radiative losses, accept all wavelength -> doWvlTest=0 the list is a dictionary self.Todo ''' # masterlist = chdata.MasterList abundAll = self.AbundAll # nonzed = abundAll > 0. minAbundAll = abundAll[nonzed].min() if minAbund: if minAbund < minAbundAll: minAbund = minAbundAll ionInfo = chio.masterListInfo() # if hasattr(self, 'Wavelength'): wvlRange = [self.Wavelength.min(), self.Wavelength.max()] elif hasattr(self, 'WvlRange'): wvlRange = self.WvlRange else: print(' need a wavelength range in ionGate ') # temperature = self.Temperature # # use the ionList but make sure the ions are in the database self.Todo = {} # # if elementList: for i, element in enumerate(elementList): elementList[i] = element.lower() if verbose: print('el = %s'%(element)) z = const.El.index(element.lower()) + 1 for one in masterlist: nameDict = util.convertName(one) if nameDict['Element'].lower() in elementList: if verbose: print(' ion = %s'%(one)) if doLines: self.Todo[one] = 'line' for stage in range(2, z+2): name = util.zion2name(z, stage) if doContinuum and not nameDict['Dielectronic']: if name not in self.Todo.keys(): self.Todo[name] = 'ff' else: self.Todo[name] += '_ff' self.Todo[name] += '_fb' if ionList: for one in ionList: nameDict = util.convertName(one) if masterlist.count(one): if doLines: self.Todo[one] = 'line' else: if verbose: pstring = ' %s not in CHIANTI database'%(one) print(pstring) if doContinuum and not nameDict['Dielectronic']: if one not in self.Todo.keys(): self.Todo[one] = 'ff' else: self.Todo[one] += '_ff' self.Todo[one] += '_fb' # # # if minAbund: for iz in range(1, 31): abundance = chdata.Abundance[self.AbundanceName]['abundance'][iz-1] if abundance >= minAbund: if verbose: print(' %5i %5s abundance = %10.2e '%(iz, const.El[iz-1], abundance)) # for ionstage in range(1, iz+1): ionS = util.zion2name(iz, ionstage) masterListTest = ionS in masterlist masterListInfoTest = ionS in sorted(ionInfo.keys()) if masterListTest or masterListInfoTest: if masterListTest or masterListInfoTest: if doWvlTest: wvlTestMin = wvlRange[0] <= ionInfo[ionS]['wmax'] wvlTestMax = wvlRange[1] >= ionInfo[ionS]['wmin'] else: wvlTestMin = 1 wvlTestMax = 1 ioneqTest = (temperature.max() >= ionInfo[ionS]['tmin']) and (temperature.min() <= ionInfo[ionS]['tmax']) # construct similar test for the dielectronic files ionSd = util.zion2name(iz, ionstage, dielectronic=1) masterListTestD = ionSd in masterlist masterListInfoTestD = ionSd in sorted(ionInfo.keys()) if masterListTestD or masterListInfoTestD: if doWvlTest: wvlTestMinD = wvlRange[0] <= ionInfo[ionSd]['wmax'] wvlTestMaxD = wvlRange[1] >= ionInfo[ionSd]['wmin'] else: wvlTestMinD = 1 wvlTestMaxD = 1 ioneqTestD = (temperature.max() >= ionInfo[ionSd]['tmin']) and (temperature.min() <=ionInfo[ionSd]['tmax']) # if masterListTest and wvlTestMin and wvlTestMax and ioneqTest and doLines: if ionS in sorted(self.Todo.keys()): self.Todo[ionS] += '_line' else: self.Todo[ionS] = 'line' # get dielectronic lines if verbose: print(' for ion %s do : %s'%(ionS, self.Todo[ionS])) if masterListTestD and wvlTestMinD and wvlTestMaxD and ioneqTestD and doLines: if ionSd in sorted(self.Todo.keys()): self.Todo[ionSd] += '_line' else: self.Todo[ionSd] = 'line' if verbose: print(' for ion %s do : %s'%(ionSd, self.Todo[ionSd])) # # for ionstage in range(2, iz+2): ionS = util.zion2name(iz, ionstage) if ionS in ionInfo.keys(): ioneqTest = (temperature.max() >= ionInfo[ionS]['tmin']) and (temperature.min() <= ionInfo[ionS]['tmax']) else: ioneqTest = 1 # construct similar test for the dielectronic files if ioneqTest and doContinuum: # ionS is the target ion, cannot be the neutral for the continuum # if verbose: # print(' setting up continuum calculation for %s '%(ionS)) if ionS in sorted(self.Todo.keys()): self.Todo[ionS] += '_ff_fb' else: self.Todo[ionS] = 'ff_fb' if verbose: print(' for ion %s do : %s'%(ionS, self.Todo[ionS])) if len(self.Todo.keys()) == 0: print(' no elements have been selected') print(' it is necessary to provide an elementList, an ionList, or set minAbund > 0.') return
def populateNew(self, popCorrect=1, verbose=0, **kwargs): """ Calculate level populations for specified ion. This is a new version that will enable the calculation of dielectronic satellite lines without resorting to the dielectronic ions, such as c_5d possible keyword arguments include temperature, eDensity, pDensity, radTemperature and rStar this is a developmental method for using the autoionizing A-values to determine level resolved dielectronic recombination rates """ # # for one in kwargs.keys(): if one not in chdata.keywordArgs: print(' keyword is not understood - %s'%(one)) # nlvls = self.Nlvls nwgfa = self.Nwgfa nscups = self.Nscups npsplups = self.Npsplups # if kwargs.has_key('temperature'): self.Temperature = np.asarray(kwargs['temperature']) temperature = self.Temperature elif hasattr(self, 'Temperature'): temperature=self.Temperature else: print(' no temperature values have been set') return {'errorMessage':' no temperature values have been set'} # if kwargs.has_key('eDensity'): self.EDensity = np.asarray(kwargs['eDensity']) eDensity = self.EDensity elif hasattr(self, 'EDensity'): eDensity = self.EDensity else: print(' no eDensity values have been set') return {'errorMessage':' no eDensity values have been set'} # if kwargs.has_key('pDensity'): if kwargs['pDensity'] == 'default': self.p2eRatio() protonDensity = self.ProtonDensityRatio*self.EDensity else: try: self.PDensity = np.asarray(kwargs['pDensity']) except: print(' could not interpret value for keyword pDensity') print(' should be either "default" or a number or array') return else: if hasattr(self, 'PDensity'): protonDensity = self.PDensity else: self.p2eRatio() self.PDensity = self.ProtonDensityRatio*self.EDensity protonDensity = self.PDensity print(' proton density not specified, set to "default"') # if 'radTemperature' in kwargs.keys() and 'rStar' in kwargs.keys(): self.RadTemperature = np.asarray(kwargs['radTemperature']) radTemperature = np.array(self.RadTemperature) self.RStar = np.asarray(kwargs['rStar']) rStar = np.asarray(self.RStar) elif hasattr(self, 'RadTemperature') and hasattr(self, 'RStar'): radTemperature = self.RadTemperature rStar = self.RStar # # # if self.Ncilvl: ci = 1 cilvl = self.Cilvl # if hasattr(self, 'CilvlRate'): # cilvlRate = self.CilvlRate # else: # self.cireclvlDescale('cilvl') # cilvlRate = self.CilvlRate self.recombRate() # lowers = util.zion2name(self.Z, self.Ion-1) # get the lower ionization stage self.Lower = ion(lowers, temperature=self.Temperature, eDensity = self.EDensity) self.Lower.ionizRate() # need to get multiplicity of lower ionization stage lowMult = self.Lower.Elvlc['mult'] else: ci = 0 # evetually will be looking for just an recLvl attribute # # if the higher ion does not exist in the database, rec=0 highers = util.zion2name(self.Z, self.Ion+1) if self.Nreclvl: reclvl = self.Reclvl if hasattr(self, 'ReclvlRate'): reclvlRate = self.ReclvlRate else: self.cireclvlDescale('reclvl') reclvlRate = self.ReclvlRate if hasattr(self, 'Auto'): self.drRateLvl() if self.Nreclvl or hasattr(self, 'Auto'): rec=1 # get ionization rate of this ion self.ionizRate() # get the higher ionization stage else: rec = 0 # if the higher ion does not exist in the database, rec=0 highers = util.zion2name(self.Z, self.Ion+1) # if highers in chdata.MasterList: self.Higher = ion(highers, temperature=self.Temperature, eDensity=self.EDensity) self.Higher.recombRate() # else: # # rad=np.zeros((nlvls+ci+rec,nlvls+ci+rec),"float64") # the populating matrix for radiative transitions # # for iwgfa in range(nwgfa): l1 = self.Wgfa["lvl1"][iwgfa]-1 l2 = self.Wgfa["lvl2"][iwgfa]-1 rad[l1+ci,l2+ci] += self.Wgfa["avalue"][iwgfa] rad[l2+ci,l2+ci] -= self.Wgfa["avalue"][iwgfa] # photo-excitation and stimulated emission if self.RadTemperature: if not self.RStar: dilute = 0.5 else: dilute = util.dilute(self.RStar) # next - don't include autoionization lines if abs(self.Wgfa['wvl'][iwgfa]) > 0.: if self.Elvlc['ecm'][l2] >= 0.: ecm2 = self.Elvlc['ecm'][l2] else: ecm2 = self.Elvlc['ecmth'][l2] # if self.Elvlc['ecm'][l1] >= 0.: ecm1 = self.Elvlc['ecm'][l1] else: ecm1 = self.Elvlc['ecmth'][l1] de = const.invCm2Erg*(ecm2 - ecm1) dekt = de/(const.boltzmann*self.RadTemperature) # photoexcitation phexFactor = dilute*(float(self.Elvlc['mult'][l2])/float(self.Elvlc['mult'][l1]))/(np.exp(dekt) -1.) rad[l2+ci,l1+ci] += self.Wgfa["avalue"][iwgfa]*phexFactor rad[l1+ci,l1+ci] -= self.Wgfa["avalue"][iwgfa]*phexFactor # stimulated emission stemFactor = dilute/(np.exp(-dekt) -1.) rad[l1+ci,l2+ci] += self.Wgfa["avalue"][iwgfa]*stemFactor rad[l2+ci,l2+ci] -= self.Wgfa["avalue"][iwgfa]*stemFactor if hasattr(self, 'Auto'): # as of 7/2012, we only consider the ground level in the next higher ionization stage # hence, requiring lvl1 = 1 or, l1 = 0 for iauto, avalue in enumerate(self.Auto['avalue']): l1 = self.Auto["lvl1"][iauto] -1 l2 = self.Auto["lvl2"][iauto] -1 # for now only consider a single level for upper/higher ion if l1 == 0 and rec: rad[l1 + ci + nlvls, l2 + ci] += avalue rad[l2 + ci, l2 + ci] -= avalue # if the higher ion is not in the database, decay to the ground level elif l1 == 0: rad[l1 + ci, l2 + ci] += avalue rad[l2 + ci, l2 + ci] -= avalue # # if self.Nscups: # print(' Nscups = %10i'%(self.Nscups)) self.upsilonDescale() ups = self.Upsilon['upsilon'] exRate = self.Upsilon['exRate'] dexRate = self.Upsilon['dexRate'] # if self.Npsplups: self.upsilonDescaleSplups(prot=1) # pups = self.PUpsilon['upsilon'] pexRate = self.PUpsilon['exRate'] pdexRate = self.PUpsilon['dexRate'] # temp=temperature ntemp=temp.size # cc=const.collision*self.EDensity ndens=cc.size if self.Npsplups: cp=const.collision*protonDensity if ntemp > 1 and ndens >1 and ntemp != ndens: print(' unless temperature or eDensity are single values') print(' the number of temperatures values must match the ') print(' the number of eDensity values') return # # get corrections for recombination and excitation # nscups = self.Nscups # # first, for ntemp=ndens=1 if ndens==1 and ntemp==1: popmat=np.copy(rad) for iscups in range(0,nscups): l1=self.Scups["lvl1"][iscups]-1 l2=self.Scups["lvl2"][iscups]-1 # popmat[l1+ci,l2+ci] += self.EDensity*dexRate[iscups] popmat[l2+ci,l1+ci] += self.EDensity*exRate[iscups] popmat[l1+ci,l1+ci] -= self.EDensity*exRate[iscups] popmat[l2+ci,l2+ci] -= self.EDensity*dexRate[iscups] # for isplups in range(0,npsplups): l1=self.Psplups["lvl1"][isplups]-1 l2=self.Psplups["lvl2"][isplups]-1 # popmat[l1+ci,l2+ci] += self.PDensity*pdexRate[isplups] popmat[l2+ci,l1+ci] += self.PDensity*pexRate[isplups] popmat[l1+ci,l1+ci] -= self.PDensity*pexRate[isplups] popmat[l2+ci,l2+ci] -= self.PDensity*pdexRate[isplups] # now include ionization rate from if ci: # # the ciRate can be computed for all temperatures # ciTot = 0. for itrans in range(len(cilvl['lvl1'])): lvl1 = cilvl['lvl1'][itrans]-1 lvl2 = cilvl['lvl2'][itrans]-1 # this is kind of double booking the ionization rate components popmat[lvl2+ci, lvl1] += self.EDensity*self.CilvlRate['rate'][itrans] popmat[lvl1, lvl1] -= self.EDensity*self.CilvlRate['rate'][itrans] ciTot += self.EDensity*self.CilvlRate['rate'][itrans] # popmat[1, 0] += (self.EDensity*self.Lower.IonizRate['rate'] - ciTot) popmat[0, 0] -= (self.EDensity*self.Lower.IonizRate['rate'] - ciTot) popmat[0, 1] += self.EDensity*self.RecombRate['rate'] popmat[1, 1] -= self.EDensity*self.RecombRate['rate'] if rec: # ntemp=ndens=1 # if hasattr(self, 'DrRateLvl'): # branch = np.zeros(self.Ndielsplups, 'float64') for idr, rate in enumerate(self.DrRateLvl['rate']): l1 = self.Auto["lvl1"][idr] - 1 l2 = self.Auto["lvl2"][idr] - 1 # popmat[l2+ci,-1] += self.EDensity*self.DrRateLvl['rate'][idr] # popmat[-1, -1] -= self.EDensity*self.DrRateLvl['rate'][idr] popmat[l2+ci,-1] += self.EDensity*rate popmat[-1, -1] -= self.EDensity*rate # dielTot = self.DrRateLvl['totalRate'][0] else: dielTot = 0. # # # for itrans in range(self.Nreclvl): # lvl1 = reclvl['lvl1'][itrans]-1 lvl2 = reclvl['lvl2'][itrans]-1 popmat[lvl2+ci, -1] += self.EDensity*reclvlRate['rate'][itrans] popmat[-1, -1] -= self.EDensity*reclvlRate['rate'][itrans] if self.Nreclvl: recTot = reclvlRate['rate'].sum(axis=0) else: recTot = 0. # # popmat[-1, ci] += self.EDensity*self.IonizRate['rate'] popmat[ci, ci] -= self.EDensity*self.IonizRate['rate'] # # next 2 line take care of overbooking netRecomb = self.EDensity*(self.Higher.RecombRate['rate'][0]- recTot - dielTot) # if netRecomb > 0.: popmat[ci, -1] += netRecomb popmat[-1, -1] -= netRecomb # # normalize to unity norm=np.ones(nlvls+ci+rec,'float64') if ci: norm[0] = 0. if rec: norm[nlvls+ci+rec-1] = 0. popmata = np.copy(popmat) normRow = (nlvls+ci+rec-1)/2 #popmata[nlvls+ci+rec-1]=norm popmata[normRow]=norm b=np.zeros(nlvls+ci+rec,'float64') #b[nlvls+ci+rec-1]=1. b[normRow] = 1. try: fullpop=np.linalg.solve(popmata,b) pop = fullpop[ci:ci+nlvls] if rec: popHigher = fullpop[-1] else: popHigher = 0. except np.linalg.LinAlgError: pop = np.zeros(nlvls, 'float64') popHigher = 0. # print ' error in matrix inversion, setting populations to zero at T = ', ('%8.2e')%(temperature) # # ------------- ntemp = 1 --------------------------------------------------------- # # elif ndens == 1: pop = np.zeros((ntemp, nlvls),"float64") popHigher = np.zeros(ntemp, 'float64') # pop=np.zeros((ntemp,ci + nlvls + rec),"float64") for itemp in range(ntemp): popmat=np.copy(rad) for iscups in range(0,nscups): l1=self.Scups["lvl1"][iscups]-1 l2=self.Scups["lvl2"][iscups]-1 popmat[l1+ci,l2+ci] += self.EDensity*dexRate[iscups, itemp] popmat[l2+ci,l1+ci] += self.EDensity*exRate[iscups, itemp] popmat[l1+ci,l1+ci] -= self.EDensity*exRate[iscups, itemp] popmat[l2+ci,l2+ci] -= self.EDensity*dexRate[iscups, itemp] for isplups in range(0,npsplups): l1=self.Psplups["lvl1"][isplups]-1 l2=self.Psplups["lvl2"][isplups]-1 # for proton excitation, the levels are all below the ionization potential # popmat[l1+ci,l2+ci] += self.PDensity[itemp]*pdexRate[isplups, itemp] popmat[l2+ci,l1+ci] += self.PDensity[itemp]*pexRate[isplups, itemp] popmat[l1+ci,l1+ci] -= self.PDensity[itemp]*pexRate[isplups, itemp] popmat[l2+ci,l2+ci] -= self.PDensity[itemp]*pdexRate[isplups, itemp] # now include ionization rate from if ci: # # the ciRate can be computed for all temperatures # ciTot = 0. for itrans in range(len(cilvl['lvl1'])): lvl1 = cilvl['lvl1'][itrans]-1 lvl2 = cilvl['lvl2'][itrans]-1 #mult = lowMult[lvl1-1] popmat[lvl2+ci, lvl1] += self.EDensity*self.CilvlRate['rate'][itrans, itemp] popmat[lvl1, lvl1] -= self.EDensity*self.CilvlRate['rate'][itrans, itemp] ciTot += self.EDensity*self.CilvlRate['rate'][itrans, itemp] # popmat[1, 0] += (self.EDensity*self.Lower.IonizRate['rate'][itemp] - ciTot) popmat[0, 0] -= (self.EDensity*self.Lower.IonizRate['rate'][itemp] - ciTot) popmat[0, 1] += self.EDensity*self.RecombRate['rate'][itemp] popmat[1, 1] -= self.EDensity*self.RecombRate['rate'][itemp] if rec: # # ndens=1 if hasattr(self, 'DrRateLvl'): for idr, rate in enumerate(self.DrRateLvl['rate']): l1 = self.Auto["lvl1"][idr] - 1 l2 = self.Auto["lvl2"][idr] - 1 popmat[l2 + ci, -1] += self.EDensity*rate[itemp] popmat[-1, -1] -= self.EDensity*rate[itemp] # # DrRateLvl['totalRate'] includes the branching ratio dielTot = self.DrRateLvl['totalRate'][itemp] else: dielTot = 0. # # for itrans in range(self.Nreclvl): lvl1 = reclvl['lvl1'][itrans]-1 lvl2 = reclvl['lvl2'][itrans]-1 popmat[lvl2+ci, -1] += self.EDensity*self.ReclvlRate['rate'][itrans, itemp] popmat[-1, -1] -= self.EDensity*self.ReclvlRate['rate'][itrans, itemp] # if self.Nreclvl: recTot = self.ReclvlRate['rate'][:, itemp].sum() else: recTot = 0. # # popmat[-1, ci] += self.EDensity*self.IonizRate['rate'][itemp] popmat[ci, ci] -= self.EDensity*self.IonizRate['rate'][itemp] # netRecomb = self.EDensity*(self.Higher.RecombRate['rate'][itemp]- recTot - dielTot) # if netRecomb > 0.: popmat[ci, -1] += netRecomb popmat[-1, -1] -= netRecomb # # normalize to unity # ndens = 1 norm=np.ones(nlvls+ci+rec,'float64') if ci: norm[0] = 0. if rec: norm[-1] = 0. popmata = np.copy(popmat) normRow = (nlvls+ci+rec-1)/2 #popmata[nlvls+ci+rec-1]=norm popmata[normRow]=norm b=np.zeros(nlvls+ci+rec,'float64') #b[nlvls+ci+rec-1]=1. b[normRow] = 1. #popmata[nlvls+ci+rec-1]=norm #b=np.zeros(nlvls+ci+rec,'float64') #b[nlvls+ci+rec-1] = 1. # b[-1] = 1. try: thispop=np.linalg.solve(popmata,b) pop[itemp] = thispop[ci:ci+nlvls] popHigher[itemp] = thispop[-1] except np.linalg.LinAlgError: pop[itemp] = np.zeros(nlvls, 'float64') popHigher[itemp] = 0. # print ' error in matrix inversion, setting populations to zero at T = ', ('%8.2e')%(temperature[itemp]) # elif ntemp == 1: pop=np.zeros((ndens,nlvls),"float64") popHigher = np.zeros(ndens, 'float64') for idens in range(0,ndens): popmat=np.copy(rad) for iscups in range(0,nscups): l1=self.Scups["lvl1"][iscups]-1 l2=self.Scups["lvl2"][iscups]-1 # popmat[l1+ci,l2+ci] += self.EDensity[idens]*dexRate[iscups] popmat[l2+ci,l1+ci] += self.EDensity[idens]*exRate[iscups] popmat[l1+ci,l1+ci] -= self.EDensity[idens]*exRate[iscups] popmat[l2+ci,l2+ci] -= self.EDensity[idens]*dexRate[iscups] # for isplups in range(0,npsplups): l1=self.Psplups["lvl1"][isplups]-1 l2=self.Psplups["lvl2"][isplups]-1 # popmat[l1+ci,l2+ci] += self.PDensity[idens]*pdexRate[isplups] popmat[l2+ci,l1+ci] += self.PDensity[idens]*pexRate[isplups] popmat[l1+ci,l1+ci] -= self.PDensity[idens]*pexRate[isplups] popmat[l2+ci,l2+ci] -= self.PDensity[idens]*pdexRate[isplups] # now include ionization rate from if ci: # # ciTot = 0. for itrans in range(len(cilvl['lvl1'])): lvl1 = cilvl['lvl1'][itrans] -1 lvl2 = cilvl['lvl2'][itrans] -1 popmat[lvl2+ci, lvl1] += self.EDensity[idens]*self.CilvlRate['rate'][itrans] popmat[lvl1, lvl1] -= self.EDensity[idens]*self.CilvlRate['rate'][itrans] ciTot += self.EDensity[idens]*self.CilvlRate['rate'][itrans] popmat[1, 0] += (self.EDensity[idens]*self.Lower.IonizRate['rate'] -ciTot) popmat[0, 0] -= (self.EDensity[idens]*self.Lower.IonizRate['rate'] -ciTot) popmat[0, 1] += self.EDensity[idens]*self.RecombRate['rate'] popmat[1, 1] -= self.EDensity[idens]*self.RecombRate['rate'] if rec: #ntemp = 1 if hasattr(self, 'DrRateLvl'): for idr, rate in enumerate(self.DrRateLvl['rate']): l1 = self.Auto["lvl1"][idr] - 1 l2 = self.Auto["lvl2"][idr] - 1 # popmat[l2+ci,l1+ci+nlvls] += self.EDensity[idens]*self.DrRateLvl['rate'][idr] # popmat[l1+ci,l1+ci] -= self.EDensity[idens]*self.DrRateLvl['rate'][idr] popmat[l2+ci,-1] += self.EDensity[idens]*rate popmat[-1, -1] -= self.EDensity[idens]*rate # dielTot = self.DrRateLvl['totalRate'][0] else: dielTot = 0. if self.Nreclvl: recTot = self.ReclvlRate['rate'].sum() else: recTot = 0. # popmat[-1, ci] += self.EDensity[idens]*self.IonizRate['rate'] popmat[ci, ci] -= self.EDensity[idens]*self.IonizRate['rate'] # netRecomb = self.EDensity[idens]*(self.Higher.RecombRate['rate'] - recTot - dielTot) if netRecomb > 0.: popmat[ci, -1] += netRecomb popmat[-1, -1] -= netRecomb # # for itrans in range(len(rrlvl['lvl1'])): for itrans in range(self.Nreclvl): lvl1 = reclvl['lvl1'][itrans]-1 lvl2 = reclvl['lvl2'][itrans]-1 popmat[lvl2+ci, -1] += self.EDensity[idens]*self.ReclvlRate['rate'][itrans] popmat[-1, -1] -= self.EDensity[idens]*self.ReclvlRate['rate'][itrans] # # normalize to unity norm=np.ones(nlvls+ci+rec,'float64') if ci: norm[0] = 0. if rec: norm[-1] = 0. popmata = np.copy(popmat) popmata[nlvls+ci+rec-1] = norm # popmat[nlvls+ci+rec-1] = norm b = np.zeros(nlvls+ci+rec,'float64') b[nlvls+ci+rec-1] = 1. try: thispop=np.linalg.solve(popmata,b) pop[idens] = thispop[ci:ci+nlvls] popHigher[idens] = thispop[-1] except np.linalg.LinAlgError: pop[idens] = np.zeros(nlvls, 'float64') popHigher[idens] = 0. # print ' error in matrix inversion, setting populations to zero at eDensity = ', ('%8.2e')%(eDensity[idens]) # elif ntemp>1 and ntemp==ndens: pop=np.zeros((ntemp,nlvls),"float64") popHigher = np.zeros(ntemp, 'float64') for itemp in range(0,ntemp): temp=self.Temperature[itemp] popmat=np.copy(rad) for iscups in range(0,nscups): l1=self.Scups["lvl1"][iscups]-1 l2=self.Scups["lvl2"][iscups]-1 # popmat[l1+ci,l2+ci] += self.EDensity[itemp]*dexRate[iscups, itemp] popmat[l2+ci,l1+ci] += self.EDensity[itemp]*exRate[iscups, itemp] popmat[l1+ci,l1+ci] -= self.EDensity[itemp]*exRate[iscups, itemp] popmat[l2+ci,l2+ci] -= self.EDensity[itemp]*dexRate[iscups, itemp] # proton rates for isplups in range(0,npsplups): l1=self.Psplups["lvl1"][isplups]-1 l2=self.Psplups["lvl2"][isplups]-1 # for proton excitation, the levels are all below the ionization potential # popmat[l1+ci,l2+ci] += self.PDensity[itemp]*pdexRate[isplups, itemp] popmat[l2+ci,l1+ci] += self.PDensity[itemp]*pexRate[isplups, itemp] popmat[l1+ci,l1+ci] -= self.PDensity[itemp]*pexRate[isplups, itemp] popmat[l2+ci,l2+ci] -= self.PDensity[itemp]*pdexRate[isplups, itemp] # now include ionization rate from if ci: # # the ciRate can be computed for all temperatures # ciTot = 0. for itrans in range(len(cilvl['lvl1'])): lvl1 = cilvl['lvl1'][itrans] -1 lvl2 = cilvl['lvl2'][itrans] -1 popmat[lvl2+ci, lvl1] += self.EDensity[itemp]*self.CilvlRate['rate'][itrans, itemp] popmat[lvl1, lvl1] -= self.EDensity[itemp]*self.CilvlRate['rate'][itrans, itemp] ciTot += self.EDensity[itemp]*self.CilvlRAte['rate'][itrans, itemp] popmat[1, 0] += (self.EDensity[itemp]*self.Lower.IonizRate['rate'][itemp] - ciTot) popmat[0, 0] -= (self.EDensity[itemp]*self.Lower.IonizRate['rate'][itemp] - ciTot) popmat[0, 1] += self.EDensity[itemp]*self.RecombRate['rate'][itemp] popmat[1, 1] -= self.EDensity[itemp]*self.RecombRate['rate'][itemp] if rec: # if hasattr(self, 'DrRateLvl'): # branch = np.zeros(self.Ndielsplups, 'float64') for idr, rate in enumerate(self.DrRateLvl['rate']): l1 = self.Auto["lvl1"][idr] - 1 l2 = self.Auto["lvl2"][idr] - 1 popmat[l2+ci,l1+ci+nlvls] += self.EDensity[itemp]*rate[itemp] popmat[-1, -1] -= self.EDensity[itemp]*rate[itemp] # dielTot = self.DrRateLvl['totalRate'][itemp] else: dielTot = 0. # if self.Nreclvl: recTot = self.RrlvlRate['rate'][:, itemp].sum() else: recTot = 0. # # popmat[-1, ci] += self.EDensity[itemp]*self.IonizRate['rate'][itemp] popmat[ci, ci] -= self.EDensity[itemp]*self.IonizRate['rate'][itemp] # netRecomb = self.EDensity[itemp]*(self.Higher.RecombRate['rate'][itemp] - recTot - dielTot) if netRecomb > 0.: popmat[ci, -1] += netRecomb popmat[-1, -1] -= netRecomb # for itrans in range(self.Nreclvl): lvl1 = reclvl['lvl1'][itrans]-1 lvl2 = reclvl['lvl2'][itrans]-1 popmat[lvl2+ci, -1] += self.EDensity[itemp]*self.RrlvlRate['rate'][itrans, itemp] popmat[-1, -1] -= self.EDensity[itemp]*self.RrlvlRate['rate'][itrans, itemp] # # normalize to unity norm=np.ones(nlvls+ci+rec,'float64') if ci: norm[0] = 0. if rec: norm[-1] = 0. popmata = np.copy(popmat) popmata[nlvls+ci+rec-1] = norm # popmat[nlvls+ci+rec-1] = norm b=np.zeros(nlvls+ci+rec,'float64') b[nlvls+ci+rec-1]=1. try: thispop=np.linalg.solve(popmata,b) pop[itemp] = thispop[ci:ci+nlvls] popHigher[itemp] = thispop[-1] except np.linalg.LinAlgError: pop[itemp] = np.zeros(nlvls, 'float64') popHigher[itemp] = 0. # print ' error in matrix inversion, setting populations to zero at T = ', ('%8.2e')%(temperature[itemp]) # pop=np.where(pop >0., pop,0.) self.Population={"temperature":temperature, "eDensity":eDensity, "population":pop, "protonDensity":protonDensity, "ci":ci, "rec":rec, 'popmat':popmata, 'b':b, 'rad':rad} if rec: self.Population['popHigher']= popHigher self.Population['higher'] = self.Higher self.Population['recTot'] = recTot self.Population['dielTot'] = dielTot self.Population['netRecomb'] = netRecomb # return
def calculate(self, temperature): """ Calculate ion fractions for given temperature array using the total ionization and recombination rates. """ self.Temperature = np.array(temperature, 'float64') if self.Temperature.size == 1: print(' temperature must be an array') return ionList = [] chIons = [] z = self.Z for stage in range(1, z+2): ionStr = util.zion2name(z, stage) ionList.append(ionStr) atom = ion(ionStr, temperature = self.Temperature) atom.ionizRate() atom.recombRate() chIons.append(atom) ntemp = chIons[0].IonizRate['temperature'].size if ntemp == 1: ioneq = np.zeros((z+1), 'Float64') factor = [] for anIon in chIons: if hasattr(anIon, 'RecombRate') and hasattr(anIon, 'IonizRate'): rat = anIon.IonizRate['rate']/anIon.RecombRate['rate'] factor.append(rat**2 + rat**(-2)) else: factor.append(0.) factor[0] = max(factor) factor[-1] = max(factor) ionmax = factor.index(min(factor)) ioneq[ionmax] = 1. for iz in range(ionmax+1, z+1): ionrate = chIons[iz-1].IonizRate['rate'] recrate = chIons[iz].RecombRate['rate'] ioneq[iz] = ionrate*ioneq[iz-1]/recrate for iz in range(ionmax-1, -1, -1): ionrate = chIons[iz].IonizRate['rate'] recrate = chIons[iz+1].RecombRate['rate'] ioneq[iz] = recrate*ioneq[iz+1]/ionrate ionsum = ioneq.sum() ioneq = ioneq/ionsum self.Ioneq = ioneq else: ioneq = np.zeros((z+1,ntemp ), 'Float64') for it in range(ntemp): factor = [] for anIon in chIons: if type(anIon.IonizRate) != type(None) and type(anIon.RecombRate) != type(None): ioniz = anIon.IonizRate['rate'][it] recomb = anIon.RecombRate['rate'][it] if ioniz == 0. or recomb == 0.: rat = 1.e-100 else: rat = anIon.IonizRate['rate'][it]/anIon.RecombRate['rate'][it] try: factor.append(rat**2 + rat**(-2)) except: factor.append(0.) else: factor.append(0.) factor[0] = max(factor) factor[-1] = max(factor) ionmax = factor.index(min(factor)) ioneq[ionmax, it] = 1. for iz in range(ionmax+1, z+1): ionrate = chIons[iz-1].IonizRate['rate'][it] recrate = chIons[iz].RecombRate['rate'][it] if recrate != 0.: ioneq[iz, it] = ionrate*ioneq[iz-1, it]/recrate else: ioneq[iz, it] = 0. for iz in range(ionmax-1, -1, -1): ionrate = chIons[iz].IonizRate['rate'][it] recrate = chIons[iz+1].RecombRate['rate'][it] if ionrate != 0.: ioneq[iz, it] = recrate*ioneq[iz+1, it]/ionrate else: ioneq[iz, it] = 0. ionsum = ioneq[:, it].sum() ioneq[:, it] = ioneq[:, it]/ionsum self.Ioneq = ioneq