Coverage for python/lsst/sims/maf/utils/snNSNUtils.py : 11%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from lsst.utils import getPackageDir
2from lsst.sims.photUtils import SignalToNoise
3from lsst.sims.photUtils import PhotometricParameters
4from lsst.sims.photUtils import Bandpass, Sed
6import numpy as np
7from scipy.constants import *
8from functools import wraps
9import os
10import h5py
11import multiprocessing
12from astropy.table import Table
13import pandas as pd
14from scipy import interpolate
15from scipy.interpolate import RegularGridInterpolator
16from astropy.cosmology import FlatLambdaCDM
18STERADIAN2SQDEG = 180.**2 / np.pi**2
19# Mpc^3 -> Mpc^3/sr
20norm = 1. / (4. * np.pi)
22__all__ = ['LCfast', 'Throughputs', 'Telescope',
23 'Load_Reference', 'GetReference', 'SN_Rate', 'CovColor']
26class LCfast:
27 """
28 class to simulate supernovae light curves in a fast way
29 The method relies on templates and broadcasting to increase speed
30 Parameters
31 ---------------
32 reference_lc:
33 x1: float
34 SN stretch
35 color: float
36 SN color
37 telescope: Telescope()
38 telescope for the study
39 mjdCol: str, opt
40 name of the MJD col in data to simulate (default: observationStartMJD)
41 RACol: str, opt
42 name of the RA col in data to simulate (default: fieldRA)
43 DecCol: str, opt
44 name of the Dec col in data to simulate (default: fieldDec)
45 filterCol: str, opt
46 name of the filter col in data to simulate (default: filter)
47 exptimeCol: str, opt
48 name of the exposure time col in data to simulate (default: visitExposureTime)
49 m5Col: str, opt
50 name of the fiveSigmaDepth col in data to simulate (default: fiveSigmaDepth)
51 seasonCol: str, opt
52 name of the season col in data to simulate (default: season)
53 snr_min: float, opt
54 minimal Signal-to-Noise Ratio to apply on LC points (default: 5)
55 """
57 def __init__(self, reference_lc, x1, color,
58 telescope, mjdCol='observationStartMJD',
59 RACol='fieldRA', DecCol='fieldDec',
60 filterCol='filter', exptimeCol='visitExposureTime',
61 m5Col='fiveSigmaDepth', seasonCol='season',
62 nexpCol='numExposures',
63 snr_min=5.):
65 # grab all vals
66 self.RACol = RACol
67 self.DecCol = DecCol
68 self.filterCol = filterCol
69 self.mjdCol = mjdCol
70 self.m5Col = m5Col
71 self.exptimeCol = exptimeCol
72 self.seasonCol = seasonCol
73 self.nexpCol = nexpCol
74 self.x1 = x1
75 self.color = color
77 # Loading reference file
78 self.reference_lc = reference_lc
80 self.telescope = telescope
82 # This cutoffs are used to select observations:
83 # phase = (mjd - DayMax)/(1.+z)
84 # selection: min_rf_phase < phase < max_rf_phase
85 # and blue_cutoff < mean_rest_frame < red_cutoff
86 # where mean_rest_frame = telescope.mean_wavelength/(1.+z)
87 self.blue_cutoff = 380.
88 self.red_cutoff = 800.
90 # SN parameters for Fisher matrix estimation
91 self.param_Fisher = ['x0', 'x1', 'daymax', 'color']
93 self.snr_min = snr_min
95 # getting the telescope zp
96 self.zp = {}
97 for b in 'ugrizy':
98 self.zp[b] = telescope.zp(b)
101 def __call__(self, obs, gen_par=None, bands='grizy'):
102 """ Simulation of the light curve
104 Parameters
105 ----------------
106 obs: array
107 array of observations
108 gen_par: array, opt
109 simulation parameters (default: None)
110 bands: str, opt
111 filters to consider for simulation (default: grizy)
112 Returns
113 ------------
114 astropy table with:
115 columns: band, flux, fluxerr, snr_m5,flux_e,zp,zpsys,time
116 metadata : SNID,RA,Dec,DayMax,X1,Color,z
117 """
119 if len(obs) == 0:
120 return None
122 tab_tot = pd.DataFrame()
124 # multiprocessing here: one process (processBand) per band
126 for band in bands:
127 idx = obs[self.filterCol] == band
128 # print('multiproc',band,j,len(obs[idx]))
129 if len(obs[idx]) > 0:
130 res = self.processBand(obs[idx], band, gen_par)
131 tab_tot = tab_tot.append(res, ignore_index=True)
133 # return produced LC
134 return tab_tot
137 def processBand(self, sel_obs, band, gen_par, j=-1, output_q=None):
138 """ LC simulation of a set of obs corresponding to a band
139 The idea is to use python broadcasting so as to estimate
140 all the requested values (flux, flux error, Fisher components, ...)
141 in a single path (i.e no loop!)
142 Parameters
143 ---------------
144 sel_obs: array
145 array of observations
146 band: str
147 band of observations
148 gen_par: array
149 simulation parameters
150 j: int, opt
151 index for multiprocessing (default: -1)
152 output_q: multiprocessing.Queue(),opt
153 queue for multiprocessing (default: None)
154 Returns
155 -------
156 astropy table with fields corresponding to LC components
157 """
159 # method used for interpolation
160 method = 'linear'
161 interpType = 'regular'
163 # if there are no observations in this filter: return None
164 if len(sel_obs) == 0:
165 if output_q is not None:
166 output_q.put({j: None})
167 else:
168 return None
170 # Get the fluxes (from griddata reference)
172 # xi = MJD-T0
173 xi = sel_obs[self.mjdCol]-gen_par['daymax'][:, np.newaxis]
175 # yi = redshift simulated values
176 # requested to avoid interpolation problems near boundaries
177 yi = np.round(gen_par['z'], 4)
178 # yi = gen_par['z']
180 # p = phases of LC points = xi/(1.+z)
181 p = xi/(1.+yi[:, np.newaxis])
182 yi_arr = np.ones_like(p)*yi[:, np.newaxis]
184 if interpType == 'regular':
186 pts = (p, yi_arr)
187 fluxes_obs = self.reference_lc.flux[band](pts)
188 fluxes_obs_err = self.reference_lc.fluxerr[band](pts)
190 # Fisher components estimation
192 dFlux = {}
194 # loop on Fisher parameters
195 for val in self.param_Fisher:
196 dFlux[val] = self.reference_lc.param[band][val](pts)
197 # get the reference components
198 # z_c = self.reference_lc.lc_ref[band]['d'+val]
199 # get Fisher components from interpolation
200 # dFlux[val] = griddata((x, y), z_c, (p, yi_arr),
201 # method=method, fill_value=0.)
203 # replace crazy fluxes by dummy values
204 fluxes_obs[fluxes_obs <= 0.] = 1.e-10
205 fluxes_obs_err[fluxes_obs_err <= 0.] = 1.e-10
207 # Fisher matrix components estimation
208 # loop on SN parameters (x0,x1,color)
209 # estimate: dF/dxi*dF/dxj/sigma_flux**2
210 Derivative_for_Fisher = {}
211 for ia, vala in enumerate(self.param_Fisher):
212 for jb, valb in enumerate(self.param_Fisher):
213 if jb >= ia:
214 Derivative_for_Fisher[vala +
215 valb] = dFlux[vala] * dFlux[valb]
217 # remove LC points outside the restframe phase range
218 min_rf_phase = gen_par['min_rf_phase'][:, np.newaxis]
219 max_rf_phase = gen_par['max_rf_phase'][:, np.newaxis]
220 flag = (p >= min_rf_phase) & (p <= max_rf_phase)
222 # remove LC points outside the (blue-red) range
223 mean_restframe_wavelength = np.array(
224 [self.telescope.mean_wavelength[band]]*len(sel_obs))
225 mean_restframe_wavelength = np.tile(
226 mean_restframe_wavelength, (len(gen_par), 1))/(1.+gen_par['z'][:, np.newaxis])
227 flag &= (mean_restframe_wavelength > self.blue_cutoff) & (
228 mean_restframe_wavelength < self.red_cutoff)
230 flag_idx = np.argwhere(flag)
232 # Correct fluxes_err (m5 in generation probably different from m5 obs)
234 # gamma_obs = self.telescope.gamma(
235 # sel_obs[self.m5Col], [band]*len(sel_obs), sel_obs[self.exptimeCol])
237 gamma_obs = self.reference_lc.gamma[band](
238 (sel_obs[self.m5Col], sel_obs[self.exptimeCol]/sel_obs[self.nexpCol], sel_obs[self.nexpCol]))
240 mag_obs = -2.5*np.log10(fluxes_obs/3631.)
242 m5 = np.asarray([self.reference_lc.m5_ref[band]]*len(sel_obs))
244 gammaref = np.asarray([self.reference_lc.gamma_ref[band]]*len(sel_obs))
246 m5_tile = np.tile(m5, (len(p), 1))
248 srand_ref = self.srand(
249 np.tile(gammaref, (len(p), 1)), mag_obs, m5_tile)
251 srand_obs = self.srand(np.tile(gamma_obs, (len(p), 1)), mag_obs, np.tile(
252 sel_obs[self.m5Col], (len(p), 1)))
254 correct_m5 = srand_ref/srand_obs
256 """
257 print(band, gammaref, gamma_obs, m5,
258 sel_obs[self.m5Col], sel_obs[self.exptimeCol])
259 """
260 fluxes_obs_err = fluxes_obs_err/correct_m5
262 # now apply the flag to select LC points
263 fluxes = np.ma.array(fluxes_obs, mask=~flag)
264 fluxes_err = np.ma.array(fluxes_obs_err, mask=~flag)
265 phases = np.ma.array(p, mask=~flag)
266 snr_m5 = np.ma.array(fluxes_obs/fluxes_obs_err, mask=~flag)
268 nvals = len(phases)
270 obs_time = np.ma.array(
271 np.tile(sel_obs[self.mjdCol], (nvals, 1)), mask=~flag)
272 seasons = np.ma.array(
273 np.tile(sel_obs[self.seasonCol], (nvals, 1)), mask=~flag)
275 z_vals = gen_par['z'][flag_idx[:, 0]]
276 daymax_vals = gen_par['daymax'][flag_idx[:, 0]]
277 mag_obs = np.ma.array(mag_obs, mask=~flag)
278 Fisher_Mat = {}
279 for key, vals in Derivative_for_Fisher.items():
280 Fisher_Mat[key] = np.ma.array(vals, mask=~flag)
282 # Store in a panda dataframe
283 lc = pd.DataFrame()
285 ndata = len(fluxes_err[~fluxes_err.mask])
287 if ndata > 0:
289 lc['flux'] = fluxes[~fluxes.mask]
290 lc['fluxerr'] = fluxes_err[~fluxes_err.mask]
291 lc['phase'] = phases[~phases.mask]
292 lc['snr_m5'] = snr_m5[~snr_m5.mask]
293 lc['time'] = obs_time[~obs_time.mask]
294 lc['mag'] = mag_obs[~mag_obs.mask]
295 lc['band'] = ['LSST::'+band]*len(lc)
296 lc.loc[:, 'zp'] = self.zp[band]
297 lc['season'] = seasons[~seasons.mask]
298 lc['season'] = lc['season'].astype(int)
299 lc['z'] = z_vals
300 lc['daymax'] = daymax_vals
301 for key, vals in Fisher_Mat.items():
302 lc.loc[:, 'F_{}'.format(
303 key)] = vals[~vals.mask]/(lc['fluxerr'].values**2)
304 # lc.loc[:, 'F_{}'.format(key)] = 999.
305 lc.loc[:, 'x1'] = self.x1
306 lc.loc[:, 'color'] = self.color
308 lc.loc[:, 'n_aft'] = (np.sign(lc['phase']) == 1) & (
309 lc['snr_m5'] >= self.snr_min)
310 lc.loc[:, 'n_bef'] = (np.sign(lc['phase'])
311 == -1) & (lc['snr_m5'] >= self.snr_min)
313 lc.loc[:, 'n_phmin'] = (lc['phase'] <= -5.)
314 lc.loc[:, 'n_phmax'] = (lc['phase'] >= 20)
316 # transform boolean to int because of some problems in the sum()
318 for colname in ['n_aft', 'n_bef', 'n_phmin', 'n_phmax']:
319 lc.loc[:, colname] = lc[colname].astype(int)
321 """
322 idb = (lc['z'] > 0.65) & (lc['z'] < 0.9)
323 print(lc[idb][['z', 'ratio', 'm5', 'flux_e_sec', 'snr_m5']])
324 """
325 if output_q is not None:
326 output_q.put({j: lc})
327 else:
328 return lc
330 def srand(self, gamma, mag, m5):
331 """
332 Method to estimate :math:`srand=\sqrt((0.04-\gamma)*x+\gamma*x^2)`
333 with :math:`x = 10^{0.4*(m-m_5)}`
335 Parameters
336 -----------
337 gamma: float
338 gamma value
339 mag: float
340 magnitude
341 m5: float
342 fiveSigmaDepth value
344 Returns
345 -------
346 srand = np.sqrt((0.04-gamma)*x+gamma*x**2)
347 with x = 10**(0.4*(mag-m5))
348 """
350 x = 10**(0.4*(mag-m5))
351 return np.sqrt((0.04-gamma)*x+gamma*x**2)
354class Throughputs(object):
355 """ class to handle instrument throughput
356 Parameters
357 -------------
358 through_dir : str, opt
359 throughput directory. If None, uses $THROUGHPUTS_DIR/baseline
360 atmos_dir : str, opt
361 directory of atmos files. If None, uses $THROUGHPUTS_DIR
362 telescope_files : list(str),opt
363 list of of throughput files
364 Default : ['detector.dat', 'lens1.dat','lens2.dat',
365 'lens3.dat','m1.dat', 'm2.dat', 'm3.dat']
366 filterlist: list(str), opt
367 list of filters to consider
368 Default : 'ugrizy'
369 wave_min : float, opt
370 min wavelength for throughput
371 Default : 300
372 wave_max : float, opt
373 max wavelength for throughput
374 Default : 1150
375 atmos : bool, opt
376 to include atmosphere affects
377 Default : True
378 aerosol : bool, opt
379 to include aerosol effects
380 Default : True
381 Returns
382 ---------
383 Accessible throughputs (per band):
384 lsst_system: system throughput (lens+mirrors+filters)
385 lsst_atmos: lsst_system+atmosphere
386 lsst_atmos_aerosol: lsst_system+atmosphere+aerosol
388 Note: I would like to see this replaced by a class in sims_photUtils instead. This does not belong in MAF.
389 """
391 def __init__(self, **kwargs):
393 params = {}
394 params['through_dir'] = os.path.join(getPackageDir('throughputs'), 'baseline')
395 params['atmos_dir'] = os.path.join(getPackageDir('throughputs'), 'atmos')
396 params['atmos'] = True
397 params['aerosol'] = True
398 params['telescope_files'] = ['detector.dat', 'lens1.dat',
399 'lens2.dat', 'lens3.dat',
400 'm1.dat', 'm2.dat', 'm3.dat']
401 params['filterlist'] = 'ugrizy'
402 params['wave_min'] = 300.
403 params['wave_max'] = 1150.
404 # This lets a user override the atmosphere and throughputs directories.
405 for par in ['through_dir', 'atmos_dir', 'atmos', 'aerosol',
406 'telescope_files', 'filterlist', 'wave_min', 'wave_max']:
407 if par in kwargs.keys():
408 params[par] = kwargs[par]
410 self.atmosDir = params['atmos_dir']
411 self.throughputsDir = params['through_dir']
413 self.telescope_files = params['telescope_files']
414 self.filter_files = ['filter_'+f+'.dat' for f in params['filterlist']]
415 if 'filter_files' in kwargs.keys():
416 self.filter_files = kwargs['filter_files']
417 self.wave_min = params['wave_min']
418 self.wave_max = params['wave_max']
420 self.filterlist = params['filterlist']
421 self.filtercolors = {'u': 'b', 'g': 'c',
422 'r': 'g', 'i': 'y', 'z': 'r', 'y': 'm'}
424 self.lsst_std = {}
425 self.lsst_system = {}
426 self.mean_wavelength = {}
427 self.lsst_detector = {}
428 self.lsst_atmos = {}
429 self.lsst_atmos_aerosol = {}
430 self.airmass = -1.
431 self.aerosol_b = params['aerosol']
432 self.Load_System()
433 self.Load_DarkSky()
435 if params['atmos']:
436 self.Load_Atmosphere()
437 else:
438 for f in self.filterlist:
439 self.lsst_atmos[f] = self.lsst_system[f]
440 self.lsst_atmos_aerosol[f] = self.lsst_system[f]
441 self.Mean_Wave()
443 @property
444 def system(self):
445 return self.lsst_system
447 @property
448 def telescope(self):
449 return self.lsst_telescope
451 @property
452 def atmosphere(self):
453 return self.lsst_atmos
455 @property
456 def aerosol(self):
457 return self.lsst_atmos_aerosol
459 def Load_System(self):
460 """ Load files required to estimate throughputs
461 """
463 for f in self.filterlist:
464 self.lsst_std[f] = Bandpass()
465 self.lsst_system[f] = Bandpass()
467 if len(self.telescope_files) > 0:
468 index = [i for i, x in enumerate(
469 self.filter_files) if f+'.dat' in x]
470 telfiles = self.telescope_files+[self.filter_files[index[0]]]
471 else:
472 telfiles = self.filter_files
473 self.lsst_system[f].readThroughputList(telfiles,
474 rootDir=self.throughputsDir,
475 wavelen_min=self.wave_min,
476 wavelen_max=self.wave_max)
478 def Load_DarkSky(self):
479 """ Load DarkSky
480 """
481 self.darksky = Sed()
482 self.darksky.readSED_flambda(os.path.join(
483 self.throughputsDir, 'darksky.dat'))
485 def Load_Atmosphere(self, airmass=1.2):
486 """ Load atmosphere files
487 and convolve with transmissions
488 Parameters
489 --------------
490 airmass : float,opt
491 airmass value
492 Default : 1.2
493 """
494 self.airmass = airmass
495 if self.airmass > 0.:
496 atmosphere = Bandpass()
497 path_atmos = os.path.join(
498 self.atmosDir, 'atmos_%d.dat' % (self.airmass*10))
499 if os.path.exists(path_atmos):
500 atmosphere.readThroughput(os.path.join(
501 self.atmosDir, 'atmos_%d.dat' % (self.airmass*10)))
502 else:
503 atmosphere.readThroughput(
504 os.path.join(self.atmosDir, 'atmos.dat'))
505 self.atmos = Bandpass(wavelen=atmosphere.wavelen, sb=atmosphere.sb)
507 for f in self.filterlist:
508 wavelen, sb = self.lsst_system[f].multiplyThroughputs(
509 atmosphere.wavelen, atmosphere.sb)
510 self.lsst_atmos[f] = Bandpass(wavelen=wavelen, sb=sb)
512 if self.aerosol_b:
513 atmosphere_aero = Bandpass()
514 atmosphere_aero.readThroughput(os.path.join(
515 self.atmosDir, 'atmos_%d_aerosol.dat' % (self.airmass*10)))
516 self.atmos_aerosol = Bandpass(
517 wavelen=atmosphere_aero.wavelen, sb=atmosphere_aero.sb)
519 for f in self.filterlist:
520 wavelen, sb = self.lsst_system[f].multiplyThroughputs(
521 atmosphere_aero.wavelen, atmosphere_aero.sb)
522 self.lsst_atmos_aerosol[f] = Bandpass(
523 wavelen=wavelen, sb=sb)
524 else:
525 for f in self.filterlist:
526 self.lsst_atmos[f] = self.lsst_system[f]
527 self.lsst_atmos_aerosol[f] = self.lsst_system[f]
529 def Mean_Wave(self):
530 """ Estimate mean wave
531 """
532 for band in self.filterlist:
533 self.mean_wavelength[band] = np.sum(
534 self.lsst_atmos[band].wavelen*self.lsst_atmos[band].sb)\
535 / np.sum(self.lsst_atmos[band].sb)
537# decorator to access parameters of the class
540def get_val_decor(func):
541 @wraps(func)
542 def func_deco(theclass, what, xlist):
543 for x in xlist:
544 if x not in theclass.data[what].keys():
545 func(theclass, what, x)
546 return func_deco
549class Telescope(Throughputs):
550 """ Telescope class
551 inherits from Throughputs
552 estimate quantities defined in LSE-40
553 The following quantities are accessible:
554 mag_sky: sky magnitude
555 m5: 5-sigma depth
556 Sigmab: see eq. (36) of LSE-40
557 zp: see eq. (43) of LSE-40
558 counts_zp:
559 Skyb: see eq. (40) of LSE-40
560 flux_sky:
561 Parameters
562 -------------
563 through_dir : str, opt
564 throughput directory
565 Default : LSST_THROUGHPUTS_BASELINE
566 atmos_dir : str, opt
567 directory of atmos files
568 Default : THROUGHPUTS_DIR
569 telescope_files : list(str),opt
570 list of of throughput files
571 Default : ['detector.dat', 'lens1.dat','lens2.dat',
572 'lens3.dat','m1.dat', 'm2.dat', 'm3.dat']
573 filterlist: list(str), opt
574 list of filters to consider
575 Default : 'ugrizy'
576 wave_min : float, opt
577 min wavelength for throughput
578 Default : 300
579 wave_max : float, opt
580 max wavelength for throughput
581 Default : 1150
582 atmos : bool, opt
583 to include atmosphere affects
584 Default : True
585 aerosol : bool, opt
586 to include aerosol effects
587 Default : True
588 airmass : float, opt
589 airmass value
590 Default : 1.
591 Returns
592 ---------
593 Accessible throughputs (per band, from Throughput class):
594 lsst_system: system throughput (lens+mirrors+filters)
595 lsst_atmos: lsst_system+atmosphere
596 lsst_atmos_aerosol: lsst_system+atmosphere+aerosol
598 Note: I would like to see this replaced by a class in sims_photUtils instead. This does not belong in MAF.
599 """
601 def __init__(self, name='unknown', airmass=1., **kwargs):
603 self.name = name
604 super().__init__(**kwargs)
606 params = ['mag_sky', 'm5', 'FWHMeff', 'Tb',
607 'Sigmab', 'zp', 'counts_zp', 'Skyb', 'flux_sky']
609 self.data = {}
610 for par in params:
611 self.data[par] = {}
613 self.data['FWHMeff'] = dict(
614 zip('ugrizy', [0.92, 0.87, 0.83, 0.80, 0.78, 0.76]))
616 # self.atmos = atmos
618 self.Load_Atmosphere(airmass)
620 @get_val_decor
621 def get(self, what, band):
622 """
623 Decorator to access quantities
624 Parameters
625 ---------------
626 what: str
627 parameter to estimate
628 band: str
629 filter
630 """
631 filter_trans = self.system[band]
632 wavelen_min, wavelen_max, wavelen_step = filter_trans.getWavelenLimits(
633 None, None, None)
635 bandpass = Bandpass(wavelen=filter_trans.wavelen, sb=filter_trans.sb)
637 flatSedb = Sed()
638 flatSedb.setFlatSED(wavelen_min, wavelen_max, wavelen_step)
639 flux0b = np.power(10., -0.4*self.mag_sky(band))
640 flatSedb.multiplyFluxNorm(flux0b)
641 photParams = PhotometricParameters(bandpass=band)
642 norm = photParams.platescale**2/2.*photParams.exptime/photParams.gain
643 trans = filter_trans
645 if self.atmos:
646 trans = self.atmosphere[band]
647 self.data['m5'][band] = SignalToNoise.calcM5(
648 flatSedb, trans, filter_trans,
649 photParams=photParams,
650 FWHMeff=self.FWHMeff(band))
651 adu_int = flatSedb.calcADU(bandpass=trans, photParams=photParams)
652 self.data['flux_sky'][band] = adu_int*norm
654 @get_val_decor
655 def get_inputs(self, what, band):
656 """
657 decorator to access Tb, Sigmab, mag_sky
658 Parameters
659 ---------------
660 what: str
661 parameter to estimate
662 band: str
663 filter
664 """
665 myup = self.Calc_Integ_Sed(self.darksky, self.system[band])
666 self.data['Tb'][band] = self.Calc_Integ(self.atmosphere[band])
667 self.data['Sigmab'][band] = self.Calc_Integ(self.system[band])
668 self.data['mag_sky'][band] = -2.5 * \
669 np.log10(myup/(3631.*self.Sigmab(band)))
671 @get_val_decor
672 def get_zp(self, what, band):
673 """
674 decorator get zero points
675 formula used here are extracted from LSE-40
676 Parameters
677 ---------------
678 what: str
679 parameter to estimate
680 band: str
681 filter
682 """
683 photParams = PhotometricParameters(bandpass=band)
684 Diameter = 2.*np.sqrt(photParams.effarea*1.e-4 /
685 np.pi) # diameter in meter
686 Cte = 3631.*np.pi*Diameter**2*2.*photParams.exptime/4/h/1.e36
688 self.data['Skyb'][band] = Cte*np.power(Diameter/6.5, 2.)\
689 * np.power(2.*photParams.exptime/30., 2.)\
690 * np.power(photParams.platescale, 2.)\
691 * 10.**0.4*(25.-self.mag_sky(band))\
692 * self.Sigmab(band)
694 Zb = 181.8*np.power(Diameter/6.5, 2.)*self.Tb(band)
695 mbZ = 25.+2.5*np.log10(Zb)
696 filtre_trans = self.system[band]
697 wavelen_min, wavelen_max, wavelen_step = filtre_trans.getWavelenLimits(
698 None, None, None)
699 bandpass = Bandpass(wavelen=filtre_trans.wavelen, sb=filtre_trans.sb)
700 flatSed = Sed()
701 flatSed.setFlatSED(wavelen_min, wavelen_max, wavelen_step)
702 flux0 = np.power(10., -0.4*mbZ)
703 flatSed.multiplyFluxNorm(flux0)
704 photParams = PhotometricParameters(bandpass=band)
705 # number of counts for exptime
706 counts = flatSed.calcADU(bandpass, photParams=photParams)
707 self.data['zp'][band] = mbZ
708 self.data['counts_zp'][band] = counts/2.*photParams.exptime
710 def return_value(self, what, band):
711 """
712 accessor
713 Parameters
714 ---------------
715 what: str
716 parameter to estimate
717 band: str
718 filter
719 """
720 if len(band) > 1:
721 return self.data[what]
722 else:
723 return self.data[what][band]
725 def m5(self, filtre):
726 """m5 accessor
727 """
728 self.get('m5', filtre)
729 return self.return_value('m5', filtre)
731 def Tb(self, filtre):
732 """Tb accessor
733 """
734 self.get_inputs('Tb', filtre)
735 return self.return_value('Tb', filtre)
737 def mag_sky(self, filtre):
738 """mag_sky accessor
739 """
740 self.get_inputs('mag_sky', filtre)
741 return self.return_value('mag_sky', filtre)
743 def Sigmab(self, filtre):
744 """
745 Sigmab accessor
746 Parameters
747 ----------------
748 band: str
749 filter
750 """
751 self.get_inputs('Sigmab', filtre)
752 return self.return_value('Sigmab', filtre)
754 def zp(self, filtre):
755 """
756 zp accessor
757 Parameters
758 ----------------
759 band: str
760 filter
761 """
762 self.get_zp('zp', filtre)
763 return self.return_value('zp', filtre)
765 def FWHMeff(self, filtre):
766 """
767 FWHMeff accessor
768 Parameters
769 ----------------
770 band: str
771 filter
772 """
773 return self.return_value('FWHMeff', filtre)
775 def Calc_Integ(self, bandpass):
776 """
777 integration over bandpass
778 Parameters
779 --------------
780 bandpass : float
781 Returns
782 ---------
783 integration
784 """
785 resu = 0.
786 dlam = 0
787 for i, wave in enumerate(bandpass.wavelen):
788 if i < len(bandpass.wavelen)-1:
789 dlam = bandpass.wavelen[i+1]-wave
790 resu += dlam*bandpass.sb[i]/wave
791 # resu+=dlam*bandpass.sb[i]
793 return resu
795 def Calc_Integ_Sed(self, sed, bandpass, wavelen=None, fnu=None):
796 """
797 SED integration
798 Parameters
799 --------------
800 sed : float
801 sed to integrate
802 bandpass : float
803 bandpass
804 wavelength : float, opt
805 wavelength values
806 Default : None
807 fnu : float, opt
808 fnu values
809 Default : None
810 Returns
811 ----------
812 integrated sed over the bandpass
813 """
814 use_self = sed._checkUseSelf(wavelen, fnu)
815 # Use self values if desired, otherwise use values passed to function.
816 if use_self:
817 # Calculate fnu if required.
818 if sed.fnu is None:
819 # If fnu not present, calculate. (does not regrid).
820 sed.flambdaTofnu()
821 wavelen = sed.wavelen
822 fnu = sed.fnu
823 # Make sure wavelen/fnu are on the same wavelength grid as bandpass.
824 wavelen, fnu = sed.resampleSED(
825 wavelen, fnu, wavelen_match=bandpass.wavelen)
827 # Calculate the number of photons.
828 nphoton = (fnu / wavelen * bandpass.sb).sum()
829 dlambda = wavelen[1] - wavelen[0]
830 return nphoton * dlambda
832 def flux_to_mag(self, flux, band, zp=None):
833 """
834 Flux to magnitude conversion
835 Parameters
836 --------------
837 flux : float
838 input fluxes
839 band : str
840 input band
841 zp : float, opt
842 zeropoints
843 Default : None
844 Returns
845 ---------
846 magnitudes
847 """
848 if zp is None:
849 zp = self.zero_points(band)
850 # print 'zp',zp,band
851 m = -2.5 * np.log10(flux) + zp
852 return m
854 def mag_to_flux(self, mag, band, zp=None):
855 """
856 Magnitude to flux conversion
857 Parameters
858 --------------
859 mag : float
860 input mags
861 band : str
862 input band
863 zp : float, opt
864 zeropoints
865 Default : None
866 Returns
867 ---------
868 fluxes
869 """
870 if zp is None:
871 zp = self.zero_points(band)
872 return np.power(10., -0.4 * (mag-zp))
874 def zero_points(self, band):
875 """
876 Zero points estimation
877 Parameters
878 --------------
879 band : list(str)
880 list of bands
881 Returns
882 ---------
883 array of zp
884 """
885 return np.asarray([self.zp[b] for b in band])
887 def mag_to_flux_e_sec(self, mag, band, exptime):
888 """
889 Mag to flux (in photoelec/sec) conversion
890 Parameters
891 --------------
892 mag : float
893 input magnitudes
894 band : str
895 input bands
896 exptime : float
897 input exposure times
898 Returns
899 ----------
900 counts : float
901 number of ADU counts
902 e_per_sec : float
903 flux in photoelectron per sec.
904 """
905 if not hasattr(mag, '__iter__'):
906 wavelen_min, wavelen_max, wavelen_step = self.atmosphere[band].getWavelenLimits(
907 None, None, None)
908 sed = Sed()
909 sed.setFlatSED()
910 flux0 = 3631.*10**(-0.4*mag) # flux in Jy
911 flux0 = sed.calcFluxNorm(mag, self.atmosphere[band])
912 sed.multiplyFluxNorm(flux0)
913 photParams = PhotometricParameters(nexp=exptime/15.)
914 counts = sed.calcADU(
915 bandpass=self.atmosphere[band], photParams=photParams)
916 e_per_sec = counts
917 e_per_sec /= exptime/photParams.gain
918 # print('hello',photParams.gain,exptime)
919 return counts, e_per_sec
920 else:
921 return np.asarray([self.mag_to_flux_e_sec(m, b, expt) for m, b, expt in zip(mag, band, exptime)])
923 def gamma(self, mag, band, exptime):
924 """
925 gamma parameter estimation
926 cf eq(5) of the paper LSST : from science drivers to reference design and anticipated data products
927 with sigma_rand = 0.2 and m=m5
928 Parameters
929 --------------
930 mag : float
931 magnitudes
932 band : str
933 band
934 exptime : float
935 exposure time
936 Returns
937 ----------
938 gamma (float)
939 """
941 if not hasattr(mag, '__iter__'):
942 photParams = PhotometricParameters(nexp=exptime/15.)
943 counts, e_per_sec = self.mag_to_flux_e_sec(mag, band, exptime)
944 return 0.04-1./(photParams.gain*counts)
945 else:
946 return np.asarray([self.gamma(m, b, e) for m, b, e in zip(mag, band, exptime)])
949class Load_Reference:
950 """
951 class to load template files requested for LCFast
952 These files should be stored in a reference_files directory
954 Parameters
955 ---------------
956 server: str, opt
957 where to get the files (default: https://me.lsst.eu/gris/DESC_SN_pipeline/Reference_Files)
958 templateDir: str, opt
959 where to put the files (default: reference_files)
961 """
963 def __init__(self, server='https://me.lsst.eu/gris/DESC_SN_pipeline',
964 templateDir=None):
966 if templateDir is None:
967 sims_maf_contrib_dir = os.getenv("SIMS_MAF_CONTRIB_DIR")
968 templateDir = os.path.join(sims_maf_contrib_dir, 'data/SNe_data')
970 self.server = server
971 # define instrument
972 self.Instrument = {}
973 self.Instrument['name'] = 'LSST' # name of the telescope (internal)
974 # dir of throughput
975 self.Instrument['throughput_dir'] = os.path.join(getPackageDir('throughputs'), 'baseline')
976 self.Instrument['atmos_dir'] = os.path.join(getPackageDir('throughputs'), 'atmos')
977 self.Instrument['airmass'] = 1.2 # airmass value
978 self.Instrument['atmos'] = True # atmos
979 self.Instrument['aerosol'] = False # aerosol
981 x1_colors = [(-2.0, 0.2), (0.0, 0.0)]
983 lc_reference = {}
985 # create this directory if it does not exist
986 if not os.path.isdir(templateDir):
987 os.system('mkdir {}'.format(templateDir))
989 list_files = ['gamma.hdf5']
990 for j in range(len(x1_colors)):
991 x1 = x1_colors[j][0]
992 color = x1_colors[j][1]
993 fname = 'LC_{}_{}_380.0_800.0_ebvofMW_0.0_vstack.hdf5'.format(
994 x1, color)
995 list_files += [fname]
997 self.check_grab(templateDir, list_files)
999 # gamma_reference
1000 self.gamma_reference = '{}/gamma.hdf5'.format(templateDir)
1002 # print('Loading reference files')
1004 resultdict = {}
1006 for j in range(len(x1_colors)):
1007 x1 = x1_colors[j][0]
1008 color = x1_colors[j][1]
1009 fname = '{}/LC_{}_{}_380.0_800.0_ebvofMW_0.0_vstack.hdf5'.format(
1010 templateDir, x1, color)
1011 resultdict[j] = self.load(fname)
1013 for j in range(len(x1_colors)):
1014 if resultdict[j] is not None:
1015 lc_reference[x1_colors[j]] = resultdict[j]
1017 self.ref = lc_reference
1019 def load(self, fname):
1020 """
1021 Method to load reference files
1023 Parameters
1024 ---------------
1025 fname: str
1026 file name
1028 Returns
1029 -----------
1031 """
1032 lc_ref = GetReference(
1033 fname, self.gamma_reference, self.Instrument)
1035 return lc_ref
1037 def check_grab(self, templateDir, listfiles):
1038 """
1039 Method that check if files are on disk.
1040 If not: grab them from a server (self.server)
1042 Parameters
1043 ---------------
1044 templateDir: str
1045 directory where files are (or will be)
1046 listfiles: list(str)
1047 list of files that are (will be) in templateDir
1048 """
1050 for fi in listfiles:
1051 # check whether the file is available; if not-> get it!
1052 fname = '{}/{}'.format(templateDir, fi)
1053 if not os.path.isfile(fname):
1054 if 'gamma' in fname:
1055 fullname = '{}/reference_files/{}'.format(self.server, fi)
1056 else:
1057 fullname = '{}/Template_LC/{}'.format(self.server, fi)
1058 print('wget path:', fullname)
1059 cmd = 'wget --no-clobber --no-verbose {} --directory-prefix {}'.format(
1060 fullname, templateDir)
1061 os.system(cmd)
1064class GetReference:
1065 """
1066 Class to load reference data
1067 used for the fast SN simulator
1068 Parameters
1069 ----------------
1070 lcName: str
1071 name of the reference file to load (lc)
1072 gammaName: str
1073 name of the reference file to load (gamma)
1074 tel_par: dict
1075 telescope parameters
1076 param_Fisher : list(str),opt
1077 list of SN parameter for Fisher estimation to consider
1078 (default: ['x0', 'x1', 'color', 'daymax'])
1079 Returns
1080 -----------
1081 The following dict can be accessed:
1082 mag_to_flux_e_sec : Interp1D of mag to flux(e.sec-1) conversion
1083 flux : dict of RegularGridInterpolator of fluxes (key: filters, (x,y)=(phase, z), result=flux)
1084 fluxerr : dict of RegularGridInterpolator of flux errors (key: filters, (x,y)=(phase, z), result=fluxerr)
1085 param : dict of dict of RegularGridInterpolator of flux derivatives wrt SN parameters
1086 (key: filters plus param_Fisher parameters; (x,y)=(phase, z), result=flux derivatives)
1087 gamma : dict of RegularGridInterpolator of gamma values (key: filters)
1088 """""
1090 def __init__(self, lcName, gammaName, tel_par, param_Fisher=['x0', 'x1', 'color', 'daymax']):
1092 # Load the file - lc reference
1094 f = h5py.File(lcName, 'r')
1095 keys = list(f.keys())
1096 # lc_ref_tot = Table.read(filename, path=keys[0])
1097 lc_ref_tot = Table.from_pandas(pd.read_hdf(lcName))
1099 idx = lc_ref_tot['z'] > 0.005
1100 lc_ref_tot = np.copy(lc_ref_tot[idx])
1102 # telescope requested
1103 telescope = Telescope(name=tel_par['name'],
1104 throughput_dir=tel_par['throughput_dir'],
1105 atmos_dir=tel_par['atmos_dir'],
1106 atmos=tel_par['atmos'],
1107 aerosol=tel_par['aerosol'],
1108 airmass=tel_par['airmass'])
1110 # Load the file - gamma values
1111 if not os.path.exists(gammaName):
1112 print('gamma file {} does not exist')
1113 print('will generate it - few minutes')
1114 mag_range = np.arange(15., 38., 1.)
1115 exptimes = np.arange(1., 3000., 10.)
1116 Gamma('ugrizy', telescope, gammaName,
1117 mag_range=mag_range,
1118 exptimes=exptimes)
1119 print('end of gamma estimation')
1121 fgamma = h5py.File(gammaName, 'r')
1123 # Load references needed for the following
1124 self.lc_ref = {}
1125 self.gamma_ref = {}
1126 self.gamma = {}
1127 self.m5_ref = {}
1128 self.mag_to_flux_e_sec = {}
1130 self.flux = {}
1131 self.fluxerr = {}
1132 self.param = {}
1134 bands = np.unique(lc_ref_tot['band'])
1135 mag_range = np.arange(10., 38., 0.01)
1136 # exptimes = np.linspace(15.,30.,2)
1137 # exptimes = [15.,30.,60.,100.]
1139 # gammArray = self.loopGamma(bands, mag_range, exptimes,telescope)
1141 method = 'linear'
1143 # for each band: load data to be used for interpolation
1144 for band in bands:
1145 idx = lc_ref_tot['band'] == band
1146 lc_sel = Table(lc_ref_tot[idx])
1148 lc_sel['z'] = lc_sel['z'].data.round(decimals=2)
1149 lc_sel['phase'] = lc_sel['phase'].data.round(decimals=1)
1151 """
1152 select phases between -20 and 50 only
1153 """
1154 idx = lc_sel['phase'] < 50.
1155 idx &= lc_sel['phase'] > -20.
1156 lc_sel = lc_sel[idx]
1158 fluxes_e_sec = telescope.mag_to_flux_e_sec(
1159 mag_range, [band]*len(mag_range), [30]*len(mag_range))
1160 self.mag_to_flux_e_sec[band] = interpolate.interp1d(
1161 mag_range, fluxes_e_sec[:, 1], fill_value=0., bounds_error=False)
1163 # these reference data will be used for griddata interp.
1164 self.lc_ref[band] = lc_sel
1165 self.gamma_ref[band] = lc_sel['gamma'][0]
1166 self.m5_ref[band] = np.unique(lc_sel['m5'])[0]
1168 # Another interpolator, faster than griddata: regulargridinterpolator
1170 # Fluxes and errors
1171 zmin, zmax, zstep, nz = self.limVals(lc_sel, 'z')
1172 phamin, phamax, phastep, npha = self.limVals(lc_sel, 'phase')
1174 zstep = np.round(zstep, 1)
1175 phastep = np.round(phastep, 1)
1177 zv = np.linspace(zmin, zmax, nz)
1178 # zv = np.round(zv,2)
1179 # print(band,zv)
1180 phav = np.linspace(phamin, phamax, npha)
1182 print('Loading ', lcName, band, len(lc_sel), npha, nz)
1183 index = np.lexsort((lc_sel['z'], lc_sel['phase']))
1184 flux = np.reshape(lc_sel[index]['flux'], (npha, nz))
1185 fluxerr = np.reshape(lc_sel[index]['fluxerr'], (npha, nz))
1187 self.flux[band] = RegularGridInterpolator(
1188 (phav, zv), flux, method=method, bounds_error=False, fill_value=0.)
1189 self.fluxerr[band] = RegularGridInterpolator(
1190 (phav, zv), fluxerr, method=method, bounds_error=False, fill_value=0.)
1192 # Flux derivatives
1193 self.param[band] = {}
1194 for par in param_Fisher:
1195 valpar = np.reshape(
1196 lc_sel[index]['d{}'.format(par)], (npha, nz))
1197 self.param[band][par] = RegularGridInterpolator(
1198 (phav, zv), valpar, method=method, bounds_error=False, fill_value=0.)
1200 # gamma estimator
1202 rec = Table.read(gammaName, path='gamma_{}'.format(band))
1204 rec['mag'] = rec['mag'].data.round(decimals=4)
1205 rec['single_exptime'] = rec['single_exptime'].data.round(
1206 decimals=4)
1208 magmin, magmax, magstep, nmag = self.limVals(rec, 'mag')
1209 expmin, expmax, expstep, nexpo = self.limVals(
1210 rec, 'single_exptime')
1211 nexpmin, nexpmax, nexpstep, nnexp = self.limVals(rec, 'nexp')
1212 mag = np.linspace(magmin, magmax, nmag)
1213 exp = np.linspace(expmin, expmax, nexpo)
1214 nexp = np.linspace(nexpmin, nexpmax, nnexp)
1216 index = np.lexsort(
1217 (rec['nexp'], np.round(rec['single_exptime'], 4), rec['mag']))
1218 gammab = np.reshape(rec[index]['gamma'], (nmag, nexpo, nnexp))
1219 fluxb = np.reshape(rec[index]['flux_e_sec'], (nmag, nexpo, nnexp))
1220 self.gamma[band] = RegularGridInterpolator(
1221 (mag, exp, nexp), gammab, method='linear', bounds_error=False, fill_value=0.)
1222 """
1223 self.mag_to_flux[band] = RegularGridInterpolator(
1224 (mag, exp, nexp), fluxb, method='linear', bounds_error=False, fill_value=0.)
1227 print('hello', rec.columns)
1228 rec['mag'] = rec['mag'].data.round(decimals=4)
1229 rec['exptime'] = rec['exptime'].data.round(decimals=4)
1231 magmin, magmax, magstep, nmag = self.limVals(rec, 'mag')
1232 expmin, expmax, expstep, nexp = self.limVals(rec, 'exptime')
1233 mag = np.linspace(magmin, magmax, nmag)
1234 exp = np.linspace(expmin, expmax, nexp)
1236 index = np.lexsort((np.round(rec['exptime'], 4), rec['mag']))
1237 gammab = np.reshape(rec[index]['gamma'], (nmag, nexp))
1238 self.gamma[band] = RegularGridInterpolator(
1239 (mag, exp), gammab, method=method, bounds_error=False, fill_value=0.)
1240 """
1241 # print(band, gammab, mag, exp)
1243 def limVals(self, lc, field):
1244 """ Get unique values of a field in a table
1245 Parameters
1246 ----------
1247 lc: Table
1248 astropy Table (here probably a LC)
1249 field: str
1250 name of the field of interest
1251 Returns
1252 -------
1253 vmin: float
1254 min value of the field
1255 vmax: float
1256 max value of the field
1257 vstep: float
1258 step value for this field (median)
1259 nvals: int
1260 number of unique values
1261 """
1263 lc.sort(field)
1264 vals = np.unique(lc[field].data.round(decimals=4))
1265 # print(vals)
1266 vmin = np.min(vals)
1267 vmax = np.max(vals)
1268 vstep = np.median(vals[1:]-vals[:-1])
1270 return vmin, vmax, vstep, len(vals)
1272 def Read_Ref(self, fi, j=-1, output_q=None):
1273 """" Load the reference file and
1274 make a single astopy Table from a set of.
1275 Parameters
1276 ----------
1277 fi: str,
1278 name of the file to be loaded
1279 Returns
1280 -------
1281 tab_tot: astropy table
1282 single table = vstack of all the tables in fi.
1283 """
1285 tab_tot = Table()
1286 """
1287 keys=np.unique([int(z*100) for z in zvals])
1288 print(keys)
1289 """
1290 f = h5py.File(fi, 'r')
1291 keys = f.keys()
1292 zvals = np.arange(0.01, 0.9, 0.01)
1293 zvals_arr = np.array(zvals)
1295 for kk in keys:
1297 tab_b = Table.read(fi, path=kk)
1299 if tab_b is not None:
1300 tab_tot = vstack([tab_tot, tab_b], metadata_conflicts='silent')
1301 """
1302 diff = tab_b['z']-zvals_arr[:, np.newaxis]
1303 # flag = np.abs(diff)<1.e-3
1304 flag_idx = np.where(np.abs(diff) < 1.e-3)
1305 if len(flag_idx[1]) > 0:
1306 tab_tot = vstack([tab_tot, tab_b[flag_idx[1]]])
1307 """
1309 """
1310 print(flag,flag_idx[1])
1311 print('there man',tab_b[flag_idx[1]])
1312 mtile = np.tile(tab_b['z'],(len(zvals),1))
1313 # print('mtile',mtile*flag)
1315 masked_array = np.ma.array(mtile,mask=~flag)
1317 print('resu masked',masked_array,masked_array.shape)
1318 print('hhh',masked_array[~masked_array.mask])
1321 for val in zvals:
1322 print('hello',tab_b[['band','z','time']],'and',val)
1323 if np.abs(np.unique(tab_b['z'])-val)<0.01:
1324 # print('loading ref',np.unique(tab_b['z']))
1325 tab_tot=vstack([tab_tot,tab_b])
1326 break
1327 """
1328 if output_q is not None:
1329 output_q.put({j: tab_tot})
1330 else:
1331 return tab_tot
1333 def Read_Multiproc(self, tab):
1334 """
1335 Multiprocessing method to read references
1336 Parameters
1337 ---------------
1338 tab: astropy Table of data
1339 Returns
1340 -----------
1341 stacked astropy Table of data
1342 """
1343 # distrib=np.unique(tab['z'])
1344 nlc = len(tab)
1345 print('ici pal', nlc)
1346 # n_multi=8
1347 if nlc >= 8:
1348 n_multi = min(nlc, 8)
1349 nvals = nlc/n_multi
1350 batch = range(0, nlc, nvals)
1351 batch = np.append(batch, nlc)
1352 else:
1353 batch = range(0, nlc)
1355 # lc_ref_tot={}
1356 # print('there pal',batch)
1357 result_queue = multiprocessing.Queue()
1358 for i in range(len(batch)-1):
1360 ida = int(batch[i])
1361 idb = int(batch[i+1])
1363 p = multiprocessing.Process(
1364 name='Subprocess_main-'+str(i), target=self.Read_Ref, args=(tab[ida:idb], i, result_queue))
1365 p.start()
1367 resultdict = {}
1368 for j in range(len(batch)-1):
1369 resultdict.update(result_queue.get())
1371 for p in multiprocessing.active_children():
1372 p.join()
1374 tab_res = Table()
1375 for j in range(len(batch)-1):
1376 if resultdict[j] is not None:
1377 tab_res = vstack([tab_res, resultdict[j]])
1379 return tab_res
1382class SN_Rate:
1383 """
1384 Estimate production rates of typeIa SN
1385 Available rates: Ripoche, Perrett, Dilday
1387 Parameters
1388 ----------
1389 rate : str,opt
1390 type of rate chosen (Ripoche, Perrett, Dilday) (default : Perrett)
1391 H0 : float, opt
1392 Hubble constant value :math:`H_{0}`(default : 70.)
1393 Om0 : float, opt
1394 matter density value :math:`\Omega_{0}` (default : 0.25)
1395 min_rf_phase : float, opt
1396 min rest-frame phase (default : -15.)
1397 max_rf_phase : float, opt
1398 max rest-frame phase (default : 30.)
1399 """
1401 def __init__(self, rate='Perrett', H0=70, Om0=0.25,
1402 min_rf_phase=-15., max_rf_phase=30.):
1404 self.astropy_cosmo = FlatLambdaCDM(H0=H0, Om0=Om0)
1405 self.rate = rate
1406 self.min_rf_phase = min_rf_phase
1407 self.max_rf_phase = max_rf_phase
1409 def __call__(self, zmin=0.1, zmax=0.2,
1410 dz=0.01, survey_area=9.6,
1411 bins=None, account_for_edges=False,
1412 duration=140., duration_z=None):
1413 """
1414 call method
1415 Parameters
1416 ----------------
1417 zmin : float, opt
1418 minimal redshift (default : 0.1)
1419 zmax : float,opt
1420 max redshift (default : 0.2)
1421 dz : float, opt
1422 redshift bin (default : 0.001)
1423 survey_area : float, opt
1424 area of the survey (:math:`deg^{2}`) (default : 9.6 :math:`deg^{2}`)
1425 bins : list(float), opt
1426 redshift bins (default : None)
1427 account_for_edges : bool
1428 to account for season edges. If true, duration of the survey will be reduced by (1+z)*(maf_rf_phase-min_rf_phase)/365.25 (default : False)
1429 duration : float, opt
1430 survey duration (in days) (default : 140 days)
1431 duration_z : list(float),opt
1432 survey duration (as a function of z) (default : None)
1433 Returns
1434 -----------
1435 Lists :
1436 zz : float
1437 redshift values
1438 rate : float
1439 production rate
1440 err_rate : float
1441 production rate error
1442 nsn : float
1443 number of SN
1444 err_nsn : float
1445 error on the number of SN
1446 """
1448 if bins is None:
1449 thebins = np.arange(zmin, zmax+dz, dz)
1450 zz = 0.5 * (thebins[1:] + thebins[:-1])
1451 else:
1452 zz = bins
1453 thebins = bins
1455 rate, err_rate = self.SNRate(zz)
1456 error_rel = err_rate/rate
1458 area = survey_area / STERADIAN2SQDEG
1459 # or area= self.survey_area/41253.
1461 dvol = norm*self.astropy_cosmo.comoving_volume(thebins).value
1463 dvol = dvol[1:] - dvol[:-1]
1465 if account_for_edges:
1466 margin = (1.+zz) * (self.max_rf_phase-self.min_rf_phase) / 365.25
1467 effective_duration = duration / 365.25 - margin
1468 effective_duration[effective_duration <= 0.] = 0.
1469 else:
1470 # duration in days!
1471 effective_duration = duration/365.25
1472 if duration_z is not None:
1473 effective_duration = duration_z(zz)/365.25
1475 normz = (1.+zz)
1476 nsn = rate * area * dvol * effective_duration / normz
1477 err_nsn = err_rate*area * dvol * effective_duration / normz
1479 return zz, rate, err_rate, nsn, err_nsn
1481 def RipocheRate(self, z):
1482 """The SNLS SNIa rate according to the (unpublished) Ripoche et al study.
1483 Parameters
1484 --------------
1485 z : float
1486 redshift
1487 Returns
1488 ----------
1489 rate : float
1490 error_rate : float
1491 """
1492 rate = 1.53e-4*0.343
1493 expn = 2.14
1494 my_z = np.copy(z)
1495 my_z[my_z > 1.] = 1.
1496 rate_sn = rate * np.power((1+my_z)/1.5, expn)
1497 return rate_sn, 0.2*rate_sn
1499 def PerrettRate(self, z):
1500 """The SNLS SNIa rate according to (Perrett et al, 201?)
1501 Parameters
1502 --------------
1503 z : float
1504 redshift
1505 Returns
1506 ----------
1507 rate : float
1508 error_rate : float
1509 """
1510 rate = 0.17E-4
1511 expn = 2.11
1512 err_rate = 0.03E-4
1513 err_expn = 0.28
1514 my_z = np.copy(z)
1515 rate_sn = rate * np.power(1+my_z, expn)
1516 err_rate_sn = np.power(1+my_z, 2.*expn)*np.power(err_rate, 2.)
1517 err_rate_sn += np.power(rate_sn*np.log(1+my_z)*err_expn, 2.)
1519 return rate_sn, np.power(err_rate_sn, 0.5)
1521 def DildayRate(self, z):
1522 """The Dilday rate according to
1523 Parameters
1524 --------------
1525 z : float
1526 redshift
1527 Returns
1528 ----------
1529 rate : float
1530 error_rate : float
1531 """
1533 rate = 2.6e-5
1534 expn = 1.5
1535 err_rate = 0.01
1536 err_expn = 0.6
1537 my_z = np.copy(z)
1538 my_z[my_z > 1.] = 1.
1539 rate_sn = rate * np.power(1+my_z, expn)
1540 err_rate_sn = rate_sn*np.log(1+my_z)*err_expn
1541 return rate_sn, err_rate_sn
1543 """
1544 def flat_rate(self, z):
1545 return 1., 0.1
1546 """
1548 def SNRate(self, z):
1549 """SN rate estimation
1550 Parameters
1551 --------------
1552 z : float
1553 redshift
1554 Returns
1555 ----------
1556 rate : float
1557 error_rate : float
1558 """
1559 if self.rate == 'Ripoche':
1560 return self.RipocheRate(z)
1561 if self.rate == 'Perrett':
1562 return self.PerrettRate(z)
1563 if self.rate == 'Dilday':
1564 return self.DildayRate(z)
1566 def PlotNSN(self, zmin=0.1, zmax=0.2,
1567 dz=0.01, survey_area=9.6,
1568 bins=None, account_for_edges=False,
1569 duration=140., duration_z=None, norm=False):
1570 """ Plot integrated number of supernovae as a function of redshift
1571 uses the __call__ function
1572 Parameters
1573 --------------
1574 zmin : float, opt
1575 minimal redshift (default : 0.1)
1576 zmax : float,opt
1577 max redshift (default : 0.2)
1578 dz : float, opt
1579 redshift bin (default : 0.001)
1580 survey_area : float, opt
1581 area of the survey (:math:`deg^{2}`) (default : 9.6 :math:`deg^{2}`)
1582 bins : list(float), opt
1583 redshift bins (default : None)
1584 account_for_edges : bool
1585 to account for season edges. If true, duration of the survey will be reduced by (1+z)*(maf_rf_phase-min_rf_phase)/365.25 (default : False)
1586 duration : float, opt
1587 survey duration (in days) (default : 140 days)
1588 duration_z : list(float),opt
1589 survey duration (as a function of z) (default : None)
1590 norm: bool, opt
1591 to normalise the results (default: False)
1592 """
1593 import pylab as plt
1595 zz, rate, err_rate, nsn, err_nsn = self.__call__(
1596 zmin=zmin, zmax=zmax, dz=dz, bins=bins,
1597 account_for_edges=account_for_edges,
1598 duration=duration, survey_area=survey_area)
1600 nsn_sum = np.cumsum(nsn)
1602 if norm is False:
1603 plt.errorbar(zz, nsn_sum, yerr=np.sqrt(np.cumsum(err_nsn**2)))
1604 else:
1605 plt.errorbar(zz, nsn_sum/nsn_sum[-1])
1606 plt.xlabel('z')
1607 plt.ylabel('N$_{SN}$ <')
1608 plt.grid()
1611class CovColor:
1612 """
1613 class to estimate CovColor from lc using Fisher matrix element
1614 Parameters
1615 ---------------
1616 lc: pandas df
1617 lc to process. Should contain the Fisher matrix components
1618 ie the sum of the derivative of the fluxes wrt SN parameters
1619 """
1621 def __init__(self, lc):
1623 self.Cov_colorcolor = self.varColor(lc)
1625 def varColor(self, lc):
1626 """
1627 Method to estimate the variance color from matrix element
1628 Parameters
1629 --------------
1630 lc: pandas df
1631 data to process containing the derivative of the flux with respect to SN parameters
1632 Returns
1633 ----------
1634 float: Cov_colorcolor
1635 """
1636 a1 = lc['F_x0x0']
1637 a2 = lc['F_x0x1']
1638 a3 = lc['F_x0daymax']
1639 a4 = lc['F_x0color']
1641 b1 = a2
1642 b2 = lc['F_x1x1']
1643 b3 = lc['F_x1daymax']
1644 b4 = lc['F_x1color']
1646 c1 = a3
1647 c2 = b3
1648 c3 = lc['F_daymaxdaymax']
1649 c4 = lc['F_daymaxcolor']
1651 d1 = a4
1652 d2 = b4
1653 d3 = c4
1654 d4 = lc['F_colorcolor']
1656 detM = a1*self.det(b2, b3, b4, c2, c3, c4, d2, d3, d4)
1657 detM -= b1*self.det(a2, a3, a4, c2, c3, c4, d2, d3, d4)
1658 detM += c1*self.det(a2, a3, a4, b2, b3, b4, d2, d3, d4)
1659 detM -= d1*self.det(a2, a3, a4, b2, b3, b4, c2, c3, c4)
1661 res = -a3*b2*c1+a2*b3*c1+a3*b1*c2-a1*b3*c2-a2*b1*c3+a1*b2*c3
1663 return res/detM
1665 def det(self, a1, a2, a3, b1, b2, b3, c1, c2, c3):
1666 """
1667 Method to estimate the det of a matrix from its values
1668 Parameters
1669 ---------------
1670 Values of the matrix
1671 ( a1 a2 a3)
1672 (b1 b2 b3)
1673 (c1 c2 c3)
1674 Returns
1675 -----------
1676 det value
1677 """
1678 resp = a1*b2*c3+b1*c2*a3+c1*a2*b3
1679 resm = a3*b2*c1+b3*c2*a1+c3*a2*b1
1681 return resp-resm