Fork me on GitHub

TARDIS

TARDIS is a Monte Carlo radiative-transfer spectral synthesis code for 1D models of supernova ejecta. It is designed for rapid spectral modelling of supernovae. The code is described in this documentation and Kerzendorf & Sim 2014.

Note

This documentation is currently under construction and does not describe all of the modes of operations available for TARDIS.

Installation

Requirements

Warning

Currently TARDIS only works on 64-bit python installations. We’re working on making it work on 32-bit python distributions.

TARDIS has the following requirements:

numpy==1.10.0
scipy==0.17.0
pandas==0.16.2
pytables==3.2.2
h5py==2.5
matplotlib==1.4.3
astropy==1.0.5
PyYAML==3.11
numexpr==2.4.4
Cython==0.21
jinja2
networkx==1.10
pytest==2.6.3

Most of these requirements are easy to install using package managers like OS X’s macports or linux package managers or using the Anaconda python distribution.

TARDIS is using astropy’s excellent installation helpers and thus uses similar instructions to astropy.

If you are interested in doing some development for TARDIS please read Developer Workflow.

Once you are done you can run the simple example listed in Running TARDIS.

Installing TARDIS with virtualenvs

A virtual environment is python’s way to ensure that the versions of third-party libraries that TARDIS requires do not interfere with the system-wide installation. This is also the way that the majority of core developers for TARDIS operate.

It is nevertheless recommended to install a number of python packages using the system packagemanager. This ensures that third-party non-python libraries like libhdf5, lapack, etc. are installed.

For OS X we recommend the macports package manager:

sudo port install python27 py27-scipy py27-numpy py27-virtualenv py27-astropy py27-h5py py27-yaml py27-pandas py27-pip py27-tables

In Ubuntu 14.04 the pre-requesite install works with this:

sudo apt-get install python-virtualenv python-numpy python-pandas python-scipy python-h5py python-yaml ipython python-matplotlib cython git

First use the following guide to install virtualenv and the TARDIS requirements Python Environment for TARDIS.

After the virtualenv and the requirements are installed, there are a few options of how to proceed.

If one just wants to use TARDIS you can install the latest stable version:

pip install tardis-sn

the latest development version can be installed using:

pip install git+https://github.com/tardis-sn/tardis

If you are interested in doing some development for TARDIS please read Developer Workflow.

Once you are done you can run the simple example listed in Running TARDIS.

Installation Troubles (FAQ)

We highly encourage with any installation problems to try the recommended install method because this often fix problems. Here are some common problems when installing and their fixes:

Problem: While building tardis via python setup.py build you may encounter the following error:

error: tardis/montecarlo/montecarlo.c: Could not find C file tardis/montecarlo/montecarlo.c for Cython file tardis/montecarlo/montecarlo.pyx when building extension tardis.montecarlo.montecarlo. Cython must be installed to build from a git checkout.

Solution: There are several solutions to this problem. A clean checkout will help. To clean up your repository please try python setup.py clean and then git clean -dfx (WARNING will delete any non tardis file in that directory) This will often clean this problem. If it still persists:

Go into the tardis/montecarlo directory and build montecarlo.c by hand:

cython montecarlo.pyx

Then, python setup.py build should run without problems.

Running TARDIS

To run TARDIS requires two files. The atomic database (for more info refer to Atomic Data for TARDIS) and a configuration file (more info at Configuration File).

Running TARDIS in the commandline

After installing TARDIS just download the example directory https://www.dropbox.com/s/svvyr5i7m8ouzdt/tardis_example.tar.gz and run TARDIS with:

tar zxvf tardis_example.tar.gz
cd tardis_example
tardis tardis_example.yml output_spectrum.dat

Then plot the output_spectrum.dat with your favourite plotting program. Here’s an example how to do this with python. (The only thing you need to install is ipython and matplotlib - in addition to TARDIS’s requirements)

ipython --pylab
>>> tardis_spec = loadtxt('output_spectrum.dat')
>>> plot(tardis_spec[:,0], tardis_spec[:,1])

More atomic datasets can be downloaded from Atomic Data for TARDIS.

Running TARDIS interactively

To get more information from each run of TARDIS one can run it interactively and have full access to the model properties

>>> from tardis import run_tardis
>>> model = run_tardis('myconfig.yml')

This model can then be used for inspecting the run as described Accessing Physical Quantities

Graphical User Interface

To get a detailed explanation on gui layout go to Graphical User Interface .

To setup and run the GUI(under development) follow these steps:

The gui can use one of two python bindings for qt, namely PyQt4 and PySide. You can choose which binding is used by setting the environment variable QT_API in your bash.

1. Installing required packages

source activate tardis
conda install ipython=3.0.0 pyside=1.2.1 shiboken=1.2.1

2. Choosing between PySide and PyQt4

#To choose PySide
export QT_API=pyside

#To choose PyQt
export QT_API=pyqt

3. An example of creating a model and GUI

To show the gui from the ipython shell use the following commands.

ipython --pylab=qt4
>>> from tardis import run_tardis
>>> mdl = run_tardis('yamlconfigfile.yml', 'atomdatafile.h5')
>>> from tardis.gui import interface
>>> interface.show(mdl)

If you just want to run from a configuration file and show the results, you can do that outside the ipython shell.To do this navigate to the folder where you installed tardis and go to tardis/tardis/gui, and use the following command.

python interface.py path-to-yaml-configuration-file path-to-atomic-data-file

What can I do with TARDIS?

TARDIS is designed to carry out calculations of synthetic supernova spectra (see Kerzendorf & Sim 2014).

A TARDIS calculation requires

  1. a model for the supernova ejecta (density and composition distribution);
  2. that parameters determining the physical and numerical properties of the code are given.

These inputs are specified via the input file (yaml file). As a starting point, examine the input file in the example download (above). The example file sets up a very simple model in which the density distribution is based on the well-known W7 model (Nomoto et al. 1984; fit following Branch et al. 1985) and in which uniform abundances are specified. To get you started, here is a short list of some settings in that example that can be experimented with to get a feel for using TARDIS. Please contact us if you have questions or would like information on more advanced possibilities!

Change the abundances

In the example file, you can alter the elemental abundances (mass fractions; specified in the “abundances” section). Use this to explore the sensitivity of features to composition.

Change the luminosity

You can alter the (emergent) luminosity and see how this affects the synthetic spectrum. Do this by varying the “luminosity requested” in the “supernova” section.

Change the epoch

In the example file, you can change the epoch for which the synthetic spectrum is calculated (change the value of “time explosion”; specified in the “supernova” section). When doing this you might also change the inner boundary velocity (“start” value in the “velocity” section), and probably the luminosity (see above)!

Experiment with the treatment of line opacity

In the “plasma” section you can change the “line interaction type” between “scatter”, “downbranch” and “macroatom” - investigate how important fluorescence is in your synthetic spectrum. (See Kerzendorf & Sim and references therein for the meaning of these settings.)

Reporting Bugs

Despite our best efforts TARDIS contains bugs. We want to make sure that fixing these is quick and painless for users and developers.

When reporting bugs please use the mailing list (tardis-sn-user) or create an issue for TARDIS on github (??? ;preferred).

If there are very long ouputs needed to debug the problem, please use a website like pastebin that way it is easier for us to look through it.

One of the major problems that we see are mismatches in installed versions of third-party libraries (that’s where virtualenvs really help - see Python Environment for TARDIS). To make sure that we can test this properly please tell us the installed version of third party packages. The easiest way is:

$ pip freeze
Cython==0.20.2
Jinja2==2.7.2
MarkupSafe==0.21
PyYAML==3.11
Pygments==1.6
Sphinx==1.2.2
astropy==1.0.dev10065
astropy-helpers==0.4.2
backports.ssl-match-hostname==3.4.0.2
cov-core==1.11
coverage==3.7.1
docutils==0.11
execnet==1.2.0
h5py==2.4.0a0
ipdb==0.8
ipython==1.2.1
latexcodec==0.3.2
matplotlib==1.4.0
mock==1.0.1
nose==1.3.4
numexpr==2.4
numpy==1.9.0
numpydoc==0.4
oset==0.1.3
pandas==0.14.1
pep8==1.5.6
py==1.4.20
pybtex==0.17
pybtex-docutils==0.2.0
pyparsing==2.0.2
pytest==2.5.2
pytest-cache==1.0
pytest-cov==1.6
pytest-ipdb==0.1-prerelease
pytest-pep8==1.0.6
python-dateutil==2.2
pytz==2014.7
pyzmq==14.2.0
scipy==0.13.3
six==1.8.0
sphinx-bootstrap-theme==0.4.0
sphinxcontrib-bibtex==0.2.9
sphinxcontrib-tikz==0.4.1
tables==3.1.1
tornado==3.2
tox==1.7.1
virtualenv==1.11.6
wsgiref==0.1.2

and pasting the output in your error report, this will show us what packages are installed and used.

The next thing that is useful to do if TARDIS is downloaded in a directory is to run the test suites and find if any of the tests fail:

python setup.py test
running test
running build
running build_py
copying tardis/./macro_atom.c -> build/lib.macosx-10.9-x86_64-2.7/tardis/.
copying tardis/./montecarlo.c -> build/lib.macosx-10.9-x86_64-2.7/tardis/.
running build_ext
skipping 'tardis/montecarlo.c' Cython extension (up-to-date)
skipping 'tardis/macro_atom.c' Cython extension (up-to-date)
running build_scripts
============================= test session starts ==============================
platform darwin -- Python 2.7.8 -- pytest-2.5.1

Running tests in tardis /Users/wkerzend/scripts/python/tardis/docs.

Platform: Darwin-13.4.0-x86_64-i386-64bit

Executable: /Users/wkerzend/.virtualenvs/tardis-devel/bin/python

Full Python Version:
2.7.8 (default, Oct 15 2014, 22:04:42)
[GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.40)]

