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 

2import warnings 

3 

4from .moMetrics import BaseMoMetric 

5 

6__all__ = ['integrateOverH', 'ValueAtHMetric', 'MeanValueAtHMetric', 

7 'MoCompletenessMetric', 'MoCompletenessAtTimeMetric'] 

8 

9 

10def integrateOverH(Mvalues, Hvalues, Hindex = 0.33): 

11 """Function to calculate a metric value integrated over an Hrange, assuming a power-law distribution. 

12 

13 Parameters 

14 ---------- 

15 Mvalues : numpy.ndarray 

16 The metric values at each H value. 

17 Hvalues : numpy.ndarray 

18 The H values corresponding to each Mvalue (must be the same length). 

19 Hindex : float, opt 

20 The power-law index expected for the H value distribution. 

21 Default is 0.33 (dN/dH = 10^(Hindex * H) ). 

22 

23 Returns 

24 -------- 

25 numpy.ndarray 

26 The integrated or cumulative metric values. 

27 """ 

28 # Set expected H distribution. 

29 # dndh = differential size distribution (number in this bin) 

30 dndh = np.power(10., Hindex*(Hvalues-Hvalues.min())) 

31 # dn = cumulative size distribution (number in this bin and brighter) 

32 intVals = np.cumsum(Mvalues*dndh)/np.cumsum(dndh) 

33 return intVals 

34 

35 

36class ValueAtHMetric(BaseMoMetric): 

37 """Return the metric value at a given H value. 

38 

39 Requires the metric values to be one-dimensional (typically, completeness values). 

40 

41 Parameters 

42 ---------- 

43 Hmark : float, opt 

44 The H value at which to look up the metric value. Default = 22. 

45 """ 

46 def __init__(self, Hmark=22, **kwargs): 

47 metricName = 'Value At H=%.1f' %(Hmark) 

48 super(ValueAtHMetric, self).__init__(metricName=metricName, **kwargs) 

49 self.Hmark = Hmark 

50 

51 def run(self, metricVals, Hvals): 

52 # Check if desired H value is within range of H values. 

53 if (self.Hmark < Hvals.min()) or (self.Hmark > Hvals.max()): 

54 warnings.warn('Desired H value of metric outside range of provided H values.') 

55 return None 

56 if metricVals.shape[0] != 1: 

57 warnings.warn('This is not an appropriate summary statistic for this data - need 1d values.') 

58 return None 

59 value = np.interp(self.Hmark, Hvals, metricVals[0]) 

60 return value 

61 

62 

63class MeanValueAtHMetric(BaseMoMetric): 

64 """Return the mean value of a metric at a given H. 

65 

66 Allows the metric values to be multi-dimensional (i.e. use a cloned H distribution). 

67 

68 Parameters 

69 ---------- 

70 Hmark : float, opt 

71 The H value at which to look up the metric value. Default = 22. 

72 """ 

73 def __init__(self, Hmark=22, reduceFunc=np.mean, metricName=None, **kwargs): 

74 if metricName is None: 

75 metricName = 'Mean Value At H=%.1f' %(Hmark) 

76 super(MeanValueAtHMetric, self).__init__(metricName=metricName, **kwargs) 

77 self.Hmark = Hmark 

78 self.reduceFunc = reduceFunc 

79 

80 def run(self, metricVals, Hvals): 

81 # Check if desired H value is within range of H values. 

82 if (self.Hmark < Hvals.min()) or (self.Hmark > Hvals.max()): 

83 warnings.warn('Desired H value of metric outside range of provided H values.') 

84 return None 

85 value = np.interp(self.Hmark, Hvals, self.reduceFunc(metricVals.swapaxes(0, 1), axis=1)) 

86 return value 

87 

88 

89class MoCompletenessMetric(BaseMoMetric): 

