In [1]:
import os

In [2]:
import numpy as np

In [3]:
from collections import Iterable

In [4]:
%matplotlib inline
import matplotlib.pyplot as plt
# import seaborn as sns
# sns.set()

In [5]:
import sncosmo

In [6]:
from astropy.table import Column

In [7]:
import transientSources.transients as ts
example_data =  os.path.join(os.path.split(ts.__file__)[0], 'example_data')

In [8]:
location = os.path.join(example_data, '2007uy/cfa_2007uy')

The representation of the astrophysical object in terms of the spectra provided is in TransientObject


In [9]:
help(ts.TransientObject)


Help on class TransientObject in module transientSources.transients:

class TransientObject(__builtin__.object)
 |  Methods defined here:
 |  
 |  __init__(self, name, spectral_data, matched_spectra, days, data_fnames=None, mangled_fnames=None)
 |      Constructor
 |      
 |      Parameters
 |      ----------
 |      name: string, mandatory
 |          name of the source, convention is author/compilation/code hyphen
 |          IAU name
 |      spectral_data: list of 2-D `np.ndarray` of floats
 |          list of all the spectral data available corresponding to the object.
 |          Each `np.ndarray` has columns of wavelength and specific flux in
 |          units of Ang and arbitrary units respectively
 |      matched_spectra: list of 2-D `np.ndarray` of floats 
 |          list of all of the 'mangled' spectral data matched to the
 |          photometry of the transient object, in the form of 2-D `np.ndarray`.
 |          The list of arrays is indexed identically to the `spectral data`.
 |          Each `np.ndarray` has columns wavelength in units of Ang, and
 |          specific flux ($f_\lambda$) in units of ergs/cm^2/s/Ang. It is
 |          assumed that the wavelengths are indexed in ascending order.
 |      days: `np.ndarray` of floats
 |          days on which the spectral_data were taken in the same.  
 |      
 |      Returns
 |      -------
 |      instance of TransientObject class
 |      
 |      .. note :: 1. the wavelength gridding of the `mangled` matched spectra
 |      should be identical, and may differ from the wavelength gridding of the
 |      spectral_data of the object from which it was constructed. 2. The
 |      wavelengths in the matched spectra are assumed to be in increasing
 |      order.
 |  
 |  getPhotometry(self, dataDir, filterList=[])
 |  
 |  setphasePeak(self, value)
 |      # @phasePeak.setter
 |  
 |  validateWavelengths(self, verbose=False)
 |      Check that the wavelength grid of the `self.matched_spectra
 |      are identical to each other and that the wavelengths are
 |      in increasing order
 |  
 |  ----------------------------------------------------------------------
 |  Class methods defined here:
 |  
 |  fromDataDir(cls, name, spectralDataDir) from __builtin__.type
 |  
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |  
 |  parse_mjds(mangledSpectraFileName)
 |  
 |  photometryTable(fname, filt, filterDict=None)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  SNCosmoSource
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  phasePeak


In [10]:
mysource = ts.TransientObject.fromDataDir(name='Karpenka-2007uy', spectralDataDir=location)

mysource is an instance of TransientObject that keeps track of how we built it. The attribute of mysource that is most interesting for our purpose is SNCosmoSource. This is the source which we will use to build SNCosmo Light curves. This is built from the restframe 'mangled' spectra provided by Natasha, and their phases which do not know where the 'peak' is. 2007uy is of Type Ib.


In [11]:
print mysource.SNCosmoSource


class      : TimeSeriesSource
name       : 'Karpenka-2007uy'
version    : None
phases     : [-9.46827, .., 78.913] days
wavelengths: [3454.82, .., 7369.95] Angstroms
parameters:
  amplitude = 1.0

In [12]:
peak = mysource.phasePeak
print peak


9.46826711652

Without being told what the peak phase is, TransientObject calculates the peak phase to be at the restFrame 'besselB' band peak. Note that in the above instance, the peak has been calculated to 9.47, and so the spectra which originally had a date of 0. now has a phase of -9.47.

However,one can also set this peak if they have prior knowledge


In [13]:
mysource.setphasePeak(3.)

In [14]:
print mysource.SNCosmoSource


class      : TimeSeriesSource
name       : 'Karpenka-2007uy'
version    : None
phases     : [-3, .., 85.3813] days
wavelengths: [3454.82, .., 7369.95] Angstroms
parameters:
  amplitude = 1.0

Note that now, the 0 phase is mapped to -3. in recognition of the fact that peak is at phase 3.0 of the supplied days.

Now we will set it back (assuming we don't know what the peak really is


In [15]:
mysource.setphasePeak(peak)

In [16]:
print mysource.SNCosmoSource


class      : TimeSeriesSource
name       : 'Karpenka-2007uy'
version    : None
phases     : [-9.46827, .., 78.913] days
wavelengths: [3454.82, .., 7369.95] Angstroms
parameters:
  amplitude = 1.0

We can examine the minimum and maximum rest frame phases and wavelength


In [17]:
mysource.SNCosmoSource.minphase(), mysource.SNCosmoSource.maxphase()


Out[17]:
(-9.4682671165156798, 78.91303288348432)

In [18]:
mysource.SNCosmoSource.minwave(), mysource.SNCosmoSource.maxwave()


Out[18]:
(3454.8200000000002, 7369.9499999999998)

This is one of the disadvantages related to single spectra (as opposed to ensembles) due to the lack of data (the rest of the notebook will show advantages of this approach in this regime of low data).

  • How do we use these models to simulate and fit SN at diffferent wavelengths and phases outside the range of the data?

Build SNCosmo Model


In [19]:
model = sncosmo.Model(source=mysource.SNCosmoSource)

In [20]:
print model


<Model at 0x108ac8110>
source:
  class      : TimeSeriesSource
  name       : 'Karpenka-2007uy'
  version    : None
  phases     : [-9.46827, .., 78.913] days
  wavelengths: [3454.82, .., 7369.95] Angstroms
parameters:
  z         = 0.0
  t0        = 0.0
  amplitude = 1.0

In [63]:
#Set parameters to match the photometry 
# t0 is a guess provided for the peak of the observed photometry.

In [21]:
t0 =54478
model.set(z=0.0007, t0=54478.)
earlymodel = sncosmo.Model(source=mysource.SNCosmoSource)
earlymodel.set(t0=54477., z=0.0007)

Look at the Model Spectra


In [22]:
waves = model._source._wave * (1.0 + 0.0007)

In [23]:
fluxes = model.flux(time=np.arange(-5.+ t0, 10. + t0), wave=waves)

In [24]:
fig = plt.figure()
ax1 = fig.add_subplot(221)
ax1.plot(waves, fluxes[0])
ax2 = fig.add_subplot(222)
for flux in fluxes:
    ax2.plot(waves, flux)


The SEDs are single object, and are therefore noisy. It is possible to smooth them, if necessary. The right figure shows a bunch of the spectra. This is easier to see in animation.


In [25]:
filtlist = ['bessellB', 'bessellV', 'bessellr', 'besselli']
filtfig, ax = plt.subplots()
for filt in filtlist:
    bp = sncosmo.get_bandpass(filt)
    ax.plot(bp.wave, bp.trans)
    ax.plot(waves, fluxes[0]*1e15)


The Bessell filters I am using and the spectra (multiplied by a constant to show on the figure). B, V are overlapped by the spectrum, i and r have support outside the wavlength range of spectrum, and therefore band fluxes cannot be computed.

Animation of the spectral series: comparison with other templates

Warning

Slowest section of the Notebook, and hence commented out, as nothing else depends on the output and I have added the output files for viewing. To run, uncomment the lines and run them but it might take a few minutes. Also you might need to have ffmpeg encoder

We will compare the evolution of the spectral series with Ia (SALT2) and other templates for Ib /Ibc. Nugent Template is a template for Type Ibc built from an ensemble of objects. The other three templates are the Nugent template matched to the photometry of these SN: 2005hl, 2005hm and 2006jo. These were first referenced in Sako, 2011, and the code to do this was probably made by Saurabh Jha.


In [69]:
# saltsource = sncosmo.Model(source='salt2')._source
# nugent_1bc = sncosmo.Model(source='nugent-sn1bc')._source
# sn_2005_hl = sncosmo.Model(source='s11-2005hl')._source
# sn_2005_hm = sncosmo.Model(source='s11-2005hm')._source
# sn_2006_jo = sncosmo.Model(source='s11-2006jo')._source

In [70]:
# test = sncosmo.animate_source([mysource.SNCosmoSource, saltsource, nugent_1bc, sn_2005_hl, sn_2005_hm, sn_2006_jo], 
#                              label=['Karpenka_2007_uy', 'salt2', 'NugentIbc', '2005_hl', '2005_hm', '2006_jo'],
#                              fname='Karpenka_comparison.mp4')

Compare photometry

Given the current directory structure, we have a getPhotometry() method, that loads the photometry into an astropy Table. This is illustrated below. Note that the fluxes are in units of ergs/cm^2/sec.


In [26]:
photometryDirectory = os.path.join(example_data, '2007uy/')
photometry = mysource.getPhotometry(photometryDirectory, filterList=['B', 'V', 'i', 'r'])

Here is a comparison of synthetic photometry photometry from SNCosmo's interpolated time series spectra, and the photometry. Note that SNCosmo does not calculate fluxes in ergs/cm^2/sec but in counts/cm^2/sec. So, the comparison is done with a branch of SNCosmo in https://github.com/rbiswas4/sncosmo/tree/CC_templates (27fbf6db25d1474ed1fb4cfbe7fd8ff680e9e4dc). So for the rest of this notebook to work you need to install this particular branch of SNCosmo.

There is a factor of 1000, that I have not managed to track down yet that is different between the photometry and the synthetic photometry flux values.

  • Fixed no longer a problem

In [27]:
def renorm_bp(band):
    
    b = sncosmo.get_bandpass(band)
    norm = np.sum(b.trans * b.dwave) 
    bp = sncosmo.Bandpass(wave=b.wave, trans=b.trans/norm)
    return bp

In [34]:
def _plotPhotometry(band, model=model):
    photometry = mysource.getPhotometry(photometryDirectory, filterList=[band])
    col = Column(name='ren', data=map(renorm_bp, np.asarray(photometry['band'])) )
    photometry.add_column(col)
    fig, ax = plt.subplots()
    # bp = sncosmo.get_bandpass()
    if isinstance(model, Iterable):
        for m in model:
            ax.plot(photometry['time'], m.bandflux(time=np.asarray(photometry['time']), band=np.asarray(photometry['ren'])))
    
    else:
        ax.plot(photometry['time'], model.bandflux(time=np.asarray(photometry['time']), band=np.asarray(photometry['ren'])), 'rs')
    
    ax.errorbar(photometry['time'], photometry['flux'], photometry['fluxerr'], fmt='k.')
    ax.grid(True)
    return fig

In [35]:
figB = _plotPhotometry('B', model=[model, earlymodel])



In [ ]:


In [30]:
figV= _plotPhotometry('V', model=[model, earlymodel])


The difference in the two bands between the red (photometry files) and the blue (synthetic photometry) from the SNCosmo source probably shows the problems with interpolation of the spectra. In the coming days, we will check a few different things which we will list in a document and issues in this repository.


In [180]:
t0


Out[180]:
54478

Overplotting Example for a single point


In [43]:
fig, ax = plt.subplots()
photometry = mysource.getPhotometry(photometryDirectory, filterList=['B'])
col = Column(name='ren', data=map(renorm_bp, np.asarray(photometry['band'])) )
photometry.add_column(col)
ax.plot(photometry['time'], model.bandflux(time=np.asarray(photometry['time']), band=np.asarray(photometry['ren'])))

# Code to plot the band flux at a single epoch of 54481.
ax.plot(54481., model.bandflux(time=54481., band=renorm_bp('BessellB')),'o')
ax.grid(True)


Compare Spectra

Let us look at the photometry comparison in B band a little more closely. Our first guess would be that the bivariate spline interpolation we used to go from the mangled spectra available at a discrete set of times (which we will refer to as the native grid of the model) to the spectra in between the times is the point of failure.

This should mean that the mismatch between the photometry points and the simulated points should be small if the photometry is at times close to the native grid points. the failure should also be bad beyond the minimum and maximum times corresponding to the native grid.

  • Find the set of times at which the observations were made in the B band light curve. Call this obsTimes
  • Find the set of times corresponding to the native grid of times. This should be easy to find by using the same peak that was used in the model and the redshift. call this nativeTimes
  • Show native times in the figure where we have photometry

In [181]:
obsTimes = photometry[photometry['band'] == 'bessellB']['time']

In [182]:
nativeTimes = mysource.SNCosmoSource._phase * (1.0 + model.get('z')) + model.get('t0')
earlyNativeTimes = mysource.SNCosmoSource._phase * (1.0 + earlymodel.get('z')) + earlymodel.get('t0')

In [183]:
ax = figB.axes[0]
map(lambda x: ax.axvline(x, linestyle='dashed', lw =2.), nativeTimes)


Out[183]:
[<matplotlib.lines.Line2D at 0x10cb73150>,
 <matplotlib.lines.Line2D at 0x10c92fbd0>,
 <matplotlib.lines.Line2D at 0x10c8965d0>,
 <matplotlib.lines.Line2D at 0x10c929850>,
 <matplotlib.lines.Line2D at 0x10c92fb90>,
 <matplotlib.lines.Line2D at 0x109d3e610>,
 <matplotlib.lines.Line2D at 0x10c8fbd10>,
 <matplotlib.lines.Line2D at 0x10df3dc10>,
 <matplotlib.lines.Line2D at 0x10d9b1810>]

In [184]:
map(lambda x: ax.axvline(x, linestyle='dashed', color='g', lw =2.), earlyNativeTimes)


Out[184]:
[<matplotlib.lines.Line2D at 0x10cb61150>,
 <matplotlib.lines.Line2D at 0x10d9a87d0>,
 <matplotlib.lines.Line2D at 0x10cdfdd50>,
 <matplotlib.lines.Line2D at 0x10d95e510>,
 <matplotlib.lines.Line2D at 0x10d95e190>,
 <matplotlib.lines.Line2D at 0x10d9695d0>,
 <matplotlib.lines.Line2D at 0x10d969a90>,
 <matplotlib.lines.Line2D at 0x10c9057d0>,
 <matplotlib.lines.Line2D at 0x10c593810>]

In [185]:
figB.set_size_inches(12, 8)

In [186]:
ax = figB.axes[0]
ax.set_xlim(54460,54520 )


Out[186]:
(54460, 54520)

In [187]:
figB


Out[187]:

This figure shows the photometry (black points with errors), and two curves with the same model except the peak position is one day apart. The vertical lines map the native phases of the model source to this plot.

So, this is quite strange we can see that there are points with appreciable difference with the photometry even though they are sampled at the same times. If the blue (t0 = 544 There are two possibilities that could explain this:

  1. The t0 information is not correct. But simce it is translationally invariant, I am not sure it can cause this. The green curve has a peak time one day early.
  2. The sources do not use the mangled spectra correctly. ie. source.flux(phase=phase_val) is not the same as the mangled spectrum corresponding to phase=phase_val.

In [213]:
figV.set_size_inches(12,8)

In [214]:
ax = figV.axes[0]
map(lambda x: ax.axvline(x, linestyle='dashed', lw =2., color='b'), nativeTimes)
map(lambda x: ax.axvline(x, linestyle='dashed', lw =2., color='g'), earlyNativeTimes)


Out[214]:
[<matplotlib.lines.Line2D at 0x10db30350>,
 <matplotlib.lines.Line2D at 0x10e44c290>,
 <matplotlib.lines.Line2D at 0x10e442350>,
 <matplotlib.lines.Line2D at 0x10e442f50>,
 <matplotlib.lines.Line2D at 0x10e442090>,
 <matplotlib.lines.Line2D at 0x10c93b650>,
 <matplotlib.lines.Line2D at 0x10d9b9990>,
 <matplotlib.lines.Line2D at 0x10e5cd290>,
 <matplotlib.lines.Line2D at 0x10d9322d0>]

The comparison seems better for V band.


In [215]:
figV


Out[215]:

In [188]:
from utils import plotutils as pu

In [203]:
def _comparetonative(mysource):
    _phases = mysource.SNCosmoSource._phase
    _wave = mysource.SNCosmoSource._wave
    fig, ax0, ax1 = pu.settwopanel(setdifflimits=(0.05, 2.0))
    for i, _phase in enumerate(_phases):
        ax0.plot(_wave, mysource.SNCosmoSource.flux(phase=_phase, wave=_wave))
        ax0.plot(_wave, mysource.mangled_spectra[i][:, 1], ls = 'dashed')
        ax1.plot(_wave, mysource.SNCosmoSource.flux(phase=_phase, wave=_wave) / mysource.mangled_spectra[i][:, 1])

In [205]:
_comparetonative(mysource)