diff --git a/py/desisurvey/ephem.py b/py/desisurvey/ephem.py index a0a1a9d..0b0a165 100644 --- a/py/desisurvey/ephem.py +++ b/py/desisurvey/ephem.py @@ -23,12 +23,10 @@ import desisurvey.tiles -# Date range 2019-2025 for tabulated ephemerides. -# This range is chosen large enough to cover commissioning, -# survey validation and the 5-year main survey, so should -# not normally need to be changed, except for testing. +# Date range 2019-2029 for tabulated ephemerides. +# This range is intended to cover out through the DESI extension. START_DATE = datetime.date(2019, 1, 1) -STOP_DATE = datetime.date(2027, 12, 31) +STOP_DATE = datetime.date(2029, 12, 31) _ephem = None diff --git a/py/desisurvey/plan.py b/py/desisurvey/plan.py index ba60250..18234ac 100644 --- a/py/desisurvey/plan.py +++ b/py/desisurvey/plan.py @@ -9,6 +9,8 @@ import astropy.table import astropy.io.fits +from astropy.coordinates import SkyCoord +from astropy import units as u import desiutil.log @@ -512,6 +514,7 @@ def afternoon_plan(self, night): self.add_pending_tile(tileid) if self.tiles_lowpass: self.prefer_low_passnum() + self.require_program_dependencies() zeropriority = ((self.tile_status != 'unobs') & (self.tile_available == 0)) self.tile_priority[zeropriority] = 0 @@ -559,3 +562,24 @@ def prefer_low_passnum(self): # unobserved, overlapping tiles with lower pass numbers. mfree[idx] = 0 self.tile_available[~mfree] = 0 + + def require_program_dependencies(self): + depends_on = self.tiles.program_dependencies() + config = desisurvey.config.Configuration() + tile_diameter = 2 * config.tile_radius() + + for program in depends_on: + mprog = self.tiles.program_mask[program] + mtodo = ((self.tile_status != 'done') & + (self.tiles.in_desi != 0)) + otherprog = depends_on[program] + # tiles we still intend to do in the underlying program + motherprog = self.tiles.program_mask[otherprog] & mtodo + cprog = SkyCoord(self.tiles.tileRA[mprog]*u.deg, + self.tiles.tileDEC[mprog]*u.deg) + cotherprog = SkyCoord(self.tiles.tileRA[motherprog]*u.deg, + self.tiles.tileDEC[motherprog]*u.deg) + idxo, idxp, _, _ = cprog.search_around_sky(cotherprog, tile_diameter) + mblocked = np.zeros(len(mprog), dtype='bool') + mblocked[np.flatnonzero(mprog)[idxp]] = True + self.tile_available[mblocked] = 0 diff --git a/py/desisurvey/plots.py b/py/desisurvey/plots.py index 17dd6f5..061ae67 100644 --- a/py/desisurvey/plots.py +++ b/py/desisurvey/plots.py @@ -19,7 +19,8 @@ # Color associated with each program in the functions below. program_color = {'DARK': 'black', 'GRAY': 'gray', 'BRIGHT': 'orange', - 'BACKUP': 'green'} + 'BACKUP': 'green', 'DARK1B': 'purple', + 'BRIGHT1B': 'red'} def plot_sky_passes(ra, dec, passnum, z, clip_lo=None, clip_hi=None, diff --git a/py/desisurvey/scheduler.py b/py/desisurvey/scheduler.py index ab14fa3..d1d73b7 100644 --- a/py/desisurvey/scheduler.py +++ b/py/desisurvey/scheduler.py @@ -213,10 +213,10 @@ def conditions_to_program(self, seeing, transparency, skylevel, return 'BRIGHT' return 'BACKUP' - def select_program(self, mjd_now, ETC, verbose=False, + def current_conditions(self, mjd_now, ETC, verbose=False, seeing=None, transparency=None, skylevel=None, airmass=None, speed=None): - """Select program to observe now. + """Return current conditions, based on ephemerides or speed. """ if mjd_now < self.night_changes[0]: if verbose: @@ -248,7 +248,9 @@ def select_program(self, mjd_now, ETC, verbose=False, else: mjd_program_end = self.night_changes[-1] return program, mjd_program_end + # select program based on ephemerides, not conditions. + # we have not actually used this in DESI. idx = 0 while ((idx + 1 < len(self.night_changes)) and (mjd_now >= self.night_changes[idx + 1])): @@ -348,14 +350,15 @@ def next_tile(self, mjd_now, ETC, seeing, transp, skylevel, HA_sigma=15., self.tile_sel = np.ones(self.tiles.ntiles, dtype=bool) if program is None: # Which program are we in? - program, mjd_program_end = self.select_program( + conditions, mjd_program_end = self.current_conditions( mjd_now, ETC, verbose=verbose, seeing=seeing, skylevel=skylevel, transparency=transp, speed=speed) - self.tile_sel &= self.tiles.allowed_in_conditions(program) + self.tile_sel &= self.tiles.allowed_in_conditions(conditions) if verbose: self.log.info( 'Selecting a tile observable in {} conditions.'.format( program)) + program = conditions else: self.tile_sel &= self.tiles.program_mask[program] mjd_program_end = self.night_changes[-1] # end of night? diff --git a/py/desisurvey/scripts/surveyinit.py b/py/desisurvey/scripts/surveyinit.py index 9139348..3d7ebfe 100644 --- a/py/desisurvey/scripts/surveyinit.py +++ b/py/desisurvey/scripts/surveyinit.py @@ -114,6 +114,9 @@ def parse(options=None): parser.add_argument( '--start', default=None, type=str, help='Use this start date instead of config.') + parser.add_argument( + '--stop', default=None, type=str, + help='Use this stop date instead of config.') if options is None: args = parser.parse_args() @@ -163,7 +166,10 @@ def calculate_initial_plan(args): start = config.first_day() else: start = desisurvey.utils.get_date(args.start) - stop = config.last_day() + if args.stop is None: + stop = config.last_day() + else: + stop = desisurvey.utils.get_date(args.stop) assert start >= first and stop <= last hdr['START'] = start.isoformat() hdr['STOP'] = stop.isoformat() diff --git a/py/desisurvey/scripts/surveymovie.py b/py/desisurvey/scripts/surveymovie.py index df12787..d236a31 100644 --- a/py/desisurvey/scripts/surveymovie.py +++ b/py/desisurvey/scripts/surveymovie.py @@ -145,7 +145,7 @@ def __init__(self, exposures_path, start, stop, label, show_scores): self.tileid = self.tiles.tileID self.prognames = [p for p in self.tiles.programs] # if DARK and BRIGHT are names, put them first - for pname in ['BRIGHT', 'DARK']: + for pname in ['BRIGHT1B', 'DARK1B', 'BRIGHT', 'DARK']: if pname in self.prognames: self.prognames.remove(pname) self.prognames = [pname] + self.prognames @@ -200,7 +200,7 @@ def init_figure(self, nightly, width=1920, height=1080, dpi=32): self.figure = plt.figure( frameon=False,figsize=(width / self.dpi, height / self.dpi), dpi=self.dpi) - grid = matplotlib.gridspec.GridSpec(2, 2) + grid = matplotlib.gridspec.GridSpec(3, 2) grid.update(left=0, right=1, bottom=0, top=0.97, hspace=0, wspace=0) axes = [] self.labels = [] @@ -222,7 +222,7 @@ def init_figure(self, nightly, width=1920, height=1080, dpi=32): self.nowcolor = np.array([0., 0.7, 0., 1.]) pcolors = desisurvey.plots.program_color progidx = 0 - for row in range(2): + for row in range(3): for col in range(2): # Create the axes for this program. ax = plt.subplot(grid[row, col], facecolor=bgcolor) @@ -230,7 +230,7 @@ def init_figure(self, nightly, width=1920, height=1080, dpi=32): ax.set_yticks([]) axes.append(ax) # Bottom-right corner is reserved for integrated progress plots. - if row == 1 and col == 1: + if row == 2 and col == 1: ax.set_xlim(0, self.survey_weeks) ax.set_ylim(0, 1) ax.plot([0, self.survey_weeks], [0., 1.], 'w-') @@ -249,7 +249,7 @@ def init_figure(self, nightly, width=1920, height=1080, dpi=32): # e.g., for no-gray mode, where we have only two programs. continue ax.set_xlim(-55, 293) - ax.set_ylim(-20, 77) + ax.set_ylim(-30, 77) # Draw label for this plot. pname = self.prognames[progidx] pc = pcolors[pname] @@ -553,7 +553,8 @@ def update(iframe): animation = matplotlib.animation.FuncAnimation( animator.figure, update, init_func=init, blit=True, frames=nframes) writer = matplotlib.animation.writers['ffmpeg']( - bitrate=2400, fps=args.fps, metadata=dict(artist='surveymovie')) + bitrate=2400, fps=args.fps, codec='vp9', + metadata=dict(artist='surveymovie')) save_name = args.save + '.mp4' animation.save(save_name, writer=writer, dpi=animator.dpi) log.info('Saved {0}.'.format(save_name)) diff --git a/py/desisurvey/tiles.py b/py/desisurvey/tiles.py index d101f30..6800f5e 100644 --- a/py/desisurvey/tiles.py +++ b/py/desisurvey/tiles.py @@ -242,6 +242,17 @@ def allowed_in_conditions(self, cond): res = res | (self.tileobsconditions == 'BRIGHT') return res + def program_dependencies(self): + depends_on = dict() + config = desisurvey.config.Configuration() + for program in config.programs.keys: + prognode = getattr(config.programs, program) + dependencies = getattr(prognode, 'depends_on', None) + if dependencies is not None: + depends_on[program] = dependencies() + return depends_on + + @property def overlapping(self): """Dictionary of tile overlap matrices. @@ -350,11 +361,6 @@ def _calculate_neighbors(self): # ignore self matches continue self._neighbors[rownum[ind1]].append(rownum[ind2]) - - for passnum in np.unique(self.tilepass[mprogram]): - m = (mprogram & (self.tilepass == passnum) & - (self.in_desi != 0)) - rownum = np.flatnonzero(m) # self._neighbors: list of lists, giving tiles neighboring each # tile