def __init__(self, x, y, n_bg_coef, wave_err=0.05, # MAGIC NUMBER: wavelength error hack log_sigma0=0., log_rho0=np.log(10.), # initial params for GP x_shift=None): self.x = np.array(x) self.y = np.array(y) if n_bg_coef >= 2: a_kw = dict([('a{}'.format(i),0.) for i in range(2,n_bg_coef)]) # estimate background a_kw['a1'] = (y[-1]-y[0])/(x[-1]-x[0]) # slope a_kw['a0'] = y[-1] - a_kw['a1']*x[-1] # estimate constant term else: a_kw = dict(a0=np.mean([y[0], y[-1]])) # initialize model self.mean_model = MeanModel(n_bg_coef=n_bg_coef, **a_kw) self.kernel = terms.Matern32Term(log_sigma=log_sigma0, log_rho=log_rho0) # set up the gp self.gp = GP(self.kernel, mean=self.mean_model, fit_mean=True) self.gp.compute(x, yerr=wave_err) logger.debug("Initial log-likelihood: {0}" .format(self.gp.log_likelihood(y))) if x_shift is None: self.x_shift = 0. else: self.x_shift = x_shift
def Pred1_2D(par, x2d, y2d, y2derr, doPlot=True, x2dpred=None): K = x2d.shape[0] k = terms.Matern32Term(log_sigma=par[-2], log_rho=par[-1]) gp = GP(k, mean=1.0) shifts = np.append(0, par[:K - 1]) x1d = (x2d + shifts[:, None]).flatten() inds = np.argsort(x1d) y1d = y2d.flatten() y1derr = y2derr.flatten() gp.compute(x1d[inds], yerr=y1derr[inds]) if x2dpred is None: x2dpred = np.copy(x2d) x2dpreds = (x2dpred + shifts[:, None]) y2dpred = np.zeros_like(x2dpred) y2dprederr = np.zeros_like(x2dpred) for k in range(K): print 'Prediction for spectrum %d' % (k + 1) x1dpred = x2dpreds[k, :].flatten() indspred = np.argsort(x1dpred) mu, var = gp.predict(y1d[inds], x1dpred[indspred], return_var=True) std = np.sqrt(var) y2dpred[k, indspred] = mu y2dprederr[k, indspred] = std if doPlot: for i in range(K): plt.errorbar(x2d[i, :], y2d[i, :] - i, yerr=y2derr[i, :], fmt=".k", capsize=0, alpha=0.5) plt.plot(x2dpred[i, :], y2dpred[i, :] - i, 'C0') plt.fill_between(x2dpred[i,:], y2dpred[i,:] + y2dprederr[i,:] - i, \ y2dpred[i,:] - y2dprederr[i,:] - i, color = 'C0', alpha = 0.4, lw = 0) return x2dpred, y2dpred, y2dprederr
def __call__(self, rho: Optional[float] = 2., niter: int = 5): logger.info("Running Celerite Matern 3/2 detrending") time = self.ts.time.copy() flux = self.ts.flux.copy() mask = ones(time.size, bool) for i in range(niter): time_learn = time[mask] flux_learn = flux[mask] wn = mad_std(diff(flux_learn)) / sqrt(2) log_sigma = log(mad_std(flux_learn)) log_rho = log(rho) kernel = Matern32Term(log_sigma, log_rho) gp = GP(kernel, mean=1.0) gp.compute(time_learn, yerr=wn) self.prediction = gp.predict(flux_learn, time, return_cov=False) mask &= ~sigma_clip(flux - self.prediction, sigma=3).mask residuals = flux - self.prediction self.mask = m = ~(sigma_clip(residuals, sigma_lower=inf, sigma_upper=4).mask) self.ts._data.update('celerite_m32', time[m], (flux - self.prediction + 1)[m], self.ts.ferr[m])
def __init__(self, lpf, name: str = 'gp', noise_ids=None, fixed_hps=None): if not with_celerite: raise ImportError("CeleriteLogLikelihood requires celerite.") self.name = name self.lpf = lpf if fixed_hps is None: self.free = True else: self.hps = asarray(fixed_hps) self.free = False if lpf.lcids is None: raise ValueError('The LPF data needs to be initialised before initialising CeleriteLogLikelihood.') self.noise_ids = noise_ids if noise_ids is not None else unique(lpf.noise_ids) self.mask = m = zeros(lpf.lcids.size, bool) for lcid,nid in enumerate(lpf.noise_ids): if nid in self.noise_ids: m[lcid == lpf.lcids] = 1 if m.sum() == lpf.lcids.size: self.times = lpf.timea self.fluxes = lpf.ofluxa else: self.times = lpf.timea[m] self.fluxes = lpf.ofluxa[m] self.gp = GP(Matern32Term(0, 0)) if self.free: self.init_parameters() else: self.compute_gp(None, force=True, hps=self.hps)
def init_gp(self, log_sigma=None, log_rho=None, amp=None, x0=None, bg=None, bg_buffer=None, **kwargs): """ **kwargs: """ # Call the different get_init methods with the correct kwargs passed sig = inspect.signature(self.get_init) kw = OrderedDict() for k in list(sig.parameters.keys()): kw[k] = kwargs.pop(k, None) p0 = self.get_init(**kw) # Call the generic init method - all line models must have these params p0_generic = self.get_init_generic(amp=amp, x0=x0, bg=bg, bg_buffer=bg_buffer) for k, v in p0_generic.items(): p0[k] = v # expand bg parameters bgs = p0.pop('bg_coef') for i in range(self.n_bg_coef): p0['bg{}'.format(i)] = bgs[i] # transform p0 = self.transform_pars(p0) # initialize model mean_model = self.MeanModel(n_bg_coef=self.n_bg_coef, absorp_emiss=self.absorp_emiss, **p0) p0_gp = self.get_init_gp(log_sigma=log_sigma, log_rho=log_rho) kernel = terms.Matern32Term(log_sigma=p0_gp['log_sigma'], log_rho=p0_gp['log_rho']) # set up the gp model self.gp = GP(kernel, mean=mean_model, fit_mean=True) if self._err is not None: self.gp.compute(self.x, self._err) else: self.gp.compute(self.x) init_params = self.gp.get_parameter_vector() init_ll = self.gp.log_likelihood(self.flux) logger.log(0, "Initial log-likelihood: {0}".format(init_ll)) return init_params
def gpSimFull(carmaTerm, SNR, duration, N, nLC=1): """Simulate full CARMA time series. Args: carmaTerm (object): celerite GP term. SNR (float): Signal to noise ratio defined as ratio between CARMA amplitude and the mode of the errors (simulated using log normal). duration (float): The duration of the simulated time series in days. N (int): The number of data points. nLC (int, optional): Number of light curves to simulate. Defaults to 1. Raises: RuntimeError: If the input CARMA term/model is not stable, thus cannot be solved by celerite. Returns: Arrays: t, y and yerr of the simulated light curves in numpy arrays. Note that errors have been added to y. """ assert isinstance( carmaTerm, celerite.celerite.terms.Term), "carmaTerm must a celerite GP term" if (not isinstance(carmaTerm, DRW_term)) and (carmaTerm._arroots.real > 0).any(): raise RuntimeError( "The covariance matrix of the provided CARMA term is not positive definite!" ) t = np.linspace(0, duration, N) noise = carmaTerm.get_rms_amp() / SNR yerr = np.random.lognormal(0, 0.25, N) * noise yerr = yerr[np.argsort(np.abs(yerr))] # init GP and solve matrix gp_sim = GP(carmaTerm) gp_sim.compute(t) # simulate, assign yerr based on y t = np.repeat(t[None, :], nLC, axis=0) y = gp_sim.sample(size=nLC) # format yerr making it heteroscedastic y_rank = y.argsort(axis=1).argsort(axis=1) yerr = np.repeat(yerr[None, :], nLC, axis=0) yerr = np.array(list(map(lambda x, y: x[y], yerr, y_rank))) yerr_sign = np.random.binomial(1, 0.5, yerr.shape) yerr_sign[yerr_sign < 1] = -1 yerr = yerr * yerr_sign if nLC == 1: return t[0], y[0] + yerr[0], yerr[0] else: return t, y + yerr, yerr
def Fit0_Jitter(x, y, yerr = None, verbose = True, doPlot = False, \ xpred = None): k = terms.Matern32Term(log_sigma=0.0, log_rho=0.0) if yerr is None: wn = np.median(abs(np.diff(y))) else: wn = np.median(yerr) k += terms.JitterTerm(log_sigma=np.log(wn)) gp = GP(k, mean=1.0) gp.compute(x) HP_init = gp.get_parameter_vector() soln = minimize(NLL0, gp.get_parameter_vector(), jac=True, args=(gp, y)) gp.set_parameter_vector(soln.x) if verbose: print 'Initial pars:', HP_init print 'Fitted pars:', soln.x if xpred is None: return soln.x mu, var = gp.predict(y, xpred, return_var=True) std = np.sqrt(var) if doPlot: plt.errorbar(x, y, yerr=yerr, fmt=".k", capsize=0) plt.plot(xpred, mu, 'C0') plt.fill_between(xpred, mu + std, mu - std, color='C0', alpha=0.4, lw=0) return soln.x, mu, std
def drw_fit(t, y, yerr, debug=False, user_bounds=None, n_iter=10): """ Fit time series to DRW. Args: t (array(float)): Time stamps of the input time series (the default unit is day). y (array(float)): y values of the input time series. yerr (array(float)): Measurement errors for y values. debug (bool, optional): Turn on/off debug mode. Defaults to False. user_bounds (list, optional): Parameter boundaries for the optimizer. Defaults to None. n_iter (int, optional): Number of iterations to run the optimizer. Defaults to 10. Raises: celerite.solver.LinAlgError: For non-positive definite autocovariance matrices. Returns: array(float): Best-fit parameters """ best_fit = np.empty(2) std = np.std(y) # init bounds for fitting if user_bounds is not None and (len(user_bounds) == 2): bounds = user_bounds else: bounds = [(-4, np.log(4 * std)), (-4, 10)] # re-position lc t = t - t[0] y = y - np.median(y) # initialize parameter and kernel kernel = DRW_term(*drw_log_param_init(std, max_tau=np.log(t[-1] / 8))) gp = GP(kernel, mean=0) gp.compute(t, yerr) best_fit_return = _min_opt( y, best_fit, gp, lambda: drw_log_param_init(std, size=n_iter, max_tau=np.log(t[-1] / 8) ), "param", debug, bounds, n_iter, ) return best_fit_return
def test_pickle(with_general, seed=42): solver = celerite.CholeskySolver() np.random.seed(seed) t = np.sort(np.random.rand(500)) diag = np.random.uniform(0.1, 0.5, len(t)) y = np.sin(t) if with_general: U = np.vander(t - np.mean(t), 4).T V = U * np.random.rand(4)[:, None] A = np.sum(U * V, axis=0) + 1e-8 else: A = np.empty(0) U = np.empty((0, 0)) V = np.empty((0, 0)) alpha_real = np.array([1.3, 1.5]) beta_real = np.array([0.5, 0.2]) alpha_complex_real = np.array([1.0]) alpha_complex_imag = np.array([0.1]) beta_complex_real = np.array([1.0]) beta_complex_imag = np.array([1.0]) def compare(solver1, solver2): assert solver1.computed() == solver2.computed() if not solver1.computed(): return assert np.allclose(solver1.log_determinant(), solver2.log_determinant()) assert np.allclose(solver1.dot_solve(y), solver2.dot_solve(y)) s = pickle.dumps(solver, -1) solver2 = pickle.loads(s) compare(solver, solver2) solver.compute(0.0, alpha_real, beta_real, alpha_complex_real, alpha_complex_imag, beta_complex_real, beta_complex_imag, A, U, V, t, diag) solver2 = pickle.loads(pickle.dumps(solver, -1)) compare(solver, solver2) # Test that models can be pickled too. kernel = terms.RealTerm(0.5, 0.1) kernel += terms.ComplexTerm(0.6, 0.7, 1.0) gp1 = GP(kernel) gp1.compute(t, diag) s = pickle.dumps(gp1, -1) gp2 = pickle.loads(s) assert np.allclose(gp1.log_likelihood(y), gp2.log_likelihood(y))
def __init__(self, lpf, name: str = 'gp', lcids=None, fixed_hps=None): if not with_celerite: raise ImportError("MultiCeleriteLogLikelihood requires celerite.") self.name = name self.lpf = lpf if fixed_hps is None: self.free = True else: self.hps = asarray(fixed_hps) self.free = False if lpf.lcids is None: raise ValueError( 'The LPF data needs to be initialised before initialising CeleriteLogLikelihood.' ) self.lcids = lcids if lcids is not None else unique(lpf.lcids) self.lcslices = [lpf.lcslices[i] for i in self.lcids] self.nlc = len(self.lcslices) self.times, self.fluxes, self.wns = [], [], [] for i in self.lcids: self.times.append(lpf.times[i]) self.fluxes.append(lpf.fluxes[i]) self.wns.append(diff(lpf.fluxes[i]).std() / sqrt(2)) self.gps = [GP(Matern32Term(0, 0)) for i in range(self.nlc)] if self.free: self.init_parameters() else: self.compute_gp(None, force=True, hps=self.hps)
def Fit0(x, y, yerr, verbose = True, doPlot = False, \ xpred = None, HP_init = None): if HP_init is None: HP_init = np.zeros(2) k = terms.Matern32Term(log_sigma=HP_init[0], log_rho=HP_init[1]) gp = GP(k, mean=1.0) gp.compute(x, yerr=yerr) soln = minimize(NLL0, HP_init, jac=True, args=(gp, y)) gp.set_parameter_vector(soln.x) if verbose: print 'Initial pars:', HP_init print 'Fitted pars:', soln.x if xpred is None: return soln.x mu, var = gp.predict(y, xpred, return_var=True) std = np.sqrt(var) if doPlot: plt.errorbar(x, y, yerr=yerr, fmt=".k", capsize=0) plt.plot(xpred, mu, 'C0') plt.fill_between(xpred, mu + std, mu - std, color='C0', alpha=0.4, lw=0) return soln.x, mu, std
def test_build_gp(method, seed=42): kernel = terms.RealTerm(0.5, 0.1) kernel += terms.ComplexTerm(0.6, 0.7, 1.0) gp = GP(kernel, method=method) assert gp.vector_size == 5 p = gp.get_parameter_vector() assert np.allclose(p, [0.5, 0.1, 0.6, 0.7, 1.0]) gp.set_parameter_vector([0.5, 0.8, 0.6, 0.7, 2.0]) p = gp.get_parameter_vector() assert np.allclose(p, [0.5, 0.8, 0.6, 0.7, 2.0]) with pytest.raises(ValueError): gp.set_parameter_vector([0.5, 0.8, -0.6]) with pytest.raises(ValueError): gp.set_parameter_vector("face1")
def test_pickle(method, seed=42): solver = get_solver(method) np.random.seed(seed) t = np.sort(np.random.rand(500)) diag = np.random.uniform(0.1, 0.5, len(t)) y = np.sin(t) alpha_real = np.array([1.3, 1.5]) beta_real = np.array([0.5, 0.2]) alpha_complex_real = np.array([1.0]) alpha_complex_imag = np.array([0.1]) beta_complex_real = np.array([1.0]) beta_complex_imag = np.array([1.0]) def compare(solver1, solver2): assert solver1.computed() == solver2.computed() if not solver1.computed(): return assert np.allclose(solver1.log_determinant(), solver2.log_determinant()) assert np.allclose(solver1.dot_solve(y), solver2.dot_solve(y)) s = pickle.dumps(solver, -1) solver2 = pickle.loads(s) compare(solver, solver2) if method != "sparse": solver.compute( alpha_real, beta_real, alpha_complex_real, alpha_complex_imag, beta_complex_real, beta_complex_imag, t, diag ) solver2 = pickle.loads(pickle.dumps(solver, -1)) compare(solver, solver2) # Test that models can be pickled too. kernel = terms.RealTerm(0.5, 0.1) kernel += terms.ComplexTerm(0.6, 0.7, 1.0) gp1 = GP(kernel, method=method) gp1.compute(t, diag) s = pickle.dumps(gp1, -1) gp2 = pickle.loads(s) assert np.allclose(gp1.log_likelihood(y), gp2.log_likelihood(y))
def Fit1(x2d, y2d, y2derr, par_in=None, verbose=True): K = x2d.shape[0] if par_in is None: par_in = np.zeros(K + 1) k = terms.Matern32Term(log_sigma=par_in[-2], log_rho=par_in[-1]) gp = GP(k, mean=1.0) soln = minimize(NLL1, par_in, args=(gp, x2d, y2d, y2derr)) if verbose: print 'Initial pars:', par_in print 'Fitted pars:', soln.x return soln.x
def __init__(self, kernel, t, yerr=None, white_noise=1e-10): """ t : the independent variable where the GP is "trained" (opt) yerr : array of observational uncertainties (opt) white_noise : value/array added to the diagonal of the kernel matrix """ self.kernel = kernel self.t = t self.yerr = yerr self.white_noise = white_noise self._GP = GPcel(self.kernel) self._GP.compute(self.t, self.yerr)
def test_nyquist_singularity(method, seed=4220): np.random.seed(seed) kernel = terms.ComplexTerm(1.0, np.log(1e-6), np.log(1.0)) gp = GP(kernel, method=method) # Samples are very close to Nyquist with f = 1.0 ts = np.array([0.0, 0.5, 1.0, 1.5]) ts[1] = ts[1] + 1e-9 * np.random.randn() ts[2] = ts[2] + 1e-8 * np.random.randn() ts[3] = ts[3] + 1e-7 * np.random.randn() yerr = np.random.uniform(low=0.1, high=0.2, size=len(ts)) y = np.random.randn(len(ts)) gp.compute(ts, yerr) llgp = gp.log_likelihood(y) K = gp.get_matrix(ts) K[np.diag_indices_from(K)] += yerr**2.0 ll = (-0.5 * np.dot(y, np.linalg.solve(K, y)) - 0.5 * np.linalg.slogdet(K)[1] - 0.5 * len(y) * np.log(2.0 * np.pi)) assert np.allclose(ll, llgp)
def learn_gps(self): for lcid in tqdm(self.lcids, desc='Learning GPs', leave=False): kernel = Matern32Term(log(self.lpf.fluxes[lcid].std()), log(0.1)) gp = GP(kernel, mean=0.0) gp.freeze_parameter('kernel:log_sigma') def nll(x): gp.set_parameter_vector(x) gp.compute(self.lpf.times[lcid], self.lpf.wn[lcid]) return -gp.log_likelihood(self.lpf.fluxes[lcid] - 1.0) res = minimize(nll, [log(1.)]) gp.set_parameter_vector(res.x) gp.compute(self.lpf.times[lcid], self.lpf.wn[lcid]) self.gps.append(gp)
class GPModel(object): def __init__( self, x, y, n_bg_coef, wave_err=0.05, # MAGIC NUMBER: wavelength error hack log_sigma0=0., log_rho0=np.log(10.), # initial params for GP x_shift=None): self.x = np.array(x) self.y = np.array(y) if n_bg_coef >= 2: a_kw = dict([('a{}'.format(i), 0.) for i in range(2, n_bg_coef)]) # estimate background a_kw['a1'] = (y[-1] - y[0]) / (x[-1] - x[0]) # slope a_kw['a0'] = y[-1] - a_kw['a1'] * x[-1] # estimate constant term else: a_kw = dict(a0=np.mean([y[0], y[-1]])) # initialize model self.mean_model = MeanModel(n_bg_coef=n_bg_coef, **a_kw) self.kernel = terms.Matern32Term(log_sigma=log_sigma0, log_rho=log_rho0) # set up the gp self.gp = GP(self.kernel, mean=self.mean_model, fit_mean=True) self.gp.compute(x, yerr=wave_err) logger.log( 0, "Initial log-likelihood: {0}".format(self.gp.log_likelihood(y))) if x_shift is None: self.x_shift = 0. else: self.x_shift = x_shift def neg_ln_like(self, params): # minimize -log(likelihood) self.gp.set_parameter_vector(params) ll = self.gp.log_likelihood(self.y) if np.isnan(ll): return np.inf return -ll def __call__(self, params): return self.neg_ln_like(params)
def test_predict(method, seed=42): np.random.seed(seed) x = np.sort(np.random.rand(10)) yerr = np.random.uniform(0.1, 0.5, len(x)) y = np.sin(x) kernel = terms.RealTerm(0.1, 0.5) for term in [(0.6, 0.7, 1.0)]: kernel += terms.ComplexTerm(*term) gp = GP(kernel, method=method) gp.compute(x, yerr) mu0, cov0 = gp.predict(y, x) mu, cov = gp.predict(y) assert np.allclose(mu0, mu) assert np.allclose(cov0, cov)
def __call__(self, period: Optional[float] = None): logger.info("Running Celerite") assert hasattr( self.ts, 'ls'), "Celerite step requires a prior Lomb-Scargle step" self.period = period if period is not None else self.ts.ls.period gp = GP(SHOTerm(log(self.ts.flux.var()), log(10), log(2 * pi / self.period)), mean=1.) gp.freeze_parameter('kernel:log_omega0') gp.compute(self.ts.time, yerr=self.ts.ferr) def minfun(pv): gp.set_parameter_vector(pv) gp.compute(self.ts.time, yerr=self.ts.ferr) return -gp.log_likelihood(self.ts.flux) res = minimize(minfun, gp.get_parameter_vector(), jac=False, method='powell') self.result = res self.parameters = res.x self.prediction = gp.predict(self.ts.flux, return_cov=False)
def carma_fit(t, y, yerr, p, q, de=True, debug=False, mode="coeff", user_bounds=None, n_iter=10): """Fit time series to any CARMA model. Args: t (object): An array of time stamps in days. y (object): An array of y values. yerr (object): An array of the errors in y values. p (int): P order of a CARMA(p, q) model. q (int): Q order of a CARMA(p, q) model. de (bool, optional): Whether to use differential_evolution as the optimizer. Defaults to True. debug (bool, optional): Turn on/off debug mode. Defaults to False. mode (str, optional): Specify which space to sample, 'param' or 'coeff'. Defaults to 'coeff'. user_bounds (list, optional): Factorized polynomial coefficient boundaries for the optimizer. Defaults to None. n_iter (int, optional): Number of iterations to run the optimizer if de==False. Defaults to 10. Raises: celerite.solver.LinAlgError: For non-positive definite matrices. Returns: object: An array of best-fit CARMA parameters """ dim = int(p + q + 1) best_fit = np.empty(dim) # init bounds for fitting if user_bounds is not None and (len(user_bounds) == dim): bounds = user_bounds elif p == 2 and q == 1: bounds = [(-10, 13), (-14, 7), (-10, 6), (-12, 3)] elif p == 2 and q == 0: bounds = [(-10, 16), (-14, 16), (-13, 15)] else: ARbounds = [(-6, 3)] * p MAbounds = [(-6, 1)] * (q + 1) bounds = ARbounds + MAbounds # re-position lc t = t - t[0] y = y - np.median(y) # initialize parameter and kernel ARpars, MApars = sample_carma(p, q) kernel = CARMA_term(np.log(ARpars), np.log(MApars)) gp = GP(kernel, mean=np.median(y)) gp.compute(t, yerr) if p > 2: mode = "coeff" if mode == "coeff": init_func = lambda: carma_log_fcoeff_init(dim) else: init_func = lambda: carma_log_param_init(dim) if de: best_fit_return = _de_opt( y, best_fit, gp, init_func, mode, debug, bounds, ) else: best_fit_return = _min_opt( y, best_fit, gp, init_func, mode, debug, bounds, n_iter, ) return best_fit_return
def gpSimFull(carmaTerm, SNR, duration, N, nLC=1, log_flux=True): """ Simulate CARMA time series using uniform sampling. Args: carmaTerm (object): An EzTao CARMA kernel. SNR (float): Signal-to-noise defined as ratio between CARMA RMS amplitude and the median of the measurement errors (simulated using log normal). duration (float): The duration of the simulated time series (default in days). N (int): The number of data points in the simulated time series. nLC (int, optional): Number of time series to simulate. Defaults to 1. log_flux (bool): Whether the flux/y values are in astronomical magnitude. This argument affects how errors are assigned. Defaults to True. Raises: RuntimeError: If the input CARMA term/model is not stable, thus cannot be solved by celerite. Returns: (array(float), array(float), array(float)): Time stamps (default in day), y values and measurement errors of the simulated time series. """ assert isinstance( carmaTerm, celerite.celerite.terms.Term), "carmaTerm must a celerite GP term" if (not isinstance(carmaTerm, DRW_term)) and (carmaTerm._arroots.real > 0).any(): raise RuntimeError( "The covariance matrix of the provided CARMA term is not positive definite!" ) t = np.linspace(0, duration, N) noise = carmaTerm.get_rms_amp() / SNR yerr = np.random.lognormal(0, 0.25, N) * noise yerr = yerr[np.argsort(np.abs(yerr))] # small->large # init GP and solve matrix gp_sim = GP(carmaTerm) gp_sim.compute(t) # simulate, assign yerr based on y t = np.repeat(t[None, :], nLC, axis=0) y = gp_sim.sample(size=nLC) # format yerr making it heteroscedastic yerr = np.repeat(yerr[None, :], nLC, axis=0) # if in mag, large value with large error; in flux, the opposite if log_flux: # ascending sort y_rank = y.argsort(axis=1).argsort(axis=1) yerr = np.array(list(map(lambda x, y: x[y], yerr, y_rank))) else: # descending sort y_rank = (-y).argsort(axis=1).argsort(axis=1) yerr = np.array(list(map(lambda x, y: x[y], yerr, y_rank))) if nLC == 1: return t[0], y[0], yerr[0] else: return t, y, yerr
def mcmc(t, y, yerr, p, q, n_walkers=32, burn_in=500, n_samples=2000, init_param=None): """ A simple wrapper to run quick MCMC using emcee. Args: t (array(float)): Time stamps of the input time series (the default unit is day). y (array(float)): y values of the input time series. yerr (array(float)): Measurement errors for y values. p (int): The p order of a CARMA(p, q) model. q (int): The q order of a CARMA(p, q) model. n_walkers (int, optional): Number of MCMC walkers. Defaults to 32. burn_in (int, optional): Number of burn in steps. Defaults to 500. n_samples (int, optional): Number of MCMC steps to run. Defaults to 2000. init_param (array(float), optional): The initial position for the MCMC walker. Defaults to None. Returns: (object, array(float), array(float)): The emcee sampler object. The MCMC flatchain (n_walkers*n_samplers, dim) and chain (n_walkers, n_samplers, dim) in CARMA space if p > 2, otherwise empty. """ assert p > q, "p order must be greater than q order." if init_param is not None and p <= 2: assert len(init_param) == int( p + q + 1 ), "The initial parameters doesn't match the dimension of the CARMA model!" else: print("Searching for best-fit CARMA parameters...") init_param = carma_fit(t, y, yerr, p, q, n_iter=200) # set on param or fcoeff if p > 2: ll = lambda *args: -neg_fcoeff_ll(*args) init_sample = CARMA_term.carma2fcoeffs(np.log(init_param[:p]), np.log(init_param[p:])) else: ll = lambda *args: -neg_param_ll(*args) init_sample = init_param # create vectorized functions vec_fcoeff2carma = np.vectorize( CARMA_term.fcoeffs2carma, excluded=[ 1, ], signature="(n)->(m),(k)", ) # reposition ts t = t - t[0] y = y - np.median(y) # init celerite kernel/GP kernel = CARMA_term(np.log(init_param[:p]), np.log(init_param[p:])) gp = GP(kernel, mean=0) gp.compute(t, yerr) # init sampler ndim = len(init_sample) sampler = emcee.EnsembleSampler(n_walkers, ndim, ll, args=[y, gp]) print("Running burn-in...") p0 = np.log(init_sample) + 1e-8 * np.random.randn(n_walkers, ndim) p0, lp, _ = sampler.run_mcmc(p0, burn_in) print("Running production...") sampler.reset() sampler.run_mcmc(p0, n_samples) carma_flatchain = np.array([]) carma_chain = np.array([]) if p > 2: ar, ma = vec_fcoeff2carma(sampler.flatchain, p) carma = np.log(np.hstack((ar, ma))) carma_flatchain = carma carma_chain = carma.reshape((n_walkers, n_samples, ndim), order="F") return sampler, carma_flatchain, carma_chain
def carma_fit( t, y, yerr, p, q, init_func=None, neg_lp_func=None, optimizer_func=None, n_opt=20, user_bounds=None, scipy_opt_kwargs={}, scipy_opt_options={}, debug=False, ): """ Fit an arbitrary CARMA model The default settings are optimized for normalized LCs. Args: t (array(float)): Time stamps of the input time series (the default unit is day). y (array(float)): y values of the input time series. yerr (array(float)): Measurement errors for y values. p (int): The p order of a CARMA(p, q) model. q (int): The q order of a CARMA(p, q) model. init_func (object, optional): A user-provided function to generate initial guesses for the optimizer. Defaults to None. neg_lp_func (object, optional): A user-provided function to compute negative probability given an array of parameters, an array of time series values and a celerite GP instance. Defaults to None. optimizer_func (object, optional): A user-provided optimizer function. Defaults to None. n_opt (int, optional): Number of optimizers to run. Defaults to 20. user_bounds (array(float), optional): Parameter boundaries for the default optimizer. If p > 2, these are boundaries for the coefficients of the factored polynomial. Defaults to None. scipy_opt_kwargs (dict, optional): Keyword arguments for scipy.optimize.minimize. Defaults to {}. scipy_opt_options (dict, optional): "options" argument for scipy.optimize.minimize. Defaults to {}. debug (bool, optional): Turn on/off debug mode. Defaults to False. Raises: celerite.solver.LinAlgError: For non-positive definite autocovariance matrices. Returns: array(float): Best-fit parameters """ # set core config dim = int(p + q + 1) mode = "fcoeff" if p > 2 else "param" # init bounds for fitting if user_bounds is not None and (len(user_bounds) == dim): bounds = user_bounds else: bounds = [(-15, 15)] * dim bounds[p:-1] = [(a[0] - 5, a[1] - 5) for a in bounds[p:-1]] bounds[-1] = (-15, 5) # re-position lc t = t - t[0] y = y - np.median(y) y_std = mad(y) * 1.4826 y = y / y_std yerr = yerr / y_std # initialize parameter and kernel ARpars, MApars = sample_carma(p, q) kernel = CARMA_term(np.log(ARpars), np.log(MApars)) gp = GP(kernel, mean=0) gp.compute(t, yerr) # determine/set init func if init_func is not None: init = init_func else: init = partial(carma_log_fcoeff_init, p, q) # determine/set negative log probability function if neg_lp_func is None: neg_lp = partial(neg_lp_flat, bounds=np.array(bounds), mode=mode) else: neg_lp = neg_lp_func # determine/set optimizer function if optimizer_func is None: scipy_opt_kwargs.update({"method": "L-BFGS-B", "bounds": bounds}) opt = partial( scipy_opt, mode=mode, opt_kwargs=scipy_opt_kwargs, opt_options=scipy_opt_options, debug=debug, ) else: opt = optimizer_func # get best-fit solution & adjust MA params (multiply by y_std) best_fit_return = opt(y, gp, init, neg_lp, n_opt) best_fit_return[p:] = best_fit_return[p:] * y_std return best_fit_return
def dho_fit( t, y, yerr, init_func=None, neg_lp_func=None, optimizer_func=None, n_opt=20, user_bounds=None, scipy_opt_kwargs={}, scipy_opt_options={}, debug=False, ): """ Fit DHO to time series The default settings are optimized for normalized LCs. Args: t (array(float)): Time stamps of the input time series (the default unit is day). y (array(float)): y values of the input time series. yerr (array(float)): Measurement errors for y values. init_func (object, optional): A user-provided function to generate initial guesses for the optimizer. Defaults to None. neg_lp_func (object, optional): A user-provided function to compute negative probability given an array of parameters, an array of time series values and a celerite GP instance. Defaults to None. optimizer_func (object, optional): A user-provided optimizer function. Defaults to None. n_opt (int, optional): Number of optimizers to run.. Defaults to 20. user_bounds (list, optional): Parameter boundaries for the default optimizer and the default flat prior. Defaults to None. scipy_opt_kwargs (dict, optional): Keyword arguments for scipy.optimize.minimize. Defaults to {}. scipy_opt_options (dict, optional): "options" argument for scipy.optimize.minimize. Defaults to {}. debug (bool, optional): Turn on/off debug mode. Defaults to False. Raises: celerite.solver.LinAlgError: For non-positive definite autocovariance matrices. Returns: array(float): Best-fit DHO parameters """ # determine user defined boundaries if any if user_bounds is not None and (len(user_bounds) == 4): bounds = user_bounds else: bounds = [(-15, 15)] * 4 bounds[2:] = [(a[0] - 8, a[1] - 8) for a in bounds[2:]] # re-position/normalize lc t = t - t[0] y = y - np.median(y) y_std = mad(y) * 1.4826 y = y / y_std yerr = yerr / y_std # determine negative log probability function if neg_lp_func is None: neg_lp = partial(neg_lp_flat, bounds=np.array(bounds), mode="param") else: neg_lp = neg_lp_func # initialize parameter, kernel and GP kernel = DHO_term(*dho_log_param_init()) gp = GP(kernel, mean=0) gp.compute(t, yerr) # determine initialize function if init_func is None: init = partial(dho_log_param_init) else: init = init_func # determine the optimizer function if optimizer_func is None: scipy_opt_kwargs.update({"method": "L-BFGS-B", "bounds": bounds}) opt = partial( scipy_opt, mode="param", opt_kwargs=scipy_opt_kwargs, opt_options=scipy_opt_options, debug=debug, ) else: opt = optimizer_func # get best-fit solution & adjust MA params (multiply by y_std) best_fit_return = opt(y, gp, init, neg_lp, n_opt) best_fit_return[2:] = best_fit_return[2:] * y_std return best_fit_return
def dho_fit(t, y, yerr, de=True, debug=False, user_bounds=None, n_iter=10): """Fix time series to a DHO model. Args: t (object): An array of time stamps in days. y (object): An array of y values. yerr (object): An array of the errors in y values. de (bool, optional): Whether to use differential_evolution as the optimizer. Defaults to True. debug (bool, optional): Turn on/off debug mode. Defaults to False. user_bounds (list, optional): Parameter boundaries for the optimizer. Defaults to None. n_iter (int, optional): Number of iterations to run the optimizer if de==False. Defaults to 10. Raises: celerite.solver.LinAlgError: For non-positive definite matrices. Returns: object: An array of best-fit parameters """ best_fit = np.zeros(4) if user_bounds is not None and (len(user_bounds) == 4): bounds = user_bounds else: bounds = [(-10, 13), (-14, 7), (-10, 6), (-12, 3)] # re-position lc t = t - t[0] y = y - np.median(y) # initialize parameter, kernel and GP kernel = DHO_term(*dho_log_param_init()) gp = GP(kernel, mean=np.mean(y)) gp.compute(t, yerr) if de: best_fit_return = _de_opt( y, best_fit, gp, lambda: dho_log_param_init(), "param", debug, bounds, ) else: best_fit_return = _min_opt( y, best_fit, gp, lambda: dho_log_param_init(), "param", debug, bounds, n_iter, ) return best_fit_return
plot_setup.setup() # Set up the dimensions of the problem N = 2**np.arange(6, 20) times = np.empty((len(N), 3)) times[:] = np.nan # Simulate a "dataset" np.random.seed(42) t = np.sort(np.random.rand(np.max(N))) yerr = np.random.uniform(0.1, 0.2, len(t)) y = np.sin(t) # Set up the GP model kernel = terms.RealTerm(1.0, 0.1) + terms.ComplexTerm(0.1, 2.0, 1.6) gp = GP(kernel) for i, n in enumerate(N): times[i, 0] = benchmark("gp.compute(t[:{0}], yerr[:{0}])".format(n), "from __main__ import gp, t, yerr") gp.compute(t[:n], yerr[:n]) times[i, 1] = benchmark("gp.log_likelihood(y[:{0}])".format(n), "from __main__ import gp, y") if n <= 4096: times[i, 2] = benchmark( """ C = gp.get_matrix(t[:{0}]) C[np.diag_indices_from(C)] += yerr[:{0}]**2 cho_factor(C)
class CeleriteLogLikelihood: def __init__(self, lpf, name: str = 'gp', noise_ids=None, fixed_hps=None): if not with_celerite: raise ImportError("CeleriteLogLikelihood requires celerite.") self.name = name self.lpf = lpf if fixed_hps is None: self.free = True else: self.hps = asarray(fixed_hps) self.free = False if lpf.lcids is None: raise ValueError( 'The LPF data needs to be initialised before initialising CeleriteLogLikelihood.' ) self.noise_ids = noise_ids if noise_ids is not None else unique( lpf.noise_ids) self.mask = m = zeros(lpf.lcids.size, bool) for lcid, nid in enumerate(lpf.noise_ids): if nid in self.noise_ids: m[lcid == lpf.lcids] = 1 if m.sum() == lpf.lcids.size: self.times = lpf.timea self.fluxes = lpf.ofluxa else: self.times = lpf.timea[m] self.fluxes = lpf.ofluxa[m] self.gp = GP(Matern32Term(0, 0)) if self.free: self.init_parameters() else: self.compute_gp(None, force=True, hps=self.hps) def init_parameters(self): name = self.name wns = log10(mad_std(diff(self.fluxes)) / sqrt(2)) pgp = [ LParameter(f'{name}_ln_out', f'{name} ln output scale', '', NP(-6, 1.5), bounds=(-inf, inf)), LParameter(f'{name}_ln_in', f'{name} ln input scale', '', UP(-8, 8), bounds=(-inf, inf)), LParameter(f'{name}_log10_wn', f'{name} log10 white noise sigma', '', NP(wns, 0.025), bounds=(-inf, inf)) ] self.lpf.ps.thaw() self.lpf.ps.add_global_block(self.name, pgp) self.lpf.ps.freeze() self.pv_slice = self.lpf.ps.blocks[-1].slice self.pv_start = self.lpf.ps.blocks[-1].start setattr(self.lpf, f"_sl_{name}", self.pv_slice) setattr(self.lpf, f"_start_{name}", self.pv_start) def compute_gp(self, pv, force: bool = False, hps=None): if self.free or force: parameters = pv[self.pv_slice] if hps is None else hps self.gp.set_parameter_vector(parameters[:-1]) self.gp.compute(self.times, yerr=10**parameters[-1]) def compute_gp_lnlikelihood(self, pv, model): self.compute_gp(pv) return self.gp.log_likelihood(self.fluxes - model[self.mask]) def predict_baseline(self, pv): self.compute_gp(pv) residuals = self.fluxes - squeeze( self.lpf.transit_model(pv))[self.mask] bl = zeros_like(self.lpf.timea) bl[self.mask] = self.gp.predict(residuals, self.times, return_cov=False) return 1. + bl def __call__(self, pvp, model): if pvp.ndim == 1: lnlike = self.compute_gp_lnlikelihood(pvp, model) else: lnlike = zeros(pvp.shape[0]) for ipv, pv in enumerate(pvp): if all(isfinite(model[ipv])): lnlike[ipv] = self.compute_gp_lnlikelihood(pv, model[ipv]) else: lnlike[ipv] = -inf return lnlike
class LineFitter(object): def __init__(self, x, flux, ivar=None, absorp_emiss=None, n_bg_coef=2, target_x=None): """ Parameters ---------- x : array_like Dispersion parameter. Usually either pixel or wavelength. flux : array_like Flux array. ivar : array_like Inverse-variance array. absorp_emiss : numeric [-1, 1] (optional) If -1, absorption line. If +1, emission line. If not specified, will try to guess. n_bg_coef : int (optional) The order of the polynomial used to fit the background. 1 = constant, 2 = linear, 3 = quadratic, etc. target_x : numeric (optional) Where we think the line of interest is. """ self.x = np.array(x) self.flux = np.array(flux) if ivar is not None: self.ivar = np.array(ivar) self._err = 1 / np.sqrt(self.ivar) else: self.ivar = np.ones_like(flux) self._err = None if absorp_emiss is None: raise NotImplementedError( "You must supply an absorp_emiss for now") self.absorp_emiss = float(absorp_emiss) self.target_x = target_x self.n_bg_coef = int(n_bg_coef) # result of fitting self.gp = None self.success = None @classmethod def transform_pars(cls, p): new_p = OrderedDict() for k, v in p.items(): if k in cls._log_pars: k = 'ln_{0}'.format(k) v = np.log(v) new_p[k] = v return new_p def get_init_generic(self, amp=None, x0=None, bg=None, bg_buffer=None): """ Get initial guesses for the generic line parameters. Parameters ---------- amp : numeric Line amplitude. This should be strictly positive; the ``absorp_emiss`` parameter controls whether it is absorption or emission. x0 : numeric Line centroid. bg : iterable Background polynomial coefficients. bg_buffer : int The number of values to use on either end of the flux array to estimate the background parameters. """ target_x = self.target_x if x0 is None: # estimate the initial guess for the centroid relmins = argrelmin(-self.absorp_emiss * self.flux)[0] if len(relmins) > 1 and target_x is None: logger.log( 0, "no target_x specified - taking largest line " "in spec region") target_x = self.x[np.argmin(-self.absorp_emiss * self.flux)] if len(relmins) == 1: x0 = self.x[relmins[0]] else: x0_idx = relmins[np.abs(self.x[relmins] - target_x).argmin()] x0 = self.x[x0_idx] # shift x array so that line is approximately at 0 _x = np.array(self.x, copy=True) x = np.array(_x) - x0 # background polynomial parameters if bg_buffer is None: bg_buffer = len(x) // 4 bg_buffer = max(1, bg_buffer) if bg is None: if self.n_bg_coef < 2: bg = np.array([0.]) bg[0] = np.median(self.flux) else: # estimate linear background model bg = np.array([0.] * self.n_bg_coef) i1 = argmedian(self.flux[:bg_buffer]) i2 = argmedian(self.flux[-bg_buffer:]) f1 = self.flux[:bg_buffer][i1] f2 = self.flux[-bg_buffer:][i2] x1 = x[:bg_buffer][i1] x2 = x[-bg_buffer:][i2] bg[1] = (f2 - f1) / (x2 - x1) # slope bg[0] = f2 - bg[1] * x2 # estimate constant term else: if len(bg) != self.n_bg_coef: raise ValueError('Number of bg polynomial coefficients does ' 'not match n_bg_coef specified when fitter ' 'was created.') if amp is None: # then estimate the initial guess for amplitude _i = np.argmin(np.abs(x)) if len(bg) > 1: _bg = bg[0] + bg[1] * x[_i] else: _bg = bg[0] # amp = np.sqrt(2*np.pi) * (flux[_i] - bg) amp = self.flux[_i] - _bg p0 = OrderedDict() p0['amp'] = np.abs(amp) p0['x0'] = x0 p0['bg_coef'] = bg return p0 def get_init_gp(self, log_sigma=None, log_rho=None, x0=None): """ Set up the GP kernel parameters. """ if log_sigma is None: if x0 is not None: # better initial guess for GP parameters mask = np.abs(self.x - x0) > 2. y_MAD = np.median( np.abs(self.flux[mask] - np.median(self.flux[mask]))) else: y_MAD = np.median(np.abs(self.flux - np.median(self.flux))) log_sigma = np.log(3 * y_MAD) if log_rho is None: log_rho = np.log(5.) p0 = OrderedDict() p0['log_sigma'] = log_sigma p0['log_rho'] = log_rho return p0 def init_gp(self, log_sigma=None, log_rho=None, amp=None, x0=None, bg=None, bg_buffer=None, **kwargs): """ **kwargs: """ # Call the different get_init methods with the correct kwargs passed sig = inspect.signature(self.get_init) kw = OrderedDict() for k in list(sig.parameters.keys()): kw[k] = kwargs.pop(k, None) p0 = self.get_init(**kw) # Call the generic init method - all line models must have these params p0_generic = self.get_init_generic(amp=amp, x0=x0, bg=bg, bg_buffer=bg_buffer) for k, v in p0_generic.items(): p0[k] = v # expand bg parameters bgs = p0.pop('bg_coef') for i in range(self.n_bg_coef): p0['bg{}'.format(i)] = bgs[i] # transform p0 = self.transform_pars(p0) # initialize model mean_model = self.MeanModel(n_bg_coef=self.n_bg_coef, absorp_emiss=self.absorp_emiss, **p0) p0_gp = self.get_init_gp(log_sigma=log_sigma, log_rho=log_rho) kernel = terms.Matern32Term(log_sigma=p0_gp['log_sigma'], log_rho=p0_gp['log_rho']) # set up the gp model self.gp = GP(kernel, mean=mean_model, fit_mean=True) if self._err is not None: self.gp.compute(self.x, self._err) else: self.gp.compute(self.x) init_params = self.gp.get_parameter_vector() init_ll = self.gp.log_likelihood(self.flux) logger.log(0, "Initial log-likelihood: {0}".format(init_ll)) return init_params def get_gp_mean_pars(self): """ Return a parameter dictionary for the mean model parameters only. """ fit_pars = OrderedDict() for k, v in self.gp.get_parameter_dict().items(): if 'mean' not in k: continue k = k[5:] # remove 'mean:' if k.startswith('ln'): if 'amp' in k: fit_pars[k[3:]] = self.absorp_emiss * np.exp(v) else: fit_pars[k[3:]] = np.exp(v) elif k.startswith('bg'): if 'bg_coef' not in fit_pars: fit_pars['bg_coef'] = [] fit_pars['bg_coef'].append(v) else: fit_pars[k] = v return fit_pars def _neg_log_like(self, params): self.gp.set_parameter_vector(params) try: ll = self.gp.log_likelihood(self.flux) except (RuntimeError, LinAlgError): return np.inf if np.isnan(ll): return np.inf return -ll def fit(self, bounds=None, **kwargs): """ """ init_params = self.init_gp() if bounds is None: bounds = OrderedDict() # default bounds for mean model parameters i = self.gp.models['mean'].get_parameter_names().index('x0') mean_bounds = self.gp.models['mean'].get_parameter_bounds() if mean_bounds[i][0] is None and mean_bounds[i][1] is None: mean_bounds[i] = (min(self.x), max(self.x)) for i, k in enumerate(self.gp.models['mean'].parameter_names): mean_bounds[i] = bounds.get(k, mean_bounds[i]) self.gp.models['mean'].parameter_bounds = mean_bounds # HACK: default bounds for kernel parameters self.gp.models['kernel'].parameter_bounds = [(None, None), (np.log(0.5), None)] soln = minimize(self._neg_log_like, init_params, method="L-BFGS-B", bounds=self.gp.get_parameter_bounds()) self.success = soln.success self.gp.set_parameter_vector(soln.x) if self.success: logger.debug("Success: {0}, Final log-likelihood: {1}".format( soln.success, -soln.fun)) else: logger.warning("Fit failed! Final log-likelihood: {0}, " "Final parameters: {1}".format(-soln.fun, soln.x)) return self def plot_fit(self, axes=None, fit_alpha=0.5): unbinned_color = '#3182bd' binned_color = '#2ca25f' gp_color = '#ff7f0e' noise_color = '#de2d26' if self.gp is None: raise ValueError("You must run .fit() first!") # ---------------------------------------------------------------------- # Plot the maximum likelihood model if axes is None: fig, axes = plt.subplots(1, 3, figsize=(15, 5), sharex=True) # data for ax in axes[:2]: ax.plot(self.x, self.flux, drawstyle='steps-mid', marker='', color='#777777', zorder=2) if self._err is not None: ax.errorbar(self.x, self.flux, self._err, marker='', ls='none', ecolor='#666666', zorder=1) # mean model wave_grid = np.linspace(self.x.min(), self.x.max(), 256) mu, var = self.gp.predict(self.flux, self.x, return_var=True) std = np.sqrt(var) axes[0].plot(wave_grid, self.gp.mean.get_unbinned_value(wave_grid), marker='', alpha=fit_alpha, zorder=10, color=unbinned_color) axes[0].plot(self.x, self.gp.mean.get_value(self.x), marker='', alpha=fit_alpha, zorder=10, drawstyle='steps-mid', color=binned_color) # full GP model axes[1].plot(self.x, mu, color=gp_color, drawstyle='steps-mid', marker='', alpha=fit_alpha, zorder=10) axes[1].fill_between(self.x, mu + std, mu - std, color=gp_color, alpha=fit_alpha / 10., edgecolor="none", step='mid') # just GP noise component mean_model = self.gp.mean.get_value(self.x) axes[2].plot(self.x, mu - mean_model, marker='', alpha=fit_alpha, zorder=10, drawstyle='steps-mid', color=noise_color) axes[2].plot(self.x, self.flux - mean_model, drawstyle='steps-mid', marker='', color='#777777', zorder=2) if self._err is not None: axes[2].errorbar(self.x, self.flux - mean_model, self._err, marker='', ls='none', ecolor='#666666', zorder=1) axes[0].set_ylabel('flux') axes[0].set_xlim(self.x.min(), self.x.max()) axes[0].set_title('Line model') axes[1].set_title('Line + noise model') axes[2].set_title('Noise model') fig = axes[0].figure fig.tight_layout() return fig
def drw_fit( t, y, yerr, init_func=None, neg_lp_func=None, optimizer_func=None, n_opt=10, user_bounds=None, scipy_opt_kwargs={}, scipy_opt_options={}, debug=False, ): """ Fit DRW. Args: t (array(float)): Time stamps of the input time series (the default unit is day). y (array(float)): y values of the input time series. yerr (array(float)): Measurement errors for y values. init_func (object, optional): A user-provided function to generate initial guesses for the optimizer. Defaults to None. neg_lp_func (object, optional): A user-provided function to compute negative probability given an array of parameters, an array of time series values and a celerite GP instance. Defaults to None. optimizer_func (object, optional): A user-provided optimizer function. Defaults to None. n_opt (int, optional): Number of optimizers to run. Defaults to 10. user_bounds (list, optional): Parameter boundaries for the default optimizer. Defaults to None. scipy_opt_kwargs (dict, optional): Keyword arguments for scipy.optimize.minimize. Defaults to {}. scipy_opt_options (dict, optional): "options" argument for scipy.optimize.minimize. Defaults to {}. debug (bool, optional): Turn on/off debug mode. Defaults to False. Returns: array(float): Best-fit DRW parameters """ # re-position lc; compute some stat t = t - t[0] y = y - np.median(y) std = np.std(y) min_dt = np.min(t[1:] - t[:-1]) # init bounds for fitting if user_bounds is not None and (len(user_bounds) == 2): bounds = user_bounds else: bounds = [ (np.log(std / 50), np.log(3 * std)), (np.log(min_dt / 5), np.log(t[-1])), ] # determine negative log probability function if neg_lp_func is None: neg_lp = partial(neg_lp_flat, bounds=np.array(bounds), mode="param") else: neg_lp = neg_lp_func # define ranges to generate initial guesses amp_range = [std / 50, 3 * std] log_tau_range = [np.log(min_dt / 5), np.log(t[-1] / 10)] # initialize parameter and kernel kernel = DRW_term(*drw_log_param_init(amp_range, log_tau_range)) gp = GP(kernel, mean=0) gp.compute(t, yerr) # determine initialize function if init_func is None: init = partial(drw_log_param_init, amp_range, log_tau_range) else: init = init_func # determine optimizer function if optimizer_func is None: scipy_opt_kwargs.update({"method": "L-BFGS-B", "bounds": bounds}) opt = partial( scipy_opt, mode="param", opt_kwargs=scipy_opt_kwargs, opt_options=scipy_opt_options, debug=debug, ) else: opt = optimizer_func best_fit_return = opt(y, gp, init, neg_lp, n_opt) return best_fit_return