ChiantiPy: a Python package for Astrophysical Spectroscopy

ChiantiPy is an interface to the CHIANTI atomic database for astrophysical spectroscopy. The highly-cited CHIANTI project, now in its 20th year, is an invaluable resource to the solar physics community. The ChiantiPy project brings the power of the scientific Python stack to the CHIANTI database, allowing solar physicists and astronomers to easily make use of this atomic data and calculate commonly used quantities from it such as radiative loss rates and emissivities for particular atomic transitions. This paper will discuss the capabilities of the CHIANTI database and the ChiantiPy project as well as the current state of the project and its place in the solar physics community. We will demonstrate how the core modules in ChiantiPy can be used to study emission from optically thin transitions and the continuum in the x-ray and EUV wavelengths. Additionally, we will discuss some of the infrastructure around the ChiantiPy project and some of the goals for the near future.


Introduction
Nearly all astrophysical observations are done through remote sensing. Light at various wavelengths is collected by instruments, either ground-or space-based, in an attempt to understand physical processes happening in distant astrophysical objects. However, in order to translate these detector measurements to meaningful physical insight, it is necessary to understand what physical conditions give rise to different spectral lines and continuum emission. Started in 1996 by researchers at the Naval Research Laboratory, the University of Cambridge, and Arcetri Astrophysical Observatory in Florence for the purpose of analyzing solar spectra, the CHIANTI atomic database provides a set of up-todate atomic data for thirty different elements as well as a suite of tools, written in the proprietary Interactive Data Language (IDL), for analyzing this data. Described in a series of 15 papers from 1997 to 2016 that have been cited collectively over 3000 times (see Table 1), the CHIANTI database is an invaluable resource to the solar physics community.
The CHIANTI project is comprised of two main parts: the database containing the actual atomic data and the IDL software libraries for accessing the data and calculating useful quantities from them. The database provides atomic data for optically-thin
Given the growing popularity of Python in the solar community and the importance of CHIANTI to solar observers and modelers alike, a well-supported Python interface to this database is critical. The ChiantiPy project, started in 2003 by Ken Dere, provides a Python package for interacting with the CHIANTI  database and an alternative to the IDL tools. ChiantiPy is not a direct translation of its IDL counterpart, but instead provides an intuitive object oriented interface to the database (compared to the more functional approach in IDL). ChiantiPy provides an easy to use API to the raw atomic data in the CHIANTI database as well as Python versions of all the primary calculations performed by the original IDL software, including the level balance equation and the ionization equilibrium calculation. This paper will give a brief overview of the CHIANTI database and demonstrate the core capabilities of the ChiantiPy package. These include the line emission for transitions and continuum emission of particular ions as well as spectra and radiative loss rates summed over many ions. We will also discuss the infrastructure of the package and plans for the future of the package.