90 """Calculate the fraction of the population that meets `threshold` value or higher. 

91 This is equivalent to calculating the completeness (relative to the entire population) given 

92 the output of a Discovery_N_Chances metric, or the fraction of the population that meets a given cutoff 

93 value for Color determination metrics. 

94 

95 Any moving object metric that outputs a float value can thus have the 'fraction of the population' 

96 with greater than X value calculated here, as a summary statistic. 

97 

98 Parameters 

99 ---------- 

100 threshold : int, opt 

101 Count the fraction of the population that exceeds this value. Default = 1. 

102 nbins : int, opt 

103 If the H values for the metric are not a cloned distribution, then split up H into this many bins. 

104 Default 20. 

105 minHrange : float, opt 

106 If the H values for the metric are not a cloned distribution, then split up H into at least this 

107 range (otherwise just use the min/max of the H values). Default 1.0 

108 cumulative : bool, opt 

109 If False, simply report the differential fractional value (or differential completeness). 

110 If True, integrate over the H distribution (using IntegrateOverH) to report a cumulative fraction. 

111 Default None which becomes True; 

112 if metricName is set and starts with 'Differential' this will then set to False. 

113 Hindex : float, opt 

114 Use Hindex as the power law to integrate over H, if cumulative is True. Default 0.3. 

115 """ 

116 def __init__(self, threshold=1, nbins=20, minHrange=1.0, cumulative=None, Hindex=0.33, **kwargs): 

117 if cumulative is None: # if metricName does not start with 'differential', then cumulative->True 

118 if 'metricName' not in kwargs: 

119 self.cumulative = True 

120 metricName = 'CumulativeCompleteness' 

121 units = '<= H' 

122 else: # 'metricName' in kwargs: 

123 metricName = kwargs.pop('metricName') 

124 if metricName.lower().startswith('differential'): 

125 self.cumulative=False 

126 units = '@ H' 

127 else: 

128 self.cumulative=True 

129 units = '<= H' 

130 else: # cumulative was set 

131 self.cumulative = cumulative 

132 if 'metricName' in kwargs: 

133 metricName = kwargs.pop('metricName') 

134 if metricName.lower().startswith('differential') and self.cumulative: 

135 warnings.warn(f'Completeness metricName is {metricName} but cumulative is True') 

136 else: 

137 if self.cumulative: 

138 metricName = 'CumulativeCompleteness' 

139 units = '<= H' 

140 else: 

141 metricName = 'DifferentialCompleteness' 

142 units = '@ H' 

143 super().__init__(metricName=metricName, units=units, **kwargs) 

144 self.threshold = threshold 

145 # If H is not a cloned distribution, then we need to specify how to bin these values. 

146 self.nbins = nbins 

147 self.minHrange = minHrange 

148 self.Hindex = Hindex 

149 

150 def run(self, metricValues, Hvals): 

151 nSsos = metricValues.shape[0] 

152 nHval = len(Hvals) 

153 metricValH = metricValues.swapaxes(0, 1) 

154 if nHval == metricValues.shape[1]: 

155 # Hvals array is probably the same as the cloned H array. 

156 completeness = np.zeros(len(Hvals), float) 

157 for i, H in enumerate(Hvals): 

158 completeness[i] = np.where(metricValH[i].filled(0) >= self.threshold)[0].size 

159 completeness = completeness / float(nSsos) 

160 else: 

161 # The Hvals are spread more randomly among the objects (we probably used one per object). 

162 hrange = Hvals.max() - Hvals.min() 

163 minH = Hvals.min() 

164 if hrange < self.minHrange: 

165 hrange = self.minHrange 

166 minH = Hvals.min() - hrange/2.0 

167 stepsize = hrange / float(self.nbins) 

168 bins = np.arange(minH, minH + hrange + stepsize/2.0, stepsize) 

169 Hvals = bins[:-1] 

170 n_all, b = np.histogram(metricValH[0], bins) 

171 condition = np.where(metricValH[0] >= self.threshold)[0] 

172 n_found, b = np.histogram(metricValH[0][condition], bins) 

173 completeness = n_found.astype(float) / n_all.astype(float) 

174 completeness = np.where(n_all==0, 0, completeness) 

175 if self.cumulative: 

176 completenessInt = integrateOverH(completeness, Hvals, self.Hindex) 

177 summaryVal = np.empty(len(completenessInt), dtype=[('name', np.str_, 20), ('value', float)]) 

178 summaryVal['value'] = completenessInt 

179 for i, Hval in enumerate(Hvals): 

180 summaryVal['name'][i] = 'H <= %f' % (Hval) 

181 else: 

182 summaryVal = np.empty(len(completeness), dtype=[('name', np.str_, 20), ('value', float)]) 

183 summaryVal['value'] = completeness 

184 for i, Hval in enumerate(Hvals): 

185 summaryVal['name'][i] = 'H = %f' % (Hval) 

186 return summaryVal 

187 

