peakfreq = freqs[0] except: pass # roughly model OMC based on single frequency sinusoid (if found) if peakfreq is not None: trend_type = "sinusoid" print("periodic component found at P = ", int(1/peakfreq), "d") LS = LombScargle(xtime, yomc) trend = LS.model(xtime, peakfreq) _, Asin, Bcos = LS.model_parameters(peakfreq) sin_priors.append((peakfreq, Asin, Bcos)) edges = [xtime.min()-0.5, xtime.max()+0.5] ngroups = int(np.ceil((xtime.max()-xtime.min())*peakfreq)) for i in range(1,ngroups): edges.append(xtime.min()+i/ngroups*(xtime.max()-xtime.min())) edges = list(np.sort(edges)) # otherwise, chose best polynomial model based on the BIC else: trend_type = "polynomial"
def LSP(x, y, dy, fVal=[0, 1, 5, 1], norm='standard', figout=None, label=None, freq_set=None): ''' 周期分析 https://docs.astropy.org/en/stable/timeseries/lombscargle.html#periodogram-algorithms 参数: x,y,dy: arrays 时间,星等,误差 figout: str 图片保存名称 fVal: list minimum_frequency,maximum_frequency,samples_per_peak(default 5),nterms(default 1) return: frequency, power, residuals, x_range, y_fit, theta if nterms=1: theta=[off_set,amplitude,phi,best_frequency] y=off_set+amplitude*np.sin(2*np.pi*best_frequency*x+phi) ''' import numpy as np import matplotlib.pyplot as plt from astropy.timeseries import LombScargle if not isinstance(x, np.ndarray): x = np.array(x) y = np.array(y) dy = np.array(dy) fVal[0] = 10**-5 if fVal[0] == 0 else fVal[0] ls = LombScargle(x, y, dy, nterms=fVal[-1], normalization=norm) frequency, power = ls.autopower(minimum_frequency=fVal[0], maximum_frequency=fVal[1], samples_per_peak=fVal[2]) fig, ax = plt.subplots(3) fig.set_size_inches(20, 27) #ax[0].invert_yaxis();ax[2].invert_yaxis(); ax[0].grid() ax[1].grid() ax[2].grid() ax[0].errorbar(x, y, dy, fmt='bo-', label=label) ax[1].set_xlim((frequency[0], frequency[-1])) ax[1].plot(frequency, power, 'b-') ax11 = ax[1].twiny() ax11.set_xlim(ax[1].get_xlim()) x_side = np.linspace(0.001 + frequency[0], frequency[-1], 10) x_side_var = np.round(24 * 60 / x_side, 2) plt.xticks(x_side, x_side_var, rotation=0) best_frequency = frequency[np.argmax(power)] peak_power = power.max() ax[1].plot(best_frequency, peak_power, 'ro') ax[1].legend([ 'spectrum distribution', 'peak frequency ' + str(round(best_frequency, 4)) + 'c/d is period ' + str(round(24 / best_frequency, 4)) + 'h' ], loc='upper right', fontsize=15, frameon=False) if fVal[-1] == 1: for cutoff in ls.false_alarm_level([0.1, 0.05, 0.01]): ax[1].axhline(cutoff, color='black', linestyle='dotted') if freq_set != None: best_frequency = freq_set phase = (x * best_frequency) % 1 y_fit = ls.model(x, best_frequency) residuals = y - y_fit y_fit = y_fit[np.argsort(phase)] ax[2].plot(np.sort(phase), y_fit, 'r-', linewidth=5) ax[2].errorbar(phase, y, dy, fmt='b.', alpha=1) ax[2].legend( ['best fitted curve is ' + str(best_frequency), 'folded data'], loc='upper right', fontsize=15, frameon=False) x_range = np.linspace(x.min(), x.max(), 100) y_fit = ls.model(x_range, best_frequency) ax[0].plot(x_range, y_fit, 'r-', label='fitting curve', linewidth=5) ax[0].legend(loc='upper right', fontsize=15, frameon=False) if figout: plt.savefig(figout, dpi=100) plt.show() if fVal[-1] == 1: print('the false alarm probability for %0.2f (%0.2f min) is %0.2e' % (best_frequency, 24 * 60 / best_frequency, ls.false_alarm_probability(peak_power, method='davies'))) theta = ls.model_parameters(best_frequency) theta[0] = ls.offset() + theta[0] if len(theta) == 3: K = (theta[1]**2 + theta[2]**2)**0.5 phi = np.arcsin(theta[2] / K) theta = [theta[0], K, phi, best_frequency] return frequency, power, residuals, x_range, y_fit, theta
# test xd = np.arange('1990-01-01', '2000-01-01', dtype='datetime64[D]') xx = (xd - np.datetime64('1990-01-01')).astype(np.float) x = xx x = np.concatenate([x[:700], x[1400:]]) prob = 0.9 x = x[np.random.rand(len(x)) >= 0.9] y = 5 * np.cos(x*2*np.pi/365)+3 * np.cos((x+120)*2*np.pi/365*2) + \ 4 * np.cos((x+60)*2*np.pi/365/4) # y = 5*np.sin(x*2*np.pi/365) y = y - np.mean(y) ls = LombScargle(x, y) # freq = np.arange(1, len(xd))/len(xd) freq = np.fft.fftfreq(len(xd)) power = ls.power(freq) p = ls.model_parameters(1 / 365) mat = ls.design_matrix(1 / 365) ym = np.zeros([len(freq), len(x)]) yp = np.zeros([len(freq), len(x)]) for k, f in enumerate(freq[1:-1]): ym[k, :] = ls.model(x, f) yp[k, :] = ym[k, :] * ls.power(f) yp = yp # yy = yy-ls.offset()/2 fig, axes = plt.subplots(3, 1, figsize=(8, 6)) axes[0].plot(x, y, '--*') axes[0].plot(x, np.sum(ym, axis=0) / 2 * (len(x) / len(xx)), '-r') axes[0].set_xlabel('day') axes[1].plot(freq, power) axes[1].set_ylabel('power')
def lombscargle_periodogram(time, flux, error, dt=0, min_period=0.1, max_period=4, peak_points=10, height=0, peak_ind=0, plot=True, xlim=(0, 1)): ''' this function determines the peak period of a given light curve, allows one to choose which peak to select, then plots the periodogram and the folded light curve Parameters ---------- time : array of float contains time data for the light curve flux : array of float contains flux data for the light curve error : array of float contains error data for the light curve dt : float time shift [default = 0] min_period : float minimum period to investigate [default = 0.1 days] max_period : float maximum period to investigate [default = 4 days] peak_points : int number of points around peaks [default = 10] height : float minimum height to consider peaks [default = 0] peak_ind : ind choose a peak number, maximum is default [peak_ind = 0] plot : bool plot a periodogram and the folded lightcurve with best fit sinusoid xlim : tuple x-limits of the folded plot [default = (0, 1)] Returns ------- Pb : tuple contains the best fit parameters for the sine wave (amplitude, period, phase) residuals : array of float contains the residuals between the best fit model and the data ''' time_fixed = time - dt # create the periodogram model = LombScargle(time_fixed, flux, error) frequencies, power = model.autopower(minimum_frequency=(1. / max_period), maximum_frequency=(1 / min_period), samples_per_peak=peak_points) # convert to periods periods = 1 / frequencies # identify and extract peaks inds, peaks = find_peaks(power, height=height) peaks = peaks['peak_heights'] sort_peaks = np.argsort(peaks) inds = inds[sort_peaks] peaks = peaks[sort_peaks] # select peak period = periods[inds[-1 - peak_ind]] # fit the sinusoid flux_fit = model.model(time_fixed, 1 / period) residuals = flux - flux_fit t0, t1, t2 = model.model_parameters(1 / period) # convert theta parameters to amplitude and phase amplitude = np.hypot(t1, t2) * np.sign(flux_fit[0]) phase = -np.arctan(t1 / t2) + np.pi / 2 Pb = (amplitude, period, phase) # plot the periodogram and folded light curve if plot == True: # periodogram fig = plt.figure(figsize=(16, 8)) plt.title('Lomb-Scargle Periodogram of Stellar Variations') plt.xlabel('Period [days]') plt.ylabel('Power [-]') plt.plot(periods, power, 'b-') plt.gca().axvline(x=period, color='k', ls=':') plt.show() # folded light curve plot_folded(time_fixed, flux, error, Pb, flux_fit, dt, xlim) print('%.6f sin(2 pi time / %.4f + %.4f)' % Pb) return Pb, residuals
power_array = np.zeros([num_along_dim, num_along_dim, num_freq]) power_array_max = np.zeros([num_along_dim, num_along_dim]) power_array_period = np.zeros([num_along_dim, num_along_dim]) power_array_phase = np.zeros([num_along_dim, num_along_dim]) for ii in range(0, num_along_dim): for jj in range(0, num_along_dim): # print(time_bin_samp_days.shape, photon_grid[:,ii,jj].shape) # blah = LombScargle(time_bin_samp_days[:num_bins_analyze], photon_grid[:,ii,jj]).power(freq) LS = LombScargle(time_bin_samp_days[:num_bins_analyze], photon_grid[:, ii, jj]) power_array[ii, jj] = LS.power(freq) power_array_max[ii, jj] = np.max(power_array[ii, jj]) power_array_period[ii, jj] = period[np.argmax(power_array[ii, jj])] model_params = LS.model_parameters(1 / power_array_period[ii, jj]) power_array_phase[ii, jj] = np.arctan2(model_params[2], model_params[1]) # power_array = np.max(power_array,axis=-1) # print(power_array_period) # print(power_array_phase) exposure_power_array = LombScargle( time_bin_samp_days[:num_bins_analyze], exposure_bin_samp[:num_bins_analyze]).power(freq) exposure_power = np.max(exposure_power_array) expo_power_freq = period[np.argmax(exposure_power_array)] print('Exposure Power array: ' + str(exposure_power_array)) print('Exposure Power array max period: ' + str(expo_power_freq))
def clean_rotationsignal_tess_singlesector_light_curve(time, mag, magisflux=False, dtr_dict=None, lsp_dict=None, maskorbitedge=True, lsp_options={ 'period_min': 0.1, 'period_max': 20 }, verbose=True): """ The goal of this function is to remove a stellar rotation signal from a single TESS light curve (ideally one without severe insturmental systematics) while preserving transits. "Cleaning" by default is taken to mean the sequence of mask_orbit_edge -> slide_clip -> detrend -> slide_clip. "Detrend" can mean any of the Wotan flatteners, Notch, or LOCOR. "slide_clip" means apply windowed sigma-clipping removal. Args: time, mag (np.ndarray): time and magnitude (or flux) vectors magisflux (bool): True if the "mag" vector is a flux already dtr_dict (optional dict): dictionary containing arguments passed to Wotan, Notch, or LOCOR. Relevant keys should include: 'dtr_method' (str): one of: ['best', 'notch', 'locor', 'pspline', 'biweight', 'none'] 'break_tolerance' (float): number of days past which a segment of light curve is considered a "new segment". 'window_length' (float): length of sliding window in days lsp_dict (optional dict): dictionary containing Lomb Scargle periodogram information, which is used in the "best" method for choosing between LOCOR or Notch detrending. If this is not passed, it'll be constructed here after the mask_orbit_edge -> slide_clip steps. lsp_options: contains keys period_min and period_max, used for the internal Lomb Scargle periodogram search. maskorbitedge (bool): whether to apply the initial "mask_orbit_edge" step. Probably would only want to be false if you had already done it elsewhere. Returns: search_time, search_flux, dtr_stages_dict (np.ndarrays and dict): light curve ready for TLS or BLS style periodograms; and a dictionary of the different processing stages (see comments for details of `dtr_stages_dict` contents). """ dtr_method = _get_detrending_method(dtr_dict) # # convert mag to flux and median-normalize # if magisflux: flux = mag else: f_x0 = 1e4 m_x0 = 10 flux = f_x0 * 10**(-0.4 * (mag - m_x0)) flux /= np.nanmedian(flux) # # ignore the times near the edges of orbits for TLS. # if maskorbitedge: _time, _flux = moe.mask_orbit_start_and_end( time, flux, raise_expectation_error=False, verbose=verbose) else: _time, _flux = time, flux # # sliding sigma clip asymmetric [20,3]*MAD, about median. use a 3-day # window, to give ~100 to 150 data points. mostly to avoid big flares. # clip_window = 3 clipped_flux = slide_clip(_time, _flux, window_length=clip_window, low=20, high=3, method='mad', center='median') sel0 = ~np.isnan(clipped_flux) # # for "best" or LOCOR detrending, you need to know the stellar rotation # period. so, if it hasn't already been run, run the LS periodogram here. # in `lsp_dict`, cache the LS peak period, amplitude, and FAP. # if (not isinstance(lsp_dict, dict)) and (dtr_method in ['locor', 'best']): period_min = lsp_options['period_min'] period_max = lsp_options['period_max'] ls = LombScargle(_time[sel0], clipped_flux[sel0], clipped_flux[sel0] * 1e-3) freq, power = ls.autopower(minimum_frequency=1 / period_max, maximum_frequency=1 / period_min) ls_fap = ls.false_alarm_probability(power.max()) best_freq = freq[np.argmax(power)] ls_period = 1 / best_freq theta = ls.model_parameters(best_freq) ls_amplitude = theta[1] lsp_dict = {} lsp_dict['ls_period'] = ls_period lsp_dict['ls_amplitude'] = np.abs(ls_amplitude) lsp_dict['ls_fap'] = ls_fap if not isinstance(dtr_dict, dict): dtr_dict = {} dtr_dict['method'] = dtr_method # # apply the detrending call based on the method given # dtr_method_used = dtr_method if dtr_method in ['pspline', 'biweight', 'none']: if 'break_tolerance' not in dtr_dict: dtr_dict['break_tolerance'] = None if 'window_length' not in dtr_dict: dtr_dict['window_length'] = None flat_flux, trend_flux = detrend_flux( _time[sel0], clipped_flux[sel0], break_tolerance=dtr_dict['break_tolerance'], method=dtr_dict['method'], cval=None, window_length=dtr_dict['window_length'], edge_cutoff=None) elif dtr_method == 'notch': flat_flux, trend_flux, notch = _run_notch(_time[sel0], clipped_flux[sel0], dtr_dict, verbose=verbose) elif dtr_method == 'locor': flat_flux, trend_flux, notch = _run_locor(_time[sel0], clipped_flux[sel0], dtr_dict, lsp_dict) elif dtr_method == 'best': # for stars with Prot < 1 day, use LOCOR. for stars with Prot > 1 day, # use Notch. (or pspline?). PERIOD_CUTOFF = 1.0 if lsp_dict['ls_period'] > PERIOD_CUTOFF: flat_flux, trend_flux, notch = _run_notch(_time[sel0], clipped_flux[sel0], dtr_dict, verbose=verbose) dtr_method_used += '-notch' elif (lsp_dict['ls_period'] < PERIOD_CUTOFF and lsp_dict['ls_period'] > 0): flat_flux, trend_flux, notch = _run_locor(_time[sel0], clipped_flux[sel0], dtr_dict, lsp_dict) dtr_method_used += '-locor' else: raise NotImplementedError(f"Got LS period {lsp_dict['ls_period']}") # # re-apply sliding sigma clip asymmetric [20,3]*MAD, about median, after # detrending. # clip_window = 3 clipped_flat_flux = slide_clip(_time[sel0], flat_flux, window_length=clip_window, low=20, high=3, method='mad', center='median') sel1 = ~np.isnan(clipped_flat_flux) search_flux = clipped_flat_flux[sel1] search_time = _time[sel0][sel1] dtr_stages_dict = { # non-nan indices from clipped_flux 'sel0': sel0, # non-nan indices from clipped_flat_flux 'sel1': sel1, # after initial window sigma_clip on flux, what is left? 'clipped_flux': clipped_flux, # after detrending, what is left? 'flat_flux': flat_flux, # after window sigma_clip on flat_flux, what is left? 'clipped_flat_flux': clipped_flat_flux, # what does the detrending algorithm give as the "trend"? 'trend_flux': trend_flux, 'trend_time': _time[sel0], # what method was used? if "best", gives "best-notch" or "best-locor" 'dtr_method_used': dtr_method_used, # times and fluxes used 'search_time': search_time, 'search_flux': search_flux } if isinstance(lsp_dict, dict): # in most cases, cache the LS period, amplitude, and FAP dtr_stages_dict['lsp_dict'] = lsp_dict return search_time, search_flux, dtr_stages_dict
def _run_locor(TIME, FLUX, dtr_dict, lsp_dict): """ NOTE: lsp_dict is created here if None is passed. """ from notch_and_locor.core import rcomb # Format "data" into recarray format needed for notch. N_points = len(TIME) data = np.recarray((N_points, ), dtype=[('t', float), ('fraw', float), ('fcor', float), ('s', float), ('qual', int), ('divisions', float)]) data.t = TIME data.fcor = FLUX data.fraw[:] = 0 data.s[:] = 0 data.qual[:] = 0 # Get rotation period, if not available. In most cases, it should be # passed in via lsp_dict. if not isinstance(lsp_dict, dict): print( "Did not get period from lsp_dict: finding via Lomb Scargle. " "WARNING: it's better to feed via lsp_dict for cacheing speeds. ") ls = LombScargle(TIME, FLUX, FLUX * 1e-3) period_min, period_max = 0.1, 10 freq, power = ls.autopower(minimum_frequency=1 / period_max, maximum_frequency=1 / period_min) ls_fap = ls.false_alarm_probability(power.max()) best_freq = freq[np.argmax(power)] ls_period = 1 / best_freq theta = ls.model_parameters(best_freq) ls_amplitude = theta[1] # NOTE: this just forces LOCOR to run irrespective of the exact # ls_period, ls_amplitude, ls_fap, or color. In other words, it # doesn't deal with the question of whether the star is young. You # should do that elsewhere, and probably only be running LOCOR for # stars with Prot <~ a few days. lsp_dict['ls_period'] = ls_period lsp_dict['ls_amplitude'] = np.abs(ls_amplitude) lsp_dict['ls_fap'] = ls_fap wsize = lsp_dict['ls_period'] # # minimum rotation period to bunch rotations together to run LOCoR. # 2 days works robustly for K2 long cadence data. TESS we shall see. # alias_num = 2.0 # Run LOCOR (Locally Optimized Combination of Rotations) fittimes, depth, detrend, polyshape, badflag = (rcomb(data, wsize, aliasnum=alias_num)) assert len(fittimes) == len(TIME) # store everything in a common format recarray N_points = len(detrend) locor = np.recarray((N_points, ), dtype=[('t', float), ('detrend', float), ('badflag', int)]) locor.detrend = detrend.copy() locor.badflag = badflag.copy() locor.polyshape = polyshape.copy() locor.t = data.t # # Convert to my naming scheme. # flat_flux = locor.detrend trend_flux = locor.polyshape return flat_flux, trend_flux, locor
class powerspectrum(object): """ Attributes: nyquist (float): Nyquist frequency in Hz. df (float): Fundamental frequency spacing in Hz. standard (tuple): Frequency in microHz and power density spectrum sampled from 0 to ``nyquist`` with a spacing of ``df``. ls (:class:`astropy.timeseries.LombScargle`): .. codeauthor:: Kristine Kousholt Mikkelsen <*****@*****.**> .. codeauthor:: Rasmus Handberg <*****@*****.**> """ #---------------------------------------------------------------------------------------------- def __init__(self, lightcurve, fit_mean=False): """ Parameters: lightcurve (:class:`lightkurve.LightCurve`): Lightcurve to estimate power spectrum for. fit_mean (boolean, optional): """ # Store the input settings: self.fit_mean = fit_mean # Calculate standard properties of the timeseries: indx = np.isfinite(lightcurve.flux) self.df = 1 / (86400 * (nanmax(lightcurve.time[indx]) - nanmin(lightcurve.time[indx]))) # Hz self.nyquist = 1 / ( 2 * 86400 * nanmedian(np.diff(lightcurve.time[indx]))) # Hz self.standard = None # Create LombScargle object of timeseries, where time is in seconds: self.ls = LombScargle(lightcurve.time[indx] * 86400, lightcurve.flux[indx], center_data=True, fit_mean=self.fit_mean) # Calculate a better estimate of the fundamental frequency spacing: self.df = self.fundamental_spacing_integral() # Calculate standard power density spectrum: # Start by calculating a complete un-scaled power spectrum: self.standard = self.powerspectrum(oversampling=1, nyquist_factor=1, scale=None) # Use the un-scaled power spectrum to finding the normalisation factor # which will ensure that Parseval's theorem holds: N = len(self.ls.t) tot_MS = np.sum((self.ls.y - nanmean(self.ls.y))**2) / N tot_lomb = np.sum(self.standard[1]) self.normfactor = tot_MS / tot_lomb # Re-scale the standard power spectrum to being in power density: self.standard = list(self.standard) self.standard[1] *= self.normfactor / (self.df * 1e6) self.standard = tuple(self.standard) #---------------------------------------------------------------------------------------------- def copy(self): """Create copy of power spectrum.""" return deepcopy(self) #---------------------------------------------------------------------------------------------- def fundamental_spacing_minimum(self): """Estimate fundamental spacing using the first minimum spectral window function.""" # Create "window" time series: freq_cen = 0.5 * self.nyquist x = 0.5 * np.sin(2 * np.pi * freq_cen * self.ls.t) + 0.5 * np.cos( 2 * np.pi * freq_cen * self.ls.t) # Calculate power spectrum for the given frequency range: ls = LombScargle(self.ls.t, x, center_data=True, fit_mean=self.fit_mean) # Minimize the window function around the first minimum: # Normalization is completely irrelevant window = lambda freq: ls.power( freq_cen + freq, normalization='psd', method='fast') res = minimize_scalar(window, [0.75 * self.df, self.df, 1.25 * self.df]) df = res.x return df #---------------------------------------------------------------------------------------------- def fundamental_spacing_integral(self): """Estimate fundamental spacing using the integral of the spectral window function.""" # Integrate the windowfunction freq, window = self.windowfunction(width=100 * self.df, oversampling=5) df = simps(window, freq) return df * 1e-6 #---------------------------------------------------------------------------------------------- def powerspectrum(self, freq=None, oversampling=1, nyquist_factor=1, scale='power'): """ Calculate power spectrum for time series. Parameters: freq (ndarray, optional): Frequencies to calculate power spectrum for. If set to None, the full frequency range from 0 to ``nyquist``*``nyquist_factor`` is calculated. oversampling (float, optional): Oversampling factor. Default=1. nyquist_factor (float, optional): Nyquist factor. Default=1. scale (str, optional): 'power', 'powerdensity' and 'amplitude'. Default='power'. Returns: tuple: Tuple of two ndarray with frequencies in microHz and corresponding power in units depending on the ``scale`` keyword. """ # The frequency axis in Hertz: assume_regular_frequency = False if freq is None: # If what we are really asking for is the standard power density spectrum, we have already # calculated it in the init-function, so just return that: if scale == 'powerdensity' and oversampling == 1 and nyquist_factor == 1 and self.standard: return self.standard # Set the standard frequency axis: freq = np.arange(self.df / oversampling, nyquist_factor * self.nyquist, self.df / oversampling, dtype='float64') assume_regular_frequency = True # Calculate power at frequencies using fast Lomb-Scargle periodiogram: power = self.ls.power( freq, normalization='psd', method='fast', assume_regular_frequency=assume_regular_frequency) # Due to numerical errors, the "fast implementation" can return power < 0. power = np.clip(power, 0, None) # Different scales: freq *= 1e6 # Rescale frequencies to being in microHz if scale is None: pass elif scale == 'power': power *= self.normfactor * 2 elif scale == 'powerdensity': power *= self.normfactor / (self.df * 1e6) elif scale == 'amplitude': power = np.sqrt(power * self.normfactor * 2) return freq, power #---------------------------------------------------------------------------------------------- def windowfunction(self, width=None, oversampling=10): """Spectral window function. Parameters: width (float, optional): The width in Hz on either side of zero to calculate spectral window. oversampling (float, optional): Oversampling factor. Default=10. """ if width is None: width = 100 * self.df freq_cen = 0.5 * self.nyquist Nfreq = int(oversampling * width / self.df) freq = freq_cen + (self.df / oversampling) * np.arange( -Nfreq, Nfreq, 1) x = 0.5 * np.sin(2 * np.pi * freq_cen * self.ls.t) + 0.5 * np.cos( 2 * np.pi * freq_cen * self.ls.t) # Calculate power spectrum for the given frequency range: ls = LombScargle(self.ls.t, x, center_data=True, fit_mean=self.fit_mean) power = ls.power(freq, method='fast', normalization='psd', assume_regular_frequency=True) power /= power[int(len(power) / 2)] # Normalize to have maximum of one freq -= freq_cen freq *= 1e6 return freq, power #---------------------------------------------------------------------------------------------- def plot(self, ax=None, xlabel='Frequency (muHz)', ylabel=None, style='powerspectrum'): if ylabel is None: ylabel = { 'powerdensity': 'Power density (ppm^2/muHz)', 'power': 'Power (ppm^2)', 'amplitude': 'Amplitude (ppm)' }['powerdensity'] # TODO: Only one setting for now... if style is None or style == 'powerspectrum': style = os.path.join(os.path.dirname(__file__), 'powerspectrum.mplstyle') elif style == 'lightkurve': style = lightkurve.MPLSTYLE with plt.style.context(style): if ax is None: fig, ax = plt.subplots(1) ax.loglog(self.standard[0], self.standard[1], 'k-') ax.set_xlabel('Frequency (muHz)') ax.set_ylabel(ylabel) ax.set_xlim(self.standard[0][0], self.standard[0][-1]) #---------------------------------------------------------------------------------------------- def optimize_peak(self, fmax): """ Optimize frequency to nearest peak. Parameters: fmax (float): Frequency in microHz. Returns: float: Optimized frequency in microHz. """ # Narrow search area around the given frequency fmax = np.atleast_1d(fmax) if len(fmax) == 3: freq_low, fmax, freq_high = fmax else: fmax = fmax[0] freq_low = fmax - 2 * self.df * 1e6 freq_high = fmax + 2 * self.df * 1e6 # Do not optimize too low to zero: freq_low = np.clip(freq_low, 0.25 * self.df * 1e6, None) # Optimize to find the correct frequency func = lambda f: -self.ls.power(f * 1e-6, method='fast', normalization='psd', assume_regular_frequency=False) #res = minimize(func, fmax, bounds=((freq_low, freq_high),), method='TNC') #return res.x[0] res = minimize_scalar(func, bracket=[freq_low, fmax, freq_high], bounds=(freq_low, freq_high), method='bounded', options={'xatol': 1e-5}) #print(res) #x = np.linspace(freq_low-10*self.df*1e6, freq_high+10*self.df*1e6, 200) #plt.figure() #plt.plot(x, func(x), 'k-', lw=0.5) #plt.axvline(fmax) #plt.axvline(freq_low) #plt.axvline(freq_high) #plt.plot(res.x, res.fun, 'ro') return res.x #---------------------------------------------------------------------------------------------- def alpha_beta(self, freq): """ omega = freq*2*np.pi*1e-6 #w = np.ones_like(self.ls.t) # self.ls.y_err**-2 # Calcultae sums: sx = np.sin(omega*self.ls.t) cx = np.cos(omega*self.ls.t) s = np.sum(self.ls.y * sx) c = np.sum(self.ls.y * cx) cs = np.sum(sx * cx) cc = np.sum(cx * cx) # Calculate ss on basis of cc and the sum # of the weights, which was calculated in # ImportData: sumWeights = len(self.ls.t) # np.sum(w) ss = sumWeights - cc # Calculate amplitude and phase: D = ss*cc - cs*cs alpha = (s * cc - c * cs)/D beta = (c * ss - s * cs)/D """ alpha, beta = self.ls.model_parameters(freq * 1e-6, units=False) return alpha, beta #---------------------------------------------------------------------------------------------- # TODO: Replace with ps.ls.model? def model(self, a, b, freq): omegax = 0.1728 * np.pi * freq * self.ls.t # Strange factor is 2 * 86400 * 1e-6 return a * np.sin(omegax) + b * np.cos(omegax) #---------------------------------------------------------------------------------------------- def false_alarm_probability(self, freq): """ Calculate Lomb-Scargle false alarm probability for given frequency. Parameters: freq (ndarray): Frequency in microHz. Returns: ndarray: False alarm probability (p-value). """ p_harmonic = self.ls.power(freq * 1e-6, method='fast') return self.ls.false_alarm_probability(p_harmonic) #---------------------------------------------------------------------------------------------- def replace_lightcurve(self, lightcurve): # Create LombScargle object of timeseries, where time is in seconds: indx = np.isfinite(lightcurve.flux) self.ls = LombScargle(lightcurve.time[indx] * 86400, lightcurve.flux[indx], center_data=True, fit_mean=self.fit_mean)
from hydroDL.post import axplot, figplot import matplotlib.pyplot as plt import importlib import pandas as pd import numpy as np import os import time # test x = np.arange(5) y = np.random.random(5) ls = LombScargle(x, y) freq = 2 / 5 p = ls.model_parameters(freq) mat = ls.design_matrix(freq) yp = ls.model(x, freq) power = ls.power(freq, normalization='psd') offset = ls.offset() a = np.fft.fft(y) yp1 = p[0] + p[1] * np.sin(2 * np.pi * freq * x) + p[2] * np.cos( 2 * np.pi * freq * x) + ls.offset() mat np.sin(2 * np.pi * freq * x) np.cos(2 * np.pi * freq * x) yp1 = yp * 0