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 builtins import zip 

2# Example of more complex metric 

3# Takes multiple columns of data (although 'night' could be calculable from 'expmjd') 

4# Returns variable length array of data 

5# Uses multiple reduce functions 

6 

7import numpy as np 

8from .baseMetric import BaseMetric 

9 

10__all__ = ['VisitGroupsMetric', 'PairFractionMetric'] 

11 

12 

13class PairFractionMetric(BaseMetric): 

14 """What fraction of observations are part of a pair. 

15 

16 Note, an observation can be a member of more than one "pair". For example, 

17 t=[0, 5, 30], all observations would be considered part of a pair because they 

18 all have an observation within the given window to pair with (the observation at t=30 

19 pairs twice). 

20 

21 Parameters 

22 ---------- 

23 minGap : float, opt 

24 Minimum time to consider something part of a pair (minutes). Default 15. 

25 maxGap : float, opt 

26 Maximum time to consider something part of a pair (minutes). Default 90. 

27 """ 

28 def __init__(self, mjdCol='observationStartMJD', metricName='PairFraction', 

29 minGap=15., maxGap=90., **kwargs): 

30 self.mjdCol = mjdCol 

31 self.minGap = minGap/60./24. 

32 self.maxGap = maxGap/60./24. 

33 units = '' 

34 super(PairFractionMetric, self).__init__(col=[mjdCol], metricName=metricName, units=units, **kwargs) 

35 

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

37 nobs = np.size(dataSlice[self.mjdCol]) 

38 times = np.sort(dataSlice[self.mjdCol]) 

39 

40 # Check which ones have a forard match 

41 t_plus = times + self.maxGap 

42 t_minus = times + self.minGap 

43 ind1 = np.searchsorted(times, t_plus) 

44 ind2 = np.searchsorted(times, t_minus) 

45 # If ind1 and ind2 are the same, there is no pairable image for that exposure 

46 diff1 = ind1 - ind2 

47 

48 # Check which have a back match 

49 t_plus = times - self.maxGap 

50 t_minus = times - self.minGap 

51 ind1 = np.searchsorted(times, t_plus) 

52 ind2 = np.searchsorted(times, t_minus) 

53 

54 diff2 = ind1 - ind2 

55 

56 # The exposure has a pair ahead or behind 

57 is_paired = np.where((diff1 != 0) | (diff2 != 0))[0] 

58 result = np.size(is_paired)/float(nobs) 

59 return result 

60 

61 

62class VisitGroupsMetric(BaseMetric): 

63 """Count the number of visits per night within deltaTmin and deltaTmax.""" 

64 def __init__(self, timeCol='observationStartMJD', nightsCol='night', metricName='VisitGroups', 

65 deltaTmin=15.0/60.0/24.0, deltaTmax=90.0/60.0/24.0, minNVisits=2, window=30, minNNights=3, 

66 **kwargs): 

67 """ 

68 Instantiate metric. 

69 

70 'timeCol' = column with the time of the visit (default expmjd), 

71 'nightsCol' = column with the night of the visit (default night), 

72 'deltaTmin' = minimum time of window: units are days (default 15 min), 

73 'deltaTmax' = maximum time of window: units are days (default 90 min), 

74 'minNVisits' = the minimum number of visits within a night (with spacing between deltaTmin/max 

75 from any other visit) required, 

76 'window' = the number of nights to consider within a window (for reduce methods), 

77 'minNNights' = the minimum required number of nights within window to make a full 'group'. 

78 """ 

79 self.times = timeCol 

80 self.nights = nightsCol 

81 eps = 1e-10 

82 self.deltaTmin = float(deltaTmin) - eps 

83 self.deltaTmax = float(deltaTmax) 

84 self.minNVisits = int(minNVisits) 

85 self.window = int(window) 

86 self.minNNights = int(minNNights) 

87 super(VisitGroupsMetric, self).__init__(col=[self.times, self.nights], metricName=metricName, **kwargs) 

88 self.reduceOrder = {'Median':0, 'NNightsWithNVisits':1, 'NVisitsInWindow':2, 

89 'NNightsInWindow':3, 'NLunations':4, 'MaxSeqLunations':5} 

90 self.comment = 'Evaluation of the number of visits within a night, with separations between ' 

