Hide keyboard shortcuts

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 

5 

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 

17 

18STERADIAN2SQDEG = 180.**2 / np.pi**2 

19# Mpc^3 -> Mpc^3/sr 

20norm = 1. / (4. * np.pi) 

21 

22__all__ = ['LCfast', 'Throughputs', 'Telescope', 

23 'Load_Reference', 'GetReference', 'SN_Rate', 'CovColor'] 

24 

25 

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 """ 

56 

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.): 

64 

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 

76 

77 # Loading reference file 

78 self.reference_lc = reference_lc 

79 

80 self.telescope = telescope 

81 

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. 

89 

90 # SN parameters for Fisher matrix estimation 

91 self.param_Fisher = ['x0', 'x1', 'daymax', 'color'] 

92 

93 self.snr_min = snr_min 

94 

95 # getting the telescope zp 

96 self.zp = {} 

97 for b in 'ugrizy': 

98 self.zp[b] = telescope.zp(b) 

99 

100 

101 def __call__(self, obs, gen_par=None, bands='grizy'): 

102 """ Simulation of the light curve 

103 

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 """ 

118 

119 if len(obs) == 0: 

120 return None 

121 

122 tab_tot = pd.DataFrame() 

123 

124 # multiprocessing here: one process (processBand) per band 

125 

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) 

132 

133 # return produced LC 

134 return tab_tot 

135 

136 

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 """ 

158 

159 # method used for interpolation 

160 method = 'linear' 

161 interpType = 'regular' 

162 

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 

169 

170 # Get the fluxes (from griddata reference) 

171 

172 # xi = MJD-T0 

173 xi = sel_obs[self.mjdCol]-gen_par['daymax'][:, np.newaxis] 

174 

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'] 

179 

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] 

183 

184 if interpType == 'regular': 

185 

186 pts = (p, yi_arr) 

187 fluxes_obs = self.reference_lc.flux[band](pts) 

188 fluxes_obs_err = self.reference_lc.fluxerr[band](pts) 

189 

190 # Fisher components estimation 

191 

192 dFlux = {} 

193 

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.) 

202 

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 

206 

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] 

216 

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) 

221 

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) 

229 

230 flag_idx = np.argwhere(flag) 

231 

232 # Correct fluxes_err (m5 in generation probably different from m5 obs) 

233 

234 # gamma_obs = self.telescope.gamma( 

235 # sel_obs[self.m5Col], [band]*len(sel_obs), sel_obs[self.exptimeCol]) 

236 

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])) 

239 

240 mag_obs = -2.5*np.log10(fluxes_obs/3631.) 

241 

242 m5 = np.asarray([self.reference_lc.m5_ref[band]]*len(sel_obs)) 

243 

244 gammaref = np.asarray([self.reference_lc.gamma_ref[band]]*len(sel_obs)) 

245 

246 m5_tile = np.tile(m5, (len(p), 1)) 

247 

248 srand_ref = self.srand( 

249 np.tile(gammaref, (len(p), 1)), mag_obs, m5_tile) 

250 

251 srand_obs = self.srand(np.tile(gamma_obs, (len(p), 1)), mag_obs, np.tile( 

252 sel_obs[self.m5Col], (len(p), 1))) 

253 

254 correct_m5 = srand_ref/srand_obs 

255 

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 

261 

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) 

267 

268 nvals = len(phases) 

269 

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) 

274 

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) 

281 

282 # Store in a panda dataframe 

283 lc = pd.DataFrame() 

284 

285 ndata = len(fluxes_err[~fluxes_err.mask]) 

286 

287 if ndata > 0: 

288 

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 

307 

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) 

312 

313 lc.loc[:, 'n_phmin'] = (lc['phase'] <= -5.) 

314 lc.loc[:, 'n_phmax'] = (lc['phase'] >= 20) 

315 

316 # transform boolean to int because of some problems in the sum() 

317 

318 for colname in ['n_aft', 'n_bef', 'n_phmin', 'n_phmax']: 

