Coverage for python/lsst/sims/maf/metrics/moMetrics.py : 62%

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
'NNightsMetric', 'ObsArcMetric', 'DiscoveryMetric', 'Discovery_N_ChancesMetric', 'Discovery_N_ObsMetric', 'Discovery_TimeMetric', 'Discovery_RADecMetric', 'Discovery_EcLonLatMetric', 'Discovery_VelocityMetric', 'ActivityOverTimeMetric', 'ActivityOverPeriodMetric', 'DiscoveryChancesMetric', 'MagicDiscoveryMetric', 'HighVelocityMetric', 'HighVelocityNightsMetric', 'LightcurveInversionMetric', 'ColorDeterminationMetric', 'PeakVMagMetric', 'KnownObjectsMetric']
"""Base class for the moving object metrics."""
comment=None, childMetrics=None, appMagCol='appMag', appMagVCol='appMagV', m5Col='fiveSigmaDepth', nightCol='night', expMJDCol='expMJD', snrCol='SNR', visCol='vis', raCol='ra', decCol='dec', seeingCol='FWHMgeom', expTimeCol='visitExpTime', filterCol='filter'): # Set metric name. # Set badval and units, leave space for 'comment' (tied to displayDict). # Set some commonly used column names. self.nightCol, self.expMJDCol, self.snrCol, self.visCol] for col in cols: self.colsReq.append(col)
raise ValueError('self.childMetrics must be a dictionary (possibly empty)') else: raise ValueError('childmetrics must be provided as a dictionary.')
"""Calculate the metric value.
Parameters ---------- ssoObs: np.ndarray The input data to the metric (same as the parent metric). orb: np.ndarray The information about the orbit for which the metric is being calculated. Hval : float The H value for which the metric is being calculated.
Returns ------- float or np.ndarray or dict """ raise NotImplementedError
"""Base class for child metrics.
Parameters ---------- parentDiscoveryMetric: BaseMoMetric The 'parent' metric which generated the metric data used to calculate this 'child' metric. badval: float, opt Value to return when metric cannot be calculated. """ self.metricDtype = kwargs['metricDtype'] else:
"""Calculate the child metric value.
Parameters ---------- ssoObs: np.ndarray The input data to the metric (same as the parent metric). orb: np.ndarray The information about the orbit for which the metric is being calculated. Hval : float The H value for which the metric is being calculated. metricValues : dict or np.ndarray The return value from the parent metric.
Returns ------- float """ raise NotImplementedError
""" Count the total number of observations where an object was 'visible'. """ """ @ snrLimit .. if snrLimit is None, this uses the _calcVis method/completeness if snrLimit is not None, this uses that value as a cutoff instead. """
else:
""" Count the number of observations for an object, but don't include any observations where it was a single observation on a night. """
else: vis = np.where(ssoObs[self.visCol] > 0)[0] return 0
"""Count the number of distinct nights an object is observed. """ """ @ snrLimit : if SNRlimit is None, this uses _calcVis method/completeness else if snrLimit is not None, it uses that value as a cutoff. """
else: return 0
"""Calculate the difference between the first and last observation of an object. """
else: return 0
"""Identify the discovery opportunities for an object.""" tMin=5./60.0/24.0, tMax=90./60./24.0, nNightsPerWindow=3, tWindow=15, snrLimit=None, badval=None, **kwargs): """ @ nObsPerNight = number of observations per night required for tracklet @ tMin = min time start/finish for the tracklet (days) @ tMax = max time start/finish for the tracklet (days) @ nNightsPerWindow = number of nights with observations required for track @ tWindow = max number of nights in track (days) @ snrLimit .. if snrLimit is None then uses 'completeness' calculation in 'vis' column. .. if snrLimit is not None, then uses this SNR value as a cutoff. """ # Define anything needed by the child metrics first. 'N_Obs': Discovery_N_ObsMetric(self), 'Time': Discovery_TimeMetric(self), 'RADec': Discovery_RADecMetric(self), 'EcLonLat': Discovery_EcLonLatMetric(self)} # Set up for inheriting from __init__. # Define anything needed for this metric.
else: vis = np.where(ssoObs[self.visCol] > 0)[0] return self.badval # Identify discovery opportunities. # Identify visits where the 'night' changes. #print 'all nights', nights # Identify all the indexes where the night changes in value. #print 'nightchanges', nights[nIdx] # Count the number of observations per night (except last night) # Add the number of observations on the last night. # Find the nights with more than nObsPerNight. # Check that nObsPerNight observations are within tMin/tMax # Identify the nights with 'clearly good' observations. # Identify the nights where we need more investigation (a subset of the visits may be within the interval). # 'good' provides mask for observations which could count as 'good to make tracklets' against ssoObs[visSort][nIdxMany] # Now identify tracklets which can make tracks. #print 'good tracklets', nights[goodIdx] return self.badval # Identify the index in ssoObs[vis][goodIdx] (sorted by expMJD) where the discovery opportunity starts. # Identify the index where the discovery opportunity ends. # Convert back to index based on ssoObs[vis] (sorted by expMJD). #print 'start', startIdxs, nights[startIdxs]#, orb['objId'], Hval #print 'end', endIdxs, nights[endIdxs]#, orb['objId'], Hval
"""Child metric to be used with DiscoveryMetric. Calculates total number of discovery opportunities in a window between nightStart / nightEnd. """ else: self.nightStart = nightStart # Update the metric name to use the nightStart/nightEnd values, if an overriding name is not given. self.name = self.name + '_n%d' % (nightStart) self.name = self.name + '_n%d' % (nightEnd)
"""Return the number of different discovery chances we had for each object/H combination. """ else: vis = np.where(ssoObs[self.visCol] > 0)[0] return self.badval else: valid = np.where((startNights >= self.nightStart) & (endNights <= self.nightEnd))[0]
"""Calculates the number of observations in the i-th discovery track. """ # The number of the discovery chance to use.
return 0
"""Returns the time of the i-th discovery opportunity. """
return self.badval else: vis = np.where(ssoObs[self.visCol] > 0)[0] return self.badval tDisc = tDisc - self.tStart
"""Returns the RA/Dec of the i-th discovery opportunity. """
return self.badval else: vis = np.where(ssoObs[self.visCol] > 0)[0] return self.badval
"""Returns the ecliptic lon/lat and solar elongation (in degrees) of the i-th discovery opportunity. """
return self.badval else: vis = np.where(ssoObs[self.visCol] > 0)[0] return self.badval
"""Returns the sky velocity of the i-th discovery opportunity. """ super(Discovery_VelocityMetric, self).__init__(parentDiscoveryMetric, badval=badval, **kwargs) self.i = i self.snrLimit = parentDiscoveryMetric.snrLimit
if self.i>=len(metricValues['start']): return self.badval if self.snrLimit is not None: vis = np.where(ssoObs[self.snrCol] >= self.snrLimit)[0] else: vis = np.where(ssoObs[self.visCol] > 0)[0] if len(vis) == 0: return self.badval visSort = np.argsort(ssoObs[self.expMJDCol][vis]) velocity = ssoObs['velocity'][vis][visSort] startIdx = metricValues['start'][self.i] return velocity[startIdx]
""" Count the time periods where we would have a chance to detect activity on a moving object. Splits observations into time periods set by 'window', then looks for observations within each window, and reports what fraction of the total windows receive 'nObs' visits. """ if metricName is None: metricName = 'Chance of detecting activity lasting %.0f days' %(window) super(ActivityOverTimeMetric, self).__init__(metricName=metricName, **kwargs) self.snrLimit = snrLimit self.window = window self.surveyYears = surveyYears self.windowBins = np.arange(0, self.surveyYears*365 + self.window/2.0, self.window) self.nWindows = len(self.windowBins) self.units = '%.1f Day Windows' %(self.window)
# For cometary activity, expect activity at the same point in its orbit at the same time, mostly # For collisions, expect activity at random times if self.snrLimit is not None: vis = np.where(ssoObs[self.snrCol] >= self.snrLimit)[0] else: vis = np.where(ssoObs[self.visCol] > 0)[0] if len(vis) == 0: return self.badval n, b = np.histogram(ssoObs[vis][self.nightCol], bins=self.windowBins) activityWindows = np.where(n>0)[0].size return activityWindows / float(self.nWindows)
""" Count the fraction of the orbit (when split into nBins) that receive observations, in order to have a chance to detect activity. """ qCol='q', eCol='e', tPeriCol='tPeri', metricName=None, **kwargs): """ @ binsize : size of orbit slice, in degrees. """ if metricName is None: metricName = 'Chance of detecting activity in %.1f of the orbit' %(window) super(ActivityOverPeriodMetric, self).__init__(metricName=metricName, **kwargs) self.qCol = qCol self.eCol = eCol self.tPeriCol = tPeriCol self.snrLimit = snrLimit self.binsize = np.radians(binsize) self.anomalyBins = np.arange(0, 2 * np.pi + self.binsize / 2.0, self.binsize) self.nBins = len(self.anomalyBins) self.units = '%.1f deg' %(np.degrees(self.binsize))
# For cometary activity, expect activity at the same point in its orbit at the same time, mostly # For collisions, expect activity at random times a = orb[self.qCol] / (1 - orb[self.eCol]) period = np.power(a, 3./2.) * 365.25 anomaly = ((ssoObs[self.expMJDCol] - orb[self.tPeriCol]) / period) % (2 * np.pi) if self.snrLimit is not None: vis = np.where(ssoObs[self.snrCol] >= self.snrLimit)[0] else: vis = np.where(ssoObs[self.visCol] > 0)[0] if len(vis) == 0: return self.badval n, b = np.histogram(anomaly[vis], bins=self.anomalyBins) activityWindows = np.where(n>0)[0].size return activityWindows / float(self.nBins)
"""Count the number of discovery opportunities for an object.
Superseded by the DiscoveryMetric + NChances child metric. """ nNightsPerWindow=3, tWindow=15, snrLimit=None, **kwargs): """ @ nObsPerNight = number of observations per night required for tracklet @ tNight = max time start/finish for the tracklet (days) @ nNightsPerWindow = number of nights with observations required for track @ tWindow = max number of nights in track (days) @ snrLimit .. if snrLimit is None then uses 'completeness' calculation, .. if snrLimit is not None, then uses this value as a cutoff. """
"""SsoObs = Dataframe, orb=Dataframe, Hval=single number.""" # Calculate visibility for this orbit at this H. else: vis = np.where(ssoObs[self.visCol] > 0)[0] return self.badval else: # Now to identify where observations meet the timing requirements. # Identify visits where the 'night' changes. #print 'all nights', nights # Identify all the indexes where the night changes (swap from one night to next) # Count the number of observations per night (except last night) # Add the number of observations on the last night. # Find the nights with at least nObsPerNight visits. # Check that nObsPerNight observations are within tNight # Identify the nights with 'clearly good' observations. # Identify the nights where we need more investigation # (a subset of the visits may be within the interval). (timesEnd - timesStart > self.tNight))[0] # 'good' provides mask for observations which could count as 'good to make tracklets' # against ssoObs[visSort][nIdxMany] # Now identify tracklets which can make tracks. #print 'good tracklet nights', ssoObs[self.nightCol][goodIdx] # Now (with indexes of start of 'good' nights with nObsPerNight within tNight), # look at the intervals between 'good' nights (for tracks) discoveryChances = self.badval else: ssoObs[self.nightCol][vis][goodIdx])
"""Count the number of discovery opportunities with very good software. """ """ @ nObs = the total number of observations required for 'discovery' @ tWindow = the timespan of the discovery window. @ snrLimit .. if snrLimit is None then uses 'completeness' calculation, .. if snrLimit is not None, then uses this value as a cutoff. """
"""SsoObs = Dataframe, orb=Dataframe, Hval=single number.""" # Calculate visibility for this orbit at this H. else: vis = np.where(ssoObs[self.visCol] > 0)[0] return self.badval
""" Count the number of times an asteroid is observed with a velocity high enough to make it appear trailed by a factor of (psfFactor)*PSF - i.e. velocity >= psfFactor * seeing / visitExpTime. Simply counts the total number of observations with high velocity. """ """ @ psfFactor = factor to multiply seeing/visitExpTime by (velocity(deg/day) >= 24*psfFactor*seeing(")/visitExptime(s)) """
else: vis = np.where(ssoObs[self.visCol] > 0)[0] return self.badval (24.* self.psfFactor * ssoObs[self.seeingCol][vis] / ssoObs[self.expTimeCol][vis]))[0]
""" Count the number of times an asteroid is observed with a velocity high enough to make it appear trailed by a factor of (psfFactor)*PSF - i.e. velocity >= psfFactor * seeing / visitExpTime, where we require nObsPerNight observations within a given night. Counts the total number of nights with enough high-velocity observations. """ """ @ psfFactor = factor to multiply seeing/visitExpTime by (velocity(deg/day) >= 24*psfFactor*seeing(")/visitExptime(s)) @ nObsPerNight = number of observations required per night """
else: vis = np.where(ssoObs[self.visCol] > 0)[0] return self.badval (24. * self.psfFactor * ssoObs[self.seeingCol][vis] / ssoObs[self.expTimeCol][vis]))[0] # Count the number of observations per night (except last night) # Add the number of observations on the last night. # Find the nights with at least nObsPerNight visits # (this is already looking at only high velocity observations).
"""Identify objects which would have observations suitable to do lightcurve inversion.
This is roughly defined as objects which have more than nObs observations with SNR greater than snrLimit, within nDays. """ super(LightcurveInversionMetric, self).__init__(**kwargs) self.nObs = nObs self.snrLimit = snrLimit self.nDays = nDays self.badval = -666
vis = np.where(ssoObs[self.snrCol] >= self.snrLimit)[0] if len(vis) < self.nObs: return 0 nights = ssoObs[self.nightCol][vis] ncounts = np.bincount(nights) # ncounts covers the range = np.arange(nights.min(), nights.max() + 1, 1) if self.nDays % 2 == 0: lWindow = self.nDays / 2 rWindow = self.nDays / 2 else: lWindow = int(self.nDays / 2) rWindow = int(self.nDays / 2) + 1 found = 0 for i in range(lWindow, len(ncounts) - rWindow): nobs = ncounts[i - lWindow:i + rWindow].sum() if nobs > self.nObs: found = 1 break return found
"""Identify objects which could have observations suitable to determine colors.
This is roughly defined as objects which have more than nPairs pairs of observations with SNR greater than snrLimit, in bands bandOne and bandTwo, within nHours. """ super(ColorDeterminationMetric, self).__init__(**kwargs) self.nPairs = nPairs self.snrLimit = snrLimit self.nHours = nHours self.bOne = bOne self.bTwo = bTwo self.badval = -666
vis = np.where(ssoObs[self.snrCol] >= self.snrLimit)[0] if len(vis) < self.nPairs * 2: return 0 bOneObs = np.where(ssoObs[self.filterCol][vis] == self.bOne)[0] bTwoObs = np.where(ssoObs[self.filterCol][vis] == self.bTwo)[0] timesbOne = ssoObs[self.expMJDCol][vis][bOneObs] timesbTwo = ssoObs[self.expMJDCol][vis][bTwoObs] if len(timesbOne) == 0 or len(timesbTwo) == 0: return 0 dTime = self.nHours / 24.0 # Calculate the time between the closest pairs of observations. inOrder = np.searchsorted(timesbOne, timesbTwo, 'right') inOrder = np.where(inOrder - 1 > 0, inOrder - 1, 0) dtPairs = timesbTwo - timesbOne[inOrder] if len(np.where(dtPairs < dTime)[0]) >= self.nPairs: found = 1 else: found = 0 return found
"""Pull out the peak V magnitude of all observations of the object. """ super(PeakVMagMetric, self).__init__(**kwargs)
peakVmag = np.min(ssoObs[self.appMagVCol]) return peakVmag
"""Identify objects which could be classified as 'previously known' based on their peak V magnitude, returning the time at which each first reached that peak V magnitude.
Parameters ----------- elongThresh : float, opt The cutoff in solar elongation to consider an object 'visible'. Default 60 deg. vMagThresh1 : float, opt The magnitude threshhold for previously known objects. Default 20.0. This is calibrated using NEOs discovered in the last 15 years and assuming a current 25% completeness. vMagThresh2 : float, opt The magnitude threshhold for previously known objects. Default 22.0. This is based on assuming PS and other surveys will be efficient down to V=22. tSwitch : float, opt The time to switch between evaluating against vMagThresh1 to vMagThresh2. Default 57023 (start of 2015). """ elongCol='Elongation', expMJDCol='MJD(UTC)', **kwargs): super(KnownObjectsMetric, self).__init__(**kwargs) self.elongThresh = elongThresh self.elongCol = elongCol self.vMagThresh1 = vMagThresh1 self.vMagThresh2 = vMagThresh2 self.tSwitch = tSwitch self.expMJDCol = expMJDCol
visible = np.where(ssoObs[self.elongCol] >= self.elongThresh, 1, 0) # Discovery before tSwitch? earlyObs = np.where((ssoObs[self.expMJDCol] < self.tSwitch) & visible)[0] overPeak = np.where(ssoObs[self.appMagVCol][earlyObs] <= self.vMagThresh1)[0] if len(overPeak) > 0: discoveryTime = ssoObs[self.expMJDCol][earlyObs][overPeak].min() else: lateObs = np.where((ssoObs[self.expMJDCol] >= self.tSwitch) & visible)[0] overPeak = np.where(ssoObs[self.appMagVCol][lateObs] <= self.vMagThresh2)[0] if len(overPeak) > 0: discoveryTime = ssoObs[self.expMJDCol][lateObs][overPeak].min() else: discoveryTime = self.badval return discoveryTime |