Fork me on GitHub

Nebular Plasma

The NebularPlasma class is a more complex description of the Plasma state than the LTEPlasma. It takes a dilution factor (W) into account, which deals with the dilution of the radiation field due to geometric, line-blocking and other effects.

The calculations follow the same steps as LTEPlasma, however the calculations are different and often take into account if a particular level is meta-stable or not. NebularPlasma will start calculating the partition functions.

\[Z_{i,j} = \underbrace{\sum_{k=0}^{max(k)_{i,j}} g_k \times e^{-E_k / (k_\textrm{b} T)}}_\textrm{metastable levels} + \underbrace{W\times\sum_{k=0}^{max(k)_{i,j}} g_k \times e^{-E_k / (k_\textrm{b} T)}}_\textrm{non-metastable levels}\]

, where Z is the partition function, g is the degeneracy factor, E the energy of the level, T the temperature of the radiation field and W the dilution factor.

The next step is to calculate the ionization balance using the Saha ionization equation. and then calculating the Number density of the ions (and an electron number density) in a second step. In the first step, we calculate the ionization balance using the LTE approximation (\(\Phi_{i, j}(\textrm{LTE})\)). Then we adjust the ionization balance using two factors \(\zeta\) and \(\delta\).

Calculating Zeta

\(\zeta\) is read in for specific temperatures and then interpolated for the target temperature.

Calculating Delta

\(\delta\) is a radiation field correction factors which is calculated according to Mazzali & Lucy 1993 ([3]; henceforth ML93)

In ML93 the radiation field correction factor is denoted as \(\delta\) and is calculated in Formula 15 & 20

The radiation correction factor changes according to a ionization energy threshold \(\chi_\textrm{T}\) and the species ionization threshold (from the ground state) \(\chi_0\).

For \(\chi_\textrm{T} \ge \chi_0\)

\[\delta = \frac{T_\textrm{e}}{b_1 W T_\textrm{R}} \exp(\frac{\chi_\textrm{T}}{k T_\textrm{R}} - \frac{\chi_0}{k T_\textrm{e}})\]

For \(\chi_\textrm{T} < \chi_0\)

\[\delta = 1 - \exp(\frac{\chi_\textrm{T}}{k T_\textrm{R}} - \frac{\chi_0}{k T_\textrm{R}}) + \frac{T_\textrm{e}}{b_1 W T_\textrm{R}} \exp(\frac{\chi_\textrm{T}}{k T_\textrm{R}} - \frac{\chi_0}{k T_\textrm{e}}),\]

where \(T_\textrm{R}\) is the radiation field Temperature, \(T_\textrm{e}\) is the electron temperature and W is the dilution factor.

Now we can calculate the ionization balance using equation 14 in [3]:

\[\begin{split}\Phi_{i,j} &= \frac{N_{i, j+1} n_e}{N_{i, j}} \\\end{split}\]\[\begin{split}\Phi_{i, j} &= W \times[\delta \zeta + W ( 1 - \zeta)] \left(\frac{T_\textrm{e}}{T_\textrm{R}}\right)^{1/2} \Phi_{i, j}(\textrm{LTE}) \\\end{split}\]

In the last step, we calculate the ion number densities according using the methods in LTEPlasma

Finally we calculate the level populations (NebularPlasma.calculate_level_populations()), by using the calculated ion species number densities:

\[\begin{split}N_{i, j, k}(\textrm{not metastable}) &= W\frac{g_k}{Z_{i, j}}\times N_{i, j} \times e^{-\beta_\textrm{rad} E_k} \\ N_{i, j, k}(\textrm{metastable}) &= \frac{g_k}{Z_{i, j}}\times N_{i, j} \times e^{-\beta_\textrm{rad} E_k} \\\end{split}\]

This concludes the calculation of the nebular plasma. In the code, the next step is calculating the \(\tau_\textrm{Sobolev}\) using the quantities calculated here.

Example Calculations

import os

from matplotlib import colors
from tardis import atomic, plasma, util
from matplotlib import pyplot as plt

import numpy as np
import pandas as pd

#Making 2 Figures for ionization balance and level populations

plt.figure(1).clf()
ax1 = plt.figure(1).add_subplot(111)

plt.figure(2).clf()
ax2 = plt.figure(2).add_subplot(111)

# expanding the tilde to the users directory
atom_fname = os.path.expanduser('~/.tardis/si_kurucz.h5')

