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 def __init__(self, col='observationStartMJD', nPeriods=5, periodMin=3., periodMax=35., nVisitsMin=3, 

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

13 """ 

14 Construct an instance of a PhaseGapMetric class 

15 

16 :param col: Name of the column to use for the observation times, commonly 'observationStartMJD' 

17 :param nPeriods: Number of periods to test 

18 :param periodMin: Minimum period to test (days) 

19 :param periodMax: Maximimum period to test (days) 

20 :param nVistisMin: minimum number of visits necessary before looking for the phase gap 

21 """ 

22 self.periodMin = periodMin 

23 self.periodMax = periodMax 

24 self.nPeriods = nPeriods 

25 self.nVisitsMin = nVisitsMin 

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

27 

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

29 """ 

30 Run the PhaseGapMetric. 

31 :param dataSlice: Data for this slice. 

32 :param slicePoint: Metadata for the slice (Optional as not used here). 

33 :return: a dictionary of the periods used here and the corresponding largest gaps. 

34 """ 

35 if len(dataSlice) < self.nVisitsMin: 

36 return self.badval 

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

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

39 if step == 0: 

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

41 else: 

42 periods = np.arange(self.nPeriods) 

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

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

45 

46 for i, period in enumerate(periods): 

47 # For each period, calculate the phases. 

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

49 phases = np.sort(phases) 

50 # Find the largest gap in coverage. 

51 gaps = np.diff(phases) 

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

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

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

55 

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

57 

58 def reduceMeanGap(self, metricVal): 

59 """ 

60 At each slicepoint, return the mean gap value. 

61 """ 

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

63 

64 def reduceMedianGap(self, metricVal): 

65 """ 

66 At each slicepoint, return the median gap value. 

67 """ 

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

69 

70 def reduceWorstPeriod(self, metricVal): 

71 """ 

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

73 """ 

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

75 return worstP 

76 

77 def reduceLargestGap(self, metricVal): 

78 """ 

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

80 """ 

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

82 

83 

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

85class PeriodicQualityMetric(BaseMetric): 

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

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

88 self.mjdCol = mjdCol 

89 self.m5Col = m5Col 

90 self.period = period 

91 self.starMag = starMag 

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

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

94 

95 def _calc_phase(self, dataSlice): 

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

97 """ 

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

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

100 x = np.cos(angles) 

101 y = np.sin(angles) 

102 

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

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

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

106 

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

108 return 1.-vector_off 

109 

110 def _calc_amp(self, dataSlice): 

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

112 """ 

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

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

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

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

117 

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

119 return amp_snr/max_snr 

120 

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

122 amplitude_fraction = self._calc_amp(dataSlice) 

123 phase_fraction = self._calc_phase(dataSlice) 

124 return amplitude_fraction * phase_fraction