def _update_info_div(self): '''Update the text div with information about the current target''' fibermap = self.spectra.fibermap[self.ispec] zb = self.zbest[self.izbest] info = list() info.append('<table>') info.append('<tr><th>TargetID</th><td>{}</td></tr>'.format( zb['TARGETID'])) info.append('<tr><th>DESI_TARGET</th><td>{0}</td></tr>'.format( ' '.join(desi_mask.names(fibermap['DESI_TARGET'])))) info.append('<tr><th>BGS_TARGET</th><td>{0}</td></tr>'.format(' '.join( bgs_mask.names(fibermap['BGS_TARGET'])))) info.append('<tr><th>MWS_TARGET</th><td>{0}</td></tr>'.format(' '.join( mws_mask.names(fibermap['MWS_TARGET'])))) info.append('</table>') self.info_div.text = '\n'.join(info)
def report_fibers_mws(tiledata, ncols=60): """ """ fmt_string = '{{:{ncols:d}}} {{:<10d}}'.format(ncols=ncols) non_mws_line = None lines = list() ubits, ubits_count = np.unique(tiledata['MWS_TARGET'], return_counts=True) for ubit, ubit_count in zip(ubits, ubits_count): cats = list() for _ in mws_mask.names(ubit): _ = _.replace('MWS_', '') cats.append(_) cat_str = format_cats(cats) if len(cat_str) > 1: for _s in cat_str[:-1]: lines.append(_s) cat_str = cat_str[-1] blank = len(cat_str.strip()) == 0 if blank: cat_str = '(non-MWS targets)' line = fmt_string.format(cat_str, ubit_count) if blank: non_mws_line = line else: lines.append(line) for line in lines: print(line) if non_mws_line is not None: print() print(non_mws_line) return
def calc_priority(targets, zcat, obscon): """ Calculate target priorities from masks, observation/redshift status. Parameters ---------- targets : :class:`~numpy.ndarray` numpy structured array or astropy Table of targets. Must include the columns `DESI_TARGET`, `BGS_TARGET`, `MWS_TARGET` (or their SV/cmx equivalents). zcat : :class:`~numpy.ndarray` numpy structured array or Table of redshift information. Must include 'Z', `ZWARN`, `NUMOBS` and be the same length as `targets`. May also contain `NUMOBS_MORE` if this isn't the first time through MTL and `NUMOBS > 0`. obscon : :class:`str` A combination of strings that are in the desitarget bitmask yaml file (specifically in `desitarget.targetmask.obsconditions`), e.g. "DARK|GRAY". Governs the behavior of how priorities are set based on "obsconditions" in the desitarget bitmask yaml file. Returns ------- :class:`~numpy.array` integer array of priorities. Notes ----- - If a target passes multiple selections, highest priority wins. - Will automatically detect if the passed targets are main survey, commissioning or SV and behave accordingly. """ # ADM check the input arrays are the same length. assert len(targets) == len(zcat) # ADM determine whether the input targets are main survey, cmx or SV. colnames, masks, survey = main_cmx_or_sv(targets, scnd=True) # ADM the target bits/names should be shared between main survey and SV. if survey != 'cmx': desi_target, bgs_target, mws_target, scnd_target = colnames desi_mask, bgs_mask, mws_mask, scnd_mask = masks else: cmx_mask = masks[0] # Default is 0 priority, i.e. do not observe. priority = np.zeros(len(targets), dtype='i8') # Determine which targets have been observed. # TODO: this doesn't distinguish between really unobserved vs not yet # processed. unobs = (zcat["NUMOBS"] == 0) log.debug('calc_priority has %d unobserved targets' % (np.sum(unobs))) if np.all(unobs): done = np.zeros(len(targets), dtype=bool) zgood = np.zeros(len(targets), dtype=bool) zwarn = np.zeros(len(targets), dtype=bool) else: nmore = zcat["NUMOBS_MORE"] assert np.all(nmore >= 0) done = ~unobs & (nmore == 0) zgood = ~unobs & (nmore > 0) & (zcat['ZWARN'] == 0) zwarn = ~unobs & (nmore > 0) & (zcat['ZWARN'] != 0) # zgood, zwarn, done, and unobs should be mutually exclusive and cover all # targets. assert not np.any(unobs & zgood) assert not np.any(unobs & zwarn) assert not np.any(unobs & done) assert not np.any(zgood & zwarn) assert not np.any(zgood & done) assert not np.any(zwarn & done) assert np.all(unobs | done | zgood | zwarn) # DESI dark time targets. if survey != 'cmx': if desi_target in targets.dtype.names: # ADM 'LRG' is the guiding column in SV and the main survey # ADM (once, it was 'LRG_1PASS' and 'LRG_2PASS' in the MS). # names = ('ELG', 'LRG_1PASS', 'LRG_2PASS') # if survey[0:2] == 'sv': names = ('ELG', 'LRG') for name in names: # ADM only update priorities for passed observing conditions. pricon = obsconditions.mask(desi_mask[name].obsconditions) if (obsconditions.mask(obscon) & pricon) != 0: ii = (targets[desi_target] & desi_mask[name]) != 0 priority[ii & unobs] = np.maximum( priority[ii & unobs], desi_mask[name].priorities['UNOBS']) priority[ii & done] = np.maximum( priority[ii & done], desi_mask[name].priorities['DONE']) priority[ii & zgood] = np.maximum( priority[ii & zgood], desi_mask[name].priorities['MORE_ZGOOD']) priority[ii & zwarn] = np.maximum( priority[ii & zwarn], desi_mask[name].priorities['MORE_ZWARN']) # QSO could be Lyman-alpha or Tracer. name = 'QSO' # ADM only update priorities for passed observing conditions. pricon = obsconditions.mask(desi_mask[name].obsconditions) if (obsconditions.mask(obscon) & pricon) != 0: ii = (targets[desi_target] & desi_mask[name]) != 0 # ADM all redshifts require more observations in SV. good_hiz = zgood & (zcat['Z'] >= 2.15) & (zcat['ZWARN'] == 0) if survey[0:2] == 'sv': good_hiz = zgood & (zcat['ZWARN'] == 0) priority[ii & unobs] = np.maximum( priority[ii & unobs], desi_mask[name].priorities['UNOBS']) priority[ii & done] = np.maximum( priority[ii & done], desi_mask[name].priorities['DONE']) priority[ii & good_hiz] = np.maximum( priority[ii & good_hiz], desi_mask[name].priorities['MORE_ZGOOD']) priority[ii & ~good_hiz] = np.maximum( priority[ii & ~good_hiz], desi_mask[name].priorities['DONE']) priority[ii & zwarn] = np.maximum( priority[ii & zwarn], desi_mask[name].priorities['MORE_ZWARN']) # BGS targets. if bgs_target in targets.dtype.names: for name in bgs_mask.names(): # ADM only update priorities for passed observing conditions. pricon = obsconditions.mask(bgs_mask[name].obsconditions) if (obsconditions.mask(obscon) & pricon) != 0: ii = (targets[bgs_target] & bgs_mask[name]) != 0 priority[ii & unobs] = np.maximum( priority[ii & unobs], bgs_mask[name].priorities['UNOBS']) priority[ii & done] = np.maximum( priority[ii & done], bgs_mask[name].priorities['DONE']) priority[ii & zgood] = np.maximum( priority[ii & zgood], bgs_mask[name].priorities['MORE_ZGOOD']) priority[ii & zwarn] = np.maximum( priority[ii & zwarn], bgs_mask[name].priorities['MORE_ZWARN']) # MWS targets. if mws_target in targets.dtype.names: for name in mws_mask.names(): # ADM only update priorities for passed observing conditions. pricon = obsconditions.mask(mws_mask[name].obsconditions) if (obsconditions.mask(obscon) & pricon) != 0: ii = (targets[mws_target] & mws_mask[name]) != 0 priority[ii & unobs] = np.maximum( priority[ii & unobs], mws_mask[name].priorities['UNOBS']) priority[ii & done] = np.maximum( priority[ii & done], mws_mask[name].priorities['DONE']) priority[ii & zgood] = np.maximum( priority[ii & zgood], mws_mask[name].priorities['MORE_ZGOOD']) priority[ii & zwarn] = np.maximum( priority[ii & zwarn], mws_mask[name].priorities['MORE_ZWARN']) # ADM Secondary targets. if scnd_target in targets.dtype.names: # APC Secondary target bits only drive updates to targets with specific DESI_TARGET bits # APC See https://github.com/desihub/desitarget/pull/530 scnd_update = (targets[desi_target] & desi_mask['SCND_ANY']) != 0 if np.any(scnd_update): # APC Allow changes to primaries if the DESI_TARGET bitmask has any of the # APC following bits set, but not any other bits. update_from_scnd_bits = (desi_mask['SCND_ANY'] | desi_mask['MWS_ANY'] | desi_mask['STD_BRIGHT'] | desi_mask['STD_FAINT'] | desi_mask['STD_WD']) scnd_update &= ((targets[desi_target] & ~update_from_scnd_bits) == 0) print('{} scnd targets to be updated'.format( scnd_update.sum())) for name in scnd_mask.names(): # ADM only update priorities for passed observing conditions. pricon = obsconditions.mask(scnd_mask[name].obsconditions) if (obsconditions.mask(obscon) & pricon) != 0: ii = (targets[scnd_target] & scnd_mask[name]) != 0 ii &= scnd_update priority[ii & unobs] = np.maximum( priority[ii & unobs], scnd_mask[name].priorities['UNOBS']) priority[ii & done] = np.maximum( priority[ii & done], scnd_mask[name].priorities['DONE']) priority[ii & zgood] = np.maximum( priority[ii & zgood], scnd_mask[name].priorities['MORE_ZGOOD']) priority[ii & zwarn] = np.maximum( priority[ii & zwarn], scnd_mask[name].priorities['MORE_ZWARN']) # Special case: IN_BRIGHT_OBJECT means priority=-1 no matter what ii = (targets[desi_target] & desi_mask.IN_BRIGHT_OBJECT) != 0 priority[ii] = -1 # ADM Special case: SV-like commissioning targets. if 'CMX_TARGET' in targets.dtype.names: for name in ['SV0_' + label for label in ('BGS', 'MWS')]: ii = (targets['CMX_TARGET'] & cmx_mask[name]) != 0 priority[ii & unobs] = np.maximum( priority[ii & unobs], cmx_mask[name].priorities['UNOBS']) priority[ii & done] = np.maximum(priority[ii & done], cmx_mask[name].priorities['DONE']) priority[ii & zgood] = np.maximum( priority[ii & zgood], cmx_mask[name].priorities['MORE_ZGOOD']) priority[ii & zwarn] = np.maximum( priority[ii & zwarn], cmx_mask[name].priorities['MORE_ZWARN']) return priority
def test_priorities(self): """Test that priorities are set correctly for both the main survey and SV. """ # ADM loop through once for SV and once for the main survey. for prefix in ["", "SV1_"]: t = self.targets.copy() z = self.zcat.copy() main_names = ['DESI_TARGET', 'BGS_TARGET', 'MWS_TARGET'] for name in main_names: t.rename_column(name, prefix + name) # ADM retrieve the mask and column names for this survey flavor. colnames, masks, _ = main_cmx_or_sv(t) desi_target, bgs_target, mws_target = colnames desi_mask, bgs_mask, mws_mask = masks # - No targeting bits set is priority=0 self.assertTrue(np.all(calc_priority(t, z) == 0)) # - test QSO > (LRG_1PASS | LRG_2PASS) > ELG t[desi_target] = desi_mask.ELG self.assertTrue( np.all( calc_priority(t, z) == desi_mask.ELG.priorities['UNOBS'])) t[desi_target] |= desi_mask.LRG_1PASS self.assertTrue( np.all( calc_priority(t, z) == desi_mask.LRG.priorities['UNOBS'])) t[desi_target] |= desi_mask.LRG_2PASS self.assertTrue( np.all( calc_priority(t, z) == desi_mask.LRG.priorities['UNOBS'])) t[desi_target] |= desi_mask.QSO self.assertTrue( np.all( calc_priority(t, z) == desi_mask.QSO.priorities['UNOBS'])) # - different states -> different priorities # - Done is Done, regardless of ZWARN. t[desi_target] = desi_mask.ELG t["PRIORITY_INIT"], t["NUMOBS_INIT"] = initial_priority_numobs(t) z['NUMOBS'] = [0, 1, 1] z['ZWARN'] = [1, 1, 0] p = make_mtl(t, z)["PRIORITY"] self.assertEqual(p[0], desi_mask.ELG.priorities['UNOBS']) self.assertEqual(p[1], desi_mask.ELG.priorities['DONE']) self.assertEqual(p[2], desi_mask.ELG.priorities['DONE']) # - BGS FAINT targets are never DONE, only MORE_ZGOOD. t[desi_target] = desi_mask.BGS_ANY t[bgs_target] = bgs_mask.BGS_FAINT t["PRIORITY_INIT"], t["NUMOBS_INIT"] = initial_priority_numobs(t) z['NUMOBS'] = [0, 1, 1] z['ZWARN'] = [1, 1, 0] p = make_mtl(t, z)["PRIORITY"] self.assertEqual(p[0], bgs_mask.BGS_FAINT.priorities['UNOBS']) self.assertEqual(p[1], bgs_mask.BGS_FAINT.priorities['MORE_ZWARN']) self.assertEqual(p[2], bgs_mask.BGS_FAINT.priorities['MORE_ZGOOD']) # BGS_FAINT: {UNOBS: 2000, MORE_ZWARN: 2000, MORE_ZGOOD: 1000, DONE: 2, OBS: 1, DONOTOBSERVE: 0} # - BGS BRIGHT targets are never DONE, only MORE_ZGOOD. t[desi_target] = desi_mask.BGS_ANY t[bgs_target] = bgs_mask.BGS_BRIGHT t["PRIORITY_INIT"], t["NUMOBS_INIT"] = initial_priority_numobs(t) z['NUMOBS'] = [0, 1, 1] z['ZWARN'] = [1, 1, 0] p = make_mtl(t, z)["PRIORITY"] self.assertEqual(p[0], bgs_mask.BGS_BRIGHT.priorities['UNOBS']) self.assertEqual(p[1], bgs_mask.BGS_BRIGHT.priorities['MORE_ZWARN']) self.assertEqual(p[2], bgs_mask.BGS_BRIGHT.priorities['MORE_ZGOOD']) # BGS_BRIGHT: {UNOBS: 2100, MORE_ZWARN: 2100, MORE_ZGOOD: 1000, DONE: 2, OBS: 1, DONOTOBSERVE: 0} # BGS targets are NEVER done even after 100 observations t[desi_target] = desi_mask.BGS_ANY t[bgs_target] = bgs_mask.BGS_BRIGHT t["PRIORITY_INIT"], t["NUMOBS_INIT"] = initial_priority_numobs(t) z['NUMOBS'] = [0, 100, 100] z['ZWARN'] = [1, 1, 0] p = calc_priority(t, z) self.assertEqual(p[0], bgs_mask.BGS_BRIGHT.priorities['UNOBS']) self.assertEqual(p[1], bgs_mask.BGS_BRIGHT.priorities['MORE_ZWARN']) self.assertEqual(p[2], bgs_mask.BGS_BRIGHT.priorities['MORE_ZGOOD']) # BGS ZGOOD targets always have lower priority than MWS targets that # are not DONE. # ADM first discard N/S informational bits from bitmask as these # ADM should never trump the other bits. bgs_names = [ name for name in bgs_mask.names() if 'NORTH' not in name and 'SOUTH' not in name ] mws_names = [ name for name in mws_mask.names() if 'NORTH' not in name and 'SOUTH' not in name ] lowest_bgs_priority_zgood = min( [bgs_mask[n].priorities['MORE_ZGOOD'] for n in bgs_names]) lowest_mws_priority_unobs = min( [mws_mask[n].priorities['UNOBS'] for n in mws_names]) lowest_mws_priority_zwarn = min( [mws_mask[n].priorities['MORE_ZWARN'] for n in mws_names]) lowest_mws_priority_zgood = min( [mws_mask[n].priorities['MORE_ZGOOD'] for n in mws_names]) lowest_mws_priority = min(lowest_mws_priority_unobs, lowest_mws_priority_zwarn, lowest_mws_priority_zgood) self.assertLess(lowest_bgs_priority_zgood, lowest_mws_priority)
def calc_priority(targets, zcat, obscon, state=False): """ Calculate target priorities from masks, observation/redshift status. Parameters ---------- targets : :class:`~numpy.ndarray` numpy structured array or astropy Table of targets. Must include the columns `DESI_TARGET`, `BGS_TARGET`, `MWS_TARGET` (or their SV/cmx equivalents) and `TARGETID`. zcat : :class:`~numpy.ndarray` numpy structured array or Table of redshift info. Must include `Z`, `ZWARN`, `NUMOBS` and `TARGETID` and BE SORTED ON TARGETID to match `targets` row-by-row. May also contain `NUMOBS_MORE` if this isn't the first time through MTL and `NUMOBS > 0`. obscon : :class:`str` A combination of strings that are in the desitarget bitmask yaml file (specifically in `desitarget.targetmask.obsconditions`), e.g. "DARK|GRAY". Governs the behavior of how priorities are set based on "obsconditions" in the desitarget bitmask yaml file. state : :class:`bool` If ``True`` then also return a string denoting the state that was set. The state is a string combining the observational state (e.g. "DONE", "MORE_ZGOOD") from the targeting yaml file and the target type (e.g. "ELG", "LRG"). Returns ------- :class:`~numpy.array` integer array of priorities. :class:`~numpy.array` string array of states. Only returned if `state`=``True`` Notes ----- - If a target passes multiple selections, highest priority wins. - Will automatically detect if the passed targets are main survey, commissioning or SV and behave accordingly. """ # ADM check input arrays are sorted to match row-by-row on TARGETID. assert np.all(targets["TARGETID"] == zcat["TARGETID"]) # ADM determine whether the input targets are main survey, cmx or SV. colnames, masks, survey = main_cmx_or_sv(targets, scnd=True) # ADM the target bits/names should be shared between main survey and SV. if survey != 'cmx': desi_target, bgs_target, mws_target, scnd_target = colnames desi_mask, bgs_mask, mws_mask, scnd_mask = masks else: cmx_mask = masks[0] # Default is 0 priority, i.e. do not observe. priority = np.zeros(len(targets), dtype='i8') # ADM set up a string to record the state of each target. from desitarget.mtl import mtldatamodel target_state = np.zeros(len(targets), dtype=mtldatamodel["TARGET_STATE"].dtype) # Determine which targets have been observed. # TODO: this doesn't distinguish between really unobserved vs not yet # processed. unobs = (zcat["NUMOBS"] == 0) log.debug('calc_priority has %d unobserved targets' % (np.sum(unobs))) if np.all(unobs): done = np.zeros(len(targets), dtype=bool) zgood = np.zeros(len(targets), dtype=bool) zwarn = np.zeros(len(targets), dtype=bool) else: nmore = zcat["NUMOBS_MORE"] assert np.all(nmore >= 0) done = ~unobs & (nmore == 0) zgood = ~unobs & (nmore > 0) & (zcat['ZWARN'] == 0) zwarn = ~unobs & (nmore > 0) & (zcat['ZWARN'] != 0) # zgood, zwarn, done, and unobs should be mutually exclusive and cover all # targets. assert not np.any(unobs & zgood) assert not np.any(unobs & zwarn) assert not np.any(unobs & done) assert not np.any(zgood & zwarn) assert not np.any(zgood & done) assert not np.any(zwarn & done) assert np.all(unobs | done | zgood | zwarn) # DESI dark time targets. if survey != 'cmx': if desi_target in targets.dtype.names: # ADM set initialstate of CALIB for potential calibration targets. names = ('SKY', 'BAD_SKY', 'SUPP_SKY', 'STD_FAINT', 'STD_WD', 'STD_BRIGHT') for name in names: # ADM only update states for passed observing conditions. pricon = obsconditions.mask(desi_mask[name].obsconditions) if (obsconditions.mask(obscon) & pricon) != 0: ii = (targets[desi_target] & desi_mask[name]) != 0 target_state[ii] = "CALIB" # ADM 'LRG' is the guiding column in SV and the main survey # ADM (once, it was 'LRG_1PASS' and 'LRG_2PASS' in the MS). # names = ('ELG', 'LRG_1PASS', 'LRG_2PASS') # if survey[0:2] == 'sv': names = ('ELG', 'LRG') for name in names: # ADM only update priorities for passed observing conditions. pricon = obsconditions.mask(desi_mask[name].obsconditions) if (obsconditions.mask(obscon) & pricon) != 0: ii = (targets[desi_target] & desi_mask[name]) != 0 for sbool, sname in zip( [unobs, done, zgood, zwarn], ["UNOBS", "DONE", "MORE_ZGOOD", "MORE_ZWARN"] ): # ADM update priorities and target states. Mxp = desi_mask[name].priorities[sname] # ADM update states BEFORE changing priorities. ts = "{}|{}".format(name, sname) target_state[ii & sbool] = np.where( priority[ii & sbool] < Mxp, ts, target_state[ii & sbool]) priority[ii & sbool] = np.where( priority[ii & sbool] < Mxp, Mxp, priority[ii & sbool]) # QSO could be Lyman-alpha or Tracer. name = 'QSO' # ADM only update priorities for passed observing conditions. pricon = obsconditions.mask(desi_mask[name].obsconditions) if (obsconditions.mask(obscon) & pricon) != 0: ii = (targets[desi_target] & desi_mask[name]) != 0 # ADM all redshifts require more observations in SV. # ADM (zcut is defined at the top of this module). good_hiz = zgood & (zcat['Z'] >= zcut) & (zcat['ZWARN'] == 0) if survey[:2] == 'sv': good_hiz = zgood & (zcat['ZWARN'] == 0) for sbool, sname in zip( [unobs, done, good_hiz, ~good_hiz, zwarn], ["UNOBS", "DONE", "MORE_ZGOOD", "DONE", "MORE_ZWARN"] ): # ADM update priorities and target states. Mxp = desi_mask[name].priorities[sname] # ADM update states BEFORE changing priorities. ts = "{}|{}".format(name, sname) target_state[ii & sbool] = np.where( priority[ii & sbool] < Mxp, ts, target_state[ii & sbool]) priority[ii & sbool] = np.where( priority[ii & sbool] < Mxp, Mxp, priority[ii & sbool]) # BGS targets. if bgs_target in targets.dtype.names: for name in bgs_mask.names(): # ADM only update priorities for passed observing conditions. pricon = obsconditions.mask(bgs_mask[name].obsconditions) if (obsconditions.mask(obscon) & pricon) != 0: ii = (targets[bgs_target] & bgs_mask[name]) != 0 for sbool, sname in zip( [unobs, done, zgood, zwarn], ["UNOBS", "DONE", "MORE_ZGOOD", "MORE_ZWARN"] ): # ADM update priorities and target states. Mxp = bgs_mask[name].priorities[sname] # ADM update states BEFORE changing priorities. ts = "{}|{}".format("BGS", sname) target_state[ii & sbool] = np.where( priority[ii & sbool] < Mxp, ts, target_state[ii & sbool]) priority[ii & sbool] = np.where( priority[ii & sbool] < Mxp, Mxp, priority[ii & sbool]) # MWS targets. if mws_target in targets.dtype.names: for name in mws_mask.names(): # ADM only update priorities for passed observing conditions. pricon = obsconditions.mask(mws_mask[name].obsconditions) if (obsconditions.mask(obscon) & pricon) != 0: ii = (targets[mws_target] & mws_mask[name]) != 0 for sbool, sname in zip( [unobs, done, zgood, zwarn], ["UNOBS", "DONE", "MORE_ZGOOD", "MORE_ZWARN"] ): # ADM update priorities and target states. Mxp = mws_mask[name].priorities[sname] # ADM update states BEFORE changing priorities. ts = "{}|{}".format("MWS", sname) target_state[ii & sbool] = np.where( priority[ii & sbool] < Mxp, ts, target_state[ii & sbool]) priority[ii & sbool] = np.where( priority[ii & sbool] < Mxp, Mxp, priority[ii & sbool]) # ADM Secondary targets. if scnd_target in targets.dtype.names: # APC Secondary target bits only drive updates to targets with specific DESI_TARGET bits # APC See https://github.com/desihub/desitarget/pull/530 scnd_update = (targets[desi_target] & desi_mask['SCND_ANY']) != 0 if np.any(scnd_update): # APC Allow changes to primaries if the DESI_TARGET bitmask has any of the # APC following bits set, but not any other bits. update_from_scnd_bits = ( desi_mask['SCND_ANY'] | desi_mask['MWS_ANY'] | desi_mask['STD_BRIGHT'] | desi_mask['STD_FAINT'] | desi_mask['STD_WD']) scnd_update &= ((targets[desi_target] & ~update_from_scnd_bits) == 0) log.info('{} scnd targets to be updated'.format(scnd_update.sum())) for name in scnd_mask.names(): # ADM only update priorities for passed observing conditions. pricon = obsconditions.mask(scnd_mask[name].obsconditions) if (obsconditions.mask(obscon) & pricon) != 0: ii = (targets[scnd_target] & scnd_mask[name]) != 0 ii &= scnd_update for sbool, sname in zip( [unobs, done, zgood, zwarn], ["UNOBS", "DONE", "MORE_ZGOOD", "MORE_ZWARN"] ): # ADM update priorities and target states. Mxp = scnd_mask[name].priorities[sname] # ADM update states BEFORE changing priorities. ts = "{}|{}".format("SCND", sname) target_state[ii & sbool] = np.where( priority[ii & sbool] < Mxp, ts, target_state[ii & sbool]) priority[ii & sbool] = np.where( priority[ii & sbool] < Mxp, Mxp, priority[ii & sbool]) # Special case: IN_BRIGHT_OBJECT means priority=-1 no matter what. ii = (targets[desi_target] & desi_mask.IN_BRIGHT_OBJECT) != 0 priority[ii] = -1 target_state[ii] = "IN_BRIGHT_OBJECT" # ADM Special case: SV-like commissioning targets. if 'CMX_TARGET' in targets.dtype.names: priority = _cmx_calc_priority(targets, priority, obscon, unobs, done, zgood, zwarn, cmx_mask, obsconditions) if state: return priority, target_state return priority
def calc_priority(targets, zcat): """ Calculate target priorities from masks, observation/redshift status. Parameters ---------- targets : :class:`~numpy.ndarray` numpy structured array or astropy Table of targets. Must include the columns `DESI_TARGET`, `BGS_TARGET`, `MWS_TARGET` (or their SV/cmx equivalents). zcat : :class:`~numpy.ndarray` numpy structured array or Table of redshift information. Must include 'Z', `ZWARN`, `NUMOBS` and be the same length as `targets`. May also contain `NUMOBS_MORE` if this isn't the first time through MTL and `NUMOBS > 0`. Returns ------- :class:`~numpy.array` integer array of priorities. Notes ----- - If a target passes multiple selections, highest priority wins. - Will automatically detect if the passed targets are main survey, commissioning or SV and behave accordingly. """ # ADM check the input arrays are the same length. assert len(targets) == len(zcat) # ADM determine whether the input targets are main survey, cmx or SV. colnames, masks, survey = main_cmx_or_sv(targets) # ADM the target bits/names should be shared between main survey and SV. if survey != 'cmx': desi_target, bgs_target, mws_target = colnames desi_mask, bgs_mask, mws_mask = masks else: cmx_mask = masks[0] # Default is 0 priority, i.e. do not observe. priority = np.zeros(len(targets), dtype='i8') # Determine which targets have been observed. # TODO: this doesn't distinguish between really unobserved vs not yet # processed. unobs = (zcat["NUMOBS"] == 0) log.debug('calc_priority has %d unobserved targets' % (np.sum(unobs))) if np.all(unobs): done = np.zeros(len(targets), dtype=bool) zgood = np.zeros(len(targets), dtype=bool) zwarn = np.zeros(len(targets), dtype=bool) else: nmore = zcat["NUMOBS_MORE"] assert np.all(nmore >= 0) done = ~unobs & (nmore == 0) zgood = ~unobs & (nmore > 0) & (zcat['ZWARN'] == 0) zwarn = ~unobs & (nmore > 0) & (zcat['ZWARN'] != 0) # zgood, zwarn, done, and unobs should be mutually exclusive and cover all # targets. assert not np.any(unobs & zgood) assert not np.any(unobs & zwarn) assert not np.any(unobs & done) assert not np.any(zgood & zwarn) assert not np.any(zgood & done) assert not np.any(zwarn & done) assert np.all(unobs | done | zgood | zwarn) # DESI dark time targets. if survey != 'cmx': if desi_target in targets.dtype.names: # ADM 'LRG' is the guiding column in SV # ADM whereas 'LRG_1PASS' and 'LRG_2PASS' are in the main survey. names = ('ELG', 'LRG_1PASS', 'LRG_2PASS') if survey[0:2] == 'sv': names = ('ELG', 'LRG') for name in names: ii = (targets[desi_target] & desi_mask[name]) != 0 priority[ii & unobs] = np.maximum( priority[ii & unobs], desi_mask[name].priorities['UNOBS']) priority[ii & done] = np.maximum( priority[ii & done], desi_mask[name].priorities['DONE']) priority[ii & zgood] = np.maximum( priority[ii & zgood], desi_mask[name].priorities['MORE_ZGOOD']) priority[ii & zwarn] = np.maximum( priority[ii & zwarn], desi_mask[name].priorities['MORE_ZWARN']) # QSO could be Lyman-alpha or Tracer. name = 'QSO' ii = (targets[desi_target] & desi_mask[name]) != 0 good_hiz = zgood & (zcat['Z'] >= 2.15) & (zcat['ZWARN'] == 0) priority[ii & unobs] = np.maximum( priority[ii & unobs], desi_mask[name].priorities['UNOBS']) priority[ii & done] = np.maximum( priority[ii & done], desi_mask[name].priorities['DONE']) priority[ii & good_hiz] = np.maximum( priority[ii & good_hiz], desi_mask[name].priorities['MORE_ZGOOD']) priority[ii & ~good_hiz] = np.maximum( priority[ii & ~good_hiz], desi_mask[name].priorities['DONE']) priority[ii & zwarn] = np.maximum( priority[ii & zwarn], desi_mask[name].priorities['MORE_ZWARN']) # BGS targets. if bgs_target in targets.dtype.names: for name in bgs_mask.names(): ii = (targets[bgs_target] & bgs_mask[name]) != 0 priority[ii & unobs] = np.maximum( priority[ii & unobs], bgs_mask[name].priorities['UNOBS']) priority[ii & done] = np.maximum( priority[ii & done], bgs_mask[name].priorities['DONE']) priority[ii & zgood] = np.maximum( priority[ii & zgood], bgs_mask[name].priorities['MORE_ZGOOD']) priority[ii & zwarn] = np.maximum( priority[ii & zwarn], bgs_mask[name].priorities['MORE_ZWARN']) # MWS targets. if mws_target in targets.dtype.names: for name in mws_mask.names(): ii = (targets[mws_target] & mws_mask[name]) != 0 priority[ii & unobs] = np.maximum( priority[ii & unobs], mws_mask[name].priorities['UNOBS']) priority[ii & done] = np.maximum( priority[ii & done], mws_mask[name].priorities['DONE']) priority[ii & zgood] = np.maximum( priority[ii & zgood], mws_mask[name].priorities['MORE_ZGOOD']) priority[ii & zwarn] = np.maximum( priority[ii & zwarn], mws_mask[name].priorities['MORE_ZWARN']) # Special case: IN_BRIGHT_OBJECT means priority=-1 no matter what ii = (targets[desi_target] & desi_mask.IN_BRIGHT_OBJECT) != 0 priority[ii] = -1 # ADM Special case: SV-like commissioning targets. if 'CMX_TARGET' in targets.dtype.names: for name in ['SV0_' + label for label in ('BGS', 'MWS')]: ii = (targets['CMX_TARGET'] & cmx_mask[name]) != 0 priority[ii & unobs] = np.maximum( priority[ii & unobs], cmx_mask[name].priorities['UNOBS']) priority[ii & done] = np.maximum(priority[ii & done], cmx_mask[name].priorities['DONE']) priority[ii & zgood] = np.maximum( priority[ii & zgood], cmx_mask[name].priorities['MORE_ZGOOD']) priority[ii & zwarn] = np.maximum( priority[ii & zwarn], cmx_mask[name].priorities['MORE_ZWARN']) return priority
def test_priorities(self): """Test that priorities are set correctly for both the main survey and SV. """ # ADM loop through once for SV and once for the main survey. for prefix in ["", "SV1_"]: t = self.targets.copy() z = self.zcat.copy() main_names = ['DESI_TARGET', 'BGS_TARGET', 'MWS_TARGET'] for name in main_names: t.rename_column(name, prefix + name) # ADM retrieve the mask and column names for this survey flavor. colnames, masks, _ = main_cmx_or_sv(t) desi_target, bgs_target, mws_target = colnames desi_mask, bgs_mask, mws_mask = masks # - No targeting bits set is priority=0 self.assertTrue(np.all(calc_priority(t, z, "BRIGHT") == 0)) # ADM test QSO > LRG > ELG for main survey and SV. t[desi_target] = desi_mask.ELG self.assertTrue( np.all( calc_priority(t, z, "GRAY|DARK") == desi_mask.ELG.priorities['UNOBS'])) t[desi_target] |= desi_mask.LRG self.assertTrue( np.all( calc_priority(t, z, "GRAY|DARK") == desi_mask.LRG.priorities['UNOBS'])) t[desi_target] |= desi_mask.QSO self.assertTrue( np.all( calc_priority(t, z, "GRAY|DARK") == desi_mask.QSO.priorities['UNOBS'])) # - different states -> different priorities # - Done is Done, regardless of ZWARN. t[desi_target] = desi_mask.ELG t["PRIORITY_INIT"], t["NUMOBS_INIT"] = initial_priority_numobs(t) z['NUMOBS'] = [0, 1, 1] z['ZWARN'] = [1, 1, 0] p = make_mtl(t, "GRAY|DARK", zcat=z)["PRIORITY"] self.assertEqual(p[0], desi_mask.ELG.priorities['UNOBS']) self.assertEqual(p[1], desi_mask.ELG.priorities['DONE']) self.assertEqual(p[2], desi_mask.ELG.priorities['DONE']) # ADM In BRIGHT conditions BGS FAINT targets are # ADM never DONE, only MORE_ZGOOD. t[desi_target] = desi_mask.BGS_ANY t[bgs_target] = bgs_mask.BGS_FAINT t["PRIORITY_INIT"], t["NUMOBS_INIT"] = initial_priority_numobs(t) z['NUMOBS'] = [0, 1, 1] z['ZWARN'] = [1, 1, 0] p = make_mtl(t, "BRIGHT", zcat=z)["PRIORITY"] self.assertEqual(p[0], bgs_mask.BGS_FAINT.priorities['UNOBS']) self.assertEqual(p[1], bgs_mask.BGS_FAINT.priorities['MORE_ZWARN']) self.assertEqual(p[2], bgs_mask.BGS_FAINT.priorities['MORE_ZGOOD']) # BGS_FAINT: {UNOBS: 2000, MORE_ZWARN: 2000, MORE_ZGOOD: 1000, DONE: 2, OBS: 1, DONOTOBSERVE: 0} # ADM but in DARK conditions, BGS_FAINT should behave as # ADM for other target classes. z = self.zcat.copy() z['NUMOBS'] = [0, 1, 1] z['ZWARN'] = [1, 1, 0] p = make_mtl(t, "DARK|GRAY", zcat=z)["PRIORITY"] self.assertEqual(p[0], bgs_mask.BGS_FAINT.priorities['UNOBS']) self.assertEqual(p[1], bgs_mask.BGS_FAINT.priorities['DONE']) self.assertEqual(p[2], bgs_mask.BGS_FAINT.priorities['DONE']) # ADM In BRIGHT conditions BGS BRIGHT targets are # ADM never DONE, only MORE_ZGOOD. t[desi_target] = desi_mask.BGS_ANY t[bgs_target] = bgs_mask.BGS_BRIGHT t["PRIORITY_INIT"], t["NUMOBS_INIT"] = initial_priority_numobs(t) z['NUMOBS'] = [0, 1, 1] z['ZWARN'] = [1, 1, 0] p = make_mtl(t, "BRIGHT", zcat=z)["PRIORITY"] self.assertEqual(p[0], bgs_mask.BGS_BRIGHT.priorities['UNOBS']) self.assertEqual(p[1], bgs_mask.BGS_BRIGHT.priorities['MORE_ZWARN']) self.assertEqual(p[2], bgs_mask.BGS_BRIGHT.priorities['MORE_ZGOOD']) # BGS_BRIGHT: {UNOBS: 2100, MORE_ZWARN: 2100, MORE_ZGOOD: 1000, DONE: 2, OBS: 1, DONOTOBSERVE: 0} # ADM In BRIGHT conditions BGS targets are # ADM NEVER done even after 100 observations t[desi_target] = desi_mask.BGS_ANY t[bgs_target] = bgs_mask.BGS_BRIGHT t["PRIORITY_INIT"], t["NUMOBS_INIT"] = initial_priority_numobs(t) z['NUMOBS'] = [0, 100, 100] z['ZWARN'] = [1, 1, 0] p = calc_priority(t, z, "BRIGHT") self.assertEqual(p[0], bgs_mask.BGS_BRIGHT.priorities['UNOBS']) self.assertEqual(p[1], bgs_mask.BGS_BRIGHT.priorities['MORE_ZWARN']) self.assertEqual(p[2], bgs_mask.BGS_BRIGHT.priorities['MORE_ZGOOD']) # BGS ZGOOD targets always have lower priority than MWS targets that # are not DONE. Exempting the MWS "BACKUP" targets. # ADM first discard N/S informational bits from bitmask as these # ADM should never trump the other bits. bgs_names = [ name for name in bgs_mask.names() if 'NORTH' not in name and 'SOUTH' not in name ] mws_names = [ name for name in mws_mask.names() if 'NORTH' not in name and 'SOUTH' not in name and 'BACKUP' not in name ] lowest_mws_priority_unobs = [ mws_mask[n].priorities['UNOBS'] for n in mws_names ] lowest_bgs_priority_zgood = np.min( [bgs_mask[n].priorities['MORE_ZGOOD'] for n in bgs_names]) # ADM MORE_ZGOOD and MORE_ZWARN are only meaningful if a # ADM target class requests more than 1 observation (except # ADM for BGS, which has a numobs=infinity exception) lowest_mws_priority_zwarn = [ mws_mask[n].priorities['MORE_ZWARN'] for n in mws_names if mws_mask[n].numobs > 1 ] lowest_mws_priority_zgood = [ mws_mask[n].priorities['MORE_ZGOOD'] for n in mws_names if mws_mask[n].numobs > 1 ] lowest_mws_priority = np.min( np.concatenate([ lowest_mws_priority_unobs, lowest_mws_priority_zwarn, lowest_mws_priority_zgood ])) self.assertLess(lowest_bgs_priority_zgood, lowest_mws_priority)