Ejemplo n.º 1
0
 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
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
    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))
Ejemplo n.º 4
0
    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()
Ejemplo n.º 5
0
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()
Ejemplo n.º 6
0
    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
Ejemplo n.º 7
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
Ejemplo n.º 8
0
    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())
Ejemplo n.º 9
0
    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
Ejemplo n.º 10
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)
Ejemplo n.º 11
0
    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 = []
Ejemplo n.º 12
0
    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 = []
Ejemplo n.º 13
0
    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)
Ejemplo n.º 14
0
    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)
Ejemplo n.º 15
0
 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)
Ejemplo n.º 16
0
    def setUpClass(cls):
        cls.location = ObservatoryLocation()
        cls.location.for_lsst()

        cls.model = ObservatoryModel(cls.location)
        cls.model.configure_from_module()
Ejemplo n.º 17
0
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)
Ejemplo n.º 18
0
    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)
Ejemplo n.º 19
0
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
Ejemplo n.º 20
0
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
Ejemplo n.º 21
0
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)