def eclipse_model(self, xi): r""" Compute eclipse model at orbital phases ``xi``. Parameters ---------- xi : `~numpy.ndarray` Orbital phase angle :math:`\xi` Returns ------- eclipse : `~numpy.ndarray` Eclipse model normalized such that flux is zero in eclipse. """ from batman import TransitModel xi_over_pi = xi / np.pi eclipse = TransitModel( self, xi_over_pi, transittype='secondary', exp_time=xi_over_pi[1] - xi_over_pi[0], supersample_factor=3, ).light_curve(self) eclipse -= eclipse.min() return eclipse
def transit_model(theta, t, exp_time=0.002): params = TransitParams() t0, p, k, r, b, q1, q2 = theta[:7] u1, u2 = q_to_u(q1, q2) a = arstar(p, r) i = inclination(a, b) params.t0 = t0 #time of inferior conjunction params.per = p #orbital period params.rp = k #planet radius (in units of stellar radii) params.a = a #semi-major axis (in units of stellar radii) params.inc = i #orbital inclination (in degrees) params.ecc = 0. #eccentricity params.w = 90. #longitude of periastron (in degrees) params.u = [u1, u2] #limb darkening coefficients params.limb_dark = "quadratic" m = TransitModel(params, t, supersample_factor=1, exp_time=exp_time) # m = TransitModel(params, t) return m.light_curve(params)
def test_phase_curve(depth_frac): """ from linea.tests.test_core import test_phase_curve as f; f() """ p = Planet.from_name("55 Cnc e") ra = generate_recarray_55Cnce(sinusoid_amp_depth_frac=depth_frac) lc = CheopsLightCurve(ra, norm=True) # check that normalization brings flux continuum to unity: assert abs(np.median(lc.flux) - 1) < 1e-5 lc.sigma_clip_flux() # Check that some points get masked by the flux sigma clipping assert np.count_nonzero(lc.mask) > 0 transit_model = TransitModel( p, lc.bjd_time[~lc.mask], supersample_factor=3, exp_time=lc.bjd_time[1] - lc.bjd_time[0], ).light_curve(p) - 1 t = lc.bjd_time[~lc.mask, None] - lc.bjd_time.mean() # Build a design matrix X = np.hstack([ # Transit model: transit_model[:, None], # Sinusoidal phase curve trend: np.sin(2 * np.pi * t / p.per), np.cos(2 * np.pi * t / p.per), # Default design matrix: lc.design_matrix(), ]) r = lc.regress(X) sinusoid = X[:, 1:3] @ r.betas[1:3] phase_curve_amp_ppm = 1e6 * sinusoid.ptp() phase_curve_amp_error_ppm = 1e6 * np.max(np.sqrt(np.diag(r.cov))[1:3]) true_amp_ppm = 2e6 * p.rp**2 * depth_frac # Check that phase curve amplitude is within 2 sigma of the true answer agreement_sigma = (abs(phase_curve_amp_ppm - true_amp_ppm) / phase_curve_amp_error_ppm) assert agreement_sigma < 2
def test_eclipse(eclipse_depth): """ from linea.tests.test_core import test_eclipse as f; f() """ p = Planet.from_name("WASP-189 b") ras = generate_recarrays_WASP189(depth_ppm=eclipse_depth) lcs = JointLightCurve([CheopsLightCurve(ra) for ra in ras]) # check that normalization brings flux continuum to unity: assert all([abs(np.median(lc.flux) - 1) < 1e-5 for lc in lcs]) for lc in lcs: lc.sigma_clip_flux() # Check that some points get masked by the flux sigma clipping assert all([np.count_nonzero(lc.mask) > 0 for lc in lcs]) all_lcs = lcs.concatenate() eclipse_model = TransitModel( p, all_lcs.bjd_time[~all_lcs.mask], supersample_factor=3, transittype='secondary', exp_time=lcs[0].bjd_time[1] - lcs[0].bjd_time[0], ).light_curve(p) - 1 X_combined = lcs.combined_design_matrix() # Build a design matrix X = np.hstack([ # Default combined design matrix: X_combined, # Eclipse model eclipse_model[:, None] ]) r = lcs.regress(X) obs_eclipse_depth = r.betas[-1] eclipse_error_ppm = np.sqrt(np.diag(r.cov))[-1] # Check that eclipse depth is within 2 sigma of the true answer agreement_sigma = (abs(obs_eclipse_depth - eclipse_depth) / eclipse_error_ppm) assert agreement_sigma < 2
def init_batman(t, init_params, exp_time=0.000694): a = inclination(init_params['p'], init_params['r']) i = inclination(a, init_params['b']) params = TransitParams() params.t0 = init_params['t0'] #time of inferior conjunction params.per = init_params['p'] #orbital period params.rp = init_params['k'] #planet radius (in units of stellar radii) params.a = a #semi-major axis (in units of stellar radii) params.inc = i #orbital inclination (in degrees) params.ecc = 0. #eccentricity params.w = 90. #longitude of periastron (in degrees) u1, u2 = q_to_u(init_params['q1'], init_params['q2']) params.u = [u1, u2] #limb darkening coefficients params.limb_dark = "quadratic" tm = TransitModel(params, t, supersample_factor=1, exp_time=exp_time) return tm
def generate_recarrays_WASP189(depth_ppm=80, seed=42, n_outliers=50, cheops_orbit_min=99.5, obs_efficiency=0.55, n_visits=4): p = Planet.from_name("WASP-189 b") np.random.seed(seed) record_arrays = [] for i in range(n_visits): all_times = np.linspace(p.t0 + p.per * (0.5 + i) - 0.1 * p.per, p.t0 + p.per * (0.5 + i) + 0.1 * p.per, 2500) cheops_phase = (((all_times[0] - all_times) * u.day) % (cheops_orbit_min * u.min) / (cheops_orbit_min * u.min)).to( u.dimensionless_unscaled).value observable = cheops_phase > (1 - obs_efficiency) bjd_time = all_times[observable] utc_time = Time(bjd_time, format='jd').isot mjd_time = Time(bjd_time, format='jd').mjd n_points = len(bjd_time) conta_lc = 10 * np.ones(n_points) + np.random.randn(n_points) conta_lc_err = np.ones(n_points) / 100 status = np.zeros(n_points) event = np.zeros(n_points) dark = 10 + np.random.randn(n_points) background = 100 + np.random.randn(n_points) roll_angle = simulate_roll_angle(cheops_phase[observable], bjd_time, phase_offset=0.1) location_x = 512 * np.ones(n_points) location_y = 512 * np.ones(n_points) centroid_x = location_x[0] + 0.2 * np.random.randn(n_points) centroid_y = location_y[0] + 0.2 * np.random.randn(n_points) p.fp = depth_ppm * 1e-6 model = TransitModel(p, bjd_time, supersample_factor=3, transittype='secondary', exp_time=bjd_time[1] - bjd_time[0]).light_curve(p) basis_vectors = ((bjd_time - bjd_time.mean()), background, conta_lc, dark, roll_angle / 360, (centroid_x - location_x)**2, (centroid_y - location_y)**2, (centroid_x - location_x), (centroid_y - location_y), dark.mean()) flux = np.zeros_like(bjd_time) for c, v in zip(true_basis_vector_weights, basis_vectors): flux += c * v flux *= model fluxerr = np.std(flux) * np.ones(len(bjd_time)) rand_indices = np.random.randint(0, flux.shape[0], size=n_outliers) flux[rand_indices] += 3.5 * flux.std() * np.random.randn(n_outliers) formatter = [('UTC_TIME', '|S26', utc_time), ('MJD_TIME', '>f8', mjd_time), ('BJD_TIME', '>f8', bjd_time), ('FLUX', '>f8', flux), ('FLUXERR', '>f8', fluxerr), ('STATUS', '>i4', status), ('EVENT', '>i4', event), ('DARK', '>f8', dark), ('BACKGROUND', '>f8', background), ('CONTA_LC', '>f8', conta_lc), ('CONTA_LC_ERR', '>f8', conta_lc_err), ('ROLL_ANGLE', '>f8', roll_angle), ('LOCATION_X', '>f4', location_x), ('LOCATION_Y', '>f4', location_y), ('CENTROID_X', '>f4', centroid_x), ('CENTROID_Y', '>f4', centroid_y)] ra = np.recarray((n_points, ), names=[name for name, _, _ in formatter], formats=[fmt for _, fmt, _ in formatter]) for name, fmt, arr in formatter: ra[name] = arr record_arrays.append(ra) return [fitsrec.FITS_rec(ra) for ra in record_arrays]
target_inds_observed.update(object_inds_to_observe) # Split the night evenly into N chunks try: split_times = np.split(times, n_objects_per_night) except ValueError: split_times = np.split( times[len(times) % n_objects_per_night:], n_objects_per_night) for i, ind in enumerate(object_inds_to_observe): obs_times = split_times[i].jd target_name = table['spc'][ind] params.inc = table['inclination'][ind] params.t0 = table['epoch'][ind] transit_model = TransitModel(params, obs_times).light_curve(params) # Only save LCs containing transits if transit_model.min() < 0.995: random_night = np.random.randint(0, len(real_lcs)) obs_fluxes = transit_model * np.tile( real_lcs[random_night].T, 2).T[:len(obs_times), 1] obs_database[target_name]['times'].append(obs_times) obs_database[target_name]['fluxes'].append(obs_fluxes) obs_database[target_name]['model'].append(transit_model) obs_database[target_name]['transit'] = True N_transits = 0 for key in obs_database: if obs_database[key]['transit']:
def light_curve(self, spot_lons, spot_lats, spot_radii, inc_stellar, planet=None, times=None, fast=False, time_ref=None, return_spots_occulted=False, transit_model_kwargs={}): """ Generate a(n ensemble of) light curve(s). Light curve output will have shape ``(n_phases, len(inc_stellar))`` or ``(len(times), len(inc_stellar))``. Parameters ---------- spot_lons : `~numpy.ndarray` Spot longitudes spot_lats : `~numpy.ndarray` Spot latitudes spot_radii : `~numpy.ndarray` Spot radii inc_stellar : `~numpy.ndarray` Stellar inclinations planet : `~batman.TransitParams`, optional Transiting planet parameters times : `~numpy.ndarray`, optional Times at which to compute the light curve fast : bool, optional When `True`, use approximation that spots are fixed on the star during a transit event. When `False`, account for motion of starspots on stellar surface due to rotation during transit event. Default is `False`. time_ref : float, optional Reference time used as the initial rotational phase of the star, such that the sub-observer point is at zero longitude at ``time_ref``. return_spots_occulted : bool, optional Return whether or not spots have been occulted. Returns ------- light_curves : `~numpy.ndarray` Stellar light curves of shape ``(n_phases, len(inc_stellar))`` or ``(len(times), len(inc_stellar))`` """ if time_ref is None: if times is not None: time_ref = 0 else: time_ref = self.phases[0] # Compute the spot positions in cartesian coordinates: tilted_spots = self.spherical_to_cartesian(spot_lons, spot_lats, inc_stellar, times=times, planet=planet, time_ref=time_ref) # Compute the distance of each spot from the stellar centroid, mask # any spots that are "behind" the star, in other words, x < 0 r = np.ma.masked_array(np.hypot(tilted_spots.y.value, tilted_spots.z.value), mask=tilted_spots.x.value < 0) ld = limb_darkening_normed(self.u_ld, r) # Compute the out-of-transit flux missing due to each spot f_spots = (np.pi * spot_radii**2 * (1 - self.spot_contrast) * ld * np.sqrt(1 - r**2)) if planet is None: # If there is no transiting planet, skip the transit routine: lambda_e = np.zeros((len(self.phases), 1)) else: if not inc_stellar.isscalar: raise ValueError('Transiting exoplanets are implemented for ' 'planets transiting single stars only, but ' '``inc_stellar`` has multiple values. ') # Compute a transit model from batman import TransitModel n_spots = len(spot_lons) m = TransitModel(planet, times, **transit_model_kwargs) lambda_e = 1 - m.light_curve(planet)[:, np.newaxis] # Compute the true anomaly of the planet at each time, f: f = m.get_true_anomaly() # Compute the position of the planet in cartesian coordinates using # Equations 53-55 of Murray & Correia (2010). Note that these # coordinates are different from the cartesian coordinates used for # the spot positions. In this system, the observer is at X-> -inf. I = np.radians(90 - planet.inc) Omega = np.radians(planet.w) # this is 90 deg by default omega = np.pi / 2 X = planet.a * (np.cos(Omega) * np.cos(omega + f) - np.sin(Omega) * np.sin(omega + f) * np.cos(I)) Y = planet.a * (np.sin(Omega) * np.cos(omega + f) + np.cos(Omega) * np.sin(omega + f) * np.cos(I)) Z = planet.a * np.sin(omega + f) * np.sin(I) # Create a shapely circle object for the planet's silhouette only # when the planet is in front of the star, otherwise append `None` planet_disk = [ circle([-Y[i], -Z[i]], planet.rp) if (np.abs(Y[i]) < 1 + planet.rp) and (X[i] < 0) else None for i in range(len(f)) ] if fast: spots_occulted = self._planet_spot_overlap_fast( planet, planet_disk, tilted_spots, spot_radii, n_spots, X, Y, lambda_e) else: spots_occulted = self._planet_spot_overlap_slow( planet, planet_disk, tilted_spots, spot_radii, n_spots, X, Y, lambda_e) # Return the flux missing from the star at each time due to spots # (f_spots/self.f0) and due to the transit (lambda_e): if return_spots_occulted: return (1 - np.sum(f_spots.filled(0) / self.f0, axis=1) - lambda_e, spots_occulted) else: return 1 - np.sum(f_spots.filled(0) / self.f0, axis=1) - lambda_e
def generate_recarray_55Cnce(seed=42, sinusoid_amp_depth_frac=0.25, n_outliers=50, cheops_orbit_min=99.5, obs_efficiency=0.5): p = Planet.from_name("55 Cnc e") np.random.seed(seed) all_times = np.linspace(p.t0 - 0.5, p.t0 + 1., 2500) cheops_phase = (((all_times[0] - all_times) * u.day) % (cheops_orbit_min * u.min) / (cheops_orbit_min * u.min)).to( u.dimensionless_unscaled).value bjd_time = all_times[cheops_phase > (1 - obs_efficiency)] utc_time = Time(bjd_time, format='jd').isot mjd_time = Time(bjd_time, format='jd').mjd n_points = len(bjd_time) conta_lc = 10 * np.ones(n_points) + np.random.randn(n_points) conta_lc_err = np.ones(n_points) / 100 status = np.zeros(n_points) event = np.zeros(n_points) dark = 10 + np.random.randn(n_points) background = 100 + np.random.randn(n_points) roll_angle = simulate_roll_angle(cheops_phase[cheops_phase > 0.5], bjd_time) location_x = 512 * np.ones(n_points) location_y = 512 * np.ones(n_points) centroid_x = location_x[0] + 0.2 * np.random.randn(n_points) centroid_y = location_y[0] + 0.2 * np.random.randn(n_points) model = TransitModel(p, bjd_time, supersample_factor=3, exp_time=bjd_time[1] - bjd_time[0]).light_curve(p) planet_phase = ((bjd_time - p.t0) % p.per) / p.per sinusoid_amp = sinusoid_amp_depth_frac * p.rp**2 model += sinusoid_amp * np.sin(2 * np.pi * (planet_phase + 0.5)) basis_vectors = (np.cos(np.radians(roll_angle)), np.sin(np.radians(roll_angle)), np.ones(len(roll_angle))) flux = np.zeros_like(bjd_time) for c, v in zip(true_basis_vector_weights, basis_vectors): flux += c * v flux *= model fluxerr = np.std(flux) * np.ones(len(bjd_time)) rand_indices = np.random.randint(0, flux.shape[0], size=n_outliers) flux[rand_indices] += 3.5 * flux.std() * np.random.randn(n_outliers) formatter = [('UTC_TIME', '|S26', utc_time), ('MJD_TIME', '>f8', mjd_time), ('BJD_TIME', '>f8', bjd_time), ('FLUX', '>f8', flux), ('FLUXERR', '>f8', fluxerr), ('STATUS', '>i4', status), ('EVENT', '>i4', event), ('DARK', '>f8', dark), ('BACKGROUND', '>f8', background), ('CONTA_LC', '>f8', conta_lc), ('CONTA_LC_ERR', '>f8', conta_lc_err), ('ROLL_ANGLE', '>f8', roll_angle), ('LOCATION_X', '>f4', location_x), ('LOCATION_Y', '>f4', location_y), ('CENTROID_X', '>f4', centroid_x), ('CENTROID_Y', '>f4', centroid_y)] ra = np.recarray((n_points, ), names=[name for name, _, _ in formatter], formats=[fmt for _, fmt, _ in formatter]) for name, fmt, arr in formatter: ra[name] = arr return fitsrec.FITS_rec(ra)