Database
The CHIANTI database is organized as a collection of directories and ASCII files that can be downloaded as a tarball from the CHIANTI database website or as part of the SolarSoftware (or SolarSoft) IDL package [FH98]. The solar physics community has typically relied on the latter as SolarSoft has served as the main hub for solar data analysis software for the last several decades. SolarSoft provides routines for updating software packages automatically and so traditionally CHIANTI users have updated their distributions, including both the software and the database, in this manner 1 . The structure of the CHIANTI database is such that each top level directory represents an element and each subdirectory is an ion of that element. Files in each of the subdirectories contain pieces of information attached to each ion. The database generally follows the structure {el}/{el}_{ion}/{el}_{ion}.{filetype}, where el is the element, ion is the ion number, and filetype is the file extension. For example, the energy level information for Fe V is in the file fe/fe_5/fe_5.elvlc. A few of these filetypes are summarized in Table 2. For a complete description of all of the different filetypes available, see Table 1 of [YDL + 16] and the CHIANTI user guide. Fig. 1 shows all of the available ions in the CHIANTI database as well as the number of levels available for each ion. ChiantiPy provides several low-level functions for reading raw data directly from the CHIANTI database. For example, to find the energy of the emitted photon for each transition for Fe V (i.e. the fifth ionization state of iron), you would first read in level information for each transition for a given ion, import ChiantiPy.tools.util as ch_util fe5_wgfa = ch_util.wgfaRead('fe_5') ilvl1 = np.array(fe5_wgfa['lvl1']) -1 ilvl2 = np.array(fe5_wgfa['lvl2']) -1 and then use the indices of the level to find the associated level energies in the ELVLC data, where the associated energy levels are given in cm −1 . In general, these functions are only used internally by the core ChiantiPy objects. However, users who need access to the raw data may find them useful.
In addition to each of the files associated with each ion, CHIANTI also provides abundance and ionization equilibrium data for each element in the database. The elemental abundance, N(X)/N(H) (i.e. the number of atoms of element X relative to the number of hydrogen atoms), in the corona and photosphere has been measured by many workers and these various measurements have been collected in the CHIANTI atomic database. For example, to read the abundance of Fe as measured by [ As with the other CHIANTI data files, the abundance values are typically read internally and then exposed to the user through more abstract objects like the ion class so reading them in this way is not necessary. Similarly, the ionization equilibrium of each ion of each element is available as a function of temperature and various sets of ionization equilibria data can be used. More details about the ionization equilibrium can be found in later sections.
Default values for the abundance and ionization equilibrium files as well as the units for wavelength (nm,Å, or eV) and energy (ergs or photons) can be set in the users chiantirc file, located in~/.chianti/chiantirc. These settings are stored in ChiantiPy.tools.data.Defaults and can be changed at anytime.
Unless otherwise noted, all quantities are expressed in the cgs unit system, with the exception of wavelengths which are recorded in angstroms (Å). As discussed above, some energies in the CHIANTI atomic database, particularly those pertaining to levels in an atom, are stored in cm −1 for convenience (i.e. with h = c = 1, a common convention in atomic physics). Results of any calculation in ChiantiPy will always be returned in cgs units (unless explicitly stated in the chiantirc file, e.g. photons instead of ergs).

Common Calculations and API
The majority of the ChiantiPy codebase is divided into two modules: tools and core. The former contains utility and helper functions that are mostly for internal use. The latter contains the primary objects for interacting with the data in the CHIANTI atomic database and performing many common calculations with these data. A summary of the objects in core can be found in Table 3. These objects can be roughly divided into two categories: those that deal with information and calculations about individual ions and those that aggregate information over a range of ions in order to perform some calculation. The ion and Continuum 1. The easiest way to acquire the CHIANTI database is to download and unpack the tarball at http://www.chiantidatabase.org/chianti_download.html. In order for ChiantiPy to find the database, it is necessary to point the XUVTOP environment variable to the top of the CHIANTI directory tree. For example, if the database is downloaded to $HOME/chianti, export XUVTOP=$HOME/chianti/dbase should be placed in the Bash shell configuration file.  5  5  5  10  15  20  204  10  204  49  25   5  70  53  52  16  161 209 195  86  72  72  204  92  243  49  25   161 209  3  10  15  20  204  10  204  45  25   29  20  161 209 195  86  72  46  204  92  243  49  25   20  161 209 195  10  15  20  204  10  243  49  25   161 209 195  86  72  46  204 108 243  49  25   209 195  10  13  20  204  10  204  49  25   138  86  22  49  204  46  243  49  36   7  35  46  204 166 268  49  25   50  58  204  20  268  49  25   42  204  20  331  Free-free and free-bound continuum for individual ions ioneq Ionization equilibrium for individual elements spectrum Calculate synthetic spectra for a range of ions radLoss Total radiative losses from multiple ions, including continuum objects calculate emissivity information related to specific ions while ioneq, spectrum, and radLoss require information from multiple ions and/or elements.

Line Emission
The most essential and actively developed portion of the ChiantiPy package is the ion object which provides an interface to the data and associated calculations for each ion in the database. The ion object is initialized with an ion name, a temperature range, and a density 2 , import ChiantiPy.core as ch import numpy as np temperature = np.logspace(4,6,100) density = 1e9 fe_5 = ch.ion ('fe_5',temperature,density) In this example, we've initialized an ion object for Fe V over a temperature range of T = 10 4 − 10 6 K at a constant electron density of n e = 10 9 cm −3 . All of the data discussed in the previous section are available as attributes of the ion object (e.g. .Elvlc and .Wgfa are dictionaries holding the various fields available in the corresponding filetypes listed in Table 3). In general, ChiantiPy objects follow the convention that methods are lowercase and return their value(s) to attributes with corresponding uppercase names 3 . For example, the abundance value of Fe is stored in fe_5.Abundance and the ionization equilibrium is calculated using the method fe_5.ioneqOne() with the value being returned to the attribute fe_5.IoneqOne.
One of the most often used calculations in CHIANTI and Chi-antiPy is the energy level populations as a function of temperature. When calculating the energy level populations in a low density, high temperature, optically-thin plasma, collisional excitation and subsequent decay often occur much more quickly than ionization and recombination, allowing these two processes to be decoupled. Furthermore, it is assumed that all transitions occur between the excited state and the ground state. These two assumptions make up what is commonly known as the coronal model approximation. Thus, the level balance equation can be written as, where A k j is the radiative decay rate, C jk is the collisional excitation coefficient, and N j is the number of electrons in excited state j [YDL + 16]. Since A and C are given by the CHIANTI database, this expression can be solved iteratively to find n j = N j / ∑ j N j , the fraction of electrons in excited state j or the level population fraction. Proton excitation rates, primarily between fine structure levels, can also be included in the calculation of n j . See Eq. 4 of [YDZL + 03].
The method fe_5.populate() can then be used to calculate the level populations for Fe V. This method populates the fe_5.Population attribute and a 100 × 34 array (i.e. number of temperatures by number of energy levels) is stored in fe_5.Population ['population']. ChiantiPy also provides the convenience method fe_5.popPlot() which provides a quick visualization of level population as a function of temperature for several of the most populated levels. Note that this calculation can be quite expensive for large temperature/density arrays and for ions with many transitions. The left panel of Fig.  2 shows the level population as a function of temperature, n j (T ), for all of the energy levels of Fe V in the CHIANTI database. When dealing with spectral line emission, we are often most interested in the line intensity, that is, the power per unit volume as a function of temperature (and density). For a particular transition λ i j , the line intensity can be written as, where Ab(X) is the abundance and X k is the ionization equilibrium. To calculate the intensity for each transition in CHIANTI for Fe V, we can use the method fe_5.intensity() which returns a 100 × 219 array (i.e. dimension of temperature by the number of available transitions). The convenience methods fe_5.intensityPlot() and fe_5.intensityList() can also be used to quickly visualize and enumerate the most intense lines produced by the ion, respectively. Finally, the intensity can be convolved with a filter to calculate the intensity as a continuous function of wavelength to simulate an observed spectrum. For a single ion this is done using the fe_5.spectrum() method (see later sections for creating multi-ion spectra). To create a spectrum for Fe V between 2600Å and 2900Å, wavelength = np.arange(2.6e3,2.9e3,0.1) fe_5.spectrum (wavelength) This method also accepts an optional keyword argument for specifying a filter with which to convolve the intensity. The default filter is a Gaussian though ChiantiPy.tools.filters includes several different filters including Lorentzian and Boxcar filters. The right panel of Fig. 2 shows the Fe V intensity (black) and spectrum folded through a Gaussian (blue) and Lorentzian (green) filter at the temperature at which the ionization fraction is maximized, T ≈ 8.5 × 10 4 K. Similar to the fe_5.populate() and fe_5.intensity(), ChiantiPy also provides the convenience method fe_5.spectrumPlot() for quickly visualizing a spectrum.

Continuum Emission
In addition to calculating emissivities for individual spectral lines, ChiantiPy also calculates the free-free, free-bound, and twophoton continuua as a function of wavelength and temperature for each ion. In particular, the Continuum object is used to calculate the free-free and free-bound emissivities. Free-free emission (or bremsstrahlung) is produced by collisions between free electrons and positively charged ions. The free-free emissivity is given by, where α is the fine structure constant, Z is the nuclear charge, T is the electron temperature, andḡ f f is the velocity-averaged Gaunt factor [RL79].ḡ f f is calculated using the methods of [ISK + 00] (Continuum.itoh_gaunt_factor()) and [Sut98] (Continuum.sutherland_gaunt_factor()), depending on the temperature range.
Similarly, free-bound emission is produced when a free electron collides with a positively-charged ion and the previouslyfree electron is captured into an excited state of the ion. Because this process (unlike free-free emission) involves the details of the energy level structure of the ion, its formulation is necessarily quantum mechanical though a semi-classical treatment is possible (see Section 4.7.2 of [PFL08] and Section 10.5 of [RL79]). From [YDZL + 03], the free-bound emission can be calculated as, where E = hc/λ is the photon energy, ω i and ω 0 are the statistical weights of the i th level of the recombined ion and the ground level of the recombining ion, respectively, σ b f i is the photoionization cross-section, and I i is the ionization potential of level i. The cross-sections are calculated using the methods of [VY95] (for the ground state, i.e. i = 0) and [KL61] (for i = 0). An optional use_verner keyword argument (True by default) is included in the Continuum.calclulate_free_bound_emission() so that users can choose to only use the method of [KL61] in the photoionization cross-section calculation.
To calculate the free-free and free-bound emission with Chi-antiPy, temperature = np.logspace(6,8.5,100) cont_fe18 = ch.Continuum('fe_18',temperature) wavelength = np.logspace(0,3,100) The Continuum.calculate_free_free_emission() (Continuum.calculate_free_bound_emission()) method stores the N T by N λ array (e.g. in the above example, 100 × 100) in the Continuum.free_free_emission (Continuum.free_bound_emission) attribute. The left and middle panels of Fig. 3 show the free-free, free-bound, and total continuum emission as a function of temperature and wavelength, respectively, and the rightmost panel shows the total continuum emission as a function of both temperature and wavelength for the Fe XVIII ion. The Continuum object also provides methods for calculating the free-free and free-bound radiative losses (i.e. the wavelengthintegrated emission). These methods are primarily used by the radiativeLoss module. The Continuum module has recently been completely refactored and validated against the corresponding IDL results.
A contribution from the two-photon continuum can also be calculated with ChiantiPy though this is included in the ion object through the method ion.twoPhoton(). The two-photon continuum calculation is included in the ion object and not the Continuum object because the level populations are required when calculating the two-photon emissivity. See Eq. 11 of [YDZL + 03].

Ionization Equilibrium
The ionization equilibrium of a particular ion describes what fraction of the ions of an element are in a particular ionization state at a given temperature. Specifically, the ionization equilibrium is determined by the balance of ionization and recombination rates. For an element X and an ionization state i, assuming ionization equilibrium, the ionization state X i = N(X +i )/N(X) is given by, where I i and R i are the total ionization and recombination rates for ionization state i, respectively. In CHIANTI, these rates are assumed to be density-independent and only a function of temperature.
In ChiantiPy, the ionization equilibrium for a particular element can be calculated using the ioneq module, The ioneq.calculate() method sets the Ioneq attribute, an array with Z + 1 columns and N T rows, where N T is the length of the temperature array. In the example above, fe_ioneq.Ioneq has 27 rows (i.e. Z = 26 for Fe) and 500 columns. Fig. 4 shows the ion population fractions for four different elements as a function of temperature, assuming ionization equilibrium. The ioneq module also allows the user to load a predefined set of ionization equilibria via the ioneq.load() method. Though CHIANTI includes several ionization equilibrium datasets from other workers, it is recommended to use the most up to date version supplied by CHIANTI (see [DLY + 09] for more details).
To load the ionization equilibrium data for Fe, fe_ioneq = ch.ioneq('Fe') fe_ioneq.load() This will populate the fe_ioneq.Temperature and fe_ioneq.Ioneq attributes with data from the appropriate ionization equilibrium file. By default, this will be ioneq/chianti.ioneq unless otherwise specified in the chiantirc file or the ioneqName keyword argument.

Spectra
In addition to being able to calculate spectra for single ions, ChiantiPy also provides a wrapper for calculating composite spectra for a range of ions, including continuum contributions. This is handled through the spectrum object. To calculate a composite spectrum in ChiantiPy, temperature = np.array ([1e+6,4e+6,1e+7]) density = 1e9 wavelength = np.linspace(10,100,1000) min_abund = 1e-4 spec = ch.spectrum (temperature, density, wavelength, minAbund=min_abund) The spectrum as a continuous function of wavelength can then be accessed in the spec.Spectrum['intensity'] attribute as a N T × N λ array (i.e. 3 × 1000 in the above example. Most of the keywords that can be passed to ion.spectrum() can also be passed to ChiantiPy.spectrum() and the attributes that are available following the calculation are largely the same. Fig. 5 shows the integrated spectrum as calculated above with several of the included transitions labeled. Because of the need to perform calculations and aggregate data over a large range of ions, running ChiantiPy.spectrum() can be very time consuming, particularly for large temperature/density ranges. The above code snippet takes approximately five minutes to execute on a modern desktop machine. To help mitigate this difficulty, ChiantiPy provides a parallelized version of the ChiantiPy.spectrum module called ChiantiPy.mspectrum 4 which takes advantage of the multiprocessing package and can help to speed up the calculation, particularly on machines with many cores. The interface to the parallelized code is largely the same as the serial version.

Radiative Losses
The radiative loss rate is an important quantity for calculating the energy loss in coronal plasmas, particularly in hydrodynamic simulations of coronal loops. The total radiative loss rate is given by, is the contribution to the radiative losses summed over every element (X), ion (X k ) and transition (λ i j ), and Λ continuum includes the free-free, free-bound, and two-photon continuum contributions to the radiative loss.
4. ChiantiPy provides an additional module ChiantiPy.ipymspectrum to support parallelized spectrum calculations inside the Jupyter notebook and qtconsole.
In ChiantiPy, the radiative loss rate can be calculated using the radLoss module for a particular temperature and density range. To calculate the total radiative loss rate for all ions with an abundance greater than 10 −4 , temperature = np.logspace(4,8,100) rl = ch.radLoss (temperature,1e9, Instantiating the radLoss object automatically calculates the radiative loss rate and stores the total loss rate in rl.RadLoss ['rate'], in this case an array of length 100. If the continuum contributions are included (doContinuum is True by default), the free-free, free-bound, and twophoton components are stored in rl.FreeFreeLoss, rl.FreeBoundLoss, and rl.TwoPhotonLoss, respectively. Ions with low abundances can be excluded with the minAbund keyword argument which can speed up the calculation. A custom abundance dataset can also be set with the abundance keyword. Note that the above calculation takes approximately 11 minutes on modern hardware. Fig. 6 shows the total radiative losses using the coronal abundances of [FMS + 92] (solid) and the photospheric abundances of [AGSS09] (dashed). The coronal abundance case is also broken down into the line emission, free-free, free-bound, and two-photon continuum components.

Documentation, Testing, and Infrastructure
The ChiantiPy project has made an effort to embrace modern development practices when it comes to developing, documenting and releasing the ChiantiPy codebase. Like many open source projects started in the late 2000s, ChiantiPy was originally hosted on SourceForge, but has now moved its development entirely to GitHub. The SVN commit history is in the process of being migrated to GitHub as well. The move to GitHub has provided increased development transparency, ease of contribution, and better integration with third-party services.
An integral part of producing quality scientific code, particularly that meant for a large user base, is continually testing said code as improvements are made and features are added. For each merge into master as well as each pull request, a series of tests is run on Travis CI, a continuous integration service that provides free and automated builds configured through GitHub webhooks. This allows each contribution to the codebase to be tested to ensure that these changes do not break the codebase in unexpected ways. Currently, ChiantiPy is tested on Python 2.7, 3.4, and 3.5, with full 3.6 support expected soon. Currently, the ChiantiPy package is installed in each of these environments and minimal set of tests of each core module is run. The actual module tests are currently quite sparse though one of the more pressing goals of the project is to increase test coverage of the core modules.
One of the most important parts of any codebase is the documentation. The ChiantiPy documentation is built using Sphinx and is hosted on Read the Docs. At each merge into the master branch, a new Read the Docs build is kicked off, ensuring that the ChiantiPy API documentation is never out of date with the most recent check in. In addition to the standard API documentation, the ChiantiPy Read the Docs page also provides a tutorial for using the various modules in ChiantiPy as well as a guide for those switching from the IDL version.
ChiantiPy has benefited greatly from the astropy-helpers package template provided by the Astropy collaboration [ART + 13]. F e X I 4 6 .3 9Å F e X II 7 3 .6 0Å F e X II I 3 4 .9 2Å F e X X 3 7 .1 5Å S i IX 8 1 .7 1Å Fig. 5: Total spectrum for all ions with an abundance greater than 10 −4 , including the continuum, integrated over three temperatures, T = 10 6 , 4 × 10 6 , 10 7 K and at a constant density of n = 10 9 cm −3 . A few of the transitions included in the spectrum are denoted by their respective spectroscopic labels and wavelengths. : Combined radiative losses for all ions in the CHIANTI database for coronal abundances (blue,solid) and photospheric abundances (blue, dashed). The coronal abundance case is also broken down into the line emission (green) and free-free (purple), free-bound (red), and two-photon (yellow) continuum components. In the coronal case, the minimum abundance for elements to be included in the calculation is 10 −4 and 10 −6 for the photospheric case.
asropy-helpers provides boilerplate code for setting up documentation and testing frameworks which has allowed the package to adopt modern testing and documentation practices with little effort.

Conclusion
In this paper, we have described the main capabilities of ChiantiPy, a package for astrophysical spectroscopy and an interface to the widely used and highly cited CHIANTI atomic database. Chi-antiPy provides basic functions for reading the raw data as well as higher-level abstractions (e.g. the ion class) for exploring the data and performing common calculations with them. ChiantiPy also provides modules for calculating continuum emission, synthesizing spectra, and calculating radiative loss curves. The project has recently made significant infrastructure improvements by moving development to GitHub, adding automatic documentation builds, and implementing a minimal test suite. Future improvements include the addition of unitful quantities throughout the codebase (e.g. the Astropy unit system) and increased test coverage.