Source code for panoptes.pocs.scheduler.dispatch

"""Dispatch-style scheduler implementation.

Implements a simple greedy/dispatch scheduler that evaluates all configured
observations against the active constraints and selects the best candidate by
weighted score. Favors the current observation when still viable to reduce
excessive switching.
"""

from panoptes.utils.time import current_time
from panoptes.utils.utils import listify

from panoptes.pocs.scheduler.scheduler import BaseScheduler


[docs] class Scheduler(BaseScheduler): """Greedy scheduler that ranks observations by constraint scores.""" def __init__(self, *args, **kwargs): """Initialize the Scheduler, delegating to BaseScheduler.""" BaseScheduler.__init__(self, *args, **kwargs)
[docs] def get_observation(self, time=None, show_all=False, constraints=None, read_file=False): """Get a valid observation. Args: time (astropy.time.Time, optional): Time at which scheduler applies, defaults to time called constraints (list of panoptes.pocs.scheduler.constraint.Constraint, optional): The constraints to check. If `None` (the default), use the `scheduler.constraints`. show_all (bool, optional): Return all valid observations along with merit value, defaults to False to only get top value constraints (list of panoptes.pocs.scheduler.constraint.Constraint, optional): The constraints to check. If `None` (the default), use the `scheduler.constraints` read_file (bool, optional): If the fields file should be reread before scheduling occurs, defaults to False. Returns: tuple or list: A tuple (or list of tuples) with name and score of ranked observations """ if read_file: self.logger.debug("Rereading fields file") self.read_field_list() if time is None: time = current_time() valid_obs = {obs: 0.0 for obs in self.observations} best_obs = [] self.set_common_properties(time) self.logger.info("Applying constraints to observations:") for obs_name, observation in self.observations.items(): self.logger.info(f"{obs_name}") # Get the global constraints. all_constraints = constraints or self.constraints.copy() # Add the observation specific constraints. if observation.constraints is not None: all_constraints += observation.constraints for constraint in listify(all_constraints): if obs_name in valid_obs: # Add a special case where we skip the Moon Avoidance constraint if # the observation name is "Moon". if constraint.name == "MoonAvoidance" and obs_name.lower() == "moon": self.logger.info(f"Skipping Moon Avoidance constraint for {obs_name}") continue veto, score = constraint.get_score( time, self.observer, observation, **self.common_properties ) if veto: self.logger.info(f"\tVetoed by {constraint}") del valid_obs[obs_name] continue valid_obs[obs_name] += score self.logger.info( f"\t{str(constraint):30s}Constraint score: {score:10.02f}\t" f"Total score: {valid_obs[obs_name]:10.02f}" ) if len(valid_obs) > 0: self.logger.info("Multiplying final scores by observation priority") for obs_name, score in valid_obs.items(): priority = self.observations[obs_name].priority new_score = score * priority self.logger.info( f"\t{obs_name:30s}Total score: {score:10.02f}\t" f"Priority: {priority:10.3f} = {new_score:10.02f}" ) valid_obs[obs_name] = new_score # Sort the list by highest score (reverse puts in correct order) best_obs = sorted(valid_obs.items(), key=lambda x: x[1])[::-1] top_obs_name, top_obs_score = best_obs[0] self.logger.info(f"Best observation: {top_obs_name}\tScore: {top_obs_score:.02f}") # Check new best against current_observation if self.current_observation is not None and top_obs_name != self.current_observation.name: self.logger.info(f"Checking if {self.current_observation} is still valid") # Favor the current observation if still available end_of_next_set = time + self.current_observation.set_duration if self.observation_available(self.current_observation, end_of_next_set): # If current is better or equal to top, use it self.logger.debug(f"{self.current_observation.merit=}") self.logger.debug(f"{top_obs_score=}") if self.current_observation.merit >= top_obs_score: best_obs.insert(0, (self.current_observation, self.current_observation.merit)) # Set the current self.current_observation = self.observations[top_obs_name] self.current_observation.merit = top_obs_score else: if self.current_observation is not None: # Favor the current observation if still available end_of_next_set = time + self.current_observation.set_duration if end_of_next_set < self.common_properties["end_of_night"] and self.observation_available( self.current_observation, end_of_next_set ): self.logger.info(f"Reusing {self.current_observation}") best_obs = [(self.current_observation.name, self.current_observation.merit)] else: self.logger.warning("No valid observations found") self.current_observation = None if not show_all and len(best_obs) > 0: best_obs = best_obs[0] return best_obs