encodings: sys: ascii, locale: UTF-8, filesystem: utf-8, unicode bits: 15
byteorder: little
float info: dig: 15, mant_dig: 15

Numpy: 1.9.1
Scipy: 0.13.3
astropy: 1.0.dev10065
yaml: 3.11
cython: 0.20.2
h5py: 2.4.0a0
Matplotlib: 1.4.0
ipython: 1.2.1

plugins: cache, cov, ipdb, pep8
collected 107 items

tardis/io/tests/test_ascii_readers.py .......
tardis/io/tests/test_config_reader.py .............................
tardis/io/tests/test_config_validator.py ........................
tardis/io/tests/test_configuration_namespace.py .........
tardis/tests/test_atomic.py .....s
tardis/tests/test_lte_plasma.py ssssssssss
tardis/tests/test_plasma_nlte.py .....
tardis/tests/test_plasma_simple.py .
tardis/tests/test_tardis_full.py s
tardis/tests/test_util.py ...............

==================== 95 passed, 12 skipped in 8.53 seconds =====================

This will hopefully help us to identify us the problem. We will continue to update this document with other techniques.

TARDIS Configuration

Most of the configuration for TARDIS uses the YAML format. If a YAML file is handed to TARDIS it is first checked using a configuration validator to see if the required keywords of the required type exist and produces a dictionary with that has the validated types as well as sensible defaults for missing values built-in. This validator uses a default validation file that already explains a lot about the required configuration items.

Configuration File

tardis_config_version:
  property_type: string
  default: None
  mandatory: True
  help: Version of the configuration file


supernova:
    luminosity_requested:
        property_type: quantity
        mandatory: True
        default: 1 solLum
        help: requested output luminosity for simulation

    time_explosion:
        property_type: quantity
        mandatory: True
        default: None
        help: time since explosion
        
    distance:
        property_type: quantity
        mandatory: False
        default: None
        help: distance to supernova
      
    luminosity_wavelength_start:
        property_type: quantity
        mandatory: False
        default: 0 angstrom
        help: start of the integral needed for getting the luminosity right


    luminosity_wavelength_end:
        property_type: quantity
        mandatory: False
        default: inf angstrom
        help: start of the integral needed for getting the luminosity right

atom_data:
    property_type: string
    mandatory: True
    help: path or filename to the Atomic Data HDF5 file
  
plasma:
    initial_t_inner:
        property_type: quantity
        mandatory: False
        default: -1 K
        help: > 
            initial temperature of the inner boundary black body. If set to -1 K 
            will result in automatic calculation of boundary
        
        
    initial_t_rad:
        property_type: quantity
        mandatory: False
        default: -1 K
        help: >
          initial radiative temperature in all cells. If set to -1 K will result
          in automtatic calculation of the initial temperatures
        
    disable_electron_scattering:
        property_type: bool
        mandatory: False
        default: False
        help: >
            disable electron scattering process in montecarlo part - non-physical only
            for tests
            
    ionization:
        property_type: string
        mandatory: True
        default: None
        allowed_value: nebular lte
        help: ionization treatment mode
          
      
    excitation:
        property_type: string
        mandatory: True
        default: None
        allowed_value: lte dilute-lte
        help: excitation treatment mode
        
    radiative_rates_type:
        property_type: string
        mandatory: True
        default: None
        allowed_value: dilute-blackbody detailed blackbody
        help: radiative rates treatment mode
        
    line_interaction_type:
        property_type: string
        mandatory: True
        default: None
        allowed_value: scatter downbranch macroatom 
        help: line interaction mode
        
    w_epsilon:
        property_type: float
        mandatory: False
        default:  1e-10
        help: w to use when j_blues get numerically 0. - avoids numerical complications

    delta_treatment:
        property_type: float
        mandatory: False
        default: None
        help: >
            In the saha calculation set delta equals to the number given in
            this configuration item. if set to None (default), normal delta
            treatment (as described in Mazzali & Lucy 1993) will be applied

    nlte:
        species:
            property_type: list
            mandatory: False
            default: []
            help: >
                Species that are requested to be NLTE treated in the format 
                ['Si 2', 'Ca 1', etc.]

        coronal_approximation:
            property_type: bool
            default: False
            mandatory: False
            help: set all jblues=0.0
    
        classical_nebular:
            property_type: bool
            default: False
            mandatory: False
            help: sets all beta_sobolevs to 1

    helium_treatment:
        property_type: string
        mandatory: False
        default: none
        allowed_value: none recomb-nlte numerical-nlte
        help: none to treat He as the other elements. recomb-nlte to treat with NLTE approximation.

    heating_rate_data_file:
        property_type: string
        mandatory: False
        default: none
        help: Path to file containing heating rate/light curve data.

model:
    structure:
        property_type : container-property
        type:
            property_type: container-declaration
            containers: ['file', 'specific']
            _file: ['filename','filetype']
            +file: ['v_inner_boundary','v_outer_boundary']
            _specific: ['velocity', 'density']


        filename:
            property_type: string
            default: None
            mandatory: True
            help: file name (with path) to structure model

        filetype:
            property_type: string
            default: None
            allowed_value: ['simple_ascii', 'artis']
            mandatory: True
            help: file type

        v_inner_boundary:
            property_type: quantity
            default: 0 km/s
            mandatory: False
            help: location of the inner boundary chosen from the model

        v_outer_boundary:
            property_type: quantity
            default: inf km/s
            mandatory: False
            help: location of the inner boundary chosen from the model


        velocity:
            property_type: quantity_range_sampled
            default: None
            mandatory: True
            help: description of the boundaries of the shells

        density:
            property_type: container-property
            type:
                property_type: container-declaration
                containers: ['branch85_w7','exponential','power_law', 'uniform']
                _uniform: ['value']
                _branch85_w7: []
                +branch85_w7: ['w7_time_0', 'w7_rho_0', 'w7_v_0']
                _power_law: ['time_0', 'rho_0', 'v_0', 'exponent']
                _exponential: ['time_0', 'rho_0', 'v_0']

            w7_time_0:
                property_type: quantity
                default: 0.000231481 day
                mandatory: False
                help: This needs no change - DO NOT TOUCH

            w7_rho_0:
                property_type: quantity
                default: 3e29 g/cm^3
                mandatory: False
                help: This needs no change - DO NOT TOUCH

            w7_v_0:
                property_type: quantity
                default: 1 km/s
                mandatory: False
                help: This needs no change - DO NOT TOUCH

            time_0:
                property_type: quantity
                default: None
                mandatory: True
                help: Time at which the pure model densities are right

            rho_0:
                property_type: quantity
                default: None
                mandatory: True
                help: density at time_0

            v_0:
                property_type: quantity
                default: None
                mandatory: True
                help: at what velocity the density rho_0 applies

            exponent:
                property_type: float
                default: None
                mandatory: True
                help: exponent for exponential density profile

            value:
                property_type: quantity
                default: None
                mandatory: True
                help: value for uniform density

    abundances:
        property_type: container-property
        type:
            property_type: container-declaration
            containers: ['file','uniform']
            _uniform: []
            _file: ['filetype','filename']
            
        filename:
            property_type: string
            default: None
            mandatory: True
            help: filename
            
        filetype:
            property_type: string
            default: None
            mandatory: False
            help: type of abundance file to read in
            
                
                
  
            


montecarlo:
    nthreads:
        property_type: int
        default: 1
        mandatory: False
        help: The number of OpenMP threads.

    seed:
        property_type: int
        default: 23111963
        mandatory: False
        help: Seed for the random number generator

    no_of_packets:
        property_type: int
        default: None
        mandatory: True
        help: Seed for the random number generator

    iterations:
        property_type: int
        default: None
        mandatory: True
        help: Number of maximum iterations

    black_body_sampling:
        property_type: quantity_range_sampled
        default: [50 angstrom, 200000 angstrom, 1000000]
        mandatory: False
        help: Sampling of the black-body for energy packet creation (giving maximum and minimum packet frequency)

    last_no_of_packets:
        property_type: int
        default: -1
        mandatory: False
        help: >
            This can set the number of packets for the last run.
            If set negative it will remain the same as all other runs.

    no_of_virtual_packets:
        property_type: int
        default: 0
        mandatory: False
        help: Setting the number of virtual packets for the last iteration.

    virtual_spectrum_range:
        property_type: quantity_range_sampled
        default: [50 angstrom, 250000 angstrom, 1000000]
        mandatory: False
        help: Limits of virtual packet spectrum (giving maximum and minimum packet frequency)


    enable_reflective_inner_boundary:
        property_type: bool
        default: False
        mandatory: False
        help: >
            experimental feature to enable a reflective boundary.
    
    inner_boundary_albedo:
        property_type: float
        default: 0.0
        mandatory: False
        help: albedo of the reflective boundary

    convergence_strategy:
        property_type : container-property
        type:
            property_type: container-declaration
            containers: ['damped', 'specific']
            _damped: []
            +damped: ['damping_constant', 't_inner', 't_rad', 'w',
            'lock_t_inner_cycles', 't_inner_update_exponent',]
            _specific: ['threshold', 'fraction', 'hold_iterations']
            +specific: ['t_inner', 't_rad', 'w', 'lock_t_inner_cycles',
            'damping_constant', 't_inner_update_exponent']
        
        t_inner_update_exponent:
            property_type: float
            default: -0.5
            mandatory: False
            help: L=4*pi*r**2*T^y
        
        lock_t_inner_cycles:
            property_type: int
            mandatory: False
            default: 1
            help: >
                The number of cycles to lock the update of the inner boundary temperature.
                This process helps with convergence. The default is to switch it off (1 cycle)
        hold_iterations:
            property_type: int
            default: 3
            mandatory: True
            help: >
                the number of iterations that the convergence criteria need to be
                fulfilled before TARDIS accepts the simulation as converged

        fraction: 
            property_type: float
            default: 0.8
            mandatory: True
            help: >
                the fraction of shells that have to converge to the given 
                convergence threshold. For example, 0.8 means that 80% of shells
                have to converge to the threshold that convergence is established
        
        damping_constant:
            property_type: float
            mandatory: False
            default: 0.5
            help: damping constant

        threshold: 
            property_type: float
            mandatory: True
            help: >
                specifies the threshold that is taken as convergence 
                (i.e. 0.05 means that the value does not change more than 5%)


        t_inner:
            damping_constant:
                property_type: float
                mandatory: False
                default: 0.5
                help: damping constant
    
            threshold: 
                property_type: float
                mandatory: False
                help: >
                    specifies the threshold that is taken as convergence 
                    (i.e. 0.05 means that the value does not change more than 5%)

        t_rad:
            damping_constant:
                property_type: float
                mandatory: False
                default: 0.5
                help: damping constant
    
            threshold: 
                property_type: float
                mandatory: True
                help: >
                    specifies the threshold that is taken as convergence 
                    (i.e. 0.05 means that the value does not change more than 5%)


        w:
            damping_constant:
                property_type: float
                mandatory: False
                default: 0.5
                help: damping constant
    
            threshold: 
                property_type: float
                mandatory: True
                help: >
                    specifies the threshold that is taken as convergence 
                    (i.e. 0.05 means that the value does not change more than 5%)

