Exemplo n.º 1
0
    def __init__(self):

        Driver.__init__(self)
        self.log = logging.getLogger("featureSchedulerDriver")

        # FIXME: Get parameters for the seeing model!
        telescope_seeing = 0.25
        optical_design_seeing = 0.08
        camera_seeing = 0.3

        self.seeing_model = SeeingModel(
            telescope_seeing=telescope_seeing,
            optical_design_seeing=optical_design_seeing,
            camera_seeing=camera_seeing)

        # self.sky_brightness = SkyModelPre()  # The sky brightness in self.sky uses opsim fields. We need healpix here
        self.sky_brightness = self.sky.sky_brightness
        self.night = 0

        self.target_list = {}

        self.scheduler = None
        self.sky_nside = 32
        self.scheduler_visit_counting_bfs = 0

        self.proposal_id_dict = {}

        self.time_distribution = False

        self.initialized = False
Exemplo n.º 2
0
Arquivo: m5.py Projeto: lsst/rtn-014
def _compute_band_fwhms(sampled_fields_band):
    band = sampled_fields_band["band"][0]
    assert np.all(band == sampled_fields_band["band"])

    logger.debug("Calculating the model seeing in %s", band)
    seeing_model = SeeingModel()

    band_idx = ["u", "g", "r", "i", "z", "y"].index(band)
    sampled_fields_band = sampled_fields_band.copy()
    sampled_fields_band["fwhm"] = seeing_model(
        0.7, sampled_fields_band["field_airmass"].values)["fwhmEff"][band_idx]
    return sampled_fields_band
Exemplo n.º 3
0
    def __init__(self, survey_start_time=DEFAULT_START_TIME):
        """Initialize source of simulated seeing values.

        Args:
           - survey_start_time :: an astropy.time.Time object
             designating the start of the survey.
        """
        self.start_time = survey_start_time
        self.seeing_data = SeeingData(self.start_time)
        self.seeing_model = SeeingModel()

        self.seeing_filter_index = {}
        for filter_index, filter_name in enumerate(
                self.seeing_model.filter_list):
            self.seeing_filter_index[filter_name] = filter_index
