def transits(planets, obstime=None, n_eclipses=3): """ Compute next transits for a list of planets data = { 'planets' : { 'TOI01557.01' : { 'period' : 0.54348, 't0' : 2458764.780884, 'duration' : 0.0912917 }, 'SDSS' : { 'period' : 0.0666, 't0' : 2458986.6912, 'duration' : 0.0034 }, }, 'n_eclipses' : 10, 'obstime' : '2020-05-28' } """ from astroplan import EclipsingSystem if obstime: observing_time = Time(obstime) else: observing_time = Time.now() # Dict with transits for all the planets planets_transits = {} for name, planet in planets.items(): primary_eclipse_time = Time(planet['t0'], format='jd') orbital_period = planet['period'] * u.day eclipse_duration = planet['duration'] * u.day transits = EclipsingSystem(primary_eclipse_time=primary_eclipse_time, orbital_period=orbital_period, duration=eclipse_duration, name=name) next_transits = transits.next_primary_eclipse_time( observing_time, n_eclipses=n_eclipses) # Transits for this planet planet_transits = [] for transit in next_transits: planet_transits.append({ 't_early': (transit - eclipse_duration / 2).iso, 't_middle': transit.iso, 't_late': (transit + eclipse_duration / 2).iso }) planets_transits[name] = planet_transits return planets_transits
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 __init__(self, planet_name=None, period=None, transit_midpoint=None, eccentricity=None, duration14=None, duration23=None, semi_a=None, inclination=None, longitude_periastron=None, planet_radius=None, stellar_radius=None, database='nasa'): self.name = planet_name self.period = period self.transit_midpoint = transit_midpoint self.eccentricity = eccentricity self.semi_a = semi_a self.stellar_radius = stellar_radius self.planet_radius = planet_radius self.inclination = inclination self.long_periastron = longitude_periastron self.duration14 = duration14 self.duration23 = duration23 self.database = database # If period or transit_center_time are not provided, look up the data # from a database if self.period is None or self.transit_midpoint is None: assert isinstance(self.name, str), '``name`` is required to find ' \ 'transit ephemeris.' # Retrieve info from the NASA Exoplanet Archive if self.database == 'nasa': # For now use a local table instead of looking up online # because I need to figure out how to add more than one column # to the online query self.planet_properties = \ NasaExoplanetArchive.query_planet( self.name, table_path='../data/planets.csv') self.period = self.planet_properties['pl_orbper'] self.transit_midpoint = \ Time(self.planet_properties['pl_tranmid'], format='jd') self.duration14 = self.planet_properties['pl_trandur'] # Retrieve info from the Exoplanet Orbit Database elif self.database == 'orbit': self.planet_properties = \ ExoplanetOrbitDatabase.query_planet(self.name) self.period = self.planet_properties['PER'] self.transit_midpoint = \ Time(self.planet_properties['TT'], format='jd') self.duration14 = self.planet_properties['T14'] else: pass self.system = EclipsingSystem( primary_eclipse_time=self.transit_midpoint, orbital_period=self.period, duration=self.duration14, eccentricity=self.eccentricity, name=self.name)
def read_ephem_db(fileName, minDep=None, maxDur=None, obsLat=None): dtypeseq = ['U40'] dtypeseq.extend(['f8'] * 7) dtypeseq.extend(['i4'] * 2) dataBlock = np.genfromtxt(fileName, delimiter='|', dtype=dtypeseq) gtName = dataBlock['f0'] gtPer = dataBlock['f1'] gtEpc = dataBlock['f2'] gtDur = dataBlock['f3'] gtDep = dataBlock['f4'] gtRa = dataBlock['f5'] gtDec = dataBlock['f6'] gtMag = dataBlock['f7'] gtDepGd = dataBlock['f8'] gtDurGd = dataBlock['f9'] print('Read in {0:d} targets'.format(len(gtPer))) # filter out targets too shallow if minDep is not None: idx = np.where((gtDep > minDep))[0] gtName = gtName[idx] gtPer, gtEpc, gtDur, gtRa, gtDec, gtMag, gtDep, gtDepGd, gtDurGd = idx_filter(idx, \ gtPer, gtEpc, gtDur, gtRa, gtDec, gtMag, gtDep, gtDepGd, gtDurGd) # filter out targets with duration too long if maxDur is not None: idx = np.where((gtDur < maxDur))[0] gtName = gtName[idx] gtPer, gtEpc, gtDur, gtRa, gtDec, gtMag, gtDep, gtDepGd, gtDurGd = idx_filter(idx, \ gtPer, gtEpc, gtDur, gtRa, gtDec, gtMag, gtDep, gtDepGd, gtDurGd) # filter out targets too far latitude away from observatory if obsLat is not None: maxDec = np.min([90.0, obsLat + 90.0]) minDec = np.max([-90.0, obsLat - 90.0]) idx = np.where((gtDec <= maxDec) & (gtDec >= minDec))[0] gtName = gtName[idx] gtPer, gtEpc, gtDur, gtRa, gtDec, gtMag, gtDep, gtDepGd, gtDurGd = idx_filter(idx, \ gtPer, gtEpc, gtDur, gtRa, gtDec, gtMag, gtDep, gtDepGd, gtDurGd) print( 'After Observatory Latitutde, depth, and duration filter {0:d} targets left' .format(len(gtPer))) # Make list of astroplan ephemeris objects from remaining targets ephemList = [] auxList = [] for i, curName in enumerate(gtName): curEpc = Time(gtEpc[i], format='jd') curPer = gtPer[i] * u.day curDur = gtDur[i] * u.hr curEphemObj = EclipsingSystem(primary_eclipse_time=curEpc, \ orbital_period = curPer, \ duration = curDur, \ name = curName) curAuxTup = (gtRa[i], gtDec[i], gtMag[i], gtDep[i], gtDepGd[i], gtDurGd[i]) ephemList.append(curEphemObj) auxList.append(curAuxTup) return ephemList, auxList
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
class Transit(object): """ The transiting exoplanet object. It is used to predict transit events. Args: planet_name (``str``, optional): Name of the planet. Default is ``None``. period (scalar, optional): Orbital period of the planet. If set to ``None``, then the value is going to be retrieved from an online database or a local table. Default is ``None``. transit_midpoint (scalar, optional): Value of a known transit midpoint. If set to ``None``, then the value is going to be retrieved from an online database or a local table. Default is ``None``. eccentricity (``float``, optional): Value of the orbital eccentricity. If set to ``None``, then the value is going to be retrieved from an online database or a local table. Default is ``None``. duration14 (scalar, optional): Value of the transit duration from the first to fourth points of contact. If set to ``None``, then the value is going to be retrieved from an online database or a local table. Default is ``None``. duration23 (scalar, optional): Value of the transit duration from the second to third points of contact. If set to ``None``, then the value is going to be retrieved from an online database or a local table. Default is ``None``. database (``str``, optional): Database choice to look up the exoplanet parameters. The current options available are ``'nasa'`` and ``'orbit'``, which correspond to the NASA Exoplanet Archive and the Exoplanet Orbit Database, respectively. Default is ``'nasa'``. """ def __init__(self, planet_name=None, period=None, transit_midpoint=None, eccentricity=None, duration14=None, duration23=None, semi_a=None, inclination=None, longitude_periastron=None, planet_radius=None, stellar_radius=None, database='nasa'): self.name = planet_name self.period = period self.transit_midpoint = transit_midpoint self.eccentricity = eccentricity self.semi_a = semi_a self.stellar_radius = stellar_radius self.planet_radius = planet_radius self.inclination = inclination self.long_periastron = longitude_periastron self.duration14 = duration14 self.duration23 = duration23 self.database = database # If period or transit_center_time are not provided, look up the data # from a database if self.period is None or self.transit_midpoint is None: assert isinstance(self.name, str), '``name`` is required to find ' \ 'transit ephemeris.' # Retrieve info from the NASA Exoplanet Archive if self.database == 'nasa': # For now use a local table instead of looking up online # because I need to figure out how to add more than one column # to the online query self.planet_properties = \ NasaExoplanetArchive.query_planet( self.name, table_path='../data/planets.csv') self.period = self.planet_properties['pl_orbper'] self.transit_midpoint = \ Time(self.planet_properties['pl_tranmid'], format='jd') self.duration14 = self.planet_properties['pl_trandur'] # Retrieve info from the Exoplanet Orbit Database elif self.database == 'orbit': self.planet_properties = \ ExoplanetOrbitDatabase.query_planet(self.name) self.period = self.planet_properties['PER'] self.transit_midpoint = \ Time(self.planet_properties['TT'], format='jd') self.duration14 = self.planet_properties['T14'] else: pass self.system = EclipsingSystem( primary_eclipse_time=self.transit_midpoint, orbital_period=self.period, duration=self.duration14, eccentricity=self.eccentricity, name=self.name) # Find the next transit(s). def next_transit(self, jd_range): """ Method to look for transit events inside a Julian Date range. Args: jd_range (array-like): The Julian Date interval where to look up for transit events. Returns: midtransit_times (``list``): List of Julian Dates of transit events. """ jd_range = np.array(jd_range) jd0 = Time(jd_range[0], format='jd') jd1 = Time(jd_range[1], format='jd') jd_center = (jd1.jd + jd0.jd) / 2 n_transits = int((jd1 - self.transit_midpoint).value / self.period.to(u.d).value) - \ int((jd0 - self.transit_midpoint).value / self.period.to(u.d).value) if n_transits > 0: midtransit_times = \ self.system.next_primary_eclipse_time(jd0, n_eclipses=n_transits) # If no transit was found, simply figure out the closest event else: next_event = self.system.next_primary_eclipse_time(jd1, n_eclipses=1)[0] next_event = Time(next_event, format='jd') previous_event = next_event - self.period if next_event.jd - jd_center < jd_center - previous_event.jd: midtransit_times = Time([next_event.value], format='jd') else: midtransit_times = Time([previous_event.value], format='jd') return midtransit_times # Calculate the nearest transit instead of the next one def nearest_transit(self, jd_range): """ Method to look for nearest transit event inside a Julian Date range. Args: jd_range (array-like): The Julian Date interval where to look up for transit events. Returns: midtransit_times (``list``): List of Julian Dates of transit events. """ next_t = self.next_transit(jd_range)[0] previous_jd_range = (jd_range[0] - self.period.to(u.d).value, jd_range[1] - self.period.to(u.d).value) previous_t = self.next_transit(previous_jd_range)[0] center = (jd_range[0] + jd_range[1]) / 2 next_diff = next_t.jd - center prev_diff = center - previous_t.jd if next_diff > prev_diff: return previous_t else: return next_t
period_low_err = period_row["lower_error"].item() period_up_err = period_row["upper_error"].item() epoch_row = fit_results[fit_results["#name"].str.contains("_epoch")] epoch = epoch_row["median"].item() epoch_low_err = epoch_row["lower_error"].item() epoch_up_err = epoch_row["upper_error"].item() duration_row = fit_derived_results[ fit_derived_results["#property"].str.contains("Full-transit duration")] duration = duration_row["value"].item() duration_low_err = duration_row["lower_error"].item() duration_up_err = duration_row["upper_error"].item() name = "SOI_" + str(args.candidate) # TODO probably convert epoch to proper JD primary_eclipse_time = Time(epoch, format='jd') system = EclipsingSystem(primary_eclipse_time=primary_eclipse_time, orbital_period=u.Quantity(period, unit="d"), duration=u.Quantity(duration, unit="h"), name=name) observer_site = Observer(latitude=args.lat, longitude=args.lon, timezone='UTC') # 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(
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 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
name='BB/RH Tucson', location=location, pressure=0.91 * u.bar, relative_humidity=0.25, temperature=20.0 * u.deg_C, timezone=timezone('US/Mountain'), description= 'Betsy Burr and Robert Henderson observation station in Tucson, AZ.') # Orbital data from explanetarchive.ipac.caltec.edu primary_eclipse_time = Time(2454942.898400, format='jd') orbital_period = 2.150009 * u.day eclipse_duration = 3.1102 * u.hour HATP32b = EclipsingSystem(primary_eclipse_time=primary_eclipse_time, orbital_period=orbital_period, duration=eclipse_duration, name='HAT-P-32b') 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)
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