spectrum:
    property_type: quantity_range_sampled
    default: None
    mandatory: True
    help: Final spectrum sampling

Configuration Validator

The default config validator takes a user configuration and a default configuration and creates a consistent and valid configuration for tardis based on the constraints given in the default configuration. Both input data are normally given as a yaml dictionary with a consistent hierarchical structure i.e. for every item in the user configuration there has to be a declaration in the default configuration at the same hierarchical level. This declaration can be either an unspecific empty level declaration like: - Main_level:

  • Second_level:
    • Third_level:

or a declaration of a configuration item like: - item:

  • property_type: int
  • default: 1
  • mandatory: True
  • help: ‘This is a doc string.’

This contains always the keywords help, default, mandatory, and property_type. The keyword help is a doc-string which describes the corresponding item. Default specifies the default value which is used in case that no value for this item is specified in the corresponding user configuration item. If the keyword mandatory is True, the item has to be specified in the user configuration. The keyword property_type is used to specify the type of the item. At the moment, the config parser knows the following types: Int: The property type int is for integer like config items. Float: The property type float is for float like config items. String: The property type string is for string like config items. Quantity: The property type quantity is for physical quantities with units given as string. The string contains value and unit separated by a whitespace E.g. 2 cm. Range: The property type range specifies a range via start and end. Note: abs(start - end ) > 0 Quantity_range: Like property type range but with quantities as start and stop. The consistency of the units is checked. Additionally to the four standard keywords the types integer, float, and quantity can have the keywords allowed_value and allowed_type. allowed_value specifies the allowed values in a list, whereas allowed_type specifies a range of allowed values like “x>10”.

Container

For more complex configurations with dependencies, you can use the containers which allow branching in the configuration. A container is declared in the default configuration file by setting the property_type to container property and specifying the properties of the container with keyword type. The property_type of this section is container-declaration which allows you to specify the possible container items with the keyword container. For every specified container item, the code expects the declaration of all sub items. The keywords for this are “_“ + “name of the container item”. If the type declaration for this container is finished you can specify all container items like normal items. Here is an example for a container configuration with two branches

..source: yaml - container_example:

  • property_type: container-property

  • type:
    • property_type: container-declaration
    • containers: [‘one’, ‘two’, ‘three’]
    • _one: [‘one_one’, ‘one_two’]
    • _two: [‘two_one’]
  • one_one:
    • property_type: string
    • default: ‘This is a container item’
    • mandatory: False
    • help: This is a container item from the container one.
  • one_two:
    • sub_one_two_one:
      • property_type: string
      • default: ‘This is a container item’
      • mandatory: False
      • help: This is a container item from the container one.
    • sub_one_two_two:
      • property_type: string
      • default: ‘This is a container item’
      • mandatory: False
      • help: This is a container item from the container one.
  • two_one:
    • quantity_range:
      • property_type: quantity_range
      • default: [1 m,10 cm] #[Start,End]
      • mandatory: False
      • help: Like property type range but with quantities as start and stop. The consistency of the units is checked.

How to use

To use the default parser create a new config object form the class ConfigurationValidator by either from a dictionaries or from yaml files:

My_config = ConfigurationValidator(default configuration dictionary, user configuration dictionary)

or - My_config = ConfigurationValidator.from_yaml(default configuration file, user configuration file) To access the configuration for tardis use the method get_config

Example Models

Here’s an overview of some of the different modes of operation and models for TARDIS.

Model with exponential density profile and uniform abundances

TARDIS can be used to compute a synthetic spectrum for a model with a user-specified density and chosen set of abundances. One simple (but flexible) class of model is when the density is parameterised as an exponential function of velocity, as described below.

Exponential density profile

In this mode, the density profile (function of velocity and time since explosion) is assumed to follow a functional form:

\[\rho (v, t_{exp}) = \rho_0 (t_{0} / t_{exp})^{3} \exp( -v / v_0)\]

defined by reference density, velocity and time parameters. These parameters are set in the input yaml file, specifically in the “structure” subsection of the “model” section, under the “density” heading (see example below).

Uniform abundances

For a model with an exponential density profile, a set of uniform abundances can be supplied directly in the input (yaml) file. Elemental abundances are set in the “abundances” subsection of the “model” section, following the “type: uniform” specifier (see example input file below). They are specified as mass fractions. E.g.

Si: 0.6
S: 0.4

will set the mass fraction of silicon (Z=14) to 0.6 and sulphur (Z=16) to 0.4.

Note

The mass fractions must sum to one. If mass fractions are supplied that do not sum to one, TARDIS will renormalise all the supplied abundances and print a “WARNING” message.

TARDIS input file

Here is an example of an input file that sets up an exponential density profile with a uniform set of abundances:

tardis_config_version: v1.0

supernova:
    luminosity_requested: 9.44 log_lsun
    time_explosion: 13 day

atom_data: kurucz_atom_pure.h5

model:
    structure:
        type: specific


        velocity:
            start : 1.1e4 km/s
            stop : 20000 km/s
            num: 20
            
            
        density:
            type : exponential
            time_0: 2. day
            rho_0: 6.e-10 g/cm^3
            v_0: 3000.  km/s
            exponent: 1.0
            

    abundances:
        type: uniform
        O: 0.19
        Mg: 0.03
        Si: 0.52
        S: 0.19
        Ar: 0.04
        Ca: 0.03

plasma:
    ionization: nebular
    excitation: dilute-lte
    radiative_rates_type: dilute-blackbody
    line_interaction_type: scatter

montecarlo:
    seed: 23111963
    no_of_packets : 2.0e+5
    iterations: 30
    last_no_of_packets: 5.0e+5
    no_of_virtual_packets: 5

spectrum:
    start: 500 angstrom
    stop: 20000 angstrom
    num: 10000

Model with power law density profile and uniform abundances

TARDIS can be used to compute a synthetic spectrum for a model with a user-specified density and chosen set of abundances. One simple (but flexible) class of model is when the density is parameterised as an power law function of velocity, as described below.

Power-law density profile

In this mode, the density profile (function of velocity and time since explosion) is assumed to follow a functional form:

\[\rho (v, t_{exp}) = \rho_0 (t_{0} / t_{exp})^{3} ( v / v_{0})^{\rm exponent}\]

This form is defined by reference density, velocity and time parameters, and the “exponent”, each of which is set in the input file (“structure” subsection of the “model” section, under the “density” heading; see example below).

Uniform abundances

For a model with an power law density profile, a set of uniform abundances can be supplied directly in the input (yaml) file. Elemental abundances are set in the “abundances” subsection of the “model” section, following the “type: uniform” specifier (see example input file below). They are specified as mass fractions. E.g.

Si: 0.6
S: 0.4

will set the mass fraction of silicon (Z=14) to 0.6 and sulphur (Z=16) to 0.4.

Note

The mass fractions must sum to one. If mass fractions are supplied that do not sum to one, TARDIS will renormalise all the supplied abundances and print a “WARNING” message.

TARDIS input file

Here is an example of an input file that sets up a power law density profile with a uniform set of abundances:

tardis_config_version: v1.0

supernova:
    luminosity_requested: 9.44 log_lsun
    time_explosion: 13 day

atom_data: kurucz_atom_pure.h5

model:
    structure:
        type: specific


        velocity:
            start : 1.1e4 km/s
            stop : 20000 km/s
            num: 20
            
            
        density:
            type : power_law
            time_0: 2 day
            rho_0: 1e-11 g/cm^3
            v_0: 11000 km/s
            exponent: -5


    abundances:
        type: uniform
        O: 0.19
        Mg: 0.03
        Si: 0.52
        S: 0.19
        Ar: 0.04
        Ca: 0.03

plasma:
    ionization: nebular
    excitation: dilute-lte
    radiative_rates_type: dilute-blackbody
    line_interaction_type: scatter

montecarlo:
    seed: 23111963
    no_of_packets : 2.0e+5
    iterations: 30
    last_no_of_packets: 5.0e+5
    no_of_virtual_packets: 5

spectrum:
    start: 500 angstrom
    stop: 20000 angstrom
    num: 10000

Model with custom density profile and uniform abundances

TARDIS can be used to compute a synthetic spectrum for a model with a user-specified density and chosen set of abundances - this makes it possible to experiment with 1D density profiles and simple specifications of the composition

Arbitrary density profile

The density profile is supplied in the form of a simple ascii file that should look something like this:

