def test_basic_information_after_creation(self):
     downtimeData = ScheduledDowntimeData(
         self.th, start_of_night_offset=self.startofnight)
     self.assertEqual(downtimeData.scheduled_downtime_db, self.downtime_db)
     self.assertEqual(self.th + TimeDelta(self.startofnight, format='jd'),
                      downtimeData.night0)
     downtimeData = ScheduledDowntimeData(self.th, start_of_night_offset=0)
     self.assertEqual(downtimeData.night0, self.th)
 def test_information_after_initialization(self):
     downtimeData = ScheduledDowntimeData(
         self.th, start_of_night_offset=self.startofnight)
     downtimeData.read_data()
     self.assertEqual(len(downtimeData.downtime), 31)
     # Check some of the downtime values.
     dnight = downtimeData.downtime['end'] - downtimeData.downtime['start']
     self.assertEqual(dnight[0].jd, 7)
     self.assertEqual(downtimeData.downtime['activity'][0],
                      'general maintenance')
     self.assertEqual(dnight[4].jd, 14)
     self.assertEqual(downtimeData.downtime['activity'][4], 'recoat mirror')
 def test_call(self):
     # Check the calculation from fwhm_500 to fwhm_eff/fwhm_geom.
     # Use simple effective wavelengths and airmass values.
     downtimeModel = DowntimeModel(self.config)
     t = Time('2022-10-01')
     sched = ScheduledDowntimeData(t)
     sched.read_data()
     unsched = UnscheduledDowntimeData(t)
     unsched.make_data()
     efdData = {'unscheduled_downtimes': unsched(),
                'scheduled_downtimes': sched()}
     # Set time to within first scheduled downtime.
     t_now = sched.downtime[0]['start'] + TimeDelta(0.5, format='jd')
     targetDict = {'test_time': t_now}
     dt_status = downtimeModel(efdData, targetDict)
     # Expect return dict of : {'status': status, 'end': end_down, 'next': next_sched['start']}
     # Check keys
     for k in ('status', 'end', 'next'):
         self.assertTrue(k in dt_status)
     # downtime status is "True" if system is down.
     self.assertEqual(True, dt_status['status'])
     self.assertEqual(dt_status['end'], sched.downtime[0]['end'])
     self.assertEqual(dt_status['next'], sched.downtime[1]['start'])
    def test_alternate_db(self):
        with getTempFilePath('.alt_downtime.db') as tmpdb:
            downtime_table = []
            downtime_table.append("night INTEGER PRIMARY KEY")
            downtime_table.append("duration INTEGER")
            downtime_table.append("activity TEXT")

            with sqlite3.connect(tmpdb) as conn:
                cur = conn.cursor()
                cur.execute("DROP TABLE IF EXISTS Downtime")
                cur.execute("CREATE TABLE Downtime({})".format(
                    ",".join(downtime_table)))
                cur.execute("INSERT INTO Downtime VALUES(?, ?, ?)",
                            (100, 7, "something to do"))
                cur.close()

            downtimeData = ScheduledDowntimeData(
                self.th,
                scheduled_downtime_db=tmpdb,
                start_of_night_offset=self.startofnight)
            downtimeData.read_data()
            self.assertEqual(len(downtimeData.downtime), 1)
            self.assertEqual(downtimeData.downtime['activity'][0],
                             'something to do')
    def change_scheduled_downtime(self, scheduled_downtime_db):
        # Code mostly taken from __init__ of Model_observatory itself
        mjd_start_time = Time(self.mjd_start, format='mjd')
        self.down_nights = []
        self.sched_downtime_data = ScheduledDowntimeData(
            mjd_start_time, scheduled_downtime_db=scheduled_downtime_db)

        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]
Beispiel #6
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
Beispiel #7
0
def _init_periods(config, location):

    logger.debug("Laying down time period boundries")
    period_index = pd.period_range(
        config["start_time"],
        config["end_time"],
        freq=config["time_freq"],
        name="period",
    )
    times = Time(
        pd.to_datetime(period_index.to_timestamp()),  # pylint: disable=no-member
        location=location,
    )

    logger.debug("Calculating solar ephemeris")
    sun_coords = astropy.coordinates.get_sun(times)

    logger.debug("Calculating lunar ephemeris")
    moon_coords = astropy.coordinates.get_moon(times)
    moon_elongation = moon_coords.separation(sun_coords)

    logger.debug("Converting solar and lunar coordinates to horizon system")
    horizon_coordinate_system = astropy.coordinates.AltAz(location=location)
    sun_hzn = sun_coords.transform_to(horizon_coordinate_system)
    moon_hzn = moon_coords.transform_to(horizon_coordinate_system)

    logger.debug("Building the period DataFrame")
    periods = pd.DataFrame(
        {
            "mjd": times.mjd,
            "time": times,
            "lst": times.sidereal_time("mean"),
            "sun_ra": sun_coords.ra.deg,
            "sun_decl": sun_coords.dec.deg,
            "sun_alt": sun_hzn.alt.deg,
            "moon_ra": moon_coords.ra.deg,
            "moon_decl": moon_coords.dec.deg,
            "moon_alt": moon_hzn.alt.deg,
            "moon_elongation": moon_elongation.deg,
        },
        index=period_index,
    )

    periods["moon_waxing"] = (periods["moon_elongation"].shift(-1) >
                              periods["moon_elongation"])

    previous_waxing = periods["moon_waxing"].shift(
        1, fill_value=periods.iloc[0]["moon_waxing"])
    periods["new_moon"] = periods["moon_waxing"] & ~previous_waxing
    periods["lunation"] = periods["new_moon"].cumsum()

    mean_local_solar_jd = 2400000.5 + periods["mjd"] + (location.lon.deg /
                                                        360.0)
    night_local_solar_jd = np.floor(mean_local_solar_jd).astype(int)
    periods["night"] = night_local_solar_jd - np.min(night_local_solar_jd) + 1

    # Mark daytime periods
    periods["observing"] = periods.sun_alt <= config["max_sun_alt_deg"]

    # Mark scheduled downtime
    periods.reset_index(inplace=True)
    periods.set_index("mjd", inplace=True)
    for down_time in ScheduledDowntimeData(times.min())():
        periods.loc[down_time["start"].mjd:down_time["end"].mjd,
                    "observing"] = False
    periods.reset_index(inplace=True)
    periods.set_index("period", drop=False, inplace=True)
    return periods
Beispiel #8
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
 def test_call(self):
     downtimeData = ScheduledDowntimeData(
         self.th, start_of_night_offset=self.startofnight)
     downtimeData.read_data()
     downtimes = downtimeData()
     self.assertEqual(downtimes['activity'][4], 'recoat mirror')