def get_next_observable_target(self, target_list, obs_time, max_time=-1, airmass=(1, 2.2), moon_sep=(30, 180), block_type=''): """ :return: """ # If the target_list is empty then all we can do is return back no # target and do a standard for the time being next_target = False if target_list.empty: return next_target, "" for row in target_list.itertuples(): constraint = astroplan.AirmassConstraint(min=airmass[0], max=airmass[1]) if row.typedesig == 'f': if astroplan.is_observable(constraint, self.obs_site, row.FixedObject, times=[obs_time]): return_dict = {'name': row.objname, 'priority': row.priority} return row.req_id, {**row.obs_dict, **return_dict} return False, False
def get_default_constraints(): """ Defines the default constraints if none are explicitly set. Those are Altitude above 30 deg, airmass less than 1.7, astronomical twilight at the observing location, and at least 45 deg seperation from the moon Returns ------- constraints : list list of constraints """ # Altitude constraints definition altcons = ap.AltitudeConstraint(min=+30 * u.deg, max=None) # Airmass constraints definition airmasscons = ap.AirmassConstraint(min=None, max=1.7) # Astronomical Nighttime constraints definition: # begin and end of each night at paranal as # AtNightConstraint.twilight_astronomical night_cons_per_night = ap.AtNightConstraint.twilight_astronomical() # Moon Constraint mooncons = ap.MoonSeparationConstraint(min=+45 * u.deg, max=None) constraints = [night_cons_per_night, altcons, airmasscons, mooncons] return constraints
def test_transitioner(self): pachon = astroplan.Observer.at_site("Cerro Pachon") start_time = astropy.time.Time(59598.66945625747, format='mjd') readout_time = 2 * 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 ] 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) duration_seconds = (block.duration / u.second).value old_filter = old_block.configuration['filter'] new_filter = new_block.configuration['filter'] if old_filter == new_filter: min_expected_duration = 2 else: min_expected_duration = 120 self.assertGreaterEqual(duration_seconds, min_expected_duration) self.assertLess(duration_seconds, 600)
delta_midnight = np.linspace(-12, 12, 1000) * u.hour times_July12_to_13 = midnight + delta_midnight frame_July12_to_13 = AltAz(obstime=times_July12_to_13, location=paranal_loc) sunaltazs_July12_to_13 = get_sun(times_July12_to_13).transform_to( frame_July12_to_13) GJ9827baltaz = GJ9827b.transform_to(AltAz(obstime=time, location=paranal_loc)) delta_midnight = np.linspace(-2, 10, 100) * u.hour obstime = midnight + delta_midnight paranal = astroplan.Observer(paranal_loc, timezone='Etc/GMT-4') Altcons = astroplan.AltitudeConstraint(min=+30 * u.deg, max=None) Airmasscons = astroplan.AirmassConstraint(min=None, max=3.0) Alt_constraints = Altcons.compute_constraint(times=obstime, observer=paranal, targets=GJ9827b) Airmass_constraints = Airmasscons.compute_constraint(times=obstime, observer=paranal, targets=GJ9827b) observable = astroplan.is_observable(constraints=[Altcons, Airmasscons], observer=paranal, targets=GJ9827b, times=obstime) midnight1 = Time('2020-7-13 00:00:00') - utcoffset midnight2 = Time('2021-1-13 00:00:00') - utcoffset
def schedule_followup(events, fields, config): """Schedule follow-up for a given set of events. Args: - events :: a pandas.DataFrame of events - fields :: a pandas.DataFrame of fields for followup - config :: a ConfigParser with follow-up configuration data. Returns: a pandas.DataFrame with the schedule of follow-up visits. """ readout_time = float(config['instrument']['readout_time']) * u.second shutter_time = float(config['instrument']['shutter_time']) * u.second exptime = float(config['obs_block']['exptime']) * u.second nexp = int(config['obs_block']['nexp']) bands = config['search']['bands'].split() # # Create an astroplan.Observer # observer = astroplan.Observer.at_site(config['site']['name']) # # Create a Transitioner # slew_src = SlewTimeSource() transitioner = apsupp.OpsimTransitioner(slew_src) # # Build astroplan constraints # alt_constraint = astroplan.AltitudeConstraint( float(config['constraints']['min_alt']) * u.deg, float(config['constraints']['max_alt']) * u.deg) airmass_constraint = astroplan.AirmassConstraint( float(config['constraints']['max_airmass'])) constraints = [alt_constraint, airmass_constraint] twilight_type = config['constraints']['twilight'] if twilight_type == 'nautical': constraints.append(astroplan.AtNightConstraint.twilight_nautical()) elif twilight_type == 'civil': constraints.append(astroplan.AtNightConstraint.twilight_civil()) elif twilight_type == 'astronomical': constraints.append(astroplan.AtNightConstraint.twilight_astronomical()) else: raise NotImplementedError # # Create an astroplan.Scheduler # time_resolution = float(config['scheduler']['time_resolution']) * u.second scheduler = DirectScheduler(constraints=constraints, observer=observer, transitioner=transitioner, time_resolution=time_resolution) # helper function to turn targets into blocks def obsblock(target, band): block = astroplan.ObservingBlock.from_exposures( target, 1, exptime, nexp, readout_time, configuration={'filter': band}) block.shutter_time = shutter_time block.duration = block.duration \ + block.number_exposures * block.shutter_time return block # Schedule follow-up for each event latest_visit_end = astropy.time.Time(0, format='mjd') search_area = float(config['search']['area']) scan_schedules = [] for event, targets in followup_targets(events, fields, search_area): event_time = astropy.time.Time(np.min(event.mjd.min()), format='mjd') trigger_time = event_time \ + float(config['search']['trigger_delay'])*u.second info( f'Scheduling event {event.event_id} at {event.ra}, {event.decl} on {event_time.iso}' ) # Only work on one event at a time if trigger_time < latest_visit_end: continue # Attempt the first strategy -- two scans the area in one # band, one hour apart, within 12 hours of the event. # If this isn't possible, two scans with larger separation, # in two bands each. try: obsblocks = [obsblock(t, bands[0]) for t in targets] schedule = two_scan_schedule(scheduler, obsblocks, trigger_time, event_time, obs_window=12 * u.hour, scan_separation=1 * u.hour) except StrategyFailed: obsblocks = [obsblock(t, b) for b in bands for t in targets] schedule = two_scan_schedule(scheduler, obsblocks, trigger_time, event_time, obs_window=24 * u.hour, scan_separation=1 * u.hour, ignore_failure=True) scan_schedules.append(schedule) schedule = pd.concat([s for s in scan_schedules], axis=0) schedule['proposals'] = config['scheduler']['proposals'] return schedule
def test_schedule(self): pachon = astroplan.Observer.at_site("Cerro Pachon") start_time = astropy.time.Time(59598.66945625747, format='mjd') readout_time = 2 * u.second exptime = 15 * u.second nexp = 2 slew_rate = 0.5 * (0.66 + 0.57) * u.deg / u.second filter_change_time = {'filter': {'default': 120 * u.second}} transitioner = astroplan.Transitioner(slew_rate, filter_change_time) 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() ] obsblocks = [ astroplan.ObservingBlock.from_exposures( target, 1, exptime, nexp, readout_time, configuration={'filter': band}, constraints=constraints) for band in ('g', 'i') for target in targets ] scheduler = apsupp.DirectScheduler(constraints=constraints, observer=pachon, transitioner=transitioner, time_resolution=1 * u.hour) schedule = astroplan.scheduling.Schedule(start_time, start_time + 1 * u.day) scheduler(obsblocks, schedule) self.assertIsInstance(schedule.slots[1].block.target, astroplan.FixedTarget) self.assertEqual(schedule.slots[1].block.target.name, '1857') self.assertIsInstance(schedule.slots[2].block, astroplan.TransitionBlock) self.assertIsInstance(schedule.slots[3].block.target, astroplan.FixedTarget) self.assertEqual(schedule.slots[3].block.target.name, '2100') self.assertIsInstance(schedule.slots[4].block, astroplan.TransitionBlock) self.assertIsInstance(schedule.slots[5].block.target, astroplan.FixedTarget) self.assertEqual(schedule.slots[5].block.target.name, '1857') self.assertIsInstance(schedule.slots[6].block, astroplan.TransitionBlock) self.assertIsInstance(schedule.slots[7].block.target, astroplan.FixedTarget) self.assertEqual(schedule.slots[7].block.target.name, '2100')
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)
def __init__( self, name: str, ra: float = None, dec: float = None, arrivaltime: str = None, date: str = None, max_airmass=2.0, observationlength: float = 300, bands: list = ["g", "r"], alertsource: str = None, verbose: bool = True, **kwargs, ): self.name = name self.arrivaltime = arrivaltime self.alertsource = alertsource self.max_airmass = max_airmass self.observationlength = observationlength self.bands = bands self.ra_err = None self.dec_err = None self.warning = None self.observable = True self.rejection_reason = None self.datasource = None self.found_in_archive = False self.search_full_archive = False if ra is None and self.alertsource in icecube: if verbose: print("Parsing an IceCube alert") # Check if request is archival: archive, latest_archive_no = gcn_parser.get_gcn_circulars_archive() # Check if the alert is younger than latest archive entry archival_names = [entry[0] for entry in archive] archival_dates = [int(entry[2:-1]) for entry in archival_names] latest_archival = max(archival_dates) this_alert_date = int(self.name[2:-1]) if this_alert_date > latest_archival: if verbose: print( "Alert too new, no GCN circular available yet. Using latest GCN notice" ) else: if verbose: print("Alert info should be in GCN circular archive") self.search_full_archive = True if self.search_full_archive: self.search_match_in_archive(archive) # Well, if it's not in the latest archive, use the full # backwards search while self.found_in_archive is False: archive, _ = gcn_parser.get_gcn_circulars_archive(latest_archive_no) self.search_match_in_archive(archive) latest_archive_no -= 1 if self.found_in_archive: gcn_info = gcn_parser.parse_gcn_circular(self.gcn_nr) self.ra = gcn_info["ra"] self.ra_err = gcn_info["ra_err"] self.dec = gcn_info["dec"] self.dec_err = gcn_info["dec_err"] self.arrivaltime = gcn_info["time"] else: if verbose: print("No archival GCN circular found. Using newest notice!") ( ra_notice, dec_notice, self.arrivaltime, revision, ) = gcn_parser.parse_latest_gcn_notice() gcn_nr_latest = archive[0][1] gcn_info = gcn_parser.parse_gcn_circular(gcn_nr_latest) ra_circ = gcn_info["ra"] ra_err_circ = gcn_info["ra_err"] dec_circ = gcn_info["dec"] dec_err_circ = gcn_info["dec_err"] coords_notice = SkyCoord( ra_notice * u.deg, dec_notice * u.deg, frame="icrs" ) coords_circular = SkyCoord( ra_circ * u.deg, dec_circ * u.deg, frame="icrs" ) separation = coords_notice.separation(coords_circular).deg if separation < 1: self.ra = ra_circ self.dec = dec_circ self.ra_err = ra_err_circ self.dec_err = dec_err_circ self.datasource = f"GCN Circular {gcn_nr_latest}\n" else: self.ra = ra_notice self.dec = dec_notice self.datasource = f"GCN Notice (Rev. {revision})\n" elif ra is None and self.alertsource in ztf: if is_ztf_name(name): print(f"{name} is a ZTF name. Looking in Fritz database for ra/dec") from ztf_plan_obs.fritzconnector import FritzInfo fritz = FritzInfo([name]) self.ra = fritz.queryresult["ra"] self.dec = fritz.queryresult["dec"] self.datasource = "Fritz\n" if np.isnan(self.ra): raise ValueError("Object apparently not found on Fritz") print("\nFound ZTF object information on Fritz") elif ra is None: raise ValueError("Please enter ra and dec") else: self.ra = ra self.dec = dec self.coordinates = SkyCoord(self.ra * u.deg, self.dec * u.deg, frame="icrs") self.coordinates_galactic = self.coordinates.galactic self.target = ap.FixedTarget(name=self.name, coord=self.coordinates) self.site = Observer.at_site("Palomar", timezone="US/Pacific") #'Roque de los Muchachos' self.now = Time(datetime.utcnow()) self.date = date if self.date is not None: self.start_obswindow = Time(self.date + " 00:00:00.000000") else: self.start_obswindow = Time( str(self.now.datetime.date()) + " 00:00:00.000000" ) self.end_obswindow = Time(self.start_obswindow.mjd + 1, format="mjd").iso constraints = [ ap.AltitudeConstraint(20 * u.deg, 90 * u.deg), ap.AirmassConstraint(max_airmass), ap.AtNightConstraint.twilight_astronomical(), ] # Obtain moon coordinates at Palomar for the full time window times = Time(self.start_obswindow + np.linspace(0, 24, 1000) * u.hour) moon_times = Time(self.start_obswindow + np.linspace(0, 24, 50) * u.hour) moon_coords = [] for time in moon_times: moon_coord = astropy.coordinates.get_moon( time=time, location=self.site.location ) moon_coords.append(moon_coord) self.moon = moon_coords airmass = self.site.altaz(times, self.target).secz airmass = np.ma.array(airmass, mask=airmass < 1) airmass = airmass.filled(fill_value=99) airmass = [x.value for x in airmass] self.twilight_evening = self.site.twilight_evening_astronomical( Time(self.start_obswindow), which="next" ) self.twilight_morning = self.site.twilight_morning_astronomical( Time(self.start_obswindow), which="next" ) indices_included = [] airmasses_included = [] times_included = [] for index, t_mjd in enumerate(times.mjd): if ( t_mjd > self.twilight_evening.mjd + 0.01 and t_mjd < self.twilight_morning.mjd - 0.01 ): if airmass[index] < 2.0: indices_included.append(index) airmasses_included.append(airmass[index]) times_included.append(times[index]) if len(airmasses_included) == 0: self.observable = False self.rejection_reason = "airmass" if np.abs(self.coordinates_galactic.b.deg) < 10: self.observable = False self.rejection_reason = "proximity to gal. plane" self.g_band_recommended_time_start = None self.g_band_recommended_time_end = None self.r_band_recommended_time_start = None self.r_band_recommended_time_end = None if self.observable: min_airmass = np.min(airmasses_included) min_airmass_index = np.argmin(airmasses_included) min_airmass_time = times_included[min_airmass_index] distance_to_evening = min_airmass_time.mjd - self.twilight_evening.mjd distance_to_morning = self.twilight_morning.mjd - min_airmass_time.mjd if distance_to_morning < distance_to_evening: if "g" in self.bands: self.g_band_recommended_time_start = round_time( min_airmass_time - self.observationlength * u.s - 0.5 * u.hour ) self.g_band_recommended_time_end = ( self.g_band_recommended_time_start + self.observationlength * u.s ) if "r" in self.bands: self.r_band_recommended_time_start = round_time( min_airmass_time - self.observationlength * u.s ) self.r_band_recommended_time_end = ( self.r_band_recommended_time_start + self.observationlength * u.s ) else: if "g" in self.bands: self.g_band_recommended_time_start = round_time( min_airmass_time + self.observationlength * u.s + 0.5 * u.hour ) self.g_band_recommended_time_end = ( self.g_band_recommended_time_start + self.observationlength * u.s ) if "r" in self.bands: self.r_band_recommended_time_start = round_time( min_airmass_time + self.observationlength * u.s ) self.r_band_recommended_time_end = ( self.r_band_recommended_time_start + self.observationlength * u.s ) if self.alertsource in icecube: summarytext = f"Name = IceCube-{self.name[2:]}\n" else: summarytext = f"Name = {self.name}\n" if self.ra_err is not None: summarytext += f"RA = {self.coordinates.ra.deg} + {self.ra_err[0]} - {self.ra_err[1]*-1}\nDec = {self.coordinates.dec.deg} + {self.dec_err[0]} - {self.dec_err[1]*-1}\n" else: summarytext += f"RADEC = {self.coordinates.ra.deg:.8f} {self.coordinates.dec.deg:.8f}\n" if self.datasource is not None: summarytext += f"Data source: {self.datasource}" if self.observable: summarytext += ( f"Minimal airmass ({min_airmass:.2f}) at {min_airmass_time}\n" ) summarytext += f"Separation from galactic plane: {self.coordinates_galactic.b.deg:.2f} deg\n" if self.observable: summarytext += "Recommended observation times:\n" if "g" in self.bands: gbandtext = f"g-band: {short_time(self.g_band_recommended_time_start)} - {short_time(self.g_band_recommended_time_end)} [UTC]" if "r" in self.bands: rbandtext = f"r-band: {short_time(self.r_band_recommended_time_start)} - {short_time(self.r_band_recommended_time_end)} [UTC]" if ( "g" in bands and "r" in bands and self.g_band_recommended_time_start < self.r_band_recommended_time_start ): bandtexts = [gbandtext + "\n", rbandtext] elif ( "g" in bands and "r" in bands and self.g_band_recommended_time_start > self.r_band_recommended_time_start ): bandtexts = [rbandtext + "\n", gbandtext] elif "g" in bands and "r" not in bands: bandtexts = [gbandtext] else: bandtexts = [rbandtext] for item in bandtexts: summarytext += item if verbose: print(summarytext) if not os.path.exists(self.name): os.makedirs(self.name) self.summarytext = summarytext