def minimal_example(): apo = Observer.at_site('APO', timezone='US/Mountain') target = FixedTarget.from_name("HD 209458") primary_eclipse_time = Time(2452826.628514, format='jd') orbital_period = 3.52474859 * u.day eclipse_duration = 0.1277 * u.day hd209458 = EclipsingSystem(primary_eclipse_time=primary_eclipse_time, orbital_period=orbital_period, duration=eclipse_duration, name='HD 209458 b') n_transits = 100 # This is the roughly number of transits per year obs_time = Time('2017-01-01 12:00') midtransit_times = hd209458.next_primary_eclipse_time( obs_time, n_eclipses=n_transits) import astropy.units as u min_local_time = dt.time(18, 0) # 18:00 local time at APO (7pm) max_local_time = dt.time(8, 0) # 08:00 local time at APO (5am) constraints = [ AtNightConstraint.twilight_civil(), AltitudeConstraint(min=30 * u.deg), LocalTimeConstraint(min=min_local_time, max=max_local_time) ] # just at midtime b = is_event_observable(constraints, apo, target, times=midtransit_times) # completely observable transits observing_time = Time('2016-01-01 00:00') ing_egr = hd209458.next_primary_ingress_egress_time(observing_time, n_eclipses=n_transits) ibe = is_event_observable(constraints, apo, target, times_ingress_egress=ing_egr) oot_duration = 30 * u.minute 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, apo, target, times_ingress_egress=oot_ing_egr)
def max_end_time(constraints_list, obs, target, time, dt=1 * u.min): time = Time(time) morning = obs.sun_rise_time(time, which='next') time_grid = np.arange(time.jd, morning.jd, dt.to('day').value) * u.d time_grid = Time(time_grid, format='jd') index = is_event_observable(constraints_list, obs, target, times=time_grid).squeeze() return time_grid[index][-1]
def target_observable(self, Nights, constraints, delta_midnight=None): """ Calculates for which times during the time span of Nights, the target is observable under the given constraints. LATER : Could include plotting of target observability. Parameters ---------- Nights : class Nights at Paranal for which to compute if the target is observable. constraints : list list of Astroplan constraints to constrain the observability. delta_midnight : numpy.linspace, Obtional grid of timesteps within 24 hours for which the observation should be calculated. """ if delta_midnight == None: # defines number of timesteps per 24 hours delta_midnight = np.linspace(-12, 12, 1000) * u.hour print(self.name + ' is getting processed') for date in Nights.date: """ Check if Nights object has attribute nights to calculate observability of target """ if hasattr(Nights, 'night'): k = list.index(Nights.date, date) night = Nights.night[k] else: Nights.Calculate_nights_paranal(delta_midnight) night = Nights.night[k] """ Check if target observable """ tar_obs = astroplan.is_event_observable( constraints=constraints, observer=paranal, target=self.Coordinates, times=night) if any(tar_obs[0] == True): print( '{} Target is observable without any primary eclipse'.format(self.name)) for n, tar in enumerate(tar_obs[0]): if tar == True: moon_target_sep, moon_phase, airmass, obs_altazs = fun.airmass_moon_sep_obj_altaz( self, night[n]) self.target_observable.append({ 'Name': self.name, 'Effective Temperature': self.star_Teff, 'J-magnitude': self.star_jmag, 'Object observable?': tar, 'Obs Data': {'time': night[n], 'airmass': airmass, 'moon sep': moon_target_sep[0], 'moon phase': moon_phase, 'az': obs_altazs.az, 'alt': obs_altazs.alt }})
def min_start_time(constraints_list, obs, target, time, dt=1 * u.min): # Convert some inputs dt = dt.to('day').value time = Time(time) # Get sun set time occuring before this event evening = obs.sun_set_time(time, which='previous') time_grid = np.arange(evening.jd, time.jd + dt, dt) * u.d time_grid = Time(time_grid, format='jd') index = is_event_observable(constraints_list, obs, target, times=time_grid).squeeze() return time_grid[index][0]
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
# TODO select proper coordinates: load from params_star.csv # TODO select proper observer place (ground, space) coords = "1:12:43.2 +1:12:43" target = FixedTarget(SkyCoord(coords, unit=(u.deg, u.deg))) # TODO select proper time n_transits = 100 # This is the roughly number of transits per year obs_time = Time('2017-01-01 12:00') # TODO bulk to file midtransit_times = system.next_primary_eclipse_time(obs_time, n_eclipses=n_transits) ingress_egress_times = system.next_primary_ingress_egress_time( obs_time, n_eclipses=n_transits) # TODO select local times somehow min_local_time = dt.time(19, 0) # 19:00 local time at APO (7pm) max_local_time = dt.time(0, 0) # 00:00 local time at APO (midnight) constraints = [ AtNightConstraint.twilight_civil(), AltitudeConstraint(min=30 * u.deg), LocalTimeConstraint(min=min_local_time, max=max_local_time) ] midtime_observable = astroplan.is_event_observable(constraints, observer_site, target, times=midtransit_times) entire_observable = astroplan.is_event_observable( constraints, observer_site, target, times=midtransit_times, times_ingress_egress=ingress_egress_times)
def single_planet(data, date_start, date_end, constraints, observer, verbose=0): """ Performs the observability and SNR estimation for a single planet Parameters ---------- data : dict data for this planet date_start : Time start date date_end : Time end date constraints : list list of observability constraints observer : astroplan.Observer telescope location verbose : int, optional how much information to print, by default 0 Returns ------- result : dict contains everything about this planet, one entry per observable transit """ set_verbose_level(verbose) primary_eclipse_time = Time(data["pl_tranmid"], format="jd") orbital_period = data["pl_orbper"] * u.day eclipse_duration = data["pl_trandur"] * u.hour name = data["pl_name"] # Update the progress bar # Define the target coordinates coord = SkyCoord(ra=data["ra"], dec=data["dec"], unit=u.deg) target = ap.FixedTarget(coord, name=name) # Define the star-planet system system = ap.EclipsingSystem( primary_eclipse_time=primary_eclipse_time, orbital_period=orbital_period, duration=eclipse_duration, name=name, ) # Find all eclipses of this system n_max_eclipses = (date_end - date_start) / orbital_period n_max_eclipses = max(n_max_eclipses, 1) eclipses = system.next_primary_ingress_egress_time( date_start, n_eclipses=n_max_eclipses) # If the last eclipse is past the final date, just cut it off if eclipses[-1][0] > date_end: eclipses = eclipses[:-1] if len(eclipses) == 0: logger.warning(f"No observable transits found for planet {name}") return None # Check if they are observable is_observable = ap.is_event_observable(constraints, observer, target, times_ingress_egress=eclipses)[0] observable_eclipses = eclipses[is_observable] n_eclipses = len(observable_eclipses) if n_eclipses == 0: logger.warning(f"No observable transits found for planet {name}") return None # Calculate the SNR for those that are left magnitude = data["sy_kmag"] * u.mag exptime = eclipse_duration # exptime = np.diff(observable_eclipses.jd, axis=1).ravel() * u.day obstime = Time(np.mean(observable_eclipses.jd, axis=1), format="jd") airmass = calculate_airmass(obstime, observer, target) snr = estimate_snr(magnitude, exptime, airmass) # Save results for later result = { "name": [name] * n_eclipses, "snr": snr.to_value(1), "exptime": [eclipse_duration.to_value(u.second)] * n_eclipses, "time": obstime.mjd, "time_begin": observable_eclipses[:, 0].datetime, "time_end": observable_eclipses[:, 1].datetime, "stellar_effective_temperature": [data["st_teff"]] * n_eclipses, } return result
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 main(args=None): p = parser() opts = p.parse_args(args) # Late imports from astroplan import (AirmassConstraint, AtNightConstraint, Observer, is_event_observable) from astropy.coordinates import EarthLocation, SkyCoord from astropy.time import Time from astropy import units as u from matplotlib import dates from matplotlib import pyplot as plt from tqdm import tqdm from ..io import fits from .. import moc from .. import plot # noqa names = ('name', 'longitude', 'latitude', 'height') length0, *lengths = (len(getattr(opts, 'site_{}'.format(name))) for name in names) if not all(length0 == length for length in lengths): p.error('these options require equal numbers of arguments: {}'.format( ', '.join('--site-{}'.format(name) for name in names))) observers = [Observer.at_site(site) for site in opts.site] for name, lon, lat, height in zip(opts.site_name, opts.site_longitude, opts.site_latitude, opts.site_height): location = EarthLocation(lon=lon * u.deg, lat=lat * u.deg, height=(height or 0) * u.m) observers.append(Observer(location, name=name)) observers = list(reversed(observers)) m = fits.read_sky_map(opts.input.name, moc=True) t0 = Time(opts.time) if opts.time is not None else Time.now() times = t0 + np.linspace(0, 1) * u.day theta, phi = moc.uniq2ang(m['UNIQ']) coords = SkyCoord(phi, 0.5 * np.pi - theta, unit='rad') prob = np.asarray(moc.uniq2pixarea(m['UNIQ']) * m['PROBDENSITY']) constraints = [ getattr(AtNightConstraint, 'twilight_{}'.format(opts.twilight))(), AirmassConstraint(opts.max_airmass) ] fig = plt.figure() width, height = fig.get_size_inches() fig.set_size_inches(width, (len(observers) + 1) / 16 * width) ax = plt.axes() locator = dates.AutoDateLocator() formatter = dates.DateFormatter('%H:%M') ax.set_xlim([times[0].plot_date, times[-1].plot_date]) ax.xaxis.set_major_formatter(formatter) ax.xaxis.set_major_locator(locator) ax.set_xlabel("Time from {0} [UTC]".format(min(times).datetime.date())) plt.setp(ax.get_xticklabels(), rotation=30, ha='right') ax.set_yticks(np.arange(len(observers))) ax.set_yticklabels([observer.name for observer in observers]) ax.yaxis.set_tick_params(left=False) ax.grid(axis='x') ax.spines['bottom'].set_visible(False) ax.spines['top'].set_visible(False) for i, observer in enumerate(tqdm(observers)): observable = 100 * np.dot( prob, is_event_observable(constraints, observer, coords, times)) ax.contourf(times.plot_date, [i - 0.4, i + 0.4], np.tile(observable, (2, 1)), levels=np.arange(10, 110, 10), cmap=plt.get_cmap().reversed()) plt.tight_layout() # Show or save output. opts.output()
def observable_windows(tforecast, forecast, target, site, constraints, max_obs_duration=np.inf): """ Identify observable follow-up windows. Parameters ---------- tforecast : `~astropy.time.Time` The time array for the transit forecast. forecast : `~numpy.array` The transit forecast. target : `~astroplan.FixedTarget` A target object. site : `~astroplan.Observer` A site object. constraints : iterable A list of `~astroplan.Constraint` objects. max_obs_duration : float or `~astropy.units.Quantity`, optional The maximum duration of an observation. Defaults to days if unit not specified. Returns ------- windows : `~pandas.DataFrame` A table of the observable windows. """ if not isinstance(tforecast, Time): raise TypeError( 'The times must be specified with an astropy.time.Time instance.') if isinstance(max_obs_duration, units.Quantity): max_obs_duration = max_obs_duration.to(units.day).value # For simplicity, just use BJD times times = tforecast.jd observability = ap.is_event_observable(constraints, site, target, times=tforecast).flatten() idx_observable = np.where(observability)[0] idx_window_list = np.split(idx_observable, np.where(np.diff(idx_observable) != 1)[0] + 1) dts = [] t_starts = [] t_ends = [] t_maxs = [] Ms = [] for idx_window in idx_window_list: t_max = (times[idx_window])[np.argmax(forecast[idx_window])] idx_window = _refine_window(times, t_max, idx_window, max_obs_duration) t_start = (times[idx_window]).min() t_end = (times[idx_window]).max() dt = (times[idx_window]).ptp() M = np.trapz(forecast[idx_window], times[idx_window]) / dt dts.append(dt) t_starts.append(t_start) t_ends.append(t_end) t_maxs.append(t_max) Ms.append(M) windows = pd.DataFrame({ 't_start': t_starts, 't_max': t_maxs, 't_end': t_ends, 'dt': dts, 'M': Ms }).sort_values('M', ascending=False) return windows
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(self, obs_time, Nights, constraints): #check_eclipse, check_target=0, delta_midnight=None """ Calculates if the Transit and the target are observable for each date during the given timespan in ''Nights'' under the given ''constraints'' and writes it as dict objects into ''~self.eclipse_observable'' or ''~self.target_observable''. Parameters ---------- obs_time : astropy.time.Time Contains the datetime as Time format after which the possible observations should be found. Nights : class Nights Containing night data of paranal, see Nights documentation. constraints : class astroplan.Constraint Constraints under which the observational events should get constrained. check_eclipse : int If ''check_eclipse'' = 1, checks if transits/eclipses are observable. check_target : int, optional If ''check_target'' = 1, checks if target is observable during the given nights. The default is 0. delta_midnight : numpy.linspace, obtional array containing a grid of timesteps for which the nights datetimes should get computed. Default is None """ Planet_next_eclipse_Times = self.Planets_eclipse.next_primary_eclipse_time( obs_time, n_eclipses=self.num_eclipses) print(self.name + ' is getting processed') # n_max = np.ceil(1/self.period_err) # for date in Nights.date: # if check_target == 1: # """ Check if Nights object has attribute nights to calculate observability of target """ # if hasattr(Nights, 'night'): # k = list.index(Nights.date, date) # night = Nights.night[k] # else: # Nights.Calculate_nights_paranal(delta_midnight) # night = Nights.night[0] Planet_Eclipes_NIGHTs_All = [] for n, planet_next_eclipse_by_date in enumerate(Planet_next_eclipse_Times): """ Loop over all eclipses coming up in the given timespan of object planet """ """ Barycentric light travel time correction, since mid transit times are in barycentric frame. Need to transform time to geocentric frame: """ planet_next_eclipse_by_date.location = paranal.location ltt_bary = planet_next_eclipse_by_date.light_travel_time(self.Coordinates.coord) planet_next_eclipse_by_date = planet_next_eclipse_by_date - ltt_bary # barycentric correction, # the minus comes from that we transform from the barycentric into the geocentric frame. # Check which eclipse can be observed in which night # if date == planet_next_eclipse_by_date.datetime.date(): # if check_eclipse == 1: Planet_next_eclipse_per_night_MID = planet_next_eclipse_by_date Planet_next_eclipse_per_night_BEGIN = Planet_next_eclipse_per_night_MID - \ self.transit_duration / 2 Planet_next_eclipse_per_night_END = Planet_next_eclipse_per_night_MID + \ self.transit_duration / 2 Planet_Eclipse_ERROR = (np.sqrt(self.tranmid_err.sec**2+(n+1)**2*self.period_err.to_value(u.second)**2)*u.second).to(u.hour) Planet_Eclipes_NIGHT = [Planet_next_eclipse_per_night_BEGIN, Planet_next_eclipse_per_night_MID, Planet_next_eclipse_per_night_END] # Begin, midpoint and end of transit # Planet_Eclipes_NIGHTs_All = Planet_Eclipes_NIGHTs_All + Planet_Eclipes_NIGHT """ Computes observability of the Transit """ ecl_obs = astroplan.is_event_observable( constraints=constraints, observer=paranal, target=self.Coordinates, times=Planet_Eclipes_NIGHT) # ecl_obs_test = astroplan.is_event_observable( # constraints=constraints, observer=paranal, target=self.Coordinates, times=Planet_Eclipes_NIGHTs_All) # for k in range(int(len(ecl_obs_test[0])/3)): # flag_not_obs = False # for n in range(3): # if ecl_obs_test[0][n+k] == False: # flag_not_obs = True # if not flag_not_obs: # Planet_Eclipes_NIGHT_obs = [Planet_Eclipes_NIGHTs_All[0+k], Planet_Eclipes_NIGHTs_All[1+k], Planet_Eclipes_NIGHTs_All[2+k]] if all(ecl_obs[0] == True): print('{} total Eclipse is observable'.format(self.name)) airmass_moon_sep_obj_altaz_RESULT = [ fun.airmass_moon_sep_obj_altaz(self, tim) for tim in Planet_Eclipes_NIGHT] #HERE Planet_Eclipes_NIGHT_obs moon_target_sep = [ out[0] for out in airmass_moon_sep_obj_altaz_RESULT] moon_phase = [out[1] for out in airmass_moon_sep_obj_altaz_RESULT] airmass = [out[2] for out in airmass_moon_sep_obj_altaz_RESULT] obs_altazs = [out[3] for out in airmass_moon_sep_obj_altaz_RESULT] # print(moon_target_sep, moon_phase, airmass, obs_altazs) self.eclipse_observable.append({ 'Name': self.name, 'Radius R_J': self.pl_radj, 'Transit Midpoint Time': Planet_next_eclipse_per_night_MID, #Planet_Eclipes_NIGHT_obs[1], 'Transit Midpoint Time uncertainty [h]': Planet_Eclipse_ERROR, 'Primary eclipse observable?': ecl_obs[0][0], 'Transit Duration [h]': self.transit_duration.to(u.hour), 'Effective Temperature K': self.star_Teff, 'J-magnitude': self.star_jmag, 'Eclipse Begin': {'time': Planet_next_eclipse_per_night_BEGIN, #Planet_Eclipes_NIGHT_obs[0] , 'airmass': airmass[0], 'moon sep': moon_target_sep[0][0], 'moon phase': moon_phase[0], 'az': obs_altazs[0].az, 'alt': obs_altazs[0].alt }, 'Eclipse Mid': {'time': Planet_next_eclipse_per_night_MID, #Planet_Eclipes_NIGHT_obs[1] , 'airmass': airmass[1], 'moon sep': moon_target_sep[1][0], 'moon phase': moon_phase[1], 'az': obs_altazs[1].az, 'alt': obs_altazs[1].alt }, 'Eclipse End': {'time': Planet_next_eclipse_per_night_END, #Planet_Eclipes_NIGHT_obs[2], 'airmass': airmass[2], 'moon sep': moon_target_sep[2][0], 'moon phase': moon_phase[2], 'az': obs_altazs[2].az, 'alt': obs_altazs[2].alt }})
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
n_transits = 169 # The number of transits per year observing_time = Time('2018-08-01 12:00') utc_minus_six_hour = TimezoneInfo(utc_offset=-6 * u.hour) constraints = [ AtNightConstraint.twilight_astronomical(), AltitudeConstraint(min=30 * u.deg), ] ing_egr = HATP32b.next_primary_ingress_egress_time(observing_time, n_eclipses=n_transits) hits = is_event_observable(constraints, observer, HATP32, times_ingress_egress=ing_egr) # Create a list of ingress-egress time objects matching constraints. candidates = [] for i in range(len(hits[0])): if hits[0][i] == True: candidates.append(ing_egr[i]) # Convert from Julian to ISO for i in candidates: i.format = 'iso' f = open('observation_times.txt', 'w') f.write("Obervation times [Ingress Egress] --- UTC (MST is UTC - 6)\n") for i in candidates:
def predict(self): # # Make sure baseline has units # if baseline is not None: # try: # baseline.unit # except AttributeError: # warn("No units specified for input 'baseline'." # +" Assuming hours.") # baseline = baseline * u.h # Inputs from object's attributes t1, t2 = self.meta['Time_limits'] info = self.info n_eclipses = 500 constraints_list = self.constraints obs = self.obs supp_cols = self.supp_cols # Define needed quantities based on planets infos # Must be quatities arrays (astropy) # Here we use a given astropy Table (info) to get the infos epoch, period, transit_duration = \ [info[k_col].quantity for k_col in ('pl_tranmid', 'pl_orbper', 'pl_trandur')] epoch = Time(epoch, format='jd') pl_name = info['pl_name'] observing_time = t1 # Init output table col_names = ( 'pl_name', 'mid_tr', 'AM_mid_tr', 'tr_start', 'tr_end', 'AM_tr_start', 'AM_tr_end', # 'start', # 'end', # 'AM_start', # 'AM_end', 'Obs_start', 'Baseline_before', 'Obs_end', 'Baseline_after', 'moon', *supp_cols) description = { 'mid_tr': "Time at mid transit [UTC]", 'AM_mid_tr': "Airmass at mid transit", 'tr_start': "Time at first contact t1 [UTC]", 'tr_end': "Time at last contact t4 [UTC]", 'AM_tr_start': "Airmass at first contact t1 [UTC]", 'AM_tr_end': "Airmass at last contact t4 [UTC]", 'Obs_start': "Beginning of target observability according to input constraints [UTC]", 'Baseline_before': "'tr_start' - 'Obs_start'. Can be negative.", 'Obs_end': "End of target observability according to input constraints [UTC]", 'Baseline_after': "'Obs_end' - 'tr_end'. Can be negative." } meta = {**self.meta} full_table = Table() # Iterations on the targets for itar, target in enumerate(self.targets): # ------------------------- # Steps to predict transits # ------------------------- # Define system sys = EclipsingSystem(primary_eclipse_time=epoch[itar], orbital_period=period[itar], duration=transit_duration[itar], name=target.name) # Find all events ... while True: t_mid = sys.next_primary_eclipse_time(observing_time, n_eclipses=n_eclipses) # ... until t2 is passed if t_mid[-1] > t2: break # or add eclipses to pass t2 else: n_eclipse += 500 # Remove events after time window t_mid = t_mid[t_mid < t2] # Number of events n_event, = t_mid.shape # Get ingress and egress times t1_t4 = sys.next_primary_ingress_egress_time(observing_time, n_eclipses=n_event) # Which mid transit times are observable i_mid = is_event_observable(constraints_list, obs, target, times=t_mid).squeeze() # Which ingress are observable ... i_t1 = is_event_observable(constraints_list, obs, target, times=t1_t4[:, 0]).squeeze() # ... when mid transit is not. i_t1 = i_t1 & ~i_mid # Which egress are observable ... i_t4 = is_event_observable(constraints_list, obs, target, times=t1_t4[:, 1]).squeeze() # ... when mid transit and ingress is not. i_t4 = i_t4 & ~i_mid & ~i_t1 # Keep events where ingress, mid_transit or egress is observable index = i_mid | i_t1 | i_t4 # Get observability for these events. # Starting point to compute the observability t_obs = np.concatenate( [t_mid[i_mid], t1_t4[i_t1, 0], t1_t4[i_t4, 1]]) t_obs = Time(t_obs).sort() # Get observability range for each of these events obs_start = min_start_times(constraints_list, obs, target, t_obs) obs_end = max_end_times(constraints_list, obs, target, t_obs) # ------------------- # End of steps to predict transits # ------------------- # Put the infos in a table and stack it to the full table if index.any(): name = np.repeat(sys.name, index.sum()).astype(str) moon = obs.moon_illumination(t_mid[index]) AM_mid = obs.altaz(t_mid[index], target).secz AM_t1_t4 = obs.altaz(t1_t4[index], target).secz # AM_base = obs.altaz(t_baseline[index], target).secz # obs_start = min_start_times(constraints_list, obs, target, t_mid[index]) baseline_before = (t1_t4[index, 0] - obs_start).to('min') # obs_end = max_end_times(constraints_list, obs, target, t_mid[index]) baseline_after = (obs_end - t1_t4[index, 1]).to('min') supp = [ np.repeat(info[key][itar], index.sum()) for key in supp_cols ] cols = [ name, t_mid[index].iso, AM_mid, t1_t4[index, 0].iso, t1_t4[index, 1].iso, AM_t1_t4[:, 0], AM_t1_t4[:, 1], # *t_baseline[index].T.iso, # *AM_base.T, obs_start.iso, baseline_before, obs_end.iso, baseline_after, moon, *supp ] table_sys = Table(cols, names=col_names, masked=True) full_table = vstack([table_sys, full_table]) else: warnings.warn('No event found for ' + sys.name, AstropyUserWarning) if full_table: full_table.sort('mid_tr') full_table.meta = meta else: warnings.warn('No event found at all', AstropyUserWarning) # Add column descriptions for col in full_table.colnames: try: full_table[col].description = description[col] except KeyError: pass return full_table
def predict(self, phase_range, obs_time=3. * u.h, dt_grid=0.5 * u.h, phase_constraint=True): ''' Parameters: ----------- phase_range: list, len=2 Phase range obs_time: quantity or float minimum required observing time dt_grid: quantity or float grid steps used to compute observability between phase range. ''' if not hasattr(obs_time, 'unit'): obs_time = obs_time * u.h if not hasattr(dt_grid, 'unit'): dt_grid = dt_grid * u.h t1, t2 = self.meta['Time_limits'] info = self.info n_eclipses = 500 # TODO: COuld be computed according to period and time range constraints_list = self.constraints obs = self.obs supp_cols = self.supp_cols # Define needed quantities based on planets infos # Must be quatities arrays (astropy) # Here we use a given astropy Table (info) to get the infos epoch, period = [ info[k_col].quantity for k_col in ('pl_phase_zero', 'pl_orbper') ] epoch = Time(epoch, format='jd') pl_name = info['pl_name'] window_start = t1 # Init output table col_names = ('pl_name', 'Obs_start', 'Phase_start', 'Obs_end', 'Phase_end', 'mid_phase', 'AM_mid_phase', 'observability', 'moon', *supp_cols) meta = {'Phase_range': phase_range, **self.meta} full_table = Table() # Iterations on the targets for itar, target in enumerate(self.targets): # ------------------------- # Steps to predict transits # ------------------------- d_phase = (dt_grid / period[itar]).decompose().value [p1, p2] = phase_range p1 += d_phase / 10 # Make sure it's not on the boundary p2 -= d_phase / 10 phase_grid = np.arange(p1, p2, d_phase) phase_grid = phase_grid * period[itar] + epoch[itar] while True: # Find all events for each phase point in grid ... t_grid = [] for phase in phase_grid: # Define a system for each phase point sys = EclipsingSystem(primary_eclipse_time=phase, orbital_period=period[itar]) # Compute all events and save t_temp = sys.next_primary_eclipse_time( window_start, n_eclipses=n_eclipses) t_grid.append(t_temp.jd) # Convert to Time object t_grid = Time(t_grid, format='jd').T # Do so until t2 is passed if t_grid[-1, -1] > t2: break # or add eclipses to pass t2 and recompute t_grid else: n_eclipses += 500 if (np.diff(t_grid.jd, axis=-1) <= 0).any(): message = 'Time limit t1 falls into phase range.' \ + ' This will be corrected eventually.' \ + ' For now, please change the time limit t1.' raise ValueError(message) t_grid = t_grid[(t_grid < t2).any(axis=1)] events = [] for grid in t_grid: index = is_event_observable(constraints_list, obs, target, times=grid).squeeze() if index.any(): events.append(np.mean(grid[index].jd)) events = Time(events, format='jd') # Finally add phase constraint sys = PeriodicEvent(epoch=epoch[itar], period=period[itar]) if phase_constraint: final_constraints = [ *constraints_list, PhaseConstraint(sys, *phase_range) ] else: final_constraints = constraints_list # TODO: Add something to check the dt in min_start_times (can bug if dt_grid too small) obs_start = min_start_times(final_constraints, obs, target, events) obs_end = max_end_times(final_constraints, obs, target, events) baseline = obs_end - obs_start t_mid = obs_start + baseline / 2 index = (obs_end - obs_start) > obs_time # ------------------- # End of steps to predict events # ------------------- # Put the infos in a table and stack it to the full table if index.any(): name = np.repeat(target.name, index.sum()).astype(str) moon = obs.moon_illumination(t_mid[index]) phase_start = sys.phase(obs_start[index]) phase_end = sys.phase(obs_end[index]) observability = obs_end[index] - obs_start[index] AM_mid = obs.altaz(t_mid[index], target).secz supp = [ np.repeat(info[key][itar], index.sum()) for key in supp_cols ] cols = [ name, obs_start[index].iso, phase_start, obs_end[index].iso, phase_end, t_mid[index].iso, AM_mid, observability.to(u.h), moon, *supp ] table_sys = Table(cols, names=col_names, masked=True) full_table = vstack([table_sys, full_table]) else: warn('No event found for ' + target.name, AstropyUserWarning) if full_table: full_table.sort('mid_phase') full_table.meta = meta else: warn('No event found at all', AstropyUserWarning) return full_table