def create_folded_figured_based_on_clicks_in_unfolded_figure( self, unfolded_figure): # Setup empty period recording clicks for folding. event_coordinates = [] event_coordinates_data_source = ColumnDataSource({ 'Time (BTJD)': [], 'Relative flux': [] }) unfolded_figure.circle('Time (BTJD)', 'Relative flux', source=event_coordinates_data_source, color='red', alpha=0.8) # Will be updated. # Prepare the folded plot. folded_data_source = ColumnDataSource({ 'Relative flux': self.relative_fluxes, 'Folded time (days)': [], 'Time (BTJD)': self.times }) folded_figure = Figure(x_axis_label='Folded time (days)', y_axis_label='Relative flux', title=f'Folded {self.title}') self.plot_light_curve_source(folded_figure, folded_data_source, time_column_name='Folded time (days)') folded_figure.sizing_mode = 'stretch_width' self_ = self def click_unfolded_figure_callback( tap_event): # Setup what should happen when a click occurs. event_coordinate = tap_event.x, tap_event.y event_coordinates.append(event_coordinate) event_coordinates_data_source.data = { 'Time (BTJD)': [coordinate[0] for coordinate in event_coordinates], 'Relative flux': [coordinate[1] for coordinate in event_coordinates] } if len( event_coordinates ) > 1: # If we have more than 1 period click, we can start folding. event_times = [ coordinate[0] for coordinate in event_coordinates ] epoch, period = self.calculate_epoch_and_period_from_approximate_event_times( event_times) folded_times = self.fold_times(self_.times, epoch, period) folded_data_source.data['Folded time (days)'] = folded_times # folded_figure.x_range.start = -period/10 # folded_figure.x_range.end = period/10 self_.period = period self_.transit_epoch = epoch period_depths = [ coordinate[1] for coordinate in event_coordinates ] self_.depth = np.abs(np.mean(period_depths)) unfolded_figure.on_event(Tap, click_unfolded_figure_callback) return folded_figure
def create_unfolded_light_curve_figure(self, data_source): figure = Figure(title='Unfolded light curve', x_axis_label='Time (BTJD)', y_axis_label='Normalized PDCSAP flux', active_drag='box_zoom') self.plot_folding_colored_light_curve_source(figure, data_source) figure.sizing_mode = 'stretch_width' return figure
def create_folded_figured_based_on_clicks_in_unfolded_figure( self, unfolded_figure, data_source): # Setup empty period recording clicks for folding. unfolded_figure.circle('Time (BTJD)', 'Normalized PDCSAP flux', source=self.fold_coordinate_data_source, color='red', alpha=0.8) # Will be updated. # Prepare the folded plot. folded_figure = Figure(x_axis_label='Folded time (days)', y_axis_label='Normalized PDCSAP flux', title=f'Folded light curve') self.plot_folding_colored_light_curve_source( folded_figure, data_source, time_column_name='Folded time (days)') folded_figure.sizing_mode = 'stretch_width' self_ = self @gen.coroutine def update_click_dots(): self.fold_coordinate_data_source.data = { 'Time (BTJD)': [coordinate[0] for coordinate in self.event_coordinates], 'Normalized PDCSAP flux': [coordinate[1] for coordinate in self.event_coordinates] } @gen.coroutine def update_folded_figure(folded_times): data_source.data['Folded time (days)'] = folded_times @gen.coroutine @without_document_lock def click_unfolded_figure_callback( tap_event): # Setup what should happen when a click occurs. event_coordinate = tap_event.x, tap_event.y self.event_coordinates.append(event_coordinate) self.bokeh_document.add_next_tick_callback(update_click_dots) if len( self.event_coordinates ) > 1: # If we have more than 1 period click, we can start folding. event_times = [ coordinate[0] for coordinate in self.event_coordinates ] epoch, period = self.calculate_epoch_and_period_from_approximate_event_times( event_times) folded_times = self.fold_times(data_source.data['Time (BTJD)'], epoch, period) self.bokeh_document.add_next_tick_callback( partial(update_folded_figure, folded_times=folded_times)) self_.period = period self_.transit_epoch = epoch period_depths = [ coordinate[1] for coordinate in self.event_coordinates ] self_.depth = np.abs(np.mean(period_depths)) unfolded_figure.on_event(Tap, click_unfolded_figure_callback) return folded_figure
def create_light_curve_figure(self): figure = Figure(title=self.title, x_axis_label='Time (BTJD)', y_axis_label='Relative flux', active_drag='box_zoom') data_source = ColumnDataSource({ 'Time (BTJD)': self.times, 'Relative flux': self.relative_fluxes }) self.plot_light_curve_source(figure, data_source) figure.sizing_mode = 'stretch_width' return figure
def view_database_file(_id, file_type): ds = load_file(_id, file_type) d = {} for data_var in ds.data_vars: d[data_var] = [ds[data_var].values] d["to_plot"] = [ds[list(ds.data_vars)[0]].values] source = ColumnDataSource(d) callback = CustomJS( args=dict(source=source), code=""" var data = source.data; data['to_plot'] = data[cb_obj.value]; source.change.emit(); """, ) select = Select(title="Variable:", options=list(ds.data_vars)) select.js_on_change("value", callback) p = Figure( x_range=(-180, 180), y_range=(-90, 90), aspect_ratio=2.5, tools="pan,wheel_zoom,box_zoom,reset, hover", ) p.sizing_mode = "scale_width" p.image( image="to_plot", x=-180, y=-90, dw=360, dh=180, source=source, palette="Viridis11", ) script, div = components( column(column(select), p, sizing_mode="stretch_width")) return render_template( "app/bokeh_plot.html", script=script, div=div, data_file_id=_id, title=file_type.capitalize(), )
def show_light_curve(self, light_curve_path: Path): """ Shows a figure of the light curve at the passed path. :param light_curve_path: The path of the light curve. """ fluxes, times = self.load_fluxes_and_times_from_fits_file( light_curve_path) figure = Figure(title=str(light_curve_path), x_axis_label='Flux', y_axis_label='Time', active_drag='box_zoom') color = 'mediumblue' figure.line(times, fluxes, line_color=color, line_alpha=0.1) figure.circle(times, fluxes, line_color=color, line_alpha=0.4, fill_color=color, fill_alpha=0.1) figure.sizing_mode = 'stretch_width' show(figure)
def create_flux_comparison_light_curve_figure( ) -> (Figure, ColumnDataSource): figure = Figure(title='Normalized flux comparison', x_axis_label='Time (BTJD)', y_axis_label='Normalized flux', active_drag='box_zoom') data_source = ColumnDataSource({ 'Time (BTJD)': [], 'Normalized PDCSAP flux': [], 'Normalized SAP flux': [], 'Time (days)': [], 'Folded time (days)': [] }) def add_light_curve(times_column_name, fluxes_column_name, legend_label, color): """Adds a light curve to the figure.""" figure.line(source=data_source, x=times_column_name, y=fluxes_column_name, line_color=color, line_alpha=0.1) figure.circle(source=data_source, x=times_column_name, y=fluxes_column_name, legend_label=legend_label, line_color=color, line_alpha=0.4, fill_color=color, fill_alpha=0.1) add_light_curve('Time (BTJD)', 'Normalized PDCSAP flux', 'PDCSAP', 'firebrick') add_light_curve('Time (BTJD)', 'Normalized SAP flux', 'SAP', 'mediumblue') figure.sizing_mode = 'stretch_width' return figure, data_source
def create_mcmc_fit_figures(self, run_fitting_button): self_ = self initial_fit_figure = Figure(x_axis_label='Folded time (days)', y_axis_label='Relative flux', title=f'Initial fit') parameters_table_columns = [ TableColumn(field=column, title=column) for column in ['parameter', 'mean', 'sd', 'r_hat'] ] parameters_table = DataTable(source=self.parameters_table_data_source, columns=parameters_table_columns, editable=True) initial_fit_data_source = self.initial_fit_data_source bokeh_document = self.bokeh_document @gen.coroutine def update_initial_fit_figure(fluxes, gp_pred, inds, lc_pred, map_soln, relative_times, times, x_fold): initial_fit_data_source.data['Time (BTJD)'] = times initial_fit_data_source.data['Time (days)'] = relative_times initial_fit_data_source.data['Folded time (days)'] = x_fold initial_fit_data_source.data[ 'Relative flux'] = fluxes - gp_pred - map_soln["mean"] initial_fit_data_source.data[ 'Fit'] = lc_pred[inds] - map_soln["mean"] initial_fit_data_source.data['Fit time'] = x_fold[ inds] # TODO: This is terrible, you should be able to line them up *afterward* to not make a duplicate time column @gen.coroutine @without_document_lock def fit(self, map_soln, model): with model: trace = pm.sample( tune=2000, draws=2000, start=map_soln, chains=4, step=xo.get_dense_nuts_step(target_accept=0.9), ) trace_summary = pm.summary( trace, round_to='none' ) # Not a typo. PyMC3 wants 'none' as a string here. epoch = round( trace_summary['mean']['Transit epoch (BTJD)'], 3) # Round the epoch differently, as BTJD needs more digits. trace_summary['mean'] = self_.round_series_to_significant_figures( trace_summary['mean'], 5) trace_summary['mean']['Transit epoch (BTJD)'] = epoch self.bokeh_document.add_next_tick_callback( partial(self.update_parameters_table, trace_summary)) with pd.option_context('display.max_columns', None, 'display.max_rows', None): print(trace_summary) print(f'Star radius: {self.star_radius}') # TODO: This should not happen automatically. Only after a button click. # scopes = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive'] # credentials = Credentials.from_service_account_file( # 'ramjet/analysis/google_spreadsheet_credentials.json', scopes=scopes) # gc = gspread.authorize(credentials) # sh = gc.open('Ramjet transit candidates shared for vetting') # worksheet = sh.get_worksheet(0) # # Find first empty row. # empty_row_index = 1 # for row_index in itertools.count(start=1): # row_values = worksheet.row_values(row_index) # if len(row_values) == 0: # empty_row_index = row_index # break # worksheet.update_cell(empty_row_index, 1, self_.tic_id) # worksheet.update_cell(empty_row_index, 2, str(self_.sectors).replace('[', '').replace(']', '')), # worksheet.update_cell(empty_row_index, 3, trace_summary['mean']['Transit epoch (BTJD)']) # worksheet.update_cell(empty_row_index, 4, trace_summary['mean']['period']) # worksheet.update_cell(empty_row_index, 5, trace_summary['mean']['Transit depth (relative flux)']) # worksheet.update_cell(empty_row_index, 6, trace_summary['mean']['Transit duration (days)']) # worksheet.update_cell(empty_row_index, 7, self_.star_radius) # worksheet.update_cell(empty_row_index, 8, trace_summary['mean']['Planet radius (solar radii)'] * self_.star_radius) @gen.coroutine @without_document_lock def run_fitting(): times = self.light_curve_data_source.data['Time (BTJD)'].astype( np.float32) flux_errors = self.light_curve_data_source.data[ 'Normalized PDCSAP flux error'] fluxes = self.light_curve_data_source.data[ 'Normalized PDCSAP flux'] relative_times = self.light_curve_data_source.data['Time (days)'] nan_indexes = np.union1d( np.argwhere(np.isnan(fluxes)), np.union1d(np.argwhere(np.isnan(times)), np.argwhere(np.isnan(flux_errors)))) fluxes = np.delete(fluxes, nan_indexes) flux_errors = np.delete(flux_errors, nan_indexes) times = np.delete(times, nan_indexes) relative_times = np.delete(relative_times, nan_indexes) with pm.Model() as model: # Stellar parameters mean = pm.Normal("mean", mu=0.0, sigma=10.0 * 1e-3) u = xo.distributions.QuadLimbDark("u") star_params = [mean, u] # Gaussian process noise model sigma = pm.InverseGamma("sigma", alpha=3.0, beta=2 * np.nanmedian(flux_errors)) log_Sw4 = pm.Normal("log_Sw4", mu=0.0, sigma=10.0) log_w0 = pm.Normal("log_w0", mu=np.log(2 * np.pi / 10.0), sigma=10.0) kernel = xo.gp.terms.SHOTerm(log_Sw4=log_Sw4, log_w0=log_w0, Q=1.0 / 3) noise_params = [sigma, log_Sw4, log_w0] # Planet parameters log_ror = pm.Normal("log_ror", mu=0.5 * np.log(self_.depth), sigma=10.0 * 1e-3) ror = pm.Deterministic("ror", tt.exp(log_ror)) depth = pm.Deterministic('Transit depth (relative flux)', tt.square(ror)) planet_radius = pm.Deterministic('Planet radius (solar radii)', ror * self_.star_radius) # Orbital parameters log_period = pm.Normal("log_period", mu=np.log(self_.period), sigma=1.0) t0 = pm.Normal('Transit epoch (BTJD)', mu=self_.transit_epoch, sigma=1.0) log_dur = pm.Normal("log_dur", mu=np.log(0.1), sigma=10.0) b = xo.distributions.ImpactParameter("b", ror=ror) period = pm.Deterministic('Transit period (days)', tt.exp(log_period)) dur = pm.Deterministic('Transit duration (days)', tt.exp(log_dur)) # Set up the orbit orbit = xo.orbits.KeplerianOrbit(period=period, duration=dur, t0=t0, b=b, r_star=self.star_radius) # We're going to track the implied density for reasons that will become clear later pm.Deterministic("rho_circ", orbit.rho_star) # Set up the mean transit model star = xo.LimbDarkLightCurve(u) def lc_model(t): return mean + tt.sum(star.get_light_curve( orbit=orbit, r=ror * self.star_radius, t=t), axis=-1) # Finally the GP observation model gp = xo.gp.GP(kernel, times, (flux_errors**2) + (sigma**2), mean=lc_model) gp.marginal("obs", observed=fluxes) # Double check that everything looks good - we shouldn't see any NaNs! print(model.check_test_point()) # Optimize the model map_soln = model.test_point map_soln = xo.optimize(map_soln, [sigma]) map_soln = xo.optimize(map_soln, [log_ror, b, log_dur]) map_soln = xo.optimize(map_soln, noise_params) map_soln = xo.optimize(map_soln, star_params) map_soln = xo.optimize(map_soln) with model: gp_pred, lc_pred = xo.eval_in_model( [gp.predict(), lc_model(times)], map_soln) x_fold = (times - map_soln['Transit epoch (BTJD)'] + 0.5 * map_soln['Transit period (days)'] ) % map_soln['Transit period (days)'] - 0.5 * map_soln[ 'Transit period (days)'] inds = np.argsort(x_fold) bokeh_document.add_next_tick_callback( partial(update_initial_fit_figure, fluxes, gp_pred, inds, lc_pred, map_soln, relative_times, times, x_fold)) self.bokeh_document.add_next_tick_callback( partial(fit, self, map_soln, model)) run_fitting_button.on_click(run_fitting) self.plot_folding_colored_light_curve_source( initial_fit_figure, self.initial_fit_data_source, time_column_name='Folded time (days)', flux_column_name='Relative flux') initial_fit_figure.line('Fit time', 'Fit', source=self.initial_fit_data_source, color='black', line_width=3) initial_fit_figure.sizing_mode = 'stretch_width' return initial_fit_figure, parameters_table