Source code for pepita.exoInfoMatrix

import numpy as np
import theano
import theano.tensor as tt
import exoplanet as xo
import matplotlib.pyplot as plt

[docs]class exoInfoMatrix: """ Information Analysis Matrix class. To initalize, use the exposure time (cadence) of the lightcurve in seconds. For example: info_matrix = exoInfoMatrix(20) # 20 second cadence Then define the parameters of the fiducial model info_matrix.set_data( time_array, period_val, t0_val, ror_val, b_val, u1_val, u2_val, m_star_val, r_star_val, ) To calculate the covariance matrix, passing the error of the measurements into it cov_matrix = info_matrix.eval_cov(sigma) """ def __init__(self, exptime, oversample=100): """Initialize the class Parameters ---------- exptime : float exptime or cadence in seconds oversample : int, optional number of samples for calculation of binned lightcurves, by default 100 """ # Initialize the fiducial model variables, cadence value and oversample value. self.exptime = exptime self.oversample = oversample self._t = None, self._period = None self._t0 = None self._ror = None self._b = None self._u1 = None self._u2 = None self._m_star_val = None self._r_star_val = None # Keeps track of whether the parameters have been defined or not self.params = None # IMPORTANT: This keeps track of the variable names of the model and in which order they're expected to be given. self.legend = { "t" : 0, "period" : 1, "t0" : 2, "ror" : 3, "b" : 4, "u1" : 5, "u2" : 6, "m_star" : 7, "r_star" : 8 } # Keep track of whether derivatives, parameters or priors have been calculated self._has_deriv = False self._has_params = False self._has_priors = False # Initialize the derivatives, prior and covariance matrices self.deriv_matrix = None self.priors = None self.cov_matrix = None
[docs] def set_data(self, time_array, period_val, t0_val, ror_val, b_val, u1_val, u2_val, m_star_val, r_star_val): """Set the data for the fiducial model. The fiducial model is used to evaluate the derivatives and can be a first approximation of the planet parameters from the maximum likelihood set of parameters or from previous fits. Parameters ---------- time_array : array[float] Timestamps of the lightcurve. In days. period_val : float Period of the planet in days t0_val : float Reference time for the middle of one of the planet transits. In days. ror_val : float Radius ratio of the planet and star b_val : float Impact parameter of the planet u1_val : float First limb-darkening parameter for a quadratic limb-darkening model u2_val : float Second limb-darkening parameter for a quadratic limb-darkening model m_star_val : float Mass of the star in solar masses r_star_val : float Radius of star in solar radii """ # Because changing data, # reset derivative matrix and require it to be calculated again self._has_deriv = False self.deriv_matrix = None self._has_fisher = False self.fisher_matrix = None # Set values self._t = time_array self._period = period_val self._t0 = t0_val self._ror = ror_val self._b = b_val self._u1 = u1_val self._u2 = u2_val self._m_star = m_star_val self._r_star = r_star_val # Initialize scalar variables t = tt.dscalar() period = tt.dscalar() t0 = tt.dscalar() ror = tt.dscalar() b = tt.dscalar() u1 = tt.dscalar() u2 = tt.dscalar() m_star = tt.dscalar() r_star = tt.dscalar() r = ror * r_star # Make sure order is the same as in the legend self.params = [t, period, t0, ror, b, u1, u2, m_star, r_star] # Initialize star self.star = xo.LimbDarkLightCurve(u1, u2) # Initialize orbit. You may change what parameters you use to define the orbit by changing the values above. Remember to define the self._variable, initalize a scalar variable, include your variables in the legend and include it in self.params. self.orbit = xo.orbits.KeplerianOrbit( r_star=r_star, m_star=m_star, period=period, t0=t0, b=b ) # Get light_curve function. Same here, you can change how you define the lightcurve self.lc = tt.sum( self.star.get_light_curve( orbit=self.orbit, r=r, t=[t], texp=self.exptime/(3600.0*24.0), oversample=self.oversample ) ) # This function serves for calculating the derivatives self.val_and_grad_func = theano.function( self.params, [self.lc] + list(theano.grad(self.lc, self.params)) ) self._has_params = True
# Updates params in case any has changed. Only for internal use def _updateParams(self): # Call set data to update params self.set_data( time_array=self._t, period_val=self._period, t0_val=self._t0, ror_val=self._ror, b_val=self._b, u1_val=self._u1, u2_val=self._u2, m_star_val=self._m_star, r_star_val=self._r_star ) # Evaluates the derivative and flux at a single point
[docs] def eval_point(self, tval): """Evaluates the derivatives at a given point Parameters ---------- tval : float Time at which to evaluate the derivatives, in same units as time_array Returns ------- array array of derivatives Raises ------ ValueError If values of the fiducial model have not been set. """ if not self._has_params: raise ValueError("Must define parameters first") # Remember to include here if you've changed the variables. The order should be the same as in self.params return np.stack( self.val_and_grad_func( tval, self._period, self._t0, self._ror, self._b, self._u1, self._u2, self._m_star, self._r_star ) )
[docs] def eval_deriv_matrix(self): """Evaluates the matrix of derivatives for all timestamps in time_array Returns ------- array Matrix of derivatives Raises ------ ValueError If fiducial model parameters have not been defined """ # Evaluates the derivative matrix if not self._has_params: raise ValueError("Must define parameters first") self.deriv_matrix = [] for tval in self._t: result = self.eval_point(tval) self.deriv_matrix.append(result) self.deriv_matrix = np.array(self.deriv_matrix) self._has_deriv = True return self.deriv_matrix
[docs] def plot_derivs(self, fig_ax=None): """Plots the derivatives of the transit model. Parameters ---------- fig_ax : (figure, axes), optional If derivatives should be plotted in given figure and axes. Note that this is meant to be used for plotting different model derivatives on top of eachother and so (figure, axis) should be the output of calling plot_derivs() in other model. Returns ------- (figure, axes) figure and axes """ # If derivative matrix hasn't yet been calculated, do it if not self._has_deriv: self.eval_deriv_matrix() # Use figure if given if fig_ax is None: fig, ax = plt.subplots(len(self.deriv_matrix[1]), figsize=(10, 20)) else: fig, ax = fig_ax for n, label in enumerate([ "Flux", "dF/dt", "dF/dperiod", "dF/dt0", "dF/dror", "dF/b", "dF/du1", "dF/du2", "dF/dm_star", "dF/dr_star" ]): if n == 0: ax[n].plot( self._t, self.deriv_matrix[:, n], label=str(self.exptime) + "s" ) else: ax[n].plot( self._t, self.deriv_matrix[:, n] ) ax[n].set_ylabel(label) ax[-1].set_xlabel("time [days]") ax[0].legend() return fig, ax
[docs] def setExptime(self, exptime): """Changes the exposure time (cadence) of the model. Will force redefinition of parameters Parameters ---------- exptime : float The new cadence in seconds. """ # Can be used to change the cadence self.exptime = exptime self._has_fisher = False self._has_deriv = False self._has_params = False self._updateParams()
[docs] def set_priors(self, period_prior=np.nan, t0_prior=np.nan, ror_prior=np.nan, b_prior=np.nan, u1_prior=np.nan, u2_prior=np.nan, m_star_prior=np.nan, r_star_prior=np.nan): """ Used to define priors for the parameters Parameters ---------- period_prior : float Prior for planet period in days t0_prior : float Prior for t0 in days ror_prior : float Prior for planet ratio b_prior : float Prior for impact parameter u1_prior : float Prior for first quadratic limb darkening parameter u2_prior : float Prior for second quadratic limb darkening parameter m_star_prior : float Prior for mass of the star r_star_prior : float Prior for radius of the star Returns ------- array Priors matrix """ # Used to set the priors diag = [ period_prior, t0_prior, ror_prior, b_prior, u1_prior, u2_prior, m_star_prior, r_star_prior ] self.priors = np.nan_to_num(np.diag(np.power(diag, -2)), 0) self._has_priors = True return self.priors
[docs] def erase_priors(self): """ Used to erase all priors """ # Erases any priors self.priors = None self._has_priors = False self._has_fisher = False
[docs] def eval_fisher(self, sigma): """Used to evaluate the information matrix Parameters ---------- sigma : float or array Error in the flux measurements. Either a single float value to be used for all points or an array of size len(time_array) with individual errors for each timestamps Returns ------- array Information matrix """ # Evaluates the fisher information matrix if not self._has_params: raise ValueError("Must define params first") if not self._has_deriv: self.eval_deriv_matrix() self.fisher_matrix = np.full( (len(self.params)-1, len(self.params)-1), np.nan ) # -1 because not interested in time for i in range(0, len(self.params)-1): for j in range(0, len(self.params)-1): # +2 because we don't want the value of flux [0] # or the value of the time derivative [1] self.fisher_matrix[i, j] = np.sum( self.deriv_matrix[:, i+2] * self.deriv_matrix[:, j+2] * np.power(sigma, -2) ) if np.nan in self.fisher_matrix: raise ValueError("Something didn't work in the calculation") if self._has_priors: self.fisher_matrix = self.fisher_matrix + self.priors return self.fisher_matrix
[docs] def eval_cov(self, sigma=None): """Used to evaluate the covariance matrix Parameters ---------- sigma : float or array Error in the flux measurements. Either a single float value to be used for all points or an array of size len(time_array) with individual errors for each timestamps Returns ------- array Covariance matrix """ # If Fisher matrix not calculated, calculate it if self._has_fisher is False: # Need sigma to calculate it if sigma is None: raise ValueError("Need a sigma") else: self.eval_fisher(sigma) # If it is calculated but a sigma is given, recalculate it elif sigma is not None: self.eval_fisher(sigma) self.cov_matrix = np.linalg.inv(self.fisher_matrix) return self.cov_matrix
[docs] def get_in_transit(self): """Get the number of data points which are in-transit Returns ------- int Number of points in transit """ # Get number of points in transit orbit = xo.orbits.KeplerianOrbit( t0=self._t0, period=self._period, b=self._b, m_star=self._m_star, r_star=self._r_star, ror=self._ror ) in_transit = orbit.in_transit( self._t, r=self._ror * self._r_star, texp=self.exptime/(3600*24) ).eval() return in_transit
[docs] def get_approx_transit_duration(self, n_points=10000): """Approximate the duration of the transit. Not efficient and used only for testing purposes Parameters ---------- n_points : int, optional number of points for calculating appproximation, by default 10000 Returns ------- float Approximate duration of the transit """ # Make an approximation of the transit duration. The higher the number of points the more accurate it will be dur_t = np.linspace(-self._period, self._period, n_points) orbit = xo.orbits.KeplerianOrbit( t0=0, period=self._period, b=self._b, m_star=self._m_star, r_star=self._r_star, ror=self._ror ) in_transit = orbit.in_transit( dur_t, r=self._ror * self._r_star, texp=self.exptime/(3600*24) ).eval() start = None end = None prev_val = None for i, val in enumerate(in_transit): if i == 0: prev_val = val continue next_val = prev_val + 1 if next_val != val: if start is None: start = val prev_val = val continue elif end is None: end = prev_val prev_val = val break prev_val = val duration = dur_t[end] - dur_t[start] return duration
[docs] def set_t(self, time_array): """Change the time array of the model Parameters ---------- time_array : array New timestamps for the data """ # Sets the time array self._t = time_array self._has_deriv = False self._has_fisher = False self._updateParams()