Exemplo n.º 4
0
def generate_ddf(ddf_name, nyears=10, space=2):
    previous_ddf = generate_dd_surveys()
    survey_names = np.array([survey.survey_name for survey in previous_ddf])
    survey_indx = np.where(survey_names == ddf_name)[0].max()
    ddf_ra = previous_ddf[survey_indx].ra * u.rad
    ddf_dec = previous_ddf[survey_indx].dec * u.rad

    site = Site('LSST')
    location = EarthLocation(lat=site.latitude,
                             lon=site.longitude,
                             height=site.height)

    mjd = np.arange(59853.5, 59853.5 + 365.25 * nyears, 20. / 60 / 24.)
    times = Time(mjd, format='mjd', location=location)

    airmass_limit = 2.5  # demand airmass lower than this
    twilight_limit = -18.  # Sun below this altitude in degrees
    dist_to_moon_limit = 30.  # minimum distance to keep from moon degrees
    zenith_limit = 10.  # Need to be this far away from zenith to start (20 min = 5 deg)
    g_m5_limit = 23.5  # mags

    season_gap = 20.  # days. Count any gap longer than this as it's own season
    season_length_limit = 80  # Days. Demand at least this many days in a season

    # How long to keep attempting a DDF
    expire_dict = {1: 36. / 24., 2: 0.5}

    sun_coords = get_sun(times)
    moon_coords = get_moon(times)

    sched_downtime_data = ScheduledDowntimeData(Time(mjd[0], format='mjd'))
    observatory_up = np.ones(mjd.size, dtype=bool)
    for dt in sched_downtime_data():
        indx = np.where((mjd >= dt['start'].mjd) & (mjd <= dt['end'].mjd))[0]
        observatory_up[indx] = False

    lst = times.sidereal_time('mean')

    sun_altaz = sun_coords.transform_to(AltAz(location=location))

    # generate a night label for each timestep
    sun_rise = np.where((sun_altaz.alt[0:-1] < 0) & (sun_altaz.alt[1:] > 0))[0]
    night = np.zeros(mjd.size, dtype=int)
    night[sun_rise] = 1
    night = np.cumsum(night) + 1  # 1-index for night

    sun_down = np.where(sun_altaz.alt < twilight_limit * u.deg)[0]

    ddf_coord = SkyCoord(ra=ddf_ra, dec=ddf_dec)
    ddf_altaz = ddf_coord.transform_to(AltAz(location=location, obstime=times))
    ddf_airmass = 1. / np.cos(np.radians(90. - ddf_altaz.az.deg))
    zenith = AltAz(alt=90. * u.deg, az=0. * u.deg)
    ddf_zenth_dist = zenith.separation(ddf_altaz)

    nside = 32
    ddf_indx = raDec2Hpid(nside, ddf_coord.ra.deg, ddf_coord.dec.deg)
    sm = SkyModelPre()

    g_sb = mjd * 0 + np.nan

    indices = np.where((sun_altaz.alt < twilight_limit * u.deg)
                       & (ddf_airmass > airmass_limit))[0]
    # In theory, one could reach into the sky brightness model and do a much faster interpolation
    # There might be an airmass limit on the sky brightness.
    for indx in sun_down:
        g_sb[indx] = sm.returnMags(mjd[indx],
                                   indx=[ddf_indx],
                                   filters='g',
                                   badval=np.nan)['g']

    dist_to_moon = ddf_coord.separation(moon_coords)
    seeing_model = SeeingModel()
    ddf_approx_fwhmEff = seeing_model(0.7, ddf_airmass)
    # I think this should pluck out the g-filter. Really should be labled
    ddf_approx_fwhmEff = ddf_approx_fwhmEff['fwhmEff'][1].ravel()

    ddf_m5 = m5_flat_sed('g',
                         g_sb,
                         ddf_approx_fwhmEff,
                         30.,
                         ddf_airmass,
                         nexp=1.)

    # demand sun down past twilight, ddf is up, and observatory is open, and not too close to the moon
    good = np.where((ddf_airmass < airmass_limit)
                    & (sun_altaz.alt < twilight_limit * u.deg)
                    & (ddf_airmass > 0) & (observatory_up == True)
                    & (dist_to_moon > dist_to_moon_limit * u.deg)
                    & (ddf_zenth_dist > zenith_limit * u.deg)
                    & (ddf_m5 > g_m5_limit))

    potential_nights = np.unique(night[good])
    night_gap = potential_nights[1:] - potential_nights[0:-1]
    big_gap = np.where(night_gap > season_gap)[0] + 1
    season = potential_nights * 0
    season[big_gap] = 1
    season = np.cumsum(season)

    u_seasons = np.unique(season)
    season_lengths = []
    for se in u_seasons:
        in_se = np.where(season == se)
        season_lengths.append(
            np.max(potential_nights[in_se]) - np.min(potential_nights[in_se]))
    season_lengths = np.array(season_lengths)

    good_seasons = u_seasons[np.where(season_lengths > season_length_limit)[0]]
    gn = np.isin(season, good_seasons)
    potential_nights = potential_nights[gn]
    season = season[gn]

    obs_attempts = []
    for sea in np.unique(season):
        night_indx = np.where(season == sea)
        obs_attempts.append(
            place_obs(potential_nights[night_indx], space=space))
    obs_attempts = np.concatenate(obs_attempts)

    mjd_observe = []
    m5_approx = []
    for indx in np.where(obs_attempts > 0)[0]:
        in_night_indx = np.where(night == potential_nights[indx])[0]
        best_depth_indx = np.min(
            np.where(
                ddf_m5[in_night_indx] == np.nanmax(ddf_m5[in_night_indx]))[0])
        mjd_start = mjd[in_night_indx[best_depth_indx]]
        m5_approx.append(ddf_m5[in_night_indx[best_depth_indx]])
        mjd_end = mjd_start + expire_dict[obs_attempts[indx]]
        mjd_observe.append((mjd_start, mjd_end))

    result = np.zeros(len(mjd_observe),
                      dtype=[('mjd_start', '<f8'), ('mjd_end', '<f8'),
                             ('label', '<U10')])
    mjd_observe = np.array(mjd_observe)
    result['mjd_start'] = mjd_observe[:, 0]
    result['mjd_end'] = mjd_observe[:, 1]
    result['label'] = ddf_name

    return result  #, ddf_ra, ddf_dec #, previous_ddf[survey_indx].observations, m5_approx
