https://github.com/ExoRob/Transit-Zones
Raw File
Tip revision: df41d3728617d3600a63a7e6eebd4946a123c53f authored by Robert Wells on 31 October 2017, 11:12:48 UTC
Updated citation to published manuscript
Tip revision: df41d37
TransitZones.py
"""
This code reproduces the figures and tables in the R. Wells et al. 2017 paper.
The code is structured into different sections for each separate code block.
Parameters for the code should be set below. Set to 0 to not run or set to 1 to run the related code block.
Requires Python 2.7 to run.
"""

# Code parameters - 0/1
plot_ecl = 1            # plots all transit zones - reproduces figure 2 in the paper
                        # saves to "FigOut/AllHelioEcliptic.pdf"
plot_k2 = 1             # overplots K2 fields - reproduces figure 5 in the paper
                        # saves to "FigOut/AllHelioEcliptic+K2Fields.pdf"
plot_gal = 0            # plots orbits of planets over the galactic plane
                        # saves to "FigOut/GalacticOrbits.pdf"
find_crossovers = 0     # finds intersection points between all transit zones
                        # - set "eq" below to run using the transit zone angle, grazing angle or approximation
                        # saves pickle files to both DataIn and DataOut directories with names
                        # "all_region_corner_points_+eq+.pkl" and "regions_subplot.pkl"
plot_intersects = 0     # plots each crossover individually while searching with plt.show() - no files are saved
                        # this was used for finding the intersection points of the 3-planet crossovers
plot_subplot = 0        # plots 2 crossover regions - reproduces figure 3 in the paper
                        # requires find_crossovers = 1 to have been run with eq = 't'
                        # saves to "FigOut/RegionsSubplot.pdf"
print_region_corner_table = 0   # outputs the table of crossover regions corners - reproduces appendix 1
                                # saves to "DataOut/RegionCornerTable.csv"
find_planets = 0                # finds all known planets which fall into transit zones -reproduces appendix 3
                                # saves to "DataOut/PlanetsInZones.csv"
print_probabilities = 0         # outputs table of all transiting probabilities - reproduces appendix 2
                                # saves to "DataOut/ProbabilityTable.csv"
                                # requires find_crossovers = 1 to have been run three times with eq = 't', 'a' and 'g'
print_comparison = 0            # outputs table comparing sizes of crossover regions - reproduces table 2
                                # saves to "DataOut/ComparisonTable.csv"
                                # requires find_crossovers = 1 to have been run three times with eq = 't', 'a' and 'g'
plot_comparison = 0             # plots comparison of a crossover region size - reproduces figure 4 in paper
                                # saves to "FigOut/AreaComparison.pdf"
                                # - set "comp_region" below to choose which crossover. e.g. 4,5: Jupiter, Saturn
                                # - set "plot_cs" below to choose which angles to compare. e.g. tz angle to approx
                                # requires find_crossovers = 1 to have been run three times with eq = 't', 'a' and 'g'
print_planet_params = 0         # outputs table of TZ & grazing angle, plus transit depths - reproduces table 1
                                # saves to "DataOut/PlanetParameters.csv"

dens = 400                  # number of data points / degree in fits
eq = 't'                    # run crossover code for transit zone angle, approximation or grazing angle ('t', 'a', 'g')
comp_region = [4, 5]        # regions to plot in comparison, 0:Mercury - 7:Neptune
plot_cs = [0, 2]            # which angles to compare - 0:transit zone, 1:approximation, 2:grazing

# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- -
# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- -
"""Code setup"""

if plot_k2 == 1 and plot_ecl == 0:              # plot_k2 requires plot_ecl
    plot_ecl = 1

plot_cs.sort()          # plot comparison in order: t, a, g
comp_region.sort()      # sort comparison region to match code

import numpy as np
from matplotlib import pyplot as plt
from astropy.coordinates import SkyCoord
from astropy import units as u
import matplotlib.cm as cm
import os
from scipy.optimize import curve_fit, fsolve
from itertools import combinations
import pickle
import sys
import matplotlib.image as mpimg
import pandas

outdirs = ['FigOut', 'DataOut']
datadirs = ['DataIn', 'DataOut']
for directory in outdirs:         # create output directories if not present
    if not os.path.exists(directory):
        os.makedirs(directory)
pandas.set_option('chained_assignment', None)   # turn off pandas copy warning

def TZ_calc(R_p, a, R_s):
    """
    Calculates the transit zone angle
    :param R_p: Planetary radius
    :param a: Sun-planet distance
    :param R_s: Solar radius
    :return: Transit zone angle
    """
    return np.degrees(2.0 * (np.arctan(R_s / a) - np.arcsin(R_p / np.sqrt(a*a + R_s*R_s))))


def graze_calc(R_p, a, R_s):            # calculates grazing angle
    return np.degrees(2.0 * np.arctan((R_p + R_s) / a))


def approx_TZ_calc(a, R_s):
    return np.degrees(2.0 * R_s / a)


def fit(x, A, d):                       # fits sine curve
    x = np.radians(x)

    return A * np.sin(x - d)


def fit2(x, A, d, c):                   # fits sine curve + offset
    x = np.radians(x)

    return A * np.sin(x - d) + c


def nfit(x, A1, d1, A2, d2, c, sign):   # transit zone equation to solve for crossovers
    x = np.radians(x)

    return A1 * np.sin(x - d1) + sign * (A2 * np.sin(x - d2) + c) / 2.0


def sd_to_prob(sd):              # converts square degrees to a transiting probability
    return sd * (np.pi / 180.0)**2.0 / (4.0 * np.pi)

# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- -
"""Loads planetary data and compute angles"""

# data from http://solarsystem.nasa.gov/planets/
au = 149597870700.0 / 1000.0    # 1 AU (km)
R_sun = 695508.0                # Radius of Sun (km)
names = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']     # names of planets
radii = [2.4397E3, 6.0518E3, 6.3710E3, 3.3895E3, 6.9911E4, 5.8232E4, 2.5362E4, 2.4622E4]    # radii of planets
s_d = [57.909227E6, 1.0820948E8, 1.4959826E8, 2.2794382E8, 7.7834082E8, 1.4266664E9,        # semi-major axis
       2.8706582E9, 4.4983964E9]

