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 

4import lsst.sims.utils as utils 

5import scipy 

6 

7__all__ = ['PeriodicDetectMetric'] 

8 

9 

10class PeriodicDetectMetric(BaseMetric): 

11 """Determine if we would be able to classify an object as periodic/non-uniform, using an F-test 

12 The idea here is that if a periodic source is aliased, it will be indistinguishable from a constant source, 

13 so we can find a best-fit constant, and if the reduced chi-squared is ~1, we know we are aliased. 

14 

15 Parameters 

16 ---------- 

17 

18 period : float (2) or array 

19 The period of the star (days). Can be a single value, or an array. If an array, amplitude and starMag 

20 should be arrays of equal length. 

21 amplitude : floar (0.1) 

22 The amplitude of the stellar variablility (mags). 

23 starMag : float (20.) 

24 The mean magnitude of the star in r (mags). 

25 sig_level : float (0.05) 

26 The value to use to compare to the p-value when deciding if we can reject the null hypothesis. 

27 SedTemplate : str ('F') 

28 The stellar SED template to use to generate realistic colors (default is an F star, so RR Lyrae-like) 

29 

30 Returns 

31 ------- 

32 

33 1 if we would detect star is variable, 0 if it is well-fit by a constant value. If using arrays to test multiple 

34 period-amplitude-mag combinations, will be the sum of the number of detected stars. 

35 """ 

36 def __init__(self, mjdCol='observationStartMJD', periods=2., amplitudes=0.1, m5Col='fiveSigmaDepth', 

37 metricName='PeriodicDetectMetric', filterCol='filter', starMags=20, sig_level=0.05, 

38 SedTemplate='F', **kwargs): 

39 

40 self.mjdCol = mjdCol 

41 self.m5Col = m5Col 

42 self.filterCol = filterCol 

43 if np.size(periods) == 1: 

44 self.periods = [periods] 

45 # Using the same magnitude for all filters. Could expand to fit the mean in each filter. 

46 self.starMags = [starMags] 

47 self.amplitudes = [amplitudes] 

48 else: 

49 self.periods = periods 

50 self.starMags = starMags 

51 self.amplitudes = amplitudes 

52 self.sig_level = sig_level 

53 self.SedTemplate = SedTemplate 

54 

55 super(PeriodicDetectMetric, self).__init__([mjdCol, m5Col, filterCol], metricName=metricName, 

56 units='N Detected (0, %i)' % np.size(periods), **kwargs) 

57 

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

59 result = 0 

60 n_pts = np.size(dataSlice[self.mjdCol]) 

61 n_filt = np.size(np.unique(dataSlice[self.filterCol])) 

62 

63 # If we had a correct model with phase, amplitude, period, mean_mags, then chi_squared/DoF would be ~1 with 3+n_filt free parameters. 

64 # The mean is one free parameter 

65 p1 = n_filt 

66 p2 = 3.+n_filt 

67 chi_sq_2 = 1.*(n_pts-p2) 

68 

69 u_filters = np.unique(dataSlice[self.filterCol]) 

70 

71 if n_pts > p2: 

72 for period, starMag, amplitude in zip(self.periods, self.starMags, self.amplitudes): 

73 chi_sq_1 = 0 

74 mags = utils.stellarMags(self.SedTemplate, rmag=starMag) 

75 for filtername in u_filters: 

76 in_filt = np.where(dataSlice[self.filterCol] == filtername)[0] 

77 lc = amplitude*np.sin(dataSlice[self.mjdCol][in_filt]*(np.pi*2)/period) + mags[filtername] 

78 snr = m52snr(lc, dataSlice[self.m5Col][in_filt]) 

79 delta_m = 2.5*np.log10(1.+1./snr) 

80 weights = 1./(delta_m**2) 

81 weighted_mean = np.sum(weights*lc)/np.sum(weights) 

82 chi_sq_1 += np.sum(((lc - weighted_mean)**2/delta_m**2)) 

83 # Yes, I'm fitting magnitudes rather than flux. At least I feel kinda bad about it. 

84 # F-test for nested models Regression problems: https://en.wikipedia.org/wiki/F-test 

85 f_numerator = (chi_sq_1 - chi_sq_2)/(p2-p1) 

86 f_denom = 1. # This is just reduced chi-squared for the more complicated model, so should be 1. 

87 f_val = f_numerator/f_denom 

88 # Has DoF (p2-p1, n-p2) 

89 # https://stackoverflow.com/questions/21494141/how-do-i-do-a-f-test-in-python/21503346 

90 p_value = scipy.stats.f.sf(f_val, p2-p1, n_pts-p2) 

91 if np.isfinite(p_value): 

92 if p_value < self.sig_level: 

93 result += 1 

94 

95 return result