# reading in the HDF5 File
atom_data = atomic.AtomData.from_hdf5(atom_fname)

#The atom_data needs to be prepared to create indices. The Class needs to know which atomic numbers are needed for the
#calculation and what line interaction is needed (for "downbranch" and "macroatom" the code creates special tables)
atom_data.prepare_atom_data([14], 'scatter')

#Initializing the NebularPlasma class using the from_abundance class method.
#This classmethod is normally only needed to test individual plasma classes
#Usually the plasma class just gets the number densities from the model class
nebular_plasma = plasma.NebularPlasma.from_abundance(10000, 0.5, {'Si': 1}, 1e-13, atom_data, 10.)


#Initializing a dataframe to store the ion populations  and level populations for the different temperatures
ion_number_densities = pd.DataFrame(index=nebular_plasma.ion_populations.index)
level_populations = pd.DataFrame(index=nebular_plasma.level_populations.ix[14, 1].index)
t_rads = np.linspace(2000, 20000, 100)

#Calculating the different ion populations and level populuatios for the given temperatures
for t_rad in t_rads:
    nebular_plasma.update_radiationfield(t_rad, w=1.0)
    #getting total si number density
    si_number_density = nebular_plasma.number_density.get_value(14)
    #Normalizing the ion populations
    ion_density = nebular_plasma.ion_populations / si_number_density
    ion_number_densities[t_rad] = ion_density

    #normalizing the level_populations for Si II
    current_level_population = nebular_plasma.level_populations.ix[14, 1] / nebular_plasma.ion_populations.ix[14, 1]
    #normalizing with statistical weight
    current_level_population /= atom_data.levels.ix[14, 1].g

    level_populations[t_rad] = current_level_population

ion_colors = ['b', 'g', 'r', 'k']

for ion_number in [0, 1, 2, 3]:
    current_ion_density = ion_number_densities.ix[14, ion_number]
    ax1.plot(current_ion_density.index, current_ion_density.values, '%s-' % ion_colors[ion_number],
             label='Si %s W=1.0' % util.int_to_roman(ion_number + 1).upper())


#only plotting every 5th radiation temperature
t_rad_normalizer = colors.Normalize(vmin=2000, vmax=20000)
t_rad_color_map = plt.cm.ScalarMappable(norm=t_rad_normalizer, cmap=plt.cm.jet)

for t_rad in t_rads[::5]:
    ax2.plot(level_populations[t_rad].index, level_populations[t_rad].values, color=t_rad_color_map.to_rgba(t_rad))
    ax2.semilogy()

#Calculating the different ion populations for the given temperatures with W=0.5
ion_number_densities = pd.DataFrame(index=nebular_plasma.ion_populations.index)
for t_rad in t_rads:
    nebular_plasma.update_radiationfield(t_rad, w=0.5)
    #getting total si number density
    si_number_density = nebular_plasma.number_density.get_value(14)
    #Normalizing the ion populations
    ion_density = nebular_plasma.ion_populations / si_number_density
    ion_number_densities[t_rad] = ion_density

    #normalizing the level_populations for Si II
    current_level_population = nebular_plasma.level_populations.ix[14, 1] / nebular_plasma.ion_populations.ix[14, 1]
    #normalizing with statistical weight
    current_level_population /= atom_data.levels.ix[14, 1].g

    level_populations[t_rad] = current_level_population

#Plotting the ion fractions

for ion_number in [0, 1, 2, 3]:
    print "w=0.5"
    current_ion_density = ion_number_densities.ix[14, ion_number]
    ax1.plot(current_ion_density.index, current_ion_density.values, '%s--' % ion_colors[ion_number],
             label='Si %s W=0.5' % util.int_to_roman(ion_number + 1).upper())

for t_rad in t_rads[::5]:
    ax2.plot(level_populations[t_rad].index, level_populations[t_rad].values, color=t_rad_color_map.to_rgba(t_rad),
             linestyle='--')
    ax2.semilogy()

t_rad_color_map.set_array(t_rads)
cb = plt.figure(2).colorbar(t_rad_color_map)

ax1.set_xlabel('T [K]')
ax1.set_ylabel('Number Density Fraction')
ax1.legend()

ax2.set_xlabel('Level Number for Si II')
ax2.set_ylabel('Number Density Fraction')
cb.set_label('T [K]')

plt.show()

(Source code)