Coverage for python/lsst/sims/maf/metrics/phaseGapMetric.py : 23%

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
5__all__ = ['PhaseGapMetric', 'PeriodicQualityMetric']
7class PhaseGapMetric(BaseMetric):
8 """
9 Measure the maximum gap in phase coverage for observations of periodic variables.
10 """
11 def __init__(self, col='observationStartMJD', nPeriods=5, periodMin=3., periodMax=35., nVisitsMin=3,
12 metricName='Phase Gap', **kwargs):
13 """
14 Construct an instance of a PhaseGapMetric class
16 :param col: Name of the column to use for the observation times, commonly 'observationStartMJD'
17 :param nPeriods: Number of periods to test
18 :param periodMin: Minimum period to test (days)
19 :param periodMax: Maximimum period to test (days)
20 :param nVistisMin: minimum number of visits necessary before looking for the phase gap
21 """
22 self.periodMin = periodMin
23 self.periodMax = periodMax
24 self.nPeriods = nPeriods
25 self.nVisitsMin = nVisitsMin
26 super(PhaseGapMetric, self).__init__(col, metricName=metricName, units='Fraction, 0-1', **kwargs)
28 def run(self, dataSlice, slicePoint=None):
29 """
30 Run the PhaseGapMetric.
31 :param dataSlice: Data for this slice.
32 :param slicePoint: Metadata for the slice (Optional as not used here).
33 :return: a dictionary of the periods used here and the corresponding largest gaps.
34 """
35 if len(dataSlice) < self.nVisitsMin:
36 return self.badval
37 # Create 'nPeriods' evenly spaced periods within range of min to max.
38 step = (self.periodMax-self.periodMin)/self.nPeriods
39 if step == 0:
40 periods = np.array([self.periodMin])
41 else:
42 periods = np.arange(self.nPeriods)
43 periods = periods/np.max(periods)*(self.periodMax-self.periodMin)+self.periodMin
44 maxGap = np.zeros(self.nPeriods, float)
46 for i, period in enumerate(periods):
47 # For each period, calculate the phases.
48 phases = (dataSlice[self.colname] % period)/period
49 phases = np.sort(phases)
50 # Find the largest gap in coverage.
51 gaps = np.diff(phases)
52 start_to_end = np.array([1.0 - phases[-1] + phases[0]], float)
53 gaps = np.concatenate([gaps, start_to_end])
54 maxGap[i] = np.max(gaps)
56 return {'periods':periods, 'maxGaps':maxGap}
58 def reduceMeanGap(self, metricVal):
59 """
60 At each slicepoint, return the mean gap value.
61 """
62 return np.mean(metricVal['maxGaps'])
64 def reduceMedianGap(self, metricVal):
65 """
66 At each slicepoint, return the median gap value.
67 """
68 return np.median(metricVal['maxGaps'])
70 def reduceWorstPeriod(self, metricVal):
71 """
72 At each slicepoint, return the period with the largest phase gap.
73 """
74 worstP = metricVal['periods'][np.where(metricVal['maxGaps'] == metricVal['maxGaps'].max())]
75 return worstP
77 def reduceLargestGap(self, metricVal):
78 """
79 At each slicepoint, return the largest phase gap value.
80 """
81 return np.max(metricVal['maxGaps'])
84# To fit a periodic source well, you need to cover the full phase, and fit the amplitude.
85class PeriodicQualityMetric(BaseMetric):
86 def __init__(self, mjdCol='observationStartMJD', period=2., m5Col='fiveSigmaDepth',
87 metricName='PhaseCoverageMetric', starMag=20, **kwargs):
88 self.mjdCol = mjdCol
89 self.m5Col = m5Col
90 self.period = period
91 self.starMag = starMag
92 super(PeriodicQualityMetric, self).__init__([mjdCol, m5Col], metricName=metricName,
93 units='Fraction, 0-1', **kwargs)
95 def _calc_phase(self, dataSlice):
96 """1 is perfectly balanced phase coverage, 0 is no effective coverage.
97 """
98 angles = dataSlice[self.mjdCol] % self.period
99 angles = angles/self.period * 2.*np.pi
100 x = np.cos(angles)
101 y = np.sin(angles)
103 snr = m52snr(self.starMag, dataSlice[self.m5Col])
104 x_ave = np.average(x, weights=snr)
105 y_ave = np.average(y, weights=snr)
107 vector_off = np.sqrt(x_ave**2+y_ave**2)
108 return 1.-vector_off
110 def _calc_amp(self, dataSlice):
111 """Fractional SNR on the amplitude, testing for a variety of possible phases
112 """
113 phases = np.arange(0, np.pi, np.pi/8.)
114 snr = m52snr(self.starMag, dataSlice[self.m5Col])
115 amp_snrs = np.sin(dataSlice[self.mjdCol]/self.period*2*np.pi + phases[:, np.newaxis])*snr
116 amp_snr = np.min(np.sqrt(np.sum(amp_snrs**2, axis=1)))
118 max_snr = np.sqrt(np.sum(snr**2))
119 return amp_snr/max_snr
121 def run(self, dataSlice, slicePoint=None):
122 amplitude_fraction = self._calc_amp(dataSlice)
123 phase_fraction = self._calc_phase(dataSlice)
124 return amplitude_fraction * phase_fraction