def add_mask(time_i, flatten_i, results_pdcsap, periods, durations, tos, a): if results_pdcsap.snr > SNR[a]: intransit = transit_mask(time_i, results_pdcsap.period, 2 * results_pdcsap.duration, results_pdcsap.T0) else: intransit = transit_mask(time_i, periods[a], 2 * durations[a], tos[a]) flatten_i[intransit] = np.nan time_j, flatten_j = cleaned_array(time_i, flatten_i) return time_j, flatten_j
def __transit_masks(self, transit_masks, time): result = np.full(len(time), False) for mask in transit_masks: intransit = transit_mask(time, mask["P"], 2 * mask["D"], mask["T0"]) result[intransit] = True return result
def mask(time, flux, flux_err, period, duration, T0): intransit = transit_mask(time, period, duration, T0) time = time[~intransit] flux = flux[~intransit] if flux_err is not None: flux_err = flux_err[~intransit] time, flux, flux_err = cleaned_array(time, flux, flux_err) else: time, flux = cleaned_array(time, flux) return time, flux, flux_err
def __tls_search(self, time, flux, rstar, rstar_min, rstar_max, mass, mstar_min, mstar_max, ab, intransit, epoch, period, min_period, max_period, min_snr, cores, transit_template): SNR = 1e12 FOUND_SIGNAL = False time = time[~intransit] flux = flux[~intransit] time, flux = cleaned_array(time, flux) run = 0 flux = wotan.flatten(time, flux, window_length=0.5, return_trend=False, method='biweight', break_tolerance=0.5) #::: search for the rest while (SNR >= min_snr) and (not FOUND_SIGNAL): model = transitleastsquares(time, flux) # R_starx = rstar / u.R_sun results = model.power( u=ab, R_star=rstar, # rstar/u.R_sun, R_star_min=rstar_min, # rstar_min/u.R_sun, R_star_max=rstar_max, # rstar_max/u.R_sun, M_star=mass, # mstar/u.M_sun, M_star_min=mstar_min, # mstar_min/u.M_sun, M_star_max=mstar_max, # mstar_max/u.M_sun, period_min=min_period, period_max=max_period, n_transits_min=2, show_progress_bar=False, use_threads=cores, transit_template=transit_template) SNR = results.snr if results.snr >= min_snr: intransit_result = transit_mask(time, results.period, 2 * results.duration, results.T0) time = time[~intransit_result] flux = flux[~intransit_result] time, flux = cleaned_array(time, flux) right_period = self.__is_multiple_of(results.period, period / 2.) right_epoch = False for tt in results.transit_times: for i in range(-5, 5): right_epoch = right_epoch or ( np.abs(tt - epoch + i * period) < (1. / 24.) ) # check if any epochs matches to within 1 hour # right_depth = (np.abs(np.sqrt(1.-results.depth)*rstar - rplanet)/rplanet < 0.05) #check if the depth matches if right_period and right_epoch: FOUND_SIGNAL = True break run = run + 1 return FOUND_SIGNAL, results.snr, results.SDE, run
def seach_one_koi(KOI): print('Working on file', KOI) time, flux = loadkoi(str(KOI)) #KOI = 1206.01 #print(catalog["KOI"]) #catalog_KOIs = #catalog_KOIs = round(catalog_KOIs, 0) #print(catalog_KOIs) #print(int(float(KOI))) #print(catalog["KOI"].astype(int)[:20]) row = numpy.argmax(catalog["KOI"].astype(int) == int(float(KOI))) KIC = catalog["KIC"][row] print('row', row) print('KIC', KIC) max_t14 = T14(R_s=catalog["R_star"][row], M_s=catalog["mass"][row], P=period_max) window = 3 * max_t14 print('R_star, mass, max_t14, window', catalog["R_star"][row], catalog["mass"][row], max_t14, window) #"KIC","KOI","KepName","Period","T0","T14","ld1","ld2","R_star","rad_up","rad_down","mass","mass_up","mass_down" #10337517,1165.01,,7.053934488,136.357253,0.07185,0.1959,0.5086,0.863,0.073,-0.065,0.854,0.054,-0.045 #plt.scatter(time, flux, s=1) #plt.show() #print(time) #print(flux) if time is not None: # Remove known planets periods, t0s, tdurs = get_planets(KIC) print('periods', periods) for no in range(len(periods)): print('Removing planet', no + 1, periods[no], t0s[no], tdurs[no]) intransit = transit_mask(time, periods[no], 2 * tdurs[no], t0s[no]) flux = flux[~intransit] time = time[~intransit] time, flux = cleaned_array(time, flux) print('Next planet is number', no + 2) # Detrend data and remove high outliers print('Detrending...') #print(time) #print(flux) trend1 = trend(time, flux, window=window, c=5) #plt.scatter(time, flux, s=1, color='black') #plt.plot(time, trend1, color='red') #plt.show() #trend = scipy.signal.medfilt(flux, 31) #trend = scipy.signal.savgol_filter(trend, 25, 2) rawflux = flux.copy() rawtime = time.copy() y_filt = flux / trend1 y_filt = sigma_clip(y_filt, sigma_upper=3, sigma_lower=1e10) time, y_filt = cleaned_array(time, y_filt) #plt.close() #plt.scatter(time, y_filt, s=1, color='black') #plt.show() a = catalog["ld1"][row] b = catalog["ld2"][row] print('LD ab = ', a, b) model = transitleastsquares(time, y_filt) results = model.power( #n_transits_min=1, u=(a, b), R_star=catalog["R_star"][row], R_star_max=catalog["R_star"][row] + 2 * catalog["rad_up"][row], # sign is OK, is negative in catalog: R_star_min=catalog["R_star"][row] + 2 * catalog["rad_down"][row], M_star=catalog["mass"][row], M_star_max=catalog["mass"][row] + 2 * catalog["mass_up"][row], M_star_min=catalog["mass"][row] + 2 * catalog["mass_down"][row], oversampling_factor=5, duration_grid_step=1.05, use_threads=1, #period_min=4.9, #period_max=5.0, show_progress_bar=False, period_max=period_max, T0_fit_margin=0.1) tls_worked = True #except ValueError: # tls_worked = False valid = True if tls_worked: # Check if phase space has gaps bins = numpy.linspace(0, 1, 100) digitized = numpy.digitize(results.folded_phase, bins) bin_means = [ results.folded_phase[digitized == i].mean() for i in range(1, len(bins)) ] #print('bin_means', bin_means) if numpy.isnan(bin_means).any(): print('Vetting fail! Phase bins contain NaNs') valid = False if results.distinct_transit_count == 1 and results.transit_count >= 2: valid = False print( 'Vetting fail! results.distinct_transit_count==1 and results.transit_count == 2' ) if results.distinct_transit_count == 2 and results.transit_count >= 3: valid = False print( 'Vetting fail! results.distinct_transit_count==2 and results.transit_count == 3' ) if results.SDE < 8: valid = False print('Vetting fail! results.SDE < 8', results.SDE) if results.snr < 7: valid = False print('Vetting fail! results.snr < 7', results.snr) upper_transit_depths = results.transit_depths + results.transit_depths_uncertainties if results.transit_count == 2 and max(upper_transit_depths) > 1: valid = False print('Vetting fail! 2 transits, only 1 significant') upper_transit_depths = results.transit_depths + results.transit_depths_uncertainties if results.transit_count == 3 and max(upper_transit_depths) > 1: valid = False print('Vetting fail! 3 transits, not all 3 significant') upper_transit_depths = results.transit_depths + results.transit_depths_uncertainties if results.transit_count == 4 and max(upper_transit_depths) > 1: valid = False print('Vetting fail! 4 transits, not all 4 significant') if results.depth < 0.95: valid = False print('Vetting fail! Transit depth < 0.95', results.depth) #if results.SDE > 9: # 9 print('Signal detection efficiency (SDE):', format(results.SDE, '.1f')) print('SNR:', format(results.snr, '.1f')) print('Search completed') # Make figure #ttime.sleep(1) #valid = True if valid: print('Valid result, attempting figure...') make_figure(KOI=str(KOI), planet_number=no + 2, results=results, t=time, y=flux, y_filt=y_filt, trend=trend1, rawtime=rawtime, rawflux=rawflux) #ttime.sleep(1) #print('Figure made') else: print('No figure made, vetting failed!') else: print('TLS failed') process = psutil.Process(os.getpid()) ram_mb = process.memory_info().rss / 2**20 print('RAM (MB)', ram_mb) if ram_mb > 25000: sys.exit()
def make_figure(KOI, planet_number, results, t, y, y_filt, trend, rawtime, rawflux): fig = plt.figure(figsize=(10, 14)) G = gridspec.GridSpec(9, 3) G.update(hspace=0) radius = catalog["R_star"][row] radius_max = catalog["rad_up"][row] radius_min = catalog["rad_down"][row] mass = catalog["mass"][row] mass_max = catalog["mass_up"][row] mass_min = catalog["mass_down"][row] # Raw flux axes_1a = plt.subplot(G[0:1, :2]) plt.plot(rawtime, rawflux / numpy.mean(rawflux), "k", linewidth=0.5) plt.plot(rawtime, trend / numpy.mean(y), color='red', linewidth=0.5) plt.xlim(min(t), max(t)) plt.xticks(()) plt.ylabel(r'Raw Flux') ppm_flux = -(1 - y_filt) * 10**6 # Detrended flux G = gridspec.GridSpec(9, 3) G.update(hspace=0.4) axes_1b = plt.subplot(G[1:2, :2]) plt.plot(t, ppm_flux, "k", linewidth=0.5) top_ppm = -(1 - max(y_filt)) * 10**6 bottom_ppm = -(1 - min(y_filt)) * 10**6 y_range = abs(bottom_ppm) + abs(top_ppm) x_range = max(t) - min(t) offset_y = bottom_ppm + y_range / 40 offset_x = min(t) + x_range / 80 #print(top_ppm, bottom_ppm, y_range, offset_y) std = numpy.std(ppm_flux) text = r'$\sigma=$' + format(std, '.0f') + r'$\,$ppm' #print(bottom_ppm, top_ppm, offset, offset) plt.text(offset_x, offset_y, text, color='red') plt.ylabel(r'Flux (ppm)') plt.xlabel('Time (BKJD, days)') plt.xlim(min(t), max(t)) # Phase fold axes_2 = plt.subplot(G[2, 0:2]) plt.plot((results.model_folded_phase - 0.5) * results.period, -(1 - results.model_folded_model) * 10**6, color='red', zorder=99) plt.scatter((results.folded_phase - 0.5) * results.period, -(1 - results.folded_y) * 10**6, color='black', s=2, alpha=0.5, zorder=2) plt.xlim(-2 * results.duration, 2 * results.duration) plt.xlabel('Time from mid-transit (days)') plt.ylabel('Flux') plt.ylim( (numpy.percentile(-(1 - y_filt) * 10**6, 0.1), numpy.percentile(-(1 - y_filt) * 10**6, 99.5))) plt.text(-1.95 * results.duration, numpy.percentile(-(1 - y_filt) * 10**6, 0.1), 'primary') plt.plot((results.folded_phase - 0.5) * results.period, -(1 - running_mean_equal_length(results.folded_y, 10)) * 10**6, color='blue', linestyle='dashed', linewidth=1, zorder=3) # Phase fold to secondary eclipse G = gridspec.GridSpec(9, 3) G.update(hspace=0.3) axes_3 = plt.subplot(G[2, 2]) plt.yticks(()) phases = fold(time=t, period=results.period, T0=results.T0) sort_index = numpy.argsort(phases, kind="mergesort") phases = phases[sort_index] flux = y_filt[sort_index] plt.scatter((phases - 0.5) * results.period, flux, color='black', s=2, alpha=0.5, zorder=2) plt.plot((-0.5, 0.5), (1, 1), color='red') plt.xlim(-2 * results.duration, 2 * results.duration) plt.ylim(numpy.percentile(y_filt, 0.1), numpy.percentile(y_filt, 99.5)) plt.plot((phases - 0.5) * results.period, running_mean_equal_length(flux, 10), color='blue', linestyle='dashed', linewidth=1, zorder=3) # Calculate secondary eclipse depth intransit_secondary = numpy.where( numpy.logical_and( (phases - 0.5) * results.period > (-0.5 * results.duration), (phases - 0.5) * results.period < (0.5 * results.duration))) mean = -(1 - numpy.mean(flux[intransit_secondary])) * 10**6 stabw = -(numpy.std(flux[intransit_secondary]) / numpy.sqrt(len(flux[intransit_secondary]))) * 10**6 significance_eb = mean / stabw #print(mean, stabw, significance) plt.scatter((phases[intransit_secondary] - 0.5) * results.period, flux[intransit_secondary], color='orange', s=20, alpha=0.5, zorder=0) if numpy.isnan(mean): mean = 0 if numpy.isnan(stabw): stabw = 0 if numpy.isnan(significance_eb): significance_eb = 99 text = r'secondary ' + str( int(mean)) + r'$\pm$' + str(int(stabw)) + ' ppm (' + format( significance_eb, '.1f') + r'$\,\sigma$)' plt.text(-1.95 * results.duration, numpy.percentile(y_filt, 0.1), text) print('secondary vespa', (mean - stabw) * 10**-6) # Full phase G = gridspec.GridSpec(9, 3) G.update(hspace=1) axes_5 = plt.subplot(G[3, :]) plt.plot(results.model_folded_phase, -(1 - results.model_folded_model) * 10**6, color='red') plt.scatter(results.folded_phase, -(1 - results.folded_y) * 10**6, color='black', s=2, alpha=0.5, zorder=2) plt.xlim(0, 1) plt.ylim(numpy.percentile(ppm_flux, 0.1), numpy.percentile(ppm_flux, 99.9)) plt.xlabel('Phase') plt.ylabel('Flux') # Check if phase gaps phase_diffs = abs(results.folded_phase - numpy.roll(results.folded_phase, -1)) phase_diffs = phase_diffs[:-1] largest_phase_peak = max(phase_diffs) / numpy.median(phase_diffs) # All transits in time series G = gridspec.GridSpec(9, 3) G.update(hspace=0) axes_6b = plt.subplot(G[4, :]) y_filt = -(1 - y_filt) * 10**6 in_transit = transit_mask(t, results.period, results.duration, results.T0) t_diff1 = abs(t - numpy.roll(t, -1)) t_diff2 = abs(t - numpy.roll(t, +1)) if max(t_diff1[in_transit]) > 0.1 or max( t_diff2[in_transit]) > 0.1: # in days transit_near_gaps = True plt.text(min(t), numpy.percentile(ppm_flux, 0.1), 'Warning: Transits near gaps') else: transit_near_gaps = False transit_touches_edge = False if max(t) in t[in_transit]: plt.text(min(t), numpy.percentile(ppm_flux, 0.1), 'Warning: Last transit touches end') transit_touches_edge = True if max(t) in t[in_transit]: plt.text(min(t), numpy.percentile(ppm_flux, 0.1), 'Warning: First transit touches start') transit_touches_edge = True plt.scatter(t[in_transit], y_filt[in_transit], color='red', s=2, zorder=0) plt.scatter(t[~in_transit], y_filt[~in_transit], color='black', alpha=0.5, s=2, zorder=0) plt.plot(results.model_lightcurve_time, -(1 - results.model_lightcurve_model) * 10**6, alpha=0.5, color='red', zorder=1) plt.xlim(min(t), max(t)) plt.ylim(numpy.percentile(ppm_flux, 0.1), numpy.percentile(ppm_flux, 99.9)) plt.xlabel('Flux') plt.ylabel('Flux') plt.xticks(()) # Transit depths error bars avg = -(1 - results.depth_mean[0]) * 10**6 if numpy.isnan(results.transit_depths_uncertainties).any(): step = 0 else: step = max(results.transit_depths_uncertainties) * 10**6 down = avg - step G = gridspec.GridSpec(9, 3) G.update(hspace=1) axes_6 = plt.subplot(G[5, :]) plt.errorbar(results.transit_times, -(1 - results.transit_depths) * 10**6, yerr=step, fmt='o', color='red') plt.plot((min(t), max(t)), (0, 0), color='black') plt.plot((min(t), max(t)), (avg, avg), color='black', linestyle='dashed') plt.xlim(min(t), max(t)) for transit in range(len(results.transit_times)): plt.text(results.transit_times[transit], down, str(int(results.per_transit_count[transit])), horizontalalignment='center') #print(str(int(results.per_transit_count[transit]))) plt.xlabel('Time (BKJD, days)') plt.ylabel('Flux') # Test statistic G = gridspec.GridSpec(9, 3) G.update(hspace=0) #G.update(hspace=3) axes_7 = plt.subplot(G[6, :]) axes_7.axvline(results.period, alpha=0.4, lw=3) for n in range(2, 5): axes_7.axvline(n * results.period, alpha=0.4, lw=1, linestyle="dashed") axes_7.axvline(results.period / n, alpha=0.4, lw=1, linestyle="dashed") plt.ylabel(r'TLS SDE') plt.xticks(()) plt.plot(results.periods, results.power, color='black', lw=0.5) plt.xlim(0, max(results.periods)) plt.text(results.period + 1, results.SDE * 0.9, 'SDE=' + format(results.SDE, '.1f')) # LS periodogram raw #freqs, lspower = LombScargle(rawtime, rawflux).autopower() freqs = numpy.geomspace(1 / min(results.periods), 1 / max(results.periods), 10000) freqs = numpy.unique(freqs) #print(freqs) lspower = LombScargle(rawtime, rawflux).power(freqs) freqs = 1 / freqs #for i in range(len(freqs)): # print(freqs[i], lspower[i]) #print('max(results.periods)', max(results.periods)) #idx_end = numpy.argmax(freqs<max(results.periods)) #print(lspower) #print('idx_end', idx_end) #print('idx_end2', numpy.argmax(freqs<5)) #freqs = freqs[:idx_end] #lspower = lspower[:idx_end] #print(lspower) #continuum = numpy.std(lspower) #lspower = numpy.divide(lspower, continuum) # Normalize to S/N #print(lspower) G = gridspec.GridSpec(9, 3) G.update(hspace=0) #G.update(hspace=0) axes_8 = plt.subplot(G[7, :]) axes_8.axvline(results.period, alpha=0.4, lw=15) #axes_8.set_yscale("log") plt.ylabel(r'LS (raw)') plt.plot(freqs, lspower, color='black', lw=0.5) plt.xlim(0, max(results.periods)) plt.ylim(0, max(lspower) * 1.1) plt.xlabel('Period (days)') #idx_start = numpy.argmax(freqs>results.period-2) #idx_end = numpy.argmax(freqs>results.period+2) """ lspower_at_period = max(lspower[idx_start:idx_end]) plt.text(results.period+1, max(lspower)*0.9, 'LS power=' + format(lspower_at_period, '.1f')) if lspower_at_period > results.SDE: text = 'sinusoidal model preferred' else: text = 'transit model preferred' plt.text(results.period+1, max(lspower)*0.8, text) """ # Calculate Lomb-Scargle periodogram detrended freqs = numpy.geomspace(1 / min(results.periods), 1 / max(results.periods), 10000) freqs = numpy.unique(freqs) lspower = LombScargle(t, y_filt).power(freqs) freqs = 1 / freqs #freqs, lspower = LombScargle(t, y_filt).autopower() #idx_end = numpy.argmax(freqs>max(results.periods)) #freqs = freqs[:idx_end] #lspower = lspower[:idx_end] continuum = numpy.std(lspower) lspower = numpy.divide(lspower, continuum) # Normalize to S/N G = gridspec.GridSpec(9, 3) G.update(hspace=0) #G.update(hspace=3) axes_9 = plt.subplot(G[8, :]) peak_index = numpy.argmax(lspower) #print(peak_index, freqs[peak_index]) axes_9.axvline(freqs[peak_index], alpha=0.4, lw=10) plt.ylabel(r'LS (detrended)') plt.plot(freqs, lspower, color='black', lw=0.5) plt.xlim(0, max(results.periods)) plt.ylim(0, max(lspower) * 1.1) plt.xlabel('Period (days)') #plt.text(freqs[peak_index]+1, max(lspower)*0.9, 'LS power=' + format(max(lspower), '.1f')) #if max(lspower) > results.SDE: # text = 'sinusoidal model preferred' # model_preferred = 'sine' #else: # text = 'transit model preferred' # model_preferred = 'transit' #plt.text(results.period+1, max(lspower)*0.8, text) # Text G = gridspec.GridSpec(9, 3) G.update(hspace=0, wspace=0.1) axes_8 = plt.subplot(G[0:2, 2]) plt.xticks(()) plt.yticks(()) plt.text(0, 0, 'KOI ' + str(KOI) + '.0' + str(planet_number), fontweight='bold') #from astroquery.vizier import Vizier #result = Vizier(columns=["Kpmag", "Dist"]).query_constraints(ID=EPIC_id, catalog="IV/34/epic")[0].as_array() #Kpmag = result[0][0] #Dist = result[0][1] #plt.text(0, -0.15, 'KPmag=' + format(Kpmag, '.1f') + ', d=' + format(Dist, '.0f') + ' pc') plt.text( 0, -0.30, 'SDE=' + format(results.SDE, '.1f') + ', SNR=' + format(results.snr, '.1f')) plt.text( 0, -0.45, 'P=' + format(results.period, '.5f') + ' +-' + format(results.period_uncertainty, '.5f') + r'$\,$d') plt.text(0, -0.60, r'$T_{dur}=$' + format(results.duration, '.5f') + r'$\,$d') plt.text( 0, -0.75, r'$R_*=$' + format(radius, '.2f') + ' (+' + format(radius_max, '.2f') + ', -' + format(radius_min, '.2f') + ') $\,R_{\odot}$') plt.text( 0, -0.90, r'$M_*=$' + format(mass, '.2f') + ' (+' + format(mass_max, '.2f') + ', -' + format(mass_min, '.2f') + ') $\,M_{\odot}$') plt.text(0, -1.05, r'$R_P/R_*=$' + format(results.rp_rs, '.3f')) print('rp_rs', results.rp_rs) rp = results.rp_rs * radius # in solar radii sun = 695700 earth = 6371 jupi = 69911 rp_earth = (sun / earth) * rp rp_jupi = (sun / jupi) * rp plt.text( 0, -1.20, r'$R_P=$' + format(rp_earth, '.2f') + '$\,R_{\oplus}=$' + format(rp_jupi, '.2f') + '$\,R_{Jup}$') plt.text( 0, -1.35, r'$\delta=$' + \ format((1-results.depth_mean[0])*10**6, '.0f') + ' ppm (' + \ format((1-results.depth_mean_odd[0])*10**6, '.0f') + ', ' + \ format((1-results.depth_mean_even[0])*10**6, '.0f') + ')') plt.text( 0, -1.50, r'odd/even mismatch: ' + format(results.odd_even_mismatch, '.2f') + '$\,\sigma$') plt.text( 0, -1.65, str(results.distinct_transit_count) + '/' + str(results.transit_count) + ' transits with data') plt.xlim(-0.1, 2) plt.ylim(-2, 0.1) axes_8.axis('off') plt.subplots_adjust(hspace=0.4) # Additional vetting criteria valid = True #if largest_phase_peak > 10: # valid = False # print('Vetting fail! largest_phase_peak > 10 median cadences: ', largest_phase_peak) if abs(significance_eb) > 3: valid = False print('Vetting fail! significance_eb > 3') #if numpy.isnan(results.odd_even_mismatch): # valid = False # print('Vetting fail! numpy.isnan(results.odd_even_mismatch)') if abs(results.odd_even_mismatch) > 3: valid = False print('Vetting fail! odd_even_mismatch larger 3 sigma') if transit_near_gaps and results.distinct_transit_count < 4: valid = False print('Vetting fail! Transit near gaps and distinct_transit_count < 4') #if model_preferred != 'transit': # valid = False # print('Sinusoidal model was preferred over transit model') #if max(lspower) > results.SDE: # valid = False # print('Vetting fail! Sinusoidal model preferred') #if transit_touches_edge: # valid = False # print('Vetting fail! First or last transit touches edge of time series') #valid = True if valid: figure_out_path = str(KOI) + '_0' + str(planet_number) plt.savefig(figure_out_path + '.png', bbox_inches='tight') #plt.savefig(figure_out_path + '.pdf', bbox_inches='tight') print('Figure made:', figure_out_path) """ print('Creating full CSV result file') numpy.savetxt( fname=str(EPIC_id) + '_statistics.csv', X=numpy.array([EPIC_id, results.period, results.T0]), delimiter=',', newline=" ", fmt='%.5f', ) """ else: print('Vetting criteria failed! No figure made.') #plt.close() print('T0', results.T0) return valid
def tls_vetsheet(lc, results=0, show=True, save=False, savename='vetsheet.png'): #!!Add functionality to plot without having to run search?!! #!!Add vertical lines for expected transit times on unprocessed LC!! #!!Change x-axis phase values for odd-even plot!! #!!Make processed and unprocessed x-axes line up, especially when LC is # masked. Maybe include grayed-out parts that were masked!! """ Function to plot a vetting sheet after running the transit_tools.signal_search function. Output is formatted to fit onto a standard 8"x11" sheet of paper. Parameters ---------- lc : `transit_tools.lightcurve` object Input transit_tools `transit_tools.lightcurve` object that has had a TLS signal search performed on it. results : int Index of results attribute array of provided 'lightcurve' object. Indicates which set of results to plot if signal_search produced more than one set of output results. Can be set to -1 to display the most recent signal run that did not meet the significance threshold. show : bool or str Flag to determine whether plots will be displayed or not. Must be set to False or 'both' for output matplotlib object to be expected. save : bool Flag to determine whether the plots will be saved as a PNG. savename : str or None File name for plots to be saved as if save is set to True. Returns ------- plots : matplotlib object Output matplotlib plot object. Optional if show is set to False or 'both'. """ if results == -1 and len(lc.results) > 0: time = lc.cleanlc[-1].time.value flux = lc.cleanlc[-1].flux.value flux_err = lc.cleanlc[-1].flux_err.value elif len(lc.results) > 0: res = lc.results[results] if results == -1: res = lc.bad_search[0] if results == 0 or len(lc.results) == 0: time = lc.time.value flux = lc.flux.value flux_err = lc.flux_err.value else: time = lc.cleanlc[results-1].time.value flux = lc.cleanlc[results-1].flux.value flux_err = lc.cleanlc[results-1].flux_err.value #setting up figure fig = plt.figure(figsize=(8, 9.2)) gs = fig.add_gridspec(5, 2) #phase-folded light curve with transit model ax = fig.add_subplot(gs[0, 1]) fold = LightCurve(res.folded_phase, res.folded_y).bin(20) ax.plot(res.model_folded_phase, res.model_folded_model, color='red', alpha=0.7) ax.scatter(res.folded_phase, res.folded_y, color='blue', s=0.2, alpha=0.5, zorder=2) ax.scatter(fold.time.value, fold.flux.value, s=2., c='k') ax.set_xlim(0.45, 0.55) ax.set_xlabel('Phase') ax.set_ylabel('Relative Flux') #raw light curve ax = fig.add_subplot(gs[1, :]) ax.scatter(lc.raw_lc.time.value, lc.raw_lc.flux.value, s=0.2, c='b', alpha=0.5) ax.set_xlabel('Time [BTJD]') ax.set_ylabel('Raw Relative Flux') ax.set_xlim(lc.raw_lc.time.value.min(), lc.raw_lc.time.value.max()) if hasattr(lc, 'trend'): plt.plot(lc.trend.time.value, lc.trend.flux.value, c='g') #processed light curve with transit model ax = fig.add_subplot(gs[2, :]) transit_time = transit_mask(time, res.period, res.duration, res.T0) time_notrans = time[~transit_time] flux_notrans = flux[~transit_time] flux_err_notrans = flux_err[~transit_time] ax.scatter(time[transit_time], flux[transit_time], color='red', s=0.2, zorder=0) ax.scatter(time[~transit_time], flux[~transit_time], color='blue', alpha=0.5, s=0.2, zorder=0) ax.plot(res.model_lightcurve_time, res.model_lightcurve_model, alpha=0.5, color='red', zorder=1) ax.set_xlim(time.min(), time.max()) ax.set_ylim(flux.min()-0.001, flux.max()+0.001) ax.set_xlabel('Time [BTJD]') ax.set_ylabel('Relative Flux') #TLS periodogram ax = fig.add_subplot(gs[3, :]) ax.axvline(res.period, alpha=0.4, lw=3) ax.set_xlim(np.min(res.periods), np.max(res.periods)) for n in range(2, 10): ax.axvline(n * res.period, alpha=0.4, lw=1, linestyle='dashed') ax.axvline(res.period / n, alpha=0.4, lw=1, linestyle='dashed') ax.set_ylabel('SDE') ax.set_xlabel('Period [days]') ax.plot(res.periods, res.power, color='k', lw=0.5) ax.set_xlim(0, max(res.periods)) #secondary eclipse ax = fig.add_subplot(gs[4, 0]) ax.plot(res.model_folded_phase-0.5, np.roll(res.model_folded_model, len(res.model_folded_model)//2), color='red') ax.scatter(res.folded_phase-0.5, np.roll(res.folded_y, len(res.folded_y)//2), color='blue', s=0.2, alpha=0.5, zorder=2) ax.set_xlim(-0.05, 0.05) ax.set_xlabel('Phase') ax.set_ylabel('Relative Flux') ax.axvspan(-(res.duration/2/res.period), (res.duration/2/res.period), alpha=0.3, color='orange', label='Transit Duration') ax.legend(loc=2, fontsize='x-small') #Odd-Even comparison ax = fig.add_subplot(gs[4, 1]) oe = LightCurve(time, flux, flux_err) oe_odd = oe.fold(2*res.period, res.T0) oe_even = oe.fold(2*res.period, res.T0+res.period) ax.scatter(oe_odd.time.value+0.5, oe_odd.flux.value, s=0.2, c='b', label='Odd') ax.scatter(oe_even.time.value+0.5, oe_even.flux.value, s=0.2, c='r', label='Even') ax.set_xlim(0.475, 0.525) ax.set_xlabel('Phase') ax.set_ylabel('Relative Flux') ax.set_xticks([0.48, 0.49, 0.50, 0.51, 0.52]) ax.set_xticklabels(['0.46', '0.48', '0.50', '0.52', '0.54']) ax.legend(loc=2, fontsize='x-small') #plot summary text fig.text(0.12, 0.97, s=(str(lc.name) + ' (TIC ' + str(lc.tic) + ')'), fontweight='bold') fig.text(0.04, 0.95, s=(r'P = %.5f +/- %.5f d, $t_{0}$ = %.5f BTJD' % (res.period, res.period_uncertainty, res.T0))) fig.text(0.04, 0.93, s=(r'$T_{dur}$ = %.5f d, %s/%s transits with data' % (res.duration, res.distinct_transit_count, res.transit_count))) fig.text(0.04, 0.91, s=('SDE = %.2f, SNR = %.2f, FAP = %.3e' % (res.SDE, res.snr, res.FAP))) fig.text(0.04, 0.89, s=(r'$R_{P}$/$R_{*}$ = %.4f, $R_{P}$ = %.3f $R_{\bigoplus}$ = %.3f $R_{Jup}$' % (res.rp_rs, (lc.star_params_tls['rstar']*res.rp_rs*c.Rsolar_m)/c.Rearth_m, (lc.star_params_tls['rstar']*res.rp_rs*c.Rsolar_m)/c.Rjup_m))) fig.text(0.04, 0.87, s=(r'odd/even mismatch = %.2f $\sigma$, $\delta$ = %.4f' % (res.odd_even_mismatch, 1-res.depth))) fig.text(0.04, 0.85, s=(r'$R_{*}$ = %.2f (+%.2f, -%.2f) $R_{\bigodot}$' % (lc.star_params_tls['rstar'], lc.star_params_tls['rhigh'], lc.star_params_tls['rlow']))) fig.text(0.04, 0.83, s=(r'$M_{*}$ = %.2f (+%.2f, -%.2f) $M_{\bigodot}$' % (lc.star_params_tls['mstar'], lc.star_params_tls['mhigh'], lc.star_params_tls['mlow']))) if lc.star_params is not None and lc.star_params['Tmag'] is not None: fig.text(0.04, 0.81, s=('Tmag = %.2f' % (lc.star_params['Tmag']))) plt.tight_layout() if show: plt.show() if save: plt.savefig(savename)
def mask(time, flux, period, duration, T0): intransit = transit_mask(time, period, duration, T0) time = time[~intransit] flux = flux[~intransit] time, flux = cleaned_array(time, flux) return time, flux
def tls_search(time, flux, epoch, period, rplanet, min_snr, transit_template): SNR = 1e12 FOUND_SIGNAL = False #::: mask out the first detection at 6.2 days, with a duration of 2.082h, and T0=1712.54922 # intransit = transit_mask(time, 6.26391, 2*2.082/24., 1712.54922) # time = time[~intransit] # flux = flux[~intransit] time, flux = cleaned_array(time, flux) run = 0 flux = wotan.flatten(time, flux, window_length=0.25, return_trend=False, method='biweight', break_tolerance=0.5) #::: search for the rest while (SNR >= min_snr) and (not FOUND_SIGNAL): model = transitleastsquares(time, flux) R_starx = rstar / u.R_sun results = model.power(u=ab, R_star=radius, # rstar/u.R_sun, R_star_min=rstar_min, # rstar_min/u.R_sun, R_star_max=rstar_max, # rstar_max/u.R_sun, M_star=mass, # mstar/u.M_sun, M_star_min=mstar_min, # mstar_min/u.M_sun, M_star_max=mstar_max, # mstar_max/u.M_sun, period_min=0.5, period_max=14, n_transits_min=2, show_progress_bar=False, use_threads=20, transit_template=transit_template ) # mass and radius for the TLS # rstar=radius ##mstar=mass # mstar_min = mass-massmin # mstar_max = mass+massmax # rstar_min = radius-radiusmin # rstar_max = radius+raduismax SNR = results.snr if results.snr >= min_snr: intransit = transit_mask(time, results.period, 2 * results.duration, results.T0) time = time[~intransit] flux = flux[~intransit] time, flux = cleaned_array(time, flux) #::: check if it found the right signal right_period = IsMultipleOf(results.period, period / 2.) # check if it is a multiple of half the period to within 5% right_epoch = False for tt in results.transit_times: for i in range(-5, 5): right_epoch = right_epoch or (np.abs(tt - epoch + i * period) < ( 1. / 24.)) # check if any epochs matches to within 1 hour # right_depth = (np.abs(np.sqrt(1.-results.depth)*rstar - rplanet)/rplanet < 0.05) #check if the depth matches if right_period and right_epoch: FOUND_SIGNAL = True break run = run + 1 return FOUND_SIGNAL, results.snr, run
def make_figure(KOI, planet_number, results, t, y, y_filt, trend, rawtime, rawflux, catalog, row, valid=False): fig = plt.figure(figsize=(10, 14)) G = gridspec.GridSpec(9, 3) G.update(hspace=0) radius = catalog["R_star"][row] radius_max = catalog["rad_up"][row] radius_min = catalog["rad_down"][row] mass = catalog["mass"][row] mass_max = catalog["mass_up"][row] mass_min = catalog["mass_down"][row] # Raw flux axes_1a = plt.subplot(G[0:1, :2]) plt.plot(rawtime, rawflux / numpy.mean(rawflux), "k", linewidth=0.5) plt.plot(rawtime, trend / numpy.mean(y), color='red', linewidth=0.5) plt.xlim(min(t), max(t)) plt.xticks(()) plt.ylabel(r'Raw Flux') ppm_flux = -(1 - y_filt) * 10**6 # Detrended flux G = gridspec.GridSpec(9, 3) G.update(hspace=0.4) axes_1b = plt.subplot(G[1:2, :2]) plt.plot(t, ppm_flux, "k", linewidth=0.5) top_ppm = -(1 - max(y_filt)) * 10**6 bottom_ppm = -(1 - min(y_filt)) * 10**6 y_range = abs(bottom_ppm) + abs(top_ppm) x_range = max(t) - min(t) offset_y = bottom_ppm + y_range / 40 offset_x = min(t) + x_range / 80 std = numpy.std(ppm_flux) text = r'$\sigma=$' + format(std, '.0f') + r'$\,$ppm' plt.text(offset_x, offset_y, text, color='red') plt.ylabel(r'Flux (ppm)') plt.xlabel('Time (BKJD, days)') plt.xlim(min(t), max(t)) # Phase fold axes_2 = plt.subplot(G[2, 0:2]) plt.plot((results.model_folded_phase - 0.5) * results.period, -(1 - results.model_folded_model) * 10**6, color='red', zorder=99) plt.scatter((results.folded_phase - 0.5) * results.period, -(1 - results.folded_y) * 10**6, color='black', s=2, alpha=0.5, zorder=2) plt.xlim(-2 * results.duration, 2 * results.duration) plt.xlabel('Time from mid-transit (days)') plt.ylabel('Flux') plt.ylim( (numpy.percentile(-(1 - y_filt) * 10**6, 0.1), numpy.percentile(-(1 - y_filt) * 10**6, 99.5))) plt.text(-1.95 * results.duration, numpy.percentile(-(1 - y_filt) * 10**6, 0.1), 'primary') plt.plot((results.folded_phase - 0.5) * results.period, -(1 - running_mean_equal_length(results.folded_y, 10)) * 10**6, color='blue', linestyle='dashed', linewidth=1, zorder=3) # Phase fold to secondary eclipse G = gridspec.GridSpec(9, 3) G.update(hspace=0.3) axes_3 = plt.subplot(G[2, 2]) plt.yticks(()) phases = fold(time=t, period=results.period, T0=results.T0) sort_index = numpy.argsort(phases, kind="mergesort") phases = phases[sort_index] flux = y_filt[sort_index] plt.scatter((phases - 0.5) * results.period, flux, color='black', s=2, alpha=0.5, zorder=2) plt.plot((-0.5, 0.5), (1, 1), color='red') plt.xlim(-2 * results.duration, 2 * results.duration) plt.ylim(numpy.percentile(y_filt, 0.1), numpy.percentile(y_filt, 99.5)) plt.plot((phases - 0.5) * results.period, running_mean_equal_length(flux, 10), color='blue', linestyle='dashed', linewidth=1, zorder=3) # Calculate secondary eclipse depth intransit_secondary = numpy.where( numpy.logical_and( (phases - 0.5) * results.period > (-0.5 * results.duration), (phases - 0.5) * results.period < (0.5 * results.duration))) mean = -(1 - numpy.mean(flux[intransit_secondary])) * 10**6 stabw = -(numpy.std(flux[intransit_secondary]) / numpy.sqrt(len(flux[intransit_secondary]))) * 10**6 significance_eb = mean / stabw plt.scatter((phases[intransit_secondary] - 0.5) * results.period, flux[intransit_secondary], color='orange', s=20, alpha=0.5, zorder=0) if numpy.isnan(mean): mean = 0 if numpy.isnan(stabw): stabw = 0 if numpy.isnan(significance_eb): significance_eb = 99 text = r'secondary ' + str( int(mean)) + r'$\pm$' + str(int(stabw)) + ' ppm (' + format( significance_eb, '.1f') + r'$\,\sigma$)' plt.text(-1.95 * results.duration, numpy.percentile(y_filt, 0.1), text) # Full phase G = gridspec.GridSpec(9, 3) G.update(hspace=1) axes_5 = plt.subplot(G[3, :]) plt.plot(results.model_folded_phase, -(1 - results.model_folded_model) * 10**6, color='red') plt.scatter(results.folded_phase, -(1 - results.folded_y) * 10**6, color='black', s=2, alpha=0.5, zorder=2) plt.xlim(0, 1) plt.ylim(numpy.percentile(ppm_flux, 0.1), numpy.percentile(ppm_flux, 99.9)) plt.xlabel('Phase') plt.ylabel('Flux') # Check if phase gaps phase_diffs = abs(results.folded_phase - numpy.roll(results.folded_phase, -1)) phase_diffs = phase_diffs[:-1] largest_phase_peak = max(phase_diffs) / numpy.median(phase_diffs) # All transits in time series G = gridspec.GridSpec(9, 3) G.update(hspace=0) axes_6b = plt.subplot(G[4, :]) y_filt = -(1 - y_filt) * 10**6 in_transit = transit_mask(t, results.period, results.duration, results.T0) t_diff1 = abs(t - numpy.roll(t, -1)) t_diff2 = abs(t - numpy.roll(t, +1)) if max(t_diff1[in_transit]) > 0.1 or max( t_diff2[in_transit]) > 0.1: # in days transit_near_gaps = True plt.text(min(t), numpy.percentile(ppm_flux, 0.1), 'Warning: Transits near gaps') else: transit_near_gaps = False transit_touches_edge = False if max(t) in t[in_transit]: plt.text(min(t), numpy.percentile(ppm_flux, 0.1), 'Warning: Last transit touches end') transit_touches_edge = True if max(t) in t[in_transit]: plt.text(min(t), numpy.percentile(ppm_flux, 0.1), 'Warning: First transit touches start') transit_touches_edge = True plt.scatter(t[in_transit], y_filt[in_transit], color='red', s=2, zorder=0) plt.scatter(t[~in_transit], y_filt[~in_transit], color='black', alpha=0.5, s=2, zorder=0) plt.plot(results.model_lightcurve_time, -(1 - results.model_lightcurve_model) * 10**6, alpha=0.5, color='red', zorder=1) plt.xlim(min(t), max(t)) plt.ylim(numpy.percentile(ppm_flux, 0.1), numpy.percentile(ppm_flux, 99.9)) plt.xlabel('Flux') plt.ylabel('Flux') plt.xticks(()) # Transit depths error bars avg = -(1 - results.depth_mean[0]) * 10**6 if numpy.isnan(results.transit_depths_uncertainties).any(): step = 0 else: step = max(results.transit_depths_uncertainties) * 10**6 down = avg - step G = gridspec.GridSpec(9, 3) G.update(hspace=1) axes_6 = plt.subplot(G[5, :]) plt.errorbar(results.transit_times, -(1 - results.transit_depths) * 10**6, yerr=step, fmt='o', color='red') plt.plot((min(t), max(t)), (0, 0), color='black') plt.plot((min(t), max(t)), (avg, avg), color='black', linestyle='dashed') plt.xlim(min(t), max(t)) for transit in range(len(results.transit_times)): plt.text(results.transit_times[transit], down, str(int(results.per_transit_count[transit])), horizontalalignment='center') plt.xlabel('Time (BKJD, days)') plt.ylabel('Flux') # Test statistic G = gridspec.GridSpec(9, 3) G.update(hspace=0) #G.update(hspace=3) axes_7 = plt.subplot(G[6, :]) axes_7.axvline(results.period, alpha=0.4, lw=3) for n in range(2, 5): axes_7.axvline(n * results.period, alpha=0.4, lw=1, linestyle="dashed") axes_7.axvline(results.period / n, alpha=0.4, lw=1, linestyle="dashed") plt.ylabel(r'TLS SDE') plt.xticks(()) plt.plot(results.periods, results.power, color='black', lw=0.5) plt.xlim(min(results.periods), max(results.periods)) plt.text(results.period + 0.1, results.SDE * 0.9, 'SDE=' + format(results.SDE, '.1f')) # LS periodogram raw freqs = numpy.geomspace(1 / min(results.periods), 1 / max(results.periods), 10000) freqs = numpy.unique(freqs) lspower = LombScargle(rawtime, rawflux).power(freqs) freqs = 1 / freqs G = gridspec.GridSpec(9, 3) G.update(hspace=0) axes_8 = plt.subplot(G[7, :]) axes_8.axvline(results.period, alpha=0.4, lw=15) plt.ylabel(r'LS (raw)') plt.plot(freqs, lspower, color='black', lw=0.5) plt.xlim(min(results.periods), max(results.periods)) plt.ylim(0, max(lspower) * 1.1) plt.xlabel('Period (days)') # Calculate Lomb-Scargle periodogram detrended freqs = numpy.geomspace(1 / min(results.periods), 1 / max(results.periods), 10000) freqs = numpy.unique(freqs) lspower = LombScargle(t, y_filt).power(freqs) freqs = 1 / freqs continuum = numpy.std(lspower) lspower = numpy.divide(lspower, continuum) # Normalize to S/N G = gridspec.GridSpec(9, 3) G.update(hspace=0) axes_9 = plt.subplot(G[8, :]) peak_index = numpy.argmax(lspower) axes_9.axvline(freqs[peak_index], alpha=0.4, lw=10) plt.ylabel(r'LS (detrended)') plt.plot(freqs, lspower, color='black', lw=0.5) plt.xlim(min(results.periods), max(results.periods)) plt.ylim(0, max(lspower) * 1.1) plt.xlabel('Period (days)') # Text G = gridspec.GridSpec(9, 3) G.update(hspace=0, wspace=0.1) axes_8 = plt.subplot(G[0:2, 2]) plt.xticks(()) plt.yticks(()) plt.text(0, 0, 'KOI ' + str(KOI) + '.0' + str(planet_number), fontweight='bold') plt.text( 0, -0.30, 'SDE=' + format(results.SDE, '.1f') + ', SNR=' + format(results.snr, '.1f')) plt.text( 0, -0.45, 'P=' + format(results.period, '.5f') + ' +-' + format(results.period_uncertainty, '.5f') + r'$\,$d') plt.text(0, -0.60, r'$T_{dur}=$' + format(results.duration, '.5f') + r'$\,$d') plt.text( 0, -0.75, r'$R_*=$' + format(radius, '.2f') + ' (+' + format(radius_max, '.2f') + ', -' + format(radius_min, '.2f') + ') $\,R_{\odot}$') plt.text( 0, -0.90, r'$M_*=$' + format(mass, '.2f') + ' (+' + format(mass_max, '.2f') + ', -' + format(mass_min, '.2f') + ') $\,M_{\odot}$') plt.text(0, -1.05, r'$R_P/R_*=$' + format(results.rp_rs, '.3f')) print('rp_rs', results.rp_rs) rp = results.rp_rs * radius # in solar radii sun = 695700 earth = 6371 jupi = 69911 rp_earth = (sun / earth) * rp rp_jupi = (sun / jupi) * rp plt.text( 0, -1.20, r'$R_P=$' + format(rp_earth, '.2f') + '$\,R_{\oplus}=$' + format(rp_jupi, '.2f') + '$\,R_{Jup}$') plt.text( 0, -1.35, r'$\delta=$' + \ format((1-results.depth_mean[0])*10**6, '.0f') + ' ppm (' + \ format((1-results.depth_mean_odd[0])*10**6, '.0f') + ', ' + \ format((1-results.depth_mean_even[0])*10**6, '.0f') + ')') plt.text( 0, -1.50, r'odd/even mismatch: ' + format(results.odd_even_mismatch, '.2f') + '$\,\sigma$') plt.text( 0, -1.65, str(results.distinct_transit_count) + '/' + str(results.transit_count) + ' transits with data') plt.xlim(-0.1, 2) plt.ylim(-2, 0.1) axes_8.axis('off') plt.subplots_adjust(hspace=0.4) if abs(significance_eb) > 3: valid = False print('Vetting fail! significance_eb > 3') if abs(results.odd_even_mismatch) > 3: valid = False print('Vetting fail! odd_even_mismatch larger 3 sigma') if transit_near_gaps and results.distinct_transit_count < 4: valid = False print('Vetting fail! Transit near gaps and distinct_transit_count < 4') if valid: # 5-50d figure_out_path = 'GOOD0-5d_' + str(KOI) + '_0' + str(planet_number) else: figure_out_path = '_FAIL0-5d_' + str(KOI) + '_0' + str(planet_number) plt.savefig(figure_out_path + '.png', bbox_inches='tight') print('Figure made:', figure_out_path) plt.close return valid
skip_header=1, delimiter=',', dtype="int32, f8, f8, f8", names=["EPIC_id", "period", "t0", "tdur"]) rows = range(len(catalog["EPIC_id"])) for row in rows: EPIC_id = catalog["EPIC_id"][row] print('New planet EPIC_id', EPIC_id) time, flux = loadfile(EPIC_id) # Remove known planets periods, t0s, tdurs = get_planets(EPIC_id) for no in range(len(periods)): print('Removing planet', periods[no], t0s[no], tdurs[no]) intransit = transit_mask(time, periods[no], 2 * tdurs[no], t0s[no]) flux = flux[~intransit] time = time[~intransit] time, flux = cleaned_array(time, flux) # Detrend data and remove high outliers trend = scipy.signal.medfilt(flux, MEDIAN_KERNEL_WINDOW) flux = flux / trend flux = sigma_clip(flux, sigma_upper=3, sigma_lower=6) # Search flushed data for additional planets ab, mass, mass_min, mass_max, radius, radius_min, radius_max = catalog_info( EPIC_id) model = transitleastsquares(time, flux) results = model.power(n_transits_min=1, u=ab,
def fit_and_remove_transits(time, flux, ID=None, mission=None, pl_orbper=None, pl_orbpererr=None, **kwargs): """Fit and subtract transits of a planet from light curves using prior information about the star-planet system. Parameters: ----------- time : n-array time array in units of days flux : n-array flux array mission : str "TESS" or "Kepler" pl_orbper : float orbital period of planet in units of days pl_orbpererr : float uncertainty on orbital period Return: ------- n-array - flux with transit signal removed """ # transit durations in TESS vary from 0.2 hours to over 30 h # at 1 min cadence this would be at least 12 datapoints per transit # which should be fine given that we have # more constraints on the transits given anyways # So: Downsample to 1 min cadence if needed: dt = np.diff(time).mean() one_min = 1. / 60. / 24. if dt < one_min: new_time, new_flux = tls.resample(time, flux / np.median(flux), factor=one_min / dt) else: new_time, new_flux = time, flux / np.median(flux) # Fetch stellar parameters from TESS and Kepler catalogs if mission is not None: if mission == "TESS": kwarg = {"TIC_ID": ID} elif mission == "Kepler": kwarg = {"KIC_ID": ID} ab, mass, mass_min, mass_max, radius, radius_min, radius_max = tls.catalog_info( **kwarg) r = radius rmin = radius - 3 * radius_min rmax = radius + 3 * radius_max m = mass mmin = mass - 3 * mass_min mmax = mass + 3 * mass_max else: # pick defaults ab = [0.4804, 0.1867] # (a G2V star in the Kepler bandpass), m, mmin, mmax = 1, 0.08, 1.3 r, rmin, rmax = 1, 0.13, 1.3 # roughly everything with a convection zone if (pl_orbper is None) | (np.isnan(pl_orbper)): print("No transit duration given.") n_transits_min = 2 period_min, period_max = 0., np.inf else: print(new_time[-1], new_time[0], pl_orbper) n_transits_min = int((new_time[-1] - new_time[0]) // pl_orbper) if pl_orbpererr is None: print("No transit duration uncertainty given.") period_min, period_max = 0.75 * pl_orbper, 1.5 * pl_orbper else: print("Transit duration and uncertainty given.") period_min = pl_orbper - 3 * pl_orbpererr period_max = pl_orbper + 3 * pl_orbpererr # Initialize the LST model model = tls.transitleastsquares(new_time, new_flux) print(new_time, new_flux, new_time.shape, new_flux.shape) print(period_min, period_max, n_transits_min, mmin, mmax, rmin, rmax, n_transits_min) # Run the search with system contraints from the catalogs results = model.power( u=ab, R_star=r, R_star_min=rmin, R_star_max=rmax, M_star=m, M_star_min=mmin, M_star_max=mmax, period_min=period_min, period_max=period_max, n_transits_min=max(n_transits_min, 1), # this could be 1, 0 would throw an error **kwargs) # resample model light curve to original cadence if type(results.model_lightcurve_model) == float: print("Transit not recovered.") return flux, results else: model_flux = np.interp(time, results.model_lightcurve_time, results.model_lightcurve_model) # get the transit mask in_transit = tls.transit_mask(time, results.period, results.duration, results.T0) # get new flux array with transits subtracted notransit_flux = np.copy(flux) notransit_flux[ in_transit] = notransit_flux[in_transit] / model_flux[in_transit] return notransit_flux, results
def find_planets(time, raw_flux, cor_flux, campaign_no, star_id, provenance, summary_log, max_planets=1, plotno=1, interactive=False): """ Find one or more planet transits given input time, raw flux and corrected flux series. Save the results to the log files and display plots of light curves and folded light curves from TLS. Parameters ---------- time: numpy.ndarray Time series. raw_flux: numpy.ndarray Raw flux series. cor_flux: numpy.ndarray Corrected flux series. campaign_no: int, str Campaign number or "sequence number". Can be a wildcard character `'*'` to indicate that all campaigns should be searched. Used for logging. star_id: int, str EPIC ID of the star. Used for logging. provenance: str Provenance name in MAST archive, e.g., ``'K2'``, ``'EVEREST'``, ``'K2SFF'``. Used for logging. summary_log: file object File object of the summary log file with write access and initialized with appropriate column names. max_planets: int Maximum number of planets to find. plotno: int Plot number to be passed directly to `~matplotlib.pyplot.figure`. If `None`, a new figure will be created. interactive: bool Indicates whether or not to draw figures on screen. When ``interactive`` is `False`, figures are saved to files in the ``'Graphs/tls/'`` and ``'Graphs/lc/'`` sub-directories. Figure's file names will be constructed using the following pattern: ``star_id + '_' + provenance + '_tls.log'`` ``star_id + '_' + provenance + '_lc.log'`` """ planet_id = 1 while (max_planets and planet_id <= max_planets) or max_planets is None: lmask, umask = detect_outliers(cor_flux) # lmask indicates lower outliers and possible transits # umask indicates indicates outliers above the trend # remove upper outliers: umask = np.logical_not(umask) lmask = lmask[umask] time = time[umask] raw_flux = raw_flux[umask] cor_flux = cor_flux[umask] # "detrend" corrected flux: normalized_cor_flux, trend = normalize_lc(cor_flux, lmask) # run TLS: tls_results, valid = run_tls(time, normalized_cor_flux) if not valid: break # if the detection is significant, plot it and log it: if _calc_stats(tls_results): if isinstance(tls_results.transit_times, list): plot_lc(time, raw_flux, cor_flux, normalized_cor_flux, trend, star_id, planet_id, provenance, tls_results, interactive=interactive) plotno += 1 plot_tls(star_id, planet_id, provenance, plotno, tls_results, interactive=interactive) plotno += 1 log_results(star_id, planet_id, provenance, tls_results) append_summary_log(summary_log, star_id, campaign_no, planet_id, provenance, tls_results) # Remove detected transit and remove it from the data series: intransit = transit_mask(time, tls_results.period, 2 * tls_results.duration, tls_results.T0) raw_flux = raw_flux[~intransit] cor_flux = cor_flux[~intransit] time = time[~intransit] time, cor_flux = cleaned_array(time, cor_flux) planet_id += 1
def tls_search(time, flux, epoch, period, rplanet): SNR = 1e12 SNR_threshold = 5. FOUND_SIGNAL = False #::: mask out the first detection at 6.2 days, with a duration of 2.082h, and T0=1712.54922 #intransit = transit_mask(time, 6.26391, 2*2.082/24., 1712.54922) #time = time[~intransit] #flux = flux[~intransit] time, flux = cleaned_array(time, flux) #::: search for the rest while (SNR >= SNR_threshold) and (FOUND_SIGNAL==False): logprint('\n====================================================================') model = transitleastsquares(time, flux) R_starx=rstar/u.R_sun #print(rstar_min/u.R_sun) #print(rstar_max/u.R_sun) #print(mstar/u.M_sun) #print(mstar_min/u.M_sun) #print(mstar_max/u.M_sun) results = model.power(u=ab, R_star= radius,#rstar/u.R_sun, R_star_min=rstar_min, #rstar_min/u.R_sun, R_star_max=rstar_max, #rstar_max/u.R_sun, M_star= mass, #mstar/u.M_sun, M_star_min= mstar_min,#mstar_min/u.M_sun, M_star_max=mstar_max, #mstar_max/u.M_sun, period_min=0.5, period_max=20, n_transits_min=1, show_progress_bar=False ) #mass and radius for the TLS #rstar=radius ##mstar=mass #mstar_min = mass-massmin #mstar_max = mass+massmax #rstar_min = radius-radiusmin #rstar_max = radius+raduismax if results.snr >= SNR_threshold: logprint('\nPeriod:', format(results.period, '.5f'), 'd') logprint(len(results.transit_times), 'transit times in time series:', ['{0:0.5f}'.format(i) for i in results.transit_times]) logprint('Transit depth:', format(1.-results.depth, '.5f')) logprint('Best duration (days):', format(results.duration, '.5f')) logprint('Signal detection efficiency (SDE):', results.SDE) logprint('Signal-to-noise ratio (SNR):', results.snr) intransit = transit_mask(time, results.period, 2*results.duration, results.T0) time = time[~intransit] flux = flux[~intransit] time, flux = cleaned_array(time, flux) #::: check if it found the right signal right_period = IsMultipleOf(results.period, period/2.) #check if it is a multiple of half the period to within 5% right_epoch = False for tt in results.transit_times: for i in range(-5,5): right_epoch = right_epoch or (np.abs(tt-epoch+i*period) < (1./24.)) #check if any epochs matches to within 1 hour # right_depth = (np.abs(np.sqrt(1.-results.depth)*rstar - rplanet)/rplanet < 0.05) #check if the depth matches if right_period and right_epoch: logprint('*** SUCCESFULLY FOUND THE TRANSIT ***') with open( os.path.join('tls_table/tls_table.csv'), 'a' ) as f: f.write(str(period)+','+str(rplanet/u.R_earth)+','+'1\n') FOUND_SIGNAL = True else: logprint('No other signals detected with SNR >= ' + str(SNR_threshold)) if FOUND_SIGNAL == False: logprint('*** FAILED FINDING THE TRANSIT ***') with open( os.path.join('tls_table/tls_table.csv'), 'a' ) as f: f.write(str(period)+','+str(rplanet)+','+'0\n') SNR = results.snr
def save_plot(time_i, global_final, results, i): #start the plotting fig, (ax1, ax2, ax3) = plt.subplots(nrows=1, ncols=3, figsize=(10, 3), constrained_layout=True) #1-Plot all the transits in_transit = transit_mask(time_i, results.period, results.duration, results.T0) ax1.scatter(time_i[in_transit], global_final[i][in_transit], color='red', s=2, zorder=0) ax1.scatter(time_i[~in_transit], global_final[i][~in_transit], color='black', alpha=0.05, s=2, zorder=0) ax1.plot(results.model_lightcurve_time, results.model_lightcurve_model, alpha=1, color='red', zorder=1) #plt.scatter(time_n, flux_new_n, color='orange', alpha=0.3, s=20, zorder=3) plt.xlim(time_i.min(), time_i.max()) #plt.xlim(1362.0,1364.0) ax1.set(xlabel='Time (days)', ylabel='Relative flux') #phase folded plus binning ax2.plot(results.model_folded_phase, results.model_folded_model, color='red') ax2.scatter(results.folded_phase, results.folded_y, color='black', s=10, alpha=0.05, zorder=2) if (results.SDE) != 0: bins = 200 bin_means, bin_edges, binnumber = stats.binned_statistic( results.folded_phase, results.folded_y, statistic='mean', bins=bins) bin_stds, _, _ = stats.binned_statistic(results.folded_phase, results.folded_y, statistic='std', bins=bins) bin_width = (bin_edges[1] - bin_edges[0]) bin_centers = bin_edges[1:] - bin_width / 2 ax2.errorbar(bin_centers, bin_means, yerr=bin_stds / 2, xerr=bin_width / 2, marker='o', markersize=4, color='teal', alpha=1, linestyle='none') ax2.set_xlim(0.45, 0.55) ax2.set(xlabel='Phase', ylabel='Relative flux') plt.ticklabel_format(useOffset=False) else: ax2.set_xlim(0.45, 0.55) ax2.set(xlabel='Phase', ylabel='Relative flux') plt.ticklabel_format(useOffset=False) #SDE ax3 = plt.gca() ax3.axvline(results.period, alpha=0.4, lw=3) plt.xlim(np.min(results.periods), np.max(results.periods)) for n in range(2, 10): ax3.axvline(n * results.period, alpha=0.4, lw=1, linestyle="dashed") ax3.axvline(results.period / n, alpha=0.4, lw=1, linestyle="dashed") ax3.set(xlabel='Period (days)', ylabel='SDE') ax3.plot(results.periods, results.power, color='black', lw=0.5) ax3.set_xlim(0., max(results.periods)) return fig
def tls_search(*args, tic=None, shape='default', star_params=None, rms_calc=True, norm_val=1., clean_lc=False, starparams_out=False, del_dur=1., verbose=False, nthreads=6, **kwargs): #!!Change if flux_err is all nans to replace it with rms flux_err!! """ Function to perform a search for periodic signals using the Transit Least Squares (TLS) algorithm developed by Hippke & Heller 2018. While slower than Box Least Squares, the transit shape used in the search is more realistic. Parameters ---------- *args : `lightkurve.LightCurve` object or multiple numpy array arguments If the len = 1, then the argument is assumed to be a `lightkurve.LightCurve` object with at least two columns, labeled ``'time'`` and ``'flux'``, respectively, with an optional ``'flux_err'`` column as the third column. If the len of > 1, then it is assumed the user is passing ``time``, ``flux``, and ``flux_err`` (optional), respectively. These columns or arguments should be arrays of equal length. tic : int or None TIC ID of the source that the light curve comes from. This will be used to query the TIC for the stellar parameters of the system. May be set to ``None`` if a full dictionary of stellar params are provided to the ``star_params`` keyword. shape : str The shape used by TLS to search for periodic signals. The user may specify ``'default'``, ``'grazing'``, or ``'box'``. See Hippke & Heller 2018 for an in-depth description of these shapes. star_params : dict or None A dictionary containing stellar parameters to be used in the TLS search. The dictionary can contain an array of limb-darkening parameters, stellar radius, lower radius error, upper radius error, stellar mass, lower mass error, and upper mass error labeled ``'ab'``, ``'rstar'``, ``'rlow'``, ``'rhigh'``, ``'mstar'``, ``'mlow'``, and ``'mhigh'``, respectively. The error values are the errors themselves and not the upper and lower values for each of the parameters. A partial list may be included, but in this case, the TIC must also be given. rms_calc : bool A flag to denote whether the root mean square error will be applied in the case that error values are not provided. norm_val : float Value that the light curve is normalized to. Default is 1. Only 1 or 0 are valid normalizations for TLS. clean_lc : bool Flag to indicate whether or not to output a cleaned lightcurve with the recovered periodic signal masked out. Results in an additional expected output. Default ``False``. starparams_out : bool Flag to indicate whether or not to output the dictionary of stellar parameters used in the TLS search. Results in an additional expected output. del_dur : float How many durations worth of data points should be excluded from cleaned light curve centered on the transit center. Default is 1. Values < 1 will result in some in-transit points remaining while values > 1 will remove some points outside the transit. verbose : bool Flag to have function print more while it runs. nthreads : int Number of threads to be used for running the signal search. Many times, cores have the capability to run multiple threads, so be sure to check your machine to optimize this parameter. **kwargs : dict Optional arguments passed to the ``transitleastsquares.power`` function. Returns ------- results : dict Results of the TLS fit. See TLS documentation for the contents of this dictionary and descriptions of each element. cleaned_lc : ``lightkurve.LightCurve`` object, optional A light curve with the transits masked out based on the results of the TLS search. """ dy = None #processing inputs if not tic and (not star_params or len(star_params) != 7): raise ValueError('Either tic or full star_params dictionary must' + ' be given!') if len(args) == 1: lc = args[0] time = lc.time flux = lc.flux try: if lc.flux_err is None: print('No flux errors provided') else: dy = lc.flux_err except: print('No flux errors provided') elif len(args) > 1: time = args[0] flux = args[1] if len(args) == 3: if args[2] is not None: dy = args[2] else: print('No flux_errors provided') else: print('No flux errors provided') if rms_calc == True and not isinstance(dy, np.ndarray): dy = np.ones(len(flux)) * rms(flux, norm_val=norm_val) print('RMS will be used for errors') #get catalog info and/or parse user-provided values if tic and (not star_params or len(star_params) != 7): print(f'Gathering stellar params that were not provided...', end='\r') ab, R_star, R_star_lowe, R_star_highe, M_star, M_star_lowe, M_star_highe = tls.catalog_info(TIC_ID=int(tic)) cat = {'ab' : ab, 'rstar' : R_star, 'rlow' : R_star_lowe, 'rhigh' : R_star_highe, 'mstar' : M_star, 'mlow' : M_star_lowe, 'mhigh' : M_star_highe} if not star_params: star_params = {} missing = list(set(cat) - set(star_params)) star_params.update({k: cat[k] for k in missing}) print('Gathering stellar params that were not provided... Done!') #quality control for stellar params dc = star_params dc['rstar'] = 1.0 if math.isnan(dc['rstar']) else dc['rstar'] dc['mstar'] = 1.0 if math.isnan(dc['mstar']) else dc['mstar'] dc['mlow'] = 0.1 if math.isnan(dc['mlow']) else dc['mlow'] dc['mhigh'] = 0.1 if math.isnan(dc['mhigh']) else dc['mhigh'] dc['rlow'] = 0.1 if math.isnan(dc['rlow']) else dc['rlow'] dc['rhigh'] = 0.1 if math.isnan(dc['rhigh']) else dc['rhigh'] rmax = dc['rstar'] + dc['rhigh'] rmin = dc['rstar'] - dc['rlow'] mmax = dc['mstar'] + dc['mhigh'] mmin = dc['mstar'] - dc['mlow'] if verbose: print('Stellar params used:') for i in list(dc.keys()): print(str(i) + ' = ' + str(dc[i])) print('(defaults are solar and 0.1 for errors)') #beginning TLS search print('Searching using TLS using %s shape...' % shape) model = tls.transitleastsquares(t=time, y=flux, dy=dy) results = model.power(R_star=dc['rstar'], R_star_min=rmin, R_star_max=rmax, M_star=dc['mstar'], M_star_min=mmin, M_star_max=mmax, u=dc['ab'], transit_template=shape, use_threads=nthreads, **kwargs) #cleaning light curve if clean_lc flag if clean_lc: intransit = tls.transit_mask(time, results.period, del_dur * results.duration, results.T0) time2 = time[~intransit] flux2 = flux[~intransit] dy2 = dy[~intransit] time2, flux2, dy2 = tls.cleaned_array(time2, flux2, dy2) lc_clean = LightCurve(time=time2, flux=flux2, flux_err=dy2) if clean_lc and not starparams_out: return results, lc_clean elif not clean_lc and starparams_out: return results, dc elif clean_lc and starparams_out: return results, lc_clean, dc else: return results
def measure_centroid(t0, per, dur, sector, sourceid, c_obj, outdir): """ Quoting Kostov+2019, who I followed: ''' 1) create the mean in-transit and out-of-transit images for each transit (ignoring cadences with non-zero quality flags), where the latter are based on the same number of exposure cadences as the former, split evenly before and after the transit; 2) calculate the overall mean in-transit and out-of-transit images by averaging over all transits; 3) subtract the overall mean out-of-transit image from the overall in-transit image to produce the overall mean difference image; and 4) measure the center-of-light for each difference and out-of-transit image by calculating the corresponding x- and y-moments of the image. The measured photocenters for the three planet candidates are shown in Figures 9, 10, and 11, and listed in Table 3. We detect no significant photocenter shifts between the respective difference images and out-of-transit images for any of the planet candidates, which confirms that the targets star is the source of the transits. ''' args: t0,per,dur : float, days. Epoch, period, duration. Used to isolate transit windows. sector (int) sourceid (int) c_obj (SkyCoord): location of target star returns: outdict = { 'm_oot_flux':m_oot_flux, # mean OOT image 'm_oot_flux_err':m_oot_flux_err, # mean OOT image uncert 'm_intra_flux':m_intra_flux, # mean in transit image 'm_intra_flux_err':m_intra_flux_err, # mean in transit image uncert 'm_oot_minus_intra_flux':m_oot_minus_intra_flux, # mean OOT - mean intra 'm_oot_minus_intra_flux_err':m_oot_minus_intra_flux_err, 'm_oot_minus_intra_snr':m_oot_minus_intra_snr, 'ctds_intra':ctds_intra, # centroids of all transits 'ctds_oot':ctds_oot, # centroids of all ootransits 'ctds_oot_minus_intra':ctds_oot_minus_intra, # centroids of diff 'm_ctd_intra':m_ctd_intra, # centroid of mean intransit image 'm_ctd_oot':m_ctd_oot, 'intra_imgs_flux':intra_imgs_flux, 'oot_imgs_flux':oot_imgs_flux, 'intra_imgs_flux_err':intra_imgs_flux_err, 'oot_imgs_flux_err':oot_imgs_flux_err } """ print('beginning tesscut for {}'.format(repr(c_obj))) try: cuthdul = Tesscut.get_cutouts(c_obj, size=10, sector=sector) except (requests.exceptions.HTTPError, requests.exceptions.ConnectionError) as e: print('got {}, try again'.format(repr(e))) pytime.sleep(30) try: cuthdul = Tesscut.get_cutouts(c_obj, size=10, sector=sector) except (requests.exceptions.HTTPError, requests.exceptions.ConnectionError) as e: print('ERR! sourceid {} FAILED TO GET TESSCUTOUT'.format(sourceid)) return None if len(cuthdul) != 1: print('ERR! sourceid {} GOT {} CUTOUTS'.format(sourceid, len(cuthdul))) return None else: cuthdul = cuthdul[0] data, data_hdr = cuthdul[1].data, cuthdul[1].header cutout_wcs = wcs.WCS(cuthdul[2].header) # flux and flux_err are image cubes of (time x spatial x spatial) quality = data['QUALITY'] flux = data['FLUX'] flux_err = data['FLUX_ERR'] time = data['TIME'] # in TJD time += 2457000 # now in BJD time = time[quality == 0] flux = flux[quality == 0] flux_err = flux_err[quality == 0] intra = transit_mask(time, per, dur, t0) mingap = per / 2 ngroups, groups = lcmath.find_lc_timegroups(time[intra], mingap=mingap) oot_times = time[~intra] # make mean in-transit and out-of-transit images & uncertainty maps for # each transit intra_imgs_flux, oot_imgs_flux = [], [] intra_imgs_flux_err, oot_imgs_flux_err = [], [] for group in groups: thistra_intra_time = time[intra][group] thistra_intra_flux = flux[intra][group] thistra_intra_flux_err = flux_err[intra][group] thistra_oot_time = _get_desired_oot_times(time, thistra_intra_time) thistra_oot_flux = flux[np.in1d(time, thistra_oot_time)] thistra_oot_flux_err = flux_err[np.in1d(time, thistra_oot_time)] intra_imgs_flux.append(np.mean(thistra_intra_flux, axis=0)) intra_imgs_flux_err.append(np.mean(thistra_intra_flux_err, axis=0)) oot_imgs_flux.append(np.mean(thistra_oot_flux, axis=0)) oot_imgs_flux_err.append(np.mean(thistra_oot_flux_err, axis=0)) # shapes: (n_transits, spatial, spatial) intra_imgs_flux = nparr(intra_imgs_flux) intra_imgs_flux_err = nparr(intra_imgs_flux_err) oot_imgs_flux = nparr(oot_imgs_flux) oot_imgs_flux_err = nparr(oot_imgs_flux_err) # average over transits, to get mean in-transit and out-of-transit images. m_intra_flux = np.mean(intra_imgs_flux, axis=0) m_intra_flux_err = np.mean(intra_imgs_flux_err, axis=0) m_oot_flux = np.mean(oot_imgs_flux, axis=0) m_oot_flux_err = np.mean(oot_imgs_flux_err, axis=0) # compute x and y centroid values for mean image m_ctd_intra = compute_centroid_from_first_moment(m_intra_flux) m_ctd_oot = compute_centroid_from_first_moment(m_oot_flux) ctd_m_oot_minus_m_intra = compute_centroid_from_first_moment(m_oot_flux - m_intra_flux) # compute x and y centroid values for each transit ctds_intra = nparr([ compute_centroid_from_first_moment(intra_imgs_flux[ix, :, :]) for ix in range(intra_imgs_flux.shape[0]) ]) ctds_oot = nparr([ compute_centroid_from_first_moment(oot_imgs_flux[ix, :, :]) for ix in range(oot_imgs_flux.shape[0]) ]) ctds_oot_minus_intra = nparr([ compute_centroid_from_first_moment(oot_imgs_flux[ix, :, :] - intra_imgs_flux[ix, :, :]) for ix in range(oot_imgs_flux.shape[0]) ]) # make OOT - intra image. NOTE: the uncertainty map might be a bit wrong, # b/c you should get a sqrt(N) on the flux in the mean image. I think. m_oot_minus_intra_flux = m_oot_flux - m_intra_flux m_oot_minus_intra_flux_err = np.sqrt(m_oot_flux_err**2 + m_intra_flux**2) m_oot_minus_intra_snr = m_oot_minus_intra_flux / m_oot_minus_intra_flux_err # calculate the centroid shift amplitude, deltaC = C_oot - C_intra delta_ctd_px = np.sqrt((m_ctd_oot[0] - m_ctd_intra[0])**2 + (m_ctd_oot[1] - m_ctd_intra[1])**2) delta_ctd_arcsec = delta_ctd_px * 21 # 21arcsec/px # error in centroid shift amplitude: assume it is roughly the scatter in # OOT centroids delta_ctd_err_px = np.sqrt( np.std(ctds_oot, axis=0)[0]**2 + np.std(ctds_oot, axis=0)[1]**2) delta_ctd_err_arcsec = delta_ctd_err_px * 21 outdict = { 'cutout_wcs': cutout_wcs, 'm_oot_flux': m_oot_flux, # mean OOT image 'm_oot_flux_err': m_oot_flux_err, # mean OOT image uncert 'm_intra_flux': m_intra_flux, # mean in transit image 'm_intra_flux_err': m_intra_flux_err, # mean in transit image uncert 'm_oot_minus_intra_flux': m_oot_minus_intra_flux, # mean OOT - mean intra 'm_oot_minus_intra_flux_err': m_oot_minus_intra_flux_err, 'm_oot_minus_intra_snr': m_oot_minus_intra_snr, 'ctds_intra': ctds_intra, # centroids of all transits 'ctds_oot': ctds_oot, # centroids of all ootransits 'ctds_oot_minus_intra': ctds_oot_minus_intra, 'm_ctd_intra': m_ctd_intra, # centroid of mean intransit image 'm_ctd_oot': m_ctd_oot, 'ctd_m_oot_minus_m_intra': ctd_m_oot_minus_m_intra, 'intra_imgs_flux': intra_imgs_flux, 'oot_imgs_flux': oot_imgs_flux, 'intra_imgs_flux_err': intra_imgs_flux_err, 'oot_imgs_flux_err': oot_imgs_flux_err, 'delta_ctd_arcsec': delta_ctd_arcsec, 'delta_ctd_err_arcsec': delta_ctd_err_arcsec, 'delta_ctd_sigma': delta_ctd_arcsec / delta_ctd_err_arcsec } outpath = os.path.join(outdir, '{}_ctds.pkl'.format(str(sourceid))) with open(outpath, 'wb') as f: pickle.dump(outdict, f) print('made {}'.format(outpath)) return outdict
def seach_one_koi(KOI): # Check if vetting file exists already figure_out_path = '_FAIL0-5d_' + str(KOI) figure_out_path2 = 'GOOD0-5d_' + str(KOI) if figure_out_path in " ".join( glob.glob("*.png")) or figure_out_path2 in " ".join( glob.glob("*.png")): print('Vetting sheet for this KOI exists already, skipping KOI', KOI) else: print('Working on file', KOI) time, flux = loadkoi(str(KOI)) row = numpy.argmax(catalog["KOI"].astype(int) == int(float(KOI))) KIC = catalog["KIC"][row] print('row', row) print('KIC', KIC) max_t14 = T14(R_s=catalog["R_star"][row], M_s=catalog["mass"][row], P=period_max) window = 3 * max_t14 print('R_star, mass, max_t14, window', catalog["R_star"][row], catalog["mass"][row], max_t14, window) if time is not None: # Remove known planets periods, t0s, tdurs = get_planets(KIC) print('periods', periods) for no in range(len(periods)): print('Removing planet', no + 1, periods[no], t0s[no], tdurs[no]) intransit = transit_mask(time, periods[no], 2 * tdurs[no], t0s[no]) flux = flux[~intransit] time = time[~intransit] time, flux = cleaned_array(time, flux) print('Next planet is number', no + 2) # Detrend data and remove high outliers print('Detrending with window...', window) #print(time) #print(flux) trend1 = trend(time, flux, window=window, c=5) #plt.scatter(time, flux, s=1, color='black') #plt.plot(time, trend1, color='red') #plt.show() #trend = scipy.signal.medfilt(flux, 31) #trend = scipy.signal.savgol_filter(trend, 25, 2) rawflux = flux.copy() rawtime = time.copy() y_filt = flux / trend1 y_filt = sigma_clip(y_filt, sigma_upper=3, sigma_lower=1e10) time, y_filt = cleaned_array(time, y_filt) #plt.close() #plt.scatter(time, y_filt, s=1, color='black') #plt.show() a = catalog["ld1"][row] b = catalog["ld2"][row] print('LD ab = ', a, b) try: model = transitleastsquares(time, y_filt) results = model.power( #n_transits_min=1, u=(a, b), R_star=catalog["R_star"][row], R_star_max=catalog["R_star"][row] + 2 * catalog["rad_up"][row], # sign is OK, is negative in catalog: R_star_min=catalog["R_star"][row] + 2 * catalog["rad_down"][row], M_star=catalog["mass"][row], M_star_max=catalog["mass"][row] + 2 * catalog["mass_up"][row], M_star_min=catalog["mass"][row] + 2 * catalog["mass_down"][row], oversampling_factor=5, duration_grid_step=1.05, use_threads=1, period_min=period_min, period_max=period_max, show_progress_bar=False, T0_fit_margin=0.1) tls_worked = True #except ValueError: # tls_worked = False valid = True if tls_worked: # Check if phase space has gaps bins = numpy.linspace(0, 1, 100) digitized = numpy.digitize(results.folded_phase, bins) bin_means = [ results.folded_phase[digitized == i].mean() for i in range(1, len(bins)) ] #print('bin_means', bin_means) if numpy.isnan(bin_means).any(): print('Vetting fail! Phase bins contain NaNs') valid = False if results.distinct_transit_count == 1 and results.transit_count >= 2: valid = False print( 'Vetting fail! results.distinct_transit_count==1 and results.transit_count == 2' ) if results.distinct_transit_count == 2 and results.transit_count >= 3: valid = False print( 'Vetting fail! results.distinct_transit_count==2 and results.transit_count == 3' ) if results.SDE < 8: valid = False print('Vetting fail! results.SDE < 8', results.SDE) if results.snr < 7: valid = False print('Vetting fail! results.snr < 7', results.snr) upper_transit_depths = results.transit_depths + results.transit_depths_uncertainties if results.transit_count == 2 and max( upper_transit_depths) > 1: valid = False print('Vetting fail! 2 transits, only 1 significant') upper_transit_depths = results.transit_depths + results.transit_depths_uncertainties if results.transit_count == 3 and max( upper_transit_depths) > 1: valid = False print( 'Vetting fail! 3 transits, not all 3 significant') upper_transit_depths = results.transit_depths + results.transit_depths_uncertainties if results.transit_count == 4 and max( upper_transit_depths) > 1: valid = False print( 'Vetting fail! 4 transits, not all 4 significant') if results.depth < 0.95: valid = False print('Vetting fail! Transit depth < 0.95', results.depth) print('Signal detection efficiency (SDE):', format(results.SDE, '.1f')) print('SNR:', format(results.snr, '.1f')) print('Search completed') #if valid: print('Attempting figure...') make_figure(KOI=str(KOI), planet_number=no + 2, results=results, t=time, y=flux, y_filt=y_filt, trend=trend1, rawtime=rawtime, rawflux=rawflux, catalog=catalog, row=row, valid=valid) #else: # print('No figure made, vetting failed!') else: print('TLS failed') process = psutil.Process(os.getpid()) ram_mb = process.memory_info().rss / 2**20 print('RAM (MB)', ram_mb) if ram_mb > 25000: sys.exit() except: pass
# Visualise fit on phase-folded curve plt.figure() plt.plot(results.model_folded_phase, results.model_folded_model, color='red') plt.scatter(results.folded_phase, results.folded_y, color='blue', s=10, alpha=0.5, zorder=2) #plt.xlim(0.49, 0.51) plt.xlabel('Phase') plt.ylabel('Relative flux') # Plot over original data plt.figure() in_transit = transit_mask(lc.time, results.period, results.duration, results.T0) plt.scatter(lc.time[in_transit], lc.flux[in_transit], color='red', s=2, zorder=0) plt.scatter(lc.time[~in_transit], lc.flux[~in_transit], color='blue', alpha=0.5, s=2, zorder=0) plt.plot(results.model_lightcurve_time, results.model_lightcurve_model, alpha=0.5, color='red',
numpy.testing.assert_almost_equal(max(results.power), 45.934646920004326, decimal=5) numpy.testing.assert_almost_equal(max(results.power_raw), 44.00867236551441, decimal=5) numpy.testing.assert_almost_equal(min(results.power), -0.620153987656165, decimal=5) numpy.testing.assert_almost_equal(min(results.power_raw), -0.29015390864908414, decimal=5) print("Detrending of power spectrum from power_raw passed") # Mask of the first planet intransit = transit_mask(t, results.period, 2 * results.duration, results.T0) y_second_run = y_filt[~intransit] t_second_run = t[~intransit] t_second_run, y_second_run = cleaned_array(t_second_run, y_second_run) # Search for second planet model_second_run = transitleastsquares(t_second_run, y_second_run) results_second_run = model_second_run.power() numpy.testing.assert_almost_equal(results_second_run.duration, 0.1478628403227008, decimal=5) numpy.testing.assert_almost_equal(results_second_run.SDE, 34.98291056410117, decimal=5) numpy.testing.assert_almost_equal(results_second_run.rp_rs, 0.025852178872027086,
def test_fit_and_remove_transits(): # -------------------------------------------------------- # test case: transitleastsuqares ORIGINAL TEST reproduced # -------------------------------------------------------- np.random.seed(seed=0) # reproducibility # Create test data time_start = 3.14 data_duration = 100 samples_per_day = 48 samples = int(data_duration * samples_per_day) time = np.linspace(time_start, time_start + data_duration, samples) # Use batman to create transits ma = batman.TransitParams() ma.t0 = time_start # time of inferior conjunction; first transit is X days after start ma.per = 10.123 # orbital period ma.rp = 6371 / 696342 # 6371 planet radius (in units of stellar radii) ma.a = 19 # semi-major axis (in units of stellar radii) ma.inc = 90 # orbital inclination (in degrees) ma.ecc = 0 # eccentricity ma.w = 90 # longitude of periastron (in degrees) ma.u = [0.4, 0.4] # limb darkening coefficients ma.limb_dark = "quadratic" # limb darkening model m = batman.TransitModel(ma, time) # initializes model synthetic_signal = m.light_curve(ma) # calculates light curve # Create noise and merge with flux ppm = 50 # Noise level in parts per million noise = np.random.normal(0, 10**-6 * ppm, int(samples)) flux = synthetic_signal + noise # call the fit function trflux, results = fit_and_remove_transits(time, flux) # test transit parameters assert ma.rp == pytest.approx(results.rp_rs, rel=.05) assert results.period == pytest.approx(ma.per, rel=1e-3) assert results.duration == pytest.approx(0.171, rel=1e-2) assert results.T0 == pytest.approx(ma.per + time_start, rel=1e-2) in_transit = tls.transit_mask(time, results.period, results.duration, results.T0) assert (np.where(in_transit)[0] == np.array([ 0, 1, 2, 3, 482, 483, 484, 485, 486, 487, 488, 489, 968, 969, 970, 971, 972, 973, 974, 975, 1453, 1454, 1455, 1456, 1457, 1458, 1459, 1460, 1461, 1939, 1940, 1941, 1942, 1943, 1944, 1945, 1946, 1947, 2425, 2426, 2427, 2428, 2429, 2430, 2431, 2432, 2911, 2912, 2913, 2914, 2915, 2916, 2917, 2918, 3397, 3398, 3399, 3400, 3401, 3402, 3403, 3404, 3883, 3884, 3885, 3886, 3887, 3888, 3889, 3890, 4369, 4370, 4371, 4372, 4373, 4374, 4375, 4376])).all() # out of transit the flux is conserved assert ((trflux-flux)[~in_transit] < 1e-16).all() # ------------------------------- # test case: SINGLE KNOWN TRANSIT # ------------------------------- np.random.seed(seed=0) ma.per = 70.123 # orbital period upped to get a single transit ma.t0 = time_start - 10 # shift the first transit such there is really only one transit left m = batman.TransitModel(ma, time) # initializes model synthetic_signal = m.light_curve(ma) # calculates light curve # Create noise and merge with flux ppm = 50 # Noise level in parts per million noise = np.random.normal(0, 10**-6 * ppm, int(samples)) flux = synthetic_signal + noise # call the fit function trflux, results = fit_and_remove_transits(time, flux) # the period recovered is too short when we have a single transit assert results.period == pytest.approx(15.48, rel=1e-2) # call the fit function with constrained orbital period # and uncertainty improves the result drastically trflux, results = fit_and_remove_transits(time, flux, pl_orbper=70., pl_orbpererr=2.) # test transit parameters assert ma.rp == pytest.approx(results.rp_rs, rel=.1) # single transits have trouble finding a correct period obviously for a single transit assert results.period == pytest.approx(71.75, rel=1e-2) # but the centering on the transit is fine assert results.T0 == pytest.approx(ma.per + ma.t0, rel=1e-2) in_transit = tls.transit_mask(time, results.period, results.duration, results.T0) # single transit recovered, yes assert (np.where(in_transit)[0] == np.arange(2865,2915)).all() # out of transit the flux is conserved assert ((trflux-flux)[~in_transit] < 1e-16).all()