1 day
# index velocity (km/s) density (g/cm^3)
0       9000.0000   5.4869692e-10
1       10500.000   2.1759646e-10
2       12000.000   9.7656229e-11
3       13500.000   4.8170911e-11
4       15000.000   2.5600000e-11
5       16500.000   1.4450533e-11
6       18000.000   8.5733893e-12
7       19500.000   5.3037103e-12
8       21000.000   3.3999447e-12
9       22500.000   2.2474623e-12

In this file:

  • the first line gives the reference time (see below)
  • (the second line in our example is a comment)
  • the remaining lines (ten in our example) give an indexed table of points that specify mass density (g / cm^3) as a function of velocity (km /s).

TARDIS will use this table of density versus velocity to specify the density distribution in the ejecta. For the calculation, TARDIS will use the reference time given in the file to scale the mass densities to whatever epoch is requested by assuming homologous expansion:

\[\rho (t_{exp}) = \rho (t_{ref}) (t_{ref} / t_{exp})^{3}\]

The values in the example here define a density profile that is dropping off with

\[\rho \propto v^{-5}\]

Note

The grid of points specified in the input file is interpreted by TARDIS as defining a grid in which the tabulated velocities are taken as the outer boundaries of grid cells and the density is assumed to be uniform with each cell.

Warning

The example given here is to show the format only. It is not a realistic model. In any real calculation better resolution (i.e. more grid points) should be used.

Uniform abundances

For a model with density profile supplied via a file (see above), uniform abundances can be supplied directly in the input (yaml) file. Elemental abundances are set in the “abundances” subsection of the “model” section, following the “type: uniform” specifier (see example input file below). They are specified as mass fractions. E.g.

Si: 0.6
S: 0.4

will set the mass fraction of silicon (Z=14) to 0.6 and sulphur (Z=16) to 0.4.

Note

The mass fractions must sum to one. If mass fractions are supplied that do not sum to one, TARDIS will renormalise all the supplied abundances and print a “WARNING” message.

TARDIS input file

If you create a correctly formatted density profile file (called “density.dat”), here is an example of how it can be combined with a uniform set of abundances:

#New configuration for TARDIS based on YAML
#IMPORTANT any pure floats need to have a +/- after the e e.g. 2e+5
#Hopefully pyyaml will fix this soon.
---
#Currently only simple1d is allowed
tardis_config_version: v1.0
supernova:
    luminosity_requested: 9.44 log_lsun
    time_explosion: 13 day

atom_data: kurucz_atom_pure_simple.h5

model:
            
    structure:
        type: file
        filename: density.dat
        filetype: simple_ascii
        v_inner_boundary: 11000 km/s
        v_outer_boundary: 20000 km/s

    abundances:
        type: uniform
        O: 0.19
        Mg: 0.03
        Si: 0.52
        S: 0.19
        Ar: 0.04
        Ca: 0.03

plasma:
    disable_electron_scattering: no
    ionization: lte
    excitation: lte
    radiative_rates_type: dilute-blackbody
    line_interaction_type: macroatom

montecarlo:
    seed: 23111963
    no_of_packets : 1.0e+5
    iterations: 20
    
    black_body_sampling:
        start: 1 angstrom
        stop: 1000000 angstrom
        num: 1.e+6
    last_no_of_packets: 1.e+5
    no_of_virtual_packets: 5

    convergence_strategy:
        type: specific
        damping_constant: 1.0
        threshold: 0.05
        fraction: 0.8
        hold_iterations: 3
        t_inner:
            damping_constant: 1.0
    
spectrum:
    start : 500 angstrom
    stop : 20000 angstrom
    num: 10000
    



    
    



    

Note

The specifications for the velocities of the inner and outer boundary values can be neglected (in which case TARDIS will default to using the full velocity range specified in the density.txt file). Values for the boundary velocities that lie outside the range covered by density.txt will not be accepted.

Model with custom density and abundance profile

TARDIS can be used to compute a synthetic spectrum for a model with user-specified density and abundance profiles - this makes it possible to experiment with 1D profiles based on explosion models or with any empirical description of a model with stratified abundances or density.

Arbitrary density profile

The density profile is supplied in the form of a simple ascii file that should look something like this:

1 day
# index velocity (km/s) density (g/cm^3)
0       9000.0000   5.4869692e-10
1       10500.000   2.1759646e-10
2       12000.000   9.7656229e-11
3       13500.000   4.8170911e-11
4       15000.000   2.5600000e-11
5       16500.000   1.4450533e-11
6       18000.000   8.5733893e-12
7       19500.000   5.3037103e-12
8       21000.000   3.3999447e-12
9       22500.000   2.2474623e-12

In this file:

  • the first line gives the reference time (see below)
  • (the second line in our example is a comment)
  • the remaining lines (ten in our example) give an indexed table of points that specify mass density (g / cm^3) as a function of velocity (km /s).

TARDIS will use this table of density versus velocity to specify the density distribution in the ejecta. For the calculation, TARDIS will use the reference time given in the file to scale the mass densities to whatever epoch is requested by assuming homologous expansion:

\[\rho (t_{exp}) = \rho (t_{ref}) (t_{ref} / t_{exp})^{3}\]

The values in the example here define a density profile that is dropping off with

\[\rho \propto v^{-5}\]

Note

The grid of points specified in the input file is interpreted by TARDIS as defining a grid in which the tabulated velocities are taken as the outer boundaries of grid cells and the density is assumed to be uniform with each cell.

Warning

The example given here is to show the format only. It is not a realistic model. In any real calculation better resolution (i.e. more grid points) should be used.

Stratified abundance profile

For a model with density profile supplied via a file (see above), uniform abundances can be supplied as normal. Alternatively, a set of stratified elemental abundances can also be supplied. As with the density, the abundances are specified via an ascii file. An ascii file that could work with the example density file given above should be formatted like this:

# index Z=1 - Z=30
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0 0.1 0 0.2 0 0.2 0 0 0 0 0 0.2 0.1 0.1 0 0
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0 0.1 0 0.2 0 0.2 0 0 0 0 0 0.2 0.1 0.1 0 0
2 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0 0.1 0 0.2 0 0.2 0 0 0 0 0 0.2 0.1 0.1 0 0
3 0 0 0 0 0 0 0 0.2 0 0 0 0.1 0 0.2 0 0.2 0 0.2 0 0.1 0 0 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0.2 0 0 0 0.1 0 0.2 0 0.2 0 0.2 0 0.1 0 0 0 0 0 0 0 0 0 0
5 0 0 0 0 0 0 0 0.2 0 0 0 0.1 0 0.2 0 0.2 0 0.2 0 0.1 0 0 0 0 0 0 0 0 0 0
6 0 0 0 0 0 0 0 0.2 0 0 0 0.1 0 0.2 0 0.2 0 0.2 0 0.1 0 0 0 0 0 0 0 0 0 0
7 0 0 0 0 0 0 0 0.2 0 0 0 0.1 0 0.2 0 0.2 0 0.2 0 0.1 0 0 0 0 0 0 0 0 0 0
8 0 0 0 0 0 0.5 0 0.5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
9 0 0 0 0 0 0.5 0 0.5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

In this file:

  • there should be the same number of rows as there were indexed points in the density profile file
  • each row contains 31 numbers, the first of which is the index (i.e. matching the zone to the density profile file)
  • the remaining 30 entries in each row give the set of elemental abundances for atomic number Z=1 to Z=30 (in order)

The abundances are specified as mass fractions (i.e. the sum of columns 1 to 30 in each row should be 1.0). TARDIS does not currently include any elements heavier that Z=30. The mass fractions specified will be adopted directly in the TARDIS calculations - so if your model is e.g. based on an explosion simulation you may need to calculate the state of any radioactive decay chains at the correct epoch.

The example file shown here has three simple layers:

  • an innermost region (indices 0 to 2) that is composed of Si (Z=14), S (Z=16), Ar (Z=18), Ca (Z=20), Fe (Z=26), Co (Z=27) and Ni (Z=28)
  • a middle region (indices 3 to 7) that is composed of O (Z=8), Mg (Z=12), Si, S, Ar and Ca
  • an outer region (indices 8 and 9) that is composed of C (Z=6) and O.

Warning

The example given here is to show the format only. It is not a realistic model. In any real calculation better resolution (i.e. more grid points) should be used.

Warning

The calculation can be no better / more complete than the atomic data set. For further information on the atomic database - including details of how to develop your own dataset to suit your needs, please contact us.

TARDIS input file

If you create a correctly formatted density profile file (called “density.dat”) and abundance profile file (called “abund.dat”), you can use them in TARDIS by putting the following lines in the model section of the yaml file (and remove all other lines from these sections):

#New configuration for TARDIS based on YAML
#IMPORTANT any pure floats need to have a +/- after the e e.g. 2e+5
#Hopefully pyyaml will fix this soon.
---
#Currently only simple1d is allowed
tardis_config_version: v1.0
supernova:
    luminosity_requested: 9.44 log_lsun
    time_explosion: 13 day

atom_data: kurucz_atom_pure_simple.h5

model:
            
    structure:
        type: file
        filename: density.dat
        filetype: simple_ascii
        v_inner_boundary: 11000 km/s
        v_outer_boundary: 20000 km/s

    abundances:
        type: file
        filename: abund.dat
        filetype: simple_ascii

plasma:
    disable_electron_scattering: no
    ionization: lte
    excitation: lte
    radiative_rates_type: dilute-blackbody
    line_interaction_type: macroatom

montecarlo:
    seed: 23111963
    no_of_packets : 1.0e+5
    iterations: 20
    
    black_body_sampling:
        start: 1 angstrom
        stop: 1000000 angstrom
        num: 1.e+6
    last_no_of_packets: 1.e+5
    no_of_virtual_packets: 5

    convergence_strategy:
        type: specific
        damping_constant: 1.0
        threshold: 0.05
        fraction: 0.8
        hold_iterations: 3
        t_inner:
            damping_constant: 1.0
    
spectrum:
    start : 500 angstrom
    stop : 20000 angstrom
    num: 10000
    



    
    



    

Note

