-
Notifications
You must be signed in to change notification settings - Fork 3
Home
iris is a Python library to generate slab models of molecular emission in disks.
The modules in iris are optimized with jax, and set up to efficiently model the emission of multiple species simultaneously, as well as the effects of radial temperature gradients and Keplerian line broadening.
- Installation
-
Background
2.1 Slab Models
2.2 Keplerian Broadening -
Crash Course
3.1 Initial Settings
3.2 Fetching HITRAN Data
3.3 Making a Slab Model
3.4 Compiling the Code - Examples
- FAQ
Install the package via pip:
pip install iris-jwstiris requires the following packages be installed beforehand: jax, pandas, astropy, astroquery
and Python >= 3.8
With iris, the molecular emission lines are modeled using a slab technique, where the intensities are set by an average excitation temperature
We assume the lines are Gaussians and have an intrinsic full-width-at-half-maximum
iris generates an optical depth grid at a very high resolution (
where
Here
Setting a distance to the source of
The rightmost term above sets the final line profile, accounting for saturation effects at the line center:
The flux model is then post-processed by iris to a user-specified resolving power (R) and wavelength grid.
Optionally, we can go a step further and include the effects of Keplerian broadening on the line profiles. iris does this in 4 steps, which only add a couple milliseconds to the compute time:
- Make a standard flux model
$F_{\lambda}$ with turbulent+thermal broadening only. - Generate a flat, Keplerian disk model with inclination
$i$ , stellar mass$M_{*}$ , innermost radius$r_{in}$ , and outermost radius$r_{out}$ . - Finely sample the line-of-sight velocities
$v_{LOS} = v_{K} \sin(i) \cos(\phi)$ within the emitting region, where$v_{K} = (G M_{*}/r)^{0.5}$ is the Keplerian velocity and$\phi$ is the azimuthal angle. Make a normalized histogram ($k_{LOS}$ ) of$v_{LOS}$ . - Convolve
$F_{\lambda}$ with$k_{LOS}$ .
The effects will be most noticeable for disks at high inclinations, very small inner radii, and/or with higher stellar masses.
Whenever we work with iris, first we need to import jax in 64-bit mode. This is very important for the kind of calculations made by iris:
import jax
jax.config.update("jax_enable_x64", True)We can then import any other useful packages and iris itself:
import jax.numpy as jnp
import numpy as np
import matplotlib.pyplot as plt
import iris as iris
from iris import setupThe very first time we model a molecule, we need to download the corresponding HITRAN spectroscopic data. iris will do this for you.
First we specify a path where we want the data to be downloaded, and then we use setup.setup_linelists to fetch and format the data:
path_to_moldata = './' # path where we want to save the HITRAN data
# Get the data for H$_2$O and $^{13}$CO
setup.setup_linelists('H2O', 'H2O', 1, path_to_moldata)
setup.setup_linelists('CO2', 'CO2', 1, path_to_moldata)
setup.setup_linelists('13CO', 'CO', 2, path_to_moldata)Note the signature of this function: the first argument is the isotopolog name, this can be whatever you want to name this species, e.g. '13CO' for
- Define a wavelength grid (in micron) to evaluate opacities. The spacing should correspond to an
$R \sim 10^{5-6}$ or whatever is enough to sample the intrinsic line widths$\Delta V$ you want.
fine_wgrid = np.arange(11.5, 18.5, 1e-5)Here we make a wavelength grid from 11.5 to 18.5 micron, spaced out by
- Define a wavelength to downsample the final flux model. This will usually be the wavelength grid of your data.
obs_wgrid = np.arange(12.0,18.0,0.002)Be careful obs_wgrid is within fine_wgrid, otherwise it's impossible to downsample the model.
- Define the resolving power of the instrument
R = 3200iris will convolve the flux density model with a Gaussian of equivalent width to the resolving power provided.
- Initialize the
slabobject. You only need to do this once, it will take a few seconds to parse the molecular data.
slab = iris.slab(molecules=['CO2', 'H2O'], wlow=11.0, whigh=19.0, path_to_moldata=path_to_moldata)We have created a slab object to model
- iris will model the molecules in the order provided.
- The
wlowandwhighmin. and max. wavelengths should extend beyond the wavelength range spanned byfine_wgrid. Otherwise, you risk missing molecular lines. Still, the code will be faster when using a smaller wavelength range.
- Set up the wavelength grid in the
slab:
slab.setup_grid(fine_wgrid, obs_wgrid, R)We call setup_grid any time we want to update the wavelength grid.
- Define your disk parameters.
Distance to source:
distance = 120 # pcThe temperature, column density, emitting area, and
# Excitation temperatures for each molecule in K
T_ex = np.array([np.array([800.0, 400.0, 200.0]),
np.array([800.0, 400.0, 200.0])])
# column densities in cm^-2
N_mol = np.array([np.array([1e16, 5e16, 1e16]),
np.array([5e18, 5e17, 1e17])])
# emitting areas in au^2
A_au = np.array([np.array([0.1, 1.0, 10.0]),
np.array([0.1, 1.0, 10.0])])
# intrinsic line widths in km/s (line FWHM)
dV = np.array([np.array([2.0, 2.0, 2.0]),
np.array([2.0, 2.0, 2.0])])Here we are modeling 3 slabs for
- Set up the disk object. For now, let's ignore Keplerian broadening.
We call the slab.setup_disk module every time we wish to update a physical parameter.
slab.setup_disk(distance, T_ex=T_ex, N_mol=N_mol, A_au=A_au, dV=dV)- Generate the slab model.
We use the slab.simulate module to calculate the slab model.
slab.simulate()If we wanted to include Keplerian effects, we would define an inclination, stellar mass, and innermost radius, and call slab.simulate_keplerian()
The full flux density model can then be accessed as flux.flux_model. The downsampled and convolved model is slab.downsampled_flux.
plt.figure(figsize=(17,4))
plt.plot(obs_wgrid, slab.downsampled_flux, color='orangered', lw=1)
plt.xlim(12, 18.0)
plt.tick_params(direction='in', top=True, right=True, length=5, labelsize=14)
plt.xlabel('Wavelength ($\mu$m)', fontsize=17)
plt.ylabel('Flux (Jy)', fontsize=17)
Thanks to jax, we can greatly speed-up performance by jit-compiling the code. In short, we want to define a function that calls slab.setup_disk, slab.setup_grid, and slab.simulate(). Then we compile using jax.jit():
def compiled_slab(distance, T_ex, N_mol, A_au, dV, fine_wgrid, wavelength, R):
slab.setup_disk(distance, T_ex, N_mol, A_au, dV) # initialize object
slab.setup_grid(fine_wgrid, wavelength, R) # set up wavelength and model parameters
slab.simulate() # make model
return slab.downsampled_flux, slab.flux_model
'''Just-in-time compilation'''
compiled_slab_jit = jax.jit(compiled_slab)
_ = compiled_slab_jit(distance, T_ex, N_mol, A_au, dV, fine_wgrid, wavelength, R)Calling compiled_slab will be
Please see the Examples page for tutorials.
- Do I need a GPU?
Not necessarily. You can install the CPU-only version of jax to use iris. But the code will be substantially slower, since the computations cannot be vectorized that way. It will take about
- How do I install jax?
Please see: https://jax.readthedocs.io/en/latest/installation.html and follow the instructions for your particular GPU and operating system.
- Can I run this on a MacBook Pro M(1-4)?
The short answer is no. iris is not set up to run on a personal laptop, and jax is not yet optimized to use the MacBook GPU. You can still install the CPU-only version of jax (See 1).
- How much GPU memory do I need?
It depends. For a few molecules and temperature components, 16 GB should be enough. To model temperature gradients with tens of components or full retrievals of many species and isotopologs, you'll need access to a computing cluster with an A100 GPU with 40 GB or more. Services like Google Colab provide these for $1.50 / hr.