Scheduler Overview

The scheduler in rubin_scheduler contains many classes which work in concert to provide flexible, configurable algorithms for choosing observations, taking into account the current weather conditions and observatory capabilities.

CoreScheduler

In both actual operations and in simulations, the CoreScheduler does the work of accepting telemetry of weather conditions and observatory state (update_conditions()), and determining the best choice for the next observation (or observations) (request_observation()) by polling its lists of Surveys . It also accepts updates of completed observations (add_observation()) and when desired, can flush its internal queue of awaiting observations (flush_queue()). The CoreScheduler is the primary interaction point when running the scheduler.

The CoreScheduler itself however, does not determine what are useful or desireable observations. That job belongs to the Surveys. While there could be only a single Survey in the CoreScheduler, typically there are many, each configured for different goals or methods of acquiring observations. The Surveys are held in the CoreScheduler.survey_lists and are organized into “tiers” – each tier contains a list of Surveys.

Each Survey object can check_feasibility() for a quick check on whether current conditions match its requirements and calc_reward_function() for the full calculation of the desirability of an observation under the current conditions, based on the survey’s configuration.

Whenever CoreScheduler.request_observation() is called, the CoreScheduler travels through each tier in survey_lists. Within each tier, the Survey which is both feasible and has the greatest reward value will be chosen to generate the next observation. If no Survey within a given tier is feasible, the next tier will be queried for feasibility; this will continue through the tiers until a Survey which is feasible is found. Thus Surveys in the first tier have priority over Surveys in later tiers.

Once a specific Survey is chosen to request an observation, the Survey.generate_observations() method will be called. This provides the specifics of the requested observation or observations. Many Surveys will request a series of observations to be executed in sequence.

A step through the workflow of the CoreScheduler at a given time to generate an observation request looks like:

        flowchart TD
    A([Start]) ==>B[[Update Conditions]]
    B ==> C[[Request Observation]]
    C ==> D([Consider Surveys in Tier])
    D --> E[Check Survey]
    D --> F[Check Survey]
    D --> G[Check Survey]
    E --> H{Is any Survey in Tier feasible?}
    F --> H
    G --> H
    H == Yes ==> J([Select Survey in Tier with highest reward])
    H == No ==> I([Go to Next tier]) ==> D
    J ==> K[Winning Survey Generates Observation]
    K ==> L([End])
    

After an observation is acquired by the observatory and CoreScheduler.add_observation() is called to register the visit, the observation is also passed to each individual Survey. Some Surveys will record this observation, while others may ignore it, depending on their configurations.

Surveys

The customization of survey strategy with rubin_scheduler happens in the Survey objects, as well as in how they are placed into the tiers in the CoreScheduler.survey_lists. There is a wide variety in how different Survey objects behave or can be configured. A Survey could be configured to observe pairs of visits at any point over the sky (such as the BlobSurvey) or it could be designed to simply follow a scripted list of RA/Dec/Filter visits at pre-specified time windows (such as a ScriptedSurvey).

Each Survey can check_feasibility(), which provides a quick check on whether the current conditions meet the Survey requirements as well as calc_reward_function(), which calculates the desirability of an observation under the current conditions. The calculation of the feasibility or reward for a given survey is governed entirely by how these functions are implemented within the specific Survey class.

A Survey is considered infeasible if check_feasibility() returns False. It is also infeasible if the maximum final reward value is -Infinity. The final reward value of a Survey is typically an array, but can be a scalar in the case of Surveys defined only at a single point (such as a FieldSurvey).

If chosen to generate an observation request, the Survey will return either a single requested observation or a series of requested observations, using generate_observations(). The specific choice of these observations (such as whether this is a single visit or a series of visits) is determined by the specific Survey’s implementation of this method. The specifics of these requested observations include details added by its Detailers which add requested rotation angle or dithering positions, if applicable.

Calculating Feasibility and Rewards

The calculation of feasibility or reward is entirely dependant on how these methods are implemented in the Survey class, and this can vary depending on the intended use of the Survey.

A ScriptedSurvey or one derived from this class may just have a single constant reward value, but determine feasibility depending on whether any of its list of desired observations (which contain possible time windows) overlap the current time.

Most Surveys do contain a list of BasisFunctions which combine to calculate the overall reward for that Survey under the current conditions. If so, each BasisFunction will also have an associated weight; the total reward for the Survey is simply the weighted sum of the BasisFunction values. These Surveys will generally also have their own Features, which track relevant information for that Survey over time.

The BasisFunctions calculate values to contribute to the reward that consider some aspect of the current conditions: a simple example is the SlewtimeBasisFunction which calculates its value based on the slewtime from the current telescope position to the desired location on the sky. The Features track relevant information for that Survey, such as how many observations have already been obtained or when the last observation at a given pointing was acquired, and can be used by the BasisFunctions for that Survey.

Typically Survey classes which are intended to be used for large areas of sky contain BasisFunctions and inherit from the BaseMarkovSurvey. Most of the observations in the current baseline come from a Survey class in this category, the BlobSurvey.

Basis Functions

For the Surveys which use BasisFunctions, the BasisFunctions are where the list of “pros” and “cons” regarding obtaining observations under the current conditions are calculated. The final reward for these Surveys is the weighted sum of its basis function values.

There are many different BasisFunctions available, and each can be configured in different ways to generate different effects. Because they can be configured in different ways, including keeping track of different observations, BasisFunctions are not shared between Surveys. Some examples of common BasisFunctions include:

        classDiagram
    BasisFunction <|-- Slewtime
    BasisFunction <|-- M5Diff
    BasisFunction <|-- Footprint
    BasisFunction <|-- MoonAvoidance
    BasisFunction <|-- FilterLoaded
    BasisFunction : + Features
    BasisFunction : check_feasibility()
    BasisFunction : calc_value()
    BasisFunction : add_observation()
    BasisFunction : label()
    class Slewtime{
      + Short Slews
    }
    class M5Diff{
      + Better depth
    }
    class Footprint{
      + Uniform coverage
    }
    class MoonAvoidance{
      + Avoid the Moon
    }
    class FilterLoaded{
      + Filter Available
    }
    

The value of a given BasisFunction can be either a scalar or a map of the sky (as healpix arrays). Generally, the value returned depends on the type of BasisFunction, although this can also be modified by the properties of the Survey (FieldSurveys, for example, only consider the BasisFunction value at the location of their target).

Most commonly, BasisFunctions return a map if they are considering a property that varies across the sky, such as M5DiffBasisFunction which tracks current skybrightness compared to the best possible skybrightness in the specified filter. If the BasisFunction returns a value of -Infinity, this will be propagated through the weighted sum of BasisFunction values to the Survey reward value. This is easiest to understand with avoidance zone masks like the MoonAvoidanceBasisFunction or the AvoidDirectWindBasisFunction which return -Infinity for the parts of the sky which should be inaccessible for the telescope: the -Infinity areas will be -Infinity in the Survey reward, and the Survey will not request observations in these parts of the sky. If multiple BasisFunctions in a Survey have regions of -Infinity, it is possible for these regions to overlap in a way that makes the final reward -Infinity at all points in the sky; this will make the Survey infeasible under those conditions.

Sometimes a BasisFunction returns a scalar value, such as for the FilterLoadedBasisFunction. This BasisFunction tracks whether the filter for a desired observation is available in the camera filter wheel. If the filter is available, it returns 0 which doesn’t modify the overall Survey reward. If the filter is not available, it returns -Infinity, which makes the Survey infeasible under those conditions.