def __init__(self, survey_start_time=DEFAULT_START_TIME): """Initialize slew time calculator. """ self.start_time = survey_start_time self.observatory = ObservatoryModel() self.observatory.configure_from_module() self.observatory.params.rotator_followsky = True
def __init__(self, obs_site_config): """Initialize the class. Parameters ---------- obs_site_config : :class:`.ObservingSite` The instance of the observing site configuration. """ self.log = logging.getLogger("observatory.MainObservatory") observatory_location = ObservatoryLocation() observatory_location.configure({"obs_site": obs_site_config.toDict()}) self.config = None self.model = ObservatoryModel(observatory_location, LoggingLevel.WORDY.value) self.date_profile = DateProfile(0, observatory_location) self.param_dict = {} self.slew_count = 0 self.observations_made = 0 self.exposures_made = 0 self.target_exposure_list = None self.observation_exposure_list = None self.slew_history = None self.slew_final_state = None self.slew_initial_state = None self.slew_activities_list = None self.slew_activities_done = 0 self.slew_maxspeeds = None self.variational_model = None
def test_configure(self): temp_model = ObservatoryModel(self.location) temp_model.configure_from_module() self.assertEqual(temp_model.location.longitude_rad, math.radians(-70.7494)) self.assertEqual(temp_model.location.longitude, -70.7494) self.assertEqual(temp_model.current_state.telalt_rad, math.radians(86.5))
def setUp(self) -> None: self.observatory_model = ObservatoryModel() self.observatory_model.configure_from_module() start_time = Time(59853.983, format="mjd", scale="tai") self.observatory_model.update_state(start_time.unix) return super().setUp()
class ObservatoryStateMock: def __init__(self): self.ptg = salobj.Controller("MTPtg") self.location = ObservatoryLocation() self.location.for_lsst() self.model = ObservatoryModel(self.location) self.model.configure_from_module() self.telemetry_sleep_time = 0.02 self.run_current_target_status_loop = True self.current_target_status_task = None self._started = False self.start_task = asyncio.create_task(self.start()) async def current_target_status(self): while self.run_current_target_status_loop: time = utils.current_tai() self.model.update_state(time) await self.ptg.tel_currentTargetStatus.set_write( timestamp=time, demandAz=self.model.current_state.telaz, demandEl=self.model.current_state.telalt, demandRot=self.model.current_state.telrot, demandRa=self.model.current_state.ra, demandDec=self.model.current_state.dec, parAngle=self.model.current_state.pa, ) await asyncio.sleep(self.telemetry_sleep_time) async def start(self): if not self._started: self._started = True await self.ptg.start_task self.run_current_target_status_loop = True self.current_target_status_task = asyncio.create_task( self.current_target_status()) async def close(self): self.run_current_target_status_loop = False try: await asyncio.wait_for(self.current_target_status_task, timeout=self.telemetry_sleep_time * 5) finally: await self.ptg.close() async def __aenter__(self): await self.start_task return self async def __aexit__(self, type, value, traceback): await self.close()
def __init__(self): self.log = logging.getLogger("schedulerDriver") self.params = DriverParameters() self.location = ObservatoryLocation() self.observatoryModel = ObservatoryModel(self.location, WORDY) self.observatoryModel2 = ObservatoryModel(self.location, WORDY) self.observatoryState = ObservatoryState() self.sky = AstronomicalSkyModel(self.location) self.db = FieldsDatabase() self.build_fields_dict() self.propid_counter = 0 self.science_proposal_list = [] self.start_time = 0.0 self.time = 0.0 self.targetid = 0 self.survey_started = False self.isnight = False self.sunset_timestamp = 0.0 self.sunrise_timestamp = 0.0 self.survey_duration_DAYS = 0.0 self.survey_duration_SECS = self.survey_duration_DAYS * 24 * 60 * 60.0 self.darktime = False self.mounted_filter = "" self.unmounted_filter = "" self.midnight_moonphase = 0.0 self.nulltarget = Target() self.nulltarget.targetid = -1 self.nulltarget.num_exp = 1 self.nulltarget.exp_times = [0.0] self.nulltarget.num_props = 1 self.nulltarget.propid_list = [0] self.nulltarget.need_list = [0.0] self.nulltarget.bonus_list = [0.0] self.nulltarget.value_list = [0.0] self.nulltarget.propboost_list = [1.0] self.last_winner_target = self.nulltarget.get_copy() self.deep_drilling_target = None self.need_filter_swap = False self.filter_to_unmount = "" self.filter_to_mount = "" self.cloud = 0.0 self.seeing = 0.0
class SlewTimeSource(object): """Callable object to calculate slew times.""" pre_position = ObservatoryPosition() post_position = ObservatoryPosition() def __init__(self, survey_start_time=DEFAULT_START_TIME): """Initialize slew time calculator. """ self.start_time = survey_start_time self.observatory = ObservatoryModel() self.observatory.configure_from_module() self.observatory.params.rotator_followsky = True def __call__(self, seconds_into_survey, pre_alt, pre_az, pre_band, post_alt, post_az, post_band): """Calculate the slew time. Args: - seconds_into_survey :: the time into the survey (in seconds) - pre_alt :: the alt of the starting pointing, in degrees - pre_az :: the az of the starting pointing, in degrees - pre_band :: the filter at the starting pointing - post_alt :: the alt of the ending pointing, in degrees - post_az :: the az of the ending pointing, in degrees - post_band :: the filter at the starting pointing """ current_time = self.start_time + TimeDelta(seconds_into_survey, format='sec') self.pre_position.time = current_time self.pre_position.tracking = True self.pre_position.alt_rad = np.radians(pre_alt) self.pre_position.az_rad = np.radians(pre_az) self.pre_position.rot_rad = self.observatory.current_state.rot_rad self.pre_position.filter = pre_band pre_state = self.observatory.get_closest_state(self.pre_position) self.post_position.time = current_time self.post_position.tracking = True self.post_position.alt_rad = np.radians(post_alt) self.post_position.az_rad = np.radians(post_az) self.post_position.rot_rad = self.observatory.current_state.rot_rad self.post_position.filter = post_band post_state = self.observatory.get_closest_state(self.post_position) slew_time = self.observatory.get_slew_delay_for_state( pre_state, post_state, False) return slew_time
def __init__(self): self.ptg = salobj.Controller("MTPtg") self.location = ObservatoryLocation() self.location.for_lsst() self.model = ObservatoryModel(self.location) self.model.configure_from_module() self.telemetry_sleep_time = 0.02 self.run_current_target_status_loop = True self.current_target_status_task = None self._started = False self.start_task = asyncio.create_task(self.start())
def __init__(self): self.log = logging.getLogger("schedulerDriver") self.params = DriverParameters() self.location = ObservatoryLocation() self.observatoryModel = ObservatoryModel(self.location, WORDY) self.observatoryModel2 = ObservatoryModel(self.location, WORDY) self.observatoryState = ObservatoryState() self.sky = AstronomicalSkyModel(self.location) self.propid_counter = 0 self.night = 0 self.start_time = 0.0 self.time = 0.0 self.targetid = 0 self.survey_started = False self.isnight = False self.sunset_timestamp = 0.0 self.sunrise_timestamp = 0.0 self.survey_duration_DAYS = 0.0 self.survey_duration_SECS = self.survey_duration_DAYS * 24 * 60 * 60.0 self.darktime = False self.mounted_filter = "" self.unmounted_filter = "" self.midnight_moonphase = 0.0 self.nulltarget = Target() self.nulltarget.targetid = -1 self.nulltarget.num_exp = 1 self.nulltarget.exp_times = [0.0] self.nulltarget.num_props = 1 self.nulltarget.propid_list = [0] self.nulltarget.need_list = [0.0] self.nulltarget.bonus_list = [0.0] self.nulltarget.value_list = [0.0] self.nulltarget.propboost_list = [1.0] self.last_winner_target = self.nulltarget.get_copy() self.deep_drilling_target = None self.need_filter_swap = False self.filter_to_unmount = "" self.filter_to_mount = "" self.cloud = 0.0 self.seeing = 0.0
def test_init(self): temp_model = ObservatoryModel(self.location) self.assertIsNotNone(temp_model.log) self.assertAlmostEqual(temp_model.location.longitude_rad, -1.23480, delta=1e6) self.assertEqual(temp_model.current_state.telalt_rad, 1.5)
def setUp(self): # Need to set current time to something the sky brightness files # available for testing have available (MJD: 59853.-59856.). self.start_time = Time(59853.983, format="mjd", scale="tai") # Step in time when there is no target (in seconds). self.no_target_time_step = 120.0 self.raw_telemetry = dict() self.raw_telemetry["timeHandler"] = None self.raw_telemetry["scheduled_targets"] = [] self.raw_telemetry["observing_queue"] = [] self.raw_telemetry["observatoryState"] = None self.raw_telemetry["bulkCloud"] = 0.0 self.raw_telemetry["seeing"] = 1.19 self.models = dict() self.models["location"] = ObservatoryLocation() self.models["location"].for_lsst() self.models["observatory_model"] = ObservatoryModel(self.models["location"]) self.models["observatory_model"].configure_from_module() self.models["observatory_model"].update_state(self.start_time.unix) self.models["observatory_state"] = ObservatoryState() self.models["observatory_state"].set( self.models["observatory_model"].current_state ) self.models["sky"] = AstronomicalSkyModel(self.models["location"]) self.models["seeing"] = SeeingModel() self.models["cloud"] = CloudModel() self.models["downtime"] = DowntimeModel() self.driver = FeatureScheduler( models=self.models, raw_telemetry=self.raw_telemetry ) self.config = types.SimpleNamespace( driver_configuration=dict( scheduler_config="", force=True, default_observing_script_name="standard_visit.py", default_observing_script_is_standard=True, stop_tracking_observing_script_name="stop_tracking.py", stop_tracking_observing_script_is_standard=True, ) ) self.files_to_delete = []
def setUp(self): self.raw_telemetry = dict() self.raw_telemetry["timeHandler"] = None self.raw_telemetry["scheduled_targets"] = [] self.raw_telemetry["observing_queue"] = [] self.raw_telemetry["observatoryState"] = None self.raw_telemetry["bulkCloud"] = 0.0 self.raw_telemetry["seeing"] = 1.0 self.models = dict() self.models["location"] = ObservatoryLocation() self.models["location"].for_lsst() self.models["observatory_model"] = ObservatoryModel(self.models["location"]) self.models["observatory_model"].configure_from_module() self.models["observatory_model"].update_state(current_tai()) self.models["observatory_state"] = ObservatoryState() self.models["observatory_state"].set( self.models["observatory_model"].current_state ) self.models["sky"] = AstronomicalSkyModel(self.models["location"]) self.models["seeing"] = SeeingModel() self.models["cloud"] = CloudModel() self.models["downtime"] = DowntimeModel() self.driver = SequentialScheduler( models=self.models, raw_telemetry=self.raw_telemetry, ) self.config = types.SimpleNamespace( driver_configuration=dict( observing_list=pathlib.Path(__file__) .parents[1] .joinpath("tests", "data", "test_observing_list.yaml"), general_propos=["Test"], default_observing_script_name="standard_visit.py", default_observing_script_is_standard=True, stop_tracking_observing_script_name="stop_tracking.py", stop_tracking_observing_script_is_standard=True, ) ) self.files_to_delete = []
def setUp(self): logging.getLogger().setLevel(logging.WARN) conf_path = conf_file_path(__name__, "conf") self.driver = Driver() driver_conf_file = os.path.join(conf_path, "scheduler", "driver.conf") survey_conf_file = os.path.join(conf_path, "survey", "test_survey.conf") driver_confdict = read_conf_file(driver_conf_file) obs_site_confdict = ObservatoryLocation.get_configure_dict() obs_model_confdict = ObservatoryModel.get_configure_dict() self.driver.configure(driver_confdict) self.driver.configure_location(obs_site_confdict) self.driver.configure_observatory(obs_model_confdict) self.driver.configure_survey(survey_conf_file)
def setUp(self): logging.getLogger().setLevel(logging.WARN) conf_path = conf_file_path(__name__, "conf") self.driver = Driver() driver_conf_file = os.path.join(conf_path, "scheduler", "driver.conf") survey_conf_file = os.path.join(conf_path, "survey", "test_survey.conf") driver_confdict = read_conf_file(driver_conf_file) obs_site_confdict = ObservatoryLocation.get_configure_dict() obs_model_confdict = ObservatoryModel.get_configure_dict() self.driver.configure(driver_confdict) self.driver.configure_location(obs_site_confdict) self.driver.configure_observatory(obs_model_confdict) self.driver.configure_survey(survey_conf_file)
def test_get_configure_dict(self): cd = ObservatoryModel.get_configure_dict() self.assertEqual(len(cd), 7) self.assertEqual(len(cd["telescope"]), 11) self.assertEqual(len(cd["camera"]), 10)
def setUpClass(cls): cls.location = ObservatoryLocation() cls.location.for_lsst() cls.model = ObservatoryModel(cls.location) cls.model.configure_from_module()
class MainObservatory(object): """Class for the Main Observatory. This class is designed to look like the real observatory. In the default case, the observatory configuration is the main LSST obesrvatory. It uses the observatory model from the LSST Scheduler as its base information. There is an option to add variations onto the parameters and values that the model calculates to simulate real world behaviors. Attributes ---------- log : logging.Logger The logging instance. model : lsst.ts.scheduler.observatory_model.ObservatoryModel The instance of the Observatory model from the LSST Scheduler. param_dict : dict The configuration parameters for the Observatory model. """ def __init__(self, obs_site_config): """Initialize the class. Parameters ---------- obs_site_config : :class:`.ObservingSite` The instance of the observing site configuration. """ self.log = logging.getLogger("observatory.MainObservatory") observatory_location = ObservatoryLocation() observatory_location.configure({"obs_site": obs_site_config.toDict()}) self.config = None self.model = ObservatoryModel(observatory_location, LoggingLevel.WORDY.value) self.date_profile = DateProfile(0, observatory_location) self.param_dict = {} self.slew_count = 0 self.observations_made = 0 self.exposures_made = 0 self.target_exposure_list = None self.observation_exposure_list = None self.slew_history = None self.slew_final_state = None self.slew_initial_state = None self.slew_activities_list = None self.slew_activities_done = 0 self.slew_maxspeeds = None self.variational_model = None def __getattr__(self, name): """Find attributes in lsst.ts.scheduler.observator_model.ObservatorModel as well as MainObservatory. """ try: return getattr(self.model, name) except AttributeError: cclass_name = self.__class__.__name__ aclass_name = self.model.__class__.__name__ raise AttributeError( "'{}' and '{}' objects have no attribute '{}'".format( cclass_name, aclass_name, name)) def calculate_visit_time(self, target, th): """Calculate the visit time from the target and camera information. This function calculates the visit time from the current camera configuration parameters and the list of effective exposure times from the target. The visit time is calculated as: shutter_time = 2.0 * (0.5 * camera shutter time) visit_time = sum over number of exposures (shutter_time + effective exposure time) visit_time += (number of exposures - 1) * camera readout time Parameters ---------- target : SALPY_scheduler.targetC The Scheduler topic instance holding the target information. Returns ------- (float, str) The calculated visit time and a unit string (default it seconds). """ self.target_exposure_list = [] self.observation_exposure_list = [] camera_config = self.config.camera shutter_time = 2.0 * (0.5 * camera_config.shutter_time) visit_time = 0.0 for i in range(target.num_exposures): self.exposures_made += 1 effective_exposure_time = target.exposure_times[i] self.target_exposure_list.append( TargetExposure(self.exposures_made, i + 1, effective_exposure_time, target.targetId)) exposure_start_time = th.future_timestamp(visit_time, "seconds") visit_time += (shutter_time + effective_exposure_time) self.observation_exposure_list.append( ObsExposure(self.exposures_made, i + 1, effective_exposure_time, exposure_start_time, self.observations_made)) if i < (target.num_exposures - 1): visit_time += camera_config.readout_time return (visit_time, "seconds") def configure(self, obs_config): """Configure the ObservatoryModel parameters. Parameters ---------- obs_config : :class:`.Observatory` The instance of the observatory configuration. """ self.config = obs_config self.param_dict.update(self.config.toDict()) self.model.configure(self.param_dict) self.variational_model = VariationalModel(obs_config) def get_slew_activities(self): """Get the slew activities for the given slew. This function retrieved the list of slew activities from the model after lsst.ts.scheduler.observatory_model.ObservatoryModel::slew is called. The activites are stored in an internal structure so parameters nor returns are necessary. """ self.slew_activities_list = [] critical_activities = self.model.lastslew_criticalpath for activity, delay in self.model.lastslew_delays_dict.items(): self.slew_activities_done += 1 self.slew_activities_list.append( SlewActivity(self.slew_activities_done, activity, delay, str(activity in critical_activities), self.slew_count)) def get_slew_state(self, slew_state_info): """Get the slew state from the current state instance. This function takes a given slew state instance and copies the information to the namedtuple that will allow it to be transferred to the database. Parameters ---------- slew_state_info : lsst.ts.scheduler.observatory_model.ObservatoryState The current slew state instance. Returns ------- :class:`.SlewState` The copied slew state information. """ slew_state = SlewState(self.slew_count, slew_state_info.time, slew_state_info.ra, slew_state_info.dec, str(slew_state_info.tracking), slew_state_info.alt, slew_state_info.az, slew_state_info.pa, slew_state_info.domalt, slew_state_info.domaz, slew_state_info.telalt, slew_state_info.telaz, slew_state_info.telrot, slew_state_info.ang, slew_state_info.filter, self.slew_count) return slew_state def observe(self, time_handler, target, observation): """Perform the observation of the given target. Parameters ---------- time_handler : :class:`.TimeHandler` An instance of the simulation's TimeHandler. target : SALPY_scheduler.targetC The Scheduler topic instance holding the target information. observation : SALPY_scheduler.observationC The Scheduler topic instance for recording the observation information. Returns ------- dict(:class:`.SlewHistory`, :class:`.SlewState`, :class:`.SlewState`, list[:class:`.SlewActivity`]) A dictionanry of all the slew information from the visit. dict(list[:class:`.TargetExposure`], list[:class:`.ObsExposure`]) A dictionary of all the exposure information from the visit. """ self.observations_made += 1 self.log.log( LoggingLevel.EXTENSIVE.value, "Starting observation {} for target {}.".format( self.observations_made, target.targetId)) slew_time = self.slew(target) time_handler.update_time(*slew_time) observation.observationId = self.observations_made observation.observation_start_time = time_handler.current_timestamp start_mjd, start_lst = self.date_profile( observation.observation_start_time) observation.observation_start_mjd = start_mjd observation.observation_start_lst = math.degrees(start_lst) observation.targetId = target.targetId observation.num_proposals = target.num_proposals for i in range(observation.num_proposals): observation.proposal_Ids[i] = target.proposal_Ids[i] observation.fieldId = target.fieldId observation.groupId = target.groupId observation.filter = target.filter observation.ra = target.ra observation.dec = target.dec observation.angle = target.angle observation.num_exposures = target.num_exposures self.log.log( LoggingLevel.EXTENSIVE.value, "Exposure Times for Target {}: {}".format( target.targetId, list(target.exposure_times))) visit_time = self.calculate_visit_time(target, time_handler) self.log.log( LoggingLevel.EXTENSIVE.value, "Visit Time for Target {}: {}".format(target.targetId, visit_time[0])) observation.visit_time = visit_time[0] for i, exposure in enumerate(self.observation_exposure_list): observation.exposure_times[i] = int(exposure.exposureTime) time_handler.update_time(*visit_time) self.log.log( LoggingLevel.EXTENSIVE.value, "Observation {} completed at {}.".format( self.observations_made, time_handler.current_timestring)) slew_info = { "slew_history": self.slew_history, "slew_initial_state": self.slew_initial_state, "slew_final_state": self.slew_final_state, "slew_activities": self.slew_activities_list, "slew_maxspeeds": self.slew_maxspeeds } exposure_info = { "target_exposures": self.target_exposure_list, "observation_exposures": self.observation_exposure_list } return slew_info, exposure_info def slew(self, target): """Perform the slewing operation for the observatory to the given target. Parameters ---------- target : SALPY_scheduler.targetC The Scheduler topic instance holding the target information. Returns ------- float The time to slew the telescope from its current position to the target position. """ self.slew_count += 1 self.log.log(LoggingLevel.TRACE.value, "Slew count: {}".format(self.slew_count)) initial_slew_state = copy.deepcopy(self.model.current_state) self.log.log(LoggingLevel.TRACE.value, "Initial slew state: {}".format(initial_slew_state)) self.slew_initial_state = self.get_slew_state(initial_slew_state) sched_target = Target.from_topic(target) self.model.slew(sched_target) final_slew_state = copy.deepcopy(self.model.current_state) self.log.log(LoggingLevel.TRACE.value, "Final slew state: {}".format(final_slew_state)) self.slew_final_state = self.get_slew_state(final_slew_state) slew_time = (final_slew_state.time - initial_slew_state.time, "seconds") slew_distance = palpy.dsep(final_slew_state.ra_rad, final_slew_state.dec_rad, initial_slew_state.ra_rad, initial_slew_state.dec_rad) self.slew_history = SlewHistory(self.slew_count, initial_slew_state.time, final_slew_state.time, slew_time[0], math.degrees(slew_distance), self.observations_made) self.get_slew_activities() self.slew_maxspeeds = SlewMaxSpeeds(self.slew_count, final_slew_state.domalt_peakspeed, final_slew_state.domaz_peakspeed, final_slew_state.telalt_peakspeed, final_slew_state.telaz_peakspeed, final_slew_state.telrot_peakspeed, self.slew_count) return slew_time def start_night(self, night, duration): """Perform start of night functions. Parameters ---------- night : int The current survey observing night. duration : int The survey duration in days. """ if self.variational_model.active: new_obs_config = self.variational_model.modify_parameters( night, duration) self.model.configure(new_obs_config) def swap_filter(self, filter_to_unmount): """Perform a filter swap. This function takes a requested filter to unmount and checks it against the list of removable filters. If it is not on the list, no filter swap is performed. If it is on the list, a filter swap is performed. Parameters ---------- filter_to_unmount : str The filter requested for unmounting. """ self.log.debug("Swap out {} filter.".format(filter_to_unmount)) self.model.swap_filter(filter_to_unmount)
def test_transitioner(self): observatory = ObservatoryModel() observatory.configure_from_module() pachon = astroplan.Observer.at_site("Cerro Pachon") start_time = astropy.time.Time(59598.66945625747, format='mjd') readout_time = observatory.params.readouttime * u.second exptime = 15 * u.second nexp = 2 targets = [ astroplan.FixedTarget(coord=SkyCoord(ra=81.1339111328125 * u.deg, dec=-17.532396316528303 * u.deg), name="1857"), astroplan.FixedTarget(coord=SkyCoord(ra=81.1758499145508 * u.deg, dec=-12.2103328704834 * u.deg), name="2100") ] constraints = [ astroplan.AltitudeConstraint(30 * u.deg, 89 * u.deg), astroplan.AirmassConstraint(2.2), astroplan.AtNightConstraint.twilight_astronomical() ] obs_blocks = [ astroplan.ObservingBlock.from_exposures( target, 1, exptime, nexp, readout_time, configuration={'filter': band}, constraints=constraints) for band in ('g', 'i') for target in targets ] self.assertGreater(observatory.params.readouttime, 1.5) self.assertGreater(observatory.params.filter_changetime, 15.0) slew_src = SlewTimeSource() transitioner = apsupp.OpsimTransitioner(slew_src) whitepaper_transitioner = apsupp.LSSTTransitioner() for old_block, new_block in zip(obs_blocks[:-1], obs_blocks[1:]): block = transitioner(old_block, new_block, start_time, pachon) wp_block = whitepaper_transitioner(old_block, new_block, start_time, pachon) duration_seconds = (block.duration / u.second).value wp_duration_seconds = (wp_block.duration / u.second).value self.assertAlmostEqual(duration_seconds, wp_duration_seconds, delta=5) old_filter = old_block.configuration['filter'] new_filter = new_block.configuration['filter'] if old_filter == new_filter: min_expected_duration = observatory.params.readouttime else: min_expected_duration = max( observatory.params.readouttime, observatory.params.filter_changetime) self.assertGreaterEqual(duration_seconds, min_expected_duration) self.assertLess(duration_seconds, 600)
class Driver(object): def __init__(self): self.log = logging.getLogger("schedulerDriver") self.params = DriverParameters() self.location = ObservatoryLocation() self.observatoryModel = ObservatoryModel(self.location, WORDY) self.observatoryModel2 = ObservatoryModel(self.location, WORDY) self.observatoryState = ObservatoryState() self.sky = AstronomicalSkyModel(self.location) self.propid_counter = 0 self.night = 0 self.start_time = 0.0 self.time = 0.0 self.targetid = 0 self.survey_started = False self.isnight = False self.sunset_timestamp = 0.0 self.sunrise_timestamp = 0.0 self.survey_duration_DAYS = 0.0 self.survey_duration_SECS = self.survey_duration_DAYS * 24 * 60 * 60.0 self.darktime = False self.mounted_filter = "" self.unmounted_filter = "" self.midnight_moonphase = 0.0 self.nulltarget = Target() self.nulltarget.targetid = -1 self.nulltarget.num_exp = 1 self.nulltarget.exp_times = [0.0] self.nulltarget.num_props = 1 self.nulltarget.propid_list = [0] self.nulltarget.need_list = [0.0] self.nulltarget.bonus_list = [0.0] self.nulltarget.value_list = [0.0] self.nulltarget.propboost_list = [1.0] self.last_winner_target = self.nulltarget.get_copy() self.deep_drilling_target = None self.need_filter_swap = False self.filter_to_unmount = "" self.filter_to_mount = "" self.cloud = 0.0 self.seeing = 0.0 def configure_scheduler(self, **kwargs): raise NotImplemented def configure_duration(self, survey_duration): self.survey_duration_DAYS = survey_duration self.survey_duration_SECS = survey_duration * 24 * 60 * 60.0 def configure(self, confdict): self.params.configure(confdict) self.log.log(WORDY, "configure: night_boundary=%.1f" % (self.params.night_boundary)) def configure_location(self, confdict): self.location.configure(confdict) self.observatoryModel.location.configure(confdict) self.observatoryModel2.location.configure(confdict) self.sky.update_location(self.location) def configure_observatory(self, confdict): """This method calls the configure()method in ObservatoryModel class, which configures all its submodules. When initializing one can issue a call to this method with a complete set of parameters on confdict. Parameters ---------- confdict : dict() Returns ------- None """ self.observatoryModel.configure(confdict) self.observatoryModel2.configure(confdict) def configure_telescope(self, confdict): self.observatoryModel.configure_telescope(confdict) self.observatoryModel2.configure_telescope(confdict) def configure_rotator(self, confdict): self.observatoryModel.configure_rotator(confdict) self.observatoryModel2.configure_rotator(confdict) def configure_dome(self, confdict): self.observatoryModel.configure_dome(confdict) self.observatoryModel2.configure_dome(confdict) def configure_optics(self, confdict): self.observatoryModel.configure_optics(confdict) self.observatoryModel2.configure_optics(confdict) def configure_camera(self, confdict): self.observatoryModel.configure_camera(confdict) self.observatoryModel2.configure_camera(confdict) def configure_slew(self, confdict): self.observatoryModel.configure_slew(confdict) self.observatoryModel2.configure_slew(confdict) def configure_park(self, confdict): self.observatoryModel.configure_park(confdict) self.observatoryModel2.configure_park(confdict) def start_survey(self, timestamp, night): """This method begins the survey. Parameters ---------- timestamp : float night : int Returns ------- None """ self.start_time = timestamp self.log.info("start_survey t=%.6f" % timestamp) self.survey_started = True self.sky.update(timestamp) (sunset, sunrise) = self.sky.get_night_boundaries(self.params.night_boundary) self.log.debug("start_survey sunset=%.6f sunrise=%.6f" % (sunset, sunrise)) # if round(sunset) <= round(timestamp) < round(sunrise): if sunset <= timestamp < sunrise: self.start_night(timestamp, night) self.sunset_timestamp = sunset self.sunrise_timestamp = sunrise def end_survey(self): """This method ends the survey. Parameters ---------- None Returns ------- None """ self.log.info("end_survey") def start_night(self, timestamp, night): """This method is called once per night before observations begin. It is not called if the observatory is undergoing downtime. Parameters ---------- timestamp : float night : int Returns ------- None """ timeprogress = (timestamp - self.start_time) / self.survey_duration_SECS self.log.info("start_night t=%.6f, night=%d timeprogress=%.2f%%" % (timestamp, night, 100 * timeprogress)) self.isnight = True def end_night(self, timestamp, night): """This method is called once per night after observing completes. Parameters ---------- timestamp : float night : int Returns ------- None """ pass def swap_filter(self, filter_to_unmount, filter_to_mount): self.log.info("swap_filter swap %s=>cam=>%s" % (filter_to_mount, filter_to_unmount)) self.observatoryModel.swap_filter(filter_to_unmount) self.unmounted_filter = filter_to_unmount self.mounted_filter = filter_to_mount return def update_time(self, timestamp, night): self.time = timestamp self.observatoryModel.update_state(self.time) if not self.survey_started: self.start_survey(timestamp, night) if self.isnight: # if round(timestamp) >= round(self.sunrise_timestamp): if timestamp >= self.sunrise_timestamp: self.end_night(timestamp, night) else: # if round(timestamp) >= round(self.sunset_timestamp): if timestamp >= self.sunset_timestamp: self.start_night(timestamp, night) return self.isnight def get_need_filter_swap(self): """When scheduler determines that a filter swap is needed, this shall return a tuple where the first element is a TRUE value, and the second and third elements are single-character strings identifying which filter to remove from the carousel, and which filter to add, respectively. Parameters ---------- None Returns ------- Tuple (bool, str, str) """ return (self.need_filter_swap, self.filter_to_unmount, self.filter_to_mount) def update_internal_conditions(self, observatory_state, night): if observatory_state.unmountedfilters != self.observatoryModel.current_state.unmountedfilters: unmount = observatory_state.unmountedfilters[0] mount = self.observatoryModel.current_state.unmountedfilters[0] self.swap_filter(unmount, mount) self.time = observatory_state.time self.observatoryModel.set_state(observatory_state) self.observatoryState.set(observatory_state) def update_external_conditions(self, cloud, seeing): self.cloud = cloud self.seeing = seeing return def cold_start(self, observations): """Rebuilds the internal state of the scheduler from a list of observations. Parameters ---------- observations : list of Observation objects Returns ------- None """ raise NotImplemented def select_next_target(self): """Picks a target and returns it as a target object. Parameters ---------- None Returns ------- Target """ raise NotImplemented def register_observation(self, observation): """Validates observation and returns a list of successfully completed observations. Parameters ---------- observation : Observation or a python list of Observations Returns ------- Python list of one or more Observations """ raise NotImplemented
class TestFeatureSchedulerTarget(unittest.TestCase): def setUp(self) -> None: self.observatory_model = ObservatoryModel() self.observatory_model.configure_from_module() start_time = Time(59853.983, format="mjd", scale="tai") self.observatory_model.update_state(start_time.unix) return super().setUp() def test_constructor(self): observation = self.make_fbs_observation(note="std") target = FeatureSchedulerTarget( observing_script_name="observing_script", observing_script_is_standard=True, observation=observation, ) slew_time, error = self.observatory_model.get_slew_delay(target) self.assertEqual(error, 0) self.assertGreater(slew_time, 0.0) def test_get_script_config(self): observation = self.make_fbs_observation(note="std") target = FeatureSchedulerTarget( observing_script_name="observing_script", observing_script_is_standard=True, observation=observation, ) script_config_expected = { "targetid": target.targetid, "band_filter": target.filter, "ra": Angle(float(observation["RA"][0]), unit=units.rad).to_string(unit=units.hourangle, sep=":"), "dec": Angle(float(observation["dec"][0]), unit=units.rad).to_string(unit=units.degree, sep=":"), "name": observation["note"][0], "program": observation["note"][0].rsplit("_", maxsplit=1)[0], "rot_sky": target.ang, "obs_time": target.obs_time, "num_exp": target.num_exp, "exp_times": target.exp_times, "estimated_slew_time": target.slewtime, } script_config_yaml = target.get_script_config() script_config_unpacked = yaml.safe_load(script_config_yaml) self.assertEqual(script_config_expected, script_config_unpacked) def test_get_script_config_cwfs(self): observation = self.make_fbs_observation(note="cwfs") target = FeatureSchedulerTarget( observing_script_name="observing_script", observing_script_is_standard=True, observation=observation, ) script_config_expected = dict(find_target=dict( az=math.degrees(float(observation["az"][0])), el=math.degrees(float(observation["alt"][0])), )) script_config_yaml = target.get_script_config() script_config_unpacked = yaml.safe_load(script_config_yaml) self.assertEqual(script_config_expected, script_config_unpacked) def test_get_script_config_cwfs_with_additional_config(self): observation = self.make_fbs_observation(note="cwfs") target = FeatureSchedulerTarget( observing_script_name="observing_script", observing_script_is_standard=True, observation=observation, script_configuration_cwfs=dict(filter="SDSSg", grating="empty_1"), ) script_config_expected = dict( find_target=dict( az=math.degrees(float(observation["az"][0])), el=math.degrees(float(observation["alt"][0])), ), filter="SDSSg", grating="empty_1", ) script_config_yaml = target.get_script_config() script_config_unpacked = yaml.safe_load(script_config_yaml) self.assertEqual(script_config_expected, script_config_unpacked) def test_get_script_config_spec(self): observation = self.make_fbs_observation(note="spec:HD12345") target = FeatureSchedulerTarget( observing_script_name="observing_script", observing_script_is_standard=True, observation=observation, ) script_config_expected = { "object_name": observation["note"][0], "object_dec": Angle(float(observation["dec"][0]), unit=units.rad).to_string(unit=units.degree, sep=":"), "object_ra": Angle(float(observation["RA"][0]), unit=units.rad).to_string(unit=units.hourangle, sep=":"), } script_config_yaml = target.get_script_config() script_config_unpacked = yaml.safe_load(script_config_yaml) self.assertEqual(script_config_expected, script_config_unpacked) def test_get_script_config_spec_with_additional_config(self): observation = self.make_fbs_observation(note="spec:HD12345") target = FeatureSchedulerTarget( observing_script_name="observing_script", observing_script_is_standard=True, observation=observation, script_configuration_spec=dict( filter_sequence=["SDSSg", "SDSSg"], grating_sequence=["empty_1", "empty_1"], ), ) script_config_expected = { "object_name": observation["note"][0], "object_dec": Angle(float(observation["dec"][0]), unit=units.rad).to_string(unit=units.degree, sep=":"), "object_ra": Angle(float(observation["RA"][0]), unit=units.rad).to_string(unit=units.hourangle, sep=":"), "filter_sequence": ["SDSSg", "SDSSg"], "grating_sequence": ["empty_1", "empty_1"], } script_config_yaml = target.get_script_config() script_config_unpacked = yaml.safe_load(script_config_yaml) self.assertEqual(script_config_expected, script_config_unpacked) def test_get_script_config_multiple_observations(self): filter_obs = "gri" observations = self.make_fbs_observation("std", filter_obs=filter_obs) target = FeatureSchedulerTarget( observing_script_name="observing_script", observing_script_is_standard=True, observation=observations, ) slew_time, error = self.observatory_model.get_slew_delay(target) script_config = yaml.safe_load(target.get_script_config()) self.assertEqual(len(script_config["exp_times"]), len(filter_obs) * 2) for filter_name in filter_obs: self.assertIn(filter_name, script_config["band_filter"]) self.assertEqual(error, 0) self.assertGreater(slew_time, 0.0) def make_fbs_observation(self, note, filter_obs="r"): observations = np.concatenate( [empty_observation() for _ in range(len(filter_obs))]) ra, dec, _ = self.observatory_model.altaz2radecpa( self.observatory_model.dateprofile, 65.0, 180.0) for obs_filter, observation in zip(filter_obs, observations): observation["RA"] = ra observation["dec"] = dec observation["mjd"] = self.observatory_model.dateprofile.mjd observation["filter"] = obs_filter observation["exptime"] = 30.0 observation["nexp"] = 2 observation["note"] = note return observations
class Driver(object): def __init__(self): self.log = logging.getLogger("schedulerDriver") self.params = DriverParameters() self.location = ObservatoryLocation() self.observatoryModel = ObservatoryModel(self.location, WORDY) self.observatoryModel2 = ObservatoryModel(self.location, WORDY) self.observatoryState = ObservatoryState() self.sky = AstronomicalSkyModel(self.location) self.db = FieldsDatabase() self.build_fields_dict() self.propid_counter = 0 self.science_proposal_list = [] self.start_time = 0.0 self.time = 0.0 self.targetid = 0 self.survey_started = False self.isnight = False self.sunset_timestamp = 0.0 self.sunrise_timestamp = 0.0 self.survey_duration_DAYS = 0.0 self.survey_duration_SECS = self.survey_duration_DAYS * 24 * 60 * 60.0 self.darktime = False self.mounted_filter = "" self.unmounted_filter = "" self.midnight_moonphase = 0.0 self.nulltarget = Target() self.nulltarget.targetid = -1 self.nulltarget.num_exp = 1 self.nulltarget.exp_times = [0.0] self.nulltarget.num_props = 1 self.nulltarget.propid_list = [0] self.nulltarget.need_list = [0.0] self.nulltarget.bonus_list = [0.0] self.nulltarget.value_list = [0.0] self.nulltarget.propboost_list = [1.0] self.last_winner_target = self.nulltarget.get_copy() self.deep_drilling_target = None self.need_filter_swap = False self.filter_to_unmount = "" self.filter_to_mount = "" self.cloud = 0.0 self.seeing = 0.0 def configure_survey(self, survey_conf_file): prop_conf_path = os.path.dirname(survey_conf_file) confdict = read_conf_file(survey_conf_file) self.survey_duration_DAYS = confdict["survey"]["survey_duration"] self.survey_duration_SECS = self.survey_duration_DAYS * 24 * 60 * 60.0 self.propid_counter = 0 self.science_proposal_list = [] if 'scripted_propconf' in confdict["proposals"]: scripted_propconflist = confdict["proposals"]["scripted_propconf"] else: scripted_propconflist = [] if not isinstance(scripted_propconflist, list): # turn it into a list with one entry propconf = scripted_propconflist scripted_propconflist = [] scripted_propconflist.append(propconf) self.log.info("configure_survey: scripted proposals %s" % (scripted_propconflist)) for k in range(len(scripted_propconflist)): self.propid_counter += 1 scripted_prop = ScriptedProposal(self.propid_counter, os.path.join(prop_conf_path, "{}".format(scripted_propconflist[k])), self.sky) self.science_proposal_list.append(scripted_prop) if 'areadistribution_propconf' in confdict["proposals"]: areadistribution_propconflist = confdict["proposals"]["areadistribution_propconf"] else: areadistribution_propconflist = [] self.log.info("areadistributionPropConf:%s default" % (areadistribution_propconflist)) if not isinstance(areadistribution_propconflist, list): # turn it into a list with one entry propconf = areadistribution_propconflist areadistribution_propconflist = [] areadistribution_propconflist.append(propconf) self.log.info("init: areadistribution proposals %s" % (areadistribution_propconflist)) for k in range(len(areadistribution_propconflist)): self.propid_counter += 1 configfilepath = os.path.join(prop_conf_path, "{}".format(areadistribution_propconflist[k])) (path, name_ext) = os.path.split(configfilepath) (name, ext) = os.path.splitext(name_ext) proposal_confdict = read_conf_file(configfilepath) self.create_area_proposal(self.propid_counter, name, proposal_confdict) for prop in self.science_proposal_list: prop.configure_constraints(self.params) def configure_duration(self, survey_duration): self.survey_duration_DAYS = survey_duration self.survey_duration_SECS = survey_duration * 24 * 60 * 60.0 def configure(self, confdict): self.params.configure(confdict) self.log.log(WORDY, "configure: coadd_values=%s" % (self.params.coadd_values)) self.log.log(WORDY, "configure: time_balancing=%s" % (self.params.time_balancing)) self.log.log(WORDY, "configure: timecost_dc=%.3f" % (self.params.timecost_dc)) self.log.log(WORDY, "configure: timecost_dt=%.3f" % (self.params.timecost_dt)) self.log.log(WORDY, "configure: timecost_k=%.3f" % (self.params.timecost_k)) self.log.log(WORDY, "configure: timecost_weight=%.3f" % (self.params.timecost_weight)) self.log.log(WORDY, "configure: night_boundary=%.1f" % (self.params.night_boundary)) self.log.log(WORDY, "configure: ignore_sky_brightness=%s" % (self.params.ignore_sky_brightness)) self.log.log(WORDY, "configure: ignore_airmass=%s" % (self.params.ignore_airmass)) self.log.log(WORDY, "configure: ignore_clouds=%s" % (self.params.ignore_clouds)) self.log.log(WORDY, "configure: ignore_seeing=%s" % (self.params.ignore_seeing)) self.log.log(WORDY, "configure: new_moon_phase_threshold=%.2f" % (self.params.new_moon_phase_threshold)) for prop in self.science_proposal_list: prop.configure_constraints(self.params) def configure_location(self, confdict): self.location.configure(confdict) self.observatoryModel.location.configure(confdict) self.observatoryModel2.location.configure(confdict) self.sky.update_location(self.location) def configure_observatory(self, confdict): self.observatoryModel.configure(confdict) self.observatoryModel2.configure(confdict) def configure_telescope(self, confdict): self.observatoryModel.configure_telescope(confdict) self.observatoryModel2.configure_telescope(confdict) def configure_rotator(self, confdict): self.observatoryModel.configure_rotator(confdict) self.observatoryModel2.configure_rotator(confdict) def configure_dome(self, confdict): self.observatoryModel.configure_dome(confdict) self.observatoryModel2.configure_dome(confdict) def configure_optics(self, confdict): self.observatoryModel.configure_optics(confdict) self.observatoryModel2.configure_optics(confdict) def configure_camera(self, confdict): self.observatoryModel.configure_camera(confdict) self.observatoryModel2.configure_camera(confdict) def configure_slew(self, confdict): self.observatoryModel.configure_slew(confdict) self.observatoryModel2.configure_slew(confdict) def configure_park(self, confdict): self.observatoryModel.configure_park(confdict) self.observatoryModel2.configure_park(confdict) def create_area_proposal(self, propid, name, config_dict): self.propid_counter += 1 area_prop = AreaDistributionProposal(propid, name, config_dict, self.sky) area_prop.configure_constraints(self.params) self.science_proposal_list.append(area_prop) def create_sequence_proposal(self, propid, name, config_dict): self.propid_counter += 1 seq_prop = TimeDistributionProposal(propid, name, config_dict, self.sky) seq_prop.configure_constraints(self.params) self.science_proposal_list.append(seq_prop) def build_fields_dict(self): sql = "select * from Field" res = self.db.query(sql) self.fields_dict = {} for row in res: field = Field() fieldid = row[0] field.fieldid = fieldid field.fov_rad = math.radians(row[1]) field.ra_rad = math.radians(row[2]) field.dec_rad = math.radians(row[3]) field.gl_rad = math.radians(row[4]) field.gb_rad = math.radians(row[5]) field.el_rad = math.radians(row[6]) field.eb_rad = math.radians(row[7]) self.fields_dict[fieldid] = field self.log.log(EXTENSIVE, "buildFieldsTable: %s" % (self.fields_dict[fieldid])) self.log.info("buildFieldsTable: %d fields" % (len(self.fields_dict))) def get_fields_dict(self): return self.fields_dict def start_survey(self, timestamp, night): self.start_time = timestamp self.log.info("start_survey t=%.6f" % timestamp) self.survey_started = True for prop in self.science_proposal_list: prop.start_survey() self.sky.update(timestamp) (sunset, sunrise) = self.sky.get_night_boundaries(self.params.night_boundary) self.log.debug("start_survey sunset=%.6f sunrise=%.6f" % (sunset, sunrise)) # if round(sunset) <= round(timestamp) < round(sunrise): if sunset <= timestamp < sunrise: self.start_night(timestamp, night) self.sunset_timestamp = sunset self.sunrise_timestamp = sunrise def end_survey(self): self.log.info("end_survey") for prop in self.science_proposal_list: prop.end_survey() def start_night(self, timestamp, night): timeprogress = (timestamp - self.start_time) / self.survey_duration_SECS self.log.info("start_night t=%.6f, night=%d timeprogress=%.2f%%" % (timestamp, night, 100 * timeprogress)) self.isnight = True for prop in self.science_proposal_list: prop.start_night(timestamp, self.observatoryModel.current_state.mountedfilters, night) def end_night(self, timestamp, night): timeprogress = (timestamp - self.start_time) / self.survey_duration_SECS self.log.info("end_night t=%.6f, night=%d timeprogress=%.2f%%" % (timestamp, night, 100 * timeprogress)) self.isnight = False self.last_winner_target = self.nulltarget self.deep_drilling_target = None total_filter_visits_dict = {} total_filter_goal_dict = {} total_filter_progress_dict = {} for prop in self.science_proposal_list: prop.end_night(timestamp) filter_visits_dict = {} filter_goal_dict = {} filter_progress_dict = {} for filter in self.observatoryModel.filters: if filter not in total_filter_visits_dict: total_filter_visits_dict[filter] = 0 total_filter_goal_dict[filter] = 0 filter_visits_dict[filter] = prop.get_filter_visits(filter) filter_goal_dict[filter] = prop.get_filter_goal(filter) filter_progress_dict[filter] = prop.get_filter_progress(filter) total_filter_visits_dict[filter] += filter_visits_dict[filter] total_filter_goal_dict[filter] += filter_goal_dict[filter] self.log.debug("end_night propid=%d name=%s filter=%s progress=%.2f%%" % (prop.propid, prop.name, filter, 100 * filter_progress_dict[filter])) for filter in self.observatoryModel.filters: if total_filter_goal_dict[filter] > 0: total_filter_progress_dict[filter] = \ float(total_filter_visits_dict[filter]) / total_filter_goal_dict[filter] else: total_filter_progress_dict[filter] = 0.0 self.log.info("end_night filter=%s progress=%.2f%%" % (filter, 100 * total_filter_progress_dict[filter])) previous_midnight_moonphase = self.midnight_moonphase self.sky.update(timestamp) (sunset, sunrise) = self.sky.get_night_boundaries(self.params.night_boundary) self.log.debug("end_night sunset=%.6f sunrise=%.6f" % (sunset, sunrise)) self.sunset_timestamp = sunset self.sunrise_timestamp = sunrise next_midnight = (sunset + sunrise) / 2 self.sky.update(next_midnight) info = self.sky.get_moon_sun_info(numpy.array([0.0]), numpy.array([0.0])) self.midnight_moonphase = info["moonPhase"] self.log.info("end_night next moonphase=%.2f%%" % (self.midnight_moonphase)) self.need_filter_swap = False self.filter_to_mount = "" self.filter_to_unmount = "" if self.darktime: if self.midnight_moonphase > previous_midnight_moonphase: self.log.info("end_night dark time waxing") if self.midnight_moonphase > self.params.new_moon_phase_threshold: self.need_filter_swap = True self.filter_to_mount = self.unmounted_filter self.filter_to_unmount = self.mounted_filter self.darktime = False else: self.log.info("end_night dark time waning") else: if self.midnight_moonphase < previous_midnight_moonphase: self.log.info("end_night bright time waning") if self.midnight_moonphase < self.params.new_moon_phase_threshold: self.need_filter_swap = True self.filter_to_mount = self.observatoryModel.params.filter_darktime max_progress = -1.0 for filter in self.observatoryModel.params.filter_removable_list: if total_filter_progress_dict[filter] > max_progress: self.filter_to_unmount = filter max_progress = total_filter_progress_dict[filter] self.darktime = True else: self.log.info("end_night bright time waxing") if self.need_filter_swap: self.log.debug("end_night filter swap %s=>cam=>%s" % (self.filter_to_mount, self.filter_to_unmount)) def swap_filter(self, filter_to_unmount, filter_to_mount): self.log.info("swap_filter swap %s=>cam=>%s" % (filter_to_mount, filter_to_unmount)) self.observatoryModel.swap_filter(filter_to_unmount) self.unmounted_filter = filter_to_unmount self.mounted_filter = filter_to_mount return def update_time(self, timestamp, night): self.time = timestamp self.observatoryModel.update_state(self.time) if not self.survey_started: self.start_survey(timestamp, night) if self.isnight: # if round(timestamp) >= round(self.sunrise_timestamp): if timestamp >= self.sunrise_timestamp: self.end_night(timestamp, night) else: # if round(timestamp) >= round(self.sunset_timestamp): if timestamp >= self.sunset_timestamp: self.start_night(timestamp, night) return self.isnight def get_need_filter_swap(self): return (self.need_filter_swap, self.filter_to_unmount, self.filter_to_mount) def update_internal_conditions(self, observatory_state, night): if observatory_state.unmountedfilters != self.observatoryModel.current_state.unmountedfilters: unmount = observatory_state.unmountedfilters[0] mount = self.observatoryModel.current_state.unmountedfilters[0] self.swap_filter(unmount, mount) for prop in self.science_proposal_list: prop.start_night(observatory_state.time, observatory_state.mountedfilters, night) self.time = observatory_state.time self.observatoryModel.set_state(observatory_state) self.observatoryState.set(observatory_state) def update_external_conditions(self, cloud, seeing): self.cloud = cloud self.seeing = seeing return def select_next_target(self): if not self.isnight: return self.nulltarget targets_dict = {} ranked_targets_list = [] propboost_dict = {} sumboost = 0.0 timeprogress = (self.time - self.start_time) / self.survey_duration_SECS for prop in self.science_proposal_list: progress = prop.get_progress() if self.params.time_balancing: if progress > 0.0: if timeprogress < 1.0: needindex = (1.0 - progress) / (1.0 - timeprogress) else: needindex = 0.0 if timeprogress > 0.0: progressindex = progress / timeprogress else: progressindex = 1.0 propboost_dict[prop.propid] = needindex / progressindex else: propboost_dict[prop.propid] = 1.0 else: propboost_dict[prop.propid] = 1.0 sumboost += propboost_dict[prop.propid] if self.deep_drilling_target is not None: self.log.debug("select_next_target: in deep drilling %s" % str(self.deep_drilling_target)) if self.observatoryModel.is_filter_change_allowed(): constrained_filter = None else: constrained_filter = self.observatoryModel.current_state.filter num_filter_changes = self.observatoryModel.get_number_filter_changes() delta_burst = self.observatoryModel.get_delta_filter_burst() delta_avg = self.observatoryModel.get_delta_filter_avg() self.log.debug("select_next_target: filter changes num=%i tburst=%.1f tavg=%.1f constrained=%s" % (num_filter_changes, delta_burst, delta_avg, constrained_filter)) for prop in self.science_proposal_list: propboost_dict[prop.propid] = \ (propboost_dict[prop.propid] * len(self.science_proposal_list) / sumboost) ** self.params.propboost_weight proptarget_list = prop.suggest_targets(self.time, self.deep_drilling_target, constrained_filter, self.cloud, self.seeing) self.log.log(EXTENSIVE, "select_next_target propid=%d name=%s " "targets=%d progress=%.2f%% propboost=%.3f" % (prop.propid, prop.name, len(proptarget_list), 100 * progress, propboost_dict[prop.propid])) for target in proptarget_list: target.num_props = 1 target.propboost = propboost_dict[prop.propid] target.propid_list = [prop.propid] target.need_list = [target.need] target.bonus_list = [target.bonus] target.value_list = [target.value] target.propboost_list = [target.propboost] target.sequenceid_list = [target.sequenceid] target.subsequencename_list = [target.subsequencename] target.groupid_list = [target.groupid] target.groupix_list = [target.groupix] target.is_deep_drilling_list = [target.is_deep_drilling] target.is_dd_firstvisit_list = [target.is_dd_firstvisit] target.remaining_dd_visits_list = [target.remaining_dd_visits] target.dd_exposures_list = [target.dd_exposures] target.dd_filterchanges_list = [target.dd_filterchanges] target.dd_exptime_list = [target.dd_exptime] fieldfilter = (target.fieldid, target.filter) if fieldfilter in targets_dict: if self.params.coadd_values: targets_dict[fieldfilter][0] = targets_dict[fieldfilter][0].get_copy() targets_dict[fieldfilter][0].need += target.need targets_dict[fieldfilter][0].bonus += target.bonus targets_dict[fieldfilter][0].value += target.value targets_dict[fieldfilter][0].propboost += target.propboost if target.is_deep_drilling: # overrides to make the coadded target a consistent deep drilling targets_dict[fieldfilter][0].is_deep_drilling = target.is_deep_drilling targets_dict[fieldfilter][0].is_dd_firstvisit = target.is_dd_firstvisit targets_dict[fieldfilter][0].remaining_dd_visits = target.remaining_dd_visits targets_dict[fieldfilter][0].dd_exposures = target.dd_exposures targets_dict[fieldfilter][0].dd_filterchanges = target.dd_filterchanges targets_dict[fieldfilter][0].dd_exptime = target.dd_exptime targets_dict[fieldfilter][0].sequenceid = target.sequenceid targets_dict[fieldfilter][0].subsequencename = target.subsequencename targets_dict[fieldfilter][0].groupid = target.groupid targets_dict[fieldfilter][0].groupix = target.groupix else: # new target to coadd is not deep drilling if not targets_dict[fieldfilter][0].is_deep_drilling: # coadded target is not deep drilling # overrides with new sequence information targets_dict[fieldfilter][0].sequenceid = target.sequenceid targets_dict[fieldfilter][0].subsequencename = target.subsequencename targets_dict[fieldfilter][0].groupid = target.groupid targets_dict[fieldfilter][0].groupix = target.groupix # if coadded target is already deep drilling, don't override targets_dict[fieldfilter][0].num_props += 1 targets_dict[fieldfilter][0].propid_list.append(prop.propid) targets_dict[fieldfilter][0].need_list.append(target.need) targets_dict[fieldfilter][0].bonus_list.append(target.bonus) targets_dict[fieldfilter][0].value_list.append(target.value) targets_dict[fieldfilter][0].propboost_list.append(target.propboost) targets_dict[fieldfilter][0].sequenceid_list.append(target.sequenceid) targets_dict[fieldfilter][0].subsequencename_list.append(target.subsequencename) targets_dict[fieldfilter][0].groupid_list.append(target.groupid) targets_dict[fieldfilter][0].groupix_list.append(target.groupix) targets_dict[fieldfilter][0].is_deep_drilling_list.append(target.is_deep_drilling) targets_dict[fieldfilter][0].is_dd_firstvisit_list.append(target.is_dd_firstvisit) targets_dict[fieldfilter][0].remaining_dd_visits_list.append( target.remaining_dd_visits) targets_dict[fieldfilter][0].dd_exposures_list.append(target.dd_exposures) targets_dict[fieldfilter][0].dd_filterchanges_list.append(target.dd_filterchanges) targets_dict[fieldfilter][0].dd_exptime_list.append(target.dd_exptime) else: targets_dict[fieldfilter].append(target) else: targets_dict[fieldfilter] = [target] filtercost = self.compute_filterchange_cost() * self.params.filtercost_weight for fieldfilter in targets_dict: slewtime = self.observatoryModel.get_slew_delay(targets_dict[fieldfilter][0]) if slewtime >= 0: timecost = self.compute_slewtime_cost(slewtime) * self.params.timecost_weight for target in targets_dict[fieldfilter]: target.slewtime = slewtime if target.filter != self.observatoryModel.current_state.filter: target.cost = timecost + filtercost else: target.cost = timecost target.rank = (target.value * target.propboost) - target.cost ranked_targets_list.append((-target.rank, target)) sorted_list = sorted(ranked_targets_list, key=itemgetter(0)) winner_found = False while len(sorted_list) > 0 and not winner_found: winner_target = sorted_list.pop(0)[1] self.observatoryModel2.set_state(self.observatoryState) self.observatoryModel2.observe(winner_target) if winner_target.is_dd_firstvisit: ttime = self.observatoryModel2.get_deep_drilling_time(winner_target) else: ttime = 0.0 ntime = self.observatoryModel2.current_state.time + ttime + 30.0 if ntime < self.sunrise_timestamp: self.observatoryModel2.update_state(ntime) if self.observatoryModel2.current_state.tracking: self.targetid += 1 winner_target.targetid = self.targetid winner_target.time = self.time winner_found = True else: self.log.debug("select_next_target: target rejected ttime=%.1f %s" % (ttime, str(winner_target))) self.log.debug("select_next_target: state rejected %s" % str(self.observatoryModel2.current_state)) else: self.log.debug("select_next_target: target rejected ttime=%.1f %s" % (ttime, str(winner_target))) self.log.debug("select_next_target: rejected due to end of night") if ttime == 0.0: # ttime == 0 means it is a regular visit (not DD) # if there is no time left for a single visit then quit break if winner_found: if not self.params.coadd_values: first_target = targets_dict[(winner_target.fieldid, winner_target.filter)][0] if first_target.propid != winner_target.propid: winner_target.copy_driver_state(first_target) self.last_winner_target = winner_target.get_copy() else: self.last_winner_target = self.nulltarget return self.last_winner_target def register_observation(self, observation): target_list = [] if observation.targetid > 0: if self.observation_fulfills_target(observation, self.last_winner_target): observation = self.last_winner_target else: self.log.info("register_observation: unexpected observation %s" % str(observation)) for prop in self.science_proposal_list: target = prop.register_observation(observation) if target is not None: target_list.append(target) self.last_observation = observation.get_copy() if self.last_observation.is_deep_drilling and (self.last_observation.remaining_dd_visits > 1): self.deep_drilling_target = self.last_observation else: self.deep_drilling_target = None return target_list def compute_slewtime_cost(self, slewtime): cost = (self.params.timecost_k / (slewtime + self.params.timecost_dt) - self.params.timecost_dc - self.params.timecost_cref) / (1.0 - self.params.timecost_cref) #cost = self.params.timecost_k / (slewtime + self.params.timecost_dt) - self.params.timecost_dc return cost def compute_filterchange_cost(self): t = self.observatoryModel.get_delta_last_filterchange() T = self.observatoryModel.params.filter_max_changes_avg_interval if t < T: cost = 1.0 - t / T else: cost = 0.0 return cost def observation_fulfills_target(self, observ, target): return (observ.fieldid == target.fieldid) and (observ.filter == target.filter)