Ejemplo n.º 1
0
                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"
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
# 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')
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
        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))
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
0
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