def _cmx_calc_priority(targets, priority, obscon, unobs, done, zgood, zwarn, cmx_mask, obsconditions): """Special-case logic for target priorities in CMX. Parameters ---------- targets : :class:`~numpy.ndarray` numpy structured array or astropy Table of targets. Must include the column `CMX_TARGET`. priority : :class:`~numpy.ndarray` Initial priority values set, in calc_priorities(). 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. unobs : :class:`~numpy.ndarray` Boolean flag on targets indicating state UNOBS. done : :class:`~numpy.ndarray` Boolean flag on targets indicating state DONE. zgood : :class:`~numpy.ndarray` Boolean flag on targets indicating state ZGOOD. zwarn : :class:`~numpy.ndarray` Boolean flag on targets indicating state ZWARN. cmx_mask : :class:`~desiutil.bitmask.BitMask` The CMX target bitmask. obscondtions : :class:`~desiutil.bitmask.BitMask` The CMX obsconditions bitmask. Returns ------- :class:`~numpy.ndarray` The updated priority values. Notes ----- - Intended to be called only from within calc_priority(), where any pre-processing of the target state flags (uobs, done, zgood, zwarn) is handled. """ # Build a whitelist of targets to update names_to_update = ['SV0_' + label for label in ('STD_FAINT', 'STD_BRIGHT', 'BGS', 'MWS', 'WD', 'MWS_FAINT', 'MWS_CLUSTER', 'MWS_CLUSTER_VERYBRIGHT')] names_to_update.extend(['BACKUP_BRIGHT', 'BACKUP_FAINT']) for name in names_to_update: pricon = obsconditions.mask(cmx_mask[name].obsconditions) if (obsconditions.mask(obscon) & pricon) != 0: 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 set_obsconditions(targets): """set the OBSCONDITIONS mask for each target bit. Parameters ---------- targets : :class:`~numpy.ndarray` An array of targets generated by, e.g., :mod:`~desitarget.cuts`. Must include at least (all of) the columns `DESI_TARGET`, `BGS_TARGET`, `MWS_TARGET` or corresponding cmx or SV columns. Returns ------- :class:`~numpy.ndarray` The OBSCONDITIONS bitmask for the passed targets. Notes ----- - the OBSCONDITIONS for each target bit is in the file, e.g. data/targetmask.yaml. It can be retrieved using, for example, `obsconditions.mask(desi_mask["ELG"].obsconditions)`. """ colnames, masks, _ = main_cmx_or_sv(targets) n = len(targets) obscon = np.zeros(n, dtype='i4') for mask, xxx_target in zip(masks, colnames): for name in mask.names(): # ADM which targets have this bit for this mask set? ii = (targets[xxx_target] & mask[name]) != 0 # ADM under what conditions can that bit be observed? if np.any(ii): obscon[ii] |= obsconditions.mask(mask[name].obsconditions) return obscon
def read_files(scxdir, scnd_mask): """Read in all secondary files and concatenate them into one array. Parameters ---------- scxdir : :class:`str` Directory produced by :func:`~secondary._check_files()`. scnd_mask : :class:`desiutil.bitmask.BitMask`, optional A mask corresponding to a set of secondary targets, e.g, could be ``from desitarget.targetmask import scnd_mask`` for the main survey mask. Returns ------- :class:`~numpy.ndarray` All secondary targets concatenated as one array with columns that correspond to `desitarget.secondary.outdatamodel`. """ # ADM the full directory name for the input data files. fulldir = os.path.join(scxdir, 'indata') scxall = [] # ADM loop through all of the scx bits. for name in scnd_mask.names(): log.debug('SCND target: {}'.format(name)) # ADM the full file path without the extension. fn = os.path.join(fulldir, scnd_mask[name].filename) log.debug(' path: {}'.format(fn)) # ADM if the relevant file is a .txt file, read it in. if os.path.exists(fn + '.txt'): scxin = np.loadtxt(fn + '.txt', usecols=[0, 1, 2, 3, 4, 5], dtype=indatamodel.dtype) # ADM otherwise it's a fits file, read it in. else: scxin = fitsio.read(fn + '.fits', columns=indatamodel.dtype.names) # ADM ensure this is a properly constructed numpy array. scxin = np.atleast_1d(scxin) # ADM the default is 2015.5 for the REF_EPOCH. ii = scxin["REF_EPOCH"] == 0 scxin["REF_EPOCH"][ii] = 2015.5 # ADM add the other output columns. dt = outdatamodel.dtype.descr + suppdatamodel.dtype.descr scxout = np.zeros(len(scxin), dtype=dt) for col in indatamodel.dtype.names: scxout[col] = scxin[col] scxout["SCND_TARGET"] = scnd_mask[name] scxout["SCND_TARGET_INIT"] = scnd_mask[name] scxout["SCND_ORDER"] = np.arange(len(scxin)) scxout["PRIORITY_INIT"] = scnd_mask[name].priorities['UNOBS'] scxout["NUMOBS_INIT"] = scnd_mask[name].numobs scxout["TARGETID"] = -1 scxout["OBSCONDITIONS"] = \ obsconditions.mask(scnd_mask[name].obsconditions) scxall.append(scxout) return np.concatenate(scxall)
def set_obsconditions(targets, scnd=False): """set the OBSCONDITIONS mask for each target bit. Parameters ---------- targets : :class:`~numpy.ndarray` An array of targets generated by, e.g., :mod:`~desitarget.cuts`. Must include at least (all of) the columns `DESI_TARGET`, `BGS_TARGET`, `MWS_TARGET` or corresponding cmx or SV columns. scnd : :class:`bool`, optional, defaults to ``False`` If ``True`` then make all of the comparisons on the `SCND_TARGET` column instead of `DESI_TARGET`, `BGS_TARGET` and `MWS_TARGET`. Returns ------- :class:`~numpy.ndarray` The OBSCONDITIONS bitmask for the passed targets. Notes ----- - the OBSCONDITIONS for each target bit is in the file, e.g. data/targetmask.yaml. It can be retrieved using, for example, `obsconditions.mask(desi_mask["ELG"].obsconditions)`. """ colnames, masks, _ = main_cmx_or_sv(targets, scnd=scnd) # ADM if we requested secondary targets, the needed information # ADM was returned as the last part of each array. if scnd: colnames, masks = colnames[-1:], masks[-1:] n = len(targets) from desitarget.mtl import mtldatamodel as mtldm obscon = np.zeros(n, dtype=mtldm["OBSCONDITIONS"].dtype) for mask, xxx_target in zip(masks, colnames): for name in mask.names(): # ADM which targets have this bit for this mask set? ii = (targets[xxx_target] & mask[name]) != 0 # ADM under what conditions can that bit be observed? if np.any(ii): obscon[ii] |= obsconditions.mask(mask[name].obsconditions) return obscon
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 initial_priority_numobs( targets, scnd=False, obscon="DARK|GRAY|BRIGHT|POOR|TWILIGHT12|TWILIGHT18"): """highest initial priority and numobs for an array of target bits. Parameters ---------- targets : :class:`~numpy.ndarray` An array of targets generated by, e.g., :mod:`~desitarget.cuts`. Must include at least (all of) the columns `DESI_TARGET`, `BGS_TARGET`, `MWS_TARGET` or corresponding cmx or SV columns. scnd : :class:`bool`, optional, defaults to ``False`` If ``True`` then make all of the comparisons on the `SCND_TARGET` column instead of `DESI_TARGET`, `BGS_TARGET` and `MWS_TARGET`. obscon : :class:`str`, optional, defaults to almost all OBSCONDITIONS A combination of strings that are in the desitarget bitmask yaml file (specifically in `desitarget.targetmask.obsconditions`). Returns ------- :class:`~numpy.ndarray` An array of integers corresponding to the highest initial priority for each target consistent with the constraints on observational conditions imposed by `obscon`. :class:`~numpy.ndarray` An array of integers corresponding to the largest number of observations for each target consistent with the constraints on observational conditions imposed by `obscon`. Notes ----- - the initial priority for each target bit is in the file, e.g., data/targetmask.yaml. It can be retrieved using, for example, `desi_mask["ELG"].priorities["UNOBS"]`. - the input obscon string can be converted to a bitmask using `desitarget.targetmask.obsconditions.mask(blat)`. """ colnames, masks, _ = main_cmx_or_sv(targets, scnd=scnd) # ADM if we requested secondary targets, the needed information # ADM was returned as the last part of each array. if scnd: colnames, masks = colnames[-1:], masks[-1:] # ADM set up the output arrays. outpriority = np.zeros(len(targets), dtype='int') # ADM remember that calibs have NUMOBS of -1. outnumobs = np.zeros(len(targets), dtype='int') - 1 # ADM convert the passed obscon string to bits. obsbits = obsconditions.mask(obscon) # ADM loop through the masks to establish all bitnames of interest. for colname, mask in zip(colnames, masks): # ADM first determine which bits actually have priorities. bitnames = [] for name in mask.names(): try: _ = mask[name].priorities["UNOBS"] # ADM also only consider bits with correct OBSCONDITIONS. obsforname = obsconditions.mask(mask[name].obsconditions) if (obsforname & obsbits) != 0: bitnames.append(name) except KeyError: pass # ADM loop through the relevant bits updating with the highest # ADM priority and the largest value of NUMOBS. for name in bitnames: # ADM indexes in the DESI/MWS/BGS_TARGET column that have this bit set istarget = (targets[colname] & mask[name]) != 0 # ADM for each index, determine where this bit is set and the priority # ADM for this bit is > than the currently stored priority. w = np.where((mask[name].priorities['UNOBS'] >= outpriority) & istarget)[0] # ADM where a larger priority trumps the stored priority, update the priority if len(w) > 0: outpriority[w] = mask[name].priorities['UNOBS'] # ADM for each index, determine where this bit is set and whether NUMOBS # ADM for this bit is > than the currently stored NUMOBS. w = np.where((mask[name].numobs >= outnumobs) & istarget)[0] # ADM where a larger NUMOBS trumps the stored NUMOBS, update NUMOBS. if len(w) > 0: outnumobs[w] = mask[name].numobs return outpriority, outnumobs
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_numobs_more(targets, zcat, obscon): """ Calculate target NUMOBS_MORE 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) `TARGETID` and `NUMOBS_INIT`. 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. Returns ------- :class:`~numpy.array` Integer array of number of additional observations (NUMOBS_MORE). Notes ----- - Will automatically detect if the passed targets are main survey, commissioning or SV and behave accordingly. - Most targets are updated to NUMOBS_MORE = NUMOBS_INIT-NUMOBS. Special cases include BGS targets which always get NUMOBS_MORE of 1 in bright time and QSO "tracer" targets which always get NUMOBS_MORE=0 in dark time. """ # 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] # ADM main case, just decrement by NUMOBS. numobs_more = np.maximum(0, targets['NUMOBS_INIT'] - zcat['NUMOBS']) if survey != 'cmx': # ADM BGS targets are observed during the BRIGHT survey, regardless # ADM of how often they've previously been observed. if (obsconditions.mask(obscon) & obsconditions.mask("BRIGHT")) != 0: ii = targets[desi_target] & desi_mask.BGS_ANY > 0 numobs_more[ii] = 1 if survey == 'main': # ADM If a DARK layer target is confirmed to have a good redshift # ADM at z < zcut it always needs just one total observation. # ADM (zcut is defined at the top of this module). if (obsconditions.mask(obscon) & obsconditions.mask("DARK")) != 0: ii = (zcat['ZWARN'] == 0) ii &= (zcat['Z'] < zcut) ii &= (zcat['NUMOBS'] > 0) numobs_more[ii] = 0 # ADM We will have to be more careful if some DARK layer targets # ADM other than QSOs request more than one observation. check = {bit: desi_mask[bit].numobs for bit in desi_mask.names() if 'DARK' in desi_mask[bit].obsconditions and 'QSO' not in bit and desi_mask[bit].numobs > 1} if len(check) > 1: msg = "logic not programmed for main survey dark-time targets other" msg += " than QSOs having NUMOBS_INIT > 1: {}".format(check) log.critical(msg) raise ValueError(msg) return numobs_more
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 nextFieldSelector(obsplan, mjd, conditions, tilesObserved, slew, previous_ra, previous_dec): """ Returns the first tile for which the current time falls inside its assigned LST window and is far enough from the Moon and planets. Args: obsplan: string, FITS file containing the afternoon plan mjd: float, current time conditions: dictionnary containing the weather info tilesObserved: list containing the tileID of all completed tiles slew: bool, True if a slew time needs to be taken into account previous_ra: float, ra of the previous observed tile (degrees) previous_dec: float, dec of the previous observed tile (degrees) Returns: target: dictionnary containing the following keys: 'tileID', 'RA', 'DEC', 'Program', 'Ebmv', 'maxLen', 'MoonFrac', 'MoonDist', 'MoonAlt', 'DESsn2', 'Status', 'Exposure', 'obsSN2', 'obsConds' overhead: float (seconds) """ hdulist = pyfits.open(obsplan) tiledata = hdulist[1].data moonfrac = hdulist[0].header['MOONFRAC'] tileID = tiledata['TILEID'] tmin = tiledata['LSTMIN'] tmax = tiledata['LSTMAX'] explen = tiledata['MAXEXPLEN'] / 240.0 ra = tiledata['RA'] dec = tiledata['DEC'] program = tiledata['PROGRAM'] obsconds = tiledata['OBSCONDITIONS'] lst = mjd2lst(mjd) dt = Time(mjd, format='mjd') found = False for i in range(len(tileID)): dra = np.abs(ra[i] - previous_ra) if dra > 180.0: dra = 360.0 - dra ddec = np.abs(dec[i] - previous_dec) overhead = setup_time(slew, dra, ddec) t1 = tmin[i] + overhead / 240.0 t2 = tmax[i] - explen[i] if (((t1 <= t2) and (lst > t1 and lst < t2)) or ((t2 < t1) and ((lst > t1 and t1 <= 360.0) or (lst >= 0.0 and lst < t2)))): if (avoidObject(dt.datetime, ra[i], dec[i]) and airMassCalculator(ra[i], dec[i], lst) < MAX_AIRMASS): moondist, moonalt, moonaz = moonLoc(dt.datetime, ra[i], dec[i]) if ((len(tilesObserved) > 0 and tileID[i] not in tilesObserved['TILEID']) or len(tilesObserved) == 0): if (((moonalt < 0.0 and (obsconds[i] & obsbits.mask('DARK')) != 0)) or (moonalt >= 0.0 and (((moonfrac < 0.2 or (moonalt * moonfrac < 12.0)) and moondist > MIN_MOON_SEP and (obsconds[i] & obsbits.mask('GRAY')) != 0) or ((obsconds[i] & obsbits.mask('BRIGHT')) != 0 and moondist > MIN_MOON_SEP_BGS)))): found = True break if found == True: tileID = tiledata['TILEID'][i] RA = ra[i] DEC = dec[i] Ebmv = tiledata['EBV_MED'][i] maxLen = tiledata['MAXEXPLEN'][i] DESsn2 = 100.0 # Some made-up number -> has to be the same as the reference in exposurecalc.py status = tiledata['STATUS'][i] exposure = -1.0 # Updated after observation obsSN2 = -1.0 # Idem target = { 'tileID': tileID, 'RA': RA, 'DEC': DEC, 'Program': program[i], 'Ebmv': Ebmv, 'maxLen': maxLen, 'MoonFrac': moonfrac, 'MoonDist': moondist, 'MoonAlt': moonalt, 'DESsn2': DESsn2, 'Status': status, 'Exposure': exposure, 'obsSN2': obsSN2, 'obsConds': obsconds[i] } else: target = None return target, overhead
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 = np.zeros(n, dtype='i4') for mask, xxx_target in zip(masks, colnames): for name in mask.names(): # - which targets have this bit for this mask set? ii = (targets[xxx_target] & mask[name]) != 0 # - under what conditions can that bit be observed? if np.any(ii): obscon[ii] |= obsconditions.mask(mask[name].obsconditions) # 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 afternoonPlan(self, day_stats, tiles_observed): """ All the file names are hard coded, so there is no need to have them as arguments to this function. Args: day_stats: dictionnary containing the following keys: 'MJDsunset', 'MJDsunrise', 'MJDetwi', 'MJDmtwi', 'MJDe13twi', 'MJDm13twi', 'MJDmoonrise', 'MJDmoonset', 'MoonFrac', 'dirName' tiles_observed: table with follwing columns: tileID, status Returns: string containg the filename for today's plan; it has the format obsplanYYYYMMDD.fits """ year = int( np.floor( (day_stats['MJDsunset'] - tiles_observed.meta['MJDBEGIN'])) / 365.25) + 1 # Adjust DARK time program tile priorities # From the DESI document 1767 (v3) "Baseline survey strategy": # In the northern galactic cap: # Year - Layer 1 tiles - Layer 2 tiles - Layer 3 tiles - Layer 4 tiles # 1 900 200 0 0 # 2 485 415 200 200 # 3 0 770 100 100 # 4 0 0 450 450 # 5 0 0 685 685 # In the southern galactic cap: # Year - Layer 1 tiles - Layer 2 tiles - Layer 3 tiles - Layer 4 tiles # 1 450 0 0 0 # 2 165 300 0 0 # 3 0 315 90 90 # 4 0 0 265 260 # 5 0 0 260 265 # Priorities shall be adjusted accordingly. # Priorities can only be set to 0, 1, 2 or 8, 9, 10 by # *human intervention*! # Update status nto = len(tiles_observed) for i in range(nto): j = np.where(self.tileID == tiles_observed['TILEID'][i]) self.status[j] = tiles_observed['STATUS'][i] planList0 = [] lst15evening = mjd2lst(day_stats['MJDetwi']) lst15morning = mjd2lst(day_stats['MJDmtwi']) lst13evening = mjd2lst(day_stats['MJDe13twi']) lst13morning = mjd2lst(day_stats['MJDe13twi']) # Dark and grey Pass 1, 2, 3, & 4 are numbered 0, 1, 2, 3. # BGS, pass 1, 2 & 3 are numbered 4, 5, 6. for i in range(len(self.tileID)): if (self.status[i] < 2): # Add this tile to the plan, first adjust its priority. if (((self.obsconds[i] & obsbits.mask('DARK|GRAY')) != 0) and (lst15evening < self.LSTmin[i] and self.LSTmax[i] < lst15morning)): if year == 1: if ((self.cap[i] == 'N' and (self.Pass[i] == 2 or self.Pass[i] == 3)) or (self.cap[i] == 'S' and (self.Pass[i] == 1 or self.Pass[i] == 2 or self.Pass[i] == 3))): self.priority[i] = 7 if (self.cap[i] == 'N' and self.Pass[i] == 0 and self.priority[i] > 3): self.priority[i] -= 1 if year == 2: if (self.cap[i] == 'S' and (self.Pass[i] == 2 or self.Pass[i] == 3)): self.priority[i] = 7 if year == 3: if self.Pass[i] == 0: self.priority[i] = 3 if self.Pass[i] == 1 and self.priority[i] > 3: self.priority[i] -= 1 if year >= 4: if self.Pass[i] <= 1: self.priority[i] = 3 elif (((self.obsconds[i] & obsbits.mask('BRIGHT')) != 0) and (lst13evening < self.LSTmin[i] and self.LSTmax[i] < lst13morning)): if year == 1: if (self.Pass[i] == 4 or self.Pass[i] == 5): self.priority[i] -= 1 if year == 2 or year == 3: if self.Pass[i] == 4: self.priority[i] = 3 if self.Pass[i] == 5: self.priority[i] -= 1 if year >= 4: if self.Pass[i] <= 5: self.priority[i] = 3 planList0.append( (self.tileID[i], self.RA[i], self.DEC[i], self.Ebmv[i], self.LSTmin[i], self.LSTmax[i], self.maxExpLen[i], self.priority[i], self.status[i], self.program[i], self.obsconds[i])) planList = sorted(planList0, key=itemgetter(7), reverse=False) cols = np.rec.array(planList, names=('TILEID', 'RA', 'DEC', 'EBV_MED', 'LSTMIN', 'LSTMAX', 'MAXEXPLEN', 'PRIORITY', 'STATUS', 'PROGRAM', 'OBSCONDITIONS'), formats=[ 'i4', 'f8', 'f8', 'f8', 'f4', 'f4', 'f4', 'i4', 'i4', 'a6', 'i2' ]) tbhdu = pyfits.BinTableHDU.from_columns(cols) prihdr = pyfits.Header() prihdr['MOONFRAC'] = day_stats['MoonFrac'] prihdu = pyfits.PrimaryHDU(header=prihdr) filename = 'obsplan' + day_stats['dirName'] + '.fits' thdulist = pyfits.HDUList([prihdu, tbhdu]) thdulist.writeto(filename, clobber=True) tilesTODO = len(planList) return tilesTODO, filename