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_local_time=dt.time(16, 0), max_local_time=dt.time(9, 0), min_altitude=20 * u.deg, oot_duration=30 * u.minute, minokmoonsep=30 * u.deg): """ 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_local_time, max_local_time: earliest time when you think observing is OK. E.g., 16:00 local and 09:00 local are earliest and latest. Note this constraint is a bit silly, since the astroplan "AtNightConstraint" is imposed automatically. As implemented, these are ignored. min_altitude (astropy quantity, units deg): 20 degrees is the more relevant constraint. oot_duration (astropy quantity, units time): with which to brack transit observations, to get an OOT baseline. """ 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 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. constraints = [ AtNightConstraint.twilight_civil(), AltitudeConstraint(min=min_altitude), MoonSeparationConstraint(min=minokmoonsep) ] #constraints = [AtNightConstraint.twilight_civil(), # AltitudeConstraint(min=min_altitude), # LocalTimeConstraint(min=min_local_time, max=max_local_time)] # 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 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 get_schedulable_blocks(self) -> list: """Returns list of schedulable blocks. Returns: List of schedulable blocks """ # get requests r = requests.get(urljoin(self._url, '/api/requestgroups/schedulable_requests/'), headers=self._header, proxies=self._proxies) if r.status_code != 200: raise ValueError('Could not fetch list of schedulable requests.') schedulable = r.json() # get proposal priorities r = requests.get(urljoin(self._url, '/api/proposals/'), headers=self._header, proxies=self._proxies) if r.status_code != 200: raise ValueError('Could not fetch list of proposals.') tac_priorities = { p['id']: p['tac_priority'] for p in r.json()['results'] } # loop all request groups blocks = [] for group in schedulable: # get base priority, which is tac_priority * ipp_value proposal = group['proposal'] if proposal not in tac_priorities: log.error('Could not find proposal "%s".', proposal) continue base_priority = group['ipp_value'] * tac_priorities[proposal] # loop all requests in group for req in group['requests']: # still pending? if req['state'] != 'PENDING': continue # duration duration = req['duration'] * u.second # time constraints time_constraints = [ TimeConstraint(Time(wnd['start']), Time(wnd['end'])) for wnd in req['windows'] ] # loop configs for cfg in req['configurations']: # get instrument and check, whether we schedule it instrument = cfg['instrument_type'] if instrument.lower( ) != self._portal_instrument_type.lower(): continue # target t = cfg['target'] target = SkyCoord(t['ra'] * u.deg, t['dec'] * u.deg, frame=t['type'].lower()) # constraints c = cfg['constraints'] constraints = [ AirmassConstraint(max=c['max_airmass'], boolean_constraint=False), MoonSeparationConstraint(min=c['min_lunar_distance'] * u.deg) ] # priority is base_priority times duration in minutes priority = base_priority * duration.value / 60. # create block block = ObservingBlock( FixedTarget(target, name=req["id"]), duration, priority, constraints=[*constraints, *time_constraints], configuration={'request': req}) blocks.append(block) # return blocks return blocks
def get_obs_data(target, observers, current_time, alt_limit=30): """Compile infomation about the target's visibility from the given observers.""" all_data = {} if target is None: return all_data for observer in observers: data = {} data['observer'] = observer data['current_time'] = current_time # Get midnight and astronomical twilight times midnight = observer.midnight(current_time, which='next') sun_set = observer.twilight_evening_astronomical(midnight, which='previous') sun_rise = observer.twilight_morning_astronomical(midnight, which='next') dark_time = Time([sun_set, sun_rise]) data['midnight'] = midnight data['sun_set'] = sun_set data['sun_rise'] = sun_rise # Apply a constraint on altitude min_alt = alt_limit * u.deg alt_constraint = AltitudeConstraint(min=min_alt, max=None) alt_observable = is_observable(alt_constraint, observer, target, time_range=dark_time)[0] data['alt_constraint'] = alt_constraint data['alt_observable'] = alt_observable # Get target rise and set times if alt_observable: with warnings.catch_warnings(): warnings.simplefilter('ignore') target_rise = observer.target_rise_time(midnight, target, which='nearest', horizon=min_alt) target_set = observer.target_set_time(target_rise, target, which='next', horizon=min_alt) # Get observation times observation_start = target_rise observation_end = target_set if target_rise.jd < 0 or target_set.jd < 0: # target is always above the horizon, so visible all night observation_start = sun_set observation_end = sun_rise if target_rise < sun_set: # target is already up when the sun sets observation_start = sun_set if target_set > sun_rise: # target sets after the sun rises observation_end = sun_rise data['target_rise'] = target_rise data['target_set'] = target_set data['observation_start'] = observation_start data['observation_end'] = observation_end else: data['target_rise'] = None data['target_set'] = None data['observation_start'] = None data['observation_end'] = None # Apply a constraint on distance from the Moon min_moon = 5 * u.deg moon_constraint = MoonSeparationConstraint(min=min_moon, max=None) moon_observable = is_observable(moon_constraint, observer, target, time_range=dark_time)[0] data['moon_constraint'] = moon_constraint data['moon_observable'] = moon_observable all_data[observer.name] = data return all_data
async def get_schedulable_blocks(self) -> List[ObservingBlock]: """Returns list of schedulable blocks. Returns: List of schedulable blocks """ # check if self._portal_instrument_type is None: raise ValueError("No instrument type for portal set.") # get data schedulable = await self._portal_get( urljoin(self._url, "/api/requestgroups/schedulable_requests/")) # get proposal priorities data = await self._portal_get(urljoin(self._url, "/api/proposals/")) tac_priorities = {p["id"]: p["tac_priority"] for p in data["results"]} # loop all request groups blocks = [] for group in schedulable: # get base priority, which is tac_priority * ipp_value proposal = group["proposal"] if proposal not in tac_priorities: log.error('Could not find proposal "%s".', proposal) continue base_priority = group["ipp_value"] * tac_priorities[proposal] # loop all requests in group for req in group["requests"]: # still pending? if req["state"] != "PENDING": continue # duration duration = req["duration"] * u.second # time constraints time_constraints = [ TimeConstraint(Time(wnd["start"]), Time(wnd["end"])) for wnd in req["windows"] ] # loop configs for cfg in req["configurations"]: # get instrument and check, whether we schedule it instrument = cfg["instrument_type"] if instrument.lower( ) != self._portal_instrument_type.lower(): continue # target t = cfg["target"] target = SkyCoord(t["ra"] * u.deg, t["dec"] * u.deg, frame=t["type"].lower()) # constraints c = cfg["constraints"] constraints = [] if "max_airmass" in c and c["max_airmass"] is not None: constraints.append( AirmassConstraint(max=c["max_airmass"], boolean_constraint=False)) if "min_lunar_distance" in c and c[ "min_lunar_distance"] is not None: constraints.append( MoonSeparationConstraint( min=c["min_lunar_distance"] * u.deg)) if "max_lunar_phase" in c and c[ "max_lunar_phase"] is not None: constraints.append( MoonIlluminationConstraint( max=c["max_lunar_phase"])) # if max lunar phase <= 0.4 (which would be DARK), we also enforce the sun to be <-18 degrees if c["max_lunar_phase"] <= 0.4: constraints.append( AtNightConstraint.twilight_astronomical()) # priority is base_priority times duration in minutes # priority = base_priority * duration.value / 60. priority = base_priority # create block block = ObservingBlock( FixedTarget(target, name=req["id"]), duration, priority, constraints=[*constraints, *time_constraints], configuration={"request": req}, ) blocks.append(block) # return blocks return blocks
def run_nights(observers, nameList, args): # Define range of times to observe between startDate = datetime.datetime.strptime(args.startDate,"%Y-%m-%d") beginTimeFirstNight = datetime.datetime(startDate.year,startDate.month,startDate.day,hour=16) endTimeFirstNight = beginTimeFirstNight + datetime.timedelta(hours=16) t_datetimes_nights_list = [] for iDay in range(args.nNights): beginTime = beginTimeFirstNight + datetime.timedelta(days=iDay) endTime = endTimeFirstNight + datetime.timedelta(days=iDay) currTime = beginTime t_datetime = [] while currTime <= endTime: t_datetime.append(currTime) currTime += datetime.timedelta(hours=1) t_datetimes_nights_list.append(t_datetime) targets = [FixedTarget(coord=lookuptarget(name),name=name) for name in nameList] targetLabelList, ylabelsize = makeTargetLabels(nameList,args) constraints = [ AltitudeConstraint(min=args.minAlt*u.deg), AtNightConstraint.twilight_astronomical(), MoonSeparationConstraint(min=args.minMoonSep*u.deg), MoonIlluminationConstraint(max=args.maxMoonIllum), ] outfn = args.outFileNameBase+"_nightly.pdf" with PdfPages(outfn) as pdf: for observer in observers: fig, axes = mpl.subplots( figsize=(8.5,11), ncols=args.nNights, sharex="col", gridspec_kw={ "top":0.92, "bottom":0.03, "left":0.13, "right":0.98, "hspace":0, "wspace":0 }, tight_layout=False,constrained_layout=False ) observability_grids = [] for iNight in range(args.nNights): t_datetime = t_datetimes_nights_list[iNight] time_grid = [Time(observer.timezone.localize(t)) for t in t_datetime] observability_grid = numpy.zeros((len(targets),len(time_grid)-1)) for i in range(len(time_grid)-1): tmp = is_always_observable(constraints, observer, targets, times=[time_grid[i],time_grid[i+1]]) observability_grid[:, i] = tmp observability_grids.append(observability_grid) observable_targets = targets observable_target_labels = targetLabelList ever_observability_grids = observability_grids if args.onlyEverObservable: target_is_observable = numpy.zeros(len(targets)) for observability_grid in observability_grids: for iTime in range(observability_grid.shape[1]): target_is_observable += observability_grid[:,iTime] target_is_observable = target_is_observable > 0. # change to boolean numpy array observable_targets = [x for x, o in zip(targets,target_is_observable) if o] observable_target_labels = [x for x, o in zip(targetLabelList,target_is_observable) if o] ever_observability_grids = [] for observability_grid in observability_grids: ever_observability_grid = observability_grid[target_is_observable,:] ever_observability_grids.append(ever_observability_grid) for iNight in range(args.nNights): ax = axes[iNight] t_datetime = t_datetimes_nights_list[iNight] extent = [0, len(t_datetime)-1, -0.5, len(observable_targets)-0.5] ax.imshow(ever_observability_grids[iNight], extent=extent, origin="lower", aspect="auto", cmap=mpl.get_cmap("Greens")) ax.xaxis.tick_top() ax.xaxis.set_label_position("top") ax.invert_yaxis() if iNight == 0: ax.set_yticks(range(0,len(observable_targets))) ax.set_yticklabels(observable_target_labels, fontsize=ylabelsize) else: ax.set_yticks([]) ax.set_xticks(range(0,len(t_datetime)-1,4)) ax.set_xticks(range(0,len(t_datetime)),minor=True) ax.set_xticklabels([t_datetime[i].strftime("%Hh") for i in range(0,len(t_datetime)-1,4)]) ax.set_xlabel(t_datetime[0].strftime("%a %b %d")) ax.set_yticks(numpy.arange(extent[2], extent[3]), minor=True) ax.grid(axis="x",which="minor",color="0.7",ls="-", linewidth=0.5) ax.grid(axis="x",which="major",color="0.7",ls="-", linewidth=1) ax.grid(axis="y",which="minor",color="0.7",ls="-", linewidth=0.5) ax.tick_params(axis='y', which='minor', left=False, right=False) ax.tick_params(axis='x', which='minor', bottom=False, top=False) fig.suptitle(f"Observability at {observer.name} in {startDate.year}") fig.text(1.0,0.0,"Constraints: Astronomical Twilight, Altitude $\geq {:.0f}^\circ$, Moon Seperation $\geq {:.0f}^\circ$, Moon Illumination $\leq {:.2f}$".format(args.minAlt,args.minMoonSep,args.maxMoonIllum),ha="right",va="bottom") pdf.savefig(fig) print(f"Writing out file: {outfn}")