319 lc.loc[:, colname] = lc[colname].astype(int) 

320 

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 

329 

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)}` 

334 

335 Parameters 

336 ----------- 

337 gamma: float 

338 gamma value 

339 mag: float 

340 magnitude 

341 m5: float 

342 fiveSigmaDepth value 

343 

344 Returns 

345 ------- 

346 srand = np.sqrt((0.04-gamma)*x+gamma*x**2) 

347 with x = 10**(0.4*(mag-m5)) 

348 """ 

349 

350 x = 10**(0.4*(mag-m5)) 

351 return np.sqrt((0.04-gamma)*x+gamma*x**2) 

352 

353 

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 

387 

388 Note: I would like to see this replaced by a class in sims_photUtils instead. This does not belong in MAF. 

389 """ 

390 

391 def __init__(self, **kwargs): 

392 

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] 

409 

410 self.atmosDir = params['atmos_dir'] 

411 self.throughputsDir = params['through_dir'] 

412 

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'] 

419 

420 self.filterlist = params['filterlist'] 

421 self.filtercolors = {'u': 'b', 'g': 'c', 

422 'r': 'g', 'i': 'y', 'z': 'r', 'y': 'm'} 

423 

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() 

434 

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() 

442 

443 @property 

444 def system(self): 

445 return self.lsst_system 

446 

447 @property 

448 def telescope(self): 

449 return self.lsst_telescope 

450 

451 @property 

452 def atmosphere(self): 

453 return self.lsst_atmos 

454 

455 @property 

456 def aerosol(self): 

457 return self.lsst_atmos_aerosol 

458 

459 def Load_System(self): 

460 """ Load files required to estimate throughputs 

461 """ 

462 

463 for f in self.filterlist: 

464 self.lsst_std[f] = Bandpass() 

465 self.lsst_system[f] = Bandpass() 

466 

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) 

477 

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')) 

484 

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) 

506 

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) 

511 

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) 

518 

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] 

528 

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) 

536 

537# decorator to access parameters of the class 

538 

539 

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 

547 

548 

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 

597 

598 Note: I would like to see this replaced by a class in sims_photUtils instead. This does not belong in MAF. 

599 """ 

600 

601 def __init__(self, name='unknown', airmass=1., **kwargs): 

602 

603 self.name = name 

604 super().__init__(**kwargs) 

605 

606 params = ['mag_sky', 'm5', 'FWHMeff', 'Tb', 

607 'Sigmab', 'zp', 'counts_zp', 'Skyb', 'flux_sky'] 

608 

609 self.data = {} 

610 for par in params: 

611 self.data[par] = {} 

612 

613 self.data['FWHMeff'] = dict( 

614 zip('ugrizy', [0.92, 0.87, 0.83, 0.80, 0.78, 0.76])) 

615 

616 # self.atmos = atmos 

617 

618 self.Load_Atmosphere(airmass) 

619 

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) 

634 

635 bandpass = Bandpass(wavelen=filter_trans.wavelen, sb=filter_trans.sb) 

636 

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 

644 

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 

653 

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))) 

670 

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 

687 

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) 

693 

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 

709 

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] 

724 

725 def m5(self, filtre): 

726 """m5 accessor 

727 """ 

728 self.get('m5', filtre) 

729 return self.return_value('m5', filtre) 

730 

731 def Tb(self, filtre): 

732 """Tb accessor 

733 """ 

734 self.get_inputs('Tb', filtre) 

735 return self.return_value('Tb', filtre) 

736 

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) 

742 

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) 

753 

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) 

764 

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) 

774 

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] 

792 

793 return resu 

794 

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) 

826 

827 # Calculate the number of photons. 

828 nphoton = (fnu / wavelen * bandpass.sb).sum() 

829 dlambda = wavelen[1] - wavelen[0] 

830 return nphoton * dlambda 

831 

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 

853 

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)) 

873 

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]) 

886 

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)]) 

922 

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 """ 

940 

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)]) 

947 

948 

949class Load_Reference: 

950 """ 

951 class to load template files requested for LCFast 

952 These files should be stored in a reference_files directory 

953 

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) 

960 

961 """ 

962 

963 def __init__(self, server='https://me.lsst.eu/gris/DESC_SN_pipeline', 

964 templateDir=None): 

965 

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') 

969 

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 

980 

981 x1_colors = [(-2.0, 0.2), (0.0, 0.0)] 

982 

983 lc_reference = {} 

984 

985 # create this directory if it does not exist 

986 if not os.path.isdir(templateDir): 

987 os.system('mkdir {}'.format(templateDir)) 

988 

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] 

996 

997 self.check_grab(templateDir, list_files) 

998 

999 # gamma_reference 

1000 self.gamma_reference = '{}/gamma.hdf5'.format(templateDir) 

1001 

1002 # print('Loading reference files') 

1003 

1004 resultdict = {} 

1005 

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) 

1012 

1013 for j in range(len(x1_colors)): 

1014 if resultdict[j] is not None: 

1015 lc_reference[x1_colors[j]] = resultdict[j] 

1016 

1017 self.ref = lc_reference 

1018 

1019 def load(self, fname): 

1020 """ 

1021 Method to load reference files 

1022 

1023 Parameters 

1024 --------------- 

1025 fname: str 

1026 file name 

1027 

1028 Returns 

1029 ----------- 

1030 

1031 """ 

1032 lc_ref = GetReference( 

1033 fname, self.gamma_reference, self.Instrument) 

1034 

1035 return lc_ref 

1036 

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) 

1041 

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 """ 

1049 

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) 

1062 

1063 

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 """"" 

1089 

1090 def __init__(self, lcName, gammaName, tel_par, param_Fisher=['x0', 'x1', 'color', 'daymax']): 

1091 

1092 # Load the file - lc reference 

1093 

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)) 

1098 

1099 idx = lc_ref_tot['z'] > 0.005 

1100 lc_ref_tot = np.copy(lc_ref_tot[idx]) 

1101 

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']) 

1109 

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') 

1120 

1121 fgamma = h5py.File(gammaName, 'r') 

1122 

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 = {} 

1129 

1130 self.flux = {} 

1131 self.fluxerr = {} 

1132 self.param = {} 

1133 

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.] 

1138 

1139 # gammArray = self.loopGamma(bands, mag_range, exptimes,telescope) 

1140 

1141 method = 'linear' 

1142 

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]) 

1147 

1148 lc_sel['z'] = lc_sel['z'].data.round(decimals=2) 

1149 lc_sel['phase'] = lc_sel['phase'].data.round(decimals=1) 

1150 

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] 

1157 

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) 

1162 

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] 

1167 

1168 # Another interpolator, faster than griddata: regulargridinterpolator 

1169 

1170 # Fluxes and errors 

1171 zmin, zmax, zstep, nz = self.limVals(lc_sel, 'z') 

1172 phamin, phamax, phastep, npha = self.limVals(lc_sel, 'phase') 

1173 

1174 zstep = np.round(zstep, 1) 

1175 phastep = np.round(phastep, 1) 

1176 

1177 zv = np.linspace(zmin, zmax, nz) 

1178 # zv = np.round(zv,2) 

1179 # print(band,zv) 

1180 phav = np.linspace(phamin, phamax, npha) 

1181 

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)) 

1186 

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.) 

1191 

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.) 

1199 

1200 # gamma estimator 

1201 

1202 rec = Table.read(gammaName, path='gamma_{}'.format(band)) 

1203 

1204 rec['mag'] = rec['mag'].data.round(decimals=4) 

1205 rec['single_exptime'] = rec['single_exptime'].data.round( 

1206 decimals=4) 

1207 

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) 

1215 

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.) 

1225 

1226  

1227 print('hello', rec.columns) 

1228 rec['mag'] = rec['mag'].data.round(decimals=4) 

1229 rec['exptime'] = rec['exptime'].data.round(decimals=4) 

1230 

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) 

1235 

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) 

1242 

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 """ 

1262 

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]) 

1269 

1270 return vmin, vmax, vstep, len(vals) 

1271 

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 """ 

1284 

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) 

1294 

1295 for kk in keys: 

1296 

1297 tab_b = Table.read(fi, path=kk) 

1298 

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 """ 

1308 

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) 

1314  

1315 masked_array = np.ma.array(mtile,mask=~flag) 

1316  

1317 print('resu masked',masked_array,masked_array.shape) 

1318 print('hhh',masked_array[~masked_array.mask]) 

1319  

1320  

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 

1332 

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) 

1354 

1355 # lc_ref_tot={} 

1356 # print('there pal',batch) 

1357 result_queue = multiprocessing.Queue() 

1358 for i in range(len(batch)-1): 

1359 

1360 ida = int(batch[i]) 

1361 idb = int(batch[i+1]) 

1362 

1363 p = multiprocessing.Process( 

1364 name='Subprocess_main-'+str(i), target=self.Read_Ref, args=(tab[ida:idb], i, result_queue)) 

1365 p.start() 

1366 

1367 resultdict = {} 

1368 for j in range(len(batch)-1): 

1369 resultdict.update(result_queue.get()) 

1370 

1371 for p in multiprocessing.active_children(): 

1372 p.join() 

1373 

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]]) 

1378 

1379 return tab_res 

1380 

1381 

1382class SN_Rate: 

1383 """ 

1384 Estimate production rates of typeIa SN 

1385 Available rates: Ripoche, Perrett, Dilday 

1386 

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 """ 

1400 

1401 def __init__(self, rate='Perrett', H0=70, Om0=0.25, 

1402 min_rf_phase=-15., max_rf_phase=30.): 

1403 

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 

1408 

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 """ 

1447 

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 

1454 

1455 rate, err_rate = self.SNRate(zz) 

1456 error_rel = err_rate/rate 

1457 

1458 area = survey_area / STERADIAN2SQDEG 

1459 # or area= self.survey_area/41253. 

1460 

1461 dvol = norm*self.astropy_cosmo.comoving_volume(thebins).value 

1462 

1463 dvol = dvol[1:] - dvol[:-1] 

1464 

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 

1474 

1475 normz = (1.+zz) 

1476 nsn = rate * area * dvol * effective_duration / normz 

1477 err_nsn = err_rate*area * dvol * effective_duration / normz 

1478 

1479 return zz, rate, err_rate, nsn, err_nsn 

1480 

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 

1498 

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.) 

1518 

1519 return rate_sn, np.power(err_rate_sn, 0.5) 

1520 

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 """ 

1532 

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 

1542 

1543 """ 

1544 def flat_rate(self, z): 

1545 return 1., 0.1 

1546 """ 

1547 

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) 

1565 

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 

1594 

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) 

1599 

1600 nsn_sum = np.cumsum(nsn) 

1601 

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() 

1609 

1610 

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 """ 

1620 

1621 def __init__(self, lc): 

1622 

1623 self.Cov_colorcolor = self.varColor(lc) 

1624 

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'] 

1640 

1641 b1 = a2 

1642 b2 = lc['F_x1x1'] 

1643 b3 = lc['F_x1daymax'] 

1644 b4 = lc['F_x1color'] 

1645 

1646 c1 = a3 

1647 c2 = b3 

1648 c3 = lc['F_daymaxdaymax'] 

1649 c4 = lc['F_daymaxcolor'] 

1650 

1651 d1 = a4 

1652 d2 = b4 

1653 d3 = c4 

1654 d4 = lc['F_colorcolor'] 

1655 

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) 

1660 

1661 res = -a3*b2*c1+a2*b3*c1+a3*b1*c2-a1*b3*c2-a2*b1*c3+a1*b2*c3 

1662 

1663 return res/detM 

1664 

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 

1680 

1681 return resp-resm