def _post_initialisation(self): self.t0 = 2458491.8771169 self.period = 1.2652328 self.k2 = 0.3**2 self.a = 10. self.phase = fold(self.timea, self.period, self.t0) self.em = UniformModel() self.em.set_data(self.timea) self._mec = self.em.evaluate(sqrt( self.k2), self.t0 + 0.5 * self.period, self.period, self.a, 0.5 * pi) - 1 self._mec = 1 + self._mec / self._mec.ptp() phi = 2 * pi * self.phase self.alpha = a = abs(phi - pi) self._rff = (sin(a) + (pi - a) * cos(a)) / pi * self._mec # Reflected light self._dbf = sin(phi) # Doppler boosting self._evf = -cos(2 * phi) # Ellipsoidal variations self._cwl = 1e-9 * tess.wl self._ctm = tess.tm self.set_prior('ms', 'NP', star_m.n, star_m.s) self.set_prior('teffh', 'NP', 3300, 100) self.set_prior('teffc', 'UP', 100, 3300) self.set_prior('gp_log10_wn', 'NP', -1.74, 0.15)
def plot_folded_planets(self, bwidth: float = 10): fig, axs = subplots(1, self.nplanets, figsize=(13, 6), sharey=True) pv = self.de.minimum_location.copy() for ipl in range(self.nplanets): planets = set(arange(self.nplanets)) planets.remove(ipl) mflux = self.transit_model(pv, planets=[ipl]) rflux = self.transit_model(pv, planets=planets) oflux = self.ofluxa / rflux phase = (fold(self.timea, pv[3 + 4 * ipl], pv[2 + 4 * ipl], 0.5) - 0.5) * pv[3 + 4 * ipl] m = abs(phase) < 0.5 * self.bldur sids = argsort(phase[m]) phase, mflux, oflux = phase[m][sids], mflux[m][sids], oflux[m][ sids] bp, bf, be = downsample_time(phase, oflux, bwidth / 24 / 60) axs[ipl].errorbar(bp, bf, be, fmt='ok') axs[ipl].plot(phase, oflux, '.', alpha=0.25) axs[ipl].plot(phase, mflux, 'k') setp(axs, xlim=(-0.5 * self.bldur, 0.5 * self.bldur), ylim=(0.996, 1.004)) fig.tight_layout() return fig
def read_eleanor(zero_epoch, period, bjdrefi, mask_transits=False, trwidth=0.053, contamination=None): dd = root / 'photometry' files = sorted(dd.glob('hlsp_eleanor*.fits')) times, fluxes, wnids, lcids = [], [], [], [] s = 0 for i, f in enumerate(files): df = Table.read(f) time = df['TIME'].data + bjdrefi flux = df['CORR_FLUX'].data m = isfinite(time) & isfinite(flux) if mask_transits: p = (fold(time, period, zero_epoch, 0.5) - 0.5) * period m &= (abs(p) > 0.5 * trwidth) flux /= nanmedian(flux) m &= create_eleanor_sector_mask(flux, i) if contamination is not None: flux = decontaminate(flux, contamination[i]) if m.sum() > 0: s += 1 times.append(time[m]) fluxes.append(flux[m]) lcids.append(full(time[m].size, i, 'int')) wns = [diff(f).std() / sqrt(2) for f in fluxes] pbs = s * ['TESS'] return times, fluxes, pbs, wns, pbs, list(arange(len(times)))
def plot_folded_tess_transit(self, method='de', pv=None, figsize=None, ylim=None): assert method in ('de', 'mc') if pv is None: if method == 'de': pv = self.de.minimum_location else: df = self.posterior_samples(derived_parameters=False) pv = df.median.values etess = self._ntess t = self.timea[:etess] fo = self.ofluxa[:etess] fm = squeeze(self.transit_model(self.de.population))[self.de.minimum_index, :etess] bl = squeeze(self.baseline(self.de.population))[self.de.minimum_index, :etess] fig, ax = subplots(figsize=figsize) phase = pv[1] * (fold(t, pv[1], pv[0], 0.5) - 0.5) sids = argsort(phase) phase = phase[sids] bp, bf, be = downsample_time(phase, (fo / bl)[sids], 4 / 24 / 60) ax.plot(phase, (fo / bl)[sids], 'k.', alpha=0.2) ax.errorbar(bp, bf, be, fmt='ko') ax.plot(phase, fm[sids], 'k') setp(ax, ylim=ylim, xlabel='Time', ylabel='Normalised flux') return fig
def __call__(self): self.logger = getLogger( f"{self.name}:{self.ts.name.lower().replace('_','-')}") self.logger.info( "Testing for a significant non-transit-like periodic variability") self.time_before = self.ts.time self.flux_before = self.ts.flux self.time_after = self.ts.time phase = fold(self.time_before, self.ts.ls.period) bp, bf = running_median(phase, self.flux_before, 0.05, 100) pv = interp1d(bp, bf)(phase) df = abs(diff(pv) / diff(self.time_before)) ps = percentile(df, [80, 99.5]) pv_amplitude = bf.ptp() bfn = -bf bfn -= bfn.min() - 1e-12 bfn /= bfn.sum() pv_entropy = -(bfn * log(bfn)).sum() max_slope = df.max() slope_percentile_ratio = ps[0] / ps[1] self._bp = bp self._bf = bf self.phase = phase * self.ts.ls.period self.model = pv self.spr = slope_percentile_ratio self.amplitude = pv_amplitude self.entropy = pv_entropy self.max_slope = max_slope self.peak_siginifance = dip_significance(24 * self.ts.ls.period * bp, bf) self.is_sigificant = pv_amplitude > 0.001 self.is_transit_like = (slope_percentile_ratio < 0.1 or self.peak_siginifance > 3.0) if self.is_sigificant and not self.is_transit_like: self.logger.info("Found and removed a periodic signal") self.model_removed = True flux = self.ts.flux - self.model + 1 self.flux_after = flux self.ts.update_data('pvarstep', self.ts.time, flux, self.ts.ferr) else: self.flux_after = self.ts.flux self.model_removed = False if self.is_sigificant: self.logger.info( "Found a periodic signal but it's too transit-like to be removed" ) else: self.logger.info( "Didn't find any significant periodic signals")
def plot_folded_tess_transit(self, method: str = 'de', pv: ndarray = None, binwidth: float = 1, plot_model: bool = True, plot_unbinned: bool = True, plot_binned: bool = True, xlim: tuple = None, ylim: tuple = None, ax=None, figsize: tuple = None): assert method in ('de', 'mc') if pv is None: if method == 'de': pv = self.de.minimum_location else: df = self.posterior_samples(derived_parameters=False) pv = df.median().values if ax is None: fig, ax = subplots(figsize=figsize) else: fig, ax = None, ax ax.autoscale(enable=True, axis='x', tight=True) etess = self._ntess t = self.timea[:etess] fo = self.ofluxa[:etess] fm = squeeze(self.transit_model(pv))[:etess] bl = squeeze(self.baseline(pv))[:etess] phase = 24 * pv[1] * (fold(t, pv[1], pv[0], 0.5) - 0.5) sids = argsort(phase) phase = phase[sids] bp, bf, be = downsample_time(phase, (fo / bl)[sids], binwidth / 60) if plot_unbinned: ax.plot(phase, (fo / bl)[sids], 'k.', alpha=0.1, ms=2) if plot_binned: ax.errorbar(bp, bf, be, fmt='ko', ms=3) if plot_model: ax.plot(phase, fm[sids], 'k') setp(ax, ylim=ylim, xlim=xlim, xlabel='Time - T$_c$ [h]', ylabel='Normalised flux') if fig is not None: fig.tight_layout() return fig
def __init__(self, name: str): times, fluxes, _, _, _, _ = read_tess(tess_file, zero_epoch, period, use_pdc=True, baseline_duration_d=period) phase, time, flux = [], [], [] for t, f in zip(times, fluxes): ph = (fold(t, period.n, zero_epoch.n, 0.5) - 0.5) * period.n mask = abs(ph) > 0.03 phase.append(ph[mask]) time.append(t[mask]) flux.append(f[mask]) super().__init__(name, ['TESS'], time, flux, wnids=arange(len(flux)), lnlikelihood='celerite')
def plot_folded_tess_transit(self, solution: str = 'de', pv: ndarray = None, binwidth: float = 1, plot_model: bool = True, plot_unbinned: bool = True, plot_binned: bool = True, xlim: tuple = None, ylim: tuple = None, ax=None, figsize: tuple = None): if pv is None: if solution.lower() == 'local': pv = self._local_minimization.x elif solution.lower() in ('de', 'global'): pv = self.de.minimum_location elif solution.lower() in ('mcmc', 'mc'): pv = self.posterior_samples().median().values else: raise NotImplementedError("'solution' should be either 'local', 'global', or 'mcmc'") if ax is None: fig, ax = subplots(figsize=figsize) else: fig, ax = None, ax ax.autoscale(enable=True, axis='x', tight=True) etess = self._ntess t = self.timea[:etess] fo = self.ofluxa[:etess] fm = squeeze(self.transit_model(pv))[:etess] bl = squeeze(self.baseline(pv))[:etess] phase = 24 * pv[1] * (fold(t, pv[1], pv[0], 0.5) - 0.5) sids = argsort(phase) phase = phase[sids] bp, bf, be = downsample_time(phase, (fo / bl)[sids], binwidth / 60) if plot_unbinned: ax.plot(phase, (fo / bl)[sids], 'k.', alpha=0.1, ms=2) if plot_binned: ax.errorbar(bp, bf, be, fmt='ko', ms=3) if plot_model: ax.plot(phase, fm[sids], 'k') setp(ax, ylim=ylim, xlim=xlim, xlabel='Time - T$_c$ [h]', ylabel='Normalised flux') if fig is not None: fig.tight_layout() return fig
def plot_rv_vs_phase(self, planet: int, method='de', pv=None, nsamples: int = 200, ntimes: int = 500, axs=None): if axs is None: fig, axs = subplots(2, 1, gridspec_kw={'height_ratios': (3, 1)}, sharex='all') else: fig, axs = None, axs if pv is None: if method == 'de': if self.lpf.de is None: raise ValueError( "The global optimizer hasn't been initialized.") pvp = None pv = self.lpf.de.minimum_location elif method == 'mcmc': if self.lpf.sampler is None: raise ValueError("The sampler hasn't been initialized.") df = self.lpf.posterior_samples() pvp = permutation(df.values)[:nsamples, :] pv = median(pvp, 0) else: if pv.ndim == 1: pvp = None pv = pv else: pvp = permutation(pv)[:nsamples, :] pv = median(pvp, 0) rv_time = linspace(self._timea.min() - 1, self._timea.max() + 1, num=ntimes) all_planets = set(range(self.nplanets)) other_planets = all_planets.difference([planet]) if pvp is None: rv_model = self.rv_model(pv, rv_time + self._tref, [planet], add_sv=False) rv_others = self.rv_model(pv, planets=other_planets, add_sv=False) rv_model_limits = None else: rv_percentiles = percentile( self.rv_model(pvp, rv_time + self._tref, [planet], add_sv=False), [50, 16, 84, 2.5, 97.5], 0) rv_model = rv_percentiles[0] rv_model_limits = rv_percentiles[1:] rv_others = median( self.rv_model(pvp, planets=other_planets, add_sv=False), 0) period = pv[self.ps.names.index(f'p_{planet + 1}')] tc = pv[self.ps.names.index(f'tc_{planet + 1}')] - self._tref phase = (fold(self._timea, period, tc, 0.5) - 0.5) * period phase_model = (fold(rv_time, period, tc, 0.5) - 0.5) * period msids = argsort(phase_model) if pvp is not None: axs[0].fill_between(phase_model[msids], rv_model_limits[2, msids], rv_model_limits[3, msids], facecolor='blue', alpha=0.15) axs[0].fill_between(phase_model[msids], rv_model_limits[0, msids], rv_model_limits[1, msids], facecolor='darkblue', alpha=0.25) axs[0].errorbar(phase, self._rva - rv_others - self.rv_shifts(pv), self._rvea, fmt='ok') axs[0].plot(phase_model[msids], rv_model[msids], 'k') axs[1].errorbar(phase, self._rva - self.rv_model(pv), self._rvea, fmt='ok') setp(axs[0], ylabel='RV [m/s]') setp(axs[1], xlabel='Phase [d]', ylabel='O-M [m/s]') axs[0].autoscale(axis='x', tight=True) if fig is not None: fig.tight_layout() return fig
def phase(self) -> Optional[ndarray]: if self.zero_epoch is not None: return fold(self.time, self.period, self.zero_epoch, 0.5) * self.period else: return None
def __call__(self, npop: int = 40, de_niter: int = 1000, mcmc_niter: int = 200, mcmc_repeats: int = 3, initialize_only: bool = False): self.logger = getLogger(f"{self.name}:{self.ts.name.lower().replace('_','-')}") self.logger.info(f"Fitting {self.mode} transits") self.ts.transit_fits[self.mode] = self epochs = epoch(self.ts.time, self.ts.zero_epoch, self.ts.period) if self.mode == 'all': mask = ones(self.ts.time.size, bool) elif self.mode == 'even': mask = epochs % 2 == 0 elif self.mode == 'odd': mask = epochs % 2 == 1 else: raise NotImplementedError mask &= abs(self.ts.phase - 0.5*self.ts.period) < 4 * 0.5 * self.ts.duration self.ts.transit_fit_masks[self.mode] = self.mask = mask self.epochs = epochs = epochs[mask] self.time = self.ts.time[mask] self.fobs = self.ts.flux[mask] tref = floor(self.time.min()) tm = QuadraticModelCL(klims=(0.01, 0.60)) if self.use_opencl else QuadraticModel(interpolate=False) self.lpf = lpf = SearchLPF(times=self.time, fluxes=self.fobs, epochs=epochs, tm=tm, nsamples=self.nsamples, exptimes=self.exptime, tref=tref) # TODO: V-shaped transits are not always modelled well. Need to set smarter priors (or starting population) # for the impact parameter and stellar density. lpf.set_prior('rho', 'UP', 0.01, 25) if self.mode == 'all': d = min(self.ts.depth, 0.75) lpf.set_prior('tc', 'NP', self.ts.zero_epoch, 0.01) lpf.set_prior('p', 'NP', self.ts.period, 0.001) lpf.set_prior('k2', 'UP', max(0.01**2, 0.5*d), min(max(0.08**2, 4*d), 0.75**2)) else: pr = self.ts.tf_all.parameters lpf.set_prior('tc', 'NP', pr.tc.med, 5*pr.tc.err) lpf.set_prior('p', 'NP', pr.p.med, pr.p.err) lpf.set_prior('k2', 'UP', max(0.01**2, 0.5 * pr.k2.med), max(0.08**2, min(0.6**2, 2 * pr.k2.med))) lpf.set_prior('q1', 'NP', pr.q1.med, pr.q1.err) lpf.set_prior('q2', 'NP', pr.q2.med, pr.q2.err) # TODO: The limb darkening table has been computed for TESS. Needs to be made flexible. if self.ts.teff is not None: ldcs = Table.read(Path(__file__).parent / "data/ldc_table.fits").to_pandas() ip = interp1d(ldcs.teff, ldcs[['q1', 'q2']].T) q1, q2 = ip(clip(self.ts.teff, 2000., 12000.)) lpf.set_prior('q1', 'NP', q1, 1e-5) lpf.set_prior('q2', 'NP', q2, 1e-5) if initialize_only: return else: lpf.optimize_global(niter=de_niter, npop=npop, use_tqdm=self.use_tqdm, plot_convergence=False) lpf.sample_mcmc(mcmc_niter, repeats=mcmc_repeats, use_tqdm=self.use_tqdm, leave=False) df = lpf.posterior_samples(derived_parameters=True) df = pd.DataFrame((df.median(), df.std()), index='med err'.split()) pv = lpf.posterior_samples(derived_parameters=False).median().values self.phase = fold(self.time, pv[1], pv[0], 0.5) * pv[1] - 0.5 * pv[1] self.fmod = lpf.flux_model(pv) self.ftra = lpf.transit_model(pv) self.fbase = lpf.baseline(pv) # Calculate the per-orbit log likelihood differences # -------------------------------------------------- ues = unique(epochs) lnl = zeros(ues.size) err = 10 ** pv[7] def lnlike_normal(o, m, e): npt = o.size return -npt * log(e) - 0.5 * npt * log(2. * pi) - 0.5 * sum((o - m) ** 2 / e ** 2) for i, e in enumerate(ues): m = epochs == e lnl[i] = lnlike_normal(self.fobs[m], self.fmod[m], err) - lnlike_normal(self.fobs[m], 1.0, err) self.parameters = df self.dll_epochs = ues self.dll_values = lnl self.zero_epoch = df.tc.med self.period = df.p.med self.duration = df.t14.med self.depth = df.k2.med if self.mode == 'all': self.delta_bic = self.ts.dbic = delta_bic(lnl.sum(), 0, 9, self.time.size) self.ts.update_ephemeris(self.zero_epoch, self.period, self.duration, self.depth)
def plot_folded_planets(self, passband: str, method: str = 'de', bwidth: float = 10, axs=None, pv: ndarray = None, nsamples: int = 100, limp=(2.5, 97.5, 16, 84), limc: str = 'darkblue', lima: float = 0.15, ylines=None): from pytransit.lpf.tesslpf import downsample_time if axs is None: fig, axs = subplots(1, self.nplanets, sharey='all') else: fig, axs = None, axs if pv is None: if method == 'de': if self.de is None: raise ValueError( "The global optimizer hasn't been initialized.") pvp = None pv = self.de.minimum_location elif method == 'mcmc': if self.sampler is None: raise ValueError("The sampler hasn't been initialized.") df = self.posterior_samples(derived_parameters=False) pvp = permutation(df.values)[:nsamples, :] pv = median(pvp, 0) else: if pv.ndim == 1: pvp = None pv = pv else: pvp = permutation(pv)[:nsamples, :] pv = median(pvp, 0) is_pb = self.pbids == self.passbands.index(passband) pbmask = zeros(self.timea.size, 'bool') for sl, cpb in zip(self.lcslices, is_pb): if cpb: pbmask[sl] = 1 tcids = [ self.ps.names.index(f'tc_{i + 1}') for i in range(self.nplanets) ] prids = [ self.ps.names.index(f'p_{i + 1}') for i in range(self.nplanets) ] t0s = pv[tcids] prs = pv[prids] for ipl in range(self.nplanets): planets = set(arange(self.nplanets)) planets.remove(ipl) if pvp is None: mflux = squeeze(self.transit_model(pv, planets=[ipl]))[pbmask] rflux = squeeze(self.transit_model(pv, planets=planets))[pbmask] mflim = None fbline = self.baseline(pv)[pbmask] else: mfluxes = self.transit_model(pvp, planets=[ipl])[:, pbmask] rfluxes = self.transit_model(pvp, planets=planets)[:, pbmask] fblines = self.baseline(pvp)[:, pbmask] mflux = median(mfluxes, 0) mflim = percentile(mfluxes, limp, 0) rflux = median(rfluxes, 0) fbline = median(fblines, 0) oflux = self.ofluxa[pbmask] / rflux / fbline phase = (fold(self.timea[pbmask], prs[ipl], t0s[ipl], 0.5) - 0.5) * prs[ipl] m = abs(phase) < 0.5 * self.bldur sids = argsort(phase[m]) if m.sum() > 0: phase, mflux, oflux = phase[m][sids], mflux[m][sids], oflux[m][ sids] bp, bf, be = downsample_time(phase, oflux, bwidth / 24 / 60) if mflim is not None: for il in range(mflim.shape[0] // 2): axs[ipl].fill_between(phase, mflim[2 * il, m][sids], mflim[2 * il + 1, m][sids], fc=limc, alpha=lima) axs[ipl].errorbar(bp, bf, be, fmt='ok') axs[ipl].plot(phase, oflux, '.', alpha=0.25) axs[ipl].plot(phase, mflux, 'k') if ylines is not None: axs[ipl].fill_between(phase, mflux, 1, fc='w', zorder=-99) for yl in ylines: axs[ipl].axhline(yl, lw=1, ls='--', c='k', alpha=0.5, zorder=-100) setp(axs, xlim=(-0.5 * self.bldur, 0.5 * self.bldur), ylim=(0.996, 1.004)) if fig is not None: fig.tight_layout() return fig
def plot_gb_transits(self, method='de', pv: ndarray = None, remove_baseline: bool = True, figsize: tuple = (14, 2), axes=None, ncol: int = 4, xlim: tuple = None, ylim: tuple = None, nsamples: int = 200): if pv is None: if method == 'de': if self.de is None: raise ValueError( "The global optimizer hasn't been initialized.") pvp = None pv = self.de.minimum_location elif method == 'mcmc': if self.sampler is None: raise ValueError("The sampler hasn't been initialized.") df = self.posterior_samples(derived_parameters=False) pvp = permutation(df.values)[:nsamples, :] pv = median(pvp, 0) else: if pv.ndim == 1: pvp = None pv = pv else: pvp = permutation(pv)[:nsamples, :] pv = median(pvp, 0) if pvp is None: if remove_baseline: fobs = self.ofluxa / squeeze(self.baseline(pv)) fmodel = squeeze(self.transit_model(pv)) fbasel = ones_like(self.ofluxa) else: fobs = self.ofluxa fmodel = squeeze(self.flux_model(pv)) fbasel = squeeze(self.baseline(pv)) fmodel_limits = None else: if remove_baseline: fobs = self.ofluxa / squeeze(self.baseline(pv)) fmodels = percentile(self.transit_model(pvp), [50, 16, 84, 2.5, 97.5], 0) fbasel = ones_like(self.ofluxa) else: fobs = self.ofluxa fmodels = percentile(self.flux_model(pvp), [50, 16, 84, 2.5, 97.5], 0) fbasel = median(self.baseline(pvp), 0) fmodel = fmodels[0] fmodel_limits = fmodels[1:] tcids = [ self.ps.names.index(f'tc_{i + 1}') for i in range(self.nplanets) ] prids = [ self.ps.names.index(f'p_{i + 1}') for i in range(self.nplanets) ] t0s = pv[tcids] prs = pv[prids] tcs = array([t.mean() for t in self.times[self._stess:]]) tds = array([ abs(fold(tcs, prs[i], t0s[i], 0.5) - 0.5) for i in range(self.nplanets) ]) pids = argmin(tds, 0) nlc = self.nlc - self._stess nrow = int(ceil(nlc / ncol)) if axes is None: fig, axs = subplots(nrow, ncol, figsize=figsize, sharex='all', sharey='all', squeeze=False) else: fig, axs = None, axes [ax.autoscale(enable=True, axis='x', tight=True) for ax in axs.flat] etess = self._stess for iax, i in enumerate(range(self.nlc - etess)): ax = axs.flat[iax] sl = self.lcslices[etess + i] t = self.times[etess + i] e = epoch(t.mean(), t0s[pids[i]], prs[pids[i]]) tc = t0s[pids[i]] + e * prs[pids[i]] tt = 24 * (t - tc) if fmodel_limits is not None: ax.fill_between(tt, fmodel_limits[2, sl], fmodel_limits[3, sl], facecolor='blue', alpha=0.15) ax.fill_between(tt, fmodel_limits[0, sl], fmodel_limits[1, sl], facecolor='darkblue', alpha=0.25) ax.plot(tt, fobs[sl], 'k.', alpha=0.2) ax.plot(tt, fmodel[sl], 'k') setp(axs, xlim=xlim, ylim=ylim) setp(axs[-1, :], xlabel='Time - T$_c$ [h]') setp(axs[:, 0], ylabel='Normalised flux') fig.tight_layout() return fig