The specifications for the velocities of the inner and outer boundary values can be neglected (in which case TARDIS will default to using the full velocity range specified in the density.txt file). Values for the boundary velocities that lie outside the range covered by density.txt will not be accepted.

Note

Increasing the number of virtual packets will improve the signal-to-noise of TARDIS spectra. You may wish to consider using a filter (e.g. Savitzky–Golay) to suppress the Monte Carlo noise for some applications.

Warning

The usefulness of any TARDIS calculations depends on the quality of the input atomic data. For further information on the atomic data - including details of how to develop your own dataset to suit your needs - please contact us.

Testing TARDIS

Testing a code like TARDIS is an important step to making sure that the output is trustworthy. The TARDIS team has opted to use the py.test testing framework for use with TARDIS (and some astropy extensions of it). To run the tests download the desired version of TARDIS and run it with:

python setup.py test

This will run the currently implemented tests (which are not as many as there should be)

To quickly test TARDIS with any atomic dataset (it will only see if it in general runs):

python setup.py test --args="--atomic-dataset=<path_to_dataset> -s"

Atomic Data for TARDIS

For a detailed description of the datasets go to Atomic Data Format Description

file name uuid1 data sources macroatom zeta synpp references database version
kurucz_cd23_chianti_H_He.zip 5ca3035ca8b311e3bb684437e69d75d7

kurucz:

Li I-II, Be I-IV, C I-IV, N I-VI O I-VI, F I-VI, Ne I-VI, Na I-VI Mg I-VI, Al I-VI, Si I-VI P I-VI, S I-VI, Cl I-V, Ar I-V K I-V, Ca I-IX, Sc I-IX, Ti I-IX V I-IX, Cr I-IX, Mn I-IX, Fe I-IX Co I-IX, Ni I-IX, Cu I-II Zn I-III

chianti:

H I,He I-II

tardis_artificial_missing_ion:

Li III, B V, C V-VI, N VII, O VII-VIII F VII-IX, Ne VII-X, Na VII-XI Mg VII-XII, Al VII-XIII Si VII-XIV, P VII-XV, S VII-XVI Cl VI-XVII, Ar VI-XVIII K VI-XIX, Ca X-XX, Sc X-XXI Ti X-XXII, V X-XXIII, Cr X-XXIV Mn X-XXV, Fe X-XXVI, Co X-XXVII Ni X-XXVIII, Cu III-XXIX Zn IV-XXX

True True True v0.9

Developer Workflow

Many of the Development workflow is taken from Astropy and credit belongs to the Astropy team for designing it.

The first step is to setup up a python environment. Python Environment for TARDIS explains how to do that.

Workflow for Developers describes how to interact with git and github in the development
of TARDIS.

General Workflow to add a new feature

In TARDIS we aim to stick to a test driven development. This uses the testing framework extensively starting with a test that shows this feature lacking via the implementation of the feature until the merging of the code to the main repository.

In most cases we try to break down big features into small, quantifiable goals which are then acted upon.

  • Document feature to be added in an issue and maybe ask the mailing list if this feature exists
  • Write a test that demonstrates what feature will be added.
  • Run the test to verify that it fails in the way you think it should.
  • If it fails in an unexpected way, your test may be wrong. This is a great time to ask the group for guidance
  • If it passes, you are done! You just added test coverage to an already existing feature, and that is great! (unlikely)
  • Add the feature (also known as “a simple matter of programming”).
  • Run the test to verify that it passes.
  • Write documentation about your feature.
  • close issue/partial PR and add to changelog.

What is TARDIS actually doing (Physics section)?

This section describes some of the Physics going on in TARDIS and will be gradually expanded

Accessing Physical Quantities

In order to compute the synthetic spectrum, Tardis must either be told or must calculate many physical properties of the model. To understand and test the code it can be important to look at these values. One easy way to do this is to run Tardis in an interactive mode and then inspect the model properties.

Runing in interactive Python session

With iPython installed launch a session using

ipython --pylab

and then run your calculation, which is based on your “my_config.yml” file

from tardis import run_tardis


model = run_tardis('myconfig.yml')

If all goes well, the simulation should run as usual. Afterwards, the information from the simulation will all exist in “radial1d” and can be examined. Some examples for useful/interesting quantities are given below (but much more information is available: contact us via tardis-sn-users if you need further help).

Examples of finding physical quantities

For example, two of our important quantities are the parameters of the radiation field model, \(T_{\rm rad}\) and \(W\). These exist as Astropy Quantities. Thus

model.t_rads.cgs

will give you a list of the \(T_{\rm rad}\)-values for the model zones in cgs units. To obtain an array of the values (without units) use

model.t_rads.cgs.value

Similarly, the \(W\)-values can be accessed using

model.ws.cgs

Several important quantities that were setup when the model was defined by the configuration file are located in the “tardis_config” section. For example, the inner and outer velocity boundaries of the zones in the model

model.tardis_config.structure.v_inner.cgs
model.tardis_config.structure.v_outer.cgs

and the average density in the zones

model.tardis_config.structure.mean_densities.cgs

Many other interesting quantities are stored in the “plasma_array”. For example the calculated ion populations or level populations:

model.plasma_array.ion_populations
model.plasma_array.level_populations

These are stored as Pandas DataFrames. An index can be supplied to obtain the population in a particular zone. E.g., for the ion populations of the innermost zone (index = 0)

model.plasma_array.ion_populations[0]

Ion populations for a particular ionization stage of a particular element can be accessed by specifying an appropriate tuple \((Z,C)\), which identifies the element (via atomic number \(Z\) ) and the charge (via the ion charge \(C\) ). Thus,

model.plasma_array.ion_populations.ix[(14,1)]

will identify the ion popuations for Si II (\(Z=14, C=1\)) in all the zones. The above examples can be combined to obtain e.g. the Si II population in the innermost zone

model.plasma_array.ion_populations[0].ix[(14,1)]

The level populations are stored (and can be accessed) in a similar way - a third label can be used to pick out a particular atomic level. E.g., to pull out the population of the ground state (index 0) of Si II

model.plasma_array.level_populations.ix[(14,1,0)]

Note

If you prefer to work in SI units, all the astropy Quantities may instead by accessed with “xxx.si”.

Note

Information that is not stored as astropy Quantities (e.g. the ion an level populations used in the example above) are usually stored in cgs units (i.e. \({\rm cm}^{-3}\) for the populations).

Radiative Monte Carlo

We assume the supernova is in homologous expansion and we assume that there is photon injection from an inner boundary and we assume an outer boundary where these photons can leave the simulation again.

Montecarlo packets

Montecarlo packets have a frequency, angle (\(\mu=\cos{\theta}\) ) and an energy: \(P(\nu, \mu, \epsilon)\). A large number \(n\) are generated at the start of each Montecarlo run in TARDIS by drawing \(\nu\) from a black-body distribution distribution with a \(\mu\) being drawn from a \(\sqrt[2]{z}\) distribution and an energy that is \(1/n\).

These packets are then launched into the simulation and there are two possible outcomes for each packet.

  • packet leaves the simulation through the outer bound and count towards the

    spectrum

  • packet leaves the simulation through the inner boundary and are lost

    (reabsorbed)

The packets can gain and loose energy throughout the simulation. If these packets scatter (through the various mechanisms), we transform into the comoving frame at that position \(v(r) = r / t_\textrm{explosion}\), where \(t_explosion\) is the time since explosion with the doppler factor \(d_\textrm{rest \rightarrow comoving}\) then change direction from \(\mu_\textrm{in}\) to \(mu_\textrm{out}\) and transform back and change the energy accordingly:

\[\begin{split}d_\textrm{rest \rightarrow comoving} = 1 - \mu_\textrm{in} v(r) / c \\ d_\textrm{rest \rightarrow comoving} = 1 - \mu_\textrm{out} v(r) / c \\ E_\textrm{after scatter} = E_\textrm{before scatter} \times \frac{1 - \mu_\textrm{in} v(r) / c}{1 - \mu_\textrm{out} v(r) / c}\end{split}\]

This way the montecarlo packets can gain or loose energy in the simulation:

(Source code, png, hires.png, pdf)

_images/plot_mu_in_out_packet.png

The spectrum is then generated as a weighted histogram. For each bin with edges \(\nu_{n}, \nu_{n+1}\) we get all the packets that left the simulation through the outer boundary and add up their remaining energies.

Montecarlo Geometry

Before any packet action is performed we calculate four different distances
( \(d_\textrm{inner}, d_\textrm{outer}, d_\textrm{line}, d_{\textrm{e}^{-}}\) )

The calculations for the distance to the outer boundary:

_images/d_outer.png

The calculations for the distance to the inner boundary:

_images/d_inner.png

Radiationfield estimators

During the monte-carlo run we collect two estimators for the radiation field:

\[\begin{split}J_\textrm{estimator} &= \sum{\epsilon l}\\ \bar{\nu}_\textrm{estimator} &= \sum{\epsilon \nu l},\end{split}\]

where \(\epsilon, \nu\) are comoving energy and comoving frequency of a packet respectively.

To calculate the temperature and dilution factor we first calculate the mean intensity in each cell ( \(J = \frac{1}{4\pi\, \Delta t\, V} J_\textrm{estimator}\) )., [Lucy03].

The weighted mean frequency is used to obtain the radiation temperature. Specifically, the radiation temperature is chosen as the temperature of a black body that has the same weighted mean frequency as has been computed in the simulation. Accordingly,

\[\frac{h \bar{\nu}}{k_{B} T_{R}} = \frac{h}{k_{B} T_{R}} \frac{\bar{\nu}_\textrm{estimator}}{J_\textrm{estimator}} = 24 \zeta(5) \frac{15}{\pi^4},\]

where the evaluation comes from the mean value of