91 self.comment += 'tMin %.1f and tMax %.1f minutes.' %(self.deltaTmin*24.0*60., self.deltaTmax*24.0*60.) 

92 self.comment += 'Groups of visits use a minimum number of visits per night of %d, ' %(self.minNVisits) 

93 self.comment += 'and minimum number of nights of %d.' %(self.minNNights) 

94 self.comment += 'Two visits within this interval would count as 2. ' 

95 self.comment += 'Visits closer than tMin, paired with visits that do fall within tMin/tMax, ' 

96 self.comment += 'count as half visits. <br>' 

97 self.comment += 'VisitsGroups_Median calculates the median number of visits between tMin/tMax for ' 

98 self.comment += 'all nights. <br>' 

99 self.comment += 'VisitGroups_NNightsWithNNights calculates the number of nights that have at ' 

100 self.comment += 'least %d visits. <br>' %(self.minNVisits) 

101 self.comment += 'VisitGroups_NVisitsInWindow calculates the max number of visits within a window of ' 

102 self.comment += '%d days. <br>' %(self.window) 

103 self.comment += 'VisitGroups_NNightsInWindow calculates the max number of nights that have more ' 

104 self.comment += 'than %d visits within %d days. <br>' %(self.minNVisits, self.window) 

105 self.comment += 'VisitGroups_NLunations calculates the number of lunations (30 days) that have ' 

106 self.comment += 'at least one group of more than %d nights with more than %d visits, within '\ 

107 %(self.minNNights, self.minNVisits) 

108 self.comment += '%d days. <br>' %(self.window) 

109 self.comment += 'VisitGroups_MaxSeqLunations calculates the maximum sequential lunations that have ' 

110 self.comment += 'at least one "group". <br>' 

111 

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

113 """ 

114 Return a dictionary of: 

115 the number of visits within a night (within delta tmin/tmax of another visit), 

116 and the nights with visits > minNVisits. 

117 Count two visits which are within tmin of each other, but which have another visit 

118 within tmin/tmax interval, as one and a half (instead of two). 

119 

120 So for example: 4 visits, where 1, 2, 3 were all within deltaTMax of each other, and 4 was later but 

121 within deltaTmax of visit 3 -- would give you 4 visits. If visit 1 and 2 were closer together 

122 than deltaTmin, the two would be counted as 1.5 visits together (if only 1 and 2 existed, 

123 then there would be 0 visits as none would be within the qualifying time interval). 

124 """ 

125 uniquenights = np.unique(dataSlice[self.nights]) 

126 nights = [] 

127 visitNum = [] 

128 # Find the nights with visits within deltaTmin/max of one another and count the number of visits 

129 for n in uniquenights: 

130 condition = (dataSlice[self.nights] == n) 

131 times = np.sort(dataSlice[self.times][condition]) 

132 nvisits = 0 

133 ntooclose = 0 

134 # Calculate difference between each visit and time of previous visit (tnext- tnow) 

135 timediff = np.diff(times) 

136 timegood = np.where((timediff <= self.deltaTmax) & (timediff >= self.deltaTmin), True, False) 

137 timetooclose = np.where(timediff < self.deltaTmin, True, False) 

138 if len(timegood)>1: 

139 # Count visits for all but last index in timediff 

140 for tg1, ttc1, tg2, ttc2 in zip(timegood[:-1], timetooclose[:-1], timegood[1:], timetooclose[1:]): 

141 if tg1: 

142 nvisits += 1 

143 if not tg2: 

144 nvisits += 1 

145 if ttc1: 

146 ntooclose += 1 

147 if not tg2 and not ttc2: 

148 ntooclose += 1 

149 # Take care of last timediff 

150 if timegood[-1]: 

151 nvisits += 2 #close out visit sequence 

152 if timetooclose[-1]: 

153 ntooclose += 1 

154 if not timegood[-2] and not timetooclose[-2]: 

155 ntooclose += 1 

156 else: 

157 if timegood: 

158 nvisits += 2 

159 # Count up all visits for night. 

160 if nvisits > 0: 

161 nvisits = nvisits + ntooclose/2.0 

162 visitNum.append(nvisits) 

163 nights.append(n) 

164 # Convert to numpy arrays. 

165 visitNum = np.array(visitNum) 

166 nights = np.array(nights) 

167 metricval = {'visits':visitNum, 'nights':nights} 