colors = cm.rainbow(np.linspace(0, 1, len(names)))              # range of colours for plotting
cols = ['darkmagenta', 'darkolivegreen', 'darkgoldenrod']       # colours for plotting

sun_distances = []          # Sun-planet distances over 1 complete orbit from JPL Horizons
for i in range(len(names)):
    a = np.genfromtxt('OrbitData/ecl_helio_'+names[i]+'.txt', delimiter=',', skip_header=34, skip_footer=50)[:, 8]
    sun_distances.append(a)

psi_TZ = []             # transit zone angle
graze = []              # grazing angle
transit_depth = []      # transit depth
psi_TZ_ar = []          # variable transit zone angle over 1 orbit
graze_ar = []           # variable grazing angle over 1 orbit
approx_ar = []          # variable approximated angle over 1 orbit
for i in range(len(names)):
    R = radii[i]                # planetary radius
    d = sun_distances[i]        # semi-major axis

    # compute angles over 1 complete orbit
    approx_ar.append([])
    psi_TZ_ar.append([])
    graze_ar.append([])
    for j in range(len(d)):
        psi_TZ_ar[i].append(TZ_calc(R, d[j]*au, R_sun))
        approx_ar[i].append(approx_TZ_calc(d[j] * au, R_sun))
        graze_ar[i].append(graze_calc(R, d[j] * au, R_sun))

    psi = TZ_calc(R, s_d[i], R_sun)
    psi_TZ.append(psi)

    td = R**2 / R_sun**2
    transit_depth.append(td)

    grz = graze_calc(R, s_d[i], R_sun)
    graze.append(grz)

# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- -
"""Outputs table of planetary parameters"""

if print_planet_params == 1:
    tab1str = 'Planet,TZ angle,Grazing angle,% diff,Transit depth\n'        # construct string
    for i in range(len(names)):
        diff_grz = round((graze[i] - psi_TZ[i]) / psi_TZ[i] * 100.0, 1)     # % difference between tz angle and approx

        tab1str += names[i] + ',' + str(format(psi_TZ[i], '.4f')) + ',' + str(format(graze[i], '.4f')) + ',' + \
                   str(diff_grz) + ',' + str(format(transit_depth[i]*100.0, '.4f')) + '\n'

    with open('DataOut/PlanetParameters.csv', 'w') as f:                    # save to file
        f.write(tab1str)
    print '> Planet parameters table saved to \"DataOut/PlanetParameters.csv\".'

# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- -
"""Loads JPL Horizons data and makes any needed files"""

# Load ecliptic data from JPL Horizons
ecl_lon_list, ecl_lat_list = [], []     # helio-centric ecliptic coordinates of the solar system planets over 1 orbit
for i in range(len(names)):
    ecl_lon_list.append(np.genfromtxt('OrbitData/ecl_helio_' + names[i] + '.txt', delimiter=',', skip_header=34,
                                      skip_footer=50)[:, 6])
    ecl_lat_list.append(np.genfromtxt('OrbitData/ecl_helio_' + names[i] + '.txt', delimiter=',', skip_header=34,
                                      skip_footer=50)[:, 7])

# Make galactic coordinates file
if plot_gal == 1:
    exists = []
    for i in range(len(names)):
        exists.append(os.path.isfile('OrbitData/gal_' + names[i] + '.txt'))
    gal_files_exist = set(exists) == {True}     # do files exist already?

    if not gal_files_exist:                     # if not, then make them
        print '> Making galactic coordinate files.'
        for i in range(len(names)):
            print '> >', i + 1, '/', len(names)
            txt_file = 'OrbitData/gal_' + names[i] + '.txt'
            with open(txt_file, 'w') as f:
                for j in range(len(ecl_lon_list[i])):       # convert to galactic coordinate system
                    g = SkyCoord(ecl_lon_list[i][j], ecl_lat_list[i][j], unit=(u.degree, u.degree),
                                 distance=30.0*u.lyr, frame='heliocentrictrueecliptic', equinox='J2000.0').galactic
                    f.write(str(g.l.degree) + '\t' + str(g.b.degree) + '\n')    # save to file

# Make ecliptic K2 fields file
if plot_k2 == 1:
    # K2 field coordinates (equatorial) from https://keplerscience.arc.nasa.gov/k2-fields.html#machine-readable-files
    d = np.genfromtxt('DataIn/k2-footprint.csv', delimiter=',', skip_header=1)

    cs_inds = []    # first coordinate of campaigns for annotating
    for i in range(17):
        cs_inds.append(list(d[:, 0]).index(i))

    if not os.path.exists('DataIn/K2_fields_ecliptic.pkl'):         # convert to ecliptic if file not present
        print '> Making ecliptic K2 fields file.'
        lon_l, lat_l = [], []

        for j in range(len(d)):
            rr, dd = [], []
            for i in range(4):  # convert to ecliptic
                ecl_c = SkyCoord(d[j][6 + 2 * i], d[j][7 + 2 * i], unit=(u.degree, u.degree), frame='icrs',
                                 equinox='J2000.0', distance=30.0 * u.lyr).heliocentrictrueecliptic

                rr.append(ecl_c.lon.degree)
                dd.append(ecl_c.lat.degree)
            lon_l.append(rr)
            lat_l.append(dd)

            if j % 100 == 0 or j+1 == len(d):
                print '> >', j, '/', len(d)

        for od in datadirs:
            with open(od+'/K2_fields_ecliptic.pkl', 'wb') as f:      # save to pickle file
                pickle.dump([lon_l, lat_l], f)

    else:       # load pickle file
        with open('DataIn/K2_fields_ecliptic.pkl', 'rb') as f:
            lon_l, lat_l = pickle.load(f)

# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- -
"""Fits sinusoidal curves to the ecliptic coordinates and variable angles over 1 complete orbit
   The parameter 'dens' gives the density of the curves. I.e. 'dens' datapoints per degree"""

print '> Fitting curves to data. (dens = ' + str(dens) + ')'
data_fits = []      # holds all fits to the coordinates
fit_params = []     # holds all parameters of each fit
psi_fits = []       # transit zone angle
psi_params = []
graze_fits = []     # grazing angle
graze_params = []
approx_fits = []    # approximate angle
approx_params = []