\[\bar{x} = \frac{ \int_0^{\infty} x^4 / (\exp{x} - 1)dx}{\int_0^{\infty} x^3 / (\exp{x} - 1)dx} = 24 \zeta(5) \frac{15}{\pi^4} = 3.8322\dots\]

and so

\[\begin{split}T_{R} &= \frac{1}{\bar{x}} \frac{h}{k_{B}} \frac{\bar{\nu}_\textrm{estimator}}{J_\textrm{estimator}} \\ &= 0.260945 \frac{h}{k_{B}} \frac{\bar{\nu}_\textrm{estimator}}{J_\textrm{estimator}}.\end{split}\]

With the radiation temperature known, we can then obtain our estimate for for the dilution factor. Our radiation field model in the nebular approximation is

\[J = W B(T_{R}) = W \frac{\sigma_{SB}}{\pi} T_{R}^4,\]

i.e. a dilute blackbody. Therefore we use our value of the mean intensity derrived from the estimator (above) to obtain the dilution factor

\[W = \frac{\pi J}{\sigma_{SB} T_{R}^4} = \frac{1}{4\sigma_{SB} T_{R}^4\, \Delta t\, V} J_\textrm{estimator}.\]

There endeth the lesson.

Algorithm Flowchart

digraph g{
  a -> b -> c
  c -> d [label="d_inner or \nd_outer"]
  c -> e [label="d_line"]
  d -> f [label="yes"]
  d -> g [label="no"]
  g -> a
  e -> a [label="no"]
  e -> h [label="yes"]
  h -> a
  a [label="We have a packet.",shape=box,fillcolor="white",style="filled,rounded"];
  b [label="Calculate\nd_line, d_electron,\nd_inner and d_outer.",shape=box,fillcolor="white",style="filled,rounded"];
  c [label="Which distance\nis smallest?", shape="diamond", fillcolor="white", style="filled"]
  d [label="Are we leaving\nsimulation area?", shape="diamond", fillcolor="white", style="filled"]
  e [label="Does the\npacket interact?", shape="diamond", fillcolor="white", style="filled"]
  f [label="Packet is re-absorbed\nor emitted.\nThis ends the loop.", shape="box", fillcolor="white", style="filled,rounded"]
  g [label="Update line\nprobabilities.", shape="box", fillcolor="white", style="filled,rounded"]
  h [label="New random direction,\nupdated energy,\nmoving packet to current position,\nupdating event random number.", shape="box", fillcolor="white", style="filled,rounded"]
}

Overview of plasma calculations

LTE Plasma

The LTEPlasma plasma class is the child of BasePlasma but is the first class that actually calculates plasma conditions. After running exactley through the same steps as BasePlasma, LTEPlasma will start calculating the partition functions.

\[Z_{i, j} = \sum_{k=0}^{max (k)} g_k \times e^{-E_k / (k_\textrm{b} T)}\]

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

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. First \(g_e=\left(\frac{2 \pi m_e k_\textrm{B}T_\textrm{rad}}{h^2}\right)^{3/2}\) is calculated (in LTEPlasma.update_t_rad), followed by calculating the ion fractions (LTEPlasma.calculate_saha).

\[\begin{split}\frac{N_{i, j+1}\times N_e}{N_{i, j}} &= \Phi_{i, (j+1)/j} \\ \Phi_{i, (j+1)/j} &= g_e \times \frac{Z_{i, j+1}}{Z_{i, j}} e^{-\chi_{j\rightarrow j+1}/k_\textrm{B}T}\\\end{split}\]

In the second step (LTEPlasma.calculate_ionization_balance), we calculate in an iterative process the electron density and the number density for each ion species.

\[\begin{split}N(X) &= N_1 + N_2 + N_3 + \dots\\ N(X) &= N_1 + \frac{N_2}{N_1} N_1 + \frac{N_3}{N_2}\frac{N_2}{N_1} N_1 + \frac{N_4}{N_3}\frac{N_3}{N_2}\frac{N_2}{N_1} N_1 + \dots\\ N(X) &= N_1 (1 + \frac{N_2}{N_1} + \frac{N_3}{N_2}\frac{N_2}{N_1} + \frac{N_4}{N_3}\frac{N_3}{N_2}\frac{N_2}{N_1} + \dots)\\ N(X) &= N_1 \underbrace{(1 + \frac{\Phi_{i, 2/1}}{N_e} + \frac{\Phi_{i, 2/2}}{N_e}\frac{\Phi_{i, 2/1}}{N_e} + \frac{\Phi_{i, 4/3}}{N_e}\frac{\Phi_{i, 3/2}}{N_e}\frac{\Phi_{i, 2/1}}{N_e} + \dots)}_{\alpha}\\ N_1 &= \frac{N(X)}{\alpha}\end{split}\]

Initially, we set the electron density (\(N_e\)) to the sum of all atom number densities. After having calculated the ion species number densities we recalculated the electron density by weighting the ion species number densities with their ion number (e.g. neutral ion number densities don’t contribute at all to the electron number density, once ionized contribute with a factor of 1, twice ionized contribute with a factor of two, ....).

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

\[N_{i, j, k} = \frac{g_k}{Z_{i, j}}\times N_{i, j} \times e^{-\beta_\textrm{rad} E_k}\]

This concludes the calculation of the 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 pyplot as plt
from matplotlib import colors
from tardis import atomic, plasma_array, util
import numpy as np
import pandas as pd
from astropy import units as u

#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.join(os.path.dirname(atomic.__file__), 'data', 'atom_data.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
lte_plasma = plasma_array.BasePlasmaArray.from_abundance({'Si':1.0}, 1e-14*u.g/u.cm**3, atom_data, 10*u.day)
lte_plasma.update_radiationfield([10000], [1.0])

