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

import numpy as np 

from .baseMetric import BaseMetric 

from lsst.sims.maf.utils import m52snr 

 

__all__ = ['PhaseGapMetric', 'PeriodicQualityMetric'] 

 

class PhaseGapMetric(BaseMetric): 

""" 

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

""" 

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

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

""" 

Construct an instance of a PhaseGapMetric class 

 

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

:param nPeriods: Number of periods to test 

:param periodMin: Minimum period to test (days) 

:param periodMax: Maximimum period to test (days) 

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

""" 

self.periodMin = periodMin 

self.periodMax = periodMax 

self.nPeriods = nPeriods 

self.nVisitsMin = nVisitsMin 

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

 

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

""" 

Run the PhaseGapMetric. 

:param dataSlice: Data for this slice. 

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

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

""" 

if len(dataSlice) < self.nVisitsMin: 

return self.badval 

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

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

if step == 0: 

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

else: 

periods = np.arange(self.nPeriods) 

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

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

 

for i, period in enumerate(periods): 

# For each period, calculate the phases. 

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

phases = np.sort(phases) 

# Find the largest gap in coverage. 

gaps = np.diff(phases) 

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

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

maxGap[i] = np.max(gaps) 

 

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

 

def reduceMeanGap(self, metricVal): 

""" 

At each slicepoint, return the mean gap value. 

""" 

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

 

def reduceMedianGap(self, metricVal): 

""" 

At each slicepoint, return the median gap value. 

""" 

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

 

def reduceWorstPeriod(self, metricVal): 

""" 

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

""" 

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

return worstP 

 

def reduceLargestGap(self, metricVal): 

""" 

At each slicepoint, return the largest phase gap value. 

""" 

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

 

 

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

class PeriodicQualityMetric(BaseMetric): 

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

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

self.mjdCol = mjdCol 

self.m5Col = m5Col 

self.period = period 

self.starMag = starMag 

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

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

 

def _calc_phase(self, dataSlice): 

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

""" 

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

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

x = np.cos(angles) 

y = np.sin(angles) 

 

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

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

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

 

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

return 1.-vector_off 

 

def _calc_amp(self, dataSlice): 

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

""" 

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

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

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

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

 

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

return amp_snr/max_snr 

 

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

amplitude_fraction = self._calc_amp(dataSlice) 

phase_fraction = self._calc_phase(dataSlice) 

return amplitude_fraction * phase_fraction