def __init__(self, time_range, targets, site='cfht', constraints=None, supp_cols=None): # Get infos from the MasterFile if isinstance(targets, list): info = load_from_masterfile(*targets) supp_cols = supp_cols or [ 'pl_orbper', 'st_j', 'st_h', 'ra', 'dec', 'pl_eqt', 'st_teff' ] else: info = targets.copy() if supp_cols is None: supp_cols = list(info.keys()) supp_cols.remove('pl_name') # Default constraint if constraints is None: constraints = [ AtNightConstraint.twilight_nautical(), AirmassConstraint(max=2.5) ] # Define time constraint and append it t1, t2 = Time(time_range) constraints.append(TimeConstraint(t1, t2)) # Convert to List_of_constraints (useful to print and save) constraints_list = List_of_constraints(constraints) # Save infos self.info = info self.constraints = constraints_list self.meta = { 'Time_limits': [t1, t2], 'Target_list': info['pl_name'].tolist(), 'Site': site, **constraints_list.show() } self.supp_cols = supp_cols self.info_cols = ['pl_name'] + supp_cols self.obs = Observer.at_site(site) # self.n_eclipses = n_eclipses # Resolve targets self.targets = [self.resolve_target(i) for i in range(len(targets))]
def get_event_observability( eventclass, site, ra, dec, name, t_mid_0, period, duration, n_transits=100, obs_start_time=Time(dt.datetime.today().isoformat()), min_altitude = None, oot_duration = 30*u.minute, minokmoonsep = 30*u.deg, max_airmass = None, twilight_limit = 'nautical'): """ note: barycentric corrections not yet implemented. (could do this myself!) -> 16 minutes of imprecision is baked into this observability calculator! args: eventclass: e.g., "OIBE". Function does NOT return longer events. site (astroplan.observer.Observer) ra, dec (units u.deg), e.g.: ra=101.28715533*u.deg, dec=16.71611586*u.deg, or can also accept ra="17 56 35.51", dec="-29 32 21.5" name (str), e.g., "Sirius" t_mid_0 (float): in BJD_TDB, preferably (but see note above). period (astropy quantity, units time) duration (astropy quantity, units time) n_transits (int): number of transits forward extrapolated to obs_start_time (astropy.Time object): when to start calculation from min_altitude (astropy quantity, units deg): 20 degrees is the more relevant constraint. max_airmass: e.g., 2.5. One of max_airmass or min_altitude is required. oot_duration (astropy quantity, units time): with which to brack transit observations, to get an OOT baseline. twilight_limit: 'astronomical', 'nautical', 'civil' for -18, -12, -6 deg. """ if eventclass not in [ 'OIBEO', 'OIBE', 'IBEO', 'IBE', 'BEO', 'OIB', 'OI', 'EO' ]: raise AssertionError if (isinstance(ra, u.quantity.Quantity) and isinstance(dec, u.quantity.Quantity) ): target_coord = SkyCoord(ra=ra, dec=dec) elif (isinstance(ra, str) and isinstance(dec, str) ): target_coord = SkyCoord(ra=ra, dec=dec, unit=(u.hourangle, u.deg)) else: raise NotImplementedError if ( not isinstance(max_airmass, float) or isinstance(min_altitude, u.quantity.Quantity) ): raise NotImplementedError target = FixedTarget(coord=target_coord, name=name) primary_eclipse_time = Time(t_mid_0, format='jd') system = EclipsingSystem(primary_eclipse_time=primary_eclipse_time, orbital_period=period, duration=duration, name=name) midtransit_times = system.next_primary_eclipse_time( obs_start_time, n_eclipses=n_transits) # for the time being, omit any local time constraints. if twilight_limit == 'astronomical': twilight_constraint = AtNightConstraint.twilight_astronomical() elif twilight_limit == 'nautical': twilight_constraint = AtNightConstraint.twilight_nautical() else: raise NotImplementedError('civil twilight is janky.') constraints = [twilight_constraint, AltitudeConstraint(min=min_altitude), AirmassConstraint(max=max_airmass), MoonSeparationConstraint(min=minokmoonsep)] # tabulate ingress and egress times. ing_egr = system.next_primary_ingress_egress_time( obs_start_time, n_eclipses=n_transits ) oibeo_window = np.concatenate( (np.array(ing_egr[:,0] - oot_duration)[:,None], np.array(ing_egr[:,1] + oot_duration)[:,None]), axis=1) oibe_window = np.concatenate( (np.array(ing_egr[:,0] - oot_duration)[:,None], np.array(ing_egr[:,1])[:,None]), axis=1) ibeo_window = np.concatenate( (np.array(ing_egr[:,0])[:,None], np.array(ing_egr[:,1] + oot_duration)[:,None]), axis=1) oib_window = np.concatenate( (np.array(ing_egr[:,0] - oot_duration)[:,None], np.array(midtransit_times)[:,None]), axis=1) beo_window = np.concatenate( (np.array(midtransit_times)[:,None], np.array(ing_egr[:,1] + oot_duration)[:,None]), axis=1) ibe_window = ing_egr oi_window = np.concatenate( (np.array(ing_egr[:,0] - oot_duration)[:,None], np.array(ing_egr[:,0])[:,None]), axis=1) eo_window = np.concatenate( (np.array(ing_egr[:,1])[:,None], np.array(ing_egr[:,1] + oot_duration)[:,None]), axis=1) keys = ['oibeo','oibe','ibeo','oib','beo','ibe','oi','eo'] windows = [oibeo_window, oibe_window, ibeo_window, oib_window, beo_window, ibe_window, oi_window, eo_window] is_obs_dict = {} for key, window in zip(keys, windows): is_obs_dict[key] = np.array( is_event_observable(constraints, site, target, times_ingress_egress=window) ).flatten() is_obs_df = pd.DataFrame(is_obs_dict) is_obs_df['ing'] = ing_egr[:,0] is_obs_df['egr'] = ing_egr[:,1] is_obs_df['isoing'] = Time(ing_egr[:,0], format='iso') is_obs_df['isoegr'] = Time(ing_egr[:,1], format='iso') # this function returns the observable events that are LONGEST. e.g., # during an OIBEO transit you COULD observe just OIB, but why would you? if eventclass == 'OIBEO': event_ind = np.array(is_obs_df[eventclass.lower()])[None,:] elif eventclass in ['IBEO', 'OIBE']: event_ind = np.array( is_obs_df[eventclass.lower()] & ~is_obs_df['oibeo'] )[None,:] elif eventclass in ['IBE', 'OIB', 'BEO']: event_ind = np.array( is_obs_df[eventclass.lower()] & ~is_obs_df['oibeo'] & ~is_obs_df['oibe'] & ~is_obs_df['ibeo'] )[None,:] elif eventclass in ['OI', 'EO']: event_ind = np.array( is_obs_df[eventclass.lower()] & ~is_obs_df['oibeo'] & ~is_obs_df['oibe'] & ~is_obs_df['ibeo'] & ~is_obs_df['oib'] & ~is_obs_df['ibe'] & ~is_obs_df['beo'] )[None,:] # get moon separation over each transit. take minimum moon sep at # ing/tmid/egr as the moon separation. moon_tmid = get_moon(midtransit_times, location=site.location) moon_separation_tmid = moon_tmid.separation(target_coord) moon_ing = get_moon(ing_egr[:,0], location=site.location) moon_separation_ing = moon_ing.separation(target_coord) moon_egr = get_moon(ing_egr[:,1], location=site.location) moon_separation_egr = moon_egr.separation(target_coord) moon_separation = np.round(np.array( [moon_separation_tmid, moon_separation_ing, moon_separation_egr]).min(axis=0),0).astype(int) moon_illumination = np.round( 100*moon.moon_illumination(midtransit_times),0).astype(int) # completely observable transits (OOT, ingress, bottom, egress, OOT) oibeo = is_event_observable(constraints, site, target, times_ingress_egress=oibeo_window) ing_tmid_egr = np.concatenate( (np.array(ing_egr[:,0])[:,None], np.array(midtransit_times)[:,None], np.array(ing_egr[:,1])[:,None]), axis=1) target_window = np.array(windows)[ int(np.argwhere(np.array(keys)==eventclass.lower())), :, : ] return ( event_ind, oibeo, ing_tmid_egr, target_window, moon_separation, moon_illumination )
def __init__(self, t0=None, period=None, perr=None, duration=None, loc='Siding Spring Observatory', timezone='Australia/NSW', ra=None, dec=None, startdate=None, starttime='0:00', run_length=180, el_limit=30, toi=None): if toi is not None: self.set_params_from_toi(toi) else: self.epoch = Time(t0 + 2457000, format='jd') self.period = period * u.d if perr is not None: self.period_err = perr * u.d else: self.period_err = None self.duration = duration / 24 * u.d self.coords = [ra * u.deg, dec * u.deg] self.alt_limit = el_limit self.observatory = loc self.timezone = timezone self.obs_mid_times_utc = None self.obs_mid_uncerts = None self.obs_mid_times_local = None coord = SkyCoord(ra=self.coords[0], dec=self.coords[1]) target = FixedTarget(coord, name='Target') SSO = Observer.at_site(self.observatory, timezone=self.timezone) planet = EclipsingSystem(primary_eclipse_time=self.epoch, orbital_period=self.period, duration=self.duration) starttime = startdate + ' ' + starttime self.start = Time(starttime) self.run_length = run_length * u.d n_transits = np.ceil(self.run_length / self.period) self.all_transit_times = planet.next_primary_eclipse_time( self.start, n_eclipses=n_transits) diff = self.all_transit_times - self.start - self.run_length real_trans = diff.value < 0 if np.sum(real_trans) < 0.5: self.all_transit_times = None return else: self.all_transit_times = self.all_transit_times[real_trans] if self.period_err is not None: self.uncert_vals = (self.all_transit_times - self.epoch ) * self.period_err / self.period * 1440 constraints = [ AtNightConstraint.twilight_nautical(), AltitudeConstraint(min=self.alt_limit * u.deg) ] obs_mid = is_event_observable(constraints, SSO, target, times=self.all_transit_times)[0] obs_start = is_event_observable(constraints, SSO, target, times=self.all_transit_times - 0.5 * self.duration)[0] obs_end = is_event_observable(constraints, SSO, target, times=self.all_transit_times + 0.5 * self.duration)[0] if np.sum(obs_mid * obs_start * obs_end) > 0: self.obs_airmass = np.zeros( (np.sum(obs_mid * obs_start * obs_end), 3)) self.obs_mid_times_utc = self.all_transit_times[obs_mid * obs_start * obs_end] if self.period_err is not None: self.obs_mid_uncerts = self.uncert_vals[obs_mid * obs_start * obs_end] for i in range(np.sum(obs_mid * obs_start * obs_end)): obs_airmass_mid = SSO.altaz(self.obs_mid_times_utc[i], coord).secz obs_airmass_start = SSO.altaz( self.obs_mid_times_utc[i] - self.duration / 2, coord).secz obs_airmass_end = SSO.altaz( self.obs_mid_times_utc[i] + self.duration / 2, coord).secz self.obs_airmass[i] = [ obs_airmass_start, obs_airmass_mid, obs_airmass_end ] # NEED A BARYCENTRIC CORRECTION tz = pytz.timezone('utc') dtime = self.obs_mid_times_utc.to_datetime() self.obs_mid_times_local = np.array([]) for i in range(len(self.obs_mid_times_utc)): inoz = tz.localize(dtime[i]) if self.period_err is not None: indiv_uncert = self.obs_mid_uncerts[i] val = inoz.astimezone(pytz.timezone(self.timezone)) self.obs_mid_times_local = np.append( self.obs_mid_times_local, val.strftime('%Y-%m-%d %H:%M:%S'))
def observability_objects(data): """ Test the observability of a list of objects for a single date Parameters ---------- data : POST data format data = { 'observatory' : 'OT', 'altitude_lower_limit' : '30', 'altitude_higher_limit' : '90', 'objects' : [{ 'name' : 'Kelt 8b', 'RA' : 283.30551667 , 'Dec' : 24.12738139, 'dates' : [ ['2020-06-11 00:16:30', '2020-06-11 03:44:26'], ['2020-06-14 06:07:56', '2020-06-14 09:35:53'] ] }, { 'name' : 'TIC 123456789', 'RA' : 13.13055667 , 'Dec' : 24.13912738, 'dates' : [ ['2020-06-11 23:59:59'] ] } ] } Returns ------- observability : dict Dictionary with the observability and moon distance for all objects {'V0879 Cas' : { 'observability' : 'True', 'moon_separation' : 30.4 }, 'RU Scl' : { 'observability' : 'True', 'moon_separation' : 10.8 } } """ import astropy.units as u from astroplan import FixedTarget from astroplan import (AltitudeConstraint, AtNightConstraint) from astroplan import is_observable, is_always_observable # Site location location = get_location(data['observatory']) # dict of observability for each target observabilities = {} if 'twilight_type' not in data.keys(): data['twilight_type'] = 'astronomical' if data['twilight_type'] == 'civil': twilight_constraint = AtNightConstraint.twilight_civil() elif data['twilight_type'] == 'nautical': twilight_constraint = AtNightConstraint.twilight_nautical() else: twilight_constraint = AtNightConstraint.twilight_astronomical() # Observation constraints constraints = [ AltitudeConstraint( float(data['altitude_lower_limit']) * u.deg, float(data['altitude_higher_limit']) * u.deg), twilight_constraint ] for target in data['objects']: coords = SkyCoord(ra=target['RA'] * u.deg, dec=target['Dec'] * u.deg) fixed_target = [FixedTarget(coord=coords, name=target['name'])] observabilities[target['name']] = [] for date in target['dates']: # time range for transits # Always observable for time range if len(date) > 1: # If exoplanet transit, test observability always during transit, # if not, test observability *ever* during night time_range = Time([date[0], date[1]]) # Are targets *always* observable in the time range? observable = is_always_observable(constraints, location, fixed_target, time_range=time_range) # No time range, *ever* observabable during the night # Observability is test from sunset to sunrise # Default time resolution is 0.5h else: sunset = location.sun_set_time(Time(date[0])) sunrise = location.sun_rise_time(Time(date[0]), 'next') time_range = Time([sunset, sunrise]) observable = is_observable(constraints, location, fixed_target, time_range=time_range) # Moon location for the observation date moon = location.moon_altaz(Time(date[0])) moon_separation = moon.separation(coords) observabilities[target['name']].append({ 'observable': str(observable[0]), 'moon_separation': moon_separation.degree }) return observabilities
def observability_dates(data): """ Test the observability of a single objects for several nights If the first element of 'dates' contains a single date, then the observability is test as *ever* for the night. If a time range is given, observability is test as *always* for the time range Parameters ---------- data : POST data format # data for transiting planet, time range constrained data = { 'name' : 'Kelt 8b', 'RA' : 283.30551667 , 'Dec' : 24.12738139, 'observatory' : 'OT', 'altitude_lower_limit' : '30', 'altitude_higher_limit' : '90', 'twilight_type' : 'astronomical', 'dates' : [ ['2020-06-11 00:16:30', '2020-06-11 03:44:26'], ['2020-06-14 06:07:56', '2020-06-14 09:35:53'] ] } # data for ordinary target, twilight constrained # single date list data = { 'name' : 'KIC8012732', 'RA' : 284.72949583 , 'Dec' : 43.86421667, 'observatory' : 'OT', 'altitude_lower_limit' : '30', 'altitude_higher_limit' : '90', 'dates' : [ ['2020-06-11 23:00:00'] ] } Returns ------- observability : dict Dictionary with the observability and moon distance for all objects {'V0879 Cas' : { 'observability' : 'True', 'moon_separation' : 30.4 }, 'RU Scl' : { 'observability' : 'True', 'moon_separation' : 10.8 } } """ import astropy.units as u from astroplan import FixedTarget from astroplan import (AltitudeConstraint, AtNightConstraint) from astroplan import is_observable, is_always_observable # Site location location = get_location(data['observatory']) coords = SkyCoord(ra=data['RA'] * u.deg, dec=data['Dec'] * u.deg) fixed_target = [FixedTarget(coord=coords, name=data['name'])] # List of dates of observability observabilities = [] if 'twilight_type' not in data.keys(): data['twilight_type'] = 'astronomical' if data['twilight_type'] == 'civil': twilight_constraint = AtNightConstraint.twilight_civil() elif data['twilight_type'] == 'nautical': twilight_constraint = AtNightConstraint.twilight_nautical() else: twilight_constraint = AtNightConstraint.twilight_astronomical() # Observation constraints constraints = [ AltitudeConstraint( float(data['altitude_lower_limit']) * u.deg, float(data['altitude_higher_limit']) * u.deg), twilight_constraint ] for date in data['dates']: # time range for transits # Always observable for time range if len(data['dates'][0]) > 0: # If exoplanet transits, check for observability always during transit, # if not, check observability *ever* during night time_range = Time([date[0], date[1]]) # Are targets *always* observable in the time range? observable = is_always_observable(constraints, location, fixed_target, time_range=time_range) # No time range, *ever* observabable during the night else: observable = is_observable(constraints, location, fixed_target, times=Time(date[0])) # Moon location for the observation date moon = location.moon_altaz(Time(date[0])) moon_separation = moon.separation(coords) observabilities.append({ 'observable': str(observable[0]), 'moon_separation': moon_separation.degree }) return observabilities
def observability(data): """ Test the observability of a list of objects for a single date Parameters ---------- data : POST data format Observatory, date, limits and objects data = { 'observatory' : 'OT', 'date' : '2020-06-11 00:16:30', 'date_end' : '2020-06-11 03:44:26', 'altitude_lower_limit' : '30', 'altitude_higher_limit' : '90', 'twilight_type' : 'astronomical', 'objects' : [{ 'name' : 'Kelt 8b', 'RA' : 283.30551667 , 'Dec' : 24.12738139 }, (more objects...) ] } Returns ------- observability : dict Dictionary with the observability and moon distance for all objects { 'V0879 Cas' : { 'observability' : 'True', 'moon_separation' : 30.4 }, 'RU Scl' : { 'observability' : 'True', 'moon_separation' : 10.8 } } """ import astropy.units as u from astroplan import FixedTarget from astroplan import (AltitudeConstraint, AtNightConstraint) from astroplan import is_observable, is_always_observable # Site location location = get_location(data['observatory']) time_range = Time([data['date'], data['date_end']]) if 'twilight_type' not in data.keys(): data['twilight_type'] = 'astronomical' if data['twilight_type'] == 'civil': twilight_constraint = AtNightConstraint.twilight_civil() elif data['twilight_type'] == 'nautical': twilight_constraint = AtNightConstraint.twilight_nautical() else: twilight_constraint = AtNightConstraint.twilight_astronomical() # Observation constraints constraints = [ AltitudeConstraint( int(data['altitude_lower_limit']) * u.deg, int(data['altitude_higher_limit']) * u.deg), twilight_constraint ] # Dictionary with star name and observability (bool str) result = {} # Moon location for the observation date middle_observing_time = time_range[-1] - (time_range[-1] - time_range[0]) / 2 moon = location.moon_altaz(middle_observing_time) for target in data['objects']: # Object coordinates coords = SkyCoord(ra=target['RA'] * u.deg, dec=target['Dec'] * u.deg) fixed_target = [FixedTarget(coord=coords, name=target['name'])] if 'transit' in target.keys(): time_range = Time( [target['transit']['t_early'], target['transit']['t_late']]) # Are targets *always* observable in the time range? observable = is_always_observable(constraints, location, fixed_target, time_range=time_range) else: time_range = Time([data['date'], data['date_end']]) # Are targets *ever* observable in the time range? observable = is_observable(constraints, location, fixed_target, time_range=time_range) moon_separation = moon.separation(coords) result[target['name']] = { 'observable': str(observable[0]), 'moon_separation': moon_separation.degree } return result
params.w = 90 params.ecc = 0 params.a = float(((G * M_star * (params.per * u.day)**2) / (4 * np.pi**2))**(1 / 3) / R_star) from astroplan import time_grid_from_range, observability_table n_objects_per_night = int(sys.argv[-1]) print(n_objects_per_night) airmass_cutoff = 3.5 fraction_cloudy = 0.3 n_years = 1 n_trials = 15 constraints = [ AtNightConstraint.twilight_nautical(), AirmassConstraint(max=airmass_cutoff) ] start_time = Time('2020-01-01 08:00') # near local midnight end_time = Time('2021-01-01 08:00') # near local midnight n_transits = [] for trial in range(n_trials): target_inds_observed = set([]) obs_database = { name: dict(times=[], fluxes=[], model=[], transit=False) for name in table['spc'] }
def create_observation_observables(object_id, object_dir, since, name, epoch, epoch_low_err, epoch_up_err, period, period_low_err, period_up_err, duration, observatories_file, timezone, latitude, longitude, altitude, max_days, min_altitude, moon_min_dist, moon_max_dist, transit_fraction, baseline, error_alert=True): """ @param object_id: the candidate id @param object_dir: the candidate directory @param since: starting plan date @param name: the name given to the candidate @param epoch: the candidate epoch @param epoch_low_err: the candidate epoch's lower error @param epoch_up_err: the candidate epoch's upper error @param period: the candidate period @param period_low_err: the candidate period's lower error @param period_up_err: the candidate period's upper error @param duration: the candidate duration @param observatories_file: the file containing the observatories file (csv format) @param timezone: the timezone of the observatory (if observatories_file=None) @param latitude: the latitude of the observatory (if observatories_file=None) @param longitude: the longitude of the observatory (if observatories_file=None) @param altitude: the altitude of the observatory (if observatories_file=None) @param max_days: the maximum number of days to compute the observables @param min_altitude: the minimum altitude of the target above the horizon @param moon_min_dist: the minimum moon distance for moon illumination = 0 @param moon_max_dist: the minimum moon distance for moon illumination = 1 @param transit_fraction: the minimum transit observability (0.25 for at least ingress/egress, 0.5 for ingress/egress + midtime, 1 for ingress, egress and midtime). @param baseline: the required baseline in hours. @param: error_alert: whether to create the alert date to signal imprecise observations @return: the generated data and target folders """ if observatories_file is not None: observatories_df = pd.read_csv(observatories_file, comment='#') else: observatories_df = pd.DataFrame( columns=['name', 'tz', 'lat', 'long', 'alt']) observatories_df = observatories_df.append("Obs-1", timezone, latitude, longitude, altitude) # TODO probably convert epoch to proper JD mission, mission_prefix, id_int = LcBuilder().parse_object_info(object_id) if mission == "TESS": primary_eclipse_time = Time(epoch, format='btjd', scale="tdb") elif mission == "Kepler" or mission == "K2": primary_eclipse_time = Time(epoch, format='bkjd', scale="tdb") else: primary_eclipse_time = Time(epoch, format='jd') target = FixedTarget(SkyCoord(coords, unit=(u.deg, u.deg))) n_transits = int(max_days // period) system = EclipsingSystem(primary_eclipse_time=primary_eclipse_time, orbital_period=u.Quantity(period, unit="d"), duration=u.Quantity(duration, unit="h"), name=name) observables_df = pd.DataFrame(columns=[ 'observatory', 'timezone', 'start_obs', 'end_obs', 'ingress', 'egress', 'midtime', "midtime_up_err_h", "midtime_low_err_h", 'twilight_evening', 'twilight_morning', 'observable', 'moon_phase', 'moon_dist' ]) plan_dir = object_dir + "/plan" images_dir = plan_dir + "/images" if os.path.exists(plan_dir): shutil.rmtree(plan_dir, ignore_errors=True) os.mkdir(plan_dir) if os.path.exists(images_dir): shutil.rmtree(images_dir, ignore_errors=True) os.mkdir(images_dir) alert_date = None for index, observatory_row in observatories_df.iterrows(): observer_site = Observer(latitude=observatory_row["lat"], longitude=observatory_row["lon"], elevation=u.Quantity(observatory_row["alt"], unit="m")) midtransit_times = system.next_primary_eclipse_time( since, n_eclipses=n_transits) ingress_egress_times = system.next_primary_ingress_egress_time( since, n_eclipses=n_transits) constraints = [ AtNightConstraint.twilight_nautical(), AltitudeConstraint(min=min_altitude * u.deg), MoonIlluminationSeparationConstraint( min_dist=moon_min_dist * u.deg, max_dist=moon_max_dist * u.deg) ] moon_for_midtransit_times = get_moon(midtransit_times) moon_dist_midtransit_times = moon_for_midtransit_times.separation( SkyCoord(star_df.iloc[0]["ra"], star_df.iloc[0]["dec"], unit="deg")) moon_phase_midtransit_times = np.round( astroplan.moon_illumination(midtransit_times), 2) transits_since_epoch = np.round( (midtransit_times - primary_eclipse_time).jd / period) midtransit_time_low_err = np.round( (((transits_since_epoch * period_low_err)**2 + epoch_low_err**2) **(1 / 2)) * 24, 2) midtransit_time_up_err = np.round( (((transits_since_epoch * period_up_err)**2 + epoch_up_err**2) **(1 / 2)) * 24, 2) low_err_delta = TimeDelta(midtransit_time_low_err * 3600, format='sec') up_err_delta = TimeDelta(midtransit_time_up_err * 3600, format='sec') i = 0 for midtransit_time in midtransit_times: twilight_evening = observer_site.twilight_evening_nautical( midtransit_time) twilight_morning = observer_site.twilight_morning_nautical( midtransit_time) ingress = ingress_egress_times[i][0] egress = ingress_egress_times[i][1] lowest_ingress = ingress - low_err_delta[i] highest_egress = egress + up_err_delta[i] if error_alert and (highest_egress - lowest_ingress).jd > 0.33: alert_date = midtransit_time if (alert_date is None) or ( alert_date is not None and alert_date >= midtransit_time) else alert_date break else: baseline_low = lowest_ingress - baseline * u.hour baseline_up = highest_egress + baseline * u.hour transit_times = baseline_low + ( baseline_up - baseline_low) * np.linspace(0, 1, 100) observable_transit_times = astroplan.is_event_observable( constraints, observer_site, target, times=transit_times)[0] observable_transit_times_true = np.argwhere( observable_transit_times) observable = len(observable_transit_times_true) / 100 if observable < transit_fraction: i = i + 1 continue start_obs = transit_times[observable_transit_times_true[0]][0] end_obs = transit_times[observable_transit_times_true[ len(observable_transit_times_true) - 1]][0] start_plot = baseline_low end_plot = baseline_up if twilight_evening > start_obs: start_obs = twilight_evening if twilight_morning < end_obs: end_obs = twilight_morning moon_dist = round(moon_dist_midtransit_times[i].degree) moon_phase = moon_phase_midtransit_times[i] # TODO get is_event_observable for several parts of the transit (ideally each 5 mins) to get the proper observable percent. Also with baseline if observatory_row["tz"] is not None and not np.isnan( observatory_row["tz"]): observer_timezone = observatory_row["tz"] else: observer_timezone = get_offset(observatory_row["lat"], observatory_row["lon"], midtransit_time.datetime) observables_df = observables_df.append( { "observatory": observatory_row["name"], "timezone": observer_timezone, "ingress": ingress.isot, "start_obs": start_obs.isot, "end_obs": end_obs.isot, "egress": egress.isot, "midtime": midtransit_time.isot, "midtime_up_err_h": str(int(midtransit_time_up_err[i] // 1)) + ":" + str(int(midtransit_time_up_err[i] % 1 * 60)).zfill(2), "midtime_low_err_h": str(int(midtransit_time_low_err[i] // 1)) + ":" + str(int(midtransit_time_low_err[i] % 1 * 60)).zfill(2), "twilight_evening": twilight_evening.isot, "twilight_morning": twilight_morning.isot, "observable": observable, "moon_phase": moon_phase, "moon_dist": moon_dist }, ignore_index=True) plot_time = start_plot + (end_plot - start_plot) * np.linspace( 0, 1, 100) plt.tick_params(labelsize=6) airmass_ax = plot_airmass(target, observer_site, plot_time, brightness_shading=False, altitude_yaxis=True) airmass_ax.axvspan(twilight_morning.plot_date, end_plot.plot_date, color='white') airmass_ax.axvspan(start_plot.plot_date, twilight_evening.plot_date, color='white') airmass_ax.axvspan(twilight_evening.plot_date, twilight_morning.plot_date, color='gray') airmass_ax.axhspan(1. / np.cos(np.radians(90 - min_altitude)), 5.0, color='green') airmass_ax.get_figure().gca().set_title("") airmass_ax.get_figure().gca().set_xlabel("") airmass_ax.get_figure().gca().set_ylabel("") airmass_ax.set_xlabel("") airmass_ax.set_ylabel("") xticks = [] xticks_labels = [] xticks.append(start_obs.plot_date) hour_min_sec_arr = start_obs.isot.split("T")[1].split(":") xticks_labels.append("T1_" + hour_min_sec_arr[0] + ":" + hour_min_sec_arr[1]) plt.axvline(x=start_obs.plot_date, color="violet") xticks.append(end_obs.plot_date) hour_min_sec_arr = end_obs.isot.split("T")[1].split(":") xticks_labels.append("T1_" + hour_min_sec_arr[0] + ":" + hour_min_sec_arr[1]) plt.axvline(x=end_obs.plot_date, color="violet") if start_plot < lowest_ingress < end_plot: xticks.append(lowest_ingress.plot_date) hour_min_sec_arr = lowest_ingress.isot.split("T")[1].split(":") xticks_labels.append("T1_" + hour_min_sec_arr[0] + ":" + hour_min_sec_arr[1]) plt.axvline(x=lowest_ingress.plot_date, color="red") if start_plot < ingress < end_plot: xticks.append(ingress.plot_date) hour_min_sec_arr = ingress.isot.split("T")[1].split(":") xticks_labels.append("T1_" + hour_min_sec_arr[0] + ":" + hour_min_sec_arr[1]) plt.axvline(x=ingress.plot_date, color="orange") if start_plot < midtransit_time < end_plot: xticks.append(midtransit_time.plot_date) hour_min_sec_arr = midtransit_time.isot.split("T")[1].split( ":") xticks_labels.append("T0_" + hour_min_sec_arr[0] + ":" + hour_min_sec_arr[1]) plt.axvline(x=midtransit_time.plot_date, color="black") if start_plot < egress < end_plot: xticks.append(egress.plot_date) hour_min_sec_arr = egress.isot.split("T")[1].split(":") xticks_labels.append("T4_" + hour_min_sec_arr[0] + ":" + hour_min_sec_arr[1]) plt.axvline(x=egress.plot_date, color="orange") if start_plot < highest_egress < end_plot: xticks.append(highest_egress.plot_date) hour_min_sec_arr = highest_egress.isot.split("T")[1].split(":") xticks_labels.append("T4_" + hour_min_sec_arr[0] + ":" + hour_min_sec_arr[1]) plt.axvline(x=highest_egress.plot_date, color="red") airmass_ax.xaxis.set_tick_params(labelsize=5) airmass_ax.set_xticks([]) airmass_ax.set_xticklabels([]) degrees_ax = get_twin(airmass_ax) degrees_ax.yaxis.set_tick_params(labelsize=6) degrees_ax.set_yticks([1., 1.55572383, 2.]) degrees_ax.set_yticklabels([90, 50, 30]) fig = matplotlib.pyplot.gcf() fig.set_size_inches(1.25, 0.75) plt.savefig(plan_dir + "/images/" + observatory_row["name"] + "_" + str(midtransit_time.isot)[:-4] + ".png", bbox_inches='tight') plt.close() i = i + 1 observables_df = observables_df.sort_values(["midtime", "observatory"], ascending=True) observables_df.to_csv(plan_dir + "/observation_plan.csv", index=False) print("Observation plan created in directory: " + object_dir) return observatories_df, observables_df, alert_date, plan_dir, images_dir
async def _schedule(self): """Actually do the scheduling, usually run in a separate process.""" # only global constraint is the night if self._twilight == "astronomical": constraints = [AtNightConstraint.twilight_astronomical()] elif self._twilight == "nautical": constraints = [AtNightConstraint.twilight_nautical()] else: raise ValueError("Unknown twilight type.") # make shallow copies of all blocks and loop them copied_blocks = [copy.copy(block) for block in self._blocks] for block in copied_blocks: # astroplan's PriorityScheduler expects lower priorities to be more important, so calculate # 1000 - priority block.priority = 1000.0 - block.priority if block.priority < 0: block.priority = 0 # it also doesn't match the requested observing windows exactly, so we make them a little smaller. for constraint in block.constraints: if isinstance(constraint, TimeConstraint): constraint.min += 30 * u.second constraint.max -= 30 * u.second # get start time for scheduler start = self._schedule_start now_plus_safety = Time.now() + self._safety_time * u.second if start is None or start < now_plus_safety: # if no ETA exists or is in the past, use safety time start = now_plus_safety # get running scheduled block, if any if self._current_task_id is None: log.info("No running block found.") running_task = None else: # get running task from archive log.info("Trying to find running block in current schedule...") now = Time.now() tasks = await self._task_archive.get_pending_tasks( now, now, include_running=True) if self._current_task_id in tasks: running_task = tasks[self._current_task_id] else: log.info("Running block not found in last schedule.") running_task = None # if start is before end time of currently running block, change that if running_task is not None: log.info("Found running block that ends at %s.", running_task.end) # get block end plus some safety block_end = running_task.end + 10.0 * u.second if start < block_end: start = block_end log.info( "Start time would be within currently running block, shifting to %s.", start.isot) # calculate end time end = start + TimeDelta(self._schedule_range * u.hour) # remove currently running block and filter by start time blocks = [] for b in filter( lambda x: x.configuration["request"]["id"] != self. _current_task_id, copied_blocks): time_constraint_found = False # loop all constraints for c in b.constraints: if isinstance(c, TimeConstraint): # we found a time constraint time_constraint_found = True # does the window start before the end of the scheduling range? if c.min < end: # yes, store block and break loop blocks.append(b) break else: # loop has finished without breaking # if no time constraint has been found, we still take the block if time_constraint_found is False: blocks.append(b) # if need new update, skip here if self._need_update: log.info("Not running scheduler, since update was requested.") return # no blocks found? if len(blocks) == 0: log.info("No blocks left for scheduling.") await self._task_archive.update_schedule([], start) return # log it log.info( "Calculating schedule for %d schedulable block(s) starting at %s...", len(blocks), start) # we don't need any transitions transitioner = Transitioner() # create scheduler scheduler = PriorityScheduler(constraints, self.observer, transitioner=transitioner) # run scheduler time_range = Schedule(start, end) loop = asyncio.get_running_loop() schedule = await loop.run_in_executor(None, scheduler, blocks, time_range) # if need new update, skip here if self._need_update: log.info( "Not using scheduler results, since update was requested.") return # update await self._task_archive.update_schedule(schedule.scheduled_blocks, start) if len(schedule.scheduled_blocks) > 0: log.info("Finished calculating schedule for %d block(s):", len(schedule.scheduled_blocks)) for i, block in enumerate(schedule.scheduled_blocks, 1): log.info( " #%d: %s to %s (%.1f)", block.configuration["request"]["id"], block.start_time.strftime("%H:%M:%S"), block.end_time.strftime("%H:%M:%S"), block.priority, ) else: log.info("Finished calculating schedule for 0 blocks.")
def get_transit_observability(site, ra, dec, name, t_mid_0, period, duration, n_transits=100, obs_start_time=Time( dt.datetime.today().isoformat()), min_altitude=None, oot_duration=30 * u.minute, minokmoonsep=30 * u.deg, max_airmass=None, twilight_limit='nautical'): """ note: barycentric corrections not yet implemented. (could do this myself!) -> 16 minutes of imprecision is baked into this observability calculator! args: site (astroplan.observer.Observer) ra, dec (units u.deg), e.g.: ra=101.28715533*u.deg, dec=16.71611586*u.deg, or can also accept ra="17 56 35.51", dec="-29 32 21.5" name (str), e.g., "Sirius" t_mid_0 (float): in BJD_TDB, preferably (but see note above). period (astropy quantity, units time) duration (astropy quantity, units time) n_transits (int): number of transits forward extrapolated to obs_start_time (astropy.Time object): when to start calculation from min_altitude (astropy quantity, units deg): 20 degrees is the more relevant constraint. max_airmass: e.g., 2.5. One of max_airmass or min_altitude is required. oot_duration (astropy quantity, units time): with which to brack transit observations, to get an OOT baseline. twilight_limit: 'astronomical', 'nautical', 'civil' for -18, -12, -6 deg. """ if (isinstance(ra, u.quantity.Quantity) and isinstance(dec, u.quantity.Quantity)): target_coord = SkyCoord(ra=ra, dec=dec) elif (isinstance(ra, str) and isinstance(dec, str)): target_coord = SkyCoord(ra=ra, dec=dec, unit=(u.hourangle, u.deg)) else: raise NotImplementedError if (not isinstance(max_airmass, float) or isinstance(min_altitude, u.quantity.Quantity)): raise NotImplementedError target = FixedTarget(coord=target_coord, name=name) primary_eclipse_time = Time(t_mid_0, format='jd') system = EclipsingSystem(primary_eclipse_time=primary_eclipse_time, orbital_period=period, duration=duration, name=name) midtransit_times = system.next_primary_eclipse_time(obs_start_time, n_eclipses=n_transits) # for the time being, omit any local time constraints. if twilight_limit == 'astronomical': twilight_constraint = AtNightConstraint.twilight_astronomical() elif twilight_limit == 'nautical': twilight_constraint = AtNightConstraint.twilight_nautical() else: raise NotImplementedError('civil twilight is janky.') constraints = [ twilight_constraint, AltitudeConstraint(min=min_altitude), AirmassConstraint(max=max_airmass), MoonSeparationConstraint(min=minokmoonsep) ] # observable just at midtime (bottom) b = is_event_observable(constraints, site, target, times=midtransit_times) # observable full transits (ingress, bottom, egress) ing_egr = system.next_primary_ingress_egress_time(obs_start_time, n_eclipses=n_transits) ibe = is_event_observable(constraints, site, target, times_ingress_egress=ing_egr) # get moon separation over each transit. take minimum moon sep at # ing/tmid/egr as the moon separation. moon_tmid = get_moon(midtransit_times, location=site.location) moon_separation_tmid = moon_tmid.separation(target_coord) moon_ing = get_moon(ing_egr[:, 0], location=site.location) moon_separation_ing = moon_ing.separation(target_coord) moon_egr = get_moon(ing_egr[:, 1], location=site.location) moon_separation_egr = moon_egr.separation(target_coord) moon_separation = np.round( np.array( [moon_separation_tmid, moon_separation_ing, moon_separation_egr]).min(axis=0), 0).astype(int) moon_illumination = np.round( 100 * moon.moon_illumination(midtransit_times), 0).astype(int) # completely observable transits (OOT, ingress, bottom, egress, OOT) oot_ing_egr = np.concatenate( (np.array(ing_egr[:, 0] - oot_duration)[:, None], np.array(ing_egr[:, 1] + oot_duration)[:, None]), axis=1) oibeo = is_event_observable(constraints, site, target, times_ingress_egress=oot_ing_egr) ing_tmid_egr = np.concatenate( (np.array(ing_egr[:, 0])[:, None], np.array(midtransit_times)[:, None], np.array(ing_egr[:, 1])[:, None]), axis=1) return ibe, oibeo, ing_tmid_egr, moon_separation, moon_illumination
def _schedule_thread(self): # only constraint is the night if self._twilight == 'astronomical': constraints = [AtNightConstraint.twilight_astronomical()] elif self._twilight == 'nautical': constraints = [AtNightConstraint.twilight_nautical()] else: raise ValueError('Unknown twilight type.') # we don't need any transitions transitioner = Transitioner() # run forever while not self.closing.is_set(): # need update? if self._need_update: # reset need for update self._need_update = False # get start time for scheduler start = self._schedule_start now_plus_safety = Time.now() + self._safety_time * u.second if start is None or start < now_plus_safety: # if no ETA exists or is in the past, use safety time start = now_plus_safety end = start + TimeDelta(self._schedule_range * u.hour) # remove currently running block and filter by start time blocks = [] for b in filter( lambda b: b.configuration['request']['id'] != self. _current_task_id, self._blocks): time_constraint_found = False # loop all constraints for c in b.constraints: if isinstance(c, TimeConstraint): # we found a time constraint time_constraint_found = True # does the window start before the end of the scheduling range? if c.min < end: # yes, store block and break loop blocks.append(b) break else: # loop has finished without breaking # if no time constraint has been found, we still take the block if time_constraint_found is False: blocks.append(b) # log it log.info( 'Calculating schedule for %d schedulable block(s) starting at %s...', len(blocks), start) # init scheduler and schedule scheduler = SequentialScheduler(constraints, self.observer, transitioner=transitioner) time_range = Schedule(start, end) schedule = scheduler(blocks, time_range) # update self._task_archive.update_schedule(schedule.scheduled_blocks, start) if len(schedule.scheduled_blocks) > 0: log.info('Finished calculating schedule for %d block(s):', len(schedule.scheduled_blocks)) for i, block in enumerate(schedule.scheduled_blocks, 1): log.info(' #%d: %s to %s (%.1f)', block.configuration['request']['id'], block.start_time, block.end_time, block.priority) else: log.info('Finished calculating schedule for 0 blocks.') # sleep a little self.closing.wait(1)