Exemple #1
0
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)
Exemple #3
0
    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)
Exemple #4
0
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
Exemple #6
0
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
Exemple #7
0
 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
    )
Exemple #9
0
    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'))
Exemple #10
0
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
Exemple #11
0
    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