Photometric calibration: fitting a stellar spectrum

The 2MASS photometry of PZ Tel A is fitted with a IRTF spectrum of a G8V type star. With the best-fit scaling parameter, synthetic photometry is calculated for the VLT/SPHERE H2 filter.

Getting started

The required modules are imported, species is initiated, and an object of Database is created.

[1]:
import urllib.request
import species
from IPython.display import Image
[2]:
species.SpeciesInit()
Initiating species v0.1.4... [DONE]
Creating species_config.ini... [DONE]
Database: /Users/tomasstolker/applications/species/docs/tutorials/species_database.hdf5
Data folder: /Users/tomasstolker/applications/species/docs/tutorials/data
Working folder: /Users/tomasstolker/applications/species/docs/tutorials
Creating species_database.hdf5... [DONE]
Creating data folder... [DONE]
[2]:
<species.core.setup.SpeciesInit at 0x127cb6470>
[3]:
database = species.Database()

The distance and certainty of PZ Tel are defined and a dictionary with the 2MASS magnitudes is created.

[4]:
distance = (47.13, 0.13)  # [pc]
[5]:
magnitudes = {'2MASS/2MASS.J':(6.856, 0.021),
              '2MASS/2MASS.H':(6.486, 0.049),
              '2MASS/2MASS.Ks':(6.366, 0.024)}

Also a list with the filter names is created.

[6]:
filters = list(magnitudes.keys())

Adding an object and calibration spectrum

The distance and magnitudes are of PZ Tel A are stored in the database as object.

[7]:
database.add_object(object_name='PZ Tel A',
                    distance=distance,
                    app_mag=magnitudes,
                    spectrum=None)
Adding filter: 2MASS/2MASS.J... [DONE]
Downloading Vega spectrum (270 kB)... [DONE]
Adding Vega spectrum... [DONE]
Adding filter: 2MASS/2MASS.H... [DONE]
Adding filter: 2MASS/2MASS.Ks... [DONE]
Adding object: PZ Tel A... [DONE]

A G8V type spectrum is downloaded from the IRTF website.

[8]:
urllib.request.urlretrieve('http://irtfweb.ifa.hawaii.edu/~spex/IRTF_Spectral_Library/Data/G8V_HD75732.txt',
                           'data/G8V_HD75732.txt')
[8]:
('data/G8V_HD75732.txt', <http.client.HTTPMessage at 0x127c69240>)

And stored as calibration data in de database.

[9]:
database.add_calibration(filename='data/G8V_HD75732.txt',
                         tag='G8V_HD75732')
Adding calibration spectrum: G8V_HD75732... [DONE]

Fitting the stellar spectrum

The stellar spectrum is fitted to the 2MASS fluxes with a scaling parameter. The boundaries for the uniform prior is provided as dictionary.

[10]:
fit = species.FitSpectrum(object_name='PZ Tel A',
                          filters=None,
                          spectrum='G8V_HD75732',
                          bounds={'scaling':(0., 1e0)})
Getting object: PZ Tel A... [DONE]

The posterior distribution are estimated with the MCMC ensemble sampler of *emcee*.

[11]:
fit.run_mcmc(nwalkers=200,
             nsteps=1000,
             guess={'scaling':5e-1},
             tag='pztel')
Running MCMC...
100%|██████████| 1000/1000 [00:02<00:00, 391.50it/s]
Mean acceptance fraction: 0.787
Integrated autocorrelation time = [3575.53872865]

Plotting the results

Let’s plot the evolution of the walkers.

[12]:
species.plot_walkers(tag='pztel',
                     nsteps=None,
                     offset=(-0.2, -0.08),
                     output='walkers.png')
Plotting walkers: walkers.png... [DONE]
[13]:
Image('walkers.png')
[13]:
../_images/tutorials_photometric_calibration_27_0.png

And the posterior distribution of the scaling parameter.

[14]:
species.plot_posterior(tag='pztel',
                       burnin=500,
                       offset=(-0.3, -0.10),
                       title_fmt='.4f',
                       output='posterior.png')
