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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

from builtins import zip 

# Example of more complex metric 

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

# Returns variable length array of data 

# Uses multiple reduce functions 

 

import numpy as np 

from .baseMetric import BaseMetric 

 

__all__ = ['VisitGroupsMetric', 'PairFractionMetric'] 

 

 

class PairFractionMetric(BaseMetric): 

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

 

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

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

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

pairs twice). 

 

Parameters 

---------- 

minGap : float, opt 

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

maxGap : float, opt 

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

""" 

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

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

self.mjdCol = mjdCol 

self.minGap = minGap/60./24. 

self.maxGap = maxGap/60./24. 

units = '' 

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

 

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

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

i = np.tile(dataSlice[self.mjdCol], (nobs, 1)) 

j = i.T 

tdiff = np.abs(i-j) 

part_pair = np.zeros(i.shape, dtype=int) 

good = np.where((tdiff < self.maxGap) & (tdiff > self.minGap)) 

part_pair[good] = 1 

part_pair = np.max(part_pair, axis=1) 

result = np.sum(part_pair)/float(nobs) 

return result 

 

 

class VisitGroupsMetric(BaseMetric): 

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

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

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

**kwargs): 

""" 

Instantiate metric. 

 

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

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

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

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

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

from any other visit) required, 

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

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

""" 

self.times = timeCol 

self.nights = nightsCol 

eps = 1e-10 

self.deltaTmin = float(deltaTmin) - eps 

self.deltaTmax = float(deltaTmax) 

self.minNVisits = int(minNVisits) 

self.window = int(window) 

self.minNNights = int(minNNights) 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

%(self.minNNights, self.minNVisits) 

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

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

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

 

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

""" 

Return a dictionary of: 

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

and the nights with visits > minNVisits. 

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

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

 

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

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

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

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

""" 

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

nights = [] 

visitNum = [] 

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

for n in uniquenights: 

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

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

nvisits = 0 

ntooclose = 0 

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

timediff = np.diff(times) 

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

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

if len(timegood)>1: 

# Count visits for all but last index in timediff 

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

if tg1: 

nvisits += 1 

if not tg2: 

nvisits += 1 

if ttc1: 

ntooclose += 1 

if not tg2 and not ttc2: 

ntooclose += 1 

# Take care of last timediff 

if timegood[-1]: 

nvisits += 2 #close out visit sequence 

if timetooclose[-1]: 

ntooclose += 1 

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

ntooclose += 1 

else: 

if timegood: 

nvisits += 2 

# Count up all visits for night. 

if nvisits > 0: 

nvisits = nvisits + ntooclose/2.0 

visitNum.append(nvisits) 

nights.append(n) 

# Convert to numpy arrays. 

visitNum = np.array(visitNum) 

nights = np.array(nights) 

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

155 ↛ 156line 155 didn't jump to line 156, because the condition on line 155 was never true if len(visitNum) == 0: 

return self.badval 

return metricval 

 

def reduceMedian(self, metricval): 

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

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

 

def reduceNNightsWithNVisits(self, metricval): 

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

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

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

 

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

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

condition2 = (visits[condition] >= minNVisits) 

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

 

def reduceNVisitsInWindow(self, metricval): 

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

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

maxnvisits = 0 

for n in metricval['nights']: 

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

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

return maxnvisits 

 

def reduceNNightsInWindow(self, metricval): 

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

maxnights = 0 

for n in metricval['nights']: 

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

maxnights = max(len(nw), maxnights) 

return maxnights 

 

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

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

return visits[condition], nights[condition] 

 

def reduceNLunations(self, metricval): 

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

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

""" 

lunationLength = 30 

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

nLunations = 0 

for l in lunations: 

# Find visits within lunation. 

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

for n in nl: 

# Find visits which are in groups within the lunation. 

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

207 ↛ 204line 207 didn't jump to line 204, because the condition on line 207 was never false if len(nw) >= self.minNNights: 

nLunations += 1 

break 

return nLunations 

 

def reduceMaxSeqLunations(self, metricval): 

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

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

""" 

lunationLength = 30 

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

maxSequence = 0 

curSequence = 0 

inSeq = False 

for l in lunations: 

# Find visits within lunation. 

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

# If no visits this lunation: 

if len(vl) == 0: 

inSeq = False 

maxSequence = max(maxSequence, curSequence) 

curSequence = 0 

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

for n in nl: 

# Find visits which are in groups within the lunation. 

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

# If there was a group within this lunation: 

234 ↛ 240line 234 didn't jump to line 240, because the condition on line 234 was never false if len(nw) >= self.minNNights: 

curSequence += 1 

inSeq = True 

break 

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

else: 

inSeq = False 

maxSequence = max(maxSequence, curSequence) 

curSequence = 0 

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

maxSequence = max(maxSequence, curSequence) 

return maxSequence