if plot_ecl == 1:
    fig = plt.figure(figsize=(15, 7))       # initialise figure
    ax = fig.add_subplot(111)
    plt.minorticks_on()

for i in range(len(names)):
    popt1, pcov1 = curve_fit(fit, ecl_lon_list[i], ecl_lat_list[i])     # fit coordinates to sine curve
    fit_params.append(popt1)

    popt2, pcov2 = curve_fit(fit2, ecl_lon_list[i], psi_TZ_ar[i])       # transit zone angle
    psi_params.append(popt2)

    popt3, pcov3 = curve_fit(fit2, ecl_lon_list[i], graze_ar[i])        # grazing angle
    graze_params.append(popt3)

    popt4, pcov4 = curve_fit(fit2, ecl_lon_list[i], approx_ar[i])       # approximate angle
    approx_params.append(popt4)

    data_fit = []
    psi_fit = []
    graze_fit = []
    approx_fit = []
    x_fit = []      # longitude for fit

    for j in range(360 * dens):
        data_fit.append(fit(j / float(dens), popt1[0], popt1[1]))
        psi_fit.append(fit2(j / float(dens), popt2[0], popt2[1], popt2[2]))
        graze_fit.append(fit2(j / float(dens), popt3[0], popt3[1], popt3[2]))
        approx_fit.append(fit2(j / float(dens), popt4[0], popt4[1], popt4[2]))
        x_fit.append(j / float(dens))

    approx_fits.append(approx_fit)
    psi_fits.append(psi_fit)
    data_fits.append(data_fit)
    graze_fits.append(graze_fit)

    if plot_ecl == 1:
        if i != 2:          # colours on plot - Earth as black
            c = colors[i]
        else:
            c = 'black'
        df1 = data_fit + np.asarray(psi_fits[i]) / 2.0      # upper transit zone boundary
        df2 = data_fit - np.asarray(psi_fits[i]) / 2.0      # lower transit zone boundary

        # sample boundaries for smaller filesize of plot
        x_fit_c, df1_c, df2_c = [], [], []
        for k in range(0, len(x_fit), dens/25):
            x_fit_c.append(x_fit[k])
            df1_c.append(df1[k])
            df2_c.append(df2[k])
        x_fit_c, df1_c, df2_c = np.asarray(x_fit_c), np.asarray(df1_c), np.asarray(df2_c)

        ax.fill_between(x_fit_c, df1_c, df2_c, where=df1_c >= df2_c, edgecolor=c,       # plot zones as coloured bands
                        facecolor=c, alpha=0.4, interpolate=True, label=names[i])

if plot_ecl == 1:
    if plot_k2 == 1:
        for i in range(len(d)):             # plot areas between corner points of detectors
            plt.fill(lon_l[i], lat_l[i], edgecolor='grey', facecolor='grey', alpha=0.5, zorder=1)

        for i in range(len(cs_inds)):       # annotate campaign numbers
            campaign_start = [lon_l[cs_inds[i]][0], lat_l[cs_inds[i]][0]]
            plt.annotate(i, xy=campaign_start, xytext=(campaign_start[0] - 15, campaign_start[1]), fontsize=20,
                         color='r')

        figname = 'FigOut/AllHelioEcliptic+K2Fields.pdf'
    else:
        figname = 'FigOut/AllHelioEcliptic.pdf'

    ax.set_xlabel('Longitude (Degrees)', fontsize=15)
    ax.set_ylabel('Latitude (Degrees)', fontsize=15)
    ax.set_xlim(0.0, 360.0)
    ax.get_xaxis().tick_bottom()
    ax.get_yaxis().tick_left()
    plt.tick_params(axis='both', which='both', width=2)
    plt.tick_params(axis='both', which='major', length=7)
    plt.tick_params(axis='both', which='minor', length=4)
    ax.legend(loc=1)
    plt.savefig(figname, format='pdf', dpi=300, bbox_inches='tight', pad_inches=0)
    print '> Transit zones plot saved to \"'+figname+'\".'

if plot_gal == 1:
    fig = plt.figure(figsize=(10.0, 5.0))
    ax = fig.add_subplot(111, projection='mollweide')       # make mollweide projection plot
    ax.grid(color='darkgrey', lw=2)
    img = mpimg.imread('DataIn/GalaxyImage.png')            # plot over Mellinger + 2MASS galaxy image
    ax.imshow(img, extent=[-np.pi, np.pi, -np.pi / 2.0, np.pi / 2.0], aspect='auto')

    for i in range(len(names)):
        gal_file = np.genfromtxt('OrbitData/gal_' + names[i] + '.txt', delimiter='\t')
        gal_l = gal_file[:, 0] - 180.0      # galactic longitude
        gal_b = gal_file[:, 1]              # galactic latitude
        gal_l, gal_b = zip(*sorted(zip(np.radians(gal_l), np.radians(gal_b))))

        plt.plot(gal_l, gal_b, color='lightgrey', lw=2, zorder=5)

    plt.xlabel('Longitude (degrees)', fontsize=15)
    plt.ylabel('Latitude (degrees)', fontsize=15)
    [i.set_color("darkgrey") for i in plt.gca().get_xticklabels()]
    ax.tick_params(labelsize=15)
    plt.savefig('FigOut/GalacticOrbits.pdf', format='pdf', dpi=200, bbox_inches='tight', pad_inches=0)
    print '> Galactic plot saved to \"FigOut/GalacticOrbits.pdf\".'
    plt.clf()

# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- -
"""Finds intersection points of the transit zones"""

