Source code for feets.extractors.ext_fourier_components

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# The MIT License (MIT)

# Copyright (c) 2017 Juan Cabral

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


# =============================================================================
# FUTURE
# =============================================================================

from __future__ import unicode_literals


# =============================================================================
# DOC
# =============================================================================

__doc__ = """"""


# =============================================================================
# IMPORTS
# =============================================================================

import numpy as np

from scipy.optimize import curve_fit

from .ext_lomb_scargle import lscargle
from .core import Extractor


# =============================================================================
# EXTRACTOR CLASS
# =============================================================================

[docs]class FourierComponents(Extractor): r""" **Periodic features extracted from light-curves using Lomb–Scargle** **(Richards et al., 2011)** Here, we adopt a model where the time series of the photometric magnitudes of variable stars is modeled as a superposition of sines and cosines: .. math:: y_i(t|f_i) = a_i\sin(2\pi f_i t) + b_i\cos(2\pi f_i t) + b_{i,\circ} where :math:`a` and :math:`b` are normalization constants for the sinusoids of frequency :math:`f_i` and :math:`b_{i,\circ}` is the magnitude offset. To find periodic variations in the data, we fit the equation above by minimizing the sum of squares, which we denote :math:`\chi^2`: .. math:: \chi^2 = \sum_k \frac{(d_k - y_i(t_k))^2}{\sigma_k^2} where :math:`\sigma_k` is the measurement uncertainty in data point :math:`d_k`. We allow the mean to float, leading to more robust period estimates in the case where the periodic phase is not uniformly sampled; in these cases, the model light curve has a non-zero mean. This can be important when searching for periods on the order of the data span :math:`T_{tot}`. Now, define .. math:: \chi^2_{\circ} = \sum_k \frac{(d_k - \mu)^2}{\sigma_k^2} where :math:`\mu` is the weighted mean .. math:: \mu = \frac{\sum_k d_k / \sigma_k^2}{\sum_k 1/\sigma_k^2} Then, the generalized Lomb-Scargle periodogram is: .. math:: P_f(f) = \frac{(N-1)}{2} \frac{\chi_{\circ}^2 - \chi_m^2(f)} {\chi_{\circ}^2} where :math:`\chi_m^2(f)` is :math:`\chi^2` minimized with respect to :math:`a, b` and :math:`b_{\circ}`. Following Debosscher et al. (2007), we fit each light curve with a linear term plus a harmonic sum of sinusoids: .. math:: y(t) = ct + \sum_{i=1}^{3}\sum_{j=1}^{4} y_i(t|jf_i) where each of the three test frequencies :math:`f_i` is allowed to have four harmonics at frequencies :math:`f_{i,j} = jf_i`. The three test frequencies :math:`f_i` are found iteratively, by successfully finding and removing periodic signal producing a peak in :math:`P_f(f)` , where :math:`P_f(f)` is the Lomb-Scargle periodogram as defined above. Given a peak in :math:`P_f(f)`, we whiten the data with respect to that frequency by fitting away a model containing that frequency as well as components with frequencies at 2, 3, and 4 times that fundamental frequency (harmonics). Then, we subtract that model from the data, update :math:`\chi_{\circ}^2`, and recalculate :math:`P_f(f)` to find more periodic components. **Algorithm:** 1. For :math:`i = {1, 2, 3}` 2. Calculate Lomb-Scargle periodogram :math:`P_f(f)` for light curve. 3. Find peak in :math:`P_f(f)`, subtract that model from data. 4. Update :math:`\chi_{\circ}^2`, return to *Step 1*. Then, the features extracted are given as an amplitude and a phase: .. math:: A_{i,j} = \sqrt{a_{i,j}^2 + b_{i,j}^2}\\ \textrm{PH}_{i,j} = \arctan(\frac{b_{i,j}}{a_{i,j}}) where :math:`A_{i,j}` is the amplitude of the :math:`j-th` harmonic of the :math:`i-th` frequency component and :math:`\textrm{PH}_{i,j}` is the phase component, which we then correct to a relative phase with respect to the phase of the first component: .. math:: \textrm{PH}'_{i,j} = \textrm{PH}_{i,j} - \textrm{PH}_{00} and remapped to :math:`|-\pi, +\pi|` References ---------- .. [richards2011machine] Richards, J. W., Starr, D. L., Butler, N. R., Bloom, J. S., Brewer, J. M., Crellin-Quick, A., ... & Rischard, M. (2011). On machine-learned classification of variable stars with sparse and noisy time-series data. The Astrophysical Journal, 733(1), 10. Doi:10.1088/0004-637X/733/1/10. """ data = ['magnitude', 'time'] features = ['Freq1_harmonics_amplitude_0', 'Freq1_harmonics_amplitude_1', 'Freq1_harmonics_amplitude_2', 'Freq1_harmonics_amplitude_3', 'Freq2_harmonics_amplitude_0', 'Freq2_harmonics_amplitude_1', 'Freq2_harmonics_amplitude_2', 'Freq2_harmonics_amplitude_3', 'Freq3_harmonics_amplitude_0', 'Freq3_harmonics_amplitude_1', 'Freq3_harmonics_amplitude_2', 'Freq3_harmonics_amplitude_3', 'Freq1_harmonics_rel_phase_0', 'Freq1_harmonics_rel_phase_1', 'Freq1_harmonics_rel_phase_2', 'Freq1_harmonics_rel_phase_3', 'Freq2_harmonics_rel_phase_0', 'Freq2_harmonics_rel_phase_1', 'Freq2_harmonics_rel_phase_2', 'Freq2_harmonics_rel_phase_3', 'Freq3_harmonics_rel_phase_0', 'Freq3_harmonics_rel_phase_1', 'Freq3_harmonics_rel_phase_2', 'Freq3_harmonics_rel_phase_3'] params = { "lscargle_kwds": { "autopower_kwds": { "normalization": "standard", "nyquist_factor": 100}} } def _model(self, x, a, b, c, Freq): return (a * np.sin(2 * np.pi * Freq * x) + b * np.cos(2 * np.pi * Freq * x) + c) def _yfunc_maker(self, Freq): def func(x, a, b, c): return (a * np.sin(2 * np.pi * Freq * x) + b * np.cos(2 * np.pi * Freq * x) + c) return func def _components(self, magnitude, time, lscargle_kwds): time = time - np.min(time) A, PH = [], [] for i in range(3): frequency, power, fmax = lscargle(time, magnitude, **lscargle_kwds) fundamental_Freq = frequency[fmax] Atemp, PHtemp = [], [] omagnitude = magnitude for j in range(4): function_to_fit = self._yfunc_maker((j + 1) * fundamental_Freq) popt0, popt1, popt2 = curve_fit( function_to_fit, time, omagnitude)[0][:3] Atemp.append(np.sqrt(popt0 ** 2 + popt1 ** 2)) PHtemp.append(np.arctan(popt1 / popt0)) model = self._model( time, popt0, popt1, popt2, (j+1) * fundamental_Freq) magnitude = np.array(magnitude) - model A.append(Atemp) PH.append(PHtemp) PH = np.asarray(PH) scaledPH = PH - PH[:, 0].reshape((len(PH), 1)) return A, scaledPH
[docs] def fit(self, magnitude, time, lscargle_kwds): lscargle_kwds = lscargle_kwds or {} A, sPH = self._components(magnitude, time, lscargle_kwds) result = { "Freq1_harmonics_amplitude_0": A[0][0], "Freq1_harmonics_amplitude_1": A[0][1], "Freq1_harmonics_amplitude_2": A[0][2], "Freq1_harmonics_amplitude_3": A[0][3], "Freq2_harmonics_amplitude_0": A[1][0], "Freq2_harmonics_amplitude_1": A[1][1], "Freq2_harmonics_amplitude_2": A[1][2], "Freq2_harmonics_amplitude_3": A[1][3], "Freq3_harmonics_amplitude_0": A[2][0], "Freq3_harmonics_amplitude_1": A[2][1], "Freq3_harmonics_amplitude_2": A[2][2], "Freq3_harmonics_amplitude_3": A[2][3], "Freq1_harmonics_rel_phase_0": sPH[0][0], "Freq1_harmonics_rel_phase_1": sPH[0][1], "Freq1_harmonics_rel_phase_2": sPH[0][2], "Freq1_harmonics_rel_phase_3": sPH[0][3], "Freq2_harmonics_rel_phase_0": sPH[1][0], "Freq2_harmonics_rel_phase_1": sPH[1][1], "Freq2_harmonics_rel_phase_2": sPH[1][2], "Freq2_harmonics_rel_phase_3": sPH[1][3], "Freq3_harmonics_rel_phase_0": sPH[2][0], "Freq3_harmonics_rel_phase_1": sPH[2][1], "Freq3_harmonics_rel_phase_2": sPH[2][2], "Freq3_harmonics_rel_phase_3": sPH[2][3]} return result