def __init__(self, location): """Initialize the class. Parameters ---------- location : `lsst.ts.dateloc.ObservatoryLocation` The instance containing the observatory location information. """ self.log = logging.getLogger("sky_model.AstronomicalSkyModel") self.date_profile = DateProfile(0, location) self.sky_brightness = SkyModelPre() self._sb_nside = self.sky_brightness.nside self.sun = Sun() self.exclude_planets = True
def __init__(self, obs_site_config): """Initialize the class. Parameters ---------- obs_site_config : :class:`.ObservingSite` The instance of the observing site configuration. """ self.log = logging.getLogger("observatory.MainObservatory") observatory_location = ObservatoryLocation() observatory_location.configure({"obs_site": obs_site_config.toDict()}) self.config = None self.model = ObservatoryModel(observatory_location, LoggingLevel.WORDY.value) self.date_profile = DateProfile(0, observatory_location) self.param_dict = {} self.slew_count = 0 self.observations_made = 0 self.exposures_made = 0 self.target_exposure_list = None self.observation_exposure_list = None self.slew_history = None self.slew_final_state = None self.slew_initial_state = None self.slew_activities_list = None self.slew_activities_done = 0 self.slew_maxspeeds = None self.variational_model = None
def get_sky_brightness_timeblock(self, timestamp, timestep, num_steps, ra, dec): """Get LSST 6 filter sky brightness for a set of fields for a range of times. This function retrieves the LSST 6 filter sky brightness magnitudes for a given set of fields at a range of MJDs provided via the timeblock information. The field ids stored in the sky brightness data are off-by-one from the stored field ids, hence the subtraction. Parameters ---------- timestamp : `float` The UTC timestamp to start the time block. timestep : `float` The number of seconds to increment the timestamp with. num_steps : `int` The number of steps to create for the time block. ra : `numpy.array` The set of fields RA coordinates to retrieve the sky brightness for. dec : `numpy.array` The set of fields RA coordinates to retrieve the sky brightness for. Returns ------- `numpy.ndarray` The LSST 6 filter sky brightness magnitudes. """ dp = DateProfile(0, self.date_profile.location) mags = [] ids = _raDec2Hpid(self._sb_nside, ra, dec) for i in range(num_steps): ts = timestamp + i * timestep mjd, _ = dp(ts) mags.append( self.sky_brightness.returnMags( dp.mjd, indx=ids, badval=float("nan"), zenith_mask=False, planet_mask=self.exclude_planets, extrapolate=False, )) return mags
def setUp(self): self.lsst_site = ObservatoryLocation() self.lsst_site.for_lsst() self.dp = DateProfile(LSST_START_TIMESTAMP, self.lsst_site)
class DateProfileTest(unittest.TestCase): def setUp(self): self.lsst_site = ObservatoryLocation() self.lsst_site.for_lsst() self.dp = DateProfile(LSST_START_TIMESTAMP, self.lsst_site) def test_basic_information_after_creation(self): self.assertEqual(self.dp.timestamp, LSST_START_TIMESTAMP) self.assertIsNotNone(self.dp.location) self.assertIsNotNone(self.dp.current_dt) self.assertEqual(self.dp.mjd, LSST_START_MJD) self.assertEqual(self.dp.lst_rad, 0.5215154816963141) def test_update_mechanism(self): new_timestamp = LSST_START_TIMESTAMP + 3600.0 self.dp.update(new_timestamp) self.assertEqual(self.dp.timestamp, new_timestamp) self.assertEqual(self.dp.mjd, LSST_START_MJD + (1.0 / 24.0)) self.assertEqual(self.dp.lst_rad, 0.7840316524739084) def test_call_mechanism(self): new_timestamp = LSST_START_TIMESTAMP + (2.0 * 3600.0) (mjd, lst_rad) = self.dp(new_timestamp) self.assertEqual(mjd, LSST_START_MJD + (2.0 / 24.0)) self.assertAlmostEqual(lst_rad, 1.0465478232515026, delta=1e-7) def test_negative_lst(self): new_timestamp = LSST_START_TIMESTAMP + (18.0 * 3600.0) (mjd, lst_rad) = self.dp(new_timestamp) self.assertEqual(mjd, LSST_START_MJD + (18.0 / 24.0)) self.assertAlmostEqual(lst_rad, 5.246806555968448, delta=1e-7) def test_midnight_timestamp(self): new_timestamp = LSST_START_TIMESTAMP + (4.0 * 3600.0) self.dp.update(new_timestamp) self.assertEqual(self.dp.midnight_timestamp(), LSST_START_TIMESTAMP) def test_next_midnight_timestamp(self): new_timestamp = LSST_START_TIMESTAMP + (4.0 * 3600.0) self.dp.update(new_timestamp) self.assertEqual( self.dp.next_midnight_timestamp(), LSST_START_TIMESTAMP + (24.0 * 60.0 * 60.0), ) def test_previous_midnight_timestamp(self): new_timestamp = LSST_START_TIMESTAMP + (4.0 * 3600.0) self.dp.update(new_timestamp) self.assertEqual( self.dp.previous_midnight_timestamp(), LSST_START_TIMESTAMP - (24.0 * 60.0 * 60.0), )
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
class AstronomicalSkyModel(object): def __init__(self, location): """Initialize the class. Parameters ---------- location : `lsst.ts.dateloc.ObservatoryLocation` The instance containing the observatory location information. """ self.log = logging.getLogger("sky_model.AstronomicalSkyModel") self.date_profile = DateProfile(0, location) self.sky_brightness = SkyModelPre() self._sb_nside = self.sky_brightness.nside self.sun = Sun() self.exclude_planets = True def configure(self, exclude_planets): """Add configuration for the sky brightness model. Parameters ---------- exclude_planets : `bool` Flag to mask planets in sky brightness information. """ self.exclude_planets = exclude_planets def get_airmass(self, ra, dec): """Get the airmass of the fields. The field ids stored in the airmass data are off-by-one from the stored field ids, hence the subtraction. Parameters ---------- ra : `numpy.array` The right ascension (radians) of the sky position. dec : `numpy.array` The declination (radians) of the sky position. Returns ------- `numpy.array` The set of airmasses. """ ids = _raDec2Hpid(self._sb_nside, ra, dec) return self.sky_brightness.returnAirmass(self.date_profile.mjd, indx=ids, badval=float("nan")) def get_alt_az(self, ra, dec): """Get the altitude (radians) and azimuth (radians) of a given sky position. Parameters ---------- ra : `numpy.array` The right ascension (radians) of the sky position. dec : `numpy.array` The declination (radians) of the sky position. Returns ------- `tuple` The altitude and azimuth of the sky position. """ hour_angle = self.date_profile.lst_rad - ra azimuth, altitude = palpy.de2hVector( hour_angle, dec, self.date_profile.location.latitude_rad) return altitude, azimuth def get_hour_angle(self, ra): """Get the hour angle (radians) of a sky position. Parameters ---------- ra : `numpy.array` The right ascension (radians) of the sky position. Returns ------- `numpy.array` The hour angle (radians) of the sky position on range of -pi to pi. """ hour_angle = self.date_profile.lst_rad - ra hour_angle = numpy.where(hour_angle < -numpy.pi, hour_angle + (2.0 * numpy.pi), hour_angle) hour_angle = numpy.where(hour_angle > numpy.pi, hour_angle - (2.0 * numpy.pi), hour_angle) return hour_angle def get_moon_sun_info(self, field_ra, field_dec): """Return the current moon and sun information. This function gets the right ascension, declination, altitude, azimuth, phase and angular distance from target (by given ra and dec) for the moon and the right ascension, declination, altitude, azimuth and solar elongation from target (by given ra and dec) for the sun. Parameters ---------- field_ra : `numpy.array` The target right ascension (radians). field_dec : `numpy.array` The target declination (radians). Returns ------- `dict` The set of information pertaining to the moon and sun. All angles are in radians. """ attrs = self.sky_brightness.returnSunMoon(self.date_profile.mjd) moon_distance = self.get_separation("moon", field_ra, field_dec) sun_distance = self.get_separation("sun", field_ra, field_dec) keys = ["moonRA", "moonDec", "sunRA", "sunDec"] info_dict = {} for key in keys: info_dict[key] = attrs[key] # moonSunSep is in degrees! Oops! info_dict["moonPhase"] = attrs["moonSunSep"] / 180.0 * 100.0 info_dict["moonAlt"], info_dict["moonAz"] = self.get_alt_az( numpy.array([attrs["moonRA"]]), numpy.array([attrs["moonDec"]])) info_dict["sunAlt"], info_dict["sunAz"] = self.get_alt_az( numpy.array([attrs["sunRA"]]), numpy.array([attrs["sunDec"]])) info_dict["moonDist"] = moon_distance info_dict["solarElong"] = sun_distance return info_dict def get_night_boundaries(self, sun_altitude, upper_limb_correction=False, precision=6): """Return the set/rise times of the sun for the given altitude. This function calculates the night boundaries (the set and rise times) for a given sun altitude. It uses the currently stored timestamp in the lsst.ts.dateloc.DateProfile instance. Parameters ---------- sun_altitude : `float` The altitude of the sun to get the set/rise times for. upper_limb_correction : `bool` Set to True is the upper limb correction should be calculated. precision : `int`, optional The place to round the rise/set times. Returns ------- `tuple` (`float`, `float`) A tuple of the set and rise times, respectively, for the sun altitude. """ longitude, latitude = ( self.date_profile.location.longitude, self.date_profile.location.latitude, ) current_timestamp = self.date_profile.timestamp midnight_timestamp = self.date_profile.midnight_timestamp() (rise_time, set_time) = self.sun.altitude_times(midnight_timestamp, longitude, latitude, sun_altitude, upper_limb_correction) set_timestamp = midnight_timestamp + ( set_time * self.date_profile.SECONDS_IN_HOUR) rise_timestamp = midnight_timestamp + ( rise_time * self.date_profile.SECONDS_IN_HOUR) if current_timestamp < rise_timestamp: midnight_timestamp = self.date_profile.previous_midnight_timestamp( ) (_, set_time) = self.sun.altitude_times( midnight_timestamp, longitude, latitude, sun_altitude, upper_limb_correction, ) set_timestamp = midnight_timestamp + ( set_time * self.date_profile.SECONDS_IN_HOUR) else: midnight_timestamp = self.date_profile.next_midnight_timestamp() (rise_time, _) = self.sun.altitude_times( midnight_timestamp, longitude, latitude, sun_altitude, upper_limb_correction, ) rise_timestamp = midnight_timestamp + ( rise_time * self.date_profile.SECONDS_IN_HOUR) return (round(set_timestamp, precision), round(rise_timestamp, precision)) def get_separation(self, body, field_ra, field_dec): """Return the separation between a body and a set of field coordinates. This function returns the separation (in radians) between the given body (either moon or sun) and a given set of fields. It uses a list of (RA, Dec) coordinates. This function assumes that :meth:`.get_sky_brightness` has been run. Parameters ---------- body : `str` The name of the body to calculate the separation. Either moon or sun. field_ra : `numpy.array` of `float` The list of field right ascensions in radians. field_dec : `numpy.array` of `float` The list of field declinations in radians. Returns ------- `numpy.array` of `float` The list of field-moon separations in radians. """ attrs = self.sky_brightness.returnSunMoon(self.date_profile.mjd) return palpy.dsepVector( field_ra, field_dec, numpy.full_like(field_ra, attrs["{}RA".format(body)]), numpy.full_like(field_dec, attrs["{}Dec".format(body)]), ) def get_sky_brightness(self, ra, dec, extrapolate=False, override_exclude_planets=None): """Get the LSST 6 filter sky brightness for a set of coordinates at a single time. This function retrieves the LSST 6 filter sky brightness magnitudes for a given set of fields at the MJD kept by the lsst.ts.dateloc.DateProfile. Parameters ---------- ra : `numpy.array` The set of fields RA coordinates to retrieve the sky brightness for. dec : `numpy.array` The set of fields RA coordinates to retrieve the sky brightness for. extrapolate : `boolean`, optional Flag to extrapolate fields with bad sky brightness to nearest field that is good. override_exclude_planets : `boolean`, optional Override the internally stored exclude_planets flag. Returns ------- `numpy.ndarray` The LSST 6 filter sky brightness magnitudes. """ if override_exclude_planets is not None: exclude_planets = override_exclude_planets else: exclude_planets = self.exclude_planets ids = _raDec2Hpid(self._sb_nside, ra, dec) return self.sky_brightness.returnMags( self.date_profile.mjd, indx=ids, badval=float("nan"), zenith_mask=False, planet_mask=exclude_planets, extrapolate=extrapolate, ) def get_sky_brightness_timeblock(self, timestamp, timestep, num_steps, ra, dec): """Get LSST 6 filter sky brightness for a set of fields for a range of times. This function retrieves the LSST 6 filter sky brightness magnitudes for a given set of fields at a range of MJDs provided via the timeblock information. The field ids stored in the sky brightness data are off-by-one from the stored field ids, hence the subtraction. Parameters ---------- timestamp : `float` The UTC timestamp to start the time block. timestep : `float` The number of seconds to increment the timestamp with. num_steps : `int` The number of steps to create for the time block. ra : `numpy.array` The set of fields RA coordinates to retrieve the sky brightness for. dec : `numpy.array` The set of fields RA coordinates to retrieve the sky brightness for. Returns ------- `numpy.ndarray` The LSST 6 filter sky brightness magnitudes. """ dp = DateProfile(0, self.date_profile.location) mags = [] ids = _raDec2Hpid(self._sb_nside, ra, dec) for i in range(num_steps): ts = timestamp + i * timestep mjd, _ = dp(ts) mags.append( self.sky_brightness.returnMags( dp.mjd, indx=ids, badval=float("nan"), zenith_mask=False, planet_mask=self.exclude_planets, extrapolate=False, )) return mags def get_target_information(self, ra, dec): """Get information about target(s). This function gathers airmass, altitude (radians) and azimuth (radians) information for the target. Parameters ---------- ra : `numpy.array` The field right ascension (radians). dec : `numpy.array` The field declination (radians). Returns ------- `dict` Set of information about the target(s). """ info_dict = {} info_dict["airmass"] = self.get_airmass(ra, dec) altitude, azimuth = self.get_alt_az(ra, dec) info_dict["altitude"] = altitude info_dict["azimuth"] = azimuth return info_dict def sky_brightness_config(self): """Get the configuration from the SkyModelPre files. Returns ------- `list` of `tuple` (key, value) """ config = [] header = self.sky_brightness.header config.append( ("sky_brightness_pre/program_version", sky_model_pre_version)) config.append(("sky_brightness_pre/file_version", header["version"])) config.append( ("sky_brightness_pre/fingerprint", header["fingerprint"])) config.append( ("sky_brightness_pre/moon_dist_limit", header["moon_dist_limit"])) config.append(("sky_brightness_pre/planet_dist_limit", header["planet_dist_limit"])) config.append( ("sky_brightness_pre/airmass_limit", header["airmass_limit"])) config.append( ("sky_brightness_pre/timestep", header["timestep"] * 24 * 3600)) config.append(("sky_brightness_pre/timestep_max", header["timestep_max"] * 24 * 3600)) config.append(("sky_brightness_pre/dm", header["dm"])) return config def update(self, timestamp): """Update the internal timestamp. Parameters ---------- timestamp : `float` The UTC timestamp to update the internal timestamp to. """ self.date_profile.update(timestamp) def update_location(self, location): """Update the current location. Parameters ---------- location : `lsst.ts.dateloc.ObservatoryLocation` The instance of an ObservatoryLocation containing the relevant information. """ self.date_profile.location = location