if find_crossovers == 1 or plot_intersects == 1:
    if eq == 't':                       # load data for specified angle
        x_ar = np.asarray(psi_fits)
        para = psi_params
    elif eq == 'a':
        x_ar = np.asarray(approx_fits)
        para = approx_params
    elif eq == 'g':
        x_ar = np.asarray(graze_fits)
        para = graze_params
    else:
        print '\n-- Bad eq (t, a, g)'
        sys.exit()

    print '> Looking for crossovers between transit zones.'
    planet_inds = np.linspace(0, 7, 8, dtype='int')    # planet ind numbers
    pls_list = []       # holds all combinations of planet_inds (2 & 3)
    for i in combinations(planet_inds, 2):      # all combinations of 2 planets
        pls_list.append(i)
    for i in combinations(planet_inds, 3):      # all combinations of 3 planets
        pls_list.append(i)

    region_pls_list, region_lon_list, region_lat_list = [], [], []    # hold all region corner point coordinates

    # load indices of intersect points - found by running with plot_intersects=1
    c3file = np.genfromtxt('DataIn/3-crossover_regions.txt', delimiter='\t', dtype='string')
    c3pls = []
    c3points = []
    for p in c3file:
        intpts = []
        c3pls.append(p[0])
        if p[1] == '-':
            c3points.append('-')
        else:
            for i in p[1].split(','):
                intpts.append(int(i))
            c3points.append(intpts)

    sp = []     # for subplot code
    t = np.linspace(0.0, 350.0, 36)     # spacing to search for crossovers

    for pls in pls_list:    # loop over all combinations
        n = len(pls)        # number of planets (1, 2 or 3)

        using_names = ''    # names of planets being used
        for i in range(n):
            using_names += names[pls[i]]
            if i != n - 1:
                using_names += ', '
        print '> > Looking for intersects between:', using_names

        if n != 2 and n != 3:     # no 4+ regions exist
            print '\n-- n is not a usable number. n =', n
            sys.exit()

        # sine function fits to data +/- psi
        l = []
        for i in pls:
            l.append(data_fits[i] + x_ar[i] / 2.0)      # upper transit zone boundary
            l.append(data_fits[i] - x_ar[i] / 2.0)      # lower transit zone boundary

        # find intersection points - 2 for each
        x_list = []     # longitude of intersect
        y_list = []     # latitude of intersect
        idcs = []
        done = []
        for i in range(n*2):            # loop over all boundaries with all boundaries
            for j in range(n*2):
                ieven = i % 2 == 0      # True / False
                jeven = j % 2 == 0
                if ieven:
                    i_range = [i, i + 1]        # indices of first zone's boundaries
                    isign = 1                   # plus angle/2
                    pli = pls[i / 2]            # indice of planet 1
                else:
                    i_range = [i - 1, i]
                    isign = -1                  # minus angle/2
                    pli = pls[(i - 1) / 2]
                if jeven:
                    jsign = 1                   # plus angle/2
                    plj = pls[j / 2]            # indice of planet 2
                else:
                    jsign = -1                  # minus angle/2
                    plj = pls[(j - 1) / 2]

                rev_str = str(j) + str(i)       # reverse of indices to check if done already

                if j not in i_range and rev_str not in done:   # if not the same planet and combination not already done
                    # solve for crossover points at each t step
                    fs = fsolve(lambda x: nfit(x, fit_params[pli][0], fit_params[pli][1], para[pli][0],
                                               para[pli][1], para[pli][2], isign) -
                                          nfit(x, fit_params[plj][0], fit_params[plj][1], para[plj][0],
                                               para[plj][1], para[plj][2], jsign), t)

                    # get unique between 0 and 360 degrees only
                    res = []
                    for k in fs:
                        if 0.0 <= k < 360.0:
                            res.append(round(k, 4))
                    dmy = list(set(res))
                    fs_x = []
                    for dm in range(-1, len(dmy)-1):
                        if not dmy[dm-1]-0.0002 < dmy[dm] < dmy[dm+1]+0.0002:
                            fs_x.append(dmy[dm])
                    x_list += fs_x

                    for x in fs_x:     # latitude of crossover points
                        y_list.append(round(nfit(x, fit_params[pli][0], fit_params[pli][1], para[pli][0],
                                                 para[pli][1], para[pli][2], isign), 4))
                    idcs.append([i, j])
                    done.append(str(i) + str(j))

        x_list, y_list = zip(*sorted(zip(x_list, y_list)))      # order lists by longitude

        if plot_intersects == 1:     # for finding region corners by eye
            plt.plot(x_list, y_list, marker='o', ls='', color='black')     # plot points
            for i in range(len(x_list)):                                   # annotate index in list
                plt.annotate(str(i), (x_list[i], y_list[i]), fontsize=15, color='black')

        if n == 3:      # 3-planet crossover
            # get fill region
            y1 = np.array(map(min, zip(l[2], l[4])))
            y2 = np.array(map(max, zip(l[1], l[5])))
            y3 = np.array(map(min, zip(y1, l[0])))
            y4 = np.array(map(max, zip(y2, l[3])))
            plt.fill_between(x_fit, y3, y4, where=y3 >= y4, color='grey', alpha=0.8)
            # plt.show()

            plsstr = ''                  # string of pls, e.g. '123'
            for i in pls:
                plsstr += str(i)

            idx = c3pls.index(plsstr)    # get index of permutation in file
            point_inds = c3points[idx]   # indices of points in x_ & y_lists

            if point_inds != '-':        # if crossover exists
                xx, yy = [], []
                for i in point_inds:
                    xx.append(x_list[i])
                    yy.append(y_list[i])
                is_region = True
            else:                        # if no crossover
                is_region = False

        elif n == 2:        # 2-planet crossover
            # get fill region
            y1 = np.array(map(min, zip(l[0], l[2])))
            y2 = np.array(map(max, zip(l[1], l[3])))
            plt.fill_between(x_fit, y1, y2, where=y1 >= y2, color='grey', alpha=0.8)
            # plt.show()

            regionsx, regionsy = [[], []], [[], []]     # separate regions
            v = 60.0                                    # maximum spacing between points

            if list(pls) == [4,7]:      # Jupiter-Neptune region crosses 0/360 line
                for i in range(len(x_list)):
                    if x_list[2] - v <= x_list[i] <= x_list[2] + v:
                        regionsx[0].append(x_list[i])
                        regionsy[0].append(y_list[i])
                    else:
                        regionsx[1].append(x_list[i])
                        regionsy[1].append(y_list[i])

                x0, y0 = regionsx[1][0], regionsy[1][0]
                del regionsx[1][0]
                del regionsy[1][0]
                regionsx[1].append(x0)
                regionsy[1].append(y0)

            else:
                for i in range(len(x_list)):
                    if x_list[5] - v <= x_list[i] <= x_list[5] + v:
                        regionsx[1].append(x_list[i])
                        regionsy[1].append(y_list[i])
                    else:
                        regionsx[0].append(x_list[i])
                        regionsy[0].append(y_list[i])

            xx = regionsx[0] + regionsx[1]
            yy = regionsy[0] + regionsy[1]
            is_region = True

        if is_region is False:
            print '> > > Region not found.'

        else:
            print '> > >', len(xx), 'corners found.'
            # save region corner coordinates
            region_pls_list.append(pls)
            region_lon_list.append(xx)
            region_lat_list.append(yy)

        if plot_intersects == 1:
            for i in range(len(l)):
                cid = (i - (i % 2)) / 2
                col = cols[cid]
                ls = '-'
                if i % 2 == 0:
                    lab = names[pls[i/2]]
                else:
                    lab = None
                plt.plot(x_fit, l[i], color=col, label=lab, ls=ls, lw=1.5)      # plot boundaries

            if is_region:
                plt.plot(xx, yy, 'o', color='black')        # plot intersection points

            plt.xlim(0, 360)
            plt.legend()
            plt.xlabel('Longitude (Degrees)')
            plt.ylabel('Latitude (Degrees)')
            plt.title('Regions of the galaxy which can detect transits of '+using_names)
            plt.tick_params(axis='both', which='both', width=2)
            plt.tick_params(axis='both', which='major', length=7)
            plt.tick_params(axis='both', which='minor', length=4)

            if is_region:
                plt.show()
            else:
                plt.clf()

        if pls == (3, 4) or pls == (0, 2, 3) and eq == 't':     # save for regions subplot (plot_subplot=1)
            sp.append([])
            sp[-1].append(pls)
            sp[-1].append(xx)
            sp[-1].append(yy)
            sp[-1].append(l)
            if n == 2:
                sp[-1].append([y1, y2])
            else:
                sp[-1].append([y3, y4])

    all = [region_pls_list, region_lon_list, region_lat_list]   # save to pickle file for table
    for od in datadirs:
        with open(od+'/all_region_corner_points_'+eq+'.pkl', 'wb') as f:
            pickle.dump(all, f)

    if eq == 't':
        sp.append(x_fit)
        for od in datadirs:
            with open(od+'/regions_subplot.pkl', 'wb') as f:         # for subplot of regions
                pickle.dump(sp, f)

# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- -
"""Plots 2 regions in one figure (figure 3 in paper)
   Requires 'DataIn/regions_subplot.pkl' file from running find_crossovers=1 with eq='t'"""

if plot_subplot == 1:
    if not os.path.exists('DataIn/regions_subplot.pkl'):
        print '\n-- First run with find_crossovers = 1 with eq = t.'
        sys.exit()

    with open('DataIn/regions_subplot.pkl', 'rb') as f:     # load subplot data
        sp = pickle.load(f)

    x_fit = sp[2]

    f, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(15, 7))     # initialise figure

    for i in range(2):
        pls = sp[i][0]          # planets in crossover
        xx = sp[i][1]           # logitude of intersect points
        yy = sp[i][2]           # latitude of intersect points
        l = sp[i][3]            # all transit zone boundaries
        y1, y2 = sp[i][4]       # borders of crossover region

        for j in range(len(l)):
            cid = (j - (j % 2)) / 2
            col = cols[cid]
            if j % 2 == 0:
                lab = names[pls[j/2]]
            else:
                lab = None

            if i == 0:
                ax1.plot(x_fit, l[j], color=col, label=lab, lw=3, alpha=1)          # plot transit zone boundaries
                ax1.set_xlim(170, 200)
                ax1.set_ylim(1.2, 1.4)
                ax1.plot(xx, yy, marker='o', markersize=6, ls='', color='black')    # plot intersection points
            else:
                ax2.plot(x_fit, l[j], color=col, label=lab, lw=3, alpha=1)
                ax2.set_xlim(35, 60)
                ax2.set_ylim(-0.5, 0.5)
                ax2.plot(xx, yy, marker='o', markersize=6, ls='', color='black')

        if i == 0:
            ax1.fill_between(x_fit, y1, y2, where=y1 >= y2, color='grey', alpha=0.6)    # shade crossover region
        else:
            ax2.fill_between(x_fit, y1, y2, where=y1 >= y2, color='grey', alpha=0.6)

    ax1.minorticks_on()
    ax2.minorticks_on()
    ax1.legend()
    ax2.legend()
    f.text(0.5, 0.04, 'Longitude (Degrees)', ha='center', va='center', fontsize=15)
    ax1.set_ylabel('Latitude (Degrees)', fontsize=15)
    ax1.tick_params(axis='both', which='both', width=2)
    ax1.tick_params(axis='both', which='major', length=7)
    ax1.tick_params(axis='both', which='minor', length=4)
    ax2.tick_params(axis='both', which='both', width=2)
    ax2.tick_params(axis='both', which='major', length=7)
    ax2.tick_params(axis='both', which='minor', length=4)
    plt.savefig('FigOut/RegionsSubplot.pdf', format='pdf', dpi=1000, bbox_inches='tight', pad_inches=0)
    print '> Subplot saved to \"FigOut/RegionsSubplot.pdf\".'

# --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- -
"""Outputs a table of all crossover intersection points using the transit zone angle
   Reproduces appendix 1 in the paper"""

