def test_cmx_priorities(self): """Test that priority calculation can handle commissioning files. """ t = self.targets.copy() z = self.zcat # ADM restructure the table to look like a commissioning table. t.rename_column('DESI_TARGET', 'CMX_TARGET') t.remove_column('BGS_TARGET') t.remove_column('MWS_TARGET') # - No targeting bits set is priority=0 self.assertTrue(np.all(calc_priority(t, z, "GRAY|DARK") == 0)) # ADM retrieve the cmx_mask. colnames, masks, _ = main_cmx_or_sv(t) cmx_mask = masks[0] # ADM test handling of unobserved SV0_BGS and SV0_MWS for name, obscon in [("SV0_BGS", "BRIGHT"), ("SV0_MWS", "POOR")]: t['CMX_TARGET'] = cmx_mask[name] self.assertTrue( np.all( calc_priority(t, z, obscon) == cmx_mask[name].priorities['UNOBS'])) # ADM done is Done, regardless of ZWARN. for name, obscon in [("SV0_BGS", "BRIGHT"), ("SV0_MWS", "POOR")]: t['CMX_TARGET'] = cmx_mask[name] t["PRIORITY_INIT"], t["NUMOBS_INIT"] = initial_priority_numobs(t) # APC: Use NUMOBS_INIT here to avoid hardcoding NOBS corresponding to "done". numobs_done = t['NUMOBS_INIT'][0] z['NUMOBS'] = [0, numobs_done, numobs_done] z['ZWARN'] = [1, 1, 0] p = make_mtl(t, obscon, zcat=z)["PRIORITY"] self.assertEqual(p[0], cmx_mask[name].priorities['UNOBS']) self.assertEqual(p[1], cmx_mask[name].priorities['DONE']) self.assertEqual(p[2], cmx_mask[name].priorities['DONE']) # BGS ZGOOD targets always have lower priority than MWS targets that # are not DONE. lowest_bgs_priority_zgood = cmx_mask['SV0_BGS'].priorities[ 'MORE_ZGOOD'] lowest_mws_priority_unobs = cmx_mask['SV0_MWS'].priorities['UNOBS'] lowest_mws_priority_zwarn = cmx_mask['SV0_MWS'].priorities[ 'MORE_ZWARN'] lowest_mws_priority_zgood = cmx_mask['SV0_MWS'].priorities[ 'MORE_ZGOOD'] 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 test_bright_mask(self): t = self.targets t['DESI_TARGET'][0] = desi_mask.ELG t['DESI_TARGET'][1] = desi_mask.ELG | desi_mask.NEAR_BRIGHT_OBJECT t['DESI_TARGET'][2] = desi_mask.ELG | desi_mask.IN_BRIGHT_OBJECT p = calc_priority(t) self.assertEqual( p[0], p[1], "NEAR_BRIGHT_OBJECT shouldn't impact priority but {} != {}".format( p[0], p[1])) self.assertEqual(p[2], -1, "IN_BRIGHT_OBJECT priority not -1")
def test_priorities(self): t = self.targets #- No targeting bits set is priority=0 self.assertTrue(np.all(calc_priority(t) == 0)) #- test QSO > LRG > ELG t['DESI_TARGET'] = desi_mask.ELG self.assertTrue(np.all(calc_priority(t) == desi_mask.ELG.priorities['UNOBS'])) t['DESI_TARGET'] |= desi_mask.LRG self.assertTrue(np.all(calc_priority(t) == desi_mask.LRG.priorities['UNOBS'])) t['DESI_TARGET'] |= desi_mask.QSO self.assertTrue(np.all(calc_priority(t) == desi_mask.QSO.priorities['UNOBS'])) #- different states -> different priorities t['DESI_TARGET'] = desi_mask.BGS_ANY t['BGS_TARGET'] |= bgs_mask.BGS_FAINT t['NUMOBS'] = [0, 1, 1] t['ZWARN'] = [1, 1, 0] p = calc_priority(t) 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: 2200, MORE_ZGOOD: 2300} #- Done is Done, regardless of ZWARN t['DESI_TARGET'] = desi_mask.BGS_ANY t['BGS_TARGET'] = bgs_mask.BGS_BRIGHT #- only one obs needed t['NUMOBS'] = [0, 1, 1] t['ZWARN'] = [1, 1, 0] p = calc_priority(t) self.assertEqual(p[0], bgs_mask.BGS_BRIGHT.priorities['UNOBS']) self.assertEqual(p[1], bgs_mask.BGS_BRIGHT.priorities['DONE']) self.assertEqual(p[2], bgs_mask.BGS_BRIGHT.priorities['DONE'])
def make_mtl(targets, zcat=None, trim=True): ''' Adds NUMOBS, PRIORITY, and GRAYLAYER columns to a targets table Args: targets : Table with columns TARGETID, DESI_TARGET Optional: zcat : redshift catalog table with columns TARGETID, NUMOBS, Z, ZWARN trim: if True (default), don't include targets that don't need any more observations. If False, include every input target. Returns: MTL Table with targets columns plus * NUMOBS_MORE - number of additional observations requested * PRIORITY - target priority (larger number = higher priority) * GRAYLAYER - can this be observed during gray time? TODO: Check if input targets is ever altered (ist shouldn't...) ''' n = len(targets) targets = Table(targets) if zcat is not None: ztargets = join(targets, zcat, keys='TARGETID', join_type='outer') if ztargets.masked: unobs = ztargets['NUMOBS'].mask ztargets['NUMOBS'][unobs] = 0 else: ztargets = targets.copy() ztargets['NUMOBS'] = np.zeros(n, dtype=np.int32) ztargets['Z'] = -1 * np.ones(n, dtype=np.float32) ztargets['ZWARN'] = -1 * np.ones(n, dtype=np.int32) ztargets['NUMOBS_MORE'] = np.maximum(0, calc_numobs(ztargets) - ztargets['NUMOBS']) mtl = targets.copy() mtl['NUMOBS_MORE'] = ztargets['NUMOBS_MORE'] mtl['PRIORITY'] = calc_priority(ztargets) #- ELGs can be observed during gray time graylayer = np.zeros(n, dtype='i4') iselg = (mtl['DESI_TARGET'] & desi_mask.ELG) != 0 graylayer[iselg] = 1 mtl['GRAYLAYER'] = graylayer if trim: notdone = mtl['NUMOBS_MORE'] > 0 mtl = mtl[notdone] return mtl
def make_mtl(targets, zcat=None, trim=False): """Adds NUMOBS, PRIORITY, and OBSCONDITIONS columns to a targets table. Parameters ---------- targets : :class:`~numpy.array` or `~astropy.table.Table` A numpy rec array or astropy Table with at least the columns ``TARGETID``, ``DESI_TARGET``, ``NUMOBS_INIT``, ``PRIORITY_INIT``. or the corresponding columns for SV or commissioning. zcat : :class:`~astropy.table.Table`, optional Redshift catalog table with columns ``TARGETID``, ``NUMOBS``, ``Z``, ``ZWARN``. trim : :class:`bool`, optional If ``True`` (default), don't include targets that don't need any more observations. If ``False``, include every input target. Returns ------- :class:`~astropy.table.Table` MTL Table with targets columns plus: * NUMOBS_MORE - number of additional observations requested * PRIORITY - target priority (larger number = higher priority) * OBSCONDITIONS - replaces old GRAYLAYER """ # ADM set up the default logger. from desiutil.log import get_logger log = get_logger() # ADM determine whether the input targets are main survey, cmx or SV. colnames, masks, survey = main_cmx_or_sv(targets) # ADM set the first column to be the "desitarget" column desi_target, desi_mask = colnames[0], masks[0] # Trim targets from zcat that aren't in original targets table if zcat is not None: ok = np.in1d(zcat['TARGETID'], targets['TARGETID']) num_extra = np.count_nonzero(~ok) if num_extra > 0: log.warning("Ignoring {} zcat entries that aren't " "in the input target list".format(num_extra)) zcat = zcat[ok] n = len(targets) # ADM if the input target columns were incorrectly called NUMOBS or PRIORITY # ADM rename them to NUMOBS_INIT or PRIORITY_INIT. # ADM Note that the syntax is slightly different for a Table. for name in ['NUMOBS', 'PRIORITY']: if isinstance(targets, Table): try: targets.rename_column(name, name + '_INIT') except KeyError: pass else: targets.dtype.names = [ name + '_INIT' if col == name else col for col in targets.dtype.names ] # ADM if a redshift catalog was passed, order it to match the input targets # ADM catalog on 'TARGETID'. if zcat is not None: # ADM there might be a quicker way to do this? # ADM set up a dictionary of the indexes of each target id. d = dict(tuple(zip(targets["TARGETID"], np.arange(n)))) # ADM loop through the zcat and look-up the index in the dictionary. zmatcher = np.array([d[tid] for tid in zcat["TARGETID"]]) ztargets = zcat if ztargets.masked: unobs = ztargets['NUMOBS'].mask ztargets['NUMOBS'][unobs] = 0 unobsz = ztargets['Z'].mask ztargets['Z'][unobsz] = -1 unobszw = ztargets['ZWARN'].mask ztargets['ZWARN'][unobszw] = -1 else: ztargets = Table() ztargets['TARGETID'] = targets['TARGETID'] ztargets['NUMOBS'] = np.zeros(n, dtype=np.int32) ztargets['Z'] = -1 * np.ones(n, dtype=np.float32) ztargets['ZWARN'] = -1 * np.ones(n, dtype=np.int32) # ADM if zcat wasn't passed, there is a one-to-one correspondence # ADM between the targets and the zcat. zmatcher = np.arange(n) # ADM extract just the targets that match the input zcat. targets_zmatcher = targets[zmatcher] # ADM use passed value of NUMOBS_INIT instead of calling the memory-heavy calc_numobs. # ztargets['NUMOBS_MORE'] = np.maximum(0, calc_numobs(ztargets) - ztargets['NUMOBS']) ztargets['NUMOBS_MORE'] = np.maximum( 0, targets_zmatcher['NUMOBS_INIT'] - ztargets['NUMOBS']) # ADM we need a minor hack to ensure that BGS targets are observed once (and only once) # ADM every time, regardless of how many times they've previously been observed. # ADM I've turned this off for commissioning. Not sure if we'll keep it in general. if survey != 'cmx': ii = targets_zmatcher[desi_target] & desi_mask.BGS_ANY > 0 ztargets['NUMOBS_MORE'][ii] = 1 # ADM assign priorities, note that only things in the zcat can have changed priorities. # ADM anything else will be assigned PRIORITY_INIT, below. priority = calc_priority(targets_zmatcher, ztargets) # If priority went to 0==DONOTOBSERVE or 1==OBS or 2==DONE, then NUMOBS_MORE should also be 0. # ## mtl['NUMOBS_MORE'] = ztargets['NUMOBS_MORE'] ii = (priority <= 2) log.info( '{:d} of {:d} targets have priority zero, setting N_obs=0.'.format( np.sum(ii), n)) ztargets['NUMOBS_MORE'][ii] = 0 # - Set the OBSCONDITIONS mask for each target bit. obscon = set_obsconditions(targets) # ADM set up the output mtl table. mtl = Table(targets) mtl.meta['EXTNAME'] = 'MTL' # ADM any target that wasn't matched to the ZCAT should retain its # ADM original (INIT) value of PRIORITY and NUMOBS. mtl['NUMOBS_MORE'] = mtl['NUMOBS_INIT'] mtl['PRIORITY'] = mtl['PRIORITY_INIT'] # ADM now populate the new mtl columns with the updated information. mtl['OBSCONDITIONS'] = obscon mtl['PRIORITY'][zmatcher] = priority mtl['NUMOBS_MORE'][zmatcher] = ztargets['NUMOBS_MORE'] # Filter out any targets marked as done. if trim: notdone = mtl['NUMOBS_MORE'] > 0 log.info('{:d} of {:d} targets are done, trimming these'.format( len(mtl) - np.sum(notdone), len(mtl))) mtl = mtl[notdone] # Filtering can reset the fill_value, which is just wrong wrong wrong # See https://github.com/astropy/astropy/issues/4707 # and https://github.com/astropy/astropy/issues/4708 mtl['NUMOBS_MORE'].fill_value = -1 return mtl
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 create_mtl(target_file, specresults_file, output_file): """ Consolidates a Merged Target List file. """ # input basic data # targets = fitsio.read(target_file, 1, upper=True) # specresults = fitsio.read(specresults_file, 1, upper=True) targets = Table.read(target_file, format='fits') specresults = Table.read(specresults_file, format='fits') n_points = len(targets['TARGETID']) n_spec = len(specresults['TARGETID']) print("{} objects in the input MTL file".format(n_points)) print("{} objects in the zcat file".format(n_spec)) # structure for output data type_table = [ ('TARGETID', '>i8'), ('BRICKNAME', '|S8'), ('RA', '>f4'), ('DEC', '>f4'), ('NUMOBS', '>i4'), ('PRIORITY', '>i4'), ('LASTPASS', '>i4'), ('DESI_TARGET', '>i8'), ('BGS_TARGET', '>i8'), ('MWS_TARGET', '>i8') ] # loops over the targets looking for: # - number of observations performed on the target # - a definite targetflag num_obs = np.zeros(n_points, dtype='int') priority = np.zeros(n_points, dtype='int') required_numobs = targets['NUMOBS'] iiobs = np.in1d(targets['TARGETID'], specresults['TARGETID']) # import IPython # IPython.embed() # update the priority, # TEMPORARY: We don't take into account the targetstate. This should be changed). priority = calc_priority(targets) id_results = 0 for i in range(n_points): if iiobs[i]: n_obs_done = specresults['NUMOBS'][id_results] id_results = id_results + 1 else: n_obs_done = 0 num_obs[i] = numobs_needed(required_numobs[i], n_obs_done) data = np.ndarray(shape=(n_points), dtype=type_table) data['TARGETID'] = targets['TARGETID'] data['RA'] = targets['RA'] data['DEC'] = targets['DEC'] data['BRICKNAME'] = targets['BRICKNAME'] data['DESI_TARGET'] = targets['DESI_TARGET'] data['BGS_TARGET'] = targets['BGS_TARGET'] data['MWS_TARGET'] = targets['MWS_TARGET'] data['LASTPASS'] = targets['LASTPASS'] data['NUMOBS'] = num_obs data['PRIORITY'] = priority #- Create header to include versions, etc. hdr = fitsio.FITSHDR() hdr['DEPNAM00'] = 'mtl' hdr.add_record(dict(name='DEPVER00', value=mtl.__version__, comment='mtl.__version__')) hdr['DEPNAM01'] = 'mtl-git' hdr.add_record(dict(name='DEPVAL01', value=mtl.gitversion(), comment='git revision')) fitsio.write(output_file, data, extname='MTL', header=hdr, clobber=True) print('wrote {} items to MTL file'.format(n_points)) #- TEMPORARY: fiberassign needs ASCII not FITS #- read it back in an write out an ascii table # t = Table.read(output_file, format='fits') #text_output = output_file.replace('.fits', '.txt') #assert text_output != output_file #t.write(text_output, format='ascii.commented_header') return
def make_mtl(targets, obscon, zcat=None, trim=False, scnd=None): """Adds NUMOBS, PRIORITY, and OBSCONDITIONS columns to a targets table. Parameters ---------- targets : :class:`~numpy.array` or `~astropy.table.Table` A numpy rec array or astropy Table with at least the columns ``TARGETID``, ``DESI_TARGET``, ``NUMOBS_INIT``, ``PRIORITY_INIT``. or the corresponding columns for SV or commissioning. 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. zcat : :class:`~astropy.table.Table`, optional Redshift catalog table with columns ``TARGETID``, ``NUMOBS``, ``Z``, ``ZWARN``. trim : :class:`bool`, optional If ``True`` (default), don't include targets that don't need any more observations. If ``False``, include every input target. scnd : :class:`~numpy.array`, `~astropy.table.Table`, optional A set of secondary targets associated with the `targets`. As with the `target` must include at least ``TARGETID``, ``NUMOBS_INIT``, ``PRIORITY_INIT`` or the corresponding SV columns. The secondary targets will be padded to have the same columns as the targets, and concatenated with them. Returns ------- :class:`~astropy.table.Table` MTL Table with targets columns plus: * NUMOBS_MORE - number of additional observations requested * PRIORITY - target priority (larger number = higher priority) * OBSCONDITIONS - replaces old GRAYLAYER """ start = time() # ADM set up the default logger. from desiutil.log import get_logger log = get_logger() # ADM if secondaries were passed, concatenate them with the targets. if scnd is not None: nrows = len(scnd) log.info( 'Pad {} primary targets with {} secondaries...t={:.1f}s'.format( len(targets), nrows, time() - start)) padit = np.zeros(nrows, dtype=targets.dtype) sharedcols = set(targets.dtype.names).intersection( set(scnd.dtype.names)) for col in sharedcols: padit[col] = scnd[col] targets = np.concatenate([targets, padit]) # APC Propagate a flag on which targets came from scnd is_scnd = np.repeat(False, len(targets)) is_scnd[-nrows:] = True log.info('Done with padding...t={:.1f}s'.format(time() - start)) # ADM determine whether the input targets are main survey, cmx or SV. colnames, masks, survey = main_cmx_or_sv(targets) # ADM set the first column to be the "desitarget" column desi_target, desi_mask = colnames[0], masks[0] # Trim targets from zcat that aren't in original targets table if zcat is not None: ok = np.in1d(zcat['TARGETID'], targets['TARGETID']) num_extra = np.count_nonzero(~ok) if num_extra > 0: log.warning("Ignoring {} zcat entries that aren't " "in the input target list".format(num_extra)) zcat = zcat[ok] n = len(targets) # ADM if the input target columns were incorrectly called NUMOBS or PRIORITY # ADM rename them to NUMOBS_INIT or PRIORITY_INIT. # ADM Note that the syntax is slightly different for a Table. for name in ['NUMOBS', 'PRIORITY']: if isinstance(targets, Table): try: targets.rename_column(name, name + '_INIT') except KeyError: pass else: targets.dtype.names = [ name + '_INIT' if col == name else col for col in targets.dtype.names ] # ADM if a redshift catalog was passed, order it to match the input targets # ADM catalog on 'TARGETID'. if zcat is not None: # ADM there might be a quicker way to do this? # ADM set up a dictionary of the indexes of each target id. d = dict(tuple(zip(targets["TARGETID"], np.arange(n)))) # ADM loop through the zcat and look-up the index in the dictionary. zmatcher = np.array([d[tid] for tid in zcat["TARGETID"]]) ztargets = zcat if ztargets.masked: unobs = ztargets['NUMOBS'].mask ztargets['NUMOBS'][unobs] = 0 unobsz = ztargets['Z'].mask ztargets['Z'][unobsz] = -1 unobszw = ztargets['ZWARN'].mask ztargets['ZWARN'][unobszw] = -1 else: ztargets = Table() ztargets['TARGETID'] = targets['TARGETID'] ztargets['NUMOBS'] = np.zeros(n, dtype=np.int32) ztargets['Z'] = -1 * np.ones(n, dtype=np.float32) ztargets['ZWARN'] = -1 * np.ones(n, dtype=np.int32) # ADM if zcat wasn't passed, there is a one-to-one correspondence # ADM between the targets and the zcat. zmatcher = np.arange(n) # ADM extract just the targets that match the input zcat. targets_zmatcher = targets[zmatcher] # ADM use passed value of NUMOBS_INIT instead of calling the memory-heavy calc_numobs. # ztargets['NUMOBS_MORE'] = np.maximum(0, calc_numobs(ztargets) - ztargets['NUMOBS']) ztargets['NUMOBS_MORE'] = np.maximum( 0, targets_zmatcher['NUMOBS_INIT'] - ztargets['NUMOBS']) # ADM need a minor hack to ensure BGS targets are observed once # ADM (and only once) every time during the BRIGHT survey, regardless # ADM of how often they've previously been observed. I've turned this # ADM off for commissioning. Not sure if we'll keep it in general. if survey != 'cmx': # ADM only if we're considering bright survey conditions. if (obsconditions.mask(obscon) & obsconditions.mask("BRIGHT")) != 0: ii = targets_zmatcher[desi_target] & desi_mask.BGS_ANY > 0 ztargets['NUMOBS_MORE'][ii] = 1 if survey == 'main': # If the object is confirmed to be a tracer QSO, then don't request more observations if (obsconditions.mask(obscon) & obsconditions.mask("DARK")) != 0: if zcat is not None: ii = ztargets['SPECTYPE'] == 'QSO' ii &= (ztargets['ZWARN'] == 0) ii &= (ztargets['Z'] < 2.1) ii &= (ztargets['NUMOBS'] > 0) ztargets['NUMOBS_MORE'][ii] = 0 # ADM assign priorities, note that only things in the zcat can have changed priorities. # ADM anything else will be assigned PRIORITY_INIT, below. priority = calc_priority(targets_zmatcher, ztargets, obscon) # If priority went to 0==DONOTOBSERVE or 1==OBS or 2==DONE, then NUMOBS_MORE should also be 0. # ## mtl['NUMOBS_MORE'] = ztargets['NUMOBS_MORE'] ii = (priority <= 2) log.info( '{:d} of {:d} targets have priority zero, setting N_obs=0.'.format( np.sum(ii), n)) ztargets['NUMOBS_MORE'][ii] = 0 # - Set the OBSCONDITIONS mask for each target bit. obsconmask = set_obsconditions(targets) # APC obsconmask will now be incorrect for secondary-only targets. Fix this # APC using the mask on secondary targets. if scnd is not None: obsconmask[is_scnd] = set_obsconditions(targets[is_scnd], scnd=True) # ADM set up the output mtl table. mtl = Table(targets) mtl.meta['EXTNAME'] = 'MTL' # ADM any target that wasn't matched to the ZCAT should retain its # ADM original (INIT) value of PRIORITY and NUMOBS. mtl['NUMOBS_MORE'] = mtl['NUMOBS_INIT'] mtl['PRIORITY'] = mtl['PRIORITY_INIT'] # ADM now populate the new mtl columns with the updated information. mtl['OBSCONDITIONS'] = obsconmask mtl['PRIORITY'][zmatcher] = priority mtl['NUMOBS_MORE'][zmatcher] = ztargets['NUMOBS_MORE'] # Filter out any targets marked as done. if trim: notdone = mtl['NUMOBS_MORE'] > 0 log.info('{:d} of {:d} targets are done, trimming these'.format( len(mtl) - np.sum(notdone), len(mtl))) mtl = mtl[notdone] # Filtering can reset the fill_value, which is just wrong wrong wrong # See https://github.com/astropy/astropy/issues/4707 # and https://github.com/astropy/astropy/issues/4708 mtl['NUMOBS_MORE'].fill_value = -1 log.info('Done...t={:.1f}s'.format(time() - start)) return mtl
def make_mtl(targets, obscon, zcat=None, scnd=None, trim=False, trimcols=False, trimtozcat=False): """Adds fiberassign and zcat columns to a targets table. Parameters ---------- targets : :class:`~numpy.array` or `~astropy.table.Table` A numpy rec array or astropy Table with at least the columns ``TARGETID``, ``DESI_TARGET``, ``NUMOBS_INIT``, ``PRIORITY_INIT``. or the corresponding columns for SV or commissioning. 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. zcat : :class:`~astropy.table.Table`, optional Redshift catalog table with columns ``TARGETID``, ``NUMOBS``, ``Z``, ``ZWARN``. scnd : :class:`~numpy.array`, `~astropy.table.Table`, optional A set of secondary targets associated with the `targets`. As with the `target` must include at least ``TARGETID``, ``NUMOBS_INIT``, ``PRIORITY_INIT`` or the corresponding SV columns. The secondary targets will be padded to have the same columns as the targets, and concatenated with them. trim : :class:`bool`, optional If ``True`` (default), don't include targets that don't need any more observations. If ``False``, include every input target. trimcols : :class:`bool`, optional, defaults to ``False`` Only pass through columns in `targets` that are actually needed for fiberassign (see `desitarget.mtl.mtldatamodel`). trimtozcat : :class:`bool`, optional, defaults to ``False`` Only return targets that have been UPDATED (i.e. the targets with a match in `zcat`). Returns all targets if `zcat` is ``None``. Returns ------- :class:`~astropy.table.Table` MTL Table with targets columns plus: * NUMOBS_MORE - number of additional observations requested * PRIORITY - target priority (larger number = higher priority) * TARGET_STATE - the observing state that corresponds to PRIORITY * OBSCONDITIONS - replaces old GRAYLAYER * TIMESTAMP - time that (this) make_mtl() function was run * VERSION - version of desitarget used to run make_mtl() """ start = time() # ADM set up the default logger. from desiutil.log import get_logger log = get_logger() # ADM if trimcols was passed, reduce input target columns to minimal. if trimcols: mtldm = switch_main_cmx_or_sv(mtldatamodel, targets) cullcols = list(set(targets.dtype.names) - set(mtldm.dtype.names)) if isinstance(targets, Table): targets.remove_columns(cullcols) else: targets = rfn.drop_fields(targets, cullcols) # ADM determine whether the input targets are main survey, cmx or SV. colnames, masks, survey = main_cmx_or_sv(targets, scnd=True) # ADM set the first column to be the "desitarget" column desi_target, desi_mask = colnames[0], masks[0] scnd_target = colnames[-1] # ADM if secondaries were passed, concatenate them with the targets. if scnd is not None: nrows = len(scnd) log.info('Pad {} primary targets with {} secondaries...t={:.1f}s'.format( len(targets), nrows, time()-start)) padit = np.zeros(nrows, dtype=targets.dtype) sharedcols = set(targets.dtype.names).intersection(set(scnd.dtype.names)) for col in sharedcols: padit[col] = scnd[col] targets = np.concatenate([targets, padit]) # APC Propagate a flag on which targets came from scnd is_scnd = np.repeat(False, len(targets)) is_scnd[-nrows:] = True log.info('Done with padding...t={:.1f}s'.format(time()-start)) # Trim targets from zcat that aren't in original targets table. if zcat is not None: ok = np.in1d(zcat['TARGETID'], targets['TARGETID']) num_extra = np.count_nonzero(~ok) if num_extra > 0: log.warning("Ignoring {} zcat entries that aren't " "in the input target list".format(num_extra)) zcat = zcat[ok] n = len(targets) # ADM if a redshift catalog was passed, order it to match the input targets # ADM catalog on 'TARGETID'. if zcat is not None: # ADM there might be a quicker way to do this? # ADM set up a dictionary of the indexes of each target id. d = dict(tuple(zip(targets["TARGETID"], np.arange(n)))) # ADM loop through the zcat and look-up the index in the dictionary. zmatcher = np.array([d[tid] for tid in zcat["TARGETID"]]) ztargets = zcat if ztargets.masked: unobs = ztargets['NUMOBS'].mask ztargets['NUMOBS'][unobs] = 0 unobsz = ztargets['Z'].mask ztargets['Z'][unobsz] = -1 unobszw = ztargets['ZWARN'].mask ztargets['ZWARN'][unobszw] = -1 else: ztargets = Table() ztargets['TARGETID'] = targets['TARGETID'] ztargets['NUMOBS'] = np.zeros(n, dtype=np.int32) ztargets['Z'] = -1 * np.ones(n, dtype=np.float32) ztargets['ZWARN'] = -1 * np.ones(n, dtype=np.int32) # ADM if zcat wasn't passed, there is a one-to-one correspondence # ADM between the targets and the zcat. zmatcher = np.arange(n) # ADM extract just the targets that match the input zcat. targets_zmatcher = targets[zmatcher] # ADM update the number of observations for the targets. ztargets['NUMOBS_MORE'] = calc_numobs_more(targets_zmatcher, ztargets, obscon) # ADM assign priorities. Only things in the zcat can have changed # ADM priorities. Anything else is assigned PRIORITY_INIT, below. priority, target_state = calc_priority( targets_zmatcher, ztargets, obscon, state=True) # If priority went to 0==DONOTOBSERVE or 1==OBS or 2==DONE, then # NUMOBS_MORE should also be 0. # ## mtl['NUMOBS_MORE'] = ztargets['NUMOBS_MORE'] ii = (priority <= 2) log.info('{:d} of {:d} targets have priority zero, setting N_obs=0.'.format( np.sum(ii), n)) ztargets['NUMOBS_MORE'][ii] = 0 # - Set the OBSCONDITIONS mask for each target bit. obsconmask = set_obsconditions(targets) # APC obsconmask will now be incorrect for secondary-only targets. Fix this # APC using the mask on secondary targets. if scnd is not None: obsconmask[is_scnd] = set_obsconditions(targets[is_scnd], scnd=True) # ADM set up the output mtl table. mtl = Table(targets) mtl.meta['EXTNAME'] = 'MTL' # ADM add a placeholder for the secondary bit-mask, if it isn't there. if scnd_target not in mtl.dtype.names: mtl[scnd_target] = np.zeros(len(mtl), dtype=mtldatamodel["SCND_TARGET"].dtype) # ADM initialize columns to avoid zero-length/missing/format errors. zcols = ["NUMOBS_MORE", "NUMOBS", "Z", "ZWARN"] for col in zcols + ["TARGET_STATE", "TIMESTAMP", "VERSION"]: mtl[col] = np.empty(len(mtl), dtype=mtldatamodel[col].dtype) # ADM any target that wasn't matched to the ZCAT should retain its # ADM original (INIT) value of PRIORITY and NUMOBS. mtl['NUMOBS_MORE'] = mtl['NUMOBS_INIT'] mtl['PRIORITY'] = mtl['PRIORITY_INIT'] mtl['TARGET_STATE'] = "UNOBS" # ADM add the time and version of the desitarget code that was run. utc = datetime.utcnow().isoformat(timespec='seconds') mtl["TIMESTAMP"] = utc mtl["VERSION"] = dt_version # ADM now populate the new mtl columns with the updated information. mtl['OBSCONDITIONS'] = obsconmask mtl['PRIORITY'][zmatcher] = priority mtl['TARGET_STATE'][zmatcher] = target_state for col in zcols: mtl[col][zmatcher] = ztargets[col] # Filter out any targets marked as done. if trim: notdone = mtl['NUMOBS_MORE'] > 0 log.info('{:d} of {:d} targets are done, trimming these'.format( len(mtl) - np.sum(notdone), len(mtl)) ) mtl = mtl[notdone] # Filtering can reset the fill_value, which is just wrong wrong wrong # See https://github.com/astropy/astropy/issues/4707 # and https://github.com/astropy/astropy/issues/4708 mtl['NUMOBS_MORE'].fill_value = -1 # ADM assert the data model is complete. # ADM turning this off for now, useful for testing. # mtltypes = [mtl[i].dtype.type for i in mtl.dtype.names] # mtldmtypes = [mtldm[i].dtype.type for i in mtl.dtype.names] # assert set(mtl.dtype.names) == set(mtldm.dtype.names) # assert mtltypes == mtldmtypes log.info('Done...t={:.1f}s'.format(time()-start)) if trimtozcat: return mtl[zmatcher] return mtl
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)
def create_mtl(target_file, specresults_file, output_file): """ Consolidates a Merged Target List file. """ # input basic data # targets = fitsio.read(target_file, 1, upper=True) # specresults = fitsio.read(specresults_file, 1, upper=True) targets = Table.read(target_file, format='fits') specresults = Table.read(specresults_file, format='fits') n_points = len(targets['TARGETID']) n_spec = len(specresults['TARGETID']) print("{} objects in the input MTL file".format(n_points)) print("{} objects in the zcat file".format(n_spec)) # structure for output data type_table = [('TARGETID', '>i8'), ('BRICKNAME', '|S8'), ('RA', '>f4'), ('DEC', '>f4'), ('NUMOBS', '>i4'), ('PRIORITY', '>i4'), ('LASTPASS', '>i4'), ('DESI_TARGET', '>i8'), ('BGS_TARGET', '>i8'), ('MWS_TARGET', '>i8')] # loops over the targets looking for: # - number of observations performed on the target # - a definite targetflag num_obs = np.zeros(n_points, dtype='int') priority = np.zeros(n_points, dtype='int') required_numobs = targets['NUMOBS'] iiobs = np.in1d(targets['TARGETID'], specresults['TARGETID']) # import IPython # IPython.embed() # update the priority, # TEMPORARY: We don't take into account the targetstate. This should be changed). priority = calc_priority(targets) id_results = 0 for i in range(n_points): if iiobs[i]: n_obs_done = specresults['NUMOBS'][id_results] id_results = id_results + 1 else: n_obs_done = 0 num_obs[i] = numobs_needed(required_numobs[i], n_obs_done) data = np.ndarray(shape=(n_points), dtype=type_table) data['TARGETID'] = targets['TARGETID'] data['RA'] = targets['RA'] data['DEC'] = targets['DEC'] data['BRICKNAME'] = targets['BRICKNAME'] data['DESI_TARGET'] = targets['DESI_TARGET'] data['BGS_TARGET'] = targets['BGS_TARGET'] data['MWS_TARGET'] = targets['MWS_TARGET'] data['LASTPASS'] = targets['LASTPASS'] data['NUMOBS'] = num_obs data['PRIORITY'] = priority #- Create header to include versions, etc. hdr = fitsio.FITSHDR() hdr['DEPNAM00'] = 'mtl' hdr.add_record( dict(name='DEPVER00', value=mtl.__version__, comment='mtl.__version__')) hdr['DEPNAM01'] = 'mtl-git' hdr.add_record( dict(name='DEPVAL01', value=mtl.gitversion(), comment='git revision')) fitsio.write(output_file, data, extname='MTL', header=hdr, clobber=True) print('wrote {} items to MTL file'.format(n_points)) #- TEMPORARY: fiberassign needs ASCII not FITS #- read it back in an write out an ascii table # t = Table.read(output_file, format='fits') #text_output = output_file.replace('.fits', '.txt') #assert text_output != output_file #t.write(text_output, format='ascii.commented_header') return
def test_priorities(self): t = self.targets #- No targeting bits set is priority=0 self.assertTrue(np.all(calc_priority(t) == 0)) #- test QSO > LRG > ELG t['DESI_TARGET'] = desi_mask.ELG self.assertTrue( np.all(calc_priority(t) == desi_mask.ELG.priorities['UNOBS'])) t['DESI_TARGET'] |= desi_mask.LRG self.assertTrue( np.all(calc_priority(t) == desi_mask.LRG.priorities['UNOBS'])) t['DESI_TARGET'] |= desi_mask.QSO self.assertTrue( np.all(calc_priority(t) == desi_mask.QSO.priorities['UNOBS'])) #- different states -> different priorities #- Done is Done, regardless of ZWARN. t['DESI_TARGET'] = desi_mask.ELG t['NUMOBS'] = [0, 1, 1] t['ZWARN'] = [1, 1, 0] p = calc_priority(t) 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['NUMOBS'] = [0, 1, 1] t['ZWARN'] = [1, 1, 0] p = calc_priority(t) 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['NUMOBS'] = [0, 1, 1] t['ZWARN'] = [1, 1, 0] p = calc_priority(t) 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['NUMOBS'] = [0, 100, 100] t['ZWARN'] = [1, 1, 0] p = calc_priority(t) 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. lowest_bgs_priority_zgood = min( [bgs_mask[n].priorities['MORE_ZGOOD'] for n in bgs_mask.names()]) lowest_mws_priority_unobs = min( [mws_mask[n].priorities['UNOBS'] for n in mws_mask.names()]) lowest_mws_priority_zwarn = min( [mws_mask[n].priorities['MORE_ZWARN'] for n in mws_mask.names()]) lowest_mws_priority_zgood = min( [mws_mask[n].priorities['MORE_ZGOOD'] for n in mws_mask.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)