188class MoCompletenessAtTimeMetric(BaseMoMetric): 

189 """Calculate the completeness (relative to the entire population) <= a given H as a function of time, 

190 given the times of each discovery. 

191 

192 Input values of the discovery times can come from the Discovery_Time (child) metric or the 

193 KnownObjects metric. 

194 

195 Parameters 

196 ---------- 

197 times : numpy.ndarray like 

198 The bins to distribute the discovery times into. Same units as the discovery time (typically MJD). 

199 Hval : float, opt 

200 The value of H to count completeness at (or cumulative completeness to). 

201 Default None, in which case a value halfway through Hvals (the slicer H range) will be chosen. 

202 cumulative : bool, opt 

203 If True, calculate the cumulative completeness (completeness <= H). 

204 If False, calculate the differential completeness (completeness @ H). 

205 Default None which becomes 'True' unless metricName starts with 'differential'. 

206 Hindex : float, opt 

207 Use Hindex as the power law to integrate over H, if cumulative is True. Default 0.3. 

208 """ 

209 

210 def __init__(self, times, Hval=None, cumulative=None, Hindex=0.33, **kwargs): 

211 self.Hval = Hval 

212 self.times = times 

213 self.Hindex = Hindex 

214 if cumulative is None: # if metricName does not start with 'differential', then cumulative->True 

215 if 'metricName' not in kwargs: 

216 self.cumulative = True 

217 metricName = 'CumulativeCompleteness@Time@H=%.2f' % self.Hval 

218 else: # 'metricName' in kwargs: 

219 metricName = kwargs.pop('metricName') 

220 if metricName.lower().startswith('differential'): 

221 self.cumulative=False 

222 else: 

223 self.cumulative=True 

224 else: # cumulative was set 

225 self.cumulative = cumulative 

226 if 'metricName' in kwargs: 

227 metricName = kwargs.pop('metricName') 

228 if metricName.lower().startswith('differential') and self.cumulative: 

229 warnings.warn(f'Completeness metricName is {metricName} but cumulative is True') 

230 else: 

231 if self.cumulative: 

232 metricName = 'CumulativeCompleteness@Time@H=%.2f' % self.Hval 

233 else: 

234 metricName = 'DifferentialCompleteness@Time@H=%.2f' % self.Hval 

235 self._setLabels() 

236 super().__init__(metricName=metricName, units=self.units, **kwargs) 

237 

238 def _setLabels(self): 

239 if self.Hval is not None: 

240 if self.cumulative: 

241 self.units = 'H <=%.1f' % (self.Hval) 

242 else: 

243 self.units = 'H = %.1f' % (self.Hval) 

244 else: 

245 self.units = 'H' 

246 

247 def run(self, discoveryTimes, Hvals): 

248 if len(Hvals) != discoveryTimes.shape[1]: 

249 warnings.warn("This summary metric expects cloned H distribution. Cannot calculate summary.") 

250 return 

251 nSsos = discoveryTimes.shape[0] 

252 timesinH = discoveryTimes.swapaxes(0, 1) 

253 completenessH = np.empty([len(Hvals), len(self.times)], float) 

254 for i, H in enumerate(Hvals): 

255 n, b = np.histogram(timesinH[i].compressed(), bins=self.times) 

256 completenessH[i][0] = 0 

257 completenessH[i][1:] = n.cumsum() 

258 completenessH = completenessH / float(nSsos) 

259 completeness = completenessH.swapaxes(0, 1) 

260 if self.cumulative: 

261 for i, t in enumerate(self.times): 

262 completeness[i] = integrateOverH(completeness[i], Hvals) 

263 # To save the summary statistic, we must pick out a given H value. 

264 if self.Hval is None: 

265 Hidx = len(Hvals) // 2 

266 self.Hval = Hvals[Hidx] 

267 self._setLabels() 

268 else: 

269 Hidx = np.where(np.abs(Hvals - self.Hval) == np.abs(Hvals - self.Hval).min())[0][0] 

270 self.Hval = Hvals[Hidx] 

271 self._setLabels() 

272 summaryVal = np.empty(len(self.times), dtype=[('name', np.str_, 20), ('value', float)]) 

273 summaryVal['value'] = completeness[:, Hidx] 

274 for i, time in enumerate(self.times): 

275 summaryVal['name'][i] = '%s @ %.2f' % (self.units, time) 

276 return summaryVal 

277 

278