Median sample:
   - scaling = 0.12
Plotting the posterior: posterior.png... [DONE]
[15]:
Image('posterior.png')
[15]:
../_images/tutorials_photometric_calibration_30_0.png

For plotting the spectrum, the data of PZ Tel A are stored in an ObjectBox.

[16]:
objectbox = database.get_object(object_name='PZ Tel A',
                                filters=None)
Getting object: PZ Tel A... [DONE]

Next, 30 randomly selected samples from the posterior are used for creating comparison spectra.

[17]:
samples = database.get_mcmc_spectra(tag='pztel',
                                    burnin=500,
                                    random=30,
                                    wavel_range=(0.1, 50.0),
                                    spec_res=None)
Getting MCMC spectra: 100%|██████████| 30/30 [00:00<00:00, 540.12it/s]
The smooth_spectrum function has not been fully tested. [WARNING]

And also the best-fit value is extracted as the median of the posterior distribution.

[18]:
median = database.get_median_sample(tag='pztel',
                                    burnin=500)

Let’s have a look at the value.

[19]:
print(median)
{'scaling': 0.11984761895274632}

With this value, the best-fit spectrum is calculated.

[20]:
readcalib = species.ReadCalibration(tag='G8V_HD75732',
                                    filter_name=None)
[21]:
spectrum = readcalib.get_spectrum(model_param=median)

The best-fit value is also used for creating synthetic photometry in the 2MASS filters.

[22]:
synphot = species.multi_photometry(datatype='calibration',
                                   spectrum='G8V_HD75732',
                                   filters=filters,
                                   parameters=median)
Calculating synthetic photometry... [DONE]

The difference between the data and the best-fit synthetic photometry is stored in a ResidualsBox.

[23]:
residuals = species.get_residuals(datatype='calibration',
                                  spectrum='G8V_HD75732',
                                  parameters=median,
                                  filters=filters,
                                  objectbox=objectbox,
                                  inc_phot=True,
                                  inc_spec=False)
Calculating synthetic photometry... [DONE]
Calculating residuals... [DONE]
Residuals [sigma]:
   - 2MASS/2MASS.J: 0.03
   - 2MASS/2MASS.H: -0.57
   - 2MASS/2MASS.Ks: 0.28

Finally, all the boxes are provide as input to the plot_spectrum function.

[24]:
species.plot_spectrum(boxes=[samples, spectrum, objectbox, synphot],
                      filters=filters,
                      colors=['gray', 'black', ('black', None), 'black'],
                      residuals=residuals,
                      xlim=(1., 2.5),
                      ylim=(-1.5e-12, 1.1e-11),
                      scale=('linear', 'linear'),
                      offset=(-0.3, -0.08),
                      output='spectrum.png')
Plotting spectrum: spectrum.png... [DONE]
[25]:
Image('spectrum.png')
[25]:
../_images/tutorials_photometric_calibration_48_0.png

Photometric calibration

The magnitude of PZ Tel B can now be computed for any other filter within the wavelength coverage of the IRTF spectrum. This example calculates the flux and magnitude for the VLT/SPHERE H2 filter.

[26]:
readcalib = species.ReadCalibration(tag='G8V_HD75732',
                                    filter_name='Paranal/SPHERE.IRDIS_D_H23_2')
Adding filter: Paranal/SPHERE.IRDIS_D_H23_2... [DONE]
[27]:
flux = readcalib.get_flux(model_param=median)
print(f'Flux density [W m-2 micron-1] = {flux[0]:.2e}')
Flux density [W m-2 micron-1] = 3.30e-12
[28]:
app_mag, abs_mag = readcalib.get_magnitude(model_param=median, distance=distance)
print(f'Apparent magnitude [mag]] = {app_mag[0]:.2f} +/- {app_mag[1]:.2e}')
print(f'Absolute magnitude [mag] = {abs_mag[0]:.2f} +/- {abs_mag[1]:.2e}')
Apparent magnitude [mag]] = 6.50 +/- 8.70e-04
Absolute magnitude [mag] = 3.13 +/- 6.05e-03