Getting Started

Preparing Data

The tutorial data are provided in this url: https://github.com/sugayu/tokult/tree/dev/tutorial. Download the data and put them on a directory to work in. You can download the data from the terminal with:

$ svn export https://github.com/sugayu/tokult/branches/dev/tutorial/
$ cd tutorial

The donwloaded data are:

tokult_mockcube_dirty.fits

The ALMA mock dirty cube with natural weighting.

tokult_cube_dirty.psf.fits

The ALMA dirty beam with natural weighting.

tokult_cube_dirty_uniform.psf.fits

The ALMA dirty beam with uniform weighting.

tokult_x-arcsec-deflect.fits

The map of the deflection angles along the \(x\) axis.

tokult_y-arcsec-deflect.fits

The map of the deflection angles along the \(y\) axis.

tokult_gamma1.fits, tokult_gamma2.fits, tokult_kappa.fits

The gamma1, gamma2, and kappa lensing parameter maps. Not needed in this tutorial.

Quickstart

Launching a Tokult instance

First of all, import the Tokult package and then launch Tokult by inputting the file names.

import tokult

tok = tokult.Tokult.launch(
    'tokult_mockcube_dirty.fits',
    'tokult_cube_dirty.psf.fits',
    ('tokult_x-arcsec-deflect.fits', 'tokult_y-arcsec-deflect.fits'),
)

tok provides basic fitting modules of Tokult. The first argument takes the file name or the np.ndarray of the data cube, the second takes the dirty beam (point spread function), and the third takes the lensing parameters summarized in a tuple.

Next, Tokult needs to know which cubic region you want to use for the model fitting. The current code does not work without specifying the region. In addition, the lensing parameters (x-deflection and y-deflection) need redshifts of the lensing cluster and the target object; in this tutorial, the cluster and mock galaxy are assumed at z = 0.541 and z = 1.7, respectively. tok can take these parameters with the methods use_region() and use_redshifts().

tok.use_region(xlim=(32, 96), ylim=(32, 96), vlim=(5, 12))
tok.use_redshifts(z_lens=0.541, z_source=1.7)

The xlim, ylim, and vlim determine the size of an argument tok.datacube.imageplane and others. The redshift information is stored in internal parameters and also used to convert the parameters to those in physical units later.

uv-coverage

This step can be skipped if you use the image-plane fitting. The current uv-plane fitting requires a re-sampled uv coverage of the observations. The uv coverage can be obtained from a dirty beam created with uniform weighting.

import numpy as np
from astropy.io import fits

data = fits.getdata('tokult_cube_dirty_uniform.psf.fits')
uvpsf_uniform = tokult.misc.rfft2(np.squeeze(data))
uvcoverage = (uvpsf_uniform[tok.datacube.vslice, :, :]) > 1.0e-5  # This value 1.0e-5 is like arbitrary.

Here, uvcoverage is a mask, i.e., np.ndarray including Boolean; True indicates that the pixels are used in fitting and vice versa.

Setting/Guessing initial parameters and bounding

Initial parameters for fitting can be set by hand, or Tokult employs the method initialguess to estimate initial parameters from the moment-0 and 1 maps. Tokult also provides tokult.get_bound_params(), which makes it easy to specify the parameter boundaries.

init = tok.initialguess()
init = init._replace(mass_dyn=2.0)  # To fix a bug(?)
bound = tokult.get_bound_params(
    x0_dyn=(32, 96),
    y0_dyn=(32, 96),
    radius_dyn=(0.01, 5.0),
    velocity_sys=(5, 12),
    mass_dyn=(-2.0, 10.0),
    velocity_dispersion=(0.1, 3.0),
    brightness_center=(0.0, 1.0),
)

The fitting parameters are explained here [TBD].

Quick initial fitting

Now you are ready! First, let’s try to perform fitting on the image plane with the least-square method.

Note

The least-square method may fall in local minima if the initial parameters are far from the true values, but it is useful to know whether the fitting code works.

sol = tok.imagefit(init=init, bound=bound, optimization='ls')

Done. Let’s check the fitting results, which are contained in sol. The best fit parameters are contained in sol.best:

sol.best
InputParams(x0_dyn=63.98926461367171, y0_dyn=64.01280881181941, PA_dyn=3.1435523445232345, inclination_dyn=1.030729882664348, radius_dyn=2.9962304513969964, velocity_sys=7.996353444267494, mass_dyn=2.001782648183586, brightness_center=0.0009768938914882768, velocity_dispersion=0.9902675180818492, radius_emi=2.9962304513969964, x0_emi=63.98926461367171, y0_emi=64.01280881181941, PA_emi=3.1435523445232345, inclination_emi=1.030729882664348)

These output values are in units of pixels for simplicity in the code. The physical units are added by:

sol.add_units()
FitParamsWithUnits(x0_dyn=<Longitude 177.38993349 deg>, y0_dyn=<Latitude 22.41271684 deg>, PA_dyn=<Quantity 3.14355234 rad>, inclination_dyn=<Quantity 1.03072988 rad>, radius_dyn=<Quantity 0.14981152 arcsec>, velocity_sys=<Quantity -0.18232766 km / s>, mass_dyn=<Dex 2.00178265 dex(pix3)>, brightness_center=<Quantity 0.39075756 Jy / arcsec2>, velocity_dispersion=<Quantity 49.52163522 km / s>, radius_emi=<Quantity 0.14981152 arcsec>, x0_emi=<Longitude 177.38993349 deg>, y0_emi=<Latitude 22.41271684 deg>, PA_emi=<Quantity 3.14355234 rad>, inclination_emi=<Quantity 1.03072988 rad>)

The method add_units() makes use of the header information of the data and the lensing parameter map.

The best-fit result can be visualized by like this:

import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 3, figsize=[6.28 * 3, 6.28])
axes[0].imshow(tok.datacube.moment0(), origin='lower')
axes[1].imshow(tok.modelcube.moment0(), origin='lower')
axes[2].imshow(tok.datacube.moment0() - tok.modelcube.moment0(), origin='lower')
_images/fig_bestfit.png

The left and middle panels show the moment-0 maps of the data and best-fit model, respectively. The data was well-reproduced by the model, and the residual map looks like pure noises as the right panel.

Restarting model-fit

It is known that the least-square method may underestimate the fitting uncertainties for the spatially-correlated data. To obtain correct uncertainties, as well as to escape from shallow local minima, the MCMC method on the uv plane is a great option.

Let’s fit an example data; but it takes more than the least-square method, maybe >~10 minutes for the tutorial data.

sol = tok.uvfit(
    init=init, bound=bound, mask_for_fit=uvcoverage, progressbar=True
)
100%|███████████████████████|5000/5000 [18:57<00:00,  4.40it/s]

If you want to use parallelization, please see [TBD].

Parallelization

Short code as an example.

import multiprocessing

with multiprocessing.Pool() as pool:
    sol = tok.uvfit(
        init=init,
        bound=bound,
        mask_for_fit=uvcoverage,
        progressbar=True,
        pool=pool
    )