Exemplo n.º 5
0
    def __init__(self,
                 nside=None,
                 mjd_start=59853.5,
                 seed=42,
                 quickTest=True,
                 alt_min=5.,
                 lax_dome=True,
                 cloud_limit=0.3,
                 sim_ToO=None,
                 seeing_db=None,
                 cloud_db=None,
                 cloud_offset_year=0):
        """
        Parameters
        ----------
        nside : int (None)
            The healpix nside resolution
        mjd_start : float (59853.5)
            The MJD to start the observatory up at
        alt_min : float (5.)
            The minimum altitude to compute models at (degrees).
        lax_dome : bool (True)
            Passed to observatory model. If true, allows dome creep.
        cloud_limit : float (0.3)
            The limit to stop taking observations if the cloud model returns something equal or higher
        sim_ToO : sim_targetoO object (None)
            If one would like to inject simulated ToOs into the telemetry stream.
        seeing_db : filename of the seeing data database (None)
            If one would like to use an alternate seeing database
        cloud_offset_year : float, opt
            Offset into the cloud database by 'offset_year' years. Default 0.
        cloud_db : filename of the cloud data database (None)
            If one would like to use an alternate seeing database
        """

        if nside is None:
            nside = set_default_nside()
        self.nside = nside

        self.cloud_limit = cloud_limit

        self.alt_min = np.radians(alt_min)
        self.lax_dome = lax_dome

        self.mjd_start = mjd_start

        # Conditions object to update and return on request
        self.conditions = Conditions(nside=self.nside)

        self.sim_ToO = sim_ToO

        # Create an astropy location
        self.site = Site('LSST')
        self.location = EarthLocation(lat=self.site.latitude,
                                      lon=self.site.longitude,
                                      height=self.site.height)

        # Load up all the models we need

        mjd_start_time = Time(self.mjd_start, format='mjd')
        # Downtime
        self.down_nights = []
        self.sched_downtime_data = ScheduledDowntimeData(mjd_start_time)
        self.unsched_downtime_data = UnscheduledDowntimeData(mjd_start_time)

        sched_downtimes = self.sched_downtime_data()
        unsched_downtimes = self.unsched_downtime_data()

        down_starts = []
        down_ends = []
        for dt in sched_downtimes:
            down_starts.append(dt['start'].mjd)
            down_ends.append(dt['end'].mjd)
        for dt in unsched_downtimes:
            down_starts.append(dt['start'].mjd)
            down_ends.append(dt['end'].mjd)

        self.downtimes = np.array(list(zip(down_starts, down_ends)),
                                  dtype=list(
                                      zip(['start', 'end'], [float, float])))
        self.downtimes.sort(order='start')

        # Make sure there aren't any overlapping downtimes
        diff = self.downtimes['start'][1:] - self.downtimes['end'][0:-1]
        while np.min(diff) < 0:
            # Should be able to do this wihtout a loop, but this works
            for i, dt in enumerate(self.downtimes[0:-1]):
                if self.downtimes['start'][i + 1] < dt['end']:
                    new_end = np.max([dt['end'], self.downtimes['end'][i + 1]])
                    self.downtimes[i]['end'] = new_end
                    self.downtimes[i + 1]['end'] = new_end

            good = np.where(
                self.downtimes['end'] - np.roll(self.downtimes['end'], 1) != 0)
            self.downtimes = self.downtimes[good]
            diff = self.downtimes['start'][1:] - self.downtimes['end'][0:-1]

        self.seeing_data = SeeingData(mjd_start_time, seeing_db=seeing_db)
        self.seeing_model = SeeingModel()
        self.seeing_indx_dict = {}
        for i, filtername in enumerate(self.seeing_model.filter_list):
            self.seeing_indx_dict[filtername] = i

        self.cloud_data = CloudData(mjd_start_time,
                                    cloud_db=cloud_db,
                                    offset_year=cloud_offset_year)
        sched_logger.info(
            f"Using {self.cloud_data.cloud_db} as cloud database with start year {self.cloud_data.start_time.iso}"
        )

        self.sky_model = sb.SkyModelPre(speedLoad=quickTest)

        self.observatory = ExtendedObservatoryModel()
        self.observatory.configure_from_module()
        # Make it so it respects my requested rotator angles
        self.observatory.params.rotator_followsky = True

        self.filterlist = ['u', 'g', 'r', 'i', 'z', 'y']
        self.seeing_FWHMeff = {}
        for key in self.filterlist:
            self.seeing_FWHMeff[key] = np.zeros(hp.nside2npix(self.nside),
                                                dtype=float)

        self.almanac = Almanac(mjd_start=mjd_start)

        # Let's make sure we're at an openable MJD
        good_mjd = False
        to_set_mjd = mjd_start
        while not good_mjd:
            good_mjd, to_set_mjd = self.check_mjd(to_set_mjd)
        self.mjd = to_set_mjd

        self.obsID_counter = 0
