Coverage for python/lsst/sims/maf/metrics/periodicDetectMetric.py : 15%

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
7__all__ = ['PeriodicDetectMetric']
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.
15 Parameters
16 ----------
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)
30 Returns
31 -------
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):
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
55 super(PeriodicDetectMetric, self).__init__([mjdCol, m5Col, filterCol], metricName=metricName,
56 units='N Detected (0, %i)' % np.size(periods), **kwargs)
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]))
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)
69 u_filters = np.unique(dataSlice[self.filterCol])
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
95 return result