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

1import numpy as np 

2from .baseMetric import BaseMetric 

3from lsst.sims.maf.utils import m52snr 

4 

5__all__ = ['PhaseGapMetric', 'PeriodicQualityMetric'] 

6 

7class PhaseGapMetric(BaseMetric): 

8 """ 

9 Measure the maximum gap in phase coverage for observations of periodic variables. 

10 

11 Parameters 

12 ---------- 

13 col: str, opt 

14 Name of the column to use for the observation times (MJD) 

15 nPeriods: int, opt 

16 Number of periods to test 

17 periodMin: float, opt 

18 Minimum period to test, in days. 

19 periodMax: float, opt 

20 Maximum period to test, in days 

21 nVisitsMin: int, opt 

22 Minimum number of visits necessary before looking for the phase gap. 

23 """ 

24 def __init__(self, col='observationStartMJD', nPeriods=5, periodMin=3., periodMax=35., nVisitsMin=3, 

25 metricName='Phase Gap', **kwargs): 

26 self.periodMin = periodMin 

27 self.periodMax = periodMax 

28 self.nPeriods = nPeriods 

29 self.nVisitsMin = nVisitsMin 

30 super(PhaseGapMetric, self).__init__(col, metricName=metricName, units='Fraction, 0-1', **kwargs) 

31 

32 def run(self, dataSlice, slicePoint=None): 

33 if len(dataSlice) < self.nVisitsMin: 

34 return self.badval 

35 # Create 'nPeriods' evenly spaced periods within range of min to max. 

36 step = (self.periodMax-self.periodMin)/self.nPeriods 

37 if step == 0: 

38 periods = np.array([self.periodMin]) 

39 else: 

40 periods = np.arange(self.nPeriods) 

41 periods = periods/np.max(periods)*(self.periodMax-self.periodMin)+self.periodMin 

42 maxGap = np.zeros(self.nPeriods, float) 

43 

44 for i, period in enumerate(periods): 

45 # For each period, calculate the phases. 

46 phases = (dataSlice[self.colname] % period)/period 

47 phases = np.sort(phases) 

48 # Find the largest gap in coverage. 

49 gaps = np.diff(phases) 

50 start_to_end = np.array([1.0 - phases[-1] + phases[0]], float) 

51 gaps = np.concatenate([gaps, start_to_end]) 

52 maxGap[i] = np.max(gaps) 

53 

54 return {'periods':periods, 'maxGaps':maxGap} 

55 

56 def reduceMeanGap(self, metricVal): 

57 """ 

58 At each slicepoint, return the mean gap value. 

59 """ 

60 return np.mean(metricVal['maxGaps']) 

61 

62 def reduceMedianGap(self, metricVal): 

63 """ 

64 At each slicepoint, return the median gap value. 

65 """ 

66 return np.median(metricVal['maxGaps']) 

67 

68 def reduceWorstPeriod(self, metricVal): 

69 """ 

70 At each slicepoint, return the period with the largest phase gap. 

71 """ 

72 worstP = metricVal['periods'][np.where(metricVal['maxGaps'] == metricVal['maxGaps'].max())] 

73 return worstP 

74 

75 def reduceLargestGap(self, metricVal): 

76 """ 

77 At each slicepoint, return the largest phase gap value. 

78 """ 

79 return np.max(metricVal['maxGaps']) 

80 

81 

82# To fit a periodic source well, you need to cover the full phase, and fit the amplitude. 

83class PeriodicQualityMetric(BaseMetric): 

84 def __init__(self, mjdCol='observationStartMJD', period=2., m5Col='fiveSigmaDepth', 

85 metricName='PhaseCoverageMetric', starMag=20, **kwargs): 

86 self.mjdCol = mjdCol 

87 self.m5Col = m5Col 

88 self.period = period 

89 self.starMag = starMag 

90 super(PeriodicQualityMetric, self).__init__([mjdCol, m5Col], metricName=metricName, 

91 units='Fraction, 0-1', **kwargs) 

92 

93 def _calc_phase(self, dataSlice): 

94 """1 is perfectly balanced phase coverage, 0 is no effective coverage. 

95 """ 

96 angles = dataSlice[self.mjdCol] % self.period 

97 angles = angles/self.period * 2.*np.pi 

98 x = np.cos(angles) 

99 y = np.sin(angles) 

100 

101 snr = m52snr(self.starMag, dataSlice[self.m5Col]) 

102 x_ave = np.average(x, weights=snr) 

103 y_ave = np.average(y, weights=snr) 

104 

105 vector_off = np.sqrt(x_ave**2+y_ave**2) 

106 return 1.-vector_off 

107 

108 def _calc_amp(self, dataSlice): 

109 """Fractional SNR on the amplitude, testing for a variety of possible phases 

110 """ 

111 phases = np.arange(0, np.pi, np.pi/8.) 

112 snr = m52snr(self.starMag, dataSlice[self.m5Col]) 

113 amp_snrs = np.sin(dataSlice[self.mjdCol]/self.period*2*np.pi + phases[:, np.newaxis])*snr 

114 amp_snr = np.min(np.sqrt(np.sum(amp_snrs**2, axis=1))) 

115 

116 max_snr = np.sqrt(np.sum(snr**2)) 

117 return amp_snr/max_snr 

118 

119 def run(self, dataSlice, slicePoint=None): 

120 amplitude_fraction = self._calc_amp(dataSlice) 

121 phase_fraction = self._calc_phase(dataSlice) 

122 return amplitude_fraction * phase_fraction