168 if len(visitNum) == 0: 

169 return self.badval 

170 return metricval 

171 

172 def reduceMedian(self, metricval): 

173 """Reduce to median number of visits per night.""" 

174 return np.median(metricval['visits']) 

175 

176 def reduceNNightsWithNVisits(self, metricval): 

177 """Reduce to total number of nights with more than 'minNVisits' visits.""" 

178 condition = (metricval['visits'] >= self.minNVisits) 

179 return len(metricval['visits'][condition]) 

180 

181 def _inWindow(self, visits, nights, night, window, minNVisits): 

182 condition = ((nights >= night) & (nights < night+window)) 

183 condition2 = (visits[condition] >= minNVisits) 

184 return visits[condition][condition2], nights[condition][condition2] 

185 

186 def reduceNVisitsInWindow(self, metricval): 

187 """Reduce to max number of total visits on all nights with more than minNVisits, 

188 within any 'window' (default=30 nights).""" 

189 maxnvisits = 0 

190 for n in metricval['nights']: 

191 vw, nw = self._inWindow(metricval['visits'], metricval['nights'], n, self.window, self.minNVisits) 

192 maxnvisits = max((vw.sum(), maxnvisits)) 

193 return maxnvisits 

194 

195 def reduceNNightsInWindow(self, metricval): 

196 """Reduce to max number of nights with more than minNVisits, within 'window' over all windows.""" 

197 maxnights = 0 

198 for n in metricval['nights']: 

199 vw, nw = self._inWindow(metricval['visits'], metricval['nights'], n, self.window, self.minNVisits) 

200 maxnights = max(len(nw), maxnights) 

201 return maxnights 

202 

203 def _inLunation(self, visits, nights, lunationStart, lunationLength): 

204 condition = ((nights >= lunationStart) & (nights < lunationStart+lunationLength)) 

205 return visits[condition], nights[condition] 

206 

207 def reduceNLunations(self, metricval): 

208 """Reduce to number of lunations (unique 30 day windows) that contain at least one 'group': 

209 a set of more than minNVisits per night, with more than minNNights of visits within 'window' time period. 

210 """ 

211 lunationLength = 30 

212 lunations = np.arange(metricval['nights'][0], metricval['nights'][-1]+lunationLength/2.0, lunationLength) 

213 nLunations = 0 

214 for l in lunations: 

215 # Find visits within lunation. 

216 vl, nl = self._inLunation(metricval['visits'], metricval['nights'], l, lunationLength) 

217 for n in nl: 

218 # Find visits which are in groups within the lunation. 

219 vw, nw = self._inWindow(vl, nl, n, self.window, self.minNVisits) 

220 if len(nw) >= self.minNNights: 

221 nLunations += 1 

222 break 

223 return nLunations 

224 

225 def reduceMaxSeqLunations(self, metricval): 

226 """Count the max number of sequential lunations (unique 30 day windows) that contain at least one 'group': 

227 a set of more than minNVisits per night, with more than minNNights of visits within 'window' time period. 

228 """ 

229 lunationLength = 30 

230 lunations = np.arange(metricval['nights'][0], metricval['nights'][-1]+lunationLength/2.0, lunationLength) 

231 maxSequence = 0 

232 curSequence = 0 

233 inSeq = False 

234 for l in lunations: 

235 # Find visits within lunation. 

236 vl, nl = self._inLunation(metricval['visits'], metricval['nights'], l, lunationLength) 

237 # If no visits this lunation: 

238 if len(vl) == 0: 

239 inSeq = False 

240 maxSequence = max(maxSequence, curSequence) 

241 curSequence = 0 

242 # Else, look to see if groups can be made from the visits. 

243 for n in nl: 

244 # Find visits which are in groups within the lunation. 

245 vw, nw = self._inWindow(vl, nl, n, self.window, self.minNVisits) 

246 # If there was a group within this lunation: 

247 if len(nw) >= self.minNNights: 

248 curSequence += 1 

249 inSeq = True 

250 break 

251 # Otherwise we're not in a sequence (anymore, or still). 

252 else: 

253 inSeq = False 

254 maxSequence = max(maxSequence, curSequence) 

255 curSequence = 0 

256 # Pick up last sequence if were in a sequence at last lunation. 

257 maxSequence = max(maxSequence, curSequence) 

258 return maxSequence