if print_region_corner_table == 1:
    with open('DataIn/all_region_corner_points_t.pkl', 'rb') as f:      # load regions
        planets, lon, lat = pickle.load(f)

    ra, dec = [], []                # equatorial coordinates of intersection points
    for i in range(len(lon)):
        ra.append([])
        dec.append([])
        for j in range(len(lon[i])):
            eq = SkyCoord(float(lon[i][j]), float(lat[i][j]), unit=(u.degree, u.degree),    # convert to equatorial
                          frame='heliocentrictrueecliptic', equinox='J2000.0', distance=30.0 * u.lyr).icrs
            ra[i].append(format(eq.ra.degree, '.4f'))       # 4 dp
            dec[i].append(format(eq.dec.degree, '.4f'))

    for i in range(len(ra)):        # order regions
        n = len(ra[i]) / 2
        reg1 = [ra[i][:n], dec[i][:n]]
        reg2 = [ra[i][n:], dec[i][n:]]
        reg1[0], reg1[1] = zip(*sorted(zip(reg1[0], reg1[1])))
        reg2[0], reg2[1] = zip(*sorted(zip(reg2[0], reg2[1])))

        ra[i] = reg1[0] + reg2[0]
        dec[i] = reg1[1] + reg2[1]

    string = ''
    for i in range(len(planets)):       # loop all regions
        n_pls = len(planets[i])         # number of planets in region

        for j in range(n_pls):          # planets in region
            pl_ind = planets[i][j]
            string += names[pl_ind]
            if j != n_pls - 1:
                string += '; '
            elif j == n_pls - 1:
                string += ','

        if i == 31:                     # Me, E, U (3 points, 1 region)
            n_pts = 3

            for j in range(n_pts):      # longitude
                string += str(ra[i][j])
                if j != n_pts - 1:
                    string += '; '
                else:
                    string += ','

            for j in range(n_pts):      # latitude
                string += str(dec[i][j])
                if j != n_pts - 1:
                    string += '; '
                else:
                    string += ','

        else:
            n_pts = len(ra[i]) / 2

            # region 1
            for j in range(n_pts):      # longitude
                string += str(ra[i][j])
                if j != n_pts - 1:
                    string += '; '
                else:
                    string += ','

            for j in range(n_pts):      # latitude
                string += str(dec[i][j])
                if j != n_pts - 1:
                    string += '; '
                else:
                    string += ','

            string += '\n,'             # next line for second region

            # region 2
            for j in range(n_pts, n_pts * 2):   # longitude
                string += str(ra[i][j])
                if j != n_pts * 2 - 1:
                    string += '; '
                else:
                    string += ','

            for j in range(n_pts, n_pts * 2):   # latitude
                string += str(dec[i][j])
                if j != n_pts * 2 - 1:
                    string += '; '

        string += '\n'

    with open('DataOut/RegionCornerTable.csv', 'w') as f:       # save table to file
        f.write(string)
    print '> Crossover corner table saved to \"DataOut/RegionCornerTable.csv\"'

# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
"""Checks coordinates of known exoplanets to identify ones which fall into one or more transit zones
   Reproduces appendix 3 from the paper"""

if find_planets == 1:
    print '> Searching for exoplanets in transit zones.'
    # Load file containing all known exoplanets from http://exoplanet.eu/catalog/
    df = pandas.read_csv('DataIn/exoplanet.eu_catalog.csv', delimiter=',', low_memory=False)

    start_ind, end_ind = 0, len(df['# name'])           # go through entire file
    name, status, ecl_lon, ecl_lat, regions, total, radec, detect, mass, radius, period = \
        [], [], [], [], [], [], [], [], [], [], []      # information to save for each planet in a transit zone

    for j in range(start_ind, end_ind):         # go through csv file
        try:
            ra_dec = [df['ra'][j], df['dec'][j]]        # RA and Dec of planet

            ecl_c = SkyCoord(ra_dec[0], ra_dec[1], unit=(u.degree, u.degree), frame='icrs', equinox='J2000.0',
                             distance=30.0*u.lyr).heliocentrictrueecliptic      # convert to ecliptic
            ecliptic_lon = ecl_c.lon.degree                                     # ecliptic coordinates
            ecliptic_lat = ecl_c.lat.degree

            in_regs = []        # in regions, e.g. [Earth, Mars]
            for i in range(len(names)):         # check if in each planet's transit zone
                A1 = fit_params[i][0]           # load fits to transit zones and angles
                d1 = fit_params[i][1]
                A2 = psi_params[i][0]
                d2 = psi_params[i][1]
                c = psi_params[i][2]

                upper = fit(ecliptic_lon, A1, d1) + fit2(ecliptic_lon, A2, d2, c) / 2.0     # max longitude
                lower = fit(ecliptic_lon, A1, d1) - fit2(ecliptic_lon, A2, d2, c) / 2.0     # min longitude

                if lower <= ecliptic_lat <= upper:      # if within transit zone
                    in_regs.append(names[i])

            if len(in_regs) > 0 and ra_dec != [0.0, 0.0]:                 # if in 1 or more transit zones, save info
                name.append(df['# name'][j])
                status.append(df['planet_status'][j])
                ecl_lon.append(ecliptic_lon)
                ecl_lat.append(ecliptic_lat)
                regions.append(in_regs)
                total.append(len(in_regs))
                radec.append(ra_dec)
                detect.append(df['detection_type'][j])
                mass.append(df['mass'][j])
                radius.append(df['radius'][j])
                period.append(df['orbital_period'][j])

        except ValueError:
            print j, '= error\n'

        if j % 100 == 0 or j+1 == end_ind:
            print '> >', j, '/', end_ind

    n_conf, n_unconf, n_cand = status.count('Confirmed'), status.count('Unconfirmed'), status.count('Candidate')
    print '> > Found:', n_conf, 'confirmed,', n_unconf, 'unconfirmed and', n_cand, 'candidates.'

    string = 'Name,Status,RA/Dec,Zones,Total,Mass (M_J),Radius (R_J),Period (days)\n'
    for i in range(len(name)):      # construct table
        string += name[i] + ',' + status[i] + ',' + str(format(radec[i][0], '.4f')) + str(format(radec[i][1], '.4f'))\
                  + ',' + ';'.join(regions[i]) + ',' + str(total[i]) + ',' + str(mass[i]) + ',' + str(radius[i]) +\
                  ',' + str(period[i]) + '\n'
    with open('DataOut/PlanetsInZones.csv', 'w') as f:      # save table to file
        f.write(string)
    print '> Known exoplanets in transit zones table saved to \"DataOut/PlanetsInZones.csv\".'

    # for i in range(8):
    #     if i == 2:
    #         c = 'black'
    #     else:
    #         c = colors[i]
    #     df1 = data_fits[i] + np.asarray(psi_fits[i]) / 2.0
    #     df2 = data_fits[i] - np.asarray(psi_fits[i]) / 2.0
    #     plt.fill_between(x_fit, df1, df2, where=df1 >= df2, edgecolor=c, facecolor=c, alpha=0.4, interpolate=True,
    #                      label=names[i])
    # plt.plot(ecl_lon, ecl_lat, 'o', color='black')
    # plt.xlim(0, 360)
    # plt.legend()
    # plt.show()

# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- --
"""Computes geometric transit probabilities for all transit zones and crossover regions
   print_probabilities and print_comparison reproduce appendix 2 and table 2 from the paper respectively
   requires .pkl files made by running find_crossovers=1 three times with eq='t', 'a' and 'g'"""

if print_probabilities == 1 or print_comparison == 1 or plot_comparison == 1:
    not_made = []
    for x in ['t', 'a', 'g']:         # test if files are made
        comp_file = 'DataIn/all_region_corner_points_' + x + '.pkl'
        if not os.path.exists(comp_file):
            not_made.append(x)
    if len(not_made) != 0:
        print '\n-- First run with find_crossovers = 1 with eq = ' + ', '.join(not_made) + '. Then re-run.'
        sys.exit()

    print '> Computing geometric transit probabilities.'
    x_fit = np.asarray(x_fit)                   # longitude of fits
    prob_str = 'Set,P,P/P_Earth\n'              # for probability table
    comp_str = 'Set,P_TZ,P_approx,% diff\n'     # for comparison table

    if print_probabilities == 0 and print_comparison == 0:
        pls_list = [tuple(comp_region)]

    else:
        planet_inds = np.linspace(0, 7, 8, dtype='int')     # [0 - 7]
        pls_list = []           # holds all combinations of planet_inds
        for i in range(8):      # list of single planets
            pls_list.append([i])
        for i in combinations(planet_inds, 2):      # all combinations of 2 planets
            pls_list.append(i)
        for i in combinations(planet_inds, 3):      # all combinations of 3 planets
            pls_list.append(i)

    # parameters for comparison plot
    tag_cols = ['grey', 'mediumseagreen', 'mediumblue']
    tag_labels = ['TZ', 'Approx', 'Graze']
    tag_alphas = [0.8, 0.3, 0.3]

    # sets of planets to compare for table
    comp_sets = [[0, 1], [1, 2], [2, 3], [0, 2, 3], [0, 1, 5], [0, 2, 6], [1, 2, 6], [0, 3, 6], [4, 5, 6], [0, 1, 7],
                 [3, 4, 7]]
    at_earth = 0.00460783226838         # transit probability of Earth

    lon, lat = [], []
    for eq in ['t', 'a', 'g']:      # load intersection points for each angle
        with open('DataIn/all_region_corner_points_' + eq + '.pkl', 'rb') as f:  # load regions for each case
            all = pickle.load(f)
        planets = all[0]
        lon.append(all[1])
        lat.append(all[2])

    plt.figure(figsize=(9, 7))      # for comparison plot

    for pls in pls_list:        # loop over all combinations
        n = len(pls)            # number of planets in combination

        using_names = ''        # names of planets in combination
        for i in range(n):
            using_names += names[pls[i]]
            if i != n - 1:
                using_names += ';'

        # transit zone boundaries for each angle
        l = []      # tz
        l_a = []    # approx
        l_g = []    # graze
        for i in pls:
            l.append(data_fits[i] + np.asarray(psi_fits[i]) / 2.0)      # upper
            l.append(data_fits[i] - np.asarray(psi_fits[i]) / 2.0)      # lower
            l_a.append(data_fits[i] + np.asarray(approx_fits[i]) / 2.0)
            l_a.append(data_fits[i] - np.asarray(approx_fits[i]) / 2.0)
            l_g.append(data_fits[i] + np.asarray(graze_fits[i]) / 2.0)
            l_g.append(data_fits[i] - np.asarray(graze_fits[i]) / 2.0)

        if n == 1:      # single planet probabilities
            i = pls[0]
            at = sd_to_prob(np.trapz(l[0], x=x_fit) - np.trapz(l[1], x=x_fit))              # tz probability
            approx_at = sd_to_prob(np.trapz(l_a[0], x=x_fit) - np.trapz(l_a[1], x=x_fit))   # approx probability
            graze_at = sd_to_prob(np.trapz(l_g[0], x=x_fit) - np.trapz(l_g[1], x=x_fit))    # graze probability

            aptz_diff = round((approx_at - at) / at * 100.0, 1)     # approx/tz difference

            if print_comparison == 1:
                comp_str += using_names + ',' + '%.2e' % at + ',' + '%.2e' % approx_at + ',' + str(aptz_diff) + '\n'

            if print_probabilities == 1:
                prob_str += using_names + ',' + '%.1e' % at + ',' + '%.1e' % (at / at_earth) + '\n'

        else:       # if 2 or 3 planets
            try:    # print_comparison ValueErrors if not in list
                all_ind = planets.index(tuple(pls))     # position in pickle file lists
                npts = len(lon[0][all_ind]) / 2         # half length of region - i.e. 4 or 6

                if n == 3:      # 3 planet region boundaries
                    # get borders of crossover region
                    y1 = np.array(map(min, zip(l[2], l[4])))
                    y2 = np.array(map(max, zip(l[1], l[5])))
                    y3 = np.array(map(min, zip(y1, l[0])))
                    y4 = np.array(map(max, zip(y2, l[3])))
                    y1_a = np.array(map(min, zip(l_a[2], l_a[4])))
                    y2_a = np.array(map(max, zip(l_a[1], l_a[5])))
                    y3_a = np.array(map(min, zip(y1_a, l_a[0])))
                    y4_a = np.array(map(max, zip(y2_a, l_a[3])))
                    y1_g = np.array(map(min, zip(l_g[2], l_g[4])))
                    y2_g = np.array(map(max, zip(l_g[1], l_g[5])))
                    y3_g = np.array(map(min, zip(y1_g, l_g[0])))
                    y4_g = np.array(map(max, zip(y2_g, l_g[3])))
                    y_u, y_l = [y3, y3_a, y3_g], [y4, y4_a, y4_g]

                elif n == 2:    # 2 planet region boundaries
                    # get borders of crossover region
                    y1 = np.array(map(min, zip(l[0], l[2])))
                    y2 = np.array(map(max, zip(l[1], l[3])))
                    y1_a = np.array(map(min, zip(l_a[0], l_a[2])))
                    y2_a = np.array(map(max, zip(l_a[1], l_a[3])))
                    y1_g = np.array(map(min, zip(l_g[0], l_g[2])))
                    y2_g = np.array(map(max, zip(l_g[1], l_g[3])))
                    y_u, y_l = [y1, y1_a, y1_g], [y2, y2_a, y2_g]

                r_inds = []     # get start and end indices of regions
                for i in range(3):
                    r1_i1 = (np.abs(x_fit - lon[i][all_ind][0])).argmin()           # first region - start
                    r1_i2 = (np.abs(x_fit - lon[i][all_ind][npts - 1])).argmin()    # first region - end
                    r2_i1 = (np.abs(x_fit - lon[i][all_ind][npts])).argmin()        # second region - start
                    r2_i2 = (np.abs(x_fit - lon[i][all_ind][-1])).argmin()          # second region - end
                    r_inds.append([r1_i1, r1_i2, r2_i1, r2_i2])                     # list of indices

                at = np.zeros(3)        # holds probabilities for each angle

                for c in range(3):      # tz, approx, graze
                    if pls == tuple([0, 2, 6]):     # Me, E, U - only 1 region
                        i1 = (np.abs(x_fit - min(lon[c][all_ind]))).argmin()        # start of region
                        i2 = (np.abs(x_fit - max(lon[c][all_ind]))).argmin()        # end of region

                        upper = y_u[c][i1:i2]       # upper boundary
                        lower = y_l[c][i1:i2]       # lower boundary
                        x_cut = x_fit[i1:i2]        # x range of boundaries

                        # transit probability = area below upper border - area below lower border
                        at[c] += sd_to_prob(np.trapz(upper, x=x_cut) - np.trapz(lower, x=x_cut))

                        if c in plot_cs:        # comparison plot
                            plt.plot(x_cut, upper, color=tag_cols[c])
                            plt.plot(x_cut, lower, color=tag_cols[c])

                    else:
                        for i in [0, 2]:        # each region
                            if i == 0:
                                reg = lon[c][all_ind][:npts]    # region 1
                            else:
                                reg = lon[c][all_ind][npts:]    # region 2

                            if max(reg) - min(reg) < 100.0:     # doesn't cross 360
                                i1, i2 = r_inds[c][i], r_inds[c][i + 1]

                                upper = y_u[c][i1:i2]       # upper boundary
                                lower = y_l[c][i1:i2]       # lower boundary
                                x_cut = x_fit[i1:i2]        # x range of boundaries

                                at[c] += sd_to_prob(np.trapz(upper, x=x_cut) - np.trapz(lower, x=x_cut))  # probability

                            else:       # crosses 360
                                split1, split2 = [], []     # split region into near 0 and near 360
                                for x in reg:
                                    if x < 100.0:
                                        split1.append(x)
                                    else:
                                        split2.append(x)

                                # get end of near 0 region and start of near 360 region
                                s1max, s2min = (np.abs(x_fit - max(split1))).argmin(), \
                                               (np.abs(x_fit - min(split2))).argmin()

                                upper1, upper2 = y_u[c][:s1max], y_u[c][s2min:]     # upper boundary
                                lower1, lower2 = y_l[c][:s1max], y_l[c][s2min:]     # lower boundary
                                x_cut1, x_cut2 = x_fit[:s1max], x_fit[s2min:]       # x range of boundaries

                                at_s1 = np.trapz(upper1, x=x_cut1) - np.trapz(lower1, x=x_cut1)     # split 1 area
                                at_s2 = np.trapz(upper2, x=x_cut2) - np.trapz(lower2, x=x_cut2)     # split 2 area
                                at[c] += sd_to_prob(at_s1 + at_s2)      # probability

                if print_comparison == 1:
                    diff_tz_a = (at[0] - at[1]) / at[0] * 100.0     # % difference tz to approx

                    comp_str += using_names + ',' + '%.2e' % at[0] + ',' + '%.2e' % at[1] + ',' + \
                                str(round(diff_tz_a, 1)) + '\n'

                if print_probabilities == 1:
                    prob_str += using_names + ',' + '%.1e' % at[0] + ',' + '%.1e' % (at[0] / at_earth) + '\n'

                if plot_comparison == 1:        # comparison plot
                    if list(comp_region) == list(pls):
                        for h in reversed(plot_cs):
                            plt.fill_between(x_fit, y_u[h], y_l[h], where=y_u[h] >= y_l[h], color=tag_cols[h],
                                             alpha=tag_alphas[h], label=tag_labels[h])
                        plt.xlim(min(x_cut) - 0.2, max(x_cut) + 0.2)
                        plt.ylim(min(lower) - 0.01, max(upper) + 0.01)
                        plt.xlabel('Longitude (Degrees)', fontsize=20)
                        plt.ylabel('Latitude (Degrees)', fontsize=20)
                        plt.legend(loc='best', fontsize=20)
                        plt.minorticks_on()
                        plt.tick_params(axis='both', which='both', width=2)
                        plt.tick_params(axis='both', which='major', length=7)
                        plt.tick_params(axis='both', which='minor', length=4)
                        # plt.show()
                        plt.savefig('FigOut/AreaComparison.pdf', format='pdf', dpi=500)
                        print '> Comparison plot saved to \"FigOut/AreaComparison.pdf\".'

            except ValueError:
                pass

    if print_comparison == 1:           # save comparison table to file
        with open('DataOut/ComparisonTable.csv', 'w') as f:
            f.write(comp_str)
        print '> Comparison table saved to \"DataOut/ComparisonTable.csv\".'
    if print_probabilities == 1:        # save probability table to file
        with open('DataOut/ProbabilityTable.csv', 'w') as f:
            f.write(prob_str)
        print '> Probability table saved to \"DataOut/ProbabilityTable.csv\".'
back to top