Coverage for python/lsst/sims/maf/metrics/snSNRMetric.py : 6%

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
1import numpy as np
2import matplotlib.pylab as plt
3import numpy.lib.recfunctions as rf
4import yaml
5from scipy import interpolate
6import lsst.sims.maf.metrics as metrics
7from lsst.sims.maf.utils.snUtils import GenerateFakeObservations
8from collections import Iterable
9import time
12class SNSNRMetric(metrics.BaseMetric):
14 """
15 Metric to estimate the detection rate for faint supernovae (x1,color) = (-2.0,0.2)
17 Parameters
18 ----------
19 list : str, opt
20 Name of the columns used to estimate the metric
21 Default : 'observationStartMJD', 'fieldRA', 'fieldDec','filter','fiveSigmaDepth',
22 'visitExposureTime','night','observationId', 'numExposures','visitTime'
23 coadd : bool, opt
24 to make "coaddition" per night (uses snStacker)
25 Default : True
26 lim_sn : class, opt
27 Reference data used to simulate LC points (interpolation)
28 names_ref : str,opt
29 names of the simulator used to produce reference data
30 season : flota,opt
31 season num
32 Default : 1.
33 z : float,opt
34 redshift for this study
35 Default : 0.01
36 """
38 def __init__(self, metricName='SNSNRMetric',
39 mjdCol='observationStartMJD', RaCol='fieldRA', DecCol='fieldDec',
40 filterCol='filter', m5Col='fiveSigmaDepth', exptimeCol='visitExposureTime',
41 nightCol='night', obsidCol='observationId', nexpCol='numExposures',
42 vistimeCol='visitTime', coadd=True, lim_sn=None, names_ref=None, season=1, z=0.01, **kwargs):
44 self.mjdCol = mjdCol
45 self.m5Col = m5Col
46 self.filterCol = filterCol
47 self.RaCol = RaCol
48 self.DecCol = DecCol
49 self.exptimeCol = exptimeCol
50 self.seasonCol = 'season'
51 self.nightCol = nightCol
52 self.obsidCol = obsidCol
53 self.nexpCol = nexpCol
54 self.vistimeCol = vistimeCol
56 cols = [self.nightCol, self.m5Col, self.filterCol, self.mjdCol, self.obsidCol,
57 self.nexpCol, self.vistimeCol, self.exptimeCol, self.seasonCol]
58 if coadd:
59 cols += ['coadd']
60 super(SNSNRMetric, self).__init__(
61 col=cols, metricName=metricName, **kwargs)
63 self.filterNames = np.array(['u', 'g', 'r', 'i', 'z', 'y'])
64 self.blue_cutoff = 300.
65 self.red_cutoff = 800.
66 self.min_rf_phase = -20.
67 self.max_rf_phase = 40.
68 self.z = z
69 self.names_ref = names_ref
70 self.season = season
72 # SN DayMax: current date - shift days
73 self.shift = 10.
75 # These are reference LC
76 self.lim_sn = lim_sn
78 self.display = False
80 def run(self, dataSlice, slicePoint=None):
81 """
82 run the metric
84 Parameters
85 ----------
86 dataSlice : array
87 simulation data under study
89 Returns
90 -------
91 detection rate : float
93 """
94 time_ref = time.time()
95 goodFilters = np.in1d(dataSlice['filter'], self.filterNames)
96 dataSlice = dataSlice[goodFilters]
97 if dataSlice.size == 0:
98 return None
99 dataSlice.sort(order=self.mjdCol)
101 if self.season != -1:
102 seasons = self.season
103 else:
104 seasons = np.unique(dataSlice['season'])
106 if not isinstance(seasons, Iterable):
107 seasons = [seasons]
109 self.info_season = None
110 for seas in seasons:
111 info = self.season_info(dataSlice, seas)
112 if info is not None and info['season_length'] >= self.shift:
113 if self.info_season is None:
114 self.info_season = info
115 else:
116 self.info_season = np.concatenate((self.info_season, info))
118 self.info_season = self.check_seasons(self.info_season)
119 if self.info_season is None:
120 return 0.
122 sel = dataSlice[np.in1d(dataSlice['season'], np.array(seasons))]
124 detect_frac = None
125 if len(sel) >= 5:
126 detect_frac = self.process(sel)
128 if detect_frac is not None:
129 return np.median(detect_frac['frac_obs_{}'.format(self.names_ref[0])])
130 else:
131 return 0.
133 def process(self, sel):
134 """Process one season
136 Parameters
137 ---------
138 sel : array
139 array of observations
140 season : int
141 season number
143 Returns
144 -----
145 record array with the following fields:
146 fieldRA (float)
147 fieldDec (float)
148 season (float)
149 band (str)
150 frac_obs_name_ref (float)
152 """
154 self.band = np.unique(sel[self.filterCol])[0]
155 time_ref = time.time()
156 snr_obs = self.snr_slice(sel) # SNR for observations
157 snr_fakes = self.snr_fakes(sel) # SNR for fakes
158 detect_frac = self.detection_rate(
159 snr_obs, snr_fakes) # Detection rate
160 snr_obs = np.asarray(snr_obs)
161 snr_fakes = np.asarray(snr_fakes)
162 #self.plot(snr_obs, snr_fakes)
163 # plt.show()
164 detect_frac = np.asarray(detect_frac)
166 return detect_frac
168 def snr_slice(self, dataSlice, j=-1, output_q=None):
169 """
170 Estimate SNR for a given dataSlice
172 Parameters
173 ---------
174 Input: dataSlice
176 Returns
177 -----
178 array with the following fields (all are of f8 type, except band which is of U1)
180 SNR_name_ref: Signal-To-Noise Ratio estimator
181 season : season
182 cadence: cadence of the season
183 season_length: length of the season
184 MJD_min: min MJD of the season
185 DayMax: SN max luminosity MJD (aka T0)
186 MJD:
187 m5_eff: mean m5 of obs passing the min_phase, max_phase cut
188 fieldRA: mean field RA
189 fieldDec: mean field Dec
190 band: band
191 m5: mean m5 (over the season)
192 Nvisits: median number of visits (per observation) (over the season)
193 ExposureTime: median exposure time (per observation) (over the season)
195 """
197 # Get few infos: RA, Dec, Nvisits, m5, exptime
198 fieldRA = np.mean(dataSlice[self.RaCol])
199 fieldDec = np.mean(dataSlice[self.DecCol])
200 # one visit = 2 exposures
201 Nvisits = np.median(dataSlice[self.nexpCol]/2.)
202 m5 = np.mean(dataSlice[self.m5Col])
203 exptime = np.median(dataSlice[self.exptimeCol])
204 dataSlice.sort(order=self.mjdCol)
205 mjds = dataSlice[self.mjdCol]
206 band = np.unique(dataSlice[self.filterCol])[0]
208 # Define MJDs to consider for metric estimation
209 # basically: step of one day between MJDmin and MJDmax
210 dates = None
212 for val in self.info_season:
213 if dates is None:
214 dates = np.arange(
215 val['MJD_min']+self.shift, val['MJD_max']+1., 1.)
216 else:
217 dates = np.concatenate(
218 (dates, np.arange(val['MJD_min']+self.shift, val['MJD_max']+1., 1.)))
220 # SN DayMax: dates-shift where shift is chosen in the input yaml file
221 T0_lc = dates-self.shift
223 # for these DayMax, estimate the phases of LC points corresponding to the current dataSlice MJDs
225 time_for_lc = -T0_lc[:, None]+mjds
227 phase = time_for_lc/(1.+self.z) # phases of LC points
228 # flag: select LC points only in between min_rf_phase and max_phase
229 phase_max = self.shift/(1.+self.z)
230 flag = (phase >= self.min_rf_phase) & (phase <= phase_max)
232 # tile m5, MJDs, and seasons to estimate all fluxes and SNR at once
233 m5_vals = np.tile(dataSlice[self.m5Col], (len(time_for_lc), 1))
234 season_vals = np.tile(dataSlice[self.seasonCol], (len(time_for_lc), 1))
236 # estimate fluxes and snr in SNR function
237 fluxes_tot, snr = self.snr(
238 time_for_lc, m5_vals, flag, season_vals, T0_lc)
240 # now save the results in a record array
241 _, idx = np.unique(snr['season'], return_inverse=True)
242 infos = self.info_season[idx]
244 vars_info = ['cadence', 'season_length', 'MJD_min']
245 snr = rf.append_fields(
246 snr, vars_info, [infos[name] for name in vars_info])
247 snr = rf.append_fields(snr, 'DayMax', T0_lc)
248 snr = rf.append_fields(snr, 'MJD', dates)
249 snr = rf.append_fields(snr, 'm5_eff', np.mean(
250 np.ma.array(m5_vals, mask=~flag), axis=1))
251 global_info = [(fieldRA, fieldDec, band, m5,
252 Nvisits, exptime)]*len(snr)
253 names = ['fieldRA', 'fieldDec', 'band',
254 'm5', 'Nvisits', 'ExposureTime']
255 global_info = np.rec.fromrecords(global_info, names=names)
256 snr = rf.append_fields(
257 snr, names, [global_info[name] for name in names])
259 if output_q is not None:
260 output_q.put({j: snr})
261 else:
262 return snr
264 def season_info(self, dataSlice, season):
265 """
266 Get info on seasons for each dataSlice
268 Parameters
269 -----
270 dataSlice : array
271 array of observations
273 Returns
274 -----
275 recordarray with the following fields:
276 season, cadence, season_length, MJDmin, MJDmax
277 """
279 rv = []
281 idx = (dataSlice[self.seasonCol] == season)
282 slice_sel = dataSlice[idx]
283 if len(slice_sel) < 5:
284 return None
285 slice_sel.sort(order=self.mjdCol)
286 mjds_season = slice_sel[self.mjdCol]
287 cadence = np.mean(mjds_season[1:]-mjds_season[:-1])
288 mjd_min = np.min(mjds_season)
289 mjd_max = np.max(mjds_season)
290 season_length = mjd_max-mjd_min
291 Nvisits = np.median(slice_sel[self.nexpCol])
292 m5 = np.median(slice_sel[self.m5Col])
293 rv.append((float(season), cadence,
294 season_length, mjd_min, mjd_max, Nvisits, m5))
296 info_season = np.rec.fromrecords(
297 rv, names=['season', 'cadence', 'season_length', 'MJD_min', 'MJD_max', 'Nvisits', 'm5'])
299 return info_season
301 def snr(self, time_lc, m5_vals, flag, season_vals, T0_lc):
302 """
303 Estimate SNR vs time
305 Parameters
306 -----------
307 time_lc :
308 m5_vals : list(float)
309 five-sigme depth values
310 flag : array(bool)
311 flag to be applied (example: selection from phase cut)
312 season_vals : array(float)
313 season values
314 T0_lc : array(float)
315 array of T0 for supernovae
317 Returns
318 -----
319 fluxes_tot : list(float)
320 list of (interpolated) fluxes
321 snr_tab : array with the following fields:
322 snr_name_ref (float) : Signal-to-Noise values
323 season (float) : season num.
324 """
326 seasons = np.ma.array(season_vals, mask=~flag)
328 fluxes_tot = {}
329 snr_tab = None
331 for ib, name in enumerate(self.names_ref):
332 fluxes = self.lim_sn.fluxes[ib](time_lc)
333 if name not in fluxes_tot.keys():
334 fluxes_tot[name] = fluxes
335 else:
336 fluxes_tot[name] = np.concatenate((fluxes_tot[name], fluxes))
338 flux_5sigma = self.lim_sn.mag_to_flux[ib](m5_vals)
339 snr = fluxes**2/flux_5sigma**2
340 snr_season = 5.*np.sqrt(np.sum(snr*flag, axis=1))
342 if snr_tab is None:
343 snr_tab = np.asarray(np.copy(snr_season), dtype=[
344 ('SNR_'+name, 'f8')])
345 else:
346 snr_tab = rf.append_fields(
347 snr_tab, 'SNR_'+name, np.copy(snr_season))
348 """
349 snr_tab = rf.append_fields(
350 snr_tab, 'season', np.mean(seasons, axis=1))
351 """
352 snr_tab = rf.append_fields(
353 snr_tab, 'season', self.get_season(T0_lc))
355 # check if any masked value remaining
356 # this would correspond to case where no obs point has been selected
357 # ie no points with phase in [phase_min,phase_max]
358 # this happens when internight gaps are large (typically larger than shift)
359 idmask = np.where(snr_tab.mask)
360 if len(idmask) > 0:
361 tofill = np.copy(snr_tab['season'])
362 season_recover = self.get_season(
363 T0_lc[np.where(snr_tab.mask)])
364 tofill[idmask] = season_recover
365 snr_tab = np.ma.filled(snr_tab, fill_value=tofill)
367 return fluxes_tot, snr_tab
369 def get_season(self, T0):
370 """
371 Estimate the seasons corresponding to T0 values
373 Parameters
374 -------
375 T0 : list(float)
376 set of T0 values
378 Returns
379 -----
380 list (float) of corresponding seasons
381 """
383 diff_min = T0[:, None]-self.info_season['MJD_min']
384 diff_max = -T0[:, None]+self.info_season['MJD_max']
385 seasons = np.tile(self.info_season['season'], (len(diff_min), 1))
386 flag = (diff_min >= 0) & (diff_max >= 0)
387 seasons = np.ma.array(seasons, mask=~flag)
389 return np.mean(seasons, axis=1)
391 def snr_fakes(self, dataSlice):
392 """
393 Estimate SNR for fake observations
394 in the same way as for observations (using SNR_Season)
396 Parameters:
397 -------
398 dataSlice : array
399 array of observations
401 Returns
402 -----
403 snr_tab : array with the following fields:
404 snr_name_ref (float) : Signal-to-Noise values
405 season (float) : season num.
407 """
409 # generate fake observations
410 fake_obs = None
412 # idx = (dataSlice[self.seasonCol] == season)
413 band = np.unique(dataSlice[self.filterCol])[0]
414 fake_obs = self.gen_fakes(dataSlice, band)
416 # estimate SNR vs MJD
418 snr_fakes = self.snr_slice(
419 fake_obs[fake_obs['filter'] == band])
421 return snr_fakes
423 def gen_fakes(self, slice_sel, band):
424 """
425 Generate fake observations
426 according to observing values extracted from simulations
428 Parameters
429 -----
430 slice_sel : array
431 array of observations
432 band : str
433 band to consider
435 Returns
436 -----
437 fake_obs_season : array
438 array of observations with the following fields
439 observationStartMJD (float)
440 fieldRA (float)
441 fieldDec (float)
442 filter (U1)
443 fiveSigmaDepth (float)
444 numExposures (float)
445 visitExposureTime (float)
446 season (int)
449 """
450 fieldRA = np.mean(slice_sel[self.RaCol])
451 fieldDec = np.mean(slice_sel[self.DecCol])
452 Tvisit = 30.
454 fake_obs = None
455 for val in self.info_season:
456 cadence = val['cadence']
457 mjd_min = val['MJD_min']
458 mjd_max = val['MJD_max']
459 season_length = val['season_length']
460 Nvisits = val['Nvisits']
461 m5 = val['m5']
463 # build the configuration file
465 config_fake = {}
466 config_fake['Ra'] = fieldRA
467 config_fake['Dec'] = fieldDec
468 config_fake['bands'] = [band]
469 config_fake['Cadence'] = [cadence]
470 config_fake['MJD_min'] = [mjd_min]
471 config_fake['season_length'] = season_length
472 config_fake['Nvisits'] = [Nvisits]
473 m5_nocoadd = m5-1.25*np.log10(float(Nvisits)*Tvisit/30.)
474 config_fake['m5'] = [m5_nocoadd]
475 config_fake['seasons'] = [val['season']]
476 config_fake['Exposure_Time'] = [30.]
477 config_fake['shift_days'] = 0.
478 fake_obs_season = GenerateFakeObservations(
479 config_fake).Observations
480 if fake_obs is None:
481 fake_obs = fake_obs_season
482 else:
483 fake_obs = np.concatenate((fake_obs, fake_obs_season))
484 return fake_obs
486 def plot(self, snr_obs, snr_fakes):
487 """ Plot SNR vs time
489 Parameters
490 -----
491 snr_obs : array
492 array estimated using snr_slice(observations)
494 snr_obs : array
495 array estimated using snr_slice(fakes)
497 """
499 fig, ax = plt.subplots(figsize=(10, 7))
501 title = 'season {} - {} band - z={}'.format(
502 self.season, self.band, self.z)
503 fig.suptitle(title)
504 ax.plot(snr_obs['MJD'], snr_obs['SNR_{}'.format(
505 self.names_ref[0])], label='Simulation')
506 ax.plot(snr_fakes['MJD'], snr_fakes['SNR_{}'.format(
507 self.names_ref[0])], ls='--', label='Fakes')
509 def PlotHistory(self, fluxes, mjd, flag, snr, T0_lc, dates):
510 """ Plot history of Plot
511 For each MJD, fluxes and snr are plotted
512 Each plot may be saved as a png to make a video afterwards
514 Parameters
515 ------
516 fluxes : list(float)
517 LC fluxes
518 mjd : list(float)
519 mjds of the fluxes
520 flag : array
521 flag for selection of fluxes
522 snr : list
523 signal-to-noise ratio
524 T0_lc : list(float)
525 list of T0 supernovae
526 dates : list(float)
527 date of the display (mjd)
529 """
531 dir_save = '/home/philippe/LSST/sn_metric_new/Plots'
532 import pylab as plt
533 plt.ion()
534 fig, ax = plt.subplots(ncols=1, nrows=2)
535 fig.canvas.draw()
537 colors = ['b', 'r']
538 myls = ['-', '--']
539 mfc = ['b', 'None']
540 tot_label = []
541 fontsize = 12
542 mjd_ma = np.ma.array(mjd, mask=~flag)
543 fluxes_ma = {}
544 for key, val in fluxes.items():
545 fluxes_ma[key] = np.ma.array(val, mask=~flag)
546 key = list(fluxes.keys())[0]
547 jmax = len(fluxes_ma[key])
548 tot_label = []
549 tot_label_snr = []
550 min_flux = []
551 max_flux = []
553 for j in range(jmax):
555 for ib, name in enumerate(fluxes_ma.keys()):
556 tot_label.append(ax[0].errorbar(
557 mjd_ma[j], fluxes_ma[name][j], marker='s', color=colors[ib], ls=myls[ib], label=name))
559 tot_label_snr.append(ax[1].errorbar(
560 snr['MJD'][:j], snr['SNR_'+name][:j], color=colors[ib], label=name))
561 fluxx = fluxes_ma[name][j]
562 fluxx = fluxx[~fluxx.mask]
563 if len(fluxx) >= 2:
564 min_flux.append(np.min(fluxx))
565 max_flux.append(np.max(fluxx))
566 else:
567 min_flux.append(0.)
568 max_flux.append(200.)
570 min_fluxes = np.min(min_flux)
571 max_fluxes = np.max(max_flux)
573 tot_label.append(ax[0].errorbar([T0_lc[j], T0_lc[j]], [
574 min_fluxes, max_fluxes], color='k', label='DayMax'))
575 tot_label.append(ax[0].errorbar([dates[j], dates[j]], [
576 min_fluxes, max_fluxes], color='k', ls='--', label='Current MJD'))
577 fig.canvas.flush_events()
578 # plt.savefig('{}/{}_{}.png'.format(dir_save, 'snr', 1000 + j))
579 if j != jmax-1:
580 ax[0].clear()
581 tot_label = []
582 tot_label_snr = []
584 labs = [l.get_label() for l in tot_label]
585 ax[0].legend(tot_label, labs, ncol=1, loc='best',
586 prop={'size': fontsize}, frameon=False)
587 ax[0].set_ylabel('Flux [e.sec$^{-1}$]', fontsize=fontsize)
589 ax[1].set_xlabel('MJD', fontsize=fontsize)
590 ax[1].set_ylabel('SNR', fontsize=fontsize)
591 ax[1].legend()
592 labs = [l.get_label() for l in tot_label_snr]
593 ax[1].legend(tot_label_snr, labs, ncol=1, loc='best',
594 prop={'size': fontsize}, frameon=False)
595 for i in range(2):
596 ax[i].tick_params(axis='x', labelsize=fontsize)
597 ax[i].tick_params(axis='y', labelsize=fontsize)
599 def detection_rate(self, snr_obs, snr_fakes):
600 """
601 Estimate the time fraction(per season) for which
602 snr_obs > snr_fakes = detection rate
603 For regular cadences one should get a result close to 1
605 Parameters
606 -------
607 snr_obs : array
608 array estimated using snr_slice(observations)
610 snr_fakes: array
611 array estimated using snr_slice(fakes)
613 Returns
614 -----
615 record array with the following fields:
616 fieldRA (float)
617 fieldDec (float)
618 season (float)
619 band (str)
620 frac_obs_name_ref (float)
621 """
623 ra = np.mean(snr_obs['fieldRA'])
624 dec = np.mean(snr_obs['fieldDec'])
625 band = np.unique(snr_obs['band'])[0]
627 rtot = []
629 for season in np.unique(snr_obs['season']):
630 idx = snr_obs['season'] == season
631 sel_obs = snr_obs[idx]
632 idxb = snr_fakes['season'] == season
633 sel_fakes = snr_fakes[idxb]
635 sel_obs.sort(order='MJD')
636 sel_fakes.sort(order='MJD')
637 r = [ra, dec, season, band]
638 names = [self.RaCol, self.DecCol, 'season', 'band']
639 for sim in self.names_ref:
640 fakes = interpolate.interp1d(
641 sel_fakes['MJD'], sel_fakes['SNR_'+sim])
642 obs = interpolate.interp1d(sel_obs['MJD'], sel_obs['SNR_'+sim])
643 mjd_min = np.max(
644 [np.min(sel_obs['MJD']), np.min(sel_fakes['MJD'])])
645 mjd_max = np.min(
646 [np.max(sel_obs['MJD']), np.max(sel_fakes['MJD'])])
647 mjd = np.arange(mjd_min, mjd_max, 1.)
649 diff_res = obs(mjd)-fakes(mjd)
651 idx = diff_res >= 0
652 r += [len(diff_res[idx])/len(diff_res)]
653 names += ['frac_obs_'+sim]
654 rtot.append(tuple(r))
656 return np.rec.fromrecords(rtot, names=names)
658 def check_seasons(self, tab):
659 """ Check wether seasons have no overlap
660 if it is the case: modify MJD_min and season length of the corresponding season
661 return only seasons with season_length > 30 days
663 Parameters
664 --------------
665 tab : array with the following fields:
668 Returns
669 ---------
670 tab : array with the following fields:
671 """
672 if tab is None or len(tab) == 1:
673 return tab
675 if len(tab) > 1:
676 diff = tab['MJD_min'][1:]-tab['MJD_max'][:-1]
677 idb = np.argwhere(diff < 20.)
678 if len(idb) >= 1:
679 tab['MJD_min'][idb+1] = tab['MJD_max'][idb]+20.
680 tab['season_length'][idb+1] = tab['MJD_max'][idb+1] - \
681 tab['MJD_min'][idb+1]
683 return tab[tab['season_length'] > 30.]