Exemplo n.º 6
0
import sys

if __name__ == "__main__":

    dds = generate_dd_surveys()
    mjd0 = 59853.5
    delta_t = 15./60./24.  # to days
    survey_length = 10.*365.25
    sun_limit = np.radians(-12.)  # degrees

    nominal_seeing = 0.7  #  arcsec

    filtername = 'g'


    seeing_model = SeeingModel()

    seeing_indx = 1 #np.where(seeing_model.filter_list == filtername)[0]

    # result

    mjds = np.arange(mjd0, mjd0+survey_length, delta_t)

    # XXX Temp
    #mjds = mjds[0:100]

    names = ['mjd', 'sun_alt']
    for survey in dds:
        names.append(survey.survey_name+'_airmass')
        names.append(survey.survey_name+'_sky_g')
        names.append(survey.survey_name+'_m5_g')
Exemplo n.º 7
0
class FeatureSchedulerDriver(Driver):
    def __init__(self):

        Driver.__init__(self)
        self.log = logging.getLogger("featureSchedulerDriver")

        # FIXME: Get parameters for the seeing model!
        telescope_seeing = 0.25
        optical_design_seeing = 0.08
        camera_seeing = 0.3

        self.seeing_model = SeeingModel(
            telescope_seeing=telescope_seeing,
            optical_design_seeing=optical_design_seeing,
            camera_seeing=camera_seeing)

        # self.sky_brightness = SkyModelPre()  # The sky brightness in self.sky uses opsim fields. We need healpix here
        self.sky_brightness = self.sky.sky_brightness
        self.night = 0

        self.target_list = {}

        self.scheduler = None
        self.sky_nside = 32
        self.scheduler_visit_counting_bfs = 0

        self.proposal_id_dict = {}

        self.time_distribution = False

        self.initialized = False

    # def start_survey(self, timestamp, night):
    #
    #     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 sunset <= timestamp < sunrise:
    #         self.start_night(timestamp, night)
    #
    #     self.sunset_timestamp = sunset
    #     self.sunrise_timestamp = sunrise

    def create_area_proposal(self, propid, name, config_dict):
        '''Override create_area_proposal from superclass.

        One call to rule them all!

        :param propid:
        :param name:
        :param config_dict:
        :return:
        '''
        if not self.initialized and name == 'WideFastDeep':
            self.initialize(config_dict)

    def create_sequence_proposal(self, propid, name, config_dict):
        pass

    def initialize(self, config_dict):
        # Load configuration from a python module.
        # TODO:
        # This can be changed later to load from a given string, I'm planning on getting the name from
        # lsst.sims.ocs.configuration.survey.general_proposals
        conf = import_module('lsst.sims.featureScheduler.driver.config')
        from lsst.ts.scheduler.kernel import Field

        self.scheduler = conf.scheduler
        self.sky_nside = conf.nside
        # self.scheduler_visit_counting_bfs = conf.scheduler_visit_counting_bfs

        # Now, configure the different proposals inside Driver
        # Get the proposal list from feature based scheduler

        survey_fields = {}
        for survey in self.scheduler.surveys:
            if 'proposals' in survey.features:
                for i, pid in enumerate(
                        survey.features['proposals'].id.keys()):
                    # This gets names of all proposals on all surveys, overwrites repeated and stores new ones
                    self.proposal_id_dict[pid] = [
                        0, survey.features['proposals'].id[pid]
                    ]

            # # Now need to get fields for each proposal
            # for field in survey.fields:
            #     if field['tag'] > 0:
            #         if field['tag'] not in survey_fields.keys():
            #             # add and skip to next iteration
            #             survey_fields[field['tag']] = np.array([field])
            #             continue
            #
            #         if field['field_id'] not in survey_fields[field['tag']]['field_id']:
            #             survey_fields[field['tag']] = np.append(survey_fields[field['tag']],
            #                                                     field)

        self.log.debug('Proposal_id_dict: %s' % self.proposal_id_dict.keys())
        # self.log.debug('survey_fields: %s' % survey_fields.keys())

        for pid in self.proposal_id_dict.keys():
            self.proposal_id_dict[pid][0] = self.propid_counter
            self.propid_counter += 1

            self.log.debug('%s: %s' % (pid, self.proposal_id_dict[pid]))

            prop = FeatureBasedProposal(pid, self.proposal_id_dict[pid][1],
                                        config_dict, self.sky)

            prop.configure_constraints(self.params)
            # create proposal field list
            # prop.survey_fields = len(survey_fields[pid])
            # for field in survey_fields[pid]:
            #     prop.survey_fields_dict[field['field_id']] = Field(fieldid=field['field_id'],
            #                                                        ra_rad=field['RA'],
            #                                                        dec_rad=field['dec'],
            #                                                        gl_rad=field['gl'],
            #                                                        gb_rad=field['gb'],
            #                                                        el_rad=field['el'],
            #                                                        eb_rad=field['eb'],
            #                                                        fov_rad=field['fov_rad'])
            self.science_proposal_list.append(prop)

        self.initialized = True

    def end_survey(self):

        self.log.info("end_survey")

    def start_night(self, timestamp, night):

        self.night = night
        super(FeatureSchedulerDriver, self).start_night(timestamp, night)
        for fieldid in self.target_list.keys():
            for filtername in self.target_list[fieldid].keys():
                self.target_list[fieldid][filtername].groupix = 0

    def select_next_target(self):

        if not self.isnight:
            return self.nulltarget

        # Telemetry stream
        telemetry_stream = self.get_telemetry()

        self.scheduler.update_conditions(telemetry_stream)
        winner_target = self.scheduler.request_observation()
        if winner_target is None:
            self.log.debug(
                '[mjd = %.3f]: No target generated by the scheduler...' %
                telemetry_stream['mjd'])
            self.scheduler.flush_queue()
            self.last_winner_target = self.nulltarget.get_copy()
            return self.last_winner_target

        self.log.debug('winner target: %s' % winner_target)
        self.scheduler_winner_target = winner_target

        hpid = _raDec2Hpid(self.sky_nside, winner_target['RA'][0],
                           winner_target['dec'][0])

        propid = winner_target['survey_id'][0]
        filtername = winner_target['filter'][0]
        indx = self.proposal_id_dict[propid][0]
        target = self.generate_target(winner_target[0])

        self.target_list[target.fieldid] = {filtername: target}
        self.science_proposal_list[indx].survey_targets_dict[
            target.fieldid] = {
                filtername: target
            }

        target.time = self.time

        target.ang_rad = self.observatoryModel.radec2altazpa(
            self.observatoryModel.dateprofile, target.ra_rad,
            target.dec_rad)[2]
        self.observatoryModel.current_state.ang_rad = target.ang_rad
        slewtime, slew_state = self.observatoryModel.get_slew_delay(target)

        if slewtime > 0.:
            self.scheduler_winner_target[
                'mjd'] = telemetry_stream['mjd'] + slewtime / 60. / 60. / 24.
            self.scheduler_winner_target['night'] = self.night
            self.scheduler_winner_target['slewtime'] = slewtime
            self.scheduler_winner_target['skybrightness'] = \
                self.sky_brightness.returnMags(self.observatoryModel.dateprofile.mjd,
                                               indx=[hpid],
                                               extrapolate=True)[filtername]
            self.scheduler_winner_target['FWHMeff'] = telemetry_stream[
                'FWHMeff_%s' % filtername][hpid]
            self.scheduler_winner_target['FWHM_geometric'] = \
                telemetry_stream['FWHM_geometric_%s' % winner_target['filter'][0]][hpid]
            self.scheduler_winner_target['airmass'] = telemetry_stream[
                'airmass'][hpid]
            self.scheduler_winner_target['fivesigmadepth'] = m5_flat_sed(
                filtername, self.scheduler_winner_target['skybrightness'],
                self.scheduler_winner_target['FWHMeff'],
                self.scheduler_winner_target['exptime'],
                self.scheduler_winner_target['airmass'])
            self.scheduler_winner_target['alt'] = target.alt_rad
            self.scheduler_winner_target['az'] = target.az_rad
            self.scheduler_winner_target['rotSkyPos'] = target.ang_rad
            self.scheduler_winner_target['clouds'] = self.cloud
            self.scheduler_winner_target['sunAlt'] = telemetry_stream['sunAlt']
            self.scheduler_winner_target['moonAlt'] = telemetry_stream[
                'moonAlt']

            target.slewtime = slewtime
            target.airmass = telemetry_stream['airmass'][hpid]
            target.sky_brightness = self.sky_brightness.returnMags(
                self.observatoryModel.dateprofile.mjd,
                indx=[hpid],
                extrapolate=True)[filtername][0]

            self.observatoryModel2.set_state(self.observatoryState)
            self.observatoryState.ang_rad = target.ang_rad
            self.observatoryModel2.observe(target)
            target.seeing = self.seeing
            target.cloud = self.cloud

            ntime = self.observatoryModel2.current_state.time
            if ntime < self.sunrise_timestamp:
                # self.observatoryModel2.update_state(ntime)
                if self.observatoryModel2.current_state.tracking:
                    target.time = self.time
                    if self.last_winner_target.targetid == target.targetid:
                        self.last_winner_target = self.nulltarget
                        self.targetid -= 1
                    else:
                        self.last_winner_target = target.get_copy()
                else:
                    self.log.debug("select_next_target: target rejected %s" %
                                   (str(target)))
                    self.log.debug("select_next_target: state rejected %s" %
                                   str(self.observatoryModel2.current_state))
                    self.last_winner_target = self.nulltarget
                    self.targetid -= 1
            else:
                self.last_winner_target = self.nulltarget
                self.targetid -= 1

        else:
            self.log.debug('Fail state: %i' % slew_state)
            self.log.debug('Slewtime lower than zero! (slewtime = %f)' %
                           slewtime)
            self.scheduler.flush_queue()
            self.targetid -= 1
            self.last_winner_target = self.nulltarget

        self.log.debug(self.last_winner_target)
        for propid in self.proposal_id_dict.keys():
            self.science_proposal_list[self.proposal_id_dict[propid]
                                       [0]].winners_list = []
        self.science_proposal_list[indx].winners_list = [target.get_copy()]

        return self.last_winner_target

    def register_observation(self, observation):
        if observation.targetid > 0:
            self.scheduler.add_observation(self.scheduler_winner_target)

            return super(FeatureSchedulerDriver,
                         self).register_observation(observation)
        else:
            return []

    def update_time(self, timestamp, night):

        delta_time = timestamp - self.time
        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.scheduler.flush_queue()  # Clean queue if night is over!
                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 generate_target(self, fb_observation, tag='generate'):
        '''Takes an observation array given by the feature based scheduler and generate an appropriate OpSim target.

        :param fb_observation: numpy.array
        :return: Target
        '''

        # self.log.debug('%s: %s' % (tag, fb_observation))
        self.targetid += 1
        filtername = fb_observation['filter']
        propid = fb_observation['survey_id']

        target = Target()
        target.targetid = self.targetid
        target.fieldid = fb_observation['field_id']
        target.filter = str(filtername)
        target.num_exp = fb_observation['nexp']
        target.exp_times = [
            fb_observation['exptime'] / fb_observation['nexp']
        ] * fb_observation['nexp']
        target.ra_rad = fb_observation['RA']
        target.dec_rad = fb_observation['dec']
        target.ang_rad = fb_observation['rotSkyPos']
        target.propid = propid
        target.goal = 100
        target.visits = 0
        target.progress = 0.0
        target.groupid = 1
        target.groupix = 0
        target.propid_list = [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]

        return target

    def append_target(self, fb_observation):
        '''Takes an observation array given by the feature based scheduler and append it to an existing OpSim target.

        :param fb_observation: numpy.array
        :return: Target
        '''
        # self.log.debug('append: %s' % fb_observation)
        # target = self.target_list[fb_observation['field_id']][fb_observation['filter']].get_copy()
        # self.targetid += 1
        # target.targetid = self.targetid
        propid = fb_observation['survey_id']
        # target.propid = propid
        # # if propid not in target.propid_list:
        #
        # target.propid_list = [propid]
        indx = self.proposal_id_dict[propid][0]
        #
        # if target.fieldid in self.science_proposal_list[indx].survey_targets_dict:
        #     self.science_proposal_list[indx].survey_targets_dict[target.fieldid][target.filter] = target
        # else:
        #     self.science_proposal_list[indx].survey_targets_dict[target.fieldid] = {target.filter: target}
        #
        # target.ra_rad = fb_observation['RA']
        # target.dec_rad = fb_observation['dec']
        # target.groupix = 0
        # self.target_list[fb_observation['field_id']][fb_observation['filter']] = target.get_copy()

        target = self.generate_target(fb_observation, 'append')
        if target.fieldid in self.science_proposal_list[
                indx].survey_targets_dict:
            self.science_proposal_list[indx].survey_targets_dict[
                target.fieldid][target.filter] = target
        else:
            self.science_proposal_list[indx].survey_targets_dict[
                target.fieldid] = {
                    target.filter: target
                }

        return target

    def get_telemetry(self):

        telemetry_stream = {}
        telemetry_stream['mjd'] = copy.copy(
            self.observatoryModel.dateprofile.mjd)
        telemetry_stream['night'] = copy.copy(self.night)
        telemetry_stream['lmst'] = copy.copy(
            self.observatoryModel.dateprofile.lst_rad * 12. / np.pi) % 24.

        dp = DateProfile(0, self.observatoryModel.dateprofile.location)
        mjd, _ = dp(self.sunrise_timestamp)
        telemetry_stream['next_twilight_start'] = copy.copy(dp.mjd)

        dp = DateProfile(0, self.observatoryModel.dateprofile.location)
        mjd, _ = dp(self.sunset_timestamp)
        telemetry_stream['next_twilight_end'] = copy.copy(dp.mjd)
        telemetry_stream['last_twilight_end'] = copy.copy(dp.mjd)

        # Telemetry about where the observatory is pointing and what filter it's using.
        telemetry_stream['filter'] = copy.copy(
            self.observatoryModel.current_state.filter)
        telemetry_stream['mounted_filters'] = copy.copy(
            self.observatoryModel.current_state.mountedfilters)
        telemetry_stream['telRA'] = copy.copy(
            np.degrees(self.observatoryModel.current_state.ra_rad))
        telemetry_stream['telDec'] = copy.copy(
            np.degrees(self.observatoryModel.current_state.dec_rad))
        telemetry_stream['telAlt'] = copy.copy(
            np.degrees(self.observatoryModel.current_state.alt_rad))
        telemetry_stream['telAz'] = copy.copy(
            np.degrees(self.observatoryModel.current_state.az_rad))
        telemetry_stream['telRot'] = copy.copy(
            np.degrees(self.observatoryModel.current_state.rot_rad))

        # What is the sky brightness over the sky (healpix map)
        telemetry_stream['skybrightness'] = copy.copy(
            self.sky_brightness.returnMags(
                self.observatoryModel.dateprofile.mjd))

        # Find expected slewtimes over the sky.
        # The slewtimes telemetry is expected to be a healpix map of slewtimes, in the current filter.
        # There are other features that balance the filter change cost, separately.
        # (this filter aspect may be something to revisit in the future?)
        # Note that these slewtimes are -1 where the telescope is not allowed to point.
        alt, az = stupidFast_RaDec2AltAz(
            self.scheduler.ra_grid_rad, self.scheduler.dec_grid_rad,
            self.observatoryModel.location.latitude_rad,
            self.observatoryModel.location.longitude_rad,
            self.observatoryModel.dateprofile.mjd,
            self.observatoryModel.dateprofile.lst_rad)
        current_filter = self.observatoryModel.current_state.filter

        telemetry_stream['slewtimes'] = copy.copy(
            self.observatoryModel.get_approximate_slew_delay(
                alt, az, current_filter))
        # What is the airmass over the sky (healpix map).

        telemetry_stream['airmass'] = copy.copy(
            self.sky_brightness.returnAirmass(
                self.observatoryModel.dateprofile.mjd))

        delta_t = (self.time - self.start_time)
        telemetry_stream['clouds'] = self.cloud

        fwhm_geometric, fwhm_effective = self.seeing_model.seeing_at_airmass(
            self.seeing, telemetry_stream['airmass'])

        for i, filtername in enumerate(['u', 'g', 'r', 'i', 'z', 'y']):
            telemetry_stream['FWHMeff_%s' % filtername] = copy.copy(
                fwhm_effective[i])  # arcsec
            telemetry_stream['FWHM_geometric_%s' % filtername] = copy.copy(
                fwhm_geometric[i])

        sunMoon_info = self.sky_brightness.returnSunMoon(
            self.observatoryModel.dateprofile.mjd)
        # Pretty sure these are radians
        telemetry_stream['sunAlt'] = copy.copy(np.max(sunMoon_info['sunAlt']))
        telemetry_stream['moonAlt'] = copy.copy(np.max(
            sunMoon_info['moonAlt']))

        return telemetry_stream