#Initializing a dataframe to store the ion populations  and level populations for the different temperatures
ion_number_densities = pd.DataFrame(index=lte_plasma.ion_populations.index)
level_populations = pd.DataFrame(index=lte_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:
    lte_plasma.update_radiationfield([t_rad], ws=[1.0])
    #getting total si number density
    si_number_density = lte_plasma.number_densities.get_value(14, 0)
    #Normalizing the ion populations
    ion_density = lte_plasma.ion_populations / si_number_density
    ion_number_densities[t_rad] = ion_density

    #normalizing the level_populations for Si II
    current_level_population = lte_plasma.level_populations[0].ix[14, 1] / lte_plasma.ion_populations.get_value((14, 1), 0)

    #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()

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)

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 ([MazzaliLucy93]; 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 [MazzaliLucy93]:

\[\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_array, 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)

Macro Atom

The macro atom is described in detail in [Lucy02]. The basic principal is that when an energy packet is absorbed that the macro atom is on a certain level. Three probabilities govern the next step the Pup, Pdown and Pdown emission being the probability for going to a higher level, a lower level and a lower level and emitting a photon while doing this respectively (see Figure 1 in [Lucy02] ).

The macro atom is the most complex idea to implement as a data structure. The setup is done in ~tardisatomic, but we will nonetheless discuss it here (as ~tardisatomic is even less documented than this one).

For each level we look at the line list to see what transitions (upwards or downwards are possible). We create a two arrays, the first is a long one-dimensional array containing the probabilities. Each level contains a set of probabilities, The first part of each set contains the upwards probabilities (internal upward), the second part the downwards probabilities (internal downward), and the last part is the downward and emission probability.

each set is stacked after the other one to make one long one dimensional ~numpy.ndarray.

The second array is for book-keeping it has exactly the length as levels (with an example for the Si II level 15):

Level ID Probability index Nup Ndown Ntotal
14001015 ??? 17 5 17 + 5*2 = 27

We now will calculate the transition probabilites, using the radiative rates in Equation 20, 21, and 22 in [Lucy02]. Then we calculate the downward emission probability from Equation 5, the downward and upward internal transition probabilities in [Lucy03].

\[\begin{split}p_\textrm{emission down}&= {\cal R}_{\textrm{i}\rightarrow\textrm{lower}}\,(\epsilon_\textrm{upper} - \epsilon_\textrm{lower}) / {\cal D}_{i}\\ p_\textrm{internal down}&= {\cal R}_{\textrm{i}\rightarrow\textrm{lower}}\,\epsilon_\textrm{lower}/{\cal D}_{i}\\, p_\textrm{internal up}&={\cal R}_{\textrm{i}\rightarrow\textrm{upper}}\,\epsilon_{i}/{\cal D}_{i}\\,\end{split}\]
where \(i\) is the current level, \(\epsilon\) is the energy of the level, and \({\cal R}\) is the radiative
rates.

We ignore the probability to emit a k-packet as TARDIS only works with photon packets. Next we calculate the radidative rates using equation 10 in [Lucy03].

\[\begin{split}{\cal R}_{\textrm{upper}\rightarrow\textrm{lower}} &= A_{\textrm{upper}\rightarrow\textrm{lower}}\beta_\textrm{Sobolev}n_\textrm{upper}\\ {\cal R}_{\textrm{lower}\rightarrow\textrm{upper}} &= (B_{\textrm{lower}\rightarrow\textrm{upper}}n_\textrm{lower}- B_{\textrm{upper}\rightarrow\textrm{lower}}n_\textrm{upper}) \beta_\textrm{Sobolev} J_{\nu}^{b}\\,\end{split}\]

with \(\beta_\textrm{Sobolev} = \frac{1}{\tau_\textrm{Sobolev}}(1-e^{-\tau_\textrm{Sobolev}})\) .

using the Einstein coefficients

\[\begin{split}A_{\textrm{upper}\rightarrow\textrm{lower}} &= \frac{8 \nu^2 \pi^2 e^2}{m_e c^3}~ \frac{g_\textrm{lower}}{g_\textrm{upper}}~f_{\textrm{lower}\rightarrow\textrm{upper}}\\ A_{\textrm{upper}\rightarrow\textrm{lower}} &= \underbrace{\frac{4 \pi^2 e^2}{m_e c}}_{C_\textrm{Einstein}}~ \frac{2\nu^2}{c^2} \frac{g_\textrm{lower}}{g_\textrm{upper}}~f_{\textrm{lower}\rightarrow\textrm{upper}}\\ B_{\textrm{lower}\rightarrow\textrm{upper}} &= \frac{4\pi^2 e^2}{m_e h\nu c}\,f_{\textrm{lower}\rightarrow\textrm{upper}}\\ B_{\textrm{lower}\rightarrow\textrm{upper}} &= \underbrace{\frac{4 \pi^2 e^2}{m_e c}}_{C_\textrm{Einstein}}\frac{1}{h\nu} f_{\textrm{lower}\rightarrow\textrm{upper}}\\\end{split}\]\[\begin{split}B_{\textrm{upper}\rightarrow\textrm{lower}} &= \frac{4\pi^2 e^2}{m_e h\nu c}\,f_{\textrm{lower}\rightarrow\textrm{upper}}\\ B_{\textrm{upper}\rightarrow\textrm{lower}} &= \underbrace{\frac{4 \pi^2 e^2}{m_e c}}_{C_\textrm{Einstein}}\frac{1}{h\nu}\frac{g_\textrm{lower}}{g_\textrm{upper}}f_{\textrm{lower}\rightarrow\textrm{upper}}\\\end{split}\]

we get

\[\begin{split}{\cal R}_{\textrm{upper}\rightarrow\textrm{lower}} &= C_\textrm{Einstein} \frac{2\nu^2}{c^2} \frac{g_\textrm{lower}}{g_\textrm{upper}}~f_{\textrm{lower}\rightarrow\textrm{upper}} \beta_\textrm{Sobolev}n_\textrm{upper}\\\end{split}\]\[\begin{split}{\cal R}_{\textrm{lower}\rightarrow\textrm{upper}} &= C_\textrm{Einstein}\frac{1}{h\nu} f_{\textrm{lower}\rightarrow\textrm{upper}} (n_\textrm{lower}-\frac{g_\textrm{lower}}{g_\textrm{upper}}n_\textrm{upper}) \beta_\textrm{Sobolev} J_{\nu}^{b}\\\end{split}\]

This results in the transition probabilities:

\[\begin{split}p_\textrm{emission down}&= C_\textrm{Einstein} \frac{2\nu^2}{c^2} \frac{g_\textrm{lower}}{g_\textrm{i}}~f_{\textrm{lower}\rightarrow\textrm{i}} \beta_\textrm{Sobolev}n_\textrm{i}\,(\epsilon_\textrm{i} - \epsilon_\textrm{lower}) / {\cal D}_{i}\\ p_\textrm{internal down}&= C_\textrm{Einstein} \frac{2\nu^2}{c^2} \frac{g_\textrm{lower}}{g_\textrm{i}}~f_{\textrm{lower}\rightarrow\textrm{i}} \beta_\textrm{Sobolev}n_\textrm{i}\,\epsilon_\textrm{lower}/{\cal D}_{i}\\ p_\textrm{internal up}&=C_\textrm{Einstein}\frac{1}{h\nu} f_{\textrm{i}\rightarrow\textrm{upper}} (n_\textrm{i}-\frac{g_\textrm{i}}{g_\textrm{upper}}n_\textrm{upper}) \beta_\textrm{Sobolev} J_{\nu}^{b}\,\epsilon_{i}/{\cal D}_{i}\\,\end{split}\]
and as we will normalise the transition probabilities numerically later, we can get rid of \(C_\textrm{Einstein}\),
\(\frac{1}{{\cal D}_i}\) and number densities.
\[\begin{split}p_\textrm{emission down}&= \frac{2\nu^2}{c^2} \frac{g_\textrm{lower}}{g_\textrm{i}}~f_{\textrm{lower}\rightarrow\textrm{i}} \beta_\textrm{Sobolev}\,(\epsilon_\textrm{i} - \epsilon_\textrm{lower})\\ p_\textrm{internal down}&= \frac{2\nu^2}{c^2} \frac{g_\textrm{lower}}{g_\textrm{i}}~f_{\textrm{lower}\rightarrow\textrm{i}} \beta_\textrm{Sobolev}\,\epsilon_\textrm{lower}\\ p_\textrm{internal up}&=\frac{1}{h\nu} f_{\textrm{i}\rightarrow\textrm{upper}} \underbrace{(1-\frac{g_\textrm{i}}{g_\textrm{upper}}\frac{n_\textrm{upper}}{n_i})} _\textrm{stimulated emission} \beta_\textrm{Sobolev} J_{\nu}^{b}\,\epsilon_{i}\\,\end{split}\]

There are two parts for each of the probabilities, one that is pre-computed by ~tardisatomic and is in the HDF5 File, and one that is computed during the plasma calculations:

\[\begin{split}p_\textrm{emission down}&= \underbrace{\frac{2\nu^2}{c^2} \frac{g_\textrm{lower}}{g_\textrm{i}}~f_{\textrm{lower}\rightarrow\textrm{i}} (\epsilon_\textrm{i} - \epsilon_\textrm{lower})}_\textrm{pre-computed} \,\beta_\textrm{Sobolev}\\ p_\textrm{internal down} &= \underbrace{\frac{2\nu^2}{c^2} \frac{g_\textrm{lower}}{g_\textrm{i}}~f_{\textrm{lower}\rightarrow\textrm{i}} \epsilon_\textrm{lower}}_\textrm{pre-computed}\,\beta_\textrm{Sobolev}\\ p_\textrm{internal up} &= \underbrace{\frac{1}{h\nu} f_{\textrm{i}\rightarrow\textrm{upper}}}_\textrm{pre-computed} \beta_\textrm{Sobolev} J_{\nu}^{b}\, (1-\frac{g_\textrm{i}}{g_\textrm{upper}}\frac{n_\textrm{upper}}{n_i}) \,\epsilon_{i}.\end{split}\]

NLTE treatment

NLTE treatment of lines is available both in ~LTEPlasma and the ~NebularPlasma class. This can be enabled by specifying which species should be treated as NLTE with a simple list of tuples (e.g. [(20,1)] for Ca II).

First let’s dive into the basics:

There are two rates to consider from a given level.

\[\begin{split}r_{\textrm{upper}\rightarrow\textrm{lower}} &= \underbrace{A_{ul} n_u}_\textrm{spontaneous emission} + \underbrace{B_{ul} n_u \bar{J}_\nu}_\textrm{stimulated emission} + \underbrace{C_{ul} n_u n_e}_\textrm{collisional deexcitation}\\ &= n_u \underbrace{(A_{ul} + B_{ul}\bar{J}_\nu + C_{ul} n_e)}_{r_{ul}} \\\end{split}\]\[\begin{split}r_{\textrm{lower}\rightarrow\textrm{upper}} &= \underbrace{B_{lu} n_l \bar{J}_\nu}_\textrm{stimulated absorption} + \underbrace{C_{lu}\,n_l\,n_e}_\textrm{collisional excitation}\\ &= n_l \underbrace{(B_{lu}\bar{J}_\nu + C_{ul}n_e)}_{r_{lu}},\end{split}\]

where \(\bar{J}_\nu\) (in LTE this is \(B(\nu, T)\)) denotes the mean intensity at the frequency of the line and \(n_e\) the number density of electrons.

Next, we calculate the rate of change of a level by adding up all outgoing and all incoming transitions from level \(j\).

\[\frac{dn_j}{dt} = \underbrace{\sum_{i \ne j} r_{ij}}_\textrm{incoming rate} - \underbrace{\sum_{i \ne j} r_{ji}}_\textrm{outgoing rate}\]

In a statistical equilibrium all incoming rates and outgoing rates add up to 0 (\(\frac{dn_j}{dt}=0\)). We use this to calculate the level populations using the rate coefficients (\(r_ij, r_ji\)).

\[\begin{split}\left( \begin{matrix} -(\cal{r}_{12} + \dots + \cal{r}_{1j}) & \dots & \cal{r}_{j1}\\ \vdots & \ddots & \vdots \\ \cal{r}_{1j} & \dots & - (\cal{r} _{j1} + \dots + \cal{r} _{j, j-1}) \\ \end{matrix} \right) % \left( \begin{matrix} n_1\\ \vdots\\ n_j\\ \end{matrix} \right) % = % \left( \begin{matrix} 0\\ 0\\ 0\\ \end{matrix} \right)\end{split}\]

with the additional constrained that all the level number populations need to add up to the current ion population $N$ we change this to

\[\begin{split}\left( \begin{matrix} 1 & 1 & \dots \\ \vdots & \ddots & \vdots \\ \cal{r}_{1j} & \dots & - (\cal{r} _{j1} + \dots + \cal{r} _{j, j-1}) \\ \end{matrix} \right) % \left( \begin{matrix} n_1\\ \vdots\\ n_j\\ \end{matrix} \right) % = % \left( \begin{matrix} N\\ 0\\ 0\\ \end{matrix} \right)\end{split}\]

For a three level atom we have:

\[\begin{split}\frac{dn_1}{dt} &= \underbrace{n_2 r_{21} + n_3 r_{31}}_\textrm{incoming rate} - \underbrace{(n_1 r_{12} + n_1 r_{13})}_\textrm{outgoing rate} = 0\\\end{split}\]\[\begin{split}\frac{dn_2}{dt} &= \underbrace{n_1 r_{12} + n_3 r_{32}}_\textrm{incoming rate} - \underbrace{(n_2 r_{21} + n_2 r_{23})}_{outgoing rate} = 0\\\end{split}\]\[\begin{split}\frac{dn_3}{dt} &= \underbrace{n_1 r_{13} + n_2 r_{23}}_\textrm{incoming rate} - \underbrace{(n_3 r_{32} + n_3 r_{31})}_\textrm{outgoing rate} = 0,\end{split}\]

which can be written in matrix from:

\[\begin{split}\left(\begin{matrix} -(r_{12} + r_{13}) & r_{21} & r_{31}\\ r_{12} & -(r_{21} + r_{23}) & r_{32}\\ r_{13} & r_{23} & -(r_{31} + r_{32}) \\ \end{matrix}\right) \left( \begin{matrix} n_1\\ n_2\\ n_3\\ \end{matrix} \right) = \left( \begin{matrix} 0\\ 0\\ 0\\ \end{matrix} \right)\end{split}\]

To solve for the level populations we need an additional constraint: \(n_1 + n_2 + n_3 = N\). By setting \(N = 1\): we can get the relative rates:

\[\begin{split}\left(\begin{matrix} 1 & 1 & 1\\ r_{12} & -(r_{21} + r_{23}) & r_{32}\\ r_{13} & r_{23} & -(r_{31} + r_{32}) \\ \end{matrix}\right) \left( \begin{matrix} n_1\\ n_2\\ n_3\\ \end{matrix} \right) = \left( \begin{matrix} 1\\ 0\\ 0\\ \end{matrix} \right)\end{split}\]

Now we go back and look at the rate coefficients used for a level population - as an example \(\frac{dn_2}{dt}\):

\[\begin{split}\frac{dn_2}{dt} &= n_1 r_{12} - n_2 (r_{21} + r_{23}) + n_3 r_{32}\\ &= n_1 B_{12} \bar{J}_{12} + n_1 C_{12} n_e - n_2 A_{21} - n_2 B_{21} \bar{J}_{21} - n_2 C_{21} n_e\\ - n_2 B_{23} \bar{J}_{23} - n_2 C_{23} n_e + n_3 A_{32} + n_3 B_{32} \bar{J}_{32} + n_3 C_{32} n_e,\\ + n_3 A_{32} + n_3 C_{32} n_e,\end{split}\]

Next we will group the stimulated emission and stimulated absorption terms as we can assume \(\bar{J_{12}} = \bar{J_{21}}\):

\[\begin{split}\frac{dn_2}{dt} &= n_1 (B_{12} \bar{J}_{12} \underbrace{(1 - \frac{n_2}{n_1}\frac{B_{21}}{B_{12}})}_\textrm{stimulated emission term} + C_{12} n_e) - n_2 (A_{21} + C_{23} n_e + n_2 B_{23} \bar{J}_{23} \underbrace{(1 - \frac{n_3}{n_2}\frac{B_{32}}{B_{23}})}_\textrm{stimulated emission term}) + n_3 (A_{32} + C_{32} n_e)\end{split}\]

Helium NLTE

The helium_treatment setting in the Tardis config. file will accept one of three options:
  • none: The default setting. Populate helium in the same way as the other elements.
  • recomb-nlte: Treats helium in NLTE using the analytical approximation outlined in an upcoming paper.
  • numerical-nlte: To be implemented. Will allow the use of a separate module (not distributed with Tardis) to perform helium NLTE calculations numerically.
Recombination He NLTE

Paper version:

This section will summarise the equations used in the calculation of the helium state for the recomb-NLTE approximation in Tardis. A full physical justification for these equations will be provided in an upcoming paper. All of the level populations are given as a function of the He II ground state population (\(n_{2,1,0}\)), and the values are then normalised using the helium number density to give the correct level number densities.

Symbols/Indexing:
  • \(N_{i,j}\): Ion Number Density
  • \(n_{i,j,k}\): Level Number Density
  • i: atomic number
  • j: ion number
  • k: level number
\[n_{2,0,0} = 0\]
\[n_{2,0,k}~(k\geq1) = n_{2,1,0}\times n_{e}\times\frac{1}{W}\times\frac{g_{2,0,k}}{2g_{2,1,0}}\times\left(\frac{h^{2}}{2\pi m_{e}kT_{r}}\right)^{3/2}\times\exp{\left(\frac{\chi_{2,1}-\epsilon_{2,0,k}}{kT_{r}}}\right)\times\left(\frac{T_{r}}{T_{e}}\right)^{1/2}\]

(Note: An extra factor of \(\frac{1}{W}\) is included for the metastable states of He I.)

\[n_{2,1,0} = 1\]
\[n_{2,1,k}~(k\geq1) = W\times\frac{g_{2,0,k}}{g_{2,1,0}}\times n_{2,1,0}\times\exp{\left(-\frac{\epsilon_{2,1,k}}{kT_{r}}\right)}\]
\[n_{2,2,0} = \frac{n_{2,1,0}}{n_{e}}\times[W(\delta_{2,2}\times\zeta_{2,2}+W(1-\zeta_{2,2})]\left(\frac{T_{e}}{T_{r}}\right)^{1/2}\times\frac{2g_{2,2,0}}{g_{2,1,0}}\times\left(\frac{2\pi m_{e}kT_{r}}{h^{2}}\right)^{3/2}\times\exp{\left(-\frac{\chi_{2,1}}{kT_{r}}\right)}\]

Code Version:

In the Tardis plasma, some of these equations are re-written slightly to make use of existing property methods (e.g. PhiSahaLTE, PhiSahaNebular) often using the relation:

\[\frac{N_{i,j}}{Z_{i,j}} = \frac{n_{i,j,k}}{g_{i,j,k}}\]
Numerical He NLTE

Another helium_treatment option offered by Tardis is numerical-nlte. The use of this plasma property requires an additional code that is the property of Stephan Hachinger (see arXiv:1201.1506) and is not distributed with Tardis. Tardis also requires a specific atomic datafile to use this module. This plasma option is included so that people who have access to and permission to use the necessary module may use it. Otherwise, the recomb-NLTE option provides a very accurate alternative approximation.

Full Changelog

1.1 (unreleased)

  • Nothing changed yet.

1.0 (2015-03-03)

New Features

  • the main feature is the re-write of the montecarlo code to C, which comes

with a 4x speed increase.

Pull Requests

0.9.2 (2014-06-12)

Bugfixes

  • “print” replaced with logger for classical nebular
  • logger statement added for coronal approximation
  • warning added to documentation since plasma is out of date (temp solution only) #108
  • fix to binary search to deal with packets at end of line list
  • warning added for density file readin outside tabulated range
  • fix to NLTE solver (treats stimulated emission directly); tests included
  • fix for zeta factor outside of temperature + test included [#134, wkerzendorf]

New Features

  • added data_sources and version to AtomData set
  • added atomic database table with downloads to documentation
  • added more conversion routines for Species strings (e.g. Si IX) to util
  • added new density models (power law and exponential) [mklauser]
  • reimplementation of binary-search in C (towards faster/profileable code) [V. Jancauskas]
  • added new astropy setup using astropy-helpers (thanks to @embray and @astrofrog for debugging help) [wkerzendorf #144]
  • added a test that runs the full calculation and compares the spectrum output [wkerzendorf #144]
  • added the new documentation validator [mklauser & wkerzendorf #134, #136]
  • added a new configuration object for TARDIS [wkerzendorf #143]
  • changed TARDISConfiguration -> Configuration [wkerzendorf #154]
  • changed most small functions to C [V. Jancauskas #142]

0.9.1 (2014-02-03)

New Features

Bugfixes

  • missing ez_setup.py and setuptools_bootstrap.py included now
  • reading of ascii density and abundance profiles
  • several fixes in the documentation

Glossary

meta-stable
A level is considered meta-stable if there’s no line that has this level as the upper state. This means that these levels can not be reached by absorbing a photon and are only reached when decaying.
synapps
simple radiative transport code for supernovae. Please refer to Synapps for more information

References

[Lucy02]L. B. Lucy. Monte Carlo transition probabilities. \aap , 384:725–735, March 2002. arXiv:arXiv:astro-ph/0107377, doi:10.1051/0004-6361:20011756.
[Lucy03]L. B. Lucy. Monte Carlo transition probabilities. II. \aap , 403:261–275, May 2003. arXiv:arXiv:astro-ph/0303202, doi:10.1051/0004-6361:20030357.
[MazzaliLucy93]P. A. Mazzali and L. B. Lucy. The application of Monte Carlo methods to the synthesis of early-time supernovae spectra. \aap , 279:447–456, November 1993.

Credits

Core Team

  • Wolfgang Kerzendorf
  • Stuart Sim
  • Michael Klauser
  • Markus Kromer

Contributers

  • Maryam Patel (documentation, test environment)
  • Adam Suban-Loewen (GUI, profiling and parallelization)
  • Chris Sasaki and Mike Reid (Logo)
  • Knox Long & Christian Knigge (comparisons with “Python” and ionization data)
  • Erik Bray (help with incorporating Astropy’s setuphelpers)
  • Robert Ryans (testing installation process)

License

TARDIS-SN License

TARDIS-SN is licensed under a 3-clause BSD style license:

Copyright (c) 2014, TARDIS-SN Developers All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

Neither the name of the TARDIS-SN nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The code is built on a few principles:

  • open - the code is fully open source and we invite usage and contributions from the community
  • modular - the code has different microphysics modules and can be easily extended
  • fast - the code is geared towards rapid spectral synthesis to fit supernovae and other transients
  • easy - the code is designed to be easily installed and run as well as a detailed documentation

We encourage you to subscribe to tardis-sn-user to ask questions about TARDIS.

If you use this code for any publications or presentations please acknowledge it accordingly. For this first version please mention the website and cite Kerzendorf & Sim 2014.

User modifications and additions that lead to publications need to be handed back to the community by incorporating them into this publicly available version of TARDIS.

The current stable version of TARDIS is 0.9.2 and can be downloaded here, further installation instructions are available here Installation.

A file containing an example configuration file and an atomic database can be found in the section Running TARDIS

If you’re interested in contributing to the code, either contact us or you can contribute directly via github. We are using Astropy’s excellent workflow - more details can be found at http://astropy.readthedocs.org/en/latest/development/workflow/maintainer_workflow.html.

Warning

Currently TARDIS only works on 64-bit python installations. We’re working on making it work on 32-bit python distributions.